The effect cut must spare an addon's own PlaySoundFile — it silenced SoftSpoken outright
My last commit made the `MasterSoundEffects` 0→1 toggle stop every playing effect, as
the reference does. It stopped too much: grom's next session carries ten
`MasterSoundEffects re-enabled` lines and he heard **none** of the ten voice lines that
produced them.
The mechanism is not subtle once stated. Every 1.12 voice-over addon spends the toggle
*and then plays its own file* — SoftSpoken's `SoundQueue:PlaySound` is literally
if Version.IsLegacyVanilla then
self:MuteGameDialogue(channel, clip.cutsGameDialogue) -- SetCVar 0; SetCVar 1
end
local willPlay = SoundUtils:PlaySound(clip, channel) -- PlaySoundFile(clip.path)
so a cut that reaches file playback takes the very thing the toggle was spent to make
room for. `stopped_by_reenable` now excludes `kit == 0`, which is `play_file`'s own
marker for the `PlaySoundFile` path.
The two exclusions have different standing and the doc says so. Music and ambience are
**derived** — the reference's `flags & 0x4000008` class, muted rather than stopped
(`0x7a65a0`). `kit == 0` is **measured**: which flag class a streamed file falls in is
unreversed, so this is the player's own observation standing in for a fact I do not have.
What it costs is the other half of SoftSpoken's use of the toggle: `StopKeeping` spends it
to stop its own clip before replaying one, and that does nothing again, so the
overlapping-replay symptom may return. That is a worse-to-better trade, not a fix, and it
is written where it lives.
The stop now logs the kits it cut and how many file channels it spared, because a bare
count cannot separate "the bark died, as intended" from "we cut the line the addon was
about to speak" — which is exactly the distinction this commit got wrong.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
This commit is contained in:
@@ -1000,11 +1000,30 @@ pub(super) fn arms_effect_cut(was: bool, now: bool) -> bool {
|
||||
now && !was
|
||||
}
|
||||
|
||||
/// Which categories the enabling edge stops. `Sfx` alone: music and ambience are the reference's
|
||||
/// `flags & 0x4000008` class, muted rather than stopped (`0x7a65a0`), so a toggle must not cost
|
||||
/// the player their music — the point of the toggle is to cut a bark, not to restart the score.
|
||||
fn stopped_by_reenable(category: SoundCategory) -> bool {
|
||||
matches!(category, SoundCategory::Sfx)
|
||||
/// Which channels the enabling edge stops: a kit-played `Sfx` channel, and nothing else.
|
||||
///
|
||||
/// Two exclusions, and they have different standing.
|
||||
///
|
||||
/// Music and ambience are **derived**: they are the reference's `flags & 0x4000008` class, muted
|
||||
/// rather than stopped (`0x7a65a0`), so a toggle must not cost the player their music — the point
|
||||
/// of the toggle is to cut a bark, not to restart the score.
|
||||
///
|
||||
/// `kit == 0`, the `PlaySoundFile` path ([`play_file`], whose doc records that id as its own
|
||||
/// marker), is **measured, not derived**, and the distinction is worth keeping honest. Stopping
|
||||
/// those too is what the first revision of this did, and it silenced SoftSpoken completely:
|
||||
/// grom's session carries ten `MasterSoundEffects re-enabled` lines and he heard none of the ten
|
||||
/// voice lines that produced them. It has to be excluded, because **every 1.12 voice-over addon
|
||||
/// toggles this CVar and then plays its own file** — a cut that reaches file playback cuts the
|
||||
/// very thing the toggle was spent to make room for.
|
||||
///
|
||||
/// Which of the reference's two flag classes a streamed file falls in is not settled here;
|
||||
/// `0x4000008`'s meaning is unreversed, so this is a player's measurement standing in for a fact
|
||||
/// we do not have. What it costs is the other half of SoftSpoken's use of the toggle:
|
||||
/// `SoundQueue.StopKeeping` spends it to stop its *own* clip before replaying one, and that does
|
||||
/// nothing again. If the overlapping-replay symptom returns, this is why — and the per-kit line
|
||||
/// the stop now logs is what will say so.
|
||||
fn stopped_by_reenable(kit: u32, category: SoundCategory) -> bool {
|
||||
matches!(category, SoundCategory::Sfx) && kit != 0
|
||||
}
|
||||
|
||||
/// Spend an armed `MasterSoundEffects` enabling edge: stop every playing effect, as the
|
||||
@@ -1042,17 +1061,25 @@ pub(super) fn cut_effects_on_reenable(
|
||||
return;
|
||||
}
|
||||
let before = out.channels.len();
|
||||
// Named, not merely counted: which kits a toggle cuts is the only evidence separating "the
|
||||
// bark died, as intended" from "we cut the line the addon was about to speak".
|
||||
let mut stopped: Vec<u32> = Vec::new();
|
||||
let mut spared_files = 0usize;
|
||||
out.channels.retain_mut(|c| {
|
||||
if !stopped_by_reenable(c.category) {
|
||||
if !stopped_by_reenable(c.kit, c.category) {
|
||||
spared_files += usize::from(c.kit == 0 && c.category == SoundCategory::Sfx);
|
||||
return true;
|
||||
}
|
||||
stopped.push(c.kit);
|
||||
c.handle.stop(mixer::declick());
|
||||
false
|
||||
});
|
||||
let stopped = before - out.channels.len();
|
||||
if stopped > 0 {
|
||||
if !stopped.is_empty() || spared_files > 0 {
|
||||
info!(
|
||||
"sound: MasterSoundEffects re-enabled — {stopped} effect channel(s) stopped (0x7a67a0)"
|
||||
"sound: MasterSoundEffects re-enabled — stopped {} of {before} channel(s), kits {:?}; \
|
||||
spared {spared_files} PlaySoundFile channel(s) (0x7a67a0)",
|
||||
stopped.len(),
|
||||
stopped
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1130,15 +1157,25 @@ mod tests {
|
||||
assert!(enabled, "and leave sound effects on");
|
||||
}
|
||||
|
||||
/// The toggle cuts effects and leaves the score alone.
|
||||
/// The toggle cuts barks, leaves the score alone, and — the one grom's silence paid for —
|
||||
/// never touches an addon's own `PlaySoundFile` audio.
|
||||
#[test]
|
||||
fn the_reenabling_edge_spares_music_and_ambience() {
|
||||
assert!(stopped_by_reenable(SoundCategory::Sfx));
|
||||
fn the_reenabling_edge_spares_music_ambience_and_addon_files() {
|
||||
assert!(
|
||||
!stopped_by_reenable(SoundCategory::Music),
|
||||
stopped_by_reenable(1, SoundCategory::Sfx),
|
||||
"a kit-played effect is the bark the toggle exists to cut"
|
||||
);
|
||||
assert!(
|
||||
!stopped_by_reenable(1, SoundCategory::Music),
|
||||
"music is the reference's mute-not-stop class (`flags & 0x4000008`, `0x7a65a0`)"
|
||||
);
|
||||
assert!(!stopped_by_reenable(SoundCategory::Ambience));
|
||||
assert!(!stopped_by_reenable(1, SoundCategory::Ambience));
|
||||
assert!(
|
||||
!stopped_by_reenable(0, SoundCategory::Sfx),
|
||||
"kit 0 is `play_file`'s marker, so this is an addon's own `PlaySoundFile`. Cutting \
|
||||
it silenced SoftSpoken outright: every 1.12 voice-over addon toggles this CVar and \
|
||||
then plays its own file, so the cut would take the line the toggle made room for"
|
||||
);
|
||||
}
|
||||
|
||||
/// Pinned as the 13 dwords at `.data` `0x87ce60`.
|
||||
|
||||
Reference in New Issue
Block a user