From f43b8f19f80fe86d081f0e184a4a33d28b0416bc Mon Sep 17 00:00:00 2001 From: Meow <30401521+me0wg4ming@users.noreply.github.com> Date: Fri, 6 Feb 2026 16:32:13 +0100 Subject: [PATCH] update for libdebuff Read the readme! Version push --- README.md | 76 ++++++++++- libs/libdebuff.lua | 310 +++++++++++++++++++++++++++++--------------- libs/libpredict.lua | 2 +- modules/castbar.lua | 34 ++++- modules/player.lua | 7 +- modules/skin.lua | 2 +- pfUI-tbc.toc | 2 +- pfUI.toc | 2 +- 8 files changed, 314 insertions(+), 121 deletions(-) diff --git a/README.md b/README.md index b6fe4770..f7fa2782 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # pfUI - Turtle WoW Edition -[![Version](https://img.shields.io/badge/version-7.6.0-blue.svg)](https://github.com/me0wg4ming/pfUI) +[![Version](https://img.shields.io/badge/version-7.6.2-blue.svg)](https://github.com/me0wg4ming/pfUI) [![Turtle WoW](https://img.shields.io/badge/Turtle%20WoW-1.18.0-brightgreen.svg)](https://turtlecraft.gg/) [![SuperWoW](https://img.shields.io/badge/SuperWoW-Required-purple.svg)](https://github.com/balakethelock/SuperWoW) [![Nampower](https://img.shields.io/badge/Nampower-Required-purple.svg)](https://gitea.com/avitasia/nampower) @@ -14,6 +14,76 @@ This version includes significant performance improvements, DLL-enhanced feature --- +## 🎯 What's New in Version 7.6.2 (February 6, 2026) + +### 🚀 Pure GetUnitField Debuff System (libdebuff.lua) + +**Major performance rewrite: UnitDebuff() now runs entirely through Nampower's GetUnitField — zero Blizzard API calls, zero tooltip scans.** + +Previously, every debuff icon required three expensive calls per update: +1. `UnitDebuff(unit, slot)` — Blizzard C-side API call +2. `scanner:SetUnitDebuff(unit, slot)` — Tooltip object creation + GameTooltip parse +3. `scanner:Line(1)` — String extraction from tooltip + +All three are now replaced by pure Lua table lookups into cached GetUnitField data: + +| Data | Old (Blizzard API) | New (GetUnitField) | +|------|-------------------|-------------------| +| Spell Name | Tooltip scan | `SpellInfo(spellId)` | +| Texture | `UnitDebuff()` ret.1 | `GetSpellIconTexture(GetSpellRecField(spellId, "spellIconID"))` | +| Stacks | `UnitDebuff()` ret.2 | `GetUnitField(guid, "auraApplications")[slot]` | +| DebuffType | `UnitDebuff()` ret.3 | `GetSpellRecField(spellId, "dispel")` → dispelTypeMap | +| Duration/Timeleft | ownDebuffs tracking | unchanged | +| Caster | slotOwnership tracking | unchanged | + +**Performance impact:** With 5 debuffs on target = 15 expensive calls eliminated per update cycle. With 10 visible nameplates × 3 debuffs = 90 calls eliminated per refresh. Estimated **3-5x faster** per UnitDebuff call. + +**DebuffType now works in Nampower path:** Previously dtype was only available from Blizzard's UnitDebuff(). Now resolved from SpellRec DBC via `GetSpellRecField(spellId, "dispel")`, meaning debuff frame border colors (Magic=blue, Curse=purple, Poison=green, Disease=brown) now work correctly for all units including nameplates. + +### 🎨 Item-Cast Icon & Name Support (libdebuff.lua + castbar.lua) + +**Castbar now shows the correct item icon and item name for item-triggered casts!** + +Previously, using items with cast times (Gnomish Death Ray, Net-o-Matic, Noggenfogger Elixir etc.) showed the generic spell icon and spell name on the castbar. Now: + +- ✅ `SPELL_START_SELF/OTHER` `arg1` (itemId) is now parsed and used +- ✅ `SPELL_GO_SELF/OTHER` `arg1` (itemId) is now parsed +- ✅ Item icon resolved via `GetItemStatsField(itemId, "displayInfoID")` → `GetItemIconTexture()` +- ✅ Item name resolved via `GetItemStatsField(itemId, "displayName")` +- ✅ `pfUI.libdebuff_item_icons` — Persistent item icon/name cache that survives SPELL_GO clearing cast data +- ✅ `castbar.lua` — Reads item icon + name from persistent cache with fallback to spell data + +**Note:** Item icon/name detection only works for your own casts (WoW 1.12.1 protocol limitation — server sends `itemId=0` to other clients). + +### 🔧 Icon Path Fix (libdebuff.lua) + +**Fixed missing icons from Nampower texture functions.** + +`GetSpellIconTexture()` and `GetItemIconTexture()` return short texture names (e.g. `INV_Gizmo_08`) without the `Interface\Icons\` prefix required by `SetTexture()`. Both `GetSpellIcon()` and the item icon lookup now auto-prefix the full path when needed. + +### ⚡ Memory & GC Optimizations (libdebuff.lua) + +- ✅ **Carnage frame recycling** — Persistent `carnageCheckFrame` reused instead of `CreateFrame()` per Ferocious Bite (eliminates frame leak in combat) +- ✅ **Recycled cleanup buffers** — `_cleanupBuf1`/`_cleanupBuf2` reused instead of `table.insert` + new table per `CleanupExpiredTimers` call +- ✅ **SelfOverwrite buffer recycling** — Reused buffer instead of new `oldCasters` table per overwrite +- ✅ **Pre-defined sort function** — `_ownDebuffSortFunc` defined once instead of anonymous closure per `UnitOwnDebuff` call +- ✅ **spellId stored in ownDebuffs** — Enables direct DBC lookups for dtype without slotMap iteration + +### 📊 Code Statistics + +**libdebuff.lua:** +- Blizzard API calls in Nampower path: 3 per debuff → 0 +- New GetUnitField calls: `aura` + `auraApplications` (cached 50ms) +- New DBC lookups: `GetSpellRecField(spellId, "dispel")` for dtype +- New exports: `pfUI.libdebuff_item_icons` + +**castbar.lua:** +- Item icon override via `pfUI.libdebuff_item_icons` (persistent cache) +- Item name override via `GetItemStatsField(itemId, "displayName")` +- Works for player + target + focus castbars + +--- + ## 🎯 What's New in Version 7.6.1 (February 6, 2026) - Added a new menu in /pfui named "Throttling" - Players who were unsatisfied with the throttling update rate can change it now for nameplates, Toolip Cursor and Chat Tab. @@ -1423,7 +1493,7 @@ Same as original pfUI - free to use and modify. --- -**Version:** 7.6.0 -**Release Date:** February 3, 2026 +**Version:** 7.6.2 +**Release Date:** February 6, 2026 **Compatibility:** Turtle WoW 1.18.0 **Status:** Stable \ No newline at end of file diff --git a/libs/libdebuff.lua b/libs/libdebuff.lua index 8ec55587..e0d52250 100644 --- a/libs/libdebuff.lua +++ b/libs/libdebuff.lua @@ -173,6 +173,7 @@ local iconCache = pfUI.libdebuff_icon_cache -- Cast Tracking: [casterGuid] = {spellID, spellName, icon, startTime, duration, endTime} -- Shared with nameplates for cast-bar display pfUI.libdebuff_casts = pfUI.libdebuff_casts or {} +pfUI.libdebuff_item_icons = pfUI.libdebuff_item_icons or {} -- [casterGuid] = icon (persists across SPELL_GO) -- Cleveroids API: [targetGUID][spellID] = {start, duration, caster, stacks} pfUI.libdebuff_objects_guid = pfUI.libdebuff_objects_guid or {} @@ -327,6 +328,10 @@ function libdebuff:GetSpellIcon(spellId) local spellIconId = GetSpellRecField(spellId, "spellIconID") if spellIconId and type(spellIconId) == "number" and spellIconId > 0 then texture = GetSpellIconTexture(spellIconId) + -- GetSpellIconTexture may return short name, needs full path for SetTexture + if texture and not string.find(texture, "\\") then + texture = "Interface\\Icons\\" .. texture + end end end @@ -365,8 +370,16 @@ end local slotMapCache = {} local SLOT_MAP_CACHE_DURATION = 0.05 -- 50ms cache (1-2 frames) +-- Dispel type mapping: SpellRec.dispel index -> Blizzard DebuffTypeColor key +local dispelTypeMap = { + [1] = "Magic", + [2] = "Curse", + [3] = "Disease", + [4] = "Poison", +} + -- Get current debuff state directly from WoW via GetUnitField --- Returns: { [displaySlot] = {auraSlot, spellId, spellName} } +-- Returns: { [displaySlot] = {auraSlot, spellId, spellName, stacks, texture, dtype} } local function GetDebuffSlotMap(guid) if not guid or not GetUnitField or not SpellInfo then return nil @@ -382,6 +395,9 @@ local function GetDebuffSlotMap(guid) local auras = GetUnitField(guid, "aura") if not auras then return nil end + -- Fetch stacks array (reusable reference - extract values immediately) + local auraApps = GetUnitField(guid, "auraApplications") + if debugStats.enabled then debugStats.getunitfield_calls = debugStats.getunitfield_calls + 1 end @@ -395,11 +411,28 @@ local function GetDebuffSlotMap(guid) if spellId and spellId > 0 then displaySlot = displaySlot + 1 local spellName = SpellInfo(spellId) + local texture = libdebuff:GetSpellIcon(spellId) + + -- Get stacks from auraApplications (extract immediately - reusable table) + local stacks = auraApps and auraApps[auraSlot] or 0 + if stacks == 0 then stacks = 1 end -- 0 means 1 stack (no stacking) + + -- Get debuff type from SpellRec DBC + local dtype = nil + if GetSpellRecField then + local dispelId = GetSpellRecField(spellId, "dispel") + if dispelId and dispelId > 0 then + dtype = dispelTypeMap[dispelId] + end + end map[displaySlot] = { auraSlot = auraSlot, spellId = spellId, - spellName = spellName or "Unknown" + spellName = spellName or "Unknown", + stacks = stacks, + texture = texture, + dtype = dtype } end end @@ -449,6 +482,10 @@ end local lastRangeCheck = 0 +-- Recycled buffers for cleanup (avoids table creation per call) +local _cleanupBuf1 = {} +local _cleanupBuf2 = {} + local function CleanupUnit(guid) if not guid then return false end @@ -491,30 +528,34 @@ local function CleanupExpiredTimers(guid) -- Cleanup ownDebuffs if ownDebuffs[guid] then - local toDelete = {} + local n = 0 for spellName, data in pairs(ownDebuffs[guid]) do local timeleft = (data.startTime + data.duration) - now if timeleft < -2 then -- Grace period - table.insert(toDelete, spellName) + n = n + 1 + _cleanupBuf1[n] = spellName end end - for _, spellName in ipairs(toDelete) do - ownDebuffs[guid][spellName] = nil + for i = 1, n do + ownDebuffs[guid][_cleanupBuf1[i]] = nil + _cleanupBuf1[i] = nil end end -- Cleanup allAuraCasts if allAuraCasts[guid] then for spellName, casterTable in pairs(allAuraCasts[guid]) do - local castersToDelete = {} + local n2 = 0 for casterGuid, data in pairs(casterTable) do local timeleft = (data.startTime + data.duration) - now if timeleft < -2 then - table.insert(castersToDelete, casterGuid) + n2 = n2 + 1 + _cleanupBuf2[n2] = casterGuid end end - for _, casterGuid in ipairs(castersToDelete) do - allAuraCasts[guid][spellName][casterGuid] = nil + for i = 1, n2 do + allAuraCasts[guid][spellName][_cleanupBuf2[i]] = nil + _cleanupBuf2[i] = nil end -- Remove empty spell tables local hasCasters = false @@ -735,46 +776,40 @@ local cache = {} function libdebuff:UnitDebuff(unit, displaySlot) local unitname = UnitName(unit) local unitlevel = UnitLevel(unit) - local texture, stacks, dtype = UnitDebuff(unit, displaySlot) -- Blizzard API for texture/stacks local duration, timeleft = nil, -1 local rank = nil local caster = nil local effect = nil + local texture = nil + local stacks = 0 + local dtype = nil - -- Get effect name from tooltip - if texture then - scanner:SetUnitDebuff(unit, displaySlot) - effect = scanner:Line(1) or "" - end - - -- Nampower: Use GetUnitField for accurate slot mapping - if hasNampower and UnitExists and effect then + -- Nampower: Use GetUnitField for ALL debuff data (no Blizzard UnitDebuff needed) + if hasNampower and UnitExists then local _, guid = UnitExists(unit) if not guid then - -- Fallback to legacy - return effect, rank, texture, stacks, dtype, duration, timeleft, caster + -- Safety fallback: no GUID available (should not happen with Nampower) + local bTexture, bStacks, bDtype = UnitDebuff(unit, displaySlot) + if bTexture then + scanner:SetUnitDebuff(unit, displaySlot) + effect = scanner:Line(1) or "" + end + return effect, rank, bTexture, bStacks, bDtype, duration, timeleft, caster end - -- Get current slot map from GetUnitField + -- Get current slot map from GetUnitField (cached 50ms) local slotMap = GetDebuffSlotMap(guid) if not slotMap or not slotMap[displaySlot] then - -- Slot doesn't exist in GetUnitField - might be Blizzard displaying stale data return nil end local slotData = slotMap[displaySlot] - local gufSpellName = slotData.spellName + effect = slotData.spellName + texture = slotData.texture + stacks = slotData.stacks + dtype = slotData.dtype local auraSlot = slotData.auraSlot - -- Verify spell names match (sanity check) - if gufSpellName ~= effect then - -- Mismatch! GetUnitField and UnitDebuff disagree. - -- This can happen during the brief moment when debuffs change. - -- Trust GetUnitField (it's more accurate). - effect = gufSpellName - texture = libdebuff:GetSpellIcon(slotData.spellId) - end - -- Get caster info for this slot local slotCasterGuid, isOurs = GetSlotCaster(guid, auraSlot, effect) @@ -833,6 +868,16 @@ function libdebuff:UnitDebuff(unit, displaySlot) -- FALLBACK: Legacy (non-Nampower) system -- ============================================================================ + local bTexture, bStacks, bDtype = UnitDebuff(unit, displaySlot) + texture = bTexture + stacks = bStacks + dtype = bDtype + + if texture then + scanner:SetUnitDebuff(unit, displaySlot) + effect = scanner:Line(1) or "" + end + if effect and libdebuff.objects[unitname] then for level, effects in pairs(libdebuff.objects[unitname]) do if effects[effect] and effects[effect].duration then @@ -854,6 +899,14 @@ end -- API: UnitOwnDebuff (only OUR debuffs) -- ============================================================================ +-- Pre-defined sort function for UnitOwnDebuff (avoids closure creation per call) +local _ownDebuffSortFunc = function(a, b) + if a.data.startTime == b.data.startTime then + return a.spellName < b.spellName + end + return a.data.startTime < b.data.startTime +end + function libdebuff:UnitOwnDebuff(unit, id) if hasNampower and UnitExists then local _, guid = UnitExists(unit) @@ -865,29 +918,35 @@ function libdebuff:UnitOwnDebuff(unit, id) for spellName, data in pairs(ownDebuffs[guid]) do local timeleft = (data.startTime + data.duration) - now if timeleft > -1 then -- Grace period - table.insert(sortedDebuffs, { + local count = table.getn(sortedDebuffs) + 1 + sortedDebuffs[count] = { spellName = spellName, data = data, timeleft = timeleft - }) + } end end -- Sort by startTime (oldest first = lowest display slot) -- If startTime is equal (e.g. after Carnage refresh), use spellName for stable sorting - table.sort(sortedDebuffs, function(a, b) - if a.data.startTime == b.data.startTime then - return a.spellName < b.spellName - end - return a.data.startTime < b.data.startTime - end) + table.sort(sortedDebuffs, _ownDebuffSortFunc) -- Return debuff at position 'id' if sortedDebuffs[id] then local entry = sortedDebuffs[id] local texture = entry.data.texture or "Interface\\Icons\\INV_Misc_QuestionMark" local displayTimeleft = entry.timeleft > 0 and entry.timeleft or 0 - return entry.spellName, entry.data.rank, texture, 1, nil, entry.data.duration, displayTimeleft, "player" + + -- Get dtype from SpellRec DBC via stored spellId + local entryDtype = nil + if entry.data.spellId and GetSpellRecField then + local dispelId = GetSpellRecField(entry.data.spellId, "dispel") + if dispelId and dispelId > 0 then + entryDtype = dispelTypeMap[dispelId] + end + end + + return entry.spellName, entry.data.rank, texture, 1, entryDtype, entry.data.duration, displayTimeleft, "player" end end return nil @@ -988,6 +1047,69 @@ if hasNampower then carnageRank = rank or 0 end + -- Persistent Carnage check frame (reused instead of CreateFrame per Bite) + local carnageState = nil -- {targetGuid, checkTime} + local carnageCheckFrame = CreateFrame("Frame") + carnageCheckFrame:Hide() + carnageCheckFrame:SetScript("OnUpdate", function() + if not carnageState then + this:Hide() + return + end + if GetTime() < carnageState.checkTime then return end + + -- Check if we gained a combo point (indicates Carnage proc) + local cp = GetComboPoints() or 0 + + if cp > 0 then + -- Carnage triggered! Refresh Rip & Rake + local guid = carnageState.targetGuid + local refreshTime = GetTime() + local myGuid = GetPlayerGUID() + + -- Refresh in ownDebuffs + if ownDebuffs[guid] then + if ownDebuffs[guid]["Rip"] then + ownDebuffs[guid]["Rip"].startTime = refreshTime + if debugStats.enabled then + DEFAULT_CHAT_FRAME:AddMessage("|cff00ffff[CARNAGE]|r Rip refreshed (CP detected)") + end + end + if ownDebuffs[guid]["Rake"] then + ownDebuffs[guid]["Rake"].startTime = refreshTime + if debugStats.enabled then + DEFAULT_CHAT_FRAME:AddMessage("|cff00ffff[CARNAGE]|r Rake refreshed (CP detected)") + end + end + end + + -- Refresh in allAuraCasts + if allAuraCasts[guid] then + if allAuraCasts[guid]["Rip"] and allAuraCasts[guid]["Rip"][myGuid] then + allAuraCasts[guid]["Rip"][myGuid].startTime = refreshTime + end + if allAuraCasts[guid]["Rake"] and allAuraCasts[guid]["Rake"][myGuid] then + allAuraCasts[guid]["Rake"][myGuid].startTime = refreshTime + end + end + + -- Trigger UI updates + if pfTarget and UnitExists("target") then + local _, currentTargetGuid = UnitExists("target") + if currentTargetGuid == guid then + pfTarget.update_aura = true + end + end + + if pfUI.nameplates and pfUI.nameplates.OnAuraUpdate then + pfUI.nameplates:OnAuraUpdate(guid) + end + end + + carnageState = nil + this:Hide() + end) + local frame = CreateFrame("Frame") frame:RegisterEvent("PLAYER_ENTERING_WORLD") frame:RegisterEvent("PLAYER_TALENT_UPDATE") @@ -1024,6 +1146,7 @@ if hasNampower then end elseif event == "SPELL_START_SELF" or event == "SPELL_START_OTHER" then + local itemId = arg1 local spellId = arg2 local casterGuid = arg3 local castTime = arg6 @@ -1033,8 +1156,31 @@ if hasNampower then local spellName = SpellInfo and SpellInfo(spellId) or nil local icon = libdebuff:GetSpellIcon(spellId) + -- Use item icon for item-triggered casts + if itemId and itemId > 0 and GetItemStatsField and GetItemIconTexture then + local displayInfoId = GetItemStatsField(itemId, "displayInfoID") + if displayInfoId then + local itemIcon = GetItemIconTexture(displayInfoId) + if itemIcon then + -- GetItemIconTexture returns short name (e.g. "INV_Gizmo_08"), needs full path + if not string.find(itemIcon, "\\") then + itemIcon = "Interface\\Icons\\" .. itemIcon + end + icon = itemIcon + end + end + -- Store in persistent item icon cache (survives SPELL_GO clearing libdebuff_casts) + pfUI.libdebuff_item_icons[casterGuid] = { + icon = icon, + name = GetItemStatsField and GetItemStatsField(itemId, "displayName") or nil + } + else + pfUI.libdebuff_item_icons[casterGuid] = nil + end + pfUI.libdebuff_casts[casterGuid] = { spellID = spellId, + itemID = itemId and itemId > 0 and itemId or nil, spellName = spellName, icon = icon, startTime = GetTime(), @@ -1044,6 +1190,7 @@ if hasNampower then } elseif event == "SPELL_GO_SELF" or event == "SPELL_GO_OTHER" then + local itemId = arg1 local spellId = arg2 local casterGuid = arg3 local targetGuid = arg4 @@ -1086,69 +1233,16 @@ if hasNampower then end -- CARNAGE TALENT: Ferocious Bite refreshes Rip & Rake - -- NEW METHOD: Check for combo point gain after Bite (indicates Carnage proc) + -- Check for combo point gain after Bite (indicates Carnage proc) -- Carnage gives +1 CP immediately after Bite if it procs if class == "DRUID" and carnageRank >= 1 and spellName == "Ferocious Bite" and casterGuid == myGuid then if targetGuid and numHit > 0 then -- Schedule delayed check (50ms to allow CP to register) - local checkFrame = CreateFrame("Frame") - checkFrame.targetGuid = targetGuid - checkFrame.startTime = GetTime() - - checkFrame:SetScript("OnUpdate", function() - -- Wait 0.05 seconds - if GetTime() - this.startTime >= 0.05 then - -- Check if we gained a combo point (indicates Carnage proc) - local cp = GetComboPoints() or 0 - - if cp > 0 then - -- Carnage triggered! Refresh Rip & Rake - local guid = this.targetGuid - local refreshTime = GetTime() - - -- Refresh in ownDebuffs - if ownDebuffs[guid] then - if ownDebuffs[guid]["Rip"] then - ownDebuffs[guid]["Rip"].startTime = refreshTime - if debugStats.enabled then - DEFAULT_CHAT_FRAME:AddMessage("|cff00ffff[CARNAGE]|r Rip refreshed (CP detected)") - end - end - if ownDebuffs[guid]["Rake"] then - ownDebuffs[guid]["Rake"].startTime = refreshTime - if debugStats.enabled then - DEFAULT_CHAT_FRAME:AddMessage("|cff00ffff[CARNAGE]|r Rake refreshed (CP detected)") - end - end - end - - -- Refresh in allAuraCasts - if allAuraCasts[guid] then - if allAuraCasts[guid]["Rip"] and allAuraCasts[guid]["Rip"][myGuid] then - allAuraCasts[guid]["Rip"][myGuid].startTime = refreshTime - end - if allAuraCasts[guid]["Rake"] and allAuraCasts[guid]["Rake"][myGuid] then - allAuraCasts[guid]["Rake"][myGuid].startTime = refreshTime - end - end - - -- Trigger UI updates - if pfTarget and UnitExists("target") then - local _, currentTargetGuid = UnitExists("target") - if currentTargetGuid == guid then - pfTarget.update_aura = true - end - end - - if pfUI.nameplates and pfUI.nameplates.OnAuraUpdate then - pfUI.nameplates:OnAuraUpdate(guid) - end - end - - -- Cleanup: Remove OnUpdate handler - this:SetScript("OnUpdate", nil) - end - end) + carnageState = { + targetGuid = targetGuid, + checkTime = GetTime() + 0.05 + } + carnageCheckFrame:Show() end end @@ -1258,14 +1352,16 @@ if hasNampower then -- Handle self-overwrite debuffs (clear other casters) if selfOverwriteDebuffs[spellName] then - local oldCasters = {} + local n = 0 for otherCaster in pairs(allAuraCasts[targetGuid][spellName]) do if otherCaster ~= casterGuid then - table.insert(oldCasters, otherCaster) + n = n + 1 + _cleanupBuf1[n] = otherCaster end end - for _, otherCaster in ipairs(oldCasters) do - allAuraCasts[targetGuid][spellName][otherCaster] = nil + for i = 1, n do + allAuraCasts[targetGuid][spellName][_cleanupBuf1[i]] = nil + _cleanupBuf1[i] = nil end -- Clear from ownDebuffs if we're being overwritten @@ -1372,6 +1468,7 @@ if hasNampower then data.duration = duration data.texture = texture data.rank = rankNum + data.spellId = spellId -- Handle variant pairs for ownDebuffs if debuffOverwritePairs[spellName] then @@ -1497,7 +1594,8 @@ if hasNampower then startTime = auraData.startTime, duration = auraData.duration, texture = texture, - rank = auraData.rank or 0 + rank = auraData.rank or 0, + spellId = spellId } if debugStats.enabled and IsCurrentTarget(guid) then diff --git a/libs/libpredict.lua b/libs/libpredict.lua index 390f90fc..7538b1ac 100644 --- a/libs/libpredict.lua +++ b/libs/libpredict.lua @@ -243,7 +243,7 @@ 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 + 0.1 -- Default: 10 FPS + this.tick = now + pfUI.throttle:Get("libpredict") -- Default: Normal (10 FPS) -- update on timeout events for timestamp, targets in pairs(events) do diff --git a/modules/castbar.lua b/modules/castbar.lua index ed235a02..c92af22f 100644 --- a/modules/castbar.lua +++ b/modules/castbar.lua @@ -149,8 +149,37 @@ pfUI:RegisterModule("castbar", "vanilla", 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 == "player" and GetPlayerGUID then + castGuid = GetPlayerGUID() + elseif 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() @@ -189,6 +218,7 @@ pfUI:RegisterModule("castbar", "vanilla", function () this.bar:SetValue(100) this.fadeout = 1 this.delay = 0 + this.itemIconApplied = nil end end) @@ -337,4 +367,4 @@ pfUI:RegisterModule("castbar", "vanilla", function () UpdateMovable(pfUI.castbar.focus) end -end) +end) \ No newline at end of file diff --git a/modules/player.lua b/modules/player.lua index 053bcd6a..b1c0b020 100644 --- a/modules/player.lua +++ b/modules/player.lua @@ -13,12 +13,7 @@ pfUI:RegisterModule("player", "vanilla:tbc", function () -- Add throttle to player frame OnUpdate if pfUI.uf.player:GetScript("OnUpdate") then - local originalOnUpdate = pfUI.uf.player:GetScript("OnUpdate") - pfUI.uf.player:SetScript("OnUpdate", function() - if (this.throttleTick or 0) > GetTime() then return end - this.throttleTick = GetTime() + 0.1 -- Default: 10 FPS - originalOnUpdate() - end) + pfUI.uf.player:SetScript("OnUpdate", pfUI.uf.player:GetScript("OnUpdate")) end -- Replace default's RESET_INSTANCES button with an always working one diff --git a/modules/skin.lua b/modules/skin.lua index 390a03f7..04f57cae 100644 --- a/modules/skin.lua +++ b/modules/skin.lua @@ -79,4 +79,4 @@ pfUI:RegisterModule("skin", "vanilla", function () else UIErrorsFrame:RegisterEvent("UI_ERROR_MESSAGE") end -end) +end) \ No newline at end of file diff --git a/pfUI-tbc.toc b/pfUI-tbc.toc index 81f9455a..acefe0df 100644 --- a/pfUI-tbc.toc +++ b/pfUI-tbc.toc @@ -3,7 +3,7 @@ ## Author: Shagu - modfied by me0wg4ming ## Notes: A complete user interface replacement. ## Notes-ruRU: Полная замена пользовательского интерфейса. -## Version: 7.6.0 (experiment version) +## Version: 7.6.2 (experiment version) ## SavedVariables: pfUI_profiles, pfUI_addon_profiles, pfUI_cache, pfUI_throttle ## SavedVariablesPerCharacter: pfUI_config, pfUI_init, pfUI_playerDB diff --git a/pfUI.toc b/pfUI.toc index 6fb07428..14a6a80e 100644 --- a/pfUI.toc +++ b/pfUI.toc @@ -3,7 +3,7 @@ ## Author: Shagu - modfied by me0wg4ming ## Notes: A complete user interface replacement. ## Notes-ruRU: Полная замена пользовательского интерфейса. -## Version: 7.6.0 (experiment version) +## Version: 7.6.2 (experiment version) ## SavedVariables: pfUI_profiles, pfUI_addon_profiles, pfUI_cache, pfUI_throttle ## SavedVariablesPerCharacter: pfUI_config, pfUI_init, pfUI_playerDB