-- 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): -- ||| -- 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 -- --------------------------------------------------------------------------- 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)