From 54246345bb8768d6cd21ffd3a00188bd526447a7 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Tue, 10 Mar 2026 08:35:36 -0700 Subject: [PATCH] game: fix NPCs not spawning on reconnect to same map On disconnect/reconnect to the same map, entityManager was not cleared and creatureInstances_ still held old entries from the previous session. When the server re-sent CREATE_OBJECT for the same GUIDs, the spawn callback's early-return guard (creatureInstances_.count(guid)) silently dropped every NPC re-spawn, leaving the world empty. Fixes: - disconnect() now calls entityManager.clear() to purge stale entities - WorldEntryCallback gains a bool isInitialEntry parameter (true on first login or reconnect, false on in-world teleport/flight landing) - Same-map optimization path skipped when isInitialEntry=true, so loadOnlineWorldTerrain runs its full cleanup and properly despawns old creature/player instances before the server refreshes them --- include/game/game_handler.hpp | 4 ++-- src/core/application.cpp | 9 ++++++--- src/game/game_handler.cpp | 8 +++++--- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index 68f2d878..4633249c 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -666,8 +666,8 @@ public: uint32_t getSkillCategory(uint32_t skillId) const; // World entry callback (online mode - triggered when entering world) - // Parameters: mapId, x, y, z (canonical WoW coordinates) - using WorldEntryCallback = std::function; + // Parameters: mapId, x, y, z (canonical WoW coords), isInitialEntry=true on first login or reconnect + using WorldEntryCallback = std::function; void setWorldEntryCallback(WorldEntryCallback cb) { worldEntryCallback_ = std::move(cb); } // Unstuck callback (resets player Z to floor height) diff --git a/src/core/application.cpp b/src/core/application.cpp index 45ac82b9..5fe635f6 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -1692,13 +1692,16 @@ void Application::setupUICallbacks() { }); // World entry callback (online mode) - load terrain when entering world - gameHandler->setWorldEntryCallback([this](uint32_t mapId, float x, float y, float z) { - LOG_INFO("Online world entry: mapId=", mapId, " pos=(", x, ", ", y, ", ", z, ")"); + gameHandler->setWorldEntryCallback([this](uint32_t mapId, float x, float y, float z, bool isInitialEntry) { + LOG_INFO("Online world entry: mapId=", mapId, " pos=(", x, ", ", y, ", ", z, ")" + " initial=", isInitialEntry); // Same-map teleport (taxi landing, GM teleport on same continent): // just update position, let terrain streamer handle tile loading incrementally. // A full reload is only needed on first entry or map change. - if (mapId == loadedMapId_ && renderer && renderer->getTerrainManager()) { + // Exception: on reconnect to the same map (isInitialEntry=true), all online entities + // are stale and must be cleared so the server's fresh CREATE_OBJECTs re-spawn them. + if (mapId == loadedMapId_ && renderer && renderer->getTerrainManager() && !isInitialEntry) { LOG_INFO("Same-map teleport (map ", mapId, "), skipping full world reload"); glm::vec3 canonical = core::coords::serverToCanonical(glm::vec3(x, y, z)); glm::vec3 renderPos = core::coords::canonicalToRender(canonical); diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 447c8a71..e0838a3c 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -499,6 +499,8 @@ void GameHandler::disconnect() { wardenModuleSize_ = 0; wardenModuleData_.clear(); wardenLoadedModule_.reset(); + // Clear entity state so reconnect sees fresh CREATE_OBJECT for all visible objects. + entityManager.clear(); setState(WorldState::DISCONNECTED); LOG_INFO("Disconnected from world server"); } @@ -6074,7 +6076,7 @@ void GameHandler::handleLoginVerifyWorld(network::Packet& packet) { // Notify application to load terrain for this map/position (online mode) if (worldEntryCallback_) { - worldEntryCallback_(data.mapId, data.x, data.y, data.z); + worldEntryCallback_(data.mapId, data.x, data.y, data.z, initialWorldEntry); } // Auto-join default chat channels @@ -15582,7 +15584,7 @@ void GameHandler::handleTeleportAck(network::Packet& packet) { // Notify application of teleport — the callback decides whether to do // a full world reload (map change) or just update position (same map). if (worldEntryCallback_) { - worldEntryCallback_(currentMapId_, serverX, serverY, serverZ); + worldEntryCallback_(currentMapId_, serverX, serverY, serverZ, false); } } @@ -15689,7 +15691,7 @@ void GameHandler::handleNewWorld(network::Packet& packet) { // Reload terrain at new position if (worldEntryCallback_) { - worldEntryCallback_(mapId, serverX, serverY, serverZ); + worldEntryCallback_(mapId, serverX, serverY, serverZ, false); } }