the addon arc: the AddOns folder opens

- benilla loads real 1.12 addons from Interface/AddOns — TOC, XML and Lua through one loader, with per-addon saved variables, Bindings.xml and the lifecycle events addons expect
- both AddOns screens are in: the character-select list with per-addon enable state, and the ESC menu's in-game list
- the API surface is measured, not guessed — a corpus harness runs hundreds of real 1.12 addons and ranks exactly which globals, templates and verbs they demand
- the Lua dialect is the client's own, read off the binary — 5.0 semantics with the 1.12 additions, and UI source files are bytes, not UTF-8

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Sam
2026-08-11 18:18:31 +07:00
co-authored by Claude Fable 5
parent bfb3e3addc
commit 44ff435304
231 changed files with 31316 additions and 5594 deletions
@@ -61,6 +61,11 @@ pub(super) fn tip_mut(model: &mut Model, h: FrameHandle) -> mlua::Result<&mut To
/// hover).
fn apply_font(d: &mut RegionData, name: &str, fo: Option<&FontObject>) {
d.font_object = Some(name.to_string());
// The severance mask is left standing — a re-point does NOT restore inheritance in the
// reference (see `super::font`'s module doc on the `+0x2c` inheritMask). A freshly created
// line has a clean mask anyway; an adopted XML-declared line keeps whatever its own attributes
// severed. Either way the line inherits the object LIVE from here: a later
// `GameTooltipText:SetFont(…)` re-paints every tooltip line through `font::propagate`.
if let Some(f) = fo {
d.font_path = f.font.clone();
d.font_height = f.height;
@@ -263,6 +268,11 @@ fn write_cell(model: &mut Model, rh: crate::widget::RegionHandle, text: &str, co
let d = model.region_data.entry(rh).or_default();
d.text = Some(text.to_string());
d.vertex_color = Some(color);
// `AddLine("…", r, g, b)`'s colour is this line's OWN colour, exactly as if Lua had called
// `SetTextColor` on the cell — so it must survive a later mutation of the font object the
// line inherits ([`super::types::FontExplicit`]). Without the mark, one
// `GameTooltipText:SetTextColor(…)` would repaint every red/green line in the tooltip.
d.font_explicit.color = true;
d.hidden = false;
}
@@ -7,7 +7,9 @@ use mlua::{Lua, Table, Value, Variadic};
use crate::layout::{Anchor, Point};
use crate::script::object::{frame_handle_of, frame_wrapper};
use crate::script::region::region_handle_of;
use crate::script::Model;
use crate::widget::{KindState, TOOLTIP_LINE_GAP, TOOLTIP_PAD};
use super::{
append_line, bool_arg, cancel_fade, clear_content, fire_cleared, hide_tooltip, now,
@@ -209,6 +211,70 @@ pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()> {
"NumLines",
lua.create_function(|lua, this: Table| with_tip(lua, &this, |t| t.num_lines as i64))?,
)?;
// AddFontStrings(left, right) — adopt two caller-made FontStrings as the next line pair
// (`0x530c40`, wow-re `system/ui/scratch/bindings.md`; the parent module's doc already names it
// as how the real class grows past its template's 30 declared pairs).
//
// The scan-tooltip idiom, and the reason this landed with the font-object work rather than
// separately: `Gratuity-2.0.lua:56-59` builds its whole 30-line hidden tooltip out of
// `tt:CreateFontString()` + `:SetFontObject(GameFontNormal)` + `tt:AddFontStrings(l, r)`, so
// publishing the font globals only moved that library's death one line down. Five corpus addons
// sit behind those four lines.
//
// The pair is placed exactly as an engine-grown one (hidden, left/right justified, hung off the
// previous line) — the layout pass owns line geometry either way — but the regions' FONTS are
// left alone, since choosing them is the entire point of the caller doing this by hand.
m.set(
"AddFontStrings",
lua.create_function(|lua, (this, left, right): (Table, Table, Table)| {
let h = frame_handle_of(lua, &this)?;
let lh = region_handle_of(lua, &left)?;
let rh = region_handle_of(lua, &right)?;
let mut model = lua.app_data_mut::<Model>().expect("model app_data");
let frame_id = model.frame_id(h);
let prev_left = match model.arena.frame(h).map(|f| &f.kind_state) {
Some(KindState::Tooltip(t)) => t.left_lines.last().copied(),
_ => return Err(mlua::Error::runtime("not a GameTooltip")),
};
let anchor = match prev_left {
None => Anchor::new(
Point::TopLeft,
frame_id,
Point::TopLeft,
TOOLTIP_PAD,
-TOOLTIP_PAD,
),
Some(prev) => {
let prev_id = model.region_id(prev);
Anchor::new(
Point::TopLeft,
prev_id,
Point::BottomLeft,
0.0,
-TOOLTIP_LINE_GAP,
)
}
};
let left_id = model.region_id(lh);
{
let d = model.region_data.entry(lh).or_default();
d.hidden = true;
d.justify_h = crate::script::JustifyH::Left;
d.anchors = vec![anchor];
}
{
let d = model.region_data.entry(rh).or_default();
d.hidden = true;
d.justify_h = crate::script::JustifyH::Right;
d.anchors = vec![Anchor::new(Point::Right, left_id, Point::Right, 0.0, 0.0)];
}
model.touch_layout(); // two line rows entered the layout graph (decision 0740)
let t = tip_mut(&mut model, h)?;
t.left_lines.push(lh);
t.right_lines.push(rh);
Ok(())
})?,
)?;
// SetMinimumWidth(w) — a floor on the auto-sized width (the ref's SetTooltipMoney calls it
// with the money row's width so coins never overhang the plate).
m.set(