From 87183b6e09dfdb9da369a247597a8f32ba911ce0 Mon Sep 17 00:00:00 2001 From: Jrc13245 Date: Wed, 3 Dec 2025 14:41:13 -0500 Subject: [PATCH] fix fallback for channeled spell icons and final fixes for swapping items in combat lag. --- Core.lua | 246 ++++++++++++++++----------------- Extensions/Tooltip/Generic.lua | 43 +++++- 2 files changed, 163 insertions(+), 126 deletions(-) diff --git a/Core.lua b/Core.lua index a46c2f0..2a1953c 100644 --- a/Core.lua +++ b/Core.lua @@ -2298,27 +2298,16 @@ function CleveRoids.EquipBagItem(msg, offhand) return false end - local now = GetTime() local invslot = offhand and 17 or 16 - -- PERFORMANCE: Combat weapon swap throttle check first (cheapest check) - if UnitAffectingCombat("player") and (invslot == 16 or invslot == 17) then - if (now - CleveRoids.lastWeaponSwapTime) < 1.5 then - return false - end - end - - -- PERFORMANCE: Pre-check if item is already equipped BEFORE any IndexItems() call - -- This avoids expensive indexing when rapidly pressing the same equip macro + -- Check if item is already equipped (fast path) local currentItemLink = GetInventoryItemLink("player", invslot) if currentItemLink then local _, _, currentID = string.find(currentItemLink, "item:(%d+)") local currentItemName = GetItemInfo(currentItemLink) - - -- Check by ID first (faster), then by name local msgLower = string.lower(msg) + if currentID then - -- Direct ID match from msg (e.g., "12345" or item:12345) local _, _, msgID = string.find(msg, "(%d+)") if msgID and tonumber(msgID) == tonumber(currentID) then return true -- Already equipped @@ -2329,35 +2318,20 @@ function CleveRoids.EquipBagItem(msg, offhand) end end - -- Now do item lookup (may trigger IndexItems if needed) - if (now - (CleveRoids.lastItemIndexTime or 0)) > 0.5 then - CleveRoids.IndexItems() - CleveRoids.lastItemIndexTime = now - end - + -- Item lookup local item = CleveRoids.GetItem(msg) if not item or not item.name then CleveRoids.IndexItems() - CleveRoids.lastItemIndexTime = now item = CleveRoids.GetItem(msg) if not item or not item.name then return false end end - -- PERFORMANCE: Use item.id directly as throttle key (no string concat) - -- Fall back to invslot for items without ID - local throttleKey = item.id or invslot - - if CleveRoids.lastEquipTime[throttleKey] and (now - CleveRoids.lastEquipTime[throttleKey]) < 0.2 then - return false - end - - -- Double-check after item lookup that it's not already equipped - if currentItemLink then + -- Double-check it's not already equipped (by item ID after lookup) + if currentItemLink and item.id then local _, _, currentID = string.find(currentItemLink, "item:(%d+)") - if currentID and item.id and tonumber(currentID) == tonumber(item.id) then - CleveRoids.lastEquipTime[throttleKey] = now + if currentID and tonumber(currentID) == tonumber(item.id) then return true end end @@ -2366,6 +2340,16 @@ function CleveRoids.EquipBagItem(msg, offhand) return false end + -- Try EquipItemByName first (fastest, no cursor operations) + if item.name and EquipItemByName then + local ok = pcall(EquipItemByName, item.name, invslot) + if ok then + CleveRoids.Items[item.name] = nil + return true + end + end + + -- Fallback: Manual pickup and equip CleveRoids.equipInProgress = true if type(CloseStackSplitFrame) == "function" then @@ -2380,48 +2364,24 @@ function CleveRoids.EquipBagItem(msg, offhand) local pickupSuccess = false if item.bagID and item.slot then CleveRoids.GetNextBagSlotForUse(item, msg) - - local link = GetContainerItemLink(item.bagID, item.slot) - if link then - local _, _, bagItemID = string.find(link, "item:(%d+)") - if bagItemID and item.id and tonumber(bagItemID) == tonumber(item.id) then - PickupContainerItem(item.bagID, item.slot) - pickupSuccess = true - end - end + PickupContainerItem(item.bagID, item.slot) + pickupSuccess = CursorHasItem and CursorHasItem() elseif item.inventoryID then PickupInventoryItem(item.inventoryID) - pickupSuccess = true + pickupSuccess = CursorHasItem and CursorHasItem() end if not pickupSuccess then - CleveRoids.equipInProgress = false - CleveRoids.lastItemIndexTime = 0 - return false - end - - if not CursorHasItem or not CursorHasItem() then ClearCursor() CleveRoids.equipInProgress = false - CleveRoids.lastItemIndexTime = 0 return false end EquipCursorItem(invslot) - ClearCursor() - CleveRoids.lastEquipTime[throttleKey] = now - - if UnitAffectingCombat("player") and (invslot == 16 or invslot == 17) then - CleveRoids.lastWeaponSwapTime = now - end - + CleveRoids.Items[item.name] = nil CleveRoids.equipInProgress = false - - -- PERFORMANCE: Removed IndexItems() call here - -- UNIT_INVENTORY_CHANGED event already triggers re-indexing with proper throttling - return true end @@ -2602,27 +2562,17 @@ function CleveRoids.OnUpdate(self) -- This eliminates per-frame checks when queue is empty -- Process deferred equipment index updates (for throttled UNIT_INVENTORY_CHANGED) - if CleveRoids.equipIndexPendingTime then + -- Skip entirely in combat - EquipBagItem handles cache invalidation + if CleveRoids.equipIndexPendingTime and not UnitAffectingCombat("player") then local now = GetTime() - local lastEquipIndex = CleveRoids.lastEquipIndexTime or 0 - if (now - lastEquipIndex) >= 0.2 then - -- Throttle has expired, process the deferred update + if (now - (CleveRoids.lastEquipIndexTime or 0)) >= 0.2 then 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 + CleveRoids.lastItemIndexTime = now + CleveRoids.IndexItems() + CleveRoids.Actions = {} + CleveRoids.Macros = {} + CleveRoids.IndexActionBars() if CleveRoidMacros.realtime == 0 then CleveRoids.QueueActionUpdate() @@ -2871,25 +2821,41 @@ end CleveRoids.Hooks.ActionHasRange = ActionHasRange function ActionHasRange(slot) local actions = CleveRoids.GetAction(slot) - -- Only override range when #showtooltip is present - -- Macros without #showtooltip should use default game behavior + -- Only override range when #showtooltip is present and we have valid range data + -- inRange == -1 means IsSpellInRange couldn't determine (channeled spells, etc.) if actions and actions.tooltip and actions.active then - return (1 and actions.active.inRange ~= -1 or nil) - else - return CleveRoids.Hooks.ActionHasRange(slot) + if actions.active.inRange ~= -1 then + return 1 -- Has range check with valid data + else + -- For channeled spells (inRange == -1), try proxy slot lookup + local spellName = actions.active.spell and actions.active.spell.name + local proxySlot = spellName and CleveRoids.GetProxyActionSlot(spellName) + if proxySlot then + return CleveRoids.Hooks.ActionHasRange(proxySlot) + end + end end + return CleveRoids.Hooks.ActionHasRange(slot) end CleveRoids.Hooks.IsActionInRange = IsActionInRange function IsActionInRange(slot, unit) local actions = CleveRoids.GetAction(slot) - -- Only override range when #showtooltip is present - -- Macros without #showtooltip should use default game behavior + -- Only override range when #showtooltip is present and we have valid range data + -- inRange == -1 means IsSpellInRange couldn't determine (channeled spells, etc.) if actions and actions.tooltip and actions.active and actions.active.type == "spell" then - return actions.active.inRange - else - return CleveRoids.Hooks.IsActionInRange(slot, unit) + if actions.active.inRange ~= -1 then + return actions.active.inRange + else + -- For channeled spells (inRange == -1), try proxy slot lookup + local spellName = actions.active.spell and actions.active.spell.name + local proxySlot = spellName and CleveRoids.GetProxyActionSlot(spellName) + if proxySlot then + return CleveRoids.Hooks.IsActionInRange(proxySlot, unit) + end + end end + return CleveRoids.Hooks.IsActionInRange(slot, unit) end CleveRoids.Hooks.OriginalIsUsableAction = IsUsableAction @@ -3719,22 +3685,15 @@ function CleveRoids.Frame:ACTIONBAR_SLOT_CHANGED() end function CleveRoids.Frame:BAG_UPDATE() + -- In combat: Skip entirely for zero lag + if UnitAffectingCombat("player") then + return + end + + -- Out of combat: Full indexing with throttle local now = GetTime() - -- 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. @@ -3750,36 +3709,26 @@ end function CleveRoids.Frame:UNIT_INVENTORY_CHANGED() if arg1 ~= "player" then return end - -- 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 + -- In combat: Skip ALL processing - EquipBagItem already handles cache invalidation + -- This eliminates lag from IndexEquippedItems during rapid gear swapping + if UnitAffectingCombat("player") then + return + end - if (now - lastEquipIndex) < 0.2 then - -- Mark that we need a deferred update after throttle expires + -- Out of combat: Full indexing with throttle + local now = GetTime() + if (now - (CleveRoids.lastEquipIndexTime or 0)) < 0.2 then 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 + CleveRoids.lastItemIndexTime = now + CleveRoids.IndexItems() + CleveRoids.Actions = {} + CleveRoids.Macros = {} + CleveRoids.IndexActionBars() if CleveRoidMacros.realtime == 0 then CleveRoids.QueueActionUpdate() @@ -4076,6 +4025,8 @@ SlashCmdList["CLEVEROID"] = function(msg) DEFAULT_CHAT_FRAME:AddMessage('/cleveroid testcasting - Test [selfcasting]/[noselfcasting] conditionals') DEFAULT_CHAT_FRAME:AddMessage('/cleveroid testchannel - Test [channeltime] conditional tracking') DEFAULT_CHAT_FRAME:AddMessage('/cleveroid channeldebug - Toggle [channeltime] conditional debug output') + DEFAULT_CHAT_FRAME:AddMessage("|cffffaa00Action Slot Debug:|r") + DEFAULT_CHAT_FRAME:AddMessage('/cleveroid slotdebug - Debug action slot state (tooltip/range/mana)') return end @@ -4172,6 +4123,53 @@ SlashCmdList["CLEVEROID"] = function(msg) return end + -- slotdebug - Debug action slot state for tooltip/range/mana issues + if cmd == "slotdebug" then + local slot = tonumber(val) + if not slot then + CleveRoids.Print("Usage: /cleveroid slotdebug - Debug action slot (1-120)") + return + end + local actions = CleveRoids.GetAction(slot) + CleveRoids.Print("|cff00ff00=== Slot " .. slot .. " Debug ===|r") + if not actions then + CleveRoids.Print("No actions data for slot " .. slot) + CleveRoids.Print("GetActionText: " .. tostring(GetActionText(slot))) + return + end + CleveRoids.Print("actions.tooltip: " .. tostring(actions.tooltip ~= nil)) + CleveRoids.Print("actions.explicitTooltip: " .. tostring(actions.explicitTooltip)) + CleveRoids.Print("actions.list count: " .. tostring(actions.list and table.getn(actions.list) or 0)) + if actions.active then + CleveRoids.Print("|cff00ffffActive action:|r") + CleveRoids.Print(" .action: " .. tostring(actions.active.action)) + CleveRoids.Print(" .type: " .. tostring(actions.active.type)) + CleveRoids.Print(" .spell: " .. tostring(actions.active.spell ~= nil)) + if actions.active.spell then + CleveRoids.Print(" .spell.name: " .. tostring(actions.active.spell.name)) + CleveRoids.Print(" .spell.cost: " .. tostring(actions.active.spell.cost)) + end + CleveRoids.Print(" .usable: " .. tostring(actions.active.usable)) + CleveRoids.Print(" .oom: " .. tostring(actions.active.oom)) + CleveRoids.Print(" .inRange: " .. tostring(actions.active.inRange)) + CleveRoids.Print(" .conditionals: " .. tostring(actions.active.conditionals ~= nil)) + if actions.active.conditionals then + CleveRoids.Print(" .target: " .. tostring(actions.active.conditionals.target)) + end + else + CleveRoids.Print("|cffff0000No active action|r") + end + CleveRoids.Print("|cffaaaa00Hook conditions:|r") + local hasTooltip = actions.tooltip ~= nil + local hasActive = actions.active ~= nil + local typeIsSpell = actions.active and actions.active.type == "spell" + CleveRoids.Print(" actions.tooltip: " .. tostring(hasTooltip)) + CleveRoids.Print(" actions.active: " .. tostring(hasActive)) + CleveRoids.Print(" .type == 'spell': " .. tostring(typeIsSpell)) + CleveRoids.Print(" Range hook would: " .. (hasTooltip and hasActive and typeIsSpell and "USE CUSTOM" or "FALLBACK")) + return + end + -- macrostatus (check macro length warning status) if cmd == "macrostatus" then CleveRoids.Print("|cff00ff00=== MacroLengthWarn Status ===|r") diff --git a/Extensions/Tooltip/Generic.lua b/Extensions/Tooltip/Generic.lua index c68090a..a6976ff 100644 --- a/Extensions/Tooltip/Generic.lua +++ b/Extensions/Tooltip/Generic.lua @@ -173,6 +173,44 @@ function CleveRoids.IndexEquippedItems() CleveRoids.Items = items end +-- PERFORMANCE: Index a single equipment slot instead of all 20 +-- Use when we know exactly which slot changed (e.g., from EquipBagItem) +function CleveRoids.IndexEquipSlot(inventoryID) + if not inventoryID then return end + + local items = CleveRoids.Items or {} + local link = GetInventoryItemLink("player", inventoryID) + + if link then + local _, _, itemID = string.find(link, "item:(%d+)") + local name, itemLink, _, _, 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 = itemLink, + } + items[itemID] = name + local lowerName = string.lower(name) + if lowerName ~= name then + items[lowerName] = name + end + else + items[name].inventoryID = inventoryID + items[name].count = count + end + end + end + + CleveRoids.lastGetItem = nil + CleveRoids.Items = items +end + function CleveRoids.IndexItems() local items = {} @@ -522,8 +560,9 @@ end function CleveRoids.GetNextBagSlotForUse(item, text) if not item then return end - if CleveRoids.lastGetItem == CleveRoids.GetItem(text) then - if table.getn(item.bagSlots) > item.slotsIndex then + -- PERFORMANCE: Compare item directly instead of calling GetItem(text) again + if CleveRoids.lastGetItem == item then + if item.bagSlots and table.getn(item.bagSlots) > item.slotsIndex then item.slotsIndex = item.slotsIndex + 1 item.bagID, item.slot = unpack(item.bagSlots[item.slotsIndex]) end