local _G = ShaguTweaks.GetGlobalEnv() local API = ShaguTweaks.API local libtipscan = ShaguTweaks.libtipscan -- Current ClassicAPI supplies spell metadata directly. Create the tooltip -- scanner only if that surface is unavailable for a specific lookup. local scanner local function GetScanner() if not scanner then scanner = libtipscan:GetScanner("libspell") end return scanner end local libspell = {} local spellmaxrank = {} local spellindex = {} local spellinfo = {} local function ClearSpellCaches() spellmaxrank = {} spellindex = {} spellinfo = {} end -- Learned ranks and talent/trainer changes can invalidate both successful and -- negative spellbook lookups. Without clearing these caches, a spell queried -- before it was learned can remain "missing" until the next UI reload. local events = CreateFrame("Frame") events:RegisterEvent("LEARNED_SPELL_IN_TAB") if API.eventutils and _G.C_EventUtils and _G.C_EventUtils.IsEventValid("SPELLS_CHANGED") then events:RegisterEvent("SPELLS_CHANGED") end events:SetScript("OnEvent", ClearSpellCaches) -- [ GetSpellMaxRank ] -- Returns the maximum rank of a players spell. -- 'name' [string] spellname to query -- return: [string],[number] maximum rank in characters and the number -- e.g "Rank 1" and "1" function libspell.GetSpellMaxRank(name) local cache = spellmaxrank[name] if cache then return cache[1], cache[2] end local rank = { 0, nil} for i = 1, GetNumSpellTabs() do local _, _, offset, num = GetSpellTabInfo(i) local bookType = BOOKTYPE_SPELL for id = offset + 1, offset + num do local spellName, spellRank = GetSpellName(id, bookType) if spellName == name then if not rank[2] then rank[2] = spellRank end local _, _, numRank = string.find(spellRank, " (%d+)$") if numRank and tonumber(numRank) > rank[1] then rank = { tonumber(numRank), spellRank} end end end end spellmaxrank[name] = { rank[2], rank[1] } return rank[2], rank[1] end -- [ GetSpellIndex ] -- Returns the spellbook index and bookid of the given spell. -- 'name' [string] spellname to query -- 'rank' [string] rank to query (optional) -- return: [number],[string] spell index and spellbook id function libspell.GetSpellIndex(name, rank) if not name then return end local cache = spellindex[name..(rank or "")] if cache then return cache[1], cache[2] end if not rank then rank = libspell.GetSpellMaxRank(name) end for i = 1, GetNumSpellTabs() do local _, _, offset, num = GetSpellTabInfo(i) local bookType = BOOKTYPE_SPELL for id = offset + 1, offset + num do local spellName, spellRank = GetSpellName(id, bookType) if rank and rank == spellRank and name == spellName then spellindex[name..rank] = { id, bookType } return id, bookType elseif not rank and name == spellName then spellindex[name] = { id, bookType } return id, bookType end end end spellindex[name..(rank or "")] = { nil } return nil end -- [ GetSpellInfo ] -- Returns several information about a spell. -- 'index' [string/number] Spellname or Index of a spell in the spellbook -- 'bookType' [string] Type of spellbook (optional) -- return: -- [string] Name of the spell -- [string] Secondary text associated with the spell -- (e.g."Rank 5", "Racial", etc.) -- [string] Path to an icon texture for the spell -- [number] Casting time of the spell in milliseconds -- [number] Minimum range from the target required to cast the spell -- [number] Maximum range from the target at which you can cast the spell function libspell.GetSpellInfo(index, bookType) local cacheKey = tostring(index) .. ":" .. tostring(bookType or "") local cache = spellinfo[cacheKey] if cache then return cache[1], cache[2], cache[3], cache[4], cache[5], cache[6] end local name, rank, id local icon = "" local castingTime = 0 local minRange = 0 local maxRange = 0 if type(index) == "string" then local _, _, sname, srank = string.find(index, '(.+)%((.+)%)') name = sname or index rank = srank or libspell.GetSpellMaxRank(name) id, bookType = libspell.GetSpellIndex(name, rank) else name, rank = GetSpellName(index, bookType) id, bookType = libspell.GetSpellIndex(name, rank) end if id then -- ClassicAPI's GetSpellInfo(slot, bookType) reads Spell.dbc, -- SpellCastTimes.dbc and SpellRange.dbc directly. This avoids localized -- tooltip parsing and gives exact millisecond/range values. local apiName, apiRank, apiIcon, _, _, _, apiCastTime, apiMinRange, apiMaxRange = API.GetSpellInfo(id, bookType) if apiName then name = apiName rank = apiRank or rank icon = apiIcon or "" castingTime = apiCastTime or 0 minRange = apiMinRange or 0 maxRange = apiMaxRange or 0 else -- Legacy fallback for an outdated/missing ClassicAPI spell-info surface. icon = GetSpellTexture(id, bookType) or "" local tip = GetScanner() tip:SetSpell(id, bookType) local _, sec = tip:Find(gsub(SPELL_CAST_TIME_SEC, "%%.3g", "%(.+%)")) local _, min = tip:Find(gsub(SPELL_CAST_TIME_MIN, "%%.3g", "%(.+%)")) local _, range = tip:Find(gsub(SPELL_RANGE, "%%s", "%(.+%)")) castingTime = (tonumber(sec) or tonumber(min) or 0) * 1000 if range then local _, _, low, high = string.find(range, "(.+)-(.+)") if low and high then minRange = tonumber(low) maxRange = tonumber(high) else minRange = 0 maxRange = tonumber(range) end end end end spellinfo[cacheKey] = { name, rank, icon, castingTime, minRange, maxRange } return name, rank, icon, castingTime, minRange, maxRange end ShaguTweaks.libspell = libspell