Files
benilla-octo/crates/benilla/assets/ui/CharacterFrame.xml
T
SamandClaude Fable 5 c1ba444119 the micro menu, the ping meter, and the PvP flag
- The main menu bar's two empty recesses fill. The micro menu — the eight arched
  buttons: character, spellbook, talents, quest log, socials, world map, main menu,
  help — sits in the dark panel between the page arrows and the bags, transcribed
  from the reference's own layout: the character button wears your portrait, a button
  holds itself down while its panel is up, and the talent button appears at level 10.
  The latency meter fills the one slot the bar art leaves transparent for exactly it,
  tinted green/yellow/red on the reference's 300/600 ms thresholds and drawn UNDER
  the bar art, seen through its window, the way 1.12 actually does it. The socials
  and main-menu buttons find their panels one snapshot later; help still waits.
- The UI explains itself on hover: 1.12 ships its detailed two-line tooltips ON — a
  FrameXML default, not the opt-in extra it is often remembered as — so the micro
  buttons, your own portrait, the XP bar, the five bag buttons and the ping meter
  all answer a hover with the reference's own explanations.
- The PvP flag, end to end: crossed swords on the player, target and party frames,
  driven by the two descriptor bits the wire actually carries, and `/pvp` speaks —
  the yellow toast plus the system chat line, the OFF sentence being the one that
  warns the flag lingers for five minutes. It really does linger: turning it off
  waits out the server's timer, which looks like a bug and is the reference.
- Quest markers stopped freezing. The grey `!` over a questgiver above your level
  goes gold the moment you ding, an NPC re-entering view is asked afresh instead of
  wearing a session-old answer, and the marker drops when the questgiver bit goes
  off — the re-ask triggers mirror the reference's own set, plus one named addition
  (your own level changing).
- Water and heights: swimming downhill no longer bounces between swim and fall ten
  times a second — the surface line is a two-sided constraint, so the swimmer
  settles with the river instead of being left behind by it. And collision height
  is per-unit now, read from the unit's own model: a gnome used to swim with a
  third of a yard of water over her head because five depth lines were quoting a
  human's number.
- A round of pixel honesty in text and chat: the newest chat line no longer wears a
  wider drop shadow than every line above it (shadows move in whole pixels now),
  keeps its descenders, and hover boxes stop painting over the chat body. The chat
  input's history browse survives programmatic edits, a failed script chunk warns
  instead of vanishing, and recall deepens to 64 lines. Idle animation got honest
  too: a model's idle is Stand resolved through its own lookup table — never
  file order, which had the duel flag bobbing on its spawn clip — and an armed idle
  is culled on the box its animation authors, not the bind pose, so the flag stops
  vanishing at most camera angles.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 19:17:47 +07:00

1805 lines
123 KiB
XML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<Ui>
<!--
benilla's character window (decision 0208, phase 1a display + phase 1b equip interactions
— the C-key window, paper doll first):
the round player portrait + name + level line + close button + the five-tab row, dressed
from the real 1.12 `CharacterFrame.xml`/`.lua` + `PaperDollFrame.xml`/`.lua` +
`CharacterFrameTemplates.xml` + the `Model_*` rotate helpers (`UIParent.lua:1421-1462`) —
extracted from the MPQ chain to session scratch this session (director-local reference
artifacts, never committed — the 0068/0138 pattern; line cites below are into that
extraction's own `ref-CharacterFrame.xml`/`.lua`, `ref-PaperDollFrame.xml`/`.lua`,
`ref-UIParent.lua`, `ref-GlobalStrings.lua`). `CharacterFrameTabButtonTemplate` is ALREADY
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) —
the first two are CLOSED, kept only because the workarounds below still read as if they held:
- `HitRectInsets` (both `BenillaCharacterFrame` and `BenillaPaperDollFrame`, ref's identical
0/30/0/45 on both) was a loader no-op when this file was written; it landed with the
micro menu (decision 0655) and now really insets the mouse rect.
- `enableMouse`/`setAllPoints` (frame-level) were no-ops on `<Frame>` too; both are wired
now (`loader/geometry.rs`'s `apply_attrs`/`apply_anchors`, and
`loader/tests.rs::frame_set_all_points_pins_to_parent`). `BenillaPaperDollFrame` still
carries an explicit `Size`+`TOPLEFT` anchor rather than the ref's `setAllPoints="true"`
— exactly equivalent for a child sized to fill its parent, so it is left alone rather
than churned.
- A plain `<Frame>` isn't mouse-enabled by construction in this engine (only Button/
CheckButton/EditBox/ScrollingMessageFrame are — `widget/mod.rs`'s ctor comment, already
the reason `UnitFrames.xml` calls `EnableMouse(true)` on its own root frame) — every
hover-tooltip `<Frame>` below (the 5 attribute rows, armor/attack/damage/ranged rows, the
5 resistance frames — none of them Buttons) gets its own `self:EnableMouse(true)` in
`OnLoad` for this reason; the 19 item slots are Buttons and need no such call.
- `TEXT(x)` (the ref's localization-tooling identity macro) has no engine equivalent and
needs none — it's a build-time no-op wrapper in the real client, so every `TEXT(FOO)` call
below is transcribed as the bare `FOO` global.
- The font color codes (`HIGHLIGHT_FONT_COLOR_CODE`/`GREEN_FONT_COLOR_CODE`/
`RED_FONT_COLOR_CODE`/`FONT_COLOR_CODE_CLOSE`, `NORMAL_FONT_COLOR`/`HIGHLIGHT_FONT_COLOR`)
are defined by the real `Fonts.xml`'s own <Script> block (ref l.8-19 — extracted and
verified this session), so ours live in our `Fonts.xml` (loaded first), not here.
Sounds (decision 0090): `igCharacterInfoOpen`/`Close` on Show/Hide (ref `CharacterFrame.lua:
83/96`), `igCharacterInfoTab` on the tab click (ref `CharacterFrameTab_OnClick`, l.47),
`igInventoryRotateCharacter` on each rotate click (ref `UIParent.lua:1432/1441`).
-->
<Script><![CDATA[
-- ── GlobalStrings values this window uses (ref-GlobalStrings.lua, cited per line — the
-- `X = X or "..."` idiom matches CastingBar.xml's own convention: a real GlobalStrings.lua,
-- if one ever loads, wins over this fallback) ──────────────────────────────────────────────
CHARACTER = CHARACTER or "Character" -- :481
SKILLS = SKILLS or "Skills" -- :3477 (decision 0437 phase 4, the Skills tab label)
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): Show*TextStatusBarText/ShowWatchedReputationBarText
-- OMITTED — no unit-frame bar-text-mode toggle or reputation-bar-text mechanism exists in
-- this engine. UpdateMicroButtons is LIVE as of the micro-menu slice (MicroMenu.xml); it was
-- omitted only for want of a micro bar. UnitPVPName -> plain UnitName (this file's header
-- comment: PVP rank is a named deferral).
function BenillaCharacterFrame_OnShow()
if UpdateMicroButtons then UpdateMicroButtons() end
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 — UpdateMicroButtons live (MicroMenu.xml), the bar-text pair still OMITTED for
-- the same reason as above. 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()
if UpdateMicroButtons then UpdateMicroButtons() end
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
-- ref Model_OnUpdate (UIParent.lua:1444-1462) — the HELD-button continuous spin, which is
-- where a rotate button's actual feel lives: half a turn per second (ROTATIONS_PER_SECOND,
-- UIParent.lua:2), against which the 0.03 click nudge above is a ~1.7° tap. Every 1.12 model
-- pane wires it (PaperDollFrame l.222-224, DressUpFrame.xml:202-204, TabardFrame.xml:406-408).
-- It used to be omitted here for want of `GetButtonState` — that method now answers "PUSHED"
-- for a LIVE mouse-held button (decision 0288 P3), so the ref's own loop ships verbatim.
-- Verbatim INCLUDES the ref's sign quirk: the held branches run OPPOSITE to the click helpers
-- (held-LEFT adds, held-RIGHT subtracts). Not a transcription slip — quoted, not re-derived.
function BenillaPaperDollModel_OnUpdate(frame, elapsed)
local step = elapsed * 2 * PI * ROTATIONS_PER_SECOND
if getglobal(frame:GetName() .. "RotateLeftButton"):GetButtonState() == "PUSHED" then
frame.facing = frame.facing + step
if frame.facing < 0 then
frame.facing = frame.facing + (2 * PI)
end
BenillaPaperDollModel_SetFacing(frame.facing)
end
if getglobal(frame:GetName() .. "RotateRightButton"):GetButtonState() == "PUSHED" then
frame.facing = frame.facing - step
if frame.facing > (2 * PI) then
frame.facing = frame.facing - (2 * PI)
end
BenillaPaperDollModel_SetFacing(frame.facing)
end
end
-- ref CharacterModelFrame_OnMouseUp (l.31-35): a plain left-click-up on the model pane
-- also auto-equips a held container item — the model pane doubles as a drop target even
-- without a drag, not just via OnReceiveDrag. AutoEquipCursorItem is already a no-op for
-- every non-fitting/non-container payload (cursor.rs's own contract), so no CursorHasItem
-- guard is needed here.
function BenillaPaperDollModel_OnMouseUp(frame, button)
if button == "LeftButton" then
AutoEquipCursorItem()
end
end
]]></Script>
<!--
The shared equipment-slot button (ref PaperDollItemSlotButtonTemplate inherits
ItemButtonTemplate, ref-PaperDollFrame.xml:4-38 — ItemButtonTemplate's own XML isn't part of
this session's extraction, so its shape is taken from this codebase's own prior
reconstruction of it, BagFrame.xml's BenillaBagSlotTemplate: 37x37, a BACKGROUND icon fill,
an OVERLAY count, the 64x64 UI-Quickslot2 ring centered (0,-1), UI-Quickslot-Depress pushed,
ButtonHilight-Square ADD highlight — duplicated here rather than cross-file inherited so this
window's slot behavior (UNIT_INVENTORY_CHANGED refresh, tooltip) stays self-contained in one
file, the same posture Merchant's own item-row template takes vs. reusing Bag's).
Cooldown ($parentCooldown, CooldownFrameTemplate) OMITTED — no cooldown feed/engine
affordance exists yet. OnClick + the drag trio land THIS phase (1b) — the NormalTexture
ring below doubles as the CURSOR_UPDATE highlight's vertex-tint target (this file's header
comment: an emulation, no true LockHighlight/UnlockHighlight exists).
-->
<Button name="BenillaPaperDollItemSlotTemplate" virtual="true">
<Size><AbsDimension x="37" y="37"/></Size>
<Layers>
<Layer level="BACKGROUND">
<Texture name="$parentIconTexture"/>
</Layer>
<Layer level="OVERLAY">
<FontString name="$parentCount" inherits="NumberFontNormal" justifyH="RIGHT">
<Anchors>
<Anchor point="BOTTOMRIGHT" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="-5" y="2"/></Offset></Anchor>
</Anchors>
</FontString>
</Layer>
</Layers>
<NormalTexture file="Interface\Buttons\UI-Quickslot2">
<Size><AbsDimension x="64" y="64"/></Size>
<Anchors>
<Anchor point="CENTER" relativePoint="CENTER"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor>
</Anchors>
</NormalTexture>
<PushedTexture file="Interface\Buttons\UI-Quickslot-Depress"/>
<HighlightTexture file="Interface\Buttons\ButtonHilight-Square" alphaMode="ADD"/>
<Scripts>
<OnClick>BenillaPaperDollSlot_OnClick(self, arg1)</OnClick>
<OnEnter>BenillaPaperDollSlot_OnEnter(self)</OnEnter>
<OnLeave>GameTooltip:Hide()</OnLeave>
<OnEvent>BenillaPaperDollSlot_OnEvent(self)</OnEvent>
<OnUpdate>BenillaPaperDollSlot_OnUpdate(self)</OnUpdate>
<!-- The reference routes both drag entries to the plain click path with modifiers
suppressed (PaperDollFrame.xml:20-25 — PickupInventoryItem, ignoring
shift/ctrl) — the bag slots' own precedent (BagFrame.xml). -->
<OnDragStart>BenillaPaperDollSlot_OnClick(self, "LeftButton", true)</OnDragStart>
<OnReceiveDrag>BenillaPaperDollSlot_OnClick(self, "LeftButton", true)</OnReceiveDrag>
</Scripts>
</Button>
<!--
The ref defines TWO byte-identical templates for the attribute/stat rows — StatFrameTemplate
(ref-PaperDollFrame.xml:39-71) and AttributeFrameTemplate (l.72-105): same 104x13 size, same
Label(GameFontNormalSmall,LEFT) + $parentStat(30x13,RIGHT)/$parentText(GameFontHighlightSmall,
RIGHT) shape — a semantic-only distinction in the real client (armor/attack rows "are stats",
the 5 primaries "are attributes"). Collapsed to ONE template here (a legitimate reuse — no
behavior differs). `self:EnableMouse(true)` in OnLoad: a plain Frame isn't mouse-enabled by
construction in this engine (this file's header comment) — every instance below needs it to
receive its own OnEnter/OnLeave hover.
-->
<Frame name="BenillaStatRowTemplate" virtual="true">
<Size><AbsDimension x="104" y="13"/></Size>
<Layers>
<Layer level="BACKGROUND">
<FontString name="$parentLabel" inherits="GameFontNormalSmall">
<Anchors><Anchor point="LEFT"/></Anchors>
</FontString>
</Layer>
</Layers>
<Frames>
<Frame name="$parentStat">
<Size><AbsDimension x="30" y="13"/></Size>
<Anchors><Anchor point="RIGHT"/></Anchors>
<Layers>
<Layer level="BACKGROUND">
<FontString name="$parentText" inherits="GameFontHighlightSmall" justifyH="RIGHT">
<Anchors><Anchor point="RIGHT"/></Anchors>
</FontString>
</Layer>
</Layers>
</Frame>
</Frames>
</Frame>
<!--
The magic-resistance icon frame (ref MagicResistanceFrameTemplate, ref-PaperDollFrame.xml:
106-123): 32x29, a generic tooltip OnEnter reading self.tooltip/self.tooltipSubtext (both
filled by 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). Sized to
exactly fill the parent instead of the ref's setAllPoints="true" — the same effect
(see this file's header: frame-level setAllPoints was a no-op when this was written
and is wired now; the explicit size is equivalent, so it stays). -->
<Frame name="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.
`RegisterForClicks("LeftButtonDown","LeftButtonUp")` is the ref's own
(l.241-243/l.263-265): OnClick fires on BOTH edges, so one tap nudges
twice (2 × 0.03), and holding hands the spin to the pane's OnUpdate. -->
<Button name="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>
<OnLoad>self:RegisterForClicks("LeftButtonDown", "LeftButtonUp")</OnLoad>
<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>
<OnLoad>self:RegisterForClicks("LeftButtonDown", "LeftButtonUp")</OnLoad>
<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>
<!-- ref l.221-224: the pane drives the held-button spin every frame. -->
<OnUpdate>BenillaPaperDollModel_OnUpdate(self, arg1)</OnUpdate>
<OnMouseUp>BenillaPaperDollModel_OnMouseUp(self, arg1)</OnMouseUp>
<OnReceiveDrag>AutoEquipCursorItem()</OnReceiveDrag>
</Scripts>
</Frame>
<!-- The attributes block (ref CharacterAttributesFrame, l.274-405): 230x78 at
TOPLEFT (67,-291), the 9-piece UI-Character-StatBackground plate (3 stacked
3-piece columns: player-stat / melee / ranged) behind the rows. -->
<Frame name="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>