mirror of
https://github.com/brues-code/SuperCleveRoidMacros.git
synced 2026-09-16 03:38:00 +00:00
5213bd5474
Squash of the classicapi_next branch (10 commits). ClassicAPI is now a hard requirement, and the Lua-side reimplementations it supersedes are gone: net -1087 lines across 12 files. Macro display: resolved macro actions are published through C_Macro.SetMacroDisplay, so the client draws macro buttons and the action-bar function overrides this addon used to install are removed. Ownership is per macro -- ClassicAPI keeps the macros we never claim. Auras and timing: ClassicAPI is the source of truth for non-player aura timing via the positional C_UnitAuras.UnitAura, replacing the write-only buff tracking tables and the overflow-slot fallback. Cleanup: the hasPfUI76 flag and the pfUI 7.6 branches it gated, two silently-shadowed code paths, and per-call allocations in the event and publish paths (SPELL_CAST_EVENT merged). Packaging moves to brues-code/packager@vCAPI. Macro syntax: a clause may carry a leading run of [group] blocks sharing one action, Blizzard-style -- groups are OR'd, first pass wins, and [] always passes. The ;-separated form is unchanged and mixes freely. @focus clauses with no focus set now fail quietly instead of printing "Invalid target" on the way past.
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
|