Apply Patch
This commit is contained in:
+140
@@ -0,0 +1,140 @@
|
||||
-- Settings remain usable through /tfb on unmodified 1.12 clients.
|
||||
TargetFrameBuffDefaults = {
|
||||
timers = true, highlight = true, sweeps = false, timerSize = 9, warning = 5
|
||||
};
|
||||
|
||||
local function RefreshAuras()
|
||||
if (TargetFrameBuff_Update and TargetFrameBuff1) then
|
||||
TargetFrameBuff_Update();
|
||||
end
|
||||
end
|
||||
|
||||
local function Print(message)
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cffffd100TargetFrameBuff:|r "..message);
|
||||
end
|
||||
|
||||
local function Validate()
|
||||
if (type(TargetFrameBuffDB) ~= "table") then TargetFrameBuffDB = {}; end
|
||||
for key, value in pairs(TargetFrameBuffDefaults) do
|
||||
if (type(TargetFrameBuffDB[key]) ~= type(value)) then TargetFrameBuffDB[key] = value; end
|
||||
end
|
||||
if (TargetFrameBuffDB.timerSize ~= TargetFrameBuffDB.timerSize or TargetFrameBuffDB.timerSize < 6 or TargetFrameBuffDB.timerSize > 20) then
|
||||
TargetFrameBuffDB.timerSize = 9;
|
||||
end
|
||||
if (TargetFrameBuffDB.warning ~= TargetFrameBuffDB.warning or TargetFrameBuffDB.warning < 0 or TargetFrameBuffDB.warning > 30) then
|
||||
TargetFrameBuffDB.warning = 5;
|
||||
end
|
||||
end
|
||||
|
||||
local panel;
|
||||
local function CreateOptions()
|
||||
if (panel or not InterfaceOptions_AddCategory) then return; end
|
||||
panel = CreateFrame("Frame", "TargetFrameBuffOptions", UIParent);
|
||||
panel.name = "TargetFrameBuff";
|
||||
local title = panel:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge");
|
||||
title:SetPoint("TOPLEFT", panel, "TOPLEFT", 16, -16);
|
||||
title:SetText("TargetFrameBuff");
|
||||
local note = panel:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall");
|
||||
note:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -12);
|
||||
note:SetText("ClassicAPI features. Sweeps work best with ClassicAPI 1.17.1 or newer.");
|
||||
local checks = {};
|
||||
local labels = {"Show countdown text", "Highlight enemy buffs you can remove", "Show cooldown sweeps"};
|
||||
local keys = {"timers", "highlight", "sweeps"};
|
||||
for i = 1, 3 do
|
||||
local check = CreateFrame("CheckButton", "TargetFrameBuffOption"..i, panel, "UICheckButtonTemplate");
|
||||
check:SetPoint("TOPLEFT", panel, "TOPLEFT", 16, -65 - (i - 1) * 32);
|
||||
check.key = keys[i];
|
||||
getglobal(check:GetName().."Text"):SetText(labels[i]);
|
||||
check:SetScript("OnClick", function()
|
||||
TargetFrameBuffDB[this.key] = this:GetChecked() and true or false;
|
||||
RefreshAuras();
|
||||
end);
|
||||
checks[i] = check;
|
||||
end
|
||||
local sliders = {};
|
||||
local sliderKeys = {"timerSize", "warning"};
|
||||
local sliderLabels = {"Timer font size", "Warning threshold (seconds)"};
|
||||
for i = 1, 2 do
|
||||
local slider = CreateFrame("Slider", "TargetFrameBuffSlider"..i, panel, "OptionsSliderTemplate");
|
||||
slider:SetPoint("TOPLEFT", panel, "TOPLEFT", 22, -190 - (i - 1) * 65);
|
||||
slider:SetWidth(240);
|
||||
slider:SetMinMaxValues(i == 1 and 6 or 0, i == 1 and 20 or 30);
|
||||
slider:SetValueStep(1);
|
||||
slider.key = sliderKeys[i];
|
||||
slider.label = sliderLabels[i];
|
||||
getglobal(slider:GetName().."Low"):SetText(i == 1 and "6" or "0");
|
||||
getglobal(slider:GetName().."High"):SetText(i == 1 and "20" or "30");
|
||||
slider:SetScript("OnValueChanged", function()
|
||||
local value = math.floor(this:GetValue() + 0.5);
|
||||
TargetFrameBuffDB[this.key] = value;
|
||||
getglobal(this:GetName().."Text"):SetText(this.label..": "..value);
|
||||
RefreshAuras();
|
||||
end);
|
||||
sliders[i] = slider;
|
||||
end
|
||||
local saved = {};
|
||||
local function Sync()
|
||||
for i = 1, 3 do checks[i]:SetChecked(TargetFrameBuffDB[checks[i].key]); end
|
||||
for i = 1, 2 do
|
||||
sliders[i]:SetValue(TargetFrameBuffDB[sliders[i].key]);
|
||||
getglobal(sliders[i]:GetName().."Text"):SetText(sliders[i].label..": "..TargetFrameBuffDB[sliders[i].key]);
|
||||
end
|
||||
end
|
||||
panel.refresh = function()
|
||||
for key in pairs(TargetFrameBuffDefaults) do saved[key] = TargetFrameBuffDB[key]; end
|
||||
Sync();
|
||||
end;
|
||||
panel.cancel = function()
|
||||
for key, value in pairs(saved) do TargetFrameBuffDB[key] = value; end
|
||||
Sync();
|
||||
RefreshAuras();
|
||||
end;
|
||||
panel.default = function()
|
||||
for key, value in pairs(TargetFrameBuffDefaults) do TargetFrameBuffDB[key] = value; end
|
||||
Sync();
|
||||
RefreshAuras();
|
||||
end;
|
||||
InterfaceOptions_AddCategory(panel);
|
||||
end
|
||||
|
||||
SLASH_TARGETFRAMEBUFF1 = "/tfb";
|
||||
SlashCmdList["TARGETFRAMEBUFF"] = function(message)
|
||||
Validate();
|
||||
local _, _, command, value = string.find(string.lower(message or ""), "^%s*(%S*)%s*(.-)%s*$");
|
||||
if (command == "timers" or command == "highlight" or command == "sweeps") then
|
||||
if (value == "on") then TargetFrameBuffDB[command] = true;
|
||||
elseif (value == "off") then TargetFrameBuffDB[command] = false;
|
||||
elseif (value == "") then TargetFrameBuffDB[command] = not TargetFrameBuffDB[command];
|
||||
else Print("Use on or off."); return; end
|
||||
Print(command..": "..(TargetFrameBuffDB[command] and "on" or "off"));
|
||||
elseif (command == "size" or command == "warning") then
|
||||
local number = tonumber(value);
|
||||
local min, max = 0, 30;
|
||||
if (command == "size") then min, max = 6, 20; end
|
||||
if (not number or number < min or number > max) then
|
||||
Print("Choose a number from "..min.." to "..max.."."); return;
|
||||
end
|
||||
TargetFrameBuffDB[command == "size" and "timerSize" or "warning"] = math.floor(number + 0.5);
|
||||
elseif (command == "reset") then
|
||||
TargetFrameBuffDB = nil;
|
||||
Validate();
|
||||
elseif (command == "" and InterfaceOptionsFrame_OpenToCategory) then
|
||||
CreateOptions();
|
||||
InterfaceOptionsFrame_OpenToCategory(panel);
|
||||
else
|
||||
Print("/tfb timers|highlight|sweeps [on|off], /tfb size 6-20, /tfb warning 0-30, /tfb reset");
|
||||
return;
|
||||
end
|
||||
RefreshAuras();
|
||||
end;
|
||||
|
||||
local loader = CreateFrame("Frame");
|
||||
loader:RegisterEvent("ADDON_LOADED");
|
||||
loader:SetScript("OnEvent", function()
|
||||
if (arg1 == "TargetFrameBuff") then
|
||||
Validate();
|
||||
CreateOptions();
|
||||
RefreshAuras();
|
||||
this:UnregisterEvent("ADDON_LOADED");
|
||||
end
|
||||
end);
|
||||
@@ -0,0 +1,494 @@
|
||||
|
||||
-- Title: TargetFrameBuff v0.9.2
|
||||
-- Notes: Shows up to 32 Buffs & 16 Debuffs on a Target (Buffs above, Debuffs below),
|
||||
-- with remaining-time countdowns when ClassicAPI is installed
|
||||
-- Author: lua@lumpn.de (modified)
|
||||
|
||||
|
||||
local lOriginal_TargetDebuffButton_Update = nil;
|
||||
local TARGETFRAMEBUFF_MAX_TARGET_DEBUFFS = 16;
|
||||
local TARGETFRAMEBUFF_MAX_TARGET_BUFFS = 32;
|
||||
|
||||
-- How often the countdowns refresh, in seconds.
|
||||
local TARGETFRAMEBUFF_TIMER_INTERVAL = 0.2;
|
||||
|
||||
local lBuffButtons = {};
|
||||
local lDebuffButtons = {};
|
||||
local lTimerElapsed = 0;
|
||||
local lAuraElapsed = 0;
|
||||
local lHasAuraDelta = false;
|
||||
local lActiveTimers = {};
|
||||
local lActiveTimerCount = 0;
|
||||
local TargetFrameBuff_UpdateTimer;
|
||||
-- Catch refreshes that change timing without a UNIT_AURA event.
|
||||
local TARGETFRAMEBUFF_AURA_INTERVAL = 0.5;
|
||||
local lHelpfulSlots = {};
|
||||
local lHarmfulSlots = {};
|
||||
local lRemovableColor = {r = 1, g = 0.82, b = 0};
|
||||
|
||||
local function TargetFrameBuff_Setting(key)
|
||||
if (TargetFrameBuffDB and TargetFrameBuffDB[key] ~= nil) then
|
||||
return TargetFrameBuffDB[key];
|
||||
end
|
||||
return TargetFrameBuffDefaults[key];
|
||||
end
|
||||
|
||||
|
||||
-- ClassicAPI (a 1.12 client DLL) backports C_UnitAuras, which reports when
|
||||
-- each aura on the target expires. Without it, the addon works as before,
|
||||
-- just without countdowns.
|
||||
local function TargetFrameBuff_HasClassicAPI()
|
||||
return C_UnitAuras and C_UnitAuras.UnitAura and GameTooltip.SetUnitAura and true;
|
||||
end
|
||||
|
||||
|
||||
-- Tooltip for an aura read through ClassicAPI. auraIndex and auraFilter are in
|
||||
-- ClassicAPI's HELPFUL/HARMFUL index space, so the tooltip always matches the
|
||||
-- icon, even for a debuff the server parked in a buff slot.
|
||||
local function TargetFrameBuff_AuraOnEnter()
|
||||
TargetFrameBuff_UpdateClassicAPI();
|
||||
if (not this.auraIndex) then GameTooltip:Hide(); return; end
|
||||
GameTooltip:SetOwner(this, "ANCHOR_BOTTOMRIGHT", 15, -25);
|
||||
GameTooltip:SetUnitAura("target", this.auraIndex, this.auraFilter);
|
||||
GameTooltip:Show();
|
||||
end
|
||||
|
||||
|
||||
-- Add a countdown label to a button. It sits on its own child frame, one level
|
||||
-- above the button, so it draws over the icon and the debuff border.
|
||||
local function TargetFrameBuff_AddTimer(button)
|
||||
local holder = CreateFrame("Frame", nil, button);
|
||||
holder:SetAllPoints(button);
|
||||
holder:SetFrameLevel(button:GetFrameLevel() + 2);
|
||||
|
||||
local timer = holder:CreateFontString(nil, "OVERLAY");
|
||||
timer:SetFont("Fonts\\FRIZQT__.TTF", 9, "OUTLINE");
|
||||
timer:SetPoint("CENTER", button, "CENTER", 0, 0);
|
||||
timer:Hide();
|
||||
|
||||
if (not button.auraCount) then
|
||||
button.auraCount = holder:CreateFontString(nil, "OVERLAY");
|
||||
button.auraCount:SetFont("Fonts\\FRIZQT__.TTF", 9, "OUTLINE");
|
||||
button.auraCount:SetPoint("BOTTOMRIGHT", button, "BOTTOMRIGHT", 1, -1);
|
||||
button.auraCount:Hide();
|
||||
local stockCount = getglobal(button:GetName().."Count");
|
||||
if (stockCount) then stockCount:Hide(); end
|
||||
end
|
||||
button.timer = timer;
|
||||
button.timerText = nil;
|
||||
button.expiration = 0;
|
||||
end
|
||||
|
||||
|
||||
-- hook original update-function
|
||||
function TargetFrameBuff_OnLoad()
|
||||
local i, button, overlay;
|
||||
for i=1, TARGETFRAMEBUFF_MAX_TARGET_BUFFS do
|
||||
lBuffButtons[i] = getglobal("TargetFrameBuff"..i);
|
||||
button = lBuffButtons[i];
|
||||
overlay = CreateFrame("Frame", nil, button);
|
||||
overlay:SetAllPoints(button);
|
||||
overlay:SetFrameLevel(button:GetFrameLevel() + 2);
|
||||
button.auraCount = overlay:CreateFontString(nil, "OVERLAY");
|
||||
button.auraCount:SetFont("Fonts\\FRIZQT__.TTF", 9, "OUTLINE");
|
||||
button.auraCount:SetPoint("BOTTOMRIGHT", button, "BOTTOMRIGHT", 1, -1);
|
||||
button.auraCount:Hide();
|
||||
button.auraBorder = overlay:CreateTexture(nil, "OVERLAY");
|
||||
button.auraBorder:SetTexture("Interface\\Buttons\\UI-Debuff-Overlays");
|
||||
button.auraBorder:SetTexCoord(0.296875, 0.5703125, 0, 0.515625);
|
||||
button.auraBorder:SetPoint("CENTER", button, "CENTER", 0, 0);
|
||||
button.auraBorder:SetWidth(23);
|
||||
button.auraBorder:SetHeight(23);
|
||||
button.auraBorder:Hide();
|
||||
end
|
||||
for i=1, TARGETFRAMEBUFF_MAX_TARGET_DEBUFFS do
|
||||
lDebuffButtons[i] = getglobal("TargetFrameDebuff"..i);
|
||||
end
|
||||
|
||||
if (TargetFrameBuff_HasClassicAPI()) then
|
||||
for i=1, TARGETFRAMEBUFF_MAX_TARGET_BUFFS do
|
||||
button = lBuffButtons[i];
|
||||
TargetFrameBuff_AddTimer(button);
|
||||
button:SetScript("OnEnter", TargetFrameBuff_AuraOnEnter);
|
||||
button:SetScript("OnUpdate", nil);
|
||||
end
|
||||
for i=1, TARGETFRAMEBUFF_MAX_TARGET_DEBUFFS do
|
||||
button = lDebuffButtons[i];
|
||||
TargetFrameBuff_AddTimer(button);
|
||||
button:SetScript("OnEnter", TargetFrameBuff_AuraOnEnter);
|
||||
button:SetScript("OnUpdate", nil);
|
||||
end
|
||||
end
|
||||
|
||||
-- The instance lookup was introduced alongside UNIT_AURA_DELTA. Gate the
|
||||
-- custom registration so older clients never register an unknown event.
|
||||
lHasAuraDelta = TargetFrameBuff_HasClassicAPI() and C_UnitAuras.GetAuraDataByAuraInstanceID and true or false;
|
||||
if (lHasAuraDelta) then TargetFrameBuffFrame:RegisterEvent("UNIT_AURA_DELTA"); end
|
||||
TargetFrameBuffFrame:RegisterEvent("UNIT_AURA");
|
||||
TargetFrameBuffFrame:RegisterEvent("SPELLS_CHANGED");
|
||||
TargetFrameBuffFrame:RegisterEvent("UNIT_PET");
|
||||
TargetFrameBuffFrame:RegisterEvent("PLAYER_TARGET_CHANGED");
|
||||
TargetFrameBuffFrame:SetScript("OnEvent", function()
|
||||
if (event == "PLAYER_TARGET_CHANGED" or event == "SPELLS_CHANGED"
|
||||
or (event == "UNIT_PET" and arg1 == "player")
|
||||
or (event == "UNIT_AURA_DELTA" and arg1 == "target")
|
||||
or (not lHasAuraDelta and event == "UNIT_AURA" and arg1 == "target")) then
|
||||
-- Rebuild the small display as one snapshot. Do not retain the delta's
|
||||
-- reused payload tables; removals can also reorder all filtered indices.
|
||||
TargetFrameBuff_Update();
|
||||
end
|
||||
end);
|
||||
|
||||
lOriginal_TargetDebuffButton_Update = TargetDebuffButton_Update;
|
||||
TargetDebuffButton_Update = TargetFrameBuff_Update;
|
||||
|
||||
lOriginal_TargetDebuffButton_Update();
|
||||
TargetFrameBuff_Restore();
|
||||
TargetFrameBuff_Update();
|
||||
end
|
||||
|
||||
|
||||
-- use extended update-function (original code from FrameXML/TargetFrame.lua)
|
||||
function TargetFrameBuff_Update()
|
||||
if (TargetFrameBuff_HasClassicAPI() and lBuffButtons[1] and lBuffButtons[1].timer) then
|
||||
TargetFrameBuff_UpdateClassicAPI();
|
||||
else
|
||||
TargetFrameBuff_UpdateNative();
|
||||
end
|
||||
TargetFrameBuff_Layout();
|
||||
-- Draw countdowns right away instead of waiting for the next tick.
|
||||
lTimerElapsed = TARGETFRAMEBUFF_TIMER_INTERVAL;
|
||||
end
|
||||
|
||||
|
||||
-- Color a debuff border by debuff type (Magic, Curse, Disease, Poison).
|
||||
-- Without this, a border keeps the color of whatever debuff last used that
|
||||
-- slot, so debuffs show the wrong category.
|
||||
local function TargetFrameBuff_ColorBorder(i, debuffType)
|
||||
local debuffBorder = getglobal("TargetFrameDebuff"..i.."Border");
|
||||
if (debuffBorder and DebuffTypeColor) then
|
||||
local color = DebuffTypeColor[debuffType or "none"] or DebuffTypeColor["none"];
|
||||
if (color) then
|
||||
debuffBorder:SetVertexColor(color.r, color.g, color.b);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function TargetFrameBuff_SetCount(i, count)
|
||||
local stockCount = getglobal("TargetFrameDebuff"..i.."Count");
|
||||
local debuffCount = lDebuffButtons[i].auraCount or stockCount;
|
||||
if (debuffCount ~= stockCount) then stockCount:Hide(); end
|
||||
if (count and count > 1) then
|
||||
debuffCount:SetText(count);
|
||||
debuffCount:Show();
|
||||
else
|
||||
debuffCount:Hide();
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Stock 1.12 path: UnitBuff/UnitDebuff, no timing data.
|
||||
function TargetFrameBuff_UpdateNative()
|
||||
local button, buff, i;
|
||||
for i=1, TARGETFRAMEBUFF_MAX_TARGET_BUFFS do
|
||||
buff = UnitBuff("target", i);
|
||||
button = lBuffButtons[i];
|
||||
if (buff) then
|
||||
getglobal("TargetFrameBuff"..i.."Icon"):SetTexture(buff);
|
||||
button:Show();
|
||||
button.id = i;
|
||||
else
|
||||
button:Hide();
|
||||
end
|
||||
end
|
||||
|
||||
local debuff, debuffApplications, debuffType;
|
||||
for i=1, TARGETFRAMEBUFF_MAX_TARGET_DEBUFFS do
|
||||
debuff, debuffApplications, debuffType = UnitDebuff("target", i);
|
||||
button = lDebuffButtons[i];
|
||||
if (debuff) then
|
||||
TargetFrameBuff_ColorBorder(i, debuffType);
|
||||
TargetFrameBuff_SetCount(i, debuffApplications);
|
||||
getglobal("TargetFrameDebuff"..i.."Icon"):SetTexture(debuff);
|
||||
button:Show();
|
||||
button.id = i;
|
||||
else
|
||||
button:Hide();
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Slot ids are used only during this scan, never cached on buttons.
|
||||
local function TargetFrameBuff_Sweep(button, duration, expiration)
|
||||
local enabled = TargetFrameBuff_Setting("sweeps");
|
||||
if (enabled and not button.cooldown and not button.noCooldown) then
|
||||
local ok, cd = pcall(CreateFrame, "Cooldown", nil, button, "CooldownFrameTemplate");
|
||||
if (ok and cd and cd.SetCooldown) then
|
||||
button.cooldown = cd;
|
||||
cd:SetAllPoints(button);
|
||||
cd:SetFrameLevel(button:GetFrameLevel() + 1);
|
||||
cd:SetReverse(true);
|
||||
cd:SetDrawEdge(true);
|
||||
if (cd.SetHideCountdownNumbers) then cd:SetHideCountdownNumbers(true); end
|
||||
if (cd.SetDrawBling) then cd:SetDrawBling(false); end
|
||||
cd:Hide();
|
||||
else
|
||||
button.noCooldown = true;
|
||||
end
|
||||
end
|
||||
local cd = button.cooldown;
|
||||
if (not cd) then return; end
|
||||
if (not enabled or not duration or duration <= 0 or not expiration or expiration <= GetTime()) then
|
||||
if (cd.Clear) then cd:Clear(); else cd:Hide(); end
|
||||
button.sweepExpiration = nil;
|
||||
elseif (button.sweepExpiration ~= expiration or button.sweepDuration ~= duration) then
|
||||
cd:SetCooldown(expiration - duration, duration);
|
||||
button.sweepExpiration = expiration;
|
||||
button.sweepDuration = duration;
|
||||
end
|
||||
end
|
||||
|
||||
local function TargetFrameBuff_HideAura(button)
|
||||
button.expiration = 0;
|
||||
button.auraIndex = nil;
|
||||
button.auraFilter = nil;
|
||||
button.timerText = nil;
|
||||
button.timer:Hide();
|
||||
if (button.cooldown) then
|
||||
if (button.cooldown.Clear) then button.cooldown:Clear(); else button.cooldown:Hide(); end
|
||||
end
|
||||
button.sweepExpiration = nil;
|
||||
if (button.auraCount) then button.auraCount:Hide(); end
|
||||
if (button.auraBorder) then button.auraBorder:Hide(); end
|
||||
if (GameTooltip:IsOwned(button)) then GameTooltip:Hide(); end
|
||||
button:Hide();
|
||||
end
|
||||
|
||||
local function TargetFrameBuff_ShowAura(button, filter, index, icon, count, dispelType, duration, expiration, removable)
|
||||
if (button.auraIcon ~= icon) then
|
||||
getglobal(button:GetName().."Icon"):SetTexture(icon);
|
||||
button.auraIcon = icon;
|
||||
end
|
||||
button.auraFilter = filter;
|
||||
button.auraIndex = index;
|
||||
button.id = index;
|
||||
button.expiration = expiration or 0;
|
||||
if (button.auraCount) then
|
||||
if (count and count > 1) then
|
||||
button.auraCount:SetText(count);
|
||||
button.auraCount:Show();
|
||||
else
|
||||
button.auraCount:Hide();
|
||||
end
|
||||
end
|
||||
if (button.auraBorder) then
|
||||
local color;
|
||||
if (filter == "HARMFUL") then
|
||||
color = DebuffTypeColor[dispelType or "none"] or DebuffTypeColor["none"];
|
||||
elseif (removable and TargetFrameBuff_Setting("highlight")) then
|
||||
color = lRemovableColor;
|
||||
end
|
||||
if (color) then
|
||||
button.auraBorder:SetVertexColor(color.r, color.g, color.b);
|
||||
button.auraBorder:Show();
|
||||
else
|
||||
button.auraBorder:Hide();
|
||||
end
|
||||
end
|
||||
TargetFrameBuff_Sweep(button, duration, expiration);
|
||||
button:Show();
|
||||
if (TargetFrameBuff_Setting("timers") and button.expiration > GetTime()) then
|
||||
lActiveTimerCount = lActiveTimerCount + 1;
|
||||
lActiveTimers[lActiveTimerCount] = button;
|
||||
TargetFrameBuff_UpdateTimer(button, GetTime());
|
||||
else
|
||||
button.timerText = nil;
|
||||
button.timer:Hide();
|
||||
end
|
||||
if (GameTooltip:IsOwned(button)) then
|
||||
GameTooltip:SetUnitAura("target", index, filter);
|
||||
end
|
||||
end
|
||||
|
||||
-- Refresh icons, counts and expiration together so index reordering cannot
|
||||
-- attach a new aura's timer to an old icon. Older ClassicAPI uses the same
|
||||
-- presentation path, with its positional index getter.
|
||||
function TargetFrameBuff_UpdateClassicAPI()
|
||||
local oldTimerCount = lActiveTimerCount;
|
||||
lActiveTimerCount = 0;
|
||||
lAuraElapsed = 0;
|
||||
local numBuffs, numDebuffs = 0, 0;
|
||||
local pass, filter, slots, n, index, token;
|
||||
local name, icon, count, dispelType, duration, expiration, source, removable;
|
||||
local batched = C_UnitAuras.GetAuraSlots and C_UnitAuras.UnitAuraBySlot;
|
||||
for pass = 1, 2 do
|
||||
if (pass == 1) then
|
||||
filter, slots = "HELPFUL", lHelpfulSlots;
|
||||
else
|
||||
filter, slots = "HARMFUL", lHarmfulSlots;
|
||||
end
|
||||
if (batched) then
|
||||
token, n = C_UnitAuras.GetAuraSlots("target", filter, nil, nil, slots);
|
||||
else
|
||||
n = TARGETFRAMEBUFF_MAX_TARGET_BUFFS + TARGETFRAMEBUFF_MAX_TARGET_DEBUFFS;
|
||||
end
|
||||
for index = 1, n do
|
||||
if (batched) then
|
||||
name, icon, count, dispelType, duration, expiration, source, removable = C_UnitAuras.UnitAuraBySlot("target", slots[index]);
|
||||
else
|
||||
name, icon, count, dispelType, duration, expiration, source, removable = C_UnitAuras.UnitAura("target", index, filter);
|
||||
end
|
||||
if (not name and not icon) then break; end
|
||||
if (pass == 2 and index <= TARGETFRAMEBUFF_MAX_TARGET_DEBUFFS) then
|
||||
numDebuffs = index;
|
||||
TargetFrameBuff_ColorBorder(index, dispelType);
|
||||
TargetFrameBuff_SetCount(index, count);
|
||||
TargetFrameBuff_ShowAura(lDebuffButtons[index], filter, index, icon, count, dispelType, duration, expiration, removable);
|
||||
elseif (numBuffs < TARGETFRAMEBUFF_MAX_TARGET_BUFFS) then
|
||||
numBuffs = numBuffs + 1;
|
||||
TargetFrameBuff_ShowAura(lBuffButtons[numBuffs], filter, index, icon, count, dispelType, duration, expiration, removable);
|
||||
else
|
||||
break;
|
||||
end
|
||||
end
|
||||
end
|
||||
for index = numBuffs + 1, TARGETFRAMEBUFF_MAX_TARGET_BUFFS do
|
||||
TargetFrameBuff_HideAura(lBuffButtons[index]);
|
||||
end
|
||||
for index = numDebuffs + 1, TARGETFRAMEBUFF_MAX_TARGET_DEBUFFS do
|
||||
TargetFrameBuff_HideAura(lDebuffButtons[index]);
|
||||
end
|
||||
for index = lActiveTimerCount + 1, oldTimerCount do
|
||||
lActiveTimers[index] = nil;
|
||||
end
|
||||
end
|
||||
|
||||
function TargetFrameBuff_Layout()
|
||||
local button;
|
||||
|
||||
-- Position buffs above target frame
|
||||
TargetFrameBuff1:ClearAllPoints();
|
||||
TargetFrameBuff1:SetPoint("BOTTOMLEFT", "TargetFrame", "TOPLEFT", 5, -10);
|
||||
|
||||
-- Position debuffs below target frame using default positioning (no gap)
|
||||
-- Layout: 4-4-8 (three rows: 4 debuffs, 4 debuffs, 8 debuffs)
|
||||
-- Debuff 1 uses default WoW positioning to avoid gap with target frame
|
||||
-- TargetFrameDebuff1:ClearAllPoints();
|
||||
-- TargetFrameDebuff1:SetPoint("TOPLEFT", "TargetFrame", "BOTTOMLEFT", 5, 0);
|
||||
|
||||
-- Position remaining buttons in 4-4-8 layout
|
||||
for i=2, TARGETFRAMEBUFF_MAX_TARGET_DEBUFFS do
|
||||
button = lDebuffButtons[i];
|
||||
if button then
|
||||
button:ClearAllPoints();
|
||||
if i == 5 then
|
||||
-- Row 2: Start below button 1
|
||||
button:SetPoint("TOPLEFT", "TargetFrameDebuff1", "BOTTOMLEFT", 0, -2);
|
||||
elseif i == 9 then
|
||||
-- Row 3: Start below button 5
|
||||
button:SetPoint("TOPLEFT", "TargetFrameDebuff5", "BOTTOMLEFT", 0, -2);
|
||||
else
|
||||
-- Position horizontally relative to previous button
|
||||
button:SetPoint("LEFT", "TargetFrameDebuff"..(i-1), "RIGHT", 3, 0);
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- "1h", "12m", "45", ... rounded up, so a countdown never shows 0 while
|
||||
-- the aura is still up.
|
||||
local function TargetFrameBuff_FormatTime(remaining)
|
||||
if (remaining >= 3600) then
|
||||
return math.ceil(remaining / 3600).."h";
|
||||
elseif (remaining >= 60) then
|
||||
return math.ceil(remaining / 60).."m";
|
||||
end
|
||||
return tostring(math.ceil(remaining));
|
||||
end
|
||||
|
||||
|
||||
TargetFrameBuff_UpdateTimer = function(button, now)
|
||||
if (not button:IsShown() or not button.auraIndex) then
|
||||
return;
|
||||
end
|
||||
|
||||
local size = TargetFrameBuff_Setting("timerSize");
|
||||
if (button.timerSize ~= size) then
|
||||
button.timer:SetFont("Fonts\\FRIZQT__.TTF", size, "OUTLINE");
|
||||
button.timerSize = size;
|
||||
end
|
||||
|
||||
local remaining = button.expiration - now;
|
||||
if (not TargetFrameBuff_Setting("timers") or button.expiration <= 0 or remaining <= 0) then
|
||||
if (button.timerText) then
|
||||
button.timerText = nil;
|
||||
button.timer:Hide();
|
||||
end
|
||||
return;
|
||||
end
|
||||
|
||||
local text = TargetFrameBuff_FormatTime(remaining);
|
||||
if (text ~= button.timerText) then
|
||||
button.timerText = text;
|
||||
button.timer:SetText(text);
|
||||
button.timer:Show();
|
||||
end
|
||||
if (remaining <= TargetFrameBuff_Setting("warning")) then
|
||||
button.timer:SetTextColor(1, 0.1, 0.1);
|
||||
else
|
||||
button.timer:SetTextColor(1, 1, 1);
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Driven by TargetFrameBuffFrame, which is parented to TargetFrame, so this
|
||||
-- only runs while you have a target.
|
||||
function TargetFrameBuff_OnUpdate(elapsed)
|
||||
if (not lBuffButtons[1] or not lBuffButtons[1].timer or not TargetFrameBuff_HasClassicAPI()) then
|
||||
return;
|
||||
end
|
||||
elapsed = elapsed or 0;
|
||||
lAuraElapsed = lAuraElapsed + elapsed;
|
||||
if (not lHasAuraDelta and lAuraElapsed >= TARGETFRAMEBUFF_AURA_INTERVAL) then
|
||||
TargetFrameBuff_UpdateClassicAPI();
|
||||
end
|
||||
if (lActiveTimerCount == 0) then
|
||||
lTimerElapsed = 0;
|
||||
return;
|
||||
end
|
||||
lTimerElapsed = lTimerElapsed + elapsed;
|
||||
if (lTimerElapsed < TARGETFRAMEBUFF_TIMER_INTERVAL) then return; end
|
||||
lTimerElapsed = 0;
|
||||
local now = GetTime();
|
||||
local i = lActiveTimerCount;
|
||||
while (i >= 1) do
|
||||
local button = lActiveTimers[i];
|
||||
TargetFrameBuff_UpdateTimer(button, now);
|
||||
if (not button:IsShown() or button.expiration <= now or not TargetFrameBuff_Setting("timers")) then
|
||||
lActiveTimers[i] = lActiveTimers[lActiveTimerCount];
|
||||
lActiveTimers[lActiveTimerCount] = nil;
|
||||
lActiveTimerCount = lActiveTimerCount - 1;
|
||||
end
|
||||
i = i - 1;
|
||||
end
|
||||
end
|
||||
|
||||
function TargetFrameBuff_Restore()
|
||||
-- Resize debuffs to full size
|
||||
local button, debuffFrame;
|
||||
for i=1, TARGETFRAMEBUFF_MAX_TARGET_DEBUFFS do
|
||||
button = getglobal("TargetFrameDebuff"..i);
|
||||
if button then
|
||||
debuffFrame = getglobal("TargetFrameDebuff"..i.."Border");
|
||||
button:SetWidth(21);
|
||||
button:SetHeight(21);
|
||||
if debuffFrame then
|
||||
debuffFrame:SetWidth(23);
|
||||
debuffFrame:SetHeight(23);
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,8 @@
|
||||
## Interface: 1700
|
||||
## Title: TargetFrameBuff v0.9.2
|
||||
## Notes: Shows up to 32 Buffs & 16 Debuffs on a Target, with countdowns when ClassicAPI is installed
|
||||
## Author: lua@lumpn.de (modified)
|
||||
## SavedVariables: TargetFrameBuffDB
|
||||
## OptionalDeps: !!!ClassicAPI
|
||||
## Dependencies:
|
||||
TargetFrameBuff.xml
|
||||
@@ -0,0 +1,266 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||
..\FrameXML\UI.xsd">
|
||||
<Script file="Settings.lua"/>
|
||||
<Script file="TargetFrameBuff.lua"/>
|
||||
|
||||
|
||||
<!-- 11 additional buff buttons (original code from FrameXML/TargetFrame.xml) -->
|
||||
<Button name="TargetFrameBuff6" inherits="TargetBuffButtonTemplate" id="6" parent="TargetFrame">
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOMLEFT" relativeTo="TargetFrameBuff1" relativePoint="TOPLEFT">
|
||||
<Offset>
|
||||
<AbsDimension x="0" y="2"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</Button>
|
||||
<Button name="TargetFrameBuff7" inherits="TargetBuffButtonTemplate" id="7" parent="TargetFrame">
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeTo="TargetFrameBuff6" relativePoint="RIGHT">
|
||||
<Offset>
|
||||
<AbsDimension x="3" y="0"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</Button>
|
||||
<Button name="TargetFrameBuff8" inherits="TargetBuffButtonTemplate" id="8" parent="TargetFrame">
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeTo="TargetFrameBuff7" relativePoint="RIGHT">
|
||||
<Offset>
|
||||
<AbsDimension x="3" y="0"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</Button>
|
||||
<Button name="TargetFrameBuff9" inherits="TargetBuffButtonTemplate" id="9" parent="TargetFrame">
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeTo="TargetFrameBuff8" relativePoint="RIGHT">
|
||||
<Offset>
|
||||
<AbsDimension x="3" y="0"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</Button>
|
||||
<Button name="TargetFrameBuff10" inherits="TargetBuffButtonTemplate" id="10" parent="TargetFrame">
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeTo="TargetFrameBuff9" relativePoint="RIGHT">
|
||||
<Offset>
|
||||
<AbsDimension x="3" y="0"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</Button>
|
||||
<Button name="TargetFrameBuff11" inherits="TargetBuffButtonTemplate" id="11" parent="TargetFrame">
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeTo="TargetFrameBuff10" relativePoint="RIGHT">
|
||||
<Offset>
|
||||
<AbsDimension x="3" y="0"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</Button>
|
||||
<Button name="TargetFrameBuff12" inherits="TargetBuffButtonTemplate" id="12" parent="TargetFrame">
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeTo="TargetFrameBuff11" relativePoint="RIGHT">
|
||||
<Offset>
|
||||
<AbsDimension x="3" y="0"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</Button>
|
||||
<Button name="TargetFrameBuff13" inherits="TargetBuffButtonTemplate" id="13" parent="TargetFrame">
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeTo="TargetFrameBuff12" relativePoint="RIGHT">
|
||||
<Offset>
|
||||
<AbsDimension x="3" y="0"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</Button>
|
||||
<Button name="TargetFrameBuff14" inherits="TargetBuffButtonTemplate" id="14" parent="TargetFrame">
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeTo="TargetFrameBuff13" relativePoint="RIGHT">
|
||||
<Offset>
|
||||
<AbsDimension x="3" y="0"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</Button>
|
||||
<Button name="TargetFrameBuff15" inherits="TargetBuffButtonTemplate" id="15" parent="TargetFrame">
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeTo="TargetFrameBuff14" relativePoint="RIGHT">
|
||||
<Offset>
|
||||
<AbsDimension x="3" y="0"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</Button>
|
||||
<Button name="TargetFrameBuff16" inherits="TargetBuffButtonTemplate" id="16" parent="TargetFrame">
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeTo="TargetFrameBuff15" relativePoint="RIGHT">
|
||||
<Offset>
|
||||
<AbsDimension x="3" y="0"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</Button>
|
||||
|
||||
<!-- 16 more buff buttons for a total of 32: row 3 (17-27) and row 4 (28-32) -->
|
||||
<Button name="TargetFrameBuff17" inherits="TargetBuffButtonTemplate" id="17" parent="TargetFrame">
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOMLEFT" relativeTo="TargetFrameBuff6" relativePoint="TOPLEFT">
|
||||
<Offset>
|
||||
<AbsDimension x="0" y="2"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</Button>
|
||||
<Button name="TargetFrameBuff18" inherits="TargetBuffButtonTemplate" id="18" parent="TargetFrame">
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeTo="TargetFrameBuff17" relativePoint="RIGHT">
|
||||
<Offset>
|
||||
<AbsDimension x="3" y="0"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</Button>
|
||||
<Button name="TargetFrameBuff19" inherits="TargetBuffButtonTemplate" id="19" parent="TargetFrame">
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeTo="TargetFrameBuff18" relativePoint="RIGHT">
|
||||
<Offset>
|
||||
<AbsDimension x="3" y="0"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</Button>
|
||||
<Button name="TargetFrameBuff20" inherits="TargetBuffButtonTemplate" id="20" parent="TargetFrame">
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeTo="TargetFrameBuff19" relativePoint="RIGHT">
|
||||
<Offset>
|
||||
<AbsDimension x="3" y="0"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</Button>
|
||||
<Button name="TargetFrameBuff21" inherits="TargetBuffButtonTemplate" id="21" parent="TargetFrame">
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeTo="TargetFrameBuff20" relativePoint="RIGHT">
|
||||
<Offset>
|
||||
<AbsDimension x="3" y="0"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</Button>
|
||||
<Button name="TargetFrameBuff22" inherits="TargetBuffButtonTemplate" id="22" parent="TargetFrame">
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeTo="TargetFrameBuff21" relativePoint="RIGHT">
|
||||
<Offset>
|
||||
<AbsDimension x="3" y="0"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</Button>
|
||||
<Button name="TargetFrameBuff23" inherits="TargetBuffButtonTemplate" id="23" parent="TargetFrame">
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeTo="TargetFrameBuff22" relativePoint="RIGHT">
|
||||
<Offset>
|
||||
<AbsDimension x="3" y="0"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</Button>
|
||||
<Button name="TargetFrameBuff24" inherits="TargetBuffButtonTemplate" id="24" parent="TargetFrame">
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeTo="TargetFrameBuff23" relativePoint="RIGHT">
|
||||
<Offset>
|
||||
<AbsDimension x="3" y="0"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</Button>
|
||||
<Button name="TargetFrameBuff25" inherits="TargetBuffButtonTemplate" id="25" parent="TargetFrame">
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeTo="TargetFrameBuff24" relativePoint="RIGHT">
|
||||
<Offset>
|
||||
<AbsDimension x="3" y="0"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</Button>
|
||||
<Button name="TargetFrameBuff26" inherits="TargetBuffButtonTemplate" id="26" parent="TargetFrame">
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeTo="TargetFrameBuff25" relativePoint="RIGHT">
|
||||
<Offset>
|
||||
<AbsDimension x="3" y="0"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</Button>
|
||||
<Button name="TargetFrameBuff27" inherits="TargetBuffButtonTemplate" id="27" parent="TargetFrame">
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeTo="TargetFrameBuff26" relativePoint="RIGHT">
|
||||
<Offset>
|
||||
<AbsDimension x="3" y="0"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</Button>
|
||||
<Button name="TargetFrameBuff28" inherits="TargetBuffButtonTemplate" id="28" parent="TargetFrame">
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOMLEFT" relativeTo="TargetFrameBuff17" relativePoint="TOPLEFT">
|
||||
<Offset>
|
||||
<AbsDimension x="0" y="2"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</Button>
|
||||
<Button name="TargetFrameBuff29" inherits="TargetBuffButtonTemplate" id="29" parent="TargetFrame">
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeTo="TargetFrameBuff28" relativePoint="RIGHT">
|
||||
<Offset>
|
||||
<AbsDimension x="3" y="0"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</Button>
|
||||
<Button name="TargetFrameBuff30" inherits="TargetBuffButtonTemplate" id="30" parent="TargetFrame">
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeTo="TargetFrameBuff29" relativePoint="RIGHT">
|
||||
<Offset>
|
||||
<AbsDimension x="3" y="0"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</Button>
|
||||
<Button name="TargetFrameBuff31" inherits="TargetBuffButtonTemplate" id="31" parent="TargetFrame">
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeTo="TargetFrameBuff30" relativePoint="RIGHT">
|
||||
<Offset>
|
||||
<AbsDimension x="3" y="0"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</Button>
|
||||
<Button name="TargetFrameBuff32" inherits="TargetBuffButtonTemplate" id="32" parent="TargetFrame">
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeTo="TargetFrameBuff31" relativePoint="RIGHT">
|
||||
<Offset>
|
||||
<AbsDimension x="3" y="0"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</Button>
|
||||
|
||||
<!-- Defined after the buttons so they all exist when OnLoad runs -->
|
||||
<Frame name="TargetFrameBuffFrame" parent="TargetFrame">
|
||||
<Scripts>
|
||||
<OnLoad>
|
||||
TargetFrameBuff_OnLoad();
|
||||
</OnLoad>
|
||||
<OnUpdate>
|
||||
TargetFrameBuff_OnUpdate(arg1);
|
||||
</OnUpdate>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
|
||||
</Ui>
|
||||
@@ -0,0 +1,19 @@
|
||||
# TargetFrameBuff v0.9.2 validation
|
||||
|
||||
17 simulated Lua 5.1 regression scenarios passed, plus XML parsing. These cover
|
||||
old/native clients, aura reordering, overflow, settings, sweep lifecycle, delta
|
||||
refreshes and full updates, target/pet/spellbook changes, reused delta payloads,
|
||||
no polling in delta mode, and native-number/flash suppression.
|
||||
|
||||
ClassicAPI 1.17.10+ is detected through GetAuraDataByAuraInstanceID, introduced with
|
||||
UNIT_AURA_DELTA. Each relevant delta triggers a full display snapshot rather than
|
||||
retaining event tables or maintaining a second aura cache. This preserves sorted
|
||||
indices for icon/tooltip alignment. Older ClassicAPI polls every 0.5 seconds.
|
||||
Countdown text remains on its existing 0.2-second active-only updater.
|
||||
|
||||
The native countdown is explicitly hidden per sweep; no global CVar is changed.
|
||||
New Clear and SetDrawBling methods are optional, preserving older versions.
|
||||
|
||||
Not tested inside a running WoW client. Verify recasts, target switches, auras
|
||||
expiring, and sweeps with countdownForCooldowns enabled. Keep ShaguTweaks Debuff
|
||||
Timer disabled to prevent overlapping addon countdowns. No FPS gain is claimed.
|
||||
@@ -0,0 +1,110 @@
|
||||
|
||||
unpack = unpack or table.unpack
|
||||
getglobal = function(n) return _G[n] end
|
||||
now = 100
|
||||
GetTime = function() return now end
|
||||
SlashCmdList = {}
|
||||
messages = {}
|
||||
DEFAULT_CHAT_FRAME = {AddMessage=function(self,s) table.insert(messages,s) end}
|
||||
DebuffTypeColor = {none={r=.8,g=0,b=0}, Magic={r=0,g=.5,b=1}, Poison={r=0,g=1,b=0}}
|
||||
frames = {}
|
||||
local M = {}
|
||||
function M:GetName() return self.name end
|
||||
function M:SetAllPoints(x) self.allPoints=x end
|
||||
function M:GetFrameLevel() return self.level or 1 end
|
||||
function M:SetFrameLevel(n) self.level=n end
|
||||
function M:SetFont(path,size,flags) self.font=path; self.fontSize=size end
|
||||
function M:SetPoint(...) self.point={...} end
|
||||
function M:ClearAllPoints() self.point=nil end
|
||||
function M:Show() self.shown=true end
|
||||
function M:Hide() self.shown=false end
|
||||
function M:IsShown() return self.shown end
|
||||
function M:SetWidth(v) self.width=v end
|
||||
function M:SetHeight(v) self.height=v end
|
||||
function M:SetText(v) self.text=v end
|
||||
function M:SetTextColor(...) self.color={...} end
|
||||
function M:SetVertexColor(...) self.color={...} end
|
||||
function M:SetTexture(v) self.texture=v end
|
||||
function M:SetTexCoord(...) self.coords={...} end
|
||||
function M:SetScript(k,v) self.scripts[k]=v end
|
||||
function M:RegisterEvent(k) self.events[k]=true end
|
||||
function M:UnregisterEvent(k) self.events[k]=nil end
|
||||
function M:SetChecked(v) self.checked=v end
|
||||
function M:GetChecked() return self.checked end
|
||||
function M:SetMinMaxValues(a,b) self.min=a; self.max=b end
|
||||
function M:SetValueStep(v) self.step=v end
|
||||
function M:SetValue(v)
|
||||
self.value=v
|
||||
if self.scripts.OnValueChanged then local old=this; this=self; self.scripts.OnValueChanged(); this=old end
|
||||
end
|
||||
function M:GetValue() return self.value end
|
||||
function M:SetCooldown(start,duration) self.start=start; self.duration=duration; self.calls=(self.calls or 0)+1; self:Show() end
|
||||
function M:SetReverse(v) self.reverse=v end
|
||||
function M:SetDrawEdge(v) self.edge=v end
|
||||
function M:IsOwned(button) return self.owner==button end
|
||||
function M:SetOwner(button) self.owner=button end
|
||||
function M:SetUnitAura(unit,index,filter) self.tooltip={unit,index,filter} end
|
||||
local function New(name)
|
||||
local f=setmetatable({name=name,shown=true,scripts={},events={}}, {__index=M})
|
||||
if name then _G[name]=f end
|
||||
return f
|
||||
end
|
||||
function M:CreateFontString(name) return New(name) end
|
||||
function M:CreateTexture(name) return New(name) end
|
||||
function CreateFrame(kind,name,parent,template)
|
||||
if kind=="Cooldown" and noCooldown then error("Unknown frame type") end
|
||||
local f=New(name); f.parent=parent; f.kind=kind
|
||||
table.insert(frames,f)
|
||||
if template=="UICheckButtonTemplate" then New(name.."Text") end
|
||||
if template=="OptionsSliderTemplate" then New(name.."Text"); New(name.."Low"); New(name.."High") end
|
||||
return f
|
||||
end
|
||||
UIParent=New("UIParent")
|
||||
GameTooltip=New("GameTooltip")
|
||||
TargetFrame=New("TargetFrame")
|
||||
TargetFrameBuffFrame=New("TargetFrameBuffFrame")
|
||||
for i=1,32 do New("TargetFrameBuff"..i); New("TargetFrameBuff"..i.."Icon") end
|
||||
for i=1,16 do New("TargetFrameDebuff"..i); New("TargetFrameDebuff"..i.."Icon"); New("TargetFrameDebuff"..i.."Count"); New("TargetFrameDebuff"..i.."Border") end
|
||||
TargetDebuffButton_Update=function() end
|
||||
auras={HELPFUL={},HARMFUL={}}
|
||||
nativeBuffs={}
|
||||
nativeDebuffs={}
|
||||
UnitBuff=function(u,i) return nativeBuffs[i] end
|
||||
UnitDebuff=function(u,i) local a=nativeDebuffs[i]; if a then return unpack(a) end end
|
||||
calls={index=0,slots=0,bySlot=0}
|
||||
local epoch=0
|
||||
local slotMap={}
|
||||
function Aura(icon,count,duration,expiration,removable,dispel)
|
||||
return {icon,icon,count or 1,dispel or "Magic",duration or 30,expiration or 130,"player",removable or false}
|
||||
end
|
||||
function InstallAPI(batched)
|
||||
C_UnitAuras={}
|
||||
C_UnitAuras.UnitAura=function(u,i,filter)
|
||||
calls.index=calls.index+1
|
||||
local a=auras[filter][i]
|
||||
if a then return unpack(a,1,8) end
|
||||
end
|
||||
if batched then
|
||||
C_UnitAuras.GetAuraSlots=function(u,filter,max,token,buf)
|
||||
calls.slots=calls.slots+1; epoch=epoch+1; slotMap={}
|
||||
local n=table.getn(auras[filter])
|
||||
for i=1,n do buf[i]=epoch*1000+i; slotMap[buf[i]]=auras[filter][i] end
|
||||
for i=n+1,table.getn(buf) do buf[i]=nil end
|
||||
buf.n=n
|
||||
return nil,n
|
||||
end
|
||||
C_UnitAuras.UnitAuraBySlot=function(u,id)
|
||||
calls.bySlot=calls.bySlot+1
|
||||
assert(slotMap[id],"stale slot used")
|
||||
return unpack(slotMap[id],1,8)
|
||||
end
|
||||
end
|
||||
end
|
||||
function LoadSettings()
|
||||
for _,f in ipairs(frames) do
|
||||
if f.events.ADDON_LOADED then
|
||||
this=f; arg1="TargetFrameBuff"; f.scripts.OnEvent()
|
||||
end
|
||||
end
|
||||
end
|
||||
function Tick() TargetFrameBuff_Update(); TargetFrameBuff_OnUpdate(.2) end
|
||||
@@ -0,0 +1,219 @@
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import xml.etree.ElementTree as ET
|
||||
sys.path.insert(0, str(Path("work/pythonlibs").resolve()))
|
||||
from lupa.lua51 import LuaRuntime
|
||||
|
||||
addon = Path(__file__).resolve().parents[1]
|
||||
mock = Path(__file__).with_name("mock_wow.lua").read_text(encoding="utf-8-sig")
|
||||
passed = 0
|
||||
def run(name, setup, body):
|
||||
global passed
|
||||
lua = LuaRuntime()
|
||||
lua.execute(mock)
|
||||
lua.execute(setup)
|
||||
lua.execute((addon/"Settings.lua").read_text(encoding="utf-8-sig"))
|
||||
lua.execute((addon/"TargetFrameBuff.lua").read_text(encoding="utf-8-sig"))
|
||||
lua.execute("TargetFrameBuff_OnLoad(); LoadSettings()")
|
||||
lua.execute(body)
|
||||
passed += 1
|
||||
print("PASS", name)
|
||||
|
||||
run("batched scans, reordered icons/timers and fresh tooltip indices",
|
||||
'InstallAPI(true); auras.HELPFUL={Aura("A",1,30,130),Aura("B",1,40,140)}',
|
||||
'''
|
||||
Tick()
|
||||
assert(calls.index==0)
|
||||
assert(TargetFrameBuff1Icon.texture=="A" and TargetFrameBuff1.timer.text=="30")
|
||||
auras.HELPFUL={Aura("B",1,40,150),Aura("A",1,30,125)}
|
||||
Tick()
|
||||
assert(TargetFrameBuff1Icon.texture=="B" and TargetFrameBuff1.timer.text=="50")
|
||||
assert(TargetFrameBuff2Icon.texture=="A" and TargetFrameBuff2.timer.text=="25")
|
||||
this=TargetFrameBuff2; this.scripts.OnEnter()
|
||||
assert(GameTooltip.tooltip[2]==2 and GameTooltip.tooltip[3]=="HELPFUL")
|
||||
auras.HELPFUL={Aura("C",1,50,160)}
|
||||
Tick()
|
||||
assert(TargetFrameBuff1Icon.texture=="C" and not TargetFrameBuff2:IsShown())
|
||||
assert(not GameTooltip:IsShown())
|
||||
''')
|
||||
run("overflow debuff stacks, borders and cleanup on reuse", 'InstallAPI(true)', '''
|
||||
auras.HELPFUL={Aura("Buff",3)}
|
||||
for i=1,18 do auras.HARMFUL[i]=Aura("Debuff"..i,4,20,120,false,"Poison") end
|
||||
Tick()
|
||||
assert(TargetFrameBuff1.auraCount.text==3)
|
||||
assert(TargetFrameBuff2Icon.texture=="Debuff17")
|
||||
assert(TargetFrameBuff2.auraIndex==17 and TargetFrameBuff2.auraFilter=="HARMFUL")
|
||||
assert(TargetFrameBuff2.auraCount.text==4 and TargetFrameBuff2.auraBorder.color[2]==1)
|
||||
this=TargetFrameBuff2; this.scripts.OnEnter()
|
||||
assert(GameTooltip.tooltip[2]==17 and GameTooltip.tooltip[3]=="HARMFUL")
|
||||
auras.HARMFUL={}; auras.HELPFUL={Aura("Buff"),Aura("Plain")}
|
||||
Tick()
|
||||
assert(not TargetFrameBuff2.auraCount:IsShown() and not TargetFrameBuff2.auraBorder:IsShown())
|
||||
assert(not TargetFrameDebuff1:IsShown())
|
||||
''')
|
||||
run("removable buff highlight toggle", 'InstallAPI(true); auras.HELPFUL={Aura("Purge",1,30,130,true)}', '''
|
||||
Tick(); assert(TargetFrameBuff1.auraBorder:IsShown())
|
||||
SlashCmdList.TARGETFRAMEBUFF("highlight off"); Tick()
|
||||
assert(not TargetFrameBuff1.auraBorder:IsShown())
|
||||
SlashCmdList.TARGETFRAMEBUFF("highlight on"); Tick()
|
||||
assert(TargetFrameBuff1.auraBorder:IsShown())
|
||||
''')
|
||||
run("sweep start, refresh, unchanged timer, disable, expiration and missing duration",
|
||||
'InstallAPI(true); auras.HELPFUL={Aura("A",1,30,130)}', '''
|
||||
assert(not TargetFrameBuff1.cooldown)
|
||||
SlashCmdList.TARGETFRAMEBUFF("sweeps on"); Tick()
|
||||
local cd=TargetFrameBuff1.cooldown
|
||||
assert(cd.start==100 and cd.duration==30 and cd.reverse==true and cd.edge==true)
|
||||
local n=cd.calls; Tick(); assert(cd.calls==n)
|
||||
auras.HELPFUL[1][6]=140; Tick(); assert(cd.start==110 and cd.calls==n+1)
|
||||
SlashCmdList.TARGETFRAMEBUFF("sweeps off"); Tick(); assert(not cd:IsShown())
|
||||
SlashCmdList.TARGETFRAMEBUFF("sweeps on"); Tick(); assert(cd:IsShown())
|
||||
now=141; Tick(); assert(not cd:IsShown() and not TargetFrameBuff1.timer:IsShown())
|
||||
auras.HELPFUL[1][5]=0; auras.HELPFUL[1][6]=0; Tick(); assert(not cd:IsShown())
|
||||
''')
|
||||
run("unknown timers, font size and warning settings", 'InstallAPI(true)', '''
|
||||
auras.HELPFUL={Aura("A",1,0,0)}; Tick(); assert(not TargetFrameBuff1.timer:IsShown())
|
||||
auras.HELPFUL={Aura("A",1,10,106)}; Tick()
|
||||
assert(TargetFrameBuff1.timer.color[2]==1)
|
||||
SlashCmdList.TARGETFRAMEBUFF("warning 10"); Tick()
|
||||
assert(TargetFrameBuff1.timer.color[2]==.1)
|
||||
SlashCmdList.TARGETFRAMEBUFF("size 14"); Tick()
|
||||
assert(TargetFrameBuff1.timer.fontSize==14)
|
||||
assert(TargetFrameBuff1.timer.font=="Fonts\\\\FRIZQT__.TTF")
|
||||
SlashCmdList.TARGETFRAMEBUFF("timers off"); Tick(); assert(not TargetFrameBuff1.timer:IsShown())
|
||||
SlashCmdList.TARGETFRAMEBUFF("timers on"); Tick(); assert(TargetFrameBuff1.timer:IsShown())
|
||||
SlashCmdList.TARGETFRAMEBUFF("size 999"); assert(TargetFrameBuffDB.timerSize==14)
|
||||
''')
|
||||
run("older ClassicAPI and unsupported Cooldown fallback", '''
|
||||
InstallAPI(false); noCooldown=true; auras.HELPFUL={Aura("Old",2,20,120)}
|
||||
''', '''
|
||||
SlashCmdList.TARGETFRAMEBUFF("sweeps on"); Tick()
|
||||
assert(TargetFrameBuff1.noCooldown and TargetFrameBuff1.timer.text=="20")
|
||||
assert(calls.index>0 and calls.slots==0)
|
||||
auras.HELPFUL={Aura("Refreshed",1,30,140)}; Tick()
|
||||
assert(TargetFrameBuff1Icon.texture=="Refreshed")
|
||||
''')
|
||||
run("native client fallback and settings persistence", '''
|
||||
nativeBuffs={"Native"}; nativeDebuffs={{"Poison",3,"Poison"}}
|
||||
TargetFrameBuffDB={timers=false,highlight=false,sweeps=false,timerSize=12,warning=7}
|
||||
''', '''
|
||||
Tick()
|
||||
assert(TargetFrameBuff1Icon.texture=="Native")
|
||||
assert(TargetFrameDebuff1Count.text==3)
|
||||
assert(not TargetFrameBuff1.timer)
|
||||
assert(TargetFrameBuffDB.timers==false and TargetFrameBuffDB.timerSize==12)
|
||||
SlashCmdList.TARGETFRAMEBUFF("sweeps on"); Tick()
|
||||
assert(not TargetFrameBuff1.cooldown)
|
||||
''')
|
||||
run("full 48-icon scan call budget and target clear", 'InstallAPI(true)', '''
|
||||
for i=1,32 do auras.HELPFUL[i]=Aura("B"..i) end
|
||||
for i=1,16 do auras.HARMFUL[i]=Aura("D"..i) end
|
||||
calls={index=0,slots=0,bySlot=0}; Tick()
|
||||
assert(calls.index==0 and calls.slots==2 and calls.bySlot==48)
|
||||
assert(TargetFrameBuff32:IsShown() and TargetFrameDebuff16:IsShown())
|
||||
auras={HELPFUL={},HARMFUL={}}; Tick()
|
||||
assert(not TargetFrameBuff1:IsShown() and not TargetFrameDebuff16:IsShown())
|
||||
''')
|
||||
run("options panel controls, cancel and defaults", '''
|
||||
InstallAPI(true)
|
||||
InterfaceOptions_AddCategory=function(p) registered=p end
|
||||
InterfaceOptionsFrame_OpenToCategory=function(p) p.refresh() end
|
||||
''', '''
|
||||
assert(registered.name=="TargetFrameBuff")
|
||||
SlashCmdList.TARGETFRAMEBUFF("")
|
||||
this=TargetFrameBuffOption3; this:SetChecked(true); this.scripts.OnClick()
|
||||
assert(TargetFrameBuffDB.sweeps==true)
|
||||
TargetFrameBuffSlider1:SetValue(13); assert(TargetFrameBuffDB.timerSize==13)
|
||||
registered.cancel()
|
||||
assert(TargetFrameBuffDB.sweeps==false and TargetFrameBuffDB.timerSize==9)
|
||||
TargetFrameBuffDB.timerSize=15; registered.default(); assert(TargetFrameBuffDB.timerSize==9)
|
||||
''')
|
||||
run("corrupt saved settings recover", 'TargetFrameBuffDB={timerSize=999,warning=-1,timers="bad"}', '''
|
||||
assert(TargetFrameBuffDB.timerSize==9 and TargetFrameBuffDB.warning==5 and TargetFrameBuffDB.timers==true)
|
||||
''')
|
||||
run("aura disappears just before hover and lower count overlay", 'InstallAPI(true); auras.HELPFUL={Aura("A")}; auras.HARMFUL={Aura("D",4)}', '''
|
||||
Tick()
|
||||
assert(TargetFrameDebuff1.auraCount.text==4)
|
||||
assert(not TargetFrameDebuff1Count:IsShown())
|
||||
auras.HELPFUL={}
|
||||
this=TargetFrameBuff1; this.scripts.OnEnter()
|
||||
assert(not GameTooltip:IsShown() and not TargetFrameBuff1.auraIndex)
|
||||
''')
|
||||
run("cached countdown avoids aura reads; silent recast fallback", 'InstallAPI(true); auras.HELPFUL={Aura("A",1,30,130)}', '''
|
||||
calls={index=0,slots=0,bySlot=0}
|
||||
now=101; TargetFrameBuff_OnUpdate(.2)
|
||||
assert(calls.slots==0 and TargetFrameBuff1.timer.text=="29")
|
||||
auras.HELPFUL[1][6]=150
|
||||
TargetFrameBuff_OnUpdate(.2); assert(calls.slots==0)
|
||||
TargetFrameBuff_OnUpdate(.1)
|
||||
assert(calls.slots==2 and TargetFrameBuff1.timer.text=="49")
|
||||
''')
|
||||
run("unit event filtering and target change refresh", 'InstallAPI(true); auras.HELPFUL={Aura("A")}', '''
|
||||
calls={index=0,slots=0,bySlot=0}
|
||||
event="UNIT_AURA"; arg1="party1"; TargetFrameBuffFrame.scripts.OnEvent()
|
||||
assert(calls.slots==0)
|
||||
auras.HELPFUL={Aura("B")}; arg1="target"; TargetFrameBuffFrame.scripts.OnEvent()
|
||||
assert(calls.slots==2 and TargetFrameBuff1Icon.texture=="B")
|
||||
auras.HELPFUL={}; event="PLAYER_TARGET_CHANGED"; TargetFrameBuffFrame.scripts.OnEvent()
|
||||
assert(not TargetFrameBuff1:IsShown())
|
||||
''')
|
||||
run("idle countdown stops but fallback discovers new timing", 'InstallAPI(true); auras.HELPFUL={Aura("A",1,0,0)}', '''
|
||||
local writes=0
|
||||
TargetFrameBuff1.timer.SetText=function() writes=writes+1 end
|
||||
calls={index=0,slots=0,bySlot=0}
|
||||
TargetFrameBuff_OnUpdate(.2); TargetFrameBuff_OnUpdate(.2)
|
||||
assert(writes==0 and calls.slots==0)
|
||||
auras.HELPFUL[1][5]=30; auras.HELPFUL[1][6]=130
|
||||
TargetFrameBuff_OnUpdate(.1)
|
||||
assert(writes==1 and TargetFrameBuff1.timer:IsShown())
|
||||
SlashCmdList.TARGETFRAMEBUFF("timers off")
|
||||
writes=0; TargetFrameBuff_OnUpdate(.2)
|
||||
assert(writes==0 and not TargetFrameBuff1.timer:IsShown())
|
||||
''')
|
||||
run("delta mode removes polling and refreshes timing/reordering", 'InstallAPI(true); C_UnitAuras.GetAuraDataByAuraInstanceID=function() end; auras.HELPFUL={Aura("A")}', '''
|
||||
assert(TargetFrameBuffFrame.events.UNIT_AURA_DELTA)
|
||||
calls={index=0,slots=0,bySlot=0}
|
||||
for i=1,20 do TargetFrameBuff_OnUpdate(.2) end
|
||||
assert(calls.slots==0)
|
||||
auras.HELPFUL={Aura("B",3,60,160)}
|
||||
event="UNIT_AURA_DELTA"; arg1="party1"; arg2={updatedAuraInstanceIDs={1}}
|
||||
TargetFrameBuffFrame.scripts.OnEvent(); assert(calls.slots==0)
|
||||
arg1="target"; TargetFrameBuffFrame.scripts.OnEvent()
|
||||
assert(calls.slots==2 and TargetFrameBuff1Icon.texture=="B" and TargetFrameBuff1.timer.text=="1m")
|
||||
arg2.updatedAuraInstanceIDs[1]=999
|
||||
now=101; TargetFrameBuff_OnUpdate(.2)
|
||||
assert(TargetFrameBuff1.timer.text=="59" and calls.slots==2)
|
||||
auras.HELPFUL={}; arg2={isFullUpdate=true}; TargetFrameBuffFrame.scripts.OnEvent()
|
||||
assert(not TargetFrameBuff1:IsShown())
|
||||
''')
|
||||
run("delta mode refreshes target switches and spell/pet capability changes", 'InstallAPI(true); C_UnitAuras.GetAuraDataByAuraInstanceID=function() end', '''
|
||||
auras.HELPFUL={Aura("A")}; event="PLAYER_TARGET_CHANGED"; TargetFrameBuffFrame.scripts.OnEvent()
|
||||
assert(TargetFrameBuff1Icon.texture=="A")
|
||||
calls={index=0,slots=0,bySlot=0}; event="UNIT_AURA"; arg1="target"; TargetFrameBuffFrame.scripts.OnEvent()
|
||||
assert(calls.slots==0)
|
||||
event="SPELLS_CHANGED"; TargetFrameBuffFrame.scripts.OnEvent(); assert(calls.slots==2)
|
||||
event="UNIT_PET"; arg1="player"; TargetFrameBuffFrame.scripts.OnEvent(); assert(calls.slots==4)
|
||||
''')
|
||||
run("modern sweeps suppress native numbers and bling, clear on removal", '''
|
||||
InstallAPI(true); auras.HELPFUL={Aura("A")}
|
||||
local baseCreate=CreateFrame
|
||||
CreateFrame=function(kind,name,parent,template)
|
||||
local f=baseCreate(kind,name,parent,template)
|
||||
if kind=="Cooldown" then
|
||||
f.SetHideCountdownNumbers=function(self,v) self.hideNumbers=v end
|
||||
f.SetDrawBling=function(self,v) self.bling=v end
|
||||
f.Clear=function(self) self.cleared=true; self:Hide() end
|
||||
end
|
||||
return f
|
||||
end
|
||||
''', '''
|
||||
SlashCmdList.TARGETFRAMEBUFF("sweeps on")
|
||||
local cd=TargetFrameBuff1.cooldown
|
||||
assert(cd.hideNumbers==true and cd.bling==false)
|
||||
SlashCmdList.TARGETFRAMEBUFF("sweeps off"); assert(cd.cleared and not cd:IsShown())
|
||||
SlashCmdList.TARGETFRAMEBUFF("sweeps on"); cd.cleared=false
|
||||
auras.HELPFUL={}; Tick(); assert(cd.cleared and not cd:IsShown())
|
||||
''')
|
||||
ET.parse(addon/"TargetFrameBuff.xml")
|
||||
print("PASS XML parsing")
|
||||
print(f"{passed} Lua regression scenarios passed; XML parsed.")
|
||||
Reference in New Issue
Block a user