148 lines
5.7 KiB
Lua
148 lines
5.7 KiB
Lua
-- OmenSync.lua
|
|
--
|
|
-- Addon-channel sync for party/raid threat values.
|
|
--
|
|
-- Why this exists
|
|
-- ---------------
|
|
-- Omen reads threat data via UnitDetailedThreatSituation(unit, mob).
|
|
-- That API only returns data the server pushes to the client. On the
|
|
-- Conquest of Azeroth realm (Vol'jin server) party-member threat is
|
|
-- not pushed — UnitDetailedThreatSituation("party1", "target")
|
|
-- returns nil even mid-combat, so Omen draws only the local player's
|
|
-- bar. The same Omen install on Bronzebeard (classic+) shows the
|
|
-- whole party because that core does push the data.
|
|
--
|
|
-- This module fills the gap by sending each player's own player+pet
|
|
-- threat over SendAddonMessage("OMSYNC", …, "PARTY"|"RAID"). Receivers
|
|
-- store incoming values keyed by senderGUID and serve them to
|
|
-- updatethreat() in Omen.lua as a fallback whenever the client API
|
|
-- returns nil. When the server *does* push data (Bronzebeard) the
|
|
-- API path wins and the sync values are simply unused — making this
|
|
-- file a no-op on healthy realms.
|
|
--
|
|
-- Wire format (pipe-separated, terse to stay under the 255-byte
|
|
-- AddonMessage payload cap):
|
|
-- <subjectGUID>|<mobGUID>|<threatValue>|<isTanking 0|1>
|
|
-- The sender's own name comes from CHAT_MSG_ADDON's `sender` arg, so
|
|
-- it doesn't need to be in the message; we only need the subject GUID
|
|
-- because each player broadcasts both their player and pet GUIDs.
|
|
--
|
|
-- Both peers must run this fork (or any addon that emits/consumes the
|
|
-- "OMSYNC" prefix). Other Omen installs ignore the prefix silently.
|
|
|
|
local Omen = LibStub("AceAddon-3.0"):GetAddon("Omen")
|
|
if not Omen then return end
|
|
|
|
local PREFIX = "OMSYNC"
|
|
local THROTTLE = 0.4 -- min seconds between sends per (subject, mob)
|
|
local STALE = 8 -- seconds; entries older than this are ignored
|
|
local MIN_DELTA = 0.05 -- 5%; smaller changes don't trigger a send
|
|
|
|
-- incomingThreat[subjectGUID][mobGUID] = { value, isTanking, time }
|
|
local incomingThreat = {}
|
|
-- lastSend[subjectGUID][mobGUID] = { value, time }
|
|
local lastSend = {}
|
|
|
|
local function nowGetTime() return GetTime() end
|
|
|
|
local function packMsg(subject, mob, value, isTanking)
|
|
return string.format("%s|%s|%d|%d", subject, mob, value, isTanking and 1 or 0)
|
|
end
|
|
|
|
local function unpackMsg(msg)
|
|
local subject, mob, val, tank = string.match(msg, "^([^|]+)|([^|]+)|(%-?%d+)|([01])$")
|
|
if not subject then return nil end
|
|
return subject, mob, tonumber(val), tank == "1"
|
|
end
|
|
|
|
local function inGroup()
|
|
return GetNumPartyMembers() > 0 or GetNumRaidMembers() > 0
|
|
end
|
|
|
|
local function pruneOlderThan(t, limit)
|
|
for k, v in pairs(t) do
|
|
if v.time and v.time < limit then t[k] = nil end
|
|
end
|
|
end
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Public API: called from Omen.lua's local updatethreat()
|
|
-- ---------------------------------------------------------------------------
|
|
|
|
-- Look up the most recent synced threat value for `subjectGUID` on
|
|
-- `mobGUID`, or nil if we don't have a fresh entry. Returns
|
|
-- (value, isTanking).
|
|
function Omen:SyncGetThreat(subjectGUID, mobGUID)
|
|
local byMob = incomingThreat[subjectGUID]
|
|
if not byMob then return nil end
|
|
local entry = byMob[mobGUID]
|
|
if not entry then return nil end
|
|
if nowGetTime() - entry.time > STALE then
|
|
byMob[mobGUID] = nil
|
|
return nil
|
|
end
|
|
return entry.value, entry.isTanking
|
|
end
|
|
|
|
-- Broadcast our own (player or pet) threat to the party/raid. Throttled
|
|
-- per (subject, mob); silently no-ops outside groups.
|
|
function Omen:SyncBroadcastThreat(subjectGUID, mobGUID, value, isTanking)
|
|
if not inGroup() then return end
|
|
if not subjectGUID or not mobGUID or not value then return end
|
|
if value < 0 then return end -- ignore the temporary-negative encoding
|
|
|
|
lastSend[subjectGUID] = lastSend[subjectGUID] or {}
|
|
local prev = lastSend[subjectGUID][mobGUID]
|
|
local now = nowGetTime()
|
|
if prev then
|
|
local age = now - prev.time
|
|
local maxV = math.max(value, prev.value, 1)
|
|
local pct = math.abs(value - prev.value) / maxV
|
|
if age < THROTTLE and pct < MIN_DELTA then return end
|
|
end
|
|
lastSend[subjectGUID][mobGUID] = { value = value, time = now }
|
|
|
|
local channel = GetNumRaidMembers() > 0 and "RAID" or "PARTY"
|
|
SendAddonMessage(PREFIX, packMsg(subjectGUID, mobGUID, value, isTanking), channel)
|
|
end
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Receiver
|
|
-- ---------------------------------------------------------------------------
|
|
|
|
-- Register the prefix so CHAT_MSG_ADDON fires for our messages on 3.3.5.
|
|
-- Without this, incoming OMSYNC packets are silently discarded by the client.
|
|
if RegisterAddonMessagePrefix then
|
|
RegisterAddonMessagePrefix(PREFIX)
|
|
end
|
|
|
|
local f = CreateFrame("Frame")
|
|
f:RegisterEvent("CHAT_MSG_ADDON")
|
|
f:RegisterEvent("PLAYER_LEAVING_WORLD")
|
|
f:RegisterEvent("PARTY_MEMBERS_CHANGED")
|
|
f:RegisterEvent("RAID_ROSTER_UPDATE")
|
|
f:SetScript("OnEvent", function(self, event, ...)
|
|
if event == "CHAT_MSG_ADDON" then
|
|
local prefix, msg, _, sender = ...
|
|
if prefix ~= PREFIX then return end
|
|
if sender == UnitName("player") then return end -- ignore self-echo
|
|
|
|
local subject, mob, value, isTanking = unpackMsg(msg)
|
|
if not subject then return end
|
|
|
|
local byMob = incomingThreat[subject]
|
|
if not byMob then
|
|
byMob = {}
|
|
incomingThreat[subject] = byMob
|
|
end
|
|
byMob[mob] = { value = value, isTanking = isTanking, time = nowGetTime() }
|
|
|
|
-- Cheap GC: prune stale mob entries off the same subject.
|
|
pruneOlderThan(byMob, nowGetTime() - STALE)
|
|
else
|
|
-- World transitions / roster changes invalidate everything.
|
|
wipe(incomingThreat)
|
|
wipe(lastSend)
|
|
end
|
|
end)
|