local ADDON_NAME = "HailMaryGuildTools" local HMGT = LibStub("AceAddon-3.0"):GetAddon(ADDON_NAME) if not HMGT then return end HMGT.TrackerDataProvider = HMGT.TrackerDataProvider or {} local internals = HMGT.TrackerInternals or {} local SafeApiNumber = internals.SafeApiNumber local GetSpellChargesInfo = internals.GetSpellChargesInfo local GetSpellCooldownInfo = internals.GetSpellCooldownInfo function HMGT:GetCooldownInfo(playerName, spellId, opts) opts = opts or {} local deferUntilEmpty = opts.deferChargeCooldownUntilEmpty and true or false local spellEntry = HMGT_SpellData.InterruptLookup[spellId] or HMGT_SpellData.CooldownLookup[spellId] local ownName = self:NormalizePlayerName(UnitName("player")) local isOwnPlayer = playerName == ownName local pData = isOwnPlayer and self.playerData[ownName] or nil local talents = pData and pData.talents or {} local effectiveCd = spellEntry and HMGT_SpellData.GetEffectiveCooldown(spellEntry, talents) or 0 local knownMaxCharges, knownChargeDuration = 0, 0 if spellEntry and isOwnPlayer then knownMaxCharges, knownChargeDuration = self:GetKnownChargeInfo(spellEntry, talents, spellId, effectiveCd) end if self:IsAvailabilitySpell(spellEntry) then local normalizedName = self:NormalizePlayerName(playerName) if normalizedName == ownName then local current, max = self:GetOwnAvailabilityProgress(spellEntry) if (tonumber(max) or 0) > 0 then self:StoreAvailabilityState(ownName, spellId, current, max, spellEntry) return 0, 0, current, max end else local current, max = self:GetAvailabilityState(normalizedName, spellId) if (tonumber(max) or 0) > 0 then return 0, 0, current, max end end return 0, 0, nil, nil end local cdData = self:GetActiveCooldown(playerName, spellId) if isOwnPlayer and not (InCombatLockdown and InCombatLockdown()) then local charges, maxCharges, chargeStart, chargeDuration = nil, nil, nil, nil if GetSpellChargesInfo then charges, maxCharges, chargeStart, chargeDuration = GetSpellChargesInfo(spellId) end charges = SafeApiNumber and SafeApiNumber(charges, 0) or tonumber(charges) or 0 maxCharges = SafeApiNumber and SafeApiNumber(maxCharges, 0) or tonumber(maxCharges) or 0 chargeStart = SafeApiNumber and SafeApiNumber(chargeStart) or tonumber(chargeStart) chargeDuration = SafeApiNumber and SafeApiNumber(chargeDuration, 0) or tonumber(chargeDuration) or 0 if maxCharges > 0 then local tempChargeState = { currentCharges = charges, maxCharges = maxCharges, chargeStart = chargeStart, chargeDuration = chargeDuration, duration = chargeDuration, } local remaining, total, curCharges, maxChargeCount = self:ResolveChargeState(tempChargeState) self:StoreKnownChargeInfo(spellId, maxChargeCount, total > 0 and total or chargeDuration) if (curCharges or 0) < maxChargeCount and remaining <= 0 and GetSpellCooldownInfo then local cdStart, cdDuration = GetSpellCooldownInfo(spellId) if cdDuration > 0 then remaining = math.max(0, cdDuration - (GetTime() - cdStart)) total = math.max(total or 0, cdDuration) end end if deferUntilEmpty and (curCharges or 0) > 0 then remaining = 0 end return remaining, total, curCharges, maxChargeCount end if GetSpellCooldownInfo then local cdStart, cdDuration = GetSpellCooldownInfo(spellId) cdStart = tonumber(cdStart) or 0 cdDuration = tonumber(cdDuration) or 0 if cdDuration > 0 then local remaining = math.max(0, cdDuration - (GetTime() - cdStart)) remaining = math.max(0, math.min(cdDuration, remaining)) if cdData and (tonumber(cdData.maxCharges) or 0) <= 0 then local cachedRemaining = (tonumber(cdData.duration) or 0) - (GetTime() - (tonumber(cdData.startTime) or GetTime())) cachedRemaining = math.max(0, math.min(tonumber(cdData.duration) or cachedRemaining, cachedRemaining)) local cachedDuration = math.max(0, tonumber(cdData.duration) or 0) if cachedDuration > 2.0 and cachedRemaining > 2.0 and cdDuration < math.max(2.0, cachedDuration * 0.35) then return cachedRemaining, cachedDuration, nil, nil end end return remaining, cdDuration, nil, nil end end end if not cdData then if isOwnPlayer and knownMaxCharges > 1 then return 0, math.max(0, knownChargeDuration or effectiveCd or 0), knownMaxCharges, knownMaxCharges end return 0, 0, nil, nil end if (tonumber(cdData.maxCharges) or 0) > 0 then local remaining, chargeDur, charges, maxCharges = self:ResolveChargeState(cdData) self:StoreKnownChargeInfo(spellId, maxCharges, chargeDur) if deferUntilEmpty and charges > 0 then remaining = 0 end return remaining, chargeDur, charges, maxCharges end if isOwnPlayer and knownMaxCharges > 1 then local remaining = (tonumber(cdData.duration) or 0) - (GetTime() - (tonumber(cdData.startTime) or GetTime())) remaining = math.max(0, math.min(tonumber(cdData.duration) or remaining, remaining)) local currentCharges = knownMaxCharges if remaining > 0 then currentCharges = math.max(0, knownMaxCharges - 1) end if deferUntilEmpty and currentCharges > 0 then remaining = 0 end return remaining, math.max(0, knownChargeDuration or effectiveCd or 0), currentCharges, knownMaxCharges end local remaining = cdData.duration - (GetTime() - cdData.startTime) remaining = math.max(0, math.min(cdData.duration, remaining)) return remaining, cdData.duration, nil, nil end function HMGT:ShouldDisplayEntry(settings, remaining, currentCharges, maxCharges, spellEntry) local rem = tonumber(remaining) or 0 local cur = tonumber(currentCharges) or 0 local max = tonumber(maxCharges) or 0 local soon = tonumber(settings.readySoonSec) or 0 local isAvailabilitySpell = spellEntry and self:IsAvailabilitySpell(spellEntry) or false local isReady if isAvailabilitySpell then isReady = max > 0 and cur >= max else isReady = rem <= 0 or (max > 0 and cur > 0) end if settings.showOnlyReady then return isReady end if soon > 0 then if isAvailabilitySpell then return isReady end return isReady or rem <= soon end return true end local DEFAULT_CATEGORY_PRIORITY = { interrupt = 1, lust = 2, defensive = 3, tank = 4, healing = 5, offensive = 6, utility = 7, cc = 8, } local TRACKER_CATEGORY_PRIORITY = { interruptTracker = { interrupt = 1, defensive = 2, utility = 3, cc = 4, healing = 5, tank = 6, offensive = 7, lust = 8, }, raidCooldownTracker = { lust = 1, defensive = 2, healing = 3, tank = 4, utility = 5, offensive = 6, cc = 7, interrupt = 8, }, groupCooldownTracker = { tank = 1, defensive = 2, healing = 3, cc = 4, utility = 5, offensive = 6, lust = 7, interrupt = 8, }, } local function GetCategoryPriority(category, trackerKey) local cat = tostring(category or "utility") local trackerOrder = trackerKey and TRACKER_CATEGORY_PRIORITY[trackerKey] if trackerOrder and trackerOrder[cat] then return trackerOrder[cat] end local order = HMGT_SpellData and HMGT_SpellData.CategoryOrder if type(order) == "table" then for idx, key in ipairs(order) do if key == cat then return idx end end return #order + 10 end return DEFAULT_CATEGORY_PRIORITY[cat] or 99 end function HMGT:SortDisplayEntries(entries, trackerKey) if type(entries) ~= "table" then return end table.sort(entries, function(a, b) local aRemaining = tonumber(a and a.remaining) or 0 local bRemaining = tonumber(b and b.remaining) or 0 local aActive = aRemaining > 0 local bActive = bRemaining > 0 if aActive ~= bActive then return aActive end local aEntry = a and a.spellEntry local bEntry = b and b.spellEntry local aPriority = tonumber(aEntry and aEntry.priority) or GetCategoryPriority(aEntry and aEntry.category, trackerKey) local bPriority = tonumber(bEntry and bEntry.priority) or GetCategoryPriority(bEntry and bEntry.category, trackerKey) if aPriority ~= bPriority then return aPriority < bPriority end if aActive and aRemaining ~= bRemaining then return aRemaining < bRemaining end local aTotal = tonumber(a and a.total) or tonumber(aEntry and HMGT_SpellData.GetBaseCooldown and HMGT_SpellData.GetBaseCooldown(aEntry)) or tonumber(aEntry and aEntry.cooldown) or 0 local bTotal = tonumber(b and b.total) or tonumber(bEntry and HMGT_SpellData.GetBaseCooldown and HMGT_SpellData.GetBaseCooldown(bEntry)) or tonumber(bEntry and bEntry.cooldown) or 0 if (not aActive) and aTotal ~= bTotal then return aTotal > bTotal end if aRemaining ~= bRemaining then return aRemaining < bRemaining end local aName = tostring(a and a.playerName or "") local bName = tostring(b and b.playerName or "") if aName ~= bName then return aName < bName end local aSpell = tonumber(aEntry and aEntry.spellId) or 0 local bSpell = tonumber(bEntry and bEntry.spellId) or 0 return aSpell < bSpell end) end