From 33ffd85782729c1780bfd49c1c3b4037b46999b1 Mon Sep 17 00:00:00 2001 From: Jrc13245 Date: Tue, 2 Dec 2025 17:20:40 -0500 Subject: [PATCH] lesse in combat item swapping lag and fix errors. --- Core.lua | 158 ++++++++++++++++++++++++--- Extensions/Tooltip/Generic.lua | 43 ++++++++ Utility.lua | 190 ++++++++++++++++++++++++++++++--- 3 files changed, 359 insertions(+), 32 deletions(-) diff --git a/Core.lua b/Core.lua index 292f397..2fd0027 100644 --- a/Core.lua +++ b/Core.lua @@ -2430,6 +2430,35 @@ function CleveRoids.OnUpdate(self) end end + -- Process deferred equipment index updates (for throttled UNIT_INVENTORY_CHANGED) + if CleveRoids.equipIndexPendingTime then + local now = GetTime() + local lastEquipIndex = CleveRoids.lastEquipIndexTime or 0 + if (now - lastEquipIndex) >= 0.2 then + -- Throttle has expired, process the deferred update + CleveRoids.lastEquipIndexTime = now + CleveRoids.equipIndexPendingTime = nil + + local inCombat = UnitAffectingCombat("player") + + if inCombat then + -- In combat: Lightweight equipment-only indexing + CleveRoids.IndexEquippedItems() + else + -- Out of combat: Full indexing + CleveRoids.lastItemIndexTime = now + CleveRoids.IndexItems() + CleveRoids.Actions = {} + CleveRoids.Macros = {} + CleveRoids.IndexActionBars() + end + + if CleveRoidMacros.realtime == 0 then + CleveRoids.QueueActionUpdate() + end + end + end + -- PERFORMANCE OPTIMIZATION: Removed UpdateCastingState() polling -- Casting state is now fully event-driven via SPELLCAST_* events -- This eliminates continuous GetCurrentCastingInfo() polling @@ -3162,7 +3191,63 @@ function CleveRoids.Frame:ADDON_LOADED(addon) end function CleveRoids.Frame:UNIT_CASTEVENT(caster,target,action,spell_id,cast_time) - if action == "MAINHAND" or action == "OFFHAND" then return end + -- Handle melee swings for judgement refresh + if action == "MAINHAND" or action == "OFFHAND" then + -- Only process if this is the player's melee swing + if caster == CleveRoids.playerGuid and CleveRoids.playerClass == "PALADIN" then + -- Refresh judgements on the target + if target and CleveRoids.libdebuff then + local normalizedTarget = CleveRoids.NormalizeGUID(target) + if normalizedTarget and CleveRoids.libdebuff.objects[normalizedTarget] then + -- Refresh all active Judgements on the target (pfUI-style: by name, not just ID) + for spellID, rec in pairs(CleveRoids.libdebuff.objects[normalizedTarget]) do + if rec.start and rec.duration then + local spellName = SpellInfo(spellID) + if spellName then + -- Remove rank to get base name + local baseName = string.gsub(spellName, "%s*%(Rank %d+%)", "") + + -- Check if this is a judgement by name (pfUI approach) + if CleveRoids.libdebuff.judgementNames and CleveRoids.libdebuff.judgementNames[baseName] then + -- Only refresh if the Judgement is still active and was cast by player + local remaining = rec.duration + rec.start - GetTime() + if remaining > 0 and rec.caster == "player" then + -- Refresh the Judgement by updating the start time + rec.start = GetTime() + + if CleveRoids.debug then + DEFAULT_CHAT_FRAME:AddMessage( + string.format("|cff00ffaa[Judgement Refresh]|r Refreshed %s (ID:%d) on %s hit - new duration: %ds", + baseName, spellID, action, rec.duration) + ) + end + + -- Also sync to pfUI if it's loaded + if pfUI and pfUI.api and pfUI.api.libdebuff then + local targetName = CleveRoids.libdebuff.guidToName[normalizedTarget] or UnitName("target") + local targetLevel = UnitLevel("target") or 0 + + if targetName then + -- Refresh in pfUI's tracking (by name) + pfUI.api.libdebuff:AddEffect(targetName, targetLevel, baseName, rec.duration, "player") + + if CleveRoids.debug then + DEFAULT_CHAT_FRAME:AddMessage( + string.format("|cff00ffaa[pfUI Judgement Refresh]|r Synced %s refresh to pfUI", baseName) + ) + end + end + end + end + end + end + end + end + end + end + end + return -- Still return early after processing melee + end -- Debug channel tracking if CleveRoids.ChannelTimeDebug then @@ -3273,7 +3358,7 @@ function CleveRoids.Frame:SPELL_CAST_EVENT(success, spellId, castType, targetGui CleveRoids.CurrentSpell.type = "channeled" CleveRoids.CurrentSpell.castingSpellId = spellId - local spellName = GetSpellInfo(spellId) + local spellName = SpellInfo(spellId) if spellName then CleveRoids.CurrentSpell.spellName = spellName DEFAULT_CHAT_FRAME:AddMessage(string.format("[SPELL_CAST_EVENT] Channel START: %s", spellName)) @@ -3294,7 +3379,7 @@ function CleveRoids.Frame:SPELLCAST_CHANNEL_START() local castId, visId, autoId, casting, channeling, onswing, autoattack = GetCurrentCastingInfo() if visId and visId > 0 then CleveRoids.CurrentSpell.castingSpellId = visId - local spellName = GetSpellInfo(visId) + local spellName = SpellInfo(visId) if spellName then CleveRoids.CurrentSpell.spellName = spellName end @@ -3325,7 +3410,7 @@ function CleveRoids.Frame:SPELLCAST_START() local castId, visId, autoId, casting, channeling, onswing, autoattack = GetCurrentCastingInfo() if castId and castId > 0 then CleveRoids.CurrentSpell.castingSpellId = castId - local spellName = GetSpellInfo(castId) + local spellName = SpellInfo(castId) if spellName then CleveRoids.CurrentSpell.spellName = spellName end @@ -3391,6 +3476,17 @@ function CleveRoids.Frame:PLAYER_LEAVE_COMBAT() end end + -- Full re-index after combat to refresh any items/bags that were skipped + -- during combat for performance. Use a slight delay to avoid spam. + local now = GetTime() + if (now - (CleveRoids.lastItemIndexTime or 0)) > 0.5 then + CleveRoids.lastItemIndexTime = now + CleveRoids.IndexItems() + CleveRoids.Actions = {} + CleveRoids.Macros = {} + CleveRoids.IndexActionBars() + end + if CleveRoidMacros.realtime == 0 then CleveRoids.QueueActionUpdate() end @@ -3456,12 +3552,23 @@ function CleveRoids.Frame:BAG_UPDATE() -- Only index items if more than 1 second has passed since the last index if (now - (CleveRoids.lastItemIndexTime or 0)) > 1.0 then CleveRoids.lastItemIndexTime = now + + -- In combat: Skip bag indexing entirely - GetItem() has fallback logic + -- and bag items rarely change meaningfully mid-combat + if UnitAffectingCombat("player") then + -- Only queue a UI update, don't rebuild caches + if CleveRoidMacros.realtime == 0 then + CleveRoids.QueueActionUpdate() + end + return + end + + -- Out of combat: Full indexing CleveRoids.IndexItems() -- Directly clear all relevant caches and force a UI refresh for all buttons. CleveRoids.Actions = {} CleveRoids.Macros = {} - --CleveRoids.ParsedMsg = {} CleveRoids.IndexActionBars() if CleveRoidMacros.realtime == 0 then CleveRoids.QueueActionUpdate() @@ -3471,17 +3578,38 @@ end function CleveRoids.Frame:UNIT_INVENTORY_CHANGED() if arg1 ~= "player" then return end - - -- Equipment changes need immediate response, bypass BAG_UPDATE throttle - local now = GetTime() - CleveRoids.lastItemIndexTime = now - CleveRoids.IndexItems() - -- Directly clear all relevant caches and force a UI refresh for all buttons - CleveRoids.Actions = {} - CleveRoids.Macros = {} - CleveRoids.IndexActionBars() - + -- Throttle equipment changes to prevent FPS drops during rapid gear swapping + -- (e.g., libram swapping). Uses 0.2s throttle for responsiveness. + local now = GetTime() + local lastEquipIndex = CleveRoids.lastEquipIndexTime or 0 + + if (now - lastEquipIndex) < 0.2 then + -- Mark that we need a deferred update after throttle expires + CleveRoids.equipIndexPendingTime = now + return + end + + -- Execute the update + CleveRoids.lastEquipIndexTime = now + CleveRoids.equipIndexPendingTime = nil + + local inCombat = UnitAffectingCombat("player") + + if inCombat then + -- In combat: Lightweight equipment-only indexing + -- Skip bag scanning and action bar re-indexing for performance + CleveRoids.IndexEquippedItems() + else + -- Out of combat: Full indexing + CleveRoids.lastItemIndexTime = now + CleveRoids.IndexItems() + -- Clear caches and re-index action bars only out of combat + CleveRoids.Actions = {} + CleveRoids.Macros = {} + CleveRoids.IndexActionBars() + end + if CleveRoidMacros.realtime == 0 then CleveRoids.QueueActionUpdate() end diff --git a/Extensions/Tooltip/Generic.lua b/Extensions/Tooltip/Generic.lua index 8f63a1d..30f7e45 100644 --- a/Extensions/Tooltip/Generic.lua +++ b/Extensions/Tooltip/Generic.lua @@ -111,6 +111,49 @@ function CleveRoids.IndexTalents() end +-- Lightweight equipment-only indexing for combat situations +-- Updates existing cache rather than rebuilding it +function CleveRoids.IndexEquippedItems() + local items = CleveRoids.Items or {} + + for inventoryID = 0, 19 do + local link = GetInventoryItemLink("player", inventoryID) + if link then + local _, _, itemID = string.find(link, "item:(%d+)") + local name, link, _, _, itemType, itemSubType, _, _, texture = GetItemInfo(itemID) + if name then + local count = GetInventoryItemCount("player", inventoryID) + if not items[name] then + items[name] = { + inventoryID = inventoryID, + id = itemID, + name = name, + count = count, + texture = texture, + link = link, + } + items[itemID] = name + local lowerName = string.lower(name) + if lowerName ~= name then + items[lowerName] = name + end + else + -- Update existing entry with current equipment state + items[name].inventoryID = inventoryID + items[name].count = count + end + end + else + -- Slot is now empty - clear inventoryID from any item that was there + -- This is handled lazily by GetItem() fallback, so we skip expensive iteration + end + end + + CleveRoids.lastGetItem = nil + CleveRoids.Items = items +end + + function CleveRoids.IndexItems() local items = {} for bagID = 0, NUM_BAG_SLOTS do diff --git a/Utility.lua b/Utility.lua index 5deb4b7..d6e9e12 100644 --- a/Utility.lua +++ b/Utility.lua @@ -661,20 +661,8 @@ lib.personalDebuffs = lib.personalDebuffs or { [5589] = 6, -- Hammer of Justice (Rank 3) [10308] = 6, -- Hammer of Justice (Rank 4) - [20184] = 10, -- Judgement of Justice - [20185] = 10, -- Judgement of Light (Rank 1) - [20267] = 10, -- Judgement of Light (Rank 2) - [20268] = 10, -- Judgement of Light (Rank 3) - [20271] = 10, -- Judgement of Light (Rank 4) - [20186] = 10, -- Judgement of Wisdom (Rank 1) - [20354] = 10, -- Judgement of Wisdom (Rank 2) - [20355] = 10, -- Judgement of Wisdom (Rank 3) - [21183] = 10, -- Judgement of the Crusader (Rank 1) - [20183] = 10, -- Judgement of the Crusader (Rank 2) - [20300] = 10, -- Judgement of the Crusader (Rank 3) - [20301] = 10, -- Judgement of the Crusader (Rank 4) - [20302] = 10, -- Judgement of the Crusader (Rank 5) - [20303] = 10, -- Judgement of the Crusader (Rank 6) + -- NOTE: Judgements moved to sharedDebuffs for proper target scanning + -- NOTE: 51750 is the CAST spell for Turtle WoW, not a debuff (debuff is 51752) -- SHAMAN [8050] = 12, -- Flame Shock (Rank 1) @@ -694,6 +682,25 @@ lib.personalDebuffs = lib.personalDebuffs or { -- SHARED DEBUFFS: Only one instance exists on a target, shared/refreshed by all players -- These are armor reductions, attack power reductions, and marks lib.sharedDebuffs = lib.sharedDebuffs or { + -- PALADIN JUDGEMENTS (tracked as shared so SeedUnit picks them up from target scanning) + [20184] = 10, -- Judgement of Justice + [20185] = 10, -- Judgement of Light (Rank 1) + [20267] = 10, -- Judgement of Light (Rank 2) + [20268] = 10, -- Judgement of Light (Rank 3) + [20271] = 10, -- Judgement of Light (Rank 4) + [20186] = 10, -- Judgement of Wisdom (Rank 1) + [20354] = 10, -- Judgement of Wisdom (Rank 2) + [20355] = 10, -- Judgement of Wisdom (Rank 3) + [51751] = 10, -- Judgement of Wisdom (Rank 4) - Turtle WoW + [51752] = 10, -- Judgement of Wisdom (Rank 5) - Turtle WoW + [21183] = 10, -- Judgement of the Crusader (Rank 1) + [20183] = 10, -- Judgement of the Crusader (Rank 2) + [20300] = 10, -- Judgement of the Crusader (Rank 3) + [20301] = 10, -- Judgement of the Crusader (Rank 4) + [20302] = 10, -- Judgement of the Crusader (Rank 5) + [20303] = 10, -- Judgement of the Crusader (Rank 6) + [51752] = 10, -- Turtle WoW: Judgement of Wisdom debuff + -- WARRIOR [7386] = 30, -- Sunder Armor (Rank 1) [7405] = 30, -- Sunder Armor (Rank 2) @@ -754,6 +761,7 @@ lib.sharedDebuffs = lib.sharedDebuffs or { -- JUDGEMENT SPELLS: These are refreshed by the paladin's melee attacks -- Track them by spell ID for refresh detection lib.judgementSpells = lib.judgementSpells or { + -- Vanilla IDs [20184] = true, -- Judgement of Justice [20185] = true, -- Judgement of Light (Rank 1) [20267] = true, -- Judgement of Light (Rank 2) @@ -768,8 +776,91 @@ lib.judgementSpells = lib.judgementSpells or { [20301] = true, -- Judgement of the Crusader (Rank 4) [20302] = true, -- Judgement of the Crusader (Rank 5) [20303] = true, -- Judgement of the Crusader (Rank 6) + + -- Turtle WoW custom judgement IDs (debuff IDs only, not cast spells) + [51751] = true, -- Judgement of Wisdom (Rank 4) - Turtle WoW + [51752] = true, -- Judgement of Wisdom (Rank 5) - Turtle WoW + -- Auto-detection will discover other Turtle WoW judgement debuffs at runtime } +-- Pending judgement casts: Map cast spell ID to target for debuff ID detection +-- When paladin casts Judgement, we need to find what debuff actually appears +lib.pendingJudgements = lib.pendingJudgements or {} + +-- Detected judgement debuff IDs: Maps debuff name patterns to spell IDs +-- This gets populated as we discover what debuffs actually appear after casting +lib.detectedJudgementDebuffIDs = lib.detectedJudgementDebuffIDs or {} + +-- Judgement names for refresh tracking (like pfUI does it) +-- We track by NAME to handle different spell IDs across servers +lib.judgementNames = lib.judgementNames or { + ["Judgement of Justice"] = true, + ["Judgement of Light"] = true, + ["Judgement of Wisdom"] = true, + ["Judgement of the Crusader"] = true, +} + +-- Judgement refresh on melee hits (pfUI-style using CHAT_MSG_COMBAT_SELF_HITS) +-- This is more reliable than UNIT_CASTEVENT MAINHAND/OFFHAND +local judgementRefreshFrame = CreateFrame("Frame", "CleveRoidsJudgementRefreshFrame", UIParent) +judgementRefreshFrame:RegisterEvent("CHAT_MSG_COMBAT_SELF_HITS") + +judgementRefreshFrame:SetScript("OnEvent", function() + -- Only process for paladins + if CleveRoids.playerClass ~= "PALADIN" then return end + if not arg1 then return end + + -- Check if this is a melee hit using global combat log patterns + -- COMBATHITSELFOTHER = "You hit %s for %d." + -- COMBATHITCRITSELFOTHER = "You crit %s for %d." + local isHit = string.find(arg1, "^You hit") or string.find(arg1, "^You crit") + if not isHit then return end + + -- Get current target GUID + local _, targetGUID = UnitExists("target") + if not targetGUID then return end + targetGUID = CleveRoids.NormalizeGUID(targetGUID) + if not targetGUID then return end + + -- Refresh all judgement debuffs on the target (by name, like pfUI) + if lib.objects[targetGUID] then + for spellID, rec in pairs(lib.objects[targetGUID]) do + if rec.start and rec.duration then + local spellName = SpellInfo(spellID) + if spellName then + -- Remove rank to get base name + local baseName = string.gsub(spellName, "%s*%(Rank %d+%)", "") + + -- Check if this is a judgement by name + if lib.judgementNames[baseName] then + local remaining = rec.duration + rec.start - GetTime() + if remaining > 0 then + -- Refresh by resetting start time + rec.start = GetTime() + + if CleveRoids.debug then + DEFAULT_CHAT_FRAME:AddMessage( + string.format("|cff00ffaa[Judgement Refresh]|r %s (ID:%d) refreshed on melee hit - %ds", + baseName, spellID, rec.duration) + ) + end + + -- Also sync to pfUI + if pfUI and pfUI.api and pfUI.api.libdebuff then + local targetName = lib.guidToName[targetGUID] or UnitName("target") + local targetLevel = UnitLevel("target") or 0 + if targetName then + pfUI.api.libdebuff:AddEffect(targetName, targetLevel, baseName, rec.duration, "player") + end + end + end + end + end + end + end + end +end) + -- Combined table for backwards compatibility (will be deprecated) lib.durations = lib.durations or {} for k, v in pairs(lib.personalDebuffs) do @@ -1531,6 +1622,54 @@ delayedTrackingFrame:SetScript("OnUpdate", function() end end + -- Process pending judgement scans to detect actual debuff IDs + if lib.pendingJudgements then + local toRemove = {} + for i, pending in ipairs(lib.pendingJudgements) do + local elapsed = GetTime() - pending.timestamp + + -- Scan target after 0.5 seconds to find the actual judgement debuff + if elapsed >= 0.5 then + -- Check if this is still the current target + local _, currentTargetGUID = UnitExists("target") + currentTargetGUID = CleveRoids.NormalizeGUID(currentTargetGUID) + + if currentTargetGUID == pending.targetGUID and CleveRoids.hasSuperwow then + -- Scan all debuffs on target to find judgement-type debuffs + for slot = 1, 16 do + local _, _, _, debuffSpellID = UnitDebuff("target", slot) + if not debuffSpellID then break end + + local debuffName = SpellInfo(debuffSpellID) + -- Check if this is a judgement debuff (name starts with "Judgement") + if debuffName and string.find(debuffName, "^Judgement") then + -- Found a judgement debuff! Store it for refresh tracking + if not lib.detectedJudgementDebuffIDs[debuffSpellID] then + lib.detectedJudgementDebuffIDs[debuffSpellID] = true + lib.judgementSpells[debuffSpellID] = true -- Add to refresh list + + if CleveRoids.debug then + DEFAULT_CHAT_FRAME:AddMessage( + string.format("|cff00ffff[Judgement Detect]|r Found debuff %s (ID:%d) from cast (ID:%d) - added to refresh list", + debuffName, debuffSpellID, pending.castSpellID) + ) + end + end + end + end + end + + -- Mark for removal + table.insert(toRemove, i) + end + end + + -- Remove processed scans (iterate backwards to avoid index shifting) + for i = table.getn(toRemove), 1, -1 do + table.remove(lib.pendingJudgements, toRemove[i]) + end + end + -- Process pending personal debuffs if lib.pendingPersonalDebuffs then local toRemove = {} @@ -1774,6 +1913,16 @@ ev:SetScript("OnEvent", function() comboPoints = comboPoints }) + -- If this is a Judgement spell cast by a Paladin, schedule a scan to find the actual debuff ID + if CleveRoids.playerClass == "PALADIN" and lib.judgementSpells[spellID] then + table.insert(lib.pendingJudgements, { + timestamp = GetTime(), + castSpellID = spellID, + targetGUID = targetGUID, + targetName = targetName + }) + end + if CleveRoids.debug then local spellName = SpellInfo(spellID) or "Unknown" DEFAULT_CHAT_FRAME:AddMessage( @@ -2380,12 +2529,19 @@ evCleanup:SetScript("OnEvent", function() end) -- Judgement refresh on melee hits --- Paladins' Judgements are refreshed when they melee attack the target +-- NOTE: Judgement refresh is now handled in Core.lua via UNIT_CASTEVENT (MAINHAND/OFFHAND) +-- This chat-based fallback is only used if SuperWoW is not available local evJudgement = CreateFrame("Frame", "CleveRoidsLibDebuffJudgementRefreshFrame", UIParent) -evJudgement:RegisterEvent("CHAT_MSG_COMBAT_SELF_HITS") -evJudgement:RegisterEvent("CHAT_MSG_COMBAT_SELF_MISSES") + +-- Only use chat-based detection if SuperWoW is not available +if not CleveRoids.hasSuperwow then + evJudgement:RegisterEvent("CHAT_MSG_COMBAT_SELF_HITS") + evJudgement:RegisterEvent("CHAT_MSG_COMBAT_SELF_MISSES") +end evJudgement:SetScript("OnEvent", function() + -- Skip if SuperWoW is available (handled by UNIT_CASTEVENT instead) + if CleveRoids.hasSuperwow then return end -- Only process for paladins if CleveRoids.playerClass ~= "PALADIN" then return end