- the binding registry goes 130 → 201 of the client's 228 live commands: a coverage gate makes registered ∪ recorded-absent exactly the 1.12 surface, each absence naming the Lua globals whose arrival refutes it — which is how 37 rows whose mechanism had already landed came home (the keyring, the pet book, the raid tab, the six action-bar pages, both vertical multibars) — and then four mechanisms bring 71 more: the five camera views with the reference's default table and its sixteen camera CVars read out of the binary (NextView/PrevView do NOT wrap), self-cast riding UseAction's third argument with autoSelfCast a real CVar at last, the friendly target cone / assist / last-hostile as one cycler with a side (the two sides use different deadness tests on purpose, and assist neither requires an assistable target nor swings), and REPLY2 — reply to the last person you told; the friendly cone also stops borrowing the enemy side's in-combat score bonus, which would have shoved a healer targeting you to the head of every cycle - the key names close their gaps: NUMPADEQUALS, F13-F24 and BUTTON6+ were keys the binding window listed and the file saved that the dispatcher could never press — named both ways now, with the namer and the codec structurally unable to disagree again; platform= becomes the OS gate it really is (off a Mac build the iTunes-remote category never even exists), and the Mac 1.12 slice settles that its F13 IS print-screen while F14/F15 map to UNKNOWN on purpose and stay unbindable; and the last-enemy stamp gains the four conjuncts it shipped without — you resolve, are not dead, not a ghost, not mounted, and the new target is alive-or-feigning — so TargetLastEnemy can no longer land somewhere the real client's never would - the quest windows wear the reference's names: ~40 of the quest window's globals were ours (the reference's name usually sitting in the comment beside them), its two six-item reward pools merge into the reference's single ten-item run with the update function ported line-for-line, the four panels and the quest log take REAL scroll frames (the thumb drags now), the gossip window gets its greeting panel, the two mail panes' inert painted-at-rest scrollbars become real ones, the Skills detail pane nests as the reference has it, and nine private constants take their real names; the one that broke a feature detect: our QuestLogCount — a name 1.12 doesn't publish — made pfUI conclude it was running on TBC and die in that branch - an EditBox is born with its constructor's five regions (the text, three selection quads and the caret), so GetRegions answers the reference's own count and authored regions start at index 6 — and a bare <NormalTexture/> now builds a live blank texture, since the element is what creates the region, not its file=; OpacityFrame lands, its first customer having arrived; and the equip / auto-equip / use bind confirms complete the soulbind arc — three different predicates with no quality leg (borrowing the loot arm's would have silenced the confirm on every white bind-on-equip item), Accept re-issuing the original action with the reference's suppress flag because 1.12 ships no confirm packet, and the soulstone use going through the same gate Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
194 lines
9.1 KiB
Rust
194 lines
9.1 KiB
Rust
//! The **five camera views** — `SetView` / `SaveView` / `ResetView` / `NextView` / `PrevView`,
|
||
//! plus `FlipCameraYaw`: the outbound half of the reference's `UIUtil\Camera.cpp` Lua surface.
|
||
//!
|
||
//! Six globals, no state — [`super::follow`]'s shape exactly. Nothing here reads the camera: every
|
||
//! one of these is an **engine action** over state this VM does not hold (the orbit arm, the pitch,
|
||
//! the character's facing, the zoom ceiling), so each call queues a [`CameraViewRequest`] the app
|
||
//! drains ([`super::UiScript::take_camera_view_requests`]) and applies to the rig
|
||
//! (`benilla_app::player::camera_view`). The alternative — reaching into the ECS from inside the
|
||
//! VM — is the thing decision 0068 §3 exists to forbid.
|
||
//!
|
||
//! **Where they come from.** The reference registers them in one table, `0x84f7a0` (`{const char*
|
||
//! name; handler}` pairs, dumped from `WoW.exe` 5875): rows 15…20 are `SetView 0x50b5b0`,
|
||
//! `SaveView 0x50b600`, `ResetView 0x50b640`, `NextView 0x50b680`, `PrevView 0x50b690`,
|
||
//! `FlipCameraYaw 0x50b6a0`. Sixteen 1.12 binding commands have one of these as their whole body
|
||
//! (`SETVIEW1`…`5`, `SAVEVIEW2`…`5`, `RESETVIEW2`…`5`, `NEXTVIEW`, `PREVVIEW`, `FLIPCAMERAYAW`).
|
||
//!
|
||
//! ## The argument ABI is a third shape, and it is neither of [`super::binding_abi`]'s two
|
||
//!
|
||
//! The four indexed entry points share one prologue, byte-identical across `0x50b5b0` /
|
||
//! `0x50b600` / `0x50b640`:
|
||
//!
|
||
//! ```text
|
||
//! 50b5b8 call 0x6f34d0 ; is-number(L, 1) — a number, or a coercible string
|
||
//! 50b5bf je <exit> ; NOT a number -> xor eax,eax ; ret (silent, zero Lua values)
|
||
//! 50b5c8 call 0x6f3620 ; tonumber(L, 1)
|
||
//! 50b5cd call 0x40a2b0 ; double -> int32, truncating toward zero
|
||
//! 50b5d4 jle <exit> ; n <= 0 -> silent
|
||
//! 50b5d9 jg <exit> ; n > 5 -> silent
|
||
//! 50b5e5 dec eax ; Lua 1..5 -> internal view 0..4
|
||
//! ```
|
||
//!
|
||
//! So the failure edge **returns without raising** — it never reaches `luaL_error 0x6f4940`, which
|
||
//! is the discriminator [`super::binding_abi`]'s header sets out. A macro doing `SetView(0)`,
|
||
//! `SetView(9)`, `SetView("left")` or `SetView()` is a silent no-op in the real client, and is one
|
||
//! here. That rules out `binding_abi::number_arg` (shape A — raises) and makes
|
||
//! `binding_abi::coerced_number` (shape C — no guard, defaults to `0.0`) merely *accidentally*
|
||
//! right, so the guard is written out rather than borrowed.
|
||
//!
|
||
//! `FlipCameraYaw` takes the same is-number guard and then the raw float — `0x6f3620` straight to
|
||
//! `fstp [ebp-4]`, with no `0x40a2b0` — so its argument is **not** truncated to an integer.
|
||
//!
|
||
//! ## Two verified facts that a reader will expect to be otherwise
|
||
//!
|
||
//! - **The range is 1…5 for all three of Set/Save/Reset.** `SaveView(1)` and `ResetView(1)` are
|
||
//! accepted by the engine and act on `FIRST_PERSON`; `0x50fa30` (SaveView's body) has no view-0
|
||
//! gate at all. 1.12 ships no `SAVEVIEW1`/`RESETVIEW1` *binding*, which is a `Bindings.xml`
|
||
//! decision, not an engine one — a macro reaches what the keybinding cannot.
|
||
//! - **`NextView`/`PrevView` do not wrap.** `0x50faa0` is `eax = view + 1; cmp eax,5; jge <ret>`
|
||
//! and `0x50fac0` is `test eax,eax; jle <ret>; dec eax` — each is a hard stop at its end of the
|
||
//! range, so holding `END` walks 1→2→3→4→5 and stays there. The app enforces it (this side only
|
||
//! queues the verb).
|
||
|
||
use mlua::{Lua, Value};
|
||
|
||
use super::Model;
|
||
|
||
/// The number of camera views — the reference's default-string table `0x84f488` is five rows, and
|
||
/// every entry point above is bounded by it.
|
||
pub const CAMERA_VIEW_COUNT: u8 = 5;
|
||
|
||
/// One camera-view intent queued by the Lua surface, drained by the app
|
||
/// ([`super::UiScript::take_camera_view_requests`]). Plain data — [`super::follow::FollowRequest`]'s
|
||
/// twin.
|
||
///
|
||
/// The indices are the **internal** ones, `0..CAMERA_VIEW_COUNT`, already range-checked here
|
||
/// exactly where the reference range-checks them (its Lua handler, `dec eax`). The app applies
|
||
/// them; it never re-validates a number that cannot be out of range.
|
||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||
pub enum CameraViewRequest {
|
||
/// `SetView(n)` — make view `n-1` the live one (distance, pitch and the saved yaw offset).
|
||
Set(u8),
|
||
/// `SaveView(n)` — store the live pose into view `n-1`.
|
||
Save(u8),
|
||
/// `ResetView(n)` — put view `n-1` back to its shipped default.
|
||
Reset(u8),
|
||
/// `NextView()` — the view above the current one, if there is one (no wrap).
|
||
Next,
|
||
/// `PrevView()` — the view below the current one, if there is one (no wrap).
|
||
Prev,
|
||
/// `FlipCameraYaw(degrees)` — add this many **degrees** to the camera's yaw. The binding body
|
||
/// 1.12 ships is `FlipCameraYaw(180)`.
|
||
FlipYaw(f32),
|
||
}
|
||
|
||
impl super::UiScript {
|
||
/// Drain the camera-view intents queued since the last call — the app applies each to the
|
||
/// camera rig.
|
||
pub fn take_camera_view_requests(&mut self) -> Vec<CameraViewRequest> {
|
||
std::mem::take(&mut self.model_mut().camera_view_requests)
|
||
}
|
||
}
|
||
|
||
/// The shared prologue of `SetView`/`SaveView`/`ResetView`: `is-number` → `tonumber` → truncate to
|
||
/// `i32` → the `1 ..= 5` window → the internal index. `None` is every silent-return edge in one.
|
||
fn view_arg(lua: &Lua, v: Value) -> Option<u8> {
|
||
// `0x6f34d0` is-number, then `0x6f3620` tonumber, then `0x40a2b0`'s chop-toward-zero cast —
|
||
// `lua.coerce_number` is the first two (Lua coerces a numeric string), `as i64 as i32` the
|
||
// third (see `binding_abi::number_arg`, whose only difference from this is that it raises).
|
||
let n = lua.coerce_number(v).ok().flatten()? as i64 as i32;
|
||
// `test eax,eax; jle` then `cmp eax,5; jg` — both silent.
|
||
(1..=i32::from(CAMERA_VIEW_COUNT))
|
||
.contains(&n)
|
||
.then(|| (n - 1) as u8)
|
||
}
|
||
|
||
/// Register the six camera-view globals.
|
||
pub(super) fn install(lua: &Lua) -> mlua::Result<()> {
|
||
let g = lua.globals();
|
||
|
||
// SetView(n) — `0x50b5b0`. Load view n (1..5). The reference then calls `0x512e90(view,
|
||
// cameraViewBlendStyle)`, which also writes the `cameraView` CVar so the active view survives
|
||
// a restart; the app does both halves.
|
||
g.set(
|
||
"SetView",
|
||
lua.create_function(|lua, n: Value| {
|
||
if let Some(view) = view_arg(lua, n) {
|
||
let mut model = lua.app_data_mut::<Model>().expect("model app_data");
|
||
model
|
||
.camera_view_requests
|
||
.push(CameraViewRequest::Set(view));
|
||
}
|
||
Ok(())
|
||
})?,
|
||
)?;
|
||
|
||
// SaveView(n) — `0x50b600` → `0x50fa30`, which reads the camera's three TARGET fields
|
||
// (`+0x198` distance, `+0x1e0` pitch, `+0x210` yaw) into the slot array and writes each to its
|
||
// archived CVar. Accepts n = 1 (see the module header).
|
||
g.set(
|
||
"SaveView",
|
||
lua.create_function(|lua, n: Value| {
|
||
if let Some(view) = view_arg(lua, n) {
|
||
let mut model = lua.app_data_mut::<Model>().expect("model app_data");
|
||
model
|
||
.camera_view_requests
|
||
.push(CameraViewRequest::Save(view));
|
||
}
|
||
Ok(())
|
||
})?,
|
||
)?;
|
||
|
||
// ResetView(n) — `0x50b640` → `0x50fae0`, which re-parses the shipped default strings at
|
||
// `0x84f488 + 12·view` into the slot and re-applies the view if it is the live one.
|
||
g.set(
|
||
"ResetView",
|
||
lua.create_function(|lua, n: Value| {
|
||
if let Some(view) = view_arg(lua, n) {
|
||
let mut model = lua.app_data_mut::<Model>().expect("model app_data");
|
||
model
|
||
.camera_view_requests
|
||
.push(CameraViewRequest::Reset(view));
|
||
}
|
||
Ok(())
|
||
})?,
|
||
)?;
|
||
|
||
// NextView() / PrevView() — `0x50b680` → `0x50faa0` and `0x50b690` → `0x50fac0`. No arguments
|
||
// at all: neither handler touches the Lua stack. Neither wraps.
|
||
g.set(
|
||
"NextView",
|
||
lua.create_function(|lua, ()| {
|
||
let mut model = lua.app_data_mut::<Model>().expect("model app_data");
|
||
model.camera_view_requests.push(CameraViewRequest::Next);
|
||
Ok(())
|
||
})?,
|
||
)?;
|
||
g.set(
|
||
"PrevView",
|
||
lua.create_function(|lua, ()| {
|
||
let mut model = lua.app_data_mut::<Model>().expect("model app_data");
|
||
model.camera_view_requests.push(CameraViewRequest::Prev);
|
||
Ok(())
|
||
})?,
|
||
)?;
|
||
|
||
// FlipCameraYaw(degrees) — `0x50b6a0`, the whole body being
|
||
// `cam[+0x100] += arg × 0.01745329238474369`. Its argument keeps its fraction (no `0x40a2b0`),
|
||
// and the is-number guard's failure edge is the same silent return the four above take.
|
||
g.set(
|
||
"FlipCameraYaw",
|
||
lua.create_function(|lua, degrees: Value| {
|
||
if let Some(d) = lua.coerce_number(degrees).ok().flatten() {
|
||
let mut model = lua.app_data_mut::<Model>().expect("model app_data");
|
||
model
|
||
.camera_view_requests
|
||
.push(CameraViewRequest::FlipYaw(d as f32));
|
||
}
|
||
Ok(())
|
||
})?,
|
||
)?;
|
||
|
||
Ok(())
|
||
}
|