ca2b78e217
/fui keeps edit mode, reset and list. The thirty diagnostic and tuning functions behind the other subcommands (bar, bags, character, tabs, close, model, minimap, mouse, map, questlog, chat, chatlines, tabard...) go with them, with Panes.Report, the bags read-back and the 205 texts they alone used. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
605 lines
20 KiB
Lua
605 lines
20 KiB
Lua
-- ForeverUI: buff and debuff bars in camelot style (blizzard_buffframe, AuraContainerMixin).
|
|
-- Data and options come from 3.3.5; the client's BuffFrame, enchants and consolidation are hidden.
|
|
-- CancelUnitBuff and CancelItemTempEnchantment are not protected in 3.3.5.
|
|
-- Not in 3.3.5, so not here: external defensives, deadly debuffs, private auras, Bleed type.
|
|
|
|
local ForeverUI = ForeverUI or {}
|
|
_G.ForeverUI = ForeverUI
|
|
|
|
local B = {}
|
|
ForeverUI.Buffs = B
|
|
|
|
local SEP = string.char(92)
|
|
local L = ForeverUI.L
|
|
|
|
local G = {
|
|
width = 30, height = 40, icon = 30,
|
|
border = 40, enchantBorder = 32,
|
|
space = 5,
|
|
collapseW = 15, collapseH = 30, arrowW = 10, arrowH = 16, highlight = 0.4,
|
|
alert = 31, alertDuration = 90,
|
|
period = 1.5, alphaMin = 0.3, alphaMax = 1,
|
|
check = 0.2, tooltip = 0.2,
|
|
tooltipPerRow = 5, tooltipScale = 0.8, tooltipMargin = 16, tooltipRight = 9, tooltipBottom = 2,
|
|
tooltipExitMargin = 10,
|
|
}
|
|
|
|
local BARS = {
|
|
buffs = { name = "ForeverUIBuffFrame", filter = "HELPFUL", max = 32, perRow = 11,
|
|
x = -255, y = -10, caption = L.BUFFS_EDIT_LABEL_BUFFS },
|
|
debuffs = { name = "ForeverUIDebuffFrame", filter = "HARMFUL", max = 16, perRow = 8,
|
|
x = -270, y = -155, caption = L.BUFFS_EDIT_LABEL_DEBUFFS, showType = true },
|
|
}
|
|
|
|
-- Dishonored debuff: the client only shows an aura its own Spell.dbc knows, and no stock spell
|
|
-- fits. ForeverUI draws it from the state mod-pvp-titles-ext sends over the addon channel
|
|
-- (PvPTab.lua, ForeverUI.Dishonor): first among debuffs, no type.
|
|
local DISHONOR_ICON = "Interface" .. SEP .. "Icons" .. SEP .. "Ability_Hunter_MarkedForDeath"
|
|
|
|
-- Border per type (camelot DEBUFF_DISPLAY_INFO): no icon, with icon, colorblind symbol.
|
|
local TYPES = {
|
|
Magic = { "ui-debuff-border-magic-noicon", "ui-debuff-border-magic-icon", "DEBUFF_SYMBOL_MAGIC" },
|
|
Curse = { "ui-debuff-border-curse-noicon", "ui-debuff-border-curse-icon", "DEBUFF_SYMBOL_CURSE" },
|
|
Disease = { "ui-debuff-border-disease-noicon", "ui-debuff-border-disease-icon", "DEBUFF_SYMBOL_DISEASE" },
|
|
Poison = { "ui-debuff-border-poison-noicon", "ui-debuff-border-poison-icon", "DEBUFF_SYMBOL_POISON" },
|
|
None = { "ui-debuff-border-default-noicon" },
|
|
}
|
|
|
|
local ENCHANT_BORDER = "Interface" .. SEP .. "Buttons" .. SEP .. "UI-TempEnchant-Border"
|
|
local CONSOLIDATION = "Interface" .. SEP .. "Buttons" .. SEP .. "BuffConsolidation"
|
|
|
|
-- ---------- Options
|
|
|
|
local function durations() return SHOW_BUFF_DURATIONS == "1" end
|
|
local function consolidateEnabled() return CONSOLIDATE_BUFFS == "1" end
|
|
local function colorblindMode() return ENABLE_COLORBLIND_MODE == "1" end
|
|
-- camelot: collapse and consolidation exclude each other; 3.3.5 has no collapse option.
|
|
local function collapseEnabled() return not consolidateEnabled() end
|
|
|
|
-- Shown unit: the vehicle while in one. PlayerFrame is suppressed, so PlayerFrame.unit
|
|
-- does not follow the vehicle and the unit is computed here.
|
|
function B.unit()
|
|
if UnitHasVehicleUI and UnitHasVehicleUI("player") and UnitExists("vehicle") then
|
|
return "vehicle"
|
|
end
|
|
return "player"
|
|
end
|
|
|
|
-- ---------- Warning blink
|
|
|
|
-- AuraContainerWarningFader: under 31 s, alpha goes 0.3 -> 1 -> 0.3 over 1.5 s, the same
|
|
-- for all icons. rest: seconds left.
|
|
function B.warningAlpha(rest)
|
|
if rest and rest < G.alert then
|
|
local half = G.period / 2
|
|
local t = (GetTime() or 0) % G.period
|
|
local p = (t < half) and (t / half) or ((G.period - t) / half)
|
|
return G.alphaMin + (G.alphaMax - G.alphaMin) * p
|
|
end
|
|
return G.alphaMax
|
|
end
|
|
|
|
-- ---------- One button
|
|
|
|
local function filterOf(b)
|
|
if b.auraType == "Buff" or b.auraType == "TempEnchant" then return "HELPFUL" end
|
|
if b.auraType == "Debuff" then return "HARMFUL" end
|
|
end
|
|
|
|
local function tooltip(b)
|
|
if b.auraType == "TempEnchant" then
|
|
GameTooltip:SetOwner(b, "ANCHOR_BOTTOMLEFT")
|
|
GameTooltip:SetInventoryItem("player", b.info.ID)
|
|
return
|
|
end
|
|
GameTooltip:SetOwner(b, "ANCHOR_BOTTOMLEFT")
|
|
GameTooltip:SetFrameLevel(b:GetFrameLevel() + 2)
|
|
if b.info.dishonor then
|
|
local c = NORMAL_FONT_COLOR
|
|
GameTooltip:SetText(ForeverUI.Dishonor.name(), 1, 1, 1)
|
|
GameTooltip:AddLine(L.BUFFS_DISHONORED_DESC, c.r, c.g, c.b, true)
|
|
GameTooltip:Show()
|
|
return
|
|
end
|
|
GameTooltip:SetUnitAura(b.unit, b.info.index, filterOf(b))
|
|
end
|
|
|
|
local function updateDuration(b, rest)
|
|
local showRegion = rest and durations()
|
|
if showRegion then
|
|
b.Duration:SetFormattedText(SecondsToTimeAbbrev(rest))
|
|
local c = (rest < G.alertDuration) and HIGHLIGHT_FONT_COLOR or NORMAL_FONT_COLOR
|
|
b.Duration:SetVertexColor(c.r, c.g, c.b)
|
|
b.Duration:Show()
|
|
else
|
|
b.Duration:Hide()
|
|
end
|
|
end
|
|
|
|
local function onUpdate(b, elapsed)
|
|
if not b.info then return end
|
|
if b.auraType == "TempEnchant" and B.unit() ~= "player" then
|
|
b:Hide()
|
|
return
|
|
end
|
|
b:SetAlpha(B.warningAlpha(b.rest))
|
|
b.rest = math.max((b.info.expirationTime or 0) - GetTime(), 0)
|
|
updateDuration(b, b.rest)
|
|
b.tooltipTimer = (b.tooltipTimer or 0) - (elapsed or 0)
|
|
if b.tooltipTimer <= 0 then
|
|
b.tooltipTimer = G.tooltip
|
|
if GameTooltip:IsOwned(b) then tooltip(b) end
|
|
end
|
|
end
|
|
|
|
local function applyType(b, auraType)
|
|
b.auraType = auraType
|
|
b.Symbol:Hide()
|
|
if auraType == "Buff" then
|
|
b.DebuffBorder:Hide()
|
|
b.TempEnchantBorder:Hide()
|
|
elseif auraType == "Debuff" then
|
|
ForeverUI.SetAtlas(b.DebuffBorder, TYPES.None[1])
|
|
b.DebuffBorder:Show()
|
|
b.TempEnchantBorder:Hide()
|
|
elseif auraType == "TempEnchant" then
|
|
b.DebuffBorder:Hide()
|
|
b.TempEnchantBorder:Show()
|
|
end
|
|
end
|
|
|
|
local function applyExpiration(b, info)
|
|
if info.expirationTime and info.expirationTime > 0 then
|
|
if durations() then b.Duration:Show() else b.Duration:Hide() end
|
|
b.rest = info.expirationTime - GetTime()
|
|
b:SetScript("OnUpdate", onUpdate)
|
|
else
|
|
b.Duration:Hide()
|
|
b:SetScript("OnUpdate", nil)
|
|
b.rest = nil
|
|
b:SetAlpha(1)
|
|
end
|
|
end
|
|
|
|
-- AuraButtonMixin:Update. info: aura data; showType: debuff border with its type icon.
|
|
function B.populate(b, info, showType)
|
|
applyType(b, info.auraType)
|
|
b.info = info
|
|
b.unit = B.unit()
|
|
if info.auraType == "Debuff" then
|
|
local t = TYPES[info.debuffType or ""] or TYPES.None
|
|
ForeverUI.SetAtlas(b.DebuffBorder, (showType and t[2]) or t[1])
|
|
if colorblindMode() and t[3] then
|
|
b.Symbol:SetText(_G[t[3]] or "")
|
|
b.Symbol:Show()
|
|
end
|
|
end
|
|
applyExpiration(b, info)
|
|
b.Icon:SetTexture(info.texture)
|
|
if (info.count or 0) > 1 then
|
|
b.Count:SetText(info.count)
|
|
b.Count:Show()
|
|
else
|
|
b.Count:Hide()
|
|
end
|
|
if GameTooltip:IsOwned(b) then tooltip(b) end
|
|
end
|
|
|
|
-- AuraButtonArtTemplate: 30 x 40, icon 30 on top, duration below; right-click cancels.
|
|
function B.createButton(parent, name)
|
|
local b = CreateFrame("Button", name, parent)
|
|
b:SetWidth(G.width)
|
|
b:SetHeight(G.height)
|
|
b.Icon = b:CreateTexture(nil, "BACKGROUND")
|
|
b.Icon:SetWidth(G.icon)
|
|
b.Icon:SetHeight(G.icon)
|
|
b.Icon:SetPoint("TOP", b, "TOP", 0, 0)
|
|
b.Duration = b:CreateFontString(nil, "BACKGROUND", "GameFontNormalSmall")
|
|
b.Duration:SetPoint("TOP", b.Icon, "BOTTOM", 0, 0)
|
|
b.Duration:Hide()
|
|
b.DebuffBorder = b:CreateTexture(nil, "OVERLAY")
|
|
b.DebuffBorder:SetWidth(G.border)
|
|
b.DebuffBorder:SetHeight(G.border)
|
|
b.DebuffBorder:SetPoint("CENTER", b.Icon, "CENTER", 0, 0)
|
|
b.TempEnchantBorder = b:CreateTexture(nil, "OVERLAY")
|
|
b.TempEnchantBorder:SetTexture(ENCHANT_BORDER)
|
|
b.TempEnchantBorder:SetWidth(G.enchantBorder)
|
|
b.TempEnchantBorder:SetHeight(G.enchantBorder)
|
|
b.TempEnchantBorder:SetPoint("CENTER", b.Icon, "CENTER", 0, 0)
|
|
b.Symbol = b:CreateFontString(nil, "OVERLAY", "TextStatusBarText")
|
|
b.Symbol:SetPoint("TOPLEFT", b, "TOPLEFT", 2, -2)
|
|
b.Count = b:CreateFontString(nil, "OVERLAY", "NumberFontNormal")
|
|
b.Count:SetPoint("BOTTOMRIGHT", b.Icon, "BOTTOMRIGHT", -2, 2)
|
|
applyType(b, nil)
|
|
b:RegisterForClicks("RightButtonUp")
|
|
b:SetScript("OnClick", function(self, button)
|
|
if button ~= "RightButton" or not self.info then return end
|
|
if self.auraType == "Buff" then
|
|
CancelUnitBuff(self.unit, self.info.index, "HELPFUL")
|
|
elseif self.auraType == "TempEnchant" then
|
|
CancelItemTempEnchantment(self.info.ID == 16 and 1 or 2)
|
|
end
|
|
end)
|
|
b:SetScript("OnEnter", function(self) if self.info then tooltip(self) end end)
|
|
b:SetScript("OnLeave", function() GameTooltip:Hide() end)
|
|
b:Hide()
|
|
return b
|
|
end
|
|
|
|
-- ---------- Grid
|
|
|
|
-- Right to left, then down (AuraContainerMixin). buttons: the buttons to place.
|
|
local function arrange(container, buttons, perRow)
|
|
for i, b in ipairs(buttons) do
|
|
local col, row = (i - 1) % perRow, math.floor((i - 1) / perRow)
|
|
b:ClearAllPoints()
|
|
b:SetPoint("TOPRIGHT", container, "TOPRIGHT",
|
|
-col * (G.width + G.space), -row * (G.height + G.space))
|
|
end
|
|
end
|
|
|
|
-- ---------- One bar
|
|
|
|
local function createBar(key)
|
|
local c = BARS[key]
|
|
local f = CreateFrame("Frame", c.name, UIParent)
|
|
f:SetFrameStrata("LOW")
|
|
f.key, f.c = key, c
|
|
f.container = CreateFrame("Frame", nil, f)
|
|
f.container:SetWidth(1)
|
|
f.container:SetHeight(1)
|
|
f.buttons = {}
|
|
for i = 1, c.max do
|
|
f.buttons[i] = B.createButton(f.container, c.name .. "Button" .. i)
|
|
end
|
|
local rows = math.ceil(c.max / c.perRow)
|
|
local width = (G.width + G.space) * c.perRow
|
|
if key == "buffs" then width = width + G.collapseW end
|
|
f:SetWidth(width)
|
|
f:SetHeight((G.height + G.space) * rows)
|
|
f.auras = {}
|
|
return f
|
|
end
|
|
|
|
-- Collapse button: bag-arrow, flipped when the bar is expanded. t: arrow texture.
|
|
local function orientArrow(t, flipped)
|
|
local e = ForeverUI.AtlasEntry("bag-arrow")
|
|
if not e then return end
|
|
t:SetTexture(e[1])
|
|
local u1, u2, v1, v2 = e[2], e[3], e[4], e[5]
|
|
if flipped then
|
|
t:SetTexCoord(u2, v2, u2, v1, u1, v2, u1, v1)
|
|
else
|
|
t:SetTexCoord(u1, v1, u1, v2, u2, v1, u2, v2)
|
|
end
|
|
end
|
|
|
|
function B.orientCollapse()
|
|
local r = B.buffs.collapse
|
|
-- Icons grow to the left: expanded, the arrow is rotated by pi.
|
|
local expanded = r:GetChecked() and true or false
|
|
for _, t in ipairs({ r:GetNormalTexture(), r:GetPushedTexture(), r:GetHighlightTexture() }) do
|
|
orientArrow(t, expanded)
|
|
end
|
|
end
|
|
|
|
local function createCollapse(f)
|
|
local r = CreateFrame("CheckButton", f.c.name .. "CollapseAndExpandButton", f)
|
|
r:SetWidth(G.collapseW)
|
|
r:SetHeight(G.collapseH)
|
|
r:SetPoint("TOPRIGHT", f, "TOPRIGHT", 0, 0)
|
|
local e = ForeverUI.AtlasEntry("bag-arrow")
|
|
r:SetNormalTexture(e and e[1] or "")
|
|
r:SetPushedTexture(e and e[1] or "")
|
|
r:SetHighlightTexture(e and e[1] or "", "ADD")
|
|
for _, t in ipairs({ r:GetNormalTexture(), r:GetPushedTexture(), r:GetHighlightTexture() }) do
|
|
t:ClearAllPoints()
|
|
t:SetWidth(G.arrowW)
|
|
t:SetHeight(G.arrowH)
|
|
t:SetPoint("CENTER", r, "CENTER", 0, 0)
|
|
end
|
|
r:GetHighlightTexture():SetAlpha(G.highlight)
|
|
r:SetChecked(true)
|
|
r:SetScript("OnClick", function(self)
|
|
B.expanded = self:GetChecked() and true or false
|
|
B.orientCollapse()
|
|
B.update()
|
|
end)
|
|
f.collapse = r
|
|
f.container:SetPoint("TOPRIGHT", r, "TOPLEFT", 0, 0)
|
|
return r
|
|
end
|
|
|
|
-- Consolidation icon (BuffConsolidation) and its popup.
|
|
local function createConsolidation(f)
|
|
local b = B.createButton(f.container, f.c.name .. "ConsolidatedBuffs")
|
|
applyType(b, "Buff")
|
|
b.Icon:SetTexture(CONSOLIDATION)
|
|
b.Icon:SetTexCoord(0.109375, 0.390625, 0.21875, 0.78125)
|
|
b:SetScript("OnClick", nil)
|
|
b:SetScript("OnLeave", nil)
|
|
b.count = 0
|
|
|
|
local tooltipFrame = CreateFrame("Frame", f.c.name .. "ConsolidatedBuffsTooltip", b)
|
|
tooltipFrame:SetFrameStrata("TOOLTIP")
|
|
tooltipFrame:SetClampedToScreen(true)
|
|
tooltipFrame:SetPoint("TOPLEFT", b.Icon, "BOTTOMLEFT", 0, 0)
|
|
tooltipFrame:SetBackdrop({
|
|
bgFile = "Interface" .. SEP .. "Tooltips" .. SEP .. "UI-Tooltip-Background",
|
|
edgeFile = "Interface" .. SEP .. "Tooltips" .. SEP .. "UI-Tooltip-Border",
|
|
tile = true, tileSize = 16, edgeSize = 16,
|
|
insets = { left = 4, right = 4, top = 4, bottom = 4 },
|
|
})
|
|
tooltipFrame:SetBackdropBorderColor(TOOLTIP_DEFAULT_COLOR.r, TOOLTIP_DEFAULT_COLOR.g, TOOLTIP_DEFAULT_COLOR.b)
|
|
tooltipFrame:SetBackdropColor(TOOLTIP_DEFAULT_BACKGROUND_COLOR.r, TOOLTIP_DEFAULT_BACKGROUND_COLOR.g, TOOLTIP_DEFAULT_BACKGROUND_COLOR.b)
|
|
tooltipFrame:Hide()
|
|
local auras = CreateFrame("Frame", nil, tooltipFrame)
|
|
auras:SetScale(G.tooltipScale)
|
|
auras:SetPoint("TOPLEFT", tooltipFrame, "TOPLEFT", G.tooltipMargin, -G.tooltipMargin)
|
|
auras:SetWidth(1)
|
|
auras:SetHeight(1)
|
|
tooltipFrame.auras = auras
|
|
tooltipFrame.buttons = {}
|
|
for i = 1, BARS.buffs.max do
|
|
tooltipFrame.buttons[i] = B.createButton(auras, f.c.name .. "ConsolidatedBuffsTooltipButton" .. i)
|
|
end
|
|
tooltipFrame:SetScript("OnUpdate", function(self)
|
|
local m = G.tooltipExitMargin
|
|
if not self:IsMouseOver(m, -m, -m, m) and not b:IsMouseOver(m, -m, -m, m) then
|
|
self:Hide()
|
|
end
|
|
end)
|
|
b:SetScript("OnEnter", function() B.fillTooltip(); tooltipFrame:Show() end)
|
|
b:SetScript("OnHide", function() tooltipFrame:Hide() end)
|
|
b.tooltipFrame = tooltipFrame
|
|
f.consolidated = b
|
|
return b
|
|
end
|
|
|
|
-- Popup: the hidden buffs, 5 per row, left to right.
|
|
function B.fillTooltip()
|
|
local b = B.buffs.consolidated
|
|
local tooltipFrame = b.tooltipFrame
|
|
local n = 0
|
|
for _, info in ipairs(B.buffs.auras) do
|
|
if info.hideable then
|
|
n = n + 1
|
|
local button = tooltipFrame.buttons[n]
|
|
if not button then break end
|
|
B.populate(button, info)
|
|
button:Show()
|
|
local col, row = (n - 1) % G.tooltipPerRow, math.floor((n - 1) / G.tooltipPerRow)
|
|
button:ClearAllPoints()
|
|
button:SetPoint("TOPLEFT", tooltipFrame.auras, "TOPLEFT",
|
|
col * (G.width + G.space), -row * (G.height + G.space))
|
|
end
|
|
end
|
|
for i = n + 1, #tooltipFrame.buttons do
|
|
tooltipFrame.buttons[i].info = nil
|
|
tooltipFrame.buttons[i]:Hide()
|
|
end
|
|
local l = (G.width + G.space) * math.min(G.tooltipPerRow, n)
|
|
local h = (G.height + G.space) * math.ceil(n / G.tooltipPerRow)
|
|
tooltipFrame:SetWidth((G.tooltipMargin + l) * G.tooltipScale + G.tooltipRight)
|
|
tooltipFrame:SetHeight((G.tooltipMargin + h) * G.tooltipScale + G.tooltipBottom)
|
|
end
|
|
|
|
-- ---------- Data
|
|
|
|
-- Weapon enchants: main hand, then off hand, listed before the buffs.
|
|
local function enchantments(list)
|
|
local now = GetTime()
|
|
local hasMain, expMain, chMain, hasOff, expOff, chOff = GetWeaponEnchantInfo()
|
|
for _, e in ipairs({ { hasMain, expMain, chMain, 16 }, { hasOff, expOff, chOff, 17 } }) do
|
|
if e[1] then
|
|
local rest = (e[2] or 0) / 1000
|
|
local info = {
|
|
auraType = "TempEnchant",
|
|
texture = GetInventoryItemTexture("player", e[4]),
|
|
count = e[3] or 0,
|
|
expirationTime = now + rest,
|
|
ID = e[4],
|
|
}
|
|
info.hideable = rest > G.alertDuration
|
|
table.insert(list, info)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Appends up to max auras of unit to list. hideable: no duration or more than 90 s left.
|
|
local function auras(unit, filter, max, list)
|
|
local now = GetTime()
|
|
for i = 1, max do
|
|
local name, _, icon, stacks, dispelType, duration, expiration = UnitAura(unit, i, filter)
|
|
if not name then break end
|
|
local info = {
|
|
auraType = (filter == "HELPFUL") and "Buff" or "Debuff",
|
|
index = i,
|
|
texture = icon,
|
|
count = stacks or 0,
|
|
debuffType = dispelType,
|
|
duration = duration or 0,
|
|
expirationTime = expiration or 0,
|
|
}
|
|
if filter == "HELPFUL" then
|
|
info.hideable = (info.duration == 0) or (info.expirationTime == 0)
|
|
or ((info.expirationTime - now) > G.alertDuration)
|
|
end
|
|
table.insert(list, info)
|
|
end
|
|
end
|
|
|
|
-- The Dishonored debuff, first, while the state lasts (player only, not in a vehicle).
|
|
local function dishonor(list)
|
|
local state = ForeverUI.Dishonor
|
|
if not state or not state.finish or B.unit() ~= "player" or state.finish <= GetTime() then return end
|
|
table.insert(list, 1, {
|
|
auraType = "Debuff",
|
|
texture = DISHONOR_ICON,
|
|
count = 0,
|
|
duration = 0,
|
|
expirationTime = state.finish,
|
|
dishonor = true,
|
|
})
|
|
end
|
|
|
|
-- ---------- Update
|
|
|
|
B.expanded = true
|
|
|
|
local function expanded()
|
|
if collapseEnabled() then return B.expanded end
|
|
return false
|
|
end
|
|
|
|
function B.updateBuffs()
|
|
local f = B.buffs
|
|
local list = {}
|
|
if B.unit() == "player" then enchantments(list) end
|
|
auras(B.unit(), "HELPFUL", f.c.max, list)
|
|
f.auras = list
|
|
local hideableCount = 0
|
|
for _, info in ipairs(list) do
|
|
if info.hideable then hideableCount = hideableCount + 1 end
|
|
end
|
|
f.hideableCount = hideableCount
|
|
-- Consolidation
|
|
local r = f.consolidated
|
|
r.count = hideableCount
|
|
r.Count:SetText(hideableCount)
|
|
local withConsolidation = consolidateEnabled() and hideableCount > 0
|
|
if withConsolidation then r:Show() else r:Hide() end
|
|
-- Buttons
|
|
local isOpen = expanded()
|
|
local following = 1
|
|
for _, b in ipairs(f.buttons) do
|
|
local info
|
|
while following <= #list do
|
|
local candidate = list[following]
|
|
following = following + 1
|
|
if isOpen or not candidate.hideable then
|
|
info = candidate
|
|
break
|
|
end
|
|
end
|
|
if info then
|
|
B.populate(b, info)
|
|
b:Show()
|
|
else
|
|
b.info = nil
|
|
b:SetScript("OnUpdate", nil)
|
|
b:Hide()
|
|
end
|
|
end
|
|
local toPlace = {}
|
|
if withConsolidation then table.insert(toPlace, r) end
|
|
for _, b in ipairs(f.buttons) do table.insert(toPlace, b) end
|
|
arrange(f.container, toPlace, f.c.perRow)
|
|
-- Collapse button only if something can be hidden
|
|
if collapseEnabled() and hideableCount > 0 then f.collapse:Show() else f.collapse:Hide() end
|
|
f.collapse:SetChecked(B.expanded)
|
|
B.orientCollapse()
|
|
if r.tooltipFrame:IsShown() then B.fillTooltip() end
|
|
-- Watch for hidden buffs that drop under 90 s and return to the bar
|
|
f.pending = (not isOpen and hideableCount > 0) and G.check or nil
|
|
end
|
|
|
|
function B.updateDebuffs()
|
|
local f = B.debuffs
|
|
local list = {}
|
|
auras(B.unit(), "HARMFUL", f.c.max, list)
|
|
dishonor(list)
|
|
f.auras = list
|
|
local toPlace = {}
|
|
for i, b in ipairs(f.buttons) do
|
|
local info = list[i]
|
|
if info then
|
|
B.populate(b, info, f.c.showType)
|
|
b:Show()
|
|
table.insert(toPlace, b)
|
|
else
|
|
b.info = nil
|
|
b:SetScript("OnUpdate", nil)
|
|
b:Hide()
|
|
end
|
|
end
|
|
arrange(f.container, toPlace, f.c.perRow)
|
|
end
|
|
|
|
function B.update()
|
|
B.updateBuffs()
|
|
B.updateDebuffs()
|
|
end
|
|
|
|
-- ---------- Build
|
|
|
|
B.buffs = createBar("buffs")
|
|
B.debuffs = createBar("debuffs")
|
|
createCollapse(B.buffs)
|
|
createConsolidation(B.buffs)
|
|
B.orientCollapse()
|
|
B.debuffs.container:SetPoint("TOPRIGHT", B.debuffs, "TOPRIGHT", 0, 0)
|
|
|
|
for _, key in ipairs({ "buffs", "debuffs" }) do
|
|
local c = BARS[key]
|
|
ForeverUI.Layout.Register(B[key], key, c.caption, "TOPRIGHT", "TOPRIGHT", c.x, c.y)
|
|
end
|
|
|
|
-- The client's BuffFrame, weapon enchants and consolidation
|
|
for _, name in ipairs({ "BuffFrame", "TemporaryEnchantFrame", "ConsolidatedBuffs", "ConsolidatedBuffsTooltip" }) do
|
|
ForeverUI.Suppress(_G[name])
|
|
end
|
|
|
|
-- Interface options (durations, consolidation) go through these two
|
|
if BuffFrame_UpdatePositions then hooksecurefunc("BuffFrame_UpdatePositions", function() B.update() end) end
|
|
if BuffFrame_Update then hooksecurefunc("BuffFrame_Update", function() B.update() end) end
|
|
|
|
local watcher = CreateFrame("Frame")
|
|
watcher:RegisterEvent("UNIT_AURA")
|
|
watcher:RegisterEvent("PLAYER_ENTERING_WORLD")
|
|
watcher:RegisterEvent("UNIT_ENTERED_VEHICLE")
|
|
watcher:RegisterEvent("UNIT_EXITED_VEHICLE")
|
|
watcher:SetScript("OnEvent", function(self, event, unit)
|
|
if event == "UNIT_AURA" then
|
|
if unit == B.unit() then B.update() end
|
|
elseif event == "PLAYER_ENTERING_WORLD" or unit == "player" then
|
|
B.update()
|
|
end
|
|
end)
|
|
|
|
-- Every 0.2 s: weapon enchants (3.3.5 has no event; its TemporaryEnchantFrame polls every
|
|
-- frame) and hidden buffs that return to the collapsed bar.
|
|
local function enchantsSignature()
|
|
if B.unit() ~= "player" then return "" end
|
|
local a, _, ca, g, _, cg = GetWeaponEnchantInfo()
|
|
return tostring(a) .. ":" .. tostring(ca) .. ":" .. tostring(GetInventoryItemTexture("player", 16))
|
|
.. "|" .. tostring(g) .. ":" .. tostring(cg) .. ":" .. tostring(GetInventoryItemTexture("player", 17))
|
|
end
|
|
|
|
watcher.clock = 0
|
|
watcher:SetScript("OnUpdate", function(self, elapsed)
|
|
self.clock = self.clock - (elapsed or 0)
|
|
if self.clock > 0 then return end
|
|
self.clock = G.check
|
|
local s = enchantsSignature()
|
|
if s ~= self.enchants then
|
|
self.enchants = s
|
|
B.update()
|
|
return
|
|
end
|
|
local f = B.buffs
|
|
if f.pending then
|
|
local now = GetTime()
|
|
for _, info in ipairs(f.auras) do
|
|
if info.hideable and info.expirationTime and info.expirationTime > 0
|
|
and (info.expirationTime - now) <= G.alertDuration then
|
|
B.update()
|
|
return
|
|
end
|
|
end
|
|
end
|
|
end)
|
|
B.watcher = watcher
|
|
|
|
B.update()
|