check nampower version to get exact functionality for that version as well as update cursive compatibility and custom spell add cursive functions

This commit is contained in:
Jrc13245
2026-01-03 16:11:13 -05:00
parent 64c0d9949d
commit f457ac7ec4
3 changed files with 957 additions and 104 deletions
+610
View File
@@ -0,0 +1,610 @@
--[[
CursiveCustomSpells Extension
Automatically injects custom spell definitions into Cursive's tracking system.
Any spell in this list will be tracked by Cursive when you cast it normally.
Format:
[spellID] = { name = "lowercase spell name", rank = X, duration = Y },
Optional fields:
variableDuration = true -- Duration can be modified by talents/haste
calculateDuration = func -- Function returning dynamic duration
numTicks = N -- Number of DoT ticks (for tick tracking)
darkHarvest = true -- Affected by Dark Harvest talent
]]
local _G = _G or getfenv(0)
local CleveRoids = _G.CleveRoids
--============================================================================
-- CUSTOM SPELL DEFINITIONS
-- Add your custom debuffs here. They will be auto-tracked when cast.
--============================================================================
CleveRoids.CustomCursiveSpells = CleveRoids.CustomCursiveSpells or {
--========================================================================
-- WARRIOR DEBUFFS (from database.turtlecraft.gg)
--========================================================================
-- Deep Wounds (talent proc, bleed over 12 sec - ticks every 3s)
[12721] = { name = "deep wounds", rank = 1, duration = 6 },
[12162] = { name = "deep wounds", rank = 1, duration = 6 },
[12850] = { name = "deep wounds", rank = 2, duration = 6 },
[12868] = { name = "deep wounds", rank = 3, duration = 6 },
-- Hamstring (15 sec snare, 50% movement speed)
[1715] = { name = "hamstring", rank = 1, duration = 15 },
[7372] = { name = "hamstring", rank = 2, duration = 15 },
[7373] = { name = "hamstring", rank = 3, duration = 15 },
--========================================================================
-- TAUNTS (personal debuffs)
--========================================================================
-- Taunt (Warrior, 3 sec)
[355] = { name = "taunt", rank = 1, duration = 3 },
[29060] = { name = "taunt", rank = 1, duration = 3 }, -- variant
-- Growl (Druid Bear, 3 sec)
[6795] = { name = "growl", rank = 1, duration = 3 },
-- Mocking Blow (Warrior, 6 sec)
[694] = { name = "mocking blow", rank = 1, duration = 6 },
[7400] = { name = "mocking blow", rank = 2, duration = 6 },
[7402] = { name = "mocking blow", rank = 3, duration = 6 },
[20559] = { name = "mocking blow", rank = 4, duration = 6 },
[20560] = { name = "mocking blow", rank = 5, duration = 6 },
-- Hand of Reckoning (Paladin, 3 sec)
[62124] = { name = "hand of reckoning", rank = 1, duration = 3 },
--========================================================================
-- PALADIN JUDGEMENT DEBUFFS
-- Generic 20271 is handled specially below to detect seal type
--========================================================================
-- Judgement of the Crusader (10 sec, +holy damage taken)
[21183] = { name = "judgement of the crusader", rank = 1, duration = 10 },
[20188] = { name = "judgement of the crusader", rank = 2, duration = 10 },
[20300] = { name = "judgement of the crusader", rank = 3, duration = 10 },
[20301] = { name = "judgement of the crusader", rank = 4, duration = 10 },
[20302] = { name = "judgement of the crusader", rank = 5, duration = 10 },
[20303] = { name = "judgement of the crusader", rank = 6, duration = 10 },
[20304] = { name = "judgement of the crusader", rank = 6, duration = 10 }, -- variant
[25942] = { name = "judgement of the crusader", rank = 1, duration = 10 }, -- variant
-- Judgement of Light (10 sec, heals attackers)
[20185] = { name = "judgement of light", rank = 1, duration = 10 },
[20344] = { name = "judgement of light", rank = 2, duration = 10 },
[20345] = { name = "judgement of light", rank = 3, duration = 10 },
[20346] = { name = "judgement of light", rank = 4, duration = 10 },
-- Judgement of Wisdom (10 sec, restores mana to attackers)
[20186] = { name = "judgement of wisdom", rank = 1, duration = 10 },
[20354] = { name = "judgement of wisdom", rank = 2, duration = 10 },
[20355] = { name = "judgement of wisdom", rank = 3, duration = 10 },
[51750] = { name = "judgement of wisdom", rank = 5, duration = 10 }, -- Rank 5 cast (confirmed)
[51751] = { name = "judgement of wisdom", rank = 4, duration = 10 }, -- Rank 4 cast
[51752] = { name = "judgement of wisdom", rank = 5, duration = 10 }, -- debuff effect ID
[25757] = { name = "judgement of wisdom", rank = 1, duration = 10 }, -- variant
-- Judgement of Justice (10 sec, prevents fleeing)
[20184] = { name = "judgement of justice", rank = 1, duration = 10 },
[25945] = { name = "judgement of justice", rank = 2, duration = 10 },
--========================================================================
-- ROGUE POISONS
--========================================================================
-- Deadly Poison (12 sec, stacking nature DoT)
[2818] = { name = "deadly poison", rank = 1, duration = 12 },
[2819] = { name = "deadly poison", rank = 2, duration = 12 },
[11353] = { name = "deadly poison", rank = 3, duration = 12 },
[11354] = { name = "deadly poison", rank = 4, duration = 12 },
[25349] = { name = "deadly poison", rank = 5, duration = 12 },
[3583] = { name = "deadly poison", rank = 1, duration = 12 }, -- proc effect
[10022] = { name = "deadly poison", rank = 2, duration = 12 }, -- proc effect
[13582] = { name = "deadly poison", rank = 3, duration = 12 }, -- proc effect
[21787] = { name = "deadly poison", rank = 4, duration = 12 }, -- proc effect
[21788] = { name = "deadly poison", rank = 5, duration = 12 }, -- proc effect
-- Crippling Poison (12 sec, -50% movement speed)
[3409] = { name = "crippling poison", rank = 1, duration = 12 },
[11201] = { name = "crippling poison", rank = 2, duration = 12 },
[25809] = { name = "crippling poison", rank = 2, duration = 12 }, -- variant
-- Wound Poison (15 sec, -healing received)
[13218] = { name = "wound poison", rank = 1, duration = 15 },
[13222] = { name = "wound poison", rank = 2, duration = 15 },
[13223] = { name = "wound poison", rank = 3, duration = 15 },
[13224] = { name = "wound poison", rank = 4, duration = 15 },
[25648] = { name = "wound poison", rank = 5, duration = 15 },
-- Mind-numbing Poison (10 sec, -casting speed)
[5760] = { name = "mind-numbing poison", rank = 1, duration = 10 },
[8692] = { name = "mind-numbing poison", rank = 2, duration = 10 },
[11398] = { name = "mind-numbing poison", rank = 3, duration = 10 },
[25810] = { name = "mind-numbing poison", rank = 2, duration = 10 }, -- variant
--========================================================================
-- THUNDERFURY PROC DEBUFFS
--========================================================================
-- Thunderfury - Nature Resist Reduction (12 sec, chains to 5 targets)
[21992] = { name = "thunderfury", rank = 1, duration = 12 },
-- Thunderfury - Attack Speed Slow (12 sec, -20% attack speed on primary target)
[27648] = { name = "thunderfury slow", rank = 1, duration = 12 },
--========================================================================
-- CUSTOM/SERVER-SPECIFIC (add your own below)
--========================================================================
-- [12345] = { name = "custom debuff", rank = 1, duration = 18 },
}
--============================================================================
-- PALADIN JUDGEMENT SEAL DETECTION
-- Maps seal buff textures to judgement debuff names and their reference spellIDs
--============================================================================
local sealToJudgement = {
-- Seal texture -> { name, referenceSpellID (must be in trackedCurseIds), judgementTexture }
["Interface\\Icons\\Spell_Holy_RighteousnessAura"] = {
name = "judgement of wisdom",
spellID = 20186, -- JoW rank 1 (in our custom list)
texture = "Interface\\Icons\\Spell_Holy_RighteousnessAura",
},
["Interface\\Icons\\Spell_Holy_HealingAura"] = {
name = "judgement of light",
spellID = 20185, -- JoL rank 1 (in our custom list)
texture = "Interface\\Icons\\Spell_Holy_HealingAura",
},
["Interface\\Icons\\Spell_Holy_HolySmite"] = {
name = "judgement of the crusader",
spellID = 21183, -- JotC rank 1 (in our custom list)
texture = "Interface\\Icons\\Spell_Holy_HolySmite",
},
["Interface\\Icons\\Spell_Holy_SealOfWrath"] = {
name = "judgement of justice",
spellID = 20184, -- JoJ rank 1 (in our custom list)
texture = "Interface\\Icons\\Spell_Holy_SealOfWrath",
},
["Interface\\Icons\\Ability_ThunderClap"] = {
name = "judgement of righteousness",
spellID = 20187, -- fallback, may need correct ID
texture = "Interface\\Icons\\Ability_ThunderClap",
},
["Interface\\Icons\\Spell_Holy_SealOfMight"] = {
name = "judgement of command",
spellID = 20467, -- fallback, may need correct ID
texture = "Interface\\Icons\\Spell_Holy_SealOfMight",
},
}
-- Detect active seal from player buffs and return judgement info
local function GetActiveSealJudgementInfo()
for i = 1, 32 do
local texture = GetPlayerBuffTexture(i)
if not texture then break end
if sealToJudgement[texture] then
return sealToJudgement[texture]
end
end
-- Fallback to generic judgement
return { name = "judgement", spellID = 20271, texture = "Interface\\Icons\\Spell_Holy_RighteousFury" }
end
--============================================================================
-- AUTOMATIC INJECTION INTO CURSIVE
-- Hooks Cursive's LoadCurses to inject our spells after every reload
--============================================================================
local injectedCount = 0
local function InjectCustomSpells()
if not Cursive or not Cursive.curses or not Cursive.curses.trackedCurseIds then
return 0
end
local count = 0
for spellID, data in pairs(CleveRoids.CustomCursiveSpells) do
-- Get texture from SpellInfo (SuperWoW API)
local name, rank, texture = SpellInfo(spellID)
if texture then
-- Always update/add (in case Cursive reloaded and cleared them)
Cursive.curses.trackedCurseIds[spellID] = {
name = data.name,
rank = data.rank or 1,
duration = data.duration,
texture = texture,
variableDuration = data.variableDuration,
calculateDuration = data.calculateDuration,
numTicks = data.numTicks,
darkHarvest = data.darkHarvest,
}
Cursive.curses.trackedCurseNamesToTextures[data.name] = texture
count = count + 1
end
end
injectedCount = count
return count
end
-- Hook Cursive's LoadCurses function to inject after every reload
local function HookCursiveLoadCurses()
if not Cursive or not Cursive.curses or not Cursive.curses.LoadCurses then
return false
end
-- Check if already hooked
if Cursive.curses._customSpellsHooked then
return true
end
-- Store original function
local originalLoadCurses = Cursive.curses.LoadCurses
-- Replace with hooked version
Cursive.curses.LoadCurses = function(self)
-- Call original first
originalLoadCurses(self)
-- Then inject our custom spells
local count = InjectCustomSpells()
if count > 0 then
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00[CursiveCustomSpells]|r Injected " .. count .. " custom spells into Cursive.")
end
end
Cursive.curses._customSpellsHooked = true
return true
end
--============================================================================
-- EXTENSION REGISTRATION
--============================================================================
local ext = CleveRoids.RegisterExtension("CursiveCustomSpells")
local hooked = false
local retryFrame = nil
-- Retry injection periodically until successful
local function StartRetryTimer()
if retryFrame then return end
retryFrame = CreateFrame("Frame")
local elapsed = 0
local attempts = 0
retryFrame:SetScript("OnUpdate", function()
elapsed = elapsed + arg1
if elapsed >= 0.5 then -- Check every 0.5 seconds
elapsed = 0
attempts = attempts + 1
if HookCursiveLoadCurses() then
hooked = true
InjectCustomSpells()
retryFrame:SetScript("OnUpdate", nil)
retryFrame = nil
elseif attempts >= 20 then -- Give up after 10 seconds
retryFrame:SetScript("OnUpdate", nil)
retryFrame = nil
end
end
end)
end
ext.OnLoad = function()
-- Try to hook immediately (Cursive might already be loaded)
if HookCursiveLoadCurses() then
hooked = true
InjectCustomSpells()
else
-- Start retry timer and also register events as backup
StartRetryTimer()
ext.RegisterEvent("PLAYER_ENTERING_WORLD", "OnPlayerEnteringWorld")
end
end
ext.OnPlayerEnteringWorld = function()
if not hooked then
if HookCursiveLoadCurses() then
hooked = true
InjectCustomSpells()
end
end
-- Don't unregister - keep checking on zone changes
end
--============================================================================
-- PALADIN JUDGEMENT HOOK
-- Intercepts generic Judgement (20271) and applies correct debuff name
--============================================================================
local JUDGEMENT_SPELL_ID = 20271
local judgementHooked = false
local function HookJudgementDetection()
if judgementHooked then return end
if not Cursive or not Cursive.curses then return end
-- DO NOT register 20271 in trackedCurseIds - we handle it ourselves
-- This prevents Cursive's native handler from creating a duplicate
-- Register our own UNIT_CASTEVENT handler
local eventFrame = CreateFrame("Frame")
eventFrame:RegisterEvent("UNIT_CASTEVENT")
eventFrame:SetScript("OnEvent", function()
local casterGuid, targetGuid, event, spellID = arg1, arg2, arg3, arg4
-- Only handle our Judgement casts
if event ~= "CAST" or spellID ~= JUDGEMENT_SPELL_ID then return end
-- Check if it's the player casting
local _, playerGuid = UnitExists("player")
if casterGuid ~= playerGuid then return end
-- Detect which seal is active and get full judgement info
local info = GetActiveSealJudgementInfo()
-- Make sure the reference spellID is in trackedCurseIds
if not Cursive.curses.trackedCurseIds[info.spellID] then
Cursive.curses.trackedCurseIds[info.spellID] = {
name = info.name,
rank = 1,
duration = 10,
texture = info.texture,
}
end
-- Apply the curse manually with the correct name
if not Cursive.curses.guids[targetGuid] then
Cursive.curses.guids[targetGuid] = {}
end
Cursive.curses.guids[targetGuid][info.name] = {
rank = 1,
duration = 10,
start = GetTime(),
spellID = info.spellID, -- Use the seal-specific spellID
targetGuid = targetGuid,
currentPlayer = true,
}
-- Add texture mapping for display
Cursive.curses.trackedCurseNamesToTextures[info.name] = info.texture
end)
judgementHooked = true
end
-- Hook judgement detection when Cursive is ready
local judgementRetryFrame = CreateFrame("Frame")
judgementRetryFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
judgementRetryFrame:SetScript("OnEvent", function()
-- Delay slightly to ensure Cursive is fully loaded
local elapsed = 0
judgementRetryFrame:SetScript("OnUpdate", function()
elapsed = elapsed + arg1
if elapsed >= 1 then
HookJudgementDetection()
judgementRetryFrame:SetScript("OnUpdate", nil)
end
end)
end)
--============================================================================
-- JUDGEMENT REFRESH ON MELEE HIT
-- Judgement of Light/Wisdom refresh their duration when the Paladin hits
--============================================================================
local judgementNames = {
["judgement of wisdom"] = true,
["judgement of light"] = true,
["judgement of the crusader"] = true,
["judgement of justice"] = true,
["judgement of righteousness"] = true,
["judgement of command"] = true,
}
-- Refresh all active judgements on a target
local function RefreshJudgementsOnTarget(targetGuid)
if not Cursive or not Cursive.curses or not Cursive.curses.guids then return end
if not targetGuid or not Cursive.curses.guids[targetGuid] then return end
local now = GetTime()
for curseName, curseData in pairs(Cursive.curses.guids[targetGuid]) do
if judgementNames[curseName] and curseData.currentPlayer then
-- Refresh the timer
curseData.start = now
end
end
end
-- Hook melee combat events for auto attacks
local meleeRefreshFrame = CreateFrame("Frame")
meleeRefreshFrame:RegisterEvent("CHAT_MSG_COMBAT_SELF_HITS")
meleeRefreshFrame:SetScript("OnEvent", function()
-- Auto attack hit patterns (vanilla WoW format)
-- "You hit TargetName for X damage."
-- "You crit TargetName for X damage."
local msg = arg1
if not msg then return end
-- Parse target name from combat message
local targetName = nil
-- Pattern: "You hit TargetName for X"
local _, _, name = string.find(msg, "^You hit (.+) for")
if name then targetName = name end
-- Pattern: "You crit TargetName for X"
if not targetName then
_, _, name = string.find(msg, "^You crit (.+) for")
if name then targetName = name end
end
if not targetName then return end
-- Check if current target matches the hit target
local currentTargetName = UnitName("target")
if currentTargetName and currentTargetName == targetName then
local _, targetGuid = UnitExists("target")
if targetGuid then
RefreshJudgementsOnTarget(targetGuid)
end
end
end)
-- Hook UNIT_CASTEVENT for melee ability hits (Crusader Strike, etc.)
local meleeAbilityFrame = CreateFrame("Frame")
meleeAbilityFrame:RegisterEvent("UNIT_CASTEVENT")
meleeAbilityFrame:SetScript("OnEvent", function()
local casterGuid, targetGuid, event, spellID = arg1, arg2, arg3, arg4
-- Only handle CAST events (successful casts)
if event ~= "CAST" then return end
-- Check if it's the player casting
local _, playerGuid = UnitExists("player")
if casterGuid ~= playerGuid then return end
-- Skip Judgement itself (20271) - it applies, doesn't refresh
if spellID == JUDGEMENT_SPELL_ID then return end
-- Only refresh for melee abilities (instant casts with target)
-- We can't easily detect "melee only" so refresh on any successful cast on target
if targetGuid and targetGuid ~= "" then
RefreshJudgementsOnTarget(targetGuid)
end
end)
--============================================================================
-- CONSOLE COMMANDS: /cleveroid cursive
--============================================================================
local originalConsoleHandler = CleveRoids.HandleConsoleCommand
CleveRoids.HandleConsoleCommand = function(msg)
local cmd, args = string.match(msg or "", "^(%S+)%s*(.*)$")
cmd = cmd and string.lower(cmd) or ""
if cmd == "cursive" then
local subcmd, subargs = string.match(args or "", "^(%S+)%s*(.*)$")
subcmd = subcmd and string.lower(subcmd) or "list"
if subcmd == "list" then
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00[CursiveCustomSpells]|r Custom spell list:")
local count = 0
for spellID, data in pairs(CleveRoids.CustomCursiveSpells) do
local status = ""
if Cursive and Cursive.curses and Cursive.curses.trackedCurseIds[spellID] then
status = " |cff00ff00[active]|r"
else
status = " |cffff0000[not loaded]|r"
end
DEFAULT_CHAT_FRAME:AddMessage(string.format(" [%d] %s (rank %d, %ds)%s",
spellID, data.name, data.rank or 1, data.duration, status))
count = count + 1
end
if count == 0 then
DEFAULT_CHAT_FRAME:AddMessage(" (none - edit CursiveCustomSpells.lua to add)")
end
DEFAULT_CHAT_FRAME:AddMessage(string.format(" Hook status: %s",
hooked and "|cff00ff00active|r" or "|cffff0000not hooked|r"))
elseif subcmd == "add" then
local spellID, duration, customName = string.match(subargs, "^(%d+)%s+(%d+)%s*(.*)$")
spellID = tonumber(spellID)
duration = tonumber(duration)
if not spellID or not duration then
DEFAULT_CHAT_FRAME:AddMessage("|cffff0000Usage:|r /cleveroid cursive add <spellID> <duration> [name]")
return
end
local name, rank, texture = SpellInfo(spellID)
if not name then
DEFAULT_CHAT_FRAME:AddMessage("|cffff0000Error:|r Spell ID " .. spellID .. " not found.")
return
end
local spellName = customName ~= "" and string.lower(customName) or string.lower(name)
CleveRoids.CustomCursiveSpells[spellID] = {
name = spellName,
rank = 1,
duration = duration,
}
-- Inject immediately
if Cursive and Cursive.curses then
Cursive.curses.trackedCurseIds[spellID] = {
name = spellName,
rank = 1,
duration = duration,
texture = texture,
}
Cursive.curses.trackedCurseNamesToTextures[spellName] = texture
end
DEFAULT_CHAT_FRAME:AddMessage(string.format("|cff00ff00[CursiveCustomSpells]|r Added: [%d] %s (%ds) - will auto-track on cast",
spellID, spellName, duration))
DEFAULT_CHAT_FRAME:AddMessage(" Note: Edit CursiveCustomSpells.lua to make permanent.")
elseif subcmd == "remove" then
local spellID = tonumber(subargs)
if not spellID then
DEFAULT_CHAT_FRAME:AddMessage("|cffff0000Usage:|r /cleveroid cursive remove <spellID>")
return
end
if CleveRoids.CustomCursiveSpells[spellID] then
local name = CleveRoids.CustomCursiveSpells[spellID].name
CleveRoids.CustomCursiveSpells[spellID] = nil
if Cursive and Cursive.curses and Cursive.curses.trackedCurseIds[spellID] then
Cursive.curses.trackedCurseIds[spellID] = nil
end
DEFAULT_CHAT_FRAME:AddMessage(string.format("|cff00ff00[CursiveCustomSpells]|r Removed: [%d] %s",
spellID, name))
else
DEFAULT_CHAT_FRAME:AddMessage("|cffff0000Error:|r Spell ID " .. spellID .. " not in custom list.")
end
elseif subcmd == "inject" then
local count = InjectCustomSpells()
DEFAULT_CHAT_FRAME:AddMessage(string.format("|cff00ff00[CursiveCustomSpells]|r Injected %d spell(s).", count))
elseif subcmd == "debugseal" then
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00[CursiveCustomSpells]|r Scanning player buffs for seals:")
for i = 1, 32 do
local texture = GetPlayerBuffTexture(i)
if not texture then break end
local match = sealToJudgement[texture] and "|cff00ff00 MATCH|r" or ""
DEFAULT_CHAT_FRAME:AddMessage(string.format(" [%d] %s%s", i, texture, match))
end
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00[CursiveCustomSpells]|r Known seal textures:")
for tex, data in pairs(sealToJudgement) do
DEFAULT_CHAT_FRAME:AddMessage(string.format(" %s -> %s", tex, data.name))
end
else
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00[CursiveCustomSpells]|r Commands:")
DEFAULT_CHAT_FRAME:AddMessage(" /cleveroid cursive list - Show custom spells and status")
DEFAULT_CHAT_FRAME:AddMessage(" /cleveroid cursive add <spellID> <duration> [name]")
DEFAULT_CHAT_FRAME:AddMessage(" /cleveroid cursive remove <spellID>")
DEFAULT_CHAT_FRAME:AddMessage(" /cleveroid cursive inject - Force re-injection")
end
return
end
if originalConsoleHandler then
originalConsoleHandler(msg)
end
end
return ext
+345 -103
View File
@@ -15,7 +15,7 @@
- GetEquippedItems / GetEquippedItem - Equipment inspection
- GetBagItems / GetBagItem - Bag contents inspection
- GetCastInfo - Detailed cast/channel/GCD information
- GetSpellIdCooldown / GetItemIdCooldown - Detailed cooldown info
- GetSpellIdCooldown / GetItemIdCooldown - Detailed cooldown info with item metadata
Trinket/Item Functions (v2.20+):
- GetTrinkets - Enumerate equipped and bagged trinkets
@@ -35,11 +35,15 @@
Utility Functions (v2.22+):
- DisenchantAll - Auto-disenchant items by ID/name or quality
Cooldown Detail Tables (v2.23+):
- GetSpellIdCooldown/GetItemIdCooldown now include item metadata:
itemId, itemHasActiveSpell, itemActiveSpellId
Settings Integration:
- Reads from NampowerSettings addon when available
- Falls back to CVars when addon not present
Current version: v2.22.0
Current version: v2.23.3
]]
local _G = _G or getfenv(0)
@@ -53,17 +57,34 @@ end
local API = CleveRoids.NampowerAPI
--------------------------------------------------------------------------------
-- VERSION DETECTION
-- VERSION DETECTION AND FEATURE FLAGS
--------------------------------------------------------------------------------
-- Cached version info (set during initialization)
API._version = { major = 0, minor = 0, patch = 0, initialized = false }
-- Get Nampower version as major, minor, patch
-- Returns 0, 0, 0 if Nampower not installed
function API.GetVersion()
if API._version.initialized then
return API._version.major, API._version.minor, API._version.patch
end
if GetNampowerVersion then
local major, minor, patch = GetNampowerVersion()
return major or 0, minor or 0, patch or 0
API._version.major = major or 0
API._version.minor = minor or 0
API._version.patch = patch or 0
end
return 0, 0, 0
API._version.initialized = true
return API._version.major, API._version.minor, API._version.patch
end
-- Get version as a single comparable number (e.g., 2.18.3 -> 2018003)
function API.GetVersionNumber()
local major, minor, patch = API.GetVersion()
return (major * 1000000) + (minor * 1000) + patch
end
-- Check if installed version meets minimum requirement
@@ -75,70 +96,191 @@ function API.HasMinimumVersion(reqMajor, reqMinor, reqPatch)
elseif major == reqMajor then
if minor > reqMinor then
return true
elseif minor == reqMinor and patch >= reqPatch then
elseif minor == reqMinor and patch >= (reqPatch or 0) then
return true
end
end
return false
end
-- Feature flags based on version
API.features = {
-- Base Nampower features (v2.0+)
hasNampower = (QueueSpellByName ~= nil),
hasSpellQueue = (QueueSpellByName ~= nil),
hasIsSpellInRange = (IsSpellInRange ~= nil),
hasGetCurrentCastingInfo = (GetCurrentCastingInfo ~= nil),
hasGetSpellIdForName = (GetSpellIdForName ~= nil),
-- Version requirements for each feature group
-- Format: { major, minor, patch, globalFunction (optional verification) }
API.VERSION_REQUIREMENTS = {
-- v2.0+ - Base spell queuing
["SpellQueue"] = { 2, 0, 0, "QueueSpellByName" },
["GetCurrentCastingInfo"] = { 2, 0, 0, "GetCurrentCastingInfo" },
-- Extended API (v2.8+)
hasGetSpellRec = (GetSpellRec ~= nil),
hasGetSpellRecField = (GetSpellRecField ~= nil),
hasGetItemStats = (GetItemStats ~= nil),
hasGetItemStatsField = (GetItemStatsField ~= nil),
hasGetUnitData = (GetUnitData ~= nil),
hasGetUnitField = (GetUnitField ~= nil),
hasGetSpellModifiers = (GetSpellModifiers ~= nil),
-- v2.8+ - DBC data access
["GetSpellRec"] = { 2, 8, 0, "GetSpellRec" },
["GetSpellRecField"] = { 2, 8, 0, "GetSpellRecField" },
["GetItemStats"] = { 2, 8, 0, "GetItemStats" },
["GetItemStatsField"] = { 2, 8, 0, "GetItemStatsField" },
["GetUnitData"] = { 2, 8, 0, "GetUnitData" },
["GetUnitField"] = { 2, 8, 0, "GetUnitField" },
["GetSpellModifiers"] = { 2, 8, 0, "GetSpellModifiers" },
-- Enhanced spell functions (accept name/"spellId:number")
hasEnhancedSpellFunctions = false, -- Detected at runtime
-- v2.11+ - GetNampowerVersion itself (meta-feature)
["GetNampowerVersion"] = { 2, 11, 0, "GetNampowerVersion" },
-- v2.18+ APIs (inventory/equipment/cast info)
hasFindPlayerItemSlot = (FindPlayerItemSlot ~= nil),
hasGetEquippedItems = (GetEquippedItems ~= nil),
hasGetEquippedItem = (GetEquippedItem ~= nil),
hasGetBagItems = (GetBagItems ~= nil),
hasGetBagItem = (GetBagItem ~= nil),
hasGetCastInfo = (GetCastInfo ~= nil),
hasGetSpellIdCooldown = (GetSpellIdCooldown ~= nil),
hasGetItemIdCooldown = (GetItemIdCooldown ~= nil),
hasChannelStopCastingNextTick = (ChannelStopCastingNextTick ~= nil),
-- v2.12+ - Enhanced spell functions and range/usable checks
["EnhancedSpellFunctions"] = { 2, 12, 0 }, -- No single function to verify
["IsSpellInRange"] = { 2, 12, 0, "IsSpellInRange" },
["IsSpellUsable"] = { 2, 12, 0, "IsSpellUsable" },
["GetSpellIdForName"] = { 2, 12, 0, "GetSpellIdForName" },
["GetSpellNameAndRankForId"]= { 2, 12, 0, "GetSpellNameAndRankForId" },
["GetSpellSlotTypeIdForName"]={ 2, 12, 0, "GetSpellSlotTypeIdForName" },
["CastSpellByNameNoQueue"] = { 2, 12, 0, "CastSpellByNameNoQueue" },
["QueueScript"] = { 2, 12, 0, "QueueScript" },
-- v2.20+ APIs (trinkets, item usage, enhanced cooldowns)
hasGetTrinkets = (GetTrinkets ~= nil),
hasGetTrinketCooldown = (GetTrinketCooldown ~= nil),
hasUseTrinket = (UseTrinket ~= nil),
hasUseItemIdOrName = (UseItemIdOrName ~= nil),
-- v2.13+ - QueueSpellByName with target parameter
["QueueSpellWithTarget"] = { 2, 13, 0 }, -- Same function, new parameter
-- v2.22+ APIs (utility functions)
hasDisenchantAll = (DisenchantAll ~= nil),
-- v2.14+ - GetItemLevel standalone function
["GetItemLevel"] = { 2, 14, 0, "GetItemLevel" },
-- v2.18+ - Inventory, equipment, detailed cast/cooldown info
["FindPlayerItemSlot"] = { 2, 18, 0, "FindPlayerItemSlot" },
["GetEquippedItems"] = { 2, 18, 0, "GetEquippedItems" },
["GetEquippedItem"] = { 2, 18, 0, "GetEquippedItem" },
["GetBagItems"] = { 2, 18, 0, "GetBagItems" },
["GetBagItem"] = { 2, 18, 0, "GetBagItem" },
["GetCastInfo"] = { 2, 18, 0, "GetCastInfo" },
["GetSpellIdCooldown"] = { 2, 18, 0, "GetSpellIdCooldown" },
["GetItemIdCooldown"] = { 2, 18, 0, "GetItemIdCooldown" },
["ChannelStopCastingNextTick"]={ 2, 18, 0, "ChannelStopCastingNextTick" },
-- v2.20+ - Trinket API, item usage, copy parameter support
["GetTrinkets"] = { 2, 20, 0, "GetTrinkets" },
["GetTrinketCooldown"] = { 2, 20, 0, "GetTrinketCooldown" },
["UseTrinket"] = { 2, 20, 0, "UseTrinket" },
["UseItemIdOrName"] = { 2, 20, 0, "UseItemIdOrName" },
["CopyParameter"] = { 2, 20, 0 }, -- Table copy support
-- v2.22+ - Utility functions
["DisenchantAll"] = { 2, 22, 0, "DisenchantAll" },
-- v2.23+ - Enhanced bag queries, cooldown item metadata
["GetBagItemsWithIndex"] = { 2, 23, 0 }, -- GetBagItems(bagIndex) parameter
["CooldownItemMetadata"] = { 2, 23, 0 }, -- itemId, itemHasActiveSpell, itemActiveSpellId
}
-- Detect if enhanced spell functions are available (accept name/spellId:number)
-- Check if a specific feature is available
-- Uses both version check AND function existence verification
function API.HasFeature(featureName)
local req = API.VERSION_REQUIREMENTS[featureName]
if not req then
return false
end
-- Check version requirement
if not API.HasMinimumVersion(req[1], req[2], req[3]) then
return false
end
-- If a global function is specified, verify it exists
if req[4] then
return _G[req[4]] ~= nil
end
return true
end
-- Get the minimum version required for a feature
-- Returns major, minor, patch or nil if feature unknown
function API.GetFeatureVersion(featureName)
local req = API.VERSION_REQUIREMENTS[featureName]
if req then
return req[1], req[2], req[3]
end
return nil
end
-- Feature flags (populated during initialization)
-- These provide quick boolean lookups after init
API.features = {}
-- Initialize all feature flags based on version detection
local function InitializeFeatures()
local f = API.features
-- Initialize version cache first
API.GetVersion()
-- Base Nampower detection (v2.0+)
f.hasNampower = API.HasFeature("SpellQueue")
f.hasSpellQueue = f.hasNampower
f.hasGetCurrentCastingInfo = API.HasFeature("GetCurrentCastingInfo")
-- v2.8+ DBC data access
f.hasGetSpellRec = API.HasFeature("GetSpellRec")
f.hasGetSpellRecField = API.HasFeature("GetSpellRecField")
f.hasGetItemStats = API.HasFeature("GetItemStats")
f.hasGetItemStatsField = API.HasFeature("GetItemStatsField")
f.hasGetUnitData = API.HasFeature("GetUnitData")
f.hasGetUnitField = API.HasFeature("GetUnitField")
f.hasGetSpellModifiers = API.HasFeature("GetSpellModifiers")
-- v2.11+ GetNampowerVersion
f.hasGetNampowerVersion = API.HasFeature("GetNampowerVersion")
-- v2.12+ Enhanced spell functions
f.hasEnhancedSpellFunctions = API.HasFeature("EnhancedSpellFunctions")
f.hasIsSpellInRange = API.HasFeature("IsSpellInRange")
f.hasIsSpellUsable = API.HasFeature("IsSpellUsable")
f.hasGetSpellIdForName = API.HasFeature("GetSpellIdForName")
f.hasGetSpellNameAndRankForId = API.HasFeature("GetSpellNameAndRankForId")
f.hasGetSpellSlotTypeIdForName = API.HasFeature("GetSpellSlotTypeIdForName")
f.hasCastSpellByNameNoQueue = API.HasFeature("CastSpellByNameNoQueue")
f.hasQueueScript = API.HasFeature("QueueScript")
-- v2.13+ QueueSpellByName with target
f.hasQueueSpellWithTarget = API.HasFeature("QueueSpellWithTarget")
-- v2.14+ GetItemLevel
f.hasGetItemLevel = API.HasFeature("GetItemLevel")
-- v2.18+ Inventory/Equipment/Cast info
f.hasFindPlayerItemSlot = API.HasFeature("FindPlayerItemSlot")
f.hasGetEquippedItems = API.HasFeature("GetEquippedItems")
f.hasGetEquippedItem = API.HasFeature("GetEquippedItem")
f.hasGetBagItems = API.HasFeature("GetBagItems")
f.hasGetBagItem = API.HasFeature("GetBagItem")
f.hasGetCastInfo = API.HasFeature("GetCastInfo")
f.hasGetSpellIdCooldown = API.HasFeature("GetSpellIdCooldown")
f.hasGetItemIdCooldown = API.HasFeature("GetItemIdCooldown")
f.hasChannelStopCastingNextTick = API.HasFeature("ChannelStopCastingNextTick")
-- v2.20+ Trinket/Item API
f.hasGetTrinkets = API.HasFeature("GetTrinkets")
f.hasGetTrinketCooldown = API.HasFeature("GetTrinketCooldown")
f.hasUseTrinket = API.HasFeature("UseTrinket")
f.hasUseItemIdOrName = API.HasFeature("UseItemIdOrName")
f.hasCopyParameter = API.HasFeature("CopyParameter")
-- v2.22+ Utility functions
f.hasDisenchantAll = API.HasFeature("DisenchantAll")
-- v2.23+ Enhanced features
f.hasGetBagItemsWithIndex = API.HasFeature("GetBagItemsWithIndex")
f.hasCooldownItemMetadata = API.HasFeature("CooldownItemMetadata")
-- Runtime detection for enhanced spell functions (verify by testing)
if f.hasEnhancedSpellFunctions and GetSpellTexture then
local success, result = pcall(function()
return GetSpellTexture("Attack")
end)
f.hasEnhancedSpellFunctions = success and (result ~= nil)
end
end
-- Legacy function for compatibility (now just calls InitializeFeatures)
local function DetectEnhancedSpellFunctions()
if not GetSpellTexture then return false end
-- Try calling GetSpellTexture with a spell name
-- If it doesn't error and returns something, enhanced functions are available
local success, result = pcall(function()
-- Use "Attack" which should exist for all characters
return GetSpellTexture("Attack")
end)
API.features.hasEnhancedSpellFunctions = success and (result ~= nil)
return API.features.hasEnhancedSpellFunctions
end
-- Initialize features immediately on load
InitializeFeatures()
--------------------------------------------------------------------------------
-- NAMPOWER SETTINGS ACCESS
--------------------------------------------------------------------------------
@@ -280,20 +422,28 @@ end
API.spellRecCache = {}
-- Get full spell record data
-- Returns nil if spell not found or API unavailable
-- Returns nil if spell not found or API unavailable (requires v2.8+)
-- Pass copy=1 to get an independent copy safe for storage (v2.20+)
-- WARNING: Without copy, table references are reused - extract values immediately!
function API.GetSpellRecord(spellId, copy)
if not spellId or spellId == 0 then return nil end
-- Requires v2.8+ for GetSpellRec
if not API.features.hasGetSpellRec then
return nil
end
-- Check cache first (only for non-copy requests)
if not copy and API.spellRecCache[spellId] then
return API.spellRecCache[spellId]
end
-- Use native function if available
-- Copy parameter requires v2.20+
local useCopy = copy and API.features.hasCopyParameter and copy or nil
-- Use native function
if GetSpellRec then
local rec = GetSpellRec(spellId, copy)
local rec = GetSpellRec(spellId, useCopy)
if rec then
if not copy then
API.spellRecCache[spellId] = rec
@@ -306,19 +456,25 @@ function API.GetSpellRecord(spellId, copy)
end
-- Get a specific field from spell record
-- Returns nil if not found, raises error if field name invalid (native behavior)
-- Returns nil if not found (requires v2.8+)
-- Pass copy=1 to get an independent copy for array fields (v2.20+)
function API.GetSpellField(spellId, fieldName, copy)
if not spellId or spellId == 0 then return nil end
-- Use native function if available (more efficient for single field)
-- Requires v2.8+ for GetSpellRecField
if not API.features.hasGetSpellRecField then
return nil
end
-- Copy parameter requires v2.20+
local useCopy = copy and API.features.hasCopyParameter and copy or nil
-- Use native function (more efficient for single field)
if GetSpellRecField then
local success, result = pcall(GetSpellRecField, spellId, fieldName, copy)
local success, result = pcall(GetSpellRecField, spellId, fieldName, useCopy)
if success then
return result
end
-- If it errors on invalid field name, let it propagate
-- But if spell just not found, return nil
return nil
end
@@ -537,19 +693,28 @@ end
API.itemStatsCache = {}
-- Get full item stats data
-- Requires v2.8+ for GetItemStats
-- Pass copy=1 to get an independent copy safe for storage (v2.20+)
-- WARNING: Without copy, table references are reused - extract values immediately!
function API.GetItemRecord(itemId, copy)
if not itemId or itemId == 0 then return nil end
-- Requires v2.8+ for GetItemStats
if not API.features.hasGetItemStats then
return nil
end
-- Check cache first (only for non-copy requests)
if not copy and API.itemStatsCache[itemId] then
return API.itemStatsCache[itemId]
end
-- Use native function if available (wrapped in pcall for safety)
-- Copy parameter requires v2.20+
local useCopy = copy and API.features.hasCopyParameter and copy or nil
-- Use native function
if GetItemStats then
local ok, stats = pcall(GetItemStats, itemId, copy)
local ok, stats = pcall(GetItemStats, itemId, useCopy)
if ok and stats then
if not copy then
API.itemStatsCache[itemId] = stats
@@ -562,13 +727,22 @@ function API.GetItemRecord(itemId, copy)
end
-- Get a specific field from item stats
-- Requires v2.8+ for GetItemStatsField
-- Pass copy=1 to get an independent copy for array fields (v2.20+)
function API.GetItemField(itemId, fieldName, copy)
if not itemId or itemId == 0 then return nil end
-- Use native function if available (more efficient)
-- Requires v2.8+ for GetItemStatsField
if not API.features.hasGetItemStatsField then
return nil
end
-- Copy parameter requires v2.20+
local useCopy = copy and API.features.hasCopyParameter and copy or nil
-- Use native function (more efficient)
if GetItemStatsField then
local success, result = pcall(GetItemStatsField, itemId, fieldName, copy)
local success, result = pcall(GetItemStatsField, itemId, fieldName, useCopy)
if success then
return result
end
@@ -585,13 +759,15 @@ function API.GetItemField(itemId, fieldName, copy)
end
-- Get item level
-- Uses standalone GetItemLevel (v2.14+) or falls back to GetItemStatsField (v2.8+)
function API.GetItemLevel(itemId)
-- Use dedicated function if available
if GetItemLevel then
-- Use dedicated function if available (v2.14+)
if API.features.hasGetItemLevel and GetItemLevel then
local success, result = pcall(GetItemLevel, itemId)
if success then return result end
end
-- Fallback to GetItemField (v2.8+)
return API.GetItemField(itemId, "itemLevel")
end
@@ -671,30 +847,48 @@ function API.GetItemEquipSlot(itemId)
end
--------------------------------------------------------------------------------
-- UNIT DATA API (GetUnitData / GetUnitField)
-- UNIT DATA API (GetUnitData / GetUnitField) - Requires v2.8+
--------------------------------------------------------------------------------
-- Get full unit data (no caching - unit data changes frequently)
-- Requires v2.8+ for GetUnitData
-- Pass copy=1 to get an independent copy safe for storage (v2.20+)
-- WARNING: Without copy, table references are reused - extract values immediately!
function API.GetUnitRecord(unitToken, copy)
if not unitToken then return nil end
-- Requires v2.8+ for GetUnitData
if not API.features.hasGetUnitData then
return nil
end
-- Copy parameter requires v2.20+
local useCopy = copy and API.features.hasCopyParameter and copy or nil
if GetUnitData then
return GetUnitData(unitToken, copy)
return GetUnitData(unitToken, useCopy)
end
return nil
end
-- Get a specific unit field
-- Requires v2.8+ for GetUnitField
-- Pass copy=1 to get an independent copy for array fields (v2.20+)
function API.GetUnitFieldValue(unitToken, fieldName, copy)
if not unitToken then return nil end
-- Use native function if available (more efficient)
-- Requires v2.8+ for GetUnitField
if not API.features.hasGetUnitField then
return nil
end
-- Copy parameter requires v2.20+
local useCopy = copy and API.features.hasCopyParameter and copy or nil
-- Use native function (more efficient)
if GetUnitField then
local success, result = pcall(GetUnitField, unitToken, fieldName, copy)
local success, result = pcall(GetUnitField, unitToken, fieldName, useCopy)
if success then
return result
end
@@ -750,6 +944,7 @@ end
--------------------------------------------------------------------------------
-- Find an item in player's inventory by ID or name
-- Requires v2.18+ for native FindPlayerItemSlot (with Lua fallback)
-- Returns: bagIndex, slot (see wiki for bag index meanings)
-- bagIndex nil + slot = equipped item (slot is equipment slot 0-18)
-- bagIndex + slot = item in bag
@@ -758,7 +953,7 @@ function API.FindPlayerItemSlot(itemIdOrName)
if not itemIdOrName then return nil, nil end
-- Use native function if available (v2.18+)
if FindPlayerItemSlot then
if API.features.hasFindPlayerItemSlot and FindPlayerItemSlot then
return FindPlayerItemSlot(itemIdOrName)
end
@@ -806,12 +1001,13 @@ function API.FindPlayerItemSlot(itemIdOrName)
end
-- Get all equipped items for a unit
-- Requires v2.18+ for native GetEquippedItems (with Lua fallback for player only)
-- Returns table with slot indices (0-18) as keys, item info tables as values
function API.GetEquippedItems(unitToken)
unitToken = unitToken or "player"
-- Use native function if available (v2.18+)
if GetEquippedItems then
if API.features.hasGetEquippedItems and GetEquippedItems then
return GetEquippedItems(unitToken)
end
@@ -838,12 +1034,13 @@ function API.GetEquippedItems(unitToken)
end
-- Get equipped item info for a specific slot
-- Requires v2.18+ for native GetEquippedItem (with Lua fallback for player only)
-- Slot numbers: 1=Head, 2=Neck, 3=Shoulder... 16=MainHand, 17=OffHand, 18=Ranged
function API.GetEquippedItem(unitToken, slot)
unitToken = unitToken or "player"
-- Use native function if available (v2.18+)
if GetEquippedItem then
if API.features.hasGetEquippedItem and GetEquippedItem then
return GetEquippedItem(unitToken, slot)
end
@@ -865,15 +1062,45 @@ function API.GetEquippedItem(unitToken, slot)
return nil
end
-- Get all items in all bags
-- Returns nested table: bagIndex -> { slot -> itemInfo }
function API.GetBagItems()
-- Get all items in bags, or items in a specific bag
-- Requires v2.18+ for native GetBagItems (with Lua fallback)
-- bagIndex parameter requires v2.23+
-- If bagIndex is nil: returns nested table: bagIndex -> { slot -> itemInfo }
-- If bagIndex is specified: returns single table: slot -> itemInfo for that bag only
-- Bag indices: 0=backpack, 1-4=bags, -1=bank slots, 5-10=bank bags, -2=keyring
function API.GetBagItems(bagIndex)
-- Use native function if available (v2.18+)
if GetBagItems then
return GetBagItems()
if API.features.hasGetBagItems and GetBagItems then
-- bagIndex parameter requires v2.23+
if bagIndex ~= nil and not API.features.hasGetBagItemsWithIndex then
-- Fall through to Lua fallback for single-bag query on older versions
else
return GetBagItems(bagIndex)
end
end
-- Fallback: manual enumeration
if bagIndex ~= nil then
-- Single bag query
local bagContents = {}
local numSlots = GetContainerNumSlots(bagIndex) or 0
for slot = 1, numSlots do
local link = GetContainerItemLink(bagIndex, slot)
if link then
local _, _, itemId = string.find(link, "item:(%d+)")
local _, count = GetContainerItemInfo(bagIndex, slot)
if itemId then
bagContents[slot] = {
itemId = tonumber(itemId),
stackCount = count or 1,
}
end
end
end
return bagContents
end
-- All bags query
local bags = {}
for bag = 0, 4 do
local numSlots = GetContainerNumSlots(bag)
@@ -899,9 +1126,10 @@ function API.GetBagItems()
end
-- Get item info for a specific bag slot
-- Requires v2.18+ for native GetBagItem (with Lua fallback)
function API.GetBagItem(bagIndex, slot)
-- Use native function if available (v2.18+)
if GetBagItem then
if API.features.hasGetBagItem and GetBagItem then
return GetBagItem(bagIndex, slot)
end
@@ -1171,12 +1399,13 @@ end
--------------------------------------------------------------------------------
-- Get detailed information about current cast/channel
-- Requires v2.18+ for native GetCastInfo (with GetCurrentCastingInfo fallback for v2.0+)
-- Returns table with: castId, spellId, guid, castType, castStartS, castEndS,
-- castRemainingMs, castDurationMs, gcdEndS, gcdRemainingMs
-- Returns nil if no active cast
function API.GetCastInfo()
-- Use native function if available (v2.18+)
if GetCastInfo then
if API.features.hasGetCastInfo and GetCastInfo then
local ok, result = pcall(GetCastInfo)
if ok then
return result
@@ -1184,8 +1413,8 @@ function API.GetCastInfo()
-- If pcall failed, fall through to fallback
end
-- Fallback: use GetCurrentCastingInfo (less detailed)
if GetCurrentCastingInfo then
-- Fallback: use GetCurrentCastingInfo (less detailed, v2.0+)
if API.features.hasGetCurrentCastingInfo and GetCurrentCastingInfo then
local castId, visId, autoId, casting, channeling, onswing, autoattack = GetCurrentCastingInfo()
if castId and castId > 0 then
return {
@@ -1228,15 +1457,17 @@ end
--------------------------------------------------------------------------------
-- Get detailed cooldown information for a spell
-- Requires v2.18+ for native GetSpellIdCooldown (with GetSpellCooldown fallback)
-- Returns table with: isOnCooldown, cooldownRemainingMs,
-- individual cooldown: individualStartS, individualDurationMs, individualRemainingMs, isOnIndividualCooldown
-- category cooldown: categoryId, categoryStartS, categoryDurationMs, categoryRemainingMs, isOnCategoryCooldown
-- GCD: gcdCategoryId, gcdCategoryStartS, gcdCategoryDurationMs, gcdCategoryRemainingMs, isOnGcdCategoryCooldown
-- item metadata (v2.23+): itemId, itemHasActiveSpell, itemActiveSpellId
function API.GetSpellCooldownInfo(spellId)
if not spellId or spellId == 0 then return nil end
-- Use native function if available (v2.18+)
if GetSpellIdCooldown then
if API.features.hasGetSpellIdCooldown and GetSpellIdCooldown then
return GetSpellIdCooldown(spellId)
end
@@ -1259,11 +1490,14 @@ function API.GetSpellCooldownInfo(spellId)
end
-- Get detailed cooldown information for an item
-- Requires v2.18+ for native GetItemIdCooldown (with GetItemCooldown fallback)
-- Returns same table structure as GetSpellCooldownInfo
-- v2.23+ adds: itemId, itemHasActiveSpell, itemActiveSpellId
function API.GetItemCooldownInfo(itemId)
if not itemId or itemId == 0 then return nil end
-- Use native function if available (v2.18+)
if GetItemIdCooldown then
if API.features.hasGetItemIdCooldown and GetItemIdCooldown then
return GetItemIdCooldown(itemId)
end
@@ -1309,9 +1543,10 @@ end
--------------------------------------------------------------------------------
-- Stop channeling early on the next tick
-- Requires v2.18+ for ChannelStopCastingNextTick
-- Only works if NP_QueueChannelingSpells is enabled
function API.StopChannelNextTick()
if ChannelStopCastingNextTick then
if API.features.hasChannelStopCastingNextTick and ChannelStopCastingNextTick then
ChannelStopCastingNextTick()
return true
end
@@ -1329,11 +1564,13 @@ API.TRINKET_SLOT = {
}
-- Get all trinkets (equipped and in bags)
-- Requires v2.20+ for native GetTrinkets (with Lua fallback)
-- Returns table with: itemId, trinketName, texture, itemLevel, bagIndex (nil=equipped), slotIndex
-- Pass copy=1 to get an independent copy safe for storage
-- Pass copy=1 to get an independent copy safe for storage (v2.20+)
function API.GetTrinkets(copy)
-- Use native function if available (v2.20+)
if GetTrinkets then
if API.features.hasGetTrinkets and GetTrinkets then
-- Copy parameter supported in v2.20+
return GetTrinkets(copy)
end
@@ -1397,13 +1634,14 @@ function API.GetTrinkets(copy)
end
-- Get cooldown for an equipped trinket
-- Requires v2.20+ for native GetTrinketCooldown (with Lua fallback)
-- slot: 1 or 13 = first trinket, 2 or 14 = second trinket
-- OR item ID (number) or item name (string) to match
-- Returns cooldown detail table (same as GetSpellIdCooldown/GetItemIdCooldown)
-- Returns -1 if no matching trinket is equipped
function API.GetTrinketCooldown(slot)
-- Use native function if available (v2.20+)
if GetTrinketCooldown then
if API.features.hasGetTrinketCooldown and GetTrinketCooldown then
return GetTrinketCooldown(slot)
end
@@ -1462,13 +1700,14 @@ function API.GetTrinketCooldown(slot)
end
-- Use an equipped trinket
-- Requires v2.20+ for native UseTrinket (with Lua fallback)
-- slot: 1 or 13 = first trinket, 2 or 14 = second trinket
-- OR item ID (number) or item name (string) to match
-- target: optional unit token or GUID
-- Returns: 1 if used, 0 if use failed, -1 if trinket not found
function API.UseTrinket(slot, target)
-- Use native function if available (v2.20+)
if UseTrinket then
if API.features.hasUseTrinket and UseTrinket then
return UseTrinket(slot, target)
end
@@ -1522,12 +1761,13 @@ end
--------------------------------------------------------------------------------
-- Use an item by ID or name
-- Requires v2.20+ for native UseItemIdOrName (with Lua fallback)
-- itemIdOrName: item ID (number) or item name (string)
-- target: optional unit token or GUID
-- Returns: 1 if used successfully, 0 if not found or use failed
function API.UseItemIdOrName(itemIdOrName, target)
-- Use native function if available (v2.20+)
if UseItemIdOrName then
if API.features.hasUseItemIdOrName and UseItemIdOrName then
return UseItemIdOrName(itemIdOrName, target)
end
@@ -1544,8 +1784,9 @@ end
--------------------------------------------------------------------------------
-- Automatically disenchant items in inventory
-- Requires v2.22+ for DisenchantAll (no fallback available)
-- Mode 1: DisenchantAll(itemIdOrName, [includeSoulbound]) - specific item by ID/name
-- Mode 2: DisenchantAll(quality, [includeSoulbound]) - "greens" or "blues"
-- Mode 2: DisenchantAll(quality, [includeSoulbound]) - "greens", "blues", or "greens|blues" (v2.23+)
-- includeSoulbound: pass 1 to include soulbound items (default: 0)
-- Returns: 1 if first disenchant succeeded, 0 if no items found or failed
--
@@ -1554,7 +1795,7 @@ end
-- Only affects bags 0-4 (not bank).
function API.DisenchantAll(itemIdOrNameOrQuality, includeSoulbound)
-- Use native function if available (v2.22+)
if DisenchantAll then
if API.features.hasDisenchantAll and DisenchantAll then
return DisenchantAll(itemIdOrNameOrQuality, includeSoulbound)
end
@@ -1675,9 +1916,10 @@ API.MODIFIER_MULTIPLE_VALUE = 27
API.MODIFIER_RESIST_DISPEL_CHANCE = 28
-- Get spell modifiers
-- Requires v2.8+ for GetSpellModifiers
-- Returns: flatMod, percentMod, hasModifier
function API.GetModifiers(spellId, modifierType)
if not GetSpellModifiers then
if not API.features.hasGetSpellModifiers or not GetSpellModifiers then
return 0, 0, false
end
@@ -2187,7 +2429,7 @@ end
-- Parameters:
-- spellName: The spell name (with optional rank)
-- target: Optional target unit (for SuperWoW CastSpellByName)
-- forceQueue: If true, always use QueueSpellByName (if available)
-- forceQueue: If true, always use QueueSpellByName (if available, v2.0+)
-- forceNoQueue: If true, never use QueueSpellByName
function API.SmartCast(spellName, target, forceQueue, forceNoQueue)
if not spellName then return false end
@@ -2208,10 +2450,10 @@ function API.SmartCast(spellName, target, forceQueue, forceNoQueue)
-- QueueSpellByName in older Nampower versions doesn't support target parameter
-- Only Nampower v2.13+ supports QueueSpellByName with target
local hasNonStandardTarget = target and target ~= "target"
local canQueueWithTarget = API.HasMinimumVersion(2, 13, 0)
local canQueueWithTarget = API.features.hasQueueSpellWithTarget
-- Cast the spell
if useQueue and QueueSpellByName then
if useQueue and API.features.hasSpellQueue and QueueSpellByName then
if hasNonStandardTarget then
if canQueueWithTarget then
-- Nampower v2.13+ supports target parameter in QueueSpellByName
@@ -2242,12 +2484,12 @@ function API.SmartCast(spellName, target, forceQueue, forceNoQueue)
return false
end
-- Cast without queuing (uses CastSpellByNameNoQueue if available)
-- Cast without queuing (uses CastSpellByNameNoQueue if available, v2.12+)
function API.CastNoQueue(spellName, target)
if not spellName then return false end
-- Use Nampower's no-queue function if available
if CastSpellByNameNoQueue then
-- Use Nampower's no-queue function if available (v2.12+)
if API.features.hasCastSpellByNameNoQueue and CastSpellByNameNoQueue then
CastSpellByNameNoQueue(spellName)
return true
end
@@ -2265,11 +2507,11 @@ function API.CastNoQueue(spellName, target)
return false
end
-- Force queue a spell (uses QueueSpellByName directly)
-- Force queue a spell (uses QueueSpellByName directly, v2.0+)
function API.ForceQueue(spellName)
if not spellName then return false end
if QueueSpellByName then
if API.features.hasSpellQueue and QueueSpellByName then
QueueSpellByName(spellName)
return true
end
@@ -2283,11 +2525,11 @@ function API.ForceQueue(spellName)
return false
end
-- Queue a script to run after current cast (uses QueueScript if available)
-- Queue a script to run after current cast (uses QueueScript if available, v2.12+)
function API.QueueScript(script, priority)
if not script then return false end
if QueueScript then
if API.features.hasQueueScript and QueueScript then
QueueScript(script, priority or 1)
return true
end
+2 -1
View File
@@ -3,7 +3,7 @@
## Title: [Super]CleveRoidMacros
## Notes: /cleveroid for settings
## Version: 1.8
## OptionalDeps: ClassicFocus, FocusFrame, pfUI, SuperMacro, Bongos_ActionBar
## OptionalDeps: ClassicFocus, FocusFrame, pfUI, SuperMacro, Bongos_ActionBar, Cursive
## SavedVariables: CleveRoidMacros, CleveRoids_LearnedDurations, CleveRoids_AuraTextures, CleveRoids_ImmunityData, CleveRoids_ComboDurations, CleveRoids_SpellSchools
Localization.lua
Init.lua
@@ -34,4 +34,5 @@ Extensions\Mouseover\sRaidFrames.lua
Extensions\Mouseover\ag_UnitFrames.lua
Extensions\Tooltip\Generic.lua
Extensions\CursiveCustomSpells.lua