diff --git a/include/audio/mount_sound_manager.hpp b/include/audio/mount_sound_manager.hpp index 6a67d154..82f23da3 100644 --- a/include/audio/mount_sound_manager.hpp +++ b/include/audio/mount_sound_manager.hpp @@ -54,6 +54,7 @@ public: void playRearUpSound(); // Rear-up flourish (whinny/roar) void playJumpSound(); // Jump start (grunt/snort) void playLandSound(); // Landing (thud/hoof) + void playIdleSound(); // Ambient idle (snort/stomp/breath) bool isMounted() const { return mounted_; } void setVolumeScale(float scale) { volumeScale_ = scale; } diff --git a/include/rendering/renderer.hpp b/include/rendering/renderer.hpp index 56104032..a24379d7 100644 --- a/include/rendering/renderer.hpp +++ b/include/rendering/renderer.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include namespace wowee { @@ -313,6 +314,7 @@ private: uint32_t rearUp = 0; // Rear-up / special flourish uint32_t run = 0; // Run animation (discovered, don't assume) uint32_t stand = 0; // Stand animation (discovered) + std::vector fidgets; // Idle fidget animations (head turn, tail swish, etc.) }; enum class MountAction { None, Jump, RearUp }; @@ -326,6 +328,8 @@ private: MountAction mountAction_ = MountAction::None; // Current mount action (jump/rear-up) uint32_t mountActionPhase_ = 0; // 0=start, 1=loop, 2=end (for jump chaining) MountAnimSet mountAnims_; // Cached animation IDs for current mount + float mountIdleFidgetTimer_ = 0.0f; // Timer for random idle fidgets + float mountIdleSoundTimer_ = 0.0f; // Timer for ambient idle sounds bool taxiFlight_ = false; bool terrainEnabled = true; diff --git a/src/audio/mount_sound_manager.cpp b/src/audio/mount_sound_manager.cpp index 87fc3b05..4d8fc111 100644 --- a/src/audio/mount_sound_manager.cpp +++ b/src/audio/mount_sound_manager.cpp @@ -302,6 +302,27 @@ void MountSoundManager::playLandSound() { } } +void MountSoundManager::playIdleSound() { + if (!mounted_ || moving_) return; + + // Ambient idle sounds (snort, breath, soft neigh) + if (currentMountType_ == MountType::GROUND && !horseBreathSounds_.empty()) { + static std::mt19937 rng(std::random_device{}()); + std::uniform_int_distribution dist(0, horseBreathSounds_.size() - 1); + const auto& sample = horseBreathSounds_[dist(rng)]; + if (!sample.data.empty()) { + AudioEngine::instance().playSound2D(sample.data, 0.3f * volumeScale_, 0.95f); + } + } else if (currentMountType_ == MountType::FLYING && !wingIdleSounds_.empty()) { + static std::mt19937 rng(std::random_device{}()); + std::uniform_int_distribution dist(0, wingIdleSounds_.size() - 1); + const auto& sample = wingIdleSounds_[dist(rng)]; + if (!sample.data.empty()) { + AudioEngine::instance().playSound2D(sample.data, 0.25f * volumeScale_, 1.0f); + } + } +} + MountType MountSoundManager::detectMountType(uint32_t creatureDisplayId) const { // TODO: Load from CreatureDisplayInfo.dbc or CreatureModelData.dbc // For now, use simple heuristics based on common display IDs diff --git a/src/rendering/renderer.cpp b/src/rendering/renderer.cpp index 2efb84fb..2968f16d 100644 --- a/src/rendering/renderer.cpp +++ b/src/rendering/renderer.cpp @@ -713,6 +713,17 @@ void Renderer::setMounted(uint32_t mountInstId, uint32_t mountDisplayId, float h mountAnims_.run = findFirst({5, 4}); // Run/Walk mountAnims_.stand = findFirst({0}); // Stand (almost always 0) + // Discover idle fidget animations (head turn, tail swish, weight shift) + mountAnims_.fidgets.clear(); + for (const auto& seq : sequences) { + bool isLoop = (seq.flags & 0x01) == 0; + if (!isLoop && seq.duration >= 500 && seq.duration <= 1500 && + std::abs(seq.movingSpeed) < 0.1f && seq.id >= 1 && seq.id <= 10) { + // Likely a fidget: non-looping, short, stationary, low ID near stand + mountAnims_.fidgets.push_back(seq.id); + } + } + // Ensure we have fallbacks for movement if (mountAnims_.stand == 0) mountAnims_.stand = 0; // Force 0 even if not found if (mountAnims_.run == 0) mountAnims_.run = mountAnims_.stand; // Fallback to stand if no run @@ -722,7 +733,8 @@ void Renderer::setMounted(uint32_t mountInstId, uint32_t mountDisplayId, float h " jumpEnd=", mountAnims_.jumpEnd, " rearUp=", mountAnims_.rearUp, " run=", mountAnims_.run, - " stand=", mountAnims_.stand); + " stand=", mountAnims_.stand, + " fidgets=", mountAnims_.fidgets.size()); // Notify mount sound manager if (mountSoundManager) { @@ -1029,6 +1041,41 @@ void Renderer::updateCharacterAnimation() { } } + // Idle fidgets: random one-shot animations when standing still + if (!moving && mountAction_ == MountAction::None && !mountAnims_.fidgets.empty()) { + mountIdleFidgetTimer_ += lastDeltaTime_; + static float nextFidgetTime = 6.0f + (rand() % 7); // 6-12 seconds + + if (mountIdleFidgetTimer_ >= nextFidgetTime) { + // Trigger random fidget animation + static std::mt19937 rng(std::random_device{}()); + std::uniform_int_distribution dist(0, mountAnims_.fidgets.size() - 1); + uint32_t fidgetAnim = mountAnims_.fidgets[dist(rng)]; + + characterRenderer->playAnimation(mountInstanceId_, fidgetAnim, false); + mountIdleFidgetTimer_ = 0.0f; + nextFidgetTime = 6.0f + (rand() % 7); // Randomize next fidget time + + LOG_INFO("Mount idle fidget: playing anim ", fidgetAnim); + } + } else if (moving) { + mountIdleFidgetTimer_ = 0.0f; // Reset timer when moving + } + + // Idle ambient sounds: random snorts/stomps/breaths when standing still + if (!moving && mountSoundManager) { + mountIdleSoundTimer_ += lastDeltaTime_; + static float nextIdleSoundTime = 8.0f + (rand() % 8); // 8-15 seconds + + if (mountIdleSoundTimer_ >= nextIdleSoundTime) { + mountSoundManager->playIdleSound(); + mountIdleSoundTimer_ = 0.0f; + nextIdleSoundTime = 8.0f + (rand() % 8); // Randomize next sound time + } + } else if (moving) { + mountIdleSoundTimer_ = 0.0f; // Reset timer when moving + } + // Only update animation if it changed and we're not in an action sequence if (mountAction_ == MountAction::None && (!haveMountState || curMountAnim != mountAnimId)) { bool loop = true; // Normal movement animations loop