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.

295 lines
7.8 KiB

if not WeakAuras.IsLibsOK() then return end
---@type string
local AddonName = ...
---@class OptionsPrivate
local OptionsPrivate = select(2, ...)
-- Lua APIs
local pairs, error, coroutine = pairs, error, coroutine
-- WoW APIs
local IsSpellKnown = IsSpellKnown
---@class WeakAuras
local WeakAuras = WeakAuras
local spellCache = {}
WeakAuras.spellCache = spellCache
local cache
local metaData
local bestIcon = {}
-- Builds a cache of name/icon pairs from existing spell data
-- This is a rather slow operation, so it's only done once, and the result is subsequently saved
function spellCache.Build()
if not cache then
error("spellCache has not been loaded. Call WeakAuras.spellCache.Load(...) first.")
end
if not metaData.needsRebuild then
return
end
local holes
if WeakAuras.IsClassicEra() then
holes = {}
holes[63707] = 81743
holes[81748] = 219002
holes[219004] = 285223
holes[285224] = 301088
holes[301101] = 324269
elseif WeakAuras.IsCataClassic() then
holes = {}
holes[121820] = 158262
holes[158263] = 186402
holes[186403] = 219002
holes[219004] = 243805
holes[243806] = 261127
holes[262591] = 281624
holes[301101] = 324269
end
wipe(cache)
local co = coroutine.create(function()
local id = 0
local misses = 0
while misses < 80000 do
id = id + 1
local name = OptionsPrivate.Private.ExecEnv.GetSpellName(id)
local icon = OptionsPrivate.Private.ExecEnv.GetSpellIcon(id)
if(icon == 136243) then -- 136243 is the a gear icon, we can ignore those spells
misses = 0;
elseif name and name ~= "" and icon then
cache[name] = cache[name] or {}
if not cache[name].spells or cache[name].spells == "" then
cache[name].spells = id .. "=" .. icon
else
cache[name].spells = cache[name].spells .. "," .. id .. "=" .. icon
end
misses = 0
if holes and holes[id] then
id = holes[id]
end
else
misses = misses + 1
end
coroutine.yield()
end
if WeakAuras.IsCataOrRetail() then
for _, category in pairs(GetCategoryList()) do
local total = GetCategoryNumAchievements(category, true)
for i = 1, total do
local id,name,_,_,_,_,_,_,_,iconID = GetAchievementInfo(category, i)
if name and iconID then
cache[name] = cache[name] or {}
if not cache[name].achievements or cache[name].achievements == "" then
cache[name].achievements = id .. "=" .. iconID
else
cache[name].achievements = cache[name].achievements .. "," .. id .. "=" .. iconID
end
end
end
coroutine.yield()
end
end
metaData.needsRebuild = false
end)
OptionsPrivate.Private.dynFrame:AddAction("spellCache", co)
end
--[[ function to help find big holes in spellIds to help speedup Build()
local id = 0
local misses = 0
local lastId
print("####")
while misses < 400000 do
id = id + 1
local name = GetSpellInfo(id)
local icon = GetSpellTexture(id)
if icon == 136243 then -- 136243 is the a gear icon, we can ignore those spells
misses = 0
elseif name and name ~= "" and icon then
if misses > 10000 then
print(("holes[%s] = %s"):format(lastId, id - 1))
end
lastId = id
misses = 0
else
misses = misses + 1
end
end
print("lastId", lastId)
]]
function spellCache.GetIcon(name)
if (name == nil) then
return nil;
end
if cache then
if (bestIcon[name]) then
return bestIcon[name]
end
local icons = cache[name]
local bestMatch = nil
if (icons) then
if (icons.spells) then
for spell, icon in icons.spells:gmatch("(%d+)=(%d+)") do
local spellId = tonumber(spell)
if not bestMatch or (spellId and spellId ~= 0 and IsSpellKnown(spellId)) then
bestMatch = tonumber(icon)
end
end
end
end
bestIcon[name] = bestMatch
return bestIcon[name]
else
error("spellCache has not been loaded. Call WeakAuras.spellCache.Load(...) first.")
end
end
function spellCache.GetSpellsMatching(name)
if cache[name] then
if cache[name].spells then
local result = {}
for spell, icon in cache[name].spells:gmatch("(%d+)=(%d+)") do
local spellId = tonumber(spell)
local iconId = tonumber(icon)
result[spellId] = icon
end
return result
end
end
end
function spellCache.AddIcon(name, id, icon)
if not cache then
error("spellCache has not been loaded. Call WeakAuras.spellCache.Load(...) first.")
return
end
if name and id and icon then
cache[name] = cache[name] or {}
if not cache[name].spells or cache[name].spells == "" then
cache[name].spells = id .. "=" .. icon
else
cache[name].spells = cache[name].spells .. "," .. id .. "=" .. icon
end
end
end
function spellCache.Get()
if cache then
return cache
else
error("spellCache has not been loaded. Call WeakAuras.spellCache.Load(...) first.")
end
end
function spellCache.Load(data)
metaData = data
cache = metaData.spellCache
local _, build = GetBuildInfo();
local locale = GetLocale();
local version = WeakAuras.versionString
local num = 0;
for i,v in pairs(cache) do
num = num + 1;
end
if(num < 39000 or metaData.locale ~= locale or metaData.build ~= build
or metaData.version ~= version or not metaData.spellCacheStrings)
then
metaData.build = build;
metaData.locale = locale;
metaData.version = version;
metaData.spellCacheAchievements = true
metaData.spellCacheStrings = true
metaData.needsRebuild = true
wipe(cache)
end
end
-- This function computes the Levenshtein distance between two strings
-- It is used in this program to match spell icon textures with "good" spell names; i.e.,
-- spell names that are very similar to the name of the texture
local function Lev(str1, str2)
local matrix = {};
for i=0, str1:len() do
matrix[i] = {[0] = i};
end
for j=0, str2:len() do
matrix[0][j] = j;
end
for j=1, str2:len() do
for i =1, str1:len() do
if(str1:sub(i, i) == str2:sub(j, j)) then
matrix[i][j] = matrix[i-1][j-1];
else
matrix[i][j] = math.min(matrix[i-1][j], matrix[i][j-1], matrix[i-1][j-1]) + 1;
end
end
end
return matrix[str1:len()][str2:len()];
end
function spellCache.BestKeyMatch(nearkey)
local bestKey = "";
local bestDistance = math.huge;
local partialMatches = {};
if cache[nearkey] then
return nearkey
end
for key, value in pairs(cache) do
if key:lower() == nearkey:lower() then
return key
end
if(key:lower():find(nearkey:lower(), 1, true)) then
partialMatches[key] = value;
end
end
for key, value in pairs(partialMatches) do
local distance = Lev(nearkey, key);
if(distance < bestDistance) then
bestKey = key;
bestDistance = distance;
end
end
return bestKey;
end
function spellCache.CorrectAuraName(input)
if (not cache) then
error("spellCache has not been loaded. Call WeakAuras.spellCache.Load(...) first.")
end
local spellId = WeakAuras.SafeToNumber(input);
if(spellId) then
local name, _, icon = OptionsPrivate.Private.ExecEnv.GetSpellInfo(spellId);
if(name) then
spellCache.AddIcon(name, spellId, icon)
return name, spellId;
else
return "Invalid Spell ID";
end
else
local ret = spellCache.BestKeyMatch(input);
if(ret == "") then
return "No Match Found", nil;
else
return ret, nil;
end
end
end