--[[ 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 -- SpellRange.dbc lookup table: rangeIndex -> maxRange (in yards) -- From vanilla 1.12.1 client data (corrected based on actual spell ranges) API.SpellRangeTable = { [0] = 0, -- Self Only [1] = 5, -- Combat Range (Melee) [2] = 30, -- 30 yard range (Frostbolt, etc.) [3] = 35, -- 35 yard range [4] = 30, -- 30 yard range (Arcane Missiles, etc.) [5] = 40, -- 40 yard range [6] = 45, -- 45 yard range [7] = 100, -- Vision Range [8] = 20, -- 20 yard range [9] = 10, -- 10 yard range [10] = 8, -- 8 yard range [11] = 15, -- 15 yard range (Charge) [12] = 25, -- 25 yard range [13] = 100, -- Anywhere/Unlimited [14] = 0, -- Self Only (alternate) [15] = 80, -- 80 yard range (hunters) [16] = 18, -- 18 yard range [17] = 60, -- 60 yard range [18] = 5, -- Melee (alternate) [19] = 25, -- 25 yard range (alternate) [20] = 30, -- 30 yard range (alternate) [21] = 35, -- 35 yard range (alternate) [22] = 40, -- 40 yard range (alternate) [23] = 0, -- Touch [24] = 41, -- 41 yard range [25] = 10, -- 10 yard range (alternate) [26] = 50, -- 50 yard range [27] = 55, -- 55 yard range [28] = 65, -- 65 yard range [29] = 70, -- 70 yard range [30] = 50000, -- Unlimited [31] = 8, -- 8 yard range (alternate) [32] = 7, -- 7 yard range [33] = 11, -- 11 yard range [34] = 12, -- 12 yard range [35] = 28, -- 28 yard range [36] = 6, -- 6 yard range [37] = 13, -- 13 yard range [38] = 15, -- 15 yard range (alternate) [39] = 100, -- 100 yard range (alternate) [40] = 150, -- 150 yard range } -- Get spell range (max range in yards) function API.GetSpellRange(spellId) if not spellId or spellId == 0 then return nil end -- First try rangeMax (Nampower may provide this as a resolved field) local rangeMax = API.GetSpellField(spellId, "rangeMax") if rangeMax and rangeMax > 0 then return rangeMax / 10 -- Convert from game units to yards end -- Fallback: lookup rangeIndex in SpellRange table local rangeIndex = API.GetSpellField(spellId, "rangeIndex") if rangeIndex then local range = API.SpellRangeTable[rangeIndex] if range then return range end 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 (WoW API uses 1-19 for inventory slots) for slot = 1, 19 do local link = GetInventoryItemLink("player", slot) if link then local name = GetItemInfo(link) if name and string.lower(name) == string.lower(itemName) then return nil, slot -- Equipped (1-indexed, matches Nampower behavior) 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 - Nampower returns 1-indexed slot directly -- (matches WoW API's GetInventoryItemLink which is also 1-indexed) local invSlot = slot 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 -- Nampower returns 1-indexed slot directly 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" -- Convert name to ID if needed local spellId = nil local checkValue = spellIdentifier if type(spellIdentifier) == "string" and not string.find(spellIdentifier, "^spellId:") then spellId = API.GetSpellIdFromName(spellIdentifier) if spellId and spellId > 0 then checkValue = spellId end elseif type(spellIdentifier) == "number" then spellId = spellIdentifier end -- Try native IsSpellInRange first if IsSpellInRange then local result = IsSpellInRange(checkValue, unit) -- result == 1 (in range), 0 (out of range), -1 (invalid/non-unit-targeted), nil (error) -- Only use native result for definitive answers (0 or 1) if result == 0 or result == 1 then return result end -- For -1 (non-unit-targeted), check if it's a self-only spell -- Self-targeted spells (rangeIndex 0) should always be considered in range if result == -1 and spellId and spellId > 0 then local rangeIndex = API.GetSpellField(spellId, "rangeIndex") -- rangeIndex 0 = Self Only, 14 = Self Only (alternate), 23 = Touch if rangeIndex == 0 or rangeIndex == 14 or rangeIndex == 23 then return 1 -- Self-targeted spells are always in range end -- For other -1 cases (ground-targeted like Blizzard), fall through to UnitXP end end -- Fallback: Use spell range from record + UnitXP distance check -- This handles channeled spells where IsSpellInRange returns nil or -1 if spellId and spellId > 0 and CleveRoids.hasUnitXP and UnitExists(unit) then local spellRange = API.GetSpellRange(spellId) if spellRange and spellRange > 0 then local distance = UnitXP("distanceBetween", "player", unit) if distance then return (distance <= spellRange) and 1 or 0 end end end return nil -- Can't determine end -- Check if a spell is usable (wrapper around IsSpellUsable) -- Returns usable, oom on success; nil on error (spell not in spellbook) function API.IsSpellUsable(spellIdentifier) if not _G.IsSpellUsable then return nil end -- If enhanced functions available, pass directly if API.features.hasEnhancedSpellFunctions then local ok, usable, oom = pcall(_G.IsSpellUsable, spellIdentifier) if ok then return usable, oom end return nil end -- Convert to ID if name if type(spellIdentifier) == "string" then local spellId = API.GetSpellIdFromName(spellIdentifier) if spellId then local ok, usable, oom = pcall(_G.IsSpellUsable, spellId) if ok then return usable, oom end return nil end end local ok, usable, oom = pcall(_G.IsSpellUsable, spellIdentifier) if ok then return usable, oom end return nil 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