mirror of
https://github.com/brues-code/SuperCleveRoidMacros.git
synced 2026-09-16 03:38:00 +00:00
nested macro showtooltip support, nampower queue casting integration, relic slot optimizations, performance updates
This commit is contained in:
@@ -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)")
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user