diff --git a/api/unitframes.lua b/api/unitframes.lua index 0ea49aef..647df472 100644 --- a/api/unitframes.lua +++ b/api/unitframes.lua @@ -216,13 +216,14 @@ local function DebuffOnClick() end -- Every enabled unit frame. Group, target and pet events re-check a frame's --- visibility in OnEvent. A frame with a tick shows another unit's target or a --- pet that can go out of sight, and no event reports either, so a timer --- re-checks those frames, hidden ones included. +-- visibility in OnEvent, and UNIT_TARGET does for a chained unit (another +-- unit's target). No event reports a unit going out of sight, though, and a +-- pet (the frames with a tick) or a chained unit can, so a timer re-checks +-- those frames, hidden ones included. local visibilityframes = {} C_Timer.NewTicker(.2, function() for frame in pairs(visibilityframes) do - if frame.tick then frame:UpdateVisibility() end + if frame.tick or frame.chain then frame:UpdateVisibility() end end end) @@ -239,6 +240,7 @@ local VISIBILITY_EVENTS = { RAID_ROSTER_UPDATE = true, PLAYER_TARGET_CHANGED = true, UNIT_PET = true, + UNIT_TARGET = true, -- a link of a chained frame's unit retargeted } -- ============================================================================ @@ -443,7 +445,24 @@ function pfUI.uf:UpdateVisibility() -- rebuilding label..id -- and calling UnitGUID -- for every unit event fired -- by anything, anywhere. A frame that is not in use drops them entirely; the -- VISIBILITY_EVENTS that bring it back are registered plainly. - self:RegisterUnitEvents(visibility ~= "hide" and self.unitstr or nil) + -- + -- A chained unit -- another unit's target, "targettarget" or "party1target" + -- -- has no token that unit events name, so the frame follows it by GUID, + -- which every unit event also fires with. It follows the unit only while + -- the client has it: a link's target can be out of sight, and its coming + -- back is then a new unit too. The unit changes with no event of its own + -- whenever a link of the chain retargets; UNIT_TARGET on every link reports + -- that (RegisterChainEvents), and the GUID compare turns it into the full + -- refresh a new unit needs. + self.chain = visibility ~= "hide" and unitstr:match("^(.+)target$") or nil + if self.chain then + local guid = UnitName(unitstr) and UnitGUID(unitstr) + if guid ~= self.eventunit then self.update_full = true end + self:RegisterUnitEvents(guid) + else + self:RegisterUnitEvents(visibility ~= "hide" and unitstr or nil) + end + self:RegisterChainEvents(self.chain) -- vanilla visibility if visibility == "hide" then @@ -970,9 +989,10 @@ function pfUI.uf:UpdateConfig() f:UpdateFrameSize() else f:UnregisterAllEvents() - -- that dropped the unit filters along with the registrations, so the cache - -- has to go too or the next UpdateVisibility believes they are still set - f.eventunit = nil + -- that dropped the unit filters along with the registrations, so the + -- caches have to go too or the next UpdateVisibility believes they are + -- still set + f.eventunit, f.chainkey = nil, nil f:Hide() end end @@ -1011,7 +1031,7 @@ function pfUI.uf.OnEvent() -- a hidden frame runs no OnUpdate, so its unit coming into existence has to -- show it from here - if VISIBILITY_EVENTS[event] and not (event == "PLAYER_TARGET_CHANGED" and pfScanActive) then + if VISIBILITY_EVENTS[event] then this:UpdateVisibility() end @@ -1020,7 +1040,7 @@ function pfUI.uf.OnEvent() this.update_full = true -- Clear GUID tracking on zone change for full rebuild if pfUI.uf.ClearGuidTracking then pfUI.uf.ClearGuidTracking() end - elseif this.label == "target" and event == "PLAYER_TARGET_CHANGED" and not pfScanActive == true then + elseif this.label == "target" and event == "PLAYER_TARGET_CHANGED" then this.update_full = true -- immediately update raid icon to avoid delay on target swap if this.raidIcon and this.config and this.config.raidicon == "1" then @@ -1064,12 +1084,12 @@ function pfUI.uf.OnEvent() this.update_aura = true elseif this.label == "pet" and event == "UNIT_HAPPINESS" then this.update_full = true - -- UNIT_XXX Events. RegisterUnitEvents means arg1 can only be this frame's own - -- unit; the compare is kept for the case the filter sits out, which is when - -- arg1 is not a string. The old GUID alternative is gone: these events fire - -- once per token that resolves to the unit AND once with the raw GUID, so the - -- token form always arrives and the GUID form was only ever a duplicate wake. - elseif arg1 and arg1 == this.unitstr then + -- UNIT_XXX Events. RegisterUnitEvents means arg1 can only be the unit this + -- frame follows -- its token, or a chained unit's GUID; the compare is kept + -- for the case the filter sits out, which is when arg1 is not a string. These + -- events fire once per token that resolves to the unit AND once with the raw + -- GUID, so filtering on one of the two never wakes the frame twice. + elseif arg1 and arg1 == this.eventunit then if event == "UNIT_PORTRAIT_UPDATE" or event == "UNIT_MODEL_CHANGED" then this.update_portrait = true elseif event == "UNIT_AURA" then @@ -1148,31 +1168,18 @@ function pfUI.uf.OnUpdate() local unitstr = this.label .. this.id this.lastTick = now + (tickInterval or .5) - -- target target has a huge delay, make sure to not tick during range checks - if this.label == "targettarget" or this.label == "targettargettarget" then - local name = UnitName(this.label) - if name ~= this.namebuf1 then - this.namebuf1 = name - elseif name ~= this.namebuf2 then - this.namebuf2 = name - else - pfUI.uf:RefreshUnitState(this) + pfUI.uf:RefreshUnitState(this) + -- group frames get their leader/loot/raid mark icons refreshed by + -- events through update_indicators, so polling them is only a safety + -- net; the other frames keep polling per tick + local label = this.label + if label == "raid" or label == "party" or label == "raidpet" or label == "partypet" then + if (this.lastIndicatorPoll or 0) < now then + this.lastIndicatorPoll = now + 3 pfUI.uf:RefreshIndicators(this) end else - pfUI.uf:RefreshUnitState(this) - -- group frames get their leader/loot/raid mark icons refreshed by - -- events through update_indicators, so polling them is only a safety - -- net; frames whose unit changes without events keep polling per tick - local label = this.label - if label == "raid" or label == "party" or label == "raidpet" or label == "partypet" then - if (this.lastIndicatorPoll or 0) < now then - this.lastIndicatorPoll = now + 3 - pfUI.uf:RefreshIndicators(this) - end - else - pfUI.uf:RefreshIndicators(this) - end + pfUI.uf:RefreshIndicators(this) end if this.config and this.config.glowaggro == "1" and pfUI.api.UnitHasAggro(unitstr) > 0 then @@ -1185,9 +1192,10 @@ function pfUI.uf.OnUpdate() this.glow:Hide() end - -- update eventless frames (targettarget, etc). A full refresh rescans - -- every aura and the portrait, so only do it when the unit behind the - -- frame changes or once a second; other ticks refresh the bars only. + -- update polled frames (the pets, created with a tick). A full refresh + -- rescans every aura and the portrait, so only do it when the unit + -- behind the frame changes or once a second; other ticks refresh the + -- bars only. if this.tick then local guid = UnitGUID(unitstr) if guid ~= this.tickGuid or (this.tickFull or 0) < now then @@ -1330,16 +1338,17 @@ local UNIT_EVENTS = { "INCOMING_RESURRECT_CHANGED", -- frame=ressIcon } --- Point this frame's unit-event subscriptions at `unitstr`, or drop them when --- the frame has no unit. Cheap to call repeatedly: it no-ops unless the unit --- actually changed, which matters because every refresh runs UpdateVisibility. -function pfUI.uf:RegisterUnitEvents(unitstr) - if self.eventunit == unitstr then return end - self.eventunit = unitstr +-- Point this frame's unit-event subscriptions at `unit` (a token, or a chained +-- unit's GUID), or drop them when the frame has no unit. Cheap to call +-- repeatedly: it no-ops unless the unit actually changed, which matters +-- because every refresh runs UpdateVisibility. +function pfUI.uf:RegisterUnitEvents(unit) + if self.eventunit == unit then return end + self.eventunit = unit for i = 1, table.getn(UNIT_EVENTS) do - if unitstr then - self:RegisterUnitEvent(UNIT_EVENTS[i], unitstr) + if unit then + self:RegisterUnitEvent(UNIT_EVENTS[i], unit) else self:UnregisterEvent(UNIT_EVENTS[i]) end @@ -1348,13 +1357,42 @@ function pfUI.uf:RegisterUnitEvents(unitstr) -- UNIT_COMBAT rides along only while the frame draws combat feedback text. -- UpdateConfig clears eventunit when it toggles that, so the next -- UpdateVisibility re-runs this. - if unitstr and self.combatfeedback then - self:RegisterUnitEvent("UNIT_COMBAT", unitstr) + if unit and self.combatfeedback then + self:RegisterUnitEvent("UNIT_COMBAT", unit) else self:UnregisterEvent("UNIT_COMBAT") end end +-- Point this frame's UNIT_TARGET subscription at every link of its chained +-- unit, whose `owner` is the unit it is the target of -- for +-- "targettargettarget" the links are "targettarget" and "target" -- or drop it +-- when `owner` is nil. By GUID, since the inner links are no token that events +-- name. Cheap to call repeatedly: it re-registers only when a link changed. +local chainlinks = {} +function pfUI.uf:RegisterChainEvents(owner) + local n, key = 0, nil + while owner do + local guid = UnitGUID(owner) + if guid then + n = n + 1 + chainlinks[n] = guid + key = key and key .. guid or guid + end + owner = owner:match("^(.+)target$") + end + + if key == self.chainkey then return end + self.chainkey = key + + if key then + for i = table.getn(chainlinks), n + 1, -1 do chainlinks[i] = nil end + self:RegisterUnitEvent("UNIT_TARGET", unpack(chainlinks)) + else + self:UnregisterEvent("UNIT_TARGET") + end +end + function pfUI.uf:EnableEvents() local f = self @@ -1473,6 +1511,7 @@ function pfUI.uf:CreateUnitFrame(unit, id, config, tick) f.EnableScripts = pfUI.uf.EnableScripts f.EnableEvents = pfUI.uf.EnableEvents f.RegisterUnitEvents = pfUI.uf.RegisterUnitEvents + f.RegisterChainEvents = pfUI.uf.RegisterChainEvents f.EnableClickCast = pfUI.uf.EnableClickCast f.GetColor = pfUI.uf.GetColor @@ -1593,9 +1632,10 @@ function pfUI.uf:CreateUnitFrame(unit, id, config, tick) f:UpdateFrameSize() else f:UnregisterAllEvents() - -- that dropped the unit filters along with the registrations, so the cache - -- has to go too or the next UpdateVisibility believes they are still set - f.eventunit = nil + -- that dropped the unit filters along with the registrations, so the + -- caches have to go too or the next UpdateVisibility believes they are + -- still set + f.eventunit, f.chainkey = nil, nil f:Hide() end @@ -1750,11 +1790,6 @@ function pfUI.uf:RefreshUnit(unit, component) if not unit.id then unit.id = "" end component = component or "" - -- don't update scanner activity - if unit.label == "target" or unit.label == "targettarget" or unit.label == "targettargettarget" then - if pfScanActive == true then return end - end - -- hide unused and invalid frames unit:UpdateVisibility() diff --git a/modules/focus.lua b/modules/focus.lua index ac808c7c..9a98cdbe 100644 --- a/modules/focus.lua +++ b/modules/focus.lua @@ -8,7 +8,7 @@ pfUI:RegisterModule("focus", function () UpdateMovable(pfUI.uf.focus) pfUI.uf.focus:Hide() - pfUI.uf.focustarget = pfUI.uf:CreateUnitFrame("FocusTarget", nil, C.unitframes.focustarget, .2) + pfUI.uf.focustarget = pfUI.uf:CreateUnitFrame("FocusTarget", nil, C.unitframes.focustarget) pfUI.uf.focustarget:UpdateFrameSize() pfUI.uf.focustarget:SetPoint("BOTTOMLEFT", pfUI.uf.focus, "TOP", 0, 10) UpdateMovable(pfUI.uf.focustarget) @@ -16,14 +16,13 @@ pfUI:RegisterModule("focus", function () -- PLAYER_FOCUS_CHANGED drives immediate refresh on focus assign / clear. -- Between events, ClassicAPI fires UNIT_* (health/mana/aura/...) with - -- arg1 == "focus" and arg1 == "focustarget", so the bars update - -- event-driven like target. No event reports the focus changing its - -- target, so focustarget ticks like targettarget. + -- arg1 == "focus", so the bars update event-driven like target. + -- focustarget is a chained unit like targettarget: UNIT_TARGET follows the + -- focus retargeting, and a new focus only needs its visibility re-checked, + -- which queues the full refresh when its target is a different unit. pfUI.events:RegisterFrameEventAndCallback("PLAYER_FOCUS_CHANGED", function() - pfUI.uf.focus.instantRefresh = true pfUI.uf:RefreshUnit(pfUI.uf.focus, "all") - pfUI.uf.focustarget.instantRefresh = true - pfUI.uf:RefreshUnit(pfUI.uf.focustarget, "all") + pfUI.uf.focustarget:UpdateVisibility() end) end) diff --git a/modules/group.lua b/modules/group.lua index 0700e31b..34312b8a 100644 --- a/modules/group.lua +++ b/modules/group.lua @@ -35,7 +35,7 @@ pfUI:RegisterModule("group", function () end if C.unitframes.grouptarget.visible == "1" and active then - pfUI.uf.group[i].target = pfUI.uf.group[i].target or pfUI.uf:CreateUnitFrame("Party" .. i .. "Target", nil, C.unitframes.grouptarget, 0.2) + pfUI.uf.group[i].target = pfUI.uf.group[i].target or pfUI.uf:CreateUnitFrame("Party" .. i .. "Target", nil, C.unitframes.grouptarget) pfUI.uf.group[i].target:UpdateFrameSize() pfUI.uf.group[i].target:SetPoint("TOPLEFT", pfUI.uf.group[i], "TOPRIGHT", 3*default_border, 0) pfUI.uf.group[i].target:UpdateConfig() diff --git a/modules/nameplates.lua b/modules/nameplates.lua index 937288cf..4a17b1fd 100644 --- a/modules/nameplates.lua +++ b/modules/nameplates.lua @@ -608,6 +608,7 @@ nameplates:RegisterUnitEvent("UNIT_SPELLCAST_NOT_INTERRUPTIBLE", "target", "focu -- RegisterUnitEvent on an already-filtered registration swaps the units. plate.nameplate:RegisterUnitEvent("UNIT_AURA", arg1) plate.nameplate:RegisterUnitEvent("UNIT_FLAGS", arg1) + plate.nameplate:RegisterUnitEvent("UNIT_TARGET", arg1) plate.nameplate:RegisterUnitEvent("UNIT_SPELLCAST_START", arg1) plate.nameplate:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_START", arg1) plate.nameplate:RegisterUnitEvent("UNIT_SPELLCAST_STOP", arg1) @@ -765,6 +766,14 @@ nameplates:RegisterUnitEvent("UNIT_SPELLCAST_NOT_INTERRUPTIBLE", "target", "focu this.auraUpdate = true elseif event == "UNIT_FLAGS" then this.eventcache = true + elseif event == "UNIT_TARGET" then + -- the unit turned to someone else: recolour its combat state now + -- rather than once the colour cache and the data tick run out + if cfg.outcombatstate or cfg.barcombatstate then + local cached = this.cachedGuid and combatColorCache[this.cachedGuid] + if cached then cached.expires = 0 end + this.eventcache = true + end elseif event == "UNIT_SPELLCAST_START" or event == "UNIT_SPELLCAST_CHANNEL_START" then -- the payload carries no timing, so poll it (PollCastInfo picks cast -- vs channel) diff --git a/modules/pettarget.lua b/modules/pettarget.lua index 1e1eaf83..3ff50021 100644 --- a/modules/pettarget.lua +++ b/modules/pettarget.lua @@ -3,7 +3,7 @@ pfUI:RegisterModule("pettarget", function () if C.unitframes.disable == "1" then return end local rawborder, default_border = GetBorderSize("unitframes") - pfUI.uf.pettarget = pfUI.uf:CreateUnitFrame("PetTarget", nil, C.unitframes.ptarget, .2) + pfUI.uf.pettarget = pfUI.uf:CreateUnitFrame("PetTarget", nil, C.unitframes.ptarget) pfUI.uf.pettarget:UpdateFrameSize() pfUI.uf.pettarget:SetPoint("TOP", pfUI.uf.pet or pfUI.uf.target or UIParent, "BOTTOM", 0, -default_border) UpdateMovable(pfUI.uf.pettarget) diff --git a/modules/targettarget.lua b/modules/targettarget.lua index cea120e4..2af040f1 100644 --- a/modules/targettarget.lua +++ b/modules/targettarget.lua @@ -2,7 +2,7 @@ pfUI:RegisterModule("targettarget", function () -- do not go further on disabled UFs if C.unitframes.disable == "1" then return end - pfUI.uf.targettarget = pfUI.uf:CreateUnitFrame("TargetTarget", nil, C.unitframes.ttarget, .2) + pfUI.uf.targettarget = pfUI.uf:CreateUnitFrame("TargetTarget", nil, C.unitframes.ttarget) pfUI.uf.targettarget:UpdateFrameSize() pfUI.uf.targettarget:SetPoint("BOTTOM", UIParent , "BOTTOM", 0, 125) UpdateMovable(pfUI.uf.targettarget) diff --git a/modules/targettargettarget.lua b/modules/targettargettarget.lua index d9bd137d..dff2ec34 100644 --- a/modules/targettargettarget.lua +++ b/modules/targettargettarget.lua @@ -2,7 +2,7 @@ pfUI:RegisterModule("targettargettarget", function () -- do not go further on disabled UFs if C.unitframes.disable == "1" then return end - pfUI.uf.targettargettarget = pfUI.uf:CreateUnitFrame("TargetTargetTarget", nil, C.unitframes.tttarget, .2) + pfUI.uf.targettargettarget = pfUI.uf:CreateUnitFrame("TargetTargetTarget", nil, C.unitframes.tttarget) pfUI.uf.targettargettarget:UpdateFrameSize() pfUI.uf.targettargettarget:SetPoint("BOTTOM", UIParent , "BOTTOM", 0, 200) UpdateMovable(pfUI.uf.targettargettarget)