if not WeakAuras.IsLibsOK() then return end if not WeakAuras.IsCataOrRetail() then return end ---@type string local AddonName = ... ---@class Private local Private = select(2, ...) --- @class LibSpecialization --- @field Register fun(self: LibSpecialization, name: string, callback: function) --- @field MySpecialization fun(): number, string, string local LibSpec = LibStub("LibSpecialization") --- @alias specData {[1]: number, [2]: string, [3]: string, [4]: string} --- @type table local nameToSpecMap = {} local nameToTalents = {} --- @type table local nameToUnitMap = { [GetUnitName("player", true)] = "player" } --- @type function[] local subscribers = {} --- @class LibSpecWrapper --- @field Register fun(callback: fun(unit: string)) --- @field SpecForUnit fun(unit: string): number? --- @field SpecRolePositionForUnit fun(unit: string): number?, string?, string? --- @field CheckTalentForUnit fun(unit: string, talentId: number): boolean? Private.LibSpecWrapper = { Register = function(callback) end, SpecForUnit = function(unit) end, SpecRolePositionForUnit = function(unit) end, CheckTalentForUnit = function(unit) end, } if LibSpec then local frame = CreateFrame("Frame") frame:RegisterEvent("PLAYER_LOGIN") frame:RegisterEvent("GROUP_ROSTER_UPDATE") frame:SetScript("OnEvent", function() --- @type string local ownName = GetUnitName("player", true) nameToUnitMap = {} nameToUnitMap[ownName] = "player" if IsInRaid() then local max = GetNumGroupMembers() for i = 1, max do local name = GetUnitName(WeakAuras.raidUnits[i], true) nameToUnitMap[name] = WeakAuras.raidUnits[i] end else local max = GetNumSubgroupMembers() for i = 1, max do local name = GetUnitName(WeakAuras.partyUnits[i], true) nameToUnitMap[name] = WeakAuras.partyUnits[i] end end for name in pairs(nameToSpecMap) do if not nameToUnitMap[name] then nameToSpecMap[name] = nil end end end) --- LibSpecialization callback ---@param specId number ---@param role string ---@param position string ---@param sender string ---@param talentString string local function LibSpecCallback(specId, role, position, sender, talentString) if nameToSpecMap[sender] and nameToSpecMap[sender][1] == specId and nameToSpecMap[sender][2] == role and nameToSpecMap[sender][3] == position and nameToSpecMap[sender][4] == talentString then return end if not nameToUnitMap[sender] then return end nameToSpecMap[sender] = {specId, role, position, talentString} nameToTalents[sender] = nil for _, f in ipairs(subscribers) do f(nameToUnitMap[sender]) end end LibSpec:Register("WeakAuras", LibSpecCallback) function Private.LibSpecWrapper.Register(f) tinsert(subscribers, f) end function Private.LibSpecWrapper.SpecForUnit(unit) if UnitIsUnit(unit, "player") then return (LibSpec:MySpecialization()) end if nameToSpecMap[GetUnitName(unit, true)] then return nameToSpecMap[GetUnitName(unit, true)][1] end end function Private.LibSpecWrapper.SpecRolePositionForUnit(unit) if UnitIsUnit(unit, "player") then return LibSpec:MySpecialization() end local data = nameToSpecMap[GetUnitName(unit, true)] if data then return unpack(data) else return nil end end local function ReadLoadoutHeader(importStream) local bitWidthHeaderVersion = 8 local bitWidthSpecID = 16 local headerBitWidth = bitWidthHeaderVersion + bitWidthSpecID + 128; local importStreamTotalBits = importStream:GetNumberOfBits(); if( importStreamTotalBits < headerBitWidth) then return false, 0, 0, 0; end local serializationVersion = importStream:ExtractValue(bitWidthHeaderVersion); local specID = importStream:ExtractValue(bitWidthSpecID); -- treeHash is a 128bit hash, passed as an array of 16, 8-bit values local treeHash = {}; for i=1,16,1 do treeHash[i] = importStream:ExtractValue(8); end return true, serializationVersion, specID, treeHash; end local validSerializationVersions = { [1] = true, [2] = true } function Private.LibSpecWrapper.CheckTalentForUnit(unit, talentId) if UnitIsUnit(unit, "player") then return select(4, WeakAuras.GetTalentById(talentId)) end local unitName = GetUnitName(unit, true) if not nameToTalents[unitName] then -- Parse Talent String once and store which talents are selected if not nameToSpecMap[unitName] then return nil end local talentString = nameToSpecMap[unitName][4] if not talentString then return nil end local importStream = CreateAndInitFromMixin(ImportDataStreamMixin, talentString) local headerValid, serializationVersion, specID, treeHash = ReadLoadoutHeader(importStream); local currentSerializationVersion = C_Traits.GetLoadoutSerializationVersion(); if(not headerValid) then return nil end if(serializationVersion ~= currentSerializationVersion or not validSerializationVersions[serializationVersion]) then return nil end local treeID = C_ClassTalents.GetTraitTreeForSpec(specID) local results = {}; local bitWidthRanksPurchased = 6 local _, _, talentsData = Private.GetTalentData(specID) local treeNodes = C_Traits.GetTreeNodes(treeID); for _, nodeId in ipairs(treeNodes) do local nodeSelectedValue = importStream:ExtractValue(1) local isNodeSelected = nodeSelectedValue == 1 local isPartiallyRanked = false local partialRanksPurchased = 0 local isChoiceNode = false local choiceNodeSelection = 1 if(isNodeSelected) then if serializationVersion == 2 then local nodePurchasedValue = importStream:ExtractValue(1) local isNodePurchased = nodePurchasedValue == 1 if(isNodePurchased) then local isPartiallyRankedValue = importStream:ExtractValue(1) isPartiallyRanked = isPartiallyRankedValue == 1 if(isPartiallyRanked) then partialRanksPurchased = importStream:ExtractValue(bitWidthRanksPurchased) end local isChoiceNodeValue = importStream:ExtractValue(1) isChoiceNode = isChoiceNodeValue == 1 if(isChoiceNode) then choiceNodeSelection = importStream:ExtractValue(2) + 1 end end else local isPartiallyRankedValue = importStream:ExtractValue(1) isPartiallyRanked = isPartiallyRankedValue == 1 if(isPartiallyRanked) then partialRanksPurchased = importStream:ExtractValue(bitWidthRanksPurchased) end local isChoiceNodeValue = importStream:ExtractValue(1) isChoiceNode = isChoiceNodeValue == 1 if(isChoiceNode) then choiceNodeSelection = importStream:ExtractValue(2) + 1 end end end local talentData = talentsData and talentsData[nodeId] and talentsData[nodeId][choiceNodeSelection] if talentData then if isPartiallyRanked then results[talentData[1]] = partialRanksPurchased else results[talentData[1]] = nodeSelectedValue == 1 and talentData[5] or 0 end end if isChoiceNode then local unselectedChoiceNodeIdx = choiceNodeSelection == 1 and 2 or 1 local unselectedTalentData = talentsData and talentsData[nodeId] and talentsData[nodeId][unselectedChoiceNodeIdx] if unselectedTalentData then results[unselectedTalentData[1]] = 0 end end end nameToTalents[unitName] = results end if nameToTalents[unitName] then return nameToTalents[unitName][talentId] end end else -- non retail function Private.LibSpecWrapper.Register(f) end function Private.LibSpecWrapper.SpecForUnit(unit) return nil end function Private.LibSpecWrapper.SpecRolePositionForUnit(unit) return nil end function Private.LibSpecWrapper.CheckTalentForUnit(unit) return nil end end -- Export for GenericTrigger WeakAuras.SpecForUnit = Private.LibSpecWrapper.SpecForUnit WeakAuras.SpecRolePositionForUnit = Private.LibSpecWrapper.SpecRolePositionForUnit WeakAuras.CheckTalentForUnit = Private.LibSpecWrapper.CheckTalentForUnit