diff --git a/mods/target-debufftimer.lua b/mods/target-debufftimer.lua index bd975c9..c55c804 100644 --- a/mods/target-debufftimer.lua +++ b/mods/target-debufftimer.lua @@ -13,56 +13,88 @@ local module = ShaguTweaks:register({ local libdebuff = ShaguTweaks.libdebuff local TimeConvert = ShaguTweaks.TimeConvert -local function GetTargetDebuff(index) - -- ClassicAPI exposes the real aura name, stack count, dispel type and, - -- when known by the client, the exact applied duration/expiration time. - -- Keep libdebuff as the compatibility fallback for missing timing data. - local aura = API and API.GetDebuffDataByIndex and API.GetDebuffDataByIndex("target", index) - if aura then - local duration = aura.duration and aura.duration > 0 and aura.duration or nil - local dtype = aura.dispelName and aura.dispelName ~= "" and aura.dispelName or nil - - if duration and aura.expirationTime and aura.expirationTime > 0 then - local timeleft = aura.expirationTime - GetTime() - if timeleft < 0 then timeleft = 0 end - return aura.name, nil, aura.icon, aura.applications, dtype, duration, timeleft - end - - -- ClassicAPI may know the aura but not its exact expiration when the cast - -- was not observed. Preserve ShaguTweaks' old estimated timer in that case. - if duration and libdebuff and libdebuff.UnitDebuff then - local effect, rank, texture, stacks, legacyType, legacyDuration, legacyTimeleft = libdebuff:UnitDebuff("target", index) - return aura.name or effect, rank, aura.icon or texture, - aura.applications or stacks, dtype or legacyType, legacyDuration, legacyTimeleft - end - - -- Duration 0 means an indefinite aura: show metadata, but no cooldown. - return aura.name, nil, aura.icon, aura.applications, dtype, nil, -1 - end - +local function GetLegacyDebuff(index) if libdebuff and libdebuff.UnitDebuff then return libdebuff:UnitDebuff("target", index) end end -local function CreateTextCooldown(cooldown) - if cooldown.readable then return end +local function NormalizeAuraString(value) + return value and value ~= "" and value or nil +end - cooldown.readable = CreateFrame("Frame", "pfCooldownFrame", cooldown:GetParent()) +local function ResolveDebuffTiming(name, icon, stacks, dtype, duration, expirationTime, index, now) + icon = NormalizeAuraString(icon) + dtype = NormalizeAuraString(dtype) + duration = duration and duration > 0 and duration or nil + + if duration and expirationTime and expirationTime > 0 then + local timeleft = expirationTime - now + if timeleft < 0 then timeleft = 0 end + return name, nil, icon, stacks, dtype, duration, timeleft + end + + -- ClassicAPI always knows the aura metadata, but expiration can be unknown + -- when the client did not observe its application. Keep ShaguTweaks' old + -- estimate only for that missing-timing case. + if duration then + local effect, rank, texture, legacyStacks, legacyType, + legacyDuration, legacyTimeleft = GetLegacyDebuff(index) + if effect then + return name or effect, rank, icon or texture, stacks or legacyStacks, + dtype or legacyType, legacyDuration, legacyTimeleft + end + end + + -- Duration 0 is a permanent/indefinite aura. Keep its metadata visible but + -- do not create a fake cooldown. + return name, nil, icon, stacks, dtype, nil, -1 +end + +local function GetTargetDebuff(index, now) + -- Prefer ClassicAPI's positional accessor: same useful aura/timing data as + -- AuraData, but no temporary Lua table allocation for every debuff button. + if API and API.aurapositional and API.UnitDebuff then + local name, icon, stacks, dtype, duration, expirationTime = + API.UnitDebuff("target", index) + if name then + return ResolveDebuffTiming(name, icon, stacks, dtype, duration, + expirationTime, index, now) + end + return + end + + -- Compatibility with older ClassicAPI builds that only expose AuraData. + local aura = API and API.GetDebuffDataByIndex and + API.GetDebuffDataByIndex("target", index) + if aura then + return ResolveDebuffTiming(aura.name, aura.icon, aura.applications, + aura.dispelName, aura.duration, aura.expirationTime, index, now) + end + + return GetLegacyDebuff(index) +end + +local function CreateTextCooldown(cooldown) + if cooldown.readable then return cooldown.readable end + + -- These overlays never need global frame names. Keeping them anonymous + -- avoids collisions when several target debuffs are visible at once. + cooldown.readable = CreateFrame("Frame", nil, cooldown:GetParent()) cooldown.readable:SetAllPoints(cooldown) cooldown.readable:SetFrameLevel(cooldown:GetParent():GetFrameLevel() + 1) - cooldown.readable.text = cooldown.readable:CreateFontString("pfCooldownFrameText", "OVERLAY") + cooldown.readable.text = cooldown.readable:CreateFontString(nil, "OVERLAY") cooldown.readable.text:SetFont(STANDARD_TEXT_FONT, 10, "OUTLINE") cooldown.readable.text:SetPoint("CENTER", cooldown.readable, "CENTER", 0, 0) cooldown.readable:SetScript("OnUpdate", function() + this.elapsed = (this.elapsed or 0) + arg1 + if this.elapsed < .1 then return end + this.elapsed = 0 + local parent = this:GetParent() if not parent then this:Hide() return end - if not this.next then this.next = GetTime() + .1 end - if this.next > GetTime() then return end - this.next = GetTime() + .1 - -- fix own alpha value (should be inherited, but somehow isn't always) this:SetAlpha(parent:GetAlpha()) @@ -73,6 +105,8 @@ local function CreateTextCooldown(cooldown) this:Hide() end end) + + return cooldown.readable end module.enable = function(self) @@ -80,8 +114,13 @@ module.enable = function(self) TargetDebuffButton_Update = function() HookTargetDebuffButton_Update() + -- All buttons in this refresh share the same clock sample. Besides being + -- cheaper, this keeps their displayed countdowns perfectly aligned. + local now = GetTime() + for i=1, MAX_TARGET_DEBUFFS do - local effect, rank, texture, stacks, dtype, duration, timeleft = GetTargetDebuff(i) + local effect, rank, texture, stacks, dtype, duration, timeleft = + GetTargetDebuff(i, now) local button = _G["TargetFrameDebuff"..i] if button and not button.cd then @@ -113,12 +152,14 @@ module.enable = function(self) end if button and effect and duration and timeleft and timeleft >= 0 then - local start = GetTime() + timeleft - duration - CreateTextCooldown(button.cd) + local start = now + timeleft - duration + local readable = CreateTextCooldown(button.cd) CooldownFrame_SetTimer(button.cd, start, duration, 1) - button.cd.readable.start = start - button.cd.readable.duration = duration - button.cd.readable:Show() + readable.start = start + readable.duration = duration + readable.elapsed = 0 + readable.text:SetText(TimeConvert(timeleft)) + readable:Show() button.cd:Show() elseif button then CooldownFrame_SetTimer(button.cd,0,0,0)