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.
2322 lines
78 KiB
2322 lines
78 KiB
--------------------------------------------------------------------------------
|
|
-- A L L T H E T H I N G S --
|
|
--------------------------------------------------------------------------------
|
|
-- Copyright 2017-2025 Dylan Fortune (Crieve-Sargeras) --
|
|
--------------------------------------------------------------------------------
|
|
-- App locals
|
|
local appName, app = ...;
|
|
local L = app.L;
|
|
|
|
local AssignChildren, CloneClassInstance, GetRelativeValue = app.AssignChildren, app.CloneClassInstance, app.GetRelativeValue;
|
|
|
|
-- Abbreviations
|
|
L.ABBREVIATIONS[L.UNSORTED .. " %> " .. L.UNSORTED] = "|T" .. app.asset("WindowIcon_Unsorted") .. ":0|t " .. L.SHORTTITLE .. " %> " .. L.UNSORTED;
|
|
|
|
-- Binding Localizations
|
|
BINDING_HEADER_ALLTHETHINGS = L.TITLE
|
|
BINDING_NAME_ALLTHETHINGS_TOGGLEACCOUNTMODE = L.TOGGLE_ACCOUNT_MODE
|
|
BINDING_NAME_ALLTHETHINGS_TOGGLECOMPLETIONISTMODE = L.TOGGLE_COMPLETIONIST_MODE
|
|
BINDING_NAME_ALLTHETHINGS_TOGGLEDEBUGMODE = L.TOGGLE_DEBUG_MODE
|
|
BINDING_NAME_ALLTHETHINGS_TOGGLEFACTIONMODE = L.TOGGLE_FACTION_MODE
|
|
BINDING_NAME_ALLTHETHINGS_TOGGLELOOTMODE = L.TOGGLE_LOOT_MODE
|
|
|
|
BINDING_HEADER_ALLTHETHINGS_PREFERENCES = PREFERENCES
|
|
BINDING_NAME_ALLTHETHINGS_TOGGLECOMPLETEDTHINGS = L.TOGGLE_COMPLETEDTHINGS
|
|
BINDING_NAME_ALLTHETHINGS_TOGGLECOMPLETEDGROUPS = L.TOGGLE_COMPLETEDGROUPS
|
|
BINDING_NAME_ALLTHETHINGS_TOGGLECOLLECTEDTHINGS = L.TOGGLE_COLLECTEDTHINGS
|
|
BINDING_NAME_ALLTHETHINGS_TOGGLEBOEITEMS = L.TOGGLE_BOEITEMS
|
|
BINDING_NAME_ALLTHETHINGS_TOGGLESOURCETEXT = L.TOGGLE_SOURCETEXT
|
|
|
|
BINDING_HEADER_ALLTHETHINGS_MODULES = L.MODULES
|
|
BINDING_NAME_ALLTHETHINGS_TOGGLEMAINLIST = L.TOGGLE_MAINLIST
|
|
BINDING_NAME_ALLTHETHINGS_TOGGLEMINILIST = L.TOGGLE_MINILIST
|
|
BINDING_NAME_ALLTHETHINGS_TOGGLE_PROFESSION_LIST = L.TOGGLE_PROFESSION_LIST
|
|
BINDING_NAME_ALLTHETHINGS_TOGGLE_RAID_ASSISTANT = L.TOGGLE_RAID_ASSISTANT
|
|
BINDING_NAME_ALLTHETHINGS_TOGGLE_WORLD_QUESTS_LIST = L.TOGGLE_WORLD_QUESTS_LIST
|
|
BINDING_NAME_ALLTHETHINGS_TOGGLERANDOM = L.TOGGLE_RANDOM
|
|
BINDING_NAME_ALLTHETHINGS_REROLL_RANDOM = L.REROLL_RANDOM
|
|
|
|
-- Global API cache
|
|
-- While this may seem silly, caching references to commonly used APIs is actually a performance gain...
|
|
local C_DateAndTime_GetServerTimeLocal
|
|
= C_DateAndTime.GetServerTimeLocal;
|
|
local ipairs, pairs, rawset, rawget, select, tinsert, tremove
|
|
= ipairs, pairs, rawset, rawget, select, tinsert, tremove;
|
|
local GetAchievementNumCriteria = _G["GetAchievementNumCriteria"];
|
|
local GetAchievementCriteriaInfo = _G["GetAchievementCriteriaInfo"];
|
|
local IsPlayerSpell, IsSpellKnown, IsSpellKnownOrOverridesKnown =
|
|
IsPlayerSpell, IsSpellKnown, IsSpellKnownOrOverridesKnown;
|
|
local C_QuestLog_IsOnQuest = C_QuestLog.IsOnQuest;
|
|
|
|
-- WoW API Cache
|
|
local GetItemInfo = app.WOWAPI.GetItemInfo;
|
|
local GetItemIcon = app.WOWAPI.GetItemIcon;
|
|
local GetItemInfoInstant = app.WOWAPI.GetItemInfoInstant;
|
|
local GetItemCount = app.WOWAPI.GetItemCount;
|
|
local GetSpellCooldown = app.WOWAPI.GetSpellCooldown;
|
|
local GetSpellName = app.WOWAPI.GetSpellName;
|
|
local GetSpellIcon = app.WOWAPI.GetSpellIcon;
|
|
local GetSpellLink = app.WOWAPI.GetSpellLink;
|
|
|
|
-- App & Module locals
|
|
local contains = app.contains;
|
|
local DESCRIPTION_SEPARATOR = app.DESCRIPTION_SEPARATOR;
|
|
local SearchForField, SearchForFieldContainer
|
|
= app.SearchForField, app.SearchForFieldContainer;
|
|
local IsRetrieving = app.Modules.RetrievingData.IsRetrieving;
|
|
local GetProgressColorText = app.Modules.Color.GetProgressColorText;
|
|
local Colorize = app.Modules.Color.Colorize;
|
|
local HexToARGB = app.Modules.Color.HexToARGB;
|
|
local RGBToHex = app.Modules.Color.RGBToHex;
|
|
local GetUnobtainableTexture
|
|
app.IsSpellKnownHelper
|
|
= IsSpellKnown;
|
|
|
|
-- Locals from future-loaded Modules
|
|
app.AddEventHandler("OnLoad", function()
|
|
GetUnobtainableTexture = app.GetUnobtainableTexture
|
|
end)
|
|
|
|
-- Data Lib
|
|
local AllTheThingsAD = {}; -- For account-wide data.
|
|
|
|
-- Color Lib
|
|
local function GetProgressTextForRow(data)
|
|
local total = data.total;
|
|
if total and (total > 1 or (total > 0 and not data.collectible)) then
|
|
return GetProgressColorText(data.progress or 0, total);
|
|
elseif data.collectible then
|
|
return app.GetCollectionIcon(data.collected);
|
|
elseif data.trackable then
|
|
return app.GetCompletionIcon(data.saved);
|
|
end
|
|
end
|
|
local function GetProgressTextForTooltip(data)
|
|
local iconOnly = app.Settings:GetTooltipSetting("ShowIconOnly");
|
|
if iconOnly then return GetProgressTextForRow(data); end
|
|
|
|
if data.total and (data.total > 1 or (data.total > 0 and not data.collectible)) then
|
|
return GetProgressColorText(data.progress or 0, data.total);
|
|
elseif data.collectible or (data.spellID and data.itemID and data.trackable) then
|
|
return app.GetCollectionText(data.collected);
|
|
elseif data.trackable then
|
|
return app.GetCompletionText(data.saved);
|
|
end
|
|
end
|
|
app.GetProgressTextForRow = GetProgressTextForRow;
|
|
app.GetProgressTextForTooltip = GetProgressTextForTooltip;
|
|
|
|
-- Keys for groups which are in-game 'Things'
|
|
-- Copied from Retail since it's used in UI/Waypoints.lua
|
|
app.ThingKeys = {
|
|
-- filterID = true,
|
|
flightpathID = true,
|
|
-- professionID = true,
|
|
-- categoryID = true,
|
|
-- mapID = true,
|
|
npcID = true,
|
|
creatureID = true,
|
|
currencyID = true,
|
|
itemID = true,
|
|
toyID = true,
|
|
sourceID = true,
|
|
speciesID = true,
|
|
recipeID = true,
|
|
runeforgepowerID = true,
|
|
spellID = true,
|
|
mountID = true,
|
|
mountmodID = true,
|
|
illusionID = true,
|
|
questID = true,
|
|
objectID = true,
|
|
encounterID = true,
|
|
artifactID = true,
|
|
azeriteessenceID = true,
|
|
followerID = true,
|
|
factionID = true,
|
|
explorationID = true,
|
|
achievementID = true, -- special handling
|
|
criteriaID = true, -- special handling
|
|
};
|
|
|
|
local MergeObject;
|
|
local CloneArray = app.CloneArray;
|
|
local function GetHash(t)
|
|
local hash = t.hash;
|
|
if hash then return hash; end
|
|
hash = app.CreateHash(t);
|
|
--app.PrintDebug("No hash for object:", hash, t.text);
|
|
return hash;
|
|
end
|
|
local function MergeObjects(g, g2)
|
|
for i,o in ipairs(g2) do
|
|
MergeObject(g, o);
|
|
end
|
|
end
|
|
MergeObject = function(g, t, index)
|
|
local hash = GetHash(t);
|
|
for i,o in ipairs(g) do
|
|
if GetHash(o) == hash then
|
|
if t.g then
|
|
local tg = t.g;
|
|
t.g = nil;
|
|
if o.g then
|
|
MergeObjects(o.g, tg);
|
|
else
|
|
o.g = tg;
|
|
end
|
|
end
|
|
for k,v in pairs(t) do
|
|
if k == "races" or k == "c" then
|
|
local c = rawget(o, k);
|
|
if not c then
|
|
c = CloneArray(v);
|
|
rawset(o, k, c);
|
|
else
|
|
for _,p in ipairs(v) do
|
|
if not contains(c, p) then
|
|
tinsert(c, p);
|
|
end
|
|
end
|
|
end
|
|
elseif k == "r" then
|
|
if o[k] and o[k] ~= v then
|
|
rawset(o, k, nil);
|
|
else
|
|
rawset(o, k, v);
|
|
end
|
|
elseif k ~= "expanded" then
|
|
rawset(o, k, v);
|
|
end
|
|
end
|
|
return o;
|
|
end
|
|
end
|
|
if index then
|
|
tinsert(g, index, t);
|
|
else
|
|
tinsert(g, t);
|
|
end
|
|
rawset(t, "nmr", (t.races and not contains(t.races, app.RaceIndex)) or (t.r and t.r ~= app.FactionID));
|
|
rawset(t, "nmc", t.c and not contains(t.c, app.ClassIndex));
|
|
return t;
|
|
end
|
|
local function MergeClone(g, o)
|
|
local clone = CloneClassInstance(o);
|
|
local u = GetRelativeValue(o, "u");
|
|
if u then clone.u = u; end
|
|
local e = GetRelativeValue(o, "e");
|
|
if e then clone.e = e; end
|
|
local lvl = GetRelativeValue(o, "lvl");
|
|
if lvl then clone.lvl = lvl; end
|
|
if not o.itemID or o.b == 1 then
|
|
local races = o.races;
|
|
if races then
|
|
clone.races = CloneArray(races);
|
|
else
|
|
local r = GetRelativeValue(o, "r");
|
|
if r then
|
|
clone.r = r;
|
|
clone.races = nil;
|
|
else
|
|
races = GetRelativeValue(o, "races");
|
|
if races then clone.races = CloneArray(races); end
|
|
end
|
|
end
|
|
local c = GetRelativeValue(o, "c");
|
|
if c then clone.c = CloneArray(c); end
|
|
end
|
|
return MergeObject(g, clone);
|
|
end
|
|
|
|
app.MergeClone = MergeClone;
|
|
app.MergeObject = MergeObject;
|
|
app.MergeObjects = MergeObjects;
|
|
|
|
local ResolveSymbolicLink;
|
|
(function()
|
|
local subroutines;
|
|
subroutines = {
|
|
["common_recipes_vendor"] = function(npcID)
|
|
return {
|
|
{"select", "npcID", npcID}, -- Main Vendor
|
|
{"pop"}, -- Remove Main Vendor and push his children into the processing queue.
|
|
{"is", "itemID"}, -- Only Items
|
|
{"exclude", "itemID",
|
|
-- Borya <Tailoring Supplies> Cataclysm Tailoring
|
|
6270, -- Pattern: Blue Linen Vest
|
|
6274, -- Pattern: Blue Overalls
|
|
10314, -- Pattern: Lavender Mageweave Shirt
|
|
10317, -- Pattern: Pink Mageweave Shirt
|
|
5772, -- Pattern: Red Woolen Bag
|
|
-- Sumi <Blacksmithing Supplies> Cataclysm Blacksmithing
|
|
12162, -- Plans: Hardened Iron Shortsword
|
|
-- Tamar <Leatherworking Supplies> Cataclysm Leatherworking
|
|
18731, -- Pattern: Heavy Leather Ball
|
|
-- Kithas <Enchanting Supplies> Cataclysm Enchanting
|
|
6349, -- Formula: Enchant 2H Weapon - Lesser Intellect
|
|
20753, -- Formula: Lesser Wizard Oil
|
|
20752, -- Formula: Minor Mana Oil
|
|
20758, -- Formula: Minor Wizard Oil
|
|
22307, -- Pattern: Enchanted Mageweave Pouch
|
|
-- Marith Lazuria <Jewelcrafting Supplies> Cataclysm Jewelcrafting
|
|
-- Shazdar <Sous Chef> Cataclysm Cooking
|
|
-- Tiffany Cartier <Jewelcrafting Supplies> Northrend Jewelcrafting
|
|
-- Timothy Jones <Jewelcrafting Trainer> Northrend Jewelcrafting
|
|
},
|
|
};
|
|
end,
|
|
["common_vendor"] = function(npcID)
|
|
return {
|
|
{"select", "npcID", npcID}, -- Main Vendor
|
|
{"pop"}, -- Remove Main Vendor and push his children into the processing queue.
|
|
{"is", "itemID"}, -- Only Items
|
|
};
|
|
end,
|
|
["pvp_gear_base"] = function(expansionID, headerID1, headerID2)
|
|
local b = {
|
|
{ "select", "expansionID", expansionID }, -- Select the Expansion header
|
|
{ "pop" }, -- Discard the Expansion header and acquire the children.
|
|
{ "where", "headerID", headerID1 }, -- Select the Season header
|
|
};
|
|
if headerID2 then
|
|
tinsert(b, { "pop" }); -- Discard the Season header and acquire the children.
|
|
tinsert(b, { "where", "headerID", headerID2 }); -- Select the Set header
|
|
end
|
|
return b;
|
|
end,
|
|
};
|
|
ResolveSymbolicLink = function(o)
|
|
if o.resolved then return o.resolved; end
|
|
if o and o.sym then
|
|
local searchResults, finalized = {}, {};
|
|
for j,sym in ipairs(o.sym) do
|
|
local cmd = sym[1];
|
|
if cmd == "select" then
|
|
-- Instruction to search the full database for multiple of a given type
|
|
local field = sym[2];
|
|
local cache;
|
|
for i=3,#sym do
|
|
local cache = SearchForField(field, sym[i]);
|
|
if #cache > 0 then
|
|
for k,result in ipairs(cache) do
|
|
local ref = ResolveSymbolicLink(result);
|
|
if ref then
|
|
if result.g then
|
|
for _,m in ipairs(result.g) do
|
|
tinsert(searchResults, m);
|
|
end
|
|
end
|
|
for _,m in ipairs(ref) do
|
|
tinsert(searchResults, m);
|
|
end
|
|
else
|
|
tinsert(searchResults, result);
|
|
end
|
|
end
|
|
elseif field == "itemID" then
|
|
tinsert(searchResults, app.CreateItem(sym[i], {
|
|
description = "This was dynamically filled using a symlink, but the information wasn't found in the addon.",
|
|
}));
|
|
else
|
|
print(app.GenerateSourceHash(o));
|
|
print("Failed to select ", field, sym[i]);
|
|
end
|
|
end
|
|
elseif cmd == "selectparent" then
|
|
-- Instruction to select the parent object of the parent that owns the symbolic link.
|
|
local cache = sym[2];
|
|
if cache and cache > 0 then
|
|
local parent = o.parent;
|
|
while cache > 1 do
|
|
parent = parent.parent;
|
|
cache = cache - 1;
|
|
end
|
|
if parent then
|
|
tinsert(searchResults, parent);
|
|
else
|
|
print("Failed to select parent " .. sym[2] .. " levels up.");
|
|
end
|
|
else
|
|
-- Select the direct parent object.
|
|
tinsert(searchResults, o.parent);
|
|
end
|
|
elseif cmd == "selectprofession" then
|
|
local requireSkill, response = sym[2], nil;
|
|
response = app:BuildSearchResponse(app.Categories.Instances, "requireSkill", requireSkill);
|
|
if response then tinsert(searchResults, {text=GROUP_FINDER,icon = app.asset("Category_D&R"),g=response}); end
|
|
response = app:BuildSearchResponse(app.Categories.Zones, "requireSkill", requireSkill);
|
|
if response then tinsert(searchResults, {text=BUG_CATEGORY2,icon = app.asset("Category_Zones"),g=response}); end
|
|
response = app:BuildSearchResponse(app.Categories.WorldDrops, "requireSkill", requireSkill);
|
|
if response then tinsert(searchResults, {text=TRANSMOG_SOURCE_4,icon = app.asset("Category_WorldDrops"),g=response}); end
|
|
response = app:BuildSearchResponse(app.Categories.Craftables, "requireSkill", requireSkill);
|
|
if response then tinsert(searchResults, {text=LOOT_JOURNAL_LEGENDARIES_SOURCE_CRAFTED_ITEM,icon = app.asset("Category_Crafting"),g=response}); end
|
|
response = app:BuildSearchResponse(app.Categories.Holidays, "requireSkill", requireSkill);
|
|
if response then tinsert(searchResults, app.CreateCustomHeader(app.HeaderConstants.HOLIDAYS, response)); end
|
|
response = app:BuildSearchResponse(app.Categories.WorldEvents, "requireSkill", requireSkill);
|
|
if response then tinsert(searchResults, {text=BATTLE_PET_SOURCE_7,icon = app.asset("Category_Event"),g=response}); end
|
|
if app.Categories.ExpansionFeatures then
|
|
response = app:BuildSearchResponse(app.Categories.ExpansionFeatures, "requireSkill", requireSkill);
|
|
if response then tinsert(searchResults, {text=EXPANSION_FILTER_TEXT,icon = app.asset("Category_ExpansionFeatures"),g=response}); end
|
|
end
|
|
elseif cmd == "pop" then
|
|
-- Instruction to "pop" all of the group values up one level.
|
|
local orig = searchResults;
|
|
searchResults = {};
|
|
for k,result in ipairs(orig) do
|
|
if result.g then
|
|
for l,t in ipairs(result.g) do
|
|
tinsert(searchResults, t);
|
|
end
|
|
end
|
|
end
|
|
elseif cmd == "where" then
|
|
-- Instruction to include only search results where a key value is a value
|
|
local key, value = sym[2], sym[3];
|
|
for k=#searchResults,1,-1 do
|
|
local result = searchResults[k];
|
|
if not result[key] or result[key] ~= value then
|
|
tremove(searchResults, k);
|
|
end
|
|
end
|
|
elseif cmd == "index" then
|
|
-- Instruction to include the search result with a given index within each of the selection's groups.
|
|
local index = sym[2];
|
|
local orig = searchResults;
|
|
searchResults = {};
|
|
for k=#orig,1,-1 do
|
|
local result = orig[k];
|
|
if result.g and index <= #result.g then
|
|
tinsert(searchResults, result.g[index]);
|
|
end
|
|
end
|
|
elseif cmd == "not" then
|
|
-- Instruction to include only search results where a key value for a field is not a value
|
|
local field = sym[2];
|
|
if #sym > 3 then
|
|
local matches = {};
|
|
for k=3,#sym,1 do
|
|
matches[sym[k]] = true;
|
|
end
|
|
for k=#searchResults,1,-1 do
|
|
local result = searchResults[k];
|
|
local value = result[field];
|
|
if value and matches[value] then
|
|
tremove(searchResults, k);
|
|
end
|
|
end
|
|
else
|
|
local value = sym[3];
|
|
for k=#searchResults,1,-1 do
|
|
local result = searchResults[k];
|
|
if result[field] and result[field] == value then
|
|
tremove(searchResults, k);
|
|
end
|
|
end
|
|
end
|
|
elseif cmd == "is" then
|
|
-- Instruction to include only search results where a key exists
|
|
local key = sym[2];
|
|
for k=#searchResults,1,-1 do
|
|
if not searchResults[k][key] then tremove(searchResults, k); end
|
|
end
|
|
elseif cmd == "isnt" then
|
|
-- Instruction to include only search results where a key doesn't exist
|
|
local key = sym[2];
|
|
for k=#searchResults,1,-1 do
|
|
if searchResults[k][key] then tremove(searchResults, k); end
|
|
end
|
|
elseif cmd == "contains" then
|
|
-- Instruction to include only search results where a key value contains a value.
|
|
local key = sym[2];
|
|
local clone = {unpack(sym)};
|
|
tremove(clone, 1);
|
|
tremove(clone, 1);
|
|
if #clone > 0 then
|
|
for k=#searchResults,1,-1 do
|
|
local result = searchResults[k];
|
|
if not result[key] or not contains(clone, result[key]) then
|
|
tremove(searchResults, k);
|
|
end
|
|
end
|
|
end
|
|
elseif cmd == "exclude" then
|
|
-- Instruction to exclude search results where a key value contains a value.
|
|
local key = sym[2];
|
|
local clone = {unpack(sym)};
|
|
tremove(clone, 1);
|
|
tremove(clone, 1);
|
|
if #clone > 0 then
|
|
for k=#searchResults,1,-1 do
|
|
local result = searchResults[k];
|
|
if result[key] and contains(clone, result[key]) then
|
|
tremove(searchResults, k);
|
|
end
|
|
end
|
|
end
|
|
elseif cmd == "finalize" then
|
|
-- Instruction to finalize the current search results and prevent additional queries from affecting this selection.
|
|
for k,result in ipairs(searchResults) do
|
|
tinsert(finalized, result);
|
|
end
|
|
wipe(searchResults);
|
|
elseif cmd == "merge" then
|
|
-- Instruction to take all of the finalized and non-finalized search results and merge them back in to the processing queue.
|
|
for k,result in ipairs(searchResults) do
|
|
tinsert(finalized, result);
|
|
end
|
|
searchResults = finalized;
|
|
finalized = {};
|
|
elseif cmd == "invtype" then
|
|
-- Instruction to include only search results where an item is of a specific inventory type.
|
|
local types = {unpack(sym)};
|
|
tremove(types, 1);
|
|
if #types > 0 then
|
|
for k=#searchResults,1,-1 do
|
|
local result = searchResults[k];
|
|
if result.itemID and not contains(types, select(4, GetItemInfoInstant(result.itemID))) then
|
|
tremove(searchResults, k);
|
|
end
|
|
end
|
|
end
|
|
elseif cmd == "sub" then
|
|
local subroutine = subroutines[sym[2]];
|
|
if subroutine then
|
|
local args = {unpack(sym)};
|
|
tremove(args, 1);
|
|
tremove(args, 1);
|
|
local commands = subroutine(unpack(args));
|
|
if commands then
|
|
local results = ResolveSymbolicLink(setmetatable({sym=commands}, {__index=o}));
|
|
if results then
|
|
for k,result in ipairs(results) do
|
|
tinsert(searchResults, result);
|
|
end
|
|
end
|
|
end
|
|
else
|
|
print("Could not find subroutine", sym[2]);
|
|
end
|
|
elseif cmd == "subif" then
|
|
-- Instruction to perform a set of commands if a conditional is returned true.
|
|
local subroutine = subroutines[sym[2]];
|
|
if subroutine then
|
|
-- If the subroutine's conditional was successful.
|
|
if sym[3] and (sym[3])(o) then
|
|
local args = {unpack(sym)};
|
|
tremove(args, 1);
|
|
tremove(args, 1);
|
|
tremove(args, 1);
|
|
local commands = subroutine(unpack(args));
|
|
if commands then
|
|
local results = ResolveSymbolicLink(setmetatable({sym=commands}, {__index=o}));
|
|
if results then
|
|
for k,result in ipairs(results) do
|
|
tinsert(searchResults, result);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
else
|
|
print("Could not find subroutine", sym[2]);
|
|
end
|
|
elseif cmd == "achievement_criteria" then
|
|
-- Instruction to select the criteria provided by the achievement this is attached to. (maybe build this into achievements?)
|
|
if GetAchievementNumCriteria then
|
|
local achievementID = o.achievementID;
|
|
local num = GetAchievementNumCriteria(achievementID);
|
|
if type(num) ~= "number" then
|
|
--print("Attempting to use 'achievement_criteria' with achievement", achievementID);
|
|
return;
|
|
end
|
|
local cache;
|
|
for criteriaID=1,num,1 do
|
|
---@diagnostic disable-next-line: redundant-parameter
|
|
local _, criteriaType, _, _, _, _, _, assetID, _, uniqueID = GetAchievementCriteriaInfo(achievementID, criteriaID, true);
|
|
if not uniqueID or uniqueID <= 0 then uniqueID = criteriaID; end
|
|
local criteriaObject = app.CreateAchievementCriteria(uniqueID);
|
|
if criteriaObject then
|
|
criteriaObject.achievementID = achievementID;
|
|
if criteriaType == 27 then
|
|
cache = SearchForField("questID", assetID);
|
|
elseif criteriaType == 36 or criteriaType == 42 then
|
|
criteriaObject.providers = {{ "i", assetID }};
|
|
elseif criteriaType == 110 or criteriaType == 29 or criteriaType == 69 or criteriaType == 52 or criteriaType == 53 or criteriaType == 54 or criteriaType == 32 then
|
|
-- Ignored
|
|
else
|
|
--print("Unhandled Criteria Type", criteriaType, assetID);
|
|
end
|
|
if cache and #cache > 0 then
|
|
local uniques = {};
|
|
MergeObjects(uniques, cache);
|
|
for i,p in ipairs(uniques) do
|
|
rawset(p, "text", nil);
|
|
for key,value in pairs(p) do
|
|
criteriaObject[key] = value;
|
|
end
|
|
end
|
|
end
|
|
criteriaObject.parent = o;
|
|
tinsert(searchResults, criteriaObject);
|
|
end
|
|
end
|
|
end
|
|
elseif cmd == "meta_achievement" then
|
|
-- Instruction to search the full database for multiple achievementID's
|
|
local cache;
|
|
for i=2,#sym do
|
|
local cache = SearchForField("achievementID", sym[i]);
|
|
if #cache > 0 then
|
|
for k,result in ipairs(cache) do
|
|
local ref = ResolveSymbolicLink(result);
|
|
if ref then
|
|
local cs = app.CloneReference(result);
|
|
if not cs.g then cs.g = {}; end
|
|
for i,m in ipairs(ref) do
|
|
tinsert(cs.g, m);
|
|
end
|
|
tinsert(searchResults, cs);
|
|
else
|
|
tinsert(searchResults, result);
|
|
end
|
|
end
|
|
else
|
|
print("Failed to select achievementID", sym[i]);
|
|
end
|
|
end
|
|
-- Remove any Criteria groups associated with those achievements
|
|
for k=#searchResults,1,-1 do
|
|
local result = searchResults[k];
|
|
if result.criteriaID then tremove(searchResults, k); end
|
|
end
|
|
elseif cmd == "partial_achievement" then
|
|
-- Instruction to search the full database for an achievementID and persist the associated Criteria
|
|
-- Do nothing, this is done in the mini list instead. We don't want to build a useless list of criteria.
|
|
end
|
|
end
|
|
|
|
-- If we have any pending finalizations to make, then merge them into the finalized table. [Equivalent to a "finalize" instruction]
|
|
if #searchResults > 0 then
|
|
for k,result in ipairs(searchResults) do
|
|
tinsert(finalized, result);
|
|
end
|
|
end
|
|
|
|
-- If we had any finalized search results, then return it.
|
|
if #finalized > 0 then
|
|
-- print("Symbolic Link for ", o.key, " ", o[o.key], " contains ", #finalized, " values after filtering.");
|
|
o.resolved = finalized;
|
|
return finalized;
|
|
else
|
|
-- print("Symbolic Link for ", o.key, " ", o[o.key], " contained no values after filtering.");
|
|
end
|
|
end
|
|
end
|
|
end)();
|
|
app.ResolveSymbolicLink = ResolveSymbolicLink;
|
|
local function BuildContainsInfo(groups, entries, paramA, paramB, indent, layer)
|
|
for i,group in ipairs(groups) do
|
|
if app.RecursiveGroupRequirementsFilter(group) then
|
|
local right = nil;
|
|
if group.total and (group.total > 1 or (group.total > 0 and not group.collectible)) then
|
|
if (group.progress / group.total) < 1 or app.Settings:Get("Show:CompletedGroups") then
|
|
right = GetProgressColorText(group.progress, group.total);
|
|
end
|
|
elseif paramA and paramB and (not group[paramA] or (group[paramA] and group[paramA] ~= paramB)) then
|
|
if group.collectible then
|
|
if group.collected then
|
|
if app.Settings:Get("Show:CollectedThings") then
|
|
right = app.GetCollectionIcon(group.collected);
|
|
end
|
|
else
|
|
right = L["NOT_COLLECTED_ICON"];
|
|
end
|
|
elseif group.trackable then
|
|
if app.Settings:Get("Show:TrackableThings") then
|
|
if group.saved then
|
|
if app.Settings:Get("Show:CollectedThings") then
|
|
right = L["COMPLETE_ICON"];
|
|
end
|
|
else
|
|
right = L["NOT_COLLECTED_ICON"];
|
|
end
|
|
elseif group.visible then
|
|
right = group.count and (group.count .. "x") or "---";
|
|
end
|
|
elseif group.visible then
|
|
right = group.count and (group.count .. "x") or "---";
|
|
end
|
|
end
|
|
|
|
if right then
|
|
-- Insert into the display.
|
|
local o = { prefix = indent, group = group, right = right };
|
|
if group.u then
|
|
local phase = L.PHASES[group.u];
|
|
if phase and (not phase.buildVersion or app.GameBuildVersion < phase.buildVersion) then
|
|
o.texture = L["UNOBTAINABLE_ITEM_TEXTURES"][phase.state];
|
|
end
|
|
elseif group.e then
|
|
o.texture = L["UNOBTAINABLE_ITEM_TEXTURES"][4];
|
|
end
|
|
if o.texture then
|
|
o.prefix = o.prefix:sub(4) .. "|T" .. o.texture .. ":0|t ";
|
|
o.texture = nil;
|
|
end
|
|
tinsert(entries, o);
|
|
|
|
-- Only go down one more level.
|
|
if layer < 2 and group.g and #group.g > 0 and not group.symbolized then
|
|
BuildContainsInfo(group.g, entries, paramA, paramB, indent .. " ", layer + 1);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
local function BuildReagentInfo(groups, entries, paramA, paramB, indent, layer)
|
|
for i,group in ipairs(groups) do
|
|
if app.RecursiveGroupRequirementsFilter(group) then
|
|
local o = { prefix = indent, group = group };
|
|
if group.u then
|
|
local phase = L.PHASES[group.u];
|
|
if phase and (not phase.buildVersion or app.GameBuildVersion < phase.buildVersion) then
|
|
o.texture = L["UNOBTAINABLE_ITEM_TEXTURES"][phase.state];
|
|
end
|
|
elseif group.e then
|
|
o.texture = L["UNOBTAINABLE_ITEM_TEXTURES"][4];
|
|
end
|
|
if o.texture then
|
|
o.prefix = o.prefix:sub(4) .. "|T" .. o.texture .. ":0|t ";
|
|
o.texture = nil;
|
|
end
|
|
if group.count then
|
|
o.right = group.count .. "x";
|
|
end
|
|
tinsert(entries, o);
|
|
end
|
|
end
|
|
end
|
|
|
|
local InitialCachedSearch;
|
|
local IsQuestReadyForTurnIn = app.IsQuestReadyForTurnIn;
|
|
local SourceLocationSettingsKey = setmetatable({
|
|
creatureID = "SourceLocations:Creatures",
|
|
}, {
|
|
__index = function(t, key)
|
|
return "SourceLocations:Things";
|
|
end
|
|
});
|
|
local UnobtainableTexture = "|T" .. app.asset("status-unobtainable") .. ":0|t";
|
|
local function HasCost(group, idType, id)
|
|
-- check if the group has a cost which includes the given parameters
|
|
if group.cost and type(group.cost) == "table" then
|
|
if idType == "itemID" then
|
|
for i,c in ipairs(group.cost) do
|
|
if c[2] == id and c[1] == "i" then
|
|
return true;
|
|
end
|
|
end
|
|
elseif idType == "currencyID" then
|
|
for i,c in ipairs(group.cost) do
|
|
if c[2] == id and c[1] == "c" then
|
|
return true;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return false;
|
|
end
|
|
local function SortByCommonBossDrops(a, b)
|
|
return not (a.headerID and a.headerID == app.HeaderConstants.COMMON_BOSS_DROPS) and b.headerID and b.headerID == app.HeaderConstants.COMMON_BOSS_DROPS;
|
|
end
|
|
local function SortByCraftTypeID(a, b)
|
|
local craftTypeA = a.craftTypeID or 0;
|
|
local craftTypeB = b.craftTypeID or 0;
|
|
if craftTypeA == craftTypeB then
|
|
return (a.name or RETRIEVING_DATA) < (b.name or RETRIEVING_DATA);
|
|
end
|
|
return craftTypeA > craftTypeB;
|
|
end
|
|
|
|
local function AddSourceLinesForTooltip(tooltipInfo, paramA, paramB, group)
|
|
if group and app.Settings:GetTooltipSetting("SourceLocations") and (not paramA or app.Settings:GetTooltipSetting(SourceLocationSettingsKey[paramA])) then
|
|
--print("SourceLocations", paramA, paramB);
|
|
local temp, text, parent = {}, nil, nil;
|
|
local unfiltered, right = {}, nil;
|
|
local showUnsorted = app.Settings:GetTooltipSetting("SourceLocations:Unsorted");
|
|
local showCompleted = app.Settings:GetTooltipSetting("SourceLocations:Completed");
|
|
local wrap = app.Settings:GetTooltipSetting("SourceLocations:Wrapping");
|
|
local FilterUnobtainable, FilterCharacter, FirstParent
|
|
= app.RecursiveUnobtainableFilter, app.RecursiveCharacterRequirementsFilter, app.GetRelativeGroup
|
|
local abbrevs = L["ABBREVIATIONS"];
|
|
|
|
-- Include Cost Sources
|
|
local sourceGroups = group;
|
|
if #sourceGroups == 0 and (paramA == "itemID" or paramA == "currencyID") then
|
|
local costGroups = SearchForField(paramA .. "AsCost", paramB);
|
|
if costGroups and #costGroups > 0 then
|
|
local regroup = {};
|
|
for i,g in ipairs(sourceGroups) do
|
|
tinsert(regroup, g);
|
|
end
|
|
for i,g in ipairs(costGroups) do
|
|
tinsert(regroup, g);
|
|
end
|
|
sourceGroups = regroup;
|
|
end
|
|
end
|
|
|
|
for _,j in ipairs(sourceGroups) do
|
|
parent = j.parent;
|
|
if parent and not FirstParent(j, "hideText") and parent.parent
|
|
and (showCompleted or not app.IsComplete(j))
|
|
and not HasCost(j, paramA, paramB)
|
|
then
|
|
text = app.GenerateSourcePathForTooltip(parent);
|
|
if showUnsorted or (not text:match(L.UNSORTED) and not text:match(L.HIDDEN_QUEST_TRIGGERS)) then
|
|
for source,replacement in pairs(abbrevs) do
|
|
text = text:gsub(source, replacement);
|
|
end
|
|
-- doesn't meet current unobtainable filters
|
|
if not FilterUnobtainable(parent) then
|
|
tinsert(unfiltered, { text, UnobtainableTexture });
|
|
-- from obtainable, different character source
|
|
elseif not FilterCharacter(parent) then
|
|
tinsert(unfiltered, { text, "|T374223:0|t" });
|
|
else
|
|
-- check if this needs an unobtainable icon even though it's being shown
|
|
right = GetUnobtainableTexture(FirstParent(parent, "e") or FirstParent(parent, "u") or j) or (j.rwp and app.asset("status-prerequisites"));
|
|
tinsert(temp, { text, right and ("|T" .. right .. ":0|t") });
|
|
end
|
|
end
|
|
end
|
|
end
|
|
-- if in Debug or no sources visible, add any unfiltered sources
|
|
if app.MODE_DEBUG or (#temp < 1 and not (paramA == "creatureID" or paramA == "encounterID")) then
|
|
for _,data in ipairs(unfiltered) do
|
|
tinsert(temp, data);
|
|
end
|
|
end
|
|
if #temp > 0 then
|
|
local listing = {};
|
|
local maximum = app.Settings:GetTooltipSetting("Locations");
|
|
local count = 0;
|
|
app.Sort(temp, app.SortDefaults.IndexOneStrings);
|
|
for _,data in ipairs(temp) do
|
|
text = data[1];
|
|
right = data[2];
|
|
if right and right:len() > 0 then
|
|
text = text .. " " .. right;
|
|
end
|
|
if not contains(listing, text) then
|
|
count = count + 1;
|
|
if count <= maximum then
|
|
tinsert(listing, text);
|
|
end
|
|
end
|
|
end
|
|
if count > maximum then
|
|
tinsert(listing, (L.AND_OTHER_SOURCES):format(count - maximum));
|
|
end
|
|
if #listing > 0 then
|
|
tooltipInfo.hasSourceLocations = true;
|
|
for _,text in ipairs(listing) do
|
|
if not group.working and IsRetrieving(text) then group.working = true; end
|
|
local left, right = DESCRIPTION_SEPARATOR:split(text);
|
|
tinsert(tooltipInfo, { left = left, right = right, wrap = wrap });
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
local SourceShowKeys = {
|
|
["achievementID"] = true,
|
|
["creatureID"] = true,
|
|
["expansionID"] = false,
|
|
["explorationID"] = true,
|
|
["factionID"] = true,
|
|
["flightpathID"] = true,
|
|
["headerID"] = false,
|
|
["itemID"] = true,
|
|
["speciesID"] = true,
|
|
["titleID"] = true,
|
|
};
|
|
if app.GameBuildVersion < 20000 then
|
|
SourceShowKeys.spellID = true;
|
|
end
|
|
app.Settings.CreateInformationType("SourceLocations", {
|
|
priority = 2.7,
|
|
text = "Source Locations",
|
|
HideCheckBox = true,
|
|
Process = function(t, data, tooltipInfo)
|
|
local key, id = data.key, data[data.key];
|
|
if key and id and SourceShowKeys[key] then
|
|
if tooltipInfo.hasSourceLocations then return; end
|
|
AddSourceLinesForTooltip(tooltipInfo, key, id, app.SearchForField(key, id));
|
|
end
|
|
end
|
|
})
|
|
|
|
local GetRelativeDifficulty = app.GetRelativeDifficulty
|
|
---@param method function
|
|
---@param paramA string
|
|
---@param paramB number
|
|
local function GetSearchResults(method, paramA, paramB, ...)
|
|
-- app.PrintDebug("GetSearchResults",method,paramA,paramB,...)
|
|
if not method then
|
|
print("GetSearchResults: Invalid method: nil");
|
|
return nil, true;
|
|
end
|
|
if not paramA then
|
|
print("GetSearchResults: Invalid paramA: nil");
|
|
return nil, true;
|
|
end
|
|
|
|
-- If we are searching for only one parameter, it is a raw link.
|
|
local rawlink;
|
|
if paramB then paramB = tonumber(paramB) or paramB;
|
|
else rawlink = paramA; end
|
|
|
|
-- This method can be called nested, and some logic should only process for the initial call
|
|
local isTopLevelSearch;
|
|
if not InitialCachedSearch then
|
|
InitialCachedSearch = true;
|
|
isTopLevelSearch = true;
|
|
end
|
|
|
|
-- Determine if this tooltip needs more work the next time it refreshes.
|
|
local tooltipInfo, mostAccessibleSource = {}, nil;
|
|
|
|
-- Call to the method to search the database.
|
|
local group, a, b = method(paramA, paramB);
|
|
if group then
|
|
if a then paramA = a; end
|
|
if b then paramB = b; end
|
|
-- Move all post processing here?
|
|
if paramA == "creatureID" or paramA == "npcID" or paramA == "encounterID" or paramA == "objectID" then
|
|
local subgroup = {};
|
|
for _,j in ipairs(group) do
|
|
if not j.ShouldExcludeFromTooltip then
|
|
tinsert(subgroup, j);
|
|
end
|
|
end
|
|
group = subgroup;
|
|
|
|
local regroup = {};
|
|
if app.MODE_DEBUG or true then
|
|
for i,j in ipairs(group) do
|
|
tinsert(regroup, j);
|
|
end
|
|
else
|
|
if app.MODE_ACCOUNT then
|
|
for i,j in ipairs(group) do
|
|
if app.RecursiveUnobtainableFilter(j) then
|
|
if j.questID and j.itemID then
|
|
if not j.saved then
|
|
-- Only show the item on the tooltip if the quest is active and incomplete or the item is a provider.
|
|
if C_QuestLog_IsOnQuest(j.questID) then
|
|
if not IsQuestReadyForTurnIn(j.questID) then
|
|
tinsert(regroup, j);
|
|
end
|
|
elseif j.providers then
|
|
tinsert(regroup, j);
|
|
else
|
|
-- Do a quick search on the itemID.
|
|
local searchResults = SearchForField("itemID", j.itemID);
|
|
if #searchResults > 0 then
|
|
for k,searchResult in ipairs(searchResults) do
|
|
if searchResult.providers then
|
|
for l,provider in ipairs(searchResult.providers) do
|
|
if provider[1] == 'i' and provider[2] == j.itemID then
|
|
tinsert(regroup, j);
|
|
break;
|
|
end
|
|
end
|
|
break;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
else
|
|
tinsert(regroup, j);
|
|
end
|
|
end
|
|
end
|
|
else
|
|
for i,j in ipairs(group) do
|
|
if app.RecursiveCharacterRequirementsFilter(j) and app.RecursiveUnobtainableFilter(j) and app.RecursiveGroupRequirementsFilter(j) then
|
|
if j.questID and j.itemID then
|
|
if not j.saved then
|
|
-- Only show the item on the tooltip if the quest is active and incomplete or the item is a provider.
|
|
if C_QuestLog_IsOnQuest(j.questID) then
|
|
if not IsQuestReadyForTurnIn(j.questID) then
|
|
tinsert(regroup, j);
|
|
end
|
|
elseif j.providers then
|
|
tinsert(regroup, j);
|
|
else
|
|
-- Do a quick search on the itemID.
|
|
local searchResults = SearchForField("itemID", j.itemID);
|
|
if #searchResults > 0 then
|
|
for k,searchResult in ipairs(searchResults) do
|
|
if searchResult.providers then
|
|
for l,provider in ipairs(searchResult.providers) do
|
|
if provider[1] == 'i' and provider[2] == j.itemID then
|
|
tinsert(regroup, j);
|
|
break;
|
|
end
|
|
end
|
|
break;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
else
|
|
tinsert(regroup, j);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if #regroup > 0 then
|
|
app.Sort(regroup, SortByCommonBossDrops);
|
|
end
|
|
group = regroup;
|
|
elseif paramA == "titleID" or paramA == "followerID" then
|
|
-- Don't do anything
|
|
local regroup = {};
|
|
if app.MODE_ACCOUNT then
|
|
for i,j in ipairs(group) do
|
|
if app.RecursiveUnobtainableFilter(j) then
|
|
tinsert(regroup, setmetatable({["g"] = {}}, { __index = j }));
|
|
end
|
|
end
|
|
else
|
|
for i,j in ipairs(group) do
|
|
if app.RecursiveCharacterRequirementsFilter(j) and app.RecursiveUnobtainableFilter(j) and app.RecursiveGroupRequirementsFilter(j) then
|
|
tinsert(regroup, setmetatable({["g"] = {}}, { __index = j }));
|
|
end
|
|
end
|
|
end
|
|
|
|
group = regroup;
|
|
end
|
|
else
|
|
group = {};
|
|
end
|
|
group.working = nil;
|
|
|
|
-- For Creatures that are inside of an instance, we only want the data relevant for the instance.
|
|
|
|
-- Determine if this is a search for an item
|
|
local itemID, itemString;
|
|
if rawlink then
|
|
---@diagnostic disable-next-line: undefined-field
|
|
itemString = rawlink:match("item[%-?%d:]+");
|
|
if itemString then
|
|
---@diagnostic disable-next-line: undefined-field
|
|
local itemID2 = select(2, (":"):split(itemString));
|
|
if itemID2 then
|
|
itemID = tonumber(itemID2) or itemID2;
|
|
paramA = "itemID";
|
|
paramB = itemID;
|
|
end
|
|
if not itemID or itemID == 0 then return nil, true; end
|
|
else
|
|
---@diagnostic disable-next-line: undefined-field
|
|
local kind, id = (":"):split(rawlink);
|
|
if id == "" then return nil, true; end
|
|
kind = kind:lower();
|
|
if id then id = tonumber(id) or id; end
|
|
if kind == "itemid" then
|
|
paramA = "itemID";
|
|
paramB = id;
|
|
itemID = id;
|
|
elseif kind == "questid" then
|
|
paramA = "questID";
|
|
paramB = id;
|
|
elseif kind == "creatureid" or kind == "npcid" then
|
|
paramA = "creatureID";
|
|
paramB = id;
|
|
end
|
|
end
|
|
elseif paramA == "itemID" then
|
|
itemID = paramB;
|
|
end
|
|
|
|
-- Find the most accessible version of the thing we're looking for.
|
|
if paramA == "spellID" and not itemID then
|
|
-- We want spells to have higher preference for the spell itself rather than the recipe.
|
|
for i,j in ipairs(group) do
|
|
if j.itemID then j.AccessibilityScore = j.AccessibilityScore + 100; end
|
|
end
|
|
end
|
|
app.Sort(group, app.SortDefaults.Accessibility);
|
|
--[[
|
|
for i,j in ipairs(group) do
|
|
print(i, j.key, j[j.key], j.text, j.AccessibilityScore);
|
|
end
|
|
]]--
|
|
for i,j in ipairs(group) do
|
|
if j[paramA] == paramB then
|
|
mostAccessibleSource = j;
|
|
--print("Most Accessible", i, j.text);
|
|
break;
|
|
end
|
|
end
|
|
|
|
-- Create a list of sources
|
|
if isTopLevelSearch then
|
|
AddSourceLinesForTooltip(tooltipInfo, paramA, paramB, group);
|
|
end
|
|
|
|
-- Create an unlinked version of the object.
|
|
if not group.g then
|
|
local merged = {};
|
|
for i,o in ipairs(group) do
|
|
MergeClone(merged, o);
|
|
end
|
|
if #merged == 1 and merged[1][paramA] == paramB then
|
|
group = merged[1];
|
|
local symbolicLink = ResolveSymbolicLink(group);
|
|
if symbolicLink then
|
|
if group.g and #group.g >= 0 then
|
|
for j=1,#symbolicLink,1 do
|
|
MergeClone(group.g, symbolicLink[j]);
|
|
end
|
|
else
|
|
for j=#symbolicLink,1,-1 do
|
|
symbolicLink[j] = CloneClassInstance(symbolicLink[j]);
|
|
end
|
|
group.g = symbolicLink;
|
|
end
|
|
end
|
|
else
|
|
for i,o in ipairs(merged) do
|
|
local symbolicLink = ResolveSymbolicLink(o);
|
|
if symbolicLink then
|
|
o.symbolized = true;
|
|
if o.g and #o.g >= 0 then
|
|
for j=1,#symbolicLink,1 do
|
|
MergeClone(o.g, symbolicLink[j]);
|
|
end
|
|
else
|
|
for j=#symbolicLink,1,-1 do
|
|
symbolicLink[j] = CloneClassInstance(symbolicLink[j]);
|
|
end
|
|
o.g = symbolicLink;
|
|
end
|
|
end
|
|
end
|
|
group = CloneClassInstance({ [paramA] = paramB, key = paramA });
|
|
group.g = merged;
|
|
end
|
|
end
|
|
|
|
if mostAccessibleSource then
|
|
group.parent = mostAccessibleSource.parent;
|
|
group.awp = mostAccessibleSource.awp;
|
|
group.rwp = mostAccessibleSource.rwp;
|
|
group.e = mostAccessibleSource.e;
|
|
group.u = mostAccessibleSource.u;
|
|
group.f = mostAccessibleSource.f;
|
|
end
|
|
|
|
-- Resolve Cost
|
|
--print("GetSearchResults", paramA, paramB);
|
|
if paramA == "currencyID" then
|
|
local costResults = SearchForField("currencyIDAsCost", paramB);
|
|
if #costResults > 0 then
|
|
if not group.g then group.g = {} end
|
|
local usedToBuy = app.CreateCustomHeader(app.HeaderConstants.VENDORS);
|
|
usedToBuy.text = "Currency For";
|
|
if not usedToBuy.g then usedToBuy.g = {}; end
|
|
for i,o in ipairs(costResults) do
|
|
MergeClone(usedToBuy.g, o);
|
|
end
|
|
MergeObject(group.g, usedToBuy);
|
|
end
|
|
elseif paramA == "itemID" then
|
|
local costResults = SearchForField("itemIDAsCost", paramB);
|
|
if #costResults > 0 then
|
|
if not group.g then group.g = {} end
|
|
local attunement = app.CreateCustomHeader(app.HeaderConstants.QUESTS);
|
|
if not attunement.g then attunement.g = {}; end
|
|
local usedToBuy = app.CreateCustomHeader(app.HeaderConstants.VENDORS);
|
|
if not usedToBuy.g then usedToBuy.g = {}; end
|
|
for i,o in ipairs(costResults) do
|
|
if o.key == "instanceID" or ((o.key == "difficultyID" or o.key == "mapID" or o.key == "headerID") and (o.parent and GetRelativeValue(o.parent, "instanceID")) and not o[o.key] == app.HeaderConstants.REWARDS) then
|
|
if app.Settings.Collectibles.Quests then
|
|
local d = CloneClassInstance(o);
|
|
d.sourceParent = o.parent;
|
|
d.collectible = true;
|
|
d.collected = GetItemCount(paramB, true) > 0;
|
|
d.progress = nil;
|
|
d.total = nil;
|
|
d.g = {};
|
|
MergeObject(attunement.g, d);
|
|
end
|
|
else
|
|
MergeClone(usedToBuy.g, o);
|
|
end
|
|
end
|
|
if #attunement.g > 0 then
|
|
attunement.text = "Attunement For";
|
|
MergeObject(group.g, attunement);
|
|
end
|
|
if #usedToBuy.g > 0 then
|
|
usedToBuy.text = "Currency For";
|
|
MergeObject(group.g, usedToBuy);
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Only need to build/update groups from the top level
|
|
if isTopLevelSearch and group.g then
|
|
group.total = 0;
|
|
group.progress = 0;
|
|
--AssignChildren(group); -- Turning this off fixed a bug with objective tooltips.
|
|
app.UpdateGroups(group, group.g);
|
|
if group.collectible then
|
|
group.total = group.total + 1;
|
|
if group.collected then
|
|
group.progress = group.progress + 1;
|
|
end
|
|
end
|
|
end
|
|
|
|
if isTopLevelSearch then
|
|
-- Add various extra field info if enabled in settings
|
|
group.itemString = itemString
|
|
app.ProcessInformationTypesForExternalTooltips(tooltipInfo, group);
|
|
end
|
|
|
|
|
|
if app.Settings:GetTooltipSetting("SummarizeThings") then
|
|
-- Contents
|
|
if group.g and #group.g > 0 then
|
|
local entries = {};
|
|
BuildContainsInfo(group.g, entries, paramA, paramB, " ", app.noDepth and 99 or 1);
|
|
if #entries > 0 then
|
|
local currentMapID = app.CurrentMapID;
|
|
local realmName, left, right, entry = GetRealmName();
|
|
tinsert(tooltipInfo, { left = "Contains:" });
|
|
if #entries < 25 then
|
|
for i,item in ipairs(entries) do
|
|
entry = item.group;
|
|
left = entry.text or RETRIEVING_DATA;
|
|
if not group.working and IsRetrieving(left) then group.working = true; end
|
|
local mapID = app.GetBestMapForGroup(entry, currentMapID);
|
|
if mapID and mapID ~= currentMapID then left = left .. " (" .. app.GetMapName(mapID) .. ")"; end
|
|
if entry.icon then item.prefix = item.prefix .. "|T" .. entry.icon .. ":0|t "; end
|
|
|
|
-- If this entry has specialization requirements, let's attempt to show the specialization icons.
|
|
right = item.right;
|
|
local specs = entry.specs;
|
|
if specs and #specs > 0 then
|
|
right = app.GetSpecsString(specs, false, false) .. right;
|
|
else
|
|
local c = entry.c;
|
|
if c and #c > 0 then
|
|
right = app.GetClassesString(c, false, false) .. right;
|
|
end
|
|
end
|
|
|
|
tinsert(tooltipInfo, { left = item.prefix .. left, right = right });
|
|
end
|
|
else
|
|
for i=1,math.min(25, #entries) do
|
|
local item = entries[i];
|
|
left = item.group.text or RETRIEVING_DATA;
|
|
if not group.working and IsRetrieving(left) then group.working = true; end
|
|
local mapID = app.GetBestMapForGroup(item.group, currentMapID);
|
|
if mapID and mapID ~= currentMapID then left = left .. " (" .. app.GetMapName(mapID) .. ")"; end
|
|
if item.group.icon then item.prefix = item.prefix .. "|T" .. item.group.icon .. ":0|t "; end
|
|
tinsert(tooltipInfo, { left = item.prefix .. left, right = item.right });
|
|
end
|
|
local more = #entries - 25;
|
|
if more > 0 then tinsert(tooltipInfo, { left = "And " .. more .. " more..." }); end
|
|
end
|
|
end
|
|
end
|
|
|
|
if itemID then
|
|
local reagentCache = AllTheThingsAD.Reagents;
|
|
if reagentCache then
|
|
reagentCache = reagentCache[itemID];
|
|
else
|
|
AllTheThingsAD.Reagents = {};
|
|
end
|
|
if reagentCache then
|
|
-- Crafted Items
|
|
if app.Settings:GetTooltipSetting("Show:CraftedItems") then
|
|
local crafted = {};
|
|
for craftedItemID,count in pairs(reagentCache[2]) do
|
|
local item = app.CreateItem(craftedItemID);
|
|
item.count = count;
|
|
tinsert(crafted, item);
|
|
end
|
|
if #crafted > 0 then
|
|
local entries = {};
|
|
BuildReagentInfo(crafted, entries, paramA, paramB, " ", app.noDepth and 99 or 1);
|
|
if #entries > 0 then
|
|
local left, right;
|
|
tinsert(tooltipInfo, { left = "Used to Craft:" });
|
|
if #entries < 25 then
|
|
app.Sort(entries, function(a, b)
|
|
if a.group.name then
|
|
if b.group.name then
|
|
return a.group.name <= b.group.name;
|
|
end
|
|
return true;
|
|
end
|
|
return false;
|
|
end);
|
|
for i,item in ipairs(entries) do
|
|
left = item.group.text or RETRIEVING_DATA;
|
|
if not group.working and IsRetrieving(left) then group.working = true; end
|
|
if item.group.icon then item.prefix = item.prefix .. "|T" .. item.group.icon .. ":0|t "; end
|
|
tinsert(tooltipInfo, { left = item.prefix .. left, right = item.right });
|
|
end
|
|
else
|
|
for i=1,math.min(25, #entries) do
|
|
local item = entries[i];
|
|
left = item.group.text or RETRIEVING_DATA;
|
|
if not group.working and IsRetrieving(left) then group.working = true; end
|
|
if item.group.icon then item.prefix = item.prefix .. "|T" .. item.group.icon .. ":0|t "; end
|
|
tinsert(tooltipInfo, { left = item.prefix .. left, right = item.right });
|
|
end
|
|
local more = #entries - 25;
|
|
if more > 0 then tinsert(tooltipInfo, { left = "And " .. more .. " more..." }); end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Recipes
|
|
if app.Settings:GetTooltipSetting("Show:Recipes") then
|
|
local recipes = {};
|
|
for spellID,count in pairs(reagentCache[1]) do
|
|
local spell = app.CreateSpell(spellID);
|
|
spell.count = count;
|
|
tinsert(recipes, spell);
|
|
end
|
|
if #recipes > 0 then
|
|
if app.Settings:GetTooltipSetting("Show:OnlyShowNonTrivialRecipes") then
|
|
local nonTrivialRecipes = {};
|
|
for _, o in pairs(recipes) do
|
|
local craftTypeID = o.craftTypeID;
|
|
if craftTypeID and craftTypeID > 0 then
|
|
tinsert(nonTrivialRecipes, o);
|
|
end
|
|
end
|
|
recipes = nonTrivialRecipes;
|
|
end
|
|
app.Sort(recipes, SortByCraftTypeID);
|
|
local entries, left = {}, nil;
|
|
BuildReagentInfo(recipes, entries, paramA, paramB, " ", app.noDepth and 99 or 1);
|
|
if #entries > 0 then
|
|
tinsert(tooltipInfo, { left = "Used in Recipes:" });
|
|
if #entries < 25 then
|
|
for i,item in ipairs(entries) do
|
|
left = item.group.craftText or item.group.text or RETRIEVING_DATA;
|
|
if not group.working and IsRetrieving(left) then group.working = true; end
|
|
if item.group.icon then item.prefix = item.prefix .. "|T" .. item.group.icon .. ":0|t "; end
|
|
tinsert(tooltipInfo, { left = item.prefix .. left, right = item.right });
|
|
end
|
|
else
|
|
for i=1,math.min(25, #entries) do
|
|
local item = entries[i];
|
|
left = item.group.craftText or item.group.text or RETRIEVING_DATA;
|
|
if not group.working and IsRetrieving(left) then group.working = true; end
|
|
if item.group.icon then item.prefix = item.prefix .. "|T" .. item.group.icon .. ":0|t "; end
|
|
tinsert(tooltipInfo, { left = item.prefix .. left, right = item.right });
|
|
end
|
|
local more = #entries - 25;
|
|
if more > 0 then tinsert(tooltipInfo, { left = "And " .. more .. " more..." }); end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- If there was any informational text generated, then attach that info.
|
|
if #tooltipInfo > 0 then
|
|
for i,item in ipairs(tooltipInfo) do
|
|
if item.color then item.a, item.r, item.g, item.b = HexToARGB(item.color); end
|
|
end
|
|
group.tooltipInfo = tooltipInfo;
|
|
end
|
|
|
|
-- Cache the result depending on if there is more work to be done.
|
|
if isTopLevelSearch then InitialCachedSearch = nil; end
|
|
return group, group.working;
|
|
end
|
|
app.GetCachedSearchResults = function(method, paramA, paramB, ...)
|
|
return app.GetCachedData((paramB and table.concat({ paramA, paramB, ...}, ":")) or paramA, GetSearchResults, method, paramA, paramB, ...);
|
|
end
|
|
|
|
-- Temporary functions to update the cache without breaking ATT.
|
|
local function UpdateRawID(field, id)
|
|
app:RefreshDataQuietly("UpdateRawID", true);
|
|
end
|
|
app.UpdateRawID = UpdateRawID;
|
|
-- Temporary functions to update the cache without breaking ATT.
|
|
local function UpdateRawIDs(field, ids)
|
|
if ids and #ids > 0 then
|
|
app:RefreshDataQuietly("UpdateRawIDs", true);
|
|
end
|
|
end
|
|
app.UpdateRawIDs = UpdateRawIDs;
|
|
|
|
-- Item Information Lib
|
|
local function SearchForLink(link)
|
|
if link:match("item") then
|
|
-- Parse the link and get the itemID and bonus ids.
|
|
local itemString = link:match("item[%-?%d:]+") or link;
|
|
if itemString then
|
|
-- Cache the Item ID and the Suffix ID.
|
|
---@diagnostic disable-next-line: undefined-field
|
|
local _, itemID, _, _, _, _, _, suffixID = (":"):split(itemString);
|
|
if itemID then
|
|
-- Ensure that the itemID and suffixID are properly formatted.
|
|
itemID = tonumber(itemID) or 0;
|
|
return SearchForField("itemID", itemID), "itemID", itemID;
|
|
end
|
|
end
|
|
end
|
|
|
|
---@diagnostic disable-next-line: undefined-field
|
|
local kind, id = (":"):split(link);
|
|
kind = kind:lower():gsub("id", "ID");
|
|
if kind:sub(1,2) == "|c" then
|
|
kind = kind:sub(11);
|
|
end
|
|
if kind:sub(1,2) == "|h" then
|
|
kind = kind:sub(3);
|
|
end
|
|
---@diagnostic disable-next-line: undefined-field
|
|
if id then id = tonumber(("|["):split(id) or id); end
|
|
--print("SearchForLink A:", kind, id);
|
|
--print("SearchForLink B:", link:gsub("|c", "c"):gsub("|h", "h"));
|
|
if kind == "i" then
|
|
kind = "itemID";
|
|
elseif kind == "quest" or kind == "q" then
|
|
kind = "questID";
|
|
elseif kind == "faction" or kind == "rep" then
|
|
kind = "factionID";
|
|
elseif kind == "ach" or kind == "achievement" then
|
|
kind = "achievementID";
|
|
elseif kind == "creature" or kind == "npcID" or kind == "npc" or kind == "n" then
|
|
kind = "creatureID";
|
|
elseif kind == "currency" then
|
|
kind = "currencyID";
|
|
elseif kind == "spell" or kind == "enchant" or kind == "talent" or kind == "recipe" or kind == "mount" then
|
|
kind = "spellID";
|
|
elseif kind == "pet" or kind == "battlepet" then
|
|
kind = "speciesID";
|
|
elseif kind == "filterforrwp" then
|
|
kind = "filterForRWP";
|
|
elseif kind == "pettype" or kind == "pettypeID" then
|
|
kind = "petTypeID";
|
|
elseif kind == "azeriteessence" or kind == "azeriteessenceID" then
|
|
kind = "azeriteessenceID";
|
|
end
|
|
local cache;
|
|
if id then
|
|
cache = SearchForField(kind, id);
|
|
if #cache == 0 then
|
|
local obj = CloneClassInstance({
|
|
key = kind, [kind] = id,
|
|
hash = kind .. ":" .. id,
|
|
});
|
|
if not obj.__type then
|
|
obj.icon = 133878;
|
|
obj.text = "Search Results for '" .. obj.hash .. "'";
|
|
local response = app:BuildSearchResponse(app:GetDataCache().g, kind, id);
|
|
if response and #response > 0 then
|
|
obj.g = {};
|
|
for i,o in ipairs(response) do
|
|
tinsert(obj.g, o);
|
|
end
|
|
end
|
|
else
|
|
obj.description = "@Crieve: This has not been sourced in ATT yet!";
|
|
end
|
|
tinsert(cache, obj);
|
|
end
|
|
else
|
|
local obj = { hash = kind };
|
|
obj.icon = 133878;
|
|
obj.text = "Search Results for '" .. obj.hash .. "'";
|
|
local response = app:BuildSearchResponseForField(app:GetDataCache().g, kind);
|
|
if response and #response > 0 then
|
|
obj.g = {};
|
|
for i,o in ipairs(response) do
|
|
tinsert(obj.g, o);
|
|
end
|
|
end
|
|
cache = {};
|
|
tinsert(cache, obj);
|
|
end
|
|
return cache, kind, id;
|
|
end
|
|
app.SearchForLink = SearchForLink;
|
|
|
|
-- Force Bind on Pickup Items to require the profession within the craftables section.
|
|
function ProcessBindOnPickupProfession(profession, requireSkill)
|
|
if profession.requireSkill then
|
|
requireSkill = profession.requireSkill;
|
|
elseif profession.b and profession.itemID then
|
|
profession.requireSkill = requireSkill;
|
|
end
|
|
if profession.g then
|
|
for i,o in ipairs(profession.g) do
|
|
ProcessBindOnPickupProfession(o, requireSkill);
|
|
end
|
|
end
|
|
end
|
|
function ProcessBindOnPickupProfessions(craftables)
|
|
for i,profession in ipairs(craftables) do
|
|
ProcessBindOnPickupProfession(profession, profession.requireSkill);
|
|
end
|
|
end
|
|
|
|
-- TODO: Move the generation of this into Parser
|
|
function app:GetDataCache()
|
|
if app.Categories then
|
|
local rootData = setmetatable({
|
|
text = L["TITLE"],
|
|
hash = "ATT",
|
|
icon = app.asset("logo_32x32"),
|
|
preview = app.asset("Discord_2_128"),
|
|
description = L["DESCRIPTION"],
|
|
font = "GameFontNormalLarge",
|
|
expanded = true,
|
|
visible = true,
|
|
progress = 0,
|
|
total = 0,
|
|
g = {},
|
|
}, {
|
|
__index = function(t, key)
|
|
if key == "title" then
|
|
return t.modeString .. DESCRIPTION_SEPARATOR .. t.untilNextPercentage;
|
|
elseif key == "progressText" then
|
|
if t.total < 1 then
|
|
local primeData = app.CurrentCharacter.PrimeData;
|
|
if primeData then
|
|
return GetProgressColorText(primeData.progress, primeData.total);
|
|
end
|
|
end
|
|
return GetProgressColorText(t.progress, t.total);
|
|
elseif key == "modeString" then
|
|
return app.Settings:GetModeString();
|
|
elseif key == "untilNextPercentage" then
|
|
if t.total < 1 then
|
|
local primeData = app.CurrentCharacter.PrimeData;
|
|
if primeData then
|
|
return app.Modules.Color.GetProgressTextToNextPercent(primeData.progress, primeData.total);
|
|
end
|
|
end
|
|
return app.Modules.Color.GetProgressTextToNextPercent(t.progress, t.total);
|
|
else
|
|
-- Something that isn't dynamic.
|
|
return table[key];
|
|
end
|
|
end
|
|
});
|
|
local g = rootData.g;
|
|
|
|
-----------------------------------------
|
|
-- P R I M A R Y C A T E G O R I E S --
|
|
-----------------------------------------
|
|
-- Dungeons & Raids
|
|
if app.Categories.Instances then
|
|
tinsert(g, {
|
|
text = GROUP_FINDER,
|
|
icon = app.asset("Category_D&R"),
|
|
g = app.Categories.Instances,
|
|
});
|
|
end
|
|
|
|
-- Outdoor Zones
|
|
if app.Categories.Zones then
|
|
tinsert(g, {
|
|
mapID = 947,
|
|
text = BUG_CATEGORY2,
|
|
icon = app.asset("Category_Zones"),
|
|
g = app.Categories.Zones,
|
|
});
|
|
end
|
|
|
|
-- World Drops
|
|
if app.Categories.WorldDrops then
|
|
tinsert(g, {
|
|
text = TRANSMOG_SOURCE_4,
|
|
icon = app.asset("Category_WorldDrops"),
|
|
g = app.Categories.WorldDrops,
|
|
isWorldDropCategory = true
|
|
});
|
|
end
|
|
|
|
-- Crafted Items
|
|
if app.Categories.Craftables then
|
|
local craftables = app.Categories.Craftables;
|
|
ProcessBindOnPickupProfessions(craftables);
|
|
tinsert(g, {
|
|
text = LOOT_JOURNAL_LEGENDARIES_SOURCE_CRAFTED_ITEM,
|
|
icon = app.asset("Category_Crafting"),
|
|
DontEnforceSkillRequirements = true,
|
|
isCraftedCategory = true,
|
|
g = craftables,
|
|
});
|
|
end
|
|
|
|
-- Group Finder
|
|
if app.Categories.GroupFinder then
|
|
tinsert(g, {
|
|
text = DUNGEONS_BUTTON,
|
|
icon = app.asset("Category_GroupFinder"),
|
|
u = 33, -- WRATH_PHASE_FOUR
|
|
g = app.Categories.GroupFinder,
|
|
});
|
|
end
|
|
|
|
-- Professions
|
|
local ProfessionsHeader = app.CreateCustomHeader(app.HeaderConstants.PROFESSIONS, {
|
|
g = app.Categories.Professions or {}
|
|
});
|
|
tinsert(g, ProfessionsHeader);
|
|
|
|
-- Holidays
|
|
if app.Categories.Holidays then
|
|
tinsert(g, app.CreateCustomHeader(app.HeaderConstants.HOLIDAYS, {
|
|
description = "These events occur at consistent dates around the year based on and themed around real world holiday events.",
|
|
g = app.Categories.Holidays,
|
|
SortType = "EventStart",
|
|
isHolidayCategory = true,
|
|
}));
|
|
end
|
|
|
|
-- Expansion Features
|
|
if app.Categories.ExpansionFeatures and #app.Categories.ExpansionFeatures > 0 then
|
|
tinsert(g, {
|
|
text = EXPANSION_FILTER_TEXT,
|
|
icon = app.asset("Category_ExpansionFeatures"),
|
|
g = app.Categories.ExpansionFeatures
|
|
});
|
|
end
|
|
|
|
-----------------------------------------
|
|
-- L I M I T E D C A T E G O R I E S --
|
|
-----------------------------------------
|
|
-- Character
|
|
if app.Categories.Character then
|
|
local db = {};
|
|
db.g = app.Categories.Character;
|
|
db.text = CHARACTER;
|
|
db.name = db.text;
|
|
db.icon = app.asset("Category_ItemSets");
|
|
tinsert(g, db);
|
|
end
|
|
|
|
-- PvP
|
|
if app.Categories.PVP then
|
|
tinsert(g, app.CreateCustomHeader(app.HeaderConstants.PVP, {
|
|
g = app.Categories.PVP,
|
|
isPVPCategory = true
|
|
}));
|
|
end
|
|
|
|
-- Promotions
|
|
if app.Categories.Promotions then
|
|
tinsert(g, {
|
|
text = BATTLE_PET_SOURCE_8,
|
|
icon = app.asset("Category_Promo"),
|
|
description = "This section is for real world promotions that seeped extremely rare content into the game prior to some of them appearing within the In-Game Shop.",
|
|
g = app.Categories.Promotions,
|
|
isPromotionCategory = true
|
|
});
|
|
end
|
|
|
|
-- Season of Discovery
|
|
if app.Categories.SeasonOfDiscovery then
|
|
for i,o in ipairs(app.Categories.SeasonOfDiscovery) do
|
|
tinsert(g, o);
|
|
end
|
|
end
|
|
|
|
-- Skills
|
|
if app.Categories.Skills then
|
|
tinsert(g, {
|
|
text = SKILLS,
|
|
icon = 136105,
|
|
g = app.Categories.Skills
|
|
});
|
|
end
|
|
|
|
-- World Events
|
|
if app.Categories.WorldEvents then
|
|
tinsert(g, {
|
|
text = BATTLE_PET_SOURCE_7;
|
|
icon = app.asset("Category_Event"),
|
|
description = "These events occur at different times in the game's timeline, typically as one time server wide events. Special celebrations such as Anniversary events and such may be found within this category.",
|
|
g = app.Categories.WorldEvents,
|
|
isEventCategory = true,
|
|
});
|
|
end
|
|
|
|
---------------------------------------
|
|
-- M A R K E T C A T E G O R I E S --
|
|
---------------------------------------
|
|
-- Black Market
|
|
if app.Categories.BlackMarket then tinsert(g, app.Categories.BlackMarket[1]); end
|
|
|
|
-- In-Game Store
|
|
if app.Categories.InGameShop then
|
|
tinsert(g, app.CreateCustomHeader(app.HeaderConstants.IN_GAME_SHOP, {
|
|
g = app.Categories.InGameShop,
|
|
expanded = false
|
|
}));
|
|
end
|
|
|
|
-----------------------------------------
|
|
-- D Y N A M I C C A T E G O R I E S --
|
|
-----------------------------------------
|
|
if app.Windows then
|
|
local keys,sortedList = {},{};
|
|
for suffix,window in pairs(app.Windows) do
|
|
if window and window.IsDynamicCategory then
|
|
if window.DynamicCategoryHeader then
|
|
if window.DynamicProfessionID then
|
|
local dynamicProfessionHeader = nil;
|
|
for i,header in ipairs(ProfessionsHeader.g) do
|
|
if header.requireSkill == window.DynamicProfessionID then
|
|
dynamicProfessionHeader = header;
|
|
break;
|
|
end
|
|
end
|
|
|
|
local recipesList = app.CreateDynamicCategory(suffix);
|
|
recipesList.IgnoreBuildRequests = true;
|
|
if dynamicProfessionHeader then
|
|
recipesList.text = "Recipes";
|
|
recipesList.icon = 134939;
|
|
if not dynamicProfessionHeader.g then
|
|
dynamicProfessionHeader.g = {};
|
|
end
|
|
tinsert(dynamicProfessionHeader.g, recipesList);
|
|
else
|
|
tinsert(ProfessionsHeader.g, recipesList);
|
|
end
|
|
else
|
|
print("Unhandled dynamic category conditional");
|
|
end
|
|
else
|
|
keys[suffix] = window;
|
|
end
|
|
end
|
|
end
|
|
for suffix,window in pairs(keys) do
|
|
tinsert(sortedList, suffix);
|
|
end
|
|
app.Sort(sortedList, app.SortDefaults.Strings);
|
|
for i,suffix in ipairs(sortedList) do
|
|
local dynamicCategory = app.CreateDynamicCategory(suffix);
|
|
dynamicCategory.sourceIgnored = 1;
|
|
tinsert(g, dynamicCategory);
|
|
end
|
|
end
|
|
|
|
-- Track Deaths!
|
|
tinsert(g, app:CreateDeathClass());
|
|
|
|
-- Yourself.
|
|
tinsert(g, app.CreateUnit("player", {
|
|
description = L.DEBUG_LOGIN,
|
|
races = { app.RaceIndex },
|
|
c = { app.ClassIndex },
|
|
r = app.FactionID,
|
|
collected = 1,
|
|
nmr = false,
|
|
OnUpdate = function(self)
|
|
self.lvl = app.Level;
|
|
if app.MODE_DEBUG then
|
|
self.collectible = true;
|
|
else
|
|
self.collectible = false;
|
|
end
|
|
end
|
|
}));
|
|
|
|
-- Now assign the parent hierarchy for this cache.
|
|
AssignChildren(rootData);
|
|
|
|
-- Now that we have all of the root data, cache it.
|
|
app.CacheFields(rootData);
|
|
|
|
-- Determine how many expansionID instances could be found
|
|
local expansionCounter = 0;
|
|
local expansionCache = SearchForFieldContainer("expansionID");
|
|
for key,value in pairs(expansionCache) do
|
|
expansionCounter = expansionCounter + 1;
|
|
end
|
|
if expansionCounter == 1 then
|
|
-- Purge the Expansion Objects. This is the Classic Layout style.
|
|
for key,values in pairs(expansionCache) do
|
|
for j,value in ipairs(values) do
|
|
local parent = value.parent;
|
|
if parent then
|
|
-- Remove the expansion object reference.
|
|
for i=#parent.g,1,-1 do
|
|
if parent.g[i] == value then
|
|
tremove(parent.g, i);
|
|
break;
|
|
end
|
|
end
|
|
|
|
-- Feed the children to its parent.
|
|
if value.g then
|
|
for i,child in ipairs(value.g) do
|
|
child.parent = parent;
|
|
tinsert(parent.g, child);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Wipe out the expansion object cache.
|
|
wipe(expansionCache);
|
|
end
|
|
|
|
-- All future calls to this function will return the root data.
|
|
app.GetDataCache = function()
|
|
return rootData;
|
|
end
|
|
return rootData;
|
|
end
|
|
end
|
|
|
|
|
|
-- Currency Lib
|
|
(function()
|
|
local CurrencyInfo = {};
|
|
local GetCurrencyCount;
|
|
---@diagnostic disable-next-line: undefined-global
|
|
local GetCurrencyLink = GetCurrencyLink;
|
|
local GetRelativeField = app.GetRelativeField;
|
|
if C_CurrencyInfo and C_CurrencyInfo.GetCurrencyInfo then
|
|
local C_CurrencyInfo_GetCurrencyInfo = C_CurrencyInfo.GetCurrencyInfo;
|
|
if C_CurrencyInfo.GetCurrencyLink then
|
|
GetCurrencyLink = C_CurrencyInfo.GetCurrencyLink;
|
|
end
|
|
setmetatable(CurrencyInfo, { __index = function(t, id)
|
|
local currencyInfo = C_CurrencyInfo_GetCurrencyInfo(id);
|
|
if currencyInfo then
|
|
local info = {
|
|
name = currencyInfo.name,
|
|
icon = currencyInfo.iconFileID
|
|
}
|
|
rawset(t, id, info);
|
|
return info;
|
|
end
|
|
end });
|
|
GetCurrencyCount = function(id)
|
|
local info = C_CurrencyInfo_GetCurrencyInfo(id);
|
|
return info and info.quantity or 0;
|
|
end
|
|
else
|
|
---@diagnostic disable-next-line: undefined-global
|
|
local GetCurrencyInfo = GetCurrencyInfo;
|
|
setmetatable(CurrencyInfo, { __index = function(t, id)
|
|
local name, amount, icon = GetCurrencyInfo(id);
|
|
if name then
|
|
local info = {
|
|
name = name,
|
|
icon = icon
|
|
}
|
|
rawset(t, id, info);
|
|
return info;
|
|
end
|
|
end });
|
|
GetCurrencyCount = function(id)
|
|
return select(2, GetCurrencyInfo(id)) or 0;
|
|
end
|
|
end
|
|
local CurrencyCollectibleAsCost = setmetatable({}, { __index = function(t, id)
|
|
local results = SearchForField("currencyIDAsCost", id, true);
|
|
if #results > 0 then
|
|
for _,ref in pairs(results) do
|
|
if ref.currencyID ~= id and app.RecursiveGroupRequirementsFilter(ref) then
|
|
if ref.collectible and not ref.collected then
|
|
t[id] = true;
|
|
return true;
|
|
elseif ref.total and ref.total > ref.progress then
|
|
t[id] = true;
|
|
return true;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
t[id] = false;
|
|
return false;
|
|
end });
|
|
local CurrencyRequirementTotals = setmetatable({}, { __index = function(t, id)
|
|
local results = SearchForField("currencyIDAsCost", id, true);
|
|
if #results > 0 then
|
|
local total = 0;
|
|
for _,ref in pairs(results) do
|
|
if ref.currencyID ~= id and app.RecursiveDefaultCharacterRequirementsFilter(ref) then
|
|
if ref.collectible and ref.collected ~= 1 then
|
|
if ref.cost then
|
|
for k,v in ipairs(ref.cost) do
|
|
if v[2] == id and v[1] == "c" then
|
|
total = total + (v[3] or 1);
|
|
end
|
|
end
|
|
end
|
|
elseif (ref.total and ref.total > 0 and ref.progress < ref.total) then
|
|
if ref.cost then
|
|
for k,v in ipairs(ref.cost) do
|
|
if v[2] == id and v[1] == "c" then
|
|
total = total + (v[3] or 1);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
t[id] = total;
|
|
return total;
|
|
end
|
|
t[id] = 0;
|
|
return 0;
|
|
end });
|
|
local CurrencyCollectedAsCost = setmetatable({}, { __index = function(t, id)
|
|
if CurrencyRequirementTotals[id] <= GetCurrencyCount(id) then
|
|
t[id] = true;
|
|
return true;
|
|
else
|
|
t[id] = false;
|
|
return false;
|
|
end
|
|
end });
|
|
app.AddEventHandler("OnRecalculate", function()
|
|
wipe(CurrencyCollectibleAsCost);
|
|
wipe(CurrencyCollectedAsCost);
|
|
wipe(CurrencyRequirementTotals);
|
|
end);
|
|
app.CreateCurrencyClass = app.CreateClass("Currency", "currencyID", {
|
|
["text"] = function(t)
|
|
return t.info.name;
|
|
end,
|
|
["icon"] = function(t)
|
|
return t.info.icon;
|
|
end,
|
|
["info"] = function(t)
|
|
local info = CurrencyInfo[t.currencyID];
|
|
if info then
|
|
t.info = info;
|
|
return info;
|
|
end
|
|
return {};
|
|
end,
|
|
["link"] = function(t)
|
|
return GetCurrencyLink(t.currencyID, 1);
|
|
end,
|
|
RefreshCollectionOnly = true,
|
|
["collectible"] = function(t)
|
|
return t.collectibleAsCost;
|
|
end,
|
|
["collectibleAsCost"] = function(t)
|
|
if not t.parent or not t.parent.saved then
|
|
if CurrencyCollectibleAsCost[t.currencyID] then
|
|
return true;
|
|
elseif t.simplemeta then
|
|
setmetatable(t, t.simplemeta);
|
|
return false;
|
|
end
|
|
end
|
|
end,
|
|
["collected"] = function(t)
|
|
return t.collectedAsCost;
|
|
end,
|
|
["collectedAsCost"] = function(t)
|
|
return CurrencyCollectedAsCost[t.currencyID];
|
|
end,
|
|
});
|
|
end)();
|
|
|
|
-- Recipe & Spell Lib
|
|
(function()
|
|
local grey = RGBToHex(0.75, 0.75, 0.75);
|
|
local craftColors = {
|
|
RGBToHex(0.25,0.75,0.25),
|
|
RGBToHex(1,1,0),
|
|
RGBToHex(1,0.5,0.25),
|
|
[0]=grey,
|
|
}
|
|
local CraftTypeIDToColor = function(craftTypeID)
|
|
return craftColors[craftTypeID] or grey;
|
|
end
|
|
app.CraftTypeToCraftTypeID = function(craftType)
|
|
if craftType then
|
|
if craftType == "optimal" then
|
|
return 3;
|
|
elseif craftType == "medium" then
|
|
return 2;
|
|
elseif craftType == "easy" then
|
|
return 1;
|
|
elseif craftType == "trivial" then
|
|
return 0;
|
|
end
|
|
end
|
|
return nil;
|
|
end
|
|
local MaxSpellRankPerSpellName = {};
|
|
local SpellIDToSpellName = {};
|
|
app.GetSpellName = function(spellID, rank)
|
|
local spellName = rawget(SpellIDToSpellName, spellID);
|
|
if spellName then return spellName; end
|
|
if rank then
|
|
spellName = GetSpellName(spellID, rank);
|
|
else
|
|
spellName = GetSpellName(spellID);
|
|
end
|
|
if not IsRetrieving(spellName) then
|
|
if not rawget(app.SpellNameToSpellID, spellName) then
|
|
rawset(app.SpellNameToSpellID, spellName, spellID);
|
|
if not rawget(SpellIDToSpellName, spellID) then
|
|
rawset(SpellIDToSpellName, spellID, spellName);
|
|
end
|
|
end
|
|
if rank then
|
|
if (rawget(MaxSpellRankPerSpellName, spellName) or 0) < rank then
|
|
rawset(MaxSpellRankPerSpellName, spellName, rank);
|
|
end
|
|
spellName = spellName .. " (" .. RANK .. " " .. rank .. ")";
|
|
if not rawget(app.SpellNameToSpellID, spellName) then
|
|
rawset(app.SpellNameToSpellID, spellName, spellID);
|
|
if not rawget(SpellIDToSpellName, spellID) then
|
|
rawset(SpellIDToSpellName, spellID, spellName);
|
|
end
|
|
end
|
|
end
|
|
return spellName;
|
|
end
|
|
end
|
|
local isSpellKnownHelper = function(spellID)
|
|
return spellID and (IsPlayerSpell(spellID) or IsSpellKnown(spellID, true) or IsSpellKnownOrOverridesKnown(spellID, true));
|
|
end
|
|
app.IsSpellKnown = function(spellID, rank, ignoreHigherRanks)
|
|
if isSpellKnownHelper(spellID) then return true; end
|
|
if rank then
|
|
local spellName = GetSpellName(spellID);
|
|
if spellName then
|
|
local maxRank = ignoreHigherRanks and rank or rawget(MaxSpellRankPerSpellName, spellName);
|
|
if maxRank then
|
|
spellName = spellName .. " (" .. RANK .. " ";
|
|
for i=maxRank,rank,-1 do
|
|
if isSpellKnownHelper(app.SpellNameToSpellID[spellName .. i .. ")"]) then
|
|
return true;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
app.SpellNameToSpellID = setmetatable(L.SPELL_NAME_TO_SPELL_ID, {
|
|
__index = function(t, key)
|
|
for _,spellID in pairs(app.SkillDB.SkillToSpell) do
|
|
app.GetSpellName(spellID);
|
|
end
|
|
for specID,spellID in pairs(app.SkillDB.SpecializationSpells) do
|
|
app.GetSpellName(spellID);
|
|
end
|
|
for spellID,g in pairs(SearchForFieldContainer("spellID")) do
|
|
local rank;
|
|
for i,o in ipairs(g) do
|
|
if o.rank then
|
|
rank = o.rank;
|
|
break;
|
|
end
|
|
end
|
|
app.GetSpellName(spellID, rank);
|
|
end
|
|
local numSpellTabs, offset, lastSpellName, currentSpellRank, lastSpellRank = GetNumSpellTabs(), 1, "", nil, 1;
|
|
for spellTabIndex=1,numSpellTabs do
|
|
local numSpells = select(4, GetSpellTabInfo(spellTabIndex));
|
|
for spellIndex=1,numSpells do
|
|
local spellName, _, _, _, _, _, spellID = GetSpellInfo(offset, BOOKTYPE_SPELL);
|
|
if spellName then
|
|
currentSpellRank = GetSpellRank(spellID);
|
|
if not currentSpellRank then
|
|
if lastSpellName == spellName then
|
|
currentSpellRank = lastSpellRank + 1;
|
|
else
|
|
lastSpellName = spellName;
|
|
currentSpellRank = 1;
|
|
end
|
|
end
|
|
app.GetSpellName(spellID, currentSpellRank);
|
|
if not rawget(t, spellName) then
|
|
rawset(t, spellName, spellID);
|
|
end
|
|
lastSpellRank = currentSpellRank;
|
|
offset = offset + 1;
|
|
end
|
|
end
|
|
end
|
|
return rawget(t, key);
|
|
end
|
|
});
|
|
|
|
-- The difference between a recipe and a spell is that a spell is not collectible.
|
|
local baseIconFromSpellID = function(t)
|
|
return GetSpellIcon(t.spellID) or (t.requireSkill and GetSpellIcon(t.requireSkill));
|
|
end;
|
|
local linkFromSpellID = function(t)
|
|
local link = GetSpellLink(t.spellID);
|
|
if not link then
|
|
if GetRelativeValue(t, "requireSkill") == 333 then
|
|
return "|cffffffff|Henchant:" .. t.spellID .. "|h[" .. t.name .. "]|h|r";
|
|
else
|
|
return "|cffffffff|Hspell:" .. t.spellID .. "|h[" .. t.name .. "]|h|r";
|
|
end
|
|
end
|
|
return link;
|
|
end;
|
|
local nameFromSpellID = function(t)
|
|
return app.GetSpellName(t.spellID) or ("Invalid Spell " .. t.spellID) or GetSpellLink(t.spellID) or RETRIEVING_DATA;
|
|
end;
|
|
local spellFields = {
|
|
CACHE = function() return "Spells" end,
|
|
IsClassIsolated = true,
|
|
["text"] = function(t)
|
|
return t.link;
|
|
end,
|
|
["craftText"] = function(t)
|
|
return Colorize(t.name, CraftTypeIDToColor(t.craftTypeID));
|
|
end,
|
|
["icon"] = function(t)
|
|
local icon = t.baseIcon;
|
|
if icon and icon ~= 136235 and icon ~= 136192 then
|
|
return icon;
|
|
end
|
|
return 134940;
|
|
end,
|
|
["description"] = app.GameBuildVersion < 20000 and function(t)
|
|
return GetSpellDescription(t.spellID);
|
|
end,
|
|
["craftTypeID"] = function(t)
|
|
return app.CurrentCharacter.SpellRanks[t.spellID] or 0;
|
|
end,
|
|
["trackable"] = function(t)
|
|
return GetSpellCooldown(t.spellID) > 0;
|
|
end,
|
|
["saved"] = function(t)
|
|
return GetSpellCooldown(t.spellID) > 0 and 1;
|
|
end,
|
|
["baseIcon"] = baseIconFromSpellID,
|
|
["name"] = nameFromSpellID,
|
|
["link"] = linkFromSpellID,
|
|
};
|
|
local createSpell = app.CreateClass("Spell", "spellID", spellFields);
|
|
|
|
local recipeFields = app.CloneDictionary(spellFields);
|
|
recipeFields.collectible = function(t)
|
|
return app.Settings.Collectibles.Recipes;
|
|
end;
|
|
recipeFields.collected = function(t)
|
|
if app.CurrentCharacter.Spells[t.spellID] then return 1; end
|
|
local state = app.SetCollected(t, "Spells", t.spellID, not t.nmc and app.IsSpellKnown(t.spellID, t.rank, GetRelativeValue(t, "requireSkill") == 261), "Recipes");
|
|
if state == 1 then return 1; elseif state == 2 and app.Settings.AccountWide.Recipes then return 2; end
|
|
end;
|
|
recipeFields.f = function(t)
|
|
return app.FilterConstants.RECIPES;
|
|
end;
|
|
recipeFields.IsClassIsolated = true;
|
|
recipeFields.RefreshCollectionOnly = true;
|
|
local createRecipe = app.CreateClass("Recipe", "spellID", recipeFields,
|
|
"WithItem", {
|
|
baseIcon = function(t)
|
|
return GetItemIcon(t.itemID) or baseIconFromSpellID(t);
|
|
end,
|
|
link = function(t)
|
|
return select(2, GetItemInfo(t.itemID));
|
|
end,
|
|
name = function(t)
|
|
return GetItemInfo(t.itemID) or nameFromSpellID(t);
|
|
end,
|
|
tsm = function(t)
|
|
---@diagnostic disable-next-line: undefined-field
|
|
return ("i:%d"):format(t.itemID);
|
|
end,
|
|
b = function(t)
|
|
return app.Settings.AccountWide.Recipes and 2;
|
|
end,
|
|
}, (function(t) return t.itemID; end));
|
|
-- local createItem = app.CreateItem; -- Temporary Recipe fix until someone fixes parser.
|
|
-- app.CreateItem = function(id, t)
|
|
-- if t and t.spellID and t.f == app.FilterConstants.RECIPES then -- This is pretty slow, would be great it someone fixes it.
|
|
-- t.f = nil;
|
|
-- t.itemID = id;
|
|
-- return createRecipe(t.spellID, t);
|
|
-- end
|
|
-- return createItem(id, t);
|
|
-- end
|
|
app.CreateRecipe = createRecipe;
|
|
app.CreateSpell = function(id, t)
|
|
if t and t.itemID then
|
|
return createRecipe(id, t);
|
|
else
|
|
return createSpell(id, t);
|
|
end
|
|
end
|
|
end)();
|
|
|
|
-- Startup Event
|
|
local ADDON_LOADED_HANDLERS = {
|
|
[appName] = function()
|
|
app.HandleEvent("OnLoad")
|
|
|
|
AllTheThingsAD = _G["AllTheThingsAD"]; -- For account-wide data.
|
|
if not AllTheThingsAD then
|
|
AllTheThingsAD = { };
|
|
_G["AllTheThingsAD"] = AllTheThingsAD;
|
|
end
|
|
|
|
-- Cache the Localized Category Data
|
|
AllTheThingsAD.LocalizedCategoryNames = setmetatable(AllTheThingsAD.LocalizedCategoryNames or {}, { __index = app.CategoryNames });
|
|
app.CategoryNames = nil;
|
|
|
|
-- Character Data Storage
|
|
local characterData = ATTCharacterData;
|
|
if not characterData then
|
|
characterData = {};
|
|
ATTCharacterData = characterData;
|
|
end
|
|
local currentCharacter = characterData[app.GUID];
|
|
if not currentCharacter then
|
|
currentCharacter = {};
|
|
characterData[app.GUID] = currentCharacter;
|
|
end
|
|
local name, realm = UnitName("player");
|
|
if not realm then realm = GetRealmName(); end
|
|
if name then currentCharacter.name = name; end
|
|
if realm then currentCharacter.realm = realm; end
|
|
if app.Me then currentCharacter.text = app.Me; end
|
|
if app.GUID then currentCharacter.guid = app.GUID; end
|
|
if app.Level then currentCharacter.lvl = app.Level; end
|
|
if app.FactionID then currentCharacter.factionID = app.FactionID; end
|
|
if app.ClassIndex then currentCharacter.classID = app.ClassIndex; end
|
|
if app.RaceIndex then currentCharacter.raceID = app.RaceIndex; end
|
|
if app.Class then currentCharacter.class = app.Class; end
|
|
if race then currentCharacter.race = race; end
|
|
if not currentCharacter.Achievements then currentCharacter.Achievements = {}; end
|
|
if not currentCharacter.ActiveSkills then currentCharacter.ActiveSkills = {}; end
|
|
if not currentCharacter.BattlePets then currentCharacter.BattlePets = {}; end
|
|
if not currentCharacter.Deaths then currentCharacter.Deaths = 0; end
|
|
if not currentCharacter.Exploration then currentCharacter.Exploration = {}; end
|
|
if not currentCharacter.Factions then currentCharacter.Factions = {}; end
|
|
if not currentCharacter.FlightPaths then currentCharacter.FlightPaths = {}; end
|
|
if not currentCharacter.Lockouts then currentCharacter.Lockouts = {}; end
|
|
if not currentCharacter.Quests then currentCharacter.Quests = {}; end
|
|
if not currentCharacter.Spells then currentCharacter.Spells = {}; end
|
|
if not currentCharacter.SpellRanks then currentCharacter.SpellRanks = {}; end
|
|
if not currentCharacter.Titles then currentCharacter.Titles = {}; end
|
|
if not currentCharacter.Transmog then currentCharacter.Transmog = {}; end
|
|
|
|
-- Update timestamps.
|
|
app.CurrentCharacter = currentCharacter;
|
|
app.AddEventHandler("OnPlayerLevelUp", function()
|
|
currentCharacter.lvl = app.Level;
|
|
end);
|
|
|
|
-- Account Wide Data Storage
|
|
local accountWideData = ATTAccountWideData;
|
|
if not accountWideData then
|
|
accountWideData = {};
|
|
ATTAccountWideData = accountWideData;
|
|
end
|
|
if not accountWideData.Achievements then accountWideData.Achievements = {}; end
|
|
if not accountWideData.BattlePets then accountWideData.BattlePets = {}; end
|
|
if not accountWideData.Deaths then accountWideData.Deaths = 0; end
|
|
if not accountWideData.Exploration then accountWideData.Exploration = {}; end
|
|
if not accountWideData.Factions then accountWideData.Factions = {}; end
|
|
if not accountWideData.FlightPaths then accountWideData.FlightPaths = {}; end
|
|
if not accountWideData.Quests then accountWideData.Quests = {}; end
|
|
if not accountWideData.Spells then accountWideData.Spells = {}; end
|
|
if not accountWideData.Titles then accountWideData.Titles = {}; end
|
|
if not accountWideData.Transmog then accountWideData.Transmog = {}; end
|
|
if not accountWideData.OneTimeQuests then accountWideData.OneTimeQuests = {}; end
|
|
|
|
-- Account Wide Settings
|
|
local accountWideSettings = app.Settings.AccountWide;
|
|
|
|
-- Notify Event Handlers that Saved Variable Data is available.
|
|
app.HandleEvent("OnSavedVariablesAvailable", currentCharacter, accountWideData, accountWideSettings);
|
|
-- Event handlers which need Saved Variable data which is added by OnSavedVariablesAvailable handlers into saved variables
|
|
app.HandleEvent("OnAfterSavedVariablesAvailable", currentCharacter, accountWideData);
|
|
|
|
-- Check to see if we have a leftover ItemDB cache
|
|
if not AllTheThingsAD.GroupQuestsByGUID then
|
|
AllTheThingsAD.GroupQuestsByGUID = {};
|
|
end
|
|
|
|
-- Wipe the Debugger Data
|
|
AllTheThingsDebugData = nil;
|
|
|
|
-- If we have RWP data on this character, let's update that to use Transmog.
|
|
for guid,character in pairs(characterData) do
|
|
local characterRWP = character.RWP;
|
|
if characterRWP then
|
|
local accountWideTransmog = accountWideData.Transmog;
|
|
local currentCharacterTransmog = character.Transmog;
|
|
if not currentCharacterTransmog then
|
|
currentCharacterTransmog = {};
|
|
character.Transmog = currentCharacterTransmog;
|
|
end
|
|
for itemID,collected in pairs(characterRWP) do
|
|
for _,item in ipairs(SearchForField("itemID", itemID)) do
|
|
if item.sourceID then
|
|
currentCharacterTransmog[item.sourceID] = collected;
|
|
accountWideTransmog[item.sourceID] = 1;
|
|
characterRWP[itemID] = nil;
|
|
end
|
|
end
|
|
end
|
|
local any = false;
|
|
for itemID,collected in pairs(characterRWP) do
|
|
any = true;
|
|
break;
|
|
end
|
|
if not any then character.RWP = nil; end
|
|
end
|
|
end
|
|
|
|
-- Refresh Collections
|
|
app.RefreshCollections();
|
|
|
|
-- Tooltip Settings
|
|
app.Settings:Initialize();
|
|
end,
|
|
};
|
|
app:RegisterEvent("ADDON_LOADED");
|
|
app.events.ADDON_LOADED = function(addonName)
|
|
local handler = ADDON_LOADED_HANDLERS[addonName];
|
|
if handler then handler(); end
|
|
end
|
|
|
|
app.AddEventHandler("OnStartupDone", function()
|
|
-- Execute the OnReady handlers.
|
|
app.HandleEvent("OnReady");
|
|
|
|
-- Mark that we're ready now!
|
|
app.IsReady = true;
|
|
end);
|