re add trinket and ring slashcommands and implement cursive support

This commit is contained in:
Jrc13245
2026-01-02 23:37:27 -05:00
parent 003d230e52
commit 64c0d9949d
5 changed files with 380 additions and 4 deletions
+205
View File
@@ -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
View File
@@ -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
+101 -4
View File
@@ -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
+4
View File
@@ -112,6 +112,10 @@ CleveRoids.dynamicCmds = {
["/equip"] = true,
["/equipmh"] = true,
["/equipoh"] = true,
["/equip11"] = true,
["/equip12"] = true,
["/equip13"] = true,
["/equip14"] = true,
}
-- Equipment swap queue system
+58
View File
@@ -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