mirror of
https://github.com/brues-code/SuperCleveRoidMacros.git
synced 2026-09-16 03:38:00 +00:00
c5ddc6c313
Hands ClassicAPI the action this addon resolved for each macro, so the icon, tooltip, cooldown sweep, range and usable state come from the client instead of from replaced action-bar globals. That also reaches the drag cursor and the macro window grid, which Lua cannot touch, and lets the client answer range for a macro slot directly rather than borrowing a proxy slot. - CleveRoids.useClassicAPIDisplay feature-detects C_Macro.SetMacroDisplay rather than checking CLASSIC_API_VERSION, which reports a dev sentinel while the API is unreleased. ClassicAPIMacroDisplay tells ClassicAPI we drive macro display, which it otherwise stands down from when it sees this addon. - macro.actions.macroID back-references the Blizzard index, since the update loop walks actions objects rather than macros. nil for SuperMacro macros, which have no index and so cannot be published. - PublishDisplay sends false, not nothing, when no action matched: that claims the macro and shows the question mark, where silence would hand it back to ClassicAPI's own #showtooltip parser. - PublishAllDisplays runs once the addon is ready and after every re-parse, since ClassicAPI re-evaluates nothing for us. It covers macros that aren't on a bar, which is what keeps the macro window grid correct. Deferred past load because SetMacroDisplay returns false until the player is in the world. - ReleaseDisplays hands every macro back on DisableAddon. The per-slot ACTIONBAR_SLOT_CHANGED fan-out is kept for the non-integrated path. Publishing repaints every slot holding the macro through the client's own notifier, so it is redundant when the API is present, but removing it outright would leave buttons never repainting on a ClassicAPI build without the API.
841 lines
30 KiB
Lua
841 lines
30 KiB
Lua
--[[
|
|
Author: Dennis Werner Garske (DWG) / brian / Mewtiny
|
|
License: MIT License
|
|
]]
|
|
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 GetSpellInfo = GetSpellInfo
|
|
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
|
|
|
|
-- PERFORMANCE: Extract item name directly from link without GetItemInfo call
|
|
-- Link format: |cFFFFFFFF|Hitem:12345:0:0:0|h[Item Name]|h|r
|
|
local function GetNameFromLink(link)
|
|
if not link then return nil end
|
|
local _, _, name = string_find(link, "|h%[(.-)%]|h")
|
|
return name
|
|
end
|
|
|
|
-- Indexes all spells the current player and pet knows
|
|
function CleveRoids.IndexSpells()
|
|
local spells = {}
|
|
local i = 0
|
|
local book = 1
|
|
local bookType = CleveRoids.bookTypes[book]
|
|
local maxBooks = table.getn(CleveRoids.bookTypes)
|
|
|
|
spells[bookType] = {}
|
|
while true do
|
|
i = i + 1
|
|
|
|
-- ClassicAPI GetSpellInfo(slot, bookType) returns the spellID as the
|
|
-- 10th value, giving us slot -> spellID natively (plus name/rank/icon
|
|
-- in one call, replacing GetSpellName + GetSpellTexture).
|
|
local spellName, spellRank, texture, _, _, _, _, _, _, spellId = GetSpellInfo(i, bookType)
|
|
if not spellName then
|
|
i = 0
|
|
book = book + 1
|
|
if book > maxBooks then
|
|
break
|
|
end
|
|
|
|
bookType = CleveRoids.bookTypes[book]
|
|
spells[bookType] = {}
|
|
else
|
|
local cost, reagent, reagentId = CleveRoids.GetSpellCost(i, bookType, spellId)
|
|
if not spells[bookType][spellName] then
|
|
spells[bookType][spellName] = {
|
|
spellSlot = i,
|
|
id = spellId,
|
|
name = spellName,
|
|
bookType = bookType,
|
|
texture = texture,
|
|
cost = cost,
|
|
reagentId = reagentId,
|
|
}
|
|
end
|
|
if spellRank and not spells[bookType][spellName][spellRank] then
|
|
spells[bookType][spellName][spellRank] = {
|
|
spellSlot = i,
|
|
id = spellId,
|
|
name = spellName,
|
|
rank = spellRank,
|
|
bookType = bookType,
|
|
texture = texture,
|
|
cost = cost,
|
|
reagentId = reagentId,
|
|
}
|
|
spells[bookType][spellName].highest = spells[bookType][spellName][spellRank]
|
|
end
|
|
-- For spells with no rank string, the base entry is the highest
|
|
if not spellRank and not spells[bookType][spellName].highest then
|
|
spells[bookType][spellName].highest = spells[bookType][spellName]
|
|
end
|
|
|
|
if reagent then
|
|
CleveRoids.countedItemTypes[reagent] = true
|
|
elseif reagentId and Item then
|
|
Item:CreateFromItemID(reagentId):ContinueOnItemLoad(function()
|
|
local loadedName = C_Item.GetItemNameByID(reagentId)
|
|
if loadedName and loadedName ~= "" then
|
|
CleveRoids.countedItemTypes[loadedName] = true
|
|
end
|
|
end)
|
|
end
|
|
end
|
|
end
|
|
|
|
CleveRoids.Spells = spells
|
|
end
|
|
|
|
-- Indexes all pet spells and their action bar slots
|
|
function CleveRoids.IndexPetSpells()
|
|
local petSpells = {}
|
|
|
|
if not UnitExists("pet") then
|
|
CleveRoids.PetSpells = petSpells
|
|
return
|
|
end
|
|
|
|
for i = 1, NUM_PET_ACTION_SLOTS do
|
|
local name, subtext, texture, isToken = GetPetActionInfo(i)
|
|
|
|
if name and not isToken then
|
|
-- Store the spell name and its slot
|
|
petSpells[name] = {
|
|
slot = i,
|
|
name = name,
|
|
subtext = subtext,
|
|
texture = texture
|
|
}
|
|
end
|
|
end
|
|
|
|
CleveRoids.PetSpells = petSpells
|
|
end
|
|
|
|
-- Gets a pet spell by name
|
|
function CleveRoids.GetPetSpell(spellName)
|
|
if not spellName or not CleveRoids.PetSpells then
|
|
return nil
|
|
end
|
|
|
|
spellName = CleveRoids.Trim(spellName)
|
|
return CleveRoids.PetSpells[spellName]
|
|
end
|
|
|
|
function CleveRoids.IndexTalents()
|
|
local talents = {[1] = true}
|
|
for tab = 1, GetNumTalentTabs() do
|
|
for i = 1, GetNumTalents(tab) do
|
|
local name, _, _, _, rank = GetTalentInfo(tab, i)
|
|
if name then
|
|
talents[name] = tonumber(rank)
|
|
end
|
|
end
|
|
end
|
|
CleveRoids.Talents = talents
|
|
end
|
|
|
|
|
|
-- Lightweight equipment-only indexing for combat situations
|
|
-- Updates existing cache rather than rebuilding it
|
|
-- PERFORMANCE: Index a single equipment slot instead of all 20
|
|
-- Use when we know exactly which slot changed (e.g., from EquipBagItem or
|
|
-- PLAYER_EQUIPMENT_CHANGED). hasCurrent is the event's arg2 (does the slot now
|
|
-- hold an item); pass false to skip the API probe on a slot we know is empty.
|
|
function CleveRoids.IndexEquipSlot(inventoryID, hasCurrent)
|
|
if not inventoryID then return end
|
|
|
|
local items = CleveRoids.Items or {}
|
|
|
|
-- Clear any stale inventoryID still pointing at this slot: the item that was
|
|
-- here is now unequipped or swapped out (its real location comes from the
|
|
-- paired BAG_UPDATE rebuild). Equip changes are user-paced, so this table
|
|
-- scan is off the hot path.
|
|
for _, entry in pairs(items) do
|
|
if type(entry) == "table" and entry.inventoryID == inventoryID then
|
|
entry.inventoryID = nil
|
|
end
|
|
end
|
|
|
|
-- hasCurrent == false (arg2) => slot is now empty, nothing to add.
|
|
local itemID = hasCurrent ~= false and GetInventoryItemID("player", inventoryID)
|
|
if itemID then
|
|
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
|
|
|
|
|
|
-- PERFORMANCE: Persistent cache for GetItemInfo results (survives between IndexItems calls)
|
|
-- This prevents repeated GetItemInfo calls for the same itemID
|
|
local _itemInfoCache = {}
|
|
local _itemInfoCacheSize = 0
|
|
local _ITEM_INFO_CACHE_MAX = 500 -- Limit cache size
|
|
|
|
local function GetCachedItemInfo(itemID)
|
|
local cached = _itemInfoCache[itemID]
|
|
if cached then
|
|
return cached.name, cached.link, cached.quality, cached.level, cached.type, cached.subType, cached.stackCount, cached.equipLoc, cached.texture
|
|
end
|
|
|
|
local name, link, quality, level, itemType, subType, stackCount, equipLoc, texture = GetItemInfo(itemID)
|
|
if name then
|
|
-- Cache the result
|
|
if _itemInfoCacheSize < _ITEM_INFO_CACHE_MAX then
|
|
_itemInfoCache[itemID] = {
|
|
name = name, link = link, quality = quality, level = level,
|
|
type = itemType, subType = subType, stackCount = stackCount,
|
|
equipLoc = equipLoc, texture = texture
|
|
}
|
|
_itemInfoCacheSize = _itemInfoCacheSize + 1
|
|
end
|
|
end
|
|
return name, link, quality, level, itemType, subType, stackCount, equipLoc, texture
|
|
end
|
|
|
|
function CleveRoids.IndexItems()
|
|
local items = {}
|
|
local NUM_BAG_SLOTS = NUM_BAG_SLOTS -- Upvalue for bag constant
|
|
|
|
-- Rebuilt each pass: itemIDs the player owns that GetItemInfo couldn't
|
|
-- resolve yet (cold cache under ClassicAPI's async warmup). The
|
|
-- GET_ITEM_INFO_RECEIVED handler consults this so it only re-indexes for
|
|
-- our own uncached items and ignores the flood of unrelated fills (quest
|
|
-- DB scans, AH, chat-link hovers, inspects) in O(1).
|
|
local pendingItemInfo = {}
|
|
CleveRoids.pendingItemInfo = pendingItemInfo
|
|
|
|
-- PERFORMANCE: Local function references
|
|
local GetContainerNumSlots = GetContainerNumSlots
|
|
local GetContainerItemInfo = GetContainerItemInfo
|
|
local GetInventoryItemCount = GetInventoryItemCount
|
|
|
|
-- Scan bags (reverse order to prefer first stack)
|
|
for bagID = 0, NUM_BAG_SLOTS do
|
|
local numSlots = GetContainerNumSlots(bagID)
|
|
for slot = numSlots, 1, -1 do
|
|
local itemID = C_Container.GetContainerItemID(bagID, slot)
|
|
if itemID then
|
|
-- Decorated name for the duplicate fast-path (no link string built)
|
|
local linkName = C_Item.GetItemName({ bagID = bagID, slotIndex = slot })
|
|
local existing = linkName and items[linkName]
|
|
|
|
if existing then
|
|
-- Item already seen - just update count and add bag slot (skip GetItemInfo)
|
|
local _, count = GetContainerItemInfo(bagID, slot)
|
|
existing.count = (existing.count or 0) + (count or 0)
|
|
table_insert(existing.bagSlots, {bagID, slot})
|
|
else
|
|
-- New item - need full info
|
|
local name, itemLink, _, _, itemType, itemSubType, _, _, texture = GetCachedItemInfo(itemID)
|
|
|
|
if name then
|
|
local _, count = GetContainerItemInfo(bagID, slot)
|
|
items[name] = {
|
|
bagID = bagID,
|
|
slot = slot,
|
|
id = itemID,
|
|
name = name,
|
|
type = itemType,
|
|
count = count,
|
|
texture = texture,
|
|
link = itemLink,
|
|
bagSlots = {{bagID, slot}},
|
|
slotsIndex = 1,
|
|
}
|
|
items[itemID] = name
|
|
local lowerName = string_lower(name)
|
|
if lowerName ~= name then
|
|
items[lowerName] = name
|
|
end
|
|
else
|
|
-- Owned but not cached yet; wait for GET_ITEM_INFO_RECEIVED.
|
|
pendingItemInfo[itemID] = true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Scan equipped items
|
|
for inventoryID = 1, 19 do
|
|
local itemID = GetInventoryItemID("player", inventoryID)
|
|
if itemID then
|
|
-- Decorated name for the duplicate fast-path (no link string built)
|
|
local linkName = C_Item.GetItemName({ equipmentSlotIndex = inventoryID })
|
|
local existing = linkName and items[linkName]
|
|
|
|
if existing then
|
|
-- Item in bags AND equipped - update existing entry
|
|
existing.inventoryID = inventoryID
|
|
local count = GetInventoryItemCount("player", inventoryID)
|
|
existing.count = (existing.count or 0) + (count or 0)
|
|
else
|
|
-- New equipped-only item
|
|
local name, itemLink, _, _, itemType, itemSubType, _, _, texture = GetCachedItemInfo(itemID)
|
|
if name then
|
|
local count = GetInventoryItemCount("player", inventoryID)
|
|
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
|
|
-- Owned but not cached yet; wait for GET_ITEM_INFO_RECEIVED.
|
|
pendingItemInfo[itemID] = true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
CleveRoids.lastGetItem = nil
|
|
CleveRoids.Items = items
|
|
|
|
-- relink an item action to the item if it wasn't in inventory/bags before
|
|
for slot, actions in CleveRoids.Actions do
|
|
for i, action in actions.list do
|
|
if action.item then
|
|
action.item = CleveRoids.GetItem(action.action)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function CleveRoids.ClearSlot(slots, slot)
|
|
if slots[slot] then
|
|
slots[slots[slot]] = nil
|
|
end
|
|
slots[slot] = nil
|
|
end
|
|
|
|
-- Map a 1-based action slot to (actionType, id, name, rank) via ClassicAPI's
|
|
-- GetActionInfo, resolving spell/item/macro names from the id. actionType is
|
|
-- "SPELL" | "ITEM" | "MACRO" to match existing callers; returns nil for an
|
|
-- empty slot. Bag-instance items (no itemID from GetActionInfo) yield no name.
|
|
function CleveRoids.GetActionButtonInfo(slot)
|
|
local actionType, id = CleveRoids.ClassicAPI.GetActionInfo(slot)
|
|
if not actionType then return end
|
|
|
|
if actionType == "spell" and id then
|
|
local rank = C_Spell.GetSpellSubtext(id)
|
|
if rank == "" then rank = nil end
|
|
return "SPELL", id, C_Spell.GetSpellName(id), rank
|
|
elseif actionType == "item" and id then
|
|
local item = CleveRoids.GetItem(id)
|
|
return "ITEM", id, (item and item.name)
|
|
elseif actionType == "macro" and id then
|
|
return "MACRO", id, GetMacroInfo(id)
|
|
end
|
|
end
|
|
|
|
function CleveRoids.IndexActionSlot(slot)
|
|
if not HasAction(slot) then
|
|
CleveRoids.Actions[slot] = nil
|
|
-- When clearing a reactive slot, check if we need to find it elsewhere
|
|
local clearedReactiveName = CleveRoids.reactiveSlots[slot]
|
|
CleveRoids.ClearSlot(CleveRoids.reactiveSlots, slot)
|
|
CleveRoids.ClearSlot(CleveRoids.actionSlots, slot)
|
|
|
|
-- If we cleared a reactive spell, rescan to find it in another slot
|
|
if clearedReactiveName then
|
|
for i = 1, 120 do
|
|
if i ~= slot and HasAction(i) then
|
|
local _, _, scanName = CleveRoids.GetActionButtonInfo(i)
|
|
if scanName == clearedReactiveName then
|
|
CleveRoids.reactiveSlots[clearedReactiveName] = i
|
|
CleveRoids.reactiveSlots[i] = clearedReactiveName
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
else
|
|
local actionType, _, name, rank = CleveRoids.GetActionButtonInfo(slot)
|
|
if name then
|
|
local reactiveName = CleveRoids.reactiveSpells[name] and name
|
|
local actionSlotName = name..(rank and ("("..rank..")") or "")
|
|
if reactiveName then
|
|
-- Always update the mapping to ensure we track the correct slot
|
|
-- Clear old mapping for this spell name first
|
|
local oldSlot = CleveRoids.reactiveSlots[reactiveName]
|
|
if oldSlot and oldSlot ~= slot then
|
|
CleveRoids.reactiveSlots[oldSlot] = nil
|
|
end
|
|
CleveRoids.reactiveSlots[reactiveName] = slot
|
|
CleveRoids.reactiveSlots[slot] = reactiveName
|
|
elseif not reactiveName then
|
|
CleveRoids.ClearSlot(CleveRoids.reactiveSlots, slot)
|
|
end
|
|
if actionType == "SPELL" or actionType == "ITEM" then
|
|
if not CleveRoids.actionSlots[actionSlotName] then
|
|
CleveRoids.actionSlots[actionSlotName] = slot
|
|
CleveRoids.actionSlots[slot] = actionSlotName
|
|
end
|
|
end
|
|
end
|
|
end
|
|
local actions = CleveRoids.GetAction(slot)
|
|
CleveRoids.TestForActiveAction(actions)
|
|
CleveRoids.PublishDisplay(actions)
|
|
CleveRoids.SendEventForAction(slot, "ACTIONBAR_SLOT_CHANGED", slot)
|
|
end
|
|
|
|
function CleveRoids.IndexActionBars()
|
|
for i = 1, 120 do
|
|
CleveRoids.IndexActionSlot(i)
|
|
end
|
|
end
|
|
|
|
function CleveRoids.GetSpell(text)
|
|
text = CleveRoids.Trim(text)
|
|
|
|
-- Explicit "spell:<id>" form. If the player knows the spell, reuse its cached
|
|
-- spellbook entry (full cost/cooldown/usability). FindSpellBookSlotByID
|
|
-- (ClassicAPI) resolves per-rank IDs and pet spells natively and returns the
|
|
-- same bookType string CleveRoids.Spells is keyed by. If the spell is not in
|
|
-- either book, fall back to an id-only entry that SetAction renders via
|
|
-- SetSpellByID; cost=0 keeps the downstream active-action checks nil-safe.
|
|
local _, _, spellId = string.find(text, "^spell:(%d+)$")
|
|
if spellId then
|
|
spellId = tonumber(spellId)
|
|
local slot, book = FindSpellBookSlotByID(spellId)
|
|
if slot then
|
|
local name, rank = GetSpellInfo(slot, book)
|
|
local byName = name and CleveRoids.Spells[book] and CleveRoids.Spells[book][name]
|
|
if byName then
|
|
return (rank and rank ~= "" and byName[rank]) or byName.highest or byName
|
|
end
|
|
end
|
|
return { id = spellId, texture = C_Spell.GetSpellTexture(spellId) or CleveRoids.unknownTexture, cost = 0 }
|
|
end
|
|
|
|
local rs, _, rank = string.find(text, "[^%s]%((Rank %d+)%)$")
|
|
local name = rank and string.sub(text, 1, rs) or text
|
|
|
|
for book, spells in CleveRoids.Spells do
|
|
if spells and spells[name] then
|
|
local entry = spells[name][rank or "highest"]
|
|
if entry then return entry end
|
|
-- Fallback: for spells with no rank, return base entry
|
|
if not rank and spells[name].spellSlot then
|
|
return spells[name]
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function CleveRoids.GetTalent(text)
|
|
text = CleveRoids.Trim(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 = GetInventoryItemID("player", inventoryID)
|
|
local name = C_Item.GetItemName({ equipmentSlotIndex = inventoryID })
|
|
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 = C_Container.GetContainerItemID(bagID, slot)
|
|
|
|
local name = C_Item.GetItemName({bagID = bagID, slotIndex = slot})
|
|
local count = 0
|
|
local texture, itemCount = GetContainerItemInfo(bagID, slot)
|
|
if itemCount then count = itemCount 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
|
|
|
|
-- Explicit "item:<id>" form: force resolution by item ID. Reuses the numeric
|
|
-- lookup below (equipped -> bags -> GetItemInfo), so location-aware tooltip
|
|
-- rendering (SetInventoryItem/SetBagItem) still applies when the player has it.
|
|
local _, _, prefixedId = string.find(text, "^item:(%d+)$")
|
|
if prefixedId then text = prefixedId end
|
|
|
|
local Items = CleveRoids.Items
|
|
local item = Items[text] or Items[tostring(text)]
|
|
if not item then
|
|
local lowerText = string_lower(text)
|
|
local canonicalName = Items[lowerText]
|
|
if canonicalName and type(canonicalName) == "string" then
|
|
item = Items[canonicalName]
|
|
end
|
|
end
|
|
|
|
if item then
|
|
if type(item) == "table" then
|
|
return item
|
|
else
|
|
return Items[tostring(item)]
|
|
end
|
|
end
|
|
|
|
local qid = tonumber(text)
|
|
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 = makeInventoryItem(qid, nil, Items)
|
|
if it then return it end
|
|
end
|
|
|
|
-- Scan equipped items
|
|
for inv = 1, 19 do
|
|
local link = GetInventoryItemLink("player", inv)
|
|
if link then
|
|
local itemID = GetInventoryItemID("player", inv)
|
|
if qid and itemID and qid == itemID then
|
|
return makeInventoryItem(inv, link, Items)
|
|
elseif qname then
|
|
-- PERFORMANCE: Extract name from link directly instead of GetItemInfo
|
|
local nm = GetNameFromLink(link)
|
|
if nm and string_lower(nm) == qname then
|
|
return makeInventoryItem(inv, link, Items)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- 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 = C_Container.GetContainerItemID(bag, slot)
|
|
if qid and itemID and qid == itemID then
|
|
return makeBagItem(bag, slot, link, Items)
|
|
elseif qname then
|
|
-- PERFORMANCE: Extract name from link directly instead of GetItemInfo
|
|
local nm = GetNameFromLink(link)
|
|
if nm and string_lower(nm) == qname then
|
|
return makeBagItem(bag, slot, link, Items)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local name, link, _, _, _, _, _, _, texture = GetItemInfo(text)
|
|
if not name then return end
|
|
local fallback = { id = text, name = name, link = link, texture = texture }
|
|
CleveRoids.Items[name] = fallback
|
|
local lowerName = string.lower(name)
|
|
if lowerName ~= name then
|
|
CleveRoids.Items[lowerName] = name
|
|
end
|
|
return fallback
|
|
end
|
|
|
|
function CleveRoids.GetNextBagSlotForUse(item, text)
|
|
if not item then return end
|
|
|
|
-- 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
|
|
end
|
|
|
|
CleveRoids.lastGetItem = item
|
|
return item
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- FAST ITEM LOOKUP (avoids full bag scan)
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- PERFORMANCE: Fast item lookup by name only - no full scan fallback
|
|
-- Returns item table or nil. Does NOT trigger IndexItems().
|
|
-- Use this for equipment swapping where we know the exact item name.
|
|
function CleveRoids.GetItemFast(text)
|
|
if not text or text == "" then return nil end
|
|
|
|
local Items = CleveRoids.Items
|
|
if not Items then return nil end
|
|
|
|
-- Direct cache lookup
|
|
local item = Items[text]
|
|
if item and type(item) == "table" then
|
|
return item
|
|
end
|
|
|
|
-- Try lowercase
|
|
local lowerText = string_lower(text)
|
|
local canonicalName = Items[lowerText]
|
|
if canonicalName and type(canonicalName) == "string" then
|
|
item = Items[canonicalName]
|
|
if item and type(item) == "table" then
|
|
return item
|
|
end
|
|
end
|
|
|
|
-- Try as item ID
|
|
local itemId = tonumber(text)
|
|
if itemId then
|
|
local name = Items[itemId]
|
|
if name and type(name) == "string" then
|
|
item = Items[name]
|
|
if item and type(item) == "table" then
|
|
return item
|
|
end
|
|
end
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
-- PERFORMANCE: Quick scan for a single item by name (doesn't update full cache)
|
|
-- Only scans until item is found, then stops.
|
|
-- Optimized: extracts name from link directly instead of calling GetItemInfo.
|
|
-- Uses cache with VALIDATION - checks if item is actually at cached location before trusting it.
|
|
-- v2.18+: Uses native FindPlayerItemSlot for O(1) lookup when available.
|
|
function CleveRoids.FindItemQuick(text)
|
|
if not text or text == "" then return nil end
|
|
|
|
-- v2.18+: Try native fast lookup first (much faster than Lua scanning)
|
|
local API = CleveRoids.NampowerAPI
|
|
if API and API.features and API.features.hasFindPlayerItemSlot then
|
|
local itemInfo = API.FindItemFast(text)
|
|
if itemInfo then
|
|
-- Update cache with the found item for future lookups
|
|
local Items = CleveRoids.Items or {}
|
|
if itemInfo.name then
|
|
Items[itemInfo.name] = itemInfo
|
|
Items[string_lower(itemInfo.name)] = itemInfo.name
|
|
if itemInfo.itemId then
|
|
Items[itemInfo.itemId] = itemInfo.name
|
|
end
|
|
end
|
|
return itemInfo
|
|
end
|
|
-- Item not found via native lookup - no need to scan
|
|
return nil
|
|
end
|
|
|
|
-- Fallback: no v2.18 API, use existing cache + scan logic
|
|
local Items = CleveRoids.Items or {}
|
|
local qid = tonumber(text)
|
|
-- Only compute lowercase name if we're NOT searching by ID
|
|
local qname = (not qid) and string_lower(text) or nil
|
|
|
|
-- Try cache first, but VALIDATE the cached location is still correct
|
|
local cached = CleveRoids.GetItemFast(text)
|
|
if cached then
|
|
-- Validate: check if item is actually at the cached location
|
|
if cached.inventoryID then
|
|
if qid then
|
|
if GetInventoryItemID("player", cached.inventoryID) == qid then
|
|
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
|
|
cached._validated = true
|
|
return cached -- Cache is valid
|
|
end
|
|
end
|
|
-- Cache is stale - item not at cached equipped slot, invalidate
|
|
if cached.name then
|
|
Items[cached.name] = nil
|
|
Items[string_lower(cached.name)] = nil
|
|
end
|
|
elseif cached.bagID and cached.slot then
|
|
if qid then
|
|
if C_Container.GetContainerItemID(cached.bagID, cached.slot) == qid then
|
|
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
|
|
cached._validated = true
|
|
return cached -- Cache is valid
|
|
end
|
|
end
|
|
-- Cache is stale - item not at cached bag slot, invalidate
|
|
if cached.name then
|
|
Items[cached.name] = nil
|
|
Items[string_lower(cached.name)] = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Cache miss or stale - do a fresh scan
|
|
-- Quick scan equipped items first (only 19 slots)
|
|
for inv = 1, 19 do
|
|
local link = GetInventoryItemLink("player", inv)
|
|
if link then
|
|
local itemID = GetInventoryItemID("player", inv)
|
|
-- ID match: fast path
|
|
if qid and qid == itemID then
|
|
return makeInventoryItem(inv, link, Items)
|
|
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 makeInventoryItem(inv, link, Items)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Quick scan bags - stop as soon as found
|
|
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 = C_Container.GetContainerItemID(bag, slot)
|
|
-- ID match: fast path
|
|
if qid and qid == itemID then
|
|
return makeBagItem(bag, slot, link, Items)
|
|
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
|
|
|
|
return nil
|
|
end
|
|
|
|
-- PERFORMANCE: Check if item is already equipped in slot (fast path)
|
|
-- Returns true if the item (by name or ID) is already in the specified slot
|
|
function CleveRoids.IsItemEquipped(text, inventoryId)
|
|
if not text or not inventoryId then return false end
|
|
|
|
local currentID = GetInventoryItemID("player", inventoryId)
|
|
if not currentID then return false end
|
|
|
|
-- Check by ID (fast path)
|
|
local textId = tonumber(text)
|
|
if textId and textId == currentID then
|
|
return true
|
|
end
|
|
|
|
-- Check by name (decorated, so suffixed gear still matches)
|
|
local currentName = C_Item.GetItemName({ equipmentSlotIndex = inventoryId })
|
|
if currentName and string_lower(currentName) == string_lower(text) then
|
|
return true
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
local Extension = CleveRoids.RegisterExtension("Generic_show")
|
|
Extension.RegisterEvent("SPELLS_CHANGED", "SPELLS_CHANGED")
|
|
|
|
function Extension.OnLoad()
|
|
end
|
|
|
|
function Extension.SPELLS_CHANGED()
|
|
end
|