- 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>
426 lines
22 KiB
XML
426 lines
22 KiB
XML
<!--
|
|
ZoneText.xml — the zone-entry splash (decision 0287): ZoneTextFrame (the THICK-outline zone
|
|
name — nominal FontHeight 102, DRAWN at the 32px one-to-one raster cap like the real client
|
|
(wow-re fontstring-overflow.md), which is why it fits its 512-wide box on one line — + the PvP
|
|
territory line) and SubZoneTextFrame (the 26px subzone line + the FFA "PvP Area" line), faded
|
|
by the FadingFrame kit when the app fires the ZONE_CHANGED family off the player's area
|
|
(`area.rs`).
|
|
|
|
The Lua below is OURS: our own implementation of the behaviour ref-ZoneText.xml specifies, as
|
|
the live 5875 patch chain ships that file (patch.MPQ overrides the interface.MPQ copy — decision
|
|
0246). What is quoted from it, deliberately, is the published surface and the measurements — the
|
|
global names addons call (SetZoneText, the three Zone*Duration constants, every frame and
|
|
fontstring name), the event names, the colours, and the frame geometry — because those are facts
|
|
about how the real interface looks and behaves rather than choices of ours. All THREE of the
|
|
reference file's frames are here, the third being AutoFollowStatus (the /follow status text, and
|
|
the only thing ref-ZoneText.lua actually contains), which waited here until follow support
|
|
existed and no longer does. The FadingFrame kit (ref-FadingFrame.lua, its own TOC line right
|
|
before ZoneText.xml) is implemented below too — our own code behind that file's published API,
|
|
since ZoneTextFrame/SubZoneTextFrame are its only consumers in the whole 1.12 FrameXML
|
|
(grep-verified over the extraction), so it rides with them. AutoFollowStatus is NOT one of them —
|
|
it rolls its own fade, which is why the kit stays a two-frame affair.
|
|
|
|
The subtle law worth naming (it looks like a bug and is not): SubZoneTextString — a child of
|
|
SubZoneTextFrame — anchors to regions of the *other* frame (ZoneTextString / PVPInfoTextString),
|
|
which is usually hidden when only the subzone changes. The resolver computes hidden frames'
|
|
rects all the same, so a subzone-only splash seats where it would sit *under the invisible zone
|
|
name* — the authentic lower position. ZoneTextFrame's OnEvent caches GetZoneText() silently on
|
|
plain ZONE_CHANGED and splashes only when an INDOORS/NEW_AREA event finds the zone text changed;
|
|
the territory line is asked for on NEW_AREA alone.
|
|
-->
|
|
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
|
..\FrameXML\UI.xsd">
|
|
<Script><![CDATA[
|
|
-- ref-GlobalStrings.lua values this file formats (the CastingBar.xml guarded idiom):
|
|
CONTESTED_TERRITORY = CONTESTED_TERRITORY or "Contested Territory";
|
|
FACTION_CONTROLLED_TERRITORY = FACTION_CONTROLLED_TERRITORY or "%s Territory"; -- Faction name
|
|
FREE_FOR_ALL_TERRITORY = FREE_FOR_ALL_TERRITORY or "PvP Area";
|
|
AUTOFOLLOWSTART = AUTOFOLLOWSTART or "Following %s.";
|
|
AUTOFOLLOWSTOP = AUTOFOLLOWSTOP or "You stop following %s.";
|
|
|
|
-- The ref's localization identity (UIParent.lua), which the AutoFollowStatus body below
|
|
-- puts its message through as the reference does. Guarded like everything else here rather
|
|
-- than leaning on UnitPopup.xml happening to load first — a one-line helper is no place to
|
|
-- acquire a load-order edge.
|
|
if ( not TEXT ) then
|
|
TEXT = function(s) return s end
|
|
end
|
|
|
|
-- ── The FadingFrame kit ────────────────────────────────────────────────────────────────
|
|
-- Our implementation of the helper ref-FadingFrame.lua specifies. The global names and the
|
|
-- `this.*` fields ARE that file's published API — addons and the frames below reach for them
|
|
-- by name — so those are fixed; the code behind them is ours.
|
|
--
|
|
-- The contract: FadingFrame_Show() starts one timeline on the frame — ramp alpha up over
|
|
-- fadeInTime, hold at full for holdTime, ramp back down over fadeOutTime, then hide. A leg
|
|
-- configured to zero is simply skipped, and a frame that has never been configured (all
|
|
-- three zero, per OnLoad) hides on its very first update.
|
|
function FadingFrame_SetFadeInTime(this, time)
|
|
this.fadeInTime = time;
|
|
end
|
|
function FadingFrame_SetHoldTime(this, time)
|
|
this.holdTime = time;
|
|
end
|
|
function FadingFrame_SetFadeOutTime(this, time)
|
|
this.fadeOutTime = time;
|
|
end
|
|
function FadingFrame_OnLoad()
|
|
-- Through the setters, so the field names are written down in exactly one place.
|
|
FadingFrame_SetFadeInTime(this, 0);
|
|
FadingFrame_SetHoldTime(this, 0);
|
|
FadingFrame_SetFadeOutTime(this, 0);
|
|
this:Hide();
|
|
end
|
|
function FadingFrame_Show()
|
|
this.startTime = GetTime();
|
|
this:Show();
|
|
end
|
|
function FadingFrame_OnUpdate()
|
|
-- One timeline, one alpha: which leg `elapsed` has reached decides the value, and
|
|
-- running off the end of the last leg is the only way the frame stops.
|
|
local elapsed = GetTime() - this.startTime;
|
|
local fadeIn = this.fadeInTime;
|
|
local holdEnds = fadeIn + this.holdTime;
|
|
local alpha;
|
|
if ( elapsed < fadeIn ) then
|
|
alpha = elapsed / fadeIn;
|
|
elseif ( elapsed < holdEnds ) then
|
|
alpha = 1.0;
|
|
elseif ( elapsed < (holdEnds + this.fadeOutTime) ) then
|
|
alpha = 1.0 - ((elapsed - holdEnds) / this.fadeOutTime);
|
|
else
|
|
this:Hide();
|
|
return;
|
|
end
|
|
this:SetAlpha(alpha);
|
|
end
|
|
|
|
-- ── The zone splash ───────────────────────────────────────────────────────────────────
|
|
-- Our implementation of what ref-ZoneText.xml's own <Script> block and its two splash
|
|
-- frames' handlers specify. The names below are that file's published API — addons re-render
|
|
-- the splash through SetZoneText and retune it by writing the three durations — so the
|
|
-- names and the numbers are fixed; the code between them is ours.
|
|
ZoneFadeInDuration = 0.5;
|
|
ZoneHoldDuration = 1.0;
|
|
ZoneFadeOutDuration = 2.0;
|
|
|
|
-- How a HELD zone is tinted, keyed by who holds it. Three cases differing only in three
|
|
-- floats want to be data — the same shape MinimapCluster.xml keeps for the minimap's zone
|
|
-- label, which reads the same GetZonePVPInfo; duplicated there and here rather than
|
|
-- published as a global the reference does not have. A pvpType that is not in the table is
|
|
-- unheld (sanctuary, or a type we do not know), and gets no territory line and the zone
|
|
-- font's own parchment colour back — ZoneTextFont's <Color> in Fonts.xml is this triple.
|
|
local ZONE_PVP_TINT = {
|
|
friendly = { r = 0.1, g = 1.0, b = 0.1 },
|
|
hostile = { r = 1.0, g = 0.1, b = 0.1 },
|
|
contested = { r = 1.0, g = 0.7, b = 0 },
|
|
};
|
|
local UNHELD_ZONE_TINT = { r = 1.0, g = 0.9294, b = 0.7607 };
|
|
|
|
-- The line under the zone name saying who holds the place. The localisation globals are
|
|
-- read here, at call time, rather than folded into the table above: something later in the
|
|
-- load order is allowed to replace them.
|
|
local function ZoneTerritoryLine(pvpType, factionName)
|
|
if ( pvpType == "contested" ) then
|
|
return CONTESTED_TERRITORY;
|
|
end
|
|
return format(FACTION_CONTROLLED_TERRITORY, factionName);
|
|
end
|
|
|
|
-- Re-render the whole splash from the host's idea of where the player is: the zone name,
|
|
-- its tint, the territory line under it, the free-for-all call-out, and where the subzone
|
|
-- line hangs. `showPVPInfo` is the caller's answer to "is this a NEW zone?" — a hop between
|
|
-- subzones of one zone re-renders without it and gets the same tint with no territory line.
|
|
function SetZoneText(showPVPInfo)
|
|
ZoneTextString:SetText(GetZoneText());
|
|
|
|
local pvpType, factionName, isArena = GetZonePVPInfo();
|
|
local held = ZONE_PVP_TINT[pvpType];
|
|
local tint = held or UNHELD_ZONE_TINT;
|
|
ZoneTextString:SetTextColor(tint.r, tint.g, tint.b);
|
|
SubZoneTextString:SetTextColor(tint.r, tint.g, tint.b);
|
|
if ( held ) then
|
|
-- The territory line wears the tint too — including when the branch below finds it
|
|
-- has nothing to say. Nothing on screen can tell (a blank string has no colour to
|
|
-- show), but an addon that reads the tint off the blank line reads this zone's.
|
|
-- Guarded, because the unheld tint is the zone FONT's parchment and this line, a
|
|
-- SubZoneTextFont string, is never given it.
|
|
PVPInfoTextString:SetTextColor(tint.r, tint.g, tint.b);
|
|
end
|
|
|
|
-- The territory line and the subzone line's seat are one decision, not two: the line
|
|
-- is there only when somebody holds this zone AND the caller asked for it, and the
|
|
-- subzone line drops to hang off the line exactly when there is one to hang off.
|
|
if ( held and showPVPInfo ) then
|
|
PVPInfoTextString:SetText(ZoneTerritoryLine(pvpType, factionName));
|
|
SubZoneTextString:SetPoint("TOP", "PVPInfoTextString", "BOTTOM", 0, 0);
|
|
else
|
|
PVPInfoTextString:SetText("");
|
|
SubZoneTextString:SetPoint("TOP", "ZoneTextString", "BOTTOM", 0, 0);
|
|
end
|
|
|
|
-- A free-for-all pit is called out under the subzone line and drags that line to the
|
|
-- hostile red whatever the zone around it is — inside the pit, everyone is hostile.
|
|
if ( isArena ) then
|
|
local ffa = ZONE_PVP_TINT.hostile;
|
|
PVPArenaTextString:SetText(FREE_FOR_ALL_TERRITORY);
|
|
SubZoneTextString:SetTextColor(ffa.r, ffa.g, ffa.b);
|
|
else
|
|
PVPArenaTextString:SetText("");
|
|
end
|
|
end
|
|
|
|
-- Both splash frames are the same kind of thing — one fade timeline, and all three of the
|
|
-- events the client splits an area change into (a move within the current zone, an interior
|
|
-- threshold crossed, a genuinely new zone) — so the arming is written once and neither can
|
|
-- drift from the other. Each decides for itself, in its OnEvent, whether the line it owns
|
|
-- actually changed.
|
|
function ZoneSplashFrame_OnLoad()
|
|
FadingFrame_OnLoad();
|
|
FadingFrame_SetFadeInTime(this, ZoneFadeInDuration);
|
|
FadingFrame_SetHoldTime(this, ZoneHoldDuration);
|
|
FadingFrame_SetFadeOutTime(this, ZoneFadeOutDuration);
|
|
this:RegisterEvent("ZONE_CHANGED");
|
|
this:RegisterEvent("ZONE_CHANGED_INDOORS");
|
|
this:RegisterEvent("ZONE_CHANGED_NEW_AREA");
|
|
end
|
|
|
|
-- The big zone name. A plain ZONE_CHANGED never splashes it: that event fires whenever the
|
|
-- player crosses a subzone boundary inside one zone, so all it may do is keep the cached
|
|
-- name honest. That silent cache write IS the subtle law in the header — it is what makes a
|
|
-- later INDOORS/NEW_AREA that lands on the same name find nothing left to say.
|
|
function ZoneTextFrame_OnEvent(event)
|
|
local zoneText = GetZoneText();
|
|
local changed = zoneText ~= this.zoneText;
|
|
this.zoneText = zoneText;
|
|
if ( event == "ZONE_CHANGED" or not changed ) then
|
|
return;
|
|
end
|
|
-- Only a genuinely new zone earns the territory line under the name.
|
|
SetZoneText(event == "ZONE_CHANGED_NEW_AREA");
|
|
FadingFrame_Show();
|
|
end
|
|
|
|
function SubZoneTextFrame_OnLoad()
|
|
ZoneSplashFrame_OnLoad();
|
|
-- The free-for-all call-out is red for good, so it is tinted once here rather than on
|
|
-- every splash.
|
|
local ffa = ZONE_PVP_TINT.hostile;
|
|
PVPArenaTextString:SetTextColor(ffa.r, ffa.g, ffa.b);
|
|
-- Prime both lines from wherever the player already is: this runs at load, and the next
|
|
-- area event may not arrive until they walk somewhere.
|
|
SetZoneText(true);
|
|
SubZoneTextString:SetText(GetSubZoneText());
|
|
end
|
|
|
|
-- The small subzone line. Any of the three events can splash it, and the text it is already
|
|
-- showing IS its cache — it has no silent path to keep a separate one honest.
|
|
function SubZoneTextFrame_OnEvent()
|
|
local subZoneText = GetSubZoneText();
|
|
if ( subZoneText == SubZoneTextString:GetText() ) then
|
|
return;
|
|
end
|
|
SubZoneTextString:SetText(subZoneText);
|
|
-- Re-render the zone half too, territory line and all. Registered second, this frame is
|
|
-- dispatched last of the two (FIFO — wow-re `event-dispatch-order.md`), so on a new zone
|
|
-- it is this call that leaves the territory line standing under the name.
|
|
SetZoneText(true);
|
|
FadingFrame_Show();
|
|
end
|
|
|
|
-- ── The auto-follow status line ────────────────────────────────────────────────────────
|
|
-- Our implementation of what ref-ZoneText.lua specifies — the only thing that .lua actually
|
|
-- contains, and the reason it exists at all. Note it does NOT use the FadingFrame kit
|
|
-- above: it rolls a plain linear alpha fade with no fade-in and no hold, armed only by
|
|
-- AUTOFOLLOW_END. BEGIN is a full reset (fade cancelled, alpha restored, shown), which is
|
|
-- why a follow that switches subject needs no END first. The 4s is the reference's.
|
|
AUTOFOLLOW_STATUS_FADETIME = 4.0;
|
|
|
|
function AutoFollowStatus_OnLoad()
|
|
this:RegisterEvent("AUTOFOLLOW_BEGIN");
|
|
this:RegisterEvent("AUTOFOLLOW_END");
|
|
this:RegisterEvent("PLAYER_ENTERING_WORLD");
|
|
end
|
|
|
|
function AutoFollowStatus_OnEvent(event)
|
|
if ( event == "PLAYER_ENTERING_WORLD" ) then
|
|
-- A world load clears the line outright: nothing to say, and nothing to fade.
|
|
this:Hide();
|
|
return;
|
|
end
|
|
|
|
-- Both follow events land the same way — put up a line naming this.unit and show it —
|
|
-- so all they differ in is the message and what they do to the fade.
|
|
local message;
|
|
if ( event == "AUTOFOLLOW_BEGIN" ) then
|
|
this.unit = arg1;
|
|
this.fadeTime = nil;
|
|
this:SetAlpha(1.0);
|
|
message = AUTOFOLLOWSTART;
|
|
elseif ( event == "AUTOFOLLOW_END" ) then
|
|
this.fadeTime = AUTOFOLLOW_STATUS_FADETIME;
|
|
message = AUTOFOLLOWSTOP;
|
|
else
|
|
return;
|
|
end
|
|
AutoFollowStatusText:SetText(format(TEXT(message), this.unit));
|
|
this:Show();
|
|
end
|
|
|
|
function AutoFollowStatus_OnUpdate(elapsed)
|
|
-- Only AUTOFOLLOW_END arms the countdown; until then the line just sits at full alpha.
|
|
local remaining = this.fadeTime;
|
|
if ( not remaining ) then
|
|
return;
|
|
end
|
|
-- Out of time: hide, and leave fadeTime alone. It is stale from here until the next
|
|
-- BEGIN clears it or the next END re-arms it, and a hidden frame gets no OnUpdate.
|
|
if ( elapsed >= remaining ) then
|
|
this:Hide();
|
|
return;
|
|
end
|
|
remaining = remaining - elapsed;
|
|
this.fadeTime = remaining;
|
|
this:SetAlpha(remaining / AUTOFOLLOW_STATUS_FADETIME);
|
|
end
|
|
]]></Script>
|
|
<!-- The two splash frames. Same box, same seat on screen, same fade: one carries the zone name
|
|
and the territory line under it, the other the subzone line and the free-for-all call-out.
|
|
Geometry is the reference's measurement, not a choice of ours — the 512-wide strings inside
|
|
a 128 box anchored BOTTOM +512 are what the real splash occupies.
|
|
(ref carries toplevel="true" on both; still dropped here, but the reason has changed.
|
|
`SetToplevel` is no longer a gap — the raise law is built, `script::object::toplevel` —
|
|
and its real trigger is Show, not a click, so "click-raise is meaningless on mouse-less
|
|
splash text" was the wrong argument. What holds now: these are LOW-strata splash frames
|
|
that overlap nothing in LOW, so the occlusion gate declines. One word to re-add.) -->
|
|
<Frame name="ZoneTextFrame" parent="UIParent" frameStrata="LOW">
|
|
<Anchors>
|
|
<Anchor point="BOTTOM">
|
|
<Offset>
|
|
<AbsDimension x="0" y="512"/>
|
|
</Offset>
|
|
</Anchor>
|
|
</Anchors>
|
|
<Size>
|
|
<AbsDimension x="128" y="128"/>
|
|
</Size>
|
|
<Layers>
|
|
<Layer level="ARTWORK">
|
|
<FontString name="ZoneTextString" inherits="ZoneTextFont">
|
|
<Size>
|
|
<AbsDimension x="512" y="0"/>
|
|
</Size>
|
|
<Anchors>
|
|
<Anchor point="CENTER"/>
|
|
</Anchors>
|
|
</FontString>
|
|
<FontString name="PVPInfoTextString" inherits="SubZoneTextFont">
|
|
<Size>
|
|
<AbsDimension x="512" y="0"/>
|
|
</Size>
|
|
<Anchors>
|
|
<Anchor point="TOP" relativeTo="ZoneTextString" relativePoint="BOTTOM"/>
|
|
</Anchors>
|
|
</FontString>
|
|
</Layer>
|
|
</Layers>
|
|
<Scripts>
|
|
<OnLoad>
|
|
ZoneSplashFrame_OnLoad();
|
|
</OnLoad>
|
|
<OnEvent>
|
|
ZoneTextFrame_OnEvent(event);
|
|
</OnEvent>
|
|
<OnUpdate>
|
|
FadingFrame_OnUpdate();
|
|
</OnUpdate>
|
|
</Scripts>
|
|
</Frame>
|
|
<Frame name="SubZoneTextFrame" parent="UIParent" frameStrata="LOW">
|
|
<Anchors>
|
|
<Anchor point="BOTTOM">
|
|
<Offset>
|
|
<AbsDimension x="0" y="512"/>
|
|
</Offset>
|
|
</Anchor>
|
|
</Anchors>
|
|
<Size>
|
|
<AbsDimension x="128" y="128"/>
|
|
</Size>
|
|
<Layers>
|
|
<Layer level="ARTWORK">
|
|
<FontString name="SubZoneTextString" inherits="SubZoneTextFont">
|
|
<Size>
|
|
<AbsDimension x="512" y="0"/>
|
|
</Size>
|
|
<Anchors>
|
|
<Anchor point="TOP" relativeTo="ZoneTextString" relativePoint="BOTTOM"/>
|
|
</Anchors>
|
|
</FontString>
|
|
<FontString name="PVPArenaTextString" inherits="SubZoneTextFont">
|
|
<Size>
|
|
<AbsDimension x="512" y="0"/>
|
|
</Size>
|
|
<Anchors>
|
|
<Anchor point="TOP" relativeTo="SubZoneTextString" relativePoint="BOTTOM"/>
|
|
</Anchors>
|
|
</FontString>
|
|
</Layer>
|
|
</Layers>
|
|
<Scripts>
|
|
<OnLoad>
|
|
SubZoneTextFrame_OnLoad();
|
|
</OnLoad>
|
|
<OnEvent>
|
|
SubZoneTextFrame_OnEvent();
|
|
</OnEvent>
|
|
<OnUpdate>
|
|
FadingFrame_OnUpdate();
|
|
</OnUpdate>
|
|
</Scripts>
|
|
</Frame>
|
|
<!-- The auto-follow status line — the third frame ref-ZoneText.xml declares, laid out as that
|
|
file specifies it (the handlers behind it, like every other line of Lua here, are ours).
|
|
It is the only thing ref-ZoneText.lua actually contains, and the reason that .lua exists at
|
|
all; it waited here until follow support did (decisions 0890, 0893). `setAllPoints` over
|
|
UIParent, so the line sits dead centre of the screen. BACKGROUND strata: it is under
|
|
everything, which is why a full-screen panel hides it.
|
|
|
|
ONE DEVIATION, forced: the ref anchors the string CENTER to **WorldFrame**, and benilla has
|
|
no WorldFrame — it is the reference's 3D-scene layer frame, which our renderer does not
|
|
model as a frame at all. This is the ONLY `relativeTo="WorldFrame"` in the whole 1.12
|
|
FrameXML (grep-verified over the extraction), so it buys no convention; left verbatim it
|
|
merely logged an unresolved-anchor WARN every boot and fell back to the owner. UIParent is
|
|
the right substitute and not an approximation: both are full-screen in the reference, so
|
|
their CENTERs coincide, and AutoFollowStatus is setAllPoints over UIParent anyway. -->
|
|
<Frame name="AutoFollowStatus" parent="UIParent" setAllPoints="true" frameStrata="BACKGROUND" hidden="true">
|
|
<Layers>
|
|
<Layer level="ARTWORK">
|
|
<FontString name="AutoFollowStatusText" inherits="GameFontNormal">
|
|
<Anchors>
|
|
<Anchor point="CENTER" relativeTo="UIParent">
|
|
<Offset>
|
|
<AbsDimension x="0" y="0"/>
|
|
</Offset>
|
|
</Anchor>
|
|
</Anchors>
|
|
<FontHeight>
|
|
<AbsValue val="20"/>
|
|
</FontHeight>
|
|
</FontString>
|
|
</Layer>
|
|
</Layers>
|
|
<Scripts>
|
|
<OnLoad>
|
|
AutoFollowStatus_OnLoad();
|
|
</OnLoad>
|
|
<OnEvent>
|
|
AutoFollowStatus_OnEvent(event);
|
|
</OnEvent>
|
|
<OnUpdate>
|
|
AutoFollowStatus_OnUpdate(arg1);
|
|
</OnUpdate>
|
|
</Scripts>
|
|
</Frame>
|
|
</Ui>
|