Files
benilla-octo/crates/benilla-app/assets/ui/CharacterFrame.xml
T
SamandClaude Fable 5 f6f2af3da9 the combat log's wire completes, and PlayerModel is its own class
- the combat log's last eight wire bodies decode — PARTYKILLLOG, SPELLINSTAKILLLOG, PROCRESIST, SPELLORDAMAGE_IMMUNE, SPELLDISPELLOG, DISPEL_FAILED, ENCHANTMENTLOG, SPELLLOGEXECUTE — each layout read off vmangos's own writer and pinned by a byte-exact golden (DISPEL_FAILED carries no count, the list runs to the end of the body; SPELLLOGEXECUTE's POWER_DRAIN row is (amount, power, multiplier), verified at the client's bytes as well as the server's); with the eleven chat types that were waiting on them, 44 chat types become 55 — 29 sentence families, the death pair and the aura rows riding descriptor diffs because 1.12 has no "unit died" opcode, plus the six trailers ((glancing), (30 blocked), (12 absorbed) and kin) that had been missing from every family; also corrected: the durability-death line was going to the red error frame with hard-coded English, and SMSG_SPELLNONMELEEDAMAGELOG lacked its split-damage branch and its blocked wording
- PlayerModel is its own frame kind: SetUnit, RefreshUnit and SetRotation were published on Model, but they belong to the <PlayerModel> method table (0x84f1fc), and the client's lookup chains derived → base only, so a plain <Model> can never reach them — they got there from a strings scan read as ownership, and a strings hit answers whether a name EXISTS, not who owns it (SetUnit's one pooled string is referenced by the PlayerModel and GameTooltip tables and by no Model entry); the new PlayerModel → Model → Frame → Region chain unblocks CreateFrame("PlayerModel"), which is how pfUI builds every unit-frame portrait — and through pfUI, pfQuest and ShaguDPS
- a nil where a number goes is four shapes, not one law: a census of all 408 widget-registrar entries found 62 numeric positions that validate-or-raise, 43 that gate then stage an alternate, 64 that silently coerce to 0.0 and 5 that gate then store nothing — so SetMaxLetters(nil) completes and stores 0, "no limit" (the argument COUNT is checked exactly, the type is not), and the backdrop colour setters are asymmetric on purpose: a nil r/g/b channel is 0.0 but a nil alpha is OPAQUE, and reading every nil as 0.0 would have turned those borders transparent; both setters now also quantize ×255 into the packed 0xAARRGGBB byte quad the client actually stores, so 0.09 reads back as 23/255
- two more addon-killers: LIGHTYELLOW_FONT_COLOR_CODE was the one missing line of Fonts.xml's seven-line colour block (aux-addon concatenates it at file scope and died on its own first line), and the guard now enumerates the whole block l.4-20 instead of the names an earlier round had noticed absent; and the unit frames publish the reference's names — 1.12 has no *PowerBar anywhere, every bar is ManaBar and the colour table is ManaBarColor (8 of the top-20 addons read it by name), so ManaBarColor is the published table our own bars index into, with 22 more regions renamed to the reference's own

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 19:54:58 +07:00

1993 lines
135 KiB
XML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<Ui>
<!--
benilla's character window (decision 0208, phase 1a display + phase 1b equip interactions
— the C-key window, paper doll first):
the round player portrait + name + level line + close button + the five-tab row, dressed
from the real 1.12 `CharacterFrame.xml`/`.lua` + `PaperDollFrame.xml`/`.lua` +
`CharacterFrameTemplates.xml` + the `Model_*` rotate helpers (`UIParent.lua:1421-1462`) —
extracted from the MPQ chain to session scratch this session (director-local reference
artifacts, never committed — the 0068/0138 pattern; line cites below are into that
extraction's own `ref-CharacterFrame.xml`/`.lua`, `ref-PaperDollFrame.xml`/`.lua`,
`ref-UIParent.lua`, `ref-GlobalStrings.lua`). `CharacterFrameTabButtonTemplate` is ALREADY
defined in `UiPanels.xml:507-563` (battle-tested by Merchant/QuestLog, 0138) — reused
here, not redefined.
Docks the left UIPanel slot (`UiPanels.xml` `UIPanelWindows["CharacterFrame"] =
{ area = "left", pushable = 2, whileDead = 1 }`, the ref's own `UIParent.lua l.19` row —
decision 1507; pushable was wrongly 0 from 0208 until then). pushable=2 means it does NOT
simply replace the NPC-session windows: a pushable-0 window opening under it slides this
sheet to the CENTER seat (vendor left, your equipment beside it), and showing this over a
pushable-0 occupant parks it at center likewise. `ToggleCharacter(tab)` (ref
`CharacterFrame.lua:3-23`) is the show/hide/tab-switch entry point;
the app's 'C' binding calls it (`ui_script/mod.rs`).
Every frame this file defines wears the REFERENCE's name (`CharacterFrame`,
`PaperDollFrame`, …) — including the strings `ToggleCharacter`/`CharacterFrame_ShowSubFrame`
pass around internally (`BENILLA_CHARACTERFRAME_SUBFRAMES` lists `"PaperDollFrame"`). The
`Benilla` prefix these carried until the reference-naming pass is gone: addons index these
globals by the reference's name, and a prefixed frame is simply the wrong name for a frame
that IS the reference's.
Phase 1a shipped the window, display-only. Phase 1b (THIS pass) adds the equip
interactions over the SAME display — `PickupInventoryItem`/`UseInventoryItem`/
`AutoEquipCursorItem`/`CursorCanGoInSlot`/`IsInventoryItemLocked`, joining the paper doll
onto the ONE cursor-payload space via its sentinel `EQUIPMENT_BAG` bag id (decision 0216
§1, extended by decision 0208's own phasing, §"Phasing"/"1a"/"1b", and 0216/0218's
cursor-arc design). Tabs 2-5 (Pet/Reputation/Skills/Honor) were all OMITTED at 1b, the 0138
merchant-tab precedent ("tabs arrive with their pages") — `BENILLA_CHARACTERFRAME_SUBFRAMES`
only lists the pages that exist. Skills arrived with decision 0437 phase 4
(`SkillFrame.xml`), **Pet with decision 1057** (`PetPaperDollFrame.xml`), which takes the
real client's own slot 2, **Reputation** (`ReputationFrame.xml`), which takes its own
slot 3 and puts Skills back on the reference's 4, and **Honor** (`HonorFrame.xml`) on its
own 5 — so the row is COMPLETE: all five tabs are the reference's, in its order, at its
numbers, and nothing is omitted any more. Three things here are shared
WITH the pet page rather than owned by this one, and each is flagged at its definition: the
`(unit, prefix)` stat setters (including `_SetDefense`, which exists for the pet row alone),
the `BenillaPaperDollModel_*` pane quartet (driven through each pane's own `frame.setFacing`),
and the tab row itself, whose tab 2 the pet page raises and hides.
Named deferrals/OMITs (each re-cited at its own site below):
- Guild line (`CharacterGuildText`) and the PVP rank name (`UnitPVPName` — plain `UnitName`
ships instead): both decision 0208's own named deferrals (guild needs `CMSG_GUILD_QUERY`;
rank needs the honor arc). `CharacterTitleText` (also PVP-rank-driven) is dropped with it.
- Shift-click chat-link insert (decision 1059) and ctrl-click `DressUpItemLink` (the
dressing room, decision 1060) both LANDED — the left-click fork below is now the
reference's own three arms (ctrl → preview, shift → post the link, else pick up).
They were named deferrals when this file was written; the note stays because the fork
below reads as a transcription of that history.
- The ammo slot (id 0) LOADS ammo by entry (`CMSG_SET_AMMO`) — it does NOT swap into an
equip slot (decision 0526; wow-re `cursor-dragdrop-slots.md`: the client's auto-equip
sender forks ammo-class → SET_AMMO). So it can't share the 19 named slots'
`PickupInventoryItem` path — its click/drop route to `BenillaCharacterAmmoSlot_OnClick`
→ `AutoEquipCursorItem` (the app's autoequip drain resolves the held entry and forks to
SET_AMMO), gated by `CursorCanGoInSlot(0)` so only an ammo payload fits. It carries no
ring, so the `CURSOR_UPDATE` highlight / `ITEM_LOCK` dim legs no-op, and there is no
drag-OUT of loaded ammo; it keeps sharing `BenillaPaperDollSlot_OnLoad`/`_Update`/
`_OnEvent` for its DISPLAY. (The four equipped-bag icons, ids 20-23, joined the
`EQUIPMENT_BAG` payload space in decision 0216/0218 — no longer out of the drag space.)
- Cooldowns (`GetInventoryItemCooldown`/`CooldownFrameTemplate`), the broken-red tint
(`GetInventoryItemBroken`), and the shift-compare tooltip (`SHOW_COMPARE_TOOLTIP`):
none of their backing feeds/engine affordances exist yet — the item stats feed has no
durability/cooldown channel (the same named gap as 0138's damaged-sell-price).
- The `CURSOR_UPDATE` slot highlight is an EMULATION, not the reference's true
`LockHighlight`/`UnlockHighlight` (grepped `benilla-ui/src/script/button.rs` this
session — no such Button methods exist; a Button's highlight texture only ever shows
on REAL mouse hover, `ButtonState::region_visible`'s hardwired `enabled && hovered`, no
forced-show path). A real forced-highlight affordance is a `benilla-ui` follow-up, not
blocking this slice — `BenillaPaperDollSlot_LockHighlight`/`_UnlockHighlight` below
tint the slot's Quickslot2 ring instead, flagged at their own definition.
- The relic slot: `UnitHasRelicSlot` is vanilla-always-false (there is no relic slot in
1.12), so the ref's `CharacterAmmoSlot:Hide()`/relic-texture branches are DEAD CODE even
in the real client — omitted outright rather than transcribing unreachable branches; the
ammo slot always shows.
- Class-specific stat-tooltip flavor text (`WARRIOR_STRENGTH_TOOLTIP` and its ~20 siblings):
only the 5 `DEFAULT_*_TOOLTIP` fallbacks ship. `PaperDollStatTooltip` still looks
the class-specific global up FIRST exactly like the ref, so dropping in the per-class
table later is pure string data, no logic change — the numeric stat lines (the
load-bearing content) are complete either way.
- The model pane ships as a booth-texture stand-in, not a live `<Model>`/`<PlayerModel>`
widget (decision 0208 §5): our engine's `FrameKind::Model` parses but has no
`SetUnit`/`RefreshUnit` surface (verified this session — grepped `benilla-ui/src/script`),
so `CharacterModelFrame` materializes here as a plain `<Frame>` sampling the paper-doll
booth bake via `BenillaSetBoothTexture(region, "paperdoll")` (the `SetPortraitTexture`
plumbing's sibling, per this session's parallel-build contract) — `DISPLAY_SIZE_CHANGED`/
`RefreshUnit` don't apply and are dropped. `OnMouseUp`-to-equip DOES apply, though (1b):
it needs no live 3D — just `EnableMouse(true)` + the two script hooks, added below. The
held-button continuous spin (`Model_OnUpdate`, ref `UIParent.lua:1444-1462`) needs
`GetButtonState()=="PUSHED"` — grepped, no such method exists on our `Button` — so this
ships CLICK-ONLY (±0.03 rad/click); the held-spin is a follow-up once/if `GetButtonState`
lands.
- The ammo slot's data path (verified this session, not the speculative framing in decision
0208 §4): the ref's `CharacterAmmoSlot` is NOT driven by a separate `PLAYER_AMMO_ID`
Lua global — it shares the exact same `PaperDollItemSlotButton_OnLoad`/`_Update` functions
the 19 named slots use (ref `ref-PaperDollFrame.xml:999-1028`, byte-identical to the
shared template's own Scripts block at l.8-37), just keyed off `GetInventorySlotInfo(
"AmmoSlot")` instead of e.g. `"HeadSlot"`. Whatever `PLAYER_AMMO_ID`/bag-summing happens
for that particular slot id is the parallel binding's own implementation detail, invisible
to this Lua — re-expressed here exactly as the ref wires it.
- `$parentStock` (the ammo slot's yellow "in bags" count, ref `ref-PaperDollFrame.xml:
970-978`): no Lua in this session's extraction ever populates it (likely an ammo-picker-
addon-only overlay outside this pull) — dropped rather than guessed at.
Engine-gap notes (transcribed anyway per this file's own instructions, or worked around) —
the first two are CLOSED, kept only because the workarounds below still read as if they held:
- `HitRectInsets` (both `CharacterFrame` and `PaperDollFrame`, ref's identical
0/30/0/45 on both) was a loader no-op when this file was written; it landed with the
micro menu (decision 0655) and now really insets the mouse rect.
- `enableMouse`/`setAllPoints` (frame-level) were no-ops on `<Frame>` too; both are wired
now (`loader/geometry.rs`'s `apply_attrs`/`apply_anchors`, and
`loader/tests.rs::frame_set_all_points_pins_to_parent`). `PaperDollFrame` still
carries an explicit `Size`+`TOPLEFT` anchor rather than the ref's `setAllPoints="true"`
— exactly equivalent for a child sized to fill its parent, so it is left alone rather
than churned.
- A plain `<Frame>` isn't mouse-enabled by construction in this engine (only Button/
CheckButton/EditBox/ScrollingMessageFrame are — `widget/mod.rs`'s ctor comment, already
the reason `UnitFrames.xml` calls `EnableMouse(true)` on its own root frame) — every
hover-tooltip `<Frame>` below (the 5 attribute rows, armor/attack/damage/ranged rows, the
5 resistance frames — none of them Buttons) gets its own `self:EnableMouse(true)` in
`OnLoad` for this reason; the 19 item slots are Buttons and need no such call.
- `TEXT(x)` (the ref's localization-tooling identity macro) has no engine equivalent and
needs none — it's a build-time no-op wrapper in the real client, so every `TEXT(FOO)` call
below is transcribed as the bare `FOO` global.
- The font color codes (`HIGHLIGHT_FONT_COLOR_CODE`/`GREEN_FONT_COLOR_CODE`/
`RED_FONT_COLOR_CODE`/`FONT_COLOR_CODE_CLOSE`, `NORMAL_FONT_COLOR`/`HIGHLIGHT_FONT_COLOR`)
are defined by the real `Fonts.xml`'s own <Script> block (ref l.8-19 — extracted and
verified this session), so ours live in our `Fonts.xml` (loaded first), not here.
Sounds (decision 0090): `igCharacterInfoOpen`/`Close` on Show/Hide (ref `CharacterFrame.lua:
83/96`), `igCharacterInfoTab` on the tab click (ref `CharacterFrameTab_OnClick`, l.47),
`igInventoryRotateCharacter` on each rotate click (ref `UIParent.lua:1432/1441`).
-->
<Script><![CDATA[
-- ── GlobalStrings values this window uses (ref-GlobalStrings.lua, cited per line — the
-- `X = X or "..."` idiom matches CastingBar.xml's own convention: a real GlobalStrings.lua,
-- if one ever loads, wins over this fallback) ──────────────────────────────────────────────
CHARACTER = CHARACTER or "Character" -- :481
SKILLS = SKILLS or "Skills" -- :3477 (decision 0437 phase 4, the Skills tab label)
PET = PET or "Pet" -- :3010 (the Pet tab label, decision 1057)
REPUTATION = REPUTATION or "Reputation" -- :3328 (the Reputation tab label)
PLAYER_LEVEL = PLAYER_LEVEL or "Level %d %s %s" -- :3075
ARMOR = ARMOR or "Armor" -- :62
ARMOR_COLON = ARMOR_COLON or "Armor:" -- :63
DEFENSE_COLON = DEFENSE_COLON or "Defense:" -- :917 (the shared
-- _SetDefense's label; only
-- the pet page has the row)
ARMOR_TOOLTIP = ARMOR_TOOLTIP or
"Decreases the amount of damage taken from physical attacks. The amount of reduction is influenced by the level of the attacker.\nDamage reduction against a level %d attacker: %.1f%%" -- :65
ATTACK_POWER_COLON = ATTACK_POWER_COLON or "Power:" -- :71
ATTACK_SPEED_COLON = ATTACK_SPEED_COLON or "Attack Speed (seconds):" -- :73
ATTACK_TOOLTIP = ATTACK_TOOLTIP or "Attack Rating" -- :75
ATTACK_TOOLTIP_SUBTEXT = ATTACK_TOOLTIP_SUBTEXT or
"Your attack rating affects your chance to hit a target, and is based on the weapon skill of the weapon you are currently wielding." -- :76
DAMAGE_COLON = DAMAGE_COLON or "Damage:" -- :888
DAMAGE_PER_SECOND = DAMAGE_PER_SECOND or "Damage per Second:" -- :889
DPS_TEMPLATE = DPS_TEMPLATE or "(%.1f damage per second)" -- :942
MELEE_ATTACK = MELEE_ATTACK or "Melee Attack" -- :2680
MELEE_ATTACK_POWER = MELEE_ATTACK_POWER or "Melee Attack Power" -- :2681
MELEE_ATTACK_POWER_TOOLTIP = MELEE_ATTACK_POWER_TOOLTIP or
"Increases damage with melee weapons by %.1f damage per second." -- :2682
NOT_APPLICABLE = NOT_APPLICABLE or "N/A" -- :2802
RANGED_ATTACK = RANGED_ATTACK or "Ranged Attack" -- :3292
RANGED_ATTACK_POWER = RANGED_ATTACK_POWER or "Ranged Attack Power" -- :3293
RANGED_ATTACK_POWER_TOOLTIP = RANGED_ATTACK_POWER_TOOLTIP or
"Increases damage with ranged weapons by %.1f damage per second." -- :3294
RANGED_ATTACK_TOOLTIP = RANGED_ATTACK_TOOLTIP or "Ranged Attack Rating" -- :3295
INVTYPE_WEAPONMAINHAND = INVTYPE_WEAPONMAINHAND or "Main Hand" -- :2375
INVTYPE_WEAPONOFFHAND = INVTYPE_WEAPONOFFHAND or "Off Hand" -- :2376
INVTYPE_RANGED = INVTYPE_RANGED or "Ranged" -- :2366
SPELL_STAT0_NAME = SPELL_STAT0_NAME or "Strength" -- :4118
SPELL_STAT1_NAME = SPELL_STAT1_NAME or "Agility" -- :4119
SPELL_STAT2_NAME = SPELL_STAT2_NAME or "Stamina" -- :4120
SPELL_STAT3_NAME = SPELL_STAT3_NAME or "Intellect" -- :4121
SPELL_STAT4_NAME = SPELL_STAT4_NAME or "Spirit" -- :4122
RESISTANCE1_NAME = RESISTANCE1_NAME or "Holy Resistance" -- :3347 (unused: Holy never rides the paper doll)
RESISTANCE2_NAME = RESISTANCE2_NAME or "Fire Resistance" -- :3348
RESISTANCE3_NAME = RESISTANCE3_NAME or "Nature Resistance" -- :3349
RESISTANCE4_NAME = RESISTANCE4_NAME or "Frost Resistance" -- :3350
RESISTANCE5_NAME = RESISTANCE5_NAME or "Shadow Resistance" -- :3351
RESISTANCE6_NAME = RESISTANCE6_NAME or "Arcane Resistance" -- :3352
RESISTANCE_TYPE1 = RESISTANCE_TYPE1 or "holy" -- :3362
RESISTANCE_TYPE2 = RESISTANCE_TYPE2 or "fire" -- :3363
RESISTANCE_TYPE3 = RESISTANCE_TYPE3 or "nature" -- :3364
RESISTANCE_TYPE4 = RESISTANCE_TYPE4 or "frost" -- :3365
RESISTANCE_TYPE5 = RESISTANCE_TYPE5 or "shadow" -- :3366
RESISTANCE_TYPE6 = RESISTANCE_TYPE6 or "arcane" -- :3367
RESISTANCE_EXCELLENT = RESISTANCE_EXCELLENT or "Excellent" -- :3353
RESISTANCE_VERYGOOD = RESISTANCE_VERYGOOD or "Very Good" -- :3368
RESISTANCE_GOOD = RESISTANCE_GOOD or "Good" -- :3355
RESISTANCE_FAIR = RESISTANCE_FAIR or "Fair" -- :3354
RESISTANCE_POOR = RESISTANCE_POOR or "Poor" -- :3358
RESISTANCE_NONE = RESISTANCE_NONE or "None" -- :3357
RESISTANCE_TOOLTIP_SUBTEXT = RESISTANCE_TOOLTIP_SUBTEXT or
"Increases the ability to resist %s-based attacks, spells and abilities.\nResistance against level %d: |cffffffff%s|r" -- :3360
-- Default (class-agnostic) stat tooltips only — see this file's header comment on the
-- class-specific (WARRIOR_STRENGTH_TOOLTIP, …) omission.
DEFAULT_STRENGTH_TOOLTIP = DEFAULT_STRENGTH_TOOLTIP or "Increases attack power with melee weapons." -- :916
DEFAULT_AGILITY_TOOLTIP = DEFAULT_AGILITY_TOOLTIP or
"Increases attack power with ranged weapons.\nImproves chance to score a critical hit with all weapons.\nIncreases armor and chance to dodge attacks." -- :911
DEFAULT_STAMINA_TOOLTIP = DEFAULT_STAMINA_TOOLTIP or "Increases health points." -- :915
DEFAULT_INTELLECT_TOOLTIP = DEFAULT_INTELLECT_TOOLTIP or "Increases the rate at which weapon skills improve." -- :913
DEFAULT_SPIRIT_TOOLTIP = DEFAULT_SPIRIT_TOOLTIP or "Increases health and mana regeneration rates." -- :914
HEADSLOT = HEADSLOT or "Head" -- :2123
NECKSLOT = NECKSLOT or "Neck" -- :2713
SHOULDERSLOT = SHOULDERSLOT or "Shoulders" -- :3428
BACKSLOT = BACKSLOT or "Back" -- :133
CHESTSLOT = CHESTSLOT or "Chest" -- :746
SHIRTSLOT = SHIRTSLOT or "Shirt" -- :3427
TABARDSLOT = TABARDSLOT or "Tabard" -- :4217
WRISTSLOT = WRISTSLOT or "Wrist" -- :5458
HANDSSLOT = HANDSSLOT or "Hands" -- :2115
WAISTSLOT = WAISTSLOT or "Waist" -- :5428
LEGSSLOT = LEGSSLOT or "Legs" -- :2565
FEETSLOT = FEETSLOT or "Feet" -- :1967
FINGER0SLOT = FINGER0SLOT or "Finger" -- :1971
FINGER1SLOT = FINGER1SLOT or "Finger" -- :1972
TRINKET0SLOT = TRINKET0SLOT or "Trinket" -- :4313
TRINKET1SLOT = TRINKET1SLOT or "Trinket" -- :4314
MAINHANDSLOT = MAINHANDSLOT or "Main Hand" -- :2658
SECONDARYHANDSLOT = SECONDARYHANDSLOT or "Off Hand" -- :3400
RANGEDSLOT = RANGEDSLOT or "Ranged" -- :3291
AMMOSLOT = AMMOSLOT or "Ammo" -- :53
-- The font color codes/tables live in our Fonts.xml (the ref's own home for them, l.8-19 —
-- verified this session), loaded before every window.
-- ── This window's own constants (ref-PaperDollFrame.lua:1-4, Benilla-prefixed) ────────────
BENILLA_NUM_STATS = 5 -- NUM_STATS
BENILLA_NUM_RESISTANCE_TYPES = 5 -- NUM_RESISTANCE_TYPES
ATTACK_POWER_MAGIC_NUMBER = 14 -- ATTACK_POWER_MAGIC_NUMBER
-- MagicResFrame1..5's ref `id=` attrs (ref-PaperDollFrame.xml:654-759) — arcane/fire/nature/
-- frost/shadow; Holy(1) never rides the paper doll (only 5 of the 6 schools show here).
BENILLA_MAGICRES_IDS = { 6, 2, 3, 4, 5 }
-- The window's pages, named exactly as the reference names them
-- ("PaperDollFrame"/"PetPaperDollFrame"/"ReputationFrame"/"SkillFrame"/"HonorFrame"). 1:1
-- with the tab row below (index N is tab N's page) — decision 0437 phase 4 added the Skills
-- page, decision 1057 the Pet one, ReputationFrame.xml took the real client's own slot 3
-- (which put Skills at the real client's own 4), and HonorFrame.xml takes the 5.
-- THE ROSTER IS NOW THE REFERENCE'S, WHOLE: five pages, five tabs, no omission left and
-- nothing past the end. The 0138 merchant-tab precedent ("tabs arrive with their pages") is
-- what kept this list a prefix of the real one until now, rather than a row of dead tabs.
-- `character_tests.rs` pins each page's `id=` against its slot here, which is what kept the
-- two in step as the middle of the row filled in.
BENILLA_CHARACTERFRAME_SUBFRAMES = {
"PaperDollFrame", "PetPaperDollFrame", "ReputationFrame", "SkillFrame", "HonorFrame",
}
-- ── The window: toggle / tab dispatch (ref CharacterFrame.lua:1-48) ─────────────────────
-- Kept BARE (not Benilla-prefixed) — the addon-facing entry point, matching this codebase's
-- own ToggleQuestLog() precedent (QuestLogFrame.xml).
function CharacterFrame_ShowSubFrame(frameName)
for _, value in ipairs(BENILLA_CHARACTERFRAME_SUBFRAMES) do
-- Nil-guarded: a page's file may not be loaded (a test subset, a later
-- load-on-demand page) — a missing sibling must never break the switch.
local page = getglobal(value)
if page then
if value == frameName then page:Show() else page:Hide() end
end
end
end
function ToggleCharacter(tab)
local subFrame = getglobal(tab)
if not subFrame then return end
-- ref CharacterFrame.lua:4-6, the pet guard: asking for the pet page with no pet does
-- NOTHING — not even close an open window. The `not IsVisible()` half is what still
-- lets the page CLOSE itself when the pet vanishes underneath it (the pet page's own
-- OnEvent calls straight back in here to do exactly that). Hoisted below the getglobal
-- (the ref tests it above) purely so the visibility read has a frame to ask.
if tab == "PetPaperDollFrame" and not HasPetUI() and not subFrame:IsVisible() then
return
end
local frame = getglobal("CharacterFrame")
-- ref CharacterFrame.lua:11, and the line whose absence desynced this window: the tab
-- row is selected HERE, from the page's own id, on EVERY path — including the one that
-- closes the window. Having it only on the tab-button OnClick meant a KEYBIND
-- (`C`/`SHIFT-P`, which call straight in here) switched the page and left the row
-- selecting the old tab; clicking that old tab then took the "already visible" branch
-- and shut the window instead of switching back (director, 2026-08-06). The id is the
-- page's own `id=` attribute — the loader really does apply it (loader/geometry.rs
-- SetID) — and `character_tests.rs` pins every page's id against its slot in
-- BENILLA_CHARACTERFRAME_SUBFRAMES, which is what keeps the two in step the day
-- Reputation lands between them.
PanelTemplates_SetTab(frame, subFrame:GetID())
if frame:IsVisible() then
if subFrame:IsVisible() then
HideUIPanel(frame)
else
PlaySound("igCharacterInfoTab")
CharacterFrame_ShowSubFrame(tab)
end
else
ShowUIPanel(frame)
CharacterFrame_ShowSubFrame(tab)
end
end
-- The tab row's own OnClick (ref CharacterFrameTab_OnClick, l.35-48), now id-based (decision
-- 0437 phase 4 grows the row to 2) — the MerchantFrame.xml Merchant/Buyback pair's own shape
-- for a >1-tab window: `id` indexes BOTH the tab button and BENILLA_CHARACTERFRAME_SUBFRAMES
-- 1:1. It does NOT select the tab itself — the ref's own tab OnClick doesn't either, because
-- ToggleCharacter is the single place that does it (above), so the click path and the
-- keybind path cannot disagree. l.47's trailing PlaySound is the ref's OWN redundant second
-- igCharacterInfoTab — ToggleCharacter already plays it on the "switch to this tab" path, so
-- this second call only matters when the window is ALREADY open on this same tab, i.e. the
-- HideUIPanel branch — kept rather than "fixed" since it's the ref's own
-- behavior, not a bug of ours.
function BenillaCharacterFrameTab_OnClick(id)
ToggleCharacter(BENILLA_CHARACTERFRAME_SUBFRAMES[id])
PlaySound("igCharacterInfoTab")
end
function CharacterFrame_OnLoad()
local frame = this
-- GetID()/SetID() DO exist (landed with slice 2's BagFrame, which relies on them for
-- its own per-window bag id) — the STALE claim this comment used to make is corrected
-- here (0208 phase 1b touched this function anyway). All 5 tabs ship with the Honor
-- page (Character + Pet + Reputation + Skills + Honor — the reference's own five); the
-- window opens on tab 1 by default, matching the real client. Tab 2 raises and hides
-- itself with the pet — PetTab_Update, which the pet page runs at its own OnLoad and on
-- every pet transition — so the COUNT here is the full row, not the currently-visible
-- one, which is why this number reaches the ref's own SetNumTabs(5) rather than
-- tracking what is on screen.
PanelTemplates_SetNumTabs(frame, 5)
PanelTemplates_SetTab(frame, 1)
-- The reference LABELS its numerals here (CharacterFrame_OnLoad l.55-57: "Health" on
-- the player's health bar, "XP" on the XP bar, with the power bar's following the
-- resource in UnitFrames.xml). Shipped in 1146 and cut the same day on the director's
-- call — bare numbers read better on our bars (decision 1147). The mechanism is
-- untouched and live: SetTextStatusBarTextPrefix works, and restoring any one of the
-- three is the one line the reference has. If it is ever restored, note the ordering
-- it depends on — a prefix only sticks on a bar that already has its TextString, and
-- this file's seat works only because the manifest runs UnitFrames (52) and ActionBar
-- (76) well ahead of it (149), exactly as FrameXML runs PlayerFrame (44) and
-- MainMenuBar (34) ahead of CharacterFrame (53).
end
-- ref CharacterFrame_OnShow (l.82-93). The Show*TextStatusBarText calls are LIVE as of
-- decision 1146 — the note that used to sit here ("no unit-frame bar-text-mode toggle
-- exists in this engine") was true when it was written and stopped being true when the
-- numerals landed (1143). Opening the character sheet reveals your own and your pet's
-- numbers for as long as it is open, whatever Status Bar Text says, through the same
-- lockShow refcount the hover uses — so a hover inside an open sheet still balances.
-- ShowWatchedReputationBarText is LIVE too, since the Reputation tab landed and with it a
-- driven watch bar (ActionBar.xml owns both, its own placement note says why): opening the
-- sheet reveals the watched faction's numbers for as long as it is open, exactly like the
-- five bars above. Guarded on existence like its neighbours — the engine's per-window
-- harnesses load this file without ActionBar.xml.
-- UnitPVPName -> plain UnitName (this file's header comment: PVP rank is a named deferral).
function CharacterFrame_OnShow()
if UpdateMicroButtons then UpdateMicroButtons() end
PlaySound("igCharacterInfoOpen")
SetPortraitTexture(getglobal("CharacterFramePortrait"), "player")
getglobal("CharacterNameText"):SetText(UnitName("player"))
if ShowTextStatusBarText then -- engine tests load file subsets
ShowTextStatusBarText(PlayerFrameHealthBar)
ShowTextStatusBarText(PlayerFrameManaBar)
ShowTextStatusBarText(MainMenuExpBar)
ShowTextStatusBarText(PetFrameHealthBar)
ShowTextStatusBarText(PetFrameManaBar)
end
if ShowWatchedReputationBarText then
ShowWatchedReputationBarText()
end
end
-- ref CharacterFrame_OnHide (l.95-104): everything past the PlaySound is the OnShow list's
-- Hide-twin — UpdateMicroButtons live (MicroMenu.xml), the bar-text calls live too (1146),
-- and the reputation one live since the watch bar started being driven. The subframe hide-loop is OUR OWN addition
-- (decision 0437 phase 4): PaperDollFrame is a true engine child of this frame (its
-- own header comment) so it hides for free on the parent's Hide-cascade, but
-- SkillFrame is a SEPARATE top-level file — this engine has no cross-file `parent=`
-- attachment (grepped `benilla-ui/src/loader`: the XML `parent=` attribute is read nowhere,
-- unlike `relativeTo`, which resolves by name regardless of tree position — see
-- SkillFrame.xml's own header comment) — so nothing else would hide it when the WHOLE
-- character window closes (ESC, the close X, or another left-slot window bumping it via
-- UIPanel exclusivity). Looping every BENILLA_CHARACTERFRAME_SUBFRAMES entry here, once, is
-- what carried that correctness through every non-nested tab that followed — Pet,
-- Reputation and now Honor, all four of the five pages that are separate files.
function CharacterFrame_OnHide()
if UpdateMicroButtons then UpdateMicroButtons() end
PlaySound("igCharacterInfoClose")
if HideTextStatusBarText then -- engine tests load file subsets
HideTextStatusBarText(PlayerFrameHealthBar)
HideTextStatusBarText(PlayerFrameManaBar)
HideTextStatusBarText(MainMenuExpBar)
HideTextStatusBarText(PetFrameHealthBar)
HideTextStatusBarText(PetFrameManaBar)
end
if HideWatchedReputationBarText then
HideWatchedReputationBarText()
end
for _, name in ipairs(BENILLA_CHARACTERFRAME_SUBFRAMES) do
local subFrame = getglobal(name)
if subFrame then subFrame:Hide() end
end
end
-- ── The paper-doll page (ref PaperDollFrame.lua) ────────────────────────────────────────
-- ref PaperDollFrame_OnLoad (l.6-29), the label lines (l.7-13) kept as the ref has them: the
-- labels live ONLY in the row template's $parentLabel (gold GameFontNormalSmall, LEFT) and
-- Lua fills them — never a re-declared region in an instance (the ref's instances carry no
-- <Layers> at all, and this engine's template expansion appends children, so a re-declared
-- $parentLabel creates a SECOND anchorless default-font FontString instead of merging — the
-- white/overlapping-label bug this line-set replaces). CHARACTER_POINTS_CHANGED/
-- UNIT_MODEL_CHANGED/PLAYER_DAMAGE_DONE_MODS/UNIT_RANGEDDAMAGE/PLAYER_GUILD_UPDATE/
-- SKILL_LINES_CHANGED are OMITTED (talents/guild/skills are out of this phase's event
-- contract; UNIT_RANGEDDAMAGE isn't in it either — UNIT_DAMAGE/UNIT_ATTACK_SPEED alone
-- trigger the ranged refresh below, same as the ref's own first branch already does).
function PaperDollFrame_OnLoad()
local frame = this
getglobal("CharacterAttackFrameLabel"):SetText(MELEE_ATTACK)
getglobal("CharacterDamageFrameLabel"):SetText(DAMAGE_COLON)
getglobal("CharacterAttackPowerFrameLabel"):SetText(ATTACK_POWER_COLON)
getglobal("CharacterRangedAttackFrameLabel"):SetText(RANGED_ATTACK)
getglobal("CharacterRangedDamageFrameLabel"):SetText(DAMAGE_COLON)
getglobal("CharacterRangedAttackPowerFrameLabel"):SetText(ATTACK_POWER_COLON)
getglobal("CharacterArmorFrameLabel"):SetText(ARMOR_COLON)
frame:RegisterEvent("PLAYER_ENTERING_WORLD")
frame:RegisterEvent("UNIT_LEVEL")
frame:RegisterEvent("UNIT_RESISTANCES")
frame:RegisterEvent("UNIT_STATS")
frame:RegisterEvent("UNIT_DAMAGE")
frame:RegisterEvent("UNIT_ATTACK_SPEED")
frame:RegisterEvent("UNIT_ATTACK_POWER")
frame:RegisterEvent("UNIT_RANGED_ATTACK_POWER")
frame:RegisterEvent("UNIT_ATTACK")
end
-- Every row of the sheet, in the order a full repaint fills them. The order is load-bearing
-- at the ranged end: SetRangedAttack is what decides `PaperDollFrame.noRanged`, and the two
-- ranged rows after it read that flag rather than re-deciding it.
--
-- Rows are NAMED, not referenced. Resolving each through the global table at call time is
-- what the reference's own call-by-name got for free, and the paper-doll setters are prime
-- addon-hook territory: a table of captured function values would keep calling the setter an
-- addon replaced.
local paperDollRows = {
"PaperDollFrame_SetLevel",
"PaperDollFrame_SetStats",
"PaperDollFrame_SetResistances",
"PaperDollFrame_SetArmor",
"PaperDollFrame_SetDamage",
"PaperDollFrame_SetAttackPower",
"PaperDollFrame_SetAttackBothHands",
"PaperDollFrame_SetRangedAttack",
"PaperDollFrame_SetRangedDamage",
"PaperDollFrame_SetRangedAttackPower",
}
-- A weapon that changed how hard it hits and one that changed how often move the same four
-- rows, so the two events share a list rather than a copy of one.
local weaponRows = {
"PaperDollFrame_SetDamage",
"PaperDollFrame_SetRangedAttack",
"PaperDollFrame_SetRangedDamage",
"PaperDollFrame_SetAttackBothHands",
}
-- Which rows a single change moves, keyed by the event reporting it. The reference spells
-- this out as an if/elseif ladder and then keeps a second, hand-parallel copy of the row
-- order in OnShow; one table read two ways cannot drift the way two call lists do.
--
-- Only this file's registered subset appears (see PaperDollFrame_OnLoad's note on the
-- omitted events). PLAYER_ENTERING_WORLD is registered and deliberately absent: the ref's
-- one use for it is `CharacterModelFrame:SetUnit("player")`, and our CharacterModelFrame is
-- a booth-texture stand-in rather than a live model widget (this file's header) — there is
-- no SetUnit to call. It keeps its early return below rather than a row here, because the
-- reference returns on it before even the visibility check.
local rowsMovedBy = {
UNIT_LEVEL = { "PaperDollFrame_SetLevel" },
UNIT_STATS = { "PaperDollFrame_SetStats" },
UNIT_RESISTANCES = { "PaperDollFrame_SetResistances", "PaperDollFrame_SetArmor" },
UNIT_ATTACK = { "PaperDollFrame_SetAttackBothHands" },
UNIT_ATTACK_POWER = { "PaperDollFrame_SetAttackPower" },
UNIT_DAMAGE = weaponRows,
UNIT_ATTACK_SPEED = weaponRows,
UNIT_RANGED_ATTACK_POWER = {
"PaperDollFrame_SetRangedAttack", "PaperDollFrame_SetRangedAttackPower",
},
}
local function paintRows(rows)
for i = 1, table.getn(rows) do
getglobal(rows[i])()
end
end
-- `unit` is the DECLARED second parameter, not the `arg1` global: the XML passes `arg1` into
-- it exactly as the reference's own does (PaperDollFrame.xml), so an addon calling the 1.12
-- arity is read correctly rather than off a stale global. Only the player's own sheet is
-- driven from here (no pet/other-unit reuse in phase 1a), and only while it is on screen —
-- a hidden page is repainted wholesale by OnShow when it comes back.
function PaperDollFrame_OnEvent(event, unit)
if event == "PLAYER_ENTERING_WORLD" then
return
end
if not this:IsVisible() then return end
if unit ~= "player" then return end
local rows = rowsMovedBy[event]
if rows then
paintRows(rows)
end
end
-- ref PaperDollFrame_OnShow (l.568-585): SetGuild OMITTED (named deferral, this file's
-- header). The relic Hide/Show branch is DROPPED outright — UnitHasRelicSlot is
-- vanilla-always-false, so CharacterAmmoSlot simply never has a reason to hide; it ships
-- with no hidden="true" at all (see its own Button below).
function PaperDollFrame_OnShow()
paintRows(paperDollRows)
end
-- ref PaperDollFrame_OnHide (l.587-592) is entirely commented-out in the ref itself (the
-- ShoppingTooltip/compare-tooltip lines, OMIT) — nothing to transcribe, no OnHide wired.
-- UnitRace/UnitClass return nil,nil until the app's first player snapshot lands (unlike the
-- real client, whose race/class are synchronous local data, never network-raced) — "?" is a
-- harmless placeholder for that one-tick window, not a fidelity change (verified live: an
-- unguarded format() errors "string expected, got nil" the instant this fires before the
-- first snapshot).
function PaperDollFrame_SetLevel()
getglobal("CharacterLevelText"):SetText(
format(PLAYER_LEVEL, UnitLevel("player"), UnitRace("player") or "?", UnitClass("player") or "?"))
end
-- ref PaperDollFrame_SetStats (l.131-174), including its per-refresh label SetText (l.140)
-- — the labels' one source of text (see PaperDollFrame_OnLoad's header comment).
function PaperDollFrame_SetStats()
for i = 1, BENILLA_NUM_STATS do
local label = getglobal("CharacterStatFrame" .. i .. "Label")
local text = getglobal("CharacterStatFrame" .. i .. "StatText")
local frame = getglobal("CharacterStatFrame" .. i)
local statName = getglobal("SPELL_STAT" .. (i - 1) .. "_NAME")
label:SetText(statName .. ":")
local stat, effectiveStat, posBuff, negBuff = UnitStat("player", i)
local tooltipText = HIGHLIGHT_FONT_COLOR_CODE .. statName .. " "
if posBuff == 0 and negBuff == 0 then
text:SetText(effectiveStat)
frame.tooltip = tooltipText .. effectiveStat .. FONT_COLOR_CODE_CLOSE
else
tooltipText = tooltipText .. effectiveStat
if posBuff > 0 or negBuff < 0 then
tooltipText = tooltipText .. " (" .. (stat - posBuff - negBuff) .. FONT_COLOR_CODE_CLOSE
end
if posBuff > 0 then
tooltipText = tooltipText .. FONT_COLOR_CODE_CLOSE .. GREEN_FONT_COLOR_CODE .. "+" .. posBuff .. FONT_COLOR_CODE_CLOSE
end
if negBuff < 0 then
tooltipText = tooltipText .. RED_FONT_COLOR_CODE .. " " .. negBuff .. FONT_COLOR_CODE_CLOSE
end
if posBuff > 0 or negBuff < 0 then
tooltipText = tooltipText .. HIGHLIGHT_FONT_COLOR_CODE .. ")" .. FONT_COLOR_CODE_CLOSE
end
frame.tooltip = tooltipText
if negBuff < 0 then
text:SetText(RED_FONT_COLOR_CODE .. effectiveStat .. FONT_COLOR_CODE_CLOSE)
else
text:SetText(GREEN_FONT_COLOR_CODE .. effectiveStat .. FONT_COLOR_CODE_CLOSE)
end
end
end
end
-- One word for how well a school is covered, from resistance-per-level. The bands descend,
-- so the first threshold cleared names it and a ratio of zero or less clears none of them.
-- (The reference leaves the answer in an implicit global; a pure function of the ratio is
-- both the honest shape and one less name in our sandboxed globals table.)
local function resistanceBand(ratio)
if ratio > 5 then return RESISTANCE_EXCELLENT end
if ratio > 3.75 then return RESISTANCE_VERYGOOD end
if ratio > 2.5 then return RESISTANCE_GOOD end
if ratio > 1.25 then return RESISTANCE_FAIR end
if ratio > 0 then return RESISTANCE_POOR end
return RESISTANCE_NONE
end
-- ref PaperDollFrame_SetResistances (l.176-228).
function PaperDollFrame_SetResistances()
for i = 1, BENILLA_NUM_RESISTANCE_TYPES do
local resistId = BENILLA_MAGICRES_IDS[i]
local text = getglobal("MagicResText" .. i)
local frame = getglobal("MagicResFrame" .. i)
local base, resistance, positive, negative = UnitResistance("player", resistId)
local resistanceName = getglobal("RESISTANCE" .. resistId .. "_NAME")
frame.tooltip = resistanceName .. " " .. resistance
if math.abs(negative) > positive then
text:SetText(RED_FONT_COLOR_CODE .. resistance .. FONT_COLOR_CODE_CLOSE)
elseif math.abs(negative) == positive then
text:SetText(resistance)
else
text:SetText(GREEN_FONT_COLOR_CODE .. resistance .. FONT_COLOR_CODE_CLOSE)
end
if positive ~= 0 or negative ~= 0 then
frame.tooltip = frame.tooltip .. " ( " .. HIGHLIGHT_FONT_COLOR_CODE .. base
if positive > 0 then
frame.tooltip = frame.tooltip .. GREEN_FONT_COLOR_CODE .. " +" .. positive
end
if negative < 0 then
frame.tooltip = frame.tooltip .. " " .. RED_FONT_COLOR_CODE .. negative
end
frame.tooltip = frame.tooltip .. FONT_COLOR_CODE_CLOSE .. " )"
end
-- The divisor floors at 20 because the reference does (l.210-211: it reads the level,
-- then clamps on the NEXT line — an earlier note here cited l.210 alone and called
-- the floor ours). The `or 1` is the only difference: the ref would error on a nil
-- level, and nothing is gained by reproducing that.
local unitLevel = math.max(UnitLevel("player") or 1, 20)
frame.tooltipSubtext = format(RESISTANCE_TOOLTIP_SUBTEXT,
getglobal("RESISTANCE_TYPE" .. resistId), unitLevel,
resistanceBand(resistance / unitLevel))
end
end
-- ref PaperDollFrame_SetArmor (l.230-250). NOTE the ref's own third UnitArmor return
-- (`armor`) is unused here too — PaperDollFormatStat gets `base`, not `armor`; the ref's
-- behavior kept, not "fixed".
function PaperDollFrame_SetArmor(unit, prefix)
unit = unit or "player"
prefix = prefix or "Character"
local base, effectiveArmor, armor, posBuff, negBuff = UnitArmor(unit)
local frame = getglobal(prefix .. "ArmorFrame")
local text = getglobal(prefix .. "ArmorFrameStatText")
PaperDollFormatStat(ARMOR, base, posBuff, negBuff, frame, text)
local playerLevel = UnitLevel(unit)
local armorReduction = effectiveArmor / ((85 * playerLevel) + 400)
armorReduction = 100 * (armorReduction / (armorReduction + 1))
frame.tooltipSubtext = format(ARMOR_TOOLTIP, playerLevel, armorReduction)
end
-- ref PaperDollFrame_SetDefense (l.252-272). Shared like every setter around it, but the
-- CHARACTER sheet has no defense row — the real client's doesn't either, and this exists
-- solely for the pet page (decision 1057), which is why it arrives now and not in 0208.
-- `UnitDefense`'s second return is a single signed modifier, not the pos/neg pair the other
-- setters get, so the split into posBuff/negBuff happens here.
function PaperDollFrame_SetDefense(unit, prefix)
unit = unit or "player"
prefix = prefix or "Character"
local base, modifier = UnitDefense(unit)
local frame = getglobal(prefix .. "DefenseFrame")
local text = getglobal(prefix .. "DefenseFrameStatText")
-- The sign is all that decides which side of the pair the one modifier lands on, and a
-- modifier of zero lands on neither.
PaperDollFormatStat(DEFENSE_COLON, base, math.max(modifier, 0), math.min(modifier, 0),
frame, text)
end
-- ── Weapon swings: the arithmetic and the wording, once ────────────────────────────────────
-- Three rows read a swing — main hand, off hand, ranged — and it is the same calculation
-- with the same vocabulary each time, so it lives here instead of two and three times over.
-- The sheet's green and red for damage figures. Byte-identical to Fonts.xml's GREEN/RED
-- font-colour codes today, and kept separate on purpose: these are the damage row's own
-- colours, and reading them off the font palette would tie one to the other.
local DAMAGE_UP_COLOR = "|cff20ff20"
local DAMAGE_DOWN_COLOR = "|cffff2020"
-- What the weapon itself swings for. The numbers we are handed already have the flat
-- physical bonuses added and the percent multiplier applied; the row shows those, but the
-- tooltip breaks out where they came from, so the un-modified swing has to be recovered.
-- Returns that raw range, the modified average, and the gap the modifiers make between them.
local function unscaleSwing(minDamage, maxDamage, bonusPos, bonusNeg, percent)
local rawMin = (minDamage / percent) - bonusPos - bonusNeg
local rawMax = (maxDamage / percent) - bonusPos - bonusNeg
local baseDamage = (rawMin + rawMax) * 0.5
local fullDamage = (baseDamage + bonusPos + bonusNeg) * percent
return rawMin, rawMax, fullDamage, fullDamage - baseDamage
end
-- Both ends of a swing, rounded outwards and floored at 1 — a weapon swinging for a
-- fraction of a point still reads as a weapon.
local function swingBounds(minDamage, maxDamage)
return math.max(math.floor(minDamage), 1), math.max(math.ceil(maxDamage), 1)
end
-- The range as a tooltip writes it, always spaced.
local function damageRange(minDamage, maxDamage)
local low, high = swingBounds(minDamage, maxDamage)
return low .. " - " .. high
end
-- The range as the ROW writes it: spaced while both ends are two digits, tightened up once
-- either reaches three and the row starts running out of width.
local function damageFigure(minDamage, maxDamage)
local low, high = swingBounds(minDamage, maxDamage)
if low < 100 and high < 100 then
return low .. " - " .. high
end
return low .. "-" .. high
end
-- The tooltip's account of what modified the raw range: the flat bonus, then the multiplier,
-- each coloured by which way it went and left out entirely when it is not in play.
local function damageModifiers(tooltip, bonusPos, bonusNeg, percent)
if bonusPos > 0 then
tooltip = tooltip .. DAMAGE_UP_COLOR .. " +" .. bonusPos .. "|r"
end
if bonusNeg < 0 then
tooltip = tooltip .. DAMAGE_DOWN_COLOR .. " " .. bonusNeg .. "|r"
end
if percent > 1 then
tooltip = tooltip .. DAMAGE_UP_COLOR .. " x" .. math.floor(percent * 100 + 0.5) .. "%|r"
elseif percent < 1 then
tooltip = tooltip .. DAMAGE_DOWN_COLOR .. " x" .. math.floor(percent * 100 + 0.5) .. "%|r"
end
return tooltip
end
-- Which way a modified figure is tinted. Only asked once a caller knows something modified
-- it, so a zero bonus never reaches here and has no colour of its own.
local function damageColor(totalBonus)
if totalBonus > 0 then
return DAMAGE_UP_COLOR
end
return DAMAGE_DOWN_COLOR
end
-- ref PaperDollFrame_SetDamage (l.274-372). The off hand is the same swing read twice: the
-- flat bonuses and the multiplier are the character's, not the weapon's, so both hands take
-- them, and only the speed and the raw range differ.
function PaperDollFrame_SetDamage(unit, prefix)
unit = unit or "player"
prefix = prefix or "Character"
local damageText = getglobal(prefix .. "DamageFrameStatText")
local damageFrame = getglobal(prefix .. "DamageFrame")
local speed, offhandSpeed = UnitAttackSpeed(unit)
local minDamage, maxDamage, minOffHandDamage, maxOffHandDamage, physicalBonusPos, physicalBonusNeg, percent =
UnitDamage(unit)
local figure = damageFigure(minDamage, maxDamage)
local rawMin, rawMax, fullDamage, totalBonus =
unscaleSwing(minDamage, maxDamage, physicalBonusPos, physicalBonusNeg, percent)
local damageTooltip = damageRange(rawMin, rawMax)
if totalBonus == 0 then
damageText:SetText(figure)
else
damageText:SetText(damageColor(totalBonus) .. figure .. "|r")
damageTooltip =
damageModifiers(damageTooltip, physicalBonusPos, physicalBonusNeg, percent)
end
damageFrame.damage = damageTooltip
damageFrame.attackSpeed = speed
damageFrame.dps = math.max(fullDamage, 1) / speed
if offhandSpeed then
local offMin, offMax, offhandFullDamage = unscaleSwing(
minOffHandDamage, maxOffHandDamage, physicalBonusPos, physicalBonusNeg, percent)
-- The off hand gets its breakdown unconditionally: it has no row of its own to keep
-- plain, only the tooltip's second half.
damageFrame.offhandDamage = damageModifiers(damageRange(offMin, offMax),
physicalBonusPos, physicalBonusNeg, percent)
damageFrame.offhandAttackSpeed = offhandSpeed
damageFrame.offhandDps = math.max(offhandFullDamage, 1) / offhandSpeed
else
damageFrame.offhandAttackSpeed = nil
end
end
-- ref PaperDollFrame_SetAttackPower (l.374-389).
function PaperDollFrame_SetAttackPower(unit, prefix)
unit = unit or "player"
prefix = prefix or "Character"
local base, posBuff, negBuff = UnitAttackPower(unit)
local frame = getglobal(prefix .. "AttackPowerFrame")
local text = getglobal(prefix .. "AttackPowerFrameStatText")
PaperDollFormatStat(MELEE_ATTACK_POWER, base, posBuff, negBuff, frame, text)
frame.tooltipSubtext = format(MELEE_ATTACK_POWER_TOOLTIP,
math.max(base + posBuff + negBuff, 0) / ATTACK_POWER_MAGIC_NUMBER)
end
-- An attack rating and whatever is moving it, on one row. Nothing moving it means the bare
-- number and no hover plate to explain; otherwise the row carries the modified TOTAL, tinted
-- by which way it went, and the plate carries the split that produced it. Both the melee row
-- and the ranged row read exactly this way.
local function setAttackRating(frame, text, base, modifier)
if modifier == 0 then
text:SetText(base)
frame.tooltip = nil
return
end
local color = RED_FONT_COLOR_CODE
local sign = " "
if modifier > 0 then
color = GREEN_FONT_COLOR_CODE
sign = " +"
end
frame.tooltip = base .. color .. sign .. modifier .. FONT_COLOR_CODE_CLOSE
text:SetText(color .. (base + modifier) .. FONT_COLOR_CODE_CLOSE)
end
-- ref PaperDollFrame_SetAttackBothHands (l.391-417) — the weapon-skill line (decision 0208's
-- own "to confirm": the equipped-weapon subclass -> skill-id table is UnitAttackBothHands's
-- own concern, not this file's).
function PaperDollFrame_SetAttackBothHands(unit, prefix)
unit = unit or "player"
prefix = prefix or "Character"
local mainHandAttackBase, mainHandAttackMod = UnitAttackBothHands(unit)
setAttackRating(getglobal(prefix .. "AttackFrame"),
getglobal(prefix .. "AttackFrameStatText"), mainHandAttackBase, mainHandAttackMod)
end
-- ref PaperDollFrame_SetRangedAttack (l.419-466). `hasRelic`/UnitHasRelicSlot DROPPED
-- outright (this file's header comment: vanilla-always-false, dead code in the ref itself).
-- The literal slot id 18 is the ref's OWN hardcoded value (l.435) — quoted verbatim; what
-- GetInventoryItemTexture resolves id 18 to is the parallel binding's call, not re-derived
-- here.
function PaperDollFrame_SetRangedAttack(unit, prefix)
if unit == "pet" then return end
unit = unit or "player"
prefix = prefix or "Character"
local frame = getglobal(prefix .. "RangedAttackFrame")
local text = getglobal(prefix .. "RangedAttackFrameStatText")
local paperDoll = getglobal("PaperDollFrame")
local rangedAttackBase, rangedAttackMod = UnitRangedAttack(unit)
local rangedTexture = GetInventoryItemTexture("player", 18)
local oldValue = paperDoll.noRanged
if rangedTexture then
paperDoll.noRanged = nil
else
text:SetText(NOT_APPLICABLE)
paperDoll.noRanged = true
frame.tooltip = nil
end
if oldValue ~= paperDoll.noRanged then
PaperDollFrame_SetRangedAttackPower()
PaperDollFrame_SetRangedDamage()
end
if not rangedTexture then return end
setAttackRating(frame, text, rangedAttackBase, rangedAttackMod)
end
-- ref PaperDollFrame_SetRangedDamage (l.468-537).
function PaperDollFrame_SetRangedDamage(unit, prefix)
if unit == "pet" then return end
unit = unit or "player"
prefix = prefix or "Character"
local damageText = getglobal(prefix .. "RangedDamageFrameStatText")
local damageFrame = getglobal(prefix .. "RangedDamageFrame")
local paperDoll = getglobal("PaperDollFrame")
if paperDoll.noRanged then
damageText:SetText(NOT_APPLICABLE)
damageFrame.damage = nil
return
end
local rangedAttackSpeed, minDamage, maxDamage, physicalBonusPos, physicalBonusNeg, percent =
UnitRangedDamage(unit)
local figure = damageFigure(minDamage, maxDamage)
local rawMin, rawMax, fullDamage, totalBonus =
unscaleSwing(minDamage, maxDamage, physicalBonusPos, physicalBonusNeg, percent)
local damagePerSecond = math.max(fullDamage, 1) / rangedAttackSpeed
local tooltip = damageRange(rawMin, rawMax)
if totalBonus == 0 then
damageText:SetText(figure)
else
damageText:SetText(damageColor(totalBonus) .. figure .. "|r")
tooltip = damageModifiers(tooltip, physicalBonusPos, physicalBonusNeg, percent)
-- Only the modified case gets a hover plate here: an unmodified bow has nothing to
-- explain beyond the figure already on the row.
damageFrame.tooltip = tooltip .. " " .. format(DPS_TEMPLATE, damagePerSecond)
end
damageFrame.attackSpeed = rangedAttackSpeed
damageFrame.damage = tooltip
damageFrame.dps = damagePerSecond
end
-- ref PaperDollFrame_SetRangedAttackPower (l.539-566).
function PaperDollFrame_SetRangedAttackPower(unit, prefix)
if unit == "pet" then return end
unit = unit or "player"
prefix = prefix or "Character"
local frame = getglobal(prefix .. "RangedAttackPowerFrame")
local text = getglobal(prefix .. "RangedAttackPowerFrameStatText")
local paperDoll = getglobal("PaperDollFrame")
if paperDoll.noRanged then
text:SetText(NOT_APPLICABLE)
frame.tooltip = nil
return
end
if HasWandEquipped() then
text:SetText("--")
frame.tooltip = nil
return
end
local base, posBuff, negBuff = UnitRangedAttackPower(unit)
PaperDollFormatStat(RANGED_ATTACK_POWER, base, posBuff, negBuff, frame, text)
frame.tooltipSubtext = format(RANGED_ATTACK_POWER_TOOLTIP,
math.max(base + posBuff + negBuff, 0) / ATTACK_POWER_MAGIC_NUMBER)
end
-- ref PaperDollFormatStat (l.804-833).
function PaperDollFormatStat(name, base, posBuff, negBuff, frame, textString)
local effective = math.max(0, base + posBuff + negBuff)
local text = HIGHLIGHT_FONT_COLOR_CODE .. name .. " " .. effective
if posBuff == 0 and negBuff == 0 then
text = text .. FONT_COLOR_CODE_CLOSE
textString:SetText(effective)
else
if posBuff > 0 or negBuff < 0 then
text = text .. " (" .. base .. FONT_COLOR_CODE_CLOSE
end
if posBuff > 0 then
text = text .. FONT_COLOR_CODE_CLOSE .. GREEN_FONT_COLOR_CODE .. "+" .. posBuff .. FONT_COLOR_CODE_CLOSE
end
if negBuff < 0 then
text = text .. RED_FONT_COLOR_CODE .. " " .. negBuff .. FONT_COLOR_CODE_CLOSE
end
if posBuff > 0 or negBuff < 0 then
text = text .. HIGHLIGHT_FONT_COLOR_CODE .. ")" .. FONT_COLOR_CODE_CLOSE
end
if negBuff < 0 then
textString:SetText(RED_FONT_COLOR_CODE .. effective .. FONT_COLOR_CODE_CLOSE)
else
textString:SetText(GREEN_FONT_COLOR_CODE .. effective .. FONT_COLOR_CODE_CLOSE)
end
end
frame.tooltip = text
end
-- ref PaperDollStatTooltip(unit, stat) (l.786-802), signature-exact: the button is the
-- ambient `this` the reference reads, NOT a leading parameter, and `unit` leads `stat`
-- exactly as the ref declares them (its pet rows pass "pet" — ref PetPaperDollFrame.xml:345;
-- ours likewise, decision 1057). A leading frame parameter here was a wrong SHAPE behind a
-- right name — 1199's failure mode — because an addon calls the 1.12 arity.
function PaperDollStatTooltip(unit, stat)
local button = this
-- classFileName nil until the first player snapshot lands (this file's SetLevel comment
-- on the same engine timing gap) — "" just misses every class lookup, falling through to
-- DEFAULT_*_TOOLTIP below exactly like an unrecognized class would. A pet has no class
-- either, so the pet rows land on the same DEFAULT_* fallback the ref gives them.
local _, classFileName = UnitClass(unit or "player")
classFileName = classFileName or ""
local classStatText = getglobal(strupper(classFileName) .. "_" .. stat .. "_TOOLTIP")
if not classStatText then
classStatText = getglobal("DEFAULT_" .. stat .. "_TOOLTIP")
end
GameTooltip:SetOwner(button, "ANCHOR_RIGHT")
GameTooltip:SetText(button.tooltip)
GameTooltip:AddLine(classStatText, NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, 1)
GameTooltip:Show()
end
-- ref CharacterDamageFrame_OnEnter (l.835-851).
function CharacterDamageFrame_OnEnter()
local button = this
GameTooltip:SetOwner(button, "ANCHOR_RIGHT")
GameTooltip:SetText(INVTYPE_WEAPONMAINHAND, HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b)
GameTooltip:AddDoubleLine(ATTACK_SPEED_COLON, string.format("%.2f", button.attackSpeed),
NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b)
GameTooltip:AddDoubleLine(DAMAGE_COLON, button.damage,
NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b)
GameTooltip:AddDoubleLine(DAMAGE_PER_SECOND, string.format("%.1f", button.dps),
NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b)
if button.offhandAttackSpeed then
GameTooltip:AddLine(" ")
GameTooltip:AddLine(INVTYPE_WEAPONOFFHAND, HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b)
GameTooltip:AddDoubleLine(ATTACK_SPEED_COLON, string.format("%.2f", button.offhandAttackSpeed),
NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b)
GameTooltip:AddDoubleLine(DAMAGE_COLON, button.offhandDamage,
NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b)
GameTooltip:AddDoubleLine(DAMAGE_PER_SECOND, string.format("%.1f", button.offhandDps),
NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b)
end
GameTooltip:Show()
end
-- ref CharacterRangedDamageFrame_OnEnter (l.853-863).
function CharacterRangedDamageFrame_OnEnter()
local button = this
if not button.damage then return end
GameTooltip:SetOwner(button, "ANCHOR_RIGHT")
GameTooltip:SetText(INVTYPE_RANGED, HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b)
GameTooltip:AddDoubleLine(ATTACK_SPEED_COLON, string.format("%.2f", button.attackSpeed),
NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b)
GameTooltip:AddDoubleLine(DAMAGE_COLON, button.damage,
NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b)
GameTooltip:AddDoubleLine(DAMAGE_PER_SECOND, string.format("%.1f", button.dps),
NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b)
GameTooltip:Show()
end
-- ── The 19 equipment slots + ammo (ref PaperDollItemSlotButton_*, l.78-98/594-784) ──────
-- Cooldowns, the broken-red tint, and SHOW_COMPARE_TOOLTIP are still OMITTED this phase —
-- this file's header comment. What ships now (1b): the icon/count fill, the empty-slot
-- art, the hover tooltip, click-to-equip/-use (l.647-662, ctrl/shift named deferrals),
-- the drag trio, lock dimming, and the CURSOR_UPDATE ring highlight (emulated — header
-- comment).
function BenillaPaperDollSlot_OnLoad(button, slotName)
button:RegisterEvent("UNIT_INVENTORY_CHANGED")
local id, texture, checkRelic = GetInventorySlotInfo(slotName)
button.invSlotId = id
button.slotName = slotName
button.emptyTexture = texture
button.checkRelic = checkRelic -- UnitHasRelicSlot is vanilla-always-false (header OMIT): never read
button.icon = getglobal(button:GetName() .. "IconTexture")
button.count = getglobal(button:GetName() .. "Count")
-- Equip interactions on the 19 named slots use the equip-swap wire (PickupInventoryItem
-- pick/place/swap). The ammo slot (id 0) is different: it LOADS ammo by entry via
-- CMSG_SET_AMMO (decision 0526; wow-re cursor-dragdrop-slots.md — the client's auto-equip
-- sender forks ammo-class → SET_AMMO), so it only wants click-to-place + drag-drop, both
-- routed to BenillaCharacterAmmoSlot_OnClick (its own Scripts block below). It carries no
-- ring, so the CURSOR_UPDATE highlight / ITEM_LOCK dim legs would no-op anyway, and there
-- is no drag-OUT (you can't pick the loaded ammo off the slot).
if slotName ~= "AmmoSlot" then
button:RegisterEvent("ITEM_LOCK_CHANGED")
button:RegisterEvent("CURSOR_UPDATE")
-- The shift-compare listener (ref PaperDollFrame.lua l.83): the ENGINE fires
-- SHOW_COMPARE_TOOLTIP(slot, n) for an equippable item hover while shift is held;
-- only the matching visible doll slot answers (the OnEvent branch below).
button:RegisterEvent("SHOW_COMPARE_TOOLTIP")
button:RegisterForClicks("LeftButtonUp", "RightButtonUp")
-- The drag trio (decision 0216 §3): click-carry and drag are ONE code path in the
-- reference (PaperDollFrame.xml:20-25 routes OnDragStart/OnReceiveDrag to the same
-- OnClick("LeftButton", ignoreModifiers=1) the plain click uses) — see
-- BenillaPaperDollSlot_OnClick below for the identical routing, the bag slots' own
-- precedent (BagFrame.xml's BenillaBagSlot_OnClick).
button:RegisterForDrag("LeftButton")
else
button:RegisterForClicks("LeftButtonUp")
end
BenillaPaperDollSlot_Update(button)
end
-- Left: the reference's own modifier fork (ref l.647-660) — CTRL previews the worn
-- item in the dressing room (decision 1060), SHIFT posts its link into an open chat edit
-- box (decision 1059), and neither falls through to a pickup a real modified click would
-- never trigger. Plain click: PickupInventoryItem (pick up / place / swap / cancel,
-- ref l.656). Right: UseInventoryItem (ref l.659). `ignoreModifiers` is the drag-route
-- calls' own suppression, the bag slots' precedent.
function BenillaPaperDollSlot_OnClick(button, mouseButton, ignoreModifiers)
if mouseButton == "LeftButton" then
if IsControlKeyDown() and not ignoreModifiers then
DressUpItemLink(GetInventoryItemLink("player", button.invSlotId))
return
elseif IsShiftKeyDown() and not ignoreModifiers then
-- Both of the ref's guards (l.652: only when the edit box is up) plus our own
-- nil-link one live in the shared helper (UIParent.xml). A shift-click with chat
-- closed does nothing here — a worn item has no stack to split, unlike a bag slot.
BenillaChatEdit_InsertLink(GetInventoryItemLink("player", button.invSlotId))
return
end
PickupInventoryItem(button.invSlotId)
GameTooltip:Hide() -- the item just left the slot for the cursor
BenillaPaperDollSlot_Update(button)
elseif mouseButton == "RightButton" then
UseInventoryItem(button.invSlotId)
end
end
-- The ammo slot's own click/drop (decision 0526): ammo LOADS by entry (CMSG_SET_AMMO), it
-- doesn't swap into an equip slot, so it can't share BenillaPaperDollSlot_OnClick's
-- PickupInventoryItem path. Only an ammo payload fits (CursorCanGoInSlot(0) — ammo's
-- find_equip_slot is [0]); the drop funnels through AutoEquipCursorItem, whose app-side drain
-- resolves the held item's entry and forks to SET_AMMO — the reference's own shared auto-equip
-- sender (wow-re cursor-dragdrop-slots.md). An empty cursor or a non-ammo payload is inert
-- (nothing fits), and there is no unload-via-slot in 1.12.
function BenillaCharacterAmmoSlot_OnClick(button)
if CursorCanGoInSlot(button.invSlotId) then
AutoEquipCursorItem()
GameTooltip:Hide()
BenillaPaperDollSlot_Update(button)
end
end
function BenillaPaperDollSlot_Update(button)
local textureName = GetInventoryItemTexture("player", button.invSlotId)
if textureName then
button.icon:SetTexture(textureName)
local count = GetInventoryItemCount("player", button.invSlotId)
if (count or 0) > 1 then
button.count:SetText(count)
else
button.count:SetText("")
end
else
button.icon:SetTexture(button.emptyTexture)
button.count:SetText("")
end
-- A BROKEN item (durability 0) tints icon + slot ring red — ref
-- PaperDollItemSlotButton_Update (l.670-676: SetItemButtonTextureVertexColor +
-- SetItemButtonNormalTextureVertexColor, both 0.9, 0, 0). The bags carry no broken
-- tint in 1.12 (ContainerFrame.lua has none) — equipped only. Kept as a flag for
-- the cursor highlight's restore (Unlock below shares the ring).
button.broken = textureName and GetInventoryItemBroken("player", button.invSlotId)
-- A locked slot (its item is up on the cursor, or a send is still pending
-- server-side, decision 0216 §4/0218 §3) dims — the bag slots' own vertex-dim
-- convention (BagFrame.xml's BenillaBagSlot_Update), applied here instead of the
-- ref's separate SetItemButtonDesaturated call (no engine equivalent — header
-- comment). The transient drag-dim outranks the broken red on the icon.
if IsInventoryItemLocked(button.invSlotId) then
button.icon:SetVertexColor(0.4, 0.4, 0.4)
elseif button.broken then
button.icon:SetVertexColor(0.9, 0, 0)
else
button.icon:SetVertexColor(1, 1, 1)
end
BenillaPaperDollSlot_UnlockHighlight(button)
if GameTooltip:IsOwned(button) then
BenillaPaperDollSlot_OnEnter(button)
end
end
-- ref PaperDollItemSlotButton_OnEnter (l.739-748): the equipped hover is the INSTANCE
-- render — SetInventoryItem, never an id/template render. That carries the live
-- durability pair (an earlier SetItemById here read the template's full max/max), and
-- an instance hover never arms a shift-compare — the engine's SetInventoryItem doesn't
-- arm (a worn item compared with itself is no comparison; director-caught: shift over
-- the doll popped a "Currently Equipped" 0/XX beside a template-full main tooltip).
-- The ref's repair-mode branch (l.757-762) is INTERIM-omitted with the binding's
-- repairCost. checkRelic (l.744-746) is TBC-flagged dead weight in 1.12 — omitted.
function BenillaPaperDollSlot_OnEnter(button)
GameTooltip:SetOwner(button, "ANCHOR_RIGHT")
local hasItem = GameTooltip:SetInventoryItem("player", button.invSlotId)
if not hasItem then
-- Empty slot: the slot-name tooltip (ref strupper(strsub(name,10)) -> the matching
-- GlobalStrings key, e.g. "HeadSlot" -> HEADSLOT -> "Head").
GameTooltip:SetText(getglobal(strupper(button.slotName)) or button.slotName)
end
GameTooltip:Show()
end
-- CURSOR_UPDATE's LockHighlight/UnlockHighlight emulation (this file's header comment: no
-- true forced-highlight affordance exists — no benilla-ui Button method shows a state
-- texture off real hover). A vertex tint on the slot's own Quickslot2 ring
-- (`NormalTexture`, the template below) stands in: green while `CursorCanGoInSlot` says
-- the held payload fits here, white otherwise.
-- Both legs no-op on a ringless slot (the ammo slot carries no NormalTexture).
function BenillaPaperDollSlot_LockHighlight(button)
local ring = button:GetNormalTexture()
if ring then
ring:SetVertexColor(0, 1, 0)
end
end
-- The restore leg: back to the ring's resting color — broken red (ref l.672) or white.
function BenillaPaperDollSlot_UnlockHighlight(button)
local ring = button:GetNormalTexture()
if not ring then
return
end
if button.broken then
ring:SetVertexColor(0.9, 0, 0)
else
ring:SetVertexColor(1, 1, 1)
end
end
function BenillaPaperDollSlot_OnEvent(button)
if event == "UNIT_INVENTORY_CHANGED" and arg1 == "player" then
BenillaPaperDollSlot_Update(button)
elseif event == "ITEM_LOCK_CHANGED" then
-- Ref PaperDollItemSlotButton_OnEvent (l.601-604): no arg filtering — every doll
-- slot just repaints on any lock transition (cheap, idempotent; it's this SLOT's
-- own dim that's actually changing, if anything is).
BenillaPaperDollSlot_Update(button)
elseif event == "CURSOR_UPDATE" then
if CursorCanGoInSlot(button.invSlotId) then
BenillaPaperDollSlot_LockHighlight(button)
else
BenillaPaperDollSlot_UnlockHighlight(button)
end
elseif event == "SHOW_COMPARE_TOOLTIP" then
-- ref PaperDollItemSlotButton_OnEvent (l.621-640, quoted; button.invSlotId for the
-- ref's this:GetID()): arg1 = the doll slot the hovered item would equip into,
-- arg2 = which shopping tooltip. Both anchor to THIS doll slot (1 to its right,
-- 2 hanging below-right) — so compares appear only with the character window
-- open, the 1.12 behavior.
if arg1 ~= button.invSlotId or not button:IsVisible() or arg2 > NUM_SHOPPING_TOOLTIPS then
return
end
local tooltip = getglobal("ShoppingTooltip" .. arg2)
local anchor = "ANCHOR_RIGHT"
if arg2 > 1 then
anchor = "ANCHOR_BOTTOMRIGHT"
end
tooltip:SetOwner(button, anchor)
local hasItem = tooltip:SetInventoryItem("player", button.invSlotId)
if not hasItem then
tooltip:Hide()
end
end
end
-- ref PaperDollItemSlotButton_OnUpdate (l.766-784), the un-throttled BagFrame-style
-- re-enter loop (BagFrame.xml's own comment on why this ships unthrottled).
function BenillaPaperDollSlot_OnUpdate(button)
if GameTooltip:IsOwned(button) then
BenillaPaperDollSlot_OnEnter(button)
end
end
-- ── The model pane (ref UIParent.lua:1420-1462) ─────────────────────────────────────────
-- This file's header comment: a booth-texture stand-in, not a live widget. `frame` here is
-- CharacterModelFrame; `frame.facing` stands in for the ref's `model.rotation`.
-- These four are the ref's SHARED Model_* helpers (UIParent.lua), and they are shared here
-- too: the pet page's pane (decision 1057) drives the very same code. The one thing that
-- differs per pane is WHERE the yaw goes — the engine has no `Model:SetFacing` widget
-- method, so each pane has its own booth-yaw binding — so the pane names its own in
-- `frame.setFacing` at OnLoad and these read it. (The inspect window still carries its own
-- copy of this quartet, predating the generalization; folding it in is a follow-up, not
-- this slice's to churn.)
function BenillaPaperDollModel_OnLoad(frame)
frame.facing = 0.61 -- ref UIParent.lua:1422, the literal default facing
frame.setFacing(frame.facing)
end
-- ref Model_RotateLeft/Right (UIParent.lua:1426-1442) — the ref's OWN left/right sign
-- convention is preserved verbatim (rotate-left SUBTRACTS, rotate-right ADDS); not
-- re-derived, just quoted.
function BenillaPaperDollModel_RotateLeft(frame)
frame.facing = frame.facing - 0.03
frame.setFacing(frame.facing)
PlaySound("igInventoryRotateCharacter")
end
function BenillaPaperDollModel_RotateRight(frame)
frame.facing = frame.facing + 0.03
frame.setFacing(frame.facing)
PlaySound("igInventoryRotateCharacter")
end
-- ref Model_OnUpdate (UIParent.lua:1444-1462) — the HELD-button continuous spin, which is
-- where a rotate button's actual feel lives: half a turn per second (ROTATIONS_PER_SECOND,
-- UIParent.lua:2), against which the 0.03 click nudge above is a ~1.7° tap. Every 1.12 model
-- pane wires it (PaperDollFrame l.222-224, DressUpFrame.xml:202-204, TabardFrame.xml:406-408).
-- It used to be omitted here for want of `GetButtonState` — that method now answers "PUSHED"
-- for a LIVE mouse-held button (decision 0288 P3), so the ref's own loop ships here.
-- That INCLUDES the ref's sign quirk: the held branches run OPPOSITE to the click helpers
-- (held-LEFT adds, held-RIGHT subtracts). Not a slip of ours — the ref's own, not re-derived.
function BenillaPaperDollModel_OnUpdate(frame, elapsed)
local step = elapsed * 2 * PI * ROTATIONS_PER_SECOND
if getglobal(frame:GetName() .. "RotateLeftButton"):GetButtonState() == "PUSHED" then
frame.facing = frame.facing + step
if frame.facing < 0 then
frame.facing = frame.facing + (2 * PI)
end
frame.setFacing(frame.facing)
end
if getglobal(frame:GetName() .. "RotateRightButton"):GetButtonState() == "PUSHED" then
frame.facing = frame.facing - step
if frame.facing > (2 * PI) then
frame.facing = frame.facing - (2 * PI)
end
frame.setFacing(frame.facing)
end
end
-- ref CharacterModelFrame_OnMouseUp (l.31-35): a plain left-click-up on the model pane
-- also auto-equips a held container item — the model pane doubles as a drop target even
-- without a drag, not just via OnReceiveDrag. AutoEquipCursorItem is already a no-op for
-- every non-fitting/non-container payload (cursor.rs's own contract), so no CursorHasItem
-- guard is needed here.
function BenillaPaperDollModel_OnMouseUp(frame, button)
if button == "LeftButton" then
AutoEquipCursorItem()
end
end
]]></Script>
<!--
The shared equipment-slot button (ref PaperDollItemSlotButtonTemplate inherits
ItemButtonTemplate, ref-PaperDollFrame.xml:4-38 — ItemButtonTemplate's own XML isn't part of
this session's extraction, so its shape is taken from this codebase's own prior
reconstruction of it, BagFrame.xml's BenillaBagSlotTemplate: 37x37, a BACKGROUND icon fill,
an OVERLAY count, the 64x64 UI-Quickslot2 ring centered (0,-1), UI-Quickslot-Depress pushed,
ButtonHilight-Square ADD highlight — duplicated here rather than cross-file inherited so this
window's slot behavior (UNIT_INVENTORY_CHANGED refresh, tooltip) stays self-contained in one
file, the same posture Merchant's own item-row template takes vs. reusing Bag's).
Cooldown ($parentCooldown, CooldownFrameTemplate) OMITTED — no cooldown feed/engine
affordance exists yet. OnClick + the drag trio land THIS phase (1b) — the NormalTexture
ring below doubles as the CURSOR_UPDATE highlight's vertex-tint target (this file's header
comment: an emulation, no true LockHighlight/UnlockHighlight exists).
-->
<Button name="BenillaPaperDollItemSlotTemplate" virtual="true">
<Size><AbsDimension x="37" y="37"/></Size>
<Layers>
<Layer level="BACKGROUND">
<Texture name="$parentIconTexture"/>
</Layer>
<Layer level="OVERLAY">
<FontString name="$parentCount" inherits="NumberFontNormal" justifyH="RIGHT">
<Anchors>
<Anchor point="BOTTOMRIGHT" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="-5" y="2"/></Offset></Anchor>
</Anchors>
</FontString>
</Layer>
</Layers>
<NormalTexture file="Interface\Buttons\UI-Quickslot2">
<Size><AbsDimension x="64" y="64"/></Size>
<Anchors>
<Anchor point="CENTER" relativePoint="CENTER"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor>
</Anchors>
</NormalTexture>
<PushedTexture file="Interface\Buttons\UI-Quickslot-Depress"/>
<HighlightTexture file="Interface\Buttons\ButtonHilight-Square" alphaMode="ADD"/>
<Scripts>
<OnClick>BenillaPaperDollSlot_OnClick(self, arg1)</OnClick>
<OnEnter>BenillaPaperDollSlot_OnEnter(self)</OnEnter>
<OnLeave>GameTooltip:Hide()</OnLeave>
<OnEvent>BenillaPaperDollSlot_OnEvent(self)</OnEvent>
<OnUpdate>BenillaPaperDollSlot_OnUpdate(self)</OnUpdate>
<!-- The reference routes both drag entries to the plain click path with modifiers
suppressed (PaperDollFrame.xml:20-25 — PickupInventoryItem, ignoring
shift/ctrl) — the bag slots' own precedent (BagFrame.xml). -->
<OnDragStart>BenillaPaperDollSlot_OnClick(self, "LeftButton", true)</OnDragStart>
<OnReceiveDrag>BenillaPaperDollSlot_OnClick(self, "LeftButton", true)</OnReceiveDrag>
</Scripts>
</Button>
<!--
The ref defines TWO byte-identical templates for the attribute/stat rows — StatFrameTemplate
(ref-PaperDollFrame.xml:39-71) and AttributeFrameTemplate (l.72-105): same 104x13 size, same
Label(GameFontNormalSmall,LEFT) + $parentStat(30x13,RIGHT)/$parentText(GameFontHighlightSmall,
RIGHT) shape — a semantic-only distinction in the real client (armor/attack rows "are stats",
the 5 primaries "are attributes"). Collapsed to ONE template here (a legitimate reuse — no
behavior differs). `self:EnableMouse(true)` in OnLoad: a plain Frame isn't mouse-enabled by
construction in this engine (this file's header comment) — every instance below needs it to
receive its own OnEnter/OnLeave hover.
-->
<Frame name="BenillaStatRowTemplate" virtual="true">
<Size><AbsDimension x="104" y="13"/></Size>
<Layers>
<Layer level="BACKGROUND">
<FontString name="$parentLabel" inherits="GameFontNormalSmall">
<Anchors><Anchor point="LEFT"/></Anchors>
</FontString>
</Layer>
</Layers>
<Frames>
<Frame name="$parentStat">
<Size><AbsDimension x="30" y="13"/></Size>
<Anchors><Anchor point="RIGHT"/></Anchors>
<Layers>
<Layer level="BACKGROUND">
<FontString name="$parentText" inherits="GameFontHighlightSmall" justifyH="RIGHT">
<Anchors><Anchor point="RIGHT"/></Anchors>
</FontString>
</Layer>
</Layers>
</Frame>
</Frames>
</Frame>
<!--
The magic-resistance icon frame (ref MagicResistanceFrameTemplate, ref-PaperDollFrame.xml:
106-123): 32x29, a generic tooltip OnEnter reading self.tooltip/self.tooltipSubtext (both
filled by PaperDollFrame_SetResistances) — no per-instance OnEnter override needed
(the ref's own MagicResFrame1-5 instances carry none either).
-->
<Frame name="BenillaMagicResistanceFrameTemplate" virtual="true">
<Size><AbsDimension x="32" y="29"/></Size>
<Scripts>
<OnLoad>self:EnableMouse(true)</OnLoad>
<OnEnter>
if self.tooltip then
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip:SetText(self.tooltip)
GameTooltip:AddLine(self.tooltipSubtext, NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, 1)
GameTooltip:Show()
end
</OnEnter>
<OnLeave>GameTooltip:Hide()</OnLeave>
</Scripts>
</Frame>
<Frame name="CharacterFrame" hidden="true">
<!-- The real 384x512 window (ref-CharacterFrame.xml:5-7). No <Anchors> — ShowUIPanel/
SetLeftFrame SetPoints this to the left slot on open (UiPanels.xml's own convention). -->
<Size><AbsDimension x="384" y="512"/></Size>
<!-- HitRectInsets: transcribed for fidelity; no loader wiring exists yet (this file's header
comment) — a harmless no-op today. -->
<HitRectInsets><AbsInset left="0" right="30" top="0" bottom="45"/></HitRectInsets>
<Layers>
<!-- Portrait 60x60 TOPLEFT (7,-6) (ref l.19-31). Set in OnShow via SetPortraitTexture
(the live model-bake binding, UnitFrames.xml's own precedent) — "player" is a fixed
token here (unlike a unit frame, this window only ever shows the local player). -->
<Layer level="ARTWORK">
<Texture name="CharacterFramePortrait">
<Size><AbsDimension x="60" y="60"/></Size>
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="7" y="-6"/></Offset></Anchor></Anchors>
</Texture>
</Layer>
</Layers>
<Frames>
<!-- Name: CENTER (6,232) wrapper, GameFontNormal 300x12 centered inside it, white (ref
l.35-64). UnitPVPName -> plain UnitName (this file's header comment: PVP rank is a
named deferral) — CharacterTitleText/CharacterGuildText (both PVP-rank/guild-driven)
are dropped with it. -->
<Frame name="CharacterNameFrame">
<Size><AbsDimension x="109" y="12"/></Size>
<Anchors><Anchor point="CENTER"><Offset><AbsDimension x="6" y="232"/></Offset></Anchor></Anchors>
<Layers>
<Layer>
<FontString name="CharacterNameText" inherits="GameFontNormal" text="NAME">
<Size><AbsDimension x="300" y="12"/></Size>
<Anchors><Anchor point="CENTER"/></Anchors>
<Color r="1.0" g="1.0" b="1.0"/>
</FontString>
</Layer>
</Layers>
<Scripts>
<OnLoad>self:SetFrameLevel(self:GetFrameLevel() + 1)</OnLoad>
</Scripts>
</Frame>
<!-- Close: the ref's own UIPanelCloseButton position (CENTER of TOPRIGHT, -44,-25 — ref
l.65-78), on the minimize-button art this codebase's close buttons all use (no
UIPanelCloseButton template ships — Gossip/Bag/Merchant's own header comments).
OnLoad raises the frame level +1 (the same idiom CharacterNameFrame above uses,
the ref's own): the window's own art is PaperDollFrame's BACKGROUND
layer — a full-window sibling created AFTER this button — so at the shared default
level 0 the page's art paints over the button by creation order (order.rs). The ref's
close button rides the client's show-time bucket-tail re-insertion instead; benilla
proxies that with creation order, so the explicit raise is the faithful compensation. -->
<Button name="CharacterFrameCloseButton">
<Size><AbsDimension x="32" y="32"/></Size>
<Anchors><Anchor point="CENTER" relativePoint="TOPRIGHT"><Offset><AbsDimension x="-44" y="-25"/></Offset></Anchor></Anchors>
<NormalTexture file="Interface\Buttons\UI-Panel-MinimizeButton-Up"/>
<PushedTexture file="Interface\Buttons\UI-Panel-MinimizeButton-Down"/>
<HighlightTexture file="Interface\Buttons\UI-Panel-MinimizeButton-Highlight" alphaMode="ADD"/>
<Scripts>
<OnLoad>self:SetFrameLevel(self:GetFrameLevel() + 1)</OnLoad>
<OnClick>HideUIPanel(getglobal("CharacterFrame"))</OnClick>
</Scripts>
</Button>
<!-- The tab row (ref l.79-168 — CENTER of BOTTOMLEFT +60,+62 — each tab chained off the
last, MerchantFrame.xml's own Merchant/Buyback LEFT-of-RIGHT -16 offset). ALL FIVE
of the real client's tabs, in ITS OWN ORDER and at ITS OWN NUMBERS:
Character/Pet/Reputation/Skills/Honor. Both deviations once flagged here are gone —
our Skills tab rendering 3rd where the reference's is 4th went with
ReputationFrame.xml, and the missing fifth went with HonorFrame.xml — so this row
carries no divergence from the reference at all.
Tab 2 starts HIDDEN — it is raised only for a unit with a pet UI, by the pet page's
PetTab_Update, which also re-anchors tab 3 onto tab 2's LEFT while it is
down so the row has no gap (ref PetPaperDollFrame.lua:189-198). That is now the
reference's own function on the reference's own tab, Reputation and all. Tab 3's
XML anchor is the WITH-pet one; the update owns it from load onward.
No hover tooltip (MicroButtonTooltipText/TOGGLECHARACTER0 have no engine surface,
and each tab's own visible label already says what it does — Merchant's own tab
pair carries no hover tooltip either). -->
<!-- The five labels are the ref's own GlobalStrings KEYS (l.79/97/114/…), not literals —
the loader resolves a key-shaped `text=` through the shipped strings, so the row is
localized like the reference's. (Tabs 1/4 carried the enUS literals from 0437;
same words either way in enUS, and now the same mechanism.) -->
<Button name="CharacterFrameTab1" inherits="CharacterFrameTabButtonTemplate" text="CHARACTER">
<Anchors><Anchor point="CENTER" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="60" y="62"/></Offset></Anchor></Anchors>
<Scripts><OnClick>BenillaCharacterFrameTab_OnClick(1)</OnClick></Scripts>
</Button>
<Button name="CharacterFrameTab2" inherits="CharacterFrameTabButtonTemplate" text="PET" hidden="true">
<Anchors><Anchor point="LEFT" relativeTo="CharacterFrameTab1" relativePoint="RIGHT"><Offset><AbsDimension x="-16" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnClick>BenillaCharacterFrameTab_OnClick(2)</OnClick></Scripts>
</Button>
<Button name="CharacterFrameTab3" inherits="CharacterFrameTabButtonTemplate" text="REPUTATION">
<Anchors><Anchor point="LEFT" relativeTo="CharacterFrameTab2" relativePoint="RIGHT"><Offset><AbsDimension x="-16" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnClick>BenillaCharacterFrameTab_OnClick(3)</OnClick></Scripts>
</Button>
<Button name="CharacterFrameTab4" inherits="CharacterFrameTabButtonTemplate" text="SKILLS">
<Anchors><Anchor point="LEFT" relativeTo="CharacterFrameTab3" relativePoint="RIGHT"><Offset><AbsDimension x="-16" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnClick>BenillaCharacterFrameTab_OnClick(4)</OnClick></Scripts>
</Button>
<Button name="CharacterFrameTab5" inherits="CharacterFrameTabButtonTemplate" text="HONOR">
<Anchors><Anchor point="LEFT" relativeTo="CharacterFrameTab4" relativePoint="RIGHT"><Offset><AbsDimension x="-16" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnClick>BenillaCharacterFrameTab_OnClick(5)</OnClick></Scripts>
</Button>
<!-- The paper-doll page (ref PaperDollFrame, ref-PaperDollFrame.xml:124-1059). Sized to
exactly fill the parent instead of the ref's setAllPoints="true" — the same effect
(see this file's header: frame-level setAllPoints was a no-op when this was written
and is wired now; the explicit size is equivalent, so it stays). -->
<Frame name="PaperDollFrame" id="1">
<Size><AbsDimension x="384" y="512"/></Size>
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"/></Anchors>
<HitRectInsets><AbsInset left="0" right="30" top="0" bottom="45"/></HitRectInsets>
<Layers>
<Layer level="BACKGROUND">
<!-- The four quadrant slabs that ARE this page's art (ref l.129-173): 256-wide
left halves, 128-wide right halves, each 256 tall, pinned per corner. -->
<Texture file="Interface\PaperDollInfoFrame\UI-Character-CharacterTab-L1">
<Size><AbsDimension x="256" y="256"/></Size>
<Anchors><Anchor point="TOPLEFT"/></Anchors>
</Texture>
<Texture file="Interface\PaperDollInfoFrame\UI-Character-CharacterTab-R1">
<Size><AbsDimension x="128" y="256"/></Size>
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="256" y="0"/></Offset></Anchor></Anchors>
</Texture>
<Texture file="Interface\PaperDollInfoFrame\UI-Character-CharacterTab-BottomLeft">
<Size><AbsDimension x="256" y="256"/></Size>
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="0" y="-256"/></Offset></Anchor></Anchors>
</Texture>
<Texture file="Interface\PaperDollInfoFrame\UI-Character-CharacterTab-BottomRight">
<Size><AbsDimension x="128" y="256"/></Size>
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="256" y="-256"/></Offset></Anchor></Anchors>
</Texture>
<!-- "Level %d %s %s" under the name (ref l.174-182). -->
<FontString name="CharacterLevelText" inherits="GameFontNormalSmall">
<Anchors>
<Anchor point="TOP" relativeTo="CharacterNameText" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="-6"/></Offset></Anchor>
</Anchors>
</FontString>
</Layer>
</Layers>
<Frames>
<!-- The model pane (ref PlayerModel CharacterModelFrame, l.204-273): 233x224 at
TOPLEFT (65,-78). This file's header comment: a plain Frame sampling the
paper-doll booth bake, not a live <Model>/<PlayerModel> widget. -->
<Frame name="CharacterModelFrame">
<Size><AbsDimension x="233" y="224"/></Size>
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="65" y="-78"/></Offset></Anchor></Anchors>
<Layers>
<!-- BACKGROUND, not ARTWORK — the booth quad is an OPAQUE stand-in for a
widget the reference renders outside the layer batches, so it must
sit as low as a region goes or it swallows same-level siblings
(decision 1070; the pet page's diet icon is where that bit). -->
<Layer level="BACKGROUND">
<Texture name="$parentTexture">
<Size><AbsDimension x="233" y="224"/></Size>
<Anchors><Anchor point="TOPLEFT"/></Anchors>
</Texture>
</Layer>
</Layers>
<Frames>
<!-- Rotate buttons (ref l.234-271): 35x35, left then right chained off its
TOPRIGHT, both at the model pane's own TOPLEFT.
`RegisterForClicks("LeftButtonDown","LeftButtonUp")` is the ref's own
(l.241-243/l.263-265): OnClick fires on BOTH edges, so one tap nudges
twice (2 × 0.03), and holding hands the spin to the pane's OnUpdate. -->
<Button name="CharacterModelFrameRotateLeftButton">
<Size><AbsDimension x="35" y="35"/></Size>
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"/></Anchors>
<NormalTexture file="Interface\Buttons\UI-RotationLeft-Button-Up"/>
<PushedTexture file="Interface\Buttons\UI-RotationLeft-Button-Down"/>
<HighlightTexture file="Interface\Buttons\ButtonHilight-Round" alphaMode="ADD"/>
<Scripts>
<OnLoad>self:RegisterForClicks("LeftButtonDown", "LeftButtonUp")</OnLoad>
<OnClick>BenillaPaperDollModel_RotateLeft(getglobal("CharacterModelFrame"))</OnClick>
</Scripts>
</Button>
<Button name="CharacterModelFrameRotateRightButton">
<Size><AbsDimension x="35" y="35"/></Size>
<Anchors><Anchor point="TOPLEFT" relativeTo="CharacterModelFrameRotateLeftButton" relativePoint="TOPRIGHT"/></Anchors>
<NormalTexture file="Interface\Buttons\UI-RotationRight-Button-Up"/>
<PushedTexture file="Interface\Buttons\UI-RotationRight-Button-Down"/>
<HighlightTexture file="Interface\Buttons\ButtonHilight-Round" alphaMode="ADD"/>
<Scripts>
<OnLoad>self:RegisterForClicks("LeftButtonDown", "LeftButtonUp")</OnLoad>
<OnClick>BenillaPaperDollModel_RotateRight(getglobal("CharacterModelFrame"))</OnClick>
</Scripts>
</Button>
</Frames>
<Scripts>
<OnLoad>
-- A plain Frame isn't mouse-enabled by construction (this file's
-- header comment) — needed THIS phase (1b) so the pane hit-tests
-- as an OnMouseUp/OnReceiveDrag drop target (ref l.226-231).
self:EnableMouse(true)
self.setFacing = BenillaPaperDollModel_SetFacing
BenillaPaperDollModel_OnLoad(self)
BenillaSetBoothTexture(getglobal(self:GetName() .. "Texture"), "paperdoll")
</OnLoad>
<!-- ref l.221-224: the pane drives the held-button spin every frame. -->
<OnUpdate>BenillaPaperDollModel_OnUpdate(self, arg1)</OnUpdate>
<OnMouseUp>BenillaPaperDollModel_OnMouseUp(self, arg1)</OnMouseUp>
<OnReceiveDrag>AutoEquipCursorItem()</OnReceiveDrag>
</Scripts>
</Frame>
<!-- The attributes block (ref CharacterAttributesFrame, l.274-405): 230x78 at
TOPLEFT (67,-291), the 9-piece UI-Character-StatBackground plate (3 stacked
3-piece columns: player-stat / melee / ranged) behind the rows. -->
<Frame name="CharacterAttributesFrame">
<Size><AbsDimension x="230" y="78"/></Size>
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="67" y="-291"/></Offset></Anchor></Anchors>
<Layers>
<Layer level="BACKGROUND">
<Texture name="PlayerStatBackgroundTop" file="Interface\PaperDollInfoFrame\UI-Character-StatBackground">
<Size><AbsDimension x="115" y="16"/></Size>
<Anchors><Anchor point="TOPLEFT"/></Anchors>
<TexCoords left="0" right="0.8984375" top="0" bottom="0.125"/>
</Texture>
<Texture name="PlayerStatBackgroundMiddle" file="Interface\PaperDollInfoFrame\UI-Character-StatBackground">
<Size><AbsDimension x="115" y="53"/></Size>
<Anchors><Anchor point="TOPLEFT" relativeTo="PlayerStatBackgroundTop" relativePoint="BOTTOMLEFT"/></Anchors>
<TexCoords left="0" right="0.8984375" top="0.125" bottom="0.1953125"/>
</Texture>
<Texture name="PlayerStatBackgroundBottom" file="Interface\PaperDollInfoFrame\UI-Character-StatBackground">
<Size><AbsDimension x="115" y="16"/></Size>
<Anchors><Anchor point="TOPLEFT" relativeTo="PlayerStatBackgroundMiddle" relativePoint="BOTTOMLEFT"/></Anchors>
<TexCoords left="0" right="0.8984375" top="0.484375" bottom="0.609375"/>
</Texture>
<Texture name="MeleeAttackBackgroundTop" file="Interface\PaperDollInfoFrame\UI-Character-StatBackground">
<Size><AbsDimension x="115" y="16"/></Size>
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="115" y="0"/></Offset></Anchor></Anchors>
<TexCoords left="0" right="0.8984375" top="0" bottom="0.125"/>
</Texture>
<Texture name="MeleeAttackBackgroundMiddle" file="Interface\PaperDollInfoFrame\UI-Character-StatBackground">
<Size><AbsDimension x="115" y="12"/></Size>
<Anchors><Anchor point="TOPLEFT" relativeTo="MeleeAttackBackgroundTop" relativePoint="BOTTOMLEFT"/></Anchors>
<TexCoords left="0" right="0.8984375" top="0.125" bottom="0.1953125"/>
</Texture>
<Texture name="MeleeAttackBackgroundBottom" file="Interface\PaperDollInfoFrame\UI-Character-StatBackground">
<Size><AbsDimension x="115" y="16"/></Size>
<Anchors><Anchor point="TOPLEFT" relativeTo="MeleeAttackBackgroundMiddle" relativePoint="BOTTOMLEFT"/></Anchors>
<TexCoords left="0" right="0.8984375" top="0.484375" bottom="0.609375"/>
</Texture>
<Texture name="RangedAttackBackgroundTop" file="Interface\PaperDollInfoFrame\UI-Character-StatBackground">
<Size><AbsDimension x="115" y="16"/></Size>
<Anchors><Anchor point="TOPLEFT" relativeTo="MeleeAttackBackgroundBottom" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="2"/></Offset></Anchor></Anchors>
<TexCoords left="0" right="0.8984375" top="0" bottom="0.125"/>
</Texture>
<Texture name="RangedAttackBackgroundMiddle" file="Interface\PaperDollInfoFrame\UI-Character-StatBackground">
<Size><AbsDimension x="115" y="11"/></Size>
<Anchors><Anchor point="TOPLEFT" relativeTo="RangedAttackBackgroundTop" relativePoint="BOTTOMLEFT"/></Anchors>
<TexCoords left="0" right="0.8984375" top="0.125" bottom="0.1953125"/>
</Texture>
<Texture name="RangedAttackBackgroundBottom" file="Interface\PaperDollInfoFrame\UI-Character-StatBackground">
<Size><AbsDimension x="115" y="16"/></Size>
<Anchors><Anchor point="TOPLEFT" relativeTo="RangedAttackBackgroundMiddle" relativePoint="BOTTOMLEFT"/></Anchors>
<TexCoords left="0" right="0.8984375" top="0.484375" bottom="0.609375"/>
</Texture>
</Layer>
</Layers>
<Frames>
<!-- The 5 primary attributes (ref l.407-491): TOPLEFT (6,-3), chained
BOTTOMLEFT-to-BOTTOMLEFT. Labels are the TEMPLATE's $parentLabel,
filled from Lua exactly like the ref (PaperDollFrame_OnLoad /
_SetStats — see the former's header comment); instances declare no
regions of their own, same as the ref's. -->
<Frame name="CharacterStatFrame1" inherits="BenillaStatRowTemplate">
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="6" y="-3"/></Offset></Anchor></Anchors>
<Scripts>
<OnLoad>self:EnableMouse(true)</OnLoad>
<OnEnter>PaperDollStatTooltip("player", "STRENGTH")</OnEnter>
<OnLeave>GameTooltip:Hide()</OnLeave>
</Scripts>
</Frame>
<Frame name="CharacterStatFrame2" inherits="BenillaStatRowTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="CharacterStatFrame1" relativePoint="BOTTOMLEFT"/></Anchors>
<Scripts>
<OnLoad>self:EnableMouse(true)</OnLoad>
<OnEnter>PaperDollStatTooltip("player", "AGILITY")</OnEnter>
<OnLeave>GameTooltip:Hide()</OnLeave>
</Scripts>
</Frame>
<Frame name="CharacterStatFrame3" inherits="BenillaStatRowTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="CharacterStatFrame2" relativePoint="BOTTOMLEFT"/></Anchors>
<Scripts>
<OnLoad>self:EnableMouse(true)</OnLoad>
<OnEnter>PaperDollStatTooltip("player", "STAMINA")</OnEnter>
<OnLeave>GameTooltip:Hide()</OnLeave>
</Scripts>
</Frame>
<Frame name="CharacterStatFrame4" inherits="BenillaStatRowTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="CharacterStatFrame3" relativePoint="BOTTOMLEFT"/></Anchors>
<Scripts>
<OnLoad>self:EnableMouse(true)</OnLoad>
<OnEnter>PaperDollStatTooltip("player", "INTELLECT")</OnEnter>
<OnLeave>GameTooltip:Hide()</OnLeave>
</Scripts>
</Frame>
<Frame name="CharacterStatFrame5" inherits="BenillaStatRowTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="CharacterStatFrame4" relativePoint="BOTTOMLEFT"/></Anchors>
<Scripts>
<OnLoad>self:EnableMouse(true)</OnLoad>
<OnEnter>PaperDollStatTooltip("player", "SPIRIT")</OnEnter>
<OnLeave>GameTooltip:Hide()</OnLeave>
</Scripts>
</Frame>
<!-- Armor (ref l.492-511), chained off Stat5's BOTTOMLEFT. -->
<Frame name="CharacterArmorFrame" inherits="BenillaStatRowTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="CharacterStatFrame5" relativePoint="BOTTOMLEFT"/></Anchors>
<Scripts>
<OnLoad>self:EnableMouse(true)</OnLoad>
<OnEnter>
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip:SetText(self.tooltip)
GameTooltip:AddLine(self.tooltipSubtext, NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, 1)
GameTooltip:Show()
</OnEnter>
<OnLeave>GameTooltip:Hide()</OnLeave>
</Scripts>
</Frame>
<!-- Melee attack rating (ref l.512-531), TOPLEFT of the attributes frame
itself (122,-2) — the second column's own chain root. -->
<Frame name="CharacterAttackFrame" inherits="BenillaStatRowTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="CharacterAttributesFrame" relativePoint="TOPLEFT"><Offset><AbsDimension x="122" y="-2"/></Offset></Anchor></Anchors>
<Scripts>
<OnLoad>self:EnableMouse(true)</OnLoad>
<OnEnter>
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip:SetText(ATTACK_TOOLTIP)
GameTooltip:AddLine(ATTACK_TOOLTIP_SUBTEXT, NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, 1)
GameTooltip:Show()
</OnEnter>
<OnLeave>GameTooltip:Hide()</OnLeave>
</Scripts>
</Frame>
<!-- Melee attack power (ref l.532-554), 99px-wide override (OnLoad). -->
<Frame name="CharacterAttackPowerFrame" inherits="BenillaStatRowTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="CharacterAttackFrame" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="5" y="1"/></Offset></Anchor></Anchors>
<Scripts>
<OnLoad>self:EnableMouse(true); self:SetWidth(99)</OnLoad>
<OnEnter>
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip:SetText(self.tooltip)
GameTooltip:AddLine(self.tooltipSubtext, NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, 1)
GameTooltip:Show()
</OnEnter>
<OnLeave>GameTooltip:Hide()</OnLeave>
</Scripts>
</Frame>
<!-- Melee damage (ref l.555-574), 99px-wide override, dedicated OnEnter. -->
<Frame name="CharacterDamageFrame" inherits="BenillaStatRowTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="CharacterAttackPowerFrame" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="1"/></Offset></Anchor></Anchors>
<Scripts>
<OnLoad>self:EnableMouse(true); self:SetWidth(99)</OnLoad>
<OnEnter>CharacterDamageFrame_OnEnter()</OnEnter>
<OnLeave>GameTooltip:Hide()</OnLeave>
</Scripts>
</Frame>
<!-- Ranged attack rating (ref l.575-594). -->
<Frame name="CharacterRangedAttackFrame" inherits="BenillaStatRowTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="CharacterDamageFrame" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="-5" y="-6"/></Offset></Anchor></Anchors>
<Scripts>
<OnLoad>self:EnableMouse(true)</OnLoad>
<OnEnter>
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip:SetText(RANGED_ATTACK_TOOLTIP)
GameTooltip:AddLine(ATTACK_TOOLTIP_SUBTEXT, NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, 1)
GameTooltip:Show()
</OnEnter>
<OnLeave>GameTooltip:Hide()</OnLeave>
</Scripts>
</Frame>
<!-- Ranged attack power (ref l.595-619), 99px-wide, conditional tooltip
(the N/A / wand-equipped states leave self.tooltip nil). -->
<Frame name="CharacterRangedAttackPowerFrame" inherits="BenillaStatRowTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="CharacterRangedAttackFrame" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="5" y="1"/></Offset></Anchor></Anchors>
<Scripts>
<OnLoad>self:EnableMouse(true); self:SetWidth(99)</OnLoad>
<OnEnter>
if self.tooltip then
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip:SetText(self.tooltip)
GameTooltip:AddLine(self.tooltipSubtext, NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, 1)
GameTooltip:Show()
end
</OnEnter>
<OnLeave>GameTooltip:Hide()</OnLeave>
</Scripts>
</Frame>
<!-- Ranged damage (ref l.620-639), 99px-wide, dedicated OnEnter (no-ops
while self.damage is nil, e.g. no ranged weapon equipped). -->
<Frame name="CharacterRangedDamageFrame" inherits="BenillaStatRowTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="CharacterRangedAttackPowerFrame" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="1"/></Offset></Anchor></Anchors>
<Scripts>
<OnLoad>self:EnableMouse(true); self:SetWidth(99)</OnLoad>
<OnEnter>CharacterRangedDamageFrame_OnEnter()</OnEnter>
<OnLeave>GameTooltip:Hide()</OnLeave>
</Scripts>
</Frame>
</Frames>
</Frame>
<!-- The 5 magic-resistance frames (ref CharacterResistanceFrame, l.642-760):
32x160 wrapper, TOPRIGHT of PaperDollFrame's own TOPLEFT (297,-77); each
32x29 icon TOP-chained off the previous, ids 6/2/3/4/5 (arcane/fire/nature/
frost/shadow — Holy(1) never rides the paper doll). -->
<Frame name="CharacterResistanceFrame">
<Size><AbsDimension x="32" y="160"/></Size>
<Anchors>
<Anchor point="TOPRIGHT" relativeTo="PaperDollFrame" relativePoint="TOPLEFT"><Offset><AbsDimension x="297" y="-77"/></Offset></Anchor>
</Anchors>
<Frames>
<Frame name="MagicResFrame1" inherits="BenillaMagicResistanceFrameTemplate">
<Anchors><Anchor point="TOP" relativeTo="CharacterResistanceFrame" relativePoint="TOP"/></Anchors>
<Layers>
<Layer level="BACKGROUND">
<Texture file="Interface\PaperDollInfoFrame\UI-Character-ResistanceIcons">
<TexCoords left="0" right="1.0" top="0.2265625" bottom="0.33984375"/>
</Texture>
<FontString name="MagicResText1" inherits="GameFontHighlightSmall" text="X">
<Anchors><Anchor point="BOTTOM"><Offset><AbsDimension x="0" y="3"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
</Layers>
</Frame>
<Frame name="MagicResFrame2" inherits="BenillaMagicResistanceFrameTemplate">
<Anchors><Anchor point="TOP" relativeTo="MagicResFrame1" relativePoint="BOTTOM"/></Anchors>
<Layers>
<Layer level="BACKGROUND">
<Texture file="Interface\PaperDollInfoFrame\UI-Character-ResistanceIcons">
<TexCoords left="0" right="1.0" top="0" bottom="0.11328125"/>
</Texture>
<FontString name="MagicResText2" inherits="GameFontHighlightSmall" text="X">
<Anchors><Anchor point="BOTTOM"><Offset><AbsDimension x="0" y="3"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
</Layers>
</Frame>
<Frame name="MagicResFrame3" inherits="BenillaMagicResistanceFrameTemplate">
<Anchors><Anchor point="TOP" relativeTo="MagicResFrame2" relativePoint="BOTTOM"/></Anchors>
<Layers>
<Layer level="BACKGROUND">
<Texture file="Interface\PaperDollInfoFrame\UI-Character-ResistanceIcons">
<TexCoords left="0" right="1.0" top="0.11328125" bottom="0.2265625"/>
</Texture>
<FontString name="MagicResText3" inherits="GameFontHighlightSmall" text="X">
<Anchors><Anchor point="BOTTOM"><Offset><AbsDimension x="0" y="3"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
</Layers>
</Frame>
<Frame name="MagicResFrame4" inherits="BenillaMagicResistanceFrameTemplate">
<Anchors><Anchor point="TOP" relativeTo="MagicResFrame3" relativePoint="BOTTOM"/></Anchors>
<Layers>
<Layer level="BACKGROUND">
<Texture file="Interface\PaperDollInfoFrame\UI-Character-ResistanceIcons">
<TexCoords left="0" right="1.0" top="0.33984375" bottom="0.453125"/>
</Texture>
<FontString name="MagicResText4" inherits="GameFontHighlightSmall" text="X">
<Anchors><Anchor point="BOTTOM"><Offset><AbsDimension x="0" y="3"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
</Layers>
</Frame>
<Frame name="MagicResFrame5" inherits="BenillaMagicResistanceFrameTemplate">
<Anchors><Anchor point="TOP" relativeTo="MagicResFrame4" relativePoint="BOTTOM"/></Anchors>
<Layers>
<Layer level="BACKGROUND">
<Texture file="Interface\PaperDollInfoFrame\UI-Character-ResistanceIcons">
<TexCoords left="0" right="1.0" top="0.453125" bottom="0.56640625"/>
</Texture>
<FontString name="MagicResText5" inherits="GameFontHighlightSmall" text="X">
<Anchors><Anchor point="BOTTOM"><Offset><AbsDimension x="0" y="3"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
</Layers>
</Frame>
</Frames>
</Frame>
<!-- The 19 equipment slots (ref l.761-931): left column TOPLEFT (21,-74) chained
BOTTOMLEFT -4, right column TOPLEFT (305,-74) chained the same way, the
weapon row off the page's own BOTTOMLEFT (122,127) then chained TOPRIGHT +5. -->
<Button name="CharacterHeadSlot" inherits="BenillaPaperDollItemSlotTemplate">
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="21" y="-74"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaPaperDollSlot_OnLoad(self, "HeadSlot")</OnLoad></Scripts>
</Button>
<Button name="CharacterNeckSlot" inherits="BenillaPaperDollItemSlotTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="CharacterHeadSlot" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-4"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaPaperDollSlot_OnLoad(self, "NeckSlot")</OnLoad></Scripts>
</Button>
<Button name="CharacterShoulderSlot" inherits="BenillaPaperDollItemSlotTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="CharacterNeckSlot" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-4"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaPaperDollSlot_OnLoad(self, "ShoulderSlot")</OnLoad></Scripts>
</Button>
<Button name="CharacterBackSlot" inherits="BenillaPaperDollItemSlotTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="CharacterShoulderSlot" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-4"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaPaperDollSlot_OnLoad(self, "BackSlot")</OnLoad></Scripts>
</Button>
<Button name="CharacterChestSlot" inherits="BenillaPaperDollItemSlotTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="CharacterBackSlot" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-4"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaPaperDollSlot_OnLoad(self, "ChestSlot")</OnLoad></Scripts>
</Button>
<Button name="CharacterShirtSlot" inherits="BenillaPaperDollItemSlotTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="CharacterChestSlot" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-4"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaPaperDollSlot_OnLoad(self, "ShirtSlot")</OnLoad></Scripts>
</Button>
<Button name="CharacterTabardSlot" inherits="BenillaPaperDollItemSlotTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="CharacterShirtSlot" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-4"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaPaperDollSlot_OnLoad(self, "TabardSlot")</OnLoad></Scripts>
</Button>
<Button name="CharacterWristSlot" inherits="BenillaPaperDollItemSlotTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="CharacterTabardSlot" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-4"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaPaperDollSlot_OnLoad(self, "WristSlot")</OnLoad></Scripts>
</Button>
<Button name="CharacterHandsSlot" inherits="BenillaPaperDollItemSlotTemplate">
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="305" y="-74"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaPaperDollSlot_OnLoad(self, "HandsSlot")</OnLoad></Scripts>
</Button>
<Button name="CharacterWaistSlot" inherits="BenillaPaperDollItemSlotTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="CharacterHandsSlot" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-4"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaPaperDollSlot_OnLoad(self, "WaistSlot")</OnLoad></Scripts>
</Button>
<Button name="CharacterLegsSlot" inherits="BenillaPaperDollItemSlotTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="CharacterWaistSlot" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-4"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaPaperDollSlot_OnLoad(self, "LegsSlot")</OnLoad></Scripts>
</Button>
<Button name="CharacterFeetSlot" inherits="BenillaPaperDollItemSlotTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="CharacterLegsSlot" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-4"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaPaperDollSlot_OnLoad(self, "FeetSlot")</OnLoad></Scripts>
</Button>
<Button name="CharacterFinger0Slot" inherits="BenillaPaperDollItemSlotTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="CharacterFeetSlot" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-4"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaPaperDollSlot_OnLoad(self, "Finger0Slot")</OnLoad></Scripts>
</Button>
<Button name="CharacterFinger1Slot" inherits="BenillaPaperDollItemSlotTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="CharacterFinger0Slot" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-4"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaPaperDollSlot_OnLoad(self, "Finger1Slot")</OnLoad></Scripts>
</Button>
<Button name="CharacterTrinket0Slot" inherits="BenillaPaperDollItemSlotTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="CharacterFinger1Slot" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-4"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaPaperDollSlot_OnLoad(self, "Trinket0Slot")</OnLoad></Scripts>
</Button>
<Button name="CharacterTrinket1Slot" inherits="BenillaPaperDollItemSlotTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="CharacterTrinket0Slot" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-4"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaPaperDollSlot_OnLoad(self, "Trinket1Slot")</OnLoad></Scripts>
</Button>
<Button name="CharacterMainHandSlot" inherits="BenillaPaperDollItemSlotTemplate">
<Anchors><Anchor point="TOPLEFT" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="122" y="127"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaPaperDollSlot_OnLoad(self, "MainHandSlot")</OnLoad></Scripts>
</Button>
<Button name="CharacterSecondaryHandSlot" inherits="BenillaPaperDollItemSlotTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="CharacterMainHandSlot" relativePoint="TOPRIGHT"><Offset><AbsDimension x="5" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaPaperDollSlot_OnLoad(self, "SecondaryHandSlot")</OnLoad></Scripts>
</Button>
<Button name="CharacterRangedSlot" inherits="BenillaPaperDollItemSlotTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="CharacterSecondaryHandSlot" relativePoint="TOPRIGHT"><Offset><AbsDimension x="5" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaPaperDollSlot_OnLoad(self, "RangedSlot")</OnLoad></Scripts>
</Button>
<!-- The ammo slot (ref CharacterAmmoSlot, l.932-1043): 27x27, LEFT of the ranged
slot's RIGHT (+15,0). NOT built from the shared slot template — its own
hand-authored art (a pouch background + a corner-accent overlay cut from the
same sheet, no Quickslot2 ring) — but reuses the SAME Lua as the 19 named
slots (this file's header comment: the ref shares PaperDollItemSlotButton_*
verbatim here too, keyed off "AmmoSlot"). $parentCount's hidden="true" (ref
default) is DROPPED — every other count fontstring in this codebase (Bag/
Merchant/this file's own template) uses the same "blank text = invisible"
idiom with no Show/Hide dance, applied here too for consistency.
$parentStock OMITTED (this file's header comment: no ref Lua populates it). -->
<Button name="CharacterAmmoSlot">
<Size><AbsDimension x="27" y="27"/></Size>
<Anchors>
<Anchor point="LEFT" relativeTo="CharacterRangedSlot" relativePoint="RIGHT"><Offset><AbsDimension x="15" y="0"/></Offset></Anchor>
</Anchors>
<Layers>
<Layer level="BACKGROUND">
<Texture file="Interface\PaperdollInfoFrame\UI-Character-AmmoSlot">
<Size><AbsDimension x="41" y="41"/></Size>
<Anchors><Anchor point="CENTER"/></Anchors>
<TexCoords left="0" right="0.640625" top="0" bottom="0.640625"/>
</Texture>
</Layer>
<Layer level="BORDER">
<Texture name="$parentIconTexture"/>
<FontString name="$parentCount" inherits="NumberFontNormal" justifyH="RIGHT">
<Anchors><Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-1" y="2"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
<Layer level="OVERLAY">
<Texture file="Interface\PaperdollInfoFrame\UI-Character-AmmoSlot">
<Size><AbsDimension x="23" y="41"/></Size>
<Anchors><Anchor point="CENTER"><Offset><AbsDimension x="-22" y="0"/></Offset></Anchor></Anchors>
<TexCoords left="0.640625" right="1.0" top="0" bottom="0.640625"/>
</Texture>
</Layer>
</Layers>
<Scripts>
<OnLoad>BenillaPaperDollSlot_OnLoad(self, "AmmoSlot")</OnLoad>
<OnEnter>BenillaPaperDollSlot_OnEnter(self)</OnEnter>
<OnLeave>GameTooltip:Hide()</OnLeave>
<OnEvent>BenillaPaperDollSlot_OnEvent(self)</OnEvent>
<OnUpdate>BenillaPaperDollSlot_OnUpdate(self)</OnUpdate>
<!-- Ammo loads by SET_AMMO, not the equip-swap wire (decision 0526):
click-to-place and drag-drop both route to the ammo handler. -->
<OnClick>BenillaCharacterAmmoSlot_OnClick(self)</OnClick>
<OnReceiveDrag>BenillaCharacterAmmoSlot_OnClick(self)</OnReceiveDrag>
</Scripts>
</Button>
</Frames>
<Scripts>
<OnLoad>PaperDollFrame_OnLoad()</OnLoad>
<OnEvent>PaperDollFrame_OnEvent(event, arg1)</OnEvent>
<OnShow>PaperDollFrame_OnShow()</OnShow>
</Scripts>
</Frame>
</Frames>
<Scripts>
<OnLoad>CharacterFrame_OnLoad()</OnLoad>
<OnShow>CharacterFrame_OnShow()</OnShow>
<OnHide>CharacterFrame_OnHide()</OnHide>
</Scripts>
</Frame>
</Ui>