From 1e23370c0eee0ba62fa66207ec18c6145df681a1 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Mon, 9 Feb 2026 16:50:37 -0800 Subject: [PATCH] Add movement sound manager for water splashes and jump/land vocalizations Implements water splash sounds (enter water + footsteps) with size-based intensity and jump/land vocalizations for all 16 race/gender combinations. --- CMakeLists.txt | 1 + include/audio/movement_sound_manager.hpp | 124 ++++++++ src/audio/movement_sound_manager.cpp | 354 +++++++++++++++++++++++ 3 files changed, 479 insertions(+) create mode 100644 include/audio/movement_sound_manager.hpp create mode 100644 src/audio/movement_sound_manager.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 6d5ad953..3921416c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -109,6 +109,7 @@ set(WOWEE_SOURCES src/audio/ui_sound_manager.cpp src/audio/combat_sound_manager.cpp src/audio/spell_sound_manager.cpp + src/audio/movement_sound_manager.cpp # Pipeline (asset loaders) src/pipeline/mpq_manager.cpp diff --git a/include/audio/movement_sound_manager.hpp b/include/audio/movement_sound_manager.hpp new file mode 100644 index 00000000..87b179dd --- /dev/null +++ b/include/audio/movement_sound_manager.hpp @@ -0,0 +1,124 @@ +#pragma once + +#include +#include +#include +#include + +namespace wowee { +namespace pipeline { +class AssetManager; +} + +namespace audio { + +class MovementSoundManager { +public: + MovementSoundManager() = default; + ~MovementSoundManager() = default; + + // Initialization + bool initialize(pipeline::AssetManager* assets); + void shutdown(); + + // Volume control + void setVolumeScale(float scale); + + // Character size (for water splash intensity) + enum class CharacterSize { + SMALL, // Gnome, Dwarf + MEDIUM, // Human, Night Elf, Undead, Troll, Blood Elf, Draenei + LARGE, // Orc, Tauren + GIANT // Large NPCs, bosses + }; + + // Player race (for jump/land vocalizations) + enum class PlayerRace { + HUMAN_MALE, + HUMAN_FEMALE, + DWARF_MALE, + DWARF_FEMALE, + NIGHT_ELF_MALE, + NIGHT_ELF_FEMALE, + ORC_MALE, + ORC_FEMALE, + TAUREN_MALE, + TAUREN_FEMALE, + TROLL_MALE, + TROLL_FEMALE, + UNDEAD_MALE, + UNDEAD_FEMALE, + GNOME_MALE, + GNOME_FEMALE + }; + + // Water interaction sounds + void playEnterWater(CharacterSize size); // Jumping into water + void playWaterFootstep(CharacterSize size); // Walking/running in water + + // Jump/Land sounds (player vocalizations) + void playJump(PlayerRace race); + void playLand(PlayerRace race); + +private: + struct MovementSample { + std::string path; + std::vector data; + bool loaded; + }; + + // Water splash sound libraries + std::vector enterWaterSmallSounds_; + std::vector enterWaterMediumSounds_; + std::vector enterWaterGiantSounds_; + + std::vector waterFootstepSmallSounds_; + std::vector waterFootstepMediumSounds_; + std::vector waterFootstepHugeSounds_; + + // Jump/Land vocal libraries (all 16 race/gender combos) + std::vector jumpHumanMaleSounds_; + std::vector landHumanMaleSounds_; + std::vector jumpHumanFemaleSounds_; + std::vector landHumanFemaleSounds_; + std::vector jumpDwarfMaleSounds_; + std::vector landDwarfMaleSounds_; + std::vector jumpDwarfFemaleSounds_; + std::vector landDwarfFemaleSounds_; + std::vector jumpNightElfMaleSounds_; + std::vector landNightElfMaleSounds_; + std::vector jumpNightElfFemaleSounds_; + std::vector landNightElfFemaleSounds_; + std::vector jumpOrcMaleSounds_; + std::vector landOrcMaleSounds_; + std::vector jumpOrcFemaleSounds_; + std::vector landOrcFemaleSounds_; + std::vector jumpTaurenMaleSounds_; + std::vector landTaurenMaleSounds_; + std::vector jumpTaurenFemaleSounds_; + std::vector landTaurenFemaleSounds_; + std::vector jumpTrollMaleSounds_; + std::vector landTrollMaleSounds_; + std::vector jumpTrollFemaleSounds_; + std::vector landTrollFemaleSounds_; + std::vector jumpUndeadMaleSounds_; + std::vector landUndeadMaleSounds_; + std::vector jumpUndeadFemaleSounds_; + std::vector landUndeadFemaleSounds_; + std::vector jumpGnomeMaleSounds_; + std::vector landGnomeMaleSounds_; + std::vector jumpGnomeFemaleSounds_; + std::vector landGnomeFemaleSounds_; + + // State tracking + float volumeScale_ = 1.0f; + bool initialized_ = false; + + // Helper methods + bool loadSound(const std::string& path, MovementSample& sample, pipeline::AssetManager* assets); + void playSound(const std::vector& library, float volumeMultiplier = 1.0f); + void playRandomSound(const std::vector& library, float volumeMultiplier = 1.0f); +}; + +} // namespace audio +} // namespace wowee diff --git a/src/audio/movement_sound_manager.cpp b/src/audio/movement_sound_manager.cpp new file mode 100644 index 00000000..07f9b51b --- /dev/null +++ b/src/audio/movement_sound_manager.cpp @@ -0,0 +1,354 @@ +#include "audio/movement_sound_manager.hpp" +#include "audio/audio_engine.hpp" +#include "pipeline/asset_manager.hpp" +#include "core/logger.hpp" +#include + +namespace wowee { +namespace audio { + +namespace { + std::random_device rd; + std::mt19937 gen(rd()); +} + +bool MovementSoundManager::initialize(pipeline::AssetManager* assets) { + if (!assets) { + LOG_ERROR("MovementSoundManager: AssetManager is null"); + return false; + } + + LOG_INFO("MovementSoundManager: Initializing..."); + + // Load water splash sounds - entering water + enterWaterSmallSounds_.resize(1); + loadSound("Sound\\Spells\\EnterWaterSmall.wav", enterWaterSmallSounds_[0], assets); + + enterWaterMediumSounds_.resize(1); + loadSound("Sound\\Spells\\EnterWaterMedium.wav", enterWaterMediumSounds_[0], assets); + + enterWaterGiantSounds_.resize(1); + loadSound("Sound\\Spells\\EnterWaterGiant.wav", enterWaterGiantSounds_[0], assets); + + // Load water footstep sounds - walking in water (5 variations each) + waterFootstepSmallSounds_.resize(5); + loadSound("Sound\\Spells\\WaterFootstepSmall1.wav", waterFootstepSmallSounds_[0], assets); + loadSound("Sound\\Spells\\WaterFootstepSmall2.wav", waterFootstepSmallSounds_[1], assets); + loadSound("Sound\\Spells\\WaterFootstepSmall3.wav", waterFootstepSmallSounds_[2], assets); + loadSound("Sound\\Spells\\WaterFootstepSmall4.wav", waterFootstepSmallSounds_[3], assets); + loadSound("Sound\\Spells\\WaterFootstepSmall5.wav", waterFootstepSmallSounds_[4], assets); + + waterFootstepMediumSounds_.resize(5); + loadSound("Sound\\Spells\\WaterFootstepMedium1.wav", waterFootstepMediumSounds_[0], assets); + loadSound("Sound\\Spells\\WaterFootstepMedium2.wav", waterFootstepMediumSounds_[1], assets); + loadSound("Sound\\Spells\\WaterFootstepMedium3.wav", waterFootstepMediumSounds_[2], assets); + loadSound("Sound\\Spells\\WaterFootstepMedium4.wav", waterFootstepMediumSounds_[3], assets); + loadSound("Sound\\Spells\\WaterFootstepMedium5.wav", waterFootstepMediumSounds_[4], assets); + + waterFootstepHugeSounds_.resize(5); + loadSound("Sound\\Spells\\WaterFootstepHuge1.wav", waterFootstepHugeSounds_[0], assets); + loadSound("Sound\\Spells\\WaterFootstepHuge2.wav", waterFootstepHugeSounds_[1], assets); + loadSound("Sound\\Spells\\WaterFootstepHuge3.wav", waterFootstepHugeSounds_[2], assets); + loadSound("Sound\\Spells\\WaterFootstepHuge4.wav", waterFootstepHugeSounds_[3], assets); + loadSound("Sound\\Spells\\WaterFootstepHuge5.wav", waterFootstepHugeSounds_[4], assets); + + // Load jump/land vocalizations for all races + // Human Male + jumpHumanMaleSounds_.resize(1); + loadSound("Sound\\Character\\Human\\HumanMaleJump1.wav", jumpHumanMaleSounds_[0], assets); + landHumanMaleSounds_.resize(1); + loadSound("Sound\\Character\\Human\\HumanMaleLand1.wav", landHumanMaleSounds_[0], assets); + + // Human Female + jumpHumanFemaleSounds_.resize(1); + loadSound("Sound\\Character\\Human\\HumanFemaleJump1.wav", jumpHumanFemaleSounds_[0], assets); + landHumanFemaleSounds_.resize(1); + loadSound("Sound\\Character\\Human\\HumanFemaleLand1.wav", landHumanFemaleSounds_[0], assets); + + // Dwarf Male + jumpDwarfMaleSounds_.resize(1); + loadSound("Sound\\Character\\Dwarf\\DwarfMaleJump1.wav", jumpDwarfMaleSounds_[0], assets); + landDwarfMaleSounds_.resize(1); + loadSound("Sound\\Character\\Dwarf\\DwarfMaleLand1.wav", landDwarfMaleSounds_[0], assets); + + // Dwarf Female + jumpDwarfFemaleSounds_.resize(1); + loadSound("Sound\\Character\\Dwarf\\DwarfFemaleJump1.wav", jumpDwarfFemaleSounds_[0], assets); + landDwarfFemaleSounds_.resize(1); + loadSound("Sound\\Character\\Dwarf\\DwarfFemaleLand1.wav", landDwarfFemaleSounds_[0], assets); + + // Night Elf Male + jumpNightElfMaleSounds_.resize(1); + loadSound("Sound\\Character\\NightElf\\NightElfMaleJump1.wav", jumpNightElfMaleSounds_[0], assets); + landNightElfMaleSounds_.resize(1); + loadSound("Sound\\Character\\NightElf\\NightElfMaleLand1.wav", landNightElfMaleSounds_[0], assets); + + // Night Elf Female + jumpNightElfFemaleSounds_.resize(1); + loadSound("Sound\\Character\\NightElf\\NightElfFemaleJump1.wav", jumpNightElfFemaleSounds_[0], assets); + landNightElfFemaleSounds_.resize(1); + loadSound("Sound\\Character\\NightElf\\NightElfFemaleLand1.wav", landNightElfFemaleSounds_[0], assets); + + // Orc Male + jumpOrcMaleSounds_.resize(1); + loadSound("Sound\\Character\\Orc\\OrcMaleJump1.wav", jumpOrcMaleSounds_[0], assets); + landOrcMaleSounds_.resize(1); + loadSound("Sound\\Character\\Orc\\OrcMaleLand1.wav", landOrcMaleSounds_[0], assets); + + // Orc Female + jumpOrcFemaleSounds_.resize(1); + loadSound("Sound\\Character\\Orc\\OrcFemaleJump1.wav", jumpOrcFemaleSounds_[0], assets); + landOrcFemaleSounds_.resize(1); + loadSound("Sound\\Character\\Orc\\OrcFemaleLand1.wav", landOrcFemaleSounds_[0], assets); + + // Tauren Male + jumpTaurenMaleSounds_.resize(1); + loadSound("Sound\\Character\\Tauren\\TaurenMaleJump1.wav", jumpTaurenMaleSounds_[0], assets); + landTaurenMaleSounds_.resize(1); + loadSound("Sound\\Character\\Tauren\\TaurenMaleLand1.wav", landTaurenMaleSounds_[0], assets); + + // Tauren Female + jumpTaurenFemaleSounds_.resize(1); + loadSound("Sound\\Character\\Tauren\\TaurenFemaleJump1.wav", jumpTaurenFemaleSounds_[0], assets); + landTaurenFemaleSounds_.resize(1); + loadSound("Sound\\Character\\Tauren\\TaurenFemaleLand1.wav", landTaurenFemaleSounds_[0], assets); + + // Troll Male + jumpTrollMaleSounds_.resize(1); + loadSound("Sound\\Character\\Troll\\TrollMaleJump1.wav", jumpTrollMaleSounds_[0], assets); + landTrollMaleSounds_.resize(1); + loadSound("Sound\\Character\\Troll\\TrollMaleLand1.wav", landTrollMaleSounds_[0], assets); + + // Troll Female + jumpTrollFemaleSounds_.resize(1); + loadSound("Sound\\Character\\Troll\\TrollFemaleJump1.wav", jumpTrollFemaleSounds_[0], assets); + landTrollFemaleSounds_.resize(1); + loadSound("Sound\\Character\\Troll\\TrollFemaleLand1.wav", landTrollFemaleSounds_[0], assets); + + // Undead Male + jumpUndeadMaleSounds_.resize(1); + loadSound("Sound\\Character\\Scourge\\ScourgeMaleJump1.wav", jumpUndeadMaleSounds_[0], assets); + landUndeadMaleSounds_.resize(1); + loadSound("Sound\\Character\\Scourge\\ScourgeMaleLand1.wav", landUndeadMaleSounds_[0], assets); + + // Undead Female + jumpUndeadFemaleSounds_.resize(1); + loadSound("Sound\\Character\\Scourge\\ScourgeFemaleJump1.wav", jumpUndeadFemaleSounds_[0], assets); + landUndeadFemaleSounds_.resize(1); + loadSound("Sound\\Character\\Scourge\\ScourgeFemaleLand1.wav", landUndeadFemaleSounds_[0], assets); + + // Gnome Male + jumpGnomeMaleSounds_.resize(1); + loadSound("Sound\\Character\\Gnome\\GnomeMaleJump1.wav", jumpGnomeMaleSounds_[0], assets); + landGnomeMaleSounds_.resize(1); + loadSound("Sound\\Character\\Gnome\\GnomeMaleLand1.wav", landGnomeMaleSounds_[0], assets); + + // Gnome Female + jumpGnomeFemaleSounds_.resize(1); + loadSound("Sound\\Character\\Gnome\\GnomeFemaleJump1.wav", jumpGnomeFemaleSounds_[0], assets); + landGnomeFemaleSounds_.resize(1); + loadSound("Sound\\Character\\Gnome\\GnomeFemaleLand1.wav", landGnomeFemaleSounds_[0], assets); + + LOG_INFO("MovementSoundManager: Water sounds - Enter small: ", enterWaterSmallSounds_[0].loaded ? "YES" : "NO", + ", Enter medium: ", enterWaterMediumSounds_[0].loaded ? "YES" : "NO", + ", Enter giant: ", enterWaterGiantSounds_[0].loaded ? "YES" : "NO"); + LOG_INFO("MovementSoundManager: Jump/Land - Human: ", jumpHumanMaleSounds_[0].loaded ? "YES" : "NO", + ", Orc: ", jumpOrcMaleSounds_[0].loaded ? "YES" : "NO", + ", Tauren: ", jumpTaurenMaleSounds_[0].loaded ? "YES" : "NO"); + + initialized_ = true; + LOG_INFO("MovementSoundManager: Initialization complete"); + return true; +} + +void MovementSoundManager::shutdown() { + initialized_ = false; +} + +bool MovementSoundManager::loadSound(const std::string& path, MovementSample& sample, pipeline::AssetManager* assets) { + sample.path = path; + sample.loaded = false; + + try { + sample.data = assets->readFile(path); + if (!sample.data.empty()) { + sample.loaded = true; + return true; + } + } catch (const std::exception& e) { + // Silently fail - not all sounds may exist + } + + return false; +} + +void MovementSoundManager::playSound(const std::vector& library, float volumeMultiplier) { + if (!initialized_ || library.empty() || !library[0].loaded) return; + + float volume = 0.7f * volumeScale_ * volumeMultiplier; + AudioEngine::instance().playSound2D(library[0].data, volume, 1.0f); +} + +void MovementSoundManager::playRandomSound(const std::vector& library, float volumeMultiplier) { + if (!initialized_ || library.empty()) return; + + // Count loaded sounds + std::vector loadedSounds; + for (const auto& sample : library) { + if (sample.loaded) { + loadedSounds.push_back(&sample); + } + } + + if (loadedSounds.empty()) return; + + // Pick random sound + std::uniform_int_distribution dist(0, loadedSounds.size() - 1); + size_t index = dist(gen); + + float volume = 0.7f * volumeScale_ * volumeMultiplier; + AudioEngine::instance().playSound2D(loadedSounds[index]->data, volume, 1.0f); +} + +void MovementSoundManager::setVolumeScale(float scale) { + volumeScale_ = std::max(0.0f, std::min(1.0f, scale)); +} + +void MovementSoundManager::playEnterWater(CharacterSize size) { + switch (size) { + case CharacterSize::SMALL: + playSound(enterWaterSmallSounds_, 0.8f); + break; + case CharacterSize::MEDIUM: + playSound(enterWaterMediumSounds_, 1.0f); + break; + case CharacterSize::LARGE: + case CharacterSize::GIANT: + playSound(enterWaterGiantSounds_, 1.2f); + break; + } +} + +void MovementSoundManager::playWaterFootstep(CharacterSize size) { + switch (size) { + case CharacterSize::SMALL: + playRandomSound(waterFootstepSmallSounds_, 0.6f); + break; + case CharacterSize::MEDIUM: + playRandomSound(waterFootstepMediumSounds_, 0.8f); + break; + case CharacterSize::LARGE: + case CharacterSize::GIANT: + playRandomSound(waterFootstepHugeSounds_, 1.0f); + break; + } +} + +void MovementSoundManager::playJump(PlayerRace race) { + switch (race) { + case PlayerRace::HUMAN_MALE: + playSound(jumpHumanMaleSounds_); + break; + case PlayerRace::HUMAN_FEMALE: + playSound(jumpHumanFemaleSounds_); + break; + case PlayerRace::DWARF_MALE: + playSound(jumpDwarfMaleSounds_); + break; + case PlayerRace::DWARF_FEMALE: + playSound(jumpDwarfFemaleSounds_); + break; + case PlayerRace::NIGHT_ELF_MALE: + playSound(jumpNightElfMaleSounds_); + break; + case PlayerRace::NIGHT_ELF_FEMALE: + playSound(jumpNightElfFemaleSounds_); + break; + case PlayerRace::ORC_MALE: + playSound(jumpOrcMaleSounds_); + break; + case PlayerRace::ORC_FEMALE: + playSound(jumpOrcFemaleSounds_); + break; + case PlayerRace::TAUREN_MALE: + playSound(jumpTaurenMaleSounds_); + break; + case PlayerRace::TAUREN_FEMALE: + playSound(jumpTaurenFemaleSounds_); + break; + case PlayerRace::TROLL_MALE: + playSound(jumpTrollMaleSounds_); + break; + case PlayerRace::TROLL_FEMALE: + playSound(jumpTrollFemaleSounds_); + break; + case PlayerRace::UNDEAD_MALE: + playSound(jumpUndeadMaleSounds_); + break; + case PlayerRace::UNDEAD_FEMALE: + playSound(jumpUndeadFemaleSounds_); + break; + case PlayerRace::GNOME_MALE: + playSound(jumpGnomeMaleSounds_); + break; + case PlayerRace::GNOME_FEMALE: + playSound(jumpGnomeFemaleSounds_); + break; + } +} + +void MovementSoundManager::playLand(PlayerRace race) { + switch (race) { + case PlayerRace::HUMAN_MALE: + playSound(landHumanMaleSounds_); + break; + case PlayerRace::HUMAN_FEMALE: + playSound(landHumanFemaleSounds_); + break; + case PlayerRace::DWARF_MALE: + playSound(landDwarfMaleSounds_); + break; + case PlayerRace::DWARF_FEMALE: + playSound(landDwarfFemaleSounds_); + break; + case PlayerRace::NIGHT_ELF_MALE: + playSound(landNightElfMaleSounds_); + break; + case PlayerRace::NIGHT_ELF_FEMALE: + playSound(landNightElfFemaleSounds_); + break; + case PlayerRace::ORC_MALE: + playSound(landOrcMaleSounds_); + break; + case PlayerRace::ORC_FEMALE: + playSound(landOrcFemaleSounds_); + break; + case PlayerRace::TAUREN_MALE: + playSound(landTaurenMaleSounds_); + break; + case PlayerRace::TAUREN_FEMALE: + playSound(landTaurenFemaleSounds_); + break; + case PlayerRace::TROLL_MALE: + playSound(landTrollMaleSounds_); + break; + case PlayerRace::TROLL_FEMALE: + playSound(landTrollFemaleSounds_); + break; + case PlayerRace::UNDEAD_MALE: + playSound(landUndeadMaleSounds_); + break; + case PlayerRace::UNDEAD_FEMALE: + playSound(landUndeadFemaleSounds_); + break; + case PlayerRace::GNOME_MALE: + playSound(landGnomeMaleSounds_); + break; + case PlayerRace::GNOME_FEMALE: + playSound(landGnomeFemaleSounds_); + break; + } +} + +} // namespace audio +} // namespace wowee