From d8002955a3ed68a1f1426939473d49916f26db91 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Mon, 9 Feb 2026 18:04:20 -0800 Subject: [PATCH] Add debug logging for GameObject spawns to diagnose duplicate cathedral Added detailed logging in spawnOnlineGameObject() to help identify duplicate game object spawns. Logs displayId, guid, model path, and position for both new spawns and position updates. This will help diagnose the floating cathedral model issue in Stormwind by showing which GUIDs are being spawned and their coordinates. --- list_mpq | Bin 0 -> 16704 bytes list_mpq.cpp | 40 ++++ src/audio/npc_voice_manager.cpp.old | 299 ++++++++++++++++++++++++++++ src/core/application.cpp | 6 + test_ambient_audio.sh | 36 ++++ test_splash_sounds.sh | 25 +++ 6 files changed, 406 insertions(+) create mode 100755 list_mpq create mode 100644 list_mpq.cpp create mode 100644 src/audio/npc_voice_manager.cpp.old create mode 100755 test_ambient_audio.sh create mode 100755 test_splash_sounds.sh diff --git a/list_mpq b/list_mpq new file mode 100755 index 0000000000000000000000000000000000000000..22ce4a6354315a0bfcfe39988cde78932ed9c037 GIT binary patch literal 16704 zcmeHOdvIJ;89$pgZ3?AHp_S4?ds`{fQnJmfwFL{Cq?>NAFPd~LAl%;U-Xs_H5q9s= z1ep>^qr?!WR32ppwJL&+sMRS_AJjm#fTK*SGsukpR7QqoKuraiD$@1$opZj;&7ECd z;~$Q*C$slE-|u^z?|%1ra=&v|cw7EF1(>tIKD z`Jjyt^EN$RvEy(+=aZ>EoML6%=~&&cG9IgrJIQQsb?=(h)vFtVnN+Y&hCp!{_|f6p zuz9m(hDn`}sj8u`;9 z=J{FZk&ep8ePS`J2%|>!$L$vrOA|Aj=kA}`EWrYs#c*(b0sKW^h488(`YlEDhl}7} zDT4p72u}NH;OB350w@&c^+oVYfE)Pv+Z6x|KM4L7{0jN6M?gyo=syl@wwNz=HYy?I zi`q0$!b2g+3dV)m+P=w**=f7W$+&j9eN%Hhm9*Qfj<_vMvn!EGniKr62CkZ6lYnh!+ z(uzBG*aG5=n@T6>np~^6Saih-VpkIzRlN-xTbr79>@e$t4Z-?4;bB$ZtP3{ed-W6D zy5K50VpDKeEyvXs0G%c&_35t+$W%$U;uzC)SA$>EnTK$>P7{vC+~xaltDP!(_4yEh zM^nql{~X6TkM1ISGKGTEqbwKw`${9DlZV0br z5OlxxGaY>-Hs20-aK7h~@Y^1o?x)-idT@T^lI*YtSNBt8G3vqfD}@w1@4?Ses&aeW zgX4gfS}6mi43siZ%0MXt|I-q;IH=YR zDsm@PUf7dNs|i*d7^J_X_=EAu{x;J2Nu8ZW_!^edla0cPfVL9xJ~IdV!l@ zn;)O-$96$To$auTeC~~%IDSJ0(Wi2UOtMK zp~%oN^!`pE4n243S_~H%dNwjVNT!itDIWU6sXC31l=o^IfR_L~EKjd-Y}saD6z3~) z_`qQq!-=0_XmNx#G(anC2F3$>M%{J{+nNvHHXXnV?@kIpJ41{Z?|WJ{QhwP)5c>hD zAZ|qvYVXQ+pU2S*hpJwmj*;HRdv0WCa8&NqpbTx~f#Z^UlE9yEfZcMC!RJXd@H7#T zjljWE(gEe_F-%KoR~F_&m0nehGx9OstqlI$ z4fH7cBjwQv>EKSv)l;ewQa-KkxW!USKfj8!#CU#bL9ix5~a0s1KD6QCPGA2^-M{T!6Otx^lzu}uVes{#vVPOsRD*&5+g zL$Cw-myyEy>GHF~rRdT(W&{5B5Gva;|y$mX42WzUGk=w7H7px7tkf z>-s1)+Z19{Yv|n&^of_+g^Gv+IzYw`>JG;}uJyP!i^pH6an{3kD!G3MS3>wUDEW}~ zhYwMb=eRI!ll($0|EK18KeYS*8sWNbzrz|m*$ipARnwiC_GsF#>0V6_XzKU(etbnp zX>4e2zS3B>y(61+vqo)wus&E*y*ew|y4`gR!J3BU8h&q&bb3q`r{#x8uO=NYs-?VF zC)Ciw3qT9$)A1}5Ria1NGir5VX#9W=uh%$VZ>-;nP5^bdJxvPHr>~$Y>wx%};Om3+ z??i{jzDOMfLGz}Qx#%F(Wh`B8FW|j;rBy- zHueib*-%0t+c$wP(S0vD0{nc8uw2)-%KxQ6;VAhjyGZc)$~MPIzihVP`y%5nNk7a@ z(4Qer@*mRYkNn>d#BUbS|Af?^u;<=3!pm@%_FZ>7fK#0Q{KSg**+u#V>cmHi=zpyU z{v>cZE+WJcD*CL%=ZoH^$@p^`5Bc+dMyU$jNjZf!7ys&T8sFs zdY0k*Zz4!sWN>cZ>c)|@m~cz3qMX1uaD#PzJ7h?nVyDirN^Gl237 t@6!L4IDWqW%mBu*OGCYRS)X|P9AO!c%REj03fafjC=HsAdz+6H{{d3D!~p;R literal 0 HcmV?d00001 diff --git a/list_mpq.cpp b/list_mpq.cpp new file mode 100644 index 00000000..20d81cf5 --- /dev/null +++ b/list_mpq.cpp @@ -0,0 +1,40 @@ +#include +#include +#include + +int main(int argc, char** argv) { + if (argc < 2) { + std::cerr << "Usage: " << argv[0] << " [search_pattern]\n"; + return 1; + } + + HANDLE hMpq; + if (!SFileOpenArchive(argv[1], 0, 0, &hMpq)) { + std::cerr << "Failed to open MPQ: " << argv[1] << "\n"; + return 1; + } + + const char* pattern = argc > 2 ? argv[2] : "*"; + + SFILE_FIND_DATA findData; + HANDLE hFind = SFileFindFirstFile(hMpq, pattern, &findData, nullptr); + + if (hFind == nullptr) { + std::cout << "No files found matching: " << pattern << "\n"; + } else { + int count = 0; + do { + std::cout << findData.cFileName << " (" << findData.dwFileSize << " bytes)\n"; + count++; + if (count > 50) { + std::cout << "... (showing first 50 matches)\n"; + break; + } + } while (SFileFindNextFile(hFind, &findData)); + + SFileFindClose(hFind); + } + + SFileCloseArchive(hMpq); + return 0; +} diff --git a/src/audio/npc_voice_manager.cpp.old b/src/audio/npc_voice_manager.cpp.old new file mode 100644 index 00000000..f8b009e9 --- /dev/null +++ b/src/audio/npc_voice_manager.cpp.old @@ -0,0 +1,299 @@ +#include "audio/npc_voice_manager.hpp" +#include "audio/audio_engine.hpp" +#include "pipeline/asset_manager.hpp" +#include "core/logger.hpp" +#include + +namespace wowee { +namespace audio { + +NpcVoiceManager::NpcVoiceManager() : rng_(std::random_device{}()) {} + +NpcVoiceManager::~NpcVoiceManager() { + shutdown(); +} + +bool NpcVoiceManager::initialize(pipeline::AssetManager* assets) { + assetManager_ = assets; + if (!assetManager_) { + LOG_WARNING("NPC voice manager: no asset manager"); + return false; + } + + // Files are .WAV not .OGG in WotLK 3.3.5a! + LOG_INFO("=== Probing for NPC voice files (.wav format) ==="); + std::vector testPaths = { + "Sound\\Creature\\HumanMaleStandardNPC\\HumanMaleStandardNPCGreetings01.wav", + "Sound\\Creature\\HumanFemaleStandardNPC\\HumanFemaleStandardNPCGreeting01.wav", + "Sound\\Creature\\DwarfMaleStandardNPC\\DwarfMaleStandardNPCGreeting01.wav", + "Sound\\Creature\\OrcMaleStandardNPC\\OrcMaleStandardNPCGreeting01.wav", + }; + for (const auto& path : testPaths) { + bool exists = assetManager_->fileExists(path); + LOG_INFO(" ", path, ": ", (exists ? "EXISTS" : "NOT FOUND")); + } + LOG_INFO("=== Probing for tavern music files ==="); + std::vector musicPaths = { + "Sound\\Music\\GlueScreenMusic\\tavern_01.mp3", + "Sound\\Music\\GlueScreenMusic\\BC_Alehouse.mp3", + "Sound\\Music\\ZoneMusic\\Tavern\\tavernAlliance01.mp3", + }; + for (const auto& path : musicPaths) { + bool exists = assetManager_->fileExists(path); + LOG_INFO(" ", path, ": ", (exists ? "EXISTS" : "NOT FOUND")); + } + LOG_INFO("==================================="); + + loadVoiceSounds(); + + int totalSamples = 0; + for (const auto& [type, samples] : voiceLibrary_) { + totalSamples += samples.size(); + } + LOG_INFO("NPC voice manager initialized (", totalSamples, " voice clips)"); + return true; +} + +void NpcVoiceManager::shutdown() { + voiceLibrary_.clear(); + lastPlayTime_.clear(); + assetManager_ = nullptr; +} + +void NpcVoiceManager::loadVoiceSounds() { + if (!assetManager_) return; + + // WotLK 3.3.5a uses .WAV files, not .OGG! + // Files use "Greeting" (singular) not "Greetings" + + // Generic - mix of all races for variety + auto& genericVoices = voiceLibrary_[VoiceType::GENERIC]; + for (const auto& path : { + "Sound\\Creature\\HumanMaleStandardNPC\\HumanMaleStandardNPCGreetings01.wav", + "Sound\\Creature\\HumanFemaleStandardNPC\\HumanFemaleStandardNPCGreeting01.wav", + "Sound\\Creature\\DwarfMaleStandardNPC\\DwarfMaleStandardNPCGreeting01.wav", + "Sound\\Creature\\GnomeMaleStandardNPC\\GnomeMaleStandardNPCGreeting01.wav", + "Sound\\Creature\\NightElfMaleStandardNPC\\NightElfMaleStandardNPCGreeting01.wav", + "Sound\\Creature\\OrcMaleStandardNPC\\OrcMaleStandardNPCGreeting01.wav", + }) { + VoiceSample sample; + if (loadSound(path, sample)) genericVoices.push_back(std::move(sample)); + } + + // Human Male (uses "Greetings" plural) + auto& humanMale = voiceLibrary_[VoiceType::HUMAN_MALE]; + for (int i = 1; i <= 6; ++i) { + std::string path = "Sound\\Creature\\HumanMaleStandardNPC\\HumanMaleStandardNPCGreetings0" + std::to_string(i) + ".wav"; + VoiceSample sample; + if (loadSound(path, sample)) humanMale.push_back(std::move(sample)); + } + + // Human Female + auto& humanFemale = voiceLibrary_[VoiceType::HUMAN_FEMALE]; + for (int i = 1; i <= 5; ++i) { + std::string path = "Sound\\Creature\\HumanFemaleStandardNPC\\HumanFemaleStandardNPCGreeting0" + std::to_string(i) + ".wav"; + VoiceSample sample; + if (loadSound(path, sample)) humanFemale.push_back(std::move(sample)); + } + + // Dwarf Male + auto& dwarfMale = voiceLibrary_[VoiceType::DWARF_MALE]; + for (int i = 1; i <= 6; ++i) { + std::string path = "Sound\\Creature\\DwarfMaleStandardNPC\\DwarfMaleStandardNPCGreeting0" + std::to_string(i) + ".wav"; + VoiceSample sample; + if (loadSound(path, sample)) dwarfMale.push_back(std::move(sample)); + } + + // Gnome Male + auto& gnomeMale = voiceLibrary_[VoiceType::GNOME_MALE]; + for (int i = 1; i <= 6; ++i) { + std::string path = "Sound\\Creature\\GnomeMaleStandardNPC\\GnomeMaleStandardNPCGreeting0" + std::to_string(i) + ".wav"; + VoiceSample sample; + if (loadSound(path, sample)) gnomeMale.push_back(std::move(sample)); + } + + // Gnome Female + auto& gnomeFemale = voiceLibrary_[VoiceType::GNOME_FEMALE]; + for (int i = 1; i <= 6; ++i) { + std::string path = "Sound\\Creature\\GnomeFemaleStandardNPC\\GnomeFemaleStandardNPCGreeting0" + std::to_string(i) + ".wav"; + VoiceSample sample; + if (loadSound(path, sample)) gnomeFemale.push_back(std::move(sample)); + } + + // Night Elf Male + auto& nelfMale = voiceLibrary_[VoiceType::NIGHTELF_MALE]; + for (int i = 1; i <= 8; ++i) { + std::string path = "Sound\\Creature\\NightElfMaleStandardNPC\\NightElfMaleStandardNPCGreeting0" + std::to_string(i) + ".wav"; + VoiceSample sample; + if (loadSound(path, sample)) nelfMale.push_back(std::move(sample)); + } + + // Night Elf Female + auto& nelfFemale = voiceLibrary_[VoiceType::NIGHTELF_FEMALE]; + for (int i = 1; i <= 6; ++i) { + std::string path = "Sound\\Creature\\NightElfFemaleStandardNPC\\NightElfFemaleStandardNPCGreeting0" + std::to_string(i) + ".wav"; + VoiceSample sample; + if (loadSound(path, sample)) nelfFemale.push_back(std::move(sample)); + } + + // Orc Male + auto& orcMale = voiceLibrary_[VoiceType::ORC_MALE]; + for (int i = 1; i <= 5; ++i) { + std::string path = "Sound\\Creature\\OrcMaleStandardNPC\\OrcMaleStandardNPCGreeting0" + std::to_string(i) + ".wav"; + VoiceSample sample; + if (loadSound(path, sample)) orcMale.push_back(std::move(sample)); + } + + // Orc Female + auto& orcFemale = voiceLibrary_[VoiceType::ORC_FEMALE]; + for (int i = 1; i <= 6; ++i) { + std::string path = "Sound\\Creature\\OrcFemaleStandardNPC\\OrcFemaleStandardNPCGreeting0" + std::to_string(i) + ".wav"; + VoiceSample sample; + if (loadSound(path, sample)) orcFemale.push_back(std::move(sample)); + } + + // Tauren Male + auto& taurenMale = voiceLibrary_[VoiceType::TAUREN_MALE]; + for (int i = 1; i <= 5; ++i) { + std::string path = "Sound\\Creature\\TaurenMaleStandardNPC\\TaurenMaleStandardNPCGreeting0" + std::to_string(i) + ".wav"; + VoiceSample sample; + if (loadSound(path, sample)) taurenMale.push_back(std::move(sample)); + } + + // Tauren Female + auto& taurenFemale = voiceLibrary_[VoiceType::TAUREN_FEMALE]; + for (int i = 1; i <= 5; ++i) { + std::string path = "Sound\\Creature\\TaurenFemaleStandardNPC\\TaurenFemaleStandardNPCGreeting0" + std::to_string(i) + ".wav"; + VoiceSample sample; + if (loadSound(path, sample)) taurenFemale.push_back(std::move(sample)); + } + + // Troll Male + auto& trollMale = voiceLibrary_[VoiceType::TROLL_MALE]; + for (int i = 1; i <= 6; ++i) { + std::string path = "Sound\\Creature\\TrollMaleStandardNPC\\TrollMaleStandardNPCGreeting0" + std::to_string(i) + ".wav"; + VoiceSample sample; + if (loadSound(path, sample)) trollMale.push_back(std::move(sample)); + } + + // Troll Female + auto& trollFemale = voiceLibrary_[VoiceType::TROLL_FEMALE]; + for (int i = 1; i <= 5; ++i) { + std::string path = "Sound\\Creature\\TrollFemaleStandardNPC\\TrollFemaleStandardNPCGreeting0" + std::to_string(i) + ".wav"; + VoiceSample sample; + if (loadSound(path, sample)) trollFemale.push_back(std::move(sample)); + } + + // Undead Male + auto& undeadMale = voiceLibrary_[VoiceType::UNDEAD_MALE]; + for (int i = 1; i <= 6; ++i) { + std::string path = "Sound\\Creature\\UndeadMaleStandardNPC\\UndeadMaleStandardNPCGreeting0" + std::to_string(i) + ".wav"; + VoiceSample sample; + if (loadSound(path, sample)) undeadMale.push_back(std::move(sample)); + } + + // Undead Female + auto& undeadFemale = voiceLibrary_[VoiceType::UNDEAD_FEMALE]; + for (int i = 1; i <= 6; ++i) { + std::string path = "Sound\\Creature\\UndeadFemaleStandardNPC\\UndeadFemaleStandardNPCGreeting0" + std::to_string(i) + ".wav"; + VoiceSample sample; + if (loadSound(path, sample)) undeadFemale.push_back(std::move(sample)); + } + + // Log loaded voice types + int totalLoaded = 0; + for (const auto& [type, samples] : voiceLibrary_) { + if (!samples.empty()) { + LOG_INFO("Loaded ", samples.size(), " voice samples for type ", static_cast(type)); + totalLoaded += samples.size(); + } + } + + if (totalLoaded == 0) { + LOG_WARNING("NPC voice manager: no voice samples loaded (files may not exist in MPQ)"); + } +} + +bool NpcVoiceManager::loadSound(const std::string& path, VoiceSample& sample) { + if (!assetManager_ || !assetManager_->fileExists(path)) { + return false; + } + + auto data = assetManager_->readFile(path); + if (data.empty()) { + return false; + } + + sample.path = path; + sample.data = std::move(data); + return true; +} + +void NpcVoiceManager::playGreeting(uint64_t npcGuid, VoiceType voiceType, const glm::vec3& position) { + LOG_INFO("NPC voice: playGreeting called for GUID ", npcGuid); + + if (!AudioEngine::instance().isInitialized()) { + LOG_WARNING("NPC voice: AudioEngine not initialized"); + return; + } + + // Check cooldown + auto now = std::chrono::steady_clock::now(); + auto it = lastPlayTime_.find(npcGuid); + if (it != lastPlayTime_.end()) { + float elapsed = std::chrono::duration(now - it->second).count(); + if (elapsed < GREETING_COOLDOWN) { + LOG_INFO("NPC voice: on cooldown (", elapsed, "s elapsed)"); + return; // Still on cooldown + } + } + + // Find voice library for this type + auto libIt = voiceLibrary_.find(voiceType); + if (libIt == voiceLibrary_.end() || libIt->second.empty()) { + LOG_INFO("NPC voice: No samples for type ", static_cast(voiceType), ", falling back to GENERIC"); + // Fall back to generic + libIt = voiceLibrary_.find(VoiceType::GENERIC); + if (libIt == voiceLibrary_.end() || libIt->second.empty()) { + LOG_WARNING("NPC voice: No voice samples available (library empty)"); + return; // No voice samples available + } + } + + const auto& samples = libIt->second; + + // Pick random voice line + std::uniform_int_distribution dist(0, samples.size() - 1); + const auto& sample = samples[dist(rng_)]; + + LOG_INFO("NPC voice: Playing sound from: ", sample.path); + + // Play with 3D positioning + std::uniform_real_distribution volumeDist(0.85f, 1.0f); + std::uniform_real_distribution pitchDist(0.98f, 1.02f); + + bool success = AudioEngine::instance().playSound3D( + sample.data, + position, + volumeDist(rng_) * volumeScale_, + pitchDist(rng_), + 40.0f // Max distance for voice + ); + + if (success) { + LOG_INFO("NPC voice: Sound played successfully"); + lastPlayTime_[npcGuid] = now; + } else { + LOG_WARNING("NPC voice: Failed to play sound"); + } +} + +VoiceType NpcVoiceManager::detectVoiceType(uint32_t creatureEntry) const { + // TODO: Use CreatureTemplate.dbc or other data to map creature entry to voice type + // For now, return generic + (void)creatureEntry; + return VoiceType::GENERIC; +} + +} // namespace audio +} // namespace wowee diff --git a/src/core/application.cpp b/src/core/application.cpp index 7059a57f..71832a88 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -2549,6 +2549,8 @@ void Application::spawnOnlineGameObject(uint64_t guid, uint32_t displayId, float // Already have a render instance — update its position (e.g. transport re-creation) auto& info = gameObjectInstances_[guid]; glm::vec3 renderPos = core::coords::canonicalToRender(glm::vec3(x, y, z)); + LOG_INFO("GameObject position update: displayId=", displayId, " guid=0x", std::hex, guid, std::dec, + " pos=(", x, ", ", y, ", ", z, ")"); if (renderer) { if (info.isWmo) { if (auto* wr = renderer->getWMORenderer()) @@ -2567,6 +2569,10 @@ void Application::spawnOnlineGameObject(uint64_t guid, uint32_t displayId, float return; } + // Log spawns to help debug duplicate objects (e.g., cathedral issue) + LOG_INFO("GameObject spawn: displayId=", displayId, " guid=0x", std::hex, guid, std::dec, + " model=", modelPath, " pos=(", x, ", ", y, ", ", z, ")"); + std::string lowerPath = modelPath; std::transform(lowerPath.begin(), lowerPath.end(), lowerPath.begin(), [](unsigned char c) { return static_cast(std::tolower(c)); }); diff --git a/test_ambient_audio.sh b/test_ambient_audio.sh new file mode 100755 index 00000000..2e1f152f --- /dev/null +++ b/test_ambient_audio.sh @@ -0,0 +1,36 @@ +#!/bin/bash +# Test script for ambient audio debugging + +echo "=== Testing Ambient Audio System ===" +echo "" +echo "Running game for 60 seconds and capturing logs..." +echo "" + +timeout 60 build/bin/wowee 2>&1 | tee /tmp/wowee_ambient_test.log + +echo "" +echo "=== Analysis ===" +echo "" + +echo "1. AmbientSoundManager Initialization:" +grep -i "AmbientSoundManager" /tmp/wowee_ambient_test.log | head -20 +echo "" + +echo "2. Fire Emitters Detected:" +grep -i "fire emitter" /tmp/wowee_ambient_test.log +echo "" + +echo "3. Water Emitters Registered:" +grep -i "water.*emitter" /tmp/wowee_ambient_test.log | head -10 +echo "" + +echo "4. Sample WMO Doodads Loaded:" +grep "WMO doodad:" /tmp/wowee_ambient_test.log | head -20 +echo "" + +echo "5. Total Ambient Emitters:" +grep "Registered.*ambient" /tmp/wowee_ambient_test.log +echo "" + +echo "Full log saved to: /tmp/wowee_ambient_test.log" +echo "Use 'grep /tmp/wowee_ambient_test.log' to search for specific issues" diff --git a/test_splash_sounds.sh b/test_splash_sounds.sh new file mode 100755 index 00000000..b4bb95e9 --- /dev/null +++ b/test_splash_sounds.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# Test if splash sounds load properly + +echo "Testing splash sound files..." +echo "" + +for file in \ + "Sound\\Character\\Footsteps\\EnterWaterSplash\\EnterWaterSmallA.wav" \ + "Sound\\Character\\Footsteps\\EnterWaterSplash\\EnterWaterMediumA.wav" \ + "Sound\\Character\\Footsteps\\EnterWaterSplash\\EnterWaterGiantA.wav" \ + "Sound\\Character\\Footsteps\\WaterSplash\\FootStepsMediumWaterA.wav" +do + echo -n "Checking: $file ... " + if ./list_mpq Data/common.MPQ "$file" 2>/dev/null | grep -q "wav"; then + echo "✓ EXISTS" + else + echo "✗ NOT FOUND" + fi +done + +echo "" +echo "Now run the game and check for:" +echo " Activity SFX loaded: jump=X splash=8 swimLoop=X" +echo "" +echo "If splash=0, the files aren't being loaded by ActivitySoundManager"