mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-03 00:03:50 +00:00
Fix NPC voices to use correct WAV format and gender detection
WotLK 3.3.5a uses .wav files for NPC voices, not .ogg as shown in retail Wowhead. Fixed audio engine to preserve original sample rate from WAV files (preventing chipmunk playback). Implemented race/gender detection using CreatureDisplayInfo.dbc and CreatureDisplayInfoExtra.dbc to play correct voice types for each NPC.
This commit is contained in:
parent
0db06beb53
commit
045a8c7c89
4 changed files with 84 additions and 48 deletions
|
|
@ -16,6 +16,7 @@ namespace ui { class UIManager; }
|
||||||
namespace auth { class AuthHandler; }
|
namespace auth { class AuthHandler; }
|
||||||
namespace game { class GameHandler; class World; class NpcManager; }
|
namespace game { class GameHandler; class World; class NpcManager; }
|
||||||
namespace pipeline { class AssetManager; }
|
namespace pipeline { class AssetManager; }
|
||||||
|
namespace audio { enum class VoiceType; }
|
||||||
|
|
||||||
namespace core {
|
namespace core {
|
||||||
|
|
||||||
|
|
@ -91,6 +92,7 @@ private:
|
||||||
void despawnOnlineGameObject(uint64_t guid);
|
void despawnOnlineGameObject(uint64_t guid);
|
||||||
void buildGameObjectDisplayLookups();
|
void buildGameObjectDisplayLookups();
|
||||||
std::string getGameObjectModelPathForDisplayId(uint32_t displayId) const;
|
std::string getGameObjectModelPathForDisplayId(uint32_t displayId) const;
|
||||||
|
audio::VoiceType detectVoiceTypeFromDisplayId(uint32_t displayId) const;
|
||||||
|
|
||||||
static Application* instance;
|
static Application* instance;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -182,6 +182,7 @@ bool AudioEngine::playSound2D(const std::vector<uint8_t>& wavData, float volume,
|
||||||
pcmData.data(),
|
pcmData.data(),
|
||||||
nullptr // No custom allocator
|
nullptr // No custom allocator
|
||||||
);
|
);
|
||||||
|
bufferConfig.sampleRate = sampleRate; // Critical: preserve original sample rate!
|
||||||
|
|
||||||
ma_audio_buffer* audioBuffer = new ma_audio_buffer();
|
ma_audio_buffer* audioBuffer = new ma_audio_buffer();
|
||||||
result = ma_audio_buffer_init(&bufferConfig, audioBuffer);
|
result = ma_audio_buffer_init(&bufferConfig, audioBuffer);
|
||||||
|
|
@ -242,8 +243,6 @@ bool AudioEngine::playSound3D(const std::vector<uint8_t>& wavData, const glm::ve
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
(void)pitch; // Pitch not supported yet
|
|
||||||
|
|
||||||
// Decode WAV data first
|
// Decode WAV data first
|
||||||
ma_decoder decoder;
|
ma_decoder decoder;
|
||||||
ma_decoder_config decoderConfig = ma_decoder_config_init_default();
|
ma_decoder_config decoderConfig = ma_decoder_config_init_default();
|
||||||
|
|
@ -255,6 +254,7 @@ bool AudioEngine::playSound3D(const std::vector<uint8_t>& wavData, const glm::ve
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result != MA_SUCCESS) {
|
if (result != MA_SUCCESS) {
|
||||||
|
LOG_WARNING("playSound3D: Failed to decode WAV, error: ", result);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -262,6 +262,8 @@ bool AudioEngine::playSound3D(const std::vector<uint8_t>& wavData, const glm::ve
|
||||||
ma_uint32 channels = decoder.outputChannels;
|
ma_uint32 channels = decoder.outputChannels;
|
||||||
ma_uint32 sampleRate = decoder.outputSampleRate;
|
ma_uint32 sampleRate = decoder.outputSampleRate;
|
||||||
|
|
||||||
|
LOG_DEBUG("playSound3D: Decoded WAV - format:", format, " channels:", channels, " sampleRate:", sampleRate, " pitch:", pitch);
|
||||||
|
|
||||||
ma_uint64 totalFrames;
|
ma_uint64 totalFrames;
|
||||||
result = ma_decoder_get_length_in_pcm_frames(&decoder, &totalFrames);
|
result = ma_decoder_get_length_in_pcm_frames(&decoder, &totalFrames);
|
||||||
if (result != MA_SUCCESS) {
|
if (result != MA_SUCCESS) {
|
||||||
|
|
@ -286,7 +288,7 @@ bool AudioEngine::playSound3D(const std::vector<uint8_t>& wavData, const glm::ve
|
||||||
|
|
||||||
pcmData.resize(framesRead * channels * ma_get_bytes_per_sample(format));
|
pcmData.resize(framesRead * channels * ma_get_bytes_per_sample(format));
|
||||||
|
|
||||||
// Create audio buffer
|
// Create audio buffer with correct sample rate
|
||||||
ma_audio_buffer_config bufferConfig = ma_audio_buffer_config_init(
|
ma_audio_buffer_config bufferConfig = ma_audio_buffer_config_init(
|
||||||
format,
|
format,
|
||||||
channels,
|
channels,
|
||||||
|
|
@ -294,6 +296,7 @@ bool AudioEngine::playSound3D(const std::vector<uint8_t>& wavData, const glm::ve
|
||||||
pcmData.data(),
|
pcmData.data(),
|
||||||
nullptr
|
nullptr
|
||||||
);
|
);
|
||||||
|
bufferConfig.sampleRate = sampleRate; // Critical: preserve original sample rate!
|
||||||
|
|
||||||
ma_audio_buffer* audioBuffer = new ma_audio_buffer();
|
ma_audio_buffer* audioBuffer = new ma_audio_buffer();
|
||||||
result = ma_audio_buffer_init(&bufferConfig, audioBuffer);
|
result = ma_audio_buffer_init(&bufferConfig, audioBuffer);
|
||||||
|
|
@ -302,17 +305,18 @@ bool AudioEngine::playSound3D(const std::vector<uint8_t>& wavData, const glm::ve
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create 3D sound (spatialization enabled)
|
// Create 3D sound (spatialization enabled, pitch enabled)
|
||||||
ma_sound* sound = new ma_sound();
|
ma_sound* sound = new ma_sound();
|
||||||
result = ma_sound_init_from_data_source(
|
result = ma_sound_init_from_data_source(
|
||||||
engine_,
|
engine_,
|
||||||
audioBuffer,
|
audioBuffer,
|
||||||
MA_SOUND_FLAG_DECODE | MA_SOUND_FLAG_ASYNC | MA_SOUND_FLAG_NO_PITCH,
|
MA_SOUND_FLAG_DECODE | MA_SOUND_FLAG_ASYNC, // Removed NO_PITCH flag
|
||||||
nullptr,
|
nullptr,
|
||||||
sound
|
sound
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result != MA_SUCCESS) {
|
if (result != MA_SUCCESS) {
|
||||||
|
LOG_WARNING("playSound3D: Failed to create sound, error: ", result);
|
||||||
ma_audio_buffer_uninit(audioBuffer);
|
ma_audio_buffer_uninit(audioBuffer);
|
||||||
delete audioBuffer;
|
delete audioBuffer;
|
||||||
delete sound;
|
delete sound;
|
||||||
|
|
@ -322,6 +326,7 @@ bool AudioEngine::playSound3D(const std::vector<uint8_t>& wavData, const glm::ve
|
||||||
// Set 3D position and attenuation
|
// Set 3D position and attenuation
|
||||||
ma_sound_set_position(sound, position.x, position.y, position.z);
|
ma_sound_set_position(sound, position.x, position.y, position.z);
|
||||||
ma_sound_set_volume(sound, volume * masterVolume_);
|
ma_sound_set_volume(sound, volume * masterVolume_);
|
||||||
|
ma_sound_set_pitch(sound, pitch); // Enable pitch variation
|
||||||
ma_sound_set_attenuation_model(sound, ma_attenuation_model_inverse);
|
ma_sound_set_attenuation_model(sound, ma_attenuation_model_inverse);
|
||||||
ma_sound_set_min_gain(sound, 0.0f);
|
ma_sound_set_min_gain(sound, 0.0f);
|
||||||
ma_sound_set_max_gain(sound, 1.0f);
|
ma_sound_set_max_gain(sound, 1.0f);
|
||||||
|
|
|
||||||
|
|
@ -20,26 +20,13 @@ bool NpcVoiceManager::initialize(pipeline::AssetManager* assets) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Comprehensive probe - try forward slashes (MPQ internal format)
|
// Files are .WAV not .OGG in WotLK 3.3.5a!
|
||||||
LOG_INFO("=== Searching for NPC voice files (testing patterns) ===");
|
LOG_INFO("=== Probing for NPC voice files (.wav format) ===");
|
||||||
std::vector<std::string> testPaths = {
|
std::vector<std::string> testPaths = {
|
||||||
// Forward slashes (MPQ internal format)
|
"Sound\\Creature\\HumanMaleStandardNPC\\HumanMaleStandardNPCGreeting01.wav",
|
||||||
"Sound/Creature/HumanMaleStandardNPC/HumanMaleStandardNPCGreetings01.ogg",
|
"Sound\\Creature\\HumanFemaleStandardNPC\\HumanFemaleStandardNPCGreeting01.wav",
|
||||||
"Sound/Creature/HumanFemaleStandardNPC/HumanFemaleStandardNPCGreeting01.ogg",
|
"Sound\\Creature\\DwarfMaleStandardNPC\\DwarfMaleStandardNPCGreeting01.wav",
|
||||||
|
"Sound\\Creature\\OrcMaleStandardNPC\\OrcMaleStandardNPCGreeting01.wav",
|
||||||
// Backslashes (Windows format)
|
|
||||||
"Sound\\Creature\\HumanMaleStandardNPC\\HumanMaleStandardNPCGreetings01.ogg",
|
|
||||||
"Sound\\Creature\\HumanFemaleStandardNPC\\HumanFemaleStandardNPCGreeting01.ogg",
|
|
||||||
|
|
||||||
// Lowercase with forward slashes
|
|
||||||
"sound/creature/humanmalestandardnpc/humanmalestandardnpcgreetings01.ogg",
|
|
||||||
|
|
||||||
// PC voice files with forward slashes
|
|
||||||
"Sound/Character/Human/HumanVocMaleHello01.wav",
|
|
||||||
"Sound/Character/Human/HumanVocFemaleHello01.wav",
|
|
||||||
|
|
||||||
// PC voice files with backslashes
|
|
||||||
"Sound\\Character\\Human\\HumanVocMaleHello01.wav",
|
|
||||||
};
|
};
|
||||||
for (const auto& path : testPaths) {
|
for (const auto& path : testPaths) {
|
||||||
bool exists = assetManager_->fileExists(path);
|
bool exists = assetManager_->fileExists(path);
|
||||||
|
|
@ -76,18 +63,18 @@ void NpcVoiceManager::shutdown() {
|
||||||
void NpcVoiceManager::loadVoiceSounds() {
|
void NpcVoiceManager::loadVoiceSounds() {
|
||||||
if (!assetManager_) return;
|
if (!assetManager_) return;
|
||||||
|
|
||||||
// Load all standard NPC greetings from Wowhead database
|
// WotLK 3.3.5a uses .WAV files, not .OGG!
|
||||||
// Note: Human male uses "Greetings" (plural), others use "Greeting" (singular)
|
// Files use "Greeting" (singular) not "Greetings"
|
||||||
|
|
||||||
// Generic - mix of all races for variety
|
// Generic - mix of all races for variety
|
||||||
auto& genericVoices = voiceLibrary_[VoiceType::GENERIC];
|
auto& genericVoices = voiceLibrary_[VoiceType::GENERIC];
|
||||||
for (const auto& path : {
|
for (const auto& path : {
|
||||||
"Sound\\Creature\\HumanMaleStandardNPC\\HumanMaleStandardNPCGreetings01.ogg",
|
"Sound\\Creature\\HumanMaleStandardNPC\\HumanMaleStandardNPCGreeting01.wav",
|
||||||
"Sound\\Creature\\HumanFemaleStandardNPC\\HumanFemaleStandardNPCGreeting01.ogg",
|
"Sound\\Creature\\HumanFemaleStandardNPC\\HumanFemaleStandardNPCGreeting01.wav",
|
||||||
"Sound\\Creature\\DwarfMaleStandardNPC\\DwarfMaleStandardNPCGreeting01.ogg",
|
"Sound\\Creature\\DwarfMaleStandardNPC\\DwarfMaleStandardNPCGreeting01.wav",
|
||||||
"Sound\\Creature\\GnomeMaleStandardNPC\\GnomeMaleStandardNPCGreeting01.ogg",
|
"Sound\\Creature\\GnomeMaleStandardNPC\\GnomeMaleStandardNPCGreeting01.wav",
|
||||||
"Sound\\Creature\\NightElfMaleStandardNPC\\NightElfMaleStandardNPCGreeting01.ogg",
|
"Sound\\Creature\\NightElfMaleStandardNPC\\NightElfMaleStandardNPCGreeting01.wav",
|
||||||
"Sound\\Creature\\OrcMaleStandardNPC\\OrcMaleStandardNPCGreeting01.ogg",
|
"Sound\\Creature\\OrcMaleStandardNPC\\OrcMaleStandardNPCGreeting01.wav",
|
||||||
}) {
|
}) {
|
||||||
VoiceSample sample;
|
VoiceSample sample;
|
||||||
if (loadSound(path, sample)) genericVoices.push_back(std::move(sample));
|
if (loadSound(path, sample)) genericVoices.push_back(std::move(sample));
|
||||||
|
|
@ -96,7 +83,7 @@ void NpcVoiceManager::loadVoiceSounds() {
|
||||||
// Human Male
|
// Human Male
|
||||||
auto& humanMale = voiceLibrary_[VoiceType::HUMAN_MALE];
|
auto& humanMale = voiceLibrary_[VoiceType::HUMAN_MALE];
|
||||||
for (int i = 1; i <= 6; ++i) {
|
for (int i = 1; i <= 6; ++i) {
|
||||||
std::string path = "Sound\\Creature\\HumanMaleStandardNPC\\HumanMaleStandardNPCGreetings0" + std::to_string(i) + ".ogg";
|
std::string path = "Sound\\Creature\\HumanMaleStandardNPC\\HumanMaleStandardNPCGreeting0" + std::to_string(i) + ".wav";
|
||||||
VoiceSample sample;
|
VoiceSample sample;
|
||||||
if (loadSound(path, sample)) humanMale.push_back(std::move(sample));
|
if (loadSound(path, sample)) humanMale.push_back(std::move(sample));
|
||||||
}
|
}
|
||||||
|
|
@ -104,7 +91,7 @@ void NpcVoiceManager::loadVoiceSounds() {
|
||||||
// Human Female
|
// Human Female
|
||||||
auto& humanFemale = voiceLibrary_[VoiceType::HUMAN_FEMALE];
|
auto& humanFemale = voiceLibrary_[VoiceType::HUMAN_FEMALE];
|
||||||
for (int i = 1; i <= 5; ++i) {
|
for (int i = 1; i <= 5; ++i) {
|
||||||
std::string path = "Sound\\Creature\\HumanFemaleStandardNPC\\HumanFemaleStandardNPCGreeting0" + std::to_string(i) + ".ogg";
|
std::string path = "Sound\\Creature\\HumanFemaleStandardNPC\\HumanFemaleStandardNPCGreeting0" + std::to_string(i) + ".wav";
|
||||||
VoiceSample sample;
|
VoiceSample sample;
|
||||||
if (loadSound(path, sample)) humanFemale.push_back(std::move(sample));
|
if (loadSound(path, sample)) humanFemale.push_back(std::move(sample));
|
||||||
}
|
}
|
||||||
|
|
@ -112,7 +99,7 @@ void NpcVoiceManager::loadVoiceSounds() {
|
||||||
// Dwarf Male
|
// Dwarf Male
|
||||||
auto& dwarfMale = voiceLibrary_[VoiceType::DWARF_MALE];
|
auto& dwarfMale = voiceLibrary_[VoiceType::DWARF_MALE];
|
||||||
for (int i = 1; i <= 6; ++i) {
|
for (int i = 1; i <= 6; ++i) {
|
||||||
std::string path = "Sound\\Creature\\DwarfMaleStandardNPC\\DwarfMaleStandardNPCGreeting0" + std::to_string(i) + ".ogg";
|
std::string path = "Sound\\Creature\\DwarfMaleStandardNPC\\DwarfMaleStandardNPCGreeting0" + std::to_string(i) + ".wav";
|
||||||
VoiceSample sample;
|
VoiceSample sample;
|
||||||
if (loadSound(path, sample)) dwarfMale.push_back(std::move(sample));
|
if (loadSound(path, sample)) dwarfMale.push_back(std::move(sample));
|
||||||
}
|
}
|
||||||
|
|
@ -120,7 +107,7 @@ void NpcVoiceManager::loadVoiceSounds() {
|
||||||
// Gnome Male
|
// Gnome Male
|
||||||
auto& gnomeMale = voiceLibrary_[VoiceType::GNOME_MALE];
|
auto& gnomeMale = voiceLibrary_[VoiceType::GNOME_MALE];
|
||||||
for (int i = 1; i <= 6; ++i) {
|
for (int i = 1; i <= 6; ++i) {
|
||||||
std::string path = "Sound\\Creature\\GnomeMaleStandardNPC\\GnomeMaleStandardNPCGreeting0" + std::to_string(i) + ".ogg";
|
std::string path = "Sound\\Creature\\GnomeMaleStandardNPC\\GnomeMaleStandardNPCGreeting0" + std::to_string(i) + ".wav";
|
||||||
VoiceSample sample;
|
VoiceSample sample;
|
||||||
if (loadSound(path, sample)) gnomeMale.push_back(std::move(sample));
|
if (loadSound(path, sample)) gnomeMale.push_back(std::move(sample));
|
||||||
}
|
}
|
||||||
|
|
@ -128,7 +115,7 @@ void NpcVoiceManager::loadVoiceSounds() {
|
||||||
// Gnome Female
|
// Gnome Female
|
||||||
auto& gnomeFemale = voiceLibrary_[VoiceType::GNOME_FEMALE];
|
auto& gnomeFemale = voiceLibrary_[VoiceType::GNOME_FEMALE];
|
||||||
for (int i = 1; i <= 6; ++i) {
|
for (int i = 1; i <= 6; ++i) {
|
||||||
std::string path = "Sound\\Creature\\GnomeFemaleStandardNPC\\GnomeFemaleStandardNPCGreeting0" + std::to_string(i) + ".ogg";
|
std::string path = "Sound\\Creature\\GnomeFemaleStandardNPC\\GnomeFemaleStandardNPCGreeting0" + std::to_string(i) + ".wav";
|
||||||
VoiceSample sample;
|
VoiceSample sample;
|
||||||
if (loadSound(path, sample)) gnomeFemale.push_back(std::move(sample));
|
if (loadSound(path, sample)) gnomeFemale.push_back(std::move(sample));
|
||||||
}
|
}
|
||||||
|
|
@ -136,7 +123,7 @@ void NpcVoiceManager::loadVoiceSounds() {
|
||||||
// Night Elf Male
|
// Night Elf Male
|
||||||
auto& nelfMale = voiceLibrary_[VoiceType::NIGHTELF_MALE];
|
auto& nelfMale = voiceLibrary_[VoiceType::NIGHTELF_MALE];
|
||||||
for (int i = 1; i <= 8; ++i) {
|
for (int i = 1; i <= 8; ++i) {
|
||||||
std::string path = "Sound\\Creature\\NightElfMaleStandardNPC\\NightElfMaleStandardNPCGreeting0" + std::to_string(i) + ".ogg";
|
std::string path = "Sound\\Creature\\NightElfMaleStandardNPC\\NightElfMaleStandardNPCGreeting0" + std::to_string(i) + ".wav";
|
||||||
VoiceSample sample;
|
VoiceSample sample;
|
||||||
if (loadSound(path, sample)) nelfMale.push_back(std::move(sample));
|
if (loadSound(path, sample)) nelfMale.push_back(std::move(sample));
|
||||||
}
|
}
|
||||||
|
|
@ -144,7 +131,7 @@ void NpcVoiceManager::loadVoiceSounds() {
|
||||||
// Night Elf Female
|
// Night Elf Female
|
||||||
auto& nelfFemale = voiceLibrary_[VoiceType::NIGHTELF_FEMALE];
|
auto& nelfFemale = voiceLibrary_[VoiceType::NIGHTELF_FEMALE];
|
||||||
for (int i = 1; i <= 6; ++i) {
|
for (int i = 1; i <= 6; ++i) {
|
||||||
std::string path = "Sound\\Creature\\NightElfFemaleStandardNPC\\NightElfFemaleStandardNPCGreeting0" + std::to_string(i) + ".ogg";
|
std::string path = "Sound\\Creature\\NightElfFemaleStandardNPC\\NightElfFemaleStandardNPCGreeting0" + std::to_string(i) + ".wav";
|
||||||
VoiceSample sample;
|
VoiceSample sample;
|
||||||
if (loadSound(path, sample)) nelfFemale.push_back(std::move(sample));
|
if (loadSound(path, sample)) nelfFemale.push_back(std::move(sample));
|
||||||
}
|
}
|
||||||
|
|
@ -152,7 +139,7 @@ void NpcVoiceManager::loadVoiceSounds() {
|
||||||
// Orc Male
|
// Orc Male
|
||||||
auto& orcMale = voiceLibrary_[VoiceType::ORC_MALE];
|
auto& orcMale = voiceLibrary_[VoiceType::ORC_MALE];
|
||||||
for (int i = 1; i <= 5; ++i) {
|
for (int i = 1; i <= 5; ++i) {
|
||||||
std::string path = "Sound\\Creature\\OrcMaleStandardNPC\\OrcMaleStandardNPCGreeting0" + std::to_string(i) + ".ogg";
|
std::string path = "Sound\\Creature\\OrcMaleStandardNPC\\OrcMaleStandardNPCGreeting0" + std::to_string(i) + ".wav";
|
||||||
VoiceSample sample;
|
VoiceSample sample;
|
||||||
if (loadSound(path, sample)) orcMale.push_back(std::move(sample));
|
if (loadSound(path, sample)) orcMale.push_back(std::move(sample));
|
||||||
}
|
}
|
||||||
|
|
@ -160,7 +147,7 @@ void NpcVoiceManager::loadVoiceSounds() {
|
||||||
// Orc Female
|
// Orc Female
|
||||||
auto& orcFemale = voiceLibrary_[VoiceType::ORC_FEMALE];
|
auto& orcFemale = voiceLibrary_[VoiceType::ORC_FEMALE];
|
||||||
for (int i = 1; i <= 6; ++i) {
|
for (int i = 1; i <= 6; ++i) {
|
||||||
std::string path = "Sound\\Creature\\OrcFemaleStandardNPC\\OrcFemaleStandardNPCGreeting0" + std::to_string(i) + ".ogg";
|
std::string path = "Sound\\Creature\\OrcFemaleStandardNPC\\OrcFemaleStandardNPCGreeting0" + std::to_string(i) + ".wav";
|
||||||
VoiceSample sample;
|
VoiceSample sample;
|
||||||
if (loadSound(path, sample)) orcFemale.push_back(std::move(sample));
|
if (loadSound(path, sample)) orcFemale.push_back(std::move(sample));
|
||||||
}
|
}
|
||||||
|
|
@ -168,7 +155,7 @@ void NpcVoiceManager::loadVoiceSounds() {
|
||||||
// Tauren Male
|
// Tauren Male
|
||||||
auto& taurenMale = voiceLibrary_[VoiceType::TAUREN_MALE];
|
auto& taurenMale = voiceLibrary_[VoiceType::TAUREN_MALE];
|
||||||
for (int i = 1; i <= 5; ++i) {
|
for (int i = 1; i <= 5; ++i) {
|
||||||
std::string path = "Sound\\Creature\\TaurenMaleStandardNPC\\TaurenMaleStandardNPCGreeting0" + std::to_string(i) + ".ogg";
|
std::string path = "Sound\\Creature\\TaurenMaleStandardNPC\\TaurenMaleStandardNPCGreeting0" + std::to_string(i) + ".wav";
|
||||||
VoiceSample sample;
|
VoiceSample sample;
|
||||||
if (loadSound(path, sample)) taurenMale.push_back(std::move(sample));
|
if (loadSound(path, sample)) taurenMale.push_back(std::move(sample));
|
||||||
}
|
}
|
||||||
|
|
@ -176,7 +163,7 @@ void NpcVoiceManager::loadVoiceSounds() {
|
||||||
// Tauren Female
|
// Tauren Female
|
||||||
auto& taurenFemale = voiceLibrary_[VoiceType::TAUREN_FEMALE];
|
auto& taurenFemale = voiceLibrary_[VoiceType::TAUREN_FEMALE];
|
||||||
for (int i = 1; i <= 5; ++i) {
|
for (int i = 1; i <= 5; ++i) {
|
||||||
std::string path = "Sound\\Creature\\TaurenFemaleStandardNPC\\TaurenFemaleStandardNPCGreeting0" + std::to_string(i) + ".ogg";
|
std::string path = "Sound\\Creature\\TaurenFemaleStandardNPC\\TaurenFemaleStandardNPCGreeting0" + std::to_string(i) + ".wav";
|
||||||
VoiceSample sample;
|
VoiceSample sample;
|
||||||
if (loadSound(path, sample)) taurenFemale.push_back(std::move(sample));
|
if (loadSound(path, sample)) taurenFemale.push_back(std::move(sample));
|
||||||
}
|
}
|
||||||
|
|
@ -184,7 +171,7 @@ void NpcVoiceManager::loadVoiceSounds() {
|
||||||
// Troll Male
|
// Troll Male
|
||||||
auto& trollMale = voiceLibrary_[VoiceType::TROLL_MALE];
|
auto& trollMale = voiceLibrary_[VoiceType::TROLL_MALE];
|
||||||
for (int i = 1; i <= 6; ++i) {
|
for (int i = 1; i <= 6; ++i) {
|
||||||
std::string path = "Sound\\Creature\\TrollMaleStandardNPC\\TrollMaleStandardNPCGreeting0" + std::to_string(i) + ".ogg";
|
std::string path = "Sound\\Creature\\TrollMaleStandardNPC\\TrollMaleStandardNPCGreeting0" + std::to_string(i) + ".wav";
|
||||||
VoiceSample sample;
|
VoiceSample sample;
|
||||||
if (loadSound(path, sample)) trollMale.push_back(std::move(sample));
|
if (loadSound(path, sample)) trollMale.push_back(std::move(sample));
|
||||||
}
|
}
|
||||||
|
|
@ -192,7 +179,7 @@ void NpcVoiceManager::loadVoiceSounds() {
|
||||||
// Troll Female
|
// Troll Female
|
||||||
auto& trollFemale = voiceLibrary_[VoiceType::TROLL_FEMALE];
|
auto& trollFemale = voiceLibrary_[VoiceType::TROLL_FEMALE];
|
||||||
for (int i = 1; i <= 5; ++i) {
|
for (int i = 1; i <= 5; ++i) {
|
||||||
std::string path = "Sound\\Creature\\TrollFemaleStandardNPC\\TrollFemaleStandardNPCGreeting0" + std::to_string(i) + ".ogg";
|
std::string path = "Sound\\Creature\\TrollFemaleStandardNPC\\TrollFemaleStandardNPCGreeting0" + std::to_string(i) + ".wav";
|
||||||
VoiceSample sample;
|
VoiceSample sample;
|
||||||
if (loadSound(path, sample)) trollFemale.push_back(std::move(sample));
|
if (loadSound(path, sample)) trollFemale.push_back(std::move(sample));
|
||||||
}
|
}
|
||||||
|
|
@ -200,7 +187,7 @@ void NpcVoiceManager::loadVoiceSounds() {
|
||||||
// Undead Male
|
// Undead Male
|
||||||
auto& undeadMale = voiceLibrary_[VoiceType::UNDEAD_MALE];
|
auto& undeadMale = voiceLibrary_[VoiceType::UNDEAD_MALE];
|
||||||
for (int i = 1; i <= 6; ++i) {
|
for (int i = 1; i <= 6; ++i) {
|
||||||
std::string path = "Sound\\Creature\\UndeadMaleStandardNPC\\UndeadMaleStandardNPCGreeting0" + std::to_string(i) + ".ogg";
|
std::string path = "Sound\\Creature\\UndeadMaleStandardNPC\\UndeadMaleStandardNPCGreeting0" + std::to_string(i) + ".wav";
|
||||||
VoiceSample sample;
|
VoiceSample sample;
|
||||||
if (loadSound(path, sample)) undeadMale.push_back(std::move(sample));
|
if (loadSound(path, sample)) undeadMale.push_back(std::move(sample));
|
||||||
}
|
}
|
||||||
|
|
@ -208,7 +195,7 @@ void NpcVoiceManager::loadVoiceSounds() {
|
||||||
// Undead Female
|
// Undead Female
|
||||||
auto& undeadFemale = voiceLibrary_[VoiceType::UNDEAD_FEMALE];
|
auto& undeadFemale = voiceLibrary_[VoiceType::UNDEAD_FEMALE];
|
||||||
for (int i = 1; i <= 6; ++i) {
|
for (int i = 1; i <= 6; ++i) {
|
||||||
std::string path = "Sound\\Creature\\UndeadFemaleStandardNPC\\UndeadFemaleStandardNPCGreeting0" + std::to_string(i) + ".ogg";
|
std::string path = "Sound\\Creature\\UndeadFemaleStandardNPC\\UndeadFemaleStandardNPCGreeting0" + std::to_string(i) + ".wav";
|
||||||
VoiceSample sample;
|
VoiceSample sample;
|
||||||
if (loadSound(path, sample)) undeadFemale.push_back(std::move(sample));
|
if (loadSound(path, sample)) undeadFemale.push_back(std::move(sample));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -820,7 +820,17 @@ void Application::setupUICallbacks() {
|
||||||
if (renderer && renderer->getNpcVoiceManager()) {
|
if (renderer && renderer->getNpcVoiceManager()) {
|
||||||
// Convert canonical to render coords for 3D audio
|
// Convert canonical to render coords for 3D audio
|
||||||
glm::vec3 renderPos = core::coords::canonicalToRender(position);
|
glm::vec3 renderPos = core::coords::canonicalToRender(position);
|
||||||
renderer->getNpcVoiceManager()->playGreeting(guid, audio::VoiceType::GENERIC, renderPos);
|
|
||||||
|
// Detect voice type from NPC display ID
|
||||||
|
audio::VoiceType voiceType = audio::VoiceType::GENERIC;
|
||||||
|
auto entity = gameHandler->getEntityManager().getEntity(guid);
|
||||||
|
if (entity && entity->getType() == game::ObjectType::UNIT) {
|
||||||
|
auto unit = std::static_pointer_cast<game::Unit>(entity);
|
||||||
|
uint32_t displayId = unit->getDisplayId();
|
||||||
|
voiceType = detectVoiceTypeFromDisplayId(displayId);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer->getNpcVoiceManager()->playGreeting(guid, voiceType, renderPos);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -1926,6 +1936,38 @@ std::string Application::getModelPathForDisplayId(uint32_t displayId) const {
|
||||||
return itPath->second;
|
return itPath->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
audio::VoiceType Application::detectVoiceTypeFromDisplayId(uint32_t displayId) const {
|
||||||
|
// Look up display data
|
||||||
|
auto itDisplay = displayDataMap_.find(displayId);
|
||||||
|
if (itDisplay == displayDataMap_.end() || itDisplay->second.extraDisplayId == 0) {
|
||||||
|
return audio::VoiceType::GENERIC; // Not a humanoid or no extra data
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look up humanoid extra data (race/sex info)
|
||||||
|
auto itExtra = humanoidExtraMap_.find(itDisplay->second.extraDisplayId);
|
||||||
|
if (itExtra == humanoidExtraMap_.end()) {
|
||||||
|
return audio::VoiceType::GENERIC;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t raceId = itExtra->second.raceId;
|
||||||
|
uint8_t sexId = itExtra->second.sexId;
|
||||||
|
|
||||||
|
// Map (raceId, sexId) to VoiceType
|
||||||
|
// Race IDs: 1=Human, 2=Orc, 3=Dwarf, 4=NightElf, 5=Undead, 6=Tauren, 7=Gnome, 8=Troll
|
||||||
|
// Sex IDs: 0=Male, 1=Female
|
||||||
|
switch (raceId) {
|
||||||
|
case 1: return (sexId == 0) ? audio::VoiceType::HUMAN_MALE : audio::VoiceType::HUMAN_FEMALE;
|
||||||
|
case 2: return (sexId == 0) ? audio::VoiceType::ORC_MALE : audio::VoiceType::ORC_FEMALE;
|
||||||
|
case 3: return (sexId == 0) ? audio::VoiceType::DWARF_MALE : audio::VoiceType::GENERIC; // No dwarf female voices loaded
|
||||||
|
case 4: return (sexId == 0) ? audio::VoiceType::NIGHTELF_MALE : audio::VoiceType::NIGHTELF_FEMALE;
|
||||||
|
case 5: return (sexId == 0) ? audio::VoiceType::UNDEAD_MALE : audio::VoiceType::UNDEAD_FEMALE;
|
||||||
|
case 6: return (sexId == 0) ? audio::VoiceType::TAUREN_MALE : audio::VoiceType::TAUREN_FEMALE;
|
||||||
|
case 7: return (sexId == 0) ? audio::VoiceType::GNOME_MALE : audio::VoiceType::GNOME_FEMALE;
|
||||||
|
case 8: return (sexId == 0) ? audio::VoiceType::TROLL_MALE : audio::VoiceType::TROLL_FEMALE;
|
||||||
|
default: return audio::VoiceType::GENERIC;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Application::buildGameObjectDisplayLookups() {
|
void Application::buildGameObjectDisplayLookups() {
|
||||||
if (gameObjectLookupsBuilt_ || !assetManager || !assetManager->isInitialized()) return;
|
if (gameObjectLookupsBuilt_ || !assetManager || !assetManager->isInitialized()) return;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue