casts: route everything through C_Spell, drop libcast + libdebuff_casts

ClassicAPI's recent C_Spell additions cover remote-unit casts natively
(SMSG_SPELL_START co-hook caching per caster GUID), so the two parallel
cast trackers pfUI was running — libcast.lua and the libdebuff_casts
table inside libdebuff.lua — can both retire.

Migrations:
- modules/castbar.lua: focus/player cast-info gathering reads
  C_Spell.UnitCastingInfo / UnitChannelInfo directly. Fallback ladder
  (libdebuff_casts → pfGetCastInfo → pfGetChannelInfo) collapses into a
  single call. Pushback handlers stop writing back into a non-existent
  cache; the local this.endTime is the source of truth.
- modules/nameplates.lua: GetCastInfo(guid) now resolves to a unit token
  via UnitTokenFromGUID and queries C_Spell, returning the same compact
  struct shape downstream code expected. UpdateCastbar collapses from a
  three-branch hierarchy (dead IterDebuffs / libdebuff_casts / libcast)
  to one C_Spell read.
- modules/afkcam.lua: pfGetCastInfo+pfGetChannelInfo round-trip becomes
  a single C_Spell.UnitCastingInfo("player") or UnitChannelInfo fallback.
- libs/libpredict.lua: HealComm timing uses C_Spell on the sender's unit
  token after a small group-roster walk to resolve the sender's name.

Deletions:
- libs/libcast.lua entirely (-571 lines) plus its init/libs.xml entry.
- libdebuff_casts / libdebuff_item_icons tables and their write sites
  in libs/libdebuff.lua (the SPELL_START_*, SPELL_GO_*, SPELL_FAILED_*
  event handlers stop maintaining them but keep firing the
  libdebuff_*_hooks broadcast surface for actionbar / swingtimer /
  libtotem). SPELLCAST_CHANNEL_STOP now reads the active channel from
  C_Spell.ChannelInfo.
- modules/superwow.lua's supercast block — UNIT_CASTEVENT writes into
  libcast.db are redundant now that C_Spell co-hooks the same packet.
- The cast-bar item-icon override that swapped in a potion/trinket
  icon for item-triggered casts. Spell icon stays; the item-icon
  metadata path (libdebuff_item_icons) went with libdebuff_casts.

Steady Shot synthetic cast bar — Turtle WoW-specific:
- castbar.lua gains a pfUI.synthetic_casts[unit] fallback that fires
  only when C_Spell returns nil, so abilities the engine treats as
  instant but which have a meaningful wait window can still render a
  cast bar.
- modules/turtle-wow.lua replaces the old libcast.customcast block with
  a Nampower SPELL_QUEUE_EVENT subscriber. ON_SWING_QUEUED matching
  the localized "Steady Shot" name writes a 1.4s synthetic entry;
  ON_SWING_QUEUE_POPPED clears it; castbar's endMs guard self-expires
  the entry as a safety net. Note: haste scaling (libcast.ApplyShotHaste)
  is gone — bar may finish slightly early under +ranged haste buffs.

Net: 152 insertions, 972 deletions.
This commit is contained in:
Brues
2026-06-25 01:18:22 -05:00
parent d62a1222df
commit b79132b9f4
9 changed files with 152 additions and 972 deletions
+1 -3
View File
@@ -151,9 +151,7 @@ pfUI:RegisterModule("afkcam", function ()
delay:SetScript("OnUpdate", function()
if ( this.tick or 0) > GetTime() then return else this.tick = GetTime() + 1 end
local name = UnitName("player")
local cast = pfGetCastInfo(name)
if not cast then cast = pfGetChannelInfo(name) end
local cast = C_Spell.UnitCastingInfo("player") or C_Spell.UnitChannelInfo("player")
if not this.delay then this.delay = 0 end
if cast then
+29 -88
View File
@@ -103,60 +103,37 @@ pfUI:RegisterModule("castbar", function ()
local query = this.unitstr ~= "" and this.unitstr or this.unitname
if not query then return end
-- Check if we have a GUID-based focus (Turtle WoW native GUID)
local focusGuid = nil
if this.unitstr and string.find(this.unitstr, "^0x") then
focusGuid = this.unitstr
elseif this.unitstr and this.unitstr == "player" and UnitGUID then
focusGuid = UnitGUID("player")
elseif this.unitstr and this.unitstr ~= "player" then
local guid = UnitGUID(this.unitstr)
if guid then focusGuid = guid end
-- C_Spell takes a unit token; ClassicAPI's resolver also accepts GUID
-- strings, so this.unitstr can be "player" / "target" / "focus" or a
-- raw "0x..." GUID transparently.
local cast, nameSubtext, texture, startTime, endTime
local name, _, tex, startMs, endMs, _, _, _, spellID = C_Spell.UnitCastingInfo(query)
local isChan
if not name then
name, _, tex, startMs, endMs, _, _, spellID = C_Spell.UnitChannelInfo(query)
isChan = true
end
this.focusGuid = focusGuid
-- Try libdebuff_casts first for GUID-based units (works with Turtle GUID + Nampower events)
local cast, nameSubtext, text, texture, startTime, endTime
local castBlocked = false
if focusGuid and pfUI.libdebuff_casts and pfUI.libdebuff_casts[focusGuid] then
local castData = pfUI.libdebuff_casts[focusGuid]
if castData.event == "CAST" or castData.event == "FAIL" then
castBlocked = true
pfUI.libdebuff_casts[focusGuid] = nil
elseif (castData.event == "START" or castData.event == "CHANNEL") and castData.endTime and castData.endTime > GetTime() then
cast = castData.spellName
texture = castData.icon
startTime = castData.startTime * 1000
endTime = castData.endTime * 1000
-- Try to get rank from spell DB via spellID (nameSubtext is not stored in libdebuff_casts)
if castData.spellID and GetSpellRecField then
nameSubtext = GetSpellRecField(castData.spellID, "rank") or ""
else
nameSubtext = ""
end
if castData.event == "CHANNEL" then
channel = cast
end
-- Synthetic fallback for abilities the engine treats as instant-cast but
-- that have a meaningful wait window (e.g. Turtle WoW Steady Shot on the
-- ranged-swing queue). Per-unit table populated by module-side hooks.
if not name and pfUI.synthetic_casts and pfUI.synthetic_casts[query] then
local s = pfUI.synthetic_casts[query]
if s.endMs > GetTime() * 1000 then
name, tex, startMs, endMs, spellID = s.name, s.icon, s.startMs, s.endMs, s.spellID
isChan = nil
end
end
local useLibcastForPlayer = this.unitstr == "player"
-- For player: use player name to query libcast.db directly
if not cast and useLibcastForPlayer then
query = UnitName("player")
end
-- Fallback: pfGetCastInfo only when no focusGuid (Nampower not available for this unit).
-- If we have a focusGuid, libdebuff is authoritative - don't fall back to libcast
-- even if no cast is active (prevents false positives e.g. spellbook clicks on CD spells).
if not cast and not castBlocked and not focusGuid and pfGetCastInfo then
cast, nameSubtext, text, texture, startTime, endTime, isTradeSkill = pfGetCastInfo(query)
end
if not cast and not castBlocked and not focusGuid and pfGetChannelInfo then
channel, nameSubtext, text, texture, startTime, endTime, isTradeSkill = pfGetChannelInfo(this.unitstr or this.unitname)
cast = channel
if name and startMs and endMs then
cast = name
texture = tex
startTime = startMs
endTime = endMs
if spellID and GetSpellRecField then
nameSubtext = GetSpellRecField(spellID, "rank") or ""
else
nameSubtext = ""
end
if isChan then channel = cast end
end
if cast then
@@ -181,35 +158,8 @@ pfUI:RegisterModule("castbar", function ()
this.icon:Show()
this.icon:SetHeight(size)
this.icon:SetWidth(size)
-- Override with item icon from libdebuff_casts or persistent item icon cache
local useTexture = texture
local useItemName = nil
if pfUI.libdebuff_casts or pfUI.libdebuff_item_icons then
local castGuid = nil
if this.unitstr and UnitExists then
local guid = UnitGUID(this.unitstr)
castGuid = guid
end
if castGuid then
-- First check active cast data
if pfUI.libdebuff_casts and pfUI.libdebuff_casts[castGuid] and pfUI.libdebuff_casts[castGuid].itemID then
useTexture = pfUI.libdebuff_casts[castGuid].icon or texture
-- Fallback to persistent item icon cache
elseif pfUI.libdebuff_item_icons and pfUI.libdebuff_item_icons[castGuid] then
useTexture = pfUI.libdebuff_item_icons[castGuid].icon or texture
useItemName = pfUI.libdebuff_item_icons[castGuid].name
end
end
end
this.icon.texture:SetTexture(useTexture)
this.icon.texture:SetTexture(texture)
this.bar:SetPoint("TOPLEFT", this.icon, "TOPRIGHT", this.spacing, 0)
-- Override spell name with item name for item-triggered casts
if useItemName and this.showname then
this.bar.left:SetText(useItemName .. " " .. rank)
end
else
this.bar:SetPoint("TOPLEFT", this, 0, 0)
this.icon:Hide()
@@ -255,7 +205,6 @@ pfUI:RegisterModule("castbar", function ()
this.lastMax = nil
this.fadeout = 1
this.delay = 0
this.itemIconApplied = nil
end
end)
@@ -267,10 +216,6 @@ pfUI:RegisterModule("castbar", function ()
if not delayMs or delayMs <= 0 or not this.endTime then return end
this.delay = (this.delay or 0) + delayMs / 1000
this.endTime = this.endTime + delayMs
local focusGuid = this.focusGuid
if focusGuid and pfUI.libdebuff_casts and pfUI.libdebuff_casts[focusGuid] then
pfUI.libdebuff_casts[focusGuid].endTime = this.endTime / 1000
end
end
cb:RegisterEvent("SPELL_DELAYED_SELF")
@@ -299,10 +244,6 @@ pfUI:RegisterModule("castbar", function ()
if diff > 50 then
this.delay = (this.delay or 0) + diff / 1000
this.endTime = newEndTime
local focusGuid = this.focusGuid
if focusGuid and pfUI.libdebuff_casts and pfUI.libdebuff_casts[focusGuid] then
pfUI.libdebuff_casts[focusGuid].endTime = newEndTime / 1000
end
end
elseif event == CASTBAR_EVENT_CAST_START or event == CASTBAR_EVENT_CHANNEL_START then
+53 -103
View File
@@ -3,8 +3,6 @@ pfUI:RegisterModule("nameplates", function ()
pcall(SetCVar, "ShowVKeyCastbar", 0)
-- Local function references for performance
local pfGetCastInfo = pfGetCastInfo -- provided by libcast for vanilla
local pfGetChannelInfo = pfGetChannelInfo -- provided by libcast for vanilla
local GetTime = GetTime
local UnitExists = UnitExists
local UnitName = UnitName
@@ -65,9 +63,30 @@ pfUI:RegisterModule("nameplates", function ()
local raidGuidCache = {} -- guid -> name (rebuilt on RAID_ROSTER_UPDATE/PARTY_MEMBERS_CHANGED)
-- Helper function to safely access libdebuff cast data
-- Resolve a plate GUID to its cast/channel info via C_Spell. Returns a
-- compact struct (spellName / icon / startTime / endTime / duration /
-- isChannel) or nil when the unit isn't casting / the GUID can't map to a
-- live token.
local function GetCastInfo(guid)
return pfUI.libdebuff_casts and pfUI.libdebuff_casts[guid]
if not guid then return nil end
local unit = UnitTokenFromGUID(guid)
if not unit then return nil end
local name, _, texture, startMs, endMs, _, _, _, spellID = C_Spell.UnitCastingInfo(unit)
local isChannel
if not name then
name, _, texture, startMs, endMs, _, _, spellID = C_Spell.UnitChannelInfo(unit)
isChannel = true
end
if not name or not startMs or not endMs then return nil end
return {
spellName = name,
spellID = spellID,
icon = texture,
startTime = startMs / 1000,
endTime = endMs / 1000,
duration = (endMs - startMs) / 1000,
isChannel = isChannel,
}
end
local guidTargetTokenCache = {} -- guid -> "<guid>target" interned string
@@ -245,15 +264,6 @@ pfUI:RegisterModule("nameplates", function ()
return
end
local function wipe(table)
if type(table) ~= "table" then
return
end
for k in pairs(table) do
table[k] = nil
end
end
local function DisableObject(object)
if not object then return end
if not object.GetObjectType then return end
@@ -559,10 +569,6 @@ end
if threatMemory[guid] then threatMemory[guid] = nil end
if guidTargetTokenCache[guid] then guidTargetTokenCache[guid] = nil end
if combatColorCache[guid] then combatColorCache[guid] = nil end
local castInfo = GetCastInfo(guid)
if castInfo and castInfo.endTime and castInfo.endTime < GetTime() then
if pfUI.libdebuff_casts then pfUI.libdebuff_casts[guid] = nil end
end
local plate = C_NamePlate.GetNamePlateForUnit(arg1)
if plate and plate.nameplate and plate.nameplate.cachedGuid == guid then
plate.nameplate.cachedGuid = nil
@@ -1305,9 +1311,7 @@ end
local isCastingNonTarget = not target and nameplate.castbar and nameplate.castbar:IsShown()
if not isCastingNonTarget and not target and cfg.showcastbar and nameplate.cachedGuid then
local castInfo = GetCastInfo(nameplate.cachedGuid)
if castInfo and castInfo.spellID and castInfo.endTime and castInfo.endTime > now then
isCastingNonTarget = true
elseif pfGetCastInfo and nameplate.castUpdate then
if castInfo and castInfo.endTime > now then
isCastingNonTarget = true
end
end
@@ -1595,94 +1599,40 @@ end
-- Shared castbar update logic (used by both dedicated frame and central loop)
nameplates.UpdateCastbar = function(nameplate, now)
if not nameplate or not nameplate.castbar then return end
local unitstr = nameplate.cachedGuid
local castInfo = unitstr and GetCastInfo(unitstr)
local castInfo = GetCastInfo(nameplate.cachedGuid)
if not castInfo or castInfo.endTime < now then
nameplate.castbar.isShown = nil
nameplate.castbar.lastEndTime = nil
nameplate.castbar:Hide()
return
end
if castInfo and castInfo.spellID then
if castInfo.startTime + castInfo.duration < now then
wipe(castInfo)
nameplate.castbar:Hide()
elseif castInfo.event == "CAST" or castInfo.event == "FAIL" then
wipe(castInfo)
nameplate.castbar:Hide()
else
-- Only update min/max, color and icon once per cast (when endTime changes)
local isChannel = castInfo.event == "CHANNEL"
local duration = castInfo.endTime - castInfo.startTime
if nameplate.castbar.lastEndTime ~= castInfo.endTime then
nameplate.castbar.lastEndTime = castInfo.endTime
nameplate.castbar.lastTextTick = nil
-- Use relative 0..duration range (same as castbar.lua) to avoid
-- floating-point precision loss with large absolute timestamps
nameplate.castbar:SetMinMaxValues(0, duration)
nameplate.castbar:SetStatusBarColor(strsplit(",", C.appearance.castbar[(isChannel and "channelcolor" or "castbarcolor")]))
if castInfo.icon then
nameplate.castbar.icon.tex:SetTexture(castInfo.icon)
nameplate.castbar.icon.tex:SetTexCoord(.1,.9,.1,.9)
end
if cfg.spellname then
nameplate.castbar.spell:SetText(castInfo.spellName)
else
nameplate.castbar.spell:SetText("")
end
end
local barValue
if isChannel then
barValue = castInfo.endTime - now
else
barValue = now - castInfo.startTime
end
barValue = barValue < 0 and 0 or barValue
barValue = barValue > duration and duration or barValue
nameplate.castbar:SetValue(barValue)
SetCastbarText(nameplate.castbar, castInfo.endTime - now)
if not nameplate.castbar.isShown then nameplate.castbar.isShown = true; nameplate.castbar:Show() end
local isChannel = castInfo.isChannel
local duration = castInfo.duration
if nameplate.castbar.lastEndTime ~= castInfo.endTime then
nameplate.castbar.lastEndTime = castInfo.endTime
nameplate.castbar.lastTextTick = nil
-- Relative 0..duration range to avoid float precision loss with large
-- absolute timestamps.
nameplate.castbar:SetMinMaxValues(0, duration)
nameplate.castbar:SetStatusBarColor(strsplit(",", C.appearance.castbar[(isChannel and "channelcolor" or "castbarcolor")]))
if castInfo.icon then
nameplate.castbar.icon.tex:SetTexture(castInfo.icon)
nameplate.castbar.icon.tex:SetTexCoord(.1,.9,.1,.9)
end
else
-- libcast fallback (vanilla without Nampower)
if unitstr and pfGetCastInfo then
local cast, _, _, texture, startTime, endTime = pfGetCastInfo(unitstr)
local channel
if not cast then
channel, _, _, texture, startTime, endTime = pfGetChannelInfo(unitstr)
end
if cast or channel then
local effect = cast or channel
local duration = endTime - startTime
local max = duration / 1000
local cur = now - startTime / 1000
if channel then cur = max + startTime / 1000 - now end
if cur < 0 then cur = 0 end
if cur > max then cur = max end
if nameplate.castbar.lastEndTime ~= endTime then
nameplate.castbar.lastEndTime = endTime
nameplate.castbar.lastTextTick = nil
nameplate.castbar:SetMinMaxValues(0, max)
nameplate.castbar:SetStatusBarColor(strsplit(",", C.appearance.castbar[(channel and "channelcolor" or "castbarcolor")]))
if texture then
nameplate.castbar.icon.tex:SetTexture(texture)
nameplate.castbar.icon.tex:SetTexCoord(.1, .9, .1, .9)
end
if cfg.spellname then
nameplate.castbar.spell:SetText(effect)
else
nameplate.castbar.spell:SetText("")
end
end
nameplate.castbar:SetValue(cur)
SetCastbarText(nameplate.castbar, channel and cur or (max - cur))
if not nameplate.castbar.isShown then nameplate.castbar.isShown = true; nameplate.castbar:Show() end
else
nameplate.castbar.isShown = nil
nameplate.castbar.lastEndTime = nil
nameplate.castbar:Hide()
end
if cfg.spellname then
nameplate.castbar.spell:SetText(castInfo.spellName)
else
nameplate.castbar.isShown = nil
nameplate.castbar.lastEndTime = nil
nameplate.castbar:Hide()
nameplate.castbar.spell:SetText("")
end
end
local barValue = isChannel and (castInfo.endTime - now) or (now - castInfo.startTime)
if barValue < 0 then barValue = 0 end
if barValue > duration then barValue = duration end
nameplate.castbar:SetValue(barValue)
SetCastbarText(nameplate.castbar, castInfo.endTime - now)
if not nameplate.castbar.isShown then nameplate.castbar.isShown = true; nameplate.castbar:Show() end
end
-- Dedicated frame that updates ONLY the target plate castbar.
-98
View File
@@ -174,102 +174,4 @@ pfUI:RegisterModule("superwow", function ()
end
end
-- Enhance libcast with SuperWoW data for NPCs and other players
-- Player casts use SPELLCAST_* events for proper pushback handling
local supercast = CreateFrame("Frame")
local playerGuid = nil
supercast:RegisterEvent("PLAYER_ENTERING_WORLD")
supercast:RegisterEvent("UNIT_CASTEVENT")
supercast:RegisterEvent("PLAYER_LOGOUT")
supercast:SetScript("OnEvent", function()
-- Handle shutdown to prevent crash 132
if event == "PLAYER_LOGOUT" then
this:UnregisterAllEvents()
this:SetScript("OnEvent", nil)
return
end
if event == "PLAYER_ENTERING_WORLD" then
-- Cache player GUID
if UnitExists then
local guid = UnitGUID("player")
playerGuid = guid
end
return
end
local guid = arg1
local isPlayer = guid == playerGuid
-- For non-player units: disable combat parsing events (one-time init)
if not isPlayer and not supercast.init then
-- disable combat parsing events in superwow mode (for non-player units)
libcast:UnregisterEvent("CHAT_MSG_SPELL_SELF_DAMAGE")
libcast:UnregisterEvent("CHAT_MSG_SPELL_HOSTILEPLAYER_DAMAGE")
libcast:UnregisterEvent("CHAT_MSG_SPELL_HOSTILEPLAYER_BUFF")
libcast:UnregisterEvent("CHAT_MSG_SPELL_FRIENDLYPLAYER_DAMAGE")
libcast:UnregisterEvent("CHAT_MSG_SPELL_FRIENDLYPLAYER_BUFF")
libcast:UnregisterEvent("CHAT_MSG_SPELL_PERIODIC_HOSTILEPLAYER_BUFFS")
libcast:UnregisterEvent("CHAT_MSG_SPELL_PERIODIC_FRIENDLYPLAYER_BUFFS")
libcast:UnregisterEvent("CHAT_MSG_SPELL_PERIODIC_HOSTILEPLAYER_DAMAGE")
libcast:UnregisterEvent("CHAT_MSG_SPELL_PERIODIC_FRIENDLYPLAYER_DAMAGE")
libcast:UnregisterEvent("CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE")
libcast:UnregisterEvent("CHAT_MSG_SPELL_PARTY_DAMAGE")
libcast:UnregisterEvent("CHAT_MSG_SPELL_PARTY_BUFF")
libcast:UnregisterEvent("CHAT_MSG_SPELL_PERIODIC_PARTY_DAMAGE")
libcast:UnregisterEvent("CHAT_MSG_SPELL_PERIODIC_PARTY_BUFFS")
libcast:UnregisterEvent("CHAT_MSG_SPELL_PERIODIC_CREATURE_DAMAGE")
libcast:UnregisterEvent("CHAT_MSG_SPELL_PERIODIC_CREATURE_BUFFS")
libcast:UnregisterEvent("CHAT_MSG_SPELL_CREATURE_VS_CREATURE_DAMAGE")
libcast:UnregisterEvent("CHAT_MSG_SPELL_CREATURE_VS_CREATURE_BUFF")
supercast.init = true
end
if arg3 == "START" or arg3 == "CAST" or arg3 == "CHANNEL" then
local target = arg2
local event_type = arg3
local spell_id = arg4
local timer = arg5
local spell = C_Spell.GetSpellName(spell_id) or UNKNOWN
local icon = C_Spell.GetSpellTexture(spell_id) or "Interface\\Icons\\INV_Misc_QuestionMark"
-- skip on buff procs during cast
if event_type == "CAST" then
if not libcast.db[guid] or libcast.db[guid].cast ~= spell then
-- ignore casts without 'START' event, while there is already another cast.
-- those events can be for example a frost shield proc while casting frostbolt.
-- we want to keep the cast itself, so we simply skip those.
return
end
end
-- For player: store in libcast.db[playerName] so pushback tracking works
-- For others: store by GUID
local dbKey = isPlayer and UnitName("player") or guid
-- add cast action to the database
if not libcast.db[dbKey] then libcast.db[dbKey] = {} end
libcast.db[dbKey].cast = spell
libcast.db[dbKey].rank = nil
libcast.db[dbKey].start = GetTime()
libcast.db[dbKey].casttime = timer or 0
libcast.db[dbKey].icon = icon
libcast.db[dbKey].channel = event_type == "CHANNEL" or false
elseif arg3 == "FAIL" then
-- For player: use playerName, for others: use GUID
local dbKey = isPlayer and UnitName("player") or guid
-- delete all cast entries
if libcast.db[dbKey] then
libcast.db[dbKey].cast = nil
libcast.db[dbKey].rank = nil
libcast.db[dbKey].start = nil
libcast.db[dbKey].casttime = nil
libcast.db[dbKey].icon = nil
libcast.db[dbKey].channel = nil
end
end
end)
end)
+32 -33
View File
@@ -312,42 +312,41 @@ pfUI:RegisterModule("turtle-wow", function ()
end
end)
-- add Trueshot recognition to to custom castbars.
if not libcast.customcast["steadyshot"] then
-- add trueshot to pfUI's custom casts
local player = UnitName("player")
-- Synthetic Steady Shot cast bar. The engine treats Steady Shot as
-- instant, but the arrow doesn't actually fire until the next ranged
-- swing (~1.4s baseline). Drive a synthetic entry off Nampower's
-- SPELL_QUEUE_EVENT so hunters see a tickdown matching the swing wait.
-- castbar.lua reads pfUI.synthetic_casts[unit] as a fallback when
-- C_Spell.UnitCastingInfo returns nil.
pfUI_locale["enUS"]["customcast"]["TRUESHOT"] = "Steady Shot"
pfUI_locale["zhCN"]["customcast"]["TRUESHOT"] = "稳固射击"
pfUI.synthetic_casts = pfUI.synthetic_casts or {}
-- add locales
pfUI_locale["enUS"]["customcast"]["TRUESHOT"] = "Steady Shot"
pfUI_locale["zhCN"]["customcast"]["TRUESHOT"] = "稳固射击"
local trueshot = L["customcast"]["TRUESHOT"] or ""
local steadyShotName = L["customcast"]["TRUESHOT"]
local STEADY_SHOT_ICON = "Interface\\Icons\\Ability_hunter_steadyshot"
local STEADY_SHOT_DURATION_MS = 1400 -- not haste-scaled; close enough for the visual cue
local ON_SWING_QUEUED, ON_SWING_QUEUE_POPPED = 0, 1
libcast.customcast[strlower(trueshot)] = function(begin, duration)
if begin then
-- cast time is 1sec, however it takes 1.4sec to fire in average
local duration = libcast.ApplyShotHaste(duration or 1400)
local _,_, lag = GetNetStats()
local start = GetTime() + lag/1000
-- add cast action to the database
libcast.db[player].cast = trueshot
libcast.db[player].rank = lastrank
libcast.db[player].start = start
libcast.db[player].casttime = duration
libcast.db[player].icon = "Interface\\Icons\\Ability_hunter_steadyshot"
libcast.db[player].channel = nil
else
-- remove cast action to the database
libcast.db[player].cast = nil
libcast.db[player].rank = nil
libcast.db[player].start = nil
libcast.db[player].casttime = nil
libcast.db[player].icon = nil
libcast.db[player].channel = nil
end
local steadyShot = CreateFrame("Frame")
steadyShot:RegisterEvent("SPELL_QUEUE_EVENT")
steadyShot:SetScript("OnEvent", function()
if event ~= "SPELL_QUEUE_EVENT" then return end
local eventCode, spellId = arg1, arg2
if eventCode ~= ON_SWING_QUEUED and eventCode ~= ON_SWING_QUEUE_POPPED then return end
if C_Spell.GetSpellName(spellId) ~= steadyShotName then return end
if eventCode == ON_SWING_QUEUED then
local now = GetTime() * 1000
pfUI.synthetic_casts["player"] = {
name = steadyShotName,
icon = STEADY_SHOT_ICON,
startMs = now,
endMs = now + STEADY_SHOT_DURATION_MS,
spellID = spellId,
}
else
pfUI.synthetic_casts["player"] = nil
end
end
end)
-- add skin to twow's talent inspect frame
if pfUI.skin["Inspect"] and pfUI_config["disabled"]["skin_Inspect"] ~= "1" then