sound: the later-era Sound_ CVar names, which three installed addons already wanted

`GetCVar("Sound_MusicVolume")` answered nil, so `Cinematic/Core.lua:3621` raised inside a
`format` and took the rest of its `PLAYER_LOGIN` handler — the music restore — with it. 2.x
renamed the sound CVars with a `Sound_` prefix and 1.12's names stayed what they are; Cinematic
is a multi-flavour addon and uses the modern spelling at **all 47** of its music sites, which
is the whole of its music-fade feature.

**This is a fix for the install, not a concession to the port.** Three addons already here ask
with the modern spelling and have been reading nil: DoiteAuras' editor and Tmog
(`Sound_MasterVolume`, `Sound_DialogVolume`, `Sound_EnableDialog`), SoftSpoken
(`Sound_EnableAllSound`, `Sound_SFXVolume`, `Sound_EnableSFX`).

Ten rows, each on the footing the `BackgroundSound` pair in this file already set: two names,
one knob, dispatched together in `sound/mod.rs`'s observer so the spellings cannot disagree
about a setting. Each default is its 1.12 sibling's, since they are the same setting twice.

Two honest compromises, labelled where they live: 1.12 has no dialogue channel, so
`Sound_DialogVolume` rides the SFX volume and `Sound_EnableDialog` the error-speech toggle; and
it has no SFX-only enable, so `Sound_EnableSFX` rides `MasterSoundEffects`, the only sound
enable it has. Riding what the mixer actually has beats inventing tracks it has not got.

**`GetCVarBool`** is absent from 1.12 on this repo's own evidence —
`reference/1.12-globals.tsv` is a capture of the real client's `_G` and carries `GetCVar` and
`GetCVarDefault` and nothing else of the family — and present in `ClassicAPI.dll`'s string
pool, so it belongs to the client this build announces itself as. It answers a real boolean, as
the name says, and **nil for a name with no row**, so "there is no such CVar" stays
distinguishable from "it is off". `Cinematic/Camera.lua:493` reads `ActionButtonUseKeyDown`
through it.

Not built, and deliberately: `IsMouseButtonDown`. `compat/classic_api/input.rs` already
declined it with the right reason — the only press state here is `Model::mouse_down_on`, which
the pointer layer fills *only* for a press that landed on a frame. `Cinematic/Camera.lua:612`
asks in order to spot a right-drag **on the world**, which is exactly what that map cannot see,
so answering from it would be wrong for the one caller. It wants a real mouse-button feed.

benilla-ui 2081 passed. Full chain green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
This commit is contained in:
2026-11-01 17:14:50 -07:00
co-authored by Claude Opus 5
parent 139965f415
commit 482ad0bc5c
4 changed files with 119 additions and 7 deletions
+67
View File
@@ -174,6 +174,73 @@ pub(crate) const REGISTERED: &[Registered] = &[
// The race/sex refusal voice lines (`0x457877`), `SoundOptionsFrame.lua:3`; the master enable
// greys it.
same("EnableErrorSpeech", "1"),
// ── The later-era `Sound_` spellings, each beside its 1.12 row above ─────────────────────
//
// 2.x renamed the sound CVars with a `Sound_` prefix and 1.12's names stayed what they are;
// `sound/mod.rs`'s observer dispatches each pair to one knob, the arrangement the
// `BackgroundSound` row below already uses, so the two spellings cannot disagree.
//
// **Not a concession to the ported addons.** Three already installed here ask with the modern
// spelling and have been reading nil: DoiteAuras' editor and Tmog (`Sound_MasterVolume`,
// `Sound_DialogVolume`, `Sound_EnableDialog`), SoftSpoken (`Sound_EnableAllSound`,
// `Sound_SFXVolume`, `Sound_EnableSFX`). Cinematic uses it at all 47 of its music sites, which
// is the whole of its music-fade feature; `GetCVar` answering nil there raised inside a
// `format` at `Core.lua:3621` and took the rest of its `PLAYER_LOGIN` handler with it.
//
// Each default is its 1.12 sibling's, since they are the same setting under two names. The
// dialogue pair is the exception worth reading: 1.12 has no separate dialogue channel, so
// `Sound_DialogVolume` rides the SFX volume and `Sound_EnableDialog` the error-speech toggle
// rather than inventing a track the mixer has not got.
ours(
"Sound_MasterVolume",
"1",
"the later-era spelling of MasterVolume; one knob, two names (sound/mod.rs)",
),
ours(
"Sound_SFXVolume",
"1",
"the later-era spelling of SoundVolume; one knob, two names (sound/mod.rs)",
),
ours(
"Sound_MusicVolume",
"0.4",
"the later-era spelling of MusicVolume; one knob, two names (sound/mod.rs)",
),
ours(
"Sound_AmbienceVolume",
"0.6",
"the later-era spelling of AmbienceVolume; one knob, two names (sound/mod.rs)",
),
ours(
"Sound_DialogVolume",
"1",
"1.12 has no dialogue channel; rides the SFX volume rather than inventing a track",
),
ours(
"Sound_EnableMusic",
"1",
"the later-era spelling of EnableMusic; one knob, two names (sound/mod.rs)",
),
ours(
"Sound_EnableAmbience",
"1",
"the later-era spelling of EnableAmbience; one knob, two names (sound/mod.rs)",
),
ours(
"Sound_EnableAllSound",
"1",
"the later-era spelling of MasterSoundEffects, the Enable All Sound box",
),
ours(
"Sound_EnableSFX",
"1",
"1.12 has no SFX-only toggle; rides MasterSoundEffects, the only sound enable it has",
),
ours(
"Sound_EnableDialog",
"1",
"1.12's nearest is EnableErrorSpeech, the race/sex refusal lines; rides that",
),
// Not a 1.12 CVar or checkbox; the later-era name. The reference mutes on losing focus, music
// included (`WM_ACTIVATE` to `0x7a4860`'s `FSOUND_SetMute(-3, active ? 0 : 1)`), so "0" is its
// behaviour. The knob is `SoundConfig::background_sound`.
+19 -7
View File
@@ -346,12 +346,24 @@ pub(crate) fn on_cvar(
mut cut: ResMut<EffectCutArmed>,
) {
let v = ev.num();
// The `Sound_`-prefixed spellings beside each 1.12 name are the later-era ones, on the footing
// the `BackgroundSound` pair below already set: two names, one knob, so the two cannot
// disagree. **They are not only the ported addons' concern** — three addons already installed
// ask for them and have been getting nil: `Sound_MasterVolume`, `Sound_DialogVolume`,
// `Sound_EnableDialog` and `Sound_EnableAllSound` (DoiteAuras' editor, Tmog, SoftSpoken),
// `Sound_SFXVolume`/`Sound_EnableSFX` (SoftSpoken). Cinematic asks with the modern spelling at
// all 47 of its music sites, which is its whole music-fade feature.
match ev.key().as_str() {
"mastervolume" => sound.master = v.clamp(0.0, 1.0),
"soundvolume" => sound.sfx = v.clamp(0.0, 1.0),
"musicvolume" => sound.music = v.clamp(0.0, 1.0),
"ambiencevolume" => sound.ambience = v.clamp(0.0, 1.0),
"mastersoundeffects" => {
"mastervolume" | "sound_mastervolume" => sound.master = v.clamp(0.0, 1.0),
"soundvolume" | "sound_sfxvolume" => sound.sfx = v.clamp(0.0, 1.0),
"musicvolume" | "sound_musicvolume" => sound.music = v.clamp(0.0, 1.0),
"ambiencevolume" | "sound_ambiencevolume" => sound.ambience = v.clamp(0.0, 1.0),
// 1.12's own dialogue knob is the error-speech one, and there is no separate dialogue
// volume; the modern pair maps onto what this client actually has rather than inventing a
// channel the mixer has no track for.
"sound_dialogvolume" => sound.sfx = v.clamp(0.0, 1.0),
"sound_enabledialog" | "sound_enableerrorspeech" => sound.error_speech = v != 0.0,
"mastersoundeffects" | "sound_enableallsound" | "sound_enablesfx" => {
// The reference's flag store is edge-triggered (`7a657f: je` returns on an unchanged
// value) and only the enabling edge reaches the stop (`7a67eb: test cl,cl`).
let now = v != 0.0;
@@ -360,8 +372,8 @@ pub(crate) fn on_cvar(
}
sound.enabled = now;
}
"enablemusic" => sound.music_enabled = v != 0.0,
"enableambience" => sound.ambience_enabled = v != 0.0,
"enablemusic" | "sound_enablemusic" => sound.music_enabled = v != 0.0,
"enableambience" | "sound_enableambience" => sound.ambience_enabled = v != 0.0,
"enableerrorspeech" => sound.error_speech = v != 0.0,
// Two names, one knob. `Sound_EnableSoundWhenGameIsInBG` is the later-era Blizzard
// spelling; `BackgroundSound` is SuperWoW's, and it is the one an addon on this server
@@ -189,6 +189,32 @@ pub(super) fn install(lua: &Lua) -> mlua::Result<Table> {
Ok(t)
}
/// `GetCVarBool(name)`: the bare global, which 1.12 has not got — the reference capture of the
/// client's own `_G` (`reference/1.12-globals.tsv`) lists `GetCVar` and `GetCVarDefault` and
/// nothing else of the family, while `ClassicAPI.dll` carries the name in its own string pool, so
/// it is part of the client this build announces itself as.
///
/// A real boolean, as the name says and as upstream documents, and **nil for a name with no row**
/// — so a caller can tell "there is no such CVar" from "it is off", which a blanket `false` would
/// hide. `Cinematic/Camera.lua:493` reads `ActionButtonUseKeyDown` through it to decide whether
/// its cast button fires on key down or up.
pub(super) fn install_globals(lua: &Lua) -> mlua::Result<()> {
lua.globals().set(
"GetCVarBool",
lua.create_function(|lua, name: Option<String>| {
let model = lua.app_data_ref::<Model>().expect("model app_data");
let Some(slot) = name.as_deref().and_then(|n| model.cvars.get(&key_of(n))) else {
return Ok(mlua::Value::Nil);
};
// The same reading `GetCVar`'s consumers use: "0" and an empty value are off, anything
// else is on, so "1", "0.4" and "true" all behave as the sound rows expect.
let on = !matches!(slot.value.as_str(), "" | "0" | "0.0" | "false");
Ok(mlua::Value::Boolean(on))
})?,
)?;
Ok(())
}
#[cfg(test)]
mod tests {
use crate::script::UiScript;
@@ -202,6 +202,13 @@ fn allowed_beyond_1_12() -> HashSet<&'static str> {
// "spellIconID")`. 1.12 reaches an icon only through a spellbook slot, so a spell the
// player does not know has no texture an addon can draw.
"GetSpellIconTexture",
// ClassicAPI's `GetCVarBool` (script/compat/classic_api/cvar.rs). Absent from 1.12 on this
// file's own evidence — `reference/1.12-globals.tsv` is a capture of the real client's
// `_G` and carries `GetCVar` and `GetCVarDefault` and nothing else of the family — and
// present in `ClassicAPI.dll`'s string pool, so it is part of the client this build
// announces itself as. `Cinematic/Camera.lua:493` reads `ActionButtonUseKeyDown` through
// it; nil for a name with no row, so "no such CVar" stays distinguishable from "off".
"GetCVarBool",
// ClassicAPI's `GetSpellInfo` as a bare global (script/compat/classic_api/spell.rs), the
// positional sibling of the table-returning `C_Spell.GetSpellInfo` — upstream lists "both
// forms". Ten returns, the WotLK signature plus the appended spellID.