Files
pfUI/modules/panel.lua
T
brues-code 75c4657ab9 ClassicAPI flavor TOCs, and drop the multi-client scaffolding (#53)
* nameplates: source totem icons from UnitCreatedBySpell

Read the totem's icon from the totem-drop spell (UnitCreatedBySpell +
GetSpellTexture) instead of the self-aura at index 1 plus a
UNIT_SPELLCAST_SUCCEEDED capture for active totems. The drop spell is a
broadcast descriptor field present for every summoned unit in range, so
it resolves immediately for passive and active totems alike and gives the
totem's own icon rather than the attack-spell proxy. Drops the
UNIT_SPELLCAST_SUCCEEDED registration and handler.

Re-read the spell each render and key the cached texture on the spell id
so an in-place totem swap (same unit, new drop spell -- no plate re-add)
refreshes the icon without needing the plate to leave and re-enter view.

* CAPI min bumped to 1.13.1

* Gracefully disable pfUI when ClassicAPI is missing

Add API_Check.lua as the first TOC entry. When the ClassicAPI DLL is
absent or below the minimum version it sets pfUI_disabled and stands up
an inert stub so the rest of the addon no-ops instead of flooding load
errors: modules and skins register their bodies into no-ops, and the
setfenv'd api/lib files run inside an environment where CreateFrame and
any missing global resolve to a null object -- so no real frames or live
handlers are created and missing API calls just return null. pfUI.lua
bails early on pfUI_disabled.

* auras: scan through GetAuraSlots instead of by-index loops

Every aura scan loop (unit frame buffs/debuffs, dispel indicators, buff and
custom indicators, player buff frame, buffwatch bars, tooltip buff row) now
enumerates a unit's auras once with C_UnitAuras.GetAuraSlots and reads each
aura by slot id via the positional C_UnitAuras.UnitAuraBySlot. The by-index
getters re-walk the aura array from slot 0 on every call, so a per-button
loop over them was quadratic in the aura count; one enumeration plus a
by-slot read per aura is linear.

pfUI.api.ScanAuraSlots(unit, filter, buf, max) wraps GetAuraSlots' fill-a-
table form (table as the 5th argument) so no vararg Lua frame is involved:
Lua 5.0 allocates an `arg` table for every vararg call, which showed up as
nameplate OnUpdate/OnEvent memory growth in the first cut of this change.

Single by-index reads in tooltip and click handlers are unchanged (one call
each, and SetUnitAura takes the same index).

Requires the ClassicAPI build that adds GetAuraSlots' fill form; on an older
DLL the 5th argument is ignored and ScanAuraSlots would read the first slot
id as the count.

* Show Faction/Race icons in chat

* Load pfUI through ClassicAPI's flavor TOCs

ClassicAPI redirects the read of pfUI\pfUI.toc to a flavored file whenever
the DLL is installed, so which TOC the client opens already answers whether
ClassicAPI is there. Split the manifest three ways and let that do the work:

  pfUI.toc             fallback, reached only when ClassicAPI is missing;
                       loads API_Check.lua and nothing else
  pfUI_ClassicAPI.toc  full addon, every non-Turtle client
  pfUI_Turtle.toc      full addon plus init\turtle.xml, on Turtle

The fallback TOC declares no SavedVariables. It used to, while API_Check.lua
reset pfUI_profiles to an empty table on the disabled path -- which truncated
the player's profiles on logout.

With the missing-DLL case handled by TOC selection, API_Check.lua drops the
null object stub that kept the other ~140 files quiet, along with
pfUI_disabled and the now unreachable early return in pfUI.lua. It keeps the
version gate, which still matters: the flavor redirect landed in ClassicAPI
v1.11.0, below the v1.13.1 pfUI needs, so an old DLL still gets served a
flavor TOC. pfUI.lua also loses a verbatim duplicate of the whole check.

Turtle-only files move to init\turtle.xml: modules\turtle-wow.lua (its
TURTLE_WOW_VERSION guard is now redundant) and the lft, turtle_shop,
barbershop, transmog and ebc skins. turtle-wow registers last instead of
75th of 84; the only ordering it relies on is pfUI.chat, registered 8th.

pfSellData moves to env\selldata.lua, listed only in pfUI_ClassicAPI.toc,
since turtle-wow.lua replaces the table wholesale on Turtle. env\tables.lua
keeps an empty declaration so sellvalue.lua has something to index when the
turtle-wow module is disabled.

The release workflow pinned PFUI_CLASSIC_API_LATEST in pfUI.lua, which has
not held that constant since it moved to API_Check.lua, so the pin was
silently doing nothing. It also switches to brues-code/packager@vCAPI, which
recognizes the _ClassicAPI and _Turtle suffixes and applies the TOC build
type filters to them.

* Split the vendor price tables into their own manifests

Turtle's pfSellData moves out of modules\turtle-wow.lua into
env\selldata_turtle.lua, matching env\selldata.lua for the stock list, and
each is pulled in by the manifest for its client: init\stock.xml from
pfUI_ClassicAPI.toc, init\turtle.xml from pfUI_Turtle.toc. Either way it
loads after init\env.xml and replaces the empty pfSellData declared there.

Turtle's prices used to be assigned inside the turtle-wow module body, which
put them on pfUI.env and skipped them entirely when that module was
disabled. At file scope they land on _G and apply either way.

* Drop the vanilla compat layer

compat\vanilla.lua named the handful of things that differed between clients
back when pfUI targeted several. Only one client remains, so every constant
had exactly one value. Inline each at its use site and delete the file,
init\compat.xml, and both TOC entries.

  COOLDOWN_FRAME_TYPE                    -> "Model"
  LOOT_BUTTON_FRAME_TYPE                 -> "LootButton"
  MINIMAP_TRACKING_FRAME                 -> _G.MiniMapTrackingFrame
  FRIENDS_NAME_LOCATION                  -> "ButtonTextNameLocation"
  EVENTS_MINIMAP_ZONE_UPDATE             -> the event list, in panel.lua
  MICRO_BUTTONS                          -> a local in panel.lua
  NAMEPLATE_OBJECTORDER                  -> a local in nameplates.lua
  ACTIONBAR_SECURE_TEMPLATE_BAR/_BUTTON  -> nil, so the argument goes away

NAMEPLATE_FRAMETYPE and PLAYER_BUFF_START_ID had no readers left.

RunMacroText moves to pfUI.lua. compat\vanilla.lua was setfenv'd into the
pfUI environment, so the function only ever existed on pfUI.env; at file
scope it lands on _G as a real export instead. Nothing in pfUI calls it, and
ClassicAPI neither defines nor looks for a RunMacroText global -- it does
the same throwaway edit box natively in src/macro/Execute.cpp and only
defers to a global RunMacro.

* bump CAPI min to 11303

* auras: uncap the self-debuff tooltip lookup

With selfdebuff on, the displayed debuff list is PLAYER-filtered while
GameTooltip:SetUnitAura indexes the unfiltered HARMFUL list, so both
handlers map one to the other by matching name + sourceGUID. That mapping
scanned slots 1..16 only.

The unfiltered harmful list is not capped at 16. Once a unit's 16 debuff
slots are full the server parks further debuffs in buff slots, and
C_UnitAuras classifies by the aura's polarity flag rather than its slot
range, so it reports those as harmful too -- verified live at 18 harmful on
a 20-aura target. Past the sixteenth the lookup found nothing and fell
through to the raw filtered index, opening the wrong tooltip or none.

Both now enumerate however many harmful auras the unit actually has, via
ScanAuraSlots, which also drops the per-index rescan the by-index accessor
was doing. Each handler gets its own slot buffer: OnEnter can fire while a
refresh is showing/hiding frames under the cursor, so sharing the refresh
buffer could clobber a scan mid-walk.

The nameplate module still collects at most 16 debuffs per plate. That one
is a display cap matching its 16 configured icon frames, not an aura-count
assumption, so it is left alone.

* bump CAPI min to 11304
2026-09-04 19:57:57 -05:00

825 lines
32 KiB
Lua

pfUI:RegisterModule("panel", function()
-- initialize gold cache if not yet happened
pfUI_cache["gold"] = pfUI_cache["gold"] or {}
local font = C.panel.use_unitfonts == "1" and pfUI.font_unit or pfUI.font_default
local font_size = C.panel.use_unitfonts == "1" and C.global.font_unit_size or C.global.font_size
local rawborder, default_border = GetBorderSize("panels")
do -- Widgets
do -- Clock & Timer
local widget = CreateFrame("Frame", "pfPanelWidgetClock",UIParent)
widget.Tooltip = function()
GameTooltip:ClearLines()
GameTooltip_SetDefaultAnchor(GameTooltip, this)
local fmt = C.global.twentyfour == "0" and "%I:%M %p" or "%H:%M"
local time = date(fmt)
local servertime = date(fmt, GetServerEpoch())
GameTooltip:AddLine("|cff555555" .. T["Time"])
GameTooltip:AddDoubleLine(T["Localtime"], "|cffffffff" .. time)
GameTooltip:AddDoubleLine(T["Servertime"], "|cffffffff".. servertime)
GameTooltip:AddLine(" ")
if TimeManagerFrame then
GameTooltip:AddDoubleLine(T["Left Click"], "|cffffffff" .. T["Show/Hide TimeManager"])
else
GameTooltip:AddDoubleLine(T["Left Click"], "|cffffffff" .. T["Show/Hide Timer"])
GameTooltip:AddDoubleLine(T["Right Click"], "|cffffffff" .. T["Reset Timer"])
end
GameTooltip:Show()
end
widget.Click = function()
this:RegisterForClicks("LeftButtonUp", "RightButtonUp")
if TimeManagerFrame then
if TimeManagerClockButton.alarmFiring then
TimeManager_TurnOffAlarm()
end
ToggleTimeManager()
return
end
if arg1 == "LeftButton" then
widget.timerFrame:SetShown(not widget.timerFrame:IsShown())
elseif arg1 == "RightButton" then
widget.timerFrame.Snapshot = GetTime()
end
end
C_Timer.NewTicker(1, function()
local secondsenabled = C.panel.seconds == "1"
local fmt
if C.global.twentyfour == "0" then
fmt = secondsenabled and "%I:%M:%S %p" or "%I:%M %p"
else
fmt = secondsenabled and "%H:%M:%S" or "%H:%M"
end
local time = C.global.servertime == "1" and date(fmt, GetServerEpoch()) or date(fmt)
pfUI.panel:OutputPanel("time", time, widget.Tooltip, widget.Click)
end)
widget.timerFrame = CreateFrame("Frame", "pfUITimer", UIParent)
widget.timerFrame:Hide()
widget.timerFrame:SetSize(120, 35)
widget.timerFrame:SetPoint("TOP", 0, -100)
UpdateMovable(widget.timerFrame)
widget.timerFrame.text = widget.timerFrame:CreateFontString("Status", "LOW", "GameFontNormal")
widget.timerFrame.text:SetFontObject(GameFontWhite)
widget.timerFrame.text:SetFont(font, font_size, "OUTLINE")
widget.timerFrame.text:SetAllPoints(widget.timerFrame)
widget.timerFrame:SetScript("OnUpdate", function()
if not widget.timerFrame.Snapshot then widget.timerFrame.Snapshot = GetTime() end
widget.timerFrame.curTime = SecondsToTime(floor(GetTime() - widget.timerFrame.Snapshot))
if widget.timerFrame.curTime ~= "" then
widget.timerFrame.text:SetText("|c33cccccc" .. widget.timerFrame.curTime)
else
widget.timerFrame.text:SetText("|cffff3333 --- " .. T["NEW TIMER"] .. " ---")
end
end)
-- Combat Timer
widget.combat = CreateFrame("Frame", "pfUICombatTimer", UIParent)
widget.combat:RegisterEvent("PLAYER_ENTERING_WORLD")
widget.combat:RegisterEvent("PLAYER_REGEN_ENABLED")
widget.combat:RegisterEvent("PLAYER_REGEN_DISABLED")
widget.combat:SetScript("OnEvent", function()
if event == "PLAYER_REGEN_DISABLED" then
if UnitAffectingCombat("player") and not this.combat then
this.combat = GetTime()
end
elseif event == "PLAYER_REGEN_ENABLED" then
if this.combat then
this.lastcombat = GetTime() - this.combat
this.combat = nil
pfUI.panel:OutputPanel("combat", "|cffffffff" .. SecondsToTime(ceil(this.lastcombat)))
end
elseif event == "PLAYER_ENTERING_WORLD" then
pfUI.panel:OutputPanel("combat", T["Combat"] .. ": " .. NOT_APPLICABLE)
end
end)
C_Timer.NewTicker(1, function()
if widget.combat.combat then
pfUI.panel:OutputPanel("combat", "|cffffaaaa" .. SecondsToTime(ceil(GetTime() - widget.combat.combat)))
end
end)
end
do -- FPS & Lag
local widget = CreateFrame("Frame", "pfPanelWidgetLag", UIParent)
local lag, fps, laghex, fpshex, _
widget.Tooltip = function()
local active = 0
GameTooltip:ClearLines()
GameTooltip_SetDefaultAnchor(GameTooltip, this)
GameTooltip:AddLine("|cff555555" .. T["Systeminfo"])
for i=1, GetNumAddOns() do
if IsAddOnLoaded(i) then
active = active + 1
end
end
local memkb, gckb = gcinfo()
local memmb = memkb and memkb > 0 and round((memkb or 0)/1000, 2) .. " MB" or UNAVAILABLE
local gcmb = gckb and gckb > 0 and round((gckb or 0)/1000, 2) .. " MB" or UNAVAILABLE
local nin, nout, nping = GetNetStats()
GameTooltip:AddDoubleLine(T["Active Addons"], "|cffffffff" .. active .. "|cff555555 / |cffffffff" .. GetNumAddOns())
GameTooltip:AddLine(" ")
GameTooltip:AddDoubleLine(T["Memory Usage"], "|cffffffff" .. memmb)
GameTooltip:AddDoubleLine(T["Next Memory Cleanup"], "|cffffffff" .. gcmb)
GameTooltip:AddLine(" ")
GameTooltip:AddDoubleLine(T["Network Down"], "|cffffffff" .. round(nin,1) .. "KB/s")
GameTooltip:AddDoubleLine(T["Network Up"], "|cffffffff" .. round(nout,1) .. "KB/s")
GameTooltip:AddDoubleLine(T["Network Latency"], "|cffffffff" .. nping .. "ms")
GameTooltip:AddLine(" ")
GameTooltip:AddDoubleLine(T["Graphic Renderer"], "|cffffffff" .. GetCVar("gxApi"))
GameTooltip:AddDoubleLine(T["Screen Resolution"], "|cffffffff" .. GetCVar("gxResolution"))
GameTooltip:AddDoubleLine(T["UI-Scale"], "|cffffffff" .. round(UIParent:GetEffectiveScale(),2))
GameTooltip:Show()
end
widget.Click = function()
if pfUI.addons and pfUI.addons:IsShown() then
pfUI.addons:Hide()
elseif pfUI.addons then
pfUI.addons:Show()
end
end
C_Timer.NewTicker(1, function()
fps = floor(GetFramerate())
_, _, lag = GetNetStats()
if C.panel.fpscolors == "1" then
_, _, _, fpshex = GetColorGradient(fps > 0 and fps/60 or 0)
_, _, _, laghex = GetColorGradient(lag > 0 and 60/lag or 0)
fps = fpshex .. fps .. "|r"
lag = laghex .. lag .. "|r"
end
pfUI.panel:OutputPanel("fps", fps .. " " .. T["fps"] .. " & " .. lag .. " " .. T["ms"], widget.Tooltip, widget.Click)
end)
end
do -- XP & Kills To Level
local widget = CreateFrame("Frame", "pfPanelWidgetXP", UIParent)
local curexp, difexp, maxexp, remexp, oldexp, remstring
widget:RegisterEvent("PLAYER_ENTERING_WORLD")
widget:RegisterEvent("PLAYER_XP_UPDATE")
widget:SetScript("OnEvent", function()
if UnitLevel("player") < _G.MAX_PLAYER_LEVEL then
curexp = UnitXP("player")
if oldexp ~= nil then
difexp = curexp - oldexp
maxexp = UnitXPMax("player")
if difexp > 0 then
remexp = floor((maxexp - curexp)/difexp)
remstring = "|cff555555 [" .. remexp .. "]|r"
else
remstring = nil
end
end
oldexp = curexp
local a=UnitXP("player")
local b=UnitXPMax("player")
local xprested = tonumber(GetXPExhaustion())
if remstring == nil then remstring = "" end
if xprested ~= nil then
pfUI.panel:OutputPanel("exp", T["Exp"] .. ": |cffaaaaff"..floor((a/b)*100).."%"..remstring)
else
pfUI.panel:OutputPanel("exp", T["Exp"] .. ": " .. floor((a/b)*100) .. "%" .. remstring)
end
else
pfUI.panel:OutputPanel("exp", T["Exp"] .. ": " .. NOT_APPLICABLE)
end
end)
end
do -- Bagspace
local widget = CreateFrame("Frame", "pfPanelWidgetBag", UIParent)
widget:RegisterEvent("PLAYER_ENTERING_WORLD")
widget:RegisterEvent("BAG_UPDATE_DELAYED")
widget:SetScript("OnEvent", function()
local maxslots = 0
local usedslots = 0
for bag = 0,4 do
if C.panel.bag.ignorespecial ~= "1" or GetBagFamily(bag) == "BAG" then
local bagsize = GetContainerNumSlots(bag)
maxslots = maxslots + bagsize
for j = 1,bagsize do
local itemID = C_Container.GetContainerItemID(bag,j)
if itemID then
usedslots = usedslots + 1
end
end
end
end
local freeslots = maxslots - usedslots
pfUI.panel:OutputPanel("bagspace", freeslots .. " (" .. usedslots .. "/" .. maxslots .. ")", nil, OpenAllBags)
end)
end
do -- Gold
local widget = CreateFrame("Frame", "pfPanelWidgetGold", UIParent)
widget:RegisterEvent("PLAYER_ENTERING_WORLD")
widget:RegisterEvent("PLAYER_MONEY")
widget.Click = function()
if IsShiftKeyDown() then
-- read current data
local realm = GetRealmName()
local unit = UnitName("player")
local money = GetMoney()
-- reset gold value and hide tooltip
pfUI_cache["gold"][realm] = { [unit] = money }
GameTooltip:Hide()
else
OpenAllBags()
end
end
widget.Tooltip = function()
local gold = floor(GetMoney()/ 10000)
local silver = floor(mod((GetMoney()/100),100))
local copper = floor(mod(GetMoney(),100))
local dmod = ""
if pfUI.panel.diffMoney < 0 then
dmod = "|cffff8888-"
elseif pfUI.panel.diffMoney > 0 then
dmod = "|cff88ff88+"
end
GameTooltip_SetDefaultAnchor(GameTooltip, this)
GameTooltip:ClearLines()
GameTooltip:AddLine("|cff555555" .. T["Money"])
GameTooltip:AddDoubleLine(T["Login"] .. ":", CreateGoldString(pfUI.panel.initMoney))
GameTooltip:AddDoubleLine(T["Now"] .. ":", CreateGoldString(GetMoney()))
GameTooltip:AddDoubleLine("|cffffffff","")
local totalgold = 0
for name, gold in pairs(pfUI_cache["gold"][GetRealmName()]) do
totalgold = totalgold + gold
if name ~= UnitName("player") then
GameTooltip:AddDoubleLine(name .. ":", CreateGoldString(gold))
end
end
GameTooltip:AddDoubleLine("|cffffffff","")
GameTooltip:AddDoubleLine(T["This Session"] .. ":", dmod .. CreateGoldString(math.abs(pfUI.panel.diffMoney)))
GameTooltip:AddDoubleLine(T["Total Gold"] .. ":", CreateGoldString(totalgold))
GameTooltip:AddLine(" ")
GameTooltip:AddLine(T["Shift-Click to reset all money totals"], .5, .5, .5, 1)
GameTooltip:Show()
end
widget:SetScript("OnEvent", function()
local realm = GetRealmName()
local unit = UnitName("player")
local money = GetMoney()
local goldstr = CreateGoldString(GetMoney())
pfUI.panel.initMoney = pfUI.panel.initMoney or money
pfUI.panel.diffMoney = money - pfUI.panel.initMoney
pfUI_cache["gold"][realm] = pfUI_cache["gold"][realm] or {}
pfUI_cache["gold"][realm][unit] = money
pfUI.panel:OutputPanel("gold", goldstr, widget.Tooltip, widget.Click)
end)
end
do -- Friends
local widget = CreateFrame("Frame", "pfPanelWidgetFriends", UIParent)
widget:RegisterEvent("PLAYER_ENTERING_WORLD")
widget:RegisterEvent("FRIENDLIST_UPDATE")
widget.Tooltip = function()
local init = nil
local all = GetNumFriends()
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
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)
end
end
GameTooltip:Show()
end
widget.Click = function() ToggleFriendsFrame(1) end
widget:SetScript("OnEvent", function()
local online = C_FriendList.GetNumOnlineFriends()
pfUI.panel:OutputPanel("friends", FRIENDS .. ": " .. online, widget.Tooltip, widget.Click)
end)
end
do -- Guild
local widget = CreateFrame("Frame", "pfPanelWidgetGuild", UIParent)
widget:RegisterEvent("PLAYER_ENTERING_WORLD")
widget:RegisterEvent("GUILD_ROSTER_UPDATE")
widget:RegisterEvent("PLAYER_GUILD_UPDATE")
widget.Tooltip = function()
-- skip without guild
if not IsInGuild() then return end
local raidparty = {}
for i=1,4 do -- detect people in group
if UnitExists("party"..i) then
raidparty[UnitName("party"..i)] = true
end
end
for i=1,40 do -- detect people in raid
if UnitExists("raid"..i) then
raidparty[UnitName("raid"..i)] = true
end
end
local all = GetNumGuildMembers()
local playerzone = GetRealZoneText()
local off = FauxScrollFrame_GetOffset(GuildListScrollFrame)
local left, field, init
for i=1, all do
local name, _, _, level, class, zone, _, _, online = GetGuildRosterInfo(off + i)
if online then
if not init then
GameTooltip_SetDefaultAnchor(GameTooltip, this)
GameTooltip:ClearLines()
GameTooltip:AddLine("|cff555555" .. T["Guild Online"])
init = true
end
local ccolor = PFUI_CLASS_COLORS[L["class"][class]] or { 1, 1, 1 }
local lcolor = GetDifficultyColor(tonumber(level)) or { 1, 1, 1 }
local level = "|cff555555" .. "[" .. rgbhex(lcolor) .. level .. "|cff555555]"
local raid = raidparty[name] and "|cff555555[|cff33ffccG|cff555555]|r" or ""
if not left then
left = level .. raid .. " " .. rgbhex(ccolor) .. name
else
field = rgbhex(ccolor) .. name .. " " .. raid .. level
GameTooltip:AddDoubleLine(left, field)
left = nil
end
end
end
if left then
GameTooltip:AddDoubleLine(left, "")
end
GameTooltip:Show()
end
widget.Click = function() ToggleFriendsFrame(3) end
widget:SetScript("OnEvent", function()
if IsInGuild() then
local count = 0
for i = 1, GetNumGuildMembers() do
local _, _, _, _, _, _, _, _, online = GetGuildRosterInfo(i)
if online then count = count + 1 end
end
pfUI.panel:OutputPanel("guild", GUILD .. ": " .. count, widget.Tooltip, widget.Click)
else
pfUI.panel:OutputPanel("guild", GUILD .. ": " .. NOT_APPLICABLE, widget.Tooltip, widget.Click)
end
end)
C_Timer.NewTicker(60, function()
if IsInGuild() then GuildRoster() end
end)
end
do -- Durability / Repair
local widget = CreateFrame("Frame", "pfPanelWidgetRepair", UIParent)
widget:RegisterEvent("PLAYER_ENTERING_WORLD")
widget:RegisterEvent("PLAYER_MONEY")
widget:RegisterEvent("PLAYER_REGEN_ENABLED")
widget:RegisterEvent("PLAYER_DEAD")
widget:RegisterEvent("PLAYER_UNGHOST")
widget:RegisterEvent("PLAYER_EQUIPMENT_CHANGED") -- ClassicAPI: equip/unequip changes total durability
widget:RegisterEvent("UPDATE_INVENTORY_DURABILITY") -- ClassicAPI: combat wear / repair
widget.Click = function() ToggleCharacter("PaperDollFrame") end
widget.Tooltip = function()
local totalRep = 0
local itemLines = {}
for id = INVSLOT_FIRST_EQUIPPED, INVSLOT_LAST_EQUIPPED do
local cur, max = GetInventoryItemDurability(id)
if cur and max then
totalRep = totalRep + (GetInventoryItemRepairCost(id) or 0)
local repPercent = floor(cur / max * 100)
if repPercent < 100 then
local _, _, _, hex = GetColorGradient(repPercent/100)
local link = GetInventoryItemLink("player", id)
local texture = GetInventoryItemTexture("player", id)
if texture then
link = CreateSimpleTextureMarkup(texture, 24) .. " " .. link
end
itemLines[table.getn(itemLines)+1] = {
link,
string.format("%s%s%%|r", hex, repPercent)
}
end
end
end
if totalRep > 0 then
GameTooltip:ClearLines()
GameTooltip_SetDefaultAnchor(GameTooltip, this)
GameTooltip:AddLine(REPAIR_COST.." " .. CreateGoldString(totalRep), 0.3333, 0.3333, 0.3333)
for _,line in ipairs(itemLines) do
GameTooltip:AddDoubleLine(line[1],line[2])
end
GameTooltip:Show()
end
end
widget:SetScript("OnEvent", function()
local totalCurr, totalMax = 0, 0
for id = INVSLOT_FIRST_EQUIPPED, INVSLOT_LAST_EQUIPPED do
local cur, max = GetInventoryItemDurability(id)
if cur and max then
totalCurr = totalCurr + cur
totalMax = totalMax + max
end
end
local armorPercent = totalMax > 0 and floor(totalCurr / totalMax * 100) or 100
pfUI.panel:OutputPanel("durability", armorPercent .. "% " .. ARMOR, widget.Tooltip, widget.Click)
end)
end
do -- Zone
local widget = CreateFrame("Frame", "pfPanelWidgetZone", UIParent)
for _,event in pairs({"PLAYER_ENTERING_WORLD", "MINIMAP_ZONE_CHANGED"}) do
widget:RegisterEvent(event)
end
widget.Tooltip = function()
local real = GetRealZoneText()
local sub = GetSubZoneText()
GameTooltip:ClearLines()
GameTooltip_SetDefaultAnchor(GameTooltip, this)
GameTooltip:AddLine("|cffffffff" .. real)
GameTooltip:AddLine(sub)
local posX, posY = GetPlayerMapPosition("player")
if posX ~= 0 and posY ~= 0 then
GameTooltip:AddLine("|cffaaaaaa" .. round(posX * 100, 1) .. ", " .. round(posY * 100, 1))
end
GameTooltip:Show()
end
widget.Click = function()
WorldMapFrame:SetShown(not WorldMapFrame:IsShown())
end
widget:SetScript("OnEvent", function()
pfUI.panel:OutputPanel("zone", GetMinimapZoneText(), widget.Tooltip, widget.Click)
end)
end
do -- Ammo
local widget = CreateFrame("Frame", "pfPanelWidgetAmmo", UIParent)
widget:RegisterEvent("PLAYER_ENTERING_WORLD")
widget:RegisterEvent("UNIT_INVENTORY_CHANGED")
widget:RegisterEvent("BAG_UPDATE_DELAYED")
widget.Tooltip = function()
if GetInventoryItemQuality("player", 0) then
local ammo = GetInventoryItemCount("player", 0)
GameTooltip:ClearLines()
GameTooltip_SetDefaultAnchor(GameTooltip, this)
GameTooltip:SetInventoryItem("player", 0)
GameTooltip:AddLine(T["Count"] .. ": " .. ammo, .3,1,.8)
GameTooltip:Show()
end
end
widget:SetScript("OnEvent", function()
if not GetInventoryItemQuality("player", 0) then
pfUI.panel:OutputPanel("ammo", AMMOSLOT .. ": -")
else
pfUI.panel:OutputPanel("ammo", AMMOSLOT .. ": " .. GetInventoryItemCount("player", 0), tooltip)
end
end)
end
do
-- Hearthstone bind location
local hearth = CreateFrame("Frame", "pfPanelBindLocation", UIParent)
hearth:RegisterEvent("PLAYER_ENTERING_WORLD")
hearth:RegisterEvent("HEARTHSTONE_BOUND")
hearth:SetScript("OnEvent", function()
pfUI.panel:OutputPanel("bindlocation", T["Hearthstone"] .. ": " .. (GetBindLocation() or T["Not Set"]))
end)
local itemIDs = {
soulshard = 6265,
flashpowder = 5140,
thistletea = 7676,
blindpowder = 5530
}
local function AddItemToPanel(panelType)
local itemID = itemIDs[panelType]
if C_Item.IsItemDataCachedByID(itemID) then
local itemName, count = C_Item.GetItemNameByID(itemID), C_Item.GetItemCount(itemID)
pfUI.panel:OutputPanel(panelType, itemName .. ": " .. count)
else
local item = Item:CreateFromItemID(itemID)
item:ContinueOnItemLoad(function()
local itemName, count = item:GetItemName(), C_Item.GetItemCount(item:GetItemID())
pfUI.panel:OutputPanel(panelType, itemName .. ": " .. count)
end)
end
end
local class = UnitClassBase('player')
if class == "WARLOCK" then
-- Soulshards
local widget = CreateFrame("Frame", "pfPanelWidgetSoulshard", UIParent)
widget:RegisterEvent("PLAYER_ENTERING_WORLD")
widget:RegisterEvent("BAG_UPDATE_DELAYED")
widget:SetScript("OnEvent", function()
AddItemToPanel("soulshard")
end)
end
if class == "ROGUE" then
-- Flash Powder
local widget = CreateFrame("Frame", "pfPanelFlashPowder", UIParent)
widget:RegisterEvent("PLAYER_ENTERING_WORLD")
widget:RegisterEvent("BAG_UPDATE_DELAYED")
widget:SetScript("OnEvent", function()
AddItemToPanel("flashpowder")
end)
-- Thistle Tea
local widget = CreateFrame("Frame", "pfPanelThistleTea", UIParent)
widget:RegisterEvent("PLAYER_ENTERING_WORLD")
widget:RegisterEvent("BAG_UPDATE_DELAYED")
widget:SetScript("OnEvent", function()
AddItemToPanel("thistletea")
end)
-- Blinding Powder
local widget = CreateFrame("Frame", "pfPanelBlindingPowder", UIParent)
widget:RegisterEvent("PLAYER_ENTERING_WORLD")
widget:RegisterEvent("BAG_UPDATE_DELAYED")
widget:SetScript("OnEvent", function()
AddItemToPanel("blindpowder")
end)
end
end
end
pfUI.panel = {}
pfUI.panel.modules = {}
-- list of available panel fields
pfUI.panel.options = { "time", "fps", "exp", "gold", "friends",
"guild", "durability", "zone", "ammo", "bagspace" }
local panels = {}
function pfUI.panel:OutputPanel(entry, value, tooltip, func)
-- return if not yet fully initialized
if not pfUI.panel.minimap then return end
-- initialize then panels if not yet done
if not panels[1] then
panels = {
{ pfUI.panel.left.left, C.panel.left.left },
{ pfUI.panel.left.center, C.panel.left.center },
{ pfUI.panel.left.right, C.panel.left.right },
{ pfUI.panel.right.left, C.panel.right.left },
{ pfUI.panel.right.center, C.panel.right.center },
{ pfUI.panel.right.right, C.panel.right.right },
{ pfUI.panel.minimap, C.panel.other.minimap },
}
end
for i,p in pairs(panels) do
local frame, config = p[1], p[2]
if config == entry then
frame.text:SetText(value)
if not frame.initialized or frame.initialized ~= entry then
if tooltip then
frame:SetScript("OnEnter", tooltip)
frame:SetScript("OnLeave", function() GameTooltip:Hide() end)
end
if func then
frame:SetScript("OnClick", func)
end
frame.initialized = entry
end
end
end
end
local function CreatePanelButton(parent, width, location, tjustify)
local frame = CreateFrame("Button", nil, parent)
frame:SetFrameLevel(0)
frame:ClearAllPoints()
frame:SetSize(width, parent:GetHeight())
frame:SetPoint(location, 0, 0)
frame.text = frame:CreateFontString("Status", "LOW", "GameFontNormal")
frame.text:ClearAllPoints()
frame.text:SetAllPoints(frame)
frame.text:SetPoint(location, 0, 0)
frame.text:SetJustifyH(tjustify)
frame.text:SetFont(font, font_size, "OUTLINE")
frame.text:SetFontObject(GameFontWhite)
return frame
end
local function CreatePanel(panelname, default_border)
local frame = CreateFrame("Frame", panelname, UIParent)
frame:SetFrameStrata("FULLSCREEN")
frame:ClearAllPoints()
frame:SetFrameStrata("DIALOG")
frame:SetHeight(C.global.font_size*1.5)
CreateBackdrop(frame, default_border, nil)
CreateBackdropShadow(frame)
return frame
end
local function CreatePanelHide(parent, leftright)
parent.hide = CreateFrame("Button", nil, parent)
parent.hide:SetFrameLevel(4)
parent.hide:SetPoint(leftright, parent.backdrop, leftright, 0, 0)
parent.hide:SetPoint("TOP", parent.backdrop, "TOP", 0, 0)
parent.hide:SetPoint("BOTTOM", parent.backdrop, "BOTTOM", 0, 0)
parent.hide:SetWidth(12)
return parent.hide
end
local function CreatePanelHideTexture(parent, framename, leftright)
SkinButton(parent)
parent:SetBackdropColor(0,0,0,0)
parent.texture = parent:CreateTexture(framename)
local imgstring = "img:" .. leftright
parent.texture:SetTexture(pfUI.media[imgstring])
parent.texture:SetPoint("CENTER", 0, 0)
parent.texture:SetSize(8, 8)
parent.texture:SetVertexColor(.25,.25,.25,1)
return parent.texture
end
-- left panel
pfUI.panel.left = CreatePanel("pfPanelLeft", default_border)
pfUI.panel.left.hide = CreatePanelHide(pfUI.panel.left, "LEFT", -5)
pfUI.panel.left.hide.texture = CreatePanelHideTexture(pfUI.panel.left.hide, "pfPanelLeftHide", "left")
if pfUI.chat then
pfUI.panel.left:SetScale(pfUI.chat.left:GetScale())
pfUI.panel.left:SetWidth(tonumber(C.chat.left.width) - 4)
pfUI.panel.left:SetPoint("BOTTOM", pfUI.chat.left, "BOTTOM", 0, 2)
else
pfUI.panel.left:SetWidth(C.chat.left.width)
pfUI.panel.left:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", 5, 5)
end
UpdateMovable(pfUI.panel.left)
pfUI.panel.left.hide:SetScript("OnClick", function()
pfUI.chat.left:SetShown(not pfUI.chat.left:IsShown())
end)
if not pfUI.chat then pfUI.panel.left.hide:Hide() end
-- buttons for left panel
local width = pfUI.panel.left:GetWidth() / 3 - 2
width = C.panel.left.left == "none" and C.panel.left.right == "none" and pfUI.panel.left:GetWidth() - 2 or width
width = C.panel.left.center == "none" and pfUI.panel.left:GetWidth() / 2 - 2 or width
pfUI.panel.left.left = CreatePanelButton(pfUI.panel.left, width, "LEFT", "CENTER")
pfUI.panel.left.center = CreatePanelButton(pfUI.panel.left, width, "CENTER", "CENTER")
pfUI.panel.left.right = CreatePanelButton(pfUI.panel.left, width, "RIGHT", "CENTER")
if C.panel.left.left == "none"
and C.panel.left.center == "none"
and C.panel.left.right == "none" then
pfUI.panel.left:Hide()
end
-- right panel
pfUI.panel.right = CreatePanel("pfPanelRight", default_border)
pfUI.panel.right.hide = CreatePanelHide(pfUI.panel.right, "RIGHT", 5)
pfUI.panel.right.hide.texture = CreatePanelHideTexture(pfUI.panel.right.hide, "pfPanelRightHide", "right")
if pfUI.chat then
pfUI.panel.right:SetScale(pfUI.chat.right:GetScale())
pfUI.panel.right:SetWidth(tonumber(C.chat.right.width) - 4)
pfUI.panel.right:SetPoint("BOTTOM", pfUI.chat.right, "BOTTOM", 0, 2)
else
pfUI.panel.right:SetWidth(C.chat.right.width)
pfUI.panel.right:SetPoint("BOTTOMRIGHT", UIParent, "BOTTOMRIGHT", -5, 5)
end
UpdateMovable(pfUI.panel.right)
pfUI.panel.right.hide:SetScript("OnClick", function()
pfUI.chat.right:SetShown(not pfUI.chat.right:IsShown())
end)
if not pfUI.chat then pfUI.panel.right.hide:Hide() end
-- buttons for right panel
local width = pfUI.panel.right:GetWidth() / 3 - 2
width = C.panel.right.left == "none" and C.panel.right.right == "none" and pfUI.panel.right:GetWidth() - 2 or width
width = C.panel.right.center == "none" and pfUI.panel.right:GetWidth() / 2 - 2 or width
pfUI.panel.right.left = CreatePanelButton(pfUI.panel.right, width, "LEFT", "CENTER")
pfUI.panel.right.center = CreatePanelButton(pfUI.panel.right, width, "CENTER", "CENTER")
pfUI.panel.right.right = CreatePanelButton(pfUI.panel.right, width, "RIGHT", "CENTER")
if C.panel.right.left == "none"
and C.panel.right.center == "none"
and C.panel.right.right == "none" then
pfUI.panel.right:Hide()
end
pfUI.panel.minimap = CreateFrame("Button", "pfPanelMinimap", UIParent)
if pfUI.minimap then
pfUI.panel.minimap:SetWidth(pfUI.minimap:GetWidth())
pfUI.panel.minimap:SetPoint("TOPLEFT", pfUI.minimap, "BOTTOMLEFT", 0 , -default_border*3)
pfUI.panel.minimap:SetPoint("TOPRIGHT", pfUI.minimap, "BOTTOMRIGHT", 0 , default_border*3)
else
pfUI.panel.minimap:SetWidth(200)
pfUI.panel.minimap:SetPoint("TOP", UIParent, "TOP", 0, -5)
end
pfUI.panel.minimap:SetHeight(C.global.font_size*1.5)
pfUI.panel.minimap:SetFrameStrata("MEDIUM")
CreateBackdrop(pfUI.panel.minimap, default_border)
CreateBackdropShadow(pfUI.panel.minimap)
UpdateMovable(pfUI.panel.minimap)
pfUI.panel.minimap.text = pfUI.panel.minimap:CreateFontString("MinimapZoneText", "LOW", "GameFontNormal")
pfUI.panel.minimap.text:SetFont(font, font_size, "OUTLINE")
pfUI.panel.minimap.text:SetAllPoints()
pfUI.panel.minimap.text:SetFontObject(GameFontWhite)
if C.panel.other.minimap == "none" then pfUI.panel.minimap:Hide() end
-- MicroButtons
if C.panel.micro.enable == "1" then
pfUI.panel.microbutton = CreateFrame("Frame", "pfPanelMicroButton", UIParent)
pfUI.panel.microbutton:SetPoint("TOP", pfUI.panel.minimap, "BOTTOM", 0, -2*default_border)
UpdateMovable(pfUI.panel.microbutton)
pfUI.panel.microbutton:SetSize(145, 23)
pfUI.panel.microbutton:SetFrameStrata("MEDIUM")
local microButtons = {
'CharacterMicroButton', 'SpellbookMicroButton', 'TalentMicroButton',
'QuestLogMicroButton', 'SocialsMicroButton', 'WorldMapMicroButton',
'MainMenuMicroButton', 'HelpMicroButton',
}
for i=1,table.getn(microButtons) do
local anchor = _G[microButtons[i-1]] or pfUI.panel.microbutton
local button = _G[microButtons[i]]
button:ClearAllPoints()
button:SetParent(pfUI.panel.microbutton)
if i == 1 then
button:SetPoint("LEFT", pfUI.panel.microbutton, "LEFT", 1, 10)
else
button:SetPoint("TOPLEFT", anchor, "TOPRIGHT", 1, 0)
end
button:SetScale(.6)
button.frame = CreateFrame("Frame", "backdrop", button)
button.frame:SetScale(1.4)
button.frame:SetPoint("TOPLEFT", button, "TOPLEFT", 1, -16)
button.frame:SetPoint("BOTTOMRIGHT", button, "BOTTOMRIGHT", -1, 1)
CreateBackdrop(button.frame, default_border)
button:Show()
end
end
pfUI.panel.autohide = CreateFrame("Frame", nil, UIParent)
pfUI.panel.autohide:RegisterEvent("PLAYER_ENTERING_WORLD")
pfUI.panel.autohide:SetScript("OnEvent", function()
if C.panel.hide_leftchat == "1" then
EnableAutohide(pfUI.panel.left, 2)
end
if C.panel.hide_rightchat == "1" then
EnableAutohide(pfUI.panel.right, 2)
end
if C.panel.hide_minimap == "1" then
EnableAutohide(pfUI.panel.minimap, 2)
end
if C.panel.hide_microbar == "1" then
EnableAutohide(pfUI.panel.microbutton, 2)
end
end)
end)