From 31eccf43b672d7eae62a07405be656059333e571 Mon Sep 17 00:00:00 2001 From: mojsior Date: Sat, 24 Jan 2026 21:45:58 +0100 Subject: [PATCH] Fix proximity cue tempo + interact cue --- Source/utils/proximity_audio.cpp | 73 +++++++++++++++++++++++++++++--- 1 file changed, 66 insertions(+), 7 deletions(-) diff --git a/Source/utils/proximity_audio.cpp b/Source/utils/proximity_audio.cpp index c6fe9c283..3a80afb16 100644 --- a/Source/utils/proximity_audio.cpp +++ b/Source/utils/proximity_audio.cpp @@ -144,6 +144,40 @@ struct InteractTarget { return static_cast(std::lround(interval)); } +void StopCueSound(const CueSound &cue) +{ + for (const auto &variant : cue.variants) { + if (variant != nullptr && variant->DSB.IsLoaded()) + variant->DSB.Stop(); + } +} + +void StopAllCuesExcept(const CueSound *cueToKeep) +{ + const auto stopIfOther = [cueToKeep](const std::optional &cue) { + if (cue && &*cue != cueToKeep) + StopCueSound(*cue); + }; + + stopIfOther(WeaponItemCue); + stopIfOther(ArmorItemCue); + stopIfOther(GoldItemCue); + stopIfOther(ChestCue); + stopIfOther(DoorCue); + stopIfOther(MonsterCue); + stopIfOther(InteractCue); +} + +[[nodiscard]] bool IsAnyOtherCuePlaying(const CueSound *cueToIgnore) +{ + const auto isOtherPlaying = [cueToIgnore](const std::optional &cue) { + return cue && cue->IsAnyPlaying() && &*cue != cueToIgnore; + }; + + return isOtherPlaying(WeaponItemCue) || isOtherPlaying(ArmorItemCue) || isOtherPlaying(GoldItemCue) || isOtherPlaying(ChestCue) || isOtherPlaying(DoorCue) + || isOtherPlaying(MonsterCue) || isOtherPlaying(InteractCue); +} + std::optional TryLoadCueSound(std::initializer_list candidatePaths) { if (!gbSndInited) @@ -204,7 +238,20 @@ void EnsureCuesLoaded() MonsterCue = TryLoadCueSound({ "audio\\monster.ogg", "..\\audio\\monster.ogg", "audio\\monster.wav", "..\\audio\\monster.wav", "audio\\monster.mp3", "..\\audio\\monster.mp3" }); - InteractCue = TryLoadCueSound({ "audio\\interactispossible.ogg", "..\\audio\\interactispossible.ogg", "audio\\interactispossible.wav", "..\\audio\\interactispossible.wav", "audio\\interactispossible.mp3", "..\\audio\\interactispossible.mp3" }); + InteractCue = TryLoadCueSound({ + "audio\\interactispossible.ogg", + "audio\\interactionispossible.ogg", + "..\\audio\\interactispossible.ogg", + "..\\audio\\interactionispossible.ogg", + "audio\\interactispossible.wav", + "audio\\interactionispossible.wav", + "..\\audio\\interactispossible.wav", + "..\\audio\\interactionispossible.wav", + "audio\\interactispossible.mp3", + "audio\\interactionispossible.mp3", + "..\\audio\\interactispossible.mp3", + "..\\audio\\interactionispossible.mp3", + }); loaded = true; } @@ -223,8 +270,8 @@ void EnsureCuesLoaded() if (!gbSndInited || !gbSoundOn) return false; - // Proximity cues are meant to guide the player; overlapping the same cue can create audio glitches/noise. - if (cue.IsAnyPlaying()) + // Allow the same cue to restart (for tempo control), but don't overlap different cue types. + if (IsAnyOtherCuePlaying(&cue)) return false; int logVolume = 0; @@ -242,6 +289,15 @@ void EnsureCuesLoaded() if (snd == nullptr || !snd->DSB.IsLoaded()) return false; + // Restart the cue if it's already playing so we can control tempo based on distance. + // Stop all variants to avoid overlaps when pitch levels are enabled. + if (cue.IsAnyPlaying()) { + for (const auto &variant : cue.variants) { + if (variant != nullptr && variant->DSB.IsLoaded()) + variant->DSB.Stop(); + } + } + snd_play_snd(snd, logVolume, logPan); return true; } @@ -554,6 +610,13 @@ std::optional FindInteractTargetInRange(const Player &player, Po } } + // Make the interact cue reliably audible even when another proximity cue is playing. + // (The new distance-based tempo restarts cues frequently.) + StopAllCuesExcept(InteractCue ? &*InteractCue : nullptr); + + if (!InteractCue || !InteractCue->IsLoaded()) + return true; + if (!PlayCueAt(*InteractCue, target->position, /*distance=*/0, /*maxDistance=*/1)) return true; (void)now; @@ -578,10 +641,6 @@ void UpdateProximityAudioCues() const uint32_t now = SDL_GetTicks(); const Point playerPosition { MyPlayer->position.future }; - // Don't start another cue while one is playing (helps avoid overlap-related stutter/noise). - if (IsAnyCuePlaying()) - return; - // Keep cues readable and reduce overlap/glitches by playing at most one per tick (priority order). if (UpdateInteractCue(playerPosition, now)) return;