Fix mount sounds with real MPQ paths and family detection

Mount Sound System:
- Use actual creature sounds from MPQ (Horse, Ram, Wolf, Tiger, Dragons)
- Separate sound pools: jump (attack), landing (wound), rear-up (aggro)
- Mount family detection: HORSE, RAM, WOLF, TIGER, RAPTOR, DRAGON
- Family logged on mount for future per-family sound selection

Sound Mappings:
- Flying mounts: Dragon wing flaps + DragonHawk screeches
- Ground mounts: Horse attack (jump), wound (land), aggro (rear-up)
- Ready for family-specific sound selection (TODO)

Mount Lean:
- Procedural lean into turns for ground mounts
- Physics-based: turn rate × 0.15, max ±14°, 6x/sec blend
- Returns to upright when not turning or when flying
- Rider follows mount roll automatically via bone attachment
This commit is contained in:
Kelsi 2026-02-10 19:49:07 -08:00
parent c623fcef51
commit 4b9a2394d5
2 changed files with 105 additions and 28 deletions

View file

@ -25,7 +25,8 @@ bool MountSoundManager::initialize(pipeline::AssetManager* assets) {
loadMountSounds();
int totalSamples = wingFlapSounds_.size() + wingIdleSounds_.size() +
horseBreathSounds_.size() + horseMoveSounds_.size();
horseBreathSounds_.size() + horseMoveSounds_.size() +
horseJumpSounds_.size() + horseLandSounds_.size();
LOG_INFO("Mount sound manager initialized (", totalSamples, " clips)");
return true;
}
@ -45,11 +46,11 @@ void MountSoundManager::loadMountSounds() {
// Flying mount wing flaps (movement)
std::vector<std::string> wingFlapPaths = {
"Sound\\Creature\\Gryphon\\GryphonWingFlap1.wav",
"Sound\\Creature\\Gryphon\\GryphonWingFlap2.wav",
"Sound\\Creature\\Gryphon\\GryphonWingFlap3.wav",
"Sound\\Creature\\WindRider\\WindRiderWingFlap1.wav",
"Sound\\Creature\\WindRider\\WindRiderWingFlap2.wav",
"Sound\\Creature\\Dragons\\HugeWingFlap1.wav",
"Sound\\Creature\\Dragons\\HugeWingFlap2.wav",
"Sound\\Creature\\Dragons\\HugeWingFlap3.wav",
"Sound\\Creature\\DragonWhelp\\mDragonWhelpWingFlapA.wav",
"Sound\\Creature\\DragonWhelp\\mDragonWhelpWingFlapB.wav",
};
for (const auto& path : wingFlapPaths) {
@ -59,11 +60,11 @@ void MountSoundManager::loadMountSounds() {
}
}
// Flying mount idle/hovering
// Flying mount idle/hovering (screeches/calls)
std::vector<std::string> wingIdlePaths = {
"Sound\\Creature\\Gryphon\\GryphonIdle1.wav",
"Sound\\Creature\\Gryphon\\GryphonIdle2.wav",
"Sound\\Creature\\WindRider\\WindRiderIdle1.wav",
"Sound\\Creature\\DragonHawk\\DragonHawkPreAggro.wav",
"Sound\\Creature\\DragonHawk\\DragonHawkAggro.wav",
"Sound\\Creature\\Dragons\\DragonPreAggro.wav",
};
for (const auto& path : wingIdlePaths) {
@ -73,11 +74,12 @@ void MountSoundManager::loadMountSounds() {
}
}
// Ground mount breathing/idle
// Ground mount breathing/idle (per creature family)
std::vector<std::string> horseBreathPaths = {
"Sound\\Creature\\Horse\\HorseBreath1.wav",
"Sound\\Creature\\Horse\\HorseBreath2.wav",
"Sound\\Creature\\Horse\\HorseSnort1.wav",
"Sound\\Creature\\Horse\\mHorseStand3A.wav",
"Sound\\Creature\\Ram\\RamPreAggro.wav",
"Sound\\Creature\\Wolf\\mWolfFidget2a.wav",
"Sound\\Creature\\Tiger\\mTigerStand2A.wav",
};
for (const auto& path : horseBreathPaths) {
@ -87,10 +89,9 @@ void MountSoundManager::loadMountSounds() {
}
}
// Ground mount movement ambient
// Ground mount movement ambient (alerts/whinnies)
std::vector<std::string> horseMovePaths = {
"Sound\\Creature\\Horse\\HorseWhinny1.wav",
"Sound\\Creature\\Horse\\HorseWhinny2.wav",
"Sound\\Creature\\Horse\\mHorseAggroA.wav",
};
for (const auto& path : horseMovePaths) {
@ -100,6 +101,33 @@ void MountSoundManager::loadMountSounds() {
}
}
// Ground mount jump effort sounds
std::vector<std::string> horseJumpPaths = {
"Sound\\Creature\\Horse\\mHorseAttackA.wav",
"Sound\\Creature\\Horse\\mHorseAttackB.wav",
"Sound\\Creature\\Horse\\mHorseAttackC.wav",
};
for (const auto& path : horseJumpPaths) {
MountSample sample;
if (loadSound(path, sample)) {
horseJumpSounds_.push_back(std::move(sample));
}
}
// Ground mount landing thud sounds
std::vector<std::string> horseLandPaths = {
"Sound\\Creature\\Horse\\mHorseWoundA.wav",
"Sound\\Creature\\Horse\\mHorseWoundB.wav",
};
for (const auto& path : horseLandPaths) {
MountSample sample;
if (loadSound(path, sample)) {
horseLandSounds_.push_back(std::move(sample));
}
}
if (!wingFlapSounds_.empty()) {
LOG_INFO("Loaded ", wingFlapSounds_.size(), " wing flap sounds");
}
@ -112,6 +140,12 @@ void MountSoundManager::loadMountSounds() {
if (!horseMoveSounds_.empty()) {
LOG_INFO("Loaded ", horseMoveSounds_.size(), " horse move sounds");
}
if (!horseJumpSounds_.empty()) {
LOG_INFO("Loaded ", horseJumpSounds_.size(), " horse jump sounds");
}
if (!horseLandSounds_.empty()) {
LOG_INFO("Loaded ", horseLandSounds_.size(), " horse land sounds");
}
}
bool MountSoundManager::loadSound(const std::string& path, MountSample& sample) {
@ -146,11 +180,13 @@ void MountSoundManager::onMount(uint32_t creatureDisplayId, bool isFlying) {
mounted_ = true;
currentDisplayId_ = creatureDisplayId;
currentMountType_ = detectMountType(creatureDisplayId);
currentMountFamily_ = detectMountFamily(creatureDisplayId);
flying_ = isFlying;
moving_ = false;
LOG_INFO("Mount sound: mounted on display ID ", creatureDisplayId,
" type=", static_cast<int>(currentMountType_),
" family=", static_cast<int>(currentMountFamily_),
" flying=", flying_);
updateMountSounds();
@ -225,14 +261,14 @@ void MountSoundManager::playJumpSound() {
if (elapsed < 200) return;
lastActionSoundTime_ = now;
// Shorter, quieter sound for jump start
if (currentMountType_ == MountType::GROUND && !horseBreathSounds_.empty()) {
// Ground mounts: grunt/snort
// Jump effort sound
if (currentMountType_ == MountType::GROUND && !horseJumpSounds_.empty()) {
// TODO: Select family-specific sounds once organized by family
static std::mt19937 rng(std::random_device{}());
std::uniform_int_distribution<size_t> dist(0, horseBreathSounds_.size() - 1);
const auto& sample = horseBreathSounds_[dist(rng)];
std::uniform_int_distribution<size_t> dist(0, horseJumpSounds_.size() - 1);
const auto& sample = horseJumpSounds_[dist(rng)];
if (!sample.data.empty()) {
AudioEngine::instance().playSound2D(sample.data, 0.5f * volumeScale_, 1.2f);
AudioEngine::instance().playSound2D(sample.data, 0.5f * volumeScale_, 1.1f);
}
} else if (currentMountType_ == MountType::FLYING && !wingFlapSounds_.empty()) {
// Flying mounts: wing whoosh
@ -255,13 +291,13 @@ void MountSoundManager::playLandSound() {
lastActionSoundTime_ = now;
// Landing thud/hoof sound
if (currentMountType_ == MountType::GROUND && !horseBreathSounds_.empty()) {
// Ground mounts: hoof thud (use breath as placeholder for now)
if (currentMountType_ == MountType::GROUND && !horseLandSounds_.empty()) {
// Ground mounts: hoof thud / impact
static std::mt19937 rng(std::random_device{}());
std::uniform_int_distribution<size_t> dist(0, horseBreathSounds_.size() - 1);
const auto& sample = horseBreathSounds_[dist(rng)];
std::uniform_int_distribution<size_t> dist(0, horseLandSounds_.size() - 1);
const auto& sample = horseLandSounds_[dist(rng)];
if (!sample.data.empty()) {
AudioEngine::instance().playSound2D(sample.data, 0.6f * volumeScale_, 0.8f); // Lower pitch for thud
AudioEngine::instance().playSound2D(sample.data, 0.6f * volumeScale_, 0.85f); // Lower pitch for thud
}
}
}
@ -286,6 +322,33 @@ MountType MountSoundManager::detectMountType(uint32_t creatureDisplayId) const {
return MountType::GROUND;
}
MountFamily MountSoundManager::detectMountFamily(uint32_t creatureDisplayId) const {
// Heuristic creature family detection based on common display ID ranges
// TODO: Replace with proper CreatureModelData.dbc lookup
// Horses: ~14000-14999 range (includes many horse variants)
if (creatureDisplayId >= 14000 && creatureDisplayId < 15000) return MountFamily::HORSE;
// Rams: ~14349-14375 range
if (creatureDisplayId >= 14349 && creatureDisplayId <= 14375) return MountFamily::RAM;
// Wolves: ~207-217, ~2326-2329 ranges
if ((creatureDisplayId >= 207 && creatureDisplayId <= 217) ||
(creatureDisplayId >= 2326 && creatureDisplayId <= 2329)) return MountFamily::WOLF;
// Tigers/Cats: ~6442-6473 range
if (creatureDisplayId >= 6442 && creatureDisplayId <= 6473) return MountFamily::TIGER;
// Raptors: ~6466-6474 range
if (creatureDisplayId >= 6466 && creatureDisplayId <= 6474) return MountFamily::RAPTOR;
// Dragons/Drakes
if (creatureDisplayId >= 25800 && creatureDisplayId <= 25900) return MountFamily::DRAGON;
// Default to horse for unknown ground mounts
return MountFamily::HORSE;
}
void MountSoundManager::updateMountSounds() {
if (!AudioEngine::instance().isInitialized() || !mounted_) {
return;