mirror of
https://github.com/brues-code/SuperCleveRoidMacros.git
synced 2026-09-16 03:38:00 +00:00
5e27e6fe58
Three loops walked 1..36 by hand, two of them re-arguing in comments why 36 is the right number. It is: the client addresses 18 account-wide macros at 1-18 and 18 character-specific ones at 19-36, and that range is what GetMacroInfo and C_Macro.SetMacroDisplay index. Nothing in the API hands it back -- GetNumMacros() returns how many of each tab are used, which cannot be summed into a range, because the character block still starts at 19 when the account block is empty. Blizzard's MAX_MACROS is no better: it lives in the load-on-demand Blizzard_MacroUI and is nil until the macro window is opened. So CleveRoids.MAX_MACRO_SLOTS in Init.lua, carrying that reasoning once. ValidateAllMacros also dropped a numAccountMacros it computed and never read. ReleaseDisplays keeps sweeping the full range rather than the macros it knows it published: PublishAllDisplays clears that record on each re-parse, so a macro claimed before one and gone after it would keep our last published value forever. Releasing a slot we never claimed costs nothing, and it runs once.
410 lines
17 KiB
Lua
410 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
|
|
|
|
-- Every macro slot the client can hold: 18 account-wide (1-18) followed by 18
|
|
-- character-specific (19-36). This is the index space GetMacroInfo and
|
|
-- C_Macro.SetMacroDisplay address, and it is fixed -- GetNumMacros() returns how
|
|
-- many of each tab are *used*, which cannot be summed into a range, because the
|
|
-- character block starts at 19 no matter how few account macros exist. Blizzard's
|
|
-- own MAX_MACROS is no help either: it lives in the load-on-demand Blizzard_MacroUI
|
|
-- and is nil until the player opens the macro window.
|
|
CleveRoids.MAX_MACRO_SLOTS = 36
|
|
|
|
-- 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
|