- 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>
233 lines
10 KiB
XML
233 lines
10 KiB
XML
<Ui>
|
|
<!--
|
|
The mirror timers - our MirrorTimer1/2/3. The extracted reference
|
|
(Interface\FrameXML\MirrorTimer.xml/.lua, mpqx; decision 0874) is the SPECIFICATION here,
|
|
not the source: it fixes the frame and function names addons bind to, the geometry, the
|
|
textures and the four colours, and this file is our own implementation of that contract.
|
|
|
|
"Mirror" is the client's word for a timer whose authority is the SERVER: the client never
|
|
computes breath or fatigue. The server ships a value + a signed rate and the frame
|
|
integrates `value + scale * elapsed` between packets - that OnUpdate line IS the whole of
|
|
the client-side motion. crate::ui_mirror fires the three events off the wire:
|
|
MIRROR_TIMER_START (name, remaining_ms, duration_ms, scale, paused, label) /
|
|
MIRROR_TIMER_PAUSE (name, paused) / MIRROR_TIMER_STOP (name).
|
|
|
|
arg1 is always the timer's NAME - "EXHAUSTION" / "BREATH" / "FEIGNDEATH" - the same key
|
|
MirrorTimerColors is indexed by, and the same key the client builds `<NAME>_LABEL` from to
|
|
look up the bar's caption ("Fatigue" / "Breath"; there is no FEIGNDEATH_LABEL in the 1.12
|
|
GlobalStrings, so that one shows an empty caption, exactly as the reference does).
|
|
|
|
Where our behaviour departs from the reference's, all three deliberate:
|
|
|
|
1. THE START DRIVER. The reference registers MIRROR_TIMER_START on UIParent itself and
|
|
calls MirrorTimer_Show from UIParent_OnEvent (ref UIParent.lua l.97 + l.374-375). Our
|
|
UIParent.xml is script-only - it has no frame of its own to register events on - so the
|
|
hidden MirrorTimerDriver below hosts that one registration and makes the identical call.
|
|
|
|
2. ONE WALK OVER THE POOL, NOT TWO. The reference asks its two questions - "is this timer
|
|
already on a bar?" then "is any bar idle?" - in two separate loops, and bounds the
|
|
second by STATICPOPUP_NUMDIALOGS, a copy-paste from StaticPopup.lua that would index a
|
|
nil MirrorTimer4 the day the two constants diverge. A bar already carrying the timer
|
|
wins over an idle one wherever either sits, so one pass answers both questions and
|
|
cannot run off the end of the pool.
|
|
|
|
3. THE PAUSE ARG. The reference's MIRROR_TIMER_PAUSE branch reads the same arg1 as both a
|
|
timer name (`arg1 ~= this.timer`, above) and a number (`arg1 > 0`), so a real pause
|
|
packet errors out in the reference UI - which is why vmangos refuses to send one and
|
|
substitutes a full START instead (Player::SendMirrorTimers says so in the source). We
|
|
read the flag from arg2, which is what both branches plainly intend.
|
|
|
|
POSITION: TOP, y=-96 - the reference's own anchor, under the top-center of the screen, with
|
|
timers 2 and 3 stacking downward from it.
|
|
-->
|
|
<Script><![CDATA[
|
|
MIRRORTIMER_NUMTIMERS = 3
|
|
|
|
-- The bar colour per timer type, keyed by the name that arrives on the wire. DEATH is in
|
|
-- the published set even though nothing in 1.12 ever raises a timer of that type - the
|
|
-- table is the contract, so it carries the whole set and not just the three we see.
|
|
MirrorTimerColors = {
|
|
EXHAUSTION = { r = 1.00, g = 0.90, b = 0.00 },
|
|
BREATH = { r = 0.00, g = 0.50, b = 1.00 },
|
|
DEATH = { r = 1.00, g = 0.70, b = 0.00 },
|
|
FEIGNDEATH = { r = 1.00, g = 0.70, b = 0.00 },
|
|
}
|
|
|
|
-- Which bar `timer` belongs on. A bar already carrying it wins wherever it sits - the
|
|
-- server re-sends a whole START on every change, and a re-STATE must land back in place
|
|
-- instead of eating another bar; failing that the first idle bar takes it, and if all
|
|
-- three are busy the timer simply goes unshown (see departure 2).
|
|
local function claim_bar(timer)
|
|
local idle
|
|
for index = 1, MIRRORTIMER_NUMTIMERS do
|
|
local bar = getglobal("MirrorTimer" .. index)
|
|
if bar:IsVisible() then
|
|
if bar.timer == timer then
|
|
return bar
|
|
end
|
|
elseif not idle then
|
|
idle = bar
|
|
end
|
|
end
|
|
return idle
|
|
end
|
|
|
|
-- Hand a bar back to the pool: off screen, holding nothing.
|
|
local function release_bar(bar)
|
|
bar:Hide()
|
|
bar.timer = nil
|
|
end
|
|
|
|
-- Raise or re-seat one timer. Milliseconds on the wire, seconds on the bar; `scale` is
|
|
-- the server's signed rate, which OnUpdate integrates from here.
|
|
function MirrorTimer_Show(timer, value, maxvalue, scale, paused, label)
|
|
local bar = claim_bar(timer)
|
|
if not bar then
|
|
return nil
|
|
end
|
|
|
|
bar.timer = timer
|
|
bar.value = value / 1000
|
|
bar.scale = scale
|
|
bar.paused = paused > 0 and 1 or nil
|
|
|
|
local name = bar:GetName()
|
|
getglobal(name .. "Text"):SetText(label)
|
|
|
|
local fill = getglobal(name .. "StatusBar")
|
|
local color = MirrorTimerColors[timer]
|
|
fill:SetMinMaxValues(0, maxvalue / 1000)
|
|
fill:SetValue(bar.value)
|
|
fill:SetStatusBarColor(color.r, color.g, color.b)
|
|
|
|
bar:Show()
|
|
return bar
|
|
end
|
|
|
|
function MirrorTimerFrame_OnLoad()
|
|
-- The two events that address a timer already running...
|
|
this:RegisterEvent("MIRROR_TIMER_PAUSE")
|
|
this:RegisterEvent("MIRROR_TIMER_STOP")
|
|
-- ...and the one that ends whatever this bar was holding.
|
|
this:RegisterEvent("PLAYER_ENTERING_WORLD")
|
|
this.timer = nil
|
|
end
|
|
|
|
function MirrorTimerFrame_OnEvent()
|
|
if event == "PLAYER_ENTERING_WORLD" then
|
|
-- A worldport ends every timer the old world was running; a stale breath bar must
|
|
-- not survive the loading screen.
|
|
release_bar(this)
|
|
return
|
|
end
|
|
-- Everything below names one running timer. Anything else is another bar's business.
|
|
if not this:IsShown() or arg1 ~= this.timer then
|
|
return
|
|
end
|
|
if event == "MIRROR_TIMER_PAUSE" then
|
|
-- The flag rides in arg2 (departure 3), and absent means running.
|
|
this.paused = (arg2 and arg2 > 0) and 1 or nil
|
|
elseif event == "MIRROR_TIMER_STOP" then
|
|
release_bar(this)
|
|
end
|
|
end
|
|
|
|
-- The whole of the client-side motion: between packets the bar walks itself along the
|
|
-- rate the server named. Nothing here decides anything - drowning is the server's call.
|
|
function MirrorTimerFrame_OnUpdate(frame, elapsed)
|
|
if not frame.paused then
|
|
frame.value = frame.value + frame.scale * elapsed
|
|
getglobal(frame:GetName() .. "StatusBar"):SetValue(frame.value)
|
|
end
|
|
end
|
|
|
|
-- The ref's UIParent_OnEvent MIRROR_TIMER_START arm, rehosted (see departure 1).
|
|
function MirrorTimerDriver_OnLoad()
|
|
this:RegisterEvent("MIRROR_TIMER_START")
|
|
end
|
|
|
|
function MirrorTimerDriver_OnEvent()
|
|
MirrorTimer_Show(arg1, arg2, arg3, arg4, arg5, arg6)
|
|
end
|
|
]]></Script>
|
|
|
|
<Frame name="MirrorTimerTemplate" toplevel="true" parent="UIParent" hidden="true" virtual="true">
|
|
<Size><AbsDimension x="206" y="26"/></Size>
|
|
<Layers>
|
|
<Layer level="BACKGROUND">
|
|
<Texture>
|
|
<Size><AbsDimension x="195" y="13"/></Size>
|
|
<Anchors>
|
|
<Anchor point="TOP">
|
|
<Offset><AbsDimension x="0" y="-2"/></Offset>
|
|
</Anchor>
|
|
</Anchors>
|
|
<Color r="0" g="0" b="0" a="0.5"/>
|
|
</Texture>
|
|
</Layer>
|
|
<Layer level="OVERLAY">
|
|
<FontString name="$parentText" inherits="GameFontHighlight">
|
|
<Anchors>
|
|
<Anchor point="TOP">
|
|
<Offset><AbsDimension x="0" y="0"/></Offset>
|
|
</Anchor>
|
|
</Anchors>
|
|
</FontString>
|
|
<Texture name="$parentBorder" file="Interface\CastingBar\UI-CastingBar-Border">
|
|
<Size><AbsDimension x="256" y="64"/></Size>
|
|
<Anchors>
|
|
<Anchor point="TOP">
|
|
<Offset><AbsDimension x="0" y="25"/></Offset>
|
|
</Anchor>
|
|
</Anchors>
|
|
</Texture>
|
|
</Layer>
|
|
</Layers>
|
|
<Frames>
|
|
<StatusBar name="$parentStatusBar">
|
|
<Size><AbsDimension x="195" y="13"/></Size>
|
|
<Anchors>
|
|
<Anchor point="TOP">
|
|
<Offset><AbsDimension x="0" y="-2"/></Offset>
|
|
</Anchor>
|
|
</Anchors>
|
|
<Scripts>
|
|
<OnLoad>this:SetFrameLevel(this:GetFrameLevel() - 1)</OnLoad>
|
|
</Scripts>
|
|
<BarTexture file="Interface\TargetingFrame\UI-StatusBar"/>
|
|
</StatusBar>
|
|
</Frames>
|
|
<Scripts>
|
|
<OnLoad>MirrorTimerFrame_OnLoad()</OnLoad>
|
|
<OnEvent>MirrorTimerFrame_OnEvent()</OnEvent>
|
|
<OnUpdate>MirrorTimerFrame_OnUpdate(this, arg1)</OnUpdate>
|
|
</Scripts>
|
|
</Frame>
|
|
|
|
<Frame name="MirrorTimer1" inherits="MirrorTimerTemplate">
|
|
<Anchors>
|
|
<Anchor point="TOP">
|
|
<Offset><AbsDimension x="0" y="-96"/></Offset>
|
|
</Anchor>
|
|
</Anchors>
|
|
</Frame>
|
|
<Frame name="MirrorTimer2" inherits="MirrorTimerTemplate">
|
|
<Anchors>
|
|
<Anchor point="TOP" relativeTo="MirrorTimer1" relativePoint="BOTTOM"/>
|
|
</Anchors>
|
|
</Frame>
|
|
<Frame name="MirrorTimer3" inherits="MirrorTimerTemplate">
|
|
<Anchors>
|
|
<Anchor point="TOP" relativeTo="MirrorTimer2" relativePoint="BOTTOM"/>
|
|
</Anchors>
|
|
</Frame>
|
|
|
|
<!-- The START driver (departure 1): no art, no size - one event registration. -->
|
|
<Frame name="MirrorTimerDriver" parent="UIParent">
|
|
<Scripts>
|
|
<OnLoad>MirrorTimerDriver_OnLoad()</OnLoad>
|
|
<OnEvent>MirrorTimerDriver_OnEvent()</OnEvent>
|
|
</Scripts>
|
|
</Frame>
|
|
</Ui>
|