From 5e0d62c2a459742d320b9f544c574890ea06a669 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Mon, 9 Feb 2026 21:59:00 -0800 Subject: [PATCH] Fix NPC spawning at initial player position NPCs were not spawning when the player first entered the world because spawnNpcs() was defined but never called. Added call to spawnNpcs() in Application::update() when in IN_GAME state. The function has a guard (npcsSpawned flag) so it only runs once. NPCs now appear immediately at spawn instead of requiring the player to walk away first. Added logging to help debug spawn preconditions. --- src/core/application.cpp | 22 +++++++++++++++++++--- src/game/game_handler.cpp | 20 +++++++++++++++++++- src/ui/game_screen.cpp | 13 +++++++++++++ 3 files changed, 51 insertions(+), 4 deletions(-) diff --git a/src/core/application.cpp b/src/core/application.cpp index 71832a88..d8ca5658 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -392,6 +392,9 @@ void Application::update(float deltaTime) { if (world) { world->update(deltaTime); } + // Spawn NPCs once when entering world + spawnNpcs(); + // Process deferred online creature spawns (throttled) processCreatureSpawnQueue(); processGameObjectSpawnQueue(); @@ -1404,16 +1407,29 @@ void Application::loadEquippedWeapons() { void Application::spawnNpcs() { if (npcsSpawned) return; - if (!assetManager || !assetManager->isInitialized()) return; - if (!renderer || !renderer->getCharacterRenderer() || !renderer->getCamera()) return; - if (!gameHandler) return; + LOG_INFO("spawnNpcs: checking preconditions..."); + if (!assetManager || !assetManager->isInitialized()) { + LOG_INFO("spawnNpcs: assetManager not ready"); + return; + } + if (!renderer || !renderer->getCharacterRenderer() || !renderer->getCamera()) { + LOG_INFO("spawnNpcs: renderer not ready"); + return; + } + if (!gameHandler) { + LOG_INFO("spawnNpcs: gameHandler not ready"); + return; + } + LOG_INFO("spawnNpcs: spawning NPCs..."); if (npcManager) { npcManager->clear(renderer->getCharacterRenderer(), &gameHandler->getEntityManager()); } npcManager = std::make_unique(); glm::vec3 playerSpawnGL = renderer->getCharacterPosition(); glm::vec3 playerCanonical = core::coords::renderToCanonical(playerSpawnGL); + LOG_INFO("spawnNpcs: player position GL=(", playerSpawnGL.x, ",", playerSpawnGL.y, ",", playerSpawnGL.z, + ") canonical=(", playerCanonical.x, ",", playerCanonical.y, ",", playerCanonical.z, ")"); std::string mapName = "Azeroth"; if (auto* minimap = renderer->getMinimap()) { mapName = minimap->getMapName(); diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 8d1d448b..e439c30e 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -4627,6 +4627,17 @@ void GameHandler::handleTrainerList(network::Packet& packet) { trainerWindowOpen_ = true; gossipWindowOpen = false; + // Debug: log known spells + LOG_INFO("Known spells count: ", knownSpells.size()); + if (knownSpells.size() <= 20) { + std::string spellList; + for (uint32_t id : knownSpells) { + if (!spellList.empty()) spellList += ", "; + spellList += std::to_string(id); + } + LOG_INFO("Known spells: ", spellList); + } + // Ensure caches are populated loadSpellNameCache(); loadSkillLineDbc(); @@ -4635,12 +4646,19 @@ void GameHandler::handleTrainerList(network::Packet& packet) { } void GameHandler::trainSpell(uint32_t spellId) { - if (state != WorldState::IN_WORLD || !socket) return; + LOG_INFO("trainSpell called: spellId=", spellId, " state=", (int)state, " socket=", (socket ? "yes" : "no")); + if (state != WorldState::IN_WORLD || !socket) { + LOG_WARNING("trainSpell: Not in world or no socket connection"); + return; + } + LOG_INFO("Sending CMSG_TRAINER_BUY_SPELL: guid=", currentTrainerList_.trainerGuid, + " trainerId=", currentTrainerList_.trainerType, " spellId=", spellId); auto packet = TrainerBuySpellPacket::build( currentTrainerList_.trainerGuid, currentTrainerList_.trainerType, spellId); socket->send(packet); + LOG_INFO("CMSG_TRAINER_BUY_SPELL sent"); } void GameHandler::closeTrainer() { diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index d2c381c8..97245b36 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -3689,6 +3689,19 @@ void GameScreen::renderTrainerWindow(game::GameHandler& gameHandler) { bool canTrain = !alreadyKnown && spell->state == 1 && prereqsMet && levelMet && (money >= spell->spellCost); + + // Debug logging for first spell to see why buttons are disabled + static bool logged = false; + if (!logged) { + LOG_INFO("Trainer button debug: spellId=", spell->spellId, + " alreadyKnown=", alreadyKnown, " state=", (int)spell->state, + " prereqsMet=", prereqsMet, " levelMet=", levelMet, + " canAfford=", (money >= spell->spellCost), + " money=", money, " cost=", spell->spellCost, + " canTrain=", canTrain); + logged = true; + } + if (!canTrain) ImGui::BeginDisabled(); if (ImGui::SmallButton("Train")) { gameHandler.trainSpell(spell->spellId);