EnableAllAddOns and DisableAllAddOns switch the Lua index array only, so a disable-all leaves the server-hidden Blizzard_* addons loadable (#480)
`EnableAllAddOns` and `DisableAllAddOns` now switch only the addons in the Lua index array, as 1.12.1's `0x48e720` and `0x48e7f0` do: bound `0x51def0`, each name from `0x51df00`, into the enable setter `0x51ea20`. That array leaves out the records `SMSG_ADDON_INFO` hid (`0x51dc4f`), which on vmangos is every `Blizzard_*` addon (`AddonHandler.cpp:129`), so an addon manager's disable-all no longer leaves the auction house, talents, trainers, trade skills, inspect, `/macro` or Key Bindings unable to load. - The per-addon verbs and the all verbs share one setter, standing for `0x51ea20`. - Before the server's addon reply the index is empty and both verbs change nothing, as the reference's count is 0 until then. Fixes #414 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
3ed9c0b17e
commit
208ab10cc3
@@ -141,6 +141,12 @@ fn row_of(model: &Model, key: &AddonKey) -> Option<usize> {
|
||||
}
|
||||
}
|
||||
|
||||
/// The enable setter (`0x51ea20`), the one write every enable verb makes, for the current
|
||||
/// character's row.
|
||||
fn set_enabled(model: &mut Model, i: usize, on: bool) {
|
||||
model.addons[i].enabled = on;
|
||||
}
|
||||
|
||||
/// The registry as [`super::addon_gate`] rows, the one input every verb's verdict reads.
|
||||
fn gate_rows(model: &Model) -> Vec<GateRow<'_>> {
|
||||
model
|
||||
@@ -322,21 +328,25 @@ pub(super) fn install(lua: &Lua) -> mlua::Result<()> {
|
||||
let mut model = lua.app_data_mut::<Model>().expect("model");
|
||||
let key = addon_key(lua, &model, &key, usage)?;
|
||||
if let Some(i) = row_of(&model, &key) {
|
||||
model.addons[i].enabled = on;
|
||||
set_enabled(&mut model, i, on);
|
||||
}
|
||||
Ok(())
|
||||
})?,
|
||||
)?;
|
||||
}
|
||||
|
||||
// No arguments read (`0x48e720`, `0x48e7f0`).
|
||||
// No arguments read (`0x48e720`, `0x48e7f0`). The loop runs over the Lua index array, not the
|
||||
// registry: bound `0x51def0`, each name from `0x51df00`, into the setter `0x51ea20`. The array
|
||||
// leaves out what `SMSG_ADDON_INFO` hid (`0x51dc4f`), which is every `Blizzard_*` addon on a
|
||||
// server that hides them (vmangos `AddonHandler.cpp:129`), so their state is never touched.
|
||||
for (name, on) in [("EnableAllAddOns", true), ("DisableAllAddOns", false)] {
|
||||
g.set(
|
||||
name,
|
||||
lua.create_function(move |lua, _: MultiValue| {
|
||||
let mut model = lua.app_data_mut::<Model>().expect("model");
|
||||
for a in &mut model.addons {
|
||||
a.enabled = on;
|
||||
for k in 0..model.addon_index.len() {
|
||||
let i = model.addon_index[k];
|
||||
set_enabled(&mut model, i, on);
|
||||
}
|
||||
Ok(())
|
||||
})?,
|
||||
|
||||
@@ -117,3 +117,91 @@ fn a_reply_that_hides_nothing_still_builds_the_array() {
|
||||
s.note_addon_info_reply(&[]);
|
||||
assert_eq!(s.eval::<i64>("return GetNumAddOns()").ok(), Some(3));
|
||||
}
|
||||
|
||||
/// A player's addon `Probe` and a hidden chain addon `Blizzard_AuctionUI`, LoadOnDemand, whose one
|
||||
/// file sets `AuctionLoaded`; the reply hides the Blizzard one, as a vmangos realm does.
|
||||
fn with_hidden_blizzard(blizzard_enabled: bool) -> UiScript {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_addon_chain_reader(Box::new(|path| {
|
||||
(path == "Interface/AddOns/Blizzard_AuctionUI/Blizzard_AuctionUI.lua")
|
||||
.then(|| b"AuctionLoaded = 1".to_vec())
|
||||
}));
|
||||
s.register_addons(
|
||||
vec![
|
||||
AddOnInfo {
|
||||
name: "Blizzard_AuctionUI".into(),
|
||||
interface: 11200,
|
||||
load_on_demand: true,
|
||||
chain: true,
|
||||
files: vec!["Blizzard_AuctionUI.lua".into()],
|
||||
enabled: blizzard_enabled,
|
||||
..Default::default()
|
||||
},
|
||||
AddOnInfo {
|
||||
name: "Probe".into(),
|
||||
interface: 11200,
|
||||
enabled: true,
|
||||
..Default::default()
|
||||
},
|
||||
],
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
s.note_addon_info_reply(&["blizzard_auctionui".into()]);
|
||||
s
|
||||
}
|
||||
|
||||
/// `GetAddOnInfo(name)`'s `enabled` slot, 1 or nil.
|
||||
fn enabled(s: &UiScript, name: &str) -> bool {
|
||||
s.eval::<bool>(&format!(
|
||||
"local _, _, _, on = GetAddOnInfo('{name}') return on ~= nil"
|
||||
))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// `DisableAllAddOns` (`0x48e7f0`) loops `0x51df00` up to `0x51def0`, the array a hidden record
|
||||
/// never enters (`0x51dc4f`), so an addon manager's disable-all leaves the auction house loadable.
|
||||
#[test]
|
||||
fn disable_all_leaves_a_hidden_addon_enabled_and_loadable() {
|
||||
let s = with_hidden_blizzard(true);
|
||||
s.run("DisableAllAddOns()").unwrap();
|
||||
assert!(!enabled(&s, "Probe"), "the visible addon is disabled");
|
||||
assert!(
|
||||
enabled(&s, "Blizzard_AuctionUI"),
|
||||
"the hidden one is untouched"
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<(Option<i64>, Option<String>)>("return LoadAddOn('Blizzard_AuctionUI')")
|
||||
.unwrap(),
|
||||
(Some(1), None)
|
||||
);
|
||||
assert_eq!(s.eval::<i64>("return AuctionLoaded").ok(), Some(1));
|
||||
}
|
||||
|
||||
/// `EnableAllAddOns` (`0x48e720`) walks the same array, so a hidden addon keeps its own row, and
|
||||
/// `AddOn_CanLoad` check 3 (`0x51e81d`) still reads that row: no check of the gate reads
|
||||
/// `[rec+0x29]`, so a hidden addon disabled by name stays `DISABLED`.
|
||||
#[test]
|
||||
fn enable_all_touches_the_visible_addons_only() {
|
||||
let s = with_hidden_blizzard(false);
|
||||
s.run("DisableAddOn('Probe') EnableAllAddOns()").unwrap();
|
||||
assert!(enabled(&s, "Probe"), "the visible addon is enabled again");
|
||||
assert!(
|
||||
!enabled(&s, "Blizzard_AuctionUI"),
|
||||
"the hidden one is untouched"
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<(Option<i64>, Option<String>)>("return LoadAddOn('Blizzard_AuctionUI')")
|
||||
.unwrap(),
|
||||
(None, Some("DISABLED".into()))
|
||||
);
|
||||
}
|
||||
|
||||
/// Before `SMSG_ADDON_INFO` the count `[0xbe1b90]` is 0, so both loops run no iteration.
|
||||
#[test]
|
||||
fn the_all_verbs_do_nothing_before_the_server_answers() {
|
||||
let s = seeded();
|
||||
s.run("DisableAllAddOns()").unwrap();
|
||||
assert!(enabled(&s, "Alpha") && enabled(&s, "Mike") && enabled(&s, "Zulu"));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user