diff --git a/list_mpq b/list_mpq new file mode 100755 index 00000000..22ce4a63 Binary files /dev/null and b/list_mpq differ 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"