- the buff bar is the reference's own `BuffFrame.xml`, and the swap found an engine defect: an empty `<OnLoad>` in 1.12's XML is a BLANK that takes an inherited handler away again (seven such sites across four stock files), which our loader read as "none given" — so the two enchant slots would have painted the player's first buff in the corner and cancelled it on right-click; the first fix then had to be corrected at the bytes (`SetScript 0x7025c0` tests only NULL and empty, and nothing trims, so `<OnLoad>↵</OnLoad>` compiles to a valid empty function rather than storing nil, which any addon calling `GetScript` can observe); `SHOW_BUFF_DURATIONS` is applied once post-load because 1.12 leaves that to `UIOptionsFrame.lua`, which benilla does not build — without it the bar ships a 10px pitch error on every buff - gossip quests come through the reference's four verbs — `GetGossipAvailableQuests`/`GetGossipActiveQuests` returning (title, level) PAIRS, with the two `Select*` verbs indexing into that list — replacing three invented names that sat on the beyond-1.12 exception list; the quest LEVEL was on the wire, parsed and dropped because the invented verb had nowhere to put it, and the reference's order (available before active, whatever the packet interleaved) comes with them - eight more files off the chain as groundwork: `LocaleProperties.lua` (the gendered/plural `GetText` selector that eleven stock call sites read their words through — fifty lines of FrameXML that looked like an engine binding), `CinematicFrame.xml`, the UIMenu kit, the `UnitFrame.lua`/`CombatFeedback.lua` kits (the only two names blocking the stock player, party, target and pet frames), the money-input kit, and `ItemButtonTemplate.xml` — the base every bag, bank, merchant, loot, trade and mail slot inherits; `GetMerchantItemMaxStack` (on the wire the whole time, dropped) and the two trade item-link verbs join the engine - two instruments for the migration: a gap report that reads a stock window's call sites against a LOADED VM and says which engine verbs it still needs (28 windows need none, the rest name 1–5), and the readiness probe's documented false positive — loading a stock window on top of our same-named one leaves `getglobal(name)` pointing at ours while the stock handlers run against theirs, so a failure inside a colliding name is a question, not an answer Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
90 lines
3.4 KiB
Rust
90 lines
3.4 KiB
Rust
//! The innkeeper bind confirm (decision 1331, BinderConfirm.xml): the dialog `ui_binder`'s feed
|
|
//! raises, its Accept, and the range poll that takes it away — driven exactly as that feed and the
|
|
//! app's NPC-session guard drive it.
|
|
|
|
use benilla_ui::script::{ScriptValue, UiScript};
|
|
|
|
use super::test_ui::load_ui as load_xml;
|
|
|
|
/// The app's own pre-state: a question is pending and in range, which is what
|
|
/// `CheckBinderDist()` reports while the dialog is up.
|
|
fn setup() -> UiScript {
|
|
let mut s = UiScript::new().unwrap();
|
|
s.set_screen_size(1024.0, 768.0);
|
|
load_xml(&s, "Fonts.xml");
|
|
load_xml(&s, "MoneyFrame.xml");
|
|
load_xml(&s, "UiPanels.xml");
|
|
load_xml(&s, "BinderConfirm.xml");
|
|
s.set_binder_pending(true);
|
|
s
|
|
}
|
|
|
|
/// `CONFIRM_BINDER(area)` raises the dialog with the area name filled in, and Accept queues the
|
|
/// one `ConfirmBinder()` that becomes `CMSG_BINDER_ACTIVATE`. The whole point of the arc: before
|
|
/// this wiring the click produced no dialog and no packet at all (B249).
|
|
#[test]
|
|
fn the_confirm_shows_the_area_and_accept_queues_the_bind() {
|
|
let mut s = setup();
|
|
s.fire_event(
|
|
"CONFIRM_BINDER",
|
|
vec![ScriptValue::Str("Dolanaar".to_string())],
|
|
);
|
|
assert!(
|
|
s.eval::<bool>("return StaticPopup1:IsVisible()").unwrap(),
|
|
"CONFIRM_BINDER shows the dialog"
|
|
);
|
|
assert_eq!(
|
|
s.eval::<String>("return StaticPopup1Text:GetText()")
|
|
.unwrap(),
|
|
"Do you want to make Dolanaar your new home?",
|
|
"arg1 fills the GlobalStrings template's %s"
|
|
);
|
|
|
|
s.run("StaticPopup_OnClick(StaticPopup1, 1)").unwrap();
|
|
assert_eq!(s.take_binder_confirms(), 1);
|
|
assert!(
|
|
!s.eval::<bool>("return StaticPopup1:IsVisible()").unwrap(),
|
|
"accepting closes it"
|
|
);
|
|
}
|
|
|
|
/// Cancel and ESC both send **nothing**: declining an innkeeper is silent on the wire (there is
|
|
/// no decline opcode), so the only observable is that no confirm was queued.
|
|
#[test]
|
|
fn declining_sends_nothing() {
|
|
let mut s = setup();
|
|
s.fire_event("CONFIRM_BINDER", vec![ScriptValue::Str("Goldshire".into())]);
|
|
s.run("StaticPopup_OnClick(StaticPopup1, 2)").unwrap();
|
|
assert_eq!(s.take_binder_confirms(), 0);
|
|
|
|
s.fire_event("CONFIRM_BINDER", vec![ScriptValue::Str("Goldshire".into())]);
|
|
s.run("ToggleGameMenu()").unwrap();
|
|
assert_eq!(s.take_binder_confirms(), 0);
|
|
assert!(
|
|
!s.eval::<bool>("return StaticPopup1:IsVisible()").unwrap(),
|
|
"hideOnEscape takes it down"
|
|
);
|
|
}
|
|
|
|
/// Walking away takes the question off screen: the entry's OnUpdate polls `CheckBinderDist()`,
|
|
/// which the app drives from the shared NPC-session range guard. While it holds, ticking changes
|
|
/// nothing; the frame it goes false, the dialog hides itself — with no packet either way.
|
|
#[test]
|
|
fn leaving_the_innkeepers_range_hides_the_confirm() {
|
|
let mut s = setup();
|
|
s.fire_event("CONFIRM_BINDER", vec![ScriptValue::Str("Kharanos".into())]);
|
|
s.run("StaticPopup_OnUpdate(StaticPopup1, 0.1)").unwrap();
|
|
assert!(
|
|
s.eval::<bool>("return StaticPopup1:IsVisible()").unwrap(),
|
|
"in range, the tick leaves it alone"
|
|
);
|
|
|
|
s.set_binder_pending(false);
|
|
s.run("StaticPopup_OnUpdate(StaticPopup1, 0.1)").unwrap();
|
|
assert!(
|
|
!s.eval::<bool>("return StaticPopup1:IsVisible()").unwrap(),
|
|
"out of range, the dialog takes itself down"
|
|
);
|
|
assert_eq!(s.take_binder_confirms(), 0, "and sends nothing");
|
|
}
|