nested macro showtooltip support, nampower queue casting integration, relic slot optimizations, performance updates

This commit is contained in:
Jrc13245
2025-12-04 18:10:30 -05:00
parent 87183b6e09
commit 04f4eacac8
5 changed files with 1622 additions and 140 deletions
+357 -135
View File
@@ -134,7 +134,17 @@ local function PerformEquipSwap(item, inventoryId, useQueueScript)
if not item or not inventoryId then return false end
-- Check if in combat and swapping weapons
local isWeapon = (inventoryId == 16 or inventoryId == 17 or inventoryId == 18)
-- Slot 18 (ranged) is only a weapon for Hunter/Warrior/Rogue/Mage/Warlock/Priest
-- For Druid/Paladin/Shaman, slot 18 is idol/libram/totem - can swap freely
local isWeapon = (inventoryId == 16 or inventoryId == 17)
if inventoryId == 18 then
-- Use cached playerClass for performance
local playerClass = CleveRoids.playerClass
-- Only treat slot 18 as weapon for classes that use ranged weapons
isWeapon = (playerClass == "HUNTER" or playerClass == "WARRIOR" or
playerClass == "ROGUE" or playerClass == "MAGE" or
playerClass == "WARLOCK" or playerClass == "PRIEST")
end
if isWeapon and UnitAffectingCombat("player") then
-- Don't swap while casting
@@ -322,16 +332,31 @@ end
-- PERFORMANCE: Self-disabling frame for queue processing with throttling
-- Only processes every EQUIP_QUEUE_INTERVAL instead of every frame
CleveRoids.EQUIP_QUEUE_INTERVAL = 0.05 -- 50ms between queue checks
CleveRoids.EQUIP_QUEUE_INTERVAL = 0.1 -- 100ms between queue checks (was 50ms)
CleveRoids.equipQueueLastUpdate = 0
CleveRoids.equipQueueFrame = CreateFrame("Frame")
CleveRoids.equipQueueFrame:Hide()
CleveRoids.equipQueueFrame:SetScript("OnUpdate", function()
local now = GetTime()
if (now - CleveRoids.equipQueueLastUpdate) < CleveRoids.EQUIP_QUEUE_INTERVAL then
return -- Throttle: skip this frame
-- PERFORMANCE: Upvalue for faster access in OnUpdate
local equipQueueFrame = CleveRoids.equipQueueFrame
local equipQueueInterval = CleveRoids.EQUIP_QUEUE_INTERVAL
equipQueueFrame:SetScript("OnUpdate", function()
-- PERFORMANCE: Use arg1 (elapsed time) if available, otherwise GetTime()
local elapsed = arg1
if elapsed then
CleveRoids.equipQueueLastUpdate = (CleveRoids.equipQueueLastUpdate or 0) + elapsed
if CleveRoids.equipQueueLastUpdate < equipQueueInterval then
return -- Throttle: skip this frame
end
CleveRoids.equipQueueLastUpdate = 0
else
local now = GetTime()
if (now - (CleveRoids.equipQueueLastUpdate or 0)) < equipQueueInterval then
return
end
CleveRoids.equipQueueLastUpdate = now
end
CleveRoids.equipQueueLastUpdate = now
CleveRoids.ProcessEquipmentQueue()
if CleveRoids.equipmentQueueLen == 0 then
@@ -571,6 +596,46 @@ function CleveRoids.GetProxyActionSlot(slot)
return CleveRoids.actionSlots[slot] or CleveRoids.actionSlots[slot.."()"]
end
-- Resolves nested macro references for #showtooltip propagation
-- If an action is a {MacroName} reference and that macro has #showtooltip,
-- returns the inner macro's active action; otherwise returns nil
-- depth parameter prevents infinite recursion
function CleveRoids.ResolveNestedMacroActive(action, depth)
if not action or not action.action then return nil end
depth = depth or 0
if depth > 5 then return nil end -- prevent infinite recursion
-- Check if this action is a macro reference {MacroName}
local macroName = CleveRoids.GetMacroNameFromAction(action.action)
if not macroName then return nil end
-- Get or parse the inner macro
local innerMacro = CleveRoids.GetMacro(macroName)
if not innerMacro then
innerMacro = CleveRoids.ParseMacro(macroName)
end
if not innerMacro or not innerMacro.actions then return nil end
-- Check if inner macro has #showtooltip (indicated by having a tooltip or action list)
if not innerMacro.actions.tooltip and table.getn(innerMacro.actions.list or {}) == 0 then
return nil
end
-- Run TestForActiveAction on inner macro to get its current active
CleveRoids.TestForActiveAction(innerMacro.actions)
-- If inner macro has an active action, check if it's also a nested macro
if innerMacro.actions.active then
local deeperActive = CleveRoids.ResolveNestedMacroActive(innerMacro.actions.active, depth + 1)
if deeperActive then
return deeperActive
end
return innerMacro.actions.active
end
return nil
end
function CleveRoids.TestForActiveAction(actions)
if not actions then return end
local currentActive = actions.active
@@ -582,9 +647,20 @@ function CleveRoids.TestForActiveAction(actions)
if actions.tooltip and table.getn(actions.list) == 0 then
if CleveRoids.TestAction(actions.cmd or "", actions.args or "") then
hasActive = true
actions.active = actions.tooltip
newActiveAction = actions.tooltip
-- Resolve nested macro references for #showtooltip propagation
local macroName = CleveRoids.GetMacroNameFromAction(actions.tooltip.action)
if macroName then
local resolved = CleveRoids.ResolveNestedMacroActive(actions.tooltip, 0)
if resolved then
newActiveAction = resolved
else
-- Inner macro didn't resolve - no active action
hasActive = false
newActiveAction = nil
end
end
end
else
-- First pass: find first action with conditionals that passes
@@ -598,11 +674,6 @@ function CleveRoids.TestForActiveAction(actions)
-- Note: CleveRoids._ignoretooltip is a side-effect flag set by GetParsedMsg
local shouldSkipForIcon = CleveRoids._ignoretooltip == 1
if not conditionals and not firstUnconditional and not shouldSkipForIcon then
-- Track first unconditional action as fallback (unless it has '?' prefix)
firstUnconditional = action
end
-- break on first action that passes tests (unless it should be skipped for icon)
if result and not shouldSkipForIcon then
hasActive = true
@@ -612,9 +683,31 @@ function CleveRoids.TestForActiveAction(actions)
if not newActiveAction then hasActive = false end
else
newActiveAction = action
-- Resolve nested macro references for #showtooltip propagation
-- If inner macro doesn't resolve, continue to next action
local macroName = CleveRoids.GetMacroNameFromAction(action.action)
if macroName then
local resolved = CleveRoids.ResolveNestedMacroActive(action, 0)
if resolved then
newActiveAction = resolved
else
-- Inner macro didn't resolve - try next action
hasActive = false
newActiveAction = nil
end
end
end
if hasActive then break end
end
-- Track first unconditional non-macro action as fallback
if not conditionals and not firstUnconditional and not shouldSkipForIcon then
-- Don't use unresolvable macro refs as fallback
local macroName = CleveRoids.GetMacroNameFromAction(action.action)
if not macroName then
firstUnconditional = action
end
end
end
-- If no conditional action passed, use first unconditional action
@@ -848,26 +941,58 @@ function CleveRoids.TestForActiveAction(actions)
return changed
end
-- PERFORMANCE: Static buffer references for hot path
local _actionsToSlotsBuffer = CleveRoids._actionsToSlotsBuffer
local _slotsBuffer = CleveRoids._slotsBuffer
local _actionsListBuffer = CleveRoids._actionsListBuffer
function CleveRoids.TestForAllActiveActions()
-- PERFORMANCE: Reuse static buffers instead of creating new tables each call
-- Group slots by their actions object to handle shared macro references
-- When the same macro appears on multiple slots, they share the same actions object
local actionsToSlots = {}
local actionsToSlots = _actionsToSlotsBuffer
local actionsList = _actionsListBuffer
local actionsCount = 0
for slot, actions in pairs(CleveRoids.Actions) do
if not actionsToSlots[actions] then
actionsToSlots[actions] = {}
-- Reuse or create slots array from pool
local slots = _slotsBuffer[actions]
if not slots then
slots = {}
_slotsBuffer[actions] = slots
end
slots[1] = slot
slots._count = 1
actionsToSlots[actions] = slots
actionsCount = actionsCount + 1
actionsList[actionsCount] = actions
else
local slots = actionsToSlots[actions]
local count = slots._count + 1
slots[count] = slot
slots._count = count
end
table.insert(actionsToSlots[actions], slot)
end
-- Test each unique actions object once and send events to ALL slots that share it
for actions, slots in pairs(actionsToSlots) do
for i = 1, actionsCount do
local actions = actionsList[i]
local slots = actionsToSlots[actions]
local stateChanged = CleveRoids.TestForActiveAction(actions)
if stateChanged then
-- Send event to ALL slots that use this macro
for _, slot in ipairs(slots) do
CleveRoids.SendEventForAction(slot, "ACTIONBAR_SLOT_CHANGED", slot)
local count = slots._count
for j = 1, count do
CleveRoids.SendEventForAction(slots[j], "ACTIONBAR_SLOT_CHANGED", slots[j])
end
end
-- Clear for reuse (reset count and clear buffer reference)
for j = 1, slots._count do
slots[j] = nil
end
slots._count = 0
actionsToSlots[actions] = nil
actionsList[i] = nil -- Clear actionsList entry for reuse
end
end
@@ -903,10 +1028,14 @@ function CleveRoids.GetActiveAction(slot)
return action and action.active
end
-- PERFORMANCE: Static buffer for arg backup
local _originalArgsBuffer = CleveRoids._originalArgsBuffer
function CleveRoids.SendEventForAction(slot, event, ...)
local old_this = this
local original_global_args = {}
-- PERFORMANCE: Reuse static buffer instead of creating table each call
local original_global_args = _originalArgsBuffer
for i = 1, 10 do
original_global_args[i] = _G["arg" .. i]
end
@@ -1857,10 +1986,16 @@ function CleveRoids.DoWithConditionals(msg, hook, fixEmptyTargetFunc, targetBefo
castMsg = msg .. "(" .. rank .. ")"
end
end
if CleveRoids.hasSuperwow and action == CastSpellByName and conditionals.target then
CastSpellByName(castMsg, conditionals.target) -- SuperWoW handles targeting via argument
elseif action == CastSpellByName then
action(castMsg)
if action == CastSpellByName then
-- Use smart cast for spell queuing based on Nampower settings
local API = CleveRoids.NampowerAPI
if API and API.SmartCast then
API.SmartCast(castMsg, conditionals.target)
elseif CleveRoids.hasSuperwow and conditionals.target then
CastSpellByName(castMsg, conditionals.target)
else
CastSpellByName(castMsg)
end
else
-- For other actions like UseContainerItem etc.
action(msg)
@@ -2262,14 +2397,36 @@ function CleveRoids.DoUse(msg)
return
end
-- Resolve by name/id via our cache
local item = CleveRoids.GetItem(msg) -- looks in equipped, then bags
-- PERFORMANCE: Try fast lookup methods first, fall back to full scan
-- Full scan is now optimized with GetNameFromLink() instead of GetItemInfo()
local item
-- Try fast cache lookup first (no scanning)
if CleveRoids.GetItemFast then
item = CleveRoids.GetItemFast(msg)
end
-- Try quick targeted scan (stops when found)
if not item and CleveRoids.FindItemQuick then
item = CleveRoids.FindItemQuick(msg)
end
-- Full scan fallback (now optimized, safe during combat)
if not item then
item = CleveRoids.GetItem(msg)
end
if not item then return end
-- If it's an equipped item (trinket etc.), use it directly
if item.inventoryID then
ClearCursor()
UseInventoryItem(item.inventoryID)
-- Invalidate cache - item location may have changed (e.g., trinket swap)
if item.name and CleveRoids.Items then
CleveRoids.Items[item.name] = nil
CleveRoids.Items[string_lower(item.name)] = nil
end
return
end
@@ -2278,8 +2435,43 @@ function CleveRoids.DoUse(msg)
-- If we tracked multiple stacks, advance politely
CleveRoids.GetNextBagSlotForUse(item, msg)
-- Before equipping, note what's in commonly swapped slots
-- so we can invalidate cache entries for displaced items
-- Slots: 13/14 (trinkets), 16/17 (weapons), 18 (relic/idol/libram)
local swapSlots = {13, 14, 16, 17, 18}
local oldSlotItems = {}
for _, slotId in ipairs(swapSlots) do
local link = GetInventoryItemLink("player", slotId)
if link then
local _, _, name = string_find(link, "|h%[(.-)%]|h")
if name then oldSlotItems[slotId] = name end
end
end
ClearCursor()
UseContainerItem(item.bagID, item.slot)
-- Invalidate cache for the item we just used
if item.name and CleveRoids.Items then
CleveRoids.Items[item.name] = nil
CleveRoids.Items[string_lower(item.name)] = nil
end
-- Invalidate cache for any displaced items in swap slots
if CleveRoids.Items then
for slotId, oldName in pairs(oldSlotItems) do
local newLink = GetInventoryItemLink("player", slotId)
local newName = nil
if newLink then
local _, _, name = string_find(newLink, "|h%[(.-)%]|h")
newName = name
end
if newName ~= oldName then
CleveRoids.Items[oldName] = nil
CleveRoids.Items[string_lower(oldName)] = nil
end
end
end
return
end
end
@@ -2300,51 +2492,76 @@ function CleveRoids.EquipBagItem(msg, offhand)
local invslot = offhand and 17 or 16
-- Check if item is already equipped (fast path)
local currentItemLink = GetInventoryItemLink("player", invslot)
if currentItemLink then
local _, _, currentID = string.find(currentItemLink, "item:(%d+)")
local currentItemName = GetItemInfo(currentItemLink)
local msgLower = string.lower(msg)
-- PERFORMANCE: Fast check if already equipped (single function call)
if CleveRoids.IsItemEquipped and CleveRoids.IsItemEquipped(msg, invslot) then
return true
end
if currentID then
local _, _, msgID = string.find(msg, "(%d+)")
if msgID and tonumber(msgID) == tonumber(currentID) then
return true -- Already equipped
-- 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
-- Helper to invalidate displaced item's cache
local function InvalidateDisplacedItem()
if oldSlotName and CleveRoids.Items then
CleveRoids.Items[oldSlotName] = nil
CleveRoids.Items[string_lower(oldSlotName)] = nil
end
end
-- PERFORMANCE: Try EquipItemByName FIRST before any lookups
-- This is the fastest path - no item lookup, no cursor operations
if EquipItemByName then
local ok = pcall(EquipItemByName, msg, invslot)
if ok then
-- Invalidate cache entry if it exists
if CleveRoids.Items then
CleveRoids.Items[msg] = nil
CleveRoids.Items[string_lower(msg)] = nil
end
end
if currentItemName and string.lower(currentItemName) == msgLower then
return true -- Already equipped
end
end
-- Item lookup
local item = CleveRoids.GetItem(msg)
if not item or not item.name then
CleveRoids.IndexItems()
item = CleveRoids.GetItem(msg)
if not item or not item.name then
return false
end
end
-- Double-check it's not already equipped (by item ID after lookup)
if currentItemLink and item.id then
local _, _, currentID = string.find(currentItemLink, "item:(%d+)")
if currentID and tonumber(currentID) == tonumber(item.id) then
InvalidateDisplacedItem()
return true
end
end
-- PERFORMANCE: Use fast lookup first, fall back to full scan
-- Full scan is now optimized with GetNameFromLink() instead of GetItemInfo()
local item = CleveRoids.GetItemFast and CleveRoids.GetItemFast(msg)
if not item then
-- Try quick targeted scan (stops when found)
item = CleveRoids.FindItemQuick and CleveRoids.FindItemQuick(msg)
end
if not item then
-- Full scan fallback (now optimized, safe during combat)
item = CleveRoids.GetItem(msg)
end
if not item or not item.name then
return false
end
-- Already equipped check (by item ID from lookup)
if item.inventoryID == invslot then
return true
end
if not item.bagID and not item.inventoryID then
return false
end
-- Try EquipItemByName first (fastest, no cursor operations)
if item.name and EquipItemByName then
-- Try EquipItemByName with resolved name (in case msg was partial/different case)
if item.name and EquipItemByName and item.name ~= msg then
local ok = pcall(EquipItemByName, item.name, invslot)
if ok then
CleveRoids.Items[item.name] = nil
if CleveRoids.Items then
CleveRoids.Items[item.name] = nil
CleveRoids.Items[string_lower(item.name)] = nil
end
InvalidateDisplacedItem()
return true
end
end
@@ -2352,18 +2569,13 @@ function CleveRoids.EquipBagItem(msg, offhand)
-- Fallback: Manual pickup and equip
CleveRoids.equipInProgress = true
if type(CloseStackSplitFrame) == "function" then
CloseStackSplitFrame()
end
-- PERFORMANCE: Single cursor check at start
if CursorHasItem and CursorHasItem() then
ClearCursor()
CleveRoids.equipInProgress = false
return false
end
local pickupSuccess = false
if item.bagID and item.slot then
CleveRoids.GetNextBagSlotForUse(item, msg)
PickupContainerItem(item.bagID, item.slot)
pickupSuccess = CursorHasItem and CursorHasItem()
elseif item.inventoryID then
@@ -2380,7 +2592,11 @@ function CleveRoids.EquipBagItem(msg, offhand)
EquipCursorItem(invslot)
ClearCursor()
CleveRoids.Items[item.name] = nil
if CleveRoids.Items and item.name then
CleveRoids.Items[item.name] = nil
CleveRoids.Items[string_lower(item.name)] = nil
end
InvalidateDisplacedItem()
CleveRoids.equipInProgress = false
return true
end
@@ -2523,7 +2739,13 @@ function CleveRoids.DoCastSequence(sequence)
local r = (sp and sp.rank) or (sp and sp.highest and sp.highest.rank)
if r and r ~= "" then msg = msg .. "(" .. r .. ")" end
end
CastSpellByName(msg)
-- Use smart cast for spell queuing based on Nampower settings
local API = CleveRoids.NampowerAPI
if API and API.SmartCast then
API.SmartCast(msg)
else
CastSpellByName(msg)
end
return true
end
@@ -2557,18 +2779,55 @@ end
CleveRoids.lastCleanupTime = 0
CleveRoids.CLEANUP_INTERVAL = 5 -- Run cleanup every 5 seconds
-- PERFORMANCE: Upvalues for OnUpdate hot path
local GetTime = GetTime
local UnitAffectingCombat = UnitAffectingCombat
local pairs = pairs
function CleveRoids.OnUpdate(self)
-- PERFORMANCE: Equipment queue now processed by self-disabling equipQueueFrame
-- This eliminates per-frame checks when queue is empty
-- PERFORMANCE: Single GetTime() call per frame
local time = GetTime()
-- PERFORMANCE: Early exit if not ready (before any other checks)
if not CleveRoids.ready then
-- Handle initialization timer only when not ready
if CleveRoids.initializationTimer and time >= CleveRoids.initializationTimer then
CleveRoids.IndexItems()
CleveRoids.IndexActionBars()
CleveRoids.ready = true
CleveRoids.initializationTimer = nil
CleveRoids.TestForAllActiveActions()
CleveRoids.lastUpdate = time
end
return
end
-- PERFORMANCE: Cache refresh rate calculation (avoid per-frame division)
local refreshRate = CleveRoids.cachedRefreshRate
if not refreshRate then
refreshRate = 1 / (CleveRoidMacros.refresh or 5)
CleveRoids.cachedRefreshRate = refreshRate
end
-- PERFORMANCE: Throttle check FIRST - skip most work on non-throttled frames
local lastUpdate = CleveRoids.lastUpdate or 0
local bypassThrottle = CleveRoids.isActionUpdateQueued and CleveRoidMacros.realtime == 0
local shouldUpdate = bypassThrottle or (time - lastUpdate) >= refreshRate
if not shouldUpdate then
return -- Early exit for non-throttled frames
end
CleveRoids.lastUpdate = time
-- Process deferred equipment index updates (for throttled UNIT_INVENTORY_CHANGED)
-- Skip entirely in combat - EquipBagItem handles cache invalidation
if CleveRoids.equipIndexPendingTime and not UnitAffectingCombat("player") then
local now = GetTime()
if (now - (CleveRoids.lastEquipIndexTime or 0)) >= 0.2 then
CleveRoids.lastEquipIndexTime = now
-- PERFORMANCE: Skip check entirely if no pending update
local pendingTime = CleveRoids.equipIndexPendingTime
if pendingTime and not UnitAffectingCombat("player") then
if (time - (CleveRoids.lastEquipIndexTime or 0)) >= 0.2 then
CleveRoids.lastEquipIndexTime = time
CleveRoids.equipIndexPendingTime = nil
CleveRoids.lastItemIndexTime = now
CleveRoids.lastItemIndexTime = time
CleveRoids.IndexItems()
CleveRoids.Actions = {}
CleveRoids.Macros = {}
@@ -2580,68 +2839,34 @@ function CleveRoids.OnUpdate(self)
end
end
-- PERFORMANCE OPTIMIZATION: Removed UpdateCastingState() polling
-- Casting state is now fully event-driven via SPELLCAST_* events
-- This eliminates continuous GetCurrentCastingInfo() polling
local time = GetTime()
local refreshRate = CleveRoidMacros.refresh or 5
refreshRate = 1/refreshRate
if CleveRoids.initializationTimer and time >= CleveRoids.initializationTimer then
CleveRoids.IndexItems()
CleveRoids.IndexActionBars()
CleveRoids.ready = true
CleveRoids.initializationTimer = nil
CleveRoids.TestForAllActiveActions()
CleveRoids.lastUpdate = time
return
end
if not CleveRoids.ready then return end
-- Check for expired reactive procs BEFORE throttle to ensure immediate icon updates
if CleveRoids.reactiveProcs then
-- PERFORMANCE: Check for expired reactive procs only if we have any
-- Use statically allocated removal buffer to avoid per-frame allocation
local reactiveProcs = CleveRoids.reactiveProcs
if reactiveProcs then
local hasExpiredProc = false
for spellName, procData in pairs(CleveRoids.reactiveProcs) do
local toRemove = CleveRoids._procRemovalBuffer
local removeCount = 0
for spellName, procData in pairs(reactiveProcs) do
if procData and procData.expiry and time >= procData.expiry then
-- DEBUG: Show proc expiration
if CleveRoids.debug then
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cffff9900[REACTIVE PROC]|r %s expired at time=%.3f", spellName, time)
)
end
CleveRoids.reactiveProcs[spellName] = nil
removeCount = removeCount + 1
toRemove[removeCount] = spellName
hasExpiredProc = true
end
end
-- If any proc expired, immediately update all actions to refresh icon states
-- Remove expired procs using indexed array (no pairs() overhead)
for i = 1, removeCount do
reactiveProcs[toRemove[i]] = nil
toRemove[i] = nil -- Clear for next use
end
-- If any proc expired, immediately update all actions
if hasExpiredProc then
if CleveRoids.debug then
DEFAULT_CHAT_FRAME:AddMessage("|cffff9900[REACTIVE PROC]|r Forcing immediate TestForAllActiveActions()")
-- Debug: Show how many actions we're checking
local actionCount = 0
for _ in pairs(CleveRoids.Actions) do
actionCount = actionCount + 1
end
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cffff9900[REACTIVE PROC]|r Checking %d action slots", actionCount)
)
end
-- Force immediate update (don't just queue it)
CleveRoids.TestForAllActiveActions()
CleveRoids.isActionUpdateQueued = false -- Clear queue flag since we just processed
if CleveRoids.debug then
DEFAULT_CHAT_FRAME:AddMessage("|cffff9900[REACTIVE PROC]|r TestForAllActiveActions() completed")
end
CleveRoids.isActionUpdateQueued = false
end
end
-- Throttle the update loop to avoid excessive CPU usage.
-- HOWEVER: If an action update is queued, allow it to bypass throttle for reactive abilities
local bypassThrottle = CleveRoids.isActionUpdateQueued and CleveRoidMacros.realtime == 0
if not bypassThrottle and (time - CleveRoids.lastUpdate) < refreshRate then
return
end
CleveRoids.lastUpdate = time
-- Check the saved variable to decide which update mode to use.
if CleveRoidMacros.realtime == 1 then
-- Realtime Mode: Force an update on every throttled tick for maximum responsiveness.
@@ -3485,9 +3710,6 @@ end
-- Nampower SPELL_CAST_EVENT handler for reliable channel tracking
-- This is the PRIMARY source of truth for channel state (not GetCurrentCastingInfo polling)
function CleveRoids.Frame:SPELL_CAST_EVENT(success, spellId, castType, targetGuid, itemId)
DEFAULT_CHAT_FRAME:AddMessage(string.format("[SPELL_CAST_EVENT] success=%s, spellId=%s, castType=%s",
tostring(success), tostring(spellId), tostring(castType)))
local CHANNEL = 4
if castType == CHANNEL and success == 1 then
@@ -3495,10 +3717,9 @@ function CleveRoids.Frame:SPELL_CAST_EVENT(success, spellId, castType, targetGui
CleveRoids.CurrentSpell.type = "channeled"
CleveRoids.CurrentSpell.castingSpellId = spellId
local spellName = SpellInfo(spellId)
local spellName = SpellInfo and SpellInfo(spellId)
if spellName then
CleveRoids.CurrentSpell.spellName = spellName
DEFAULT_CHAT_FRAME:AddMessage(string.format("[SPELL_CAST_EVENT] Channel START: %s", spellName))
end
-- Force immediate action update
@@ -4048,6 +4269,7 @@ SlashCmdList["CLEVEROID"] = function(msg)
local num = tonumber(val)
if num and num >= 1 and num <= 10 then
CleveRoidMacros.refresh = num
CleveRoids.cachedRefreshRate = nil -- Invalidate cached rate
CleveRoids.Print("refresh set to " .. num .. " times per second")
else
CleveRoids.Print("Usage: /cleveroid refresh X - Set refresh rate. (1 to 10 updates per second. Default: 5)")
+151 -4
View File
@@ -25,6 +25,13 @@ 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()
@@ -516,8 +523,9 @@ function CleveRoids.GetItem(text)
if qid and itemID and qid == itemID then
return makeInventoryItem(inv, link, Items)
elseif qname and itemID then
local nm = GetItemInfo(itemID)
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
@@ -536,8 +544,9 @@ function CleveRoids.GetItem(text)
if qid and itemID and qid == itemID then
return makeBagItem(bag, slot, link, Items)
elseif qname and itemID then
local nm = GetItemInfo(itemID)
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
@@ -572,6 +581,144 @@ function CleveRoids.GetNextBagSlotForUse(item, text)
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.
function CleveRoids.FindItemQuick(text)
if not text or text == "" then return nil end
-- First try fast cache lookup
local cached = CleveRoids.GetItemFast(text)
if cached then return cached end
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
-- Quick scan equipped items first (only 19 slots)
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
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
-- 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 = string_find(link, "item:(%d+)")
if itemID then
itemID = tonumber(itemID)
-- 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
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 link = GetInventoryItemLink("player", inventoryId)
if not link then return false end
local _, _, currentID = string_find(link, "item:(%d+)")
if not currentID then return false end
-- Check by ID (fast path)
local textId = tonumber(text)
if textId and textId == tonumber(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
end
return false
end
local Extension = CleveRoids.RegisterExtension("Generic_show")
Extension.RegisterEvent("SPELLS_CHANGED", "SPELLS_CHANGED")
+48 -1
View File
@@ -105,6 +105,17 @@ CleveRoids.EQUIP_GLOBAL_COOLDOWN = 0.5 -- Global cooldown
-- PERFORMANCE: Table pool for queue entries to reduce garbage collection
CleveRoids.queueEntryPool = {}
-- PERFORMANCE: Static buffer for proc removal to avoid per-frame allocation
CleveRoids._procRemovalBuffer = {}
-- PERFORMANCE: Static buffer for action grouping to avoid per-call allocation
CleveRoids._actionsToSlotsBuffer = {}
CleveRoids._slotsBuffer = {}
CleveRoids._actionsListBuffer = {}
-- PERFORMANCE: Static buffer for arg backup in SendEventForAction
CleveRoids._originalArgsBuffer = {}
-- Spell queue state (Nampower)
CleveRoids.queuedSpell = nil
CleveRoids.lastCastSpell = nil
@@ -189,11 +200,22 @@ CleveRoids.WeaponTypeNames = {
CleveRoids.hasNampower = (QueueSpellByName ~= nil)
CleveRoids.hasUnitXP = pcall(UnitXP, "nop", "nop")
-- Extended Nampower feature flags (populated by NampowerAPI.lua)
CleveRoids.nampowerVersion = { major = 0, minor = 0, patch = 0 }
CleveRoids.hasExtendedNampower = false -- True if v2.12+ with new API functions
-- Feature detection messages
local function PrintFeatures()
local features = {}
if CleveRoids.hasSuperwow then table.insert(features, "SuperWoW") end
if CleveRoids.hasNampower then table.insert(features, "Nampower") end
if CleveRoids.hasNampower then
local ver = CleveRoids.nampowerVersion
if ver.major > 0 then
table.insert(features, string.format("Nampower v%d.%d.%d", ver.major, ver.minor, ver.patch))
else
table.insert(features, "Nampower")
end
end
if CleveRoids.hasUnitXP then table.insert(features, "UnitXP") end
if CleveRoids.hasTurtle then table.insert(features, "Turtle") end
@@ -207,6 +229,31 @@ local initFrame = CreateFrame("Frame")
initFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
initFrame:SetScript("OnEvent", function()
this:UnregisterAllEvents()
-- Initialize NampowerAPI if available
if CleveRoids.NampowerAPI then
local API = CleveRoids.NampowerAPI
-- Get version info
local major, minor, patch = API.GetVersion()
CleveRoids.nampowerVersion = { major = major, minor = minor, patch = patch }
-- Check for extended API (v2.12+)
CleveRoids.hasExtendedNampower = API.HasMinimumVersion(2, 12, 0)
-- Sync feature flags
if API.features then
CleveRoids.hasGetSpellRec = API.features.hasGetSpellRec
CleveRoids.hasGetItemStats = API.features.hasGetItemStats
CleveRoids.hasGetUnitData = API.features.hasGetUnitData
CleveRoids.hasGetSpellModifiers = API.features.hasGetSpellModifiers
CleveRoids.hasEnhancedSpellFunctions = API.features.hasEnhancedSpellFunctions
end
-- Initialize the API
API.Initialize()
end
PrintFeatures()
end)
+1065
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -7,6 +7,7 @@
## SavedVariables: CleveRoidMacros, CleveRoids_LearnedDurations, CleveRoids_AuraTextures, CleveRoids_ImmunityData, CleveRoids_ComboDurations, CleveRoids_SpellSchools
Localization.lua
Init.lua
NampowerAPI.lua
Utility.lua
Core.lua
Conditionals.lua