From 30ed4e2bf90970f059c429fb1abc1bfba204e0b7 Mon Sep 17 00:00:00 2001 From: Jrc13245 Date: Wed, 3 Dec 2025 08:22:46 -0500 Subject: [PATCH] improve targeting function and further improve equipment mid combat lag. --- Core.lua | 389 ++++++++++++++++++++++++--------- Extensions/Tooltip/Generic.lua | 250 +++++++++++---------- Init.lua | 4 + 3 files changed, 429 insertions(+), 214 deletions(-) diff --git a/Core.lua b/Core.lua index 2fd0027..a46c2f0 100644 --- a/Core.lua +++ b/Core.lua @@ -26,6 +26,33 @@ CleveRoids.spellIdCache = {} -- PERFORMANCE: Spell name construction cache CleveRoids.spellNameCache = {} +-- PERFORMANCE: Upvalues for frequently called global functions (avoid global lookups) +local GetTime = GetTime +local UnitExists = UnitExists +local UnitAffectingCombat = UnitAffectingCombat +local GetContainerItemLink = GetContainerItemLink +local GetContainerItemInfo = GetContainerItemInfo +local GetContainerNumSlots = GetContainerNumSlots +local GetInventoryItemLink = GetInventoryItemLink +local GetItemInfo = GetItemInfo +local PickupContainerItem = PickupContainerItem +local PickupInventoryItem = PickupInventoryItem +local EquipCursorItem = EquipCursorItem +local CursorHasItem = CursorHasItem +local ClearCursor = ClearCursor +local TargetUnit = TargetUnit +local pcall = pcall +local pairs = pairs +local ipairs = ipairs +local type = type +local tonumber = tonumber +local tostring = tostring +local string_find = string.find +local string_lower = string.lower +local string_gsub = string.gsub +local table_insert = table.insert +local table_getn = table.getn + local requirementCheckFrame = CreateFrame("Frame") requirementCheckFrame:RegisterEvent("ADDON_LOADED") requirementCheckFrame:SetScript("OnEvent", function() @@ -92,79 +119,117 @@ local function GetInventoryIdFromSlot(slotName) return SLOT_TO_INVID[slotName] or GetInventorySlotInfo(slotName) end -local function IsSlotOnCooldown(slot) - local now = GetTime() +-- PERFORMANCE: Cache GetTime() result for cooldown checks within same frame +local function IsSlotOnCooldown(slot, now) + now = now or GetTime() local slotTime = CleveRoids.lastEquipTime[slot] or 0 local globalTime = CleveRoids.lastGlobalEquipTime or 0 - if (now - slotTime) < CleveRoids.EQUIP_COOLDOWN then - return true - end - - if (now - globalTime) < CleveRoids.EQUIP_GLOBAL_COOLDOWN then - return true - end - - return false + return (now - slotTime) < CleveRoids.EQUIP_COOLDOWN or + (now - globalTime) < CleveRoids.EQUIP_GLOBAL_COOLDOWN end -local function PerformEquipSwap(item, inventoryId) +-- PERFORMANCE: Consolidated cursor handling, fewer CursorHasItem() calls +local function PerformEquipSwap(item, inventoryId, useQueueScript) if not item or not inventoryId then return false end -- Check if in combat and swapping weapons - local inCombat = UnitAffectingCombat("player") local isWeapon = (inventoryId == 16 or inventoryId == 17 or inventoryId == 18) - if inCombat and isWeapon then + if isWeapon and UnitAffectingCombat("player") then -- Don't swap while casting if CleveRoids.CurrentSpell.type ~= "" then return false end - -- Check for on-swing spells if available + -- Check for on-swing spells if available (Nampower) if GetCurrentCastingInfo then - local _, _, _, _, _, onswing = GetCurrentCastingInfo() + local castId, _, _, casting, channeling, onswing = GetCurrentCastingInfo() + if onswing == 1 then return false end + + -- If casting/channeling and QueueScript available, queue the swap for after cast + if (casting == 1 or channeling == 1) and QueueScript and useQueueScript and item.name then + local script = string.format('EquipItemByName("%s",%d)', item.name, inventoryId) + QueueScript(script, 3) -- Priority 3 = after queued spells + return true + end end end - -- Try to equip - local success = false + -- PERFORMANCE: Try EquipItemByName first if available (handles everything internally) + if item.name and EquipItemByName then + local ok = pcall(EquipItemByName, item.name, inventoryId) + if ok then return true end + end - -- Method 1: Use item by bag/slot + -- Fallback: Manual pickup and equip if item.bagID and item.slot then PickupContainerItem(item.bagID, item.slot) - if CursorHasItem() then - EquipCursorItem(inventoryId) - success = not CursorHasItem() - end - end - - -- Method 2: Use item by inventory ID - if not success and item.inventoryID then + elseif item.inventoryID then PickupInventoryItem(item.inventoryID) - if CursorHasItem() then - EquipCursorItem(inventoryId) - success = not CursorHasItem() - end + else + return false end - -- Method 3: Use EquipItemByName (SuperWoW) - if not success and item.name and EquipItemByName then - local ok = pcall(EquipItemByName, item.name, inventoryId) - success = ok - end - - -- Clear cursor - if CursorHasItem() then - ClearCursor() + -- Single cursor check after pickup attempt + if not CursorHasItem() then + return false end + EquipCursorItem(inventoryId) + local success = not CursorHasItem() + ClearCursor() return success end +-- PERFORMANCE: Get a queue entry from pool or create new +local function GetQueueEntry() + local pool = CleveRoids.queueEntryPool + local n = pool and table_getn(pool) or 0 + if n > 0 then + local entry = pool[n] + pool[n] = nil + return entry + end + return {} +end + +-- PERFORMANCE: Return entry to pool for reuse +local function ReleaseQueueEntry(entry) + if not entry then return end + -- Clear the entry (set to nil, don't create new table) + entry.item = nil + entry.slotName = nil + entry.inventoryId = nil + entry.queueTime = nil + entry.retries = nil + entry.maxRetries = nil + entry.itemId = nil + -- Add to pool (max 10 pooled entries) + local pool = CleveRoids.queueEntryPool + if table_getn(pool) < 10 then + table_insert(pool, entry) + end +end + +-- PERFORMANCE: Swap-and-pop removal (O(1) instead of O(n)) +local function RemoveQueueEntry(i) + local queue = CleveRoids.equipmentQueue + local n = CleveRoids.equipmentQueueLen + local entry = queue[i] + + if i < n then + queue[i] = queue[n] + end + queue[n] = nil + CleveRoids.equipmentQueueLen = n - 1 + + ReleaseQueueEntry(entry) +end + -- Queue equipment swap function function CleveRoids.QueueEquipItem(item, slotName) if not item or not slotName then return false end @@ -173,10 +238,21 @@ function CleveRoids.QueueEquipItem(item, slotName) if not inventoryId then return false end local now = GetTime() + local itemId = item.id - -- Try immediate equip if not on cooldown - if not IsSlotOnCooldown(inventoryId) then - local success = PerformEquipSwap(item, inventoryId) + -- PERFORMANCE: Check for duplicate queue entries before adding + local queue = CleveRoids.equipmentQueue + local queueLen = CleveRoids.equipmentQueueLen + for i = 1, queueLen do + local q = queue[i] + if q and q.inventoryId == inventoryId and q.itemId == itemId then + return false -- Already queued + end + end + + -- Try immediate equip if not on cooldown (use QueueScript for smoother mid-cast swaps) + if not IsSlotOnCooldown(inventoryId, now) then + local success = PerformEquipSwap(item, inventoryId, true) if success then CleveRoids.lastEquipTime[inventoryId] = now @@ -185,44 +261,55 @@ function CleveRoids.QueueEquipItem(item, slotName) end end - -- Queue for later - table.insert(CleveRoids.equipmentQueue, { - item = item, - slotName = slotName, - inventoryId = inventoryId, - queueTime = now, - retries = 0, - maxRetries = 5 - }) + -- Queue for later using pooled entry + local entry = GetQueueEntry() + entry.item = item + entry.slotName = slotName + entry.inventoryId = inventoryId + entry.queueTime = now + entry.retries = 0 + entry.maxRetries = 5 + entry.itemId = itemId + + CleveRoids.equipmentQueueLen = queueLen + 1 + queue[CleveRoids.equipmentQueueLen] = entry + + -- PERFORMANCE: Start the queue processing frame + if CleveRoids.equipQueueFrame then + CleveRoids.equipQueueFrame:Show() + end return false end --- Process equipment queue (called from OnUpdate) +-- PERFORMANCE: Process equipment queue (called from self-disabling frame) function CleveRoids.ProcessEquipmentQueue() - if not CleveRoids.equipmentQueue or table.getn(CleveRoids.equipmentQueue) == 0 then - return - end + local queueLen = CleveRoids.equipmentQueueLen + if queueLen == 0 then return end local now = GetTime() + local queue = CleveRoids.equipmentQueue local i = 1 - while i <= table.getn(CleveRoids.equipmentQueue) do - local queued = CleveRoids.equipmentQueue[i] + while i <= CleveRoids.equipmentQueueLen do + local queued = queue[i] - -- Check if cooldown passed - if not IsSlotOnCooldown(queued.inventoryId) then - local success = PerformEquipSwap(queued.item, queued.inventoryId) + -- Remove expired entries first (>10 seconds old) + if (now - queued.queueTime) > 10 then + RemoveQueueEntry(i) + -- Don't increment i, we swapped in the last element + elseif not IsSlotOnCooldown(queued.inventoryId, now) then + -- Try to equip (use QueueScript for smoother mid-cast swaps) + local success = PerformEquipSwap(queued.item, queued.inventoryId, true) if success then CleveRoids.lastEquipTime[queued.inventoryId] = now CleveRoids.lastGlobalEquipTime = now - table.remove(CleveRoids.equipmentQueue, i) + RemoveQueueEntry(i) else queued.retries = queued.retries + 1 - if queued.retries >= queued.maxRetries then - table.remove(CleveRoids.equipmentQueue, i) + RemoveQueueEntry(i) else i = i + 1 end @@ -230,14 +317,28 @@ function CleveRoids.ProcessEquipmentQueue() else i = i + 1 end - - -- Remove expired entries (>10 seconds old) - if queued and (now - queued.queueTime) > 10 then - table.remove(CleveRoids.equipmentQueue, i) - end end end +-- PERFORMANCE: Self-disabling frame for queue processing with throttling +-- Only processes every EQUIP_QUEUE_INTERVAL instead of every frame +CleveRoids.EQUIP_QUEUE_INTERVAL = 0.05 -- 50ms between queue checks +CleveRoids.equipQueueLastUpdate = 0 +CleveRoids.equipQueueFrame = CreateFrame("Frame") +CleveRoids.equipQueueFrame:Hide() +CleveRoids.equipQueueFrame:SetScript("OnUpdate", function() + local now = GetTime() + if (now - CleveRoids.equipQueueLastUpdate) < CleveRoids.EQUIP_QUEUE_INTERVAL then + return -- Throttle: skip this frame + end + CleveRoids.equipQueueLastUpdate = now + + CleveRoids.ProcessEquipmentQueue() + if CleveRoids.equipmentQueueLen == 0 then + this:Hide() + end +end) + -- Improved DisableAddon function function CleveRoids.DisableAddon(reason) -- Prevent multiple disable calls @@ -1871,6 +1972,9 @@ function CleveRoids.DoTarget(msg) return ok end + -- Save original target GUID for potential restoration (SuperWoW returns GUID as 2nd value) + local _, originalTargetGuid = UnitExists("target") + do local unitTok = conditionals.target @@ -1973,6 +2077,56 @@ function CleveRoids.DoTarget(msg) end end + -- UnitXP 3D enemy scanning: cycles through enemies in world space (no nameplate required) + -- This is the most powerful scan - finds enemies by line of sight and distance + if wantsHarm and CleveRoids.hasUnitXP then + -- Try nearestEnemy first - most common case and most efficient + local found = UnitXP("target", "nearestEnemy") + if found and UnitExists("target") and IsGuidValid("target", conditionals) then + return true + end + + -- Determine scan mode: use distance-priority for melee conditionals + local wantsMelee = conditionals.meleerange or conditionals.nomeleerange + local scanMode = wantsMelee and "nextEnemyConsideringDistance" or "nextEnemyInCycle" + + local seenGuids = {} + local firstGuid = nil + local maxIterations = 50 -- Safety limit + + for i = 1, maxIterations do + found = UnitXP("target", scanMode) + if not found then break end + + local _, currentGuid = UnitExists("target") + if not currentGuid then break end + + -- Check if we've cycled back to start + if firstGuid == nil then + firstGuid = currentGuid + elseif currentGuid == firstGuid then + break -- Completed full cycle + end + + -- Skip already-seen targets + if not seenGuids[currentGuid] then + seenGuids[currentGuid] = true + + -- Test this target against all conditionals + if IsGuidValid("target", conditionals) then + return true -- Found matching target + end + end + end + + -- No match found via UnitXP scan - restore original target if we had one + if originalTargetGuid and UnitExists(originalTargetGuid) then + TargetUnit(originalTargetGuid) + elseif not originalTargetGuid then + ClearTarget() + end + end + return true end @@ -2145,6 +2299,37 @@ function CleveRoids.EquipBagItem(msg, offhand) 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 + 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 + end + end + if currentItemName and string.lower(currentItemName) == msgLower then + return true -- Already equipped + 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 @@ -2160,27 +2345,18 @@ function CleveRoids.EquipBagItem(msg, offhand) end end - local invslot = offhand and 17 or 16 - local throttleKey = invslot .. "_" .. (item.id or item.name) - - if UnitAffectingCombat("player") and (invslot == 16 or invslot == 17) then - local timeSinceLastSwap = now - CleveRoids.lastWeaponSwapTime - if timeSinceLastSwap < 1.5 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 - local currentItemLink = GetInventoryItemLink("player", invslot) + -- Double-check after item lookup that it's not already equipped if currentItemLink then local _, _, currentID = string.find(currentItemLink, "item:(%d+)") - local currentItemName = GetItemInfo(currentItemLink) - - if (currentID and item.id and tonumber(currentID) == tonumber(item.id)) or - (currentItemName and currentItemName == item.name) then + if currentID and item.id and tonumber(currentID) == tonumber(item.id) then CleveRoids.lastEquipTime[throttleKey] = now return true end @@ -2243,8 +2419,8 @@ function CleveRoids.EquipBagItem(msg, offhand) CleveRoids.equipInProgress = false - CleveRoids.IndexItems() - CleveRoids.lastItemIndexTime = now + -- PERFORMANCE: Removed IndexItems() call here + -- UNIT_INVENTORY_CHANGED event already triggers re-indexing with proper throttling return true end @@ -2422,13 +2598,8 @@ CleveRoids.lastCleanupTime = 0 CleveRoids.CLEANUP_INTERVAL = 5 -- Run cleanup every 5 seconds function CleveRoids.OnUpdate(self) - -- PERFORMANCE OPTIMIZATION: Only process equipment queue when it has items - -- This avoids unnecessary function calls and table length checks every frame - if CleveRoids.equipmentQueue and table.getn(CleveRoids.equipmentQueue) > 0 then - if CleveRoids.ProcessEquipmentQueue then - CleveRoids.ProcessEquipmentQueue() - end - end + -- PERFORMANCE: Equipment queue now processed by self-disabling equipQueueFrame + -- This eliminates per-frame checks when queue is empty -- Process deferred equipment index updates (for throttled UNIT_INVENTORY_CHANGED) if CleveRoids.equipIndexPendingTime then @@ -4661,19 +4832,39 @@ end SLASH_CLEAREQUIPQUEUE1 = "/clearequipqueue" SlashCmdList.CLEAREQUIPQUEUE = function() - CleveRoids.equipmentQueue = {} + -- Release all entries back to pool + for i = 1, CleveRoids.equipmentQueueLen do + local entry = CleveRoids.equipmentQueue[i] + if entry then + CleveRoids.equipmentQueue[i] = nil + -- Return to pool if space available + if table.getn(CleveRoids.queueEntryPool) < 10 then + entry.item = nil + entry.slotName = nil + entry.inventoryId = nil + table.insert(CleveRoids.queueEntryPool, entry) + end + end + end + CleveRoids.equipmentQueueLen = 0 + if CleveRoids.equipQueueFrame then + CleveRoids.equipQueueFrame:Hide() + end CleveRoids.Print("Equipment queue cleared") end SLASH_EQUIPQUEUESTATUS1 = "/equipqueuestatus" SlashCmdList.EQUIPQUEUESTATUS = function() - local count = table.getn(CleveRoids.equipmentQueue) + local count = CleveRoids.equipmentQueueLen CleveRoids.Print("Equipment queue has " .. count .. " pending items") - for i, entry in ipairs(CleveRoids.equipmentQueue) do - local itemName = entry.item.name or "Unknown" - local slotName = entry.slotName or "Unknown" - local retries = entry.retries or 0 - CleveRoids.Print(i .. ". " .. itemName .. " -> " .. slotName .. " (retries: " .. retries .. ")") + for i = 1, count do + local entry = CleveRoids.equipmentQueue[i] + if entry then + local itemName = (entry.item and entry.item.name) or "Unknown" + local slotName = entry.slotName or "Unknown" + local retries = entry.retries or 0 + CleveRoids.Print(i .. ". " .. itemName .. " -> " .. slotName .. " (retries: " .. retries .. ")") + end end end diff --git a/Extensions/Tooltip/Generic.lua b/Extensions/Tooltip/Generic.lua index 30f7e45..c68090a 100644 --- a/Extensions/Tooltip/Generic.lua +++ b/Extensions/Tooltip/Generic.lua @@ -5,6 +5,26 @@ local _G = _G or getfenv(0) local CleveRoids = _G.CleveRoids or {} +-- PERFORMANCE: Upvalues for frequently called functions +local GetTime = GetTime +local GetContainerItemLink = GetContainerItemLink +local GetContainerItemInfo = GetContainerItemInfo +local GetContainerNumSlots = GetContainerNumSlots +local GetInventoryItemLink = GetInventoryItemLink +local GetInventoryItemTexture = GetInventoryItemTexture +local GetInventoryItemCount = GetInventoryItemCount +local GetItemInfo = GetItemInfo +local GetSpellName = GetSpellName +local GetSpellTexture = GetSpellTexture +local tonumber = tonumber +local type = type +local pairs = pairs +local string_find = string.find +local string_lower = string.lower +local string_gsub = string.gsub +local table_insert = table.insert +local table_getn = table.getn + -- Indexes all spells the current player and pet knows function CleveRoids.IndexSpells() @@ -156,16 +176,21 @@ end function CleveRoids.IndexItems() local items = {} + local NUM_BAG_SLOTS = NUM_BAG_SLOTS -- Upvalue for bag constant + + -- Scan bags (reverse order to prefer first stack) for bagID = 0, NUM_BAG_SLOTS do - for slot = GetContainerNumSlots(bagID), 1, -1 do + local numSlots = GetContainerNumSlots(bagID) + for slot = numSlots, 1, -1 do local link = GetContainerItemLink(bagID, slot) if link then - local _, _, itemID = string.find(link, "item:(%d+)") - local name, link, _, _, itemType, itemSubType, _, _, texture = GetItemInfo(itemID) + local _, _, itemID = string_find(link, "item:(%d+)") + local name, itemLink, _, _, itemType, itemSubType, _, _, texture = GetItemInfo(itemID) if name then local _, count = GetContainerItemInfo(bagID, slot) - if not items[name] then + local existing = items[name] + if not existing then items[name] = { bagID = bagID, slot = slot, @@ -174,48 +199,50 @@ function CleveRoids.IndexItems() type = itemType, count = count, texture = texture, - link = link, + link = itemLink, bagSlots = {{bagID, slot}}, slotsIndex = 1, } items[itemID] = name - local lowerName = string.lower(name) + local lowerName = string_lower(name) if lowerName ~= name then items[lowerName] = name end else - items[name].count = (items[name].count or 0) + (count or 0) - table.insert(items[name].bagSlots, {bagID, slot}) + existing.count = (existing.count or 0) + (count or 0) + table_insert(existing.bagSlots, {bagID, slot}) end end end end end + -- Scan equipped items 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) + 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 + local existing = items[name] + if not existing then items[name] = { inventoryID = inventoryID, id = itemID, name = name, count = count, texture = texture, - link = link, + link = itemLink, } items[itemID] = name - local lowerName = string.lower(name) + local lowerName = string_lower(name) if lowerName ~= name then items[lowerName] = name end else - items[name].inventoryID = inventoryID - items[name].count = (items[name].count or 0) + (count or 0) + existing.inventoryID = inventoryID + existing.count = (existing.count or 0) + (count or 0) end end end @@ -344,148 +371,141 @@ function CleveRoids.GetTalent(text) return CleveRoids.Talents[text] end +-- PERFORMANCE: Helper functions defined once (not inline per-call) +local function makeInventoryItem(inventoryID, link, Items) + if not link then link = GetInventoryItemLink("player", inventoryID) end + if not link then return end + + local _, _, itemID = string_find(link, "item:(%d+)") + itemID = itemID and tonumber(itemID) or nil + + local name = itemID and GetItemInfo(itemID) or nil + local texture = GetInventoryItemTexture("player", inventoryID) + local count = GetInventoryItemCount("player", inventoryID) + + local it = { + inventoryID = inventoryID, + id = itemID, + name = name, + texture = texture, + link = link, + count = count + } + if name then + Items[name] = it + local lowerName = string_lower(name) + if lowerName ~= name then + Items[lowerName] = name + end + end + if itemID then Items[itemID] = name end + return it +end + +local function makeBagItem(bagID, slot, link, Items) + if not link then + link = GetContainerItemLink(bagID, slot) + end + if not link then return end + + local _, _, itemID = string_find(link, "item:(%d+)") + itemID = itemID and tonumber(itemID) or nil + + local name, _, _, _, _, _, _, _, texture = GetItemInfo(itemID) + local count = 0 + local tex, itemCount = GetContainerItemInfo(bagID, slot) + if itemCount then count = itemCount end + if not texture then texture = tex end + + local it = { + bagID = bagID, + slot = slot, + id = itemID, + name = name, + texture = texture, + link = link, + count = count, + bagSlots = { { bagID, slot } }, + slotsIndex = 1 + } + if name then + Items[name] = it + local lowerName = string_lower(name) + if lowerName ~= name then + Items[lowerName] = name + end + end + if itemID then Items[itemID] = name end + return it +end + function CleveRoids.GetItem(text) if not text or text == "" then return end - local item = CleveRoids.Items[text] or CleveRoids.Items[tostring(text)] + local Items = CleveRoids.Items + local item = Items[text] or Items[tostring(text)] if not item then - local lowerText = string.lower(text) - local canonicalName = CleveRoids.Items[lowerText] + local lowerText = string_lower(text) + local canonicalName = Items[lowerText] if canonicalName and type(canonicalName) == "string" then - item = CleveRoids.Items[canonicalName] + item = Items[canonicalName] end end - + if item then if type(item) == "table" then return item else - return CleveRoids.Items[tostring(item)] + return Items[tostring(item)] end end - local function makeInv(inventoryID, link) - if not link then link = GetInventoryItemLink("player", inventoryID) end - if not link then return end - - local _, _, itemID = string.find(link, "item:(%d+)") - itemID = itemID and tonumber(itemID) or nil - - local name = itemID and GetItemInfo(itemID) or nil - local texture = GetInventoryItemTexture("player", inventoryID) - local count = GetInventoryItemCount("player", inventoryID) - - local it = { - inventoryID = inventoryID, - id = itemID, - name = name, - texture = texture, - link = link, - count = count - } - if name then - CleveRoids.Items[name] = it - local lowerName = string.lower(name) - if lowerName ~= name then - CleveRoids.Items[lowerName] = name - end - end - if itemID then CleveRoids.Items[itemID] = name end - return it - end - - local function makeBag(bagID, slot, link) - if not link and GetContainerItemLink then - link = GetContainerItemLink(bagID, slot) - end - if not link then return end - - local _, _, itemID = string.find(link, "item:(%d+)") - itemID = itemID and tonumber(itemID) or nil - - local name, _, _, _, _, _, _, _, texture = GetItemInfo(itemID or text) - local count = 0 - if GetContainerItemInfo then - local tex, itemCount = GetContainerItemInfo(bagID, slot) - if itemCount then count = itemCount end - if not texture then texture = tex end - end - - local it = { - bagID = bagID, - slot = slot, - id = itemID, - name = name, - texture = texture, - link = link, - count = count, - bagSlots = { { bagID, slot } }, - slotsIndex = 1 - } - if name then - CleveRoids.Items[name] = it - local lowerName = string.lower(name) - if lowerName ~= name then - CleveRoids.Items[lowerName] = name - end - end - if itemID then CleveRoids.Items[itemID] = name end - return it - end - local qid = tonumber(text) - local qname = (type(text) == "string") and string.lower(text) or nil + local qname = (type(text) == "string") and string_lower(text) or nil + -- Direct inventory slot lookup by ID if qid and qid >= 1 and qid <= 19 then - local it = makeInv(qid) + local it = makeInventoryItem(qid, nil, Items) if it then return it end end - local inv = 1 - while inv <= 19 do + -- Scan equipped items + for inv = 1, 19 do local link = GetInventoryItemLink("player", inv) if link then - local _, _, itemID = string.find(link, "item:(%d+)") + local _, _, itemID = string_find(link, "item:(%d+)") itemID = itemID and tonumber(itemID) or nil if qid and itemID and qid == itemID then - local it = makeInv(inv, link) - if it then return it end + return makeInventoryItem(inv, link, Items) elseif qname and itemID then local nm = GetItemInfo(itemID) - if nm and string.lower(nm) == qname then - local it = makeInv(inv, link) - if it then return it end + if nm and string_lower(nm) == qname then + return makeInventoryItem(inv, link, Items) end end end - inv = inv + 1 end - local bag = 0 - while bag <= 4 do - local slots = (GetContainerNumSlots and GetContainerNumSlots(bag)) or 0 - local slot = 1 - while slot <= slots do - local link = GetContainerItemLink and GetContainerItemLink(bag, slot) + -- Scan bags + for bag = 0, 4 do + local slots = GetContainerNumSlots(bag) or 0 + for slot = 1, slots do + local link = GetContainerItemLink(bag, slot) if link then - local _, _, itemID = string.find(link, "item:(%d+)") + local _, _, itemID = string_find(link, "item:(%d+)") itemID = itemID and tonumber(itemID) or nil if qid and itemID and qid == itemID then - local it = makeBag(bag, slot, link) - if it then return it end + return makeBagItem(bag, slot, link, Items) elseif qname and itemID then local nm = GetItemInfo(itemID) - if nm and string.lower(nm) == qname then - local it = makeBag(bag, slot, link) - if it then return it end + if nm and string_lower(nm) == qname then + return makeBagItem(bag, slot, link, Items) end end end - slot = slot + 1 end - bag = bag + 1 end local name, link, _, _, _, _, _, _, texture = GetItemInfo(text) diff --git a/Init.lua b/Init.lua index 41d1bd6..71ac5a2 100644 --- a/Init.lua +++ b/Init.lua @@ -96,11 +96,15 @@ CleveRoids.dynamicCmds = { -- Equipment swap queue system CleveRoids.equipmentQueue = {} +CleveRoids.equipmentQueueLen = 0 -- PERFORMANCE: Track length to avoid table.getn() every frame CleveRoids.lastEquipTime = {} CleveRoids.lastGlobalEquipTime = 0 CleveRoids.EQUIP_COOLDOWN = 1.5 -- Per-slot cooldown CleveRoids.EQUIP_GLOBAL_COOLDOWN = 0.5 -- Global cooldown +-- PERFORMANCE: Table pool for queue entries to reduce garbage collection +CleveRoids.queueEntryPool = {} + -- Spell queue state (Nampower) CleveRoids.queuedSpell = nil CleveRoids.lastCastSpell = nil