Both modules land together because `classic_api/mod.rs` and `widget_surface.rs` carry rows for
each: split, the first commit named a module the second added, and would not have compiled.
## The secure-handler family, because 1.12 has nothing to be secure from
`frame.rs` declined it with a stated reason — "modern restricted execution, which 1.12 has no
equivalent of" — and that is right about the *sandbox* and is the whole of why it is now built.
A secure handler exists so an untainted snippet may call **protected** functions; 1.12 has
neither taint nor protected functions, so the restriction has no counterpart here, while the
plumbing underneath (a named frame store, a snippet on a child's handler) is ordinary
behaviour the snippets need. The body runs as plain Lua with the names the secure environment
would bind.
**The per-header shared table is the part worth getting right.** `MenuWheel.lua:484` writes a
bare `held = false` inside a snippet, and in the real secure environment that lands in a table
per header, not `_G`. `setfenv` onto one table per frame, `__index` to `_G` so the rest of the
API resolves; the test asserts `held` is **not** a global afterwards. My first test asserted
through `_G` and failed — the implementation was right and the test measured the wrong thing.
**`SecureActionButtonTemplate` is not empty**: reading your own attributes on click is plain
dispatch. `type="click"` clicks the frame in `clickbutton` (how the wheels reach the game's
micro buttons), and the lookup follows upstream's order (`type-<button>`, `*type*`, `type`).
**That exposed a real bug of ours**: `SetAttribute` silently dropped a frame.
`attribute_value` kept bool/int/number/string and sent everything else to `ScriptValue::Nil`,
so `SetAttribute("clickbutton", SomeFrame)` stored nothing — and upstream's `clickbutton` is
routinely a frame, which `Cinematic/MenuWheel.lua` relies on. A widget table is now kept as
`ScriptValue::Frame(id)`, the variant `NAME_PLATE_CREATED`'s arg1 already uses, which
round-trips through `object::frame_wrapper`. A non-widget table is still nil, documented.
**Not built, named rather than left to be discovered**: the binding half (`ClearBindings`,
`SetBindingClick`, …) are no-ops — no override-binding layer here to write into — so a wheel
opened with the mouse works and hold-a-key-and-release does not; and the `_onshow`/`_onhide`
attribute snippets are not run, since `SetAttribute` is a plain store with nothing to drive
them.
Templates in `assets/ui/CompatTemplates.xml`, listed on that wall with the reason: no stock
file to migrate from, and XML rather than `compat/` only because `inherits=` is loader-resolved.
A trap caught before shipping: `BackdropTemplateMixin.GetBackdropColor` forwarding to
`self:GetBackdropColor()` calls **itself** once `Mixin()` copies it onto the frame — the stock
getters answer, so there are no getter rows, and a test mixes in and reads back.
## `CreateAnimationGroup`: the surface, with nothing animated
1.12 has no animation system and this UI pass has no interpolation to hang one on. The cost is
the **absent constructor**: `Cinematic/Options.lua:7106` builds a rotating spinner and the nil
method aborted the whole options panel. Measured first — that is the addon's only animation in
55,661 lines, so this is a surface for a decoration.
**A non-looping group finishes the moment it plays**, firing `OnFinished`, because nothing
moves and that is the reading that keeps an addon's flow right; one that chains its next step
off `OnFinished` would otherwise wait forever. A looping group plays until stopped. Stated in
the header, since the name implies motion: a spinner does not spin, a fade appears at its
destination, a sequence lands on its last frame. Setters and getters round-trip.
Corpus: all 100 of grom's addons surveyed before and after — 85 ok / 1 fail, rosters
identical. benilla-ui 2081 passed. Full chain green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
205 lines
8.6 KiB
Rust
205 lines
8.6 KiB
Rust
//! ClassicAPI (<https://github.com/brues-code/ClassicAPI>) compatibility: 550+ functions and 50+
|
|
//! events across ~60 namespaces backported from modern WoW onto 1.12.1, plus a Lua 5.1 syntax
|
|
//! backport. That repo's own `docs/API.md` is the authoritative per-function spec (a point-in-time
|
|
//! names-only snapshot lives at `docs/classic-api-functions.md`, re-fetch upstream before
|
|
//! resuming); `docs/ADDON_COMPAT.md` tracks namespace-by-namespace status. One file per namespace
|
|
//! here, each a `pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()>` (a bare-global
|
|
//! namespace) or returning an `mlua::Table` (a `C_*` namespace), called from this file's own
|
|
//! `install`.
|
|
//!
|
|
//! **Both halves of the 5.1 backport are here, and both were maintainer calls:**
|
|
//!
|
|
//! * the *stdlib* half (`select`, `string.match`/`gmatch`/`reverse`, `math.fmod`/`modf`) in
|
|
//! `stdlib_backport.rs`, which hands back the originals `script/lua50.rs` stashed before
|
|
//! nil-ing them;
|
|
//! * the *syntax* half (`#`, `%`, `...` as an expression, `0x` hex, `[=[ ]=]`) in the Lua fork
|
|
//! itself, `third_party/lua-src/BENILLA.md`. It is **not** free, which this comment used to
|
|
//! claim: the fork had deliberately deleted the first three from 5.1's parser and 5.0-ified
|
|
//! `read_numeral` to match stock 1.12, and the realm's addons are written against a client
|
|
//! where ClassicAPI has put them back. SoftSpoken's nine load failures were all of them.
|
|
//!
|
|
//! Both are always-on, like everything in `compat/`, and both are the same trade: an addon that
|
|
//! feature-tests for a modern client now takes the modern branch. On this server, with ClassicAPI
|
|
//! loaded, that *is* the correct answer. `lua51_probe_fallout.rs` carries the two addons for
|
|
//! which it is not.
|
|
//!
|
|
//! Detection: [`CLASSIC_API_VERSION`] is set, and deliberately so — see its own comment for what
|
|
//! the number claims and why it is the number it is. Most functions here need no flag at all (an
|
|
//! addon checks `if FnName then` directly), but pfUI gates its *entire* load on this global, so
|
|
//! withholding it is not neutral: it disables the addon outright.
|
|
|
|
use mlua::Lua;
|
|
|
|
mod action;
|
|
mod addons;
|
|
mod animation;
|
|
mod callback_registry;
|
|
mod chat_bubbles;
|
|
mod chat_info;
|
|
mod class_color;
|
|
mod color_mixin;
|
|
mod color_util;
|
|
mod container;
|
|
mod creature_info;
|
|
mod cvar;
|
|
mod date_and_time;
|
|
mod encoding_util;
|
|
mod enums;
|
|
mod equipment_set;
|
|
mod event_util;
|
|
mod expansion;
|
|
mod faction;
|
|
mod focus;
|
|
pub(in crate::script) mod frame;
|
|
mod friend_list;
|
|
mod gossip;
|
|
mod hooks;
|
|
mod input;
|
|
mod item;
|
|
mod item_location;
|
|
mod item_mixin;
|
|
mod loot_history;
|
|
mod loss_of_control;
|
|
mod lua51_probe_fallout;
|
|
mod lua_ext;
|
|
mod macro_ext;
|
|
mod macro_icons;
|
|
mod map;
|
|
mod math_ext;
|
|
mod merchant_frame;
|
|
mod misc;
|
|
// `pub(in …compat)` for its `plate_frame`, which SuperWoW's `UnitNameplate` shares.
|
|
pub(in crate::script::compat) mod nameplate;
|
|
mod new_items;
|
|
mod pixel_util;
|
|
mod player_cache;
|
|
mod player_info;
|
|
mod player_util;
|
|
mod pools;
|
|
mod quest_log;
|
|
mod secure;
|
|
mod spell;
|
|
mod spellbook;
|
|
mod state;
|
|
mod stdlib_backport;
|
|
mod string_methods;
|
|
mod table_util;
|
|
mod taxi_map;
|
|
mod timer;
|
|
mod tooltip;
|
|
mod totem;
|
|
mod unit;
|
|
mod unit_auras;
|
|
mod unit_extra;
|
|
mod unit_menu;
|
|
|
|
/// `CLASSIC_API_VERSION`, packed `X*10000 + Y*100 + Z` as upstream packs it.
|
|
///
|
|
/// **What this number claims, and why it is this number.** It is not a version of benilla's code:
|
|
/// it is a claim about which ClassicAPI *API surface* is present, and real addons gate on it. pfUI
|
|
/// 9.0.25 refuses to load at all below `11304` (its `API_Check.lua`'s `PFUI_CLASSIC_API_MIN`), so
|
|
/// a lower number is not a humbler claim — it is "pfUI is disabled", which is strictly worse for a
|
|
/// player than a few missing functions that error where they are actually used.
|
|
///
|
|
/// So this says v1.13.4: the surface pfUI asks for. We do **not** implement every function of that
|
|
/// release (`docs/ADDON_COMPAT.md` has the per-namespace truth, and several namespaces are partial
|
|
/// or absent). An addon reaching for something unbuilt gets a clean `nil`-call error naming the
|
|
/// function, which is how the rest of this compat layer already behaves and is a far better
|
|
/// failure than a silent wrong answer. Raise this only alongside real coverage, and never to
|
|
/// quiet a specific addon without checking what else keys on it.
|
|
const CLASSIC_API_VERSION: i64 = 11304;
|
|
|
|
/// `INTERFACE_VERSION`: the client's own interface number, 1.12.1's `11200` — the `## Interface`
|
|
/// value a 1.12 `.toc` carries. Unrelated to [`CLASSIC_API_VERSION`] despite living beside it.
|
|
const INTERFACE_VERSION: i64 = 11200;
|
|
|
|
pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()> {
|
|
unit::install(lua)?;
|
|
unit_menu::install(lua)?;
|
|
unit_extra::install(lua)?;
|
|
focus::install(lua)?;
|
|
let g = lua.globals();
|
|
g.set("CLASSIC_API_VERSION", CLASSIC_API_VERSION)?;
|
|
g.set("INTERFACE_VERSION", INTERFACE_VERSION)?;
|
|
g.set("Enum", enums::install(lua)?)?;
|
|
g.set("C_Timer", timer::install(lua)?)?;
|
|
g.set("C_DateAndTime", date_and_time::install(lua)?)?;
|
|
g.set("C_ChatBubbles", chat_bubbles::install(lua)?)?;
|
|
g.set("C_ChatInfo", chat_info::install(lua)?)?;
|
|
g.set("C_EncodingUtil", encoding_util::install(lua)?)?;
|
|
g.set("C_EquipmentSet", equipment_set::install(lua)?)?;
|
|
g.set("C_LootHistory", loot_history::install(lua)?)?;
|
|
g.set("C_LossOfControl", loss_of_control::install(lua)?)?;
|
|
g.set("C_Container", container::install(lua)?)?;
|
|
g.set("C_CreatureInfo", creature_info::install(lua)?)?;
|
|
g.set("C_CVar", cvar::install(lua)?)?;
|
|
cvar::install_globals(lua)?;
|
|
g.set("C_NamePlate", nameplate::install(lua)?)?;
|
|
g.set("C_NewItems", new_items::install(lua)?)?;
|
|
g.set("C_Macro", macro_ext::install(lua)?)?;
|
|
g.set("C_Item", item::install(lua)?)?;
|
|
g.set("C_AddOns", addons::install(lua)?)?;
|
|
// `C_Spell`'s bare half hands out the very same function values the table holds, so the
|
|
// namespaced and bare spellings cannot drift — hence the table before the globals.
|
|
let c_spell = spell::install(lua)?;
|
|
spell::install_globals(lua, &c_spell)?;
|
|
g.set("C_Spell", c_spell)?;
|
|
g.set("C_SpellBook", spellbook::install(lua)?)?;
|
|
g.set("C_UnitAuras", unit_auras::install(lua)?)?;
|
|
g.set("C_ColorUtil", color_util::install(lua)?)?;
|
|
g.set("C_ClassColor", class_color::install(lua)?)?;
|
|
g.set("C_QuestLog", quest_log::install(lua)?)?;
|
|
g.set("C_Reputation", faction::install(lua)?)?;
|
|
g.set("C_FriendList", friend_list::install(lua)?)?;
|
|
g.set("C_GossipInfo", gossip::install(lua)?)?;
|
|
g.set("C_MerchantFrame", merchant_frame::install(lua)?)?;
|
|
g.set("C_Map", map::install(lua)?)?;
|
|
taxi_map::install(lua)?;
|
|
g.set("C_PlayerCache", player_cache::install(lua)?)?;
|
|
g.set("C_PlayerInfo", player_info::install(lua)?)?;
|
|
g.set("PlayerUtil", player_util::install(lua)?)?;
|
|
item::install_globals(lua)?;
|
|
macro_ext::install_globals(lua)?;
|
|
action::install(lua)?;
|
|
state::install(lua)?;
|
|
event_util::install(lua)?;
|
|
expansion::install(lua)?;
|
|
input::install(lua)?;
|
|
lua_ext::install(lua)?;
|
|
// After the frame and addon surfaces it probes (`CreateFrame`, `IsAddOnLoaded`).
|
|
lua51_probe_fallout::install(lua)?;
|
|
math_ext::install(lua)?;
|
|
// After `math_ext`: its `Round` is the rounding `PixelUtil.GetNearestPixelSize` does.
|
|
pixel_util::install(lua)?;
|
|
pools::install(lua)?;
|
|
totem::install(lua)?;
|
|
timer::install_globals(lua)?;
|
|
callback_registry::install(lua)?;
|
|
color_mixin::install(lua)?;
|
|
misc::install(lua)?;
|
|
faction::install_globals(lua)?;
|
|
merchant_frame::install_mail_globals(lua)?;
|
|
hooks::install(lua)?;
|
|
frame::install(lua)?;
|
|
// After `frame::install`: the secure-handler methods go onto the same frame method table and
|
|
// use its `delegate`, and nothing in them is read at load time.
|
|
secure::install(lua)?;
|
|
// Likewise: `CreateAnimationGroup` goes onto the frame table and both region leaves.
|
|
animation::install(lua)?;
|
|
tooltip::install(lua)?;
|
|
stdlib_backport::install(lua)?;
|
|
string_methods::install(lua)?;
|
|
table_util::install(lua)?;
|
|
// After `item::install`: the mixin's `IsValid` calls `C_Item.DoesItemExist`. Only at *call*
|
|
// time, as `color_mixin.rs` explains for `CreateColor`, so the order is for the reader rather
|
|
// than for correctness.
|
|
item_location::install(lua)?;
|
|
item_mixin::install(lua)?;
|
|
// After `lua_ext::install`: the mixin chunk is a `CreateFromMixins` consumer and the picker
|
|
// constructs it through `CreateAndInitFromMixin`, both of which that file defines. Unlike the
|
|
// two above, this order is load-time, not call-time — `CreateAndInitFromMixin` is itself a
|
|
// chunk, so it must already be a global.
|
|
macro_icons::install(lua)?;
|
|
Ok(())
|
|
}
|