From 77a9b3192dab65f461f3d0b923daf2916b7fa566 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Mon, 9 Feb 2026 16:14:03 -0800 Subject: [PATCH] Add major city ambient audio with day/night variations Implemented city-specific ambient soundscapes for all six major cities: Alliance cities: - Stormwind: day/night crowd and marketplace sounds - Ironforge: underground forge ambience (no day/night) - Darnassus: day/night elven city sounds Horde cities: - Orgrimmar: day/night orcish city atmosphere - Undercity: underground undead ambience (no day/night) - Thunder Bluff: day/night tauren plateau sounds Technical details: - Added CityType enum (NONE, STORMWIND, IRONFORGE, DARNASSUS, ORGRIMMAR, UNDERCITY, THUNDERBLUFF) - Loads 12 city sound files from Sound\Ambience\WMOAmbience - Underground cities (Ironforge, Undercity) use single sound without day/night variants - 20s loop interval for city ambience (more frequent than zone ambience) - Volume at 0.4 for noticeable but not overwhelming urban atmosphere - Cities take priority over zone ambience to prevent mixing - updateZoneAmbience() now checks for active city and skips if in city - State change logging for debugging city transitions --- include/audio/ambient_sound_manager.hpp | 28 ++++++++ src/audio/ambient_sound_manager.cpp | 96 ++++++++++++++++++++++++- 2 files changed, 121 insertions(+), 3 deletions(-) diff --git a/include/audio/ambient_sound_manager.hpp b/include/audio/ambient_sound_manager.hpp index caa5d4dd..fb880584 100644 --- a/include/audio/ambient_sound_manager.hpp +++ b/include/audio/ambient_sound_manager.hpp @@ -45,6 +45,19 @@ public: void setZoneType(ZoneType type); ZoneType getCurrentZone() const { return currentZone_; } + // City ambience control + enum class CityType { + NONE, + STORMWIND, + IRONFORGE, + DARNASSUS, + ORGRIMMAR, + UNDERCITY, + THUNDERBLUFF + }; + void setCityType(CityType type); + CityType getCurrentCity() const { return currentCity_; } + // Emitter management enum class AmbientType { FIREPLACE_SMALL, @@ -127,6 +140,18 @@ private: std::vector desertPlainsDaySounds_; std::vector desertPlainsNightSounds_; + // City ambience libraries (day and night versions) + std::vector stormwindDaySounds_; + std::vector stormwindNightSounds_; + std::vector ironforgeSounds_; // No separate day/night + std::vector darnassusDaySounds_; + std::vector darnassusNightSounds_; + std::vector orgrimmarDaySounds_; + std::vector orgrimmarNightSounds_; + std::vector undercitySounds_; // No separate day/night (underground) + std::vector thunderbluffDaySounds_; + std::vector thunderbluffNightSounds_; + // Active emitters std::vector emitters_; uint64_t nextEmitterId_ = 1; @@ -141,12 +166,14 @@ private: float weatherLoopTime_ = 0.0f; float oceanLoopTime_ = 0.0f; float zoneLoopTime_ = 0.0f; + float cityLoopTime_ = 0.0f; bool wasIndoor_ = false; bool wasBlacksmith_ = false; bool wasSwimming_ = false; bool initialized_ = false; WeatherType currentWeather_ = WeatherType::NONE; ZoneType currentZone_ = ZoneType::NONE; + CityType currentCity_ = CityType::NONE; // Active audio tracking struct ActiveSound { @@ -163,6 +190,7 @@ private: void updateWeatherAmbience(float deltaTime, bool isIndoor); void updateWaterAmbience(float deltaTime, bool isSwimming); void updateZoneAmbience(float deltaTime, bool isIndoor); + void updateCityAmbience(float deltaTime); bool loadSound(const std::string& path, AmbientSample& sample, pipeline::AssetManager* assets); // Time of day helpers diff --git a/src/audio/ambient_sound_manager.cpp b/src/audio/ambient_sound_manager.cpp index 33f1f936..61925e2f 100644 --- a/src/audio/ambient_sound_manager.cpp +++ b/src/audio/ambient_sound_manager.cpp @@ -152,6 +152,37 @@ bool AmbientSoundManager::initialize(pipeline::AssetManager* assets) { desertPlainsNightSounds_.resize(1); bool desertPlainsNightLoaded = loadSound("Sound\\Ambience\\ZoneAmbience\\PlainsDesertNight.wav", desertPlainsNightSounds_[0], assets); + // Load city ambience sounds (day and night where available) + stormwindDaySounds_.resize(1); + bool stormwindDayLoaded = loadSound("Sound\\Ambience\\WMOAmbience\\StormwindDay.wav", stormwindDaySounds_[0], assets); + + stormwindNightSounds_.resize(1); + bool stormwindNightLoaded = loadSound("Sound\\Ambience\\WMOAmbience\\StormwindNight.wav", stormwindNightSounds_[0], assets); + + ironforgeSounds_.resize(1); + bool ironforgeLoaded = loadSound("Sound\\Ambience\\WMOAmbience\\Ironforge.wav", ironforgeSounds_[0], assets); + + darnassusDaySounds_.resize(1); + bool darnassusDayLoaded = loadSound("Sound\\Ambience\\WMOAmbience\\DarnassusDay.wav", darnassusDaySounds_[0], assets); + + darnassusNightSounds_.resize(1); + bool darnassusNightLoaded = loadSound("Sound\\Ambience\\WMOAmbience\\DarnassusNight.wav", darnassusNightSounds_[0], assets); + + orgrimmarDaySounds_.resize(1); + bool orgrimmarDayLoaded = loadSound("Sound\\Ambience\\WMOAmbience\\OrgrimmarDay.wav", orgrimmarDaySounds_[0], assets); + + orgrimmarNightSounds_.resize(1); + bool orgrimmarNightLoaded = loadSound("Sound\\Ambience\\WMOAmbience\\OrgrimmarNight.wav", orgrimmarNightSounds_[0], assets); + + undercitySounds_.resize(1); + bool undercityLoaded = loadSound("Sound\\Ambience\\WMOAmbience\\Undercity.wav", undercitySounds_[0], assets); + + thunderbluffDaySounds_.resize(1); + bool thunderbluffDayLoaded = loadSound("Sound\\Ambience\\WMOAmbience\\ThunderBluffDay.wav", thunderbluffDaySounds_[0], assets); + + thunderbluffNightSounds_.resize(1); + bool thunderbluffNightLoaded = loadSound("Sound\\Ambience\\WMOAmbience\\ThunderBluffNight.wav", thunderbluffNightSounds_[0], assets); + LOG_INFO("AmbientSoundManager: Wind loaded: ", windLoaded ? "YES" : "NO", ", Tavern loaded: ", tavernLoaded ? "YES" : "NO", ", Blacksmith loaded: ", blacksmithLoaded ? "YES" : "NO"); @@ -162,6 +193,9 @@ bool AmbientSoundManager::initialize(pipeline::AssetManager* assets) { LOG_INFO("AmbientSoundManager: Zone sounds - Forest: ", (forestDayLoaded && forestNightLoaded) ? "YES" : "NO", ", Beach: ", (beachDayLoaded && beachNightLoaded) ? "YES" : "NO", ", Desert: ", (desertCanyonDayLoaded && desertPlainsDayLoaded) ? "YES" : "NO"); + LOG_INFO("AmbientSoundManager: City sounds - Stormwind: ", (stormwindDayLoaded && stormwindNightLoaded) ? "YES" : "NO", + ", Ironforge: ", ironforgeLoaded ? "YES" : "NO", + ", Orgrimmar: ", (orgrimmarDayLoaded && orgrimmarNightLoaded) ? "YES" : "NO"); // Initialize timers with random offsets birdTimer_ = randomFloat(0.0f, 5.0f); @@ -219,10 +253,11 @@ void AmbientSoundManager::update(float deltaTime, const glm::vec3& cameraPos, bo updateWindAmbience(deltaTime, isIndoor); } - // Update weather, water, and zone ambience + // Update weather, water, zone, and city ambience updateWeatherAmbience(deltaTime, isIndoor); updateWaterAmbience(deltaTime, isSwimming); updateZoneAmbience(deltaTime, isIndoor); + updateCityAmbience(deltaTime); // Track indoor state changes wasIndoor_ = isIndoor; @@ -481,6 +516,15 @@ void AmbientSoundManager::setZoneType(ZoneType type) { } } +void AmbientSoundManager::setCityType(CityType type) { + if (currentCity_ != type) { + LOG_INFO("AmbientSoundManager: City changed from ", static_cast(currentCity_), + " to ", static_cast(type)); + currentCity_ = type; + cityLoopTime_ = 12.0f; // Play city ambience soon after entering + } +} + void AmbientSoundManager::updateWeatherAmbience(float deltaTime, bool isIndoor) { // Don't play weather sounds when indoors if (isIndoor || currentWeather_ == WeatherType::NONE) return; @@ -559,8 +603,8 @@ void AmbientSoundManager::updateWaterAmbience(float deltaTime, bool isSwimming) } void AmbientSoundManager::updateZoneAmbience(float deltaTime, bool isIndoor) { - // Don't play zone ambience when indoors - if (isIndoor || currentZone_ == ZoneType::NONE) return; + // Don't play zone ambience when indoors or in cities + if (isIndoor || currentZone_ == ZoneType::NONE || currentCity_ != CityType::NONE) return; zoneLoopTime_ += deltaTime; @@ -610,5 +654,51 @@ void AmbientSoundManager::updateZoneAmbience(float deltaTime, bool isIndoor) { } } +void AmbientSoundManager::updateCityAmbience(float deltaTime) { + // Only play city ambience when actually in a city + if (currentCity_ == CityType::NONE) return; + + cityLoopTime_ += deltaTime; + + // Select appropriate sound library based on city type and time of day + const std::vector* cityLibrary = nullptr; + bool isDay = isDaytime(); + + switch (currentCity_) { + case CityType::STORMWIND: + cityLibrary = isDay ? &stormwindDaySounds_ : &stormwindNightSounds_; + break; + case CityType::IRONFORGE: + cityLibrary = &ironforgeSounds_; // No day/night (underground) + break; + case CityType::DARNASSUS: + cityLibrary = isDay ? &darnassusDaySounds_ : &darnassusNightSounds_; + break; + case CityType::ORGRIMMAR: + cityLibrary = isDay ? &orgrimmarDaySounds_ : &orgrimmarNightSounds_; + break; + case CityType::UNDERCITY: + cityLibrary = &undercitySounds_; // No day/night (underground) + break; + case CityType::THUNDERBLUFF: + cityLibrary = isDay ? &thunderbluffDaySounds_ : &thunderbluffNightSounds_; + break; + default: + return; + } + + // Play city ambience sound if library is loaded and timer expired + if (cityLibrary && !cityLibrary->empty() && (*cityLibrary)[0].loaded) { + // Play every 20 seconds for city ambience (moderate intervals for urban atmosphere) + if (cityLoopTime_ >= 20.0f) { + float volume = 0.4f * volumeScale_; // City ambience at moderate volume + AudioEngine::instance().playSound2D((*cityLibrary)[0].data, volume, 1.0f); + LOG_INFO("Playing city ambience: type ", static_cast(currentCity_), + " (", isDay ? "day" : "night", ")"); + cityLoopTime_ = 0.0f; + } + } +} + } // namespace audio } // namespace wowee