initial commit v.2.1.0
This commit is contained in:
524
Modules/Tracker/TrackerDetection.lua
Normal file
524
Modules/Tracker/TrackerDetection.lua
Normal file
@@ -0,0 +1,524 @@
|
||||
local ADDON_NAME = "HailMaryGuildTools"
|
||||
local HMGT = LibStub("AceAddon-3.0"):GetAddon(ADDON_NAME)
|
||||
if not HMGT then return end
|
||||
|
||||
HMGT.TrackerDetection = HMGT.TrackerDetection or {}
|
||||
|
||||
local internals = HMGT.TrackerInternals or {}
|
||||
local GetSpellChargesInfo = internals.GetSpellChargesInfo
|
||||
local GetSpellCooldownInfo = internals.GetSpellCooldownInfo
|
||||
local GetGlobalCooldownInfo = internals.GetGlobalCooldownInfo
|
||||
local GetSpellDebugLabel = internals.GetSpellDebugLabel
|
||||
local BuildCooldownStateFingerprint = internals.BuildCooldownStateFingerprint
|
||||
local ApplyOwnCooldownReducers = internals.ApplyOwnCooldownReducers
|
||||
local ApplyObservedCooldownReducers = internals.ApplyObservedCooldownReducers
|
||||
|
||||
function HMGT:HandleOwnSpellCast(spellId)
|
||||
local isInterrupt = HMGT_SpellData.InterruptLookup[spellId] ~= nil
|
||||
local isCooldown = HMGT_SpellData.CooldownLookup[spellId] ~= nil
|
||||
if not isInterrupt and not isCooldown then return end
|
||||
|
||||
local spellEntry = HMGT_SpellData.InterruptLookup[spellId]
|
||||
or HMGT_SpellData.CooldownLookup[spellId]
|
||||
spellId = tonumber(spellEntry and spellEntry.spellId) or spellId
|
||||
local name = self:NormalizePlayerName(UnitName("player"))
|
||||
local pData = self.playerData[name]
|
||||
local talents = pData and pData.talents or {}
|
||||
if self:IsAvailabilitySpell(spellEntry) then
|
||||
self:LogTrackedSpellCast(name, spellEntry, {
|
||||
stateKind = "availability",
|
||||
required = HMGT_SpellData.GetEffectiveAvailabilityRequired(spellEntry, talents),
|
||||
})
|
||||
if self:RefreshOwnAvailabilitySpell(spellEntry) then
|
||||
self:PublishOwnSpellState(spellId, { sendLegacy = true })
|
||||
end
|
||||
self:TriggerTrackerUpdate()
|
||||
return
|
||||
end
|
||||
|
||||
local effectiveCd = HMGT_SpellData.GetEffectiveCooldown(spellEntry, talents)
|
||||
local now = GetTime()
|
||||
local inCombat = InCombatLockdown and InCombatLockdown()
|
||||
local cur, max, chargeStart, chargeDuration = nil, nil, nil, nil
|
||||
if not inCombat and GetSpellChargesInfo then
|
||||
cur, max, chargeStart, chargeDuration = GetSpellChargesInfo(spellId)
|
||||
end
|
||||
local cachedMaxCharges, cachedChargeDuration = self:GetKnownChargeInfo(
|
||||
spellEntry,
|
||||
talents,
|
||||
spellId,
|
||||
(not inCombat and tonumber(chargeDuration) and tonumber(chargeDuration) > 0) and tonumber(chargeDuration) or effectiveCd
|
||||
)
|
||||
local inferredMaxCharges, inferredChargeDuration = HMGT_SpellData.GetEffectiveChargeInfo(
|
||||
spellEntry,
|
||||
talents,
|
||||
(not inCombat and tonumber(max) and tonumber(max) > 0) and tonumber(max) or ((cachedMaxCharges > 0) and cachedMaxCharges or nil),
|
||||
(not inCombat and tonumber(chargeDuration) and tonumber(chargeDuration) > 0) and tonumber(chargeDuration)
|
||||
or ((cachedChargeDuration > 0) and cachedChargeDuration or effectiveCd)
|
||||
)
|
||||
|
||||
local hasCharges = ((tonumber(max) or 0) > 1) or (tonumber(inferredMaxCharges) or 0) > 1
|
||||
local currentCharges = 0
|
||||
local maxCharges = 0
|
||||
local chargeDur = 0
|
||||
local chargeStartTime = nil
|
||||
|
||||
local startTime = now
|
||||
local duration = effectiveCd
|
||||
local expiresIn = effectiveCd
|
||||
|
||||
local existingCd = self:GetActiveCooldown(name, spellId)
|
||||
if existingCd and (tonumber(existingCd.maxCharges) or 0) > 0 then
|
||||
self:ResolveChargeState(existingCd, now)
|
||||
end
|
||||
|
||||
if hasCharges then
|
||||
maxCharges = math.max(1, tonumber(max) or cachedMaxCharges or tonumber(inferredMaxCharges) or 1)
|
||||
currentCharges = tonumber(cur)
|
||||
if currentCharges == nil then
|
||||
local prevCharges = existingCd and tonumber(existingCd.currentCharges)
|
||||
local prevMax = existingCd and tonumber(existingCd.maxCharges)
|
||||
if prevCharges and prevMax and prevMax == maxCharges then
|
||||
currentCharges = math.max(0, prevCharges - 1)
|
||||
else
|
||||
currentCharges = math.max(0, maxCharges - 1)
|
||||
end
|
||||
end
|
||||
currentCharges = math.max(0, math.min(maxCharges, currentCharges))
|
||||
|
||||
chargeDur = tonumber(chargeDuration)
|
||||
or cachedChargeDuration
|
||||
or tonumber(inferredChargeDuration)
|
||||
or tonumber(effectiveCd)
|
||||
or 0
|
||||
chargeDur = math.max(0, chargeDur)
|
||||
self:StoreKnownChargeInfo(spellId, maxCharges, chargeDur)
|
||||
|
||||
if currentCharges < maxCharges and chargeDur > 0 then
|
||||
chargeStartTime = tonumber(chargeStart) or now
|
||||
local missing = maxCharges - currentCharges
|
||||
startTime = chargeStartTime
|
||||
duration = missing * chargeDur
|
||||
expiresIn = math.max(0, duration - (now - startTime))
|
||||
else
|
||||
startTime = now
|
||||
duration = 0
|
||||
expiresIn = 0
|
||||
end
|
||||
end
|
||||
|
||||
self:Debug(
|
||||
"verbose",
|
||||
"HandleOwnSpellCast name=%s spellId=%s cd=%.2f charges=%s/%s",
|
||||
tostring(name),
|
||||
tostring(spellId),
|
||||
tonumber(effectiveCd) or 0,
|
||||
hasCharges and tostring(currentCharges) or "-",
|
||||
hasCharges and tostring(maxCharges) or "-"
|
||||
)
|
||||
|
||||
self._cdNonce = (self._cdNonce or 0) + 1
|
||||
local nonce = self._cdNonce
|
||||
|
||||
self:SetActiveCooldown(name, spellId, {
|
||||
startTime = startTime,
|
||||
duration = duration,
|
||||
spellEntry = spellEntry,
|
||||
currentCharges = hasCharges and currentCharges or nil,
|
||||
maxCharges = hasCharges and maxCharges or nil,
|
||||
chargeStart = hasCharges and chargeStartTime or nil,
|
||||
chargeDuration = hasCharges and chargeDur or nil,
|
||||
_nonce = nonce,
|
||||
})
|
||||
|
||||
self:LogTrackedSpellCast(name, spellEntry, {
|
||||
cooldown = effectiveCd,
|
||||
currentCharges = hasCharges and currentCharges or nil,
|
||||
maxCharges = hasCharges and maxCharges or nil,
|
||||
chargeCooldown = hasCharges and chargeDur or nil,
|
||||
})
|
||||
|
||||
if expiresIn > 0 then
|
||||
self:ScheduleTimer(function()
|
||||
local current = self:GetActiveCooldown(name, spellId)
|
||||
if current and current._nonce == nonce then
|
||||
self:ClearActiveCooldown(name, spellId)
|
||||
self:PublishOwnSpellState(spellId)
|
||||
self:TriggerTrackerUpdate()
|
||||
end
|
||||
end, expiresIn)
|
||||
end
|
||||
|
||||
self:PublishOwnSpellState(spellId, { sendLegacy = true })
|
||||
self:TriggerTrackerUpdate()
|
||||
end
|
||||
|
||||
function HMGT:RefreshOwnCooldownStateFromGame(spellId)
|
||||
local sid = tonumber(spellId)
|
||||
if not sid then return false end
|
||||
if InCombatLockdown and InCombatLockdown() then
|
||||
return false
|
||||
end
|
||||
|
||||
local ownName = self:NormalizePlayerName(UnitName("player"))
|
||||
if not ownName then return false end
|
||||
|
||||
local spellEntry = HMGT_SpellData.InterruptLookup[sid]
|
||||
or HMGT_SpellData.CooldownLookup[sid]
|
||||
if not spellEntry or self:IsAvailabilitySpell(spellEntry) then
|
||||
return false
|
||||
end
|
||||
sid = tonumber(spellEntry.spellId) or sid
|
||||
|
||||
local existing = self:GetActiveCooldown(ownName, sid)
|
||||
local before = BuildCooldownStateFingerprint and BuildCooldownStateFingerprint(existing) or "nil"
|
||||
local now = GetTime()
|
||||
local pData = self.playerData[ownName]
|
||||
local talents = pData and pData.talents or {}
|
||||
local effectiveCd = HMGT_SpellData.GetEffectiveCooldown(spellEntry, talents)
|
||||
local cur, max, chargeStart, chargeDuration = nil, nil, nil, nil
|
||||
if GetSpellChargesInfo then
|
||||
cur, max, chargeStart, chargeDuration = GetSpellChargesInfo(sid)
|
||||
end
|
||||
local inferredMaxCharges, inferredChargeDuration = HMGT_SpellData.GetEffectiveChargeInfo(
|
||||
spellEntry,
|
||||
talents,
|
||||
(tonumber(max) and tonumber(max) > 0) and tonumber(max) or nil,
|
||||
(tonumber(chargeDuration) and tonumber(chargeDuration) > 0) and tonumber(chargeDuration) or effectiveCd
|
||||
)
|
||||
|
||||
local hasCharges = ((tonumber(max) or 0) > 1) or (tonumber(inferredMaxCharges) or 0) > 1
|
||||
|
||||
if hasCharges then
|
||||
local maxCharges = math.max(1, tonumber(max) or tonumber(inferredMaxCharges) or 1)
|
||||
local currentCharges = tonumber(cur)
|
||||
if currentCharges == nil then
|
||||
currentCharges = maxCharges
|
||||
end
|
||||
currentCharges = math.max(0, math.min(maxCharges, currentCharges))
|
||||
|
||||
local chargeDur = tonumber(chargeDuration) or tonumber(inferredChargeDuration) or tonumber(effectiveCd) or 0
|
||||
chargeDur = math.max(0, chargeDur)
|
||||
|
||||
if currentCharges < maxCharges and chargeDur > 0 then
|
||||
local chargeStartTime = tonumber(chargeStart) or now
|
||||
local missing = maxCharges - currentCharges
|
||||
local updatedEntry = self:SetActiveCooldown(ownName, sid, {
|
||||
startTime = chargeStartTime,
|
||||
duration = missing * chargeDur,
|
||||
spellEntry = spellEntry,
|
||||
currentCharges = currentCharges,
|
||||
maxCharges = maxCharges,
|
||||
chargeStart = chargeStartTime,
|
||||
chargeDuration = chargeDur,
|
||||
})
|
||||
self:RefreshCooldownExpiryTimer(ownName, sid, updatedEntry)
|
||||
else
|
||||
self:ClearActiveCooldown(ownName, sid)
|
||||
end
|
||||
else
|
||||
local cooldownStart, cooldownDuration = 0, 0
|
||||
if GetSpellCooldownInfo then
|
||||
cooldownStart, cooldownDuration = GetSpellCooldownInfo(sid)
|
||||
end
|
||||
cooldownStart = tonumber(cooldownStart) or 0
|
||||
cooldownDuration = tonumber(cooldownDuration) or 0
|
||||
local gcdStart, gcdDuration = 0, 0
|
||||
if GetGlobalCooldownInfo then
|
||||
gcdStart, gcdDuration = GetGlobalCooldownInfo()
|
||||
end
|
||||
gcdStart = tonumber(gcdStart) or 0
|
||||
gcdDuration = tonumber(gcdDuration) or 0
|
||||
local existingDuration = tonumber(existing and existing.duration) or 0
|
||||
local existingStart = tonumber(existing and existing.startTime) or now
|
||||
local existingRemaining = math.max(0, existingDuration - (now - existingStart))
|
||||
|
||||
local isLikelyGlobalCooldown = cooldownDuration > 0
|
||||
and gcdDuration > 0
|
||||
and math.abs(cooldownDuration - gcdDuration) <= 0.15
|
||||
and (tonumber(effectiveCd) or 0) > (gcdDuration + 1.0)
|
||||
|
||||
local isSuspiciousShortRefresh = cooldownDuration > 0
|
||||
and existingRemaining > 2.0
|
||||
and existingDuration > 2.0
|
||||
and cooldownDuration < math.max(2.0, existingDuration * 0.35)
|
||||
and cooldownDuration < math.max(2.0, (tonumber(effectiveCd) or 0) * 0.35)
|
||||
|
||||
if isLikelyGlobalCooldown or isSuspiciousShortRefresh then
|
||||
self:DebugScoped(
|
||||
"verbose",
|
||||
"TrackedSpells",
|
||||
"Ignore suspicious refresh for %s: spellCD=%.3f gcd=%.3f existing=%.3f remaining=%.3f effective=%.3f",
|
||||
GetSpellDebugLabel and GetSpellDebugLabel(sid) or tostring(sid),
|
||||
cooldownDuration,
|
||||
gcdDuration,
|
||||
existingDuration,
|
||||
existingRemaining,
|
||||
tonumber(effectiveCd) or 0
|
||||
)
|
||||
return false
|
||||
end
|
||||
|
||||
if cooldownDuration > 0 then
|
||||
local updatedEntry = self:SetActiveCooldown(ownName, sid, {
|
||||
startTime = cooldownStart,
|
||||
duration = cooldownDuration,
|
||||
spellEntry = spellEntry,
|
||||
})
|
||||
self:RefreshCooldownExpiryTimer(ownName, sid, updatedEntry)
|
||||
else
|
||||
self:ClearActiveCooldown(ownName, sid)
|
||||
end
|
||||
end
|
||||
|
||||
local after = BuildCooldownStateFingerprint and BuildCooldownStateFingerprint(self:GetActiveCooldown(ownName, sid)) or "nil"
|
||||
return before ~= after
|
||||
end
|
||||
|
||||
function HMGT:DidOwnInterruptSucceed(triggerSpellId, talents)
|
||||
local sid = tonumber(triggerSpellId)
|
||||
if not sid then return false end
|
||||
|
||||
local spellEntry = HMGT_SpellData and HMGT_SpellData.InterruptLookup and HMGT_SpellData.InterruptLookup[sid]
|
||||
if not spellEntry then return false end
|
||||
sid = tonumber(spellEntry.spellId) or sid
|
||||
|
||||
local observedDuration = 0
|
||||
if GetSpellCooldownInfo then
|
||||
local _, duration = GetSpellCooldownInfo(sid)
|
||||
observedDuration = duration
|
||||
end
|
||||
observedDuration = tonumber(observedDuration) or 0
|
||||
if observedDuration <= 0 then return false end
|
||||
|
||||
local expectedDuration = HMGT_SpellData.GetEffectiveCooldown(spellEntry, talents)
|
||||
expectedDuration = tonumber(expectedDuration) or 0
|
||||
if expectedDuration <= 0 then return false end
|
||||
|
||||
return observedDuration < (expectedDuration - 0.05)
|
||||
end
|
||||
|
||||
function HMGT:HandleOwnCooldownReductionTrigger(triggerSpellId)
|
||||
local ownName = self:NormalizePlayerName(UnitName("player"))
|
||||
if not ownName then return end
|
||||
|
||||
local pData = self.playerData[ownName]
|
||||
local classToken = pData and pData.class or select(2, UnitClass("player"))
|
||||
local specIndex = pData and pData.specIndex or GetSpecialization()
|
||||
local talents = pData and pData.talents or {}
|
||||
if not classToken or not specIndex then return end
|
||||
|
||||
local reducers = HMGT_SpellData.GetCooldownReducersForCast(classToken, specIndex, triggerSpellId, talents)
|
||||
if not reducers or #reducers == 0 then return end
|
||||
|
||||
local instantReducers = {}
|
||||
local observedInstantReducers = {}
|
||||
local successReducers = {}
|
||||
local observedSuccessReducers = {}
|
||||
for _, reducer in ipairs(reducers) do
|
||||
local observed = type(reducer.observe) == "table"
|
||||
if reducer.requireInterruptSuccess then
|
||||
if observed then
|
||||
observedSuccessReducers[#observedSuccessReducers + 1] = reducer
|
||||
else
|
||||
successReducers[#successReducers + 1] = reducer
|
||||
end
|
||||
else
|
||||
if observed then
|
||||
observedInstantReducers[#observedInstantReducers + 1] = reducer
|
||||
else
|
||||
instantReducers[#instantReducers + 1] = reducer
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local castTs = GetServerTime()
|
||||
if #instantReducers > 0 and ApplyOwnCooldownReducers then
|
||||
ApplyOwnCooldownReducers(self, ownName, triggerSpellId, instantReducers, castTs)
|
||||
end
|
||||
if #observedInstantReducers > 0 and ApplyObservedCooldownReducers then
|
||||
ApplyObservedCooldownReducers(self, ownName, observedInstantReducers)
|
||||
end
|
||||
|
||||
if #successReducers > 0 or #observedSuccessReducers > 0 then
|
||||
local function ApplySuccessReducers()
|
||||
if not self:DidOwnInterruptSucceed(triggerSpellId, talents) then
|
||||
return false
|
||||
end
|
||||
if #successReducers > 0 and ApplyOwnCooldownReducers then
|
||||
ApplyOwnCooldownReducers(self, ownName, triggerSpellId, successReducers, castTs)
|
||||
end
|
||||
if #observedSuccessReducers > 0 and ApplyObservedCooldownReducers then
|
||||
ApplyObservedCooldownReducers(self, ownName, observedSuccessReducers)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
if not ApplySuccessReducers() then
|
||||
C_Timer.After(0.12, function()
|
||||
if not self or not self.playerData or not self.playerData[ownName] then
|
||||
return
|
||||
end
|
||||
ApplySuccessReducers()
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function HMGT:HandleRemoteSpellCast(playerName, spellId, castTimestamp, curCharges, maxCharges, chargeRemaining, chargeDuration)
|
||||
local spellEntry = HMGT_SpellData.InterruptLookup[spellId]
|
||||
or HMGT_SpellData.CooldownLookup[spellId]
|
||||
if not spellEntry then return end
|
||||
spellId = tonumber(spellEntry.spellId) or spellId
|
||||
if self:IsAvailabilitySpell(spellEntry) then return end
|
||||
|
||||
local pData = self.playerData[playerName]
|
||||
local talents = pData and pData.talents or {}
|
||||
local effectiveCd = HMGT_SpellData.GetEffectiveCooldown(spellEntry, talents)
|
||||
|
||||
castTimestamp = tonumber(castTimestamp) or GetServerTime()
|
||||
local existingEntry = self:GetActiveCooldown(playerName, spellId)
|
||||
if (tonumber(maxCharges) or 0) <= 0 and existingEntry and existingEntry.lastCastTimestamp then
|
||||
local prevTs = tonumber(existingEntry.lastCastTimestamp) or 0
|
||||
if math.abs(prevTs - castTimestamp) <= 1 then
|
||||
return
|
||||
end
|
||||
end
|
||||
local now = GetTime()
|
||||
local elapsed = math.max(0, GetServerTime() - castTimestamp)
|
||||
|
||||
local incomingCur = tonumber(curCharges) or 0
|
||||
local incomingMax = tonumber(maxCharges) or 0
|
||||
local incomingChargeRemaining = tonumber(chargeRemaining) or 0
|
||||
local incomingChargeDuration = tonumber(chargeDuration) or 0
|
||||
|
||||
local inferredMaxCharges, inferredChargeDuration = HMGT_SpellData.GetEffectiveChargeInfo(
|
||||
spellEntry,
|
||||
talents,
|
||||
(incomingMax > 0) and incomingMax or nil,
|
||||
(incomingChargeDuration > 0) and incomingChargeDuration or effectiveCd
|
||||
)
|
||||
local hasCharges = (incomingMax > 1) or (tonumber(inferredMaxCharges) or 0) > 1
|
||||
|
||||
local currentCharges = 0
|
||||
local maxChargeCount = 0
|
||||
local chargeDur = 0
|
||||
local nextChargeRemaining = 0
|
||||
local chargeStartTime = nil
|
||||
local startTime, duration, expiresIn
|
||||
|
||||
if hasCharges then
|
||||
maxChargeCount = math.max(1, (incomingMax > 0 and incomingMax) or tonumber(inferredMaxCharges) or 1)
|
||||
chargeDur = tonumber(incomingChargeDuration) or tonumber(inferredChargeDuration) or tonumber(effectiveCd) or 0
|
||||
chargeDur = math.max(0, chargeDur)
|
||||
if chargeDur <= 0 then
|
||||
chargeDur = math.max(0, tonumber(effectiveCd) or 0)
|
||||
end
|
||||
|
||||
if incomingMax > 0 then
|
||||
currentCharges = math.max(0, math.min(maxChargeCount, incomingCur))
|
||||
nextChargeRemaining = math.max(0, math.min(chargeDur, incomingChargeRemaining - elapsed))
|
||||
if currentCharges < maxChargeCount and chargeDur > 0 then
|
||||
chargeStartTime = now - math.max(0, chargeDur - nextChargeRemaining)
|
||||
end
|
||||
else
|
||||
local existing = self:GetActiveCooldown(playerName, spellId)
|
||||
if existing and (tonumber(existing.maxCharges) or 0) == maxChargeCount then
|
||||
self:ResolveChargeState(existing, now)
|
||||
local prevCharges = tonumber(existing.currentCharges) or maxChargeCount
|
||||
local prevStart = tonumber(existing.chargeStart)
|
||||
local prevDur = tonumber(existing.chargeDuration) or chargeDur
|
||||
if prevDur > 0 then
|
||||
chargeDur = prevDur
|
||||
end
|
||||
|
||||
currentCharges = math.max(0, prevCharges - 1)
|
||||
if currentCharges < maxChargeCount and chargeDur > 0 then
|
||||
if prevCharges >= maxChargeCount then
|
||||
chargeStartTime = now
|
||||
else
|
||||
chargeStartTime = prevStart or now
|
||||
end
|
||||
nextChargeRemaining = math.max(0, chargeDur - (now - chargeStartTime))
|
||||
end
|
||||
else
|
||||
currentCharges = math.max(0, maxChargeCount - 1)
|
||||
if currentCharges < maxChargeCount and chargeDur > 0 then
|
||||
chargeStartTime = now
|
||||
nextChargeRemaining = chargeDur
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if currentCharges >= maxChargeCount and maxChargeCount > 0 then
|
||||
currentCharges = math.max(0, maxChargeCount - 1)
|
||||
if chargeDur > 0 then
|
||||
chargeStartTime = now
|
||||
nextChargeRemaining = chargeDur
|
||||
end
|
||||
end
|
||||
|
||||
if currentCharges < maxChargeCount and chargeDur > 0 then
|
||||
chargeStartTime = chargeStartTime or now
|
||||
local missing = maxChargeCount - currentCharges
|
||||
startTime = chargeStartTime
|
||||
duration = missing * chargeDur
|
||||
expiresIn = math.max(0, duration - (now - startTime))
|
||||
else
|
||||
startTime = now
|
||||
duration = 0
|
||||
expiresIn = 0
|
||||
end
|
||||
else
|
||||
local remaining = effectiveCd - elapsed
|
||||
if remaining <= 0 then return end
|
||||
startTime = now - elapsed
|
||||
duration = effectiveCd
|
||||
expiresIn = remaining
|
||||
end
|
||||
|
||||
self:Debug(
|
||||
"verbose",
|
||||
"HandleRemoteSpellCast name=%s spellId=%s elapsed=%.2f expiresIn=%.2f charges=%s/%s",
|
||||
tostring(playerName),
|
||||
tostring(spellId),
|
||||
tonumber(elapsed) or 0,
|
||||
tonumber(expiresIn) or 0,
|
||||
hasCharges and tostring(currentCharges) or "-",
|
||||
hasCharges and tostring(maxChargeCount) or "-"
|
||||
)
|
||||
|
||||
self._cdNonce = (self._cdNonce or 0) + 1
|
||||
local nonce = self._cdNonce
|
||||
|
||||
self:SetActiveCooldown(playerName, spellId, {
|
||||
startTime = startTime,
|
||||
duration = duration,
|
||||
spellEntry = spellEntry,
|
||||
currentCharges = hasCharges and currentCharges or nil,
|
||||
maxCharges = hasCharges and maxChargeCount or nil,
|
||||
chargeStart = hasCharges and chargeStartTime or nil,
|
||||
chargeDuration = hasCharges and chargeDur or nil,
|
||||
lastCastTimestamp = castTimestamp,
|
||||
_nonce = nonce,
|
||||
})
|
||||
|
||||
self:LogTrackedSpellCast(playerName, spellEntry, {
|
||||
cooldown = effectiveCd,
|
||||
currentCharges = hasCharges and currentCharges or nil,
|
||||
maxCharges = hasCharges and maxChargeCount or nil,
|
||||
chargeCooldown = hasCharges and chargeDur or nil,
|
||||
})
|
||||
|
||||
if expiresIn > 0 then
|
||||
self:ScheduleTimer(function()
|
||||
local current = self:GetActiveCooldown(playerName, spellId)
|
||||
if current and current._nonce == nonce then
|
||||
self:ClearActiveCooldown(playerName, spellId)
|
||||
self:TriggerTrackerUpdate()
|
||||
end
|
||||
end, expiresIn)
|
||||
end
|
||||
|
||||
self:TriggerTrackerUpdate()
|
||||
end
|
||||
Reference in New Issue
Block a user