Files
coa-details/Libs/LibOpenRaid/LibOpenRaid.lua
T
Tercio Jose 1830359943 General Fixes, Backend Improvements, Library Updates.
Release Documentation:
- Classic now uses the same combat log reader as retail (Flamanis).
- Merged Rage of Fyr'alath spells (equara).
- Added Rogue Ambushes to merged spells (WillowGryph).
- The Remove Common Segments option now also removes segments trash between raid bosses.
- Fixed an issue where auras applied before combat start, such as Power Infusion and Prescience, which are counted towards the target, were not being accounted for.
- Added to Combat Class: classCombat:GetRunTimeNoDefault(). This returns the run time of the Mythic+ if available, nil otherwise.

Technical Notes:
- Classic now uses retail parser.
- Combat class now have the member: classCombat:GetRunTimeNoDefault(); Returns the run time of a M+ (after completed).
- The Utility class's buff scan at the start of combat has been improved, and the code has been cleaned. Also, the scan runs now on the next frame after combat start.
- Augmentation Evoker won't track auras from the combat start aura scan, if the player isn't in combat (example: a player in the group enters in combat).
- Remove tier bonus for Augmentation Evoker Ebon Might damage prediction and nerfed Close as Cluthmates to 10%.
- Segments Container's ResetDataByCombatType() now supports multiple combat types per classification.
- Code cleanup on Segments menu code to use the new Mythic+ functions added to Combat class.
- Mythic+ start detection produced errors if a WORLD_STATE_TIMER_START event triggered before the CHALLENGE_MODE_START event.
- Mythic+ finish code was bugging when 'time' returned by C_ChallengeMode.GetCompletionInfo() wasn't being checked again nil value.
- Rogue's Ambush ability and Rage of Fyr'alath spellIds added to override_spellId within the parser.
- Details! Framework updated.
- Open Raid Library updated.
2023-12-14 11:33:30 -03:00

2844 lines
124 KiB
Lua

--[=[
Please refer to the docs.txt within this file folder for a guide on how to use this library.
If you get lost on implementing the lib, be free to contact Tercio on Details! discord: https://discord.gg/AGSzAZX or email to terciob@gmail.com
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.
TODO:
- add into gear info how many tier set parts the player has
- raid lockouts normal-heroic-mythic
BUGS:
- after a /reload, it is not starting new tickers for spells under cooldown
--]=]
---@alias castername string
---@alias castspellid string
---@alias schedulename string
LIB_OPEN_RAID_CAN_LOAD = false
local versionString, revision, launchDate, gameVersion = GetBuildInfo()
local isExpansion_Dragonflight = function()
if (gameVersion >= 100000) then
return true
end
end
--don't load if it's not retail, emergencial patch due to classic and bcc stuff not transposed yet
if (WOW_PROJECT_ID ~= WOW_PROJECT_MAINLINE and not isExpansion_Dragonflight()) then
return
end
local major = "LibOpenRaid-1.0"
local CONST_LIB_VERSION = 115
if (LIB_OPEN_RAID_MAX_VERSION) then
if (CONST_LIB_VERSION <= LIB_OPEN_RAID_MAX_VERSION) then
return
end
end
--declare the library within the LibStub
local libStub = _G.LibStub
local openRaidLib = libStub:NewLibrary(major, CONST_LIB_VERSION)
if (not openRaidLib) then
return
end
openRaidLib.__version = CONST_LIB_VERSION
LIB_OPEN_RAID_CAN_LOAD = true
LIB_OPEN_RAID_MAX_VERSION = CONST_LIB_VERSION
--locals
local unpack = table.unpack or _G.unpack
openRaidLib.__errors = {} --/dump LibStub:GetLibrary("LibOpenRaid-1.0").__errors
--default values
openRaidLib.inGroup = false
openRaidLib.UnitIDCache = {}
local CONST_CVAR_TEMPCACHE = "LibOpenRaidTempCache"
local CONST_CVAR_TEMPCACHE_DEBUG = "LibOpenRaidTempCacheDebug"
--delay to request all data from other players
local CONST_REQUEST_ALL_DATA_COOLDOWN = 30
--delay to send all data to other players
local CONST_SEND_ALL_DATA_COOLDOWN = 30
--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
--show data received from other players
local CONST_DIAGNOSTIC_COMM_RECEIVED = 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_COOLDOWNCHANGES_PREFIX = "S"
local CONST_COMM_COOLDOWNREQUEST_PREFIX = "Z"
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_COMM_KEYSTONE_DATA_PREFIX = "K"
local CONST_COMM_KEYSTONE_DATAREQUEST_PREFIX = "J"
local CONST_COMM_SENDTO_PARTY = "0x1"
local CONST_COMM_SENDTO_RAID = "0x2"
local CONST_COMM_SENDTO_GUILD = "0x4"
local CONST_ONE_SECOND = 1.0
local CONST_TWO_SECONDS = 2.0
local CONST_THREE_SECONDS = 3.0
local CONST_SPECIALIZATION_VERSION_CLASSIC = 0
local CONST_SPECIALIZATION_VERSION_MODERN = 1
local CONST_COOLDOWN_CHECK_INTERVAL = CONST_THREE_SECONDS
local CONST_COOLDOWN_TIMELEFT_HAS_CHANGED = CONST_THREE_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
local CONST_COOLDOWN_INDEX_AURA_DURATION = 6
local CONST_COOLDOWN_INFO_SIZE = 6
local CONST_USE_DEFAULT_SCHEDULE_TIME = true
local GetContainerNumSlots = GetContainerNumSlots or C_Container.GetContainerNumSlots
local GetContainerItemID = GetContainerItemID or C_Container.GetContainerItemID
local GetContainerItemLink = GetContainerItemLink or C_Container.GetContainerItemLink
--from vanilla to cataclysm, the specID did not existed, hence its considered version 0
--for mists of pandaria and beyond it's version 1
local getSpecializationVersion = function()
if (gameVersion >= 50000) then
return CONST_SPECIALIZATION_VERSION_MODERN
else
return CONST_SPECIALIZATION_VERSION_CLASSIC
end
end
function openRaidLib.ShowDiagnosticErrors(value)
CONST_DIAGNOSTIC_ERRORS = value
end
--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 diagnosticFilter = nil
local diagnosticComm = function(msg, ...)
if (CONST_DIAGNOSTIC_COMM) then
if (diagnosticFilter) then
local lowerMessage = msg:lower()
if (lowerMessage:find(diagnosticFilter)) then
sendChatMessage("|cFFFF9922OpenRaidLib|r:", msg, ...)
--dumpt(msg)
end
else
sendChatMessage("|cFFFF9922OpenRaidLib|r:", msg, ...)
end
end
end
local diagnosticCommReceivedFilter = nil
openRaidLib.diagnosticCommReceived = function(msg, ...)
if (diagnosticCommReceivedFilter) then
local lowerMessage = msg:lower()
if (lowerMessage:find(diagnosticCommReceivedFilter)) then
sendChatMessage("|cFFFF9922OpenRaidLib|r:", msg, ...)
end
else
sendChatMessage("|cFFFF9922OpenRaidLib|r:", msg, ...)
end
end
openRaidLib.DeprecatedMessage = function(msg)
sendChatMessage("|cFFFF9922OpenRaidLib|r:", "|cFFFF5555" .. msg .. "|r")
end
local isTimewalkWoW = function()
local _, _, _, buildInfo = GetBuildInfo()
if (buildInfo < 40000) then
return true
end
end
local checkClientVersion = function(...)
for i = 1, select("#", ...) do
local clientVersion = select(i, ...)
if (clientVersion == "retail" and (WOW_PROJECT_ID == WOW_PROJECT_MAINLINE or isExpansion_Dragonflight())) then --retail
return true
elseif (clientVersion == "classic_era" and WOW_PROJECT_ID == WOW_PROJECT_CLASSIC) then --classic era (vanila)
return true
elseif (clientVersion == "bcc" and WOW_PROJECT_ID == WOW_PROJECT_BURNING_CRUSADE_CLASSIC) then --the burning crusade classic
return true
end
end
end
--------------------------------------------------------------------------------------------------------------------------------
--~internal cache
--use a console variable to create a flash cache to keep data while the game reload
--this is not a long term database as saved variables are and it get clean up often
C_CVar.RegisterCVar(CONST_CVAR_TEMPCACHE)
C_CVar.RegisterCVar(CONST_CVAR_TEMPCACHE_DEBUG)
--internal namespace
local tempCache = {
debugString = "",
}
tempCache.copyCache = function(t1, t2)
for key, value in pairs(t2) do
if (type(value) == "table") then
t1[key] = t1[key] or {}
tempCache.copyCache(t1[key], t2[key])
else
t1[key] = value
end
end
return t1
end
--use debug cvar to find issues that occurred during the logoff process
function openRaidLib.PrintTempCacheDebug()
local debugMessage = C_CVar.GetCVar(CONST_CVAR_TEMPCACHE_DEBUG)
sendChatMessage("|cFFFF9922OpenRaidLib|r Temp CVar Result:\n", debugMessage)
end
function tempCache.SaveDebugText()
C_CVar.SetCVar(CONST_CVAR_TEMPCACHE_DEBUG, "0")
--C_CVar.SetCVar(CONST_CVAR_TEMPCACHE_DEBUG, tempCache.debugString)
end
function tempCache.AddDebugText(text)
tempCache.debugString = tempCache.debugString .. date("%H:%M:%S") .. "| " .. text .. "\n"
end
function tempCache.SaveCacheOnCVar(data)
C_CVar.SetCVar(CONST_CVAR_TEMPCACHE, "0")
--C_CVar.SetCVar(CONST_CVAR_TEMPCACHE, data)
tempCache.AddDebugText("CVars Saved on saveCahceOnCVar(), Size: " .. #data)
end
function tempCache.RestoreData()
local data = C_CVar.GetCVar(CONST_CVAR_TEMPCACHE)
if (data and type(data) == "string" and string.len(data) > 2) then
local LibAceSerializer = LibStub:GetLibrary("AceSerializer-3.0", true)
if (LibAceSerializer) then
local okay, cacheInfo = LibAceSerializer:Deserialize(data)
if (okay) then
local age = cacheInfo.createdAt
--if the data is older than 5 minutes, much has been changed from the group and the data is out dated
if (age + (60 * 5) < time()) then
return
end
local unitsInfo = cacheInfo.unitsInfo
local cooldownsInfo = cacheInfo.cooldownsInfo
local gearInfo = cacheInfo.gearInfo
local okayUnitsInfo, unitsInfo = LibAceSerializer:Deserialize(unitsInfo)
local okayCooldownsInfo, cooldownsInfo = LibAceSerializer:Deserialize(cooldownsInfo)
local okayGearInfo, gearInfo = LibAceSerializer:Deserialize(gearInfo)
if (okayUnitsInfo and unitsInfo) then
openRaidLib.UnitInfoManager.UnitData = tempCache.copyCache(openRaidLib.UnitInfoManager.UnitData, unitsInfo)
else
tempCache.AddDebugText("invalid UnitInfo")
end
if (okayCooldownsInfo and cooldownsInfo) then
openRaidLib.CooldownManager.UnitData = tempCache.copyCache(openRaidLib.CooldownManager.UnitData, cooldownsInfo)
else
tempCache.AddDebugText("invalid CooldownsInfo")
end
if (okayGearInfo and gearInfo) then
openRaidLib.GearManager.UnitData = tempCache.copyCache(openRaidLib.GearManager.UnitData, gearInfo)
else
tempCache.AddDebugText("invalid GearInfo")
end
else
tempCache.AddDebugText("Deserialization not okay, reason: " .. cacheInfo)
end
else
tempCache.AddDebugText("LibAceSerializer not found")
end
else
if (not data) then
tempCache.AddDebugText("invalid temporary cache: getCVar returned nil")
elseif (type(data) ~= "string") then
tempCache.AddDebugText("invalid temporary cache: getCVar did not returned a string")
elseif (string.len(data) < 2) then
tempCache.AddDebugText("invalid temporary cache: data length lower than 2 bytes (first login?)")
else
tempCache.AddDebugText("invalid temporary cache: no reason found")
end
end
end
function tempCache.SaveData()
tempCache.AddDebugText("SaveData() called.")
local LibAceSerializer = LibStub:GetLibrary("AceSerializer-3.0", true)
if (LibAceSerializer) then
local allUnitsInfo = openRaidLib.UnitInfoManager.UnitData
local allUnitsCooldowns = openRaidLib.CooldownManager.UnitData
local allPlayersGear = openRaidLib.GearManager.UnitData
local cacheInfo = {
createdAt = time(),
}
local unitsInfoSerialized = LibAceSerializer:Serialize(allUnitsInfo)
local unitsCooldownsSerialized = LibAceSerializer:Serialize(allUnitsCooldowns)
local playersGearSerialized = LibAceSerializer:Serialize(allPlayersGear)
if (unitsInfoSerialized) then
cacheInfo.unitsInfo = unitsInfoSerialized
tempCache.AddDebugText("SaveData() units info serialized okay.")
else
tempCache.AddDebugText("SaveData() units info serialized failed.")
end
if (unitsCooldownsSerialized) then
cacheInfo.cooldownsInfo = unitsCooldownsSerialized
tempCache.AddDebugText("SaveData() cooldowns info serialized okay.")
else
tempCache.AddDebugText("SaveData() cooldowns info serialized failed.")
end
if (playersGearSerialized) then
cacheInfo.gearInfo = playersGearSerialized
tempCache.AddDebugText("SaveData() gear info serialized okay.")
else
tempCache.AddDebugText("SaveData() gear info serialized failed.")
end
local cacheInfoSerialized = LibAceSerializer:Serialize(cacheInfo)
tempCache.SaveCacheOnCVar(cacheInfoSerialized)
else
tempCache.AddDebugText("SaveData() AceSerializer not found.")
end
tempCache.SaveDebugText()
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
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)
--some users are reporting errors where 'data is nil'. Making some sanitization
if (not data) then
openRaidLib.DiagnosticError("Invalid data from player:", sender, "data:", text)
return
elseif (type(data) ~= "string") then
openRaidLib.DiagnosticError("Invalid data from player:", sender, "data:", text, "data type is:", type(data))
return
end
--get the first byte of the data, it indicates what type of data was transmited
local dataTypePrefix = data:match("^.")
if (not dataTypePrefix) then
openRaidLib.DiagnosticError("Invalid dataTypePrefix from player:", sender, "data:", data, "dataTypePrefix:", dataTypePrefix)
return
elseif (openRaidLib.commPrefixDeprecated[dataTypePrefix]) then
openRaidLib.DiagnosticError("Invalid dataTypePrefix from player:", sender, "data:", data, "dataTypePrefix:", dataTypePrefix)
return
end
--if this is isn't a keystone data comm, check if the lib can receive comms
if (dataTypePrefix ~= CONST_COMM_KEYSTONE_DATA_PREFIX and dataTypePrefix ~= CONST_COMM_KEYSTONE_DATAREQUEST_PREFIX) then
if (not openRaidLib.IsCommAllowed()) then
openRaidLib.DiagnosticError("comm not allowed.")
return
end
end
if (CONST_DIAGNOSTIC_COMM_RECEIVED) then
openRaidLib.diagnosticCommReceived(data)
end
--get the table with functions regitered for this type of data
local callbackTable = openRaidLib.commHandler.commCallback[dataTypePrefix]
if (not callbackTable) then
openRaidLib.DiagnosticError("Not callbackTable for dataTypePrefix:", dataTypePrefix, "from player:", sender, "data:", data)
return
end
--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_COOLDOWNCHANGES_PREFIX] = {}, --cooldowns got added or removed
[CONST_COMM_COOLDOWNREQUEST_PREFIX] = {}, --a unit requested an update on a spell
[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
[CONST_COMM_KEYSTONE_DATA_PREFIX] = {}, --received keystone data
[CONST_COMM_KEYSTONE_DATAREQUEST_PREFIX] = {}, --received a request to send keystone data
}
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
--@flags
--0x1: to party
--0x2: to raid
--0x4: to guild
local sendData = function(dataEncoded, channel)
local aceComm = LibStub:GetLibrary("AceComm-3.0", true)
if (aceComm) then
aceComm:SendCommMessage(CONST_COMM_PREFIX, dataEncoded, channel, nil, "ALERT")
else
C_ChatInfo.SendAddonMessage(CONST_COMM_PREFIX, dataEncoded, channel)
end
end
---@class commdata : table
---@field data string
---@field channel string
---@type {}[]
local commScheduler = {}
do
--if there's an old version that already registered the comm ticker, cancel it
if (LIB_OPEN_RAID_COMM_SCHEDULER) then
LIB_OPEN_RAID_COMM_SCHEDULER:Cancel()
end
--make the lib throttle the comms to one per second
local newTickerHandle = C_Timer.NewTicker(1, function()
for i = #commScheduler, 1, -1 do
local commData = commScheduler[i]
if (commData) then
sendData(commData.data, commData.channel)
table.remove(commScheduler, i)
return
end
end
end)
LIB_OPEN_RAID_COMM_SCHEDULER = newTickerHandle
end
function openRaidLib.commHandler.SendCommData(data, flags)
local LibDeflate = LibStub:GetLibrary("LibDeflate")
local dataCompressed = LibDeflate:CompressDeflate(data, {level = 9})
local dataEncoded = LibDeflate:EncodeForWoWAddonChannel(dataCompressed)
if (flags) then
if (bit.band(flags, CONST_COMM_SENDTO_PARTY)) then --send to party
if (IsInGroup() and not IsInRaid()) then
---@type commdata
local commData = {data = dataEncoded, channel = IsInGroup(LE_PARTY_CATEGORY_INSTANCE) and "INSTANCE_CHAT" or "PARTY"}
table.insert(commScheduler, commData)
end
end
if (bit.band(flags, CONST_COMM_SENDTO_RAID)) then --send to raid
if (IsInRaid()) then
local commData = {data = dataEncoded, channel = IsInRaid(LE_PARTY_CATEGORY_INSTANCE) and "INSTANCE_CHAT" or "RAID"}
table.insert(commScheduler, commData)
end
end
if (bit.band(flags, CONST_COMM_SENDTO_GUILD)) then --send to guild
if (IsInGuild()) then
local commData = {data = dataEncoded, channel = "GUILD"}
table.insert(commScheduler, commData)
end
end
else
if (IsInGroup() and not IsInRaid()) then --in party only
local commData = {data = dataEncoded, channel = IsInGroup(LE_PARTY_CATEGORY_INSTANCE) and "INSTANCE_CHAT" or "PARTY"}
table.insert(commScheduler, commData)
elseif (IsInRaid()) then
local commData = {data = dataEncoded, channel = IsInRaid(LE_PARTY_CATEGORY_INSTANCE) and "INSTANCE_CHAT" or "RAID"}
table.insert(commScheduler, commData)
end
end
end
--------------------------------------------------------------------------------------------------------------------------------
--~schedule ~timers
---@type table<schedulename, number>
local defaultScheduleCooldownTimeByScheduleName = {
["sendAllPlayerCooldownsFromTalentChange_Schedule"] = 2,
["talentChangedCallback_Schedule"] = 20,
["sendFullData_Schedule"] = 25,
["sendAllPlayerCooldowns_Schedule"] = 23,
["sendDurability_Schedule"] = 10,
["sendAllGearInfo_Schedule"] = 20,
["petStatus_Schedule"] = 8,
["updatePlayerData_Schedule"] = 22,
["sendTalent_Schedule"] = 20,
["sendPvPTalent_Schedule"] = 14,
["leaveCombat_Schedule"] = 18,
["encounterEndCooldownsCheck_Schedule"] = 24,
["sendKeystoneInfoToParty_Schedule"] = 7,
["sendKeystoneInfoToGuild_Schedule"] = 7,
}
openRaidLib.Schedules = {
registeredUniqueTimers = {}
}
--run a scheduled function with its payload
local triggerScheduledTick = function(tickerObject)
local payload = tickerObject.payload
local callback = tickerObject.callback
if (tickerObject.isUnique) then
local namespace = tickerObject.namespace
local scheduleName = tickerObject.scheduleName
openRaidLib.Schedules.CancelUniqueTimer(namespace, scheduleName)
end
--check if the player is still in group
if (not openRaidLib.IsInGroup()) then
return
end
local result, errortext = xpcall(callback, geterrorhandler(), unpack(payload))
if (not result) then
sendChatMessage("openRaidLib: error on scheduler:", tickerObject.scheduleName, tickerObject.stack)
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, ...)
--the the schedule uses a default time, get it from the table, if the timer already exists, quit
if (time == CONST_USE_DEFAULT_SCHEDULE_TIME) then
if (openRaidLib.Schedules.IsUniqueTimerOnCooldown(namespace, scheduleName)) then
return
end
time = defaultScheduleCooldownTimeByScheduleName[scheduleName]
else
openRaidLib.Schedules.CancelUniqueTimer(namespace, scheduleName)
end
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
--does timer by schedule name exists?
function openRaidLib.Schedules.IsUniqueTimerOnCooldown(namespace, scheduleName)
local registeredUniqueTimers = openRaidLib.Schedules.registeredUniqueTimers
local currentSchedule = registeredUniqueTimers[namespace] and registeredUniqueTimers[namespace][scheduleName]
if (currentSchedule) then
return true
end
return false
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:IsCancelled()) 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:IsCancelled()) 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",
"CooldownAdded",
"CooldownRemoved",
"UnitDeath",
"UnitAlive",
"GearListWipe",
"GearUpdate",
"GearDurabilityUpdate",
"UnitInfoUpdate",
"UnitInfoWipe",
"TalentUpdate",
"PvPTalentUpdate",
"KeystoneUpdate",
"KeystoneWipe",
}
--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 eventCallbacks = openRaidLib.publicCallback.events[event]
for i = 1, #eventCallbacks do
local thisCallback = eventCallbacks[i] --got a case where this was nil, which is kinda impossible? | event: CooldownUpdate
local addonObject = thisCallback[1] --670: attempt to index local 'thisCallback' (a nil value)
local functionName = thisCallback[2]
--[=[
eventCallbacks = {
1 = {}
}
(for index) = 2
(for limit) = 2
(for step) = 1
i = 2
thisCallback = nil
--]=]
--get the function from within the addon object
local functionToCallback = addonObject[functionName]
if (functionToCallback) then
--if this isn't a function, xpcall trigger an error
local okay, errorMessage = xpcall(functionToCallback, geterrorhandler(), ...)
if (not okay) then
sendChatMessage("error on callback for event:", event)
end
else
--the registered function wasn't found
end
end
end
function openRaidLib.RegisterCallback(addonObject, event, callbackMemberName)
--check of integrity
local passIntegrityTest = checkRegisterDataIntegrity(addonObject, event, callbackMemberName)
if (passIntegrityTest and type(passIntegrityTest) ~= "boolean") then
return passIntegrityTest
end
--register
tinsert(openRaidLib.publicCallback.events[event], {addonObject, callbackMemberName})
return true
end
function openRaidLib.UnregisterCallback(addonObject, event, callbackMemberName)
--check of integrity
local passIntegrityTest = checkRegisterDataIntegrity(addonObject, event, callbackMemberName)
if (passIntegrityTest and type(passIntegrityTest) ~= "boolean") then
return passIntegrityTest
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"] = {},
["mythicDungeonEnd"] = {},
["unitAuraRemoved"] = {},
}
openRaidLib.internalCallback.RegisterCallback = function(event, func)
tinsert(openRaidLib.internalCallback.events[event], func)
end
openRaidLib.internalCallback.UnRegisterCallback = function(event, func)
local eventCallbacks = openRaidLib.internalCallback.events[event]
for i = 1, #eventCallbacks do
if (eventCallbacks[i] == func) then
tremove(eventCallbacks, i)
break
end
end
end
function openRaidLib.internalCallback.TriggerEvent(event, ...)
local eventCallbacks = openRaidLib.internalCallback.events[event]
for i = 1, #eventCallbacks do
local functionToCallback = eventCallbacks[i]
functionToCallback(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 talentChangedCallback = function()
openRaidLib.internalCallback.TriggerEvent("talentUpdate")
end
local delayedTalentChange = function()
openRaidLib.Schedules.NewUniqueTimer(math.random(4, 8), talentChangedCallback, "TalentChangeEventGroup", "talentChangedCallback_Schedule")
end
local eventFunctions = {
--check if the player joined a group
["GROUP_ROSTER_UPDATE"] = function()
local bEventTriggered = false
if (openRaidLib.IsInGroup()) then
if (not openRaidLib.inGroup) then
openRaidLib.inGroup = true
openRaidLib.internalCallback.TriggerEvent("onEnterGroup")
bEventTriggered = true
end
else
if (openRaidLib.inGroup) then
openRaidLib.inGroup = false
openRaidLib.internalCallback.TriggerEvent("onLeaveGroup")
bEventTriggered = true
end
end
if (not bEventTriggered 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 = 5 + math.random() + math.random(1, 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()
--some spells has many different spellIds, get the default
spellId = LIB_OPEN_RAID_SPELL_DEFAULT_IDS[spellId] or spellId
--trigger internal callbacks
openRaidLib.internalCallback.TriggerEvent("playerCast", spellId, UnitIsUnit(unitId, "pet"))
end)
end,
["PLAYER_ENTERING_WORLD"] = function(...)
--has the selected character just loaded?
if (not openRaidLib.bHasEnteredWorld) then
--register events
openRaidLib.OnEnterWorldRegisterEvents()
--openRaidLib.AuraTracker.StartScanUnitAuras("player")
if (IsInGroup()) then
openRaidLib.RequestAllData()
openRaidLib.UpdateUnitIDCache()
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.bHasEnteredWorld = true
end
openRaidLib.internalCallback.TriggerEvent("onEnterWorld")
end,
["PLAYER_SPECIALIZATION_CHANGED"] = function(...)
delayedTalentChange()
end,
["PLAYER_TALENT_UPDATE"] = function(...)
delayedTalentChange()
end,
["TRAIT_CONFIG_UPDATED"] = function(...)
delayedTalentChange()
end,
["TRAIT_TREE_CURRENCY_INFO_UPDATED"] = function(...)
delayedTalentChange()
end,
--SPELLS_CHANGED
["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(1.1, 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,
["CHALLENGE_MODE_COMPLETED"] = function()
openRaidLib.internalCallback.TriggerEvent("mythicDungeonEnd")
end,
["PLAYER_LOGOUT"] = function()
tempCache.SaveData()
end,
}
openRaidLib.eventFunctions = eventFunctions
eventFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
eventFrame:SetScript("OnEvent", function(self, event, ...)
local eventCallbackFunc = eventFunctions[event]
eventCallbackFunc(...)
end)
--run when PLAYER_ENTERING_WORLD triggers, this avoid any attempt of getting information without the game has completed the load process
function openRaidLib.OnEnterWorldRegisterEvents()
eventFrame:RegisterEvent("GROUP_ROSTER_UPDATE")
eventFrame:RegisterUnitEvent("UNIT_SPELLCAST_SUCCEEDED", "player", "pet")
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_DEAD")
eventFrame:RegisterEvent("PLAYER_ALIVE")
eventFrame:RegisterEvent("PLAYER_UNGHOST")
eventFrame:RegisterEvent("PLAYER_LOGOUT")
if (checkClientVersion("retail")) then
eventFrame:RegisterEvent("PLAYER_TALENT_UPDATE")
eventFrame:RegisterEvent("PLAYER_PVP_TALENT_UPDATE")
eventFrame:RegisterEvent("ENCOUNTER_END")
eventFrame:RegisterEvent("CHALLENGE_MODE_START")
eventFrame:RegisterEvent("CHALLENGE_MODE_COMPLETED")
eventFrame:RegisterEvent("PLAYER_SPECIALIZATION_CHANGED")
eventFrame:RegisterEvent("TRAIT_TREE_CURRENCY_INFO_UPDATED")
eventFrame:RegisterEvent("TRAIT_CONFIG_UPDATED")
end
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
--C_Timer.After(2, function()
local playerGearInfo = openRaidLib.GearManager.GetPlayerFullGearInfo()
openRaidLib.GearManager.AddUnitGearList(unitName, unpack(playerGearInfo))
--end)
--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()
--the the player isn't in group, don't send the request
if (not IsInGroup()) then
return
end
openRaidLib.requestAllInfoCooldown = openRaidLib.requestAllInfoCooldown or 0
--check if the player can sent another request
if (openRaidLib.requestAllInfoCooldown > GetTime()) then
return
end
openRaidLib.commHandler.SendCommData(CONST_COMM_FULLINFO_PREFIX)
diagnosticComm("RequestAllInfo| " .. CONST_COMM_FULLINFO_PREFIX) --debug
openRaidLib.requestAllInfoCooldown = GetTime() + CONST_REQUEST_ALL_DATA_COOLDOWN
return true
end
--this function handles the request from another player to send all data
function openRaidLib.commHandler.SendFullData()
openRaidLib.mainControl.SendFullData()
end
openRaidLib.commHandler.RegisterComm(CONST_COMM_FULLINFO_PREFIX, function(data, sourceName)
openRaidLib.sendRequestedAllInfoCooldown = openRaidLib.sendRequestedAllInfoCooldown or 0
--check if there's some delay before sending the data
if (openRaidLib.sendRequestedAllInfoCooldown > GetTime()) then
--reschedule the function call
openRaidLib.Schedules.NewUniqueTimer(openRaidLib.sendRequestedAllInfoCooldown - GetTime(), openRaidLib.commHandler.SendFullData, "CommHandler", "sendFullData_Schedule")
return
end
openRaidLib.Schedules.NewUniqueTimer(math.random(1, 6), openRaidLib.commHandler.SendFullData, "CommHandler", "sendFullData_Schedule")
--set the delay for the next request
openRaidLib.sendRequestedAllInfoCooldown = GetTime() + CONST_SEND_ALL_DATA_COOLDOWN
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)
if (not GetSpecializationInfoByID) then --tbc hot fix
return
end
local specId, specName, specDescription, specIcon, role = GetSpecializationInfoByID(specId or 0)
local className, classString, classId = UnitClass(unitName)
--cold login bug where the player class info cannot be retrived by the player name, after a /reload it's all good
if (not className) then
local playerName = UnitName("player")
if (playerName == unitName) then
className, classString, classId = UnitClass("player")
end
end
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 playerInfo1 = tonumber(data[2])
local playerInfo2 = tonumber(data[3])
if (not playerInfo2 or playerInfo2 > 4) then --cleanup on 10.0
--invalid covanentId - different lib versions, it'll fix itself on dragonflight
return
end
local talentsSize = tonumber(data[4])
if (not talentsSize) then
return
end
local borrowedTalentsTableIndex = tonumber((talentsSize + 1) + 3) + 1 -- +3 for spec, playerInfo1 and playerInfo2 data | talentSizeIndex + talentSize | +1 for talents size
local borrowedTalentsSize = data[borrowedTalentsTableIndex]
local pvpTalentsTableIndex = 3 + 3 + talentsSize + borrowedTalentsSize -- +3 for spec, playerInfo1 and playerInfo2 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 borrowed talents data as a ipairs table
local borrowedTalentsTableUnpacked = openRaidLib.UnpackTable(data, borrowedTalentsTableIndex, false, false, borrowedTalentsSize)
--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, playerInfo1, playerInfo2, talentsTableUnpacked, borrowedTalentsTableUnpacked, {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, playerInfo1, playerInfo2, talentsTableUnpacked, borrowedTalentsTableUnpacked, 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] .. "," --player info 1
dataToSend = dataToSend .. playerInfo[3] .. "," --player info 2
dataToSend = dataToSend .. openRaidLib.PackTable(playerInfo[4]) .. "," --player talents class-spec
dataToSend = dataToSend .. openRaidLib.PackTable(playerInfo[5]) .. "," --player talents borrowed
dataToSend = dataToSend .. openRaidLib.PackTable(playerInfo[6]) .. "," --player talents pvp
--send the data
openRaidLib.commHandler.SendCommData(dataToSend)
diagnosticComm("SendGetUnitInfoFullData| " .. dataToSend) --debug
end
--player info format:
--index 1: number: specId
--index 2: number: tbd, depends on expansion
--index 3: number: tbd, depends on expansion
--index 4: talents 1: player talents: length vary depends on talent system
--index 5: talents 2: borrowed power talents: length vary from expansions
--index 6: talents 3: pvp talents
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
local specId = 0
if (getSpecializationVersion() == CONST_SPECIALIZATION_VERSION_MODERN) then
local selectedSpecialization = GetSpecialization()
if (selectedSpecialization) then
specId = GetSpecializationInfo(selectedSpecialization) or 0
end
end
playerInfo[1] = specId
--player information 1 (this can be different for each expansion)
playerInfo[2] = openRaidLib.UnitInfoManager.GetPlayerInfo1()
--player information 2 (this can be different for each expansion)
playerInfo[3] = openRaidLib.UnitInfoManager.GetPlayerInfo2()
--player class-spec talents
local talents = openRaidLib.UnitInfoManager.GetPlayerTalents()
playerInfo[4] = talents
--borrowed talents (conduits talents on shadowlands)
local borrowedTalents = openRaidLib.UnitInfoManager.GetPlayerBorrowedTalents()
playerInfo[5] = borrowedTalents
--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 playerName = UnitName("player")
local unitInfo = openRaidLib.UnitInfoManager.GetUnitInfo(playerName, 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()
--this talent update could be a specialization change, so we need to pass the specId as well
local playerName = UnitName("player")
local unitInfo = openRaidLib.UnitInfoManager.GetUnitInfo(playerName, true)
local specId = 0
if (getSpecializationVersion() == CONST_SPECIALIZATION_VERSION_MODERN) then
local selectedSpecialization = GetSpecialization()
if (selectedSpecialization) then
specId = GetSpecializationInfo(selectedSpecialization) or 0
end
end
openRaidLib.UnitInfoManager.SetUnitInfo(playerName, unitInfo, specId, nil, nil, openRaidLib.UnitInfoManager.GetPlayerTalents())
--trigger public callback event
openRaidLib.publicCallback.TriggerCallback("TalentUpdate", "player", unitInfo.talents, unitInfo, openRaidLib.UnitInfoManager.GetAllUnitsInfo())
--schedule send to the group
openRaidLib.Schedules.NewUniqueTimer(1 + math.random(0, 1), openRaidLib.UnitInfoManager.SendTalentUpdate, "UnitInfoManager", "sendTalent_Schedule")
end
openRaidLib.internalCallback.RegisterCallback("talentUpdate", openRaidLib.UnitInfoManager.OnPlayerTalentChanged)
function openRaidLib.UnitInfoManager.OnReceiveTalentsUpdate(data, unitName)
local talentsTableUnpacked = openRaidLib.UnpackTable(data, 1, false, false, 7) --this 7 should be a constant
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 playerName = UnitName("player")
local unitInfo = openRaidLib.UnitInfoManager.GetUnitInfo(playerName, true)
local pvpTalentsToSend = unitInfo.pvpTalents
local pvpTalentsString = openRaidLib.PackTable(pvpTalentsToSend)
local dataToSend = "" .. CONST_COMM_PLAYERINFO_PVPTALENTS_PREFIX .. ","
dataToSend = dataToSend .. pvpTalentsString
--send the data
openRaidLib.commHandler.SendCommData(dataToSend)
diagnosticComm("SendPvPTalentUpdateData| " .. dataToSend) --debug
end
function openRaidLib.UnitInfoManager.OnPlayerPvPTalentChanged()
--update the local player
local playerName = UnitName("player")
local unitInfo = openRaidLib.UnitInfoManager.GetUnitInfo(playerName, true)
openRaidLib.UnitInfoManager.SetUnitInfo(playerName, 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) --this 3 should be a constant
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 averageGearDurability, lowestDurability = openRaidLib.GearManager.GetPlayerGearDurability()
dataToSend = dataToSend .. averageGearDurability
--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, {}, {}, {}, 0, 0}
end
--item level
local itemLevel = openRaidLib.GearManager.GetPlayerItemLevel()
--repair status
local gearDurability, lowestItemDurability = openRaidLib.GearManager.GetPlayerGearDurability()
--get weapon enchant
local weaponEnchant, mainHandEnchantId, offHandEnchantId = openRaidLib.GearManager.GetPlayerWeaponEnchant()
--enchants and gems
local slotsWithoutGems, slotsWithoutEnchant = openRaidLib.GearManager.GetPlayerGemsAndEnchantInfo()
--full gear list {{slotId, gemAmount, itemLevel, itemLink}, {slotId, gemAmount, itemLevel, itemLink}, }
local equippedGearList = openRaidLib.GearManager.BuildPlayerEquipmentList()
--build the table with the gear information
local playerGearInfo = {}
playerGearInfo[#playerGearInfo+1] = itemLevel --[1] - one index
playerGearInfo[#playerGearInfo+1] = gearDurability --[2] - one index
playerGearInfo[#playerGearInfo+1] = weaponEnchant --[3] - one index
playerGearInfo[#playerGearInfo+1] = slotsWithoutEnchant --[4] - undefined
playerGearInfo[#playerGearInfo+1] = slotsWithoutGems --[5] - undefined
playerGearInfo[#playerGearInfo+1] = equippedGearList --[6] - undefined
playerGearInfo[#playerGearInfo+1] = mainHandEnchantId --[7]
playerGearInfo[#playerGearInfo+1] = offHandEnchantId --[8]
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, equippedGearList, mainHandEnchantId, offHandEnchantId)
local unitGearInfo = openRaidLib.GearManager.GetUnitGear(unitName, true)
unitGearInfo.ilevel = itemLevel
unitGearInfo.durability = durability
unitGearInfo.weaponEnchant = weaponEnchant
unitGearInfo.noGems = noGemsTable
unitGearInfo.noEnchants = noEnchantTable
unitGearInfo.mainHandEnchantId = mainHandEnchantId
unitGearInfo.offHandEnchantId = offHandEnchantId
--parse and replace the 'equippedGearList'
openRaidLib.GearManager.BuildEquipmentItemLinks(equippedGearList)
unitGearInfo.equippedGear = equippedGearList
local tierAmount = 0
for i = 1, #equippedGearList do
if (equippedGearList[i].isTier) then
tierAmount = tierAmount + 1
end
end
unitGearInfo.tierAmount = tierAmount
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]) --1 index
local durability = tonumber(data[2]) --1 index
local weaponEnchant = tonumber(data[3]) --1 index
local noEnchantTableSize = tonumber(data[4])
local noGemsTableIndex = tonumber(noEnchantTableSize + 5) --5 is the three first indexes, the enchant table size and +1 to jump to next index
local noGemsTableSize = data[noGemsTableIndex]
local equippedGearListIndex = tonumber(noEnchantTableSize + noGemsTableSize + 6) --6 is the same has the 5 but +1 index for the gems table size
local equippedGearListSize = data[equippedGearListIndex]
local mainHandEnchantId, offHandEnchantId = 0, 0
if equippedGearListSize then
local mainHandEnchantIdIndex = tonumber(noEnchantTableSize + noGemsTableSize + equippedGearListSize + 7)
mainHandEnchantId = tonumber(data[mainHandEnchantIdIndex]) or 0
local offHandEnchantIdIndex = tonumber(mainHandEnchantIdIndex + 1)
offHandEnchantId = tonumber(data[offHandEnchantIdIndex]) or 0
end
--unpack the enchant data as a ipairs table
local noEnchantTableUnpacked = openRaidLib.UnpackTable(data, 4, false, false, noEnchantTableSize)
--unpack the gems data as a ipairs table
local noGemsTableUnpacked = openRaidLib.UnpackTable(data, noGemsTableIndex, false, false, noGemsTableSize)
--unpack the full gear
local equippedGearListUnpacked = equippedGearListIndex and openRaidLib.UnpackTable(data, equippedGearListIndex, false, true, 4) or {}
--add to the list of gear information
openRaidLib.GearManager.AddUnitGearList(unitName, itemLevel, durability, weaponEnchant, noEnchantTableUnpacked, noGemsTableUnpacked, equippedGearListUnpacked, mainHandEnchantId, offHandEnchantId)
end
openRaidLib.commHandler.RegisterComm(CONST_COMM_GEARINFO_FULL_PREFIX, openRaidLib.GearManager.OnReceiveGearFullInfo)
--todo: on changing an item in the inventory, send an update only for the slot that got changed
function openRaidLib.GearManager.SendAllGearInfo()
--get gear information, gear info has 6 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
--[6] table with all gear from the player
--[7] int mainHandEnchantId
--[8] int offHandEnchantId
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
dataToSend = dataToSend .. openRaidLib.PackTableAndSubTables(playerGearInfo[6]) .. "," --full equipped equipment
dataToSend = dataToSend .. playerGearInfo[7] .. "," --main hand weapon enchant
dataToSend = dataToSend .. playerGearInfo[8] --off hand weapon enchant
--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, auraDuration = openRaidLib.CooldownManager.GetPlayerCooldownStatus(spellId)
local bUpdateLocally = false
--is the spell ready to use?
if (timeLeft == 0) then
--it's ready
openRaidLib.CooldownManager.SendPlayerCooldownUpdate(spellId, 0, charges, 0, 0, 0)
openRaidLib.CooldownManager.CooldownTickers[spellId] = nil
tickerObject:Cancel()
bUpdateLocally = 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, auraDuration)
tickerObject.cooldownTimeLeft = timeLeft
bUpdateLocally = true
end
end
if (bUpdateLocally) then
--get the cooldown time for this spell
local timeLeft, charges, startTimeOffset, duration, auraDuration = openRaidLib.CooldownManager.GetPlayerCooldownStatus(spellId) --return 5 values
--update the cooldown
openRaidLib.CooldownManager.CooldownSpellUpdate(playerName, spellId, timeLeft, charges, startTimeOffset, duration, auraDuration) --need 7 values
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:IsCancelled()) 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, auraDuration = 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 cooldownInfo = unitCooldownTable[spellId]
return cooldownInfo
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
--cooldownInfo: [1] timeLeft [2] charges [3] startOffset [4] duration [5] updateTime [6] auraDuration
function openRaidLib.CooldownManager.CooldownSpellUpdate(unitName, spellId, newTimeLeft, newCharges, startTimeOffset, duration, auraDuration)
--get the cooldown table where all cooldowns are stored for this unit
local unitCooldownTable = cooldownGetUnitTable(unitName)
--is this a cooldown info?
local cooldownInfo = unitCooldownTable[spellId] or {}
cooldownInfo[CONST_COOLDOWN_INDEX_TIMELEFT] = newTimeLeft
cooldownInfo[CONST_COOLDOWN_INDEX_CHARGES] = newCharges
cooldownInfo[CONST_COOLDOWN_INDEX_TIMEOFFSET] = startTimeOffset
cooldownInfo[CONST_COOLDOWN_INDEX_DURATION] = duration
cooldownInfo[CONST_COOLDOWN_INDEX_UPDATETIME] = GetTime()
cooldownInfo[CONST_COOLDOWN_INDEX_AURA_DURATION] = auraDuration
unitCooldownTable[spellId] = cooldownInfo
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
function openRaidLib.GetSpellFilters(spellId, defaultFilterOnly, customFiltersOnly)
return openRaidLib.CooldownManager.GetSpellFilters(spellId, defaultFilterOnly, customFiltersOnly)
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 openRaidLib.CooldownManager.GetCooldownInfoValues(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 openRaidLib.CooldownManager.GetCooldownInfoValues(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
local bIsReady = timeLeft <= 2
return bIsReady, 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, auraDuration
local unitCooldownsTable = openRaidLib.GetUnitCooldowns(unitId)
if (unitCooldownsTable) then
local cooldownInfo = unitCooldownsTable[spellId]
if (cooldownInfo) then
timeLeft, charges, timeOffset, duration, updateTime, auraDuration = openRaidLib.CooldownManager.GetCooldownInfoValues(cooldownInfo)
end
end
return calculatePercent(timeOffset, duration, updateTime, charges)
end
---return the values to be use on a progress bar or cooldown frame
---values returned: bIsReady, percent, timeLeft, charges, minValue, maxValue, currentValue, duration
---@param cooldownInfo table
---@return boolean bIsReady
---@return number percent
---@return number timeLeft
---@return number charges
---@return number minValue
---@return number maxValue
---@return number currentValue
---@return number duration
function openRaidLib.GetCooldownStatusFromCooldownInfo(cooldownInfo)
local timeLeft, charges, timeOffset, duration, updateTime, auraDuration = openRaidLib.CooldownManager.GetCooldownInfoValues(cooldownInfo)
if (not timeOffset) then
return false, 0, 0, 0, 0, 0, 0, 0
end
return calculatePercent(timeOffset, duration, updateTime, charges)
end
--internals
function openRaidLib.CooldownManager.GetCooldownInfoValues(cooldownInfo)
local timeLeft, charges, timeOffset, duration, updateTime, auraDuration = unpack(cooldownInfo)
return timeLeft, charges, timeOffset, duration, updateTime, auraDuration
end
function openRaidLib.CooldownManager.OnPlayerCast(event, spellId, isPlayerPet) --~cast
--player casted a spell, check if the spell is registered as cooldown
--issue: pet spells isn't in this table yet, might mess with pet interrupts
if (LIB_OPEN_RAID_PLAYERCOOLDOWNS[spellId]) then --check if the casted spell is a cooldown the player has available
local playerName = UnitName("player")
--get the cooldown time for this spell
local timeLeft, charges, startTimeOffset, duration, auraDuration = openRaidLib.CooldownManager.GetPlayerCooldownStatus(spellId) --return 5 values
--check for shared cooldown time - warning: this block of code is duplicated at "openRaidLib.commHandler.RegisterComm(CONST_COMM_COOLDOWNUPDATE_PREFIX"
local spellData = LIB_OPEN_RAID_COOLDOWNS_INFO[spellId]
local sharedCooldownId = spellData and spellData.shareid
if (sharedCooldownId) then
local spellsWithSharedCooldown = LIB_OPEN_RAID_COOLDOWNS_SHARED_ID[sharedCooldownId]
for thisSpellId in pairs(spellsWithSharedCooldown) do
--don't run for the spell that triggered the shared cooldown
if (thisSpellId ~= spellId) then
openRaidLib.CooldownManager.CooldownSpellUpdate(playerName, thisSpellId, timeLeft, charges, startTimeOffset, duration, auraDuration)
local cooldownInfo = cooldownGetSpellInfo(playerName, thisSpellId)
local unitCooldownTable = openRaidLib.GetUnitCooldowns(playerName)
--trigger a public callback
openRaidLib.publicCallback.TriggerCallback("CooldownUpdate", openRaidLib.GetUnitID(playerName), thisSpellId, cooldownInfo, unitCooldownTable, openRaidLib.CooldownManager.UnitData)
end
end
end
--update the cooldown
openRaidLib.CooldownManager.CooldownSpellUpdate(playerName, spellId, timeLeft, charges, startTimeOffset, duration, auraDuration) --receive 7 values
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, auraDuration)
--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
--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", "sendAllPlayerCooldownsFromTalentChange_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()
openRaidLib.CooldownManager.CheckCooldownChanges()
end
function openRaidLib.CooldownManager.OnAuraRemoved(event, unitId, spellId)
--under development ~aura
local timeLeft, charges, startTimeOffset, duration, auraDuration = openRaidLib.CooldownManager.GetPlayerCooldownStatus(spellId)
--do need to update?
if (not timeLeft or timeLeft < 1 or not auraDuration or auraDuration < 1) then
return
end
local latencyCompensation = 1
if (spellId) then
if (auraDuration > latencyCompensation) then
--cooldown aura got removed before expiration
local newAuraDuration = 0
local unitName = GetUnitName(unitId, true) or unitId
openRaidLib.CooldownManager.CooldownSpellUpdate(unitName, spellId, timeLeft, charges, startTimeOffset, duration, newAuraDuration)
--trigger a public callback
local playerCooldownTable = openRaidLib.GetUnitCooldowns(unitName)
local cooldownInfo = cooldownGetSpellInfo(unitName, spellId)
openRaidLib.publicCallback.TriggerCallback("CooldownUpdate", "player", spellId, cooldownInfo, playerCooldownTable, openRaidLib.CooldownManager.UnitData)
--send to comm
openRaidLib.CooldownManager.SendPlayerCooldownUpdate(spellId, timeLeft, charges, startTimeOffset, duration, newAuraDuration)
end
end
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)
openRaidLib.internalCallback.RegisterCallback("unitAuraRemoved", openRaidLib.CooldownManager.OnAuraRemoved)
--send a list through comm with cooldowns added or removed
function openRaidLib.CooldownManager.CheckCooldownChanges()
--important: CheckForSpellsAdeedOrRemoved() already change the cooldowns on the player locally
local spellsAdded, spellsRemoved = openRaidLib.CooldownManager.CheckForSpellsAdeedOrRemoved()
--add a prefix to make things easier during unpack
if (#spellsAdded > 0) then
tinsert(spellsAdded, 1, "A")
end
--insert the spells that has been removed at the end of the spells added table and pack the table
if (#spellsRemoved > 0) then
spellsAdded[#spellsAdded+1] = "R"
for _, spellId in ipairs(spellsRemoved) do
spellsAdded[#spellsAdded+1] = spellId
end
end
--send a comm if has any changes
if (#spellsAdded > 0) then
--pack
local playerCooldownChangesString = openRaidLib.PackTable(spellsAdded)
local dataToSend = CONST_COMM_COOLDOWNCHANGES_PREFIX .. ","
dataToSend = dataToSend .. playerCooldownChangesString
openRaidLib.commHandler.SendCommData(dataToSend)
diagnosticComm("CheckCooldownChanges| " .. dataToSend) --debug
end
end
function openRaidLib.CooldownManager.OnReceiveUnitCooldownChanges(data, unitName)
local currentCooldowns = openRaidLib.CooldownManager.UnitData[unitName]
--if does not have the full list of cooldowns of this unit, ignore cooldown add/remove comms
if (not currentCooldowns or not openRaidLib.CooldownManager.HasFullCooldownList[unitName]) then
return
end
--create a table to be ready to unpack
local addedCooldowns = {}
local removedCooldowns = {}
local bIsCooldownAdded = false
local bIsCooldownRemoved = false
--the letters A and R separate cooldowns added and cooldowns removed
for i = 1, #data do
local thisData = data[i]
if (thisData == "A") then
bIsCooldownAdded = true
elseif (thisData == "R") then
bIsCooldownAdded = false
bIsCooldownRemoved = true
end
if (bIsCooldownAdded) then
thisData = tonumber(thisData)
if (thisData) then
addedCooldowns[#addedCooldowns+1] = thisData
end
elseif(bIsCooldownRemoved) then
local spellId = tonumber(thisData)
if (spellId) then
removedCooldowns[#removedCooldowns+1] = spellId
end
end
end
if (#addedCooldowns > 0) then
tinsert(addedCooldowns, 1, #addedCooldowns) --amount of indexes for UnpackTable()
local cooldownsAddedUnpacked = openRaidLib.UnpackTable(addedCooldowns, 1, true, true, CONST_COOLDOWN_INFO_SIZE)
for spellId, cooldownInfo in pairs(cooldownsAddedUnpacked) do
--add the spell into the list of cooldowns of this unit
local timeLeft, charges, timeOffset, duration, updateTime, auraDuration = openRaidLib.CooldownManager.GetCooldownInfoValues(cooldownInfo)
openRaidLib.CooldownManager.CooldownSpellUpdate(unitName, spellId, timeLeft, charges, timeOffset, duration, auraDuration)
--mark the filter cache of this unit as dirt
openRaidLib.CooldownManager.NeedRebuildFilters[unitName] = true
--trigger public callback
openRaidLib.publicCallback.TriggerCallback("CooldownAdded", openRaidLib.GetUnitID(unitName), spellId, cooldownInfo, openRaidLib.GetUnitCooldowns(unitName), openRaidLib.CooldownManager.UnitData)
end
end
if (#removedCooldowns > 0) then
for _, spellId in ipairs(removedCooldowns) do
--remove the spell from this unit cooldown list
currentCooldowns[spellId] = nil
--mark the filter cache of this unit as dirt
openRaidLib.CooldownManager.NeedRebuildFilters[unitName] = true
--trigger public callback
openRaidLib.publicCallback.TriggerCallback("CooldownRemoved", openRaidLib.GetUnitID(unitName), spellId, openRaidLib.GetUnitCooldowns(unitName), openRaidLib.CooldownManager.UnitData)
end
end
end
openRaidLib.commHandler.RegisterComm(CONST_COMM_COOLDOWNCHANGES_PREFIX, openRaidLib.CooldownManager.OnReceiveUnitCooldownChanges)
--compare the current list of spells of the player with a new spell list generated
--add or remove spells from the player cooldown list
--return two tables, the first has added spells and is a index table ready to pack and send to comm
--the second table is a index table with a list of spells that has been removed, also ready to pack
function openRaidLib.CooldownManager.CheckForSpellsAdeedOrRemoved()
local playerName = UnitName("player")
local currentCooldowns = openRaidLib.CooldownManager.UnitData[playerName]
local _, newCooldownList = openRaidLib.CooldownManager.GetPlayerCooldownList()
local spellsAdded, spellsRemoved = {}, {}
for spellId, cooldownInfo in pairs(newCooldownList) do
if (not currentCooldowns[spellId]) then
--a spell has been added
local timeLeft, charges, timeOffset, duration, updateTime, auraDuration = openRaidLib.CooldownManager.GetCooldownInfoValues(cooldownInfo)
openRaidLib.CooldownManager.CooldownSpellUpdate(playerName, spellId, timeLeft, charges, timeOffset, duration, auraDuration)
local timeLeft, charges, startTimeOffset, duration, auraDuration = openRaidLib.CooldownManager.GetPlayerCooldownStatus(spellId) --return 5 values
spellsAdded[#spellsAdded+1] = spellId
spellsAdded[#spellsAdded+1] = timeLeft
spellsAdded[#spellsAdded+1] = charges
spellsAdded[#spellsAdded+1] = startTimeOffset
spellsAdded[#spellsAdded+1] = duration
spellsAdded[#spellsAdded+1] = auraDuration
--mark the filter cache of this unit as dirt
openRaidLib.CooldownManager.NeedRebuildFilters[playerName] = true
openRaidLib.publicCallback.TriggerCallback("CooldownAdded", "player", spellId, cooldownInfo, openRaidLib.GetUnitCooldowns("player"), openRaidLib.CooldownManager.UnitData)
end
end
for spellId in pairs(currentCooldowns) do
if (not newCooldownList[spellId]) then
--a spell has been removed
currentCooldowns[spellId] = nil
spellsRemoved[#spellsRemoved+1] = spellId
--mark the filter cache of this unit as dirt
openRaidLib.CooldownManager.NeedRebuildFilters[playerName] = true
openRaidLib.publicCallback.TriggerCallback("CooldownRemoved", "player", spellId, openRaidLib.GetUnitCooldowns("player"), openRaidLib.CooldownManager.UnitData)
end
end
return spellsAdded, spellsRemoved
end
--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])
local auraDuration = tonumber(dataAsArray[6])
--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")
elseif (not auraDuration) then
return openRaidLib.DiagnosticError("CooldownManager|comm received|auraDuration is invalid")
end
--check for shared cooldown time
local spellData = LIB_OPEN_RAID_COOLDOWNS_INFO[spellId] --warning this block of code is duplicated at warning: this block of code is duplicated at "openRaidLib.CooldownManager.OnPlayerCast"
local sharedCooldownId = spellData and spellData.shareid
if (sharedCooldownId) then
local spellsWithSharedCooldown = LIB_OPEN_RAID_COOLDOWNS_SHARED_ID[sharedCooldownId]
for thisSpellId in pairs(spellsWithSharedCooldown) do
--don't run for the spell that triggered the shared cooldown
if (thisSpellId ~= spellId) then
openRaidLib.CooldownManager.CooldownSpellUpdate(unitName, thisSpellId, cooldownTimer, charges, startTime, duration, auraDuration)
local cooldownInfo = cooldownGetSpellInfo(unitName, thisSpellId)
local unitCooldownTable = openRaidLib.GetUnitCooldowns(unitName)
--trigger a public callback
openRaidLib.publicCallback.TriggerCallback("CooldownUpdate", openRaidLib.GetUnitID(unitName), thisSpellId, cooldownInfo, unitCooldownTable, openRaidLib.CooldownManager.UnitData)
end
end
end
--update
--unitName, spellId, cooldownTimer, charges, startTime, duration, auraDuration
openRaidLib.CooldownManager.CooldownSpellUpdate(unitName, spellId, cooldownTimer, charges, startTime, duration, auraDuration)
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, auraDuration)
local dataToSend = "" .. CONST_COMM_COOLDOWNUPDATE_PREFIX .. "," .. spellId .. "," .. cooldownTimeLeft .. "," .. charges .. "," .. startTimeOffset .. "," .. duration .. "," .. auraDuration
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
local unpackedTable = openRaidLib.UnpackTable(data, 1, true, true, CONST_COOLDOWN_INFO_SIZE)
--[=[ --debug for data received from Evokers
local _, class = UnitClass(unitName)
if (class == "EVOKER") then
print(unitName)
dumpt(unpackedTable)
end
--]=]
--add the list of cooldowns
openRaidLib.CooldownManager.AddUnitCooldownsList(unitName, unpackedTable)
end
openRaidLib.commHandler.RegisterComm(CONST_COMM_COOLDOWNFULLLIST_PREFIX, openRaidLib.CooldownManager.OnReceiveUnitCooldowns)
--send a comm requesting other units in the raid to send an update on the requested spell
--any unit in the raid that has this cooldown should send a CONST_COMM_COOLDOWNUPDATE_PREFIX
--@spellId: spellId to query
function openRaidLib.CooldownManager.RequestCooldownInfo(spellId)
local dataToSend = "" .. CONST_COMM_COOLDOWNREQUEST_PREFIX .. "," .. spellId
openRaidLib.commHandler.SendCommData(dataToSend)
diagnosticComm("RequestCooldownInfo| " .. dataToSend) --debug
end
function openRaidLib.RequestCooldownInfo(spellId) --api alias
return openRaidLib.CooldownManager.RequestCooldownInfo(spellId)
end
function openRaidLib.CooldownManager.OnReceiveRequestForCooldownInfoUpdate(data, unitName)
local spellId = tonumber(data[1])
--check if this unit has this cooldown in its list of cooldowns
if (not cooldownGetSpellInfo(UnitName("player"), spellId)) then
return
end
--get the cooldown time for this spell
local timeLeft, charges, startTimeOffset, duration, auraDuration = openRaidLib.CooldownManager.GetPlayerCooldownStatus(spellId)
openRaidLib.CooldownManager.SendPlayerCooldownUpdate(spellId, timeLeft, charges, startTimeOffset, duration, auraDuration)
end
openRaidLib.commHandler.RegisterComm(CONST_COMM_COOLDOWNREQUEST_PREFIX, openRaidLib.CooldownManager.OnReceiveRequestForCooldownInfoUpdate)
--------------------------------------------------------------------------------------------------------------------------------
--~keystones
--public callback does not check if the keystone has changed from the previous callback
--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
function openRaidLib.RequestKeystoneDataFromGuild()
if (IsInGuild()) then
local dataToSend = "" .. CONST_COMM_KEYSTONE_DATAREQUEST_PREFIX
openRaidLib.commHandler.SendCommData(dataToSend, 0x4)
diagnosticComm("RequestKeystoneDataFromGuild| " .. dataToSend) --debug
return true
else
return false
end
end
function openRaidLib.RequestKeystoneDataFromParty()
if (IsInGroup() and not IsInRaid()) then
local dataToSend = "" .. CONST_COMM_KEYSTONE_DATAREQUEST_PREFIX
openRaidLib.commHandler.SendCommData(dataToSend, 0x1)
diagnosticComm("RequestKeystoneDataFromParty| " .. dataToSend) --debug
return true
else
return false
end
end
function openRaidLib.RequestKeystoneDataFromRaid()
if (IsInRaid()) then
local dataToSend = "" .. CONST_COMM_KEYSTONE_DATAREQUEST_PREFIX
openRaidLib.commHandler.SendCommData(dataToSend, 0x2)
diagnosticComm("RequestKeystoneDataFromRaid| " .. dataToSend) --debug
return true
else
return false
end
end
function openRaidLib.WipeKeystoneData()
wipe(openRaidLib.KeystoneInfoManager.KeystoneData)
--trigger public callback
openRaidLib.publicCallback.TriggerCallback("KeystoneWipe", openRaidLib.KeystoneInfoManager.KeystoneData)
--keystones are only available on retail
if (not checkClientVersion("retail")) then
return
end
--generate keystone info for the player
local unitName = UnitName("player")
local keystoneInfo = openRaidLib.KeystoneInfoManager.GetKeystoneInfo(unitName, true)
openRaidLib.KeystoneInfoManager.UpdatePlayerKeystoneInfo(keystoneInfo)
openRaidLib.publicCallback.TriggerCallback("KeystoneUpdate", unitName, keystoneInfo, openRaidLib.KeystoneInfoManager.KeystoneData)
return true
end
--manager constructor
openRaidLib.KeystoneInfoManager = {
--structure:
--[playerName] = {level = 2, mapID = 222}
KeystoneData = {},
}
local keystoneTablePrototype = {
level = 0,
mapID = 0,
challengeMapID = 0,
classID = 0,
rating = 0,
mythicPlusMapID = 0,
}
--search the player backpack to find a mythic keystone
--with the keystone object, it'll attempt to get the mythicPlusMapID to be used with C_ChallengeMode.GetMapUIInfo(mythicPlusMapID)
--ATM we are obligated to do this due to C_MythicPlus.GetOwnedKeystoneMapID() return the same mapID for the two Tazavesh dungeons
local getMythicPlusMapID = function()
for backpackId = 0, 4 do
for slotId = 1, GetContainerNumSlots(backpackId) do
local itemId = GetContainerItemID(backpackId, slotId)
if (itemId == LIB_OPEN_RAID_MYTHICKEYSTONE_ITEMID) then
local itemLink = GetContainerItemLink(backpackId, slotId)
local destroyedItemLink = itemLink:gsub("|", "")
local color, itemID, mythicPlusMapID = strsplit(":", destroyedItemLink)
return tonumber(mythicPlusMapID)
end
end
end
end
function openRaidLib.KeystoneInfoManager.UpdatePlayerKeystoneInfo(keystoneInfo)
keystoneInfo.level = C_MythicPlus.GetOwnedKeystoneLevel() or 0
keystoneInfo.mapID = C_MythicPlus.GetOwnedKeystoneMapID() or 0
keystoneInfo.mythicPlusMapID = getMythicPlusMapID() or 0
keystoneInfo.challengeMapID = C_MythicPlus.GetOwnedKeystoneChallengeMapID() or 0
local _, _, playerClassID = UnitClass("player")
keystoneInfo.classID = playerClassID
local ratingSummary = C_PlayerInfo.GetPlayerMythicPlusRatingSummary("player")
keystoneInfo.rating = ratingSummary and ratingSummary.currentSeasonScore 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
return keystoneInfo
end
local getKeystoneInfoToComm = function()
local playerName = UnitName("player")
local keystoneInfo = openRaidLib.KeystoneInfoManager.GetKeystoneInfo(playerName, true)
openRaidLib.KeystoneInfoManager.UpdatePlayerKeystoneInfo(keystoneInfo)
local dataToSend = CONST_COMM_KEYSTONE_DATA_PREFIX .. "," .. keystoneInfo.level .. "," .. keystoneInfo.mapID .. "," .. keystoneInfo.challengeMapID .. "," .. keystoneInfo.classID .. "," .. keystoneInfo.rating .. "," .. keystoneInfo.mythicPlusMapID
return dataToSend
end
function openRaidLib.KeystoneInfoManager.SendPlayerKeystoneInfoToParty()
local dataToSend = getKeystoneInfoToComm()
openRaidLib.commHandler.SendCommData(dataToSend, CONST_COMM_SENDTO_PARTY)
diagnosticComm("SendPlayerKeystoneInfoToParty| " .. dataToSend) --debug
end
function openRaidLib.KeystoneInfoManager.SendPlayerKeystoneInfoToGuild()
local dataToSend = getKeystoneInfoToComm()
openRaidLib.commHandler.SendCommData(dataToSend, CONST_COMM_SENDTO_GUILD)
diagnosticComm("SendPlayerKeystoneInfoToGuild| " .. dataToSend) --debug
end
--when a request data is received, only send the data to party and guild
--sending stuff to raid need to be called my the application with 'openRaidLib.RequestKeystoneDataFromRaid()'
function openRaidLib.KeystoneInfoManager.OnReceiveRequestData()
if (not checkClientVersion("retail")) then
return
end
--update the information about the key stone the player has
local keystoneInfo = openRaidLib.KeystoneInfoManager.GetKeystoneInfo(UnitName("player"), true)
openRaidLib.KeystoneInfoManager.UpdatePlayerKeystoneInfo(keystoneInfo)
if (IsInGroup() and not IsInRaid()) then
openRaidLib.Schedules.NewUniqueTimer(0.1, openRaidLib.KeystoneInfoManager.SendPlayerKeystoneInfoToParty, "KeystoneInfoManager", "sendKeystoneInfoToParty_Schedule")
end
if (IsInGuild()) then
openRaidLib.Schedules.NewUniqueTimer(math.random(0, 3) + math.random(), openRaidLib.KeystoneInfoManager.SendPlayerKeystoneInfoToGuild, "KeystoneInfoManager", "sendKeystoneInfoToGuild_Schedule")
end
end
openRaidLib.commHandler.RegisterComm(CONST_COMM_KEYSTONE_DATAREQUEST_PREFIX, openRaidLib.KeystoneInfoManager.OnReceiveRequestData)
function openRaidLib.KeystoneInfoManager.OnReceiveKeystoneData(data, unitName)
if (not checkClientVersion("retail")) then
return
end
local level = tonumber(data[1])
local mapID = tonumber(data[2])
local challengeMapID = tonumber(data[3])
local classID = tonumber(data[4])
local rating = tonumber(data[5])
local mythicPlusMapID = tonumber(data[6])
if (level and mapID and challengeMapID and classID and rating and mythicPlusMapID) then
local keystoneInfo = openRaidLib.KeystoneInfoManager.GetKeystoneInfo(unitName, true)
keystoneInfo.level = level
keystoneInfo.mapID = mapID
keystoneInfo.mythicPlusMapID = mythicPlusMapID
keystoneInfo.challengeMapID = challengeMapID
keystoneInfo.classID = classID
keystoneInfo.rating = rating
--trigger public callback
openRaidLib.publicCallback.TriggerCallback("KeystoneUpdate", unitName, keystoneInfo, openRaidLib.KeystoneInfoManager.KeystoneData)
end
end
openRaidLib.commHandler.RegisterComm(CONST_COMM_KEYSTONE_DATA_PREFIX, openRaidLib.KeystoneInfoManager.OnReceiveKeystoneData)
--on entering a group, send keystone information for the party
function openRaidLib.KeystoneInfoManager.OnPlayerEnterGroup()
--keystones are only available on retail
if (not checkClientVersion("retail")) then
return
end
if (IsInGroup() and not IsInRaid()) then
--update the information about the key stone the player has
local keystoneInfo = openRaidLib.KeystoneInfoManager.GetKeystoneInfo(UnitName("player"), true)
openRaidLib.KeystoneInfoManager.UpdatePlayerKeystoneInfo(keystoneInfo)
--send to the group which keystone the player has
openRaidLib.Schedules.NewUniqueTimer(1 + math.random(0, 2) + math.random(), openRaidLib.KeystoneInfoManager.SendPlayerKeystoneInfoToParty, "KeystoneInfoManager", "sendKeystoneInfoToParty_Schedule")
end
end
function openRaidLib.KeystoneInfoManager.OnPlayerEnterWorld()
--keystones are only available on retail
if (not checkClientVersion("retail")) then
return
end
--hack: on received data send data to party and guild
openRaidLib.KeystoneInfoManager.OnReceiveRequestData()
--trigger public callback
local unitName = UnitName("player")
local keystoneInfo = openRaidLib.KeystoneInfoManager.GetKeystoneInfo(unitName, true)
openRaidLib.KeystoneInfoManager.UpdatePlayerKeystoneInfo(keystoneInfo)
openRaidLib.publicCallback.TriggerCallback("KeystoneUpdate", unitName, keystoneInfo, openRaidLib.KeystoneInfoManager.KeystoneData)
end
function openRaidLib.KeystoneInfoManager.OnMythicDungeonFinished()
--keystones are only available on retail
if (not checkClientVersion("retail")) then
return
end
--hack: on received data send data to party and guild
openRaidLib.KeystoneInfoManager.OnReceiveRequestData()
--trigger public callback
local unitName = UnitName("player")
local keystoneInfo = openRaidLib.KeystoneInfoManager.GetKeystoneInfo(unitName, true)
openRaidLib.KeystoneInfoManager.UpdatePlayerKeystoneInfo(keystoneInfo)
openRaidLib.publicCallback.TriggerCallback("KeystoneUpdate", unitName, keystoneInfo, openRaidLib.KeystoneInfoManager.KeystoneData)
end
openRaidLib.internalCallback.RegisterCallback("onEnterWorld", openRaidLib.KeystoneInfoManager.OnPlayerEnterWorld)
openRaidLib.internalCallback.RegisterCallback("onEnterGroup", openRaidLib.KeystoneInfoManager.OnPlayerEnterGroup)
openRaidLib.internalCallback.RegisterCallback("mythicDungeonEnd", openRaidLib.KeystoneInfoManager.OnMythicDungeonFinished)
--------------------------------------------------------------------------------------------------------------------------------
--data
local createLocalCooldownTracker = function()
local cdTrackerFrame = CreateFrame("frame")
cdTrackerFrame:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED")
local allCooldownsFromLib = LIB_OPEN_RAID_COOLDOWNS_INFO
---@type table<castername, table<castspellid, number>>
local recentCastedSpells = {}
cdTrackerFrame:SetScript("OnEvent", function(self, event, ...)
if (event == "UNIT_SPELLCAST_SUCCEEDED") then
local unitId, castGUID, spellId = ...
--don't track spells casted by the player
local bUnitIsThePlayer = UnitIsUnit(unitId, "player")
if (not bUnitIsThePlayer) then
--get the caster name and check if it's a unit in the group
local casterName = GetUnitName(unitId, true)
if (casterName) then
local unitInGroup = UnitInParty(unitId) or UnitInRaid(unitId)
if (unitInGroup) then
--check if the library has the spell in the list of cooldowns
local spellData = allCooldownsFromLib[spellId]
--check for overwrite spell ids
if (spellData) then
--check for cast_success spam from channel spells using a cooldown timer
local unitCastCooldown = recentCastedSpells[casterName]
if (not unitCastCooldown) then
unitCastCooldown = {}
recentCastedSpells[casterName] = unitCastCooldown
end
--don't register the cooldown if the spell was casted recently
if (not unitCastCooldown[spellId] or unitCastCooldown[spellId]+5 < GetTime()) then
unitCastCooldown[spellId] = GetTime()
--local auraName, texture, count, auraType, auraDuration, expirationTime = openRaidLib.AuraTracker.FindBuffDurationByUnitName(casterName, casterName, spellId)
local auraDuration = openRaidLib.CooldownManager.GetSpellBuffDuration(spellId, unitId)
--trigger a cooldown usage
local timeLeft = spellData.cooldown
local duration = spellData.duration or 0
local newCharges = 0
local startTimeOffset = 0
local buffDuration = auraDuration or spellData.duration or 0
openRaidLib.CooldownManager.CooldownSpellUpdate(casterName, spellId, timeLeft, newCharges, startTimeOffset, duration, buffDuration)
local cooldownInfo = cooldownGetSpellInfo(casterName, spellId)
local unitCooldownsTable = openRaidLib.GetUnitCooldowns(casterName)
--trigger a public callback
openRaidLib.publicCallback.TriggerCallback("CooldownUpdate", openRaidLib.GetUnitID(casterName), spellId, cooldownInfo, unitCooldownsTable, openRaidLib.CooldownManager.UnitData)
end
end
end
end
end
end
end)
end
--vintage cooldown tracker and interrupt tracker
C_Timer.After(0.1, function()
createLocalCooldownTracker()
end)
tempCache.RestoreData()