Files
SuperCleveRoidMacros/Init.lua
T
Brues 77c0dba2c4 Alias [mouseuse] to [cursor] via a conditional alias table
Adds CleveRoids.conditionalAliases, applied in ParseMsg so a deprecated name
is rewritten to its current keyword before anything downstream (evaluation,
_groups) sees it. This is the mechanism for renamed modifiers, which -- unlike
predicates such as [stl] -> [stealth] -- have no Keywords function an alias
could simply point at.

[mouseuse] now resolves to [cursor], routing old macros through ClassicAPI's
C_Spell.CastAtCursor / C_Item.UseAtCursor. Drops the post-cast block that cast
normally and then faked a mouse click via CameraOrSelectOrMoveStart/Stop to
place the AoE circle, along with its workaround for that call spuriously
starting auto-attack.

MacroErrorChecker seeds VALID_CONDITIONALS from the alias table, so the old
names stay valid syntax without needing a placeholder ignoreKeywords entry.
2026-09-09 02:18:36 -05:00

385 lines
16 KiB
Lua

local _G = _G or getfenv(0)
local CleveRoids = _G.CleveRoids or {}
_G.CleveRoids = CleveRoids
-- Global flag for other addons to detect SRCM regardless of folder name
-- (pfUI macrotweak checks IsAddOnLoaded("SuperCleveRoidMacros") which fails
-- if the folder was renamed, e.g. "SuperCleveRoidMacros-main" from GitHub)
_G.SRCM_LOADED = true
CleveRoids.ready = false
CleveRoids.Hooks = CleveRoids.Hooks or {}
CleveRoids.Hooks.GameTooltip = {}
CleveRoids.Extensions = CleveRoids.Extensions or {}
CleveRoids.actionEventHandlers = {}
CleveRoids.mouseOverResolvers = {}
CleveRoids.mouseoverUnit = CleveRoids.mouseoverUnit or nil
CleveRoids.mouseOverUnit = nil
-- Environment flags
CleveRoids.hasSuperwow = SetAutoloot and true or false
CleveRoids.hasTurtle = (type(_G.TURTLE_WOW_VERSION) ~= "nil")
CleveRoids.supported = CleveRoids.hasTurtle
CleveRoids.ParsedMsg = {}
CleveRoids.Items = {}
CleveRoids.Spells = {}
CleveRoids.PetSpells = {}
CleveRoids.Talents = {}
CleveRoids.Cooldowns = {}
CleveRoids.Macros = {}
CleveRoids.Actions = {}
CleveRoids.Sequences = {}
CleveRoids.lastUpdate = 0
CleveRoids.lastGetItem = nil
CleveRoids.currentSequence = nil
CleveRoids.bookTypes = {BOOKTYPE_SPELL, BOOKTYPE_PET}
CleveRoids.unknownTexture = "Interface\\Icons\\INV_Misc_QuestionMark"
CleveRoids.spell_tracking = {}
-- GUID-based cast tracking (populated by pfUI 7.6 or standalone SPELL_START events)
-- Format: [casterGuid] = {spellID, spellName, icon, startTime, duration, endTime}
CleveRoids.castTracking = {}
-- pfUI 7.6+ with Nampower 2.31.0+ detected (GUID-based cast tracking available)
CleveRoids.hasPfUI76 = false
-- Combo point tracking (initialized early for /cast hook)
CleveRoids.lastComboPoints = 0
CleveRoids.lastComboPointsTime = 0
-- Resist tracking state
-- Structure: { resistType = "full"|"partial", targetGUID = guid }
CleveRoids.resistState = nil
-- KEY_DOWN/KEY_UP state table (populated when Nampower v2.41+ hasKeyEvents)
CleveRoids._keyState = {}
-- Holds information about the currently cast spell
CleveRoids.CurrentSpell = {
-- "channeled" or "cast"
type = "",
-- the name of the spell
spellName = "",
-- is the Attack ability enabled
autoAttack = false,
-- is the Auto Shot ability enabled
autoShot = false,
-- is the Shoot ability (wands) enabled
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
-- NOTE: Channel state is EXCLUSIVELY managed by SPELLCAST_CHANNEL_START/STOP events
-- This function NEVER touches channel state, only regular casts
if casting == 1 then
CleveRoids.CurrentSpell.type = "cast"
CleveRoids.CurrentSpell.castingSpellId = castId
elseif CleveRoids.CurrentSpell.type == "cast" then
-- Only clear if we were in a regular cast (not channel)
CleveRoids.CurrentSpell.type = ""
CleveRoids.CurrentSpell.castingSpellId = nil
end
-- DO NOT touch channel state here - events handle it
-- Always update metadata from GetCurrentCastingInfo (onswing/autoattack only here)
CleveRoids.CurrentSpell.autoAttack = (autoattack == 1)
CleveRoids.CurrentSpell.onSwingPending = (onswing == 1)
CleveRoids.CurrentSpell.visualSpellId = visId
CleveRoids.CurrentSpell.autoRepeatSpellId = autoId
-- Enhanced timing data from GetCastInfo (Nampower 2.18+)
if GetCastInfo then
local ok, info = pcall(GetCastInfo)
if ok and info then
CleveRoids.CurrentSpell.castRemainingMs = info.castRemainingMs
CleveRoids.CurrentSpell.castEndTime = info.castEndS
CleveRoids.CurrentSpell.gcdRemainingMs = info.gcdRemainingMs
CleveRoids.CurrentSpell.gcdEndTime = info.gcdEndS
else
CleveRoids.CurrentSpell.castRemainingMs = nil
CleveRoids.CurrentSpell.castEndTime = nil
CleveRoids.CurrentSpell.gcdRemainingMs = nil
CleveRoids.CurrentSpell.gcdEndTime = nil
end
end
return true
end
CleveRoids.dynamicCmds = {
["/cast"] = true,
["/castpet"] = true,
["/castsequence"] = true,
["/use"] = true,
["/feedpet"] = true,
["/equip"] = true,
["/equipmh"] = true,
["/equipoh"] = true,
["/equip11"] = true,
["/equip12"] = true,
["/equip13"] = true,
["/equip14"] = true,
["/applymain"] = true,
["/applyoff"] = true,
}
-- Equipment swap queue system
CleveRoids.equipmentQueue = {}
CleveRoids.equipmentQueueLen = 0 -- PERFORMANCE: Track length to avoid table.getn() every frame
CleveRoids.lastEquipTime = {}
CleveRoids.lastGlobalEquipTime = 0
CleveRoids.EQUIP_COOLDOWN = 1.5 -- Per-slot cooldown
CleveRoids.EQUIP_GLOBAL_COOLDOWN = 0.5 -- Global cooldown
-- PERFORMANCE: Table pool for queue entries to reduce garbage collection
CleveRoids.queueEntryPool = {}
-- PERFORMANCE: Static buffer for proc removal to avoid per-frame allocation
CleveRoids._procRemovalBuffer = {}
-- PERFORMANCE: Static buffer for action grouping to avoid per-call allocation
CleveRoids._actionsToSlotsBuffer = {}
CleveRoids._slotsBuffer = {}
CleveRoids._actionsListBuffer = {}
-- PERFORMANCE: Static buffer for arg backup in SendEventForAction
CleveRoids._originalArgsBuffer = {}
-- KEYED DEBUG: Only prints when the message for a given key changes from last print.
-- Usage: CleveRoids.DebugChanged("immunity_moonfire", formatted_msg)
-- Prevents spam when the same state is reported repeatedly (e.g., per-frame or per-eval).
CleveRoids._lastDebugState = {}
function CleveRoids.DebugChanged(key, msg)
if not CleveRoids.debug then return end
if CleveRoids._lastDebugState[key] == msg then return end
CleveRoids._lastDebugState[key] = msg
DEFAULT_CHAT_FRAME:AddMessage(msg)
end
-- Spell queue state (Nampower)
CleveRoids.queuedSpell = nil
CleveRoids.lastCastSpell = nil
-- Macro execution control
CleveRoids.stopMacroFlag = false
-- PERFORMANCE: Event-driven cached state (updated on events, not polled)
CleveRoids._cachedPlayerInCombat = nil -- Updated on PLAYER_REGEN_DISABLED, PLAYER_REGEN_ENABLED
CleveRoids.ignoreKeywords = {
action = true,
ignoretooltip = true,
cancelaura = true,
noSpam = true, -- ! prefix flag: prevent toggle-off at execution time
_operators = true, -- Metadata for AND/OR operator tracking
_groups = true, -- Grouped conditional values for AND/OR evaluation
multiscan = true, -- Processed before Keywords loop (target resolution)
cursor = true, -- Modifier: place ground-target spell/item at cursor (CastAtCursor)
stopattack = true, -- Post-cast modifier: stop autoattack after cast (CheapShot pattern)
}
-- Deprecated conditional names, rewritten to their current keyword in ParseMsg
-- before anything downstream (evaluation, _groups) sees them. This is the path
-- for renamed *modifiers* -- ignoreKeywords entries, which have no Keywords
-- predicate that an alias could just point at the way [stl] does for [stealth].
-- MacroErrorChecker also reads this table, so the old names stay valid syntax.
CleveRoids.conditionalAliases = {
mouseuse = "cursor", -- pre-ClassicAPI name, back when it clicked the AoE reticle
}
-- TODO: Localize?
CleveRoids.countedItemTypes = {
["Consumable"] = true,
["Reagent"] = true,
["Projectile"] = true,
["Trade Goods"] = true,
}
-- TODO: Localize?
CleveRoids.actionSlots = {}
CleveRoids.reactiveSlots = {}
CleveRoids.reactiveSpells = {
["Revenge"] = true,
["Overpower"] = true,
["Riposte"] = true,
["Surprise Attack"] = true,
["Lacerate"] = true,
["Baited Shot"] = true,
["Counterattack"] = true,
["Arcane Surge"] = true,
["Aquatic Form"] = true,
}
-- PERFORMANCE: Static lookup for toggled buff abilities (built once, used per-frame)
CleveRoids._toggledBuffAbilities = {
[CleveRoids.Localized.Spells["Prowl"]] = true,
[CleveRoids.Localized.Spells["Shadowmeld"]] = true,
}
function CleveRoids.IsToggledBuffAbility(spellName)
return CleveRoids._toggledBuffAbilities[spellName]
end
CleveRoids.auraTextures = {
[CleveRoids.Localized.Spells["Stealth"]] = "Interface\\Icons\\Ability_Stealth",
[CleveRoids.Localized.Spells["Prowl"]] = "Interface\\Icons\\Spell_Nature_Invisibilty",
[CleveRoids.Localized.Spells["Shadowform"]] = "Interface\\Icons\\Spell_Shadow_Shadowform",
[CleveRoids.Localized.Spells["Shadowmeld"]] = "Interface\\Icons\\Spell_Nature_WispSplode",
["Seal of Wisdom"] = "Interface\\Icons\\Spell_Holy_RighteousnessAura",
["Seal of the Crusader"] = "Interface\\Icons\\Spell_Holy_HolySmite",
["Seal of Light"] = "Interface\\Icons\\Spell_Holy_HealingAura",
["Seal of the Justice"] = "Interface\\Icons\\Spell_Holy_SealOfWrath",
["Seal of Righteousness"] = "Interface\\Icons\\Ability_ThunderBolt",
["Seal of Command"] = "Interface\\Icons\\Ability_Warrior_InnerRage",
}
-- I need to make a 2h modifier
-- Maps easy-to-use weapon type names (e.g. Axes, Shields) to their inventory
-- slot plus the locale-independent item class/subclass IDs that identify them
-- (read via C_Item.GetItemInfoInstant). class 2 = Weapon, 4 = Armor (shields).
-- subClass is a set because the logical "Axes"/"Swords"/"Maces" types span both
-- the one-handed and two-handed weapon subclasses.
CleveRoids.WeaponTypeNames = {
Daggers = { slot = "MainHandSlot", class = 2, subClass = { [15] = true } },
Fists = { slot = "MainHandSlot", class = 2, subClass = { [13] = true } },
Axes = { slot = "MainHandSlot", class = 2, subClass = { [0] = true, [1] = true } },
Swords = { slot = "MainHandSlot", class = 2, subClass = { [7] = true, [8] = true } },
Staves = { slot = "MainHandSlot", class = 2, subClass = { [10] = true } },
Maces = { slot = "MainHandSlot", class = 2, subClass = { [4] = true, [5] = true } },
Polearms = { slot = "MainHandSlot", class = 2, subClass = { [6] = true } },
-- OH
Daggers2 = { slot = "SecondaryHandSlot", class = 2, subClass = { [15] = true } },
Fists2 = { slot = "SecondaryHandSlot", class = 2, subClass = { [13] = true } },
Axes2 = { slot = "SecondaryHandSlot", class = 2, subClass = { [0] = true, [1] = true } },
Swords2 = { slot = "SecondaryHandSlot", class = 2, subClass = { [7] = true, [8] = true } },
Maces2 = { slot = "SecondaryHandSlot", class = 2, subClass = { [4] = true, [5] = true } },
Shields = { slot = "SecondaryHandSlot", class = 4, subClass = { [6] = true } },
-- ranged
Guns = { slot = "RangedSlot", class = 2, subClass = { [3] = true } },
Crossbows = { slot = "RangedSlot", class = 2, subClass = { [18] = true } },
Bows = { slot = "RangedSlot", class = 2, subClass = { [2] = true } },
Thrown = { slot = "RangedSlot", class = 2, subClass = { [16] = true } },
Wands = { slot = "RangedSlot", class = 2, subClass = { [19] = true } },
}
-- Detect available features
CleveRoids.hasNampower = (QueueSpellByName ~= nil)
CleveRoids.hasUnitXP = pcall(UnitXP, "nop", "nop")
-- Extended Nampower feature flags (populated by NampowerAPI.lua)
CleveRoids.nampowerVersion = { major = 0, minor = 0, patch = 0 }
CleveRoids.hasExtendedNampower = false -- True if v2.12+ with new API functions
-- Feature detection messages
local function PrintFeatures()
local features = {}
if CleveRoids.hasSuperwow then table.insert(features, "SuperWoW") end
if CleveRoids.hasNampower then
local ver = CleveRoids.nampowerVersion
if ver.major > 0 then
table.insert(features, string.format("Nampower v%d.%d.%d", ver.major, ver.minor, ver.patch))
else
table.insert(features, "Nampower")
end
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
-- Immunity data version - increment this when changing immunity data format
-- This will cause all immunity data to be reset on addon update
-- v3: Fixed Master Strike false physical immunity recording (split CC spell handling)
-- v4: Fixed false immunity recording when target dies with spells in-flight (dead = IMMUNE)
-- v5: Fixed false physical immunity from unknown spell schools (now uses DBC lookup; unknown defaults to nil not "physical")
-- v6: Reset stale immunity data that may contain false positives
CleveRoids.IMMUNITY_DATA_VERSION = 6
-- Call on next frame to ensure everything is loaded
local initFrame = CreateFrame("Frame")
initFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
initFrame:SetScript("OnEvent", function()
this:UnregisterAllEvents()
-- Check immunity data version and reset if outdated
CleveRoidMacros = CleveRoidMacros or {}
CleveRoids_ImmunityData = CleveRoids_ImmunityData or {}
local savedVersion = CleveRoidMacros.immunityDataVersion or 0
if savedVersion < CleveRoids.IMMUNITY_DATA_VERSION then
-- Check if there was existing data to clear
local hadData = next(CleveRoids_ImmunityData) ~= nil
-- Version changed - reset all immunity data
CleveRoids_ImmunityData = {}
CleveRoidMacros.immunityDataVersion = CleveRoids.IMMUNITY_DATA_VERSION
if hadData then
-- Show message if we actually cleared existing data
CleveRoids.Print("|cffff9900Immunity data reset|r - addon updated to data version " .. CleveRoids.IMMUNITY_DATA_VERSION)
end
end
-- Detect pfUI macrotweak conflict (folder name mismatch)
-- pfUI macrotweak disables itself via IsAddOnLoaded("SuperCleveRoidMacros"),
-- but this fails when the addon folder is renamed (e.g. GitHub download adds "-main").
-- When both are active: conflicting SendChatMessage hooks, duplicate /use and /equip
-- handlers, and #showtooltip can leak into chat (pfUI only filters "#showtooltip "
-- with trailing space, missing bare "#showtooltip").
if pfUI and pfUI.module and pfUI.module["macrotweak"]
and not IsAddOnLoaded("SuperCleveRoidMacros") then
CleveRoids.Print("|cffff0000WARNING:|r Your addon folder name is not |cff00ff00SuperCleveRoidMacros|r.")
CleveRoids.Print("This causes a conflict with pfUI's macrotweak module.")
CleveRoids.Print("Please rename the folder to exactly |cff00ff00SuperCleveRoidMacros|r and /reload.")
end
-- Initialize NampowerAPI if available
if CleveRoids.NampowerAPI then
local API = CleveRoids.NampowerAPI
-- Get version info
local major, minor, patch = API.GetVersion()
CleveRoids.nampowerVersion = { major = major, minor = minor, patch = patch }
-- Check for extended API (v2.12+)
CleveRoids.hasExtendedNampower = API.HasMinimumVersion(2, 12, 0)
-- Sync feature flags
if API.features then
CleveRoids.hasGetSpellRec = API.features.hasGetSpellRec
CleveRoids.hasGetItemStats = API.features.hasGetItemStats
CleveRoids.hasGetUnitData = API.features.hasGetUnitData
CleveRoids.hasGetSpellModifiers = API.features.hasGetSpellModifiers
CleveRoids.hasEnhancedSpellFunctions = API.features.hasEnhancedSpellFunctions
-- v2.37+: CastSpellByName supports unit token strings as 2nd param
CleveRoids.hasCastSpellByNameUnitToken = API.features.hasCastSpellByNameUnitToken
end
-- Initialize the API
API.Initialize()
end
PrintFeatures()
end)
_G["CleveRoids"] = CleveRoids