Files
benilla-octo/crates/benilla/assets/ui/CharacterFrame.xml
T
SamandClaude Fable 5 2ad0043523 benilla: initial public release
A from-scratch World of Warcraft 1.12.1 client in Rust + Bevy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 14:27:01 +07:00

1772 lines
121 KiB
XML

<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
transcribed in `UiPanels.xml:507-563` (battle-tested by Merchant/QuestLog, 0138) — reused
here, not redefined.
Docks the left UIPanel slot exactly like Gossip/Merchant/Quest/QuestLog (`UiPanels.xml`
`UIPanelWindows["BenillaCharacterFrame"] = { area = "left", pushable = 0 }`, mirroring the
ref's own `CharacterFrame` row, `UIParent.lua` pin cited in `UiPanels.xml`'s header) — so it
competes for/replaces those windows, never stacks with them. `ToggleCharacter(tab)` (ref
`CharacterFrame.lua:3-23`, kept BARE — not Benilla-prefixed — matching this codebase's own
`ToggleQuestLog()` precedent: the addon-facing entry point keeps the real global name even
though the frames it drives are Benilla-prefixed) is the show/hide/tab-switch entry point;
the app's 'C' binding calls it (`ui_script/mod.rs`).
ONE deliberate naming deviation from the ref, applied consistently: every frame this file
defines is Benilla-prefixed (`BenillaCharacterFrame`, `BenillaPaperDollFrame`, …) — including
the string `ToggleCharacter`/`CharacterFrame_ShowSubFrame` pass around internally
(`BENILLA_CHARACTERFRAME_SUBFRAMES` lists `"BenillaPaperDollFrame"`, not the ref's bare
`"PaperDollFrame"`) — matching the same-prefix convention every other window in this codebase
already follows (Merchant/Bag/QuestLog never leave a sub-piece unprefixed).
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) are still OMITTED, the 0138
merchant-tab precedent ("tabs arrive with their pages") — `BENILLA_CHARACTERFRAME_SUBFRAMES`
only lists the one page that exists.
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 and ctrl-click `DressUpItemLink` (the dressing room): both
named deferrals this phase too — the left-click fork recognizes the modifiers and does
nothing, rather than silently falling through to a pickup a real shift/ctrl-click
would never have triggered.
- 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. `BenillaPaperDollStatTooltip` 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 — transcribed 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):
- `HitRectInsets` (both `BenillaCharacterFrame` and `BenillaPaperDollFrame`, ref's identical
0/30/0/45 on both) has no loader wiring yet (grepped `benilla-ui/src/loader` — no
`HitRectInsets` handling exists) — included anyway (harmless no-op, documents the ref's
intent for whenever hit-rect insets land).
- `enableMouse`/`setAllPoints` (frame-level) are also no-ops on `<Frame>` (only the
region-level `setAllPoints` shorthand is wired, `loader/regions.rs:130`) — `
BenillaPaperDollFrame` therefore gets an explicit `Size`+`TOPLEFT` anchor instead of the
ref's `setAllPoints="true"` (the same effect, since it's sized to exactly fill its
parent).
- 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)
PLAYER_LEVEL = PLAYER_LEVEL or "Level %d %s %s" -- :3075
ARMOR = ARMOR or "Armor" -- :62
ARMOR_COLON = ARMOR_COLON or "Armor:" -- :63
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
BENILLA_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 pages that exist so far (this file's header comment; the 0138 merchant-tab
-- precedent — tabs arrive with their pages). Benilla-prefixed, unlike the ref's bare
-- "PaperDollFrame"/"SkillFrame". 1:1 with the tab row below (index N is tab N's page) —
-- decision 0437 phase 4 adds "BenillaSkillFrame" as tab 2; Pet/Reputation/Honor (the
-- real client's tabs 2/3/5) are still OMITTED.
BENILLA_CHARACTERFRAME_SUBFRAMES = { "BenillaPaperDollFrame", "BenillaSkillFrame" }
-- ── 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
local frame = getglobal("BenillaCharacterFrame")
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. PanelTemplates_SetTab visually selects the clicked tab (the ref's own
-- PanelTemplates_SetTab(this, subFrame:GetID()) call, dropped by the original phase-1a
-- single-tab comment above — restored now that a second tab exists to disagree with the
-- first). 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 —
-- transcribed verbatim rather than "fixed" since it's the ref's own behavior, not a bug of ours.
function BenillaCharacterFrameTab_OnClick(id)
PanelTemplates_SetTab(getglobal("BenillaCharacterFrame"), id)
ToggleCharacter(BENILLA_CHARACTERFRAME_SUBFRAMES[id])
PlaySound("igCharacterInfoTab")
end
function BenillaCharacterFrame_OnLoad(frame)
-- 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). 2 tabs ship as of decision 0437
-- phase 4 (Character + Skills); the window opens on tab 1 by default, matching the
-- real client.
PanelTemplates_SetNumTabs(frame, 2)
PanelTemplates_SetTab(frame, 1)
end
-- ref CharacterFrame_OnShow (l.82-93): UpdateMicroButtons/Show*TextStatusBarText/
-- ShowWatchedReputationBarText all OMITTED — no micro-button bar, unit-frame bar-text-mode
-- toggle, or reputation-bar-text mechanism exists in this engine. UnitPVPName -> plain
-- UnitName (this file's header comment: PVP rank is a named deferral).
function BenillaCharacterFrame_OnShow()
PlaySound("igCharacterInfoOpen")
SetPortraitTexture(getglobal("BenillaCharacterFramePortrait"), "player")
getglobal("BenillaCharacterNameText"):SetText(UnitName("player"))
end
-- ref CharacterFrame_OnHide (l.95-104): everything past the PlaySound is the OnShow list's
-- Hide-twin, OMITTED for the same reason. The subframe hide-loop is OUR OWN addition
-- (decision 0437 phase 4): BenillaPaperDollFrame is a true engine child of this frame (its
-- own header comment) so it hides for free on the parent's Hide-cascade, but
-- BenillaSkillFrame 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,
-- keeps this correct as more non-nested tabs (Pet/Reputation/Honor) land the same way.
function BenillaCharacterFrame_OnHide()
PlaySound("igCharacterInfoClose")
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) transcribed verbatim: 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 BenillaPaperDollFrame_OnLoad(frame)
getglobal("BenillaCharacterAttackFrameLabel"):SetText(MELEE_ATTACK)
getglobal("BenillaCharacterDamageFrameLabel"):SetText(DAMAGE_COLON)
getglobal("BenillaCharacterAttackPowerFrameLabel"):SetText(ATTACK_POWER_COLON)
getglobal("BenillaCharacterRangedAttackFrameLabel"):SetText(RANGED_ATTACK)
getglobal("BenillaCharacterRangedDamageFrameLabel"):SetText(DAMAGE_COLON)
getglobal("BenillaCharacterRangedAttackPowerFrameLabel"):SetText(ATTACK_POWER_COLON)
getglobal("BenillaCharacterArmorFrameLabel"):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
-- ref PaperDollFrame_OnEvent (l.37-76), the unit-token branch only (unit == "player" always
-- true here — no pet/other-unit reuse in phase 1a) and this file's registered subset only.
function BenillaPaperDollFrame_OnEvent(frame)
if event == "PLAYER_ENTERING_WORLD" then
return
end
if not frame:IsVisible() then return end
if arg1 ~= "player" then return end
if event == "UNIT_LEVEL" then
BenillaPaperDollFrame_SetLevel()
elseif event == "UNIT_DAMAGE" or event == "UNIT_ATTACK_SPEED" then
BenillaPaperDollFrame_SetDamage()
BenillaPaperDollFrame_SetRangedAttack()
BenillaPaperDollFrame_SetRangedDamage()
BenillaPaperDollFrame_SetAttackBothHands()
elseif event == "UNIT_ATTACK" then
BenillaPaperDollFrame_SetAttackBothHands()
elseif event == "UNIT_RESISTANCES" then
BenillaPaperDollFrame_SetResistances()
BenillaPaperDollFrame_SetArmor()
elseif event == "UNIT_STATS" then
BenillaPaperDollFrame_SetStats()
elseif event == "UNIT_ATTACK_POWER" then
BenillaPaperDollFrame_SetAttackPower()
elseif event == "UNIT_RANGED_ATTACK_POWER" then
BenillaPaperDollFrame_SetRangedAttack()
BenillaPaperDollFrame_SetRangedAttackPower()
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 BenillaPaperDollFrame_OnShow()
BenillaPaperDollFrame_SetLevel()
BenillaPaperDollFrame_SetStats()
BenillaPaperDollFrame_SetResistances()
BenillaPaperDollFrame_SetArmor()
BenillaPaperDollFrame_SetDamage()
BenillaPaperDollFrame_SetAttackPower()
BenillaPaperDollFrame_SetAttackBothHands()
BenillaPaperDollFrame_SetRangedAttack()
BenillaPaperDollFrame_SetRangedDamage()
BenillaPaperDollFrame_SetRangedAttackPower()
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 BenillaPaperDollFrame_SetLevel()
getglobal("BenillaCharacterLevelText"):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 BenillaPaperDollFrame_OnLoad's header comment).
function BenillaPaperDollFrame_SetStats()
for i = 1, BENILLA_NUM_STATS do
local label = getglobal("BenillaCharacterStatFrame" .. i .. "Label")
local text = getglobal("BenillaCharacterStatFrame" .. i .. "StatText")
local frame = getglobal("BenillaCharacterStatFrame" .. 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
-- ref PaperDollFrame_SetResistances (l.176-228). `resistanceLevel` is a proper `local` here
-- (the ref leaks it as an implicit global, harmless there — Lua 5.0's default — but our
-- sandboxed globals table is worth not littering).
function BenillaPaperDollFrame_SetResistances()
for i = 1, BENILLA_NUM_RESISTANCE_TYPES do
local resistId = BENILLA_MAGICRES_IDS[i]
local text = getglobal("BenillaMagicResText" .. i)
local frame = getglobal("BenillaMagicResFrame" .. 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
local unitLevel = math.max(UnitLevel("player") or 1, 20)
local magicResistanceNumber = resistance / unitLevel
local resistanceLevel
if magicResistanceNumber > 5 then
resistanceLevel = RESISTANCE_EXCELLENT
elseif magicResistanceNumber > 3.75 then
resistanceLevel = RESISTANCE_VERYGOOD
elseif magicResistanceNumber > 2.5 then
resistanceLevel = RESISTANCE_GOOD
elseif magicResistanceNumber > 1.25 then
resistanceLevel = RESISTANCE_FAIR
elseif magicResistanceNumber > 0 then
resistanceLevel = RESISTANCE_POOR
else
resistanceLevel = RESISTANCE_NONE
end
frame.tooltipSubtext = format(RESISTANCE_TOOLTIP_SUBTEXT,
getglobal("RESISTANCE_TYPE" .. resistId), unitLevel, resistanceLevel)
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`; transcribed
-- verbatim, not "fixed".
function BenillaPaperDollFrame_SetArmor(unit, prefix)
unit = unit or "player"
prefix = prefix or "BenillaCharacter"
local base, effectiveArmor, armor, posBuff, negBuff = UnitArmor(unit)
local frame = getglobal(prefix .. "ArmorFrame")
local text = getglobal(prefix .. "ArmorFrameStatText")
BenillaPaperDollFormatStat(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_SetDamage (l.274-372).
function BenillaPaperDollFrame_SetDamage(unit, prefix)
unit = unit or "player"
prefix = prefix or "BenillaCharacter"
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 displayMin = math.max(math.floor(minDamage), 1)
local displayMax = math.max(math.ceil(maxDamage), 1)
minDamage = (minDamage / percent) - physicalBonusPos - physicalBonusNeg
maxDamage = (maxDamage / percent) - physicalBonusPos - physicalBonusNeg
local baseDamage = (minDamage + maxDamage) * 0.5
local fullDamage = (baseDamage + physicalBonusPos + physicalBonusNeg) * percent
local totalBonus = fullDamage - baseDamage
local damagePerSecond = math.max(fullDamage, 1) / speed
local damageTooltip = math.max(math.floor(minDamage), 1) .. " - " .. math.max(math.ceil(maxDamage), 1)
local colorPos = "|cff20ff20"
local colorNeg = "|cffff2020"
if totalBonus == 0 then
if displayMin < 100 and displayMax < 100 then
damageText:SetText(displayMin .. " - " .. displayMax)
else
damageText:SetText(displayMin .. "-" .. displayMax)
end
else
local color
if totalBonus > 0 then color = colorPos else color = colorNeg end
if displayMin < 100 and displayMax < 100 then
damageText:SetText(color .. displayMin .. " - " .. displayMax .. "|r")
else
damageText:SetText(color .. displayMin .. "-" .. displayMax .. "|r")
end
if physicalBonusPos > 0 then
damageTooltip = damageTooltip .. colorPos .. " +" .. physicalBonusPos .. "|r"
end
if physicalBonusNeg < 0 then
damageTooltip = damageTooltip .. colorNeg .. " " .. physicalBonusNeg .. "|r"
end
if percent > 1 then
damageTooltip = damageTooltip .. colorPos .. " x" .. math.floor(percent * 100 + 0.5) .. "%|r"
elseif percent < 1 then
damageTooltip = damageTooltip .. colorNeg .. " x" .. math.floor(percent * 100 + 0.5) .. "%|r"
end
end
damageFrame.damage = damageTooltip
damageFrame.attackSpeed = speed
damageFrame.dps = damagePerSecond
if offhandSpeed then
minOffHandDamage = (minOffHandDamage / percent) - physicalBonusPos - physicalBonusNeg
maxOffHandDamage = (maxOffHandDamage / percent) - physicalBonusPos - physicalBonusNeg
local offhandBaseDamage = (minOffHandDamage + maxOffHandDamage) * 0.5
local offhandFullDamage = (offhandBaseDamage + physicalBonusPos + physicalBonusNeg) * percent
local offhandDamagePerSecond = math.max(offhandFullDamage, 1) / offhandSpeed
local offhandDamageTooltip =
math.max(math.floor(minOffHandDamage), 1) .. " - " .. math.max(math.ceil(maxOffHandDamage), 1)
if physicalBonusPos > 0 then
offhandDamageTooltip = offhandDamageTooltip .. colorPos .. " +" .. physicalBonusPos .. "|r"
end
if physicalBonusNeg < 0 then
offhandDamageTooltip = offhandDamageTooltip .. colorNeg .. " " .. physicalBonusNeg .. "|r"
end
if percent > 1 then
offhandDamageTooltip = offhandDamageTooltip .. colorPos .. " x" .. math.floor(percent * 100 + 0.5) .. "%|r"
elseif percent < 1 then
offhandDamageTooltip = offhandDamageTooltip .. colorNeg .. " x" .. math.floor(percent * 100 + 0.5) .. "%|r"
end
damageFrame.offhandDamage = offhandDamageTooltip
damageFrame.offhandAttackSpeed = offhandSpeed
damageFrame.offhandDps = offhandDamagePerSecond
else
damageFrame.offhandAttackSpeed = nil
end
end
-- ref PaperDollFrame_SetAttackPower (l.374-389).
function BenillaPaperDollFrame_SetAttackPower(unit, prefix)
unit = unit or "player"
prefix = prefix or "BenillaCharacter"
local base, posBuff, negBuff = UnitAttackPower(unit)
local frame = getglobal(prefix .. "AttackPowerFrame")
local text = getglobal(prefix .. "AttackPowerFrameStatText")
BenillaPaperDollFormatStat(MELEE_ATTACK_POWER, base, posBuff, negBuff, frame, text)
frame.tooltipSubtext = format(MELEE_ATTACK_POWER_TOOLTIP,
math.max(base + posBuff + negBuff, 0) / BENILLA_ATTACK_POWER_MAGIC_NUMBER)
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 BenillaPaperDollFrame_SetAttackBothHands(unit, prefix)
unit = unit or "player"
prefix = prefix or "BenillaCharacter"
local mainHandAttackBase, mainHandAttackMod = UnitAttackBothHands(unit)
local frame = getglobal(prefix .. "AttackFrame")
local text = getglobal(prefix .. "AttackFrameStatText")
if mainHandAttackMod == 0 then
text:SetText(mainHandAttackBase)
frame.tooltip = nil
else
local color = RED_FONT_COLOR_CODE
if mainHandAttackMod > 0 then
color = GREEN_FONT_COLOR_CODE
frame.tooltip = mainHandAttackBase .. color .. " +" .. mainHandAttackMod .. FONT_COLOR_CODE_CLOSE
else
frame.tooltip = mainHandAttackBase .. color .. " " .. mainHandAttackMod .. FONT_COLOR_CODE_CLOSE
end
text:SetText(color .. (mainHandAttackBase + mainHandAttackMod) .. FONT_COLOR_CODE_CLOSE)
end
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 BenillaPaperDollFrame_SetRangedAttack(unit, prefix)
if unit == "pet" then return end
unit = unit or "player"
prefix = prefix or "BenillaCharacter"
local frame = getglobal(prefix .. "RangedAttackFrame")
local text = getglobal(prefix .. "RangedAttackFrameStatText")
local paperDoll = getglobal("BenillaPaperDollFrame")
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
BenillaPaperDollFrame_SetRangedAttackPower()
BenillaPaperDollFrame_SetRangedDamage()
end
if not rangedTexture then return end
if rangedAttackMod == 0 then
text:SetText(rangedAttackBase)
frame.tooltip = nil
else
local color = RED_FONT_COLOR_CODE
if rangedAttackMod > 0 then
color = GREEN_FONT_COLOR_CODE
frame.tooltip = rangedAttackBase .. color .. " +" .. rangedAttackMod .. FONT_COLOR_CODE_CLOSE
else
frame.tooltip = rangedAttackBase .. color .. " " .. rangedAttackMod .. FONT_COLOR_CODE_CLOSE
end
text:SetText(color .. (rangedAttackBase + rangedAttackMod) .. FONT_COLOR_CODE_CLOSE)
end
end
-- ref PaperDollFrame_SetRangedDamage (l.468-537).
function BenillaPaperDollFrame_SetRangedDamage(unit, prefix)
if unit == "pet" then return end
unit = unit or "player"
prefix = prefix or "BenillaCharacter"
local damageText = getglobal(prefix .. "RangedDamageFrameStatText")
local damageFrame = getglobal(prefix .. "RangedDamageFrame")
local paperDoll = getglobal("BenillaPaperDollFrame")
if paperDoll.noRanged then
damageText:SetText(NOT_APPLICABLE)
damageFrame.damage = nil
return
end
local rangedAttackSpeed, minDamage, maxDamage, physicalBonusPos, physicalBonusNeg, percent =
UnitRangedDamage(unit)
local displayMin = math.max(math.floor(minDamage), 1)
local displayMax = math.max(math.ceil(maxDamage), 1)
minDamage = (minDamage / percent) - physicalBonusPos - physicalBonusNeg
maxDamage = (maxDamage / percent) - physicalBonusPos - physicalBonusNeg
local baseDamage = (minDamage + maxDamage) * 0.5
local fullDamage = (baseDamage + physicalBonusPos + physicalBonusNeg) * percent
local totalBonus = fullDamage - baseDamage
local damagePerSecond = math.max(fullDamage, 1) / rangedAttackSpeed
local tooltip = math.max(math.floor(minDamage), 1) .. " - " .. math.max(math.ceil(maxDamage), 1)
if totalBonus == 0 then
if displayMin < 100 and displayMax < 100 then
damageText:SetText(displayMin .. " - " .. displayMax)
else
damageText:SetText(displayMin .. "-" .. displayMax)
end
else
local colorPos = "|cff20ff20"
local colorNeg = "|cffff2020"
local color
if totalBonus > 0 then color = colorPos else color = colorNeg end
if displayMin < 100 and displayMax < 100 then
damageText:SetText(color .. displayMin .. " - " .. displayMax .. "|r")
else
damageText:SetText(color .. displayMin .. "-" .. displayMax .. "|r")
end
if physicalBonusPos > 0 then
tooltip = tooltip .. colorPos .. " +" .. physicalBonusPos .. "|r"
end
if physicalBonusNeg < 0 then
tooltip = tooltip .. colorNeg .. " " .. physicalBonusNeg .. "|r"
end
if percent > 1 then
tooltip = tooltip .. colorPos .. " x" .. math.floor(percent * 100 + 0.5) .. "%|r"
elseif percent < 1 then
tooltip = tooltip .. colorNeg .. " x" .. math.floor(percent * 100 + 0.5) .. "%|r"
end
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 BenillaPaperDollFrame_SetRangedAttackPower(unit, prefix)
if unit == "pet" then return end
unit = unit or "player"
prefix = prefix or "BenillaCharacter"
local frame = getglobal(prefix .. "RangedAttackPowerFrame")
local text = getglobal(prefix .. "RangedAttackPowerFrameStatText")
local paperDoll = getglobal("BenillaPaperDollFrame")
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)
BenillaPaperDollFormatStat(RANGED_ATTACK_POWER, base, posBuff, negBuff, frame, text)
frame.tooltipSubtext = format(RANGED_ATTACK_POWER_TOOLTIP,
math.max(base + posBuff + negBuff, 0) / BENILLA_ATTACK_POWER_MAGIC_NUMBER)
end
-- ref PaperDollFormatStat (l.804-833).
function BenillaPaperDollFormatStat(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 (l.786-802) — `this` (the ref's ambient global) becomes an
-- explicit `button` param here, matching this codebase's own house convention (Merchant/
-- Bag/QuestLog all thread `self` explicitly rather than lean on the engine's `this`
-- global, which DOES exist — `benilla-ui/src/script/event.rs` — but isn't this codebase's
-- idiom).
function BenillaPaperDollStatTooltip(button, stat)
-- 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.
local _, classFileName = UnitClass("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 BenillaCharacterDamageFrame_OnEnter(button)
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 BenillaCharacterRangedDamageFrame_OnEnter(button)
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: shift/ctrl are named deferrals (chat-link insert / DressUpItemLink, header
-- comment) — recognized and done, not fallen 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
return -- DressUpItemLink: named deferral, no dressing room yet
elseif IsShiftKeyDown() and not ignoreModifiers then
return -- chat-link insert: named deferral
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
-- BenillaCharacterModelFrame; `frame.facing` stands in for the ref's `model.rotation`.
function BenillaPaperDollModel_OnLoad(frame)
frame.facing = 0.61 -- ref UIParent.lua:1422, the literal default facing
BenillaPaperDollModel_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
BenillaPaperDollModel_SetFacing(frame.facing)
PlaySound("igInventoryRotateCharacter")
end
function BenillaPaperDollModel_RotateRight(frame)
frame.facing = frame.facing + 0.03
BenillaPaperDollModel_SetFacing(frame.facing)
PlaySound("igInventoryRotateCharacter")
end
-- The held-button continuous spin (ref Model_OnUpdate, UIParent.lua:1444-1462,
-- ROTATIONS_PER_SECOND=0.5) needs GetButtonState()=="PUSHED" — no such method exists on our
-- Button (grepped) — so this ships CLICK-ONLY; no OnUpdate wired on the model frame.
-- 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 BenillaPaperDollFrame_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="BenillaCharacterFrame" 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="BenillaCharacterFramePortrait">
<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="BenillaCharacterNameFrame">
<Size><AbsDimension x="109" y="12"/></Size>
<Anchors><Anchor point="CENTER"><Offset><AbsDimension x="6" y="232"/></Offset></Anchor></Anchors>
<Layers>
<Layer>
<FontString name="BenillaCharacterNameText" 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 BenillaCharacterNameFrame above uses,
transcribed from the ref): the window's own art is BenillaPaperDollFrame'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="BenillaCharacterFrameCloseButton">
<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("BenillaCharacterFrame"))</OnClick>
</Scripts>
</Button>
<!-- The tab row (ref l.79-96 — CENTER of BOTTOMLEFT +60,+62 — tab 2 chained off tab 1,
MerchantFrame.xml's own Merchant/Buyback LEFT-of-RIGHT -16 offset). DEVIATION
flagged (decision 0437 phase 4): the real client's Skills tab is 4th of five
(Character/Pet/Reputation/Skills/Honor, ref l.79-168); ours renders 2nd until
Pet/Reputation/Honor land (the "tabs arrive with their pages" precedent, 0138/0208).
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). -->
<Button name="BenillaCharacterFrameTab1" 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="BenillaCharacterFrameTab2" inherits="CharacterFrameTabButtonTemplate" text="Skills">
<Anchors><Anchor point="LEFT" relativeTo="BenillaCharacterFrameTab1" relativePoint="RIGHT"><Offset><AbsDimension x="-16" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnClick>BenillaCharacterFrameTab_OnClick(2)</OnClick></Scripts>
</Button>
<!-- The paper-doll page (ref PaperDollFrame, ref-PaperDollFrame.xml:124-1059). No
setAllPoints="true" (frame-level setAllPoints is a no-op in this engine, this file's
header comment) — sized to exactly fill the parent instead, the same effect. -->
<Frame name="BenillaPaperDollFrame">
<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="BenillaCharacterLevelText" inherits="GameFontNormalSmall">
<Anchors>
<Anchor point="TOP" relativeTo="BenillaCharacterNameText" 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="BenillaCharacterModelFrame">
<Size><AbsDimension x="233" y="224"/></Size>
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="65" y="-78"/></Offset></Anchor></Anchors>
<Layers>
<Layer level="ARTWORK">
<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. Held-spin OMITTED
(this file's header comment: no GetButtonState) — click-only, so no
RegisterForClicks("LeftButtonDown",...) either (default LeftButtonUp
suffices). -->
<Button name="BenillaCharacterModelFrameRotateLeftButton">
<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>
<OnClick>BenillaPaperDollModel_RotateLeft(getglobal("BenillaCharacterModelFrame"))</OnClick>
</Scripts>
</Button>
<Button name="BenillaCharacterModelFrameRotateRightButton">
<Size><AbsDimension x="35" y="35"/></Size>
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCharacterModelFrameRotateLeftButton" 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>
<OnClick>BenillaPaperDollModel_RotateRight(getglobal("BenillaCharacterModelFrame"))</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)
BenillaPaperDollModel_OnLoad(self)
BenillaSetBoothTexture(getglobal(self:GetName() .. "Texture"), "paperdoll")
</OnLoad>
<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="BenillaCharacterAttributesFrame">
<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="BenillaPlayerStatBackgroundTop" 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="BenillaPlayerStatBackgroundMiddle" file="Interface\PaperDollInfoFrame\UI-Character-StatBackground">
<Size><AbsDimension x="115" y="53"/></Size>
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaPlayerStatBackgroundTop" relativePoint="BOTTOMLEFT"/></Anchors>
<TexCoords left="0" right="0.8984375" top="0.125" bottom="0.1953125"/>
</Texture>
<Texture name="BenillaPlayerStatBackgroundBottom" file="Interface\PaperDollInfoFrame\UI-Character-StatBackground">
<Size><AbsDimension x="115" y="16"/></Size>
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaPlayerStatBackgroundMiddle" relativePoint="BOTTOMLEFT"/></Anchors>
<TexCoords left="0" right="0.8984375" top="0.484375" bottom="0.609375"/>
</Texture>
<Texture name="BenillaMeleeAttackBackgroundTop" 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="BenillaMeleeAttackBackgroundMiddle" file="Interface\PaperDollInfoFrame\UI-Character-StatBackground">
<Size><AbsDimension x="115" y="12"/></Size>
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaMeleeAttackBackgroundTop" relativePoint="BOTTOMLEFT"/></Anchors>
<TexCoords left="0" right="0.8984375" top="0.125" bottom="0.1953125"/>
</Texture>
<Texture name="BenillaMeleeAttackBackgroundBottom" file="Interface\PaperDollInfoFrame\UI-Character-StatBackground">
<Size><AbsDimension x="115" y="16"/></Size>
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaMeleeAttackBackgroundMiddle" relativePoint="BOTTOMLEFT"/></Anchors>
<TexCoords left="0" right="0.8984375" top="0.484375" bottom="0.609375"/>
</Texture>
<Texture name="BenillaRangedAttackBackgroundTop" file="Interface\PaperDollInfoFrame\UI-Character-StatBackground">
<Size><AbsDimension x="115" y="16"/></Size>
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaMeleeAttackBackgroundBottom" 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="BenillaRangedAttackBackgroundMiddle" file="Interface\PaperDollInfoFrame\UI-Character-StatBackground">
<Size><AbsDimension x="115" y="11"/></Size>
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaRangedAttackBackgroundTop" relativePoint="BOTTOMLEFT"/></Anchors>
<TexCoords left="0" right="0.8984375" top="0.125" bottom="0.1953125"/>
</Texture>
<Texture name="BenillaRangedAttackBackgroundBottom" file="Interface\PaperDollInfoFrame\UI-Character-StatBackground">
<Size><AbsDimension x="115" y="16"/></Size>
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaRangedAttackBackgroundMiddle" 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 (BenillaPaperDollFrame_OnLoad /
_SetStats — see the former's header comment); instances declare no
regions of their own, same as the ref's. -->
<Frame name="BenillaCharacterStatFrame1" inherits="BenillaStatRowTemplate">
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="6" y="-3"/></Offset></Anchor></Anchors>
<Scripts>
<OnLoad>self:EnableMouse(true)</OnLoad>
<OnEnter>BenillaPaperDollStatTooltip(self, "STRENGTH")</OnEnter>
<OnLeave>GameTooltip:Hide()</OnLeave>
</Scripts>
</Frame>
<Frame name="BenillaCharacterStatFrame2" inherits="BenillaStatRowTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCharacterStatFrame1" relativePoint="BOTTOMLEFT"/></Anchors>
<Scripts>
<OnLoad>self:EnableMouse(true)</OnLoad>
<OnEnter>BenillaPaperDollStatTooltip(self, "AGILITY")</OnEnter>
<OnLeave>GameTooltip:Hide()</OnLeave>
</Scripts>
</Frame>
<Frame name="BenillaCharacterStatFrame3" inherits="BenillaStatRowTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCharacterStatFrame2" relativePoint="BOTTOMLEFT"/></Anchors>
<Scripts>
<OnLoad>self:EnableMouse(true)</OnLoad>
<OnEnter>BenillaPaperDollStatTooltip(self, "STAMINA")</OnEnter>
<OnLeave>GameTooltip:Hide()</OnLeave>
</Scripts>
</Frame>
<Frame name="BenillaCharacterStatFrame4" inherits="BenillaStatRowTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCharacterStatFrame3" relativePoint="BOTTOMLEFT"/></Anchors>
<Scripts>
<OnLoad>self:EnableMouse(true)</OnLoad>
<OnEnter>BenillaPaperDollStatTooltip(self, "INTELLECT")</OnEnter>
<OnLeave>GameTooltip:Hide()</OnLeave>
</Scripts>
</Frame>
<Frame name="BenillaCharacterStatFrame5" inherits="BenillaStatRowTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCharacterStatFrame4" relativePoint="BOTTOMLEFT"/></Anchors>
<Scripts>
<OnLoad>self:EnableMouse(true)</OnLoad>
<OnEnter>BenillaPaperDollStatTooltip(self, "SPIRIT")</OnEnter>
<OnLeave>GameTooltip:Hide()</OnLeave>
</Scripts>
</Frame>
<!-- Armor (ref l.492-511), chained off Stat5's BOTTOMLEFT. -->
<Frame name="BenillaCharacterArmorFrame" inherits="BenillaStatRowTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCharacterStatFrame5" 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="BenillaCharacterAttackFrame" inherits="BenillaStatRowTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCharacterAttributesFrame" 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="BenillaCharacterAttackPowerFrame" inherits="BenillaStatRowTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCharacterAttackFrame" 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="BenillaCharacterDamageFrame" inherits="BenillaStatRowTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCharacterAttackPowerFrame" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="1"/></Offset></Anchor></Anchors>
<Scripts>
<OnLoad>self:EnableMouse(true); self:SetWidth(99)</OnLoad>
<OnEnter>BenillaCharacterDamageFrame_OnEnter(self)</OnEnter>
<OnLeave>GameTooltip:Hide()</OnLeave>
</Scripts>
</Frame>
<!-- Ranged attack rating (ref l.575-594). -->
<Frame name="BenillaCharacterRangedAttackFrame" inherits="BenillaStatRowTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCharacterDamageFrame" 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="BenillaCharacterRangedAttackPowerFrame" inherits="BenillaStatRowTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCharacterRangedAttackFrame" 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="BenillaCharacterRangedDamageFrame" inherits="BenillaStatRowTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCharacterRangedAttackPowerFrame" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="1"/></Offset></Anchor></Anchors>
<Scripts>
<OnLoad>self:EnableMouse(true); self:SetWidth(99)</OnLoad>
<OnEnter>BenillaCharacterRangedDamageFrame_OnEnter(self)</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="BenillaCharacterResistanceFrame">
<Size><AbsDimension x="32" y="160"/></Size>
<Anchors>
<Anchor point="TOPRIGHT" relativeTo="BenillaPaperDollFrame" relativePoint="TOPLEFT"><Offset><AbsDimension x="297" y="-77"/></Offset></Anchor>
</Anchors>
<Frames>
<Frame name="BenillaMagicResFrame1" inherits="BenillaMagicResistanceFrameTemplate">
<Anchors><Anchor point="TOP" relativeTo="BenillaCharacterResistanceFrame" 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="BenillaMagicResText1" inherits="GameFontHighlightSmall" text="X">
<Anchors><Anchor point="BOTTOM"><Offset><AbsDimension x="0" y="3"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
</Layers>
</Frame>
<Frame name="BenillaMagicResFrame2" inherits="BenillaMagicResistanceFrameTemplate">
<Anchors><Anchor point="TOP" relativeTo="BenillaMagicResFrame1" 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="BenillaMagicResText2" inherits="GameFontHighlightSmall" text="X">
<Anchors><Anchor point="BOTTOM"><Offset><AbsDimension x="0" y="3"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
</Layers>
</Frame>
<Frame name="BenillaMagicResFrame3" inherits="BenillaMagicResistanceFrameTemplate">
<Anchors><Anchor point="TOP" relativeTo="BenillaMagicResFrame2" 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="BenillaMagicResText3" inherits="GameFontHighlightSmall" text="X">
<Anchors><Anchor point="BOTTOM"><Offset><AbsDimension x="0" y="3"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
</Layers>
</Frame>
<Frame name="BenillaMagicResFrame4" inherits="BenillaMagicResistanceFrameTemplate">
<Anchors><Anchor point="TOP" relativeTo="BenillaMagicResFrame3" 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="BenillaMagicResText4" inherits="GameFontHighlightSmall" text="X">
<Anchors><Anchor point="BOTTOM"><Offset><AbsDimension x="0" y="3"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
</Layers>
</Frame>
<Frame name="BenillaMagicResFrame5" inherits="BenillaMagicResistanceFrameTemplate">
<Anchors><Anchor point="TOP" relativeTo="BenillaMagicResFrame4" 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="BenillaMagicResText5" 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="BenillaCharacterHeadSlot" 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="BenillaCharacterNeckSlot" inherits="BenillaPaperDollItemSlotTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCharacterHeadSlot" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-4"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaPaperDollSlot_OnLoad(self, "NeckSlot")</OnLoad></Scripts>
</Button>
<Button name="BenillaCharacterShoulderSlot" inherits="BenillaPaperDollItemSlotTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCharacterNeckSlot" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-4"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaPaperDollSlot_OnLoad(self, "ShoulderSlot")</OnLoad></Scripts>
</Button>
<Button name="BenillaCharacterBackSlot" inherits="BenillaPaperDollItemSlotTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCharacterShoulderSlot" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-4"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaPaperDollSlot_OnLoad(self, "BackSlot")</OnLoad></Scripts>
</Button>
<Button name="BenillaCharacterChestSlot" inherits="BenillaPaperDollItemSlotTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCharacterBackSlot" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-4"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaPaperDollSlot_OnLoad(self, "ChestSlot")</OnLoad></Scripts>
</Button>
<Button name="BenillaCharacterShirtSlot" inherits="BenillaPaperDollItemSlotTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCharacterChestSlot" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-4"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaPaperDollSlot_OnLoad(self, "ShirtSlot")</OnLoad></Scripts>
</Button>
<Button name="BenillaCharacterTabardSlot" inherits="BenillaPaperDollItemSlotTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCharacterShirtSlot" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-4"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaPaperDollSlot_OnLoad(self, "TabardSlot")</OnLoad></Scripts>
</Button>
<Button name="BenillaCharacterWristSlot" inherits="BenillaPaperDollItemSlotTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCharacterTabardSlot" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-4"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaPaperDollSlot_OnLoad(self, "WristSlot")</OnLoad></Scripts>
</Button>
<Button name="BenillaCharacterHandsSlot" 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="BenillaCharacterWaistSlot" inherits="BenillaPaperDollItemSlotTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCharacterHandsSlot" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-4"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaPaperDollSlot_OnLoad(self, "WaistSlot")</OnLoad></Scripts>
</Button>
<Button name="BenillaCharacterLegsSlot" inherits="BenillaPaperDollItemSlotTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCharacterWaistSlot" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-4"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaPaperDollSlot_OnLoad(self, "LegsSlot")</OnLoad></Scripts>
</Button>
<Button name="BenillaCharacterFeetSlot" inherits="BenillaPaperDollItemSlotTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCharacterLegsSlot" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-4"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaPaperDollSlot_OnLoad(self, "FeetSlot")</OnLoad></Scripts>
</Button>
<Button name="BenillaCharacterFinger0Slot" inherits="BenillaPaperDollItemSlotTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCharacterFeetSlot" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-4"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaPaperDollSlot_OnLoad(self, "Finger0Slot")</OnLoad></Scripts>
</Button>
<Button name="BenillaCharacterFinger1Slot" inherits="BenillaPaperDollItemSlotTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCharacterFinger0Slot" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-4"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaPaperDollSlot_OnLoad(self, "Finger1Slot")</OnLoad></Scripts>
</Button>
<Button name="BenillaCharacterTrinket0Slot" inherits="BenillaPaperDollItemSlotTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCharacterFinger1Slot" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-4"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaPaperDollSlot_OnLoad(self, "Trinket0Slot")</OnLoad></Scripts>
</Button>
<Button name="BenillaCharacterTrinket1Slot" inherits="BenillaPaperDollItemSlotTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCharacterTrinket0Slot" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-4"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaPaperDollSlot_OnLoad(self, "Trinket1Slot")</OnLoad></Scripts>
</Button>
<Button name="BenillaCharacterMainHandSlot" 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="BenillaCharacterSecondaryHandSlot" inherits="BenillaPaperDollItemSlotTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCharacterMainHandSlot" relativePoint="TOPRIGHT"><Offset><AbsDimension x="5" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaPaperDollSlot_OnLoad(self, "SecondaryHandSlot")</OnLoad></Scripts>
</Button>
<Button name="BenillaCharacterRangedSlot" inherits="BenillaPaperDollItemSlotTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCharacterSecondaryHandSlot" 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="BenillaCharacterAmmoSlot">
<Size><AbsDimension x="27" y="27"/></Size>
<Anchors>
<Anchor point="LEFT" relativeTo="BenillaCharacterRangedSlot" 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>BenillaPaperDollFrame_OnLoad(self)</OnLoad>
<OnEvent>BenillaPaperDollFrame_OnEvent(self)</OnEvent>
<OnShow>BenillaPaperDollFrame_OnShow()</OnShow>
</Scripts>
</Frame>
</Frames>
<Scripts>
<OnLoad>BenillaCharacterFrame_OnLoad(self)</OnLoad>
<OnShow>BenillaCharacterFrame_OnShow()</OnShow>
<OnHide>BenillaCharacterFrame_OnHide()</OnHide>
</Scripts>
</Frame>
</Ui>