mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
audio: stop precast sound on spell completion, failure, or interrupt
Add AudioEngine::playSound2DStoppable() + stopSound() so callers can hold a handle and cancel playback early. SpellSoundManager::playPrecast() now stores the handle in activePrecastId_; stopPrecast() cuts the sound. playCast() calls stopPrecast() before playing the release sound, so the channeling audio never bleeds past cast time. SMSG_SPELL_FAILURE and SMSG_CAST_FAILED both call stopPrecast() so interrupted casts silence immediately.
This commit is contained in:
parent
e0d47040d3
commit
9d1616a11b
5 changed files with 100 additions and 3 deletions
|
|
@ -45,6 +45,11 @@ public:
|
|||
bool playSound2D(const std::vector<uint8_t>& wavData, float volume = 1.0f, float pitch = 1.0f);
|
||||
bool playSound2D(const std::string& mpqPath, float volume = 1.0f, float pitch = 1.0f);
|
||||
|
||||
// Stoppable 2D sound — returns a non-zero handle, or 0 on failure
|
||||
uint32_t playSound2DStoppable(const std::vector<uint8_t>& wavData, float volume = 1.0f);
|
||||
// Stop a sound started with playSound2DStoppable (no-op if already finished)
|
||||
void stopSound(uint32_t id);
|
||||
|
||||
// 3D positional sound playback
|
||||
bool playSound3D(const std::vector<uint8_t>& wavData, const glm::vec3& position,
|
||||
float volume = 1.0f, float pitch = 1.0f, float maxDistance = 100.0f);
|
||||
|
|
@ -70,8 +75,10 @@ private:
|
|||
ma_sound* sound;
|
||||
void* buffer; // ma_audio_buffer* - Keep audio buffer alive
|
||||
std::shared_ptr<const std::vector<uint8_t>> pcmDataRef; // Keep decoded PCM alive
|
||||
uint32_t id = 0; // 0 = anonymous (not stoppable)
|
||||
};
|
||||
std::vector<ActiveSound> activeSounds_;
|
||||
uint32_t nextSoundId_ = 1;
|
||||
|
||||
// Music track state
|
||||
ma_sound* musicSound_ = nullptr;
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ public:
|
|||
|
||||
// Spell casting sounds
|
||||
void playPrecast(MagicSchool school, SpellPower power); // Channeling/preparation
|
||||
void stopPrecast(); // Stop precast sound early
|
||||
void playCast(MagicSchool school); // When spell fires
|
||||
void playImpact(MagicSchool school, SpellPower power); // When spell hits target
|
||||
|
||||
|
|
@ -96,6 +97,7 @@ private:
|
|||
// State tracking
|
||||
float volumeScale_ = 1.0f;
|
||||
bool initialized_ = false;
|
||||
uint32_t activePrecastId_ = 0; // Handle from AudioEngine::playSound2DStoppable()
|
||||
|
||||
// Helper methods
|
||||
bool loadSound(const std::string& path, SpellSample& sample, pipeline::AssetManager* assets);
|
||||
|
|
|
|||
|
|
@ -288,11 +288,77 @@ bool AudioEngine::playSound2D(const std::vector<uint8_t>& wavData, float volume,
|
|||
}
|
||||
|
||||
// Track this sound for cleanup (decoded PCM shared across plays)
|
||||
activeSounds_.push_back({sound, audioBuffer, decoded.pcmData});
|
||||
activeSounds_.push_back({sound, audioBuffer, decoded.pcmData, 0u});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t AudioEngine::playSound2DStoppable(const std::vector<uint8_t>& wavData, float volume) {
|
||||
if (!initialized_ || !engine_ || wavData.empty()) return 0;
|
||||
if (masterVolume_ <= 0.0f) return 0;
|
||||
|
||||
DecodedWavCacheEntry decoded;
|
||||
if (!decodeWavCached(wavData, decoded) || !decoded.pcmData || decoded.frames == 0) return 0;
|
||||
|
||||
ma_audio_buffer_config bufferConfig = ma_audio_buffer_config_init(
|
||||
decoded.format, decoded.channels, decoded.frames, decoded.pcmData->data(), nullptr);
|
||||
bufferConfig.sampleRate = decoded.sampleRate;
|
||||
|
||||
ma_audio_buffer* audioBuffer = static_cast<ma_audio_buffer*>(std::malloc(sizeof(ma_audio_buffer)));
|
||||
if (!audioBuffer) return 0;
|
||||
if (ma_audio_buffer_init(&bufferConfig, audioBuffer) != MA_SUCCESS) {
|
||||
std::free(audioBuffer);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ma_sound* sound = static_cast<ma_sound*>(std::malloc(sizeof(ma_sound)));
|
||||
if (!sound) {
|
||||
ma_audio_buffer_uninit(audioBuffer);
|
||||
std::free(audioBuffer);
|
||||
return 0;
|
||||
}
|
||||
ma_result result = ma_sound_init_from_data_source(
|
||||
engine_, audioBuffer,
|
||||
MA_SOUND_FLAG_DECODE | MA_SOUND_FLAG_ASYNC | MA_SOUND_FLAG_NO_PITCH | MA_SOUND_FLAG_NO_SPATIALIZATION,
|
||||
nullptr, sound);
|
||||
if (result != MA_SUCCESS) {
|
||||
ma_audio_buffer_uninit(audioBuffer);
|
||||
std::free(audioBuffer);
|
||||
std::free(sound);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ma_sound_set_volume(sound, volume);
|
||||
if (ma_sound_start(sound) != MA_SUCCESS) {
|
||||
ma_sound_uninit(sound);
|
||||
ma_audio_buffer_uninit(audioBuffer);
|
||||
std::free(audioBuffer);
|
||||
std::free(sound);
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t id = nextSoundId_++;
|
||||
if (nextSoundId_ == 0) nextSoundId_ = 1; // Skip 0 (sentinel)
|
||||
activeSounds_.push_back({sound, audioBuffer, decoded.pcmData, id});
|
||||
return id;
|
||||
}
|
||||
|
||||
void AudioEngine::stopSound(uint32_t id) {
|
||||
if (id == 0) return;
|
||||
for (auto it = activeSounds_.begin(); it != activeSounds_.end(); ++it) {
|
||||
if (it->id == id) {
|
||||
ma_sound_stop(it->sound);
|
||||
ma_sound_uninit(it->sound);
|
||||
std::free(it->sound);
|
||||
ma_audio_buffer* buffer = static_cast<ma_audio_buffer*>(it->buffer);
|
||||
ma_audio_buffer_uninit(buffer);
|
||||
std::free(buffer);
|
||||
activeSounds_.erase(it);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool AudioEngine::playSound2D(const std::string& mpqPath, float volume, float pitch) {
|
||||
if (!assetManager_) {
|
||||
LOG_WARNING("AudioEngine::playSound2D(path): no AssetManager set");
|
||||
|
|
|
|||
|
|
@ -220,12 +220,22 @@ void SpellSoundManager::playPrecast(MagicSchool school, SpellPower power) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (library) {
|
||||
playSound(*library);
|
||||
if (library && !library->empty() && (*library)[0].loaded) {
|
||||
stopPrecast(); // Stop any previous precast still playing
|
||||
float volume = 0.75f * volumeScale_;
|
||||
activePrecastId_ = AudioEngine::instance().playSound2DStoppable((*library)[0].data, volume);
|
||||
}
|
||||
}
|
||||
|
||||
void SpellSoundManager::stopPrecast() {
|
||||
if (activePrecastId_ != 0) {
|
||||
AudioEngine::instance().stopSound(activePrecastId_);
|
||||
activePrecastId_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void SpellSoundManager::playCast(MagicSchool school) {
|
||||
stopPrecast(); // Ensure precast doesn't overlap the cast sound
|
||||
switch (school) {
|
||||
case MagicSchool::FIRE:
|
||||
playSound(castFireSounds_);
|
||||
|
|
|
|||
|
|
@ -2516,6 +2516,11 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
// Spell failed mid-cast
|
||||
casting = false;
|
||||
currentCastSpellId = 0;
|
||||
if (auto* renderer = core::Application::getInstance().getRenderer()) {
|
||||
if (auto* ssm = renderer->getSpellSoundManager()) {
|
||||
ssm->stopPrecast();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Opcode::SMSG_SPELL_COOLDOWN:
|
||||
handleSpellCooldown(packet);
|
||||
|
|
@ -12368,6 +12373,13 @@ void GameHandler::handleCastFailed(network::Packet& packet) {
|
|||
currentCastSpellId = 0;
|
||||
castTimeRemaining = 0.0f;
|
||||
|
||||
// Stop precast sound — spell failed before completing
|
||||
if (auto* renderer = core::Application::getInstance().getRenderer()) {
|
||||
if (auto* ssm = renderer->getSpellSoundManager()) {
|
||||
ssm->stopPrecast();
|
||||
}
|
||||
}
|
||||
|
||||
// Add system message about failed cast with readable reason
|
||||
int powerType = -1;
|
||||
auto playerEntity = entityManager.getEntity(playerGuid);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue