10 Commits

Author SHA1 Message Date
Brues d060b04908 Reclaim SendChatMessage hook so #showtooltip stays filtered
Our #showtooltip filter hooks the global SendChatMessage. An addon that
snapshots SendChatMessage at its own file-load and later calls that
snapshot directly (e.g. LeafVillageAchievements at PLAYER_ENTERING_WORLD+3s)
orphans our hook if it loaded before us -- its snapshot predates our filter,
so #showtooltip leaks to chat. This bites the fork specifically: it sorts
after LeafVillageAchievements alphabetically, so it loads too late to be in
the snapshot, whereas upstream "CleveRoidMacros" sorted before it.

Make the filter a named function and add EnsureSendChatMessageHook, which
re-asserts it as the outermost SendChatMessage hook (no-op once on top).
OnUpdate calls it each throttled tick via a cheap identity check, so we
reclaim the top of the chain within a frame of being displaced. Normal
messages still flow through the chained-over hook untouched.
2026-07-24 09:41:26 -05:00
Brues 6ac69501f5 pass base object rather than ItemLocation 2026-07-20 10:35:09 -05:00
Brues 782c685fba Replace tooltip-scanning GetSpellCost with ClassicAPI DBC reads
GetSpellCost now reads power cost and reagents straight from Spell.dbc via
C_Spell.GetSpellPowerCost (effective, talent-modified cost) and
C_Spell.GetSpellReagents (itemID), dropping the GameTooltip owner/scan
frames, their font strings, and all locale-dependent line parsing.

Reagent counting is now itemID-based end to end: GetReagentCount takes an
itemID and matches by id in the Items cache / bag scan. This removes the
hardcoded English reagent tables (_ReagentBySpell, _ReagentIdByName) and
the name-matching bag-scan tooltip, so it works on any client locale.
Verified in-game that GetSpellReagents covers DBC reagent spells (Vanish
-> Flash Powder), which the hand table previously special-cased.

The localized reagent name is still used for countedItemTypes registration
(recognizing a reagent item placed on the action bar); when that name
isn't cached yet, warm it via the ClassicAPI Item mixin
(Item:CreateFromItemID/ContinueOnItemLoad) and register it once it lands.
2026-07-19 16:34:19 -05:00
Brues 97e898f14c Add /cancelform command and CancelShapeshiftForm
Adds a new /cancelform slash command (Console.lua) wired to CleveRoids.DoCancelForm. Replaces the previous _unshiftAction with _cancelFormAction that calls CancelShapeshiftForm, and updates DoUnshift to delegate to DoCancelForm for backwards compatibility. Also fixes a unit name check by replacing GetUnitName with UnitName in DoRetarget.
2026-07-19 14:32:13 -05:00
Brues b84835a567 Use GetInventoryItemID instead of string parsing 2026-07-17 21:25:25 -05:00
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
5 changed files with 292 additions and 626 deletions
+41 -69
View File
@@ -273,24 +273,17 @@ local function BuildEquipmentCache()
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
local link = GetInventoryItemLink("player", slot)
if link then
local _, _, id = string_find(link, "item:(%d+)")
local _, _, nameInBrackets = string_find(link, "%[(.+)%]")
if id then
_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
local id = GetInventoryItemID("player", slot)
if id then
_equippedItemIDs[slot] = id
local name = C_Item.GetItemName({ equipmentSlotIndex = slot })
if name then
_equippedItemNames[slot] = string_lower(name)
end
end
end
@@ -350,8 +343,7 @@ function CleveRoids.FindItemLocation(item)
if numericItem then
-- Check if it's an equipment slot (1-19)
if numericItem >= 1 and numericItem <= 19 then
local link = GetInventoryItemLink("player", numericItem)
if link then
if GetInventoryItemID("player", numericItem) then
return { type = "inventory", inventoryID = numericItem }
end
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
local slotNum = tonumber(name)
if slotNum and slotNum >= 1 and slotNum <= 19 then
local link = GetInventoryItemLink("player", slotNum)
if link then
local _, _, itemName = string.find(link, "%[(.+)%]")
if itemName then name = itemName end
end
local itemName = C_Item.GetItemName({ equipmentSlotIndex = slotNum })
if itemName then name = itemName end
end
args = {name = name}
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
local slotNum = tonumber(name)
if slotNum and slotNum >= 1 and slotNum <= 19 then
local link = GetInventoryItemLink("player", slotNum)
if link then
local _, _, itemName = string.find(link, "%[(.+)%]")
if itemName then name = itemName end
end
local itemName = C_Item.GetItemName({ equipmentSlotIndex = slotNum })
if itemName then name = itemName end
end
args.name = name
else
@@ -4869,13 +4855,11 @@ function CleveRoids.HasItem(item)
if type(item) == "string" and item ~= "" then
local itemLower = string.lower(item)
-- Check equipped slots for substring match
for slot = 0, 19 do
local link = GetInventoryItemLink("player", slot)
if link then
if string.find(string.lower(link), itemLower, 1, true) then
return true
end
-- Check equipped slots for substring match (decorated name, no link build)
for slot = 1, 19 do
local name = C_Item.GetItemName({ equipmentSlotIndex = slot })
if name and string.find(string.lower(name), itemLower, 1, true) then
return true
end
end
@@ -4884,11 +4868,9 @@ function CleveRoids.HasItem(item)
local size = GetContainerNumSlots(bag)
if size and size > 0 then
for slotIndex = 1, size do
local link = GetContainerItemLink(bag, slotIndex)
if link then
if string.find(string.lower(link), itemLower, 1, true) then
return true
end
local name = C_Item.GetItemName({ bagID = bag, slotIndex = slotIndex })
if name and string.find(string.lower(name), itemLower, 1, true) then
return true
end
end
end
@@ -4933,14 +4915,12 @@ function CleveRoids.GetItemCooldown(item)
local itemLower = string.lower(item)
local start, dur, en
-- Check equipped slots for substring match
for slot = 0, 19 do
local link = GetInventoryItemLink("player", slot)
if link then
if string.find(string.lower(link), itemLower, 1, true) then
start, dur, en = GetInventoryItemCooldown("player", slot)
return _norm(start, dur, en)
end
-- Check equipped slots for substring match (decorated name, no link build)
for slot = 1, 19 do
local name = C_Item.GetItemName({ equipmentSlotIndex = slot })
if name and string.find(string.lower(name), itemLower, 1, true) then
start, dur, en = GetInventoryItemCooldown("player", slot)
return _norm(start, dur, en)
end
end
@@ -4949,12 +4929,10 @@ function CleveRoids.GetItemCooldown(item)
local size = GetContainerNumSlots(bag)
if size and size > 0 then
for slotIndex = 1, size do
local link = GetContainerItemLink(bag, slotIndex)
if link then
if string.find(string.lower(link), itemLower, 1, true) then
start, dur, en = GetContainerItemCooldown(bag, slotIndex)
return _norm(start, dur, en)
end
local name = C_Item.GetItemName({ bagID = bag, slotIndex = slotIndex })
if name and string.find(string.lower(name), itemLower, 1, true) then
start, dur, en = GetContainerItemCooldown(bag, slotIndex)
return _norm(start, dur, en)
end
end
end
@@ -5887,13 +5865,10 @@ CleveRoids.Keywords = {
local itemName = name
local slotNum = tonumber(name)
if slotNum and slotNum >= 1 and slotNum <= 19 then
-- Resolve slot number to item name
local link = GetInventoryItemLink("player", slotNum)
if link then
local _, _, extractedName = string.find(link, "%[(.+)%]")
if extractedName then
itemName = extractedName
end
-- Resolve slot number to item name (decorated, no link build)
local extractedName = C_Item.GetItemName({ equipmentSlotIndex = slotNum })
if extractedName then
itemName = extractedName
end
end
@@ -5925,13 +5900,10 @@ CleveRoids.Keywords = {
local itemName = name
local slotNum = tonumber(name)
if slotNum and slotNum >= 1 and slotNum <= 19 then
-- Resolve slot number to item name
local link = GetInventoryItemLink("player", slotNum)
if link then
local _, _, extractedName = string.find(link, "%[(.+)%]")
if extractedName then
itemName = extractedName
end
-- Resolve slot number to item name (decorated, no link build)
local extractedName = C_Item.GetItemName({ equipmentSlotIndex = slotNum })
if extractedName then
itemName = extractedName
end
end
+4
View File
@@ -67,6 +67,10 @@ SLASH_UNSHIFT1 = "/unshift"
SlashCmdList.UNSHIFT = CleveRoids.DoUnshift
SLASH_CANCELFORM1 = "/cancelform"
SlashCmdList.CANCELFORM = CleveRoids.DoCancelForm
SLASH_UNQUEUE1 = "/unqueue"
SlashCmdList.UNQUEUE = SpellStopCasting
+177 -453
View File
@@ -46,10 +46,8 @@ CleveRoids.spellNameCache = {}
local GetTime = GetTime
local UnitExists = UnitExists
local UnitAffectingCombat = UnitAffectingCombat
local GetContainerItemLink = GetContainerItemLink
local GetContainerItemInfo = GetContainerItemInfo
local GetContainerNumSlots = GetContainerNumSlots
local GetInventoryItemLink = GetInventoryItemLink
local GetItemInfo = GetItemInfo
local PickupContainerItem = PickupContainerItem
local PickupInventoryItem = PickupInventoryItem
@@ -63,7 +61,6 @@ local ipairs = ipairs
local type = type
local tonumber = tonumber
local tostring = tostring
local string_find = string.find
local string_lower = string.lower
local string_gsub = string.gsub
local table_insert = table.insert
@@ -577,118 +574,34 @@ function CleveRoids.QueueActionUpdate()
end
end
local function _StripColor(s)
if not s then return s end
if string.sub(s,1,2) == "|c" then return string.sub(s,11,-3) end
return s
end
-- Count of `itemId` in *bags only* (not bank), matched by itemID throughout --
-- locale-independent, no hardcoded item names.
-- PERFORMANCE: uses the CleveRoids.Items cache (itemId -> name -> data) for an
-- O(1) lookup when available, falling back to a bag scan on a cache miss.
function CleveRoids.GetReagentCount(itemId)
if not itemId then return 0 end
local _ReagentBySpell = {
["Vanish"] = "Flash Powder", -- 5140
["Blind"] = "Blinding Powder", -- 5530
}
-- Expose for use in Generic.lua (IndexSpells fallback)
CleveRoids.ReagentBySpell = _ReagentBySpell
-- Minimal map for rogue reagents; extend as needed
local _ReagentIdByName = {
["Flash Powder"] = 5140, -- Vanish
["Blinding Powder"] = 5530, -- Blind
}
-- Lazy bag-scan tooltip (only if we need to scan a bag slot by name)
local function CRM_GetBagScanTip()
local tip = _G.CleveRoidsBagScanTip
if tip then return tip end
local ok, created = pcall(CreateFrame, "GameTooltip", "CleveRoidsBagScanTip", UIParent, "GameTooltipTemplate")
if ok and created then
tip = created
else
tip = CreateFrame("GameTooltip", "CleveRoidsBagScanTip", UIParent)
local L1 = tip:CreateFontString("$parentTextLeft1", nil, "GameTooltipText")
local R1 = tip:CreateFontString("$parentTextRight1", nil, "GameTooltipText")
tip:AddFontStrings(L1, R1)
for i=2,10 do
tip:CreateFontString("$parentTextLeft"..i, nil, "GameTooltipText")
tip:CreateFontString("$parentTextRight"..i, nil, "GameTooltipText")
end
end
tip:SetOwner(WorldFrame, "ANCHOR_NONE")
_G.CleveRoidsBagScanTip = tip
return tip
end
-- Return total in *bags only* (not bank), by id when possible; fallback to name
-- PERFORMANCE: Uses CleveRoids.Items cache for O(1) lookup when available
function CleveRoids.GetReagentCount(reagentName)
if not reagentName or reagentName == "" then return 0 end
-- Fast path: check cache first
local Items = CleveRoids.Items
if Items then
local wantId = _ReagentIdByName[reagentName]
-- Try by ID first if we have a mapping
if wantId then
local itemName = Items[wantId]
if itemName then
local itemData = Items[itemName]
if itemData and itemData.count and not itemData.inventoryID then
-- Item is in bags (not equipped), return cached count
return itemData.count
end
local itemName = Items[itemId]
if itemName then
local itemData = Items[itemName]
if itemData and itemData.count and not itemData.inventoryID then
-- Item is in bags (not equipped), return cached count
return itemData.count
end
end
-- Try by name
local itemData = Items[reagentName]
if type(itemData) == "string" then
itemData = Items[itemData] -- Resolve indirection
end
if itemData and type(itemData) == "table" and itemData.count and not itemData.inventoryID then
return itemData.count
end
-- Try lowercase
local lowerName = string.lower(reagentName)
local resolved = Items[lowerName]
if type(resolved) == "string" then
itemData = Items[resolved]
elseif type(resolved) == "table" then
itemData = resolved
end
if itemData and type(itemData) == "table" and itemData.count and not itemData.inventoryID then
return itemData.count
end
end
-- Slow path fallback: full bag scan (only when cache miss)
local wantId = _ReagentIdByName[reagentName]
-- Slow path: scan bags by itemID (only on a cache miss)
local total = 0
for bag = 0, 4 do
local slots = GetContainerNumSlots(bag) or 0
for slot = 1, slots do
local _, count = GetContainerItemInfo(bag, slot)
count = count or 0
local link = GetContainerItemLink and GetContainerItemLink(bag, slot)
if link then
local _, _, idstr = string.find(link, "item:(%d+)")
local id = idstr and tonumber(idstr) or nil
if (wantId and id == wantId) or (not wantId and string.find(link, "%["..reagentName.."%]")) then
total = total + count
end
else
-- 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
end
-- Base itemID straight off the slot (no link string, no regex).
if C_Container.GetContainerItemID(bag, slot) == itemId then
local _, count = GetContainerItemInfo(bag, slot)
total = total + (count or 0)
end
end
end
@@ -701,102 +614,55 @@ end
function CleveRoids.GetLiveItemCount(itemName)
if not itemName or itemName == "" then return 0 end
-- Escape Lua pattern special chars in item name to avoid crashes
local escaped = string.gsub(itemName, "([%(%)%.%%%+%-%*%?%[%]%^%$])", "%%%1")
local pattern = "%[" .. escaped .. "%]"
local lowerPattern = string.lower(pattern)
local wantLower = string.lower(itemName)
local total = 0
for bag = 0, 4 do
local slots = GetContainerNumSlots(bag) or 0
for slot = 1, slots do
local link = GetContainerItemLink and GetContainerItemLink(bag, slot)
if link and string.find(string.lower(link), lowerPattern) then
local _, count = GetContainerItemInfo(bag, slot)
total = total + (count or 0)
local id = C_Container.GetContainerItemID(bag, slot)
if id then
local name = C_Item.GetItemNameByID(id)
if name and string.lower(name) == wantLower then
local _, count = GetContainerItemInfo(bag, slot)
total = total + (count or 0)
end
end
end
end
return total
end
function CleveRoids.GetSpellCost(spellSlot, bookType)
-- Fast path: existing fixed-slot read
CleveRoids.Frame:SetOwner(WorldFrame, "ANCHOR_NONE")
CleveRoids.Frame:SetSpell(spellSlot, bookType)
local cost, reagent
local costText = CleveRoids.Frame.costFontString:GetText()
if costText then
_, _, cost = string.find(costText, "^(%d+)%s+[^yYsS]")
-- Power cost and (first) reagent for a spellbook slot, read from Spell.dbc via
-- ClassicAPI -- no GameTooltip scan, no locale-dependent string parsing. Pass
-- spellId to skip the slot->id resolution when the caller already has it.
-- cost: C_Spell.GetSpellPowerCost returns the *effective* (talent-modified)
-- cost the engine actually charges -- matches the old tooltip value,
-- unlike GetSpellInfo().cost which is base-only.
-- reagent: C_Spell.GetSpellReagents returns { {itemID, count}, ... }; take the
-- first reagent's itemID (Spell.dbc covers reagent spells like Vanish).
-- Returns cost, reagentName (localized, for display), reagentId (for counting).
function CleveRoids.GetSpellCost(spellSlot, bookType, spellId)
if not spellId then
spellId = select(10, GetSpellInfo(spellSlot, bookType))
end
local reagentText = CleveRoids.Frame.reagentFontString:GetText()
if reagentText then
_, _, reagent = string.find(reagentText, "^Reagents?%s*:%s*(.*)")
end
reagent = _StripColor(reagent)
if reagent == "" then reagent = nil end
local cost = 0
local reagent, reagentId
-- Fallback: scan all lines on a named tooltip (handles Vanish layout)
if not reagent or not cost then
local tip = CleveRoidsTooltipScan
if not tip then
-- belt & suspenders: create if somehow missing
local ok,_ = pcall(CreateFrame, "GameTooltip", "CleveRoidsTooltipScan", UIParent, "GameTooltipTemplate")
if not ok or not CleveRoidsTooltipScan then
CleveRoidsTooltipScan = CreateFrame("GameTooltip", "CleveRoidsTooltipScan", UIParent)
local L1 = CleveRoidsTooltipScan:CreateFontString("$parentTextLeft1", nil, "GameTooltipText")
local R1 = CleveRoidsTooltipScan:CreateFontString("$parentTextRight1", nil, "GameTooltipText")
CleveRoidsTooltipScan:AddFontStrings(L1, R1)
for i=2,32 do
CleveRoidsTooltipScan:CreateFontString("$parentTextLeft"..i, nil, "GameTooltipText")
CleveRoidsTooltipScan:CreateFontString("$parentTextRight"..i, nil, "GameTooltipText")
end
end
CleveRoidsTooltipScan:SetOwner(WorldFrame, "ANCHOR_NONE")
tip = CleveRoidsTooltipScan
if spellId then
local costs = C_Spell.GetSpellPowerCost(spellId)
if costs and costs[1] and costs[1].cost then
cost = costs[1].cost
end
tip:ClearLines()
tip:SetOwner(WorldFrame, "ANCHOR_NONE")
tip:SetSpell(spellSlot, bookType)
local base = tip:GetName() or "CleveRoidsTooltipScan"
local maxLines = (tip.NumLines and tip:NumLines()) or 32
for i = 1, maxLines do
local L = _G[base.."TextLeft"..i]
local R = _G[base.."TextRight"..i]
local lt = L and L:GetText() or ""
local rt = R and R:GetText() or ""
if not reagent then
if string.find(lt, "^[Rr]eagents?%s*:") then
reagent = _StripColor((rt ~= "" and rt) or (string.gsub(lt, "^[Rr]eagents?%s*:%s*", "")))
elseif string.find(rt, "^[Rr]eagents?%s*:") then
reagent = _StripColor((lt ~= "" and lt) or (string.gsub(rt, "^[Rr]eagents?%s*:%s*", "")))
end
if reagent == "" then reagent = nil end
end
if not cost and rt ~= "" then
local _, _, num = string.find(rt, "^(%d+)%s+(Mana|Energy|Rage|Focus)")
if num then cost = tonumber(num) end
end
if reagent and cost then break end
local reagents = C_Spell.GetSpellReagents(spellId)
if reagents and reagents[1] and reagents[1].itemID then
reagentId = reagents[1].itemID
reagent = C_Item.GetItemNameByID(reagentId)
end
end
if not reagent or reagent == "" then
reagent = nil
local name = GetSpellName(spellSlot, bookType)
if name then
name = CleveRoids.StripRank(name)
reagent = _ReagentBySpell[name]
end
end
return (cost and tonumber(cost) or 0), (reagent and reagent ~= "" and tostring(reagent) or nil)
return cost, (reagent and reagent ~= "" and tostring(reagent) or nil), reagentId
end
function CleveRoids.GetProxyActionSlot(slot)
@@ -2689,11 +2555,13 @@ function CleveRoids.DoWithConditionals(msg, hook, fixEmptyTargetFunc, targetBefo
CastSpellByName(castMsg)
end
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
CleveRoids.Print("|cff00ff00[EquipLog] Calling action('" .. tostring(msg) .. "')|r")
end
action(msg)
action(msg, conditionals.target)
end
end
@@ -3196,6 +3064,36 @@ function CleveRoids.DoConditionalClearTarget(msg)
return false
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
-- 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
@@ -3207,184 +3105,42 @@ function CleveRoids.DoUse(msg)
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
if type(CloseStackSplitFrame) == "function" then CloseStackSplitFrame() 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)
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
UseInventoryItem(slotId)
return
end
-- Try to interpret as item ID (numbers > 19)
-- v2.18+: Use FindPlayerItemSlot directly for item IDs (no name resolution needed)
if slotId and slotId > 19 then
local API = CleveRoids.NampowerAPI
-- v2.18+: Native lookup can find item directly by ID
if API and API.features and API.features.hasFindPlayerItemSlot then
local itemInfo = API.FindItemFast(slotId)
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
-- Equipped items (trinkets, weapons) live outside bags, so C_Item.UseItemByName
-- can't reach them. Fire an equipped match through its slot first.
local invSlot = FindEquippedItemSlot(msg, slotId)
if invSlot then
ClearCursor()
if CleveRoids.equipDebugLog then
CleveRoids.Print("|cff888888[UseLog] /use " .. msg .. " via UseInventoryItem(" .. invSlot .. ")|r")
end
UseInventoryItem(invSlot)
return
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
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
C_Item.UseItemByName(slotId or msg, useUnit)
end
-- PERFORMANCE: Use numeric iteration to avoid pairs() iterator allocation
@@ -3410,12 +3166,9 @@ local function FindItemInBagsByName(itemName)
for bag = 0, 4 do
local numSlots = GetContainerNumSlots(bag) or 0
for slot = 1, numSlots do
local link = GetContainerItemLink(bag, slot)
if link then
local _, _, name = string_find(link, "|h%[(.-)%]|h")
if name and string_lower(name) == lowerName then
return bag, slot
end
local name = C_Item.GetItemName({ bagID = bag, slotIndex = slot })
if name and string_lower(name) == lowerName then
return bag, slot
end
end
end
@@ -3552,12 +3305,7 @@ function CleveRoids.EquipBagItem(msg, slotOrOffhand)
end
-- Note what's currently in the target slot so we can invalidate its cache
local oldSlotLink = GetInventoryItemLink("player", invslot)
local oldSlotName = nil
if oldSlotLink then
local _, _, name = string_find(oldSlotLink, "|h%[(.-)%]|h")
oldSlotName = name
end
local oldSlotName = C_Item.GetItemName({ equipmentSlotIndex = invslot })
-- Helper to invalidate displaced item's cache
local function InvalidateDisplacedItem()
@@ -3574,10 +3322,9 @@ function CleveRoids.EquipBagItem(msg, slotOrOffhand)
local pairedSlots = {[13] = 14, [14] = 13, [16] = 17, [17] = 16, [11] = 12, [12] = 11}
local checkSlot = pairedSlots[invslot]
if checkSlot then
local link = GetInventoryItemLink("player", checkSlot)
if link then
local _, _, slotItemName = string_find(link, "|h%[(.-)%]|h")
if slotItemName and string_lower(slotItemName) == string_lower(msg) then
local slotItemName = C_Item.GetItemName({ equipmentSlotIndex = checkSlot })
if slotItemName then
if string_lower(slotItemName) == string_lower(msg) then
-- Item found in paired slot - but prefer a bag copy if one exists
local bagCopyBag, bagCopySlot = FindItemInBagsByName(msg)
if bagCopyBag then
@@ -3629,17 +3376,14 @@ function CleveRoids.EquipBagItem(msg, slotOrOffhand)
-- Verify the item actually landed in the target slot
-- 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)
local newLink = GetInventoryItemLink("player", invslot)
if newLink then
local _, _, newName = string_find(newLink, "|h%[(.-)%]|h")
if newName and string_lower(newName) == string_lower(msg) then
if CleveRoids.Items then
CleveRoids.Items[msg] = nil
CleveRoids.Items[string_lower(msg)] = nil
end
InvalidateDisplacedItem()
return true
local newName = C_Item.GetItemName({ equipmentSlotIndex = invslot })
if newName and string_lower(newName) == string_lower(msg) then
if CleveRoids.Items then
CleveRoids.Items[msg] = nil
CleveRoids.Items[string_lower(msg)] = nil
end
InvalidateDisplacedItem()
return true
end
-- Verification failed - EquipItemByName didn't place item in target slot
if CleveRoids.equipDebugLog then
@@ -3681,10 +3425,9 @@ function CleveRoids.EquipBagItem(msg, slotOrOffhand)
local ok = pcall(EquipItemByName, item.name, invslot)
if ok then
-- Verify the item actually landed in the target slot (same guard as above)
local newLink = GetInventoryItemLink("player", invslot)
if newLink then
local _, _, newName = string_find(newLink, "|h%[(.-)%]|h")
if newName and string_lower(newName) == string_lower(item.name) then
local newName = C_Item.GetItemName({ equipmentSlotIndex = invslot })
if newName then
if string_lower(newName) == string_lower(item.name) then
if CleveRoids.Items then
CleveRoids.Items[item.name] = nil
CleveRoids.Items[string_lower(item.name)] = nil
@@ -3773,11 +3516,8 @@ local function _equipRing2Action(msg)
return CleveRoids.EquipBagItem(msg, 12)
end
local function _unshiftAction()
local currentShapeshiftIndex = CleveRoids.GetCurrentShapeshiftIndex()
if currentShapeshiftIndex ~= 0 then
CastShapeshiftForm(currentShapeshiftIndex)
end
local function _cancelFormAction()
CancelShapeshiftForm()
end
function CleveRoids.DoEquipMainhand(msg)
@@ -3848,27 +3588,29 @@ function CleveRoids.DoEquipRing2(msg)
return false
end
function CleveRoids.DoUnshift(msg)
function CleveRoids.DoCancelForm(msg)
local handled
-- PERFORMANCE: Use numeric iteration to avoid pairs() iterator allocation
local parts = CleveRoids.splitStringIgnoringQuotes(msg)
for i = 1, table.getn(parts) do
handled = false
if CleveRoids.DoWithConditionals(parts[i], _unshiftAction, CleveRoids.FixEmptyTarget, false, _unshiftAction) then
if CleveRoids.DoWithConditionals(parts[i], _cancelFormAction, CleveRoids.FixEmptyTarget, false, _cancelFormAction) then
handled = true
break
end
end
if handled == nil then
_unshiftAction()
_cancelFormAction()
end
return handled
end
CleveRoids.DoUnshift = CleveRoids.DoCancelForm
function CleveRoids.DoRetarget()
if GetUnitName("target") == nil
if UnitName("target") == nil
or UnitHealth("target") == 0
or not UnitCanAttack("player", "target")
then
@@ -4193,6 +3935,13 @@ function CleveRoids.OnUpdate(self)
CR.lastUpdate = time
-- Reclaim the top of the SendChatMessage hook chain if another addon has
-- displaced our #showtooltip filter (see EnsureSendChatMessageHook). Cheap
-- identity check; only re-hooks when actually displaced.
if CR.EnsureSendChatMessageHook then
CR.EnsureSendChatMessageHook()
end
-- Process deferred equipment index updates (for throttled UNIT_INVENTORY_CHANGED)
-- PERFORMANCE: Skip check entirely if no pending update
local pendingTime = CR.equipIndexPendingTime
@@ -4921,21 +4670,17 @@ function GetActionCount(slot)
count = CleveRoids.GetLiveItemCount(actionToCheck.item.name or actionToCheck.action)
elseif actionToCheck.spell then
local reagent = actionToCheck.spell.reagent
if not reagent or reagent == "" then
reagent = nil
local reagentId = actionToCheck.spell.reagentId
if not reagentId then
local ss, bt = actionToCheck.spell.spellSlot, actionToCheck.spell.bookType
if ss and bt then
local _, r = CleveRoids.GetSpellCost(ss, bt)
reagent = r
local _, _, rid = CleveRoids.GetSpellCost(ss, bt, actionToCheck.spell.id)
actionToCheck.spell.reagentId = rid -- cache itemID so we don't re-derive every frame
reagentId = rid
end
if (not reagent) and _ReagentBySpell and actionToCheck.spell.name then
reagent = _ReagentBySpell[actionToCheck.spell.name] -- e.g., Vanish → Flash Powder
end
actionToCheck.spell.reagent = reagent -- cache it so we don't re-scan every frame
end
if reagent and reagent ~= "" then
count = CleveRoids.GetReagentCount(reagent) -- id-first bag scan, falls back to name/tooltip
if reagentId then
count = CleveRoids.GetReagentCount(reagentId) -- id-based bag scan
end
end
end
@@ -4973,17 +4718,8 @@ function IsConsumableAction(slot)
end
if actionToCheck.spell then
local reagent = actionToCheck.spell.reagent
if (not reagent or reagent == "") and actionToCheck.spell.name then
reagent = _ReagentBySpell[actionToCheck.spell.name]
if reagent then
actionToCheck.spell.reagent = reagent
end
end
if reagent and reagent ~= "" then
return 1
end
if actionToCheck.spell and actionToCheck.spell.reagentId then
return 1
end
end
@@ -5053,37 +4789,9 @@ if not CleveRoids.RunMacroHooked then
CleveRoids.RunMacroHooked = true
end
-- Robust named tooltip for scanning spells/items
if not CleveRoidsTooltipScan then
-- Try to create with the standard template first
local ok, _ = pcall(CreateFrame, "GameTooltip", "CleveRoidsTooltipScan", UIParent, "GameTooltipTemplate")
if not ok or not CleveRoidsTooltipScan then
-- Fallback: manual tooltip with plenty of prebuilt lines
CleveRoidsTooltipScan = CreateFrame("GameTooltip", "CleveRoidsTooltipScan", UIParent)
local L1 = CleveRoidsTooltipScan:CreateFontString("$parentTextLeft1", nil, "GameTooltipText")
local R1 = CleveRoidsTooltipScan:CreateFontString("$parentTextRight1", nil, "GameTooltipText")
CleveRoidsTooltipScan:AddFontStrings(L1, R1)
for i = 2, 32 do
CleveRoidsTooltipScan:CreateFontString("$parentTextLeft"..i, nil, "GameTooltipText")
CleveRoidsTooltipScan:CreateFontString("$parentTextRight"..i, nil, "GameTooltipText")
end
end
CleveRoidsTooltipScan:SetOwner(WorldFrame, "ANCHOR_NONE")
CleveRoidsTooltipScan:EnableMouse(false)
end
-- This single dummy frame handles events AND serves as our tooltip scanner.
CleveRoids.Frame = CreateFrame("GameTooltip")
CleveRoids.Frame:EnableMouse(false) -- Prevent tooltip from capturing mouse input
-- Create the extra font strings needed for other functions like GetSpellCost.
CleveRoids.Frame.costFontString = CleveRoids.Frame:CreateFontString()
CleveRoids.Frame.rangeFontString = CleveRoids.Frame:CreateFontString()
CleveRoids.Frame.reagentFontString = CleveRoids.Frame:CreateFontString()
CleveRoids.Frame:AddFontStrings(CleveRoids.Frame:CreateFontString(), CleveRoids.Frame:CreateFontString())
CleveRoids.Frame:AddFontStrings(CleveRoids.Frame.costFontString, CleveRoids.Frame.rangeFontString)
CleveRoids.Frame:AddFontStrings(CleveRoids.Frame:CreateFontString(), CleveRoids.Frame:CreateFontString())
CleveRoids.Frame:AddFontStrings(CleveRoids.Frame.reagentFontString, CleveRoids.Frame:CreateFontString())
-- This single frame drives the addon's event loop and OnUpdate. (Spell cost /
-- reagent lookups now read Spell.dbc directly via ClassicAPI -- no tooltip scan.)
CleveRoids.Frame = CreateFrame("Frame")
CleveRoids.Frame:SetScript("OnUpdate", CleveRoids.OnUpdate)
CleveRoids.Frame:SetScript("OnEvent", function(...)
@@ -5263,8 +4971,7 @@ function CleveRoids.DoWDBWarmup()
for bag = 0, 4 do
local slots = GetContainerNumSlots(bag) or 0
for slot = 1, slots do
local link = GetContainerItemLink(bag, slot)
if link then
if C_Container.GetContainerItemID(bag, slot) then
-- Tooltip scan loads the item into WDB
tip:ClearLines()
tip:SetBagItem(bag, slot)
@@ -5274,9 +4981,8 @@ function CleveRoids.DoWDBWarmup()
end
-- Scan equipped items
for slot = 0, 19 do
local link = GetInventoryItemLink("player", slot)
if link then
for slot = 1, 19 do
if GetInventoryItemID("player", slot) then
tip:ClearLines()
tip:SetInventoryItem("player", slot)
scanned = scanned + 1
@@ -6291,18 +5997,36 @@ function CleveRoids.Frame:KEY_UP()
CleveRoids.isActionUpdateQueued = true
end
CleveRoids.Hooks.SendChatMessage = SendChatMessage
function SendChatMessage(msg, ...)
-- Filter out #showtooltip lines
-- pfUI's macrotweak also does this, but our pattern is more specific
-- Filter out the #showtooltip line that Blizzard's native macro executor sends
-- to chat when a macro runs from an action button. Named (not anonymous) so we
-- can detect displacement and reclaim the top of the hook chain.
local function CleveRoids_SendChatMessage(msg, ...)
if msg and string.find(msg, "^#showtooltip") then
return
end
-- Call the original (or pfUI's hook if it's in the chain)
-- Call whatever we chained over (another addon's hook, or the real function)
CleveRoids.Hooks.SendChatMessage(msg, unpack(arg))
end
-- (Re-)assert our filter as the OUTERMOST SendChatMessage hook. This is a no-op
-- once we're already on top. It matters because some addons snapshot
-- SendChatMessage at their file-load and later install a hook that calls that
-- snapshot DIRECTLY -- if they loaded before us, their snapshot predates our
-- filter, so calling it directly orphans us and #showtooltip leaks to chat.
-- (LeafVillageAchievements does exactly this at PLAYER_ENTERING_WORLD+3s; and
-- because the fork sorts after it alphabetically, we load too late to be in its
-- snapshot -- upstream "CleveRoidMacros" sorted before it and wasn't affected.)
-- OnUpdate calls this so we reclaim the top within a frame of being displaced;
-- being outermost also guarantees we see the pristine line for the anchor match.
function CleveRoids.EnsureSendChatMessageHook()
if SendChatMessage ~= CleveRoids_SendChatMessage then
CleveRoids.Hooks.SendChatMessage = SendChatMessage
SendChatMessage = CleveRoids_SendChatMessage
end
end
CleveRoids.EnsureSendChatMessageHook()
CleveRoids.RegisterActionEventHandler = function(fn)
if type(fn) == "function" then
table.insert(CleveRoids.actionEventHandlers, fn)
@@ -7329,9 +7053,9 @@ local function FindItemInBags(itemName)
local searchName = string.lower(itemName)
for bag = 0, 4 do
for slot = 1, GetContainerNumSlots(bag) do
local link = GetContainerItemLink(bag, slot)
if link then
local _, _, foundName = string.find(link, "%[(.+)%]")
local id = C_Container.GetContainerItemID(bag, slot)
if id then
local foundName = C_Item.GetItemNameByID(id)
if foundName and string.find(string.lower(foundName), searchName, 1, true) then
return bag, slot, foundName
end
+69 -98
View File
@@ -58,11 +58,7 @@ function CleveRoids.IndexSpells()
bookType = CleveRoids.bookTypes[book]
spells[bookType] = {}
else
local cost, reagent = CleveRoids.GetSpellCost(i, bookType)
-- Fallback for known reagent spells if tooltip scan failed
if (not reagent or reagent == "") and CleveRoids.ReagentBySpell then
reagent = CleveRoids.ReagentBySpell[spellName]
end
local cost, reagent, reagentId = CleveRoids.GetSpellCost(i, bookType, spellId)
if not spells[bookType][spellName] then
spells[bookType][spellName] = {
spellSlot = i,
@@ -71,7 +67,7 @@ function CleveRoids.IndexSpells()
bookType = bookType,
texture = texture,
cost = cost,
reagent = reagent,
reagentId = reagentId,
}
end
if spellRank and not spells[bookType][spellName][spellRank] then
@@ -83,7 +79,7 @@ function CleveRoids.IndexSpells()
bookType = bookType,
texture = texture,
cost = cost,
reagent = reagent
reagentId = reagentId,
}
spells[bookType][spellName].highest = spells[bookType][spellName][spellRank]
end
@@ -94,6 +90,13 @@ function CleveRoids.IndexSpells()
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
@@ -156,10 +159,9 @@ end
function CleveRoids.IndexEquippedItems()
local items = CleveRoids.Items or {}
for inventoryID = 0, 19 do
local link = GetInventoryItemLink("player", inventoryID)
if link then
local _, _, itemID = string.find(link, "item:(%d+)")
for inventoryID = 1, 19 do
local itemID = GetInventoryItemID("player", inventoryID)
if itemID then
local name, link, _, _, itemType, itemSubType, _, _, texture = GetItemInfo(itemID)
if name then
local count = GetInventoryItemCount("player", inventoryID)
@@ -199,10 +201,9 @@ function CleveRoids.IndexEquipSlot(inventoryID)
if not inventoryID then return end
local items = CleveRoids.Items or {}
local link = GetInventoryItemLink("player", inventoryID)
local itemID = GetInventoryItemID("player", inventoryID)
if link then
local _, _, itemID = string.find(link, "item:(%d+)")
if itemID then
local name, itemLink, _, _, itemType, itemSubType, _, _, texture = GetItemInfo(itemID)
if name then
local count = GetInventoryItemCount("player", inventoryID)
@@ -265,21 +266,17 @@ function CleveRoids.IndexItems()
-- PERFORMANCE: Local function references
local GetContainerNumSlots = GetContainerNumSlots
local GetContainerItemLink = GetContainerItemLink
local GetContainerItemInfo = GetContainerItemInfo
local GetInventoryItemLink = GetInventoryItemLink
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 link = GetContainerItemLink(bagID, slot)
if link then
local _, _, itemID = string_find(link, "item:(%d+)")
-- PERFORMANCE: Try to extract name from link first to check for duplicates
local _, _, linkName = string_find(link, "%[(.+)%]")
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
@@ -317,13 +314,11 @@ function CleveRoids.IndexItems()
end
-- Scan equipped items
for inventoryID = 0, 19 do
local link = GetInventoryItemLink("player", inventoryID)
if link then
local _, _, itemID = string_find(link, "item:(%d+)")
-- PERFORMANCE: Try to extract name from link first
local _, _, linkName = string_find(link, "%[(.+)%]")
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
@@ -477,10 +472,8 @@ local function makeInventoryItem(inventoryID, link, Items)
if not link then link = GetInventoryItemLink("player", inventoryID) end
if not link then return end
local _, _, itemID = string_find(link, "item:(%d+)")
itemID = itemID and tonumber(itemID) or nil
local name = itemID and GetItemInfo(itemID) or nil
local itemID = GetInventoryItemID("player", inventoryID)
local name = C_Item.GetItemName({ equipmentSlotIndex = inventoryID })
local texture = GetInventoryItemTexture("player", inventoryID)
local count = GetInventoryItemCount("player", inventoryID)
@@ -509,14 +502,12 @@ local function makeBagItem(bagID, slot, link, Items)
end
if not link then return end
local _, _, itemID = string_find(link, "item:(%d+)")
itemID = itemID and tonumber(itemID) or nil
local itemID = C_Container.GetContainerItemID(bagID, slot)
local name, _, _, _, _, _, _, _, texture = GetItemInfo(itemID)
local name = C_Item.GetItemName({bagID = bagID, slotIndex = slot})
local count = 0
local tex, itemCount = GetContainerItemInfo(bagID, slot)
local texture, itemCount = GetContainerItemInfo(bagID, slot)
if itemCount then count = itemCount end
if not texture then texture = tex end
local it = {
bagID = bagID,
@@ -574,9 +565,7 @@ function CleveRoids.GetItem(text)
for inv = 1, 19 do
local link = GetInventoryItemLink("player", inv)
if link then
local _, _, itemID = string_find(link, "item:(%d+)")
itemID = itemID and tonumber(itemID) or nil
local itemID = GetInventoryItemID("player", inv)
if qid and itemID and qid == itemID then
return makeInventoryItem(inv, link, Items)
elseif qname then
@@ -595,9 +584,7 @@ function CleveRoids.GetItem(text)
for slot = 1, slots do
local link = GetContainerItemLink(bag, slot)
if link then
local _, _, itemID = string_find(link, "item:(%d+)")
itemID = itemID and tonumber(itemID) or nil
local itemID = C_Container.GetContainerItemID(bag, slot)
if qid and itemID and qid == itemID then
return makeBagItem(bag, slot, link, Items)
elseif qname then
@@ -720,18 +707,16 @@ function CleveRoids.FindItemQuick(text)
if cached then
-- Validate: check if item is actually at the cached location
if cached.inventoryID then
local link = GetInventoryItemLink("player", cached.inventoryID)
if link then
local nm = GetNameFromLink(link)
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
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
-- Cache is stale - item not at cached equipped slot, invalidate
@@ -740,18 +725,16 @@ function CleveRoids.FindItemQuick(text)
Items[string_lower(cached.name)] = nil
end
elseif cached.bagID and cached.slot then
local link = GetContainerItemLink(cached.bagID, cached.slot)
if link then
local nm = GetNameFromLink(link)
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
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
-- Cache is stale - item not at cached bag slot, invalidate
@@ -767,20 +750,17 @@ function CleveRoids.FindItemQuick(text)
for inv = 1, 19 do
local link = GetInventoryItemLink("player", inv)
if link then
local _, _, itemID = string_find(link, "item:(%d+)")
if itemID then
itemID = tonumber(itemID)
-- ID match: fast path
if qid and qid == itemID 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
-- 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
end
@@ -791,20 +771,17 @@ function CleveRoids.FindItemQuick(text)
for slot = 1, slots do
local link = GetContainerItemLink(bag, slot)
if link then
local _, _, itemID = string_find(link, "item:(%d+)")
if itemID then
itemID = tonumber(itemID)
-- ID match: fast path
if qid and qid == itemID 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
-- 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
@@ -818,25 +795,19 @@ end
function CleveRoids.IsItemEquipped(text, inventoryId)
if not text or not inventoryId then return false end
local link = GetInventoryItemLink("player", inventoryId)
if not link then return false end
local _, _, currentID = string_find(link, "item:(%d+)")
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 == tonumber(currentID) then
if textId and textId == currentID then
return true
end
-- Check by name - extract from link instead of GetItemInfo for performance
local currentName = GetNameFromLink(link)
if currentName then
local textLower = string_lower(text)
if string_lower(currentName) == textLower 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
+1 -6
View File
@@ -6918,12 +6918,7 @@ function CleveRoids.ApplyEquipmentModifier(spellID, baseDuration)
local modifiedDuration = modifier.modifier(baseDuration, itemID)
if modifiedDuration ~= baseDuration and CleveRoids.debug then
local itemName = "Unknown"
local itemLink = GetInventoryItemLink("player", modifier.slot)
if itemLink then
local _, _, _n = string.find(itemLink, "%[(.-)%]")
itemName = _n or "Unknown"
end
local itemName = C_Item.GetItemName({ equipmentSlotIndex = modifier.slot }) or "Unknown"
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cffff00ff[Equipment Modifier]|r %s (ID:%d): %ds -> %ds (item: %s [%d])",