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
This commit is contained in:
Kelsi 2026-02-09 16:14:03 -08:00
parent d6f0c2ec46
commit 77a9b3192d
2 changed files with 121 additions and 3 deletions

View file

@ -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<AmbientSample> desertPlainsDaySounds_;
std::vector<AmbientSample> desertPlainsNightSounds_;
// City ambience libraries (day and night versions)
std::vector<AmbientSample> stormwindDaySounds_;
std::vector<AmbientSample> stormwindNightSounds_;
std::vector<AmbientSample> ironforgeSounds_; // No separate day/night
std::vector<AmbientSample> darnassusDaySounds_;
std::vector<AmbientSample> darnassusNightSounds_;
std::vector<AmbientSample> orgrimmarDaySounds_;
std::vector<AmbientSample> orgrimmarNightSounds_;
std::vector<AmbientSample> undercitySounds_; // No separate day/night (underground)
std::vector<AmbientSample> thunderbluffDaySounds_;
std::vector<AmbientSample> thunderbluffNightSounds_;
// Active emitters
std::vector<AmbientEmitter> 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

View file

@ -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<int>(currentCity_),
" to ", static_cast<int>(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<AmbientSample>* 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<int>(currentCity_),
" (", isDay ? "day" : "night", ")");
cityLoopTime_ = 0.0f;
}
}
}
} // namespace audio
} // namespace wowee