6 Commits
Author SHA1 Message Date
Brues 40cf2c8010 More ClassicAPI porting 2026-07-15 19:45:31 -05:00
Brues a5a0b9c00a Adopt ClassicAPI C_Item.GetItemName in Utility.lua equip-modifier debug
Replace the debug-only GetInventoryItemLink name scrape with the
decorated C_Item.GetItemName location form.
2026-07-15 19:36:18 -05:00
Brues 6e57ff01dc Adopt ClassicAPI C_Item item reads in Tooltip/Generic.lua indexers
Migrate the link-scrape-then-discard sites to direct ClassicAPI reads:

- IndexEquippedItems / IndexEquipSlot: GetInventoryItemID for the id
  (GetItemInfo still supplies the stored base link/texture)
- IndexItems bag+equip scans: GetContainerItemID/GetInventoryItemID +
  C_Item.GetItemName (decorated) for the dedup fast-path; drop the now
  dead GetContainerItemLink/GetInventoryItemLink locals
- GetItemFast cache validation: alloc-free id compare via
  GetInventoryItemID/GetContainerItemID, decorated-name compare otherwise
- IsItemEquipped: GetInventoryItemID + C_Item.GetItemName

Left untouched: makeInventoryItem/makeBagItem and the GetItem/GetItemFast
scan loops, which build a real link that is stored on the item and later
fed to GameTooltip:SetHyperlink.

Note: the equipped-scan loops now start at slot 1 (GetInventoryItemID
requires slot>=1), so the ammo slot (0) is no longer walked by these
indexers -- ammo is not referenced by name anywhere in the addon.
2026-07-15 19:33:07 -05:00
Brues 9cd79d682d Adopt ClassicAPI C_Item item reads in Conditionals.lua
Replace GetInventoryItemLink/GetContainerItemLink link-scraping with
direct ClassicAPI reads across the item conditionals:

- [equipped] cache build: GetInventoryItemID + C_Item.GetItemName
  (decorated name replaces the old bracket-name / GetItemInfo two-step)
- slot-number -> item name resolves ([cd], [usable]/[nousable]):
  C_Item.GetItemName location form
- HasItem / GetItemCooldown substring fallbacks: match the decorated
  name instead of the raw link string
- numeric-slot presence check: GetInventoryItemID

Suffix decoration is preserved via the location form, so partial/
suffixed [equipped:...] matches behave as before.
2026-07-15 19:17:00 -05:00
Brues f54eda5ae3 Adopt ClassicAPI C_Item/C_Container item reads in Core.lua
Replace GetInventoryItemLink/GetContainerItemLink link-scraping (built
solely to regex out item:ID or the bracketed name) with direct ClassicAPI
reads:

- id/presence: GetInventoryItemID, C_Container.GetContainerItemID
- suffix-sensitive names (equip-by-name on gear): C_Item.GetItemName
  location form, which now carries random-suffix decoration
- suffix-free names (consumables/reagents/poisons) in hot bag scans:
  id + C_Item.GetItemNameByID (base name, zero table allocation)

Also covers /use equipped-slot resolution and the WDB warm scans. Drops
the now-dead GetContainerItemLink/GetInventoryItemLink/string_find
upvalue aliases. No behavior change; equip-by-name keeps full suffixed
matching via the decorated location form.
2026-07-15 19:12:56 -05:00
Brues 653f8db3d7 Reconcile /startattack against real action-bar state, not cached flag
The autoAttack flag could drift stale-true (optimistic set after
AttackTarget, or a target dying without PLAYER_LEAVE_COMBAT), making
/startattack skip the attack until the target was dropped and reselected.
The old fallback called the overridden IsCurrentAction, which just echoes
the cached flag for the attack slot, so it never detected drift. Use the
original Hooks.IsCurrentAction as ground truth and sync the flag to it.
2026-07-04 19:38:37 -05:00
4 changed files with 208 additions and 389 deletions
+41 -69
View File
@@ -273,24 +273,17 @@ local function BuildEquipmentCache()
end end
end end
-- Fallback: manual slot enumeration -- Fallback: manual slot enumeration via ClassicAPI (id + decorated name),
-- no link string built. C_Item.GetItemName carries random-suffix decoration
-- and falls back to the base name internally, so it replaces the old
-- bracket-name / GetItemInfo two-step in a single call.
for slot = 1, 19 do for slot = 1, 19 do
local link = GetInventoryItemLink("player", slot) local id = GetInventoryItemID("player", slot)
if link then if id then
local _, _, id = string_find(link, "item:(%d+)") _equippedItemIDs[slot] = id
local _, _, nameInBrackets = string_find(link, "%[(.+)%]") local name = C_Item.GetItemName({ equipmentSlotIndex = slot })
if name then
if id then _equippedItemNames[slot] = string_lower(name)
_equippedItemIDs[slot] = tonumber(id)
end
if nameInBrackets then
_equippedItemNames[slot] = string_lower(nameInBrackets)
elseif id then
-- Fallback: resolve via GetItemInfo
local itemName = GetItemInfo(tonumber(id))
if itemName then
_equippedItemNames[slot] = string_lower(itemName)
end
end end
end end
end end
@@ -350,8 +343,7 @@ function CleveRoids.FindItemLocation(item)
if numericItem then if numericItem then
-- Check if it's an equipment slot (1-19) -- Check if it's an equipment slot (1-19)
if numericItem >= 1 and numericItem <= 19 then if numericItem >= 1 and numericItem <= 19 then
local link = GetInventoryItemLink("player", numericItem) if GetInventoryItemID("player", numericItem) then
if link then
return { type = "inventory", inventoryID = numericItem } return { type = "inventory", inventoryID = numericItem }
end end
return nil return nil
@@ -3466,11 +3458,8 @@ function CleveRoids.ValidateCooldown(args, ignoreGCD)
-- If this is a numeric slot (1-19), resolve to the equipped item's name -- If this is a numeric slot (1-19), resolve to the equipped item's name
local slotNum = tonumber(name) local slotNum = tonumber(name)
if slotNum and slotNum >= 1 and slotNum <= 19 then if slotNum and slotNum >= 1 and slotNum <= 19 then
local link = GetInventoryItemLink("player", slotNum) local itemName = C_Item.GetItemName({ equipmentSlotIndex = slotNum })
if link then if itemName then name = itemName end
local _, _, itemName = string.find(link, "%[(.+)%]")
if itemName then name = itemName end
end
end end
args = {name = name} args = {name = name}
else else
@@ -3481,11 +3470,8 @@ function CleveRoids.ValidateCooldown(args, ignoreGCD)
-- If this is a numeric slot (1-19), resolve to the equipped item's name -- If this is a numeric slot (1-19), resolve to the equipped item's name
local slotNum = tonumber(name) local slotNum = tonumber(name)
if slotNum and slotNum >= 1 and slotNum <= 19 then if slotNum and slotNum >= 1 and slotNum <= 19 then
local link = GetInventoryItemLink("player", slotNum) local itemName = C_Item.GetItemName({ equipmentSlotIndex = slotNum })
if link then if itemName then name = itemName end
local _, _, itemName = string.find(link, "%[(.+)%]")
if itemName then name = itemName end
end
end end
args.name = name args.name = name
else else
@@ -4869,13 +4855,11 @@ function CleveRoids.HasItem(item)
if type(item) == "string" and item ~= "" then if type(item) == "string" and item ~= "" then
local itemLower = string.lower(item) local itemLower = string.lower(item)
-- Check equipped slots for substring match -- Check equipped slots for substring match (decorated name, no link build)
for slot = 0, 19 do for slot = 1, 19 do
local link = GetInventoryItemLink("player", slot) local name = C_Item.GetItemName({ equipmentSlotIndex = slot })
if link then if name and string.find(string.lower(name), itemLower, 1, true) then
if string.find(string.lower(link), itemLower, 1, true) then return true
return true
end
end end
end end
@@ -4884,11 +4868,9 @@ function CleveRoids.HasItem(item)
local size = GetContainerNumSlots(bag) local size = GetContainerNumSlots(bag)
if size and size > 0 then if size and size > 0 then
for slotIndex = 1, size do for slotIndex = 1, size do
local link = GetContainerItemLink(bag, slotIndex) local name = C_Item.GetItemName({ bagID = bag, slotIndex = slotIndex })
if link then if name and string.find(string.lower(name), itemLower, 1, true) then
if string.find(string.lower(link), itemLower, 1, true) then return true
return true
end
end end
end end
end end
@@ -4933,14 +4915,12 @@ function CleveRoids.GetItemCooldown(item)
local itemLower = string.lower(item) local itemLower = string.lower(item)
local start, dur, en local start, dur, en
-- Check equipped slots for substring match -- Check equipped slots for substring match (decorated name, no link build)
for slot = 0, 19 do for slot = 1, 19 do
local link = GetInventoryItemLink("player", slot) local name = C_Item.GetItemName({ equipmentSlotIndex = slot })
if link then if name and string.find(string.lower(name), itemLower, 1, true) then
if string.find(string.lower(link), itemLower, 1, true) then start, dur, en = GetInventoryItemCooldown("player", slot)
start, dur, en = GetInventoryItemCooldown("player", slot) return _norm(start, dur, en)
return _norm(start, dur, en)
end
end end
end end
@@ -4949,12 +4929,10 @@ function CleveRoids.GetItemCooldown(item)
local size = GetContainerNumSlots(bag) local size = GetContainerNumSlots(bag)
if size and size > 0 then if size and size > 0 then
for slotIndex = 1, size do for slotIndex = 1, size do
local link = GetContainerItemLink(bag, slotIndex) local name = C_Item.GetItemName({ bagID = bag, slotIndex = slotIndex })
if link then if name and string.find(string.lower(name), itemLower, 1, true) then
if string.find(string.lower(link), itemLower, 1, true) then start, dur, en = GetContainerItemCooldown(bag, slotIndex)
start, dur, en = GetContainerItemCooldown(bag, slotIndex) return _norm(start, dur, en)
return _norm(start, dur, en)
end
end end
end end
end end
@@ -5887,13 +5865,10 @@ CleveRoids.Keywords = {
local itemName = name local itemName = name
local slotNum = tonumber(name) local slotNum = tonumber(name)
if slotNum and slotNum >= 1 and slotNum <= 19 then if slotNum and slotNum >= 1 and slotNum <= 19 then
-- Resolve slot number to item name -- Resolve slot number to item name (decorated, no link build)
local link = GetInventoryItemLink("player", slotNum) local extractedName = C_Item.GetItemName({ equipmentSlotIndex = slotNum })
if link then if extractedName then
local _, _, extractedName = string.find(link, "%[(.+)%]") itemName = extractedName
if extractedName then
itemName = extractedName
end
end end
end end
@@ -5925,13 +5900,10 @@ CleveRoids.Keywords = {
local itemName = name local itemName = name
local slotNum = tonumber(name) local slotNum = tonumber(name)
if slotNum and slotNum >= 1 and slotNum <= 19 then if slotNum and slotNum >= 1 and slotNum <= 19 then
-- Resolve slot number to item name -- Resolve slot number to item name (decorated, no link build)
local link = GetInventoryItemLink("player", slotNum) local extractedName = C_Item.GetItemName({ equipmentSlotIndex = slotNum })
if link then if extractedName then
local _, _, extractedName = string.find(link, "%[(.+)%]") itemName = extractedName
if extractedName then
itemName = extractedName
end
end end
end end
+116 -235
View File
@@ -46,10 +46,8 @@ CleveRoids.spellNameCache = {}
local GetTime = GetTime local GetTime = GetTime
local UnitExists = UnitExists local UnitExists = UnitExists
local UnitAffectingCombat = UnitAffectingCombat local UnitAffectingCombat = UnitAffectingCombat
local GetContainerItemLink = GetContainerItemLink
local GetContainerItemInfo = GetContainerItemInfo local GetContainerItemInfo = GetContainerItemInfo
local GetContainerNumSlots = GetContainerNumSlots local GetContainerNumSlots = GetContainerNumSlots
local GetInventoryItemLink = GetInventoryItemLink
local GetItemInfo = GetItemInfo local GetItemInfo = GetItemInfo
local PickupContainerItem = PickupContainerItem local PickupContainerItem = PickupContainerItem
local PickupInventoryItem = PickupInventoryItem local PickupInventoryItem = PickupInventoryItem
@@ -63,7 +61,6 @@ local ipairs = ipairs
local type = type local type = type
local tonumber = tonumber local tonumber = tonumber
local tostring = tostring local tostring = tostring
local string_find = string.find
local string_lower = string.lower local string_lower = string.lower
local string_gsub = string.gsub local string_gsub = string.gsub
local table_insert = table.insert local table_insert = table.insert
@@ -672,21 +669,27 @@ function CleveRoids.GetReagentCount(reagentName)
for slot = 1, slots do for slot = 1, slots do
local _, count = GetContainerItemInfo(bag, slot) local _, count = GetContainerItemInfo(bag, slot)
count = count or 0 count = count or 0
local link = GetContainerItemLink and GetContainerItemLink(bag, slot) -- Base itemID straight off the slot (no link string, no regex).
if link then local id = C_Container.GetContainerItemID(bag, slot)
local _, _, idstr = string.find(link, "item:(%d+)") if id then
local id = idstr and tonumber(idstr) or nil local match
if (wantId and id == wantId) or (not wantId and string.find(link, "%["..reagentName.."%]")) then if wantId then
total = total + count match = (id == wantId)
else
local name = C_Item.GetItemNameByID(id)
if name then
match = (name == reagentName)
else
-- Name not cached yet: fall back to a tooltip scan (expensive, rare)
local tip = CRM_GetBagScanTip()
tip:ClearLines()
tip:SetBagItem(bag, slot)
local left1 = _G[tip:GetName().."TextLeft1"]
local tname = left1 and left1:GetText()
match = (tname == reagentName)
end
end end
else if match then
-- Fallback: scan bag slot tooltip for the name (expensive, only when no link)
local tip = CRM_GetBagScanTip()
tip:ClearLines()
tip:SetBagItem(bag, slot)
local left1 = _G[tip:GetName().."TextLeft1"]
local name = left1 and left1:GetText()
if name and name == reagentName then
total = total + count total = total + count
end end
end end
@@ -701,17 +704,18 @@ end
function CleveRoids.GetLiveItemCount(itemName) function CleveRoids.GetLiveItemCount(itemName)
if not itemName or itemName == "" then return 0 end if not itemName or itemName == "" then return 0 end
-- Escape Lua pattern special chars in item name to avoid crashes -- Escape Lua pattern special chars in item name to avoid crashes
local escaped = string.gsub(itemName, "([%(%)%.%%%+%-%*%?%[%]%^%$])", "%%%1") local wantLower = string.lower(itemName)
local pattern = "%[" .. escaped .. "%]"
local lowerPattern = string.lower(pattern)
local total = 0 local total = 0
for bag = 0, 4 do for bag = 0, 4 do
local slots = GetContainerNumSlots(bag) or 0 local slots = GetContainerNumSlots(bag) or 0
for slot = 1, slots do for slot = 1, slots do
local link = GetContainerItemLink and GetContainerItemLink(bag, slot) local id = C_Container.GetContainerItemID(bag, slot)
if link and string.find(string.lower(link), lowerPattern) then if id then
local _, count = GetContainerItemInfo(bag, slot) local name = C_Item.GetItemNameByID(id)
total = total + (count or 0) if name and string.lower(name) == wantLower then
local _, count = GetContainerItemInfo(bag, slot)
total = total + (count or 0)
end
end end
end end
end end
@@ -2689,11 +2693,13 @@ function CleveRoids.DoWithConditionals(msg, hook, fixEmptyTargetFunc, targetBefo
CastSpellByName(castMsg) CastSpellByName(castMsg)
end end
else else
-- For other actions like UseContainerItem etc. -- For other actions like item use etc. Pass the resolved unit token so
-- item-use can target it directly (DoUse -> C_Item.UseItemByName); action
-- closures that only take (msg) simply ignore the extra arg.
if CleveRoids.equipDebugLog then if CleveRoids.equipDebugLog then
CleveRoids.Print("|cff00ff00[EquipLog] Calling action('" .. tostring(msg) .. "')|r") CleveRoids.Print("|cff00ff00[EquipLog] Calling action('" .. tostring(msg) .. "')|r")
end end
action(msg) action(msg, conditionals.target)
end end
end end
@@ -3100,15 +3106,17 @@ end
-- PERFORMANCE: Module-level action to avoid closure allocation per call -- PERFORMANCE: Module-level action to avoid closure allocation per call
local function _startAttackAction() local function _startAttackAction()
if not UnitExists("target") or CleveRoids.IsUnitDead("target") then TargetNearestEnemy() end if not UnitExists("target") or CleveRoids.IsUnitDead("target") then TargetNearestEnemy() end
-- Check both event-based flag AND action bar state for reliable detection -- Ground truth is the real action-bar state, NOT the cached autoAttack flag.
-- The flag can drift stale-true (e.g. set optimistically after AttackTarget below,
-- or a target dying without PLAYER_LEAVE_COMBAT firing), which would make us wrongly
-- believe we're already swinging and skip the attack. Use the ORIGINAL API here:
-- the overridden global IsCurrentAction just echoes the cached flag for the attack
-- slot, so it can't detect drift. Fall back to the flag only if the slot is unknown.
local isAttacking = CleveRoids.CurrentSpell.autoAttack local isAttacking = CleveRoids.CurrentSpell.autoAttack
if not isAttacking then local slot = CleveRoids.GetProxyActionSlot(CleveRoids.Localized.Attack)
-- Fallback: check action bar state via IsCurrentAction if slot then
local slot = CleveRoids.GetProxyActionSlot(CleveRoids.Localized.Attack) isAttacking = CleveRoids.Hooks.IsCurrentAction(slot) and true or false
if slot and IsCurrentAction(slot) then CleveRoids.CurrentSpell.autoAttack = isAttacking
CleveRoids.CurrentSpell.autoAttack = true
isAttacking = true
end
end end
if not isAttacking and not CleveRoids.CurrentSpell.autoAttackLock and UnitExists("target") and UnitCanAttack("player", "target") then if not isAttacking and not CleveRoids.CurrentSpell.autoAttackLock and UnitExists("target") and UnitCanAttack("player", "target") then
CleveRoids.CurrentSpell.autoAttackLock = true CleveRoids.CurrentSpell.autoAttackLock = true
@@ -3194,6 +3202,36 @@ function CleveRoids.DoConditionalClearTarget(msg)
return false return false
end end
-- Resolve an EQUIPPED inventory slot (1-19) holding the item, or nil.
-- C_Item.UseItemByName only searches bags, so equipped-only items (trinkets,
-- weapons) must be fired via their slot. Prefer nampower's fast lookup; fall
-- back to a manual scan for clients without FindPlayerItemSlot (< v2.18).
local function FindEquippedItemSlot(msg, itemId)
local API = CleveRoids.NampowerAPI
if type(API) == "table" and API.FindItemFast then
local info = API.FindItemFast(itemId or msg)
if info and info.inventoryID then return info.inventoryID end
-- Found only in bags (or not at all) -> not an equipped-only item.
if info then return nil end
end
-- Fallback scan of equipped slots by ID or name, read straight from each
-- slot via ClassicAPI (GetInventoryItemID / C_Item.GetItemNameByID) -- no
-- link string built, no "item:"/"|h[..]|h" parse. Slots are 1-19.
local wantLower = (not itemId) and string_lower(msg) or nil
for slot = 1, 19 do
local id = GetInventoryItemID("player", slot)
if id then
if itemId then
if id == itemId then return slot end
else
local nm = C_Item.GetItemNameByID(id)
if nm and string_lower(nm) == wantLower then return slot end
end
end
end
return nil
end
-- Attempts to use or equip an item by a set of conditionals -- Attempts to use or equip an item by a set of conditionals
-- Also checks if a condition is a spell so that you can mix item and spell use -- Also checks if a condition is a spell so that you can mix item and spell use
-- msg: The raw message intercepted from a /use or /equip command -- msg: The raw message intercepted from a /use or /equip command
@@ -3205,184 +3243,42 @@ function CleveRoids.DoUse(msg)
local handled = false local handled = false
local action = function(msg) local action = function(msg, unit)
-- Defensive: make sure we are not in "split stack" mode and nothing is on the cursor -- Defensive: make sure we are not in "split stack" mode and nothing is on the cursor
if type(CloseStackSplitFrame) == "function" then CloseStackSplitFrame() end if type(CloseStackSplitFrame) == "function" then CloseStackSplitFrame() end
if CursorHasItem and CursorHasItem() then ClearCursor() end if CursorHasItem and CursorHasItem() then ClearCursor() end
-- Try to interpret the message as a direct inventory slot ID first. -- Only pass a cast target when it actually exists, so a stale @unit doesn't
-- waste a consumable. Self-use items (potions/food/hearth) ignore it anyway.
local useUnit = (unit and UnitExists(unit)) and unit or nil
-- Direct equipped inventory slot ID (1-19): use it in place.
local slotId = tonumber(msg) local slotId = tonumber(msg)
if slotId and slotId >= 1 and slotId <= 19 then -- Character slots are 1-19 if slotId and slotId >= 1 and slotId <= 19 then
ClearCursor() -- extra safety before using equipped items ClearCursor() -- extra safety before using equipped items
UseInventoryItem(slotId) UseInventoryItem(slotId)
return return
end end
-- Try to interpret as item ID (numbers > 19) -- Equipped items (trinkets, weapons) live outside bags, so C_Item.UseItemByName
-- v2.18+: Use FindPlayerItemSlot directly for item IDs (no name resolution needed) -- can't reach them. Fire an equipped match through its slot first.
if slotId and slotId > 19 then local invSlot = FindEquippedItemSlot(msg, slotId)
local API = CleveRoids.NampowerAPI if invSlot then
-- v2.18+: Native lookup can find item directly by ID ClearCursor()
if API and API.features and API.features.hasFindPlayerItemSlot then if CleveRoids.equipDebugLog then
local itemInfo = API.FindItemFast(slotId) CleveRoids.Print("|cff888888[UseLog] /use " .. msg .. " via UseInventoryItem(" .. invSlot .. ")|r")
if itemInfo then
ClearCursor()
if itemInfo.inventoryID then
if CleveRoids.equipDebugLog then
CleveRoids.Print("|cff888888[UseLog] /use " .. slotId .. " via UseInventoryItem(" .. itemInfo.inventoryID .. ") [v2.18 ID lookup]|r")
end
UseInventoryItem(itemInfo.inventoryID)
return
elseif itemInfo.bagID and itemInfo.slot then
if CleveRoids.equipDebugLog then
CleveRoids.Print("|cff888888[UseLog] /use " .. slotId .. " via UseContainerItem(" .. itemInfo.bagID .. "," .. itemInfo.slot .. ") [v2.18 ID lookup]|r")
end
UseContainerItem(itemInfo.bagID, itemInfo.slot)
return
end
end
-- Item not found by ID - fail
if CleveRoids.equipDebugLog then
CleveRoids.Print("|cffff8800[UseLog] Item ID " .. slotId .. " not found in inventory [v2.18]|r")
end
return
end
-- Fallback: Resolve item ID to name for legacy lookup
local itemName = nil
if API and API.GetItemName then
itemName = API.GetItemName(slotId)
end
-- Fall back to GetItemInfo
if not itemName and GetItemInfo then
itemName = GetItemInfo(slotId)
end
if itemName then
if CleveRoids.equipDebugLog then
CleveRoids.Print("|cff888888[UseLog] Resolved item ID " .. slotId .. " to '" .. itemName .. "'|r")
end
msg = itemName -- Replace ID with name for subsequent lookups
else
if CleveRoids.equipDebugLog then
CleveRoids.Print("|cffff8800[UseLog] Could not resolve item ID " .. slotId .. " - item not in cache|r")
end
-- Item not in client cache - can't resolve without seeing it first
return
end
end
-- v2.18+: Use native fast lookup (much faster than Lua cache + scan)
local API = CleveRoids.NampowerAPI
if API and API.features and API.features.hasFindPlayerItemSlot then
local itemInfo = API.FindItemFast(msg)
if itemInfo then
ClearCursor()
if itemInfo.inventoryID then
if CleveRoids.equipDebugLog then
CleveRoids.Print("|cff888888[UseLog] /use " .. msg .. " via UseInventoryItem(" .. itemInfo.inventoryID .. ") [v2.18 native]|r")
end
UseInventoryItem(itemInfo.inventoryID)
return
elseif itemInfo.bagID and itemInfo.slot then
if CleveRoids.equipDebugLog then
CleveRoids.Print("|cff888888[UseLog] /use " .. msg .. " via UseContainerItem(" .. itemInfo.bagID .. "," .. itemInfo.slot .. ") [v2.18 native]|r")
end
UseContainerItem(itemInfo.bagID, itemInfo.slot)
return
end
end
-- v2.18 lookup didn't find item - fall through to legacy path
-- (might be partial match or different case that native doesn't handle)
end
-- PERFORMANCE: Try cache lookup first (O(1) instead of O(n) scan)
-- IMPORTANT: Validate cache hits to prevent stale data during combat
-- (IndexItems() is skipped during combat, so cache may have old bag/slot locations)
local location = CleveRoids.FindItemLocation(msg)
if location then
local cacheValid = false
local qname = string_lower(msg)
if location.type == "inventory" then
-- Validate: check if this slot actually contains the item we want
local link = GetInventoryItemLink("player", location.inventoryID)
if link then
local _, _, nm = string_find(link, "|h%[(.-)%]|h")
if nm and string_lower(nm) == qname then
cacheValid = true
end
end
else
-- Validate: check if this bag slot actually contains the item we want
local link = GetContainerItemLink(location.bag, location.slot)
if link then
local _, _, nm = string_find(link, "|h%[(.-)%]|h")
if nm and string_lower(nm) == qname then
cacheValid = true
end
end
end
if cacheValid then
ClearCursor()
if location.type == "inventory" then
if CleveRoids.equipDebugLog then
CleveRoids.Print("|cff888888[UseLog] /use " .. msg .. " via UseInventoryItem(" .. location.inventoryID .. ") [cached]|r")
end
UseInventoryItem(location.inventoryID)
else
if CleveRoids.equipDebugLog then
CleveRoids.Print("|cff888888[UseLog] /use " .. msg .. " via UseContainerItem(" .. location.bag .. "," .. location.slot .. ") [cached]|r")
end
UseContainerItem(location.bag, location.slot)
end
return
elseif CleveRoids.equipDebugLog then
CleveRoids.Print("|cff888888[UseLog] /use " .. msg .. " - cache STALE, falling back to scan|r")
end
end
-- Slow path fallback: full scan for substring matches or cache miss
local qname = string_lower(msg)
-- Search equipped inventory slots first (for trinkets, etc.)
for slot = 0, 19 do
local link = GetInventoryItemLink("player", slot)
if link then
local _, _, nm = string_find(link, "|h%[(.-)%]|h")
if nm and string_lower(nm) == qname then
if CleveRoids.equipDebugLog then
CleveRoids.Print("|cff888888[UseLog] /use " .. msg .. " via UseInventoryItem(" .. slot .. ")|r")
end
ClearCursor()
UseInventoryItem(slot)
return
end
end
end
-- Then search bags
for bag = 0, 4 do
local numSlots = GetContainerNumSlots(bag) or 0
for bagSlot = 1, numSlots do
local link = GetContainerItemLink(bag, bagSlot)
if link then
local _, _, nm = string_find(link, "|h%[(.-)%]|h")
if nm and string_lower(nm) == qname then
if CleveRoids.equipDebugLog then
CleveRoids.Print("|cff888888[UseLog] /use " .. msg .. " via UseContainerItem(" .. bag .. "," .. bagSlot .. ")|r")
end
ClearCursor()
UseContainerItem(bag, bagSlot)
return
end
end
end end
UseInventoryItem(invSlot)
return
end end
-- Bag path: one ClassicAPI call finds the item in bags and dispatches by
-- type (potion/food/scroll/on-use), honoring `useUnit` for targeted-spell
-- items. itemIDs pass as numbers; names/links pass through unchanged.
if CleveRoids.equipDebugLog then if CleveRoids.equipDebugLog then
CleveRoids.Print("|cff888888[UseLog] /use " .. msg .. " - not found in equipped slots or bags|r") CleveRoids.Print("|cff888888[UseLog] /use " .. msg .. " via C_Item.UseItemByName(" .. tostring(slotId or msg) .. ", " .. tostring(useUnit) .. ")|r")
end end
C_Item.UseItemByName(slotId or msg, useUnit)
end end
-- PERFORMANCE: Use numeric iteration to avoid pairs() iterator allocation -- PERFORMANCE: Use numeric iteration to avoid pairs() iterator allocation
@@ -3408,12 +3304,9 @@ local function FindItemInBagsByName(itemName)
for bag = 0, 4 do for bag = 0, 4 do
local numSlots = GetContainerNumSlots(bag) or 0 local numSlots = GetContainerNumSlots(bag) or 0
for slot = 1, numSlots do for slot = 1, numSlots do
local link = GetContainerItemLink(bag, slot) local name = C_Item.GetItemName({ bagID = bag, slotIndex = slot })
if link then if name and string_lower(name) == lowerName then
local _, _, name = string_find(link, "|h%[(.-)%]|h") return bag, slot
if name and string_lower(name) == lowerName then
return bag, slot
end
end end
end end
end end
@@ -3550,12 +3443,7 @@ function CleveRoids.EquipBagItem(msg, slotOrOffhand)
end end
-- Note what's currently in the target slot so we can invalidate its cache -- Note what's currently in the target slot so we can invalidate its cache
local oldSlotLink = GetInventoryItemLink("player", invslot) local oldSlotName = C_Item.GetItemName({ equipmentSlotIndex = invslot })
local oldSlotName = nil
if oldSlotLink then
local _, _, name = string_find(oldSlotLink, "|h%[(.-)%]|h")
oldSlotName = name
end
-- Helper to invalidate displaced item's cache -- Helper to invalidate displaced item's cache
local function InvalidateDisplacedItem() local function InvalidateDisplacedItem()
@@ -3572,10 +3460,9 @@ function CleveRoids.EquipBagItem(msg, slotOrOffhand)
local pairedSlots = {[13] = 14, [14] = 13, [16] = 17, [17] = 16, [11] = 12, [12] = 11} local pairedSlots = {[13] = 14, [14] = 13, [16] = 17, [17] = 16, [11] = 12, [12] = 11}
local checkSlot = pairedSlots[invslot] local checkSlot = pairedSlots[invslot]
if checkSlot then if checkSlot then
local link = GetInventoryItemLink("player", checkSlot) local slotItemName = C_Item.GetItemName({ equipmentSlotIndex = checkSlot })
if link then if slotItemName then
local _, _, slotItemName = string_find(link, "|h%[(.-)%]|h") if string_lower(slotItemName) == string_lower(msg) then
if slotItemName and string_lower(slotItemName) == string_lower(msg) then
-- Item found in paired slot - but prefer a bag copy if one exists -- Item found in paired slot - but prefer a bag copy if one exists
local bagCopyBag, bagCopySlot = FindItemInBagsByName(msg) local bagCopyBag, bagCopySlot = FindItemInBagsByName(msg)
if bagCopyBag then if bagCopyBag then
@@ -3627,17 +3514,14 @@ function CleveRoids.EquipBagItem(msg, slotOrOffhand)
-- Verify the item actually landed in the target slot -- Verify the item actually landed in the target slot
-- EquipItemByName may silently no-op for same-named items in paired slots -- EquipItemByName may silently no-op for same-named items in paired slots
-- (e.g., dual-wielding Scimitar: MH copy found first, "equipped" to same slot) -- (e.g., dual-wielding Scimitar: MH copy found first, "equipped" to same slot)
local newLink = GetInventoryItemLink("player", invslot) local newName = C_Item.GetItemName({ equipmentSlotIndex = invslot })
if newLink then if newName and string_lower(newName) == string_lower(msg) then
local _, _, newName = string_find(newLink, "|h%[(.-)%]|h") if CleveRoids.Items then
if newName and string_lower(newName) == string_lower(msg) then CleveRoids.Items[msg] = nil
if CleveRoids.Items then CleveRoids.Items[string_lower(msg)] = nil
CleveRoids.Items[msg] = nil
CleveRoids.Items[string_lower(msg)] = nil
end
InvalidateDisplacedItem()
return true
end end
InvalidateDisplacedItem()
return true
end end
-- Verification failed - EquipItemByName didn't place item in target slot -- Verification failed - EquipItemByName didn't place item in target slot
if CleveRoids.equipDebugLog then if CleveRoids.equipDebugLog then
@@ -3679,10 +3563,9 @@ function CleveRoids.EquipBagItem(msg, slotOrOffhand)
local ok = pcall(EquipItemByName, item.name, invslot) local ok = pcall(EquipItemByName, item.name, invslot)
if ok then if ok then
-- Verify the item actually landed in the target slot (same guard as above) -- Verify the item actually landed in the target slot (same guard as above)
local newLink = GetInventoryItemLink("player", invslot) local newName = C_Item.GetItemName({ equipmentSlotIndex = invslot })
if newLink then if newName then
local _, _, newName = string_find(newLink, "|h%[(.-)%]|h") if string_lower(newName) == string_lower(item.name) then
if newName and string_lower(newName) == string_lower(item.name) then
if CleveRoids.Items then if CleveRoids.Items then
CleveRoids.Items[item.name] = nil CleveRoids.Items[item.name] = nil
CleveRoids.Items[string_lower(item.name)] = nil CleveRoids.Items[string_lower(item.name)] = nil
@@ -5261,8 +5144,7 @@ function CleveRoids.DoWDBWarmup()
for bag = 0, 4 do for bag = 0, 4 do
local slots = GetContainerNumSlots(bag) or 0 local slots = GetContainerNumSlots(bag) or 0
for slot = 1, slots do for slot = 1, slots do
local link = GetContainerItemLink(bag, slot) if C_Container.GetContainerItemID(bag, slot) then
if link then
-- Tooltip scan loads the item into WDB -- Tooltip scan loads the item into WDB
tip:ClearLines() tip:ClearLines()
tip:SetBagItem(bag, slot) tip:SetBagItem(bag, slot)
@@ -5272,9 +5154,8 @@ function CleveRoids.DoWDBWarmup()
end end
-- Scan equipped items -- Scan equipped items
for slot = 0, 19 do for slot = 1, 19 do
local link = GetInventoryItemLink("player", slot) if GetInventoryItemID("player", slot) then
if link then
tip:ClearLines() tip:ClearLines()
tip:SetInventoryItem("player", slot) tip:SetInventoryItem("player", slot)
scanned = scanned + 1 scanned = scanned + 1
@@ -7327,9 +7208,9 @@ local function FindItemInBags(itemName)
local searchName = string.lower(itemName) local searchName = string.lower(itemName)
for bag = 0, 4 do for bag = 0, 4 do
for slot = 1, GetContainerNumSlots(bag) do for slot = 1, GetContainerNumSlots(bag) do
local link = GetContainerItemLink(bag, slot) local id = C_Container.GetContainerItemID(bag, slot)
if link then if id then
local _, _, foundName = string.find(link, "%[(.+)%]") local foundName = C_Item.GetItemNameByID(id)
if foundName and string.find(string.lower(foundName), searchName, 1, true) then if foundName and string.find(string.lower(foundName), searchName, 1, true) then
return bag, slot, foundName return bag, slot, foundName
end end
+50 -79
View File
@@ -156,10 +156,9 @@ end
function CleveRoids.IndexEquippedItems() function CleveRoids.IndexEquippedItems()
local items = CleveRoids.Items or {} local items = CleveRoids.Items or {}
for inventoryID = 0, 19 do for inventoryID = 1, 19 do
local link = GetInventoryItemLink("player", inventoryID) local itemID = GetInventoryItemID("player", inventoryID)
if link then if itemID then
local _, _, itemID = string.find(link, "item:(%d+)")
local name, link, _, _, itemType, itemSubType, _, _, texture = GetItemInfo(itemID) local name, link, _, _, itemType, itemSubType, _, _, texture = GetItemInfo(itemID)
if name then if name then
local count = GetInventoryItemCount("player", inventoryID) local count = GetInventoryItemCount("player", inventoryID)
@@ -199,10 +198,9 @@ function CleveRoids.IndexEquipSlot(inventoryID)
if not inventoryID then return end if not inventoryID then return end
local items = CleveRoids.Items or {} local items = CleveRoids.Items or {}
local link = GetInventoryItemLink("player", inventoryID) local itemID = GetInventoryItemID("player", inventoryID)
if link then if itemID then
local _, _, itemID = string.find(link, "item:(%d+)")
local name, itemLink, _, _, itemType, itemSubType, _, _, texture = GetItemInfo(itemID) local name, itemLink, _, _, itemType, itemSubType, _, _, texture = GetItemInfo(itemID)
if name then if name then
local count = GetInventoryItemCount("player", inventoryID) local count = GetInventoryItemCount("player", inventoryID)
@@ -265,21 +263,17 @@ function CleveRoids.IndexItems()
-- PERFORMANCE: Local function references -- PERFORMANCE: Local function references
local GetContainerNumSlots = GetContainerNumSlots local GetContainerNumSlots = GetContainerNumSlots
local GetContainerItemLink = GetContainerItemLink
local GetContainerItemInfo = GetContainerItemInfo local GetContainerItemInfo = GetContainerItemInfo
local GetInventoryItemLink = GetInventoryItemLink
local GetInventoryItemCount = GetInventoryItemCount local GetInventoryItemCount = GetInventoryItemCount
-- Scan bags (reverse order to prefer first stack) -- Scan bags (reverse order to prefer first stack)
for bagID = 0, NUM_BAG_SLOTS do for bagID = 0, NUM_BAG_SLOTS do
local numSlots = GetContainerNumSlots(bagID) local numSlots = GetContainerNumSlots(bagID)
for slot = numSlots, 1, -1 do for slot = numSlots, 1, -1 do
local link = GetContainerItemLink(bagID, slot) local itemID = C_Container.GetContainerItemID(bagID, slot)
if link then if itemID then
local _, _, itemID = string_find(link, "item:(%d+)") -- Decorated name for the duplicate fast-path (no link string built)
local linkName = C_Item.GetItemName({ bagID = bagID, slotIndex = slot })
-- PERFORMANCE: Try to extract name from link first to check for duplicates
local _, _, linkName = string_find(link, "%[(.+)%]")
local existing = linkName and items[linkName] local existing = linkName and items[linkName]
if existing then if existing then
@@ -317,13 +311,11 @@ function CleveRoids.IndexItems()
end end
-- Scan equipped items -- Scan equipped items
for inventoryID = 0, 19 do for inventoryID = 1, 19 do
local link = GetInventoryItemLink("player", inventoryID) local itemID = GetInventoryItemID("player", inventoryID)
if link then if itemID then
local _, _, itemID = string_find(link, "item:(%d+)") -- Decorated name for the duplicate fast-path (no link string built)
local linkName = C_Item.GetItemName({ equipmentSlotIndex = inventoryID })
-- PERFORMANCE: Try to extract name from link first
local _, _, linkName = string_find(link, "%[(.+)%]")
local existing = linkName and items[linkName] local existing = linkName and items[linkName]
if existing then if existing then
@@ -477,10 +469,8 @@ local function makeInventoryItem(inventoryID, link, Items)
if not link then link = GetInventoryItemLink("player", inventoryID) end if not link then link = GetInventoryItemLink("player", inventoryID) end
if not link then return end if not link then return end
local _, _, itemID = string_find(link, "item:(%d+)") local itemID = GetInventoryItemID("player", inventoryID)
itemID = itemID and tonumber(itemID) or nil local name = C_Item.GetItemName({ equipmentSlotIndex = inventoryID })
local name = itemID and GetItemInfo(itemID) or nil
local texture = GetInventoryItemTexture("player", inventoryID) local texture = GetInventoryItemTexture("player", inventoryID)
local count = GetInventoryItemCount("player", inventoryID) local count = GetInventoryItemCount("player", inventoryID)
@@ -509,14 +499,12 @@ local function makeBagItem(bagID, slot, link, Items)
end end
if not link then return end if not link then return end
local _, _, itemID = string_find(link, "item:(%d+)") local itemID = C_Container.GetContainerItemID(bagID, slot)
itemID = itemID and tonumber(itemID) or nil
local name, _, _, _, _, _, _, _, texture = GetItemInfo(itemID) local name = C_Item.GetItemName(ItemLocation:CreateFromBagAndSlot(bagID, slot))
local count = 0 local count = 0
local tex, itemCount = GetContainerItemInfo(bagID, slot) local texture, itemCount = GetContainerItemInfo(bagID, slot)
if itemCount then count = itemCount end if itemCount then count = itemCount end
if not texture then texture = tex end
local it = { local it = {
bagID = bagID, bagID = bagID,
@@ -574,9 +562,7 @@ function CleveRoids.GetItem(text)
for inv = 1, 19 do for inv = 1, 19 do
local link = GetInventoryItemLink("player", inv) local link = GetInventoryItemLink("player", inv)
if link then if link then
local _, _, itemID = string_find(link, "item:(%d+)") local itemID = GetInventoryItemID("player", inv)
itemID = itemID and tonumber(itemID) or nil
if qid and itemID and qid == itemID then if qid and itemID and qid == itemID then
return makeInventoryItem(inv, link, Items) return makeInventoryItem(inv, link, Items)
elseif qname then elseif qname then
@@ -595,9 +581,7 @@ function CleveRoids.GetItem(text)
for slot = 1, slots do for slot = 1, slots do
local link = GetContainerItemLink(bag, slot) local link = GetContainerItemLink(bag, slot)
if link then if link then
local _, _, itemID = string_find(link, "item:(%d+)") local itemID = C_Container.GetContainerItemID(bag, slot)
itemID = itemID and tonumber(itemID) or nil
if qid and itemID and qid == itemID then if qid and itemID and qid == itemID then
return makeBagItem(bag, slot, link, Items) return makeBagItem(bag, slot, link, Items)
elseif qname then elseif qname then
@@ -720,18 +704,16 @@ function CleveRoids.FindItemQuick(text)
if cached then if cached then
-- Validate: check if item is actually at the cached location -- Validate: check if item is actually at the cached location
if cached.inventoryID then if cached.inventoryID then
local link = GetInventoryItemLink("player", cached.inventoryID) if qid then
if link then if GetInventoryItemID("player", cached.inventoryID) == qid then
local nm = GetNameFromLink(link) cached._validated = true
return cached -- Cache is valid
end
else
local nm = C_Item.GetItemName({ equipmentSlotIndex = cached.inventoryID })
if nm and qname and string_lower(nm) == qname then if nm and qname and string_lower(nm) == qname then
cached._validated = true cached._validated = true
return cached -- Cache is valid return cached -- Cache is valid
elseif qid then
local _, _, itemID = string_find(link, "item:(%d+)")
if itemID and tonumber(itemID) == qid then
cached._validated = true
return cached -- Cache is valid
end
end end
end end
-- Cache is stale - item not at cached equipped slot, invalidate -- Cache is stale - item not at cached equipped slot, invalidate
@@ -740,18 +722,16 @@ function CleveRoids.FindItemQuick(text)
Items[string_lower(cached.name)] = nil Items[string_lower(cached.name)] = nil
end end
elseif cached.bagID and cached.slot then elseif cached.bagID and cached.slot then
local link = GetContainerItemLink(cached.bagID, cached.slot) if qid then
if link then if C_Container.GetContainerItemID(cached.bagID, cached.slot) == qid then
local nm = GetNameFromLink(link) cached._validated = true
return cached -- Cache is valid
end
else
local nm = C_Item.GetItemName({ bagID = cached.bagID, slotIndex = cached.slot })
if nm and qname and string_lower(nm) == qname then if nm and qname and string_lower(nm) == qname then
cached._validated = true cached._validated = true
return cached -- Cache is valid return cached -- Cache is valid
elseif qid then
local _, _, itemID = string_find(link, "item:(%d+)")
if itemID and tonumber(itemID) == qid then
cached._validated = true
return cached -- Cache is valid
end
end end
end end
-- Cache is stale - item not at cached bag slot, invalidate -- Cache is stale - item not at cached bag slot, invalidate
@@ -791,20 +771,17 @@ function CleveRoids.FindItemQuick(text)
for slot = 1, slots do for slot = 1, slots do
local link = GetContainerItemLink(bag, slot) local link = GetContainerItemLink(bag, slot)
if link then if link then
local _, _, itemID = string_find(link, "item:(%d+)") local itemID = C_Container.GetContainerItemID(bag, slot)
if itemID then -- ID match: fast path
itemID = tonumber(itemID) if qid and qid == itemID then
-- ID match: fast path return makeBagItem(bag, slot, link, Items)
if qid and qid == itemID then end
-- Name match: extract name from link (faster than GetItemInfo)
if qname then
local nm = GetNameFromLink(link)
if nm and string_lower(nm) == qname then
return makeBagItem(bag, slot, link, Items) return makeBagItem(bag, slot, link, Items)
end end
-- Name match: extract name from link (faster than GetItemInfo)
if qname then
local nm = GetNameFromLink(link)
if nm and string_lower(nm) == qname then
return makeBagItem(bag, slot, link, Items)
end
end
end end
end end
end end
@@ -818,25 +795,19 @@ end
function CleveRoids.IsItemEquipped(text, inventoryId) function CleveRoids.IsItemEquipped(text, inventoryId)
if not text or not inventoryId then return false end if not text or not inventoryId then return false end
local link = GetInventoryItemLink("player", inventoryId) local currentID = GetInventoryItemID("player", inventoryId)
if not link then return false end
local _, _, currentID = string_find(link, "item:(%d+)")
if not currentID then return false end if not currentID then return false end
-- Check by ID (fast path) -- Check by ID (fast path)
local textId = tonumber(text) local textId = tonumber(text)
if textId and textId == tonumber(currentID) then if textId and textId == currentID then
return true return true
end end
-- Check by name - extract from link instead of GetItemInfo for performance -- Check by name (decorated, so suffixed gear still matches)
local currentName = GetNameFromLink(link) local currentName = C_Item.GetItemName({ equipmentSlotIndex = inventoryId })
if currentName then if currentName and string_lower(currentName) == string_lower(text) then
local textLower = string_lower(text) return true
if string_lower(currentName) == textLower then
return true
end
end end
return false return false
+1 -6
View File
@@ -6918,12 +6918,7 @@ function CleveRoids.ApplyEquipmentModifier(spellID, baseDuration)
local modifiedDuration = modifier.modifier(baseDuration, itemID) local modifiedDuration = modifier.modifier(baseDuration, itemID)
if modifiedDuration ~= baseDuration and CleveRoids.debug then if modifiedDuration ~= baseDuration and CleveRoids.debug then
local itemName = "Unknown" local itemName = C_Item.GetItemName({ equipmentSlotIndex = modifier.slot }) or "Unknown"
local itemLink = GetInventoryItemLink("player", modifier.slot)
if itemLink then
local _, _, _n = string.find(itemLink, "%[(.-)%]")
itemName = _n or "Unknown"
end
DEFAULT_CHAT_FRAME:AddMessage( DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cffff00ff[Equipment Modifier]|r %s (ID:%d): %ds -> %ds (item: %s [%d])", string.format("|cffff00ff[Equipment Modifier]|r %s (ID:%d): %ds -> %ds (item: %s [%d])",