mirror of
https://github.com/brues-code/SuperCleveRoidMacros.git
synced 2026-09-16 03:38:00 +00:00
re add trinket and ring slashcommands and implement cursive support
This commit is contained in:
@@ -1336,6 +1336,99 @@ function CleveRoids.ValidateTTE(operator, amount)
|
||||
return false
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- CURSIVE ADDON INTEGRATION
|
||||
-- ============================================================================
|
||||
-- Integrates with Cursive addon for accurate debuff time tracking
|
||||
-- Cursive tracks debuffs by GUID with precise timing (accounts for Dark Harvest, etc.)
|
||||
|
||||
--- Check if Cursive addon is available and enabled
|
||||
--- @return boolean True if Cursive is available
|
||||
function CleveRoids.HasCursive()
|
||||
return type(Cursive) == "table" and
|
||||
type(Cursive.curses) == "table" and
|
||||
type(Cursive.curses.HasCurse) == "function"
|
||||
end
|
||||
|
||||
--- Require Cursive for a feature, warn once if missing
|
||||
--- @param feature string Feature name for warning message
|
||||
--- @return boolean True if Cursive is available
|
||||
function CleveRoids.RequireCursive(feature)
|
||||
if CleveRoids.HasCursive() then
|
||||
return true
|
||||
end
|
||||
if not CleveRoids._cursiveErrorShown then
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cffff0000[SuperCleveRoidMacros]|r The [cursive] conditional requires the Cursive addon.", 1, 0.5, 0.5)
|
||||
CleveRoids._cursiveErrorShown = true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
--- Get time remaining on a Cursive-tracked debuff
|
||||
--- @param unit string Unit to check (will be converted to GUID)
|
||||
--- @param spellName string Spell name to check (will be lowercased and rank-stripped)
|
||||
--- @return number|nil Time remaining in seconds, or nil if not found
|
||||
function CleveRoids.GetCursiveTimeRemaining(unit, spellName)
|
||||
if not CleveRoids.HasCursive() then return nil end
|
||||
if not unit or not UnitExists(unit) then return nil end
|
||||
|
||||
local _, guid = UnitExists(unit)
|
||||
if not guid then return nil end
|
||||
|
||||
-- Normalize spell name (lowercase, no rank) to match Cursive's format
|
||||
local lowercaseName = Cursive.utils.GetLowercaseSpellNameNoRank(spellName)
|
||||
|
||||
local curseData = Cursive.curses:GetCurseData(lowercaseName, guid)
|
||||
if not curseData then return nil end
|
||||
|
||||
return Cursive.curses:TimeRemaining(curseData)
|
||||
end
|
||||
|
||||
--- Check if unit has a Cursive-tracked debuff with optional time comparison
|
||||
--- @param unit string Unit to check
|
||||
--- @param spellName string Spell name to check
|
||||
--- @param operator string|nil Comparison operator (>, <, =, >=, <=, ~=)
|
||||
--- @param amount number|nil Time threshold in seconds
|
||||
--- @return boolean True if debuff exists and passes time check
|
||||
function CleveRoids.ValidateCursiveDebuff(unit, spellName, operator, amount)
|
||||
if not CleveRoids.HasCursive() then return false end
|
||||
if not unit or not UnitExists(unit) then return false end
|
||||
|
||||
local _, guid = UnitExists(unit)
|
||||
if not guid then return false end
|
||||
|
||||
-- Normalize spell name for Cursive lookup
|
||||
local lowercaseName = Cursive.utils.GetLowercaseSpellNameNoRank(spellName)
|
||||
|
||||
-- If no operator, just check if debuff exists with any time remaining
|
||||
if not operator then
|
||||
return Cursive.curses:HasCurse(lowercaseName, guid, 0) == true
|
||||
end
|
||||
|
||||
-- With operator, check time remaining
|
||||
local timeRemaining = CleveRoids.GetCursiveTimeRemaining(unit, spellName)
|
||||
if not timeRemaining then return false end
|
||||
|
||||
if CleveRoids.operators[operator] and amount then
|
||||
return CleveRoids.comparators[operator](timeRemaining, amount)
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
--- Check if ANY Cursive-tracked debuff exists on unit
|
||||
--- @param unit string Unit to check
|
||||
--- @return boolean True if unit has any tracked debuffs
|
||||
function CleveRoids.HasAnyCursiveDebuff(unit)
|
||||
if not CleveRoids.HasCursive() then return false end
|
||||
if not unit or not UnitExists(unit) then return false end
|
||||
|
||||
local _, guid = UnitExists(unit)
|
||||
if not guid then return false end
|
||||
|
||||
return Cursive.curses:HasAnyCurse(guid) == true
|
||||
end
|
||||
|
||||
function CleveRoids.ValidateKnown(args)
|
||||
if not args then
|
||||
return false
|
||||
@@ -4599,6 +4692,118 @@ CleveRoids.Keywords = {
|
||||
end, conditionals, "notte")
|
||||
end,
|
||||
|
||||
-- =========================================================================
|
||||
-- CURSIVE ADDON INTEGRATION
|
||||
-- =========================================================================
|
||||
-- Checks debuffs using Cursive's GUID-based tracking for accurate time remaining
|
||||
-- Supports time comparisons: [cursive:Rake>3] [cursive:Rip<5]
|
||||
-- Works with multiscan for intelligent target selection
|
||||
|
||||
-- [cursive] - Check if target has ANY Cursive-tracked debuff
|
||||
-- [cursive:SpellName] - Check if target has specific debuff tracked by Cursive
|
||||
-- [cursive:SpellName>3] - Check if debuff has more than 3 seconds remaining
|
||||
-- [cursive:SpellName<5] - Check if debuff has less than 5 seconds remaining
|
||||
-- Examples: [cursive:Rake] [@focus,cursive:Rip>5] [cursive:Corruption<3]
|
||||
cursive = function(conditionals)
|
||||
-- Check if Cursive addon is available
|
||||
if not CleveRoids.RequireCursive("cursive") then
|
||||
return false
|
||||
end
|
||||
|
||||
local target = conditionals.target or "target"
|
||||
|
||||
-- Boolean form [cursive] - check if target has ANY tracked debuff
|
||||
if not conditionals.cursive or
|
||||
conditionals.cursive == true or
|
||||
(type(conditionals.cursive) == "table" and table.getn(conditionals.cursive) == 0) then
|
||||
return CleveRoids.HasAnyCursiveDebuff(target)
|
||||
end
|
||||
|
||||
-- Spell name form with optional time comparison
|
||||
return Multi(conditionals.cursive, function(args)
|
||||
if type(args) == "string" then
|
||||
-- Simple spell name check: [cursive:Rake]
|
||||
return CleveRoids.ValidateCursiveDebuff(target, args, nil, nil)
|
||||
elseif type(args) == "table" then
|
||||
-- Time comparison: [cursive:Rake>3] or multi-comparison [cursive:Rake>3&<10]
|
||||
local spellName = args.name
|
||||
|
||||
-- Handle multi-comparison (e.g., >3&<10)
|
||||
if args.comparisons and type(args.comparisons) == "table" then
|
||||
local timeRemaining = CleveRoids.GetCursiveTimeRemaining(target, spellName)
|
||||
if not timeRemaining then return false end
|
||||
|
||||
-- ALL comparisons must pass (AND logic)
|
||||
for _, comp in ipairs(args.comparisons) do
|
||||
if not CleveRoids.operators[comp.operator] then
|
||||
return false
|
||||
end
|
||||
if not CleveRoids.comparators[comp.operator](timeRemaining, comp.amount) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- Single comparison: [cursive:Rake>3]
|
||||
return CleveRoids.ValidateCursiveDebuff(target, spellName, args.operator, args.amount)
|
||||
end
|
||||
return false
|
||||
end, conditionals, "cursive")
|
||||
end,
|
||||
|
||||
-- [nocursive] - Check if target does NOT have any Cursive-tracked debuff
|
||||
-- [nocursive:SpellName] - Check if target does NOT have specific debuff
|
||||
-- [nocursive:SpellName>3] - Check if debuff does NOT exist with >3 seconds remaining
|
||||
-- (true if missing OR has <=3 seconds)
|
||||
nocursive = function(conditionals)
|
||||
-- Check if Cursive addon is available (return true if missing = treat as "no debuff")
|
||||
if not CleveRoids.HasCursive() then
|
||||
return true
|
||||
end
|
||||
|
||||
local target = conditionals.target or "target"
|
||||
|
||||
-- Boolean form [nocursive] - check if target has NO tracked debuffs
|
||||
if not conditionals.nocursive or
|
||||
conditionals.nocursive == true or
|
||||
(type(conditionals.nocursive) == "table" and table.getn(conditionals.nocursive) == 0) then
|
||||
return not CleveRoids.HasAnyCursiveDebuff(target)
|
||||
end
|
||||
|
||||
-- Negated spell name form
|
||||
return NegatedMulti(conditionals.nocursive, function(args)
|
||||
if type(args) == "string" then
|
||||
-- Simple spell name check: [nocursive:Rake] = true if Rake is missing
|
||||
return not CleveRoids.ValidateCursiveDebuff(target, args, nil, nil)
|
||||
elseif type(args) == "table" then
|
||||
local spellName = args.name
|
||||
|
||||
-- Handle multi-comparison negation
|
||||
if args.comparisons and type(args.comparisons) == "table" then
|
||||
local timeRemaining = CleveRoids.GetCursiveTimeRemaining(target, spellName)
|
||||
-- If debuff missing, negation passes
|
||||
if not timeRemaining then return true end
|
||||
|
||||
-- Negated: true if ANY comparison fails
|
||||
for _, comp in ipairs(args.comparisons) do
|
||||
if not CleveRoids.operators[comp.operator] then
|
||||
return true
|
||||
end
|
||||
if not CleveRoids.comparators[comp.operator](timeRemaining, comp.amount) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- Single comparison negation
|
||||
return not CleveRoids.ValidateCursiveDebuff(target, spellName, args.operator, args.amount)
|
||||
end
|
||||
return true
|
||||
end, conditionals, "nocursive")
|
||||
end,
|
||||
|
||||
-- Slam clip window conditionals for Warrior Slam rotation optimization
|
||||
-- Based on math: MaxSlamPercent = (SwingTimer - SlamCastTime) / SwingTimer * 100
|
||||
-- Requires SP_SwingTimer addon and Nampower for cast time lookup
|
||||
|
||||
+12
@@ -51,6 +51,18 @@ SlashCmdList.EQUIPMH = CleveRoids.DoEquipMainhand
|
||||
SLASH_EQUIPOH1 = "/equipoh"
|
||||
SlashCmdList.EQUIPOH = CleveRoids.DoEquipOffhand
|
||||
|
||||
SLASH_EQSLOT111 = "/equip11"
|
||||
SlashCmdList.EQSLOT11 = CleveRoids.DoEquipRing1
|
||||
|
||||
SLASH_EQSLOT121 = "/equip12"
|
||||
SlashCmdList.EQSLOT12 = CleveRoids.DoEquipRing2
|
||||
|
||||
SLASH_EQSLOT131 = "/equip13"
|
||||
SlashCmdList.EQSLOT13 = CleveRoids.DoEquipTrinket1
|
||||
|
||||
SLASH_EQSLOT141 = "/equip14"
|
||||
SlashCmdList.EQSLOT14 = CleveRoids.DoEquipTrinket2
|
||||
|
||||
SLASH_UNSHIFT1 = "/unshift"
|
||||
|
||||
SlashCmdList.UNSHIFT = CleveRoids.DoUnshift
|
||||
|
||||
@@ -2794,9 +2794,9 @@ function CleveRoids.DoUse(msg)
|
||||
return false
|
||||
end
|
||||
|
||||
function CleveRoids.EquipBagItem(msg, offhand)
|
||||
function CleveRoids.EquipBagItem(msg, slotOrOffhand)
|
||||
if CleveRoids.equipDebugLog then
|
||||
CleveRoids.Print("|cff00ffff[EquipLog] EquipBagItem called: '" .. tostring(msg) .. "' offhand=" .. tostring(offhand) .. "|r")
|
||||
CleveRoids.Print("|cff00ffff[EquipLog] EquipBagItem called: '" .. tostring(msg) .. "' slot=" .. tostring(slotOrOffhand) .. "|r")
|
||||
end
|
||||
|
||||
if CleveRoids.equipInProgress then
|
||||
@@ -2806,7 +2806,13 @@ function CleveRoids.EquipBagItem(msg, offhand)
|
||||
return false
|
||||
end
|
||||
|
||||
local invslot = offhand and 17 or 16
|
||||
-- Accept slot number directly, or boolean for backward compatibility (false=16/MH, true=17/OH)
|
||||
local invslot
|
||||
if type(slotOrOffhand) == "number" then
|
||||
invslot = slotOrOffhand
|
||||
else
|
||||
invslot = slotOrOffhand and 17 or 16
|
||||
end
|
||||
local API = CleveRoids.NampowerAPI
|
||||
|
||||
-- v2.18+: Use native fast lookup for item ID or name
|
||||
@@ -2919,7 +2925,38 @@ function CleveRoids.EquipBagItem(msg, offhand)
|
||||
end
|
||||
end
|
||||
|
||||
-- PERFORMANCE: Try EquipItemByName FIRST before any lookups
|
||||
-- Check if item is already equipped in the paired slot (swap case)
|
||||
-- EquipItemByName doesn't handle swapping equipped items, so we must do it manually
|
||||
-- Paired slots: trinkets (13<->14), weapons (16<->17), rings (11<->12)
|
||||
local pairedSlots = {[13] = 14, [14] = 13, [16] = 17, [17] = 16, [11] = 12, [12] = 11}
|
||||
local checkSlot = pairedSlots[invslot]
|
||||
if checkSlot then
|
||||
local link = GetInventoryItemLink("player", checkSlot)
|
||||
if link then
|
||||
local _, _, slotItemName = string_find(link, "|h%[(.-)%]|h")
|
||||
if slotItemName and string_lower(slotItemName) == string_lower(msg) then
|
||||
-- Found item in paired slot - swap it manually
|
||||
if CleveRoids.equipDebugLog then
|
||||
CleveRoids.Print("|cff00ffff[EquipLog] Swapping from slot " .. checkSlot .. " to slot " .. invslot .. "|r")
|
||||
end
|
||||
ClearCursor()
|
||||
PickupInventoryItem(checkSlot)
|
||||
if CursorHasItem and CursorHasItem() then
|
||||
EquipCursorItem(invslot)
|
||||
ClearCursor()
|
||||
if CleveRoids.Items then
|
||||
CleveRoids.Items[msg] = nil
|
||||
CleveRoids.Items[string_lower(msg)] = nil
|
||||
end
|
||||
InvalidateDisplacedItem()
|
||||
return true
|
||||
end
|
||||
ClearCursor()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- PERFORMANCE: Try EquipItemByName for bag items (fast path)
|
||||
-- This is the fastest path - no item lookup, no cursor operations
|
||||
if EquipItemByName then
|
||||
local ok = pcall(EquipItemByName, msg, invslot)
|
||||
@@ -3019,6 +3056,22 @@ local function _equipOffhandAction(msg)
|
||||
return CleveRoids.EquipBagItem(msg, true)
|
||||
end
|
||||
|
||||
local function _equipTrinket1Action(msg)
|
||||
return CleveRoids.EquipBagItem(msg, 13)
|
||||
end
|
||||
|
||||
local function _equipTrinket2Action(msg)
|
||||
return CleveRoids.EquipBagItem(msg, 14)
|
||||
end
|
||||
|
||||
local function _equipRing1Action(msg)
|
||||
return CleveRoids.EquipBagItem(msg, 11)
|
||||
end
|
||||
|
||||
local function _equipRing2Action(msg)
|
||||
return CleveRoids.EquipBagItem(msg, 12)
|
||||
end
|
||||
|
||||
local function _unshiftAction()
|
||||
local currentShapeshiftIndex = CleveRoids.GetCurrentShapeshiftIndex()
|
||||
if currentShapeshiftIndex ~= 0 then
|
||||
@@ -3050,6 +3103,50 @@ function CleveRoids.DoEquipOffhand(msg)
|
||||
return false
|
||||
end
|
||||
|
||||
function CleveRoids.DoEquipTrinket1(msg)
|
||||
local parts = CleveRoids.splitStringIgnoringQuotes(msg)
|
||||
for i = 1, table.getn(parts) do
|
||||
local v = string.gsub(parts[i], "^%?", "")
|
||||
if CleveRoids.DoWithConditionals(v, _equipTrinket1Action, CleveRoids.FixEmptyTarget, false, _equipTrinket1Action) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function CleveRoids.DoEquipTrinket2(msg)
|
||||
local parts = CleveRoids.splitStringIgnoringQuotes(msg)
|
||||
for i = 1, table.getn(parts) do
|
||||
local v = string.gsub(parts[i], "^%?", "")
|
||||
if CleveRoids.DoWithConditionals(v, _equipTrinket2Action, CleveRoids.FixEmptyTarget, false, _equipTrinket2Action) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function CleveRoids.DoEquipRing1(msg)
|
||||
local parts = CleveRoids.splitStringIgnoringQuotes(msg)
|
||||
for i = 1, table.getn(parts) do
|
||||
local v = string.gsub(parts[i], "^%?", "")
|
||||
if CleveRoids.DoWithConditionals(v, _equipRing1Action, CleveRoids.FixEmptyTarget, false, _equipRing1Action) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function CleveRoids.DoEquipRing2(msg)
|
||||
local parts = CleveRoids.splitStringIgnoringQuotes(msg)
|
||||
for i = 1, table.getn(parts) do
|
||||
local v = string.gsub(parts[i], "^%?", "")
|
||||
if CleveRoids.DoWithConditionals(v, _equipRing2Action, CleveRoids.FixEmptyTarget, false, _equipRing2Action) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function CleveRoids.DoUnshift(msg)
|
||||
local handled
|
||||
-- PERFORMANCE: Use numeric iteration to avoid pairs() iterator allocation
|
||||
|
||||
@@ -112,6 +112,10 @@ CleveRoids.dynamicCmds = {
|
||||
["/equip"] = true,
|
||||
["/equipmh"] = true,
|
||||
["/equipoh"] = true,
|
||||
["/equip11"] = true,
|
||||
["/equip12"] = true,
|
||||
["/equip13"] = true,
|
||||
["/equip14"] = true,
|
||||
}
|
||||
|
||||
-- Equipment swap queue system
|
||||
|
||||
@@ -509,6 +509,10 @@ Default `[noimmune]` checks the debuff school (bleed).
|
||||
| /equip | * | Equip item by name/ID |
|
||||
| /equipmh | * | Equip main hand |
|
||||
| /equipoh | * | Equip off-hand |
|
||||
| /equip11 | * | Equip ring slot 1 |
|
||||
| /equip12 | * | Equip ring slot 2 |
|
||||
| /equip13 | * | Equip trinket slot 1 |
|
||||
| /equip14 | * | Equip trinket slot 2 |
|
||||
| /unshift | * | Cancel shapeshift form |
|
||||
| /cancelaura | | Cancel buff/aura |
|
||||
| /unbuff | | Alias for cancelaura |
|
||||
@@ -830,6 +834,7 @@ ClassicFocus/FocusFrame, SuperMacro, ShaguTweaks
|
||||
- [TWThreat](https://github.com/MarcelineVQ/TWThreat) - `[threat]` conditional
|
||||
- [TimeToKill](https://github.com/jrc13245/TimeToKill) - `[ttk]` and `[tte]` conditionals
|
||||
- [QuickHeal](https://github.com/jrc13245/QuickHeal) - `/quickheal` command
|
||||
- [Cursive](https://github.com/pepopo978/Cursive) - `[cursive]` conditional for GUID-based debuff tracking
|
||||
|
||||
---
|
||||
|
||||
@@ -1042,6 +1047,56 @@ Uses RLS algorithm to predict when target will die or reach execute phase (20% H
|
||||
|
||||
---
|
||||
|
||||
# Cursive Integration
|
||||
|
||||
Requires [Cursive](https://github.com/pepopo978/Cursive).
|
||||
|
||||
**How It Works:**
|
||||
Uses Cursive's GUID-based debuff tracking for accurate time remaining on your DoTs. Unlike the standard `[debuff]` conditional, Cursive tracks by GUID so timers survive target switching, accounts for pending casts, and handles Dark Harvest reductions.
|
||||
|
||||
**Conditionals:**
|
||||
- `[cursive]` - Target has ANY Cursive-tracked debuff
|
||||
- `[cursive:Spell]` - Target has specific debuff
|
||||
- `[cursive:Spell>N]` - Debuff has more than N seconds remaining
|
||||
- `[cursive:Spell<N]` - Debuff has less than N seconds remaining
|
||||
- `[nocursive:Spell]` - Target does NOT have the debuff
|
||||
|
||||
**Syntax:**
|
||||
```lua
|
||||
[cursive:Rake] -- Target has Rake tracked by Cursive
|
||||
[cursive:Rake>3] -- Rake has more than 3 seconds remaining
|
||||
[cursive:Rake<5] -- Rake has less than 5 seconds remaining
|
||||
[nocursive:Rip] -- Target missing Rip
|
||||
[@focus,cursive:DoT] -- Focus target has DoT
|
||||
```
|
||||
|
||||
**Examples:**
|
||||
```lua
|
||||
-- Refresh Rake only when low on duration
|
||||
#showtooltip Rake
|
||||
/cast [cursive:Rake<3] Rake
|
||||
|
||||
-- Apply Rip only if missing (Cursive knows about pending casts)
|
||||
#showtooltip Rip
|
||||
/cast [nocursive:Rip] Rip
|
||||
|
||||
-- DoT spreading with multiscan
|
||||
#showtooltip Rake
|
||||
/cast [multiscan:nearest,nocursive:Rake] Rake
|
||||
|
||||
-- Focus target DoT management
|
||||
#showtooltip Corruption
|
||||
/cast [@focus,cursive:Corruption<4] Corruption
|
||||
```
|
||||
|
||||
**Why use Cursive over debuff?**
|
||||
- **GUID-based**: Tracks by unit GUID, not unit token - survives target switching
|
||||
- **Pending cast aware**: Knows about spells in flight before they hit
|
||||
- **Accurate timing**: Accounts for Dark Harvest, latency compensation
|
||||
- **Works at debuff cap**: Pre-registers expected debuffs
|
||||
|
||||
---
|
||||
|
||||
# QuickHeal Integration
|
||||
|
||||
Requires [QuickHeal](https://github.com/jrc13245/QuickHeal).
|
||||
@@ -1115,6 +1170,9 @@ Use MarcelineVQ's updated [LunaUnitFrames](https://github.com/MarcelineVQ/LunaUn
|
||||
- [Roid-Macros](https://github.com/DennisWG/Roid-Macros) by DennisWG (DWG)
|
||||
- [CleverMacro](https://github.com/DanielAdolfsson/CleverMacro) by DanielAdolfsson (_brain)
|
||||
|
||||
## Contributors
|
||||
- **Avitasia** - Cursive integration
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
Reference in New Issue
Block a user