- the world-state readout — the top-centre counters a battleground or Silithus keeps up — takes the reference's real geometry: every icon those rows name is a sprite authored into the upper-left corner of a power-of-two canvas (`AllianceTower` is 16×16 of ink at (3,2) in a 32×32 file), nothing anywhere crops it, and the reference compensates geometrically — a 42×42 slot hung 6 units off the row's left edge with the label seated 10 above its centreline, taken as one system; our snug 20×20 box stripped exactly that compensation, so the art drew at half size floating away from its own `0/200` label — and an empty icon column now reserves its rect instead of sliding the text left, which straightens the readout's left edge - the dynamic icon is a second slot, not a swap: `Icon` and `DynamicIcon` feed two different regions and shipped content never mixes them, so the flag is a 32×32 button off the row's right edge with an ADD-blend flash overlay pulsing 0.5 s in / 0.5 s out, lit only at `uiState == 2` — the flag-taken value; before, any non-zero state replaced the static art, which fires on the ordinary state 1 and hid a faction shield that should never have moved, and the pulse's alpha ramp is now pinned by a test, not just its art - the dev cost pill yields the band: the readout is the game, the pill is scaffolding, and a stock 1.12 UI has no free corner to move house to — so it steps below whatever the readout draws, reading `WorldStateAlwaysUpFrame:GetBottom()` on its own 4 Hz cadence and only while it is drawing, rather than mirroring the frame's row pitch in Rust; the probe's cost is stated at the site (an edge read settles the layout — up to one graph solve, ~47 µs, four times a second) - the Hearthglen ballista's bolt heads and shields stop flickering: a shine layer is a second batch over its base's own triangles, and ours split the pair across lanes — the base rode the retained lane's f64 CPU bake while the env-mapped, additive, no-depth-write shine stayed on the entity path's f32 GPU rotate, and a few ULPs between coplanar surfaces turns the depth test into a per-pixel coin flip re-rolled by every camera move; a batch now names the M2 skin section it draws, neither member of a shared section diverts into a consolidator (depths equal by construction), and a corpus census bounds the cost — 1,080 of 9,691 models draw a section more than once, 264 such sections across 221 `World\` doodads — with toggling pixels at the pin dropping 0.26% to 0.14%, matching the consolidators-off arm bit-for-bit Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
281 lines
15 KiB
XML
281 lines
15 KiB
XML
<Ui>
|
|
<!--
|
|
The always-up world-state readout — report B190's second half: the alliance/horde progress
|
|
line an outdoor-PvP zone or a battleground shows at the top of the screen. In Eastern
|
|
Plaguelands that is the two tower counters ("Towers Controlled: 3"); in Warsong Gulch the
|
|
flag captures; in Arathi Basin bases and resources.
|
|
|
|
The SPEC is the reference's two C bindings, byte-carved (wow-re
|
|
system/ui/scratch/worldstate-ui-law.md, 0x4c5a40 / 0x4c5a70) and re-expressed here:
|
|
|
|
GetNumWorldStateUI() -> how many rows the current scope admits
|
|
GetWorldStateUIInfo(i) -> TEN values, in this order:
|
|
uiState, text, icon, dynamicIcon,
|
|
tooltip, dynamicTooltip, extendedUI,
|
|
extUIState1, extUIState2, extUIState3
|
|
|
|
Note what that return is NOT: there is no uiType and no hidden in 1.12 — those are a later
|
|
expansion of the API, and reading this list as if they were there shifts every value by two.
|
|
`text` arrives already macro-expanded (the host runs the reference's %<id>w expander);
|
|
every other string is its DBC column verbatim, and never nil — an empty column is "".
|
|
|
|
Which rows exist at all is the host's (crates/benilla-app/src/world_state_ui.rs), including
|
|
the gate that makes this frame look broken if you don't know about it: the world-PvP rows
|
|
(Eastern Plaguelands, Silithus) are Type 1, which the reference admits only while the player
|
|
has joined the zone-dependent DEFENSE chat channel. Leave LocalDefense and the readout goes
|
|
away while the map icons stay.
|
|
|
|
THE GEOMETRY IS THE REFERENCE'S, and it is not what you would guess (wow-re
|
|
worldstate-ui-law.md §12, carved 2026-08-25; decision 1604 folds it back). The first pass
|
|
of this frame invented a snug 20x20 icon in a 22px row, and that was wrong in a way that
|
|
showed: EVERY icon these rows name is a sprite authored into the UPPER-LEFT corner of a
|
|
power-of-two canvas with the rest transparent — AllianceTower/HordeTower are a 16x16 sprite
|
|
at (3,2) of a 32x32 file, UI-PVP-Alliance/Horde a ~40x40 crest in a 64x64 one. Squeeze that
|
|
whole canvas into a snug box and the visible art floats up and to the left of the box,
|
|
away from its label. NOTHING IS CROPPED to fix that — there is no <TexCoords> on any of
|
|
these regions, and a texture with none samples the full 0..1 quad (CSimpleTexture's ctor
|
|
0x76fc40 stamps it). The compensation is geometric, and these three numbers are one tuned
|
|
system: the icon slot is OVERSIZED to 42x42 (a 32px file upscaled 1.3125x), hung SIX UNITS
|
|
OFF the row's left edge, and the text is seated TEN UNITS ABOVE the slot's centreline. The
|
|
upper-left ink then lands as a ~21x21 square whose centre sits just under the text's, which
|
|
is what "icon next to its label" is supposed to look like.
|
|
|
|
THERE ARE TWO ICON SLOTS, fed by two different DBC columns, and shipped content never puts
|
|
the same art in both:
|
|
|
|
Icon (+0x0c) -> the 42x42 slot below. AllianceTower, HordeTower,
|
|
UI-PVP-Alliance, UI-PVP-Horde.
|
|
DynamicIcon (+0x64) -> a SEPARATE 32x32 button off the row's right edge, shown only
|
|
while uiState == 2. HordeFlag/AllianceFlag, Warsong Gulch only.
|
|
|
|
So the dynamic icon does NOT replace the static one — both are on screen together, which is
|
|
the Warsong Gulch look: the faction shield with its capture count, and a flashing enemy
|
|
flag to its right while a carrier is running. `uiState == 2` is the flag-taken value; the
|
|
first pass swapped the art on `uiState ~= 0`, which fired on the ordinary state 1 and
|
|
replaced a shield that should never have moved.
|
|
|
|
OUT, named: the CAPTUREPOINT extended UI — the sliding bar over
|
|
Interface\WorldStateFrame\WorldState-CaptureBar that the one Eastern Plaguelands progress
|
|
row drives. Its two states come through the binding already (extUIState1/2 carry the
|
|
alliance/horde fractions), so it is a widget to add, not a seam to change. NOW KNOWN, and
|
|
worth writing down before someone builds it: in the reference that row never reaches this
|
|
frame at all — the update path branches on a non-empty extendedUI FIRST, and id 138 is the
|
|
only always-up-class row with one, so EPL's real readout is two text rows here plus a
|
|
separate bar anchored under the MinimapCluster. Until that bar exists we keep showing the
|
|
row as its text line (decision 1590 §7's stand-in, unchanged) so the progress number is on
|
|
screen; it costs a third line the reference doesn't draw, and it goes when the bar lands.
|
|
Also out: the battleground scoreboard (the Type 2 rows), which the reference builds from the
|
|
same DBC through a DIFFERENT handler and a different pair of bindings whose bail leg is not
|
|
even the same shape — GetNumBattlefieldStats/GetBattlefieldStatInfo, a separate slice.
|
|
-->
|
|
<Script>
|
|
<![CDATA[
|
|
NUM_ALWAYS_UP_ROWS = 0;
|
|
|
|
-- The reference's own numbers (worldstate-ui-law.md §12). The row is an anchor node, not a
|
|
-- box: its 42-wide icon slot hangs off its left edge and its text runs past its right, so
|
|
-- none of these are "the readout's size" — they are the offsets that seat corner-authored
|
|
-- art against a label. The pitch is the template height, because rows chain TOP to the
|
|
-- previous row's BOTTOM with no gap.
|
|
WORLD_STATE_ROW_HEIGHT = 24;
|
|
-- The dynamic-icon flash: 0.5 s in, 0.5 s out, indefinitely.
|
|
WORLD_STATE_FLASH_HALF = 0.5;
|
|
|
|
-- The flash overlay's ramp. The art (…Flash.blp) is DXT1 with no alpha channel — black IS
|
|
-- the transparency — which is why it draws ADD; the pulse is the region's own alpha.
|
|
function WorldStateDynamicIcon_OnUpdate(elapsed)
|
|
local t = (this.flashTime or 0) + elapsed;
|
|
local period = 2 * WORLD_STATE_FLASH_HALF;
|
|
while ( t >= period ) do
|
|
t = t - period;
|
|
end
|
|
this.flashTime = t;
|
|
local level = t / WORLD_STATE_FLASH_HALF;
|
|
if ( level > 1 ) then
|
|
level = 2 - level;
|
|
end
|
|
getglobal(this:GetName() .. "Flash"):SetAlpha(level);
|
|
end
|
|
|
|
-- Rebuild the readout from the host's list. Cheap enough to run whole on every fire: the
|
|
-- list is at most a handful of rows, and the events are a zone's worth apart.
|
|
function WorldStateAlwaysUpFrame_Update()
|
|
local numUI = GetNumWorldStateUI();
|
|
|
|
-- Grow the row pool on demand and never shrink it, the way every pooled list here
|
|
-- does: the tail from a busier zone is parked hidden rather than destroyed. Row 1
|
|
-- hangs off the container; every later row chains to the one above it, so the pitch is
|
|
-- the template's height and lives in exactly one place.
|
|
if ( numUI > NUM_ALWAYS_UP_ROWS ) then
|
|
for i = NUM_ALWAYS_UP_ROWS + 1, numUI do
|
|
local row = CreateFrame("Frame", "WorldStateAlwaysUpFrame" .. i,
|
|
WorldStateAlwaysUpFrame, "WorldStateAlwaysUpTemplate");
|
|
if ( i == 1 ) then
|
|
row:SetPoint("TOP", WorldStateAlwaysUpFrame, "TOP", -23, -20);
|
|
else
|
|
row:SetPoint("TOP", getglobal("WorldStateAlwaysUpFrame" .. (i - 1)),
|
|
"BOTTOM", 0, 0);
|
|
end
|
|
end
|
|
NUM_ALWAYS_UP_ROWS = numUI;
|
|
end
|
|
|
|
for i = 1, NUM_ALWAYS_UP_ROWS do
|
|
local row = getglobal("WorldStateAlwaysUpFrame" .. i);
|
|
if ( i > numUI ) then
|
|
row:Hide();
|
|
else
|
|
local uiState, text, icon, dynamicIcon, tooltip, dynamicTooltip = GetWorldStateUIInfo(i);
|
|
local name = row:GetName();
|
|
|
|
-- The static slot takes its column verbatim. An EMPTY column is not a special
|
|
-- case: SetTexture("") clears the art and draws nothing while the authored
|
|
-- 42x42 rect — and so everything anchored to it — stays exactly where it was.
|
|
-- The slot is reserved and blank, never collapsed; a row that slid its text
|
|
-- left into the empty slot would ragged the readout's left edge, and the
|
|
-- reference does no such thing.
|
|
getglobal(name .. "Icon"):SetTexture(icon);
|
|
getglobal(name .. "Text"):SetText(text);
|
|
row.tooltip = tooltip;
|
|
|
|
-- The dynamic slot is a SEPARATE button off the row's right edge, and it is
|
|
-- the flag-taken state alone that shows it.
|
|
local dynamic = getglobal(name .. "DynamicIcon");
|
|
if ( dynamicIcon ~= "" and uiState == 2 ) then
|
|
getglobal(dynamic:GetName() .. "Texture"):SetTexture(dynamicIcon);
|
|
getglobal(dynamic:GetName() .. "Flash"):SetTexture(dynamicIcon .. "Flash");
|
|
dynamic.tooltip = dynamicTooltip;
|
|
dynamic.flashTime = 0;
|
|
dynamic:Show();
|
|
else
|
|
dynamic:Hide();
|
|
end
|
|
row:Show();
|
|
end
|
|
end
|
|
|
|
-- OURS, and non-visual: the reference's container is a bare 10x10 anchor node it never
|
|
-- resizes, so its own bottom says nothing about how far the rows reach. We keep the
|
|
-- authored size but grow the height to the rows' real extent, so "how much of the top
|
|
-- of the screen is this using?" is a question the frame can answer — the dev cost pill
|
|
-- asks it, and seats itself below (crates/benilla-app/src/perf/hud.rs). Rows anchor to
|
|
-- the container's TOP, which is fixed, so nothing moves.
|
|
if ( numUI > 0 ) then
|
|
WorldStateAlwaysUpFrame:SetHeight(20 + numUI * WORLD_STATE_ROW_HEIGHT);
|
|
WorldStateAlwaysUpFrame:Show();
|
|
else
|
|
WorldStateAlwaysUpFrame:Hide();
|
|
end
|
|
end
|
|
|
|
function WorldStateAlwaysUpFrame_OnEvent()
|
|
WorldStateAlwaysUpFrame_Update();
|
|
end
|
|
]]>
|
|
</Script>
|
|
|
|
<!-- One row: the oversized icon slot, its line of text, and the dynamic-icon button that only
|
|
Warsong Gulch ever lights. Grown on demand into WorldStateAlwaysUpFrame by the update
|
|
above. Every offset here is the reference's — see the header for why they are not the
|
|
snug numbers you would pick by eye. -->
|
|
<Frame name="WorldStateAlwaysUpTemplate" virtual="true" enableMouse="true">
|
|
<Size><AbsDimension x="45" y="24"/></Size>
|
|
<Layers>
|
|
<Layer level="ARTWORK">
|
|
<Texture name="$parentIcon">
|
|
<Size><AbsDimension x="42" y="42"/></Size>
|
|
<Anchors>
|
|
<Anchor point="LEFT">
|
|
<Offset><AbsDimension x="-6" y="0"/></Offset>
|
|
</Anchor>
|
|
</Anchors>
|
|
</Texture>
|
|
<FontString name="$parentText" inherits="GameFontNormalSmall" justifyH="LEFT">
|
|
<Anchors>
|
|
<Anchor point="LEFT" relativeTo="$parentIcon" relativePoint="RIGHT">
|
|
<Offset><AbsDimension x="-12" y="10"/></Offset>
|
|
</Anchor>
|
|
</Anchors>
|
|
</FontString>
|
|
</Layer>
|
|
</Layers>
|
|
<Frames>
|
|
<Button name="$parentDynamicIcon" hidden="true">
|
|
<Size><AbsDimension x="32" y="32"/></Size>
|
|
<Anchors>
|
|
<Anchor point="RIGHT">
|
|
<Offset><AbsDimension x="40" y="9"/></Offset>
|
|
</Anchor>
|
|
</Anchors>
|
|
<Layers>
|
|
<Layer level="ARTWORK">
|
|
<Texture name="$parentTexture">
|
|
<Size><AbsDimension x="32" y="32"/></Size>
|
|
<Anchors><Anchor point="CENTER"/></Anchors>
|
|
</Texture>
|
|
</Layer>
|
|
<Layer level="OVERLAY">
|
|
<Texture name="$parentFlash" alphaMode="ADD">
|
|
<Size><AbsDimension x="32" y="32"/></Size>
|
|
<Anchors><Anchor point="CENTER"/></Anchors>
|
|
</Texture>
|
|
</Layer>
|
|
</Layers>
|
|
<Scripts>
|
|
<OnUpdate>
|
|
WorldStateDynamicIcon_OnUpdate(arg1);
|
|
</OnUpdate>
|
|
<OnEnter>
|
|
if ( this.tooltip and this.tooltip ~= "" ) then
|
|
GameTooltip:SetOwner(this, "ANCHOR_BOTTOMLEFT");
|
|
GameTooltip:SetText(this.tooltip);
|
|
GameTooltip:Show();
|
|
end
|
|
</OnEnter>
|
|
<OnLeave>
|
|
GameTooltip:Hide();
|
|
</OnLeave>
|
|
</Scripts>
|
|
</Button>
|
|
</Frames>
|
|
<Scripts>
|
|
<OnEnter>
|
|
if ( this.tooltip and this.tooltip ~= "" ) then
|
|
GameTooltip:SetOwner(this, "ANCHOR_BOTTOMLEFT");
|
|
GameTooltip:SetText(this.tooltip);
|
|
GameTooltip:Show();
|
|
end
|
|
</OnEnter>
|
|
<OnLeave>
|
|
GameTooltip:Hide();
|
|
</OnLeave>
|
|
</Scripts>
|
|
</Frame>
|
|
|
|
<!-- The container. Top-centre of the screen, which is where an outdoor-PvP zone's counters and
|
|
a battleground's score belong: the minimap owns the top-right and the readout must not
|
|
fight it. The reference declares it 10x10 and never touches it again — it is an anchor
|
|
node, and rows overflow it freely (see the update for the one thing we do differently, and
|
|
why). Hidden until a scope admits a row. -->
|
|
<Frame name="WorldStateAlwaysUpFrame" parent="UIParent" toplevel="true" hidden="true">
|
|
<Size><AbsDimension x="10" y="10"/></Size>
|
|
<Anchors>
|
|
<Anchor point="TOP" relativeTo="UIParent" relativePoint="TOP">
|
|
<Offset><AbsDimension x="-5" y="-15"/></Offset>
|
|
</Anchor>
|
|
</Anchors>
|
|
<Scripts>
|
|
<OnLoad>
|
|
-- UPDATE_WORLD_STATES is the reference's own event for this list (0x20e). The
|
|
-- entering-world fire is what paints the readout on a login that lands inside a
|
|
-- scope the server has already sent states for.
|
|
this:RegisterEvent("UPDATE_WORLD_STATES");
|
|
this:RegisterEvent("PLAYER_ENTERING_WORLD");
|
|
</OnLoad>
|
|
<OnEvent>
|
|
WorldStateAlwaysUpFrame_OnEvent();
|
|
</OnEvent>
|
|
</Scripts>
|
|
</Frame>
|
|
</Ui>
|