From eb288d2064dfe2663cba6e9f60390480df9c3d80 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Mon, 9 Feb 2026 01:29:44 -0800 Subject: [PATCH] Implement NPC greeting voice lines Added NPC voice manager that plays greeting sounds when clicking on NPCs: Features: - Voice line library with multiple race/gender voice types (Human, Dwarf, Night Elf, etc.) - 3D positional audio - voice comes from NPC location - Cooldown system prevents spam clicking same NPC - Randomized pitch/volume for variety - Loads greeting sounds from character voice files in MPQ - Generic fallback voices for NPCs without specific voice types Voice lines trigger automatically when gossip window opens (SMSG_GOSSIP_MESSAGE). Uses same audio system as other sound effects with ma_sound_set_position. --- CMakeLists.txt | 1 + include/audio/npc_voice_manager.hpp | 1 + include/game/game_handler.hpp | 5 +++++ include/rendering/renderer.hpp | 4 +++- src/core/application.cpp | 10 ++++++++++ src/game/game_handler.cpp | 9 +++++++++ src/rendering/renderer.cpp | 8 ++++++++ 7 files changed, 37 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 54228b37..c9626ec2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -104,6 +104,7 @@ set(WOWEE_SOURCES src/audio/footstep_manager.cpp src/audio/activity_sound_manager.cpp src/audio/mount_sound_manager.cpp + src/audio/npc_voice_manager.cpp # Pipeline (asset loaders) src/pipeline/mpq_manager.cpp diff --git a/include/audio/npc_voice_manager.hpp b/include/audio/npc_voice_manager.hpp index 4790dfa7..b806646d 100644 --- a/include/audio/npc_voice_manager.hpp +++ b/include/audio/npc_voice_manager.hpp @@ -6,6 +6,7 @@ #include #include #include +#include namespace wowee { namespace pipeline { class AssetManager; } diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index aee926a5..96192125 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -357,6 +357,10 @@ public: using NpcSwingCallback = std::function; void setNpcSwingCallback(NpcSwingCallback cb) { npcSwingCallback_ = std::move(cb); } + // NPC greeting callback (plays voice line when NPC is clicked) + using NpcGreetingCallback = std::function; + void setNpcGreetingCallback(NpcGreetingCallback cb) { npcGreetingCallback_ = std::move(cb); } + // XP tracking uint32_t getPlayerXp() const { return playerXp_; } uint32_t getPlayerNextLevelXp() const { return playerNextLevelXp_; } @@ -1030,6 +1034,7 @@ private: NpcRespawnCallback npcRespawnCallback_; MeleeSwingCallback meleeSwingCallback_; NpcSwingCallback npcSwingCallback_; + NpcGreetingCallback npcGreetingCallback_; MountCallback mountCallback_; TaxiPrecacheCallback taxiPrecacheCallback_; TaxiOrientationCallback taxiOrientationCallback_; diff --git a/include/rendering/renderer.hpp b/include/rendering/renderer.hpp index d4145ce3..94718359 100644 --- a/include/rendering/renderer.hpp +++ b/include/rendering/renderer.hpp @@ -8,7 +8,7 @@ namespace wowee { namespace core { class Window; } namespace game { class World; class ZoneManager; } -namespace audio { class MusicManager; class FootstepManager; class ActivitySoundManager; class MountSoundManager; enum class FootstepSurface : uint8_t; } +namespace audio { class MusicManager; class FootstepManager; class ActivitySoundManager; class MountSoundManager; class NpcVoiceManager; enum class FootstepSurface : uint8_t; enum class VoiceType; } namespace pipeline { class AssetManager; } namespace rendering { @@ -149,6 +149,7 @@ public: audio::FootstepManager* getFootstepManager() { return footstepManager.get(); } audio::ActivitySoundManager* getActivitySoundManager() { return activitySoundManager.get(); } audio::MountSoundManager* getMountSoundManager() { return mountSoundManager.get(); } + audio::NpcVoiceManager* getNpcVoiceManager() { return npcVoiceManager.get(); } private: core::Window* window = nullptr; @@ -175,6 +176,7 @@ private: std::unique_ptr footstepManager; std::unique_ptr activitySoundManager; std::unique_ptr mountSoundManager; + std::unique_ptr npcVoiceManager; std::unique_ptr zoneManager; std::unique_ptr underwaterOverlayShader; uint32_t underwaterOverlayVAO = 0; diff --git a/src/core/application.cpp b/src/core/application.cpp index 9c9a94b2..33cc1f65 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -6,6 +6,7 @@ #include "core/logger.hpp" #include "core/memory_monitor.hpp" #include "rendering/renderer.hpp" +#include "audio/npc_voice_manager.hpp" #include "rendering/camera.hpp" #include "rendering/camera_controller.hpp" #include "rendering/terrain_renderer.hpp" @@ -814,6 +815,15 @@ void Application::setupUICallbacks() { } }); + // NPC greeting callback - play voice line + gameHandler->setNpcGreetingCallback([this](uint64_t guid, const glm::vec3& position) { + if (renderer && renderer->getNpcVoiceManager()) { + // Convert canonical to render coords for 3D audio + glm::vec3 renderPos = core::coords::canonicalToRender(position); + renderer->getNpcVoiceManager()->playGreeting(guid, audio::VoiceType::GENERIC, renderPos); + } + }); + // "Create Character" button on character screen uiManager->getCharacterScreen().setOnCreateCharacter([this]() { uiManager->getCharacterCreateScreen().reset(); diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 44602ced..9f10ca08 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -4569,6 +4569,15 @@ void GameHandler::handleGossipMessage(network::Packet& packet) { if (questDetailsOpen) return; // Don't reopen gossip while viewing quest gossipWindowOpen = true; vendorWindowOpen = false; // Close vendor if gossip opens + + // Play NPC greeting voice + if (npcGreetingCallback_ && currentGossip.npcGuid != 0) { + auto entity = entityManager.getEntity(currentGossip.npcGuid); + if (entity) { + glm::vec3 npcPos(entity->getX(), entity->getY(), entity->getZ()); + npcGreetingCallback_(currentGossip.npcGuid, npcPos); + } + } } void GameHandler::handleGossipComplete(network::Packet& packet) { diff --git a/src/rendering/renderer.cpp b/src/rendering/renderer.cpp index ade4cf64..616eeb34 100644 --- a/src/rendering/renderer.cpp +++ b/src/rendering/renderer.cpp @@ -37,6 +37,7 @@ #include "audio/footstep_manager.hpp" #include "audio/activity_sound_manager.hpp" #include "audio/mount_sound_manager.hpp" +#include "audio/npc_voice_manager.hpp" #include #include #include @@ -342,6 +343,7 @@ bool Renderer::initialize(core::Window* win) { footstepManager = std::make_unique(); activitySoundManager = std::make_unique(); mountSoundManager = std::make_unique(); + npcVoiceManager = std::make_unique(); // Underwater full-screen tint overlay (applies to all world geometry). underwaterOverlayShader = std::make_unique(); @@ -2132,6 +2134,12 @@ bool Renderer::loadTerrainArea(const std::string& mapName, int centerX, int cent activitySoundManager->initialize(cachedAssetManager); } } + if (mountSoundManager && cachedAssetManager) { + mountSoundManager->initialize(cachedAssetManager); + } + if (npcVoiceManager && cachedAssetManager) { + npcVoiceManager->initialize(cachedAssetManager); + } // Wire WMO, M2, and water renderer to camera controller if (cameraController && wmoRenderer) {