initial commit v.2.1.0
This commit is contained in:
410
Modules/Tracker/TrackerState.lua
Normal file
410
Modules/Tracker/TrackerState.lua
Normal file
@@ -0,0 +1,410 @@
|
||||
local ADDON_NAME = "HailMaryGuildTools"
|
||||
local HMGT = LibStub("AceAddon-3.0"):GetAddon(ADDON_NAME)
|
||||
if not HMGT then return end
|
||||
|
||||
HMGT.TrackerState = HMGT.TrackerState or {}
|
||||
|
||||
function HMGT:EnsureTrackerStateTables()
|
||||
self.playerData = self.playerData or {}
|
||||
self.activeCDs = self.activeCDs or {}
|
||||
self.availabilityStates = self.availabilityStates or {}
|
||||
self.localSpellStateRevisions = self.localSpellStateRevisions or {}
|
||||
self.remoteSpellStateRevisions = self.remoteSpellStateRevisions or {}
|
||||
self.knownChargeInfo = self.knownChargeInfo or {}
|
||||
end
|
||||
|
||||
function HMGT:ResetTrackerState()
|
||||
self.playerData = {}
|
||||
self.activeCDs = {}
|
||||
self.availabilityStates = {}
|
||||
self.localSpellStateRevisions = {}
|
||||
self.remoteSpellStateRevisions = {}
|
||||
self.knownChargeInfo = {}
|
||||
end
|
||||
|
||||
function HMGT:GetPlayerCooldownMap(playerName, create)
|
||||
local normalizedName = self:NormalizePlayerName(playerName)
|
||||
if not normalizedName then
|
||||
return nil
|
||||
end
|
||||
self:EnsureTrackerStateTables()
|
||||
if create then
|
||||
self.activeCDs[normalizedName] = self.activeCDs[normalizedName] or {}
|
||||
end
|
||||
return self.activeCDs[normalizedName]
|
||||
end
|
||||
|
||||
function HMGT:GetAvailabilityStateMap(playerName, create)
|
||||
local normalizedName = self:NormalizePlayerName(playerName)
|
||||
if not normalizedName then
|
||||
return nil
|
||||
end
|
||||
self:EnsureTrackerStateTables()
|
||||
if create then
|
||||
self.availabilityStates[normalizedName] = self.availabilityStates[normalizedName] or {}
|
||||
end
|
||||
return self.availabilityStates[normalizedName]
|
||||
end
|
||||
|
||||
function HMGT:GetAvailabilityStateEntry(playerName, spellId)
|
||||
local sid = tonumber(spellId)
|
||||
local states = self:GetAvailabilityStateMap(playerName, false)
|
||||
return states and sid and states[sid] or nil
|
||||
end
|
||||
|
||||
function HMGT:SetAvailabilityStateEntry(playerName, spellId, stateData)
|
||||
local sid = tonumber(spellId)
|
||||
if not sid or sid <= 0 or type(stateData) ~= "table" then
|
||||
return nil
|
||||
end
|
||||
local states = self:GetAvailabilityStateMap(playerName, true)
|
||||
if not states then
|
||||
return nil
|
||||
end
|
||||
states[sid] = stateData
|
||||
return stateData
|
||||
end
|
||||
|
||||
function HMGT:ClearAvailabilityState(playerName, spellId)
|
||||
local sid = tonumber(spellId)
|
||||
local normalizedName = self:NormalizePlayerName(playerName)
|
||||
if not normalizedName or not sid or sid <= 0 then
|
||||
return false
|
||||
end
|
||||
|
||||
local states = self.availabilityStates and self.availabilityStates[normalizedName]
|
||||
if not states or not states[sid] then
|
||||
return false
|
||||
end
|
||||
|
||||
states[sid] = nil
|
||||
if not next(states) then
|
||||
self.availabilityStates[normalizedName] = nil
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function HMGT:GetActiveCooldown(playerName, spellId)
|
||||
local sid = tonumber(spellId)
|
||||
local cooldowns = self:GetPlayerCooldownMap(playerName, false)
|
||||
return cooldowns and sid and cooldowns[sid] or nil
|
||||
end
|
||||
|
||||
function HMGT:SetActiveCooldown(playerName, spellId, cdData)
|
||||
local sid = tonumber(spellId)
|
||||
if not sid or sid <= 0 or type(cdData) ~= "table" then
|
||||
return nil
|
||||
end
|
||||
local cooldowns = self:GetPlayerCooldownMap(playerName, true)
|
||||
if not cooldowns then
|
||||
return nil
|
||||
end
|
||||
cooldowns[sid] = cdData
|
||||
return cdData
|
||||
end
|
||||
|
||||
function HMGT:ClearActiveCooldown(playerName, spellId)
|
||||
local sid = tonumber(spellId)
|
||||
local normalizedName = self:NormalizePlayerName(playerName)
|
||||
if not normalizedName or not sid or sid <= 0 then
|
||||
return false
|
||||
end
|
||||
|
||||
local cooldowns = self.activeCDs and self.activeCDs[normalizedName]
|
||||
if not cooldowns or not cooldowns[sid] then
|
||||
return false
|
||||
end
|
||||
|
||||
cooldowns[sid] = nil
|
||||
if not next(cooldowns) then
|
||||
self.activeCDs[normalizedName] = nil
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function HMGT:ClearPlayerCooldowns(playerName)
|
||||
local normalizedName = self:NormalizePlayerName(playerName)
|
||||
if not normalizedName then
|
||||
return false
|
||||
end
|
||||
if self.activeCDs and self.activeCDs[normalizedName] then
|
||||
self.activeCDs[normalizedName] = nil
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function HMGT:GetLocalSpellStateRevision(spellId)
|
||||
local sid = tonumber(spellId)
|
||||
if not sid or sid <= 0 then
|
||||
return 0
|
||||
end
|
||||
self:EnsureTrackerStateTables()
|
||||
return tonumber(self.localSpellStateRevisions[sid]) or 0
|
||||
end
|
||||
|
||||
function HMGT:EnsureLocalSpellStateRevision(spellId)
|
||||
local sid = tonumber(spellId)
|
||||
if not sid or sid <= 0 then
|
||||
return 0
|
||||
end
|
||||
self:EnsureTrackerStateTables()
|
||||
local current = tonumber(self.localSpellStateRevisions[sid]) or 0
|
||||
if current <= 0 then
|
||||
current = 1
|
||||
self.localSpellStateRevisions[sid] = current
|
||||
end
|
||||
return current
|
||||
end
|
||||
|
||||
function HMGT:NextLocalSpellStateRevision(spellId)
|
||||
local sid = tonumber(spellId)
|
||||
if not sid or sid <= 0 then
|
||||
return 0
|
||||
end
|
||||
self:EnsureTrackerStateTables()
|
||||
local nextRevision = (tonumber(self.localSpellStateRevisions[sid]) or 0) + 1
|
||||
self.localSpellStateRevisions[sid] = nextRevision
|
||||
return nextRevision
|
||||
end
|
||||
|
||||
function HMGT:GetRemoteSpellStateRevision(playerName, spellId)
|
||||
local normalizedName = self:NormalizePlayerName(playerName)
|
||||
local sid = tonumber(spellId)
|
||||
local bySpell = normalizedName and self.remoteSpellStateRevisions[normalizedName]
|
||||
return tonumber(bySpell and bySpell[sid]) or 0
|
||||
end
|
||||
|
||||
function HMGT:SetRemoteSpellStateRevision(playerName, spellId, revision)
|
||||
local normalizedName = self:NormalizePlayerName(playerName)
|
||||
local sid = tonumber(spellId)
|
||||
local rev = tonumber(revision) or 0
|
||||
if not normalizedName or not sid or sid <= 0 or rev <= 0 then
|
||||
return
|
||||
end
|
||||
self:EnsureTrackerStateTables()
|
||||
self.remoteSpellStateRevisions[normalizedName] = self.remoteSpellStateRevisions[normalizedName] or {}
|
||||
self.remoteSpellStateRevisions[normalizedName][sid] = rev
|
||||
end
|
||||
|
||||
function HMGT:ClearRemoteSpellStateRevisions(playerName)
|
||||
local normalizedName = self:NormalizePlayerName(playerName)
|
||||
if not normalizedName then
|
||||
return false
|
||||
end
|
||||
if self.remoteSpellStateRevisions and self.remoteSpellStateRevisions[normalizedName] then
|
||||
self.remoteSpellStateRevisions[normalizedName] = nil
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function HMGT:ClearTrackerStateForPlayer(playerName)
|
||||
local normalizedName = self:NormalizePlayerName(playerName)
|
||||
if not normalizedName then
|
||||
return false
|
||||
end
|
||||
|
||||
local changed = false
|
||||
if self.activeCDs and self.activeCDs[normalizedName] then
|
||||
self.activeCDs[normalizedName] = nil
|
||||
changed = true
|
||||
end
|
||||
if self.availabilityStates and self.availabilityStates[normalizedName] then
|
||||
self.availabilityStates[normalizedName] = nil
|
||||
changed = true
|
||||
end
|
||||
if self.remoteSpellStateRevisions and self.remoteSpellStateRevisions[normalizedName] then
|
||||
self.remoteSpellStateRevisions[normalizedName] = nil
|
||||
changed = true
|
||||
end
|
||||
|
||||
return changed
|
||||
end
|
||||
|
||||
function HMGT:StoreKnownChargeInfo(spellId, maxCharges, chargeDuration)
|
||||
local sid = tonumber(spellId)
|
||||
local maxCount = tonumber(maxCharges)
|
||||
if not sid or sid <= 0 or not maxCount or maxCount <= 1 then
|
||||
return
|
||||
end
|
||||
|
||||
self:EnsureTrackerStateTables()
|
||||
self.knownChargeInfo[sid] = {
|
||||
maxCharges = math.max(1, math.floor(maxCount + 0.5)),
|
||||
chargeDuration = math.max(0, tonumber(chargeDuration) or 0),
|
||||
updatedAt = GetTime(),
|
||||
}
|
||||
end
|
||||
|
||||
function HMGT:GetKnownChargeInfo(spellEntry, talents, spellId, fallbackChargeDuration)
|
||||
local sid = tonumber(spellId or (spellEntry and spellEntry.spellId))
|
||||
if not sid or sid <= 0 then
|
||||
return 0, 0
|
||||
end
|
||||
|
||||
local cached = self.knownChargeInfo and self.knownChargeInfo[sid]
|
||||
local cachedMax = tonumber(cached and cached.maxCharges) or 0
|
||||
local cachedDuration = tonumber(cached and cached.chargeDuration) or 0
|
||||
|
||||
local inferredMax, inferredDuration = HMGT_SpellData.GetEffectiveChargeInfo(
|
||||
spellEntry,
|
||||
talents or {},
|
||||
(cachedMax > 0) and cachedMax or nil,
|
||||
(cachedDuration > 0) and cachedDuration or fallbackChargeDuration
|
||||
)
|
||||
|
||||
local maxCharges = math.max(cachedMax, tonumber(inferredMax) or 0)
|
||||
local chargeDuration = math.max(
|
||||
tonumber(inferredDuration) or 0,
|
||||
cachedDuration,
|
||||
tonumber(fallbackChargeDuration) or 0
|
||||
)
|
||||
|
||||
if maxCharges > 1 then
|
||||
self:StoreKnownChargeInfo(sid, maxCharges, chargeDuration)
|
||||
end
|
||||
|
||||
return maxCharges, chargeDuration
|
||||
end
|
||||
|
||||
function HMGT:PruneAvailabilityStates(playerName, knownSpells)
|
||||
local normalizedName = self:NormalizePlayerName(playerName)
|
||||
local states = normalizedName and self.availabilityStates[normalizedName]
|
||||
if not states or type(knownSpells) ~= "table" then
|
||||
return false
|
||||
end
|
||||
|
||||
local changed = false
|
||||
for sid in pairs(states) do
|
||||
if not knownSpells[tonumber(sid)] then
|
||||
states[sid] = nil
|
||||
changed = true
|
||||
end
|
||||
end
|
||||
|
||||
if not next(states) then
|
||||
self.availabilityStates[normalizedName] = nil
|
||||
end
|
||||
return changed
|
||||
end
|
||||
|
||||
function HMGT:ResolveChargeState(cdData, now)
|
||||
if type(cdData) ~= "table" then
|
||||
return 0, 0, 0, 0
|
||||
end
|
||||
|
||||
now = tonumber(now) or GetTime()
|
||||
local maxCharges = math.max(0, tonumber(cdData.maxCharges) or 0)
|
||||
local currentCharges = math.max(0, tonumber(cdData.currentCharges) or 0)
|
||||
local chargeDuration = math.max(0, tonumber(cdData.chargeDuration) or 0)
|
||||
local chargeStart = tonumber(cdData.chargeStart)
|
||||
|
||||
if maxCharges <= 0 then
|
||||
return 0, chargeDuration, currentCharges, maxCharges
|
||||
end
|
||||
if currentCharges >= maxCharges or chargeDuration <= 0 or not chargeStart then
|
||||
return 0, chargeDuration, math.min(currentCharges, maxCharges), maxCharges
|
||||
end
|
||||
|
||||
local elapsed = math.max(0, now - chargeStart)
|
||||
local gainedCharges = math.floor(elapsed / chargeDuration)
|
||||
local remaining = chargeDuration - (elapsed % chargeDuration)
|
||||
|
||||
if gainedCharges > 0 then
|
||||
currentCharges = math.min(maxCharges, currentCharges + gainedCharges)
|
||||
if currentCharges >= maxCharges then
|
||||
currentCharges = maxCharges
|
||||
chargeStart = nil
|
||||
remaining = 0
|
||||
else
|
||||
chargeStart = now - (elapsed % chargeDuration)
|
||||
end
|
||||
|
||||
cdData.currentCharges = currentCharges
|
||||
cdData.chargeStart = chargeStart
|
||||
if currentCharges >= maxCharges then
|
||||
cdData.startTime = now
|
||||
cdData.duration = 0
|
||||
else
|
||||
local missing = maxCharges - currentCharges
|
||||
cdData.startTime = chargeStart
|
||||
cdData.duration = missing * chargeDuration
|
||||
end
|
||||
end
|
||||
|
||||
if currentCharges >= maxCharges then
|
||||
return 0, chargeDuration, currentCharges, maxCharges
|
||||
end
|
||||
return math.max(0, remaining), chargeDuration, currentCharges, maxCharges
|
||||
end
|
||||
|
||||
function HMGT:RefreshCooldownExpiryTimer(playerName, spellId, cdData)
|
||||
if not cdData then return 0 end
|
||||
local now = GetTime()
|
||||
local duration = tonumber(cdData.duration) or 0
|
||||
local startTime = tonumber(cdData.startTime) or now
|
||||
local expiresIn = math.max(0, duration - (now - startTime))
|
||||
|
||||
self._cdNonce = (self._cdNonce or 0) + 1
|
||||
local nonce = self._cdNonce
|
||||
cdData._nonce = nonce
|
||||
|
||||
if expiresIn > 0 then
|
||||
self:ScheduleTimer(function()
|
||||
local current = self:GetActiveCooldown(playerName, spellId)
|
||||
if current and current._nonce == nonce then
|
||||
self:ClearActiveCooldown(playerName, spellId)
|
||||
if playerName == self:NormalizePlayerName(UnitName("player")) then
|
||||
self:PublishOwnSpellState(spellId)
|
||||
end
|
||||
self:TriggerTrackerUpdate()
|
||||
end
|
||||
end, expiresIn)
|
||||
end
|
||||
return expiresIn
|
||||
end
|
||||
|
||||
function HMGT:CleanupStaleCooldowns()
|
||||
local now = GetTime()
|
||||
local ownName = self:NormalizePlayerName(UnitName("player"))
|
||||
local removed = 0
|
||||
for playerName, spells in pairs(self.activeCDs) do
|
||||
for spellId, cdInfo in pairs(spells) do
|
||||
local duration = tonumber(cdInfo.duration) or 0
|
||||
local startTime = tonumber(cdInfo.startTime) or now
|
||||
local rem = duration - (now - startTime)
|
||||
local hasCharges = (tonumber(cdInfo.maxCharges) or 0) > 0
|
||||
local currentCharges = tonumber(cdInfo.currentCharges) or 0
|
||||
local maxCharges = tonumber(cdInfo.maxCharges) or 0
|
||||
if hasCharges then
|
||||
local _, _, cur, max = self:ResolveChargeState(cdInfo, now)
|
||||
currentCharges = cur
|
||||
maxCharges = max
|
||||
end
|
||||
local shouldDrop = false
|
||||
if hasCharges then
|
||||
if currentCharges >= maxCharges then
|
||||
shouldDrop = true
|
||||
elseif (tonumber(cdInfo.chargeDuration) or 0) <= 0 and rem <= -2 then
|
||||
shouldDrop = true
|
||||
end
|
||||
elseif rem <= -2 then
|
||||
shouldDrop = true
|
||||
end
|
||||
if shouldDrop then
|
||||
spells[spellId] = nil
|
||||
if playerName == ownName then
|
||||
self:PublishOwnSpellState(spellId)
|
||||
end
|
||||
removed = removed + 1
|
||||
end
|
||||
end
|
||||
if not next(spells) then
|
||||
self.activeCDs[playerName] = nil
|
||||
end
|
||||
end
|
||||
if removed > 0 then
|
||||
self:Debug("verbose", "CleanupStaleCooldowns removed=%d", removed)
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user