Files
SuperCleveRoidMacros/NampowerAPI.lua
T

1777 lines
56 KiB
Lua

--[[
NampowerAPI.lua - Nampower API Integration Layer
Provides wrapper functions for Nampower's extended Lua API with fallbacks
for older versions or when functions are unavailable.
New Nampower Functions Wrapped:
- GetSpellRec / GetSpellRecField - Spell record data from client DB
- GetItemStats / GetItemStatsField - Item stats from client DB
- GetUnitData / GetUnitField - Low-level unit field access
- GetSpellModifiers - Spell modifier calculations (talents, buffs, etc.)
Enhanced Functions (now accept spell name or "spellId:number"):
- GetSpellTexture, GetSpellName, GetSpellCooldown, GetSpellAutocast
- ToggleSpellAutocast, PickupSpell, CastSpell, IsCurrentCast, IsSpellPassive
Settings Integration:
- Reads from NampowerSettings addon when available
- Falls back to CVars when addon not present
]]
local _G = _G or getfenv(0)
local CleveRoids = _G.CleveRoids
-- Nampower API namespace
CleveRoids.NampowerAPI = CleveRoids.NampowerAPI or {}
local API = CleveRoids.NampowerAPI
--------------------------------------------------------------------------------
-- VERSION DETECTION
--------------------------------------------------------------------------------
-- Get Nampower version as major, minor, patch
-- Returns 0, 0, 0 if Nampower not installed
function API.GetVersion()
if GetNampowerVersion then
local major, minor, patch = GetNampowerVersion()
return major or 0, minor or 0, patch or 0
end
return 0, 0, 0
end
-- Check if installed version meets minimum requirement
function API.HasMinimumVersion(reqMajor, reqMinor, reqPatch)
local major, minor, patch = API.GetVersion()
if major > reqMajor then
return true
elseif major == reqMajor then
if minor > reqMinor then
return true
elseif minor == reqMinor and patch >= reqPatch 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),
-- 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),
-- Enhanced spell functions (accept name/"spellId:number")
hasEnhancedSpellFunctions = false, -- Detected at runtime
-- 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),
}
-- Detect if enhanced spell functions are available (accept name/spellId:number)
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
--------------------------------------------------------------------------------
-- NAMPOWER SETTINGS ACCESS
--------------------------------------------------------------------------------
-- Cache for settings to avoid repeated lookups
API.settingsCache = {}
API.settingsCacheTime = 0
API.SETTINGS_CACHE_DURATION = 5 -- Refresh every 5 seconds
-- All known Nampower CVars with their defaults
API.defaultSettings = {
NP_QueueCastTimeSpells = "1",
NP_QueueInstantSpells = "1",
NP_QueueChannelingSpells = "1",
NP_QueueTargetingSpells = "1",
NP_QueueOnSwingSpells = "0",
NP_QueueSpellsOnCooldown = "1",
NP_InterruptChannelsOutsideQueueWindow = "0",
NP_SpellQueueWindowMs = "500",
NP_OnSwingBufferCooldownMs = "500",
NP_ChannelQueueWindowMs = "1500",
NP_TargetingQueueWindowMs = "500",
NP_CooldownQueueWindowMs = "250",
NP_MinBufferTimeMs = "55",
NP_NonGcdBufferTimeMs = "100",
NP_MaxBufferIncreaseMs = "30",
NP_RetryServerRejectedSpells = "1",
NP_QuickcastTargetingSpells = "0",
NP_QuickcastOnDoubleCast = "0", -- v2.18+: cast targeting spells by double-casting
NP_ReplaceMatchingNonGcdCategory = "0",
NP_OptimizeBufferUsingPacketTimings = "0",
NP_PreventRightClickTargetChange = "0",
NP_PreventRightClickPvPAttack = "1",
NP_DoubleCastToEndChannelEarly = "0",
NP_SpamProtectionEnabled = "1",
NP_ChannelLatencyReductionPercentage = "75",
NP_NameplateDistance = "41",
}
-- Get a Nampower setting value
-- Priority: NampowerSettings addon (per-character) > CVar > default
function API.GetSetting(settingName)
-- Check if NampowerSettings addon is loaded and has per-character settings enabled
if Nampower and Nampower.db and Nampower.db.profile then
if Nampower.db.profile.per_character_settings and Nampower.db.profile[settingName] ~= nil then
return Nampower.db.profile[settingName]
end
end
-- Fall back to CVar
local cvarValue = GetCVar(settingName)
if cvarValue then
return cvarValue
end
-- Return default if known
return API.defaultSettings[settingName]
end
-- Get a Nampower setting as a boolean
function API.GetSettingBool(settingName)
local value = API.GetSetting(settingName)
if type(value) == "boolean" then
return value
end
return value == "1" or value == true
end
-- Get a Nampower setting as a number
function API.GetSettingNumber(settingName)
local value = API.GetSetting(settingName)
return tonumber(value) or 0
end
-- Check if spell queuing is enabled for a given spell type
function API.IsQueueingEnabled(spellType)
if not API.features.hasNampower then
return false
end
if spellType == "cast" or spellType == "casttime" then
return API.GetSettingBool("NP_QueueCastTimeSpells")
elseif spellType == "instant" or spellType == "gcd" then
return API.GetSettingBool("NP_QueueInstantSpells")
elseif spellType == "channel" or spellType == "channeling" then
return API.GetSettingBool("NP_QueueChannelingSpells")
elseif spellType == "targeting" or spellType == "aoe" then
return API.GetSettingBool("NP_QueueTargetingSpells")
elseif spellType == "onswing" or spellType == "swing" then
return API.GetSettingBool("NP_QueueOnSwingSpells")
elseif spellType == "cooldown" then
return API.GetSettingBool("NP_QueueSpellsOnCooldown")
end
-- Default: all queuing enabled
return API.GetSettingBool("NP_QueueCastTimeSpells")
end
-- Check if ANY spell queuing is enabled
function API.IsAnyQueueingEnabled()
if not API.features.hasNampower then
return false
end
-- Check all spell type queuing settings
return API.GetSettingBool("NP_QueueCastTimeSpells")
or API.GetSettingBool("NP_QueueInstantSpells")
or API.GetSettingBool("NP_QueueChannelingSpells")
or API.GetSettingBool("NP_QueueOnSwingSpells")
end
-- Get the queue window for a given spell type (in seconds)
function API.GetQueueWindow(spellType)
local ms = 500 -- default
if spellType == "channel" or spellType == "channeling" then
ms = API.GetSettingNumber("NP_ChannelQueueWindowMs")
elseif spellType == "targeting" or spellType == "aoe" then
ms = API.GetSettingNumber("NP_TargetingQueueWindowMs")
elseif spellType == "cooldown" then
ms = API.GetSettingNumber("NP_CooldownQueueWindowMs")
elseif spellType == "onswing" or spellType == "swing" then
ms = API.GetSettingNumber("NP_OnSwingBufferCooldownMs")
else
ms = API.GetSettingNumber("NP_SpellQueueWindowMs")
end
return ms / 1000 -- Convert to seconds
end
--------------------------------------------------------------------------------
-- SPELL RECORD API (GetSpellRec / GetSpellRecField)
--------------------------------------------------------------------------------
-- Cache for spell records
API.spellRecCache = {}
-- Get full spell record data
-- Returns nil if spell not found or API unavailable
function API.GetSpellRecord(spellId)
if not spellId or spellId == 0 then return nil end
-- Check cache first
if API.spellRecCache[spellId] then
return API.spellRecCache[spellId]
end
-- Use native function if available
if GetSpellRec then
local rec = GetSpellRec(spellId)
if rec then
API.spellRecCache[spellId] = rec
return rec
end
end
return nil
end
-- Get a specific field from spell record
-- Returns nil if not found, raises error if field name invalid (native behavior)
function API.GetSpellField(spellId, fieldName)
if not spellId or spellId == 0 then return nil end
-- Use native function if available (more efficient for single field)
if GetSpellRecField then
local success, result = pcall(GetSpellRecField, spellId, fieldName)
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
-- Fallback to full record lookup
local rec = API.GetSpellRecord(spellId)
if rec then
return rec[fieldName]
end
return nil
end
-- Get spell name by ID (enhanced, uses new API when available)
function API.GetSpellNameById(spellId)
if not spellId or spellId == 0 then return nil end
-- Try GetSpellNameAndRankForId first (Nampower function)
if GetSpellNameAndRankForId then
local name, rank = GetSpellNameAndRankForId(spellId)
if name then return name, rank end
end
-- Fall back to SpellInfo (SuperWoW)
if SpellInfo then
local name = SpellInfo(spellId)
if name then return name end
end
-- Fall back to GetSpellRecField
if GetSpellRecField then
local name = GetSpellRecField(spellId, "name")
local rank = GetSpellRecField(spellId, "rank")
if name then return name, rank end
end
return nil
end
-- Get spell cast time in seconds
function API.GetSpellCastTime(spellId)
local castTime = API.GetSpellField(spellId, "castTime")
if castTime then
return castTime / 1000 -- Convert from ms to seconds
end
return nil
end
-- Get spell school (0=Physical, 1=Holy, 2=Fire, 3=Nature, 4=Frost, 5=Shadow, 6=Arcane)
function API.GetSpellSchool(spellId)
return API.GetSpellField(spellId, "school")
end
-- Get spell mana cost
function API.GetSpellManaCost(spellId)
return API.GetSpellField(spellId, "manaCost")
end
-- Get spell range (max range in yards)
function API.GetSpellRange(spellId)
local rangeMax = API.GetSpellField(spellId, "rangeMax")
if rangeMax then
return rangeMax / 10 -- Convert from game units to yards
end
return nil
end
-- Get spell cooldown from record (base cooldown, not current)
function API.GetSpellBaseCooldown(spellId)
local recoveryTime = API.GetSpellField(spellId, "recoveryTime")
if recoveryTime then
return recoveryTime / 1000 -- Convert from ms to seconds
end
return nil
end
--------------------------------------------------------------------------------
-- ITEM STATS API (GetItemStats / GetItemStatsField)
--------------------------------------------------------------------------------
-- Cache for item stats
API.itemStatsCache = {}
-- Get full item stats data
function API.GetItemRecord(itemId)
if not itemId or itemId == 0 then return nil end
-- Check cache first
if API.itemStatsCache[itemId] then
return API.itemStatsCache[itemId]
end
-- Use native function if available (wrapped in pcall for safety)
if GetItemStats then
local ok, stats = pcall(GetItemStats, itemId)
if ok and stats then
API.itemStatsCache[itemId] = stats
return stats
end
end
return nil
end
-- Get a specific field from item stats
function API.GetItemField(itemId, fieldName)
if not itemId or itemId == 0 then return nil end
-- Use native function if available (more efficient)
if GetItemStatsField then
local success, result = pcall(GetItemStatsField, itemId, fieldName)
if success then
return result
end
return nil
end
-- Fallback to full record lookup
local stats = API.GetItemRecord(itemId)
if stats then
return stats[fieldName]
end
return nil
end
-- Get item level
function API.GetItemLevel(itemId)
-- Use dedicated function if available
if GetItemLevel then
local success, result = pcall(GetItemLevel, itemId)
if success then return result end
end
return API.GetItemField(itemId, "itemLevel")
end
-- Get item name
function API.GetItemName(itemId)
return API.GetItemField(itemId, "displayName")
end
-- Get item quality (0=Poor, 1=Common, 2=Uncommon, 3=Rare, 4=Epic, 5=Legendary)
function API.GetItemQuality(itemId)
return API.GetItemField(itemId, "quality")
end
-- Get weapon speed in seconds
function API.GetWeaponSpeed(itemId)
local delay = API.GetItemField(itemId, "delay")
if delay then
return delay / 1000
end
return nil
end
-- Get item inventory type (equipment slot)
-- Returns: inventoryType number, or nil if not equippable
-- Common types: 0=Non-equip, 1=Head, 2=Neck, 3=Shoulder, 4=Shirt, 5=Chest,
-- 6=Waist, 7=Legs, 8=Feet, 9=Wrist, 10=Hands, 11=Finger, 12=Trinket,
-- 13=One-Hand, 14=Shield, 15=Ranged, 16=Back, 17=Two-Hand,
-- 18=Bag, 20=Robe, 21=Main Hand, 22=Off Hand, 23=Holdable,
-- 24=Ammo, 25=Thrown, 26=Ranged Right, 28=Relic
function API.GetItemInventoryType(itemId)
local ok, result = pcall(API.GetItemField, itemId, "inventoryType")
if ok then return result end
return nil
end
-- Convert inventoryType to equipment slot ID(s)
-- Returns slotId, altSlotId (for rings/trinkets)
API.inventoryTypeToSlot = {
[1] = 1, -- Head -> HeadSlot
[2] = 2, -- Neck -> NeckSlot
[3] = 3, -- Shoulder -> ShoulderSlot
[4] = 4, -- Shirt -> ShirtSlot
[5] = 5, -- Chest -> ChestSlot
[6] = 6, -- Waist -> WaistSlot
[7] = 7, -- Legs -> LegsSlot
[8] = 8, -- Feet -> FeetSlot
[9] = 9, -- Wrist -> WristSlot
[10] = 10, -- Hands -> HandsSlot
[11] = {11, 12}, -- Finger -> Finger0Slot or Finger1Slot
[12] = {13, 14}, -- Trinket -> Trinket0Slot or Trinket1Slot
[13] = 16, -- One-Hand -> MainHandSlot (can also go off-hand)
[14] = 17, -- Shield -> SecondaryHandSlot
[15] = 18, -- Ranged -> RangedSlot
[16] = 15, -- Back -> BackSlot
[17] = 16, -- Two-Hand -> MainHandSlot
[20] = 5, -- Robe -> ChestSlot
[21] = 16, -- Main Hand -> MainHandSlot
[22] = 17, -- Off Hand -> SecondaryHandSlot
[23] = 17, -- Holdable -> SecondaryHandSlot
[24] = 0, -- Ammo -> AmmoSlot
[25] = 18, -- Thrown -> RangedSlot
[26] = 18, -- Ranged Right -> RangedSlot
[28] = 18, -- Relic -> RangedSlot
}
-- Get equipment slot ID for an item
-- Returns slotId, altSlotId for items that can go in multiple slots
function API.GetItemEquipSlot(itemId)
local ok, invType = pcall(API.GetItemInventoryType, itemId)
if not ok or not invType or invType == 0 then return nil end
local slotInfo = API.inventoryTypeToSlot[invType]
if type(slotInfo) == "table" then
return slotInfo[1], slotInfo[2] -- Primary and alt slot
end
return slotInfo, nil
end
--------------------------------------------------------------------------------
-- UNIT DATA API (GetUnitData / GetUnitField)
--------------------------------------------------------------------------------
-- Get full unit data (no caching - unit data changes frequently)
function API.GetUnitRecord(unitToken)
if not unitToken then return nil end
if GetUnitData then
return GetUnitData(unitToken)
end
return nil
end
-- Get a specific unit field
function API.GetUnitFieldValue(unitToken, fieldName)
if not unitToken then return nil end
-- Use native function if available (more efficient)
if GetUnitField then
local success, result = pcall(GetUnitField, unitToken, fieldName)
if success then
return result
end
return nil
end
-- Fallback to full record
local data = API.GetUnitRecord(unitToken)
if data then
return data[fieldName]
end
return nil
end
-- Get unit's current auras as spell IDs
function API.GetUnitAuras(unitToken)
return API.GetUnitFieldValue(unitToken, "aura")
end
-- Get unit resistances table
function API.GetUnitResistances(unitToken)
return API.GetUnitFieldValue(unitToken, "resistances")
end
-- Get specific resistance value
-- school: 1=Armor, 2=Holy, 3=Fire, 4=Nature, 5=Frost, 6=Shadow, 7=Arcane
function API.GetUnitResistance(unitToken, school)
local resistances = API.GetUnitResistances(unitToken)
if resistances and school then
return resistances[school]
end
return nil
end
-- Check if unit has a specific aura by spell ID
function API.UnitHasAura(unitToken, spellId)
local auras = API.GetUnitAuras(unitToken)
if not auras or not spellId then return false end
for _, auraId in ipairs(auras) do
if auraId == spellId then
return true
end
end
return false
end
--------------------------------------------------------------------------------
-- INVENTORY & EQUIPMENT API (v2.18+)
--------------------------------------------------------------------------------
-- Find an item in player's inventory by ID or name
-- 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
-- nil, nil = item not found
function API.FindPlayerItemSlot(itemIdOrName)
if not itemIdOrName then return nil, nil end
-- Use native function if available (v2.18+)
if FindPlayerItemSlot then
return FindPlayerItemSlot(itemIdOrName)
end
-- Fallback: manual search for older versions
-- This is much slower but works without Nampower 2.18
local itemName = itemIdOrName
if type(itemIdOrName) == "number" then
-- Try to get item name from ID
itemName = API.GetItemName(itemIdOrName)
if not itemName then
-- Try GetItemInfo as fallback (only works for cached items)
itemName = GetItemInfo(itemIdOrName)
end
if not itemName then
return nil, nil -- Can't resolve item ID
end
end
-- Search equipped items first (slots 0-18, but WoW uses 1-19 in API)
for slot = 0, 18 do
local link = GetInventoryItemLink("player", slot + 1) -- GetInventoryItemLink is 1-indexed
if link then
local name = GetItemInfo(link)
if name and string.lower(name) == string.lower(itemName) then
return nil, slot -- Equipped
end
end
end
-- Search bags (0 = backpack, 1-4 = bags)
for bag = 0, 4 do
local numSlots = GetContainerNumSlots(bag)
for slot = 1, numSlots do
local link = GetContainerItemLink(bag, slot)
if link then
local name = GetItemInfo(link)
if name and string.lower(name) == string.lower(itemName) then
return bag, slot
end
end
end
end
return nil, nil -- Not found
end
-- Get all equipped items for a unit
-- 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
return GetEquippedItems(unitToken)
end
-- Fallback: manual lookup for player only
if unitToken ~= "player" then
return nil -- Can't inspect other units without native API
end
local items = {}
for slot = 0, 18 do
local link = GetInventoryItemLink("player", slot + 1)
if link then
local _, _, itemId = string.find(link, "item:(%d+)")
if itemId then
items[slot] = {
itemId = tonumber(itemId),
-- Other fields not available without native API
}
end
end
end
return items
end
-- Get equipped item info for a specific slot
-- 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
return GetEquippedItem(unitToken, slot)
end
-- Fallback: manual lookup for player
if unitToken ~= "player" then
return nil
end
local link = GetInventoryItemLink("player", slot + 1) -- 1-indexed
if link then
local _, _, itemId = string.find(link, "item:(%d+)")
if itemId then
return {
itemId = tonumber(itemId),
}
end
end
return nil
end
-- Get all items in all bags
-- Returns nested table: bagIndex -> { slot -> itemInfo }
function API.GetBagItems()
-- Use native function if available (v2.18+)
if GetBagItems then
return GetBagItems()
end
-- Fallback: manual enumeration
local bags = {}
for bag = 0, 4 do
local numSlots = GetContainerNumSlots(bag)
if numSlots > 0 then
bags[bag] = {}
for slot = 1, numSlots do
local link = GetContainerItemLink(bag, slot)
if link then
local _, _, itemId = string.find(link, "item:(%d+)")
local _, count = GetContainerItemInfo(bag, slot)
if itemId then
bags[bag][slot] = {
itemId = tonumber(itemId),
stackCount = count or 1,
}
end
end
end
end
end
return bags
end
-- Get item info for a specific bag slot
function API.GetBagItem(bagIndex, slot)
-- Use native function if available (v2.18+)
if GetBagItem then
return GetBagItem(bagIndex, slot)
end
-- Fallback: manual lookup
local link = GetContainerItemLink(bagIndex, slot)
if link then
local _, _, itemId = string.find(link, "item:(%d+)")
local _, count = GetContainerItemInfo(bagIndex, slot)
if itemId then
return {
itemId = tonumber(itemId),
stackCount = count or 1,
}
end
end
return nil
end
--------------------------------------------------------------------------------
-- OPTIMIZED ITEM FINDING (v2.18+)
-- These functions provide fast item lookup compatible with CleveRoids.Items format
--------------------------------------------------------------------------------
-- Fast item lookup using v2.18 FindPlayerItemSlot
-- Returns item info table compatible with CleveRoids.Items format:
-- { name, itemId, inventoryID } for equipped items
-- { name, itemId, bagID, slot } for bag items
-- Returns nil if item not found
function API.FindItemFast(itemIdOrName)
if not itemIdOrName then return nil end
-- Use native v2.18 function if available (much faster than Lua scan)
if FindPlayerItemSlot then
local bag, slot = FindPlayerItemSlot(itemIdOrName)
if slot then
local itemInfo = {}
if bag == nil then
-- Equipped item (slot is equipment slot 0-18)
-- Nampower uses 0-indexed, WoW API uses 1-indexed for GetInventoryItemLink
local invSlot = slot + 1
local link = GetInventoryItemLink("player", invSlot)
if link then
local _, _, itemId = string.find(link, "item:(%d+)")
local _, _, name = string.find(link, "|h%[(.-)%]|h")
itemInfo.inventoryID = invSlot
itemInfo.itemId = tonumber(itemId)
itemInfo.name = name
itemInfo._source = "nampower_equipped"
return itemInfo
end
else
-- Bag item
local link = GetContainerItemLink(bag, slot)
if link then
local _, _, itemId = string.find(link, "item:(%d+)")
local _, _, name = string.find(link, "|h%[(.-)%]|h")
local _, count = GetContainerItemInfo(bag, slot)
itemInfo.bagID = bag
itemInfo.slot = slot
itemInfo.itemId = tonumber(itemId)
itemInfo.name = name
itemInfo.count = count or 1
itemInfo._source = "nampower_bag"
return itemInfo
end
end
end
return nil -- Not found via native lookup
end
-- Fallback: no v2.18 API, return nil (caller should use existing methods)
return nil
end
-- Check if an item is equipped in a specific slot (fast)
-- Returns true if item matches by ID or name
function API.IsItemInSlot(itemIdOrName, inventorySlot)
if not itemIdOrName or not inventorySlot then return false end
-- Use native v2.18 GetEquippedItem if available
if GetEquippedItem then
local slotInfo = GetEquippedItem("player", inventorySlot - 1) -- 0-indexed
if slotInfo and slotInfo.itemId then
local checkId = tonumber(itemIdOrName)
if checkId then
return slotInfo.itemId == checkId
else
-- Check by name
local itemName = API.GetItemName(slotInfo.itemId)
if itemName then
return string.lower(itemName) == string.lower(itemIdOrName)
end
end
end
return false
end
-- Fallback: use GetInventoryItemLink
local link = GetInventoryItemLink("player", inventorySlot)
if not link then return false end
local checkId = tonumber(itemIdOrName)
if checkId then
local _, _, currentId = string.find(link, "item:(%d+)")
return currentId and tonumber(currentId) == checkId
else
local _, _, currentName = string.find(link, "|h%[(.-)%]|h")
return currentName and string.lower(currentName) == string.lower(itemIdOrName)
end
end
-- Check if item is equipped anywhere (fast)
-- Returns inventorySlot if equipped, nil otherwise
function API.FindEquippedItem(itemIdOrName)
if not itemIdOrName then return nil end
-- Use native v2.18 FindPlayerItemSlot if available
if FindPlayerItemSlot then
local bag, slot = FindPlayerItemSlot(itemIdOrName)
if bag == nil and slot then
return slot + 1 -- Convert to 1-indexed WoW API slot
end
return nil -- Not equipped (might be in bag or not found)
end
-- Fallback: scan equipped slots
local checkId = tonumber(itemIdOrName)
local checkName = (not checkId) and string.lower(itemIdOrName) or nil
for invSlot = 1, 19 do
local link = GetInventoryItemLink("player", invSlot)
if link then
if checkId then
local _, _, currentId = string.find(link, "item:(%d+)")
if currentId and tonumber(currentId) == checkId then
return invSlot
end
elseif checkName then
local _, _, currentName = string.find(link, "|h%[(.-)%]|h")
if currentName and string.lower(currentName) == checkName then
return invSlot
end
end
end
end
return nil
end
-- Find item in bags only (not equipped) - fast
-- Returns bag, slot if found, nil otherwise
function API.FindBagItem(itemIdOrName)
if not itemIdOrName then return nil, nil end
-- Use native v2.18 FindPlayerItemSlot if available
if FindPlayerItemSlot then
local bag, slot = FindPlayerItemSlot(itemIdOrName)
if bag ~= nil and slot then
return bag, slot
end
return nil, nil -- Not in bags (might be equipped or not found)
end
-- Fallback: scan bags
local checkId = tonumber(itemIdOrName)
local checkName = (not checkId) and string.lower(itemIdOrName) or nil
for bag = 0, 4 do
local numSlots = GetContainerNumSlots(bag) or 0
for slot = 1, numSlots do
local link = GetContainerItemLink(bag, slot)
if link then
if checkId then
local _, _, currentId = string.find(link, "item:(%d+)")
if currentId and tonumber(currentId) == checkId then
return bag, slot
end
elseif checkName then
local _, _, currentName = string.find(link, "|h%[(.-)%]|h")
if currentName and string.lower(currentName) == checkName then
return bag, slot
end
end
end
end
end
return nil, nil
end
-- Use an item by ID or name (fast lookup)
-- Returns true if item was used, false otherwise
function API.UseItem(itemIdOrName)
if not itemIdOrName then return false end
-- Try native v2.18 lookup first
local itemInfo = API.FindItemFast(itemIdOrName)
if itemInfo then
ClearCursor()
if itemInfo.inventoryID then
UseInventoryItem(itemInfo.inventoryID)
return true
elseif itemInfo.bagID and itemInfo.slot then
UseContainerItem(itemInfo.bagID, itemInfo.slot)
return true
end
end
-- No v2.18 API or item not found via native lookup
return false
end
-- Equip an item from bags to a specific slot (fast lookup)
-- Returns true if equip was attempted, false if item not found
function API.EquipItem(itemIdOrName, targetSlot)
if not itemIdOrName then return false end
-- Check if already equipped in target slot
if targetSlot and API.IsItemInSlot(itemIdOrName, targetSlot) then
return true -- Already equipped
end
-- Find the item
local itemInfo = API.FindItemFast(itemIdOrName)
if not itemInfo then return false end
-- If already equipped (but not in target slot), need to swap
if itemInfo.inventoryID then
if targetSlot and itemInfo.inventoryID ~= targetSlot then
-- Item equipped in wrong slot - pick up and move
ClearCursor()
PickupInventoryItem(itemInfo.inventoryID)
if CursorHasItem and CursorHasItem() then
EquipCursorItem(targetSlot)
ClearCursor()
return true
end
end
return true -- Already equipped (and no target slot specified)
end
-- Item in bag - equip it
if itemInfo.bagID and itemInfo.slot then
ClearCursor()
PickupContainerItem(itemInfo.bagID, itemInfo.slot)
if CursorHasItem and CursorHasItem() then
if targetSlot then
EquipCursorItem(targetSlot)
else
-- Auto-equip to appropriate slot
AutoEquipCursorItem()
end
ClearCursor()
return true
end
end
return false
end
--------------------------------------------------------------------------------
-- CAST INFO API (v2.18+)
--------------------------------------------------------------------------------
-- Get detailed information about current cast/channel
-- 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
return GetCastInfo()
end
-- Fallback: use GetCurrentCastingInfo (less detailed)
if GetCurrentCastingInfo then
local castId, visId, autoId, casting, channeling, onswing, autoattack = GetCurrentCastingInfo()
if castId and castId > 0 then
return {
spellId = castId,
castType = channeling == 1 and 3 or 0, -- 3 = CHANNEL, 0 = NORMAL
-- Timing info not available without native GetCastInfo
}
end
end
return nil
end
-- Check if currently casting (convenience wrapper)
function API.IsCasting()
local info = API.GetCastInfo()
return info ~= nil
end
-- Get GCD remaining in milliseconds
function API.GetGCDRemainingMs()
local info = API.GetCastInfo()
if info and info.gcdRemainingMs then
return info.gcdRemainingMs
end
return 0
end
-- Get cast remaining in milliseconds
function API.GetCastRemainingMs()
local info = API.GetCastInfo()
if info and info.castRemainingMs then
return info.castRemainingMs
end
return 0
end
--------------------------------------------------------------------------------
-- COOLDOWN API (v2.18+)
--------------------------------------------------------------------------------
-- Get detailed cooldown information for a spell
-- Returns table with: isOnCooldown, cooldownRemainingMs,
-- individual cooldown: individualStartS, individualDurationMs, individualRemainingMs, isOnIndividualCooldown
-- category cooldown: categoryId, categoryStartS, categoryDurationMs, categoryRemainingMs, isOnCategoryCooldown
-- GCD: gcdCategoryId, gcdCategoryStartS, gcdCategoryDurationMs, gcdCategoryRemainingMs, isOnGcdCategoryCooldown
function API.GetSpellCooldownInfo(spellId)
if not spellId or spellId == 0 then return nil end
-- Use native function if available (v2.18+)
if GetSpellIdCooldown then
return GetSpellIdCooldown(spellId)
end
-- Fallback: use GetSpellCooldown (less detailed)
local spellName = API.GetSpellNameById(spellId)
if spellName then
local start, duration = GetSpellCooldown(spellName, BOOKTYPE_SPELL)
if start and start > 0 then
local remaining = (start + duration) - GetTime()
if remaining > 0 then
return {
isOnCooldown = 1,
cooldownRemainingMs = remaining * 1000,
}
end
end
end
return { isOnCooldown = 0, cooldownRemainingMs = 0 }
end
-- Get detailed cooldown information for an item
function API.GetItemCooldownInfo(itemId)
if not itemId or itemId == 0 then return nil end
-- Use native function if available (v2.18+)
if GetItemIdCooldown then
return GetItemIdCooldown(itemId)
end
-- Fallback: use GetItemCooldown (less detailed)
local start, duration = GetItemCooldown(itemId)
if start and start > 0 then
local remaining = (start + duration) - GetTime()
if remaining > 0 then
return {
isOnCooldown = 1,
cooldownRemainingMs = remaining * 1000,
}
end
end
return { isOnCooldown = 0, cooldownRemainingMs = 0 }
end
-- Check if a spell is on cooldown (ignoring GCD)
function API.IsSpellOnCooldown(spellId, ignoreGCD)
local info = API.GetSpellCooldownInfo(spellId)
if not info then return false end
if ignoreGCD then
-- Only check individual and category cooldowns
return (info.isOnIndividualCooldown == 1) or (info.isOnCategoryCooldown == 1)
end
return info.isOnCooldown == 1
end
-- Get remaining cooldown for a spell in seconds
function API.GetSpellCooldownRemaining(spellId)
local info = API.GetSpellCooldownInfo(spellId)
if info and info.cooldownRemainingMs then
return info.cooldownRemainingMs / 1000
end
return 0
end
--------------------------------------------------------------------------------
-- CHANNEL CONTROL API (v2.18+)
--------------------------------------------------------------------------------
-- Stop channeling early on the next tick
-- Only works if NP_QueueChannelingSpells is enabled
function API.StopChannelNextTick()
if ChannelStopCastingNextTick then
ChannelStopCastingNextTick()
return true
end
return false
end
--------------------------------------------------------------------------------
-- EVENT CONSTANTS (v2.18+)
--------------------------------------------------------------------------------
-- Spell queue event codes
API.QUEUE_EVENT = {
ON_SWING_QUEUED = 0,
ON_SWING_QUEUE_POPPED = 1,
NORMAL_QUEUED = 2,
NORMAL_QUEUE_POPPED = 3,
NON_GCD_QUEUED = 4,
NON_GCD_QUEUE_POPPED = 5,
}
-- Spell cast event types
API.CAST_TYPE = {
NORMAL = 1,
NON_GCD = 2,
ON_SWING = 3,
CHANNEL = 4,
TARGETING = 5,
TARGETING_NON_GCD = 6,
}
-- Buff/debuff event names (v2.18+)
API.AURA_EVENTS = {
"BUFF_ADDED_SELF",
"BUFF_REMOVED_SELF",
"BUFF_ADDED_OTHER",
"BUFF_REMOVED_OTHER",
"DEBUFF_ADDED_SELF",
"DEBUFF_REMOVED_SELF",
"DEBUFF_ADDED_OTHER",
"DEBUFF_REMOVED_OTHER",
}
-- Spell school constants (for reference)
API.SPELL_SCHOOL = {
PHYSICAL = 0,
HOLY = 1,
FIRE = 2,
NATURE = 3,
FROST = 4,
SHADOW = 5,
ARCANE = 6,
}
-- Resistance indices (1-indexed for Lua tables)
API.RESISTANCE_INDEX = {
ARMOR = 1,
HOLY = 2,
FIRE = 3,
NATURE = 4,
FROST = 5,
SHADOW = 6,
ARCANE = 7,
}
--------------------------------------------------------------------------------
-- SPELL MODIFIERS API (GetSpellModifiers)
--------------------------------------------------------------------------------
-- Modifier type constants
API.MODIFIER_DAMAGE = 0
API.MODIFIER_DURATION = 1
API.MODIFIER_THREAT = 2
API.MODIFIER_ATTACK_POWER = 3
API.MODIFIER_CHARGES = 4
API.MODIFIER_RANGE = 5
API.MODIFIER_RADIUS = 6
API.MODIFIER_CRITICAL_CHANCE = 7
API.MODIFIER_ALL_EFFECTS = 8
API.MODIFIER_NOT_LOSE_CASTING_TIME = 9
API.MODIFIER_CASTING_TIME = 10
API.MODIFIER_COOLDOWN = 11
API.MODIFIER_SPEED = 12
API.MODIFIER_COST = 14
API.MODIFIER_CRIT_DAMAGE_BONUS = 15
API.MODIFIER_RESIST_MISS_CHANCE = 16
API.MODIFIER_JUMP_TARGETS = 17
API.MODIFIER_CHANCE_OF_SUCCESS = 18
API.MODIFIER_ACTIVATION_TIME = 19
API.MODIFIER_EFFECT_PAST_FIRST = 20
API.MODIFIER_CASTING_TIME_OLD = 21
API.MODIFIER_DOT = 22
API.MODIFIER_HASTE = 23
API.MODIFIER_SPELL_BONUS_DAMAGE = 24
API.MODIFIER_MULTIPLE_VALUE = 27
API.MODIFIER_RESIST_DISPEL_CHANCE = 28
-- Get spell modifiers
-- Returns: flatMod, percentMod, hasModifier
function API.GetModifiers(spellId, modifierType)
if not GetSpellModifiers then
return 0, 0, false
end
if not spellId or spellId == 0 then
return 0, 0, false
end
local flat, percent, ret = GetSpellModifiers(spellId, modifierType)
return flat or 0, percent or 0, ret and ret ~= 0
end
-- Get duration modifier for a spell (useful for debuff tracking)
-- Returns modified duration given base duration
function API.GetModifiedDuration(spellId, baseDuration)
if not baseDuration then return nil end
local flat, percent, hasModifier = API.GetModifiers(spellId, API.MODIFIER_DURATION)
if not hasModifier then
return baseDuration
end
-- Apply flat modifier first, then percentage
local modified = baseDuration + flat
if percent ~= 0 then
modified = modified * (1 + percent / 100)
end
return modified
end
-- Get damage modifier for a spell
function API.GetDamageModifier(spellId)
local flat, percent = API.GetModifiers(spellId, API.MODIFIER_DAMAGE)
return flat, percent
end
-- Get cooldown modifier for a spell
function API.GetCooldownModifier(spellId)
local flat, percent = API.GetModifiers(spellId, API.MODIFIER_COOLDOWN)
return flat, percent
end
-- Get cast time modifier for a spell
function API.GetCastTimeModifier(spellId)
local flat, percent = API.GetModifiers(spellId, API.MODIFIER_CASTING_TIME)
return flat, percent
end
-- Get cost modifier for a spell
function API.GetCostModifier(spellId)
local flat, percent = API.GetModifiers(spellId, API.MODIFIER_COST)
return flat, percent
end
--------------------------------------------------------------------------------
-- ENHANCED SPELL LOOKUP (using new "spellId:number" and name syntax)
--------------------------------------------------------------------------------
-- Convert spell identifier to spellId:number format if needed
-- Input can be: spellSlot (number), "spellId:123", or "Spell Name"
function API.NormalizeSpellIdentifier(identifier, bookType)
if not identifier then return nil end
-- Already in spellId:number format
if type(identifier) == "string" and string.find(identifier, "^spellId:") then
return identifier
end
-- If it's a number and enhanced functions exist, keep as-is (slot) or convert to spellId
if type(identifier) == "number" then
if API.features.hasEnhancedSpellFunctions then
-- Could be a spell slot - check if we have GetSpellSlotTypeIdForName to get the ID
if identifier > 0 and identifier < 1000 then
-- Likely a spell slot, use as-is
return identifier, bookType or BOOKTYPE_SPELL
else
-- Likely a spell ID, convert to spellId:number format
return "spellId:" .. identifier
end
end
return identifier, bookType
end
-- It's a spell name
if type(identifier) == "string" then
-- If enhanced functions available, pass name directly
if API.features.hasEnhancedSpellFunctions then
return identifier
end
-- Fall back to manual lookup
local spell = CleveRoids.GetSpell and CleveRoids.GetSpell(identifier)
if spell then
return spell.spellSlot, spell.bookType
end
end
return nil
end
-- Get spell texture (works with name, spellId:number, or slot)
function API.GetSpellTexture(identifier, bookType)
if API.features.hasEnhancedSpellFunctions then
-- Pass directly to native function
local id = API.NormalizeSpellIdentifier(identifier)
if id then
return GetSpellTexture(id, bookType)
end
else
-- Fall back to slot-based lookup
local slot, book = API.NormalizeSpellIdentifier(identifier, bookType)
if slot then
return GetSpellTexture(slot, book or BOOKTYPE_SPELL)
end
end
return nil
end
-- Get spell name (works with name, spellId:number, or slot)
function API.GetSpellName(identifier, bookType)
if API.features.hasEnhancedSpellFunctions then
local id = API.NormalizeSpellIdentifier(identifier)
if id then
return GetSpellName(id, bookType)
end
else
local slot, book = API.NormalizeSpellIdentifier(identifier, bookType)
if slot then
return GetSpellName(slot, book or BOOKTYPE_SPELL)
end
end
return nil
end
-- Get spell cooldown (works with name, spellId:number, or slot)
-- Returns: start, duration (same as native)
function API.GetSpellCooldown(identifier, bookType)
if API.features.hasEnhancedSpellFunctions then
local id = API.NormalizeSpellIdentifier(identifier)
if id then
return GetSpellCooldown(id, bookType)
end
else
local slot, book = API.NormalizeSpellIdentifier(identifier, bookType)
if slot then
return GetSpellCooldown(slot, book or BOOKTYPE_SPELL)
end
end
return nil
end
-- Check if spell is passive
function API.IsSpellPassive(identifier, bookType)
if API.features.hasEnhancedSpellFunctions then
local id = API.NormalizeSpellIdentifier(identifier)
if id then
return IsSpellPassive(id, bookType)
end
else
local slot, book = API.NormalizeSpellIdentifier(identifier, bookType)
if slot then
return IsSpellPassive(slot, book or BOOKTYPE_SPELL)
end
end
return nil
end
--------------------------------------------------------------------------------
-- SPELL ID LOOKUP UTILITIES
--------------------------------------------------------------------------------
-- Cache for spell ID lookups
API.spellIdCache = {}
-- Get spell ID from name (cached)
function API.GetSpellIdFromName(spellName)
if not spellName then return nil end
-- Check cache
if API.spellIdCache[spellName] then
return API.spellIdCache[spellName]
end
-- Use Nampower's GetSpellIdForName
if GetSpellIdForName then
local spellId = GetSpellIdForName(spellName)
if spellId and spellId > 0 then
API.spellIdCache[spellName] = spellId
return spellId
end
end
return nil
end
-- Get spell slot, book type, and ID from name
function API.GetSpellSlotInfo(spellName)
if not spellName then return nil end
-- Use Nampower's GetSpellSlotTypeIdForName
if GetSpellSlotTypeIdForName then
local slot, bookType, spellId = GetSpellSlotTypeIdForName(spellName)
if slot and slot > 0 then
return slot, bookType, spellId
end
end
-- Fallback to CleveRoids.GetSpell
if CleveRoids.GetSpell then
local spell = CleveRoids.GetSpell(spellName)
if spell then
local spellId = API.GetSpellIdFromName(spellName)
return spell.spellSlot, spell.bookType, spellId
end
end
return nil
end
--------------------------------------------------------------------------------
-- INITIALIZATION
--------------------------------------------------------------------------------
-- Initialize the API module
function API.Initialize()
-- Detect enhanced spell functions
DetectEnhancedSpellFunctions()
end
-- Clear caches (call on respec/spell change)
function API.ClearCaches()
API.spellRecCache = {}
API.itemStatsCache = {}
API.spellIdCache = {}
API.settingsCache = {}
API.spellTypeCache = {}
end
--------------------------------------------------------------------------------
-- CONVENIENCE WRAPPERS FOR EXISTING CODE
--------------------------------------------------------------------------------
-- Check if a spell is in range (wrapper around IsSpellInRange with fallbacks)
function API.IsSpellInRange(spellIdentifier, unit)
unit = unit or "target"
if not IsSpellInRange then
return nil -- Can't determine
end
-- Convert name to ID if needed
local checkValue = spellIdentifier
if type(spellIdentifier) == "string" and not string.find(spellIdentifier, "^spellId:") then
local spellId = API.GetSpellIdFromName(spellIdentifier)
if spellId and spellId > 0 then
checkValue = spellId
end
end
return IsSpellInRange(checkValue, unit)
end
-- Check if a spell is usable (wrapper around IsSpellUsable)
function API.IsSpellUsable(spellIdentifier)
if not _G.IsSpellUsable then
return nil
end
-- If enhanced functions available, pass directly
if API.features.hasEnhancedSpellFunctions then
return _G.IsSpellUsable(spellIdentifier)
end
-- Convert to ID if name
if type(spellIdentifier) == "string" then
local spellId = API.GetSpellIdFromName(spellIdentifier)
if spellId then
return _G.IsSpellUsable(spellId)
end
end
return _G.IsSpellUsable(spellIdentifier)
end
--------------------------------------------------------------------------------
-- SMART CASTING SYSTEM
--------------------------------------------------------------------------------
-- Spell type constants
API.SPELL_TYPE_UNKNOWN = 0
API.SPELL_TYPE_CAST = 1 -- Has cast time
API.SPELL_TYPE_INSTANT = 2 -- Instant, on GCD
API.SPELL_TYPE_CHANNEL = 3 -- Channeled spell
API.SPELL_TYPE_ON_SWING = 4 -- Next-melee (Heroic Strike, etc.)
API.SPELL_TYPE_NON_GCD = 5 -- Instant, not on GCD (trinkets, etc.)
-- Known on-swing spells (by name, localized via CleveRoids.Localized when available)
API.onSwingSpells = {
["Heroic Strike"] = true,
["Cleave"] = true,
["Maul"] = true,
["Slam"] = true,
["Raptor Strike"] = true,
["Mongoose Bite"] = true,
}
-- Known channeled spells (by name)
API.channeledSpells = {
["Arcane Missiles"] = true,
["Blizzard"] = true,
["Drain Life"] = true,
["Drain Mana"] = true,
["Drain Soul"] = true,
["Evocation"] = true,
["Health Funnel"] = true,
["Hellfire"] = true,
["Hurricane"] = true,
["Mind Flay"] = true,
["Rain of Fire"] = true,
["Tranquility"] = true,
["Volley"] = true,
["Mind Soothe"] = true,
["Mind Vision"] = true,
["First Aid"] = true, -- Bandaging
}
-- Cache for spell type lookups
API.spellTypeCache = {}
-- Determine spell type for a given spell
-- Returns: SPELL_TYPE_* constant
function API.GetSpellType(spellName)
if not spellName then return API.SPELL_TYPE_UNKNOWN end
-- Strip rank from name for cache lookup
local baseName = string.gsub(spellName, "%s*%([Rr]ank%s*%d+%)%s*$", "")
-- Check cache first
if API.spellTypeCache[baseName] then
return API.spellTypeCache[baseName]
end
local spellType = API.SPELL_TYPE_UNKNOWN
-- Check known on-swing spells first (highest priority)
if API.onSwingSpells[baseName] then
spellType = API.SPELL_TYPE_ON_SWING
API.spellTypeCache[baseName] = spellType
return spellType
end
-- Check known channeled spells
if API.channeledSpells[baseName] then
spellType = API.SPELL_TYPE_CHANNEL
API.spellTypeCache[baseName] = spellType
return spellType
end
-- Try to use GetSpellRec for accurate detection
local spellId = API.GetSpellIdFromName(spellName)
if spellId and spellId > 0 and API.features.hasGetSpellRec then
local rec = API.GetSpellRecord(spellId)
if rec then
-- Check for channeled via attributes
-- SPELL_ATTR_EX_CHANNELED = 4 (0x00000004) in attributesEx
local attrEx = rec.attributesEx or 0
if bit and bit.band(attrEx, 4) ~= 0 then
spellType = API.SPELL_TYPE_CHANNEL
API.spellTypeCache[baseName] = spellType
return spellType
end
-- Check cast time
local castTime = rec.castTime or 0
if castTime > 0 then
spellType = API.SPELL_TYPE_CAST
else
-- Instant spell - check if it's on GCD
-- startRecoveryTime > 0 means it triggers GCD
local recoveryTime = rec.startRecoveryTime or 0
if recoveryTime > 0 then
spellType = API.SPELL_TYPE_INSTANT
else
spellType = API.SPELL_TYPE_NON_GCD
end
end
API.spellTypeCache[baseName] = spellType
return spellType
end
end
-- Fallback: assume instant if no other info
-- This is safe because Nampower will handle it correctly anyway
spellType = API.SPELL_TYPE_INSTANT
API.spellTypeCache[baseName] = spellType
return spellType
end
-- Get the queue setting name for a spell type
local function GetQueueSettingForType(spellType)
if spellType == API.SPELL_TYPE_CAST then
return "NP_QueueCastTimeSpells"
elseif spellType == API.SPELL_TYPE_INSTANT then
return "NP_QueueInstantSpells"
elseif spellType == API.SPELL_TYPE_CHANNEL then
return "NP_QueueChannelingSpells"
elseif spellType == API.SPELL_TYPE_ON_SWING then
return "NP_QueueOnSwingSpells"
elseif spellType == API.SPELL_TYPE_NON_GCD then
return "NP_QueueInstantSpells" -- Non-GCD uses instant setting
end
return nil
end
-- Check if queuing is enabled for a specific spell
function API.IsSpellQueueingEnabled(spellName)
if not API.features.hasSpellQueue then
return false
end
local spellType = API.GetSpellType(spellName)
local settingName = GetQueueSettingForType(spellType)
if settingName then
return API.GetSettingBool(settingName)
end
-- Default: use cast time spell setting
return API.GetSettingBool("NP_QueueCastTimeSpells")
end
-- Smart cast function that uses QueueSpellByName when appropriate
-- Returns: true if cast was attempted, false otherwise
-- Parameters:
-- spellName: The spell name (with optional rank)
-- target: Optional target unit (for SuperWoW CastSpellByName)
-- forceQueue: If true, always use QueueSpellByName (if available)
-- forceNoQueue: If true, never use QueueSpellByName
function API.SmartCast(spellName, target, forceQueue, forceNoQueue)
if not spellName then return false end
-- Determine whether to use queuing
local useQueue = false
if forceNoQueue then
useQueue = false
elseif forceQueue then
useQueue = API.features.hasSpellQueue
else
-- Check if queuing is enabled for this spell type
useQueue = API.IsSpellQueueingEnabled(spellName)
end
-- Check if we have a non-standard target (e.g., @mouseover)
-- 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)
-- Cast the spell
if useQueue and QueueSpellByName then
if hasNonStandardTarget then
if canQueueWithTarget then
-- Nampower v2.13+ supports target parameter in QueueSpellByName
QueueSpellByName(spellName, target)
return true
else
-- Older Nampower: can't queue with non-standard target
-- Fall through to CastSpellByName with target
end
else
-- No target or target is "target" - safe to use queue
QueueSpellByName(spellName)
return true
end
end
-- Use standard casting (either queuing wasn't possible or we need target support)
if CastSpellByName then
if target and CleveRoids.hasSuperwow then
-- SuperWoW supports target parameter
CastSpellByName(spellName, target)
else
CastSpellByName(spellName)
end
return true
end
return false
end
-- Cast without queuing (uses CastSpellByNameNoQueue if available)
function API.CastNoQueue(spellName, target)
if not spellName then return false end
-- Use Nampower's no-queue function if available
if CastSpellByNameNoQueue then
CastSpellByNameNoQueue(spellName)
return true
end
-- Fall back to standard cast
if CastSpellByName then
if target and CleveRoids.hasSuperwow then
CastSpellByName(spellName, target)
else
CastSpellByName(spellName)
end
return true
end
return false
end
-- Force queue a spell (uses QueueSpellByName directly)
function API.ForceQueue(spellName)
if not spellName then return false end
if QueueSpellByName then
QueueSpellByName(spellName)
return true
end
-- Fall back to standard cast if queue not available
if CastSpellByName then
CastSpellByName(spellName)
return true
end
return false
end
-- Queue a script to run after current cast (uses QueueScript if available)
function API.QueueScript(script, priority)
if not script then return false end
if QueueScript then
QueueScript(script, priority or 1)
return true
end
return false
end
-- Clear spell type cache (call on spec change)
function API.ClearSpellTypeCache()
API.spellTypeCache = {}
end
-- Expose API globally for other addons
_G.CleveRoidsNampowerAPI = API