fix: incorrect draw order of creature when walking diagonally (#1793)

Bug Fixes:
- Improved creature positioning while walking across tiles, including diagonal movement.
- Corrected rendering order so walking creatures appear properly relative to non-walkable ground objects.
- Improved display of stationary creatures and local-player positioning.
- Removed visual inconsistencies caused by outdated virtual-tile rendering behavior.
This commit is contained in:
karlo 2026-08-05 16:27:09 +02:00 committed by GitHub
parent 5a33b4a166
commit 46ec6f13de
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 61 additions and 50 deletions

View file

@ -742,6 +742,18 @@ void Creature::updateWalkingTile()
g_gameConfig.getSpriteSize() + (m_walkOffset.y - displacementY),
g_gameConfig.getSpriteSize(), g_gameConfig.getSpriteSize());
for (int xi = -1; xi <= 1 && !newWalkingTile; ++xi) {
for (int yi = -1; yi <= 1 && !newWalkingTile; ++yi) {
Rect virtualTileRect((xi + 1) * g_gameConfig.getSpriteSize(), (yi + 1) * g_gameConfig.getSpriteSize(), g_gameConfig.getSpriteSize(), g_gameConfig.getSpriteSize());
// only render creatures where bottom right is inside tile rect
if (virtualTileRect.contains(virtualCreatureRect.bottomRight()))
newWalkingTile = g_map.getOrCreateTile(getPosition().translated(xi, yi, 0));
}
}
// NW - for the effect of going behind the object west of it, walking creature will be drawn in front of the object for the half of the way, and after behind
// SE - for the effect of going in front the object south of it, walking creature will be drawn behind the object for the half of the way, and after in front
if (m_walkedPixels < g_gameConfig.getSpriteSize() / 2) {
if (m_direction == Otc::Direction::NorthWest)
newWalkingTile = m_walkingTile ? m_walkingTile : getTile();
@ -749,20 +761,6 @@ void Creature::updateWalkingTile()
newWalkingTile = g_map.getTile(getPosition().translated(-1, -1, 0));
}
for (int xi = -1; xi <= 1 && !newWalkingTile; ++xi) {
for (int yi = -1; yi <= 1 && !newWalkingTile; ++yi) {
Rect virtualTileRect((xi + 1) * g_gameConfig.getSpriteSize(), (yi + 1) * g_gameConfig.getSpriteSize(), g_gameConfig.getSpriteSize(), g_gameConfig.getSpriteSize());
// when creature is moving to the upper left tile, because of drawing order (we want creature to be behind the object to the left if its a tree for example)
if (m_direction == Otc::Direction::NorthWest && virtualTileRect.contains(virtualCreatureRect.topLeft())) {
newWalkingTile = g_map.getOrCreateTile(getPosition().translated(xi, yi, 0));
} else if (virtualTileRect.contains(virtualCreatureRect.bottomRight())) {
// only render creatures where bottom right is inside tile rect
newWalkingTile = g_map.getOrCreateTile(getPosition().translated(xi, yi, 0));
}
}
}
if (newWalkingTile == m_walkingTile) return;
const auto& self = static_self_cast<Creature>();

View file

@ -78,12 +78,37 @@ void Tile::draw(const MapPosInfo& mapRect, const Point& dest, const int flags, L
return;
}
// when walking diagonally over a tile that has a object on it like a tree the creature should be rendered behind it
// i.e. render creature first then the tree
std::vector<ThingPtr> skipped_non_walkable;
for (const auto& thing : m_things) {
if (!thing->isGround() && !thing->isGroundBorder() && !thing->isOnBottom())
break;
// delay drawing after NE/SW walking creature
if (thing->isNotWalkable()) {
skipped_non_walkable.push_back(thing);
continue;
}
drawThing(thing, dest, flags, drawElevation);
}
drawAttachedEffect(dest, dest, lightView, false);
if (hasCommonItem()) {
for (auto& item : std::ranges::reverse_view(m_things)) {
if (!item->isCommon()) continue;
drawThing(item, dest, flags, drawElevation);
}
}
// when walking diagonally over a tile that has a non-walkable object on it (for example - a tree) the creature should be drawn behind it
if (hasWalkingCreature()) {
g_drawPool.setDrawOrder(DrawOrder::THIRD);
for (const auto& creature : m_walkingCreatures) {
if (creature->getDirection() == Otc::Direction::NorthEast || creature->getDirection() == Otc::Direction::SouthWest) {
// if creature is stepping into this tile then draw it later
if (creature->getLastStepToPosition() == getPosition())
continue;
const auto& cDest = Point(
dest.x + ((creature->getPosition().x - m_position.x) * g_gameConfig.getSpriteSize() - creature->getDrawElevation()) * g_drawPool.getScaleFactor(),
dest.y + ((creature->getPosition().y - m_position.y) * g_gameConfig.getSpriteSize() - creature->getDrawElevation()) * g_drawPool.getScaleFactor()
@ -100,21 +125,8 @@ void Tile::draw(const MapPosInfo& mapRect, const Point& dest, const int flags, L
g_drawPool.resetDrawOrder();
}
for (const auto& thing : m_things) {
if (!thing->isGround() && !thing->isGroundBorder() && !thing->isOnBottom())
break;
for (const auto& thing : skipped_non_walkable)
drawThing(thing, dest, flags, drawElevation);
}
drawAttachedEffect(dest, dest, lightView, false);
if (hasCommonItem()) {
for (auto& item : std::ranges::reverse_view(m_things)) {
if (!item->isCommon()) continue;
drawThing(item, dest, flags, drawElevation);
}
}
// after we render 2x2 lying corpses, we must redraw previous creatures/ontop above them
if (m_tilesRedraw) {
@ -155,26 +167,6 @@ void Tile::drawCreature(const MapPosInfo& mapRect, const Point& dest, const int
if (!forceDraw && !m_drawTopAndCreature)
return;
g_drawPool.setDrawOrder(DrawOrder::THIRD);
for (const auto& creature : m_walkingCreatures) {
// already drawn by this point
if (creature->getDirection() == Otc::Direction::NorthEast || creature->getDirection() == Otc::Direction::SouthWest)
continue;
const auto& cDest = Point(
dest.x + ((creature->getPosition().x - m_position.x) * g_gameConfig.getSpriteSize() - creature->getDrawElevation()) * g_drawPool.getScaleFactor(),
dest.y + ((creature->getPosition().y - m_position.y) * g_gameConfig.getSpriteSize() - creature->getDrawElevation()) * g_drawPool.getScaleFactor()
);
if (flags == Otc::DrawLights)
creature->drawLight(cDest, lightView);
else {
creature->draw(cDest, flags & Otc::DrawThings);
creature->drawInformation(mapRect, cDest + creature->getDrawElevation() * g_drawPool.getScaleFactor(), flags);
}
}
g_drawPool.resetDrawOrder();
bool localPlayerDrawed = false;
if (hasCreatures()) {
for (const auto& thing : m_things) {
@ -190,6 +182,27 @@ void Tile::drawCreature(const MapPosInfo& mapRect, const Point& dest, const int
}
}
g_drawPool.setDrawOrder(DrawOrder::THIRD);
for (const auto& creature : m_walkingCreatures) {
// already drawn by this point
if (creature->getDirection() == Otc::Direction::NorthEast || creature->getDirection() == Otc::Direction::SouthWest)
if (creature->getLastStepToPosition() != getPosition())
continue;
const auto& cDest = Point(
dest.x + ((creature->getPosition().x - m_position.x) * g_gameConfig.getSpriteSize() - creature->getDrawElevation()) * g_drawPool.getScaleFactor(),
dest.y + ((creature->getPosition().y - m_position.y) * g_gameConfig.getSpriteSize() - creature->getDrawElevation()) * g_drawPool.getScaleFactor()
);
if (flags == Otc::DrawLights)
creature->drawLight(cDest, lightView);
else {
creature->draw(cDest, flags & Otc::DrawThings);
creature->drawInformation(mapRect, cDest + creature->getDrawElevation() * g_drawPool.getScaleFactor(), flags);
}
}
g_drawPool.resetDrawOrder();
// draw the local character if he is on a virtual tile, that is, his visual position is not the same as the server.
if (!localPlayerDrawed && g_game.getLocalPlayer() && !g_game.getLocalPlayer()->isWalking() && g_game.getLocalPlayer()->getPosition() == m_position) {
drawThing(g_game.getLocalPlayer(), dest, flags, drawElevation, lightView);