- forty files that carried the reference's literal Lua are rewritten as our own expression of the behaviour — worst identical run 61 lines down to 7 — and the README now says exactly that - the overlap gate measures the code itself across all four reference corpora, and ratchets: the number may only go down - a lost session ends at the login screen instead of silently re-authenticating and stealing the account back - a building's skybox is a real model: Caverns of Time's white ceiling becomes its painted nebula and additive stars Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2115 lines
126 KiB
XML
2115 lines
126 KiB
XML
<Ui>
|
||
<!--
|
||
benilla's player + target unit frames (decision 0068 slice-1 game shell), RE-SKINNED to the
|
||
real 1.12 UI-TargetingFrame art + geometry (dressing arc, decision 0084). The geometry below
|
||
is QUOTED from the real FrameXML (scratchpad ref-PlayerFrame.xml, ref-TargetFrame.xml; line
|
||
citations inline), turned onto our own snapshot → Unit* → event data flow. The player and
|
||
target are horizontal MIRRORS of one shared art (Interface\TargetingFrame\UI-TargetingFrame):
|
||
the player samples it flipped (portrait window on the LEFT), the target un-flipped (window on
|
||
the RIGHT). The mirror is a `<TexCoords left> right`; it reaches the vertex buffer un-normalized
|
||
via the engine's UvRect (see crates/benilla/src/ui_pass.rs).
|
||
|
||
LAYERING (matches the ref's structure). The draw order must be trough < bars < ring art < text:
|
||
the frame's metal well-edges overlap the bar edges so the bars read RECESSED, not pasted on top.
|
||
The ref gets this with a child `TargetFrameTextureFrame` (ring art) and `SetFrameLevel(bar,
|
||
textureFrame-1)` to push the bars beneath it, and we do the same, in BenillaUnitFrame_OnLoad:
|
||
the parent holds trough + portrait (+ target's name plate) in BACKGROUND; the
|
||
`$parentHealthBar`/`$parentPowerBar` StatusBars sit at the PARENT's frame level; and
|
||
`$parentTextureFrame` (232×100, filling the parent) holds the ring art (BACKGROUND) + name/level/dead text
|
||
(OVERLAY) one level ABOVE them, so it draws over the bar edges and the portrait.
|
||
|
||
That SetFrameLevel is load-bearing, and this file used to do without it. Until decision 0884
|
||
the bars were held under the ring art by DECLARING `$parentTextureFrame` after them, leaning on
|
||
the old draw key's insertion-order tie-break between siblings at one `(strata, level)`. 0884
|
||
pinned the real law at the binary: the draw LAYER is bucket-wide and outranks the frame, so
|
||
siblings interleave by layer and the bars' ARTWORK fills painted straight over the TextureFrame's
|
||
BACKGROUND ring art (the frames read as pasted-on slabs — director report, 2026-08-02). Frame
|
||
level is the one term above the layer, which is exactly why the reference spends a level here.
|
||
Do not "simplify" this back into declaration order. The ring's art is a THIN
|
||
band with transparent corners (not an opaque surround), so the square is hidden by the portrait
|
||
itself — set via SetPortraitToTexture, masked to its inscribed circle (the client's portraits are
|
||
always round: a model render or a circular-cropped icon), so no corner reaches the ring's gap. The
|
||
text lives under the TextureFrame, so its globals carry the `…TextureFrame…` name prefix (OnLoad).
|
||
The shared black `…Background` trough sits behind the unfilled bar portion.
|
||
|
||
DATA FLOW UNCHANGED (pin: re-anchor + re-skin, don't rewrite the eventing): BenillaUnitFrame_*
|
||
handlers, the power-color table, hide-on-nonexistent-unit. Names resolve asynchronously through
|
||
the name-query cache (UNIT_NAME_UPDATE repaints). A powerless unit (maxPower 0) hides its power
|
||
bar, as the real frames do. The target frame's "DEAD" text shows when the target's health hits
|
||
zero (ref TargetFrame_CheckDead, ref-TargetFrame.lua l.220-228).
|
||
|
||
DEVIATIONS (noted, not bugs):
|
||
- Portrait: the live model bake (SetPortraitTexture(region, unit) → crates/benilla/src/portrait.rs),
|
||
the modern high-res 2D take on the ref's low-res model snapshot (director's call; wow-re
|
||
portrait RE §5-verified). Circle-cut at draw (ui_quad.wgsl, the ref's stencil shape); the
|
||
TemporaryPortrait stand-in shows while a model streams in; the bake poses a fresh
|
||
instance at Stand through the model's authored portrait camera, helms/shoulders/held
|
||
items riding their bones (portrait/).
|
||
- Bar value FontStrings (ref-PlayerFrame.xml l.79-96) are omitted — the real client hides them
|
||
unless the statusText CVar is set; we match the default look.
|
||
- TargetFrameNameBackground (the level plate) is faction-tinted (TargetFrame_CheckFaction,
|
||
ref-TargetFrame.lua l.144-191). A PLAYER-controlled target takes the player branch (red when
|
||
mutually hostile, yellow attackable-not-hostile, green when we can assist them but they are
|
||
PvP-flagged, else blue — a player never reads the *reaction* green); an NPC
|
||
takes the reaction swatch, resolved from the same faction-template / reputation decode the
|
||
selection ring uses (ui_unit.rs → UnitState.reaction → UnitReaction). The player branch's
|
||
two middle legs — yellow "attackable but not hostile" (l.158) and green "assist +
|
||
PvP-flagged" (l.163) — landed with the PvP wire (decision 0646); only the tapped-by-other
|
||
grey (l.176) is still deferred, on the tap wire. The ref gates the player
|
||
branch on UnitPlayerControlled (which also covers pets); we resolve its player half via
|
||
UnitIsPlayer, so a player-controlled PET takes the NPC reaction branch until that flag lands.
|
||
- The classification border swap (elite/rare art, ref-TargetFrame.lua l.205-218) LANDED with
|
||
decision 0782 — UnitClassification is bound and TargetFrame_CheckClassification
|
||
below runs the ref's branch table. Two things worth knowing at this altitude. The rank
|
||
`UnitClassification` reads is the client's GATED rank (`0x605620`): a pet or charmed unit
|
||
reports rank 0, so an enslaved elite wears the plain border, and because the tooltip's
|
||
ELITE/BOSS word and UnitLevel's world-boss skull read the same getter, the gate lives at
|
||
the app's single write (ui_unit.rs enrich_unit), not per reader. And UNIT_CLASSIFICATION_
|
||
CHANGED is the repaint wire, edge-fired on that rank — without it the border would only be
|
||
right on re-target, since Update is otherwise the sole caller.
|
||
- The level slot runs the full CheckLevel law (ref-TargetFrame.lua l.119-142, re-expressed in
|
||
Update below): corpse or "can't tell" (UnitLevel ≤ 0) → the HighLevelTexture skull;
|
||
otherwise the number, GetDifficultyColor-tinted when attackable, gold when not.
|
||
- The PVP icon landed with decision 0646 (the three-branch law in BenillaUnitFrame_UpdatePvP).
|
||
Its `PlayerPVPIconHitArea` did NOT, deliberately: the ref's hit area draws nothing at the
|
||
default settings — GameTooltip_AddNewbieTip with SHOW_NEWBIE_TIPS off and noNormalText=1
|
||
(which is exactly how it is called) shows no tooltip at all — while an enableMouse frame
|
||
over the portrait would eat the player frame's own hover and right-click. Zero pixels for a
|
||
real regression is not a trade; it comes back with the newbie-tip cvar if that ever ships.
|
||
RE-VERIFIED in 0652, both legs: ref-GameTooltip.lua l.107-123 takes the `not noNormalText`
|
||
branch when tips are off (so `noNormalText=1` → nothing), and "newbie" does not appear
|
||
anywhere in WoW.exe's strings, so SHOW_NEWBIE_TIPS has no cvar producer to be on.
|
||
- OUT (no wire / future arcs): leader/master/rest/attack icons, group indicator,
|
||
hit indicators, raid target icon, playtime, target-of-target.
|
||
|
||
TARGET AURA ROWS (0255's deferred slice): 5 buffs / 16 debuffs under the frame, geometry +
|
||
law QUOTED from ref-TargetFrame.xml (button templates l.26-87, instances l.293-481) and
|
||
TargetDebuffButton_Update (ref-TargetFrame.lua l.263-387): friend ⇒ buff row first, else
|
||
debuffs first and buffs below row 2; icons 21px shrinking to 17 when the debuff count
|
||
reaches the wrap; dispel-tinted UI-Debuff-Overlays border; stack count above 1; NO timers
|
||
(the 1.12 wire carries no duration for another unit — byte-verified, decision 0257 B6). The
|
||
list UnitBuff/UnitDebuff enumerate for the target is the app's straight descriptor read,
|
||
ascending raw slot within the half — the verified other-unit law (0257) — so icons sit in
|
||
server slot order, not insertion order, exactly like the reference. The target-of-target
|
||
branches take the not-shown leg (wrap 6, buffs under Debuff7) until a ToT frame exists.
|
||
-->
|
||
<!-- ── TargetBuffButtonTemplate (ref TargetFrame.xml l.9-27, VERBATIM) ──────────────────────
|
||
A reference template addons INHERIT and we did not declare. `CT_UnitFrames` builds its own
|
||
`CT_UnitFrames_BuffButtonTemplate` on top of it (CT_TargetFrame.xml:4), and an unresolved
|
||
`inherits=` is SILENT — no load error, no session error, nothing in any column. The addon
|
||
simply gets a button with no size, no icon region and no tooltip scripts, and nothing
|
||
anywhere says so. That invisibility is why this row only ever appeared in the report's
|
||
missing-TEMPLATES ranking.
|
||
|
||
Transcribed verbatim because every piece of it already resolves here: `GameTooltip:SetOwner`
|
||
and `GameTooltip:SetUnitBuff` are both ours, and the icon is a plain `setAllPoints` texture.
|
||
The `$parentIcon` name matters — that is what a consumer getglobals to set the texture. -->
|
||
<Button name="TargetBuffButtonTemplate" virtual="true">
|
||
<Size><AbsDimension x="21" y="21"/></Size>
|
||
<Layers>
|
||
<Layer level="ARTWORK">
|
||
<Texture name="$parentIcon" setAllPoints="true"/>
|
||
</Layer>
|
||
</Layers>
|
||
<Scripts>
|
||
<OnEnter>
|
||
GameTooltip:SetOwner(this, "ANCHOR_BOTTOMRIGHT", 15, -25);
|
||
GameTooltip:SetUnitBuff("target", this.id);
|
||
</OnEnter>
|
||
<OnLeave>
|
||
GameTooltip:Hide();
|
||
</OnLeave>
|
||
</Scripts>
|
||
</Button>
|
||
|
||
<Script><![CDATA[
|
||
-- The right-click-menu explanations the detailed-tooltip fork shows in place of the unit
|
||
-- lines (BenillaUnitFrame_OnEnter below) — ref GlobalStrings.lua l.2993/3081/2754/2755
|
||
-- (enUS), fallback form: the real localized GlobalStrings.lua runs before this file at
|
||
-- runtime (ui_script/mod.rs), so these only fill in for the tests. CombatText.xml's shape.
|
||
PARTY_OPTIONS_LABEL = PARTY_OPTIONS_LABEL or "Party Options"
|
||
PLAYER_OPTIONS_LABEL = PLAYER_OPTIONS_LABEL or "Player Options"
|
||
NEWBIE_TOOLTIP_PARTYOPTIONS = NEWBIE_TOOLTIP_PARTYOPTIONS or "If you belong to a party, you can right-click your portrait to bring up the Party Options menu. This menu will allow you to quit the party or see what looting rules the party is using. A party leader can add or remove party members, as well as change the party's looting rules."
|
||
NEWBIE_TOOLTIP_PLAYEROPTIONS = NEWBIE_TOOLTIP_PLAYEROPTIONS or "Right-click to bring up special commands for interacting with another player. You can inspect their equipment, issue a party invite, initiate a trade, or challenge a player to a duel. A group leader can promote or remove that player from the group."
|
||
|
||
-- The client's PowerBarColor values, keyed by the UnitPowerType token.
|
||
BENILLA_POWER_COLORS = {
|
||
MANA = { r = 0.0, g = 0.0, b = 1.0 },
|
||
RAGE = { r = 1.0, g = 0.0, b = 0.0 },
|
||
FOCUS = { r = 1.0, g = 0.5, b = 0.25 },
|
||
ENERGY = { r = 1.0, g = 1.0, b = 0.0 },
|
||
HAPPINESS = { r = 0.0, g = 1.0, b = 1.0 },
|
||
}
|
||
|
||
-- ── The raid-target mark atlas (ref TargetFrame.lua l.460-463 + l.475-484) ──────────────
|
||
-- `Interface\TargetingFrame\UI-RaidTargetingIcons` is one 256x256 sheet of 4x4 64px cells,
|
||
-- in mark order: star, circle, diamond, triangle, moon, square, cross, skull. The helper
|
||
-- turns a 1..8 mark index into that cell's texcoords on a CALLER-SUPPLIED texture — it owns
|
||
-- no frame and reads no unit, which is why an addon can hand it a texture of its own.
|
||
--
|
||
-- Three corpus addons do, in two codebases (`Optional` and `oRA2` ship the same two files):
|
||
-- CustomNameplates options.lua:363 (its options preview)
|
||
-- Optional / oRA2 MainTank.lua:1216, PlayerTarget.lua:1472
|
||
-- We answer `GetRaidTargetIndex(unit)` already (0434 §6); this is the other half — what an
|
||
-- addon DRAWS with that number — and without it those lines raise while redrawing a mark.
|
||
--
|
||
-- KEPT WHOLE, INCLUDING THE REFERENCE'S OWN SLIP: the row is derived with
|
||
-- `/ RAID_TARGET_TEXTURE_ROWS` while the column uses `mod(.., RAID_TARGET_TEXTURE_COLUMNS)`.
|
||
-- Rows and columns are both 4, so the two agree for every index this sheet holds and the
|
||
-- output is right; "fixing" it to divide by COLUMNS computes the same coords today and would
|
||
-- quietly diverge from the client the day a caller changes a constant. These are globals in
|
||
-- the reference for exactly that reason — addons read them, and two of ours are addons that
|
||
-- draw their own marks.
|
||
RAID_TARGET_ICON_DIMENSION = 64
|
||
RAID_TARGET_TEXTURE_DIMENSION = 256
|
||
RAID_TARGET_TEXTURE_COLUMNS = 4
|
||
RAID_TARGET_TEXTURE_ROWS = 4
|
||
|
||
function SetRaidTargetIconTexture(texture, raidTargetIconIndex)
|
||
raidTargetIconIndex = raidTargetIconIndex - 1
|
||
local left, right, top, bottom
|
||
local coordIncrement = RAID_TARGET_ICON_DIMENSION / RAID_TARGET_TEXTURE_DIMENSION
|
||
left = mod(raidTargetIconIndex, RAID_TARGET_TEXTURE_COLUMNS) * coordIncrement
|
||
right = left + coordIncrement
|
||
top = floor(raidTargetIconIndex / RAID_TARGET_TEXTURE_ROWS) * coordIncrement
|
||
bottom = top + coordIncrement
|
||
texture:SetTexCoord(left, right, top, bottom)
|
||
end
|
||
|
||
-- The client's UnitReactionColor table (ref-TargetFrame.lua l.6-14), indexed by
|
||
-- UnitReaction 1..8 (hated..exalted): 1-2 red, 3 orange, 4 neutral yellow, 5-7 green. The
|
||
-- name-plate reaction tint (TargetFrame_CheckFaction) reads it — this is what makes a neutral
|
||
-- beast's plate yellow, a hostile's red, a friendly's green.
|
||
BENILLA_REACTION_COLORS = {
|
||
{ r = 1.0, g = 0.0, b = 0.0 },
|
||
{ r = 1.0, g = 0.0, b = 0.0 },
|
||
{ r = 1.0, g = 0.5, b = 0.0 },
|
||
{ r = 1.0, g = 1.0, b = 0.0 },
|
||
{ r = 0.0, g = 1.0, b = 0.0 },
|
||
{ r = 0.0, g = 1.0, b = 0.0 },
|
||
{ r = 0.0, g = 1.0, b = 0.0 },
|
||
}
|
||
|
||
-- Wire an instance to its unit token (called from each frame's OnLoad), cache its child
|
||
-- widgets, register the event set, and do the first paint. deadText is target-only
|
||
-- (getglobal returns nil for the player, which has no DEAD region) — guarded at use.
|
||
function BenillaUnitFrame_OnLoad(frame, unit)
|
||
frame.unit = unit
|
||
local name = frame:GetName()
|
||
frame.healthBar = getglobal(name .. "HealthBar")
|
||
frame.powerBar = getglobal(name .. "PowerBar")
|
||
-- The name/level/dead text live inside $parentTextureFrame (so they draw over the ring art
|
||
-- that draws over the bars), so their global names carry the TextureFrame prefix.
|
||
local tf = name .. "TextureFrame"
|
||
-- The recessed-bar look, the reference's own mechanism (ref-TargetFrame.lua l.32-34):
|
||
-- push the bars one frame level BELOW the ring art's TextureFrame. Frame level is the
|
||
-- only key term above the draw layer (decision 0884), so this is the only thing that can
|
||
-- hold a BACKGROUND ring texture over an ARTWORK bar fill. See the LAYERING note up top.
|
||
local textureFrameLevel = getglobal(tf):GetFrameLevel()
|
||
frame.healthBar:SetFrameLevel(textureFrameLevel - 1)
|
||
frame.powerBar:SetFrameLevel(textureFrameLevel - 1)
|
||
-- The on-bar numerals (TextStatusBar.xml, decision 1082; the switch that shows them
|
||
-- is 1140's Status Bar Text). Wiring is unconditional — a frame with no text regions
|
||
-- gets a no-op, since SetTextStatusBarText drops a nil text and UpdateTextString skips
|
||
-- a bar with no TextString — so the target frame is one FontString pair away from
|
||
-- having them the day its hover lands. `textLockable` is the REFERENCE's own split
|
||
-- and the only conditional here: 1.12 sets it on the player's two bars, the pet's two
|
||
-- and the XP bar, and on nothing else, which is exactly why Status Bar Text pins your
|
||
-- own numbers and your pet's but never the target's (decision 1143).
|
||
if TextStatusBar_Initialize then -- engine tests load file subsets
|
||
TextStatusBar_Initialize(frame.healthBar)
|
||
TextStatusBar_Initialize(frame.powerBar)
|
||
SetTextStatusBarText(frame.healthBar, getglobal(tf .. "HealthBarText"))
|
||
SetTextStatusBarText(frame.powerBar, getglobal(tf .. "PowerBarText"))
|
||
if unit == "player" then
|
||
frame.healthBar.textLockable = 1
|
||
frame.powerBar.textLockable = 1
|
||
end
|
||
end
|
||
-- The hover seam (1146): who this bar belongs to, and what its click means.
|
||
local click = TargetFrame_OnClick
|
||
if unit == "player" then
|
||
click = PlayerFrame_OnClick
|
||
end
|
||
frame.healthBar.owner = frame
|
||
frame.powerBar.owner = frame
|
||
frame.healthBar.ownerClick = click
|
||
frame.powerBar.ownerClick = click
|
||
frame.nameText = getglobal(tf .. "NameText")
|
||
frame.levelText = getglobal(tf .. "LevelText")
|
||
frame.deadText = getglobal(tf .. "DeadText")
|
||
-- Target-only (the "??" skull on the level slot); getglobal returns nil for the
|
||
-- player, guarded at use.
|
||
frame.highLevel = getglobal(tf .. "HighLevelTexture")
|
||
-- Target-only (the level/name plate); getglobal returns nil for the player, guarded at use.
|
||
frame.nameBackground = getglobal(name .. "NameBackground")
|
||
-- The live model portrait (the real API's global, ref-UnitFrame.lua): bind the portrait
|
||
-- region to this frame's unit token; the app renders that unit's model off-screen and feeds
|
||
-- the bake back into the region (crates/benilla/src/portrait.rs). The binding is a token, so
|
||
-- one call suffices — the app keeps the image current per token (it re-bakes on target /
|
||
-- model change), which is why the ref's UNIT_PORTRAIT_UPDATE re-call isn't needed here (the
|
||
-- event joins the surface when addon parity wants it). Until a bake lands (no unit / model
|
||
-- still loading) the circle is simply empty.
|
||
frame.portrait = getglobal(name .. "Portrait")
|
||
SetPortraitTexture(frame.portrait, unit)
|
||
-- The PvP flag icon (decision 0646), under the TextureFrame like the ring art it
|
||
-- draws over — see BenillaUnitFrame_UpdatePvP.
|
||
frame.pvpIcon = getglobal(tf .. "PVPIcon")
|
||
frame:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||
frame:RegisterEvent("UNIT_HEALTH")
|
||
frame:RegisterEvent("UNIT_MAXHEALTH")
|
||
frame:RegisterEvent("UNIT_LEVEL")
|
||
frame:RegisterEvent("UNIT_POWER_UPDATE")
|
||
frame:RegisterEvent("UNIT_MAXPOWER")
|
||
frame:RegisterEvent("UNIT_DISPLAYPOWER")
|
||
frame:RegisterEvent("UNIT_NAME_UPDATE")
|
||
-- The PvP icon's repaint wire (decision 0646 §2) — in the reference exactly the three
|
||
-- frames that draw the icon register it (PlayerFrame, TargetFrame, PartyMemberFrame)
|
||
-- and nothing else does.
|
||
frame:RegisterEvent("UNIT_FACTION")
|
||
frame:RegisterEvent("PLAYER_TARGET_CHANGED")
|
||
-- The aura rows and the classification border are target-only (ref TargetFrame_OnLoad
|
||
-- registers UNIT_AURA and UNIT_CLASSIFICATION_CHANGED; PlayerFrame registers neither,
|
||
-- and there is no CheckClassification for the player — your own frame never wears a
|
||
-- dragon). The ring-art region is the one the border swap re-textures, so only the
|
||
-- target caches the handle: its presence is what gates the swap in Update, the same
|
||
-- nil-handle idiom highLevel/nameBackground use above.
|
||
if unit == "target" then
|
||
frame:RegisterEvent("UNIT_AURA")
|
||
frame:RegisterEvent("UNIT_CLASSIFICATION_CHANGED")
|
||
frame.frameTexture = getglobal(tf .. "Texture")
|
||
end
|
||
-- The portrait hit indicator (decision 0576): only the player frame registers
|
||
-- UNIT_COMBAT in 1.12 (ref PlayerFrame_OnLoad — the target frame has no feedback
|
||
-- text). Ref: CombatFeedback_Initialize(PlayerHitIndicator, 30).
|
||
if unit == "player" then
|
||
frame.feedbackText = getglobal(tf .. "HitIndicator")
|
||
frame.feedbackFontHeight = 30
|
||
frame:RegisterEvent("UNIT_COMBAT")
|
||
-- The status flash (ref PlayerFrame_OnLoad, decision 1082): the resting zzz /
|
||
-- auto-attack swords under the portrait, the gold/red ring, and the pulse
|
||
-- counters. Handles cached like pvpIcon above; their presence is what gates
|
||
-- PlayerFrame_UpdateStatus in the shared Update.
|
||
frame.statusTexture = getglobal(tf .. "StatusTexture")
|
||
frame.attackBackground = getglobal(tf .. "AttackBackground")
|
||
frame.restIcon = getglobal(tf .. "RestIcon")
|
||
frame.attackIcon = getglobal(tf .. "AttackIcon")
|
||
frame.statusGlow = getglobal(name .. "StatusGlow")
|
||
frame.restGlow = getglobal(name .. "StatusGlowRestGlow")
|
||
frame.attackGlow = getglobal(name .. "StatusGlowAttackGlow")
|
||
frame.statusCounter = 0
|
||
frame.statusSign = -1
|
||
frame.attackBackground:SetVertexColor(0.8, 0.1, 0.1)
|
||
frame.attackBackground:SetAlpha(0.4)
|
||
-- Its four wires (ref PlayerFrame_OnLoad's registrations): auto-attack toggling
|
||
-- (ENTER/LEAVE_COMBAT), the aggro list (REGEN_DISABLED/ENABLED), and the rest
|
||
-- flag (UPDATE_RESTING); PLAYER_ENTERING_WORLD is registered above for all units.
|
||
frame:RegisterEvent("PLAYER_ENTER_COMBAT")
|
||
frame:RegisterEvent("PLAYER_LEAVE_COMBAT")
|
||
frame:RegisterEvent("PLAYER_REGEN_DISABLED")
|
||
frame:RegisterEvent("PLAYER_REGEN_ENABLED")
|
||
frame:RegisterEvent("PLAYER_UPDATE_RESTING")
|
||
end
|
||
-- A plain Frame isn't mouse-enabled by construction (only Button/EditBox are); enable it so
|
||
-- the hover tooltip fires (the real client's unit frames take the mouse to target/tooltip).
|
||
frame:EnableMouse(true)
|
||
UnitFrame_Update()
|
||
end
|
||
|
||
-- Hover: the engine's SetUnit — the byte-verified unit line law (decision 0276: name/
|
||
-- subtitle/level composition/PvP/Skinnable + the health bar), then the ref's reaction
|
||
-- recolor of line 1 (ref UnitFrame_OnEnter l.68-76: the FrameXML half of the split).
|
||
-- The plate goes to the DEFAULT corner, not beside the frame — ref UnitFrame_OnEnter
|
||
-- l.56 is GameTooltip_SetDefaultAnchor(GameTooltip, this): unit-frame tooltips live at
|
||
-- the screen's bottom-right like world hovers. (The TOOLTIP_UPDATE_TIME re-poll is not
|
||
-- ours: the engine's health watcher is the live half of it, and name/level don't drift
|
||
-- mid-hover yet.)
|
||
--
|
||
-- The DETAILED-TOOLTIP FORK (ref UnitFrame_OnEnter l.58-67, director-approved 0663) is the
|
||
-- surprising half: with tips on — 1.12's default, decision 0661 — the frame explains its
|
||
-- RIGHT-CLICK MENU instead of the unit, and returns BEFORE SetUnit, so the health/level
|
||
-- lines never render at all. Your own portrait always takes it; another player's takes it
|
||
-- whenever they're your target. The ref excepts PartyMemberFrame1-4 by name; ours are a
|
||
-- separate file with their own OnEnter (PartyFrame.xml), so there is nothing to except here.
|
||
--
|
||
-- One divergence, named: the ref gates the second branch on UnitPlayerControlled("target"),
|
||
-- which is true for a player OR a player's pet/charm. This engine has no such binding and no
|
||
-- flag to build one from — UnitIsPlayer is the nearest honest read, so a charmed mob or
|
||
-- another player's pet falls through to the unit tooltip where the reference would explain
|
||
-- the menu. The player case, which is the one anybody hovers, is exact.
|
||
-- ref UnitFrame_OnEnter l.55-57, ahead of everything else: the ONLY lit/grey cursor split
|
||
-- over a UI element in 1.12, and it is authored here in Lua rather than in the engine
|
||
-- (wow-re item-target-cursor-and-dropitemonunit.md 4.3). Bag and paper-doll slots have no
|
||
-- such branch, which is why they show the ordinary pointer.
|
||
-- A BAR's own hover (decision 1146; ref TextStatusBar's OnEnter/OnLeave on a mouse-enabled
|
||
-- bar). Two jobs, and the second is the one a naive transcription drops: reveal the numbers
|
||
-- while the pointer is on the bar, then hand the hover ON to the frame underneath, because
|
||
-- a mouse-enabled child swallows the parent's OnEnter — and with it the unit tooltip AND
|
||
-- the cast-cursor split that only exists over unit frames. Same for the click: the ref
|
||
-- routes its bars' OnMouseUp straight back to the frame's own handler (PlayerFrame.xml
|
||
-- l.304-306), which is what keeps a bar click targeting.
|
||
--
|
||
-- `owner`/`ownerClick` are stamped at load rather than read from GetParent(), because the
|
||
-- pet's bars are siblings of its TextureFrame, not children of the frame they belong to.
|
||
function BenillaUnitFrameBar_OnEnter(bar)
|
||
if TextStatusBar_UpdateTextString then
|
||
TextStatusBar_UpdateTextString(bar)
|
||
ShowTextStatusBarText(bar)
|
||
end
|
||
if bar.owner and BenillaUnitFrame_OnEnter and bar.owner.unit then
|
||
BenillaUnitFrame_OnEnter(bar.owner)
|
||
end
|
||
end
|
||
|
||
function BenillaUnitFrameBar_OnLeave(bar)
|
||
if HideTextStatusBarText then
|
||
HideTextStatusBarText(bar)
|
||
end
|
||
if bar.owner and BenillaUnitFrame_OnLeave and bar.owner.unit then
|
||
BenillaUnitFrame_OnLeave(bar.owner)
|
||
end
|
||
end
|
||
|
||
function BenillaUnitFrameBar_OnMouseUp(bar, button)
|
||
if bar.ownerClick then
|
||
bar.ownerClick(button)
|
||
end
|
||
end
|
||
|
||
-- The reference's names for the two unit-frame hover hooks, `this`-shaped, delegating to
|
||
-- ours. `TipBuddy.lua:2770-2773` is the exact idiom this exists for:
|
||
-- originalUnitFrame_OnEnter = UnitFrame_OnEnter
|
||
-- function UnitFrame_OnEnter() originalUnitFrame_OnEnter() ... end
|
||
-- Against a missing global that captured nil and raised on the first hover; against a
|
||
-- RENAMED one it would have called ours with no frame. See ActionBar.xml for the full note.
|
||
function UnitFrame_OnEnter()
|
||
BenillaUnitFrame_OnEnter(this)
|
||
end
|
||
|
||
function UnitFrame_OnLeave()
|
||
BenillaUnitFrame_OnLeave(this)
|
||
end
|
||
|
||
function BenillaUnitFrame_OnEnter(frame)
|
||
local unit = frame.unit
|
||
if SpellIsTargeting() then
|
||
if SpellCanTargetUnit(unit) then
|
||
SetCursor("CAST_CURSOR")
|
||
else
|
||
SetCursor("CAST_ERROR_CURSOR")
|
||
end
|
||
end
|
||
if not UnitExists(unit) then return end
|
||
GameTooltip_SetDefaultAnchor(GameTooltip, frame)
|
||
if SHOW_NEWBIE_TIPS == "1" then
|
||
if frame:GetName() == "PlayerFrame" then
|
||
BenillaTooltip_AddNewbieTipFor(frame, PARTY_OPTIONS_LABEL, 1.0, 1.0, 1.0,
|
||
NEWBIE_TOOLTIP_PARTYOPTIONS)
|
||
return
|
||
elseif UnitIsPlayer("target") and not UnitIsUnit("target", "player")
|
||
and not UnitIsUnit("target", "pet") then
|
||
BenillaTooltip_AddNewbieTipFor(frame, PLAYER_OPTIONS_LABEL, 1.0, 1.0, 1.0,
|
||
NEWBIE_TOOLTIP_PLAYEROPTIONS)
|
||
return
|
||
end
|
||
end
|
||
if GameTooltip:SetUnit(unit) then
|
||
getglobal("GameTooltipTextLeft1"):SetTextColor(GameTooltip_UnitColor(unit))
|
||
end
|
||
end
|
||
|
||
-- ref UnitFrame_OnLeave l.84-88: the fade is the tips-OFF branch. With detailed tips on the
|
||
-- plate HIDES outright — the ref's own pairing, and the right one: an explanation that
|
||
-- lingers on a ramp reads as a stuck tooltip, where a unit readout fading is the client's
|
||
-- signature. (Items Hide, units fade — that split still holds for the world mouseover.)
|
||
function BenillaUnitFrame_OnLeave(frame)
|
||
-- ref UnitFrame_OnLeave l.86: leaving a unit frame while still targeting goes to the
|
||
-- GREY cast cursor, not to the base one — you have left the only thing the armed spell
|
||
-- could have bound.
|
||
if SpellIsTargeting() then SetCursor("CAST_ERROR_CURSOR") end
|
||
if SHOW_NEWBIE_TIPS == "1" then
|
||
GameTooltip:Hide()
|
||
else
|
||
GameTooltip:FadeOut()
|
||
end
|
||
end
|
||
|
||
function UnitFrame_OnEvent(event)
|
||
local frame = this
|
||
-- UNIT_* events name their unit in arg1; ignore other units' updates.
|
||
if string.sub(event, 1, 5) == "UNIT_" and arg1 ~= frame.unit then
|
||
return
|
||
end
|
||
-- The hit-indicator feed (ref PlayerFrame_OnEvent's UNIT_COMBAT arm): no repaint —
|
||
-- the event carries feedback, not state.
|
||
if event == "UNIT_COMBAT" then
|
||
CombatFeedback_OnCombatEvent(frame, arg2, arg3, arg4, arg5)
|
||
return
|
||
end
|
||
-- The status flash's state arms (ref PlayerFrame_OnEvent l.100-115, decision 1082):
|
||
-- inCombat = auto-attack active, onHateList = on something's aggro list; a world
|
||
-- entry resets both (the ref's own PLAYER_ENTERING_WORLD arm). The repaint itself
|
||
-- rides the shared Update below — this file's posture (the PvP sound note).
|
||
if event == "PLAYER_ENTERING_WORLD" then
|
||
frame.inCombat = nil
|
||
frame.onHateList = nil
|
||
elseif event == "PLAYER_ENTER_COMBAT" then
|
||
frame.inCombat = 1
|
||
elseif event == "PLAYER_LEAVE_COMBAT" then
|
||
frame.inCombat = nil
|
||
elseif event == "PLAYER_REGEN_DISABLED" then
|
||
frame.onHateList = 1
|
||
elseif event == "PLAYER_REGEN_ENABLED" then
|
||
frame.onHateList = nil
|
||
end
|
||
UnitFrame_Update()
|
||
-- The player's flag-change sound (decision 0646 §2/§4). The reference plays
|
||
-- igPVPUpdate inside PlayerFrame_UpdatePvPStatus's two show branches, which it only
|
||
-- ever reaches from UNIT_FACTION; our repaint runs from the shared Update on every
|
||
-- event, so the sound rides the (edge-fired) event instead of the repaint — one
|
||
-- sound per real flag change, which is what the reference audibly does.
|
||
if event == "UNIT_FACTION" and frame.unit == "player" and frame.pvpShown then
|
||
PlaySound("igPVPUpdate")
|
||
end
|
||
end
|
||
|
||
-- The PvP flag icon (decision 0646 §4). The reference writes this law twice with
|
||
-- identical branches — PlayerFrame_UpdatePvPStatus (ref-PlayerFrame.lua l.55-79) and the
|
||
-- tail of TargetFrame_CheckFaction (ref-TargetFrame.lua l.193-202) — so it lives once
|
||
-- here. Branch ORDER is the reference's and it is load-bearing: free-for-all wins
|
||
-- outright (an FFA-flagged player shows the FFA skull even while ordinarily flagged), and
|
||
-- the faction leg needs BOTH a resolvable side and the flag. `frame.pvpShown` is what the
|
||
-- caller sounds off.
|
||
function BenillaUnitFrame_UpdatePvP(frame)
|
||
local icon = frame.pvpIcon
|
||
if not icon then return end
|
||
local unit = frame.unit
|
||
local texture
|
||
if UnitIsPVPFreeForAll(unit) then
|
||
texture = "Interface\\TargetingFrame\\UI-PVP-FFA"
|
||
else
|
||
local factionGroup = UnitFactionGroup(unit)
|
||
if factionGroup and UnitIsPVP(unit) then
|
||
texture = "Interface\\TargetingFrame\\UI-PVP-" .. factionGroup
|
||
end
|
||
end
|
||
frame.pvpShown = texture ~= nil
|
||
if texture then
|
||
icon:SetTexture(texture)
|
||
icon:Show()
|
||
else
|
||
icon:Hide()
|
||
end
|
||
end
|
||
|
||
-- ── Combat feedback: the portrait hit indicator (decision 0576) ──────────────────────
|
||
-- Our implementation of what ref CombatFeedback.lua specifies, on frame-param style (the
|
||
-- ref reads `this`). Timings, colors and size factors are its MEASURED values. The words
|
||
-- come from the GLOBALS, exactly as the ref builds this table (CombatFeedback.lua l.6-17:
|
||
-- CombatFeedbackText["MISS"] = TEXT(MISS)), so a localized install shows its own — the app
|
||
-- runs the real GlobalStrings.lua off the patch chain before this file (ui_script/mod.rs).
|
||
-- The enUS literals are the fallback for the test harnesses, which load no patch chain.
|
||
COMBATFEEDBACK_FADEINTIME = 0.2
|
||
COMBATFEEDBACK_HOLDTIME = 0.7
|
||
COMBATFEEDBACK_FADEOUTTIME = 0.3
|
||
|
||
CombatFeedbackText = {
|
||
INTERRUPT = INTERRUPT or "Interrupt",
|
||
MISS = MISS or "Miss",
|
||
RESIST = RESIST or "Resist",
|
||
DODGE = DODGE or "Dodge",
|
||
PARRY = PARRY or "Parry",
|
||
BLOCK = BLOCK or "Block",
|
||
EVADE = EVADE or "Evade",
|
||
IMMUNE = IMMUNE or "Immune",
|
||
DEFLECT = DEFLECT or "Deflect",
|
||
ABSORB = ABSORB or "Absorb",
|
||
REFLECT = REFLECT or "Reflect",
|
||
}
|
||
|
||
-- The indicator says one of two things: a NUMBER, when something moved a bar, or a WORD,
|
||
-- when nothing did. Everything below is that split. `scale` is a multiple of the frame's
|
||
-- own indicator height — 1 is "as authored" — and the multiples themselves are the
|
||
-- reference's measurements: half again for a big hit, three quarters for one that was
|
||
-- stopped, half for one that could never land.
|
||
|
||
-- The two events that move a bar the friendly way, and the colour each prints in. (A
|
||
-- WOUND prints a number too, but its colour depends on the school, so it is settled in
|
||
-- place below.)
|
||
local COMBAT_FEEDBACK_GAIN = {
|
||
HEAL = { r = 0.0, g = 1.0, b = 0.0 },
|
||
ENERGIZE = { r = 0.41, g = 0.8, b = 0.94 },
|
||
}
|
||
|
||
-- The words that do not print at the indicator's authored height. Everything else does.
|
||
local COMBAT_FEEDBACK_WORD_SCALE = {
|
||
IMMUNE = 0.5,
|
||
BLOCK = 0.75,
|
||
}
|
||
|
||
-- A wound that did no damage was stopped by one of three things, and in each case the
|
||
-- flag naming it is also the key of the word for it. Anything else was a plain miss.
|
||
local COMBAT_FEEDBACK_SOAKED = { ABSORB = 1, BLOCK = 1, RESIST = 1 }
|
||
|
||
-- What a zero-damage wound reads as: the word, and the size it prints at. Being stopped
|
||
-- reads small; missing outright reads full size, because a miss is the ordinary case.
|
||
local function CombatFeedback_Stopped(flags)
|
||
if COMBAT_FEEDBACK_SOAKED[flags] then
|
||
return CombatFeedbackText[flags], 0.75
|
||
end
|
||
return CombatFeedbackText["MISS"], 1.0
|
||
end
|
||
|
||
-- The size a landed blow reads at. A crushing blow is as loud as a critical; a glancing
|
||
-- one is as quiet as a soak.
|
||
local function CombatFeedback_HitScale(flags)
|
||
if flags == "CRITICAL" or flags == "CRUSHING" then
|
||
return 1.5
|
||
elseif flags == "GLANCING" then
|
||
return 0.75
|
||
end
|
||
return 1.0
|
||
end
|
||
|
||
function CombatFeedback_OnCombatEvent(frame, event, flags, amount, type)
|
||
local feedbackText = frame.feedbackText
|
||
if not feedbackText then return end
|
||
|
||
-- The default reading, which most events take unchanged: this event's own word, at the
|
||
-- authored height, in white. An event with no word at all prints nothing, which is the
|
||
-- honest thing to do with a feedback kind we do not have a string for.
|
||
local text = CombatFeedbackText[event]
|
||
local scale = 1.0
|
||
local r, g, b = 1.0, 1.0, 1.0
|
||
|
||
if event == "WOUND" then
|
||
if amount == 0 then
|
||
text, scale = CombatFeedback_Stopped(flags)
|
||
else
|
||
text = amount
|
||
scale = CombatFeedback_HitScale(flags)
|
||
-- A non-zero school means a spell landed it: spell damage prints yellow where
|
||
-- a physical hit prints white.
|
||
if type > 0 then
|
||
r, g, b = 1.0, 1.0, 0.0
|
||
end
|
||
end
|
||
else
|
||
local gain = COMBAT_FEEDBACK_GAIN[event]
|
||
if gain then
|
||
text = amount
|
||
r, g, b = gain.r, gain.g, gain.b
|
||
-- A critical heal or power gain reads big. Note this is the ONLY flag that
|
||
-- resizes one — a wound's crushing/glancing legs do not apply here.
|
||
if flags == "CRITICAL" then
|
||
scale = 1.5
|
||
end
|
||
else
|
||
scale = COMBAT_FEEDBACK_WORD_SCALE[event] or 1.0
|
||
end
|
||
end
|
||
|
||
frame.feedbackStartTime = GetTime()
|
||
|
||
feedbackText:SetTextHeight(frame.feedbackFontHeight * scale)
|
||
feedbackText:SetText(text)
|
||
feedbackText:SetTextColor(r, g, b)
|
||
feedbackText:SetAlpha(0.0)
|
||
feedbackText:Show()
|
||
end
|
||
|
||
-- The fade envelope (ref CombatFeedback_OnUpdate): 0.2 s ramp in, 0.7 s hold, 0.3 s
|
||
-- ramp out, then Hide. GetTime-based like the ref — no per-tick accumulation.
|
||
function CombatFeedback_OnUpdate(frame)
|
||
local feedbackText = frame.feedbackText
|
||
if not (feedbackText and feedbackText:IsVisible()) then return end
|
||
local elapsedTime = GetTime() - frame.feedbackStartTime
|
||
local fadeInTime = COMBATFEEDBACK_FADEINTIME
|
||
if elapsedTime < fadeInTime then
|
||
feedbackText:SetAlpha(elapsedTime / fadeInTime)
|
||
return
|
||
end
|
||
local holdTime = COMBATFEEDBACK_HOLDTIME
|
||
if elapsedTime < fadeInTime + holdTime then
|
||
feedbackText:SetAlpha(1.0)
|
||
return
|
||
end
|
||
local fadeOutTime = COMBATFEEDBACK_FADEOUTTIME
|
||
if elapsedTime < fadeInTime + holdTime + fadeOutTime then
|
||
feedbackText:SetAlpha(1.0 - (elapsedTime - holdTime - fadeInTime) / fadeOutTime)
|
||
return
|
||
end
|
||
feedbackText:Hide()
|
||
end
|
||
|
||
-- Pull the unit's current state and repaint. A non-existent unit hides the whole frame.
|
||
-- The classification border art (TargetFrame_CheckClassification, ref-TargetFrame.lua
|
||
-- l.205-218) — re-expressed branch for branch, and the shape is the surprise: 1.12 has FIVE
|
||
-- classifications but only THREE border textures, because elite, rareelite and worldboss all
|
||
-- take the same Elite art. There is no rare-elite border in this build and no rare-elite
|
||
-- tooltip word either (the level line's rank table is {"", ELITE, ELITE, BOSS, ""}), so
|
||
-- "rareelite" is a distinction the API makes and the pixels never do. Verified rather than
|
||
-- assumed, because later expansions DO ship UI-TargetingFrame-Rare-Elite and it would be the
|
||
-- natural thing to write: that path is absent from 1.12's patch chain entirely, and the ref
|
||
-- Lua sends rareelite to the Elite art.
|
||
--
|
||
-- SetTexture is the whole operation — no TexCoords, size or anchor changes. That is only
|
||
-- sound because all three BLPs are 256x128 (checked, not assumed), so the region's
|
||
-- XML-declared TexCoords (0.09375..1.0 x 0..0.78125 = the 232x100 crop) frame all three
|
||
-- identically, and our SetTexture touches only the path (it leaves tex_coords alone).
|
||
function TargetFrame_CheckClassification()
|
||
local frame = this
|
||
local classification = UnitClassification(frame.unit)
|
||
if classification == "worldboss" or classification == "rareelite" or classification == "elite" then
|
||
frame.frameTexture:SetTexture("Interface\\TargetingFrame\\UI-TargetingFrame-Elite")
|
||
elseif classification == "rare" then
|
||
frame.frameTexture:SetTexture("Interface\\TargetingFrame\\UI-TargetingFrame-Rare")
|
||
else
|
||
frame.frameTexture:SetTexture("Interface\\TargetingFrame\\UI-TargetingFrame")
|
||
end
|
||
end
|
||
|
||
function UnitFrame_Update()
|
||
local frame = this
|
||
local unit = frame.unit
|
||
if not UnitExists(unit) then
|
||
frame:Hide()
|
||
return
|
||
end
|
||
frame:Show()
|
||
|
||
frame.healthBar:SetMinMaxValues(0, UnitHealthMax(unit))
|
||
frame.healthBar:SetValue(UnitHealth(unit))
|
||
|
||
local maxPower = UnitManaMax(unit)
|
||
if maxPower > 0 then
|
||
frame.powerBar:Show()
|
||
local _, token = UnitPowerType(unit)
|
||
local c = BENILLA_POWER_COLORS[token] or BENILLA_POWER_COLORS.MANA
|
||
frame.powerBar:SetStatusBarColor(c.r, c.g, c.b)
|
||
-- NO label. 1.12 names the resource here (ref UnitFrame_UpdateManaType l.129 →
|
||
-- "Rage 0 / 100" off ManaBarColor's own prefix field, and CharacterFrame_OnLoad
|
||
-- labels health and XP the same way). Shipped in 1146, cut on the director's call
|
||
-- the same day: bare numbers read better on these bars (decision 1147). The
|
||
-- mechanism stays — SetTextStatusBarTextPrefix is live and one call brings any
|
||
-- label back — only the calls are gone.
|
||
frame.powerBar:SetMinMaxValues(0, maxPower)
|
||
frame.powerBar:SetValue(UnitMana(unit))
|
||
else
|
||
frame.powerBar:Hide()
|
||
end
|
||
|
||
frame.nameText:SetText(UnitName(unit) or "Unknown")
|
||
|
||
-- The level slot (TargetFrame_CheckLevel, ref-TargetFrame.lua l.119-142): a corpse
|
||
-- target or one whose level "can't be told" (UnitLevel ≤ 0 — the −1 return for a
|
||
-- world boss / a hostile 10+ levels up) shows the skull instead of the number; a
|
||
-- told level is difficulty-colored when the target is attackable (GetDifficultyColor,
|
||
-- the same con law the V-plate paints its level with) and plain gold when not.
|
||
-- The player frame has no skull region and shows its own level, always gold.
|
||
local level = UnitLevel(unit)
|
||
if frame.highLevel then
|
||
if UnitIsCorpse(unit) or level <= 0 then
|
||
frame.levelText:Hide()
|
||
frame.highLevel:Show()
|
||
else
|
||
frame.levelText:SetText(level)
|
||
if UnitCanAttack("player", unit) then
|
||
local color = GetDifficultyColor(level)
|
||
frame.levelText:SetVertexColor(color.r, color.g, color.b)
|
||
else
|
||
frame.levelText:SetVertexColor(1.0, 0.82, 0.0)
|
||
end
|
||
frame.levelText:Show()
|
||
frame.highLevel:Hide()
|
||
end
|
||
else
|
||
frame.levelText:SetText(level)
|
||
end
|
||
|
||
-- Tint the name/level plate (target only; TargetFrame_CheckFaction, ref-TargetFrame.lua
|
||
-- l.144-191). A PLAYER-CONTROLLED target takes the player branch (l.145-174); only an NPC
|
||
-- takes the reaction swatch (l.179-190): the resolved UnitReactionColor (neutral →
|
||
-- yellow), or blue when the reaction can't be told (nil) — a player never reads the
|
||
-- reaction green, which is why the two paths differ at all.
|
||
-- The player branch runs the ref's full ladder now that the
|
||
-- PvP wire carries it (decision 0646 §4): hostile red, else attackable-but-not-hostile
|
||
-- yellow (l.158), else "we can assist them but they're PvP-flagged" green (l.163), else
|
||
-- blue. Only the tapped-by-other grey (l.176) is still deferred — it needs the tap wire.
|
||
-- The ref's first leg tests mutual attackability (UnitCanAttack both ways); our
|
||
-- UnitCanAttack carries only the player→unit direction, so UnitIsEnemy (reaction ≤ 2)
|
||
-- stands in for it — the same substitution this branch has always made. (The ref gates on
|
||
-- UnitPlayerControlled, which also covers pets; UnitIsPlayer is the player half we can
|
||
-- resolve — see the header deviation.)
|
||
if frame.nameBackground then
|
||
if UnitIsPlayer(unit) then
|
||
local c
|
||
if UnitIsEnemy(unit, "player") then
|
||
c = BENILLA_REACTION_COLORS[2]
|
||
elseif UnitCanAttack("player", unit) then
|
||
c = BENILLA_REACTION_COLORS[4]
|
||
elseif UnitIsPVP(unit) then
|
||
c = BENILLA_REACTION_COLORS[6]
|
||
end
|
||
if c then
|
||
frame.nameBackground:SetVertexColor(c.r, c.g, c.b)
|
||
else
|
||
frame.nameBackground:SetVertexColor(0.0, 0.0, 1.0)
|
||
end
|
||
else
|
||
local reaction = UnitReaction(unit, "player")
|
||
local c = reaction and BENILLA_REACTION_COLORS[reaction]
|
||
if c then
|
||
frame.nameBackground:SetVertexColor(c.r, c.g, c.b)
|
||
else
|
||
frame.nameBackground:SetVertexColor(0.0, 0.0, 1.0)
|
||
end
|
||
end
|
||
end
|
||
|
||
-- The classification border (target only) — the ref's TargetFrame_Update calls
|
||
-- CheckClassification right here, between CheckFaction and CheckDead.
|
||
if frame.frameTexture then
|
||
TargetFrame_CheckClassification()
|
||
end
|
||
|
||
-- DEAD text (target only): shown at zero health (ref TargetFrame_CheckDead). Blanked
|
||
-- via SetText when alive — a site that predates region Show/Hide (region.rs has the
|
||
-- real pair now, the level slot above uses it); equivalent either way.
|
||
if frame.deadText then
|
||
if UnitHealth(unit) <= 0 then
|
||
frame.deadText:SetText("DEAD")
|
||
else
|
||
frame.deadText:SetText("")
|
||
end
|
||
end
|
||
|
||
-- The PvP flag icon, on both frames (decision 0646 §4).
|
||
BenillaUnitFrame_UpdatePvP(frame)
|
||
|
||
-- The status flash, player frame only (handle-gated like pvpIcon; decision 1082).
|
||
if frame.statusTexture then
|
||
PlayerFrame_UpdateStatus()
|
||
end
|
||
|
||
-- The aura rows (target only) — the ref's TargetFrame_Update →
|
||
-- TargetDebuffButton_Update: repainted on PLAYER_TARGET_CHANGED and UNIT_AURA alike.
|
||
if unit == "target" then
|
||
BenillaTargetAuras_Update()
|
||
end
|
||
end
|
||
|
||
-- ref TargetFrame.lua l.2-3.
|
||
MAX_TARGET_DEBUFFS = 16
|
||
MAX_TARGET_BUFFS = 5
|
||
|
||
-- TargetDebuffButton_Update (ref-TargetFrame.lua l.263-387), re-expressed onto the Era API
|
||
-- (decision 0255: UnitBuff returns name,icon,count,debuffType,… — the 1.12 original returned
|
||
-- the texture first). Only called with a live target (UnitFrame_Update guards).
|
||
-- DebuffTypeColor is defined by BuffFrame.xml, which loads AFTER this file — safe, because
|
||
-- this function only runs on events, never at load (no target exists at load).
|
||
function BenillaTargetAuras_Update()
|
||
local button
|
||
local numBuffs = 0
|
||
for i = 1, MAX_TARGET_BUFFS do
|
||
local name, icon = UnitBuff("target", i)
|
||
button = getglobal("TargetFrameBuff" .. i)
|
||
if name then
|
||
getglobal(button:GetName() .. "Icon"):SetTexture(icon or BENILLA_FALLBACK_ICON)
|
||
button:Show()
|
||
numBuffs = numBuffs + 1
|
||
else
|
||
button:Hide()
|
||
end
|
||
end
|
||
|
||
local numDebuffs = 0
|
||
for i = 1, MAX_TARGET_DEBUFFS do
|
||
local name, icon, count, debuffType = UnitDebuff("target", i)
|
||
button = getglobal("TargetFrameDebuff" .. i)
|
||
if name then
|
||
getglobal(button:GetName() .. "Icon"):SetTexture(icon or BENILLA_FALLBACK_ICON)
|
||
local color = DebuffTypeColor[debuffType] or DebuffTypeColor["none"]
|
||
local countText = getglobal(button:GetName() .. "Count")
|
||
if count and count > 1 then
|
||
countText:SetText(count)
|
||
else
|
||
countText:SetText("")
|
||
end
|
||
getglobal(button:GetName() .. "Border"):SetVertexColor(color.r, color.g, color.b)
|
||
button:Show()
|
||
numDebuffs = numDebuffs + 1
|
||
else
|
||
button:Hide()
|
||
end
|
||
end
|
||
|
||
-- The dynamic anchors (ref l.312-331). No target-of-target frame exists yet, so its
|
||
-- branches take the not-shown leg: wrap 6, buffs open under Debuff7.
|
||
local targetofTarget = false
|
||
local debuffWrap = 6
|
||
|
||
if UnitIsFriend("player", "target") then
|
||
TargetFrameBuff1:SetPoint("TOPLEFT", "TargetFrame", "BOTTOMLEFT", 5, 32)
|
||
TargetFrameDebuff1:SetPoint("TOPLEFT", "TargetFrameBuff1", "BOTTOMLEFT", 0, -2)
|
||
else
|
||
TargetFrameDebuff1:SetPoint("TOPLEFT", "TargetFrame", "BOTTOMLEFT", 5, 32)
|
||
TargetFrameBuff1:SetPoint("TOPLEFT", "TargetFrameDebuff7", "BOTTOMLEFT", 0, -2)
|
||
end
|
||
|
||
-- Shrink when the debuffs would overlap the frame (ref l.340-347): 21→17 icons,
|
||
-- 23→19 borders.
|
||
local debuffSize, debuffFrameSize
|
||
if (targetofTarget and numBuffs == 5) or numDebuffs >= debuffWrap then
|
||
debuffSize = 17
|
||
debuffFrameSize = 19
|
||
else
|
||
debuffSize = 21
|
||
debuffFrameSize = 23
|
||
end
|
||
|
||
for i = 1, 5 do
|
||
button = getglobal("TargetFrameBuff" .. i)
|
||
button:SetWidth(debuffSize)
|
||
button:SetHeight(debuffSize)
|
||
end
|
||
-- The ref resizes only the FIRST ROW's debuffs (l.358-368) — rows 2+ stay 17px always.
|
||
for i = 1, 6 do
|
||
button = getglobal("TargetFrameDebuff" .. i)
|
||
local border = getglobal(button:GetName() .. "Border")
|
||
border:SetWidth(debuffFrameSize)
|
||
border:SetHeight(debuffFrameSize)
|
||
button:SetWidth(debuffSize)
|
||
button:SetHeight(debuffSize)
|
||
end
|
||
|
||
-- Re-seat the wrap points (ref l.370-385) — constant with wrap pinned at 6, kept
|
||
-- verbatim so the ToT wrap-5 leg drops in unchanged.
|
||
getglobal("TargetFrameDebuff" .. debuffWrap):ClearAllPoints()
|
||
getglobal("TargetFrameDebuff" .. debuffWrap):SetPoint("LEFT", getglobal("TargetFrameDebuff" .. (debuffWrap - 1)), "RIGHT", 3, 0)
|
||
getglobal("TargetFrameDebuff" .. (debuffWrap + 1)):ClearAllPoints()
|
||
getglobal("TargetFrameDebuff" .. (debuffWrap + 1)):SetPoint("TOPLEFT", "TargetFrameDebuff1", "BOTTOMLEFT", 0, -2)
|
||
getglobal("TargetFrameDebuff" .. (debuffWrap + 2)):ClearAllPoints()
|
||
getglobal("TargetFrameDebuff" .. (debuffWrap + 2)):SetPoint("LEFT", getglobal("TargetFrameDebuff" .. (debuffWrap + 1)), "RIGHT", 3, 0)
|
||
if debuffWrap == 5 then
|
||
TargetFrameDebuff11:ClearAllPoints()
|
||
TargetFrameDebuff11:SetPoint("TOPLEFT", "TargetFrameDebuff6", "BOTTOMLEFT", 0, -2)
|
||
else
|
||
TargetFrameDebuff11:ClearAllPoints()
|
||
TargetFrameDebuff11:SetPoint("LEFT", "TargetFrameDebuff10", "RIGHT", 3, 0)
|
||
end
|
||
end
|
||
|
||
-- The aura hover (ref TargetDebuffButtonTemplate/TargetBuffButtonTemplate OnEnter,
|
||
-- l.60-62/l.79-81): the aura tooltip, seated BOTTOMRIGHT of the button.
|
||
function BenillaTargetAuraButton_OnEnter(button, harmful)
|
||
if not GameTooltip then return end -- engine tests load file subsets
|
||
GameTooltip:SetOwner(button, "ANCHOR_BOTTOMRIGHT", 15, -25)
|
||
if harmful then
|
||
GameTooltip:SetUnitDebuff("target", button:GetID())
|
||
else
|
||
GameTooltip:SetUnitBuff("target", button:GetID())
|
||
end
|
||
end
|
||
|
||
-- The status flash's paint (ref PlayerFrame_UpdateStatus, re-expressed onto frame-param
|
||
-- style; decision 1082). Priority is the ref's: RESTING wins over combat — sitting in an
|
||
-- inn mid-auto-attack shows the zzz — then the auto-attack state (gold ring/zzz vs red
|
||
-- ring/swords/red disc), then bare swords for on-someone's-aggro-list, then nothing.
|
||
function PlayerFrame_UpdateStatus()
|
||
local frame = this
|
||
if IsResting() then
|
||
frame.statusTexture:SetVertexColor(1.0, 0.88, 0.25, 1.0)
|
||
frame.statusTexture:Show()
|
||
frame.restIcon:Show()
|
||
frame.attackIcon:Hide()
|
||
frame.restGlow:Show()
|
||
frame.attackGlow:Hide()
|
||
frame.statusGlow:Show()
|
||
frame.attackBackground:Hide()
|
||
elseif frame.inCombat then
|
||
frame.statusTexture:SetVertexColor(1.0, 0.0, 0.0, 1.0)
|
||
frame.statusTexture:Show()
|
||
frame.attackIcon:Show()
|
||
frame.restIcon:Hide()
|
||
frame.attackGlow:Show()
|
||
frame.restGlow:Hide()
|
||
frame.statusGlow:Show()
|
||
frame.attackBackground:Show()
|
||
elseif frame.onHateList then
|
||
frame.attackIcon:Show()
|
||
frame.restIcon:Hide()
|
||
frame.statusGlow:Hide()
|
||
frame.attackBackground:Hide()
|
||
else
|
||
frame.statusTexture:Hide()
|
||
frame.restIcon:Hide()
|
||
frame.attackIcon:Hide()
|
||
frame.statusGlow:Hide()
|
||
frame.attackBackground:Hide()
|
||
end
|
||
end
|
||
|
||
-- ref PlayerFrame_OnUpdate: the status pulse — a 0.5 s triangle wave between alpha
|
||
-- 55/255 and 255/255 on the ADD-blend status ring and the icon glow, run only while the
|
||
-- ring is visible (resting or auto-attacking); the hit-indicator fade rides the same
|
||
-- tick, exactly as before.
|
||
function PlayerFrame_OnUpdate(elapsed)
|
||
local frame = this
|
||
if frame.statusTexture:IsVisible() then
|
||
local counter = frame.statusCounter + elapsed
|
||
local sign = frame.statusSign
|
||
if counter > 0.5 then
|
||
sign = -sign
|
||
frame.statusSign = sign
|
||
end
|
||
counter = mod(counter, 0.5)
|
||
frame.statusCounter = counter
|
||
local alpha
|
||
if sign == 1 then
|
||
alpha = (55 + counter * 400) / 255
|
||
else
|
||
alpha = (255 - counter * 400) / 255
|
||
end
|
||
frame.statusTexture:SetAlpha(alpha)
|
||
frame.statusGlow:SetAlpha(alpha)
|
||
end
|
||
CombatFeedback_OnUpdate(frame)
|
||
end
|
||
|
||
-- Left-click the player frame to target yourself (ref PlayerFrame_OnClick, the LeftButton
|
||
-- branch: an item on the cursor auto-equips, otherwise TargetUnit("player")); right-click
|
||
-- opens the SELF menu (ref's ToggleDropDownMenu at (106, 27), decision 0434 phase 5). The
|
||
-- ref's frame is a Button; ours is a mouse-enabled Frame, which fires OnClick on release
|
||
-- with arg1 = the button. The self-target itself is the byte-verified way the client
|
||
-- selects its own unit; the world-model click is a separate path, deliberately left out
|
||
-- (director's call).
|
||
function PlayerFrame_OnClick(button)
|
||
if button == "RightButton" then
|
||
ToggleDropDownMenu(1, nil, PlayerFrameDropDown, "PlayerFrame", 106, 27)
|
||
return
|
||
end
|
||
if button ~= "LeftButton" then return end
|
||
if CursorHasItem() then
|
||
AutoEquipCursorItem()
|
||
else
|
||
TargetUnit("player")
|
||
end
|
||
end
|
||
|
||
-- The right-click dropdowns (decision 0434 phase 5; ref TargetFrame.lua l.431-455,
|
||
-- PlayerFrame.lua l.235-241): MENU displayMode over the shared kit (UIDropDownMenu.xml),
|
||
-- the menus themselves from UnitPopup.xml. The menu pick is the ref's law: self → SELF,
|
||
-- party member → PARTY, other player → PLAYER, NPC → the bare raid-target icon list.
|
||
function TargetFrameDropDown_OnLoad()
|
||
UIDropDownMenu_Initialize(this, TargetFrameDropDown_Initialize, "MENU")
|
||
end
|
||
|
||
-- Which menu the CURRENT target gets, and the heading that menu needs (only the bare mark
|
||
-- list carries one — the unit menus title themselves from the unit). Most specific first:
|
||
-- yourself → SELF
|
||
-- your own pet → PET. Selecting your pet and right-clicking the TARGET
|
||
-- frame is still the pet's menu; the ref's own second
|
||
-- branch (TargetFrame.lua l.440-441), which sat as a
|
||
-- comment here until the "pet" token (0990) and then the
|
||
-- menu itself (1066).
|
||
-- anything that is not a player → an NPC, which gets only the raid-mark list
|
||
-- a player, in your party or not → PARTY / PLAYER
|
||
local function TargetFrameMenu()
|
||
if UnitIsUnit("target", "player") then
|
||
return "SELF"
|
||
end
|
||
if UnitIsUnit("target", "pet") then
|
||
return "PET"
|
||
end
|
||
if not UnitIsPlayer("target") then
|
||
return "RAID_TARGET_ICON", RAID_TARGET_ICON
|
||
end
|
||
if UnitInParty("target") then
|
||
return "PARTY"
|
||
end
|
||
return "PLAYER"
|
||
end
|
||
|
||
function TargetFrameDropDown_Initialize()
|
||
local menu, name = TargetFrameMenu()
|
||
if menu then
|
||
UnitPopup_ShowMenu(TargetFrameDropDown, menu, "target", name)
|
||
end
|
||
end
|
||
|
||
function PlayerFrameDropDown_OnLoad()
|
||
UIDropDownMenu_Initialize(this, PlayerFrameDropDown_Initialize, "MENU")
|
||
end
|
||
|
||
function PlayerFrameDropDown_Initialize()
|
||
UnitPopup_ShowMenu(PlayerFrameDropDown, "SELF", "player")
|
||
end
|
||
|
||
-- ref TargetFrame_OnClick: right-click opens the unit menu at (120, 10). The ref's
|
||
-- LeftButton branch (SpellTargetUnit / DropItemOnUnit) rides the spell-cursor and
|
||
-- item-drop slices — a bare left-click is a no-op on the ref too.
|
||
function TargetFrame_OnClick(button)
|
||
if button == "RightButton" then
|
||
ToggleDropDownMenu(1, nil, TargetFrameDropDown, "TargetFrame", 120, 10)
|
||
end
|
||
end
|
||
]]></Script>
|
||
|
||
<!--
|
||
PLAYER FRAME (ref-PlayerFrame.xml). Button 232×100 at UIParent TOPLEFT (-19,-4) (l.4-13); the
|
||
negative x trims the art's transparent left padding, seating the visible frame flush at the
|
||
screen's top-left, exactly as the real client does (our UIParent is the screen).
|
||
-->
|
||
<!-- frameStrata BACKGROUND is the ref's own (`PlayerFrame.xml` l.4) — the unit frames live
|
||
BELOW the MEDIUM panel windows on purpose, and the player frame shares the top-left corner
|
||
with the left-slot panel. See the note on PartyFrame.xml's template (decision 0597) for why
|
||
the default MEDIUM makes a unit frame's bar children draw through a window's background. -->
|
||
<Frame name="PlayerFrame" frameStrata="BACKGROUND">
|
||
<Size><AbsDimension x="232" y="100"/></Size>
|
||
<Anchors>
|
||
<Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="-19" y="-4"/></Offset></Anchor>
|
||
</Anchors>
|
||
<Layers>
|
||
<Layer level="BACKGROUND">
|
||
<!-- The bar trough: 119×41 black a=0.5 behind both bars (ref l.20-32). -->
|
||
<Texture name="$parentBackground">
|
||
<Size><AbsDimension x="119" y="41"/></Size>
|
||
<Anchors>
|
||
<Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="106" y="-22"/></Offset></Anchor>
|
||
</Anchors>
|
||
<Color r="0" g="0" b="0" a="0.5"/>
|
||
</Texture>
|
||
<!-- Portrait 64×64 TOPLEFT (42,-12) (ref l.35-46, which puts it in ARTWORK; ours sits
|
||
in BACKGROUND, a leftover of the pre-0884 layering. The ring art now clears it by
|
||
frame LEVEL, and nothing else in this layer overlaps the portrait's square, so the
|
||
two are indistinguishable here — left alone rather than churned inside a bug fix).
|
||
Texture set in OnLoad via SetPortraitToTexture (circle-masked). -->
|
||
<Texture name="$parentPortrait">
|
||
<Size><AbsDimension x="64" y="64"/></Size>
|
||
<Anchors>
|
||
<Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="42" y="-12"/></Offset></Anchor>
|
||
</Anchors>
|
||
</Texture>
|
||
</Layer>
|
||
<!-- The ring art + name/level text live in $parentTextureFrame below, one frame level
|
||
ABOVE the bars, so they draw OVER the bar edges — the recessed-bar look. OnLoad puts
|
||
them there (SetFrameLevel); see the LAYERING note at the top of this file. -->
|
||
</Layers>
|
||
<Frames>
|
||
<!-- Health bar 119×12 TOPLEFT (106,-41), UI-StatusBar, green (ref l.288-315). -->
|
||
<StatusBar name="$parentHealthBar" enableMouse="true">
|
||
<Size><AbsDimension x="119" y="12"/></Size>
|
||
<Anchors>
|
||
<Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="106" y="-41"/></Offset></Anchor>
|
||
</Anchors>
|
||
<BarTexture file="Interface\TargetingFrame\UI-StatusBar"/>
|
||
<BarColor r="0.0" g="1.0" b="0.0"/>
|
||
<Scripts>
|
||
<OnEvent>if TextStatusBar_OnEvent then TextStatusBar_OnEvent(arg1, arg2) end</OnEvent>
|
||
<OnValueChanged>if TextStatusBar_OnValueChanged then TextStatusBar_OnValueChanged() end</OnValueChanged>
|
||
<OnEnter>BenillaUnitFrameBar_OnEnter(self)</OnEnter>
|
||
<OnLeave>BenillaUnitFrameBar_OnLeave(self)</OnLeave>
|
||
<OnMouseUp>BenillaUnitFrameBar_OnMouseUp(self, arg1)</OnMouseUp>
|
||
</Scripts>
|
||
</StatusBar>
|
||
<!-- Power bar 119×12 TOPLEFT (106,-52), UI-StatusBar; color set at runtime (ref l.316-341). -->
|
||
<StatusBar name="$parentPowerBar" enableMouse="true">
|
||
<Size><AbsDimension x="119" y="12"/></Size>
|
||
<Anchors>
|
||
<Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="106" y="-52"/></Offset></Anchor>
|
||
</Anchors>
|
||
<BarTexture file="Interface\TargetingFrame\UI-StatusBar"/>
|
||
<BarColor r="0.0" g="0.0" b="1.0"/>
|
||
<Scripts>
|
||
<OnEvent>if TextStatusBar_OnEvent then TextStatusBar_OnEvent(arg1, arg2) end</OnEvent>
|
||
<OnValueChanged>if TextStatusBar_OnValueChanged then TextStatusBar_OnValueChanged() end</OnValueChanged>
|
||
<OnEnter>BenillaUnitFrameBar_OnEnter(self)</OnEnter>
|
||
<OnLeave>BenillaUnitFrameBar_OnLeave(self)</OnLeave>
|
||
<OnMouseUp>BenillaUnitFrameBar_OnMouseUp(self, arg1)</OnMouseUp>
|
||
</Scripts>
|
||
</StatusBar>
|
||
<!-- The ring art + text, over the bars (ref TargetFrameTextureFrame). 232×100 fills the
|
||
parent; it draws over the bar edges because OnLoad pushes the bars one frame level
|
||
BELOW it (the ref's SetFrameLevel(bar, textureFrame-1)) — NOT because of where it
|
||
is declared. See the LAYERING note at the top of this file. -->
|
||
<Frame name="$parentTextureFrame">
|
||
<Size><AbsDimension x="232" y="100"/></Size>
|
||
<Anchors>
|
||
<Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor>
|
||
</Anchors>
|
||
<Layers>
|
||
<Layer level="BACKGROUND">
|
||
<!-- Ring art, MIRRORED: TexCoords left=1.0 right=0.09375 top=0 bottom=0.78125 (ref l.55-57). -->
|
||
<Texture name="$parentTexture" file="Interface\TargetingFrame\UI-TargetingFrame">
|
||
<Size><AbsDimension x="232" y="100"/></Size>
|
||
<Anchors>
|
||
<Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor>
|
||
</Anchors>
|
||
<TexCoords left="1.0" right="0.09375" top="0" bottom="0.78125"/>
|
||
</Texture>
|
||
<!-- Name and level ride BACKGROUND with the ring art, like the ref's
|
||
(ref-PlayerFrame.xml l.54-97) — NOT the OVERLAY the state icons sit
|
||
in. The layer choice is load-bearing for the level number: the zzz /
|
||
crossed-swords badge (OVERLAY, below) sits exactly on the level
|
||
badge, and the client's bucket-wide texture-before-fontstring order
|
||
(decision 0884) means a fontstring can never hide UNDER a texture
|
||
of its own layer — the ref hides the number while resting by putting
|
||
it in a LOWER layer than the badge. Shipped in OVERLAY by 1082, the
|
||
number rode ON the badge (director report, 2026-08-07); decision 1093. -->
|
||
<!-- Name 100×10 CENTER (50,19) (ref l.58-69).
|
||
|
||
A DIVERGENCE, measured and deliberately kept. The reference names the
|
||
three unit-frame name FontStrings LITERALLY — `PlayerName`
|
||
(PlayerFrame.xml:58), `TargetName` (TargetFrame.xml:152), `PetName`
|
||
(PetFrame.xml:69) — where ours derive from `$parent`. That is 1222's
|
||
shadowed-global class on a region, and it was tried: the rename is
|
||
NOT free and it buys nothing.
|
||
· Not free — `UnitFrame_OnLoad` binds the region by COMPOSING the
|
||
name (`getglobal(tf .. "NameText")`, l.174), so a text search for
|
||
the derived global finds zero hits and the rename still breaks
|
||
every unit frame. It broke 40 tests. A name built inside
|
||
`getglobal` is invisible to a grep — the same trap that once left
|
||
CleanMinimap in the tail for three rounds.
|
||
· Buys nothing — the survey's "why" read-back matches ZERO
|
||
error rows for `TargetName` across
|
||
the 218-addon corpus. The one syntactic caller,
|
||
`CT_UnitFrames/CT_TargetFrame.xml:181`'s
|
||
`<Anchor relativeTo="TargetName">`, is not what stops that addon:
|
||
it dies at its own `CT_PlayerFrame.lua:206`.
|
||
So: 1222's rejected clause applies (a shadowed name with no observable
|
||
caller is building against a guess), and the cost lands on the frames
|
||
the director looks at every session. Revisit if a row ever names it. -->
|
||
<FontString name="$parentNameText" inherits="GameFontNormalSmall" text="Unknown">
|
||
<Size><AbsDimension x="100" y="10"/></Size>
|
||
<Anchors>
|
||
<Anchor point="CENTER" relativePoint="CENTER"><Offset><AbsDimension x="50" y="19"/></Offset></Anchor>
|
||
</Anchors>
|
||
</FontString>
|
||
<!-- Level number CENTER (-63,-16) (ref l.70-78). -->
|
||
<FontString name="$parentLevelText" inherits="GameFontNormalSmall">
|
||
<Anchors>
|
||
<Anchor point="CENTER" relativePoint="CENTER"><Offset><AbsDimension x="-63" y="-16"/></Offset></Anchor>
|
||
</Anchors>
|
||
</FontString>
|
||
<!-- The on-bar numerals, "value / max" (decision 1143). The reference's
|
||
own seats are CENTER (50,4) and (50,-8) (ref-PlayerFrame.xml l.79-96);
|
||
ours sit **1 px lower** — the director's call on the shipped result,
|
||
2026-08-08, and the deviation is recorded rather than the literals
|
||
(decision 1145). Why a digit string reads high at the ref's own offset:
|
||
a FontString centres on its LINE BOX, ascent + descent, and digits
|
||
occupy only the ascent — so the visible mass sits above the box centre
|
||
and a numerals-only string looks lifted inside a 12 px bar. The nudge
|
||
is optical, and it belongs on the seat rather than on the centring,
|
||
which every other string in the UI shares.
|
||
The LAYER is the reference's: BACKGROUND beside the name and level, not
|
||
the OVERLAY the state badges sit in, for the reason spelled out above.
|
||
They draw over the bar FILL because the whole TextureFrame outranks the
|
||
bars by a frame level. Empty until Status Bar Text is on or (later) the
|
||
bar is hovered — TextStatusBar.xml owns that decision, not this file. -->
|
||
<FontString name="$parentHealthBarText" inherits="TextStatusBarText">
|
||
<Anchors>
|
||
<Anchor point="CENTER" relativePoint="CENTER"><Offset><AbsDimension x="50" y="3"/></Offset></Anchor>
|
||
</Anchors>
|
||
</FontString>
|
||
<FontString name="$parentPowerBarText" inherits="TextStatusBarText">
|
||
<Anchors>
|
||
<Anchor point="CENTER" relativePoint="CENTER"><Offset><AbsDimension x="50" y="-9"/></Offset></Anchor>
|
||
</Anchors>
|
||
</FontString>
|
||
</Layer>
|
||
<Layer level="ARTWORK">
|
||
<!-- The PvP flag icon, 64×64 TOPLEFT (18,-20) (ref PlayerPVPIcon,
|
||
ref-PlayerFrame.xml l.124-135) — ARTWORK over the BACKGROUND ring art,
|
||
like the ref's. Texture chosen at runtime (decision 0646 §4); the ref's
|
||
PlayerPVPIconHitArea is deliberately OUT (see the header). -->
|
||
<Texture name="$parentPVPIcon" hidden="true">
|
||
<Size><AbsDimension x="64" y="64"/></Size>
|
||
<Anchors>
|
||
<Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="18" y="-20"/></Offset></Anchor>
|
||
</Anchors>
|
||
</Texture>
|
||
<!-- The status ring (ref PlayerStatusTexture, ref-PlayerFrame.xml l.99-111):
|
||
the ADD-blend glow ring around the whole frame, tinted gold while
|
||
resting / red while auto-attacking, pulsed by the OnUpdate flash
|
||
(decision 1082). 190×66 TOPLEFT (35,-8), the ring art's own recess. -->
|
||
<Texture name="$parentStatusTexture" file="Interface\CharacterFrame\UI-Player-Status" alphaMode="ADD" hidden="true">
|
||
<Size><AbsDimension x="190" y="66"/></Size>
|
||
<Anchors>
|
||
<Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="35" y="-8"/></Offset></Anchor>
|
||
</Anchors>
|
||
<TexCoords left="0" right="0.74609375" top="0" bottom="0.53125"/>
|
||
</Texture>
|
||
<!-- ref PlayerAttackBackground (l.112-123): the dark red disc behind the
|
||
attack state icon; OnLoad tints it (0.8,0.1,0.1) at alpha 0.4. -->
|
||
<Texture name="$parentAttackBackground" file="Interface\TargetingFrame\UI-TargetingFrame-AttackBackground" hidden="true">
|
||
<Size><AbsDimension x="32" y="32"/></Size>
|
||
<Anchors>
|
||
<Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="37" y="-50"/></Offset></Anchor>
|
||
</Anchors>
|
||
</Texture>
|
||
</Layer>
|
||
<Layer level="OVERLAY">
|
||
<!-- The portrait hit indicator (ref PlayerHitIndicator, decision 0576):
|
||
CENTER at (73,-42) from the frame's TOPLEFT — the portrait circle's
|
||
center. Hidden until a UNIT_COMBAT event lands; CombatFeedback drives
|
||
text/size/color and the fade. -->
|
||
<FontString name="$parentHitIndicator" hidden="true" inherits="NumberFontNormalHuge">
|
||
<Anchors>
|
||
<Anchor point="CENTER" relativePoint="TOPLEFT"><Offset><AbsDimension x="73" y="-42"/></Offset></Anchor>
|
||
</Anchors>
|
||
</FontString>
|
||
<!-- The state icon pair under the portrait (ref PlayerRestIcon /
|
||
PlayerAttackIcon, ref-PlayerFrame.xml l.171-201): the two halves of
|
||
UI-StateIcon — zzz left, crossed swords right (decision 1082). -->
|
||
<Texture name="$parentRestIcon" file="Interface\CharacterFrame\UI-StateIcon" hidden="true">
|
||
<Size><AbsDimension x="31" y="33"/></Size>
|
||
<Anchors>
|
||
<Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="37" y="-49"/></Offset></Anchor>
|
||
</Anchors>
|
||
<TexCoords left="0" right="0.5" top="0" bottom="0.421875"/>
|
||
</Texture>
|
||
<Texture name="$parentAttackIcon" file="Interface\CharacterFrame\UI-StateIcon" hidden="true">
|
||
<Size><AbsDimension x="32" y="32"/></Size>
|
||
<Anchors>
|
||
<Anchor point="TOPLEFT" relativeTo="$parentRestIcon"><Offset><AbsDimension x="1" y="1"/></Offset></Anchor>
|
||
</Anchors>
|
||
<TexCoords left="0.5" right="1.0" top="0" bottom="0.5"/>
|
||
</Texture>
|
||
</Layer>
|
||
</Layers>
|
||
</Frame>
|
||
<!-- The state icon's own ADD-blend glow (ref PlayerStatusGlow, ref-PlayerFrame.xml
|
||
l.216-248): a sibling FRAME over the icon so the whole thing can pulse as one
|
||
SetAlpha group with the status ring (the OnUpdate flash, decision 1082). -->
|
||
<Frame name="$parentStatusGlow" hidden="true">
|
||
<Size><AbsDimension x="32" y="32"/></Size>
|
||
<Anchors>
|
||
<Anchor point="TOPLEFT" relativeTo="$parentTextureFrameRestIcon"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor>
|
||
</Anchors>
|
||
<Layers>
|
||
<Layer level="OVERLAY">
|
||
<Texture name="$parentRestGlow" file="Interface\CharacterFrame\UI-StateIcon" alphaMode="ADD">
|
||
<Size><AbsDimension x="32" y="32"/></Size>
|
||
<Anchors>
|
||
<Anchor point="TOPLEFT" relativePoint="TOPLEFT"/>
|
||
</Anchors>
|
||
<TexCoords left="0" right="0.5" top="0.5" bottom="1.0"/>
|
||
</Texture>
|
||
<Texture name="$parentAttackGlow" file="Interface\CharacterFrame\UI-StateIcon" alphaMode="ADD">
|
||
<Size><AbsDimension x="32" y="32"/></Size>
|
||
<Anchors>
|
||
<Anchor point="TOPLEFT" relativePoint="TOPLEFT"/>
|
||
</Anchors>
|
||
<TexCoords left="0.5" right="1.0" top="0.5" bottom="1.0"/>
|
||
</Texture>
|
||
</Layer>
|
||
</Layers>
|
||
</Frame>
|
||
</Frames>
|
||
<Scripts>
|
||
<OnLoad><![CDATA[BenillaUnitFrame_OnLoad(this, "player")]]></OnLoad>
|
||
<OnEvent><![CDATA[UnitFrame_OnEvent(event)]]></OnEvent>
|
||
<OnUpdate><![CDATA[PlayerFrame_OnUpdate(arg1)]]></OnUpdate>
|
||
<OnEnter><![CDATA[BenillaUnitFrame_OnEnter(this)]]></OnEnter>
|
||
<OnLeave><![CDATA[BenillaUnitFrame_OnLeave(this)]]></OnLeave>
|
||
<OnClick><![CDATA[PlayerFrame_OnClick(arg1)]]></OnClick>
|
||
</Scripts>
|
||
</Frame>
|
||
|
||
<!-- The SELF-menu dropdown host (ref $parentDropDown, PlayerFrame.xml): hidden state-holder
|
||
only — the list anchors to the player frame by name, never to this. -->
|
||
<Frame name="PlayerFrameDropDown" parent="PlayerFrame" inherits="UIDropDownMenuTemplate" hidden="true">
|
||
<Size><AbsDimension x="10" y="10"/></Size>
|
||
<Anchors><Anchor point="TOP"><Offset><AbsDimension x="-20" y="-10"/></Offset></Anchor></Anchors>
|
||
<Scripts>
|
||
<OnLoad>PlayerFrameDropDown_OnLoad()</OnLoad>
|
||
</Scripts>
|
||
</Frame>
|
||
|
||
<!-- ref TargetDebuffButtonTemplate (l.26-68): 17×17 icon, 19×19 UI-Debuff-Overlays border
|
||
(dispel-tinted at update), bottom-right stack count. Sizes are re-set every update by the
|
||
shrink law (first row only, verbatim ref quirk). -->
|
||
<Button name="BenillaTargetDebuffTemplate" virtual="true">
|
||
<Size><AbsDimension x="17" y="17"/></Size>
|
||
<Layers>
|
||
<Layer level="ARTWORK">
|
||
<Texture name="$parentIcon" setAllPoints="true"/>
|
||
</Layer>
|
||
<Layer level="OVERLAY">
|
||
<Texture name="$parentBorder" file="Interface\Buttons\UI-Debuff-Overlays">
|
||
<Size><AbsDimension x="19" y="19"/></Size>
|
||
<Anchors><Anchor point="CENTER"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor></Anchors>
|
||
<TexCoords left="0.296875" right="0.5703125" top="0" bottom="0.515625"/>
|
||
</Texture>
|
||
<FontString name="$parentCount" inherits="NumberFontNormalSmall">
|
||
<Anchors><Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-1" y="0"/></Offset></Anchor></Anchors>
|
||
</FontString>
|
||
</Layer>
|
||
</Layers>
|
||
<Scripts>
|
||
<OnEnter>BenillaTargetAuraButton_OnEnter(this, true)</OnEnter>
|
||
<OnLeave>GameTooltip:Hide()</OnLeave>
|
||
</Scripts>
|
||
</Button>
|
||
|
||
<!-- ref TargetBuffButtonTemplate (l.69-87): 21×21 icon, no border, no count. -->
|
||
<Button name="BenillaTargetBuffTemplate" virtual="true">
|
||
<Size><AbsDimension x="21" y="21"/></Size>
|
||
<Layers>
|
||
<Layer level="ARTWORK">
|
||
<Texture name="$parentIcon" setAllPoints="true"/>
|
||
</Layer>
|
||
</Layers>
|
||
<Scripts>
|
||
<OnEnter>BenillaTargetAuraButton_OnEnter(this, false)</OnEnter>
|
||
<OnLeave>GameTooltip:Hide()</OnLeave>
|
||
</Scripts>
|
||
</Button>
|
||
|
||
<!--
|
||
TARGET FRAME (ref-TargetFrame.xml). Button 232×100 at UIParent TOPLEFT (250,-4) (l.88-98). The
|
||
un-mirrored twin of the player frame: portrait window on the RIGHT, bars anchored TOPRIGHT.
|
||
-->
|
||
<!-- hidden="true" is a deliberate deviation from the ref (whose TargetFrame starts shown and
|
||
gets its no-target Hide behind the loading screen, before audio is live): our OnLoad
|
||
Update runs with audio up, so starting shown would fire OnHide's lost-target sound at
|
||
every login. Steady-state behavior is identical — Update Shows/Hides on target changes. -->
|
||
<!-- frameStrata LOW is the ref's own (`TargetFrame.xml` l.88) — one above the player frame's
|
||
BACKGROUND, still below the MEDIUM panels. Same reasoning as PlayerFrame above. -->
|
||
<Frame name="TargetFrame" hidden="true" frameStrata="LOW">
|
||
<Size><AbsDimension x="232" y="100"/></Size>
|
||
<Anchors>
|
||
<Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="250" y="-4"/></Offset></Anchor>
|
||
</Anchors>
|
||
<Layers>
|
||
<Layer level="BACKGROUND">
|
||
<!-- Bar trough 119×41 black a=0.5 TOPRIGHT (-106,-22) (ref l.104-116). -->
|
||
<Texture name="$parentBackground">
|
||
<Size><AbsDimension x="119" y="41"/></Size>
|
||
<Anchors>
|
||
<Anchor point="TOPRIGHT" relativePoint="TOPRIGHT"><Offset><AbsDimension x="-106" y="-22"/></Offset></Anchor>
|
||
</Anchors>
|
||
<Color r="0" g="0" b="0" a="0.5"/>
|
||
</Texture>
|
||
<!-- The level/name plate 119×19 TOPRIGHT (-106,-22) (ref l.117-128) — target only,
|
||
untinted (see the header deviation on faction tinting). -->
|
||
<Texture name="$parentNameBackground" file="Interface\TargetingFrame\UI-TargetingFrame-LevelBackground">
|
||
<Size><AbsDimension x="119" y="19"/></Size>
|
||
<Anchors>
|
||
<Anchor point="TOPRIGHT" relativePoint="TOPRIGHT"><Offset><AbsDimension x="-106" y="-22"/></Offset></Anchor>
|
||
</Anchors>
|
||
</Texture>
|
||
<!-- Portrait 64×64 TOPRIGHT (-42,-12) (ref l.131-142, BORDER→BACKGROUND for the same
|
||
ring-over-portrait layering as the player). -->
|
||
<Texture name="$parentPortrait">
|
||
<Size><AbsDimension x="64" y="64"/></Size>
|
||
<Anchors>
|
||
<Anchor point="TOPRIGHT" relativePoint="TOPRIGHT"><Offset><AbsDimension x="-42" y="-12"/></Offset></Anchor>
|
||
</Anchors>
|
||
</Texture>
|
||
</Layer>
|
||
<!-- The ring art + name/level/dead text live in $parentTextureFrame below (declared AFTER
|
||
the bars) so they draw OVER the bar edges — the recessed-bar look. -->
|
||
</Layers>
|
||
<Frames>
|
||
<!-- Health bar 119×12 TOPRIGHT (-106,-41), UI-StatusBar, green (ref l.252-273). -->
|
||
<StatusBar name="$parentHealthBar" enableMouse="true">
|
||
<Size><AbsDimension x="119" y="12"/></Size>
|
||
<Anchors>
|
||
<Anchor point="TOPRIGHT" relativePoint="TOPRIGHT"><Offset><AbsDimension x="-106" y="-41"/></Offset></Anchor>
|
||
</Anchors>
|
||
<BarTexture file="Interface\TargetingFrame\UI-StatusBar"/>
|
||
<BarColor r="0.0" g="1.0" b="0.0"/>
|
||
<Scripts>
|
||
<OnEvent>if TextStatusBar_OnEvent then TextStatusBar_OnEvent(arg1, arg2) end</OnEvent>
|
||
<OnValueChanged>if TextStatusBar_OnValueChanged then TextStatusBar_OnValueChanged() end</OnValueChanged>
|
||
<OnEnter>BenillaUnitFrameBar_OnEnter(self)</OnEnter>
|
||
<OnLeave>BenillaUnitFrameBar_OnLeave(self)</OnLeave>
|
||
<OnMouseUp>BenillaUnitFrameBar_OnMouseUp(self, arg1)</OnMouseUp>
|
||
</Scripts>
|
||
</StatusBar>
|
||
<!-- Power bar 119×12 TOPRIGHT (-106,-52), UI-StatusBar; color set at runtime (ref l.274-292). -->
|
||
<StatusBar name="$parentPowerBar" enableMouse="true">
|
||
<Size><AbsDimension x="119" y="12"/></Size>
|
||
<Anchors>
|
||
<Anchor point="TOPRIGHT" relativePoint="TOPRIGHT"><Offset><AbsDimension x="-106" y="-52"/></Offset></Anchor>
|
||
</Anchors>
|
||
<BarTexture file="Interface\TargetingFrame\UI-StatusBar"/>
|
||
<BarColor r="0.0" g="0.0" b="1.0"/>
|
||
<Scripts>
|
||
<OnEvent>if TextStatusBar_OnEvent then TextStatusBar_OnEvent(arg1, arg2) end</OnEvent>
|
||
<OnValueChanged>if TextStatusBar_OnValueChanged then TextStatusBar_OnValueChanged() end</OnValueChanged>
|
||
<OnEnter>BenillaUnitFrameBar_OnEnter(self)</OnEnter>
|
||
<OnLeave>BenillaUnitFrameBar_OnLeave(self)</OnLeave>
|
||
<OnMouseUp>BenillaUnitFrameBar_OnMouseUp(self, arg1)</OnMouseUp>
|
||
</Scripts>
|
||
</StatusBar>
|
||
<!-- The ring art + text, over the bars (ref TargetFrameTextureFrame); it draws over the
|
||
bar edges because OnLoad pushes the bars one frame level BELOW it (the ref's
|
||
SetFrameLevel(bar, textureFrame-1)) — NOT because of where it is declared. See the
|
||
LAYERING note at the top of this file. -->
|
||
<Frame name="$parentTextureFrame">
|
||
<Size><AbsDimension x="232" y="100"/></Size>
|
||
<Anchors>
|
||
<Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor>
|
||
</Anchors>
|
||
<Layers>
|
||
<Layer level="BACKGROUND">
|
||
<!-- Ring art, UN-mirrored: TexCoords left=0.09375 right=1.0 (ref l.146-151). -->
|
||
<Texture name="$parentTexture" file="Interface\TargetingFrame\UI-TargetingFrame">
|
||
<Size><AbsDimension x="232" y="100"/></Size>
|
||
<Anchors>
|
||
<Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor>
|
||
</Anchors>
|
||
<TexCoords left="0.09375" right="1.0" top="0" bottom="0.78125"/>
|
||
</Texture>
|
||
</Layer>
|
||
<Layer level="ARTWORK">
|
||
<!-- The PvP flag icon, 64×64 TOPRIGHT (3,-20) (ref TargetPVPIcon,
|
||
ref-TargetFrame.xml l.208-219) — the player's icon mirrored to the
|
||
frame's other edge, same law (decision 0646 §4). -->
|
||
<Texture name="$parentPVPIcon" hidden="true">
|
||
<Size><AbsDimension x="64" y="64"/></Size>
|
||
<Anchors>
|
||
<Anchor point="TOPRIGHT" relativePoint="TOPRIGHT"><Offset><AbsDimension x="3" y="-20"/></Offset></Anchor>
|
||
</Anchors>
|
||
</Texture>
|
||
</Layer>
|
||
<Layer level="OVERLAY">
|
||
<!-- Name 100×10 CENTER (-50,19) (ref l.152-163). -->
|
||
<FontString name="$parentNameText" inherits="GameFontNormalSmall" text="Unknown">
|
||
<Size><AbsDimension x="100" y="10"/></Size>
|
||
<Anchors>
|
||
<Anchor point="CENTER" relativePoint="CENTER"><Offset><AbsDimension x="-50" y="19"/></Offset></Anchor>
|
||
</Anchors>
|
||
</FontString>
|
||
<!-- Level number CENTER (63,-16) (ref l.164-172). -->
|
||
<FontString name="$parentLevelText" inherits="GameFontNormalSmall">
|
||
<Anchors>
|
||
<Anchor point="CENTER" relativePoint="CENTER"><Offset><AbsDimension x="63" y="-16"/></Offset></Anchor>
|
||
</Anchors>
|
||
</FontString>
|
||
<!-- The "too high to tell" skull, 16×16 on the level slot (ref
|
||
TargetHighLevelTexture, l.184-195): shown for a corpse target or
|
||
UnitLevel ≤ 0, replacing the number (TargetFrame_CheckLevel). -->
|
||
<Texture name="$parentHighLevelTexture" file="Interface\TargetingFrame\UI-TargetingFrame-Skull">
|
||
<Size><AbsDimension x="16" y="16"/></Size>
|
||
<Anchors>
|
||
<Anchor point="CENTER" relativePoint="CENTER"><Offset><AbsDimension x="63" y="-16"/></Offset></Anchor>
|
||
</Anchors>
|
||
</Texture>
|
||
<!-- DEAD, CENTER (-50,3), shown at zero health (ref l.173-181). Blank while alive. -->
|
||
<FontString name="$parentDeadText" inherits="GameFontNormalSmall">
|
||
<Anchors>
|
||
<Anchor point="CENTER" relativePoint="CENTER"><Offset><AbsDimension x="-50" y="3"/></Offset></Anchor>
|
||
</Anchors>
|
||
</FontString>
|
||
<!-- The target's numerals (decision 1146), mirrored from the player's seats
|
||
the way the whole frame is: x = −50 against the player's +50, the same
|
||
1 px optical drop (1145). 1143 wired these bars and left the regions
|
||
out because nothing could ever paint them — 1.12 never makes the target
|
||
bars textLockable, so Status Bar Text does not pin them. The HOVER is
|
||
what they were waiting for, and it is the reference's behaviour whole:
|
||
on the target, the numbers are something you go and look at. -->
|
||
<FontString name="$parentHealthBarText" inherits="TextStatusBarText">
|
||
<Anchors>
|
||
<Anchor point="CENTER" relativePoint="CENTER"><Offset><AbsDimension x="-50" y="3"/></Offset></Anchor>
|
||
</Anchors>
|
||
</FontString>
|
||
<FontString name="$parentPowerBarText" inherits="TextStatusBarText">
|
||
<Anchors>
|
||
<Anchor point="CENTER" relativePoint="CENTER"><Offset><AbsDimension x="-50" y="-9"/></Offset></Anchor>
|
||
</Anchors>
|
||
</FontString>
|
||
</Layer>
|
||
</Layers>
|
||
</Frame>
|
||
<!-- The aura rows (ref l.293-481, names re-based on $parent). Static anchors are the
|
||
ref's; the update re-seats Debuff1/6/7/8/11 and Buff1 every pass (the friend swap +
|
||
the wrap law). Row 1 debuffs 1-6 (+3 gaps), row 2 debuffs 7-16, buffs below. -->
|
||
<Button name="$parentDebuff1" inherits="BenillaTargetDebuffTemplate" id="1">
|
||
<Anchors><Anchor point="TOPLEFT" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="5" y="32"/></Offset></Anchor></Anchors>
|
||
</Button>
|
||
<Button name="$parentDebuff2" inherits="BenillaTargetDebuffTemplate" id="2">
|
||
<Anchors><Anchor point="LEFT" relativeTo="$parentDebuff1" relativePoint="RIGHT"><Offset><AbsDimension x="3" y="0"/></Offset></Anchor></Anchors>
|
||
</Button>
|
||
<Button name="$parentDebuff3" inherits="BenillaTargetDebuffTemplate" id="3">
|
||
<Anchors><Anchor point="LEFT" relativeTo="$parentDebuff2" relativePoint="RIGHT"><Offset><AbsDimension x="3" y="0"/></Offset></Anchor></Anchors>
|
||
</Button>
|
||
<Button name="$parentDebuff4" inherits="BenillaTargetDebuffTemplate" id="4">
|
||
<Anchors><Anchor point="LEFT" relativeTo="$parentDebuff3" relativePoint="RIGHT"><Offset><AbsDimension x="3" y="0"/></Offset></Anchor></Anchors>
|
||
</Button>
|
||
<Button name="$parentDebuff5" inherits="BenillaTargetDebuffTemplate" id="5">
|
||
<Anchors><Anchor point="LEFT" relativeTo="$parentDebuff4" relativePoint="RIGHT"><Offset><AbsDimension x="3" y="0"/></Offset></Anchor></Anchors>
|
||
</Button>
|
||
<Button name="$parentDebuff6" inherits="BenillaTargetDebuffTemplate" id="6">
|
||
<Anchors><Anchor point="LEFT" relativeTo="$parentDebuff5" relativePoint="RIGHT"><Offset><AbsDimension x="3" y="0"/></Offset></Anchor></Anchors>
|
||
</Button>
|
||
<Button name="$parentDebuff7" inherits="BenillaTargetDebuffTemplate" id="7">
|
||
<Anchors><Anchor point="TOPLEFT" relativeTo="$parentDebuff1" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors>
|
||
</Button>
|
||
<Button name="$parentDebuff8" inherits="BenillaTargetDebuffTemplate" id="8">
|
||
<Anchors><Anchor point="LEFT" relativeTo="$parentDebuff7" relativePoint="RIGHT"><Offset><AbsDimension x="3" y="0"/></Offset></Anchor></Anchors>
|
||
</Button>
|
||
<Button name="$parentDebuff9" inherits="BenillaTargetDebuffTemplate" id="9">
|
||
<Anchors><Anchor point="LEFT" relativeTo="$parentDebuff8" relativePoint="RIGHT"><Offset><AbsDimension x="3" y="0"/></Offset></Anchor></Anchors>
|
||
</Button>
|
||
<Button name="$parentDebuff10" inherits="BenillaTargetDebuffTemplate" id="10">
|
||
<Anchors><Anchor point="LEFT" relativeTo="$parentDebuff9" relativePoint="RIGHT"><Offset><AbsDimension x="3" y="0"/></Offset></Anchor></Anchors>
|
||
</Button>
|
||
<Button name="$parentDebuff11" inherits="BenillaTargetDebuffTemplate" id="11">
|
||
<Anchors><Anchor point="LEFT" relativeTo="$parentDebuff10" relativePoint="RIGHT"><Offset><AbsDimension x="3" y="0"/></Offset></Anchor></Anchors>
|
||
</Button>
|
||
<Button name="$parentDebuff12" inherits="BenillaTargetDebuffTemplate" id="12">
|
||
<Anchors><Anchor point="LEFT" relativeTo="$parentDebuff11" relativePoint="RIGHT"><Offset><AbsDimension x="3" y="0"/></Offset></Anchor></Anchors>
|
||
</Button>
|
||
<Button name="$parentDebuff13" inherits="BenillaTargetDebuffTemplate" id="13">
|
||
<Anchors><Anchor point="LEFT" relativeTo="$parentDebuff12" relativePoint="RIGHT"><Offset><AbsDimension x="3" y="0"/></Offset></Anchor></Anchors>
|
||
</Button>
|
||
<Button name="$parentDebuff14" inherits="BenillaTargetDebuffTemplate" id="14">
|
||
<Anchors><Anchor point="LEFT" relativeTo="$parentDebuff13" relativePoint="RIGHT"><Offset><AbsDimension x="3" y="0"/></Offset></Anchor></Anchors>
|
||
</Button>
|
||
<Button name="$parentDebuff15" inherits="BenillaTargetDebuffTemplate" id="15">
|
||
<Anchors><Anchor point="LEFT" relativeTo="$parentDebuff14" relativePoint="RIGHT"><Offset><AbsDimension x="3" y="0"/></Offset></Anchor></Anchors>
|
||
</Button>
|
||
<Button name="$parentDebuff16" inherits="BenillaTargetDebuffTemplate" id="16">
|
||
<Anchors><Anchor point="LEFT" relativeTo="$parentDebuff15" relativePoint="RIGHT"><Offset><AbsDimension x="3" y="0"/></Offset></Anchor></Anchors>
|
||
</Button>
|
||
<Button name="$parentBuff1" inherits="BenillaTargetBuffTemplate" id="1">
|
||
<Anchors><Anchor point="TOPLEFT" relativeTo="$parentDebuff1" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-2"/></Offset></Anchor></Anchors>
|
||
</Button>
|
||
<Button name="$parentBuff2" inherits="BenillaTargetBuffTemplate" id="2">
|
||
<Anchors><Anchor point="LEFT" relativeTo="$parentBuff1" relativePoint="RIGHT"><Offset><AbsDimension x="3" y="0"/></Offset></Anchor></Anchors>
|
||
</Button>
|
||
<Button name="$parentBuff3" inherits="BenillaTargetBuffTemplate" id="3">
|
||
<Anchors><Anchor point="LEFT" relativeTo="$parentBuff2" relativePoint="RIGHT"><Offset><AbsDimension x="3" y="0"/></Offset></Anchor></Anchors>
|
||
</Button>
|
||
<Button name="$parentBuff4" inherits="BenillaTargetBuffTemplate" id="4">
|
||
<Anchors><Anchor point="LEFT" relativeTo="$parentBuff3" relativePoint="RIGHT"><Offset><AbsDimension x="3" y="0"/></Offset></Anchor></Anchors>
|
||
</Button>
|
||
<Button name="$parentBuff5" inherits="BenillaTargetBuffTemplate" id="5">
|
||
<Anchors><Anchor point="LEFT" relativeTo="$parentBuff4" relativePoint="RIGHT"><Offset><AbsDimension x="3" y="0"/></Offset></Anchor></Anchors>
|
||
</Button>
|
||
</Frames>
|
||
<Scripts>
|
||
<OnLoad><![CDATA[BenillaUnitFrame_OnLoad(this, "target")]]></OnLoad>
|
||
<OnEvent><![CDATA[UnitFrame_OnEvent(event)]]></OnEvent>
|
||
<OnEnter><![CDATA[BenillaUnitFrame_OnEnter(this)]]></OnEnter>
|
||
<OnLeave><![CDATA[BenillaUnitFrame_OnLeave(this)]]></OnLeave>
|
||
<!-- The select/deselect pair rides the frame's visibility, exactly the ref
|
||
(TargetFrame_OnShow/OnHide, TargetFrame.lua l.104-117). In 5875 data all three
|
||
select kits resolve to the same iSelectTarget.wav — the reaction branch is
|
||
data-plumbing fidelity, not an audible difference. -->
|
||
<OnShow><![CDATA[
|
||
if UnitIsEnemy("target", "player") then
|
||
PlaySound("igCreatureAggroSelect")
|
||
elseif UnitIsFriend("player", "target") then
|
||
PlaySound("igCharacterNPCSelect")
|
||
else
|
||
PlaySound("igCreatureNeutralSelect")
|
||
end
|
||
]]></OnShow>
|
||
<!-- ref TargetFrame_OnHide: the lost-target sound + close any open unit menu (a
|
||
cleared target must not leave its menu acting on a stale unit). -->
|
||
<OnHide><![CDATA[
|
||
PlaySound("INTERFACESOUND_LOSTTARGETUNIT")
|
||
CloseDropDownMenus()
|
||
]]></OnHide>
|
||
<OnClick><![CDATA[TargetFrame_OnClick(arg1)]]></OnClick>
|
||
</Scripts>
|
||
</Frame>
|
||
|
||
<!-- The target-menu dropdown host (ref $parentDropDown, TargetFrame.xml): hidden
|
||
state-holder only, like the player's above. -->
|
||
<Frame name="TargetFrameDropDown" parent="TargetFrame" inherits="UIDropDownMenuTemplate" hidden="true">
|
||
<Size><AbsDimension x="10" y="10"/></Size>
|
||
<Anchors><Anchor point="TOP"><Offset><AbsDimension x="-20" y="-10"/></Offset></Anchor></Anchors>
|
||
<Scripts>
|
||
<OnLoad>TargetFrameDropDown_OnLoad()</OnLoad>
|
||
</Scripts>
|
||
</Frame>
|
||
|
||
<!--
|
||
THE PET FRAME (decision 0990). Geometry and art are MEASURED off ref PetFrame.xml +
|
||
PetFrame.lua and the behaviour is theirs; the code is our own. 128×53 parented to
|
||
the player frame at TOPLEFT (80,-60) — a static anchor, deliberately: the reference does not
|
||
list PetFrame in UIPARENT_MANAGED_FRAME_POSITIONS at all, because it rides its parent.
|
||
|
||
The art is the SMALL targeting frame, and it has two states the ref swaps at runtime
|
||
(PetFrame.lua's Update): `UI-SmallTargetingFrame` when the pet has a power bar, `-NoMana`
|
||
when it has none. That is not decoration — a warlock's imp has mana and a hunter's boar has
|
||
focus, but a skeleton has neither, and the plain art draws a mana rail with nothing in it.
|
||
|
||
HAPPINESS is no longer deferred — decision 1005 built it, off the carve 0990 was waiting
|
||
for. `GetPetHappiness 0x4be900` returns a bucket the CLIENT has already computed from
|
||
PetPersonality.dbc's own three thresholds, so nothing here is derived from vmangos and
|
||
0988's warning was answered rather than dodged. Its three siblings (`GetPetLoyalty`,
|
||
`GetPetTrainingPoints`, `GetPetExperience`) landed with it.
|
||
|
||
The PET RIGHT-CLICK MENU is no longer deferred either — decision 1066 built the last of
|
||
the five things it was waiting on (the pet paper doll, the fifth, landed as 1057). The
|
||
frame's non-left-click arm opens PetFrameDropDown below; the rows, their fork and their
|
||
dialogs are UnitPopup.xml's.
|
||
|
||
STILL DEFERRED, with its reason:
|
||
- PartyMemberBuffTooltip on hover (the ref's OnEnter panel): it belongs to the party
|
||
frames' own deferred buff panel, not here.
|
||
-->
|
||
<Script><![CDATA[
|
||
function PetFrame_OnLoad()
|
||
local frame = this
|
||
frame.portrait = PetFrameTextureFramePortrait
|
||
frame.healthBar = PetFrameHealthBar
|
||
frame.manaBar = PetFrameManaBar
|
||
frame.nameText = PetFrameTextureFrameName
|
||
-- The recessed-bar look, the same mechanism the player/target/party frames use
|
||
-- (decision 0884): push the bars one frame level BELOW the art's TextureFrame, because
|
||
-- the draw layer is bucket-wide and the frame level is the one key term above it.
|
||
local level = PetFrameTextureFrame:GetFrameLevel()
|
||
frame.healthBar:SetFrameLevel(level - 1)
|
||
frame.manaBar:SetFrameLevel(level - 1)
|
||
-- The pet's numerals, textLockable like the ref's (PetFrame.xml l.139/164; 1143).
|
||
if TextStatusBar_Initialize then -- engine tests load file subsets
|
||
TextStatusBar_Initialize(frame.healthBar)
|
||
TextStatusBar_Initialize(frame.manaBar)
|
||
SetTextStatusBarText(frame.healthBar, PetFrameTextureFrameHealthBarText)
|
||
SetTextStatusBarText(frame.manaBar, PetFrameTextureFrameManaBarText)
|
||
frame.healthBar.textLockable = 1
|
||
frame.manaBar.textLockable = 1
|
||
end
|
||
-- The pet's bars hover for their numbers, but hand nothing on: the pet frame has no
|
||
-- OnEnter of its own, and its click is its own handler (1146).
|
||
frame.healthBar.ownerClick = PetFrame_OnClick
|
||
frame.manaBar.ownerClick = PetFrame_OnClick
|
||
frame.attackModeCounter = 0
|
||
frame.attackModeSign = -1
|
||
frame:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||
frame:RegisterEvent("UNIT_PET")
|
||
frame:RegisterEvent("UNIT_AURA")
|
||
frame:RegisterEvent("UNIT_HEALTH")
|
||
frame:RegisterEvent("UNIT_MAXHEALTH")
|
||
frame:RegisterEvent("UNIT_POWER_UPDATE")
|
||
frame:RegisterEvent("UNIT_MAXPOWER")
|
||
frame:RegisterEvent("UNIT_DISPLAYPOWER")
|
||
frame:RegisterEvent("UNIT_NAME_UPDATE")
|
||
frame:RegisterEvent("PET_ATTACK_START")
|
||
frame:RegisterEvent("PET_ATTACK_STOP")
|
||
frame:RegisterEvent("UNIT_HAPPINESS")
|
||
frame:EnableMouse(true)
|
||
PetFrame_Update()
|
||
end
|
||
|
||
-- ref PetFrame_OnEvent, with both of its argument gates kept: UNIT_PET names the OWNER
|
||
-- ("player" — wow-re §9 carves the arg), every other UNIT_* names the pet itself. Getting
|
||
-- those backwards is how a pet frame ends up repainting off the player's health.
|
||
function PetFrame_OnEvent(event)
|
||
local frame = this
|
||
if event == "PET_ATTACK_START" then
|
||
PetFrameAttackMode:SetVertexColor(1.0, 1.0, 1.0, 1.0)
|
||
PetFrameAttackMode:Show()
|
||
return
|
||
elseif event == "PET_ATTACK_STOP" then
|
||
PetFrameAttackMode:Hide()
|
||
return
|
||
elseif event == "UNIT_HAPPINESS" then
|
||
-- The ref's own last arm: repaint the icon alone, not the whole frame.
|
||
PetFrame_SetHappiness()
|
||
return
|
||
elseif event == "UNIT_PET" then
|
||
if arg1 ~= "player" then return end
|
||
elseif string.sub(event, 1, 5) == "UNIT_" and arg1 ~= "pet" then
|
||
return
|
||
end
|
||
PetFrame_Update()
|
||
end
|
||
|
||
function PetFrame_Update()
|
||
local frame = this
|
||
if not UnitExists("pet") then
|
||
frame:Hide()
|
||
return
|
||
end
|
||
frame:Show()
|
||
SetPortraitTexture(frame.portrait, "pet")
|
||
frame.nameText:SetText(UnitName("pet") or "")
|
||
|
||
frame.healthBar:SetMinMaxValues(0, UnitHealthMax("pet"))
|
||
frame.healthBar:SetValue(UnitHealth("pet"))
|
||
|
||
-- The ref's art swap (PetFrame.lua's Update): a powerless pet wears the -NoMana plate,
|
||
-- which has no mana rail to leave empty. The bar hides with it, as on every other frame.
|
||
local maxPower = UnitManaMax("pet")
|
||
if maxPower > 0 then
|
||
PetFrameTexture:SetTexture("Interface\\TargetingFrame\\UI-SmallTargetingFrame")
|
||
frame.manaBar:Show()
|
||
local _, token = UnitPowerType("pet")
|
||
local c = BENILLA_POWER_COLORS[token] or BENILLA_POWER_COLORS.MANA
|
||
frame.manaBar:SetStatusBarColor(c.r, c.g, c.b)
|
||
frame.manaBar:SetMinMaxValues(0, maxPower)
|
||
frame.manaBar:SetValue(UnitMana("pet"))
|
||
else
|
||
PetFrameTexture:SetTexture("Interface\\TargetingFrame\\UI-SmallTargetingFrame-NoMana")
|
||
frame.manaBar:Hide()
|
||
end
|
||
|
||
BenillaPetFrame_UpdateDebuffs()
|
||
PetFrame_SetHappiness()
|
||
end
|
||
|
||
-- ── The happiness face (decision 1005; ref PetFrame_SetHappiness, PetFrame.lua l.132-156)
|
||
--
|
||
-- `Interface\PetPaperDollFrame\UI-PetHappiness` is one row of three faces, and each cut is
|
||
-- a MEASURED value off that file. The buckets run RIGHT to left across the strip — bucket
|
||
-- 1 (unhappy) takes the rightmost cut and bucket 3 (happy) sits at x=0 — which is why the
|
||
-- left edges below descend as the index climbs. A bucket outside 1..3 selects no cell at
|
||
-- all and the icon keeps whatever face it was already wearing, deliberately: see the note
|
||
-- on bucket 0 below.
|
||
local PET_HAPPINESS_CELL = {
|
||
{ 0.375, 0.5625, 0, 0.359375 },
|
||
{ 0.1875, 0.375, 0, 0.359375 },
|
||
{ 0, 0.1875, 0, 0.359375 },
|
||
}
|
||
|
||
-- `GetPetHappiness` hands back a PRE-BUCKETED 1/2/3 — the client does the thresholding
|
||
-- against PetPersonality.dbc, not this function — plus the two numbers that bucket
|
||
-- selects. Three things here are load-bearing and each is the reference's own:
|
||
-- * the hide test is on the bucket being ABSENT, not on it being zero, so a bucket of
|
||
-- **0** (truthy in Lua) leaves the icon SHOWING with whatever texcoords it had.
|
||
-- Testing `happiness == 0` or coercing 0 to nil would hide a frame the reference shows.
|
||
-- * only a HUNTER's pet has a face at all — `HasPetUI`'s second return is the whole gate,
|
||
-- and its first return is nothing to do with this frame.
|
||
-- * the loyalty line is chosen by the SIGN of loyaltyRate, which is why that return is
|
||
-- unscaled and allowed to be negative. Exactly zero means "steady", and says nothing.
|
||
function PetFrame_SetHappiness()
|
||
local happiness, damagePercentage, loyaltyRate = GetPetHappiness()
|
||
local _, isHunterPet = HasPetUI()
|
||
if not (happiness and isHunterPet) then
|
||
PetFrameHappiness:Hide()
|
||
return
|
||
end
|
||
PetFrameHappiness:Show()
|
||
|
||
local cell = PET_HAPPINESS_CELL[happiness]
|
||
if cell then
|
||
PetFrameHappinessTexture:SetTexCoord(cell[1], cell[2], cell[3], cell[4])
|
||
end
|
||
|
||
PetFrameHappiness.tooltip = getglobal("PET_HAPPINESS" .. happiness)
|
||
PetFrameHappiness.tooltipDamage = format(PET_DAMAGE_PERCENTAGE, damagePercentage)
|
||
if loyaltyRate < 0 then
|
||
PetFrameHappiness.tooltipLoyalty = getglobal("LOSING_LOYALTY")
|
||
elseif loyaltyRate > 0 then
|
||
PetFrameHappiness.tooltipLoyalty = getglobal("GAINING_LOYALTY")
|
||
else
|
||
PetFrameHappiness.tooltipLoyalty = nil
|
||
end
|
||
end
|
||
|
||
-- The happiness face's hover. Both extra lines are drawn the same way — plain white,
|
||
-- nothing in the right-hand column — and either can be absent, so the stanza is written
|
||
-- once. The whole tooltip waits on the headline: until SetHappiness has run there is
|
||
-- nothing to say, and an empty plate is worse than none.
|
||
local function AddHappinessTooltipLine(text)
|
||
if text then
|
||
GameTooltip:AddLine(text, "", 1, 1, 1)
|
||
end
|
||
end
|
||
|
||
function BenillaPetHappiness_OnEnter(frame)
|
||
if not frame.tooltip then
|
||
return
|
||
end
|
||
GameTooltip:SetOwner(frame, "ANCHOR_RIGHT")
|
||
GameTooltip:SetText(frame.tooltip)
|
||
AddHappinessTooltipLine(frame.tooltipDamage)
|
||
AddHappinessTooltipLine(frame.tooltipLoyalty)
|
||
GameTooltip:Show()
|
||
end
|
||
|
||
-- The four debuff buttons (ref PetFrame.xml's $parentDebuff1..4 over RefreshBuffs). The pet
|
||
-- carries no aura timers — no unit but yourself does on the 1.12 wire (decision 0257 B6) —
|
||
-- so these are icon + border + stack count, the party rows' shape exactly.
|
||
function BenillaPetFrame_UpdateDebuffs()
|
||
for i = 1, 4 do
|
||
local button = getglobal("PetFrameDebuff" .. i)
|
||
local _, icon, count = UnitDebuff("pet", i)
|
||
if icon then
|
||
getglobal(button:GetName() .. "Icon"):SetTexture(icon)
|
||
local text = getglobal(button:GetName() .. "Count")
|
||
if count and count > 1 then
|
||
text:SetText(count)
|
||
text:Show()
|
||
else
|
||
text:Hide()
|
||
end
|
||
button:Show()
|
||
else
|
||
button:Hide()
|
||
end
|
||
end
|
||
end
|
||
|
||
-- ref PetFrame_OnUpdate: the attack overlay's alpha ramp — a 0.5 s-per-leg triangle between
|
||
-- 55/255 and 1, sign-flipped at each end (the ref's own counter shape, transcribed).
|
||
function PetFrame_OnUpdate(elapsed)
|
||
local frame = this
|
||
if not PetFrameAttackMode:IsVisible() then return end
|
||
local counter = frame.attackModeCounter + elapsed
|
||
local sign = frame.attackModeSign
|
||
if counter > 0.5 then
|
||
sign = -sign
|
||
frame.attackModeSign = sign
|
||
end
|
||
counter = mod(counter, 0.5)
|
||
frame.attackModeCounter = counter
|
||
local alpha
|
||
if sign == 1 then
|
||
alpha = (55 + (counter * 400)) / 255
|
||
else
|
||
alpha = (255 - (counter * 400)) / 255
|
||
end
|
||
PetFrameAttackMode:SetVertexColor(1.0, 1.0, 1.0, alpha)
|
||
end
|
||
|
||
-- Clicking the pet frame (decision 1066 built the right-hand half). The LEFT button hands
|
||
-- the pet whatever the cursor is carrying; every other button opens its menu — except for
|
||
-- the one case that is easy to drop, a RIGHT click with a spell already armed, which puts
|
||
-- the spell away instead. That exception is the right button's alone: a middle click while
|
||
-- casting still opens the menu.
|
||
function PetFrame_OnClick(button)
|
||
if button ~= "LeftButton" then
|
||
if button == "RightButton" and SpellIsTargeting() then
|
||
SpellStopTargeting()
|
||
else
|
||
-- No anchor arguments, unlike the player and target frames: this menu opens at
|
||
-- the CURSOR (ref PetFrame.lua l.128).
|
||
ToggleDropDownMenu(1, nil, PetFrameDropDown)
|
||
end
|
||
return
|
||
end
|
||
-- Most specific first: an armed spell binds to the pet, then a held item drops on it
|
||
-- (this is how you feed one), and with an empty cursor the click simply selects it.
|
||
if SpellIsTargeting() then
|
||
SpellTargetUnit("pet")
|
||
elseif CursorHasItem() then
|
||
DropItemOnUnit("pet")
|
||
else
|
||
TargetUnit("pet")
|
||
end
|
||
end
|
||
|
||
-- The PET menu's owner (ref PetFrame.lua l.158-166). The UnitExists gate is the ref's own,
|
||
-- and it is not redundant with the frame being hidden: this dropdown is also what the
|
||
-- TARGET frame's menu falls through to when you have your own pet selected.
|
||
function PetFrameDropDown_OnLoad()
|
||
UIDropDownMenu_Initialize(this, PetFrameDropDown_Initialize, "MENU")
|
||
end
|
||
|
||
function PetFrameDropDown_Initialize()
|
||
if UnitExists("pet") then
|
||
UnitPopup_ShowMenu(PetFrameDropDown, "PET", "pet")
|
||
end
|
||
end
|
||
|
||
function BenillaPetFrame_OnEnter(frame)
|
||
-- The pet frame is a unit frame in the reference, and takes the same cursor split:
|
||
-- hovering it with a spell armed lights or greys exactly as the target frame does.
|
||
if SpellIsTargeting() then
|
||
if SpellCanTargetUnit("pet") then
|
||
SetCursor("CAST_CURSOR")
|
||
else
|
||
SetCursor("CAST_ERROR_CURSOR")
|
||
end
|
||
end
|
||
if not UnitExists("pet") then return end
|
||
GameTooltip_SetDefaultAnchor(GameTooltip, frame)
|
||
if GameTooltip:SetUnit("pet") then
|
||
getglobal("GameTooltipTextLeft1"):SetTextColor(GameTooltip_UnitColor("pet"))
|
||
end
|
||
end
|
||
]]></Script>
|
||
|
||
<!-- The debuff button — ref PartyBuffButtonTemplate (PartyFrameTemplates.xml l.3-35), which is
|
||
what the ref's PetFrame inherits for its four: a 15×15 icon under a 17×17
|
||
UI-Debuff-Overlays border. Untinted: the ref's party/pet buttons never recolor it (only the
|
||
target frame's dispel-tint law does), so this stays the flat swatch the TexCoords select. -->
|
||
<Button name="BenillaPetDebuffTemplate" virtual="true" hidden="true">
|
||
<Size><AbsDimension x="15" y="15"/></Size>
|
||
<Layers>
|
||
<Layer level="ARTWORK">
|
||
<Texture name="$parentIcon" setAllPoints="true"/>
|
||
</Layer>
|
||
<Layer level="OVERLAY">
|
||
<Texture name="$parentBorder" file="Interface\Buttons\UI-Debuff-Overlays">
|
||
<Size><AbsDimension x="17" y="17"/></Size>
|
||
<Anchors><Anchor point="CENTER"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor></Anchors>
|
||
<TexCoords left="0.296875" right="0.5703125" top="0" bottom="0.515625"/>
|
||
</Texture>
|
||
<FontString name="$parentCount" inherits="NumberFontNormalSmall">
|
||
<Anchors><Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-1" y="0"/></Offset></Anchor></Anchors>
|
||
</FontString>
|
||
</Layer>
|
||
</Layers>
|
||
<Scripts>
|
||
<OnEnter><![CDATA[
|
||
if GameTooltip then
|
||
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
|
||
GameTooltip:SetUnitDebuff("pet", this:GetID())
|
||
end
|
||
]]></OnEnter>
|
||
<OnLeave>GameTooltip:Hide()</OnLeave>
|
||
</Scripts>
|
||
</Button>
|
||
|
||
<!-- frameStrata LOW matches the party frames' and the target's — above the player frame's
|
||
BACKGROUND (which it overlaps), still below the MEDIUM panels. -->
|
||
<Frame name="PetFrame" parent="PlayerFrame" hidden="true" frameStrata="LOW">
|
||
<Size><AbsDimension x="128" y="53"/></Size>
|
||
<Anchors>
|
||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="80" y="-60"/></Offset></Anchor>
|
||
</Anchors>
|
||
<Frames>
|
||
<!-- The PET-menu dropdown host (ref $parentDropDown, PetFrame.xml l.35-51): a hidden
|
||
state-holder, like the player and target frames'. It carries the ref's own id=1
|
||
and its 10×10 size; nothing anchors to it, because this menu opens at the cursor.
|
||
It lives INSIDE the pet frame, which means it is only ever initialized once the
|
||
pet frame exists — the same nesting the reference uses. -->
|
||
<Frame name="PetFrameDropDown" inherits="UIDropDownMenuTemplate" id="1" hidden="true">
|
||
<Size><AbsDimension x="10" y="10"/></Size>
|
||
<Anchors><Anchor point="TOP"><Offset><AbsDimension x="-20" y="-20"/></Offset></Anchor></Anchors>
|
||
<Scripts>
|
||
<OnLoad>PetFrameDropDown_OnLoad()</OnLoad>
|
||
</Scripts>
|
||
</Frame>
|
||
<!-- Bars first, pushed one frame level under the TextureFrame at load (see OnLoad). -->
|
||
<StatusBar name="$parentHealthBar" enableMouse="true">
|
||
<Size><AbsDimension x="70" y="8"/></Size>
|
||
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="47" y="-22"/></Offset></Anchor></Anchors>
|
||
<BarTexture file="Interface\TargetingFrame\UI-StatusBar"/>
|
||
<BarColor r="0" g="1.0" b="0"/>
|
||
<Scripts>
|
||
<OnEvent>if TextStatusBar_OnEvent then TextStatusBar_OnEvent(arg1, arg2) end</OnEvent>
|
||
<OnValueChanged>if TextStatusBar_OnValueChanged then TextStatusBar_OnValueChanged() end</OnValueChanged>
|
||
<OnEnter>BenillaUnitFrameBar_OnEnter(self)</OnEnter>
|
||
<OnLeave>BenillaUnitFrameBar_OnLeave(self)</OnLeave>
|
||
<OnMouseUp>BenillaUnitFrameBar_OnMouseUp(self, arg1)</OnMouseUp>
|
||
</Scripts>
|
||
</StatusBar>
|
||
<StatusBar name="$parentManaBar" enableMouse="true">
|
||
<Size><AbsDimension x="70" y="8"/></Size>
|
||
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="47" y="-29"/></Offset></Anchor></Anchors>
|
||
<BarTexture file="Interface\TargetingFrame\UI-StatusBar"/>
|
||
<BarColor r="0" g="0" b="1.0"/>
|
||
<Scripts>
|
||
<OnEvent>if TextStatusBar_OnEvent then TextStatusBar_OnEvent(arg1, arg2) end</OnEvent>
|
||
<OnValueChanged>if TextStatusBar_OnValueChanged then TextStatusBar_OnValueChanged() end</OnValueChanged>
|
||
<OnEnter>BenillaUnitFrameBar_OnEnter(self)</OnEnter>
|
||
<OnLeave>BenillaUnitFrameBar_OnLeave(self)</OnLeave>
|
||
<OnMouseUp>BenillaUnitFrameBar_OnMouseUp(self, arg1)</OnMouseUp>
|
||
</Scripts>
|
||
</StatusBar>
|
||
<Frame name="$parentTextureFrame">
|
||
<Size><AbsDimension x="128" y="53"/></Size>
|
||
<Anchors><Anchor point="TOPLEFT"/></Anchors>
|
||
<Layers>
|
||
<Layer level="BACKGROUND">
|
||
<!-- Portrait 37×37 TOPLEFT (7,-6), under the art's round window (ref
|
||
PetPortrait, PetFrame.xml l.16-27). -->
|
||
<Texture name="$parentPortrait">
|
||
<Size><AbsDimension x="37" y="37"/></Size>
|
||
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="7" y="-6"/></Offset></Anchor></Anchors>
|
||
</Texture>
|
||
</Layer>
|
||
<Layer level="BORDER">
|
||
<!-- 128×64 art at (0,-2) — it hangs 13 px below the frame rect, as
|
||
authored. The file is swapped at update (see the header). -->
|
||
<Texture name="PetFrameTexture" file="Interface\TargetingFrame\UI-SmallTargetingFrame">
|
||
<Size><AbsDimension x="128" y="64"/></Size>
|
||
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="0" y="-2"/></Offset></Anchor></Anchors>
|
||
</Texture>
|
||
</Layer>
|
||
<Layer level="ARTWORK">
|
||
<!-- The attacking overlay (ref PetAttackModeTexture, l.104-118): the right
|
||
29.7% of UI-Player-AttackStatus, ADD-blended, pulsed by OnUpdate. -->
|
||
<Texture name="PetFrameAttackMode" file="Interface\TargetingFrame\UI-Player-AttackStatus" alphaMode="ADD" hidden="true">
|
||
<Size><AbsDimension x="76" y="64"/></Size>
|
||
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="6" y="-9"/></Offset></Anchor></Anchors>
|
||
<TexCoords left="0.703125" right="1.0" top="0" bottom="1.0"/>
|
||
</Texture>
|
||
</Layer>
|
||
<Layer level="OVERLAY">
|
||
<FontString name="$parentName" inherits="GameFontNormalSmall" justifyH="LEFT">
|
||
<Anchors><Anchor point="BOTTOMLEFT"><Offset><AbsDimension x="50" y="33"/></Offset></Anchor></Anchors>
|
||
</FontString>
|
||
<!-- The pet's numerals: the reference's LITERAL seats, CENTER-from-TOPLEFT
|
||
(82,-27) and (82,-38) (PetFrame.xml l.87-104). 1143 anchored these to
|
||
the bars' own centres instead, on a comparison that was simply wrong —
|
||
it read the ref's TEXT offsets against OUR BAR offsets and called the
|
||
pitch different. The bars are byte-identical to the reference's, 70x8
|
||
at (47,-22) and (47,-29), so the ref's text literals apply unchanged.
|
||
The overlap that produced (director report, 2026-08-08, decision 1147)
|
||
is what the literals exist to avoid: a 7 px bar pitch has no room for
|
||
two centred ~12 px strings, so the reference drops the SECOND one clear
|
||
of its bar (the mana bar ends at -37; its numerals sit at -38, just
|
||
under it) rather than centring it. Centring both is what collided.
|
||
No 1145 nudge here: that pixel was for a string centred in a bar, and
|
||
the reference has already placed these deliberately off-centre. -->
|
||
<FontString name="$parentHealthBarText" inherits="TextStatusBarText">
|
||
<Anchors><Anchor point="CENTER" relativePoint="TOPLEFT"><Offset><AbsDimension x="82" y="-27"/></Offset></Anchor></Anchors>
|
||
</FontString>
|
||
<FontString name="$parentManaBarText" inherits="TextStatusBarText">
|
||
<Anchors><Anchor point="CENTER" relativePoint="TOPLEFT"><Offset><AbsDimension x="82" y="-38"/></Offset></Anchor></Anchors>
|
||
</FontString>
|
||
</Layer>
|
||
</Layers>
|
||
</Frame>
|
||
<!-- HAPPINESS (ref PetFrameHappiness, PetFrame.xml l.233-253; decision 1005). 24×23
|
||
anchored LEFT to the frame's RIGHT at (-7,-4), so it sits just inside the plate's
|
||
right edge. The file is a three-icon strip and the AUTHORED TexCoords are
|
||
happiness 3's — the ref ships the happy face and re-cuts it at update. -->
|
||
<Frame name="$parentHappiness" enableMouse="true">
|
||
<Size><AbsDimension x="24" y="23"/></Size>
|
||
<Anchors><Anchor point="LEFT" relativePoint="RIGHT"><Offset><AbsDimension x="-7" y="-4"/></Offset></Anchor></Anchors>
|
||
<Layers>
|
||
<Layer level="BACKGROUND">
|
||
<Texture name="$parentTexture" file="Interface\PetPaperDollFrame\UI-PetHappiness" setAllPoints="true">
|
||
<Size><AbsDimension x="24" y="23"/></Size>
|
||
<TexCoords left="0" right="0.1875" top="0" bottom="0.359375"/>
|
||
</Texture>
|
||
</Layer>
|
||
</Layers>
|
||
<Scripts>
|
||
<OnEnter>
|
||
BenillaPetHappiness_OnEnter(this)
|
||
</OnEnter>
|
||
<OnLeave>
|
||
GameTooltip:Hide()
|
||
</OnLeave>
|
||
</Scripts>
|
||
</Frame>
|
||
<!-- The four debuffs, ref geometry: the first at (48,-42), then +2 px chained right. -->
|
||
<Button name="$parentDebuff1" inherits="BenillaPetDebuffTemplate" id="1">
|
||
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="48" y="-42"/></Offset></Anchor></Anchors>
|
||
</Button>
|
||
<Button name="$parentDebuff2" inherits="BenillaPetDebuffTemplate" id="2">
|
||
<Anchors><Anchor point="LEFT" relativeTo="PetFrameDebuff1" relativePoint="RIGHT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor></Anchors>
|
||
</Button>
|
||
<Button name="$parentDebuff3" inherits="BenillaPetDebuffTemplate" id="3">
|
||
<Anchors><Anchor point="LEFT" relativeTo="PetFrameDebuff2" relativePoint="RIGHT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor></Anchors>
|
||
</Button>
|
||
<Button name="$parentDebuff4" inherits="BenillaPetDebuffTemplate" id="4">
|
||
<Anchors><Anchor point="LEFT" relativeTo="PetFrameDebuff3" relativePoint="RIGHT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor></Anchors>
|
||
</Button>
|
||
</Frames>
|
||
<Scripts>
|
||
<OnLoad>PetFrame_OnLoad()</OnLoad>
|
||
<OnEvent>PetFrame_OnEvent(event)</OnEvent>
|
||
<OnUpdate>PetFrame_OnUpdate(arg1)</OnUpdate>
|
||
<OnEnter>BenillaPetFrame_OnEnter(this)</OnEnter>
|
||
<OnLeave>if SpellIsTargeting() then SetCursor("CAST_ERROR_CURSOR") end GameTooltip:FadeOut()</OnLeave>
|
||
<OnClick>PetFrame_OnClick(arg1)</OnClick>
|
||
</Scripts>
|
||
</Frame>
|
||
</Ui>
|