local ADDON_NAME = "HailMaryGuildTools" local HMGT = LibStub("AceAddon-3.0"):GetAddon(ADDON_NAME) if not HMGT then return end HMGT.TrackerBridge = HMGT.TrackerBridge or {} function HMGT:RegisterExternalAddonSource(sourceName) local source = tostring(sourceName or "") if source == "" then return false end self.externalAddonSources = self.externalAddonSources or {} self.externalAddonSources[source] = true return true end function HMGT:GetCanonicalExternalSpellEntry(spellId) local sid = tonumber(spellId) if not sid or sid <= 0 or not HMGT_SpellData then return nil, sid end local spellEntry = HMGT_SpellData.InterruptLookup and HMGT_SpellData.InterruptLookup[sid] or HMGT_SpellData.CooldownLookup and HMGT_SpellData.CooldownLookup[sid] if not spellEntry then return nil, sid end return spellEntry, tonumber(spellEntry.spellId) or sid end function HMGT:InferClassFromSpellEntry(spellEntry) if type(spellEntry) ~= "table" or type(spellEntry.classes) ~= "table" then return nil end local foundClass for key, value in pairs(spellEntry.classes) do local classToken = type(value) == "string" and value or key if foundClass and foundClass ~= classToken then return nil end foundClass = classToken end return foundClass end function HMGT:ApplyExternalKnownSpell(sourceName, playerName, spellId, class, cooldown) local source = tostring(sourceName or "External") local normalizedName = self:NormalizePlayerName(playerName) local sid = tonumber(spellId) if not normalizedName or normalizedName == "" or not sid or sid <= 0 then return false, "invalid_args" end if not self:IsPlayerInCurrentGroup(normalizedName) then return false, "not_in_group" end local spellEntry, canonicalSid = self:GetCanonicalExternalSpellEntry(sid) if not spellEntry or not canonicalSid or canonicalSid <= 0 then return false, "unknown_spell" end sid = canonicalSid self:RegisterExternalAddonSource(source) local previous = self.playerData[normalizedName] or {} local knownSpells = previous.knownSpells if type(knownSpells) ~= "table" then knownSpells = {} end knownSpells[sid] = true local classToken = class or previous.class or self:InferClassFromSpellEntry(spellEntry) self.playerData[normalizedName] = { class = classToken, specIndex = previous.specIndex, talentHash = previous.talentHash, talents = previous.talents or {}, knownSpells = knownSpells, externalSource = source, } self:SetPlayerBridgeStatus(normalizedName, source) self:DebugScoped("verbose", "TrackerBridge", "Bridge known spell source=%s player=%s spellId=%s", tostring(source), tostring(normalizedName), tostring(sid)) if tonumber(cooldown) and tonumber(cooldown) > 0 then spellEntry._hmgtExternalBaseCd = tonumber(cooldown) end self:TriggerTrackerUpdate("trackers") return true end function HMGT:ApplyExternalSpecInfo(sourceName, playerName, class, specId, talentHash) local source = tostring(sourceName or "External") local normalizedName = self:NormalizePlayerName(playerName) local spec = tonumber(specId) local classToken = class and tostring(class) or self:GetClassTokenForSpecId(spec) if not normalizedName or normalizedName == "" or not classToken or classToken == "" or not spec or spec <= 0 then return false, "invalid_args" end if not self:IsPlayerInCurrentGroup(normalizedName) then return false, "not_in_group" end self:RegisterExternalAddonSource(source) local previous = self.playerData[normalizedName] or {} local knownSpells = previous.knownSpells if type(knownSpells) ~= "table" then knownSpells = {} end if HMGT_SpellData and type(HMGT_SpellData.GetSpellsForSpec) == "function" then for _, datasetName in ipairs({ "Interrupts", "RaidCooldowns", "GroupCooldowns" }) do local dataset = HMGT_SpellData[datasetName] for _, spellEntry in ipairs(HMGT_SpellData.GetSpellsForSpec(classToken, spec, dataset)) do local sid = tonumber(spellEntry and spellEntry.spellId) if sid and sid > 0 then knownSpells[sid] = true end end end end self.playerData[normalizedName] = { class = classToken, specIndex = spec, talentHash = talentHash or previous.talentHash, talents = self:ParseTalentHash(talentHash or previous.talentHash), knownSpells = knownSpells, externalSource = source, } self:SetPlayerBridgeStatus(normalizedName, source) self:DebugScoped("info", "TrackerBridge", "Bridge spec sync source=%s player=%s class=%s spec=%s", tostring(source), tostring(normalizedName), tostring(classToken), tostring(spec)) self:PruneAvailabilityStates(normalizedName, knownSpells) self:TriggerTrackerUpdate("trackers") return true end function HMGT:ApplyExternalCooldown(sourceName, playerName, spellId, cooldown) local source = tostring(sourceName or "External") local normalizedName = self:NormalizePlayerName(playerName) local sid = tonumber(spellId) local cd = tonumber(cooldown) if not normalizedName or normalizedName == "" or not sid or sid <= 0 or not cd or cd <= 0 then return false, "invalid_args" end if not self:IsPlayerInCurrentGroup(normalizedName) then return false, "not_in_group" end local spellEntry, canonicalSid = self:GetCanonicalExternalSpellEntry(sid) if not spellEntry or not canonicalSid or canonicalSid <= 0 then return false, "unknown_spell" end sid = canonicalSid self:RegisterExternalAddonSource(source) self:ApplyExternalKnownSpell(source, normalizedName, sid, nil, cd) self:DebugScoped("info", "TrackerBridge", "Bridge cooldown source=%s player=%s spellId=%s cooldown=%.1f", tostring(source), tostring(normalizedName), tostring(sid), cd) self:HandleRemoteSpellCast(normalizedName, sid, GetServerTime(), nil, nil, nil, cd) return true end function HMGT:IsPlayerInCurrentGroup(playerName) local target = self:NormalizePlayerName(playerName) if not target then return false end local own = self:NormalizePlayerName(UnitName("player")) if target == own then return true end if IsInRaid() then for i = 1, GetNumGroupMembers() do local n = self:NormalizePlayerName(UnitName("raid" .. i)) if n == target then return true end end return false end if IsInGroup() then for i = 1, GetNumSubgroupMembers() do local n = self:NormalizePlayerName(UnitName("party" .. i)) if n == target then return true end end end return false end