You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
343 lines
12 KiB
343 lines
12 KiB
--- = Background =
|
|
-- Blizzard's IsSpellInRange API has always been very limited - you either must have the name of the spell, or its spell book ID. Checking directly by spellID is simply not possible.
|
|
-- Now, in Mists of Pandaria, Blizzard changed the way that many talents and specialization spells work - instead of giving you a new spell when leaned, they replace existing spells. These replacement spells do not work with Blizzard's IsSpellInRange function whatsoever; this limitation is what prompted the creation of this lib.
|
|
-- = Usage =
|
|
-- **LibSpellRange-1.0** exposes an enhanced version of IsSpellInRange that:
|
|
-- * Allows ranged checking based on both spell name and spellID.
|
|
-- * Works correctly with replacement spells that will not work using Blizzard's IsSpellInRange method alone.
|
|
--
|
|
-- @class file
|
|
-- @name LibSpellRange-1.0.lua
|
|
|
|
local major = "SpellRange-1.0"
|
|
local minor = 21
|
|
|
|
assert(LibStub, format("%s requires LibStub.", major))
|
|
|
|
local Lib = LibStub:NewLibrary(major, minor)
|
|
if not Lib then return end
|
|
|
|
local tonumber = _G.tonumber
|
|
local strlower = _G.strlower
|
|
local wipe = _G.wipe
|
|
local type = _G.type
|
|
local select = _G.select
|
|
|
|
local GetSpellBookItemInfo = _G.GetSpellBookItemInfo or _G.C_SpellBook.GetSpellBookItemType
|
|
local GetSpellBookItemName = _G.GetSpellBookItemName or _G.C_SpellBook.GetSpellBookItemName
|
|
local GetSpellLink = _G.GetSpellLink or _G.C_Spell.GetSpellLink
|
|
local GetSpellInfo = _G.GetSpellInfo or _G.C_Spell.GetSpellName
|
|
|
|
local IsSpellInRange = _G.IsSpellInRange or function(id, unit)
|
|
local result = C_Spell.IsSpellInRange(id, unit)
|
|
if result == true then
|
|
return 1
|
|
elseif result == false then
|
|
return 0
|
|
end
|
|
return nil
|
|
end
|
|
local isTWWSpellInRange = IsSpellInRange ~= _G.IsSpellInRange
|
|
local IsSpellBookItemInRange = _G.IsSpellInRange or function(index, spellBank, unit)
|
|
local result = C_SpellBook.IsSpellBookItemInRange(index, spellBank, unit)
|
|
if result == true then
|
|
return 1
|
|
elseif result == false then
|
|
return 0
|
|
end
|
|
return nil
|
|
end
|
|
|
|
local SpellHasRange = _G.SpellHasRange or _G.C_Spell.SpellHasRange
|
|
local isTWWSpellHasRange = SpellHasRange ~= _G.SpellHasRange
|
|
local SpellBookHasRange = _G.SpellHasRange or _G.C_SpellBook.IsSpellBookItemInRange
|
|
|
|
local UnitExists = _G.UnitExists
|
|
local GetPetActionInfo = _G.GetPetActionInfo
|
|
local UnitIsUnit = _G.UnitIsUnit
|
|
|
|
local playerBook = _G.GetSpellBookItemName and "spell" or _G.Enum.SpellBookSpellBank.Player
|
|
local petBook = _G.GetSpellBookItemName and "pet" or _G.Enum.SpellBookSpellBank.Pet
|
|
|
|
-- isNumber is basically a tonumber cache for maximum efficiency
|
|
Lib.isNumber = Lib.isNumber or setmetatable({}, {
|
|
__mode = "kv",
|
|
__index = function(t, i)
|
|
local o = tonumber(i) or false
|
|
t[i] = o
|
|
return o
|
|
end})
|
|
local isNumber = Lib.isNumber
|
|
|
|
-- strlower cache for maximum efficiency
|
|
Lib.strlowerCache = Lib.strlowerCache or setmetatable(
|
|
{}, {
|
|
__index = function(t, i)
|
|
if not i then return end
|
|
local o
|
|
if type(i) == "number" then
|
|
o = i
|
|
else
|
|
o = strlower(i)
|
|
end
|
|
t[i] = o
|
|
return o
|
|
end,
|
|
}) local strlowerCache = Lib.strlowerCache
|
|
|
|
-- Matches lowercase player spell names to their spellBookID
|
|
Lib.spellsByName_spell = Lib.spellsByName_spell or {}
|
|
local spellsByName_spell = Lib.spellsByName_spell
|
|
|
|
-- Matches player spellIDs to their spellBookID
|
|
Lib.spellsByID_spell = Lib.spellsByID_spell or {}
|
|
local spellsByID_spell = Lib.spellsByID_spell
|
|
|
|
-- Matches lowercase pet spell names to their spellBookID
|
|
Lib.spellsByName_pet = Lib.spellsByName_pet or {}
|
|
local spellsByName_pet = Lib.spellsByName_pet
|
|
|
|
-- Matches pet spellIDs to their spellBookID
|
|
Lib.spellsByID_pet = Lib.spellsByID_pet or {}
|
|
local spellsByID_pet = Lib.spellsByID_pet
|
|
|
|
-- Matches pet spell names to their pet action bar slot
|
|
Lib.actionsByName_pet = Lib.actionsByName_pet or {}
|
|
local actionsByName_pet = Lib.actionsByName_pet
|
|
|
|
-- Matches pet spell IDs to their pet action bar slot
|
|
Lib.actionsById_pet = Lib.actionsById_pet or {}
|
|
local actionsById_pet = Lib.actionsById_pet
|
|
|
|
-- Caches whether a pet spell has been observed to ever have had a range.
|
|
-- Since this should never change for any particular spell,
|
|
-- it is not wiped.
|
|
Lib.petSpellHasRange = Lib.petSpellHasRange or {}
|
|
local petSpellHasRange = Lib.petSpellHasRange
|
|
|
|
-- Updates spellsByName and spellsByID
|
|
|
|
local GetNumSpellTabs = _G.GetNumSpellTabs or C_SpellBook.GetNumSpellBookSkillLines
|
|
local GetSpellTabInfo = _G.GetSpellTabInfo or function(index)
|
|
local skillLineInfo = C_SpellBook.GetSpellBookSkillLineInfo(index);
|
|
if skillLineInfo then
|
|
return skillLineInfo.name,
|
|
skillLineInfo.iconID,
|
|
skillLineInfo.itemIndexOffset,
|
|
skillLineInfo.numSpellBookItems,
|
|
skillLineInfo.isGuild,
|
|
skillLineInfo.offSpecID,
|
|
skillLineInfo.shouldHide,
|
|
skillLineInfo.specID;
|
|
end
|
|
end
|
|
|
|
local function UpdateBook(bookType)
|
|
local book = bookType == "spell" and playerBook or petBook
|
|
local max = 0
|
|
for i = 1, GetNumSpellTabs() do
|
|
local _, _, offs, numspells, _, specId = GetSpellTabInfo(i)
|
|
if specId == 0 then
|
|
max = offs + numspells
|
|
end
|
|
end
|
|
|
|
local spellsByName = Lib["spellsByName_" .. bookType]
|
|
local spellsByID = Lib["spellsByID_" .. bookType]
|
|
|
|
wipe(spellsByName)
|
|
wipe(spellsByID)
|
|
|
|
for spellBookID = 1, max do
|
|
local type, baseSpellID = GetSpellBookItemInfo(spellBookID, book)
|
|
|
|
if type == "SPELL" or type == "PETACTION" then
|
|
local currentSpellName, _, currentSpellID = GetSpellBookItemName(spellBookID, book)
|
|
if not currentSpellID then
|
|
local link = GetSpellLink(currentSpellName)
|
|
currentSpellID = tonumber(link and link:gsub("|", "||"):match("spell:(%d+)"))
|
|
end
|
|
|
|
-- For each entry we add to a table,
|
|
-- only add it if there isn't anything there already.
|
|
-- This prevents weird passives from overwriting real, legit spells.
|
|
-- For example, in WoW 7.3.5 the ret paladin mastery
|
|
-- was coming back with a base spell named "Judgement",
|
|
-- which was overwriting the real "Judgement".
|
|
-- Passives usually come last in the spellbook,
|
|
-- so this should work just fine as a workaround.
|
|
-- This issue with "Judgement" is gone in BFA because the mastery changed.
|
|
|
|
if currentSpellName and not spellsByName[strlower(currentSpellName)] then
|
|
spellsByName[strlower(currentSpellName)] = spellBookID
|
|
end
|
|
if currentSpellID and not spellsByID[currentSpellID] then
|
|
spellsByID[currentSpellID] = spellBookID
|
|
end
|
|
|
|
if type == "SPELL" then
|
|
-- PETACTION (pet abilities) don't return a spellID for baseSpellID,
|
|
-- so base spells only work for proper player spells.
|
|
local baseSpellName = GetSpellInfo(baseSpellID)
|
|
if baseSpellName and not spellsByName[strlower(baseSpellName)] then
|
|
spellsByName[strlower(baseSpellName)] = spellBookID
|
|
end
|
|
if baseSpellID and not spellsByID[baseSpellID] then
|
|
spellsByID[baseSpellID] = spellBookID
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function UpdatePetBar()
|
|
wipe(actionsByName_pet)
|
|
wipe(actionsById_pet)
|
|
if not UnitExists("pet") then return end
|
|
|
|
for i = 1, NUM_PET_ACTION_SLOTS do
|
|
local name, texture, isToken, isActive, autoCastAllowed, autoCastEnabled, spellID, checksRange, inRange = GetPetActionInfo(i)
|
|
if checksRange then
|
|
actionsByName_pet[strlower(name)] = i
|
|
actionsById_pet[spellID] = i
|
|
|
|
petSpellHasRange[strlower(name)] = true
|
|
petSpellHasRange[spellID] = true
|
|
end
|
|
end
|
|
end
|
|
UpdatePetBar()
|
|
|
|
-- Handles updating spellsByName and spellsByID
|
|
if not Lib.updaterFrame then
|
|
Lib.updaterFrame = CreateFrame("Frame")
|
|
end
|
|
Lib.updaterFrame:UnregisterAllEvents()
|
|
Lib.updaterFrame:RegisterEvent("SPELLS_CHANGED")
|
|
Lib.updaterFrame:RegisterEvent("PET_BAR_UPDATE")
|
|
Lib.updaterFrame:RegisterEvent("PLAYER_TARGET_CHANGED")
|
|
|
|
local function UpdateSpells(_, event)
|
|
if event == "PET_BAR_UPDATE" then
|
|
UpdatePetBar()
|
|
elseif event == "PLAYER_TARGET_CHANGED" then
|
|
-- `checksRange` from GetPetActionInfo() changes based on whether the player has a target or not.
|
|
UpdatePetBar()
|
|
elseif event == "SPELLS_CHANGED" then
|
|
UpdateBook("spell")
|
|
UpdateBook("pet")
|
|
end
|
|
end
|
|
|
|
Lib.updaterFrame:SetScript("OnEvent", UpdateSpells)
|
|
UpdateSpells()
|
|
|
|
--- Improved spell range checking function.
|
|
-- @name SpellRange.IsSpellInRange
|
|
-- @paramsig spell, unit
|
|
-- @param spell Name or spellID of a spell that you wish to check the range of. The spell must be a spell that you have in your spellbook or your pet's spellbook.
|
|
-- @param unit UnitID of the spell that you wish to check the range on.
|
|
-- @return Exact same returns as http://wowprogramming.com/docs/api/IsSpellInRange
|
|
-- @usage
|
|
-- -- Check spell range by spell name on unit "target"
|
|
-- local SpellRange = LibStub("SpellRange-1.0")
|
|
-- local inRange = SpellRange.IsSpellInRange("Stormstrike", "target")
|
|
--
|
|
-- -- Check spell range by spellID on unit "mouseover"
|
|
-- local SpellRange = LibStub("SpellRange-1.0")
|
|
-- local inRange = SpellRange.IsSpellInRange(17364, "mouseover")
|
|
function Lib.IsSpellInRange(spellInput, unit)
|
|
if isNumber[spellInput] then
|
|
local spell = spellsByID_spell[spellInput]
|
|
if spell then
|
|
return IsSpellBookItemInRange(spell, playerBook, unit)
|
|
else
|
|
local spell = spellsByID_pet[spellInput]
|
|
if spell then
|
|
local petResult = IsSpellBookItemInRange(spell, petBook, unit)
|
|
if petResult ~= nil then
|
|
return petResult
|
|
end
|
|
|
|
-- IsSpellInRange seems to no longer work for pet spellbook,
|
|
-- so we also try the action bar API.
|
|
local actionSlot = actionsById_pet[spellInput]
|
|
if actionSlot and (unit == "target" or UnitIsUnit(unit, "target")) then
|
|
return select(9, GetPetActionInfo(actionSlot)) and 1 or 0
|
|
end
|
|
end
|
|
end
|
|
if isTWWSpellInRange then
|
|
return IsSpellInRange(spellInput, unit)
|
|
end
|
|
else
|
|
local spellInput = strlowerCache[spellInput]
|
|
|
|
local spell = spellsByName_spell[spellInput]
|
|
if spell then
|
|
return IsSpellBookItemInRange(spell, playerBook, unit)
|
|
else
|
|
local spell = spellsByName_pet[spellInput]
|
|
if spell then
|
|
local petResult = IsSpellBookItemInRange(spell, petBook, unit)
|
|
if petResult ~= nil then
|
|
return petResult
|
|
end
|
|
|
|
-- IsSpellInRange seems to no longer work for pet spellbook,
|
|
-- so we also try the action bar API.
|
|
local actionSlot = actionsByName_pet[spellInput]
|
|
if actionSlot and (unit == "target" or UnitIsUnit(unit, "target")) then
|
|
return select(9, GetPetActionInfo(actionSlot)) and 1 or 0
|
|
end
|
|
end
|
|
end
|
|
return IsSpellInRange(spellInput, unit)
|
|
end
|
|
end
|
|
|
|
|
|
--- Improved SpellHasRange.
|
|
-- @name SpellRange.SpellHasRange
|
|
-- @paramsig spell
|
|
-- @param spell Name or spellID of a spell that you wish to check for a range. The spell must be a spell that you have in your spellbook or your pet's spellbook.
|
|
-- @return Exact same returns as http://wowprogramming.com/docs/api/SpellHasRange
|
|
-- @usage
|
|
-- -- Check if a spell has a range by spell name
|
|
-- local SpellRange = LibStub("SpellRange-1.0")
|
|
-- local hasRange = SpellRange.SpellHasRange("Stormstrike")
|
|
--
|
|
-- -- Check if a spell has a range by spellID
|
|
-- local SpellRange = LibStub("SpellRange-1.0")
|
|
-- local hasRange = SpellRange.SpellHasRange(17364)
|
|
function Lib.SpellHasRange(spellInput)
|
|
if isNumber[spellInput] then
|
|
local spell = spellsByID_spell[spellInput]
|
|
if spell then
|
|
return SpellBookHasRange(spell, playerBook)
|
|
else
|
|
local spell = spellsByID_pet[spellInput]
|
|
if spell then
|
|
-- SpellHasRange seems to no longer work for pet spellbook.
|
|
return SpellBookHasRange(spell, petBook) or petSpellHasRange[spellInput] or false
|
|
end
|
|
end
|
|
if isTWWSpellHasRange then
|
|
return SpellHasRange(spellInput)
|
|
end
|
|
else
|
|
local spellInput = strlowerCache[spellInput]
|
|
|
|
local spell = spellsByName_spell[spellInput]
|
|
if spell then
|
|
return SpellBookHasRange(spell, playerBook)
|
|
else
|
|
local spell = spellsByName_pet[spellInput]
|
|
if spell then
|
|
-- SpellHasRange seems to no longer work for pet spellbook.
|
|
return SpellBookHasRange(spell, petBook) or petSpellHasRange[spellInput] or false
|
|
end
|
|
end
|
|
return SpellHasRange(spellInput)
|
|
end
|
|
end
|