diff --git a/include/core/application.hpp b/include/core/application.hpp index 4d10acc7..7587fb7b 100644 --- a/include/core/application.hpp +++ b/include/core/application.hpp @@ -78,6 +78,7 @@ public: // Render bounds lookup (for click targeting / selection) bool getRenderBoundsForGuid(uint64_t guid, glm::vec3& outCenter, float& outRadius) const; bool getRenderFootZForGuid(uint64_t guid, float& outFootZ) const; + bool getRenderPositionForGuid(uint64_t guid, glm::vec3& outPos) const; // Character skin composite state (saved at spawn for re-compositing on equipment change) const std::string& getBodySkinPath() const { return bodySkinPath_; } diff --git a/include/rendering/character_renderer.hpp b/include/rendering/character_renderer.hpp index 7a01c0d7..f516b3a4 100644 --- a/include/rendering/character_renderer.hpp +++ b/include/rendering/character_renderer.hpp @@ -91,6 +91,7 @@ public: bool getInstanceModelName(uint32_t instanceId, std::string& modelName) const; bool getInstanceBounds(uint32_t instanceId, glm::vec3& outCenter, float& outRadius) const; bool getInstanceFootZ(uint32_t instanceId, float& outFootZ) const; + bool getInstancePosition(uint32_t instanceId, glm::vec3& outPos) const; /** Debug: Log all available animations for an instance */ void dumpAnimations(uint32_t instanceId) const; diff --git a/src/core/application.cpp b/src/core/application.cpp index ca528bb2..9f32c66b 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -4879,6 +4879,26 @@ bool Application::getRenderFootZForGuid(uint64_t guid, float& outFootZ) const { return renderer->getCharacterRenderer()->getInstanceFootZ(instanceId, outFootZ); } +bool Application::getRenderPositionForGuid(uint64_t guid, glm::vec3& outPos) const { + if (!renderer || !renderer->getCharacterRenderer()) return false; + uint32_t instanceId = 0; + + if (gameHandler && guid == gameHandler->getPlayerGuid()) { + instanceId = renderer->getCharacterInstanceId(); + } + if (instanceId == 0) { + auto pit = playerInstances_.find(guid); + if (pit != playerInstances_.end()) instanceId = pit->second; + } + if (instanceId == 0) { + auto it = creatureInstances_.find(guid); + if (it != creatureInstances_.end()) instanceId = it->second; + } + if (instanceId == 0) return false; + + return renderer->getCharacterRenderer()->getInstancePosition(instanceId, outPos); +} + pipeline::M2Model Application::loadCreatureM2Sync(const std::string& m2Path) { auto m2Data = assetManager->readFile(m2Path); if (m2Data.empty()) { diff --git a/src/rendering/character_renderer.cpp b/src/rendering/character_renderer.cpp index f69ae75c..59965ec8 100644 --- a/src/rendering/character_renderer.cpp +++ b/src/rendering/character_renderer.cpp @@ -3175,6 +3175,13 @@ bool CharacterRenderer::getInstanceFootZ(uint32_t instanceId, float& outFootZ) c return true; } +bool CharacterRenderer::getInstancePosition(uint32_t instanceId, glm::vec3& outPos) const { + auto it = instances.find(instanceId); + if (it == instances.end()) return false; + outPos = it->second.position; + return true; +} + void CharacterRenderer::detachWeapon(uint32_t charInstanceId, uint32_t attachmentId) { auto charIt = instances.find(charInstanceId); if (charIt == instances.end()) return; diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index bded7481..0eef9b83 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -522,11 +522,25 @@ void GameScreen::render(game::GameHandler& gameHandler) { if (gameHandler.hasTarget()) { auto target = gameHandler.getTarget(); if (target) { - targetGLPos = core::coords::canonicalToRender( - glm::vec3(target->getX(), target->getY(), target->getZ())); - float footZ = 0.0f; - if (core::Application::getInstance().getRenderFootZForGuid(target->getGuid(), footZ)) { - targetGLPos.z = footZ; + // Prefer the renderer's actual instance position so the selection + // circle tracks the rendered model (not a parallel entity-space + // interpolator that can drift from the visual position). + glm::vec3 instPos; + if (core::Application::getInstance().getRenderPositionForGuid(target->getGuid(), instPos)) { + targetGLPos = instPos; + // Override Z with foot position to sit the circle on the ground. + float footZ = 0.0f; + if (core::Application::getInstance().getRenderFootZForGuid(target->getGuid(), footZ)) { + targetGLPos.z = footZ; + } + } else { + // Fallback: entity game-logic position (no CharacterRenderer instance yet) + targetGLPos = core::coords::canonicalToRender( + glm::vec3(target->getX(), target->getY(), target->getZ())); + float footZ = 0.0f; + if (core::Application::getInstance().getRenderFootZForGuid(target->getGuid(), footZ)) { + targetGLPos.z = footZ; + } } renderer->setTargetPosition(&targetGLPos);