diff --git a/crates/benilla-app/assets/ui/ActionBar.xml b/crates/benilla-app/assets/ui/ActionBar.xml index 767983d5..2057650a 100644 --- a/crates/benilla-app/assets/ui/ActionBar.xml +++ b/crates/benilla-app/assets/ui/ActionBar.xml @@ -233,10 +233,21 @@ -- page 5, else the paged/bonus formula); we carry the fork as data — a fixed per-button -- page base set at OnLoad (MultiBars.xml), nil for the bonus-paged main bar. function BenillaActionButton_Action(button) + -- `index` falls back to the button's ID, which is where the REFERENCE reads it from: + -- `ActionButton_GetPagedID` uses `this:GetID()`, and our `.index` is only that value + -- cached at OnLoad. A button that inherits `ActionBarButtonTemplate` and wires its own + -- OnLoad — zBar.xml:7 does exactly this — never runs ours, so `.index` is nil while + -- `GetID()` is set from the XML `id=`. Before the fallback, our own handler raised + -- `ActionBar.xml:228: arithmetic on local 'index'` on somebody else's button. + -- + -- Still nothing? Action 0, which is not a real slot: every reader answers nil for it + -- and the button draws empty. Deliberately not an early `return nil` — the callers are + -- arithmetic all the way down, and a nil would move the raise rather than remove it. + local index = button.index or button:GetID() or 0 if button.base then - return button.base + button.index + return button.base + index end - return BenillaActionBar_ActionFor(button.index) + return BenillaActionBar_ActionFor(index) end -- Hover: SetAction's pure delegation (decision 0276 — a SPELL slot renders the spell @@ -991,7 +1002,19 @@ ButtonHilight-Square ADD (l.87); CheckedTexture CheckButtonHilight ADD (l.88 — the IsCurrentAction/IsAutoRepeatAction ring). Only Name (macros) stays OUT. --> - + + @@ -1010,6 +1033,16 @@ + + + + + + + + + + BenillaActionButton_OnClick(self) BenillaActionButton_OnDragStart(self) @@ -1047,6 +1084,11 @@ + + + diff --git a/crates/benilla-app/src/creature_anim/breath.rs b/crates/benilla-app/src/creature_anim/breath.rs index 7b4d2ece..0be8107c 100644 --- a/crates/benilla-app/src/creature_anim/breath.rs +++ b/crates/benilla-app/src/creature_anim/breath.rs @@ -169,7 +169,9 @@ pub(super) fn classify_breath( Some(id) if areas.0.is_cold(id) => Breath::Cold, _ => Breath::None, }; - commands.entity(entity).insert(BreathEnv { + // try_insert: a streamed unit can despawn (teardown, teleport, range-out) between this + // system's parallel run and its sync point — a plain insert panics on the dead entity. + commands.entity(entity).try_insert(BreathEnv { kind, stale_at: now + RECLASSIFY_SECS, }); diff --git a/crates/benilla-app/src/ui_script/action_bar_tests.rs b/crates/benilla-app/src/ui_script/action_bar_tests.rs index 2edaf353..df0c1d48 100644 --- a/crates/benilla-app/src/ui_script/action_bar_tests.rs +++ b/crates/benilla-app/src/ui_script/action_bar_tests.rs @@ -1073,3 +1073,85 @@ fn the_reference_action_bar_constants_are_defined() { "CURRENT_ACTIONBAR_PAGE is mutable page state we do not keep — nil fails loudly, a frozen 1 lies" ); } + +/// **The reference's two-level action-button split, both halves inheritable by name.** +/// +/// `ActionButtonTemplate` (ref ActionButtonTemplate.xml:3) is regions only and carries NO scripts; +/// `ActionBarButtonTemplate` (ref ActionBarFrame.xml:4) inherits it and adds the handlers. Ours +/// conflated them under one `Benilla*` name, so an addon inheriting either reference name got a +/// bare frame — no art, no regions, and no error (1203's silent shape). +/// +/// `zBar.xml:7` is the corpus shape: it inherits `ActionBarButtonTemplate`, wires its own OnLoad, +/// and then reads `getglobal(button:GetName().."NormalTexture")` — a derived name it only has +/// because the template declares `$parentNormalTexture`. That read is where it died. +/// +/// The last assertion is the one that keeps our own bars safe: the alias must still resolve to the +/// full thing, or 48 `inherits=` sites across four files silently lose their handlers. +#[test] +fn both_reference_action_button_templates_are_inheritable() { + let s = UiScript::new().unwrap(); + load_action_bar(&s); + + // zBar's exact shape: inherit the bar template, supply your own OnLoad. + let doc = benilla_ui::framexml::parse( + r#" + + + + + + + "#, + ) + .unwrap(); + let report = benilla_ui::loader::load(&s, &doc, &|_| None); + assert!( + report.errors.is_empty(), + "loader errors: {:?}", + report.errors + ); + assert!( + !report + .warnings + .iter() + .any(|w| w.contains("unknown template")), + "both names must resolve: {:?}", + report.warnings + ); + + // The derived name zBar reads, on a button built from each half. + for owner in ["ZLikeButton", "BareLikeButton"] { + assert!( + s.eval::(&format!("return {owner}NormalTexture ~= nil")) + .unwrap(), + "{owner}NormalTexture — zBar.lua:88's read" + ); + assert!( + s.eval::(&format!( + "return {owner}Icon ~= nil and {owner}Cooldown ~= nil" + )) + .unwrap(), + "{owner} must carry the template's regions" + ); + } + + // The base half carries NO handlers, exactly as the reference's does — an addon inheriting it + // wires its own, and must not silently receive ours. + assert!( + !s.eval::("return BareLikeButton:GetScript(\"OnClick\") ~= nil") + .unwrap(), + "ActionButtonTemplate is regions only; handlers belong to the bar half" + ); + assert!( + s.eval::("return ZLikeButton:GetScript(\"OnClick\") ~= nil") + .unwrap(), + "ActionBarButtonTemplate carries the handler set" + ); + + // ...and our own alias still resolves to the full thing. + assert!( + s.eval::("return ActionButton1NormalTexture ~= nil and ActionButton1:GetScript(\"OnClick\") ~= nil") + .unwrap(), + "BenillaActionButtonTemplate's 48 inherits= sites must be untouched by the split" + ); +}