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.
This commit is contained in:
Kelsi 2026-02-09 01:29:44 -08:00
parent 71c4fb3ae6
commit eb288d2064
7 changed files with 37 additions and 1 deletions

View file

@ -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();

View file

@ -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) {

View file

@ -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 <GL/glew.h>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtx/euler_angles.hpp>
@ -342,6 +343,7 @@ bool Renderer::initialize(core::Window* win) {
footstepManager = std::make_unique<audio::FootstepManager>();
activitySoundManager = std::make_unique<audio::ActivitySoundManager>();
mountSoundManager = std::make_unique<audio::MountSoundManager>();
npcVoiceManager = std::make_unique<audio::NpcVoiceManager>();
// Underwater full-screen tint overlay (applies to all world geometry).
underwaterOverlayShader = std::make_unique<Shader>();
@ -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) {