mirror of
https://github.com/brues-code/SuperCleveRoidMacros.git
synced 2026-09-16 03:38:00 +00:00
6edd37a76f
An out-of-date ClassicAPI did not degrade the addon, it decapitated it. Utility.lua calls ev:RegisterUnitEvent at file scope (v1.15.0), so on an older build that call raises and Lua abandons the rest of the chunk -- every definition below line 4118 of a 10040-line file, some 6000 lines, silently never happens. What the user sees is the first one a common path reaches: PLAYER_TARGET_CHANGED calling a nil ClearResistState, once per target swap, forever. The requirement check still said v1.12.1 (the positional C_UnitAuras.UnitAura), so the affected client sailed through it and the warning it did print promised only that "dispel-type conditionals will be unavailable" -- describing a graceful degradation that was not happening. The gate is now v1.15.0 and says the addon cannot finish loading, with the installed version in the message the way the Nampower branch does it. ClassicAPI.lua gains GetVersion() to decode CLASSIC_API_VERSION, keeping the X*10000 + Y*100 + Z encoding inside that module, and its header notes that the minimum has to rise with any ClassicAPI call adopted at file scope -- that is the property that turns a version mismatch into a silent half-load rather than a missing feature. C_Macro.SetMacroDisplay also ships in v1.15.0, which retires the last pre-ClassicAPI display path: useClassicAPIDisplay folds into ClassicAPIMacroDisplay. The two were always equal -- set together at load, cleared together in ReleaseDisplays -- and one of them is a handshake ClassicAPI reads, so a second name for the same state could only ever drift. TestForAllActiveActions loses the per-slot ACTIONBAR_SLOT_CHANGED fan-out. Publishing repaints every slot holding the macro through the client's own notifier; the fan-out was what ran when SetMacroDisplay was unavailable, and its one remaining reachable state was after DisableAddon, where ReleaseDisplays has already handed all 36 macros back and ClassicAPI repaints them itself. The load-time feature detect stays, with a new reason: below the minimum the addon is already broken, so a nil SetMacroDisplay now means ClassicAPI is absent outright -- the case the requirement check warns about and then keeps running.
401 lines
17 KiB
Lua
401 lines
17 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
|
|
|
|
-- ClassicAPI macro display (C_Macro.SetMacroDisplay): hand ClassicAPI the resolved
|
|
-- action per macro instead of replacing the action-bar globals in Lua, so the icon,
|
|
-- tooltip, cooldown sweep, range and usable state all come from the client -- including
|
|
-- the drag cursor and the macro window grid, which Lua cannot reach.
|
|
--
|
|
-- ClassicAPI stands down from macro display entirely when it sees this addon loaded;
|
|
-- ClassicAPIMacroDisplay is what tells it we drive it instead, and ReleaseDisplays
|
|
-- clears it to hand every macro back. A fork that leaves the flag unset keeps the old
|
|
-- behavior -- both must never drive the same buttons. It doubles as the internal
|
|
-- "may we call C_Macro.SetMacroDisplay" guard, so the two can never disagree.
|
|
--
|
|
-- Feature-detect rather than version-check: SetMacroDisplay ships in ClassicAPI
|
|
-- v1.15.0, this addon's minimum, so a nil here means the client mod is missing
|
|
-- outright -- the case Core.lua's requirement check warns about but keeps running.
|
|
CleveRoids.ClassicAPIMacroDisplay =
|
|
(type(C_Macro) == "table" and C_Macro.SetMacroDisplay ~= nil) and true or false
|
|
|
|
CleveRoids.ParsedMsg = {}
|
|
CleveRoids.ExpandedGroups = {}
|
|
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 from our own SPELL_START handlers and pruned
|
|
-- in OnUpdate. Format: [casterGuid] = {spellID, spellName, icon, startTime, duration,
|
|
-- endTime}
|
|
CleveRoids.castTracking = {}
|
|
|
|
-- 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
|