From 3a3e9f3c79868d57ce45e3017f280c30968c34cb Mon Sep 17 00:00:00 2001 From: Kelsi Date: Mon, 9 Feb 2026 16:42:15 -0800 Subject: [PATCH] Add player character combat vocals with 60+ voice lines Implemented player combat vocals for Blood Elf and Draenei races: Player vocal types: - Attack grunts: Multiple variations per race/gender - Wound sounds: Pain reactions when hit - Wound crits: Special sounds for critical hits taken - Death cries: Final sounds when player dies Race coverage (60+ voice lines): - Blood Elf Male: 9 attacks, 8 wounds, 3 crit wounds, 2 deaths - Blood Elf Female: 5 attacks, 7 wounds, 1 death - Draenei Male: 7 attacks, 8 wounds, 3 crit wounds, 2 deaths - Draenei Female: 7 attacks, 4 wounds, 3 crit wounds, 1 death Technical details: - Loads 60+ vocal sound files from Sound\Character\*PC folders - Simple API: playPlayerAttackGrunt(race), playPlayerWound(race, crit), playPlayerDeath(race) - Random variation selection for immersion - Volume at 0.9-1.1 depending on sound type - Crit wounds play 1.1x louder for emphasis - Extensible design ready for other races - Only BC races have dedicated PC vocal folders in WotLK 3.3.5a Usage examples: ```cpp combatSoundManager->playPlayerAttackGrunt(PlayerRace::BLOOD_ELF_MALE); combatSoundManager->playPlayerWound(PlayerRace::DRAENEI_FEMALE, true); // Crit combatSoundManager->playPlayerDeath(PlayerRace::BLOOD_ELF_FEMALE); ``` This adds essential combat immersion with player character reactions! --- include/audio/combat_sound_manager.hpp | 32 +++++ src/audio/combat_sound_manager.cpp | 157 +++++++++++++++++++++++++ 2 files changed, 189 insertions(+) diff --git a/include/audio/combat_sound_manager.hpp b/include/audio/combat_sound_manager.hpp index 90d829bf..8bc1063c 100644 --- a/include/audio/combat_sound_manager.hpp +++ b/include/audio/combat_sound_manager.hpp @@ -50,6 +50,18 @@ public: // Emote sounds void playClap(); + // Player character vocals + enum class PlayerRace { + BLOOD_ELF_MALE, + BLOOD_ELF_FEMALE, + DRAENEI_MALE, + DRAENEI_FEMALE + }; + + void playPlayerAttackGrunt(PlayerRace race); + void playPlayerWound(PlayerRace race, bool isCrit = false); + void playPlayerDeath(PlayerRace race); + private: struct CombatSample { std::string path; @@ -83,6 +95,26 @@ private: // Emote sounds std::vector clapSounds_; + // Player character vocal libraries + std::vector bloodElfMaleAttackSounds_; + std::vector bloodElfMaleWoundSounds_; + std::vector bloodElfMaleWoundCritSounds_; + std::vector bloodElfMaleDeathSounds_; + + std::vector bloodElfFemaleAttackSounds_; + std::vector bloodElfFemaleWoundSounds_; + std::vector bloodElfFemaleDeathSounds_; + + std::vector draeneiMaleAttackSounds_; + std::vector draeneiMaleWoundSounds_; + std::vector draeneiMaleWoundCritSounds_; + std::vector draeneiMaleDeathSounds_; + + std::vector draeneiFemaleAttackSounds_; + std::vector draeneiFemaleWoundSounds_; + std::vector draeneiFemaleWoundCritSounds_; + std::vector draeneiFemaleDeathSounds_; + // State tracking float volumeScale_ = 1.0f; bool initialized_ = false; diff --git a/src/audio/combat_sound_manager.cpp b/src/audio/combat_sound_manager.cpp index fe1e7311..d433a40c 100644 --- a/src/audio/combat_sound_manager.cpp +++ b/src/audio/combat_sound_manager.cpp @@ -104,12 +104,101 @@ bool CombatSoundManager::initialize(pipeline::AssetManager* assets) { loadSound("Sound\\Character\\EmoteClap" + std::to_string(i + 1) + ".wav", clapSounds_[i], assets); } + // Load Blood Elf Male PC vocals + bloodElfMaleAttackSounds_.resize(9); + for (char c = 'A'; c <= 'I'; ++c) { + std::string path = "Sound\\Character\\BloodElfMalePC\\BloodElfMalePCAttack" + std::string(1, c) + ".wav"; + loadSound(path, bloodElfMaleAttackSounds_[c - 'A'], assets); + } + + bloodElfMaleWoundSounds_.resize(8); + for (char c = 'A'; c <= 'H'; ++c) { + std::string path = "Sound\\Character\\BloodElfMalePC\\BloodElfMalePCWound" + std::string(1, c) + ".wav"; + loadSound(path, bloodElfMaleWoundSounds_[c - 'A'], assets); + } + + bloodElfMaleWoundCritSounds_.resize(3); + for (char c = 'A'; c <= 'C'; ++c) { + std::string path = "Sound\\Character\\BloodElfMalePC\\BloodElfMalePCWoundCrit" + std::string(1, c) + ".wav"; + loadSound(path, bloodElfMaleWoundCritSounds_[c - 'A'], assets); + } + + bloodElfMaleDeathSounds_.resize(2); + loadSound("Sound\\Character\\BloodElfMalePC\\BloodElfMalePCDeath.wav", bloodElfMaleDeathSounds_[0], assets); + loadSound("Sound\\Character\\BloodElfMalePC\\BloodElfMalePCDeath2.wav", bloodElfMaleDeathSounds_[1], assets); + + // Load Blood Elf Female PC vocals + bloodElfFemaleAttackSounds_.resize(5); + for (char c = 'A'; c <= 'E'; ++c) { + std::string path = "Sound\\Character\\BloodElfFemalePC\\BloodElfFemalePCAttack" + std::string(1, c) + ".wav"; + loadSound(path, bloodElfFemaleAttackSounds_[c - 'A'], assets); + } + + bloodElfFemaleWoundSounds_.resize(7); + const char* femaleWoundSuffixes[] = {"A", "B", "D", "E", "F", "G", ""}; + for (int i = 0; i < 7; ++i) { + std::string path = "Sound\\Character\\BloodElfFemalePC\\BloodElfFemalePCWound" + std::string(femaleWoundSuffixes[i]) + ".wav"; + loadSound(path, bloodElfFemaleWoundSounds_[i], assets); + } + + bloodElfFemaleDeathSounds_.resize(1); + loadSound("Sound\\Character\\BloodElfFemalePC\\BloodElfFemalePCDeath.wav", bloodElfFemaleDeathSounds_[0], assets); + + // Load Draenei Male PC vocals + draeneiMaleAttackSounds_.resize(7); + for (char c = 'A'; c <= 'G'; ++c) { + std::string path = "Sound\\Character\\DraeneiMalePC\\DraeneiMalePCAttack" + std::string(1, c) + ".wav"; + loadSound(path, draeneiMaleAttackSounds_[c - 'A'], assets); + } + + draeneiMaleWoundSounds_.resize(8); + for (char c = 'A'; c <= 'H'; ++c) { + std::string path = "Sound\\Character\\DraeneiMalePC\\DraeneiMalePCWound" + std::string(1, c) + ".wav"; + loadSound(path, draeneiMaleWoundSounds_[c - 'A'], assets); + } + + draeneiMaleWoundCritSounds_.resize(3); + for (char c = 'A'; c <= 'C'; ++c) { + std::string path = "Sound\\Character\\DraeneiMalePC\\DraeneiMalePCWoundCrit" + std::string(1, c) + ".wav"; + loadSound(path, draeneiMaleWoundCritSounds_[c - 'A'], assets); + } + + draeneiMaleDeathSounds_.resize(2); + loadSound("Sound\\Character\\DraeneiMalePC\\DraeneiMalePCDeath.wav", draeneiMaleDeathSounds_[0], assets); + loadSound("Sound\\Character\\DraeneiMalePC\\DraeneiMalePCDeath2.wav", draeneiMaleDeathSounds_[1], assets); + + // Load Draenei Female PC vocals + draeneiFemaleAttackSounds_.resize(7); + for (char c = 'A'; c <= 'G'; ++c) { + std::string path = "Sound\\Character\\DraeneiFemalePC\\DraeneiFemalePCAttack" + std::string(1, c) + ".wav"; + loadSound(path, draeneiFemaleAttackSounds_[c - 'A'], assets); + } + + draeneiFemaleWoundSounds_.resize(4); + for (char c = 'A'; c <= 'D'; ++c) { + std::string path = "Sound\\Character\\DraeneiFemalePC\\DraeneiFemalePCWound" + std::string(1, c) + ".wav"; + loadSound(path, draeneiFemaleWoundSounds_[c - 'A'], assets); + } + + draeneiFemaleWoundCritSounds_.resize(3); + for (char c = 'A'; c <= 'C'; ++c) { + std::string path = "Sound\\Character\\DraeneiFemalePC\\DraeneiFemalePCWoundCrit" + std::string(1, c) + ".wav"; + loadSound(path, draeneiFemaleWoundCritSounds_[c - 'A'], assets); + } + + draeneiFemaleDeathSounds_.resize(1); + loadSound("Sound\\Character\\DraeneiFemalePC\\DraeneiFemalePCDeath.wav", draeneiFemaleDeathSounds_[0], assets); + LOG_INFO("CombatSoundManager: Weapon swings - Small: ", swingSmallSounds_[0].loaded ? "YES" : "NO", ", Medium: ", swingMediumSounds_[0].loaded ? "YES" : "NO", ", Large: ", swingLargeSounds_[0].loaded ? "YES" : "NO"); LOG_INFO("CombatSoundManager: Impact sounds - Flesh: ", hitFleshSounds_[0].loaded ? "YES" : "NO", ", Chain: ", hitChainSounds_[0].loaded ? "YES" : "NO", ", Plate: ", hitPlateSounds_[0].loaded ? "YES" : "NO"); + LOG_INFO("CombatSoundManager: Player vocals - BE Male: ", bloodElfMaleAttackSounds_[0].loaded ? "YES" : "NO", + ", BE Female: ", bloodElfFemaleAttackSounds_[0].loaded ? "YES" : "NO", + ", Draenei Male: ", draeneiMaleAttackSounds_[0].loaded ? "YES" : "NO", + ", Draenei Female: ", draeneiFemaleAttackSounds_[0].loaded ? "YES" : "NO"); LOG_INFO("CombatSoundManager: Emote sounds - Clap: ", clapSounds_[0].loaded ? "YES" : "NO"); initialized_ = true; @@ -253,5 +342,73 @@ void CombatSoundManager::playClap() { playRandomSound(clapSounds_, 0.9f); } +void CombatSoundManager::playPlayerAttackGrunt(PlayerRace race) { + switch (race) { + case PlayerRace::BLOOD_ELF_MALE: + playRandomSound(bloodElfMaleAttackSounds_, 0.9f); + break; + case PlayerRace::BLOOD_ELF_FEMALE: + playRandomSound(bloodElfFemaleAttackSounds_, 0.9f); + break; + case PlayerRace::DRAENEI_MALE: + playRandomSound(draeneiMaleAttackSounds_, 0.9f); + break; + case PlayerRace::DRAENEI_FEMALE: + playRandomSound(draeneiFemaleAttackSounds_, 0.9f); + break; + } +} + +void CombatSoundManager::playPlayerWound(PlayerRace race, bool isCrit) { + if (isCrit) { + switch (race) { + case PlayerRace::BLOOD_ELF_MALE: + playRandomSound(bloodElfMaleWoundCritSounds_, 1.1f); + break; + case PlayerRace::BLOOD_ELF_FEMALE: + playRandomSound(bloodElfFemaleWoundSounds_, 1.1f); // No separate crit sounds + break; + case PlayerRace::DRAENEI_MALE: + playRandomSound(draeneiMaleWoundCritSounds_, 1.1f); + break; + case PlayerRace::DRAENEI_FEMALE: + playRandomSound(draeneiFemaleWoundCritSounds_, 1.1f); + break; + } + } else { + switch (race) { + case PlayerRace::BLOOD_ELF_MALE: + playRandomSound(bloodElfMaleWoundSounds_, 0.9f); + break; + case PlayerRace::BLOOD_ELF_FEMALE: + playRandomSound(bloodElfFemaleWoundSounds_, 0.9f); + break; + case PlayerRace::DRAENEI_MALE: + playRandomSound(draeneiMaleWoundSounds_, 0.9f); + break; + case PlayerRace::DRAENEI_FEMALE: + playRandomSound(draeneiFemaleWoundSounds_, 0.9f); + break; + } + } +} + +void CombatSoundManager::playPlayerDeath(PlayerRace race) { + switch (race) { + case PlayerRace::BLOOD_ELF_MALE: + playRandomSound(bloodElfMaleDeathSounds_, 1.0f); + break; + case PlayerRace::BLOOD_ELF_FEMALE: + playRandomSound(bloodElfFemaleDeathSounds_, 1.0f); + break; + case PlayerRace::DRAENEI_MALE: + playRandomSound(draeneiMaleDeathSounds_, 1.0f); + break; + case PlayerRace::DRAENEI_FEMALE: + playRandomSound(draeneiFemaleDeathSounds_, 1.0f); + break; + } +} + } // namespace audio } // namespace wowee