Files

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)