diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7d097d6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +wow-api-type-definitions/ +.gitignore diff --git a/Conditionals.lua b/Conditionals.lua index 8c31541..f2d12cb 100644 --- a/Conditionals.lua +++ b/Conditionals.lua @@ -215,34 +215,31 @@ end function CleveRoids.HasGearEquipped(gearId) if not gearId then return false end + -- Handle both numeric IDs and string IDs like "5196" local wantId = tonumber(gearId) - local wantName = (type(gearId) == "string") and string.lower(gearId) or nil + local wantName = (type(gearId) == "string" and not wantId) and string.lower(gearId) or nil for slot = 1, 19 do local link = GetInventoryItemLink("player", slot) if link then - -- Lua 5.0: use string.find with captures local _, _, id = string.find(link, "item:(%d+)") local _, _, nameInBrackets = string.find(link, "%[(.+)%]") if wantId and id and tonumber(id) == wantId then return true end - if wantName and nameInBrackets and string.lower(nameInBrackets) == wantName then return true end - - -- Fallback: resolve via GetItemInfo using the numeric id if we have it + -- Fallback: resolve via GetItemInfo if wantName and not nameInBrackets and id then - local itemName = GetItemInfo(tonumber(id)) -- may be nil if not cached + local itemName = GetItemInfo(tonumber(id)) if itemName and string.lower(itemName) == wantName then return true end end end end - return false end @@ -567,7 +564,7 @@ function CleveRoids.ValidateCooldown(args, ignoreGCD) local expires = CleveRoids.GetCooldown(args.name, ignoreGCD) if not args.operator and not args.amount then - return expires > 0 + return expires > GetTime() elseif CleveRoids.operators[args.operator] then return CleveRoids.comparators[args.operator](expires - GetTime(), args.amount) end @@ -796,6 +793,45 @@ function CleveRoids.ValidatePlayerDebuff(args) return CleveRoids.ValidateAura("player", args, false) end +function CleveRoids.ValidateWeaponImbue(slot, imbueName) + -- Check if weapon has enchant + local hasMainEnchant, mainExpiration, mainCharges, hasOffEnchant, offExpiration, offCharges = GetWeaponEnchantInfo() + + local hasEnchant = (slot == "mh" and hasMainEnchant) or (slot == "oh" and hasOffEnchant) + if not hasEnchant then return false end + + -- If no specific imbue requested, just return enchant exists + if not imbueName or imbueName == "" then return true end + + -- Create tooltip scanner if needed + if not CleveRoidsTooltip then + CreateFrame("GameTooltip", "CleveRoidsTooltip", nil, "GameTooltipTemplate") + end + + -- Scan weapon tooltip + CleveRoidsTooltip:SetOwner(UIParent, "ANCHOR_NONE") + CleveRoidsTooltip:ClearLines() + CleveRoidsTooltip:SetInventoryItem("player", slot == "mh" and 16 or 17) + + -- Look for green text (temporary enchant) + for i = 1, CleveRoidsTooltip:NumLines() do + local text = _G["CleveRoidsTooltipTextLeft"..i] + if text then + local line = text:GetText() + local r, g, b = text:GetTextColor() + -- Green text indicates temporary enchant + if line and g > 0.8 and r < 0.2 and b < 0.2 then + -- Normalize and compare + line = string.lower(line) + imbueName = string.lower(string.gsub(imbueName, "_", " ")) + return string.find(line, imbueName, 1, true) ~= nil + end + end + end + + return false +end + -- TODO: Look into https://github.com/Stanzilla/WoWUIBugs/issues/47 if needed function CleveRoids.GetCooldown(name, ignoreGCD) if not name then return 0 end @@ -952,6 +988,21 @@ function CleveRoids.GetItemCooldown(item) return 0, 0, 0 end +function CleveRoids.ValidatePlayerAuraCount(bigger, amount) + local aura_ix = -1 + local num = 0 + while true do + aura_ix = GetPlayerBuff(num,"HELPFUL|PASSIVE") + if aura_ix == -1 then break end + num = num + 1 + end + if bigger == 0 then + return num < tonumber(amount) + else + return num > tonumber(amount) + end +end + function CleveRoids.IsReactive(name) return CleveRoids.reactiveSpells[spellName] ~= nil end @@ -970,12 +1021,21 @@ function CleveRoids.GetActionButtonInfo(slot) end function CleveRoids.IsReactiveUsable(spellName) + -- Use Nampower's IsSpellUsable if available (more accurate) + if IsSpellUsable then + local usable, oom = IsSpellUsable(spellName) + if usable == 1 and oom ~= 1 then + return 1 + else + return nil, oom + end + end + + -- Fallback to original method if not CleveRoids.reactiveSlots[spellName] then return false end local actionSlot = CleveRoids.reactiveSlots[spellName] - local isUsable, oom = CleveRoids.Hooks.OriginalIsUsableAction(actionSlot) local start, duration = GetActionCooldown(actionSlot) - if isUsable and (start == 0 or duration == 1.5) then -- 1.5 just means gcd is active return 1 else @@ -983,6 +1043,35 @@ function CleveRoids.IsReactiveUsable(spellName) end end +-- Check if any spell is usable (not just reactive) +function CleveRoids.CheckSpellUsable(spellName) + if not spellName then return false end + + -- Use Nampower's IsSpellUsable if available + if IsSpellUsable then + local usable, oom = IsSpellUsable(spellName) + return (usable == 1 and oom ~= 1) + end + + -- Fallback: check if spell exists and player has mana/rage/energy + local spell = CleveRoids.GetSpell(spellName) + if not spell then return false end + + -- Check mana cost + local currentPower = UnitMana("player") + if spell.cost and currentPower < spell.cost then + return false + end + + -- Check cooldown (ignore GCD) + local start, duration = GetSpellCooldown(spell.spellSlot, spell.bookType) + if start > 0 and duration > 1.5 then + return false + end + + return true +end + function CleveRoids.CheckSpellCast(unit, spell) if not CleveRoids.hasSuperwow then return false end @@ -1150,13 +1239,45 @@ CleveRoids.Keywords = { end, equipped = function(conditionals) - return And(conditionals.equipped, function (v) + local itemsToCheck = {} + + -- Case 1: conditionals.equipped is a string (e.g., [equipped]ItemName) + if type(conditionals.equipped) == "string" then + table.insert(itemsToCheck, conditionals.equipped) + -- Case 2: conditionals.equipped is a table (e.g., [equipped:Shields]) + elseif type(conditionals.equipped) == "table" and table.getn(conditionals.equipped) > 0 then + itemsToCheck = conditionals.equipped + -- Case 3: No value provided, check the action + elseif conditionals.action then + table.insert(itemsToCheck, conditionals.action) + else + return false + end + + -- Check all items + return Or(itemsToCheck, function(v) return (CleveRoids.HasWeaponEquipped(v) or CleveRoids.HasGearEquipped(v)) end) end, noequipped = function(conditionals) - return And(conditionals.noequipped, function (v) + local itemsToCheck = {} + + -- Case 1: conditionals.noequipped is a string (e.g., [noequipped]ItemName) + if type(conditionals.noequipped) == "string" then + table.insert(itemsToCheck, conditionals.noequipped) + -- Case 2: conditionals.noequipped is a table (e.g., [noequipped:Shields]) + elseif type(conditionals.noequipped) == "table" and table.getn(conditionals.noequipped) > 0 then + itemsToCheck = conditionals.noequipped + -- Case 3: No value provided, check the action + elseif conditionals.action then + table.insert(itemsToCheck, conditionals.action) + else + return false + end + + -- Check all items - ALL must be NOT equipped for this to pass + return And(itemsToCheck, function(v) return not (CleveRoids.HasWeaponEquipped(v) or CleveRoids.HasGearEquipped(v)) end) end, @@ -1188,11 +1309,33 @@ CleveRoids.Keywords = { end, noreactive = function(conditionals) - return And(conditionals.noreactive,function (v) + return And(conditionals.noreactive, function (v) return not CleveRoids.IsReactiveUsable(v) end) end, + usable = function(conditionals) + return Or(conditionals.usable, function(spellName) + -- If checking a reactive spell, use reactive logic + if CleveRoids.reactiveSpells[spellName] then + return CleveRoids.IsReactiveUsable(spellName) + end + -- Otherwise check general spell usability + return CleveRoids.CheckSpellUsable(spellName) + end) + end, + + nousable = function(conditionals) + return And(conditionals.nousable, function(spellName) + -- If checking a reactive spell, use reactive logic + if CleveRoids.reactiveSpells[spellName] then + return not CleveRoids.IsReactiveUsable(spellName) + end + -- Otherwise check general spell usability + return not CleveRoids.CheckSpellUsable(spellName) + end) + end, + member = function(conditionals) return Or(conditionals.member, function(v) return @@ -1412,10 +1555,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, @@ -1442,14 +1593,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, @@ -1604,5 +1793,174 @@ CleveRoids.Keywords = { noswimming = function(conditionals) -- Check if "Aquatic Form" is NOT usable return not CleveRoids.IsReactiveUsable("Aquatic Form") + 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, + + mybuffcount = function(conditionals) + return And(conditionals.mybuffcount,function (v) return CleveRoids.ValidatePlayerAuraCount(v.bigger, v.amount) end) + end, + + mhimbue = function(conditionals) + return CleveRoids.ValidateWeaponImbue("mh", conditionals.value) + end, + + nomhimbue = function(conditionals) + return not CleveRoids.ValidateWeaponImbue("mh", conditionals.value) + end, + + ohimbue = function(conditionals) + return CleveRoids.ValidateWeaponImbue("oh", conditionals.value) + end, + + noohimbue = function(conditionals) + return not CleveRoids.ValidateWeaponImbue("oh", conditionals.value) end } diff --git a/Core.lua b/Core.lua index 7282e09..a1a6578 100644 --- a/Core.lua +++ b/Core.lua @@ -9,13 +9,232 @@ local CleveRoids = _G.CleveRoids or {} _G.CleveRoids = CleveRoids CleveRoids.lastItemIndexTime = 0 CleveRoids.initializationTimer = nil -CleveRoids.isActionUpdateQueued = true -- Flag to check if a full action update is needed +CleveRoids.isActionUpdateQueued = true +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() @@ -26,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") @@ -284,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 @@ -299,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 @@ -312,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 @@ -1648,15 +1889,48 @@ function CleveRoids.DoUse(msg) end function CleveRoids.EquipBagItem(msg, offhand) + if CleveRoids.equipInProgress then + return false + end + + local now = GetTime() + if (now - (CleveRoids.lastItemIndexTime or 0)) > 0.5 then + CleveRoids.IndexItems() + CleveRoids.lastItemIndexTime = now + end + local item = CleveRoids.GetItem(msg) - if not item or not item.name then return false end + if not item or not item.name then + CleveRoids.IndexItems() + CleveRoids.lastItemIndexTime = now + item = CleveRoids.GetItem(msg) + if not item or not item.name then + return false + end + end local invslot = offhand and 17 or 16 + local throttleKey = invslot .. "_" .. (item.id or item.name) + + if UnitAffectingCombat("player") and (invslot == 16 or invslot == 17) then + local timeSinceLastSwap = now - CleveRoids.lastWeaponSwapTime + if timeSinceLastSwap < 1.5 then + return false + end + end + + if CleveRoids.lastEquipTime[throttleKey] and (now - CleveRoids.lastEquipTime[throttleKey]) < 0.2 then + return false + end local currentItemLink = GetInventoryItemLink("player", invslot) if currentItemLink then + local _, _, currentID = string.find(currentItemLink, "item:(%d+)") local currentItemName = GetItemInfo(currentItemLink) - if currentItemName and currentItemName == item.name then + + if (currentID and item.id and tonumber(currentID) == tonumber(item.id)) or + (currentItemName and currentItemName == item.name) then + CleveRoids.lastEquipTime[throttleKey] = now return true end end @@ -1665,34 +1939,72 @@ function CleveRoids.EquipBagItem(msg, offhand) return false end - if item.bagID then + CleveRoids.equipInProgress = true + + if type(CloseStackSplitFrame) == "function" then + CloseStackSplitFrame() + end + 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) - if type(CloseStackSplitFrame) == "function" then CloseStackSplitFrame() end - if CursorHasItem and CursorHasItem() then ClearCursor() end - - PickupContainerItem(item.bagID, item.slot) - else + local link = GetContainerItemLink(item.bagID, item.slot) + if link then + local _, _, bagItemID = string.find(link, "item:(%d+)") + if bagItemID and item.id and tonumber(bagItemID) == tonumber(item.id) then + PickupContainerItem(item.bagID, item.slot) + pickupSuccess = true + end + end + elseif item.inventoryID then PickupInventoryItem(item.inventoryID) + pickupSuccess = true + end + + if not pickupSuccess then + CleveRoids.equipInProgress = false + CleveRoids.lastItemIndexTime = 0 + return false + end + + if not CursorHasItem or not CursorHasItem() then + ClearCursor() + CleveRoids.equipInProgress = false + CleveRoids.lastItemIndexTime = 0 + return false end EquipCursorItem(invslot) + ClearCursor() - CleveRoids.lastItemIndexTime = 0 + + CleveRoids.lastEquipTime[throttleKey] = now + + if UnitAffectingCombat("player") and (invslot == 16 or invslot == 17) then + CleveRoids.lastWeaponSwapTime = now + end + + CleveRoids.equipInProgress = false + + CleveRoids.IndexItems() + CleveRoids.lastItemIndexTime = now + return true end --- TODO: Refactor all these DoWithConditionals sections function CleveRoids.DoEquipMainhand(msg) local handled = false - local action = function(msg) return CleveRoids.EquipBagItem(msg, false) 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 @@ -1703,14 +2015,11 @@ end function CleveRoids.DoEquipOffhand(msg) local handled = false - local action = function(msg) return CleveRoids.EquipBagItem(msg, true) 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 @@ -1858,6 +2167,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 @@ -2237,11 +2556,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 @@ -2307,27 +2632,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) @@ -2560,6 +2875,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, ...) @@ -2770,3 +3137,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/Extensions/Tooltip/Generic.lua b/Extensions/Tooltip/Generic.lua index 426d607..0029301 100644 --- a/Extensions/Tooltip/Generic.lua +++ b/Extensions/Tooltip/Generic.lua @@ -192,6 +192,36 @@ function CleveRoids.ClearSlot(slots, slot) slots[slot] = nil end +-- Local helper for 1.12.1: safely get action button info via tooltip +function CleveRoids.GetActionButtonInfo(slot) + if not CleveRoidsActionTooltip then + CreateFrame("GameTooltip", "CleveRoidsActionTooltip", UIParent, "GameTooltipTemplate") + end + + local tooltip = CleveRoidsActionTooltip + tooltip:SetOwner(UIParent, "ANCHOR_NONE") + tooltip:ClearLines() + tooltip:SetAction(slot) + + local name, rank + local text = _G["CleveRoidsActionTooltipTextLeft1"] + if text then name = text:GetText() end + local text2 = _G["CleveRoidsActionTooltipTextLeft2"] + if text2 then + local maybeRank = text2:GetText() + -- Rank lines usually start with "Rank" + if maybeRank and string.find(maybeRank, "Rank") then + rank = maybeRank + end + end + + -- Determine if it's an item or spell based on texture (heuristic) + local tex = GetActionTexture(slot) + local actionType = tex and "SPELL" or "ITEM" + + return actionType, tex, name, rank +end + function CleveRoids.IndexActionSlot(slot) if not HasAction(slot) then CleveRoids.Actions[slot] = nil @@ -202,7 +232,6 @@ function CleveRoids.IndexActionSlot(slot) if name then local reactiveName = CleveRoids.reactiveSpells[name] and name local actionSlotName = name..(rank and ("("..rank..")") or "") - if reactiveName then if not CleveRoids.reactiveSlots[reactiveName] then CleveRoids.reactiveSlots[reactiveName] = slot @@ -211,7 +240,6 @@ function CleveRoids.IndexActionSlot(slot) elseif not reactiveName then CleveRoids.ClearSlot(CleveRoids.reactiveSlots, slot) end - if actionType == "SPELL" or actionType == "ITEM" then if not CleveRoids.actionSlots[actionSlotName] then CleveRoids.actionSlots[actionSlotName] = slot @@ -220,7 +248,6 @@ function CleveRoids.IndexActionSlot(slot) end end end - CleveRoids.TestForActiveAction(CleveRoids.GetAction(slot)) CleveRoids.SendEventForAction(slot, "ACTIONBAR_SLOT_CHANGED", slot) 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 9f99ec7..4ec847a 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,6 +323,10 @@ 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 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.| +| mhimbue/ohimbue | [mhimbue:Flametongue]
[ohimbue] | | * | If the player has weapon imbue on their mh/oh.| ### Unit Based ### The default @unitid is usually @target if you don't specify one @@ -384,6 +344,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. | @@ -398,6 +359,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: