-- Modules/Tracker/SingleFrameTrackerBase.lua -- Shared implementation for single-frame tracker modules. local ADDON_NAME = "HailMaryGuildTools" local HMGT = LibStub("AceAddon-3.0"):GetAddon(ADDON_NAME) if not HMGT then return end HMGT.SingleFrameTrackerBase = HMGT.SingleFrameTrackerBase or {} local Base = HMGT.SingleFrameTrackerBase local function GetDefaultGroupPlayers() local players = {} local ownName = HMGT:NormalizePlayerName(UnitName("player")) local ownClass = select(2, UnitClass("player")) table.insert(players, { name = ownName, class = ownClass, isOwn = true, unitId = "player" }) if IsInRaid() then for i = 1, GetNumGroupMembers() do local unitId = "raid" .. i local name = HMGT:NormalizePlayerName(UnitName(unitId)) local class = select(2, UnitClass(unitId)) if name and name ~= ownName then table.insert(players, { name = name, class = class, unitId = unitId }) end end elseif IsInGroup() then for i = 1, GetNumGroupMembers() - 1 do local unitId = "party" .. i local name = HMGT:NormalizePlayerName(UnitName(unitId)) local class = select(2, UnitClass(unitId)) if name and name ~= ownName then table.insert(players, { name = name, class = class, unitId = unitId }) end end end return players end local function ResolveConfigValue(configValue, self) if type(configValue) == "function" then return configValue(self) end return configValue end local function EntryNeedsVisualTicker(entry) if type(entry) ~= "table" then return false end local remaining = tonumber(entry.remaining) or 0 if remaining > 0 then return true end local maxCharges = tonumber(entry.maxCharges) or 0 local currentCharges = tonumber(entry.currentCharges) if maxCharges > 0 and currentCharges ~= nil and currentCharges < maxCharges then return true end return false end function Base:Create(config) local tracker = { frame = nil, updateTicker = nil, lastEntryCount = 0, } function tracker:GetSettings() return HMGT.db.profile[config.profileKey] end function tracker:GetDatabase() return ResolveConfigValue(config.database, self) or {} end function tracker:GetTitle() return ResolveConfigValue(config.title, self) or config.frameName end function tracker:GetDemoKey() return ResolveConfigValue(config.demoKey, self) or config.profileKey end function tracker:GetCooldownInfoOpts() return ResolveConfigValue(config.cooldownInfoOpts, self) end function tracker:GetGroupPlayers() local custom = ResolveConfigValue(config.groupPlayersProvider, self) if type(custom) == "table" then return custom end return GetDefaultGroupPlayers() end function tracker:EnsureUpdateTicker() if self.updateTicker then return end self.updateTicker = C_Timer.NewTicker(0.1, function() self:UpdateDisplay() end) end function tracker:StopUpdateTicker() if self.updateTicker then self.updateTicker:Cancel() self.updateTicker = nil end end function tracker:SetUpdateTickerEnabled(enabled) if enabled then self:EnsureUpdateTicker() else self:StopUpdateTicker() end end function tracker:Enable() local s = self:GetSettings() if not s.enabled and not s.demoMode and not s.testMode then return end if not self.frame then self.frame = HMGT.TrackerFrame:CreateTrackerFrame(config.frameName, s) HMGT.TrackerFrame:SetTitle(self.frame, self:GetTitle()) end if HMGT:IsVisibleForCurrentGroup(s) then self.frame:Show() else self.frame:Hide() end self:UpdateDisplay() end function tracker:Disable() self:StopUpdateTicker() if self.frame then self.frame:Hide() end end function tracker:UpdateDisplay() if not self.frame then self:StopUpdateTicker() return end local s = self:GetSettings() local database = self:GetDatabase() local cooldownInfoOpts = self:GetCooldownInfoOpts() if s.testMode then HMGT.TrackerFrame:SetLocked(self.frame, s.locked) local entries = HMGT:GetOwnTestEntries(database, s, cooldownInfoOpts) self.lastEntryCount = #entries HMGT.TrackerFrame:UpdateFrame(self.frame, entries) self.frame:Show() local shouldTick = false for _, entry in ipairs(entries) do if EntryNeedsVisualTicker(entry) then shouldTick = true break end end self:SetUpdateTickerEnabled(shouldTick) return end if s.demoMode then HMGT.TrackerFrame:SetLocked(self.frame, s.locked) local entries = HMGT:GetDemoEntries(self:GetDemoKey(), database, s) self.lastEntryCount = #entries HMGT.TrackerFrame:UpdateFrame(self.frame, entries) self.frame:Show() self:SetUpdateTickerEnabled(#entries > 0) return end if not s.enabled then self.lastEntryCount = 0 self.frame:Hide() self:StopUpdateTicker() return end if not HMGT:IsVisibleForCurrentGroup(s) then self.lastEntryCount = 0 self.frame:Hide() self:StopUpdateTicker() return end HMGT.TrackerFrame:SetLocked(self.frame, s.locked) local entries = self:CollectEntries() self.lastEntryCount = #entries if HMGT.SortDisplayEntries then HMGT:SortDisplayEntries(entries, config.profileKey) end HMGT.TrackerFrame:UpdateFrame(self.frame, entries) self.frame:Show() local shouldTick = false for _, entry in ipairs(entries) do if EntryNeedsVisualTicker(entry) then shouldTick = true break end end self:SetUpdateTickerEnabled(shouldTick) end function tracker:CollectEntries() local entries = {} local s = self:GetSettings() local database = self:GetDatabase() local cooldownInfoOpts = self:GetCooldownInfoOpts() local players = self:GetGroupPlayers() for _, playerInfo in ipairs(players) do repeat local name = playerInfo.name local pData = HMGT.playerData[name] local class = pData and pData.class or playerInfo.class local specIdx if playerInfo.isOwn then specIdx = GetSpecialization() if not specIdx or specIdx == 0 then break end else specIdx = pData and pData.specIndex or nil if not specIdx or tonumber(specIdx) <= 0 then break end end local talents = pData and pData.talents or {} if not class then break end local knownSpells = HMGT_SpellData.GetSpellsForSpec(class, specIdx, database) for _, spellEntry in ipairs(knownSpells) do if s.enabledSpells[spellEntry.spellId] ~= false then local remaining, total, curCharges, maxCharges = HMGT:GetCooldownInfo(name, spellEntry.spellId, cooldownInfoOpts) local isAvailabilitySpell = HMGT.IsAvailabilitySpell and HMGT:IsAvailabilitySpell(spellEntry) local effectiveCd = HMGT_SpellData.GetEffectiveCooldown(spellEntry, talents) local include = HMGT:ShouldDisplayEntry(s, remaining, curCharges, maxCharges, spellEntry) local spellKnown = HMGT:IsTrackedSpellKnownForPlayer(name, spellEntry.spellId) local hasPartialCharges = (tonumber(maxCharges) or 0) > 0 and (tonumber(curCharges) or tonumber(maxCharges) or 0) < (tonumber(maxCharges) or 0) local hasActiveCd = ((remaining or 0) > 0) or hasPartialCharges if not spellKnown and not hasActiveCd then include = false end if isAvailabilitySpell and not spellKnown then include = false end if not playerInfo.isOwn then if isAvailabilitySpell and not HMGT:HasAvailabilityState(name, spellEntry.spellId) then include = false end end if include then entries[#entries + 1] = { playerName = name, class = class, spellEntry = spellEntry, remaining = remaining, total = total > 0 and total or effectiveCd, currentCharges = curCharges, maxCharges = maxCharges, } end end end until true end return entries end return tracker end function Base:CreateModule(moduleName, config, ...) if type(moduleName) ~= "string" or moduleName == "" then return self:Create(config) end local module = HMGT:NewModule(moduleName, ...) local tracker = self:Create(config) for key, value in pairs(tracker) do module[key] = value end HMGT[moduleName] = module return module end