--[=[ Please refer to the docs.txt within this file folder for a guide on how to use this library. UnitID: UnitID use: "player", "target", "raid18", "party3", etc... If passing the unit name, use GetUnitName(unitId, true) or Ambiguate(playerName, 'none') Code Rules: - When a function or variable name refers to 'Player', it indicates the local player. - When 'Unit' is use instead, it indicates any entity. - Internal callbacks are the internal communication of the library, e.g. when an event triggers it send to all modules that registered that event. - Public callbacks are callbacks registered by an external addon. Change Log: - added interrupts to cooldown tracker, new filter: "interrupt" - after encounter_end cooldowns now check for cooldowns reset. - each module now controls what to do with regen_enabled. - filter cooldowns done. - move portions of the code to other files to make this one smaller. - major function and variables rename. - implemented pvp talents. - player information is always available even when not in a group. TODO: - need to finish the CheckForSpellsAdeedOrRemoved(), need to send the comm, need to create the local callbacks - create comm to add or remove a cooldown from an unit - add unit_connected through comm to know if a unit disconnected - keystone info (portion of the logic is implemented, need to share the information) - raid lockouts normal-heroic-mythic - soulbind character (covenant choise) - probably not used in 10.0 - add into gear info how many tier set parts the player has - (bug) after a /reload, it is not starting new tickers for spells under cooldown --]=] local major = "LibOpenRaid-1.0" local CONST_LIB_VERSION = 31 LIB_OPEN_RAID_CAN_LOAD = false --declae the library within the LibStub local libStub = _G.LibStub local openRaidLib = libStub:NewLibrary(major, CONST_LIB_VERSION) if (not openRaidLib) then return end LIB_OPEN_RAID_CAN_LOAD = true --default values openRaidLib.inGroup = false openRaidLib.UnitIDCache = {} --show failures (when the function return an error) results to chat local CONST_DIAGNOSTIC_ERRORS = false --show the data to be sent and data received from comm local CONST_DIAGNOSTIC_COMM = false local CONST_COMM_PREFIX = "LRS" local CONST_COMM_FULLINFO_PREFIX = "F" local CONST_COMM_COOLDOWNUPDATE_PREFIX = "U" local CONST_COMM_COOLDOWNFULLLIST_PREFIX = "C" local CONST_COMM_COOLDOWNADDSPELL_PREFIX = "S" local CONST_COMM_COOLDOWNREMOVESPELL_PREFIX = "E" local CONST_COMM_GEARINFO_FULL_PREFIX = "G" local CONST_COMM_GEARINFO_DURABILITY_PREFIX = "R" local CONST_COMM_PLAYER_DEAD_PREFIX = "D" local CONST_COMM_PLAYER_ALIVE_PREFIX = "A" local CONST_COMM_PLAYERINFO_PREFIX = "P" local CONST_COMM_PLAYERINFO_TALENTS_PREFIX = "T" local CONST_COMM_PLAYERINFO_PVPTALENTS_PREFIX = "V" local CONST_ONE_SECOND = 1.0 local CONST_TWO_SECONDS = 2.0 local CONST_THREE_SECONDS = 3.0 local CONST_COOLDOWN_CHECK_INTERVAL = CONST_TWO_SECONDS local CONST_COOLDOWN_TIMELEFT_HAS_CHANGED = CONST_TWO_SECONDS local CONST_COOLDOWN_INDEX_TIMELEFT = 1 local CONST_COOLDOWN_INDEX_CHARGES = 2 local CONST_COOLDOWN_INDEX_TIMEOFFSET = 3 local CONST_COOLDOWN_INDEX_DURATION = 4 local CONST_COOLDOWN_INDEX_UPDATETIME = 5 --make the 'pri-nt' word be only used once, this makes easier to find lost debug pri-nts in the code local sendChatMessage = function(...) print(...) end openRaidLib.DiagnosticError = function(msg, ...) if (CONST_DIAGNOSTIC_ERRORS) then sendChatMessage("|cFFFF9922OpenRaidLib|r:", msg, ...) end end local diagnosticComm = function(msg, ...) if (CONST_DIAGNOSTIC_COMM) then sendChatMessage("|cFFFF9922OpenRaidLib|r:", msg, ...) end end openRaidLib.DeprecatedMessage = function(msg) sendChatMessage("|cFFFF9922OpenRaidLib|r:", "|cFFFF5555" .. msg .. "|r") end local isTimewalkWoW = function() local gameVersion = GetBuildInfo() if (gameVersion:match("%d") == "1" or gameVersion:match("%d") == "2") then return true end end -------------------------------------------------------------------------------------------------------------------------------- --> ~comms openRaidLib.commHandler = {} function openRaidLib.commHandler.OnReceiveComm(self, event, prefix, text, channel, sender, target, zoneChannelID, localID, name, instanceID) --check if the data belong to us if (prefix == CONST_COMM_PREFIX) then --check if the lib can receive comms if (not openRaidLib.IsCommAllowed()) then return end sender = Ambiguate(sender, "none") --don't receive comms from the player it self local playerName = UnitName("player") if (playerName == sender) then return end local data = text local LibDeflate = LibStub:GetLibrary("LibDeflate") local dataCompressed = LibDeflate:DecodeForWoWAddonChannel(data) data = LibDeflate:DecompressDeflate(dataCompressed) --get the first byte of the data, it indicates what type of data was transmited local dataTypePrefix = data:match("^.") --get the table with functions regitered for this type of data local callbackTable = openRaidLib.commHandler.commCallback[dataTypePrefix] --convert to table local dataAsTable = {strsplit(",", data)} --remove the first index (prefix) tremove(dataAsTable, 1) --trigger callbacks for i = 1, #callbackTable do callbackTable[i](dataAsTable, sender) end end end C_ChatInfo.RegisterAddonMessagePrefix(CONST_COMM_PREFIX) openRaidLib.commHandler.eventFrame = CreateFrame("frame") openRaidLib.commHandler.eventFrame:RegisterEvent("CHAT_MSG_ADDON") openRaidLib.commHandler.eventFrame:SetScript("OnEvent", openRaidLib.commHandler.OnReceiveComm) openRaidLib.commHandler.commCallback = { --when transmiting [CONST_COMM_FULLINFO_PREFIX] = {}, --update all [CONST_COMM_COOLDOWNFULLLIST_PREFIX] = {}, --all cooldowns of a player [CONST_COMM_COOLDOWNUPDATE_PREFIX] = {}, --an update of a single cooldown [CONST_COMM_GEARINFO_FULL_PREFIX] = {}, --an update of gear information [CONST_COMM_GEARINFO_DURABILITY_PREFIX] = {}, --an update of the player gear durability [CONST_COMM_PLAYER_DEAD_PREFIX] = {}, --player is dead [CONST_COMM_PLAYER_ALIVE_PREFIX] = {}, --player is alive [CONST_COMM_PLAYERINFO_PREFIX] = {}, --info about the player [CONST_COMM_PLAYERINFO_TALENTS_PREFIX] = {}, --talents info [CONST_COMM_PLAYERINFO_PVPTALENTS_PREFIX] = {}, --pvp talents info } function openRaidLib.commHandler.RegisterComm(prefix, func) --the table for the prefix need to be declared at the 'openRaidLib.commHandler.commCallback' table tinsert(openRaidLib.commHandler.commCallback[prefix], func) end function openRaidLib.commHandler.SendCommData(data) local LibDeflate = LibStub:GetLibrary("LibDeflate") local dataCompressed = LibDeflate:CompressDeflate(data, {level = 9}) local dataEncoded = LibDeflate:EncodeForWoWAddonChannel(dataCompressed) if (IsInGroup() and not IsInRaid()) then --in party only C_ChatInfo.SendAddonMessage(CONST_COMM_PREFIX, dataEncoded, IsInGroup(LE_PARTY_CATEGORY_INSTANCE) and "INSTANCE_CHAT" or "PARTY") elseif (IsInRaid()) then C_ChatInfo.SendAddonMessage(CONST_COMM_PREFIX, dataEncoded, IsInRaid(LE_PARTY_CATEGORY_INSTANCE) and "INSTANCE_CHAT" or "RAID") end end -------------------------------------------------------------------------------------------------------------------------------- --> ~schedule ~timers openRaidLib.Schedules = { registeredUniqueTimers = {} } --run a scheduled function with its payload local triggerScheduledTick = function(tickerObject) local payload = tickerObject.payload local callback = tickerObject.callback local result, errortext = pcall(callback, _G.unpack(payload)) if (not result) then sendChatMessage("openRaidLib: error on scheduler:", tickerObject.scheduleName, tickerObject.stack, errortext) end if (tickerObject.isUnique) then local namespace = tickerObject.namespace local scheduleName = tickerObject.scheduleName openRaidLib.Schedules.CancelUniqueTimer(namespace, scheduleName) end return result end --create a new schedule function openRaidLib.Schedules.NewTimer(time, callback, ...) local payload = {...} local newTimer = C_Timer.NewTimer(time, triggerScheduledTick) newTimer.payload = payload newTimer.callback = callback newTimer.stack = debugstack() return newTimer end --create an unique schedule --if a schedule already exists, cancels it and make a new function openRaidLib.Schedules.NewUniqueTimer(time, callback, namespace, scheduleName, ...) openRaidLib.Schedules.CancelUniqueTimer(namespace, scheduleName) local newTimer = openRaidLib.Schedules.NewTimer(time, callback, ...) newTimer.namespace = namespace newTimer.scheduleName = scheduleName newTimer.stack = debugstack() newTimer.isUnique = true local registeredUniqueTimers = openRaidLib.Schedules.registeredUniqueTimers registeredUniqueTimers[namespace] = registeredUniqueTimers[namespace] or {} registeredUniqueTimers[namespace][scheduleName] = newTimer end --cancel an unique schedule function openRaidLib.Schedules.CancelUniqueTimer(namespace, scheduleName) local registeredUniqueTimers = openRaidLib.Schedules.registeredUniqueTimers local currentSchedule = registeredUniqueTimers[namespace] and registeredUniqueTimers[namespace][scheduleName] if (currentSchedule) then if (not currentSchedule._cancelled) then currentSchedule:Cancel() end registeredUniqueTimers[namespace][scheduleName] = nil end end --cancel all unique timers function openRaidLib.Schedules.CancelAllUniqueTimers() local registeredUniqueTimers = openRaidLib.Schedules.registeredUniqueTimers for namespace, schedulesTable in pairs(registeredUniqueTimers) do for scheduleName, timerObject in pairs (schedulesTable) do if (timerObject and not timerObject._cancelled) then timerObject:Cancel() end end end table.wipe(registeredUniqueTimers) end -------------------------------------------------------------------------------------------------------------------------------- --> ~public ~callbacks --these are the events where other addons can register and receive calls local allPublicCallbacks = { "CooldownListUpdate", "CooldownListWipe", "CooldownUpdate", "UnitDeath", "UnitAlive", "GearListWipe", "GearUpdate", "GearDurabilityUpdate", "UnitInfoUpdate", "UnitInfoWipe", "TalentUpdate", "PvPTalentUpdate", } --save build the table to avoid lose registered events on older versions openRaidLib.publicCallback = openRaidLib.publicCallback or {} openRaidLib.publicCallback.events = openRaidLib.publicCallback.events or {} for _, callbackName in ipairs(allPublicCallbacks) do openRaidLib.publicCallback.events[callbackName] = openRaidLib.publicCallback.events[callbackName] or {} end local checkRegisterDataIntegrity = function(addonObject, event, callbackMemberName) --check of integrity if (type(addonObject) == "string") then addonObject = _G[addonObject] end if (type(addonObject) ~= "table") then return 1 end if (not openRaidLib.publicCallback.events[event]) then return 2 elseif (not addonObject[callbackMemberName]) then return 3 end return true end --call the registered function within the addon namespace --payload is sent together within the call function openRaidLib.publicCallback.TriggerCallback(event, ...) local callbacks = openRaidLib.publicCallback.events[event] for i = 1, #callbacks do local addonObject = callbacks[i][1] local functionName = callbacks[i][2] local func = addonObject[functionName] if (func) then --using pcall at the moment, should get a better caller in the future local okay, errorMessage = pcall(func, ...) if (not okay) then sendChatMessage("error:", errorMessage) end end end end function openRaidLib.RegisterCallback(addonObject, event, callbackMemberName) --check of integrity local integrity = checkRegisterDataIntegrity(addonObject, event, callbackMemberName) if (integrity and type(integrity) ~= "boolean") then return integrity end --register tinsert(openRaidLib.publicCallback.events[event], {addonObject, callbackMemberName}) return true end function openRaidLib.UnregisterCallback(addonObject, event, callbackMemberName) --check of integrity local integrity = checkRegisterDataIntegrity(addonObject, event, callbackMemberName) if (integrity and type(integrity) ~= "boolean") then return integrity end for i = 1, #openRaidLib.publicCallback.events[event] do local registeredCallback = openRaidLib.publicCallback.events[event][i] if (registeredCallback[1] == addonObject and registeredCallback[2] == callbackMemberName) then tremove(openRaidLib.publicCallback.events[event], i) break end end end -------------------------------------------------------------------------------------------------------------------------------- --> ~internal ~callbacks --internally, each module can register events through the internal callback to be notified when something happens in the game openRaidLib.internalCallback = {} openRaidLib.internalCallback.events = { ["onEnterGroup"] = {}, ["onLeaveGroup"] = {}, ["onLeaveCombat"] = {}, ["playerCast"] = {}, ["onEnterWorld"] = {}, ["talentUpdate"] = {}, ["pvpTalentUpdate"] = {}, ["onPlayerDeath"] = {}, ["onPlayerRess"] = {}, ["raidEncounterEnd"] = {}, ["mythicDungeonStart"] = {}, ["playerPetChange"] = {}, } openRaidLib.internalCallback.RegisterCallback = function(event, func) tinsert(openRaidLib.internalCallback.events[event], func) end openRaidLib.internalCallback.UnRegisterCallback = function(event, func) local container = openRaidLib.internalCallback.events[event] for i = 1, #container do if (container[i] == func) then tremove(container, i) break end end end function openRaidLib.internalCallback.TriggerEvent(event, ...) local container = openRaidLib.internalCallback.events[event] for i = 1, #container do container[i](event, ...) end end --create the frame for receiving game events local eventFrame = _G.OpenRaidLibFrame if (not eventFrame) then eventFrame = CreateFrame("frame", "OpenRaidLibFrame", UIParent) end local eventFunctions = { --check if the player joined a group ["GROUP_ROSTER_UPDATE"] = function() local eventTriggered = false if (openRaidLib.IsInGroup()) then if (not openRaidLib.inGroup) then openRaidLib.inGroup = true openRaidLib.internalCallback.TriggerEvent("onEnterGroup") eventTriggered = true end else if (openRaidLib.inGroup) then openRaidLib.inGroup = false openRaidLib.internalCallback.TriggerEvent("onLeaveGroup") eventTriggered = true end end if (not eventTriggered and openRaidLib.IsInGroup()) then --the player didn't left or enter a group --the group has changed, trigger a long timer to send full data --as the timer is unique, a new change to the group will replace and refresh the time --using random time, players won't trigger all at the same time local randomTime = 1.0 + math.random(1.0, 5.5) openRaidLib.Schedules.NewUniqueTimer(randomTime, openRaidLib.mainControl.SendFullData, "mainControl", "sendFullData_Schedule") end openRaidLib.UpdateUnitIDCache() end, ["UNIT_SPELLCAST_SUCCEEDED"] = function(...) local unitId, castGUID, spellId = ... C_Timer.After(0.1, function() openRaidLib.internalCallback.TriggerEvent("playerCast", spellId, UnitIsUnit(unitId, "pet")) end) end, ["PLAYER_ENTERING_WORLD"] = function(...) if (not openRaidLib.firstEnteringWorld) then --player logon if (IsInGroup()) then openRaidLib.RequestAllData() end --> this part is under development if (Details) then local detailsEventListener = Details:CreateEventListener() function detailsEventListener:UnitSpecFound(event, unitId, specId, unitGuid) local unitName = GetUnitName(unitId, true) or unitId if (not UnitInParty(unitName) and not UnitInRaid(unitName)) then return end --check if there's unit information about this unit --is still did not received a list of cooldowns from this player if (not openRaidLib.CooldownManager.HasFullCooldownList[unitName]) then --build a generic list from the spec end end function detailsEventListener:UnitTalentsFound(event, unitId, talentTable, unitGuid) local unitName = GetUnitName(unitId, true) or unitId if (not UnitInParty(unitName) and not UnitInRaid(unitName)) then return end end detailsEventListener:RegisterEvent("UNIT_SPEC", "UnitSpecFound") detailsEventListener:RegisterEvent("UNIT_TALENTS", "UnitTalentsFound") end openRaidLib.firstEnteringWorld = true end openRaidLib.internalCallback.TriggerEvent("onEnterWorld") end, --["PLAYER_SPECIALIZATION_CHANGED"] = function(...) end, --on changing spec, the talent_update event is also triggered ["PLAYER_TALENT_UPDATE"] = function(...) openRaidLib.internalCallback.TriggerEvent("talentUpdate") end, ["PLAYER_PVP_TALENT_UPDATE"] = function(...) openRaidLib.internalCallback.TriggerEvent("pvpTalentUpdate") end, ["PLAYER_DEAD"] = function(...) openRaidLib.mainControl.UpdatePlayerAliveStatus() end, ["PLAYER_ALIVE"] = function(...) openRaidLib.mainControl.UpdatePlayerAliveStatus() end, ["PLAYER_UNGHOST"] = function(...) openRaidLib.mainControl.UpdatePlayerAliveStatus() end, ["PLAYER_REGEN_DISABLED"] = function(...) --entered in combat end, ["PLAYER_REGEN_ENABLED"] = function(...) openRaidLib.internalCallback.TriggerEvent("onLeaveCombat") end, ["UPDATE_INVENTORY_DURABILITY"] = function(...) --an item has changed its durability --do not trigger this event while in combat if (not InCombatLockdown()) then openRaidLib.Schedules.NewUniqueTimer(5 + math.random(0, 4), openRaidLib.GearManager.SendDurability, "GearManager", "sendDurability_Schedule") end end, ["PLAYER_EQUIPMENT_CHANGED"] = function(...) --player changed an equipment openRaidLib.Schedules.NewUniqueTimer(4 + math.random(0, 5), openRaidLib.GearManager.SendAllGearInfo, "GearManager", "sendAllGearInfo_Schedule") end, ["ENCOUNTER_END"] = function() if (IsInRaid()) then openRaidLib.internalCallback.TriggerEvent("raidEncounterEnd") end end, ["CHALLENGE_MODE_START"] = function() openRaidLib.internalCallback.TriggerEvent("mythicDungeonStart") end, ["UNIT_PET"] = function(unitId) if (UnitIsUnit(unitId, "player")) then openRaidLib.Schedules.NewUniqueTimer(0.5, function() openRaidLib.internalCallback.TriggerEvent("playerPetChange") end, "mainControl", "petStatus_Schedule") --if the pet is alive, register to know when it dies if (UnitExists("pet") and UnitHealth("pet") >= 1) then eventFrame:RegisterUnitEvent("UNIT_FLAGS", "pet") end end end, ["UNIT_FLAGS"] = function(unitId) local petHealth = UnitHealth(unitId) if (petHealth < 1) then eventFrame:UnregisterEvent("UNIT_FLAGS") openRaidLib.eventFunctions["UNIT_PET"]("player") end end } openRaidLib.eventFunctions = eventFunctions eventFrame:RegisterEvent("GROUP_ROSTER_UPDATE") eventFrame:RegisterUnitEvent("UNIT_SPELLCAST_SUCCEEDED", "player", "pet") eventFrame:RegisterEvent("PLAYER_ENTERING_WORLD") eventFrame:RegisterEvent("PLAYER_REGEN_DISABLED") eventFrame:RegisterEvent("PLAYER_REGEN_ENABLED") eventFrame:RegisterEvent("UPDATE_INVENTORY_DURABILITY") eventFrame:RegisterEvent("PLAYER_EQUIPMENT_CHANGED") eventFrame:RegisterEvent("UNIT_PET") --eventFrame:RegisterEvent("PLAYER_SPECIALIZATION_CHANGED") if (not isTimewalkWoW()) then eventFrame:RegisterEvent("PLAYER_TALENT_UPDATE") eventFrame:RegisterEvent("PLAYER_PVP_TALENT_UPDATE") eventFrame:RegisterEvent("ENCOUNTER_END") eventFrame:RegisterEvent("CHALLENGE_MODE_START") end eventFrame:RegisterEvent("PLAYER_DEAD") eventFrame:RegisterEvent("PLAYER_ALIVE") eventFrame:RegisterEvent("PLAYER_UNGHOST") eventFrame:SetScript("OnEvent", function(self, event, ...) eventFunctions[event](...) end) -------------------------------------------------------------------------------------------------------------------------------- --> ~main ~control openRaidLib.mainControl = { playerAliveStatus = {}, } --send full data (all data available) function openRaidLib.mainControl.SendFullData() --send player data openRaidLib.UnitInfoManager.SendAllPlayerInfo() --send gear data openRaidLib.GearManager.SendAllGearInfo() --send cooldown data openRaidLib.CooldownManager.SendAllPlayerCooldowns() end openRaidLib.mainControl.onEnterWorld = function() --update the alive status of the player openRaidLib.mainControl.UpdatePlayerAliveStatus(true) --the game client is fully loadded and all information is available if (openRaidLib.IsInGroup()) then openRaidLib.Schedules.NewUniqueTimer(1.0, openRaidLib.mainControl.SendFullData, "mainControl", "sendFullData_Schedule") end end --update player data, even if not in group --called on every player_entering_world event openRaidLib.mainControl.UpdatePlayerData = function() local unitName = UnitName("player") --> player data local playerFullInfo = openRaidLib.UnitInfoManager.GetPlayerFullInfo() openRaidLib.UnitInfoManager.AddUnitInfo(unitName, unpack(playerFullInfo)) --> gear info local playerGearInfo = openRaidLib.GearManager.GetPlayerFullGearInfo() openRaidLib.GearManager.AddUnitGearList(unitName, unpack(playerGearInfo)) --> cooldowns openRaidLib.CooldownManager.UpdatePlayerCooldownsLocally() end --this function runs on all Player Entering World, it is delayed due to covenant data many times aren't available after a cold login function openRaidLib.mainControl.scheduleUpdatePlayerData() openRaidLib.Schedules.NewUniqueTimer(1.0, openRaidLib.mainControl.UpdatePlayerData, "mainControl", "updatePlayerData_Schedule") end function openRaidLib.UpdatePlayer() return openRaidLib.mainControl.UpdatePlayerData() end openRaidLib.mainControl.OnEnterGroup = function() --the player entered in a group --schedule to send data openRaidLib.Schedules.NewUniqueTimer(1.0, openRaidLib.mainControl.SendFullData, "mainControl", "sendFullData_Schedule") end openRaidLib.mainControl.OnLeftGroup = function() --the player left a group --wipe group data (each module registers the OnLeftGroup) --cancel all schedules openRaidLib.Schedules.CancelAllUniqueTimers() --wipe alive status table.wipe(openRaidLib.mainControl.playerAliveStatus) --toggle off comms end openRaidLib.mainControl.OnPlayerDeath = function() local playerName = UnitName("player") openRaidLib.mainControl.playerAliveStatus[playerName] = false local dataToSend = CONST_COMM_PLAYER_DEAD_PREFIX openRaidLib.commHandler.SendCommData(dataToSend) diagnosticComm("OnPlayerDeath| " .. dataToSend) --debug openRaidLib.publicCallback.TriggerCallback("UnitDeath", "player") end openRaidLib.mainControl.OnPlayerRess = function() local playerName = UnitName("player") openRaidLib.mainControl.playerAliveStatus[playerName] = true local dataToSend = CONST_COMM_PLAYER_ALIVE_PREFIX openRaidLib.commHandler.SendCommData(dataToSend) diagnosticComm("OnPlayerRess| " .. dataToSend) --debug openRaidLib.publicCallback.TriggerCallback("UnitAlive", "player") end openRaidLib.internalCallback.RegisterCallback("onEnterWorld", openRaidLib.mainControl.onEnterWorld) openRaidLib.internalCallback.RegisterCallback("onEnterWorld", openRaidLib.mainControl.scheduleUpdatePlayerData) openRaidLib.internalCallback.RegisterCallback("onEnterGroup", openRaidLib.mainControl.OnEnterGroup) openRaidLib.internalCallback.RegisterCallback("onLeaveGroup", openRaidLib.mainControl.OnLeftGroup) openRaidLib.internalCallback.RegisterCallback("onPlayerDeath", openRaidLib.mainControl.OnPlayerDeath) openRaidLib.internalCallback.RegisterCallback("onPlayerRess", openRaidLib.mainControl.OnPlayerRess) --a player in the group died openRaidLib.commHandler.RegisterComm(CONST_COMM_PLAYER_DEAD_PREFIX, function(data, unitName) openRaidLib.mainControl.playerAliveStatus[unitName] = false openRaidLib.publicCallback.TriggerCallback("UnitDeath", openRaidLib.GetUnitID(unitName)) end) --a player in the group is now alive openRaidLib.commHandler.RegisterComm(CONST_COMM_PLAYER_ALIVE_PREFIX, function(data, unitName) openRaidLib.mainControl.playerAliveStatus[unitName] = true openRaidLib.publicCallback.TriggerCallback("UnitAlive", openRaidLib.GetUnitID(unitName)) end) function openRaidLib.mainControl.UpdatePlayerAliveStatus(onLogin) if (UnitIsDeadOrGhost("player")) then if (openRaidLib.playerAlive) then openRaidLib.playerAlive = false --trigger event if this isn't from login if (not onLogin) then openRaidLib.internalCallback.TriggerEvent("onPlayerDeath") end end else if (not openRaidLib.playerAlive) then openRaidLib.playerAlive = true --trigger event if this isn't from login if (not onLogin) then openRaidLib.internalCallback.TriggerEvent("onPlayerRess") end end end end -------------------------------------------------------------------------------------------------------------------------------- --> ~all, request data from all players --send a request to all players in the group to send their data function openRaidLib.RequestAllData() if (not IsInGroup()) then return end openRaidLib.requestAllInfoCooldown = openRaidLib.requestAllInfoCooldown or 0 if (openRaidLib.requestAllInfoCooldown > GetTime()) then return end openRaidLib.commHandler.SendCommData(CONST_COMM_FULLINFO_PREFIX) diagnosticComm("RequestAllInfo| " .. CONST_COMM_FULLINFO_PREFIX) --debug openRaidLib.requestAllInfoCooldown = GetTime() + 5 return true end openRaidLib.commHandler.RegisterComm(CONST_COMM_FULLINFO_PREFIX, function(data, sourceName) openRaidLib.sendRequestedAllInfoCooldown = openRaidLib.sendRequestedAllInfoCooldown or 0 --some player in the group requested all information from all players if (openRaidLib.sendRequestedAllInfoCooldown > GetTime()) then return end openRaidLib.Schedules.NewUniqueTimer(random() + math.random(0, 3), openRaidLib.mainControl.SendFullData, "mainControl", "sendFullData_Schedule") openRaidLib.sendRequestedAllInfoCooldown = GetTime() + 5 end) -------------------------------------------------------------------------------------------------------------------------------- --> ~player general ~info ~unit --> API calls --return a table containing all information of units --format: [playerName-realm] = {information} function openRaidLib.GetAllUnitsInfo() return openRaidLib.UnitInfoManager.GetAllUnitsInfo() end --return a table containing information of a single unit function openRaidLib.GetUnitInfo(unitId) local unitName = GetUnitName(unitId, true) or unitId return openRaidLib.UnitInfoManager.GetUnitInfo(unitName) end --> manager constructor openRaidLib.UnitInfoManager = { --structure: --[playerName] = {ilevel = 100, durability = 100, weaponEnchant = 0, noGems = {}, noEnchants = {}} UnitData = {}, } local unitTablePrototype = { specId = 0, specName = "", role = "", renown = 1, covenantId = 0, talents = {}, conduits = {}, pvpTalents = {}, class = "", classId = 0, className = "", name = "", nameFull = "", } function openRaidLib.UnitInfoManager.GetAllUnitsInfo() return openRaidLib.UnitInfoManager.UnitData end --get the unit table or create a new one if 'createNew' is true function openRaidLib.UnitInfoManager.GetUnitInfo(unitName, createNew) local unitInfo = openRaidLib.UnitInfoManager.UnitData[unitName] if (not unitInfo and createNew) then unitInfo = {} openRaidLib.TCopy(unitInfo, unitTablePrototype) openRaidLib.UnitInfoManager.UnitData[unitName] = unitInfo end return unitInfo end function openRaidLib.UnitInfoManager.EraseData() table.wipe(openRaidLib.UnitInfoManager.UnitData) end function openRaidLib.UnitInfoManager.SetUnitInfo(unitName, unitInfo, specId, renown, covenantId, talentsTableUnpacked, conduitsTableUnpacked, pvpTalentsTableUnpacked) local specId, specName, specDescription, specIcon, role = GetSpecializationInfoByID(specId or 0) local className, classString, classId = UnitClass(unitName) unitInfo.specId = specId or unitInfo.specId unitInfo.specName = specName or unitInfo.specName unitInfo.role = role or "DAMAGER" unitInfo.renown = renown or unitInfo.renown unitInfo.covenantId = covenantId or unitInfo.covenantId unitInfo.talents = talentsTableUnpacked or unitInfo.talents unitInfo.conduits = conduitsTableUnpacked or unitInfo.conduits unitInfo.pvpTalents = pvpTalentsTableUnpacked or unitInfo.pvpTalents unitInfo.class = classString unitInfo.classId = classId unitInfo.className = className unitInfo.name = unitName:gsub(("%-.*"), "") unitInfo.nameFull = unitName end function openRaidLib.UnitInfoManager.AddUnitInfo(unitName, specId, renown, covenantId, talentsTableUnpacked, conduitsTableUnpacked, pvpTalentsTableUnpacked) local unitInfo = openRaidLib.UnitInfoManager.GetUnitInfo(unitName, true) openRaidLib.UnitInfoManager.SetUnitInfo(unitName, unitInfo, specId, renown, covenantId, talentsTableUnpacked, conduitsTableUnpacked, pvpTalentsTableUnpacked) openRaidLib.publicCallback.TriggerCallback("UnitInfoUpdate", openRaidLib.GetUnitID(unitName), openRaidLib.UnitInfoManager.UnitData[unitName], openRaidLib.UnitInfoManager.GetAllUnitsInfo()) end --triggered when the lib receives a unit information from another player in the raid --@data: table received from comm --@unitName: player name function openRaidLib.UnitInfoManager.OnReceiveUnitFullInfo(data, unitName) local specId = tonumber(data[1]) local renown = tonumber(data[2]) local covenantId = tonumber(data[3]) local talentsSize = tonumber(data[4]) local conduitsTableIndex = tonumber((talentsSize + 1) + 3) + 1 -- +3 for spec, renown and covenant data | talentSizeIndex + talentSize | +1 for talents size local conduitsSize = data[conduitsTableIndex] local pvpTalentsTableIndex = 3 + 3 + talentsSize + conduitsSize -- +3 for spec, renown and covenant data | +3 for talents, conduit and pvptalents index for size local pvpTalentsSize = data[pvpTalentsTableIndex] --unpack the talents data as a ipairs table local talentsTableUnpacked = openRaidLib.UnpackTable(data, 4, false, false, talentsSize) --unpack the conduits data as a ipairs table local conduitsTableUnpacked = openRaidLib.UnpackTable(data, conduitsTableIndex, false, false, conduitsSize) --back compatibility with versions without pvp talents if (type(data[pvpTalentsTableIndex]) == "string" or not data[pvpTalentsTableIndex]) then --add a dummy table as pvp talents openRaidLib.UnitInfoManager.AddUnitInfo(unitName, specId, renown, covenantId, talentsTableUnpacked, conduitsTableUnpacked, {0, 0, 0}) return end --unpack the pvp talents data as a ipairs table local pvpTalentsTableUnpacked = openRaidLib.UnpackTable(data, pvpTalentsTableIndex, false, false, pvpTalentsSize) --add to the list of players information and also trigger a public callback openRaidLib.UnitInfoManager.AddUnitInfo(unitName, specId, renown, covenantId, talentsTableUnpacked, conduitsTableUnpacked, pvpTalentsTableUnpacked) end openRaidLib.commHandler.RegisterComm(CONST_COMM_PLAYERINFO_PREFIX, openRaidLib.UnitInfoManager.OnReceiveUnitFullInfo) function openRaidLib.UnitInfoManager.SendAllPlayerInfo() local playerInfo = openRaidLib.UnitInfoManager.GetPlayerFullInfo() local dataToSend = CONST_COMM_PLAYERINFO_PREFIX .. "," dataToSend = dataToSend .. playerInfo[1] .. "," --spec id dataToSend = dataToSend .. playerInfo[2] .. "," --renown dataToSend = dataToSend .. playerInfo[3] .. "," --covenantId dataToSend = dataToSend .. openRaidLib.PackTable(playerInfo[4]) .. "," --talents dataToSend = dataToSend .. openRaidLib.PackTable(playerInfo[5]) .. "," --conduits dataToSend = dataToSend .. openRaidLib.PackTable(playerInfo[6]) .. "," --pvp talents --send the data openRaidLib.commHandler.SendCommData(dataToSend) diagnosticComm("SendGetUnitInfoFullData| " .. dataToSend) --debug end function openRaidLib.UnitInfoManager.GetPlayerFullInfo() local playerInfo = {} if (isTimewalkWoW()) then --indexes: specId, renown, covenant, talent, conduits, pvp talents --return a placeholder table return {0, 0, 0, {0, 0, 0, 0, 0, 0, 0}, {0, 0}, 0} end --spec local specId = 0 local selectedSpecialization = GetSpecialization() if (selectedSpecialization) then specId = GetSpecializationInfo(selectedSpecialization) or 0 end playerInfo[1] = specId --renown local renown = C_CovenantSanctumUI.GetRenownLevel() or 1 playerInfo[2] = renown --covenant local covenant = C_Covenants.GetActiveCovenantID() playerInfo[3] = covenant --talents local talents = openRaidLib.UnitInfoManager.GetPlayerTalents() playerInfo[4] = talents --conduits local conduits = openRaidLib.UnitInfoManager.GetPlayerConduits() playerInfo[5] = conduits --pvp talents local pvpTalents = openRaidLib.UnitInfoManager.GetPlayerPvPTalents() playerInfo[6] = pvpTalents return playerInfo end --talent update (when the player changes a talent and the lib needs to notify other players in the group) function openRaidLib.UnitInfoManager.SendTalentUpdate() --talents local unitInfo = openRaidLib.UnitInfoManager.GetUnitInfo("player", true) local talentsToSend = unitInfo.talents local dataToSend = CONST_COMM_PLAYERINFO_TALENTS_PREFIX .. "," local talentsString = openRaidLib.PackTable(talentsToSend) dataToSend = dataToSend .. talentsString --send the data openRaidLib.commHandler.SendCommData(dataToSend) diagnosticComm("SendTalentUpdateData| " .. dataToSend) --debug end function openRaidLib.UnitInfoManager.OnPlayerTalentChanged() --update the local player local unitInfo = openRaidLib.UnitInfoManager.GetUnitInfo("player", true) local unitName = UnitName("player") openRaidLib.UnitInfoManager.SetUnitInfo(unitName, unitInfo, nil, nil, nil, openRaidLib.UnitInfoManager.GetPlayerTalents()) --schedule send to the group openRaidLib.Schedules.NewUniqueTimer(1 + math.random(0, 1), openRaidLib.UnitInfoManager.SendTalentUpdate, "UnitInfoManager", "sendTalent_Schedule") --trigger public callback event openRaidLib.publicCallback.TriggerCallback("TalentUpdate", "player", unitInfo.talents, unitInfo, openRaidLib.UnitInfoManager.GetAllUnitsInfo()) end openRaidLib.internalCallback.RegisterCallback("talentUpdate", openRaidLib.UnitInfoManager.OnPlayerTalentChanged) function openRaidLib.UnitInfoManager.OnReceiveTalentsUpdate(data, unitName) local talentsTableUnpacked = openRaidLib.UnpackTable(data, 1, false, false, 7) local unitInfo = openRaidLib.UnitInfoManager.GetUnitInfo(unitName, true) if (unitInfo) then openRaidLib.UnitInfoManager.SetUnitInfo(unitName, unitInfo, nil, nil, nil, talentsTableUnpacked) --trigger public callback event openRaidLib.publicCallback.TriggerCallback("TalentUpdate", openRaidLib.GetUnitID(unitName), unitInfo.talents, unitInfo, openRaidLib.UnitInfoManager.GetAllUnitsInfo()) end end openRaidLib.commHandler.RegisterComm(CONST_COMM_PLAYERINFO_TALENTS_PREFIX, openRaidLib.UnitInfoManager.OnReceiveTalentsUpdate) --pvp talent update (when the player changes a pvp talent and the lib needs to notify other players in the group) function openRaidLib.UnitInfoManager.SendPvPTalentUpdate() --pvp talents local unitInfo = openRaidLib.UnitInfoManager.GetUnitInfo("player", true) local pvpTalentsToSend = unitInfo.pvpTalents local dataToSend = CONST_COMM_PLAYERINFO_PVPTALENTS_PREFIX .. "," local pvpTalentsString = openRaidLib.PackTable(pvpTalentsToSend) dataToSend = dataToSend .. pvpTalentsString --send the data openRaidLib.commHandler.SendCommData(dataToSend) diagnosticComm("SendPvPTalentUpdateData| " .. dataToSend) --debug end function openRaidLib.UnitInfoManager.OnPlayerPvPTalentChanged() --update the local player local unitInfo = openRaidLib.UnitInfoManager.GetUnitInfo("player", true) local unitName = UnitName("player") openRaidLib.UnitInfoManager.SetUnitInfo(unitName, unitInfo, nil, nil, nil, nil, nil, openRaidLib.UnitInfoManager.GetPlayerPvPTalents()) --schedule send to the group openRaidLib.Schedules.NewUniqueTimer(1 + math.random(0, 1), openRaidLib.UnitInfoManager.SendPvPTalentUpdate, "UnitInfoManager", "sendPvPTalent_Schedule") --trigger public callback event openRaidLib.publicCallback.TriggerCallback("PvPTalentUpdate", "player", unitInfo.pvpTalents, unitInfo, openRaidLib.UnitInfoManager.GetAllUnitsInfo()) end openRaidLib.internalCallback.RegisterCallback("pvpTalentUpdate", openRaidLib.UnitInfoManager.OnPlayerPvPTalentChanged) function openRaidLib.UnitInfoManager.OnReceivePvPTalentsUpdate(data, unitName) local pvpTalentsTableUnpacked = openRaidLib.UnpackTable(data, 1, false, false, 3) local unitInfo = openRaidLib.UnitInfoManager.GetUnitInfo(unitName, true) if (unitInfo) then unitInfo.pvpTalents = pvpTalentsTableUnpacked --trigger public callback event openRaidLib.publicCallback.TriggerCallback("PvPTalentUpdate", openRaidLib.GetUnitID(unitName), unitInfo.pvpTalents, unitInfo, openRaidLib.UnitInfoManager.GetAllUnitsInfo()) end end openRaidLib.commHandler.RegisterComm(CONST_COMM_PLAYERINFO_PVPTALENTS_PREFIX, openRaidLib.UnitInfoManager.OnReceivePvPTalentsUpdate) function openRaidLib.UnitInfoManager.OnPlayerLeaveGroup() local unitName = UnitName("player") --clear the data openRaidLib.UnitInfoManager.EraseData() --trigger a public callback openRaidLib.publicCallback.TriggerCallback("UnitInfoWipe", openRaidLib.UnitInfoManager.UnitData) --need to build the player info again local playerFullInfo = openRaidLib.UnitInfoManager.GetPlayerFullInfo() openRaidLib.UnitInfoManager.AddUnitInfo(unitName, unpack(playerFullInfo)) end openRaidLib.internalCallback.RegisterCallback("onLeaveGroup", openRaidLib.UnitInfoManager.OnPlayerLeaveGroup) --send data when leaving combat function openRaidLib.UnitInfoManager.SendPlayerInfoAfterCombat() openRaidLib.UnitInfoManager.SendAllPlayerInfo() end function openRaidLib.UnitInfoManager.OnLeaveCombat() openRaidLib.Schedules.NewUniqueTimer(1 + math.random(1, 4), openRaidLib.UnitInfoManager.SendPlayerInfoAfterCombat, "UnitInfoManager", "leaveCombat_Schedule") end openRaidLib.internalCallback.RegisterCallback("onLeaveCombat", openRaidLib.UnitInfoManager.OnLeaveCombat) -------------------------------------------------------------------------------------------------------------------------------- --> ~equipment openRaidLib.GearManager = { --structure: [playerName] = {ilevel = 100, durability = 100, weaponEnchant = 0, noGems = {}, noEnchants = {}} UnitData = {}, } local gearTablePrototype = { ilevel = 0, durability = 0, weaponEnchant = 0, noGems = {}, noEnchants = {}, } function openRaidLib.GetAllUnitsGear() return openRaidLib.GearManager.GetAllUnitsGear() end function openRaidLib.GetUnitGear(unitId, createNew) local unitName = GetUnitName(unitId, true) or unitId return openRaidLib.GearManager.GetUnitGear(unitName) end function openRaidLib.GearManager.GetAllUnitsGear() return openRaidLib.GearManager.UnitData end function openRaidLib.GearManager.GetUnitGear(unitName, createNew) local unitGearInfo = openRaidLib.GearManager.UnitData[unitName] if (not unitGearInfo and createNew) then unitGearInfo = {} openRaidLib.TCopy(unitGearInfo, gearTablePrototype) openRaidLib.GearManager.UnitData[unitName] = unitGearInfo end return unitGearInfo end --clear data stored function openRaidLib.GearManager.EraseData() table.wipe(openRaidLib.GearManager.UnitData) end function openRaidLib.GearManager.OnPlayerLeaveGroup() local unitName = GetUnitName("player") --clear the data openRaidLib.GearManager.EraseData() --trigger a public callback openRaidLib.publicCallback.TriggerCallback("GearListWipe", openRaidLib.GearManager.UnitData) --need to build the player gear again local playerGearInfo = openRaidLib.GearManager.GetPlayerFullGearInfo() openRaidLib.GearManager.AddUnitGearList(unitName, unpack(playerGearInfo)) end openRaidLib.internalCallback.RegisterCallback("onLeaveGroup", openRaidLib.GearManager.OnPlayerLeaveGroup) --when the player is ressed while in a group, send the cooldown list function openRaidLib.GearManager.OnPlayerRess() --check if is in group if (openRaidLib.IsInGroup()) then openRaidLib.Schedules.NewUniqueTimer(1.0 + math.random(0.0, 6.0), openRaidLib.GearManager.SendDurability, "GearManager", "sendDurability_Schedule") end end openRaidLib.internalCallback.RegisterCallback("onPlayerRess", openRaidLib.GearManager.OnPlayerRess) --send data when leaving combat function openRaidLib.GearManager.SendGearInfoAfterCombat() openRaidLib.GearManager.SendAllGearInfo() end function openRaidLib.GearManager.OnLeaveCombat() openRaidLib.Schedules.NewUniqueTimer(1 + math.random(1, 4), openRaidLib.GearManager.SendGearInfoAfterCombat, "GearManager", "leaveCombat_Schedule") end openRaidLib.internalCallback.RegisterCallback("onLeaveCombat", openRaidLib.GearManager.OnLeaveCombat) --send only the gear durability function openRaidLib.GearManager.SendDurability() local dataToSend = CONST_COMM_GEARINFO_DURABILITY_PREFIX .. "," local playerGearDurability = openRaidLib.GearManager.GetPlayerGearDurability() dataToSend = dataToSend .. playerGearDurability --send the data openRaidLib.commHandler.SendCommData(dataToSend) diagnosticComm("SendGearDurabilityData| " .. dataToSend) --debug end function openRaidLib.GearManager.OnReceiveGearDurability(data, unitName) local durability = tonumber(data[1]) openRaidLib.GearManager.UpdateUnitGearDurability(unitName, durability) end openRaidLib.commHandler.RegisterComm(CONST_COMM_GEARINFO_DURABILITY_PREFIX, openRaidLib.GearManager.OnReceiveGearDurability) --on receive the durability (sent when the player get a ress) function openRaidLib.GearManager.UpdateUnitGearDurability(unitName, durability) local unitGearInfo = openRaidLib.GearManager.GetUnitGear(unitName) if (unitGearInfo) then unitGearInfo.durability = durability openRaidLib.publicCallback.TriggerCallback("GearDurabilityUpdate", openRaidLib.GetUnitID(unitName), durability, unitGearInfo, openRaidLib.GearManager.GetAllUnitsGear()) end end --get gear information from what the player has equipped at the moment function openRaidLib.GearManager.GetPlayerFullGearInfo() --get the player class and specId local _, playerClass = UnitClass("player") local specId = openRaidLib.GetPlayerSpecId() --get which attribute the spec uses local specMainAttribute = openRaidLib.specAttribute[playerClass][specId] --1 int, 2 dex, 3 str if (not specId or not specMainAttribute) then return {0, 0, 0, {}, {}} end --item level local itemLevel = openRaidLib.GearManager.GetPlayerItemLevel() --repair status local gearDurability = openRaidLib.GearManager.GetPlayerGearDurability() --get weapon enchant local weaponEnchant = openRaidLib.GearManager.GetPlayerWeaponEnchant() --enchants and gems local slotsWithoutGems, slotsWithoutEnchant = openRaidLib.GearManager.GetPlayerGemsAndEnchantInfo() --build the table with the gear information local playerGearInfo = {} playerGearInfo[#playerGearInfo+1] = itemLevel --[1] playerGearInfo[#playerGearInfo+1] = gearDurability --[2] playerGearInfo[#playerGearInfo+1] = weaponEnchant --[3] playerGearInfo[#playerGearInfo+1] = slotsWithoutEnchant --[4] playerGearInfo[#playerGearInfo+1] = slotsWithoutGems --[5] return playerGearInfo end --when received the gear update from another player, store it and trigger a callback function openRaidLib.GearManager.AddUnitGearList(unitName, itemLevel, durability, weaponEnchant, noEnchantTable, noGemsTable) local unitGearInfo = openRaidLib.GearManager.GetUnitGear(unitName, true) unitGearInfo.ilevel = itemLevel unitGearInfo.durability = durability unitGearInfo.weaponEnchant = weaponEnchant unitGearInfo.noGems = noGemsTable unitGearInfo.noEnchants = noEnchantTable openRaidLib.publicCallback.TriggerCallback("GearUpdate", openRaidLib.GetUnitID(unitName), unitGearInfo, openRaidLib.GearManager.GetAllUnitsGear()) end --triggered when the lib receives a gear information from another player in the raid --@data: table received from comm --@unitName: player name function openRaidLib.GearManager.OnReceiveGearFullInfo(data, unitName) local itemLevel = tonumber(data[1]) local durability = tonumber(data[2]) local weaponEnchant = tonumber(data[3]) local noEnchantTableSize = tonumber(data[4]) local noGemsTableIndex = tonumber(noEnchantTableSize + 5) local noGemsTableSize = data[noGemsTableIndex] --unpack the enchant data as a ipairs table local noEnchantTableUnpacked = openRaidLib.UnpackTable(data, 4, false, false, noEnchantTableSize) --unpack the enchant data as a ipairs table local noGemsTableUnpacked = openRaidLib.UnpackTable(data, noGemsTableIndex, false, false, noGemsTableSize) --add to the list of gear information openRaidLib.GearManager.AddUnitGearList(unitName, itemLevel, durability, weaponEnchant, noEnchantTableUnpacked, noGemsTableUnpacked) end openRaidLib.commHandler.RegisterComm(CONST_COMM_GEARINFO_FULL_PREFIX, openRaidLib.GearManager.OnReceiveGearFullInfo) function openRaidLib.GearManager.SendAllGearInfo() --get gear information, gear info has 5 indexes: --[1] int item level --[2] int durability --[3] int weapon enchant --[4] table with integers of equipSlot without enchant --[5] table with integers of equipSlot which has a gem slot but the slot is empty local dataToSend = CONST_COMM_GEARINFO_FULL_PREFIX .. "," local playerGearInfo = openRaidLib.GearManager.GetPlayerFullGearInfo() --update the player table openRaidLib.GearManager.AddUnitGearList(UnitName("player"), unpack(playerGearInfo)) dataToSend = dataToSend .. playerGearInfo[1] .. "," --item level dataToSend = dataToSend .. playerGearInfo[2] .. "," --durability dataToSend = dataToSend .. playerGearInfo[3] .. "," --weapon enchant dataToSend = dataToSend .. openRaidLib.PackTable(playerGearInfo[4]) .. "," --slots without enchant dataToSend = dataToSend .. openRaidLib.PackTable(playerGearInfo[5]) -- slots with empty gem sockets --send the data openRaidLib.commHandler.SendCommData(dataToSend) diagnosticComm("SendGearFullData| " .. dataToSend) --debug end -------------------------------------------------------------------------------------------------------------------------------- --> ~cooldowns openRaidLib.CooldownManager = { UnitData = {}, --stores the list of cooldowns each player has sent UnitDataFilterCache = {}, --same as the table above but cooldowns are separated has offensive, defensive, etc. FilterCooldowns in functions.lua NeedRebuildFilters = {}, --mark people that has invalid filter cache and need to rebuild it CooldownTickers = {}, --store C_Timer.NewTicker HasFullCooldownList = {}, --store player names with the library } --check if a cooldown time has changed or finished --this function run within a ticker, the internal is CONST_COOLDOWN_CHECK_INTERVAL local cooldownTimeLeftCheck_Ticker = function(tickerObject) local spellId = tickerObject.spellId --if the spell does not exists anymore in the player table, cancel the ticker local playerName = UnitName("player") if (not openRaidLib.CooldownManager.UnitData[playerName][spellId]) then tickerObject:Cancel() return end tickerObject.cooldownTimeLeft = tickerObject.cooldownTimeLeft - CONST_COOLDOWN_CHECK_INTERVAL local timeLeft, charges, startTimeOffset, duration = openRaidLib.CooldownManager.GetPlayerCooldownStatus(spellId) local updateLocally = false --is the spell ready to use? if (timeLeft == 0) then --it's ready openRaidLib.CooldownManager.SendPlayerCooldownUpdate(spellId, 0, charges, 0, 0) openRaidLib.CooldownManager.CooldownTickers[spellId] = nil tickerObject:Cancel() updateLocally = true else --check if the time left has changed, this check if the cooldown got its time reduced and if the cooldown time has been slow down by modRate if (not openRaidLib.isNearlyEqual(tickerObject.cooldownTimeLeft, timeLeft, CONST_COOLDOWN_TIMELEFT_HAS_CHANGED)) then --there's a deviation, send a comm to communicate the change in the time left openRaidLib.CooldownManager.SendPlayerCooldownUpdate(spellId, timeLeft, charges, startTimeOffset, duration) tickerObject.cooldownTimeLeft = timeLeft updateLocally = true end end if (updateLocally) then --get the cooldown time for this spell local timeLeft, charges, startTimeOffset, duration = openRaidLib.CooldownManager.GetPlayerCooldownStatus(spellId) --update the cooldown openRaidLib.CooldownManager.CooldownSpellUpdate(playerName, spellId, timeLeft, charges, startTimeOffset, duration) local playerCooldownTable = openRaidLib.GetUnitCooldowns(playerName) local cooldownInfo = openRaidLib.GetUnitCooldownInfo(playerName, spellId) openRaidLib.publicCallback.TriggerCallback("CooldownUpdate", "player", spellId, cooldownInfo, playerCooldownTable, openRaidLib.CooldownManager.UnitData) end end --after a spell is casted by the player, start a ticker to check its cooldown local cooldownStartTicker = function(spellId, cooldownTimeLeft) local existingTicker = openRaidLib.CooldownManager.CooldownTickers[spellId] if (existingTicker) then --if a ticker already exists, might be the cooldown of a charge --if the ticker isn't about to expire, just keep the timer --when the ticker finishes it'll check again for charges if (existingTicker.startTime + existingTicker.cooldownTimeLeft - GetTime() > 2) then return end --cancel the existing ticker if (not existingTicker._cancelled) then existingTicker:Cancel() end end --create a new ticker local maxTicks = ceil(cooldownTimeLeft / CONST_COOLDOWN_CHECK_INTERVAL) local newTicker = C_Timer.NewTicker(CONST_COOLDOWN_CHECK_INTERVAL, cooldownTimeLeftCheck_Ticker, maxTicks) --store the ticker openRaidLib.CooldownManager.CooldownTickers[spellId] = newTicker newTicker.spellId = spellId newTicker.cooldownTimeLeft = cooldownTimeLeft newTicker.startTime = GetTime() newTicker.endTime = GetTime() + cooldownTimeLeft end function openRaidLib.CooldownManager.CleanupCooldownTickers() for spellId, tickerObject in pairs(openRaidLib.CooldownManager.CooldownTickers) do local timeLeft, charges, startTimeOffset, duration = openRaidLib.CooldownManager.GetPlayerCooldownStatus(spellId) if (timeLeft == 0) then tickerObject:Cancel() openRaidLib.CooldownManager.CooldownTickers[spellId] = nil end end end local cooldownGetUnitTable = function(unitName, shouldWipe) local unitCooldownTable = openRaidLib.CooldownManager.UnitData[unitName] --check if the unit has a cooldownTable if (not unitCooldownTable) then unitCooldownTable = {} openRaidLib.CooldownManager.UnitData[unitName] = unitCooldownTable else --as the unit could have changed a talent or spec, wipe the table before using it if (shouldWipe) then table.wipe(unitCooldownTable) end end return unitCooldownTable end local cooldownGetSpellInfo = function(unitName, spellId) local unitCooldownTable = cooldownGetUnitTable(unitName) local spellIdTable = unitCooldownTable[spellId] return spellIdTable end --update a single cooldown timer --called when the player casted a cooldown and when received a cooldown update from another player --only update the db, no other action is taken function openRaidLib.CooldownManager.CooldownSpellUpdate(unitName, spellId, newTimeLeft, newCharges, startTimeOffset, duration) local unitCooldownTable = cooldownGetUnitTable(unitName) local spellIdTable = unitCooldownTable[spellId] or {} spellIdTable[CONST_COOLDOWN_INDEX_TIMELEFT] = newTimeLeft spellIdTable[CONST_COOLDOWN_INDEX_CHARGES] = newCharges spellIdTable[CONST_COOLDOWN_INDEX_TIMEOFFSET] = startTimeOffset spellIdTable[CONST_COOLDOWN_INDEX_DURATION] = duration spellIdTable[CONST_COOLDOWN_INDEX_UPDATETIME] = GetTime() unitCooldownTable[spellId] = spellIdTable end --> API Calls --return a table with unit names as key and a table with unit cooldowns as the value --table format: [playerName] = {[spellId] = cooldownInfo} function openRaidLib.GetAllUnitsCooldown() return openRaidLib.CooldownManager.UnitData end --return a table with all the unit cooldowns --table format: [spellId] = cooldownInfo function openRaidLib.GetUnitCooldowns(unitId, filter) local unitName = GetUnitName(unitId, true) or unitId local allCooldowns = openRaidLib.CooldownManager.UnitData[unitName] --check if there's a filter and if there's at least one cooldown existing if (allCooldowns and next(allCooldowns)) then if (filter and filter ~= "") then if (type(filter) == "string") then local filterCooldowns = openRaidLib.FilterCooldowns(unitName, allCooldowns, filter) return filterCooldowns else openRaidLib.DiagnosticError("CooldownManager|GetUnitCooldowns|filter isn't a string") end else return allCooldowns end else return {} end end function openRaidLib.DoesSpellPassFilters(spellId, filter) return openRaidLib.CooldownManager.DoesSpellPassFilters(spellId, filter) end --return values about the cooldown time --values returned: timeLeft, charges, timeOffset, duration, updateTime function openRaidLib.GetCooldownTimeFromUnitSpellID(unitId, spellId) local unitCooldownsTable = openRaidLib.GetUnitCooldowns(unitId) if (unitCooldownsTable) then local cooldownInfo = unitCooldownsTable[spellId] if (cooldownInfo) then return unpack(cooldownInfo) end end end --return values about the cooldown time from a cooldown info --values returned: timeLeft, charges, timeOffset, duration, updateTime function openRaidLib.GetCooldownTimeFromCooldownInfo(cooldownInfo) if (cooldownInfo) then return unpack(cooldownInfo) end end --return a table containing values about the cooldown time --values returned: {timeLeft, charges, timeOffset, duration, updateTime} function openRaidLib.GetUnitCooldownInfo(unitId, spellId) local unitCooldownsTable = openRaidLib.GetUnitCooldowns(unitId) if (unitCooldownsTable) then local cooldownInfo = unitCooldownsTable[spellId] return cooldownInfo end end local calculatePercent = function(timeOffset, duration, updateTime, charges) timeOffset = abs(timeOffset) local minValue = updateTime - timeOffset local maxValue = minValue + duration local currentValue = GetTime() local percent = openRaidLib.GetRangePercent(minValue, maxValue, currentValue) percent = min(percent, 1) local timeLeft = max(maxValue - currentValue, 0) --lag compensation if (timeLeft <= 2) then timeLeft = 0 if (charges == 0) then charges = 1 end minValue = currentValue maxValue = 1 currentValue = 1 end return timeLeft <= 2, percent, timeLeft, charges, minValue, maxValue, min(currentValue, maxValue), duration end --return the values to be use on a progress bar or cooldown frame --require a unitId and a spellId to query the values --values returned: isReady, timeLeft, charges, normalized percent, minValue, maxValue, currentValue --values are in the GetTime() format function openRaidLib.GetCooldownStatusFromUnitSpellID(unitId, spellId) local timeLeft, charges, timeOffset, duration, updateTime local unitCooldownsTable = openRaidLib.GetUnitCooldowns(unitId) if (unitCooldownsTable) then local cooldownInfo = unitCooldownsTable[spellId] if (cooldownInfo) then timeLeft, charges, timeOffset, duration, updateTime = unpack(cooldownInfo) end end return calculatePercent(timeOffset, duration, updateTime, charges) end --return the values to be use on a progress bar or cooldown frame --require the cooldownInfo table --values returned: isReady, timeLeft, charges, normalized percent, minValue, maxValue, currentValue --values are in the GetTime() format --GetPercentFromCooldownInfo function openRaidLib.GetCooldownStatusFromCooldownInfo(cooldownInfo) local timeLeft, charges, timeOffset, duration, updateTime = unpack(cooldownInfo) return calculatePercent(timeOffset, duration, updateTime, charges) end --> internals function openRaidLib.CooldownManager.OnPlayerCast(event, spellId, isPlayerPet) --~cast --player casted a spell, check if the spell is registered as cooldown local playerSpec = openRaidLib.GetPlayerSpecId() if (playerSpec) then if (LIB_OPEN_RAID_COOLDOWNS_BY_SPEC[playerSpec] and LIB_OPEN_RAID_COOLDOWNS_BY_SPEC[playerSpec][spellId]) then local playerName = UnitName("player") --get the cooldown time for this spell local timeLeft, charges, startTimeOffset, duration = openRaidLib.CooldownManager.GetPlayerCooldownStatus(spellId) --update the cooldown openRaidLib.CooldownManager.CooldownSpellUpdate(playerName, spellId, timeLeft, charges, startTimeOffset, duration) local cooldownInfo = cooldownGetSpellInfo(playerName, spellId) --trigger a public callback local playerCooldownTable = openRaidLib.GetUnitCooldowns(playerName) openRaidLib.publicCallback.TriggerCallback("CooldownUpdate", "player", spellId, cooldownInfo, playerCooldownTable, openRaidLib.CooldownManager.UnitData) --send to comm openRaidLib.CooldownManager.SendPlayerCooldownUpdate(spellId, timeLeft, charges, startTimeOffset, duration) --create a timer to monitor the time of this cooldown --as there's just a few of them to monitor, there's no issue on creating one timer per spell cooldownStartTicker(spellId, timeLeft) end end end --when the player is ressed while in a group, send the cooldown list function openRaidLib.CooldownManager.OnPlayerRess() --check if is in group if (openRaidLib.IsInGroup()) then openRaidLib.Schedules.NewUniqueTimer(1.0 + math.random(0.0, 6.0), openRaidLib.CooldownManager.SendAllPlayerCooldowns, "CooldownManager", "sendAllPlayerCooldowns_Schedule") end end function openRaidLib.CooldownManager.OnPlayerLeaveGroup() --clear the data openRaidLib.CooldownManager.EraseData() --trigger a public callback openRaidLib.publicCallback.TriggerCallback("CooldownListWipe", openRaidLib.CooldownManager.UnitData) --recreate the player cooldowns openRaidLib.CooldownManager.UpdatePlayerCooldownsLocally() end --when a talent has changed, it might remove or add a cooldown function openRaidLib.CooldownManager.OnPlayerTalentChanged() --immediatelly update the player cooldowns locally openRaidLib.CooldownManager.UpdatePlayerCooldownsLocally() --schedule send to the group, using a large delay to send due to the player might change more talents at once openRaidLib.Schedules.NewUniqueTimer(4 + math.random(0, 1), openRaidLib.CooldownManager.SendAllPlayerCooldowns, "CooldownManager", "sendAllPlayerCooldowns_Schedule") end --check cooldown reset after a raid encounter ends finishing ongoing timeLeft tickers function openRaidLib.CooldownManager.CheckCooldownsAfterEncounterEnd() openRaidLib.CooldownManager.CleanupCooldownTickers() openRaidLib.Schedules.NewUniqueTimer(1 + math.random(1, 4), openRaidLib.CooldownManager.SendAllPlayerCooldowns, "CooldownManager", "sendAllPlayerCooldowns_Schedule") end function openRaidLib.CooldownManager.OnEncounterEnd() --run on next frame openRaidLib.Schedules.NewUniqueTimer(0.1, openRaidLib.CooldownManager.CheckCooldownsAfterEncounterEnd, "CooldownManager", "encounterEndCooldownsCheck_Schedule") end function openRaidLib.CooldownManager.OnMythicPlusStart() openRaidLib.Schedules.NewUniqueTimer(0.5, openRaidLib.CooldownManager.SendAllPlayerCooldowns, "CooldownManager", "sendAllPlayerCooldowns_Schedule") end function openRaidLib.CooldownManager.OnPlayerPetChanged() --local spellsAdded, spellsRemoved = openRaidLib.CooldownManager.CheckForSpellsAdeedOrRemoved() --and send a comm telling this player has a new spell instead of sending all the list of spells -- local dataToSend = CONST_COMM_COOLDOWNFULLLIST_PREFIX .. "," -- openRaidLib.commHandler.SendCommData(dataToSend) --openRaidLib.Schedules.NewUniqueTimer(0.5, openRaidLib.CooldownManager.SendAllPlayerCooldowns, "CooldownManager", "sendAllPlayerCooldowns_Schedule") end openRaidLib.internalCallback.RegisterCallback("onLeaveGroup", openRaidLib.CooldownManager.OnPlayerLeaveGroup) openRaidLib.internalCallback.RegisterCallback("playerCast", openRaidLib.CooldownManager.OnPlayerCast) openRaidLib.internalCallback.RegisterCallback("onPlayerRess", openRaidLib.CooldownManager.OnPlayerRess) openRaidLib.internalCallback.RegisterCallback("talentUpdate", openRaidLib.CooldownManager.OnPlayerTalentChanged) openRaidLib.internalCallback.RegisterCallback("raidEncounterEnd", openRaidLib.CooldownManager.OnEncounterEnd) openRaidLib.internalCallback.RegisterCallback("onLeaveCombat", openRaidLib.CooldownManager.OnEncounterEnd) openRaidLib.internalCallback.RegisterCallback("mythicDungeonStart", openRaidLib.CooldownManager.OnMythicPlusStart) openRaidLib.internalCallback.RegisterCallback("playerPetChange", openRaidLib.CooldownManager.OnPlayerPetChanged) --update the list of cooldowns of the player it self locally --this is called right after changes in the player cooldowns function openRaidLib.CooldownManager.UpdatePlayerCooldownsLocally(playerCooldownHash) if (not playerCooldownHash) then playerCooldownHash = select(2, openRaidLib.CooldownManager.GetPlayerCooldownList()) end local playerName = UnitName("player") openRaidLib.CooldownManager.AddUnitCooldownsList(playerName, playerCooldownHash) end --adds a list of cooldowns for another player in the group --this is only called from the received cooldown list from comm function openRaidLib.CooldownManager.AddUnitCooldownsList(unitName, cooldownsTable, noCallback) local unitCooldownTable = cooldownGetUnitTable(unitName, true) --sending true to wipe previous data openRaidLib.TCopy(unitCooldownTable, cooldownsTable) --add the unitName to the list of units detected with the lib openRaidLib.CooldownManager.HasFullCooldownList[unitName] = true --mark the filter cache of this unit as dirt openRaidLib.CooldownManager.NeedRebuildFilters[unitName] = true --get the time where the cooldown data was received, this is used with the timeleft and startTimeOffset local timeNow = GetTime() for spellId, cooldownTable in pairs(cooldownsTable) do cooldownTable[CONST_COOLDOWN_INDEX_UPDATETIME] = timeNow end --trigger a public callback if (not noCallback) then openRaidLib.publicCallback.TriggerCallback("CooldownListUpdate", openRaidLib.GetUnitID(unitName), unitCooldownTable, openRaidLib.CooldownManager.UnitData) end end --received a cooldown update from another unit (sent by the function above) openRaidLib.commHandler.RegisterComm(CONST_COMM_COOLDOWNUPDATE_PREFIX, function(data, unitName) --get data local dataAsArray = data local spellId = tonumber(dataAsArray[1]) local cooldownTimer = tonumber(dataAsArray[2]) local charges = tonumber(dataAsArray[3]) local startTime = tonumber(dataAsArray[4]) local duration = tonumber(dataAsArray[5]) --check integrity if (not spellId or spellId == 0) then return openRaidLib.DiagnosticError("CooldownManager|comm received|spellId is invalid") elseif (not cooldownTimer) then return openRaidLib.DiagnosticError("CooldownManager|comm received|cooldownTimer is invalid") elseif (not charges) then return openRaidLib.DiagnosticError("CooldownManager|comm received|charges is invalid") elseif (not startTime) then return openRaidLib.DiagnosticError("CooldownManager|comm received|startTime is invalid") elseif (not duration) then return openRaidLib.DiagnosticError("CooldownManager|comm received|duration is invalid") end --update openRaidLib.CooldownManager.CooldownSpellUpdate(unitName, spellId, cooldownTimer, charges, startTime, duration) local cooldownInfo = cooldownGetSpellInfo(unitName, spellId) local unitCooldownTable = openRaidLib.GetUnitCooldowns(unitName) --trigger a public callback openRaidLib.publicCallback.TriggerCallback("CooldownUpdate", openRaidLib.GetUnitID(unitName), spellId, cooldownInfo, unitCooldownTable, openRaidLib.CooldownManager.UnitData) end) --clear data stored, this is called after the player quit from a group function openRaidLib.CooldownManager.EraseData() table.wipe(openRaidLib.CooldownManager.UnitDataFilterCache) table.wipe(openRaidLib.CooldownManager.HasFullCooldownList) table.wipe(openRaidLib.CooldownManager.NeedRebuildFilters) table.wipe(openRaidLib.CooldownManager.UnitData) end --send to comm all cooldowns available for the player function openRaidLib.CooldownManager.SendAllPlayerCooldowns() --get the full cooldown list local playerCooldownList, playerCooldownHash = openRaidLib.CooldownManager.GetPlayerCooldownList() --update the player cooldowns locally openRaidLib.CooldownManager.UpdatePlayerCooldownsLocally(playerCooldownHash) local dataToSend = CONST_COMM_COOLDOWNFULLLIST_PREFIX .. "," --pack local playerCooldownString = openRaidLib.PackTable(playerCooldownList) dataToSend = dataToSend .. playerCooldownString --send the data openRaidLib.commHandler.SendCommData(dataToSend) diagnosticComm("SendAllPlayerCooldowns| " .. dataToSend) --debug end --send to comm a specific cooldown that was just used, a charge got available or its cooldown is over (ready to use) function openRaidLib.CooldownManager.SendPlayerCooldownUpdate(spellId, cooldownTimeLeft, charges, startTimeOffset, duration) local dataToSend = CONST_COMM_COOLDOWNUPDATE_PREFIX .. "," .. spellId .. "," .. cooldownTimeLeft .. "," .. charges .. "," .. startTimeOffset .. "," .. duration openRaidLib.commHandler.SendCommData(dataToSend) diagnosticComm("SendPlayerCooldownUpdate| " .. dataToSend) --debug end --triggered when the lib receives a full list of cooldowns from another player in the raid --@data: table received from comm --@unitName: player name function openRaidLib.CooldownManager.OnReceiveUnitCooldowns(data, unitName) --unpack the table as a pairs table | the cooldown info uses 5 indexes local unpackedTable = openRaidLib.UnpackTable(data, 1, true, true, 5) --add the list of cooldowns openRaidLib.CooldownManager.AddUnitCooldownsList(unitName, unpackedTable) end openRaidLib.commHandler.RegisterComm(CONST_COMM_COOLDOWNFULLLIST_PREFIX, openRaidLib.CooldownManager.OnReceiveUnitCooldowns) -------------------------------------------------------------------------------------------------------------------------------- --> ~keystones --> API calls --return a table containing all information of units --format: [playerName-realm] = {information} function openRaidLib.GetAllKeystonesInfo() return openRaidLib.KeystoneInfoManager.GetAllKeystonesInfo() end --return a table containing information of a single unit function openRaidLib.GetKeystoneInfo(unitId) local unitName = GetUnitName(unitId, true) or unitId return openRaidLib.KeystoneInfoManager.GetKeystoneInfo(unitName) end --> manager constructor openRaidLib.KeystoneInfoManager = { --structure: --[playerName] = {level = 2, mapID = 222} KeystoneData = {}, } local keystoneTablePrototype = { level = 0, mapID = 0, challengeMapID = 0, } local updateKeystoneInfo = function(keystoneInfo) keystoneInfo.level = C_MythicPlus.GetOwnedKeystoneLevel() or 0 keystoneInfo.mapID = C_MythicPlus.GetOwnedKeystoneMapID() or 0 keystoneInfo.challengeMapID = C_MythicPlus.GetOwnedKeystoneChallengeMapID() or 0 end function openRaidLib.KeystoneInfoManager.GetAllKeystonesInfo() return openRaidLib.KeystoneInfoManager.KeystoneData end --get the keystone info table or create a new one if 'createNew' is true function openRaidLib.KeystoneInfoManager.GetKeystoneInfo(unitName, createNew) local keystoneInfo = openRaidLib.KeystoneInfoManager.KeystoneData[unitName] if (not keystoneInfo and createNew) then keystoneInfo = {} openRaidLib.TCopy(keystoneInfo, keystoneTablePrototype) openRaidLib.KeystoneInfoManager.KeystoneData[unitName] = keystoneInfo end updateKeystoneInfo(keystoneInfo) return keystoneInfo end -------------------------------------------------------------------------------------------------------------------------------- --> data --vintage cooldown tracker and interrupt tracker C_Timer.After(0.1, function() local vintageCDTrackerFrame = CreateFrame("frame") vintageCDTrackerFrame:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED") local allCooldownsFromLib = LIB_OPEN_RAID_COOLDOWNS_INFO local recentCastedSpells = {} vintageCDTrackerFrame:SetScript("OnEvent", function(self, event, ...) if (event == "UNIT_SPELLCAST_SUCCEEDED") then local unit, castGUID, spellId = ... local unitIsThePlayer = UnitIsUnit(unit, "player") if (not unitIsThePlayer) then local unitName = GetUnitName(unit, true) local hasLib = openRaidLib.CooldownManager.HasFullCooldownList[unitName] if (unitName and not hasLib) then local unitInGroup = UnitInParty(unit) or UnitInRaid(unit) if (unitInGroup) then local cooldownInfo = allCooldownsFromLib[spellId] if (cooldownInfo) then -- and not openRaidLib.GetUnitCooldown(unitName) --check for cast_success spam from channel spells local unitCastCooldown = recentCastedSpells[unitName] if (not unitCastCooldown) then unitCastCooldown = {} recentCastedSpells[unitName] = unitCastCooldown end if (not unitCastCooldown[spellId] or unitCastCooldown[spellId]+5 < GetTime()) then unitCastCooldown[spellId] = GetTime() --trigger a cooldown usage local duration = cooldownInfo.duration --time left, charges, startTimeOffset, duration openRaidLib.CooldownManager.CooldownSpellUpdate(unitName, spellId, duration, 0, 0, duration) local cooldownInfo = cooldownGetSpellInfo(unitName, spellId) local unitCooldownsTable = openRaidLib.GetUnitCooldowns(unitName) --trigger a public callback openRaidLib.publicCallback.TriggerCallback("CooldownUpdate", openRaidLib.GetUnitID(unitName), spellId, cooldownInfo, unitCooldownsTable, openRaidLib.CooldownManager.UnitData) end end end end end end end) end)