mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
Fix NPC movement animation sync and minimap marker behavior
- Use interpolated moveInstanceTo() for normal server NPC position deltas so walk/run animations play instead of visual sliding - Keep hard snaps only for dead units and large corrections/teleports - Add per-creature render position cache to drive interpolation safely across frames and clear it on spawn/despawn/reset - Keep minimap controls visible regardless of quest-status availability - Correct minimap NPC/quest marker projection mapping to match minimap shader transform - Add optional nearby NPC minimap dots setting (default OFF), exposed in Gameplay > Interface and persisted in settings
This commit is contained in:
parent
504d5d2b15
commit
ebaeea43cb
4 changed files with 81 additions and 25 deletions
|
|
@ -176,6 +176,7 @@ private:
|
|||
std::unordered_map<uint32_t, FacialHairGeosets> facialHairGeosetMap_;
|
||||
std::unordered_map<uint64_t, uint32_t> creatureInstances_; // guid → render instanceId
|
||||
std::unordered_map<uint64_t, uint32_t> creatureModelIds_; // guid → loaded modelId
|
||||
std::unordered_map<uint64_t, glm::vec3> creatureRenderPosCache_; // guid -> last synced render position
|
||||
std::unordered_set<uint64_t> deadCreatureGuids_; // GUIDs that should spawn in corpse/death pose
|
||||
std::unordered_map<uint32_t, uint32_t> displayIdModelCache_; // displayId → modelId (model caching)
|
||||
uint32_t nextCreatureModelId_ = 5000; // Model IDs for online creatures
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@ private:
|
|||
int pendingUiOpacity = 65;
|
||||
bool pendingMinimapRotate = false;
|
||||
bool pendingMinimapSquare = false;
|
||||
bool pendingMinimapNpcDots = false;
|
||||
bool pendingSeparateBags = true;
|
||||
bool pendingAutoLoot = false;
|
||||
bool pendingUseOriginalSoundtrack = true;
|
||||
|
|
@ -105,6 +106,7 @@ private:
|
|||
float uiOpacity_ = 0.65f;
|
||||
bool minimapRotate_ = false;
|
||||
bool minimapSquare_ = false;
|
||||
bool minimapNpcDots_ = false;
|
||||
bool minimapSettingsApplied_ = false;
|
||||
bool volumeSettingsApplied_ = false; // True once saved volume settings applied to audio managers
|
||||
|
||||
|
|
|
|||
|
|
@ -494,6 +494,7 @@ void Application::reloadExpansionData() {
|
|||
displayDataMap_.clear();
|
||||
humanoidExtraMap_.clear();
|
||||
creatureModelIds_.clear();
|
||||
creatureRenderPosCache_.clear();
|
||||
buildCreatureDisplayLookups();
|
||||
}
|
||||
|
||||
|
|
@ -1001,7 +1002,27 @@ void Application::update(float deltaTime) {
|
|||
}
|
||||
}
|
||||
|
||||
charRenderer->setInstancePosition(instanceId, renderPos);
|
||||
auto posIt = creatureRenderPosCache_.find(guid);
|
||||
if (posIt == creatureRenderPosCache_.end()) {
|
||||
charRenderer->setInstancePosition(instanceId, renderPos);
|
||||
creatureRenderPosCache_[guid] = renderPos;
|
||||
} else {
|
||||
const glm::vec3 prevPos = posIt->second;
|
||||
const glm::vec2 delta2(renderPos.x - prevPos.x, renderPos.y - prevPos.y);
|
||||
float planarDist = glm::length(delta2);
|
||||
float dz = std::abs(renderPos.z - prevPos.z);
|
||||
|
||||
const bool deadOrCorpse = unit->getHealth() == 0;
|
||||
const bool largeCorrection = (planarDist > 6.0f) || (dz > 3.0f);
|
||||
if (deadOrCorpse || largeCorrection) {
|
||||
charRenderer->setInstancePosition(instanceId, renderPos);
|
||||
} else if (planarDist > 0.03f || dz > 0.08f) {
|
||||
// Use movement interpolation so step/run animation can play.
|
||||
float duration = std::clamp(planarDist / 5.5f, 0.05f, 0.22f);
|
||||
charRenderer->moveInstanceTo(instanceId, renderPos, duration);
|
||||
}
|
||||
posIt->second = renderPos;
|
||||
}
|
||||
float renderYaw = entity->getOrientation() + glm::radians(90.0f);
|
||||
charRenderer->setInstanceRotation(instanceId, glm::vec3(0.0f, 0.0f, renderYaw));
|
||||
}
|
||||
|
|
@ -4135,6 +4156,7 @@ void Application::spawnOnlineCreature(uint64_t guid, uint32_t displayId, float x
|
|||
// Track instance
|
||||
creatureInstances_[guid] = instanceId;
|
||||
creatureModelIds_[guid] = modelId;
|
||||
creatureRenderPosCache_[guid] = renderPos;
|
||||
LOG_DEBUG("Spawned creature: guid=0x", std::hex, guid, std::dec,
|
||||
" displayId=", displayId, " at (", x, ", ", y, ", ", z, ")");
|
||||
}
|
||||
|
|
@ -5307,6 +5329,7 @@ void Application::despawnOnlineCreature(uint64_t guid) {
|
|||
|
||||
creatureInstances_.erase(it);
|
||||
creatureModelIds_.erase(guid);
|
||||
creatureRenderPosCache_.erase(guid);
|
||||
|
||||
LOG_DEBUG("Despawned creature: guid=0x", std::hex, guid, std::dec);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5755,6 +5755,7 @@ void GameScreen::renderSettingsWindow() {
|
|||
pendingUiOpacity = static_cast<int>(std::lround(uiOpacity_ * 100.0f));
|
||||
pendingMinimapRotate = minimapRotate_;
|
||||
pendingMinimapSquare = minimapSquare_;
|
||||
pendingMinimapNpcDots = minimapNpcDots_;
|
||||
if (renderer) {
|
||||
if (auto* minimap = renderer->getMinimap()) {
|
||||
minimap->setRotateWithCamera(minimapRotate_);
|
||||
|
|
@ -6035,6 +6036,10 @@ void GameScreen::renderSettingsWindow() {
|
|||
}
|
||||
saveSettings();
|
||||
}
|
||||
if (ImGui::Checkbox("Show Nearby NPC Dots", &pendingMinimapNpcDots)) {
|
||||
minimapNpcDots_ = pendingMinimapNpcDots;
|
||||
saveSettings();
|
||||
}
|
||||
// Zoom controls
|
||||
ImGui::Text("Minimap Zoom:");
|
||||
ImGui::SameLine();
|
||||
|
|
@ -6083,11 +6088,13 @@ void GameScreen::renderSettingsWindow() {
|
|||
pendingUiOpacity = 65;
|
||||
pendingMinimapRotate = false;
|
||||
pendingMinimapSquare = false;
|
||||
pendingMinimapNpcDots = false;
|
||||
pendingSeparateBags = true;
|
||||
inventoryScreen.setSeparateBags(true);
|
||||
uiOpacity_ = 0.65f;
|
||||
minimapRotate_ = false;
|
||||
minimapSquare_ = false;
|
||||
minimapNpcDots_ = false;
|
||||
if (renderer) {
|
||||
if (auto* cameraController = renderer->getCameraController()) {
|
||||
cameraController->setMouseSensitivity(pendingMouseSensitivity);
|
||||
|
|
@ -6287,10 +6294,48 @@ void GameScreen::renderMinimapMarkers(game::GameHandler& gameHandler) {
|
|||
sinB = std::sin(bearing);
|
||||
}
|
||||
|
||||
if (statuses.empty()) return;
|
||||
|
||||
auto* drawList = ImGui::GetForegroundDrawList();
|
||||
|
||||
auto projectToMinimap = [&](const glm::vec3& worldRenderPos, float& sx, float& sy) -> bool {
|
||||
float dx = worldRenderPos.x - playerRender.x;
|
||||
float dy = worldRenderPos.y - playerRender.y;
|
||||
|
||||
// Match minimap shader transform exactly.
|
||||
// Render axes: +X=west, +Y=north. Minimap screen axes: +X=right(east), +Y=down(south).
|
||||
float rx = -dx * cosB + dy * sinB;
|
||||
float ry = -dx * sinB - dy * cosB;
|
||||
|
||||
// Scale to minimap pixels
|
||||
float px = rx / viewRadius * mapRadius;
|
||||
float py = ry / viewRadius * mapRadius;
|
||||
|
||||
float distFromCenter = std::sqrt(px * px + py * py);
|
||||
if (distFromCenter > mapRadius - 3.0f) {
|
||||
return false;
|
||||
}
|
||||
|
||||
sx = centerX + px;
|
||||
sy = centerY + py;
|
||||
return true;
|
||||
};
|
||||
|
||||
// Optional base nearby NPC dots (independent of quest status packets).
|
||||
if (minimapNpcDots_) {
|
||||
for (const auto& [guid, entity] : gameHandler.getEntityManager().getEntities()) {
|
||||
if (!entity || entity->getType() != game::ObjectType::UNIT) continue;
|
||||
|
||||
auto unit = std::static_pointer_cast<game::Unit>(entity);
|
||||
if (!unit || unit->getHealth() == 0) continue;
|
||||
|
||||
glm::vec3 npcRender = core::coords::canonicalToRender(glm::vec3(entity->getX(), entity->getY(), entity->getZ()));
|
||||
float sx = 0.0f, sy = 0.0f;
|
||||
if (!projectToMinimap(npcRender, sx, sy)) continue;
|
||||
|
||||
ImU32 baseDot = unit->isHostile() ? IM_COL32(220, 70, 70, 220) : IM_COL32(245, 245, 245, 210);
|
||||
drawList->AddCircleFilled(ImVec2(sx, sy), 1.0f, baseDot);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& [guid, status] : statuses) {
|
||||
ImU32 dotColor;
|
||||
const char* marker = nullptr;
|
||||
|
|
@ -6317,28 +6362,8 @@ void GameScreen::renderMinimapMarkers(game::GameHandler& gameHandler) {
|
|||
glm::vec3 canonical(entity->getX(), entity->getY(), entity->getZ());
|
||||
glm::vec3 npcRender = core::coords::canonicalToRender(canonical);
|
||||
|
||||
// Offset from player in render coords
|
||||
float dx = npcRender.x - playerRender.x;
|
||||
float dy = npcRender.y - playerRender.y;
|
||||
|
||||
// Rotate by camera bearing (minimap north-up rotation)
|
||||
float rx = dx * cosB - dy * sinB;
|
||||
float ry = dx * sinB + dy * cosB;
|
||||
|
||||
// Scale to minimap pixels
|
||||
float px = rx / viewRadius * mapRadius;
|
||||
float py = -ry / viewRadius * mapRadius; // screen Y is inverted
|
||||
|
||||
// Clamp to circle
|
||||
float distFromCenter = std::sqrt(px * px + py * py);
|
||||
if (distFromCenter > mapRadius - 4.0f) {
|
||||
float scale = (mapRadius - 4.0f) / distFromCenter;
|
||||
px *= scale;
|
||||
py *= scale;
|
||||
}
|
||||
|
||||
float sx = centerX + px;
|
||||
float sy = centerY + py;
|
||||
float sx = 0.0f, sy = 0.0f;
|
||||
if (!projectToMinimap(npcRender, sx, sy)) continue;
|
||||
|
||||
// Draw dot with marker text
|
||||
drawList->AddCircleFilled(ImVec2(sx, sy), 5.0f, dotColor);
|
||||
|
|
@ -6707,6 +6732,7 @@ void GameScreen::saveSettings() {
|
|||
out << "ui_opacity=" << pendingUiOpacity << "\n";
|
||||
out << "minimap_rotate=" << (pendingMinimapRotate ? 1 : 0) << "\n";
|
||||
out << "minimap_square=" << (pendingMinimapSquare ? 1 : 0) << "\n";
|
||||
out << "minimap_npc_dots=" << (pendingMinimapNpcDots ? 1 : 0) << "\n";
|
||||
out << "separate_bags=" << (pendingSeparateBags ? 1 : 0) << "\n";
|
||||
|
||||
// Audio
|
||||
|
|
@ -6772,6 +6798,10 @@ void GameScreen::loadSettings() {
|
|||
int v = std::stoi(val);
|
||||
minimapSquare_ = (v != 0);
|
||||
pendingMinimapSquare = minimapSquare_;
|
||||
} else if (key == "minimap_npc_dots") {
|
||||
int v = std::stoi(val);
|
||||
minimapNpcDots_ = (v != 0);
|
||||
pendingMinimapNpcDots = minimapNpcDots_;
|
||||
} else if (key == "separate_bags") {
|
||||
pendingSeparateBags = (std::stoi(val) != 0);
|
||||
inventoryScreen.setSeparateBags(pendingSeparateBags);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue