From 46ec6f13de515daccdf0b5ea224aeee2bff78fee Mon Sep 17 00:00:00 2001 From: karlo <55144452+kiletic@users.noreply.github.com> Date: Wed, 5 Aug 2026 16:27:09 +0200 Subject: [PATCH] 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. --- src/client/creature.cpp | 26 ++++++------- src/client/tile.cpp | 85 ++++++++++++++++++++++++----------------- 2 files changed, 61 insertions(+), 50 deletions(-) diff --git a/src/client/creature.cpp b/src/client/creature.cpp index c1a3a97ab..47caf4962 100644 --- a/src/client/creature.cpp +++ b/src/client/creature.cpp @@ -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(); diff --git a/src/client/tile.cpp b/src/client/tile.cpp index eeb43f8bb..9366a5ae7 100644 --- a/src/client/tile.cpp +++ b/src/client/tile.cpp @@ -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 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);