mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-25 08:30:13 +00:00
Add property-based mount animation discovery and procedural lean
Mount Animation System: - Property-based jump animation discovery using sequence metadata - Chain linkage scoring (nextAnimation/aliasNext) for accurate detection - Correct loop detection: flags & 0x01 == 0 means looping - Avoids brake/stop animations via blendTime penalties - Works on any mount model without hardcoded animation IDs Mount Physics: - Physics-based jump height: vz = sqrt(2 * g * h) - Configurable MOUNT_JUMP_HEIGHT constant (1.0m default) - Procedural lean into turns for ground mounts - Smooth roll based on turn rate (±14° max, 6x/sec blend) Audio Improvements: - State-machine driven mount sounds (jump, land, rear-up) - Semantic sound methods (no animation ID dependencies) - Debug logging for missing sound files Bug Fixes: - Fixed mount animation sequencing (JumpStart → JumpLoop → JumpEnd) - Fixed animation loop flag interpretation (0x20 vs 0x21) - Rider bone attachment working correctly during all mount actions
This commit is contained in:
parent
3c783d1845
commit
c623fcef51
16 changed files with 1083 additions and 145 deletions
|
|
@ -116,16 +116,19 @@ void MountSoundManager::loadMountSounds() {
|
|||
|
||||
bool MountSoundManager::loadSound(const std::string& path, MountSample& sample) {
|
||||
if (!assetManager_ || !assetManager_->fileExists(path)) {
|
||||
LOG_WARNING("Mount sound file not found: ", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto data = assetManager_->readFile(path);
|
||||
if (data.empty()) {
|
||||
LOG_WARNING("Mount sound file empty: ", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
sample.path = path;
|
||||
sample.data = std::move(data);
|
||||
LOG_INFO("Loaded mount sound: ", path);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -184,6 +187,85 @@ void MountSoundManager::setGrounded(bool grounded) {
|
|||
setFlying(!grounded);
|
||||
}
|
||||
|
||||
void MountSoundManager::playRearUpSound() {
|
||||
if (!mounted_) return;
|
||||
|
||||
// Cooldown to prevent spam (200ms)
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - lastActionSoundTime_).count();
|
||||
if (elapsed < 200) return;
|
||||
lastActionSoundTime_ = now;
|
||||
|
||||
// Use semantic sound based on mount family
|
||||
if (currentMountType_ == MountType::GROUND && !horseMoveSounds_.empty()) {
|
||||
// Ground mounts: whinny/roar
|
||||
static std::mt19937 rng(std::random_device{}());
|
||||
std::uniform_int_distribution<size_t> dist(0, horseMoveSounds_.size() - 1);
|
||||
const auto& sample = horseMoveSounds_[dist(rng)];
|
||||
if (!sample.data.empty()) {
|
||||
AudioEngine::instance().playSound2D(sample.data, 0.7f * volumeScale_, 1.0f);
|
||||
}
|
||||
} else if (currentMountType_ == MountType::FLYING && !wingIdleSounds_.empty()) {
|
||||
// Flying mounts: screech/roar
|
||||
static std::mt19937 rng(std::random_device{}());
|
||||
std::uniform_int_distribution<size_t> dist(0, wingIdleSounds_.size() - 1);
|
||||
const auto& sample = wingIdleSounds_[dist(rng)];
|
||||
if (!sample.data.empty()) {
|
||||
AudioEngine::instance().playSound2D(sample.data, 0.6f * volumeScale_, 1.1f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MountSoundManager::playJumpSound() {
|
||||
if (!mounted_) return;
|
||||
|
||||
// Cooldown to prevent spam
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - lastActionSoundTime_).count();
|
||||
if (elapsed < 200) return;
|
||||
lastActionSoundTime_ = now;
|
||||
|
||||
// Shorter, quieter sound for jump start
|
||||
if (currentMountType_ == MountType::GROUND && !horseBreathSounds_.empty()) {
|
||||
// Ground mounts: grunt/snort
|
||||
static std::mt19937 rng(std::random_device{}());
|
||||
std::uniform_int_distribution<size_t> dist(0, horseBreathSounds_.size() - 1);
|
||||
const auto& sample = horseBreathSounds_[dist(rng)];
|
||||
if (!sample.data.empty()) {
|
||||
AudioEngine::instance().playSound2D(sample.data, 0.5f * volumeScale_, 1.2f);
|
||||
}
|
||||
} else if (currentMountType_ == MountType::FLYING && !wingFlapSounds_.empty()) {
|
||||
// Flying mounts: wing whoosh
|
||||
static std::mt19937 rng(std::random_device{}());
|
||||
std::uniform_int_distribution<size_t> dist(0, wingFlapSounds_.size() - 1);
|
||||
const auto& sample = wingFlapSounds_[dist(rng)];
|
||||
if (!sample.data.empty()) {
|
||||
AudioEngine::instance().playSound2D(sample.data, 0.4f * volumeScale_, 1.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MountSoundManager::playLandSound() {
|
||||
if (!mounted_) return;
|
||||
|
||||
// Cooldown to prevent spam
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - lastActionSoundTime_).count();
|
||||
if (elapsed < 200) return;
|
||||
lastActionSoundTime_ = now;
|
||||
|
||||
// Landing thud/hoof sound
|
||||
if (currentMountType_ == MountType::GROUND && !horseBreathSounds_.empty()) {
|
||||
// Ground mounts: hoof thud (use breath as placeholder for now)
|
||||
static std::mt19937 rng(std::random_device{}());
|
||||
std::uniform_int_distribution<size_t> dist(0, horseBreathSounds_.size() - 1);
|
||||
const auto& sample = horseBreathSounds_[dist(rng)];
|
||||
if (!sample.data.empty()) {
|
||||
AudioEngine::instance().playSound2D(sample.data, 0.6f * volumeScale_, 0.8f); // Lower pitch for thud
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue