mirror of
https://github.com/brues-code/pfUI.git
synced 2026-10-26 14:00:48 +00:00
Migrate player castbar to ClassicAPI UNIT_SPELLCAST_* events
Drive the player cast path off ClassicAPI's synthesized, player-only
UNIT_SPELLCAST_* events instead of the vanilla SPELLCAST_* plus nampower
SPELL_{START,GO,DELAYED}_SELF mix. START/CHANNEL_START poll UnitCastingInfo/
UnitChannelInfo and stamp; STOP/CHANNEL_STOP/FAILED/INTERRUPTED clear;
SUCCEEDED drives tradeskill craft counting; DELAYED/CHANNEL_UPDATE re-poll.
Since UNIT_SPELLCAST_START fires for chained same-spell recasts, the nampower
SELF deps and their RunNextFrame co-hook-ordering workarounds are gone.
Remote target/focus bars keep SPELL_*_OTHER (the new events are player-only).
Fix the cast-start flash: StampBar now primes the fill on the stamp frame,
and ClearBar no longer snaps the bar to full (that snap lingered in the fade
tail and flashed when the next cast stamped).
Add a red flash on a cancelled cast: new appearance.castbar.failcolor,
flashed by ClearBar only when a cast was actually in progress.
Handle pushback Quartz-style: DELAYED/CHANNEL_UPDATE re-poll just the times
and accumulate the endMs shift into this.delay for the +/- indicator (cast
+X, channel -X), rather than a full restamp.
This commit is contained in:
@@ -155,6 +155,7 @@ function pfUI:LoadConfig()
|
|||||||
pfUI:UpdateConfig("appearance", "cd", "dynamicsize", "1")
|
pfUI:UpdateConfig("appearance", "cd", "dynamicsize", "1")
|
||||||
pfUI:UpdateConfig("appearance", "castbar", "castbarcolor", ".7,.7,.9,.8")
|
pfUI:UpdateConfig("appearance", "castbar", "castbarcolor", ".7,.7,.9,.8")
|
||||||
pfUI:UpdateConfig("appearance", "castbar", "channelcolor", ".9,.9,.7,.8")
|
pfUI:UpdateConfig("appearance", "castbar", "channelcolor", ".9,.9,.7,.8")
|
||||||
|
pfUI:UpdateConfig("appearance", "castbar", "failcolor", "1,.2,.2,.8")
|
||||||
pfUI:UpdateConfig("appearance", "castbar", "texture", "Interface\\AddOns\\pfUI\\img\\bar")
|
pfUI:UpdateConfig("appearance", "castbar", "texture", "Interface\\AddOns\\pfUI\\img\\bar")
|
||||||
pfUI:UpdateConfig("appearance", "infight", "screen", "0")
|
pfUI:UpdateConfig("appearance", "infight", "screen", "0")
|
||||||
pfUI:UpdateConfig("appearance", "infight", "aggro", "0")
|
pfUI:UpdateConfig("appearance", "infight", "aggro", "0")
|
||||||
|
|||||||
+99
-108
@@ -16,9 +16,13 @@ pfUI:RegisterModule("castbar", function ()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Clear cast state on the bar. Shows the bar full for one frame, then
|
-- Clear cast state on the bar and start the fade-out. On a normal end the bar
|
||||||
-- OnUpdate fades it out.
|
-- is left at its current fill (a completed cast is already ~full; nothing
|
||||||
local function ClearBar(cb)
|
-- snaps to full, which used to flash for a frame when the next cast stamped
|
||||||
|
-- the bar). When `failed` is set for a cast that was actually in progress, the
|
||||||
|
-- bar flashes full red before fading — the cancelled-cast indicator.
|
||||||
|
local function ClearBar(cb, failed)
|
||||||
|
local wasActive = cb.endTime ~= nil
|
||||||
cb.startTime, cb.endTime, cb.isChannel = nil, nil, nil
|
cb.startTime, cb.endTime, cb.isChannel = nil, nil, nil
|
||||||
cb.activeName, cb.spellID = nil, nil
|
cb.activeName, cb.spellID = nil, nil
|
||||||
cb.isTradeskill = nil
|
cb.isTradeskill = nil
|
||||||
@@ -26,8 +30,11 @@ pfUI:RegisterModule("castbar", function ()
|
|||||||
cb.tradeskillSingleMs, cb.currentCraftStart = nil, nil
|
cb.tradeskillSingleMs, cb.currentCraftStart = nil, nil
|
||||||
cb.lastMax = nil
|
cb.lastMax = nil
|
||||||
cb.delay = 0
|
cb.delay = 0
|
||||||
cb.bar:SetMinMaxValues(1, 100)
|
if failed and wasActive then
|
||||||
cb.bar:SetValue(100)
|
cb.bar:SetStatusBarColor(GetStringColor(C.appearance.castbar.failcolor))
|
||||||
|
cb.bar:SetMinMaxValues(0, 1)
|
||||||
|
cb.bar:SetValue(1)
|
||||||
|
end
|
||||||
if cb.bar.spark then cb.bar.spark:Hide() end
|
if cb.bar.spark then cb.bar.spark:Hide() end
|
||||||
cb.fadeout = 1
|
cb.fadeout = 1
|
||||||
end
|
end
|
||||||
@@ -65,8 +72,8 @@ pfUI:RegisterModule("castbar", function ()
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Mark the start of craft 2..N within an active merge (called from the
|
-- Mark the start of craft 2..N within an active merge (called from the
|
||||||
-- SPELLCAST_START / SPELL_START_SELF handlers). Resets the per-craft
|
-- UNIT_SPELLCAST_START handler). Resets the per-craft spark to the left
|
||||||
-- spark to the left edge of the bar.
|
-- edge of the bar.
|
||||||
local function StartTradeskillCraft(cb)
|
local function StartTradeskillCraft(cb)
|
||||||
cb.currentCraftStart = GetTime() * 1000
|
cb.currentCraftStart = GetTime() * 1000
|
||||||
local remaining = cb.tradeskillTotal - (cb.tradeskillCompleted or 0)
|
local remaining = cb.tradeskillTotal - (cb.tradeskillCompleted or 0)
|
||||||
@@ -122,6 +129,16 @@ pfUI:RegisterModule("castbar", function ()
|
|||||||
|
|
||||||
cb.bar:SetMinMaxValues(0, duration)
|
cb.bar:SetMinMaxValues(0, duration)
|
||||||
cb.lastMax = duration
|
cb.lastMax = duration
|
||||||
|
|
||||||
|
-- Prime the fill on this frame. StampBar otherwise leaves the previous
|
||||||
|
-- value in place (ClearBar, run on the prior cast's STOP, leaves it full),
|
||||||
|
-- so the bar would flash full for the frame between here and the next
|
||||||
|
-- OnUpdate tick. Reset the throttle too so the timer text updates promptly.
|
||||||
|
local nowSec = GetTime()
|
||||||
|
local cur = isChannel and (endMs / 1000 - nowSec) or (nowSec - startMs / 1000)
|
||||||
|
if cur < 0 then cur = 0 elseif cur > duration then cur = duration end
|
||||||
|
cb.bar:SetValue(cur)
|
||||||
|
cb.tick = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
-- One-shot poll: read C_Spell for the bar's unit, stamp or clear. Called
|
-- One-shot poll: read C_Spell for the bar's unit, stamp or clear. Called
|
||||||
@@ -294,27 +311,24 @@ pfUI:RegisterModule("castbar", function ()
|
|||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- Cast lifecycle events. Player bars react to vanilla SPELLCAST_*; non-
|
-- Cast lifecycle. The player's own casts come from ClassicAPI's
|
||||||
-- player bars also react to Nampower SPELL_*_OTHER (gated by the
|
-- UNIT_SPELLCAST_* (player-only, synthesized engine-side); remote units
|
||||||
-- NP_EnableSpell{Start,Go}Events CVars, enabled by libdebuff) plus the
|
-- (target/focus not resolving to the player) come from Nampower
|
||||||
-- retarget event. Player events also feed non-player bars for the
|
-- SPELL_*_OTHER, gated by the NP_EnableSpell{Start,Go}Events CVars enabled
|
||||||
-- target=self case.
|
-- by libdebuff, plus the retarget event. The player feed is registered on
|
||||||
cb:RegisterEvent("SPELLCAST_START")
|
-- every bar so target=self / focus=self casts show too (UnitIsUnit gate in
|
||||||
cb:RegisterEvent("SPELLCAST_STOP")
|
-- the handler). UNIT_SPELLCAST_START fires for chained same-spell recasts,
|
||||||
cb:RegisterEvent("SPELLCAST_FAILED")
|
-- pushback and completion, so it replaces the vanilla SPELLCAST_* plus the
|
||||||
cb:RegisterEvent("SPELLCAST_INTERRUPTED")
|
-- nampower SPELL_{START,GO,DELAYED}_SELF workarounds.
|
||||||
cb:RegisterEvent("SPELLCAST_CHANNEL_START")
|
cb:RegisterEvent("UNIT_SPELLCAST_START")
|
||||||
cb:RegisterEvent("SPELLCAST_CHANNEL_STOP")
|
cb:RegisterEvent("UNIT_SPELLCAST_STOP")
|
||||||
cb:RegisterEvent("SPELLCAST_CHANNEL_UPDATE")
|
cb:RegisterEvent("UNIT_SPELLCAST_FAILED")
|
||||||
cb:RegisterEvent("SPELL_DELAYED_SELF")
|
cb:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED")
|
||||||
-- Chained same-spell recasts never run the client cast path (the 1.12
|
cb:RegisterEvent("UNIT_SPELLCAST_DELAYED")
|
||||||
-- engine short-circuits at spellID == current-cast), so vanilla
|
cb:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED")
|
||||||
-- SPELLCAST_START never fires for them. nampower's SPELL_START_SELF
|
cb:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START")
|
||||||
-- (server-driven) is the only signal that shows them.
|
cb:RegisterEvent("UNIT_SPELLCAST_CHANNEL_STOP")
|
||||||
cb:RegisterEvent("SPELL_START_SELF")
|
cb:RegisterEvent("UNIT_SPELLCAST_CHANNEL_UPDATE")
|
||||||
if unitstr == "player" then
|
|
||||||
cb:RegisterEvent("SPELL_GO_SELF")
|
|
||||||
end
|
|
||||||
if unitstr ~= "player" and unitstr ~= "" then
|
if unitstr ~= "player" and unitstr ~= "" then
|
||||||
cb:RegisterEvent("SPELL_START_OTHER")
|
cb:RegisterEvent("SPELL_START_OTHER")
|
||||||
cb:RegisterEvent("SPELL_FAILED_OTHER")
|
cb:RegisterEvent("SPELL_FAILED_OTHER")
|
||||||
@@ -346,19 +360,20 @@ pfUI:RegisterModule("castbar", function ()
|
|||||||
end
|
end
|
||||||
|
|
||||||
if event == "SPELL_FAILED_OTHER" then
|
if event == "SPELL_FAILED_OTHER" then
|
||||||
if arg1 == UnitGUID(unit) then ClearBar(this) end
|
if arg1 == UnitGUID(unit) then ClearBar(this, true) end
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Vanilla SPELLCAST_* + SPELL_DELAYED_SELF fire only for the local
|
-- UNIT_SPELLCAST_* are player-only (arg1 == "player"). Non-player bars
|
||||||
-- player. Non-player bars handle them only when their unit currently
|
-- consume them only when their unit currently resolves to the player
|
||||||
-- resolves to the player (target=self / focus=self).
|
-- (target=self / focus=self); the remote path is SPELL_*_OTHER above.
|
||||||
|
-- Args: arg1="player", arg2=castGUID, arg3=spellID, arg4=name, arg5=rank.
|
||||||
if not UnitIsUnit(unit, 'player') then return end
|
if not UnitIsUnit(unit, 'player') then return end
|
||||||
|
|
||||||
if event == "SPELLCAST_START" or event == "SPELLCAST_CHANNEL_START" then
|
if event == "UNIT_SPELLCAST_START" or event == "UNIT_SPELLCAST_CHANNEL_START" then
|
||||||
|
-- START also fires per craft in a same-spell chain, so an active merge
|
||||||
|
-- just resyncs the current craft's spark/label instead of restamping.
|
||||||
if this.tradeskillTotal then
|
if this.tradeskillTotal then
|
||||||
-- Mid-chain craft N+1 of N. Keep the merged bar; resync the spark
|
|
||||||
-- to the new craft's start and refresh the "(N)" count label.
|
|
||||||
StartTradeskillCraft(this)
|
StartTradeskillCraft(this)
|
||||||
else
|
else
|
||||||
RefreshBar(this)
|
RefreshBar(this)
|
||||||
@@ -368,48 +383,11 @@ pfUI:RegisterModule("castbar", function ()
|
|||||||
end
|
end
|
||||||
this.pendingTradeskillCount = nil
|
this.pendingTradeskillCount = nil
|
||||||
end
|
end
|
||||||
elseif event == "SPELL_START_SELF" then
|
elseif event == "UNIT_SPELLCAST_SUCCEEDED" then
|
||||||
-- Catches chained same-spell recasts (no SPELLCAST_START fires) —
|
-- Per-craft completion during a tradeskill merge (arg3 = spellID):
|
||||||
-- including tradeskill chaining where craft 2..N reuse one spell.
|
-- count and clear when the chain is done. A no-op for normal casts,
|
||||||
-- Defer one frame so ClassicAPI's SMSG_SPELL_START co-hook has
|
-- which are cleared by UNIT_SPELLCAST_STOP.
|
||||||
-- stamped g_cast before we poll, regardless of co-hook order.
|
if this.tradeskillTotal and arg3 == this.tradeskillSpellID then
|
||||||
if this.tradeskillTotal then
|
|
||||||
StartTradeskillCraft(this)
|
|
||||||
else
|
|
||||||
local target = this
|
|
||||||
RunNextFrame(function()
|
|
||||||
-- Re-check: SPELL_START_SELF (nampower co-hook) fires before vanilla
|
|
||||||
-- SPELLCAST_START on the same packet, so SPELLCAST_START may have
|
|
||||||
-- entered merge in this same frame. Don't restamp over it.
|
|
||||||
if target.tradeskillTotal then
|
|
||||||
StartTradeskillCraft(target)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
RefreshBar(target)
|
|
||||||
if target.unitstr == "player" and target.isTradeskill
|
|
||||||
and (target.pendingTradeskillCount or 0) > 1
|
|
||||||
and C.castbar.player.mergetradeskill == "1" then
|
|
||||||
EnterTradeskillMerge(target, target.startTime, target.endTime, target.pendingTradeskillCount)
|
|
||||||
end
|
|
||||||
target.pendingTradeskillCount = nil
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
elseif event == "SPELLCAST_CHANNEL_STOP" then
|
|
||||||
-- A channel's stop can arrive after a following cast already claimed
|
|
||||||
-- the bar (channel->cast transition); only clear if a channel is
|
|
||||||
-- actually being shown, so it doesn't wipe an active cast bar.
|
|
||||||
if this.isChannel then ClearBar(this) end
|
|
||||||
elseif event == "SPELLCAST_STOP" then
|
|
||||||
-- During a tradeskill chain, SPELL_GO_SELF already counted this craft
|
|
||||||
-- and either cleared the bar (chain done) or kept it running. Only a
|
|
||||||
-- non-merge cast clears here.
|
|
||||||
if not this.tradeskillTotal then ClearBar(this) end
|
|
||||||
elseif event == "SPELLCAST_FAILED" or event == "SPELLCAST_INTERRUPTED" then
|
|
||||||
ClearBar(this)
|
|
||||||
elseif event == "SPELL_GO_SELF" then
|
|
||||||
-- arg2 = spellId. In a tradeskill merge, count each successful craft
|
|
||||||
-- and clear when the chain is done.
|
|
||||||
if this.tradeskillTotal and arg2 == this.tradeskillSpellID then
|
|
||||||
this.tradeskillCompleted = (this.tradeskillCompleted or 0) + 1
|
this.tradeskillCompleted = (this.tradeskillCompleted or 0) + 1
|
||||||
if this.tradeskillCompleted >= this.tradeskillTotal then
|
if this.tradeskillCompleted >= this.tradeskillTotal then
|
||||||
ClearBar(this)
|
ClearBar(this)
|
||||||
@@ -417,33 +395,46 @@ pfUI:RegisterModule("castbar", function ()
|
|||||||
UpdateTradeskillLabel(this)
|
UpdateTradeskillLabel(this)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
elseif event == "SPELL_DELAYED_SELF" then
|
elseif event == "UNIT_SPELLCAST_CHANNEL_STOP" then
|
||||||
-- Cast pushback. nampower's event carries the delay (arg2); apply it
|
-- A channel's stop can arrive after a following cast already claimed
|
||||||
-- locally rather than re-polling, so the bar doesn't depend on
|
-- the bar (channel->cast transition); only clear if a channel is
|
||||||
-- ClassicAPI's SMSG_SPELL_DELAYED co-hook having bumped g_cast before
|
-- actually being shown, so it doesn't wipe an active cast bar.
|
||||||
-- this fires (co-hook order vs nampower is not guaranteed).
|
if this.isChannel then ClearBar(this) end
|
||||||
if not this.endTime or not arg2 then return end
|
elseif event == "UNIT_SPELLCAST_STOP" then
|
||||||
local delayMs = tonumber(arg2) or 0
|
-- STOP fires between crafts in a merge too (each craft is a new cast);
|
||||||
if delayMs > 0 then
|
-- UNIT_SPELLCAST_SUCCEEDED owns the count, so only a non-merge cast
|
||||||
this.delay = (this.delay or 0) + delayMs / 1000
|
-- clears here.
|
||||||
this.endTime = this.endTime + delayMs
|
if not this.tradeskillTotal then ClearBar(this) end
|
||||||
local newDuration = (this.endTime - this.startTime) / 1000
|
elseif event == "UNIT_SPELLCAST_FAILED" or event == "UNIT_SPELLCAST_INTERRUPTED" then
|
||||||
this.bar:SetMinMaxValues(0, newDuration)
|
ClearBar(this, true)
|
||||||
this.lastMax = newDuration
|
elseif event == "UNIT_SPELLCAST_DELAYED" or event == "UNIT_SPELLCAST_CHANNEL_UPDATE" then
|
||||||
end
|
-- Pushback: a cast delayed later or a channel shortened. Following
|
||||||
elseif event == "SPELLCAST_CHANNEL_UPDATE" then
|
-- Quartz, re-poll just the times and accumulate the shift into a running
|
||||||
-- Channel pushback. ClassicAPI doesn't track channel delay in
|
-- this.delay for the +/- indicator, rather than a full restamp (which
|
||||||
-- g_channel, so we adjust endTime + delay locally and resize the
|
-- would reset that total and re-render icon/text). ClassicAPI moves
|
||||||
-- bar so OnUpdate animates against the new total.
|
-- endMs (SpellDelayed_h bumps g_cast.endMs; the MSG_CHANNEL_UPDATE
|
||||||
if not this.endTime or not arg1 then return end
|
-- co-hook rewrites g_channel.endMs) while startMs stays put, so we diff
|
||||||
local newEndMs = GetTime() * 1000 + arg1
|
-- endMs. delay is a positive magnitude; OnUpdate signs it "+" for casts
|
||||||
local diff = this.endTime - newEndMs
|
-- and "-" for channels. Skipped during a tradeskill merge.
|
||||||
if diff > 50 then
|
if this.endTime and not this.tradeskillTotal then
|
||||||
this.delay = (this.delay or 0) + diff / 1000
|
local query = this.unitstr ~= "" and this.unitstr or this.unitname
|
||||||
this.endTime = newEndMs
|
local startMs, endMs
|
||||||
local newDuration = (this.endTime - this.startTime) / 1000
|
if this.isChannel then
|
||||||
this.bar:SetMinMaxValues(0, newDuration)
|
local _, _, _, s, e = C_Spell.UnitChannelInfo(query)
|
||||||
this.lastMax = newDuration
|
startMs, endMs = s, e
|
||||||
|
else
|
||||||
|
local _, _, _, s, e = C_Spell.UnitCastingInfo(query)
|
||||||
|
startMs, endMs = s, e
|
||||||
|
end
|
||||||
|
if startMs and endMs then
|
||||||
|
local shift = this.isChannel and (this.endTime - endMs) or (endMs - this.endTime)
|
||||||
|
this.delay = (this.delay or 0) + shift / 1000
|
||||||
|
this.startTime = startMs
|
||||||
|
this.endTime = endMs
|
||||||
|
local newDuration = (endMs - startMs) / 1000
|
||||||
|
this.bar:SetMinMaxValues(0, newDuration)
|
||||||
|
this.lastMax = newDuration
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
@@ -489,10 +480,10 @@ pfUI:RegisterModule("castbar", function ()
|
|||||||
UpdateMovable(pfUI.castbar.player)
|
UpdateMovable(pfUI.castbar.player)
|
||||||
|
|
||||||
-- Tradeskill merge: hook DoTradeSkill so the player castbar knows the
|
-- Tradeskill merge: hook DoTradeSkill so the player castbar knows the
|
||||||
-- requested count before the first SPELLCAST_START fires. Always-on hook
|
-- requested count before the first UNIT_SPELLCAST_START fires. Always-on
|
||||||
-- (the config knob is read at event time so toggling takes effect on the
|
-- hook (the config knob is read at event time so toggling takes effect on
|
||||||
-- next craft without a /reload). DoTradeSkill is synchronous; the server
|
-- the next craft without a /reload). DoTradeSkill is synchronous; the
|
||||||
-- roundtrip to SPELLCAST_START gives us plenty of time after this hook.
|
-- server roundtrip to UNIT_SPELLCAST_START gives us plenty of time.
|
||||||
hooksecurefunc("DoTradeSkill", function(index, num)
|
hooksecurefunc("DoTradeSkill", function(index, num)
|
||||||
if pfUI.castbar.player then
|
if pfUI.castbar.player then
|
||||||
pfUI.castbar.player.pendingTradeskillCount = tonumber(num) or 1
|
pfUI.castbar.player.pendingTradeskillCount = tonumber(num) or 1
|
||||||
|
|||||||
Reference in New Issue
Block a user