Files
benilla-octo/crates/benilla-ui/src/script/instance.rs
T
SamandClaude Fable 5 03c70a7035 the bag windows are the real 1.12 files, and /walk actually walks
- the bag windows are the reference's own `ContainerFrame.xml` running off the player's patch chain — decision 1751 makes the stock 1.12 UI off the install the end state and `assets/ui` scaffolding that retires file-by-file (86 files against the 99-file stock corpus, 72% of the 1128-function global surface already built) — via one ordered manifest, `benilla.toc`, where a bare name is a file we ship and an `Interface\FrameXML\` path is the player's own file; the windows on screen are `ContainerFrame1..12`, the bank's six copy-pasted popouts fold into them at ids 5..10, net −750 lines, and the swap forced real engine verbs into being rather than stubs (`SetBagPortaitTexture` with its id-0 no-texture arm, `GameTooltip:AppendText`, `SetButtonPulse`, and a `<Model>` whose file is the cooldown indicator materializing as the native sweep, which three corpus addons inherit)
- the 1.12 walk toggle lands: `TOGGLERUN` latches one bit with `ToggleRun`'s own silent refusal chain (dead, rooted, on a live server spline), and the walk animation was never missing at all — the flag reaches it through exactly one channel, the speed (`0x5fd100` picks Walk over Run on `speed > 2 × walkSpeed`); pinning it caught our remote extrapolator with the two min rungs backwards — `min(walk, run)` runs BEFORE the backward min, so a walking backpedaller moves at 2.5 yd/s where we would have played them the Run clip at 4.5 — and one shared `current_speed` now keeps the local controller and the extrapolator from ever disagreeing about it
- the instance and raid lockout messages exist — six packets that were dropped as unknown opcodes now land, four of them as the CHAT_MSG_SYSTEM lines the client composes itself, which is why no Lua handler ever surfaced them — plus `IsInInstance`, `ResetInstances`, and the unit popup's "Reset all instances" row behind the reference's own predicate; where the binary contradicts vmangos: the ownership flag means "any instance bind" but vmangos only ever names raid binds, so the row was dead for every character without a raid lockout — term 1 is now also satisfied by our own eyes, walking out of a party dungeon the server itself bound us to
- opening the world map showed 3D world at the sheet's margins: `BlackoutWorld` was parented to the very `UIParent` a fullscreen panel hides, and three more frames had crossed the same boundary — the shared dropdown lists (so the map's continent and zone pickers answered a click with nothing at all), the master-looter menu, and the screenshot confirmation, which could never appear during a cinematic; `frame_flag_gate` now checks the parent seat against the reference both ways a frame acquires one (the attribute, template-inherited included, and `<Frames>` nesting), and its first harvest took 30 divergences down to three accepted ones

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 20:17:25 +07:00

113 lines
4.5 KiB
Rust

//! The instance-lockout **Era API surface** (decision 1748) — three globals, no snapshot struct.
//!
//! `IsInInstance`, `CanShowResetInstances` and `ResetInstances` are all engine bindings in 1.12
//! (`reference/1.12-globals.tsv` lists the three as `function`/`engine`), and between them they
//! are the whole Lua side of the lockout family: everything else it does arrives as a
//! `CHAT_MSG_SYSTEM` line the client composes itself, with no event and no getter.
//!
//! Both readers answer off state the app pushes, because both are `Map.dbc` questions and the DBC
//! is the app's to read — the same split [`super::party::SavedInstanceInfo`] already makes.
//!
//! **`IsInInstance` returns a pair**: `1`/`nil` for "is this an instance at all", and the type as a
//! string. The four strings are the reference's own table at `0x83de58`, indexed by
//! `Map.dbc`'s `InstanceType` behind a `< 4` guard: `none` (0), `party` (1), `raid` (2),
//! `pvp` (3) — `IsInInstance 0x48a750`.
use mlua::{Lua, MultiValue, Value};
use super::Model;
/// `Map.dbc`'s `InstanceType` → the word `IsInInstance()` returns for it — the reference's
/// `0x83de58` string table, with its own out-of-range fallback (`cmp esi,4; jae` → `"none"`).
pub fn instance_type_name(instance_type: u32) -> &'static str {
match instance_type {
1 => "party",
2 => "raid",
3 => "pvp",
// 0 and anything the table cannot index.
_ => "none",
}
}
impl super::UiScript {
/// Push the current map's `Map.dbc` `InstanceType` — `IsInInstance()`'s whole input.
/// `None` = the map id has no DBC row, which the reference reaches by null-checking the record
/// pointer; it then returns without pushing anything at all (a real 1.12 stack bug). We answer
/// `nil, "none"` there, which is what every caller of it actually means to read.
pub fn set_instance_type(&mut self, instance_type: Option<u32>) {
let mut model = self.model_mut();
if model.instance_type != instance_type {
model.instance_type = instance_type;
}
}
/// Push `CanShowResetInstances()`'s answer — the app owns every term of it (the ownership
/// latch, the last dungeon, its age, and the map we are standing on).
pub fn set_can_reset_instances(&mut self, can: bool) {
let mut model = self.model_mut();
if model.can_reset_instances != can {
model.can_reset_instances = can;
}
}
/// Drain the `ResetInstances()` calls queued since the last drain — each one is a
/// `CMSG_RESET_INSTANCES`. A count, like [`super::UiScript::take_binder_confirms`]: the intent
/// has no payload of its own.
pub fn take_reset_instance_asks(&mut self) -> u32 {
std::mem::take(&mut self.model_mut().reset_instance_asks)
}
}
/// Register the three lockout globals ([`super::binder`]'s style).
pub(super) fn install(lua: &Lua) -> mlua::Result<()> {
let g = lua.globals();
// IsInInstance() → inInstance, instanceType. The first return is the NUMBER 1 rather than a
// boolean because the reference pushes 1.0 (`push 0x3ff00000; push 0` = the double 1.0), and
// 1.12 UI code compares it numerically.
g.set(
"IsInInstance",
lua.create_function(|lua, ()| {
let ty = {
let model = lua.app_data_ref::<Model>().expect("model app_data");
model.instance_type
};
let name = instance_type_name(ty.unwrap_or(0));
let inside = match ty {
Some(t) if t != 0 => Value::Number(1.0),
_ => Value::Nil,
};
Ok(MultiValue::from_vec(vec![
inside,
Value::String(lua.create_string(name)?),
]))
})?,
)?;
// CanShowResetInstances() — UnitPopup's gate on the SELF menu's "Reset all instances" row.
// 1.0/nil for the same reason as above.
g.set(
"CanShowResetInstances",
lua.create_function(|lua, ()| {
let model = lua.app_data_ref::<Model>().expect("model app_data");
Ok(if model.can_reset_instances {
Value::Number(1.0)
} else {
Value::Nil
})
})?,
)?;
// ResetInstances() — the CONFIRM_RESET_INSTANCES dialog's Yes.
g.set(
"ResetInstances",
lua.create_function(|lua, ()| {
let mut model = lua.app_data_mut::<Model>().expect("model app_data");
model.reset_instance_asks += 1;
Ok(())
})?,
)?;
Ok(())
}