You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

535 lines
15 KiB

local E = select(2, ...):unpack()
local P, CM = E.Party, E.Comm
local pairs, next, concat, tonumber, strmatch, strsplit, format, gsub, floor, abs = pairs, next, table.concat, tonumber, string.match, string.split, string.format, string.gsub, math.floor, math.abs
local GetTime = GetTime
local GetSpellCooldown = GetSpellCooldown or function(spellID)
local spellCooldownInfo = C_Spell.GetSpellCooldown(spellID);
if spellCooldownInfo then
return spellCooldownInfo.startTime, spellCooldownInfo.duration, spellCooldownInfo.isEnabled and 1 or 0, spellCooldownInfo.modRate;
end
end
local GetSpellCharges = GetSpellCharges or function(spellID)
local spellChargeInfo = C_Spell.GetSpellCharges(spellID);
if spellChargeInfo then
return spellChargeInfo.currentCharges, spellChargeInfo.maxCharges, spellChargeInfo.cooldownStartTime, spellChargeInfo.cooldownDuration, spellChargeInfo.chargeModRate;
end
end
local GetItemCount = C_Item and C_Item.GetItemCount or GetItemCount
local LibDeflate = LibStub("LibDeflate")
local CooldownSyncFrame = CreateFrame("Frame")
local COOLDOWN_SYNC_INTERVAL = 2
local MSG_DESYNC = "DESYNC"
local MSG_INFO_REQUEST = "REQ"
local MSG_INFO_UPDATE = "UPD"
local MSG_STRIVE_PVP = "STRIVE"
local MSG_COOLDOWN_SYNC = "CD"
local NULL = ""
CM.syncedGroupMembers = {}
CM.cooldownSyncIDs = {}
CM.serializedSyncData = NULL
function CM:SendComm(...)
local message = strjoin(",", ...)
if IsInRaid() then
self:SendCommMessage(self.AddonPrefix, message, (not IsInRaid(LE_PARTY_CATEGORY_HOME) and IsInRaid(LE_PARTY_CATEGORY_INSTANCE)) and "INSTANCE_CHAT" or "RAID")
elseif IsInGroup() then
self:SendCommMessage(self.AddonPrefix, message, (not IsInGroup(LE_PARTY_CATEGORY_HOME) and IsInGroup(LE_PARTY_CATEGORY_INSTANCE)) and "INSTANCE_CHAT" or "PARTY")
end
end
function CM:RequestSync()
self:SendComm(MSG_INFO_REQUEST, E.userGUID, self.serializedSyncData)
end
function CM:SendUserSyncData(sender)
if self.serializedSyncData == NULL then
self:InspectUser()
end
self:SendComm(sender or MSG_INFO_UPDATE, E.userGUID, self.serializedSyncData)
end
function CM:DesyncFromGroup()
wipe(self.syncedGroupMembers)
CooldownSyncFrame:Hide()
self:SendComm(MSG_DESYNC, E.userGUID, 1)
end
function CM:IsVersionIncompatible(serializationVersion)
return serializationVersion ~= self.SERIALIZATION_VERSION
end
local aceUserNameFix = CM.ACECOMM and E.userName or gsub(E.userNameWithRealm, " ", "")
function CM:CHAT_MSG_ADDON(prefix, message, _, sender)
if prefix ~= self.AddonPrefix or sender == aceUserNameFix then
return
end
local header, guid, body = strmatch(message, "(.-),(.-),(.+)")
local info = P.groupInfo[guid]
if not info then
return
end
local isSyncedUnit = self.syncedGroupMembers[guid]
if header == MSG_COOLDOWN_SYNC then
if isSyncedUnit then
self.SyncCooldowns(guid, body)
end
return
elseif header == MSG_INFO_REQUEST then
self:SendUserSyncData(guid)
elseif header == MSG_INFO_UPDATE then
if not isSyncedUnit then
return
end
elseif header == MSG_DESYNC then
if isSyncedUnit then
self.syncedGroupMembers[guid] = nil
end
self:ToggleCooldownSync()
return
elseif header == MSG_STRIVE_PVP then
if isSyncedUnit and (not P.loginsessionData[guid] or not P.loginsessionData[guid]["strivedPvpCD"]) then
local elapsed, cd = strsplit(":", body, 3)
self:SyncStrivePvpTalentCD(guid, tonumber(elapsed), tonumber(cd))
end
return
elseif header ~= E.userGUID then
return
end
local decodedData = LibDeflate:DecodeForWoWAddonChannel(body)
if not decodedData then
error("Error decoding sync message from " .. info.name)
end
local decompressedData = LibDeflate:DecompressDeflate(decodedData)
if not decompressedData then
error("Error decompressing sync message from " .. info.name)
end
while ( decompressedData ) do
local t, rest = strsplit("^", decompressedData, 2)
decompressedData = rest
local k, v = strsplit(",", t, 2)
if ( k == "T" ) then
while ( v ) do
local id, idlist = strsplit(",", v, 2)
v = idlist
local spellID, rank = strsplit(":", id)
spellID = tonumber(spellID)
if ( spellID ) then
if ( spellID > 0 ) then
info.talentData[spellID] = tonumber(rank) or 1
else
info.talentData[-spellID] = "PVP"
end
end
end
elseif ( k == "M" ) then
while ( v ) do
local id, idlist = strsplit(",", v, 2)
v = idlist
local key, src = strsplit(":", id)
local spellID = tonumber(key)
local value = tonumber(src) or src or true
if ( not spellID ) then
info.talentData[key] = value
elseif ( spellID > 0 ) then
if ( src == "AE" ) then
local rank1 = CM.essencePowerIDs[spellID]
if ( rank1 ) then
info.talentData[rank1] = src
info.talentData["essMajorRank1"] = rank1
info.talentData["essMajorID"] = spellID
end
elseif ( src == "ae" ) then
info.talentData["essStriveMult"] = spellID
else
info.talentData[spellID] = value
end
else
info.talentData[-spellID] = value
end
end
elseif ( k == "E" ) then
while ( v ) do
local id, idlist = strsplit(",", v, 2)
v = idlist
id = tonumber(id)
if ( id ) then
if ( id > 0 ) then
info.itemData[id] = true
else
info.rangedWeaponSpeed = -id
end
end
end
elseif ( k == "C" ) then
wipe(info.shadowlandsData)
local covenantID, soulbindID, conduits = strsplit(",", v, 3)
covenantID = tonumber(covenantID)
soulbindID = tonumber(soulbindID)
local covenantSpellID = E.covenant_to_spellid[covenantID]
info.shadowlandsData.covenantID = covenantSpellID
info.shadowlandsData.soulbindID = soulbindID
info.talentData[covenantSpellID] = "C"
while ( conduits ) do
local id, idlist = strsplit(",", conduits, 2)
conduits = idlist
local conduitSpellID, rankValue = strsplit(":", id)
conduitSpellID = tonumber(conduitSpellID)
rankValue = tonumber(rankValue)
if ( rankValue ) then
info.talentData[conduitSpellID] = rankValue
elseif ( conduitSpellID ) then
info.talentData[conduitSpellID] = 0
end
end
else
k = tonumber(k)
if ( not k or self:IsVersionIncompatible(k) ) then
return
end
info.spec = tonumber(v)
wipe(info.talentData)
wipe(info.itemData)
end
end
local unit = info.unit
if info.name == "" or info.name == "Unknown" then
info.name = GetUnitName(unit, true)
end
if info.level == 200 then
local lvl = UnitLevel(unit)
if lvl > 0 then
info.level = lvl
end
end
self.syncedGroupMembers[guid] = true
self:DequeueInspect(guid)
P:UpdateUnitBar(guid)
self:ToggleCooldownSync()
end
local function SendUpdatedUserSyncData()
CM:InspectUser()
CM:SendUserSyncData()
end
function CM:CHARACTER_POINTS_CHANGED(change)
if change == -1 then
SendUpdatedUserSyncData()
end
end
local equipmentTimer
local SendUserSyncData_OnTimerEnd = function()
SendUpdatedUserSyncData()
equipmentTimer = nil
end
function CM:PLAYER_EQUIPMENT_CHANGED(slotID)
if equipmentTimer or slotID > 16 then
return
end
equipmentTimer = C_Timer.NewTicker(0.1, SendUserSyncData_OnTimerEnd, 1)
end
CM.PLAYER_TALENT_UPDATE = SendUpdatedUserSyncData
CM.PLAYER_SPECIALIZATION_CHANGED = SendUpdatedUserSyncData
CM.COVENANT_CHOSEN = SendUpdatedUserSyncData
CM.SOULBIND_ACTIVATED = SendUpdatedUserSyncData
CM.SOULBIND_NODE_LEARNED = SendUpdatedUserSyncData
CM.SOULBIND_NODE_UNLEARNED = SendUpdatedUserSyncData
CM.SOULBIND_NODE_UPDATED = SendUpdatedUserSyncData
CM.SOULBIND_CONDUIT_INSTALLED = SendUpdatedUserSyncData
CM.SOULBIND_PATH_CHANGED = SendUpdatedUserSyncData
CM.COVENANT_SANCTUM_RENOWN_LEVEL_CHANGED = SendUpdatedUserSyncData
CM.TRAIT_CONFIG_UPDATED = SendUpdatedUserSyncData
CM.PLAYER_LEAVING_WORLD = CM.DesyncFromGroup
function CM:SyncStrivePvpTalentCD(guid, elapsed, cd)
local info = P.groupInfo[guid]
if not info then
return
end
local spellID = info.talentData["essStrivedPvpID"]
local icon = info.spellIcons[spellID]
if icon then
local active = info.active[spellID]
if active then
local modRate = active.iconModRate or 1
local newTime = GetTime() - elapsed * modRate
local newCd = cd * modRate
icon.cooldown:SetCooldown(newTime, newCd, modRate)
active.startTime = newTime
active.duration = newCd
end
icon.duration = cd
end
P.loginsessionData[guid] = P.loginsessionData[guid] or {}
P.loginsessionData[guid]["strivedPvpCD"] = cd
end
function CM.SendStrivePvpTalentCD(spellID)
local st, cd, _, modRate = GetSpellCooldown(spellID)
if cd < 2 then
return
end
local elapsed = GetTime() - st/modRate
cd = cd/modRate
if not P.isUserDisabled then
CM:SyncStrivePvpTalentCD(E.userGUID, elapsed, cd)
end
CM:SendComm(MSG_STRIVE_PVP, E.userGUID, elapsed, cd)
end
function CM.SyncCooldowns(guid, encodedData)
local info = P.groupInfo[guid]
if not info then return end
local compressedData = LibDeflate:DecodeForWoWAddonChannel(encodedData)
if not compressedData then
return
end
local serializedCooldownData = LibDeflate:DecompressDeflate(compressedData)
if not serializedCooldownData then
return
end
local now = GetTime()
while ( serializedCooldownData ) do
local spellID, duration, remainingTime, modRate, charges, rest = strsplit(",", serializedCooldownData, 6)
serializedCooldownData = rest
spellID = tonumber(spellID)
if ( spellID ) then
local icon = info.spellIcons[spellID]
if ( not icon ) then
spellID = E.spell_merged[spellID]
icon = info.spellIcons[spellID]
end
if ( icon ) then
duration = tonumber(duration)
remainingTime = tonumber(remainingTime)
modRate = tonumber(modRate)
charges = tonumber(charges)
local rawCharges = charges
charges = icon.maxcharges and charges ~= -1 and charges or nil
local active = icon.active and info.active[spellID]
if ( active and duration == 0 ) then
P:ResetCooldown(icon)
if spellID == 6262 then
icon.cooldown:Clear()
info.preactiveIcons[spellID] = icon
icon.icon:SetVertexColor(0.4, 0.4, 0.4)
icon.count:SetText(rawCharges)
info.auras.healthStoneStacks = rawCharges
local statusBar = icon.statusBar
if statusBar then
statusBar.BG:SetVertexColor(0.7, 0.7, 0.7)
end
end
elseif ( active and (spellID ~= 642 or not active.forbearanceOvertime or active.forbearanceOvertime ~= 0) and (abs(active.duration - (now - active.startTime) - remainingTime) > 1 or active.charges ~= charges) )
or ( not active and duration > 0 and E.sync_periodic[spellID] ) then
local startTime = now - (duration - remainingTime)
icon.cooldown:SetCooldown(startTime, duration, modRate)
P:SetCooldownElements(nil, icon, charges)
if ( not active ) then
info.active[spellID] = {}
active = info.active[spellID]
end
active.startTime = startTime
active.duration = duration
active.iconModRate = modRate
if ( charges ) then
active.charges = charges
icon.active = charges
icon.count:SetText(charges)
elseif ( spellID == 6262 ) then
info.preactiveIcons[spellID] = nil
icon.icon:SetVertexColor(1, 1, 1)
icon.count:SetText(rawCharges)
info.auras.healthStoneStacks = rawCharges
end
local statusBar = icon.statusBar
if ( statusBar ) then
P.OmniCDCastingBarFrame_OnEvent(statusBar.CastingBar, E.db.extraBars[statusBar.key].reverseFill and 'UNIT_SPELLCAST_CHANNEL_UPDATE' or 'UNIT_SPELLCAST_CAST_UPDATE')
end
end
end
end
end
end
local function GetCooldownFix(spellID)
local start, duration, enabled, modRate = GetSpellCooldown(spellID)
local currentCharges, maxCharges, cooldownStart, cooldownDuration, chargeModRate = GetSpellCharges(spellID)
local charges = maxCharges and maxCharges > 1 and currentCharges or -1
if enabled == 1 then
if start and start > 0 then
if duration < 1.5 or (currentCharges and currentCharges > 0) then
return nil
end
return start, duration, modRate, charges
elseif maxCharges and maxCharges > currentCharges then
return cooldownStart, cooldownDuration, chargeModRate, charges
end
end
return 0, 0, 1, charges, enabled
end
local cooldownData = {}
local elapsedTime = 0
local OFF_CD, THIRD_DECIMAL, TRUNCATE_ZEROS = "0,0,1", "%.3f", "%.?0+$"
local function CooldownSyncFrame_OnUpdate(_, elapsed)
elapsedTime = elapsedTime + elapsed
if elapsedTime < COOLDOWN_SYNC_INTERVAL then
return
end
local now = GetTime()
local c = 0
for id, cooldownInfo in pairs(CM.cooldownSyncIDs) do
local start, duration, modRate, charges, enabled = GetCooldownFix(id)
if start then
if id == 6262 then
charges = GetItemCount(5512, false, true)
end
local prevStart, prevCharges = cooldownInfo[1], cooldownInfo[2]
local isPeriodic = E.sync_periodic[id]
if duration == 0 then
if isPeriodic and (prevStart ~= 0 or enabled == 0) then
cooldownInfo[1] = start
cooldownInfo[2] = charges
cooldownData[c + 1] = id
cooldownData[c + 2] = OFF_CD
cooldownData[c + 3] = charges
c = c + 3
end
else
if abs(start - prevStart) > .49 or charges > prevCharges then
cooldownInfo[1] = start
cooldownInfo[2] = charges
local remainingTime = start + duration - now
if modRate == 1 then
remainingTime = floor(remainingTime)
else
duration = format(THIRD_DECIMAL, duration):gsub(TRUNCATE_ZEROS, NULL)
modRate = format(THIRD_DECIMAL, modRate):gsub(TRUNCATE_ZEROS, NULL)
remainingTime = format(THIRD_DECIMAL, remainingTime):gsub(TRUNCATE_ZEROS, NULL)
end
cooldownData[c + 1] = id
cooldownData[c + 2] = duration
cooldownData[c + 3] = remainingTime
cooldownData[c + 4] = modRate
cooldownData[c + 5] = charges
c = c + 5
elseif start == prevStart and charges > -1 and charges < prevCharges then
cooldownInfo[2] = charges
end
end
end
end
elapsedTime = 0
if c == 0 then
return
end
for i = #cooldownData, c + 1, -1 do
cooldownData[i] = nil
end
local serializedCooldownData = concat(cooldownData, ",")
local compressedData = LibDeflate:CompressDeflate(serializedCooldownData)
local encodedData = LibDeflate:EncodeForWoWAddonChannel(compressedData)
if not P.isUserDisabled then
CM.SyncCooldowns(E.userGUID, encodedData)
end
if next(CM.syncedGroupMembers) then
CM:SendComm(MSG_COOLDOWN_SYNC, E.userGUID, encodedData)
end
end
function CM:ForceSyncCooldowns()
elapsedTime = 100
end
function CM:ToggleCooldownSync()
if E.preCata then
return
end
if next(self.cooldownSyncIDs) and P.disabled == false and (not P.isUserDisabled or next(self.syncedGroupMembers)) then
CooldownSyncFrame:Show()
else
CooldownSyncFrame:Hide()
end
end
local CooldownSyncFrame_OnShow = function(self)
self.isShown = true
end
local CooldownSyncFrame_OnHide = function(self)
self.isShown = false
end
function CM:InitCooldownSync()
if self.initCooldownSync or E.preCata then
return
end
CooldownSyncFrame:Hide()
CooldownSyncFrame:SetScript("OnShow", CooldownSyncFrame_OnShow)
CooldownSyncFrame:SetScript("OnHide", CooldownSyncFrame_OnHide)
CooldownSyncFrame:SetScript("OnUpdate", CooldownSyncFrame_OnUpdate)
self.initCooldownSync = true
end