mirror of
https://github.com/brues-code/SuperCleveRoidMacros.git
synced 2026-09-16 03:38:00 +00:00
@@ -0,0 +1,2 @@
|
||||
wow-api-type-definitions/
|
||||
.gitignore
|
||||
+373
-15
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]<br/>[pet:Voidwalker]<br/>[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]<br/>[mybuffcount:<=X] | | | If the player has more or less auras present than X.|
|
||||
| queuedspell | [queuedspell]<br/>[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]<br/>[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]<br/>[hp:>=X/<=Y] | * | | The @unitid health **PERCENT** compared to X. |
|
||||
| hplost | [hplost:>=X]<br/>[hplost:>=X/<=Y] | * | | The @unitid health lost compared to X. |
|
||||
| inrange | [inrange]<br/>[inrange:"Name"] | * | * | If the specified @unitid is in range of the spell. |
|
||||
| outrange | [outrange]<br/>[outrange:"Name"] | * | | If the specified @unitid is out of range of the spell. |
|
||||
| isnpc | [isnpc] | | | If the @unitid is an npc.<br/>See this [article](https://wowpedia.fandom.com/wiki/UnitId) for a list of unitids.<br/>Not all units are valid in vanilla. |
|
||||
| isplayer | [isplayer] | | | If the @unitid is a player.<br/>See this [article](https://wowpedia.fandom.com/wiki/UnitId) for a list of unitids.<br/>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]<br/>[class:Warrior/Priest] | * | * | The target is a player of the specified class/classes. |
|
||||
| distance | [distance:>X]<br/>[distance:<X] | | * | If the player is closer or farther than X yards from the target.|
|
||||
| behind | [behind] | | * | If the player is behind the target.|
|
||||
| insight | [insight] | | * | If the player is in line of sight of the target. |
|
||||
| meleerange | [meleerange] | | * | If the player is melee range of the target.|
|
||||
|
||||
### Unitids
|
||||
| Name (N=party/raid slot number) |
|
||||
|
||||
+6
-6
@@ -515,12 +515,12 @@ lib.durations = lib.durations or {
|
||||
[1824] = 9, -- Rake (Rank 3)
|
||||
[9904] = 9, -- Rake (Rank 4)
|
||||
|
||||
[1079] = 8, -- Rip (Rank 1) - base duration (scales with combo points)
|
||||
[9492] = 8, -- Rip (Rank 2)
|
||||
[9493] = 8, -- Rip (Rank 3)
|
||||
[9752] = 8, -- Rip (Rank 4)
|
||||
[9894] = 8, -- Rip (Rank 5)
|
||||
[9896] = 8, -- Rip (Rank 6)
|
||||
[1079] = 10, -- Rip (Rank 1) - base duration (scales with combo points)
|
||||
[9492] = 10, -- Rip (Rank 2)
|
||||
[9493] = 10, -- Rip (Rank 3)
|
||||
[9752] = 10, -- Rip (Rank 4)
|
||||
[9894] = 10, -- Rip (Rank 5)
|
||||
[9896] = 10, -- Rip (Rank 6)
|
||||
|
||||
[2908] = 15, -- Soothe Animal (Rank 1)
|
||||
[8955] = 15, -- Soothe Animal (Rank 2)
|
||||
|
||||
Reference in New Issue
Block a user