diff --git a/Conditionals.lua b/Conditionals.lua
index f3b3071..bfb34cb 100644
--- a/Conditionals.lua
+++ b/Conditionals.lua
@@ -1427,10 +1427,18 @@ CleveRoids.Keywords = {
end,
channeled = function(conditionals)
+ if GetCurrentCastingInfo then
+ local _, _, _, _, channeling = GetCurrentCastingInfo()
+ return channeling == 1
+ end
return CleveRoids.CurrentSpell.type == "channeled"
end,
nochanneled = function(conditionals)
+ if GetCurrentCastingInfo then
+ local _, _, _, _, channeling = GetCurrentCastingInfo()
+ return channeling ~= 1
+ end
return CleveRoids.CurrentSpell.type ~= "channeled"
end,
@@ -1457,14 +1465,52 @@ CleveRoids.Keywords = {
inrange = function(conditionals)
if not IsSpellInRange then return end
return And(conditionals.inrange, function(spellName)
- return IsSpellInRange(spellName or conditionals.action, conditionals.target) == 1
+ local target = conditionals.target or "target"
+ local checkValue = spellName or conditionals.action
+
+ -- Try to convert spell name to ID for better accuracy (Nampower)
+ if type(checkValue) == "string" and GetSpellIdForName then
+ local spellId = GetSpellIdForName(checkValue)
+ if spellId and spellId > 0 then
+ checkValue = spellId
+ end
+ end
+
+ return IsSpellInRange(checkValue, target) == 1
end)
end,
noinrange = function(conditionals)
if not IsSpellInRange then return end
return And(conditionals.noinrange, function(spellName)
- return IsSpellInRange(spellName or conditionals.action, conditionals.target) == 0
+ local target = conditionals.target or "target"
+ local checkValue = spellName or conditionals.action
+
+ if type(checkValue) == "string" and GetSpellIdForName then
+ local spellId = GetSpellIdForName(checkValue)
+ if spellId and spellId > 0 then
+ checkValue = spellId
+ end
+ end
+
+ return IsSpellInRange(checkValue, target) == 0
+ end)
+ end,
+
+ outrange = function(conditionals)
+ if not IsSpellInRange then return end
+ return And(conditionals.outrange, function(spellName)
+ local target = conditionals.target or "target"
+ local checkValue = spellName or conditionals.action
+
+ if type(checkValue) == "string" and GetSpellIdForName then
+ local spellId = GetSpellIdForName(checkValue)
+ if spellId and spellId > 0 then
+ checkValue = spellId
+ end
+ end
+
+ return IsSpellInRange(checkValue, target) == 0
end)
end,
@@ -1621,7 +1667,152 @@ CleveRoids.Keywords = {
return not CleveRoids.IsReactiveUsable("Aquatic Form")
end,
- mybuffcount = function(conditionals)
- return And(conditionals.mybuffcount,function (v) return CleveRoids.ValidatePlayerAuraCount(v.bigger, v.amount) end)
- end
+ distance = function(conditionals)
+ if not CleveRoids.hasUnitXP then return false end
+
+ return And(conditionals.distance, function(args)
+ if type(args) ~= "table" or not args.operator or not args.amount then
+ return false
+ end
+
+ local unit = conditionals.target or "target"
+ if not UnitExists(unit) then return false end
+
+ local distance = UnitXP("distanceBetween", "player", unit)
+ if not distance then return false end
+
+ return CleveRoids.comparators[args.operator](distance, args.amount)
+ end)
+ end,
+
+ nodistance = function(conditionals)
+ if not CleveRoids.hasUnitXP then return false end
+
+ return And(conditionals.nodistance, function(args)
+ if type(args) ~= "table" or not args.operator or not args.amount then
+ return false
+ end
+
+ local unit = conditionals.target or "target"
+ if not UnitExists(unit) then return false end
+
+ local distance = UnitXP("distanceBetween", "player", unit)
+ if not distance then return false end
+
+ return not CleveRoids.comparators[args.operator](distance, args.amount)
+ end)
+ end,
+
+ behind = function(conditionals)
+ if not CleveRoids.hasUnitXP then return false end
+
+ local unit = conditionals.target or "target"
+ if not UnitExists(unit) then return false end
+
+ return UnitXP("behind", "player", unit) == true
+ end,
+
+ nobehind = function(conditionals)
+ if not CleveRoids.hasUnitXP then return false end
+
+ local unit = conditionals.target or "target"
+ if not UnitExists(unit) then return false end
+
+ return UnitXP("behind", "player", unit) ~= true
+ end,
+
+ insight = function(conditionals)
+ if not CleveRoids.hasUnitXP then return false end
+
+ local unit = conditionals.target or "target"
+ if not UnitExists(unit) then return false end
+
+ return UnitXP("inSight", "player", unit) == true
+ end,
+
+ noinsight = function(conditionals)
+ if not CleveRoids.hasUnitXP then return false end
+
+ local unit = conditionals.target or "target"
+ if not UnitExists(unit) then return false end
+
+ return UnitXP("inSight", "player", unit) ~= true
+ end,
+
+ meleerange = function(conditionals)
+ local unit = conditionals.target or "target"
+ if not UnitExists(unit) then return false end
+
+ if CleveRoids.hasUnitXP then
+ local distance = UnitXP("distanceBetween", "player", unit, "meleeAutoAttack")
+ return distance and distance <= 5
+ else
+ -- Fallback: use CheckInteractDistance (3 = melee range)
+ return CheckInteractDistance(unit, 3)
+ end
+ end,
+
+ nomeleerange = function(conditionals)
+ local unit = conditionals.target or "target"
+ if not UnitExists(unit) then return true end
+
+ if CleveRoids.hasUnitXP then
+ local distance = UnitXP("distanceBetween", "player", unit, "meleeAutoAttack")
+ return not distance or distance > 5
+ else
+ return not CheckInteractDistance(unit, 3)
+ end
+ end,
+
+ queuedspell = function(conditionals)
+ if not CleveRoids.hasNampower then return false end
+ if not CleveRoids.queuedSpell then return false end
+
+ -- If no specific spell name provided, check if ANY spell is queued
+ if not conditionals.queuedspell or table.getn(conditionals.queuedspell) == 0 then
+ return true
+ end
+
+ -- Check if specific spell is queued
+ return Or(conditionals.queuedspell, function(spellName)
+ if not CleveRoids.queuedSpell.spellName then return false end
+ local queuedName = string.gsub(CleveRoids.queuedSpell.spellName, "%s*%(.-%)%s*$", "")
+ local checkName = string.gsub(spellName, "%s*%(.-%)%s*$", "")
+ return string.lower(queuedName) == string.lower(checkName)
+ end)
+ end,
+
+ noqueuedspell = function(conditionals)
+ if not CleveRoids.hasNampower then return false end
+
+ -- If no specific spell name, check if NO spell is queued
+ if not conditionals.noqueuedspell or table.getn(conditionals.noqueuedspell) == 0 then
+ return CleveRoids.queuedSpell == nil
+ end
+
+ -- Check if specific spell is NOT queued
+ if not CleveRoids.queuedSpell or not CleveRoids.queuedSpell.spellName then
+ return true
+ end
+
+ return And(conditionals.noqueuedspell, function(spellName)
+ local queuedName = string.gsub(CleveRoids.queuedSpell.spellName, "%s*%(.-%)%s*$", "")
+ local checkName = string.gsub(spellName, "%s*%(.-%)%s*$", "")
+ return string.lower(queuedName) ~= string.lower(checkName)
+ end)
+ end,
+
+ onswingpending = function(conditionals)
+ if not GetCurrentCastingInfo then return false end
+
+ local _, _, _, _, _, onswing = GetCurrentCastingInfo()
+ return onswing == 1
+ end,
+
+ noonswingpending = function(conditionals)
+ if not GetCurrentCastingInfo then return true end
+
+ local _, _, _, _, _, onswing = GetCurrentCastingInfo()
+ return onswing ~= 1
+ end,
}
diff --git a/Core.lua b/Core.lua
index 60fee62..41da16f 100644
--- a/Core.lua
+++ b/Core.lua
@@ -14,11 +14,227 @@ CleveRoids.lastEquipTime = CleveRoids.lastEquipTime or {}
CleveRoids.lastWeaponSwapTime = 0
CleveRoids.equipInProgress = false
+local requirementCheckFrame = CreateFrame("Frame")
+requirementCheckFrame:RegisterEvent("ADDON_LOADED")
+requirementCheckFrame:SetScript("OnEvent", function()
+ if arg1 ~= "SuperCleveRoidMacros" then return end
+
+ -- Check requirements immediately when our addon loads
+ local hasSuperwow = CleveRoids.hasSuperwow
+ local hasNampower = (IsSpellInRange ~= nil)
+ local hasUnitXP = pcall(UnitXP, "nop", "nop")
+
+ if not hasSuperwow or not hasNampower or not hasUnitXP then
+ -- Show errors
+ if not hasSuperwow then
+ CleveRoids.Print("|cFFFF0000SuperCleveRoidMacros|r requires |cFF00FFFFbalakethelock's SuperWoW|r:")
+ CleveRoids.Print("https://github.com/balakethelock/SuperWoW")
+ end
+ if not hasNampower then
+ CleveRoids.Print("|cFFFF0000SuperCleveRoidMacros|r requires |cFF00FFFFpepopo978's Nampower|r:")
+ CleveRoids.Print("https://gitea.com/avitasia/nampower")
+ end
+ if not hasUnitXP then
+ CleveRoids.Print("|cFFFF0000SuperCleveRoidMacros|r requires |cFF00FFFFKonaka's UnitXP_SP3|r:")
+ CleveRoids.Print("https://codeberg.org/konaka/UnitXP_SP3")
+ end
+
+ -- Disable immediately
+ CleveRoids.DisableAddon("Missing Requirements")
+
+ -- Unregister this check frame
+ this:UnregisterAllEvents()
+ return
+ end
+
+ -- Requirements met - allow normal initialization
+ CleveRoids.Print("|cFF4477FFSuperCleveR|r|cFFFFFFFFoid Macros|r |cFF00FF00Loaded|r - See the README.")
+
+ -- Unregister this check frame
+ this:UnregisterAllEvents()
+end)
+
+local SLOT_TO_INVID = {
+ ["MainHandSlot"] = 16,
+ ["SecondaryHandSlot"] = 17,
+ ["RangedSlot"] = 18,
+ ["HeadSlot"] = 1,
+ ["NeckSlot"] = 2,
+ ["ShoulderSlot"] = 3,
+ ["ChestSlot"] = 5,
+ ["WaistSlot"] = 6,
+ ["LegsSlot"] = 7,
+ ["FeetSlot"] = 8,
+ ["WristSlot"] = 9,
+ ["HandsSlot"] = 10,
+ ["Finger0Slot"] = 11,
+ ["Finger1Slot"] = 12,
+ ["Trinket0Slot"] = 13,
+ ["Trinket1Slot"] = 14,
+ ["BackSlot"] = 15,
+ ["ShirtSlot"] = 4,
+ ["TabardSlot"] = 19,
+}
+
+local function GetInventoryIdFromSlot(slotName)
+ return SLOT_TO_INVID[slotName] or GetInventorySlotInfo(slotName)
+end
+
+local function IsSlotOnCooldown(slot)
+ local now = GetTime()
+ local slotTime = CleveRoids.lastEquipTime[slot] or 0
+ local globalTime = CleveRoids.lastGlobalEquipTime or 0
+
+ if (now - slotTime) < CleveRoids.EQUIP_COOLDOWN then
+ return true
+ end
+
+ if (now - globalTime) < CleveRoids.EQUIP_GLOBAL_COOLDOWN then
+ return true
+ end
+
+ return false
+end
+
+local function PerformEquipSwap(item, inventoryId)
+ if not item or not inventoryId then return false end
+
+ -- Check if in combat and swapping weapons
+ local inCombat = UnitAffectingCombat("player")
+ local isWeapon = (inventoryId == 16 or inventoryId == 17 or inventoryId == 18)
+
+ if inCombat and isWeapon then
+ -- Don't swap while casting
+ if CleveRoids.CurrentSpell.type ~= "" then
+ return false
+ end
+
+ -- Check for on-swing spells if available
+ if GetCurrentCastingInfo then
+ local _, _, _, _, _, onswing = GetCurrentCastingInfo()
+ if onswing == 1 then
+ return false
+ end
+ end
+ end
+
+ -- Try to equip
+ local success = false
+
+ -- Method 1: Use item by bag/slot
+ if item.bagID and item.slot then
+ PickupContainerItem(item.bagID, item.slot)
+ if CursorHasItem() then
+ EquipCursorItem(inventoryId)
+ success = not CursorHasItem()
+ end
+ end
+
+ -- Method 2: Use item by inventory ID
+ if not success and item.inventoryID then
+ PickupInventoryItem(item.inventoryID)
+ if CursorHasItem() then
+ EquipCursorItem(inventoryId)
+ success = not CursorHasItem()
+ end
+ end
+
+ -- Method 3: Use EquipItemByName (SuperWoW)
+ if not success and item.name and EquipItemByName then
+ local ok = pcall(EquipItemByName, item.name, inventoryId)
+ success = ok
+ end
+
+ -- Clear cursor
+ if CursorHasItem() then
+ ClearCursor()
+ end
+
+ return success
+end
+
+-- Queue equipment swap function
+function CleveRoids.QueueEquipItem(item, slotName)
+ if not item or not slotName then return false end
+
+ local inventoryId = GetInventoryIdFromSlot(slotName)
+ if not inventoryId then return false end
+
+ local now = GetTime()
+
+ -- Try immediate equip if not on cooldown
+ if not IsSlotOnCooldown(inventoryId) then
+ local success = PerformEquipSwap(item, inventoryId)
+
+ if success then
+ CleveRoids.lastEquipTime[inventoryId] = now
+ CleveRoids.lastGlobalEquipTime = now
+ return true
+ end
+ end
+
+ -- Queue for later
+ table.insert(CleveRoids.equipmentQueue, {
+ item = item,
+ slotName = slotName,
+ inventoryId = inventoryId,
+ queueTime = now,
+ retries = 0,
+ maxRetries = 5
+ })
+
+ return false
+end
+
+-- Process equipment queue (called from OnUpdate)
+function CleveRoids.ProcessEquipmentQueue()
+ if not CleveRoids.equipmentQueue or table.getn(CleveRoids.equipmentQueue) == 0 then
+ return
+ end
+
+ local now = GetTime()
+ local i = 1
+
+ while i <= table.getn(CleveRoids.equipmentQueue) do
+ local queued = CleveRoids.equipmentQueue[i]
+
+ -- Check if cooldown passed
+ if not IsSlotOnCooldown(queued.inventoryId) then
+ local success = PerformEquipSwap(queued.item, queued.inventoryId)
+
+ if success then
+ CleveRoids.lastEquipTime[queued.inventoryId] = now
+ CleveRoids.lastGlobalEquipTime = now
+ table.remove(CleveRoids.equipmentQueue, i)
+ else
+ queued.retries = queued.retries + 1
+
+ if queued.retries >= queued.maxRetries then
+ table.remove(CleveRoids.equipmentQueue, i)
+ else
+ i = i + 1
+ end
+ end
+ else
+ i = i + 1
+ end
+
+ -- Remove expired entries (>10 seconds old)
+ if queued and (now - queued.queueTime) > 10 then
+ table.remove(CleveRoids.equipmentQueue, i)
+ end
+ end
+end
+
+-- Improved DisableAddon function
function CleveRoids.DisableAddon(reason)
- -- mark state
+ -- Prevent multiple disable calls
+ if CleveRoids.disabled then return end
+
+ -- Mark state
CleveRoids.disabled = true
- -- stop frame activity
+ -- Stop main frame activity
if CleveRoids.Frame then
if CleveRoids.Frame.UnregisterAllEvents then
CleveRoids.Frame:UnregisterAllEvents()
@@ -29,26 +245,30 @@ function CleveRoids.DisableAddon(reason)
end
end
- -- neuter slash command if you have one
- if SlashCmdList and SlashCmdList.CLEVEROIDS then
- SlashCmdList.CLEVEROIDS = function()
- CleveRoids.Print("|cffff0000CleveRoidMacros is disabled|r" ..
+ -- Neuter all slash commands
+ if SlashCmdList then
+ local disabledMsg = function()
+ CleveRoids.Print("|cffff0000SuperCleveRoidMacros is disabled|r" ..
(reason and (": " .. tostring(reason)) or ""))
end
+
+ SlashCmdList.CLEVEROIDS = disabledMsg
+ SlashCmdList.CAST = disabledMsg
+ SlashCmdList.USE = disabledMsg
+ SlashCmdList.EQUIP = disabledMsg
+ SlashCmdList.EQUIPMH = disabledMsg
+ SlashCmdList.EQUIPOH = disabledMsg
end
- -- try to disable for next login (if available in this client)
- local addonName = CleveRoids.addonName or "SuperCleveRoidMacros"
- if type(DisableAddOn) == "function" and addonName then
- -- pcall so old clients without per-character variants don’t explode
+ -- Try to disable for next login
+ local addonName = "SuperCleveRoidMacros"
+ if type(DisableAddOn) == "function" then
pcall(DisableAddOn, addonName)
- -- If your client supports per-character disabling you could also try:
- -- pcall(DisableAddOn, addonName, UnitName("player"))
end
- -- final notice
- CleveRoids.Print("|cffff0000Disabled|r" ..
- (reason and (" - " .. tostring(reason)) or ""))
+ -- Final notice
+ CleveRoids.Print("|cffff0000Disabled|r - " .. (reason or "Unknown reason"))
+ CleveRoids.Print("Please install required dependencies and /reload")
end
local frame = CreateFrame("Frame")
@@ -287,7 +507,7 @@ function CleveRoids.TestForActiveAction(actions)
if actions.active.spell then
actions.active.inRange = 1
- -- nampower range check (rebuild name(rank) like DoWithConditionals)
+ -- Enhanced nampower range check with spell ID support
if IsSpellInRange then
local unit = actions.active.conditionals and actions.active.conditionals.target or "target"
if unit == "focus" and pfUI and pfUI.uf and pfUI.uf.focus and pfUI.uf.focus.label and pfUI.uf.focus.id then
@@ -302,7 +522,16 @@ function CleveRoids.TestForActiveAction(actions)
end
end
if UnitExists(unit) then
- local r = IsSpellInRange(castName, unit)
+ -- Try to get spell ID for more accurate range check
+ local checkValue = castName
+ if GetSpellIdForName then
+ local spellId = GetSpellIdForName(castName)
+ if spellId and spellId > 0 then
+ checkValue = spellId
+ end
+ end
+
+ local r = IsSpellInRange(checkValue, unit)
if r ~= nil then
actions.active.inRange = r
end
@@ -315,7 +544,16 @@ function CleveRoids.TestForActiveAction(actions)
local onCooldown = (start > 0 and duration > 0)
if actions.active.isReactive then
- if not CleveRoids.IsReactiveUsable(actions.active.action) then
+ -- Use Nampower's IsSpellUsable if available for better detection
+ if IsSpellUsable then
+ local usable, oom = IsSpellUsable(actions.active.action)
+ if usable == 1 and oom ~= 1 then
+ actions.active.usable = (pfUI and pfUI.bars) and nil or 1
+ else
+ actions.active.usable = nil
+ end
+ actions.active.oom = false
+ elseif not CleveRoids.IsReactiveUsable(actions.active.action) then
actions.active.oom = false
actions.active.usable = nil
else
@@ -1760,41 +1998,43 @@ function CleveRoids.EquipBagItem(msg, offhand)
return true
end
--- TODO: Refactor all these DoWithConditionals sections
function CleveRoids.DoEquipMainhand(msg)
- local handled = false
+ local conditionals, item = CleveRoids.ParseConditionals(msg)
- local action = function(msg)
- return CleveRoids.EquipBagItem(msg, false)
- end
+ if not item or item == "" then return end
- for k, v in pairs(CleveRoids.splitStringIgnoringQuotes(msg)) do
- v = string.gsub(v, "^%?", "")
-
- if CleveRoids.DoWithConditionals(v, action, CleveRoids.FixEmptyTarget, false, action) then
- handled = true
- break
+ if not CleveRoids.DoWithConditionals(conditionals, function()
+ local itemObj = CleveRoids.GetItem(item)
+ if not itemObj then
+ return false
end
+
+ -- Use queue system for reliable swaps
+ return CleveRoids.QueueEquipItem(itemObj, "MainHandSlot")
+ end) then
+ return false
end
- return handled
+
+ return true
end
function CleveRoids.DoEquipOffhand(msg)
- local handled = false
+ local conditionals, item = CleveRoids.ParseConditionals(msg)
- local action = function(msg)
- return CleveRoids.EquipBagItem(msg, true)
- end
+ if not item or item == "" then return end
- for k, v in pairs(CleveRoids.splitStringIgnoringQuotes(msg)) do
- v = string.gsub(v, "^%?", "")
-
- if CleveRoids.DoWithConditionals(v, action, CleveRoids.FixEmptyTarget, false, action) then
- handled = true
- break
+ if not CleveRoids.DoWithConditionals(conditionals, function()
+ local itemObj = CleveRoids.GetItem(item)
+ if not itemObj then
+ return false
end
+
+ return CleveRoids.QueueEquipItem(itemObj, "SecondaryHandSlot")
+ end) then
+ return false
end
- return handled
+
+ return true
end
function CleveRoids.DoUnshift(msg)
@@ -1936,6 +2176,16 @@ CleveRoids.DoConditionalCancelAura = function(msg)
end
function CleveRoids.OnUpdate(self)
+ -- Process equipment queue
+ if CleveRoids.ProcessEquipmentQueue then
+ CleveRoids.ProcessEquipmentQueue()
+ end
+
+ -- Update casting state from Nampower
+ if CleveRoids.UpdateCastingState then
+ CleveRoids.UpdateCastingState()
+ end
+
local time = GetTime()
local refreshRate = CleveRoidMacros.refresh or 5
refreshRate = 1/refreshRate
@@ -2315,11 +2565,17 @@ CleveRoids.Frame:RegisterEvent("UNIT_POWER")
if CleveRoids.hasSuperwow then
CleveRoids.Frame:RegisterEvent("UNIT_CASTEVENT")
end
+if QueueSpellByName then
+ CleveRoids.Frame:RegisterEvent("SPELL_QUEUE_EVENT")
+ CleveRoids.Frame:RegisterEvent("SPELL_CAST_EVENT")
+end
CleveRoids.Frame:RegisterEvent("START_AUTOREPEAT_SPELL")
CleveRoids.Frame:RegisterEvent("STOP_AUTOREPEAT_SPELL")
CleveRoids.Frame:RegisterEvent("SPELLCAST_CHANNEL_START")
CleveRoids.Frame:RegisterEvent("SPELLCAST_CHANNEL_STOP")
+
+
-- Order-agnostic SuperMacro hook installer
local function CRM_SM_InstallHook()
if CleveRoids.SM_RunLineHooked then return end
@@ -2385,27 +2641,17 @@ function CleveRoids.Frame:UNIT_PET()
end
end
+-- Simplified PLAYER_LOGIN - requirements already checked
function CleveRoids.Frame:PLAYER_LOGIN()
+ -- Skip if already disabled
+ if CleveRoids.disabled then return end
+
_, CleveRoids.playerClass = UnitClass("player")
_, CleveRoids.playerGuid = UnitExists("player")
CleveRoids.IndexSpells()
CleveRoids.IndexPetSpells()
CleveRoids.initializationTimer = GetTime() + 1.5
CRM_SM_InstallHook()
- if not CleveRoids.hasSuperwow or not IsSpellInRange then
- if not CleveRoids.hasSuperwow then
- CleveRoids.Print("|cFFFF0000CleveRoidMacros|r requires |cFF00FFFFbalakethelock's SuperWoW|r:")
- CleveRoids.Print("https://github.com/balakethelock/SuperWoW")
- end
- if not IsSpellInRange then
- CleveRoids.Print("|cFFFF0000CleveRoidMacros|r requires |cFF00FFFFpepopo978's Nampower|r:")
- CleveRoids.Print("https://github.com/pepopo978/nampower")
- end
- CleveRoids.DisableAddon("Missing Requirements")
- return
- else
- CleveRoids.Print("|cFF4477FFCleveR|r|cFFFFFFFFoid Macros|r |cFF00FF00Loaded|r - See the README.")
- end
end
function CleveRoids.Frame:ADDON_LOADED(addon)
@@ -2638,6 +2884,58 @@ function CleveRoids.Frame:UNIT_POWER()
end
end
+function CleveRoids.Frame:SPELL_QUEUE_EVENT()
+ if event == "SPELL_QUEUE_EVENT" then
+ local eventCode = arg1
+ local spellId = arg2
+
+ local NORMAL_QUEUED = 2
+ local NON_GCD_QUEUED = 4
+ local ON_SWING_QUEUED = 0
+ local NORMAL_QUEUE_POPPED = 3
+ local NON_GCD_QUEUE_POPPED = 5
+ local ON_SWING_QUEUE_POPPED = 1
+
+ if eventCode == NORMAL_QUEUED or eventCode == NON_GCD_QUEUED or eventCode == ON_SWING_QUEUED then
+ CleveRoids.queuedSpell = {
+ spellId = spellId,
+ queueType = eventCode,
+ queueTime = GetTime()
+ }
+ if SpellInfo then
+ local name = SpellInfo(spellId)
+ if name then
+ CleveRoids.queuedSpell.spellName = name
+ end
+ end
+ CleveRoids.QueueActionUpdate()
+ elseif eventCode == NORMAL_QUEUE_POPPED or eventCode == NON_GCD_QUEUE_POPPED or eventCode == ON_SWING_QUEUE_POPPED then
+ CleveRoids.queuedSpell = nil
+ CleveRoids.QueueActionUpdate()
+ end
+ end
+end
+
+function CleveRoids.Frame:SPELL_CAST_EVENT()
+ if event == "SPELL_CAST_EVENT" then
+ local success = arg1
+ local spellId = arg2
+
+ if success == 1 then
+ CleveRoids.lastCastSpell = {
+ spellId = spellId,
+ timestamp = GetTime()
+ }
+ if SpellInfo then
+ local name = SpellInfo(spellId)
+ if name then
+ CleveRoids.lastCastSpell.spellName = name
+ end
+ end
+ end
+ end
+end
+
CleveRoids.Hooks.SendChatMessage = SendChatMessage
function SendChatMessage(msg, ...)
@@ -2848,3 +3146,22 @@ SlashCmdList["CLEVEROID"] = function(msg)
DEFAULT_CHAT_FRAME:AddMessage("/cleveroid debug [0|1] - Toggle learning debug messages")
end
end
+
+SLASH_CLEAREQUIPQUEUE1 = "/clearequipqueue"
+SlashCmdList.CLEAREQUIPQUEUE = function()
+ CleveRoids.equipmentQueue = {}
+ CleveRoids.Print("Equipment queue cleared")
+end
+
+SLASH_EQUIPQUEUESTATUS1 = "/equipqueuestatus"
+SlashCmdList.EQUIPQUEUESTATUS = function()
+ local count = table.getn(CleveRoids.equipmentQueue)
+ CleveRoids.Print("Equipment queue has " .. count .. " pending items")
+
+ for i, entry in ipairs(CleveRoids.equipmentQueue) do
+ local itemName = entry.item.name or "Unknown"
+ local slotName = entry.slotName or "Unknown"
+ local retries = entry.retries or 0
+ CleveRoids.Print(i .. ". " .. itemName .. " -> " .. slotName .. " (retries: " .. retries .. ")")
+ end
+end
diff --git a/Init.lua b/Init.lua
index 943793f..6e7f033 100644
--- a/Init.lua
+++ b/Init.lua
@@ -52,6 +52,32 @@ CleveRoids.CurrentSpell = {
wand = false,
}
+-- Enhanced casting state tracking
+CleveRoids.UpdateCastingState = function()
+ if not GetCurrentCastingInfo then return false end
+
+ local castId, visId, autoId, casting, channeling, onswing, autoattack = GetCurrentCastingInfo()
+
+ -- Update CurrentSpell based on actual cast state
+ if casting == 1 then
+ CleveRoids.CurrentSpell.type = "cast"
+ CleveRoids.CurrentSpell.castingSpellId = castId
+ elseif channeling == 1 then
+ CleveRoids.CurrentSpell.type = "channeled"
+ CleveRoids.CurrentSpell.castingSpellId = visId
+ else
+ CleveRoids.CurrentSpell.type = ""
+ CleveRoids.CurrentSpell.castingSpellId = nil
+ end
+
+ CleveRoids.CurrentSpell.autoAttack = (autoattack == 1)
+ CleveRoids.CurrentSpell.onSwingPending = (onswing == 1)
+ CleveRoids.CurrentSpell.visualSpellId = visId
+ CleveRoids.CurrentSpell.autoRepeatSpellId = autoId
+
+ return true
+end
+
CleveRoids.dynamicCmds = {
["/cast"] = true,
["/castpet"] = true,
@@ -62,6 +88,17 @@ CleveRoids.dynamicCmds = {
["/equipoh"] = true,
}
+-- Equipment swap queue system
+CleveRoids.equipmentQueue = {}
+CleveRoids.lastEquipTime = {}
+CleveRoids.lastGlobalEquipTime = 0
+CleveRoids.EQUIP_COOLDOWN = 1.5 -- Per-slot cooldown
+CleveRoids.EQUIP_GLOBAL_COOLDOWN = 0.5 -- Global cooldown
+
+-- Spell queue state (Nampower)
+CleveRoids.queuedSpell = nil
+CleveRoids.lastCastSpell = nil
+
CleveRoids.ignoreKeywords = {
action = true,
ignoretooltip = true,
@@ -136,4 +173,29 @@ CleveRoids.WeaponTypeNames = {
Wands = { slot = "RangedSlot", name = CleveRoids.Localized.Wand },
}
+-- Detect available features
+CleveRoids.hasNampower = (QueueSpellByName ~= nil)
+CleveRoids.hasUnitXP = pcall(UnitXP, "nop", "nop")
+
+-- 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.hasUnitXP then table.insert(features, "UnitXP") end
+ if CleveRoids.hasTurtle then table.insert(features, "Turtle") end
+
+ if table.getn(features) > 0 then
+ CleveRoids.Print("Enhanced features: " .. table.concat(features, ", "))
+ end
+end
+
+-- Call on next frame to ensure everything is loaded
+local initFrame = CreateFrame("Frame")
+initFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
+initFrame:SetScript("OnEvent", function()
+ this:UnregisterAllEvents()
+ PrintFeatures()
+end)
+
_G["CleveRoids"] = CleveRoids
diff --git a/README.md b/README.md
index 5bc0343..4f6ce40 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,11 @@
# SuperCleveRoid Macros
This was originally an effort to bring the dynamic tooltip and cast sequence functionality of [CleverMacro](https://github.com/DanielAdolfsson/CleverMacro) into [Roid-Macros](https://github.com/MarcelineVQ/Roid-Macros). It has since expanded after some additional changes I wanted along with feedback from others. The majority of credit goes to the [original addon authors](#original-addons--authors). Still a work in progress.
-Both [SuperWoW](https://github.com/balakethelock/SuperWoW) and [Nampower](https://github.com/pepopo978/nampower) are REQUIRED.
+# REQUIRED DLLS
+[SuperWoW](https://github.com/balakethelock/SuperWoW)
+[Nampower](https://github.com/pepopo978/nampower)
+[UnitXP_SP3](https://codeberg.org/konaka/UnitXP_SP3)
+# ALL 3 ARE REQUIRED!!!
Check slash command and all conditional lists for new usages!
---
@@ -13,13 +17,13 @@ Check slash command and all conditional lists for new usages!
3. Rename the `SuperCleveRoidMacros-main` folder to `SuperCleveRoidMacros`
4. Check that it is enabled in your addon list in-game.
5. Make sure you don't have other macro addons that may interfere.
-6. SUPERWOW and NAMPOWER are REQUIRED!
+6. SUPERWOW ,NAMPOWER, and UNITXP_SP3 are REQUIRED!
7. Disable Macrotweak module in pfui, and disable macrotweak in shagutweaks!
-
+8. Disable Scan Macros for spells in the actionbar section of pfui!
* [SuperWoW](https://github.com/balakethelock/SuperWoW) dll mod is required
* [Nampower](https://github.com/pepopo978/nampower) dll mod is required
-
+* [UnitXP_SP3](https://codeberg.org/konaka/UnitXP_SP3) dll mod is required
### SuperCleveRoidMacros Settings
* `/cleveroid` - View current settings
@@ -43,57 +47,7 @@ Check slash command and all conditional lists for new usages!
---
# What's Different
-
-## Commands and Conditionals
### ***Important! Spells, Items, conditionals, etc are case sensitive. Barring a bug, if there is an issue, it's almost always because something is typed incorrectly.***
-* New Alias for stopcasting 'unqueue'
-* New update settings to swap between realtime or event based. Event based is the default and saves cpu and memory usage.
-* New Conditional class/noclass.
-* Conditional exists is now noable.
-* Added alive/dead alias' nodead/noalive.
-* Conditional combat can now take unitids.
-* Renamed `attacking`/`noattacking` contitionals to `targeting`/`notargeting` for better clarity of what they do.
-* All conditionals that should have an implied target of @target now do
-* Conditionals can be separated by comma or space
-* Conditional arguments with spaces that would need to be replaced with underscores (`_`) can instead be enclosed in quotes.
-* Updated syntax for conditionals with stack/time checking
- * You can use `>, <, =, ~=, >=, <=` as valid operators. Include a `#` sign to designate you're checking the stack's, not time
- * Operators should be placed after the colon, either by itself (if applicable) or after the argument
- `[power:>20]`, `[debuff:"Sunder Armor<#5"]`, `[buff:Thorns<30]`
- * Omitting an operator or amount will just check that the buff/debuff exists
- * When stack checking, omitting an operator assumes equals (=)
- `[debuff:"Sunder Armor#3]` is the same as `[debuff:"Sunder Armor=#3]`
- * Omitting the argument entirely will assume you want to use the action in the condition check
- `[nobuff] Power Word: Fortitude` is the same as `[nobuff:"Power Word: Fortitude"] Power Word: Fortitude`
-* Updated most conditionals (that make sense) to allow for multiple arguments. Use a / to separate them.
-* Added `plain` [mod] and [nomod] to conditionals
-* Added `alive` conditional
-* Added `member` conditional
-* Added `form` conditional (an alias of stance)
-* Added Priest Shadowform to `stance`/`form` conditionals. Use `[stance:1]` or `[form:1]`
-* Added Rogue Stealth to `stance`/`form` conditionals. Use `[stance:1]` or `[form:1]`
-* Added `combo` conditional
-* Added `known` conditional
-* Added `resting` conditional
-* Changed `cooldown` conditional to ignore the GCD (will show usable if the cooldown is exactly 1.5 seconds) -- It felt better but not 100% on keeping this, send feedback.
-* Added `cdgcd` conditional that works the same as `cooldown` that will trigger for any length of cooldown
-* Added `inrange` conditional ([Nampower](https://github.com/pepopo978/nampower) required)
-* Added changes to make usable/mana/range updating show properly in pfUI.
-* Added Bongos action bars compatibility support
-* Added /runmacro command. Same as /cast {macroname} but always ignores icon/tooltip
-* Added /retarget command. Clears your target if it doesn't exist, has 0 hp or if you can't attack it and then targets the nearest enemy
-* Added /equipmh command. Equips the weapon into your mainhand slot.
-* Added ? flag to prevent an action from affecting the icon/tooltips. It must be the first character.
- ```lua
- #showtooltip
- /use ?Some Item
- /cast [reactive] Overpower; Heroic Strike
- ```
-* Updated ! flag as a shortcut easily make spammable spells.
-* Added ~ flag to either cast or cancel the buff/aura if possible. (Toggles the spell on/off)
-* Added /stopmacro command.
-* Added conditional use to /startattack /stopattack /stopcasting.
-* Added stat conditional. ex: '/cast [hp:<=20 stat:AP>=2000]Bloodthirst'
## Dynamic Icons and Tooltips
* The icon and tooltip for a macro will automatically update to the first true condition's action. Left to right, top to bottom.
* Consumables and certain other item types will now show a count on the action bar.
@@ -159,34 +113,6 @@ Check slash command and all conditional lists for new usages!
```
---
-# Usage
-## Slash Commands
-| Command | Conditionals Supported | Purpose |
-|-----------------------| :-: |---------|
-| /cleveroid | | show update settings. /cleveroid realtime 0/1 : /cleveroid refresh 1-10 |
-| /target | * | Targets a unit that matchets the conditionals. Requires friendly nameplates for friendly non-party/raid units or their pets. Requires enemy nameplates for non-friendly units. |
-| /retarget | | Clears your target if it doesn't exist, has 0 hp or if you can't attack it and then targets the nearest enemy. |
-| /startattack | * | Starts auto-attacking. |
-| /stopattack | * | Stops auto-attacking. |
-| /stopcasting | * | Stops casting. |
-| /unqueue | * | Stops casting. |
-| /petattack | * | Command pet to Attack. |
-| /petfollow | * | Command pet to Follow. |
-| /petwait | * | Command pet to Wait (Stay). |
-| /petaggressive | * | Set pet mode to Aggressive. |
-| /petdefensive | * | Set pet mode to Defensive. |
-| /petpassive | * | Set pet mode to Passive. |
-| /castsequence | * | Performs a cast sequence. See [below](#cast-sequence) for more infomation. |
-| /equip | * | Equips an item by name or itemid. |
-| /equipmh | * | Equips an item by name or itemid into your mainhand slot. |
-| /equipoh | * | Equips an item by name or itemid into your offhand slot. |
-| /unshift | * | Cancels your current shapeshift form. |
-| /cancelaura, /unbuff | | Cancels a valid buff/aura. |
-| /runmacro | | Runs a macro. Use /runmacro {macroname} |
-| /use | * | Uses an item by name or id |
-| /cast | * | Casts a spell by name |
-| /stopmacro | * | prevent any lines under /stopmacro from being run unless conditionals are met |
-
## Debuff Timer System (SuperWoW) BY: yani9o
SuperCleveRoidMacros includes a built-in **debuff timer tracking system** that accurately tracks your debuff durations using SuperWoW's advanced features:
@@ -249,6 +175,35 @@ The system includes pre-configured durations for 329+ debuffs across all classes
- Falls back to static database for unknown spells
- No dependency on pfUI or other addons
+---
+# Usage
+## Slash Commands
+| Command | Conditionals Supported | Purpose |
+|-----------------------| :-: |---------|
+| /cleveroid | | show update settings. /cleveroid realtime 0/1 : /cleveroid refresh 1-10 |
+| /target | * | Targets a unit that matchets the conditionals. Requires friendly nameplates for friendly non-party/raid units or their pets. Requires enemy nameplates for non-friendly units. |
+| /retarget | | Clears your target if it doesn't exist, has 0 hp or if you can't attack it and then targets the nearest enemy. |
+| /startattack | * | Starts auto-attacking. |
+| /stopattack | * | Stops auto-attacking. |
+| /stopcasting | * | Stops casting. |
+| /unqueue | * | Stops casting. |
+| /petattack | * | Command pet to Attack. |
+| /petfollow | * | Command pet to Follow. |
+| /petwait | * | Command pet to Wait (Stay). |
+| /petaggressive | * | Set pet mode to Aggressive. |
+| /petdefensive | * | Set pet mode to Defensive. |
+| /petpassive | * | Set pet mode to Passive. |
+| /castsequence | * | Performs a cast sequence. See [below](#cast-sequence) for more infomation. |
+| /equip | * | Equips an item by name or itemid. |
+| /equipmh | * | Equips an item by name or itemid into your mainhand slot. |
+| /equipoh | * | Equips an item by name or itemid into your offhand slot. |
+| /unshift | * | Cancels your current shapeshift form. |
+| /cancelaura, /unbuff | | Cancels a valid buff/aura. |
+| /runmacro | | Runs a macro. Use /runmacro {macroname} |
+| /use | * | Uses an item by name or id |
+| /cast | * | Casts a spell by name |
+| /stopmacro | * | prevent any lines under /stopmacro from being run unless conditionals are met |
+
---
## Cast Sequence
@@ -322,8 +277,9 @@ The system includes pre-configured durations for 329+ debuffs across all classes
* You can omit the value of a conditional if you want to check the same spell/item that you are using in the action.
`[debuff:"Sunder Armor"<#5] Sunder Armor` == `[debuff:<#5] Sunder Armor`
`[nobuff:"Mark of the Wild"] Mark of the Wild` == `[nobuff] Mark of the Wild`
-* [SuperWoW](https://github.com/balakethelock/SuperWoW) dll mod is required for some conditionals
-* [Nampower](https://github.com/pepopo978/nampower) dll mod required for some conditionals
+* [SuperWoW](https://github.com/balakethelock/SuperWoW) dll mod is required
+* [Nampower](https://github.com/pepopo978/nampower) dll mod required
+* [UnitXP_SP3](https://codeberg.org/konaka/UnitXP_SP3) dll mod required
### Special Characters
| Character | Syntax Examples | Description |
@@ -367,7 +323,9 @@ The system includes pre-configured durations for 329+ debuffs across all classes
| stat | [stat:stat>=x/<=y] | * | | Check if one of the players statistics is greater or less than a specific number. Available Stats: str/strength, agi/agility, stam/stamina, int/intellect, spi/spirit, ap/attackpower, rap/rangedattackpower, healing/healingpower, arcane_power, fire_power, frost_power, nature_power, shadow_power, armor, defense, arcane_res, fire_res, frost_res, nature_res, shadow_res. |
| pet | [pet]
[pet:Voidwalker]
[pet:Imp/Felhunter] | * | * | If the player has a pet summoned and optionally if it matches the specified pet type(s). Works for Warlock demons and Hunter pets. |
| swimming | [swimming] | | * | Druid only, works like reactive but for aquatic form, must have aquatic form on one of your non-stance actionbars.*** |
-| mybuffcount | [mybuffcount:>=X]
[mybuffcount:<=X] | | | If they player has more or less auras present than X.|
+| mybuffcount | [mybuffcount:>=X]
[mybuffcount:<=X] | | | If the player has more or less auras present than X.|
+| queuedspell | [queuedspell]
[queuedspell:X] | | * | if the player has any or a specific spell queued with nampower. |
+| onswingpending | [onswingpending] | | * | If the player has a on swing spell pending.|
### Unit Based
### The default @unitid is usually @target if you don't specify one
@@ -385,6 +343,7 @@ The system includes pre-configured durations for 329+ debuffs across all classes
| hp | [hp:>=X]
[hp:>=X/<=Y] | * | | The @unitid health **PERCENT** compared to X. |
| hplost | [hplost:>=X]
[hplost:>=X/<=Y] | * | | The @unitid health lost compared to X. |
| inrange | [inrange]
[inrange:"Name"] | * | * | If the specified @unitid is in range of the spell. |
+| outrange | [outrange]
[outrange:"Name"] | * | | If the specified @unitid is out of range of the spell. |
| isnpc | [isnpc] | | | If the @unitid is an npc.
See this [article](https://wowpedia.fandom.com/wiki/UnitId) for a list of unitids.
Not all units are valid in vanilla. |
| isplayer | [isplayer] | | | If the @unitid is a player.
See this [article](https://wowpedia.fandom.com/wiki/UnitId) for a list of unitids.
Not all units are valid in vanilla. |
| member | [member] | * | | If the @unitid is in your party OR raid. |
@@ -399,6 +358,10 @@ The system includes pre-configured durations for 329+ debuffs across all classes
| exists | [exists] | | * | If the @unitid exists. |
| @unitid | [@mouseover] | | | The @unitid is a valid target. |
| class | [class:classname1/classname2]
[class:Warrior/Priest] | * | * | The target is a player of the specified class/classes. |
+| distance | [distance:>X]
[distance: