8 Commits
Author SHA1 Message Date
Brues 0fa78d3812 bump CAPI min 2026-08-23 14:01:37 -05:00
Brues bea2cedc08 buffwatch: use official dispel-type colours for debuff bars
The debuff-bar auto colour read the vanilla DebuffTypeColor global,
diverging from unitframes/buff which use C_UnitAuras.GetAuraDispelTypeColor
(the BC GlobalColor.dbc palette ClassicAPI ships). Switch buffwatch to the
same accessor so all aura displays share one palette; nil/unknown types
fall back to DEBUFF_TYPE_NONE instead of the old ad-hoc (1, .2, .2).
2026-08-23 13:25:06 -05:00
Brues 23c783622c buff/buffwatch: positional aura reads in hot refresh loops
buff.lua RefreshBuffButton runs 50x per PLAYER_AURAS_CHANGED and
buffwatch.lua GetBuffData runs 32x per RefreshBuffBarFrame; both now use
the zero-allocation positional C_UnitAuras.UnitAura instead of the
table-allocating GetAuraDataByIndex.

buffwatch also threads the aura's dispelType out of GetBuffData onto the
frame.buffs row, so the debuff-bar colour reads it from the row instead
of re-fetching the aura with GetDebuffDataByIndex on every name change.

Cold single-lookups (hover/click tooltips) keep the readable table API.
2026-08-23 13:05:42 -05:00
Brues 3cb52618a9 unitframes: scan debuff slots once for dispel indicators
The dispellable-indicator block re-scanned all 16 harmful slots for
every dispellable type (O(types x 16) UnitDebuff calls per refresh).
Scan the 16 slots once into a reusable set, then have the per-type loop
check the set. Zero-allocation (the set is cleared and reused within the
single RefreshUnit call).
2026-08-23 13:05:29 -05:00
Brues 461ad22a57 Use positional C_UnitAuras accessors in hot aura scans
Swap the per-frame aura-refresh loops from table-allocating
GetAuraDataByIndex/GetBuffDataByIndex/GetDebuffDataByIndex/GetUnitAuras
to the zero-allocation positional C_UnitAuras.UnitAura/UnitBuff/UnitDebuff.

unitframes: buff/debuff icon loops, the dispellable-indicator 16-slot
scan, the two GetUnitAuras indicator passes, and the custom-debuff scan.
nameplates: the per-plate debuff scan now writes positional results
straight into the reusable display buffer, dropping the per-aura tables
and the result array.

Filters change from HARMFUL|PLAYER/HARMFUL to PLAYER/nil since UnitBuff/
UnitDebuff lock the range and still honor the PLAYER predicate. Cold
single-lookups (hover/click, tooltip, totem icon, libdebuff) keep the
readable table API.
2026-08-23 12:53:56 -05:00
Brues 10b028b0c2 Bump ClassicAPI minimum version 2026-08-21 15:00:25 -05:00
Brues 7ccc56c241 utlize inline icons and CreateGoldString in repair module 2026-08-21 15:00:25 -05:00
Brues c64a501e6f Utilize SetFormattedText
This change updates several text setters to use `SetFormattedText` instead of passing `string.format(...)` into `SetText`. It also switches a few regex captures from `string.find` to `string.match` for cleaner capture handling, and replaces explicit width/height setters with `SetSize` in the first-run dialog for consistency.
2026-08-21 15:00:25 -05:00
76 changed files with 9019 additions and 7590 deletions
+3 -3
View File
@@ -23,12 +23,12 @@ jobs:
IFS=. read -r MAJOR MINOR PATCH <<<"$LATEST"
PACKED=$((MAJOR * 10000 + MINOR * 100 + PATCH))
echo "Pinning PFUI_CLASSIC_API_LATEST to $LATEST ($PACKED)"
sed -i "s/^\([[:space:]]*\)local PFUI_CLASSIC_API_LATEST = .*/\1local PFUI_CLASSIC_API_LATEST = $PACKED/" API_Check.lua
grep 'local PFUI_CLASSIC_API_' API_Check.lua
sed -i "s/^\([[:space:]]*\)local PFUI_CLASSIC_API_LATEST = .*/\1local PFUI_CLASSIC_API_LATEST = $PACKED/" pfUI.lua
grep 'local PFUI_CLASSIC_API_' pfUI.lua
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Package and release to GitHub
uses: brues-code/packager@vCAPI
uses: BigWigsMods/packager@v2
env:
GITHUB_OAUTH: ${{ secrets.GITHUB_TOKEN }}
-22
View File
@@ -1,22 +0,0 @@
{
"runtime": {
"version": "Lua 5.1"
},
"diagnostics": {
"disable": ["deprecated"],
"globals": [
"this",
"event",
"arg",
"arg1",
"arg2",
"arg3",
"arg4",
"arg5",
"arg6",
"arg7",
"arg8",
"arg9"
]
}
}
-4
View File
@@ -1,4 +0,0 @@
package-as: pfUI
ignore:
- .luarc.json
-78
View File
@@ -1,78 +0,0 @@
do
-- ClassicAPI dependency gate.
--
-- Which TOC the client opened already answers "is ClassicAPI here?". Whenever
-- the DLL is loaded it redirects the read of `pfUI\pfUI.toc` to
-- `pfUI_Turtle.toc` (Turtle clients) or `pfUI_ClassicAPI.toc` (everything
-- else). Reaching this file from the plain `pfUI.toc` therefore means
-- ClassicAPI is absent -- and that TOC deliberately loads nothing but this
-- file, so there is nothing to disable, only a notice to show.
--
-- The flavor TOCs still need a version gate of their own: the redirect has
-- existed since ClassicAPI v1.11.0, well below the API surface pfUI relies
-- on. There pfUI does load, and will throw wherever it reaches for something
-- the installed DLL doesn't have yet -- the popup names the cause so those
-- errors aren't a mystery.
local PFUI_CLASSIC_API_MIN = 11703 -- (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"
local function FormatVersion(packed)
local x = math.floor(packed / 10000)
local y = math.floor(math.mod(packed, 10000) / 100)
local z = math.mod(packed, 100)
return string.format("v%d.%d.%d", x, y, z)
end
local headline, detail
if not CLASSIC_API_VERSION then
headline = "|cff33ffccpf|cffffffffUI|r has been disabled."
detail = "The ClassicAPI DLL isn't loaded. Download the latest release from:"
elseif CLASSIC_API_VERSION < PFUI_CLASSIC_API_MIN then
headline = "|cff33ffccpf|cffffffffUI|r will not work correctly on ClassicAPI " .. FormatVersion(CLASSIC_API_VERSION) .. "."
detail = FormatVersion(PFUI_CLASSIC_API_MIN) .. " or newer is required -- any errors alongside this one are the APIs it is missing. Download the latest release from:"
end
if detail then
local function ShowRequiredPopup()
StaticPopupDialogs["PFUI_CLASSICAPI_REQUIRED"] = {
text = headline .. "\n\n" .. detail,
button1 = OKAY,
hasEditBox = 1,
editBoxWidth = 280,
timeout = 0,
whileDead = 1,
hideOnEscape = 1,
preferredIndex = 3,
OnShow = function()
local editBox = getglobal(this:GetName().."EditBox")
if editBox then
editBox:SetText(PFUI_CLASSIC_API_LATEST_URL)
editBox:HighlightText()
editBox:SetFocus()
end
end,
}
StaticPopup_Show("PFUI_CLASSICAPI_REQUIRED")
DEFAULT_CHAT_FRAME:AddMessage(
headline .. " " .. detail .. " " .. PFUI_CLASSIC_API_LATEST_URL,
1, 0.3, 0.3
)
end
local loginFrame = CreateFrame("Frame")
loginFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
loginFrame:SetScript("OnEvent", function()
loginFrame:UnregisterEvent("PLAYER_ENTERING_WORLD")
ShowRequiredPopup()
end)
elseif CLASSIC_API_VERSION < PFUI_CLASSIC_API_LATEST then
EventUtil.ContinueOnPlayerLogin(function()
C_Timer.After(8, function()
DEFAULT_CHAT_FRAME:AddMessage(
"|cff33ffccpf|rUI: ClassicAPI " .. FormatVersion(PFUI_CLASSIC_API_LATEST) .. " is available — " .. PFUI_CLASSIC_API_LATEST_URL,
1, 0.85, 0.3
)
end)
end)
end
end
+4 -1
View File
@@ -1,10 +1,13 @@
# pfUI - ClassicAPI Edition
[![Octo WoW](https://img.shields.io/badge/Octo%20WoW-1.18.1-brightgreen.svg)](https://octowow.st/)
[![ClassicAPI](https://img.shields.io/badge/ClassicAPI-Required-purple.svg)](https://github.com/brues-code/ClassicAPI)
[![Nampower](https://img.shields.io/badge/Nampower-Required-purple.svg)](https://github.com/brues-code/nampower)
[![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/)**
This version includes significant performance improvements and DLL-enhanced features.
## Installation
@@ -15,7 +18,7 @@ This version includes significant performance improvements and DLL-enhanced feat
## DLL Enhancements
Since pfUI 6.0.0 includes integrations with client-side DLLs for enhanced functionality:
Since pfUI 6.0.0 includes integrations with client-side DLLs for enhanced functionality. These DLLs are permitted on Octo WoW:
### [ClassicAPI](https://github.com/brues-code/ClassicAPI)
+4 -103
View File
@@ -204,26 +204,6 @@ function pfUI.api.UnitHasBuff(unit, name)
return C_UnitAuras.GetAuraDataBySpellName(unit, name, "HELPFUL") ~= nil or nil
end
-- [ ScanAuraSlots ]
-- Fills `buf` with the slot ids of the auras on `unit` that match `filter` and
-- returns how many (buf[1..n]; entries past n are cleared), so a module can
-- keep one buffer and refill it every refresh. Read each aura with
-- C_UnitAuras.UnitAuraBySlot(unit, buf[i]) (positional, no table) or
-- GetAuraDataBySlot. One enumeration walks the aura array once, where a
-- by-index loop (UnitAura(unit, i)) re-walks it from the start for every i.
-- Uses GetAuraSlots' fill-a-table form (table as the 5th argument) instead of
-- its vararg return: Lua 5.0 builds an `arg` table for every vararg call, so
-- collecting the returns in a Lua helper would allocate once per scan.
-- unit [string] unit token
-- filter [string] aura filter ("HELPFUL", "HARMFUL|PLAYER", ...)
-- buf [table] reusable buffer, filled in place
-- max [number] optional cap on the slot count (nil = all)
-- return: [number] count of slot ids written to buf
function pfUI.api.ScanAuraSlots(unit, filter, buf, max)
local _, n = C_UnitAuras.GetAuraSlots(unit, filter, max, nil, buf)
return n
end
-- [ IsPlayerGuid ]
-- Returns whether a GUID or unit token refers to the local player.
-- guid [string] A unit GUID (or unitID) to test.
@@ -262,37 +242,6 @@ function pfUI.api.GetUnitColor(unitstr)
return classColor:GenerateHexColorMarkup(), classColor:GetRGB()
end
-- [ GetRaceIcon ]
-- Builds an inline race icon texture from the shared races atlas.
-- 'raceKey' [string] englishRace key (e.g. "NightElf")
-- 'sex' [int] unit sex (3 = female)
-- return: [string] inline texture escape, or an empty string
function pfUI.api.GetRaceIcon(raceKey, sex)
if not raceKey then return "" end
local gender = sex == 3 and "FEMALE" or "MALE"
local coords = RACE_ICON_TCOORDS[strupper(raceKey) .. "_" .. gender]
if not coords then return "" end
return string.format(
"|TInterface\\Glues\\CharacterCreate\\UI-CharacterCreate-Races:0:0:0:0:512:512:%d:%d:%d:%d|t",
coords[1] * 512, coords[2] * 512, coords[3] * 512, coords[4] * 512)
end
-- inline faction emblems (cropped from the target-frame PvP banners)
local FACTION_ICON = {
Alliance = "|TInterface\\TargetingFrame\\UI-PVP-Alliance:0:0:0:0:64:64:5:37:3:35|t",
Horde = "|TInterface\\TargetingFrame\\UI-PVP-Horde:0:0:0:0:64:64:5:37:3:35|t",
}
-- [ GetFactionIcon ]
-- Resolves an inline faction emblem from the englishRace key stored in L["race"].
-- 'raceKey' [string] englishRace key (e.g. "Orc")
-- return: [string] inline texture escape, or an empty string
function pfUI.api.GetFactionIcon(raceKey)
local info = raceKey and L["race"][raceKey]
if not info then return "" end
return FACTION_ICON[info.faction] or ""
end
-- [ strvertical ]
-- Creates vertical text using linebreaks. Multibyte char friendly.
-- 'str' [string] String to columnize.
@@ -1325,32 +1274,6 @@ function pfUI.api.CreateBackdropShadow(f)
f.backdrop_shadow:SetBackdropBorderColor(0, 0, 0, tonumber(pfUI_config.appearance.border.shadow_intensity))
end
-- [ Set Castbar Shield ]
-- Colors a castbar for a cast that none of the player's own interrupts or
-- silences can stop. The bar and the icon's border both take the shield color,
-- so the state still reads on a bar configured without an icon. Not shielded
-- restores the normal cast/channel color and the configured border.
-- 'bar' [frame] the castbar statusbar
-- 'icon' [frame] the icon frame, or nil for a bar without one
-- 'shielded' [bool] whether the cast is uninterruptible
-- 'isChannel' [bool] selects the unshielded color
function pfUI.api.SetCastbarShield(bar, icon, shielded, isChannel)
if shielded then
bar:SetStatusBarColor(pfUI.api.GetStringColor(C.appearance.castbar.shieldcolor))
else
bar:SetStatusBarColor(pfUI.api.GetStringColor(C.appearance.castbar[isChannel and "channelcolor" or "castbarcolor"]))
end
local iconborder = icon and icon.backdrop
if not iconborder then return end
if shielded then
iconborder:SetBackdropBorderColor(pfUI.api.GetStringColor(C.appearance.castbar.shieldcolor))
else
iconborder:SetBackdropBorderColor(pfUI.api.GetStringColor(C.appearance.border.color))
end
end
-- [ Bar Layout Options ] --
-- 'barsize' size of bar in number of buttons
-- returns: array of options as strings for pfUI.gui.bar
@@ -1535,18 +1458,6 @@ function pfUI.api.BarButtonAnchor(button,basename,buttonindex,barsize,formfactor
return button._anchor
end
-- [ MouseIsOverHitRect ] --
-- Like frame:IsMouseOver(), but tests the frame's hit rect, the area the
-- engine uses for mouse focus and OnEnter/OnLeave. CreateBackdrop widens
-- that rect by the border, so plain IsMouseOver misses a band around it.
-- 'frame' the frame to test
-- return true when the cursor is inside the frame's hit rect
function pfUI.api.MouseIsOverHitRect(frame)
if not frame.GetHitRectInsets then return frame:IsMouseOver() end
local left, right, top, bottom = frame:GetHitRectInsets()
return frame:IsMouseOver(-top, bottom, left, -right)
end
-- [ Enable Autohide ] --
-- 'frame' the frame that should be hidden
function pfUI.api.EnableAutohide(frame, timeout, combat)
@@ -1559,20 +1470,11 @@ function pfUI.api.EnableAutohide(frame, timeout, combat)
frame.hover.parent = frame
frame.hover:Show()
if not frame.hover.fade then
frame.hover.fade = frame:CreateAnimationGroup()
local fade = frame.hover.fade:CreateAnimation("Alpha")
fade:SetChange(-1)
fade:SetDuration(1)
frame.hover.fade:SetScript("OnFinished", function() frame:SetAlpha(0) end)
end
if combat then
frame.hover:RegisterEvent("PLAYER_REGEN_ENABLED")
frame.hover:RegisterEvent("PLAYER_REGEN_DISABLED")
frame.hover:SetScript("OnEvent", function()
if event == "PLAYER_REGEN_DISABLED" then
this.fade:Stop()
this.parent:SetAlpha(1)
this.activeTo = "keep"
elseif event == "PLAYER_REGEN_ENABLED" then
@@ -1588,13 +1490,13 @@ function pfUI.api.EnableAutohide(frame, timeout, combat)
if this.activeTo == "keep" then return end
if this:IsMouseOver(10, -10, -10, 10) then
if MouseIsOver(this, 10, -10, -10, 10) then
this.activeTo = GetTime() + timeout
this.fade:Stop()
this.parent:SetAlpha(1)
elseif this.activeTo then
if this.activeTo < GetTime() and this.parent:GetAlpha() > 0 and not this.fade:IsPlaying() then
this.fade:Play()
if this.activeTo < GetTime() and this.parent:GetAlpha() > 0 then
local fps = (60 / math.max(GetFramerate(), 1))
this.parent:SetAlpha(this.parent:GetAlpha() - 0.05*fps)
end
else
this.activeTo = GetTime() + timeout
@@ -1611,7 +1513,6 @@ function pfUI.api.DisableAutohide(frame)
frame.hover:SetScript("OnEvent", nil)
frame.hover:SetScript("OnUpdate", nil)
frame.hover:Hide()
frame.hover.fade:Stop()
frame:SetAlpha(1)
end
+3 -13
View File
@@ -156,8 +156,6 @@ function pfUI:LoadConfig()
pfUI:UpdateConfig("appearance", "castbar", "castbarcolor", ".7,.7,.9,.8")
pfUI:UpdateConfig("appearance", "castbar", "channelcolor", ".9,.9,.7,.8")
pfUI:UpdateConfig("appearance", "castbar", "failcolor", "1,.2,.2,.8")
pfUI:UpdateConfig("appearance", "castbar", "shieldcolor", ".8,.45,.45,.8")
pfUI:UpdateConfig("appearance", "castbar", "fsrcolor", ".9,.6,.2,.8")
pfUI:UpdateConfig("appearance", "castbar", "texture", "Interface\\AddOns\\pfUI\\img\\bar")
pfUI:UpdateConfig("appearance", "infight", "screen", "0")
pfUI:UpdateConfig("appearance", "infight", "aggro", "0")
@@ -228,6 +226,7 @@ function pfUI:LoadConfig()
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")
pfUI:UpdateConfig("unitframes", nil, "druidmanabar", "1")
pfUI:UpdateConfig("unitframes", nil, "druidmanaheight", "10")
pfUI:UpdateConfig("unitframes", nil, "druidmanawidth", "-1")
@@ -325,9 +324,6 @@ function pfUI:LoadConfig()
pfUI:UpdateConfig("unitframes", "player", "showRest", "0")
pfUI:UpdateConfig("unitframes", "player", "energy", "1")
pfUI:UpdateConfig("unitframes", "player", "manatick", "0")
pfUI:UpdateConfig("unitframes", "player", "showfsr", "1")
pfUI:UpdateConfig("unitframes", "player", "fsrheight", "4")
pfUI:UpdateConfig("unitframes", "player", "fsrstyle", "0")
pfUI:UpdateConfig("unitframes", "player", "display_haste", "0")
pfUI:UpdateConfig("unitframes", "player", "display_haste_color", "1,1,1,1")
pfUI:UpdateConfig("unitframes", "player", "display_spellpower", "1")
@@ -520,16 +516,10 @@ function pfUI:LoadConfig()
pfUI:UpdateConfig("unitframes", unit, "buffsize", "20")
pfUI:UpdateConfig("unitframes", unit, "bufflimit", "32")
pfUI:UpdateConfig("unitframes", unit, "buffperrow", "8")
pfUI:UpdateConfig("unitframes", unit, "ownbuffsize", "-1")
pfUI:UpdateConfig("unitframes", unit, "buffspacingx", "1")
pfUI:UpdateConfig("unitframes", unit, "buffspacingy", "1")
pfUI:UpdateConfig("unitframes", unit, "debuffs", "TOPLEFT")
pfUI:UpdateConfig("unitframes", unit, "debuffsize", "20")
pfUI:UpdateConfig("unitframes", unit, "debufflimit", "32")
pfUI:UpdateConfig("unitframes", unit, "debuffperrow", "8")
pfUI:UpdateConfig("unitframes", unit, "owndebuffsize", "-1")
pfUI:UpdateConfig("unitframes", unit, "debuffspacingx", "1")
pfUI:UpdateConfig("unitframes", unit, "debuffspacingy", "1")
pfUI:UpdateConfig("unitframes", unit, "invert_healthbar", "0")
pfUI:UpdateConfig("unitframes", unit, "verticalbar", "0")
pfUI:UpdateConfig("unitframes", unit, "buff_indicator", "0")
@@ -655,6 +645,7 @@ function pfUI:LoadConfig()
pfUI:UpdateConfig("bars", nil, "animation", "zoomfade")
pfUI:UpdateConfig("bars", nil, "animmode", "keypress")
pfUI:UpdateConfig("bars", nil, "animalways", "0")
pfUI:UpdateConfig("bars", nil, "macroscan", "1")
pfUI:UpdateConfig("bars", nil, "reagents", "1")
pfUI:UpdateConfig("bars", nil, "hunterbar", "0")
pfUI:UpdateConfig("bars", nil, "pagemasteralt", "0")
@@ -826,8 +817,7 @@ function pfUI:LoadConfig()
pfUI:UpdateConfig("chat", "text", "playerlinks", "1")
pfUI:UpdateConfig("chat", "text", "detecturl", "1")
pfUI:UpdateConfig("chat", "text", "classcolor", "1")
pfUI:UpdateConfig("chat", "text", "factionicons", "0")
pfUI:UpdateConfig("chat", "text", "raceicons", "0")
pfUI:UpdateConfig("chat", "text", "whosearchunknown", "0")
pfUI:UpdateConfig("chat", "text", "playerlevel", "0")
pfUI:UpdateConfig("chat", "left", "width", "380")
pfUI:UpdateConfig("chat", "left", "height", "180")
+9 -15
View File
@@ -170,7 +170,7 @@ do -- dropdown
end
local function MenuOnUpdate()
if not this:IsMouseOver(100, -100, -100, 100) then
if not MouseIsOver(this, 100, -100, -100, 100) then
this.button:HideMenu()
end
end
@@ -661,7 +661,7 @@ function pfUI.api.SkinButton(button, cr, cg, cb, icon, disableHighlight)
end
b.UnlockHighlight = function()
if not MouseIsOverHitRect(b) then
if not MouseIsOver(b) then
b:SetBackdropBorderColor(GetStringColor(pfUI_config.appearance.border.color))
end
b.locked = false
@@ -683,7 +683,6 @@ function pfUI.api.SkinCollapseButton(button, all)
b.icon:SetSize(size, size)
b.icon:SetPoint("LEFT", 2, 2)
CreateBackdrop(b.icon)
b.icon:EnableMouse(false)
b.icon.text = b.icon:CreateFontString(nil, "OVERLAY")
b.icon.text:SetFontObject(GameFontWhite)
b.icon.text:SetPoint("CENTER", -1, 0)
@@ -1161,22 +1160,18 @@ function pfUI.api.CreateInfoBox(text, time, parent, height)
infobox = CreateFrame("Button", "pfInfoBox", UIParent)
infobox:Hide()
infobox.fade = infobox:CreateAnimationGroup()
local fade = infobox.fade:CreateAnimation("Alpha")
fade:SetChange(-1)
fade:SetDuration(.3)
infobox.fade:SetScript("OnFinished", function()
infobox:Hide()
infobox:SetAlpha(1)
end)
infobox:SetScript("OnUpdate", function()
local time = infobox.lastshow + infobox.duration - GetTime()
infobox.timeout:SetValue(time)
if GetTime() > infobox.lastshow + infobox.duration then
if not infobox.fade:IsPlaying() then infobox.fade:Play() end
elseif MouseIsOverHitRect(this) then
infobox:SetAlpha(infobox:GetAlpha()-0.05)
if infobox:GetAlpha() <= 0.1 then
infobox:Hide()
infobox:SetAlpha(1)
end
elseif MouseIsOver(this) then
this:SetAlpha(max(0.4, this:GetAlpha() - .1))
else
this:SetAlpha(min(1, this:GetAlpha() + .1))
@@ -1219,7 +1214,6 @@ function pfUI.api.CreateInfoBox(text, time, parent, height)
infobox:SetParent(parent)
infobox:SetFrameStrata("FULLSCREEN_DIALOG")
infobox.fade:Stop()
infobox:Show()
end
+408 -361
View File
File diff suppressed because it is too large Load Diff
+43
View File
@@ -0,0 +1,43 @@
-- load pfUI environment
setfenv(1, pfUI:GetEnvironment())
-- [[ Constants ]]--
EVENTS_MINIMAP_ZONE_UPDATE = {"PLAYER_ENTERING_WORLD", "MINIMAP_ZONE_CHANGED"}
MICRO_BUTTONS = {
'CharacterMicroButton', 'SpellbookMicroButton', 'TalentMicroButton',
'QuestLogMicroButton', 'SocialsMicroButton', 'WorldMapMicroButton',
'MainMenuMicroButton', 'HelpMicroButton',
}
NAMEPLATE_OBJECTORDER = { "border", "glow", "name", "level", "levelicon", "raidicon" }
NAMEPLATE_FRAMETYPE = "Button"
MINIMAP_TRACKING_FRAME = _G.MiniMapTrackingFrame
FRIENDS_NAME_LOCATION = "ButtonTextNameLocation"
COOLDOWN_FRAME_TYPE = "Model"
LOOT_BUTTON_FRAME_TYPE = "LootButton"
PLAYER_BUFF_START_ID = -1
ACTIONBAR_SECURE_TEMPLATE_BAR = nil
ACTIONBAR_SECURE_TEMPLATE_BUTTON = nil
--[[ Vanilla API Extensions ]]--
do -- RunMacroText
local obj = { ["GetText"] = function(self) return self.text end }
obj = setmetatable(obj, {__index = function(tab,key)
local value = function() return end
rawset(tab,key,value)
return value
end})
function RunMacroText(text)
obj.text = text
ChatEdit_ParseText(obj, 1)
end
end
+2358
View File
File diff suppressed because it is too large Load Diff
-2377
View File
File diff suppressed because it is too large Load Diff
-2991
View File
File diff suppressed because it is too large Load Diff
+3
View File
@@ -200,6 +200,7 @@ pfUI_translation["deDE"] = {
["Description Font"] = nil,
["Description Font Size"] = nil,
["Detailed Random Roll Announcement"] = nil,
["Detect Enemy Buffs"] = nil,
["Disable"] = nil,
["Disable Blizzard Castbar"] = nil,
["Disabled"] = nil,
@@ -669,6 +670,7 @@ pfUI_translation["deDE"] = {
["Scale"] = nil,
["Scale Border On HiDPI Displays"] = nil,
["Scaling"] = nil,
["Scan Macros For Spells"] = nil,
["Screen Edge Glow Intensity"] = nil,
["Screen Resolution"] = nil,
["Screenshot"] = nil,
@@ -872,6 +874,7 @@ pfUI_translation["deDE"] = {
["Website"] = nil,
["Welcome to |cff33ffccpf|cffffffffUI|r!\n\nI'm the first run wizard that will guide you through some basic configuration. If you're lazy, feel free to hit the \"Defaults\" button. If you wish to run this dialog again, go to the settings and hit the \"Reset Firstrun\" button.\n\nVisit |cff33ffcc%s|r to check for the latest version."] = nil,
["Whitelist"] = nil,
["Who Search Unknown Classes (|cffffaaaaExperimental|r)"] = nil,
["Width"] = nil,
["Wobble Zoom"] = nil,
["Won"] = nil,
+3 -6
View File
@@ -87,8 +87,6 @@ pfUI_translation["enUS"] = {
["Buffs"] = nil,
["Buff Size"] = nil,
["Buff Spacing"] = nil,
["Buff X-Spacing"] = nil,
["Buff Y-Spacing"] = nil,
["Buffs Per Row"] = nil,
["Button"] = nil,
["Button already exists in pfMinimapButtons frame"] = nil,
@@ -194,8 +192,6 @@ pfUI_translation["enUS"] = {
["Debuff Position"] = nil,
["Debuffs"] = nil,
["Debuff Size"] = nil,
["Debuff X-Spacing"] = nil,
["Debuff Y-Spacing"] = nil,
["Debuffs Per Row"] = nil,
["Decode"] = nil,
["Default"] = nil,
@@ -209,6 +205,7 @@ pfUI_translation["enUS"] = {
["Description Font"] = nil,
["Description Font Size"] = nil,
["Detailed Random Roll Announcement"] = nil,
["Detect Enemy Buffs"] = nil,
["Disable"] = nil,
["Disable Blizzard Castbar"] = nil,
["Disabled"] = nil,
@@ -585,8 +582,6 @@ pfUI_translation["enUS"] = {
["Overwrite If Unit Is Attacking Others"] = nil,
["Overwrite If Unit Is Attacking You"] = nil,
["Overwrite If Unit Is Casting"] = nil,
["Own Buff Size"] = nil,
["Own Debuff Size"] = nil,
["Owner Name"] = nil,
["Pageable"] = nil,
["Paging Actionbar"] = nil,
@@ -688,6 +683,7 @@ pfUI_translation["enUS"] = {
["Scale"] = nil,
["Scale Border On HiDPI Displays"] = nil,
["Scaling"] = nil,
["Scan Macros For Spells"] = nil,
["Screen Edge Glow Intensity"] = nil,
["Screen Resolution"] = nil,
["Screenshot"] = nil,
@@ -900,6 +896,7 @@ pfUI_translation["enUS"] = {
["Website"] = nil,
["Welcome to |cff33ffccpf|cffffffffUI|r!\n\nI'm the first run wizard that will guide you through some basic configuration. If you're lazy, feel free to hit the \"Defaults\" button. If you wish to run this dialog again, go to the settings and hit the \"Reset Firstrun\" button.\n\nVisit |cff33ffcc%s|r to check for the latest version."] = nil,
["Whitelist"] = nil,
["Who Search Unknown Classes (|cffffaaaaExperimental|r)"] = nil,
["Width"] = nil,
["Wobble Zoom"] = nil,
["Won"] = nil,
+3
View File
@@ -200,6 +200,7 @@ pfUI_translation["esES"] = {
["Description Font"] = "Fuente de descripción",
["Description Font Size"] = "Tamaño de la fuente de descripción",
["Detailed Random Roll Announcement"] = "Anuncio detallado para tirar los dados aleatoriamente",
["Detect Enemy Buffs"] = nil,
["Disable"] = "Desactivar",
["Disable Blizzard Castbar"] = "Desactivar la barra de lanzamiento de Blizzard",
["Disabled"] = "Desactivado",
@@ -669,6 +670,7 @@ pfUI_translation["esES"] = {
["Scale"] = "Escala",
["Scale Border On HiDPI Displays"] = "Escalar los bordes en las pantallas con DPI alto",
["Scaling"] = "Escalada",
["Scan Macros For Spells"] = nil,
["Screen Edge Glow Intensity"] = "Intensidad de brillo en los bordes de la pantalla",
["Screen Resolution"] = "Resolución de pantalla",
["Screenshot"] = "Captura de pantalla",
@@ -872,6 +874,7 @@ pfUI_translation["esES"] = {
["Website"] = "Sitio de web",
["Welcome to |cff33ffccpf|cffffffffUI|r!\n\nI'm the first run wizard that will guide you through some basic configuration. If you're lazy, feel free to hit the \"Defaults\" button. If you wish to run this dialog again, go to the settings and hit the \"Reset Firstrun\" button.\n\nVisit |cff33ffcc%s|r to check for the latest version."] = "¡Bienvenido a |cff33ffccpf|cffffffffUI|r!\n\nSoy el asistente de instalación que te guiará con la configuración básica. Si prefieres, puedes hacer clic el botón \"Predeterminado\" para cargar la configuración por defecto. Si quieres abrir este diálogo de nuevo, ve a la configuración y haz clic en el botón \"Reiniciar Primera vez\".\n\nVisita |cff33ffcc%s|r para descargar la última versión.",
["Whitelist"] = "Lista blanca",
["Who Search Unknown Classes (|cffffaaaaExperimental|r)"] = nil,
["Width"] = "Ancho",
["Wobble Zoom"] = "Acerca tambalea",
["Won"] = "Ganado",
+3
View File
@@ -200,6 +200,7 @@ pfUI_translation["frFR"] = {
["Description Font"] = "Description de la Police",
["Description Font Size"] = "Description de la taille de la Police",
["Detailed Random Roll Announcement"] = "Annonce détaillée des Rolls aléatoires",
["Detect Enemy Buffs"] = nil,
["Disable"] = "Désactiver",
["Disable Blizzard Castbar"] = "Désactiver la barre d'incantation d'origine",
["Disabled"] = "Désactivé",
@@ -669,6 +670,7 @@ pfUI_translation["frFR"] = {
["Scale"] = "Échelle",
["Scale Border On HiDPI Displays"] = "Échelle de bordure sur les écrans HiDPI",
["Scaling"] = "Mise à l'échelle",
["Scan Macros For Spells"] = nil,
["Screen Edge Glow Intensity"] = nil,
["Screen Resolution"] = "Résolution d'écran",
["Screenshot"] = "Imprime écran",
@@ -872,6 +874,7 @@ pfUI_translation["frFR"] = {
["Website"] = "Site web",
["Welcome to |cff33ffccpf|cffffffffUI|r!\n\nI'm the first run wizard that will guide you through some basic configuration. If you're lazy, feel free to hit the \"Defaults\" button. If you wish to run this dialog again, go to the settings and hit the \"Reset Firstrun\" button.\n\nVisit |cff33ffcc%s|r to check for the latest version."] = "Bienvenue sur l'assistant d'installation de l'interface |cff33ffccpf|cffffffffUI|r!\n\nNous allons effectuer quelques configurations de base. Si vous n'avez pas la patience, cliquer sur le bouton \"Paramètres par défaut\". Si vous souhaitez relancer cette fenêtre de dialogue, allez dans les options et cliquez sur le bouton\"Réinitialiser l'assistant d'installation\" .\n\nAllez sur |cff33ffcc%s|r pour télécharger la dernière version.",
["Whitelist"] = "List blanche",
["Who Search Unknown Classes (|cffffaaaaExperimental|r)"] = nil,
["Width"] = "Largeur",
["Wobble Zoom"] = "Zoom oscillant",
["Won"] = "A gagné",
+3
View File
@@ -200,6 +200,7 @@ pfUI_translation["koKR"] = {
["Description Font"] = nil,
["Description Font Size"] = nil,
["Detailed Random Roll Announcement"] = nil,
["Detect Enemy Buffs"] = nil,
["Disable"] = "가리기",
["Disable Blizzard Castbar"] = "블리자드 캐스트바 사용 중지",
["Disabled"] = "가리기",
@@ -669,6 +670,7 @@ pfUI_translation["koKR"] = {
["Scale"] = nil,
["Scale Border On HiDPI Displays"] = nil,
["Scaling"] = nil,
["Scan Macros For Spells"] = nil,
["Screen Edge Glow Intensity"] = nil,
["Screen Resolution"] = "화면 해상도",
["Screenshot"] = nil,
@@ -872,6 +874,7 @@ pfUI_translation["koKR"] = {
["Website"] = nil,
["Welcome to |cff33ffccpf|cffffffffUI|r!\n\nI'm the first run wizard that will guide you through some basic configuration. If you're lazy, feel free to hit the \"Defaults\" button. If you wish to run this dialog again, go to the settings and hit the \"Reset Firstrun\" button.\n\nVisit |cff33ffcc%s|r to check for the latest version."] = "|cff33ffccpf|cffffffffUI|r에 |cff33ffcc오신걸 환영합니다.|r \n\n저는 몇 가지 기본 구성을 안내 할 첫 번째 실행 위자드입니다. 모든 단계를 건너 뛰고 싶으시면 \"기본값 사용\" 버튼을 누르십시오. 이 대화 상자를 다시 실행하려면 추후 설정으로 이동하여 \"첫단계로 재설정\" 버튼을 누르십시오.\n\n최신버전 다운로드 사이트: |cff33ffcc%s|r",
["Whitelist"] = nil,
["Who Search Unknown Classes (|cffffaaaaExperimental|r)"] = nil,
["Width"] = nil,
["Wobble Zoom"] = nil,
["Won"] = nil,
+3
View File
@@ -200,6 +200,7 @@ pfUI_translation["ruRU"] = {
["Description Font"] = "Шрифт описания",
["Description Font Size"] = "Размер шрифта описания",
["Detailed Random Roll Announcement"] = "Подробное объявление о случайном броске",
["Detect Enemy Buffs"] = nil,
["Disable"] = "Отключить",
["Disable Blizzard Castbar"] = "Отключить панель применения Blizzard",
["Disabled"] = "Отключено",
@@ -669,6 +670,7 @@ pfUI_translation["ruRU"] = {
["Scale"] = "Масштаб",
["Scale Border On HiDPI Displays"] = "Масштабировать границы на HiDPI мониторах",
["Scaling"] = "Масштаб интерфейса",
["Scan Macros For Spells"] = nil,
["Screen Edge Glow Intensity"] = "Интенсивность свечения на краях экрана",
["Screen Resolution"] = "Разрешение экрана",
["Screenshot"] = "Снимок экрана",
@@ -872,6 +874,7 @@ pfUI_translation["ruRU"] = {
["Website"] = "Веб-сайт",
["Welcome to |cff33ffccpf|cffffffffUI|r!\n\nI'm the first run wizard that will guide you through some basic configuration. If you're lazy, feel free to hit the \"Defaults\" button. If you wish to run this dialog again, go to the settings and hit the \"Reset Firstrun\" button.\n\nVisit |cff33ffcc%s|r to check for the latest version."] = "Добро пожаловать в |cff33ffccpf|cffffffffUI|r!\n\nЯ мастер первого запуска, который проведет вас через некоторую базовую конфигурацию. Если вы ленитесь, не стесняйтесь нажать кнопку \"По умолчанию\". Если вы хотите снова запустить этот диалог, перейдите к настройкам и нажмите кнопку \"Первый запуск\".\n\nПосетите |cff33ffcc%s|r, чтобы проверить последнюю версию.",
["Whitelist"] = "\"Белый\" список",
["Who Search Unknown Classes (|cffffaaaaExperimental|r)"] = nil,
["Width"] = "Ширина",
["Wobble Zoom"] = "Подпрыгивание",
["Won"] = "Победа",
+3
View File
@@ -200,6 +200,7 @@ pfUI_translation["zhCN"] = {
["Description Font"] = "描述字体",
["Description Font Size"] = "描述字体大小",
["Detailed Random Roll Announcement"] = "通告详细随机Roll点",
["Detect Enemy Buffs"] = "探测敌方Buffs",
["Disable"] = "禁用",
["Disable Blizzard Castbar"] = "隐藏暴雪施法条",
["Disabled"] = "不显示",
@@ -669,6 +670,7 @@ pfUI_translation["zhCN"] = {
["Scale"] = "比例",
["Scale Border On HiDPI Displays"] = "缩放高DPI显示器上的边框",
["Scaling"] = "UI缩放",
["Scan Macros For Spells"] = "扫描宏命令中的法术",
["Screen Edge Glow Intensity"] = "屏幕边缘发光强度",
["Screen Resolution"] = "屏幕分辨率",
["Screenshot"] = "屏幕截图",
@@ -873,6 +875,7 @@ pfUI_translation["zhCN"] = {
["Website"] = "网站",
["Welcome to |cff33ffccpf|cffffffffUI|r!\n\nI'm the first run wizard that will guide you through some basic configuration. If you're lazy, feel free to hit the \"Defaults\" button. If you wish to run this dialog again, go to the settings and hit the \"Reset Firstrun\" button.\n\nVisit |cff33ffcc%s|r to check for the latest version."] = "欢迎使用 |cff33ffccpf|cffffffffUI|r! \n\n这是第一次运行向导,将引导您完成一些基本配置。 如果你想跳过所有步骤\"使用默认\"按钮。 如果您再次运行此对话框,请转到设置并点击 \"Reset Firstrun\" 按钮。\n\n访问 |cff33ffcc%s|r 检查最新版本。",
["Whitelist"] = "白名单",
["Who Search Unknown Classes (|cffffaaaaExperimental|r)"] = nil,
["Width"] = "宽度",
["Wobble Zoom"] = "摆动变焦",
["Won"] = "获胜",
+3
View File
@@ -200,6 +200,7 @@ pfUI_translation["zhTW"] = {
["Description Font"] = nil,
["Description Font Size"] = nil,
["Detailed Random Roll Announcement"] = "通告詳細隨機Roll點",
["Detect Enemy Buffs"] = nil,
["Disable"] = "禁用",
["Disable Blizzard Castbar"] = "隱藏暴雪施法條",
["Disabled"] = "不顯示",
@@ -669,6 +670,7 @@ pfUI_translation["zhTW"] = {
["Scale"] = "比例",
["Scale Border On HiDPI Displays"] = nil,
["Scaling"] = nil,
["Scan Macros For Spells"] = nil,
["Screen Edge Glow Intensity"] = nil,
["Screen Resolution"] = "螢幕解析度",
["Screenshot"] = nil,
@@ -872,6 +874,7 @@ pfUI_translation["zhTW"] = {
["Website"] = nil,
["Welcome to |cff33ffccpf|cffffffffUI|r!\n\nI'm the first run wizard that will guide you through some basic configuration. If you're lazy, feel free to hit the \"Defaults\" button. If you wish to run this dialog again, go to the settings and hit the \"Reset Firstrun\" button.\n\nVisit |cff33ffcc%s|r to check for the latest version."] = "歡迎使用 |cff33ffccpf|cffffffffUI|r! \n\n這是第一次運行嚮導,將引導您完成一些基本配置。 如果你想跳過所有步驟\"使用預設\"按鈕。 如果您再次運行此對話方塊,請轉到設置並點擊 \"Reset Firstrun\" 按鈕。\n\n訪問 |cff33ffcc%s|r 檢查最新版本。",
["Whitelist"] = "白名單",
["Who Search Unknown Classes (|cffffaaaaExperimental|r)"] = nil,
["Width"] = nil,
["Wobble Zoom"] = nil,
["Won"] = nil,
+1 -1
View File
@@ -1,3 +1,3 @@
<Ui xmlns="http://www.blizzard.com/wow/ui/">
<Include file="..\env\tables_stock.lua"/>
<Include file="..\compat\vanilla.lua"/>
</Ui>
+2
View File
@@ -70,7 +70,9 @@
<Include file="..\modules\addoncompat.lua"/>
<Include file="..\modules\energytick.lua"/>
<Include file="..\modules\totems.lua"/>
<Include file="..\modules\macrotweak.lua"/>
<Include file="..\modules\macroicons.lua"/>
<Include file="..\modules\turtle-wow.lua"/>
<Include file="..\modules\superwow.lua"/>
<Include file="..\modules\innervatecall.lua"/>
<Include file="..\modules\nampower.lua"/>
+7 -2
View File
@@ -30,12 +30,17 @@
<Include file="..\skins\blizzard\options-sound.lua"/>
<Include file="..\skins\blizzard\options-video.lua"/>
<Include file="..\skins\blizzard\options-new.lua"/>
<Include file="..\skins\blizzard\options-interface.lua"/>
<Include file="..\skins\blizzard\petition.lua"/>
<Include file="..\skins\blizzard\taxi.lua"/>
<Include file="..\skins\blizzard\tooltips.lua"/>
<Include file="..\skins\blizzard\tabard.lua"/>
<Include file="..\skins\blizzard\itemtext.lua"/>
<Include file="..\skins\blizzard\lft.lua"/>
<Include file="..\skins\blizzard\turtle_shop.lua"/>
<!-- Turtle WoW -->
<Include file="..\skins\blizzard\barbershop.lua"/>
<Include file="..\skins\blizzard\transmog.lua"/>
<Include file="..\skins\blizzard\ebc.lua"/>
<!-- Turtle WoW skins live in init\turtle.xml (pfUI_Turtle.toc only). -->
</Ui>
-14
View File
@@ -1,14 +0,0 @@
<Ui xmlns="http://www.blizzard.com/wow/ui/">
<!-- Turtle WoW only: pulled in by pfUI_Turtle.toc, which ClassicAPI selects
when it detects a Turtle client. Everything in here targets frames that
only a Turtle client ever creates. -->
<Include file="..\env\tables_turtle.lua"/>
<Include file="..\skins\blizzard\lft.lua"/>
<Include file="..\skins\blizzard\turtle_shop.lua"/>
<Include file="..\skins\blizzard\barbershop.lua"/>
<Include file="..\skins\blizzard\transmog.lua"/>
<Include file="..\skins\blizzard\ebc.lua"/>
<Include file="..\modules\turtle-wow.lua"/>
</Ui>
+14 -37
View File
@@ -18,7 +18,12 @@ local ItemQuality = Enum.ItemQuality
local function SortCategoryPrefix(itemId, classID, quality)
if itemId == HEARTHSTONE_ITEM_ID then return "00" end
if quality == ItemQuality.Poor then return "13" end -- gray always last
if classID == ItemClass.Weapon or classID == ItemClass.Armor then return "01" end
if classID == ItemClass.Weapon or classID == ItemClass.Armor then
if quality and quality >= ItemQuality.Epic then return "01" end -- Epic+ gear
if quality == ItemQuality.Rare then return "02" end -- Rare gear
if quality == ItemQuality.Uncommon then return "03" end -- Uncommon gear
return "04" -- Common/poor gear
end
if classID == ItemClass.Consumable then return "05" end
if classID == ItemClass.Reagent then return "06" end
if classID == ItemClass.Tradegoods then return "07" end
@@ -37,40 +42,12 @@ local function SortCountSuffix(count)
return string.sub(s, -6)
end
-- Gear's slot order, keyed by the equipLoc token (INVTYPE_*). Not the
-- token's alphabetical order, which puts chest before head, and not its
-- INVTYPE number, which parts robe (20) from chest (5). Same order as
-- ClassicAPI's C_Container.SortBags, taken from Baganator: weapons by hand,
-- ranged and ammo, then armor with shields and off-hands first, head to
-- feet, then jewelry, shirt and tabard.
local SLOT_ORDER = {
"INVTYPE_2HWEAPON", "INVTYPE_WEAPON", "INVTYPE_WEAPONMAINHAND",
"INVTYPE_SHIELD", "INVTYPE_HOLDABLE", "INVTYPE_RANGEDRIGHT",
"INVTYPE_WEAPONOFFHAND", "INVTYPE_RANGED", "INVTYPE_THROWN",
"INVTYPE_AMMO", "INVTYPE_QUIVER", "INVTYPE_RELIC",
"INVTYPE_HEAD", "INVTYPE_SHOULDER", "INVTYPE_CLOAK", "INVTYPE_CHEST",
"INVTYPE_ROBE", "INVTYPE_WRIST", "INVTYPE_HAND", "INVTYPE_WAIST",
"INVTYPE_LEGS", "INVTYPE_FEET",
"INVTYPE_NECK", "INVTYPE_FINGER", "INVTYPE_TRINKET", "INVTYPE_BODY",
"INVTYPE_TABARD", "INVTYPE_BAG",
}
local SLOT_RANK = {}
for i, token in ipairs(SLOT_ORDER) do SLOT_RANK[token] = i end
local function SortKey(itemId, name, classID, subClassID, quality, count, equipLoc)
-- Zero-pad the numeric fields so they sort numerically as strings ("10"
-- would otherwise precede "2").
local key = SortCategoryPrefix(itemId, classID, quality)
.. string.format("%02d|", classID or 99)
if classID == ItemClass.Weapon or classID == ItemClass.Armor then
-- Gear: slot, then subtype, then best quality first, so all helms sit
-- together, then all shoulders.
key = key .. string.format("%02d|%02d|%d|",
SLOT_RANK[equipLoc] or 99, subClassID or 99, 9 - (quality or 0))
else
key = key .. string.format("%02d|", subClassID or 99)
end
return key .. (name or "zzz") .. "|" .. SortCountSuffix(count)
local function SortKey(itemId, name, classID, subClassID, quality, count)
-- Zero-pad the class/subclass so the secondary grouping sorts numerically
-- (as a string, "10" would otherwise precede "2").
return SortCategoryPrefix(itemId, classID, quality)
.. string.format("%02d|%02d|", classID or 99, subClassID or 99)
.. (name or "zzz") .. "|" .. SortCountSuffix(count)
end
local function ClearSortData()
@@ -191,10 +168,10 @@ local function BuildSortGrid()
-- sit at positions 12/13. We categorize on those numeric class IDs
-- rather than the localized itemType/itemSubType strings. (pfUI's
-- shimmed global GetItemInfo is only 10 fields and lacks them.)
local name, _, quality, _, _, _, _, _, equipLoc, _, _, classID, subClassID = C_Item.GetItemInfo(itemId)
local name, _, quality, _, _, _, _, _, _, _, _, classID, subClassID = C_Item.GetItemInfo(itemId)
local _, count = GetContainerItemInfo(bag, slot)
local item = {
key = SortKey(itemId, name, classID, subClassID, quality, count, equipLoc),
key = SortKey(itemId, name, classID, subClassID, quality, count),
-- vanilla items carry at most one family bit, so equality
-- against a bag family suffices (no bit.band needed).
family = C_Item.GetItemFamily(itemId) or 0,
+1616 -8
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -10,8 +10,8 @@ local libhealth = CreateFrame("Frame")
libhealth.enabled = true
libhealth.reqhit = 4
libhealth.reqdmg = 5
libhealth:RegisterUnitEvent("UNIT_HEALTH", "target")
libhealth:RegisterUnitEvent("UNIT_COMBAT", "target")
libhealth:RegisterEvent("UNIT_HEALTH")
libhealth:RegisterEvent("UNIT_COMBAT")
libhealth:RegisterEvent("PLAYER_TARGET_CHANGED")
libhealth:RegisterEvent("PLAYER_ENTERING_WORLD")
libhealth:SetScript("OnEvent", function()
+206 -162
View File
@@ -13,23 +13,34 @@ setfenv(1, pfUI:GetEnvironment())
-- including resurrections. It has an option to disable the sending of those
-- messages in case HealComm is already active.
--
-- Casts are read from ClassicAPI's UNIT_SPELLCAST_* events and heal amounts
-- from COMBAT_LOG_EVENT_UNFILTERED. GetHotDuration() reads live HoT timers
-- from C_UnitAuras (via libdebuff) and falls back to the HealComm-fed table.
-- HOT TRACKING INTEGRATION (NEW):
-- With Nampower enabled, HoT tracking now primarily uses libdebuff's AURA_CAST
-- event system for accurate server-side buff/debuff tracking with full rank
-- protection. GetHotDuration() first checks libdebuff, then falls back to the
-- legacy prediction system for backwards compatibility with non-Nampower clients.
-- This provides:
-- - Accurate duration from server (no prediction needed)
-- - Automatic rank protection (lower ranks won't overwrite higher ranks)
-- - Support for multiple casters of same HoT on one target
-- - Zero event overhead (libdebuff already tracks all auras)
-- return instantly when another libpredict is already active
if pfUI.api.libpredict then return end
-- Check if libdebuff integration is available
local libdebuff_available = (pfUI.api.libdebuff and pfUI.api.libdebuff.GetBestAuraCast) and true or false
-- Check if Nampower is available for SPELL_FAILED events
local senttarget
local heals, ress, events, hots = {}, {}, {}, {}
local spell_queue = { "DUMMY", "DUMMYRank 9", "TARGET" }
local player = UnitName("player")
local cache, gear_string = {}, ""
local foreignCache = {} -- [casterName][spellKey] = amount, in-memory cache for other healers
local rejuvDuration, renewDuration = 12, 15 --default durations
local ressGuidToName = {} -- [casterGuid] = casterName, for interrupted-cast cleanup
local healGuidToName = {} -- [casterGuid] = casterName, for interrupted-cast cleanup
local sentTargets = {} -- [castGUID] = target name of an own cast, from UNIT_SPELLCAST_SENT
local castTargets = {} -- [castGUID] = target name of a foreign cast, from UNIT_SPELLCAST_START
local ressGuidToName = {} -- [casterGuid] = casterName, for SPELL_FAILED_OTHER cleanup
local healGuidToName = {} -- [casterGuid] = casterName, for SPELL_FAILED_OTHER cleanup
local ress_timers = {} -- [target][sender] = expiry_timestamp (60s rez window)
local RESS_TIMEOUT = 60 -- Vanilla: rez offer expires after 60s
@@ -113,25 +124,43 @@ libpredict:SetScript("OnEvent", function()
if origOnEvent then origOnEvent() end
end)
-- Own cast-time spell started (heals + rez)
local function OnOwnCastStart(spellId, castGUID)
-- SPELL_START_SELF: own cast started (heals + rez)
pfUI.libdebuff_spell_start_self_hooks = pfUI.libdebuff_spell_start_self_hooks or {}
pfUI.libdebuff_spell_start_self_hooks["libpredict"] = function(spellId, casterGuid, targetGuid, castTime)
local spellName = C_Spell.GetSpellName(spellId)
if not spellName then return end
-- SENT names the unit the cast went out at; self and no-target casts
-- carry none, so fall back to what the cast hooks recorded
local target = sentTargets[castGUID] or spell_queue[3]
local _, _, _, startMs, endMs = C_Spell.CastingInfo()
local castTime = startMs and endMs - startMs or 0
local pendingTarget = nil
local pendingTargetGuid = nil
local pending = pfUI.libpredict_pending_cast
if pending and pending.spellName == spellName and pending.targetGuid
and pending.time and (GetTime() - pending.time) < 1 then
-- If SPELL_CAST_EVENT target differs from SPELL_START_SELF target,
-- the client redirected the cast (e.g. offline target → selfcast).
-- Discard pending and trust the actual targetGuid from SPELL_START_SELF.
if pending.targetGuid == targetGuid then
local name = UnitName(pending.targetGuid)
if name and name ~= UNKNOWNOBJECT and name ~= UKNOWNBEING then
pendingTarget = name
pendingTargetGuid = pending.targetGuid
end
end
pending.spellId = nil
pending.spellName = nil
pending.targetGuid = nil
pending.time = nil
end
local target = pendingTarget or resolveNameFromGuid(targetGuid) or senttarget or spell_queue[3]
-- resolved GUID: prefer pendingTargetGuid (mouseover/click-to-cast), fall back to targetGuid
local resolvedTargetGuid = pendingTargetGuid or targetGuid
libpredict.sender.castGUID = castGUID
libpredict.sender.delayMs = 0
libpredict.sender.current_cast = spellName
libpredict.sender.current_cast_target = target
if L["resurrections"][spellName] then
if target then
libpredict:Ress(player, target, UnitGUID("player"))
libpredict:Ress(player, target, casterGuid)
libpredict.sender:SendHealCommMsg("Resurrection/" .. target .. "/start/")
libpredict.sender:SendResCommMsg("RES " .. target)
libpredict.sender.resurrecting = true
@@ -139,9 +168,6 @@ local function OnOwnCastStart(spellId, castGUID)
return
end
spell_queue[1] = spellName
spell_queue[2] = spellName .. (C_Spell.GetSpellSubtext(spellId) or "")
if spell_queue[1] == spellName and cache[spell_queue[2]] then
local amount = cache[spell_queue[2]][1]
local casttime = castTime
@@ -206,17 +232,27 @@ local function OnOwnCastStart(spellId, castGUID)
return -- skip the generic Heal call below
end
libpredict:Heal(player, target, amount, casttime)
libpredict.sender:SendHealCommMsg("Heal/" .. (target or "") .. "/" .. amount .. "/" .. casttime .. "/")
-- If the resolved heal target is itself hostile (e.g. self-cast heal while targeting an enemy),
-- store the heal under the player so the player frame shows the prediction.
-- Use resolvedTargetGuid (prefers pendingTargetGuid from mouseover/click-to-cast) so we
-- don't wrongly fall back to player when healing a friend via mouseover with a foe in target.
local healTarget = target
local unitTargetGuid = UnitGUID and UnitGUID("target")
local healTargetIsHostile = resolvedTargetGuid and unitTargetGuid
and resolvedTargetGuid == unitTargetGuid
and UnitExists("target")
and (UnitCanAttack("player", "target") or not UnitIsFriend("player", "target"))
if healTargetIsHostile then
healTarget = player
end
libpredict:Heal(player, healTarget, amount, casttime)
libpredict.sender:SendHealCommMsg("Heal/" .. (healTarget or "") .. "/" .. amount .. "/" .. casttime .. "/")
libpredict.sender.healing = true
end
end
-- Own cast landed: HealStop, Regrowth timer, instant HoTs, own rez timer
local function OnOwnCastSucceeded(spellId, castGUID)
local target = sentTargets[castGUID] or spell_queue[3]
sentTargets[castGUID] = nil
-- SPELL_GO_SELF: own cast landed (HealStop + Regrowth timer)
pfUI.libdebuff_spell_go_hooks["libpredict_sender"] = function(spellId)
libpredict:HealStop(player)
local spellName = C_Spell.GetSpellName(spellId)
if spellName == REGROWTH then
@@ -228,57 +264,32 @@ local function OnOwnCastSucceeded(spellId, castGUID)
end
libpredict.sender.regrowth_timer = now + 0.1
end
libpredict.sender.castGUID = nil
libpredict.sender.current_cast = nil
libpredict.sender.current_cast_target = nil
if not target then return end
-- Instant HoTs — classify by name (rank-independent) instead of a
-- hardcoded per-rank ID table.
local hotType
if spellName == REJUVENATION then hotType = "Reju"
elseif spellName == RENEW then hotType = "Renew"
end
if hotType then
local duration = hotType == "Reju" and rejuvDuration or renewDuration
local rank = 0
local rankSub = C_Spell.GetSpellSubtext(spellId)
if rankSub and rankSub ~= "" then
rank = tonumber((string.gsub(rankSub, "Rank ", ""))) or 0
end
libpredict:Hot(player, target, hotType, duration, nil, "UNIT_SPELLCAST_SUCCEEDED", rank)
libpredict.sender:SendHealCommMsg(hotType .. "/" .. target .. "/" .. duration .. "/" .. rank .. "/")
end
if isRezSpell(spellId) then
libpredict:RessSetTimer(player, target)
end
end
-- Foreign cast-time spell started (heals + rez)
local function OnOtherCastStart(unit, spellId, castGUID)
-- SPELL_START_OTHER: foreign cast started (heals + rez)
-- Signature: fn(spellId, casterGuid, targetGuid, castTime)
pfUI.libdebuff_spell_start_other_hooks = pfUI.libdebuff_spell_start_other_hooks or {}
pfUI.libdebuff_spell_start_other_hooks["libpredict"] = function(spellId, casterGuid, targetGuid, castTime)
local spellName = C_Spell.GetSpellName(spellId)
if not spellName then return end
local casterGuid = UnitGUID(unit)
local casterName = resolveNameFromGuid(casterGuid)
if not casterName then return end
local targetName = UnitSpellTargetName(unit)
if not targetName then return end
castTargets[castGUID] = targetName
-- Resurrection cast
if L["resurrections"][spellName] then
libpredict:Ress(casterName, targetName, casterGuid)
local targetName = resolveNameFromGuid(targetGuid)
if targetName then
libpredict:Ress(casterName, targetName, casterGuid)
end
return
end
-- Heal cast: look up cached amount from a previous cast by this healer
local _, _, _, startMs, endMs = C_Spell.UnitCastingInfo(unit)
if not startMs then return end
local castTime = endMs - startMs
local targetName = resolveNameFromGuid(targetGuid)
if not targetName then return end
local rankStr = C_Spell.GetSpellSubtext(spellId) or ""
local spellKey = spellName .. (rankStr or "")
@@ -324,26 +335,77 @@ local function OnOtherCastStart(unit, spellId, castGUID)
libpredict:Heal(casterName, targetName, amount, castTime, casterGuid)
end
-- Foreign cast landed: rez timer, or the predicted heal is fulfilled
local function OnOtherCastSucceeded(unit, spellId, castGUID)
local casterGuid = UnitGUID(unit)
-- SPELL_GO_SELF: own cast landed - HoTs + own rez timer
-- Signature: fn(spellId, arg1, arg2, arg3, arg4, arg5, arg6, arg7)
pfUI.libdebuff_spell_go_hooks = pfUI.libdebuff_spell_go_hooks or {}
pfUI.libdebuff_spell_go_hooks["libpredict"] = function(spellId, a1, a2, a3, a4, a5, a6, a7)
-- Instant HoTs — classify by name (rank-independent) instead of a
-- hardcoded per-rank ID table.
local spellName = C_Spell.GetSpellName(spellId)
local hotType
if spellName == REJUVENATION then hotType = "Reju"
elseif spellName == RENEW then hotType = "Renew"
end
if hotType then
local targetGuid = a4
local targetName = resolveNameFromGuid(targetGuid)
if targetName then
local duration
if hotType == "Reju" then duration = rejuvDuration or 12
elseif hotType == "Renew" then duration = renewDuration or 15
end
local rank = 0
local rankSub = C_Spell.GetSpellSubtext(spellId)
if rankSub and rankSub ~= "" then
rank = tonumber((string.gsub(rankSub, "Rank ", ""))) or 0
end
local playerName = UnitName("player")
libpredict:Hot(playerName, targetName, hotType, duration, nil, "SPELL_GO_SELF", rank)
local rankStr = tostring(rank)
if libpredict.sender and libpredict.sender.SendHealCommMsg then
libpredict.sender:SendHealCommMsg(hotType .. "/" .. targetName .. "/" .. duration .. "/" .. rankStr .. "/")
elseif IsInRaid() then
SendAddonMessage("HealComm", hotType .. "/" .. targetName .. "/" .. duration .. "/" .. rankStr .. "/", "RAID")
elseif IsInGroup() then
SendAddonMessage("HealComm", hotType .. "/" .. targetName .. "/" .. duration .. "/" .. rankStr .. "/", "PARTY")
end
end
end
-- Own rez landed: set timer
if isRezSpell(spellId) then
local targetGuid = a4
local targetName = resolveNameFromGuid(targetGuid)
local playerName = UnitName("player")
if playerName and targetName then
libpredict:RessSetTimer(playerName, targetName)
end
end
end
-- SPELL_GO_OTHER: foreign cast landed - rez timer + HealStop
-- Signature: fn(spellId, casterGuid, targetGuid)
pfUI.libdebuff_spell_go_other_hooks = pfUI.libdebuff_spell_go_other_hooks or {}
pfUI.libdebuff_spell_go_other_hooks["libpredict"] = function(spellId, casterGuid, targetGuid)
local casterName = resolveNameFromGuid(casterGuid)
if not casterName then return end
if isRezSpell(spellId) then
local targetName = castTargets[castGUID]
local targetName = resolveNameFromGuid(targetGuid)
if targetName then
libpredict:RessSetTimer(casterName, targetName)
end
return
end
-- Heal landed → prediction fulfilled, clean up
libpredict:HealStop(casterName)
end
-- Foreign cast interrupted: remove cancelled resses/heals
local function OnOtherCastInterrupted(unit)
local casterGuid = UnitGUID(unit)
-- SPELL_FAILED_OTHER: remove cancelled resses/heals
-- Signature: fn(casterGuid, spellId)
pfUI.libdebuff_spell_failed_other_hooks = pfUI.libdebuff_spell_failed_other_hooks or {}
pfUI.libdebuff_spell_failed_other_hooks["libpredict"] = function(casterGuid, spellId)
if not casterGuid then return end
local ressName = ressGuidToName[casterGuid]
if ressName then
@@ -502,7 +564,7 @@ function libpredict:ParseChatMessage(sender, msg, comm)
libpredict:Heal(sender, target, heal, time)
end
elseif msgtype == "Ress" then
-- HealComm: only for other players (own resses tracked via UNIT_SPELLCAST_START)
-- HealComm: only for other players (own resses tracked via SPELL_START_SELF hook)
local playerName = UnitName("player")
if sender ~= playerName then
local senderGuid
@@ -847,7 +909,7 @@ hooksecurefunc("CastSpell", function(id, bookType)
rankNum = tonumber((string.gsub(rank, "Rank ", ""))) or nil
end
-- Instant HoTs: UNIT_SPELLCAST_SUCCEEDED and C_UnitAuras, hook method as fallback
-- Instant HoTs: libdebuff/Nampower via GetHotDuration, hook method as fallback
if effect == REJUVENATION then
local target = spell_queue[3]
@@ -911,7 +973,7 @@ hooksecurefunc("CastSpellByName", function(effect, target)
spell_queue[3] = target or default
end
-- Instant HoTs: UNIT_SPELLCAST_SUCCEEDED and C_UnitAuras, hook method as fallback
-- Instant HoTs: libdebuff/Nampower via GetHotDuration, hook method as fallback
if effect == REJUVENATION then
local hotTarget = target or default
@@ -972,7 +1034,7 @@ hooksecurefunc("UseAction", function(slot, target, selfcast)
rankNum = tonumber((string.gsub(rank, "Rank ", ""))) or nil
end
-- Instant HoTs: UNIT_SPELLCAST_SUCCEEDED and C_UnitAuras, hook method as fallback
-- Instant HoTs: libdebuff/Nampower via GetHotDuration, hook method as fallback
if effect == REJUVENATION then
local hotTarget = spell_queue[3]
@@ -1062,17 +1124,15 @@ libpredict.sender:SetScript("OnUpdate", function()
end
end)
-- Own casts arrive as "player"; every other caster fires once per unit token
-- it currently holds (raidN, target, nameplateN, ...), so other-unit handlers
-- drop the repeats by castGUID.
libpredict.sender:RegisterEvent("UNIT_SPELLCAST_SENT")
libpredict.sender:RegisterEvent("UNIT_SPELLCAST_START")
libpredict.sender:RegisterEvent("UNIT_SPELLCAST_DELAYED")
libpredict.sender:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED")
libpredict.sender:RegisterEvent("UNIT_SPELLCAST_FAILED")
libpredict.sender:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED")
libpredict.sender:RegisterEvent("UNIT_SPELLCAST_STOP")
libpredict.sender:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
-- Nampower events
libpredict.sender:RegisterEvent("SPELL_FAILED_SELF")
libpredict.sender:RegisterEvent("SPELL_DELAYED_SELF")
libpredict.sender:RegisterEvent("SPELL_HEAL_BY_SELF")
libpredict.sender:RegisterEvent("SPELL_HEAL_BY_OTHER") -- populates foreignCache for other healers
-- force cache updates
libpredict.sender:RegisterEvent("UNIT_INVENTORY_CHANGED")
libpredict.sender:RegisterEvent("SKILL_LINES_CHANGED")
-- Shared cleanup helper for failed/interrupted casts
local function onCastFailed()
@@ -1082,7 +1142,7 @@ local function onCastFailed()
s:SendHealCommMsg("Healstop")
s.healing = nil
elseif s.resurrecting then
local target = s.current_cast_target or spell_queue[3]
local target = s.current_cast_target or senttarget or spell_queue[3]
libpredict:RessStop(UnitName("player"))
s:SendHealCommMsg("Resurrection/stop/")
s:SendResCommMsg("RESNO " .. (target or ""))
@@ -1094,103 +1154,81 @@ local function onCastFailed()
s.regrowth_target_next = nil
s.regrowth_start_next = nil
end
s.castGUID = nil
s.current_cast = nil
s.current_cast_target = nil
end
-- Real heal amounts: own heals refresh the spell cache, other group healers'
-- heals fill foreignCache (one event per hit target for AoE heals like PoH).
local function OnHeal()
-- arg4=sourceGUID, arg12=spellId, arg13=spellName, arg15=amount, arg18=critical
local spellId, spellName, amount, isCrit = arg12, arg13, arg15, arg18 == 1
if not spellName or not amount or amount <= 0 then return end
if IsPlayerGuid(arg4) then
if spell_queue[1] == spellName then
libpredict.sender:SetScript("OnEvent", function()
-- ============================================================
-- NAMPOWER PATH
-- ============================================================
if event == "SPELL_HEAL_BY_SELF" then
-- arg1=targetGuid, arg2=casterGuid, arg3=spellId, arg4=amount, arg5=critical, arg6=periodic
local spellId = arg3
local amount = arg4
local isCrit = arg5 == 1
local isPeriodic = arg6 == 1
local spellName = C_Spell.GetSpellName(spellId)
if spellName and spell_queue[1] == spellName then
UpdateCache(spell_queue[2], amount, isCrit)
end
return
end
local casterName = resolveNameFromGuid(arg4)
if not casterName then return end
elseif event == "SPELL_HEAL_BY_OTHER" then
-- Fires once per hit target for AoE heals (e.g. PoH).
-- Use this to build a per-caster cache of real heal amounts.
-- arg1=targetGuid, arg2=casterGuid, arg3=spellId, arg4=amount, arg5=critical, arg6=periodic
local casterGuid = arg2
local spellId = arg3
local amount = arg4
local isCrit = arg5 == 1
local isPeriodic = arg6 == 1
local spellKey = spellName .. (C_Spell.GetSpellSubtext(spellId) or "")
if isPeriodic or not spellId or not amount or amount <= 0 then return end
foreignCache[casterName] = foreignCache[casterName] or {}
local existing = foreignCache[casterName][spellKey]
-- Store highest non-crit value for best prediction accuracy
if isCrit then
-- Estimate base from crit (vanilla crit = 150%)
local base = math.floor(amount * 2 / 3)
if not existing or base > existing then
foreignCache[casterName][spellKey] = base
end
else
if not existing or amount > existing then
foreignCache[casterName][spellKey] = amount
end
end
end
local casterName = resolveNameFromGuid(casterGuid)
if not casterName or casterName == player then return end -- own heals handled by SPELL_HEAL_BY_SELF
local lastOtherCast = {} -- [event] = castGUID of the last other-unit event handled
local spellName = C_Spell.GetSpellName(spellId)
if not spellName then return end
local rankStr = C_Spell.GetSpellSubtext(spellId)
local spellKey = spellName .. (rankStr or "")
libpredict.sender:SetScript("OnEvent", function()
if event == "COMBAT_LOG_EVENT_UNFILTERED" then
if arg2 == "SPELL_HEAL" then OnHeal() end
return
end
-- UNIT_SPELLCAST_SENT: arg1=unit, arg2=target token, arg3=castGUID, arg4=spellID
-- every other UNIT_SPELLCAST_*: arg1=unit, arg2=castGUID, arg3=spellID
local unit = arg1
if unit == "player" then
if event == "UNIT_SPELLCAST_SENT" then
sentTargets[arg3] = arg2 ~= "" and UnitName(arg2) or nil
elseif event == "UNIT_SPELLCAST_START" then
OnOwnCastStart(arg3, arg2)
elseif event == "UNIT_SPELLCAST_SUCCEEDED" then
OnOwnCastSucceeded(arg3, arg2)
elseif event == "UNIT_SPELLCAST_FAILED" or event == "UNIT_SPELLCAST_INTERRUPTED" then
sentTargets[arg2] = nil
-- a refused cast (cooldown, range) fails under its own castGUID and
-- must not cancel the heal that is still casting
if arg2 == this.castGUID then onCastFailed() end
elseif event == "UNIT_SPELLCAST_DELAYED" then
-- delayTimeMs is the pushback so far; HealComm wants each step
local _, _, _, _, _, _, _, _, _, _, delayMs = C_Spell.CastingInfo()
if this.healing and delayMs and delayMs > this.delayMs then
local step = delayMs - this.delayMs
this.delayMs = delayMs
libpredict:HealDelay(player, step)
this:SendHealCommMsg("Healdelay/" .. step .. "/")
foreignCache[casterName] = foreignCache[casterName] or {}
local existing = foreignCache[casterName][spellKey]
-- Store highest non-crit value for best prediction accuracy
if isCrit then
-- Estimate base from crit (vanilla crit = 150%)
local base = math.floor(amount * 2 / 3)
if not existing or base > existing then
foreignCache[casterName][spellKey] = base
end
else
if not existing or amount > existing then
foreignCache[casterName][spellKey] = amount
end
end
return
end
if UnitIsUnit(unit, "player") or lastOtherCast[event] == arg2 then return end
lastOtherCast[event] = arg2
elseif event == "SPELL_FAILED_SELF" then
onCastFailed()
elseif event == "SPELL_DELAYED_SELF" then
-- arg1=casterGuid, arg2=delayMs
if libpredict.sender.healing then
libpredict:HealDelay(player, arg2)
libpredict.sender:SendHealCommMsg("Healdelay/" .. arg2 .. "/")
end
if event == "UNIT_SPELLCAST_START" then
OnOtherCastStart(unit, arg3, arg2)
elseif event == "UNIT_SPELLCAST_SUCCEEDED" then
OnOtherCastSucceeded(unit, arg3, arg2)
elseif event == "UNIT_SPELLCAST_INTERRUPTED" then
OnOtherCastInterrupted(unit)
elseif event == "UNIT_SPELLCAST_STOP" then
castTargets[arg2] = nil
end
end)
function libpredict:GetHotDuration(unit, spell)
if unit == UNKNOWNOBJECT or unit == UKNOWNBEING then return end
-- Live aura timer first (C_UnitAuras via libdebuff)
if pfUI.api.libdebuff then
-- NEW: Try libdebuff first (Nampower AURA_CAST events)
if pfUI.api.libdebuff and pfUI.api.libdebuff.GetBestAuraCast then
local guid = UnitGUID(unit)
if guid then
-- Get the best (highest rank) aura cast for this spell
local spellName = spell
-- Map short spell codes to full names
@@ -1205,6 +1243,7 @@ function libpredict:GetHotDuration(unit, spell)
local start, duration, timeleft, rank, casterGuid = pfUI.api.libdebuff:GetBestAuraCast(guid, spellName)
if start and duration and timeleft then
-- SUCCESS: libdebuff has accurate server-side data!
if libpredict.debug then
DEFAULT_CHAT_FRAME:AddMessage(string.format("|cff00ff00[GetHotDuration]|r %s on %s via libdebuff: dur=%.1fs timeleft=%.1fs rank=%d",
spell, unit, duration, timeleft, rank or 0))
@@ -1214,7 +1253,7 @@ function libpredict:GetHotDuration(unit, spell)
end
end
-- FALLBACK: the HealComm/cast-fed table, for HoTs C_UnitAuras has no timer for
-- FALLBACK: Use old prediction system (for non-Nampower clients or no AURA_CAST data)
local start, duration, timeleft
local now = pfUI.uf.now or GetTime()
@@ -1248,11 +1287,16 @@ _G.SlashCmdList.HOTDEBUG = function()
DEFAULT_CHAT_FRAME:AddMessage("|cff00ffff[HoT Tracking Debug]|r")
DEFAULT_CHAT_FRAME:AddMessage("|cff00ffff========================================|r")
if pfUI.api.libdebuff then
-- Check libdebuff availability
local libdebuff_now = (pfUI.api.libdebuff and pfUI.api.libdebuff.GetBestAuraCast) and true or false
if libdebuff_now then
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00[PRIMARY]|r libdebuff integration: ACTIVE")
DEFAULT_CHAT_FRAME:AddMessage(" Using C_UnitAuras timers")
DEFAULT_CHAT_FRAME:AddMessage(" Using AURA_CAST events for server-side tracking")
DEFAULT_CHAT_FRAME:AddMessage(" Rank protection: ENABLED")
else
DEFAULT_CHAT_FRAME:AddMessage("|cffff9900[PRIMARY]|r libdebuff integration: NOT AVAILABLE")
DEFAULT_CHAT_FRAME:AddMessage(" Reason: Nampower not enabled or libdebuff outdated")
end
DEFAULT_CHAT_FRAME:AddMessage("|cff00ffff[FALLBACK]|r Legacy prediction system: ACTIVE")
+124 -120
View File
@@ -81,9 +81,6 @@ pfUI:RegisterModule("actionbar", function ()
["ACTIONBAR_UPDATE_COOLDOWN"] = true,
["ACTIONBAR_UPDATE_USABLE"] = true,
["ACTIONBAR_UPDATE_STATE"] = true,
-- fires on every ammo shot / consumable use, but only counts, equipped
-- state, cooldowns and usability can change from it
["UNIT_INVENTORY_CHANGED"] = true,
}
-- events that are shared across all buttons
@@ -100,6 +97,8 @@ pfUI:RegisterModule("actionbar", function ()
["TRADE_SKILL_CLOSE"] = true,
["PLAYER_ENTER_COMBAT"] = true,
["PLAYER_LEAVE_COMBAT"] = true,
-- cooldown updates
["UNIT_INVENTORY_CHANGED"] = true,
-- auto repeat action
["START_AUTOREPEAT_SPELL"] = true,
["STOP_AUTOREPEAT_SPELL"] = true,
@@ -318,7 +317,7 @@ pfUI:RegisterModule("actionbar", function ()
-- handle button highlight
if keystate == "down" then
self.highlight:Show()
elseif not MouseIsOverHitRect(self) then
elseif not MouseIsOver(self) then
self.highlight:Hide()
end
end
@@ -361,6 +360,68 @@ pfUI:RegisterModule("actionbar", function ()
end
end
local function ButtonMacroScan(self)
if self.bar > 10 then return end
if not self.scanmacro then return end
if pfUI.bars.skip_macro then return end
-- SuperCleveRoidMacros: for macros it manages, leave spellslot/booktype unset
-- so the button's icon, cooldown, and tooltip flow through the hooked
-- GetActionTexture / GetActionCooldown / GameTooltip:SetAction and follow the
-- 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
return
end
local kind, slot = GetActionInfo(self.id)
self.spellslot, self.booktype, self.spellID = nil, nil, nil
if kind == 'macro' then
local name, _, body = GetMacroInfo(slot)
if name and body then
local match
for line in gfind(body, "[^%\n]+") do
_, _, match = string.find(line, '^#showtooltip (.+)')
-- allow the user to disable the scan
if match and strfind(match, "disable") then
return
end
if not match then
-- add support to specify custom tooltips via:
-- /run --showtooltip SPELLNAME
_, _, match = string.find(line, '%-%-showtooltip (.+)')
end
if not match then
_, _, match = string.find(line, '^/cast (.+)')
end
if not match then
_, _, match = string.find(line, '^/pfcast (.+)')
end
if not match then
_, _, match = string.find(line, '^/pfmouse (.+)')
end
if not match then
_, _, match = string.find(line, 'CastSpellByName%(%"(.+)%"%)')
end
if match then
self.spellslot, self.booktype, self.spellID = select(7, libspell.GetSpellInfo(match))
if self.spellslot and self.spellslot > 0 then return end
end
end
end
end
end
local function ButtonEnter(self)
self = self or this
@@ -401,28 +462,6 @@ pfUI:RegisterModule("actionbar", function ()
GameTooltip:Hide()
end
-- item counts and equipped border, the parts of a slot an inventory change touches
local function ButtonInventoryUpdate(self)
if self.bar == 11 or self.bar == 12 then return end
local sid = self.id
-- update consumables
if IsConsumableAction(sid) then
self.count:SetText(GetActionCount(sid))
elseif IsReagentAction and IsReagentAction(sid) then
self.count:SetText(GetReagentCount(sid))
else
self.count:SetText(nil)
end
-- equipped item
if IsEquippedAction(sid) and C.bars.showequipped == "1" then
self.equipped:Show()
else
self.equipped:Hide()
end
end
local start, duration, enable, castable, autocast, token
local grid, sid, id, bar, active, texture, _
local function ButtonSlotUpdate(self)
@@ -483,9 +522,23 @@ pfUI:RegisterModule("actionbar", function ()
self.active:Hide()
end
ButtonInventoryUpdate(self)
if self.bar ~= 11 and self.bar ~= 12 then
-- update consumables
if IsConsumableAction(sid) then
self.count:SetText(GetActionCount(sid))
elseif IsReagentAction and IsReagentAction(sid) then
self.count:SetText(GetReagentCount(sid))
else
self.count:SetText(nil)
end
-- equipped item
if IsEquippedAction(sid) and C.bars.showequipped == "1" then
self.equipped:Show()
else
self.equipped:Hide()
end
-- update macro text
if C.bars["bar"..self.bar] and C.bars["bar"..self.bar].showmacro == "1" then
self.macro:SetText(GetActionText(sid))
@@ -637,6 +690,7 @@ pfUI:RegisterModule("actionbar", function ()
local function ButtonFullUpdate(button)
if not button then return end
ButtonMacroScan(button)
ButtonSlotUpdate(button)
ButtonRangeUpdate(button)
ButtonUsableUpdate(button)
@@ -705,29 +759,11 @@ pfUI:RegisterModule("actionbar", function ()
end
end
-- The cached passes below only touch visible buttons. Hidden ones (other
-- pages, hidden bars) are flagged stale and get a full update once the
-- range pass sees them visible again.
-- run cached inventory changes (ammo, reagents, consumables)
if eventcache["UNIT_INVENTORY_CHANGED"] then
eventcache["UNIT_INVENTORY_CHANGED"] = nil
for id, button in pairs(buttoncache) do
if button:IsVisible() then
ButtonInventoryUpdate(button)
ButtonCooldownUpdate(button)
ButtonUsableUpdate(button)
else
button.stale = true
end
end
end
-- run cached usable usable actions
if eventcache["ACTIONBAR_UPDATE_USABLE"] then
eventcache["ACTIONBAR_UPDATE_USABLE"] = nil
for id, button in pairs(buttoncache) do
if button:IsVisible() then ButtonUsableUpdate(button) else button.stale = true end
ButtonUsableUpdate(button)
end
end
@@ -735,7 +771,7 @@ pfUI:RegisterModule("actionbar", function ()
if eventcache["ACTIONBAR_UPDATE_COOLDOWN"] then
eventcache["ACTIONBAR_UPDATE_COOLDOWN"] = nil
for id, button in pairs(buttoncache) do
if button:IsVisible() then ButtonCooldownUpdate(button) else button.stale = true end
ButtonCooldownUpdate(button)
end
end
@@ -743,7 +779,7 @@ pfUI:RegisterModule("actionbar", function ()
if eventcache["ACTIONBAR_UPDATE_STATE"] then
eventcache["ACTIONBAR_UPDATE_STATE"] = nil
for id, button in pairs(buttoncache) do
if button:IsVisible() then ButtonIsActiveUpdate(button) else button.stale = true end
ButtonIsActiveUpdate(button)
end
end
@@ -765,40 +801,16 @@ pfUI:RegisterModule("actionbar", function ()
if ( this.tick or .2) > GetTime() then return else this.tick = GetTime() + .2 end
for id, button in pairs(buttoncache) do
if button.stale and button:IsVisible() then
-- catch up on updates skipped while hidden
button.stale = nil
ButtonFullUpdate(button)
elseif button:IsShown() then
ButtonRangeUpdate(button)
end
if button:IsShown() then ButtonRangeUpdate(button) end
end
end
-- create the main event and update handler for pfUI actionbars
local bars = CreateFrame("Frame", "pfActionBar", UIParent)
-- The only unit events in the tables above; both concern the player alone.
-- A registration keeps its kind, so these have to go in unit-filtered from
-- the start -- RegisterUnitEvent over a plain registration stays plain.
local event_units = {
["UNIT_INVENTORY_CHANGED"] = "player",
["UNIT_PET"] = "player",
}
local function RegisterBarEvent(event)
local unit = event_units[event]
if unit then
bars:RegisterUnitEvent(event, unit)
else
bars:RegisterEvent(event)
end
end
for event in pairs(special_events) do RegisterBarEvent(event) end
for event in pairs(global_events) do RegisterBarEvent(event) end
for event in pairs(aura_events) do RegisterBarEvent(event) end
for event in pairs(pet_events) do RegisterBarEvent(event) end
for event in pairs(special_events) do bars:RegisterEvent(event) end
for event in pairs(global_events) do bars:RegisterEvent(event) end
for event in pairs(aura_events) do bars:RegisterEvent(event) end
for event in pairs(pet_events) do bars:RegisterEvent(event) end
-- refresh actionbar buttons on event
bars:SetScript("OnEvent", BarsEvent)
@@ -861,28 +873,17 @@ pfUI:RegisterModule("actionbar", function ()
do
local formpage, shift, ctrl, alt, default = 8, 6, 5, 3, 1
-- while a page modifier is held, MOD-key must run the main bar binding on
-- that key so the switched page fires. Overrides do this without touching
-- the saved binding set.
local pagemods = {}
if C.bars.pagemasteralt == "1" then table.insert(pagemods, "ALT") end
if C.bars.pagemasterctrl == "1" then table.insert(pagemods, "CTRL") end
if C.bars.pagemastershift == "1" then table.insert(pagemods, "SHIFT") end
if table.getn(pagemods) > 0 then
local pagemaster = CreateFrame("Frame", "pfPageMaster", UIParent)
pagemaster:RegisterEvent("PLAYER_ENTERING_WORLD")
pagemaster:RegisterEvent("UPDATE_BINDINGS")
pagemaster:SetScript("OnEvent", function()
ClearOverrideBindings(this)
for i = 1, 12 do
for _, key in ipairs({ GetBindingKey("ACTIONBUTTON"..i) }) do
-- skip keys that already carry a modifier, e.g. "SHIFT-Q"
if not key:find("^%u+%-.") then
for _, mod in ipairs(pagemods) do
SetOverrideBinding(this, false, mod.."-"..key, "ACTIONBUTTON"..i)
end
end
-- set temporary pagemaster bindings keybinds
if C.bars.pagemaster == "1" then
local modifier = { "ALT", "SHIFT", "CTRL" }
local buttons = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "+", "=", "´" }
local current = CURRENT_ACTIONBAR_PAGE
bars.pagemaster = bars.pagemaster or CreateFrame("Frame", "pfPageMaster", UIParent)
bars.pagemaster:RegisterEvent("PLAYER_ENTERING_WORLD")
bars.pagemaster:SetScript("OnEvent", function()
for _,mod in pairs(modifier) do
for _,but in pairs(buttons) do
SetBinding(mod.."-"..but)
end
end
end)
@@ -897,10 +898,6 @@ pfUI:RegisterModule("actionbar", function ()
pageswitch:RegisterEvent("UPDATE_SHAPESHIFT_FORM")
pageswitch:RegisterEvent("PLAYER_AURAS_CHANGED")
pageswitch:RegisterEvent("PLAYER_LOGOUT")
local PROWL_IDS = { [5215] = true, [6783] = true, [9913] = true }
if class == "DRUID" then
pageswitch:RegisterUnitEvent("UNIT_SPELLCAST_SUCCEEDED", "player")
end
pageswitch:SetScript("OnEvent", function()
if event == "PLAYER_LOGOUT" then
this:UnregisterAllEvents()
@@ -923,18 +920,6 @@ pfUI:RegisterModule("actionbar", function ()
return
end
-- Eager prowl detection on the cast itself — zero-latency vs.
-- PLAYER_AURAS_CHANGED, which can land a frame or two behind it.
-- arg3 = spellID
if event == "UNIT_SPELLCAST_SUCCEEDED" then
if PROWL_IDS[arg3] then
inCatForm = true
prowlActive = true
formpaging = true
end
return
end
if event == "UPDATE_SHAPESHIFT_FORM" then
-- Authoritative form-change signal — recompute and short-circuit.
inCatForm = HasCatForm()
@@ -958,6 +943,20 @@ pfUI:RegisterModule("actionbar", function ()
end
end)
-- Eager prowl detection via Nampower SPELL_GO_SELF — zero-latency vs.
-- PLAYER_AURAS_CHANGED, which can land a frame or two behind the cast.
-- Form changes are no longer hooked here; UPDATE_SHAPESHIFT_FORM covers
-- those without needing a per-spellID watchlist.
local PROWL_IDS = { [5215] = true, [6783] = true, [9913] = true }
pfUI.libdebuff_spell_go_hooks = pfUI.libdebuff_spell_go_hooks or {}
pfUI.libdebuff_spell_go_hooks["actionbar_prowl"] = function(spellId)
if class ~= "DRUID" then return end
if PROWL_IDS[spellId] then
inCatForm = true
prowlActive = true
formpaging = true
end
end
pageswitch:SetScript("OnUpdate", function()
-- switch actionbar page depending on meta key that is pressed
if C.bars.pagemastershift == "1" and IsShiftKeyDown() then
@@ -1017,7 +1016,7 @@ pfUI:RegisterModule("actionbar", function ()
local id = (bar-1)*12+button
local exists = _G[button_name] and true or nil
local f = _G[button_name] or CreateFrame("Button", button_name, parent)
local f = _G[button_name] or CreateFrame("Button", button_name, parent, ACTIONBAR_SECURE_TEMPLATE_BUTTON)
-- no button available, create a new one
if not exists then
@@ -1043,7 +1042,7 @@ pfUI:RegisterModule("actionbar", function ()
f.slot = id
-- cooldown
f.cd = CreateFrame("Cooldown", f:GetName() .. "Cooldown", f, "CooldownFrameTemplate")
f.cd = CreateFrame(COOLDOWN_FRAME_TYPE, f:GetName() .. "Cooldown", f, "CooldownFrameTemplate")
f.cd.pfCooldownStyleAnimation = 1
f.cd.pfCooldownType = "NOGCD"
f.cd.pfCooldownSize = cd_size
@@ -1149,7 +1148,12 @@ pfUI:RegisterModule("actionbar", function ()
f.count:SetJustifyH("RIGHT")
f.count:SetJustifyV("BOTTOM")
f.scanmacro, f.spellslot, f.booktype = nil, nil, nil
-- macro spell scan (disabled when macro addons are loaded)
if C.bars.macroscan == "0" or pfUI:MacroAddonsLoaded() then
f.scanmacro, f.spellslot, f.booktype = nil, nil, nil
else
f.scanmacro = true
end
-- range glow color
f.rangeColor = GetStringColorObject(C.bars.rangecolor)
@@ -1229,7 +1233,7 @@ pfUI:RegisterModule("actionbar", function ()
-- create frame
local init = not bars[i]
bars[i] = bars[i] or CreateFrame("Frame", "pfActionBar" .. barnames[i], UIParent)
bars[i] = bars[i] or CreateFrame("Frame", "pfActionBar" .. barnames[i], UIParent, ACTIONBAR_SECURE_TEMPLATE_BAR)
bars[i]:SetID(i)
-- autohide
+3 -4
View File
@@ -399,16 +399,15 @@ pfUI:RegisterModule("addonbuttons", function ()
-- Initial setup on the next frame, once other addons' minimap buttons exist
RunNextFrame(function()
-- rescan before deciding whether the panel should be shown by default
pfUI.addonbuttons:ProcessButtons()
-- check if the panel should be shown by default
if C.abuttons.showdefault == "1" and GetNumButtons() > 0 then
pfUI.addonbuttons:Show()
else
pfUI.addonbuttons:Hide()
end
-- apply workarounds
-- update all buttons and apply workarounds
pfUI.addonbuttons:ProcessButtons()
for k, v in pairs(pfUI.addonbuttons.overrides) do
_G[k] = v
end
+1
View File
@@ -9,6 +9,7 @@ pfUI:RegisterModule("addons", function ()
pfUI.addons:Show()
HideUIPanel(GameMenuFrame)
end)
SkinButton(pfUIAddonButton)
local point, relativeTo, relativePoint, xOffset, yOffset = GameMenuButtonOptions:GetPoint()
GameMenuButtonOptions:SetPoint(point, relativeTo, relativePoint, xOffset, yOffset - 22)
+4 -3
View File
@@ -396,13 +396,14 @@ pfUI:RegisterModule("bags", function ()
if tpl == "BankItemButtonGenericTemplate" then
local bankslot = pfUI.bags[bag].slots[slot].frame
local name = "pfBag" .. bag .. "item" .. slot .. "Cooldown"
bankslot.cd = CreateFrame("Cooldown", name, bankslot, "CooldownFrameTemplate")
bankslot.cd = CreateFrame(COOLDOWN_FRAME_TYPE, name, bankslot, "CooldownFrameTemplate")
bankslot.cd:SetAllPoints(bankslot)
bankslot.cd.pfCooldownStyleAnimation = 1
bankslot.cd.pfCooldownType = "NOGCD"
bankslot.cd.pfCooldownType = "ALL"
else
local bagslot = pfUI.bags[bag].slots[slot].frame
bagslot.cd = _G[bagslot:GetName().."Cooldown"]
bagslot.cd.pfCooldownType = "NOGCD"
bagslot.cd.pfCooldownType = "ALL"
end
if not pfUI.bags[bag].slots[slot].frame.backdrop then
+3 -18
View File
@@ -7,19 +7,10 @@ pfUI:RegisterModule("buff", function ()
local br, bg, bb, ba = GetStringColor(pfUI_config.appearance.border.color)
-- Player aura slot ids, enumerated once per refresh pass (ScanPlayerAuraSlots)
-- and read per button by its aura index: one GetAuraSlots walk per range
-- instead of a by-index walk per button.
local helpfulSlots, harmfulSlots = {}, {}
local function ScanPlayerAuraSlots()
ScanAuraSlots("player", "HELPFUL", helpfulSlots, 32)
ScanAuraSlots("player", "HARMFUL", harmfulSlots, 16)
end
local function RefreshBuffButton(buff)
if buff.btype == "HELPFUL" then
if C.buffs.separateweapons == "1" then
buff.id = buff.gid - (buff.weapon ~= nil and buff.gid or 0)
buff.id = buff.gid - (buff.weapon ~= nill and buff.gid or 0)
else
buff.id = buff.gid - pfUI.buff.wepbuffs.count
end
@@ -32,8 +23,7 @@ pfUI:RegisterModule("buff", function ()
CreateBackdropShadow(buff)
end
local slots = buff.btype == "HELPFUL" and helpfulSlots or harmfulSlots
local name, icon, count, dispelType, _, expirationTime, _, _, _, spellId = C_UnitAuras.UnitAuraBySlot("player", slots[buff.id])
local name, icon, count, dispelType, _, expirationTime, _, _, _, spellId = C_UnitAuras.UnitAura("player", buff.id, buff.btype)
--detect weapon buffs
if buff.btype == "HELPFUL" and ((C.buffs.separateweapons == "0" and buff.gid <= pfUI.buff.wepbuffs.count) or (pfUI.buff.wepbuffs.count > 0 and buff.weapon ~= nil)) then
@@ -164,7 +154,7 @@ pfUI:RegisterModule("buff", function ()
pfUI.buff = CreateFrame("Frame", "pfGlobalBuffFrame", UIParent)
pfUI.buff:RegisterEvent("PLAYER_AURAS_CHANGED")
pfUI.buff:RegisterEvent("PLAYER_EQUIPMENT_CHANGED")
pfUI.buff:RegisterUnitEvent("UNIT_MODEL_CHANGED", "player")
pfUI.buff:RegisterEvent("UNIT_MODEL_CHANGED")
pfUI.buff:RegisterEvent("BUFF_UPDATE_DURATION_SELF")
pfUI.buff:RegisterEvent("DEBUFF_UPDATE_DURATION_SELF")
pfUI.buff:SetScript("OnEvent", function()
@@ -176,8 +166,6 @@ pfUI:RegisterModule("buff", function ()
pfUI.buff.wepbuffs.count = 0
end
ScanPlayerAuraSlots()
for i=1,32 do
RefreshBuffButton(pfUI.buff.buffs.buttons[i])
end
@@ -258,9 +246,6 @@ pfUI:RegisterModule("buff", function ()
end
end)
-- CreateBuffButton refreshes each new button from the slot buffers
ScanPlayerAuraSlots()
-- Weapon Buffs
pfUI.buff.wepbuffs = CreateFrame("Frame", "pfWepBuffFrame", UIParent)
pfUI.buff.wepbuffs.count = 0
+29 -16
View File
@@ -73,12 +73,9 @@ pfUI:RegisterModule("buffwatch", function ()
return anchor
end
-- reusable GetAuraSlots buffer, filled once per RefreshBuffBarFrame
local auraSlots = {}
-- Reads one aura by the slot id GetAuraSlots returned (nil slot -> nil).
local function GetBuffData(unit, slot)
local name, icon, count, dispelType, _, expirationTime = C_UnitAuras.UnitAuraBySlot(unit, slot)
local function GetBuffData(unit, id, type, selfdebuff)
local filter = (selfdebuff and type == "HARMFUL") and "HARMFUL|PLAYER" or type
local name, icon, count, dispelType, _, expirationTime = C_UnitAuras.UnitAura(unit, id, filter)
if not name then return end
local remaining = expirationTime > 0 and (expirationTime - GetTime()) or 0
return remaining, icon, name, count, dispelType
@@ -112,9 +109,31 @@ pfUI:RegisterModule("buffwatch", function ()
local function StatusBarOnEnter()
GameTooltip:SetOwner(this, "NONE")
-- C_UnitAuras lists the player's own auras first, so the selfdebuff
-- (HARMFUL|PLAYER) list is a prefix of the HARMFUL list: same index.
GameTooltip:SetUnitAura(this.unit, this.id, this.type)
if this.unit == "player" then
GameTooltip:SetUnitAura("player", this.id, this.type)
elseif this.type == "HARMFUL" then
-- selfdebuff filters the displayed list to player-cast harmful auras, but
-- SetUnitAura's index has to be into the engine's full HARMFUL list. Look
-- up the displayed aura via the PLAYER filter, then scan engine slots for
-- one whose name + sourceGUID match.
local config = this.parent and this.parent.config
if config and config.selfdebuff == "1" then
local ownAura = C_UnitAuras.GetAuraDataByIndex(this.unit, this.id, "HARMFUL|PLAYER")
if ownAura then
for gameSlot = 1, 16 do
local check = C_UnitAuras.GetDebuffDataByIndex(this.unit, gameSlot)
if check and check.name == ownAura.name and check.sourceGUID == ownAura.sourceGUID then
GameTooltip:SetUnitAura(this.unit, gameSlot, "HARMFUL")
break
end
end
end
else
GameTooltip:SetUnitAura(this.unit, this.id, "HARMFUL")
end
elseif this.type == "HELPFUL" then
GameTooltip:SetUnitAura(this.unit, this.id, "HELPFUL")
end
if IsShiftKeyDown() then
GameTooltip:AddLine(" ")
@@ -215,15 +234,9 @@ pfUI:RegisterModule("buffwatch", function ()
local function RefreshBuffBarFrame(frame)
-- reinitialize all active buffs
local selfdebuff = frame.config.selfdebuff == "1"
local filter = (selfdebuff and frame.type == "HARMFUL") and "HARMFUL|PLAYER" or frame.type
-- one GetAuraSlots enumeration per refresh instead of a by-index walk per
-- bar; the i-th slot is the i-th aura of the filtered list, so `i` stays
-- the index the tooltip / cancel handlers pass to the by-index API
ScanAuraSlots(frame.unit, filter, auraSlots, 32)
for i=1,32 do
local timeleft, texture, name, stacks, dtype = GetBuffData(frame.unit, auraSlots[i])
local timeleft, texture, name, stacks, dtype = GetBuffData(frame.unit, i, frame.type, selfdebuff)
timeleft = timeleft or 0
if texture and name and name ~= "" and BuffIsVisible(frame.config, name) then
+23 -45
View File
@@ -36,7 +36,7 @@ pfUI:RegisterModule("castbar", function ()
cb.bar:SetValue(1)
end
if cb.bar.spark then cb.bar.spark:Hide() end
if cb:GetAlpha() > 0 and not cb.fade:IsPlaying() then cb.fade:Play() end
cb.fadeout = 1
end
-- "Spellname (N)" label for an active tradeskill merge — N is the number
@@ -86,7 +86,7 @@ pfUI:RegisterModule("castbar", function ()
-- Stamp the bar with cast data and render text/icon/lag once. OnUpdate
-- then animates the fill from this state without touching C_Spell.
local function StampBar(cb, name, tex, startMs, endMs, spellID, isChannel, delayMs, isTradeskill, rank, noInterrupt)
local function StampBar(cb, name, tex, startMs, endMs, spellID, isChannel, delayMs, isTradeskill, rank)
cb.startTime = startMs
cb.endTime = endMs
cb.isChannel = isChannel
@@ -94,13 +94,10 @@ pfUI:RegisterModule("castbar", function ()
cb.activeName = name
cb.isTradeskill = isTradeskill
cb.delay = (delayMs or 0) / 1000
cb.fade:Stop()
cb:SetAlpha(1)
cb.fadeout = nil
-- Shield an uninterruptible cast. Not on the player's own bar: the flag is
-- measured against the reader's own interrupts and silences, so on your own
-- cast it answers a question nobody asked.
SetCastbarShield(cb.bar, cb.icon, noInterrupt and cb.unitstr ~= "player", isChannel)
cb.bar:SetStatusBarColor(GetStringColor(C.appearance.castbar[isChannel and "channelcolor" or "castbarcolor"]))
-- Rank: prefer the value the UNIT_SPELLCAST_* event delivered (arg5, passed
-- through by RefreshBar). Only the retarget re-poll has no event in hand, so
@@ -155,10 +152,10 @@ pfUI:RegisterModule("castbar", function ()
ClearBar(cb)
return
end
local name, _, tex, startMs, endMs, isTradeskill, _, noInterrupt, spellID, _, delayMs = C_Spell.UnitCastingInfo(query)
local name, _, tex, startMs, endMs, isTradeskill, _, _, spellID, _, delayMs = C_Spell.UnitCastingInfo(query)
local isChan
if not name then
name, _, tex, startMs, endMs, _, noInterrupt, spellID = C_Spell.UnitChannelInfo(query)
name, _, tex, startMs, endMs, _, _, spellID = C_Spell.UnitChannelInfo(query)
isChan = true
end
-- Synthetic fallback for abilities the engine treats as instant-cast but
@@ -172,7 +169,7 @@ pfUI:RegisterModule("castbar", function ()
end
end
if name and startMs and endMs then
StampBar(cb, name, tex, startMs, endMs, spellID, isChan, delayMs, isTradeskill, rank, noInterrupt)
StampBar(cb, name, tex, startMs, endMs, spellID, isChan, delayMs, isTradeskill, rank)
else
ClearBar(cb)
end
@@ -248,14 +245,8 @@ pfUI:RegisterModule("castbar", function ()
cb.bar.spark:SetPoint("BOTTOM", cb.bar, "BOTTOMLEFT", 0, 0)
cb.bar.spark:Hide()
-- Fade-out after a cast ends. The bar stays at alpha 0 while idle.
cb.fade = cb:CreateAnimationGroup()
local fade = cb.fade:CreateAnimation("Alpha")
fade:SetChange(-1)
fade:SetDuration(.4)
cb.fade:SetScript("OnFinished", function() cb:SetAlpha(0) end)
-- OnUpdate animates the bar fill. All state transitions (cast start/stop/interrupt, channel start/stop,
-- OnUpdate animates the bar fill and fades it out on completion. All
-- state transitions (cast start/stop/interrupt, channel start/stop,
-- pushback) come from the event handler below — we never poll C_Spell
-- here.
cb:SetScript("OnUpdate", function()
@@ -263,12 +254,15 @@ pfUI:RegisterModule("castbar", function ()
this.tick = GetTime() + 0.020 -- ~50 FPS
if this.drag and this.drag:IsShown() then
this.fade:Stop()
this:SetAlpha(1)
return
end
if this.fade:IsPlaying() then return end
if this.fadeout and this:GetAlpha() > 0 then
this:SetAlpha(this:GetAlpha() - 0.05)
if this:GetAlpha() <= 0 then this.fadeout = nil end
return
end
if not this.endTime then
if this:GetAlpha() ~= 0 then this:SetAlpha(0) end
@@ -328,32 +322,20 @@ pfUI:RegisterModule("castbar", function ()
-- casts only ever fire arg1=="player" -- when the bar's unit resolves to the
-- player (target=self). PLAYER_TARGET/FOCUS_CHANGED re-polls so a unit
-- already mid-cast when it becomes the target/focus still shows.
-- Filter to this bar's unit, plus "player" for the target/focus bars: the
-- player's own casts only ever fire arg1=="player", so a self-targeted cast
-- has to reach them too (nil for the player bar itself, which the filter
-- skips). This only narrows what arrives -- the arg1/UnitIsUnit test below
-- still decides whether the bar acts on it.
local selfunit = unitstr ~= "player" and "player" or nil
cb:RegisterUnitEvent("UNIT_SPELLCAST_START", unitstr, selfunit)
cb:RegisterUnitEvent("UNIT_SPELLCAST_STOP", unitstr, selfunit)
cb:RegisterUnitEvent("UNIT_SPELLCAST_FAILED", unitstr, selfunit)
cb:RegisterUnitEvent("UNIT_SPELLCAST_INTERRUPTED", unitstr, selfunit)
cb:RegisterUnitEvent("UNIT_SPELLCAST_DELAYED", unitstr, selfunit)
cb:RegisterUnitEvent("UNIT_SPELLCAST_SUCCEEDED", unitstr, selfunit)
cb:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_START", unitstr, selfunit)
cb:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_STOP", unitstr, selfunit)
cb:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_UPDATE", unitstr, selfunit)
cb:RegisterEvent("UNIT_SPELLCAST_START")
cb:RegisterEvent("UNIT_SPELLCAST_STOP")
cb:RegisterEvent("UNIT_SPELLCAST_FAILED")
cb:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED")
cb:RegisterEvent("UNIT_SPELLCAST_DELAYED")
cb:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED")
cb:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START")
cb:RegisterEvent("UNIT_SPELLCAST_CHANNEL_STOP")
cb:RegisterEvent("UNIT_SPELLCAST_CHANNEL_UPDATE")
if unitstr == "target" then
cb:RegisterEvent("PLAYER_TARGET_CHANGED")
elseif unitstr == "focus" then
cb:RegisterEvent("PLAYER_FOCUS_CHANGED")
end
-- The shield can flip mid-cast (the caster gains or loses an aura that
-- blocks interrupts). These only fire for target and focus.
if unitstr == "target" or unitstr == "focus" then
cb:RegisterUnitEvent("UNIT_SPELLCAST_INTERRUPTIBLE", unitstr)
cb:RegisterUnitEvent("UNIT_SPELLCAST_NOT_INTERRUPTIBLE", unitstr)
end
cb:SetScript("OnEvent", function()
local unit = this.unitstr
@@ -409,10 +391,6 @@ pfUI:RegisterModule("castbar", function ()
if not this.tradeskillTotal then ClearBar(this) end
elseif event == "UNIT_SPELLCAST_FAILED" or event == "UNIT_SPELLCAST_INTERRUPTED" then
ClearBar(this, true)
elseif event == "UNIT_SPELLCAST_INTERRUPTIBLE" or event == "UNIT_SPELLCAST_NOT_INTERRUPTIBLE" then
if this.endTime then
SetCastbarShield(this.bar, this.icon, event == "UNIT_SPELLCAST_NOT_INTERRUPTIBLE", this.isChannel)
end
elseif event == "UNIT_SPELLCAST_DELAYED" or event == "UNIT_SPELLCAST_CHANNEL_UPDATE" then
-- Pushback: a cast delayed later or a channel shortened. Following
-- Quartz, re-poll just the times and accumulate the shift into a running
+78 -143
View File
@@ -63,23 +63,6 @@ pfUI:RegisterModule("chat", function ()
return pfUI_cache["chathistory"][realm][player][id]
end
-- [ Chat Panel Colors ]
-- The panels normally inherit pfUI's global appearance theme; custom colors let chat
-- deviate from it. CreateBackdrop is re-run first to restore the theme, so toggling
-- custom colors back off doesn't leave the previous override behind.
local function ApplyPanelColors(panel)
if not panel then return end
CreateBackdrop(panel, default_border, nil, .8)
if C.chat.global.custombg ~= "1" then return end
local r, g, b, a = GetStringColor(C.chat.global.background)
panel.backdrop:SetBackdropColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
local r, g, b, a = GetStringColor(C.chat.global.border)
panel.backdrop:SetBackdropBorderColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
end
pfUI.chat = CreateFrame("Frame",nil,UIParent)
pfUI.chat.left = CreateFrame("Frame", "pfChatLeft", UIParent)
@@ -93,11 +76,19 @@ pfUI:RegisterModule("chat", function ()
pfUI.chat.left:SetPoint("BOTTOMLEFT", 2*default_border,2*default_border)
pfUI.chat.left:SetScript("OnShow", function() pfUI.chat:RefreshChat() end)
UpdateMovable(pfUI.chat.left)
ApplyPanelColors(pfUI.chat.left)
CreateBackdrop(pfUI.chat.left, default_border, nil, .8)
if C.chat.global.frameshadow == "1" then
CreateBackdropShadow(pfUI.chat.left)
end
if C.chat.global.custombg == "1" then
local r, g, b, a = GetStringColor(C.chat.global.background)
pfUI.chat.left.backdrop:SetBackdropColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
local r, g, b, a = GetStringColor(C.chat.global.border)
pfUI.chat.left.backdrop:SetBackdropBorderColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
end
pfUI.chat.left.panelTop = CreateFrame("Frame", "leftChatPanelTop", pfUI.chat.left)
pfUI.chat.left.panelTop:ClearAllPoints()
pfUI.chat.left.panelTop:SetHeight(C.global.font_size+default_border*2)
@@ -260,11 +251,19 @@ pfUI:RegisterModule("chat", function ()
pfUI.chat.right:SetPoint("BOTTOMRIGHT", -2*default_border,2*default_border)
pfUI.chat.right:SetScript("OnShow", function() pfUI.chat:RefreshChat() end)
UpdateMovable(pfUI.chat.right)
ApplyPanelColors(pfUI.chat.right)
CreateBackdrop(pfUI.chat.right, default_border, nil, .8)
if C.chat.global.frameshadow == "1" then
CreateBackdropShadow(pfUI.chat.right)
end
if C.chat.global.custombg == "1" then
local r, g, b, a = GetStringColor(C.chat.global.background)
pfUI.chat.right.backdrop:SetBackdropColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
local r, g, b, a = GetStringColor(C.chat.global.border)
pfUI.chat.right.backdrop:SetBackdropBorderColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
end
pfUI.chat.right.panelTop = CreateFrame("Frame", "rightChatPanelTop", pfUI.chat.right)
pfUI.chat.right.panelTop:ClearAllPoints()
pfUI.chat.right.panelTop:SetHeight(C.global.font_size+default_border*2)
@@ -297,68 +296,6 @@ pfUI:RegisterModule("chat", function ()
end
end
-- [ Chat Background Alpha ]
-- Blizzard's per-window transparency slider drives FCF_SetWindowAlpha, which only
-- touches the ChatFrame*Background textures that pfUI hides on docked frames. Mirror
-- the window's stored alpha onto the visible pfUI backdrop so the native slider
-- controls it directly.
local function ApplyChatBackgroundAlpha(panel, frame)
if not (panel and panel.backdrop and frame) then return end
local _, _, _, _, _, alpha = GetChatWindowInfo(frame:GetID())
alpha = tonumber(alpha)
if not alpha then return end
local r, g, b = panel.backdrop:GetBackdropColor()
panel.backdrop:SetBackdropColor(r, g, b, alpha)
end
function pfUI.chat:RefreshBackgroundAlpha()
-- Custom colors own the alpha. C.chat.global.background carries its own alpha, is
-- set by profiles and is shared with the meter skins, so the native slider must not
-- overwrite it -- doing so reverted the configured opacity on every refresh. The
-- slider only drives the panel when the user hasn't opted into pfUI's chat colors.
if C.chat.global.custombg == "1" then return end
-- left panel follows the currently selected docked tab
local selected = SELECTED_CHAT_FRAME
if not (selected and selected:GetParent() == pfUI.chat.left) then
selected = ChatFrame1
end
ApplyChatBackgroundAlpha(pfUI.chat.left, selected)
if C.chat.right.enable == "1" then
ApplyChatBackgroundAlpha(pfUI.chat.right, ChatFrame3)
end
end
-- live config apply, resolved by the gui as U["chat"]. Lets the color pickers
-- take effect on the spot instead of waiting for a /reload.
function pfUI.chat:UpdateConfig()
ApplyPanelColors(pfUI.chat.left)
ApplyPanelColors(pfUI.chat.right)
pfUI.chat:RefreshBackgroundAlpha()
end
function pfUI.chat:MigrateBackgroundAlpha()
-- Runs once per character. The previous SetupPositions default stored a hard 0 window
-- alpha, which now renders the pfUI backdrop fully transparent. Restore the historical
-- 0.8 look for any pfUI-managed window still sitting at 0. Only existing installs that
-- already ran chat setup carry that legacy 0; fresh installs get 0.8 from SetupPositions.
if pfUI_init.chatbgalpha then return end
if not pfUI_init["chat_position"] then return end
pfUI_init.chatbgalpha = true
local frames = { ChatFrame1, ChatFrame2 }
if C.chat.right.enable == "1" then table.insert(frames, ChatFrame3) end
for _, frame in ipairs(frames) do
local _, _, _, _, _, alpha = GetChatWindowInfo(frame:GetID())
alpha = tonumber(alpha)
if alpha and alpha <= 0 then
FCF_SetWindowAlpha(frame, 0.8)
end
end
end
function pfUI.chat:RefreshChat()
local panelheight = C.global.font_size*1.5 + default_border*2 + 2
@@ -510,13 +447,9 @@ pfUI:RegisterModule("chat", function ()
for index, value in pairs(DOCKED_CHAT_FRAMES) do
FCF_UpdateButtonSide(value)
end
pfUI.chat:RefreshBackgroundAlpha()
end
hooksecurefunc("FCF_SaveDock", pfUI.chat.RefreshChat)
hooksecurefunc("FCF_SetWindowAlpha", function() pfUI.chat:RefreshBackgroundAlpha() end)
hooksecurefunc("FCF_SelectDockFrame", function() pfUI.chat:RefreshBackgroundAlpha() end)
if C.chat.global.tabmouse == "1" then
pfUI.chat.mouseovertab = CreateFrame("Frame")
@@ -526,10 +459,10 @@ pfUI:RegisterModule("chat", function ()
if pfUI.chat.hideLock then return end
if pfUI.chat.left:IsMouseOver(10, -10, -10, 10) then
if MouseIsOver(pfUI.chat.left, 10, -10, -10, 10) then
pfUI.chat.left.panelTop:Show()
FCF_DockUpdate()
elseif pfUI.chat.right:IsMouseOver(10, -10, -10, 10) then
elseif MouseIsOver(pfUI.chat.right, 10, -10, -10, 10) then
-- disable while dock is active
if pfUI.chat.right:GetAlpha() == 0 then return end
@@ -567,7 +500,7 @@ pfUI:RegisterModule("chat", function ()
FCF_SetLocked(ChatFrame1, 1)
FCF_SetWindowName(ChatFrame1, GENERAL)
FCF_SetWindowColor(ChatFrame1, 0, 0, 0)
FCF_SetWindowAlpha(ChatFrame1, 0.8)
FCF_SetWindowAlpha(ChatFrame1, 0)
FCF_SetChatWindowFontSize(ChatFrame1, 12)
ChatFrame1:SetUserPlaced(1)
@@ -575,7 +508,7 @@ pfUI:RegisterModule("chat", function ()
FCF_SetLocked(ChatFrame2, 1)
FCF_SetWindowName(ChatFrame2, COMBAT_LOG)
FCF_SetWindowColor(ChatFrame2, 0, 0, 0)
FCF_SetWindowAlpha(ChatFrame2, 0.8)
FCF_SetWindowAlpha(ChatFrame2, 0)
FCF_SetChatWindowFontSize(ChatFrame2, 12)
ChatFrame2:SetUserPlaced(1)
@@ -585,7 +518,7 @@ pfUI:RegisterModule("chat", function ()
FCF_SetLocked(ChatFrame3, 1)
FCF_SetWindowName(ChatFrame3, T["Loot & Spam"])
FCF_SetWindowColor(ChatFrame3, 0, 0, 0)
FCF_SetWindowAlpha(ChatFrame3, 0.8)
FCF_SetWindowAlpha(ChatFrame3, 0)
FCF_SetChatWindowFontSize(ChatFrame3, 12)
FCF_UnDockFrame(ChatFrame3)
FCF_SetTabPosition(ChatFrame3, 0)
@@ -633,9 +566,6 @@ pfUI:RegisterModule("chat", function ()
end
pfUI.chat:SetScript("OnEvent", function()
-- restore legacy chat windows stuck at 0 alpha before anything reads it
pfUI.chat:MigrateBackgroundAlpha()
-- set the default chat
FCF_SelectDockFrame(SELECTED_CHAT_FRAME)
@@ -782,6 +712,16 @@ pfUI:RegisterModule("chat", function ()
local r,g,b = GetStringColor(C.chat.text.unknowncolor)
local unknowncolorhex = rgbhex(r,g,b)
-- Suppress FriendsFrame's WHO_LIST_UPDATE handling for our DLL-issued
-- name lookups so they don't pop the friends panel. User-issued /who
-- still works because IsWhoQueryPending only reports true for queries
-- issued via C_FriendList.SendWhoQueryByName.
local original_FriendsFrame_OnEvent = FriendsFrame_OnEvent
_G.FriendsFrame_OnEvent = function()
if _G.event == "WHO_LIST_UPDATE" and C_FriendList.IsWhoQueryPending() then return end
return original_FriendsFrame_OnEvent()
end
local function GetPlayerLevel(name)
if not pfUI_playerDB then return nil end
if not pfUI_playerDB[name] then return nil end
@@ -796,10 +736,6 @@ pfUI:RegisterModule("chat", function ()
return frame:HookAddMessage(text, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17)
end
-- payload of the CHAT_MSG event printing this line; nil for lines
-- that don't come from one (system text, addon output, history)
local event, _, sender, _, _, _, _, _, _, _, _, _, guid = GetCurrentChatEvent()
-- Remove prat CLINKs
text = gsub(text, "{CLINK:(%x+):([%d-]-:[%d-]-:[%d-]-:[%d-]-:[%d-]-:[%d-]-:[%d-]-:[%d-]-):([^}]-)}", "|c%1|Hitem:%2|h[%3]|h|r") -- tbc
text = gsub(text, "{CLINK:(%x+):([%d-]-:[%d-]-:[%d-]-:[%d-]-):([^}]-)}", "|c%1|Hitem:%2|h[%3]|h|r") -- vanilla
@@ -815,64 +751,63 @@ pfUI:RegisterModule("chat", function ()
text = pfUI.chat:HandleLink(text)
end
-- display class colors and player levels if already indexed
local classcolor = C.chat.text.classcolor == "1"
local playerlevel = C.chat.text.playerlevel == "1"
if classcolor or playerlevel then
-- the engine name-queries a chat sender before the event fires,
-- so the sender's GUID always resolves; other names in the text
-- fall back to the name-keyed lookups
local _, senderClass, senderRace, senderSex
if classcolor and guid and guid ~= "" then
_, senderClass, _, senderRace, senderSex = GetPlayerInfoByGUID(guid)
end
-- display class colors if already indexed
if C.chat.text.classcolor == "1" then
for name in gfind(text, "|Hplayer:(.-)|h") do
local real, _ = strsplit(":", name)
local color, icon = "", ""
if classcolor then
local class, raceKey, sex
if senderClass and real == sender then
class, raceKey, sex = senderClass, senderRace, senderSex
else
_, class, _, raceKey, sex = C_PlayerCache.GetPlayerInfoByName(real)
end
if not class then
class = GetUnitInfo(real)
end
if class and class ~= UNKNOWN then
local color = unknowncolorhex
local match = false
local _, class = C_PlayerCache.GetPlayerInfoByName(real)
-- local guid = GetCurrentChatGUID()
-- if guid then
-- _, class = GetPlayerInfoByGUID(guid)
-- end
if not class then
class = GetUnitInfo(real)
end
if class then
if class ~= UNKNOWN then
color = "|c" .. PFUI_CLASS_COLORS[class].colorStr
if C.chat.text.factionicons == "1" then
icon = GetFactionIcon(raceKey)
end
if C.chat.text.raceicons == "1" then
icon = icon .. GetRaceIcon(raceKey, sex)
end
elseif C.chat.text.tintunknown == "1" then
color = unknowncolorhex
match = true
end
elseif C.chat.text.whosearchunknown == "1" then
C_FriendList.SendWhoQueryByName(name) -- cooldown + state managed by DLL
end
local suffix = ""
local level = playerlevel and GetPlayerLevel(real)
if level and level > 0 then
suffix = " " .. rgbhex(GetDifficultyColor(level)) .. level .. "|r"
elseif level then
-- Show ?? for unknown levels (e.g. -1 from UnitLevel)
suffix = " |cffff0000??|r"
end
if color ~= "" or suffix ~= "" then
if C.chat.text.tintunknown == "1" or match then
text = string.gsub(text, "|Hplayer:"..name.."|h%["..real.."%]|h(.-:-)",
left..icon..color.."|Hplayer:"..name.."|h" .. color .. real .. "|h|r"..suffix..right.."%1")
left..color.."|Hplayer:"..name.."|h" .. color .. real .. "|h|r"..right.."%1")
end
end
end
-- display player levels if available
if C.chat.text.playerlevel == "1" then
for name in gfind(text, "|Hplayer:(.-)|h") do
local real, _ = strsplit(":", name)
local level = GetPlayerLevel(real)
if level and level > 0 then
local levelcolor = rgbhex(GetDifficultyColor(level))
-- Add level after the player name, before the closing bracket
text = string.gsub(text, "(|Hplayer:" .. name .. "|h.-|h|r)" .. right,
"%1 " .. levelcolor .. level .. "|r" .. right)
elseif level and level <= 0 then
-- Show ?? for unknown levels (e.g. -1 from UnitLevel)
text = string.gsub(text, "(|Hplayer:" .. name .. "|h.-|h|r)" .. right,
"%1 |cffff0000??|r" .. right)
end
end
end
-- reduce channel name to number
if C.chat.text.channelnumonly == "1" and event == "CHAT_MSG_CHANNEL" then
text = string.gsub(text, "%[(%d+)%..-%] ", left .. "%1" .. right .. " ", 1)
if C.chat.text.channelnumonly == "1" then
local channel = string.gsub(text, ".*%[(.-)%]%s+(.*|Hplayer).+", "%1")
if string.find(channel, "%d+%. ") then
channel = string.gsub(channel, "(%d+)%..*", "channel%1")
channel = string.gsub(channel, "channel", "")
text = string.gsub(text, "%[%d+%..-%]%s+(.*|Hplayer)", left .. channel .. right .. " %1")
end
end
-- detect the whisper prefix BEFORE prepending a timestamp; the timestamp
+1 -1
View File
@@ -52,7 +52,7 @@ pfUI:RegisterModule("combopoints", function ()
-- combo
if class == "DRUID" or class == "ROGUE" then
local combo = CreateFrame("Frame")
combo:RegisterUnitEvent("UNIT_COMBO_POINTS", "player")
combo:RegisterEvent("UNIT_COMBO_POINTS")
combo:RegisterEvent("PLAYER_COMBO_POINTS")
combo:RegisterEvent("PLAYER_TARGET_CHANGED")
combo:RegisterEvent("PLAYER_ENTERING_WORLD")
+22 -34
View File
@@ -6,35 +6,30 @@ pfUI:RegisterModule("cooldown", function ()
-- local hourcolor = {strsplit(",", C.appearance.cd.hourcolor)}
-- local daycolor = {strsplit(",", C.appearance.cd.daycolor)}
local parent, parent_name
local function pfCooldownOnUpdate()
-- Throttle FIRST. One of these runs per visible cooldown text, every frame,
-- so anything above this gate is multiplied by the frame rate and by how
-- many cooldowns are ticking.
local now = GetTime()
if (this.tick or 0) > now then return end
this.tick = now + .1
local parent = this:GetParent()
parent = this:GetParent()
if not parent then this:Hide() return end
parent_name = parent:GetName()
-- avoid to set cooldowns on invalid frames. The cooldown frame is stashed
-- at creation: resolving it as _G[parent:GetName() .. "Cooldown"] built and
-- interned that string twice per call, and this is the hottest path in the
-- UI. The stashed reference is also the frame itself rather than a guess
-- from its parent's name, so it holds for cooldowns named anything else.
if this.cooldown and not this.cooldown:IsShown() then
this:Hide()
return
-- avoid to set cooldowns on invalid frames
if parent_name and _G[parent_name .. "Cooldown"] then
if not _G[parent_name .. "Cooldown"]:IsShown() then
this:Hide()
end
end
-- only run every 0.1 seconds from here on
if ( this.tick or .1) > GetTime() then return else this.tick = GetTime() + .1 end
-- fix own alpha value (should be inherited, but somehow isn't always)
if this:GetAlpha() ~= parent:GetAlpha() then
this:SetAlpha(parent:GetAlpha())
end
if this.start < now then
if this.start < GetTime() then
-- calculating remaining time as it should be
local remaining = this.duration - (now - this.start)
local remaining = this.duration - (GetTime() - this.start)
if remaining >= 0 then
this.text:SetText(GetColoredTimeString(remaining))
else
@@ -43,13 +38,13 @@ pfUI:RegisterModule("cooldown", function ()
else
-- I have absolutely no idea, but it works:
-- https://github.com/Stanzilla/WoWUIBugs/issues/47
local currentTime = time()
local startupTime = currentTime - now
local time = time()
local startupTime = time - GetTime()
-- just a simplification of: ((2^32) - (start * 1000)) / 1000
local cdTime = (2 ^ 32) / 1000 - this.start
local cdStartTime = startupTime - cdTime
local cdEndTime = cdStartTime + this.duration
local remaining = cdEndTime - currentTime
local remaining = cdEndTime - time
if remaining >= 0 then
this.text:SetText(GetColoredTimeString(remaining))
@@ -60,16 +55,11 @@ pfUI:RegisterModule("cooldown", function ()
end
local height, size
local textcount = 0
local function pfCreateCoolDown(cooldown, start, duration)
textcount = textcount + 1
local name = cooldown.GetName and cooldown:GetName() or "pfCooldown" .. textcount
cooldown.pfCooldownText = CreateFrame("Frame", name .. "Text", cooldown:GetParent())
cooldown.pfCooldownText.cooldown = cooldown
cooldown.pfCooldownText = CreateFrame("Frame", "pfCooldownFrame", cooldown:GetParent())
cooldown.pfCooldownText:SetAllPoints(cooldown)
cooldown.pfCooldownText:SetFrameLevel(cooldown:GetParent():GetFrameLevel() + 2)
cooldown.pfCooldownText.text = cooldown.pfCooldownText:CreateFontString(name .. "TextString", "OVERLAY")
cooldown.pfCooldownText.text = cooldown.pfCooldownText:CreateFontString("pfCooldownFrameText", "OVERLAY")
if not cooldown.pfCooldownType then
size = tonumber(C.appearance.cd.font_size_foreign)
@@ -104,10 +94,8 @@ pfUI:RegisterModule("cooldown", function ()
return
end
-- realign the indicator Models that stock buttons build; the model is
-- drawn at 36px and needs scaling to its button. A Cooldown draws its
-- sweep to its own rect.
local parent = this.GetParent and not this:IsObjectType("Cooldown") and this:GetParent()
-- realign cooldown frames
local parent = this.GetParent and this:GetParent()
if parent and parent:GetWidth() / 36 > 0 then
this:SetScale(parent:GetWidth() / 36)
this:SetPoint("TOPLEFT", parent, "TOPLEFT", -1, 1)
@@ -146,7 +134,7 @@ pfUI:RegisterModule("cooldown", function ()
end
end
-- hook the SetTimer function that every cooldown, Cooldown frame or
-- stock indicator Model, is driven through.
-- vanilla does not have a cooldown frame type, so we hook the
-- regular SetTimer function that each one is calling.
hooksecurefunc("CooldownFrame_SetTimer", SetCooldown)
end)
+63 -225
View File
@@ -1,82 +1,20 @@
-- One server clock drives every power: Player::RegenerateAll fires every
-- REGEN_TIME_FULL (2s), re-arms with `+=`, and is never reset by casting. The
-- five-second rule (SetLastManaUse on any mana-costing cast) changes what a tick
-- pays, never when it lands; mp5 and the player's MOD_MANA_REGEN_INTERRUPT share
-- still come in. That share can't be computed here -- item sources are equip
-- auras absent from the buff list and m_modManaRegenInterrupt is never sent --
-- so the spark shows it instead: dim through the window until a tick lands.
--
-- The sweep free-runs on that clock and phase-locks to observed gains. A gain
-- mid-sweep (Illumination, Judgement of Wisdom, potions, a Mana Spring totem on
-- its own phase) is not the tick and never moves it.
local FIVE_SECOND_RULE = 5
-- gains farther than this from the predicted boundary are not the tick
local TICK_TOLERANCE = .25
-- arrival jitter; a tick inside this band confirms the sweep rather than
-- re-anchoring it, or the spark hitches at every wrap
local TICK_JITTER = .08
-- Player::RegenerateAll:
-- mod = GetTotalAuraModifier(SPELL_AURA_MOD_ENERGY_REGEN_TIME)
-- if mod > 0 then mod = mod * agility / 10 end
-- m_regenTimer += max(1, REGEN_TIME_FULL - mod) -- milliseconds
local REGEN_TIME_FULL = 2
local ENERGY_REGEN_TIME_AURA = 217 -- SPELL_AURA_MOD_ENERGY_REGEN_TIME
-- fixed magnitude is basePoints + baseDice (stored 11 -> 12); a die above 1 is
-- a roll the client can't know, so it counts as nothing rather than a guess
local amountCache = {}
local function auraAmount(spellID)
local amount = amountCache[spellID]
if amount then return amount end
amount = 0
local effects = C_Spell.GetSpellEffectInfo(spellID) -- nil for an id with no record
if effects then
for i = 1, 3 do
local fx = effects[i]
if fx.auraName == ENERGY_REGEN_TIME_AURA and fx.dieSides <= 1 then
amount = fx.basePoints + fx.baseDice
break
end
end
end
amountCache[spellID] = amount
return amount
end
-- A passive is in effect exactly while known (current rank only, never in the
-- buff list); anything castable or cast on us counts only while it is up.
local function getEnergyRegenTimeMod()
local sum = 0
for _, spellID in ipairs(C_SpellBook.GetPlayerSpellsByAura(ENERGY_REGEN_TIME_AURA)) do
if C_Spell.IsSpellPassive(spellID) then
sum = sum + auraAmount(spellID)
end
end
for i = 1, 32 do
local spellID = select(10, C_UnitAuras.UnitAura("player", i, "HELPFUL"))
if not spellID then break end
sum = sum + auraAmount(spellID)
end
return sum
end
-- cleared on SPELLS_CHANGED (passives) and PLAYER_AURAS_CHANGED (buffs), and
-- recomputed by the next tick that asks. Agility stays live: it's one call.
local energyRegenTimeMod
local function getAdjustedTickTimer()
if not energyRegenTimeMod then
energyRegenTimeMod = getEnergyRegenTimeMod()
end
if energyRegenTimeMod == 0 then return REGEN_TIME_FULL end
local adjustedEnergyTick = 2
-- ms on the server, seconds here; the 1ms floor is the server's and this is a divisor
local reduction = energyRegenTimeMod * UnitStat("player", 2) / 10000
return math.max(0.001, REGEN_TIME_FULL - reduction)
-- Check rogue talents and compute energy tick timing reduction for Combat spec (1.18.0 Blade Rush Talent)
if UnitClassBase("player") == "ROGUE" then
local _, _, _, _, currRank = GetTalentInfo(2, 16)
local bladeRushRank = currRank or 0
if bladeRushRank > 0 then
local agility = UnitStat("player", 2) -- 2 is agility stat index
local reductionPerAgi = 0.0006 * bladeRushRank -- 0.0006 for rank 1, 0.0012 for rank 2
local totalReduction = agility * reductionPerAgi
adjustedEnergyTick = adjustedEnergyTick - totalReduction
end
end
return adjustedEnergyTick
end
pfUI:RegisterModule("energytick", function()
@@ -84,60 +22,14 @@ pfUI:RegisterModule("energytick", function()
return
end
-- inside the module body on purpose: C is on pfUI.env, not _G
local function getBarWidth()
return C.unitframes.player.pwidth ~= "-1" and C.unitframes.player.pwidth or C.unitframes.player.width
end
local rawborder, default_border = GetBorderSize("unitframes")
local cbtexture = pfUI.media[C.appearance.castbar.texture]
-- style is read once: switching it in the GUI needs a /reload, same as the
-- other structural (not cosmetic) toggles in this file
local fsrOverlay = C.unitframes.player.fsrstyle == "1"
-- was this gain the regen tick? if so, re-anchor the sweep on it
local function lockTick(frame)
local now, period = GetTime(), getAdjustedTickTimer()
if frame.start then
-- signed distance to the nearest predicted boundary
local err = mod(now - frame.start, period)
if err > period / 2 then err = err - period end
if math.abs(err) <= TICK_TOLERANCE then
-- correct only what lies beyond normal jitter
if err > TICK_JITTER then
frame.start = frame.start + (err - TICK_JITTER)
elseif err < -TICK_JITTER then
frame.start = frame.start + (err + TICK_JITTER)
end
frame.max, frame.rejected = period, nil
return true
end
-- two rejected gains one period apart are the real clock: relock to it
local periodic = frame.rejected and math.abs(now - frame.rejected - period) <= TICK_TOLERANCE
if not periodic then
frame.rejected = now
return false
end
end
frame.start, frame.max, frame.rejected = now, period, nil
return true
end
local energytick = CreateFrame("Frame", nil, pfUI.uf.player.power.bar)
energytick:SetAllPoints(pfUI.uf.player.power.bar)
energytick:RegisterEvent("PLAYER_ENTERING_WORLD")
energytick:RegisterUnitEvent("UNIT_DISPLAYPOWER", "player")
energytick:RegisterUnitEvent("UNIT_ENERGY", "player")
energytick:RegisterUnitEvent("UNIT_MANA", "player")
energytick:RegisterUnitEvent("UNIT_SPELLCAST_SUCCEEDED", "player")
energytick:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_STOP", "player")
energytick:RegisterEvent("SPELLS_CHANGED")
energytick:RegisterEvent("PLAYER_AURAS_CHANGED")
energytick:RegisterEvent("UNIT_DISPLAYPOWER")
energytick:RegisterEvent("UNIT_ENERGY")
energytick:RegisterEvent("UNIT_MANA")
energytick:RegisterEvent("CHAT_MSG_SPELL_SELF_BUFF")
energytick:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_SELF_BUFFS")
energytick:SetScript("OnEvent", function()
if UnitPowerType("player") == Enum.PowerType.Mana and C.unitframes.player.manatick == "1" then
@@ -150,52 +42,44 @@ pfUI:RegisterModule("energytick", function()
this:Hide()
end
if event == "SPELLS_CHANGED" or event == "PLAYER_AURAS_CHANGED" then
energyRegenTimeMod = nil
-- Filter nur eigene Energy-Gewinne von Talents/Buffs
if event == "CHAT_MSG_SPELL_SELF_BUFF" or event == "CHAT_MSG_SPELL_PERIODIC_SELF_BUFFS" then
if string.find(arg1, "You gain") and string.find(arg1, "Energy from") then
this.ignoreNextGain = true
end
return
end
if event == "PLAYER_ENTERING_WORLD" then
this.lastPower = UnitPower("player")
end
-- the rule arms on the cast (Spell::TakePower: mana powerType, cost > 0),
-- not on a mana drop -- Mana Burn lowers mana without arming it
if event == "UNIT_SPELLCAST_SUCCEEDED" and arg1 == "player" then
local cost = C_Spell.GetSpellPowerCost(arg3)
cost = cost and cost[1]
if cost and cost.type == Enum.PowerType.Mana and cost.cost > 0 then
this.fsrSpell, this.fsrEnd = arg3, GetTime() + FIVE_SECOND_RULE
this.fsrGain = nil
end
return
end
-- Unit::Update won't expire the rule while the spending spell still channels
if event == "UNIT_SPELLCAST_CHANNEL_STOP" and arg1 == "player" then
if this.fsrSpell and this.fsrSpell == arg3 then
this.fsrEnd, this.fsrGain = GetTime() + FIVE_SECOND_RULE, nil
end
return
this.lastMana = UnitPower("player")
end
if (event == "UNIT_MANA" or event == "UNIT_ENERGY") and arg1 == "player" then
local power = UnitPower("player")
local diff = this.lastPower and (power - this.lastPower) or 0
this.lastPower = power
this.currentMana = UnitPower("player")
local diff = 0
if this.lastMana then
diff = this.currentMana - this.lastMana
end
-- only a gain can be the tick; a spend never touches the phase
if diff > 0 and lockTick(this) then
-- a tick inside the window proves regen continues through it
if this.fsrEnd and this.fsrEnd > GetTime() then
this.fsrGain = true
if this.mode == "MANA" and diff < 0 then
this.target = 5
elseif this.mode == "MANA" and diff > 0 then
if UnitPower("player") >= UnitPowerMax("player") then
this.start = nil
this.spark:SetAlpha(0)
this:Hide()
elseif this.max ~= 5 and diff > (this.badtick and this.badtick * 1.2 or 5) then
this.target = 2
else
this.badtick = diff
end
elseif this.mode == "ENERGY" and diff >= 0 then
if not this.ignoreNextGain then
this.target = getAdjustedTickTimer()
end
this.ignoreNextGain = false
end
-- phase is kept while hidden; OnUpdate catches up by whole periods
if this.mode == "MANA" and power >= UnitPowerMax("player") then
this:Hide()
end
this.lastMana = this.currentMana
end
end)
@@ -206,79 +90,37 @@ pfUI:RegisterModule("energytick", function()
end
this.tick = GetTime() + 0.020 -- ~50 FPS
-- five-second rule drains to nothing. The display is cosmetic and can be
-- hidden on its own; the window itself (fsrEnd/fsrGain) keeps running
-- either way -- it still dims the sweep spark below.
local remaining = this.fsrEnd and (this.fsrEnd - GetTime()) or 0
if this.mode == "MANA" and remaining > 0 then
if C.unitframes.player.showfsr == "1" then
if fsrOverlay then
this.fsrbar:SetWidth(getBarWidth() * remaining / FIVE_SECOND_RULE)
else
this.fsrbar:SetValue(remaining)
end
this.fsrbar:Show()
else
this.fsrbar:Hide()
end
else
this.fsrSpell, this.fsrEnd, this.fsrGain = nil, nil, nil
this.fsrbar:Hide()
if this.target then
this.start, this.max = GetTime(), this.target
this.target = nil
this.spark:SetAlpha(1)
this:Show()
end
if not this.start then
this.spark:SetAlpha(0)
return
end
if this.mode == "MANA" and UnitPower("player") >= UnitPowerMax("player") then
this.spark:SetAlpha(0)
return
end
this.current = GetTime() - this.start
-- roll over by whole periods, not from now: restarting bakes frame
-- overshoot into the phase as drift the lock then has to chase
if this.current > this.max then
this.start = this.start + this.max * math.floor(this.current / this.max)
this.max = getAdjustedTickTimer()
this.current = GetTime() - this.start
-- Don't restart tick timer if mana is full
if this.mode == "MANA" and UnitPower("player") >= UnitPowerMax("player") then
this.start = nil
this.spark:SetAlpha(0)
return
end
this.start, this.max, this.current = GetTime(), getAdjustedTickTimer(), 0
end
-- dim while the rule is up and nothing has ticked inside it yet
this.spark:SetAlpha((remaining > 0 and not this.fsrGain) and .4 or 1)
local pos = (C.unitframes.player.pwidth ~= "-1" and C.unitframes.player.pwidth or C.unitframes.player.width)
* (this.current / this.max)
if not C.unitframes.player.pheight then
return
end
local pos = getBarWidth() * (this.current / this.max)
this.spark:SetPoint("LEFT", pos - ((C.unitframes.player.pheight + 5) / 2), 0)
end)
if fsrOverlay then
-- classic style: a translucent shade on top of the power bar's own fill
energytick.fsrbar = energytick:CreateTexture(nil, "ARTWORK")
energytick.fsrbar:SetTexture(1, 1, 1, .15)
energytick.fsrbar:SetPoint("TOPLEFT", 0, 0)
energytick.fsrbar:SetPoint("BOTTOMLEFT", 0, 0)
else
-- a real bar below the power bar, not an overlay on top of it -- the same
-- treatment nameplate castbars get relative to the health bar, so the
-- window can't be mistaken for missing mana
energytick.fsrbar = CreateFrame("StatusBar", nil, pfUI.uf.player)
energytick.fsrbar:SetStatusBarTexture(cbtexture)
energytick.fsrbar:SetMinMaxValues(0, FIVE_SECOND_RULE)
energytick.fsrbar:SetPoint("TOPLEFT", pfUI.uf.player.power.bar, "BOTTOMLEFT", 0, -default_border * 2)
energytick.fsrbar:SetPoint("TOPRIGHT", pfUI.uf.player.power.bar, "BOTTOMRIGHT", 0, -default_border * 2)
energytick.fsrbar:SetHeight(tonumber(C.unitframes.player.fsrheight) or 4)
energytick.fsrbar:SetStatusBarColor(GetStringColor(C.appearance.castbar.fsrcolor))
CreateBackdrop(energytick.fsrbar, default_border)
CreateBackdropShadow(energytick.fsrbar)
end
energytick.fsrbar:Hide()
energytick.spark = energytick:CreateTexture(nil, "OVERLAY")
energytick.spark:SetTexture("Interface\\CastingBar\\UI-CastingBar-Spark")
energytick.spark:SetHeight(C.unitframes.player.pheight + 15)
@@ -289,10 +131,6 @@ pfUI:RegisterModule("energytick", function()
function pfUI.uf.player.UpdateConfig()
energytick.spark:SetHeight(C.unitframes.player.pheight + 15)
energytick.spark:SetWidth(C.unitframes.player.pheight + 5)
if not fsrOverlay then
energytick.fsrbar:SetHeight(tonumber(C.unitframes.player.fsrheight) or 4)
energytick.fsrbar:SetStatusBarColor(GetStringColor(C.appearance.castbar.fsrcolor))
end
hookUpdateConfig(pfUI.uf.player)
end
end)
end)
+1 -20
View File
@@ -130,28 +130,9 @@ pfUI:RegisterModule("eqcompare", function ()
SetTradeTargetItem = GetTradeTargetItemLink
}
-- Guda anchors its item tooltips with ANCHOR_NONE and its own SetPoint, and
-- the tooltip module then moves every ANCHOR_NONE tooltip to its configured
-- spot. Reading the rect inside the Set* call picks the side against the
-- position the tooltip is about to leave, so place the compare a frame later.
local deferred
if C_AddOns.DoesAddOnExist("Guda") then
EventUtil.ContinueOnAddOnLoaded("Guda", function()
deferred = true
end)
end
local function makeHook(getter)
return function(tooltip, arg1, arg2, arg3)
local link = getter(arg1, arg2, arg3)
if not deferred then
return ShowCompareItem(tooltip, link)
end
RunNextFrame(function()
if tooltip:IsShown() then
ShowCompareItem(tooltip, link)
end
end)
ShowCompareItem(tooltip, getter(arg1, arg2, arg3))
end
end
+6 -25
View File
@@ -176,8 +176,8 @@ pfUI:RegisterModule("equipmentmanager", function()
rowMenu:RegisterEvent("GLOBAL_MOUSE_DOWN")
rowMenu:SetScript("OnEvent", function()
if not this:IsShown() then return end
if MouseIsOverHitRect(this) then return end
if this.anchorBtn and MouseIsOverHitRect(this.anchorBtn) then return end
if MouseIsOver(this) then return end
if this.anchorBtn and MouseIsOver(this.anchorBtn) then return end
this:Hide()
end)
tinsert(UISpecialFrames, "pfEqMgrRowMenu") -- Escape closes it
@@ -301,22 +301,12 @@ pfUI:RegisterModule("equipmentmanager", function()
pfUI.equipmentmanager.Refresh()
end)
-- Drag a set onto an action bar. PickupEquipmentSet is newer than
-- the rest of C_EquipmentSet, so dragging stays inert on ClassicAPI
-- builds that don't ship it yet.
if C_EquipmentSet.PickupEquipmentSet then
row:RegisterForDrag("LeftButton")
row:SetScript("OnDragStart", function()
if row.setID then C_EquipmentSet.PickupEquipmentSet(row.setID) end
end)
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
-- MouseIsOverHitRect(row) check covers both. OnLeave on the row and the
-- gear catches every exit path, so no OnUpdate poll is needed.
-- 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 MouseIsOverHitRect(row) then return end
if MouseIsOver(row) then return end
GameTooltip:Hide()
row.gear:Hide()
end
@@ -596,7 +586,6 @@ pfUI:RegisterModule("equipmentmanager", function()
if i <= num then
local location = ordered[i]
b.specialAction = nil; b.bag = nil; b.slot = nil; b.invSlot = nil
local quality
if location == PLACEINBAGS_LOCATION then
b.specialAction = "placeInBags"
b.texture:SetTexture(pfUI.path.."\\img\\UI-GearManager-ItemIntoBag")
@@ -613,23 +602,15 @@ pfUI:RegisterModule("equipmentmanager", function()
local loc = EquipmentManager_GetLocationData(location)
if loc.isBags then
b.bag = loc.bag; b.slot = loc.slot
local tex, count, _, q = GetContainerItemInfo(loc.bag, loc.slot)
quality = q
local tex, count = GetContainerItemInfo(loc.bag, loc.slot)
b.texture:SetTexture(tex)
if count and count > 1 then b.count:SetText(count) else b.count:SetText("") end
else -- isPlayer (equipped in some other slot)
b.invSlot = loc.slot
b.texture:SetTexture(GetInventoryItemTexture("player", loc.slot))
b.count:SetText("")
quality = GetInventoryItemQuality("player", loc.slot)
end
end
-- Quality border, matching the character slots
if quality and quality > 0 then
b.backdrop:SetBackdropBorderColor(GetItemQualityColor(quality))
else
b.backdrop:SetBackdropBorderColor(pfUI.cache.er, pfUI.cache.eg, pfUI.cache.eb, pfUI.cache.ea)
end
local col = math.mod(i - 1, FLYOUT_COLS)
local row = math.floor((i - 1) / FLYOUT_COLS)
b:ClearAllPoints()
+14 -27
View File
@@ -58,11 +58,10 @@ pfUI:RegisterModule("gui", function ()
return
end
local over = MouseIsOverHitRect(this)
if over and not this.over then
if MouseIsOver(this) and not this.over then
this.tex:Show()
this.over = true
elseif not over and this.over then
elseif not MouseIsOver(this) and this.over then
this.tex:Hide()
this.over = nil
end
@@ -2073,6 +2072,9 @@ pfUI:RegisterModule("gui", function ()
CreateConfig(nil, T["Combopoint Width"], C.unitframes, "combowidth")
CreateConfig(nil, T["Combopoint Height"], C.unitframes, "comboheight")
CreateConfig(nil, T["Show Resting"], C.unitframes.player, "showRest", "checkbox")
CreateConfig(nil, T["Enable Energy Ticks"], C.unitframes.player, "energy", "checkbox")
CreateConfig(nil, T["Enable Mana Ticks"], C.unitframes.player, "manatick", "checkbox")
CreateConfig(nil, T["Detect Enemy Buffs"], C.unitframes, "buffdetect", "checkbox", nil, nil, nil, nil)
CreateConfig(nil, T["Raid Mark Icon Style"], C.unitframes, "blizzard_raidicons", "dropdown", function() return {"1:Original Blizzard", "0:pfUI Design"} end)
CreateConfig(nil, T["Swing Timer"], nil, nil, "header")
@@ -2318,15 +2320,6 @@ pfUI:RegisterModule("gui", function ()
CreateConfig(U[c], T["Right Text Y Offset"], C.unitframes[c], "txtpowerrightoffy")
CreateConfig(U[c], T["Power Bar Anchor"], C.unitframes[c], "panchor", "dropdown", pfUI.gui.dropdowns.uf_powerbar_position)
if c == "player" then
CreateConfig(nil, T["Energy/Mana Ticks"], nil, nil, "header")
CreateConfig(nil, T["Enable Energy Ticks"], C.unitframes[c], "energy", "checkbox")
CreateConfig(nil, T["Enable Mana Ticks"], C.unitframes[c], "manatick", "checkbox")
CreateConfig(nil, T["Show Five Second Rule"], C.unitframes[c], "showfsr", "checkbox")
CreateConfig(nil, T["Five Second Rule Style"], C.unitframes[c], "fsrstyle", "dropdown", function() return {"0:Separate Bar", "1:Overlay"} end)
CreateConfig(nil, T["Five Second Rule Height"], C.unitframes[c], "fsrheight")
end
CreateConfig(U[c], T["Combat Text"], nil, nil, "header")
CreateConfig(U[c], T["Show Combat Text"], C.unitframes[c], "hitindicator", "checkbox")
CreateConfig(U[c], T["Combat Text Font"], C.unitframes[c], "hitindicatorfont", "dropdown", pfUI.gui.dropdowns.fonts)
@@ -2347,18 +2340,12 @@ pfUI:RegisterModule("gui", function ()
CreateConfig(U[c], T["Buff Size"], C.unitframes[c], "buffsize")
CreateConfig(U[c], T["Buff Limit"], C.unitframes[c], "bufflimit")
CreateConfig(U[c], T["Buffs Per Row"], C.unitframes[c], "buffperrow")
CreateConfig(U[c], T["Own Buff Size"], C.unitframes[c], "ownbuffsize")
CreateConfig(U[c], T["Buff X-Spacing"], C.unitframes[c], "buffspacingx")
CreateConfig(U[c], T["Buff Y-Spacing"], C.unitframes[c], "buffspacingy")
CreateConfig(U[c], T["Debuffs"], nil, nil, "header")
CreateConfig(U[c], T["Debuff Position"], C.unitframes[c], "debuffs", "dropdown", pfUI.gui.dropdowns.uf_buff_position)
CreateConfig(U[c], T["Debuff Size"], C.unitframes[c], "debuffsize")
CreateConfig(U[c], T["Debuff Limit"], C.unitframes[c], "debufflimit")
CreateConfig(U[c], T["Debuffs Per Row"], C.unitframes[c], "debuffperrow")
CreateConfig(U[c], T["Own Debuff Size"], C.unitframes[c], "owndebuffsize")
CreateConfig(U[c], T["Debuff X-Spacing"], C.unitframes[c], "debuffspacingx")
CreateConfig(U[c], T["Debuff Y-Spacing"], C.unitframes[c], "debuffspacingy")
if c ~= "player" then
end
@@ -2569,6 +2556,9 @@ pfUI:RegisterModule("gui", function ()
CreateConfig(U["bars"], T["Button Animation"], C.bars, "animation", "dropdown", pfUI.gui.dropdowns.actionbuttonanimations)
CreateConfig(U["bars"], T["Button Animation Trigger"], C.bars, "animmode", "dropdown", pfUI.gui.dropdowns.animationmode)
CreateConfig(U["bars"], T["Show Animation On Hidden Bars"], C.bars, "animalways", "checkbox")
if not pfUI:MacroAddonsLoaded() then
CreateConfig(U["bars"], T["Scan Macros For Spells"], C.bars, "macroscan", "checkbox", nil, nil, nil, nil)
end
CreateConfig(U["bars"], T["Show Reagent Count"], C.bars, "reagents", "checkbox")
CreateConfig(U["bars"], T["Highlight Equipped Items"], C.bars, "showequipped", "checkbox")
CreateConfig(U["bars"], T["Equipped Item Color"], C.bars, "eqcolor", "color")
@@ -2805,8 +2795,6 @@ pfUI:RegisterModule("gui", function ()
CreateConfig(nil, T["Use Unit Fonts"], C.castbar, "use_unitfonts", "checkbox")
CreateConfig(nil, T["Casting Color"], C.appearance.castbar, "castbarcolor", "color")
CreateConfig(nil, T["Channeling Color"], C.appearance.castbar, "channelcolor", "color")
CreateConfig(nil, T["Uninterruptible Color"], C.appearance.castbar, "shieldcolor", "color")
CreateConfig(nil, T["Five Second Rule Color"], C.appearance.castbar, "fsrcolor", "color")
CreateConfig(nil, T["Castbar Texture"], C.appearance.castbar, "texture", "dropdown", pfUI.gui.dropdowns.uf_bartexture)
CreateConfig(nil, T["Disable Blizzard Castbar"], C.castbar.player, "hide_blizz", "checkbox")
end)
@@ -2879,10 +2867,8 @@ pfUI:RegisterModule("gui", function ()
CreateConfig(nil, T["Generate Playerlinks"], C.chat.text, "playerlinks", "checkbox")
CreateConfig(nil, T["Enable URL Detection"], C.chat.text, "detecturl", "checkbox")
CreateConfig(nil, T["Enable Class Colors"], C.chat.text, "classcolor", "checkbox")
local _, playerRace = UnitRace("player")
CreateConfig(nil, T["Show Faction Icons"] .. " " .. GetFactionIcon(playerRace), C.chat.text, "factionicons", "checkbox")
CreateConfig(nil, T["Show Race Icons"] .. " " .. GetRaceIcon(playerRace, UnitSex("player")), C.chat.text, "raceicons", "checkbox")
CreateConfig(nil, T["Enable Player Levels"], C.chat.text, "playerlevel", "checkbox")
CreateConfig(nil, T["Who Search Unknown Classes (|cffffaaaaExperimental|r)"], C.chat.text, "whosearchunknown", "checkbox")
CreateConfig(nil, T["Colorize Unknown Classes"], C.chat.text, "tintunknown", "checkbox")
CreateConfig(nil, T["Unknown Class Color"], C.chat.text, "unknowncolor", "color")
CreateConfig(nil, T["Left Chat Width"], C.chat.left, "width")
@@ -2894,9 +2880,9 @@ pfUI:RegisterModule("gui", function ()
CreateConfig(nil, T["Only Show Chat Dock On Mouseover"], C.chat.global, "tabmouse", "checkbox")
CreateConfig(nil, T["Enable Chat Tab Flashing"], C.chat.global, "chatflash", "checkbox")
CreateConfig(nil, T["Enable Frame Shadow"], C.chat.global, "frameshadow", "checkbox")
CreateConfig(U["chat"], T["Enable Custom Colors"], C.chat.global, "custombg", "checkbox")
CreateConfig(U["chat"], T["Chat Background Color"], C.chat.global, "background", "color")
CreateConfig(U["chat"], T["Chat Border Color"], C.chat.global, "border", "color")
CreateConfig(nil, T["Enable Custom Colors"], C.chat.global, "custombg", "checkbox")
CreateConfig(nil, T["Chat Background Color"], C.chat.global, "background", "color")
CreateConfig(nil, T["Chat Border Color"], C.chat.global, "border", "color")
CreateConfig(nil, T["Enable Custom Incoming Whispers Layout"], C.chat.global, "whispermod", "checkbox")
CreateConfig(nil, T["Incoming Whispers Color"], C.chat.global, "whisper", "color")
CreateConfig(nil, T["Enable Sticky Chat"], C.chat.global, "sticky", "checkbox")
@@ -3042,7 +3028,8 @@ pfUI:RegisterModule("gui", function ()
CreateGUIEntry(T["Components"], T["Modules"], function()
table.sort(pfUI.modules)
for i,m in pairs(pfUI.modules) do
if m ~= "gui" then
-- skip gui and macrotweak when macro addons are loaded
if m ~= "gui" and not (m == "macrotweak" and pfUI:MacroAddonsLoaded()) then
-- create disabled entry if not existing and display
pfUI:UpdateConfig("disabled", nil, m, "0")
CreateConfig(nil, T["Disable Module"] .. " " .. m, C.disabled, m, "checkbox")
+5 -21
View File
@@ -105,10 +105,6 @@ pfUI:RegisterModule("hoverbind", function ()
end
end
local function BindingName(command)
return _G["BINDING_NAME_" .. command] or command
end
function pfUI.hoverbind:GetPrefix()
return string.format("%s%s%s",
(IsAltKeyDown() and modifiers.ALT or ""),
@@ -161,29 +157,17 @@ pfUI:RegisterModule("hoverbind", function ()
if arg1 == "ESCAPE" and not binding then pfUI.hoverbind:Hide() return end
if binding then
if arg1 == "ESCAPE" then
-- Remove all existing bindings
local keys = { GetBindingKey(binding) }
for _, key in ipairs(keys) do
-- Remove existing binding
local key = (GetBindingKey(binding))
if (key) then
SetBinding(key)
end
if keys[1] then
SaveBindings(GetCurrentBindingSet())
end
else
-- Create new binding
local key = prefix .. (map and map[arg1] or arg1)
local previous = GetBindingAction(key)
if (SetBinding(key, binding)) then
local key = map and map[arg1] or arg1
if (SetBinding(prefix..key, binding)) then
SaveBindings(GetCurrentBindingSet())
if previous and previous ~= "" and previous ~= binding then
DEFAULT_CHAT_FRAME:AddMessage("|cff33ffcc" .. key .. "|r " .. T["was unbound from"] .. " " .. BindingName(previous))
end
local active = GetBindingAction(key, true)
if active ~= binding then
DEFAULT_CHAT_FRAME:AddMessage("|cff33ffcc" .. key .. "|r " .. T["is currently overridden by"] .. " " .. BindingName(active))
end
end
end
end
+53 -33
View File
@@ -1,13 +1,25 @@
-- Innervate Callout Module
-- Announces Innervate casts via raid/party/battleground chat
-- Listens to COMBAT_LOG_EVENT_UNFILTERED - zero polling, pure event-driven
-- Registers AURA_CAST events directly - zero polling, pure event-driven
pfUI:RegisterModule("innervatecall", function ()
-- Requires Nampower for AURA_CAST events
if not GetNampowerVersion then return end
-- Only load for druids
if UnitClassBase("player") ~= "DRUID" then return end
local INNERVATE_SPELLID = 29166
local GCD_DURATION = 1.5
-- GUID → name resolution for the target. UnitTokenFromGUID walks the
-- engine's known unit tokens (player, party, raid, target, ...) and
-- returns the first one currently bound to the GUID, so we don't have
-- to manually iterate party/raid here.
local function ResolveTargetName(targetGuid)
if not targetGuid then return nil end
local token = UnitTokenFromGUID(targetGuid)
return token and UnitName(token) or nil
end
-- Determine chat channel based on group context
local function GetAnnounceChannel()
@@ -27,49 +39,57 @@ pfUI:RegisterModule("innervatecall", function ()
return nil
end
-- Set on our cast, cleared once the "ready" timer is scheduled
local pending = false
-- Schedule the "ready" announcement once Innervate's own cooldown is in
-- the spell history. Until then GetSpellCooldown reports only the GCD,
-- which has no isOnGCD flag here, so tell them apart by duration.
local function ScheduleReady()
local cd = C_Spell.GetSpellCooldown(INNERVATE_SPELLID)
if not cd or cd.duration <= GCD_DURATION then return end
pending = false
C_Timer.After(cd.startTime + cd.duration - GetTime(), function()
local ch = GetAnnounceChannel()
if ch then
SendChatMessage(">> Innervate is ready <<", ch)
end
end)
end
-- Event frame - registers AURA_CAST directly (bypasses libdebuff hooks
-- which are gated behind ownDebuffs/pendingCasts checks meant for debuffs)
local frame = CreateFrame("Frame")
frame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
frame:RegisterEvent("SPELL_UPDATE_COOLDOWN")
-- AURA_CAST_ON_SELF fires when a buff lands ON the player
-- AURA_CAST_ON_OTHER fires when a buff lands on someone else
-- Both needed: self-innervate = ON_SELF, innervate on others = ON_OTHER
-- casterGuid check prevents announcing other druids' innervates
frame:RegisterEvent("AURA_CAST_ON_SELF")
frame:RegisterEvent("AURA_CAST_ON_OTHER")
frame:RegisterEvent("PLAYER_LOGOUT")
frame:SetScript("OnEvent", function()
if event == "SPELL_UPDATE_COOLDOWN" then
if pending then ScheduleReady() end
if event == "PLAYER_LOGOUT" then
this:UnregisterAllEvents()
this:SetScript("OnEvent", nil)
return
end
-- Innervate is instant, so it logs SPELL_CAST_SUCCESS with its target
-- arg2=subevent, arg4=sourceGUID, arg9=destName, arg12=spellId
if arg2 ~= "SPELL_CAST_SUCCESS" or arg12 ~= INNERVATE_SPELLID then return end
-- AURA_CAST args: arg1=spellId, arg2=casterGuid, arg3=targetGuid
local spellId = arg1
local casterGuid = arg2
local targetGuid = arg3
if spellId ~= INNERVATE_SPELLID then return end
-- Only announce our own casts
if not IsPlayerGuid(arg4) then return end
local destName = arg9
if not IsPlayerGuid(casterGuid) then return end
-- Resolve target name from GUID
local targetName = ResolveTargetName(targetGuid) or "Unknown"
-- Determine channel
local channel = GetAnnounceChannel()
if not channel then return end -- solo, no announcement
SendChatMessage(">> Innervate cast on " .. (destName or "Unknown") .. " <<", channel)
SendChatMessage(">> Innervate casted on " .. targetName .. " <<", channel)
pending = true
ScheduleReady()
-- 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.
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
end
C_Timer.After(cdRemaining, function()
local ch = GetAnnounceChannel()
if ch then
SendChatMessage(">> Innervate is ready <<", ch)
end
end)
end)
end)
+1 -1
View File
@@ -511,7 +511,7 @@ pfUI:RegisterModule("loot", function ()
end
function pfUI.loot:CreateSlot(id)
local frame = CreateFrame("LootButton", 'pfLootButton'..id, pfUI.loot)
local frame = CreateFrame(LOOT_BUTTON_FRAME_TYPE, 'pfLootButton'..id, pfUI.loot)
frame:RegisterForClicks('LeftButtonUp', 'RightButtonUp')
frame:SetPoint("LEFT", border*2, 0)
frame:SetPoint("RIGHT", -border*2, 0)
+17 -6
View File
@@ -104,6 +104,7 @@ pfUI:RegisterModule("loothistory", function ()
-- Frame pools
-- ==========================================================================
local itemFrames = {}
local usedPlayers, freePlayers = {}, {}
local FullUpdate -- forward declaration (toggle handlers call it)
@@ -203,10 +204,20 @@ pfUI:RegisterModule("loothistory", function ()
return f
end
local playerPool = CreateObjectPool(CreatePlayerFrame, function(_, pf)
pf:Hide()
pf:ClearAllPoints()
end)
local function RecycleAllPlayers()
for i = 1, table.getn(usedPlayers) do
local pf = usedPlayers[i]
pf:Hide()
table.insert(freePlayers, pf)
end
usedPlayers = {}
end
local function GetPlayerFrame()
local pf = table.remove(freePlayers) or CreatePlayerFrame()
table.insert(usedPlayers, pf)
return pf
end
local function SetToggleTexture(toggle, isExpanded)
if isExpanded then
@@ -298,7 +309,7 @@ pfUI:RegisterModule("loothistory", function ()
function FullUpdate()
if not pfUI.loothistory:IsShown() then return end
playerPool:ReleaseAll()
RecycleAllPlayers()
local num = C_LootHistory.GetNumItems()
local y = -2
@@ -316,7 +327,7 @@ pfUI:RegisterModule("loothistory", function ()
for p = 1, f.numPlayers do
local name, class, rollType, roll, isWinner, isMe = C_LootHistory.GetPlayerInfo(i, p)
if ShouldDisplayPlayer(f.isDone, roll, isMe) then
local pf = playerPool:Acquire()
local pf = GetPlayerFrame()
RenderPlayerFrame(pf, name, class, rollType, roll, isWinner)
pf:ClearAllPoints()
pf:SetPoint("TOPLEFT", list, "TOPLEFT", 22, y)
+67
View File
@@ -0,0 +1,67 @@
pfUI:RegisterModule("macrotweak", function ()
local conflictAddons = { "Supermacro", "SuperCleveRoidMacros", "UltimaMacros" }
local disabled = false
for _, addon in pairs(conflictAddons) do
local name = addon
EventUtil.ContinueOnAddOnLoaded(name, function()
if not disabled then
DEFAULT_CHAT_FRAME:AddMessage("|cff33ffccpfUI|r: " .. name .. " found, macrotweak disabled.")
end
disabled = true
end)
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
local userinput
ChatFrameEditBox._AddHistoryLine = ChatFrameEditBox.AddHistoryLine
ChatFrameEditBox.AddHistoryLine = function(self, text)
if disabled then return ChatFrameEditBox._AddHistoryLine(self, text) end
if not userinput and text and string.find(text, "^/run(.+)") then return end
if not userinput and string.find(text, "^/script(.+)") then return end
if not userinput and string.find(text, "^/cast(.+)") then return end
ChatFrameEditBox._AddHistoryLine(self, text)
end
local OnEnter = ChatFrameEditBox:GetScript("OnEnterPressed")
ChatFrameEditBox:SetScript("OnEnterPressed", function(a1,a2,a3,a4)
userinput = true
OnEnter(a1,a2,a3,a4)
userinput = nil
end)
end
-- make sure #showtooltip inside macros won't be sent
local hookSendChatMessage = SendChatMessage
function _G.SendChatMessage(msg, ...)
if disabled then return hookSendChatMessage(msg, unpack(arg)) end
if msg and string.find(msg, "^#showtooltip ") then return end
hookSendChatMessage(msg, unpack(arg))
end
-- add /use and /equip to the macro api:
-- https://wowwiki.fandom.com/wiki/Making_a_macro
-- supported arguments:
-- /use <itemname>
-- /use <inventory slot>
-- /use <bag> <slot>
pfUI.api.RegisterSlashCommand("PFUSE", { "/equip" , "/use", "/pfequip", "/pfuse" }, function (msg)
if not msg or msg == "" then return end
local bag, slot, _
if string.find(msg, "%d+%s+%d+") then
_, _, bag, slot = string.find(msg, "(%d+)%s+(%d+)")
elseif string.find(msg, "%d+") then
_, _, slot = string.find(msg, "(%d+)")
else
bag, slot = FindItem(msg)
end
if bag and slot then
UseContainerItem(bag, slot)
elseif not bag and slot then
UseInventoryItem(slot)
end
end)
end)
+1 -1
View File
@@ -180,7 +180,7 @@ pfUI:RegisterModule("map", function ()
my = (( my + height / 2 ) - ( y / scale )) / height * 100
end
if mx and my and WorldMapButton:IsMouseOver() then
if mx and my and MouseIsOver(WorldMapButton) then
WorldMapButton.coords.text:SetFormattedText('%.1f / %.1f', mx, my)
else
WorldMapButton.coords.text:SetText("")
+3 -3
View File
@@ -95,7 +95,7 @@ pfUI:RegisterModule("mapcolors", function ()
local tooltipText = ""
if unit_button_name == 'WorldMap' then
-- check player
if WorldMapPlayer:IsMouseOver() then
if MouseIsOver(WorldMapPlayer) then
ColorizeName(WorldMapPlayer)
tooltipText = WorldMapPlayer.name
end
@@ -104,7 +104,7 @@ pfUI:RegisterModule("mapcolors", function ()
-- check party
for i=1, MAX_PARTY_MEMBERS do
local frame = _G[unit_button_name.."Party"..i]
if frame:IsVisible() and frame:IsMouseOver() then
if frame:IsVisible() and MouseIsOver(frame) then
ColorizeName(frame)
tooltipText = tooltipText.."\n"..frame.name
end
@@ -113,7 +113,7 @@ pfUI:RegisterModule("mapcolors", function ()
--check Raid
for i=1, MAX_RAID_MEMBERS do
local frame = _G[unit_button_name.."Raid"..i]
if frame:IsVisible() and frame:IsMouseOver() then
if frame:IsVisible() and MouseIsOver(frame) then
ColorizeName(frame)
tooltipText = tooltipText.."\n"..frame.name
end
+44 -42
View File
@@ -30,14 +30,20 @@ pfUI:RegisterModule("mapreveal", function ()
pfUI.mapreveal:UpdateConfig()
end)
local explores = {}
local explorecaches = {}
local alreadyknown = {} -- per-zone accumulator: { [zone] = { [texName] = true } }
-- Own texture pool - separate from Blizzard's WorldMapOverlay textures
local overlayPool = CreateTexturePool(WorldMapDetailFrame, "BORDER", nil, nil, function(_, tex)
tex:Hide()
tex:ClearAllPoints()
end)
local pfOverlays = {}
local pfOverlayMax = 0
local function pfGetOverlay(idx)
if not pfOverlays[idx] then
pfOverlays[idx] = WorldMapDetailFrame:CreateTexture("pfReveal"..idx, "BORDER")
end
return pfOverlays[idx]
end
local exploreEnter = function()
WorldMapTooltip:ClearLines()
@@ -63,38 +69,14 @@ pfUI:RegisterModule("mapreveal", function ()
end
end
-- Magnifying-glass icons for unexplored overlays. Everything constant lives
-- in the creator; the update only anchors and labels what it acquires -- and
-- it only acquires the ones it is going to show, where the old table grew an
-- icon for every overlay in the zone and hid most of them again.
local function CreateExplore()
local explore = CreateFrame("Frame", nil, WorldMapDetailFrame)
explore:SetSize(16, 16)
explore:SetScript("OnEnter", exploreEnter)
explore:SetScript("OnLeave", exploreLeave)
explore:EnableMouse(true)
explore:SetFrameLevel(255)
explore.tex = explore:CreateTexture(nil, "OVERLAY")
explore.tex:SetTexture("Interface\\WorldMap\\WorldMap-MagnifyingGlass")
explore.tex:SetBlendMode("ADD")
explore.tex:SetTexCoord(.08, .92, .08, .92)
explore.tex:SetAllPoints()
return explore
end
local explorePool = CreateObjectPool(CreateExplore, function(_, explore)
explore:Hide()
explore:ClearAllPoints()
end)
local function pfWorldMapFrame_Update()
-- clear stale caches
for k in pairs(explorecaches) do explorecaches[k] = nil end
-- hide all our textures from last frame
overlayPool:ReleaseAll()
for i = 1, pfOverlayMax do
pfOverlays[i]:Hide()
end
local r,g,b,a = GetStringColor(C.appearance.worldmap.mapreveal_color)
local mapFileName = GetMapInfo()
@@ -112,13 +94,14 @@ pfUI:RegisterModule("mapreveal", function ()
local zoneKnown = alreadyknown[mapFileName]
-- hide explore icons
explorePool:ReleaseAll()
for _, frame in pairs(explores) do frame:Hide() end
-- ClassicAPI: full overlay list for the viewed zone (explored + unexplored),
-- read straight from WorldMapOverlay.dbc. Replaces the hand-measured pfMapOverlayData.
local zoneData = C_Map.GetMapOverlays() or {}
local textureCount = 0
for _, overlay in ipairs(zoneData) do
for i, overlay in ipairs(zoneData) do
local name = overlay.textureName -- bare, e.g. "DRYGULCHRAVINE"
local textureName = overlay.texturePath -- full engine path (for SetTexture)
local textureWidth = overlay.textureWidth
@@ -126,15 +109,30 @@ pfUI:RegisterModule("mapreveal", function ()
local offsetX = overlay.offsetX
local offsetY = overlay.offsetY
-- explore magnifying glass icon. `alreadyknown` stores the FULL paths
-- GetMapOverlayInfo returns, so compare with the full path, not the bare
-- name.
-- explore magnifying glass icon
explores[i] = explores[i] or CreateFrame("Frame", nil, WorldMapDetailFrame)
local explore = explores[i]
explore:SetWidth(16)
explore:SetHeight(16)
explore:SetPoint("TOPLEFT", "WorldMapDetailFrame", "TOPLEFT", offsetX + textureWidth/2, -offsetY - textureHeight/2)
explore:SetScript("OnEnter", exploreEnter)
explore:SetScript("OnLeave", exploreLeave)
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)
explore.tex:SetAllPoints()
-- `alreadyknown` stores the FULL paths GetMapOverlayInfo returns,
-- so compare with the full path, not the bare name.
if C.appearance.worldmap.mapexploration == "1" and not zoneKnown[string.upper(textureName)] then
local explore = explorePool:Acquire()
explore:SetPoint("TOPLEFT", "WorldMapDetailFrame", "TOPLEFT", offsetX + textureWidth/2, -offsetY - textureHeight/2)
explore.name = mapFileName .. " (" .. name .. ")"
explore.area = name -- cache key: explorecaches is keyed by the plain area name
explore.tex:SetTexture("Interface\\WorldMap\\WorldMap-MagnifyingGlass")
explore:Show()
else
explore:Hide()
end
-- render overlay texture tiles on BORDER draw layer
@@ -149,9 +147,11 @@ pfUI:RegisterModule("mapreveal", function ()
-- exactly what shears quirky overlays (e.g. Icepoint's Kaneq'nuun).
if C.appearance.worldmap.mapreveal == "1" then
for _, tile in ipairs(overlay.tiles) do
local tex = overlayPool:Acquire()
textureCount = textureCount + 1
local tex = pfGetOverlay(textureCount)
tex:SetSize(tile.width, tile.height)
tex:SetWidth(tile.width)
tex:SetHeight(tile.height)
tex:SetTexCoord(0, tile.texCoordX, 0, tile.texCoordY)
tex:ClearAllPoints()
tex:SetPoint("TOPLEFT", "WorldMapDetailFrame", "TOPLEFT", tile.offsetX, -tile.offsetY)
@@ -165,6 +165,8 @@ pfUI:RegisterModule("mapreveal", function ()
end
end
end
pfOverlayMax = math.max(pfOverlayMax, textureCount)
end
-- hook WorldMapFrame_Update
+18 -38
View File
@@ -1,9 +1,12 @@
pfUI:RegisterModule("marktracking", function ()
if not UnitExists("mark1") and not UnitExists("mark8") then
if not pcall(function() UnitExists("mark1") end) then return end
end
local rawborder, border = GetBorderSize()
local markerOrder = { 8, 7, 6, 5, 4, 3, 2, 1 } -- skull, cross, square, moon, triangle, diamond, circle, star
local markerTokens = {} -- [i] = "markN"
local markerIndex = {} -- ["markN"] = i, for the event handler's arg1
local markerTokens = {}
local markerConfigKeys = {
"raidmarkercolor_star",
@@ -19,7 +22,6 @@ pfUI:RegisterModule("marktracking", function ()
local markerColors = {}
for i, markKey in ipairs(markerConfigKeys) do
markerTokens[i] = "mark" .. i
markerIndex[markerTokens[i]] = i
local r, g, b, a = GetStringColor(C.unitframes[markKey])
markerColors[i] = { tonumber(r), tonumber(g), tonumber(b), tonumber(a) }
end
@@ -71,7 +73,8 @@ pfUI.marktracking = CreateFrame("Frame", "pfMarkTracking", UIParent)
else
pfUI.marktracking:SetPoint("TOP", UIParent, "CENTER", 0, 0)
end
pfUI.marktracking:SetSize(TOTAL_ROW_WIDTH, 8 * (ROW_HEIGHT + 1) + border * 2 - 1)
pfUI.marktracking:SetWidth(TOTAL_ROW_WIDTH)
pfUI.marktracking:SetHeight(8 * (ROW_HEIGHT + 1) + border * 2 - 1)
pfUI.marktracking:Hide()
CreateBackdrop(pfUI.marktracking)
@@ -281,50 +284,27 @@ pfUI.marktracking = CreateFrame("Frame", "pfMarkTracking", UIParent)
-- Event-driven scanner frame
local scanner = CreateFrame("Frame")
-- Fallback poll: catches units that come into range AFTER a marker was set
-- (no event fires for that case, so we need this safety net). UpdateDisplay
-- is a full eight-row rebuild, so it exists only while grouped -- raid markers
-- are a group feature and there is nothing to discover alone. The group events
-- below start and cancel it, so outside a group there is no timer queued at
-- all rather than one waking every second to return early.
--
-- Deliberately NOT keyed on a mark being visible: a marker set on a unit that
-- is out of range shows no row, and that is exactly what this poll catches.
local poll
local function UpdatePoll()
local grouped = IsInGroup()
if grouped and not poll then
poll = C_Timer.NewTicker(FALLBACK_INTERVAL, UpdateDisplay)
elseif not grouped and poll then
poll:Cancel()
poll = nil
end
end
-- RAID_TARGET_UPDATE: a raid marker was set/cleared -> full refresh
-- PLAYER_ENTERING_WORLD: login/reload/zone -> full refresh
-- PARTY_MEMBERS_CHANGED/RAID_ROSTER_UPDATE: joined or left a group -> the rows
-- can change, and the fallback poll starts or stops with it
-- UNIT_HEALTH/UNIT_MAXHEALTH: filtered to the eight mark tokens, so arg1 is
-- always "markN" and we refresh just that row (UpdateRow) instead of
-- rescanning all eight.
-- UNIT_HEALTH/UNIT_MAXHEALTH: ClassicAPI fires these per token; with the mark
-- tokens observed they arrive as arg1 == "markN", so we refresh just that
-- one row (UpdateRow) instead of rescanning all eight.
scanner:RegisterEvent("RAID_TARGET_UPDATE")
scanner:RegisterEvent("PLAYER_ENTERING_WORLD")
scanner:RegisterEvent("PARTY_MEMBERS_CHANGED")
scanner:RegisterEvent("RAID_ROSTER_UPDATE")
scanner:RegisterUnitEvent("UNIT_HEALTH", "mark1", "mark2", "mark3", "mark4", "mark5", "mark6", "mark7", "mark8")
scanner:RegisterUnitEvent("UNIT_MAXHEALTH", "mark1", "mark2", "mark3", "mark4", "mark5", "mark6", "mark7", "mark8")
scanner:RegisterEvent("UNIT_HEALTH")
scanner:RegisterEvent("UNIT_MAXHEALTH")
scanner:SetScript("OnEvent", function()
if event == "UNIT_HEALTH" or event == "UNIT_MAXHEALTH" then
-- arg1 is one of the eight tokens we registered for, so this is a lookup
-- rather than a parse -- string.match would allocate a capture and
-- tonumber would parse it, on every health tick of every marked unit.
local i = arg1 and markerIndex[arg1]
-- arg1 is the token; "markN" -> N, nil for any non-mark token.
local i = arg1 and tonumber(string.match(arg1, "^mark(%d)"))
if i then UpdateRow(i) end
return
end
if event ~= "RAID_TARGET_UPDATE" then UpdatePoll() end
UpdateDisplay()
end)
-- Fallback poll: catches units that come into range AFTER a marker was set
-- (no event fires for that case, so we need this safety net)
C_Timer.NewTicker(FALLBACK_INTERVAL, UpdateDisplay)
end)
+16 -18
View File
@@ -109,26 +109,24 @@ pfUI:RegisterModule("minimap", function ()
this.highlight.tex:SetPoint("BOTTOMRIGHT", MiniMapMailIcon, "BOTTOMRIGHT", 2, -2)
this.highlight.tex:SetVertexColor(1,.5,.5)
-- Blink ten times between .9 and .1, then hide
local highlight = this.highlight
highlight:SetAlpha(.9)
highlight.blink = highlight:CreateAnimationGroup()
for i = 1, 10 do
local down = highlight.blink:CreateAnimation("Alpha")
down:SetChange(-.8)
down:SetDuration(.3)
down:SetOrder(i * 2 - 1)
local up = highlight.blink:CreateAnimation("Alpha")
up:SetChange(.8)
up:SetDuration(.3)
up:SetOrder(i * 2)
end
highlight.blink:SetScript("OnFinished", function() highlight:Hide() end)
this.highlight:SetScript("OnUpdate", function()
if not this.count then this.count = 0 end
if not this.modifier then this.modifier = 1 end
if this.count >= 10 then this:Hide() end
this:SetAlpha(this:GetAlpha() + this.modifier)
if this:GetAlpha() <= 0.1 then
this.modifier = 0.05
this.count = this.count + 1
elseif this:GetAlpha() >= 0.9 then
this.modifier = -0.05
end
end)
end
this.highlight.count = 0
this.highlight:Show()
this.highlight.blink:Stop()
this.highlight.blink:Play()
end)
-- Create coordinates text frame with location configurable
@@ -227,7 +225,7 @@ pfUI:RegisterModule("minimap", function ()
pfUI.minimap.pvpicon = CreateFrame("Frame", nil, pfUI.minimap)
pfUI.minimap.pvpicon:Hide()
pfUI.minimap.pvpicon:RegisterEvent("UPDATE_FACTION")
pfUI.minimap.pvpicon:RegisterUnitEvent("UNIT_FACTION", "player")
pfUI.minimap.pvpicon:RegisterEvent("UNIT_FACTION")
pfUI.minimap.pvpicon:SetFrameStrata("HIGH")
pfUI.minimap.pvpicon:SetSize(16, 16)
pfUI.minimap.pvpicon:SetAlpha(.5)
+152 -184
View File
@@ -5,7 +5,10 @@ pfUI:RegisterModule("nameplates", function ()
-- Local function references for performance
local GetTime = GetTime
local UnitName = UnitName
local UnitClass = UnitClass
local UnitLevel = UnitLevel
local UnitIsPlayer = UnitIsPlayer
local UnitIsDead = UnitIsDead
local UnitAffectingCombat = UnitAffectingCombat
local UnitIsUnit = UnitIsUnit
local UnitCanAssist = UnitCanAssist
@@ -77,10 +80,10 @@ pfUI:RegisterModule("nameplates", function ()
-- vs channel itself. Never called per frame.
local function PollCastInfo(unit)
if not unit then return nil end
local name, _, texture, startMs, endMs, _, _, noInterrupt, spellID = C_Spell.UnitCastingInfo(unit)
local name, _, texture, startMs, endMs, _, _, _, spellID = C_Spell.UnitCastingInfo(unit)
local isChannel
if not name then
name, _, texture, startMs, endMs, _, noInterrupt, spellID = C_Spell.UnitChannelInfo(unit)
name, _, texture, startMs, endMs, _, _, spellID = C_Spell.UnitChannelInfo(unit)
isChannel = true
end
if not name or not startMs or not endMs then return nil end
@@ -92,7 +95,6 @@ pfUI:RegisterModule("nameplates", function ()
endTime = endMs / 1000,
duration = (endMs - startMs) / 1000,
isChannel = isChannel,
noInterrupt = noInterrupt,
}
end
@@ -114,7 +116,6 @@ pfUI:RegisterModule("nameplates", function ()
-- Reusable per-plate debuff display buffer (avoid GC churn from per-call table creation)
local debuffDisplayBuf = {} -- [i] = { effect, texture, stacks, dtype, duration, timeleft }
for i = 1, 16 do debuffDisplayBuf[i] = {} end
local auraSlots = {} -- reusable GetAuraSlots buffer for the per-plate aura scan
local threatMemory = {} -- guid -> true if mob had player targeted
-- local debuffSeen = {} -- reusable table for debuff tracking (avoid GC churn)
@@ -160,24 +161,6 @@ pfUI:RegisterModule("nameplates", function ()
cfg.debuffanim = tonumber(C.nameplates.debuffanim) or 0
cfg.debufftext = tonumber(C.nameplates.debufftext) or 1
-- Throttle delays, resolved once instead of per plate per tick.
-- libthrottle:Get walks the saved-variable table, a defaults fallback and a
-- preset table, and can build a "<category>_custom" key -- the per-plate
-- OnUpdate was calling it one or two times for every visible plate, a
-- hundred times a second, just to decide it had nothing to do.
--
-- cfg.throttle_min is the floor across all four. No plate can ever be due
-- sooner than that, so the update can bail on it before working out which
-- category it actually belongs to.
cfg.throttle_target = pfUI.throttle:Get("nameplates_target")
cfg.throttle_mass = pfUI.throttle:Get("nameplates_mass")
cfg.throttle_normal = pfUI.throttle:Get("nameplates")
cfg.throttle_castbar = pfUI.throttle:Get("nameplates_castbar")
cfg.throttle_min = cfg.throttle_target
if cfg.throttle_mass < cfg.throttle_min then cfg.throttle_min = cfg.throttle_mass end
if cfg.throttle_normal < cfg.throttle_min then cfg.throttle_min = cfg.throttle_normal end
if cfg.throttle_castbar < cfg.throttle_min then cfg.throttle_min = cfg.throttle_castbar end
-- Rebuild offtanks lookup table
offtanks = {}
for k, v in pairs({strsplit("#", C.nameplates.combatofftanks)}) do
@@ -280,6 +263,10 @@ pfUI:RegisterModule("nameplates", function ()
return color
end
local function DoNothing()
return
end
local function DisableObject(object)
if not object then return end
if not object.GetObjectType then return end
@@ -309,23 +296,18 @@ pfUI:RegisterModule("nameplates", function ()
return plate.creatureType
end
-- Totem icon: UnitCreatedBySpell returns the totem-drop spell, whose icon IS
-- the totem's icon. It's a broadcast descriptor field the client has for every
-- summoned unit in range, so it resolves immediately for passive and active
-- totems alike -- no self-aura read or attack-cast capture needed.
--
-- Re-read the spell every call rather than caching the icon outright: a shaman
-- can swap the totem in place (same unit, new drop spell) with no plate re-add
-- to invalidate a cache, so key the cached texture on the spell id and refresh
-- it only when the spell changes.
-- 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
local spellId = UnitCreatedBySpell(plate.cachedGuid)
if spellId ~= plate.totemSpell then
plate.totemSpell = spellId
plate.totemIcon = spellId and C_Spell.GetSpellTexture(spellId) or 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
@@ -377,23 +359,16 @@ pfUI:RegisterModule("nameplates", function ()
end
-- UnitSelectionType: 0 hostile, 1 unfriendly, 2 neutral, 3 friendly (or a
-- PvP-flagged player), 4 unflagged player. Type 3 is ambiguous, so callers
-- still promote *_NPC to *_PLAYER from their own player check.
local selectionTypes = {
[0] = "ENEMY_NPC",
[1] = "ENEMY_NPC",
[2] = "NEUTRAL_NPC",
[3] = "FRIENDLY_NPC",
[4] = "FRIENDLY_PLAYER",
}
local function GetUnitType(plate)
-- an unresolved token reads as the player's own type (4), so never ask
-- without a GUID
local guid = plate.cachedGuid
if not guid or guid == "" then return "ENEMY_NPC" end
return selectionTypes[UnitSelectionType(guid)]
local function GetUnitType(red, green, blue)
if red > .9 and green < .2 and blue < .2 then
return "ENEMY_NPC"
elseif red > .9 and green > .9 and blue < .2 then
return "NEUTRAL_NPC"
elseif red < .2 and green < .2 and blue > 0.9 then
return "FRIENDLY_PLAYER"
elseif red < .2 and green > .9 and blue < .2 then
return "FRIENDLY_NPC"
end
end
local filter, list, cache
@@ -440,8 +415,22 @@ pfUI:RegisterModule("nameplates", function ()
plate.debuffs[index].stacks:SetJustifyV("BOTTOM")
plate.debuffs[index].stacks:SetTextColor(1,1,0)
plate.debuffs[index].cd = CreateFrame("Cooldown", plate.platename.."Debuff"..index.."Cooldown", plate.debuffs[index], "CooldownFrameTemplate")
plate.debuffs[index].cd:SetFrameLevel(6)
-- PERF: Use lightweight fake cooldown frame when animation disabled
-- The Model-based CooldownFrameTemplate causes major lag with many nameplates
if cfg.debuffanim ~= 1 then
plate.debuffs[index].cd = CreateFrame("Frame", plate.platename.."Debuff"..index.."Cooldown", plate.debuffs[index])
plate.debuffs[index].cd:SetAllPoints(plate.debuffs[index])
plate.debuffs[index].cd:SetFrameLevel(6)
plate.debuffs[index].cd:SetScript("OnUpdate", CooldownFrame_OnUpdateModel)
plate.debuffs[index].cd.AdvanceTime = DoNothing
plate.debuffs[index].cd.SetSequence = DoNothing
plate.debuffs[index].cd.SetSequenceTime = DoNothing
else
-- Use CooldownFrameTemplate for animation
plate.debuffs[index].cd = CreateFrame(COOLDOWN_FRAME_TYPE, plate.platename.."Debuff"..index.."Cooldown", plate.debuffs[index], "CooldownFrameTemplate")
plate.debuffs[index].cd:SetAllPoints(plate.debuffs[index])
plate.debuffs[index].cd:SetFrameLevel(6)
end
-- Set initial config flags (will be cached per-cooldown later)
plate.debuffs[index].cd.pfCooldownStyleAnimation = cfg.debuffanim
@@ -495,7 +484,7 @@ local nameplates = CreateFrame("Frame", "pfNameplates", UIParent)
nameplates:RegisterEvent("PLAYER_ENTERING_WORLD")
nameplates:RegisterEvent("PLAYER_TARGET_CHANGED")
nameplates:RegisterEvent("PLAYER_LOGOUT")
nameplates:RegisterUnitEvent("UNIT_COMBO_POINTS", "player")
nameplates:RegisterEvent("UNIT_COMBO_POINTS")
nameplates:RegisterEvent("PLAYER_COMBO_POINTS")
nameplates:RegisterEvent("ZONE_CHANGED_NEW_AREA")
nameplates:RegisterEvent("RAID_ROSTER_UPDATE")
@@ -503,15 +492,15 @@ nameplates:RegisterEvent("PARTY_MEMBERS_CHANGED")
nameplates:RegisterEvent("NAME_PLATE_CREATED")
nameplates:RegisterEvent("NAME_PLATE_UNIT_ADDED")
nameplates:RegisterEvent("NAME_PLATE_UNIT_REMOVED")
nameplates:RegisterEvent("UNIT_AURA")
nameplates:RegisterEvent("UNIT_FLAGS")
nameplates:RegisterEvent("UPDATE_MOUSEOVER_UNIT")
-- UNIT_AURA / UNIT_FLAGS / UNIT_SPELLCAST_* are registered per plate, on the
-- plate's own frame, against its own token -- see OnCreate and
-- NAME_PLATE_UNIT_ADDED.
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")
-- The interruptible flips only fire for target and focus, not nameplate
-- tokens. castState is keyed by GUID, so they still reach those units' plates.
nameplates:RegisterUnitEvent("UNIT_SPELLCAST_INTERRUPTIBLE", "target", "focus")
nameplates:RegisterUnitEvent("UNIT_SPELLCAST_NOT_INTERRUPTIBLE", "target", "focus")
nameplates:SetScript("OnEvent", function()
-- Stop event handling during logout to prevent crash 132
@@ -522,15 +511,6 @@ nameplates:RegisterUnitEvent("UNIT_SPELLCAST_NOT_INTERRUPTIBLE", "target", "focu
if nameplates.mouselook then
nameplates.mouselook:SetScript("OnUpdate", nil)
end
-- The plates hold their own unit subscriptions now, so silencing this
-- frame alone would leave them dispatching through logout -- exactly what
-- this branch exists to prevent.
for plate in pairs(registry) do
if plate.nameplate then
plate.nameplate:UnregisterAllEvents()
plate.nameplate:SetScript("OnEvent", nil)
end
end
return
elseif event == "PLAYER_GUILD_UPDATE" and arg1 == 'player' then
@@ -606,19 +586,8 @@ nameplates:RegisterUnitEvent("UNIT_SPELLCAST_NOT_INTERRUPTIBLE", "target", "focu
local guid = UnitGUID(arg1)
plate.nameplate.cachedGuid = guid
plate.nameplate.unit = arg1
-- Point this plate's own subscriptions at the token it just took. On a
-- recycled frame these replace the previous unit rather than stacking:
-- RegisterUnitEvent on an already-filtered registration swaps the units.
plate.nameplate:RegisterUnitEvent("UNIT_AURA", arg1)
plate.nameplate:RegisterUnitEvent("UNIT_FLAGS", arg1)
plate.nameplate:RegisterUnitEvent("UNIT_SPELLCAST_START", arg1)
plate.nameplate:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_START", arg1)
plate.nameplate:RegisterUnitEvent("UNIT_SPELLCAST_STOP", arg1)
plate.nameplate:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_STOP", arg1)
plate.nameplate.creatureType = nil -- recompute for the new unit
plate.nameplate.totemIcon = nil
plate.nameplate.totemSpell = nil
if guid then
plateByGuid[guid] = plate.nameplate
-- Seed: the unit may already be mid-cast (its UNIT_SPELLCAST_START
@@ -646,9 +615,14 @@ nameplates:RegisterUnitEvent("UNIT_SPELLCAST_NOT_INTERRUPTIBLE", "target", "focu
if plate and plate.nameplate and plate.nameplate.cachedGuid == guid then
plate.nameplate.cachedGuid = nil
plate.nameplate.unit = nil
-- Drop the subscriptions with the token: this slot is now free and the
-- next plate to take it would otherwise feed this frame its events.
plate.nameplate:UnregisterAllEvents()
end
end
elseif event == "UNIT_FLAGS" then
if arg1 and strfind(arg1, "^nameplate") then
local plate = C_NamePlate.GetNamePlateForUnit(arg1)
if plate and plate.nameplate then
plate.nameplate.eventcache = true
end
end
@@ -663,6 +637,64 @@ nameplates:RegisterUnitEvent("UNIT_SPELLCAST_NOT_INTERRUPTIBLE", "target", "focu
if pn then pn.eventcache = true end
end
elseif event == "UNIT_SPELLCAST_START" or event == "UNIT_SPELLCAST_CHANNEL_START" then
-- ClassicAPI fires UNIT_SPELLCAST_* per unit token, including the caster's
-- "nameplateN". The payload has no timing, so poll it (PollCastInfo picks
-- cast vs channel) and cache -- only for a unit we have a plate for, so
-- the table stays bounded to on-screen casters.
if arg1 and strfind(arg1, "^nameplate") then
local guid = UnitGUID(arg1)
local plate = guid and plateByGuid[guid]
if plate then
castState[guid] = PollCastInfo(arg1)
if castState[guid] then
plate.castUpdate = true -- bypass the throttle so the bar shows now
end
end
end
elseif event == "UNIT_SPELLCAST_STOP" or event == "UNIT_SPELLCAST_CHANNEL_STOP" then
-- Cast/channel ended (natural, interrupted, or cancelled -- the poll fires
-- STOP for all three). Clear the cached cast and refresh its plate.
if arg1 and strfind(arg1, "^nameplate") then
local guid = UnitGUID(arg1)
if guid and castState[guid] then
castState[guid] = nil
local plate = plateByGuid[guid]
if plate then plate.castUpdate = true end
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
-- fresh C_UnitAuras read next tick instead of waiting on the 0.5s
-- throttle -- covers expirations, dispels, refreshes, and stack changes
-- in one event. Guard on the token prefix (UNIT_AURA also fires for
-- target/party/raid).
if arg1 and strfind(arg1, "^nameplate") then
local plate = C_NamePlate.GetNamePlateForUnit(arg1)
if plate and plate.nameplate then
plate.nameplate.auraUpdate = true
end
end
elseif event == "PLAYER_TARGET_CHANGED" then
frameState.targetGuid = UnitGUID('target')
-- Flag the target's plate for update
@@ -673,17 +705,6 @@ nameplates:RegisterUnitEvent("UNIT_SPELLCAST_NOT_INTERRUPTIBLE", "target", "focu
-- Also propagate to all plates for alpha/strata updates
this.eventcache = true
elseif event == "UNIT_SPELLCAST_INTERRUPTIBLE" or event == "UNIT_SPELLCAST_NOT_INTERRUPTIBLE" then
local guid = UnitGUID(arg1)
local info = guid and castState[guid]
local plate = info and plateByGuid[guid]
if plate and plate.castbar then
info.noInterrupt = event == "UNIT_SPELLCAST_NOT_INTERRUPTIBLE"
-- UpdateCastbar only restyles on a new cast; force it
plate.castbar.lastEndTime = nil
plate.castUpdate = true
end
elseif event == "PLAYER_COMBO_POINTS" or event == "UNIT_COMBO_POINTS" then
-- Only flag the target's plate for combo point update
local plate = C_NamePlate.GetNamePlateForUnit("target")
@@ -755,47 +776,11 @@ nameplates:RegisterUnitEvent("UNIT_SPELLCAST_NOT_INTERRUPTIBLE", "target", "focu
nameplate.cache = {}
nameplate.original = {}
-- Each plate watches its own unit. With RegisterUnitEvent the token IS the
-- subscription, so there is no central listener sifting every unit event in
-- the world for a "^nameplate" prefix and then resolving the plate back out
-- of arg1 -- the event arrives only at the plate it concerns, and `this` is
-- already that plate. NAME_PLATE_UNIT_ADDED points the registration at the
-- new token; _REMOVED drops it, which matters because freed slots are
-- reused and a stale token would feed this frame another unit's events.
nameplate:SetScript("OnEvent", function()
if event == "UNIT_AURA" then
-- a fresh C_UnitAuras read next tick rather than waiting out the 0.5s
-- throttle -- covers expiry, dispels, refreshes and stack changes
this.auraUpdate = true
elseif event == "UNIT_FLAGS" then
this.eventcache = true
elseif event == "UNIT_SPELLCAST_START" or event == "UNIT_SPELLCAST_CHANNEL_START" then
-- the payload carries no timing, so poll it (PollCastInfo picks cast
-- vs channel)
local guid = this.cachedGuid
if guid then
castState[guid] = PollCastInfo(this.unit)
if castState[guid] then
this.castUpdate = true -- bypass the throttle so the bar shows now
end
end
elseif event == "UNIT_SPELLCAST_STOP" or event == "UNIT_SPELLCAST_CHANNEL_STOP" then
-- ended: natural, interrupted or cancelled -- the poll fires STOP for all
local guid = this.cachedGuid
if guid and castState[guid] then
castState[guid] = nil
this.castUpdate = true
end
end
end)
-- create shortcuts for all known elements and disable them
nameplate.original.healthbar, nameplate.original.castbar = parent:GetChildren()
DisableObject(nameplate.original.healthbar)
DisableObject(nameplate.original.castbar)
local NAMEPLATE_OBJECTORDER = { "border", "glow", "name", "level", "levelicon", "raidicon" }
for i, object in pairs({parent:GetRegions()}) do
if NAMEPLATE_OBJECTORDER[i] and NAMEPLATE_OBJECTORDER[i] == "raidicon" then
nameplate[NAMEPLATE_OBJECTORDER[i]] = object
@@ -964,7 +949,6 @@ nameplates:RegisterUnitEvent("UNIT_SPELLCAST_NOT_INTERRUPTIBLE", "target", "focu
nameplate.name:SetJustifyH(nameTextPos)
nameplate.health:SetOrientation(orientation)
nameplate.health:SetRotatesTexture(orientation == "VERTICAL")
-- Bar anchors to the plate directly so the name's JustifyH (configurable
-- below) can shift left/right without dragging the bar with it.
nameplate.health:ClearAllPoints()
@@ -1055,29 +1039,13 @@ nameplates:RegisterUnitEvent("UNIT_SPELLCAST_NOT_INTERRUPTIBLE", "target", "focu
local mouseover = plate.cachedGuid and plate.cachedGuid == frameState.mouseoverGuid or nil
local unitstr = target and "target" or mouseover and "mouseover" or plate.cachedGuid or nil
-- target event sometimes fires too quickly, where nameplate identifiers are not
-- yet updated. So while being inside this event, we cannot trust the unitstr.
if event == "PLAYER_TARGET_CHANGED" then unitstr = nil end
-- remove unitstr when it doesn't resolve to this plate's unit (stale istarget,
-- stale mouseover guid or a despawned unit). Must run before the cache fills
-- below -- an unrelated unitstr would poison cache.player/minion with the
-- *other* unit's answer, and the nil-gate would then never recompute it.
if unitstr and UnitName(unitstr) ~= name then unitstr = nil end
-- resolve player vs npc from plate's own unit so libunitscan can't return
-- a player record for an NPC sharing the same name (e.g. Chromie). Stored
-- as true/false/nil so it doubles as the GetUnitInfo hint.
if plate.cache.player == nil and unitstr then
plate.cache.player = UnitIsPlayer(unitstr) and true or false
end
if plate.cache.minion == nil and unitstr then
plate.cache.minion = UnitIsMinion(unitstr) and true or false
end
local class, ulevel, elite, player, guild
if not plate.cache.minion then
class, ulevel, elite, player, guild = GetUnitInfo(name, true, plate.cache.player)
end
local class, ulevel, elite, player, guild = GetUnitInfo(name, true, plate.cache.player)
if plate.cache.player ~= nil then player = plate.cache.player or nil end
-- Use database level ONLY if current level is ?? (fixes ?? after reload, but doesn't override visible levels)
@@ -1087,7 +1055,8 @@ nameplates:RegisterUnitEvent("UNIT_SPELLCAST_NOT_INTERRUPTIBLE", "target", "focu
levelFromDB = true
end
local unittype = GetUnitType(plate)
local red, green, blue = plate.original.healthbar:GetStatusBarColor()
local unittype = GetUnitType(red, green, blue) or "ENEMY_NPC"
local font_size = C.nameplates.use_unitfonts == "1" and C.global.font_unit_size or C.global.font_size
-- ignore players with npc names if plate level is lower than player level
@@ -1096,11 +1065,18 @@ nameplates:RegisterUnitEvent("UNIT_SPELLCAST_NOT_INTERRUPTIBLE", "target", "focu
if player and unittype == "ENEMY_NPC" then unittype = "ENEMY_PLAYER" end
if player and unittype == "FRIENDLY_NPC" then unittype = "FRIENDLY_PLAYER" end
elite = plate.original.levelicon:IsShown() and not player and "boss" or elite
if not class and not plate.cache.minion then plate.wait_for_scan = true end
if not class then plate.wait_for_scan = true end
-- skip data updates on invisible frames
if not visible then return end
-- target event sometimes fires too quickly, where nameplate identifiers are not
-- yet updated. So while being inside this event, we cannot trust the unitstr.
if event == "PLAYER_TARGET_CHANGED" then unitstr = nil end
-- remove unitstr on unit name mismatch
if unitstr and UnitName(unitstr) ~= name then unitstr = nil end
-- always make sure to keep plate visible
plate:Show()
@@ -1134,7 +1110,7 @@ nameplates:RegisterUnitEvent("UNIT_SPELLCAST_NOT_INTERRUPTIBLE", "target", "focu
local TotemIcon = TotemPlate(plate)
if TotemIcon then
-- icon resolved from the totem-drop spell (already a full path)
-- icon resolved from the totem's aura / attack cast (already a full path)
plate.totem.icon:SetTexture(TotemIcon)
plate.glow:Hide()
@@ -1338,12 +1314,11 @@ nameplates:RegisterUnitEvent("UNIT_SPELLCAST_NOT_INTERRUPTIBLE", "target", "focu
if unitstr then
local filter = cfg.owndebuffs and "HARMFUL|PLAYER" or "HARMFUL"
local now = GetTime()
-- one GetAuraSlots enumeration, then positional by-slot reads straight
-- into the reusable buffer: the per-plate scan allocates nothing (no
-- per-aura table, no result array) and walks the aura array once
local n = ScanAuraSlots(unitstr, filter, auraSlots, 16)
for i = 1, n do
local aname, icon, count, dispelType, duration, expirationTime = C_UnitAuras.UnitAuraBySlot(unitstr, auraSlots[i])
-- positional UnitAura writes straight into the reusable buffer, so the
-- per-plate scan allocates nothing (no per-aura table, no result array)
local i = 1
while debuffCount < 16 do
local aname, icon, count, dispelType, duration, expirationTime = C_UnitAuras.UnitAura(unitstr, i, filter)
if not aname then break end
debuffCount = debuffCount + 1
local b = debuffDisplayBuf[debuffCount]
@@ -1353,6 +1328,7 @@ nameplates:RegisterUnitEvent("UNIT_SPELLCAST_NOT_INTERRUPTIBLE", "target", "focu
b.dtype = dispelType
b.duration = duration
b.timeleft = (expirationTime and expirationTime > 0) and (expirationTime - now) or nil
i = i + 1
end
end
for i = 1, 16 do
@@ -1432,16 +1408,6 @@ nameplates:RegisterUnitEvent("UNIT_SPELLCAST_NOT_INTERRUPTIBLE", "target", "focu
-- cachedGuid is maintained by NAME_PLATE_UNIT_ADDED / _REMOVED events.
-- Cheap gate first. The central loop calls this for every visible plate ~100
-- times a second, and classifying the plate below costs two C calls, a cast
-- lookup and a throttle resolution -- all of it wasted on a plate that is
-- throttled to 10fps. cfg.throttle_min is the floor across every category,
-- so nothing that would have updated can be turned away here; the real
-- category-specific throttle is still applied after the classification.
-- Event flags bypass both gates, as before.
local hasEventUpdate = nameplate.eventcache or nameplate.auraUpdate or nameplate.castUpdate or nameplate.targetUpdate or nameplate.comboUpdate
if not hasEventUpdate and (nameplate.lasttick or 0) + cfg.throttle_min > now then return end
-- PERF: Intelligent throttling based on target/castbar status and plate count
-- Use GUID comparison as primary target detection: instant, immune to alpha transitions,
-- and immediately correct on de-target (unlike istarget which updates one tick later)
@@ -1468,24 +1434,25 @@ nameplates:RegisterUnitEvent("UNIT_SPELLCAST_NOT_INTERRUPTIBLE", "target", "focu
end
end
-- Resolved in CacheConfig, so these are table reads rather than a walk
-- through the saved variables and preset tables.
local throttle
if target then
throttle = cfg.throttle_target
throttle = pfUI.throttle:Get("nameplates_target")
elseif visiblePlateCount > 20 then
throttle = cfg.throttle_mass
throttle = pfUI.throttle:Get("nameplates_mass")
else
throttle = cfg.throttle_normal
throttle = pfUI.throttle:Get("nameplates")
end
-- Non-target plates with active castbar use the castbar throttle
if isCastingNonTarget and cfg.throttle_castbar < throttle then
throttle = cfg.throttle_castbar
if isCastingNonTarget then
local cbThrottle = pfUI.throttle:Get("nameplates_castbar")
if cbThrottle < throttle then throttle = cbThrottle end
end
-- The category-specific gate. hasEventUpdate was read above, before the
-- classification, and still bypasses the throttle.
-- Check for pending event updates (these bypass throttle for immediate response)
local hasEventUpdate = nameplate.eventcache or nameplate.auraUpdate or nameplate.castUpdate or nameplate.targetUpdate or nameplate.comboUpdate
-- Event updates bypass throttle
if not hasEventUpdate and (nameplate.lasttick or 0) + throttle > now then return end
nameplate.lasttick = now
@@ -1584,7 +1551,7 @@ nameplates:RegisterUnitEvent("UNIT_SPELLCAST_NOT_INTERRUPTIBLE", "target", "focu
-- otherwise an NPC sharing a name with a known player flips wait_for_scan
-- off here, then OnDataChanged re-sets it, every frame until the mob scan
-- lands.
if nameplate.wait_for_scan and not nameplate.cache.minion and GetUnitInfo(name, true, nameplate.cache.player) then
if nameplate.wait_for_scan and GetUnitInfo(name, true, nameplate.cache.player) then
nameplate.wait_for_scan = nil
update = true
end
@@ -1687,9 +1654,10 @@ nameplates:RegisterUnitEvent("UNIT_SPELLCAST_NOT_INTERRUPTIBLE", "target", "focu
-- engine framerate, decoupled from central loop). Only update non-target castbars here.
local isTargetPlate = target or nameplate.istarget or (nameplate.health and nameplate.health.zoomed)
if cfg.showcastbar and not cfg.targetcastbar and not isTargetPlate then
local cbThrottle = cfg.throttle_castbar
if visiblePlateCount > 20 and cfg.throttle_mass > cbThrottle then
cbThrottle = cfg.throttle_mass
local cbThrottle = pfUI.throttle:Get("nameplates_castbar")
if visiblePlateCount > 20 then
local massThrottle = pfUI.throttle:Get("nameplates_mass")
if massThrottle > cbThrottle then cbThrottle = massThrottle end
end
if (nameplate.castbar_tick or 0) + cbThrottle <= now then
nameplate.castbar_tick = now
@@ -1756,7 +1724,7 @@ nameplates:RegisterUnitEvent("UNIT_SPELLCAST_NOT_INTERRUPTIBLE", "target", "focu
-- Relative 0..duration range to avoid float precision loss with large
-- absolute timestamps.
nameplate.castbar:SetMinMaxValues(0, duration)
SetCastbarShield(nameplate.castbar, nameplate.castbar.icon, castInfo.noInterrupt, isChannel)
nameplate.castbar:SetStatusBarColor(GetStringColor(C.appearance.castbar[(isChannel and "channelcolor" or "castbarcolor")]))
if castInfo.icon then
nameplate.castbar.icon.tex:SetTexture(castInfo.icon)
nameplate.castbar.icon.tex:SetTexCoord(.1,.9,.1,.9)
+6 -13
View File
@@ -122,7 +122,6 @@ pfUI:RegisterModule("panel", function()
local gcmb = gckb and gckb > 0 and round((gckb or 0)/1000, 2) .. " MB" or UNAVAILABLE
local nin, nout, nping = GetNetStats()
local screenWidth, screenHeight = GetPhysicalScreenSize()
GameTooltip:AddDoubleLine(T["Active Addons"], "|cffffffff" .. active .. "|cff555555 / |cffffffff" .. GetNumAddOns())
GameTooltip:AddLine(" ")
@@ -134,7 +133,7 @@ pfUI:RegisterModule("panel", function()
GameTooltip:AddDoubleLine(T["Network Latency"], "|cffffffff" .. nping .. "ms")
GameTooltip:AddLine(" ")
GameTooltip:AddDoubleLine(T["Graphic Renderer"], "|cffffffff" .. GetCVar("gxApi"))
GameTooltip:AddDoubleLine(T["Screen Resolution"], "|cffffffff" .. screenWidth .. 'x' .. screenHeight)
GameTooltip:AddDoubleLine(T["Screen Resolution"], "|cffffffff" .. GetCVar("gxResolution"))
GameTooltip:AddDoubleLine(T["UI-Scale"], "|cffffffff" .. round(UIParent:GetEffectiveScale(),2))
GameTooltip:Show()
end
@@ -458,7 +457,7 @@ pfUI:RegisterModule("panel", function()
do -- Zone
local widget = CreateFrame("Frame", "pfPanelWidgetZone", UIParent)
for _,event in pairs({"PLAYER_ENTERING_WORLD", "MINIMAP_ZONE_CHANGED"}) do
for _,event in pairs(EVENTS_MINIMAP_ZONE_UPDATE) do
widget:RegisterEvent(event)
end
widget.Tooltip = function()
@@ -487,7 +486,7 @@ pfUI:RegisterModule("panel", function()
do -- Ammo
local widget = CreateFrame("Frame", "pfPanelWidgetAmmo", UIParent)
widget:RegisterEvent("PLAYER_ENTERING_WORLD")
widget:RegisterUnitEvent("UNIT_INVENTORY_CHANGED", "player")
widget:RegisterEvent("UNIT_INVENTORY_CHANGED")
widget:RegisterEvent("BAG_UPDATE_DELAYED")
widget.Tooltip = function()
if GetInventoryItemQuality("player", 0) then
@@ -778,15 +777,9 @@ pfUI:RegisterModule("panel", function()
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]]
for i=1,table.getn(MICRO_BUTTONS) do
local anchor = _G[MICRO_BUTTONS[i-1]] or pfUI.panel.microbutton
local button = _G[MICRO_BUTTONS[i]]
button:ClearAllPoints()
button:SetParent(pfUI.panel.microbutton)
if i == 1 then
+36 -1
View File
@@ -1,4 +1,39 @@
pfUI:RegisterModule("skin", function ()
-- align UIParent panels
pfUI.panelalign = CreateFrame("Frame", "pfUIParentPanelAlign", UIParent)
pfUI.panelalign:SetScript("OnUpdate", function()
-- throttle to 5 updates per second instead of every frame
if (this.tick or 0.2) > GetTime() then return else this.tick = GetTime() + 0.2 end
local left = UIParent.left
local center = UIParent.center
local rbpos, ropos
-- detect outer frame backdrops
if left and not left.rightObj then
left.rightObj = left.backdrop or left
for _, frame in pairs({left:GetChildren()}) do
rbpos = frame.backdrop and frame.backdrop.GetRight and frame.backdrop:GetRight()
ropos = left.rightObj and left.rightObj.GetRight and left.rightObj:GetRight()
if rbpos and ropos and rbpos > ropos then
left.rightObj = frame.backdrop
end
end
end
-- reset anchors
if not left or not left:IsShown() or not center or not center:IsShown() then
UIParent.pfLeftAligned = nil
return
elseif center and center:IsShown() and left and left:IsShown() then
local width = left.rightObj:GetRight()
if width and width ~= UIParent.pfLeftAligned then
center:SetPoint("TOPLEFT", "UIParent", "TOPLEFT", width + 10, -104)
UIParent.pfLeftAligned = width
end
end
end)
-- durability frame
pfUI.durability = CreateFrame("Frame", "pfDurability", UIParent)
if pfUI.minimap then
@@ -44,4 +79,4 @@ pfUI:RegisterModule("skin", function ()
else
UIErrorsFrame:RegisterEvent("UI_ERROR_MESSAGE")
end
end)
end)
+1 -1
View File
@@ -81,7 +81,7 @@ pfUI:RegisterModule("socialmod", function ()
if not info or not info.name or info.name == _G.UNKNOWN then break end
local name = info.name
local friendName = _G["FriendsFrameFriendButton"..i.."ButtonTextName"]
local friendLoc = _G["FriendsFrameFriendButton"..i.."ButtonTextNameLocation"]
local friendLoc = _G["FriendsFrameFriendButton"..i..FRIENDS_NAME_LOCATION]
local friendInfo = _G["FriendsFrameFriendButton"..i.."ButtonTextInfo"]
local caption = friendName or friendLoc
+266 -59
View File
@@ -1,6 +1,10 @@
pfUI:RegisterModule("swingtimer", function ()
local rawborder, border = GetBorderSize()
-- HitInfo flags (EVENTS.md)
local HITINFO_LEFTSWING = 4 -- 4: Off-hand attack
local HITINFO_NOACTION = 65536 -- 65536: server did not advance the swing clock
-- SPELL_QUEUE_EVENT codes (EVENTS.md)
local ON_SWING_QUEUED = 0
local ON_SWING_QUEUE_POPPED = 1
@@ -15,6 +19,8 @@ pfUI:RegisterModule("swingtimer", function ()
lastMhMarkerX = -1, lastOhMarkerX = -1, lastRaMarkerX = -1,
autoAttackActive = false,
inCombat = false,
pendingCastSpellId = nil,
mhFrozenAt = nil,
hsQueued = false, cleaveQueued = false, maulQueued = false,
hsSeenCurrent = false, cleaveSeenCurrent = false, maulSeenCurrent = false,
isWarrior = false,
@@ -25,6 +31,13 @@ pfUI:RegisterModule("swingtimer", function ()
onSwingCache = {},
}
-- Wand "Shoot" runs on the ranged bar but is INDEPENDENT of the melee
-- swing clock — casters melee-weave between mainhand swings and wand
-- fires, both timers tick concurrently. Every other ranged-auto-attack
-- (Hunter Auto Shot, any future auto-repeat ranged spell) replaces MH.
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.
-- Covers Raptor Strike, Maul, Mongoose Bite, Holy Strike, etc. automatically.
local function IsOnSwingSpell(spellId)
@@ -190,19 +203,36 @@ pfUI:RegisterModule("swingtimer", function ()
CreateBackdropShadow(pfUI.swingtimer.offhand)
-- Ranged bar
-- Hunters fill from the center: the bar drains toward the middle during
-- the reload, then the auto shot deadzone grows back out in the warn color.
pfUI.swingtimer.ranged = CreateFrame("StatusBar", "pfSwingTimerRanged", UIParent)
pfUI.swingtimer.ranged = CreateFrame("Frame", "pfSwingTimerRanged", UIParent)
pfUI.swingtimer.ranged:SetPoint("BOTTOM", UIParent, "CENTER", -189, 83)
pfUI.swingtimer.ranged:SetWidth(sw_width)
pfUI.swingtimer.ranged:SetHeight(sw_height)
pfUI.swingtimer.ranged:SetMinMaxValues(0, 1)
pfUI.swingtimer.ranged:SetValue(0)
pfUI.swingtimer.ranged:SetStatusBarTexture(sw_texture)
pfUI.swingtimer.ranged:SetStatusBarColor(raR, raG, raB, raA)
pfUI.swingtimer.ranged:SetFillStyle(isHunter and "CENTER" or "STANDARD")
pfUI.swingtimer.ranged:Hide()
pfUI.swingtimer.ranged.left = pfUI.swingtimer.ranged:CreateTexture(nil, "ARTWORK")
pfUI.swingtimer.ranged.left:SetTexture(sw_texture)
pfUI.swingtimer.ranged.left:SetPoint("RIGHT", pfUI.swingtimer.ranged, "CENTER", 0, 0)
pfUI.swingtimer.ranged.left:SetHeight(sw_height)
pfUI.swingtimer.ranged.left:SetWidth(sw_width / 2)
pfUI.swingtimer.ranged.left:SetTexCoord(0, 0.5, 0, 1)
pfUI.swingtimer.ranged.left:SetVertexColor(raR, raG, raB, raA)
pfUI.swingtimer.ranged.right = pfUI.swingtimer.ranged:CreateTexture(nil, "ARTWORK")
pfUI.swingtimer.ranged.right:SetTexture(sw_texture)
pfUI.swingtimer.ranged.right:SetPoint("LEFT", pfUI.swingtimer.ranged, "CENTER", 0, 0)
pfUI.swingtimer.ranged.right:SetHeight(sw_height)
pfUI.swingtimer.ranged.right:SetWidth(sw_width / 2)
pfUI.swingtimer.ranged.right:SetTexCoord(0.5, 1, 0, 1)
pfUI.swingtimer.ranged.right:SetVertexColor(raR, raG, raB, raA)
pfUI.swingtimer.ranged.warn = pfUI.swingtimer.ranged:CreateTexture(nil, "ARTWORK")
pfUI.swingtimer.ranged.warn:SetTexture(sw_texture)
pfUI.swingtimer.ranged.warn:SetPoint("CENTER", pfUI.swingtimer.ranged, "CENTER", 0, 0)
pfUI.swingtimer.ranged.warn:SetHeight(sw_height)
pfUI.swingtimer.ranged.warn:SetWidth(1)
pfUI.swingtimer.ranged.warn:SetVertexColor(rwR, rwG, rwB, rwA)
pfUI.swingtimer.ranged.warn:Hide()
pfUI.swingtimer.ranged.text = pfUI.swingtimer.ranged:CreateFontString("Status", "DIALOG", "GameFontNormal")
pfUI.swingtimer.ranged.text:SetPoint("CENTER", pfUI.swingtimer.ranged, "CENTER", 0, 0)
pfUI.swingtimer.ranged.text:SetFont(pfUI.font_default, sw_fontsize, "OUTLINE")
@@ -274,41 +304,67 @@ pfUI:RegisterModule("swingtimer", function ()
S.raSpeed = (rs and rs > 0) and rs or 0
end
-- Start* restart a countdown from a PLAYER_SWING reset. `remaining` can be
-- shorter than the weapon speed (parry haste, ranged wind-up), so the bar
-- length stays the full swing and the fill starts part-way along.
local function StartMH(remaining)
-- Reset MH countdown to full speed (server confirmed swing)
local function ResetMH()
if S.mhFrozenAt then
S.mhFrozenAt = nil
end
UpdateWeaponSpeeds()
pfUI.swingtimer.mhGraceAt = nil -- cancel any pending hide
S.mhTimerMax = math.max(S.mhSpeed, remaining)
S.mhTimer = remaining
S.mhTimerMax = S.mhSpeed
S.mhTimer = S.mhSpeed
S.mhActive = true
pfUI.swingtimer.mainhand:Show()
pfUI.swingtimer:Show()
end
local function StartOH(remaining)
-- Reset OH countdown to full speed
local function ResetOH()
UpdateWeaponSpeeds()
-- The off-hand attack time stays set with a shield equipped
if S.ohSpeed <= 0 then return end
pfUI.swingtimer.ohGraceAt = nil -- cancel any pending hide
S.ohTimerMax = math.max(S.ohSpeed, remaining)
S.ohTimer = remaining
S.ohTimerMax = S.ohSpeed
S.ohTimer = S.ohSpeed
S.ohActive = true
if sw_showoh then pfUI.swingtimer.offhand:Show() end
pfUI.swingtimer:Show()
end
local function StartRanged(remaining)
-- Reset ranged countdown. replaceMH=true (Hunter Auto Shot, Throw) stops
-- the melee swing clock while ranged ticks; replaceMH=false (wand Shoot)
-- leaves it running for melee weaving.
local function ResetRanged(replaceMH)
if not sw_showranged then return end
UpdateWeaponSpeeds()
if S.raSpeed <= 0 then return end
S.raTimerMax = math.max(S.raSpeed, remaining)
S.raTimer = remaining
if replaceMH then
S.mhActive = false
pfUI.swingtimer.mainhand:Hide()
end
S.raTimerMax = S.raSpeed
S.raTimer = S.raSpeed
S.raActive = true
pfUI.swingtimer.ranged:SetStatusBarColor(raR, raG, raB, raA)
pfUI.swingtimer.ranged:SetValue(isHunter and 1 or 0)
if isHunter then
pfUI.swingtimer.ranged.left:ClearAllPoints()
pfUI.swingtimer.ranged.left:SetPoint("RIGHT", pfUI.swingtimer.ranged, "CENTER", 0, 0)
pfUI.swingtimer.ranged.left:SetWidth(sw_width / 2)
pfUI.swingtimer.ranged.left:SetTexCoord(0, 0.5, 0, 1)
pfUI.swingtimer.ranged.right:SetWidth(sw_width / 2)
pfUI.swingtimer.ranged.right:SetTexCoord(0.5, 1, 0, 1)
else
pfUI.swingtimer.ranged.left:ClearAllPoints()
pfUI.swingtimer.ranged.left:SetPoint("TOPLEFT", pfUI.swingtimer.ranged, "TOPLEFT", 0, 0)
pfUI.swingtimer.ranged.left:SetWidth(0.1)
pfUI.swingtimer.ranged.left:Hide()
pfUI.swingtimer.ranged.left:SetTexCoord(0, 0, 0, 1)
pfUI.swingtimer.ranged.right:SetWidth(0.1)
pfUI.swingtimer.ranged.right:Hide()
end
pfUI.swingtimer.ranged.left:SetVertexColor(raR, raG, raB, raA)
pfUI.swingtimer.ranged.right:SetVertexColor(raR, raG, raB, raA)
pfUI.swingtimer.ranged.warn:SetWidth(1)
pfUI.swingtimer.ranged.warn:Hide()
pfUI.swingtimer.ranged:Show()
pfUI.swingtimer:Show()
end
@@ -430,17 +486,23 @@ pfUI:RegisterModule("swingtimer", function ()
local anyActive = false
-- Tick timers down. When a timer expires we give a short grace period before
-- hiding the bar, to bridge the 1-2 frame gap until PLAYER_SWING arrives.
-- If PLAYER_SWING arrives during the grace period it resets the timer
-- hiding the bar, to bridge the 1-2 frame gap until AUTO_ATTACK_SELF arrives.
-- If AUTO_ATTACK_SELF arrives during the grace period it resets the timer
-- normally and the grace timer is cleared. If not (e.g. auto-attack was
-- turned off), the bar hides after the grace period ends.
local GRACE = 0.15 -- seconds to wait after timer hits 0 before hiding
if S.mhActive then
S.mhTimer = S.mhTimer - delta
-- Don't tick while frozen (swing-delay spell like Slam is being cast)
if not S.mhFrozenAt then
S.mhTimer = S.mhTimer - delta
end
if S.mhTimer <= 0 then
S.mhTimer = 0
if not pfUI.swingtimer.mhGraceAt then
if S.mhFrozenAt then
-- Swing-delay spell cast in progress: bar holds at 0, skip grace/hide.
-- SPELL_GO handler will unfreeze and add cast duration to timer.
elseif not pfUI.swingtimer.mhGraceAt then
pfUI.swingtimer.mhGraceAt = GetTime() + GRACE
elseif GetTime() >= pfUI.swingtimer.mhGraceAt then
pfUI.swingtimer.mhGraceAt = nil
@@ -579,12 +641,30 @@ pfUI:RegisterModule("swingtimer", function ()
local remaining = S.raTimer
if isHunter then
local DEADZONE = 0.5
local halfW = sw_width / 2
if remaining > DEADZONE then
pfUI.swingtimer.ranged:SetStatusBarColor(raR, raG, raB, raA)
pfUI.swingtimer.ranged:SetValue((remaining - DEADZONE) / (S.raTimerMax - DEADZONE))
local elapsed = S.raTimerMax - remaining
local phase1dur = S.raTimerMax - DEADZONE
local p = elapsed / phase1dur
local w = halfW * (1 - p)
if w < 1 then w = 1 end
pfUI.swingtimer.ranged.left:Show()
pfUI.swingtimer.ranged.left:SetWidth(w)
pfUI.swingtimer.ranged.left:SetTexCoord(0, (1 - p) * 0.5, 0, 1)
pfUI.swingtimer.ranged.left:SetVertexColor(raR, raG, raB, raA)
pfUI.swingtimer.ranged.right:Show()
pfUI.swingtimer.ranged.right:SetWidth(w)
pfUI.swingtimer.ranged.right:SetTexCoord(1 - (1 - p) * 0.5, 1, 0, 1)
pfUI.swingtimer.ranged.right:SetVertexColor(raR, raG, raB, raA)
pfUI.swingtimer.ranged.warn:Hide()
else
pfUI.swingtimer.ranged:SetStatusBarColor(rwR, rwG, rwB, rwA)
pfUI.swingtimer.ranged:SetValue(1 - (remaining / DEADZONE))
local p = 1 - (remaining / DEADZONE)
local w = sw_width * p
if w < 1 then w = 1 end
pfUI.swingtimer.ranged.left:Hide()
pfUI.swingtimer.ranged.right:Hide()
pfUI.swingtimer.ranged.warn:SetWidth(w)
pfUI.swingtimer.ranged.warn:Show()
end
if sw_showtext then
if remaining <= 0.5 then
@@ -594,7 +674,14 @@ pfUI:RegisterModule("swingtimer", function ()
end
end
else
pfUI.swingtimer.ranged:SetValue(1 - (remaining / S.raTimerMax))
local progress = 1 - (remaining / S.raTimerMax)
local w = sw_width * progress
if w < 1 then w = 1 end
pfUI.swingtimer.ranged.left:Show()
pfUI.swingtimer.ranged.left:SetWidth(w)
pfUI.swingtimer.ranged.left:SetTexCoord(0, progress, 0, 1)
pfUI.swingtimer.ranged.right:Hide()
pfUI.swingtimer.ranged.warn:Hide()
if sw_showtext then
pfUI.swingtimer.ranged.text:SetFormattedText("%.1f", math.floor(remaining * 10) / 10)
end
@@ -635,46 +722,164 @@ pfUI:RegisterModule("swingtimer", function ()
end
end)
-- SPELL_START_SELF: fires only for cast-time spells, never instants
local spellStartFrame = CreateFrame("Frame")
spellStartFrame:RegisterEvent("SPELL_START_SELF")
spellStartFrame:SetScript("OnEvent", function()
if not (arg1 and arg1 > 0) then return end
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).
if S.mhActive then
if not C_Spell.ResetsMeleeSwing(arg1) then
S.mhFrozenAt = GetTime()
end
end
end)
-- Cast cancel/fail/interrupt: unfreeze swing timer if a delay spell was interrupted
local spellFailFrame = CreateFrame("Frame")
spellFailFrame:RegisterEvent("SPELLCAST_FAILED")
spellFailFrame:RegisterEvent("SPELLCAST_INTERRUPTED")
spellFailFrame:SetScript("OnEvent", function()
if S.mhFrozenAt then
S.mhFrozenAt = nil
end
S.pendingCastSpellId = nil
end)
-- SPELL_GO hook via libdebuff
pfUI.libdebuff_spell_go_hooks = pfUI.libdebuff_spell_go_hooks or {}
pfUI.libdebuff_spell_go_hooks["swingtimer"] = function(spellId)
-- C_Spell.IsRangedAutoAttackSpell catches both Auto Shot (75) and
-- wand Shoot (5019) via Spell.dbc's AUTO_REPEAT attribute (covers
-- any future auto-repeat ranged spell automatically). Wand is the
-- one independent of the MH swing — everything else replaces it.
-- Throw isn't auto-repeat (single-shot) so it's handled explicitly.
if C_Spell.IsRangedAutoAttackSpell(spellId) then
ResetRanged(spellId ~= WAND_SHOOT_SPELLID)
return
elseif spellId == THROW_SPELLID then
ResetRanged(true)
return
elseif IsOnSwingSpell(spellId) then
-- On-next-swing ability (HS / Cleave / Maul / Raptor Strike / etc.)
-- — the swing fires as the spell consumes it. Drop the queued color.
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
if S.mhActive and S.mhSpeed > 0 then
UpdateWeaponSpeeds()
S.mhTimerMax = S.mhSpeed
S.mhTimer = S.mhSpeed
end
if S.ohActive and S.ohSpeed > 0 then
S.ohTimerMax = S.ohSpeed
S.ohTimer = S.ohSpeed
end
elseif S.mhFrozenAt then
local castDuration = GetTime() - S.mhFrozenAt
S.mhTimer = S.mhTimer + castDuration
S.mhTimerMax = S.mhTimerMax + castDuration
S.mhFrozenAt = nil
end
end
S.pendingCastSpellId = nil
end
-- SPELL_CAST_EVENT hook: HS/Cleave/Maul queue tracking
pfUI.libdebuff_spell_cast_hooks = pfUI.libdebuff_spell_cast_hooks or {}
pfUI.libdebuff_spell_cast_hooks["swingtimer"] = function(success, spellId)
local kind = ClassifyOnSwingSpell(spellId)
if not kind then return end
S.useSpellQueueEvent = true
SetQueuedKind(kind)
end
local events = CreateFrame("Frame")
events:RegisterEvent("PLAYER_SWING")
events:RegisterEvent("AUTO_ATTACK_SELF")
events:RegisterEvent("AUTO_ATTACK_OTHER")
events:RegisterEvent("PLAYER_ENTERING_WORLD")
events:RegisterEvent("PLAYER_EQUIPMENT_CHANGED") -- ClassicAPI: per-slot, equipment-only
events:RegisterEvent("PLAYER_REGEN_DISABLED")
events:RegisterEvent("PLAYER_REGEN_ENABLED")
events:RegisterEvent("ACTIONBAR_SLOT_CHANGED")
events:RegisterEvent("PLAYER_DEAD")
events:RegisterUnitEvent("UNIT_SPELLCAST_SENT", "player")
events:RegisterUnitEvent("UNIT_SPELLCAST_SUCCEEDED", "player")
events:RegisterEvent("UNIT_DIED")
events:RegisterEvent("SPELL_QUEUE_EVENT")
events:RegisterEvent("START_AUTOATTACK")
events:RegisterEvent("STOP_AUTOATTACK")
events:SetScript("OnEvent", function()
if event == "PLAYER_SWING" then
-- ClassicAPI tracks the server's swing clock (white hits, on-next-swing
-- abilities, swing-reset casts, parry haste, extra attacks, weapon
-- swaps) and hands over the seconds left until the next swing.
if arg2 == Enum.PlayerSwingType.MainHand then
StartMH(arg1)
elseif arg2 == Enum.PlayerSwingType.OffHand then
StartOH(arg1)
elseif arg2 == Enum.PlayerSwingType.Ranged then
StartRanged(arg1)
if event == "AUTO_ATTACK_SELF" then
local hitInfo = arg4 or 0
local isOffhand = bit.band(hitInfo, HITINFO_LEFTSWING) ~= 0
local noAction = bit.band(hitInfo, HITINFO_NOACTION) ~= 0
-- NOACTION means server did not advance swing clock (dodge/parry/miss).
-- Only skip if the timer is already running - if it's not active yet
-- (first swing ever), we still want to start it so the bar appears.
if noAction then
if isOffhand and S.ohActive then return end
if not isOffhand and S.mhActive then return end
end
elseif event == "UNIT_SPELLCAST_SENT" then
-- arg4 = spellID: an on-next-swing ability (HS / Cleave / Maul) queued
local kind = ClassifyOnSwingSpell(arg4)
if kind then
S.useSpellQueueEvent = true
SetQueuedKind(kind)
-- Extra attack detection: if timer still has >20% remaining for that hand,
-- the server did NOT reset the swing clock -> this is an extra attack, skip.
-- Use 20% here (SP_SwingTimer's ShouldResetTimer threshold).
-- Exception: if timer is already at 0 (expired), always accept.
if isOffhand then
local pct = S.ohActive and (S.ohTimer / S.ohTimerMax) or 0
if S.ohActive and S.ohTimer > 0 and pct > 0.20 then
return
end
ResetOH()
else
local pct = S.mhActive and (S.mhTimer / S.mhTimerMax) or 0
if S.mhActive and S.mhTimer > 0 and pct > 0.20 then
return
end
ResetMH()
end
elseif event == "UNIT_SPELLCAST_SUCCEEDED" then
-- arg3 = spellID: the on-next-swing ability resolved, which consumes
-- the queue, so drop the queued color
if IsOnSwingSpell(arg3) then
S.hsQueued = false; S.cleaveQueued = false; S.maulQueued = false
elseif event == "AUTO_ATTACK_OTHER" then
-- Parry haste: enemy attacked the player and player parried
local targetGuid = arg2
if not targetGuid or not IsPlayerGuid(targetGuid) then return end
local victimState = arg5 or 0
-- VICTIMSTATE_PARRY = 3
-- Vanilla: parry reduces the NEXT swing timer by 40% of weapon speed,
-- minimum 20% of weapon speed remaining (SP_SwingTimer approach)
if victimState == 3 then
-- Apply to whichever swing comes next (smallest % remaining = closest to firing)
if S.ohActive and S.ohSpeed > 0 and (S.ohTimer / S.ohTimerMax) < (S.mhTimer / S.mhTimerMax) then
local minimum = S.ohSpeed * 0.20
if S.ohTimer > minimum then
local reduct = S.ohSpeed * 0.40
local before = S.ohTimer
S.ohTimer = S.ohTimer - reduct
if S.ohTimer < minimum then S.ohTimer = minimum end
end
elseif S.mhActive and S.mhSpeed > 0 then
local minimum = S.mhSpeed * 0.20
if S.mhTimer > minimum then
local reduct = S.mhSpeed * 0.40
local before = S.mhTimer
S.mhTimer = S.mhTimer - reduct
if S.mhTimer < minimum then S.mhTimer = minimum end
end
else
end
end
elseif event == "SPELL_QUEUE_EVENT" then
@@ -726,8 +931,10 @@ pfUI:RegisterModule("swingtimer", function ()
S.cleaveQueued = false
S.maulQueued = false
elseif event == "PLAYER_DEAD" then
ResetAll()
elseif event == "UNIT_DIED" then
if IsPlayerGuid(arg1) then
ResetAll()
end
end
end)
+12
View File
@@ -866,6 +866,9 @@ pfUI:RegisterModule("thirdparty-vanilla", function()
if C.thirdparty.supermacro.enable == "0" then return end
if not pfUI.bars then return end
-- skin game menu button
SkinButton(GameMenuButtonSuperMacro)
-- hook events to include SuperMacro refreshs
local ButtonFullUpdate = pfUI.bars.ButtonFullUpdate
pfUI.bars.ButtonFullUpdate = function(button)
@@ -1194,7 +1197,16 @@ pfUI:RegisterModule("thirdparty-vanilla", function()
HookAddonOrVariable("LunaUnitFrames", EnableHealComm)
HookAddonOrVariable("NotGrid", EnableHealComm)
HookAddonOrVariable("MoveAnything", function()
SkinButton(GameMenuButtonMoveAnything)
end)
HookAddonOrVariable("MCP", function()
SkinButton(GameMenuButtonAddOns)
end)
HookAddonOrVariable("HardcoreDeath", function()
SkinButton(GameMenuButtonHardcoreDeathLogGUI)
GameMenuFrame:SetHeight(GameMenuFrame:GetHeight() + 20)
end)
+7 -11
View File
@@ -189,7 +189,6 @@ pfUI:RegisterModule("tooltip", function ()
-- 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
local auraSlots = {} -- reusable GetAuraSlots buffer for UpdateBuffs
pfUI.tooltip.buffs = CreateFrame("Frame", "pfTooltipBuffs", GameTooltip)
pfUI.tooltip.buffs:SetPoint("BOTTOMLEFT", GameTooltipStatusBar, "TOPLEFT", 0, default_border + 2)
pfUI.tooltip.buffs:SetHeight(BUFF_SIZE)
@@ -240,20 +239,17 @@ pfUI:RegisterModule("tooltip", function ()
end
local prev, count = nil, 0
-- one GetAuraSlots enumeration + positional by-slot reads: no per-aura
-- table and no per-index re-walk of the aura array
local n = ScanAuraSlots(unit, "HELPFUL", auraSlots, BUFF_MAX)
for i = 1, n do
local name, texture, applications, _, _, expirationTime = C_UnitAuras.UnitAuraBySlot(unit, auraSlots[i])
if not name then break end
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(texture)
icon.stacks:SetText(applications and applications > 1 and applications or "")
icon.texture:SetTexture(aura.icon)
icon.stacks:SetText(aura.applications and aura.applications > 1 and aura.applications or "")
icon.expirationTime = expirationTime
local timeleft = expirationTime and expirationTime > 0 and (expirationTime - GetTime()) or 0
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
+1 -1
View File
@@ -113,7 +113,7 @@ pfUI:RegisterModule("totems", function ()
self.bar[i].cdbg = self.bar[i].cdbg or CreateFrame("Frame", nil, self.bar[i])
self.bar[i].cdbg:SetSize(self.iconsize - 3, self.iconsize - 3)
self.bar[i].cdbg:SetPoint("CENTER", self.bar[i], "CENTER", 0, 0)
self.bar[i].cd = self.bar[i].cd or CreateFrame("Cooldown", "pfTotemsBar"..i.."Cooldown", self.bar[i].cdbg, "CooldownFrameTemplate")
self.bar[i].cd = self.bar[i].cd or CreateFrame(COOLDOWN_FRAME_TYPE, "pfTotemsBar"..i.."Cooldown", self.bar[i].cdbg, "CooldownFrameTemplate")
self.bar[i].cd.pfCooldownStyleAnimation = 1
self.bar[i].cd.pfCooldownType = "ALL"
+110 -36
View File
@@ -1,17 +1,58 @@
pfUI:RegisterModule("tracking", function ()
_G.MiniMapTrackingFrame:UnregisterAllEvents()
_G.MiniMapTrackingFrame:Hide()
MINIMAP_TRACKING_FRAME:UnregisterAllEvents()
MINIMAP_TRACKING_FRAME:Hide()
local function HasEntries(tbl)
for _ in pairs(tbl) do
return true
end
return nil
end
local rawborder, border = GetBorderSize()
local size = tonumber(C.appearance.minimap.tracking_size)
local pulse = C.appearance.minimap.tracking_pulse == "1"
-- Tracking spells come from ClassicAPI's native GetNumTrackingTypes /
-- GetTrackingInfo (see the DLL's docs/API.md "Tracking"). The DLL
-- enumerates them straight from the spellbook by tracking-aura effect,
-- so there is no spell table to maintain here and server-custom trackers
-- (e.g. Turtle's Find Trees) are picked up automatically.
-- Tracking spells identified by SpellID + expected icon path.
-- SpellID is the primary identifier (stable, locale-independent).
-- Icon path is used as secondary confirmation when scanning the spellbook.
local knownTrackingSpells = {
any = {
{ id = 2481, icon = "Racial_Dwarf_FindTreasure" }, -- Find Treasure
{ id = 2580, icon = "Spell_Nature_Earthquake" }, -- Find Minerals
{ id = 2383, icon = "INV_Misc_Flower_02" }, -- Find Herbs (Rank 1)
{ id = 8387, icon = "INV_Misc_Flower_02" }, -- Find Herbs (Rank 2)
{ id = 52917, icon = "INV_TradeSkillItem_03" }, -- Find Trees (TurtleWow)
},
HUNTER = {
{ id = 1494, icon = "Ability_Tracking" }, -- Track Beasts
{ id = 19883, icon = "Spell_Holy_PrayerOfHealing" }, -- Track Humanoids
{ id = 19884, icon = "Spell_Shadow_DarkSummoning" }, -- Track Undead
{ id = 19885, icon = "Ability_Stealth" }, -- Track Hidden
{ id = 19880, icon = "Spell_Frost_SummonWaterElemental" }, -- Track Elementals
{ id = 19878, icon = "Spell_Shadow_SummonFelHunter" }, -- Track Demons
{ id = 19882, icon = "Ability_Racial_Avatar" }, -- Track Giants
{ id = 19879, icon = "INV_Misc_Head_Dragon_01" }, -- Track Dragonkin
},
PALADIN = {
{ id = 5502, icon = "Spell_Holy_SenseUndead" }, -- Sense Undead
},
WARLOCK = {
{ id = 5500, icon = "Spell_Shadow_Metamorphosis" }, -- Sense Demons
},
DRUID = {
{ id = 5225, icon = "Ability_Tracking" }, -- Track Humanoids (Cat Form only)
},
}
-- Build a flat lookup: spellId -> entry, for fast spellbook matching
local spellIdLookup = {}
for _, entries in pairs(knownTrackingSpells) do
for _, entry in ipairs(entries) do
spellIdLookup[entry.id] = entry
end
end
local state = {
texture = nil,
@@ -34,14 +75,6 @@ pfUI:RegisterModule("tracking", function ()
pfUI.tracking.icon:SetTexCoord(.08, .92, .08, .92)
pfUI.tracking.icon:SetAllPoints(pfUI.tracking)
-- Pulses the icon while no tracking spell is active
pfUI.tracking.pulse = pfUI.tracking.icon:CreateAnimationGroup()
pfUI.tracking.pulse:SetLooping("BOUNCE")
local fade = pfUI.tracking.pulse:CreateAnimation("Alpha")
fade:SetChange(-.5)
fade:SetDuration(.5)
fade:SetSmoothing("IN_OUT")
pfUI.tracking.menu = CreateFrame("Frame", "pfUIDropDownMenuTracking", nil, "UIDropDownMenuTemplate")
pfUI.tracking:RegisterEvent("PLAYER_ENTERING_WORLD")
@@ -56,6 +89,19 @@ pfUI:RegisterModule("tracking", function ()
this:RefreshMenu()
end)
pfUI.tracking:SetScript("OnUpdate", function()
if this.pulse then
local _,_,_,alpha = this.icon:GetVertexColor()
local fpsmod = GetFramerate() / 30
if not alpha or alpha >= 0.9 then
this.modifier = -0.03 / fpsmod
elseif alpha <= .5 then
this.modifier = 0.03 / fpsmod
end
this.icon:SetVertexColor(1,1,1,alpha + this.modifier)
end
end)
pfUI.tracking:RegisterForClicks("LeftButtonUp", "RightButtonUp")
pfUI.tracking:SetScript("OnClick", function()
if arg1 == "RightButton" then
@@ -82,24 +128,50 @@ pfUI:RegisterModule("tracking", function ()
end)
function pfUI.tracking:RefreshSpells()
state.spells = {}
if not GetNumTrackingTypes then return end
local playerClass = UnitClassBase("player")
local isCatForm = pfUI.tracking:PlayerIsDruidInCatForm(playerClass)
-- Druid Track Humanoids (5225) only casts in Cat Form. Hide it out of
-- form so the menu never offers a tracker that would fail to cast.
local isCatForm = pfUI.tracking:PlayerIsDruidInCatForm(UnitClassBase("player"))
-- Build set of valid SpellIDs for this class
local validIds = {}
for _, entry in ipairs(knownTrackingSpells.any) do
validIds[entry.id] = true
end
if knownTrackingSpells[playerClass] then
for _, entry in ipairs(knownTrackingSpells[playerClass]) do
validIds[entry.id] = true
end
end
for i = 1, GetNumTrackingTypes() do
local name, texture, _, _, spellId = GetTrackingInfo(i)
local castable = not (spellId == 5225 and not isCatForm)
if name and texture and castable
and not pfUI.tracking.invalidSpells[name] then
state.spells[i] = {
index = i, -- tracking index, passed to SetTracking()
name = name,
texture = texture,
spellId = spellId,
}
-- Druids only get Track Humanoids in Cat Form
if playerClass == "DRUID" and not isCatForm then
validIds[5225] = nil
end
-- Scan spellbook: match by icon path, confirm SpellID is valid for this class
for tabIndex = 1, GetNumSpellTabs() do
local _, _, offset, numSpells = GetSpellTabInfo(tabIndex)
for spellIndex = offset + 1, offset + numSpells do
local spellTexture = GetSpellTexture(spellIndex, BOOKTYPE_SPELL)
local spellName = GetSpellName(spellIndex, BOOKTYPE_SPELL)
if pfUI.tracking.invalidSpells[spellName] then
spellTexture = nil
end
if spellTexture then
local lowerTexture = string.lower(spellTexture)
for spellId, entry in pairs(spellIdLookup) do
if validIds[spellId] and not state.spells[spellId]
and strfind(lowerTexture, string.lower(entry.icon)) then
state.spells[spellId] = {
index = spellIndex,
name = spellName,
texture = spellTexture,
spellId = spellId,
}
end
end
end
end
end
end
@@ -108,18 +180,20 @@ pfUI:RegisterModule("tracking", function ()
local texture = GetTrackingTexture()
if texture and texture ~= state.texture then
state.texture = texture
pfUI.tracking.pulse:Stop()
pfUI.tracking.pulse = nil
pfUI.tracking.icon:SetTexture(texture)
pfUI.tracking.icon:SetVertexColor(1,1,1,1)
pfUI.tracking:Show()
elseif not texture then
state.texture = nil
if pulse and next(state.spells) ~= nil then
if pulse and HasEntries(state.spells) then
pfUI.tracking.pulse = true
pfUI.tracking.icon:SetTexture("Interface\\Icons\\INV_Misc_QuestionMark")
if not pfUI.tracking.pulse:IsPlaying() then pfUI.tracking.pulse:Play() end
pfUI.tracking.icon:SetVertexColor(1,1,1,1)
pfUI.tracking:Show()
else
pfUI.tracking.pulse:Stop()
pfUI.tracking.pulse = nil
pfUI.tracking:Hide()
end
end
@@ -143,7 +217,7 @@ pfUI:RegisterModule("tracking", function ()
checked = spell.texture == state.texture,
arg1 = spell,
func = function (arg1)
SetTracking(arg1.index)
CastSpell(arg1.index, BOOKTYPE_SPELL)
CloseDropDownMenus()
end
})
+2962 -3
View File
File diff suppressed because it is too large Load Diff
+12 -11
View File
@@ -130,6 +130,15 @@ pfUI:RegisterModule("unlock", function ()
return frames
end
local function DraggerOnUpdate()
this.text:SetAlpha(this.text:GetAlpha() -0.05)
if this.text:GetAlpha() < 0.1 then
this.text:SetText(strsub(this:GetParent():GetName(),3))
this.text:SetAlpha(1)
this:SetScript("OnUpdate", function() return end)
end
end
local function SaveScale(frame, scale)
frame:SetScale(scale)
@@ -139,8 +148,9 @@ pfUI:RegisterModule("unlock", function ()
C.position[frame:GetName()]["scale"] = scale
frame.drag.text:SetText(T["Scale"] .. ": " .. scale)
frame.drag.fade:Stop()
frame.drag.fade:Play()
frame.drag.text:SetAlpha(1)
frame.drag:SetScript("OnUpdate", DraggerOnUpdate)
end
local function SavePosition(frame)
@@ -445,15 +455,6 @@ pfUI:RegisterModule("unlock", function ()
d.text:SetPoint("CENTER", 0, 0)
d.text:SetFontObject(GameFontWhite)
-- Fades out the scale readout, then puts the frame name back
d.fade = d.text:CreateAnimationGroup()
local fade = d.fade:CreateAnimation("Alpha")
fade:SetChange(-1)
fade:SetDuration(.3)
d.fade:SetScript("OnFinished", function()
d.text:SetText(strsub(f:GetName(),3))
end)
d.frame = f
d.fname = fname
d.label = label
-21
View File
@@ -34,25 +34,4 @@ pfUI:RegisterModule("unusable", function ()
hooksecurefunc("BankFrameItemButton_UpdateLock", function()
pfUI.unusable:UpdateSlot(-1, this:GetID())
end)
-- requirement changes (level, proficiency/skill, required spell, reputation,
-- honor rank) don't touch the bags; queue a full rebag through pfUI.bag's
-- coalesced delay, whose UpdateSlot resets the tint before we reapply it
local refresh = CreateFrame("Frame")
refresh:RegisterEvent("PLAYER_LEVEL_UP")
refresh:RegisterEvent("SKILL_LINES_CHANGED")
refresh:RegisterEvent("SPELLS_CHANGED")
refresh:RegisterEvent("FACTION_STANDING_CHANGED")
refresh:RegisterEvent("PLAYER_PVP_RANK_CHANGED")
refresh:SetScript("OnEvent", function()
for _, bag in pairs(pfUI.BACKPACK) do
pfUI.bag.delay.UpdateBag[bag] = true
end
-- the bank frame is only created on first bank visit
if pfUI.bag.left and pfUI.bag.left:IsShown() then
for _, bag in pairs(pfUI.BANK) do
pfUI.bag.delay.UpdateBag[bag] = true
end
end
end)
end)
+12 -32
View File
@@ -63,16 +63,9 @@ pfUI:RegisterModule("xpbar", function ()
end
end)
-- Cancel a running fade-out and show the bar at full alpha
local function Reveal(self)
self.fade:Stop()
self:SetAlpha(1)
end
local function OnLeave(self)
local self = self or this
self.hover = nil
self.tick = GetTime() + 3.00
GameTooltip:Hide()
end
@@ -89,8 +82,7 @@ local function OnEnter(self)
mode = "REP"
end
self.hover = true
Reveal(self)
self:SetAlpha(1)
if mode == "XP" then
local xp, xpmax, exh = UnitXP("player"), UnitXPMax("player"), GetXPExhaustion()
@@ -191,15 +183,13 @@ end
local self = self or this
if self.text_mouse == "1" then
self.bar.text:SetShown(self.hover)
self.bar.text:SetShown(MouseIsOver(self))
end
if self.always then return end
-- Hover comes from OnEnter/OnLeave, the same mouse focus that reveals the
-- bar, so the fade can't restart under the cursor
if self:GetAlpha() == 0 or self.hover or self.fade:IsPlaying() then return end
if ( self.tick or 1) > GetTime() then return end
self.fade:Play()
if self:GetAlpha() == 0 or MouseIsOver(self) then return end
if ( self.tick or 1) > GetTime() then return else self.tick = GetTime() + .01 end
self:SetAlpha(self:GetAlpha() - .05)
end
local function OnEvent(self)
@@ -220,7 +210,7 @@ end
end
if self.always then
Reveal(self)
self:SetAlpha(1)
self:Show()
end
@@ -230,7 +220,7 @@ end
if mode == "XP" then
self.enabled = true
Reveal(self)
self:SetAlpha(1)
self.bar:SetMinMaxValues(0, UnitXPMax("player"))
self.bar:SetValue(UnitXP("player"))
if GetXPExhaustion() then
@@ -256,7 +246,7 @@ end
elseif mode == "PETXP" then
self.restedbar:Hide()
self.enabled = true
Reveal(self)
self:SetAlpha(1)
local currXP, nextXP = GetPetExperience()
self.bar:SetMinMaxValues(min(0, currXP), nextXP)
@@ -274,7 +264,7 @@ end
local name, standingID, barMin, barMax, barValue = GetRepDisplay(self.factionID)
if name then
self.enabled = true
Reveal(self)
self:SetAlpha(1)
barMax = barMax - barMin
barValue = barValue - barMin
@@ -352,7 +342,6 @@ end
local cr, cg, cb, ca = pfUI.api.GetStringColor(b.xp_color)
b.bar:SetStatusBarColor(cr,cg,cb,ca)
b.bar:SetOrientation(b.mode)
b.bar:SetRotatesTexture(b.mode == "VERTICAL")
b.bar.text = b.bar.text or b.bar:CreateFontString()
b.bar.text:SetPoint("CENTER", b, "CENTER", 0, b.text_off_y)
@@ -369,23 +358,14 @@ end
local cr, cg, cb, ca = pfUI.api.GetStringColor(b.rest_color)
b.restedbar:SetStatusBarColor(cr,cg,cb,ca)
b.restedbar:SetOrientation(b.mode)
b.restedbar:SetRotatesTexture(b.mode == "VERTICAL")
-- auto hide
b:EnableMouse(true)
if not b.fade then
b.fade = b:CreateAnimationGroup()
local fade = b.fade:CreateAnimation("Alpha")
fade:SetChange(-1)
fade:SetDuration(.3)
b.fade:SetScript("OnFinished", function() b:SetAlpha(0) end)
end
b:RegisterEvent("FACTION_STANDING_CHANGED")
b:RegisterUnitEvent("UNIT_PET", "player")
b:RegisterUnitEvent("UNIT_LEVEL", "player")
b:RegisterUnitEvent("UNIT_PET_EXPERIENCE", "player", "pet")
b:RegisterEvent("UNIT_PET")
b:RegisterEvent("UNIT_LEVEL")
b:RegisterEvent("UNIT_PET_EXPERIENCE")
b:RegisterEvent("PLAYER_ENTERING_WORLD")
b:RegisterEvent("UPDATE_EXHAUSTION")
b:RegisterEvent("PLAYER_XP_UPDATE")
+103 -38
View File
@@ -3,8 +3,6 @@ function SlashCmdList.RELOAD(msg, editbox)
ReloadUI()
end
local addonName = ...
SLASH_PFUI1 = '/pfui'
function SlashCmdList.PFUI(msg, editbox)
pfUI.gui:SetShown(not pfUI.gui:IsShown())
@@ -15,19 +13,87 @@ function SlashCmdList.GM(msg, editbox)
ToggleHelpFrame(1)
end
local pfUI = CreateFrame("Frame", addonName, UIParent)
pfUI = CreateFrame("Frame", nil, UIParent)
pfUI:RegisterEvent("ADDON_LOADED")
-- setup bootvar
pfUI.bootup = true
pfUI_playerDB = pfUI_playerDB or {}
pfUI_config = pfUI_config or {}
pfUI_init = pfUI_init or {}
pfUI_profiles = pfUI_profiles or {}
pfUI_addon_profiles = pfUI_addon_profiles or {}
pfUI_cache = pfUI_cache or {}
pfUI_throttle = pfUI_throttle or {}
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 = 12100 -- (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"
local function FormatVersion(packed)
local x = math.floor(packed / 10000)
local y = math.floor(math.mod(packed, 10000) / 100)
local z = math.mod(packed, 100)
return string.format("v%d.%d.%d", x, y, z)
end
if not CLASSIC_API_VERSION or CLASSIC_API_VERSION < PFUI_CLASSIC_API_MIN then
local minVersion = FormatVersion(PFUI_CLASSIC_API_MIN)
pfUI.disabled = true
local detail
if not CLASSIC_API_VERSION then
detail = "The ClassicAPI DLL isn't loaded. The |cff33ffcc!!!ClassicAPI|r addon ships bundled with it -- delete your |cff33ffcc!!!ClassicAPI|r folder and install the latest release from:"
else
detail = "ClassicAPI " .. minVersion .. " or newer is required. Delete your |cff33ffcc!!!ClassicAPI|r folder and reinstall the latest release from:"
end
local function ShowRequiredPopup()
StaticPopupDialogs["PFUI_CLASSICAPI_REQUIRED"] = {
text = "|cff33ffccpf|cffffffffUI|r has been disabled.\n\n" .. detail,
button1 = OKAY,
hasEditBox = 1,
editBoxWidth = 280,
timeout = 0,
whileDead = 1,
hideOnEscape = 1,
preferredIndex = 3,
OnShow = function()
local editBox = getglobal(this:GetName().."EditBox")
if editBox then
editBox:SetText(PFUI_CLASSIC_API_LATEST_URL)
editBox:HighlightText()
editBox:SetFocus()
end
end,
}
StaticPopup_Show("PFUI_CLASSICAPI_REQUIRED")
DEFAULT_CHAT_FRAME:AddMessage(
"|cff33ffccpf|cffffffffUI|r disabled: " .. detail .. " " .. PFUI_CLASSIC_API_LATEST_URL,
1, 0.3, 0.3
)
end
local loginFrame = CreateFrame("Frame")
loginFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
loginFrame:SetScript("OnEvent", function()
loginFrame:UnregisterEvent("PLAYER_ENTERING_WORLD")
ShowRequiredPopup()
end)
elseif CLASSIC_API_VERSION < PFUI_CLASSIC_API_LATEST then
EventUtil.ContinueOnPlayerLogin(function()
C_Timer.After(8, function()
DEFAULT_CHAT_FRAME:AddMessage(
"|cff33ffccpf|rUI: ClassicAPI " .. FormatVersion(PFUI_CLASSIC_API_LATEST) .. " is available — " .. PFUI_CLASSIC_API_LATEST_URL,
1, 0.85, 0.3
)
end)
end)
end
end
-- initialize saved variables
pfUI_playerDB = {}
pfUI_config = {}
pfUI_init = {}
pfUI_profiles = {}
pfUI_addon_profiles = {}
pfUI_cache = {}
pfUI_throttle = {}
-- localization
pfUI_locale = {}
@@ -52,24 +118,35 @@ pfUI.env = {}
-- the flag; one that lacks it should drop the flag and get the safe fallback.
pfUI.handlesHookScript = true
pfUI.events = Mixin({}, CallbackRegistryMixin)
pfUI.events:OnLoad()
pfUI.events:SetUndefinedEventsAllowed(true)
if not pfUI.disabled then
pfUI.events = Mixin({}, CallbackRegistryMixin)
pfUI.events:OnLoad()
pfUI.events:SetUndefinedEventsAllowed(true)
end
-- check if macro addons are loaded (disables macrotweak/macroscan)
function pfUI:MacroAddonsLoaded()
return IsAddOnLoaded("Supermacro") or IsAddOnLoaded("SuperCleveRoidMacros") or IsAddOnLoaded("UltimaMacros")
end
pfUI.name = addonName
pfUI.path = "Interface\\AddOns\\" .. addonName
-- detect current addon path
local tocs = { "", "-master", "-tbc", "-wotlk" }
for _, name in pairs(tocs) do
local current = string.format("pfUI%s", name)
local title = C_AddOns.GetAddOnName(current)
if title then
pfUI.name = current
pfUI.path = "Interface\\AddOns\\" .. current
break
end
end
-- handle/convert media dir paths
pfUI.media = setmetatable({}, { __index = function(tab,key)
local value = tostring(key)
if value:find("img:") then
if strfind(value, "img:") then
value = string.gsub(value, "img:", pfUI.path .. "\\img\\")
elseif value:find("font:") then
elseif strfind(value, "font:") then
value = string.gsub(value, "font:", pfUI.path .. "\\fonts\\")
else
value = string.gsub(value, "Interface\\AddOns\\pfUI\\", pfUI.path .. "\\")
@@ -78,7 +155,9 @@ pfUI.media = setmetatable({}, { __index = function(tab,key)
return value
end})
pfUI.client = INTERFACE_VERSION
-- cache client version
local _, _, _, client = GetBuildInfo()
pfUI.client = client or 11200
-- setup pfUI namespace
setmetatable(pfUI.env, {__index = getfenv(0)})
@@ -312,13 +391,14 @@ function pfUI:CheckNewModules()
end
local function BackwardsCompatRegister(func, arg3)
if arg3 and type(func) == "string" and type(arg3) == "function" and func:find("vanilla") then
if arg3 and type(func) == "string" and type(arg3) == "function" and string.find(func, "vanilla") then
return arg3
end
return func
end
function pfUI:RegisterModule(name, func, arg3)
if pfUI.disabled then return end
if pfUI.module[name] then return end
func = BackwardsCompatRegister(func, arg3)
pfUI.module[name] = func
@@ -329,6 +409,7 @@ function pfUI:RegisterModule(name, func, arg3)
end
function pfUI:RegisterSkin(name, func, arg3)
if pfUI.disabled then return end
if pfUI.skin[name] then return end
func = BackwardsCompatRegister(func, arg3)
pfUI.skin[name] = func
@@ -349,6 +430,7 @@ function pfUI:LoadSkin(s)
end
pfUI:SetScript("OnEvent", function()
if pfUI.disabled then return end
-- make sure to initialize and set our fonts
-- each time an addon got loaded but only
@@ -362,7 +444,7 @@ pfUI:SetScript("OnEvent", function()
-- "@project-version@" until release tooling substitutes it — mark those
-- explicitly as "dev" instead of pretending they're a real numbered build.
local raw = tostring(GetAddOnMetadata(pfUI.name, "Version"))
if raw:find("@") then
if strfind(raw, "@") then
pfUI.version.major, pfUI.version.minor, pfUI.version.fix = 0, 0, 0
pfUI.version.string = "dev"
else
@@ -495,21 +577,4 @@ function pfUI.SetupCVars()
COMBAT_TEXT_SHOW_HONOR_GAINED = "1"
end
UIParentLoadAddOn("Blizzard_CombatText")
end
do -- RunMacroText
local obj = setmetatable({ ["GetText"] = function(self) return self.text end }, {
__index = function(tab,key)
local value = function() return end
rawset(tab,key,value)
return value
end
})
function RunMacroText(text)
obj.text = text
ChatEdit_ParseText(obj, 1)
end
end
_G.PLAYER_BUFF_START_ID = -1
end
+7 -5
View File
@@ -8,9 +8,11 @@
## SavedVariablesPerCharacter: pfUI_config, pfUI_init, pfUI_playerDB
## X-Website: https://github.com/brues-code/pfUI
# ClassicAPI-less fallback. ClassicAPI redirects this read to pfUI_Turtle.toc
# (Turtle clients) or pfUI_ClassicAPI.toc (everything else), so the client only
# ever reaches THIS file when the DLL is missing. Load the notice and nothing
# else.
pfUI.lua
API_Check.lua
init\env.xml
init\compat.xml
init\api.xml
init\libs.xml
init\skins.xml
init\modules.xml
-23
View File
@@ -1,23 +0,0 @@
## Interface: 11200
## Title: |cff33ffccpf|cffffffffUI
## Author: Shagu - modified by me0wg4ming & brues
## Notes: A complete user interface replacement.
## Notes-ruRU: Полная замена пользовательского интерфейса.
## Version: @project-version@
## SavedVariables: pfUI_profiles, pfUI_addon_profiles, pfUI_cache, pfUI_throttle
## SavedVariablesPerCharacter: pfUI_config, pfUI_init, pfUI_playerDB
## X-Website: https://github.com/brues-code/pfUI
# Selected by ClassicAPI on every non-Turtle client. Turtle clients get
# pfUI_Turtle.toc instead, which trades init\stock.xml for init\turtle.xml.
API_Check.lua
pfUI.lua
init\env.xml
init\stock.xml
init\api.xml
init\libs.xml
init\skins.xml
init\modules.xml
-24
View File
@@ -1,24 +0,0 @@
## Interface: 11200
## Title: |cff33ffccpf|cffffffffUI
## Author: Shagu - modified by me0wg4ming & brues
## Notes: A complete user interface replacement.
## Notes-ruRU: Полная замена пользовательского интерфейса.
## Version: @project-version@
## SavedVariables: pfUI_profiles, pfUI_addon_profiles, pfUI_cache, pfUI_throttle
## SavedVariablesPerCharacter: pfUI_config, pfUI_init, pfUI_playerDB
## X-Website: https://github.com/brues-code/pfUI
# Selected by ClassicAPI on Turtle WoW, where it wins over pfUI_ClassicAPI.toc.
# Same list with init\stock.xml swapped for init\turtle.xml: the modules, skins
# and vendor prices that only mean anything on a Turtle client.
API_Check.lua
pfUI.lua
init\env.xml
init\api.xml
init\libs.xml
init\skins.xml
init\modules.xml
init\turtle.xml
+2 -2
View File
@@ -8,8 +8,8 @@ pfUI:RegisterSkin("Friends", function ()
CreateBackdropShadow(FriendsFrame)
FriendsFrame.backdrop:SetPoint("TOPLEFT", 8, -10)
FriendsFrame.backdrop:SetPoint("BOTTOMRIGHT", -32, 72)
FriendsFrame:SetHitRectInsets(8,32,10,72)
FriendsFrame.backdrop:SetPoint("BOTTOMRIGHT", -32, 74)
FriendsFrame:SetHitRectInsets(8,32,10,74)
EnableMovable("FriendsFrame")
SkinCloseButton(FriendsFrameCloseButton, FriendsFrame.backdrop, -6, -6)
+15 -11
View File
@@ -19,20 +19,24 @@ pfUI:RegisterSkin("Game Menu", function ()
pfUI.gui:Show()
HideUIPanel(GameMenuFrame)
end)
SkinButton(pfUIButton)
local point, relativeTo, relativePoint, xOffset, yOffset = GameMenuButtonOptions:GetPoint()
GameMenuButtonOptions:SetPoint(point, relativeTo, relativePoint, xOffset, yOffset - 22)
-- skin every button on the menu, including ones other addons add later
local function SkinMenuButtons()
for _, child in pairs({GameMenuFrame:GetChildren()}) do
if child:GetFrameType() == "Button" and not child._pfMenuSkinned then
SkinButton(child)
child._pfMenuSkinned = true
end
end
end
local buttons = {
GameMenuButtonOptions,
GameMenuButtonSoundOptions,
GameMenuButtonUIOptions,
GameMenuButtonKeybindings,
GameMenuButtonMacros,
GameMenuButtonRatings,
GameMenuButtonLogout,
GameMenuButtonQuit,
GameMenuButtonContinue,
}
SkinMenuButtons()
GameMenuFrame:HookScript("OnShow", SkinMenuButtons)
for _, button in pairs(buttons) do
if button then SkinButton(button) end
end
end)
-49
View File
@@ -1,49 +0,0 @@
pfUI:RegisterSkin("Options - Interface", function ()
local rawborder, border = GetBorderSize()
local bpad = rawborder > 1 and border - GetPerfectPixel() or GetPerfectPixel()
local cr, cg, cb = PFUI_CLASS_COLORS[UnitClassBase("player")]:GetRGB()
StripTextures(InterfaceOptionsFrame)
CreateBackdrop(InterfaceOptionsFrame, nil, nil, .75)
CreateBackdropShadow(InterfaceOptionsFrame)
local title = GetNoNameObject(InterfaceOptionsFrame, "FontString", "ARTWORK", ADDON_OPTIONS)
title:ClearAllPoints()
title:SetPoint("TOP", InterfaceOptionsFrame, "TOP", 0, -10)
CreateBackdrop(InterfaceOptionsFrameAddOns, nil, true, .75)
CreateBackdrop(InterfaceOptionsFramePanelContainer, nil, true, .75)
SkinScrollbar(InterfaceOptionsFrameAddOnsListScrollBar)
for _, button in ipairs(InterfaceOptionsFrameAddOns.buttons) do
button:GetHighlightTexture():SetTexture(cr, cg, cb, .25)
-- the list update swaps plus/minus textures on the toggle by path
local toggle = _G[button:GetName() .. "Toggle"]
SkinCollapseButton(toggle)
toggle:SetPushedTexture("")
toggle:SetHighlightTexture("")
toggle.SetPushedTexture = function() end
end
-- ClassicAPI's own pages (Options\Panel.lua) number their controls
local i = 1
while _G["ClassicAPIOptionsControl" .. i] do
local control = _G["ClassicAPIOptionsControl" .. i]
local ctype = control:GetFrameType()
if ctype == "CheckButton" then
SkinCheckbox(control)
elseif ctype == "Slider" then
SkinSlider(control)
else
SkinDropDown(control, nil, nil, nil, true)
end
i = i + 1
end
SkinButton(InterfaceOptionsFrameDefaults)
SkinButton(InterfaceOptionsFrameCancel)
SkinButton(InterfaceOptionsFrameOkay)
InterfaceOptionsFrameOkay:ClearAllPoints()
InterfaceOptionsFrameOkay:SetPoint("RIGHT", InterfaceOptionsFrameCancel, "LEFT", -2*bpad, 0)
end)
+9 -9
View File
@@ -2,19 +2,19 @@ pfUI:RegisterSkin("Quest Log", function ()
local rawborder, border = GetBorderSize()
local bpad = rawborder > 1 and border - GetPerfectPixel() or GetPerfectPixel()
_G.QUESTS_DISPLAYED = 22
_G.QUESTS_DISPLAYED = 23
_G.MAX_WATCHABLE_QUESTS = 20 -- TODO
do -- quest log frame
QuestLogQuestCount:ClearAllPoints()
QuestLogQuestCount:SetPoint("TOPRIGHT", -10, -30)
-- let the panel manager anchor this doublewide panel at 10, -114
UIPanelWindows["QuestLogFrame"].xoffset = 10
UIPanelWindows["QuestLogFrame"].yoffset = -10
hooksecurefunc("QuestLog_OnShow", function()
QuestLogFrame:ClearAllPoints()
QuestLogFrame:SetPoint("TOPLEFT", 10, -104)
end)
-- match the 430px height and top edge of the other skinned panels
QuestLogFrame:SetSize(676, 430)
QuestLogFrame:SetSize(676, 440)
QuestLogFrame:DisableDrawLayer("BACKGROUND")
StripTextures(QuestLogFrame, true)
@@ -144,7 +144,7 @@ pfUI:RegisterSkin("Quest Log", function ()
end
QuestLogListScrollFrame:SetPoint("TOPLEFT", 10, -54)
QuestLogListScrollFrame:SetHeight(340)
QuestLogListScrollFrame:SetHeight(350)
hooksecurefunc("QuestLog_Update", function()
local numEntries = GetNumQuestLogEntries()
@@ -181,8 +181,8 @@ pfUI:RegisterSkin("Quest Log", function ()
QuestLogDetailScrollFrame:ClearAllPoints()
QuestLogDetailScrollFrame:SetPoint("TOPLEFT", QuestLogListScrollFrame, "TOPRIGHT", 35, 0)
QuestLogDetailScrollFrame:SetHeight(366)
QuestLogDetailScrollChildFrame:SetHeight(366)
QuestLogDetailScrollFrame:SetHeight(376)
QuestLogDetailScrollChildFrame:SetHeight(376)
local bg = QuestLogDetailScrollFrame:CreateTexture(nil, "BACKGROUND")
bg:SetAllPoints()
+2 -2
View File
@@ -6,9 +6,9 @@ pfUI:RegisterSkin("Spellbook", function ()
CreateBackdrop(SpellBookFrame, nil, nil, .75)
CreateBackdropShadow(SpellBookFrame)
SpellBookFrame.backdrop:SetPoint("TOPLEFT", 10, -10)
SpellBookFrame.backdrop:SetPoint("TOPLEFT", 12, -12)
SpellBookFrame.backdrop:SetPoint("BOTTOMRIGHT", -30, 72)
SpellBookFrame:SetHitRectInsets(10,30,10,72)
SpellBookFrame:SetHitRectInsets(12,30,12,72)
EnableMovable(SpellBookFrame)
SpellBookTitleText:ClearAllPoints()
+3 -10
View File
@@ -22,16 +22,9 @@ pfUI:RegisterSkin("Pet Stable Master", function ()
PetStablePetInfo:ClearAllPoints()
PetStablePetInfo:SetPoint("TOPLEFT", PetStableModel, "TOPLEFT", 10, 0)
-- Servers differ in stable size (Turtle WoW has 4 slots), so collect
-- every PetStableStabledPetN the client created.
local buttons = { PetStableCurrentPet }
local i = 1
while _G["PetStableStabledPet"..i] do
table.insert(buttons, _G["PetStableStabledPet"..i])
i = i + 1
end
for _, button in pairs(buttons) do
local buttons = {"PetStableCurrentPet", "PetStableStabledPet1", "PetStableStabledPet2"}
for _,v in pairs(buttons) do
local button = _G[v]
StripTextures(button)
SkinButton(button, nil, nil, nil, _G[button:GetName().."IconTexture"])