mirror of
https://github.com/brues-code/pfUI.git
synced 2026-10-31 16:06:57 +00:00
ClassicAPI's SetColorTexture is the same engine call as SetTexture(r, g, b [, a]) under the name that says what it does. 83 calls across 25 files, renamed only.
299 lines
11 KiB
Lua
299 lines
11 KiB
Lua
-- One server clock drives every power: Player::RegenerateAll fires every
|
|
-- REGEN_TIME_FULL (2s), re-arms with `+=`, and is never reset by casting. The
|
|
-- five-second rule (SetLastManaUse on any mana-costing cast) changes what a tick
|
|
-- pays, never when it lands; mp5 and the player's MOD_MANA_REGEN_INTERRUPT share
|
|
-- still come in. That share can't be computed here -- item sources are equip
|
|
-- auras absent from the buff list and m_modManaRegenInterrupt is never sent --
|
|
-- so the spark shows it instead: dim through the window until a tick lands.
|
|
--
|
|
-- The sweep free-runs on that clock and phase-locks to observed gains. A gain
|
|
-- mid-sweep (Illumination, Judgement of Wisdom, potions, a Mana Spring totem on
|
|
-- its own phase) is not the tick and never moves it.
|
|
|
|
local FIVE_SECOND_RULE = 5
|
|
|
|
-- gains farther than this from the predicted boundary are not the tick
|
|
local TICK_TOLERANCE = .25
|
|
|
|
-- arrival jitter; a tick inside this band confirms the sweep rather than
|
|
-- re-anchoring it, or the spark hitches at every wrap
|
|
local TICK_JITTER = .08
|
|
|
|
-- Player::RegenerateAll:
|
|
-- mod = GetTotalAuraModifier(SPELL_AURA_MOD_ENERGY_REGEN_TIME)
|
|
-- if mod > 0 then mod = mod * agility / 10 end
|
|
-- m_regenTimer += max(1, REGEN_TIME_FULL - mod) -- milliseconds
|
|
local REGEN_TIME_FULL = 2
|
|
local ENERGY_REGEN_TIME_AURA = 217 -- SPELL_AURA_MOD_ENERGY_REGEN_TIME
|
|
|
|
-- fixed magnitude is basePoints + baseDice (stored 11 -> 12); a die above 1 is
|
|
-- a roll the client can't know, so it counts as nothing rather than a guess
|
|
local amountCache = {}
|
|
local function auraAmount(spellID)
|
|
local amount = amountCache[spellID]
|
|
if amount then return amount end
|
|
amount = 0
|
|
local effects = C_Spell.GetSpellEffectInfo(spellID) -- nil for an id with no record
|
|
if effects then
|
|
for i = 1, 3 do
|
|
local fx = effects[i]
|
|
if fx.auraName == ENERGY_REGEN_TIME_AURA and fx.dieSides <= 1 then
|
|
amount = fx.basePoints + fx.baseDice
|
|
break
|
|
end
|
|
end
|
|
end
|
|
amountCache[spellID] = amount
|
|
return amount
|
|
end
|
|
|
|
-- A passive is in effect exactly while known (current rank only, never in the
|
|
-- buff list); anything castable or cast on us counts only while it is up.
|
|
local function getEnergyRegenTimeMod()
|
|
local sum = 0
|
|
for _, spellID in ipairs(C_SpellBook.GetPlayerSpellsByAura(ENERGY_REGEN_TIME_AURA)) do
|
|
if C_Spell.IsSpellPassive(spellID) then
|
|
sum = sum + auraAmount(spellID)
|
|
end
|
|
end
|
|
for i = 1, 32 do
|
|
local spellID = select(10, C_UnitAuras.UnitAura("player", i, "HELPFUL"))
|
|
if not spellID then break end
|
|
sum = sum + auraAmount(spellID)
|
|
end
|
|
return sum
|
|
end
|
|
|
|
-- cleared on SPELLS_CHANGED (passives) and PLAYER_AURAS_CHANGED (buffs), and
|
|
-- recomputed by the next tick that asks. Agility stays live: it's one call.
|
|
local energyRegenTimeMod
|
|
|
|
local function getAdjustedTickTimer()
|
|
if not energyRegenTimeMod then
|
|
energyRegenTimeMod = getEnergyRegenTimeMod()
|
|
end
|
|
if energyRegenTimeMod == 0 then return REGEN_TIME_FULL end
|
|
|
|
-- ms on the server, seconds here; the 1ms floor is the server's and this is a divisor
|
|
local reduction = energyRegenTimeMod * UnitStat("player", 2) / 10000
|
|
return math.max(0.001, REGEN_TIME_FULL - reduction)
|
|
end
|
|
|
|
pfUI:RegisterModule("energytick", function()
|
|
if not pfUI.uf or not pfUI.uf.player then
|
|
return
|
|
end
|
|
|
|
-- inside the module body on purpose: C is on pfUI.env, not _G
|
|
local function getBarWidth()
|
|
return C.unitframes.player.pwidth ~= "-1" and C.unitframes.player.pwidth or C.unitframes.player.width
|
|
end
|
|
|
|
local rawborder, default_border = GetBorderSize("unitframes")
|
|
local cbtexture = pfUI.media[C.appearance.castbar.texture]
|
|
|
|
-- style is read once: switching it in the GUI needs a /reload, same as the
|
|
-- other structural (not cosmetic) toggles in this file
|
|
local fsrOverlay = C.unitframes.player.fsrstyle == "1"
|
|
|
|
-- was this gain the regen tick? if so, re-anchor the sweep on it
|
|
local function lockTick(frame)
|
|
local now, period = GetTime(), getAdjustedTickTimer()
|
|
|
|
if frame.start then
|
|
-- signed distance to the nearest predicted boundary
|
|
local err = mod(now - frame.start, period)
|
|
if err > period / 2 then err = err - period end
|
|
|
|
if math.abs(err) <= TICK_TOLERANCE then
|
|
-- correct only what lies beyond normal jitter
|
|
if err > TICK_JITTER then
|
|
frame.start = frame.start + (err - TICK_JITTER)
|
|
elseif err < -TICK_JITTER then
|
|
frame.start = frame.start + (err + TICK_JITTER)
|
|
end
|
|
frame.max, frame.rejected = period, nil
|
|
return true
|
|
end
|
|
|
|
-- two rejected gains one period apart are the real clock: relock to it
|
|
local periodic = frame.rejected and math.abs(now - frame.rejected - period) <= TICK_TOLERANCE
|
|
if not periodic then
|
|
frame.rejected = now
|
|
return false
|
|
end
|
|
end
|
|
|
|
frame.start, frame.max, frame.rejected = now, period, nil
|
|
return true
|
|
end
|
|
|
|
local energytick = CreateFrame("Frame", nil, pfUI.uf.player.power.bar)
|
|
energytick:SetAllPoints(pfUI.uf.player.power.bar)
|
|
energytick:RegisterEvent("PLAYER_ENTERING_WORLD")
|
|
energytick:RegisterUnitEvent("UNIT_DISPLAYPOWER", "player")
|
|
energytick:RegisterUnitEvent("UNIT_ENERGY", "player")
|
|
energytick:RegisterUnitEvent("UNIT_MANA", "player")
|
|
energytick:RegisterUnitEvent("UNIT_SPELLCAST_SUCCEEDED", "player")
|
|
energytick:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_STOP", "player")
|
|
energytick:RegisterEvent("SPELLS_CHANGED")
|
|
energytick:RegisterEvent("PLAYER_AURAS_CHANGED")
|
|
|
|
energytick:SetScript("OnEvent", function()
|
|
if UnitPowerType("player") == Enum.PowerType.Mana and C.unitframes.player.manatick == "1" then
|
|
this.mode = "MANA"
|
|
this:Show()
|
|
elseif UnitPowerType("player") == Enum.PowerType.Energy and C.unitframes.player.energy == "1" then
|
|
this.mode = "ENERGY"
|
|
this:Show()
|
|
else
|
|
this:Hide()
|
|
end
|
|
|
|
if event == "SPELLS_CHANGED" or event == "PLAYER_AURAS_CHANGED" then
|
|
energyRegenTimeMod = nil
|
|
return
|
|
end
|
|
|
|
if event == "PLAYER_ENTERING_WORLD" then
|
|
this.lastPower = UnitPower("player")
|
|
end
|
|
|
|
-- the rule arms on the cast (Spell::TakePower: mana powerType, cost > 0),
|
|
-- not on a mana drop -- Mana Burn lowers mana without arming it
|
|
if event == "UNIT_SPELLCAST_SUCCEEDED" and arg1 == "player" then
|
|
local cost = C_Spell.GetSpellPowerCost(arg3)
|
|
cost = cost and cost[1]
|
|
if cost and cost.type == Enum.PowerType.Mana and cost.cost > 0 then
|
|
this.fsrSpell, this.fsrEnd = arg3, GetTime() + FIVE_SECOND_RULE
|
|
this.fsrGain = nil
|
|
end
|
|
return
|
|
end
|
|
|
|
-- Unit::Update won't expire the rule while the spending spell still channels
|
|
if event == "UNIT_SPELLCAST_CHANNEL_STOP" and arg1 == "player" then
|
|
if this.fsrSpell and this.fsrSpell == arg3 then
|
|
this.fsrEnd, this.fsrGain = GetTime() + FIVE_SECOND_RULE, nil
|
|
end
|
|
return
|
|
end
|
|
|
|
if (event == "UNIT_MANA" or event == "UNIT_ENERGY") and arg1 == "player" then
|
|
local power = UnitPower("player")
|
|
local diff = this.lastPower and (power - this.lastPower) or 0
|
|
this.lastPower = power
|
|
|
|
-- only a gain can be the tick; a spend never touches the phase
|
|
if diff > 0 and lockTick(this) then
|
|
-- a tick inside the window proves regen continues through it
|
|
if this.fsrEnd and this.fsrEnd > GetTime() then
|
|
this.fsrGain = true
|
|
end
|
|
end
|
|
|
|
-- phase is kept while hidden; OnUpdate catches up by whole periods
|
|
if this.mode == "MANA" and power >= UnitPowerMax("player") then
|
|
this:Hide()
|
|
end
|
|
end
|
|
end)
|
|
|
|
energytick:SetScript("OnUpdate", function()
|
|
-- Throttle for performance
|
|
if (this.tick or 0) > GetTime() then
|
|
return
|
|
end
|
|
this.tick = GetTime() + 0.020 -- ~50 FPS
|
|
|
|
-- five-second rule drains to nothing. The display is cosmetic and can be
|
|
-- hidden on its own; the window itself (fsrEnd/fsrGain) keeps running
|
|
-- either way -- it still dims the sweep spark below.
|
|
local remaining = this.fsrEnd and (this.fsrEnd - GetTime()) or 0
|
|
if this.mode == "MANA" and remaining > 0 then
|
|
if C.unitframes.player.showfsr == "1" then
|
|
if fsrOverlay then
|
|
this.fsrbar:SetWidth(getBarWidth() * remaining / FIVE_SECOND_RULE)
|
|
else
|
|
this.fsrbar:SetValue(remaining)
|
|
end
|
|
this.fsrbar:Show()
|
|
else
|
|
this.fsrbar:Hide()
|
|
end
|
|
else
|
|
this.fsrSpell, this.fsrEnd, this.fsrGain = nil, nil, nil
|
|
this.fsrbar:Hide()
|
|
end
|
|
|
|
if not this.start then
|
|
this.spark:SetAlpha(0)
|
|
return
|
|
end
|
|
|
|
if this.mode == "MANA" and UnitPower("player") >= UnitPowerMax("player") then
|
|
this.spark:SetAlpha(0)
|
|
return
|
|
end
|
|
|
|
this.current = GetTime() - this.start
|
|
|
|
-- roll over by whole periods, not from now: restarting bakes frame
|
|
-- overshoot into the phase as drift the lock then has to chase
|
|
if this.current > this.max then
|
|
this.start = this.start + this.max * math.floor(this.current / this.max)
|
|
this.max = getAdjustedTickTimer()
|
|
this.current = GetTime() - this.start
|
|
end
|
|
|
|
-- dim while the rule is up and nothing has ticked inside it yet
|
|
this.spark:SetAlpha((remaining > 0 and not this.fsrGain) and .4 or 1)
|
|
|
|
if not C.unitframes.player.pheight then
|
|
return
|
|
end
|
|
|
|
local pos = getBarWidth() * (this.current / this.max)
|
|
this.spark:SetPoint("LEFT", pos - ((C.unitframes.player.pheight + 5) / 2), 0)
|
|
end)
|
|
|
|
if fsrOverlay then
|
|
-- classic style: a translucent shade on top of the power bar's own fill
|
|
energytick.fsrbar = energytick:CreateTexture(nil, "ARTWORK")
|
|
energytick.fsrbar:SetColorTexture(1, 1, 1, .15)
|
|
energytick.fsrbar:SetPoint("TOPLEFT", 0, 0)
|
|
energytick.fsrbar:SetPoint("BOTTOMLEFT", 0, 0)
|
|
else
|
|
-- a real bar below the power bar, not an overlay on top of it -- the same
|
|
-- treatment nameplate castbars get relative to the health bar, so the
|
|
-- window can't be mistaken for missing mana
|
|
energytick.fsrbar = CreateFrame("StatusBar", nil, pfUI.uf.player)
|
|
energytick.fsrbar:SetStatusBarTexture(cbtexture)
|
|
energytick.fsrbar:SetMinMaxValues(0, FIVE_SECOND_RULE)
|
|
energytick.fsrbar:SetPoint("TOPLEFT", pfUI.uf.player.power.bar, "BOTTOMLEFT", 0, -default_border * 2)
|
|
energytick.fsrbar:SetPoint("TOPRIGHT", pfUI.uf.player.power.bar, "BOTTOMRIGHT", 0, -default_border * 2)
|
|
energytick.fsrbar:SetHeight(tonumber(C.unitframes.player.fsrheight) or 4)
|
|
energytick.fsrbar:SetStatusBarColor(GetStringColor(C.appearance.castbar.fsrcolor))
|
|
CreateBackdrop(energytick.fsrbar, default_border)
|
|
CreateBackdropShadow(energytick.fsrbar)
|
|
end
|
|
energytick.fsrbar:Hide()
|
|
|
|
energytick.spark = energytick:CreateTexture(nil, "OVERLAY")
|
|
energytick.spark:SetTexture("Interface\\CastingBar\\UI-CastingBar-Spark")
|
|
energytick.spark:SetHeight(C.unitframes.player.pheight + 15)
|
|
energytick.spark:SetWidth(C.unitframes.player.pheight + 5)
|
|
energytick.spark:SetBlendMode("ADD")
|
|
|
|
local hookUpdateConfig = pfUI.uf.player.UpdateConfig
|
|
function pfUI.uf.player.UpdateConfig()
|
|
energytick.spark:SetHeight(C.unitframes.player.pheight + 15)
|
|
energytick.spark:SetWidth(C.unitframes.player.pheight + 5)
|
|
if not fsrOverlay then
|
|
energytick.fsrbar:SetHeight(tonumber(C.unitframes.player.fsrheight) or 4)
|
|
energytick.fsrbar:SetStatusBarColor(GetStringColor(C.appearance.castbar.fsrcolor))
|
|
end
|
|
hookUpdateConfig(pfUI.uf.player)
|
|
end
|
|
end)
|