mirror of
https://github.com/brues-code/pfUI.git
synced 2026-09-22 15:46:56 +00:00
Merge remote-tracking branch 'TWoWPfui/master'
This commit is contained in:
+129
-4
@@ -3,6 +3,82 @@ pfUI.api = { }
|
||||
-- load pfUI environment
|
||||
setfenv(1, pfUI:GetEnvironment())
|
||||
|
||||
-- [ DLL Detection Helpers ]
|
||||
-- Detects presence of various DLL extensions for enhanced functionality
|
||||
|
||||
-- [ HasSuperWoW ]
|
||||
-- Returns true if SuperWoW DLL is active
|
||||
-- SuperWoW provides: UNIT_CASTEVENT, UnitPosition, SetMouseoverUnit, SpellInfo, etc.
|
||||
function pfUI.api.HasSuperWoW()
|
||||
return SUPERWOW_VERSION or (SetAutoloot and SpellInfo)
|
||||
end
|
||||
|
||||
-- [ HasUnitXP ]
|
||||
-- Returns true if UnitXP_SP3 DLL is active
|
||||
-- UnitXP provides: distance, line of sight, behind detection, targeting helpers
|
||||
function pfUI.api.HasUnitXP()
|
||||
local success = pcall(UnitXP, "nop", "nop")
|
||||
return success
|
||||
end
|
||||
|
||||
-- [ HasNampower ]
|
||||
-- Returns true if Nampower DLL is active
|
||||
-- Nampower provides: spell queuing, GetCastInfo, GetSpellIdCooldown, IsSpellInRange, etc.
|
||||
function pfUI.api.HasNampower()
|
||||
return GetNampowerVersion and true or false
|
||||
end
|
||||
|
||||
-- [ GetUnitDistance ]
|
||||
-- Returns distance to unit using best available method
|
||||
-- 'unit1' [string] first unit (default: "player")
|
||||
-- 'unit2' [string] second unit
|
||||
-- returns: [number] distance in yards, or nil if unavailable
|
||||
function pfUI.api.GetUnitDistance(unit1, unit2)
|
||||
if not unit2 then
|
||||
unit2 = unit1
|
||||
unit1 = "player"
|
||||
end
|
||||
|
||||
if not UnitExists(unit2) then return nil end
|
||||
|
||||
-- Try UnitXP first (most accurate)
|
||||
if pfUI.api.HasUnitXP() then
|
||||
local success, distance = pcall(UnitXP, "distanceBetween", unit1, unit2)
|
||||
if success and distance then return distance end
|
||||
end
|
||||
|
||||
-- Try SuperWoW UnitPosition
|
||||
if pfUI.api.HasSuperWoW() and UnitPosition then
|
||||
local x1, y1, z1 = UnitPosition(unit1)
|
||||
local x2, y2, z2 = UnitPosition(unit2)
|
||||
if x1 and y1 and z1 and x2 and y2 and z2 then
|
||||
return ((x2 - x1)^2 + (y2 - y1)^2 + (z2 - z1)^2)^0.5
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
-- [ UnitInLineOfSight ]
|
||||
-- Returns true if unit1 has line of sight to unit2
|
||||
-- Requires UnitXP_SP3
|
||||
function pfUI.api.UnitInLineOfSight(unit1, unit2)
|
||||
if not pfUI.api.HasUnitXP() then return nil end
|
||||
local success, inSight = pcall(UnitXP, "inSight", unit1, unit2)
|
||||
if success then return inSight end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- [ UnitIsBehind ]
|
||||
-- Returns true if unit1 is behind unit2
|
||||
-- Requires UnitXP_SP3
|
||||
function pfUI.api.UnitIsBehind(unit1, unit2)
|
||||
if not pfUI.api.HasUnitXP() then return nil end
|
||||
local success, behind = pcall(UnitXP, "behind", unit1, unit2)
|
||||
if success then return behind end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Client API shortcuts
|
||||
gfind = string.gmatch or string.gfind
|
||||
mod = math.mod or mod
|
||||
@@ -369,18 +445,33 @@ end
|
||||
-- 'number' [number] the number that should be abbreviated
|
||||
-- 'returns: [string] the abbreviated value
|
||||
function pfUI.api.Abbreviate(number)
|
||||
if pfUI_config.unitframes.abbrevnum == "1" then
|
||||
local mode = pfUI_config.unitframes.abbrevnum
|
||||
-- mode "0" = disabled (full numbers)
|
||||
-- mode "1" = 2 decimals (4250 -> 4.25k) [legacy/default]
|
||||
-- mode "2" = 1 decimal (4250 -> 4.2k) - always rounds DOWN
|
||||
|
||||
if mode == "1" or mode == "2" then
|
||||
local sign = number < 0 and -1 or 1
|
||||
number = math.abs(number)
|
||||
|
||||
if number > 1000000 then
|
||||
return pfUI.api.round(number/1000000*sign,2) .. "m"
|
||||
if mode == "2" then
|
||||
-- 1 decimal, round DOWN: 4.18m -> 4.1m
|
||||
return (floor(number/100000) / 10 * sign) .. "m"
|
||||
else
|
||||
return pfUI.api.round(number/1000000*sign, 2) .. "m"
|
||||
end
|
||||
elseif number > 1000 then
|
||||
return pfUI.api.round(number/1000*sign,2) .. "k"
|
||||
if mode == "2" then
|
||||
-- 1 decimal, round DOWN: 4180 -> 4.1k (not 4.2k)
|
||||
return (floor(number/100) / 10 * sign) .. "k"
|
||||
else
|
||||
return pfUI.api.round(number/1000*sign, 2) .. "k"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return number
|
||||
return math.floor(number)
|
||||
end
|
||||
|
||||
-- [ SendChatMessageWide ]
|
||||
@@ -1184,6 +1275,10 @@ function pfUI.api.EnableAutohide(frame, timeout, combat)
|
||||
end
|
||||
|
||||
frame.hover:SetScript("OnUpdate", function()
|
||||
-- throttle to 0.05s
|
||||
if (this.tick or 0) > GetTime() then return end
|
||||
this.tick = GetTime() + 0.05
|
||||
|
||||
if this.activeTo == "keep" then return end
|
||||
|
||||
if MouseIsOver(this, 10, -10, -10, 10) then
|
||||
@@ -1371,3 +1466,33 @@ function pfUI.api.GetNoNameObject(frame, objtype, layer, arg1, arg2)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- [ TryMemoizedFuncLoadstringForSpellCasts ]
|
||||
-- Memoizes lua function strings for spell casts to improve performance.
|
||||
-- Supports both string functions and direct function values.
|
||||
-- 'funcOrStr' [function|string] Either a function or a lua string to execute
|
||||
-- return: [function|nil] The function to execute, or nil on error
|
||||
local memoizedFuncs = {}
|
||||
function pfUI.api.TryMemoizedFuncLoadstringForSpellCasts(funcOrStr)
|
||||
-- If it's already a function, return it directly
|
||||
if type(funcOrStr) == "function" then
|
||||
return funcOrStr
|
||||
end
|
||||
|
||||
-- If it's a string, try to memoize it
|
||||
if type(funcOrStr) == "string" then
|
||||
-- Check if we've already compiled this string
|
||||
if memoizedFuncs[funcOrStr] then
|
||||
return memoizedFuncs[funcOrStr]
|
||||
end
|
||||
|
||||
-- Try to compile the string
|
||||
local func = loadstring(funcOrStr)
|
||||
if func then
|
||||
memoizedFuncs[funcOrStr] = func
|
||||
return func
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
+48
-6
@@ -148,7 +148,6 @@ function pfUI:LoadConfig()
|
||||
pfUI:UpdateConfig("appearance", "cd", "font_size", "12")
|
||||
pfUI:UpdateConfig("appearance", "cd", "font_size_blizz", "12")
|
||||
pfUI:UpdateConfig("appearance", "cd", "font_size_foreign","12")
|
||||
pfUI:UpdateConfig("appearance", "cd", "debuffs", "1")
|
||||
pfUI:UpdateConfig("appearance", "cd", "blizzard", "1")
|
||||
pfUI:UpdateConfig("appearance", "cd", "foreign", "0")
|
||||
pfUI:UpdateConfig("appearance", "cd", "milliseconds", "1")
|
||||
@@ -219,14 +218,48 @@ function pfUI:LoadConfig()
|
||||
pfUI:UpdateConfig("unitframes", nil, "rangecheck", "0")
|
||||
pfUI:UpdateConfig("unitframes", nil, "buffdetect", "0")
|
||||
pfUI:UpdateConfig("unitframes", nil, "druidmanabar", "1")
|
||||
pfUI:UpdateConfig("unitframes", nil, "druidmanaheight", "2")
|
||||
pfUI:UpdateConfig("unitframes", nil, "druidmanatext", "0")
|
||||
pfUI:UpdateConfig("unitframes", nil, "druidmanaheight", "10")
|
||||
pfUI:UpdateConfig("unitframes", nil, "druidmanawidth", "-1")
|
||||
pfUI:UpdateConfig("unitframes", nil, "druidmanaoffx", "0")
|
||||
pfUI:UpdateConfig("unitframes", nil, "druidmanaoffy", "0")
|
||||
pfUI:UpdateConfig("unitframes", nil, "druidmanaspace", "-3")
|
||||
pfUI:UpdateConfig("unitframes", nil, "druidmanatexture", "Interface\\AddOns\\pfUI\\img\\bar")
|
||||
|
||||
pfUI:UpdateConfig("unitframes", nil, "rangechecki", "4")
|
||||
pfUI:UpdateConfig("unitframes", nil, "combowidth", "6")
|
||||
pfUI:UpdateConfig("unitframes", nil, "comboheight", "6")
|
||||
pfUI:UpdateConfig("unitframes", nil, "swingtimerwidth", "200")
|
||||
pfUI:UpdateConfig("unitframes", nil, "swingtimerheight", "12")
|
||||
pfUI:UpdateConfig("unitframes", nil, "swingtimertexture", "Interface\\AddOns\\pfUI\\img\\bar")
|
||||
pfUI:UpdateConfig("unitframes", nil, "swingtimertext", "1")
|
||||
pfUI:UpdateConfig("unitframes", nil, "swingtimerlabel", "1")
|
||||
pfUI:UpdateConfig("unitframes", nil, "swingtimeroffhand","1")
|
||||
pfUI:UpdateConfig("unitframes", nil, "swingtimerranged", "1")
|
||||
pfUI:UpdateConfig("unitframes", nil, "swingtimerfontsize","12")
|
||||
pfUI:UpdateConfig("unitframes", nil, "swingtimermhcolor",".8,.3,.3,1")
|
||||
pfUI:UpdateConfig("unitframes", nil, "swingtimerohcolor",".3,.8,.3,1")
|
||||
pfUI:UpdateConfig("unitframes", nil, "swingtimerrangedcolor",".3,.6,1,1")
|
||||
pfUI:UpdateConfig("unitframes", nil, "swingtimerrangedwarncolor",".9,0,0,1")
|
||||
pfUI:UpdateConfig("unitframes", nil, "swingtimerhsqueue","1")
|
||||
pfUI:UpdateConfig("unitframes", nil, "abbrevnum", "1")
|
||||
pfUI:UpdateConfig("unitframes", nil, "castbardecimals", "2")
|
||||
pfUI:UpdateConfig("unitframes", nil, "abbrevname", "1")
|
||||
|
||||
-- Nampower Settings
|
||||
pfUI:UpdateConfig("unitframes", nil, "spellqueue", "1")
|
||||
pfUI:UpdateConfig("unitframes", nil, "spellqueuesize", "24")
|
||||
pfUI:UpdateConfig("unitframes", nil, "gcd_indicator", "0")
|
||||
pfUI:UpdateConfig("unitframes", nil, "gcd_size", "4")
|
||||
pfUI:UpdateConfig("unitframes", nil, "reactive_indicator", "0")
|
||||
pfUI:UpdateConfig("unitframes", nil, "reactive_size", "28")
|
||||
pfUI:UpdateConfig("unitframes", nil, "damage_tracking", "0")
|
||||
|
||||
-- UnitXP Settings
|
||||
pfUI:UpdateConfig("unitframes", nil, "los_indicator", "0")
|
||||
pfUI:UpdateConfig("unitframes", nil, "behind_indicator", "0")
|
||||
pfUI:UpdateConfig("unitframes", nil, "unitxp_notify", "0")
|
||||
pfUI:UpdateConfig("unitframes", nil, "track_group", "0")
|
||||
|
||||
pfUI:UpdateConfig("unitframes", nil, "selfingroup", "0")
|
||||
pfUI:UpdateConfig("unitframes", nil, "selfinraid", "0")
|
||||
pfUI:UpdateConfig("unitframes", nil, "raidforgroup", "0")
|
||||
@@ -697,6 +730,8 @@ function pfUI:LoadConfig()
|
||||
pfUI:UpdateConfig("tooltip", nil, "font_tooltip", "Interface\\AddOns\\pfUI\\fonts\\Myriad-Pro.ttf")
|
||||
pfUI:UpdateConfig("tooltip", nil, "font_tooltip_size", "12")
|
||||
|
||||
-- Throttle Settings
|
||||
|
||||
pfUI:UpdateConfig("chat", "text", "input_width", "0")
|
||||
pfUI:UpdateConfig("chat", "text", "input_height", "0")
|
||||
pfUI:UpdateConfig("chat", "text", "outline", "1")
|
||||
@@ -714,6 +749,7 @@ function pfUI:LoadConfig()
|
||||
pfUI:UpdateConfig("chat", "text", "detecturl", "1")
|
||||
pfUI:UpdateConfig("chat", "text", "classcolor", "1")
|
||||
pfUI:UpdateConfig("chat", "text", "whosearchunknown", "0")
|
||||
pfUI:UpdateConfig("chat", "text", "playerlevel", "0")
|
||||
pfUI:UpdateConfig("chat", "left", "width", "380")
|
||||
pfUI:UpdateConfig("chat", "left", "height", "180")
|
||||
pfUI:UpdateConfig("chat", "right", "enable", "0")
|
||||
@@ -737,9 +773,11 @@ function pfUI:LoadConfig()
|
||||
pfUI:UpdateConfig("chat", "bubbles", "borders", "1")
|
||||
pfUI:UpdateConfig("chat", "bubbles", "alpha", ".75")
|
||||
|
||||
pfUI:UpdateConfig("nameplates", nil, "showhostile", "1")
|
||||
pfUI:UpdateConfig("nameplates", nil, "showfriendly", "0")
|
||||
pfUI:UpdateConfig("nameplates", nil, "use_unitfonts", "0")
|
||||
pfUI:UpdateConfig("nameplates", nil, "showhostile", "1")
|
||||
pfUI:UpdateConfig("nameplates", nil, "showfriendly", "0")
|
||||
pfUI:UpdateConfig("nameplates", nil, "disable_hostile_in_friendly", "0")
|
||||
pfUI:UpdateConfig("nameplates", nil, "disable_friendly_in_friendly", "0")
|
||||
pfUI:UpdateConfig("nameplates", nil, "use_unitfonts", "0")
|
||||
pfUI:UpdateConfig("nameplates", nil, "legacy", "0")
|
||||
pfUI:UpdateConfig("nameplates", nil, "overlap", "0")
|
||||
pfUI:UpdateConfig("nameplates", nil, "verticalhealth", "0")
|
||||
@@ -818,6 +856,9 @@ function pfUI:LoadConfig()
|
||||
pfUI:UpdateConfig("nameplates", "debuffs", "blacklist", "")
|
||||
pfUI:UpdateConfig("nameplates", "debuffs", "showstacks", "0")
|
||||
pfUI:UpdateConfig("nameplates", "debuffs", "position", "BOTTOM")
|
||||
pfUI:UpdateConfig("nameplates", nil, "debufftimers", "1")
|
||||
pfUI:UpdateConfig("nameplates", nil, "debufftext", "1")
|
||||
pfUI:UpdateConfig("nameplates", nil, "debuffanim", "0")
|
||||
|
||||
pfUI:UpdateConfig("abuttons", nil, "enable", "1")
|
||||
pfUI:UpdateConfig("abuttons", nil, "position", "bottom")
|
||||
@@ -862,6 +903,7 @@ function pfUI:LoadConfig()
|
||||
pfUI:UpdateConfig("thirdparty", "bcs", "enable", "1")
|
||||
pfUI:UpdateConfig("thirdparty", "crafty", "enable", "1")
|
||||
pfUI:UpdateConfig("thirdparty", "clevermacro", "enable", "1")
|
||||
pfUI:UpdateConfig("thirdparty", "supercleveroidmacros", "enable", "1")
|
||||
pfUI:UpdateConfig("thirdparty", "flightmap", "enable", "1")
|
||||
pfUI:UpdateConfig("thirdparty", "sheepwatch", "enable", "1")
|
||||
pfUI:UpdateConfig("thirdparty", "totemtimers", "enable", "1")
|
||||
|
||||
+885
-176
File diff suppressed because it is too large
Load Diff
Vendored
+32
@@ -878,4 +878,36 @@ pfUI_translation["deDE"] = {
|
||||
["Zonetime"] = nil,
|
||||
["Zoom & Fade"] = nil,
|
||||
["Zoom Target Nameplate"] = nil,
|
||||
|
||||
-- Throttling translations
|
||||
["Throttling"] = "Drosselung",
|
||||
["Nameplates"] = "Namensplaketten",
|
||||
["Tooltips"] = "Tooltips",
|
||||
["Chat Tab"] = "Chat Tab",
|
||||
["Libpredict"] = "Libpredict",
|
||||
["Panel Alignment"] = "Panel Ausrichtung",
|
||||
["Nameplate Update Rate"] = "Namensplaketten Aktualisierungsrate",
|
||||
["Target/Casting Plates"] = "Ziel/Cast Plaketten",
|
||||
["Normal Plates"] = "Normale Plaketten",
|
||||
["Mass Pulls (20+ Plates)"] = "Große Pulls (20+ Plaketten)",
|
||||
["Tooltip Update Rate"] = "Tooltip Aktualisierungsrate",
|
||||
["Cursor Follow"] = "Cursor Folgen",
|
||||
["Health/Status Bar"] = "Leben/Status Leiste",
|
||||
["Unit Frame Update Rate"] = "Einheiten Frame Aktualisierungsrate",
|
||||
["Raid/Party Frames"] = "Raid/Gruppe Frames",
|
||||
["Player Frame"] = "Spieler Frame",
|
||||
["Chat Tab Hover Check"] = "Chat Tab Hover Prüfung",
|
||||
["Heal Prediction Cleanup"] = "Heilvorhersage Bereinigung",
|
||||
["Panel Alignment Check"] = "Panel Ausrichtungsprüfung",
|
||||
["Custom FPS"] = "Benutzerdefiniert FPS",
|
||||
["Very Slow"] = "Sehr Langsam",
|
||||
["Slow"] = "Langsam",
|
||||
["Normal"] = "Normal",
|
||||
["Fast"] = "Schnell",
|
||||
["Very Fast"] = "Sehr Schnell",
|
||||
["Fastest"] = "Schnellste",
|
||||
["Reset to Defaults"] = "Auf Standard zurücksetzen",
|
||||
["Target/Casting"] = "Ziel/Cast",
|
||||
["Normal"] = "Normal",
|
||||
["Mass"] = "Masse",
|
||||
}
|
||||
|
||||
Vendored
+32
@@ -878,4 +878,36 @@ pfUI_translation["enUS"] = {
|
||||
["Zonetime"] = nil,
|
||||
["Zoom & Fade"] = nil,
|
||||
["Zoom Target Nameplate"] = nil,
|
||||
|
||||
-- Throttling translations
|
||||
["Throttling"] = nil,
|
||||
["Nameplates"] = nil,
|
||||
["Tooltips"] = nil,
|
||||
["Chat Tab"] = nil,
|
||||
["Libpredict"] = nil,
|
||||
["Panel Alignment"] = nil,
|
||||
["Nameplate Update Rate"] = nil,
|
||||
["Target/Casting Plates"] = nil,
|
||||
["Normal Plates"] = nil,
|
||||
["Mass Pulls (20+ Plates)"] = nil,
|
||||
["Tooltip Update Rate"] = nil,
|
||||
["Cursor Follow"] = nil,
|
||||
["Health/Status Bar"] = nil,
|
||||
["Unit Frame Update Rate"] = nil,
|
||||
["Raid/Party Frames"] = nil,
|
||||
["Player Frame"] = nil,
|
||||
["Chat Tab Hover Check"] = nil,
|
||||
["Heal Prediction Cleanup"] = nil,
|
||||
["Panel Alignment Check"] = nil,
|
||||
["Custom FPS"] = nil,
|
||||
["Very Slow"] = nil,
|
||||
["Slow"] = nil,
|
||||
["Normal"] = nil,
|
||||
["Fast"] = nil,
|
||||
["Very Fast"] = nil,
|
||||
["Fastest"] = nil,
|
||||
["Reset to Defaults"] = nil,
|
||||
["Target/Casting"] = nil,
|
||||
["Normal"] = nil,
|
||||
["Mass"] = nil,
|
||||
}
|
||||
|
||||
Vendored
+1
@@ -700,6 +700,7 @@ pfUI_translation["zhCN"] = {
|
||||
["Show Empty Buttons"] = "显示空按钮",
|
||||
["Show FPS and Latency Colors"] = "显示帧数以及延迟颜色",
|
||||
["Show Guild Name"] = "显示公会名称",
|
||||
["Show Player Levels"] ="显示玩家等级",
|
||||
["Show Happiness Icon"] = "显示高兴值图标",
|
||||
["Show Health Points"] = "显示生命值",
|
||||
["Show/Hide TimeManager"] = "显示/隐藏时间管理器",
|
||||
|
||||
@@ -8,5 +8,6 @@
|
||||
<Include file="..\libs\libtooltip.lua"/>
|
||||
<Include file="..\libs\libhealth.lua"/>
|
||||
<Include file="..\libs\libtotem.lua"/>
|
||||
<Include file="..\libs\libthrottle.lua"/>
|
||||
<Include file="..\libs\libpredict.lua"/>
|
||||
</Ui>
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
<Include file="..\modules\focus.lua"/>
|
||||
<Include file="..\modules\target.lua"/>
|
||||
<Include file="..\modules\combopoints.lua"/>
|
||||
<Include file="..\modules\swingtimer.lua"/>
|
||||
<Include file="..\modules\targettarget.lua"/>
|
||||
<Include file="..\modules\targettargettarget.lua"/>
|
||||
<Include file="..\modules\pet.lua"/>
|
||||
@@ -72,4 +73,7 @@
|
||||
<Include file="..\modules\macrotweak.lua"/>
|
||||
<Include file="..\modules\turtle-wow.lua"/>
|
||||
<Include file="..\modules\superwow.lua"/>
|
||||
<Include file="..\modules\nampower.lua"/>
|
||||
<Include file="..\modules\unitxp.lua"/>
|
||||
<Include file="..\modules\bgscore.lua"/>
|
||||
</Ui>
|
||||
|
||||
+174
-18
@@ -53,14 +53,84 @@ local scanner = libtipscan:GetScanner("libcast")
|
||||
local libcast = CreateFrame("Frame", "pfEnemyCast")
|
||||
local player = UnitName("player")
|
||||
|
||||
UnitChannelInfo = _G.UnitChannelInfo or function(unit)
|
||||
-- Store original SuperWoW UnitChannelInfo if it exists
|
||||
local SuperWoW_UnitChannelInfo = _G.UnitChannelInfo
|
||||
|
||||
UnitChannelInfo = function(unit)
|
||||
-- convert to name if unitstring was given
|
||||
unit = pfValidUnits[unit] and UnitName(unit) or unit
|
||||
local unitName = pfValidUnits[unit] and UnitName(unit) or unit
|
||||
|
||||
-- Get GUID if Nampower is available
|
||||
local guid = nil
|
||||
|
||||
-- Check if unit itself is a GUID (starts with "0x")
|
||||
if type(unit) == "string" and string.sub(unit, 1, 2) == "0x" then
|
||||
guid = unit -- unit IS the GUID
|
||||
elseif pfValidUnits[unit] and UnitExists then
|
||||
-- unit is a token like "target" - get GUID from it
|
||||
local _, unitGuid = UnitExists(unit)
|
||||
guid = unitGuid
|
||||
end
|
||||
|
||||
-- For player: ALWAYS use libcast.db because it handles channel updates correctly
|
||||
local isPlayer = unit == "player" or unitName == player
|
||||
|
||||
if isPlayer then
|
||||
local cast, nameSubtext, text, texture, startTime, endTime, isTradeSkill
|
||||
local db = libcast.db[player]
|
||||
|
||||
if db and db.cast and db.start + db.casttime / 1000 > GetTime() then
|
||||
if not db.channel then return end
|
||||
cast = db.cast
|
||||
nameSubtext = db.rank
|
||||
text = ""
|
||||
texture = db.icon
|
||||
startTime = db.start * 1000
|
||||
endTime = startTime + db.casttime
|
||||
isTradeSkill = nil
|
||||
elseif db then
|
||||
db.cast = nil
|
||||
db.rank = nil
|
||||
db.start = nil
|
||||
db.casttime = nil
|
||||
db.icon = nil
|
||||
db.channel = nil
|
||||
end
|
||||
|
||||
return cast, nameSubtext, text, texture, startTime, endTime, isTradeSkill
|
||||
end
|
||||
|
||||
-- For non-player units: use SuperWoW if available, otherwise use libcast.db
|
||||
if SuperWoW_UnitChannelInfo then
|
||||
return SuperWoW_UnitChannelInfo(unit)
|
||||
end
|
||||
|
||||
-- Try GUID-based lookup first (from libdebuff's SPELL_START tracking)
|
||||
local db = nil
|
||||
if guid and pfUI.libdebuff_casts and pfUI.libdebuff_casts[guid] then
|
||||
-- Use libdebuff's cast tracking (from SPELL_START_OTHER events)
|
||||
local castData = pfUI.libdebuff_casts[guid]
|
||||
if castData.event == "START" and castData.endTime and castData.endTime > GetTime() then
|
||||
-- Convert libdebuff format to libcast format
|
||||
db = {
|
||||
cast = castData.spellName,
|
||||
rank = nil,
|
||||
start = castData.startTime,
|
||||
casttime = castData.duration * 1000, -- Convert back to ms
|
||||
icon = castData.icon,
|
||||
channel = nil -- TODO: libdebuff should distinguish channel vs cast
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
-- Fallback to name-based lookup (CHAT_MSG castbars)
|
||||
if not db and libcast.db[unitName] then
|
||||
db = libcast.db[unitName]
|
||||
end
|
||||
|
||||
-- Fallback to libcast.db for non-player units
|
||||
local cast, nameSubtext, text, texture, startTime, endTime, isTradeSkill
|
||||
local db = libcast.db[unit]
|
||||
|
||||
-- clean legacy values
|
||||
if db and db.cast and db.start + db.casttime / 1000 > GetTime() then
|
||||
if not db.channel then return end
|
||||
cast = db.cast
|
||||
@@ -71,7 +141,6 @@ UnitChannelInfo = _G.UnitChannelInfo or function(unit)
|
||||
endTime = startTime + db.casttime
|
||||
isTradeSkill = nil
|
||||
elseif db then
|
||||
-- remove cast action to the database
|
||||
db.cast = nil
|
||||
db.rank = nil
|
||||
db.start = nil
|
||||
@@ -83,14 +152,85 @@ UnitChannelInfo = _G.UnitChannelInfo or function(unit)
|
||||
return cast, nameSubtext, text, texture, startTime, endTime, isTradeSkill
|
||||
end
|
||||
|
||||
UnitCastingInfo = _G.UnitCastingInfo or function(unit)
|
||||
-- Store original SuperWoW UnitCastingInfo if it exists
|
||||
local SuperWoW_UnitCastingInfo = _G.UnitCastingInfo
|
||||
|
||||
UnitCastingInfo = function(unit)
|
||||
-- convert to name if unitstring was given
|
||||
unit = pfValidUnits[unit] and UnitName(unit) or unit
|
||||
local unitName = pfValidUnits[unit] and UnitName(unit) or unit
|
||||
|
||||
-- Get GUID if Nampower is available
|
||||
local guid = nil
|
||||
|
||||
-- Check if unit itself is a GUID (starts with "0x")
|
||||
if type(unit) == "string" and string.sub(unit, 1, 2) == "0x" then
|
||||
guid = unit -- unit IS the GUID
|
||||
elseif pfValidUnits[unit] and UnitExists then
|
||||
-- unit is a token like "target" - get GUID from it
|
||||
local _, unitGuid = UnitExists(unit)
|
||||
guid = unitGuid
|
||||
end
|
||||
|
||||
-- For player: ALWAYS use libcast.db because it handles pushback correctly
|
||||
-- SuperWoW's UnitCastingInfo doesn't track SPELLCAST_DELAYED events
|
||||
local isPlayer = unit == "player" or unitName == player
|
||||
|
||||
if isPlayer then
|
||||
local cast, nameSubtext, text, texture, startTime, endTime, isTradeSkill
|
||||
local db = libcast.db[player]
|
||||
|
||||
if db and db.cast and db.start + db.casttime / 1000 > GetTime() then
|
||||
if db.channel then return end
|
||||
cast = db.cast
|
||||
nameSubtext = db.rank or ""
|
||||
text = ""
|
||||
texture = db.icon
|
||||
startTime = db.start * 1000
|
||||
endTime = startTime + db.casttime
|
||||
isTradeSkill = nil
|
||||
elseif db then
|
||||
db.cast = nil
|
||||
db.rank = nil
|
||||
db.start = nil
|
||||
db.casttime = nil
|
||||
db.icon = nil
|
||||
db.channel = nil
|
||||
end
|
||||
|
||||
return cast, nameSubtext, text, texture, startTime, endTime, isTradeSkill
|
||||
end
|
||||
|
||||
-- For non-player units: use SuperWoW if available, otherwise use libcast.db
|
||||
if SuperWoW_UnitCastingInfo then
|
||||
return SuperWoW_UnitCastingInfo(unit)
|
||||
end
|
||||
|
||||
-- Try GUID-based lookup first (from libdebuff's SPELL_START tracking)
|
||||
local db = nil
|
||||
if guid and pfUI.libdebuff_casts and pfUI.libdebuff_casts[guid] then
|
||||
-- Use libdebuff's cast tracking (from SPELL_START_OTHER events)
|
||||
local castData = pfUI.libdebuff_casts[guid]
|
||||
if castData.event == "START" and castData.endTime and castData.endTime > GetTime() then
|
||||
-- Convert libdebuff format to libcast format
|
||||
db = {
|
||||
cast = castData.spellName,
|
||||
rank = nil,
|
||||
start = castData.startTime,
|
||||
casttime = castData.duration * 1000, -- Convert back to ms
|
||||
icon = castData.icon,
|
||||
channel = nil -- TODO: libdebuff should distinguish channel vs cast
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
-- Fallback to name-based lookup (CHAT_MSG castbars)
|
||||
if not db and libcast.db[unitName] then
|
||||
db = libcast.db[unitName]
|
||||
end
|
||||
|
||||
-- Fallback to libcast.db for non-player units
|
||||
local cast, nameSubtext, text, texture, startTime, endTime, isTradeSkill
|
||||
local db = libcast.db[unit]
|
||||
|
||||
-- clean legacy values
|
||||
if db and db.cast and db.start + db.casttime / 1000 > GetTime() then
|
||||
if db.channel then return end
|
||||
cast = db.cast
|
||||
@@ -101,7 +241,6 @@ UnitCastingInfo = _G.UnitCastingInfo or function(unit)
|
||||
endTime = startTime + db.casttime
|
||||
isTradeSkill = nil
|
||||
elseif db then
|
||||
-- remove cast action to the database
|
||||
db.cast = nil
|
||||
db.rank = nil
|
||||
db.start = nil
|
||||
@@ -182,17 +321,31 @@ libcast:RegisterEvent("SPELLCAST_CHANNEL_STOP")
|
||||
libcast:RegisterEvent("SPELLCAST_CHANNEL_UPDATE")
|
||||
|
||||
local mob, spell, icon, _
|
||||
|
||||
libcast:SetScript("OnEvent", function()
|
||||
-- Fill database with player casts
|
||||
if event == "SPELLCAST_START" then
|
||||
icon = L["spells"][arg1] and L["spells"][arg1].icon and string.format("%s%s", "Interface\\Icons\\", L["spells"][arg1].icon) or lastcasttex
|
||||
-- add cast action to the database
|
||||
this.db[player].cast = arg1
|
||||
this.db[player].rank = lastrank
|
||||
this.db[player].start = GetTime()
|
||||
this.db[player].casttime = arg2
|
||||
this.db[player].icon = icon
|
||||
this.db[player].channel = nil
|
||||
|
||||
-- Check if SuperWoW already set the cast data (with correct haste-adjusted casttime)
|
||||
-- If so, only update icon if needed, don't overwrite casttime
|
||||
local superWowAlreadySet = this.db[player].cast == arg1 and this.db[player].casttime and this.db[player].casttime > 0
|
||||
|
||||
if superWowAlreadySet then
|
||||
-- SuperWoW already set correct casttime, only update icon if better
|
||||
if icon and not this.db[player].icon then
|
||||
this.db[player].icon = icon
|
||||
end
|
||||
else
|
||||
-- No SuperWoW data, use SPELLCAST_START data
|
||||
this.db[player].cast = arg1
|
||||
this.db[player].rank = lastrank
|
||||
this.db[player].start = GetTime()
|
||||
this.db[player].casttime = arg2
|
||||
this.db[player].icon = icon
|
||||
this.db[player].channel = nil
|
||||
end
|
||||
|
||||
if not L["spells"][arg1] or not L["spells"][arg1].icon or not L["spells"][arg1].t then
|
||||
L["spells"][arg1] = L["spells"][arg1] or { }
|
||||
L["spells"][arg1].icon = L["spells"][arg1].icon or icon
|
||||
@@ -214,7 +367,9 @@ libcast:SetScript("OnEvent", function()
|
||||
end
|
||||
elseif event == "SPELLCAST_DELAYED" then
|
||||
if this.db[player].cast then
|
||||
this.db[player].start = this.db[player].start + arg1/1000
|
||||
-- Pushback: increase casttime instead of shifting start
|
||||
-- arg1 is the delay amount in milliseconds
|
||||
this.db[player].casttime = this.db[player].casttime + arg1
|
||||
end
|
||||
elseif event == "SPELLCAST_CHANNEL_START" then
|
||||
-- add cast action to the database
|
||||
@@ -224,6 +379,7 @@ libcast:SetScript("OnEvent", function()
|
||||
this.db[player].casttime = arg1
|
||||
this.db[player].icon = L["spells"][arg2] and L["spells"][arg2].icon and string.format("%s%s", "Interface\\Icons\\", L["spells"][arg2].icon) or lastcasttex
|
||||
this.db[player].channel = true
|
||||
|
||||
lastcasttex, lastrank = nil, nil
|
||||
elseif event == "SPELLCAST_CHANNEL_STOP" then
|
||||
if this.db[player] and this.db[player].channel then
|
||||
|
||||
+1794
-178
File diff suppressed because it is too large
Load Diff
+666
-98
@@ -9,14 +9,30 @@ setfenv(1, pfUI:GetEnvironment())
|
||||
-- UnitGetIncomingHeals(unit)
|
||||
-- UnitHasIncomingResurrection(unit)
|
||||
--
|
||||
-- The library is able to receive and send compatible messages to HealComm (vanilla)
|
||||
-- and HealComm (tbc) including the ressurections of both versions. It has an option
|
||||
-- to disable the sending of those messages in case one of the mentioned libraries
|
||||
-- is already active.
|
||||
-- The library is able to receive and send compatible messages to HealComm
|
||||
-- including resurrections. It has an option to disable the sending of those
|
||||
-- messages in case HealComm is already active.
|
||||
--
|
||||
-- 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 hasNampower = GetNampowerVersion ~= nil
|
||||
|
||||
local senttarget
|
||||
local heals, ress, events, hots = {}, {}, {}, {}
|
||||
|
||||
@@ -80,35 +96,168 @@ do -- Regrowth
|
||||
REGROWTH = locales[GetLocale()] or locales["enUS"]
|
||||
end
|
||||
|
||||
-- SuperWoW detection
|
||||
local superwow_active = SpellInfo ~= nil
|
||||
|
||||
-- Spell IDs für UNIT_CASTEVENT (SuperWoW)
|
||||
local SPELL_IDS = {
|
||||
-- Rejuvenation (alle Ränge)
|
||||
[774] = "Reju", [1058] = "Reju", [1430] = "Reju", [2090] = "Reju", [2091] = "Reju",
|
||||
[3627] = "Reju", [8910] = "Reju", [9839] = "Reju", [9840] = "Reju", [9841] = "Reju",
|
||||
[25299] = "Reju", [26981] = "Reju", [26982] = "Reju",
|
||||
-- Renew (alle Ränge)
|
||||
[139] = "Renew", [6074] = "Renew", [6075] = "Renew", [6076] = "Renew", [6077] = "Renew",
|
||||
[6078] = "Renew", [10927] = "Renew", [10928] = "Renew", [10929] = "Renew", [25315] = "Renew",
|
||||
[25221] = "Renew", [25222] = "Renew",
|
||||
}
|
||||
|
||||
local libpredict = CreateFrame("Frame")
|
||||
libpredict:RegisterEvent("UNIT_HEALTH")
|
||||
libpredict:RegisterEvent("CHAT_MSG_ADDON")
|
||||
libpredict:RegisterEvent("PLAYER_TARGET_CHANGED")
|
||||
libpredict:RegisterEvent("PLAYER_LOGOUT")
|
||||
|
||||
-- SuperWoW: Registriere UNIT_CASTEVENT für akkurate Instant-HoT Detection
|
||||
if superwow_active then
|
||||
libpredict:RegisterEvent("UNIT_CASTEVENT")
|
||||
end
|
||||
|
||||
libpredict:SetScript("OnEvent", function()
|
||||
-- Handle shutdown to prevent crash 132
|
||||
if event == "PLAYER_LOGOUT" then
|
||||
this:UnregisterAllEvents()
|
||||
this:SetScript("OnEvent", nil)
|
||||
return
|
||||
end
|
||||
|
||||
if event == "CHAT_MSG_ADDON" and (arg1 == "HealComm" or arg1 == "CTRA") then
|
||||
this:ParseChatMessage(arg4, arg2, arg1)
|
||||
elseif event == "UNIT_HEALTH" then
|
||||
local name = UnitName(arg1)
|
||||
if ress[name] and not UnitIsDeadOrGhost(arg1) then
|
||||
ress[UnitName(arg1)] = nil
|
||||
if name and ress[name] and not UnitIsDeadOrGhost(arg1) then
|
||||
ress[name] = nil -- Reuse 'name' variable instead of calling UnitName again
|
||||
end
|
||||
elseif event == "UNIT_CASTEVENT" and superwow_active then
|
||||
-- arg1 = casterGUID, arg2 = targetGUID, arg3 = event type, arg4 = spellId, arg5 = castTime
|
||||
local casterGUID, targetGUID, castEvent, spellId = arg1, arg2, arg3, arg4
|
||||
|
||||
-- Nur eigene Casts (player)
|
||||
local _, playerGUID = UnitExists("player")
|
||||
if casterGUID ~= playerGUID then return end
|
||||
|
||||
-- Nur "CAST" events (erfolgreiche Instant-Casts)
|
||||
if castEvent ~= "CAST" then return end
|
||||
|
||||
-- Prüfe ob es ein Instant-HoT ist
|
||||
local hotType = SPELL_IDS[spellId]
|
||||
if not hotType then return end
|
||||
|
||||
-- Finde Target Name
|
||||
local targetName
|
||||
for i = 1, 40 do
|
||||
local unit = "raid" .. i
|
||||
if UnitExists(unit) then
|
||||
local _, guid = UnitExists(unit)
|
||||
if guid == targetGUID then
|
||||
targetName = UnitName(unit)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if not targetName then
|
||||
for i = 1, 4 do
|
||||
local unit = "party" .. i
|
||||
if UnitExists(unit) then
|
||||
local _, guid = UnitExists(unit)
|
||||
if guid == targetGUID then
|
||||
targetName = UnitName(unit)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if not targetName then
|
||||
local _, guid = UnitExists("player")
|
||||
if guid == targetGUID then
|
||||
targetName = UnitName("player")
|
||||
end
|
||||
end
|
||||
if not targetName then
|
||||
local _, guid = UnitExists("target")
|
||||
if guid == targetGUID then
|
||||
targetName = UnitName("target")
|
||||
end
|
||||
end
|
||||
|
||||
if not targetName then return end
|
||||
|
||||
-- Duration bestimmen
|
||||
local duration
|
||||
if hotType == "Reju" then
|
||||
duration = rejuvDuration or 12
|
||||
elseif hotType == "Renew" then
|
||||
duration = renewDuration or 15
|
||||
end
|
||||
|
||||
-- Extract rank from spellId (if SpellInfo available)
|
||||
local rank = nil
|
||||
if SpellInfo then
|
||||
local _, rankString = SpellInfo(spellId)
|
||||
if rankString and rankString ~= "" then
|
||||
rank = tonumber((string.gsub(rankString, "Rank ", ""))) or nil
|
||||
end
|
||||
end
|
||||
|
||||
if libpredict.debug then
|
||||
DEFAULT_CHAT_FRAME:AddMessage(string.format("|cff00ffff[UNIT_CASTEVENT]|r spell=%s target=%s dur=%s rank=%s",
|
||||
hotType, targetName, tostring(duration), tostring(rank or "?")))
|
||||
end
|
||||
|
||||
-- Sende HoT mit Rank
|
||||
local playerName = UnitName("player")
|
||||
libpredict:Hot(playerName, targetName, hotType, duration, nil, "UNIT_CASTEVENT", rank)
|
||||
|
||||
-- Sende HealComm Nachricht mit Rank (backwards compatible: rank optional)
|
||||
-- Use "0" for unknown rank instead of empty string to avoid parsing issues
|
||||
local rankStr = rank and tostring(rank) or "0"
|
||||
if libpredict.sender and libpredict.sender.SendHealCommMsg then
|
||||
libpredict.sender:SendHealCommMsg(hotType .. "/" .. targetName .. "/" .. duration .. "/" .. rankStr .. "/")
|
||||
else
|
||||
-- Fallback: direkt senden (smart channel selection)
|
||||
local msg = hotType .. "/" .. targetName .. "/" .. duration .. "/" .. rankStr .. "/"
|
||||
if GetNumRaidMembers() > 0 then
|
||||
SendAddonMessage("HealComm", msg, "RAID")
|
||||
elseif GetNumPartyMembers() > 0 then
|
||||
SendAddonMessage("HealComm", msg, "PARTY")
|
||||
end
|
||||
-- Note: BATTLEGROUND channel not used (no reliable way to detect BG in Vanilla)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
libpredict:SetScript("OnUpdate", function()
|
||||
-- throttle cleanup - no need to check every frame
|
||||
local now = pfUI.uf.now or GetTime()
|
||||
if (this.tick or 0) > now then return end
|
||||
this.tick = now + pfUI.throttle:Get("libpredict") -- Default: Normal (10 FPS)
|
||||
|
||||
-- update on timeout events
|
||||
for timestamp, targets in pairs(events) do
|
||||
if GetTime() >= timestamp then
|
||||
if now >= timestamp then
|
||||
events[timestamp] = nil
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
function libpredict:ParseComm(sender, msg)
|
||||
local msgtype, target, heal, time
|
||||
local msgtype, target, heal, time, rank
|
||||
|
||||
if msg == "Healstop" or msg == "GrpHealstop" then
|
||||
if msg == "HealStop" or msg == "Healstop" or msg == "GrpHealstop" then
|
||||
msgtype = "Stop"
|
||||
-- DEBUG: Log when HealStop received
|
||||
if libpredict.debug then
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cff00ffff[libpredict RX]|r HealStop from " .. tostring(sender))
|
||||
end
|
||||
elseif msg == "Resurrection/stop/" then
|
||||
msgtype = "RessStop"
|
||||
elseif msg then
|
||||
@@ -137,6 +286,13 @@ function libpredict:ParseComm(sender, msg)
|
||||
|
||||
if msgobj[1] == "Reju" or msgobj[1] == "Renew" or msgobj[1] == "Regr" then --hots
|
||||
msgtype, target, heal, time = "Hot", msgobj[2], msgobj[1], msgobj[3]
|
||||
-- NEW: Parse rank (optional, backwards compatible)
|
||||
-- Format: "Reju/Target/12/10/" where msgobj[3]=duration, msgobj[4]=rank
|
||||
-- "0" = unknown rank (for clients without rank extraction)
|
||||
local rankStr = msgobj[4]
|
||||
if rankStr and rankStr ~= "" and rankStr ~= "/" and rankStr ~= "0" then
|
||||
rank = tonumber(rankStr)
|
||||
end
|
||||
end
|
||||
elseif select and UnitCastingInfo then
|
||||
-- latest healcomm
|
||||
@@ -166,14 +322,18 @@ function libpredict:ParseComm(sender, msg)
|
||||
end
|
||||
end
|
||||
|
||||
return msgtype, target, heal, time
|
||||
return msgtype, target, heal, time, rank
|
||||
end
|
||||
|
||||
-- Duplikat-Erkennung für HoT Nachrichten
|
||||
local recentHots = {}
|
||||
local DUPLICATE_WINDOW = 0.5 -- Ignoriere gleiche Nachricht innerhalb 0.5s
|
||||
|
||||
function libpredict:ParseChatMessage(sender, msg, comm)
|
||||
local msgtype, target, heal, time
|
||||
local msgtype, target, heal, time, rank
|
||||
|
||||
if comm == "HealComm" then
|
||||
msgtype, target, heal, time = libpredict:ParseComm(sender, msg)
|
||||
msgtype, target, heal, time, rank = libpredict:ParseComm(sender, msg)
|
||||
elseif comm == "CTRA" then
|
||||
local _, _, cmd, ctratarget = string.find(msg, "(%a+)%s?([^#]*)")
|
||||
if cmd and ctratarget and cmd == "RES" and ctratarget ~= "" and ctratarget ~= UNKNOWN then
|
||||
@@ -201,7 +361,44 @@ function libpredict:ParseChatMessage(sender, msg, comm)
|
||||
elseif msgtype == "Ress" then
|
||||
libpredict:Ress(sender, target)
|
||||
elseif msgtype == "Hot" then
|
||||
libpredict:Hot(sender, target, heal, time)
|
||||
-- Duplikat-Check: gleicher sender+target+spell innerhalb DUPLICATE_WINDOW ignorieren
|
||||
local now = pfUI.uf.now or GetTime()
|
||||
local key = sender .. target .. heal
|
||||
if recentHots[key] and (now - recentHots[key]) < DUPLICATE_WINDOW then
|
||||
if libpredict.debug then
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cffff0000[DUPLICATE IGNORED]|r " .. key)
|
||||
end
|
||||
return
|
||||
end
|
||||
recentHots[key] = now
|
||||
|
||||
-- Cleanup alte Einträge (alle 10s)
|
||||
if not libpredict.lastCleanup or (now - libpredict.lastCleanup) > 10 then
|
||||
for k, v in pairs(recentHots) do
|
||||
if (now - v) > DUPLICATE_WINDOW then
|
||||
recentHots[k] = nil
|
||||
end
|
||||
end
|
||||
libpredict.lastCleanup = now
|
||||
end
|
||||
|
||||
-- Für eigene HoTs: Korrigiere die startTime
|
||||
if sender == UnitName("player") then
|
||||
local existing = hots[target] and hots[target][heal]
|
||||
|
||||
-- Wenn bereits ein aktiver Timer existiert, nicht überschreiben
|
||||
if existing and (existing.start + existing.duration) > now then
|
||||
return
|
||||
end
|
||||
|
||||
-- Kompensiere HealComm Verzögerung
|
||||
local delay = (heal == "Regr") and 0.3 or 0
|
||||
local correctedStart = now - delay
|
||||
|
||||
libpredict:Hot(sender, target, heal, time, correctedStart, "ParseComm-Self", rank)
|
||||
return
|
||||
end
|
||||
libpredict:Hot(sender, target, heal, time, nil, "ParseComm", rank)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -215,24 +412,70 @@ function libpredict:Heal(sender, target, amount, duration)
|
||||
return
|
||||
end
|
||||
|
||||
local timeout = duration/1000 + GetTime()
|
||||
local now = pfUI.uf.now or GetTime()
|
||||
local timeout = duration/1000 + now
|
||||
heals[target] = heals[target] or {}
|
||||
heals[target][sender] = { amount, timeout }
|
||||
libpredict:AddEvent(timeout, target)
|
||||
end
|
||||
|
||||
function libpredict:Hot(sender, target, spell, duration)
|
||||
-- Debug flag
|
||||
libpredict.debug = false
|
||||
|
||||
function libpredict:Hot(sender, target, spell, duration, startTime, source, rank)
|
||||
hots[target] = hots[target] or {}
|
||||
hots[target][spell] = hots[target][spell] or {}
|
||||
|
||||
-- Korrigiere Regrowth Duration (Server gibt 21 zurück, sollte aber 20 sein)
|
||||
if spell == "Regr" then
|
||||
duration = 20
|
||||
end
|
||||
|
||||
-- Sicherstellen dass duration eine Zahl ist
|
||||
duration = tonumber(duration) or duration
|
||||
|
||||
-- Rank protection: Don't overwrite higher rank HoT with lower rank
|
||||
local existing = hots[target][spell]
|
||||
if existing and existing.rank and rank then
|
||||
local existingRank = tonumber(existing.rank) or 0
|
||||
local newRank = tonumber(rank) or 0
|
||||
|
||||
local now = pfUI.uf.now or GetTime()
|
||||
local existingTimeleft = (existing.start + existing.duration) - now
|
||||
|
||||
-- If existing HoT is still active and has higher rank, don't overwrite
|
||||
if existingTimeleft > 0 and newRank > 0 and newRank < existingRank then
|
||||
if libpredict.debug then
|
||||
DEFAULT_CHAT_FRAME:AddMessage(string.format("|cffff0000[Hot RANK BLOCK]|r %s Rank %d cannot overwrite Rank %d on %s",
|
||||
spell, newRank, existingRank, target))
|
||||
end
|
||||
return -- Don't overwrite!
|
||||
end
|
||||
end
|
||||
|
||||
local now = pfUI.uf.now or GetTime()
|
||||
hots[target][spell].duration = duration
|
||||
hots[target][spell].start = GetTime()
|
||||
hots[target][spell].start = startTime or now
|
||||
hots[target][spell].rank = rank -- Store rank for protection
|
||||
|
||||
-- Debug
|
||||
if libpredict.debug then
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cff33ffcc[Hot]|r src=" .. (source or "?") ..
|
||||
" | sender=" .. (sender or "nil") ..
|
||||
" | target=" .. (target or "nil") ..
|
||||
" | spell=" .. (spell or "nil") ..
|
||||
" | dur=" .. tostring(duration) .. " (" .. type(duration) .. ")" ..
|
||||
" | rank=" .. tostring(rank or "?"))
|
||||
end
|
||||
|
||||
-- update aura events of relevant unitframes
|
||||
if pfUI and pfUI.uf and pfUI.uf.frames then
|
||||
for _, frame in pairs(pfUI.uf.frames) do
|
||||
if frame.namecache == target then
|
||||
frame.update_aura = true
|
||||
if libpredict.debug then
|
||||
DEFAULT_CHAT_FRAME:AddMessage(" |cff00ff00-> Frame update triggered for " .. (frame:GetName() or "?") .. "|r")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -276,16 +519,18 @@ function libpredict:RessStop(sender)
|
||||
end
|
||||
|
||||
function libpredict:UnitGetIncomingHeals(unit)
|
||||
if not unit or not UnitName(unit) then return 0 end
|
||||
if UnitIsDeadOrGhost(unit) then return 0 end
|
||||
if not unit then return 0 end
|
||||
local name = UnitName(unit)
|
||||
if not name then return 0 end
|
||||
if UnitIsDeadOrGhost(unit) then return 0 end
|
||||
|
||||
local sumheal = 0
|
||||
if not heals[name] then
|
||||
return sumheal
|
||||
else
|
||||
local now = pfUI.uf.now or GetTime()
|
||||
for sender, amount in pairs(heals[name]) do
|
||||
if amount[2] <= GetTime() then
|
||||
if amount[2] <= now then
|
||||
heals[name][sender] = nil
|
||||
else
|
||||
sumheal = sumheal + amount[1]
|
||||
@@ -296,8 +541,9 @@ function libpredict:UnitGetIncomingHeals(unit)
|
||||
end
|
||||
|
||||
function libpredict:UnitHasIncomingResurrection(unit)
|
||||
if not unit or not UnitName(unit) then return nil end
|
||||
if not unit then return nil end
|
||||
local name = UnitName(unit)
|
||||
if not name then return nil end
|
||||
|
||||
if not ress[name] then
|
||||
return nil
|
||||
@@ -389,6 +635,23 @@ local function UpdateCache(spell, heal, crit)
|
||||
end
|
||||
end
|
||||
|
||||
-- Cooldown für lokale Instant-HoT Hooks (verhindert Spam bei Click-to-Cast)
|
||||
local instantHotCooldown = {}
|
||||
local INSTANT_HOT_COOLDOWN = 1.0 -- 1 Sekunde Cooldown (GCD ist 1.5s)
|
||||
|
||||
-- Pending HoTs Queue - wird nach Delay verifiziert
|
||||
local pendingHots = {}
|
||||
|
||||
-- Hilfsfunktion: Prüfe ob Buff auf Unit vorhanden ist
|
||||
local function UnitHasBuff(unit, buffName)
|
||||
for i = 1, 32 do
|
||||
local name = UnitBuff(unit, i)
|
||||
if not name then break end
|
||||
if name == buffName then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- Gather Data by User Actions
|
||||
hooksecurefunc("CastSpell", function(id, bookType)
|
||||
if not libpredict.sender.enabled then return end
|
||||
@@ -397,6 +660,52 @@ hooksecurefunc("CastSpell", function(id, bookType)
|
||||
spell_queue[1] = effect
|
||||
spell_queue[2] = effect.. ( rank or "" )
|
||||
spell_queue[3] = UnitName("target") and UnitCanAssist("player", "target") and UnitName("target") or UnitName("player")
|
||||
|
||||
-- Extract rank number
|
||||
local rankNum = nil
|
||||
if rank and rank ~= "" then
|
||||
rankNum = tonumber((string.gsub(rank, "Rank ", ""))) or nil
|
||||
end
|
||||
|
||||
-- Instant-HoTs: Mit SuperWoW nutzen wir UNIT_CASTEVENT (akkurater)
|
||||
-- Ohne SuperWoW: Fallback auf Hook-Methode mit Cooldown
|
||||
if superwow_active then return end
|
||||
|
||||
if effect == REJUVENATION then
|
||||
local target = spell_queue[3]
|
||||
local now = pfUI.uf.now or GetTime()
|
||||
local key = "Reju" .. target
|
||||
|
||||
-- Cooldown-Check
|
||||
if instantHotCooldown[key] and (now - instantHotCooldown[key]) < INSTANT_HOT_COOLDOWN then
|
||||
return
|
||||
end
|
||||
instantHotCooldown[key] = now
|
||||
|
||||
if libpredict.debug then
|
||||
DEFAULT_CHAT_FRAME:AddMessage(string.format("|cff00ff00[CastSpell REJU INSTANT]|r target=%s rank=%s (Fallback)", target, tostring(rankNum or "?")))
|
||||
end
|
||||
libpredict:Hot(player, target, "Reju", rejuvDuration, nil, "CastSpell-Instant", rankNum)
|
||||
local rankStr = rankNum and tostring(rankNum) or "0"
|
||||
libpredict.sender:SendHealCommMsg("Reju/"..target.."/"..rejuvDuration.."/"..rankStr.."/")
|
||||
elseif effect == RENEW then
|
||||
local target = spell_queue[3]
|
||||
local now = pfUI.uf.now or GetTime()
|
||||
local key = "Renew" .. target
|
||||
|
||||
-- Cooldown-Check
|
||||
if instantHotCooldown[key] and (now - instantHotCooldown[key]) < INSTANT_HOT_COOLDOWN then
|
||||
return
|
||||
end
|
||||
instantHotCooldown[key] = now
|
||||
|
||||
if libpredict.debug then
|
||||
DEFAULT_CHAT_FRAME:AddMessage(string.format("|cff00ff00[CastSpell RENEW INSTANT]|r target=%s rank=%s (Fallback)", target, tostring(rankNum or "?")))
|
||||
end
|
||||
libpredict:Hot(player, target, "Renew", renewDuration, nil, "CastSpell-Instant", rankNum)
|
||||
local rankStr = rankNum and tostring(rankNum) or "0"
|
||||
libpredict.sender:SendHealCommMsg("Renew/"..target.."/"..renewDuration.."/"..rankStr.."/")
|
||||
end
|
||||
end)
|
||||
|
||||
hooksecurefunc("CastSpellByName", function(effect, target)
|
||||
@@ -412,9 +721,59 @@ hooksecurefunc("CastSpellByName", function(effect, target)
|
||||
target = target and target == true and UnitName("player") or target
|
||||
target = target and target == 1 and UnitName("player") or target
|
||||
|
||||
spell_queue[1] = effect
|
||||
spell_queue[2] = effect.. ( rank or "" )
|
||||
spell_queue[3] = target or mouseover or default
|
||||
-- Extract rank number
|
||||
local rankNum = nil
|
||||
if rank and rank ~= "" then
|
||||
rankNum = tonumber((string.gsub(rank, "Rank ", ""))) or nil
|
||||
end
|
||||
|
||||
-- Nur spell_queue überschreiben wenn kein Cast läuft
|
||||
-- (verhindert dass Instant-Spam während Regrowth-Cast die Queue zerstört)
|
||||
if not libpredict.sender.current_cast then
|
||||
spell_queue[1] = effect
|
||||
spell_queue[2] = effect.. ( rank or "" )
|
||||
spell_queue[3] = target or mouseover or default
|
||||
end
|
||||
|
||||
-- Instant-HoTs: Mit SuperWoW nutzen wir UNIT_CASTEVENT (akkurater)
|
||||
-- Ohne SuperWoW: Fallback auf Hook-Methode mit Cooldown
|
||||
if superwow_active then return end
|
||||
|
||||
if effect == REJUVENATION then
|
||||
local hotTarget = target or mouseover or default
|
||||
local now = pfUI.uf.now or GetTime()
|
||||
local key = "Reju" .. hotTarget
|
||||
|
||||
-- Cooldown-Check
|
||||
if instantHotCooldown[key] and (now - instantHotCooldown[key]) < INSTANT_HOT_COOLDOWN then
|
||||
return
|
||||
end
|
||||
instantHotCooldown[key] = now
|
||||
|
||||
if libpredict.debug then
|
||||
DEFAULT_CHAT_FRAME:AddMessage(string.format("|cff00ff00[CastSpellByName REJU INSTANT]|r target=%s rank=%s (Fallback)", hotTarget, tostring(rankNum or "?")))
|
||||
end
|
||||
libpredict:Hot(player, hotTarget, "Reju", rejuvDuration, nil, "CastSpellByName-Instant", rankNum)
|
||||
local rankStr = rankNum and tostring(rankNum) or "0"
|
||||
libpredict.sender:SendHealCommMsg("Reju/"..hotTarget.."/"..rejuvDuration.."/"..rankStr.."/")
|
||||
elseif effect == RENEW then
|
||||
local hotTarget = target or mouseover or default
|
||||
local now = pfUI.uf.now or GetTime()
|
||||
local key = "Renew" .. hotTarget
|
||||
|
||||
-- Cooldown-Check
|
||||
if instantHotCooldown[key] and (now - instantHotCooldown[key]) < INSTANT_HOT_COOLDOWN then
|
||||
return
|
||||
end
|
||||
instantHotCooldown[key] = now
|
||||
|
||||
if libpredict.debug then
|
||||
DEFAULT_CHAT_FRAME:AddMessage(string.format("|cff00ff00[CastSpellByName RENEW INSTANT]|r target=%s rank=%s (Fallback)", hotTarget, tostring(rankNum or "?")))
|
||||
end
|
||||
libpredict:Hot(player, hotTarget, "Renew", renewDuration, nil, "CastSpellByName-Instant", rankNum)
|
||||
local rankStr = rankNum and tostring(rankNum) or "0"
|
||||
libpredict.sender:SendHealCommMsg("Renew/"..hotTarget.."/"..renewDuration.."/"..rankStr.."/")
|
||||
end
|
||||
end)
|
||||
|
||||
local scanner = libtipscan:GetScanner("prediction")
|
||||
@@ -427,41 +786,105 @@ hooksecurefunc("UseAction", function(slot, target, selfcast)
|
||||
spell_queue[1] = effect
|
||||
spell_queue[2] = effect.. ( rank or "" )
|
||||
spell_queue[3] = selfcast and UnitName("player") or UnitName("target") and UnitCanAssist("player", "target") and UnitName("target") or UnitName("player")
|
||||
|
||||
-- Extract rank number
|
||||
local rankNum = nil
|
||||
if rank and rank ~= "" then
|
||||
rankNum = tonumber((string.gsub(rank, "Rank ", ""))) or nil
|
||||
end
|
||||
|
||||
-- Instant-HoTs: Mit SuperWoW nutzen wir UNIT_CASTEVENT (akkurater)
|
||||
-- Ohne SuperWoW: Fallback auf Hook-Methode mit Cooldown
|
||||
if superwow_active then return end
|
||||
|
||||
if effect == REJUVENATION then
|
||||
local hotTarget = spell_queue[3]
|
||||
local now = pfUI.uf.now or GetTime()
|
||||
local key = "Reju" .. hotTarget
|
||||
|
||||
-- Cooldown-Check
|
||||
if instantHotCooldown[key] and (now - instantHotCooldown[key]) < INSTANT_HOT_COOLDOWN then
|
||||
return
|
||||
end
|
||||
instantHotCooldown[key] = now
|
||||
|
||||
if libpredict.debug then
|
||||
DEFAULT_CHAT_FRAME:AddMessage(string.format("|cff00ff00[UseAction REJU INSTANT]|r target=%s rank=%s (Fallback)", hotTarget, tostring(rankNum or "?")))
|
||||
end
|
||||
libpredict:Hot(player, hotTarget, "Reju", rejuvDuration, nil, "UseAction-Instant", rankNum)
|
||||
local rankStr = rankNum and tostring(rankNum) or "0"
|
||||
libpredict.sender:SendHealCommMsg("Reju/"..hotTarget.."/"..rejuvDuration.."/"..rankStr.."/")
|
||||
elseif effect == RENEW then
|
||||
local hotTarget = spell_queue[3]
|
||||
local now = pfUI.uf.now or GetTime()
|
||||
local key = "Renew" .. hotTarget
|
||||
|
||||
-- Cooldown-Check
|
||||
if instantHotCooldown[key] and (now - instantHotCooldown[key]) < INSTANT_HOT_COOLDOWN then
|
||||
return
|
||||
end
|
||||
instantHotCooldown[key] = now
|
||||
|
||||
if libpredict.debug then
|
||||
DEFAULT_CHAT_FRAME:AddMessage(string.format("|cff00ff00[UseAction RENEW INSTANT]|r target=%s rank=%s (Fallback)", hotTarget, tostring(rankNum or "?")))
|
||||
end
|
||||
libpredict:Hot(player, hotTarget, "Renew", renewDuration, nil, "UseAction-Instant", rankNum)
|
||||
local rankStr = rankNum and tostring(rankNum) or "0"
|
||||
libpredict.sender:SendHealCommMsg("Renew/"..hotTarget.."/"..renewDuration.."/"..rankStr.."/")
|
||||
end
|
||||
end)
|
||||
|
||||
libpredict.sender = CreateFrame("Frame", "pfPredictionSender", UIParent)
|
||||
libpredict.sender.enabled = true
|
||||
libpredict.sender.SendHealCommMsg = function(self, msg)
|
||||
SendAddonMessage("HealComm", msg, "RAID")
|
||||
SendAddonMessage("HealComm", msg, "BATTLEGROUND")
|
||||
-- Smart channel selection: Only send to relevant channel to avoid duplicates
|
||||
if GetNumRaidMembers() > 0 then
|
||||
-- In raid: Only send to RAID (includes all raid members)
|
||||
SendAddonMessage("HealComm", msg, "RAID")
|
||||
elseif GetNumPartyMembers() > 0 then
|
||||
-- In party: Only send to PARTY
|
||||
SendAddonMessage("HealComm", msg, "PARTY")
|
||||
end
|
||||
-- Note: BATTLEGROUND channel not used (no reliable way to detect BG in Vanilla)
|
||||
-- BG groups are handled by RAID channel
|
||||
end
|
||||
libpredict.sender.SendResCommMsg = function(self, msg)
|
||||
SendAddonMessage("CTRA", msg, "RAID")
|
||||
SendAddonMessage("CTRA", msg, "BATTLEGROUND")
|
||||
-- Smart channel selection: Only send to relevant channel to avoid duplicates
|
||||
if GetNumRaidMembers() > 0 then
|
||||
-- In raid: Only send to RAID (includes all raid members)
|
||||
SendAddonMessage("CTRA", msg, "RAID")
|
||||
elseif GetNumPartyMembers() > 0 then
|
||||
-- In party: Only send to PARTY
|
||||
SendAddonMessage("CTRA", msg, "PARTY")
|
||||
end
|
||||
-- Note: BATTLEGROUND channel not used (no reliable way to detect BG in Vanilla)
|
||||
-- BG groups are handled by RAID channel
|
||||
end
|
||||
|
||||
libpredict.sender:SetScript("OnUpdate", function()
|
||||
-- trigger delayed regrowth timers
|
||||
if this.regrowth_timer and GetTime() > this.regrowth_timer then
|
||||
local now = pfUI.uf.now or GetTime()
|
||||
if this.regrowth_timer and now > this.regrowth_timer then
|
||||
local target = this.regrowth_target or player
|
||||
local duration = 21
|
||||
local duration = 20
|
||||
local startTime = this.regrowth_start
|
||||
local rank = this.regrowth_rank
|
||||
|
||||
libpredict:Hot(player, target, "Regr", duration)
|
||||
libpredict.sender:SendHealCommMsg("Regr/"..target.."/"..duration.."/")
|
||||
libpredict:Hot(player, target, "Regr", duration, startTime, "OnUpdate", rank)
|
||||
local rankStr = rank and tostring(rank) or "0"
|
||||
libpredict.sender:SendHealCommMsg("Regr/"..target.."/"..duration.."/"..rankStr.."/")
|
||||
|
||||
-- Übernehme nächsten Regrowth falls vorhanden
|
||||
this.regrowth_target = this.regrowth_target_next
|
||||
this.regrowth_start = this.regrowth_start_next
|
||||
this.regrowth_rank = this.regrowth_rank_next
|
||||
this.regrowth_target_next = nil
|
||||
this.regrowth_start_next = nil
|
||||
this.regrowth_rank_next = nil
|
||||
this.regrowth_timer = nil
|
||||
end
|
||||
end)
|
||||
|
||||
-- tbc
|
||||
libpredict.sender:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
|
||||
libpredict.sender:RegisterEvent("UNIT_SPELLCAST_START")
|
||||
libpredict.sender:RegisterEvent("UNIT_SPELLCAST_STOP")
|
||||
libpredict.sender:RegisterEvent("UNIT_SPELLCAST_FAILED")
|
||||
libpredict.sender:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED")
|
||||
libpredict.sender:RegisterEvent("UNIT_SPELLCAST_SENT")
|
||||
|
||||
-- vanilla
|
||||
libpredict.sender:RegisterEvent("CHAT_MSG_SPELL_SELF_BUFF")
|
||||
libpredict.sender:RegisterEvent("SPELLCAST_START")
|
||||
libpredict.sender:RegisterEvent("SPELLCAST_STOP")
|
||||
@@ -469,6 +892,18 @@ libpredict.sender:RegisterEvent("SPELLCAST_FAILED")
|
||||
libpredict.sender:RegisterEvent("SPELLCAST_INTERRUPTED")
|
||||
libpredict.sender:RegisterEvent("SPELLCAST_DELAYED")
|
||||
|
||||
-- Nampower: Register SPELL_FAILED_SELF for more reliable cast fail detection
|
||||
if hasNampower then
|
||||
libpredict.sender:RegisterEvent("SPELL_FAILED_SELF")
|
||||
if libpredict.debug then
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00[libpredict]|r Nampower detected - SPELL_FAILED_SELF registered")
|
||||
end
|
||||
else
|
||||
if libpredict.debug then
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cffffcc00[libpredict]|r Nampower NOT detected - using vanilla SPELLCAST_FAILED only")
|
||||
end
|
||||
end
|
||||
|
||||
-- force cache updates
|
||||
libpredict.sender:RegisterEvent("UNIT_INVENTORY_CHANGED")
|
||||
libpredict.sender:RegisterEvent("SKILL_LINES_CHANGED")
|
||||
@@ -498,35 +933,57 @@ libpredict.sender:SetScript("OnEvent", function()
|
||||
if spell == spell_queue[1] then UpdateCache(spell_queue[2], heal, true) end
|
||||
return
|
||||
end
|
||||
elseif event == "COMBAT_LOG_EVENT_UNFILTERED" and arg2 == "SPELL_HEAL" and arg4 == player then -- tbc
|
||||
local spell, heal, crit = arg10, arg12, arg13
|
||||
if spell and heal and crit then
|
||||
if spell == spell_queue[1] then UpdateCache(spell_queue[2], heal, true) end
|
||||
elseif spell and heal then
|
||||
if spell == spell_queue[1] then UpdateCache(spell_queue[2], heal) end
|
||||
end
|
||||
elseif event == "UNIT_SPELLCAST_SENT" and arg4 then -- fix tbc mouseover macros
|
||||
senttarget = arg4
|
||||
elseif strfind(event, "SPELLCAST_START", 1) then
|
||||
elseif event == "SPELLCAST_START" then
|
||||
local spell, time = arg1, arg2
|
||||
|
||||
if strfind(event, "UNIT_", 1) then -- tbc
|
||||
if arg1 ~= "player" then return end
|
||||
local spellname, _, _, _, starttime, endtime = UnitCastingInfo("player")
|
||||
spell, time = spellname, endtime - starttime
|
||||
|
||||
-- Resolve target from SPELL_CAST_EVENT (via libdebuff) for Nampower queued casts.
|
||||
-- SPELL_CAST_EVENT fires right before SPELLCAST_START with the actual targetGuid,
|
||||
-- solving the problem where spell_queue[3] is stale because CastSpellByName hook
|
||||
-- could not update it while current_cast was set (Nampower spell queuing).
|
||||
local pending = pfUI.libpredict_pending_cast
|
||||
local pendingTarget = nil
|
||||
if pending and pending.spellName == spell and pending.targetGuid
|
||||
and pending.time and (GetTime() - pending.time) < 1 then
|
||||
pendingTarget = UnitName(pending.targetGuid)
|
||||
-- Validate: must be a friendly unit for heal prediction
|
||||
if pendingTarget and pendingTarget ~= UNKNOWNOBJECT and pendingTarget ~= UKNOWNBEING then
|
||||
if libpredict.debug then
|
||||
DEFAULT_CHAT_FRAME:AddMessage(string.format(
|
||||
"|cff00ffff[libpredict]|r SPELL_CAST_EVENT target: %s (guid: %s) for %s",
|
||||
pendingTarget, pending.targetGuid, spell))
|
||||
end
|
||||
else
|
||||
pendingTarget = nil
|
||||
end
|
||||
-- Clear pending after consumption
|
||||
pending.spellId = nil
|
||||
pending.spellName = nil
|
||||
pending.targetGuid = nil
|
||||
pending.time = nil
|
||||
end
|
||||
|
||||
-- Speichere aktuellen Cast (wird nicht von Instant-Hooks überschrieben)
|
||||
this.current_cast = spell
|
||||
this.current_cast_target = pendingTarget or senttarget or spell_queue[3]
|
||||
|
||||
if spell_queue[1] == spell and cache[spell_queue[2]] then
|
||||
local sender = player
|
||||
local target = senttarget or spell_queue[3]
|
||||
local target = pendingTarget or senttarget or spell_queue[3]
|
||||
local amount = cache[spell_queue[2]][1]
|
||||
local casttime = time
|
||||
|
||||
if spell == REGROWTH then
|
||||
-- Extract rank from spell_queue[2] which contains "spell + rank"
|
||||
local fullSpell = spell_queue[2]
|
||||
local _, _, rankStr = fullSpell and string.find(fullSpell, "Rank (%d+)")
|
||||
local rankNum = rankStr and tonumber(rankStr) or nil
|
||||
|
||||
if this.regrowth_timer then
|
||||
this.regrowth_target_next = spell_queue[3]
|
||||
this.regrowth_target_next = pendingTarget or spell_queue[3]
|
||||
this.regrowth_rank_next = rankNum
|
||||
else
|
||||
this.regrowth_target = spell_queue[3]
|
||||
this.regrowth_target = pendingTarget or spell_queue[3]
|
||||
this.regrowth_rank = rankNum
|
||||
end
|
||||
end
|
||||
|
||||
@@ -536,88 +993,199 @@ libpredict.sender:SetScript("OnEvent", function()
|
||||
for i=1,4 do
|
||||
if CheckInteractDistance("party"..i, 4) then
|
||||
libpredict:Heal(player, UnitName("party"..i), amount, casttime)
|
||||
if pfUI.client < 20000 then -- vanilla
|
||||
libpredict.sender:SendHealCommMsg("Heal/" .. UnitName("party"..i) .. "/" .. amount .. "/" .. casttime .. "/")
|
||||
else -- tbc
|
||||
libpredict.sender:SendHealCommMsg(string.format("002%05d%s", math.min(amount, 99999), UnitName("party"..i)))
|
||||
end
|
||||
libpredict.sender:SendHealCommMsg("Heal/" .. UnitName("party"..i) .. "/" .. amount .. "/" .. casttime .. "/")
|
||||
libpredict.sender.healing = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
libpredict:Heal(player, target, amount, casttime)
|
||||
if pfUI.client < 20000 then -- vanilla
|
||||
libpredict.sender:SendHealCommMsg("Heal/" .. target .. "/" .. amount .. "/" .. casttime .. "/")
|
||||
else -- tbc
|
||||
libpredict.sender:SendHealCommMsg(string.format("002%05d%s", math.min(amount, 99999), target))
|
||||
end
|
||||
libpredict.sender:SendHealCommMsg("Heal/" .. target .. "/" .. amount .. "/" .. casttime .. "/")
|
||||
libpredict.sender.healing = true
|
||||
|
||||
elseif spell_queue[1] == spell and L["resurrections"][spell] then
|
||||
local target = senttarget or spell_queue[3]
|
||||
local target = pendingTarget or senttarget or spell_queue[3]
|
||||
libpredict:Ress(player, target)
|
||||
libpredict.sender:SendHealCommMsg("Resurrection/" .. target .. "/start/")
|
||||
libpredict.sender:SendResCommMsg("RES " .. target)
|
||||
libpredict.sender.resurrecting = true
|
||||
end
|
||||
elseif strfind(event, "SPELLCAST_FAILED", 1) or strfind(event, "SPELLCAST_INTERRUPTED", 1) then
|
||||
if strfind(event, "UNIT_", 1) and arg1 ~= "player" then return end
|
||||
elseif event == "SPELLCAST_FAILED" or event == "SPELLCAST_INTERRUPTED" then
|
||||
if libpredict.sender.healing then
|
||||
libpredict:HealStop(player)
|
||||
if pfUI.client < 20000 then -- vanilla
|
||||
libpredict.sender:SendHealCommMsg("HealStop")
|
||||
else -- tbc
|
||||
libpredict.sender:SendHealCommMsg("001F")
|
||||
|
||||
-- DEBUG: Log when sending HealStop
|
||||
if libpredict.debug then
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cffff00ff[libpredict TX]|r Sending HealStop (via " .. event .. ") to group")
|
||||
end
|
||||
libpredict.sender:SendHealCommMsg("Healstop")
|
||||
libpredict.sender.healing = nil
|
||||
elseif libpredict.sender.resurrecting then
|
||||
local target = senttarget or spell_queue[3]
|
||||
local target = this.current_cast_target or senttarget or spell_queue[3]
|
||||
libpredict:RessStop(player)
|
||||
libpredict.sender:SendHealCommMsg("Resurrection/stop/")
|
||||
libpredict.sender:SendResCommMsg("RESNO " .. target)
|
||||
libpredict.sender.resurrecting = nil
|
||||
end
|
||||
if spell_queue[1] == REGROWTH then
|
||||
-- Nutze current_cast für Regrowth cleanup
|
||||
if this.current_cast == REGROWTH then
|
||||
this.regrowth_timer = nil
|
||||
this.regrowth_start = nil
|
||||
this.regrowth_target_next = nil
|
||||
this.regrowth_start_next = nil
|
||||
end
|
||||
-- Cleanup
|
||||
this.current_cast = nil
|
||||
this.current_cast_target = nil
|
||||
elseif event == "SPELL_FAILED_SELF" then
|
||||
-- Nampower SPELL_FAILED_SELF: More reliable than vanilla SPELLCAST_FAILED
|
||||
-- Same cleanup as SPELLCAST_FAILED
|
||||
if libpredict.sender.healing then
|
||||
libpredict:HealStop(player)
|
||||
|
||||
-- DEBUG: Log when sending HealStop
|
||||
if libpredict.debug then
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cffff00ff[libpredict TX]|r Sending HealStop to group (SPELL_FAILED_SELF)")
|
||||
end
|
||||
libpredict.sender:SendHealCommMsg("Healstop")
|
||||
libpredict.sender.healing = nil
|
||||
elseif libpredict.sender.resurrecting then
|
||||
local target = this.current_cast_target or senttarget or spell_queue[3]
|
||||
libpredict:RessStop(player)
|
||||
libpredict.sender:SendHealCommMsg("Resurrection/stop/")
|
||||
libpredict.sender:SendResCommMsg("RESNO " .. target)
|
||||
libpredict.sender.resurrecting = nil
|
||||
end
|
||||
-- Regrowth cleanup
|
||||
if this.current_cast == REGROWTH then
|
||||
this.regrowth_timer = nil
|
||||
this.regrowth_start = nil
|
||||
this.regrowth_target_next = nil
|
||||
this.regrowth_start_next = nil
|
||||
end
|
||||
-- Cleanup
|
||||
this.current_cast = nil
|
||||
this.current_cast_target = nil
|
||||
elseif event == "SPELLCAST_DELAYED" then
|
||||
if libpredict.sender.healing then
|
||||
libpredict:HealDelay(player, arg1)
|
||||
libpredict.sender:SendHealCommMsg("Healdelay/" .. arg1 .. "/")
|
||||
end
|
||||
elseif strfind(event, "SPELLCAST_STOP", 1) then
|
||||
if strfind(event, "UNIT_", 1) and arg1 ~= "player" then return end
|
||||
elseif event == "SPELLCAST_STOP" then
|
||||
libpredict:HealStop(player)
|
||||
if pfUI.client < 20000 then -- vanilla
|
||||
if spell_queue[1] == REJUVENATION then
|
||||
libpredict:Hot(player, spell_queue[3], "Reju", rejuvDuration)
|
||||
libpredict.sender:SendHealCommMsg("Reju/"..spell_queue[3].."/"..rejuvDuration.."/")
|
||||
elseif spell_queue[1] == RENEW then
|
||||
libpredict:Hot(player, spell_queue[3], "Renew", renewDuration)
|
||||
libpredict.sender:SendHealCommMsg("Renew/"..spell_queue[3].."/"..renewDuration.."/")
|
||||
elseif spell_queue[1] == REGROWTH then
|
||||
this.regrowth_timer = GetTime() + 0.1
|
||||
|
||||
-- Nur Regrowth wird hier verarbeitet (hat Cast-Zeit)
|
||||
-- Nutze this.current_cast (wird bei SPELLCAST_START gesetzt, nicht von Instant-Hooks überschrieben)
|
||||
if this.current_cast == REGROWTH then
|
||||
local now = pfUI.uf.now or GetTime()
|
||||
if this.regrowth_timer then
|
||||
-- Bereits ein Regrowth aktiv, speichere für den nächsten
|
||||
this.regrowth_start_next = now
|
||||
else
|
||||
this.regrowth_start = now
|
||||
end
|
||||
else -- tbc
|
||||
--todo
|
||||
this.regrowth_timer = now + 0.1
|
||||
end
|
||||
|
||||
-- Cleanup
|
||||
this.current_cast = nil
|
||||
this.current_cast_target = nil
|
||||
end
|
||||
end)
|
||||
|
||||
function libpredict:GetHotDuration(unit, spell)
|
||||
if unit == UNKNOWNOBJECT or unit == UNKOWNBEING then return end
|
||||
|
||||
|
||||
-- NEW: Try libdebuff first (Nampower AURA_CAST events)
|
||||
if pfUI.api.libdebuff and pfUI.api.libdebuff.GetBestAuraCast then
|
||||
local _, guid = UnitExists(unit) -- FIX: Get GUID, not exists boolean!
|
||||
if guid then
|
||||
-- Get the best (highest rank) aura cast for this spell
|
||||
local spellName = spell
|
||||
|
||||
-- Map short spell codes to full names
|
||||
if spell == "Reju" then
|
||||
spellName = REJUVENATION
|
||||
elseif spell == "Regr" then
|
||||
spellName = REGROWTH
|
||||
elseif spell == "Renew" then
|
||||
spellName = RENEW
|
||||
end
|
||||
|
||||
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))
|
||||
end
|
||||
return start, duration, timeleft
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- FALLBACK: Use old prediction system (for non-Nampower clients or no AURA_CAST data)
|
||||
local start, duration, timeleft
|
||||
|
||||
local unitdata = hots[UnitName(unit)]
|
||||
if unitdata and unitdata[spell] and (unitdata[spell].start + unitdata[spell].duration) > GetTime() - 1 then
|
||||
start = unitdata[spell].start
|
||||
duration = unitdata[spell].duration
|
||||
timeleft = (start + duration) - GetTime()
|
||||
local now = pfUI.uf.now or GetTime()
|
||||
|
||||
local unitName = UnitName(unit)
|
||||
local unitdata = hots[unitName]
|
||||
|
||||
if unitdata and unitdata[spell] then
|
||||
local spellData = unitdata[spell]
|
||||
if spellData.start and spellData.duration then
|
||||
local endTime = spellData.start + spellData.duration
|
||||
if endTime > now - 1 then
|
||||
start = spellData.start
|
||||
duration = spellData.duration
|
||||
timeleft = endTime - now
|
||||
|
||||
if libpredict.debug then
|
||||
DEFAULT_CHAT_FRAME:AddMessage(string.format("|cffff9900[GetHotDuration]|r %s on %s via prediction: dur=%.1fs timeleft=%.1fs",
|
||||
spell, unit, duration, timeleft))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return start, duration, timeleft
|
||||
end
|
||||
|
||||
pfUI.api.libpredict = libpredict
|
||||
-- Debug command: /hotdebug - Show HoT tracking status
|
||||
_G.SLASH_HOTDEBUG1 = "/hotdebug"
|
||||
_G.SlashCmdList.HOTDEBUG = function()
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cff00ffff========================================|r")
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cff00ffff[HoT Tracking Debug]|r")
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cff00ffff========================================|r")
|
||||
|
||||
-- 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 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")
|
||||
DEFAULT_CHAT_FRAME:AddMessage(" Using UNIT_CASTEVENT + HealComm messages")
|
||||
|
||||
-- Show active HoTs in tracking
|
||||
local hotCount = 0
|
||||
for target, spells in pairs(hots) do
|
||||
for spell, data in pairs(spells) do
|
||||
hotCount = hotCount + 1
|
||||
end
|
||||
end
|
||||
|
||||
DEFAULT_CHAT_FRAME:AddMessage(string.format("|cff00ffff[TRACKED]|r %d HoTs in legacy system", hotCount))
|
||||
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cff00ffff========================================|r")
|
||||
DEFAULT_CHAT_FRAME:AddMessage("Tip: /libpredict.debug = true for verbose logging")
|
||||
end
|
||||
|
||||
pfUI.api.libpredict = libpredict
|
||||
+48
-13
@@ -1,6 +1,8 @@
|
||||
-- load pfUI environment
|
||||
setfenv(1, pfUI:GetEnvironment())
|
||||
|
||||
local superwow_active = HasSuperWoW()
|
||||
|
||||
--[[ librange ]]--
|
||||
-- A pfUI library that detects and caches distance to units.
|
||||
--
|
||||
@@ -39,26 +41,29 @@ local spells = {
|
||||
},
|
||||
}
|
||||
|
||||
-- use native IsSpellInRange checker for tbc and skip
|
||||
-- the whole targeting approach that is required for vanilla
|
||||
if pfUI.expansion == "tbc" then
|
||||
local spell
|
||||
-- Use Nampower's IsSpellInRange if available (vanilla only)
|
||||
-- This provides more accurate range checking without needing to find spell slots
|
||||
local nampower_spell
|
||||
if GetNampowerVersion then
|
||||
librange:RegisterEvent("LEARNED_SPELL_IN_TAB")
|
||||
librange:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||||
librange:SetScript("OnEvent", function()
|
||||
-- abort on non healing classes
|
||||
if not spells[class] then return end
|
||||
|
||||
nampower_spell = nil
|
||||
|
||||
for i = 1, GetNumSpellTabs() do
|
||||
local _, _, offset, num = GetSpellTabInfo(i)
|
||||
for id = offset + 1, offset + num do
|
||||
local name, rank = GetSpellName(id, BOOKTYPE_SPELL)
|
||||
local texture = GetSpellTexture(name)
|
||||
local texture = GetSpellTexture(id, BOOKTYPE_SPELL)
|
||||
|
||||
for _, tex in pairs(spells[class]) do
|
||||
if tex == texture then
|
||||
spell = name
|
||||
return
|
||||
if texture then
|
||||
for _, tex in pairs(spells[class]) do
|
||||
if tex == texture then
|
||||
nampower_spell = name
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -66,8 +71,12 @@ if pfUI.expansion == "tbc" then
|
||||
end)
|
||||
|
||||
function librange:UnitInSpellRange(unit)
|
||||
if not spell then return nil end
|
||||
return IsSpellInRange(spell, unit) == 1 and true or nil
|
||||
if not nampower_spell then return nil end
|
||||
-- Nampower's IsSpellInRange returns 1 if in range, 0 if not, -1 if invalid
|
||||
local result = IsSpellInRange(nampower_spell, unit)
|
||||
if result == 1 then return 1
|
||||
elseif result == 0 then return nil
|
||||
else return nil end
|
||||
end
|
||||
|
||||
-- add librange to pfUI API
|
||||
@@ -114,12 +123,25 @@ combo:SetScript("OnEvent", function()
|
||||
hascombopoints = GetComboPoints() > 0
|
||||
end)
|
||||
|
||||
-- Flag to prevent UnitXP calls during logout (crash prevention)
|
||||
local librange_isLoggingOut = false
|
||||
|
||||
librange:Hide()
|
||||
librange:RegisterEvent("ACTIONBAR_SLOT_CHANGED")
|
||||
librange:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||||
librange:RegisterEvent("PLAYER_ENTER_COMBAT")
|
||||
librange:RegisterEvent("PLAYER_LEAVE_COMBAT")
|
||||
librange:RegisterEvent("PLAYER_LOGOUT")
|
||||
librange:RegisterEvent("PLAYER_LEAVING_WORLD")
|
||||
librange:SetScript("OnEvent", function()
|
||||
-- Handle logout to prevent UnitXP crashes during shutdown
|
||||
if event == "PLAYER_LOGOUT" or event == "PLAYER_LEAVING_WORLD" then
|
||||
librange_isLoggingOut = true
|
||||
this:SetScript("OnUpdate", nil) -- Stop OnUpdate completely
|
||||
this:Hide()
|
||||
return
|
||||
end
|
||||
|
||||
-- disable range checking activities
|
||||
if pfUI_config.unitframes.rangecheck == "0" or not spells[class] then
|
||||
this:Hide()
|
||||
@@ -147,6 +169,9 @@ local target_event = TargetFrame_OnEvent
|
||||
local target_nop = function() return end
|
||||
|
||||
librange:SetScript("OnUpdate", function()
|
||||
-- Prevent UnitXP calls during logout (crash prevention)
|
||||
if librange_isLoggingOut then return end
|
||||
|
||||
if ( this.tick or 1) > GetTime() then
|
||||
return
|
||||
else
|
||||
@@ -161,7 +186,17 @@ librange:SetScript("OnUpdate", function()
|
||||
if this.id <= numunits and librange.slot then
|
||||
local unit = units[this.id]
|
||||
if not UnitIsUnit("target", unit) then
|
||||
-- try to read distance via superwow first
|
||||
-- Try UnitXP_SP3 first (most accurate distance measurement)
|
||||
local unitxp_success, unitxp_distance = pcall(function()
|
||||
return UnitXP("distanceBetween", "player", unit)
|
||||
end)
|
||||
if unitxp_success and unitxp_distance then
|
||||
unitdata[unit] = unitxp_distance < 45 and 1 or 0
|
||||
this.id = this.id + 1
|
||||
return
|
||||
end
|
||||
|
||||
-- try to read distance via superwow second
|
||||
if superwow_active then
|
||||
local x1, y1, z1 = UnitPosition("player")
|
||||
local x2, y2, z2 = UnitPosition(unit)
|
||||
@@ -282,4 +317,4 @@ function librange:UnitInSpellRange(unit)
|
||||
end
|
||||
|
||||
-- add librange to pfUI API
|
||||
pfUI.api.librange = librange
|
||||
pfUI.api.librange = librange
|
||||
@@ -0,0 +1,172 @@
|
||||
-- load pfUI environment
|
||||
setfenv(1, pfUI:GetEnvironment())
|
||||
|
||||
-- return instantly when another libthrottle is already active
|
||||
if pfUI.api.libthrottle then return end
|
||||
|
||||
-- Create libthrottle namespace
|
||||
local libthrottle = CreateFrame("Frame", "pfLibThrottle")
|
||||
pfUI.api.libthrottle = libthrottle
|
||||
|
||||
-- Preset definitions (FPS -> seconds)
|
||||
libthrottle.presets = {
|
||||
["very_slow"] = { fps = 2, delay = 0.5 },
|
||||
["slow"] = { fps = 5, delay = 0.2 },
|
||||
["normal"] = { fps = 10, delay = 0.1 },
|
||||
["fast"] = { fps = 20, delay = 0.05 },
|
||||
["very_fast"] = { fps = 30, delay = 0.033 },
|
||||
["fastest"] = { fps = 50, delay = 0.02 },
|
||||
}
|
||||
|
||||
-- Localized preset names
|
||||
libthrottle.presetNames = {
|
||||
["very_slow"] = "Very Slow (2 FPS)",
|
||||
["slow"] = "Slow (5 FPS)",
|
||||
["normal"] = "Normal (10 FPS)",
|
||||
["fast"] = "Fast (20 FPS)",
|
||||
["very_fast"] = "Very Fast (30 FPS)",
|
||||
["fastest"] = "Fastest (50 FPS)",
|
||||
["custom"] = "Custom",
|
||||
}
|
||||
|
||||
-- Default throttle categories
|
||||
libthrottle.defaults = {
|
||||
nameplates = "custom",
|
||||
nameplates_target = "custom",
|
||||
nameplates_mass = "custom",
|
||||
tooltip_cursor = "custom",
|
||||
chat_tab = "custom",
|
||||
}
|
||||
|
||||
-- Convert FPS to throttle delay in seconds
|
||||
function libthrottle:FpsToDelay(fps)
|
||||
if not fps or fps <= 0 then return 0.1 end
|
||||
return 1 / fps
|
||||
end
|
||||
|
||||
-- Convert delay to FPS
|
||||
function libthrottle:DelayToFps(delay)
|
||||
if not delay or delay <= 0 then return 10 end
|
||||
return math.floor(1 / delay)
|
||||
end
|
||||
|
||||
-- Get throttle delay for a category
|
||||
-- Returns: delay in seconds
|
||||
function libthrottle:Get(category)
|
||||
local configValue = _G.pfUI_throttle and _G.pfUI_throttle[category]
|
||||
if not configValue then
|
||||
configValue = self.defaults[category] or "normal"
|
||||
end
|
||||
|
||||
-- Check if it's a preset name
|
||||
local preset = self.presets[configValue]
|
||||
if preset then
|
||||
return preset.delay
|
||||
end
|
||||
|
||||
-- If it's "custom", read from the _custom field
|
||||
if configValue == "custom" then
|
||||
local customFps = tonumber(_G.pfUI_throttle[category .. "_custom"])
|
||||
if customFps then
|
||||
return self:FpsToDelay(customFps)
|
||||
end
|
||||
end
|
||||
|
||||
-- Fallback to normal preset
|
||||
return self.presets["normal"].delay
|
||||
end
|
||||
|
||||
-- Get FPS value for a category (for display purposes)
|
||||
function libthrottle:GetFps(category)
|
||||
local delay = self:Get(category)
|
||||
return self:DelayToFps(delay)
|
||||
end
|
||||
|
||||
-- Get preset name for a category
|
||||
function libthrottle:GetPreset(category)
|
||||
local configValue = _G.pfUI_throttle and _G.pfUI_throttle[category]
|
||||
if not configValue then
|
||||
return self.defaults[category] or "normal"
|
||||
end
|
||||
|
||||
-- Check if it's a known preset
|
||||
if self.presets[configValue] then
|
||||
return configValue
|
||||
end
|
||||
|
||||
-- Must be a custom value
|
||||
return "custom"
|
||||
end
|
||||
|
||||
-- Check if a category is using custom FPS
|
||||
function libthrottle:IsCustom(category)
|
||||
return self:GetPreset(category) == "custom"
|
||||
end
|
||||
|
||||
-- Set throttle for a category
|
||||
function libthrottle:Set(category, value)
|
||||
if not _G.pfUI_throttle then _G.pfUI_throttle = {} end
|
||||
|
||||
-- Validate preset
|
||||
if type(value) == "string" and self.presets[value] then
|
||||
_G.pfUI_throttle[category] = value
|
||||
return true
|
||||
end
|
||||
|
||||
-- If it's "custom", keep it
|
||||
if value == "custom" then
|
||||
_G.pfUI_throttle[category] = value
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
-- Reset a category to its default value
|
||||
function libthrottle:ResetToDefault(category)
|
||||
local default = self.defaults[category]
|
||||
if default then
|
||||
if not _G.pfUI_throttle then _G.pfUI_throttle = {} end
|
||||
_G.pfUI_throttle[category] = default
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- Reset all categories to defaults
|
||||
function libthrottle:ResetAllToDefaults()
|
||||
if not _G.pfUI_throttle then _G.pfUI_throttle = {} end
|
||||
for category, default in pairs(self.defaults) do
|
||||
_G.pfUI_throttle[category] = default
|
||||
end
|
||||
end
|
||||
|
||||
-- Initialize - set defaults if config doesn't exist
|
||||
libthrottle:SetScript("OnEvent", function()
|
||||
if event == "PLAYER_ENTERING_WORLD" then
|
||||
if not _G.pfUI_throttle then
|
||||
_G.pfUI_throttle = {}
|
||||
end
|
||||
|
||||
-- Set defaults for any missing categories
|
||||
for category, default in pairs(libthrottle.defaults) do
|
||||
if not _G.pfUI_throttle[category] then
|
||||
_G.pfUI_throttle[category] = default
|
||||
end
|
||||
end
|
||||
|
||||
-- Set defaults for custom fields if missing
|
||||
if not _G.pfUI_throttle.nameplates_target_custom then _G.pfUI_throttle.nameplates_target_custom = "50" end
|
||||
if not _G.pfUI_throttle.nameplates_custom then _G.pfUI_throttle.nameplates_custom = "10" end
|
||||
if not _G.pfUI_throttle.nameplates_mass_custom then _G.pfUI_throttle.nameplates_mass_custom = "7" end
|
||||
if not _G.pfUI_throttle.tooltip_cursor_custom then _G.pfUI_throttle.tooltip_cursor_custom = "10" end
|
||||
if not _G.pfUI_throttle.chat_tab_custom then _G.pfUI_throttle.chat_tab_custom = "10" end
|
||||
|
||||
this:UnregisterEvent("PLAYER_ENTERING_WORLD")
|
||||
end
|
||||
end)
|
||||
|
||||
libthrottle:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||||
|
||||
-- Export to pfUI namespace for easier access
|
||||
pfUI.throttle = libthrottle
|
||||
@@ -131,6 +131,8 @@ libunitscan:SetScript("OnEvent", function()
|
||||
if UnitIsPlayer(scan) then
|
||||
_, class = UnitClass(scan)
|
||||
level = UnitLevel(scan)
|
||||
-- UnitLevel returns -1 for unknown levels, don't overwrite known values
|
||||
level = level > 0 and level or nil
|
||||
name = UnitName(scan)
|
||||
guild = GetGuildInfo(scan)
|
||||
AddData("players", name, class, level, nil, guild)
|
||||
@@ -138,6 +140,8 @@ libunitscan:SetScript("OnEvent", function()
|
||||
_, class = UnitClass(scan)
|
||||
elite = UnitClassification(scan)
|
||||
level = UnitLevel(scan)
|
||||
-- UnitLevel returns -1 for unknown levels, don't overwrite known values
|
||||
level = level > 0 and level or nil
|
||||
name = UnitName(scan)
|
||||
AddData("mobs", name, class, level, elite)
|
||||
end
|
||||
|
||||
+114
-51
@@ -1,4 +1,4 @@
|
||||
pfUI:RegisterModule("actionbar", "vanilla:tbc", function ()
|
||||
pfUI:RegisterModule("actionbar", "vanilla", function ()
|
||||
local _, class = UnitClass("player")
|
||||
local color = RAID_CLASS_COLORS[class]
|
||||
local cr, cg, cb = color.r , color.g, color.b
|
||||
@@ -668,7 +668,10 @@ pfUI:RegisterModule("actionbar", "vanilla:tbc", function ()
|
||||
start, duration, enable = GetActionCooldown(button.id)
|
||||
end
|
||||
|
||||
CooldownFrame_SetTimer(button.cd, start, duration, enable)
|
||||
-- Nil-protect: GetActionCooldown can return nil during macro parsing/indexing
|
||||
if start and duration then
|
||||
CooldownFrame_SetTimer(button.cd, start, duration, enable or 1)
|
||||
end
|
||||
end
|
||||
|
||||
local _, active
|
||||
@@ -755,9 +758,14 @@ pfUI:RegisterModule("actionbar", "vanilla:tbc", function ()
|
||||
end
|
||||
|
||||
local self, button, unlock
|
||||
-- Main update loop with throttle for performance optimization
|
||||
local function BarsUpdate(self)
|
||||
self = self or this
|
||||
|
||||
-- Throttle for performance
|
||||
if (this.tick_main or 0) > GetTime() then return end
|
||||
this.tick_main = GetTime() + 0.025
|
||||
|
||||
-- update buttons whenever a button drag is assumed
|
||||
AssumeButtonDrag()
|
||||
|
||||
@@ -912,24 +920,40 @@ pfUI:RegisterModule("actionbar", "vanilla:tbc", function ()
|
||||
end
|
||||
|
||||
local cat, stealth
|
||||
local function IsCatStealth()
|
||||
local inCatForm = nil -- cached from buff scan
|
||||
local prowlActive = nil -- tracks if prowl is active
|
||||
|
||||
-- Full scan for cat form and prowl (only on login/reload)
|
||||
local function FullScan()
|
||||
if class ~= "DRUID" then return nil end
|
||||
cat, stealth = nil, nil
|
||||
|
||||
|
||||
local foundCat, foundStealth = nil, nil
|
||||
|
||||
for i = 0, 31 do
|
||||
local texture = GetPlayerBuffTexture(i)
|
||||
if not texture then break end
|
||||
|
||||
-- catform icon detected
|
||||
if strfind(texture, "Ability_Druid_CatForm") then
|
||||
if stealth then return true end
|
||||
cat = true
|
||||
foundCat = true
|
||||
end
|
||||
|
||||
-- stealth icon detected
|
||||
if strfind(texture, "Ability_Ambush") then
|
||||
if cat then return true end
|
||||
stealth = true
|
||||
foundStealth = true
|
||||
end
|
||||
end
|
||||
|
||||
inCatForm = foundCat
|
||||
prowlActive = foundCat and foundStealth
|
||||
return prowlActive
|
||||
end
|
||||
|
||||
-- Quick scan only for prowl (when we know we're in cat form)
|
||||
local function HasProwlBuff()
|
||||
for i = 0, 31 do
|
||||
local texture = GetPlayerBuffTexture(i)
|
||||
if not texture then break end
|
||||
if strfind(texture, "Ability_Ambush") then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return nil
|
||||
@@ -956,7 +980,76 @@ pfUI:RegisterModule("actionbar", "vanilla:tbc", function ()
|
||||
end
|
||||
|
||||
-- setup page switch frame
|
||||
local prowling = nil
|
||||
local pageswitch = CreateFrame("Frame", "pfActionBarPageSwitch", UIParent)
|
||||
pageswitch:RegisterEvent("PLAYER_AURAS_CHANGED")
|
||||
pageswitch:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||||
pageswitch:RegisterEvent("UNIT_CASTEVENT")
|
||||
pageswitch:RegisterEvent("PLAYER_LOGOUT")
|
||||
pageswitch:SetScript("OnEvent", function()
|
||||
-- Handle shutdown to prevent crash 132
|
||||
if event == "PLAYER_LOGOUT" then
|
||||
this:UnregisterAllEvents()
|
||||
this:SetScript("OnEvent", nil)
|
||||
this:SetScript("OnUpdate", nil)
|
||||
return
|
||||
end
|
||||
|
||||
if class ~= "DRUID" then return end
|
||||
|
||||
-- On login/reload: full scan
|
||||
if event == "PLAYER_ENTERING_WORLD" then
|
||||
prowling = FullScan()
|
||||
return
|
||||
end
|
||||
|
||||
-- UNIT_CASTEVENT: detect Prowl cast instantly
|
||||
-- Prowl Spell IDs: 5215 (Rank 1), 6783 (Rank 2), 9913 (Rank 3)
|
||||
if event == "UNIT_CASTEVENT" then
|
||||
local guid, target, cEvent, spellId = arg1, arg2, arg3, arg4
|
||||
local _, playerGuid = UnitExists("player")
|
||||
if guid == playerGuid and cEvent == "CAST" then
|
||||
if spellId == 5215 or spellId == 6783 or spellId == 9913 then
|
||||
-- Prowl cast detected
|
||||
inCatForm = true
|
||||
prowlActive = true
|
||||
prowling = true
|
||||
elseif spellId == 768 then
|
||||
-- Cat Form cast (Spell ID 768)
|
||||
inCatForm = true
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
-- PLAYER_AURAS_CHANGED: smart scanning
|
||||
if event == "PLAYER_AURAS_CHANGED" then
|
||||
if prowlActive then
|
||||
-- We were prowling, check if still prowling
|
||||
if HasProwlBuff() then
|
||||
prowling = true
|
||||
else
|
||||
-- Prowl ended
|
||||
prowlActive = nil
|
||||
prowling = nil
|
||||
-- Also check if still in cat form
|
||||
inCatForm = nil
|
||||
for i = 0, 31 do
|
||||
local texture = GetPlayerBuffTexture(i)
|
||||
if not texture then break end
|
||||
if strfind(texture, "Ability_Druid_CatForm") then
|
||||
inCatForm = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif not inCatForm then
|
||||
-- Not in cat form, do a full scan (might have just shifted)
|
||||
prowling = FullScan()
|
||||
end
|
||||
-- If inCatForm but not prowlActive, no scan needed (wait for UNIT_CASTEVENT)
|
||||
end
|
||||
end)
|
||||
pageswitch:SetScript("OnUpdate", function()
|
||||
-- switch actionbar page depending on meta key that is pressed
|
||||
if C.bars.pagemastershift == "1" and IsShiftKeyDown() then
|
||||
@@ -974,10 +1067,9 @@ pfUI:RegisterModule("actionbar", "vanilla:tbc", function ()
|
||||
|
||||
-- switch actionbar page if druid stealth is detected
|
||||
if C.bars.druidstealth == "1" then
|
||||
local stealth = IsCatStealth()
|
||||
if stealth and _G.CURRENT_ACTIONBAR_PAGE == 1 then
|
||||
if prowling and _G.CURRENT_ACTIONBAR_PAGE == 1 then
|
||||
SwitchBar(prowl)
|
||||
elseif not stealth and _G.CURRENT_ACTIONBAR_PAGE == 8 then
|
||||
elseif not prowling and _G.CURRENT_ACTIONBAR_PAGE == 8 then
|
||||
SwitchBar(default)
|
||||
end
|
||||
end
|
||||
@@ -1109,39 +1201,6 @@ pfUI:RegisterModule("actionbar", "vanilla:tbc", function ()
|
||||
buttoncache[id] = f
|
||||
end
|
||||
|
||||
-- set required attributes for regular tbc buttons
|
||||
if pfUI.client > 11200 then
|
||||
if bar == 11 then
|
||||
f:SetAttribute("type", "spell")
|
||||
f:SetAttribute('spell', select(2, GetShapeshiftFormInfo(button)))
|
||||
elseif bar == 12 then
|
||||
f:SetAttribute("type1", "pet")
|
||||
f:SetAttribute("action1", button)
|
||||
f:SetAttribute("type2", "macro")
|
||||
f:SetAttribute("macrotext2", "/click PetActionButton".. button .. " RightButton")
|
||||
else
|
||||
bars[bar]:SetAttribute("addchild", f)
|
||||
f:SetAttribute("type", "action")
|
||||
f:SetAttribute("action", id)
|
||||
f:SetAttribute("checkselfcast", true)
|
||||
f:SetAttribute("useparent-unit", true)
|
||||
f:SetAttribute("useparent-statebutton", true)
|
||||
|
||||
for state = 0, 11 do -- add custom states
|
||||
local action = ((state == 0 and bar or state)-1)*12+button
|
||||
f:SetAttribute(string.format("*type-S%d", state), "action")
|
||||
f:SetAttribute(string.format("*type-S%dRight", state), "action")
|
||||
f:SetAttribute(string.format("*action-S%d", state), action)
|
||||
f:SetAttribute(string.format("*action-S%dRight", state), action)
|
||||
if C.bars.rightself == "1" then
|
||||
f:SetAttribute(string.format("*unit-S%dRight", state), "player")
|
||||
else
|
||||
f:SetAttribute(string.format("*unit-S%dRight", state), nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- set keydown option
|
||||
if C.bars.keydown == "1" then
|
||||
f:RegisterForClicks("LeftButtonDown", "RightButtonDown")
|
||||
@@ -1183,8 +1242,8 @@ pfUI:RegisterModule("actionbar", "vanilla:tbc", function ()
|
||||
f.count:SetJustifyH("RIGHT")
|
||||
f.count:SetJustifyV("BOTTOM")
|
||||
|
||||
-- macro spell scan
|
||||
if C.bars.macroscan == "0" then
|
||||
-- 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
|
||||
@@ -1653,9 +1712,13 @@ pfUI:RegisterModule("actionbar", "vanilla:tbc", function ()
|
||||
end
|
||||
end)
|
||||
|
||||
-- limit events to one per second and smoothen action scanning
|
||||
-- Reagent counter update with throttle for performance optimization
|
||||
reagentcounter:SetScript("OnUpdate", function()
|
||||
-- scan one action slot per frame
|
||||
-- Throttle entire function to 10 FPS for smooth scanning
|
||||
if (this.tick_update or 0) > GetTime() then return end
|
||||
this.tick_update = GetTime() + 0.1
|
||||
|
||||
-- scan one action slot per update
|
||||
if this.scan and this.scan <= 120 then
|
||||
UpdateSlot(this.scan)
|
||||
this.scan = this.scan + 1
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
pfUI:RegisterModule("bgscore", "vanilla", function ()
|
||||
local bgframe = WorldStateAlwaysUpFrame
|
||||
if not bgframe then
|
||||
bgframe = CreateFrame("Frame", "WorldStateAlwaysUpFrame", UIParent)
|
||||
bgframe:SetWidth(200)
|
||||
bgframe:SetHeight(25)
|
||||
bgframe:SetPoint("TOP", UIParent, "TOP", 0, -100)
|
||||
end
|
||||
|
||||
local mover = CreateFrame("Frame", "pfUIBGScoreMover", UIParent)
|
||||
mover:SetWidth(220)
|
||||
mover:SetHeight(30)
|
||||
mover:SetPoint("TOP", UIParent, "TOP", 0, -100)
|
||||
mover:SetFrameStrata("DIALOG")
|
||||
mover:SetMovable(true)
|
||||
mover:EnableMouse(true)
|
||||
mover:RegisterForDrag("LeftButton")
|
||||
mover:SetScript("OnDragStart", function() mover:StartMoving() end)
|
||||
mover:SetScript("OnDragStop", function()
|
||||
mover:StopMovingOrSizing()
|
||||
local x = mover:GetLeft()
|
||||
local y = mover:GetTop()
|
||||
pfUI_config = pfUI_config or {}
|
||||
pfUI_config.positions = pfUI_config.positions or {}
|
||||
pfUI_config.positions["WorldStateAlwaysUpFrame"] = { x = x, y = y }
|
||||
bgframe:ClearAllPoints()
|
||||
bgframe:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", x, y)
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cff33ffccBG Score Frame|r position saved.")
|
||||
end)
|
||||
mover:Hide()
|
||||
|
||||
pfUI.api.CreateBackdrop(mover, nil, nil, .8)
|
||||
|
||||
-- Title label
|
||||
local title = mover:CreateFontString(nil, "OVERLAY")
|
||||
title:SetFont("Fonts\\FRIZQT__.TTF", 14, "OUTLINE")
|
||||
title:SetText("Battleground Frames")
|
||||
title:SetPoint("TOP", mover, "TOP", 0, -2)
|
||||
|
||||
-- BG score preview text
|
||||
local bgscore = mover:CreateFontString(nil, "OVERLAY")
|
||||
bgscore:SetFont("Fonts\\FRIZQT__.TTF", 10, "OUTLINE")
|
||||
bgscore:SetText("|cff3399ffAlliance: 123|r | |cffff4444Horde: 456|r")
|
||||
bgscore:SetPoint("BOTTOM", mover, "BOTTOM", 0, 2)
|
||||
|
||||
mover.label = "BG Score"
|
||||
pfUI.unlock.frames = pfUI.unlock.frames or {}
|
||||
table.insert(pfUI.unlock.frames, mover)
|
||||
|
||||
local pos = pfUI_config and pfUI_config.positions and pfUI_config.positions["WorldStateAlwaysUpFrame"]
|
||||
if pos then
|
||||
bgframe:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", pos.x, pos.y)
|
||||
mover:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", pos.x, pos.y)
|
||||
end
|
||||
|
||||
local origShow = pfUI.unlock.Show
|
||||
local origHide = pfUI.unlock.Hide
|
||||
pfUI.unlock.Show = function(self) origShow(self); mover:Show() end
|
||||
pfUI.unlock.Hide = function(self) origHide(self); mover:Hide(); bgframe:Show() end
|
||||
end)
|
||||
+116
-26
@@ -76,8 +76,55 @@ pfUI:RegisterModule("buff", "vanilla:tbc", function ()
|
||||
buff.backdrop:SetBackdropBorderColor(br,bg,bb,ba)
|
||||
end
|
||||
else
|
||||
buff:Hide()
|
||||
return
|
||||
-- Fallback: try UnitBuff/UnitDebuff API which may be more reliable in some cases
|
||||
local fallbackTexture, fallbackStacks, fallbackDispelType, fallbackSpellId
|
||||
local maxSlots = buff.btype == "HELPFUL" and 32 or 16
|
||||
|
||||
if buff.id >= 1 and buff.id <= maxSlots then
|
||||
if buff.btype == "HELPFUL" and C.buffs.buffs == "1" then
|
||||
for i = 1, maxSlots do
|
||||
local tex, stacks, dtype, spellId = UnitBuff("player", i)
|
||||
if tex and i == buff.id then
|
||||
fallbackTexture, fallbackStacks, fallbackDispelType, fallbackSpellId = tex, stacks, dtype, spellId
|
||||
break
|
||||
end
|
||||
if not tex then break end
|
||||
end
|
||||
elseif buff.btype == "HARMFUL" and C.buffs.debuffs == "1" then
|
||||
for i = 1, maxSlots do
|
||||
local tex, stacks, dtype, spellId = UnitDebuff("player", i)
|
||||
if tex and i == buff.id then
|
||||
fallbackTexture, fallbackStacks, fallbackDispelType, fallbackSpellId = tex, stacks, dtype, spellId
|
||||
break
|
||||
end
|
||||
if not tex then break end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if fallbackTexture then
|
||||
buff.mode = buff.btype
|
||||
buff.fallbackSpellId = fallbackSpellId
|
||||
buff.texture:SetTexture(fallbackTexture)
|
||||
if buff.btype == "HARMFUL" then
|
||||
if fallbackDispelType == "Magic" then
|
||||
buff.backdrop:SetBackdropBorderColor(0,1,1,1)
|
||||
elseif fallbackDispelType == "Poison" then
|
||||
buff.backdrop:SetBackdropBorderColor(0,1,0,1)
|
||||
elseif fallbackDispelType == "Curse" then
|
||||
buff.backdrop:SetBackdropBorderColor(1,0,1,1)
|
||||
elseif fallbackDispelType == "Disease" then
|
||||
buff.backdrop:SetBackdropBorderColor(1,1,0,1)
|
||||
else
|
||||
buff.backdrop:SetBackdropBorderColor(1,0,0,1)
|
||||
end
|
||||
else
|
||||
buff.backdrop:SetBackdropBorderColor(br,bg,bb,ba)
|
||||
end
|
||||
else
|
||||
buff:Hide()
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
buff:Show()
|
||||
@@ -119,30 +166,8 @@ pfUI:RegisterModule("buff", "vanilla:tbc", function ()
|
||||
buff.btype = btype
|
||||
buff.gid = i
|
||||
|
||||
buff:SetScript("OnUpdate", function()
|
||||
if not this.next then this.next = GetTime() + .1 end
|
||||
if this.next > GetTime() then return end
|
||||
this.next = GetTime() + .1
|
||||
|
||||
local timeleft = 0
|
||||
local stacks = 0
|
||||
|
||||
if this.mode == this.btype then
|
||||
timeleft = GetPlayerBuffTimeLeft(this.bid, this.btype)
|
||||
stacks = GetPlayerBuffApplications(this.bid, this.btype)
|
||||
elseif this.mode == "MAINHAND" then
|
||||
local _, mhtime, mhcharge = GetWeaponEnchantInfo()
|
||||
timeleft = mhtime/1000
|
||||
stacks = mhcharge
|
||||
elseif this.mode == "OFFHAND" then
|
||||
local _, _, _, _, ohtime, ohcharge = GetWeaponEnchantInfo()
|
||||
timeleft = ohtime/1000
|
||||
stacks = ohcharge
|
||||
end
|
||||
|
||||
this.timer:SetText(timeleft > 0 and GetColoredTimeString(timeleft) or "")
|
||||
this.stacks:SetText(stacks > 1 and stacks or "")
|
||||
end)
|
||||
-- PERF: OnUpdate moved to consolidated parent frame handler (see pfUI.buff:SetScript("OnUpdate"))
|
||||
-- Individual buff frames no longer have their own OnUpdate
|
||||
|
||||
buff:SetScript("OnEnter", function()
|
||||
GameTooltip:SetOwner(this, "ANCHOR_BOTTOMRIGHT")
|
||||
@@ -251,6 +276,71 @@ pfUI:RegisterModule("buff", "vanilla:tbc", function ()
|
||||
end
|
||||
end)
|
||||
|
||||
-- PERF: Consolidated OnUpdate handler for all buff timers
|
||||
-- This replaces 50 individual OnUpdate handlers with a single one
|
||||
pfUI.buff:SetScript("OnUpdate", function()
|
||||
local now = GetTime()
|
||||
if not this.nextUpdate then this.nextUpdate = now + 0.1 end
|
||||
if this.nextUpdate > now then return end
|
||||
this.nextUpdate = now + 0.1
|
||||
|
||||
-- Cache weapon enchant info once per update cycle
|
||||
local mh, mhtime, mhcharge, oh, ohtime, ohcharge = GetWeaponEnchantInfo()
|
||||
|
||||
-- Update all visible buff buttons
|
||||
local buttons = pfUI.buff.buffs.buttons
|
||||
for i = 1, 32 do
|
||||
local buff = buttons[i]
|
||||
if buff:IsShown() then
|
||||
local timeleft, stacks = 0, 0
|
||||
if buff.mode == buff.btype then
|
||||
timeleft = GetPlayerBuffTimeLeft(buff.bid, buff.btype)
|
||||
stacks = GetPlayerBuffApplications(buff.bid, buff.btype)
|
||||
elseif buff.mode == "MAINHAND" then
|
||||
timeleft = mhtime and mhtime / 1000 or 0
|
||||
stacks = mhcharge or 0
|
||||
elseif buff.mode == "OFFHAND" then
|
||||
timeleft = ohtime and ohtime / 1000 or 0
|
||||
stacks = ohcharge or 0
|
||||
end
|
||||
buff.timer:SetText(timeleft > 0 and GetColoredTimeString(timeleft) or "")
|
||||
buff.stacks:SetText(stacks > 1 and stacks or "")
|
||||
end
|
||||
end
|
||||
|
||||
-- Update all visible debuff buttons
|
||||
buttons = pfUI.buff.debuffs.buttons
|
||||
for i = 1, 16 do
|
||||
local buff = buttons[i]
|
||||
if buff:IsShown() then
|
||||
local timeleft = GetPlayerBuffTimeLeft(buff.bid, buff.btype)
|
||||
local stacks = GetPlayerBuffApplications(buff.bid, buff.btype)
|
||||
buff.timer:SetText(timeleft > 0 and GetColoredTimeString(timeleft) or "")
|
||||
buff.stacks:SetText(stacks > 1 and stacks or "")
|
||||
end
|
||||
end
|
||||
|
||||
-- Update weapon buff buttons if separate
|
||||
if C.buffs.separateweapons == "1" then
|
||||
buttons = pfUI.buff.wepbuffs.buttons
|
||||
for i = 1, 2 do
|
||||
local buff = buttons[i]
|
||||
if buff:IsShown() then
|
||||
local timeleft, stacks = 0, 0
|
||||
if buff.mode == "MAINHAND" then
|
||||
timeleft = mhtime and mhtime / 1000 or 0
|
||||
stacks = mhcharge or 0
|
||||
elseif buff.mode == "OFFHAND" then
|
||||
timeleft = ohtime and ohtime / 1000 or 0
|
||||
stacks = ohcharge or 0
|
||||
end
|
||||
buff.timer:SetText(timeleft > 0 and GetColoredTimeString(timeleft) or "")
|
||||
buff.stacks:SetText(stacks > 1 and stacks or "")
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- Weapon Buffs
|
||||
pfUI.buff.wepbuffs = CreateFrame("Frame", "pfWepBuffFrame", UIParent)
|
||||
pfUI.buff.wepbuffs.count = 0
|
||||
|
||||
+25
-2
@@ -125,7 +125,23 @@ pfUI:RegisterModule("buffwatch", "vanilla:tbc", function ()
|
||||
if this.unit == "player" then
|
||||
GameTooltip:SetPlayerBuff(GetPlayerBuff(PLAYER_BUFF_START_ID+this.id,this.type))
|
||||
elseif this.type == "HARMFUL" then
|
||||
GameTooltip:SetUnitDebuff(this.unit, this.id)
|
||||
-- For "only own debuffs" mode: find the REAL slot by matching spell name AND caster
|
||||
local config = this.parent and this.parent.config
|
||||
if config and config.selfdebuff == "1" and libdebuff then
|
||||
local ownDebuffName = libdebuff:UnitOwnDebuff(this.unit, this.id)
|
||||
if ownDebuffName then
|
||||
-- Search through all game slots to find OUR debuff with matching name
|
||||
for gameSlot = 1, 16 do
|
||||
local gameName, _, _, _, _, _, _, gameCaster = libdebuff:UnitDebuff(this.unit, gameSlot)
|
||||
if gameName == ownDebuffName and gameCaster == "player" then
|
||||
GameTooltip:SetUnitDebuff(this.unit, gameSlot)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
GameTooltip:SetUnitDebuff(this.unit, this.id)
|
||||
end
|
||||
elseif this.type == "HELPFUL" then
|
||||
GameTooltip:SetUnitBuff(this.unit, this.id)
|
||||
end
|
||||
@@ -261,7 +277,14 @@ pfUI:RegisterModule("buffwatch", "vanilla:tbc", function ()
|
||||
and data[3] and data[3] ~= "" -- buff has a name
|
||||
and data[4] and data[4] ~= "" -- buff has a texture
|
||||
then
|
||||
local uuid = data[4] .. data[3] -- we use that to cache some values for buffs
|
||||
-- For player: no slot in uuid (slots shift when other buffs expire)
|
||||
-- For target: include slot (multiple players can have same debuff, slot identifies who)
|
||||
local uuid
|
||||
if frame.unit == "player" then
|
||||
uuid = data[4] .. data[3] -- texture + name only
|
||||
else
|
||||
uuid = data[4] .. data[3] .. data[2] -- texture + name + slot
|
||||
end
|
||||
|
||||
-- update bar data
|
||||
frame.bars[bar] = frame.bars[bar] or CreateStatusBar(bar, frame)
|
||||
|
||||
+88
-10
@@ -1,9 +1,22 @@
|
||||
pfUI:RegisterModule("castbar", "vanilla:tbc", function ()
|
||||
pfUI:RegisterModule("castbar", "vanilla", function ()
|
||||
local superwow_active = HasSuperWoW()
|
||||
|
||||
local font = C.castbar.use_unitfonts == "1" and pfUI.font_unit or pfUI.font_default
|
||||
local font_size = C.castbar.use_unitfonts == "1" and C.global.font_unit_size or C.global.font_size
|
||||
local rawborder, default_border = GetBorderSize("unitframes")
|
||||
local cbtexture = pfUI.media[C.appearance.castbar.texture]
|
||||
|
||||
-- Helper function for castbar timer formatting
|
||||
local function FormatCastbarTime(value)
|
||||
if C.unitframes.castbardecimals == "1" then
|
||||
-- 1 decimal, always floor
|
||||
return string.format("%.1f", floor(value * 10) / 10)
|
||||
else
|
||||
-- 2 decimals (default)
|
||||
return string.format("%.2f", value)
|
||||
end
|
||||
end
|
||||
|
||||
local function CreateCastbar(name, parent, unitstr, unitname)
|
||||
local cb = CreateFrame("Frame", name, parent or UIParent)
|
||||
|
||||
@@ -64,7 +77,12 @@ pfUI:RegisterModule("castbar", "vanilla:tbc", function ()
|
||||
cb.bar.lag:SetPoint("BOTTOMRIGHT", cb.bar, "BOTTOMRIGHT", 0, 0)
|
||||
cb.bar.lag:SetTexture(1,.2,.2,.2)
|
||||
|
||||
-- OnUpdate script with throttle for performance optimization
|
||||
cb:SetScript("OnUpdate", function()
|
||||
-- Throttle for performance
|
||||
if (this.tick or 0) > GetTime() then return end
|
||||
this.tick = GetTime() + 0.020 -- ~60 FPS for smooth castbar
|
||||
|
||||
if this.drag and this.drag:IsShown() then
|
||||
this:SetAlpha(1)
|
||||
return
|
||||
@@ -86,14 +104,46 @@ pfUI:RegisterModule("castbar", "vanilla:tbc", function ()
|
||||
local query = this.unitstr ~= "" and this.unitstr or this.unitname
|
||||
if not query then return end
|
||||
|
||||
-- transform all non player unitstrings to unit guids
|
||||
if superwow_active and this.unitstr and not UnitIsUnit(this.unitstr, 'player') then
|
||||
-- Check if we have a GUID-based focus (Turtle WoW native GUID)
|
||||
local focusGuid = nil
|
||||
if this.unitstr and string.find(this.unitstr, "^0x") then
|
||||
focusGuid = this.unitstr
|
||||
end
|
||||
|
||||
-- Try libdebuff_casts first for GUID-based units (works with Turtle GUID + Nampower events)
|
||||
local cast, nameSubtext, text, texture, startTime, endTime
|
||||
if focusGuid and pfUI.libdebuff_casts and pfUI.libdebuff_casts[focusGuid] then
|
||||
local castData = pfUI.libdebuff_casts[focusGuid]
|
||||
if castData.event == "START" and castData.endTime and castData.endTime > GetTime() then
|
||||
cast = castData.spellName
|
||||
texture = castData.icon
|
||||
startTime = castData.startTime * 1000 -- libdebuff uses seconds, castbar expects milliseconds
|
||||
endTime = castData.endTime * 1000
|
||||
nameSubtext = "" -- Rank info not available in libdebuff_casts
|
||||
end
|
||||
end
|
||||
|
||||
-- Fallback: transform unitstrings to unit guids when SuperWoW is active
|
||||
-- SuperWoW stores cast data by GUID for all units INCLUDING player
|
||||
-- BUT: For player casts, we need to use libcast data because it handles pushback correctly
|
||||
local useLibcastForPlayer = this.unitstr == "player"
|
||||
|
||||
if not cast and superwow_active and this.unitstr and not useLibcastForPlayer then
|
||||
local _, guid = UnitExists(this.unitstr)
|
||||
query = guid or query
|
||||
end
|
||||
|
||||
-- For player: use player name to query libcast.db directly
|
||||
if not cast and useLibcastForPlayer then
|
||||
query = UnitName("player")
|
||||
end
|
||||
|
||||
local cast, nameSubtext, text, texture, startTime, endTime, isTradeSkill = UnitCastingInfo(query)
|
||||
if not cast then
|
||||
-- Fallback: Try UnitCastingInfo if we haven't found cast data yet
|
||||
if not cast and UnitCastingInfo then
|
||||
cast, nameSubtext, text, texture, startTime, endTime, isTradeSkill = UnitCastingInfo(query)
|
||||
end
|
||||
|
||||
if not cast and UnitChannelInfo then
|
||||
-- scan for channel spells if no cast was found
|
||||
channel, nameSubtext, text, texture, startTime, endTime, isTradeSkill = UnitChannelInfo(query)
|
||||
cast = channel
|
||||
@@ -122,8 +172,35 @@ pfUI:RegisterModule("castbar", "vanilla:tbc", function ()
|
||||
this.icon:Show()
|
||||
this.icon:SetHeight(size)
|
||||
this.icon:SetWidth(size)
|
||||
this.icon.texture:SetTexture(texture)
|
||||
|
||||
-- Override with item icon from libdebuff_casts or persistent item icon cache
|
||||
local useTexture = texture
|
||||
local useItemName = nil
|
||||
if pfUI.libdebuff_casts or pfUI.libdebuff_item_icons then
|
||||
local castGuid = nil
|
||||
if this.unitstr and UnitExists then
|
||||
local _, guid = UnitExists(this.unitstr)
|
||||
castGuid = guid
|
||||
end
|
||||
if castGuid then
|
||||
-- First check active cast data
|
||||
if pfUI.libdebuff_casts and pfUI.libdebuff_casts[castGuid] and pfUI.libdebuff_casts[castGuid].itemID then
|
||||
useTexture = pfUI.libdebuff_casts[castGuid].icon or texture
|
||||
-- Fallback to persistent item icon cache
|
||||
elseif pfUI.libdebuff_item_icons and pfUI.libdebuff_item_icons[castGuid] then
|
||||
useTexture = pfUI.libdebuff_item_icons[castGuid].icon or texture
|
||||
useItemName = pfUI.libdebuff_item_icons[castGuid].name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
this.icon.texture:SetTexture(useTexture)
|
||||
this.bar:SetPoint("TOPLEFT", this.icon, "TOPRIGHT", this.spacing, 0)
|
||||
|
||||
-- Override spell name with item name for item-triggered casts
|
||||
if useItemName and this.showname then
|
||||
this.bar.left:SetText(useItemName .. " " .. rank)
|
||||
end
|
||||
else
|
||||
this.bar:SetPoint("TOPLEFT", this, 0, 0)
|
||||
this.icon:Hide()
|
||||
@@ -149,10 +226,10 @@ pfUI:RegisterModule("castbar", "vanilla:tbc", function ()
|
||||
|
||||
if this.showtimer then
|
||||
if this.delay and this.delay > 0 then
|
||||
local delay = "|cffffaaaa" .. (channel and "-" or "+") .. round(this.delay,1) .. " |r "
|
||||
this.bar.right:SetText(delay .. string.format("%.1f",cur) .. " / " .. round(max,1))
|
||||
local delay = "|cffffaaaa" .. (channel and "-" or "+") .. FormatCastbarTime(this.delay) .. " |r "
|
||||
this.bar.right:SetText(delay .. FormatCastbarTime(cur) .. " / " .. FormatCastbarTime(max))
|
||||
else
|
||||
this.bar.right:SetText(string.format("%.1f",cur) .. " / " .. round(max,1))
|
||||
this.bar.right:SetText(FormatCastbarTime(cur) .. " / " .. FormatCastbarTime(max))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -162,6 +239,7 @@ pfUI:RegisterModule("castbar", "vanilla:tbc", function ()
|
||||
this.bar:SetValue(100)
|
||||
this.fadeout = 1
|
||||
this.delay = 0
|
||||
this.itemIconApplied = nil
|
||||
end
|
||||
end)
|
||||
|
||||
@@ -310,4 +388,4 @@ pfUI:RegisterModule("castbar", "vanilla:tbc", function ()
|
||||
|
||||
UpdateMovable(pfUI.castbar.focus)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
@@ -455,6 +455,8 @@ pfUI:RegisterModule("chat", "vanilla:tbc", function ()
|
||||
if C.chat.global.tabmouse == "1" then
|
||||
pfUI.chat.mouseovertab = CreateFrame("Frame")
|
||||
pfUI.chat.mouseovertab:SetScript("OnUpdate", function()
|
||||
-- throttle
|
||||
if ( this.tick or .1) > GetTime() then return else this.tick = GetTime() + pfUI.throttle:Get("chat_tab") end -- Default: Normal (10 FPS)
|
||||
|
||||
if pfUI.chat.hideLock then return end
|
||||
|
||||
@@ -725,6 +727,12 @@ pfUI:RegisterModule("chat", "vanilla:tbc", function ()
|
||||
end
|
||||
end)
|
||||
|
||||
local function GetPlayerLevel(name)
|
||||
if not pfUI_playerDB then return nil end
|
||||
if not pfUI_playerDB[name] then return nil end
|
||||
return pfUI_playerDB[name].level
|
||||
end
|
||||
|
||||
local function ScanWhoName(name)
|
||||
-- abort if another query is ongoing
|
||||
if who_query.pending then return end
|
||||
@@ -783,6 +791,25 @@ pfUI:RegisterModule("chat", "vanilla:tbc", function ()
|
||||
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" then
|
||||
local channel = string.gsub(text, ".*%[(.-)%]%s+(.*|Hplayer).+", "%1")
|
||||
|
||||
@@ -144,4 +144,4 @@ pfUI:RegisterModule("cooldown", "vanilla:tbc", function ()
|
||||
local methods = getmetatable(CreateFrame('Cooldown', nil, nil, 'CooldownFrameTemplate')).__index
|
||||
hooksecurefunc(methods, 'SetCooldown', SetCooldown)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
+20
-6
@@ -7,6 +7,9 @@ pfUI:RegisterModule("energytick", "vanilla:tbc", function ()
|
||||
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") == 0 and C.unitframes.player.manatick == "1" then
|
||||
this.mode = "MANA"
|
||||
@@ -18,6 +21,14 @@ pfUI:RegisterModule("energytick", "vanilla:tbc", function ()
|
||||
this:Hide()
|
||||
end
|
||||
|
||||
-- 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.lastMana = UnitMana("player")
|
||||
end
|
||||
@@ -38,13 +49,20 @@ pfUI:RegisterModule("energytick", "vanilla:tbc", function ()
|
||||
this.badtick = diff
|
||||
end
|
||||
elseif this.mode == "ENERGY" and diff > 0 then
|
||||
this.target = 2
|
||||
if not this.ignoreNextGain then
|
||||
this.target = 2
|
||||
end
|
||||
this.ignoreNextGain = false
|
||||
end
|
||||
this.lastMana = this.currentMana
|
||||
end
|
||||
end)
|
||||
|
||||
energytick:SetScript("OnUpdate", function()
|
||||
-- Throttle for performance
|
||||
if (this.tick or 0) > GetTime() then return end
|
||||
this.tick = GetTime() + 0.020
|
||||
|
||||
if this.target then
|
||||
this.start, this.max = GetTime(), this.target
|
||||
this.target = nil
|
||||
@@ -69,14 +87,10 @@ pfUI:RegisterModule("energytick", "vanilla:tbc", function ()
|
||||
energytick.spark:SetWidth(C.unitframes.player.pheight + 5)
|
||||
energytick.spark:SetBlendMode('ADD')
|
||||
|
||||
-- update spark size on player frame changes
|
||||
local hookUpdateConfig = pfUI.uf.player.UpdateConfig
|
||||
function pfUI.uf.player.UpdateConfig()
|
||||
-- update spark sizes
|
||||
energytick.spark:SetHeight(C.unitframes.player.pheight + 15)
|
||||
energytick.spark:SetWidth(C.unitframes.player.pheight + 5)
|
||||
|
||||
-- run default unitframe update function
|
||||
hookUpdateConfig(pfUI.uf.player)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
+152
-13
@@ -17,17 +17,95 @@ end)
|
||||
|
||||
-- register focus emulation commands for vanilla
|
||||
if pfUI.client > 11200 then return end
|
||||
|
||||
-- Helper: set focus frame to a GUID
|
||||
local function SetFocusByGUID(guid)
|
||||
pfUI.uf.focus.unitname = nil
|
||||
pfUI.uf.focus.label = guid
|
||||
pfUI.uf.focus.id = ""
|
||||
|
||||
if pfUI.uf.focustarget then
|
||||
pfUI.uf.focustarget.unitname = nil
|
||||
pfUI.uf.focustarget.label = guid .. "target"
|
||||
pfUI.uf.focustarget.id = ""
|
||||
end
|
||||
end
|
||||
|
||||
-- Helper: set focus frame by name (fallback, no Nampower)
|
||||
local function SetFocusByName(name)
|
||||
pfUI.uf.focus.unitname = strlower(name)
|
||||
pfUI.uf.focus.label = nil
|
||||
pfUI.uf.focus.id = nil
|
||||
|
||||
if pfUI.uf.focustarget then
|
||||
pfUI.uf.focustarget.unitname = strlower(name) .. "target"
|
||||
pfUI.uf.focustarget.label = nil
|
||||
pfUI.uf.focustarget.id = nil
|
||||
end
|
||||
end
|
||||
|
||||
SLASH_PFFOCUS1, SLASH_PFFOCUS2 = '/focus', '/pffocus'
|
||||
function SlashCmdList.PFFOCUS(msg)
|
||||
if not pfUI.uf or not pfUI.uf.focus then return end
|
||||
|
||||
if msg ~= "" then
|
||||
pfUI.uf.focus.unitname = strlower(msg)
|
||||
elseif UnitName("target") then
|
||||
pfUI.uf.focus.unitname = strlower(UnitName("target"))
|
||||
-- Try to resolve GUID via short target swap
|
||||
if UnitExists then
|
||||
local _, prevGUID = UnitExists("target")
|
||||
local prevPlayer = UnitIsUnit("target", "player")
|
||||
|
||||
-- Suppress "Unknown unit" errors during targeting attempts (fired async)
|
||||
UIErrorsFrame:UnregisterEvent("UI_ERROR_MESSAGE")
|
||||
|
||||
-- Try exact match first, then prefix match via /tar
|
||||
TargetByName(msg, true)
|
||||
local _, guid = UnitExists("target")
|
||||
|
||||
if not guid or guid == "0x0000000000000000" then
|
||||
-- Fallback: prefix match (like /tar storm -> Stormwind Guard)
|
||||
SlashCmdList.TARGET(msg)
|
||||
_, guid = UnitExists("target")
|
||||
end
|
||||
|
||||
-- Re-enable errors next frame (errors are fired async)
|
||||
local restore = CreateFrame("Frame")
|
||||
restore:SetScript("OnUpdate", function()
|
||||
UIErrorsFrame:RegisterEvent("UI_ERROR_MESSAGE")
|
||||
restore:SetScript("OnUpdate", nil)
|
||||
end)
|
||||
|
||||
-- Restore previous target
|
||||
if prevGUID and prevGUID ~= "0x0000000000000000" then
|
||||
TargetUnit(prevGUID)
|
||||
elseif prevPlayer then
|
||||
TargetUnit("player")
|
||||
else
|
||||
ClearTarget()
|
||||
end
|
||||
|
||||
if guid and guid ~= "0x0000000000000000" then
|
||||
SetFocusByGUID(guid)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- Fallback: name-based (non-Nampower clients)
|
||||
SetFocusByName(msg)
|
||||
else
|
||||
pfUI.uf.focus.unitname = nil
|
||||
pfUI.uf.focus.label = nil
|
||||
-- No msg: use current target
|
||||
if UnitExists then
|
||||
local _, guid = UnitExists("target")
|
||||
if guid and guid ~= "0x0000000000000000" then
|
||||
SetFocusByGUID(guid)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- Fallback: name-based
|
||||
local name = UnitName("target")
|
||||
if name then
|
||||
SetFocusByName(name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -53,11 +131,56 @@ function SlashCmdList.PFCASTFOCUS(msg)
|
||||
return
|
||||
end
|
||||
|
||||
local func = pfUI.api.TryMemoizedFuncLoadstringForSpellCasts(msg)
|
||||
local focusGUID = pfUI.uf.focus.label
|
||||
local hasGUID = focusGUID and focusGUID ~= "" and focusGUID ~= "0x0000000000000000"
|
||||
|
||||
-- GUID-based cast (Nampower) - no target toggle needed
|
||||
if hasGUID and CastSpellByName and not func then
|
||||
CastSpellByName(msg, focusGUID)
|
||||
return
|
||||
end
|
||||
|
||||
-- For lua functions with GUID: short target swap via GUID
|
||||
if hasGUID and func then
|
||||
local _, currentGUID = UnitExists("target")
|
||||
local isPlayer = UnitIsUnit("target", "player")
|
||||
|
||||
TargetUnit(focusGUID)
|
||||
local _, newGUID = UnitExists("target")
|
||||
|
||||
if newGUID ~= focusGUID then
|
||||
-- Could not target focus, restore and fail
|
||||
if currentGUID and currentGUID ~= "0x0000000000000000" then
|
||||
TargetUnit(currentGUID)
|
||||
elseif isPlayer then
|
||||
TargetUnit("player")
|
||||
else
|
||||
TargetLastTarget()
|
||||
end
|
||||
UIErrorsFrame:AddMessage(SPELL_FAILED_BAD_TARGETS, 1, 0, 0)
|
||||
return
|
||||
end
|
||||
|
||||
func()
|
||||
|
||||
if currentGUID and currentGUID ~= "0x0000000000000000" then
|
||||
TargetUnit(currentGUID)
|
||||
elseif isPlayer then
|
||||
TargetUnit("player")
|
||||
else
|
||||
TargetLastTarget()
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
-- Fallback: name-based target swap (no Nampower / no GUID)
|
||||
local skiptarget = false
|
||||
local player = UnitIsUnit("target", "player")
|
||||
local unitname = ""
|
||||
|
||||
if pfUI.uf.focus.label and UnitIsUnit("target", pfUI.uf.focus.label .. pfUI.uf.focus.id) then
|
||||
if pfUI.uf.focus.label and pfUI.uf.focus.id and
|
||||
UnitIsUnit("target", pfUI.uf.focus.label .. pfUI.uf.focus.id) then
|
||||
skiptarget = true
|
||||
else
|
||||
pfScanActive = true
|
||||
@@ -69,7 +192,7 @@ function SlashCmdList.PFCASTFOCUS(msg)
|
||||
TargetByName(pfUI.uf.focus.unitname, true)
|
||||
end
|
||||
|
||||
if strlower(UnitName("target")) ~= strlower(unitname) then
|
||||
if strlower(UnitName("target") or "") ~= strlower(unitname or "") then
|
||||
pfScanActive = nil
|
||||
TargetLastTarget()
|
||||
UIErrorsFrame:AddMessage(SPELL_FAILED_BAD_TARGETS, 1, 0, 0)
|
||||
@@ -77,7 +200,6 @@ function SlashCmdList.PFCASTFOCUS(msg)
|
||||
end
|
||||
end
|
||||
|
||||
local func = loadstring(msg or "")
|
||||
if func then
|
||||
func()
|
||||
else
|
||||
@@ -98,9 +220,26 @@ SLASH_PFSWAPFOCUS1, SLASH_PFSWAPFOCUS2 = '/swapfocus', '/pfswapfocus'
|
||||
function SlashCmdList.PFSWAPFOCUS(msg)
|
||||
if not pfUI.uf or not pfUI.uf.focus then return end
|
||||
|
||||
local oldunit = UnitExists("target") and strlower(UnitName("target"))
|
||||
if oldunit and pfUI.uf.focus.unitname then
|
||||
TargetByName(pfUI.uf.focus.unitname)
|
||||
pfUI.uf.focus.unitname = oldunit
|
||||
local _, guid = nil, nil
|
||||
if UnitExists then
|
||||
_, guid = UnitExists("target")
|
||||
end
|
||||
end
|
||||
|
||||
if guid and guid ~= "0x0000000000000000" then
|
||||
local oldGUID = pfUI.uf.focus.label
|
||||
|
||||
SetFocusByGUID(guid)
|
||||
|
||||
-- Target old focus if we had one
|
||||
if oldGUID and oldGUID ~= "" and oldGUID ~= "0x0000000000000000" then
|
||||
TargetUnit(oldGUID)
|
||||
end
|
||||
else
|
||||
-- Fallback: name-based swap
|
||||
local oldunit = UnitExists("target") and strlower(UnitName("target") or "")
|
||||
if oldunit and pfUI.uf.focus.unitname then
|
||||
TargetByName(pfUI.uf.focus.unitname, true)
|
||||
pfUI.uf.focus.unitname = oldunit
|
||||
end
|
||||
end
|
||||
end
|
||||
+389
-14
@@ -351,13 +351,13 @@ pfUI:RegisterModule("gui", "vanilla:tbc", function ()
|
||||
|
||||
entry.text = text
|
||||
entry.func = function()
|
||||
if category[config] ~= value then
|
||||
if category and category[config] ~= value then
|
||||
category[config] = value
|
||||
if ufunc then ufunc() else pfUI.gui.settingChanged = true end
|
||||
end
|
||||
end
|
||||
|
||||
if category[config] == value then
|
||||
if category and category[config] == value then
|
||||
frame.input.current = i
|
||||
end
|
||||
|
||||
@@ -656,7 +656,11 @@ pfUI:RegisterModule("gui", "vanilla:tbc", function ()
|
||||
pfUI.gui.hoverbind:SetHeight(25)
|
||||
pfUI.gui.hoverbind:SetText(T["Hoverbind"])
|
||||
pfUI.gui.hoverbind:SetScript("OnClick", function()
|
||||
if pfUI.hoverbind then pfUI.hoverbind:Show() end
|
||||
if pfUI.hoverbind then
|
||||
pfUI.hoverbind:Show()
|
||||
else
|
||||
message("Please enable the Hoverbind module to use this feature.")
|
||||
end
|
||||
end)
|
||||
|
||||
SkinButton(pfUI.gui.hoverbind)
|
||||
@@ -671,6 +675,8 @@ pfUI:RegisterModule("gui", "vanilla:tbc", function ()
|
||||
if pfShare then
|
||||
pfShare:Show()
|
||||
pfShareExport:Click()
|
||||
else
|
||||
message("Please enable the Share module to share your config.")
|
||||
end
|
||||
end)
|
||||
SkinButton(pfUI.gui.share)
|
||||
@@ -885,6 +891,15 @@ pfUI:RegisterModule("gui", "vanilla:tbc", function ()
|
||||
"7:" .. T["Small"],
|
||||
"8:" .. T["Tiny (PixelPerfect)"],
|
||||
},
|
||||
["abbrevnum"] = {
|
||||
"0:" .. T["Full Numbers (4250)"],
|
||||
"1:" .. T["Abbreviate 2 Decimals (4.25k)"],
|
||||
"2:" .. T["Abbreviate 1 Decimal (4.2k)"],
|
||||
},
|
||||
["castbardecimals"] = {
|
||||
"1:" .. T["1 Decimal (2.1)"],
|
||||
"2:" .. T["2 Decimals (2.14)"],
|
||||
},
|
||||
["orientation"] = {
|
||||
"HORIZONTAL:" .. T["Horizontal"],
|
||||
"VERTICAL:" .. T["Vertical"],
|
||||
@@ -1372,7 +1387,7 @@ pfUI:RegisterModule("gui", "vanilla:tbc", function ()
|
||||
donate:SetHeight(20)
|
||||
donate:SetText(T["Donate"])
|
||||
donate:SetScript("OnClick", function()
|
||||
pfUI.chat.urlcopy.CopyText("https://ko-fi.com/shagu")
|
||||
pfUI.chat.urlcopy.CopyText("https://buymeacoffee.com/w1ot8abps4")
|
||||
end)
|
||||
SkinButton(donate)
|
||||
|
||||
@@ -1382,7 +1397,7 @@ pfUI:RegisterModule("gui", "vanilla:tbc", function ()
|
||||
github:SetHeight(20)
|
||||
github:SetText(T["GitHub"])
|
||||
github:SetScript("OnClick", function()
|
||||
pfUI.chat.urlcopy.CopyText("https://github.com/shagu/pfUI")
|
||||
pfUI.chat.urlcopy.CopyText("https://github.com/me0wg4ming/pfUI")
|
||||
end)
|
||||
SkinButton(github)
|
||||
|
||||
@@ -1392,7 +1407,7 @@ pfUI:RegisterModule("gui", "vanilla:tbc", function ()
|
||||
website:SetHeight(20)
|
||||
website:SetText(T["Website"])
|
||||
website:SetScript("OnClick", function()
|
||||
pfUI.chat.urlcopy.CopyText("https://shagu.org/pfUI")
|
||||
pfUI.chat.urlcopy.CopyText("https://github.com/me0wg4ming/pfUI")
|
||||
end)
|
||||
SkinButton(website)
|
||||
end)
|
||||
@@ -1490,7 +1505,8 @@ pfUI:RegisterModule("gui", "vanilla:tbc", function ()
|
||||
CreateConfig(nil, T["Disable Errors in UIErrors Frame"], C.global, "errors_hide", "checkbox")
|
||||
CreateConfig(nil, T["Highlight Settings That Require Reload"], C.gui, "reloadmarker", "checkbox")
|
||||
CreateConfig(nil, T["Show Incompatible Config Entries"], C.gui, "showdisabled", "checkbox")
|
||||
CreateConfig(nil, T["Abbreviate Numbers (4200 -> 4.2k)"], C.unitframes, "abbrevnum", "checkbox")
|
||||
CreateConfig(nil, T["Abbreviate Numbers"], C.unitframes, "abbrevnum", "dropdown", pfUI.gui.dropdowns.abbrevnum)
|
||||
CreateConfig(nil, T["Castbar Timer Decimals"], C.unitframes, "castbardecimals", "dropdown", pfUI.gui.dropdowns.castbardecimals)
|
||||
CreateConfig(nil, T["Abbreviate Unit Names"], C.unitframes, "abbrevname", "checkbox")
|
||||
CreateConfig(nil, T["Health Point Estimation"], nil, nil, "header")
|
||||
CreateConfig(nil, T["Estimate Enemy Health Points"], C.global, "libhealth", "checkbox")
|
||||
@@ -1584,7 +1600,6 @@ pfUI:RegisterModule("gui", "vanilla:tbc", function ()
|
||||
CreateConfig(nil, T["Cooldown Text Font Size (Blizzard Frames)"], C.appearance.cd, "font_size_blizz")
|
||||
CreateConfig(nil, T["Cooldown Text Font Size (Foreign Frames)"], C.appearance.cd, "font_size_foreign")
|
||||
CreateConfig(nil, T["Cooldown Text Time Threshold"], C.appearance.cd, "threshold")
|
||||
CreateConfig(nil, T["Display Debuff Durations"], C.appearance.cd, "debuffs", "checkbox")
|
||||
CreateConfig(nil, T["Enable Durations On Blizzard Frames"], C.appearance.cd, "blizzard", "checkbox")
|
||||
CreateConfig(nil, T["Enable Durations On Foreign Frames"], C.appearance.cd, "foreign", "checkbox")
|
||||
CreateConfig(nil, T["Hide Foreign Cooldown Animations"], C.appearance.cd, "hideanim", "checkbox")
|
||||
@@ -1610,6 +1625,321 @@ pfUI:RegisterModule("gui", "vanilla:tbc", function ()
|
||||
CreateConfig(nil, T["Selected Core"], C.gm, "server", "dropdown", pfUI.gui.dropdowns.gmserver_text)
|
||||
end)
|
||||
|
||||
-- Throttling Menu
|
||||
CreateGUIEntry(T["Throttling"], T["Nameplates"], function()
|
||||
local header = CreateConfig(nil, T["Nameplate Update Rate"], nil, nil, "header")
|
||||
header:GetParent().objectCount = header:GetParent().objectCount - 1
|
||||
header:SetHeight(20)
|
||||
|
||||
local targetCustom -- declare first so callback can use it
|
||||
|
||||
CreateConfig(function()
|
||||
-- Callback when dropdown changes - update custom field immediately
|
||||
if targetCustom and targetCustom.input then
|
||||
local isCustom = pfUI.throttle:IsCustom("nameplates_target")
|
||||
if not isCustom then
|
||||
-- Preset selected - show FPS from preset, make readonly
|
||||
targetCustom.input:EnableMouse(false)
|
||||
targetCustom.input:EnableKeyboard(false)
|
||||
targetCustom.input:ClearFocus()
|
||||
targetCustom.input:SetTextColor(.5,.5,.5,1)
|
||||
targetCustom.input:SetText(tostring(pfUI.throttle:GetFps("nameplates_target")))
|
||||
else
|
||||
-- Custom selected - make editable
|
||||
targetCustom.input:EnableMouse(true)
|
||||
targetCustom.input:EnableKeyboard(true)
|
||||
targetCustom.input:SetTextColor(.2,1,.8,1)
|
||||
if _G.pfUI_throttle.nameplates_target_custom then
|
||||
targetCustom.input:SetText(_G.pfUI_throttle.nameplates_target_custom)
|
||||
end
|
||||
end
|
||||
end
|
||||
end, T["Target/Casting Plates"], _G.pfUI_throttle, "nameplates_target", "dropdown", {
|
||||
"very_slow:" .. T["Very Slow"] .. " (2 FPS)",
|
||||
"slow:" .. T["Slow"] .. " (5 FPS)",
|
||||
"normal:" .. T["Normal"] .. " (10 FPS)",
|
||||
"fast:" .. T["Fast"] .. " (20 FPS)",
|
||||
"very_fast:" .. T["Very Fast"] .. " (30 FPS)",
|
||||
"fastest:" .. T["Fastest"] .. " (50 FPS)",
|
||||
"custom:" .. T["Custom"],
|
||||
})
|
||||
|
||||
-- Now create custom field AFTER dropdown
|
||||
targetCustom = CreateConfig(nil, T["Custom FPS"], _G.pfUI_throttle, "nameplates_target_custom")
|
||||
|
||||
-- Set initial state
|
||||
local isCustom = pfUI.throttle:IsCustom("nameplates_target")
|
||||
if not isCustom then
|
||||
targetCustom.input:EnableMouse(false)
|
||||
targetCustom.input:EnableKeyboard(false)
|
||||
targetCustom.input:SetTextColor(.5,.5,.5,1)
|
||||
targetCustom.input:SetText(tostring(pfUI.throttle:GetFps("nameplates_target")))
|
||||
else
|
||||
targetCustom.input:EnableMouse(true)
|
||||
targetCustom.input:EnableKeyboard(true)
|
||||
targetCustom.input:SetTextColor(.2,1,.8,1)
|
||||
if _G.pfUI_throttle.nameplates_target_custom then
|
||||
targetCustom.input:SetText(_G.pfUI_throttle.nameplates_target_custom)
|
||||
end
|
||||
end
|
||||
|
||||
-- Spacer after custom field
|
||||
local spacer1 = CreateConfig(nil, " ", nil, nil, "header")
|
||||
spacer1:GetParent().objectCount = spacer1:GetParent().objectCount - 1
|
||||
spacer1:SetHeight(5)
|
||||
|
||||
local normalCustom
|
||||
|
||||
CreateConfig(function()
|
||||
if normalCustom and normalCustom.input then
|
||||
local isCustom = pfUI.throttle:IsCustom("nameplates")
|
||||
if not isCustom then
|
||||
normalCustom.input:EnableMouse(false)
|
||||
normalCustom.input:EnableKeyboard(false)
|
||||
normalCustom.input:ClearFocus()
|
||||
normalCustom.input:SetTextColor(.5,.5,.5,1)
|
||||
normalCustom.input:SetText(tostring(pfUI.throttle:GetFps("nameplates")))
|
||||
else
|
||||
normalCustom.input:EnableMouse(true)
|
||||
normalCustom.input:EnableKeyboard(true)
|
||||
normalCustom.input:SetTextColor(.2,1,.8,1)
|
||||
if _G.pfUI_throttle.nameplates_custom then
|
||||
normalCustom.input:SetText(_G.pfUI_throttle.nameplates_custom)
|
||||
end
|
||||
end
|
||||
end
|
||||
end, T["Normal Plates"], _G.pfUI_throttle, "nameplates", "dropdown", {
|
||||
"very_slow:" .. T["Very Slow"] .. " (2 FPS)",
|
||||
"slow:" .. T["Slow"] .. " (5 FPS)",
|
||||
"normal:" .. T["Normal"] .. " (10 FPS)",
|
||||
"fast:" .. T["Fast"] .. " (20 FPS)",
|
||||
"very_fast:" .. T["Very Fast"] .. " (30 FPS)",
|
||||
"fastest:" .. T["Fastest"] .. " (50 FPS)",
|
||||
"custom:" .. T["Custom"],
|
||||
})
|
||||
|
||||
normalCustom = CreateConfig(nil, T["Custom FPS"], _G.pfUI_throttle, "nameplates_custom")
|
||||
|
||||
local isCustom2 = pfUI.throttle:IsCustom("nameplates")
|
||||
if not isCustom2 then
|
||||
normalCustom.input:EnableMouse(false)
|
||||
normalCustom.input:EnableKeyboard(false)
|
||||
normalCustom.input:SetTextColor(.5,.5,.5,1)
|
||||
normalCustom.input:SetText(tostring(pfUI.throttle:GetFps("nameplates")))
|
||||
else
|
||||
normalCustom.input:EnableMouse(true)
|
||||
normalCustom.input:EnableKeyboard(true)
|
||||
normalCustom.input:SetTextColor(.2,1,.8,1)
|
||||
if _G.pfUI_throttle.nameplates_custom then
|
||||
normalCustom.input:SetText(_G.pfUI_throttle.nameplates_custom)
|
||||
end
|
||||
end
|
||||
|
||||
-- Spacer after custom field
|
||||
local spacer2 = CreateConfig(nil, " ", nil, nil, "header")
|
||||
spacer2:GetParent().objectCount = spacer2:GetParent().objectCount - 1
|
||||
spacer2:SetHeight(5)
|
||||
|
||||
local massCustom
|
||||
|
||||
CreateConfig(function()
|
||||
if massCustom and massCustom.input then
|
||||
local isCustom = pfUI.throttle:IsCustom("nameplates_mass")
|
||||
if not isCustom then
|
||||
massCustom.input:EnableMouse(false)
|
||||
massCustom.input:EnableKeyboard(false)
|
||||
massCustom.input:ClearFocus()
|
||||
massCustom.input:SetTextColor(.5,.5,.5,1)
|
||||
massCustom.input:SetText(tostring(pfUI.throttle:GetFps("nameplates_mass")))
|
||||
else
|
||||
massCustom.input:EnableMouse(true)
|
||||
massCustom.input:EnableKeyboard(true)
|
||||
massCustom.input:SetTextColor(.2,1,.8,1)
|
||||
if _G.pfUI_throttle.nameplates_mass_custom then
|
||||
massCustom.input:SetText(_G.pfUI_throttle.nameplates_mass_custom)
|
||||
end
|
||||
end
|
||||
end
|
||||
end, T["Mass Pulls (20+ Plates)"], _G.pfUI_throttle, "nameplates_mass", "dropdown", {
|
||||
"very_slow:" .. T["Very Slow"] .. " (2 FPS)",
|
||||
"slow:" .. T["Slow"] .. " (5 FPS)",
|
||||
"normal:" .. T["Normal"] .. " (10 FPS)",
|
||||
"fast:" .. T["Fast"] .. " (20 FPS)",
|
||||
"very_fast:" .. T["Very Fast"] .. " (30 FPS)",
|
||||
"fastest:" .. T["Fastest"] .. " (50 FPS)",
|
||||
"custom:" .. T["Custom"],
|
||||
})
|
||||
|
||||
massCustom = CreateConfig(nil, T["Custom FPS"], _G.pfUI_throttle, "nameplates_mass_custom")
|
||||
|
||||
local isCustom3 = pfUI.throttle:IsCustom("nameplates_mass")
|
||||
if not isCustom3 then
|
||||
massCustom.input:EnableMouse(false)
|
||||
massCustom.input:EnableKeyboard(false)
|
||||
massCustom.input:SetTextColor(.5,.5,.5,1)
|
||||
massCustom.input:SetText(tostring(pfUI.throttle:GetFps("nameplates_mass")))
|
||||
else
|
||||
massCustom.input:EnableMouse(true)
|
||||
massCustom.input:EnableKeyboard(true)
|
||||
massCustom.input:SetTextColor(.2,1,.8,1)
|
||||
if _G.pfUI_throttle.nameplates_mass_custom then
|
||||
massCustom.input:SetText(_G.pfUI_throttle.nameplates_mass_custom)
|
||||
end
|
||||
end
|
||||
|
||||
-- Spacer before reset button
|
||||
local spacer = CreateConfig(nil, " ", nil, nil, "header")
|
||||
spacer:GetParent().objectCount = spacer:GetParent().objectCount - 1
|
||||
spacer:SetHeight(10)
|
||||
|
||||
-- Reset to defaults button
|
||||
CreateConfig(nil, T["Reset to Defaults"], nil, nil, "button", function()
|
||||
pfUI.throttle:ResetToDefault("nameplates_target")
|
||||
pfUI.throttle:ResetToDefault("nameplates")
|
||||
pfUI.throttle:ResetToDefault("nameplates_mass")
|
||||
-- Also reset custom fields to their default FPS values
|
||||
_G.pfUI_throttle.nameplates_target_custom = "50"
|
||||
_G.pfUI_throttle.nameplates_custom = "10"
|
||||
_G.pfUI_throttle.nameplates_mass_custom = "7"
|
||||
Reload()
|
||||
end, true)
|
||||
end)
|
||||
|
||||
CreateGUIEntry(T["Throttling"], T["Tooltips"], function()
|
||||
local header = CreateConfig(nil, T["Tooltip Update Rate"], nil, nil, "header")
|
||||
header:GetParent().objectCount = header:GetParent().objectCount - 1
|
||||
header:SetHeight(20)
|
||||
|
||||
local cursorCustom
|
||||
|
||||
CreateConfig(function()
|
||||
if cursorCustom and cursorCustom.input then
|
||||
local isCustom = pfUI.throttle:IsCustom("tooltip_cursor")
|
||||
if not isCustom then
|
||||
cursorCustom.input:EnableMouse(false)
|
||||
cursorCustom.input:EnableKeyboard(false)
|
||||
cursorCustom.input:ClearFocus()
|
||||
cursorCustom.input:SetTextColor(.5,.5,.5,1)
|
||||
cursorCustom.input:SetText(tostring(pfUI.throttle:GetFps("tooltip_cursor")))
|
||||
else
|
||||
cursorCustom.input:EnableMouse(true)
|
||||
cursorCustom.input:EnableKeyboard(true)
|
||||
cursorCustom.input:SetTextColor(.2,1,.8,1)
|
||||
if _G.pfUI_throttle.tooltip_cursor_custom then
|
||||
cursorCustom.input:SetText(_G.pfUI_throttle.tooltip_cursor_custom)
|
||||
end
|
||||
end
|
||||
end
|
||||
end, T["Cursor Follow"], _G.pfUI_throttle, "tooltip_cursor", "dropdown", {
|
||||
"very_slow:" .. T["Very Slow"] .. " (2 FPS)",
|
||||
"slow:" .. T["Slow"] .. " (5 FPS)",
|
||||
"normal:" .. T["Normal"] .. " (10 FPS)",
|
||||
"fast:" .. T["Fast"] .. " (20 FPS)",
|
||||
"very_fast:" .. T["Very Fast"] .. " (30 FPS)",
|
||||
"fastest:" .. T["Fastest"] .. " (50 FPS)",
|
||||
"custom:" .. T["Custom"],
|
||||
})
|
||||
|
||||
cursorCustom = CreateConfig(nil, T["Custom FPS"], _G.pfUI_throttle, "tooltip_cursor_custom")
|
||||
|
||||
local isCustom = pfUI.throttle:IsCustom("tooltip_cursor")
|
||||
if not isCustom then
|
||||
cursorCustom.input:EnableMouse(false)
|
||||
cursorCustom.input:EnableKeyboard(false)
|
||||
cursorCustom.input:SetTextColor(.5,.5,.5,1)
|
||||
cursorCustom.input:SetText(tostring(pfUI.throttle:GetFps("tooltip_cursor")))
|
||||
else
|
||||
cursorCustom.input:EnableMouse(true)
|
||||
cursorCustom.input:EnableKeyboard(true)
|
||||
cursorCustom.input:SetTextColor(.2,1,.8,1)
|
||||
if _G.pfUI_throttle.tooltip_cursor_custom then
|
||||
cursorCustom.input:SetText(_G.pfUI_throttle.tooltip_cursor_custom)
|
||||
end
|
||||
end
|
||||
|
||||
-- Small spacer
|
||||
local spacer1 = CreateConfig(nil, " ", nil, nil, "header")
|
||||
spacer1:GetParent().objectCount = spacer1:GetParent().objectCount - 1
|
||||
spacer1:SetHeight(5)
|
||||
|
||||
-- Info note about Native mode
|
||||
local infoText = CreateConfig(nil, T["Note: Only works when Cursor Align is NOT 'Native'"], nil, nil, "header")
|
||||
infoText:GetParent().objectCount = infoText:GetParent().objectCount - 1
|
||||
infoText:SetHeight(25)
|
||||
|
||||
-- Spacer before reset button
|
||||
local spacer = CreateConfig(nil, " ", nil, nil, "header")
|
||||
spacer:GetParent().objectCount = spacer:GetParent().objectCount - 1
|
||||
spacer:SetHeight(5)
|
||||
|
||||
-- Reset to defaults button
|
||||
CreateConfig(nil, T["Reset to Defaults"], nil, nil, "button", function()
|
||||
pfUI.throttle:ResetToDefault("tooltip_cursor")
|
||||
_G.pfUI_throttle.tooltip_cursor_custom = "10"
|
||||
Reload()
|
||||
end, true)
|
||||
end)
|
||||
CreateGUIEntry(T["Throttling"], T["Chat Tab"], function()
|
||||
local chatCustom
|
||||
|
||||
CreateConfig(function()
|
||||
if chatCustom and chatCustom.input then
|
||||
local isCustom = pfUI.throttle:IsCustom("chat_tab")
|
||||
if not isCustom then
|
||||
chatCustom.input:EnableMouse(false)
|
||||
chatCustom.input:EnableKeyboard(false)
|
||||
chatCustom.input:ClearFocus()
|
||||
chatCustom.input:SetTextColor(.5,.5,.5,1)
|
||||
chatCustom.input:SetText(tostring(pfUI.throttle:GetFps("chat_tab")))
|
||||
else
|
||||
chatCustom.input:EnableMouse(true)
|
||||
chatCustom.input:EnableKeyboard(true)
|
||||
chatCustom.input:SetTextColor(.2,1,.8,1)
|
||||
if _G.pfUI_throttle.chat_tab_custom then
|
||||
chatCustom.input:SetText(_G.pfUI_throttle.chat_tab_custom)
|
||||
end
|
||||
end
|
||||
end
|
||||
end, T["Chat Tab Hover Check"], _G.pfUI_throttle, "chat_tab", "dropdown", {
|
||||
"very_slow:" .. T["Very Slow"] .. " (2 FPS)",
|
||||
"slow:" .. T["Slow"] .. " (5 FPS)",
|
||||
"normal:" .. T["Normal"] .. " (10 FPS)",
|
||||
"fast:" .. T["Fast"] .. " (20 FPS)",
|
||||
"very_fast:" .. T["Very Fast"] .. " (30 FPS)",
|
||||
"fastest:" .. T["Fastest"] .. " (50 FPS)",
|
||||
"custom:" .. T["Custom"],
|
||||
})
|
||||
|
||||
chatCustom = CreateConfig(nil, T["Custom FPS"], _G.pfUI_throttle, "chat_tab_custom")
|
||||
|
||||
local isCustom = pfUI.throttle:IsCustom("chat_tab")
|
||||
if not isCustom then
|
||||
chatCustom.input:EnableMouse(false)
|
||||
chatCustom.input:EnableKeyboard(false)
|
||||
chatCustom.input:SetTextColor(.5,.5,.5,1)
|
||||
chatCustom.input:SetText(tostring(pfUI.throttle:GetFps("chat_tab")))
|
||||
else
|
||||
chatCustom.input:EnableMouse(true)
|
||||
chatCustom.input:EnableKeyboard(true)
|
||||
chatCustom.input:SetTextColor(.2,1,.8,1)
|
||||
if _G.pfUI_throttle.chat_tab_custom then
|
||||
chatCustom.input:SetText(_G.pfUI_throttle.chat_tab_custom)
|
||||
end
|
||||
end
|
||||
|
||||
-- Spacer before reset button
|
||||
local spacer = CreateConfig(nil, " ", nil, nil, "header")
|
||||
spacer:GetParent().objectCount = spacer:GetParent().objectCount - 1
|
||||
spacer:SetHeight(10)
|
||||
|
||||
-- Reset to defaults button
|
||||
CreateConfig(nil, T["Reset to Defaults"], nil, nil, "button", function()
|
||||
pfUI.throttle:ResetToDefault("chat_tab")
|
||||
_G.pfUI_throttle.chat_tab_custom = "10"
|
||||
Reload()
|
||||
end, true)
|
||||
end)
|
||||
|
||||
CreateGUIEntry(T["Unit Frames"], T["General"], function()
|
||||
CreateConfig(nil, T["Disable pfUI Unit Frames"], C.unitframes, "disable", "checkbox")
|
||||
CreateConfig(nil, T["Healthbar Animation Speed"], C.unitframes, "animation_speed", "dropdown", pfUI.gui.dropdowns.uf_animationspeed)
|
||||
@@ -1624,6 +1954,21 @@ pfUI:RegisterModule("gui", "vanilla:tbc", function ()
|
||||
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, "vanilla" )
|
||||
|
||||
CreateConfig(nil, T["Swing Timer"], nil, nil, "header")
|
||||
CreateConfig(nil, T["Swing Timer Width"], C.unitframes, "swingtimerwidth")
|
||||
CreateConfig(nil, T["Swing Timer Height"], C.unitframes, "swingtimerheight")
|
||||
CreateConfig(nil, T["Swing Timer Texture"], C.unitframes, "swingtimertexture", "dropdown", pfUI.gui.dropdowns.uf_bartexture)
|
||||
CreateConfig(nil, T["Swing Timer Font Size"], C.unitframes, "swingtimerfontsize")
|
||||
CreateConfig(nil, T["Show Timer Text"], C.unitframes, "swingtimertext", "checkbox")
|
||||
CreateConfig(nil, T["Show MH/OH Labels"], C.unitframes, "swingtimerlabel", "checkbox")
|
||||
CreateConfig(nil, T["Show Offhand Bar"], C.unitframes, "swingtimeroffhand", "checkbox")
|
||||
CreateConfig(nil, T["Show Ranged Bar"], C.unitframes, "swingtimerranged", "checkbox")
|
||||
CreateConfig(nil, T["Mainhand Bar Color"], C.unitframes, "swingtimermhcolor", "color")
|
||||
CreateConfig(nil, T["Offhand Bar Color"], C.unitframes, "swingtimerohcolor", "color")
|
||||
CreateConfig(nil, T["Ranged Bar Color"], C.unitframes, "swingtimerrangedcolor", "color")
|
||||
CreateConfig(nil, T["Ranged Warn Color (Hunter)"], C.unitframes, "swingtimerrangedwarncolor", "color")
|
||||
CreateConfig(nil, T["Show HS/Cleave Queue Color (Warrior)"], C.unitframes, "swingtimerhsqueue", "checkbox")
|
||||
|
||||
CreateConfig(U[c], T["Font Options"], nil, nil, "header")
|
||||
CreateConfig(nil, T["Unit Frame Text Font"], C.global, "font_unit", "dropdown", pfUI.gui.dropdowns.fonts)
|
||||
CreateConfig(nil, T["Unit Frame Text Size"], C.global, "font_unit_size")
|
||||
@@ -1653,10 +1998,29 @@ pfUI:RegisterModule("gui", "vanilla:tbc", function ()
|
||||
CreateConfig(nil, T["Energy Color"], C.unitframes, "energycolor", "color")
|
||||
CreateConfig(nil, T["Focus Color"], C.unitframes, "focuscolor", "color")
|
||||
|
||||
CreateConfig(nil, T["SuperWoW Settings"], nil, nil, "header")
|
||||
CreateConfig(nil, T["Druid Settings"], nil, nil, "header")
|
||||
CreateConfig(nil, T["Show Druid Mana Bar"], C.unitframes, "druidmanabar", "checkbox", nil, nil, nil, nil, "vanilla" )
|
||||
CreateConfig(nil, T["Druid Mana Bar Height"], C.unitframes, "druidmanaheight", nil, nil, nil, nil, nil, "vanilla" )
|
||||
CreateConfig(nil, T["Druid Mana Bar Text"], C.unitframes, "druidmanatext", "checkbox", nil, nil, nil, nil, "vanilla" )
|
||||
CreateConfig(nil, T["Druid Mana Bar Width (-1 = auto)"], C.unitframes, "druidmanawidth", nil, nil, nil, nil, nil, "vanilla" )
|
||||
CreateConfig(nil, T["Druid Mana Bar X-Offset"], C.unitframes, "druidmanaoffx", nil, nil, nil, nil, nil, "vanilla" )
|
||||
CreateConfig(nil, T["Druid Mana Bar Y-Offset"], C.unitframes, "druidmanaoffy", nil, nil, nil, nil, nil, "vanilla" )
|
||||
CreateConfig(nil, T["Druid Mana Bar Spacing"], C.unitframes, "druidmanaspace", nil, nil, nil, nil, nil, "vanilla" )
|
||||
CreateConfig(nil, T["Druid Mana Bar Texture"], C.unitframes, "druidmanatexture", "dropdown", pfUI.gui.dropdowns.uf_bartexture, nil, nil, nil, "vanilla" )
|
||||
|
||||
|
||||
CreateConfig(nil, T["SuperWoW Settings"], nil, nil, "header")
|
||||
CreateConfig(nil, T["Track Group on Minimap"], C.unitframes, "track_group", "checkbox", nil, nil, nil, nil, "vanilla" )
|
||||
|
||||
CreateConfig(nil, T["Nampower Settings"], nil, nil, "header")
|
||||
CreateConfig(nil, T["Show Spell Queue Indicator"], C.unitframes, "spellqueue", "checkbox", nil, nil, nil, nil, "vanilla" )
|
||||
CreateConfig(nil, T["Spell Queue Icon Size"], C.unitframes, "spellqueuesize", nil, nil, nil, nil, nil, "vanilla" )
|
||||
CreateConfig(nil, T["Show Reactive Spell Indicator"], C.unitframes, "reactive_indicator", "checkbox", nil, nil, nil, nil, "vanilla" )
|
||||
CreateConfig(nil, T["Reactive Indicator Size"], C.unitframes, "reactive_size", nil, nil, nil, nil, nil, "vanilla" )
|
||||
|
||||
CreateConfig(nil, T["UnitXP Settings"], nil, nil, "header")
|
||||
CreateConfig(nil, T["Show Line of Sight Indicator"], C.unitframes, "los_indicator", "checkbox", nil, nil, nil, nil, "vanilla" )
|
||||
CreateConfig(nil, T["Show Behind Indicator"], C.unitframes, "behind_indicator", "checkbox", nil, nil, nil, nil, "vanilla" )
|
||||
CreateConfig(nil, T["Enable OS Notifications"], C.unitframes, "unitxp_notify", "checkbox", nil, nil, nil, nil, "vanilla" )
|
||||
end)
|
||||
|
||||
-- Shared Unit- and Groupframes
|
||||
@@ -1813,7 +2177,7 @@ pfUI:RegisterModule("gui", "vanilla:tbc", function ()
|
||||
|
||||
CreateConfig(U[c], T["Timer"], nil, nil, "header")
|
||||
CreateConfig(U[c], T["Show Timer Text"], C.unitframes[c], "cooldown_text", "checkbox")
|
||||
CreateConfig(U[c], T["Show Timer Animation"], C.unitframes[c], "cooldown_anim", "checkbox")
|
||||
CreateConfig(Reload, T["Show Timer Animation"], C.unitframes[c], "cooldown_anim", "checkbox")
|
||||
|
||||
CreateConfig(U[c], T["Buffs"], nil, nil, "header")
|
||||
CreateConfig(U[c], T["Buff Position"], C.unitframes[c], "buffs", "dropdown", pfUI.gui.dropdowns.uf_buff_position)
|
||||
@@ -2014,7 +2378,9 @@ pfUI:RegisterModule("gui", "vanilla:tbc", 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")
|
||||
CreateConfig(U["bars"], T["Scan Macros For Spells"], C.bars, "macroscan", "checkbox", nil, nil, nil, nil, "vanilla")
|
||||
if not pfUI:MacroAddonsLoaded() then
|
||||
CreateConfig(U["bars"], T["Scan Macros For Spells"], C.bars, "macroscan", "checkbox", nil, nil, nil, nil, "vanilla")
|
||||
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")
|
||||
@@ -2263,6 +2629,7 @@ pfUI:RegisterModule("gui", "vanilla:tbc", 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")
|
||||
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")
|
||||
@@ -2294,6 +2661,8 @@ pfUI:RegisterModule("gui", "vanilla:tbc", function ()
|
||||
CreateGUIEntry(T["Nameplates"], nil, function()
|
||||
CreateConfig(U["nameplates"], T["Show On Hostile Units"], C.nameplates, "showhostile", "checkbox")
|
||||
CreateConfig(U["nameplates"], T["Show On Friendly Units"], C.nameplates, "showfriendly", "checkbox")
|
||||
CreateConfig(U["nameplates"], T["Disable Hostile Nameplates In Friendly Zones"], C.nameplates, "disable_hostile_in_friendly", "checkbox")
|
||||
CreateConfig(U["nameplates"], T["Disable Friendly Nameplates In Friendly Zones"], C.nameplates, "disable_friendly_in_friendly", "checkbox")
|
||||
CreateConfig(U["nameplates"], T["Vertical Offset (|cffffaaaaExperimental|r)"], C.nameplates, "vertical_offset", nil, nil, nil, nil, nil, "vanilla")
|
||||
CreateConfig(U["nameplates"], T["Inactive Nameplate Alpha"], C.nameplates, "notargalpha", "dropdown", pfUI.gui.dropdowns.percent_small)
|
||||
CreateConfig(U["nameplates"], T["Draw Glow Around Target Nameplate"], C.nameplates, "targetglow", "checkbox")
|
||||
@@ -2334,6 +2703,10 @@ pfUI:RegisterModule("gui", "vanilla:tbc", function ()
|
||||
CreateConfig(U["nameplates"], T["Debuff Icon Size"], C.nameplates, "debuffsize")
|
||||
CreateConfig(U["nameplates"], T["Estimate Debuffs"], C.nameplates, "guessdebuffs", "checkbox")
|
||||
CreateConfig(U["nameplates"], T["Show Debuff Stacks"], C.nameplates.debuffs, "showstacks", "checkbox")
|
||||
CreateConfig(U["nameplates"], T["Enable Debuff Timers"], C.nameplates, "debufftimers", "checkbox")
|
||||
CreateConfig(U["nameplates"], T["Show Timer Text"], C.nameplates, "debufftext", "checkbox")
|
||||
CreateConfig(Reload, T["Show Timer Animation"], C.nameplates, "debuffanim", "checkbox")
|
||||
|
||||
CreateConfig(U["nameplates"], T["Only Show Own Debuffs (|cffffaaaaExperimental|r)"], C.nameplates, "selfdebuff", "checkbox")
|
||||
CreateConfig(U["nameplates"], T["Filter Mode"], C.nameplates.debuffs, "filter", "dropdown", pfUI.gui.dropdowns.buffbarfilter)
|
||||
CreateConfig(U["nameplates"], T["Blacklist"], C.nameplates.debuffs, "blacklist", "list")
|
||||
@@ -2406,6 +2779,7 @@ pfUI:RegisterModule("gui", "vanilla:tbc", function ()
|
||||
CreateConfig(nil, "BetterCharacterStats", C.thirdparty.bcs, "enable", "checkbox", nil, nil, nil, nil, "vanilla")
|
||||
CreateConfig(nil, "Crafty", C.thirdparty.crafty, "enable", "checkbox", nil, nil, nil, nil, "vanilla")
|
||||
CreateConfig(nil, "CleverMacro", C.thirdparty.clevermacro, "enable", "checkbox", nil, nil, nil, nil, "vanilla")
|
||||
CreateConfig(nil, "SuperCleveRoidMacros", C.thirdparty.supercleveroidmacros, "enable", "checkbox", nil, nil, nil, nil, "vanilla")
|
||||
CreateConfig(nil, "AckisRecipeList", C.thirdparty.ackis, "enable", "checkbox", nil, nil, nil, nil, "tbc")
|
||||
CreateConfig(nil, "SheepWatch", C.thirdparty.sheepwatch, "enable", "checkbox", nil, nil, nil, nil, "tbc")
|
||||
CreateConfig(nil, "TotemTimers", C.thirdparty.totemtimers, "enable", "checkbox", nil, nil, nil, nil, "tbc")
|
||||
@@ -2423,7 +2797,8 @@ pfUI:RegisterModule("gui", "vanilla:tbc", 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")
|
||||
@@ -2440,4 +2815,4 @@ pfUI:RegisterModule("gui", "vanilla:tbc", function ()
|
||||
end
|
||||
end)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
@@ -1,4 +1,7 @@
|
||||
pfUI:RegisterModule("macrotweak", "vanilla", function ()
|
||||
-- disable macrotweak when macro addons are loaded
|
||||
if IsAddOnLoaded("Supermacro") or IsAddOnLoaded("SuperCleveRoidMacros") or IsAddOnLoaded("UltimaMacros") then return end
|
||||
|
||||
-- do not write macro calls into chat input history
|
||||
if ChatFrameEditBox._AddHistoryLine then
|
||||
local userinput
|
||||
|
||||
+5
-2
@@ -134,8 +134,11 @@ pfUI:RegisterModule("minimap", "vanilla:tbc", function ()
|
||||
-- Create coordinates text frame with location configurable
|
||||
pfUI.minimapCoordinates = CreateFrame("Frame", "pfMinimapCoord", pfUI.minimap)
|
||||
pfUI.minimapCoordinates:SetScript("OnUpdate", function()
|
||||
-- update coords every 0.1 seconds
|
||||
if C.appearance.minimap.coordstext ~= "off" and ( this.tick or .1) > GetTime() then return else this.tick = GetTime() + .1 end
|
||||
-- Throttle to update coords every 0.1 seconds
|
||||
if ( this.tick or 0) > GetTime() then return end
|
||||
this.tick = GetTime() + .1
|
||||
|
||||
if C.appearance.minimap.coordstext == "off" then return end
|
||||
|
||||
this.posX, this.posY = GetPlayerMapPosition("player")
|
||||
if this.posX ~= 0 and this.posY ~= 0 then
|
||||
|
||||
@@ -34,7 +34,7 @@ pfUI:RegisterModule("mouseover", "vanilla", function ()
|
||||
_G.SLASH_PFCAST1, _G.SLASH_PFCAST2 = "/pfcast", "/pfmouse"
|
||||
function SlashCmdList.PFCAST(msg)
|
||||
local restore_target = true
|
||||
local func = loadstring(msg or "")
|
||||
local func = pfUI.api.TryMemoizedFuncLoadstringForSpellCasts(msg)
|
||||
local unit = "mouseover"
|
||||
|
||||
if not UnitExists(unit) then
|
||||
@@ -50,6 +50,13 @@ pfUI:RegisterModule("mouseover", "vanilla", function ()
|
||||
end
|
||||
end
|
||||
|
||||
-- Nampower: CastSpellByName supports a second unit parameter directly.
|
||||
-- unit is already resolved to "mouseover", "target" or "player" at this point.
|
||||
if not func and GetNampowerVersion then
|
||||
CastSpellByName(msg, unit)
|
||||
return
|
||||
end
|
||||
|
||||
-- If target and mouseover are friendly units, we can't use spell target as it
|
||||
-- would cast on the target instead of the mouseover. However, if the mouseover
|
||||
-- is friendly and the target is not, we can try to obtain the best unitstring
|
||||
@@ -90,4 +97,4 @@ pfUI:RegisterModule("mouseover", "vanilla", function ()
|
||||
TargetLastTarget()
|
||||
end
|
||||
end
|
||||
end)
|
||||
end)
|
||||
+763
-213
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,672 @@
|
||||
-- Nampower integration module
|
||||
-- Provides spell queue indicator and enhanced cast information
|
||||
-- Requires Nampower DLL: https://gitea.com/avitasia/nampower
|
||||
|
||||
pfUI:RegisterModule("nampower", "vanilla", function ()
|
||||
-- Only load if Nampower is available
|
||||
if not GetNampowerVersion then return end
|
||||
|
||||
-- Safe wrapper for SuperWoW's GetSpellNameAndRankForId (may not be available)
|
||||
local function SafeGetSpellNameAndRank(spellId)
|
||||
if not GetSpellNameAndRankForId then return nil, nil end
|
||||
local success, name, rank = pcall(GetSpellNameAndRankForId, spellId)
|
||||
if success then
|
||||
return name, rank
|
||||
end
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
local rawborder, border = GetBorderSize()
|
||||
|
||||
-- Spell Queue Indicator
|
||||
-- Shows the currently queued spell icon near the castbar
|
||||
if C.unitframes.spellqueue == "1" then
|
||||
local size = tonumber(C.unitframes.spellqueuesize) or 32
|
||||
|
||||
pfUI.spellqueue = CreateFrame("Frame", "pfSpellQueue", UIParent)
|
||||
pfUI.spellqueue:SetFrameStrata("HIGH")
|
||||
pfUI.spellqueue:SetWidth(size)
|
||||
pfUI.spellqueue:SetHeight(size)
|
||||
pfUI.spellqueue:Hide()
|
||||
|
||||
-- Position near player castbar if available
|
||||
if pfUI.castbar and pfUI.castbar.player then
|
||||
pfUI.spellqueue:SetPoint("LEFT", pfUI.castbar.player, "RIGHT", border*3, 0)
|
||||
else
|
||||
pfUI.spellqueue:SetPoint("CENTER", UIParent, "CENTER", 100, -100)
|
||||
end
|
||||
|
||||
pfUI.spellqueue.icon = pfUI.spellqueue:CreateTexture("OVERLAY")
|
||||
pfUI.spellqueue.icon:SetAllPoints(pfUI.spellqueue)
|
||||
pfUI.spellqueue.icon:SetTexCoord(.08, .92, .08, .92)
|
||||
|
||||
UpdateMovable(pfUI.spellqueue)
|
||||
CreateBackdrop(pfUI.spellqueue)
|
||||
CreateBackdropShadow(pfUI.spellqueue)
|
||||
|
||||
-- Event codes from Nampower
|
||||
local ON_SWING_QUEUED = 0
|
||||
local ON_SWING_QUEUE_POPPED = 1
|
||||
local NORMAL_QUEUED = 2
|
||||
local NORMAL_QUEUE_POPPED = 3
|
||||
local NON_GCD_QUEUED = 4
|
||||
local NON_GCD_QUEUE_POPPED = 5
|
||||
|
||||
local queue = CreateFrame("Frame")
|
||||
queue:RegisterEvent("SPELL_QUEUE_EVENT")
|
||||
queue:RegisterEvent("PLAYER_LOGOUT")
|
||||
queue:SetScript("OnEvent", function()
|
||||
-- Handle shutdown to prevent crash 132
|
||||
if event == "PLAYER_LOGOUT" then
|
||||
this:UnregisterAllEvents()
|
||||
this:SetScript("OnEvent", nil)
|
||||
return
|
||||
end
|
||||
|
||||
local eventCode = arg1
|
||||
local spellId = arg2
|
||||
|
||||
if eventCode == NORMAL_QUEUED or eventCode == NON_GCD_QUEUED or eventCode == ON_SWING_QUEUED then
|
||||
-- Get spell texture from GetSpellRec (Nampower) or SpellInfo (SuperWoW fallback)
|
||||
local texture
|
||||
if GetSpellRec then
|
||||
local rec = GetSpellRec(spellId)
|
||||
texture = rec and rec.spellIconID and GetSpellIconTexture(rec.spellIconID) or nil
|
||||
elseif SpellInfo then
|
||||
local _, _, tex = SpellInfo(spellId)
|
||||
texture = tex
|
||||
end
|
||||
|
||||
if texture then
|
||||
pfUI.spellqueue.icon:SetTexture(texture)
|
||||
pfUI.spellqueue:Show()
|
||||
end
|
||||
elseif eventCode == NORMAL_QUEUE_POPPED or eventCode == NON_GCD_QUEUE_POPPED or eventCode == ON_SWING_QUEUE_POPPED then
|
||||
pfUI.spellqueue:Hide()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
-- NOTE: Buff tracking removed - was dead code (data collected but never used for display)
|
||||
|
||||
-- Direct Aura Access API using GetUnitField
|
||||
-- Much faster than tooltip scanning - reads aura arrays directly from unit fields
|
||||
if GetUnitField then
|
||||
pfUI.api.GetUnitAuras = function(unit)
|
||||
local auras = GetUnitField(unit, "aura")
|
||||
local auraLevels = GetUnitField(unit, "auraLevels")
|
||||
local auraStacks = GetUnitField(unit, "auraApplications")
|
||||
|
||||
if not auras then return nil end
|
||||
|
||||
local result = {}
|
||||
for i = 1, 48 do
|
||||
local spellId = auras[i]
|
||||
if spellId and spellId > 0 then
|
||||
local name, rank, texture
|
||||
if GetSpellRec then
|
||||
local rec = GetSpellRec(spellId)
|
||||
if rec then
|
||||
name = rec.name
|
||||
rank = rec.rank
|
||||
local iconID = rec.spellIconID
|
||||
texture = iconID and GetSpellIconTexture(iconID) or nil
|
||||
end
|
||||
elseif SpellInfo then
|
||||
name, rank, texture = SpellInfo(spellId)
|
||||
end
|
||||
if not name then
|
||||
name, rank = SafeGetSpellNameAndRank(spellId)
|
||||
end
|
||||
|
||||
result[i] = {
|
||||
spellId = spellId,
|
||||
name = name,
|
||||
rank = rank,
|
||||
texture = texture,
|
||||
level = auraLevels and auraLevels[i] or 0,
|
||||
stacks = auraStacks and auraStacks[i] or 1,
|
||||
isBuff = i <= 32, -- First 32 slots are buffs, rest are debuffs
|
||||
}
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
-- Quick check if unit has specific aura by spellId
|
||||
pfUI.api.UnitHasAura = function(unit, spellId)
|
||||
local auras = GetUnitField(unit, "aura")
|
||||
if not auras then return false end
|
||||
for i = 1, 48 do
|
||||
if auras[i] == spellId then return true, i end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- Get unit resistances directly
|
||||
pfUI.api.GetUnitResistances = function(unit)
|
||||
local res = GetUnitField(unit, "resistances")
|
||||
if not res then return nil end
|
||||
return {
|
||||
armor = res[1] or 0,
|
||||
holy = res[2] or 0,
|
||||
fire = res[3] or 0,
|
||||
nature = res[4] or 0,
|
||||
frost = res[5] or 0,
|
||||
shadow = res[6] or 0,
|
||||
arcane = res[7] or 0
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
-- Reactive Spell Indicator using IsSpellUsable
|
||||
-- Shows when reactive abilities like Overpower, Revenge, Execute are usable
|
||||
if IsSpellUsable and C.unitframes.reactive_indicator == "1" then
|
||||
local size = tonumber(C.unitframes.reactive_size) or 28
|
||||
local _, class = UnitClass("player")
|
||||
|
||||
-- Reactive spells by class
|
||||
local reactiveSpells = {
|
||||
WARRIOR = {
|
||||
{ name = "Overpower", texture = "Interface\\Icons\\Ability_MeleeDamage" },
|
||||
{ name = "Revenge", texture = "Interface\\Icons\\Ability_Warrior_Revenge" },
|
||||
{ name = "Execute", texture = "Interface\\Icons\\INV_Sword_48" },
|
||||
},
|
||||
ROGUE = {
|
||||
{ name = "Riposte", texture = "Interface\\Icons\\Ability_Warrior_Challange" },
|
||||
},
|
||||
HUNTER = {
|
||||
{ name = "Mongoose Bite", texture = "Interface\\Icons\\Ability_Hunter_SwiftStrike" },
|
||||
{ name = "Counterattack", texture = "Interface\\Icons\\Ability_Warrior_Challange" },
|
||||
},
|
||||
}
|
||||
|
||||
local spells = reactiveSpells[class]
|
||||
if spells then
|
||||
pfUI.reactive = CreateFrame("Frame", "pfReactiveIndicator", UIParent)
|
||||
pfUI.reactive:SetFrameStrata("HIGH")
|
||||
local spellCount = table.getn(spells)
|
||||
pfUI.reactive:SetWidth(size * spellCount + 4 * (spellCount - 1))
|
||||
pfUI.reactive:SetHeight(size)
|
||||
pfUI.reactive:SetPoint("CENTER", UIParent, "CENTER", 0, -200)
|
||||
pfUI.reactive:Hide()
|
||||
|
||||
pfUI.reactive.icons = {}
|
||||
for i, spell in ipairs(spells) do
|
||||
local icon = CreateFrame("Frame", nil, pfUI.reactive)
|
||||
icon:SetWidth(size)
|
||||
icon:SetHeight(size)
|
||||
icon:SetPoint("LEFT", pfUI.reactive, "LEFT", (i-1) * (size + 4), 0)
|
||||
|
||||
icon.texture = icon:CreateTexture(nil, "ARTWORK")
|
||||
icon.texture:SetAllPoints(icon)
|
||||
icon.texture:SetTexture(spell.texture)
|
||||
icon.texture:SetTexCoord(.08, .92, .08, .92)
|
||||
|
||||
icon.glow = icon:CreateTexture(nil, "OVERLAY")
|
||||
icon.glow:SetPoint("TOPLEFT", icon, "TOPLEFT", -4, 4)
|
||||
icon.glow:SetPoint("BOTTOMRIGHT", icon, "BOTTOMRIGHT", 4, -4)
|
||||
icon.glow:SetTexture(pfUI.media["img:glow"])
|
||||
icon.glow:SetVertexColor(1, 1, 0, 0.8)
|
||||
|
||||
CreateBackdrop(icon)
|
||||
icon:Hide()
|
||||
icon.spellName = spell.name
|
||||
pfUI.reactive.icons[i] = icon
|
||||
end
|
||||
|
||||
UpdateMovable(pfUI.reactive)
|
||||
|
||||
pfUI.reactive:SetScript("OnUpdate", function()
|
||||
local anyVisible = false
|
||||
for _, icon in ipairs(this.icons) do
|
||||
local usable = IsSpellUsable(icon.spellName)
|
||||
if usable == 1 then
|
||||
icon:Show()
|
||||
anyVisible = true
|
||||
else
|
||||
icon:Hide()
|
||||
end
|
||||
end
|
||||
if anyVisible then
|
||||
this:Show()
|
||||
else
|
||||
this:Hide()
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
-- Enhanced Cooldown Tracking API using GetSpellIdCooldown
|
||||
if GetSpellIdCooldown then
|
||||
pfUI.api.GetPreciseCooldown = function(spellId)
|
||||
local cd = GetSpellIdCooldown(spellId)
|
||||
if not cd then return nil end
|
||||
return {
|
||||
onCooldown = (cd.isOnCooldown or 0) == 1,
|
||||
remaining = (cd.cooldownRemainingMs or 0) / 1000,
|
||||
remainingMs = cd.cooldownRemainingMs or 0,
|
||||
gcdRemaining = (cd.gcdCategoryRemainingMs or 0) / 1000,
|
||||
gcdRemainingMs = cd.gcdCategoryRemainingMs or 0,
|
||||
individualRemaining = (cd.individualRemainingMs or 0) / 1000,
|
||||
categoryRemaining = (cd.categoryRemainingMs or 0) / 1000,
|
||||
}
|
||||
end
|
||||
|
||||
-- Item cooldown helper
|
||||
pfUI.api.GetPreciseItemCooldown = function(itemId)
|
||||
if not GetItemIdCooldown then return nil end
|
||||
local cd = GetItemIdCooldown(itemId)
|
||||
if not cd then return nil end
|
||||
return {
|
||||
onCooldown = (cd.isOnCooldown or 0) == 1,
|
||||
remaining = (cd.cooldownRemainingMs or 0) / 1000,
|
||||
remainingMs = cd.cooldownRemainingMs or 0,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
-- UNIT_DIED event handling - placeholder for future use
|
||||
-- (Debuff/buff cleanup removed as tracking is now handled by libdebuff)
|
||||
|
||||
-- Trinket Management API
|
||||
if GetTrinkets then
|
||||
pfUI.api.GetEquippedTrinkets = function()
|
||||
local trinkets = GetTrinkets()
|
||||
if not trinkets then return {} end
|
||||
local equipped = {}
|
||||
for _, trinket in pairs(trinkets) do
|
||||
if trinket and trinket.bagIndex == nil then -- nil bagIndex = equipped
|
||||
table.insert(equipped, trinket)
|
||||
end
|
||||
end
|
||||
return equipped
|
||||
end
|
||||
|
||||
pfUI.api.GetTrinketCooldown = function(slot)
|
||||
if not GetTrinketCooldown then return nil end
|
||||
local cd = GetTrinketCooldown(slot)
|
||||
if cd == -1 or not cd then return nil end
|
||||
return {
|
||||
onCooldown = (cd.isOnCooldown or 0) == 1,
|
||||
remaining = (cd.cooldownRemainingMs or 0) / 1000,
|
||||
remainingMs = cd.cooldownRemainingMs or 0,
|
||||
}
|
||||
end
|
||||
|
||||
pfUI.api.UseTrinket = function(slot, target)
|
||||
if not UseTrinket then return false end
|
||||
return UseTrinket(slot, target) == 1
|
||||
end
|
||||
end
|
||||
|
||||
-- Nampower Item Stats API (use distinct name to avoid conflicts)
|
||||
if GetItemStats then
|
||||
pfUI.api.GetNampowerItemStats = function(itemId)
|
||||
local success, stats = pcall(GetItemStats, itemId, true)
|
||||
if not success or not stats then return nil end
|
||||
return stats
|
||||
end
|
||||
|
||||
-- Quick item level lookup
|
||||
pfUI.api.GetNampowerItemLevel = function(itemId)
|
||||
if GetItemLevel then
|
||||
return GetItemLevel(itemId)
|
||||
end
|
||||
local success, stats = pcall(GetItemStats, itemId, true)
|
||||
if success and stats and stats.itemLevel then
|
||||
return stats.itemLevel
|
||||
end
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
-- Spell Modifiers API for damage/heal predictions
|
||||
if GetSpellModifiers then
|
||||
pfUI.api.GetSpellBonus = function(spellId, modType)
|
||||
-- modType: 0=DAMAGE, 1=DURATION, 6=RADIUS, 7=CRIT, 10=CAST_TIME, 14=COST, etc.
|
||||
local flat, percent, hasmod = GetSpellModifiers(spellId, modType or 0)
|
||||
return {
|
||||
flat = flat or 0,
|
||||
percent = percent or 0,
|
||||
hasModifier = hasmod and hasmod ~= 0,
|
||||
}
|
||||
end
|
||||
|
||||
-- Common spell modifier lookups
|
||||
pfUI.api.GetSpellDamageBonus = function(spellId)
|
||||
return pfUI.api.GetSpellBonus(spellId, 0) -- DAMAGE
|
||||
end
|
||||
|
||||
pfUI.api.GetSpellCritBonus = function(spellId)
|
||||
return pfUI.api.GetSpellBonus(spellId, 7) -- CRITICAL_CHANCE
|
||||
end
|
||||
|
||||
pfUI.api.GetSpellCostReduction = function(spellId)
|
||||
return pfUI.api.GetSpellBonus(spellId, 14) -- COST
|
||||
end
|
||||
end
|
||||
|
||||
-- Inventory/Bag API
|
||||
if GetBagItems then
|
||||
pfUI.api.GetAllBagItems = function()
|
||||
return GetBagItems()
|
||||
end
|
||||
|
||||
pfUI.api.FindItem = function(itemIdOrName)
|
||||
if FindPlayerItemSlot then
|
||||
local bag, slot = FindPlayerItemSlot(itemIdOrName)
|
||||
return bag, slot
|
||||
end
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
pfUI.api.UseItem = function(itemIdOrName, target)
|
||||
if UseItemIdOrName then
|
||||
return UseItemIdOrName(itemIdOrName, target) == 1
|
||||
end
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
-- Equipment Inspection API
|
||||
if GetEquippedItems then
|
||||
pfUI.api.GetPlayerEquipment = function()
|
||||
return GetEquippedItems("player")
|
||||
end
|
||||
|
||||
pfUI.api.GetTargetEquipment = function()
|
||||
return GetEquippedItems("target")
|
||||
end
|
||||
|
||||
pfUI.api.GetEquippedItemInfo = function(unit, slot)
|
||||
if GetEquippedItem then
|
||||
return GetEquippedItem(unit, slot)
|
||||
end
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
-- Spell Lookup Helpers
|
||||
if GetSpellIdForName then
|
||||
pfUI.api.GetMaxRankSpellId = function(spellName)
|
||||
return GetSpellIdForName(spellName)
|
||||
end
|
||||
end
|
||||
|
||||
if GetSpellSlotTypeIdForName then
|
||||
pfUI.api.GetSpellSlotInfo = function(spellName)
|
||||
local slot, bookType, spellId = GetSpellSlotTypeIdForName(spellName)
|
||||
return {
|
||||
slot = slot,
|
||||
bookType = bookType,
|
||||
spellId = spellId,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
-- Queue Script API for advanced macro functionality
|
||||
if QueueScript then
|
||||
pfUI.api.QueueLuaScript = function(script, priority)
|
||||
QueueScript(script, priority or 1)
|
||||
end
|
||||
end
|
||||
|
||||
if QueueSpellByName then
|
||||
pfUI.api.QueueSpell = function(spellName)
|
||||
QueueSpellByName(spellName)
|
||||
end
|
||||
end
|
||||
|
||||
-- Channel optimization
|
||||
if ChannelStopCastingNextTick then
|
||||
pfUI.api.StopChannelNextTick = function()
|
||||
ChannelStopCastingNextTick()
|
||||
end
|
||||
end
|
||||
|
||||
-- Spell Database Access via GetSpellRec
|
||||
if GetSpellRec then
|
||||
pfUI.api.GetSpellRecord = function(spellId)
|
||||
local success, rec = pcall(GetSpellRec, spellId)
|
||||
if not success or not rec then return nil end
|
||||
return {
|
||||
spellId = spellId,
|
||||
name = rec.name or "",
|
||||
rank = rec.rank or "",
|
||||
description = rec.description or "",
|
||||
manaCost = rec.manaCost or 0,
|
||||
baseLevel = rec.baseLevel or 0,
|
||||
spellLevel = rec.spellLevel or 0,
|
||||
maxLevel = rec.maxLevel or 0,
|
||||
maxTargetLevel = rec.maxTargetLevel or 0,
|
||||
maxTargets = rec.maxTargets or 0,
|
||||
durationIndex = rec.durationIndex or 0,
|
||||
powerType = rec.powerType or 0,
|
||||
rangeIndex = rec.rangeIndex or 0,
|
||||
speed = rec.speed or 0,
|
||||
schoolMask = rec.schoolMask or 0,
|
||||
runeCostID = rec.runeCostID or 0,
|
||||
spellMissileID = rec.spellMissileID or 0,
|
||||
iconID = rec.iconID or 0,
|
||||
activeIconID = rec.activeIconID or 0,
|
||||
nameSubtext = rec.nameSubtext or "",
|
||||
castingTimeIndex = rec.castingTimeIndex or 0,
|
||||
categoryRecoveryTime = rec.categoryRecoveryTime or 0,
|
||||
recoveryTime = rec.recoveryTime or 0,
|
||||
startRecoveryCategory = rec.startRecoveryCategory or 0,
|
||||
startRecoveryTime = rec.startRecoveryTime or 0,
|
||||
}
|
||||
end
|
||||
|
||||
-- Get spell school (fire, frost, nature, etc.)
|
||||
pfUI.api.GetSpellSchool = function(spellId)
|
||||
local success, rec = pcall(GetSpellRec, spellId)
|
||||
if not success or not rec or not rec.schoolMask then return nil end
|
||||
local schools = {
|
||||
[1] = "Physical",
|
||||
[2] = "Holy",
|
||||
[4] = "Fire",
|
||||
[8] = "Nature",
|
||||
[16] = "Frost",
|
||||
[32] = "Shadow",
|
||||
[64] = "Arcane",
|
||||
}
|
||||
return schools[rec.schoolMask] or "Unknown"
|
||||
end
|
||||
end
|
||||
|
||||
-- Disenchant All utility
|
||||
if DisenchantAll then
|
||||
pfUI.api.DisenchantAllItems = function()
|
||||
DisenchantAll()
|
||||
end
|
||||
|
||||
SLASH_PFDISENCHANTALL1 = "/disenchantall"
|
||||
SLASH_PFDISENCHANTALL2 = "/dea"
|
||||
SlashCmdList["PFDISENCHANTALL"] = function()
|
||||
DisenchantAll()
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cff33ffccpfUI|r: Disenchanting all eligible items...")
|
||||
end
|
||||
end
|
||||
|
||||
-- Druid Secondary Mana Bar
|
||||
-- Shows base mana when druid is in shapeshift form (Bear/Cat uses Rage/Energy)
|
||||
-- Uses Nampower's GetUnitField to get base mana values
|
||||
-- Fully self-contained: uses its own config settings from C.unitframes.druidmana*
|
||||
local _, playerClass = UnitClass("player")
|
||||
|
||||
if GetUnitField and pfUI.uf and playerClass == "DRUID" and pfUI_config.unitframes.druidmanabar == "1" then
|
||||
local rawborder, default_border = GetBorderSize("unitframes")
|
||||
local DC = C.unitframes -- druid mana config lives here as druidmana* keys
|
||||
|
||||
-- Shared helper: create a druid mana bar on a unit frame
|
||||
local function CreateDruidManaBar(parent, unit)
|
||||
if not parent then return nil end
|
||||
|
||||
local parentConfig = parent.config
|
||||
|
||||
-- Read own config values
|
||||
local dmHeight = tonumber(DC.druidmanaheight) or 10
|
||||
local dmWidth = DC.druidmanawidth or "-1"
|
||||
local dmOffX = tonumber(DC.druidmanaoffx) or 0
|
||||
local dmOffY = tonumber(DC.druidmanaoffy) or 0
|
||||
local dmSpace = tonumber(DC.druidmanaspace) or -3
|
||||
local dmTexture = DC.druidmanatexture or "Interface\\AddOns\\pfUI\\img\\bar"
|
||||
|
||||
local bar = CreateFrame("StatusBar", "pfDruidMana_" .. unit, parent)
|
||||
bar:SetFrameStrata(parent:GetFrameStrata())
|
||||
bar:SetFrameLevel(parent:GetFrameLevel() + 5)
|
||||
bar:SetStatusBarTexture(pfUI.media[dmTexture] or dmTexture)
|
||||
|
||||
-- Bar color: use same manacolor logic as the normal power bar
|
||||
local manacolor = parentConfig.defcolor == "0" and parentConfig.manacolor or C.unitframes.manacolor
|
||||
local r, g, b, a = pfUI.api.strsplit(",", manacolor)
|
||||
bar:SetStatusBarColor(tonumber(r) or .25, tonumber(g) or .25, tonumber(b) or 1, tonumber(a) or 1)
|
||||
|
||||
-- Size: own width/height, fallback to parent power bar width if -1
|
||||
local width = dmWidth ~= "-1" and tonumber(dmWidth) or nil
|
||||
if width then
|
||||
bar:SetWidth(width)
|
||||
end
|
||||
bar:SetHeight(dmHeight)
|
||||
|
||||
-- Position below the power bar with own spacing + offsets
|
||||
local spacing = -2 * default_border - dmSpace
|
||||
if width then
|
||||
-- Fixed width: use single point with offset
|
||||
bar:SetPoint("TOP", parent.power, "BOTTOM", dmOffX, spacing + dmOffY)
|
||||
else
|
||||
-- Auto width: anchor to both sides of power bar
|
||||
bar:SetPoint("TOPLEFT", parent.power, "BOTTOMLEFT", dmOffX, spacing + dmOffY)
|
||||
bar:SetPoint("TOPRIGHT", parent.power, "BOTTOMRIGHT", dmOffX, spacing + dmOffY)
|
||||
end
|
||||
bar:Hide()
|
||||
|
||||
CreateBackdrop(bar)
|
||||
CreateBackdropShadow(bar)
|
||||
|
||||
-- Font settings (same logic as power bar)
|
||||
local fontname = pfUI.font_unit
|
||||
local fontsize = tonumber(pfUI_config.global.font_unit_size)
|
||||
local fontstyle = pfUI_config.global.font_unit_style
|
||||
|
||||
if parentConfig.customfont == "1" then
|
||||
fontname = pfUI.media[parentConfig.customfont_name]
|
||||
fontsize = tonumber(parentConfig.customfont_size)
|
||||
fontstyle = parentConfig.customfont_style
|
||||
end
|
||||
|
||||
-- Text color (always mana-colored)
|
||||
local tr, tg, tb = ManaBarColor[0].r, ManaBarColor[0].g, ManaBarColor[0].b
|
||||
if C.unitframes.pastel == "1" then
|
||||
tr, tg, tb = (tr + .75) * .5, (tg + .75) * .5, (tb + .75) * .5
|
||||
end
|
||||
|
||||
-- Single center text showing current/max
|
||||
bar.text = bar:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
|
||||
bar.text:SetFontObject(GameFontWhite)
|
||||
bar.text:SetFont(fontname, fontsize, fontstyle)
|
||||
bar.text:SetPoint("CENTER", bar, "CENTER", 0, 0)
|
||||
bar.text:SetJustifyH("CENTER")
|
||||
bar.text:SetTextColor(tr, tg, tb, 1)
|
||||
|
||||
return bar
|
||||
end
|
||||
|
||||
-- Shared helper: update druid mana bar values and text
|
||||
local function UpdateDruidManaBar(bar, unit)
|
||||
if not UnitExists(unit) then
|
||||
bar:Hide()
|
||||
return
|
||||
end
|
||||
|
||||
local powerType = UnitPowerType(unit)
|
||||
|
||||
-- Only show when NOT using mana (i.e., in Bear/Cat form)
|
||||
if powerType == 0 then
|
||||
bar:Hide()
|
||||
return
|
||||
end
|
||||
|
||||
-- Get base mana using Nampower's GetUnitField
|
||||
local baseMana, baseMaxMana
|
||||
local _, guid = UnitExists(unit)
|
||||
|
||||
if guid then
|
||||
baseMana = GetUnitField(guid, "power1")
|
||||
baseMaxMana = GetUnitField(guid, "maxPower1")
|
||||
end
|
||||
|
||||
-- Round down power values (Nampower can return decimals)
|
||||
if baseMana then baseMana = math.floor(baseMana) end
|
||||
if baseMaxMana then baseMaxMana = math.floor(baseMaxMana) end
|
||||
|
||||
if type(baseMana) ~= "number" or type(baseMaxMana) ~= "number" or baseMaxMana == 0 then
|
||||
bar:Hide()
|
||||
return
|
||||
end
|
||||
|
||||
-- Update bar
|
||||
bar:SetMinMaxValues(0, baseMaxMana)
|
||||
bar:SetValue(baseMana)
|
||||
|
||||
-- Always show current/max
|
||||
bar.text:SetText(string.format("%s/%s", Abbreviate(baseMana), Abbreviate(baseMaxMana)))
|
||||
|
||||
bar:Show()
|
||||
end
|
||||
|
||||
-- ===== Player Druid Mana Bar =====
|
||||
if pfUI.uf.player then
|
||||
local playerMana = CreateDruidManaBar(pfUI.uf.player, "player")
|
||||
|
||||
if playerMana then
|
||||
playerMana:RegisterEvent("UNIT_MANA")
|
||||
playerMana:RegisterEvent("UNIT_MAXMANA")
|
||||
playerMana:RegisterEvent("UNIT_DISPLAYPOWER")
|
||||
playerMana:RegisterEvent("UPDATE_SHAPESHIFT_FORM")
|
||||
playerMana:RegisterEvent("PLAYER_LOGOUT")
|
||||
playerMana:SetScript("OnEvent", function()
|
||||
if event == "PLAYER_LOGOUT" then
|
||||
this:UnregisterAllEvents()
|
||||
this:SetScript("OnEvent", nil)
|
||||
return
|
||||
end
|
||||
if arg1 == nil or arg1 == "player" then
|
||||
UpdateDruidManaBar(playerMana, "player")
|
||||
end
|
||||
end)
|
||||
|
||||
-- Initial update
|
||||
UpdateDruidManaBar(playerMana, "player")
|
||||
end
|
||||
end
|
||||
|
||||
-- ===== Target Druid Mana Bar =====
|
||||
if pfUI.uf.target then
|
||||
local targetMana = CreateDruidManaBar(pfUI.uf.target, "target")
|
||||
|
||||
if targetMana then
|
||||
targetMana:RegisterEvent("UNIT_MANA")
|
||||
targetMana:RegisterEvent("UNIT_MAXMANA")
|
||||
targetMana:RegisterEvent("UNIT_DISPLAYPOWER")
|
||||
targetMana:RegisterEvent("PLAYER_TARGET_CHANGED")
|
||||
targetMana:RegisterEvent("PLAYER_LOGOUT")
|
||||
targetMana:SetScript("OnEvent", function()
|
||||
if event == "PLAYER_LOGOUT" then
|
||||
this:UnregisterAllEvents()
|
||||
this:SetScript("OnEvent", nil)
|
||||
return
|
||||
end
|
||||
if event == "PLAYER_TARGET_CHANGED" or arg1 == nil or arg1 == "target" then
|
||||
UpdateDruidManaBar(targetMana, "target")
|
||||
end
|
||||
end)
|
||||
|
||||
-- Initial update
|
||||
UpdateDruidManaBar(targetMana, "target")
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
+6
-1
@@ -11,6 +11,11 @@ pfUI:RegisterModule("player", "vanilla:tbc", function ()
|
||||
pfUI.uf.player:SetPoint("BOTTOMRIGHT", UIParent, "BOTTOM", -75, 125)
|
||||
UpdateMovable(pfUI.uf.player)
|
||||
|
||||
-- Add throttle to player frame OnUpdate
|
||||
if pfUI.uf.player:GetScript("OnUpdate") then
|
||||
pfUI.uf.player:SetScript("OnUpdate", pfUI.uf.player:GetScript("OnUpdate"))
|
||||
end
|
||||
|
||||
-- Replace default's RESET_INSTANCES button with an always working one
|
||||
UnitPopupButtons["RESET_INSTANCES_FIX"] = { text = RESET_INSTANCES, dist = 0 }
|
||||
for id, text in pairs(UnitPopupMenus["SELF"]) do
|
||||
@@ -25,4 +30,4 @@ pfUI:RegisterModule("player", "vanilla:tbc", function ()
|
||||
StaticPopup_Show("CONFIRM_RESET_INSTANCES")
|
||||
end
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
+31
-1
@@ -87,6 +87,10 @@ pfUI:RegisterModule("raid", "vanilla:tbc", function ()
|
||||
pfUI.uf.raid:RegisterEvent("VARIABLES_LOADED")
|
||||
pfUI.uf.raid:SetScript("OnEvent", function() this:Show() end)
|
||||
pfUI.uf.raid:SetScript("OnUpdate", function()
|
||||
-- Throttle raid roster updates to 1 FPS
|
||||
if (this.tick or 0) > GetTime() then return end
|
||||
this.tick = GetTime() + 1.0
|
||||
|
||||
-- don't proceed without raid or during combat
|
||||
if not UnitInRaid("player") or (InCombatLockdown and InCombatLockdown()) then return end
|
||||
|
||||
@@ -109,6 +113,32 @@ pfUI:RegisterModule("raid", "vanilla:tbc", function ()
|
||||
end
|
||||
end
|
||||
|
||||
-- Smart GUID-based updates: only refresh frames where unit changed
|
||||
if pfUI.uf.guidTracker then
|
||||
local tracker = pfUI.uf.guidTracker
|
||||
|
||||
for i = 1, maxraid do
|
||||
local frame = pfUI.uf.raid[i]
|
||||
if frame and frame.id and frame.id > 0 then
|
||||
local unit = "raid" .. frame.id
|
||||
local _, newGuid = UnitExists(unit)
|
||||
local oldGuid = tracker.frameToGuid[frame]
|
||||
|
||||
if newGuid ~= oldGuid then
|
||||
-- GUID changed = different player = need full update
|
||||
tracker.frameToGuid[frame] = newGuid
|
||||
frame.update_full = true
|
||||
frame.update_aura = true -- Force aura refresh!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- rebuild unitmap after frame IDs are assigned
|
||||
if pfUI.uf.RebuildUnitmap then
|
||||
pfUI.uf.RebuildUnitmap()
|
||||
end
|
||||
|
||||
this:Hide()
|
||||
end)
|
||||
|
||||
@@ -131,4 +161,4 @@ pfUI:RegisterModule("raid", "vanilla:tbc", function ()
|
||||
pfUI.uf.raid:Show()
|
||||
end
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
+5
-2
@@ -1,7 +1,10 @@
|
||||
pfUI:RegisterModule("skin", "vanilla:tbc", function ()
|
||||
pfUI:RegisterModule("skin", "vanilla", 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
|
||||
@@ -76,4 +79,4 @@ pfUI:RegisterModule("skin", "vanilla:tbc", function ()
|
||||
else
|
||||
UIErrorsFrame:RegisterEvent("UI_ERROR_MESSAGE")
|
||||
end
|
||||
end)
|
||||
end)
|
||||
+245
-215
@@ -1,6 +1,43 @@
|
||||
-- Compatibility layer to use castbars provided by SuperWoW:
|
||||
-- https://github.com/balakethelock/SuperWoW
|
||||
|
||||
-- DLL Status Check Command (always available)
|
||||
SLASH_PFDLLSTATUS1 = "/pfdll"
|
||||
SlashCmdList["PFDLLSTATUS"] = function()
|
||||
local chat = DEFAULT_CHAT_FRAME
|
||||
chat:AddMessage("|cff33ffccpfUI|r: DLL Status Check")
|
||||
|
||||
-- SuperWoW
|
||||
if SUPERWOW_VERSION then
|
||||
chat:AddMessage(" |cff00ff00SuperWoW|r: v" .. tostring(SUPERWOW_VERSION))
|
||||
elseif SpellInfo or SetAutoloot then
|
||||
chat:AddMessage(" |cffffff00SuperWoW|r: Detected (old version)")
|
||||
else
|
||||
chat:AddMessage(" |cffff0000SuperWoW|r: Not detected")
|
||||
end
|
||||
|
||||
-- Nampower
|
||||
if GetNampowerVersion then
|
||||
chat:AddMessage(" |cff00ff00Nampower|r: v" .. tostring(GetNampowerVersion()))
|
||||
else
|
||||
chat:AddMessage(" |cffff0000Nampower|r: Not detected")
|
||||
end
|
||||
|
||||
-- Check if castbar exists for indicator positioning
|
||||
if pfUI.castbar and pfUI.castbar.player then
|
||||
chat:AddMessage(" |cff00ff00Castbar|r: Available for indicator anchoring")
|
||||
else
|
||||
chat:AddMessage(" |cffffff00Castbar|r: Not available (indicators use fallback position)")
|
||||
end
|
||||
|
||||
-- Check indicator frames
|
||||
if pfUI.uf and pfUI.uf.target then
|
||||
chat:AddMessage(" |cff00ff00Target frame|r: exists")
|
||||
else
|
||||
chat:AddMessage(" |cffff0000Target frame|r: NOT found")
|
||||
end
|
||||
end
|
||||
|
||||
pfUI:RegisterModule("superwow", "vanilla", function ()
|
||||
if SetAutoloot and SpellInfo and not SUPERWOW_VERSION then
|
||||
-- Turn every enchanting link that we create in the enchanting frame,
|
||||
@@ -46,214 +83,201 @@ pfUI:RegisterModule("superwow", "vanilla", function ()
|
||||
end)
|
||||
end
|
||||
|
||||
-- Add native mouseover support
|
||||
if SUPERWOW_VERSION and pfUI.uf and pfUI.uf.mouseover then
|
||||
_G.SlashCmdList.PFCAST = function(msg)
|
||||
local func = loadstring(msg or "")
|
||||
local unit = "mouseover"
|
||||
-- TrackUnit API for adding group members to minimap
|
||||
-- Tracks friendly units on the minimap for easier group coordination
|
||||
if TrackUnit and C.unitframes.track_group == "1" then
|
||||
local trackFrame = CreateFrame("Frame")
|
||||
trackFrame:RegisterEvent("PARTY_MEMBERS_CHANGED")
|
||||
trackFrame:RegisterEvent("RAID_ROSTER_UPDATE")
|
||||
trackFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||||
trackFrame:RegisterEvent("PLAYER_LOGOUT")
|
||||
|
||||
if not UnitExists(unit) then
|
||||
local frame = GetMouseFocus()
|
||||
if frame.label and frame.id then
|
||||
unit = frame.label .. frame.id
|
||||
elseif UnitExists("target") then
|
||||
unit = "target"
|
||||
elseif GetCVar("autoSelfCast") == "1" then
|
||||
unit = "player"
|
||||
else
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
if func then
|
||||
-- set mouseover to target for script if needed
|
||||
local switch_target = not UnitIsUnit("target", unit)
|
||||
if switch_target then TargetUnit(unit) end
|
||||
func()
|
||||
if switch_target then TargetLastTarget() end
|
||||
else
|
||||
-- write temporary unit name
|
||||
pfUI.uf.mouseover.unit = unit
|
||||
|
||||
-- cast spell to unitstr
|
||||
CastSpellByName(msg, unit)
|
||||
|
||||
-- remove temporary mouseover unit
|
||||
pfUI.uf.mouseover.unit = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Add support for druid mana bars
|
||||
if SUPERWOW_VERSION and pfUI.uf and pfUI.uf.player and pfUI_config.unitframes.druidmanabar == "1" then
|
||||
local parent = pfUI.uf.player.power.bar
|
||||
local config = pfUI.uf.player.config
|
||||
local mana = config.defcolor == "0" and config.manacolor or pfUI_config.unitframes.manacolor
|
||||
local r, g, b, a = pfUI.api.strsplit(",", mana)
|
||||
local rawborder, default_border = GetBorderSize("unitframes")
|
||||
local _, class = UnitClass("player")
|
||||
local width = config.pwidth ~= "-1" and config.pwidth or config.width
|
||||
|
||||
local fontname = pfUI.font_unit
|
||||
local fontsize = tonumber(pfUI_config.global.font_unit_size)
|
||||
local fontstyle = pfUI_config.global.font_unit_style
|
||||
|
||||
if config.customfont == "1" then
|
||||
fontname = pfUI.media[config.customfont_name]
|
||||
fontsize = tonumber(config.customfont_size)
|
||||
fontstyle = config.customfont_style
|
||||
end
|
||||
|
||||
local druidmana = CreateFrame("StatusBar", "pfDruidMana", UIParent)
|
||||
druidmana:SetFrameStrata(parent:GetFrameStrata())
|
||||
druidmana:SetFrameLevel(parent:GetFrameLevel() + 16)
|
||||
druidmana:SetStatusBarTexture(pfUI.media[config.pbartexture])
|
||||
druidmana:SetStatusBarColor(r, g, b, a)
|
||||
druidmana:SetPoint("TOPLEFT", parent, "BOTTOMLEFT", 0, -2*default_border - config.pspace)
|
||||
druidmana:SetPoint("TOPRIGHT", parent, "BOTTOMRIGHT", 0, -2*default_border - config.pspace)
|
||||
druidmana:SetWidth(width)
|
||||
druidmana:SetHeight(tonumber(pfUI_config.unitframes.druidmanaheight) or 6)
|
||||
druidmana:EnableMouse(true)
|
||||
druidmana:Hide()
|
||||
|
||||
UpdateMovable(druidmana)
|
||||
CreateBackdrop(druidmana)
|
||||
CreateBackdropShadow(druidmana)
|
||||
|
||||
druidmana:RegisterEvent("UNIT_MANA")
|
||||
druidmana:RegisterEvent("UNIT_MAXMANA")
|
||||
druidmana:RegisterEvent("UNIT_DISPLAYPOWER")
|
||||
druidmana:SetScript("OnEvent", function()
|
||||
if UnitPowerType("player") == 0 then
|
||||
this:Hide()
|
||||
trackFrame:SetScript("OnEvent", function()
|
||||
-- Handle shutdown to prevent crash 132
|
||||
if event == "PLAYER_LOGOUT" then
|
||||
this:UnregisterAllEvents()
|
||||
this:SetScript("OnEvent", nil)
|
||||
return
|
||||
end
|
||||
|
||||
local _, mana = UnitMana("player")
|
||||
local _, max = UnitManaMax("player")
|
||||
local perc = math.ceil(mana / max * 100)
|
||||
if perc == 100 then
|
||||
this.text:SetText(string.format("%s", Abbreviate(mana)))
|
||||
else
|
||||
this.text:SetText(string.format("%s - %s%%", Abbreviate(mana), perc))
|
||||
-- Track party members
|
||||
for i = 1, 4 do
|
||||
local unit = "party" .. i
|
||||
if UnitExists(unit) and UnitIsConnected(unit) then
|
||||
pcall(TrackUnit, unit)
|
||||
end
|
||||
end
|
||||
|
||||
-- Track raid members
|
||||
for i = 1, 40 do
|
||||
local unit = "raid" .. i
|
||||
if UnitExists(unit) and UnitIsConnected(unit) and not UnitIsUnit(unit, "player") then
|
||||
pcall(TrackUnit, unit)
|
||||
end
|
||||
end
|
||||
this:SetMinMaxValues(0, max)
|
||||
this:SetValue(mana)
|
||||
this:Show()
|
||||
end)
|
||||
end
|
||||
|
||||
druidmana.text = druidmana:CreateFontString("Status", "OVERLAY", "GameFontNormalSmall")
|
||||
druidmana.text:SetFontObject(GameFontWhite)
|
||||
druidmana.text:SetFont(fontname, fontsize, fontstyle)
|
||||
druidmana.text:SetPoint("RIGHT", -2*(default_border + config.txtpowerrightoffx), 0)
|
||||
druidmana.text:SetPoint("LEFT", 2*(default_border + config.txtpowerrightoffx), 0)
|
||||
druidmana.text:SetJustifyH("RIGHT")
|
||||
-- Raid Marker Targeting API
|
||||
-- Allows targeting units by raid marker ("mark1" to "mark8")
|
||||
if SUPERWOW_VERSION then
|
||||
pfUI.api.GetMarkedUnit = function(markIndex)
|
||||
local markUnit = "mark" .. markIndex
|
||||
if UnitExists(markUnit) then
|
||||
return markUnit
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
if config["powercolor"] == "1" then
|
||||
local r = ManaBarColor[0].r
|
||||
local g = ManaBarColor[0].g
|
||||
local b = ManaBarColor[0].b
|
||||
pfUI.api.TargetMark = function(markIndex)
|
||||
local markUnit = "mark" .. markIndex
|
||||
if UnitExists(markUnit) then
|
||||
TargetUnit(markUnit)
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
if pfUI_config.unitframes.pastel == "1" then
|
||||
druidmana.text:SetTextColor((r+.75)*.5, (g+.75)*.5, (b+.75)*.5, 1)
|
||||
-- Get owner of pet/totem using "owner" suffix
|
||||
pfUI.api.GetUnitOwner = function(unit)
|
||||
local ownerUnit = unit .. "owner"
|
||||
if UnitExists(ownerUnit) then
|
||||
return UnitName(ownerUnit), ownerUnit
|
||||
end
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
-- Clickthrough Mode API
|
||||
-- Allows clicking through corpses to loot underneath
|
||||
if Clickthrough then
|
||||
pfUI.api.SetClickthrough = function(enabled)
|
||||
Clickthrough(enabled and 1 or 0)
|
||||
end
|
||||
|
||||
pfUI.api.GetClickthrough = function()
|
||||
return Clickthrough() == 1
|
||||
end
|
||||
|
||||
pfUI.api.ToggleClickthrough = function()
|
||||
local current = Clickthrough()
|
||||
Clickthrough(current == 1 and 0 or 1)
|
||||
return Clickthrough() == 1
|
||||
end
|
||||
|
||||
-- Add slash command for clickthrough toggle
|
||||
SLASH_PFCLICKTHROUGH1 = "/clickthrough"
|
||||
SLASH_PFCLICKTHROUGH2 = "/ct"
|
||||
SlashCmdList["PFCLICKTHROUGH"] = function()
|
||||
local enabled = pfUI.api.ToggleClickthrough()
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cff33ffccpfUI|r: Clickthrough mode " .. (enabled and "|cff00ff00enabled|r" or "|cffff0000disabled|r"))
|
||||
end
|
||||
end
|
||||
|
||||
-- Autoloot Control API
|
||||
if SetAutoloot then
|
||||
pfUI.api.SetAutoloot = function(enabled)
|
||||
SetAutoloot(enabled and 1 or 0)
|
||||
end
|
||||
|
||||
pfUI.api.GetAutoloot = function()
|
||||
return SetAutoloot() == 1
|
||||
end
|
||||
|
||||
pfUI.api.ToggleAutoloot = function()
|
||||
local current = SetAutoloot()
|
||||
SetAutoloot(current == 1 and 0 or 1)
|
||||
return SetAutoloot() == 1
|
||||
end
|
||||
end
|
||||
|
||||
-- GetPlayerBuffID wrapper
|
||||
if GetPlayerBuffID then
|
||||
pfUI.api.GetPlayerBuffSpellId = function(buffIndex)
|
||||
return GetPlayerBuffID(buffIndex)
|
||||
end
|
||||
end
|
||||
|
||||
-- CombatLogAdd wrapper for logging
|
||||
if CombatLogAdd then
|
||||
pfUI.api.LogToCombatLog = function(text, raw)
|
||||
CombatLogAdd(text, raw and 1 or nil)
|
||||
end
|
||||
end
|
||||
|
||||
-- Local Raid Markers (marks only visible to self)
|
||||
if SetRaidTarget then
|
||||
local origSetRaidTarget = SetRaidTarget
|
||||
pfUI.api.SetLocalRaidTarget = function(unit, index)
|
||||
origSetRaidTarget(unit, index, "local")
|
||||
end
|
||||
end
|
||||
|
||||
-- Enhanced GetContainerItemInfo for charges
|
||||
-- SuperWoW returns charges as negative numbers
|
||||
pfUI.api.GetItemCharges = function(bag, slot)
|
||||
local texture, count = GetContainerItemInfo(bag, slot)
|
||||
if count and count < 0 then
|
||||
return math.abs(count) -- Return positive charge count
|
||||
end
|
||||
return nil -- Not a charged item
|
||||
end
|
||||
|
||||
-- Weapon Enchant Info on other players
|
||||
if GetWeaponEnchantInfo then
|
||||
local origGetWeaponEnchantInfo = GetWeaponEnchantInfo
|
||||
pfUI.api.GetUnitWeaponEnchants = function(unit)
|
||||
if unit and unit ~= "player" then
|
||||
local mhName, ohName = GetWeaponEnchantInfo(unit)
|
||||
return {
|
||||
mainHand = mhName,
|
||||
offHand = ohName,
|
||||
}
|
||||
else
|
||||
druidmana.text:SetTextColor(r, g, b, a)
|
||||
local hasMainHandEnchant, mainHandExpiration, mainHandCharges, hasOffHandEnchant, offHandExpiration, offHandCharges = origGetWeaponEnchantInfo()
|
||||
return {
|
||||
mainHand = hasMainHandEnchant and true or false,
|
||||
mainHandExpiration = mainHandExpiration,
|
||||
mainHandCharges = mainHandCharges,
|
||||
offHand = hasOffHandEnchant and true or false,
|
||||
offHandExpiration = offHandExpiration,
|
||||
offHandCharges = offHandCharges,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
if pfUI_config.unitframes.druidmanatext == "1" then
|
||||
druidmana.text:Show()
|
||||
else
|
||||
druidmana.text:Hide()
|
||||
end
|
||||
|
||||
if class ~= "DRUID" then
|
||||
druidmana:UnregisterAllEvents()
|
||||
druidmana:Hide()
|
||||
end
|
||||
end
|
||||
|
||||
-- Add support for guid based focus frame
|
||||
if SUPERWOW_VERSION and pfUI.uf and pfUI.uf.focus then
|
||||
local focus = function(unitstr)
|
||||
-- try to read target's unit guid
|
||||
local _, guid = UnitExists(unitstr)
|
||||
|
||||
if guid and pfUI.uf.focus then
|
||||
-- update focus frame
|
||||
pfUI.uf.focus.unitname = nil
|
||||
pfUI.uf.focus.label = guid
|
||||
pfUI.uf.focus.id = ""
|
||||
|
||||
-- update focustarget frame
|
||||
pfUI.uf.focustarget.unitname = nil
|
||||
pfUI.uf.focustarget.label = guid .. "target"
|
||||
pfUI.uf.focustarget.id = ""
|
||||
end
|
||||
|
||||
return guid
|
||||
end
|
||||
|
||||
-- extend the builtin /focus slash command
|
||||
local legacyfocus = SlashCmdList.PFFOCUS
|
||||
function SlashCmdList.PFFOCUS(msg)
|
||||
-- try to perform guid based focus
|
||||
local guid = focus("target")
|
||||
|
||||
-- run old focus emulation
|
||||
if not guid then legacyfocus(msg) end
|
||||
end
|
||||
|
||||
-- extend the builtin /swapfocus slash command
|
||||
local legacyswapfocus = SlashCmdList.PFSWAPFOCUS
|
||||
function SlashCmdList.PFSWAPFOCUS(msg)
|
||||
-- save previous focus values
|
||||
local oldlabel = pfUI.uf.focus.label or ""
|
||||
local oldid = pfUI.uf.focus.id or ""
|
||||
|
||||
-- try to perform guid based focus
|
||||
local guid = focus("target")
|
||||
|
||||
-- target old focus
|
||||
if guid and oldlabel and oldid then
|
||||
TargetUnit(oldlabel..oldid)
|
||||
end
|
||||
|
||||
-- run old focus emulation
|
||||
if not guid then legacyswapfocus(msg) end
|
||||
end
|
||||
end
|
||||
|
||||
-- Enhance libdebuff with SuperWoW data
|
||||
local superdebuff = CreateFrame("Frame")
|
||||
superdebuff:RegisterEvent("UNIT_CASTEVENT")
|
||||
superdebuff:SetScript("OnEvent", function()
|
||||
-- variable assignments
|
||||
local caster, target, event, spell, duration = arg1, arg2, arg3, arg4
|
||||
|
||||
-- skip other caster and empty target events
|
||||
local _, guid = UnitExists("player")
|
||||
if caster ~= guid then return end
|
||||
if event ~= "CAST" then return end
|
||||
if not target or target == "" then return end
|
||||
|
||||
-- assign all required data
|
||||
local unit = UnitName(target)
|
||||
local unitlevel = UnitLevel(target)
|
||||
local effect, rank = SpellInfo(spell)
|
||||
local duration = libdebuff:GetDuration(effect, rank)
|
||||
local caster = "player"
|
||||
|
||||
-- add effect to current debuff data
|
||||
libdebuff:AddEffect(unit, unitlevel, effect, duration, caster)
|
||||
end)
|
||||
|
||||
-- Enhance libcast with SuperWoW data
|
||||
-- Enhance libcast with SuperWoW data for NPCs and other players
|
||||
-- Player casts use SPELLCAST_* events for proper pushback handling
|
||||
local supercast = CreateFrame("Frame")
|
||||
local playerGuid = nil
|
||||
|
||||
supercast:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||||
supercast:RegisterEvent("UNIT_CASTEVENT")
|
||||
supercast:RegisterEvent("PLAYER_LOGOUT")
|
||||
supercast:SetScript("OnEvent", function()
|
||||
if not supercast.init then
|
||||
-- disable combat parsing events in superwow mode
|
||||
-- Handle shutdown to prevent crash 132
|
||||
if event == "PLAYER_LOGOUT" then
|
||||
this:UnregisterAllEvents()
|
||||
this:SetScript("OnEvent", nil)
|
||||
return
|
||||
end
|
||||
|
||||
if event == "PLAYER_ENTERING_WORLD" then
|
||||
-- Cache player GUID
|
||||
if UnitExists then
|
||||
local _, guid = UnitExists("player")
|
||||
playerGuid = guid
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
local guid = arg1
|
||||
local isPlayer = guid == playerGuid
|
||||
|
||||
-- For non-player units: disable combat parsing events (one-time init)
|
||||
if not isPlayer and not supercast.init then
|
||||
-- disable combat parsing events in superwow mode (for non-player units)
|
||||
libcast:UnregisterEvent("CHAT_MSG_SPELL_SELF_DAMAGE")
|
||||
libcast:UnregisterEvent("CHAT_MSG_SPELL_HOSTILEPLAYER_DAMAGE")
|
||||
libcast:UnregisterEvent("CHAT_MSG_SPELL_HOSTILEPLAYER_BUFF")
|
||||
@@ -276,17 +300,21 @@ pfUI:RegisterModule("superwow", "vanilla", function ()
|
||||
end
|
||||
|
||||
if arg3 == "START" or arg3 == "CAST" or arg3 == "CHANNEL" then
|
||||
-- human readable argument list
|
||||
local guid = arg1
|
||||
local target = arg2
|
||||
local event_type = arg3
|
||||
local spell_id = arg4
|
||||
local timer = arg5
|
||||
local start = GetTime()
|
||||
|
||||
-- get spell info from spell id
|
||||
local spell, icon, _
|
||||
if SpellInfo and SpellInfo(spell_id) then
|
||||
if GetSpellRec then
|
||||
local rec = GetSpellRec(spell_id)
|
||||
if rec then
|
||||
spell = rec.name
|
||||
local iconID = rec.spellIconID
|
||||
icon = iconID and GetSpellIconTexture(iconID) or nil
|
||||
end
|
||||
elseif SpellInfo and SpellInfo(spell_id) then
|
||||
spell, _, icon = SpellInfo(spell_id)
|
||||
end
|
||||
|
||||
@@ -304,28 +332,30 @@ pfUI:RegisterModule("superwow", "vanilla", function ()
|
||||
end
|
||||
end
|
||||
|
||||
-- For player: store in libcast.db[playerName] so pushback tracking works
|
||||
-- For others: store by GUID
|
||||
local dbKey = isPlayer and UnitName("player") or guid
|
||||
|
||||
-- add cast action to the database
|
||||
if not libcast.db[guid] then libcast.db[guid] = {} end
|
||||
libcast.db[guid].cast = spell
|
||||
libcast.db[guid].rank = nil
|
||||
libcast.db[guid].start = GetTime()
|
||||
libcast.db[guid].casttime = timer
|
||||
libcast.db[guid].icon = icon
|
||||
libcast.db[guid].channel = event_type == "CHANNEL" or false
|
||||
|
||||
-- write state variable
|
||||
superwow_active = true
|
||||
if not libcast.db[dbKey] then libcast.db[dbKey] = {} end
|
||||
libcast.db[dbKey].cast = spell
|
||||
libcast.db[dbKey].rank = nil
|
||||
libcast.db[dbKey].start = GetTime()
|
||||
libcast.db[dbKey].casttime = timer or 0
|
||||
libcast.db[dbKey].icon = icon
|
||||
libcast.db[dbKey].channel = event_type == "CHANNEL" or false
|
||||
elseif arg3 == "FAIL" then
|
||||
local guid = arg1
|
||||
|
||||
-- delete all cast entries of guid
|
||||
if libcast.db[guid] then
|
||||
libcast.db[guid].cast = nil
|
||||
libcast.db[guid].rank = nil
|
||||
libcast.db[guid].start = nil
|
||||
libcast.db[guid].casttime = nil
|
||||
libcast.db[guid].icon = nil
|
||||
libcast.db[guid].channel = nil
|
||||
-- For player: use playerName, for others: use GUID
|
||||
local dbKey = isPlayer and UnitName("player") or guid
|
||||
|
||||
-- delete all cast entries
|
||||
if libcast.db[dbKey] then
|
||||
libcast.db[dbKey].cast = nil
|
||||
libcast.db[dbKey].rank = nil
|
||||
libcast.db[dbKey].start = nil
|
||||
libcast.db[dbKey].casttime = nil
|
||||
libcast.db[dbKey].icon = nil
|
||||
libcast.db[dbKey].channel = nil
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
@@ -0,0 +1,586 @@
|
||||
pfUI:RegisterModule("swingtimer", "vanilla:tbc", function ()
|
||||
local rawborder, border = GetBorderSize()
|
||||
|
||||
-- HitInfo flags (EVENTS.md)
|
||||
local HITINFO_LEFTSWING = 4 -- 0x4: Off-hand attack
|
||||
local HITINFO_NOACTION = 65536 -- 0x10000: server did not advance the swing clock
|
||||
|
||||
-- SPELL_QUEUE_EVENT codes (EVENTS.md)
|
||||
local ON_SWING_QUEUED = 0
|
||||
local ON_SWING_QUEUE_POPPED = 1
|
||||
|
||||
-- Swing state
|
||||
local swingState = {
|
||||
mainhand = { speed = 0, nextSwing = 0, swinging = false },
|
||||
offhand = { speed = 0, nextSwing = 0, swinging = false },
|
||||
ranged = { speed = 0, nextSwing = 0, swinging = false },
|
||||
}
|
||||
|
||||
-- Ranged spell IDs that trigger the ranged swing timer (replaces MH)
|
||||
local RANGED_SPELLIDS = {
|
||||
[75] = true, -- Auto Shot (Hunter)
|
||||
[2764] = true, -- Throw (Warrior/Rogue)
|
||||
}
|
||||
|
||||
-- Create container frame
|
||||
pfUI.swingtimer = CreateFrame("Frame", "pfSwingTimer", UIParent)
|
||||
pfUI.swingtimer:SetFrameStrata("MEDIUM")
|
||||
pfUI.swingtimer:Hide()
|
||||
|
||||
-- Read config once at load into locals
|
||||
local sw_width = tonumber(C.unitframes.swingtimerwidth) or 200
|
||||
local sw_height = tonumber(C.unitframes.swingtimerheight) or 12
|
||||
local sw_texture = C.unitframes.swingtimertexture or "Interface\\AddOns\\pfUI\\img\\bar"
|
||||
local sw_showtext = C.unitframes.swingtimertext ~= "0"
|
||||
local sw_showlabel = C.unitframes.swingtimerlabel ~= "0"
|
||||
local sw_showoh = C.unitframes.swingtimeroffhand ~= "0"
|
||||
local sw_showranged = C.unitframes.swingtimerranged ~= "0"
|
||||
local sw_fontsize = tonumber(C.unitframes.swingtimerfontsize) or 12
|
||||
local sw_hsqueue = C.unitframes.swingtimerhsqueue ~= "0"
|
||||
|
||||
-- Parse color strings "r,g,b,a" into components
|
||||
local function ParseColor(str, dr, dg, db, da)
|
||||
if not str or str == "" then return dr, dg, db, da end
|
||||
local _, _, r, g, b, a = string.find(str, "([%d%.]+),([%d%.]+),([%d%.]+),([%d%.]+)")
|
||||
if r then
|
||||
return tonumber(r) or dr, tonumber(g) or dg, tonumber(b) or db, tonumber(a) or da
|
||||
end
|
||||
return dr, dg, db, da
|
||||
end
|
||||
|
||||
local mhR, mhG, mhB, mhA = ParseColor(C.unitframes.swingtimermhcolor, 0.8, 0.3, 0.3, 1)
|
||||
local ohR, ohG, ohB, ohA = ParseColor(C.unitframes.swingtimerohcolor, 0.3, 0.8, 0.3, 1)
|
||||
local raR, raG, raB, raA = ParseColor(C.unitframes.swingtimerrangedcolor, 0.3, 0.6, 1.0, 1)
|
||||
local rwR, rwG, rwB, rwA = ParseColor(C.unitframes.swingtimerrangedwarncolor, 0.9, 0.0, 0.0, 1)
|
||||
local isHunter = UnitClass("player") == "Hunter"
|
||||
|
||||
-- Store default MH color for HS/Cleave restore
|
||||
local mhDefaultR, mhDefaultG, mhDefaultB = mhR, mhG, mhB
|
||||
|
||||
-- Mainhand bar
|
||||
pfUI.swingtimer.mainhand = CreateFrame("StatusBar", "pfSwingTimerMainhand", UIParent)
|
||||
pfUI.swingtimer.mainhand:SetPoint("CENTER", UIParent, "CENTER", 0, -100)
|
||||
pfUI.swingtimer.mainhand:SetWidth(sw_width)
|
||||
pfUI.swingtimer.mainhand:SetHeight(sw_height)
|
||||
pfUI.swingtimer.mainhand:SetMinMaxValues(0, 1)
|
||||
pfUI.swingtimer.mainhand:SetValue(0)
|
||||
pfUI.swingtimer.mainhand:SetStatusBarTexture(sw_texture)
|
||||
pfUI.swingtimer.mainhand:SetStatusBarColor(mhR, mhG, mhB, mhA)
|
||||
pfUI.swingtimer.mainhand:Hide()
|
||||
|
||||
pfUI.swingtimer.mainhand.text = pfUI.swingtimer.mainhand:CreateFontString("Status", "DIALOG", "GameFontNormal")
|
||||
pfUI.swingtimer.mainhand.text:SetPoint("CENTER", pfUI.swingtimer.mainhand, "CENTER", 0, 0)
|
||||
pfUI.swingtimer.mainhand.text:SetFont(pfUI.font_default, sw_fontsize, "OUTLINE")
|
||||
pfUI.swingtimer.mainhand.text:SetTextColor(1, 1, 1, 1)
|
||||
pfUI.swingtimer.mainhand.text:SetText("")
|
||||
if not sw_showtext then pfUI.swingtimer.mainhand.text:Hide() end
|
||||
|
||||
pfUI.swingtimer.mainhand.label = pfUI.swingtimer.mainhand:CreateFontString("Status", "DIALOG", "GameFontNormal")
|
||||
pfUI.swingtimer.mainhand.label:SetPoint("RIGHT", pfUI.swingtimer.mainhand, "LEFT", -4, 0)
|
||||
pfUI.swingtimer.mainhand.label:SetFont(pfUI.font_default, sw_fontsize, "OUTLINE")
|
||||
pfUI.swingtimer.mainhand.label:SetTextColor(0.8, 0.8, 0.8, 1)
|
||||
pfUI.swingtimer.mainhand.label:SetText(sw_showlabel and "MH" or "")
|
||||
|
||||
CreateBackdrop(pfUI.swingtimer.mainhand)
|
||||
CreateBackdropShadow(pfUI.swingtimer.mainhand)
|
||||
|
||||
-- Offhand bar
|
||||
pfUI.swingtimer.offhand = CreateFrame("StatusBar", "pfSwingTimerOffhand", UIParent)
|
||||
pfUI.swingtimer.offhand:SetPoint("TOP", pfUI.swingtimer.mainhand, "BOTTOM", 0, -4)
|
||||
pfUI.swingtimer.offhand:SetWidth(sw_width)
|
||||
pfUI.swingtimer.offhand:SetHeight(sw_height)
|
||||
pfUI.swingtimer.offhand:SetMinMaxValues(0, 1)
|
||||
pfUI.swingtimer.offhand:SetValue(0)
|
||||
pfUI.swingtimer.offhand:SetStatusBarTexture(sw_texture)
|
||||
pfUI.swingtimer.offhand:SetStatusBarColor(ohR, ohG, ohB, ohA)
|
||||
pfUI.swingtimer.offhand:Hide()
|
||||
|
||||
pfUI.swingtimer.offhand.text = pfUI.swingtimer.offhand:CreateFontString("Status", "DIALOG", "GameFontNormal")
|
||||
pfUI.swingtimer.offhand.text:SetPoint("CENTER", pfUI.swingtimer.offhand, "CENTER", 0, 0)
|
||||
pfUI.swingtimer.offhand.text:SetFont(pfUI.font_default, sw_fontsize, "OUTLINE")
|
||||
pfUI.swingtimer.offhand.text:SetTextColor(1, 1, 1, 1)
|
||||
pfUI.swingtimer.offhand.text:SetText("")
|
||||
if not sw_showtext then pfUI.swingtimer.offhand.text:Hide() end
|
||||
|
||||
pfUI.swingtimer.offhand.label = pfUI.swingtimer.offhand:CreateFontString("Status", "DIALOG", "GameFontNormal")
|
||||
pfUI.swingtimer.offhand.label:SetPoint("RIGHT", pfUI.swingtimer.offhand, "LEFT", -4, 0)
|
||||
pfUI.swingtimer.offhand.label:SetFont(pfUI.font_default, sw_fontsize, "OUTLINE")
|
||||
pfUI.swingtimer.offhand.label:SetTextColor(0.8, 0.8, 0.8, 1)
|
||||
pfUI.swingtimer.offhand.label:SetText(sw_showlabel and "OH" or "")
|
||||
|
||||
CreateBackdrop(pfUI.swingtimer.offhand)
|
||||
CreateBackdropShadow(pfUI.swingtimer.offhand)
|
||||
|
||||
-- Ranged bar (bow/gun/crossbow - triggered by SPELL_GO_SELF for Auto Shot / Throw)
|
||||
-- Hunter uses a special "close from outside->in, open inside->out" animation
|
||||
-- instead of a normal left->right StatusBar fill.
|
||||
pfUI.swingtimer.ranged = CreateFrame("Frame", "pfSwingTimerRanged", UIParent)
|
||||
pfUI.swingtimer.ranged:SetPoint("CENTER", UIParent, "CENTER", 0, -120)
|
||||
pfUI.swingtimer.ranged:SetWidth(sw_width)
|
||||
pfUI.swingtimer.ranged:SetHeight(sw_height)
|
||||
pfUI.swingtimer.ranged:Hide()
|
||||
|
||||
-- Phase 1: left half, anchored to CENTER (right edge fixed), shrinks leftward = outside->in
|
||||
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)
|
||||
|
||||
-- Phase 1: right half, anchored to CENTER (left edge fixed), shrinks rightward = outside->in
|
||||
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)
|
||||
|
||||
-- Phase 2: warning color, anchored CENTER, grows outward
|
||||
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")
|
||||
pfUI.swingtimer.ranged.text:SetTextColor(1, 1, 1, 1)
|
||||
pfUI.swingtimer.ranged.text:SetText("")
|
||||
if not sw_showtext then pfUI.swingtimer.ranged.text:Hide() end
|
||||
|
||||
pfUI.swingtimer.ranged.label = pfUI.swingtimer.ranged:CreateFontString("Status", "DIALOG", "GameFontNormal")
|
||||
pfUI.swingtimer.ranged.label:SetPoint("RIGHT", pfUI.swingtimer.ranged, "LEFT", -4, 0)
|
||||
pfUI.swingtimer.ranged.label:SetFont(pfUI.font_default, sw_fontsize, "OUTLINE")
|
||||
pfUI.swingtimer.ranged.label:SetTextColor(0.8, 0.8, 0.8, 1)
|
||||
pfUI.swingtimer.ranged.label:SetText(sw_showlabel and "Ra" or "")
|
||||
|
||||
CreateBackdrop(pfUI.swingtimer.ranged)
|
||||
CreateBackdropShadow(pfUI.swingtimer.ranged)
|
||||
|
||||
-- HS/Cleave queue state
|
||||
local hsQueued = false
|
||||
local cleaveQueued = false
|
||||
local isWarrior = false
|
||||
local cachedHSSlots = {}
|
||||
local cachedCleaveSlots = {}
|
||||
local useSpellQueueEvent = false
|
||||
|
||||
-- Heroic Strike spell IDs (all ranks)
|
||||
local hsSpellIDs = {
|
||||
[78] = true, [284] = true, [285] = true, [1608] = true,
|
||||
[11564] = true, [11565] = true, [11566] = true, [11567] = true,
|
||||
[25286] = true,
|
||||
}
|
||||
-- Cleave spell IDs (all ranks)
|
||||
local cleaveSpellIDs = {
|
||||
[845] = true, [7369] = true, [11608] = true, [11609] = true,
|
||||
[20569] = true,
|
||||
}
|
||||
|
||||
local function RebuildQueueSlotCache()
|
||||
if not isWarrior or not sw_hsqueue or useSpellQueueEvent then return end
|
||||
|
||||
cachedHSSlots = {}
|
||||
cachedCleaveSlots = {}
|
||||
|
||||
for slot = 1, 120 do
|
||||
local tex = GetActionTexture(slot)
|
||||
local name = GetActionText(slot)
|
||||
|
||||
if tex then
|
||||
if string.find(tex, "Ability_Rogue_Ambush") then
|
||||
table.insert(cachedHSSlots, slot)
|
||||
elseif string.find(tex, "Ability_Warrior_Cleave") then
|
||||
table.insert(cachedCleaveSlots, slot)
|
||||
end
|
||||
end
|
||||
|
||||
if name then
|
||||
local lower = string.lower(name)
|
||||
if lower == "heroic strike" or lower == "heroicstrike" or lower == "hs" then
|
||||
table.insert(cachedHSSlots, slot)
|
||||
elseif lower == "cleave" then
|
||||
table.insert(cachedCleaveSlots, slot)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function CheckQueuedAction(slotList)
|
||||
for i = 1, table.getn(slotList) do
|
||||
if IsCurrentAction(slotList[i]) then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function IsHSOrCleaveQueued()
|
||||
if not sw_hsqueue or not isWarrior then return false, false end
|
||||
if useSpellQueueEvent then
|
||||
return hsQueued, cleaveQueued
|
||||
end
|
||||
return CheckQueuedAction(cachedHSSlots), CheckQueuedAction(cachedCleaveSlots)
|
||||
end
|
||||
|
||||
UpdateMovable(pfUI.swingtimer.mainhand)
|
||||
UpdateMovable(pfUI.swingtimer.ranged)
|
||||
|
||||
-- VERSION B TEST: HasOffhandWeapon() removed.
|
||||
-- The original used GetItemInfo() to detect OH weapon type, but GetItemInfo()
|
||||
-- returns nil on first login before the item cache is populated, causing
|
||||
-- offhand.speed to stay 0. Now we just read offhandAttackTime directly,
|
||||
-- same as the old working version.
|
||||
-- Check offhand slot for an actual weapon. GetItemInfo may return nil on first
|
||||
-- login (item cache not yet populated), so we return nil in that case to signal
|
||||
-- "unknown" rather than false, allowing the caller to keep the previous value.
|
||||
-- inventoryType 13 = INVTYPE_WEAPONOFFHAND, 21 = INVTYPE_WEAPON (one-hand, dual wieldable)
|
||||
-- Shields = 14, held-in-hand = 23, everything else = no swing
|
||||
local OH_WEAPON_TYPES = { [13]=true, [21]=true }
|
||||
|
||||
local function HasOffhandWeapon()
|
||||
local l = GetInventoryItemLink("player", 17)
|
||||
if not l then return false end
|
||||
local _, _, id = string.find(l, "item:(%d+)")
|
||||
id = tonumber(id)
|
||||
if not id then return false end
|
||||
local s = GetItemStats and GetItemStats(id)
|
||||
if not s then return false end
|
||||
return OH_WEAPON_TYPES[s.inventoryType] == true
|
||||
end
|
||||
|
||||
local function UpdateWeaponSpeeds()
|
||||
if not GetUnitField then return end
|
||||
|
||||
local mhSpeed = GetUnitField("player", "baseAttackTime")
|
||||
local ohSpeed = GetUnitField("player", "offhandAttackTime")
|
||||
|
||||
if mhSpeed and mhSpeed > 0 then
|
||||
swingState.mainhand.speed = mhSpeed / 1000
|
||||
end
|
||||
|
||||
if HasOffhandWeapon() and ohSpeed and ohSpeed > 0 then
|
||||
swingState.offhand.speed = ohSpeed / 1000
|
||||
else
|
||||
swingState.offhand.speed = 0
|
||||
end
|
||||
|
||||
local raSpeed = GetUnitField("player", "rangedAttackTime")
|
||||
if raSpeed and raSpeed > 0 then
|
||||
swingState.ranged.speed = raSpeed / 1000
|
||||
else
|
||||
swingState.ranged.speed = 0
|
||||
end
|
||||
end
|
||||
|
||||
local function StartSwing(isOffhand)
|
||||
local now = GetTime()
|
||||
|
||||
-- always refresh speeds to catch haste buffs/debuffs
|
||||
UpdateWeaponSpeeds()
|
||||
|
||||
-- dual-wield guard: if MH swing just started (<100ms ago) and this isn't
|
||||
-- flagged as offhand, it's likely an OH event with missing flag
|
||||
if not isOffhand and swingState.offhand.speed > 0 then
|
||||
local mhAge = now - (swingState.mainhand.nextSwing - swingState.mainhand.speed)
|
||||
if swingState.mainhand.swinging and mhAge > 0 and mhAge < 0.1 then
|
||||
isOffhand = true
|
||||
end
|
||||
end
|
||||
|
||||
if isOffhand and swingState.offhand.speed > 0 then
|
||||
swingState.offhand.nextSwing = now + swingState.offhand.speed
|
||||
swingState.offhand.swinging = true
|
||||
if sw_showoh then pfUI.swingtimer.offhand:Show() end
|
||||
else
|
||||
swingState.mainhand.nextSwing = now + swingState.mainhand.speed
|
||||
swingState.mainhand.swinging = true
|
||||
pfUI.swingtimer.mainhand:Show()
|
||||
end
|
||||
|
||||
pfUI.swingtimer:Show()
|
||||
end
|
||||
|
||||
local function StartRangedSwing()
|
||||
if not sw_showranged then return end
|
||||
UpdateWeaponSpeeds()
|
||||
if swingState.ranged.speed <= 0 then return end
|
||||
-- Ranged replaces MH: cancel mainhand swing
|
||||
swingState.mainhand.swinging = false
|
||||
pfUI.swingtimer.mainhand:Hide()
|
||||
swingState.ranged.nextSwing = GetTime() + swingState.ranged.speed
|
||||
swingState.ranged.swinging = true
|
||||
|
||||
if isHunter then
|
||||
-- Hunter: left/right halves anchored to CENTER, shrink outside->in
|
||||
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
|
||||
-- Non-Hunter: left anchored to TOPLEFT, grows left->right like MH/OH
|
||||
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
|
||||
|
||||
local swingThrottle = 0
|
||||
pfUI.swingtimer:SetScript("OnUpdate", function()
|
||||
swingThrottle = swingThrottle + arg1
|
||||
if swingThrottle < 0.016 then return end
|
||||
swingThrottle = 0
|
||||
local now = GetTime()
|
||||
local anyActive = false
|
||||
|
||||
local curR, curG, curB = mhDefaultR, mhDefaultG, mhDefaultB
|
||||
if sw_hsqueue and isWarrior then
|
||||
local hs, cl = IsHSOrCleaveQueued()
|
||||
if cl then
|
||||
curR, curG, curB = 0.2, 0.9, 0.2
|
||||
elseif hs then
|
||||
curR, curG, curB = 0.9, 0.9, 0.2
|
||||
end
|
||||
end
|
||||
|
||||
if swingState.mainhand.swinging then
|
||||
local remaining = swingState.mainhand.nextSwing - now
|
||||
|
||||
if remaining <= 0 then
|
||||
swingState.mainhand.swinging = false
|
||||
pfUI.swingtimer.mainhand:Hide()
|
||||
else
|
||||
local progress = 1 - (remaining / swingState.mainhand.speed)
|
||||
pfUI.swingtimer.mainhand:SetValue(progress)
|
||||
pfUI.swingtimer.mainhand:SetStatusBarColor(curR, curG, curB, mhA)
|
||||
if sw_showtext then
|
||||
pfUI.swingtimer.mainhand.text:SetText(string.format("%.1f", remaining))
|
||||
end
|
||||
anyActive = true
|
||||
end
|
||||
end
|
||||
|
||||
if sw_showoh and swingState.offhand.swinging then
|
||||
local remaining = swingState.offhand.nextSwing - now
|
||||
|
||||
if remaining <= 0 then
|
||||
swingState.offhand.swinging = false
|
||||
pfUI.swingtimer.offhand:Hide()
|
||||
else
|
||||
local progress = 1 - (remaining / swingState.offhand.speed)
|
||||
pfUI.swingtimer.offhand:SetValue(progress)
|
||||
if sw_showtext then
|
||||
pfUI.swingtimer.offhand.text:SetText(string.format("%.1f", remaining))
|
||||
end
|
||||
anyActive = true
|
||||
end
|
||||
elseif not sw_showoh then
|
||||
pfUI.swingtimer.offhand:Hide()
|
||||
end
|
||||
|
||||
if sw_showranged and swingState.ranged.swinging then
|
||||
local remaining = swingState.ranged.nextSwing - now
|
||||
|
||||
if remaining <= 0 then
|
||||
swingState.ranged.swinging = false
|
||||
pfUI.swingtimer.ranged:Hide()
|
||||
else
|
||||
if isHunter then
|
||||
-- Hunter ranged animation: two phases
|
||||
-- Phase 1 (speed-0.5s): bar visible full, shrinks from outside->in toward center (normal color)
|
||||
-- Phase 2 (0.5s): bar grows from center outward (warning color)
|
||||
local DEADZONE = 0.5
|
||||
local halfW = sw_width / 2
|
||||
|
||||
if remaining > DEADZONE then
|
||||
-- Phase 1: left/right halves shrink from outside->in toward center
|
||||
local elapsed = swingState.ranged.speed - remaining
|
||||
local phase1dur = swingState.ranged.speed - DEADZONE
|
||||
local p = elapsed / phase1dur -- 0 = full, 1 = gone
|
||||
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
|
||||
-- Phase 2: warning color grows from center->outside
|
||||
local p = 1 - (remaining / DEADZONE) -- 0 = nothing, 1 = full
|
||||
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
|
||||
else
|
||||
-- Non-Hunter (Warrior Throw, Rogue): simple left->right fill like MH/OH
|
||||
local progress = 1 - (remaining / swingState.ranged.speed)
|
||||
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()
|
||||
end
|
||||
if sw_showtext then
|
||||
if isHunter and remaining <= 0.5 then
|
||||
pfUI.swingtimer.ranged.text:SetText(string.format("%.1f", remaining))
|
||||
elseif isHunter then
|
||||
-- Show time until deadzone starts, not full remaining
|
||||
pfUI.swingtimer.ranged.text:SetText(string.format("%.1f", remaining - 0.5))
|
||||
else
|
||||
pfUI.swingtimer.ranged.text:SetText(string.format("%.1f", remaining))
|
||||
end
|
||||
end
|
||||
anyActive = true
|
||||
end
|
||||
elseif not sw_showranged then
|
||||
pfUI.swingtimer.ranged:Hide()
|
||||
end
|
||||
|
||||
if not anyActive then
|
||||
if not pfUI.swingtimer.mainhand:IsShown()
|
||||
and not pfUI.swingtimer.offhand:IsShown()
|
||||
and not pfUI.swingtimer.ranged:IsShown() then
|
||||
this:Hide()
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
local events = CreateFrame("Frame")
|
||||
events:RegisterEvent("AUTO_ATTACK_SELF")
|
||||
events:RegisterEvent("AUTO_ATTACK_OTHER")
|
||||
events:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||||
events:RegisterEvent("UNIT_INVENTORY_CHANGED")
|
||||
events:RegisterEvent("PLAYER_REGEN_DISABLED")
|
||||
events:RegisterEvent("PLAYER_REGEN_ENABLED")
|
||||
events:RegisterEvent("ACTIONBAR_SLOT_CHANGED")
|
||||
events:RegisterEvent("UNIT_DIED")
|
||||
events:RegisterEvent("SPELL_QUEUE_EVENT")
|
||||
events:RegisterEvent("SPELL_GO_SELF")
|
||||
|
||||
local function ResetSwingTimers()
|
||||
swingState.mainhand.swinging = false
|
||||
swingState.offhand.swinging = false
|
||||
swingState.ranged.swinging = false
|
||||
pfUI.swingtimer.mainhand:Hide()
|
||||
pfUI.swingtimer.offhand:Hide()
|
||||
pfUI.swingtimer.ranged:Hide()
|
||||
pfUI.swingtimer:Hide()
|
||||
end
|
||||
|
||||
local playerGUID = nil
|
||||
|
||||
events:SetScript("OnEvent", function()
|
||||
if event == "AUTO_ATTACK_SELF" then
|
||||
local hitInfo = arg4 or 0
|
||||
-- HITINFO_NOACTION: server did not advance the swing clock, ignore
|
||||
if bit.band(hitInfo, HITINFO_NOACTION) ~= 0 then return end
|
||||
local isOffhand = bit.band(hitInfo, HITINFO_LEFTSWING) ~= 0
|
||||
StartSwing(isOffhand)
|
||||
|
||||
elseif event == "AUTO_ATTACK_OTHER" then
|
||||
if not swingState.mainhand.swinging then return end
|
||||
local targetGuid = arg2
|
||||
if not targetGuid or not playerGUID then return end
|
||||
if targetGuid ~= playerGUID then return end
|
||||
local victimState = arg5 or 0
|
||||
if victimState == 3 then
|
||||
local now = GetTime()
|
||||
local remaining = swingState.mainhand.nextSwing - now
|
||||
local reduction = swingState.mainhand.speed * 0.4
|
||||
local minRemaining = swingState.mainhand.speed * 0.2
|
||||
local newRemaining = remaining - reduction
|
||||
if newRemaining < minRemaining then newRemaining = minRemaining end
|
||||
if newRemaining < remaining then
|
||||
swingState.mainhand.nextSwing = now + newRemaining
|
||||
end
|
||||
end
|
||||
|
||||
elseif event == "SPELL_QUEUE_EVENT" then
|
||||
local eventCode = arg1 or -1
|
||||
local spellId = arg2 or 0
|
||||
if eventCode == ON_SWING_QUEUED then
|
||||
useSpellQueueEvent = true
|
||||
if hsSpellIDs[spellId] then
|
||||
hsQueued = true; cleaveQueued = false
|
||||
elseif cleaveSpellIDs[spellId] then
|
||||
cleaveQueued = true; hsQueued = false
|
||||
end
|
||||
elseif eventCode == ON_SWING_QUEUE_POPPED then
|
||||
hsQueued = false; cleaveQueued = false
|
||||
end
|
||||
|
||||
elseif event == "PLAYER_ENTERING_WORLD" then
|
||||
local _, class = UnitClass("player")
|
||||
isWarrior = (class == "WARRIOR")
|
||||
local _, guid = UnitExists("player")
|
||||
playerGUID = guid
|
||||
UpdateWeaponSpeeds()
|
||||
RebuildQueueSlotCache()
|
||||
|
||||
elseif event == "SPELL_GO_SELF" then
|
||||
local spellId = arg2 or 0
|
||||
if RANGED_SPELLIDS[spellId] then
|
||||
StartRangedSwing()
|
||||
end
|
||||
|
||||
elseif event == "UNIT_INVENTORY_CHANGED" then
|
||||
if arg1 and arg1 ~= "player" then return end
|
||||
UpdateWeaponSpeeds()
|
||||
if swingState.offhand.speed == 0 then
|
||||
swingState.offhand.swinging = false
|
||||
pfUI.swingtimer.offhand:Hide()
|
||||
end
|
||||
if swingState.ranged.speed == 0 then
|
||||
swingState.ranged.swinging = false
|
||||
pfUI.swingtimer.ranged:Hide()
|
||||
end
|
||||
|
||||
elseif event == "ACTIONBAR_SLOT_CHANGED" then
|
||||
RebuildQueueSlotCache()
|
||||
|
||||
elseif event == "PLAYER_REGEN_DISABLED" then
|
||||
UpdateWeaponSpeeds()
|
||||
|
||||
elseif event == "PLAYER_REGEN_ENABLED" then
|
||||
ResetSwingTimers()
|
||||
hsQueued = false
|
||||
cleaveQueued = false
|
||||
|
||||
elseif event == "UNIT_DIED" then
|
||||
-- Only reset if the player themselves died
|
||||
local guid = arg1
|
||||
if not guid then return end
|
||||
if guid == playerGUID then
|
||||
ResetSwingTimers()
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
UpdateWeaponSpeeds()
|
||||
end)
|
||||
@@ -927,6 +927,15 @@ pfUI:RegisterModule("thirdparty-vanilla", "vanilla", function()
|
||||
end)
|
||||
end)
|
||||
|
||||
-- SuperCleveRoidMacros
|
||||
HookAddonOrVariable("SuperCleveRoidMacros", function()
|
||||
if C.thirdparty.supercleveroidmacros.enable == "0" then return end
|
||||
if not pfUI.bars then return end
|
||||
|
||||
-- disable pfUI macro scanning
|
||||
pfUI.bars.skip_macro = true
|
||||
end)
|
||||
|
||||
HookAddonOrVariable("AtlasLoot", function()
|
||||
if C.thirdparty.atlasloot.enable == "0" then return end
|
||||
|
||||
|
||||
+5
-1
@@ -1,4 +1,4 @@
|
||||
pfUI:RegisterModule("tooltip", "vanilla:tbc", function ()
|
||||
pfUI:RegisterModule("tooltip", "vanilla", function ()
|
||||
local rawborder, default_border = GetBorderSize()
|
||||
|
||||
pfUI.tooltip = CreateFrame('Frame', "pfTooltip", GameTooltip)
|
||||
@@ -33,6 +33,10 @@ pfUI:RegisterModule("tooltip", "vanilla:tbc", function ()
|
||||
tooltip.cursor:SetWidth(tonumber(C.tooltip.cursoroffset) * 2)
|
||||
tooltip.cursor:SetHeight(tonumber(C.tooltip.cursoroffset) * 2)
|
||||
tooltip.cursor:SetScript("OnUpdate", function()
|
||||
-- throttle - cursor following doesn't need to be every frame
|
||||
if (this.tick or 0) > GetTime() then return end
|
||||
this.tick = GetTime() + (pfUI.throttle and pfUI.throttle:Get("tooltip_cursor") or 0.1)
|
||||
|
||||
local scale = UIParent:GetScale()
|
||||
local x, y = GetCursorPosition()
|
||||
this:SetPoint("CENTER", UIParent, "BOTTOMLEFT", x/scale, y/scale)
|
||||
|
||||
+2
-1
@@ -27,11 +27,12 @@ pfUI:RegisterModule("totems", "vanilla:tbc", function ()
|
||||
totems.OnEnter = function(self)
|
||||
if not this.id then return end
|
||||
local active, name, start, duration, icon = GetTotemInfo(this.id)
|
||||
if not name or not active then return end -- Prüfen ob name gültig ist
|
||||
local color = slots[this.id]
|
||||
GameTooltip:SetOwner(this, "ANCHOR_LEFT")
|
||||
GameTooltip:SetText(name, color.r+.2, color.g+.2, color.b+.2)
|
||||
GameTooltip:Show()
|
||||
end
|
||||
end
|
||||
|
||||
totems.OnLeave = function(self)
|
||||
GameTooltip:Hide()
|
||||
|
||||
+15
-5
@@ -32,20 +32,30 @@ pfUI:RegisterModule("turtle-wow", "vanilla", function ()
|
||||
end
|
||||
end
|
||||
|
||||
-- refresh rip duration on ferocious bite
|
||||
-- refresh rip and rake duration on ferocious bite (Turtle WoW feature)
|
||||
-- Only refresh if Ferocious Bite actually hit (not missed/dodged/parried/etc.)
|
||||
local match = string.find(arg1, "Ferocious Bite")
|
||||
if match and arg2 then
|
||||
if match and arg2 and not libdebuff:DidSpellFail("Ferocious Bite") then
|
||||
local name = UnitName("target")
|
||||
local level = UnitLevel("target")
|
||||
|
||||
-- Refresh Rip mit existierender Duration
|
||||
if libdebuff.objects[name] and libdebuff.objects[name][level] and libdebuff.objects[name][level]["Rip"] then
|
||||
libdebuff:AddEffect(name, level, "Rip")
|
||||
local existingDuration = libdebuff.objects[name][level]["Rip"].duration
|
||||
libdebuff:AddEffect(name, level, "Rip", existingDuration)
|
||||
end
|
||||
|
||||
-- Refresh Rake mit existierender Duration
|
||||
if libdebuff.objects[name] and libdebuff.objects[name][level] and libdebuff.objects[name][level]["Rake"] then
|
||||
local existingDuration = libdebuff.objects[name][level]["Rake"].duration
|
||||
libdebuff:AddEffect(name, level, "Rake", existingDuration)
|
||||
end
|
||||
end
|
||||
|
||||
-- refresh Immolate duration after cast Conflagrate
|
||||
-- Only refresh if Conflagrate actually hit
|
||||
local conflagrate = string.find(string.sub(arg1,6,17), "Conflagrate")
|
||||
--arg2 is spell dmg when it hits, nil when it misses
|
||||
if conflagrate and arg2 then
|
||||
if conflagrate and arg2 and not libdebuff:DidSpellFail("Conflagrate") then
|
||||
local name = UnitName("target")
|
||||
local level = UnitLevel("target")
|
||||
if libdebuff.objects[name] and libdebuff.objects[name][level] and libdebuff.objects[name][level]["Immolate"] then
|
||||
|
||||
@@ -0,0 +1,259 @@
|
||||
-- UnitXP_SP3 integration module
|
||||
-- Provides Line of Sight indicator, OS notifications, and enhanced targeting
|
||||
-- Requires UnitXP_SP3 DLL: https://github.com/allfoxwy/UnitXP_SP3
|
||||
|
||||
pfUI:RegisterModule("unitxp", "vanilla", function ()
|
||||
-- Check if UnitXP is available
|
||||
local hasUnitXP = pcall(UnitXP, "nop", "nop")
|
||||
if not hasUnitXP then return end
|
||||
|
||||
local rawborder, border = GetBorderSize()
|
||||
|
||||
-- Helper to create indicators after target frame exists
|
||||
local function CreateTargetIndicators()
|
||||
if not pfUI.uf or not pfUI.uf.target then return false end
|
||||
|
||||
-- Behind Indicator for all units (TOP)
|
||||
if C.unitframes.behind_indicator == "1" and not pfUI.uf.target.behindIndicator then
|
||||
local behindFrame = CreateFrame("Frame", "pfBehindIndicator", pfUI.uf.target)
|
||||
behindFrame:SetAllPoints(pfUI.uf.target)
|
||||
behindFrame:SetFrameLevel(pfUI.uf.target:GetFrameLevel() + 10)
|
||||
|
||||
behindFrame.text = behindFrame:CreateFontString(nil, "OVERLAY")
|
||||
behindFrame.text:SetFont(pfUI.font_default, 13, "OUTLINE")
|
||||
behindFrame.text:SetPoint("RIGHT", behindFrame, "RIGHT", -1, 7)
|
||||
behindFrame.text:SetTextColor(0.3, 1, 0.3, 1)
|
||||
behindFrame.text:SetText("BEHIND")
|
||||
behindFrame.text:Hide()
|
||||
|
||||
local lastCheck = 0
|
||||
behindFrame:SetScript("OnUpdate", function()
|
||||
if GetTime() - lastCheck < 0.1 then return end
|
||||
lastCheck = GetTime()
|
||||
|
||||
if not UnitExists("target") then
|
||||
this.text:Hide()
|
||||
return
|
||||
end
|
||||
|
||||
local success, behind = pcall(UnitXP, "behind", "player", "target")
|
||||
if success and behind then
|
||||
this.text:Show()
|
||||
else
|
||||
this.text:Hide()
|
||||
end
|
||||
end)
|
||||
|
||||
pfUI.uf.target.behindIndicator = behindFrame
|
||||
end
|
||||
|
||||
-- Line of Sight Indicator on Target Frame (BELOW BEHIND)
|
||||
if C.unitframes.los_indicator == "1" and not pfUI.uf.target.losIndicator then
|
||||
local losFrame = CreateFrame("Frame", "pfLoSIndicator", pfUI.uf.target)
|
||||
losFrame:SetAllPoints(pfUI.uf.target)
|
||||
losFrame:SetFrameLevel(pfUI.uf.target:GetFrameLevel() + 10)
|
||||
|
||||
losFrame.text = losFrame:CreateFontString(nil, "OVERLAY")
|
||||
losFrame.text:SetFont(pfUI.font_default, 13, "OUTLINE")
|
||||
losFrame.text:SetPoint("RIGHT", losFrame, "RIGHT", -1, -7)
|
||||
losFrame.text:SetTextColor(1, 0.3, 0.3, 1)
|
||||
losFrame.text:SetText("NO LOS")
|
||||
losFrame.text:Hide()
|
||||
|
||||
local lastCheck = 0
|
||||
losFrame:SetScript("OnUpdate", function()
|
||||
if GetTime() - lastCheck < 0.2 then return end
|
||||
lastCheck = GetTime()
|
||||
|
||||
if not UnitExists("target") then
|
||||
this.text:Hide()
|
||||
return
|
||||
end
|
||||
|
||||
local success, inSight = pcall(UnitXP, "inSight", "player", "target")
|
||||
if success and inSight == false then
|
||||
this.text:Show()
|
||||
else
|
||||
this.text:Hide()
|
||||
end
|
||||
end)
|
||||
|
||||
pfUI.uf.target.losIndicator = losFrame
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
-- Try to create indicators now
|
||||
CreateTargetIndicators()
|
||||
|
||||
-- Also try on PLAYER_ENTERING_WORLD in case target frame wasn't ready
|
||||
local initFrame = CreateFrame("Frame")
|
||||
initFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||||
initFrame:RegisterEvent("PLAYER_LOGOUT")
|
||||
initFrame:SetScript("OnEvent", function()
|
||||
-- Handle shutdown to prevent crash 132
|
||||
if event == "PLAYER_LOGOUT" then
|
||||
this:UnregisterAllEvents()
|
||||
this:SetScript("OnEvent", nil)
|
||||
-- Stop indicator OnUpdate scripts
|
||||
if pfUI.uf and pfUI.uf.target then
|
||||
if pfUI.uf.target.behindIndicator then
|
||||
pfUI.uf.target.behindIndicator:SetScript("OnUpdate", nil)
|
||||
end
|
||||
if pfUI.uf.target.losIndicator then
|
||||
pfUI.uf.target.losIndicator:SetScript("OnUpdate", nil)
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
CreateTargetIndicators()
|
||||
this:UnregisterAllEvents()
|
||||
end)
|
||||
|
||||
-- OS Notification Support
|
||||
if C.unitframes.unitxp_notify == "1" then
|
||||
local notifyFrame = CreateFrame("Frame")
|
||||
notifyFrame:RegisterEvent("CHAT_MSG_WHISPER")
|
||||
notifyFrame:RegisterEvent("CHAT_MSG_BN_WHISPER")
|
||||
notifyFrame:RegisterEvent("READY_CHECK")
|
||||
notifyFrame:RegisterEvent("RAID_INSTANCE_WELCOME")
|
||||
notifyFrame:RegisterEvent("PLAYER_LOGOUT")
|
||||
|
||||
notifyFrame:SetScript("OnEvent", function()
|
||||
-- Handle shutdown to prevent crash 132
|
||||
if event == "PLAYER_LOGOUT" then
|
||||
this:UnregisterAllEvents()
|
||||
this:SetScript("OnEvent", nil)
|
||||
return
|
||||
end
|
||||
|
||||
pcall(UnitXP, "notify", "taskbarIcon")
|
||||
pcall(UnitXP, "notify", "systemSound")
|
||||
end)
|
||||
|
||||
-- Also notify on BG queue pop
|
||||
local origBattlefieldPortShow = BattlefieldFrame_Show
|
||||
if origBattlefieldPortShow then
|
||||
BattlefieldFrame_Show = function()
|
||||
pcall(UnitXP, "notify", "taskbarIcon")
|
||||
pcall(UnitXP, "notify", "systemSound")
|
||||
return origBattlefieldPortShow()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Enhanced Distance API
|
||||
pfUI.api.GetPreciseDistance = function(unit1, unit2)
|
||||
if not unit2 then
|
||||
unit2 = unit1
|
||||
unit1 = "player"
|
||||
end
|
||||
local success, distance = pcall(UnitXP, "distanceBetween", unit1, unit2)
|
||||
if success then return distance end
|
||||
return nil
|
||||
end
|
||||
|
||||
pfUI.api.IsInMeleeRange = function(unit)
|
||||
local success, distance = pcall(UnitXP, "distanceBetween", "player", unit, "meleeAutoAttack")
|
||||
if success and distance then
|
||||
return distance <= 5
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
pfUI.api.GetAoEDistance = function(unit1, unit2)
|
||||
if not unit2 then
|
||||
unit2 = unit1
|
||||
unit1 = "player"
|
||||
end
|
||||
local success, distance = pcall(UnitXP, "distanceBetween", unit1, unit2, "AoE")
|
||||
if success then return distance end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Smart Targeting Helpers
|
||||
pfUI.api.TargetNearestEnemy = function()
|
||||
local success, found = pcall(UnitXP, "target", "nearestEnemy")
|
||||
return success and found
|
||||
end
|
||||
|
||||
pfUI.api.TargetHighestHP = function()
|
||||
local success, found = pcall(UnitXP, "target", "mostHP")
|
||||
return success and found
|
||||
end
|
||||
|
||||
pfUI.api.TargetNextEnemy = function()
|
||||
local success, found = pcall(UnitXP, "target", "nextEnemyInCycle")
|
||||
return success and found
|
||||
end
|
||||
|
||||
pfUI.api.TargetPreviousEnemy = function()
|
||||
local success, found = pcall(UnitXP, "target", "previousEnemyInCycle")
|
||||
return success and found
|
||||
end
|
||||
|
||||
pfUI.api.TargetNextMarked = function(order)
|
||||
local success, found = pcall(UnitXP, "target", "nextMarkedEnemyInCycle", order)
|
||||
return success and found
|
||||
end
|
||||
|
||||
pfUI.api.UnitInLineOfSight = function(unit1, unit2)
|
||||
if not unit2 then
|
||||
unit2 = unit1
|
||||
unit1 = "player"
|
||||
end
|
||||
local success, inSight = pcall(UnitXP, "inSight", unit1, unit2)
|
||||
if success then return inSight end
|
||||
return nil
|
||||
end
|
||||
|
||||
pfUI.api.UnitIsBehind = function(unit1, unit2)
|
||||
if not unit2 then
|
||||
unit2 = unit1
|
||||
unit1 = "player"
|
||||
end
|
||||
local success, behind = pcall(UnitXP, "behind", unit1, unit2)
|
||||
if success then return behind end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Debug command to test UnitXP indicators
|
||||
SLASH_PFUNITXP1 = "/pfunitxp"
|
||||
SlashCmdList["PFUNITXP"] = function()
|
||||
local chat = DEFAULT_CHAT_FRAME
|
||||
chat:AddMessage("|cff33ffccpfUI|r: UnitXP Indicator Debug")
|
||||
|
||||
-- Check if target exists
|
||||
if not UnitExists("target") then
|
||||
chat:AddMessage(" |cffff0000No target selected|r")
|
||||
return
|
||||
end
|
||||
|
||||
-- Test behind
|
||||
local successB, behind = pcall(UnitXP, "behind", "player", "target")
|
||||
chat:AddMessage(" Behind check: success=" .. tostring(successB) .. " value=" .. tostring(behind) .. " type=" .. type(behind))
|
||||
|
||||
-- Test LOS
|
||||
local successL, inSight = pcall(UnitXP, "inSight", "player", "target")
|
||||
chat:AddMessage(" LOS check: success=" .. tostring(successL) .. " value=" .. tostring(inSight) .. " type=" .. type(inSight))
|
||||
|
||||
-- Check if indicator frames exist
|
||||
if pfUI.uf and pfUI.uf.target then
|
||||
chat:AddMessage(" Target frame: |cff00ff00exists|r")
|
||||
if pfUI.uf.target.behindIndicator then
|
||||
chat:AddMessage(" Behind indicator: |cff00ff00created|r, visible=" .. tostring(pfUI.uf.target.behindIndicator:IsVisible()))
|
||||
else
|
||||
chat:AddMessage(" Behind indicator: |cffff0000NOT created|r (check settings)")
|
||||
end
|
||||
if pfUI.uf.target.losIndicator then
|
||||
chat:AddMessage(" LOS indicator: |cff00ff00created|r")
|
||||
else
|
||||
chat:AddMessage(" LOS indicator: |cffff0000NOT created|r (check settings)")
|
||||
end
|
||||
else
|
||||
chat:AddMessage(" Target frame: |cffff0000NOT found|r")
|
||||
end
|
||||
end
|
||||
end)
|
||||
+3
-3
@@ -1,10 +1,10 @@
|
||||
## Interface: 20400
|
||||
## Title: |cff33ffccpf|cffffffffUI
|
||||
## Author: Shagu
|
||||
## Author: Shagu - modfied by me0wg4ming
|
||||
## Notes: A complete user interface replacement.
|
||||
## Notes-ruRU: Полная замена пользовательского интерфейса.
|
||||
## Version: 5.5.4
|
||||
## SavedVariables: pfUI_profiles, pfUI_addon_profiles, pfUI_cache
|
||||
## Version: 7.6.2 (experiment version)
|
||||
## SavedVariables: pfUI_profiles, pfUI_addon_profiles, pfUI_cache, pfUI_throttle
|
||||
## SavedVariablesPerCharacter: pfUI_config, pfUI_init, pfUI_playerDB
|
||||
|
||||
pfUI.lua
|
||||
|
||||
@@ -30,6 +30,7 @@ pfUI_init = {}
|
||||
pfUI_profiles = {}
|
||||
pfUI_addon_profiles = {}
|
||||
pfUI_cache = {}
|
||||
pfUI_throttle = {}
|
||||
|
||||
-- localization
|
||||
pfUI_locale = {}
|
||||
@@ -47,6 +48,11 @@ pfUI.version = {}
|
||||
pfUI.hooks = {}
|
||||
pfUI.env = {}
|
||||
|
||||
-- check if macro addons are loaded (disables macrotweak/macroscan)
|
||||
function pfUI:MacroAddonsLoaded()
|
||||
return IsAddOnLoaded("Supermacro") or IsAddOnLoaded("SuperCleveRoidMacros") or IsAddOnLoaded("UltimaMacros")
|
||||
end
|
||||
|
||||
-- detect current addon path
|
||||
local tocs = { "", "-master", "-tbc", "-wotlk" }
|
||||
for _, name in pairs(tocs) do
|
||||
@@ -236,6 +242,7 @@ function pfUI:GetEnvironment()
|
||||
|
||||
pfUI.env._G = getfenv(0)
|
||||
pfUI.env.C = pfUI_config
|
||||
pfUI.env.pfUI_throttle = _G.pfUI_throttle
|
||||
pfUI.env.L = (pfUI_locale[GetLocale()] or pfUI_locale["enUS"])
|
||||
|
||||
return pfUI.env
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
## Interface: 11200
|
||||
## Title: |cff33ffccpf|cffffffffUI
|
||||
## Author: Shagu
|
||||
## Author: Shagu - modfied by me0wg4ming
|
||||
## Notes: A complete user interface replacement.
|
||||
## Notes-ruRU: Полная замена пользовательского интерфейса.
|
||||
## Version: 5.5.4
|
||||
## SavedVariables: pfUI_profiles, pfUI_addon_profiles, pfUI_cache
|
||||
## Version: 7.6.2 (experiment version)
|
||||
## SavedVariables: pfUI_profiles, pfUI_addon_profiles, pfUI_cache, pfUI_throttle
|
||||
## SavedVariablesPerCharacter: pfUI_config, pfUI_init, pfUI_playerDB
|
||||
|
||||
pfUI.lua
|
||||
|
||||
Reference in New Issue
Block a user