- 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>
717 lines
40 KiB
XML
717 lines
40 KiB
XML
<Ui>
|
|
<!--
|
|
The center-screen scrolling combat text (decision 0578) — the 1.11+ "Floating Combat Text"
|
|
option, written here in our own terms. The shipped load-on-demand addon
|
|
`Blizzard_CombatText` (patch.MPQ `Interface\AddOns\Blizzard_CombatText\`, extracted to
|
|
WoW/_extracted_addons/Blizzard_CombatText/) is the SPECIFICATION, and what it fixes is
|
|
contract: the message-type keys, the option family, the scroll/fade/crit envelope and
|
|
every number in them are all addressed by name from addons, so they are reproduced exactly
|
|
while the code that carries them is ours. The picture: screen-space FontStrings scrolling
|
|
up from UIParent-bottom +384 (≈ over the character), driven by the engine events this
|
|
frame registers. Where our behaviour departs from the spec, it is named:
|
|
- The ref is load-on-demand (`UIParentLoadAddOn` when SHOW_COMBAT_TEXT == "1"); we load
|
|
always and let CombatText_UpdateDisplayedMessages gate registration, ref-identically.
|
|
- SHOW_COMBAT_TEXT defaults "1" here (ref "0") — the director's ask IS the enabled
|
|
experience; every sub-toggle keeps its ref default (UIOptionsFrame.lua l.69-84).
|
|
- The ref's virtual FontString template (CombatTextTemplate) expands to 20 identical
|
|
strings; region templates are outside the v1 loader's object model (BuffFrame.xml has
|
|
the precedent note), so the 20 instances inherit the CombatTextFont Font object and
|
|
carry the template's anchor directly.
|
|
- UNIT_MANA → UNIT_POWER_UPDATE + the Era Unit* API (our event surface, decision 0068);
|
|
same low-mana law. PLAYER_COMBO_POINTS rides the combo wire (0869); its arm reads
|
|
`GetComboPoints()`, so it is rogue/druid-only and per-target for free — the binding
|
|
gates, not this file (0875).
|
|
- Lua 5.0 `for k,v in t` iteration becomes explicit index loops (5.1 VM), backwards
|
|
where the body removes.
|
|
- COMBAT_TEXT_TYPE_INFO is grown from a compact spec (colour · gate · flags · the types
|
|
that share them) walked by the loop under it, instead of one literal per row: the same
|
|
facts with the repetition taken out. The table it produces IS the contract — the same
|
|
keys with the same values, and one fresh entry table per key, because `show` is
|
|
rewritten per entry and an addon may edit any of them.
|
|
- The two low-resource warnings, the two flow directions in CombatText_AddMessage, and the
|
|
"park a string back at the seat" step all appear once here; the spec writes each of them
|
|
out twice.
|
|
The wire → messageType mapping feeding COMBAT_TEXT_UPDATE lives app-side
|
|
(net/apply/combat_log.rs + ui_unit.rs) and is PROVISIONAL pending the dispatched wow-re
|
|
emission pin (decision 0578).
|
|
-->
|
|
<Script><![CDATA[
|
|
-- ── The pool ────────────────────────────────────────────────────────────────────────────
|
|
-- One screen line per pool string, and the list of the ones currently in flight. The pool
|
|
-- is fixed: a message arriving with all twenty busy evicts the oldest.
|
|
NUM_COMBAT_TEXT_LINES = 20
|
|
COMBAT_TEXT_TO_ANIMATE = {}
|
|
|
|
-- ── Where a line sits ───────────────────────────────────────────────────────────────────
|
|
-- COMBAT_TEXT_LOCATIONS is the seat and the target the current float mode implies; it is
|
|
-- filled in by CombatText_UpdateDisplayedMessages, not here. A new line is pushed clear of
|
|
-- everything already in flight by one line plus COMBAT_TEXT_SPACING, but the stack never
|
|
-- runs further than COMBAT_TEXT_MAX_OFFSET from the seat — past that it starts over. The
|
|
-- staggered types jitter sideways by up to half COMBAT_TEXT_STAGGER_RANGE either way, so a
|
|
-- flurry of hits does not print as one column.
|
|
COMBAT_TEXT_LOCATIONS = {}
|
|
COMBAT_TEXT_SPACING = 10
|
|
COMBAT_TEXT_MAX_OFFSET = 130
|
|
COMBAT_TEXT_STAGGER_RANGE = 20
|
|
|
|
-- ── How long a line lives ───────────────────────────────────────────────────────────────
|
|
-- It travels for COMBAT_TEXT_SCROLLSPEED seconds; COMBAT_TEXT_FADEOUT_TIME is when the
|
|
-- fade STARTS, not how long it takes, so the fade is the tail of the same window.
|
|
COMBAT_TEXT_SCROLLSPEED = 1.9
|
|
COMBAT_TEXT_FADEOUT_TIME = 1.3
|
|
|
|
-- ── How big it is drawn ─────────────────────────────────────────────────────────────────
|
|
-- An ordinary line is COMBAT_TEXT_HEIGHT and stays there. A crit "pops": seeded at the
|
|
-- minimum, grown to the maximum across CRIT_SCALE_TIME, and back down to ordinary size by
|
|
-- CRIT_SHRINKTIME (CombatText_OnUpdate walks that envelope).
|
|
COMBAT_TEXT_HEIGHT = 25
|
|
COMBAT_TEXT_CRIT_MINHEIGHT = 30
|
|
COMBAT_TEXT_CRIT_MAXHEIGHT = 60
|
|
COMBAT_TEXT_CRIT_SCALE_TIME = 0.05
|
|
COMBAT_TEXT_CRIT_SHRINKTIME = 0.2
|
|
|
|
-- ── When the low-resource warnings latch ────────────────────────────────────────────────
|
|
-- Fractions of the bar, tested on the way down; see CombatText_LowResourceWarning.
|
|
COMBAT_TEXT_LOW_HEALTH_THRESHOLD = 0.2
|
|
COMBAT_TEXT_LOW_MANA_THRESHOLD = 0.2
|
|
|
|
-- The option family: every saved global this feature owns, next to the value it ships
|
|
-- with. All fourteen sit at the spec's defaults (UIOptionsFrame.lua l.69-84) except the
|
|
-- master SHOW_COMBAT_TEXT, which ships "1" against the spec's "0" — the director's ask IS
|
|
-- the enabled experience (see the header).
|
|
--
|
|
-- Each name is assigned AT FILE SCOPE and registered for saving in the same pass, because
|
|
-- the load order is what makes a saved value stick (decision 1128): defaults first, the
|
|
-- saved chunk over the top, consumers third; reverse any two and a saved value can never
|
|
-- win. The Options window's Combat page drives all fourteen (decision 1134) and takes its
|
|
-- "Defaults" from these very globals, read at each row's OnLoad — which runs before the
|
|
-- saved chunk — so a default lives in exactly one place, this table, and the panel cannot
|
|
-- drift from it.
|
|
local combatTextOptions = {
|
|
{"SHOW_COMBAT_TEXT", "1"},
|
|
{"COMBAT_TEXT_SHOW_LOW_HEALTH_MANA", "1"},
|
|
{"COMBAT_TEXT_SHOW_AURAS", "1"},
|
|
{"COMBAT_TEXT_SHOW_AURA_FADE", "0"},
|
|
{"COMBAT_TEXT_SHOW_COMBAT_STATE", "1"},
|
|
{"COMBAT_TEXT_SHOW_DODGE_PARRY_MISS", "0"},
|
|
{"COMBAT_TEXT_SHOW_RESISTANCES", "0"},
|
|
{"COMBAT_TEXT_SHOW_REPUTATION", "0"},
|
|
{"COMBAT_TEXT_SHOW_REACTIVES", "0"},
|
|
{"COMBAT_TEXT_SHOW_FRIENDLY_NAMES", "0"},
|
|
{"COMBAT_TEXT_SHOW_COMBO_POINTS", "0"},
|
|
{"COMBAT_TEXT_SHOW_MANA", "0"},
|
|
{"COMBAT_TEXT_SHOW_HONOR_GAINED", "1"},
|
|
{"COMBAT_TEXT_FLOAT_MODE", "1"},
|
|
}
|
|
for i = 1, getn(combatTextOptions) do
|
|
local name, default = combatTextOptions[i][1], combatTextOptions[i][2]
|
|
setglobal(name, default)
|
|
RegisterForSave(name)
|
|
end
|
|
|
|
-- The message words (shipped enUS GlobalStrings values, hardcoded like the rest of our
|
|
-- enUS-only data; or-guarded so an earlier definition wins).
|
|
MANA = MANA or "Mana"
|
|
RAGE = RAGE or "Rage"
|
|
FOCUS = FOCUS or "Focus"
|
|
ENERGY = ENERGY or "Energy"
|
|
HEALTH_LOW = HEALTH_LOW or "Health Low"
|
|
MANA_LOW = MANA_LOW or "Mana Low"
|
|
ENTERING_COMBAT = ENTERING_COMBAT or "Entering Combat"
|
|
LEAVING_COMBAT = LEAVING_COMBAT or "Leaving Combat"
|
|
BLOCK = BLOCK or "Block"
|
|
ABSORB = ABSORB or "Absorb"
|
|
RESIST = RESIST or "Resist"
|
|
SPELL_MISSED = SPELL_MISSED or "Miss"
|
|
SPELL_DODGED = SPELL_DODGED or "Dodge"
|
|
SPELL_PARRIED = SPELL_PARRIED or "Parry"
|
|
SPELL_BLOCKED = SPELL_BLOCKED or "Block"
|
|
SPELL_EVADED = SPELL_EVADED or "Evade"
|
|
SPELL_IMMUNE = SPELL_IMMUNE or "Immune"
|
|
SPELL_DEFLECTED = SPELL_DEFLECTED or "Deflect"
|
|
SPELL_REFLECTED = SPELL_REFLECTED or "Reflect"
|
|
SPELL_RESISTED = SPELL_RESISTED or "Resist"
|
|
SPELL_ABSORBED = SPELL_ABSORBED or "Absorb"
|
|
AURA_END = AURA_END or "<%s> fades"
|
|
BLOCK_TRAILER = BLOCK_TRAILER or " (%d blocked)"
|
|
ABSORB_TRAILER = ABSORB_TRAILER or " (%d absorbed)"
|
|
RESIST_TRAILER = RESIST_TRAILER or " (%d resisted)"
|
|
COMBAT_TEXT_HONOR_GAINED = COMBAT_TEXT_HONOR_GAINED or "Honor %s"
|
|
|
|
--[[
|
|
COMBAT_TEXT_TYPE_INFO — one entry per message type, and the four things an entry says:
|
|
|
|
r, g, b the colour the line prints in, as 0..1 floats
|
|
show 1 while this type is currently being printed, nil while it is not.
|
|
CombatText_UpdateDisplayedMessages rewrites it for every gated type from
|
|
the option `var` names, so an entry carrying `var` and no `show` has
|
|
merely not been refreshed yet, while an entry carrying `show` and no
|
|
`var` is on for as long as the feature itself is
|
|
isStaggered 1 to jitter the line sideways instead of printing it on the centre line
|
|
var the name of the saved global that gates this type; nil = ungated
|
|
|
|
This table is API: addons index it by message-type key and read and write these fields, so
|
|
the keys and the values are exactly the specification's. The rows below are OURS — almost
|
|
every type shares its colour and its gate with several others, so the facts are written
|
|
once per group and the loop underneath expands them into one entry per type. Fresh table
|
|
per entry, never a shared one: `show` is rewritten per entry, and an addon may edit any
|
|
single entry without that leaking into its neighbours.
|
|
]]
|
|
|
|
-- The six colours the whole table draws from.
|
|
local CT_WHITE = {1, 1, 1}
|
|
local CT_RED = {1, 0.1, 0.1}
|
|
local CT_GREEN = {0.1, 1, 0.1}
|
|
local CT_BLUE = {0.1, 0.1, 1}
|
|
local CT_VIOLET = {0.79, 0.3, 0.85}
|
|
local CT_GOLD = {1, 0.82, 0}
|
|
|
|
-- Colour · gate · flags · the message types that share them. `always` is the `show` an
|
|
-- ungated type is born with; `jitter` is `isStaggered`; `gate` is `var`.
|
|
local combatTextSpec = {
|
|
-- Always on. Only the master toggle takes these away.
|
|
{colour = CT_RED, always = 1, jitter = 1, types = {"DAMAGE"}},
|
|
{colour = CT_RED, always = 1, types = {"DAMAGE_CRIT"}},
|
|
{colour = CT_VIOLET, always = 1, types = {"SPELL_DAMAGE"}},
|
|
{colour = CT_WHITE, always = 1, types = {"SPLIT_DAMAGE"}},
|
|
{colour = CT_GREEN, always = 1, types = {"HEAL", "HEAL_CRIT", "PERIODIC_HEAL", "SPELL_CAST"}},
|
|
|
|
-- Never on by themselves: no `always`, no gate, so `show` is nil and stays nil. They
|
|
-- carry a colour so that anything which does switch one on has one ready.
|
|
{colour = CT_WHITE, types = {"INTERRUPT", "ENCHANTMENT_ADDED", "ENCHANTMENT_REMOVED",
|
|
"DISPEL_FAILED", "SPELL_CAST_START", "SPELL_DISPELLED", "DAMAGE_SHIELD",
|
|
"EXTRA_ATTACKS"}},
|
|
|
|
-- "Show dodge/parry/miss": swings that went nowhere, melee in red and spells in white.
|
|
-- The melee whiffs jitter because several can land in the same instant.
|
|
{colour = CT_RED, gate = "COMBAT_TEXT_SHOW_DODGE_PARRY_MISS", jitter = 1,
|
|
types = {"MISS", "DODGE", "PARRY", "EVADE"}},
|
|
{colour = CT_RED, gate = "COMBAT_TEXT_SHOW_DODGE_PARRY_MISS",
|
|
types = {"IMMUNE", "DEFLECT"}},
|
|
{colour = CT_WHITE, gate = "COMBAT_TEXT_SHOW_DODGE_PARRY_MISS",
|
|
types = {"SPELL_MISSED", "SPELL_DODGED", "SPELL_PARRIED", "SPELL_EVADED",
|
|
"SPELL_IMMUNE", "SPELL_DEFLECTED", "SPELL_REFLECTED"}},
|
|
|
|
-- "Show resistances": a hit that landed but was partly eaten.
|
|
{colour = CT_RED, gate = "COMBAT_TEXT_SHOW_RESISTANCES",
|
|
types = {"BLOCK", "ABSORB", "RESIST", "PROC_RESISTED"}},
|
|
{colour = CT_WHITE, gate = "COMBAT_TEXT_SHOW_RESISTANCES", types = {"SPELL_BLOCKED"}},
|
|
{colour = CT_VIOLET, gate = "COMBAT_TEXT_SHOW_RESISTANCES",
|
|
types = {"SPELL_ABSORBED", "SPELL_RESISTED"}},
|
|
|
|
-- The aura pair — what landed, and what wore off — each split by whose side it helps.
|
|
{colour = CT_GREEN, gate = "COMBAT_TEXT_SHOW_AURAS", types = {"AURA_START"}},
|
|
{colour = CT_RED, gate = "COMBAT_TEXT_SHOW_AURAS", types = {"AURA_START_HARMFUL"}},
|
|
{colour = CT_GREEN, gate = "COMBAT_TEXT_SHOW_AURA_FADE", types = {"AURA_END"}},
|
|
{colour = CT_RED, gate = "COMBAT_TEXT_SHOW_AURA_FADE", types = {"AURA_END_HARMFUL"}},
|
|
|
|
-- Resources and standing: everything you gained rather than took.
|
|
{colour = CT_BLUE, gate = "COMBAT_TEXT_SHOW_MANA",
|
|
types = {"MANA", "RAGE", "FOCUS", "ENERGY"}},
|
|
{colour = CT_BLUE, gate = "COMBAT_TEXT_SHOW_COMBO_POINTS", types = {"COMBO_POINTS"}},
|
|
{colour = CT_BLUE, gate = "COMBAT_TEXT_SHOW_REPUTATION", types = {"FACTION"}},
|
|
{colour = CT_BLUE, gate = "COMBAT_TEXT_SHOW_HONOR_GAINED", types = {"HONOR_GAINED"}},
|
|
|
|
-- The state warnings, each on its own switch.
|
|
{colour = CT_RED, gate = "COMBAT_TEXT_SHOW_LOW_HEALTH_MANA",
|
|
types = {"HEALTH_LOW", "MANA_LOW"}},
|
|
{colour = CT_RED, gate = "COMBAT_TEXT_SHOW_COMBAT_STATE",
|
|
types = {"ENTERING_COMBAT", "LEAVING_COMBAT"}},
|
|
{colour = CT_GOLD, gate = "COMBAT_TEXT_SHOW_REACTIVES", types = {"SPELL_ACTIVE"}},
|
|
}
|
|
|
|
COMBAT_TEXT_TYPE_INFO = {}
|
|
for i = 1, getn(combatTextSpec) do
|
|
local row = combatTextSpec[i]
|
|
for j = 1, getn(row.types) do
|
|
COMBAT_TEXT_TYPE_INFO[row.types[j]] = {
|
|
r = row.colour[1],
|
|
g = row.colour[2],
|
|
b = row.colour[3],
|
|
show = row.always,
|
|
isStaggered = row.jitter,
|
|
var = row.gate,
|
|
}
|
|
end
|
|
end
|
|
|
|
-- A latched low-resource warning. The line prints on the crossing INTO the danger band and
|
|
-- not again until the bar has climbed back out of it — otherwise every tick of a fight
|
|
-- spent under 20% would print another one. `flag` names the field on the CombatText frame
|
|
-- that remembers we already warned; `low` is the caller's verdict, because "low" is not
|
|
-- the same question for the two bars (mana additionally requires the bar to BE mana: a
|
|
-- rage or energy user is never low on mana). Returns the message type to print, or nil.
|
|
local function CombatText_LowResourceWarning(flag, low, messageType)
|
|
if not low then
|
|
CombatText[flag] = nil
|
|
return nil
|
|
end
|
|
if CombatText[flag] then
|
|
return nil
|
|
end
|
|
CombatText[flag] = 1
|
|
return messageType
|
|
end
|
|
|
|
-- The engine events whose entire meaning is "print this message type". Everything else
|
|
-- either carries its own type in arg1 (COMBAT_TEXT_UPDATE) or IS its own type.
|
|
local combatTextStateMessage = {
|
|
PLAYER_REGEN_DISABLED = "ENTERING_COMBAT",
|
|
PLAYER_REGEN_ENABLED = "LEAVING_COMBAT",
|
|
}
|
|
|
|
function CombatText_OnLoad()
|
|
CombatText_UpdateDisplayedMessages()
|
|
CombatText.previousMana = {}
|
|
end
|
|
|
|
function CombatText_OnEvent(event)
|
|
-- What we are about to print: the type (which picks the colour and the gating), the
|
|
-- payload it formats from, the finished line, and the optional "crit" presentation.
|
|
local messageType, message
|
|
local data = arg2
|
|
local displayType
|
|
|
|
if event == "UNIT_HEALTH" then
|
|
if arg1 == "player" then
|
|
local low = UnitHealth("player") / UnitHealthMax("player") <= COMBAT_TEXT_LOW_HEALTH_THRESHOLD
|
|
messageType = CombatText_LowResourceWarning("lowHealth", low, "HEALTH_LOW")
|
|
end
|
|
if not messageType then
|
|
return
|
|
end
|
|
elseif event == "UNIT_POWER_UPDATE" then
|
|
-- Spec: UNIT_MANA + UnitMana/UnitManaMax (header deviation — the Era surface).
|
|
-- Power type 0 is mana; the same event also carries rage, focus and energy, none
|
|
-- of which has a low warning.
|
|
local mana = UnitMana("player")
|
|
local powerType = UnitPowerType("player")
|
|
if arg1 == "player" then
|
|
local low = mana / UnitManaMax("player") <= COMBAT_TEXT_LOW_MANA_THRESHOLD and powerType == 0
|
|
messageType = CombatText_LowResourceWarning("lowMana", low, "MANA_LOW")
|
|
end
|
|
if not messageType then
|
|
return
|
|
end
|
|
elseif event == "PLAYER_COMBO_POINTS" then
|
|
-- The count itself is the payload, and a full bar is worth a crit-sized line.
|
|
-- Spending them fires this too, with nothing left to announce.
|
|
data = GetComboPoints()
|
|
if data <= 0 then
|
|
return
|
|
end
|
|
messageType = "COMBO_POINTS"
|
|
if data == MAX_COMBO_POINTS then
|
|
displayType = "crit"
|
|
end
|
|
elseif event == "COMBAT_TEXT_UPDATE" then
|
|
messageType = arg1
|
|
else
|
|
messageType = combatTextStateMessage[event] or event
|
|
end
|
|
|
|
-- The type decides the colour and whether this is printed at all. An unknown type
|
|
-- is white and, having no `show`, prints nothing — which is how a message type we do
|
|
-- not know about stays silent instead of erroring.
|
|
local info = COMBAT_TEXT_TYPE_INFO[messageType]
|
|
if not info then
|
|
info = {r = 1, g = 1, b = 1}
|
|
end
|
|
if not info.show then
|
|
return
|
|
end
|
|
|
|
local isStaggered = info.isStaggered
|
|
if messageType == "" then
|
|
message = nil
|
|
elseif messageType == "DAMAGE_CRIT" then
|
|
displayType = "crit"
|
|
message = "-" .. data
|
|
elseif messageType == "DAMAGE" or messageType == "SPELL_DAMAGE" then
|
|
message = "-" .. data
|
|
elseif messageType == "SPELL_CAST" or messageType == "AURA_START" or messageType == "AURA_START_HARMFUL" then
|
|
message = "<" .. data .. ">"
|
|
elseif messageType == "AURA_END" or messageType == "AURA_END_HARMFUL" then
|
|
message = format(AURA_END, data)
|
|
elseif messageType == "HEAL" or messageType == "PERIODIC_HEAL" then
|
|
if COMBAT_TEXT_SHOW_FRIENDLY_NAMES == "1" and messageType == "HEAL" and UnitName("player") ~= data then
|
|
message = "+" .. arg3 .. " [" .. data .. "]"
|
|
else
|
|
message = "+" .. arg3
|
|
end
|
|
elseif messageType == "HEAL_CRIT" then
|
|
displayType = "crit"
|
|
if COMBAT_TEXT_SHOW_FRIENDLY_NAMES == "1" and UnitName("player") ~= data then
|
|
message = "+" .. arg3 .. " [" .. data .. "]"
|
|
else
|
|
message = "+" .. arg3
|
|
end
|
|
elseif messageType == "MANA" then
|
|
if tonumber(data) > 0 then
|
|
data = "+" .. data
|
|
end
|
|
message = data .. " " .. MANA
|
|
elseif messageType == "RAGE" then
|
|
message = "+" .. data .. " " .. RAGE
|
|
elseif messageType == "FOCUS" then
|
|
message = "+" .. data .. " " .. FOCUS
|
|
elseif messageType == "ENERGY" then
|
|
message = "+" .. data .. " " .. ENERGY
|
|
elseif messageType == "FACTION" then
|
|
if tonumber(arg3) > 0 then
|
|
arg3 = "+" .. arg3
|
|
end
|
|
message = "(" .. data .. " " .. arg3 .. ")"
|
|
elseif messageType == "BLOCK" then
|
|
if arg3 then
|
|
message = data .. " " .. format(BLOCK_TRAILER, arg3)
|
|
else
|
|
message = BLOCK
|
|
end
|
|
elseif messageType == "ABSORB" then
|
|
if arg3 then
|
|
message = arg2 .. " " .. format(ABSORB_TRAILER, arg3)
|
|
else
|
|
message = ABSORB
|
|
end
|
|
elseif messageType == "RESIST" or messageType == "SPELL_RESISTED" then
|
|
if arg3 then
|
|
message = data .. " " .. format(RESIST_TRAILER, arg3)
|
|
else
|
|
message = RESIST
|
|
end
|
|
elseif messageType == "HONOR_GAINED" then
|
|
if tonumber(data) > 0 then
|
|
data = "+" .. data
|
|
end
|
|
message = format(COMBAT_TEXT_HONOR_GAINED, data)
|
|
elseif messageType == "SPELL_ACTIVE" then
|
|
displayType = "crit"
|
|
message = "<" .. data .. ">"
|
|
elseif messageType == "COMBO_POINTS" then
|
|
message = data .. " Combo Points"
|
|
else
|
|
message = getglobal(messageType)
|
|
if not message then
|
|
message = getglobal("COMBAT_TEXT_" .. messageType)
|
|
end
|
|
end
|
|
|
|
-- A branch that produced no line (the empty type, or a global-string lookup that
|
|
-- came back nil) simply prints nothing.
|
|
if message then
|
|
CombatText_AddMessage(message, COMBAT_TEXT_SCROLL_FUNCTION, info.r, info.g, info.b, displayType, isStaggered)
|
|
end
|
|
end
|
|
|
|
function CombatText_OnUpdate(elapsed)
|
|
-- Backwards, so the removal inside the body can't skip a neighbor (the ref's 5.0
|
|
-- `for in` + tremove tolerated it; a 5.1 index loop must not).
|
|
for i = getn(COMBAT_TEXT_TO_ANIMATE), 1, -1 do
|
|
local value = COMBAT_TEXT_TO_ANIMATE[i]
|
|
if value.scrollTime >= COMBAT_TEXT_SCROLLSPEED then
|
|
CombatText_RemoveMessage(value)
|
|
else
|
|
value.scrollTime = value.scrollTime + elapsed
|
|
local xPos, yPos = value.scrollFunction(value)
|
|
value.yPos = yPos
|
|
value:SetPoint("TOP", UIParent, "BOTTOM", xPos, yPos)
|
|
if value.scrollTime >= COMBAT_TEXT_FADEOUT_TIME then
|
|
local alpha = 1 - ((value.scrollTime - COMBAT_TEXT_FADEOUT_TIME) / (COMBAT_TEXT_SCROLLSPEED - COMBAT_TEXT_FADEOUT_TIME))
|
|
value:SetAlpha(max(alpha, 0))
|
|
end
|
|
-- The crit pop: up to the maximum inside the scale window, back down by the
|
|
-- shrink time, then it stops being a crit and holds its size.
|
|
if value.isCrit then
|
|
if value.scrollTime <= COMBAT_TEXT_CRIT_SCALE_TIME then
|
|
value:SetTextHeight(floor(COMBAT_TEXT_CRIT_MINHEIGHT + ((COMBAT_TEXT_CRIT_MAXHEIGHT - COMBAT_TEXT_CRIT_MINHEIGHT) * value.scrollTime / COMBAT_TEXT_CRIT_SCALE_TIME)))
|
|
elseif value.scrollTime <= COMBAT_TEXT_CRIT_SHRINKTIME then
|
|
value:SetTextHeight(floor(COMBAT_TEXT_CRIT_MAXHEIGHT - ((COMBAT_TEXT_CRIT_MAXHEIGHT - COMBAT_TEXT_CRIT_MINHEIGHT) * (value.scrollTime - COMBAT_TEXT_CRIT_SCALE_TIME) / (COMBAT_TEXT_CRIT_SHRINKTIME - COMBAT_TEXT_CRIT_SCALE_TIME))))
|
|
else
|
|
value.isCrit = nil
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- A string that is not in flight: invisible, hidden, and back on the seat so the next
|
|
-- caller measures it from a known place. Both ways out of the animation list end here.
|
|
local function CombatText_ParkString(string)
|
|
string:SetAlpha(0)
|
|
string:Hide()
|
|
string:SetPoint("TOP", UIParent, "BOTTOM", COMBAT_TEXT_LOCATIONS.startX, COMBAT_TEXT_LOCATIONS.startY)
|
|
end
|
|
|
|
function CombatText_AddMessage(message, scrollFunction, r, g, b, displayType, isStaggered)
|
|
local string, noStringsAvailable = CombatText_GetAvailableString()
|
|
if noStringsAvailable then
|
|
return
|
|
end
|
|
|
|
string:SetText(message)
|
|
string:SetTextColor(r, g, b)
|
|
string.scrollTime = 0
|
|
|
|
-- Where the line is seated. `flow` is the direction the STACK grows — back down the
|
|
-- way the run came, so -1 when the mode floats upward and +1 when it floats down —
|
|
-- and having it as a number is what lets one walk serve both modes. Start at the
|
|
-- string's own parked edge, then push clear of every line already in flight by one
|
|
-- 16-unit line plus COMBAT_TEXT_SPACING; if that has carried the stack further than
|
|
-- COMBAT_TEXT_MAX_OFFSET from the seat, start the column over at the parked edge
|
|
-- instead of marching off the screen. (`GetBottom`/`GetTop` are nil before the first
|
|
-- layout resolve — the parked seat stands in; identical after frame 1.)
|
|
local flow, parkedY
|
|
if COMBAT_TEXT_LOCATIONS.startY < COMBAT_TEXT_LOCATIONS.endY then
|
|
flow = -1
|
|
parkedY = string:GetBottom()
|
|
else
|
|
flow = 1
|
|
parkedY = string:GetTop()
|
|
end
|
|
parkedY = parkedY or COMBAT_TEXT_LOCATIONS.startY
|
|
|
|
local seatY = parkedY
|
|
for i = 1, getn(COMBAT_TEXT_TO_ANIMATE) do
|
|
local clearOf = COMBAT_TEXT_TO_ANIMATE[i].yPos + flow * (16 + COMBAT_TEXT_SPACING)
|
|
if (clearOf - seatY) * flow >= 0 then
|
|
seatY = clearOf
|
|
end
|
|
end
|
|
if (seatY - COMBAT_TEXT_LOCATIONS.startY) * flow > COMBAT_TEXT_MAX_OFFSET then
|
|
seatY = parkedY
|
|
end
|
|
|
|
-- Everything `displayType` decides, in one place. A crit pops through its own scale
|
|
-- envelope and always takes the straight scroll (an arc would fight the pop); a crit or
|
|
-- a sticky ends where it began, so it holds the seat instead of travelling; anything
|
|
-- else takes the caller's scroll and runs the whole way.
|
|
if displayType == "crit" then
|
|
string.scrollFunction = CombatText_StandardScroll
|
|
string.isCrit = 1
|
|
string.endY = COMBAT_TEXT_LOCATIONS.startY
|
|
string:SetTextHeight(COMBAT_TEXT_CRIT_MINHEIGHT)
|
|
else
|
|
string.scrollFunction = scrollFunction
|
|
string:SetTextHeight(COMBAT_TEXT_HEIGHT)
|
|
if displayType == "sticky" then
|
|
string.endY = COMBAT_TEXT_LOCATIONS.startY
|
|
else
|
|
string.endY = COMBAT_TEXT_LOCATIONS.endY
|
|
end
|
|
end
|
|
|
|
-- The fountain arcs alternate sides, so consecutive lines do not trace one path.
|
|
if CombatText.xDir == 1 then
|
|
CombatText.xDir = -1
|
|
else
|
|
CombatText.xDir = 1
|
|
end
|
|
string.xDir = CombatText.xDir
|
|
|
|
-- Staggered types jitter up to half COMBAT_TEXT_STAGGER_RANGE either side of centre.
|
|
string.startX = COMBAT_TEXT_LOCATIONS.startX
|
|
if isStaggered then
|
|
string.startX = string.startX + random(0, COMBAT_TEXT_STAGGER_RANGE) - COMBAT_TEXT_STAGGER_RANGE / 2
|
|
end
|
|
|
|
string.startY = seatY
|
|
string.yPos = seatY
|
|
string:SetPoint("TOP", UIParent, "BOTTOM", string.startX, seatY)
|
|
string:SetAlpha(1)
|
|
string:Show()
|
|
tinsert(COMBAT_TEXT_TO_ANIMATE, string)
|
|
end
|
|
|
|
function CombatText_RemoveMessage(string)
|
|
for i = 1, getn(COMBAT_TEXT_TO_ANIMATE) do
|
|
if COMBAT_TEXT_TO_ANIMATE[i] == string then
|
|
tremove(COMBAT_TEXT_TO_ANIMATE, i)
|
|
CombatText_ParkString(string)
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
function CombatText_GetAvailableString()
|
|
local string
|
|
for i = 1, NUM_COMBAT_TEXT_LINES do
|
|
string = getglobal("CombatText" .. i)
|
|
if not string:IsShown() then
|
|
return string
|
|
end
|
|
end
|
|
return CombatText_GetOldestString(), 1
|
|
end
|
|
|
|
function CombatText_GetOldestString()
|
|
local oldestString = COMBAT_TEXT_TO_ANIMATE[1]
|
|
CombatText_RemoveMessage(oldestString)
|
|
return oldestString
|
|
end
|
|
|
|
function CombatText_ClearAnimationList()
|
|
COMBAT_TEXT_TO_ANIMATE = {}
|
|
for i = 1, NUM_COMBAT_TEXT_LINES do
|
|
CombatText_ParkString(getglobal("CombatText" .. i))
|
|
end
|
|
end
|
|
|
|
function CombatText_UpdateDisplayedMessages()
|
|
-- The master toggle is enforced at the source: with the feature off we stop
|
|
-- listening entirely rather than filtering every message as it arrives.
|
|
if SHOW_COMBAT_TEXT == "0" then
|
|
CombatText:UnregisterEvent("COMBAT_TEXT_UPDATE")
|
|
CombatText:UnregisterEvent("UNIT_HEALTH")
|
|
CombatText:UnregisterEvent("UNIT_POWER_UPDATE")
|
|
CombatText:UnregisterEvent("PLAYER_COMBO_POINTS")
|
|
CombatText:UnregisterEvent("PLAYER_REGEN_DISABLED")
|
|
CombatText:UnregisterEvent("PLAYER_REGEN_ENABLED")
|
|
return
|
|
else
|
|
CombatText:RegisterEvent("COMBAT_TEXT_UPDATE")
|
|
CombatText:RegisterEvent("UNIT_HEALTH")
|
|
CombatText:RegisterEvent("UNIT_POWER_UPDATE")
|
|
CombatText:RegisterEvent("PLAYER_COMBO_POINTS")
|
|
CombatText:RegisterEvent("PLAYER_REGEN_DISABLED")
|
|
CombatText:RegisterEvent("PLAYER_REGEN_ENABLED")
|
|
end
|
|
-- Re-read every gated type's option into its `show`. This is the only writer of
|
|
-- `show` for a type that carries a `var`, so it runs on every option change.
|
|
for messageType, value in pairs(COMBAT_TEXT_TYPE_INFO) do
|
|
if value.var then
|
|
if getglobal(value.var) == "1" then
|
|
value.show = 1
|
|
else
|
|
value.show = nil
|
|
end
|
|
end
|
|
end
|
|
-- The engine's UI space is the 768-virtual coordinate system itself (decision
|
|
-- 0582), so the ref constants stand verbatim (0581's px shim retired). The ref's
|
|
-- uiScale-cvar division joins when a cvar system exists.
|
|
local uiScale = 1
|
|
-- The float mode picks the path and the run: modes 1 and 3 rise to 609 (3 on an
|
|
-- arc), mode 2 falls to 159. All three start at the same seat.
|
|
if COMBAT_TEXT_FLOAT_MODE == "1" then
|
|
COMBAT_TEXT_SCROLL_FUNCTION = CombatText_StandardScroll
|
|
COMBAT_TEXT_LOCATIONS = {
|
|
startX = 0,
|
|
startY = 384 / uiScale,
|
|
endX = 0,
|
|
endY = 609 / uiScale
|
|
}
|
|
elseif COMBAT_TEXT_FLOAT_MODE == "2" then
|
|
COMBAT_TEXT_SCROLL_FUNCTION = CombatText_StandardScroll
|
|
COMBAT_TEXT_LOCATIONS = {
|
|
startX = 0,
|
|
startY = 384 / uiScale,
|
|
endX = 0,
|
|
endY = 159 / uiScale
|
|
}
|
|
else
|
|
COMBAT_TEXT_SCROLL_FUNCTION = CombatText_FountainScroll
|
|
COMBAT_TEXT_LOCATIONS = {
|
|
startX = 0,
|
|
startY = 384 / uiScale,
|
|
endX = 0,
|
|
endY = 609 / uiScale
|
|
}
|
|
end
|
|
CombatText_ClearAnimationList()
|
|
end
|
|
|
|
function CombatText_StandardScroll(value)
|
|
local xPos = value.startX + ((COMBAT_TEXT_LOCATIONS.endX - COMBAT_TEXT_LOCATIONS.startX) * value.scrollTime / COMBAT_TEXT_SCROLLSPEED)
|
|
local yPos = value.startY + ((value.endY - COMBAT_TEXT_LOCATIONS.startY) * value.scrollTime / COMBAT_TEXT_SCROLLSPEED)
|
|
return xPos, yPos
|
|
end
|
|
|
|
function CombatText_FountainScroll(value)
|
|
local radius = 150
|
|
local xPos = value.xDir * (value.startX + radius * cos(90 * value.scrollTime / COMBAT_TEXT_SCROLLSPEED)) - value.xDir * radius
|
|
local yPos = value.startY + radius * sin(90 * value.scrollTime / COMBAT_TEXT_SCROLLSPEED)
|
|
return xPos, yPos
|
|
end
|
|
]]></Script>
|
|
|
|
<!-- The 20-string pool (ref CombatText1..20, each an expansion of CombatTextTemplate:
|
|
CombatTextFont, hidden, TOP anchored to UIParent BOTTOM (0,384) — re-seated per frame by
|
|
CombatText_OnUpdate). -->
|
|
<Frame name="CombatText" toplevel="true" parent="UIParent" hidden="false">
|
|
<Size><AbsDimension x="10" y="10"/></Size>
|
|
<Anchors>
|
|
<Anchor point="CENTER"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor>
|
|
</Anchors>
|
|
<Layers>
|
|
<Layer level="BACKGROUND">
|
|
<FontString name="CombatText1" inherits="CombatTextFont" hidden="true">
|
|
<Anchors><Anchor point="TOP" relativeTo="UIParent" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="384"/></Offset></Anchor></Anchors>
|
|
</FontString>
|
|
<FontString name="CombatText2" inherits="CombatTextFont" hidden="true">
|
|
<Anchors><Anchor point="TOP" relativeTo="UIParent" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="384"/></Offset></Anchor></Anchors>
|
|
</FontString>
|
|
<FontString name="CombatText3" inherits="CombatTextFont" hidden="true">
|
|
<Anchors><Anchor point="TOP" relativeTo="UIParent" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="384"/></Offset></Anchor></Anchors>
|
|
</FontString>
|
|
<FontString name="CombatText4" inherits="CombatTextFont" hidden="true">
|
|
<Anchors><Anchor point="TOP" relativeTo="UIParent" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="384"/></Offset></Anchor></Anchors>
|
|
</FontString>
|
|
<FontString name="CombatText5" inherits="CombatTextFont" hidden="true">
|
|
<Anchors><Anchor point="TOP" relativeTo="UIParent" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="384"/></Offset></Anchor></Anchors>
|
|
</FontString>
|
|
<FontString name="CombatText6" inherits="CombatTextFont" hidden="true">
|
|
<Anchors><Anchor point="TOP" relativeTo="UIParent" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="384"/></Offset></Anchor></Anchors>
|
|
</FontString>
|
|
<FontString name="CombatText7" inherits="CombatTextFont" hidden="true">
|
|
<Anchors><Anchor point="TOP" relativeTo="UIParent" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="384"/></Offset></Anchor></Anchors>
|
|
</FontString>
|
|
<FontString name="CombatText8" inherits="CombatTextFont" hidden="true">
|
|
<Anchors><Anchor point="TOP" relativeTo="UIParent" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="384"/></Offset></Anchor></Anchors>
|
|
</FontString>
|
|
<FontString name="CombatText9" inherits="CombatTextFont" hidden="true">
|
|
<Anchors><Anchor point="TOP" relativeTo="UIParent" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="384"/></Offset></Anchor></Anchors>
|
|
</FontString>
|
|
<FontString name="CombatText10" inherits="CombatTextFont" hidden="true">
|
|
<Anchors><Anchor point="TOP" relativeTo="UIParent" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="384"/></Offset></Anchor></Anchors>
|
|
</FontString>
|
|
<FontString name="CombatText11" inherits="CombatTextFont" hidden="true">
|
|
<Anchors><Anchor point="TOP" relativeTo="UIParent" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="384"/></Offset></Anchor></Anchors>
|
|
</FontString>
|
|
<FontString name="CombatText12" inherits="CombatTextFont" hidden="true">
|
|
<Anchors><Anchor point="TOP" relativeTo="UIParent" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="384"/></Offset></Anchor></Anchors>
|
|
</FontString>
|
|
<FontString name="CombatText13" inherits="CombatTextFont" hidden="true">
|
|
<Anchors><Anchor point="TOP" relativeTo="UIParent" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="384"/></Offset></Anchor></Anchors>
|
|
</FontString>
|
|
<FontString name="CombatText14" inherits="CombatTextFont" hidden="true">
|
|
<Anchors><Anchor point="TOP" relativeTo="UIParent" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="384"/></Offset></Anchor></Anchors>
|
|
</FontString>
|
|
<FontString name="CombatText15" inherits="CombatTextFont" hidden="true">
|
|
<Anchors><Anchor point="TOP" relativeTo="UIParent" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="384"/></Offset></Anchor></Anchors>
|
|
</FontString>
|
|
<FontString name="CombatText16" inherits="CombatTextFont" hidden="true">
|
|
<Anchors><Anchor point="TOP" relativeTo="UIParent" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="384"/></Offset></Anchor></Anchors>
|
|
</FontString>
|
|
<FontString name="CombatText17" inherits="CombatTextFont" hidden="true">
|
|
<Anchors><Anchor point="TOP" relativeTo="UIParent" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="384"/></Offset></Anchor></Anchors>
|
|
</FontString>
|
|
<FontString name="CombatText18" inherits="CombatTextFont" hidden="true">
|
|
<Anchors><Anchor point="TOP" relativeTo="UIParent" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="384"/></Offset></Anchor></Anchors>
|
|
</FontString>
|
|
<FontString name="CombatText19" inherits="CombatTextFont" hidden="true">
|
|
<Anchors><Anchor point="TOP" relativeTo="UIParent" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="384"/></Offset></Anchor></Anchors>
|
|
</FontString>
|
|
<FontString name="CombatText20" inherits="CombatTextFont" hidden="true">
|
|
<Anchors><Anchor point="TOP" relativeTo="UIParent" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="384"/></Offset></Anchor></Anchors>
|
|
</FontString>
|
|
</Layer>
|
|
</Layers>
|
|
<Scripts>
|
|
<OnLoad>CombatText_OnLoad()</OnLoad>
|
|
<OnEvent>CombatText_OnEvent(event)</OnEvent>
|
|
<OnUpdate>CombatText_OnUpdate(arg1)</OnUpdate>
|
|
</Scripts>
|
|
</Frame>
|
|
</Ui>
|