Files
benilla-octo/crates/benilla/assets/ui/UnitFrames.xml
T
SamandClaude Fable 5 94de741cb1 /target, /assist and /follow, and right click opens a clam
- /target and /assist find units by name like the original, and /follow walks you behind your target
- right click opens clams and lockboxes, and the tooltip promises it
- the freeze thaws clean, poses release instead of sticking, and you drop to neutral standing before it grips
- unit frame art layers over the bars like the original

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 18:02:44 +07:00

1156 lines
70 KiB
XML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<Ui>
<!--
benilla's 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 BenillaTargetFrame_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, transcribed 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.
-->
<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 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)
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")
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)
BenillaUnitFrame_Update(frame)
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.
function BenillaUnitFrame_OnEnter(frame)
local unit = frame.unit
if not UnitExists(unit) then return end
GameTooltip_SetDefaultAnchor(GameTooltip, frame)
if SHOW_NEWBIE_TIPS == "1" then
if frame:GetName() == "BenillaPlayerFrame" then
GameTooltip_AddNewbieTip(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
GameTooltip_AddNewbieTip(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)
if SHOW_NEWBIE_TIPS == "1" then
GameTooltip:Hide()
else
GameTooltip:FadeOut()
end
end
function BenillaUnitFrame_OnEvent(frame)
-- 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
BenillaUnitFrame_Update(frame)
-- 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) ──────────────────────
-- Ref CombatFeedback.lua, transcribed onto frame-param style (the ref reads `this`).
-- Timings/colors/size factors verbatim. 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",
}
function CombatFeedback_OnCombatEvent(frame, event, flags, amount, type)
local feedbackText = frame.feedbackText
if not feedbackText then return end
local fontHeight = frame.feedbackFontHeight
local text = ""
local r, g, b = 1.0, 1.0, 1.0
if event == "IMMUNE" then
fontHeight = fontHeight * 0.5
text = CombatFeedbackText[event]
elseif event == "WOUND" then
if amount ~= 0 then
if flags == "CRITICAL" or flags == "CRUSHING" then
fontHeight = fontHeight * 1.5
elseif flags == "GLANCING" then
fontHeight = fontHeight * 0.75
end
-- type > 0 = a spell school: the number reads yellow, physical white.
if type > 0 then
r, g, b = 1.0, 1.0, 0.0
end
text = amount
elseif flags == "ABSORB" then
fontHeight = fontHeight * 0.75
text = CombatFeedbackText["ABSORB"]
elseif flags == "BLOCK" then
fontHeight = fontHeight * 0.75
text = CombatFeedbackText["BLOCK"]
elseif flags == "RESIST" then
fontHeight = fontHeight * 0.75
text = CombatFeedbackText["RESIST"]
else
text = CombatFeedbackText["MISS"]
end
elseif event == "BLOCK" then
fontHeight = fontHeight * 0.75
text = CombatFeedbackText[event]
elseif event == "HEAL" then
text = amount
r, g, b = 0.0, 1.0, 0.0
if flags == "CRITICAL" then
fontHeight = fontHeight * 1.5
end
elseif event == "ENERGIZE" then
text = amount
r, g, b = 0.41, 0.8, 0.94
if flags == "CRITICAL" then
fontHeight = fontHeight * 1.5
end
else
text = CombatFeedbackText[event]
end
frame.feedbackStartTime = GetTime()
feedbackText:SetTextHeight(fontHeight)
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) — transcribed 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 BenillaTargetFrame_CheckClassification(frame)
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 BenillaUnitFrame_Update(frame)
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 = UnitPowerMax(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)
frame.powerBar:SetMinMaxValues(0, maxPower)
frame.powerBar:SetValue(UnitPower(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
BenillaTargetFrame_CheckClassification(frame)
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 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), transcribed 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 (BenillaUnitFrame_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("BenillaTargetFrameBuff" .. 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("BenillaTargetFrameDebuff" .. 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
BenillaTargetFrameBuff1:SetPoint("TOPLEFT", "BenillaTargetFrame", "BOTTOMLEFT", 5, 32)
BenillaTargetFrameDebuff1:SetPoint("TOPLEFT", "BenillaTargetFrameBuff1", "BOTTOMLEFT", 0, -2)
else
BenillaTargetFrameDebuff1:SetPoint("TOPLEFT", "BenillaTargetFrame", "BOTTOMLEFT", 5, 32)
BenillaTargetFrameBuff1:SetPoint("TOPLEFT", "BenillaTargetFrameDebuff7", "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("BenillaTargetFrameBuff" .. 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("BenillaTargetFrameDebuff" .. 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("BenillaTargetFrameDebuff" .. debuffWrap):ClearAllPoints()
getglobal("BenillaTargetFrameDebuff" .. debuffWrap):SetPoint("LEFT", getglobal("BenillaTargetFrameDebuff" .. (debuffWrap - 1)), "RIGHT", 3, 0)
getglobal("BenillaTargetFrameDebuff" .. (debuffWrap + 1)):ClearAllPoints()
getglobal("BenillaTargetFrameDebuff" .. (debuffWrap + 1)):SetPoint("TOPLEFT", "BenillaTargetFrameDebuff1", "BOTTOMLEFT", 0, -2)
getglobal("BenillaTargetFrameDebuff" .. (debuffWrap + 2)):ClearAllPoints()
getglobal("BenillaTargetFrameDebuff" .. (debuffWrap + 2)):SetPoint("LEFT", getglobal("BenillaTargetFrameDebuff" .. (debuffWrap + 1)), "RIGHT", 3, 0)
if debuffWrap == 5 then
BenillaTargetFrameDebuff11:ClearAllPoints()
BenillaTargetFrameDebuff11:SetPoint("TOPLEFT", "BenillaTargetFrameDebuff6", "BOTTOMLEFT", 0, -2)
else
BenillaTargetFrameDebuff11:ClearAllPoints()
BenillaTargetFrameDebuff11:SetPoint("LEFT", "BenillaTargetFrameDebuff10", "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
-- 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 BenillaPlayerFrame_OnClick(button)
if button == "RightButton" then
ToggleDropDownMenu(1, nil, PlayerFrameDropDown, "BenillaPlayerFrame", 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
function TargetFrameDropDown_Initialize()
local menu
local name
if UnitIsUnit("target", "player") then
menu = "SELF"
elseif UnitIsPlayer("target") then
if UnitInParty("target") then
menu = "PARTY"
else
menu = "PLAYER"
end
else
-- The ref checks UnitIsUnit("target", "pet") → PET before this branch; no "pet"
-- token exists until the pet slice (phase 4).
menu = "RAID_TARGET_ICON"
name = RAID_TARGET_ICON
end
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 BenillaTargetFrame_OnClick(button)
if button == "RightButton" then
ToggleDropDownMenu(1, nil, TargetFrameDropDown, "BenillaTargetFrame", 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="BenillaPlayerFrame" 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">
<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"/>
</StatusBar>
<!-- Power bar 119×12 TOPLEFT (106,-52), UI-StatusBar; color set at runtime (ref l.316-341). -->
<StatusBar name="$parentPowerBar">
<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"/>
</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>
</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>
</Layer>
<Layer level="OVERLAY">
<!-- Name 100×10 CENTER (50,19) (ref l.58-69). -->
<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 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>
</Layer>
</Layers>
</Frame>
</Frames>
<Scripts>
<OnLoad><![CDATA[BenillaUnitFrame_OnLoad(this, "player")]]></OnLoad>
<OnEvent><![CDATA[BenillaUnitFrame_OnEvent(this)]]></OnEvent>
<OnUpdate><![CDATA[CombatFeedback_OnUpdate(this)]]></OnUpdate>
<OnEnter><![CDATA[BenillaUnitFrame_OnEnter(this)]]></OnEnter>
<OnLeave><![CDATA[BenillaUnitFrame_OnLeave(this)]]></OnLeave>
<OnClick><![CDATA[BenillaPlayerFrame_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="BenillaPlayerFrame" 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="BenillaTargetFrame" 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">
<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"/>
</StatusBar>
<!-- Power bar 119×12 TOPRIGHT (-106,-52), UI-StatusBar; color set at runtime (ref l.274-292). -->
<StatusBar name="$parentPowerBar">
<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"/>
</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>
</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[BenillaUnitFrame_OnEvent(this)]]></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[BenillaTargetFrame_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="BenillaTargetFrame" 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>
</Ui>