From 482ad0bc5c1dda266bc1ce5c2dfa341aa874d6c4 Mon Sep 17 00:00:00 2001 From: grom Date: Sun, 1 Nov 2026 17:14:50 -0700 Subject: [PATCH] sound: the later-era Sound_ CVar names, which three installed addons already wanted MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `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 Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn --- crates/benilla-app/src/cvars.rs | 67 +++++++++++++++++++ crates/benilla-app/src/sound/mod.rs | 26 +++++-- .../src/script/compat/classic_api/cvar.rs | 26 +++++++ .../src/script/tests/reference_surface.rs | 7 ++ 4 files changed, 119 insertions(+), 7 deletions(-) diff --git a/crates/benilla-app/src/cvars.rs b/crates/benilla-app/src/cvars.rs index 951ca410..e4bc3674 100644 --- a/crates/benilla-app/src/cvars.rs +++ b/crates/benilla-app/src/cvars.rs @@ -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`. diff --git a/crates/benilla-app/src/sound/mod.rs b/crates/benilla-app/src/sound/mod.rs index ec7861ed..c7db767b 100644 --- a/crates/benilla-app/src/sound/mod.rs +++ b/crates/benilla-app/src/sound/mod.rs @@ -346,12 +346,24 @@ pub(crate) fn on_cvar( mut cut: ResMut, ) { 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 diff --git a/crates/benilla-ui/src/script/compat/classic_api/cvar.rs b/crates/benilla-ui/src/script/compat/classic_api/cvar.rs index 3d9b6fc8..e8803eb6 100644 --- a/crates/benilla-ui/src/script/compat/classic_api/cvar.rs +++ b/crates/benilla-ui/src/script/compat/classic_api/cvar.rs @@ -189,6 +189,32 @@ pub(super) fn install(lua: &Lua) -> mlua::Result { 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| { + let model = lua.app_data_ref::().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; diff --git a/crates/benilla-ui/src/script/tests/reference_surface.rs b/crates/benilla-ui/src/script/tests/reference_surface.rs index 5d499ddf..ff55692f 100644 --- a/crates/benilla-ui/src/script/tests/reference_surface.rs +++ b/crates/benilla-ui/src/script/tests/reference_surface.rs @@ -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.