2 Commits
Author SHA1 Message Date
Brues ec410996ec Gate the macro icon hook behind a config flag
Run the Blizzard_MacroUI hook only when C.disabled["macroicons"] is
"1".
2026-08-05 03:44:11 -05:00
Brues b08198145a Add pfUI-native macro icon picker
Replace Blizzard's MacroPopupFrame (the name + icon dialog) with a
custom pfUI picker driven by IconDataProviderMixin, so the full spell +
item + loose icon set is available instead of the stock spell-only list.
It has a searchable, filterable icon grid and saves through
C_Macro.CreateMacro / C_Macro.EditMacro, which take the icon as a texture
string -- so arbitrary icons (including index-less INV_* item icons)
persist. The macro-frame API is identical on the modern (Turtle/Octo)
and stock-vanilla macro UIs, so the picker works on both with no client
detection.

The new macroicons module takes over the New / Change-Icon buttons and
retires the stock popup; the blizzard macro skin now only skins
MacroPopupFrame when that module is disabled.
2026-08-05 03:03:30 -05:00
59 changed files with 8276 additions and 1179 deletions
+1 -1
View File
@@ -6,7 +6,7 @@
[![SuperWoW](https://img.shields.io/badge/SuperWoW-Optional-yellow.svg)](https://github.com/balakethelock/SuperWoW)
[![UnitXP](https://img.shields.io/badge/UnitXP__SP3-Optional-yellow.svg)](https://github.com/brues-code/UnitXP_SP3)
**A pfUI fork specifically optimized for ClassicAPI on [Octo WoW](https://octowow.st/)**
**A pfUI fork specifically optimized for ClassicAPI on [Octo WoW](https://octowow.st/) which requires Nampower and SuperWoW with optional UnitXP_SP3 DLL integration.**
This version includes significant performance improvements and DLL-enhanced features.
+1 -20
View File
@@ -397,33 +397,14 @@ end
-- Returns an itemLink for the given itemname
-- 'name' [string] name of the item
-- returns: [string] entire itemLink for the given item
local itemLinkCache = {}
local itemLinkMisses = {}
local ITEMLINK_MAX_SCANS = 2
function pfUI.api.GetItemLinkByName(name)
-- GetInboxItem() hands us a nil name for attachment-less mail
if not name then return end
-- cache successful resolutions so repeated lookups (e.g. per inbox click) are free
if itemLinkCache[name] then return itemLinkCache[name] end
-- Failures have to be counted, not just retried. An unresolvable name walks
-- the entire id range and finds nothing -- a visible hitch on every tooltip
-- hover. Allow a couple of attempts (rendering the tooltip caches the item,
-- so the next hover usually resolves), then stop scanning for that name.
local misses = itemLinkMisses[name] or 0
if misses >= ITEMLINK_MAX_SCANS then return end
-- Octo/Turtle custom items live well past the 25818 vanilla ceiling
for itemID = 1, 61000 do
for itemID = 1, 25818 do
local itemName = C_Item.GetItemNameByID(itemID)
if itemName and itemName == name then
local _, itemLink = C_Item.GetItemInfo(itemID)
itemLinkCache[name] = itemLink
return itemLink
end
end
itemLinkMisses[name] = misses + 1
end
-- [ FindItem ]
+2 -7
View File
@@ -223,7 +223,7 @@ function pfUI:LoadConfig()
pfUI:UpdateConfig("unitframes", nil, "always2dportrait", "0")
pfUI:UpdateConfig("unitframes", nil, "portraittexture", "1")
pfUI:UpdateConfig("unitframes", nil, "layout", "default")
pfUI:UpdateConfig("unitframes", nil, "rangecheck", "1")
pfUI:UpdateConfig("unitframes", nil, "rangecheck", "0")
pfUI:UpdateConfig("unitframes", nil, "rangecheck_mode", "vanilla")
pfUI:UpdateConfig("unitframes", nil, "rangecheck_distance", "40")
pfUI:UpdateConfig("unitframes", nil, "buffdetect", "0")
@@ -554,6 +554,7 @@ function pfUI:LoadConfig()
pfUI:UpdateConfig("unitframes", unit, "glowaggro", "1")
pfUI:UpdateConfig("unitframes", unit, "glowcombat", "1")
pfUI:UpdateConfig("unitframes", unit, "showtooltip", "1")
pfUI:UpdateConfig("unitframes", unit, "healthcolor", "1")
pfUI:UpdateConfig("unitframes", unit, "powercolor", "1")
pfUI:UpdateConfig("unitframes", unit, "levelcolor", "1")
@@ -784,10 +785,6 @@ function pfUI:LoadConfig()
pfUI:UpdateConfig("tooltip", nil, "extguild", "1")
pfUI:UpdateConfig("tooltip", nil, "itemid", "0")
pfUI:UpdateConfig("tooltip", nil, "movespeed", "0")
pfUI:UpdateConfig("tooltip", nil, "aurasource", "0")
pfUI:UpdateConfig("tooltip", nil, "spellid", "0")
pfUI:UpdateConfig("tooltip", nil, "unitid", "0")
pfUI:UpdateConfig("tooltip", nil, "showbuffs", "0")
pfUI:UpdateConfig("tooltip", nil, "alpha", "0.8")
pfUI:UpdateConfig("tooltip", nil, "alwaysperc", "0")
pfUI:UpdateConfig("tooltip", "compare", "basestats", "1")
@@ -958,7 +955,6 @@ function pfUI:LoadConfig()
pfUI:UpdateConfig("questlog", nil, "showQuestLevels", "0")
pfUI:UpdateConfig("character", "inventory", "durability", "1")
pfUI:UpdateConfig("character", "inventory", "equipflyout", "0")
pfUI:UpdateConfig("character", "reputation", "repRequired", "1")
pfUI:UpdateConfig("thirdparty", nil, "chatbg", "1")
pfUI:UpdateConfig("thirdparty", nil, "showmeter", "0")
@@ -966,7 +962,6 @@ function pfUI:LoadConfig()
pfUI:UpdateConfig("thirdparty", "dpsmate", "dock", "0")
pfUI:UpdateConfig("thirdparty", "shagudps", "skin", "0")
pfUI:UpdateConfig("thirdparty", "shagudps", "dock", "0")
pfUI:UpdateConfig("thirdparty", "greedmeter", "dock", "0")
pfUI:UpdateConfig("thirdparty", "swstats", "skin", "0")
pfUI:UpdateConfig("thirdparty", "swstats", "dock", "0")
pfUI:UpdateConfig("thirdparty", "ktm", "skin", "0")
+4 -250
View File
@@ -587,7 +587,7 @@ end
function pfUI.api.SetHighlight(frame, cr, cg, cb)
if not frame then return end
if not cr or not cg or not cb then
cr, cg, cb = PFUI_CLASS_COLORS[UnitClassBase('player')]:GetRGB()
cr, cg, cb = GetClassColor(UnitClassBase('player'))
end
frame.cr, frame.cg, frame.cb = cr, cg, cb, ca
@@ -632,7 +632,7 @@ function pfUI.api.SkinButton(button, cr, cg, cb, icon, disableHighlight)
if not b then b = button end
if not b then return end
if not cr or not cg or not cb then
cr, cg, cb = PFUI_CLASS_COLORS[UnitClassBase('player')]:GetRGB()
cr, cg, cb = GetClassColor(UnitClassBase('player'))
end
pfUI.api.CreateBackdrop(b, nil, true)
b:SetNormalTexture("")
@@ -709,7 +709,7 @@ end
function pfUI.api.SkinRotateButton(button)
pfUI.api.CreateBackdrop(button)
local cr, cg, cb = PFUI_CLASS_COLORS[UnitClassBase('player')]:GetRGB()
local cr, cg, cb = GetClassColor(UnitClassBase('player'))
local btnW, btnH = button:GetSize()
button:SetSize(btnW - 18, btnH - 18)
@@ -907,7 +907,7 @@ function pfUI.api.SkinDropDown(frame, cr, cg, cb, useSmall)
end
if not cr or not cg or not cb then
cr, cg, cb = PFUI_CLASS_COLORS[UnitClassBase('player')]:GetRGB()
cr, cg, cb = GetClassColor(UnitClassBase('player'))
end
SetHighlight(button, cr, cg, cb)
@@ -1246,249 +1246,3 @@ function pfUI.api.SkinMoneyInputFrame(frame)
copperIcon:ClearAllPoints()
copperIcon:SetPoint("LEFT", copper_editbox, "RIGHT", 2, 0)
end
-- Shared icon picker widget. Builds a name field, a "currently selected"
-- preview, and a 10x8 icon grid backed by IconDataProviderMixin with an
-- All/Spells/Items filter and a name search. Used by the macro icon picker
-- and the equipment manager's name/icon popup. The widgets attach to
-- `parent`; the caller owns the surrounding frame, its OK/Cancel buttons,
-- and the save flow. Selection is tracked by texture PATH (not index) so it
-- survives filter changes -- a spell icon you picked still saves after you
-- switch the filter to "Items" and it drops out of the visible grid.
--
-- name global-name prefix for the scroll frame and editboxes
-- parent the popup frame to attach the widgets to
-- extraType IconDataProviderExtraType.* seed (Spellbook / Equipment)
-- nameLabelText header shown above the name field
--
-- Returns a handle:
-- .editbox name EditBox (caller wires enter/escape)
-- .search search EditBox
-- .GetIcon() current selected texture path
-- .SetIcon(path) set selection (nil -> question mark)
-- .Refresh() ensure provider, redraw grid + preview
-- .SetIconAreaShown(shown) show/hide the grid, filter, search, and labels
function pfUI.api.CreateIconPicker(name, parent, extraType, nameLabelText)
local QUESTION_MARK = "INTERFACE\\ICONS\\INV_MISC_QUESTIONMARK"
local ICON_GRID_COLS = 10
local ICON_GRID_ROWS = 8
local ICON_BTN_SIZE = 36
local ICON_BTN_PAD = 6
local provider = nil
local selectedIconPath = QUESTION_MARK
local currentFilter = "all"
local searchText = ""
local filtered = nil -- provider indices matching the search, or nil when empty
local RefreshIconGrid, RebuildFilter
local function EnsureProvider()
if not provider then
provider = CreateAndInitFromMixin(IconDataProviderMixin, extraType)
end
end
-- Normalize a texture to an uppercase basename so matching works across
-- sources: GetMacroInfo / stored set icons return "Interface\Icons\<name>"
-- while provider paths are uppercase-prefixed for base icons and
-- mixed-case for spellbook extras.
local function IconKey(path)
if type(path) ~= "string" then return path end
return string.gsub(string.upper(path), "^.*[\\/]", "")
end
parent.nameLabel = parent:CreateFontString(nil, "OVERLAY", "GameFontNormal")
parent.nameLabel:SetPoint("TOPLEFT", parent, "TOPLEFT", 14, -20)
parent.nameLabel:SetText(nameLabelText)
parent.editbox = CreateFrame("EditBox", name.."Name", parent, "InputBoxTemplate")
parent.editbox:SetSize(280, 20)
parent.editbox:SetPoint("TOPLEFT", parent, "TOPLEFT", 14, -38)
parent.editbox:SetAutoFocus(false)
parent.editbox:SetMaxLetters(16)
CreateBackdrop(parent.editbox)
parent.selectedLabel = parent:CreateFontString(nil, "OVERLAY", "GameFontNormal")
parent.selectedLabel:SetPoint("TOPRIGHT", parent, "TOPRIGHT", -14, -14)
parent.selectedLabel:SetText(ICON_SELECTION_TITLE_CURRENT)
parent.selectedLabel:SetTextColor(1, 0.82, 0)
parent.selectedPreview = CreateFrame("Frame", nil, parent)
parent.selectedPreview:SetSize(42, 42)
parent.selectedPreview:SetPoint("TOPRIGHT", parent, "TOPRIGHT", -14, -30)
CreateBackdrop(parent.selectedPreview)
parent.selectedPreview.tex = parent.selectedPreview:CreateTexture(nil, "ARTWORK")
parent.selectedPreview.tex:SetAllPoints(parent.selectedPreview)
parent.selectedPreview.tex:SetTexCoord(.08, .92, .08, .92)
parent.iconLabel = parent:CreateFontString(nil, "OVERLAY", "GameFontNormal")
parent.iconLabel:SetPoint("TOPLEFT", parent, "TOPLEFT", 14, -86)
parent.iconLabel:SetText(MACRO_POPUP_CHOOSE_ICON)
-- Icon grid + scroll
local iconScroll = CreateFrame("ScrollFrame", name.."Scroll", parent, "FauxScrollFrameTemplate")
iconScroll:SetPoint("TOPLEFT", parent, "TOPLEFT", 14, -116)
iconScroll:SetWidth(ICON_GRID_COLS * (ICON_BTN_SIZE + ICON_BTN_PAD) - ICON_BTN_PAD)
iconScroll:SetHeight(ICON_GRID_ROWS * (ICON_BTN_SIZE + ICON_BTN_PAD) - ICON_BTN_PAD)
local scrollbar = _G[name.."ScrollScrollBar"]
scrollbar:ClearAllPoints()
scrollbar:SetPoint("TOPLEFT", iconScroll, "TOPRIGHT", 8, -16)
scrollbar:SetPoint("BOTTOMLEFT", iconScroll, "BOTTOMRIGHT", 8, 16)
SkinScrollbar(scrollbar)
-- Filter dropdown: All / Spells / Items
local filterDropdown = CreateFrame("Frame", name.."Filter", parent, "UIDropDownMenuTemplate")
filterDropdown:SetPoint("TOPRIGHT", parent, "TOPRIGHT", 0, -80)
local function ApplyFilter(value)
currentFilter = value
UIDropDownMenu_SetSelectedValue(filterDropdown, value)
if provider then
if value == "spells" then provider:SetIconTypes({ IconDataProviderIconType.Spell })
elseif value == "items" then provider:SetIconTypes({ IconDataProviderIconType.Item })
else provider:SetIconTypes(nil) end
RebuildFilter()
RefreshIconGrid()
end
end
UIDropDownMenu_Initialize(filterDropdown, function()
local info
info = {}; info.text = ICON_FILTER_ALL; info.value = "all"
info.func = function() ApplyFilter("all") end
info.checked = currentFilter == "all"
UIDropDownMenu_AddButton(info)
info = {}; info.text = ICON_FILTER_SPELL; info.value = "spells"
info.func = function() ApplyFilter("spells") end
info.checked = currentFilter == "spells"
UIDropDownMenu_AddButton(info)
info = {}; info.text = ICON_FILTER_ITEM; info.value = "items"
info.func = function() ApplyFilter("items") end
info.checked = currentFilter == "items"
UIDropDownMenu_AddButton(info)
end)
UIDropDownMenu_SetWidth(120, filterDropdown)
UIDropDownMenu_SetSelectedValue(filterDropdown, "all")
SkinDropDown(filterDropdown)
local iconButtons = {}
for r = 1, ICON_GRID_ROWS do
for c = 1, ICON_GRID_COLS do
local i = (r - 1) * ICON_GRID_COLS + c
local btn = CreateFrame("Button", nil, parent)
btn:SetSize(ICON_BTN_SIZE, ICON_BTN_SIZE)
btn:SetPoint("TOPLEFT", iconScroll, "TOPLEFT", (c-1) * (ICON_BTN_SIZE + ICON_BTN_PAD), -(r-1) * (ICON_BTN_SIZE + ICON_BTN_PAD))
CreateBackdrop(btn)
btn.texture = btn:CreateTexture(nil, "ARTWORK")
btn.texture:SetAllPoints(btn)
btn.texture:SetTexCoord(.08, .92, .08, .92)
btn:SetScript("OnClick", function()
if this.iconIndex and provider then
local path = provider:GetIconByIndex(this.iconIndex)
if path then selectedIconPath = path end
RefreshIconGrid()
end
end)
btn:SetScript("OnEnter", function()
if not this.iconIndex or not provider then return end
local path = provider:GetIconByIndex(this.iconIndex)
if type(path) == "string" then
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText(IconKey(path), 1, 1, 1)
GameTooltip:Show()
end
end)
btn:SetScript("OnLeave", GameTooltip_Hide)
iconButtons[i] = btn
end
end
-- Rebuild the search-filtered index list (provider indices whose icon
-- basename contains the search text); nil when the search box is empty.
function RebuildFilter()
EnsureProvider()
if searchText == "" then
filtered = nil
return
end
filtered = {}
for i = 1, provider:GetNumIcons() do
local path = provider:GetIconByIndex(i)
if type(path) == "string" and string.find(IconKey(path), searchText, 1, true) then
table.insert(filtered, i)
end
end
end
function RefreshIconGrid()
EnsureProvider()
local numIcons = filtered and table.getn(filtered) or provider:GetNumIcons()
local numRows = math.ceil(numIcons / ICON_GRID_COLS)
FauxScrollFrame_Update(iconScroll, numRows, ICON_GRID_ROWS, ICON_BTN_SIZE + ICON_BTN_PAD)
local offset = FauxScrollFrame_GetOffset(iconScroll)
for i = 1, ICON_GRID_ROWS * ICON_GRID_COLS do
local listIdx = i + offset * ICON_GRID_COLS
local btn = iconButtons[i]
if listIdx <= numIcons then
btn:Show()
local providerIdx = filtered and filtered[listIdx] or listIdx
btn.iconIndex = providerIdx
local path = provider:GetIconByIndex(providerIdx)
btn.texture:SetTexture(path)
if IconKey(path) == IconKey(selectedIconPath) then
btn.backdrop:SetBackdropBorderColor(1, 0.82, 0, 1)
else
btn.backdrop:SetBackdropBorderColor(pfUI.cache.er, pfUI.cache.eg, pfUI.cache.eb, pfUI.cache.ea)
end
else
btn:Hide()
btn.iconIndex = nil
end
end
parent.selectedPreview.tex:SetTexture(selectedIconPath)
end
iconScroll:SetScript("OnVerticalScroll", function()
FauxScrollFrame_OnVerticalScroll(ICON_BTN_SIZE + ICON_BTN_PAD, function() RefreshIconGrid() end)
end)
-- Search box (bottom-left) filters the grid by icon name.
parent.searchLabel = parent:CreateFontString(nil, "OVERLAY", "GameFontNormal")
parent.searchLabel:SetPoint("BOTTOMLEFT", parent, "BOTTOMLEFT", 16, 18)
parent.searchLabel:SetText(SEARCH)
parent.search = CreateFrame("EditBox", name.."Search", parent, "InputBoxTemplate")
parent.search:SetSize(180, 20)
parent.search:SetPoint("LEFT", parent.searchLabel, "RIGHT", 10, 0)
parent.search:SetAutoFocus(false)
CreateBackdrop(parent.search)
parent.search:SetScript("OnEscapePressed", function() this:ClearFocus() end)
parent.search:SetScript("OnTextChanged", function()
searchText = strupper(strtrim(this:GetText()))
RebuildFilter()
scrollbar:SetValue(0)
RefreshIconGrid()
end)
-- Release the provider when the popup closes so its icon cache GCs.
parent:HookScript("OnHide", function()
if provider then provider:Release(); provider = nil end
end)
local handle = { editbox = parent.editbox, search = parent.search }
function handle.GetIcon() return selectedIconPath end
function handle.SetIcon(path) selectedIconPath = path or QUESTION_MARK end
function handle.Refresh() RefreshIconGrid() end
function handle.SetIconAreaShown(shown)
local method = shown and "Show" or "Hide"
iconScroll[method](iconScroll)
for _, b in ipairs(iconButtons) do b[method](b) end
parent.iconLabel[method](parent.iconLabel)
parent.selectedLabel[method](parent.selectedLabel)
parent.selectedPreview[method](parent.selectedPreview)
filterDropdown[method](filterDropdown)
parent.searchLabel[method](parent.searchLabel)
parent.search[method](parent.search)
end
return handle
end
+20 -90
View File
@@ -140,43 +140,32 @@ function pfUI.api.GetUnitStats(unitstr)
end
local aggrodata = { }
local aggroScan = nil -- static { u, t, tt } triples, built once so the hot scan allocates nothing
function pfUI.api.UnitHasAggro(unit)
local now = GetTime()
local data = aggrodata[unit]
-- Cache positive results 1s (so aggro clears fast) AND negative results 0.3s
-- (so we don't rescan the whole unit table on every call while nothing has aggro).
if data and now < data.check + (data.state > 0 and 1 or 0.3) then
return data.state
-- Only cache positive results to allow instant detection when aggro changes
if aggrodata[unit] and aggrodata[unit].state > 0 and GetTime() < aggrodata[unit].check + 1 then
return aggrodata[unit].state
end
if not data then data = { }; aggrodata[unit] = data end
data.check = now
data.state = 0
aggrodata[unit] = aggrodata[unit] or { }
aggrodata[unit].check = GetTime()
aggrodata[unit].state = 0
if UnitExists(unit) and UnitIsFriend(unit, "player") then
-- pfValidUnits never changes after load, so precompute the "<u>target" /
-- "<u>targettarget" token strings once instead of concatenating per call.
if not aggroScan then
aggroScan = {}
for u in pairs(pfValidUnits) do
local t = u .. "target"
table.insert(aggroScan, { u = u, t = t, tt = t .. "target" })
end
end
for u in pairs(pfValidUnits) do
local t = u .. "target"
local tt = t .. "target"
for i = 1, table.getn(aggroScan) do
local s = aggroScan[i]
if UnitExists(s.t) and UnitIsUnit(s.t, unit) and UnitCanAttack(s.u, unit) then
data.state = data.state + 1
if UnitExists(t) and UnitIsUnit(t, unit) and UnitCanAttack(u, unit) then
aggrodata[unit].state = aggrodata[unit].state + 1
end
if UnitExists(s.tt) and UnitIsUnit(s.tt, unit) and UnitCanAttack(s.t, unit) then
data.state = data.state + 1
if UnitExists(tt) and UnitIsUnit(tt, unit) and UnitCanAttack(t, unit) then
aggrodata[unit].state = aggrodata[unit].state + 1
end
end
end
return data.state
return aggrodata[unit].state
end
pfUI.uf.glow = CreateFrame("Frame", nil, UIParent)
@@ -413,7 +402,7 @@ function pfUI.uf:UpdateConfig()
if tonumber(f.config.pheight) < 0 then f.power:Hide() end
pfUI.api.CreateBackdrop(f.power, default_border)
f.power.bar:SetFrameLevel(f.power.backdrop:GetFrameLevel() + 1)
f.power.bar:SetFrameLevel(f.power:GetFrameLevel() + 1)
f.power.bar:SetStatusBarTexture(pfUI.media[f.config.pbartexture])
f.power.bar:SetAllPoints(f.power)
@@ -1220,72 +1209,16 @@ function pfUI.uf:EnableEvents()
'MiddleButtonUp', 'Button4Up', 'Button5Up')
end
-- ============================================================
-- Clique compatibility
-- Clique's pfUI plugin replaces pfUI.uf.ClickAction and expects an
-- OnClick script to call it (with `this` = frame, `arg1` = button). The
-- secure attribute path replaced that dispatch, so when Clique is loaded
-- EnableScripts routes clicks through this legacy Lua path instead. The
-- secure path is skipped there because type1="target" switches the target
-- before Clique can cast.
-- ============================================================
function pfUI.uf.OnClick()
if not this.label and this.unitname then
TargetByName(this.unitname, true)
else
pfUI.uf:ClickAction(arg1)
end
end
function pfUI.uf:ClickAction(button)
local label = this.label or ""
local id = this.id or ""
local unitstr = label .. id
if SpellIsTargeting() and button == "RightButton" then
SpellStopTargeting()
return
end
if SpellIsTargeting() and button == "LeftButton" then
SpellTargetUnit(unitstr)
elseif CursorHasItem() then
DropItemOnUnit(unitstr)
end
-- right-click opens the standard unit menu (ClassicAPI resolves the type)
if button == "RightButton" then
ClassicAPI_ToggleUnitMenu(unitstr)
return
end
-- drop food on petframe
if label == "pet" and CursorHasItem() then
if UnitClassBase("player") == "HUNTER" then
DropItemOnUnit("pet")
return
end
end
-- default click
TargetUnit(unitstr)
end
function pfUI.uf:EnableScripts()
local f = self
if IsAddOnLoaded("Clique") then
f:SetScript("OnClick", pfUI.uf.OnClick)
else
f:SetAttribute("type1", "target")
f:SetAttribute("type2", "menu")
f:EnableClickCast()
end
f:SetAttribute("type1", "target")
f:SetAttribute("type2", "menu")
f:SetScript("OnShow", pfUI.uf.OnShow)
f:SetScript("OnEvent", pfUI.uf.OnEvent)
f:SetScript("OnUpdate", pfUI.uf.OnUpdate)
f:EnableClickCast()
-- add frame to visibility refresh handler
visibilityscan.frames[f] = true
@@ -1420,7 +1353,7 @@ function pfUI.uf:CreateUnitFrame(unit, id, config, tick)
f.group:SetFont(pfUI.font_unit, 8, "OUTLINE")
f.group:SetTextColor(1,1,1,.8)
f.group:SetHeight(16)
f.group:SetText(T["Group"] .. " " .. group)
f.group:SetText("Group " .. group)
f.group:Hide()
end
@@ -2152,9 +2085,6 @@ function pfUI.uf:EnableClickCast()
self:SetAttribute(prefix .. "type" .. bid, "target")
elseif low == "focus" then
self:SetAttribute(prefix .. "type" .. bid, "focus")
elseif string.find(low, "^macro:") then
self:SetAttribute(prefix .. "type" .. bid, "macro")
self:SetAttribute(prefix .. "macro" .. bid, string.gsub(string.sub(action, 7), "^%s+", ""))
elseif string.find(action, "^/") then
self:SetAttribute(prefix .. "type" .. bid, "macro")
self:SetAttribute(prefix .. "macrotext" .. bid, action)
+1065
View File
File diff suppressed because it is too large Load Diff
+1067
View File
File diff suppressed because it is too large Load Diff
+1060
View File
File diff suppressed because it is too large Load Diff
+1056
View File
File diff suppressed because it is too large Load Diff
+1026
View File
File diff suppressed because it is too large Load Diff
+1063
View File
File diff suppressed because it is too large Load Diff
+1080
View File
File diff suppressed because it is too large Load Diff
+1 -4
View File
@@ -51,12 +51,10 @@ pfUI_translation["deDE"] = {
["Auto Sell Grey Items"] = nil,
["Average Per Hour"] = nil,
["Background Color"] = nil,
["Bags"] = "Taschen",
["Bags & Bank"] = nil,
["Bags Border Size"] = nil,
["Bagslots Per Row"] = nil,
["Bagspace"] = nil,
["Bank"] = "Bank",
["Banker"] = nil,
["Bankslots Per Row"] = nil,
["Bar Background"] = nil,
@@ -292,6 +290,7 @@ pfUI_translation["deDE"] = {
["Enable Mana Ticks"] = nil,
["Enable Micro Bar"] = nil,
["Enable Mouselook With Right Click"] = nil,
["Enable Mouseover Tooltip"] = nil,
["Enable Movable Bags"] = nil,
["Enable Offscreen Frame Positions"] = nil,
["Enable Overlap"] = nil,
@@ -312,7 +311,6 @@ pfUI_translation["deDE"] = {
["Encode"] = nil,
["Ended"] = nil,
["Energy Color"] = nil,
["Equipped"] = "Angelegt",
["Equipped Item Color"] = nil,
["Estimate Debuffs"] = nil,
["Estimate Enemy Health Points"] = nil,
@@ -821,7 +819,6 @@ pfUI_translation["deDE"] = {
["Top Actionbar"] = nil,
["Top Left"] = nil,
["Top Right"] = nil,
["Total"] = "Gesamt",
["Total Gold"] = nil,
["Totem Direction"] = nil,
["Totem Icons"] = nil,
+1 -10
View File
@@ -52,19 +52,15 @@ pfUI_translation["enUS"] = {
["Auto Sell Grey Items"] = nil,
["Average Per Hour"] = nil,
["Background Color"] = nil,
["Bags"] = nil,
["Bags & Bank"] = nil,
["Bags Border Size"] = nil,
["Bagslots Per Row"] = nil,
["Bagspace"] = nil,
["Bank"] = nil,
["Banker"] = nil,
["Bankslots Per Row"] = nil,
["Bar Background"] = nil,
["Bar Texture"] = nil,
["Battleground Frames"] = nil,
["Battleground Statistics"] = nil,
["BEHIND"] = nil,
["Blacklist"] = nil,
["Blinding Powder"] = nil,
["Blue Border On Friendly Players"] = nil,
@@ -297,6 +293,7 @@ pfUI_translation["enUS"] = {
["Enable Mana Ticks"] = nil,
["Enable Micro Bar"] = nil,
["Enable Mouselook With Right Click"] = nil,
["Enable Mouseover Tooltip"] = nil,
["Enable Movable Bags"] = nil,
["Enable Offscreen Frame Positions"] = nil,
["Enable Overlap"] = nil,
@@ -317,7 +314,6 @@ pfUI_translation["enUS"] = {
["Encode"] = nil,
["Ended"] = nil,
["Energy Color"] = nil,
["Equipped"] = nil,
["Equipped Item Color"] = nil,
["Estimate Debuffs"] = nil,
["Estimate Enemy Health Points"] = nil,
@@ -330,7 +326,6 @@ pfUI_translation["enUS"] = {
["Export"] = nil,
["Face"] = nil,
["Fade To Custom Color"] = nil,
["FARM MODE"] = nil,
["Fast"] = nil,
["Filter Mode"] = nil,
["Finish"] = nil,
@@ -541,7 +536,6 @@ pfUI_translation["enUS"] = {
["Next Memory Cleanup"] = nil,
["No"] = nil,
["No Anchor"] = nil,
["NO LOS"] = nil,
["None"] = nil,
["Not a valid button!"] = nil,
["No tracking spell active"] = nil,
@@ -777,8 +771,6 @@ pfUI_translation["enUS"] = {
["Small"] = nil,
["Some settings need to reload the UI to take effect.\nDo you want to reload now?"] = nil,
["Some settings need to reload the UI to take effect.\nDo you want to reloadUI now?"] = nil,
["Sort Bags"] = nil,
["Sort Bank"] = nil,
["Sort Order"] = nil,
["Soul Shard"] = nil,
["Soulshard Counter"] = nil,
@@ -843,7 +835,6 @@ pfUI_translation["enUS"] = {
["Top Actionbar"] = nil,
["Top Left"] = nil,
["Top Right"] = nil,
["Total"] = nil,
["Total Gold"] = nil,
["Totem Direction"] = nil,
["Totem Icons"] = nil,
+1 -4
View File
@@ -51,12 +51,10 @@ pfUI_translation["esES"] = {
["Auto Sell Grey Items"] = "Vender objetos grises automáticamente",
["Average Per Hour"] = "Promedio por hora",
["Background Color"] = "Color del fondo",
["Bags"] = "Bolsas",
["Bags & Bank"] = "Bolsas y banco",
["Bags Border Size"] = "Tamaño del borde de las bolsas",
["Bagslots Per Row"] = "Casillas de la bolsa por fila",
["Bagspace"] = "Huecos en las bolsas",
["Bank"] = "Banco",
["Banker"] = "Banquero",
["Bankslots Per Row"] = "Casillas del banco por fila",
["Bar Background"] = "Fondo de la barra",
@@ -292,6 +290,7 @@ pfUI_translation["esES"] = {
["Enable Mana Ticks"] = "Activar el indicador de pulsos del maná",
["Enable Micro Bar"] = "Activar microbarra",
["Enable Mouselook With Right Click"] = "Mirar usando el puntero con clic derecho",
["Enable Mouseover Tooltip"] = "Activar la descripción emergente al pasar el puntero",
["Enable Movable Bags"] = "Permitir mover las bolsas",
["Enable Offscreen Frame Positions"] = "Permitir mover los marcos fuera de la pantalla",
["Enable Overlap"] = "Permitir superposición",
@@ -312,7 +311,6 @@ pfUI_translation["esES"] = {
["Encode"] = "Codificar",
["Ended"] = "Terminado",
["Energy Color"] = "Color de energía",
["Equipped"] = "Equipado",
["Equipped Item Color"] = "Color del objeto equipado",
["Estimate Debuffs"] = "Estimar los perjuicios",
["Estimate Enemy Health Points"] = "Estimar los puntos de salud del enemigo",
@@ -821,7 +819,6 @@ pfUI_translation["esES"] = {
["Top Actionbar"] = "Barra de acción superior",
["Top Left"] = "Superior derecha",
["Top Right"] = "Superior izquierda",
["Total"] = "Total",
["Total Gold"] = "Oro Total",
["Totem Direction"] = "Dirección del tótem",
["Totem Icons"] = "Iconos de los tótems",
+1 -4
View File
@@ -51,12 +51,10 @@ pfUI_translation["frFR"] = {
["Auto Sell Grey Items"] = "Vente automatique des objets gris",
["Average Per Hour"] = "Moyenne par heure",
["Background Color"] = "Couleur de l'arrière-plan",
["Bags"] = "Sacs",
["Bags & Bank"] = "Sacs et Banque",
["Bags Border Size"] = "Taille de la bordure des Sacs",
["Bagslots Per Row"] = "Emplacements des sacs par rangée",
["Bagspace"] = "Place libre des sacs",
["Bank"] = "Banque",
["Banker"] = "Banquier",
["Bankslots Per Row"] = "Emplacements de la banque par rangée",
["Bar Background"] = "Arrière plan de la barre",
@@ -292,6 +290,7 @@ pfUI_translation["frFR"] = {
["Enable Mana Ticks"] = "Activer les Ticks de mana",
["Enable Micro Bar"] = "Activer la barre des menus miniature",
["Enable Mouselook With Right Click"] = "Activer le déplacement de la caméra avec le clic droit",
["Enable Mouseover Tooltip"] = "Activer l'infobulle au passage de la souris",
["Enable Movable Bags"] = "Activer les sacs mobiles",
["Enable Offscreen Frame Positions"] = "Activer le positionnement des cadres en dehors de l'écran",
["Enable Overlap"] = "Activer la superposition",
@@ -312,7 +311,6 @@ pfUI_translation["frFR"] = {
["Encode"] = "Encoder",
["Ended"] = "Terminé",
["Energy Color"] = "Couleur de l'energie",
["Equipped"] = "Équipé",
["Equipped Item Color"] = "Couleur de l'objet équipé",
["Estimate Debuffs"] = "Estimer les Affaiblissements",
["Estimate Enemy Health Points"] = "Estimer les points de vie ennemis",
@@ -821,7 +819,6 @@ pfUI_translation["frFR"] = {
["Top Actionbar"] = "Barre d'action supérieure",
["Top Left"] = "Haut Gauche",
["Top Right"] = "Haut Droit",
["Total"] = "Total",
["Total Gold"] = "Or Total",
["Totem Direction"] = "Direction des totems",
["Totem Icons"] = "Icones des totems",
+1 -4
View File
@@ -51,12 +51,10 @@ pfUI_translation["koKR"] = {
["Auto Sell Grey Items"] = "회색아이템 자동 판매",
["Average Per Hour"] = nil,
["Background Color"] = "백그라운드 색상",
["Bags"] = "가방",
["Bags & Bank"] = "가방&은행",
["Bags Border Size"] = "가방 테두리 크기",
["Bagslots Per Row"] = nil,
["Bagspace"] = "가방 공간",
["Bank"] = "은행",
["Banker"] = nil,
["Bankslots Per Row"] = nil,
["Bar Background"] = nil,
@@ -292,6 +290,7 @@ pfUI_translation["koKR"] = {
["Enable Mana Ticks"] = nil,
["Enable Micro Bar"] = "미니바 보기(블리자드 마이크로바)",
["Enable Mouselook With Right Click"] = "오른쪽 클릭 Mouselook 사용",
["Enable Mouseover Tooltip"] = nil,
["Enable Movable Bags"] = nil,
["Enable Offscreen Frame Positions"] = "오프스크린 프레임 위치 활성화",
["Enable Overlap"] = "겹쳐서 표시",
@@ -312,7 +311,6 @@ pfUI_translation["koKR"] = {
["Encode"] = nil,
["Ended"] = nil,
["Energy Color"] = nil,
["Equipped"] = "착용 중",
["Equipped Item Color"] = nil,
["Estimate Debuffs"] = nil,
["Estimate Enemy Health Points"] = nil,
@@ -821,7 +819,6 @@ pfUI_translation["koKR"] = {
["Top Actionbar"] = nil,
["Top Left"] = nil,
["Top Right"] = nil,
["Total"] = "총",
["Total Gold"] = "총 금",
["Totem Direction"] = nil,
["Totem Icons"] = nil,
+1 -4
View File
@@ -51,12 +51,10 @@ pfUI_translation["ruRU"] = {
["Auto Sell Grey Items"] = "Автоматическая продажа серых предметов",
["Average Per Hour"] = "Среднее за час",
["Background Color"] = "Цвет фона",
["Bags"] = "Сумки",
["Bags & Bank"] = "Сумки & Банк",
["Bags Border Size"] = "Размер границы сумок",
["Bagslots Per Row"] = "Количество ячеек в сумке на строку",
["Bagspace"] = "Место в сумках",
["Bank"] = "Банк",
["Banker"] = "Банкир",
["Bankslots Per Row"] = "Количество ячеек в банке на строку",
["Bar Background"] = "Фон панели",
@@ -292,6 +290,7 @@ pfUI_translation["ruRU"] = {
["Enable Mana Ticks"] = "Включить восполнение маны",
["Enable Micro Bar"] = "Включить панель микро меню",
["Enable Mouselook With Right Click"] = "Включить нажатие правой кнопки мыши на индикаторе здоровья",
["Enable Mouseover Tooltip"] = "Включить подсказку при наведении мыши",
["Enable Movable Bags"] = "Включить подвижные сумки",
["Enable Offscreen Frame Positions"] = "Включить расположение окон за пределами экрана",
["Enable Overlap"] = "Включить перекрытие",
@@ -312,7 +311,6 @@ pfUI_translation["ruRU"] = {
["Encode"] = "Кодировать",
["Ended"] = "Окончание",
["Energy Color"] = "Цвет энергии",
["Equipped"] = "Экипировано",
["Equipped Item Color"] = "Цвет экипированных предметов",
["Estimate Debuffs"] = "Оценка дебафов",
["Estimate Enemy Health Points"] = "Оценка очков здоровья врага",
@@ -821,7 +819,6 @@ pfUI_translation["ruRU"] = {
["Top Actionbar"] = "Верхняя",
["Top Left"] = "Сверху слева",
["Top Right"] = "Сверху справа",
["Total"] = "Всего",
["Total Gold"] = "Всего золота",
["Totem Direction"] = "Направление тотемов",
["Totem Icons"] = "Иконки тотемов",
+1 -4
View File
@@ -51,12 +51,10 @@ pfUI_translation["zhCN"] = {
["Auto Sell Grey Items"] = "自动贩卖灰色物品",
["Average Per Hour"] = "平均每小时",
["Background Color"] = "背景颜色",
["Bags"] = "背包",
["Bags & Bank"] = "背包和银行",
["Bags Border Size"] = "背包边框大小",
["Bagslots Per Row"] = "每行包裹槽数量",
["Bagspace"] = "背包空间",
["Bank"] = "银行",
["Banker"] = "工会银行角色",
["Bankslots Per Row"] = "每行银行槽数量",
["Bar Background"] = "动作条背景",
@@ -292,6 +290,7 @@ pfUI_translation["zhCN"] = {
["Enable Mana Ticks"] = "显示法力值刻度",
["Enable Micro Bar"] = "显示菜单栏",
["Enable Mouselook With Right Click"] = "启动右键移动镜头",
["Enable Mouseover Tooltip"] = "启用鼠标悬停工具提示",
["Enable Movable Bags"] = "启用可移动包裹",
["Enable Offscreen Frame Positions"] = "允许本插件所有框体移出屏幕边缘",
["Enable Overlap"] = "叠加显示",
@@ -312,7 +311,6 @@ pfUI_translation["zhCN"] = {
["Encode"] = "编码",
["Ended"] = "结束",
["Energy Color"] = "能量值颜色",
["Equipped"] = "已装备",
["Equipped Item Color"] = "已装备物品颜色",
["Estimate Debuffs"] = "预估Debuffs",
["Estimate Enemy Health Points"] = "预估敌人的生命值",
@@ -822,7 +820,6 @@ pfUI_translation["zhCN"] = {
["Top Actionbar"] = "上方动作条",
["Top Left"] = "左上方",
["Top Right"] = "右上方",
["Total"] = "总计",
["Total Gold"] = "总黄金",
["Totem Direction"] = "图腾方向",
["Totem Icons"] = "图腾图标",
+1 -4
View File
@@ -51,12 +51,10 @@ pfUI_translation["zhTW"] = {
["Auto Sell Grey Items"] = "自動販賣灰色物品",
["Average Per Hour"] = "平均每小時",
["Background Color"] = "背景顏色",
["Bags"] = "背包",
["Bags & Bank"] = "背包和銀行",
["Bags Border Size"] = "背包邊框大小",
["Bagslots Per Row"] = "每行包裹槽數量",
["Bagspace"] = "背包空間",
["Bank"] = "銀行",
["Banker"] = "工會銀行角色",
["Bankslots Per Row"] = "每行銀行槽數量",
["Bar Background"] = nil,
@@ -292,6 +290,7 @@ pfUI_translation["zhTW"] = {
["Enable Mana Ticks"] = nil,
["Enable Micro Bar"] = "顯示功能表列",
["Enable Mouselook With Right Click"] = "啟動右鍵移動鏡頭",
["Enable Mouseover Tooltip"] = "啟用滑鼠懸停工具提示",
["Enable Movable Bags"] = "啟用可移動包裹",
["Enable Offscreen Frame Positions"] = "啟用螢幕框架固定",
["Enable Overlap"] = "疊加顯示",
@@ -312,7 +311,6 @@ pfUI_translation["zhTW"] = {
["Encode"] = "編碼",
["Ended"] = nil,
["Energy Color"] = nil,
["Equipped"] = "已裝備",
["Equipped Item Color"] = nil,
["Estimate Debuffs"] = nil,
["Estimate Enemy Health Points"] = nil,
@@ -821,7 +819,6 @@ pfUI_translation["zhTW"] = {
["Top Actionbar"] = nil,
["Top Left"] = "左上方",
["Top Right"] = "右上方",
["Total"] = "全部",
["Total Gold"] = "總金量",
["Totem Direction"] = nil,
["Totem Icons"] = nil,
-1
View File
@@ -81,5 +81,4 @@
<Include file="..\modules\equipmentmanager.lua"/>
<Include file="..\modules\loothistory.lua"/>
<Include file="..\modules\newitem.lua"/>
<Include file="..\modules\friendnotes.lua"/>
</Ui>
+2
View File
@@ -41,6 +41,8 @@
<!-- Turtle WoW -->
<Include file="..\skins\blizzard\barbershop.lua"/>
<Include file="..\skins\blizzard\transmog.lua"/>
<Include file="..\skins\blizzard\custom_merchant.lua"/>
<Include file="..\skins\blizzard\arena_score.lua"/>
<Include file="..\skins\blizzard\ebc.lua"/>
</Ui>
+67 -26
View File
@@ -376,9 +376,11 @@ local function GetDebuffSlotMap(guid)
local texture = libdebuff:GetSpellIcon(spellId)
local stacks = (auraApps and auraApps[auraSlot] or 0) + 1
local dtype = nil
local dispelId = C_Spell.GetSpellDispelType(spellId)
if dispelId and dispelId > 0 then
dtype = dispelTypeMap[dispelId]
if GetSpellRecField then
local dispelId = GetSpellRecField(spellId, "dispel")
if dispelId and dispelId > 0 then
dtype = dispelTypeMap[dispelId]
end
end
map[displaySlot] = {
auraSlot = auraSlot,
@@ -591,13 +593,37 @@ end
-- DURATION FUNCTIONS
-- ============================================================================
-- Report the live, talent-accurate duration of a matching aura on the player.
-- ClassicAPI's C_UnitAuras carries the real (mod-folded) duration straight from
-- the engine, so there's no static duration table to maintain. Returns 0 when
-- the player has no such aura (e.g. a debuff that only ever lands on enemies).
function libdebuff:GetDuration(effect, rank)
local aura = C_UnitAuras.GetAuraDataBySpellName("player", effect)
return aura and aura.duration or 0
if L["debuffs"][effect] then
local rank = rank and tonumber((string.gsub(rank, RANK, ""))) or 0
local rank = L["debuffs"][effect][rank] and rank or libdebuff:GetMaxRank(effect)
local duration = L["debuffs"][effect][rank]
if effect == L["dyndebuffs"]["Demoralizing Shout"] then
local _,_,_,_,count = GetTalentInfo(2,1)
if count and count > 0 then duration = duration + ( duration / 100 * (count*10)) end
elseif effect == L["dyndebuffs"]["Shadow Word: Pain"] then
local _,_,_,_,count = GetTalentInfo(3,4)
if count and count > 0 then duration = duration + count * 3 end
elseif effect == L["dyndebuffs"]["Frostbolt"] then
local _,_,_,_,count = GetTalentInfo(3,7)
if count and count > 0 then duration = duration + count end
elseif effect == L["dyndebuffs"]["Gouge"] then
local _,_,_,_,count = GetTalentInfo(3,3)
if count and count > 0 then duration = duration + (count*.5) end
end
return duration
else
return 0
end
end
function libdebuff:GetMaxRank(effect)
local max = 0
for id in pairs(L["debuffs"][effect]) do
if id > max then max = id end
end
return max
end
function libdebuff:UpdateDuration(unit, unitlevel, effect, duration)
@@ -623,6 +649,7 @@ libdebuff.objects = {}
function libdebuff:AddPending(unit, unitlevel, effect, duration, caster, rank)
if not unit or duration <= 0 then return end
if not L["debuffs"][effect] then return end
if libdebuff.pending[3] then return end
libdebuff.pending[1] = unit
@@ -701,25 +728,39 @@ end
-- API: GetBestAuraCast (for libpredict HoT tracking)
-- ============================================================================
-- Report the active aura for spellName on `guid`, read straight from
-- C_UnitAuras. expirationTime is the true, caster-modified remaining time, so
-- no cast-event bookkeeping is needed -- and a unit only ever holds one instance
-- of a given spell, so this is inherently the "best" one.
-- Returns (startTime, duration, timeleft, rank, casterGuid) or nil.
function libdebuff:GetBestAuraCast(guid, spellName)
if not guid or not spellName then return nil end
local aura = C_UnitAuras.GetAuraDataBySpellName(guid, spellName)
if not aura or not aura.expirationTime or aura.expirationTime == 0 then return nil end
local timeleft = aura.expirationTime - GetTime()
if timeleft <= 0 then return nil end
local duration = (aura.duration and aura.duration > 0) and aura.duration or timeleft
local startTime = aura.expirationTime - duration
local rank = aura.spellId and tonumber((string.gsub(C_Spell.GetSpellSubtext(aura.spellId) or "", RANK, ""))) or nil
return startTime, duration, timeleft, rank, aura.sourceGUID
-- Check ownDebuffs first (for our casts)
if ownDebuffs[guid] and ownDebuffs[guid][spellName] then
local data = ownDebuffs[guid][spellName]
local timeleft = (data.startTime + data.duration) - GetTime()
if timeleft > 0 then
return data.startTime, data.duration, timeleft, data.rank, GetPlayerGuid()
end
end
-- Check allAuraCasts (for any caster)
if allAuraCasts[guid] and allAuraCasts[guid][spellName] then
local bestData = nil
local bestCaster = nil
local bestTimeleft = 0
for casterGuid, data in pairs(allAuraCasts[guid][spellName]) do
local timeleft = (data.startTime + data.duration) - GetTime()
if timeleft > bestTimeleft then
bestTimeleft = timeleft
bestData = data
bestCaster = casterGuid
end
end
if bestData and bestTimeleft > 0 then
return bestData.startTime, bestData.duration, bestTimeleft, bestData.rank, bestCaster
end
end
return nil
end
-- ============================================================================
+1 -1
View File
@@ -1222,7 +1222,7 @@ libpredict.sender:SetScript("OnEvent", function()
end)
function libpredict:GetHotDuration(unit, spell)
if unit == UNKNOWNOBJECT or unit == UKNOWNBEING then return end
if unit == UNKNOWNOBJECT or unit == UNKOWNBEING then return end
-- NEW: Try libdebuff first (Nampower AURA_CAST events)
if pfUI.api.libdebuff and pfUI.api.libdebuff.GetBestAuraCast then
+5 -6
View File
@@ -83,13 +83,12 @@ end
-- [number] Casting time of the spell in milliseconds
-- [number] Minimum range from the target required to cast the spell
-- [number] Maximum range from the target at which you can cast the spell
-- [number] Spell's index in the book
-- [number] The type of the spellbook that the spell is in
-- [number] The numeric spell-id of the spell
-- [number] The type of the spellbook that the spell is in
local spellinfo = {}
function libspell.GetSpellInfo(index, bookType)
local cache = spellinfo[index]
if cache then return unpack(cache) end
if cache then return cache[1], cache[2], cache[3], cache[4], cache[5], cache[6], cache[7], cache[8] end
local slot
if type(index) == "string" then
@@ -109,10 +108,10 @@ function libspell.GetSpellInfo(index, bookType)
-- ClassicAPI's GetSpellInfo returns: name, rank, icon, cost, isFunnel, powerType,
-- castTime(ms), minRange, maxRange, spellID. Keep libspell's historical positional
-- shape (castingTime at 4, ranges at 5/6, slot+bookType at 7/8).
local name, rank, icon, _, _, _, castingTime, minRange, maxRange, spellId = GetSpellInfo(slot, bookType)
local name, rank, icon, _, _, _, castingTime, minRange, maxRange = GetSpellInfo(slot, bookType)
spellinfo[index] = SafePack(name, rank, icon, castingTime, minRange, maxRange, slot, bookType, spellId)
return name, rank, icon, castingTime, minRange, maxRange, slot, bookType, spellId
spellinfo[index] = { name, rank, icon, castingTime, minRange, maxRange, slot, bookType }
return name, rank, icon, castingTime, minRange, maxRange, slot, bookType
end
-- Reset all spell caches whenever new spells are learned/unlearned
+11 -10
View File
@@ -96,12 +96,13 @@ libunitscan:SetScript("OnEvent", function()
RememberByUnit("player", name, class)
elseif event == "FRIENDLIST_UPDATE" then
local name, class, level
for i = 1, GetNumFriends() do
local info = C_FriendList.GetFriendInfoByIndex(i)
if info then
local level = info.level > 0 and info.level or nil
AddData("players", info.name, info.classFilename, level)
end
name, level, class = GetFriendInfo(i)
class = L["class"][class] or nil
-- friendlist updates due to friend going off-line return level 0, let's not overwrite good older values
level = level > 0 and level or nil
AddData("players", name, class, level)
end
elseif event == "GUILD_ROSTER_UPDATE" then
@@ -136,11 +137,11 @@ libunitscan:SetScript("OnEvent", function()
end
elseif event == "WHO_LIST_UPDATE" or event == "CHAT_MSG_SYSTEM" then
for i = 1, C_FriendList.GetNumWhoResults() do
local info = C_FriendList.GetWhoInfo(i)
if info then
AddData("players", info.fullName, info.filename, info.level, nil, info.fullGuildName)
end
local name, class, level, guild, _
for i = 1, GetNumWhoResults() do
name, guild, level, _, class, _ = GetWhoInfo(i)
class = L["class"][class] or nil
AddData("players", name, class, level, nil, guild)
end
elseif event == "UPDATE_MOUSEOVER_UNIT" or event == "PLAYER_TARGET_CHANGED" or event == "NAME_PLATE_UNIT_ADDED" then
+12 -11
View File
@@ -371,12 +371,12 @@ pfUI:RegisterModule("actionbar", function ()
-- active conditional dynamically, instead of being frozen to the first
-- statically-scanned spell.
if CleveRoids and CleveRoids.IsManagedAction and CleveRoids.IsManagedAction(self.id) then
self.spellslot, self.booktype, self.spellID = nil, nil, nil
self.spellslot, self.booktype = nil, nil
return
end
local kind, slot = GetActionInfo(self.id)
self.spellslot, self.booktype, self.spellID = nil, nil, nil
self.spellslot, self.booktype = nil, nil
if kind == 'macro' then
local name, _, body = GetMacroInfo(slot)
@@ -414,7 +414,10 @@ pfUI:RegisterModule("actionbar", function ()
end
if match then
self.spellslot, self.booktype, self.spellID = select(7, libspell.GetSpellInfo(match))
local _, _, spell, rank = string.find(match, '(.+)%((.+)%)')
spell = spell or match
self.spellslot, self.booktype = libspell.GetSpellIndex(spell, rank)
if self.spellslot and self.spellslot > 0 then return end
end
end
@@ -423,7 +426,7 @@ pfUI:RegisterModule("actionbar", function ()
end
local function ButtonEnter(self)
self = self or this
local self = self or this
-- indicate that dragging could get enabled
drag_await = true
@@ -441,8 +444,6 @@ pfUI:RegisterModule("actionbar", function ()
else
GameTooltip:SetPetAction(self.id)
end
elseif self.spellID then
GameTooltip:SetSpellByID(self.spellID)
elseif self.spellslot and self.booktype then
GameTooltip:SetSpell(self.spellslot, self.booktype)
else
@@ -453,7 +454,7 @@ pfUI:RegisterModule("actionbar", function ()
end
local function ButtonLeave(self)
self = self or this
local self = self or this
-- no longer wait for a drag event
drag_await = nil
@@ -466,7 +467,7 @@ pfUI:RegisterModule("actionbar", function ()
local grid, sid, id, bar, active, texture, _
local function ButtonSlotUpdate(self)
if not self then return end
self = self or this
local self = self or this
sid = self.id -- 1 to 120
-- reset shared variables
@@ -585,7 +586,7 @@ pfUI:RegisterModule("actionbar", function ()
local sid, usable, oom, _
local function ButtonUsableUpdate(self)
self = self or this
local self = self or this
sid = self.id -- 1 to 120
if self.bar == 11 then
@@ -621,7 +622,7 @@ pfUI:RegisterModule("actionbar", function ()
end
local function ButtonRangeUpdate(self)
self = self or this
local self = self or this
-- update range display
if C.bars.glowrange == "1" and self.bar ~= 11 and self.bar ~= 12 and HasAction(self.id) and ActionHasRange(self.id) and IsActionInRange(self.id) == 0 then
@@ -699,7 +700,7 @@ pfUI:RegisterModule("actionbar", function ()
end
local function BarsEvent(self)
self = self or this
local self = self or this
-- refresh only specific slots
if event == "ACTIONBAR_SLOT_CHANGED" and arg1 and arg1 ~= 0 then
-5
View File
@@ -2,11 +2,6 @@ pfUI:RegisterModule("autoshift", function ()
pfUI.autoshift = CreateFrame("Frame")
pfUI.autoshift:RegisterEvent("UI_ERROR_MESSAGE")
-- Empty stub kept only so Turtle WoW's pfUI-turtle addon doesn't error when
-- its obsolete moonkin autoshift module iterates pfUI.autoshift.shapeshifts.
-- This fork drives autoshift off GetShapeshiftFormID() and no longer uses it.
pfUI.autoshift.shapeshifts = {}
pfUI.autoshift.scanString = string.gsub(SPELL_FAILED_ONLY_SHAPESHIFT, "%%s", "(.+)")
pfUI.autoshift.errors = { SPELL_FAILED_NOT_MOUNTED, ERR_ATTACK_MOUNTED, ERR_TAXIPLAYERALREADYMOUNTED,
+17 -2
View File
@@ -972,7 +972,7 @@ pfUI:RegisterModule("bags", function ()
frame.sort.backdrop:SetBackdropBorderColor(1,1,.25,1)
frame.sort.texture:SetVertexColor(1,1,.25,1)
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText(T["Sort Bags"])
GameTooltip:SetText("Sort Bags")
GameTooltip:Show()
end)
@@ -1085,6 +1085,21 @@ pfUI:RegisterModule("bags", function ()
frame.search.edit:ClearFocus()
end)
frame.search:SetScript("OnHide", function()
frame.search.edit:SetText(T["Search"])
for bag = -2, 11 do
if pfUI.bags[bag] then
local bagsize = GetContainerNumSlots(bag)
if bag == -2 and pfUI.bag.showKeyring == true then bagsize = GetKeyRingSize() end
for slot = 1, bagsize do
if pfUI.bags[bag] and pfUI.bags[bag].slots[slot] then
pfUI.bags[bag].slots[slot].frame:SetAlpha(1)
end
end
end
end
end)
frame.search.edit:SetScript("OnMouseUp", function()
if arg1 == "RightButton" then
this:ClearFocus()
@@ -1192,7 +1207,7 @@ pfUI:RegisterModule("bags", function ()
frame.sort.backdrop:SetBackdropBorderColor(1,1,.25,1)
frame.sort.texture:SetVertexColor(1,1,.25,1)
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText(T["Sort Bank"])
GameTooltip:SetText("Sort Bank")
GameTooltip:Show()
end)
+1 -1
View File
@@ -32,7 +32,7 @@ pfUI:RegisterModule("bgscore", function ()
-- Title label
local title = mover:CreateFontString(nil, "OVERLAY")
title:SetFont("Fonts\\FRIZQT__.TTF", 14, "OUTLINE")
title:SetText(T["Battleground Frames"])
title:SetText("Battleground Frames")
title:SetPoint("TOP", mover, "TOP", 0, -2)
-- BG score preview text
+8 -2
View File
@@ -138,10 +138,16 @@ pfUI:RegisterModule("buff", function ()
end
end)
buff:SetScript("OnLeave", GameTooltip_Hide)
buff:SetScript("OnLeave", function()
GameTooltip:Hide()
end)
buff:SetScript("OnClick", function()
if this.spellId then
if CancelItemTempEnchantment and this.mode and this.mode == "MAINHAND" then
CancelItemTempEnchantment(1)
elseif CancelItemTempEnchantment and this.mode and this.mode == "OFFHAND" then
CancelItemTempEnchantment(2)
elseif this.spellId then
C_Spell.CancelSpellByID(this.spellId)
end
end)
-2
View File
@@ -90,14 +90,12 @@ pfUI:RegisterModule("buffwatch", function ()
if val == skill then return end
end
config.whitelist = config.whitelist .. "#" .. skill
fcache[tostring(config)] = nil -- invalidate so the new entry takes effect immediately
DEFAULT_CHAT_FRAME:AddMessage("|cff33ffcc" .. skill .. "|r" .. T["is now whitelisted."])
elseif IsShiftKeyDown() then
for _, val in pairs({strsplit("#", config.blacklist)}) do
if val == skill then return end
end
config.blacklist = config.blacklist .. "#" .. skill
fcache[tostring(config)] = nil -- invalidate so the new entry takes effect immediately
DEFAULT_CHAT_FRAME:AddMessage("|cff33ffcc" .. skill .. "|r" .. T["is now blacklisted."])
end
elseif this.parent.unit == "player" then
+5 -7
View File
@@ -810,25 +810,23 @@ pfUI:RegisterModule("chat", function ()
end
end
-- detect the whisper prefix BEFORE prepending a timestamp; the timestamp
-- pushes the whisper colour code off position 1 and the find below fails
local isWhisper = C.chat.global.whispermod == "1" and string.find(text, wcol, 1) == 1
-- show timestamp in chat
if C.chat.text.time == "1" then
text = timecolorhex .. tleft .. date(C.chat.text.timeformat) .. tright .. "|r " .. text
end
-- save chat history
if isWhisper then
if C.chat.global.whispermod == "1" and string.find(text, wcol, 1) == 1 then
SaveChatHistory(frame:GetID(), string.gsub(text, wcol, ""), cr, cg, cb)
else
SaveChatHistory(frame:GetID(), text, a1, a2, a3)
end
if isWhisper then
if C.chat.global.whispermod == "1" then
-- patch incoming whisper string to match the colors
text = string.gsub(text, "|r", "|r" .. wcol)
if string.find(text, wcol, 1) == 1 then
text = string.gsub(text, "|r", "|r" .. wcol)
end
end
frame:HookAddMessage(text, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17)
+1 -1
View File
@@ -9,7 +9,7 @@ pfUI:RegisterModule("cooldown", function ()
local parent, parent_name
local function pfCooldownOnUpdate()
parent = this:GetParent()
if not parent then this:Hide() return end
if not parent then this:Hide() end
parent_name = parent:GetName()
-- avoid to set cooldowns on invalid frames
+270 -94
View File
@@ -1,10 +1,11 @@
-- Equipment Manager module
-- Backport of the 4.3.4 GearManagerDialog UI on top of ClassicAPI's C_EquipmentSet.* API.
-- Backport of the 4.3.4 GearManagerDialog UI on top of ClassicAPI's
-- C_EquipmentSet.* API. Adds a 6th tab to CharacterFrame.
pfUI:RegisterModule("equipmentmanager", function()
if not C_EquipmentSet or not C_EquipmentSet.CanUseEquipmentSets() then return end
pfUI.equipmentmanager = {}
pfUI.equipmentmanager = pfUI.equipmentmanager or {}
local SET_ROW_HEIGHT = 36
@@ -13,7 +14,6 @@ pfUI:RegisterModule("equipmentmanager", function()
local pendingAction = nil -- "new" | "save" | "rename" — what the popups apply to
local slotOverlays = {} -- [invSlotID] = ignored-overlay texture on the character slot
local popoutButtons = {} -- popout arrow buttons; toggled with the EM frame
local UpdatePopouts -- forward decl; assigned once popouts + flyout exist
-- Pending ignored-slot toggles per set: a slotID here means the
-- effective state is flipped from what's persisted. Committed on
@@ -55,7 +55,7 @@ pfUI:RegisterModule("equipmentmanager", function()
local function EquipSet(setID)
if not setID then return end
if C_EquipmentSet.EquipmentSetContainsLockedItems(setID) then
UIErrorsFrame:AddMessage(ERR_CLIENT_LOCKED_OUT, 1, .1, .1, 1)
UIErrorsFrame:AddMessage(ERR_CLIENT_LOCKED_OUT or "Locked items in set", 1, .1, .1, 1)
return
end
ClearCursor()
@@ -68,7 +68,8 @@ pfUI:RegisterModule("equipmentmanager", function()
-- ============================================================
local frame = CreateFrame("Frame", "pfEquipmentManagerFrame", CharacterFrame)
frame:SetSize(220, 350)
frame:SetWidth(220)
frame:SetHeight(350)
frame:SetFrameStrata("HIGH")
frame:SetScript("OnShow", function()
this:ClearAllPoints()
@@ -77,7 +78,7 @@ pfUI:RegisterModule("equipmentmanager", function()
else
frame:SetPoint("TOPLEFT", CharacterFrame, "TOPRIGHT", 0, 0)
end
UpdatePopouts()
for _, b in ipairs(popoutButtons) do b:Show() end
end)
CreateBackdrop(frame, nil, nil, .9)
CreateBackdropShadow(frame)
@@ -85,7 +86,7 @@ pfUI:RegisterModule("equipmentmanager", function()
frame.title = frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
frame.title:SetPoint("TOP", frame, "TOP", 0, -10)
frame.title:SetText(EQUIPMENT_MANAGER)
frame.title:SetText(T["Equipment Manager"] or "Equipment Manager")
local closeBtn = CreateFrame("Button", nil, frame, "UIPanelCloseButton")
closeBtn:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -4, -4)
@@ -97,14 +98,15 @@ pfUI:RegisterModule("equipmentmanager", function()
-- ============================================================
local toggleBtn = CreateFrame("Button", "pfEqMgrToggleButton", PaperDollFrame)
toggleBtn:SetSize(28, 28)
toggleBtn:SetWidth(28)
toggleBtn:SetHeight(28)
toggleBtn:SetPoint("BOTTOM", CharacterHandsSlot, "TOP", 0, 4)
toggleBtn:SetNormalTexture(pfUI.path.."\\img\\UI-GearManager-Button")
toggleBtn:SetPushedTexture(pfUI.path.."\\img\\UI-GearManager-Button-Pushed")
toggleBtn:SetHighlightTexture("Interface\\Buttons\\ButtonHilight-Square", "ADD")
toggleBtn:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText(PAPERDOLL_EQUIPMENTMANAGER)
GameTooltip:SetText(T["Equipment Manager"] or "Equipment Manager")
GameTooltip:Show()
end)
toggleBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)
@@ -124,13 +126,13 @@ pfUI:RegisterModule("equipmentmanager", function()
-- ============================================================
local btnEquip = CreateFrame("Button", "pfEqMgrEquip", frame, "UIPanelButtonTemplate")
btnEquip:SetSize(86, 22); btnEquip:SetText(EQUIPSET_EQUIP)
btnEquip:SetWidth(86); btnEquip:SetHeight(22); btnEquip:SetText(T["Equip"] or "Equip")
btnEquip:SetPoint("TOPLEFT", frame, "TOPLEFT", 16, -30)
SkinButton(btnEquip)
btnEquip:SetScript("OnClick", function() EquipSet(selectedSetID) end)
local btnSave = CreateFrame("Button", "pfEqMgrSave", frame, "UIPanelButtonTemplate")
btnSave:SetSize(86, 22); btnSave:SetText(SAVE)
btnSave:SetWidth(86); btnSave:SetHeight(22); btnSave:SetText(T["Save"] or "Save")
btnSave:SetPoint("LEFT", btnEquip, "RIGHT", 6, 0)
SkinButton(btnSave)
btnSave:SetScript("OnClick", function()
@@ -139,7 +141,7 @@ pfUI:RegisterModule("equipmentmanager", function()
if not name then return end
local targetID = selectedSetID
StaticPopupDialogs["PFUI_EQMGR_SAVE_CONFIRM"] = {
text = string.format(CONFIRM_SAVE_EQUIPMENT_SET, name),
text = string.format(T["Would you like to save the equipment set '%s'?"] or "Would you like to save the equipment set '%s'?", name),
button1 = YES, button2 = NO,
OnAccept = function()
C_EquipmentSet.ClearIgnoredSlotsForSave()
@@ -164,7 +166,7 @@ pfUI:RegisterModule("equipmentmanager", function()
local rowMenu = CreateFrame("Frame", "pfEqMgrRowMenu", UIParent)
rowMenu:SetFrameStrata("DIALOG")
rowMenu:SetSize(140, 50);
rowMenu:SetWidth(140); rowMenu:SetHeight(50)
rowMenu:Hide()
CreateBackdrop(rowMenu, nil, nil, .95)
CreateBackdropShadow(rowMenu)
@@ -183,9 +185,9 @@ pfUI:RegisterModule("equipmentmanager", function()
tinsert(UISpecialFrames, "pfEqMgrRowMenu") -- Escape closes it
rowMenu.changeBtn = CreateFrame("Button", nil, rowMenu, "UIPanelButtonTemplate")
rowMenu.changeBtn:SetSize(130, 20);
rowMenu.changeBtn:SetWidth(130); rowMenu.changeBtn:SetHeight(20)
rowMenu.changeBtn:SetPoint("TOPLEFT", rowMenu, "TOPLEFT", 5, -3)
rowMenu.changeBtn:SetText(EQUIPMENT_SET_EDIT)
rowMenu.changeBtn:SetText(T["Change Name/Icon"] or "Change Name/Icon")
SkinButton(rowMenu.changeBtn)
rowMenu.changeBtn:SetScript("OnClick", function()
rowMenu:Hide()
@@ -196,9 +198,9 @@ pfUI:RegisterModule("equipmentmanager", function()
end)
rowMenu.deleteBtn = CreateFrame("Button", nil, rowMenu, "UIPanelButtonTemplate")
rowMenu.deleteBtn:SetSize(130, 20);
rowMenu.deleteBtn:SetWidth(130); rowMenu.deleteBtn:SetHeight(20)
rowMenu.deleteBtn:SetPoint("TOP", rowMenu.changeBtn, "BOTTOM", 0, -2)
rowMenu.deleteBtn:SetText(DELETE)
rowMenu.deleteBtn:SetText(T["Delete"] or "Delete")
SkinButton(rowMenu.deleteBtn)
rowMenu.deleteBtn:SetScript("OnClick", function()
rowMenu:Hide()
@@ -206,7 +208,7 @@ pfUI:RegisterModule("equipmentmanager", function()
local targetID = rowMenu.targetSetID
local name = C_EquipmentSet.GetEquipmentSetInfo(targetID)
StaticPopupDialogs["PFUI_EQMGR_DELETE"] = {
text = string.format(CONFIRM_DELETE_EQUIPMENT_SET, name or "?"),
text = string.format(T["Delete equipment set '%s'?"] or "Delete equipment set '%s'?", name or "?"),
button1 = YES, button2 = NO,
OnAccept = function()
pendingIgnoredToggles[targetID] = nil
@@ -227,8 +229,9 @@ pfUI:RegisterModule("equipmentmanager", function()
local LIST_ROW_STRIDE = SET_ROW_HEIGHT + 2
local listFrame = CreateFrame("Frame", nil, frame)
listFrame:SetPoint("TOPLEFT", frame, "TOPLEFT", 16, -58)
listFrame:SetWidth(180)
-- Height fits N rows + (N-1) inter-row gaps + 3px top/bottom padding.
listFrame:SetSize(180, LIST_VISIBLE_ROWS * LIST_ROW_STRIDE + 4)
listFrame:SetHeight(LIST_VISIBLE_ROWS * LIST_ROW_STRIDE + 4)
CreateBackdrop(listFrame, nil, nil, .75)
-- Mouse-wheel scroll: list of [sets..., newSetRow] is virtualized
@@ -244,13 +247,15 @@ pfUI:RegisterModule("equipmentmanager", function()
local setRows = {}
local function CreateSetRow()
local row = CreateFrame("Button", nil, listFrame)
row:SetSize(170, SET_ROW_HEIGHT)
row:SetWidth(170)
row:SetHeight(SET_ROW_HEIGHT)
-- Position is set per-Refresh based on the row's visible slot;
-- start anchored to avoid uninitialized geometry before first Refresh.
row:SetPoint("TOPLEFT", listFrame, "TOPLEFT", 5, -3)
row.icon = row:CreateTexture(nil, "ARTWORK")
row.icon:SetSize(30, 30)
row.icon:SetWidth(30)
row.icon:SetHeight(30)
row.icon:SetPoint("LEFT", row, "LEFT", 2, 0)
row.icon:SetTexCoord(.08, .92, .08, .92)
@@ -265,7 +270,7 @@ pfUI:RegisterModule("equipmentmanager", function()
row.highlight:Hide()
row.gear = CreateFrame("Button", nil, row)
row.gear:SetSize(16, 16);
row.gear:SetWidth(16); row.gear:SetHeight(16)
row.gear:SetPoint("RIGHT", row, "RIGHT", -4, 0)
row.gear.tex = row.gear:CreateTexture(nil, "ARTWORK")
row.gear.tex:SetAllPoints(row.gear)
@@ -301,27 +306,27 @@ pfUI:RegisterModule("equipmentmanager", function()
pfUI.equipmentmanager.Refresh()
end)
-- Show the gear and tooltip while the cursor is over the row or its
-- gear child. The gear sits inside the row's rectangle, so a single
-- MouseIsOver(row) check covers both. OnLeave on the row and the gear
-- catches every exit path, so no OnUpdate poll is needed.
local function HideRowHover()
if MouseIsOver(row) then return end
GameTooltip:Hide()
row.gear:Hide()
end
-- Hover handling: OnLeave fires when the cursor moves onto a child
-- (the gear button becomes the topmost mouse target). Use an
-- OnUpdate poll so the gear stays shown while the cursor is over
-- the row OR the gear itself.
row:SetScript("OnEnter", function()
if not row.setID then return end
local name = C_EquipmentSet.GetEquipmentSetInfo(row.setID)
if not this.setID then return end
local name = C_EquipmentSet.GetEquipmentSetInfo(this.setID)
if name then
GameTooltip:SetOwner(row, "ANCHOR_RIGHT")
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetEquipmentSet(name)
GameTooltip:Show()
end
row.gear:Show()
this:SetScript("OnUpdate", function()
if not MouseIsOver(this) and not MouseIsOver(row.gear) then
this:SetScript("OnUpdate", nil)
GameTooltip:Hide()
row.gear:Hide()
end
end)
end)
row:SetScript("OnLeave", HideRowHover)
row.gear:SetScript("OnLeave", HideRowHover)
return row
end
@@ -333,10 +338,10 @@ pfUI:RegisterModule("equipmentmanager", function()
-- specific set index (row[i] always shows ids[i]).
local newSetRow = CreateFrame("Button", nil, listFrame)
newSetRow:SetSize(170, SET_ROW_HEIGHT);
newSetRow:SetWidth(170); newSetRow:SetHeight(SET_ROW_HEIGHT)
newSetRow.icon = newSetRow:CreateTexture(nil, "ARTWORK")
newSetRow.icon:SetSize(24, 24);
newSetRow.icon:SetWidth(24); newSetRow.icon:SetHeight(24)
newSetRow.icon:SetPoint("LEFT", newSetRow, "LEFT", 5, 0)
newSetRow.icon:SetTexture(pfUI.path.."\\img\\Character-Plus")
@@ -344,7 +349,7 @@ pfUI:RegisterModule("equipmentmanager", function()
newSetRow.text:SetPoint("LEFT", newSetRow.icon, "RIGHT", 8, 0)
newSetRow.text:SetPoint("RIGHT", newSetRow, "RIGHT", -4, 0)
newSetRow.text:SetJustifyH("LEFT")
newSetRow.text:SetText(PAPERDOLL_NEWEQUIPMENTSET)
newSetRow.text:SetText(T["New Set"] or "New Set")
newSetRow.text:SetTextColor(0.2, 1, 0.2)
newSetRow.highlight = newSetRow:CreateTexture(nil, "BACKGROUND")
@@ -357,7 +362,8 @@ pfUI:RegisterModule("equipmentmanager", function()
local function MakeButton(name, label, parent, anchor, ax, ay, width)
local b = CreateFrame("Button", name, parent, "UIPanelButtonTemplate")
b:SetSize(width or 70, 22)
b:SetWidth(width or 70)
b:SetHeight(22)
b:SetText(label)
b:SetPoint("TOPLEFT", anchor, "BOTTOMLEFT", ax, ay)
SkinButton(b)
@@ -370,7 +376,8 @@ pfUI:RegisterModule("equipmentmanager", function()
local namePopup = CreateFrame("Frame", "pfEqMgrNamePopup", UIParent)
namePopup:SetFrameStrata("DIALOG")
namePopup:SetSize(472, 484)
namePopup:SetWidth(472)
namePopup:SetHeight(498)
namePopup:SetPoint("CENTER", UIParent, "CENTER")
namePopup:Hide()
CreateBackdrop(namePopup, nil, nil, .9)
@@ -381,28 +388,200 @@ pfUI:RegisterModule("equipmentmanager", function()
namePopup:SetScript("OnDragStart", function() this:StartMoving() end)
namePopup:SetScript("OnDragStop", function() this:StopMovingOrSizing() end)
-- Equipment seed leads the grid with gear-relevant item icons. The widget
-- owns the name field, preview, grid, filter, and search; this module keeps
-- the OK/Cancel buttons and the create/save/rename flow below.
local iconPicker = CreateIconPicker("pfEqMgrIcon", namePopup,
IconDataProviderExtraType.Equipment, GEARSETS_POPUP_TEXT)
namePopup.title = namePopup:CreateFontString(nil, "OVERLAY", "GameFontNormal")
namePopup.title:SetPoint("TOP", namePopup, "TOP", 0, -10)
namePopup.title:SetText(T["Save Set"] or "Save Set")
local btnPopupOK = MakeButton("pfEqMgrPopupOK", OKAY, namePopup, namePopup, 14, -340, 80)
namePopup.nameLabel = namePopup:CreateFontString(nil, "OVERLAY", "GameFontNormal")
namePopup.nameLabel:SetPoint("TOPLEFT", namePopup, "TOPLEFT", 14, -34)
namePopup.nameLabel:SetText(T["Enter Set Name (Max 16 Characters):"] or "Enter Set Name (Max 16 Characters):")
local btnPopupCancel = MakeButton("pfEqMgrPopupCancel", CANCEL, namePopup, namePopup, 0, 0, 80)
namePopup.editbox = CreateFrame("EditBox", "pfEqMgrNameEdit", namePopup, "InputBoxTemplate")
namePopup.editbox:SetWidth(280)
namePopup.editbox:SetHeight(20)
namePopup.editbox:SetPoint("TOPLEFT", namePopup, "TOPLEFT", 14, -52)
namePopup.editbox:SetAutoFocus(false)
namePopup.editbox:SetMaxLetters(16)
CreateBackdrop(namePopup.editbox)
namePopup.selectedLabel = namePopup:CreateFontString(nil, "OVERLAY", "GameFontNormal")
namePopup.selectedLabel:SetPoint("TOPRIGHT", namePopup, "TOPRIGHT", -14, -28)
namePopup.selectedLabel:SetText(T["Currently Selected"] or "Currently Selected")
namePopup.selectedLabel:SetTextColor(1, 0.82, 0)
namePopup.selectedPreview = CreateFrame("Frame", nil, namePopup)
namePopup.selectedPreview:SetWidth(42)
namePopup.selectedPreview:SetHeight(42)
namePopup.selectedPreview:SetPoint("TOPRIGHT", namePopup, "TOPRIGHT", -14, -44)
CreateBackdrop(namePopup.selectedPreview)
namePopup.selectedPreview.tex = namePopup.selectedPreview:CreateTexture(nil, "ARTWORK")
namePopup.selectedPreview.tex:SetAllPoints(namePopup.selectedPreview)
namePopup.selectedPreview.tex:SetTexCoord(.08, .92, .08, .92)
namePopup.iconLabel = namePopup:CreateFontString(nil, "OVERLAY", "GameFontNormal")
namePopup.iconLabel:SetPoint("TOPLEFT", namePopup, "TOPLEFT", 14, -100)
namePopup.iconLabel:SetText(T["Choose an Icon:"] or "Choose an Icon:")
-- Icon picker: 10×8 grid of buttons + scroll
local ICON_GRID_COLS = 10
local ICON_GRID_ROWS = 8
local ICON_BTN_SIZE = 36
local ICON_BTN_PAD = 6
local iconScroll = CreateFrame("ScrollFrame", "pfEqMgrIconScroll", namePopup, "FauxScrollFrameTemplate")
iconScroll:SetPoint("TOPLEFT", namePopup, "TOPLEFT", 14, -130)
iconScroll:SetWidth(ICON_GRID_COLS * (ICON_BTN_SIZE + ICON_BTN_PAD) - ICON_BTN_PAD)
iconScroll:SetHeight(ICON_GRID_ROWS * (ICON_BTN_SIZE + ICON_BTN_PAD) - ICON_BTN_PAD)
-- IconDataProviderMixin owns the icon DB, dedup, and lazy load.
-- Init on first picker open; release on hide so the cache GCs.
local provider = nil
-- Selection is tracked by PATH (not index) so it survives filter
-- changes: a spell icon you picked still saves correctly even after
-- you switch the filter to "Items" and it's no longer in the visible list.
local QUESTION_MARK = "INTERFACE\\ICONS\\INV_MISC_QUESTIONMARK"
local selectedIconPath = QUESTION_MARK
local function EnsureProvider()
if not provider then
provider = CreateAndInitFromMixin(IconDataProviderMixin,
IconDataProviderExtraType.Equipment)
end
end
-- Anchor scrollbar to iconScroll's right edge so its position tracks
-- the icon grid rather than the popup. -16/+16 vertical insets are the
-- standard up/down arrow spacing for UIPanelScrollBarTemplate.
local scrollbar = _G["pfEqMgrIconScrollScrollBar"]
if scrollbar then
scrollbar:ClearAllPoints()
scrollbar:SetPoint("TOPLEFT", iconScroll, "TOPRIGHT", 8, -16)
scrollbar:SetPoint("BOTTOMLEFT", iconScroll, "BOTTOMRIGHT", 8, 16)
SkinScrollbar(scrollbar)
end
-- Filter dropdown: "All Icons" / "Spells" / "Items" (top right of icon area).
local filterDropdown = CreateFrame("Frame", "pfEqMgrIconFilter", namePopup, "UIDropDownMenuTemplate")
filterDropdown:SetPoint("TOPRIGHT", namePopup, "TOPRIGHT", 0, -94)
local currentFilter = "all"
local function ApplyFilter(value)
currentFilter = value
UIDropDownMenu_SetSelectedValue(filterDropdown, value)
if provider then
if value == "spells" then provider:SetIconTypes({ IconDataProviderIconType.Spell })
elseif value == "items" then provider:SetIconTypes({ IconDataProviderIconType.Item })
else provider:SetIconTypes(nil) end
-- Don't reset selection — selectedIconPath persists. If the
-- selected icon isn't in the new filter, no grid entry will be
-- highlighted but Save will still write the chosen icon.
pfUI.equipmentmanager.RefreshIconGrid()
end
end
UIDropDownMenu_Initialize(filterDropdown, function()
local info
info = {}; info.text = T["All Icons"] or "All Icons"; info.value = "all"
info.func = function() ApplyFilter("all") end
info.checked = currentFilter == "all"
UIDropDownMenu_AddButton(info)
info = {}; info.text = T["Spells"] or "Spells"; info.value = "spells"
info.func = function() ApplyFilter("spells") end
info.checked = currentFilter == "spells"
UIDropDownMenu_AddButton(info)
info = {}; info.text = T["Items"] or "Items"; info.value = "items"
info.func = function() ApplyFilter("items") end
info.checked = currentFilter == "items"
UIDropDownMenu_AddButton(info)
end)
UIDropDownMenu_SetWidth(120, filterDropdown)
UIDropDownMenu_SetSelectedValue(filterDropdown, "all")
SkinDropDown(filterDropdown)
local iconButtons = {}
for r = 1, ICON_GRID_ROWS do
for c = 1, ICON_GRID_COLS do
local i = (r - 1) * ICON_GRID_COLS + c
local btn = CreateFrame("Button", nil, namePopup)
btn:SetWidth(ICON_BTN_SIZE)
btn:SetHeight(ICON_BTN_SIZE)
btn:SetPoint("TOPLEFT", iconScroll, "TOPLEFT", (c-1) * (ICON_BTN_SIZE + ICON_BTN_PAD), -(r-1) * (ICON_BTN_SIZE + ICON_BTN_PAD))
CreateBackdrop(btn)
btn.texture = btn:CreateTexture(nil, "ARTWORK")
btn.texture:SetAllPoints(btn)
btn.texture:SetTexCoord(.08, .92, .08, .92)
btn.gridIndex = i
btn:SetScript("OnClick", function()
if this.iconIndex and provider then
local path = provider:GetIconByIndex(this.iconIndex)
if path then selectedIconPath = path end
pfUI.equipmentmanager.RefreshIconGrid()
end
end)
btn:SetScript("OnEnter", function()
if not this.iconIndex or not provider then return end
local path = provider:GetIconByIndex(this.iconIndex)
if type(path) == "string" then
local name = string.gsub(path, "^.-INTERFACE\\\\ICONS\\\\", "")
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText(name)
GameTooltip:Show()
end
end)
btn:SetScript("OnLeave", GameTooltip_Hide)
iconButtons[i] = btn
end
end
function pfUI.equipmentmanager.RefreshIconGrid()
EnsureProvider()
local numIcons = provider:GetNumIcons()
local numRows = math.ceil(numIcons / ICON_GRID_COLS)
FauxScrollFrame_Update(iconScroll, numRows, ICON_GRID_ROWS, ICON_BTN_SIZE + ICON_BTN_PAD)
local offset = FauxScrollFrame_GetOffset(iconScroll)
for i = 1, ICON_GRID_ROWS * ICON_GRID_COLS do
local listIdx = i + offset * ICON_GRID_COLS
local btn = iconButtons[i]
if listIdx <= numIcons then
btn:Show()
btn.iconIndex = listIdx
local path = provider:GetIconByIndex(listIdx)
btn.texture:SetTexture(path)
if path == selectedIconPath then
btn.backdrop:SetBackdropBorderColor(1, 0.82, 0, 1)
else
btn.backdrop:SetBackdropBorderColor(pfUI.cache.er, pfUI.cache.eg, pfUI.cache.eb, pfUI.cache.ea)
end
else
btn:Hide()
btn.iconIndex = nil
end
end
-- Sync the "Currently Selected" preview from the path directly so it
-- still shows the chosen icon when filtered out of the grid.
namePopup.selectedPreview.tex:SetTexture(selectedIconPath)
end
iconScroll:SetScript("OnVerticalScroll", function()
FauxScrollFrame_OnVerticalScroll(ICON_BTN_SIZE + ICON_BTN_PAD, function() pfUI.equipmentmanager.RefreshIconGrid() end)
end)
namePopup:SetScript("OnHide", function()
if provider then provider:Release(); provider = nil end
end)
local btnPopupOK = MakeButton("pfEqMgrPopupOK", T["OK"] or "OK", namePopup, namePopup, 14, -340, 80)
btnPopupOK:ClearAllPoints()
btnPopupOK:SetPoint("BOTTOMLEFT", namePopup, "BOTTOMLEFT", 14, 12)
local btnPopupCancel = MakeButton("pfEqMgrPopupCancel", T["Cancel"] or "Cancel", namePopup, namePopup, 0, 0, 80)
btnPopupCancel:ClearAllPoints()
btnPopupCancel:SetPoint("BOTTOMRIGHT", namePopup, "BOTTOMRIGHT", -14, 12)
btnPopupCancel:SetScript("OnClick", function() namePopup:Hide() end)
-- Okay | Cancel, both anchored to the bottom-right corner.
btnPopupOK:ClearAllPoints()
btnPopupOK:SetPoint("BOTTOMRIGHT", btnPopupCancel, "BOTTOMLEFT", -6, 0)
btnPopupOK:SetScript("OnClick", function()
local name = namePopup.editbox:GetText()
if not name or name == "" then return end
-- Strip the prefix to match ClassicAPI's persisted short-form basenames.
local iconForSave = string.gsub(iconPicker.GetIcon(), "INTERFACE\\ICONS\\", "")
local iconForSave = string.gsub(selectedIconPath, "INTERFACE\\ICONS\\", "")
if pendingAction == "new" then
C_EquipmentSet.CreateEquipmentSet(name, iconForSave)
C_EquipmentSet.ClearIgnoredSlotsForSave()
@@ -431,20 +610,31 @@ pfUI:RegisterModule("equipmentmanager", function()
function OpenNamePopup(action, prefillName, prefillIcon)
pendingAction = action
namePopup.editbox:SetText(prefillName or "")
EnsureProvider()
if prefillIcon then
-- Stored icons come back either as a full path or a bare basename
-- (see the row rendering in Refresh). Only prepend the prefix when
-- there's no path separator, so a full path isn't double-prefixed.
iconPicker.SetIcon(string.find(prefillIcon, "\\") and prefillIcon
or ("INTERFACE\\ICONS\\" .. prefillIcon))
local short = string.gsub(prefillIcon, "INTERFACE\\ICONS\\", "")
selectedIconPath = "INTERFACE\\ICONS\\" .. strupper(short)
else
iconPicker.SetIcon(nil)
selectedIconPath = QUESTION_MARK
end
if action == "rename" then
namePopup.title:SetText(T["Rename Set"] or "Rename Set")
iconScroll:Hide()
for _, b in ipairs(iconButtons) do b:Hide() end
namePopup.iconLabel:Hide()
namePopup.selectedLabel:Hide()
namePopup.selectedPreview:Hide()
filterDropdown:Hide()
else
namePopup.title:SetText(action == "new" and (T["Name Set"] or "Name Set") or (T["Save Set"] or "Save Set"))
iconScroll:Show()
namePopup.iconLabel:Show()
namePopup.selectedLabel:Show()
namePopup.selectedPreview:Show()
filterDropdown:Show()
end
namePopup.search:SetText("")
-- Rename only changes the name, so hide the whole icon-picking area.
iconPicker.SetIconAreaShown(action ~= "rename")
namePopup:Show()
if action ~= "rename" then iconPicker.Refresh() end
if action ~= "rename" then pfUI.equipmentmanager.RefreshIconGrid() end
namePopup.editbox:SetFocus()
end
@@ -453,13 +643,9 @@ pfUI:RegisterModule("equipmentmanager", function()
-- ============================================================
local flyout = CreateFrame("Frame", "pfEqMgrFlyout", UIParent)
-- The name/icon popup is sidecar-only, so it closes with the sidecar.
-- Popout arrows and the flyout are reconciled by UpdatePopouts: by
-- default they follow the sidecar, but with the "Always Show Equipment
-- Slot Flyouts" option they stay while the paperdoll is open.
frame:SetScript("OnHide", function()
namePopup:Hide()
UpdatePopouts()
for _, b in ipairs(popoutButtons) do b:Hide() end
flyout:Hide()
end)
flyout:SetFrameStrata("DIALOG")
flyout:Hide()
@@ -490,7 +676,7 @@ pfUI:RegisterModule("equipmentmanager", function()
end
end
ClearCursor()
UIErrorsFrame:AddMessage(ERR_EQUIPMENT_MANAGER_BAGS_FULL, 1, .1, .1, 1)
UIErrorsFrame:AddMessage(EQUIPMENT_MANAGER_BAGS_FULL or "Your bags are full.", 1, .1, .1, 1)
end
local function MakeFlyoutButton(i)
@@ -506,11 +692,11 @@ pfUI:RegisterModule("equipmentmanager", function()
b:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
if this.specialAction == "placeInBags" then
GameTooltip:SetText(EQUIPMENT_MANAGER_PLACE_IN_BAGS, 1, 1, 1)
GameTooltip:SetText(EQUIPMENT_MANAGER_PLACE_IN_BAGS or "Place in Bags", 1, 1, 1)
elseif this.specialAction == "ignore" then
GameTooltip:SetText(EQUIPMENT_MANAGER_IGNORE_SLOT, 1, 1, 1)
GameTooltip:SetText(EQUIPMENT_MANAGER_IGNORE_SLOT or "Ignore this slot", 1, 1, 1)
elseif this.specialAction == "unignore" then
GameTooltip:SetText(EQUIPMENT_MANAGER_UNIGNORE_SLOT, 1, 1, 1)
GameTooltip:SetText(EQUIPMENT_MANAGER_UNIGNORE_SLOT or "Stop ignoring this slot", 1, 1, 1)
elseif this.bag then
GameTooltip:SetBagItem(this.bag, this.slot)
elseif this.invSlot then
@@ -518,7 +704,7 @@ pfUI:RegisterModule("equipmentmanager", function()
end
GameTooltip:Show()
end)
b:SetScript("OnLeave", GameTooltip_Hide)
b:SetScript("OnLeave", function() GameTooltip:Hide() end)
b:SetScript("OnClick", function()
if this.specialAction == "placeInBags" then
UnequipToBags(flyout.targetInvSlot)
@@ -623,6 +809,7 @@ pfUI:RegisterModule("equipmentmanager", function()
if num == 0 then
flyout:Hide()
UIErrorsFrame:AddMessage(T["No matching items in bags"] or "No matching items in bags", 1, 1, 0, 1)
return
end
@@ -751,26 +938,15 @@ pfUI:RegisterModule("equipmentmanager", function()
end
end
-- Popout arrows follow the EM sidecar by default. With the
-- "Always Show Equipment Slot Flyouts" option they follow the paperdoll
-- instead, so gear can be swapped without opening the equipment manager.
local function PopoutsActive()
if C.character.inventory.equipflyout == "1" then
return PaperDollFrame:IsShown()
end
return frame:IsShown()
end
function UpdatePopouts()
local show = PopoutsActive()
for _, b in ipairs(popoutButtons) do
if show then b:Show() else b:Hide() end
end
if not show then flyout:Hide() end
end
PaperDollFrame:HookScript("OnShow", UpdatePopouts)
PaperDollFrame:HookScript("OnHide", UpdatePopouts)
-- Tie popout visibility to the EM sidecar. They appear when the
-- sidecar opens, hide when it closes, and the flyout closes too.
frame:HookScript("OnShow", function()
for _, b in ipairs(popoutButtons) do b:Show() end
end)
frame:HookScript("OnHide", function()
for _, b in ipairs(popoutButtons) do b:Hide() end
if flyout then flyout:Hide() end
end)
-- ============================================================
-- Refresh
+1 -1
View File
@@ -136,7 +136,7 @@ pfUI:RegisterModule("farmmode", function ()
pfUI.farmmap.button.txt = pfUI.farmmap.button:CreateFontString("pfFarmMapText", "LOW", "GameFontWhite")
pfUI.farmmap.button.txt:SetFont(pfUI.font_default, C.global.font_size, "OUTLINE")
pfUI.farmmap.button.txt:SetPoint("CENTER", 0, 0)
pfUI.farmmap.button.txt:SetText("|cff33ffcc " .. T["FARM MODE"])
pfUI.farmmap.button.txt:SetText("|cff33ffcc FARM MODE")
end
CreateBackdrop(pfUI.farmmap)
+3 -3
View File
@@ -263,7 +263,7 @@ pfUI:RegisterModule("firstrun", function ()
SkinCheckbox(f.checkbox, 18)
f.NextScript = function()
if not pfUI.chat then message("Couldn't apply settings. Chat module is disabled.") return end
if not pfUI.chat then message("Couldn't apply settings. Chat module is disabled.") end
pfUI.chat.SetupRightChat(f.checkbox:GetChecked())
end
@@ -283,7 +283,7 @@ pfUI:RegisterModule("firstrun", function ()
SkinCheckbox(f.checkbox, 18)
f.NextScript = function()
if not pfUI.chat then message("Couldn't apply settings. Chat module is disabled.") return end
if not pfUI.chat then message("Couldn't apply settings. Chat module is disabled.") end
if f.checkbox:GetChecked() then
pfUI.chat.SetupPositions()
end
@@ -305,7 +305,7 @@ pfUI:RegisterModule("firstrun", function ()
SkinCheckbox(f.checkbox, 18)
f.NextScript = function()
if not pfUI.chat then message("Couldn't apply settings. Chat module is disabled.") return end
if not pfUI.chat then message("Couldn't apply settings. Chat module is disabled.") end
if f.checkbox:GetChecked() then
pfUI.chat.SetupChannels()
end
-93
View File
@@ -1,93 +0,0 @@
-- Friend notes
-- Client-side notes for friends via ClassicAPI's C_FriendList note API
-- (SetFriendNotes / GetFriendInfo(.notes)). Adds an "Edit Note" entry to the
-- friend right-click menu (the "FRIEND" UnitPopup) and shows the note in a
-- tooltip when you hover a friend in the list.
pfUI:RegisterModule("friendnotes", function ()
local EDIT_TOKEN = "PFUI_FRIEND_NOTE"
-- Note editor. In 1.12 StaticPopup, OnAccept runs with `this` = the OK
-- button (the dialog is this:GetParent()); the friend name arrives as the
-- data argument passed to StaticPopup_Show.
StaticPopupDialogs["PFUI_FRIEND_NOTE_EDIT"] = {
text = SET_FRIENDNOTE_LABEL,
button1 = SAVE,
button2 = CANCEL,
hasEditBox = 1,
maxLetters = 128,
OnAccept = function(name)
C_FriendList.SetFriendNotes(name, _G[this:GetParent():GetName().."EditBox"]:GetText())
end,
EditBoxOnEnterPressed = function(name)
C_FriendList.SetFriendNotes(name, this:GetText())
this:GetParent():Hide()
end,
EditBoxOnEscapePressed = function() this:GetParent():Hide() end,
timeout = 0, whileDead = 1, hideOnEscape = 1,
}
local function OpenNoteEditor(name)
if not name or name == "" then return end
local info = C_FriendList.GetFriendInfo(name)
local dialog = StaticPopup_Show("PFUI_FRIEND_NOTE_EDIT", name, nil, name)
if not dialog then return end
local editbox = _G[dialog:GetName().."EditBox"]
editbox:SetText((info and info.notes) or "")
editbox:HighlightText()
editbox:SetFocus()
end
-- Add "Edit Note" to the friend right-click menu, just before Cancel.
UnitPopupButtons[EDIT_TOKEN] = { text = SET_NOTE, dist = 0 }
local friendMenu = UnitPopupMenus["FRIEND"]
table.insert(friendMenu, table.getn(friendMenu), EDIT_TOKEN)
UnitPopupMenus["PFUI_FRIENDNOTE"] = { EDIT_TOKEN, "CANCEL" }
local function OfflineNoteDropDown_Initialize()
UnitPopup_ShowMenu(_G[UIDROPDOWNMENU_OPEN_MENU], "PFUI_FRIENDNOTE", nil, FriendsDropDown.name)
end
local FriendsFrame_ShowDropdown_orig = FriendsFrame_ShowDropdown
_G.FriendsFrame_ShowDropdown = function(name, connected)
if connected then return FriendsFrame_ShowDropdown_orig(name, connected) end
HideDropDownMenu(1)
FriendsDropDown.initialize = OfflineNoteDropDown_Initialize
FriendsDropDown.displayMode = "MENU"
FriendsDropDown.name = name
ToggleDropDownMenu(1, nil, FriendsDropDown, "cursor")
end
-- Route our entry to the editor and close the menu; delegate the rest. A
-- bare assignment lands on pfUI.env, so set the global explicitly.
local UnitPopup_OnClick_orig = UnitPopup_OnClick
_G.UnitPopup_OnClick = function()
if this and this.value == EDIT_TOKEN then
local dropdown = _G[UIDROPDOWNMENU_INIT_MENU]
local name = dropdown and dropdown.name
CloseDropDownMenus()
OpenNoteEditor(name)
return
end
return UnitPopup_OnClick_orig()
end
-- Show the note in a tooltip while hovering a friend in the list. Friend
-- buttons carry their friend index via SetID (see FriendsFrame_Update).
local function FriendButton_OnEnter()
local index = this:GetID()
if not index or index < 1 then return end
local info = C_FriendList.GetFriendInfoByIndex(index)
if not info or not info.notes or info.notes == "" then return end
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText(info.name or "")
GameTooltip:AddLine(info.notes, 1, 1, 1, 1)
GameTooltip:Show()
end
for i = 1, FRIENDS_TO_DISPLAY do
local button = _G["FriendsFrameFriendButton"..i]
if button then
button:HookScript("OnEnter", FriendButton_OnEnter)
button:HookScript("OnLeave", GameTooltip_Hide)
end
end
end)
+5 -17
View File
@@ -2240,6 +2240,7 @@ pfUI:RegisterModule("gui", function ()
-- build config entries
CreateConfig(U[c], T["Display Frame"] .. ": " .. t, C.unitframes[c], "visible", "checkbox")
CreateConfig(U[c], T["Enable Mouseover Tooltip"], C.unitframes[c], "showtooltip", "checkbox")
CreateConfig(U[c], T["Default Transparency"], C.unitframes[c], "alpha_visible", "dropdown", pfUI.gui.dropdowns.percent_small)
CreateConfig(U[c], T["Out Of Range Transparency"], C.unitframes[c], "alpha_outrange", "dropdown", pfUI.gui.dropdowns.percent_small)
CreateConfig(U[c], T["Offline Transparency"], C.unitframes[c], "alpha_offline", "dropdown", pfUI.gui.dropdowns.percent_small)
@@ -2411,7 +2412,6 @@ pfUI:RegisterModule("gui", function ()
CreateGUIEntry(T["Character"], T["Inventory"], function()
CreateConfig(nil, T["Show Durability"], C.character.inventory, "durability", "checkbox")
CreateConfig(nil, T["Always Show Equipment Slot Flyouts"], C.character.inventory, "equipflyout", "checkbox")
end)
CreateGUIEntry(T["Character"], T["Reputation"], function()
@@ -2763,32 +2763,21 @@ pfUI:RegisterModule("gui", function ()
end)
CreateGUIEntry(T["Tooltip"], nil, function()
CreateConfig(nil, T["General"], nil, nil, "header")
CreateConfig(nil, T["Tooltip Position"], C.tooltip, "position", "dropdown", pfUI.gui.dropdowns.tooltip_position)
CreateConfig(nil, T["Tooltip Text Font"], C.tooltip, "font_tooltip", "dropdown", pfUI.gui.dropdowns.fonts)
CreateConfig(nil, T["Tooltip Text Font Size"], C.tooltip, "font_tooltip_size")
CreateConfig(nil, T["Cursor Tooltip Align"], C.tooltip, "cursoralign", "dropdown", pfUI.gui.dropdowns.tooltip_align)
CreateConfig(nil, T["Cursor Tooltip Offset"], C.tooltip, "cursoroffset")
CreateConfig(nil, T["Enable Extended Guild Information"], C.tooltip, "extguild", "checkbox")
CreateConfig(nil, T["Always Show Health In Percent"], C.tooltip, "alwaysperc", "checkbox")
CreateConfig(nil, T["Show Item IDs"], C.tooltip, "itemid", "checkbox")
CreateConfig(nil, T["Show Movement Speed"], C.tooltip, "movespeed", "checkbox")
CreateConfig(nil, T["Custom Transparency"], C.tooltip, "alpha")
CreateConfig(nil, T["Status Bar Texture"], C.tooltip.statusbar, "texture", "dropdown", pfUI.gui.dropdowns.uf_bartexture)
CreateConfig(nil, T["Items"], nil, nil, "header")
CreateConfig(nil, T["Always Show Item Comparison"], C.tooltip.compare, "showalways", "checkbox")
CreateConfig(nil, T["Always Show Extended Vendor Values"], C.tooltip.vendor, "showalways", "checkbox")
CreateConfig(U["questitem"], T["Show Related Quest On Questitems"], C.tooltip.questitem, "showquest", "checkbox")
CreateConfig(U["questitem"], T["Show Required Questitem Count"], C.tooltip.questitem, "showcount", "checkbox")
CreateConfig(nil, T["Show Item IDs"], C.tooltip, "itemid", "checkbox")
CreateConfig(nil, T["Spells"], nil, nil, "header")
CreateConfig(nil, T["Show Aura Caster"], C.tooltip, "aurasource", "checkbox")
CreateConfig(nil, T["Show Spell IDs"], C.tooltip, "spellid", "checkbox")
CreateConfig(nil, T["Units"], nil, nil, "header")
CreateConfig(nil, T["Enable Extended Guild Information"], C.tooltip, "extguild", "checkbox")
CreateConfig(nil, T["Always Show Health In Percent"], C.tooltip, "alwaysperc", "checkbox")
CreateConfig(nil, T["Show Movement Speed"], C.tooltip, "movespeed", "checkbox")
CreateConfig(nil, T["Show Unit Buffs"], C.tooltip, "showbuffs", "checkbox")
CreateConfig(nil, T["Show Unit IDs"], C.tooltip, "unitid", "checkbox")
end)
CreateGUIEntry(T["Castbar"], T["General"], function()
@@ -2999,7 +2988,6 @@ pfUI:RegisterModule("gui", function ()
CreateConfig(nil, T["Use Chat Colors for Meters"], C.thirdparty, "chatbg", "checkbox")
CreateConfig(nil, "ShaguDPS (" .. T["Skin"] .. ")", C.thirdparty.shagudps, "skin", "checkbox")
CreateConfig(nil, "ShaguDPS (" .. T["Dock"] .. ")", C.thirdparty.shagudps, "dock", "checkbox")
CreateConfig(nil, "GreedMeter (" .. T["Dock"] .. ")", C.thirdparty.greedmeter, "dock", "checkbox")
CreateConfig(nil, "DPSMate (" .. T["Skin"] .. ")", C.thirdparty.dpsmate, "skin", "checkbox")
CreateConfig(nil, "DPSMate (" .. T["Dock"] .. ")", C.thirdparty.dpsmate, "dock", "checkbox")
CreateConfig(nil, "SWStats (" .. T["Skin"] .. ")", C.thirdparty.swstats, "skin", "checkbox")
+9 -34
View File
@@ -1,8 +1,10 @@
pfUI:RegisterModule("hunterbar", function ()
if UnitClassBase("player") ~= "HUNTER" or C.bars.hunterbar == "0" then return end
local class = UnitClassBase("player")
if class ~= "HUNTER" or C.bars.hunterbar == "0" then return end
-- Wing Clip (any rank) and Arcane Shot (any rank) spell IDs.
-- C_Spell.IsSpellInRange works with any spell ID, no actionbar slot needed.
-- IsSpellInRange(spellId) works with any spell ID via Nampower,
-- no actionbar slot needed.
local WINGCLIP_ID = 2974 -- melee range indicator (~5 yd)
local ARCANESHOT_ID = 3044 -- ranged range indicator (~35 yd)
@@ -10,37 +12,22 @@ pfUI:RegisterModule("hunterbar", function ()
-- Only swap BACK to melee bar when Wing Clip is actually in range.
-- This prevents rapid bar-flipping in the transition zone.
local THROTTLE = 0.02 -- seconds between range checks (was every frame)
pfUI.hunterbar = CreateFrame("Frame", "pfHunterBar", UIParent)
-- track which page we last forced so we don't spam ChangeActionBarPage()
pfUI.hunterbar.lastPage = nil
pfUI.hunterbar.elapsed = 0
-- Idle until there's a live, attackable target. A hidden frame runs no
-- OnUpdate, so the range polling only ticks while it can matter.
pfUI.hunterbar:Hide()
pfUI.hunterbar:SetScript("OnUpdate", function()
this.elapsed = this.elapsed + arg1
if this.elapsed < THROTTLE then return end
this.elapsed = 0
-- Target died or turned unattackable without a target-change event
-- (it dropped to a corpse); stop ticking until the next target.
-- only act when there is a live, attackable target
if not UnitExists("target") or not UnitCanAttack("player", "target") then
this:Hide()
return
end
-- C_Spell.IsSpellInRange returns true / false / nil (rangeless). The
-- explicit == true / == false tests leave the bar unchanged on nil.
local wingclipInRange = C_Spell.IsSpellInRange(WINGCLIP_ID, "target")
local arcaneshotInRange = C_Spell.IsSpellInRange(ARCANESHOT_ID, "target")
local wingclipInRange = IsSpellInRange(WINGCLIP_ID, "target")
local arcaneshotInRange = IsSpellInRange(ARCANESHOT_ID, "target")
-- swap to ranged bar: out of melee range AND arcane shot (8yd) in range
if wingclipInRange == false and arcaneshotInRange == true then
if wingclipInRange == 0 and arcaneshotInRange == 1 then
if this.lastPage ~= 2 then
this.lastPage = 2
_G.CURRENT_ACTIONBAR_PAGE = 2
@@ -48,7 +35,7 @@ pfUI:RegisterModule("hunterbar", function ()
end
-- swap to melee bar: in melee range AND arcane shot (8yd) out of range
elseif wingclipInRange == true and arcaneshotInRange == false then
elseif wingclipInRange == 1 and arcaneshotInRange == 0 then
if this.lastPage ~= 1 then
this.lastPage = 1
_G.CURRENT_ACTIONBAR_PAGE = 1
@@ -56,16 +43,4 @@ pfUI:RegisterModule("hunterbar", function ()
end
end
end)
-- Start ticking only when we gain an attackable target.
pfUI.hunterbar:RegisterEvent("PLAYER_TARGET_CHANGED")
pfUI.hunterbar:RegisterEvent("PLAYER_ENTERING_WORLD")
pfUI.hunterbar:SetScript("OnEvent", function()
if UnitExists("target") and UnitCanAttack("player", "target") then
this.elapsed = THROTTLE -- run the first check on the next frame
this:Show()
else
this:Hide()
end
end)
end)
+7 -7
View File
@@ -75,14 +75,14 @@ pfUI:RegisterModule("innervatecall", function ()
SendChatMessage(">> Innervate casted on " .. targetName .. " <<", channel)
-- Schedule "ready" announcement when the cooldown expires. Read the
-- precise remaining time from C_Spell.GetSpellCooldown (startTime and
-- duration are seconds from the GetTime epoch); fall back to 360s.
-- Schedule "ready" announcement when cooldown expires
-- Use GetSpellIdCooldown for precise remaining time, fallback to 360s
local cdRemaining = 360
local cd = C_Spell.GetSpellCooldown(INNERVATE_SPELLID)
if cd and cd.duration and cd.duration > 0 then
local remaining = cd.startTime + cd.duration - GetTime()
if remaining > 0 then cdRemaining = remaining end
if GetSpellIdCooldown then
local cd = GetSpellIdCooldown(INNERVATE_SPELLID)
if cd and cd.cooldownRemainingMs and cd.cooldownRemainingMs > 0 then
cdRemaining = cd.cooldownRemainingMs / 1000
end
end
C_Timer.After(cdRemaining, function()
+4 -8
View File
@@ -26,14 +26,10 @@ pfUI:RegisterModule("itemcount", function ()
if bank < 0 then bank = 0 end
frame:AddLine(" ")
if bags > 0 then frame:AddDoubleLine(T["Bags"] .. ":", bags, 1, 1, 1, 1, 1, 1) end
if bank > 0 then frame:AddDoubleLine(T["Bank"] .. ":", bank, 1, 1, 1, 1, 1, 1) end
if equipped > 0 then frame:AddDoubleLine(T["Equipped"] .. ":", equipped, 1, 1, 1, 1, 1, 1) end
local sources = (bags > 0 and 1 or 0) + (bank > 0 and 1 or 0) + (equipped > 0 and 1 or 0)
if sources > 1 then
frame:AddDoubleLine(T["Total"] .. ":", total, 1, 1, 1, 1, 1, 1)
end
if bags > 0 then frame:AddDoubleLine("Bags:", bags, 1, 1, 1, 1, 1, 1) end
if bank > 0 then frame:AddDoubleLine("Bank:", bank, 1, 1, 1, 1, 1, 1) end
if equipped > 0 then frame:AddDoubleLine("Equipped:", equipped, 1, 1, 1, 1, 1, 1) end
frame:AddDoubleLine("Total:", total, 1, 1, 1, 1, 1, 1)
frame:Show()
end
+15 -5
View File
@@ -77,7 +77,9 @@ pfUI:RegisterModule("loot", function ()
if pfUI.loot.my_index or pfUI.loot.disenchanter_index or pfUI.loot.banker_index then
info = wipe(info)
info.text = T["Special Recipient"]
info.textR, info.textG, info.textB = NORMAL_FONT_COLOR:GetRGB()
info.textR = NORMAL_FONT_COLOR.r
info.textG = NORMAL_FONT_COLOR.g
info.textB = NORMAL_FONT_COLOR.b
info.textHeight = 12
info.hasArrow = 1
info.notCheckable = 1
@@ -156,7 +158,9 @@ pfUI:RegisterModule("loot", function ()
if level == 1 then
info = wipe(info)
info.text = T["Random"]
info.textR, info.textG, info.textB = NORMAL_FONT_COLOR:GetRGB()
info.textR = NORMAL_FONT_COLOR.r
info.textG = NORMAL_FONT_COLOR.g
info.textB = NORMAL_FONT_COLOR.b
info.value = "PFRANDOM"
info.textHeight = 12
info.notCheckable = 1
@@ -166,7 +170,9 @@ pfUI:RegisterModule("loot", function ()
info = wipe(info)
info.text = T["Request Rolls"]
info.textR, info.textG, info.textB = NORMAL_FONT_COLOR:GetRGB()
info.textR = NORMAL_FONT_COLOR.r
info.textG = NORMAL_FONT_COLOR.g
info.textB = NORMAL_FONT_COLOR.b
info.value = "PFROLLS"
info.textHeight = 12
info.hasArrow = 1
@@ -177,7 +183,9 @@ pfUI:RegisterModule("loot", function ()
if UIDROPDOWNMENU_MENU_VALUE == "PFROLLS" then
info = wipe(info)
info.text = T["Clear Rolls"]
info.textR, info.textG, info.textB = NORMAL_FONT_COLOR:GetRGB()
info.textR = NORMAL_FONT_COLOR.r
info.textG = NORMAL_FONT_COLOR.g
info.textB = NORMAL_FONT_COLOR.b
info.value = "PFCLEARROLLS"
info.notCheckable = 1
info.func = pfUI.loot.ClearRolls
@@ -185,7 +193,9 @@ pfUI:RegisterModule("loot", function ()
info = wipe(info)
info.text = T["Reroll Ties"]
info.textR, info.textG, info.textB = NORMAL_FONT_COLOR:GetRGB()
info.textR = NORMAL_FONT_COLOR.r
info.textG = NORMAL_FONT_COLOR.g
info.textB = NORMAL_FONT_COLOR.b
info.value = "PFTIEROLL"
info.notCheckable = 1
info.arg1 = pfUI.loot.rollers_sorted
+8 -2
View File
@@ -16,6 +16,12 @@ pfUI:RegisterModule("loothistory", function ()
local WINMARK = "Interface\\Buttons\\UI-CheckBox-Check"
local QUESTIONMARK = "Interface\\Icons\\INV_Misc_QuestionMark"
local function ClassColor(class)
local c = class and RAID_CLASS_COLORS[class]
if c then return c.r, c.g, c.b end
return 1, 1, 1
end
-- Paint an item row's icon/name/quality from a loaded Item mixin.
local function RenderItemVisual(f, item)
local r, g, b = 1, 1, 1
@@ -271,7 +277,7 @@ pfUI:RegisterModule("loothistory", function ()
if wroll and wroll > 0 then f.winroll:SetText(wroll) else f.winroll:SetText("") end
f.winroll:Show()
f.winname:SetText(wname or UNKNOWN)
f.winname:SetTextColor(PFUI_CLASS_COLORS[wclass]:GetRGB())
f.winname:SetTextColor(ClassColor(wclass))
f.winname:Show()
else
-- nobody won: everyone passed
@@ -292,7 +298,7 @@ pfUI:RegisterModule("loothistory", function ()
local function RenderPlayerFrame(pf, name, class, rollType, roll, isWinner)
pf.name:SetText(name or UNKNOWN)
pf.name:SetTextColor(PFUI_CLASS_COLORS[class]:GetRGB())
pf.name:SetTextColor(ClassColor(class))
pf.rollicon:SetTexture(ROLL_TEX[rollType] or ROLL_TEX[ROLL_PASS])
if roll and roll > 0 then pf.rolltext:SetText(roll) else pf.rolltext:SetText("") end
+215 -12
View File
@@ -9,13 +9,36 @@
-- MacroFrame (list + body editor) is Blizzard's, already skinned elsewhere.
pfUI:RegisterModule("macroicons", function ()
HookAddonOrVariable("Blizzard_MacroUI", function()
local QUESTION_MARK = "INTERFACE\\ICONS\\INV_MISC_QUESTIONMARK"
local ICON_GRID_COLS = 10
local ICON_GRID_ROWS = 8
local ICON_BTN_SIZE = 36
local ICON_BTN_PAD = 6
-- Shared picker state (forward-declared so the grid/filter closures below
-- can capture them before RefreshIconGrid is assigned).
local provider = nil
local selectedIconPath = QUESTION_MARK
local currentFilter = "all"
local searchText = ""
local filtered = nil -- provider indices matching the search, or nil when empty
local RefreshIconGrid, RebuildFilter
local function EnsureProvider()
if not provider then
-- Spellbook extra type leads with class-relevant spell/talent icons.
provider = CreateAndInitFromMixin(IconDataProviderMixin, IconDataProviderExtraType.Spellbook)
end
end
-- ============================================================
-- Popup frame
-- ============================================================
local picker = CreateFrame("Frame", "pfMacroIconPicker", UIParent)
picker:SetFrameStrata("DIALOG")
picker:SetSize(472, 484)
picker:SetSize(472, 498)
-- Starts anchored to the right of the macro panel (re-anchored at open
-- once the skin's backdrop exists); dragging pins it in place after.
picker:SetPoint("BOTTOMLEFT", MacroFrame, "BOTTOMRIGHT", 8, 0)
@@ -31,9 +54,167 @@ pfUI:RegisterModule("macroicons", function ()
this.userMoved = true
end)
-- Spellbook seed leads the grid with class-relevant spell/talent icons.
local iconPicker = CreateIconPicker("pfMacroIcon", picker,
IconDataProviderExtraType.Spellbook, MACRO_POPUP_TEXT)
picker.title = picker:CreateFontString(nil, "OVERLAY", "GameFontNormal")
picker.title:SetPoint("TOP", picker, "TOP", 0, -10)
picker.nameLabel = picker:CreateFontString(nil, "OVERLAY", "GameFontNormal")
picker.nameLabel:SetPoint("TOPLEFT", picker, "TOPLEFT", 14, -34)
picker.nameLabel:SetText(MACRO_POPUP_TEXT)
picker.editbox = CreateFrame("EditBox", "pfMacroIconPickerName", picker, "InputBoxTemplate")
picker.editbox:SetSize(280, 20)
picker.editbox:SetPoint("TOPLEFT", picker, "TOPLEFT", 14, -52)
picker.editbox:SetAutoFocus(false)
picker.editbox:SetMaxLetters(16)
CreateBackdrop(picker.editbox)
picker.selectedPreview = CreateFrame("Frame", nil, picker)
picker.selectedPreview:SetSize(42, 42)
picker.selectedPreview:SetPoint("TOPRIGHT", picker, "TOPRIGHT", -14, -44)
CreateBackdrop(picker.selectedPreview)
picker.selectedPreview.tex = picker.selectedPreview:CreateTexture(nil, "ARTWORK")
picker.selectedPreview.tex:SetAllPoints(picker.selectedPreview)
picker.selectedPreview.tex:SetTexCoord(.08, .92, .08, .92)
picker.iconLabel = picker:CreateFontString(nil, "OVERLAY", "GameFontNormal")
picker.iconLabel:SetPoint("TOPLEFT", picker, "TOPLEFT", 14, -100)
picker.iconLabel:SetText(T["Choose an Icon:"] or "Choose an Icon:")
-- Icon grid + scroll
local iconScroll = CreateFrame("ScrollFrame", "pfMacroIconScroll", picker, "FauxScrollFrameTemplate")
iconScroll:SetPoint("TOPLEFT", picker, "TOPLEFT", 14, -130)
iconScroll:SetWidth(ICON_GRID_COLS * (ICON_BTN_SIZE + ICON_BTN_PAD) - ICON_BTN_PAD)
iconScroll:SetHeight(ICON_GRID_ROWS * (ICON_BTN_SIZE + ICON_BTN_PAD) - ICON_BTN_PAD)
local scrollbar = _G["pfMacroIconScrollScrollBar"]
scrollbar:ClearAllPoints()
scrollbar:SetPoint("TOPLEFT", iconScroll, "TOPRIGHT", 8, -16)
scrollbar:SetPoint("BOTTOMLEFT", iconScroll, "BOTTOMRIGHT", 8, 16)
SkinScrollbar(scrollbar)
-- Filter dropdown: All / Spells / Items
local filterDropdown = CreateFrame("Frame", "pfMacroIconFilter", picker, "UIDropDownMenuTemplate")
filterDropdown:SetPoint("TOPRIGHT", picker, "TOPRIGHT", 0, -94)
local function ApplyFilter(value)
currentFilter = value
UIDropDownMenu_SetSelectedValue(filterDropdown, value)
if provider then
if value == "spells" then provider:SetIconTypes({ IconDataProviderIconType.Spell })
elseif value == "items" then provider:SetIconTypes({ IconDataProviderIconType.Item })
else provider:SetIconTypes(nil) end
RebuildFilter()
RefreshIconGrid()
end
end
UIDropDownMenu_Initialize(filterDropdown, function()
local info
info = {}; info.text = T["All Icons"] or "All Icons"; info.value = "all"
info.func = function() ApplyFilter("all") end
info.checked = currentFilter == "all"
UIDropDownMenu_AddButton(info)
info = {}; info.text = T["Spells"] or "Spells"; info.value = "spells"
info.func = function() ApplyFilter("spells") end
info.checked = currentFilter == "spells"
UIDropDownMenu_AddButton(info)
info = {}; info.text = T["Items"] or "Items"; info.value = "items"
info.func = function() ApplyFilter("items") end
info.checked = currentFilter == "items"
UIDropDownMenu_AddButton(info)
end)
UIDropDownMenu_SetWidth(120, filterDropdown)
UIDropDownMenu_SetSelectedValue(filterDropdown, "all")
SkinDropDown(filterDropdown)
local iconButtons = {}
for r = 1, ICON_GRID_ROWS do
for c = 1, ICON_GRID_COLS do
local i = (r - 1) * ICON_GRID_COLS + c
local btn = CreateFrame("Button", nil, picker)
btn:SetSize(ICON_BTN_SIZE, ICON_BTN_SIZE)
btn:SetPoint("TOPLEFT", iconScroll, "TOPLEFT", (c-1) * (ICON_BTN_SIZE + ICON_BTN_PAD), -(r-1) * (ICON_BTN_SIZE + ICON_BTN_PAD))
CreateBackdrop(btn)
btn.texture = btn:CreateTexture(nil, "ARTWORK")
btn.texture:SetAllPoints(btn)
btn.texture:SetTexCoord(.08, .92, .08, .92)
btn:SetScript("OnClick", function()
if this.iconIndex and provider then
local path = provider:GetIconByIndex(this.iconIndex)
if path then selectedIconPath = path end
RefreshIconGrid()
end
end)
btn:SetScript("OnEnter", function()
if not this.iconIndex or not provider then return end
local path = provider:GetIconByIndex(this.iconIndex)
if type(path) == "string" then
local name = string.gsub(path, "^.-INTERFACE\\\\ICONS\\\\", "")
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText(name)
GameTooltip:Show()
end
end)
btn:SetScript("OnLeave", GameTooltip_Hide)
iconButtons[i] = btn
end
end
-- Normalize a texture to an uppercase basename so selection matching
-- works across sources: GetMacroInfo returns "Interface\Icons\<name>"
-- while provider paths are uppercase-prefixed for base icons and
-- mixed-case for spellbook extras.
local function IconKey(path)
if type(path) ~= "string" then return path end
return string.gsub(string.upper(path), "^.*[\\/]", "")
end
-- Rebuild the search-filtered index list (provider indices whose icon
-- basename contains the search text); nil when the search box is empty.
function RebuildFilter()
EnsureProvider()
if searchText == "" then
filtered = nil
return
end
filtered = {}
for i = 1, provider:GetNumIcons() do
local path = provider:GetIconByIndex(i)
if type(path) == "string" and string.find(IconKey(path), searchText, 1, true) then
table.insert(filtered, i)
end
end
end
function RefreshIconGrid()
EnsureProvider()
local numIcons = filtered and table.getn(filtered) or provider:GetNumIcons()
local numRows = math.ceil(numIcons / ICON_GRID_COLS)
FauxScrollFrame_Update(iconScroll, numRows, ICON_GRID_ROWS, ICON_BTN_SIZE + ICON_BTN_PAD)
local offset = FauxScrollFrame_GetOffset(iconScroll)
for i = 1, ICON_GRID_ROWS * ICON_GRID_COLS do
local listIdx = i + offset * ICON_GRID_COLS
local btn = iconButtons[i]
if listIdx <= numIcons then
btn:Show()
local providerIdx = filtered and filtered[listIdx] or listIdx
btn.iconIndex = providerIdx
local path = provider:GetIconByIndex(providerIdx)
btn.texture:SetTexture(path)
if IconKey(path) == IconKey(selectedIconPath) then
btn.backdrop:SetBackdropBorderColor(1, 0.82, 0, 1)
else
btn.backdrop:SetBackdropBorderColor(pfUI.cache.er, pfUI.cache.eg, pfUI.cache.eb, pfUI.cache.ea)
end
else
btn:Hide()
btn.iconIndex = nil
end
end
picker.selectedPreview.tex:SetTexture(selectedIconPath)
end
iconScroll:SetScript("OnVerticalScroll", function()
FauxScrollFrame_OnVerticalScroll(ICON_BTN_SIZE + ICON_BTN_PAD, function() RefreshIconGrid() end)
end)
-- ============================================================
-- Open / save
@@ -54,11 +235,13 @@ pfUI:RegisterModule("macroicons", function ()
picker.mode = "edit"
local name, texture = GetMacroInfo(MacroFrame.selectedMacro)
picker.editbox:SetText(name or "")
iconPicker.SetIcon(texture)
selectedIconPath = texture or QUESTION_MARK
picker.title:SetText(T["Edit Macro"] or "Edit Macro")
else
picker.mode = "new"
picker.editbox:SetText("")
iconPicker.SetIcon(nil)
selectedIconPath = QUESTION_MARK
picker.title:SetText(T["New Macro"] or "New Macro")
end
picker.search:SetText("")
if not picker.userMoved then
@@ -68,25 +251,27 @@ pfUI:RegisterModule("macroicons", function ()
SetMacroButtons(false)
picker:Show()
picker.editbox:SetFocus()
iconPicker.Refresh()
RefreshIconGrid()
end
local function SaveMacroPopup()
local text = picker.editbox:GetText()
local text = strtrim(picker.editbox:GetText())
if text == "" then return end
local icon = iconPicker.GetIcon()
if picker.mode == "edit" and MacroFrame.selectedMacro then
C_Macro.EditMacro(MacroFrame.selectedMacro, text, icon, nil)
C_Macro.EditMacro(MacroFrame.selectedMacro, text, selectedIconPath, nil)
MacroFrame_SelectMacro(MacroFrame.selectedMacro)
else
local idx = C_Macro.CreateMacro(text, icon, nil, (MacroFrame.macroBase or 0) > 0)
local idx = C_Macro.CreateMacro(text, selectedIconPath, nil, (MacroFrame.macroBase or 0) > 0)
if idx then MacroFrame_SelectMacro(idx) end
end
picker:Hide()
end
picker:SetScript("OnHide", function() MacroFrame_Update() end)
picker:SetScript("OnHide", function()
if provider then provider:Release(); provider = nil end
MacroFrame_Update()
end)
picker.editbox:SetScript("OnEnterPressed", SaveMacroPopup)
picker.editbox:SetScript("OnEscapePressed", function() picker:Hide() end)
@@ -108,6 +293,24 @@ pfUI:RegisterModule("macroicons", function ()
-- Okay | Cancel, both anchored to the bottom-right corner.
okay:SetPoint("BOTTOMRIGHT", cancel, "BOTTOMLEFT", -6, 0)
-- Search box (bottom-left) filters the grid by icon name.
picker.searchLabel = picker:CreateFontString(nil, "OVERLAY", "GameFontNormal")
picker.searchLabel:SetPoint("BOTTOMLEFT", picker, "BOTTOMLEFT", 16, 18)
picker.searchLabel:SetText(SEARCH)
picker.search = CreateFrame("EditBox", "pfMacroIconSearch", picker, "InputBoxTemplate")
picker.search:SetSize(180, 20)
picker.search:SetPoint("LEFT", picker.searchLabel, "RIGHT", 10, 0)
picker.search:SetAutoFocus(false)
CreateBackdrop(picker.search)
picker.search:SetScript("OnEscapePressed", function() this:ClearFocus() end)
picker.search:SetScript("OnTextChanged", function()
searchText = strupper(strtrim(this:GetText()))
RebuildFilter()
scrollbar:SetValue(0)
RefreshIconGrid()
end)
-- ============================================================
-- Take over the New / Change-Icon buttons; retire Blizzard's popup
-- ============================================================
+1 -2
View File
@@ -13,8 +13,7 @@ pfUI:RegisterModule("macrotweak", function ()
end
-- do not write macro calls into chat input history
-- (install once: _AddHistoryLine is our backup slot and is nil until we set it)
if not ChatFrameEditBox._AddHistoryLine then
if ChatFrameEditBox._AddHistoryLine then
local userinput
ChatFrameEditBox._AddHistoryLine = ChatFrameEditBox.AddHistoryLine
ChatFrameEditBox.AddHistoryLine = function(self, text)
+1 -7
View File
@@ -77,13 +77,7 @@ pfUI:RegisterModule("map", function ()
if point == "TOPLEFT" and relpoint == "TOPLEFT" then
offx = offx*oldscale/scale
offy = offy*oldscale/scale
-- Anchor to the parent (3-arg SetPoint) rather than re-using the frame
-- GetPoint handed back. Re-anchoring to `rel` throws
-- "<unnamed> is dependent on this" as soon as anything else has anchored
-- itself to WorldMapFrame, which aborts the whole zoom handler. The
-- parent-relative form is also what LoadMovable/SaveMovable already
-- assume: SaveMovable stores only xpos/ypos, with no relative frame.
WorldMapFrame:SetPoint(point, offx, offy)
WorldMapFrame:SetPoint(point, rel, relpoint, offx, offy)
end
WorldMapFrame:SetScale(scale)
+4 -5
View File
@@ -52,19 +52,19 @@ pfUI:RegisterModule("mapreveal", function ()
WorldMapTooltip:AddLine(this.name, 1, 1, 1)
WorldMapTooltip:Show()
if not explorecaches[this.area] then return end
if not explorecaches[this.name] then return end
if C.appearance.worldmap.mapreveal == "0" then return end
for texture in pairs(explorecaches[this.area]) do
for texture in pairs(explorecaches[this.name]) do
texture:SetVertexColor(1,1,1,1)
end
end
local exploreLeave = function()
WorldMapTooltip:Hide()
if not explorecaches[this.area] then return end
if not explorecaches[this.name] then return end
if C.appearance.worldmap.mapreveal == "0" then return end
local r,g,b,a = GetStringColor(C.appearance.worldmap.mapreveal_color)
for texture in pairs(explorecaches[this.area]) do
for texture in pairs(explorecaches[this.name]) do
texture:SetVertexColor(r,g,b,a)
end
end
@@ -120,7 +120,6 @@ pfUI:RegisterModule("mapreveal", function ()
explore:EnableMouse(true)
explore:SetFrameLevel(255)
explore.name = mapFileName .. " (" .. name .. ")"
explore.area = name -- cache key: explorecaches is keyed by the plain area name
explore.tex = explore.tex or explore:CreateTexture("", "OVERLAY")
explore.tex:SetBlendMode("ADD")
explore.tex:SetTexCoord(.08, .92, .08, .92)
+29 -74
View File
@@ -55,7 +55,6 @@ pfUI:RegisterModule("nameplates", function ()
local savedHostileState = nil
local savedFriendlyState = nil
local inFriendlyZone = false
local myGuild = nil
local platecount = 0
local registry = {}
-- Subset of registry that currently has a unit assigned (between
@@ -283,35 +282,15 @@ pfUI:RegisterModule("nameplates", function ()
end
end
-- Numeric CreatureType.dbc id for the plate's unit (8 = Critter, 11 = Totem).
-- It's fixed per unit, so cache it and reset on plate reuse. nil while the GUID
-- can't resolve yet (incl. the no-SuperWoW case, where cachedGuid is empty).
-- Locale-proof and covers custom units for free -- no name tables needed.
local function CreatureType(plate)
local guid = plate.cachedGuid
if not guid or guid == "" then return nil end
if not plate.creatureType then
plate.creatureType = UnitCreatureTypeID(guid) -- nil retries next call
local function TotemPlate(name)
if C.nameplates.totemicons == "1" then
for totem, icon in pairs(L["totems"]) do
if string.find(name, totem) then return icon end
end
end
return plate.creatureType
end
-- Totem icon, read straight from the game:
-- * Passive totems self-cast their provided buff, so they carry exactly one
-- aura whose icon IS the totem's icon (Totem::Summon TOTEM_PASSIVE).
-- * Active totems (Searing/Magma/Fire Nova) cast at enemies and hold no
-- self-aura, so their icon arrives via UNIT_SPELLCAST_SUCCEEDED (cached
-- into plate.totemIcon by the event handler); nil here until then.
local function TotemPlate(plate)
if C.nameplates.totemicons ~= "1" then return nil end
if CreatureType(plate) ~= 11 then return nil end
if plate.totemIcon then return plate.totemIcon end
local aura = C_UnitAuras.GetBuffDataByIndex(plate.cachedGuid, 1)
if aura then plate.totemIcon = aura.icon end
return plate.totemIcon
end
local function HidePlate(unittype, fullhp, target, plate)
local function HidePlate(unittype, name, fullhp, target)
-- keep some plates always visible according to config
if C.nameplates.fullhealth == "1" and not fullhp then return nil end
if C.nameplates.target == "1" and target then return nil end
@@ -327,10 +306,14 @@ pfUI:RegisterModule("nameplates", function ()
return true
elseif C.nameplates.friendlyplayer == "1" and unittype == "FRIENDLY_PLAYER" then
return true
elseif C.nameplates.critters == "1" and CreatureType(plate) == 8 then
return true
elseif C.nameplates.totems == "1" and CreatureType(plate) == 11 then
return true
elseif C.nameplates.critters == "1" and unittype == "NEUTRAL_NPC" then
for i, critter in pairs(L["critters"]) do
if string.lower(name) == string.lower(critter) then return true end
end
elseif C.nameplates.totems == "1" then
for totem in pairs(L["totems"]) do
if string.find(name, totem) then return true end
end
end
-- nothing to hide
@@ -499,8 +482,6 @@ nameplates:RegisterEvent("UNIT_SPELLCAST_START")
nameplates:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START")
nameplates:RegisterEvent("UNIT_SPELLCAST_STOP")
nameplates:RegisterEvent("UNIT_SPELLCAST_CHANNEL_STOP")
nameplates:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED")
nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
nameplates:SetScript("OnEvent", function()
-- Stop event handling during logout to prevent crash 132
@@ -512,17 +493,13 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
nameplates.mouselook:SetScript("OnUpdate", nil)
end
return
elseif event == "PLAYER_GUILD_UPDATE" and arg1 == 'player' then
myGuild = GetGuildInfo("player")
elseif event == "PLAYER_ENTERING_WORLD" or event == "ZONE_CHANGED_NEW_AREA" then
if event == "PLAYER_ENTERING_WORLD" then
CacheConfig()
this:SetGameVariables()
RebuildRaidGuidCache()
frameState.targetGuid = UnitGUID("target")
myGuild = GetGuildInfo("player")
end
-- Handle friendly zone nameplate disable feature
@@ -586,8 +563,6 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
local guid = UnitGUID(arg1)
plate.nameplate.cachedGuid = guid
plate.nameplate.unit = arg1
plate.nameplate.creatureType = nil -- recompute for the new unit
plate.nameplate.totemIcon = nil
if guid then
plateByGuid[guid] = plate.nameplate
-- Seed: the unit may already be mid-cast (its UNIT_SPELLCAST_START
@@ -665,22 +640,6 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
end
end
elseif event == "UNIT_SPELLCAST_SUCCEEDED" then
-- Active totems (Searing/Magma/Fire Nova) carry no self-aura, so their
-- attack cast is the only icon source. Capture it once per totem, gated on
-- creature type so a normal caster's spell never styles it as a totem.
if arg1 and strfind(arg1, "^nameplate") then
local guid = UnitGUID(arg1)
local plate = guid and plateByGuid[guid]
if plate and not plate.totemIcon and arg3 and CreatureType(plate) == 11 then
local tex = C_Spell.GetSpellTexture(arg3)
if tex then
plate.totemIcon = tex
plate.castUpdate = true -- re-render now so the icon shows
end
end
end
elseif event == "UNIT_AURA" then
-- ClassicAPI: fires with arg1 == "nameplateN" when a unit's aura set
-- changes (add/remove/modify). Flag the matching plate so OnUpdate does a
@@ -1107,11 +1066,11 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
end
-- hide frames according to the configuration
local TotemIcon = TotemPlate(plate)
local TotemIcon = TotemPlate(name)
if TotemIcon then
-- icon resolved from the totem's aura / attack cast (already a full path)
plate.totem.icon:SetTexture(TotemIcon)
-- create totem icon
plate.totem.icon:SetTexture("Interface\\Icons\\" .. TotemIcon)
plate.glow:Hide()
plate.level:Hide()
@@ -1119,7 +1078,7 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
plate.health:Hide()
plate.guild:Hide()
plate.totem:Show()
elseif HidePlate(unittype, (hpmax-hp == hpmin), target, plate) then
elseif HidePlate(unittype, name, (hpmax-hp == hpmin), target) then
plate.level:SetPoint("RIGHT", plate.name, "LEFT", -3, 0)
plate.name:SetParent(plate)
plate.guild:SetPoint("BOTTOM", plate.name, "BOTTOM", -2, -(font_size + 2))
@@ -1162,7 +1121,7 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
if guild and C.nameplates.showguildname == "1" then
plate.guild:SetText(guild)
if guild == myGuild then
if UnitIsInMyGuild(plate.unit) then
plate.guild:SetTextColor(0, 0.9, 0, 1)
else
plate.guild:SetTextColor(0.8, 0.8, 0.8, 1)
@@ -1226,11 +1185,12 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
local r, g, b, a = unpack(unitcolors[unittype])
if class and (
(unittype == "ENEMY_PLAYER" and C.nameplates["enemyclassc"] == "1") or
(unittype == "FRIENDLY_PLAYER" and C.nameplates["friendclassc"] == "1")
) then
r, g, b, a = PFUI_CLASS_COLORS[class]:GetRGBA()
if class then
if unittype == "ENEMY_PLAYER" and C.nameplates["enemyclassc"] == "1" then
r, g, b, a = PFUI_CLASS_COLORS[class]:GetRGBA()
elseif unittype == "FRIENDLY_PLAYER" and C.nameplates["friendclassc"] == "1" then
r, g, b, a = PFUI_CLASS_COLORS[class]:GetRGBA()
end
end
if unitstr and UnitIsTapped(unitstr) and not UnitIsTappedByPlayer(unitstr) then
@@ -1256,15 +1216,10 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
plate.cache.namecolor = r + g + b
end
-- update combopoints
for i=1, 5 do plate.combopoints[i]:Hide() end
if target and C.nameplates.cpdisplay == "1" then
local cp = GetComboPoints("target")
if plate.cpShown ~= cp then
for i=1, 5 do plate.combopoints[i]:SetShown(i <= cp) end
plate.cpShown = cp
end
elseif plate.cpShown then
for i=1, 5 do plate.combopoints[i]:Hide() end
plate.cpShown = nil
for i=1, GetComboPoints("target") do plate.combopoints[i]:Show() end
end
-- update debuffs
+15 -7
View File
@@ -296,18 +296,18 @@ pfUI:RegisterModule("panel", function()
local playerzone = GetRealZoneText()
for friendIndex=1, all do
local info = C_FriendList.GetFriendInfoByIndex(friendIndex)
if info and info.connected and info.classFilename and info.level then
local friend_name, friend_level, friend_class, friend_area, friend_connected = GetFriendInfo(friendIndex)
if friend_connected and friend_class and friend_level then
if not init then
GameTooltip_SetDefaultAnchor(GameTooltip, this)
GameTooltip:ClearLines()
GameTooltip:AddLine("|cff555555" .. T["Friends Online"])
init = true
end
local ccolor = PFUI_CLASS_COLORS[info.classFilename]
local lcolor = GetDifficultyColor(tonumber(info.level)) or { 1, 1, 1 }
local zcolor = info.area == playerzone and "|cff33ffcc" or "|cffcccccc"
GameTooltip:AddDoubleLine(ccolor:WrapTextInColorCode(info.name) .. rgbhex(lcolor) .. " [" .. info.level .. "]", zcolor .. info.area)
local ccolor = PFUI_CLASS_COLORS[L["class"][friend_class]] or { 1, 1, 1 }
local lcolor = GetDifficultyColor(tonumber(friend_level)) or { 1, 1, 1 }
local zcolor = friend_area == playerzone and "|cff33ffcc" or "|cffcccccc"
GameTooltip:AddDoubleLine(rgbhex(ccolor) .. friend_name .. rgbhex(lcolor) .. " [" .. friend_level .. "]", zcolor .. friend_area)
end
end
@@ -315,7 +315,15 @@ pfUI:RegisterModule("panel", function()
end
widget.Click = function() ToggleFriendsFrame(1) end
widget:SetScript("OnEvent", function()
local online = C_FriendList.GetNumOnlineFriends()
local online = 0
local all = GetNumFriends()
for friendIndex=1, all do
local friend_name, friend_level, friend_class, friend_area, friend_connected = GetFriendInfo(friendIndex)
if ( friend_connected ) then
online = online + 1
end
end
pfUI.panel:OutputPanel("friends", FRIENDS .. ": " .. online, widget.Tooltip, widget.Click)
end)
end
+1 -3
View File
@@ -4,9 +4,7 @@ pfUI:RegisterModule("raid", function ()
-- tell RaidFrame.lua pfUI replaces party frames
HookAddonOrVariable("Blizzard_RaidUI", function()
-- must reach _G: RaidFrame.lua reads this global, and pfUI.env has
-- __index but no __newindex, so a bare assignment stays in the sandbox
_G.GROUP_REPLACE_PARTY = "1"
GROUP_REPLACE_PARTY = "1"
end)
pfUI.uf.raid = CreateFrame("Frame", "pfRaidUpdater", UIParent)
+2 -7
View File
@@ -7,12 +7,8 @@ pfUI:RegisterModule("roll", function ()
local LOOT_ROLL_NEED = string.gsub(LOOT_ROLL_NEED, "%%s|Hitem:%%d:%%d:%%d:%%d|h%[%%s%]|h%%s", "%%s")
local LOOT_ROLL_PASSED = string.gsub(LOOT_ROLL_PASSED, "%%s|Hitem:%%d:%%d:%%d:%%d|h%[%%s%]|h%%s", "%%s")
-- detect the "everyone passed" subject exactly as the loot scanner will capture
-- it: feed a dummy item into LOOT_ROLL_ALL_PASSED and run the same LOOT_ROLL_PASSED
-- match. (The old strfind had no captures, so `everyone` was always nil and
-- "Everyone has passed on: X" got counted as a fake roller.)
local everyoneSample = string.gsub(LOOT_ROLL_ALL_PASSED, "%%s", "x")
local everyone = cmatch(everyoneSample, LOOT_ROLL_PASSED)
-- try to detect the everyone string
local _, _, everyone, _ = strfind(LOOT_ROLL_ALL_PASSED, LOOT_ROLL_PASSED)
pfUI.roll.blacklist = { YOU, everyone }
pfUI.roll.cache = {}
@@ -47,7 +43,6 @@ pfUI:RegisterModule("roll", function ()
local _, _, itemLink = string.find(hyperlink, "(item:%d+:%d+:%d+:%d+)")
local itemName = C_Item.GetItemInfo(itemLink)
if not itemName then return end -- uncached item: avoid cache[nil] "table index is nil"
-- delete obsolete tables
if pfUI.roll.cache[itemName] and pfUI.roll.cache[itemName]["TIMESTAMP"] < GetTime() - 60 then
+70 -68
View File
@@ -4,7 +4,7 @@ pfUI:RegisterModule("socialmod", function ()
pfUI.socialmod:RegisterEvent("CHAT_MSG_SYSTEM")
pfUI.socialmod:SetScript("OnEvent", function()
local name = cmatch(arg1, _G.ERR_FRIEND_ONLINE_SS)
name = name or cmatch(arg1, _G.ERR_FRIEND_OFFLINE_S)
name = cmatch(arg1, _G.ERR_FRIEND_OFFLINE_S)
if name and playerdb[name] and playerdb[name].cname then
playerdb[name].lastseen = date("%a %d-%b-%Y")
end
@@ -77,19 +77,20 @@ pfUI:RegisterModule("socialmod", function ()
local off = FauxScrollFrame_GetOffset(FriendsFrameFriendsScrollFrame)
for i=1, FRIENDS_TO_DISPLAY do
local info = C_FriendList.GetFriendInfoByIndex(off + i)
if not info or not info.name or info.name == _G.UNKNOWN then break end
local name = info.name
local name, level, class, zone, connected, status = GetFriendInfo(off + i)
if not name or name == _G.UNKNOWN then break end
local friendName = _G["FriendsFrameFriendButton"..i.."ButtonTextName"]
local friendLoc = _G["FriendsFrameFriendButton"..i..FRIENDS_NAME_LOCATION]
local friendInfo = _G["FriendsFrameFriendButton"..i.."ButtonTextInfo"]
local caption = friendName or friendLoc
if info.connected then
local ccolor = PFUI_CLASS_COLORS[info.classFilename]
local status = info.afk and CHAT_FLAG_AFK or info.dnd and CHAT_FLAG_DND or ""
local zone = ( info.area == playerzone and "|cffffffff" or "|cffcccccc" ) .. info.area .. "|r"
local cname = ccolor:WrapTextInColorCode(name)
if connected then
if not class or class == _G.UNKNOWN then break end
local ccolor = PFUI_CLASS_COLORS[L["class"][class]] or { 1, 1, 1 }
local lcolor = GetDifficultyColor(tonumber(level)) or { 1, 1, 1 }
zone = ( zone == playerzone and "|cffffffff" or "|cffcccccc" ) .. zone .. "|r"
local cname = rgbhex(ccolor) .. name .. "|r"
if playerdb[name] then
playerdb[name].lastseen = date("%a %d-%b-%Y")
playerdb[name].cname = cname
@@ -102,7 +103,7 @@ pfUI:RegisterModule("socialmod", function ()
friendLoc:SetText(format(TEXT(FRIENDS_LIST_TEMPLATE), cname, zone, status))
end
friendInfo:SetText(format(TEXT(FRIENDS_LEVEL_TEMPLATE), info.level, info.className))
friendInfo:SetText(format(TEXT(FRIENDS_LEVEL_TEMPLATE), level, class))
caption:SetVertexColor(1,1,1,.9)
friendInfo:SetVertexColor(1,1,1,.9)
else
@@ -123,73 +124,74 @@ pfUI:RegisterModule("socialmod", function ()
do -- add colors to who list
hooksecurefunc("WhoList_Update", function()
local num, max = C_FriendList.GetNumWhoResults()
local num, max = GetNumWhoResults()
local off = FauxScrollFrame_GetOffset(WhoListScrollFrame)
local playerzone = GetRealZoneText()
local playerrace = UnitRace("player")
local playerguild = GetGuildInfo("player")
if num + 1 >= MAX_WHOS_FROM_SERVER then
WhoFrameTotals:SetText("|cffffffff" .. format(GetText("WHO_FRAME_TOTAL_TEMPLATE", nil, num), max).." |cffaaaaaa"..format(WHO_FRAME_SHOWN_TEMPLATE, MAX_WHOS_FROM_SERVER))
else
WhoFrameTotals:SetText("|cffffffff" .. format(GetText("WHO_FRAME_TOTAL_TEMPLATE", nil, num), num).." |cffaaaaaa"..format(WHO_FRAME_SHOWN_TEMPLATE, num))
end
for i=1, WHOS_TO_DISPLAY do
local info = C_FriendList.GetWhoInfo(off + i)
if info then
-- filename is the class token, so no L["class"] reversal is needed
local class = info.filename
local name, guild, level, race, class, zone = GetWhoInfo(off + i)
local displayedText = ""
_G["WhoFrameButton"..i.."Name"]:SetTextColor(NORMAL_FONT_COLOR:GetRGB())
if (UIDropDownMenu_GetSelectedID(WhoFrameDropDown) == 1) then
if (info.area == playerzone) then
_G["WhoFrameButton"..i.."Variable"]:SetTextColor(.5, 1, 1)
else
_G["WhoFrameButton"..i.."Variable"]:SetTextColor(1, 1, 1)
end
elseif (UIDropDownMenu_GetSelectedID(WhoFrameDropDown) == 2) then
if (info.fullGuildName == playerguild) then
_G["WhoFrameButton"..i.."Variable"]:SetTextColor(.5, 1, 1)
else
_G["WhoFrameButton"..i.."Variable"]:SetTextColor(1, 1, 1)
end
elseif (UIDropDownMenu_GetSelectedID(WhoFrameDropDown) == 3) then
if (info.raceStr == playerrace) then
_G["WhoFrameButton"..i.."Variable"]:SetTextColor(.5, 1, 1)
else
_G["WhoFrameButton"..i.."Variable"]:SetTextColor(1, 1, 1)
end
end
if class then
local classicon = _G["WhoFrameButton"..i].classicon
local coords = CLASS_ICON_TCOORDS[class]
local color = PFUI_CLASS_COLORS[class]
-- do we have classicons? (skin enabled?)
if classicon then
_G["WhoFrameButton"..i.."Class"]:SetTextColor(0,0,0,0)
_G["WhoFrameButton"..i.."Name"]:SetTextColor(color.r,color.g,color.b,1)
if coords then
classicon:Show()
classicon:SetTexCoord(unpack(coords))
else
classicon:Hide()
end
else
_G["WhoFrameButton"..i.."Class"]:SetTextColor(color.r,color.g,color.b,1)
end
end
local color = GetDifficultyColor(info.level)
_G["WhoFrameButton"..i.."Level"]:SetTextColor(color.r, color.g, color.b)
if num + 1 >= MAX_WHOS_FROM_SERVER then
displayedText = format(WHO_FRAME_SHOWN_TEMPLATE, MAX_WHOS_FROM_SERVER)
WhoFrameTotals:SetText("|cffffffff" .. format(GetText("WHO_FRAME_TOTAL_TEMPLATE", nil, num), max).." |cffaaaaaa"..displayedText)
else
displayedText = format(WHO_FRAME_SHOWN_TEMPLATE, num)
WhoFrameTotals:SetText("|cffffffff" .. format(GetText("WHO_FRAME_TOTAL_TEMPLATE", nil, num), num).." |cffaaaaaa"..displayedText)
end
class = L["class"][class]
_G["WhoFrameButton"..i.."Name"]:SetTextColor(NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b)
if (UIDropDownMenu_GetSelectedID(WhoFrameDropDown) == 1) then
if (zone == playerzone) then
_G["WhoFrameButton"..i.."Variable"]:SetTextColor(.5, 1, 1)
else
_G["WhoFrameButton"..i.."Variable"]:SetTextColor(1, 1, 1)
end
elseif (UIDropDownMenu_GetSelectedID(WhoFrameDropDown) == 2) then
if (guild == playerguild) then
_G["WhoFrameButton"..i.."Variable"]:SetTextColor(.5, 1, 1)
else
_G["WhoFrameButton"..i.."Variable"]:SetTextColor(1, 1, 1)
end
elseif (UIDropDownMenu_GetSelectedID(WhoFrameDropDown) == 3) then
if (race == playerrace) then
_G["WhoFrameButton"..i.."Variable"]:SetTextColor(.5, 1, 1)
else
_G["WhoFrameButton"..i.."Variable"]:SetTextColor(1, 1, 1)
end
end
if class then
local classicon = _G["WhoFrameButton"..i].classicon
local coords = CLASS_ICON_TCOORDS[class]
local color = PFUI_CLASS_COLORS[class]
-- do we have classicons? (skin enabled?)
if classicon then
_G["WhoFrameButton"..i.."Class"]:SetTextColor(0,0,0,0)
_G["WhoFrameButton"..i.."Name"]:SetTextColor(color.r,color.g,color.b,1)
if coords then
classicon:Show()
classicon:SetTexCoord(unpack(coords))
else
classicon:Hide()
end
else
_G["WhoFrameButton"..i.."Class"]:SetTextColor(color.r,color.g,color.b,1)
end
end
local color = GetDifficultyColor(level)
_G["WhoFrameButton"..i.."Level"]:SetTextColor(color.r, color.g, color.b)
end
end)
end
+1 -4
View File
@@ -75,10 +75,7 @@ pfUI:RegisterModule("superwow", function ()
DEFAULT_CHAT_FRAME:AddMessage("-> https://github.com/balakethelock/SuperWoW/releases/")
end
-- compare numerically: an exact string match silently drops this on any
-- SuperWoW release past 1.5
local swVersion = tonumber(SUPERWOW_VERSION)
if swVersion and swVersion >= 1.5 then
if SUPERWOW_VERSION == "1.5" then
QueueFunction(function()
local pfCombatText_AddMessage = _G.CombatText_AddMessage
_G.CombatText_AddMessage = function(message, a, b, c, d, e, f)
+26 -17
View File
@@ -9,6 +9,11 @@ pfUI:RegisterModule("swingtimer", function ()
local ON_SWING_QUEUED = 0
local ON_SWING_QUEUE_POPPED = 1
-- Spell.dbc bits used to mirror server-side swing-reset rules.
local FLAG_AUTOATTACK = tonumber("0x08", 16) -- SPELL_INTERRUPT_FLAG_AUTOATTACK
local ATTR_KEEP_SWINGS = tonumber("0x20000", 16) -- SPELL_ATTR_EX2_NOT_RESET_AUTO_ACTIONS
local ATTR_ON_NEXT_SWING = tonumber("0x04", 16) -- SPELL_ATTR_ON_NEXT_SWING
-- Consolidate state into a table to avoid Lua 5.0 upvalue limit (32 max)
local S = {
mhTimer = 0, mhTimerMax = 1,
@@ -38,11 +43,12 @@ pfUI:RegisterModule("swingtimer", function ()
local WAND_SHOOT_SPELLID = 5019
local THROW_SPELLID = 2764 -- one-shot ranged, not auto-repeat
-- On-next-swing spells replace the next auto-attack swing.
-- ATTR_ON_NEXT_SWING: spell replaces next auto-attack swing.
-- Covers Raptor Strike, Maul, Mongoose Bite, Holy Strike, etc. automatically.
local function IsOnSwingSpell(spellId)
if S.onSwingCache[spellId] ~= nil then return S.onSwingCache[spellId] end
local result = C_Spell.IsNextMeleeSpell(spellId)
local attr = GetSpellRecField(spellId, "attributes") or 0
local result = bit.band(attr, ATTR_ON_NEXT_SWING) ~= 0
S.onSwingCache[spellId] = result
return result
end
@@ -284,9 +290,7 @@ pfUI:RegisterModule("swingtimer", function ()
UpdateMovable(pfUI.swingtimer.ranged)
-- OH weapon detection
-- OH weapon detection: slot 17 (off hand) accepts one-hand (13) and off-hand
-- weapons (22); main-hand-only (21) can never sit there.
local OH_WEAPON_TYPES = { [13]=true, [22]=true }
local OH_WEAPON_TYPES = { [13]=true, [21]=true }
local function HasOffhandWeapon()
local l = GetInventoryItemLink("player", 17)
if not l then return false end
@@ -730,12 +734,13 @@ pfUI:RegisterModule("swingtimer", function ()
S.pendingCastSpellId = arg1
-- Freeze the swing timer for cast-time spells that DON'T reset auto-
-- attack on completion (Slam, Hammer of Wrath on Turtle, etc.) — those
-- let the swing resume from where it paused. C_Spell.ResetsMeleeSwing
-- mirrors the server rule; spells that reset don't need freezing (they
-- reset on SPELL_GO_SELF). Subsumes the old hardcoded swingDelaySpells
-- list (no list maintenance for new Slam-style spells).
-- let the swing resume from where it paused. Detect dynamically via the
-- absent AUTOATTACK interrupt flag (8); spells with that bit reset on
-- SPELL_GO_SELF so freezing isn't necessary. Subsumes the old hardcoded
-- swingDelaySpells list (no list maintenance for new Slam-style spells).
if S.mhActive then
if not C_Spell.ResetsMeleeSwing(arg1) then
local iflags = GetSpellRecField(arg1, "interruptFlags") or 0
if bit.band(iflags, FLAG_AUTOATTACK) == 0 then
S.mhFrozenAt = GetTime()
end
end
@@ -772,13 +777,17 @@ pfUI:RegisterModule("swingtimer", function ()
S.hsQueued = false; S.cleaveQueued = false; S.maulQueued = false
ResetMH()
else
-- C_Spell.ResetsMeleeSwing mirrors the server rule (Turtle's
-- Spell::IsMeleeAttackResetSpell): InterruptFlags has AUTOATTACK and
-- AttributesEx2 lacks NOT_RESET_AUTO_ACTIONS. When the spell resets the
-- swing, snap the timers to full. Otherwise (elseif) a frozen-swing-
-- during-cast is a Slam-style cast — push the timer forward by the cast
-- duration so the bar resumes from where it paused.
if C_Spell.ResetsMeleeSwing(spellId) then
-- Mirror the server rule for "does this spell reset the auto-attack
-- swing" (Spell::IsMeleeAttackResetSpell in Turtle's core):
-- InterruptFlags has SPELL_INTERRUPT_FLAG_AUTOATTACK (0x08)
-- AND AttributesEx2 lacks NOT_RESET_AUTO_ACTIONS (0x20000).
-- If neither path resets and we're holding a frozen-swing-during-cast
-- (mhFrozenAt set by SPELL_START_SELF for non-AUTOATTACK spells), this
-- is a Slam-style cast — push the timer forward by the cast duration
-- so the bar resumes from where it paused.
local iflags = GetSpellRecField(spellId, "interruptFlags") or 0
if bit.band(iflags, FLAG_AUTOATTACK) ~= 0
and bit.band(GetSpellRecField(spellId, "attributesEx2") or 0, ATTR_KEEP_SWINGS) == 0 then
if S.mhActive and S.mhSpeed > 0 then
UpdateWeaponSpeeds()
S.mhTimerMax = S.mhSpeed
-97
View File
@@ -277,103 +277,6 @@ pfUI:RegisterModule("thirdparty", function()
end
end)
-- GreedMeter Damage Meter
-- Vanilla: https://github.com/iGreed/GreedMeter
-- GreedMeter builds its windows lazily and supports several of them, so the
-- frames are resolved at call time. The primary window takes the "damage"
-- dock slot; a second window (GreedMeter.UI.frames[2]) takes the "threat"
-- slot so the two tile the panel side-by-side. Docking triggers a
-- LayoutBars/RefreshFrame so the bars refit the new width.
HookAddonOrVariable("GreedMeter", function()
local UI = GreedMeter.UI
local function frameAt(i)
return UI and UI.frames and UI.frames[i]
end
local function primary()
return (UI and UI.mainFrame) or getglobal("GreedMeterFrame1")
end
local function relayout(frame)
if not UI then return end
if UI.LayoutBars then UI:LayoutBars(frame) end
if UI.RefreshFrame then UI:RefreshFrame(frame) end
end
-- Builds a docktable for a window resolved through getframe(). left picks
-- the left half in split mode; the primary window uses the right half.
local function BuildDock(name, getframe, left)
return { "greedmeter", "GreedMeter", name,
function() -- single: fill the panel
local frame = getframe()
if not frame then return end
frame:ClearAllPoints()
frame:SetAllPoints(pfUI.chat.right)
frame:SetWidth(pfUI.chat.right:GetWidth())
relayout(frame)
end,
function() -- dual: take one half
local frame = getframe()
if not frame then return end
frame:ClearAllPoints()
if left then
frame:SetPoint("TOPLEFT", pfUI.chat.right, "TOPLEFT", 0, 0)
frame:SetPoint("BOTTOMRIGHT", pfUI.chat.right, "BOTTOM", 0, 0)
else
frame:SetPoint("TOPLEFT", pfUI.chat.right, "TOP", 0, 0)
frame:SetPoint("BOTTOMRIGHT", pfUI.chat.right, "BOTTOMRIGHT", 0, 0)
end
frame:SetWidth(pfUI.chat.right:GetWidth() / 2)
relayout(frame)
end,
function() -- show
local frame = getframe()
if frame then frame:Show() relayout(frame) end
end,
function() -- hide
local frame = getframe()
if frame then frame:Hide() end
end
}
end
local threatdock = BuildDock("GreedMeterFrame2", function() return frameAt(2) end, true)
pfUI.thirdparty.meters:RegisterMeter("damage", BuildDock("GreedMeterFrame1", primary, false))
-- pfUI's dock decides fill-vs-split by whether the threat slot is filled,
-- not by counting windows, so reconcile that slot whenever GreedMeter adds
-- or removes one. Only the first two windows fit; any beyond that float.
local function SyncSecond()
if not pfUI.thirdparty.meters.damage then return end -- dock disabled
local has2 = frameAt(2) ~= nil
if has2 and not pfUI.thirdparty.meters.threat then
pfUI.thirdparty.meters.threat = threatdock
elseif not has2 and pfUI.thirdparty.meters.threat == threatdock then
pfUI.thirdparty.meters.threat = nil
end
pfUI.thirdparty.meters:Resize()
-- match the second window's visibility to the panel (the primary tracks it)
local f1, f2 = primary(), frameAt(2)
if f2 then
if f1 and f1:IsShown() then f2:Show() relayout(f2) else f2:Hide() end
end
end
-- AddFrame and the login-time layout restore both go through
-- CreateMeterFrame; RemoveFrame handles closing a window.
local origCreate = UI.CreateMeterFrame
UI.CreateMeterFrame = function(self, isPrimary, copyFrom)
local frame = origCreate(self, isPrimary, copyFrom)
SyncSecond()
return frame
end
local origRemove = UI.RemoveFrame
UI.RemoveFrame = function(self, frame)
origRemove(self, frame)
SyncSecond()
end
end)
-- DPSMate Damage Meter
-- Vanilla: https://github.com/Geigerkind/DPSMate
-- TBC: https://github.com/Geigerkind/DPSMateTBC
+1 -116
View File
@@ -185,102 +185,14 @@ pfUI:RegisterModule("tooltip", function ()
GameTooltipStatusBar.SetStatusBarColor_orig = GameTooltipStatusBar.SetStatusBarColor
GameTooltipStatusBar.SetStatusBarColor = function() return end
-- A row of buff icons anchored above the tooltip for mouseover units.
-- Icons come from a ClassicAPI object pool: ReleaseAll hides the whole
-- set each refresh, then we Acquire and re-anchor left-to-right.
local BUFF_SIZE, BUFF_SPACING, BUFF_MAX = 20, 2, 32
pfUI.tooltip.buffs = CreateFrame("Frame", "pfTooltipBuffs", GameTooltip)
pfUI.tooltip.buffs:SetPoint("BOTTOMLEFT", GameTooltipStatusBar, "TOPLEFT", 0, default_border + 2)
pfUI.tooltip.buffs:SetHeight(BUFF_SIZE)
local function CreateBuffIcon()
local icon = CreateFrame("Frame", nil, pfUI.tooltip.buffs)
icon:SetSize(BUFF_SIZE, BUFF_SIZE)
icon.texture = icon:CreateTexture(nil, "BACKGROUND")
icon.texture:SetTexCoord(.07, .93, .07, .93)
icon.texture:SetAllPoints(icon)
CreateBackdrop(icon)
icon.stacks = icon:CreateFontString(nil, "OVERLAY", "GameFontNormal")
icon.stacks:SetPoint("BOTTOMRIGHT", icon, "BOTTOMRIGHT", 1, 0)
icon.stacks:SetFont(C.tooltip.font_tooltip, C.tooltip.font_tooltip_size, "OUTLINE")
icon.stacks:SetTextColor(1, 1, 1, 1)
icon.timer = icon:CreateFontString(nil, "OVERLAY", "GameFontNormal")
icon.timer:SetPoint("CENTER", icon, "CENTER", 0, 0)
icon.timer:SetFont(C.tooltip.font_tooltip, C.tooltip.font_tooltip_size, "OUTLINE")
icon.timer:SetTextColor(1, 1, 1, 1)
return icon
end
pfUI.tooltip.buffpool = CreateObjectPool(CreateBuffIcon, function(_, icon)
icon:Hide()
icon:ClearAllPoints()
end)
-- Tick the remaining-time text on the visible icons. OnUpdate only fires
-- while the row is shown, so it stops as soon as the tooltip hides.
pfUI.tooltip.buffs:SetScript("OnUpdate", function()
local now = GetTime()
if (this.tick or 0) > now then return end
this.tick = now + 0.1
for icon in pfUI.tooltip.buffpool:EnumerateActive() do
local timeleft = icon.expirationTime and icon.expirationTime > 0 and (icon.expirationTime - now) or 0
icon.timer:SetText(timeleft > 0 and GetColoredTimeString(timeleft) or "")
end
end)
function pfUI.tooltip:UpdateBuffs(unit)
pfUI.tooltip.buffpool:ReleaseAll()
if C.tooltip.showbuffs ~= "1" or not unit or unit == "none" then
pfUI.tooltip.buffs:Hide()
return
end
local prev, count = nil, 0
for i = 1, BUFF_MAX do
local aura = C_UnitAuras.GetBuffDataByIndex(unit, i)
if not aura then break end
count = count + 1
local icon = pfUI.tooltip.buffpool:Acquire()
icon.texture:SetTexture(aura.icon)
icon.stacks:SetText(aura.applications and aura.applications > 1 and aura.applications or "")
icon.expirationTime = aura.expirationTime
local timeleft = aura.expirationTime and aura.expirationTime > 0 and (aura.expirationTime - GetTime()) or 0
icon.timer:SetText(timeleft > 0 and GetColoredTimeString(timeleft) or "")
if prev then
icon:SetPoint("LEFT", prev, "RIGHT", BUFF_SPACING, 0)
else
icon:SetPoint("BOTTOMLEFT", pfUI.tooltip.buffs, "BOTTOMLEFT", 0, 0)
end
icon:Show()
prev = icon
end
if count > 0 then
pfUI.tooltip.buffs:SetWidth(count * BUFF_SIZE + (count - 1) * BUFF_SPACING)
pfUI.tooltip.buffs:Show()
else
pfUI.tooltip.buffs:Hide()
end
end
function pfUI.tooltip:Update()
local unit = pfUI.tooltip:GetUnit()
pfUI.tooltip:UpdateBuffs(unit)
if unit == "none" then
-- process item tooltips
if C.tooltip.itemid == "1" and GameTooltip:HasItem() then
local _, _, itemID = GameTooltip:GetItem()
GameTooltip:AddLine(T["ItemID"] .. ": " .. itemID, .25,.5,1)
GameTooltip:Show()
elseif C.tooltip.spellid == "1" and GameTooltip:HasSpell() then
local _, _, spellID = GameTooltip:GetSpell()
GameTooltip:AddLine(T["SpellID"] .. ": " .. spellID, .25,.5,1)
GameTooltip:Show()
end
return
@@ -347,13 +259,6 @@ pfUI:RegisterModule("tooltip", function ()
end
end
if C.tooltip.unitid == "1" and not UnitIsPlayer(unit) then
local npcID = C_CreatureInfo.GetCreatureID(UnitGUID(unit))
if npcID then
GameTooltip:AddLine(T["UnitID"] .. ": " .. npcID, .25,.5,1)
end
end
if hp and hpm then
if hp >= 1000 then hp = round(hp / 1000, 1) .. "k" end
if hpm >= 1000 then hpm = round(hpm / 1000, 1) .. "k" end
@@ -361,24 +266,4 @@ pfUI:RegisterModule("tooltip", function ()
end
GameTooltip:Show()
end
if C.tooltip.aurasource == "1" or C.tooltip.spellid == "1" then
hooksecurefunc(GameTooltip, "SetUnitAura", function(self, ...)
local aura = C_UnitAuras.GetAuraDataByIndex(unpack(arg))
if not aura then return end
if C.tooltip.aurasource == "1" then
local caster = aura.sourceUnit and UnitName(aura.sourceUnit)
if not caster and aura.sourceGUID then
caster = UnitNameFromGUID(aura.sourceGUID)
end
if caster and caster ~= "" then
self:AddLine(T["Cast by"] .. ": " .. caster, .25,.5,1)
end
end
if C.tooltip.spellid == "1" then
self:AddLine(T["SpellID"] .. ": " .. aura.spellId, .25,.5,1)
end
self:Show()
end)
end
end)
+3 -12
View File
@@ -28,7 +28,7 @@ pfUI:RegisterModule("unitxp", function ()
behindFrame.text:SetFont(pfUI.font_default, fontSize, "OUTLINE")
behindFrame.text:SetPoint("RIGHT", behindFrame, "RIGHT", -1, 0)
behindFrame.text:SetTextColor(0.3, 1, 0.3, 1)
behindFrame.text:SetText(T["BEHIND"])
behindFrame.text:SetText("BEHIND")
behindFrame.text:Hide()
local lastCheck = 0
@@ -62,7 +62,7 @@ pfUI:RegisterModule("unitxp", function ()
losFrame.text:SetFont(pfUI.font_default, fontSize, "OUTLINE")
losFrame.text:SetPoint("RIGHT", losFrame, "RIGHT", -1, -slot)
losFrame.text:SetTextColor(1, 0.3, 0.3, 1)
losFrame.text:SetText(T["NO LOS"])
losFrame.text:SetText("NO LOS")
losFrame.text:Hide()
local lastCheck = 0
@@ -169,9 +169,6 @@ pfUI:RegisterModule("unitxp", function ()
local throttle = 0
local scanner = CreateFrame("Frame")
-- expose the poller frame so PLAYER_LOGOUT can stop its UnitXP OnUpdate
-- (it lives on this separate frame, not on distanceIndicator) -> crash 132
pfUI.uf.target.distanceScanner = scanner
scanner:SetScript("OnUpdate", function()
throttle = throttle + arg1
if throttle < 0.05 then return end
@@ -239,10 +236,6 @@ pfUI:RegisterModule("unitxp", function ()
if pfUI.uf.target.distanceIndicator then
pfUI.uf.target.distanceIndicator:SetScript("OnUpdate", nil)
end
-- free-frame mode polls from its own scanner frame, not the indicator
if pfUI.uf.target.distanceScanner then
pfUI.uf.target.distanceScanner:SetScript("OnUpdate", nil)
end
end
return
end
@@ -275,9 +268,7 @@ pfUI:RegisterModule("unitxp", function ()
-- Also notify on BG queue pop
local origBattlefieldPortShow = BattlefieldFrame_Show
if origBattlefieldPortShow then
-- pfUI.env has __index but no __newindex, so a bare global assignment
-- inside a RegisterModule closure never leaves the sandbox
_G.BattlefieldFrame_Show = function()
BattlefieldFrame_Show = function()
pcall(UnitXP, "notify", "taskbarIcon")
pcall(UnitXP, "notify", "systemSound")
return origBattlefieldPortShow()
+2 -3
View File
@@ -23,7 +23,7 @@ do
-- ClassicAPI dependency check.
-- pfUI relies pervasively on the modern C_* / SuperWoW / nameplate / focus
-- API surface that ClassicAPI polyfills, so presence is required.
local PFUI_CLASSIC_API_MIN = 10906 -- (X*10000 + Y*100 + Z)
local PFUI_CLASSIC_API_MIN = 10900 -- (X*10000 + Y*100 + Z)
local PFUI_CLASSIC_API_LATEST = PFUI_CLASSIC_API_MIN
local PFUI_CLASSIC_API_WEBSITE = "https://github.com/brues-code/ClassicAPI"
local PFUI_CLASSIC_API_LATEST_URL = PFUI_CLASSIC_API_WEBSITE .. "/releases/latest"
@@ -292,7 +292,6 @@ function pfUI:GetEnvironment()
pfUI.env.C = pfUI_config
pfUI.env.pfUI_throttle = _G.pfUI_throttle
pfUI.env.L = (pfUI_locale[GetLocale()] or pfUI_locale["enUS"])
pfUI.env.L["class"] = pfUI.env.L["class"] or tInvert(LOCALIZED_CLASS_NAMES_MALE)
return pfUI.env
end
@@ -426,7 +425,7 @@ pfUI:SetScript("OnEvent", function()
pfUI.version.major, pfUI.version.minor, pfUI.version.fix = 0, 0, 0
pfUI.version.string = "dev"
else
local major, minor, fix = pfUI.api.strsplit(".", string.gsub(raw, "^[vV]", ""))
local major, minor, fix = pfUI.api.strsplit(".", raw)
pfUI.version.major = tonumber(major) or 0
pfUI.version.minor = tonumber(minor) or 0
pfUI.version.fix = tonumber(fix) or 0