- 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>
113 lines
4.5 KiB
Rust
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(())
|
|
}
|