mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-23 15:50:20 +00:00
Add comprehensive NPC voice system with interaction and combat sounds
Implements full NPC voice interaction system supporting 6 different sound categories for all playable races/genders. System loads ~450+ voice clips from MPQ archives. Voice Categories: - Greeting: Play on NPC right-click interaction - Farewell: Play when closing gossip/dialog windows - Vendor: Play when opening merchant/vendor windows - Pissed: Play after clicking NPC 5+ times (spam protection) - Aggro: Play when NPC enters combat with player - Flee: Play when NPC is fleeing (ready for low-health triggers) Features: - Race/gender detection from NPC display IDs via CreatureDisplayInfoExtra.dbc - Intelligent click tracking for pissed sounds - Combat sounds use player character vocal files for humanoid NPCs - Cooldown system prevents voice spam (2s default, combat sounds bypass) - Generic fallback voices for unsupported NPC types - 3D positional audio support Voice Support: - All playable races: Human, Dwarf, Gnome, Night Elf, Orc, Tauren, Troll, Undead - Male and female variants for each race - StandardNPC sounds for social interactions - Character vocal sounds for combat Technical Changes: - Refactored NpcVoiceManager to support multiple sound categories - Added callbacks: NpcFarewell, NpcVendor, NpcAggro - Extended voice loading to parse both StandardNPC and Character vocal paths - Integrated with GameHandler for gossip, vendor, and combat events - Added detailed voice detection logging for debugging Also includes: - Sound manifest files added to docs/ for reference - Blacksmith hammer pitch increased to 1.6x (was 1.4x) - Blacksmith volume reduced 30% to 0.25 (was 0.35)
This commit is contained in:
parent
251f0ac246
commit
28d009f7db
8 changed files with 10908 additions and 198 deletions
|
|
@ -834,6 +834,57 @@ void Application::setupUICallbacks() {
|
|||
}
|
||||
});
|
||||
|
||||
// NPC farewell callback - play farewell voice line
|
||||
gameHandler->setNpcFarewellCallback([this](uint64_t guid, const glm::vec3& position) {
|
||||
if (renderer && renderer->getNpcVoiceManager()) {
|
||||
glm::vec3 renderPos = core::coords::canonicalToRender(position);
|
||||
|
||||
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()->playFarewell(guid, voiceType, renderPos);
|
||||
}
|
||||
});
|
||||
|
||||
// NPC vendor callback - play vendor voice line
|
||||
gameHandler->setNpcVendorCallback([this](uint64_t guid, const glm::vec3& position) {
|
||||
if (renderer && renderer->getNpcVoiceManager()) {
|
||||
glm::vec3 renderPos = core::coords::canonicalToRender(position);
|
||||
|
||||
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()->playVendor(guid, voiceType, renderPos);
|
||||
}
|
||||
});
|
||||
|
||||
// NPC aggro callback - play combat start voice line
|
||||
gameHandler->setNpcAggroCallback([this](uint64_t guid, const glm::vec3& position) {
|
||||
if (renderer && renderer->getNpcVoiceManager()) {
|
||||
glm::vec3 renderPos = core::coords::canonicalToRender(position);
|
||||
|
||||
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()->playAggro(guid, voiceType, renderPos);
|
||||
}
|
||||
});
|
||||
|
||||
// "Create Character" button on character screen
|
||||
uiManager->getCharacterScreen().setOnCreateCharacter([this]() {
|
||||
uiManager->getCharacterCreateScreen().reset();
|
||||
|
|
@ -1936,32 +1987,41 @@ audio::VoiceType Application::detectVoiceTypeFromDisplayId(uint32_t displayId) c
|
|||
// Look up display data
|
||||
auto itDisplay = displayDataMap_.find(displayId);
|
||||
if (itDisplay == displayDataMap_.end() || itDisplay->second.extraDisplayId == 0) {
|
||||
LOG_INFO("Voice detection: displayId ", displayId, " -> GENERIC (no display data)");
|
||||
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()) {
|
||||
LOG_INFO("Voice detection: displayId ", displayId, " -> GENERIC (no humanoid extra data)");
|
||||
return audio::VoiceType::GENERIC;
|
||||
}
|
||||
|
||||
uint8_t raceId = itExtra->second.raceId;
|
||||
uint8_t sexId = itExtra->second.sexId;
|
||||
|
||||
const char* raceName = "Unknown";
|
||||
const char* sexName = (sexId == 0) ? "Male" : "Female";
|
||||
|
||||
// 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
|
||||
audio::VoiceType result;
|
||||
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;
|
||||
case 1: raceName = "Human"; result = (sexId == 0) ? audio::VoiceType::HUMAN_MALE : audio::VoiceType::HUMAN_FEMALE; break;
|
||||
case 2: raceName = "Orc"; result = (sexId == 0) ? audio::VoiceType::ORC_MALE : audio::VoiceType::ORC_FEMALE; break;
|
||||
case 3: raceName = "Dwarf"; result = (sexId == 0) ? audio::VoiceType::DWARF_MALE : audio::VoiceType::GENERIC; break;
|
||||
case 4: raceName = "NightElf"; result = (sexId == 0) ? audio::VoiceType::NIGHTELF_MALE : audio::VoiceType::NIGHTELF_FEMALE; break;
|
||||
case 5: raceName = "Undead"; result = (sexId == 0) ? audio::VoiceType::UNDEAD_MALE : audio::VoiceType::UNDEAD_FEMALE; break;
|
||||
case 6: raceName = "Tauren"; result = (sexId == 0) ? audio::VoiceType::TAUREN_MALE : audio::VoiceType::TAUREN_FEMALE; break;
|
||||
case 7: raceName = "Gnome"; result = (sexId == 0) ? audio::VoiceType::GNOME_MALE : audio::VoiceType::GNOME_FEMALE; break;
|
||||
case 8: raceName = "Troll"; result = (sexId == 0) ? audio::VoiceType::TROLL_MALE : audio::VoiceType::TROLL_FEMALE; break;
|
||||
default: result = audio::VoiceType::GENERIC; break;
|
||||
}
|
||||
|
||||
LOG_INFO("Voice detection: displayId ", displayId, " -> ", raceName, " ", sexName, " (race=", (int)raceId, ", sex=", (int)sexId, ")");
|
||||
return result;
|
||||
}
|
||||
|
||||
void Application::buildGameObjectDisplayLookups() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue