--[=[ This file has the functions to get player information Dumping them here, make the code of the main file smaller --]=] if (not LIB_OPEN_RAID_CAN_LOAD) then return end local openRaidLib = LibStub:GetLibrary("LibOpenRaid-1.0") local CONST_TALENT_VERSION_CLASSIC = 1 local CONST_TALENT_VERSION_LEGION = 4 local CONST_TALENT_VERSION_DRAGONFLIGHT = 5 local CONST_BTALENT_VERSION_COVENANTS = 9 local CONST_SPELLBOOK_CLASSSPELLS_TABID = 2 local CONST_SPELLBOOK_GENERAL_TABID = 1 local isTimewalkWoW = function() local _, _, _, buildInfo = GetBuildInfo() if (buildInfo < 40000) then return true end end local IsDragonflight = function() return select(4, GetBuildInfo()) >= 100000 end local IsShadowlands = function() local versionString, revision, launchDate, gameVersion = GetBuildInfo() if (gameVersion >= 90000 and gameVersion < 100000) then return true end end --information about the player character to send, each expansion has its own system and data can be different --it's always a number function openRaidLib.UnitInfoManager.GetPlayerInfo1() if (IsShadowlands()) then --return the renown level within the player covenant local renown = C_CovenantSanctumUI.GetRenownLevel() or 1 return renown end return 0 end --information about the player character to send, each expansion has its own system and data can be different --it's always a number function openRaidLib.UnitInfoManager.GetPlayerInfo2() if (IsShadowlands()) then --return which covenant the player picked local covenant = C_Covenants.GetActiveCovenantID() or 0 return covenant end return 0 end --default player class-spec talent system function openRaidLib.GetTalentVersion() local _, _, _, buildInfo = GetBuildInfo() if (buildInfo >= 1 and buildInfo <= 40000) then --vanilla tbc wotlk cataclysm return CONST_TALENT_VERSION_CLASSIC end if (buildInfo >= 70000 and buildInfo <= 100000) then --legion bfa shadowlands return CONST_TALENT_VERSION_LEGION end if (buildInfo >= 100000 and buildInfo <= 200000) then --dragonflight return CONST_TALENT_VERSION_DRAGONFLIGHT end end --secondary talent tree, can be a legendary weapon talent tree, covenant talent tree, etc... function openRaidLib.GetBorrowedTalentVersion() if (IsShadowlands()) then return CONST_BTALENT_VERSION_COVENANTS end end local getDragonflightTalentsAsIndexTable = function() local allTalents = {} local configId = C_ClassTalents.GetActiveConfigID() if (not configId) then return allTalents end local configInfo = C_Traits.GetConfigInfo(configId) for treeIndex, treeId in ipairs(configInfo.treeIDs) do local treeNodes = C_Traits.GetTreeNodes(treeId) for nodeIdIndex, treeNodeID in ipairs(treeNodes) do local traitNodeInfo = C_Traits.GetNodeInfo(configId, treeNodeID) if (traitNodeInfo) then local activeEntry = traitNodeInfo.activeEntry if (activeEntry) then local entryId = activeEntry.entryID local rank = activeEntry.rank if (rank > 0) then --get the entry info local traitEntryInfo = C_Traits.GetEntryInfo(configId, entryId) local definitionId = traitEntryInfo.definitionID --definition info local traitDefinitionInfo = C_Traits.GetDefinitionInfo(definitionId) local spellId = traitDefinitionInfo.overriddenSpellID or traitDefinitionInfo.spellID local spellName, _, spellTexture = GetSpellInfo(spellId) if (spellName) then allTalents[#allTalents+1] = spellId end end end end end end return allTalents end --creates two tables, one with indexed talents and another with pairs values ([talentId] = true) function openRaidLib.UnitInfoManager.GetPlayerTalentsAsPairsTable() local talentsPairs = {} local talentVersion = openRaidLib.GetTalentVersion() if (talentVersion == CONST_TALENT_VERSION_DRAGONFLIGHT) then local allTalents = getDragonflightTalentsAsIndexTable() for i = 1, #allTalents do local spellId = allTalents[i] talentsPairs[spellId] = true end elseif (talentVersion == CONST_TALENT_VERSION_LEGION) then for i = 1, 7 do for o = 1, 3 do local talentId, _, _, selected = GetTalentInfo(i, o, 1) if (selected) then talentsPairs[talentId] = true break end end end end return talentsPairs end function openRaidLib.UnitInfoManager.GetPlayerTalents() local talents = {} local talentVersion = openRaidLib.GetTalentVersion() if (talentVersion == CONST_TALENT_VERSION_DRAGONFLIGHT) then talents = getDragonflightTalentsAsIndexTable() elseif (talentVersion == CONST_TALENT_VERSION_LEGION) then talents = {0, 0, 0, 0, 0, 0, 0} for talentTier = 1, 7 do for talentColumn = 1, 3 do local talentId, name, texture, selected, available = GetTalentInfo(talentTier, talentColumn, 1) if (selected) then talents[talentTier] = talentId break end end end end return talents end function openRaidLib.UnitInfoManager.GetPlayerPvPTalents() if (IsDragonflight()) then return {} end local talentsPvP = {0, 0, 0} local talentList = C_SpecializationInfo.GetAllSelectedPvpTalentIDs() for talentIndex, talentId in ipairs(talentList) do local doesExists = GetPvpTalentInfoByID(talentId) if (doesExists) then talentsPvP[talentIndex] = talentId end end return talentsPvP end --return the current specId of the player function openRaidLib.GetPlayerSpecId() if (isTimewalkWoW()) then return 0 end local spec = GetSpecialization() if (spec) then local specId = GetSpecializationInfo(spec) if (specId and specId > 0) then return specId end end end --borrowed talent tree from shadowlands function openRaidLib.UnitInfoManager.GetPlayerConduits() local conduits = {} local soulbindID = C_Soulbinds.GetActiveSoulbindID() if (soulbindID ~= 0) then local soulbindData = C_Soulbinds.GetSoulbindData(soulbindID) if (soulbindData ~= 0) then local tree = soulbindData.tree local nodes = tree.nodes table.sort(nodes, function(t1, t2) return t1.row < t2.row end) local C_Soulbinds_GetConduitCollectionData = C_Soulbinds.GetConduitCollectionData for nodeId, nodeInfo in ipairs(nodes) do --check if the node is a conduit placed by the player if (nodeInfo.state == Enum.SoulbindNodeState.Selected) then local conduitId = nodeInfo.conduitID local conduitRank = nodeInfo.conduitRank if (conduitId and conduitRank) then --have spell id when it's a default conduit from the game local spellId = nodeInfo.spellID --have conduit id when is a conduid placed by the player local conduitId = nodeInfo.conduitID if (spellId == 0) then --is player conduit spellId = C_Soulbinds.GetConduitSpellID(nodeInfo.conduitID, nodeInfo.conduitRank) conduits[#conduits+1] = spellId local collectionData = C_Soulbinds_GetConduitCollectionData(conduitId) conduits[#conduits+1] = collectionData and collectionData.conduitItemLevel or 0 else --is default conduit conduits[#conduits+1] = spellId conduits[#conduits+1] = 0 end end end end end end return conduits end function openRaidLib.UnitInfoManager.GetPlayerBorrowedTalents() local borrowedTalentVersion = openRaidLib.GetBorrowedTalentVersion() if (borrowedTalentVersion == CONST_BTALENT_VERSION_COVENANTS) then return openRaidLib.UnitInfoManager.GetPlayerConduits() end return {} end function openRaidLib.GearManager.GetPlayerItemLevel() if (_G.GetAverageItemLevel) then local _, itemLevel = GetAverageItemLevel() itemLevel = floor(itemLevel) return itemLevel else return 0 end end --return an integer between zero and one hundret indicating the player gear durability function openRaidLib.GearManager.GetPlayerGearDurability() local durabilityTotalPercent, totalItems = 0, 0 for i = INVSLOT_FIRST_EQUIPPED, INVSLOT_LAST_EQUIPPED do local durability, maxDurability = GetInventoryItemDurability(i) if (durability and maxDurability) then local itemDurability = durability / maxDurability * 100 durabilityTotalPercent = durabilityTotalPercent + itemDurability totalItems = totalItems + 1 end end if (totalItems == 0) then return 100 end return floor(durabilityTotalPercent / totalItems) end function openRaidLib.GearManager.GetPlayerWeaponEnchant() local weaponEnchant = 0 local _, _, _, mainHandEnchantId, _, _, _, offHandEnchantId = GetWeaponEnchantInfo() if (LIB_OPEN_RAID_WEAPON_ENCHANT_IDS[mainHandEnchantId]) then weaponEnchant = 1 elseif(LIB_OPEN_RAID_WEAPON_ENCHANT_IDS[offHandEnchantId]) then weaponEnchant = 1 end return weaponEnchant end function openRaidLib.GearManager.GetPlayerGemsAndEnchantInfo() --hold equipmentSlotId of equipment with a gem socket but it's empty local slotsWithoutGems = {} --hold equipmentSlotId of equipments without an enchant local slotsWithoutEnchant = {} for equipmentSlotId = 1, 17 do local itemLink = GetInventoryItemLink("player", equipmentSlotId) if (itemLink) then --get the information from the item local _, itemId, enchantId, gemId1, gemId2, gemId3, gemId4, suffixId, uniqueId, levelOfTheItem, specId, upgradeInfo, instanceDifficultyId, numBonusIds, restLink = strsplit(":", itemLink) local gemsIds = {gemId1, gemId2, gemId3, gemId4} --enchant --check if the slot can receive enchat and if the equipment has an enchant local enchantAttribute = LIB_OPEN_RAID_ENCHANT_SLOTS[equipmentSlotId] if (enchantAttribute) then --this slot can receive an enchat --check if this slot is relevant for the class, some slots can have enchants only for Agility which won't matter for Priests as an example --if the value is an integer it points to an attribute (int, dex, str), otherwise it's true (boolean) local slotIsRelevant = true if (type(enchantAttribute) == "number") then if (specMainAttribute ~= enchantAttribute) then slotIsRelevant = false end end if (slotIsRelevant) then --does the slot has any enchant? if (not enchantId or enchantId == "0" or enchantId == "") then slotsWithoutEnchant[#slotsWithoutEnchant+1] = equipmentSlotId else --convert to integer local enchantIdInt = tonumber(enchantId) if (enchantIdInt) then --does the enchant is relevent for the character? if (not LIB_OPEN_RAID_ENCHANT_IDS[enchantIdInt]) then slotsWithoutEnchant[#slotsWithoutEnchant+1] = equipmentSlotId end else --the enchat has an invalid id slotsWithoutEnchant[#slotsWithoutEnchant+1] = equipmentSlotId end end end end --gems local itemStatsTable = {} --fill the table above with information about the item GetItemStats(itemLink, itemStatsTable) --check if the item has a socket if (itemStatsTable.EMPTY_SOCKET_PRISMATIC) then --check if the socket is empty for i = 1, itemStatsTable.EMPTY_SOCKET_PRISMATIC do local gemId = tonumber(gemsIds[i]) if (not gemId or gemId == 0) then slotsWithoutGems[#slotsWithoutGems+1] = equipmentSlotId --check if the gem is not a valid gem (deprecated gem) elseif (not LIB_OPEN_RAID_GEM_IDS[gemId]) then slotsWithoutGems[#slotsWithoutGems+1] = equipmentSlotId end end end end end return slotsWithoutGems, slotsWithoutEnchant end local playerHasPetOfNpcId = function(npcId) if (UnitExists("pet") and UnitHealth("pet") >= 1) then local guid = UnitGUID("pet") if (guid) then local split = {strsplit("-", guid)} local playerPetNpcId = tonumber(split[6]) if (playerPetNpcId) then if (npcId == playerPetNpcId) then return true end end end end end local addCooldownToTable = function(cooldowns, cooldownsHash, cooldownSpellId, timeNow) local timeLeft, charges, startTimeOffset, duration, auraDuration = openRaidLib.CooldownManager.GetPlayerCooldownStatus(cooldownSpellId) cooldowns[#cooldowns+1] = cooldownSpellId cooldowns[#cooldowns+1] = timeLeft cooldowns[#cooldowns+1] = charges cooldowns[#cooldowns+1] = startTimeOffset cooldowns[#cooldowns+1] = duration cooldowns[#cooldowns+1] = auraDuration cooldownsHash[cooldownSpellId] = {timeLeft, charges, startTimeOffset, duration, timeNow, auraDuration} end local canAddCooldown = function(cooldownInfo) local petNpcIdNeeded = cooldownInfo.pet if (petNpcIdNeeded) then if (not playerHasPetOfNpcId(petNpcIdNeeded)) then return false end end return true end local getSpellListAsHashTableFromSpellBook = function() local completeListOfSpells = {} --this line might not be compatible with classic local specId, specName, _, specIconTexture = GetSpecializationInfo(GetSpecialization()) --local classNameLoc, className, classId = UnitClass("player") --not in use local locPlayerRace, playerRace, playerRaceId = UnitRace("player") --get racials from the general tab local tabName, tabTexture, offset, numSpells, isGuild, offspecId = GetSpellTabInfo(CONST_SPELLBOOK_GENERAL_TABID) offset = offset + 1 local tabEnd = offset + numSpells for entryOffset = offset, tabEnd - 1 do local spellType, spellId = GetSpellBookItemInfo(entryOffset, "player") if (spellId and LIB_OPEN_RAID_COOLDOWNS_INFO[spellId] and LIB_OPEN_RAID_COOLDOWNS_INFO[spellId].raceid == playerRaceId) then spellId = C_SpellBook.GetOverrideSpell(spellId) local spellName = GetSpellInfo(spellId) local bIsPassive = IsPassiveSpell(spellId, "player") if (spellName and not bIsPassive) then completeListOfSpells[spellId] = true end end end --get spells from the Spec spellbook for i = 1, GetNumSpellTabs() do local tabName, tabTexture, offset, numSpells, isGuild, offspecId = GetSpellTabInfo(i) if (tabTexture == specIconTexture) then offset = offset + 1 local tabEnd = offset + numSpells for entryOffset = offset, tabEnd - 1 do local spellType, spellId = GetSpellBookItemInfo(entryOffset, "player") if (spellId) then if (spellType == "SPELL") then spellId = C_SpellBook.GetOverrideSpell(spellId) local spellName = GetSpellInfo(spellId) local bIsPassive = IsPassiveSpell(spellId, "player") if (spellName and not bIsPassive) then completeListOfSpells[spellId] = true end end end end end end --get class shared spells from the spell book local tabName, tabTexture, offset, numSpells, isGuild, offspecId = GetSpellTabInfo(CONST_SPELLBOOK_CLASSSPELLS_TABID) offset = offset + 1 local tabEnd = offset + numSpells for entryOffset = offset, tabEnd - 1 do local spellType, spellId = GetSpellBookItemInfo(entryOffset, "player") if (spellId) then if (spellType == "SPELL") then spellId = C_SpellBook.GetOverrideSpell(spellId) local spellName = GetSpellInfo(spellId) local bIsPassive = IsPassiveSpell(spellId, "player") if (spellName and not bIsPassive) then completeListOfSpells[spellId] = true end end end end local getNumPetSpells = function() --'HasPetSpells' contradicts the name and return the amount of pet spells available instead of a boolean return HasPetSpells() end --get pet spells from the pet spellbook local numPetSpells = getNumPetSpells() if (numPetSpells) then for i = 1, numPetSpells do local spellName, _, unmaskedSpellId = GetSpellBookItemName(i, "pet") if (unmaskedSpellId) then unmaskedSpellId = C_SpellBook.GetOverrideSpell(unmaskedSpellId) local bIsPassive = IsPassiveSpell(unmaskedSpellId, "pet") if (spellName and not bIsPassive) then completeListOfSpells[unmaskedSpellId] = true end end end end return completeListOfSpells end local updateCooldownAvailableList = function() table.wipe(LIB_OPEN_RAID_PLAYERCOOLDOWNS) local _, playerClass = UnitClass("player") local locPlayerRace, playerRace, playerRaceId = UnitRace("player") local spellBookSpellList = getSpellListAsHashTableFromSpellBook() --build a list of all spells assigned as cooldowns for the player class for spellID, spellData in pairs(LIB_OPEN_RAID_COOLDOWNS_INFO) do if (spellData.class == playerClass or spellData.raceid == playerRaceId) then --need to implement here to get the racial as racial cooldowns does not carry a class if (spellBookSpellList[spellID]) then LIB_OPEN_RAID_PLAYERCOOLDOWNS[spellID] = spellData end end end end --build a list with the local player cooldowns --called only from SendAllPlayerCooldowns() function openRaidLib.CooldownManager.GetPlayerCooldownList() --update the list of cooldowns the player has available if (IsDragonflight()) then --this fill the global LIB_OPEN_RAID_PLAYERCOOLDOWNS updateCooldownAvailableList() --get the player specId local specId = openRaidLib.GetPlayerSpecId() if (specId) then --get the cooldowns for the specialization local playerCooldowns = LIB_OPEN_RAID_PLAYERCOOLDOWNS if (not playerCooldowns) then openRaidLib.DiagnosticError("CooldownManager|GetPlayerCooldownList|LIB_OPEN_RAID_PLAYERCOOLDOWNS is nil") return {}, {} end local cooldowns = {} --table to ship on comm local cooldownsHash = {} --table with [spellId] = cooldownInfo local talentsHash = openRaidLib.UnitInfoManager.GetPlayerTalentsAsPairsTable() local timeNow = GetTime() for cooldownSpellId, cooldownInfo in pairs(playerCooldowns) do --does this cooldown is based on a talent? local talentId = cooldownInfo.talent --check if the player has a talent which makes this cooldown unavailable local ignoredByTalentId = cooldownInfo.ignoredIfTalent local bIsIgnoredByTalentId = false if (ignoredByTalentId) then if (talentsHash[ignoredByTalentId]) then bIsIgnoredByTalentId = true end end if (not bIsIgnoredByTalentId) then if (talentId) then --check if the player has the talent selected if (talentsHash[talentId]) then if (canAddCooldown(cooldownInfo)) then addCooldownToTable(cooldowns, cooldownsHash, cooldownSpellId, timeNow) end end else if (canAddCooldown(cooldownInfo)) then addCooldownToTable(cooldowns, cooldownsHash, cooldownSpellId, timeNow) end end end end return cooldowns, cooldownsHash else return {}, {} end end return {} end --aura frame handles only UNIT_AURA events to grab the duration of the buff placed by the aura local bIsNewUnitAuraAvailable = C_UnitAuras and C_UnitAuras.GetAuraDataBySlot and true local auraSpellID local auraDurationTime local handleBuffAura = function(aura) local auraInfo = C_UnitAuras.GetAuraDataByAuraInstanceID("player", aura.auraInstanceID) if (auraInfo) then local spellId = auraInfo.spellId if (auraSpellID == spellId) then auraSpellID = nil auraDurationTime = auraInfo.duration return true end end end local getAuraDuration = function(spellId) --some auras does not have the same spellId of the cast as the spell for its aura duration --in these cases, it's necessary to declare the buff spellId which tells the duration of the effect by adding 'durationSpellId = spellId' within the cooldown data if (not LIB_OPEN_RAID_PLAYERCOOLDOWNS[spellId]) then --local spellname = GetSpellInfo(spellId) --print("spell not found:", spellname) return 0 end local customBuffDuration = LIB_OPEN_RAID_PLAYERCOOLDOWNS[spellId].durationSpellId --spellId = customBuffDuration or spellId --can't replace the spellId by customBuffDurationSpellId has it wount be found in LIB_OPEN_RAID_PLAYERCOOLDOWNS if (bIsNewUnitAuraAvailable) then local bBatchCount = false local bUsePackedAura = true auraSpellID = customBuffDuration or spellId auraDurationTime = 0 --reset duration AuraUtil.ForEachAura("player", "HELPFUL", bBatchCount, handleBuffAura, bUsePackedAura) --check auras to find a buff for the spellId if (auraDurationTime == 0) then --if the buff wasn't found, attempt to get the duration from the file return LIB_OPEN_RAID_PLAYERCOOLDOWNS[spellId].duration or 0 end return auraDurationTime else --this is classic end end function openRaidLib.CooldownManager.GetSpellBuffDuration(spellId) return getAuraDuration(spellId) end --check if a player cooldown is ready or if is in cooldown --@spellId: the spellId to check for cooldown --return timeLeft, charges, startTimeOffset, duration, buffDuration function openRaidLib.CooldownManager.GetPlayerCooldownStatus(spellId) --check if is a charge spell local cooldownInfo = LIB_OPEN_RAID_COOLDOWNS_INFO[spellId] if (cooldownInfo) then local buffDuration = getAuraDuration(spellId) local chargesAvailable, chargesTotal, start, duration = GetSpellCharges(spellId) if chargesAvailable then if (chargesAvailable == chargesTotal) then return 0, chargesTotal, 0, 0, 0 --all charges are ready to use else --return the time to the next charge local timeLeft = start + duration - GetTime() local startTimeOffset = start - GetTime() return ceil(timeLeft), chargesAvailable, startTimeOffset, duration, buffDuration --time left, charges, startTime, duration, buffDuration end else local start, duration = GetSpellCooldown(spellId) if (start == 0) then --cooldown is ready return 0, 1, 0, 0, 0 --time left, charges, startTime else local timeLeft = start + duration - GetTime() local startTimeOffset = start - GetTime() return ceil(timeLeft), 0, ceil(startTimeOffset), duration, buffDuration --time left, charges, startTime, duration, buffDuration end end else return openRaidLib.DiagnosticError("CooldownManager|GetPlayerCooldownStatus()|cooldownInfo not found|", spellId) end end --which is the main attribute of each spec --1 Intellect --2 Agility --3 Strenth openRaidLib.specAttribute = { ["DEMONHUNTER"] = { [577] = 2, [581] = 2, }, ["DEATHKNIGHT"] = { [250] = 3, [251] = 3, [252] = 3, }, ["WARRIOR"] = { [71] = 3, [72] = 3, [73] = 3, }, ["MAGE"] = { [62] = 1, [63] = 1, [64] = 1, }, ["ROGUE"] = { [259] = 2, [260] = 2, [261] = 2, }, ["DRUID"] = { [102] = 1, [103] = 2, [104] = 2, [105] = 1, }, ["HUNTER"] = { [253] = 2, [254] = 2, [255] = 2, }, ["SHAMAN"] = { [262] = 1, [263] = 2, [264] = 1, }, ["PRIEST"] = { [256] = 1, [257] = 1, [258] = 1, }, ["WARLOCK"] = { [265] = 1, [266] = 1, [267] =1 , }, ["PALADIN"] = { [65] = 1, [66] = 3, [70] = 3, }, ["MONK"] = { [268] = 2, [269] = 2, [270] = 1, }, ["EVOKER"] = { [1467] = 1, --Devastation [1468] = 1, --Preservation }, }