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.
14649 lines
486 KiB
14649 lines
486 KiB
--------------------------------------------------------------------------------
|
|
-- A L L T H E T H I N G S --
|
|
--------------------------------------------------------------------------------
|
|
-- Copyright 2017-2023 Dylan Fortune (Crieve-Sargeras) --
|
|
--------------------------------------------------------------------------------
|
|
-- App locals
|
|
local appName, app = ...;
|
|
local contains, containsAny, containsValue = app.contains, app.containsAny, app.containsValue;
|
|
local CloneArray, CloneDictionary, CloneReference = app.CloneArray, app.CloneDictionary, app.CloneReference;
|
|
local GetRelativeValue = app.GetRelativeValue;
|
|
local L = app.L;
|
|
|
|
-- Binding Localizations
|
|
BINDING_HEADER_ALLTHETHINGS = L.TITLE
|
|
BINDING_NAME_ALLTHETHINGS_TOGGLEACCOUNTMODE = L["TOGGLE_ACCOUNT_MODE"]
|
|
BINDING_NAME_ALLTHETHINGS_TOGGLEDEBUGMODE = L["TOGGLE_DEBUG_MODE"]
|
|
BINDING_NAME_ALLTHETHINGS_TOGGLEFACTIONMODE = L["TOGGLE_FACTION_MODE"]
|
|
|
|
BINDING_HEADER_ALLTHETHINGS_PREFERENCES = L["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_TOGGLELOOTDROPS = L["TOGGLE_LOOTDROPS"]
|
|
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_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, pcall, tinsert, tremove, sformat
|
|
= ipairs, pairs, rawset, rawget, pcall, tinsert, tremove, string.format;
|
|
local C_Map_GetMapInfo, C_Map_GetAreaInfo = C_Map.GetMapInfo, C_Map.GetAreaInfo;
|
|
local C_Map_GetPlayerMapPosition = C_Map.GetPlayerMapPosition;
|
|
local GetAchievementInfo = _G["GetAchievementInfo"];
|
|
local GetAchievementNumCriteria = _G["GetAchievementNumCriteria"];
|
|
local GetAchievementCriteriaInfo = _G["GetAchievementCriteriaInfo"];
|
|
local GetAchievementCriteriaInfoByID = _G["GetAchievementCriteriaInfoByID"];
|
|
local GetCategoryInfo = _G["GetCategoryInfo"];
|
|
local GetFactionInfoByID = _G["GetFactionInfoByID"];
|
|
local GetItemInfo = _G["GetItemInfo"];
|
|
local GetItemInfoInstant = _G["GetItemInfoInstant"];
|
|
local GetItemCount = _G["GetItemCount"];
|
|
local C_ToyBox, PlayerHasToy = _G["C_ToyBox"], _G["PlayerHasToy"];
|
|
local InCombatLockdown = _G["InCombatLockdown"];
|
|
local GetSpellInfo, IsPlayerSpell, IsSpellKnown, IsSpellKnownOrOverridesKnown, IsTitleKnown =
|
|
GetSpellInfo, IsPlayerSpell, IsSpellKnown, IsSpellKnownOrOverridesKnown, IsTitleKnown;
|
|
local C_QuestLog_GetAllCompletedQuestIDs = C_QuestLog.GetAllCompletedQuestIDs;
|
|
local C_QuestLog_IsQuestFlaggedCompleted = C_QuestLog.IsQuestFlaggedCompleted;
|
|
local C_QuestLog_IsOnQuest = C_QuestLog.IsOnQuest;
|
|
local ALLIANCE_FACTION_ID = Enum.FlightPathFaction.Alliance;
|
|
local HORDE_FACTION_ID = Enum.FlightPathFaction.Horde;
|
|
|
|
-- App & Module locals
|
|
local SearchForField, SearchForFieldContainer
|
|
= app.SearchForField, app.SearchForFieldContainer;
|
|
local IsRetrieving = app.Modules.RetrievingData.IsRetrieving;
|
|
|
|
-- Add a Feader & Filter debugger
|
|
setmetatable(app.FilterConstants, {
|
|
__index = function(t, key)
|
|
print("MISSING FilterConstant:", key);
|
|
rawset(t, key, -9999999999);
|
|
return -9999999999;
|
|
end
|
|
});
|
|
setmetatable(app.HeaderConstants, {
|
|
__index = function(t, key)
|
|
print("MISSING HeaderConstant:", key);
|
|
rawset(t, key, -9999999999);
|
|
return -9999999999;
|
|
end
|
|
});
|
|
|
|
-- Local Variables
|
|
local DESCRIPTION_SEPARATOR = "`";
|
|
local ALLIANCE_ONLY = {
|
|
1,
|
|
3,
|
|
4,
|
|
7,
|
|
11,
|
|
22,
|
|
25,
|
|
29,
|
|
30,
|
|
32,
|
|
34,
|
|
37,
|
|
};
|
|
local HORDE_ONLY = {
|
|
2,
|
|
5,
|
|
6,
|
|
8,
|
|
9,
|
|
10,
|
|
26,
|
|
27,
|
|
28,
|
|
31,
|
|
35,
|
|
36,
|
|
};
|
|
|
|
-- Helper Functions
|
|
local UpdateGroup, UpdateGroups;
|
|
local constructor = function(id, t, typeID)
|
|
if t then
|
|
if not t.g and t[1] then
|
|
return { g=t, [typeID]=id };
|
|
else
|
|
t[typeID] = id;
|
|
return t;
|
|
end
|
|
else
|
|
return {[typeID] = id};
|
|
end
|
|
end
|
|
function distance( x1, y1, x2, y2 )
|
|
return math.sqrt( (x2-x1)^2 + (y2-y1)^2 )
|
|
end
|
|
|
|
local pendingCollection, pendingRemovals, retrievingCollection, pendingCollectionCooldown = {},{},{},0;
|
|
local function PendingCollectionCoroutine()
|
|
while not app.IsReady do coroutine.yield(); end
|
|
while pendingCollectionCooldown > 0 do
|
|
pendingCollectionCooldown = pendingCollectionCooldown - 1;
|
|
coroutine.yield();
|
|
|
|
-- If any of the collection objects is retrieving data, try again.
|
|
local anyRetrieving = false;
|
|
for hash,thing in pairs(retrievingCollection) do
|
|
local retries = thing[1];
|
|
if retries > 0 then
|
|
retries = retries - 1;
|
|
thing[1] = retries;
|
|
if IsRetrieving(thing[2].text) then
|
|
retrievingCollection[hash] = nil;
|
|
anyRetrieving = true;
|
|
end
|
|
end
|
|
end
|
|
if anyRetrieving then
|
|
pendingCollectionCooldown = pendingCollectionCooldown + 1;
|
|
end
|
|
end
|
|
|
|
-- Report new things to your collection!
|
|
local any,allTypes = false,{};
|
|
local reportCollected = app.Settings:GetTooltipSetting("Report:Collected");
|
|
for hash,t in pairs(pendingCollection) do
|
|
local f = t.f;
|
|
if f then allTypes[f] = true; end
|
|
if reportCollected then
|
|
print((t.text or RETRIEVING_DATA) .. " was added to your collection!");
|
|
end
|
|
any = true;
|
|
end
|
|
if any then
|
|
wipe(pendingCollection);
|
|
|
|
-- Check if there was a mount.
|
|
if allTypes[app.FilterConstants.MOUNTS] then
|
|
app:PlayRareFindSound();
|
|
else
|
|
app:PlayFanfare();
|
|
end
|
|
end
|
|
|
|
-- Report removed things from your collection...
|
|
any = false;
|
|
for hash,t in pairs(pendingRemovals) do
|
|
if reportCollected then
|
|
print((t.text or RETRIEVING_DATA) .. " was removed from your collection!");
|
|
end
|
|
any = true;
|
|
end
|
|
if any then
|
|
wipe(pendingRemovals);
|
|
app:PlayRemoveSound();
|
|
end
|
|
end
|
|
local function AddToCollection(group)
|
|
if not group then return; end
|
|
local hash = group.hash;
|
|
if IsRetrieving(group.text) then
|
|
retrievingCollection[hash] = { 5, group };
|
|
end
|
|
|
|
-- Do not add the item to the pending list if it was already in it.
|
|
if pendingRemovals[hash] then
|
|
pendingRemovals[hash] = nil;
|
|
else
|
|
pendingCollection[hash] = group;
|
|
pendingCollectionCooldown = 10;
|
|
app:StartATTCoroutine("Pending Collection", PendingCollectionCoroutine);
|
|
end
|
|
end
|
|
local function RemoveFromCollection(group)
|
|
if not group then return; end
|
|
local hash = group.hash;
|
|
if IsRetrieving(group.text) then
|
|
retrievingCollection[hash] = { 5, group };
|
|
end
|
|
|
|
-- Do not add the item to the pending list if it was already in it.
|
|
if pendingCollection[hash] then
|
|
pendingCollection[hash] = nil;
|
|
else
|
|
pendingRemovals[hash] = group;
|
|
pendingCollectionCooldown = 10;
|
|
app:StartATTCoroutine("Pending Collection", PendingCollectionCoroutine);
|
|
end
|
|
end
|
|
|
|
-- Data Lib
|
|
local attData;
|
|
local AllTheThingsAD = {}; -- For account-wide data.
|
|
local function SetDataMember(member, data)
|
|
AllTheThingsAD[member] = data;
|
|
end
|
|
local function GetDataMember(member, default)
|
|
attData = AllTheThingsAD[member];
|
|
if attData == nil then
|
|
AllTheThingsAD[member] = default;
|
|
return default;
|
|
else
|
|
return attData;
|
|
end
|
|
end
|
|
local function SetDataSubMember(member, submember, data)
|
|
attData = AllTheThingsAD[member];
|
|
if attData == nil then
|
|
AllTheThingsAD[member] = { [submember] = data };
|
|
else
|
|
attData[submember] = data;
|
|
end
|
|
end
|
|
local function GetDataSubMember(member, submember, default)
|
|
attData = AllTheThingsAD[member];
|
|
if attData then
|
|
attData = attData[submember];
|
|
if attData == nil then
|
|
AllTheThingsAD[member][submember] = default;
|
|
return default;
|
|
else
|
|
return attData;
|
|
end
|
|
else
|
|
AllTheThingsAD[member] = { [submember] = default };
|
|
return default;
|
|
end
|
|
end
|
|
app.SetDataMember = SetDataMember;
|
|
app.GetDataMember = GetDataMember;
|
|
app.SetDataSubMember = SetDataSubMember;
|
|
app.GetDataSubMember = GetDataSubMember;
|
|
app.SetAccountCollected = function()
|
|
app.print("SetCollected not initialized yet...");
|
|
end;
|
|
app.SetAccountCollectedForSubType = function()
|
|
app.print("SetCollectedForSubType not initialized yet...");
|
|
end
|
|
app.SetCollected = function()
|
|
app.print("SetCollected not initialized yet...");
|
|
end;
|
|
app.SetCollectedForSubType = function()
|
|
app.print("SetCollectedForSubType not initialized yet...");
|
|
end
|
|
|
|
local backdrop = {
|
|
bgFile = "Interface/Tooltips/UI-Tooltip-Background",
|
|
edgeFile = "Interface/Tooltips/UI-Tooltip-Border",
|
|
tile = true, tileSize = 16, edgeSize = 16,
|
|
insets = { left = 4, right = 4, top = 4, bottom = 4 }
|
|
};
|
|
|
|
-- Game Tooltip Icon
|
|
local GameTooltipIcon = CreateFrame("FRAME", nil, GameTooltip);
|
|
GameTooltipIcon:SetPoint("TOPRIGHT", GameTooltip, "TOPLEFT", 0, 0);
|
|
GameTooltipIcon:SetSize(72, 72);
|
|
GameTooltipIcon.icon = GameTooltipIcon:CreateTexture(nil, "ARTWORK");
|
|
GameTooltipIcon.icon:SetAllPoints(GameTooltipIcon);
|
|
GameTooltipIcon.icon:Show();
|
|
GameTooltipIcon.icon.Background = GameTooltipIcon:CreateTexture(nil, "BACKGROUND");
|
|
GameTooltipIcon.icon.Background:SetAllPoints(GameTooltipIcon);
|
|
GameTooltipIcon.icon.Background:Show();
|
|
GameTooltipIcon.icon.Border = GameTooltipIcon:CreateTexture(nil, "BORDER");
|
|
GameTooltipIcon.icon.Border:SetAllPoints(GameTooltipIcon);
|
|
GameTooltipIcon.icon.Border:Show();
|
|
GameTooltipIcon:Hide();
|
|
|
|
-- Model is used to display the model of an NPC/Encounter.
|
|
local GameTooltipModel, model, fi = CreateFrame("FRAME", "ATTGameTooltipModel", GameTooltip, BackdropTemplateMixin and "BackdropTemplate");
|
|
GameTooltipModel:SetPoint("TOPRIGHT", GameTooltip, "TOPLEFT", 0, 0);
|
|
GameTooltipModel:SetSize(128, 128);
|
|
GameTooltipModel:SetBackdrop(backdrop);
|
|
GameTooltipModel:SetBackdropBorderColor(1, 1, 1, 1);
|
|
GameTooltipModel:SetBackdropColor(0, 0, 0, 1);
|
|
GameTooltipModel.Models = {};
|
|
GameTooltipModel.Model = CreateFrame("DressUpModel", nil, GameTooltipModel);
|
|
GameTooltipModel.Model:SetPoint("TOPLEFT", GameTooltipModel ,"TOPLEFT", 4, -4)
|
|
GameTooltipModel.Model:SetPoint("BOTTOMRIGHT", GameTooltipModel ,"BOTTOMRIGHT", -4, 4)
|
|
GameTooltipModel.Model:SetFacing(MODELFRAME_DEFAULT_ROTATION);
|
|
GameTooltipModel.Model:SetScript("OnUpdate", function(self, elapsed)
|
|
self:SetFacing(self:GetFacing() + elapsed);
|
|
end);
|
|
GameTooltipModel.Model:Hide();
|
|
|
|
local MAX_CREATURES_PER_ENCOUNTER = 9;
|
|
for i=1,MAX_CREATURES_PER_ENCOUNTER do
|
|
model = CreateFrame("DressUpModel", "ATTGameTooltipModel" .. i, GameTooltipModel);
|
|
model:SetPoint("TOPLEFT", GameTooltipModel ,"TOPLEFT", 4, -4);
|
|
model:SetPoint("BOTTOMRIGHT", GameTooltipModel ,"BOTTOMRIGHT", -4, 4);
|
|
model:SetCamDistanceScale(1.7);
|
|
model:SetDisplayInfo(987);
|
|
model:SetFacing(MODELFRAME_DEFAULT_ROTATION);
|
|
fi = math.floor(i / 2);
|
|
model:SetPosition(fi * -0.1, (fi * (i % 2 == 0 and -1 or 1)) * ((MAX_CREATURES_PER_ENCOUNTER - i) * 0.1), fi * 0.2 - 0.3);
|
|
if model.SetDepth then
|
|
model:SetDepth(i);
|
|
end
|
|
model:Hide();
|
|
tinsert(GameTooltipModel.Models, model);
|
|
end
|
|
GameTooltipModel.HideAllModels = function(self)
|
|
for i=1,MAX_CREATURES_PER_ENCOUNTER do
|
|
GameTooltipModel.Models[i]:Hide();
|
|
end
|
|
GameTooltipModel.Model:Hide();
|
|
end
|
|
GameTooltipModel.SetCreatureID = function(self, creatureID)
|
|
GameTooltipModel.HideAllModels(self);
|
|
if creatureID > 0 then
|
|
self.Model:SetUnit("none");
|
|
self.Model:SetCreature(creatureID);
|
|
local displayID = self.Model:GetDisplayInfo();
|
|
if not displayID then
|
|
app:StartATTCoroutine("SetCreatureID", function()
|
|
if self.lastModel == creatureID then
|
|
self:SetCreatureID(creatureID);
|
|
end
|
|
end);
|
|
end
|
|
end
|
|
self:Show();
|
|
end
|
|
GameTooltipModel.TrySetDisplayInfos = function(self, reference, displayInfos)
|
|
if displayInfos then
|
|
local count = #displayInfos;
|
|
if count > 0 then
|
|
local rotation = reference.modelRotation and ((reference.modelRotation * math.pi) / 180) or MODELFRAME_DEFAULT_ROTATION;
|
|
local scale = reference.modelScale or 1;
|
|
if count > 1 then
|
|
count = math.min(count, MAX_CREATURES_PER_ENCOUNTER);
|
|
local ratio = count / MAX_CREATURES_PER_ENCOUNTER;
|
|
if count < 3 then
|
|
for i=1,count do
|
|
model = self.Models[i];
|
|
model:SetDisplayInfo(displayInfos[i]);
|
|
model:SetCamDistanceScale(scale);
|
|
model:SetFacing(rotation);
|
|
model:SetPosition(0, (i % 2 == 0 and 0.5 or -0.5), 0);
|
|
model:Show();
|
|
end
|
|
else
|
|
scale = (1 + (ratio * 0.5)) * scale;
|
|
for i=1,count do
|
|
model = self.Models[i];
|
|
model:SetDisplayInfo(displayInfos[i]);
|
|
model:SetCamDistanceScale(scale);
|
|
model:SetFacing(rotation);
|
|
fi = math.floor(i / 2);
|
|
model:SetPosition(fi * -0.1, (fi * (i % 2 == 0 and -1 or 1)) * ((MAX_CREATURES_PER_ENCOUNTER - i) * 0.1), fi * 0.2 - (ratio * 0.15));
|
|
model:Show();
|
|
end
|
|
end
|
|
else
|
|
self.Model:SetFacing(rotation);
|
|
self.Model:SetCamDistanceScale(scale);
|
|
self.Model:SetDisplayInfo(displayInfos[1]);
|
|
self.Model:Show();
|
|
end
|
|
self:Show();
|
|
return true;
|
|
end
|
|
end
|
|
end
|
|
-- Attempts to return the displayID for the data, or every displayID if 'all' is specified
|
|
local function GetDisplayID(data, all)
|
|
-- don't create a displayID for groups with a sourceID/itemID/difficultyID/mapID
|
|
if data.s or data.itemID or data.difficultyID or data.mapID then return; end
|
|
if all then
|
|
local displayInfo, _ = {};
|
|
-- specific displayID
|
|
_ = data.displayID;
|
|
if _ then tinsert(displayInfo, _); data.displayInfo = displayInfo; return displayInfo; end
|
|
|
|
-- specific creatureID for displayID
|
|
_ = data.creatureID and app.NPCDisplayIDFromID[data.creatureID];
|
|
if _ then tinsert(displayInfo, _); data.displayInfo = displayInfo; return displayInfo; end
|
|
|
|
-- loop through "n" providers
|
|
if data.providers then
|
|
for k,v in pairs(data.providers) do
|
|
-- if one of the providers is an NPC, we should show its texture regardless of other providers
|
|
if v[1] == "n" then
|
|
_ = v[2] and app.NPCDisplayIDFromID[v[2]];
|
|
if _ then tinsert(displayInfo, _); end
|
|
end
|
|
end
|
|
end
|
|
if displayInfo[1] then data.displayInfo = displayInfo; return displayInfo; end
|
|
|
|
-- for quest givers
|
|
if data.qgs then
|
|
for k,v in pairs(data.qgs) do
|
|
_ = v and app.NPCDisplayIDFromID[v];
|
|
if _ then tinsert(displayInfo, _); end
|
|
end
|
|
end
|
|
if displayInfo[1] then data.displayInfo = displayInfo; return displayInfo; end
|
|
else
|
|
-- specific displayID
|
|
local _ = data.displayID or data.fetchedDisplayID;
|
|
if _ then return _; end
|
|
|
|
-- specific creatureID for displayID
|
|
_ = data.creatureID and app.NPCDisplayIDFromID[data.creatureID];
|
|
if _ then data.fetchedDisplayID = _; return _; end
|
|
|
|
-- loop through "n" providers
|
|
if data.providers then
|
|
for k,v in pairs(data.providers) do
|
|
-- if one of the providers is an NPC, we should show its texture regardless of other providers
|
|
if v[1] == "n" then
|
|
_ = v[2] and app.NPCDisplayIDFromID[v[2]];
|
|
if _ then data.fetchedDisplayID = _; return _; end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- for quest givers
|
|
if data.qgs then
|
|
for k,v in pairs(data.qgs) do
|
|
_ = v and app.NPCDisplayIDFromID[v];
|
|
if _ then data.fetchedDisplayID = _; return _; end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
GameTooltipModel.TrySetModel = function(self, reference)
|
|
GameTooltipModel.HideAllModels(self);
|
|
if app.Settings:GetTooltipSetting("Models") then
|
|
self.lastModel = reference;
|
|
local displayInfos = reference.displayInfo or GetDisplayID(reference, true);
|
|
if GameTooltipModel.TrySetDisplayInfos(self, reference, displayInfos) then
|
|
return true;
|
|
end
|
|
|
|
if reference.displayID then
|
|
self.Model:SetFacing(reference.modelRotation and ((reference.modelRotation * math.pi) / 180) or MODELFRAME_DEFAULT_ROTATION);
|
|
self.Model:SetCamDistanceScale(reference.modelScale or 1);
|
|
self.Model:SetDisplayInfo(reference.displayID);
|
|
self.Model:Show();
|
|
self:Show();
|
|
return true;
|
|
elseif reference.modelID then
|
|
self.Model:SetFacing(reference.modelRotation and ((reference.modelRotation * math.pi) / 180) or MODELFRAME_DEFAULT_ROTATION);
|
|
self.Model:SetCamDistanceScale(reference.modelScale or 1);
|
|
self.Model:SetDisplayInfo(reference.modelID);
|
|
self.Model:Show();
|
|
self:Show();
|
|
return true;
|
|
elseif reference.unit and not reference.icon then
|
|
self.Model:SetFacing(reference.modelRotation and ((reference.modelRotation * math.pi) / 180) or MODELFRAME_DEFAULT_ROTATION);
|
|
self.Model:SetCamDistanceScale(reference.modelScale or 1);
|
|
self.Model:SetUnit(reference.unit);
|
|
self.Model:Show();
|
|
self:Show();
|
|
end
|
|
local modelID = reference.model and tonumber(reference.model);
|
|
if modelID and modelID > 0 then
|
|
self.Model:SetFacing(reference.modelRotation and ((reference.modelRotation * math.pi) / 180) or MODELFRAME_DEFAULT_ROTATION);
|
|
self.Model:SetCamDistanceScale(reference.modelScale or 1);
|
|
self.Model:SetUnit("none");
|
|
self.Model:SetModel(modelID);
|
|
self.Model:Show();
|
|
self:Show();
|
|
return true;
|
|
elseif reference.creatureID and reference.creatureID > 0 then
|
|
self.Model:SetFacing(reference.modelRotation and ((reference.modelRotation * math.pi) / 180) or MODELFRAME_DEFAULT_ROTATION);
|
|
self.Model:SetCamDistanceScale(reference.modelScale or 1);
|
|
self:SetCreatureID(reference.creatureID);
|
|
self.Model:Show();
|
|
return true;
|
|
end
|
|
if reference.atlas then
|
|
GameTooltipIcon:SetSize(64,64);
|
|
GameTooltipIcon.icon:SetAtlas(reference.atlas);
|
|
GameTooltipIcon:Show();
|
|
if reference["atlas-background"] then
|
|
GameTooltipIcon.icon.Background:SetAtlas(reference["atlas-background"]);
|
|
GameTooltipIcon.icon.Background:Show();
|
|
end
|
|
if reference["atlas-border"] then
|
|
GameTooltipIcon.icon.Border:SetAtlas(reference["atlas-border"]);
|
|
GameTooltipIcon.icon.Border:Show();
|
|
if reference["atlas-color"] then
|
|
local swatches = reference["atlas-color"];
|
|
GameTooltipIcon.icon.Border:SetVertexColor(swatches[1], swatches[2], swatches[3], swatches[4] or 1.0);
|
|
else
|
|
GameTooltipIcon.icon.Border:SetVertexColor(1, 1, 1, 1.0);
|
|
end
|
|
end
|
|
return true;
|
|
end
|
|
end
|
|
end
|
|
GameTooltipModel:Hide();
|
|
|
|
app.AlwaysShowUpdate = function(data) data.visible = true; return true; end
|
|
app.AlwaysShowUpdateWithoutReturn = function(data) data.visible = true; end
|
|
app.print = function(...)
|
|
print(L["TITLE"], ...);
|
|
end
|
|
|
|
-- Color Lib
|
|
local CS = CreateFrame("ColorSelect", nil, app.frame);
|
|
local function Colorize(str, color)
|
|
return "|c" .. color .. str .. "|r";
|
|
end
|
|
local function HexToRGB(hex)
|
|
return tonumber("0x"..hex:sub(3,4)) / 255, tonumber("0x"..hex:sub(5,6)) / 255, tonumber("0x"..hex:sub(7,8)) / 255;
|
|
end
|
|
local function RGBToHex(r, g, b)
|
|
return sformat("ff%02x%02x%02x",
|
|
r <= 255 and r >= 0 and r or 0,
|
|
g <= 255 and g >= 0 and g or 0,
|
|
b <= 255 and b >= 0 and b or 0);
|
|
end
|
|
local function ConvertColorRgbToHsv(r, g, b)
|
|
CS:SetColorRGB(r, g, b);
|
|
local h,s,v = CS:GetColorHSV()
|
|
return {h=h,s=s,v=v}
|
|
end
|
|
local red, green = ConvertColorRgbToHsv(1,0,0), ConvertColorRgbToHsv(0,1,0);
|
|
local progress_colors = setmetatable({ [1] = app.Colors.Completed }, {
|
|
__index = function(t, p)
|
|
local h;
|
|
p = tonumber(p);
|
|
if abs(red.h - green.h) > 180 then
|
|
local angle = (360 - abs(red.h - green.h)) * p;
|
|
if red.h < green.h then
|
|
h = floor(red.h - angle);
|
|
if h < 0 then h = 360 + h end
|
|
else
|
|
h = floor(red.h + angle);
|
|
if h > 360 then h = h - 360 end
|
|
end
|
|
else
|
|
h = floor(red.h-(red.h-green.h)*p)
|
|
end
|
|
CS:SetColorHSV(h, red.s-(red.s-green.s)*p, red.v-(red.v-green.v)*p);
|
|
local r,g,b = CS:GetColorRGB();
|
|
local color = RGBToHex(r * 255, g * 255, b * 255);
|
|
rawset(t, p, color);
|
|
return color;
|
|
end
|
|
});
|
|
local function GetNumberWithZeros(number, desiredLength)
|
|
if desiredLength > 0 then
|
|
local str = tostring(number);
|
|
local length = string.len(str);
|
|
local pos = string.find(str,"[.]");
|
|
if not pos then
|
|
str = str .. ".";
|
|
for i=desiredLength,1,-1 do
|
|
str = str .. "0";
|
|
end
|
|
else
|
|
local totalExtra = desiredLength - (length - pos);
|
|
for i=totalExtra,1,-1 do
|
|
str = str .. "0";
|
|
end
|
|
if totalExtra < 1 then
|
|
str = string.sub(str, 1, pos + desiredLength);
|
|
end
|
|
end
|
|
return str;
|
|
else
|
|
return tostring(floor(number));
|
|
end
|
|
end
|
|
local function GetProgressTextDefault(progress, total)
|
|
return tostring(progress) .. " / " .. tostring(total);
|
|
end
|
|
local function GetProgressTextRemaining(progress, total)
|
|
return tostring(total - progress);
|
|
end
|
|
local function GetProgressColor(p)
|
|
return progress_colors[p];
|
|
end
|
|
local function GetProgressColorText(progress, total)
|
|
if total and total > 0 then
|
|
local percent = progress / total;
|
|
return "|c" .. GetProgressColor(percent) .. app.GetProgressText(progress, total) .. " (" .. GetNumberWithZeros(percent * 100, app.Settings:GetTooltipSetting("Precision")) .. "%) |r";
|
|
end
|
|
end
|
|
local function GetCollectionIcon(state)
|
|
return L[(state and (state == 2 and "COLLECTED_APPEARANCE_ICON" or "COLLECTED_ICON")) or "NOT_COLLECTED_ICON"];
|
|
end
|
|
local function GetCollectionText(state)
|
|
return L[(state and (state == 2 and "COLLECTED_APPEARANCE" or "COLLECTED")) or "NOT_COLLECTED"];
|
|
end
|
|
local function GetCompletionIcon(state)
|
|
return L[state and "COMPLETE_ICON" or "NOT_COLLECTED_ICON"];
|
|
end
|
|
local function GetCompletionText(state)
|
|
return L[(state == 2 and "COMPLETE_OTHER") or ((state == 1 or state == true) and "COMPLETE") or "INCOMPLETE"];
|
|
end
|
|
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 GetCollectionIcon(data.collected);
|
|
elseif data.trackable then
|
|
return GetCompletionIcon(data.saved);
|
|
end
|
|
end
|
|
local function GetProgressTextForTooltip(data)
|
|
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 GetCollectionText(data.collected);
|
|
elseif data.trackable then
|
|
return GetCompletionText(data.saved);
|
|
end
|
|
end
|
|
local function GetAddedWithPatchString(awp, addedBack)
|
|
if awp then
|
|
awp = tonumber(awp);
|
|
local formatString = "ADDED";
|
|
if app.GameBuildVersion == awp then
|
|
formatString = "WAS_" .. formatString;
|
|
elseif app.GameBuildVersion > awp then
|
|
return nil; -- Don't want to show this at the moment, let's add a configuration first!
|
|
end
|
|
if addedBack then formatString = formatString .. "_BACK"; end
|
|
return sformat(L[formatString .. "_WITH_PATCH_FORMAT"],
|
|
math.floor(awp / 10000) .. "." .. (math.floor(awp / 100) % 10) .. "." .. (awp % 10));
|
|
end
|
|
end
|
|
local function GetRemovedWithPatchString(rwp)
|
|
if rwp then
|
|
rwp = tonumber(rwp);
|
|
return sformat(L.REMOVED_WITH_PATCH_FORMAT, math.floor(rwp / 10000) .. "." .. (math.floor(rwp / 100) % 10) .. "." .. (rwp % 10));
|
|
end
|
|
end
|
|
app.GetProgressText = GetProgressTextDefault;
|
|
app.GetProgressTextDefault = GetProgressTextDefault;
|
|
app.GetProgressTextRemaining = GetProgressTextRemaining;
|
|
app.GetProgressColorText = GetProgressColorText;
|
|
CS:Hide();
|
|
|
|
|
|
-- Source ID Harvesting Lib
|
|
local DressUpModel = CreateFrame('DressUpModel');
|
|
local NPCModelHarvester = CreateFrame('DressUpModel', nil, OffScreenFrame);
|
|
local inventorySlotsMap = { -- Taken directly from CanIMogIt (Thanks!)
|
|
["INVTYPE_HEAD"] = {1},
|
|
["INVTYPE_NECK"] = {2},
|
|
["INVTYPE_SHOULDER"] = {3},
|
|
["INVTYPE_BODY"] = {4},
|
|
["INVTYPE_CHEST"] = {5},
|
|
["INVTYPE_ROBE"] = {5},
|
|
["INVTYPE_WAIST"] = {6},
|
|
["INVTYPE_LEGS"] = {7},
|
|
["INVTYPE_FEET"] = {8},
|
|
["INVTYPE_WRIST"] = {9},
|
|
["INVTYPE_HAND"] = {10},
|
|
["INVTYPE_RING"] = {11},
|
|
["INVTYPE_TRINKET"] = {12},
|
|
["INVTYPE_CLOAK"] = {15},
|
|
["INVTYPE_WEAPON"] = {16, 17},
|
|
["INVTYPE_SHIELD"] = {17},
|
|
["INVTYPE_2HWEAPON"] = {16, 17},
|
|
["INVTYPE_WEAPONMAINHAND"] = {16},
|
|
["INVTYPE_RANGED"] = {16},
|
|
["INVTYPE_RANGEDRIGHT"] = {16},
|
|
["INVTYPE_WEAPONOFFHAND"] = {17},
|
|
["INVTYPE_HOLDABLE"] = {17},
|
|
["INVTYPE_TABARD"] = {19},
|
|
};
|
|
local achievementTooltipText = {
|
|
[17213] = "DPA", -- Defense Protocol Alpha: Utgarde Keep
|
|
[17283] = "DPA", -- Defense Protocol Alpha: The Nexus
|
|
[17285] = "DPA", -- Defense Protocol Alpha: Azjol-Nerub
|
|
[17291] = "DPA", -- Defense Protocol Alpha: Ahn'kahet: The Old Kingdom
|
|
[17292] = "DPA", -- Defense Protocol Alpha: Drak'Tharon Keep
|
|
[17293] = "DPA", -- Defense Protocol Alpha: The Violet Hold
|
|
[17295] = "DPA", -- Defense Protocol Alpha: Gundrak
|
|
[17297] = "DPA", -- Defense Protocol Alpha: Halls of Stone
|
|
[17299] = "DPA", -- Defense Protocol Alpha: Halls of Lightning
|
|
[17300] = "DPA", -- Defense Protocol Alpha: The Oculus
|
|
[17301] = "DPA", -- Defense Protocol Alpha: Utgarde Pinnacle
|
|
[17302] = "DPA", -- Defense Protocol Alpha: The Culling of Stratholme
|
|
|
|
[18590] = "DPB", -- Defense Protocol Beta: Utgarde Keep
|
|
[18591] = "DPB", -- Defense Protocol Beta: The Nexus
|
|
[18592] = "DPB", -- Defense Protocol Beta: Azjol-Nerub
|
|
[18593] = "DPB", -- Defense Protocol Beta: Ahn'kahet: The Old Kingdom
|
|
[18594] = "DPB", -- Defense Protocol Beta: Drak'Tharon Keep
|
|
[18595] = "DPB", -- Defense Protocol Beta: The Violet Hold
|
|
[18596] = "DPB", -- Defense Protocol Beta: Gundrak
|
|
[18597] = "DPB", -- Defense Protocol Beta: Halls of Stone
|
|
[18598] = "DPB", -- Defense Protocol Beta: Halls of Lightning
|
|
[18599] = "DPB", -- Defense Protocol Beta: The Oculus
|
|
[18600] = "DPB", -- Defense Protocol Beta: Utgarde Pinnacle
|
|
[18601] = "DPB", -- Defense Protocol Beta: The Culling of Stratholme
|
|
[18677] = "DPB", -- Defense Protocol Beta: Trial of the Champion (A)
|
|
[18678] = "DPB", -- Defense Protocol Beta: Trial of the Champion (H)
|
|
|
|
[19427] = "DPG", -- Defense Protocol Gamma: Utgarde Keep
|
|
[19428] = "DPG", -- Defense Protocol Gamma: The Nexus
|
|
[19429] = "DPG", -- Defense Protocol Gamma: Azjol-Nerub
|
|
[19430] = "DPG", -- Defense Protocol Gamma: Ahn'kahet: The Old Kingdom
|
|
[19431] = "DPG", -- Defense Protocol Gamma: Drak'Tharon Keep
|
|
[19432] = "DPG", -- Defense Protocol Gamma: The Violet Hold
|
|
[19433] = "DPG", -- Defense Protocol Gamma: Gundrak
|
|
[19434] = "DPG", -- Defense Protocol Gamma: Halls of Stone
|
|
[19435] = "DPG", -- Defense Protocol Gamma: Halls of Lightning
|
|
[19436] = "DPG", -- Defense Protocol Gamma: The Oculus
|
|
[19437] = "DPG", -- Defense Protocol Gamma: Utgarde Pinnacle
|
|
[19438] = "DPG", -- Defense Protocol Gamma: The Culling of Stratholme
|
|
[19426] = "DPG", -- Defense Protocol Gamma: Trial of the Champion (A)
|
|
[19425] = "DPG", -- Defense Protocol Gamma: Trial of the Champion (H)
|
|
};
|
|
local function BuildGroups(parent)
|
|
local g = parent.g;
|
|
if g then
|
|
-- Iterate through the groups
|
|
for i=1,#g,1 do
|
|
-- Set the group's parent
|
|
local group = g[i];
|
|
group.parent = parent;
|
|
|
|
-- Build the groups
|
|
BuildGroups(group);
|
|
end
|
|
end
|
|
end
|
|
local function BuildSourceText(group, l, skip)
|
|
if group then
|
|
local parent = group.parent;
|
|
if parent then
|
|
if not group.itemID and not skip and (parent.key == "filterID" or parent.key == "spellID" or ((parent.headerID or (parent.spellID and (group.categoryID or group.tierID)))
|
|
and ((parent.headerID == app.HeaderConstants.VENDORS or parent.headerID == app.HeaderConstants.QUESTS or parent.headerID == app.HeaderConstants.WORLD_BOSSES) or (parent.parent and parent.parent.parent)))) then
|
|
return BuildSourceText(parent.parent, 5, skip) .. DESCRIPTION_SEPARATOR .. (group.text or RETRIEVING_DATA) .. " (" .. (parent.text or RETRIEVING_DATA) .. ")";
|
|
end
|
|
if group.headerID then
|
|
if group.headerID == app.HeaderConstants.ZONE_DROPS then
|
|
if group.crs and #group.crs == 1 then
|
|
return BuildSourceText(parent, l + 1, skip) .. DESCRIPTION_SEPARATOR .. (app.NPCNameFromID[group.crs[1]] or RETRIEVING_DATA) .. " (Drop)";
|
|
end
|
|
return BuildSourceText(parent, l + 1, skip) .. DESCRIPTION_SEPARATOR .. (group.text or RETRIEVING_DATA);
|
|
end
|
|
if parent.difficultyID then
|
|
return BuildSourceText(parent, l + 1, skip);
|
|
end
|
|
if parent.parent then
|
|
return BuildSourceText(parent, l + 1, skip) .. DESCRIPTION_SEPARATOR .. (group.text or RETRIEVING_DATA);
|
|
end
|
|
end
|
|
if group.key == "criteriaID" and group.achievementID then
|
|
local tooltipText = achievementTooltipText[group.achievementID];
|
|
if tooltipText then
|
|
return BuildSourceText(parent, 5, group.itemID or skip) .. " (" .. tooltipText .. ")";
|
|
else
|
|
return BuildSourceText(parent, 5, group.itemID or skip);
|
|
end
|
|
end
|
|
if parent.key == "categoryID" or parent.key == "tierID" or group.key == "filterID" or group.key == "spellID" or group.key == "encounterID" or (parent.key == "mapID" and group.key == "npcID") then
|
|
return BuildSourceText(parent, 5, skip) .. DESCRIPTION_SEPARATOR .. (group.text or RETRIEVING_DATA);
|
|
end
|
|
if l < 1 then
|
|
return BuildSourceText(parent, l + 1, group.itemID or skip);
|
|
else
|
|
return BuildSourceText(parent, l + 1, group.itemID or skip) .. " > " .. (group.text or RETRIEVING_DATA);
|
|
end
|
|
end
|
|
return group.text or RETRIEVING_DATA;
|
|
end
|
|
return L.TITLE;
|
|
end
|
|
local function BuildSourceTextForChat(group, l)
|
|
if group.parent then
|
|
if l < 1 then
|
|
return BuildSourceTextForChat(group.parent, l + 1);
|
|
else
|
|
return BuildSourceTextForChat(group.parent, l + 1) .. " > " .. (group.text or "*");
|
|
end
|
|
return group.text or "*";
|
|
end
|
|
return "ATT";
|
|
end
|
|
local function BuildSourceTextForDynamicPath(group)
|
|
local parent = group.parent;
|
|
if parent then
|
|
return BuildSourceTextForDynamicPath(parent) .. ">" .. (group.hash or group.name or group.text);
|
|
else
|
|
return group.hash or group.name or group.text;
|
|
end
|
|
end
|
|
local function CloneData(group)
|
|
local clone = setmetatable({}, getmetatable(group));
|
|
for key,value in pairs(group) do
|
|
clone[key] = value;
|
|
end
|
|
if group.g then
|
|
local g = {};
|
|
for i,group in ipairs(group.g) do
|
|
local child = CloneData(group);
|
|
child.parent = clone;
|
|
tinsert(g, child);
|
|
end
|
|
clone.g = g;
|
|
end
|
|
return clone;
|
|
end
|
|
app.IsComplete = function(o)
|
|
if o.total then return o.total == o.progress; end
|
|
if o.collectible then return o.collected; end
|
|
if o.trackable then return o.saved; end
|
|
end
|
|
app.MaximumItemInfoRetries = 400;
|
|
local function GetDisplayID(data)
|
|
if data.displayID then
|
|
return data.displayID;
|
|
elseif data.creatureID then
|
|
local displayID = app.NPCDisplayIDFromID[data.creatureID];
|
|
if displayID then
|
|
return displayID;
|
|
end
|
|
end
|
|
|
|
if data.providers and #data.providers > 0 then
|
|
for k,v in pairs(data.providers) do
|
|
-- if one of the providers is an NPC, we should show its texture regardless of other providers
|
|
if v[1] == "n" then
|
|
return app.NPCDisplayIDFromID[v[2]];
|
|
end
|
|
end
|
|
end
|
|
|
|
if data.qgs and #data.qgs > 0 then
|
|
return app.NPCDisplayIDFromID[data.qgs[1]];
|
|
end
|
|
end
|
|
|
|
|
|
|
|
local function GetBestMapForGroup(group, currentMapID)
|
|
if group then
|
|
local mapID = group.mapID;
|
|
if mapID and mapID == currentMapID then
|
|
return mapID;
|
|
end
|
|
|
|
local coords = group.coords;
|
|
if coords then
|
|
for i,coord in ipairs(coords) do
|
|
mapID = coord[3];
|
|
if mapID == currentMapID then
|
|
return mapID;
|
|
end
|
|
end
|
|
end
|
|
local maps = group.maps;
|
|
if maps then
|
|
for i,otherMapID in ipairs(maps) do
|
|
mapID = otherMapID;
|
|
if mapID == currentMapID then
|
|
return mapID;
|
|
end
|
|
end
|
|
end
|
|
|
|
return mapID or GetBestMapForGroup(group.parent, currentMapID);
|
|
end
|
|
end
|
|
local function GetRelativeDifficulty(group, difficultyID)
|
|
if group then
|
|
if group.difficultyID then
|
|
if group.difficultyID == difficultyID then
|
|
return true;
|
|
end
|
|
if group.difficulties then
|
|
for i, difficulty in ipairs(group.difficulties) do
|
|
if difficulty == difficultyID then
|
|
return true;
|
|
end
|
|
end
|
|
end
|
|
return false;
|
|
end
|
|
if group.parent then
|
|
return GetRelativeDifficulty(group.sourceParent or group.parent, difficultyID);
|
|
else
|
|
return true;
|
|
end
|
|
end
|
|
end
|
|
local function GetRelativeMap(group, currentMapID)
|
|
if group then
|
|
return group.mapID or (group.maps and (contains(group.maps, currentMapID) and currentMapID or group.maps[1])) or GetRelativeMap(group.parent, currentMapID);
|
|
end
|
|
return currentMapID;
|
|
end
|
|
local function GetRelativeField(group, field, value)
|
|
if group then
|
|
return group[field] == value or GetRelativeField(group.parent, field, value);
|
|
end
|
|
end
|
|
local function GetDeepestRelativeValue(group, field)
|
|
if group then
|
|
return GetDeepestRelativeValue(group.parent, field) or group[field];
|
|
end
|
|
end
|
|
|
|
-- Quest Completion Lib
|
|
local CompletedQuests, DirtyQuests = {}, {};
|
|
local IsQuestFlaggedCompleted = function(questID)
|
|
return questID and CompletedQuests[questID];
|
|
end
|
|
local IsQuestFlaggedCompletedForObject = function(t)
|
|
if IsQuestFlaggedCompleted(t.questID) then return 1; end
|
|
if app.Settings.AccountWide.Quests and not t.repeatable then
|
|
if t.questID and ATTAccountWideData.Quests[t.questID] then
|
|
return 2;
|
|
end
|
|
end
|
|
local altQuests = t.altQuests;
|
|
if altQuests then
|
|
for i,questID in ipairs(altQuests) do
|
|
if IsQuestFlaggedCompleted(questID) then
|
|
return 2;
|
|
end
|
|
end
|
|
if app.Settings.AccountWide.Quests then
|
|
for i,questID in ipairs(altQuests) do
|
|
if ATTAccountWideData.Quests[questID] then
|
|
return 2;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Search Caching
|
|
local searchCache = {};
|
|
app.searchCache = searchCache;
|
|
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 CreateObject(t)
|
|
local s = {};
|
|
if t[1] then
|
|
-- array
|
|
for i,o in ipairs(t) do
|
|
tinsert(s, CreateObject(o));
|
|
end
|
|
return s;
|
|
else
|
|
for k,v in pairs(t) do
|
|
rawset(s, k, v);
|
|
end
|
|
if t.g then
|
|
s.g = {};
|
|
for i,o in ipairs(t.g) do
|
|
tinsert(s.g, CreateObject(o));
|
|
end
|
|
end
|
|
|
|
local meta = getmetatable(t);
|
|
if meta then
|
|
setmetatable(s, meta);
|
|
return s;
|
|
else
|
|
t = s;
|
|
if t.mapID then
|
|
t = app.CreateMap(t.mapID, t);
|
|
elseif t.currencyID then
|
|
t = app.CreateCurrencyClass(t.currencyID, t);
|
|
elseif t.achID then
|
|
t = app.CreateAchievement(t.achID, t);
|
|
elseif t.achievementID then
|
|
t = app.CreateAchievement(t.achievementID, t);
|
|
elseif t.objectID then
|
|
t = app.CreateObject(t.objectID, t);
|
|
elseif t.professionID then
|
|
t = app.CreateProfession(t.professionID, t);
|
|
elseif t.categoryID then
|
|
t = app.CreateCategory(t.categoryID, t);
|
|
elseif t.illusionID then
|
|
t = app.CreateIllusion(t.illusionID, t);
|
|
elseif t.recipeID then
|
|
t = app.CreateRecipe(t.recipeID, t);
|
|
elseif t.spellID then
|
|
if t.f == app.FilterConstants.RECIPES then
|
|
t = app.CreateRecipe(t.spellID, t);
|
|
else
|
|
t = app.CreateSpell(t.spellID, t);
|
|
end
|
|
elseif t.itemID then
|
|
if t.toyID then
|
|
t = app.CreateToy(t.itemID, t);
|
|
else
|
|
t = app.CreateItem(t.itemID, t);
|
|
end
|
|
elseif t.classID then
|
|
t = app.CreateCharacterClass(t.classID, t);
|
|
elseif t.npcID or t.creatureID then
|
|
t = app.CreateNPC(t.npcID or t.creatureID, t);
|
|
elseif t.headerID then
|
|
t = app.CreateNPC(t.headerID, t); -- For now.
|
|
elseif t.questID then
|
|
t = app.CreateQuest(t.questID, t);
|
|
elseif t.factionID then
|
|
t = app.CreateFaction(t.factionID, t);
|
|
else
|
|
t = setmetatable({}, { __index = t });
|
|
end
|
|
t.visible = true;
|
|
return t;
|
|
end
|
|
end
|
|
end
|
|
|
|
local MergeObject;
|
|
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
|
|
rawset(o, "nmr", (o.races and not contains(o.races, app.RaceIndex)) or (o.r and o.r ~= app.FactionID));
|
|
rawset(o, "nmc", o.c and not contains(o.c, app.ClassIndex));
|
|
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 = CreateObject(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
|
|
if not o.itemID or o.b == 1 then
|
|
local r = GetRelativeValue(o, "r");
|
|
if r then
|
|
clone.r = r;
|
|
clone.races = nil;
|
|
else
|
|
local races = GetRelativeValue(o, "races");
|
|
if races then clone.races = CloneArray(races); end
|
|
end
|
|
local c = GetRelativeValue(o, "c");
|
|
if c then clone.c = CloneArray(c); end
|
|
end
|
|
return MergeObject(g, clone);
|
|
end
|
|
local function ExpandGroupsRecursively(group, expanded, manual)
|
|
if group.g and (not group.itemID or manual) then
|
|
group.expanded = expanded;
|
|
for i, subgroup in ipairs(group.g) do
|
|
ExpandGroupsRecursively(subgroup, expanded, manual);
|
|
end
|
|
end
|
|
end
|
|
local function ReapplyExpand(g, g2)
|
|
for j,p in ipairs(g2) do
|
|
local found = false;
|
|
local key = p.key;
|
|
local id = p[key];
|
|
for i,o in ipairs(g) do
|
|
if o[key] == id then
|
|
found = true;
|
|
if o.expanded then
|
|
if not p.expanded then
|
|
p.expanded = true;
|
|
if o.g and p.g then ReapplyExpand(o.g, p.g); end
|
|
end
|
|
end
|
|
break;
|
|
end
|
|
end
|
|
if not found then
|
|
ExpandGroupsRecursively(p, true);
|
|
end
|
|
end
|
|
end
|
|
app.MergeClone = MergeClone;
|
|
|
|
local ResolveSymbolicLink;
|
|
(function()
|
|
local subroutines;
|
|
subroutines = {
|
|
["common_recipes_vendor"] = function(npcID)
|
|
return {
|
|
{"select", "creatureID", 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", "creatureID", npcID}, -- Main Vendor
|
|
{"pop"}, -- Remove Main Vendor and push his children into the processing queue.
|
|
{"is", "itemID"}, -- Only Items
|
|
};
|
|
end,
|
|
["pvp_gear_base"] = function(tierID, headerID1, headerID2)
|
|
local b = {
|
|
{ "select", "tierID", tierID }, -- 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,s in ipairs(cache) do
|
|
local ref = ResolveSymbolicLink(s);
|
|
if ref then
|
|
if s.g then
|
|
for i,m in ipairs(s.g) do
|
|
tinsert(searchResults, m);
|
|
end
|
|
end
|
|
for i,m in ipairs(ref) do
|
|
tinsert(searchResults, m);
|
|
end
|
|
else
|
|
tinsert(searchResults, s);
|
|
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(BuildSourceTextForDynamicPath(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];
|
|
if app.Categories.Achievements then
|
|
response = app:BuildSearchResponse(app.Categories.Achievements, "requireSkill", requireSkill);
|
|
if response then tinsert(searchResults, {text=ACHIEVEMENTS,icon = app.asset("Category_Achievements"),g=response}); end
|
|
end
|
|
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.CreateNPC(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
|
|
elseif cmd == "fill" then
|
|
-- Instruction to fill with identical content cached elsewhere for this group
|
|
local cache = SearchForField(o.key, o[o.key]);
|
|
if #cache > 0 then
|
|
o.symbolizing = true;
|
|
for k,s in ipairs(cache) do
|
|
if not s.symbolizing then
|
|
local ref = ResolveSymbolicLink(s);
|
|
if ref then
|
|
if s.g then
|
|
for i,m in ipairs(s.g) do
|
|
tinsert(searchResults, m);
|
|
end
|
|
end
|
|
for i,m in ipairs(ref) do
|
|
tinsert(searchResults, m);
|
|
end
|
|
else
|
|
tinsert(searchResults, s);
|
|
end
|
|
end
|
|
end
|
|
o.symbolizing = nil;
|
|
else
|
|
print("Failed to select ", sym[2], sym[3]);
|
|
end
|
|
elseif cmd == "pop" then
|
|
-- Instruction to "pop" all of the group values up one level.
|
|
local orig = searchResults;
|
|
searchResults = {};
|
|
for k,s in ipairs(orig) do
|
|
if s.g then
|
|
for l,t in ipairs(s.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 s = searchResults[k];
|
|
if not s[key] or s[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 s = orig[k];
|
|
if s.g and index <= #s.g then
|
|
tinsert(searchResults, s.g[index]);
|
|
end
|
|
end
|
|
elseif cmd == "not" then
|
|
-- Instruction to include only search results where a key value is not a value
|
|
if #sym > 3 then
|
|
local dict = {};
|
|
for k=2,#sym,2 do
|
|
dict[sym[k] ] = sym[k + 1];
|
|
end
|
|
for k=#searchResults,1,-1 do
|
|
local s = searchResults[k];
|
|
local matched = true;
|
|
for key,value in pairs(dict) do
|
|
if not s[key] or s[key] ~= value then
|
|
matched = false;
|
|
break;
|
|
end
|
|
end
|
|
if matched then
|
|
tremove(searchResults, k);
|
|
end
|
|
end
|
|
else
|
|
local key, value = sym[2], sym[3];
|
|
for k=#searchResults,1,-1 do
|
|
local s = searchResults[k];
|
|
if s[key] and s[key] == 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
|
|
local s = searchResults[k];
|
|
if not s[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
|
|
local s = searchResults[k];
|
|
if s[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 s = searchResults[k];
|
|
if not s[key] or not contains(clone, s[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 s = searchResults[k];
|
|
if s[key] and contains(clone, s[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,s in ipairs(searchResults) do
|
|
tinsert(finalized, s);
|
|
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,s in ipairs(searchResults) do
|
|
tinsert(finalized, s);
|
|
end
|
|
searchResults = finalized;
|
|
finalized = {};
|
|
elseif cmd == "postprocess" then
|
|
-- Instruction to take all of the current search results and ensure that there are no duplicated primary keys.
|
|
local uniques = {};
|
|
MergeObjects(uniques, searchResults);
|
|
searchResults = uniques;
|
|
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 s = searchResults[k];
|
|
if s.itemID and not contains(types, select(4, GetItemInfoInstant(s.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,s in ipairs(results) do
|
|
tinsert(searchResults, s);
|
|
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,s in ipairs(results) do
|
|
tinsert(searchResults, s);
|
|
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 cache;
|
|
for criteriaID=1,GetAchievementNumCriteria(achievementID),1 do
|
|
local criteriaString, criteriaType, completed, quantity, reqQuantity, charName, flags, assetID, quantityString, uniqueID = GetAchievementCriteriaInfo(achievementID, criteriaID, true);
|
|
if not uniqueID or uniqueID <= 0 then uniqueID = criteriaID; end
|
|
local criteriaObject = app.CreateAchievementCriteria(uniqueID);
|
|
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
|
|
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,s in ipairs(cache) do
|
|
local ref = ResolveSymbolicLink(s);
|
|
if ref then
|
|
local cs = CloneReference(s);
|
|
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, s);
|
|
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 s = searchResults[k];
|
|
if s.criteriaID then tremove(searchResults, k); end
|
|
end
|
|
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,s in ipairs(searchResults) do
|
|
tinsert(finalized, s);
|
|
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)();
|
|
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 = GetCollectionIcon(group.collected);
|
|
end
|
|
else
|
|
right = L["NOT_COLLECTED_ICON"];
|
|
end
|
|
elseif group.trackable then
|
|
if app.Settings:Get("Show:IncompleteThings") 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 reason = L["UNOBTAINABLE_ITEM_REASONS"][group.u];
|
|
if reason and (not reason[5] or app.GameBuildVersion < reason[5]) then
|
|
o.texture = L["UNOBTAINABLE_ITEM_TEXTURES"][reason[1]];
|
|
end
|
|
elseif group.e then
|
|
o.texture = L["UNOBTAINABLE_ITEM_TEXTURES"][4];
|
|
end
|
|
if o.texture then
|
|
o.prefix = string.sub(o.prefix, 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 GetCachedSearchResults(search, method, paramA, paramB, ...)
|
|
if search then
|
|
local now = time();
|
|
local cache = searchCache[search];
|
|
if cache and (now - cache[1]) < cache[2] then return cache[3]; end
|
|
|
|
-- Determine if this tooltip needs more work the next time it refreshes.
|
|
if not paramA then paramA = ""; end
|
|
local working, info, crafted, recipes, mostAccessibleSource = false, {}, {}, {};
|
|
cache = { now, 100000000 };
|
|
searchCache[search] = cache;
|
|
|
|
-- Call to the method to search the database.
|
|
local group, a, b = method(paramA, paramB, ...);
|
|
if not group then
|
|
group = {};
|
|
elseif group.g then
|
|
group = group.g;
|
|
end
|
|
if a then paramA = a; end
|
|
if b then paramB = b; end
|
|
|
|
-- For Creatures that are inside of an instance, we only want the data relevant for the instance.
|
|
if paramA == "creatureID" or paramA == "encounterID" then
|
|
if group and #group > 0 then
|
|
local difficultyID = (IsInInstance() and select(3, GetInstanceInfo())) or (paramA == "encounterID" and EJ_GetDifficulty and EJ_GetDifficulty()) or 0;
|
|
if difficultyID > 0 then
|
|
local subgroup = {};
|
|
for _,j in ipairs(group) do
|
|
if GetRelativeDifficulty(j, difficultyID) then
|
|
tinsert(subgroup, j);
|
|
end
|
|
end
|
|
group = subgroup;
|
|
end
|
|
|
|
local regroup = {};
|
|
if app.Settings:Get("DebugMode") then
|
|
for i,j in ipairs(group) do
|
|
tinsert(regroup, j);
|
|
end
|
|
else
|
|
if app.Settings:Get("AccountMode") 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 IsQuestComplete(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.RecursiveClassAndRaceFilter(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 IsQuestComplete(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
|
|
if app.Settings:GetTooltipSetting("Lore") then
|
|
for i,j in ipairs(regroup) do
|
|
if j.lore and j[paramA] and j[paramA] == paramB then
|
|
tinsert(info, 1, { left = j.lore, wrap = true, color = app.Colors.TooltipLore });
|
|
end
|
|
end
|
|
end
|
|
if app.Settings:GetTooltipSetting("Descriptions") then
|
|
for i,j in ipairs(regroup) do
|
|
if j.description and j[paramA] and j[paramA] == paramB then
|
|
tinsert(info, 1, { left = j.description, wrap = true, color = app.Colors.TooltipDescription });
|
|
end
|
|
end
|
|
end
|
|
app.Sort(regroup, function(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);
|
|
end
|
|
group = regroup;
|
|
end
|
|
elseif paramA == "titleID" then
|
|
-- Don't do anything
|
|
local regroup = {};
|
|
if app.Settings:Get("AccountMode") 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.RecursiveClassAndRaceFilter(j) and app.RecursiveUnobtainableFilter(j) then
|
|
tinsert(regroup, setmetatable({["g"] = {}}, { __index = j }));
|
|
end
|
|
end
|
|
end
|
|
|
|
group = regroup;
|
|
else
|
|
-- Determine if this is a cache for an item
|
|
local itemID;
|
|
if not paramB then
|
|
local itemString = string.match(paramA, "item[%-?%d:]+");
|
|
if itemString then
|
|
local itemID2 = select(2, strsplit(":", itemString));
|
|
if itemID2 then
|
|
itemID = tonumber(itemID2);
|
|
paramA = "itemID";
|
|
paramB = itemID;
|
|
end
|
|
else
|
|
local kind, id = strsplit(":", paramA);
|
|
kind = string.lower(kind);
|
|
if id then id = tonumber(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
|
|
|
|
if itemID then
|
|
-- Show the unobtainable source text
|
|
local u, e = 99999999;
|
|
app.Sort(group, app.SortDefaults.Accessibility);
|
|
for i,j in ipairs(group) do
|
|
if j.itemID == itemID then
|
|
mostAccessibleSource = j;
|
|
if j.u and u > j.u and (not j.crs or paramA == "itemID") then
|
|
u = j.u;
|
|
end
|
|
if j.e then
|
|
e = j.e;
|
|
end
|
|
break;
|
|
end
|
|
end
|
|
if u < 99999999 then
|
|
local reason = L["UNOBTAINABLE_ITEM_REASONS"][u];
|
|
if reason and (not reason[5] or app.GameBuildVersion < reason[5]) then
|
|
tinsert(info, { left = reason[2], wrap = true });
|
|
end
|
|
end
|
|
if e then
|
|
local reason = app.Modules.Events.GetEventTooltipNoteForGroup({ e = e });
|
|
if reason then
|
|
local left, right = strsplit(DESCRIPTION_SEPARATOR, reason);
|
|
if right then
|
|
tinsert(info, { left = left, right = right, color = app.Colors.TooltipDescription });
|
|
else
|
|
tinsert(info, { left = left, color = app.Colors.TooltipDescription });
|
|
end
|
|
end
|
|
end
|
|
local itemName, itemLink = GameTooltip:GetItem();
|
|
if app.Settings:GetTooltipSetting("itemID") then tinsert(info, { left = L["ITEM_ID"], right = tostring(itemID) }); end
|
|
if app.Settings:GetTooltipSetting("itemLevel") then tinsert(info, { left = "Item Level", right = select(4, GetItemInfo(itemLink or itemID)) }); end
|
|
if app.Settings:GetTooltipSetting("itemString") and itemLink then tinsert(info, { left = "Item String", right = string.match(itemLink, "item[%-?%d:]+") }); end
|
|
app.ShowSoftReservesForItem(itemID, info);
|
|
|
|
local reagentCache = app.GetDataSubMember("Reagents", itemID);
|
|
if reagentCache then
|
|
for spellID,count in pairs(reagentCache[1]) do
|
|
MergeClone(recipes, { ["spellID"] = spellID, ["collectible"] = false, ["count"] = count });
|
|
end
|
|
for craftedItemID,count in pairs(reagentCache[2]) do
|
|
MergeClone(crafted, { ["itemID"] = craftedItemID, ["count"] = count });
|
|
local searchResults = SearchForField("itemID", craftedItemID);
|
|
if #searchResults > 0 then
|
|
for i,o in ipairs(searchResults) do
|
|
if not o.itemID and o.cost then
|
|
-- Reagent for something that crafts a thing required for something else.
|
|
MergeClone(group, { ["itemID"] = craftedItemID, ["count"] = count, ["g"] = { CreateObject(o) } });
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Create a list of sources
|
|
if app.Settings:GetTooltipSetting("SourceLocations") and (not paramA or (app.Settings:GetTooltipSetting(paramA == "creatureID" and "SourceLocations:Creatures" or "SourceLocations:Things"))) then
|
|
local temp = {};
|
|
local unfiltered = {};
|
|
local abbrevs = L["ABBREVIATIONS"];
|
|
local abbrevs_post = L["ABBREVIATIONS_POST"];
|
|
if not abbrevs_post[" true "] then
|
|
abbrevs_post[" %> " .. app.GetMapName(947)] = "";
|
|
abbrevs_post[" %> " .. app.GetMapName(1415)] = "";
|
|
abbrevs_post[" %> " .. app.GetMapName(1414)] = "";
|
|
abbrevs_post[" false "] = " 0 ";
|
|
abbrevs_post[" true "] = " 1 ";
|
|
end
|
|
for i,j in ipairs(group) do
|
|
if j.parent and not GetRelativeValue(j, "hideText") and j.parent.parent
|
|
and (app.Settings:GetTooltipSetting("SourceLocations:Completed") or not app.IsComplete(j)) then
|
|
local text = BuildSourceText(paramA ~= "itemID" and j.parent or j, paramA ~= "itemID" and 1 or 0);
|
|
for source,replacement in pairs(abbrevs) do
|
|
text = string.gsub(text, source, replacement);
|
|
end
|
|
for source,replacement in pairs(abbrevs_post) do
|
|
text = string.gsub(text, source, replacement);
|
|
end
|
|
|
|
local right = " ";
|
|
if j.u then
|
|
local reason = L["UNOBTAINABLE_ITEM_REASONS"][j.u];
|
|
if reason and (not reason[5] or app.GameBuildVersion < reason[5]) then
|
|
right = "|T" .. L["UNOBTAINABLE_ITEM_TEXTURES"][reason[1]] .. ":0|t";
|
|
end
|
|
end
|
|
if j.rwp then right = right .. "|T" .. L["UNOBTAINABLE_ITEM_TEXTURES"][2] .. ":0|t"; end
|
|
if j.e then right = right .. "|T" .. L["UNOBTAINABLE_ITEM_TEXTURES"][4] .. ":0|t"; end
|
|
|
|
if not app.RecursiveClassAndRaceFilter(j.parent) then
|
|
tinsert(unfiltered, { text, right .. "|TInterface\\FriendsFrame\\StatusIcon-Away:0|t" });
|
|
elseif not app.RecursiveUnobtainableFilter(j.parent) then
|
|
tinsert(unfiltered, { text, right .. "|TInterface\\FriendsFrame\\StatusIcon-DnD:0|t" });
|
|
else
|
|
tinsert(temp, { text, right });
|
|
end
|
|
end
|
|
end
|
|
if (#temp < 1 and not (paramA == "creatureID")) or app.Settings:Get("DebugMode") then
|
|
for i,data in ipairs(unfiltered) do
|
|
tinsert(temp, data);
|
|
end
|
|
end
|
|
if #temp > 0 then
|
|
local listing, listingByText = {}, {};
|
|
local maximum = app.Settings:GetTooltipSetting("Locations");
|
|
for i,data in ipairs(temp) do
|
|
local text = data[1] or RETRIEVING_DATA;
|
|
if not listingByText[text] then
|
|
listingByText[text] = data;
|
|
tinsert(listing, 1, data);
|
|
if string.find(text, RETRIEVING_DATA) then working = true; end
|
|
end
|
|
end
|
|
local count, splitCounts, splitCount = 0, { };
|
|
for i,data in ipairs(listing) do
|
|
local left, right = strsplit(DESCRIPTION_SEPARATOR, data[1]);
|
|
left = left .. data[2];
|
|
splitCount = splitCounts[left];
|
|
if not splitCount then
|
|
splitCount = { count = 0, data=data, variants ={} };
|
|
splitCounts[left] = splitCount;
|
|
end
|
|
if right and not contains(splitCount.variants, right) then
|
|
tinsert(splitCount.variants, right);
|
|
if string.find(right, BATTLE_PET_SOURCE_2) then
|
|
splitCount.count = splitCount.count + 1;
|
|
end
|
|
end
|
|
end
|
|
for left,splitCount in pairs(splitCounts) do
|
|
if splitCount.count < 6 then
|
|
if #splitCount.variants < 1 then
|
|
tinsert(info, 1, { left = left, wrap = not string.find(left, " > ") });
|
|
count = count + 1;
|
|
else
|
|
for i,right in ipairs(splitCount.variants) do
|
|
tinsert(info, 1, { left = left, right = right, wrap = not string.find(left, " > ") });
|
|
count = count + 1;
|
|
end
|
|
end
|
|
else
|
|
tinsert(info, 1, { left = left, right = TRACKER_HEADER_QUESTS, wrap = not string.find(left, " > ") });
|
|
count = count + 1;
|
|
for i,right in ipairs(splitCount.variants) do
|
|
if not string.find(right, BATTLE_PET_SOURCE_2) then
|
|
tinsert(info, 1, { left = left, right = right, wrap = not string.find(left, " > ") });
|
|
count = count + 1;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if count > maximum + 1 then
|
|
for i=count,maximum + 1,-1 do
|
|
tremove(info, 1);
|
|
end
|
|
tinsert(info, 1, "And " .. (count - maximum) .. " other sources...");
|
|
end
|
|
end
|
|
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] = CreateObject(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] = CreateObject(symbolicLink[j]);
|
|
end
|
|
o.g = symbolicLink;
|
|
end
|
|
end
|
|
end
|
|
group = CreateObject({ [paramA] = paramB });
|
|
group.g = merged;
|
|
end
|
|
end
|
|
|
|
if mostAccessibleSource then
|
|
group.rwp = mostAccessibleSource.rwp;
|
|
group.e = mostAccessibleSource.e;
|
|
group.u = mostAccessibleSource.u;
|
|
end
|
|
|
|
-- Resolve Cost
|
|
--print("GetCachedSearchResults", 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.CreateNPC(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.CreateNPC(app.HeaderConstants.QUESTS);
|
|
if not attunement.g then attunement.g = {}; end
|
|
local usedToBuy = app.CreateNPC(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"))) then
|
|
if app.Settings.Collectibles.Quests then
|
|
local d = CreateObject(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
|
|
|
|
if group.g then
|
|
group.total = 0;
|
|
group.progress = 0;
|
|
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 group.lore and app.Settings:GetTooltipSetting("Lore") and not (paramA == "titleID") then
|
|
tinsert(info, 1, { left = group.lore, wrap = true, color = app.Colors.TooltipLore });
|
|
end
|
|
|
|
if group.description and app.Settings:GetTooltipSetting("Descriptions") and not (paramA == "titleID") then
|
|
tinsert(info, 1, { left = group.description, wrap = true, color = app.Colors.TooltipDescription });
|
|
end
|
|
|
|
if group.nextEvent then
|
|
local timeStrings = app.Modules.Events.GetEventTimeStrings(group.nextEvent);
|
|
if timeStrings then
|
|
for i,timeString in ipairs(timeStrings) do
|
|
tinsert(info, 1, { left = timeString, wrap = true, color = app.Colors.TooltipDescription });
|
|
end
|
|
end
|
|
end
|
|
|
|
local awp, rwp = GetRelativeValue(group, "awp"), group.rwp;
|
|
local awpGreaterThanRWP = true;
|
|
if awp and ((rwp or (group.u and group.u < 3)) or awp >= app.GameBuildVersion) then
|
|
awpGreaterThanRWP = rwp and awp >= rwp;
|
|
local awpString = GetAddedWithPatchString(awp, awpGreaterThanRWP);
|
|
if awpString then
|
|
tinsert(info, 1, { left = awpString, wrap = true, color = app.Colors.AddedWithPatch });
|
|
else
|
|
awpGreaterThanRWP = true;
|
|
end
|
|
end
|
|
if rwp then
|
|
tinsert(info, awpGreaterThanRWP and 1 or 2, { left = GetRemovedWithPatchString(rwp), wrap = true, color = app.Colors.RemovedWithPatch });
|
|
end
|
|
|
|
if group.isLimited then
|
|
tinsert(info, 1, { left = L.LIMITED_QUANTITY, wrap = true, color = app.Colors.TooltipDescription });
|
|
end
|
|
|
|
if group.pvp then
|
|
tinsert(info, { left = L["REQUIRES_PVP"] });
|
|
end
|
|
|
|
local showOtherCharacterQuests = app.Settings:GetTooltipSetting("Show:OtherCharacterQuests");
|
|
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 = GetRealmName();
|
|
tinsert(info, { left = "Contains:" });
|
|
if #entries < 25 then
|
|
for i,item in ipairs(entries) do
|
|
left = item.group.text or RETRIEVING_DATA;
|
|
if IsRetrieving(left) then working = true; end
|
|
local mapID = 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(info, { left = item.prefix .. left, right = item.right });
|
|
|
|
if item.group.questID and not item.group.repeatable and showOtherCharacterQuests then
|
|
local incompletes = {};
|
|
for guid,character in pairs(ATTCharacterData) do
|
|
if not character.ignored and character.realm == realmName
|
|
and (not item.group.r or (character.factionID and item.group.r == character.factionID))
|
|
and (not item.group.races or (character.raceID and contains(item.group.races, character.raceID)))
|
|
and (not item.group.c or (character.classID and contains(item.group.c, character.classID)))
|
|
and (character.Quests and not character.Quests[item.group.questID]) then
|
|
incompletes[guid] = character;
|
|
end
|
|
end
|
|
local desc, j = "", 0;
|
|
for guid,character in pairs(incompletes) do
|
|
if j > 0 then desc = desc .. ", "; end
|
|
desc = desc .. (character.text or guid);
|
|
j = j + 1;
|
|
end
|
|
if j > 0 then
|
|
tinsert(info, { left = " ", right = string.gsub(desc, "-" .. realmName, ""), hash = "HASH" .. item.group.questID });
|
|
end
|
|
end
|
|
end
|
|
else
|
|
for i=1,math.min(25, #entries) do
|
|
local item = entries[i];
|
|
left = item.group.text or RETRIEVING_DATA;
|
|
if IsRetrieving(left) then working = true; end
|
|
local mapID = 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(info, { left = item.prefix .. left, right = item.right });
|
|
|
|
if item.group.questID and not item.group.repeatable and showOtherCharacterQuests then
|
|
local incompletes = {};
|
|
for guid,character in pairs(ATTCharacterData) do
|
|
if not character.ignored and character.realm == realmName and character.Quests and not character.Quests[item.group.questID] then
|
|
incompletes[guid] = character;
|
|
end
|
|
end
|
|
local desc, j = "", 0;
|
|
for guid,character in pairs(incompletes) do
|
|
if j > 0 then desc = desc .. ", "; end
|
|
desc = desc .. (character.text or guid);
|
|
j = j + 1;
|
|
end
|
|
tinsert(info, { left = " ", right = string.gsub(desc, "-" .. realmName, ""), hash = "HASH" .. item.group.questID });
|
|
end
|
|
end
|
|
local more = #entries - 25;
|
|
if more > 0 then tinsert(info, { left = "And " .. more .. " more..." }); end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Crafted Items
|
|
if crafted and #crafted > 0 then
|
|
if app.Settings:GetTooltipSetting("Show:CraftedItems") then
|
|
local entries = {};
|
|
BuildContainsInfo(crafted, entries, paramA, paramB, " ", app.noDepth and 99 or 1);
|
|
if #entries > 0 then
|
|
local left, right;
|
|
tinsert(info, { 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 IsRetrieving(left) then working = true; end
|
|
if item.group.icon then item.prefix = item.prefix .. "|T" .. item.group.icon .. ":0|t "; end
|
|
tinsert(info, { 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 IsRetrieving(left) then working = true; end
|
|
if item.group.icon then item.prefix = item.prefix .. "|T" .. item.group.icon .. ":0|t "; end
|
|
tinsert(info, { left = item.prefix .. left, right = item.right });
|
|
end
|
|
local more = #entries - 25;
|
|
if more > 0 then tinsert(info, { left = "And " .. more .. " more..." }); end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Recipes
|
|
if recipes and #recipes > 0 then
|
|
if app.Settings:GetTooltipSetting("Show:Recipes") then
|
|
local entries, left, right = {};
|
|
BuildContainsInfo(recipes, entries, paramA, paramB, " ", app.noDepth and 99 or 1);
|
|
if #entries > 0 then
|
|
tinsert(info, { left = "Used in Recipes:" });
|
|
if #entries < 25 then
|
|
app.Sort(entries, function(a, b)
|
|
if a and a.group.name then
|
|
if b and 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 IsRetrieving(left) then working = true; end
|
|
if item.group.icon then item.prefix = item.prefix .. "|T" .. item.group.icon .. ":0|t "; end
|
|
tinsert(info, { 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 IsRetrieving(left) then working = true; end
|
|
if item.group.icon then item.prefix = item.prefix .. "|T" .. item.group.icon .. ":0|t "; end
|
|
tinsert(info, { left = item.prefix .. left, right = item.right });
|
|
end
|
|
local more = #entries - 25;
|
|
if more > 0 then tinsert(info, { left = "And " .. more .. " more..." }); end
|
|
end
|
|
end
|
|
end
|
|
if app.Settings:GetTooltipSetting("Show:SpellRanks") then
|
|
if app.Settings:Get("AccountMode") or app.Settings:Get("DebugMode") then
|
|
-- Show all characters
|
|
else
|
|
-- Show only the current character
|
|
local nonTrivialRecipes = {};
|
|
for i, o in pairs(recipes) do
|
|
local craftTypeID = app.CurrentCharacter.SpellRanks[o.spellID];
|
|
if craftTypeID and craftTypeID > 0 then
|
|
o.craftTypeID = craftTypeID;
|
|
tinsert(nonTrivialRecipes, o);
|
|
end
|
|
end
|
|
local entries, left, right = {};
|
|
BuildContainsInfo(nonTrivialRecipes, entries, paramA, paramB, " ", app.noDepth and 99 or 1);
|
|
if #entries > 0 then
|
|
tinsert(info, { left = "Available Skill Ups:" });
|
|
if #entries < 25 then
|
|
app.Sort(entries, function(a, b)
|
|
if a.group.craftTypeID == b.group.craftTypeID then
|
|
if a.group.name then
|
|
if b.group.name then
|
|
return a.group.name <= b.group.name;
|
|
end
|
|
return true;
|
|
end
|
|
return false;
|
|
end
|
|
return a.group.craftTypeID > b.group.craftTypeID;
|
|
end);
|
|
for i,item in ipairs(entries) do
|
|
left = item.group.text or RETRIEVING_DATA;
|
|
if IsRetrieving(left) then working = true; end
|
|
if item.group.icon then item.prefix = item.prefix .. "|T" .. item.group.icon .. ":0|t "; end
|
|
tinsert(info, { 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 IsRetrieving(left) then working = true; end
|
|
if item.group.icon then item.prefix = item.prefix .. "|T" .. item.group.icon .. ":0|t "; end
|
|
tinsert(info, { left = item.prefix .. left, right = item.right });
|
|
end
|
|
local more = #entries - 25;
|
|
if more > 0 then tinsert(info, { left = "And " .. more .. " more..." }); end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- If the item is a recipe, then show which characters know this recipe.
|
|
if group.collectible and app.Settings:GetTooltipSetting("KnownBy") then
|
|
local knownBy, kind = {}, nil;
|
|
if group.speciesID then
|
|
kind = "Owned by ";
|
|
for guid,character in pairs(ATTCharacterData) do
|
|
if character.BattlePets and character.BattlePets[group.speciesID] then
|
|
tinsert(knownBy, character);
|
|
end
|
|
end
|
|
elseif group.spellID then
|
|
kind = "Known by ";
|
|
for guid,character in pairs(ATTCharacterData) do
|
|
if character.Spells and character.Spells[group.spellID] then
|
|
tinsert(knownBy, character);
|
|
end
|
|
end
|
|
elseif group.toyID then
|
|
kind = "Owned by ";
|
|
for guid,character in pairs(ATTCharacterData) do
|
|
if character.Toys and character.Toys[group.itemID] then
|
|
tinsert(knownBy, character);
|
|
end
|
|
end
|
|
elseif group.itemID then
|
|
kind = "Owned by ";
|
|
for guid,character in pairs(ATTCharacterData) do
|
|
if (character.RWP and character.RWP[group.itemID]) then
|
|
tinsert(knownBy, character);
|
|
end
|
|
end
|
|
elseif group.achievementID then
|
|
kind = "Completed by ";
|
|
for guid,character in pairs(ATTCharacterData) do
|
|
if character.Achievements and character.Achievements[group.achievementID] then
|
|
tinsert(knownBy, character);
|
|
end
|
|
end
|
|
elseif group.questID then
|
|
kind = "Completed by ";
|
|
for guid,character in pairs(ATTCharacterData) do
|
|
if character.Quests and character.Quests[group.questID] then
|
|
tinsert(knownBy, character);
|
|
end
|
|
end
|
|
end
|
|
if #knownBy > 0 and kind then
|
|
app.Sort(knownBy, app.SortDefaults.Name);
|
|
local desc = kind;
|
|
for i,character in ipairs(knownBy) do
|
|
if i > 1 then desc = desc .. ", "; end
|
|
desc = desc .. (character.text or "???");
|
|
if group.itemID and character == app.CurrentCharacter then
|
|
local count = GetItemCount(group.itemID, true);
|
|
if count and count > 1 then
|
|
desc = desc .. " (x" .. count .. ")";
|
|
end
|
|
end
|
|
end
|
|
tinsert(info, { left = string.gsub(desc, "-" .. GetRealmName(), ""), wrap = true, color = app.Colors.TooltipDescription });
|
|
end
|
|
end
|
|
|
|
-- If the user wants to show the progress of this search result, do so.
|
|
if app.Settings:GetTooltipSetting("Progress") and (not group.spellID or #info > 0) then
|
|
group.collectionText = (app.Settings:GetTooltipSetting("ShowIconOnly") and GetProgressTextForRow or GetProgressTextForTooltip)(group);
|
|
|
|
-- add the progress as a new line for encounter tooltips instead of using right text since it can overlap the NPC name
|
|
if group.encounterID then tinsert(info, 1, { left = "Progress", right = group.collectionText }); end
|
|
end
|
|
|
|
-- If there was any informational text generated, then attach that info.
|
|
if #info > 0 then
|
|
local uniques, dupes, _ = {}, {};
|
|
for i,item in ipairs(info) do
|
|
_ = item.hash or item.left;
|
|
if not _ then
|
|
tinsert(uniques, item);
|
|
else
|
|
if item.right then _ = _ .. item.right; end
|
|
if not dupes[_] then
|
|
dupes[_] = true;
|
|
tinsert(uniques, item);
|
|
end
|
|
end
|
|
end
|
|
|
|
for i,item in ipairs(uniques) do
|
|
if item.color then item.r, item.g, item.b = HexToRGB(item.color); end
|
|
end
|
|
group.tooltipInfo = uniques;
|
|
end
|
|
|
|
-- Cache the result for a while depending on if there is more work to be done.
|
|
cache[2] = (working and 0.01) or 100000000;
|
|
cache[3] = group;
|
|
return group;
|
|
end
|
|
end
|
|
local function SendGroupChatMessage(msg)
|
|
if IsInRaid() then
|
|
SendChatMessage(msg, "RAID", nil, nil);
|
|
elseif IsInGroup(LE_PARTY_CATEGORY_HOME) then
|
|
SendChatMessage(msg, "PARTY", nil, nil);
|
|
end
|
|
end
|
|
local function SendGroupMessage(msg)
|
|
if IsInGroup(LE_PARTY_CATEGORY_INSTANCE) and IsInInstance() then
|
|
C_ChatInfo.SendAddonMessage("ATTC", msg, "INSTANCE_CHAT")
|
|
elseif IsInRaid() then
|
|
C_ChatInfo.SendAddonMessage("ATTC", msg, "RAID")
|
|
elseif IsInGroup(LE_PARTY_CATEGORY_HOME) then
|
|
C_ChatInfo.SendAddonMessage("ATTC", msg, "PARTY")
|
|
end
|
|
end
|
|
local function SendGuildMessage(msg)
|
|
if IsInGuild() then
|
|
C_ChatInfo.SendAddonMessage("ATTC", msg, "GUILD");
|
|
else
|
|
app.events.CHAT_MSG_ADDON("ATTC", msg, "WHISPER", "player");
|
|
end
|
|
end
|
|
local function SendResponseMessage(msg, player)
|
|
if UnitInRaid(player) or UnitInParty(player) then
|
|
SendGroupMessage("to\t" .. player .. "\t" .. msg);
|
|
else
|
|
C_ChatInfo.SendAddonMessage("ATTC", msg, "WHISPER", player);
|
|
end
|
|
end
|
|
|
|
-- Item Information Lib
|
|
local function SearchForLink(link)
|
|
if string.match(link, "item") then
|
|
-- Parse the link and get the itemID and bonus ids.
|
|
local itemString = string.match(link, "item[%-?%d:]+") or link;
|
|
if itemString then
|
|
-- Cache the Item ID and the Suffix ID.
|
|
local _, itemID, _, _, _, _, _, suffixID = strsplit(":", itemString);
|
|
if itemID then
|
|
-- Ensure that the itemID and suffixID are properly formatted.
|
|
itemID = tonumber(itemID) or 0;
|
|
if itemID > 0 then
|
|
if suffixID and suffixID ~= "" then
|
|
suffixID = tonumber(suffixID) or 0;
|
|
if suffixID > 0 then
|
|
-- Record the Suffix as valid for this itemID.
|
|
local suffixes = GetDataSubMember("ValidSuffixesPerItemID", itemID);
|
|
if not suffixes then
|
|
suffixes = {};
|
|
GetDataSubMember("ValidSuffixesPerItemID", itemID, suffixes);
|
|
end
|
|
if not suffixes[suffixID] then
|
|
suffixes[suffixID] = 1;
|
|
app.ClearItemCache();
|
|
end
|
|
end
|
|
end
|
|
return SearchForField("itemID", itemID), "itemID", itemID;
|
|
end
|
|
end
|
|
end
|
|
else
|
|
local kind, id = strsplit(":", link);
|
|
kind = string.gsub(string.lower(kind), "id", "ID");
|
|
if string.sub(kind,1,2) == "|c" then
|
|
kind = string.sub(kind,11);
|
|
end
|
|
if string.sub(kind,1,2) == "|h" then
|
|
kind = string.sub(kind,3);
|
|
end
|
|
if id then id = tonumber(strsplit("|[", id) or id); end
|
|
--print("SearchForLink A:", kind, id);
|
|
--print("SearchForLink B:", string.gsub(string.gsub(link, "|c", "c"), "|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";
|
|
end
|
|
local cache;
|
|
if id then
|
|
cache = SearchForField(kind, id);
|
|
if #cache == 0 then
|
|
local obj = CreateObject({
|
|
key = kind, [kind] = id,
|
|
hash = kind .. ":" .. id,
|
|
});
|
|
if not obj.__type then
|
|
obj.icon = "Interface\\ICONS\\INV_Misc_EngGizmos_20";
|
|
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 = "Interface\\ICONS\\INV_Misc_EngGizmos_20";
|
|
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
|
|
return {}, "", 0;
|
|
end
|
|
local function SearchForMissingItemsRecursively(group, listing)
|
|
if group.visible then
|
|
if group.itemID and (group.collectible or (group.total and group.total > 0)) and (not group.b or group.b == 2 or group.b == 3) then
|
|
tinsert(listing, group);
|
|
end
|
|
if group.g and group.expanded then
|
|
-- Go through the sub groups and determine if any of them have a response.
|
|
for i, subgroup in ipairs(group.g) do
|
|
SearchForMissingItemsRecursively(subgroup, listing);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
local function SearchForMissingItems(group)
|
|
local listing = {};
|
|
SearchForMissingItemsRecursively(group, listing);
|
|
return listing;
|
|
end
|
|
local function SearchForMissingItemNames(group)
|
|
-- Auctionator needs unique Item Names. Nothing else.
|
|
local uniqueNames = {};
|
|
for i,group in ipairs(SearchForMissingItems(group)) do
|
|
local name = group.name;
|
|
if name then uniqueNames[name] = 1; end
|
|
end
|
|
|
|
-- Build the array of names.
|
|
local arr = {};
|
|
for key,value in pairs(uniqueNames) do
|
|
tinsert(arr, key);
|
|
end
|
|
return arr;
|
|
end
|
|
local function UpdateSearchResults(searchResults)
|
|
if searchResults and #searchResults > 0 then
|
|
-- Attempt to cleanly refresh the data.
|
|
local fresh = false;
|
|
|
|
-- Mark all results as marked. This prevents a double +1 on parents.
|
|
for i,result in ipairs(searchResults) do
|
|
if result.visible and result.parent and result.parent.total then
|
|
result.marked = true;
|
|
end
|
|
end
|
|
|
|
-- Only unmark and +1 marked search results.
|
|
for i,result in ipairs(searchResults) do
|
|
if result.marked then
|
|
result.marked = nil;
|
|
if result.total then
|
|
-- This is an item that has a relative set of groups
|
|
app.UpdateParentProgress(result);
|
|
|
|
-- If this is NOT a group...
|
|
if not result.g then
|
|
-- If we've collected the item, use the "Show Collected Items" filter.
|
|
result.visible = app.CollectedItemVisibilityFilter(result);
|
|
end
|
|
else
|
|
app.UpdateParentProgress(result.parent);
|
|
|
|
-- If we've collected the item, use the "Show Collected Items" filter.
|
|
result.visible = app.CollectedItemVisibilityFilter(result);
|
|
end
|
|
fresh = true;
|
|
end
|
|
end
|
|
|
|
-- If the data is fresh, don't force a refresh.
|
|
if fresh then
|
|
app:RefreshDataCompletely("UpdateSearchResults");
|
|
else
|
|
app:RefreshDataQuietly("UpdateSearchResults", true);
|
|
end
|
|
end
|
|
end
|
|
app.SearchForLink = SearchForLink;
|
|
|
|
-- Dynamic Categories
|
|
do
|
|
local onClickForDynamicCategory = function(row, button)
|
|
local window = row.ref.dynamicWindow;
|
|
if window then
|
|
if button == "RightButton" then
|
|
window:Toggle();
|
|
return true;
|
|
elseif not row.ref.g or #row.ref.g < 1 then
|
|
if #window.data.g < 1 then window:ForceRebuild(); end
|
|
local prime = app:GetWindow("Prime");
|
|
local primeData = prime.data;
|
|
if primeData then
|
|
local progress, total = window.data.progress or 0, window.data.total or 0;
|
|
local g = CloneReference(window.data).g;
|
|
for i,o in ipairs(g) do
|
|
o.parent = row.ref;
|
|
end
|
|
row.ref.g = g;
|
|
row.ref.progress = progress;
|
|
row.ref.total = total;
|
|
prime:Refresh();
|
|
end
|
|
end
|
|
end
|
|
end
|
|
local onUpdateForDynamicCategory = function(data)
|
|
data.progress = nil; data.total = nil;
|
|
data.dynamicWindow:ForceRebuild();
|
|
--print("onUpdateForDynamicCategory", data.text, data.progress, data.total);
|
|
local parent, total = data.parent, data.total;
|
|
if parent and total then
|
|
parent.progress = parent.progress + data.progress;
|
|
parent.total = parent.total + total;
|
|
data.visible = app.GroupVisibilityFilter(data);
|
|
else
|
|
data.visible = true;
|
|
end
|
|
return true;
|
|
end
|
|
app.CreateDynamicCategory = app.CreateClass("DynamicCategory", "suffix", {
|
|
["dynamicWindow"] = function(t)
|
|
local window = app:GetWindow(t.suffix);
|
|
if window then return window; end
|
|
end,
|
|
["dynamicWindowData"] = function(t)
|
|
local window = app:GetWindow(t.suffix);
|
|
if window and window.data then
|
|
return window.data;
|
|
end
|
|
return app.EmptyTable;
|
|
end,
|
|
["text"] = function(t)
|
|
return t.dynamicWindowData.text or ("Dynamic Category: " .. t.suffix);
|
|
end,
|
|
["icon"] = function(t)
|
|
return t.dynamicWindowData.icon or "Interface/ICONS/INV_Misc_Gear_02";
|
|
end,
|
|
["description"] = function(t)
|
|
return t.dynamicWindowData.description;
|
|
end,
|
|
["progress"] = function(t)
|
|
return t.dynamicWindowData.progress;
|
|
end,
|
|
["total"] = function(t)
|
|
return t.dynamicWindowData.total;
|
|
end,
|
|
["summary"] = function(t)
|
|
local total = t.total;
|
|
if not total or total < 1 then
|
|
return "[Click to Cache]";
|
|
end
|
|
end,
|
|
["OnClick"] = function(t)
|
|
return onClickForDynamicCategory;
|
|
end,
|
|
["OnUpdate"] = function(t)
|
|
return onUpdateForDynamicCategory;
|
|
end,
|
|
});
|
|
end
|
|
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 app.GetProgressColorText(primeData.progress, primeData.total);
|
|
end
|
|
end
|
|
return app.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.GetNumberOfItemsUntilNextPercentage(primeData.progress, primeData.total);
|
|
end
|
|
end
|
|
return app.GetNumberOfItemsUntilNextPercentage(t.progress, t.total);
|
|
else
|
|
-- Something that isn't dynamic.
|
|
return table[key];
|
|
end
|
|
end
|
|
});
|
|
local g = rootData.g;
|
|
|
|
-- 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
|
|
tinsert(g, {
|
|
text = LOOT_JOURNAL_LEGENDARIES_SOURCE_CRAFTED_ITEM,
|
|
icon = app.asset("Category_Crafting"),
|
|
DontEnforceSkillRequirements = true,
|
|
g = app.Categories.Craftables,
|
|
isCraftedCategory = true
|
|
});
|
|
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
|
|
if app.Categories.Professions then
|
|
tinsert(g, {
|
|
text = TRADE_SKILLS,
|
|
icon = app.asset("Category_Professions"),
|
|
description = "This section will only show your character's professions outside of Account and Debug Mode.",
|
|
g = app.Categories.Professions
|
|
});
|
|
end
|
|
|
|
-- Holidays
|
|
if app.Categories.Holidays then
|
|
tinsert(g, app.CreateNPC(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,
|
|
OnUpdate = function(t)
|
|
local now = C_DateAndTime_GetServerTimeLocal();
|
|
app.Sort(t.g, function(a, b)
|
|
return (a.nextEvent and a.nextEvent.start or 0) < (b.nextEvent and b.nextEvent.start or 0);
|
|
end);
|
|
local temp = {};
|
|
for i=#t.g,1,-1 do
|
|
local a = t.g[i];
|
|
if a and a.nextEvent and a.nextEvent["end"] < now then
|
|
tremove(t.g, i);
|
|
tinsert(temp, a);
|
|
end
|
|
end
|
|
for i=#temp,1,-1 do
|
|
tinsert(t.g, temp[i]);
|
|
end
|
|
end,
|
|
isHolidayCategory = true,
|
|
}));
|
|
end
|
|
|
|
-- Expansion Features
|
|
if app.Categories.ExpansionFeatures and #app.Categories.ExpansionFeatures > 0 then
|
|
tinsert(g, {
|
|
text = "Expansion Features",
|
|
icon = app.asset("Category_ExpansionFeatures"),
|
|
g = app.Categories.ExpansionFeatures
|
|
});
|
|
end
|
|
|
|
-- In-Game Store
|
|
if app.Categories.InGameShop then
|
|
tinsert(g, app.CreateNPC(app.HeaderConstants.IN_GAME_SHOP, {
|
|
g = app.Categories.InGameShop,
|
|
expanded = false
|
|
}));
|
|
end
|
|
|
|
-- PvP
|
|
if app.Categories.PVP then
|
|
tinsert(g, app.CreateNPC(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
|
|
|
|
-- Skills
|
|
if app.Categories.Skills then
|
|
tinsert(g, {
|
|
text = SKILLS,
|
|
icon = "Interface\\ICONS\\SPELL_NATURE_THUNDERCLAP",
|
|
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
|
|
|
|
-- Now that we have all of the root data, cache it.
|
|
app.CacheFields(rootData);
|
|
|
|
-- The achievements window has a mix of dynamic and non-dynamic information.
|
|
local achievementDynamicCategory = app.CreateDynamicCategory("Achievements");
|
|
BuildGroups(achievementDynamicCategory.dynamicWindow.data);
|
|
tinsert(g, achievementDynamicCategory);
|
|
|
|
-- Dynamic Categories (Content generated and managed by a separate Window)
|
|
tinsert(g, app.CreateDynamicCategory("Battle Pets"));
|
|
tinsert(g, app.CreateDynamicCategory("Factions"));
|
|
tinsert(g, app.CreateDynamicCategory("Flight Paths"));
|
|
if C_Heirloom then tinsert(g, app.CreateDynamicCategory("Heirlooms")); end
|
|
tinsert(g, app.CreateDynamicCategory("Mounts"));
|
|
tinsert(g, app.CreateDynamicCategory("Titles"));
|
|
tinsert(g, app.CreateDynamicCategory("Toys"));
|
|
|
|
-- Track Deaths!
|
|
tinsert(g, app:CreateDeathClass());
|
|
|
|
-- Yourself.
|
|
tinsert(g, app.CreateUnit("player", {
|
|
description = "Awarded for logging in.\n\nGood job! YOU DID IT!\n\nOnly visible while in Debug Mode.",
|
|
races = { app.RaceIndex },
|
|
c = { app.ClassIndex },
|
|
r = app.FactionID,
|
|
collected = 1,
|
|
nmr = false,
|
|
OnUpdate = function(self)
|
|
self.lvl = app.Level;
|
|
if app.Settings:Get("DebugMode") then
|
|
self.collectible = true;
|
|
else
|
|
self.collectible = false;
|
|
end
|
|
end
|
|
}));
|
|
|
|
-- Now assign the parent hierarchy for this cache.
|
|
BuildGroups(rootData);
|
|
|
|
-- Determine how many tierID instances could be found
|
|
local tierCounter = 0;
|
|
local tierCache = SearchForFieldContainer("tierID");
|
|
for key,value in pairs(tierCache) do
|
|
tierCounter = tierCounter + 1;
|
|
end
|
|
if tierCounter == 1 then
|
|
-- Purge the Tier Objects. This is the Classic Layout style.
|
|
for key,values in pairs(tierCache) do
|
|
for j,value in ipairs(values) do
|
|
local parent = value.parent;
|
|
if parent then
|
|
-- Remove the tier 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 tier object cache.
|
|
wipe(tierCache);
|
|
end
|
|
|
|
-- All future calls to this function will return the root data.
|
|
app.GetDataCache = function()
|
|
return rootData;
|
|
end
|
|
return rootData;
|
|
end
|
|
end
|
|
|
|
-- Tooltip Functions
|
|
local EXTERMINATOR = {
|
|
["Player-4372-00B131BB"] = true, -- Aivet
|
|
["Player-4372-004A0418"] = true, -- Jubilee
|
|
["Player-4372-00273DCA"] = true, -- Havadin
|
|
["Player-4372-00DED426"] = true, -- Krieve
|
|
["Player-4372-00862D32"] = true, -- Aethbric
|
|
["Player-4372-0128B376"] = true, -- Alizewsaur
|
|
["Player-4372-012A730E"] = true, -- Allysandra
|
|
["Player-4372-00FE5CE7"] = true, -- Amiera
|
|
["Player-4372-0073B95B"] = true, -- Amyralynn
|
|
["Player-4372-0087049A"] = true, -- Asandra
|
|
["Player-4372-003159A9"] = true, -- Astromarus
|
|
["Player-4372-006A97BA"] = true, -- Azwel
|
|
["Player-4372-0014521D"] = true, -- Bombeon
|
|
["Player-4372-00E86132"] = true, -- Borlemont
|
|
["Player-4372-010B9178"] = true, -- Braven
|
|
["Player-4372-0063664F"] = true, -- Brittbrat
|
|
["Player-4372-001BA8B1"] = true, -- Darkirontank
|
|
["Player-4372-0100DF23"] = true, -- Dizplaced
|
|
["Player-4372-01230376"] = true, -- Drixxtwo
|
|
["Player-4372-002719C4"] = true, -- Drunkninja
|
|
["Player-4372-0124174F"] = true, -- Dubsteve
|
|
["Player-4372-00BD6CC7"] = true, -- Enthira
|
|
["Player-4372-00A3A0FD"] = true, -- Fairplay
|
|
["Player-4372-0046DDA0"] = true, -- Firasia
|
|
["Player-4372-004A7A3F"] = true, -- Fortress
|
|
["Player-4372-00CF7821"] = true, -- Glas
|
|
["Player-4372-00E7DEC4"] = true, -- Gnubs
|
|
["Player-4372-0108DCC1"] = true, -- Grotesque
|
|
["Player-4372-00E8CC3C"] = true, -- Hairyplodder
|
|
["Player-4372-00D38E94"] = true, -- Havachant
|
|
["Player-4372-00312AD9"] = true, -- Hewn
|
|
["Player-4372-0046F7E8"] = true, -- Holochops
|
|
["Player-4372-007AF4B7"] = true, -- Intothefray
|
|
["Player-4372-01125B10"] = true, -- Jonaya
|
|
["Player-4372-011C1FE9"] = true, -- Katalysm
|
|
["Player-4372-00EBCC07"] = true, -- Lilithann
|
|
["Player-4372-0075A187"] = true, -- Loknido
|
|
["Player-4372-01390D2A"] = true, -- Manamontanna
|
|
["Player-4372-00FE5DA2"] = true, -- Mimico
|
|
["Player-4372-00D7B345"] = true, -- Narom
|
|
["Player-4372-01353958"] = true, -- Naromot
|
|
["Player-4372-01294037"] = true, -- Necrid
|
|
["Player-4372-00793732"] = true, -- Nirv
|
|
["Player-4372-01250D6D"] = true, -- Pewpeu
|
|
["Player-4372-0008B144"] = true, -- Pixl
|
|
["Player-4372-00C2F945"] = true, -- Rooni
|
|
["Player-4372-0058A418"] = true, -- Saitosan [Druid]
|
|
["Player-4372-0123A5BE"] = true, -- Sheisskopf
|
|
["Player-4372-00F82168"] = true, -- Semiha
|
|
["Player-4372-001F92DA"] = true, -- Shadrac
|
|
["Player-4372-00732218"] = true, -- Solow
|
|
["Player-4372-01091DE4"] = true, -- Tacolock
|
|
["Player-4372-00451B8E"] = true, -- Tinybit
|
|
["Player-4372-00E5AE25"] = true, -- Villeinia
|
|
["Player-4372-0066A25C"] = true, -- Viran
|
|
["Player-4372-00D96703"] = true, -- Worfin
|
|
};
|
|
local GOLD_TYCOON = {
|
|
["Player-4372-004A0418"] = true, -- Jubilee
|
|
["Player-4372-00273DCA"] = true, -- Havadin
|
|
["Player-4372-0068D548"] = true, -- Headphones
|
|
["Player-4372-00F2D620"] = true, -- Notloknido
|
|
["Player-4372-00FF84F0"] = true, -- Saitosan [Priest]
|
|
};
|
|
local SCARAB_LORD = {
|
|
["Player-4372-000B3C4D"] = true, -- Congelatore
|
|
["Player-4372-00A64EA0"] = true, -- Macpayn
|
|
};
|
|
local function AttachTooltipRawSearchResults(self, lineNumber, group)
|
|
if group then
|
|
-- If there was info text generated for this search result, then display that first.
|
|
if group.tooltipInfo and #group.tooltipInfo > 0 then
|
|
local left, right, o;
|
|
local leftname = self:GetName() .. "TextLeft";
|
|
local rightname = self:GetName() .. "TextRight";
|
|
for i,entry in ipairs(group.tooltipInfo) do
|
|
local found = false;
|
|
left = entry.left or " ";
|
|
for i=self:NumLines(),1,-1 do
|
|
if _G[leftname..i]:GetText() == left then
|
|
o = _G[rightname..i];
|
|
if o and o:GetText() == entry.right then
|
|
found = true;
|
|
break;
|
|
end
|
|
end
|
|
end
|
|
if not found then
|
|
right = entry.right;
|
|
if right then
|
|
if entry.r then
|
|
self:AddDoubleLine(left, right, entry.r, entry.g, entry.b, entry.r, entry.g, entry.b);
|
|
else
|
|
self:AddDoubleLine(left, right);
|
|
end
|
|
elseif entry.r then
|
|
if entry.wrap then
|
|
self:AddLine(left, entry.r, entry.g, entry.b, 1);
|
|
else
|
|
self:AddLine(left, entry.r, entry.g, entry.b);
|
|
end
|
|
else
|
|
if entry.wrap then
|
|
self:AddLine(left, 1, 1, 1, 1);
|
|
else
|
|
self:AddLine(left);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- If the user has Show Collection Progress turned on.
|
|
if group.encounterID then
|
|
self:Show();
|
|
elseif group.collectionText and self:NumLines() > 0 then
|
|
local rightSide = _G[self:GetName() .. "TextRight" .. (lineNumber or 1)];
|
|
if rightSide then
|
|
rightSide:SetText(group.collectionText);
|
|
rightSide:Show();
|
|
end
|
|
end
|
|
end
|
|
end
|
|
local function AttachTooltipSearchResults(self, lineNumber, search, method, ...)
|
|
AttachTooltipRawSearchResults(self, lineNumber, GetCachedSearchResults(search, method, ...));
|
|
end
|
|
local function AttachTooltip(self)
|
|
if not self.ATTCProcessing then
|
|
self.ATTCProcessing = true;
|
|
if (not InCombatLockdown() or app.Settings:GetTooltipSetting("DisplayInCombat")) and app.Settings:GetTooltipSetting("Enabled") then
|
|
local numLines = self:NumLines();
|
|
if numLines > 0 then
|
|
--[[--
|
|
-- Debug all of the available fields on the tooltip.
|
|
for i,j in pairs(self) do
|
|
self:AddDoubleLine(tostring(i), tostring(j));
|
|
end
|
|
self:Show();
|
|
self:AddDoubleLine("GetItem", tostring(select(2, self:GetItem()) or "nil"));
|
|
self:AddDoubleLine("GetSpell", tostring(select(2, self:GetSpell()) or "nil"));
|
|
self:AddDoubleLine("GetUnit", tostring(select(2, self:GetUnit()) or "nil"));
|
|
--]]--
|
|
|
|
-- Does the tooltip have an owner?
|
|
local owner = self:GetOwner();
|
|
if owner then
|
|
if owner.SpellHighlightTexture and false then
|
|
-- Actionbars, don't want that.
|
|
return true;
|
|
end
|
|
end
|
|
|
|
-- Does the tooltip have a target?
|
|
local target = select(2, self:GetUnit());
|
|
if target then
|
|
-- Yes.
|
|
local guid = UnitGUID(target);
|
|
if guid then
|
|
local type, zero, server_id, instance_id, zone_uid, npcID, spawn_uid = strsplit("-",guid);
|
|
-- print(guid, type, npcID);
|
|
if type == "Player" then
|
|
if guid == "Player-4372-0000390A" then
|
|
local leftSide = _G[self:GetName() .. "TextLeft1"];
|
|
if leftSide then
|
|
leftSide:SetText("|c" .. app.Colors.Raid .. UnitName(target) .. " the Completionist|r");
|
|
end
|
|
local rightSide = _G[self:GetName() .. "TextRight2"];
|
|
leftSide = _G[self:GetName() .. "TextLeft2"];
|
|
if leftSide and rightSide then
|
|
leftSide:SetText(L["TITLE"]);
|
|
leftSide:Show();
|
|
rightSide:SetText("Author");
|
|
rightSide:Show();
|
|
else
|
|
self:AddDoubleLine(L["TITLE"], "Author");
|
|
end
|
|
elseif SCARAB_LORD[guid] then
|
|
local leftSide = _G[self:GetName() .. "TextLeft1"];
|
|
if leftSide then leftSide:SetText("|c" .. app.Colors.Raid .. "Scarab Lord " .. UnitName(target) .. "|r"); end
|
|
elseif GOLD_TYCOON[guid] then
|
|
local leftSide = _G[self:GetName() .. "TextLeft1"];
|
|
if leftSide then leftSide:SetText("|c" .. app.Colors.Raid .. "Gold Tycoon " .. UnitName(target) .. "|r"); end
|
|
elseif EXTERMINATOR[guid] then
|
|
local leftSide = _G[self:GetName() .. "TextLeft1"];
|
|
if leftSide then leftSide:SetText("|cffa335ee" .. UnitName(target) .. " the Exterminator|r"); end
|
|
elseif guid == "Player-4372-00006B41" then
|
|
local leftSide = _G[self:GetName() .. "TextLeft1"];
|
|
if leftSide then leftSide:SetText("|cffF58CBA" .. UnitName(target) .. " the Huggler|r"); end
|
|
elseif guid == "Player-4647-031D0890" then
|
|
local leftSide = _G[self:GetName() .. "TextLeft1"];
|
|
if leftSide then leftSide:SetText("|cff665a2c" .. UnitName(target) .. " the Time-Loser|r"); end
|
|
local rightSide = _G[self:GetName() .. "TextRight2"];
|
|
if rightSide then rightSide:SetText(GetCollectionIcon(0)); end
|
|
self:AddLine("This scumbag abused an auto-invite addon to steal the Time-Lost Proto Drake from a person that had them on their friends list. ATT has deemed this unacceptable behaviour and will forever stain this player's reputation so long as they remain on the server.", 0.4, 0.8, 1, true);
|
|
end
|
|
elseif type == "Creature" or type == "Vehicle" then
|
|
if app.Settings:GetTooltipSetting("creatureID") then self:AddDoubleLine(L["CREATURE_ID"], tostring(npcID)); end
|
|
AttachTooltipSearchResults(self, 1, "creatureID:" .. npcID, SearchForField, "creatureID", tonumber(npcID));
|
|
end
|
|
return true;
|
|
end
|
|
end
|
|
|
|
-- Does the tooltip have a spell? [Mount Journal, Action Bars, etc]
|
|
local spellID = select(2, self:GetSpell());
|
|
if spellID then
|
|
AttachTooltipSearchResults(self, 1, "spellID:" .. spellID, SearchForField, "spellID", spellID);
|
|
self:Show();
|
|
if owner and owner.ActiveTexture then
|
|
self.ATTCProcessing = nil;
|
|
end
|
|
return true;
|
|
end
|
|
|
|
-- Does the tooltip have an itemlink?
|
|
local itemName, link = self:GetItem();
|
|
if link then
|
|
if owner and owner.cooldownWrapper then
|
|
local parent = owner:GetParent();
|
|
if parent then
|
|
parent = parent:GetParent();
|
|
if parent and parent.fanfareToys then
|
|
-- Toy Box, it needs a Show call.
|
|
-- Also the ToyBox UI is broken and returns the wrong item information when you look at any other item's tooltip before looking at the toybox.
|
|
local leftSide = _G[self:GetName() .. "TextLeft1"]:GetText();
|
|
if itemName ~= leftSide then link = select(2, GetItemInfo(leftSide)); end
|
|
AttachTooltipSearchResults(self, 1, link, SearchForLink, link);
|
|
self:Show();
|
|
return true;
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Normal item tooltip, not on the Toy Box.
|
|
AttachTooltipSearchResults(self, 1, link, SearchForLink, link);
|
|
end
|
|
|
|
-- If the owner has a ref, it's an ATT row. Ignore it.
|
|
if owner and owner.ref then return true; end
|
|
|
|
local objectID = app.GetBestObjectIDForName(_G[self:GetName() .. "TextLeft1"]:GetText());
|
|
if objectID then
|
|
AttachTooltipSearchResults(self, 1, "objectID:" .. objectID, SearchForField, "objectID", objectID);
|
|
self:Show();
|
|
return true;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
local function ClearTooltip(self)
|
|
self.ATTCProcessing = nil;
|
|
end
|
|
local function ShowItemCompareTooltips(...)
|
|
local items = { ... };
|
|
local count = #items;
|
|
if count > 0 then
|
|
for i,item in ipairs(items) do
|
|
local shoppingTooltip = GameTooltip.shoppingTooltips[i];
|
|
if shoppingTooltip then
|
|
shoppingTooltip.attItem = type(item) == "number" and select(2, GetItemInfo(item)) or item;
|
|
pcall(shoppingTooltip.SetHyperlink, shoppingTooltip, shoppingTooltip.attItem);
|
|
else
|
|
break;
|
|
end
|
|
end
|
|
|
|
-- Quick maths
|
|
-- Taken from https://github.com/Ennie/wow-ui-source/blob/master/FrameXML/GameTooltip.lua
|
|
local shoppingTooltip1, shoppingTooltip2, shoppingTooltip3 = unpack(GameTooltip.shoppingTooltips);
|
|
local leftPos, rightPos = (GameTooltip:GetLeft() or 0), (GameTooltip:GetRight() or 0);
|
|
if GetScreenWidth() - rightPos < leftPos then
|
|
side = "left";
|
|
else
|
|
side = "right";
|
|
end
|
|
|
|
-- see if we should slide the tooltip
|
|
local anchorType = GameTooltip:GetAnchorType();
|
|
if anchorType and anchorType ~= "ANCHOR_PRESERVE" then
|
|
local totalWidth = shoppingTooltip1:GetWidth();
|
|
if count > 1 then totalWidth = totalWidth + shoppingTooltip2:GetWidth(); end
|
|
if count > 2 then totalWidth = totalWidth + shoppingTooltip3:GetWidth(); end
|
|
if ( (side == "left") and (totalWidth > leftPos) ) then
|
|
GameTooltip:SetAnchorType(anchorType, (totalWidth - leftPos), 0);
|
|
elseif ( (side == "right") and (rightPos + totalWidth) > GetScreenWidth() ) then
|
|
GameTooltip:SetAnchorType(anchorType, -((rightPos + totalWidth) - GetScreenWidth()), 0);
|
|
end
|
|
end
|
|
|
|
-- anchor the compare tooltips
|
|
if count > 2 then
|
|
shoppingTooltip3:SetOwner(GameTooltip, "ANCHOR_NONE");
|
|
shoppingTooltip3:ClearAllPoints();
|
|
if side == "left" then
|
|
shoppingTooltip3:SetPoint("TOPRIGHT", GameTooltip, "TOPLEFT", 0, -10);
|
|
else
|
|
shoppingTooltip3:SetPoint("TOPLEFT", GameTooltip, "TOPRIGHT", 0, -10);
|
|
end
|
|
pcall(shoppingTooltip3.SetHyperlink, shoppingTooltip3, shoppingTooltip3.attItem);
|
|
shoppingTooltip3:Show();
|
|
shoppingTooltip1:SetOwner(shoppingTooltip3, "ANCHOR_NONE");
|
|
else
|
|
shoppingTooltip1:SetOwner(GameTooltip, "ANCHOR_NONE");
|
|
end
|
|
shoppingTooltip1:ClearAllPoints();
|
|
if side == "left" then
|
|
if count > 2 then
|
|
shoppingTooltip1:SetPoint("TOPRIGHT", shoppingTooltip3, "TOPLEFT", 0, 0);
|
|
else
|
|
shoppingTooltip1:SetPoint("TOPRIGHT", GameTooltip, "TOPLEFT", 0, -10);
|
|
end
|
|
else
|
|
if count > 2 then
|
|
shoppingTooltip1:SetPoint("TOPLEFT", shoppingTooltip3, "TOPRIGHT", 0, 0);
|
|
else
|
|
shoppingTooltip1:SetPoint("TOPLEFT", GameTooltip, "TOPRIGHT", 0, -10);
|
|
end
|
|
end
|
|
pcall(shoppingTooltip1.SetHyperlink, shoppingTooltip1, shoppingTooltip1.attItem);
|
|
shoppingTooltip1:Show();
|
|
|
|
if count > 1 then
|
|
shoppingTooltip2:SetOwner(shoppingTooltip1, "ANCHOR_NONE");
|
|
shoppingTooltip2:ClearAllPoints();
|
|
if side == "left" then
|
|
shoppingTooltip2:SetPoint("TOPRIGHT", shoppingTooltip1, "TOPLEFT", 0, 0);
|
|
else
|
|
shoppingTooltip2:SetPoint("TOPLEFT", shoppingTooltip1, "TOPRIGHT", 0, 0);
|
|
end
|
|
pcall(shoppingTooltip2.SetHyperlink, shoppingTooltip2, shoppingTooltip2.attItem);
|
|
shoppingTooltip2:Show();
|
|
end
|
|
|
|
return shoppingTooltip1, shoppingTooltip2, shoppingTooltip3;
|
|
end
|
|
end
|
|
app.ShowItemCompareTooltips = ShowItemCompareTooltips;
|
|
|
|
-- 10.0.2
|
|
-- https://wowpedia.fandom.com/wiki/Patch_10.0.2/API_changes#Tooltip_Changes
|
|
if TooltipDataProcessor then
|
|
local Enum_TooltipDataType = Enum.TooltipDataType;
|
|
local TooltipTypes = {
|
|
[Enum_TooltipDataType.Toy] = "itemID",
|
|
[Enum_TooltipDataType.Item] = "itemID",
|
|
[Enum_TooltipDataType.Spell] = "spellID",
|
|
[Enum_TooltipDataType.UnitAura] = "spellID",
|
|
--[Enum_TooltipDataType.Mount] = "spellID",
|
|
--[Enum_TooltipDataType.Macro] = "spellID", -- Possibly?
|
|
[Enum_TooltipDataType.Achievement] = "achievementID",
|
|
[Enum_TooltipDataType.Quest] = "questID",
|
|
[Enum_TooltipDataType.QuestPartyProgress] = "questID",
|
|
--[Enum_TooltipDataType.BattlePet] = "speciesID",
|
|
--[Enum_TooltipDataType.CompanionPet] = "speciesID",
|
|
[Enum_TooltipDataType.Currency] = "currencyID",
|
|
[Enum_TooltipDataType.InstanceLock] = "instanceID",
|
|
};
|
|
--[[
|
|
for key,id in pairs(Enum_TooltipDataType) do
|
|
if not TooltipTypes[id] then
|
|
print("Not handling Tooltip Type", key, id);
|
|
end
|
|
end
|
|
]]--
|
|
TooltipDataProcessor.AddTooltipPostCall(TooltipDataProcessor.AllTypes, function(tooltip, data)
|
|
if InCombatLockdown() and not app.Settings:GetTooltipSetting("DisplayInCombat") then
|
|
return;
|
|
end
|
|
if not app.Settings:GetTooltipSetting("Enabled") then
|
|
return;
|
|
end
|
|
|
|
local enumType = data.type;
|
|
local key = TooltipTypes[enumType];
|
|
if key then
|
|
if key == "" then
|
|
-- Intentionally blacklisted.
|
|
return;
|
|
end
|
|
|
|
-- Try the default.
|
|
local id = data.id;
|
|
if id then
|
|
AttachTooltipSearchResults(tooltip, 1, key .. ":" .. id, SearchForField, key, id);
|
|
return;
|
|
end
|
|
|
|
local name, link, id = TooltipUtil.GetDisplayedItem(tooltip);
|
|
if link and id then
|
|
if id == 137642 then -- skip Mark of Honor for now
|
|
AttachTooltipSearchResults(self, 1, link, app.EmptyFunction, "itemID", 137642);
|
|
return true;
|
|
else
|
|
AttachTooltipSearchResults(tooltip, 1, link, SearchForLink, link);
|
|
return;
|
|
end
|
|
end
|
|
|
|
local name, spellID = TooltipUtil.GetDisplayedSpell(tooltip);
|
|
if spellID then
|
|
AttachTooltipSearchResults(tooltip, 1, "spellID:" .. spellID, SearchForField, "spellID", spellID);
|
|
return;
|
|
end
|
|
elseif enumType == 10 then
|
|
-- Mounts!
|
|
local spellID = select(2, C_MountJournal.GetMountInfoByID(data.id));
|
|
if spellID then
|
|
AttachTooltipSearchResults(tooltip, 1, "spellID:" .. spellID, SearchForField, "spellID", spellID);
|
|
return;
|
|
end
|
|
elseif enumType == 4 then
|
|
-- Objects!
|
|
local objectID = app.GetBestObjectIDForName(data.lines[1].leftText);
|
|
if objectID then
|
|
AttachTooltipSearchResults(tooltip, 1, "objectID:" .. objectID, SearchForField, "objectID", objectID);
|
|
return true;
|
|
end
|
|
elseif enumType == 21 then
|
|
-- Minimap Mouseover
|
|
local content = data.lines;
|
|
if content and #content > 0 then
|
|
local text = content[1].leftText;
|
|
local arr = { strsplit("|", text) };
|
|
if #arr == 3 then text = strsub(arr[3], 2); end
|
|
local objectID = app.GetBestObjectIDForName(text);
|
|
if objectID then
|
|
AttachTooltipSearchResults(tooltip, 1, "objectID:" .. objectID, SearchForField, "objectID", objectID);
|
|
return true;
|
|
end
|
|
end
|
|
elseif enumType == 2 then
|
|
-- Units!
|
|
local name, t, guid = TooltipUtil.GetDisplayedUnit(tooltip);
|
|
if guid then
|
|
local type, zero, server_id, instance_id, zone_uid, npcID, spawn_uid = strsplit("-",guid);
|
|
-- print(guid, type, npcID);
|
|
if type == "Player" then
|
|
if guid == "Player-4372-0000390A" or guid == "Player-76-0895E23B" then
|
|
local leftSide = _G[tooltip:GetName() .. "TextLeft1"];
|
|
if leftSide then
|
|
leftSide:SetText("|c" .. app.Colors.Raid .. name .. " the Completionist|r");
|
|
end
|
|
local rightSide = _G[tooltip:GetName() .. "TextRight2"];
|
|
leftSide = _G[tooltip:GetName() .. "TextLeft2"];
|
|
if leftSide and rightSide then
|
|
leftSide:SetText(L["TITLE"]);
|
|
leftSide:Show();
|
|
rightSide:SetText("Author");
|
|
rightSide:Show();
|
|
else
|
|
tooltip:AddDoubleLine(L["TITLE"], "Author");
|
|
end
|
|
elseif SCARAB_LORD[guid] then
|
|
local leftSide = _G[tooltip:GetName() .. "TextLeft1"];
|
|
if leftSide then leftSide:SetText("|c" .. app.Colors.Raid .. "Scarab Lord " .. name .. "|r"); end
|
|
elseif GOLD_TYCOON[guid] then
|
|
local leftSide = _G[tooltip:GetName() .. "TextLeft1"];
|
|
if leftSide then leftSide:SetText("|c" .. app.Colors.Raid .. "Gold Tycoon " .. name .. "|r"); end
|
|
elseif EXTERMINATOR[guid] then
|
|
local leftSide = _G[tooltip:GetName() .. "TextLeft1"];
|
|
if leftSide then leftSide:SetText("|cffa335ee" .. name .. " the Exterminator|r"); end
|
|
elseif guid == "Player-4372-00006B41" then
|
|
local leftSide = _G[tooltip:GetName() .. "TextLeft1"];
|
|
if leftSide then leftSide:SetText("|cffF58CBA" .. name .. " the Huggler|r"); end
|
|
elseif guid == "Player-4647-031D0890" then
|
|
local leftSide = _G[tooltip:GetName() .. "TextLeft1"];
|
|
if leftSide then leftSide:SetText("|cff665a2c" .. name .. " the Time-Loser|r"); end
|
|
local rightSide = _G[tooltip:GetName() .. "TextRight2"];
|
|
if rightSide then rightSide:SetText(GetCollectionIcon(0)); end
|
|
tooltip:AddLine("This scumbag abused an auto-invite addon to steal the Time-Lost Proto Drake from a person that had them on their friends list. ATT has deemed this unacceptable behaviour and will forever stain this player's reputation so long as they remain on the server.", 0.4, 0.8, 1, true);
|
|
end
|
|
elseif type == "Creature" or type == "Vehicle" then
|
|
if app.Settings:GetTooltipSetting("creatureID") then tooltip:AddDoubleLine(L["CREATURE_ID"], tostring(npcID)); end
|
|
AttachTooltipSearchResults(tooltip, 1, "creatureID:" .. npcID, SearchForField, "creatureID", tonumber(npcID));
|
|
end
|
|
return true;
|
|
end
|
|
elseif enumType == 25 then
|
|
-- Macro!
|
|
local content = data.lines;
|
|
if content and #content > 1 then
|
|
local tooltipType = content[2].type;
|
|
if tooltipType == 13 then
|
|
local spellID = content[1].tooltipID;
|
|
if spellID then
|
|
AttachTooltipSearchResults(tooltip, 1, "spellID:" .. spellID, SearchForField, "spellID", spellID);
|
|
return;
|
|
end
|
|
elseif tooltipType == 29 then
|
|
local itemID = content[1].tooltipID;
|
|
if itemID then
|
|
AttachTooltipSearchResults(tooltip, 1, "itemID:" .. itemID, SearchForField, "itemID", itemID);
|
|
return;
|
|
end
|
|
end
|
|
end
|
|
else
|
|
--[[
|
|
for key,value in pairs(data) do
|
|
if type(value) == "table" then
|
|
tooltip:AddLine(key);
|
|
for key2,value2 in pairs(value) do
|
|
tooltip:AddLine(" " .. key2);
|
|
if type(value2) == "table" then
|
|
for key3,value3 in pairs(value2) do
|
|
tooltip:AddDoubleLine(" " .. key3, tostring(value3));
|
|
end
|
|
else
|
|
tooltip:AddDoubleLine(" " .. key2, tostring(value2));
|
|
end
|
|
end
|
|
else
|
|
tooltip:AddDoubleLine(key, tostring(value));
|
|
end
|
|
end
|
|
-- Report unhandled types.
|
|
for key,id in pairs(Enum_TooltipDataType) do
|
|
if id == enumType then
|
|
print("Unhandled Tooltip Type", key, enumType);
|
|
return;
|
|
end
|
|
end
|
|
print("Unhandled Tooltip Type", enumType);
|
|
]]--
|
|
end
|
|
end)
|
|
else
|
|
GameTooltip:HookScript("OnTooltipSetQuest", AttachTooltip);
|
|
GameTooltip:HookScript("OnTooltipSetItem", AttachTooltip);
|
|
GameTooltip:HookScript("OnTooltipSetUnit", AttachTooltip);
|
|
GameTooltip:HookScript("OnTooltipCleared", ClearTooltip);
|
|
GameTooltip:HookScript("OnShow", AttachTooltip);
|
|
GameTooltip:HookScript("OnUpdate", AttachTooltip);
|
|
GameTooltip:HookScript("OnHide", ClearTooltip);
|
|
ItemRefTooltip:HookScript("OnShow", AttachTooltip);
|
|
ItemRefTooltip:HookScript("OnTooltipSetQuest", AttachTooltip);
|
|
ItemRefTooltip:HookScript("OnTooltipSetItem", AttachTooltip);
|
|
ItemRefTooltip:HookScript("OnTooltipCleared", ClearTooltip);
|
|
ItemRefShoppingTooltip1:HookScript("OnShow", AttachTooltip);
|
|
ItemRefShoppingTooltip1:HookScript("OnTooltipSetQuest", AttachTooltip);
|
|
ItemRefShoppingTooltip1:HookScript("OnTooltipSetItem", AttachTooltip);
|
|
ItemRefShoppingTooltip1:HookScript("OnTooltipCleared", ClearTooltip);
|
|
ItemRefShoppingTooltip2:HookScript("OnShow", AttachTooltip);
|
|
ItemRefShoppingTooltip2:HookScript("OnTooltipSetQuest", AttachTooltip);
|
|
ItemRefShoppingTooltip2:HookScript("OnTooltipSetItem", AttachTooltip);
|
|
ItemRefShoppingTooltip2:HookScript("OnTooltipCleared", ClearTooltip);
|
|
WorldMapTooltip.ItemTooltip.Tooltip:HookScript("OnTooltipSetQuest", AttachTooltip);
|
|
WorldMapTooltip.ItemTooltip.Tooltip:HookScript("OnTooltipSetItem", AttachTooltip);
|
|
WorldMapTooltip.ItemTooltip.Tooltip:HookScript("OnTooltipSetUnit", AttachTooltip);
|
|
WorldMapTooltip.ItemTooltip.Tooltip:HookScript("OnTooltipCleared", ClearTooltip);
|
|
WorldMapTooltip:HookScript("OnTooltipSetItem", AttachTooltip);
|
|
WorldMapTooltip:HookScript("OnTooltipSetQuest", AttachTooltip);
|
|
WorldMapTooltip:HookScript("OnTooltipCleared", ClearTooltip);
|
|
WorldMapTooltip:HookScript("OnShow", AttachTooltip);
|
|
|
|
tinsert(app.EventHandlers.OnReady, function()
|
|
local GameTooltip_SetLFGDungeonReward = GameTooltip.SetLFGDungeonReward;
|
|
if GameTooltip_SetLFGDungeonReward then
|
|
GameTooltip.SetLFGDungeonReward = function(self, dungeonID, rewardIndex)
|
|
GameTooltip_SetLFGDungeonReward(self, dungeonID, rewardIndex);
|
|
local name, texturePath, quantity, isBonusReward, spec, itemID = GetLFGDungeonRewardInfo(dungeonID, rewardIndex);
|
|
if itemID then
|
|
if spec == "item" then
|
|
AttachTooltipSearchResults(self, 1, "itemID:" .. itemID, SearchForField, "itemID", itemID);
|
|
self:Show();
|
|
elseif spec == "currency" then
|
|
AttachTooltipSearchResults(self, 1, "currencyID:" .. itemID, SearchForField, "currencyID", itemID);
|
|
self:Show();
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local GameTooltip_SetLFGDungeonShortageReward = GameTooltip.SetLFGDungeonShortageReward;
|
|
if GameTooltip_SetLFGDungeonShortageReward then
|
|
GameTooltip.SetLFGDungeonShortageReward = function(self, dungeonID, shortageSeverity, lootIndex)
|
|
GameTooltip_SetLFGDungeonShortageReward(self, dungeonID, shortageSeverity, lootIndex);
|
|
local name, texturePath, quantity, isBonusReward, spec, itemID = GetLFGDungeonShortageRewardInfo(dungeonID, shortageSeverity, lootIndex);
|
|
if itemID then
|
|
if spec == "item" then
|
|
AttachTooltipSearchResults(self, 1, "itemID:" .. itemID, SearchForField, "itemID", itemID);
|
|
self:Show();
|
|
elseif spec == "currency" then
|
|
AttachTooltipSearchResults(self, 1, "currencyID:" .. itemID, SearchForField, "currencyID", itemID);
|
|
self:Show();
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end);
|
|
end
|
|
if app.GameBuildVersion > 11403 then
|
|
app:RegisterEvent("CURSOR_CHANGED");
|
|
app.events.CURSOR_CHANGED = function()
|
|
app:StartATTCoroutine("UpdateTooltip", function()
|
|
while not GameTooltip:IsShown() do
|
|
coroutine.yield();
|
|
end
|
|
AttachTooltip(GameTooltip);
|
|
end);
|
|
end
|
|
end
|
|
|
|
local oldItemSetHyperlink = ItemRefTooltip.SetHyperlink;
|
|
function ItemRefTooltip:SetHyperlink(link, a, b, c, d, e, f)
|
|
-- Make sure to call the default function, but with a try-catch.
|
|
local status, err = pcall(function () oldItemSetHyperlink(self, link, a, b, c, d, e, f) end)
|
|
if not status then
|
|
-- Search for the Link in the database
|
|
local linkType, id, params = strsplit(':', link)
|
|
local cmd = linkType .. "ID" .. ":" .. id;
|
|
local group = GetCachedSearchResults(cmd, SearchForLink, cmd);
|
|
if group then app:CreateMiniListForGroup(group); end
|
|
end
|
|
end
|
|
|
|
|
|
-- Achievement Lib
|
|
(function()
|
|
local SetAchievementCollected = function(t, achievementID, collected)
|
|
return app.SetCollected(t, "Achievements", achievementID, collected);
|
|
end
|
|
|
|
-- These achievement handlers are good to use at any point.
|
|
local LOREMASTER_CreateQuests = function(t, extraQuestIDs)
|
|
local parent = t.sourceParent or t.parent;
|
|
if parent then
|
|
-- Get the quests list from the zone itself.
|
|
local g, quests, count = parent.parent and parent.parent.g, {}, 0;
|
|
if g and #g > 0 then
|
|
for i,o in ipairs(g) do
|
|
if o.headerID == app.HeaderConstants.QUESTS then
|
|
-- Clone the list to prevent dirtying the quest list in the zone.
|
|
for j,quest in ipairs(o.g) do
|
|
if quest.key == "questID" and not quest.repeatable then
|
|
count = count + 1;
|
|
quests[count] = quest;
|
|
end
|
|
end
|
|
break;
|
|
end
|
|
end
|
|
end
|
|
|
|
-- If additional questIDs were manually included, let's do some extra work.
|
|
if extraQuestIDs and #extraQuestIDs > 0 then
|
|
for i,questID in ipairs(extraQuestIDs) do
|
|
local results = SearchForField("questID", questID);
|
|
if #results > 0 then
|
|
local quest = results[1];
|
|
if quest.key == "questID" and not quest.repeatable then
|
|
count = count + 1;
|
|
quests[count] = quest;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if count > 0 then
|
|
return quests;
|
|
end
|
|
end
|
|
end;
|
|
local LOREMASTER_CreateQuestsAndStructures = function(t, mapID, extraQuestIDs)
|
|
-- Grab all of the quests on the continent.
|
|
local response;
|
|
local results = SearchForField("mapID", mapID);
|
|
local count = #results;
|
|
if count < 1 then
|
|
return;
|
|
elseif count > 1 then
|
|
-- Uh wasn't expecting this.
|
|
local bestResult;
|
|
for i=1,#results,1 do
|
|
local g = results[i].g;
|
|
if g and not bestResult or #g > #bestResult.g then
|
|
bestResult = results[i];
|
|
end
|
|
end
|
|
response = app:BuildSearchResponseForField(bestResult.g, "questID");
|
|
else
|
|
response = app:BuildSearchResponseForField(results[1].g, "questID");
|
|
end
|
|
|
|
|
|
local quests, structures = {}, {};
|
|
if response then
|
|
-- Get the quests list from the zone itself.
|
|
local zones_header = {text=BUG_CATEGORY2,icon = app.asset("Category_Zones"),description = "These are outdoor quests that involve the associated maps for the continent.",g = response};
|
|
app:BuildFlatSearchResponseForField(response, "questID", quests);
|
|
if #zones_header.g > 0 then
|
|
tinsert(structures, zones_header);
|
|
end
|
|
|
|
-- Get a list of all of the mapIDs in this structure.
|
|
response = {};
|
|
app:BuildFlatSearchResponseForField(zones_header.g, "mapID", response);
|
|
if #response > 0 then
|
|
local mapIDs = {};
|
|
for i,o in ipairs(response) do
|
|
if o.mapID and not mapIDs[o.mapID] then
|
|
mapIDs[o.mapID] = true;
|
|
end
|
|
if o.maps then
|
|
for j,id in ipairs(o.maps) do
|
|
if not mapIDs[id] then
|
|
mapIDs[id] = true;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
response = app:BuildSearchFilteredResponse(app.Categories.Instances, function(group)
|
|
if group.questID and not group.repeatable then
|
|
if group.coords then
|
|
for i,coord in ipairs(group.coords) do
|
|
if coord[3] and mapIDs[coord[3]] then
|
|
return true;
|
|
end
|
|
end
|
|
end
|
|
if group.maps then
|
|
for i,id in ipairs(group.maps) do
|
|
if mapIDs[id] then
|
|
return true;
|
|
end
|
|
end
|
|
end
|
|
return false;
|
|
end
|
|
end);
|
|
if response then
|
|
local dungeon_header = {text=GROUP_FINDER,icon = app.asset("Category_D&R"),description = "These are dungeon quests that involve the associated maps for the continent. They may or may not count towards the loremaster achievement. Just get it done and don't be lazy or complain to me.\n\n - Crieve",g = response};
|
|
app:BuildFlatSearchResponseForField(response, "questID", quests);
|
|
if #dungeon_header.g > 0 then
|
|
tinsert(structures, 1, dungeon_header);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- If additional questIDs were manually included, let's do some extra work.
|
|
if extraQuestIDs and #extraQuestIDs > 0 then
|
|
for i,questID in ipairs(extraQuestIDs) do
|
|
local results = SearchForField("questID", questID);
|
|
if #results > 0 then
|
|
tinsert(quests, 1, results[1]);
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Return the Outdoor Zones and Dungeon structures.
|
|
return quests, structures;
|
|
end
|
|
local commonAchievementHandlers = {
|
|
["COMPANIONS_OnClick"] = function(row, button)
|
|
if button == "RightButton" then
|
|
local t = row.ref;
|
|
local template = {};
|
|
for i,o in pairs(SearchForFieldContainer("speciesID")) do
|
|
tinsert(template, o[1]);
|
|
end
|
|
|
|
local clone = app:CreateMiniListForGroup(app.CreateAchievement(t[t.key], template)).data;
|
|
clone.OnTooltip = t.OnTooltip;
|
|
clone.OnUpdate = t.OnUpdate;
|
|
clone.rank = t.rank;
|
|
return true;
|
|
end
|
|
end,
|
|
["DEDICATED_10M_OnUpdate"] = function(t)
|
|
rawset(t, "collectible", nil);
|
|
if app.Settings:Get("DebugMode") or app.Settings:Get("AccountMode") then
|
|
return false;
|
|
elseif IsInGroup() and GetNumGroupMembers() >= 9 then
|
|
rawset(t, "collectible", false);
|
|
return true;
|
|
end
|
|
end,
|
|
["DEDICATED_25M_OnUpdate"] = function(t)
|
|
rawset(t, "collectible", nil);
|
|
if app.Settings:Get("DebugMode") or app.Settings:Get("AccountMode") then
|
|
return false;
|
|
elseif IsInGroup() and GetNumGroupMembers() >= 21 then
|
|
rawset(t, "collectible", false);
|
|
return true;
|
|
end
|
|
end,
|
|
["EXALTED_REP_OnInit"] = function(t, factionID)
|
|
t.BuildReputation = function()
|
|
local r = t.rep;
|
|
if not r then
|
|
local f = SearchForField("factionID", factionID);
|
|
if #f > 0 then
|
|
r = f[1];
|
|
for i,o in ipairs(f) do
|
|
if o.key == "factionID" then
|
|
if o.maxReputation then
|
|
r = CloneData(o);
|
|
r.maxReputation = nil;
|
|
else
|
|
r = o;
|
|
end
|
|
break;
|
|
end
|
|
end
|
|
t.rep = r;
|
|
end
|
|
end
|
|
return r;
|
|
end
|
|
return t;
|
|
end,
|
|
["EXALTED_REP_OnClick"] = function(row, button)
|
|
if button == "RightButton" then
|
|
local t = row.ref;
|
|
local r = t.rep or (t.BuildReputation and t:BuildReputation());
|
|
local clone = app:CreateMiniListForGroup(app.CreateAchievement(t[t.key], { r })).data;
|
|
clone.description = t.description;
|
|
return true;
|
|
end
|
|
end,
|
|
["EXALTED_REP_OnUpdate"] = function(t)
|
|
if t.collectible then
|
|
local r = t.rep or (t.BuildReputation and t:BuildReputation());
|
|
if not r then return true; end
|
|
t:SetAchievementCollected(t.achievementID, r.standing == 8);
|
|
end
|
|
end,
|
|
["EXALTED_REP_OnTooltip"] = function(t)
|
|
if t.collectible then
|
|
local r = t.rep or (t.BuildReputation and t:BuildReputation());
|
|
if r then
|
|
GameTooltip:AddLine(" ");
|
|
GameTooltip:AddDoubleLine(" |T" .. r.icon .. ":0|t " .. r.text, app.L[r.standing == 8 and "COLLECTED_ICON" or "NOT_COLLECTED_ICON"], 1, 1, 1);
|
|
end
|
|
end
|
|
end,
|
|
["EXALTED_REPS_OnInit"] = function(t, ...)
|
|
local factionIDs = { ... };
|
|
t.BuildReputations = function()
|
|
local reps = t.reps;
|
|
if not reps then
|
|
reps = {};
|
|
for i,factionID in ipairs(factionIDs) do
|
|
local f = SearchForField("factionID", factionID);
|
|
if #f > 0 then
|
|
local best = f[1];
|
|
for _,o in ipairs(f) do
|
|
if o.key == "factionID" then
|
|
best = o;
|
|
break;
|
|
end
|
|
end
|
|
if best.maxReputation then
|
|
best = CloneData(best);
|
|
best.maxReputation = nil;
|
|
end
|
|
tinsert(reps, best);
|
|
end
|
|
end
|
|
if #reps > 0 then
|
|
t.reps = reps;
|
|
else
|
|
reps = nil;
|
|
end
|
|
end
|
|
return reps;
|
|
end
|
|
return t;
|
|
end,
|
|
["EXALTED_REPS_OnClick"] = function(row, button)
|
|
if button == "RightButton" then
|
|
local t = row.ref;
|
|
local reps = t.reps or (t.BuildReputations and t:BuildReputations());
|
|
local clone = app:CreateMiniListForGroup(app.CreateAchievement(t[t.key], reps)).data;
|
|
clone.description = t.description;
|
|
return true;
|
|
end
|
|
end,
|
|
["EXALTED_REPS_OnTooltip"] = function(t)
|
|
if t.collectible then
|
|
local reps = t.reps or (t.BuildReputations and t:BuildReputations());
|
|
if reps then
|
|
GameTooltip:AddLine(" ");
|
|
for i,faction in ipairs(reps) do
|
|
GameTooltip:AddDoubleLine(" |T" .. faction.icon .. ":0|t " .. faction.text, app.L[faction.standing == 8 and "COLLECTED_ICON" or "NOT_COLLECTED_ICON"], 1, 1, 1);
|
|
end
|
|
end
|
|
end
|
|
end,
|
|
["LOREMASTER_OnClick"] = function(row, button)
|
|
if button == "RightButton" then
|
|
local t = row.ref;
|
|
local data = t.quests or (t.BuildQuests and t:BuildQuests());
|
|
local clone = app:CreateMiniListForGroup(app.CreateAchievement(t[t.key], data)).data;
|
|
clone.description = t.description;
|
|
return true;
|
|
end
|
|
end,
|
|
["LOREMASTER_CONTINENT_OnClick"] = function(row, button)
|
|
if button == "RightButton" then
|
|
local t = row.ref;
|
|
local data = t.structures or (t.BuildStructures and t:BuildStructures()) or t.quests;
|
|
local clone = app:CreateMiniListForGroup(app.CreateAchievement(t[t.key], data)).data;
|
|
clone.description = t.description;
|
|
return true;
|
|
end
|
|
end,
|
|
["MOUNTS_OnClick"] = function(row, button)
|
|
if button == "RightButton" then
|
|
local t = row.ref;
|
|
local template,r = {},{};
|
|
for i,o in pairs(SearchForFieldContainer("spellID")) do
|
|
if #o > 0 then
|
|
if ((o[1].f and o[1].f == app.FilterConstants.MOUNTS)
|
|
or (o[1].filterID and o[1].filterID == app.FilterConstants.MOUNTS)) and not r[i] then
|
|
tinsert(template, o[1]);
|
|
r[i] = 1;
|
|
end
|
|
end
|
|
end
|
|
|
|
local clone = app:CreateMiniListForGroup(app.CreateAchievement(t[t.key], template)).data;
|
|
clone.OnTooltip = t.OnTooltip;
|
|
clone.OnUpdate = t.OnUpdate;
|
|
clone.rank = t.rank;
|
|
return true;
|
|
end
|
|
end,
|
|
["REPUTATIONS_OnClick"] = function(row, button)
|
|
if button == "RightButton" then
|
|
local t = row.ref;
|
|
local template = {};
|
|
for i,o in ipairs(app:GetDataCache().g) do
|
|
if o.headerID == app.HeaderConstants.FACTIONS then
|
|
for j,p in ipairs(o.g) do
|
|
if (not p.minReputation or (p.minReputation[1] == p.factionID and p.minReputation[2] >= 41999)) and (not p.maxReputation or (p.maxReputation[1] ~= p.factionID and p.reputation >= 0)) then
|
|
tinsert(template, p);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local clone = app:CreateMiniListForGroup(app.CreateAchievement(t[t.key], template)).data;
|
|
clone.OnTooltip = t.OnTooltip;
|
|
clone.OnUpdate = t.OnUpdate;
|
|
clone.rank = t.rank;
|
|
return true;
|
|
end
|
|
end,
|
|
};
|
|
app.CommonAchievementHandlers = commonAchievementHandlers;
|
|
|
|
local fields = {
|
|
["collectible"] = function(t)
|
|
return app.Settings.Collectibles.Achievements;
|
|
end,
|
|
["collected"] = function(t)
|
|
if app.CurrentCharacter.Achievements[t.achievementID] then return 1; end
|
|
if app.Settings.AccountWide.Achievements and ATTAccountWideData.Achievements[t.achievementID] then return 2; end
|
|
end,
|
|
["OnUpdate"] = function(t) ResolveSymbolicLink(t); end,
|
|
};
|
|
local categoryFields = {
|
|
["icon"] = function(t)
|
|
return app.asset("Category_Achievements");
|
|
end,
|
|
};
|
|
if GetCategoryInfo and (GetCategoryInfo(92) ~= "" and GetCategoryInfo(92) ~= nil) then
|
|
-- Achievements are in. We can use the API.
|
|
local GetAchievementCategory = _G["GetAchievementCategory"];
|
|
fields.text = function(t)
|
|
return t.link or "|cffffff00[" .. (t.name or ("@CRIEVE: INVALID ACHIEVEMENT " .. t.achievementID)) .. "]|r";
|
|
end
|
|
fields.name = function(t)
|
|
local name = select(2, GetAchievementInfo(t.achievementID));
|
|
if name then return name; end
|
|
local data = L.ACHIEVEMENT_DATA[t.achievementID];
|
|
if data and data[2] then return data[2]; end
|
|
if t.providers then
|
|
for k,v in ipairs(t.providers) do
|
|
if v[2] > 0 then
|
|
if v[1] == "o" then
|
|
return app.ObjectNames[v[2]];
|
|
elseif v[1] == "i" then
|
|
return GetItemInfo(v[2]);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if t.spellID then return GetSpellInfo(t.spellID); end
|
|
end
|
|
fields.link = function(t)
|
|
return GetAchievementLink(t.achievementID);
|
|
end
|
|
fields.icon = function(t)
|
|
local name = select(10, GetAchievementInfo(t.achievementID));
|
|
if name then return name; end
|
|
local data = L.ACHIEVEMENT_DATA[t.achievementID];
|
|
if data and data[3] then return data[3]; end
|
|
if t.providers then
|
|
for k,v in ipairs(t.providers) do
|
|
if v[2] > 0 then
|
|
if v[1] == "o" then
|
|
local icon = app.ObjectIcons[v[2]];
|
|
if icon then return icon; end
|
|
elseif v[1] == "i" then
|
|
return select(5, GetItemInfoInstant(v[2])) or "Interface\\Worldmap\\Gear_64Grey";
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if t.spellID then return select(3, GetSpellInfo(t.spellID)); end
|
|
return t.parent.icon or "Interface\\Worldmap\\Gear_64Grey";
|
|
end
|
|
fields.parentCategoryID = function(t)
|
|
local data = GetAchievementCategory(t.achievementID);
|
|
if data then return data; end
|
|
data = L.ACHIEVEMENT_DATA[t.achievementID];
|
|
if data then return data[1]; end
|
|
return -1;
|
|
end
|
|
fields.SetAchievementCollected = function(t)
|
|
if t.achievementID == 5788 or t.achievementID == 6059 then
|
|
return SetAchievementCollected;
|
|
else
|
|
print("Attempted to retrieve the function SetAchievementCollected from the Achievement object. (no longer available)");
|
|
return function(t, achievementID, collected)
|
|
print("Attempted to set achievement " .. achievementID .. " as collected: " .. (collected and 1 or 0));
|
|
end
|
|
end
|
|
end
|
|
fields.isStatistic = function(t)
|
|
return select(15, GetAchievementInfo(t.achievementID));
|
|
end
|
|
local onTooltipForAchievement = function(t)
|
|
local achievementID = t.achievementID;
|
|
if achievementID and IsShiftKeyDown() then
|
|
local criteriaDatas,criteriaDatasByUID = {}, {};
|
|
for criteriaID=1,99999,1 do
|
|
local criteriaString, criteriaType, completed, quantity, reqQuantity, charName, flags, assetID, quantityString, criteriaUID = GetAchievementCriteriaInfoByID(achievementID, criteriaID);
|
|
if criteriaString then
|
|
criteriaDatasByUID[criteriaUID] = true;
|
|
tinsert(criteriaDatas, {
|
|
" [" .. criteriaUID .. "]: " .. tostring(criteriaString),
|
|
"(" .. tostring(assetID) .. " @ " .. tostring(criteriaType) .. ") " .. tostring(quantityString) .. " " .. GetCompletionIcon(completed)
|
|
});
|
|
end
|
|
end
|
|
local totalCriteria = GetAchievementNumCriteria(achievementID) or 0;
|
|
if totalCriteria > 0 then
|
|
for criteriaIndex=1,totalCriteria,1 do
|
|
local criteriaString, criteriaType, completed, quantity, reqQuantity, charName, flags, assetID, quantityString, criteriaUID = GetAchievementCriteriaInfo(achievementID, criteriaIndex, true);
|
|
if criteriaString and (not criteriaDatasByUID[criteriaUID] or criteriaUID == 0) then
|
|
tinsert(criteriaDatas, {
|
|
" [" .. criteriaUID .. " @ Index: " .. criteriaIndex .. "]: " .. tostring(criteriaString),
|
|
"(" .. tostring(assetID) .. " @ " .. tostring(criteriaType) .. ") " .. tostring(quantityString) .. " " .. GetCompletionIcon(completed)
|
|
});
|
|
end
|
|
end
|
|
end
|
|
if #criteriaDatas > 0 then
|
|
GameTooltip:AddLine(" ", 1, 1, 1);
|
|
GameTooltip:AddDoubleLine("Total Criteria", tostring(#criteriaDatas), 0.8, 0.8, 1);
|
|
for i,criteriaData in ipairs(criteriaDatas) do
|
|
GameTooltip:AddDoubleLine(criteriaData[1], criteriaData[2], 1, 1, 1, 1, 1, 1);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
local onTooltipForAchievementCriteria = function(t)
|
|
local achievementID = t.achievementID;
|
|
if achievementID then
|
|
GameTooltip:AddDoubleLine(L.CRITERIA_FOR, "|cffffff00[" .. (select(2, GetAchievementInfo(achievementID)) or RETRIEVING_DATA) .. "]|r");
|
|
if IsShiftKeyDown() then
|
|
local criteriaID = t.criteriaID;
|
|
if criteriaID then
|
|
GameTooltip:AddLine(" ", 1, 1, 1);
|
|
local criteriaString, criteriaType, completed, quantity, reqQuantity, charName, flags, assetID, quantityString, criteriaUID = t.GetInfo(achievementID, criteriaID, true)
|
|
if criteriaString then
|
|
GameTooltip:AddDoubleLine(" [" .. criteriaUID .. "]: " .. tostring(criteriaString),
|
|
"(" .. tostring(assetID) .. " @ " .. tostring(criteriaType) .. ") " .. tostring(quantityString) .. " " .. GetCompletionIcon(completed), 1, 1, 1, 1, 1, 1);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
fields.OnTooltip = function(t)
|
|
return onTooltipForAchievement;
|
|
end
|
|
categoryFields.text = function(t)
|
|
local data = GetCategoryInfo(t.achievementCategoryID);
|
|
if data then return data; end
|
|
data = L.ACHIEVEMENT_CRITERIA_DATA[t.achievementCategoryID];
|
|
if data then return data[2]; end
|
|
return RETRIEVING_DATA .. " achcat:" .. t.achievementCategoryID;
|
|
end
|
|
categoryFields.parentCategoryID = function(t)
|
|
local data = select(2, GetCategoryInfo(t.achievementCategoryID));
|
|
if data then return data; end
|
|
data = L.ACHIEVEMENT_CRITERIA_DATA[t.achievementCategoryID];
|
|
if data then return data[1]; end
|
|
return -1;
|
|
end
|
|
app.CreateAchievement = app.CreateClass("Achievement", "achievementID", fields);
|
|
app.CreateAchievementCriteria = app.CreateClass("AchievementCriteria", "criteriaID", {
|
|
["achievementID"] = function(t)
|
|
return t.achID or t.criteriaParent.achievementID;
|
|
end,
|
|
["criteriaParent"] = function(t)
|
|
return t.sourceParent or t.parent or app.EmptyTable;
|
|
end,
|
|
["index"] = function(t)
|
|
return 1;
|
|
end,
|
|
["collectible"] = function(t)
|
|
return app.Settings.Collectibles.Achievements;
|
|
end,
|
|
["trackable"] = app.ReturnTrue,
|
|
["text"] = function(t)
|
|
return "|cffffff00[Criteria: " .. (t.name or RETRIEVING_DATA) .. "]|r";
|
|
end,
|
|
["name"] = function(t)
|
|
local achievementID = t.achievementID;
|
|
if achievementID then
|
|
local criteriaID = t.criteriaID;
|
|
if criteriaID then
|
|
local name = t.GetInfo(achievementID, criteriaID, true);
|
|
if not IsRetrieving(name) then return name; end
|
|
|
|
local providers = t.providers;
|
|
if providers then
|
|
for k,v in ipairs(providers) do
|
|
if v[2] > 0 then
|
|
if v[1] == "o" then
|
|
return app.ObjectNames[v[2]];
|
|
elseif v[1] == "i" then
|
|
return GetItemInfo(v[2]);
|
|
elseif v[1] == "n" then
|
|
return app.NPCNameFromID[v[2]];
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local sourceQuests = t.sourceQuests;
|
|
if sourceQuests then
|
|
for k,id in ipairs(sourceQuests) do
|
|
return app.GetQuestName(id);
|
|
end
|
|
end
|
|
return "achievementID:" .. achievementID .. ":" .. criteriaID;
|
|
end
|
|
end
|
|
end,
|
|
["icon"] = function(t)
|
|
if t.providers then
|
|
for k,v in ipairs(t.providers) do
|
|
if v[2] > 0 then
|
|
if v[1] == "o" then
|
|
local icon = app.ObjectIcons[v[2]];
|
|
if icon then return icon; end
|
|
elseif v[1] == "i" then
|
|
return select(5, GetItemInfoInstant(v[2])) or "Interface\\Icons\\INV_Misc_Bag_10";
|
|
end
|
|
end
|
|
end
|
|
end
|
|
local achievementID = t.achievementID;
|
|
if achievementID then
|
|
return select(10, GetAchievementInfo(achievementID));
|
|
end
|
|
end,
|
|
["model"] = function(t)
|
|
if t.providers then
|
|
for k,v in ipairs(t.providers) do
|
|
if v[2] > 0 then
|
|
if v[1] == "o" then
|
|
local model = app.ObjectModels[v[2]];
|
|
if model then return model; end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end,
|
|
["collected"] = function(t)
|
|
-- Check to see if the criteria was completed.
|
|
local achievementID = t.achievementID;
|
|
if achievementID then
|
|
if app.CurrentCharacter.Achievements[achievementID] then return 1; end
|
|
if app.Settings.AccountWide.Achievements and ATTAccountWideData.Achievements[achievementID] then return 2; end
|
|
|
|
local criteriaID = t.criteriaID;
|
|
if criteriaID then
|
|
local collected = false;
|
|
local status, err = pcall(function()
|
|
collected = select(3, t.GetInfo(achievementID, criteriaID, true));
|
|
end);
|
|
if not status then
|
|
print("ERROR", achievementID, criteriaID, err);
|
|
end
|
|
return collected;
|
|
end
|
|
end
|
|
end,
|
|
["saved"] = function(t)
|
|
-- Check to see if the criteria was completed.
|
|
local achievementID = t.achievementID;
|
|
if achievementID then
|
|
if app.CurrentCharacter.Achievements[achievementID] then return true; end
|
|
local criteriaID = t.criteriaID;
|
|
if criteriaID then
|
|
return select(3, t.GetInfo(achievementID, criteriaID, true));
|
|
end
|
|
end
|
|
end,
|
|
["OnTooltip"] = function()
|
|
return onTooltipForAchievementCriteria;
|
|
end,
|
|
GetInfo = function()
|
|
return GetAchievementCriteriaInfoByID;
|
|
end,
|
|
},
|
|
"WithIndex", {
|
|
GetInfo = function()
|
|
return GetAchievementCriteriaInfo;
|
|
end;
|
|
}, (function(t) return t.criteriaID < 100; end));
|
|
|
|
local function CheckAchievementCollectionStatus(achievementID)
|
|
achievementID = tonumber(achievementID);
|
|
SetAchievementCollected(SearchForField("achievementID", achievementID)[1], achievementID, select(13, GetAchievementInfo(achievementID)));
|
|
end
|
|
local function refreshAchievementCollection()
|
|
if ATTAccountWideData then
|
|
for achievementID,container in pairs(SearchForFieldContainer("achievementID")) do
|
|
local id = tonumber(achievementID);
|
|
if achievementID ~= 5788 then
|
|
SetAchievementCollected(container[1], id, select(13, GetAchievementInfo(id)));
|
|
end
|
|
end
|
|
end
|
|
end
|
|
tinsert(app.EventHandlers.OnRecalculate, refreshAchievementCollection);
|
|
app:RegisterEvent("ADDON_LOADED");
|
|
app:RegisterEvent("ACHIEVEMENT_EARNED");
|
|
app.events.ACHIEVEMENT_EARNED = CheckAchievementCollectionStatus;
|
|
app.events.ADDON_LOADED = function(addonName)
|
|
if addonName == "Blizzard_AchievementUI" then
|
|
refreshAchievementCollection();
|
|
app:UnregisterEvent("ADDON_LOADED");
|
|
end
|
|
end
|
|
|
|
-- Achievements are supported in this version, so we don't need to manually check anything!
|
|
-- Most calculations that were previously in the OnUpdate can now exist in a build script instead.
|
|
commonAchievementHandlers.EXPLORATION_OnClick = function(row, button)
|
|
if button == "RightButton" then
|
|
local t = row.ref;
|
|
local areas = t.areas;
|
|
if not areas then
|
|
local g = (t.sourceParent or t.parent).parent.g;
|
|
if g and #g > 0 then
|
|
for i,o in ipairs(g) do
|
|
if o.headerID == app.HeaderConstants.EXPLORATION then
|
|
areas = o.g;
|
|
break;
|
|
end
|
|
end
|
|
if not areas then return; end
|
|
else
|
|
return;
|
|
end
|
|
t.areas = areas;
|
|
end
|
|
local clone = app:CreateMiniListForGroup(app.CreateAchievement(t[t.key], areas)).data;
|
|
clone.description = t.description;
|
|
return true;
|
|
end
|
|
end
|
|
commonAchievementHandlers.LOREMASTER_CONTINENT_OnUpdate = function(t, mapID, ...)
|
|
t.OnUpdate = nil;
|
|
local extraQuestIDs = { ... };
|
|
t.BuildStructures = function()
|
|
if not t.structures then
|
|
local quests, structures = LOREMASTER_CreateQuestsAndStructures(t, mapID, extraQuestIDs);
|
|
if quests then
|
|
t.quests = quests;
|
|
t.structures = structures;
|
|
return structures;
|
|
end
|
|
else
|
|
return t.structures;
|
|
end
|
|
end
|
|
end
|
|
commonAchievementHandlers.LOREMASTER_OnUpdate = function(t, ...)
|
|
t.OnUpdate = nil;
|
|
local extraQuestIDs = { ... };
|
|
t.BuildQuests = function()
|
|
local quests = t.quests;
|
|
if not quests then
|
|
quests = LOREMASTER_CreateQuests(t, extraQuestIDs);
|
|
if quests then
|
|
t.quests = quests;
|
|
end
|
|
end
|
|
return quests;
|
|
end
|
|
end
|
|
else
|
|
-- Achievements are NOT in. We can't use the API.
|
|
fields.text = function(t)
|
|
return "|cffffff00[" .. (t.name or RETRIEVING_DATA) .. "]|r";
|
|
end
|
|
fields.name = function(t)
|
|
local data = L.ACHIEVEMENT_DATA[t.achievementID];
|
|
if data and data[2] then return data[2]; end
|
|
if t.providers then
|
|
for k,v in ipairs(t.providers) do
|
|
if v[2] > 0 then
|
|
if v[1] == "o" then
|
|
return app.ObjectNames[v[2]];
|
|
elseif v[1] == "i" then
|
|
return GetItemInfo(v[2]);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if t.spellID then return GetSpellInfo(t.spellID); end
|
|
end
|
|
fields.icon = function(t)
|
|
local data = L.ACHIEVEMENT_DATA[t.achievementID];
|
|
if data and data[3] then return data[3]; end
|
|
if t.providers then
|
|
for k,v in ipairs(t.providers) do
|
|
if v[2] > 0 then
|
|
if v[1] == "o" then
|
|
local icon = app.ObjectIcons[v[2]];
|
|
if icon then return icon; end
|
|
elseif v[1] == "i" then
|
|
return select(5, GetItemInfoInstant(v[2])) or "Interface\\Worldmap\\Gear_64Grey";
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if t.spellID then return select(3, GetSpellInfo(t.spellID)); end
|
|
return t.parent.icon or "Interface\\Worldmap\\Gear_64Grey";
|
|
end
|
|
fields.parentCategoryID = function(t)
|
|
local data = L.ACHIEVEMENT_DATA[t.achievementID];
|
|
if data then return data[1]; end
|
|
return -1;
|
|
end
|
|
fields.SetAchievementCollected = function(t)
|
|
return SetAchievementCollected;
|
|
end
|
|
categoryFields.text = function(t)
|
|
local data = L.ACHIEVEMENT_CRITERIA_DATA[t.achievementCategoryID];
|
|
if data then return data[2]; end
|
|
return RETRIEVING_DATA .. " achcat:" .. t.achievementCategoryID;
|
|
end
|
|
categoryFields.parentCategoryID = function(t)
|
|
local data = L.ACHIEVEMENT_CRITERIA_DATA[t.achievementCategoryID];
|
|
if data then return data[1]; end
|
|
return -1;
|
|
end
|
|
|
|
local fieldsWithSpellID = {
|
|
OnUpdate = function(t)
|
|
if t.collectible then
|
|
t:SetAchievementCollected(t.achievementID, app.IsSpellKnown(t.spellID, t.rank));
|
|
end
|
|
end
|
|
};
|
|
app.CreateAchievement = app.CreateClass("Achievement", "achievementID", fields,
|
|
"WithSpell", fieldsWithSpellID, function(t) return t.spellID; end); -- This is a conditional contructor.
|
|
app.CreateAchievementCriteria = function(id, t) return nil; end -- We don't support showing criteria before Wrath
|
|
|
|
-- Add in manual achievement handlers
|
|
-- These are required to manually detect if an "achievement" is collected or not.
|
|
-- They do not use the WOW Achievement API since it doesn't exist in this version.
|
|
-- A lot of times these require additional calculations in the OnUpdate, so do not use these outside of the required environment!
|
|
local LOREMASTER_EXPLICIT_OnUpdate = function(t)
|
|
-- This is detached because we don't want contribs calling to this directly, it's a helper function.
|
|
local quests = t.quests;
|
|
if quests and #quests > 0 then
|
|
local p = 0;
|
|
if app.FilterItemClass_RequireRaces(t) then
|
|
for i,o in ipairs(quests) do
|
|
if app.FilterItemClass(o) then
|
|
if o.collected == 1 then
|
|
p = p + 1;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
t.p = p;
|
|
t:SetAchievementCollected(t.achievementID, p >= t.rank);
|
|
else
|
|
return true;
|
|
end
|
|
end
|
|
commonAchievementHandlers.ALL_ITEM_PROVIDERS = function(t)
|
|
local collected = true;
|
|
for i,provider in ipairs(t.providers) do
|
|
if provider[1] == "i" and GetItemCount(provider[2], true) == 0 then
|
|
collected = false;
|
|
break;
|
|
end
|
|
end
|
|
t:SetAchievementCollected(t.achievementID, collected);
|
|
end
|
|
commonAchievementHandlers.ANY_ITEM_PROVIDER = function(t)
|
|
local collected = false;
|
|
for i,provider in ipairs(t.providers) do
|
|
if provider[1] == "i" and GetItemCount(provider[2], true) > 0 then
|
|
collected = true;
|
|
break;
|
|
end
|
|
end
|
|
t:SetAchievementCollected(t.achievementID, collected);
|
|
end
|
|
commonAchievementHandlers.ALL_SOURCE_QUESTS = function(t)
|
|
local collected = true;
|
|
for i,questID in ipairs(t.sourceQuests) do
|
|
if not C_QuestLog.IsQuestFlaggedCompleted(questID) then
|
|
collected = false;
|
|
break;
|
|
end
|
|
end
|
|
t:SetAchievementCollected(t.achievementID, collected);
|
|
end
|
|
commonAchievementHandlers.ANY_SOURCE_QUEST = function(t)
|
|
local collected = false;
|
|
for i,questID in ipairs(t.sourceQuests) do
|
|
if C_QuestLog.IsQuestFlaggedCompleted(questID) then
|
|
collected = true;
|
|
break;
|
|
end
|
|
end
|
|
t:SetAchievementCollected(t.achievementID, collected);
|
|
end
|
|
commonAchievementHandlers.COMPANIONS_OnUpdate = function(t)
|
|
if app.Settings.Collectibles.BattlePets then
|
|
local count = 0;
|
|
for i,g in pairs(SearchForFieldContainer("speciesID")) do
|
|
if g[1].collected then
|
|
count = count + 1;
|
|
end
|
|
end
|
|
if t.rank > 1 then
|
|
t.progress = math.min(count, t.rank);
|
|
t.total = t.rank;
|
|
t.collectible = false;
|
|
|
|
if app.GroupFilter(t) then
|
|
local parent = t.parent;
|
|
parent.total = (parent.total or 0) + t.total;
|
|
parent.progress = (parent.progress or 0) + t.progress;
|
|
t.visible = (t.progress < t.total or app.CollectedItemVisibilityFilter(t));
|
|
else
|
|
t.visible = false;
|
|
end
|
|
else
|
|
t.collected = count >= 1;
|
|
t.collectible = collectible;
|
|
|
|
if app.GroupFilter(t) then
|
|
local parent = t.parent;
|
|
parent.total = (parent.total or 0) + 1;
|
|
if t.collected then parent.progress = (parent.progress or 0) + 1; end
|
|
t.visible = (not t.collected or app.CollectedItemVisibilityFilter(t));
|
|
else
|
|
t.visible = false;
|
|
end
|
|
end
|
|
else
|
|
t.collected = nil;
|
|
t.collectible = false;
|
|
t.progress = nil;
|
|
t.total = nil;
|
|
t.visible = false;
|
|
end
|
|
return true;
|
|
end
|
|
commonAchievementHandlers.COMPANIONS_OnTooltip = function(t)
|
|
GameTooltip:AddLine("Collect " .. t.rank .. " companion pets.");
|
|
if t.total and t.progress < t.total and t.rank >= 25 then
|
|
GameTooltip:AddLine(" ");
|
|
local c = 0;
|
|
for i,g in pairs(SearchForFieldContainer("speciesID")) do
|
|
local p = g[1];
|
|
if p.visible then
|
|
c = c + 1;
|
|
if c < 16 then
|
|
GameTooltip:AddDoubleLine(" |T" .. p.icon .. ":0|t " .. p.text, app.L[p.collected and "COLLECTED_ICON" or "NOT_COLLECTED_ICON"], 1, 1, 1);
|
|
end
|
|
end
|
|
end
|
|
if c > 15 then GameTooltip:AddLine(" And " .. (c - 15) .. " more!"); end
|
|
end
|
|
end
|
|
commonAchievementHandlers.EXALTED_REPS_OnUpdate = function(t)
|
|
if t.collectible then
|
|
local reps = t.reps or (t.BuildReputations and t:BuildReputations());
|
|
if not reps then return true; end
|
|
local collected = true;
|
|
for i,r in ipairs(reps) do
|
|
if r.standing < 8 then
|
|
collected = false;
|
|
break;
|
|
end
|
|
end
|
|
t:SetAchievementCollected(t.achievementID, collected);
|
|
end
|
|
end
|
|
commonAchievementHandlers.EXALTED_REPS_ANY_OnUpdate = function(t)
|
|
if t.collectible then
|
|
local reps = t.reps or (t.BuildReputations and t:BuildReputations());
|
|
if not reps then return true; end
|
|
local collected = false;
|
|
for i,r in ipairs(reps) do
|
|
if r.standing == 8 then
|
|
collected = true;
|
|
break;
|
|
end
|
|
end
|
|
t:SetAchievementCollected(t.achievementID, collected);
|
|
end
|
|
end
|
|
commonAchievementHandlers.EXPLORATION_OnUpdate = function(t)
|
|
if t.collectible and t.parent then
|
|
if not t.areas then
|
|
local g = (t.sourceParent or t.parent).parent.g;
|
|
if g and #g > 0 then
|
|
for i,o in ipairs(g) do
|
|
if o.headerID == app.HeaderConstants.EXPLORATION then
|
|
t.areas = o.g;
|
|
break;
|
|
end
|
|
end
|
|
if not t.areas then return; end
|
|
else
|
|
return;
|
|
end
|
|
end
|
|
local collected = true;
|
|
for i,o in ipairs(t.areas) do
|
|
if o.collected ~= 1 and app.FilterItemClass_UnobtainableItem(o) then
|
|
collected = false;
|
|
break;
|
|
end
|
|
end
|
|
t:SetAchievementCollected(t.achievementID, collected);
|
|
end
|
|
end
|
|
commonAchievementHandlers.EXPLORATION_OnClick = function(row, button)
|
|
if button == "RightButton" then
|
|
local t = row.ref;
|
|
local clone = app:CreateMiniListForGroup(app.CreateAchievement(t[t.key], t.areas)).data;
|
|
clone.description = t.description;
|
|
return true;
|
|
end
|
|
end
|
|
commonAchievementHandlers.KNOW_SPELLS_OnUpdate = function(t, ...)
|
|
if t.collectible then
|
|
if not t.spells then
|
|
local spells = {};
|
|
for i,spellID in ipairs({ ... }) do
|
|
local f = SearchForField("spellID", spellID);
|
|
if #f > 0 then
|
|
tinsert(spells, f[1]);
|
|
else
|
|
return true;
|
|
end
|
|
end
|
|
if #spells < 1 then return true; end
|
|
t.spells = spells;
|
|
end
|
|
local collected = true;
|
|
for i,spell in ipairs(t.spells) do
|
|
if spell.collected then
|
|
collected = false;
|
|
break;
|
|
end
|
|
end
|
|
t:SetAchievementCollected(t.achievementID, collected);
|
|
end
|
|
end
|
|
commonAchievementHandlers.KNOW_SPELLS_ANY_OnUpdate = function(t, ...)
|
|
if t.collectible then
|
|
if not t.spells then
|
|
local spells = {};
|
|
for i,spellID in ipairs({ ... }) do
|
|
local f = SearchForField("spellID", spellID);
|
|
if #f > 0 then
|
|
tinsert(spells, f[1]);
|
|
else
|
|
tinsert(spells, app.CreateSpell(spellID));
|
|
end
|
|
end
|
|
if #spells < 1 then return true; end
|
|
t.spells = spells;
|
|
end
|
|
local collected = false;
|
|
for i,spell in ipairs(t.spells) do
|
|
if spell.collected then
|
|
collected = true;
|
|
break;
|
|
end
|
|
end
|
|
t:SetAchievementCollected(t.achievementID, collected);
|
|
end
|
|
end
|
|
commonAchievementHandlers.KNOW_SPELLS_OnClick = function(row, button)
|
|
if button == "RightButton" then
|
|
local t = row.ref;
|
|
local clone = app:CreateMiniListForGroup(app.CreateAchievement(t[t.key], t.spells)).data;
|
|
clone.description = t.description;
|
|
return true;
|
|
end
|
|
end
|
|
commonAchievementHandlers.KNOW_SPELLS_OnTooltip = function(t)
|
|
if t.collectible and t.spells then
|
|
GameTooltip:AddLine(" ");
|
|
for i,spell in ipairs(t.spells) do
|
|
GameTooltip:AddDoubleLine(" |T" .. spell.icon .. ":0|t " .. spell.text, app.L[spell.collected and "COLLECTED_ICON" or "NOT_COLLECTED_ICON"], 1, 1, 1);
|
|
end
|
|
end
|
|
end
|
|
commonAchievementHandlers.LEVEL_OnUpdate = function(t)
|
|
t:SetAchievementCollected(t.achievementID, app.Level >= t.lvl);
|
|
end
|
|
commonAchievementHandlers.LOREMASTER_CONTINENT_OnUpdate = function(t, mapID, ...)
|
|
if t.collectible and t.parent then
|
|
if not t.quests then
|
|
local quests, structures = LOREMASTER_CreateQuestsAndStructures(t, mapID, { ... });
|
|
if quests then
|
|
t.quests = quests;
|
|
t.structures = structures;
|
|
else
|
|
return;
|
|
end
|
|
end
|
|
return LOREMASTER_EXPLICIT_OnUpdate(t);
|
|
end
|
|
end
|
|
commonAchievementHandlers.LOREMASTER_OnUpdate = function(t, ...)
|
|
if t.collectible and t.parent then
|
|
local quests = t.quests;
|
|
if not quests then
|
|
quests = LOREMASTER_CreateQuests(t, { ... });
|
|
if quests then
|
|
t.quests = quests;
|
|
else
|
|
return;
|
|
end
|
|
end
|
|
return LOREMASTER_EXPLICIT_OnUpdate(t);
|
|
end
|
|
end
|
|
commonAchievementHandlers.LOREMASTER_OnTooltip = function(t)
|
|
if t.collectible and t.p and not t.collected then
|
|
GameTooltip:AddLine(" ");
|
|
GameTooltip:AddDoubleLine(" ", app.GetProgressText(min(t.rank, t.p),t.rank), 1, 1, 1);
|
|
end
|
|
end
|
|
commonAchievementHandlers.META_ACHCAT_OnUpdate = function(t, achievementCategoryID)
|
|
if t.collectible then
|
|
if not t.achievements then
|
|
local achievements;
|
|
for i,o in ipairs(t.parent.g) do
|
|
if o.achievementCategoryID == achievementCategoryID then
|
|
achievements = o.g;
|
|
break;
|
|
end
|
|
end
|
|
if not achievements then return true; end
|
|
t.achievements = achievements;
|
|
end
|
|
local collected = true;
|
|
for i,faction in ipairs(t.achievements) do
|
|
if not faction.collected and app.FilterItemClass_UnobtainableItem(faction) then
|
|
collected = false;
|
|
break;
|
|
end
|
|
end
|
|
t:SetAchievementCollected(t.achievementID, collected);
|
|
end
|
|
end
|
|
commonAchievementHandlers.META_OnUpdate = function(t, ...)
|
|
if t.collectible then
|
|
if not t.achievements then
|
|
local achievements = {};
|
|
for i,achievementID in ipairs({ ... }) do
|
|
local f = SearchForField("achievementID", achievementID);
|
|
if #f > 0 then
|
|
tinsert(achievements, f[1]);
|
|
else
|
|
return true;
|
|
end
|
|
end
|
|
if #achievements < 1 then return true; end
|
|
t.achievements = achievements;
|
|
end
|
|
local collected = true;
|
|
for i,faction in ipairs(t.achievements) do
|
|
if not faction.collected then
|
|
collected = false;
|
|
break;
|
|
end
|
|
end
|
|
t:SetAchievementCollected(t.achievementID, collected);
|
|
end
|
|
end
|
|
commonAchievementHandlers.META_OnClick = function(row, button)
|
|
if button == "RightButton" then
|
|
local t = row.ref;
|
|
local clone = app:CreateMiniListForGroup(app.CreateAchievement(t[t.key], t.achievements)).data;
|
|
clone.description = t.description;
|
|
return true;
|
|
end
|
|
end
|
|
commonAchievementHandlers.META_OnTooltip = function(t)
|
|
if t.collectible and t.achievements then
|
|
GameTooltip:AddLine(" ");
|
|
for i,achievement in ipairs(t.achievements) do
|
|
GameTooltip:AddDoubleLine(" |T" .. achievement.icon .. ":0|t " .. achievement.text, app.L[achievement.collected and "COLLECTED_ICON" or "NOT_COLLECTED_ICON"], 1, 1, 1);
|
|
end
|
|
end
|
|
end
|
|
commonAchievementHandlers.MOUNTS_OnUpdate = function(t)
|
|
if app.Settings.Collectibles.Mounts then
|
|
local count,r = 0,{};
|
|
for i,g in pairs(SearchForFieldContainer("spellID")) do
|
|
for j,o in ipairs(g) do
|
|
if ((o.f and o.f == app.FilterConstants.MOUNTS)
|
|
or (o.filterID and o.filterID == app.FilterConstants.MOUNTS)) and not r[i] then
|
|
if o.collected then count = count + 1; end
|
|
r[i] = 1;
|
|
break;
|
|
end
|
|
end
|
|
end
|
|
if t.rank > 1 then
|
|
t.progress = math.min(count, t.rank);
|
|
t.total = t.rank;
|
|
t.collectible = false;
|
|
|
|
if app.GroupFilter(t) then
|
|
local parent = t.parent;
|
|
parent.total = (parent.total or 0) + t.total;
|
|
parent.progress = (parent.progress or 0) + t.progress;
|
|
t.visible = (t.progress < t.total or app.CollectedItemVisibilityFilter(t));
|
|
else
|
|
t.visible = false;
|
|
end
|
|
else
|
|
t.collected = count >= 1;
|
|
t.collectible = collectible;
|
|
|
|
if app.GroupFilter(t) then
|
|
local parent = t.parent;
|
|
parent.total = (parent.total or 0) + 1;
|
|
if t.collected then parent.progress = (parent.progress or 0) + 1; end
|
|
t.visible = (not t.collected or app.CollectedItemVisibilityFilter(t));
|
|
else
|
|
t.visible = false;
|
|
end
|
|
end
|
|
else
|
|
t.collected = nil;
|
|
t.collectible = false;
|
|
t.progress = nil;
|
|
t.total = nil;
|
|
t.visible = false;
|
|
end
|
|
return true;
|
|
end
|
|
commonAchievementHandlers.MOUNTS_OnTooltip = function(t)
|
|
GameTooltip:AddLine("Collect " .. t.rank .. " mounts.");
|
|
if t.total and t.progress < t.total and t.rank >= 25 then
|
|
GameTooltip:AddLine(" ");
|
|
local c = 0;
|
|
local template,r = {},{};
|
|
for i,o in pairs(SearchForFieldContainer("spellID")) do
|
|
local p = o[1];
|
|
if ((p.f and p.f == app.FilterConstants.MOUNTS)
|
|
or (p.filterID and p.filterID == app.FilterConstants.MOUNTS)) and not r[i] then
|
|
r[i] = 1;
|
|
if p.visible then
|
|
c = c + 1;
|
|
if c < 16 then
|
|
GameTooltip:AddDoubleLine(" |T" .. p.icon .. ":0|t " .. p.text, app.L[p.collected and "COLLECTED_ICON" or "NOT_COLLECTED_ICON"], 1, 1, 1);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if c > 15 then GameTooltip:AddLine(" And " .. (c - 15) .. " more!"); end
|
|
end
|
|
end
|
|
commonAchievementHandlers.REPUTATIONS_OnUpdate = function(t)
|
|
if app.Settings.Collectibles.Achievements then
|
|
local count = 0;
|
|
local ignored = app.IgnoredReputationsForAchievements;
|
|
if not ignored then
|
|
ignored = {[169] = 1};
|
|
app.IgnoredReputationsForAchievements = ignored;
|
|
end
|
|
for factionID,g in pairs(SearchForFieldContainer("factionID")) do
|
|
if not ignored[factionID] and #g > 0 and g[1].standing == 8 then
|
|
count = count + 1;
|
|
end
|
|
end
|
|
if t.rank > 1 then
|
|
t.progress = math.min(count, t.rank);
|
|
t.total = t.rank;
|
|
t.collectible = false;
|
|
|
|
if app.GroupFilter(t) then
|
|
local parent = t.parent;
|
|
parent.total = (parent.total or 0) + t.total;
|
|
parent.progress = (parent.progress or 0) + t.progress;
|
|
t.visible = (t.progress < t.total or app.CollectedItemVisibilityFilter(t));
|
|
else
|
|
t.visible = false;
|
|
end
|
|
else
|
|
t.collected = count >= 1;
|
|
t.collectible = collectible;
|
|
|
|
if app.GroupFilter(t) then
|
|
local parent = t.parent;
|
|
parent.total = (parent.total or 0) + 1;
|
|
if t.collected then parent.progress = (parent.progress or 0) + 1; end
|
|
t.visible = (not t.collected or app.CollectedItemVisibilityFilter(t));
|
|
else
|
|
t.visible = false;
|
|
end
|
|
end
|
|
else
|
|
t.collected = nil;
|
|
t.collectible = false;
|
|
t.progress = nil;
|
|
t.total = nil;
|
|
t.visible = false;
|
|
end
|
|
return true;
|
|
end
|
|
commonAchievementHandlers.REPUTATIONS_OnTooltip = function(t)
|
|
GameTooltip:AddLine("Raise " .. t.rank .. " reputations to Exalted.");
|
|
if t.total and t.progress < t.total and t.rank >= 25 then
|
|
GameTooltip:AddLine(" ");
|
|
local ignored = app.IgnoredReputationsForAchievements;
|
|
if not ignored then
|
|
ignored = {[169] = 1};
|
|
app.IgnoredReputationsForAchievements = ignored;
|
|
end
|
|
for i,o in ipairs(app:GetDataCache().g) do
|
|
if o.headerID == app.HeaderConstants.FACTIONS then
|
|
for j,p in ipairs(o.g) do
|
|
if (p.visible or p.factionID == 909) and not ignored[p.factionID] and (not p.minReputation or (p.minReputation[1] == p.factionID and p.minReputation[2] >= 41999)) and (not p.maxReputation or (p.maxReputation[1] ~= p.factionID and p.reputation >= 0)) then
|
|
GameTooltip:AddDoubleLine(" |T" .. p.icon .. ":0|t " .. p.text, app.L[p.standing >= 8 and "COLLECTED_ICON" or "NOT_COLLECTED_ICON"], 1, 1, 1);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
app.CreateAchievementCategory = app.CreateClass("AchievementCategory", "achievementCategoryID", categoryFields);
|
|
end)();
|
|
|
|
-- Category Lib
|
|
(function()
|
|
local defaultIcon = "Interface/ICONS/INV_Misc_Gear_02";
|
|
if app.GameBuildVersion > 60001 then
|
|
defaultIcon = "Interface/ICONS/INV_Garrison_Blueprints1";
|
|
end
|
|
app.CreateCategory = app.CreateClass("Category", "categoryID", {
|
|
["text"] = function(t)
|
|
return AllTheThingsAD.LocalizedCategoryNames[t.categoryID] or ("Unknown Category #" .. t.categoryID);
|
|
end,
|
|
["icon"] = function(t)
|
|
return app.CategoryIcons[t.categoryID] or defaultIcon;
|
|
end,
|
|
});
|
|
end)();
|
|
|
|
-- Character Class Lib
|
|
(function()
|
|
local classIcons = {
|
|
[1] = app.asset("ClassIcon_Warrior"),
|
|
[2] = app.asset("ClassIcon_Paladin"),
|
|
[3] = app.asset("ClassIcon_Hunter"),
|
|
[4] = app.asset("ClassIcon_Rogue"),
|
|
[5] = app.asset("ClassIcon_Priest"),
|
|
[6] = app.asset("ClassIcon_DeathKnight"),
|
|
[7] = app.asset("ClassIcon_Shaman"),
|
|
[8] = app.asset("ClassIcon_Mage"),
|
|
[9] = app.asset("ClassIcon_Warlock"),
|
|
[10] = app.asset("ClassIcon_Monk"),
|
|
[11] = app.asset("ClassIcon_Druid"),
|
|
[12] = app.asset("ClassIcon_DemonHunter"),
|
|
};
|
|
local GetClassIDFromClassFile = function(classFile)
|
|
for i,icon in pairs(classIcons) do
|
|
local info = C_CreatureInfo.GetClassInfo(i);
|
|
if info and info.classFile == classFile then
|
|
return i;
|
|
end
|
|
end
|
|
end
|
|
app.ClassDB = setmetatable({}, { __index = function(t, className)
|
|
for i,_ in pairs(classIcons) do
|
|
local info = C_CreatureInfo.GetClassInfo(i);
|
|
if info and info.className == className then
|
|
rawset(t, className, i);
|
|
return i;
|
|
end
|
|
end
|
|
end });
|
|
|
|
app.CreateCharacterClass, app.BaseCharacterClass = app.CreateClass("CharacterClass", "classID", {
|
|
["text"] = function(t)
|
|
local text = "|c" .. t.classColors.colorStr .. t.name .. "|r";
|
|
rawset(t, "text", text);
|
|
return text;
|
|
end,
|
|
["icon"] = function(t)
|
|
return classIcons[t.classID];
|
|
end,
|
|
["name"] = function(t)
|
|
return C_CreatureInfo.GetClassInfo(t.classID).className;
|
|
end,
|
|
["c"] = function(t)
|
|
local c = { t.classID };
|
|
rawset(t, "c", c);
|
|
return c;
|
|
end,
|
|
["nmc"] = function(t)
|
|
return t.classID ~= app.ClassIndex;
|
|
end,
|
|
["classColors"] = function(t)
|
|
return RAID_CLASS_COLORS[C_CreatureInfo.GetClassInfo(t.classID).classFile];
|
|
end,
|
|
});
|
|
app.CreateUnit = app.CreateClass("Unit", "unit", {
|
|
["text"] = function(t)
|
|
return t.classText;
|
|
end,
|
|
["info"] = function(t)
|
|
local unit = t.unit;
|
|
for guid,character in pairs(ATTCharacterData) do
|
|
if guid == unit or character.name == unit then
|
|
rawset(t, "guid", character.guid);
|
|
rawset(t, "name", character.name);
|
|
rawset(t, "lvl", character.lvl);
|
|
if character.classID then
|
|
rawset(t, "classID", character.classID);
|
|
rawset(t, "classes", { character.classID });
|
|
local classInfo = C_CreatureInfo.GetClassInfo(character.classID);
|
|
if classInfo then
|
|
rawset(t, "className", classInfo.className);
|
|
rawset(t, "classFile", classInfo.classFile);
|
|
end
|
|
end
|
|
if character.raceID then
|
|
rawset(t, "raceID", character.raceID);
|
|
rawset(t, "races", { character.raceID });
|
|
rawset(t, "race", C_CreatureInfo.GetRaceInfo(character.raceID).raceName);
|
|
end
|
|
return t;
|
|
end
|
|
end
|
|
local name, guid, className, classFile, classID, raceName, raceFile, raceID;
|
|
if #{strsplit("-", unit)} > 1 then
|
|
-- It's a GUID.
|
|
guid = unit;
|
|
className, classFile, raceName, raceFile, raceID, name = GetPlayerInfoByGUID(guid);
|
|
classID = GetClassIDFromClassFile(classFile);
|
|
else
|
|
name = UnitName(unit);
|
|
if name then
|
|
guid = UnitGUID(unit);
|
|
className, classFile, classID = UnitClass(unit);
|
|
raceName, raceFile, raceID = UnitRace(unit);
|
|
else
|
|
rawset(t, "name", unit);
|
|
return t;
|
|
end
|
|
end
|
|
if name then
|
|
rawset(t, "name", name);
|
|
rawset(t, "guid", guid);
|
|
if classID then
|
|
rawset(t, "className", className);
|
|
rawset(t, "classFile", classFile);
|
|
rawset(t, "classID", classID);
|
|
end
|
|
if raceID then
|
|
rawset(t, "raceID", raceID);
|
|
rawset(t, "races", { raceID });
|
|
rawset(t, "race", C_CreatureInfo.GetRaceInfo(raceID).raceName);
|
|
end
|
|
end
|
|
return t;
|
|
end,
|
|
["name"] = function(t)
|
|
return rawget(t.info, "name");
|
|
end,
|
|
["icon"] = function(t)
|
|
local classID = rawget(t.info, "classID");
|
|
if classID then return classIcons[classID]; end
|
|
end,
|
|
["guid"] = function(t)
|
|
local guid = rawget(t.info, "guid");
|
|
if guid then return guid; end
|
|
end,
|
|
["title"] = function(t)
|
|
if IsInGroup() then
|
|
if rawget(t, "isML") then return MASTER_LOOTER; end
|
|
if UnitIsGroupLeader(t.unit, "raid") then return RAID_LEADER; end
|
|
end
|
|
end,
|
|
["lvl"] = function(t)
|
|
return UnitLevel(t.unit);
|
|
end,
|
|
["race"] = function(t)
|
|
return rawget(t.info, "race");
|
|
end,
|
|
["className"] = function(t)
|
|
return rawget(t.info, "className");
|
|
end,
|
|
["classFile"] = function(t)
|
|
return rawget(t.info, "classFile");
|
|
end,
|
|
["classText"] = function(t)
|
|
local classFile = t.classFile;
|
|
if classFile then return "|c" .. RAID_CLASS_COLORS[classFile].colorStr .. t.name .. "|r"; end
|
|
return t.name;
|
|
end,
|
|
["tooltipText"] = function(t)
|
|
local text = t.text;
|
|
local icon = t.icon;
|
|
if icon then text = "|T" .. icon .. ":0|t " .. text; end
|
|
return text;
|
|
end,
|
|
});
|
|
app.CreateQuestUnit = app.ExtendClass("Unit", "QuestUnit", "unit", {
|
|
["visible"] = app.ReturnTrue,
|
|
["collectible"] = app.ReturnTrue,
|
|
["trackable"] = app.ReturnTrue,
|
|
["collected"] = function(t)
|
|
return t.saved;
|
|
end,
|
|
["OnClick"] = function(t)
|
|
return app.NoFilter;
|
|
end,
|
|
["OnUpdate"] = function(t)
|
|
return app.AlwaysShowUpdateWithoutReturn;
|
|
end,
|
|
["saved"] = function(t)
|
|
local questID = GetRelativeValue(t, "questID");
|
|
if questID then
|
|
local guid = t.guid;
|
|
if guid and questID then
|
|
if guid == app.GUID then
|
|
return IsQuestFlaggedCompleted(questID);
|
|
else
|
|
local questsForGUID = GetDataMember("GroupQuestsByGUID")[guid] or (ATTCharacterData[guid] and ATTCharacterData[guid].Quests);
|
|
return questsForGUID and questsForGUID[questID];
|
|
end
|
|
end
|
|
end
|
|
end,
|
|
});
|
|
end)();
|
|
|
|
-- Companion Lib
|
|
(function()
|
|
local SetBattlePetCollected = function(t, speciesID, collected)
|
|
return app.SetCollected(t, "BattlePets", speciesID, collected);
|
|
end
|
|
local SetMountCollected = function(t, spellID, collected)
|
|
return app.SetCollectedForSubType(t, "Spells", "Mounts", spellID, collected);
|
|
end
|
|
local speciesFields = {
|
|
["f"] = function(t)
|
|
return app.FilterConstants.BATTLE_PETS;
|
|
end,
|
|
["collectible"] = function(t)
|
|
return app.Settings.Collectibles.BattlePets;
|
|
end,
|
|
["text"] = function(t)
|
|
return "|cff0070dd" .. (t.name or RETRIEVING_DATA) .. "|r";
|
|
end,
|
|
["link"] = function(t)
|
|
if t.itemID then
|
|
local link = select(2, GetItemInfo(t.itemID));
|
|
if link then
|
|
t.link = link;
|
|
return link;
|
|
end
|
|
end
|
|
end,
|
|
["tsm"] = function(t)
|
|
if t.itemID then return sformat("i:%d", t.itemID); end
|
|
return sformat("p:%d:1:3", t.speciesID);
|
|
end,
|
|
};
|
|
local mountFields = {
|
|
["text"] = function(t)
|
|
return "|cffb19cd9" .. t.name .. "|r";
|
|
end,
|
|
["icon"] = function(t)
|
|
return select(3, GetSpellInfo(t.spellID));
|
|
end,
|
|
["link"] = function(t)
|
|
return (t.itemID and select(2, GetItemInfo(t.itemID))) or GetSpellLink(t.spellID);
|
|
end,
|
|
["f"] = function(t)
|
|
return app.FilterConstants.MOUNTS;
|
|
end,
|
|
["collectible"] = function(t)
|
|
return app.Settings.Collectibles.Mounts;
|
|
end,
|
|
["explicitlyCollected"] = function(t)
|
|
return IsSpellKnown(t.spellID) or (t.questID and IsQuestFlaggedCompleted(t.questID)) or (t.itemID and GetItemCount(t.itemID, true) > 0);
|
|
end,
|
|
["b"] = function(t)
|
|
return (t.parent and t.parent.b) or 1;
|
|
end,
|
|
["name"] = function(t)
|
|
return GetSpellInfo(t.spellID) or RETRIEVING_DATA;
|
|
end,
|
|
["tsmForItem"] = function(t)
|
|
if t.itemID then return sformat("i:%d", t.itemID); end
|
|
if t.parent and t.parent.itemID then return sformat("i:%d", t.parent.itemID); end
|
|
end,
|
|
["linkForItem"] = function(t)
|
|
return select(2, GetItemInfo(t.itemID)) or GetSpellLink(t.spellID);
|
|
end,
|
|
};
|
|
|
|
if C_PetJournal then
|
|
-- Once the Pet Journal API is available, then all pets become account wide.
|
|
SetBattlePetCollected = function(t, speciesID, collected)
|
|
return app.SetAccountCollected(t, "BattlePets", speciesID, collected);
|
|
end
|
|
speciesFields.icon = function(t)
|
|
return select(2, C_PetJournal.GetPetInfoBySpeciesID(t.speciesID));
|
|
end
|
|
speciesFields.name = function(t)
|
|
return C_PetJournal.GetPetInfoBySpeciesID(t.speciesID);
|
|
end
|
|
speciesFields.petTypeID = function(t)
|
|
return select(3, C_PetJournal.GetPetInfoBySpeciesID(t.speciesID));
|
|
end
|
|
speciesFields.displayID = function(t)
|
|
return select(12, C_PetJournal.GetPetInfoBySpeciesID(t.speciesID));
|
|
end
|
|
speciesFields.description = function(t)
|
|
return select(6, C_PetJournal.GetPetInfoBySpeciesID(t.speciesID));
|
|
end
|
|
speciesFields.collected = function(t)
|
|
local count = C_PetJournal.GetNumCollectedInfo(t.speciesID);
|
|
return SetBattlePetCollected(t, t.speciesID, count and count > 0);
|
|
end
|
|
|
|
local C_MountJournal = _G["C_MountJournal"];
|
|
if C_MountJournal then
|
|
-- Once the Mount Journal API is available, then all mounts become account wide.
|
|
SetMountCollected = function(t, spellID, collected)
|
|
return app.SetAccountCollectedForSubType(t, "Spells", "Mounts", spellID, collected);
|
|
end
|
|
local SpellIDToMountID = setmetatable({}, { __index = function(t, id)
|
|
local allMountIDs = C_MountJournal.GetMountIDs();
|
|
if allMountIDs and #allMountIDs > 0 then
|
|
for i,mountID in ipairs(allMountIDs) do
|
|
local spellID = select(2, C_MountJournal.GetMountInfoByID(mountID));
|
|
if spellID then rawset(t, spellID, mountID); end
|
|
end
|
|
setmetatable(t, nil);
|
|
return rawget(t, id);
|
|
end
|
|
end });
|
|
mountFields.mountID = function(t)
|
|
return SpellIDToMountID[t.spellID];
|
|
end
|
|
mountFields.name = function(t)
|
|
local mountID = t.mountID;
|
|
if mountID then return C_MountJournal.GetMountInfoByID(mountID); end
|
|
return GetSpellInfo(t.spellID) or RETRIEVING_DATA;
|
|
end
|
|
mountFields.displayID = function(t)
|
|
local mountID = t.mountID;
|
|
if mountID then return C_MountJournal.GetMountInfoExtraByID(mountID); end
|
|
end
|
|
mountFields.lore = function(t)
|
|
local mountID = t.mountID;
|
|
if mountID then return select(2, C_MountJournal.GetMountInfoExtraByID(mountID)); end
|
|
end
|
|
mountFields.collected = function(t)
|
|
local mountID = t.mountID;
|
|
if mountID then
|
|
local _, spellID, _, _, _, _, _, _, _, _, isCollected = C_MountJournal.GetMountInfoByID(mountID);
|
|
return SetMountCollected(t, spellID, isCollected);
|
|
else
|
|
local spellID = t.spellID;
|
|
for i,o in ipairs(SearchForField("spellID", spellID)) do
|
|
if o.explicitlyCollected then
|
|
return SetMountCollected(t, spellID, true);
|
|
end
|
|
end
|
|
return SetMountCollected(t, spellID, false);
|
|
end
|
|
end
|
|
else
|
|
mountFields.name = function(t)
|
|
return GetSpellInfo(t.spellID) or RETRIEVING_DATA;
|
|
end
|
|
mountFields.collected = function(t)
|
|
local spellID = t.spellID;
|
|
for i,o in ipairs(SearchForField("spellID", spellID)) do
|
|
if o.explicitlyCollected then
|
|
return SetMountCollected(t, spellID, true);
|
|
end
|
|
end
|
|
return SetMountCollected(t, spellID, false);
|
|
end
|
|
end
|
|
else
|
|
speciesFields.icon = function(t)
|
|
if t.itemID then
|
|
return select(5, GetItemInfoInstant(t.itemID)) or "Interface\\Icons\\INV_Misc_QuestionMark";
|
|
end
|
|
return "Interface\\Icons\\INV_Misc_QuestionMark";
|
|
end
|
|
speciesFields.name = function(t)
|
|
return t.itemID and GetItemInfo(t.itemID) or RETRIEVING_DATA;
|
|
end
|
|
mountFields.name = function(t)
|
|
return GetSpellInfo(t.spellID) or RETRIEVING_DATA;
|
|
end
|
|
if GetCompanionInfo and GetNumCompanions("CRITTER") ~= nil then
|
|
local CollectedBattlePetHelper = {};
|
|
local CollectedMountHelper = {};
|
|
local function RefreshCompanionCollectionStatus(companionType)
|
|
local anythingNew = false;
|
|
if not companionType or companionType == "CRITTER" then
|
|
setmetatable(CollectedBattlePetHelper, nil);
|
|
local critterCount = GetNumCompanions("CRITTER");
|
|
if not critterCount then
|
|
print("Failed to get Companion Info for Critters");
|
|
else
|
|
for i=critterCount,1,-1 do
|
|
local spellID = select(3, GetCompanionInfo("CRITTER", i));
|
|
if spellID then
|
|
if not CollectedBattlePetHelper[spellID] then
|
|
CollectedBattlePetHelper[spellID] = true;
|
|
anythingNew = true;
|
|
end
|
|
else
|
|
print("Failed to get Companion Info for Critter ".. i);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if not companionType or companionType == "MOUNT" then
|
|
setmetatable(CollectedMountHelper, nil);
|
|
for i=GetNumCompanions("MOUNT"),1,-1 do
|
|
local spellID = select(3, GetCompanionInfo("MOUNT", i));
|
|
if spellID then
|
|
if not CollectedMountHelper[spellID] then
|
|
CollectedMountHelper[spellID] = true;
|
|
anythingNew = true;
|
|
end
|
|
else
|
|
print("Failed to get Companion Info for Mount ".. i);
|
|
end
|
|
end
|
|
end
|
|
if anythingNew then app:RefreshDataQuietly("RefreshCompanionCollectionStatus", true); end
|
|
end
|
|
local meta = { __index = function(t, spellID)
|
|
RefreshCompanionCollectionStatus();
|
|
return rawget(t, spellID);
|
|
end };
|
|
setmetatable(CollectedBattlePetHelper, meta);
|
|
setmetatable(CollectedMountHelper, meta);
|
|
speciesFields.collected = function(t)
|
|
return SetBattlePetCollected(t, t.speciesID, (t.spellID and CollectedBattlePetHelper[t.spellID]));
|
|
end
|
|
mountFields.collected = function(t)
|
|
return SetMountCollected(t, t.spellID, (t.spellID and CollectedMountHelper[t.spellID]));
|
|
end
|
|
app:RegisterEvent("COMPANION_LEARNED");
|
|
app:RegisterEvent("COMPANION_UNLEARNED");
|
|
app:RegisterEvent("COMPANION_UPDATE");
|
|
app.events.COMPANION_LEARNED = RefreshCompanionCollectionStatus;
|
|
app.events.COMPANION_UNLEARNED = RefreshCompanionCollectionStatus;
|
|
app.events.COMPANION_UPDATE = RefreshCompanionCollectionStatus;
|
|
else
|
|
speciesFields.collected = function(t)
|
|
return SetBattlePetCollected(t, t.speciesID, t.itemID and GetItemCount(t.itemID, true) > 0);
|
|
end
|
|
mountFields.collected = function(t)
|
|
local spellID = t.spellID;
|
|
for i,o in ipairs(SearchForField("spellID", spellID)) do
|
|
if o.explicitlyCollected then
|
|
return SetMountCollected(t, spellID, true);
|
|
end
|
|
end
|
|
return SetMountCollected(t, spellID, false);
|
|
end
|
|
end
|
|
end
|
|
|
|
app.CreateMount = app.CreateClass("Mount", "spellID", mountFields,
|
|
"WithItem", { -- This is a conditional contructor.
|
|
link = mountFields.linkForItem;
|
|
tsm = mountFields.tsmForItem
|
|
}, function(t) return t.itemID; end);
|
|
app.CreatePetType = app.CreateClass("PetType", "petTypeID", {
|
|
["text"] = function(t)
|
|
return _G["BATTLE_PET_NAME_" .. t.petTypeID];
|
|
end,
|
|
["icon"] = function(t)
|
|
return app.asset("Icon_PetFamily_"..PET_TYPE_SUFFIX[t.petTypeID]);
|
|
end,
|
|
});
|
|
app.CreateSpecies = app.CreateClass("Species", "speciesID", speciesFields);
|
|
end)();
|
|
|
|
-- Currency Lib
|
|
(function()
|
|
local CurrencyInfo = {};
|
|
local GetCurrencyCount;
|
|
local GetCurrencyLink = GetCurrencyLink;
|
|
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)
|
|
return C_CurrencyInfo_GetCurrencyInfo(id).quantity or 0;
|
|
end
|
|
else
|
|
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 CurrencyCollectedAsCost = setmetatable({}, { __index = function(t, id)
|
|
local any, partial;
|
|
local results = SearchForField("currencyIDAsCost", id, true);
|
|
if #results > 0 then
|
|
local count = GetCurrencyCount(id);
|
|
for _,ref in pairs(results) do
|
|
if ref.currencyID ~= id and app.RecursiveDefaultClassAndRaceFilter(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
|
|
if count >= (v[3] or 1) then
|
|
partial = true;
|
|
else
|
|
t[id] = false;
|
|
return false;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
elseif (ref.total and ref.total > 0 and not GetRelativeField(t, "parent", ref) 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
|
|
if count >= (v[3] or 1) then
|
|
partial = true;
|
|
else
|
|
t[id] = false;
|
|
return false;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
any = true;
|
|
end
|
|
end
|
|
if any then
|
|
t[id] = partial and 2 or 1;
|
|
return partial and 2 or 1;
|
|
end
|
|
end
|
|
t[id] = false;
|
|
return false;
|
|
end });
|
|
tinsert(app.EventHandlers.OnRecalculate, function()
|
|
wipe(CurrencyCollectibleAsCost);
|
|
wipe(CurrencyCollectedAsCost);
|
|
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,
|
|
["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,
|
|
});
|
|
|
|
if not TooltipDataProcessor then
|
|
local GameTooltip_SetCurrencyByID = GameTooltip.SetCurrencyByID;
|
|
GameTooltip.SetCurrencyByID = function(self, currencyID, count)
|
|
-- Make sure to call to base functionality
|
|
if GameTooltip_SetCurrencyByID then
|
|
GameTooltip_SetCurrencyByID(self, currencyID, count);
|
|
else
|
|
local results = SearchForField("currencyID", currencyID);
|
|
if #results > 0 then
|
|
GameTooltip:AddLine(results[1].text or RETRIEVING_DATA, 1, 1, 1);
|
|
end
|
|
end
|
|
if (not InCombatLockdown() or app.Settings:GetTooltipSetting("DisplayInCombat")) and app.Settings:GetTooltipSetting("Enabled") then
|
|
AttachTooltipSearchResults(self, 1, "currencyID:" .. currencyID, SearchForField, "currencyID", currencyID);
|
|
if app.Settings:GetTooltipSetting("currencyID") then self:AddDoubleLine(L["CURRENCY_ID"], tostring(currencyID)); end
|
|
self:Show();
|
|
end
|
|
end
|
|
local GameTooltip_SetCurrencyToken = GameTooltip.SetCurrencyToken;
|
|
GameTooltip.SetCurrencyToken = function(self, tokenID)
|
|
-- Make sure to call to base functionality
|
|
if GameTooltip_SetCurrencyToken then GameTooltip_SetCurrencyToken(self, tokenID); end
|
|
if (not InCombatLockdown() or app.Settings:GetTooltipSetting("DisplayInCombat")) and app.Settings:GetTooltipSetting("Enabled") then
|
|
-- Determine what kind of list data this is. (Blizzard is whack and using this API call for headers too...)
|
|
local name, isHeader = GetCurrencyListInfo(tokenID);
|
|
if not isHeader then
|
|
-- Determine which currencyID is the one that we're dealing with.
|
|
for currencyID,results in pairs(SearchForFieldContainer("currencyID")) do
|
|
-- Compare the name of the currency vs the name of the token
|
|
if #results > 0 and results[1].text == name then
|
|
if not GameTooltip_SetCurrencyToken then
|
|
GameTooltip:AddLine(results[1].text or RETRIEVING_DATA, 1, 1, 1);
|
|
end
|
|
AttachTooltipSearchResults(self, 1, "currencyID:" .. currencyID, SearchForField, "currencyID", currencyID);
|
|
if app.Settings:GetTooltipSetting("currencyID") then self:AddDoubleLine(L["CURRENCY_ID"], tostring(currencyID)); end
|
|
self:Show();
|
|
break;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end)();
|
|
|
|
-- Death Tracker Lib
|
|
(function()
|
|
local fields = {
|
|
["text"] = function(t)
|
|
return "Total Deaths";
|
|
end,
|
|
["icon"] = function(t)
|
|
return app.asset("Category_Deaths");
|
|
end,
|
|
["hash"] = function(t)
|
|
return "deathtracker";
|
|
end,
|
|
["progress"] = function(t)
|
|
return math.min(t.total, app.Settings.AccountWide.Deaths and ATTAccountWideData.Deaths or app.CurrentCharacter.Deaths);
|
|
end,
|
|
["OnTooltip"] = function(t)
|
|
local c = {};
|
|
for _,character in pairs(ATTCharacterData) do
|
|
if character and character.Deaths and character.Deaths > 0 then
|
|
tinsert(c, character);
|
|
end
|
|
end
|
|
if #c > 0 then
|
|
GameTooltip:AddLine(" ");
|
|
GameTooltip:AddLine("Deaths Per Character:");
|
|
app.Sort(c, function(a, b)
|
|
return a.Deaths > b.Deaths;
|
|
end);
|
|
for i,character in ipairs(c) do
|
|
GameTooltip:AddDoubleLine(" " .. string.gsub(character.text, "-" .. GetRealmName(), ""), character.Deaths, 1, 1, 1);
|
|
end
|
|
end
|
|
end,
|
|
};
|
|
if C_GameRules and C_GameRules.IsHardcoreActive() then
|
|
fields.description = function(t)
|
|
return "The ATT Gods must be sated. Go forth and attempt to level, mortal!\n\n 'Live! Die! Try Again!'\n";
|
|
end;
|
|
fields.total = function(t) return 1; end
|
|
else
|
|
fields.description = function(t)
|
|
return "The ATT Gods must be sated. Go forth and attempt to level, mortal!\n\n 'Live! Die! Live Again!'\n";
|
|
end;
|
|
fields.total = function(t) return 1000; end
|
|
end
|
|
if GetStatistic and GetStatistic(60) then
|
|
-- Statistics are available, this means we can get the actual statistic from the server's database.
|
|
local OnUpdateForDeathTrackerLib = function(t)
|
|
if app.Settings:Get("Thing:Deaths") then
|
|
local stat = GetStatistic(60) or "0";
|
|
if stat == "--" then stat = "0"; end
|
|
local deaths = tonumber(stat);
|
|
if deaths > 0 and deaths > app.CurrentCharacter.Deaths then
|
|
ATTAccountWideData.Deaths = ATTAccountWideData.Deaths + (deaths - app.CurrentCharacter.Deaths);
|
|
app.CurrentCharacter.Deaths = deaths;
|
|
end
|
|
t.parent.progress = t.parent.progress + t.progress;
|
|
t.parent.total = t.parent.total + t.total;
|
|
t.visible = app.GroupVisibilityFilter(t);
|
|
else
|
|
t.visible = false;
|
|
end
|
|
return true;
|
|
end
|
|
fields.OnUpdate = function(t)
|
|
return OnUpdateForDeathTrackerLib;
|
|
end
|
|
app.events.PLAYER_DEAD = function()
|
|
app:PlayDeathSound();
|
|
end
|
|
else
|
|
-- Oh boy, we have to track it ourselves!
|
|
local OnUpdateForDeathTrackerLib = function(t)
|
|
if app.Settings:Get("Thing:Deaths") then
|
|
t.parent.progress = t.parent.progress + t.progress;
|
|
t.parent.total = t.parent.total + t.total;
|
|
t.visible = app.GroupVisibilityFilter(t);
|
|
else
|
|
t.visible = false;
|
|
end
|
|
return true;
|
|
end
|
|
fields.OnUpdate = function(t)
|
|
return OnUpdateForDeathTrackerLib;
|
|
end
|
|
app.events.PLAYER_DEAD = function()
|
|
ATTAccountWideData.Deaths = ATTAccountWideData.Deaths + 1;
|
|
app.CurrentCharacter.Deaths = app.CurrentCharacter.Deaths + 1;
|
|
app:PlayDeathSound();
|
|
app:RefreshDataQuietly("PLAYER_DEAD");
|
|
end
|
|
end
|
|
app:RegisterEvent("PLAYER_DEAD");
|
|
app.CreateDeathClass = app.CreateClass("DeathsTracker", "deaths", fields);
|
|
end)();
|
|
|
|
-- Difficulty Lib
|
|
(function()
|
|
local difficulties = {
|
|
[1] = { 9, 148, 173 },
|
|
[2] = { 174 },
|
|
[3] = { 175 },
|
|
[4] = { 176 },
|
|
[9] = { 1 },
|
|
[148] = { 1 },
|
|
[173] = { 1 },
|
|
[174] = { 2 },
|
|
[175] = { 3 },
|
|
[176] = { 4 },
|
|
};
|
|
app.DifficultyColors = {
|
|
[2] = "ff0070dd",
|
|
[5] = "ff0070dd",
|
|
[6] = "ff0070dd",
|
|
[7] = "ff9d9d9d",
|
|
[15] = "ff0070dd",
|
|
[16] = "ffa335ee",
|
|
[17] = "ff9d9d9d",
|
|
[23] = "ffa335ee",
|
|
[24] = "ffe6cc80",
|
|
[33] = "ffe6cc80",
|
|
};
|
|
app.DifficultyIcons = {
|
|
[1] = app.asset("Difficulty_Normal"),
|
|
[2] = app.asset("Difficulty_Heroic"),
|
|
[3] = app.asset("Difficulty_Normal"),
|
|
[4] = app.asset("Difficulty_Normal"),
|
|
[5] = app.asset("Difficulty_Heroic"),
|
|
[6] = app.asset("Difficulty_Heroic"),
|
|
[7] = app.asset("Difficulty_LFR"),
|
|
[9] = app.asset("Difficulty_Mythic"),
|
|
[11] = app.asset("Difficulty_Normal"),
|
|
[12] = app.asset("Difficulty_Heroic"),
|
|
[14] = app.asset("Difficulty_Normal"),
|
|
[15] = app.asset("Difficulty_Heroic"),
|
|
[16] = app.asset("Difficulty_Mythic"),
|
|
[17] = app.asset("Difficulty_LFR"),
|
|
[18] = app.asset("Category_Event"),
|
|
[23] = app.asset("Difficulty_Mythic"),
|
|
[24] = app.asset("Difficulty_Timewalking"),
|
|
[33] = app.asset("Difficulty_Timewalking"),
|
|
};
|
|
app.CreateDifficulty = app.CreateClass("Difficulty", "difficultyID", {
|
|
["text"] = function(t)
|
|
return t.sourceParent and sformat("%s [%s]", t.name, t.sourceParent.text or UNKNOWN) or t.name;
|
|
end,
|
|
["name"] = function(t)
|
|
return GetDifficultyInfo(t.difficultyID) or "Unknown Difficulty";
|
|
end,
|
|
["icon"] = function(t)
|
|
return app.DifficultyIcons[t.difficultyID];
|
|
end,
|
|
["saved"] = function(t)
|
|
return t.locks;
|
|
end,
|
|
["locks"] = function(t)
|
|
local locks = t.parent and t.parent.locks;
|
|
if locks then
|
|
if t.parent.isLockoutShared and not (t.difficultyID == 7 or t.difficultyID == 17) then
|
|
rawset(t, "locks", locks.shared);
|
|
return locks.shared;
|
|
else
|
|
-- Look for this difficulty's lockout.
|
|
for difficultyKey, lock in pairs(locks) do
|
|
if difficultyKey == "shared" then
|
|
-- ignore this one
|
|
elseif difficultyKey == t.difficultyID then
|
|
rawset(t, "locks", lock);
|
|
return lock;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end,
|
|
["difficulties"] = function(t)
|
|
return difficulties[t.difficultyID];
|
|
end,
|
|
["u"] = function(t)
|
|
if t.difficultyID == 24 or t.difficultyID == 33 then
|
|
return 42;
|
|
end
|
|
end,
|
|
["description"] = function(t)
|
|
if t.difficultyID == 24 or t.difficultyID == 33 then
|
|
return L["WE_JUST_HATE_TIMEWALKING"];
|
|
end
|
|
end,
|
|
["hash"] = function(t)
|
|
if t.parent then return t.key .. t[t.key] .. "~" .. t.parent.key .. t.parent[t.parent.key]; end
|
|
end,
|
|
});
|
|
end)();
|
|
|
|
-- Encounter Lib
|
|
(function()
|
|
if EJ_GetEncounterInfo then
|
|
app.CreateEncounter = app.CreateClass("Encounter", "encounterID", {
|
|
["name"] = function(t)
|
|
return EJ_GetEncounterInfo(t.encounterID);
|
|
end,
|
|
["lore"] = function(t)
|
|
return select(2, EJ_GetEncounterInfo(t.encounterID));
|
|
end,
|
|
["displayID"] = function(t)
|
|
return select(4, EJ_GetCreatureInfo(1, t.encounterID));
|
|
end,
|
|
["displayInfo"] = function(t)
|
|
local displayInfos, id, displayInfo = {}, t.encounterID;
|
|
for i=1,MAX_CREATURES_PER_ENCOUNTER do
|
|
displayInfo = select(4, EJ_GetCreatureInfo(i, id));
|
|
if displayInfo then
|
|
tinsert(displayInfos, displayInfo);
|
|
else
|
|
break;
|
|
end
|
|
end
|
|
rawset(t, "displayInfo", displayInfos);
|
|
return displayInfos;
|
|
end,
|
|
["icon"] = function(t)
|
|
return app.DifficultyIcons[GetRelativeValue(t, "difficultyID") or 1];
|
|
end,
|
|
},
|
|
"WithQuest", {
|
|
trackable = app.ReturnTrue,
|
|
saved = function(t)
|
|
return IsQuestFlaggedCompletedForObject(t) == 1;
|
|
end
|
|
}, (function(t) return t.questID; end));
|
|
else
|
|
app.CreateEncounter = function(id, t)
|
|
local npcID = t.creatureID or (t.crs and t.crs[1]) or t.npcID or (t.qgs and t.qgs[1]);
|
|
if npcID then
|
|
t = app.CreateNPC(npcID, t);
|
|
t.encounterID = id;
|
|
return t;
|
|
else
|
|
t = constructor(id, t, "encounterID");
|
|
t.text = "INVALID ENCOUNTER " .. id;
|
|
print(t.text);
|
|
return t;
|
|
end
|
|
end
|
|
end
|
|
end)();
|
|
|
|
-- Faction Lib
|
|
(function()
|
|
local setFactionCollected = function(t, factionID, collected)
|
|
return app.SetCollectedForSubType(t, "Factions", "Reputations", factionID, collected);
|
|
end
|
|
local StandingByID = {
|
|
{ -- 1: HATED
|
|
["color"] = GetProgressColor(0),
|
|
["threshold"] = -42000,
|
|
},
|
|
{ -- 2: HOSTILE
|
|
["color"] = "00FF0000",
|
|
["threshold"] = -6000,
|
|
},
|
|
{ -- 3: UNFRIENDLY
|
|
["color"] = "00EE6622",
|
|
["threshold"] = -3000,
|
|
},
|
|
{ -- 4: NEUTRAL
|
|
["color"] = "00FFFF00",
|
|
["threshold"] = 0,
|
|
},
|
|
{ -- 5: FRIENDLY
|
|
["color"] = "0000FF00",
|
|
["threshold"] = 3000,
|
|
},
|
|
{ -- 6: HONORED
|
|
["color"] = "0000FF88",
|
|
["threshold"] = 9000,
|
|
},
|
|
{ -- 7: REVERED
|
|
["color"] = "0000FFCC",
|
|
["threshold"] = 21000,
|
|
},
|
|
{ -- 8: EXALTED
|
|
["color"] = GetProgressColor(1),
|
|
["threshold"] = 42000,
|
|
},
|
|
};
|
|
app.FactionNameByID = setmetatable({}, { __index = function(t, id)
|
|
local name = GetFactionInfoByID(id);
|
|
if name then
|
|
rawset(t, id, name);
|
|
rawset(app.FactionIDByName, name, id);
|
|
return name;
|
|
end
|
|
end });
|
|
app.FactionIDByName = setmetatable({}, { __index = function(t, name)
|
|
for i=1,3000,1 do
|
|
if app.FactionNameByID[i] == name then
|
|
return i;
|
|
end
|
|
end
|
|
end });
|
|
app.ColorizeStandingText = function(standingID, text)
|
|
local standing = StandingByID[standingID];
|
|
if standing then
|
|
return Colorize(text, standing.color);
|
|
else
|
|
local rgb = FACTION_BAR_COLORS[standingID];
|
|
return Colorize(text, RGBToHex(rgb.r * 255, rgb.g * 255, rgb.b * 255));
|
|
end
|
|
end
|
|
app.GetFactionIDByName = function(name)
|
|
name = strtrim(name);
|
|
return app.FactionIDByName[name] or name;
|
|
end
|
|
app.GetFactionStanding = function(reputation)
|
|
-- Total earned rep from GetFactionInfoByID is a value AWAY FROM ZERO, not a value within the standing bracket.
|
|
if reputation then
|
|
for i=#StandingByID,1,-1 do
|
|
local threshold = StandingByID[i].threshold;
|
|
if reputation >= threshold then
|
|
return i, threshold < 0 and (threshold - reputation) or (reputation - threshold);
|
|
end
|
|
end
|
|
end
|
|
return 1, 0
|
|
end
|
|
app.GetFactionStandingText = function(standingID)
|
|
return app.ColorizeStandingText(standingID, _G["FACTION_STANDING_LABEL" .. standingID] or UNKNOWN);
|
|
end
|
|
app.GetFactionStandingThresholdFromString = function(replevel)
|
|
replevel = strtrim(replevel);
|
|
for standing=1,8,1 do
|
|
if _G["FACTION_STANDING_LABEL" .. standing] == replevel then
|
|
return StandingByID[standing].threshold;
|
|
end
|
|
end
|
|
end
|
|
app.IsFactionExclusive = function(factionID)
|
|
return factionID == 934 or factionID == 932 or factionID == 1104 or factionID == 1105;
|
|
end
|
|
local fields = {
|
|
["text"] = function(t)
|
|
return app.ColorizeStandingText(t.standing, t.name);
|
|
end,
|
|
["name"] = function(t)
|
|
return app.FactionNameByID[t.factionID] or (t.creatureID and app.NPCNameFromID[t.creatureID]) or (FACTION .. " #" .. t.factionID);
|
|
end,
|
|
["icon"] = function(t)
|
|
return app.asset("Category_Factions");
|
|
end,
|
|
["trackable"] = app.ReturnTrue,
|
|
["collectible"] = function(t)
|
|
if app.Settings.Collectibles.Reputations then
|
|
-- If your reputation is higher than the maximum for a different faction, return partial completion.
|
|
if not app.Settings.AccountWide.Reputations and t.maxReputation and t.maxReputation[1] ~= t.factionID and (select(3, GetFactionInfoByID(t.maxReputation[1])) or 4) >= app.GetFactionStanding(t.maxReputation[2]) then
|
|
return false;
|
|
end
|
|
return true;
|
|
end
|
|
return false;
|
|
end,
|
|
["saved"] = function(t)
|
|
local factionID = t.factionID;
|
|
local minReputation = t.minReputation;
|
|
if minReputation and minReputation[1] == factionID then
|
|
return setFactionCollected(t, factionID, (select(6, GetFactionInfoByID(factionID)) or 0) >= minReputation[2]);
|
|
else
|
|
return setFactionCollected(t, factionID, t.standing >= t.maxstanding);
|
|
end
|
|
end,
|
|
["title"] = function(t)
|
|
local reputation = t.reputation;
|
|
local amount, ceiling = select(2, app.GetFactionStanding(reputation)), t.ceiling;
|
|
local title = _G["FACTION_STANDING_LABEL" .. t.standing];
|
|
if ceiling then
|
|
title = title .. DESCRIPTION_SEPARATOR .. amount .. " / " .. ceiling;
|
|
if reputation < 42000 then
|
|
return title .. " (" .. (42000 - reputation) .. " to " .. _G["FACTION_STANDING_LABEL8"] .. ")";
|
|
end
|
|
end
|
|
return title;
|
|
end,
|
|
["reputation"] = function(t)
|
|
return select(6, GetFactionInfoByID(t.factionID));
|
|
end,
|
|
["ceiling"] = function(t)
|
|
local _, _, _, m, ma = GetFactionInfoByID(t.factionID);
|
|
return ma and m and (ma - m);
|
|
end,
|
|
["standing"] = function(t)
|
|
return select(3, GetFactionInfoByID(t.factionID)) or 1;
|
|
end,
|
|
["maxstanding"] = function(t)
|
|
if t.minReputation and t.minReputation[1] == t.factionID then
|
|
return app.GetFactionStanding(t.minReputation[2]);
|
|
end
|
|
return 8;
|
|
end,
|
|
["description"] = function(t)
|
|
return select(2, GetFactionInfoByID(t.factionID)) or "Not all reputations can be viewed on a single character. IE: Warsong Outriders cannot be viewed by an Alliance Player and Silverwing Sentinels cannot be viewed by a Horde Player.";
|
|
end,
|
|
};
|
|
fields.collected = fields.saved;
|
|
app.CreateFaction = app.CreateClass("Faction", "factionID", fields);
|
|
app.OnUpdateReputationRequired = function(t)
|
|
if app.Settings:Get("DebugMode") or app.Settings:Get("AccountMode") then
|
|
t.visible = true;
|
|
return false;
|
|
else
|
|
local reputationID = t.minReputation[1];
|
|
t.visible = (select(3, GetFactionInfoByID(reputationID)) or 1) >= 4;
|
|
return true;
|
|
end
|
|
end
|
|
end)();
|
|
|
|
-- Flight Path Lib
|
|
(function()
|
|
local arrOfNodes = {
|
|
1414, -- Kalimdor
|
|
1415, -- Eastern Kingdoms
|
|
1941, -- Eversong Woods (and Ghostlands + Isle of Quel'Danas)
|
|
1943, -- Azuremyst Isle (and Bloodmyst)
|
|
1944, -- Hellfire Peninsula (All of Outland)
|
|
|
|
-- TODO:
|
|
118, -- Icecrown (All of Northrend)
|
|
--422, -- Dread Wastes (All of Pandaria)
|
|
--525, -- Frostfire Ridge (All of Draenor)
|
|
--630, -- Azsuna (All of Broken Isles)
|
|
--882, -- Mac'Aree (All of Argus)
|
|
--862, -- Zuldazar (All of Zuldazar)
|
|
--896, -- Drustvar (All of Kul Tiras)
|
|
|
|
1209, -- Kalimdor
|
|
1208, -- Eastern Kingdoms
|
|
1467, -- Outland
|
|
1384, -- Northrend
|
|
1923, -- Pandaria
|
|
1922, -- Draenor
|
|
993, -- Broken Isles
|
|
994, -- Argus
|
|
1011, -- Zandalar
|
|
1014, -- Kul Tiras
|
|
1504, -- Nazjatar
|
|
1647, -- The Shadowlands
|
|
1409, -- Exile's Reach [Correct]
|
|
2046, -- Zereth Mortis
|
|
2057, -- Dragon Isles
|
|
2055, -- Sepulcher of the First Ones (has FPs inside)
|
|
2149, -- Ohn'ahran Plains [The Nokhud Offensive] (has FPs inside)
|
|
2175, -- Zaralek Cavern
|
|
};
|
|
app.CacheFlightPathData = function()
|
|
local newNodes, anyNew = {}, false;
|
|
for i,mapID in ipairs(arrOfNodes) do
|
|
local allNodeData = C_TaxiMap.GetTaxiNodesForMap(mapID);
|
|
if allNodeData then
|
|
for j,nodeData in ipairs(allNodeData) do
|
|
if nodeData.name then
|
|
AllTheThingsAD.LocalizedFlightPathNames[nodeData.nodeID] = nodeData.name;
|
|
if #SearchForField("flightPathID", nodeData.nodeID) < 1 then
|
|
newNodes[nodeData.nodeID] = nodeData.name;
|
|
anyNew = true;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if anyNew then
|
|
print("Found new flight path data:");
|
|
for i,name in pairs(newNodes) do
|
|
print(i, name);
|
|
end
|
|
SetDataMember("NewFlightPathData", newNodes);
|
|
end
|
|
end
|
|
app.CacheFlightPathDataForMap = function(mapID, nodes)
|
|
local count = 0;
|
|
local temp = {};
|
|
for nodeID,_ in pairs(SearchForFieldContainer("flightPathID")) do
|
|
for i,node in ipairs(_) do
|
|
if not node.u and node.coords and node.coords[1][3] == mapID then
|
|
if not node.r or node.r == app.FactionID then
|
|
temp[nodeID] = node;
|
|
count = count + 1;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if count > 0 then
|
|
if count > 1 then
|
|
count = 0;
|
|
local mapID = app.CurrentMapID;
|
|
if mapID then
|
|
local pos = C_Map_GetPlayerMapPosition(mapID, "player");
|
|
if pos then
|
|
local px, py = pos:GetXY();
|
|
px, py = px * 100, py * 100;
|
|
|
|
-- Select the best flight path node.
|
|
for nodeID,node in pairs(temp) do
|
|
local coord = node.coords and node.coords[1];
|
|
if coord then
|
|
-- Allow for a little bit of leeway.
|
|
if distance(px, py, coord[1], coord[2]) < 0.6 then
|
|
nodes[nodeID] = true;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
else
|
|
for nodeID,_ in pairs(temp) do
|
|
nodes[nodeID] = true;
|
|
end
|
|
end
|
|
end
|
|
return count;
|
|
end
|
|
app.CacheFlightPathDataForTarget = function(nodes)
|
|
local guid = UnitGUID("npc") or UnitGUID("target");
|
|
if guid then
|
|
local type, zero, server_id, instance_id, zone_uid, npcID, spawn_uid = strsplit("-",guid);
|
|
if type == "Creature" and npcID then
|
|
npcID = tonumber(npcID);
|
|
local count = 0;
|
|
local searchResults = SearchForField("creatureID", npcID);
|
|
if searchResults and #searchResults > 0 then
|
|
for i,group in ipairs(searchResults) do
|
|
if group.flightPathID and not group.nmr and not group.nmc and (not group.u or group.u > 1) then
|
|
nodes[group.flightPathID] = true;
|
|
count = count + 1;
|
|
end
|
|
end
|
|
end
|
|
return count;
|
|
end
|
|
end
|
|
return 0;
|
|
end
|
|
app.CreateFlightPath = app.CreateClass("FlightPath", "flightPathID", {
|
|
["text"] = function(t)
|
|
return t.name;
|
|
end,
|
|
["name"] = function(t)
|
|
return AllTheThingsAD.LocalizedFlightPathNames[t.flightPathID] or "Visit the Flight Master to cache.";
|
|
end,
|
|
["icon"] = function(t)
|
|
local r = t.r;
|
|
if r then
|
|
if r == HORDE_FACTION_ID then
|
|
return app.asset("fp_horde");
|
|
else
|
|
return app.asset("fp_alliance");
|
|
end
|
|
end
|
|
return app.asset("fp_neutral");
|
|
end,
|
|
["description"] = function(t)
|
|
return "Flight paths are cached when you look at the flight master at each location.\n - Crieve";
|
|
end,
|
|
["collectible"] = function(t)
|
|
return app.Settings.Collectibles.FlightPaths;
|
|
end,
|
|
["collected"] = function(t)
|
|
if app.CurrentCharacter.FlightPaths[t.flightPathID] then return 1; end
|
|
if app.Settings.AccountWide.FlightPaths and ATTAccountWideData.FlightPaths[t.flightPathID] then return 2; end
|
|
if t.altQuests then
|
|
for i,questID in ipairs(t.altQuests) do
|
|
if IsQuestFlaggedCompleted(questID) then
|
|
return 2;
|
|
end
|
|
end
|
|
end
|
|
end,
|
|
["nmc"] = function(t)
|
|
local c = t.c;
|
|
if c and not contains(c, app.ClassIndex) then
|
|
rawset(t, "nmc", true); -- "Not My Class"
|
|
return true;
|
|
end
|
|
rawset(t, "nmc", false); -- "My Class"
|
|
return false;
|
|
end,
|
|
["nmr"] = function(t)
|
|
local r = t.r;
|
|
return r and r ~= app.FactionID;
|
|
end,
|
|
});
|
|
app.events.GOSSIP_SHOW = function()
|
|
local knownNodeIDs = {};
|
|
if app.CacheFlightPathDataForTarget(knownNodeIDs) > 0 then
|
|
local any = false;
|
|
for nodeID,_ in pairs(knownNodeIDs) do
|
|
nodeID = tonumber(nodeID);
|
|
if not app.CurrentCharacter.FlightPaths[nodeID] then
|
|
local searchResults = SearchForField("flightPathID", nodeID);
|
|
app.SetCollected(#searchResults > 0 and searchResults[1], "FlightPaths", nodeID, true);
|
|
any = true;
|
|
end
|
|
end
|
|
if any then
|
|
app:RefreshDataQuietly("GOSSIP_SHOW", true);
|
|
end
|
|
end
|
|
end
|
|
app.events.TAXIMAP_OPENED = function()
|
|
local knownNodeIDs = {};
|
|
app.CacheFlightPathDataForTarget(knownNodeIDs);
|
|
app.CacheFlightPathDataForMap(app.CurrentMapID, knownNodeIDs);
|
|
|
|
local allNodeData = C_TaxiMap.GetAllTaxiNodes(app.CurrentMapID);
|
|
if allNodeData then
|
|
for j,nodeData in ipairs(allNodeData) do
|
|
if nodeData.state and nodeData.state < 2 then
|
|
knownNodeIDs[nodeData.nodeID] = true;
|
|
end
|
|
end
|
|
end
|
|
|
|
local any = false;
|
|
for nodeID,_ in pairs(knownNodeIDs) do
|
|
nodeID = tonumber(nodeID);
|
|
if not app.CurrentCharacter.FlightPaths[nodeID] then
|
|
local searchResults = SearchForField("flightPathID", nodeID);
|
|
app.SetCollected(#searchResults > 0 and searchResults[1], "FlightPaths", nodeID, true);
|
|
any = true;
|
|
end
|
|
end
|
|
if any then
|
|
app:RefreshDataQuietly("TAXIMAP_OPENED", true);
|
|
end
|
|
end
|
|
end)();
|
|
|
|
-- Garrison Lib
|
|
(function()
|
|
if C_Garrison then
|
|
local C_Garrison_GetBuildingInfo = C_Garrison.GetBuildingInfo;
|
|
local GarrisonBuildingInfoMeta = { __index = function(t, key)
|
|
local _, name, _, icon, lore, _, _, _, _, _, uncollected = C_Garrison_GetBuildingInfo(t.garrisonBuildingID);
|
|
if not name then return nil; end
|
|
t.name = name;
|
|
t.icon = icon;
|
|
t.lore = lore;
|
|
setmetatable(t, nil);
|
|
return t[key];
|
|
end };
|
|
local GarrisonBuildingInfoByID = setmetatable({}, { __index = function(t, id)
|
|
local info = setmetatable({ garrisonBuildingID = id }, GarrisonBuildingInfoMeta);
|
|
t[id] = info;
|
|
return info;
|
|
end });
|
|
app.CreateGarrisonBuilding = app.CreateClass("GarrisonBuilding", "garrisonBuildingID", {
|
|
info = function(t)
|
|
return GarrisonBuildingInfoByID[t.garrisonBuildingID];
|
|
end,
|
|
name = function(t)
|
|
return t.info.name;
|
|
end,
|
|
icon = function(t)
|
|
return t.info.icon;
|
|
end,
|
|
lore = function(t)
|
|
return t.info.lore;
|
|
end,
|
|
},
|
|
"WithItem", {
|
|
icon = function(t)
|
|
return select(5, GetItemInfoInstant(t.itemID)) or t.info.icon;
|
|
end,
|
|
link = function(t)
|
|
return select(2, GetItemInfo(t.itemID)) or RETRIEVING_DATA;
|
|
end,
|
|
name = function(t)
|
|
return GetItemInfo(t.itemID) or t.info.name;
|
|
end,
|
|
tsm = function(t)
|
|
return sformat("i:%d", t.itemID);
|
|
end,
|
|
f = function(t)
|
|
return app.FilterConstants.RECIPES;
|
|
end,
|
|
collectible = function(t)
|
|
return app.Settings.Collectibles.Recipes;
|
|
end,
|
|
collected = function(t)
|
|
local id = t.garrisonBuildingID;
|
|
if app.CurrentCharacter.GarrisonBuildings[id] then return 1; end
|
|
return app.SetCollected(t, "GarrisonBuildings", id, not select(11, C_Garrison_GetBuildingInfo(id)));
|
|
end,
|
|
}, (function(t) return t.itemID; end));
|
|
|
|
local C_Garrison_GetMissionName = C_Garrison.GetMissionName;
|
|
app.CreateGarrisonMission = app.CreateClass("GarrisonMission", "garrisonMissionID", {
|
|
name = function(t)
|
|
return C_Garrison_GetMissionName(t.missionID);
|
|
end,
|
|
icon = function(t)
|
|
return "Interface/ICONS/INV_Icon_Mission_Complete_Order";
|
|
end,
|
|
});
|
|
|
|
local C_Garrison_GetTalentInfo = C_Garrison.GetTalentInfo;
|
|
local GarrisonTalentInfoMeta = { __index = function(t, key)
|
|
local info = C_Garrison_GetTalentInfo(t.garrisonTalentID);
|
|
if not info then return nil; end
|
|
t.name = info.name;
|
|
t.icon = info.icon or "Interface/ICONS/INV_Icon_Mission_Complete_Order";
|
|
t.description = info.description;
|
|
setmetatable(t, nil);
|
|
return t[key];
|
|
end };
|
|
local GarrisonTalentInfoByID = setmetatable({}, { __index = function(t, id)
|
|
local info = setmetatable({ garrisonTalentID = id }, GarrisonTalentInfoMeta);
|
|
t[id] = info;
|
|
return info;
|
|
end });
|
|
app.CreateGarrisonTalent = app.CreateClass("GarrisonTalent", "garrisonTalentID", {
|
|
info = function(t)
|
|
return GarrisonTalentInfoByID[t.garrisonTalentID];
|
|
end,
|
|
name = function(t)
|
|
return t.info.name;
|
|
end,
|
|
icon = function(t)
|
|
return t.info.icon;
|
|
end,
|
|
description = function(t)
|
|
return t.info.description;
|
|
end,
|
|
trackable = app.ReturnTrue,
|
|
saved = function(t)
|
|
return C_Garrison_GetTalentInfo(t.garrisonTalentID).researched;
|
|
end,
|
|
});
|
|
else
|
|
app.CreateGarrisonBuilding = function(id, t)
|
|
return { text = "GarrisonBuilding #" .. id, description = "This data type is not supported at this time." };
|
|
end
|
|
app.CreateGarrisonMission = function(id, t)
|
|
return { text = "GarrisonMission #" .. id, description = "This data type is not supported at this time." };
|
|
end
|
|
app.CreateGarrisonTalent = function(id, t)
|
|
return { text = "GarrisonTalent #" .. id, description = "This data type is not supported at this time." };
|
|
end
|
|
end
|
|
end)();
|
|
|
|
-- Item Lib
|
|
(function()
|
|
local BestSuffixPerItemID = setmetatable({}, { __index = function(t, id)
|
|
local suffixes = GetDataSubMember("ValidSuffixesPerItemID", id);
|
|
if suffixes then
|
|
for suffixID,_ in pairs(suffixes) do
|
|
rawset(t, id, suffixID);
|
|
return suffixID;
|
|
end
|
|
else
|
|
-- No valid suffixes
|
|
rawset(t, id, 0);
|
|
return 0;
|
|
end
|
|
end });
|
|
local TotalRetriesPerItemID = setmetatable({}, { __index = function(t, id)
|
|
return 0;
|
|
end });
|
|
local BestItemLinkPerItemID = setmetatable({}, { __index = function(t, id)
|
|
local suffixID = BestSuffixPerItemID[id];
|
|
local link = select(2, GetItemInfo(suffixID > 0 and sformat("item:%d:0:0:0:0:0:%d", id, suffixID) or id));
|
|
if link then
|
|
rawset(t, id, link);
|
|
return link;
|
|
end
|
|
end });
|
|
local BlacklistedRWPItems = {
|
|
[22736] = true, -- Andonisus, Reaper of Souls
|
|
};
|
|
local baseGetItemCount = function(t)
|
|
return GetItemCount(t.itemID, true);
|
|
end;
|
|
app.ParseItemID = function(itemName)
|
|
if type(itemName) == "number" then
|
|
return itemName;
|
|
else
|
|
local itemID = tonumber(itemName);
|
|
if string.match(tostring(itemID), itemName) then
|
|
-- This was actually an item ID.
|
|
return itemID;
|
|
else
|
|
-- The itemID given was actually the name or a link.
|
|
itemID = GetItemInfoInstant(itemName);
|
|
if itemID then
|
|
-- Oh good, it was cached by WoW.
|
|
return itemID;
|
|
else
|
|
-- Oh no, gonna need to work for it.
|
|
for id,_ in pairs(SearchForFieldContainer("itemID")) do
|
|
local text = BestItemLinkPerItemID[id];
|
|
if text and string.match(text, itemName) then
|
|
return id;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
app.ClearItemCache = function()
|
|
wipe(BestSuffixPerItemID);
|
|
wipe(BestItemLinkPerItemID);
|
|
end
|
|
local collectibleAsCostForItem = function(t)
|
|
local id = t.itemID;
|
|
local results = SearchForField("itemIDAsCost", id);
|
|
if #results > 0 then
|
|
local costTotal = 0;
|
|
if not t.parent or not t.parent.saved then
|
|
for _,ref in pairs(results) do
|
|
if ref.itemID ~= id and app.RecursiveGroupRequirementsFilter(ref) then
|
|
if ref.key == "instanceID" or ((ref.key == "difficultyID" or ref.key == "mapID" or ref.key == "headerID") and (ref.parent and GetRelativeValue(ref.parent, "instanceID"))) then
|
|
if costTotal < 1 then -- This is for Keys
|
|
costTotal = costTotal + 1;
|
|
end
|
|
elseif (ref.collectible and not ref.collected) or (ref.total and ref.total > ref.progress) then
|
|
if ref.cost then
|
|
for k,v in ipairs(ref.cost) do
|
|
if v[2] == id and v[1] == "i" then
|
|
costTotal = costTotal + (v[3] or 1);
|
|
end
|
|
end
|
|
end
|
|
if ref.providers then
|
|
for k,v in ipairs(ref.providers) do
|
|
if v[2] == id and v[1] == "i" then
|
|
if ref.objectiveID then
|
|
costTotal = costTotal + (t.objectiveCost or 0);
|
|
else
|
|
costTotal = costTotal + 1;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
t.costTotal = costTotal;
|
|
return costTotal > 0;
|
|
elseif t.simplemeta then
|
|
-- If no references to the item were used as a cost evaluation, then simplify the meta.
|
|
setmetatable(t, t.simplemeta);
|
|
return false;
|
|
end
|
|
end;
|
|
local collectedAsCostForItem = function(t)
|
|
if t.costTotal and t.costTotal > 0 then
|
|
return t.GetItemCount(t) >= t.costTotal;
|
|
end
|
|
end;
|
|
local collectibleAsQuest = function(t)
|
|
if app.Settings.Collectibles.Quests then
|
|
if (not t.repeatable and not t.isBreadcrumb) or C_QuestLog_IsOnQuest(t.questID) then
|
|
return true;
|
|
end
|
|
end
|
|
end
|
|
local isCollectibleRWP = function(t)
|
|
return t.f and (t.rwp or (t.u and (t.u == 2 or t.u == 3 or t.u == 4))) and not BlacklistedRWPItems[t.itemID] and app.Settings:GetFilterForRWPBase(t.f);
|
|
end
|
|
local collectedAsRWP = function(t)
|
|
if app.Settings.Collectibles.RWP then
|
|
-- If it's a BOE we can collect it on this character.
|
|
local id, b = t.itemID, t.b;
|
|
if not b or b == 2 or b == 3 then
|
|
-- This item is BOE. You CAN collect this on this character! (but not from a quest)
|
|
return app.SetCollected(t, "RWP", id, GetItemCount(id, true) > 0);
|
|
elseif app.Settings:GetFilterForRWP(t.f) or (t.filterForRWP and app.Settings:GetFilterForRWP(t.filterForRWP)) then
|
|
-- This character matches requirements
|
|
if GetItemCount(id, true) > 0 then
|
|
-- You kept this item. Nice!
|
|
return app.SetCollected(t, "RWP", id, true);
|
|
else
|
|
-- Check to see if this item was a quest reward.
|
|
local searchResults = SearchForField("itemID", id);
|
|
if #searchResults > 0 then
|
|
for i,o in ipairs(searchResults) do
|
|
if ((o.key == "questID" and o.saved) or (o.parent and o.parent.key == "questID" and o.parent.saved)) and app.RecursiveDefaultClassAndRaceFilter(o) then
|
|
return app.SetCollected(t, "RWP", id, true);
|
|
end
|
|
end
|
|
return app.SetCollected(t, "RWP", id, false);
|
|
end
|
|
end
|
|
else
|
|
-- This character does NOT match requirements and the item is BOP. You can't collect these on this character. :(
|
|
return app.SetCollected(t, "RWP", id, false);
|
|
end
|
|
end
|
|
end;
|
|
local itemFields = {
|
|
["text"] = function(t)
|
|
return t.link;
|
|
end,
|
|
["icon"] = function(t)
|
|
return select(5, GetItemInfoInstant(t.itemID)) or "Interface\\Icons\\INV_Misc_QuestionMark";
|
|
end,
|
|
["link"] = function(t)
|
|
return BestItemLinkPerItemID[t.itemID];
|
|
end,
|
|
["name"] = function(t)
|
|
local link = t.link;
|
|
return link and GetItemInfo(link);
|
|
end,
|
|
["b"] = function(t)
|
|
return 2;
|
|
end,
|
|
["f"] = function(t)
|
|
if t.questID then return app.FilterConstants.QUEST_ITEMS; end
|
|
if #SearchForField("itemIDAsCost", t.itemID) > 0 then
|
|
return app.FilterConstants.QUEST_ITEMS;
|
|
end
|
|
end,
|
|
["tsm"] = function(t)
|
|
return sformat("i:%d", t.itemID);
|
|
end,
|
|
["GetItemCount"] = function(t)
|
|
return baseGetItemCount;
|
|
end,
|
|
["collectible"] = function(t)
|
|
return t.collectibleAsCost;
|
|
end,
|
|
["collected"] = function(t)
|
|
return t.collectedAsCost;
|
|
end,
|
|
["collectibleAsCost"] = collectibleAsCostForItem, -- These two references can get replaced/removed
|
|
["collectedAsCost"] = collectedAsCostForItem,
|
|
};
|
|
app.CreateItem = app.CreateClass("Item", "itemID", itemFields,
|
|
"AsRWP", {
|
|
collectible = function(t)
|
|
return t.collectibleAsCost or app.Settings.Collectibles.RWP;
|
|
end,
|
|
collected = function(t)
|
|
if t.collectedAsCost == false then
|
|
return;
|
|
end
|
|
return collectedAsRWP(t);
|
|
end,
|
|
}, isCollectibleRWP,
|
|
"WithFactionAndQuest", {
|
|
collectible = function(t)
|
|
return t.collectibleAsCost or collectibleAsQuest(t) or app.Settings.Collectibles.Reputations;
|
|
end,
|
|
collected = function(t)
|
|
if t.collectedAsCost == false then
|
|
return 0;
|
|
end
|
|
if not IsQuestFlaggedCompletedForObject(t) then
|
|
return 0;
|
|
end
|
|
-- This is used by reputation tokens. (turn in items)
|
|
if app.CurrentCharacter.Factions[t.factionID] then return 1; end
|
|
if app.Settings.AccountWide.Reputations and ATTAccountWideData.Factions[t.factionID] then return 2; end
|
|
end,
|
|
trackable = app.ReturnTrue,
|
|
saved = function(t)
|
|
return IsQuestFlaggedCompletedForObject(t) == 1;
|
|
end
|
|
}, (function(t) return t.factionID and t.questID; end),
|
|
"WithQuest", {
|
|
collectible = function(t)
|
|
return t.collectibleAsCost or collectibleAsQuest(t);
|
|
end,
|
|
collected = function(t)
|
|
return IsQuestFlaggedCompletedForObject(t) or t.collectedAsCost;
|
|
end,
|
|
trackable = app.ReturnTrue,
|
|
saved = function(t)
|
|
return IsQuestFlaggedCompletedForObject(t) == 1;
|
|
end
|
|
}, (function(t) return t.questID; end),
|
|
"WithFaction", {
|
|
collectible = function(t)
|
|
return t.collectibleAsCost or app.Settings.Collectibles.Reputations;
|
|
end,
|
|
collected = function(t)
|
|
if t.collectedAsCost == false then
|
|
return 0;
|
|
end
|
|
-- This is used by reputation tokens. (turn in items)
|
|
if app.CurrentCharacter.Factions[t.factionID] then return 1; end
|
|
if app.Settings.AccountWide.Reputations and ATTAccountWideData.Factions[t.factionID] then return 2; end
|
|
end,
|
|
}, (function(t) return t.factionID; end));
|
|
|
|
-- Heirloom Lib
|
|
if C_Heirloom then
|
|
-- Heirloom API is available. Awesome!
|
|
local C_Heirloom_GetHeirloomInfo = C_Heirloom.GetHeirloomInfo;
|
|
local C_Heirloom_GetHeirloomLink = C_Heirloom.GetHeirloomLink;
|
|
local C_Heirloom_PlayerHasHeirloom = C_Heirloom.PlayerHasHeirloom;
|
|
local C_Heirloom_GetHeirloomMaxUpgradeLevel = C_Heirloom.GetHeirloomMaxUpgradeLevel;
|
|
local heirloomIDs = {};
|
|
local CreateHeirloomUnlock = app.CreateClass("HeirloomUnlock", "heirloomUnlockID", {
|
|
name = function(t)
|
|
return L["HEIRLOOM_TEXT"];
|
|
end,
|
|
icon = function(t)
|
|
return app.asset("Weapon_Type_Heirloom");
|
|
end,
|
|
description = function(t)
|
|
return L["HEIRLOOM_TEXT_DESC"];
|
|
end,
|
|
collectible = function(t)
|
|
return app.Settings.Collectibles.Heirlooms;
|
|
end,
|
|
collected = function(t)
|
|
return C_Heirloom_PlayerHasHeirloom(t.heirloomUnlockID);
|
|
end,
|
|
});
|
|
|
|
-- Clone base item fields and extend the properties.
|
|
local heirloomFields = {
|
|
icon = function(t)
|
|
return select(4, C_Heirloom_GetHeirloomInfo(t.itemID)) or select(5, GetItemInfoInstant(t.itemID));
|
|
end,
|
|
link = function(t)
|
|
return C_Heirloom_GetHeirloomLink(t.itemID) or select(2, GetItemInfo(t.itemID));
|
|
end,
|
|
};
|
|
|
|
-- Are heirloom upgrades available? (6.1.0.19445)
|
|
local gameBuildVersion = app.GameBuildVersion;
|
|
if gameBuildVersion > 60100 then
|
|
-- Extend the heirloom lib to account for upgrade levels.
|
|
local armorTextures = {
|
|
"Interface/ICONS/INV_Icon_HeirloomToken_Armor01",
|
|
"Interface/ICONS/INV_Icon_HeirloomToken_Armor02",
|
|
"Interface/ICONS/Inv_leather_draenordungeon_c_01shoulder",
|
|
"Interface/ICONS/inv_mail_draenorquest90_b_01shoulder",
|
|
"Interface/ICONS/inv_leather_warfrontsalliance_c_01_shoulder",
|
|
"Interface/ICONS/inv_shoulder_armor_dragonspawn_c_02",
|
|
};
|
|
local weaponTextures = {
|
|
"Interface/ICONS/INV_Icon_HeirloomToken_Weapon01",
|
|
"Interface/ICONS/INV_Icon_HeirloomToken_Weapon02",
|
|
"Interface/ICONS/inv_weapon_shortblade_112",
|
|
"Interface/ICONS/inv_weapon_shortblade_111",
|
|
"Interface/ICONS/inv_weapon_shortblade_102",
|
|
"Interface/ICONS/inv_weapon_shortblade_84",
|
|
};
|
|
|
|
local weaponFilterIDs = { 20, 29, 28, 21, 22, 23, 24, 25, 26, 50, 57, 34, 35, 27, 33, 32, 31 };
|
|
local hierloomLevelFields = {
|
|
["key"] = function(t)
|
|
return "heirloomLevelID";
|
|
end,
|
|
["level"] = function(t)
|
|
return 1;
|
|
end,
|
|
["name"] = function(t)
|
|
t.name = sformat(HEIRLOOM_UPGRADE_TOOLTIP_FORMAT, t.level, t.levelMax);
|
|
return t.name;
|
|
end,
|
|
["icon"] = function(t)
|
|
return t.isWeapon and weaponTextures[t.level] or armorTextures[t.level];
|
|
end,
|
|
["description"] = function(t)
|
|
return L["HEIRLOOMS_UPGRADES_DESC"];
|
|
end,
|
|
["collectible"] = function(t)
|
|
return app.Settings.Collectibles.Heirlooms and app.Settings.Collectibles.HeirloomUpgrades;
|
|
end,
|
|
["collected"] = function(t)
|
|
local itemID = t.heirloomLevelID;
|
|
if itemID then
|
|
if t.level <= (ATTAccountWideData.HeirloomRanks[itemID] or 0) then return true; end
|
|
local level = select(5, C_Heirloom_GetHeirloomInfo(itemID));
|
|
if level then
|
|
ATTAccountWideData.HeirloomRanks[itemID] = level;
|
|
if t.level <= level then return true; end
|
|
end
|
|
end
|
|
end,
|
|
["trackable"] = app.ReturnTrue,
|
|
["isWeapon"] = function(t)
|
|
local isWeapon = t.f and contains(weaponFilterIDs, t.f);
|
|
t.isWeapon = isWeapon;
|
|
return isWeapon;
|
|
end,
|
|
};
|
|
local CreateHeirloomLevel = app.CreateClass("HeirloomLevel", "heirloomLevelID", hierloomLevelFields);
|
|
heirloomFields.isWeapon = hierloomLevelFields.isWeapon;
|
|
heirloomFields.saved = function(t)
|
|
return t.collected == 1;
|
|
end
|
|
|
|
-- Will retrieve all the cached entries by itemID for existing heirlooms and generate their
|
|
-- upgrade levels into the respective upgrade tokens
|
|
app.CacheHeirlooms = function()
|
|
-- app.PrintDebug("CacheHeirlooms",#heirloomIDs)
|
|
if #heirloomIDs < 1 then return; end
|
|
|
|
-- Setup upgrade tokens that contain levels for the heirlooms. Order matters.
|
|
-- Ranks 1 & 2 were added with WOD (6.1.0.19445)
|
|
local armorTokenItemIDs = {
|
|
122338, -- Rank 1: Ancient Heirloom Armor Casing
|
|
122340, -- Rank 2: Timeworn Heirloom Armor Casing
|
|
};
|
|
local weaponTokenItemIDs = {
|
|
122339, -- Rank 1: Ancient Heirloom Scabbard
|
|
122341, -- Rank 2: Timeworn Heirloom Scabbard
|
|
};
|
|
|
|
-- Rank 3 was added with Legion (7.2.5.24076)
|
|
if gameBuildVersion > 70205 then
|
|
tinsert(armorTokenItemIDs, 151614); -- Weathered Heirloom Armor Casing
|
|
tinsert(weaponTokenItemIDs, 151615); -- Weathered Heirloom Scabbard
|
|
|
|
-- Rank 4 was added with BFA (8.1.5.29701)
|
|
if gameBuildVersion > 80105 then
|
|
tinsert(armorTokenItemIDs, 167731); -- Battle-Hardened Heirloom Armor Casing
|
|
tinsert(weaponTokenItemIDs, 167732); -- Battle-Hardened Heirloom Scabbard
|
|
|
|
-- Rank 5 was added with Shadowlands (9.1.5.40871)
|
|
if gameBuildVersion > 90105 then
|
|
tinsert(armorTokenItemIDs, 187997); -- Eternal Heirloom Armor Casing
|
|
tinsert(weaponTokenItemIDs, 187998); -- Eternal Heirloom Scabbard
|
|
|
|
-- Rank 6 was added with Dragonflight (10.1.0.49407)
|
|
if gameBuildVersion > 100100 then
|
|
tinsert(armorTokenItemIDs, 204336); -- Awakened Heirloom Armor Casing
|
|
tinsert(weaponTokenItemIDs, 204337); -- Awakened Heirloom Scabbard
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Build headers that will contain each type.
|
|
local armorTokens, weaponTokens = {}, {};
|
|
for i=#armorTokenItemIDs,1,-1 do
|
|
tinsert(armorTokens, app.CreateItem(armorTokenItemIDs[i], {
|
|
collectible = false,
|
|
g = {},
|
|
}));
|
|
tinsert(weaponTokens, app.CreateItem(weaponTokenItemIDs[i], {
|
|
collectible = false,
|
|
g = {},
|
|
}));
|
|
end
|
|
|
|
|
|
-- for each cached heirloom, push a copy of itself with respective upgrade level under the respective upgrade token
|
|
local Search = app.SearchForObject;
|
|
local uniques, heirloom, upgrades = {};
|
|
for _,itemID in ipairs(heirloomIDs) do
|
|
if not uniques[itemID] then
|
|
uniques[itemID] = true;
|
|
heirloom = Search("itemID", itemID, "field");
|
|
if heirloom then
|
|
upgrades = C_Heirloom_GetHeirloomMaxUpgradeLevel(itemID);
|
|
if upgrades and upgrades > 0 then
|
|
local meta = { __index = heirloom };
|
|
local tokenType = heirloom.isWeapon and weaponTokens or armorTokens;
|
|
for i=1,upgrades,1 do
|
|
-- Create a non-collectible version of the heirloom item itself to hold the upgrade within the token
|
|
tinsert(tokenType[upgrades + 1 - i].g,
|
|
setmetatable({ collectible = false, g = {
|
|
CreateHeirloomLevel({
|
|
heirloomLevelID = itemID,
|
|
levelMax = upgrades,
|
|
level = i,
|
|
f = heirloom.f,
|
|
e = heirloom.e,
|
|
u = heirloom.u,
|
|
})
|
|
}}, meta));
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- build groups for each upgrade token
|
|
-- and copy the set of upgrades into the cached versions of the upgrade tokens so they therefore exist in the main list
|
|
-- where the sources of the upgrade tokens exist
|
|
for i,item in ipairs(armorTokens) do
|
|
for _,token in ipairs(SearchForField("itemID", item.itemID)) do
|
|
-- ensure the tokens do not have a modID attached
|
|
token.modID = nil;
|
|
token.modItemID = nil;
|
|
if not token.sym then
|
|
for _,heirloom in ipairs(item.g) do
|
|
NestObject(token, heirloom, true);
|
|
end
|
|
BuildGroups(token);
|
|
end
|
|
end
|
|
end
|
|
for i,item in ipairs(weaponTokens) do
|
|
for _,token in ipairs(SearchForField("itemID", item.itemID)) do
|
|
-- ensure the tokens do not have a modID attached
|
|
token.modID = nil;
|
|
token.modItemID = nil;
|
|
if not token.sym then
|
|
for _,heirloom in ipairs(item.g) do
|
|
NestObject(token, heirloom, true);
|
|
end
|
|
BuildGroups(token);
|
|
end
|
|
end
|
|
end
|
|
|
|
wipe(heirloomIDs);
|
|
end
|
|
end
|
|
|
|
local CreateHeirloom = app.ExtendClass("Item", "Heirloom", "itemID", heirloomFields,
|
|
"AsRWP", {
|
|
collectible = function(t)
|
|
return t.collectibleAsCost or app.Settings.Collectibles.RWP;
|
|
end,
|
|
collected = function(t)
|
|
if t.collectedAsCost == false then
|
|
return;
|
|
end
|
|
return collectedAsRWP(t);
|
|
end,
|
|
description = function()
|
|
return "This item also has an RWP sourceID with it, keep at least one somewhere on your account. I'm not sure if Blizzard is planning on deprecating this completely before transmog comes out or not!\n\n - Crieve";
|
|
end,
|
|
}, isCollectibleRWP,
|
|
"WithFaction", {
|
|
collectible = function(t)
|
|
return t.collectibleAsCost or app.Settings.Collectibles.Reputations;
|
|
end,
|
|
collected = function(t)
|
|
if t.collectedAsCost == false then
|
|
return 0;
|
|
end
|
|
if t.repeatable then
|
|
return (app.CurrentCharacter.Factions[t.factionID] and 1)
|
|
or (ATTAccountWideData.Factions[t.factionID] and 2);
|
|
else
|
|
-- This is used for the Grand Commendations unlocking Bonus Reputation
|
|
if ATTAccountWideData.FactionBonus[t.factionID] then return 1; end
|
|
if select(15, GetFactionInfoByID(t.factionID)) then
|
|
ATTAccountWideData.FactionBonus[t.factionID] = 1;
|
|
return 1;
|
|
end
|
|
end
|
|
-- This is used by reputation tokens. (turn in items)
|
|
if app.CurrentCharacter.Factions[t.factionID] then return 1; end
|
|
if app.Settings.AccountWide.Reputations and ATTAccountWideData.Factions[t.factionID] then return 2; end
|
|
end,
|
|
}, (function(t) return t.factionID; end));
|
|
app.CreateHeirloom = function(id, t)
|
|
t = CreateHeirloom(id, t);
|
|
--t.b = 2; -- Heirlooms are always BoA
|
|
|
|
-- unlocking the heirloom is the only thing contained in the heirloom
|
|
t.g = { CreateHeirloomUnlock(id, { e = t.e, u = t.u }); }
|
|
tinsert(heirloomIDs, id);
|
|
return t;
|
|
end
|
|
else
|
|
-- No difference between an item and an heirloom in classic, yet.
|
|
app.CreateHeirloom = function(id, t)
|
|
return app.CreateItem(id, t);
|
|
end
|
|
end
|
|
|
|
local fields = CloneDictionary(itemFields);
|
|
fields.collectible = function(t)
|
|
return app.Settings.Collectibles.Toys;
|
|
end
|
|
fields.collected = function(t)
|
|
return app.SetCollected(t, "Toys", t.toyID, GetItemCount(t.toyID, true) > 0);
|
|
end
|
|
fields.itemID = function(t)
|
|
return t.toyID;
|
|
end
|
|
if C_ToyBox then
|
|
-- Toy API is in!
|
|
local C_ToyBox_GetToyInfo = C_ToyBox.GetToyInfo;
|
|
local function isBNETCollectible(toyID)
|
|
if C_ToyBox_GetToyInfo(toyID) then
|
|
return true;
|
|
end
|
|
end
|
|
fields.collected = function(t)
|
|
local toyID = t.toyID;
|
|
if isBNETCollectible(toyID) then
|
|
if ATTAccountWideData.Toys[toyID] then return 1; end
|
|
return app.SetAccountCollected(t, "Toys", toyID, PlayerHasToy(toyID));
|
|
else
|
|
return app.SetCollected(t, "Toys", toyID, GetItemCount(toyID, true) > 0);
|
|
end
|
|
end;
|
|
fields.description = function(t)
|
|
if not isBNETCollectible(t.toyID) then
|
|
return "This is not a Toy as classified by Blizzard, but it is something that SHOULD be a Toy! Keep this in your inventory somewhere on an alt until Blizzard fixes it.";
|
|
end
|
|
end;
|
|
fields.isBNETCollectible = function(t)
|
|
return isBNETCollectible(t.toyID);
|
|
end
|
|
|
|
app.events.TOYS_UPDATED = function(toyID, new)
|
|
if toyID then
|
|
app.SetAccountCollected(app.SearchForField("toyID", toyID)[1] or app.CreateToy(toyID), "Toys", toyID, PlayerHasToy(toyID));
|
|
app:RefreshDataQuietly("TOYS_UPDATED", true);
|
|
end
|
|
end
|
|
tinsert(app.EventHandlers.OnReady, function()
|
|
app:RegisterEvent("TOYS_UPDATED");
|
|
end);
|
|
end
|
|
app.CreateToy = app.CreateClass("Toy", "toyID", fields);
|
|
|
|
local HarvestedItemDatabase = {};
|
|
local itemHarvesterFields = CloneDictionary(itemFields);
|
|
itemHarvesterFields.collectible = function(t)
|
|
return true;
|
|
end
|
|
itemHarvesterFields.collected = app.ReturnFalse;
|
|
itemHarvesterFields.text = function(t)
|
|
local link = t.link;
|
|
if link then
|
|
local itemName, itemLink, itemQuality, itemLevel, itemMinLevel, itemType, itemSubType, itemStackCount,
|
|
itemEquipLoc, itemTexture, sellPrice, classID, subclassID, bindType, expacID, setID, isCraftingReagent
|
|
= GetItemInfo(link);
|
|
if itemName then
|
|
local spellName, spellID;
|
|
if classID == "Recipe" or classID == "Mount" then
|
|
spellName, spellID = GetItemSpell(t.itemID);
|
|
if spellName == "Learning" then spellID = nil; end -- RIP.
|
|
end
|
|
--setmetatable(t, t.conditions[2]);
|
|
local info = {
|
|
["name"] = itemName,
|
|
["itemID"] = t.itemID,
|
|
["equippable"] = itemEquipLoc and itemEquipLoc ~= "" and true or false,
|
|
["class"] = classID,
|
|
["subclass"] = subclassID,
|
|
["inventoryType"] = C_Item.GetItemInventoryTypeByID(t.itemID),
|
|
["b"] = bindType,
|
|
["q"] = itemQuality,
|
|
["iLvl"] = itemLevel,
|
|
["spellID"] = spellID,
|
|
};
|
|
if itemMinLevel and itemMinLevel > 1 then
|
|
info.lvl = itemMinLevel;
|
|
end
|
|
if info.inventoryType == 0 then
|
|
info.inventoryType = nil;
|
|
end
|
|
if info.b and info.b ~= 1 then
|
|
info.b = nil;
|
|
end
|
|
if info.q and info.q < 1 then
|
|
info.q = nil;
|
|
end
|
|
if info.iLvl and info.iLvl < 2 then
|
|
info.iLvl = nil;
|
|
end
|
|
t.itemType = itemType;
|
|
t.itemSubType = itemSubType;
|
|
t.info = info;
|
|
t.retries = nil;
|
|
HarvestedItemDatabase[t.itemID] = info;
|
|
AllTheThingsAD.HarvestedItemDatabase = HarvestedItemDatabase;
|
|
return link;
|
|
end
|
|
end
|
|
|
|
t.retries = (t.retries or 0) + 1;
|
|
if t.retries > 30 then
|
|
rawset(t, "collected", true);
|
|
end
|
|
end
|
|
local ItemHarvester = CreateFrame("GameTooltip", "ATTCItemHarvester", UIParent, "GameTooltipTemplate");
|
|
app.CreateItemHarvester = app.CreateClass("ItemHarvester", "itemID", itemHarvesterFields,
|
|
"TooltipHarvester", {
|
|
text = function(t)
|
|
local link = t.link;
|
|
if link then
|
|
ItemHarvester:SetOwner(UIParent,"ANCHOR_NONE")
|
|
ItemHarvester:SetHyperlink(link);
|
|
local lineCount = ItemHarvester:NumLines();
|
|
if ATTCItemHarvesterTextLeft1:GetText() and ATTCItemHarvesterTextLeft1:GetText() ~= RETRIEVING_DATA and lineCount > 0 then
|
|
local requirements = {};
|
|
for index=2,lineCount,1 do
|
|
local line = _G["ATTCItemHarvesterTextLeft" .. index] or _G["ATTCItemHarvesterText" .. index];
|
|
if line then
|
|
local text = line:GetText();
|
|
if text then
|
|
if string.find(text, "Classes: ") then
|
|
local classes = {};
|
|
local _,list = strsplit(":", text);
|
|
for i,s in ipairs({strsplit(",", list)}) do
|
|
tinsert(classes, app.ClassDB[strtrim(s)]);
|
|
end
|
|
if #classes > 0 then
|
|
t.info.classes = classes;
|
|
end
|
|
elseif string.find(text, "Races: ") then
|
|
local races = {};
|
|
local _,list = strsplit(":", text);
|
|
for i,s in ipairs({strsplit(",", list)}) do
|
|
tinsert(races, app.RaceDB[strtrim(s)]);
|
|
end
|
|
if #races > 0 then
|
|
t.info.races = races;
|
|
end
|
|
elseif string.find(text, "Requires") and not string.find(text, "Level") and not string.find(text, "Riding") then
|
|
local c = strsub(text, 1, 1);
|
|
if c ~= " " and c ~= "\t" and c ~= "\n" and c ~= "\r" then
|
|
text = strsub(strtrim(text), 9);
|
|
if string.find(text, "-") then
|
|
local faction,replevel = strsplit("-", text);
|
|
t.info.minReputation = { app.GetFactionIDByName(faction), app.GetFactionStandingThresholdFromString(replevel) };
|
|
else
|
|
if string.find(text, "%(") then
|
|
if t.info.requireSkill then
|
|
-- If non-specialization skill is already assigned, skip this part.
|
|
text = nil;
|
|
else
|
|
text = strsplit("(", text);
|
|
end
|
|
end
|
|
if text then
|
|
local spellName = strtrim(text);
|
|
if spellName == "Herbalism" then spellName = "Herb Gathering"; end
|
|
local spellID = app.SpellNameToSpellID[spellName];
|
|
if spellID then
|
|
local skillID = app.SpellIDToSkillID[spellID];
|
|
if skillID then
|
|
t.info.requireSkill = skillID;
|
|
else
|
|
print("Unknown Skill '" .. spellName .. "'");
|
|
tinsert(requirements, spellName);
|
|
end
|
|
else
|
|
print("Unknown Spell '" .. spellName .. "'");
|
|
tinsert(requirements, spellName);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if #requirements > 0 then
|
|
t.info.otherRequirements = requirements;
|
|
end
|
|
rawset(t, "text", link);
|
|
rawset(t, "collected", true);
|
|
end
|
|
ItemHarvester:Hide();
|
|
return link;
|
|
end
|
|
end
|
|
}, app.ReturnFalse);
|
|
end)();
|
|
|
|
-- Map Lib
|
|
(function()
|
|
local C_Map_GetMapArtID = C_Map.GetMapArtID;
|
|
local C_Map_GetMapLevels = C_Map.GetMapLevels;
|
|
local C_Map_GetBestMapForUnit = C_Map.GetBestMapForUnit;
|
|
local C_MapExplorationInfo_GetExploredMapTextures = C_MapExplorationInfo.GetExploredMapTextures;
|
|
local C_MapExplorationInfo_GetExploredAreaIDsAtPosition = C_MapExplorationInfo.GetExploredAreaIDsAtPosition;
|
|
local mapIDToMapName, mapIDToAreaID = {}, {
|
|
[427] = { 132, 800 }, -- Coldridge Valley, Coldridge Pass
|
|
[465] = { 154 }, -- Deathknell
|
|
[425] = { 9, 59, 24, 34 }, -- Northshire Valley, Northshire Vineyards, Northshire Abbey, Echo Ridge Mine
|
|
[460] = { 188 }, -- Shadowglen
|
|
[462] = { 221 }, -- Camp Narache
|
|
[467] = { 3431 }, -- Sunstrider Isle
|
|
[468] = { 3526, 3527, 3560, 3528, 3559, 3529, 3530, 3561 }, -- Ammen Vale, Crash Site, Ammen Fields, Silverline Lake, Nestlewood Hills, Nestlewood Thicket, Shadow Ridge, The Sacred Grove
|
|
[348] = { 4095 }, -- Magisters' Terrace
|
|
};
|
|
for mapID,area in pairs(mapIDToAreaID) do
|
|
local info = C_Map_GetAreaInfo(area[1]);
|
|
if info then
|
|
mapIDToMapName[mapID] = info;
|
|
L.ZONE_TEXT_TO_MAP_ID[info] = mapID;
|
|
if #area > 1 then
|
|
for i=2,#area,1 do
|
|
local info = C_Map_GetAreaInfo(area[i]);
|
|
if info then L.ALT_ZONE_TEXT_TO_MAP_ID[info] = mapID; end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
app.GetCurrentMapID = function()
|
|
local originalMapID = C_Map_GetBestMapForUnit("player");
|
|
local substitutions = L.QUEST_ID_TO_MAP_ID[originalMapID];
|
|
if substitutions then
|
|
for questID,mapID in pairs(substitutions) do
|
|
if not C_QuestLog_IsQuestFlaggedCompleted(questID) then
|
|
return mapID;
|
|
end
|
|
end
|
|
end
|
|
local zoneTextSubstitution = L.MAP_ID_TO_ZONE_TEXT[originalMapID];
|
|
if zoneTextSubstitution then
|
|
local zone = GetRealZoneText();
|
|
if zone then
|
|
if zoneTextSubstitution == zone then return originalMapID; end
|
|
local mapID = L.ZONE_TEXT_TO_MAP_ID[zone] or L.ALT_ZONE_TEXT_TO_MAP_ID[zone];
|
|
if mapID then
|
|
return mapID;
|
|
end
|
|
end
|
|
zone = GetSubZoneText();
|
|
if zone and zone ~= "" then
|
|
if zoneTextSubstitution == zone then return originalMapID; end
|
|
local mapID = L.ZONE_TEXT_TO_MAP_ID[zone] or L.ALT_ZONE_TEXT_TO_MAP_ID[zone];
|
|
if mapID then return mapID; end
|
|
end
|
|
else
|
|
local zone = GetRealZoneText();
|
|
if zone then
|
|
local mapID = L.ZONE_TEXT_TO_MAP_ID[zone] or L.ALT_ZONE_TEXT_TO_MAP_ID[zone];
|
|
if mapID then
|
|
return mapID;
|
|
end
|
|
end
|
|
zone = GetSubZoneText();
|
|
if zone and zone ~= "" then
|
|
local mapID = L.ZONE_TEXT_TO_MAP_ID[zone] or L.ALT_ZONE_TEXT_TO_MAP_ID[zone];
|
|
if mapID then return mapID; end
|
|
end
|
|
end
|
|
return originalMapID;
|
|
end
|
|
app.GetMapName = function(mapID)
|
|
if mapID then
|
|
local zoneTextSubstitution = L.MAP_ID_TO_ZONE_TEXT[mapID];
|
|
if zoneTextSubstitution then return zoneTextSubstitution; end
|
|
if mapIDToMapName[mapID] then return mapIDToMapName[mapID]; end
|
|
|
|
local info = C_Map_GetMapInfo(mapID);
|
|
return (info and info.name) or ("Map ID #" .. mapID);
|
|
else
|
|
return "Map ID #???";
|
|
end
|
|
end
|
|
|
|
app.CreateExploration = app.CreateClass("Exploration", "explorationID", {
|
|
["text"] = function(t)
|
|
return C_Map_GetAreaInfo(t.explorationID) or RETRIEVING_DATA;
|
|
end,
|
|
["title"] = function(t)
|
|
return t.maphash;
|
|
end,
|
|
["description"] = function(t)
|
|
if not TomTom then
|
|
return "You can use Alt+Right Click to plot the coordinates with TomTom installed. If this refuses to be marked collected for you in ATT, try reloading your UI or relogging.";
|
|
else
|
|
return "You can use Alt+Right Click to plot the coordinates. If this refuses to be marked collected for you in ATT, try reloading your UI or relogging.";
|
|
end
|
|
end,
|
|
["preview"] = function(t)
|
|
local exploredMapTextureInfo = t.exploredMapTextureInfo;
|
|
if exploredMapTextureInfo then
|
|
local texture = exploredMapTextureInfo.fileDataIDs[1];
|
|
if texture then
|
|
rawset(t, "preview", texture);
|
|
return texture;
|
|
end
|
|
end
|
|
end,
|
|
["artID"] = function(t)
|
|
return t.parent and (t.parent.artID or (t.parent.parent and t.parent.parent.artID));
|
|
end,
|
|
["icon"] = function(t)
|
|
return app.asset("Category_Exploration");
|
|
end,
|
|
["mapID"] = function(t)
|
|
return t.parent and (t.parent.mapID or (t.parent.parent and t.parent.parent.mapID));
|
|
end,
|
|
["collectible"] = function(t)
|
|
return app.Settings.Collectibles.Exploration;
|
|
end,
|
|
["collected"] = function(t)
|
|
if app.CurrentCharacter.Exploration[t.explorationID] then return 1; end
|
|
if app.Settings.AccountWide.Exploration and ATTAccountWideData.Exploration[t.explorationID] then return 2; end
|
|
|
|
local maphash = t.maphash;
|
|
if maphash then
|
|
local exploredMapTextures = C_MapExplorationInfo_GetExploredMapTextures(t.mapID)
|
|
if exploredMapTextures then
|
|
for i,info in ipairs(exploredMapTextures) do
|
|
local hash = info.textureWidth..":"..info.textureHeight..":"..info.offsetX..":"..info.offsetY;
|
|
if hash == maphash then return app.SetCollected(nil, "Exploration", t.explorationID, true); end
|
|
end
|
|
--[[
|
|
if not app.MAPHASHTHING then app.MAPHASHTHING = {}; end
|
|
if not app.MAPHASHTHING[t.explorationID] then
|
|
app.MAPHASHTHING[t.explorationID] = true;
|
|
print("Failed to detect maphash '" .. maphash .. "' on map " .. t.mapID .. ".");
|
|
end
|
|
]]--
|
|
end
|
|
end
|
|
--[[
|
|
if not app.MAPTHING then app.MAPTHING = {}; end
|
|
local exploredMapTextures = C_MapExplorationInfo_GetExploredMapTextures(t.mapID);
|
|
if not app.MAPTHING[t.mapID] and exploredMapTextures then
|
|
app.MAPTHING[t.mapID] = true;
|
|
local hashes = {};
|
|
for i,o in ipairs(t.parent.g) do
|
|
if o.maphash then hashes[o.maphash] = o; end
|
|
end
|
|
for i,info in ipairs(exploredMapTextures) do
|
|
local hash = info.textureWidth..":"..info.textureHeight..":"..info.offsetX..":"..info.offsetY;
|
|
if hash and not hashes[hash] then
|
|
print("Failed to find areaID for maphash '" .. hash .. "' on map " .. t.mapID .. ".");
|
|
end
|
|
end
|
|
end
|
|
local coords = t.coords;
|
|
if coords and #coords > 0 then
|
|
local c = coords[1];
|
|
local explored = C_MapExplorationInfo_GetExploredAreaIDsAtPosition(c[2], CreateVector2D(c[1] / 100, c[2] / 100, c[2]));
|
|
if explored then
|
|
for _,areaID in ipairs(explored) do
|
|
if areaID == t.explorationID then
|
|
app.SetCollected(nil, "Exploration", areaID, true);
|
|
return 1;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
]]--
|
|
end,
|
|
["coords"] = function(t)
|
|
local coords = app.ExplorationAreaPositionDB[t.explorationID];
|
|
if not coords then
|
|
local maphash = t.maphash;
|
|
if maphash then
|
|
local coords = {};
|
|
local width, height, offsetX, offsetY = strsplit(":", maphash);
|
|
tinsert(coords, {((offsetX + (width * 0.5)) * 100) / WorldMapFrame:GetWidth(), ((offsetY + (height * 0.5)) * 100) / WorldMapFrame:GetHeight(), t.mapID});
|
|
return coords;
|
|
end
|
|
end
|
|
return coords;
|
|
end,
|
|
["exploredMapTextureInfo"] = function(t)
|
|
local maphash = t.maphash;
|
|
if maphash then
|
|
local exploredMapTextures = C_MapExplorationInfo_GetExploredMapTextures(t.mapID)
|
|
if exploredMapTextures then
|
|
for i,info in ipairs(exploredMapTextures) do
|
|
local hash = info.textureWidth..":"..info.textureHeight..":"..info.offsetX..":"..info.offsetY;
|
|
if hash == maphash then
|
|
rawset(t, "exploredMapTextureInfo", info);
|
|
return info;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
});
|
|
|
|
local ExplorationGrid = {};
|
|
local levelOfDetail = -1;--200;
|
|
for i=0,levelOfDetail,1 do
|
|
for j=0,levelOfDetail,1 do
|
|
tinsert(ExplorationGrid, CreateVector2D(i / levelOfDetail, j / levelOfDetail));
|
|
end
|
|
end
|
|
|
|
local DiscoveredNewArea = {};
|
|
local simplifyExplorationData = function()
|
|
local i = 100;
|
|
while InCombatLockdown() do coroutine.yield(); end
|
|
while i > 0 do i = i - 1; coroutine.yield(); end
|
|
app.print("Simplifying Exploration Data...");
|
|
local allMapData = {};
|
|
local explorationDB = {};
|
|
local explorationAreaPositionDB = {};
|
|
for areaID,coords in pairs(app.ExplorationAreaPositionDB) do
|
|
for i,coord in ipairs(coords) do
|
|
local mapID = coord[3];
|
|
if mapID then
|
|
local x, y = math.floor(coord[1] * 100), math.floor(coord[2] * 100);
|
|
local hash = x .. ":" .. y;
|
|
local mapData = allMapData[mapID];
|
|
if not mapData then
|
|
mapData = {};
|
|
mapData.areas = {};
|
|
mapData.areaList = {};
|
|
mapData.hits = {};
|
|
allMapData[mapID] = mapData;
|
|
explorationDB[mapID] = mapData.areaList;
|
|
end
|
|
if not mapData.areas[areaID] then
|
|
mapData.areas[areaID] = 1;
|
|
tinsert(mapData.areaList, areaID);
|
|
end
|
|
if not mapData.hits[hash] then
|
|
mapData.hits[hash] = { areaID };
|
|
else
|
|
tinsert(mapData.hits[hash], areaID);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
app.print("Determining best coordinates for areas...");
|
|
coroutine.yield();
|
|
local sortMethod = function(a, b)
|
|
return a[1] > b[1];
|
|
end;
|
|
for mapID,mapData in pairs(allMapData) do
|
|
app.print("Determining best coordinates for map ".. mapID);
|
|
local hitsByAreaID, hitsByCount = {}, {};
|
|
for i,areaID in ipairs(mapData.areaList) do
|
|
hitsByAreaID[areaID] = {};
|
|
end
|
|
for hash,hits in pairs(mapData.hits) do
|
|
tinsert(hitsByCount, { #hits, hash, hits});
|
|
end
|
|
app.Sort(hitsByCount, sortMethod);
|
|
coroutine.yield();
|
|
|
|
-- Now randomly grab hashes until every area has a few hashes
|
|
while #hitsByCount > 0 do
|
|
local index = math.random(#hitsByCount);
|
|
local hit = hitsByCount[index];
|
|
tremove(hitsByCount, index);
|
|
for i,areaID in ipairs(hit[3]) do
|
|
local hits = hitsByAreaID[areaID];
|
|
if #hits < 10 then tinsert(hits, hit[2]); end
|
|
end
|
|
end
|
|
|
|
-- Now that each has some hashes (probably), let's simplfy that data table.
|
|
for areaID,hits in pairs(hitsByAreaID) do
|
|
local positions = explorationAreaPositionDB[areaID];
|
|
if not positions then
|
|
positions = {};
|
|
explorationAreaPositionDB[areaID] = positions;
|
|
end
|
|
for i,hash in ipairs(hits) do
|
|
local x, y = hash:match("(%d+):(%d+)");
|
|
tinsert(positions, { tonumber(x) * 0.01, tonumber(y) * 0.01, mapID });
|
|
end
|
|
end
|
|
end
|
|
AllTheThingsAD.ExplorationDB = explorationDB;
|
|
AllTheThingsAD.ExplorationAreaPositionDB = explorationAreaPositionDB;
|
|
app.ExplorationDB = explorationDB;
|
|
app.ExplorationAreaPositionDB = explorationAreaPositionDB;
|
|
app.print("Done Simplifying Exploration Data.");
|
|
end
|
|
local onMapUpdate = function(t)
|
|
local explorationByAreaID = {};
|
|
local explorationHeader = nil;
|
|
local coordinates = {};
|
|
if t.g then
|
|
for i,o in ipairs(t.g) do
|
|
if o.key == "headerID" and o.headerID == app.HeaderConstants.EXPLORATION then
|
|
explorationHeader = o;
|
|
if o.g then
|
|
for j,e in ipairs(o.g) do
|
|
explorationByAreaID[e.explorationID] = e;
|
|
if e.coords and #e.coords > 0 then
|
|
tinsert(coordinates, e.coords[1]);
|
|
else
|
|
--print("Missing Coordinates for areaID", e.explorationID);
|
|
end
|
|
end
|
|
end
|
|
break;
|
|
end
|
|
end
|
|
end
|
|
|
|
local id = t.mapID;
|
|
local newExplorationObjects = {};
|
|
local areaIDs = app.ExplorationDB[id];
|
|
for _,pos in ipairs(coordinates) do
|
|
local explored = C_MapExplorationInfo_GetExploredAreaIDsAtPosition(pos[3] or id, CreateVector2D(pos[1] / 100, pos[2] / 100));
|
|
if explored then
|
|
for _,areaID in ipairs(explored) do
|
|
app.SetCollected(nil, "Exploration", areaID, true);
|
|
local o = explorationByAreaID[areaID];
|
|
if not o and not DiscoveredNewArea[areaID] and #SearchForField("explorationID", areaID) < 1 then
|
|
DiscoveredNewArea[areaID] = true;
|
|
o = app.CreateExploration(areaID);
|
|
explorationByAreaID[areaID] = o;
|
|
tinsert(newExplorationObjects, o);
|
|
print("Found New AreaID:", id, t.text, areaID, o.text);
|
|
tinsert(areaIDs, areaID);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
for _,pos in ipairs(ExplorationGrid) do
|
|
local explored = C_MapExplorationInfo_GetExploredAreaIDsAtPosition(id, pos);
|
|
if explored then
|
|
for _,areaID in ipairs(explored) do
|
|
app.SetCollected(nil, "Exploration", areaID, true);
|
|
local o = explorationByAreaID[areaID];
|
|
if not o and not DiscoveredNewArea[areaID] then
|
|
DiscoveredNewArea[areaID] = true;
|
|
o = app.CreateExploration(areaID);
|
|
explorationByAreaID[areaID] = o;
|
|
tinsert(newExplorationObjects, o);
|
|
print("Found New AreaID:", id, t.text, areaID, o.text);
|
|
tinsert(areaIDs, areaID);
|
|
end
|
|
local coords = app.ExplorationAreaPositionDB[areaID];
|
|
if not coords then
|
|
coords = {};
|
|
app.ExplorationAreaPositionDB[areaID] = coords;
|
|
local missing = AllTheThingsAD.ExplorationAreaPositionDB;
|
|
if not missing then
|
|
missing = {};
|
|
--AllTheThingsAD.ExplorationAreaPositionDB = missing;
|
|
end
|
|
missing[areaID] = coords;
|
|
end
|
|
tinsert(coords, {pos.x * 100, pos.y * 100, id});
|
|
end
|
|
end
|
|
end
|
|
if #newExplorationObjects > 0 then
|
|
if explorationHeader then
|
|
if not explorationHeader.g then
|
|
explorationHeader.g = {};
|
|
end
|
|
for i,o in ipairs(newExplorationObjects) do
|
|
tinsert(explorationHeader.g, o);
|
|
o.parent = explorationHeader;
|
|
end
|
|
else
|
|
explorationHeader = app.CreateNPC(app.HeaderConstants.EXPLORATION, newExplorationObjects);
|
|
explorationHeader.u = t.u;
|
|
for i,o in ipairs(newExplorationObjects) do
|
|
o.parent = explorationHeader;
|
|
if not o.u then o.u = t.u; end
|
|
end
|
|
explorationHeader.parent = t;
|
|
tinsert(t.g, 1, explorationHeader);
|
|
end
|
|
end
|
|
if explorationHeader and explorationHeader.g then
|
|
app.Sort(explorationHeader.g, app.SortDefaults.Text);
|
|
end
|
|
rawset(t, "OnUpdate", nil);
|
|
--app:StartATTCoroutine("Simplifying Exploration Data", simplifyExplorationData);
|
|
end;
|
|
app.SortExplorationDB = function()
|
|
local e,t=ATTC.ExplorationDB,{};
|
|
for mapID,areas in pairs(e) do
|
|
local s = {};
|
|
t[mapID] = s;
|
|
for i,areaID in ipairs(areas) do
|
|
tinsert(s, { areaID, C_Map_GetAreaInfo(areaID) });
|
|
end
|
|
app.Sort(s, function(a, b)
|
|
return a[2] < b[2];
|
|
end);
|
|
end
|
|
AllTheThingsAD.NamedExplorationDB = t;
|
|
end
|
|
local createMap, mapClass = app.CreateClass("Map", "mapID", {
|
|
["text"] = function(t)
|
|
return t.isRaid and ("|c" .. app.Colors.Raid .. t.name .. "|r") or t.name;
|
|
end,
|
|
["name"] = function(t)
|
|
return t.headerID and app.NPCNameFromID[t.headerID] or app.GetMapName(t.mapID);
|
|
end,
|
|
["icon"] = function(t)
|
|
return t.headerID and L["HEADER_ICONS"][t.headerID] or app.asset("Category_Zones");
|
|
end,
|
|
["lore"] = function(t)
|
|
return t.headerID and L["HEADER_LORE"][t.headerID];
|
|
end,
|
|
["description"] = function(t)
|
|
return t.headerID and L["HEADER_DESCRIPTIONS"][t.headerID];
|
|
end,
|
|
["back"] = function(t)
|
|
if app.CurrentMapID == t.mapID or (t.maps and contains(t.maps, app.CurrentMapID)) then
|
|
return 1;
|
|
end
|
|
end,
|
|
["artID"] = function(t)
|
|
return C_Map_GetMapArtID(t.mapID);
|
|
end,
|
|
["lvl"] = function(t)
|
|
return C_Map_GetMapLevels(t.mapID);
|
|
end,
|
|
});
|
|
app.BaseMap = mapClass;
|
|
app.CreateMap = function(id, t)
|
|
local t = createMap(id, t);
|
|
local artID = t.artID;
|
|
if artID and t.g then
|
|
local explorationByAreaID = {};
|
|
local explorationHeader = nil;
|
|
for i,o in ipairs(t.g) do
|
|
if o.headerID == app.HeaderConstants.EXPLORATION then
|
|
explorationHeader = o;
|
|
if o.g then
|
|
for j,e in ipairs(o.g) do
|
|
local explorationID = e.explorationID;
|
|
if explorationID then
|
|
explorationByAreaID[explorationID] = e;
|
|
end
|
|
end
|
|
end
|
|
break;
|
|
end
|
|
end
|
|
|
|
local newExplorationObjects = {};
|
|
local areaIDs = app.ExplorationDB[id];
|
|
if not areaIDs then
|
|
areaIDs = {};
|
|
app.ExplorationDB[id] = areaIDs;
|
|
--AllTheThingsAD.ExplorationDB = ATTC.ExplorationDB;
|
|
else
|
|
for _,areaID in ipairs(areaIDs) do
|
|
if not explorationByAreaID[areaID] then
|
|
o = app.CreateExploration(areaID);
|
|
explorationByAreaID[areaID] = o;
|
|
tinsert(newExplorationObjects, o);
|
|
end
|
|
end
|
|
end
|
|
if #newExplorationObjects > 0 then
|
|
if explorationHeader then
|
|
if not explorationHeader.g then
|
|
explorationHeader.g = {};
|
|
end
|
|
for i,o in ipairs(newExplorationObjects) do
|
|
tinsert(explorationHeader.g, o);
|
|
o.parent = explorationHeader;
|
|
end
|
|
else
|
|
explorationHeader = app.CreateNPC(app.HeaderConstants.EXPLORATION, newExplorationObjects);
|
|
explorationHeader.u = t.u;
|
|
for i,o in ipairs(newExplorationObjects) do
|
|
o.parent = explorationHeader;
|
|
if not o.u then o.u = t.u; end
|
|
end
|
|
explorationHeader.parent = t;
|
|
tinsert(t.g, 1, explorationHeader);
|
|
end
|
|
end
|
|
if explorationHeader and explorationHeader.g then
|
|
app.Sort(explorationHeader.g, app.SortDefaults.Text);
|
|
end
|
|
if not rawget(t, "OnUpdate") then
|
|
t.OnUpdate = onMapUpdate;
|
|
end
|
|
end
|
|
if t.creatureID and t.creatureID < 0 then
|
|
t.headerID = t.creatureID;
|
|
t.creatureID = nil;
|
|
end
|
|
return t;
|
|
end
|
|
app.CreateInstance = app.CreateClass("Instance", "instanceID", {
|
|
["text"] = function(t)
|
|
return t.isRaid and ("|c" .. app.Colors.Raid .. t.name .. "|r") or t.name;
|
|
end,
|
|
["name"] = function(t)
|
|
return t.headerID and app.NPCNameFromID[t.headerID] or app.GetMapName(t.mapID);
|
|
end,
|
|
["icon"] = function(t)
|
|
return t.headerID and L["HEADER_ICONS"][t.headerID] or app.asset("Category_Zones");
|
|
end,
|
|
["back"] = function(t)
|
|
if app.CurrentMapID == t.mapID or (t.maps and contains(t.maps, app.CurrentMapID)) then
|
|
return 1;
|
|
end
|
|
end,
|
|
["mapID"] = function(t)
|
|
return t.maps and t.maps[1];
|
|
end,
|
|
["lvl"] = function(t)
|
|
return C_Map_GetMapLevels(t.mapID);
|
|
end,
|
|
["locks"] = function(t)
|
|
local lockouts = app.CurrentCharacter.Lockouts;
|
|
local locks = lockouts[t.name];
|
|
if locks then
|
|
t.locks = locks;
|
|
return locks;
|
|
end
|
|
local sins = t.sins;
|
|
if sins then
|
|
for i=1,#sins,1 do
|
|
locks = lockouts[sins[i]];
|
|
if locks then
|
|
t.locks = locks;
|
|
return locks;
|
|
end
|
|
end
|
|
end
|
|
local areaID = t["zone-text-areaID"];
|
|
if areaID then
|
|
local name = C_Map_GetAreaInfo(areaID);
|
|
if name then
|
|
locks = lockouts[name];
|
|
if locks then
|
|
t.locks = locks;
|
|
return locks;
|
|
end
|
|
end
|
|
end
|
|
end,
|
|
["saved"] = function(t)
|
|
return t.locks;
|
|
end,
|
|
},
|
|
"WithCreature", {}, (function(t)
|
|
if t.creatureID and t.creatureID < 0 then
|
|
t.headerID = t.creatureID;
|
|
t.creatureID = nil;
|
|
return true;
|
|
end
|
|
end));
|
|
|
|
app:RegisterEvent("MAP_EXPLORATION_UPDATED");
|
|
app:RegisterEvent("UI_INFO_MESSAGE");
|
|
app.events.MAP_EXPLORATION_UPDATED = function(...)
|
|
app.CurrentMapID = app.GetCurrentMapID();
|
|
app:StartATTCoroutine("RefreshExploration", function()
|
|
coroutine.yield();
|
|
local mapID = app.CurrentMapID;
|
|
while not mapID do
|
|
coroutine.yield();
|
|
mapID = app.CurrentMapID;
|
|
end
|
|
local pos = C_Map_GetPlayerMapPosition(mapID, "player");
|
|
if pos then
|
|
local px, py = pos:GetXY();
|
|
px, py = px * 100, py * 100;
|
|
local explored = C_MapExplorationInfo_GetExploredAreaIDsAtPosition(app.CurrentMapID, pos);
|
|
if explored then
|
|
local newArea = false;
|
|
for _,areaID in ipairs(explored) do
|
|
if not app.CurrentCharacter.Exploration[areaID] then
|
|
app.SetCollected(nil, "Exploration", areaID, true);
|
|
newArea = true;
|
|
if not app.ExplorationAreaPositionDB[areaID] then
|
|
local coord = {px, py, app.CurrentMapID};
|
|
print("New Coordinate: ", C_Map_GetAreaInfo(areaID), coord);
|
|
app.ExplorationAreaPositionDB[areaID] = { coord };
|
|
end
|
|
end
|
|
end
|
|
if newArea then app:RefreshDataQuietly("RefreshExploration", true); end
|
|
end
|
|
end
|
|
end);
|
|
end
|
|
app.events.UI_INFO_MESSAGE = function(messageID)
|
|
if messageID == 372 then
|
|
app.events.MAP_EXPLORATION_UPDATED();
|
|
end
|
|
end
|
|
|
|
local function UpdateMap()
|
|
app.CurrentMapID = app.GetCurrentMapID();
|
|
end
|
|
app:RegisterEvent("ZONE_CHANGED");
|
|
app:RegisterEvent("ZONE_CHANGED_INDOORS");
|
|
app:RegisterEvent("ZONE_CHANGED_NEW_AREA");
|
|
app.events.ZONE_CHANGED = UpdateMap;
|
|
app.events.ZONE_CHANGED_INDOORS = UpdateMap;
|
|
app.events.ZONE_CHANGED_NEW_AREA = UpdateMap;
|
|
UpdateMap();
|
|
end)();
|
|
|
|
-- NPC Lib
|
|
(function()
|
|
-- NPC Model Harvester (also acquires the displayID)
|
|
local npcModelHarvester = CreateFrame("DressUpModel", nil, UIParent);
|
|
npcModelHarvester:SetPoint("TOPRIGHT", UIParent, "BOTTOMRIGHT", 0, 0);
|
|
npcModelHarvester:SetSize(1, 1);
|
|
npcModelHarvester:Hide();
|
|
local NPCDisplayIDFromID = setmetatable({}, { __index = function(t, id)
|
|
if id > 0 then
|
|
npcModelHarvester:SetDisplayInfo(0);
|
|
npcModelHarvester:SetUnit("none");
|
|
npcModelHarvester:SetCreature(id);
|
|
local displayID = npcModelHarvester:GetDisplayInfo();
|
|
if displayID and displayID ~= 0 then
|
|
rawset(t, id, displayID);
|
|
app:RedrawWindows("NPCDisplayIDFromID");
|
|
return displayID;
|
|
end
|
|
end
|
|
end});
|
|
app.NPCDisplayIDFromID = NPCDisplayIDFromID;
|
|
|
|
-- NPC & Title Name Harvesting Lib (https://us.battle.net/forums/en/wow/topic/20758497390?page=1#post-4, Thanks Gello!)
|
|
local NPCNameFromID, NPCTitlesFromID = {},{};
|
|
local C_TooltipInfo_GetHyperlink = C_TooltipInfo and C_TooltipInfo.GetHyperlink;
|
|
if C_TooltipInfo_GetHyperlink then
|
|
setmetatable(NPCNameFromID, { __index = function(t, id)
|
|
if id > 0 then
|
|
local tooltipData = C_TooltipInfo_GetHyperlink(format("unit:Creature-0-0-0-0-%d-0000000000",id));
|
|
if tooltipData then
|
|
local title = tooltipData.lines[1].leftText;
|
|
if title and #tooltipData.lines > 2 then
|
|
NPCTitlesFromID[id] = tooltipData.lines[2].leftText;
|
|
end
|
|
if title and title ~= RETRIEVING_DATA then
|
|
t[id] = title;
|
|
return title;
|
|
end
|
|
end
|
|
else
|
|
return L.HEADER_NAMES[id];
|
|
end
|
|
end});
|
|
else
|
|
local ATTCNPCHarvester = CreateFrame("GameTooltip", "ATTCNPCHarvester", UIParent, "GameTooltipTemplate");
|
|
setmetatable(NPCNameFromID, { __index = function(t, id)
|
|
if id > 0 then
|
|
ATTCNPCHarvester:SetOwner(UIParent,"ANCHOR_NONE")
|
|
ATTCNPCHarvester:SetHyperlink(format("unit:Creature-0-0-0-0-%d-0000000000",id))
|
|
local title = ATTCNPCHarvesterTextLeft1:GetText();
|
|
if title and ATTCNPCHarvester:NumLines() > 2 then
|
|
NPCTitlesFromID[id] = ATTCNPCHarvesterTextLeft2:GetText();
|
|
end
|
|
ATTCNPCHarvester:Hide();
|
|
if title and title ~= RETRIEVING_DATA then
|
|
t[id] = title;
|
|
return title;
|
|
end
|
|
else
|
|
return L.HEADER_NAMES[id];
|
|
end
|
|
end});
|
|
end
|
|
app.NPCNameFromID = NPCNameFromID;
|
|
app.NPCTitlesFromID = NPCTitlesFromID;
|
|
|
|
-- Event, Header, and NPC Lib
|
|
local createNPC = app.CreateClass("NPC", "npcID", {
|
|
["key"] = function(t)
|
|
return "npcID";
|
|
end,
|
|
["text"] = function(t)
|
|
return t.isRaid and ("|c" .. app.Colors.Raid .. t.name .. "|r") or t.name;
|
|
end,
|
|
["name"] = function(t)
|
|
return NPCNameFromID[t.npcID] or RETRIEVING_DATA;
|
|
end,
|
|
["icon"] = function(t)
|
|
return (t.parent and t.parent.headerID == app.HeaderConstants.VENDORS and "Interface\\Icons\\INV_Misc_Coin_01")
|
|
or app.DifficultyIcons[GetRelativeValue(t, "difficultyID") or 1];
|
|
end,
|
|
["title"] = function(t)
|
|
return NPCTitlesFromID[t.npcID];
|
|
end,
|
|
["displayID"] = function(t)
|
|
return app.NPCDisplayIDFromID[t.npcID];
|
|
end,
|
|
["creatureID"] = function(t) -- TODO: Do something about this, it's silly.
|
|
return t.npcID;
|
|
end,
|
|
},
|
|
"WithQuest", {
|
|
collectible = function(t)
|
|
return app.Settings.Collectibles.Quests and (not t.repeatable and not t.isBreadcrumb or C_QuestLog_IsOnQuest(t.questID));
|
|
end,
|
|
collected = function(t)
|
|
return IsQuestFlaggedCompletedForObject(t);
|
|
end,
|
|
trackable = app.ReturnTrue,
|
|
saved = function(t)
|
|
return IsQuestFlaggedCompletedForObject(t) == 1;
|
|
end
|
|
}, (function(t) return t.questID; end));
|
|
local createCustomHeader = app.CreateClass("Header", "headerID", {
|
|
["text"] = function(t)
|
|
return t.isRaid and ("|c" .. app.Colors.Raid .. t.name .. "|r") or t.name;
|
|
end,
|
|
["name"] = function(t)
|
|
return L["HEADER_NAMES"][t.headerID];
|
|
end,
|
|
["icon"] = function(t)
|
|
return L["HEADER_ICONS"][t.headerID] or app.asset("Category_Zones");
|
|
end,
|
|
["description"] = function(t)
|
|
return L["HEADER_DESCRIPTIONS"][t.headerID];
|
|
end,
|
|
["lore"] = function(t)
|
|
return L["HEADER_LORE"][t.headerID];
|
|
end,
|
|
["saved"] = function(t)
|
|
return IsQuestFlaggedCompletedForObject(t) == 1;
|
|
end,
|
|
["trackable"] = function(t)
|
|
return t.questID;
|
|
end,
|
|
},
|
|
"WithEvent", app.Modules.Events.Fields, (function(t) return L.HEADER_EVENTS[t.headerID]; end));
|
|
app.CreateCustomHeader = createCustomHeader;
|
|
app.CreateNPC = function(id, t)
|
|
if not id then
|
|
print("Broken ID for CreateNPC");
|
|
t = {};
|
|
if t[1] then
|
|
t = { g = t };
|
|
end
|
|
t.OnUpdate = function()
|
|
print("HEY! FIX THIS", BuildSourceTextForDynamicPath(t, 0));
|
|
print(t.progress, t.total, t.g and #t.g);
|
|
|
|
end
|
|
id = 0;
|
|
end
|
|
if id < 1 then
|
|
if t and t.npcID == id then t.npcID = nil; end
|
|
return createCustomHeader(id, t);
|
|
else
|
|
return createNPC(id, t);
|
|
end
|
|
end
|
|
|
|
-- Automatic Headers
|
|
local HeaderTypeAbbreviations = {
|
|
["a"] = "achievementID",
|
|
["c"] = "classID",
|
|
["m"] = "mapID",
|
|
["n"] = "npcID",
|
|
["i"] = "itemID",
|
|
["q"] = "questID",
|
|
["s"] = "spellID",
|
|
};
|
|
-- Alternate functions to attach data into a table based on an id for a given type code
|
|
local AlternateDataTypes = {
|
|
["ac"] = function(id)
|
|
return { text = GetCategoryInfo(id) };
|
|
end,
|
|
["crit"] = function(id)
|
|
local ach = math.floor(id);
|
|
local crit = math.floor(100 * (id - ach) + 0.005);
|
|
return { text = GetAchievementCriteriaInfo(ach, crit) };
|
|
end,
|
|
["d"] = function(id)
|
|
local name, _, _, _, _, _, _, _, _, _, textureFilename = GetLFGDungeonInfo(id);
|
|
return { text = name, icon = textureFilename };
|
|
end,
|
|
["df"] = function(id)
|
|
local aid = math.floor(id);
|
|
local hid = math.floor(10000 * (id - aid) + 0.005);
|
|
id = app.FactionID == Enum.FlightPathFaction.Alliance and tonumber(aid) or tonumber(hid);
|
|
local name, _, _, _, _, _, _, _, _, _, textureFilename = GetLFGDungeonInfo(id);
|
|
return { text = name, icon = textureFilename };
|
|
end,
|
|
["_G"] = function(id)
|
|
return { ["text"] = _G[id] };
|
|
end,
|
|
};
|
|
app.CreateHeader = app.CreateClass("AutomaticHeader", "autoID", {
|
|
["text"] = function(t)
|
|
return t.isRaid and ("|c" .. app.Colors.Raid .. t.name .. "|r") or t.name;
|
|
end,
|
|
["name"] = function(t)
|
|
return t.result.name or t.result.text;
|
|
end,
|
|
["icon"] = function(t)
|
|
return t.result.icon;
|
|
end,
|
|
["result"] = function(t)
|
|
local typ = HeaderTypeAbbreviations[t.type];
|
|
if typ then
|
|
local cache = SearchForField(typ, t.autoID);
|
|
if #cache > 0 then
|
|
t.result = cache[1];
|
|
return cache[1];
|
|
else
|
|
cache = CreateObject({[typ] = t.autoID});
|
|
t.result = cache;
|
|
return cache;
|
|
end
|
|
else
|
|
local cache = AlternateDataTypes[t.type];
|
|
if cache then
|
|
cache = cache(t.autoID);
|
|
if cache then
|
|
t.result = cache;
|
|
return cache;
|
|
end
|
|
end
|
|
end
|
|
print("Unhandled Header Type", t.type, t.autoID, typ);
|
|
return app.EmptyTable;
|
|
end,
|
|
},
|
|
"WithQuest", {
|
|
collectible = function(t)
|
|
return app.Settings.Collectibles.Quests and (not t.repeatable and not t.isBreadcrumb or C_QuestLog_IsOnQuest(t.questID));
|
|
end,
|
|
collected = function(t)
|
|
return IsQuestFlaggedCompletedForObject(t);
|
|
end,
|
|
trackable = app.ReturnTrue,
|
|
saved = function(t)
|
|
return IsQuestFlaggedCompletedForObject(t) == 1;
|
|
end
|
|
}, (function(t) return t.questID; end));
|
|
end)();
|
|
|
|
-- Object Lib (as in "World Object")
|
|
(function()
|
|
app.CreateObject = app.CreateClass("Object", "objectID", {
|
|
["text"] = function(t)
|
|
return t.isRaid and ("|c" .. app.Colors.Raid .. t.name .. "|r") or t.name;
|
|
end,
|
|
["name"] = function(t)
|
|
if t.providers then
|
|
for k,v in ipairs(t.providers) do
|
|
if v[2] > 0 then
|
|
if v[1] == "o" then
|
|
return app.ObjectNames[v[2]] or RETRIEVING_DATA;
|
|
elseif v[1] == "i" then
|
|
return GetItemInfo(v[2]) or RETRIEVING_DATA;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return app.ObjectNames[t.objectID] or ("Object ID #" .. t.objectID);
|
|
end,
|
|
["icon"] = function(t)
|
|
if t.providers then
|
|
for k,v in ipairs(t.providers) do
|
|
if v[2] > 0 then
|
|
if v[1] == "o" then
|
|
local icon = app.ObjectIcons[v[2]];
|
|
if icon then return icon; end
|
|
elseif v[1] == "i" then
|
|
return select(5, GetItemInfoInstant(v[2])) or "Interface\\Icons\\INV_Misc_Bag_10";
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return app.ObjectIcons[t.objectID] or "Interface\\Icons\\INV_Misc_Bag_10";
|
|
end,
|
|
["model"] = function(t)
|
|
return app.ObjectModels[t.objectID];
|
|
end,
|
|
},
|
|
"WithQuest", {
|
|
collectible = function(t)
|
|
return app.Settings.Collectibles.Quests and (not t.repeatable and not t.isBreadcrumb or C_QuestLog_IsOnQuest(t.questID));
|
|
end,
|
|
collected = function(t)
|
|
return IsQuestFlaggedCompletedForObject(t);
|
|
end,
|
|
trackable = app.ReturnTrue,
|
|
saved = function(t)
|
|
return IsQuestFlaggedCompletedForObject(t) == 1;
|
|
end
|
|
}, (function(t) return t.questID; end));
|
|
end)();
|
|
|
|
-- Profession Lib
|
|
(function()
|
|
app.SkillIDToSpellID = setmetatable({
|
|
[171] = 2259, -- Alchemy
|
|
[794] = 158762, -- Arch
|
|
[261] = 5149, -- Beast Training
|
|
[164] = 2018, -- Blacksmithing
|
|
[185] = 2550, -- Cooking
|
|
[333] = 7411, -- Enchanting
|
|
[202] = 4036, -- Engineering
|
|
[356] = 7620, -- Fishing
|
|
[129] = 3273, -- First Aid
|
|
[182] = 2366, -- Herb Gathering
|
|
[773] = 45357, -- Inscription
|
|
[755] = 25229, -- Jewelcrafting
|
|
[165] = 2108, -- Leatherworking
|
|
[186] = 2575, -- Mining
|
|
[393] = 8613, -- Skinning
|
|
[197] = 3908, -- Tailoring
|
|
[960] = 53428, -- Runeforging
|
|
[40] = 2842, -- Poisons
|
|
[633] = 1809, -- Lockpicking
|
|
[921] = 921, -- Pickpocketing
|
|
|
|
-- Riding
|
|
[762] = 33388, -- Riding
|
|
|
|
-- Specializations
|
|
[20219] = 20219, -- Gnomish Engineering
|
|
[20222] = 20222, -- Goblin Engineering
|
|
[9788] = 9788, -- Armorsmith
|
|
[9787] = 9787, -- Weaponsmith
|
|
[17041] = 17041, -- Master Axesmith
|
|
[17040] = 17040, -- Master Hammersmith
|
|
[17039] = 17039, -- Master Swordsmith
|
|
[10656] = 10656, -- Dragonscale Leatherworking
|
|
[10658] = 10658, -- Elemental Leatherworking
|
|
[10660] = 10660, -- Tribal Leatherworking
|
|
[26801] = 26801, -- Shadoweave Tailoring
|
|
[26797] = 26797, -- Spellfire Tailoring
|
|
[26798] = 26798, -- Mooncloth Tailoring
|
|
[125589] = 125589, -- Way of the Brew
|
|
[124694] = 124694, -- Way of the Grill
|
|
[125588] = 125588, -- Way of the Oven
|
|
[125586] = 125586, -- Way of the Pot
|
|
[125587] = 125587, -- Way of the Steamer
|
|
[125584] = 125584, -- Way of the Wok
|
|
}, {__index = function(t,k) return k; end});
|
|
app.SpellIDToSkillID = {};
|
|
for skillID,spellID in pairs(app.SkillIDToSpellID) do
|
|
app.SpellIDToSkillID[spellID] = skillID;
|
|
end
|
|
app.SpecializationSpellIDs = setmetatable({
|
|
[20219] = 4036, -- Gnomish Engineering
|
|
[20222] = 4036, -- Goblin Engineering
|
|
[9788] = 2018, -- Armorsmith
|
|
[9787] = 2018, -- Weaponsmith
|
|
[17041] = 2018, -- Master Axesmith
|
|
[17040] = 2018, -- Master Hammersmith
|
|
[17039] = 2018, -- Master Swordsmith
|
|
[10656] = 2108, -- Dragonscale Leatherworking
|
|
[10658] = 2108, -- Elemental Leatherworking
|
|
[10660] = 2108, -- Tribal Leatherworking
|
|
[26801] = 3908, -- Shadoweave Tailoring
|
|
[26797] = 3908, -- Spellfire Tailoring
|
|
[26798] = 3908, -- Mooncloth Tailoring
|
|
[125589] = 2550,-- Way of the Brew
|
|
[124694] = 2550,-- Way of the Grill
|
|
[125588] = 2550,-- Way of the Oven
|
|
[125586] = 2550,-- Way of the Pot
|
|
[125587] = 2550,-- Way of the Steamer
|
|
[125584] = 2550,-- Way of the Wok
|
|
}, {__index = function(t,k) return k; end})
|
|
|
|
local BLACKSMITHING = ATTC.SkillIDToSpellID[164];
|
|
local LEATHERWORKING = ATTC.SkillIDToSpellID[165];
|
|
local TAILORING = ATTC.SkillIDToSpellID[197];
|
|
app.OnUpdateForOmarionsHandbook = function(t)
|
|
t.visible = true;
|
|
t.collectible = nil;
|
|
if app.Settings:Get("DebugMode") or app.Settings:Get("AccountMode") or CompletedQuests[9233] or C_QuestLog_IsOnQuest(9233) then
|
|
return false;
|
|
else
|
|
for spellID,skills in pairs(app.CurrentCharacter.ActiveSkills) do
|
|
if (spellID == BLACKSMITHING or spellID == LEATHERWORKING or spellID == TAILORING) and skills[1] > 270 then
|
|
t.collectible = false;
|
|
t.visible = false;
|
|
return true;
|
|
end
|
|
end
|
|
end
|
|
end;
|
|
app.CreateProfession = app.CreateClass("Profession", "professionID", {
|
|
["text"] = function(t)
|
|
return GetSpellInfo(t.spellID);
|
|
end,
|
|
["icon"] = function(t)
|
|
return select(3, GetSpellInfo(t.spellID));
|
|
end,
|
|
["spellID"] = function(t)
|
|
return app.SkillIDToSpellID[t.professionID];
|
|
end,
|
|
["requireSkill"] = function(t)
|
|
return t.professionID;
|
|
end,
|
|
["sym"] = function(t)
|
|
return {{"selectprofession", t.professionID}};
|
|
end
|
|
});
|
|
end)();
|
|
|
|
-- Quest Lib
|
|
(function()
|
|
local C_QuestLog_GetQuestInfo = C_QuestLog.GetTitleForQuestID or C_QuestLog.GetQuestInfo;
|
|
local C_QuestLog_GetQuestObjectives = C_QuestLog.GetQuestObjectives;
|
|
local GetQuestLogIndexByID = C_QuestLog.GetLogIndexForQuestID or GetQuestLogIndexByID;
|
|
local questRetries = {};
|
|
local QuestTitleFromID = setmetatable({}, { __index = function(t, id)
|
|
local title = C_QuestLog_GetQuestInfo(id);
|
|
if title and title ~= RETRIEVING_DATA then
|
|
rawset(questRetries, id, nil);
|
|
rawset(t, id, title);
|
|
return title;
|
|
else
|
|
local retries = rawget(questRetries, id);
|
|
if retries and retries > 120 then
|
|
title = "Quest #" .. id .. "*";
|
|
rawset(questRetries, id, nil);
|
|
rawset(t, id, title);
|
|
return title;
|
|
else
|
|
rawset(questRetries, id, (retries or 0) + 1);
|
|
end
|
|
end
|
|
end })
|
|
local IgnoreErrorQuests = {
|
|
[1476]=1, -- Hearts of the Pure (Horde Pre-req for the Undercity Succubus Binding quest)
|
|
[1474]=1, -- The Binding (Succubus) [Undercity]
|
|
[1508]=1, -- Blind Cazul (Horde Pre-req for the Orgrimmar Succubus Binding quest)
|
|
[1509]=1, -- News of Dogran (1/2) (Horde Pre-req for the Orgrimmar Succubus Binding quest)
|
|
[1510]=1, -- News of Dogran (2/2) (Horde Pre-req for the Orgrimmar Succubus Binding quest)
|
|
[1511]=1, -- Ken'zigla's Draught (Horde Pre-req for the Orgrimmar Succubus Binding quest)
|
|
[1515]=1, -- Dogran's Captivity (Horde Pre-req for the Orgrimmar Succubus Binding quest)
|
|
[1512]=1, -- Love's Gift (Horde Pre-req for the Orgrimmar Succubus Binding quest)
|
|
[1513]=1, -- The Binding (Succubus) [Orgrimmar]
|
|
[1738]=1, -- Heartswood (Alliance Pre-req for the Stormwind City Succubus Binding quest)
|
|
[1739]=1, -- The Binding (Succubus) [Stormwind City]
|
|
[1516]=1, -- Call of Earth (1/3 Durotar)
|
|
[1519]=1, -- Call of Earth (1/3 Mulgore)
|
|
[9449]=1, -- Call of Earth (1/3 Ammen Vale)
|
|
[555]=1, -- Soothing Turtle Bisque (A)
|
|
[7321]=1, -- Soothing Turtle Bisque (H)
|
|
[3630]=1, -- Gnome Engineering [A]
|
|
[3632]=1, -- Gnome Engineering [A]
|
|
[3634]=1, -- Gnome Engineering [H]
|
|
[3635]=1, -- Gnome Engineering [H]
|
|
[3637]=1, -- Gnome Engineering [H]
|
|
[3526]=1, -- Goblin Engineering [H]
|
|
[3629]=1, -- Goblin Engineering [A]
|
|
[3633]=1, -- Goblin Engineering [H]
|
|
[4181]=1, -- Goblin Engineering [A]
|
|
[5517]=1, -- Chromatic Mantle of the Dawn
|
|
[5521]=1, -- Chromatic Mantle of the Dawn
|
|
[5524]=1, -- Chromatic Mantle of the Dawn
|
|
[5504]=1, -- Mantles of the Dawn
|
|
[5507]=1, -- Mantles of the Dawn
|
|
[5513]=1, -- Mantles of the Dawn
|
|
[7170]=1, -- Earned Reverence (Alliance)
|
|
[7165]=1, -- Earned Reverence (Horde)
|
|
[7171]=1, -- Legendary Heroes (Alliance)
|
|
[7166]=1, -- Legendary Heroes (Horde)
|
|
[7168]=1, -- Rise and Be Recognized (Alliance)
|
|
[7163]=1, -- Rise and Be Recognized (Horde)
|
|
[7172]=1, -- The Eye of Command (Alliance)
|
|
[7167]=1, -- The Eye of Command (Horde)
|
|
[7164]=1, -- Honored Amongst the Clan
|
|
[7169]=1, -- Honored Amongst the Guard
|
|
[8870]=1, -- The Lunar Festival
|
|
[8871]=1, -- The Lunar Festival
|
|
[8872]=1, -- The Lunar Festival
|
|
[8873]=1, -- The Lunar Festival
|
|
[8874]=1, -- The Lunar Festival
|
|
[8875]=1, -- The Lunar Festival
|
|
[8700]=1, -- Band of Unending Life
|
|
[8692]=1, -- Cloak of Unending Life
|
|
[8708]=1, -- Mace of Unending Life
|
|
[8704]=1, -- Signet of the Unseen Path
|
|
[8696]=1, -- Cloak of the Unseen Path
|
|
[8712]=1, -- Scythe of the Unseen Path
|
|
[8699]=1, -- Band of Vaulted Secrets
|
|
[8691]=1, -- Drape of Vaulted Secrets
|
|
[8707]=1, -- Blade of Vaulted Secrets
|
|
[8703]=1, -- Ring of Eternal Justice
|
|
[8695]=1, -- Cape of Eternal Justice
|
|
[8711]=1, -- Blade of Eternal Justice
|
|
[8697]=1, -- Ring of Infinite Wisdom
|
|
[8689]=1, -- Shroud of Infinite Wisdom
|
|
[8705]=1, -- Gavel of Infinite Wisdom
|
|
[8701]=1, -- Band of Veiled Shadows
|
|
[8693]=1, -- Cloak of Veiled Shadows
|
|
[8709]=1, -- Dagger of Veiled Shadows
|
|
[8698]=1, -- Ring of the Gathering Storm
|
|
[8690]=1, -- Cloak of the Gathering Storm
|
|
[8706]=1, -- Hammer of the Gathering Storm
|
|
[8702]=1, -- Ring of Unspoken Names
|
|
[8694]=1, -- Shroud of Unspoken Names
|
|
[8710]=1, -- Kris of Unspoken Names
|
|
[8556]=1, -- Signet of Unyielding Strength
|
|
[8557]=1, -- Drape of Unyielding Strength
|
|
[8558]=1, -- Sickle of Unyielding Strength
|
|
[9520]=1, -- Diabolical Plans [Alliance]
|
|
[9535]=1, -- Diabolical Plans [Horde]
|
|
[9522]=1, -- Never Again! [Alliance]
|
|
[9536]=1, -- Never Again! [Horde]
|
|
[10371]=1, -- Yorus Barleybrew (Draenei)
|
|
[10621]=1, -- Illidari Bane-Shard (A)
|
|
[10623]=1, -- Illidari Bane-Shard (H)
|
|
[10759]=1, -- Find the Deserter (A)
|
|
[10761]=1, -- Find the Deserter (H)
|
|
[11185]=1, -- The Apothecary's Letter
|
|
[11186]=1, -- Signs of Treachery?
|
|
[11201]=1, -- The Grimtotem Plot
|
|
[11123]=1, -- Inspecting the Ruins [Alliance]
|
|
[11124]=1, -- Inspecting the Ruins [Horde]
|
|
[11150]=1, -- Raze Direhorn Post! [Alliance]
|
|
[11205]=1, -- Raze Direhorn Post! [Horde]
|
|
[11215]=1, -- Help Mudsprocket
|
|
};
|
|
setmetatable(CompletedQuests, {__newindex = function (t, key, value)
|
|
if value then
|
|
rawset(t, key, value);
|
|
rawset(DirtyQuests, key, true);
|
|
app.SetCollected(nil, "Quests", key, true);
|
|
if app.Settings:GetTooltipSetting("Report:CompletedQuests") then
|
|
local searchResults = SearchForField("questID", key);
|
|
if #searchResults > 0 then
|
|
local questID, nmr, nmc, text = key, false, false, "";
|
|
for i,searchResult in ipairs(searchResults) do
|
|
if searchResult.key == "questID" and not IgnoreErrorQuests[questID] and not GetRelativeField(searchResult, "headerID", app.HeaderConstants.TIER_ZERO_POINT_FIVE_SETS) then
|
|
if searchResult.nmr and not nmr then
|
|
nmr = true;
|
|
text = searchResult.text;
|
|
end
|
|
if searchResult.nmc and not nmc then
|
|
nmc = true;
|
|
text = searchResult.text;
|
|
end
|
|
end
|
|
end
|
|
if app.Settings:GetTooltipSetting("Report:UnsortedQuests") then
|
|
return true;
|
|
end
|
|
if nmc then key = key .. " [C]"; end
|
|
if nmr then key = key .. " [R]"; end
|
|
key = key .. " (" .. (text or RETRIEVING_DATA) .. ")";
|
|
else
|
|
local text = C_QuestLog_GetQuestInfo(key) or RETRIEVING_DATA;
|
|
key = key .. " [M] (" .. text .. ")";
|
|
end
|
|
print("Completed Quest #" .. key);
|
|
end
|
|
end
|
|
end});
|
|
app.GetQuestName = function(questID)
|
|
return QuestTitleFromID[questID];
|
|
end
|
|
|
|
local criteriaFuncs = {
|
|
["achID"] = function(achievementID)
|
|
return app.CurrentCharacter.Achievements[achievementID];
|
|
end,
|
|
["lvl"] = function(v)
|
|
return app.Level >= v;
|
|
end,
|
|
["questID"] = function(questID)
|
|
return IsQuestFlaggedCompleted(questID);
|
|
end,
|
|
["spellID"] = function(spellID)
|
|
return app.CurrentCharacter.Spells[spellID] or app.CurrentCharacter.ActiveSkills[spellID];
|
|
end,
|
|
["factionID"] = function(v)
|
|
-- v = factionID.standingRequiredToLock
|
|
local factionID = math.floor(v + 0.00001);
|
|
local lockStanding = math.floor((v - factionID) * 10 + 0.00001);
|
|
local standing = select(3, GetFactionInfoByID(factionID)) or 4;
|
|
--app.print("Check Faction", factionID, "Standing (", standing, ") is locked @ (", lockStanding, ")");
|
|
return standing >= lockStanding;
|
|
end,
|
|
};
|
|
local OnUpdateForLockCriteria = function(t)
|
|
local lockCriteria = t.lc;
|
|
if lockCriteria then
|
|
local criteriaRequired = lockCriteria[1];
|
|
local critKey, critFunc, nonQuestLock;
|
|
for i=2,#lockCriteria,2 do
|
|
critKey = lockCriteria[i];
|
|
critFunc = criteriaFuncs[critKey];
|
|
if critFunc then
|
|
if critFunc(lockCriteria[i + 1]) then
|
|
if not nonQuestLock and critKey ~= "questID" then
|
|
nonQuestLock = true;
|
|
end
|
|
criteriaRequired = criteriaRequired - 1;
|
|
if criteriaRequired <= 0 then
|
|
t.locked = true;
|
|
-- if this was locked due to something other than a Quest specifically, indicate it cannot be done in Party Sync
|
|
if nonQuestLock then
|
|
-- app.PrintDebug("Automatic DisablePartySync", app:Linkify(questID, app.Colors.ChatLink, "search:questID:" .. questID))
|
|
t.DisablePartySync = true;
|
|
end
|
|
break;
|
|
end
|
|
end
|
|
else
|
|
app.print("Unknown 'lockCriteria' key:", critKey, lockCriteria[i + 1]);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
local createQuest = app.CreateClass("Quest", "questID", {
|
|
["text"] = function(t)
|
|
if t.repeatable then return "|cff0070DD" .. t.name .. "|r"; end
|
|
return t.name;
|
|
end,
|
|
["name"] = function(t)
|
|
return QuestTitleFromID[t.questID] or (t.npcID and app.NPCNameFromID[t.npcID]) or RETRIEVING_DATA;
|
|
end,
|
|
["icon"] = function(t)
|
|
if t.providers then
|
|
for k,v in ipairs(t.providers) do
|
|
if v[2] > 0 then
|
|
if v[1] == "o" then
|
|
local icon = app.ObjectIcons[v[2]];
|
|
if icon then return icon; end
|
|
elseif v[1] == "i" then
|
|
return select(5, GetItemInfoInstant(v[2])) or "Interface\\Icons\\INV_Misc_Book_09";
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if t.isWorldQuest then
|
|
return app.asset("Interface_WorldQuest");
|
|
elseif t.repeatable then
|
|
return app.asset("Interface_Questd");
|
|
else
|
|
return app.asset("Interface_Quest");
|
|
end
|
|
end,
|
|
["model"] = function(t)
|
|
if t.providers then
|
|
for k,v in ipairs(t.providers) do
|
|
if v[2] > 0 then
|
|
if v[1] == "o" then
|
|
return app.ObjectModels[v[2]];
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end,
|
|
["link"] = function(t)
|
|
if t.questID then return "[" .. t.name .. " (".. t.questID .. ")]"; end
|
|
end,
|
|
["collectible"] = function(t)
|
|
if app.Settings.Collectibles.Quests then
|
|
if C_QuestLog_IsOnQuest(t.questID) then
|
|
return true;
|
|
end
|
|
if t.locked then return app.Settings.AccountWide.Quests; end
|
|
return not t.repeatable and not t.isBreadcrumb;
|
|
end
|
|
end,
|
|
["collected"] = function(t)
|
|
if C_QuestLog_IsOnQuest(t.questID) then
|
|
return false;
|
|
end
|
|
return IsQuestFlaggedCompletedForObject(t);
|
|
end,
|
|
["trackable"] = app.ReturnTrue,
|
|
["saved"] = function(t)
|
|
return IsQuestFlaggedCompletedForObject(t) == 1;
|
|
end,
|
|
},
|
|
"SetupLockCriteria", nil, (function(t)
|
|
if t.lc then
|
|
if t.OnUpdate then
|
|
print("BRUH ON UPDATE WITH LOCK CRITERIA QUEST ID #", t.questID);
|
|
else
|
|
t.OnUpdate = OnUpdateForLockCriteria;
|
|
end
|
|
end
|
|
end),
|
|
"WithReputation", {
|
|
collectible = function(t)
|
|
if app.Settings.Collectibles.Quests then
|
|
if C_QuestLog_IsOnQuest(t.questID) then
|
|
return true;
|
|
end
|
|
if t.locked then return app.Settings.AccountWide.Quests; end
|
|
if t.maxReputation and app.Settings.Collectibles.Reputations then
|
|
return true;
|
|
end
|
|
return not t.repeatable and not t.isBreadcrumb;
|
|
end
|
|
end,
|
|
collected = function(t)
|
|
if C_QuestLog_IsOnQuest(t.questID) then
|
|
return false;
|
|
end
|
|
local flag = IsQuestFlaggedCompletedForObject(t);
|
|
if flag then
|
|
return flag;
|
|
end
|
|
if t.maxReputation then
|
|
if (select(6, GetFactionInfoByID(t.maxReputation[1])) or 0) >= t.maxReputation[2] then
|
|
return t.repeatable and 1 or 2;
|
|
end
|
|
if app.Settings.AccountWide.Reputations then
|
|
local searchResults = SearchForField("factionID", t.maxReputation[1]);
|
|
if #searchResults > 0 then
|
|
for i,searchResult in ipairs(searchResults) do
|
|
if searchResult.key ~= "questID" and searchResult.collected then
|
|
return 2;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
}, (function(t) return t.maxReputation; end),
|
|
"AsBreadcrumb", {
|
|
collectible = function(t)
|
|
if app.Settings.Collectibles.Quests then
|
|
if C_QuestLog_IsOnQuest(t.questID) or IsQuestFlaggedCompletedForObject(t) then
|
|
return true;
|
|
end
|
|
local results = SearchForField("sourceQuestID", t.questID);
|
|
if #results > 0 then
|
|
for i,o in ipairs(results) do
|
|
if o.collectible and not o.collected then
|
|
return true;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return false;
|
|
end,
|
|
collected = function(t)
|
|
return IsQuestFlaggedCompletedForObject(t);
|
|
end,
|
|
text = function(t)
|
|
return "|cffcbc3e3" .. t.name .. "|r";
|
|
end
|
|
}, (function(t) return t.isBreadcrumb; end));
|
|
app.CreateQuest = createQuest;
|
|
app.CreateQuestWithFactionData = function(t)
|
|
local aqd, hqd = t.aqd, t.hqd;
|
|
local questData, otherQuestData;
|
|
if app.FactionID == HORDE_FACTION_ID then
|
|
questData = hqd;
|
|
otherQuestData = aqd;
|
|
else
|
|
questData = aqd;
|
|
otherQuestData = hqd;
|
|
end
|
|
|
|
-- Move over the quest data's groups.
|
|
if questData.g then
|
|
if not t.g then
|
|
t.g = questData.g;
|
|
else
|
|
for _,o in ipairs(questData.g) do
|
|
tinsert(t.g, 1, o);
|
|
end
|
|
end
|
|
questData.g = nil;
|
|
end
|
|
if otherQuestData.g then
|
|
for _,o in ipairs(otherQuestData.g) do
|
|
o.parent = otherQuestData;
|
|
end
|
|
end
|
|
|
|
-- Apply this quest's current data into the other faction's quest. (this is for tooltip caching and source quest resolution)
|
|
for key,value in pairs(t) do
|
|
if key ~= "g" then
|
|
otherQuestData[key] = value;
|
|
end
|
|
end
|
|
|
|
-- Apply the faction specific quest data to this object.
|
|
for key,value in pairs(questData) do t[key] = value; end
|
|
aqd.r = ALLIANCE_FACTION_ID;
|
|
hqd.r = HORDE_FACTION_ID;
|
|
t.otherQuestData = otherQuestData;
|
|
otherQuestData.nmr = 1;
|
|
return createQuest(t.questID, t);
|
|
end
|
|
app.CreateQuestObjective = app.CreateClass("Objective", "objectiveID", {
|
|
["text"] = function(t)
|
|
return t.name;
|
|
end,
|
|
["name"] = function(t)
|
|
local questID = t.questID;
|
|
if questID then
|
|
local objectives = C_QuestLog_GetQuestObjectives(questID);
|
|
if objectives then
|
|
local objective = objectives[t.objectiveID];
|
|
if objective then
|
|
return objective.text;
|
|
end
|
|
end
|
|
if t.providers then
|
|
for k,v in ipairs(t.providers) do
|
|
if v[2] > 0 then
|
|
if v[1] == "o" then
|
|
return app.ObjectNames[v[2]] or RETRIEVING_DATA;
|
|
elseif v[1] == "i" then
|
|
return GetItemInfo(v[2]) or RETRIEVING_DATA;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if t.spellID then return GetSpellInfo(t.spellID); end
|
|
return RETRIEVING_DATA;
|
|
end
|
|
return "INVALID: Must be relative to a Quest Object.";
|
|
end,
|
|
["icon"] = function(t)
|
|
if t.providers then
|
|
for k,v in ipairs(t.providers) do
|
|
if v[2] > 0 then
|
|
if v[1] == "o" then
|
|
local icon = app.ObjectIcons[v[2]];
|
|
if icon then return icon; end
|
|
elseif v[1] == "i" then
|
|
return select(5, GetItemInfoInstant(v[2])) or "Interface\\Worldmap\\Gear_64Grey";
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if t.spellID then return select(3, GetSpellInfo(t.spellID)); end
|
|
return t.parent.icon or "Interface\\Worldmap\\Gear_64Grey";
|
|
end,
|
|
["model"] = function(t)
|
|
if t.providers then
|
|
for k,v in ipairs(t.providers) do
|
|
if v[2] > 0 then
|
|
if v[1] == "o" then
|
|
return app.ObjectModels[v[2]];
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end,
|
|
["hash"] = function(t)
|
|
return "ob:" .. t.objectiveID .. ":" .. (t.questID or 0);
|
|
end,
|
|
["objectiveID"] = function(t)
|
|
return 1;
|
|
end,
|
|
["questID"] = function(t)
|
|
return t.parent.questID;
|
|
end,
|
|
["isDaily"] = function(t)
|
|
return t.parent.isDaily;
|
|
end,
|
|
["isWeekly"] = function(t)
|
|
return t.parent.isWeekly;
|
|
end,
|
|
["isMonthly"] = function(t)
|
|
return t.parent.isMonthly;
|
|
end,
|
|
["isYearly"] = function(t)
|
|
return t.parent.isYearly;
|
|
end,
|
|
["isWorldQuest"] = function(t)
|
|
return t.parent.isWorldQuest;
|
|
end,
|
|
["repeatable"] = function(t)
|
|
return t.parent.repeatable;
|
|
end,
|
|
["collectible"] = function(t)
|
|
if not t.questID then
|
|
return false;
|
|
end
|
|
return app.Settings.Collectibles.Quests and C_QuestLog_IsOnQuest(t.questID);
|
|
end,
|
|
["trackable"] = function(t)
|
|
if not t.questID then
|
|
return false;
|
|
end
|
|
return C_QuestLog_IsOnQuest(t.questID);
|
|
end,
|
|
["collected"] = function(t)
|
|
-- If the parent is collected, return immediately.
|
|
local collected = t.parent.collected;
|
|
if collected then return collected; end
|
|
|
|
-- Check to see if the objective was completed.
|
|
local questID = t.questID;
|
|
if questID then
|
|
-- If the player isn't on that quest, return.
|
|
local index = GetQuestLogIndexByID(questID);
|
|
if index == 0 then return 0; end
|
|
|
|
-- If the player completed the quest, return.
|
|
if select(6, GetQuestLogTitle(index)) then return 1; end
|
|
|
|
local objectives = C_QuestLog_GetQuestObjectives(questID);
|
|
if objectives then
|
|
local objective = objectives[t.objectiveID];
|
|
if objective then
|
|
return objective.finished and 1;
|
|
end
|
|
end
|
|
end
|
|
end,
|
|
["saved"] = function(t)
|
|
-- If the parent is saved, return immediately.
|
|
local saved = t.parent.saved;
|
|
if saved then return saved; end
|
|
|
|
-- Check to see if the objective was completed.
|
|
local questID = t.questID;
|
|
if questID then
|
|
local objectives = C_QuestLog_GetQuestObjectives(questID);
|
|
if objectives then
|
|
local objective = objectives[t.objectiveID];
|
|
if objective then
|
|
return objective.finished and 1;
|
|
end
|
|
end
|
|
end
|
|
end,
|
|
["objectiveCost"] = function(t)
|
|
-- This is only used to calculate how many things are required for an objective when its using a provider.
|
|
local questID = t.questID;
|
|
if questID then
|
|
-- If the player isn't on that quest, return.
|
|
local index = GetQuestLogIndexByID(questID);
|
|
if index == 0 then return 0; end
|
|
|
|
-- If the player completed the quest, return.
|
|
if select(6, GetQuestLogTitle(index)) then return 0; end
|
|
|
|
local objectives = C_QuestLog_GetQuestObjectives(questID);
|
|
if objectives then
|
|
local objective = objectives[t.objectiveID];
|
|
if objective then
|
|
if objective.finished then
|
|
return 0;
|
|
end
|
|
if questID == 14107 then
|
|
print(t.text, objective.numRequired);
|
|
end
|
|
return objective.numRequired or 1;
|
|
end
|
|
end
|
|
end
|
|
return 0;
|
|
end
|
|
});
|
|
app.CompareQuestieDB = function()
|
|
if QuestieLoader then
|
|
local QuestieDB,missingQuestIDs = QuestieLoader:ImportModule("QuestieDB"), {};
|
|
for id,_ in pairs(QuestieDB.QuestPointers) do
|
|
local s = SearchForField("questID", id);
|
|
if #s == 0 then
|
|
tinsert(missingQuestIDs, id);
|
|
end
|
|
end
|
|
app.Sort(missingQuestIDs, app.SortDefaults.Number);
|
|
for _,id in ipairs(missingQuestIDs) do
|
|
print("Missing Quest ", id);
|
|
end
|
|
else
|
|
print("Error: Questie not available. Please enable it!");
|
|
end
|
|
end
|
|
app.AddQuestObjectivesToTooltip = function(tooltip, reference)
|
|
local objectified = false;
|
|
local questLogIndex = GetQuestLogIndexByID(reference.questID);
|
|
if questLogIndex then
|
|
local lore, objective = GetQuestLogQuestText(questLogIndex);
|
|
if lore and app.Settings:GetTooltipSetting("Lore") then
|
|
tooltip:AddLine(Colorize(lore, app.Colors.TooltipLore), 1, 1, 1, 1);
|
|
end
|
|
if objective and app.Settings:GetTooltipSetting("Objectives") then
|
|
tooltip:AddLine(QUEST_OBJECTIVES, 1, 1, 1, 1);
|
|
tooltip:AddLine(objective, 0.4, 0.8, 1, 1);
|
|
objectified = true;
|
|
end
|
|
end
|
|
if not reference.saved and app.Settings:GetTooltipSetting("Objectives") then
|
|
local objectives = C_QuestLog_GetQuestObjectives(reference.questID);
|
|
if objectives and #objectives > 0 then
|
|
if not objectified then
|
|
tooltip:AddLine(QUEST_OBJECTIVES, 1, 1, 1, 1);
|
|
end
|
|
for i,objective in ipairs(objectives) do
|
|
local _ = objective.text;
|
|
if not _ or _:sub(1, 1) == " " then
|
|
_ = RETRIEVING_DATA;
|
|
end
|
|
tooltip:AddDoubleLine(" " .. _, GetCompletionIcon(objective.finished), 1, 1, 1, 1);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Game Events that trigger visual updates, but no computation updates.
|
|
local softRefresh = function()
|
|
wipe(searchCache);
|
|
end;
|
|
app.events.BAG_NEW_ITEMS_UPDATED = softRefresh;
|
|
app.events.CRITERIA_UPDATE = softRefresh;
|
|
app.events.QUEST_REMOVED = softRefresh;
|
|
app.events.QUEST_WATCH_UPDATE = softRefresh;
|
|
app.events.QUEST_ACCEPTED = function(questLogIndex, questID)
|
|
if not questID then questID = questLogIndex; end
|
|
if questID then rawset(QuestTitleFromID, questID, nil); end
|
|
softRefresh();
|
|
end
|
|
app.events.QUEST_LOG_UPDATE = function()
|
|
app:UnregisterEvent("QUEST_LOG_UPDATE");
|
|
if C_QuestLog_GetAllCompletedQuestIDs then
|
|
local completedQuests = C_QuestLog_GetAllCompletedQuestIDs();
|
|
if completedQuests and #completedQuests > 0 then
|
|
for i,questID in ipairs(completedQuests) do
|
|
CompletedQuests[questID] = true;
|
|
end
|
|
end
|
|
else
|
|
GetQuestsCompleted(CompletedQuests);
|
|
end
|
|
for questID,completed in pairs(DirtyQuests) do
|
|
app.QuestCompletionHelper(tonumber(questID));
|
|
end
|
|
wipe(DirtyQuests);
|
|
wipe(searchCache);
|
|
end
|
|
app.events.QUEST_TURNED_IN = function(questID)
|
|
local quest = SearchForField("questID", questID);
|
|
if #quest > 0 and (not quest[1].repeatable or (quest[1].isDaily or quest[1].isMonthly or quest[1].isYearly)) then
|
|
CompletedQuests[questID] = true;
|
|
for questID,completed in pairs(DirtyQuests) do
|
|
app.QuestCompletionHelper(tonumber(questID));
|
|
end
|
|
wipe(DirtyQuests);
|
|
end
|
|
app:RefreshDataQuietly("QUEST_TURNED_IN", true);
|
|
end
|
|
app:RegisterEvent("BAG_NEW_ITEMS_UPDATED");
|
|
app:RegisterEvent("CRITERIA_UPDATE");
|
|
app:RegisterEvent("QUEST_ACCEPTED");
|
|
app:RegisterEvent("QUEST_LOG_UPDATE");
|
|
app:RegisterEvent("QUEST_REMOVED");
|
|
app:RegisterEvent("QUEST_TURNED_IN");
|
|
app:RegisterEvent("QUEST_WATCH_UPDATE");
|
|
end)();
|
|
|
|
-- Recipe & Spell Lib
|
|
(function()
|
|
local craftColors = {
|
|
RGBToHex(64,192,64),
|
|
RGBToHex(255,255,0),
|
|
RGBToHex(255,128,64),
|
|
[0]=RGBToHex(128, 128, 128),
|
|
};
|
|
local CraftTypeIDToColor = function(craftTypeID)
|
|
return craftColors[craftTypeID];
|
|
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 = GetSpellInfo(spellID, rank);
|
|
else
|
|
spellName = GetSpellInfo(spellID);
|
|
end
|
|
if spellName and spellName ~= "" and spellName ~= RETRIEVING_DATA 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 = GetSpellInfo(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.SkillIDToSpellID) do
|
|
app.GetSpellName(spellID);
|
|
end
|
|
for specID,spellID in pairs(app.SpecializationSpellIDs) 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, "", 1, 1;
|
|
for spellTabIndex=1,numSpellTabs do
|
|
local numSpells = select(4, GetSpellTabInfo(spellTabIndex));
|
|
for spellIndex=1,numSpells do
|
|
local spellName, _, _, _, _, _, spellID = GetSpellInfo(offset, BOOKTYPE_SPELL);
|
|
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
|
|
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 select(3, GetSpellInfo(t.spellID)) or (t.requireSkill and select(3, GetSpellInfo(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 GetSpellLink(t.spellID) or RETRIEVING_DATA;
|
|
end;
|
|
local spellFields = {
|
|
["text"] = function(t)
|
|
return t.craftTypeID and Colorize(t.name, CraftTypeIDToColor(t.craftTypeID)) or t.link;
|
|
end,
|
|
["icon"] = function(t)
|
|
local icon = t.baseIcon;
|
|
if icon and icon ~= 136235 and icon ~= 136192 then
|
|
return icon;
|
|
end
|
|
return "Interface\\ICONS\\INV_Scroll_04";
|
|
end,
|
|
["craftTypeID"] = function(t)
|
|
return app.CurrentCharacter.SpellRanks[t.spellID];
|
|
end,
|
|
["trackable"] = app.ReturnTrue,
|
|
["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 = 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 isKnown = not t.nmc and app.IsSpellKnown(t.spellID, t.rank, GetRelativeValue(t, "requireSkill") == 261);
|
|
return app.SetCollectedForSubType(t, "Spells", "Recipes", t.spellID, isKnown);
|
|
end;
|
|
recipeFields.f = function(t)
|
|
return app.FilterConstants.RECIPES;
|
|
end;
|
|
local createRecipe = app.CreateClass("Recipe", "spellID", recipeFields,
|
|
"WithItem", {
|
|
baseIcon = function(t)
|
|
return select(5, GetItemInfoInstant(t.itemID)) or baseIconFromSpellID(t);
|
|
end,
|
|
link = function(t)
|
|
return select(2, GetItemInfo(t.itemID)) or RETRIEVING_DATA;
|
|
end,
|
|
name = function(t)
|
|
return GetItemInfo(t.itemID) or nameFromSpellID(t);
|
|
end,
|
|
tsm = function(t)
|
|
return sformat("i:%d", 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)();
|
|
|
|
-- Tier Lib
|
|
(function()
|
|
local math_floor = math.floor;
|
|
local baseTier = {
|
|
__index = function(t, key)
|
|
if key == "key" then
|
|
return "tierID";
|
|
else
|
|
local info = rawget(L.TIER_DATA, t.tierID);
|
|
return info and rawget(info, key);
|
|
end
|
|
end
|
|
};
|
|
app.CreateTier = function(id, t)
|
|
-- patch can be included in the id
|
|
local tierID = math_floor(id);
|
|
t = constructor(tierID, t, "tierID");
|
|
if id > tierID then
|
|
local patch_decimal = 100 * (id - tierID);
|
|
local patch = math_floor(patch_decimal + 0.0001);
|
|
local rev = math_floor(10 * (patch_decimal - patch) + 0.0001);
|
|
-- print("tier cache",id,tierID,patch_decimal,patch,rev)
|
|
t.text = tostring(tierID).."."..tostring(patch).."."..tostring(rev);
|
|
end
|
|
return setmetatable(t, baseTier);
|
|
end
|
|
end)();
|
|
|
|
-- Title Lib
|
|
(function()
|
|
local function StylizePlayerTitle(title, style, me)
|
|
if title then
|
|
if style == 0 then
|
|
-- Prefix
|
|
return title .. me;
|
|
elseif style == 1 then
|
|
-- Player Name First
|
|
return me .. title;
|
|
elseif style == 2 then
|
|
-- Player Name First (with space)
|
|
return me .. " " .. title;
|
|
elseif style == 3 then
|
|
-- Comma Separated
|
|
return me .. ", " .. title;
|
|
end
|
|
else
|
|
return title or "??";
|
|
end
|
|
end
|
|
local OnUpdateForSpecificGender = function(t)
|
|
if not (app.Settings:Get("AccountMode") or app.Settings:Get("DebugMode") or t.playerGender == UnitSex("player")) then
|
|
t.visible = false;
|
|
return true;
|
|
elseif t.parent.titleIDs then
|
|
UpdateGroup(t.parent, t);
|
|
t.visible = false;
|
|
return true;
|
|
end
|
|
end
|
|
local OnUpdateForGenderedTitle = function(t)
|
|
if not (app.Settings:Get("AccountMode") or app.Settings:Get("DebugMode")) then
|
|
t.progress = nil;
|
|
t.total = nil;
|
|
t.g = nil;
|
|
else
|
|
if not t.titleObjects then
|
|
local titleObjects = {};
|
|
for index,titleID in ipairs(t.titleIDs) do
|
|
local titleObject = app.CreateTitle(titleID, { ["playerGender"] = index == 1 and 2 or 3, ["OnUpdate"] = OnUpdateForSpecificGender });
|
|
titleObject.parent = t;
|
|
tinsert(titleObjects, titleObject);
|
|
end
|
|
t.titleObjects = titleObjects;
|
|
end
|
|
local g = {};
|
|
for index,titleObject in ipairs(t.titleObjects) do
|
|
if titleObject.titleID ~= t.titleID then tinsert(g, titleObject); end
|
|
end
|
|
if #g > 0 then t.g = g; end
|
|
end
|
|
end
|
|
app.CreateTitle = app.CreateClass("Title", "titleID", {
|
|
["icon"] = function(t)
|
|
return "Interface\\Icons\\INV_Misc_Horn_01";
|
|
end,
|
|
["description"] = function(t)
|
|
return L["TITLES_DESC"];
|
|
end,
|
|
["text"] = function(t)
|
|
return "|c" .. app.Colors.Account .. (t.name or RETRIEVING_DATA) .. "|r";
|
|
end,
|
|
["name"] = function(t)
|
|
return StylizePlayerTitle(t.titleName, t.style, UnitName("player"));
|
|
end,
|
|
["playerGender"] = function(t)
|
|
return UnitSex("player");
|
|
end,
|
|
["titleName"] = function(t)
|
|
return GetTitleName(t.titleID);
|
|
end,
|
|
["title"] = function(t)
|
|
return StylizePlayerTitle(t.titleName, t.style, "("..CALENDAR_PLAYER_NAME..")");
|
|
end,
|
|
["style"] = function(t)
|
|
local name = t.titleName;
|
|
if name then
|
|
local first = string.sub(name, 1, 1);
|
|
if first == " " then
|
|
-- Suffix
|
|
first = string.sub(name, 2, 2);
|
|
if first == string.upper(first) then
|
|
-- Comma Separated
|
|
return 3;
|
|
end
|
|
|
|
-- Player Name First
|
|
return 1;
|
|
else
|
|
local last = string.sub(name, -1);
|
|
if last == " " then
|
|
-- Prefix
|
|
return 0;
|
|
end
|
|
|
|
-- Suffix
|
|
if first == string.lower(first) then
|
|
-- Player Name First with a space
|
|
return 2;
|
|
end
|
|
|
|
-- Comma Separated
|
|
return 3;
|
|
end
|
|
end
|
|
|
|
return 1; -- Player Name First
|
|
end,
|
|
["collectible"] = function(t)
|
|
return app.Settings.Collectibles.Titles;
|
|
end,
|
|
["trackable"] = app.ReturnTrue,
|
|
["collected"] = function(t)
|
|
local titleID = t.titleID;
|
|
return app.SetCollected(t, "Titles", titleID, IsTitleKnown(titleID));
|
|
end,
|
|
["saved"] = function(t)
|
|
return IsTitleKnown(t.titleID);
|
|
end,
|
|
["OnUpdateForSpecificGender"] = function(t)
|
|
return OnUpdateForSpecificGender;
|
|
end,
|
|
},
|
|
"WithGender", {
|
|
collected = function(t)
|
|
local ids, acctTitles, charTitles = t.titleIDs, ATTAccountWideData.Titles, app.CurrentCharacter.Titles;
|
|
local m, f = ids[1], ids[2];
|
|
local alreadyHaveOne = charTitles[m] or charTitles[f];
|
|
local collectedM = app.SetCollected(nil, "Titles", m, IsTitleKnown(m));
|
|
local collectedF = app.SetCollected(nil, "Titles", f, IsTitleKnown(f));
|
|
if collectedM == 1 or collectedF == 1 then
|
|
if not alreadyHaveOne then AddToCollection(t); end
|
|
return 1;
|
|
elseif collectedM == 2 or collectedF == 2 then
|
|
return 2;
|
|
end
|
|
end,
|
|
description = function(t)
|
|
return "This title changes its state whenever your character changes its gender identity. This is particularly common in Brunnhildar Village in Storm Peaks or by means of using an Engineering teleport. In account mode you will need to have multiple characters with representation of both gender types.";
|
|
end,
|
|
saved = function(t)
|
|
local ids = t.titleIDs;
|
|
return IsTitleKnown(ids[1]) or IsTitleKnown(ids[2]);
|
|
end,
|
|
title = function(t)
|
|
local ids, acctTitles = t.titleIDs, ATTAccountWideData.Titles;
|
|
local m, f = ids[1], ids[2];
|
|
local player = "("..CALENDAR_PLAYER_NAME..")";
|
|
return " " .. StylizePlayerTitle(GetTitleName(m), t.style, player).. "\n " .. StylizePlayerTitle(GetTitleName(f), t.style, player)
|
|
.. DESCRIPTION_SEPARATOR .. GetCollectionIcon(acctTitles[m]) .. "\n" .. GetCollectionIcon(acctTitles[f]);
|
|
end,
|
|
titleID = function(t)
|
|
return t.playerGender == 2 and t.titleIDs[1] or t.titleIDs[2];
|
|
end,
|
|
OnUpdate = function(t)
|
|
return OnUpdateForGenderedTitle;
|
|
end
|
|
}, (function(t) return t.titleIDs; end));
|
|
end)();
|
|
|
|
-- Unsupported Libs
|
|
(function()
|
|
-- Neither of these are supported at this time.
|
|
app.CreateArtifact = function(id, t)
|
|
return { text = "Artifact #" .. id, description = "This data type is not supported at this time." };
|
|
end
|
|
app.CreateAzeriteEssence = function(id, t)
|
|
return { text = "AzeriteEssence #" .. id, description = "This data type is not supported at this time." };
|
|
end
|
|
app.CreateConduit = function(id, t)
|
|
return { text = "Conduit #" .. id, description = "This data type is not supported at this time." };
|
|
end
|
|
app.CreateDrakewatcherManuscript = function(id, t)
|
|
return { text = "DrakewatcherManuscript #" .. id, description = "This data type is not supported at this time." };
|
|
end
|
|
app.CreateFollower = function(id, t)
|
|
return { text = "Follower #" .. id, description = "This data type is not supported at this time." };
|
|
end
|
|
|
|
app.CreateGearSet = function(id, t)
|
|
return { text = "GearSet #" .. id, description = "This data type is not supported at this time." };
|
|
end
|
|
app.CreateGearSetHeader = function(id, t)
|
|
return { text = "GearSetHeader #" .. id, description = "This data type is not supported at this time." };
|
|
end
|
|
app.CreateGearSetSubHeader = function(id, t)
|
|
return { text = "GearSetSubHeader #" .. id, description = "This data type is not supported at this time." };
|
|
end
|
|
app.CreateMusicRoll = function(questID, t)
|
|
return { text = "MusicRoll #" .. questID, description = "This data type is not supported at this time." };
|
|
end
|
|
app.CreatePetAbility = function(id, t)
|
|
return { text = "PetAbility #" .. id, description = "This data type is not supported at this time." };
|
|
end
|
|
app.CreateRace = function(id, t)
|
|
return { text = "Race #" .. id, description = "This data type is not supported at this time." };
|
|
end
|
|
app.CreateRuneforgeLegendary = function(id, t)
|
|
return { text = "RuneforgeLegendary #" .. id, description = "This data type is not supported at this time." };
|
|
end
|
|
app.CreateSelfieFilter = function(id, t)
|
|
return { text = "SelfieFilter #" .. id, description = "This data type is not supported at this time." };
|
|
end
|
|
app.CreateVignette = function(id, t)
|
|
return { text = "Vignette #" .. id, description = "This data type is not supported at this time." };
|
|
end
|
|
app.CreateItemSource = function(sourceID, itemID, t)
|
|
t = app.CreateItem(itemID, t);
|
|
t.s = sourceID;
|
|
return t;
|
|
end
|
|
end)();
|
|
|
|
-- Filtering
|
|
function app.Filter()
|
|
-- Meaning "Don't display."
|
|
return false;
|
|
end
|
|
function app.NoFilter()
|
|
-- Meaning "Display as expected."
|
|
return true;
|
|
end
|
|
function app.FilterGroupsByLevel(group)
|
|
-- after 9.0, transition to a req lvl range, either min, or min + max
|
|
if group.lvl then
|
|
local minlvl, maxlvl;
|
|
if type(group.lvl) == "table" then
|
|
minlvl = group.lvl[1];
|
|
maxlvl = group.lvl[2];
|
|
else
|
|
minlvl = group.lvl;
|
|
end
|
|
|
|
if maxlvl then
|
|
-- min and max provided
|
|
return app.Level >= minlvl and app.Level <= maxlvl;
|
|
elseif minlvl then
|
|
-- only min provided
|
|
return app.Level >= minlvl;
|
|
end
|
|
end
|
|
-- no level requirement on the group, have to include it
|
|
return true;
|
|
end
|
|
function app.FilterGroupsByCompletion(group)
|
|
return group.progress < group.total;
|
|
end
|
|
function app.FilterItemBind(item)
|
|
return item.b == 2 or item.b == 3; -- BoE
|
|
end
|
|
function app.FilterItemClass(item)
|
|
if app.UnobtainableItemFilter(item) and app.RequireEventFilter(item) and app.PvPFilter(item) then
|
|
if app.ItemBindFilter(item) then return true; end
|
|
return app.ItemTypeFilter(item)
|
|
and app.RequireBindingFilter(item)
|
|
and app.RequiredSkillFilter(item)
|
|
and app.ClassRequirementFilter(item)
|
|
and app.RaceRequirementFilter(item)
|
|
and app.RequireFactionFilter(item);
|
|
end
|
|
end
|
|
function app.FilterItemPvP(item)
|
|
if item.pvp then
|
|
return false;
|
|
else
|
|
return true;
|
|
end
|
|
end
|
|
function app.FilterItemClass_RequireClasses(item)
|
|
return not item.nmc;
|
|
end
|
|
function app.FilterItemClass_RequireItemFilter(item)
|
|
if item.f then
|
|
if app.Settings:GetFilter(item.f) then
|
|
return true;
|
|
else
|
|
return false;
|
|
end
|
|
else
|
|
return true;
|
|
end
|
|
end
|
|
function app.FilterItemClass_RequireRaces(item)
|
|
return not item.nmr;
|
|
end
|
|
if app.GameBuildVersion > 11403 then
|
|
function app.FilterItemClass_RequireRacesCurrentFaction(item)
|
|
if item.nmr then
|
|
if item.r then
|
|
if item.r == app.FactionID then
|
|
return true;
|
|
else
|
|
return false;
|
|
end
|
|
end
|
|
if item.races then
|
|
if app.FactionID == HORDE_FACTION_ID then
|
|
return containsAny(item.races, HORDE_ONLY);
|
|
else
|
|
return containsAny(item.races, ALLIANCE_ONLY);
|
|
end
|
|
else
|
|
return false;
|
|
end
|
|
else
|
|
return true;
|
|
end
|
|
end
|
|
else
|
|
function app.FilterItemClass_RequireRacesCurrentFaction(item)
|
|
if item.nmr then
|
|
if item.r then
|
|
if item.r == app.FactionID then
|
|
return true;
|
|
else
|
|
return false;
|
|
end
|
|
end
|
|
if item.races then
|
|
if app.FactionID == HORDE_FACTION_ID then
|
|
return containsAny(item.races, HORDE_ONLY);
|
|
else
|
|
return containsAny(item.races, ALLIANCE_ONLY);
|
|
end
|
|
else
|
|
return false;
|
|
end
|
|
else
|
|
if item.nmc then
|
|
if item.c and #item.c == 1 then
|
|
if app.FactionID == HORDE_FACTION_ID then
|
|
return item.c[1] ~= 2; -- Check for Paladin
|
|
else
|
|
return item.c[1] ~= 7; -- Check for Shaman
|
|
end
|
|
else
|
|
return true;
|
|
end
|
|
else
|
|
return true;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
function app.FilterItemClass_RequireBinding(item)
|
|
if item.b and (item.b == 2 or item.b == 3) then
|
|
return false;
|
|
else
|
|
return true;
|
|
end
|
|
end
|
|
function app.FilterItemClass_RequiredSkill(item)
|
|
local requireSkill = item.requireSkill;
|
|
if requireSkill and (not item.professionID or not GetRelativeValue(item, "DontEnforceSkillRequirements") or item.b == 1) then
|
|
requireSkill = app.SkillIDToSpellID[requireSkill];
|
|
return requireSkill and app.CurrentCharacter.ActiveSkills[requireSkill];
|
|
else
|
|
return true;
|
|
end
|
|
end
|
|
function app.FilterItemClass_RequireFaction(item)
|
|
if item.minReputation and app.IsFactionExclusive(item.minReputation[1]) then
|
|
if item.minReputation[2] > (select(6, GetFactionInfoByID(item.minReputation[1])) or 0) then
|
|
return false;
|
|
else
|
|
return true;
|
|
end
|
|
else
|
|
return true;
|
|
end
|
|
end
|
|
function app.FilterItemClass_UnobtainableItem(item)
|
|
if item.u and not app.Settings:GetUnobtainableFilter(item.u) then
|
|
return false;
|
|
else
|
|
return true;
|
|
end
|
|
end
|
|
function app.FilterItemTrackable(group)
|
|
return group.trackable;
|
|
end
|
|
function app.ObjectVisibilityFilter(group)
|
|
return group.visible;
|
|
end
|
|
|
|
-- Default Filter Settings (changed in ADDON_LOADED and in the Options Menu)
|
|
app.VisibilityFilter = app.ObjectVisibilityFilter;
|
|
app.GroupFilter = app.FilterItemClass;
|
|
app.GroupRequirementsFilter = app.NoFilter;
|
|
app.GroupVisibilityFilter = app.NoFilter;
|
|
app.ItemBindFilter = app.FilterItemBind;
|
|
app.ItemTypeFilter = app.NoFilter;
|
|
app.PvPFilter = app.NoFilter;
|
|
app.CollectedItemVisibilityFilter = app.NoFilter;
|
|
app.ClassRequirementFilter = app.NoFilter;
|
|
app.RaceRequirementFilter = app.NoFilter;
|
|
app.RequireBindingFilter = app.NoFilter;
|
|
app.RequireEventFilter = app.Modules.Events.FilterIsEventActive;
|
|
app.RequiredSkillFilter = app.NoFilter;
|
|
app.RequireFactionFilter = app.FilterItemClass_RequireFaction;
|
|
app.UnobtainableItemFilter = app.FilterItemClass_UnobtainableItem;
|
|
app.ShowIncompleteThings = app.Filter;
|
|
|
|
-- Recursive Checks
|
|
app.RecursiveGroupRequirementsFilter = function(group)
|
|
if app.GroupRequirementsFilter(group) and app.GroupFilter(group) then
|
|
if group.parent then return app.RecursiveGroupRequirementsFilter(group.parent); end
|
|
return true;
|
|
end
|
|
return false;
|
|
end
|
|
app.RecursiveClassAndRaceFilter = function(group)
|
|
if app.ClassRequirementFilter(group) and app.RaceRequirementFilter(group) then
|
|
if group.parent then return app.RecursiveClassAndRaceFilter(group.parent); end
|
|
return true;
|
|
end
|
|
return false;
|
|
end
|
|
app.RecursiveDefaultClassAndRaceFilter = function(group)
|
|
if app.FilterItemClass_RequireClasses(group) and app.FilterItemClass_RequireRaces(group) then
|
|
if group.parent then return app.RecursiveDefaultClassAndRaceFilter(group.parent); end
|
|
return true;
|
|
end
|
|
return false;
|
|
end
|
|
app.RecursiveUnobtainableFilter = function(group)
|
|
if app.UnobtainableItemFilter(group) and app.RequireEventFilter(group) then
|
|
if group.parent then return app.RecursiveUnobtainableFilter(group.parent); end
|
|
return true;
|
|
end
|
|
return false;
|
|
end
|
|
app.RecursiveIsDescendantOfParentWithValue = function(group, field, value)
|
|
if group then
|
|
if group[field] and group[field] == value then
|
|
return true
|
|
else
|
|
if group.parent then
|
|
return app.RecursiveIsDescendantOfParentWithValue(group.parent, field, value)
|
|
end
|
|
end
|
|
end
|
|
return false;
|
|
end
|
|
|
|
-- Processing Functions (Coroutines)
|
|
UpdateGroup = function(parent, group)
|
|
local visible = false;
|
|
|
|
-- Determine if this user can enter the instance or acquire the item.
|
|
if app.GroupRequirementsFilter(group) then
|
|
-- Check if this is a group
|
|
if group.g then
|
|
-- If this item is collectible, then mark it as such.
|
|
if group.collectible then
|
|
-- An item is a special case where it may have both an appearance and a set of items
|
|
group.progress = group.collected and 1 or 0;
|
|
group.total = 1;
|
|
else
|
|
-- Default to 0 for both
|
|
group.progress = 0;
|
|
group.total = 0;
|
|
end
|
|
|
|
-- Update the subgroups recursively...
|
|
visible = UpdateGroups(group, group.g);
|
|
|
|
-- If the 'can equip' filter says true
|
|
if app.GroupFilter(group) and app.ClassRequirementFilter(group) and app.RaceRequirementFilter(group) then
|
|
-- Increment the parent group's totals.
|
|
parent.total = (parent.total or 0) + group.total;
|
|
parent.progress = (parent.progress or 0) + group.progress;
|
|
|
|
-- If this group is trackable, then we should show it.
|
|
if group.total > 0 and app.GroupVisibilityFilter(group) then
|
|
visible = true;
|
|
elseif app.ShowIncompleteThings(group) and not group.saved then
|
|
visible = true;
|
|
elseif ((group.itemID and group.f) or group.sym) and app.Settings.Collectibles.Loot then
|
|
visible = true;
|
|
end
|
|
else
|
|
visible = false;
|
|
end
|
|
else
|
|
-- If the 'can equip' filter says true
|
|
if app.GroupFilter(group) then
|
|
if group.collectible then
|
|
-- Increment the parent group's totals.
|
|
parent.total = (parent.total or 0) + 1;
|
|
|
|
-- If we've collected the item, use the "Show Collected Items" filter.
|
|
if group.collected then
|
|
parent.progress = (parent.progress or 0) + 1;
|
|
if app.CollectedItemVisibilityFilter(group) then
|
|
visible = true;
|
|
end
|
|
else
|
|
visible = true;
|
|
end
|
|
elseif group.trackable then
|
|
-- If this group is trackable, then we should show it.
|
|
if app.ShowIncompleteThings(group) and not group.saved then
|
|
visible = true;
|
|
end
|
|
elseif ((group.itemID and group.f) or group.sym) and app.Settings.Collectibles.Loot then
|
|
visible = true;
|
|
elseif app.Settings:Get("DebugMode") then
|
|
visible = true;
|
|
end
|
|
elseif app.Settings:Get("DebugMode") then
|
|
visible = true;
|
|
else
|
|
visible = false;
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Set the visibility
|
|
group.visible = visible;
|
|
return visible;
|
|
end
|
|
UpdateGroups = function(parent, g)
|
|
if g then
|
|
local visible = false;
|
|
for i=1,#g,1 do
|
|
local group = g[i];
|
|
if group.OnUpdate then
|
|
--local lastUpdate = GetTimePreciseSec();
|
|
local result = group:OnUpdate(group);
|
|
--local duration = (GetTimePreciseSec() - lastUpdate) * 10000;
|
|
--if duration > 10 then print(group.text, "OnUpdate: ", duration); end
|
|
if not result then
|
|
if UpdateGroup(parent, group) then
|
|
visible = true;
|
|
end
|
|
elseif group.visible then
|
|
visible = true;
|
|
end
|
|
elseif UpdateGroup(parent, group) then
|
|
visible = true;
|
|
end
|
|
end
|
|
return visible;
|
|
end
|
|
end
|
|
local function UpdateParentProgress(group)
|
|
if group.collectible then
|
|
group.progress = group.progress + 1;
|
|
end
|
|
|
|
-- Continue on to this object's parent.
|
|
if group.parent then
|
|
if group.visible then
|
|
-- If we were initially visible, then update the parent.
|
|
UpdateParentProgress(group.parent);
|
|
|
|
-- If this group is trackable, then we should show it.
|
|
if app.GroupVisibilityFilter(group) then
|
|
group.visible = true;
|
|
elseif app.ShowIncompleteThings(group) then
|
|
group.visible = not group.saved;
|
|
else
|
|
group.visible = false;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
app.UpdateGroups = UpdateGroups;
|
|
app.UpdateParentProgress = UpdateParentProgress;
|
|
|
|
-- Helper Methods
|
|
function app.GetNumberOfItemsUntilNextPercentage(progress, total)
|
|
if total <= progress then
|
|
return "|c" .. GetProgressColor(1) .. "YOU DID IT!|r";
|
|
else
|
|
local originalPercent = progress / total;
|
|
local nextPercent = math.ceil(originalPercent * 100);
|
|
local roundedPercent = nextPercent * 0.01;
|
|
local diff = math.ceil(total * (roundedPercent - originalPercent));
|
|
if diff < 1 or nextPercent == 100 then
|
|
return "|c" .. GetProgressColor(1) .. (total - progress) .. " THINGS UNTIL 100%|r";
|
|
elseif diff == 1 then
|
|
return "|c" .. GetProgressColor(roundedPercent) .. diff .. " THING UNTIL " .. nextPercent .. "%|r";
|
|
else
|
|
return "|c" .. GetProgressColor(roundedPercent) .. diff .. " THINGS UNTIL " .. nextPercent .. "%|r";
|
|
end
|
|
end
|
|
end
|
|
function app.QuestCompletionHelper(questID)
|
|
-- Search ATT for the related quests.
|
|
local searchResults = SearchForField("questID", questID);
|
|
if #searchResults > 0 then
|
|
-- Only increase progress for Quests as Collectible users.
|
|
if app.Settings.Collectibles.Quests then
|
|
-- Attempt to cleanly refresh the data.
|
|
for i,result in ipairs(searchResults) do
|
|
if result.visible and result.parent and result.parent.total then
|
|
result.marked = true;
|
|
end
|
|
end
|
|
for i,result in ipairs(searchResults) do
|
|
if result.marked then
|
|
result.marked = nil;
|
|
if result.total then
|
|
-- This is an item that has a relative set of groups
|
|
UpdateParentProgress(result);
|
|
|
|
-- If this is NOT a group...
|
|
if not result.g and result.collectible then
|
|
-- If we've collected the item, use the "Show Collected Items" filter.
|
|
result.visible = app.CollectedItemVisibilityFilter(result);
|
|
end
|
|
else
|
|
UpdateParentProgress(result.parent);
|
|
|
|
if result.collectible then
|
|
-- If we've collected the item, use the "Show Collected Items" filter.
|
|
result.visible = app.CollectedItemVisibilityFilter(result);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Refresh certain kinds of data.
|
|
local function RefreshSkills()
|
|
-- Store Skill Data
|
|
local activeSkills = app.CurrentCharacter.ActiveSkills;
|
|
wipe(activeSkills);
|
|
rawset(app.SpellNameToSpellID, 0, nil);
|
|
app.GetSpellName(0);
|
|
local GetSkillLineInfo = _G["GetSkillLineInfo"];
|
|
if GetSkillLineInfo then
|
|
for index=GetNumSkillLines(),2,-1 do
|
|
local skillName, header, isExpanded, skillRank, numTempPoints, skillModifier,
|
|
skillMaxRank, isAbandonable, stepCost, rankCost, minLevel, skillCostType,
|
|
skillDescription = GetSkillLineInfo(index);
|
|
if not header then
|
|
local spellID = app.SpellNameToSpellID[skillName];
|
|
if spellID then
|
|
local spellName = GetSpellInfo(spellID);
|
|
for _,s in pairs(app.SkillIDToSpellID) do
|
|
if GetSpellInfo(s) == spellName then
|
|
spellID = s;
|
|
break;
|
|
end
|
|
end
|
|
activeSkills[spellID] = { skillRank, skillMaxRank };
|
|
else
|
|
for _,s in pairs(app.SkillIDToSpellID) do
|
|
if GetSpellInfo(s) == skillName then
|
|
spellID = s;
|
|
break;
|
|
end
|
|
end
|
|
if spellID then
|
|
activeSkills[spellID] = { skillRank, skillMaxRank };
|
|
else
|
|
--print(skillName, header, isExpanded, skillRank, numTempPoints, skillModifier, skillMaxRank, isAbandonable, stepCost, rankCost, minLevel, skillCostType, skillDescription);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Hunter Only
|
|
if app.ClassIndex == 3 then
|
|
activeSkills[5149] = { 1, 1 };
|
|
end
|
|
|
|
-- Clone the data for the specializations.
|
|
for specID,spellID in pairs(app.SpecializationSpellIDs) do
|
|
local baseSpell = activeSkills[spellID];
|
|
if baseSpell and (app.CurrentCharacter.Spells[specID] or IsSpellKnown(specID)) then
|
|
activeSkills[specID] = baseSpell;
|
|
end
|
|
end
|
|
end
|
|
local function RefreshCollections()
|
|
app:StartATTCoroutine("RefreshingCollections", function()
|
|
while InCombatLockdown() do coroutine.yield(); end
|
|
app.print("Refreshing collection...");
|
|
app.events.QUEST_LOG_UPDATE();
|
|
coroutine.yield();
|
|
|
|
-- Harvest Illusion Collections
|
|
if C_TransmogCollection and C_TransmogCollection.GetIllusions then
|
|
local collectedIllusions = ATTAccountWideData.Illusions;
|
|
for _,illusion in ipairs(C_TransmogCollection.GetIllusions()) do
|
|
if illusion.isCollected then collectedIllusions[illusion.sourceID] = 1; end
|
|
end
|
|
coroutine.yield();
|
|
end
|
|
|
|
-- Refresh Toys
|
|
local collected;
|
|
for id,t in pairs(app.SearchForFieldContainer("toyID")) do
|
|
if #t > 0 then
|
|
collected = t[1].collected; -- Run the collected field's code.
|
|
end
|
|
end
|
|
coroutine.yield();
|
|
|
|
RefreshSkills();
|
|
app:RefreshDataCompletely("RefreshCollections");
|
|
app.print("Done refreshing collection.");
|
|
end);
|
|
end
|
|
app.RefreshCollections = RefreshCollections;
|
|
|
|
-- Automatically Refresh Saved Instances
|
|
local function RefreshSaves()
|
|
local waitTimer = 30;
|
|
while waitTimer > 0 do
|
|
coroutine.yield();
|
|
waitTimer = waitTimer - 1;
|
|
end
|
|
|
|
-- While the player is in combat, wait for combat to end.
|
|
while InCombatLockdown() do coroutine.yield(); end
|
|
|
|
-- While the player is still logging in, wait.
|
|
while not app.GUID do coroutine.yield(); end
|
|
|
|
-- Cache the lockouts across your account.
|
|
local serverTime = GetServerTime();
|
|
|
|
-- Check to make sure that the old instance data has expired
|
|
for guid,character in pairs(ATTCharacterData) do
|
|
local locks = character.Lockouts;
|
|
if locks then
|
|
for name,instance in pairs(locks) do
|
|
local count = 0;
|
|
for difficulty,lock in pairs(instance) do
|
|
if type(lock) ~= "table" or type(lock.reset) ~= "number" or serverTime >= lock.reset then
|
|
-- Clean this up.
|
|
instance[difficulty] = nil;
|
|
else
|
|
count = count + 1;
|
|
end
|
|
end
|
|
if count == 0 then
|
|
-- Clean this up.
|
|
locks[name] = nil;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- While the player is still waiting for information, wait.
|
|
-- NOTE: Usually, this is only 1 wait.
|
|
local counter = 0;
|
|
while GetNumSavedInstances() < 1 do
|
|
coroutine.yield();
|
|
counter = counter + 1;
|
|
if counter > 10 then
|
|
app.refreshingSaves = false;
|
|
return false;
|
|
end
|
|
end
|
|
|
|
-- Update Saved Instances
|
|
local myLockouts = app.CurrentCharacter.Lockouts;
|
|
for instanceIter=1,GetNumSavedInstances() do
|
|
local name, id, reset, difficulty, locked, _, _, isRaid, _, _, numEncounters = GetSavedInstanceInfo(instanceIter);
|
|
if locked then
|
|
-- Update the name of the instance and cache the lock for this instance
|
|
difficulty = difficulty or 7;
|
|
reset = serverTime + reset;
|
|
local locks = myLockouts[name];
|
|
if not locks then
|
|
locks = {};
|
|
myLockouts[name] = locks;
|
|
end
|
|
|
|
-- Create the lock for this difficulty
|
|
local lock = locks[difficulty];
|
|
if not lock then
|
|
lock = { ["id"] = id, ["reset"] = reset, ["encounters"] = {}};
|
|
locks[difficulty] = lock;
|
|
else
|
|
lock.id = id;
|
|
lock.reset = reset;
|
|
end
|
|
|
|
-- If this is LFR, then don't share.
|
|
if difficulty == 7 or difficulty == 17 then
|
|
if #lock.encounters == 0 then
|
|
-- Check Encounter locks
|
|
for encounterIter=1,numEncounters do
|
|
local name, _, isKilled = GetSavedInstanceEncounterInfo(instanceIter, encounterIter);
|
|
tinsert(lock.encounters, { ["name"] = name, ["isKilled"] = isKilled });
|
|
end
|
|
else
|
|
-- Check Encounter locks
|
|
for encounterIter=1,numEncounters do
|
|
local name, _, isKilled = GetSavedInstanceEncounterInfo(instanceIter, encounterIter);
|
|
if not lock.encounters[encounterIter] then
|
|
tinsert(lock.encounters, { ["name"] = name, ["isKilled"] = isKilled });
|
|
elseif isKilled then
|
|
lock.encounters[encounterIter].isKilled = true;
|
|
end
|
|
end
|
|
end
|
|
else
|
|
-- Create the pseudo "shared" lock
|
|
local shared = locks["shared"];
|
|
if not shared then
|
|
shared = {};
|
|
shared.id = id;
|
|
shared.reset = reset;
|
|
shared.encounters = {};
|
|
locks["shared"] = shared;
|
|
|
|
-- Check Encounter locks
|
|
for encounterIter=1,numEncounters do
|
|
local name, _, isKilled = GetSavedInstanceEncounterInfo(instanceIter, encounterIter);
|
|
tinsert(lock.encounters, { ["name"] = name, ["isKilled"] = isKilled });
|
|
|
|
-- Shared Encounter is always assigned if this is the first lock seen for this instance
|
|
tinsert(shared.encounters, { ["name"] = name, ["isKilled"] = isKilled });
|
|
end
|
|
else
|
|
-- Check Encounter locks
|
|
for encounterIter=1,numEncounters do
|
|
local name, _, isKilled = GetSavedInstanceEncounterInfo(instanceIter, encounterIter);
|
|
if not lock.encounters[encounterIter] then
|
|
tinsert(lock.encounters, { ["name"] = name, ["isKilled"] = isKilled });
|
|
elseif isKilled then
|
|
lock.encounters[encounterIter].isKilled = true;
|
|
end
|
|
if not shared.encounters[encounterIter] then
|
|
tinsert(shared.encounters, { ["name"] = name, ["isKilled"] = isKilled });
|
|
elseif isKilled then
|
|
shared.encounters[encounterIter].isKilled = true;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Mark that we're done now.
|
|
app:RedrawWindows("RefreshSaves");
|
|
end
|
|
app:RegisterEvent("BOSS_KILL");
|
|
app.events.BOSS_KILL = function(id, name, ...)
|
|
-- This is so that when you kill a boss, you can trigger
|
|
-- an automatic update of your saved instance cache.
|
|
-- (It does lag a little, but you can disable this if you want.)
|
|
-- Waiting until the LOOT_CLOSED occurs will prevent the failed Auto Loot bug.
|
|
-- print("BOSS_KILL", id, name, ...);
|
|
app:UnregisterEvent("LOOT_CLOSED");
|
|
app:RegisterEvent("LOOT_CLOSED");
|
|
end
|
|
app.events.LOOT_CLOSED = function()
|
|
-- Once the loot window closes after killing a boss, THEN trigger the update.
|
|
app:UnregisterEvent("LOOT_CLOSED");
|
|
app:UnregisterEvent("UPDATE_INSTANCE_INFO");
|
|
app:RegisterEvent("UPDATE_INSTANCE_INFO");
|
|
RequestRaidInfo();
|
|
end
|
|
app.events.UPDATE_INSTANCE_INFO = function()
|
|
app:UnregisterEvent("UPDATE_INSTANCE_INFO");
|
|
app:StartATTCoroutine("RefreshSaves", RefreshSaves);
|
|
end
|
|
|
|
-- TomTom Support
|
|
local __TomTomWaypointCacheIndexY = { __index = function(t, y)
|
|
local o = {};
|
|
rawset(t, y, o);
|
|
return o;
|
|
end };
|
|
local __TomTomWaypointCacheIndexX = { __index = function(t, x)
|
|
local o = setmetatable({}, __TomTomWaypointCacheIndexY);
|
|
rawset(t, x, o);
|
|
return o;
|
|
end };
|
|
local __TomTomWaypointCache, __TomTomWaypointFirst = setmetatable({}, { __index = function(t, mapID)
|
|
local o = setmetatable({}, __TomTomWaypointCacheIndexX);
|
|
rawset(t, mapID, o);
|
|
return o;
|
|
end });
|
|
local function AddTomTomWaypointCache(coord, group)
|
|
local mapID = coord[3];
|
|
if mapID then
|
|
__TomTomWaypointCache[mapID][math.floor(coord[1] * 10)][math.floor(coord[2] * 10)][group.key .. ":" .. group[group.key]] = group;
|
|
else
|
|
print("Missing mapID for", group.text, coord[1], coord[2], mapID);
|
|
end
|
|
end
|
|
local function AddTomTomWaypointInternal(group, depth)
|
|
if group.visible then
|
|
if group.plotting then return false; end
|
|
group.plotting = true;
|
|
if group.g then
|
|
depth = depth + 1;
|
|
for _,o in ipairs(group.g) do
|
|
AddTomTomWaypointInternal(o, depth);
|
|
end
|
|
depth = depth - 1;
|
|
end
|
|
|
|
local searchResults = ResolveSymbolicLink(group);
|
|
if searchResults then
|
|
depth = depth + 1;
|
|
for _,o in ipairs(searchResults) do
|
|
AddTomTomWaypointInternal(o, depth);
|
|
end
|
|
depth = depth - 1;
|
|
end
|
|
group.plotting = nil;
|
|
|
|
if TomTom then
|
|
if (depth == 0 and not __TomTomWaypointFirst) or not group.saved then
|
|
if group.coords or group.coord then
|
|
__TomTomWaypointFirst = false;
|
|
if group.coords then
|
|
for _,coord in ipairs(group.coords) do
|
|
AddTomTomWaypointCache(coord, group);
|
|
end
|
|
end
|
|
if group.coord then AddTomTomWaypointCache(group.coord, group); end
|
|
end
|
|
end
|
|
elseif C_SuperTrack then
|
|
if depth == 0 or __TomTomWaypointFirst then
|
|
local coord = group.coords and group.coords[1] or group.coord;
|
|
if coord then
|
|
__TomTomWaypointFirst = false;
|
|
C_SuperTrack.SetSuperTrackedUserWaypoint(false);
|
|
C_Map.ClearUserWaypoint();
|
|
C_Map.SetUserWaypoint(UiMapPoint.CreateFromCoordinates(coord[3] or defaultMapID,coord[1]/100,coord[2]/100));
|
|
C_SuperTrack.SetSuperTrackedUserWaypoint(true);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
local function AddTomTomWaypoint(group)
|
|
if TomTom or C_SuperTrack then
|
|
__TomTomWaypointFirst = true;
|
|
wipe(__TomTomWaypointCache);
|
|
AddTomTomWaypointInternal(group, 0);
|
|
if TomTom then
|
|
local xnormal;
|
|
for mapID,c in pairs(__TomTomWaypointCache) do
|
|
for x,d in pairs(c) do
|
|
xnormal = x / 1000;
|
|
for y,datas in pairs(d) do
|
|
-- Determine the Root and simplify NPC/Object data.
|
|
-- An NPC/Object can contain all of the other types by reference and don't need individual entries.
|
|
local root,rootByCreatureID,rootByObjectID = {},{},{};
|
|
for key,group in pairs(datas) do
|
|
local creatureID, objectID;
|
|
if group.npcID or group.creatureID then
|
|
creatureID = group.npcID or group.creatureID;
|
|
elseif group.objectID then
|
|
objectID = group.objectID;
|
|
else
|
|
if group.providers then
|
|
for i,provider in ipairs(group.providers) do
|
|
if provider[1] == "n" then
|
|
if provider[2] > 0 then
|
|
creatureID = provider[2];
|
|
end
|
|
elseif provider[1] == "o" then
|
|
if provider[2] > 0 then
|
|
objectID = provider[2];
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if group.qgs then
|
|
local count = #group.qgs;
|
|
if count > 1 and group.coords and #group.coords == count then
|
|
for i=count,1,-1 do
|
|
local coord = group.coords[i];
|
|
if coord[3] == mapID and math.floor(coord[1] * 10) == x and math.floor(coord[2] * 10) == y then
|
|
creatureID = group.qgs[i];
|
|
break;
|
|
end
|
|
end
|
|
if not creatureID then
|
|
creatureID = group.qgs[1];
|
|
end
|
|
else
|
|
creatureID = group.qgs[1];
|
|
end
|
|
end
|
|
if group.crs then
|
|
local count = #group.crs;
|
|
if count > 1 and group.coords and #group.coords == count then
|
|
for i=count,1,-1 do
|
|
local coord = group.coords[i];
|
|
if coord[3] == mapID and math.floor(coord[1] * 10) == x and math.floor(coord[2] * 10) == y then
|
|
creatureID = group.crs[i];
|
|
break;
|
|
end
|
|
end
|
|
if not creatureID then
|
|
creatureID = group.crs[1];
|
|
end
|
|
else
|
|
creatureID = group.crs[1];
|
|
end
|
|
end
|
|
end
|
|
if creatureID then
|
|
if not rootByCreatureID[creatureID] then
|
|
rootByCreatureID[creatureID] = group;
|
|
tinsert(root, app.CreateNPC(creatureID));
|
|
end
|
|
elseif objectID then
|
|
if not rootByObjectID[objectID] then
|
|
rootByObjectID[objectID] = group;
|
|
tinsert(root, app.CreateObject(objectID));
|
|
end
|
|
else
|
|
tinsert(root, group);
|
|
end
|
|
end
|
|
|
|
local first = root[1];
|
|
if first then
|
|
local opt = { from = "ATT", persistent = false };
|
|
opt.title = first.text or RETRIEVING_DATA;
|
|
local displayID = GetDisplayID(first);
|
|
if displayID then
|
|
opt.minimap_displayID = displayID;
|
|
opt.worldmap_displayID = displayID;
|
|
end
|
|
if first.icon then
|
|
opt.minimap_icon = first.icon;
|
|
opt.worldmap_icon = first.icon;
|
|
end
|
|
|
|
if TomTom.DefaultCallbacks then
|
|
local callbacks = TomTom:DefaultCallbacks();
|
|
callbacks.minimap.tooltip_update = nil;
|
|
callbacks.minimap.tooltip_show = function(event, tooltip, uid, dist)
|
|
tooltip:ClearLines();
|
|
for i,o in ipairs(root) do
|
|
local line = tooltip:NumLines() + 1;
|
|
tooltip:AddLine(o.text);
|
|
if o.title and not o.explorationID then tooltip:AddLine(o.title); end
|
|
local key = o.key;
|
|
if key == "objectiveID" then
|
|
if o.parent and o.parent.questID then tooltip:AddLine("Objective for " .. o.parent.text); end
|
|
elseif key == "criteriaID" then
|
|
tooltip:AddDoubleLine(L.CRITERIA_FOR, GetAchievementLink(group.achievementID));
|
|
else
|
|
if key == "npcID" then key = "creatureID"; end
|
|
AttachTooltipSearchResults(tooltip, 1, key .. ":" .. o[o.key], SearchForField, key, o[o.key], line);
|
|
end
|
|
end
|
|
tooltip:Show();
|
|
end
|
|
callbacks.world.tooltip_update = nil;
|
|
callbacks.world.tooltip_show = callbacks.minimap.tooltip_show;
|
|
opt.callbacks = callbacks;
|
|
end
|
|
TomTom:AddWaypoint(mapID, xnormal, y / 1000, opt);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
TomTom:SetClosestWaypoint();
|
|
end
|
|
if C_SuperTrack and group.questID and C_QuestLog_IsOnQuest(group.questID) then
|
|
C_SuperTrack.SetSuperTrackedQuestID(group.questID);
|
|
end
|
|
else
|
|
app.print("You must have TomTom installed to plot coordinates.");
|
|
end
|
|
end
|
|
|
|
-- Minimap Button
|
|
function AllTheThings_MinimapButtonOnClick(self, button)
|
|
if button == "RightButton" then
|
|
-- Right Button opens the Options menu.
|
|
app.Settings:Open();
|
|
else
|
|
-- Left Button
|
|
if IsShiftKeyDown() then
|
|
app.RefreshCollections();
|
|
elseif IsAltKeyDown() or IsControlKeyDown() then
|
|
app.ToggleMiniListForCurrentZone();
|
|
else
|
|
app.ToggleMainList();
|
|
end
|
|
end
|
|
end
|
|
function AllTheThings_MinimapButtonOnEnter(self, button)
|
|
GameTooltip:SetOwner(type(self) ~= "string" and self or button, "ANCHOR_LEFT");
|
|
GameTooltip:ClearLines();
|
|
|
|
local reference = app:GetDataCache();
|
|
if reference then
|
|
GameTooltipIcon:SetSize(72,72);
|
|
GameTooltipIcon:ClearAllPoints();
|
|
GameTooltipIcon:SetPoint("TOPRIGHT", GameTooltip, "TOPLEFT", 0, 0);
|
|
GameTooltipIcon.icon:SetTexture(reference.preview or reference.icon);
|
|
local texcoord = reference.texcoord;
|
|
if texcoord then
|
|
GameTooltipIcon.icon:SetTexCoord(texcoord[1], texcoord[2], texcoord[3], texcoord[4]);
|
|
else
|
|
GameTooltipIcon.icon:SetTexCoord(0, 1, 0, 1);
|
|
end
|
|
GameTooltipIcon:Show();
|
|
|
|
local left, right = strsplit(DESCRIPTION_SEPARATOR, reference.title);
|
|
GameTooltip:AddDoubleLine(reference.text, reference.progressText, 1, 1, 1);
|
|
GameTooltip:AddDoubleLine(left, right, 1, 1, 1);
|
|
|
|
local prime = app:GetWindow("Prime");
|
|
if prime and prime.forceFullDataRefresh then
|
|
GameTooltip:AddDoubleLine("Updates Paused", L["MAIN_LIST_REQUIRES_REFRESH"], 1, 0.4, 0.4);
|
|
else
|
|
GameTooltip:AddLine(reference.description, 0.4, 0.8, 1, 1);
|
|
end
|
|
else
|
|
GameTooltip:AddDoubleLine(L["TITLE"], "Click to load addon.", 1, 1, 1);
|
|
GameTooltipIcon:Hide();
|
|
end
|
|
GameTooltip:AddLine(L["MINIMAP_MOUSEOVER_TEXT"], 1, 1, 1);
|
|
GameTooltip:Show();
|
|
end
|
|
function AllTheThings_MinimapButtonOnLeave()
|
|
GameTooltip:Hide();
|
|
GameTooltipIcon.icon.Background:Hide();
|
|
GameTooltipIcon.icon.Border:Hide();
|
|
GameTooltipIcon:Hide();
|
|
GameTooltipModel:Hide();
|
|
end
|
|
local function CreateMinimapButton()
|
|
-- Create the Button for the Minimap frame. Create a local and non-local copy.
|
|
local size = app.Settings:GetTooltipSetting("MinimapSize");
|
|
local button = CreateFrame("BUTTON", appName .. "-Minimap", Minimap);
|
|
button:SetPoint("CENTER", 0, 0);
|
|
button:SetFrameStrata("HIGH");
|
|
button:SetMovable(true);
|
|
button:EnableMouse(true);
|
|
button:RegisterForDrag("LeftButton", "RightButton");
|
|
button:RegisterForClicks("LeftButtonUp", "RightButtonUp");
|
|
button:SetSize(size, size);
|
|
|
|
-- Create the Button Texture
|
|
local texture = button:CreateTexture(nil, "BACKGROUND");
|
|
texture:SetTexture(app.asset("Discord_2_64"));
|
|
texture:SetAllPoints();
|
|
button.texture = texture;
|
|
|
|
-- Create the Button Texture
|
|
local oldtexture = button:CreateTexture(nil, "BACKGROUND");
|
|
oldtexture:SetPoint("CENTER", 0, 0);
|
|
oldtexture:SetTexture(app.asset("logo_tiny"));
|
|
oldtexture:SetSize(21, 21);
|
|
button.oldtexture = oldtexture;
|
|
|
|
-- Create the Button Tracking Border
|
|
local border = button:CreateTexture(nil, "BORDER");
|
|
border:SetTexture("Interface\\Minimap\\MiniMap-TrackingBorder");
|
|
border:SetPoint("CENTER", 12, -12);
|
|
border:SetSize(56, 56);
|
|
button.border = border;
|
|
button.UpdateStyle = function(self)
|
|
if app.Settings:GetTooltipSetting("MinimapStyle") then
|
|
self:SetHighlightTexture("Interface\\Minimap\\UI-Minimap-ZoomButton-Highlight", "ADD");
|
|
self.texture:Hide();
|
|
self.oldtexture:Show();
|
|
self.border:Show();
|
|
else
|
|
self:SetHighlightTexture(app.asset("MinimapHighlight_64x64"));
|
|
self.texture:Show();
|
|
self.oldtexture:Hide();
|
|
self.border:Hide();
|
|
end
|
|
end
|
|
button:UpdateStyle();
|
|
|
|
-- Button Configuration
|
|
local rounding = 10;
|
|
local MinimapShapes = {
|
|
-- quadrant booleans (same order as SetTexCoord)
|
|
-- {bottom-right, bottom-left, top-right, top-left}
|
|
-- true = rounded, false = squared
|
|
["ROUND"] = {true, true, true, true },
|
|
["SQUARE"] = {false, false, false, false},
|
|
["CORNER-TOPLEFT"] = {false, false, false, true },
|
|
["CORNER-TOPRIGHT"] = {false, false, true, false},
|
|
["CORNER-BOTTOMLEFT"] = {false, true, false, false},
|
|
["CORNER-BOTTOMRIGHT"] = {true, false, false, false},
|
|
["SIDE-LEFT"] = {false, true, false, true },
|
|
["SIDE-RIGHT"] = {true, false, true, false},
|
|
["SIDE-TOP"] = {false, false, true, true },
|
|
["SIDE-BOTTOM"] = {true, true, false, false},
|
|
["TRICORNER-TOPLEFT"] = {false, true, true, true },
|
|
["TRICORNER-TOPRIGHT"] = {true, false, true, true },
|
|
["TRICORNER-BOTTOMLEFT"] = {true, true, false, true },
|
|
["TRICORNER-BOTTOMRIGHT"] = {true, true, true, false},
|
|
};
|
|
button.update = function(self)
|
|
local position = GetDataMember("Position", -10.31);
|
|
local angle = math.rad(position) -- determine position on your own
|
|
local x, y
|
|
local cos = math.cos(angle)
|
|
local sin = math.sin(angle)
|
|
local q = 1;
|
|
if cos < 0 then
|
|
q = q + 1; -- lower
|
|
end
|
|
if sin > 0 then
|
|
q = q + 2; -- right
|
|
end
|
|
local width = (Minimap:GetWidth() * 0.5) + 8;
|
|
local height = (Minimap:GetHeight() * 0.5) + 8;
|
|
if MinimapShapes[GetMinimapShape and GetMinimapShape() or "ROUND"][q] then
|
|
x = cos*width;
|
|
y = sin*height;
|
|
else
|
|
x = math.max(-width, math.min(cos*(math.sqrt(2*(width)^2)-rounding), width))
|
|
y = math.max(-height, math.min(sin*(math.sqrt(2*(height)^2)-rounding), height))
|
|
end
|
|
self:SetPoint("CENTER", "Minimap", "CENTER", -math.floor(x), math.floor(y));
|
|
end
|
|
local update = function(self)
|
|
local w, x = GetCursorPosition();
|
|
local y, z = Minimap:GetLeft(), Minimap:GetBottom();
|
|
local s = UIParent:GetScale();
|
|
w = y - w / s + 70; x = x / s - z - 70;
|
|
SetDataMember("Position", math.deg(math.atan2(x, w)));
|
|
self:Raise();
|
|
self:update();
|
|
end
|
|
|
|
-- Register for Frame Events
|
|
button:SetScript("OnDragStart", function(self)
|
|
self:SetScript("OnUpdate", update);
|
|
end);
|
|
button:SetScript("OnDragStop", function(self)
|
|
self:SetScript("OnUpdate", nil);
|
|
end);
|
|
button:SetScript("OnEnter", AllTheThings_MinimapButtonOnEnter);
|
|
button:SetScript("OnLeave", AllTheThings_MinimapButtonOnLeave);
|
|
button:SetScript("OnClick", AllTheThings_MinimapButtonOnClick);
|
|
button:update();
|
|
button:Show();
|
|
return button;
|
|
end
|
|
app.CreateMinimapButton = CreateMinimapButton;
|
|
|
|
-- Row Helper Functions
|
|
local SetPortraitTexture = _G["SetPortraitTexture"];
|
|
local SetPortraitTextureFromDisplayID = _G["SetPortraitTextureFromCreatureDisplayID"];
|
|
local function SetPortraitIcon(self, data, x)
|
|
local displayID = GetDisplayID(data);
|
|
if displayID then
|
|
SetPortraitTextureFromDisplayID(self, displayID);
|
|
self:SetWidth(self:GetHeight());
|
|
self:SetTexCoord(0, 1, 0, 1);
|
|
return true;
|
|
elseif data.unit and not data.icon then
|
|
SetPortraitTexture(self, data.unit);
|
|
self:SetWidth(self:GetHeight());
|
|
self:SetTexCoord(0, 1, 0, 1);
|
|
return true;
|
|
end
|
|
|
|
-- Fallback to a traditional icon.
|
|
if data.atlas then
|
|
self:SetAtlas(data.atlas);
|
|
self:SetWidth(self:GetHeight());
|
|
self:SetTexCoord(0, 1, 0, 1);
|
|
if data["atlas-background"] then
|
|
self.Background:SetAtlas(data["atlas-background"]);
|
|
self.Background:SetWidth(self:GetHeight());
|
|
self.Background:Show();
|
|
end
|
|
if data["atlas-border"] then
|
|
self.Border:SetAtlas(data["atlas-border"]);
|
|
self.Border:SetWidth(self:GetHeight());
|
|
self.Border:Show();
|
|
if data["atlas-color"] then
|
|
local swatches = data["atlas-color"];
|
|
self.Border:SetVertexColor(swatches[1], swatches[2], swatches[3], swatches[4] or 1.0);
|
|
else
|
|
self.Border:SetVertexColor(1, 1, 1, 1.0);
|
|
end
|
|
end
|
|
return true;
|
|
elseif data.icon then
|
|
self:SetWidth(self:GetHeight());
|
|
self:SetTexture(data.icon);
|
|
local texcoord = data.texcoord;
|
|
if texcoord then
|
|
self:SetTexCoord(texcoord[1], texcoord[2], texcoord[3], texcoord[4]);
|
|
else
|
|
self:SetTexCoord(0, 1, 0, 1);
|
|
end
|
|
return true;
|
|
end
|
|
end
|
|
local function CalculateRowBack(data)
|
|
if data.back then return data.back; end
|
|
if data.parent then
|
|
return CalculateRowBack(data.parent) * 0.5;
|
|
else
|
|
return 0;
|
|
end
|
|
end
|
|
local function CalculateRowIndent(data)
|
|
if data.indent then return data.indent; end
|
|
if data.parent then
|
|
return CalculateRowIndent(data.parent) + 1;
|
|
else
|
|
return 0;
|
|
end
|
|
end
|
|
|
|
local CreateRow;
|
|
local function SetRowData(self, row, data)
|
|
if row.ref ~= data then
|
|
-- New data, update everything
|
|
row.ref = data;
|
|
row.summaryText = nil;
|
|
if not data then
|
|
row.Background:SetAlpha(0);
|
|
row.Background:Hide();
|
|
row.Texture:Hide();
|
|
row.Texture.Background:Hide();
|
|
row.Texture.Border:Hide();
|
|
row.Indicator:Hide();
|
|
row.Summary:Hide();
|
|
row.Label:Hide();
|
|
row:Hide();
|
|
return;
|
|
end
|
|
|
|
local font = data.font or "GameFontNormal";
|
|
if font ~= row.lastFont then
|
|
row.Label:SetFontObject(font);
|
|
row.Summary:SetFontObject(font);
|
|
row.lastFont = font;
|
|
end
|
|
|
|
-- Every valid row has a summary and label
|
|
row.Label:SetPoint("RIGHT", row.Summary, "LEFT", 0, 0);
|
|
row.Summary:Show();
|
|
row.Label:Show();
|
|
row:Show();
|
|
|
|
-- Calculate the indent
|
|
local indent = ((CalculateRowIndent(data) * 8) or 0) + 8;
|
|
row.Texture.Background:SetPoint("LEFT", row, "LEFT", indent, 0);
|
|
row.Texture.Border:SetPoint("LEFT", row, "LEFT", indent, 0);
|
|
row.Texture:SetPoint("LEFT", row, "LEFT", indent, 0);
|
|
row.indent = indent;
|
|
|
|
-- Calculate the back color
|
|
local back = CalculateRowBack(data);
|
|
if back then
|
|
row.back = back;
|
|
if back > 0 then
|
|
row.Background:SetAlpha(back);
|
|
row.Background:Show();
|
|
else
|
|
row.Background:Hide();
|
|
end
|
|
end
|
|
elseif not data then
|
|
return; -- Already cleared
|
|
end
|
|
|
|
-- Update the Summary Text (this will be the thing that updates the most)
|
|
local summary = data.summary or GetProgressTextForRow(data);
|
|
local oldSummary = row.summaryText;
|
|
if oldSummary then
|
|
if summary then
|
|
if oldSummary ~= summary then
|
|
row.Summary:SetText(summary);
|
|
row.summaryText = summary;
|
|
self.smudged = true; -- Mark this as smudged, so that it knowns to Update the rows completely. (this means that something changed its state)
|
|
end
|
|
else
|
|
row.Summary:SetText((data.g and not data.expanded and #data.g > 0 and "+++") or "---");
|
|
row.summaryText = nil;
|
|
self.smudged = true; -- Mark this as smudged, so that it knowns to Update the rows completely. (this means that something changed its state)
|
|
end
|
|
else
|
|
if summary then
|
|
row.Summary:SetText(summary);
|
|
row.summaryText = summary;
|
|
else
|
|
row.Summary:SetText((data.g and not data.expanded and #data.g > 0 and "+++") or "---");
|
|
end
|
|
end
|
|
|
|
-- Determine the Indicator Texture
|
|
-- TODO: Move this to a field?
|
|
local indicatorTexture = data.e and L["UNOBTAINABLE_ITEM_TEXTURES"][4];
|
|
if data.u then
|
|
local reason = L["UNOBTAINABLE_ITEM_REASONS"][data.u];
|
|
if reason and (not reason[5] or app.GameBuildVersion < reason[5]) then
|
|
indicatorTexture = L["UNOBTAINABLE_ITEM_TEXTURES"][reason[1]];
|
|
end
|
|
end
|
|
|
|
-- If data is quest and is currently accepted or saved...
|
|
if data.questID and C_QuestLog_IsOnQuest(data.questID) then
|
|
indicatorTexture = app.asset("known_circle");
|
|
elseif data.saved then
|
|
if data.parent and data.parent.locks or data.isDaily then
|
|
indicatorTexture = app.asset("known");
|
|
else
|
|
indicatorTexture = app.asset("known_green");
|
|
end
|
|
end
|
|
|
|
-- Check to see what the text is currently
|
|
local text = data.text;
|
|
if text ~= row.text then
|
|
if not text then
|
|
text = RETRIEVING_DATA;
|
|
self.processingLinks = true;
|
|
elseif string.match(text, RETRIEVING_DATA) or text:find("^%[%]") or text:find("%[]") then
|
|
-- This means the link is still rendering
|
|
self.processingLinks = true;
|
|
else
|
|
row.text = text;
|
|
end
|
|
row.Label:SetText(text);
|
|
row:SetHeight(select(2, row.Label:GetFont()) + 4);
|
|
end
|
|
|
|
-- If the data has a texture, assign it.
|
|
if SetPortraitIcon(row.Texture, data) and row.Texture:GetTextureFilePath() then
|
|
row.Texture:Show();
|
|
row.Label:SetPoint("LEFT", row.Texture, "RIGHT", 2, 0);
|
|
|
|
-- If we have a texture, let's assign it.
|
|
if indicatorTexture then
|
|
row.Indicator:SetTexture(indicatorTexture);
|
|
row.Indicator:SetPoint("RIGHT", row.Texture, "LEFT", -2, 0);
|
|
row.Indicator:Show();
|
|
else
|
|
row.Indicator:Hide();
|
|
end
|
|
else
|
|
row.Texture:Hide();
|
|
row.Label:SetPoint("LEFT", row, "LEFT", row.indent, 0);
|
|
|
|
-- If we have a texture, let's assign it.
|
|
if indicatorTexture then
|
|
row.Indicator:SetTexture(indicatorTexture);
|
|
row.Indicator:SetPoint("RIGHT", row, "LEFT", row.indent, 0);
|
|
row.Indicator:Show();
|
|
else
|
|
row.Indicator:Hide();
|
|
end
|
|
end
|
|
end
|
|
local function RedrawVisibleRowData(self)
|
|
-- If there is no raw data, then return immediately.
|
|
if not self.rowData then return; end
|
|
|
|
-- Make it so that if you scroll all the way down, you have the ability to see all of the text every time.
|
|
local totalRowCount = #self.rowData;
|
|
if totalRowCount > 0 then
|
|
-- Ensure that the first row doesn't move out of position.
|
|
local container = self.Container;
|
|
local row = container.rows[1];
|
|
if not row then return; end
|
|
SetRowData(self, row, row.ref);
|
|
|
|
-- Fill the remaining rows up to the (visible) row count.
|
|
local containerHeight, totalHeight = container:GetHeight(), row:GetHeight();
|
|
for i=2,totalRowCount do
|
|
row = container.rows[i];
|
|
if row then
|
|
SetRowData(self, row, row.ref);
|
|
totalHeight = totalHeight + row:GetHeight();
|
|
if totalHeight > containerHeight then
|
|
break;
|
|
end
|
|
else
|
|
break;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
local function UpdateRowProgress(group)
|
|
if group.collectible then
|
|
group.progress = group.collected and 1 or 0;
|
|
group.total = 1;
|
|
else
|
|
group.progress = 0;
|
|
group.total = 0;
|
|
end
|
|
if group.g then
|
|
for i,subgroup in ipairs(group.g) do
|
|
UpdateRowProgress(subgroup);
|
|
if subgroup.total then
|
|
group.progress = group.progress + subgroup.progress;
|
|
group.total = group.total + subgroup.total;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
local function UpdateVisibleRowData(self)
|
|
-- If there is no raw data, then return immediately.
|
|
if not self.rowData then return; end
|
|
if self:GetHeight() > 48 then self.ScrollBar:Show(); else self.ScrollBar:Hide(); end
|
|
|
|
-- Make it so that if you scroll all the way down, you have the ability to see all of the text every time.
|
|
local totalRowCount = #self.rowData;
|
|
if totalRowCount > 0 then
|
|
-- Ensure that the first row doesn't move out of position.
|
|
local container = self.Container;
|
|
local row = container.rows[1] or CreateRow(container);
|
|
SetRowData(self, row, self.rowData[1]);
|
|
|
|
-- Fill the remaining rows up to the (visible) row count.
|
|
local current, rowCount, containerHeight, totalHeight =
|
|
math.max(1, math.min(self.CurrentIndex, totalRowCount)) + 1, 1, container:GetHeight(), row:GetHeight();
|
|
|
|
for i=2,totalRowCount do
|
|
row = container.rows[i] or CreateRow(container);
|
|
SetRowData(self, row, self.rowData[current]);
|
|
totalHeight = totalHeight + row:GetHeight();
|
|
if totalHeight > containerHeight then
|
|
break;
|
|
else
|
|
current = current + 1;
|
|
rowCount = rowCount + 1;
|
|
end
|
|
end
|
|
|
|
-- Hide the extra rows if any exist
|
|
for i=math.max(2, rowCount + 1),#container.rows do
|
|
local row = container.rows[i];
|
|
if row.ref then
|
|
SetRowData(self, row, nil);
|
|
else
|
|
break;
|
|
end
|
|
end
|
|
self:SetMinMaxValues(rowCount, totalRowCount + 1);
|
|
|
|
-- The data is smudged, meaning it needs to be Updated.
|
|
if self.smudged then
|
|
self.smudged = nil;
|
|
self:Update(true);
|
|
end
|
|
|
|
-- If the rows need to be processed again, do so next update.
|
|
if self.processingLinks then
|
|
self:StartATTCoroutine("Process Links", function()
|
|
while self.processingLinks do
|
|
self.processingLinks = nil;
|
|
coroutine.yield();
|
|
self:Redraw();
|
|
end
|
|
if self.UpdateDone then
|
|
self:StartATTCoroutine("UpdateDone", function()
|
|
coroutine.yield();
|
|
self:StartATTCoroutine("UpdateDoneP2", function()
|
|
coroutine.yield();
|
|
self:UpdateDone();
|
|
end);
|
|
end);
|
|
end
|
|
end);
|
|
elseif self.UpdateDone and rowCount > 5 then
|
|
self:StartATTCoroutine("UpdateDone", function()
|
|
coroutine.yield();
|
|
self:StartATTCoroutine("UpdateDoneP2", function()
|
|
coroutine.yield();
|
|
self:UpdateDone();
|
|
end);
|
|
end);
|
|
end
|
|
else
|
|
self:Hide();
|
|
end
|
|
end
|
|
local function IsSelfOrChild(self, focus)
|
|
-- This function helps validate that the focus is within the local hierarchy.
|
|
return focus and (self == focus or IsSelfOrChild(self, focus:GetParent()));
|
|
end
|
|
local function StopMovingOrSizing(self)
|
|
if self.isMoving then
|
|
self:StopMovingOrSizing();
|
|
self.isMoving = false;
|
|
self:RecordSettings();
|
|
end
|
|
end
|
|
local function StartMovingOrSizing(self)
|
|
if not (self:IsMovable() or self:IsResizable()) then
|
|
return
|
|
end
|
|
if self.isMoving then
|
|
StopMovingOrSizing(self);
|
|
else
|
|
self.isMoving = true;
|
|
if not self:IsMovable() or ((select(2, GetCursorPosition()) / self:GetEffectiveScale()) < math.max(self:GetTop() - 40, self:GetBottom() + 10)) then
|
|
self:StartSizing();
|
|
self:StartATTCoroutine("StartMovingOrSizing (Sizing)", function()
|
|
while self.isMoving do
|
|
self:Refresh();
|
|
coroutine.yield();
|
|
end
|
|
end);
|
|
else
|
|
self:StartMoving();
|
|
self:StartATTCoroutine("StartMovingOrSizing (Moving)", function()
|
|
while IsSelfOrChild(self, GetMouseFocus()) do
|
|
coroutine.yield();
|
|
end
|
|
StopMovingOrSizing(self);
|
|
end);
|
|
end
|
|
end
|
|
end
|
|
local function RowOnClick(self, button)
|
|
local reference = self.ref;
|
|
if reference then
|
|
-- If the row data itself has an OnClick handler... execute that first.
|
|
if reference.OnClick and reference.OnClick(self, button) then
|
|
return true;
|
|
end
|
|
|
|
if IsShiftKeyDown() then
|
|
if button == "RightButton" then
|
|
if app.Settings:GetTooltipSetting("Sort:Progress") then
|
|
app.print("Sorting selection by total progress...");
|
|
app:StartATTCoroutine("Sorting", function() app.SortGroup(reference, "progress", self, false) end);
|
|
else
|
|
app.print("Sorting selection alphabetically...");
|
|
app:StartATTCoroutine("Sorting", function() app.SortGroup(reference, "name", self, false) end);
|
|
end
|
|
return true;
|
|
end
|
|
|
|
-- If we're at the Auction House
|
|
if (AuctionFrame and AuctionFrame:IsShown()) or (AuctionHouseFrame and AuctionHouseFrame:IsShown()) then
|
|
local search = SearchForMissingItemNames(reference);
|
|
local count = #search;
|
|
if count < 1 then
|
|
app.print("No cached items found in search. Expand the group and view the items to cache the names and try again. Only Bind on Equip items will be found using this search.");
|
|
return true;
|
|
end
|
|
|
|
-- Auctionator Support
|
|
if TSM_API and TSM_API.IsUIVisible("AUCTION") then
|
|
app.print("TSM is not currently supported as the API for Classic is really limited.");
|
|
return true;
|
|
elseif Atr_SearchAH then
|
|
Atr_SelectPane(3);
|
|
if count > 1 then
|
|
Atr_SearchAH(L["TITLE"], search, LE_ITEM_CLASS_ARMOR);
|
|
return true;
|
|
else
|
|
Atr_SetSearchText (search[1]);
|
|
Atr_Search_Onclick ();
|
|
return true;
|
|
end
|
|
elseif Auctionator and Auctionator.API and AuctionatorTabs_Shopping then
|
|
Auctionator.API.v1.MultiSearchExact(L["TITLE"], search);
|
|
return;
|
|
end
|
|
|
|
-- Attempt to search manually with the link.
|
|
local link = reference.link or reference.silentLink;
|
|
if link and HandleModifiedItemClick(link) then
|
|
AuctionFrameBrowse_Search();
|
|
end
|
|
return true;
|
|
else
|
|
-- Not at the Auction House
|
|
-- If this reference has a link, then attempt to preview the appearance or write to the chat window.
|
|
local link = reference.link or reference.silentLink;
|
|
if link then
|
|
if HandleModifiedItemClick(link) or ChatEdit_InsertLink(link or BuildSourceTextForChat(reference, 0)) then return true; end
|
|
local _, dialog = StaticPopup_Visible("ALL_THE_THINGS_EDITBOX");
|
|
if dialog then dialog.editBox:SetText(link); return true; end
|
|
end
|
|
if button == "LeftButton" then RefreshCollections(); end
|
|
return true;
|
|
end
|
|
end
|
|
|
|
-- Control Click Expands the Groups
|
|
if IsControlKeyDown() then
|
|
-- If this reference has a link, then attempt to preview the appearance.
|
|
local link = reference.link or reference.silentLink;
|
|
if link and HandleModifiedItemClick(link) then
|
|
return true;
|
|
end
|
|
|
|
-- If this reference is anything else, expand the groups.
|
|
if reference.g then
|
|
if self.index < 1 and #reference.g > 0 then
|
|
ExpandGroupsRecursively(reference, not reference.g[1].expanded, true);
|
|
else
|
|
ExpandGroupsRecursively(reference, not reference.expanded, true);
|
|
end
|
|
self:GetParent():GetParent():Update();
|
|
return true;
|
|
end
|
|
end
|
|
|
|
-- All non-Shift Right Clicks open a mini list or the settings.
|
|
if button == "RightButton" then
|
|
if IsAltKeyDown() then
|
|
AddTomTomWaypoint(reference, false);
|
|
elseif self.index > 0 then
|
|
app:CreateMiniListForGroup(self.ref);
|
|
else
|
|
app.Settings:Open();
|
|
end
|
|
elseif self.index > 0 then
|
|
reference.expanded = not reference.expanded;
|
|
self:GetParent():GetParent():Update();
|
|
elseif not reference.expanded then
|
|
reference.expanded = true;
|
|
self:GetParent():GetParent():Update();
|
|
else
|
|
-- Allow the First Frame to move the parent.
|
|
local owner = self:GetParent():GetParent();
|
|
if owner:IsMovable() then
|
|
self:SetScript("OnMouseUp", function(self)
|
|
self:SetScript("OnMouseUp", nil);
|
|
StopMovingOrSizing(owner);
|
|
end);
|
|
StartMovingOrSizing(owner);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
local function RowOnEnter(self)
|
|
local reference = self.ref; -- NOTE: This is the good ref value, not the parasitic one.
|
|
if reference and GameTooltip then
|
|
GameTooltipIcon.icon.Background:Hide();
|
|
GameTooltipIcon.icon.Border:Hide();
|
|
GameTooltipIcon:Hide();
|
|
GameTooltipModel:Hide();
|
|
GameTooltip:ClearLines();
|
|
GameTooltipIcon:ClearAllPoints();
|
|
GameTooltipModel:ClearAllPoints();
|
|
if self:GetCenter() > (UIParent:GetWidth() / 2) then
|
|
GameTooltip:SetOwner(self, "ANCHOR_LEFT");
|
|
GameTooltipIcon:SetPoint("TOPRIGHT", GameTooltip, "TOPLEFT", 0, 0);
|
|
GameTooltipModel:SetPoint("TOPRIGHT", GameTooltip, "TOPLEFT", 0, 0);
|
|
else
|
|
GameTooltip:SetOwner(self, "ANCHOR_RIGHT");
|
|
GameTooltipIcon:SetPoint("TOPLEFT", GameTooltip, "TOPRIGHT", 0, 0);
|
|
GameTooltipModel:SetPoint("TOPLEFT", GameTooltip, "TOPRIGHT", 0, 0);
|
|
end
|
|
|
|
-- NOTE: Order matters, we "fall-through" certain values in order to pass this information to the item ID section.
|
|
if not reference.creatureID then
|
|
if reference.itemID then
|
|
local link = reference.link;
|
|
if link and link ~= "" then
|
|
pcall(GameTooltip.SetHyperlink, GameTooltip, link);
|
|
else
|
|
GameTooltip:AddLine("Item #" .. reference.itemID);
|
|
if reference and reference.u then
|
|
local reason = L["UNOBTAINABLE_ITEM_REASONS"][reference.u];
|
|
if reason and (not reason[5] or app.GameBuildVersion < reason[5]) then GameTooltip:AddLine(reason[2], 1, 1, 1, true); end
|
|
end
|
|
if reference.e then
|
|
local reason = app.Modules.Events.GetEventTooltipNoteForGroup(reference);
|
|
if reason then
|
|
local left, right = strsplit(DESCRIPTION_SEPARATOR, reason);
|
|
if right then
|
|
GameTooltip:AddDoubleLine(left, right, 0.4, 0.8, 1, 0.4, 0.8, 1, 1);
|
|
else
|
|
GameTooltip:AddLine(left, 0.4, 0.8, 1, 1);
|
|
end
|
|
end
|
|
end
|
|
AttachTooltipSearchResults(GameTooltip, 1, "itemID:" .. reference.itemID, SearchForField, "itemID", reference.itemID);
|
|
end
|
|
elseif reference.currencyID then
|
|
GameTooltip:SetCurrencyByID(reference.currencyID, 1);
|
|
elseif reference.key ~= "questID" then
|
|
local link = reference.link;
|
|
if link then
|
|
pcall(GameTooltip.SetHyperlink, GameTooltip, link);
|
|
if reference.spellID then
|
|
local requireSkill = GetRelativeValue(reference, "requireSkill");
|
|
if requireSkill == 333 then
|
|
AttachTooltipSearchResults(GameTooltip, 1, "spellID:" .. reference.spellID, SearchForField, "spellID", reference.spellID);
|
|
elseif requireSkill == 960 then
|
|
GameTooltip:AddLine(GameTooltipTextLeft1:GetText(), 1, 1, 1, true);
|
|
GameTooltipTextLeft1:SetText(reference.name);
|
|
GameTooltip:Show();
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Miscellaneous fields
|
|
if GameTooltip:NumLines() < 1 then
|
|
GameTooltip:AddLine(reference.text);
|
|
end
|
|
if app.Settings:GetTooltipSetting("Progress") then
|
|
if reference.trackable and reference.total and reference.total >= 2 then
|
|
GameTooltip:AddDoubleLine("Tracking Progress", GetCompletionText(reference.saved));
|
|
end
|
|
end
|
|
|
|
-- Relative ATT location
|
|
if reference.parent and not reference.itemID then
|
|
if reference.parent.parent then
|
|
GameTooltip:AddDoubleLine(reference.parent.parent.text or RETRIEVING_DATA, reference.parent.text or RETRIEVING_DATA);
|
|
else
|
|
--GameTooltip:AddLine(reference.parent.text or RETRIEVING_DATA, 1, 1, 1);
|
|
end
|
|
end
|
|
|
|
local linesByText = {};
|
|
for i=1,GameTooltip:NumLines() do
|
|
linesByText[_G["GameTooltipTextLeft"..i]:GetText()] = true;
|
|
end
|
|
|
|
local title = reference.title;
|
|
if title then
|
|
local left, right = strsplit(DESCRIPTION_SEPARATOR, title);
|
|
if right then
|
|
GameTooltip:AddDoubleLine(left, right, 1, 1, 1);
|
|
else
|
|
GameTooltip:AddLine(title, 1, 1, 1);
|
|
end
|
|
elseif reference.retries then
|
|
GameTooltip:AddLine("Failed to acquire information. This quest may have been removed from the game. " .. tostring(reference.retries), 1, 1, 1);
|
|
end
|
|
if reference.lvl then
|
|
local minlvl, maxlvl;
|
|
if type(reference.lvl) == "table" then
|
|
minlvl = reference.lvl[1] or 0;
|
|
maxlvl = reference.lvl[2] or 0;
|
|
else
|
|
minlvl = reference.lvl;
|
|
end
|
|
-- i suppose a maxlvl of 1 might exist?
|
|
if maxlvl and maxlvl > 0 then
|
|
GameTooltip:AddDoubleLine(L["REQUIRES_LEVEL"], tostring(minlvl) .. " to " .. tostring(maxlvl));
|
|
-- no point to show 'requires lvl 1'
|
|
elseif minlvl and minlvl > 1 then
|
|
GameTooltip:AddDoubleLine(L["REQUIRES_LEVEL"], tostring(minlvl));
|
|
end
|
|
end
|
|
if reference.b and app.Settings:GetTooltipSetting("binding") then GameTooltip:AddDoubleLine("Binding", tostring(reference.b)); end
|
|
if reference.requireSkill then GameTooltip:AddDoubleLine(L["REQUIRES"], GetSpellInfo(app.SkillIDToSpellID[reference.requireSkill] or 0) or RETRIEVING_DATA); end
|
|
if reference.f and reference.f > 0 and app.Settings:GetTooltipSetting("filterID") then
|
|
if reference.filterForRWP then
|
|
GameTooltip:AddDoubleLine(L["FILTER_ID"], tostring(L["FILTER_ID_TYPES"][reference.f]) .. " -> " .. tostring(L["FILTER_ID_TYPES"][reference.filterForRWP]));
|
|
else
|
|
GameTooltip:AddDoubleLine(L["FILTER_ID"], tostring(L["FILTER_ID_TYPES"][reference.f]));
|
|
end
|
|
end
|
|
if reference.achievementID and app.Settings:GetTooltipSetting("achievementID") then
|
|
GameTooltip:AddDoubleLine(L["ACHIEVEMENT_ID"], tostring(reference.achievementID));
|
|
if reference.sourceQuests and not (GetCategoryInfo and GetCategoryInfo(92) ~= "") then
|
|
GameTooltip:AddLine("This achievement has associated quests that can be completed before the introduction of the Achievement system coming with the Wrath Prepatch. Not all achievements can be tracked this way, but for those that can, they will be displayed. All other non-trackable achievements will be activated with the prepatch.", 0.4, 0.8, 1, true);
|
|
end
|
|
end
|
|
if app.Settings:GetTooltipSetting("creatureID") then
|
|
if reference.creatureID then
|
|
GameTooltip:AddDoubleLine(L["CREATURE_ID"], tostring(reference.creatureID));
|
|
elseif reference.npcID then
|
|
GameTooltip:AddDoubleLine(L["NPC_ID"], tostring(reference.npcID));
|
|
end
|
|
end
|
|
if reference.factionID and app.Settings:GetTooltipSetting("factionID") then GameTooltip:AddDoubleLine(L["FACTION_ID"], tostring(reference.factionID)); end
|
|
if reference.minReputation and not reference.maxReputation then
|
|
local standingId, offset = app.GetFactionStanding(reference.minReputation[2])
|
|
local msg = "Requires a minimum standing of"
|
|
if offset ~= 0 then msg = msg .. " " .. offset end
|
|
msg = msg .. " " .. app.GetFactionStandingText(standingId) .. " with " .. (GetFactionInfoByID(reference.minReputation[1]) or "the opposite faction") .. "."
|
|
GameTooltip:AddLine(msg);
|
|
end
|
|
if reference.maxReputation and not reference.minReputation then
|
|
local standingId, offset = app.GetFactionStanding(reference.maxReputation[2])
|
|
local msg = "Requires a standing lower than"
|
|
if offset ~= 0 then msg = msg .. " " .. offset end
|
|
msg = msg .. " " .. app.GetFactionStandingText(standingId) .. " with " .. (GetFactionInfoByID(reference.maxReputation[1]) or "the opposite faction") .. "."
|
|
GameTooltip:AddLine(msg);
|
|
end
|
|
if reference.minReputation and reference.maxReputation then
|
|
local minStandingId, minOffset = app.GetFactionStanding(reference.minReputation[2])
|
|
local maxStandingId, maxOffset = app.GetFactionStanding(reference.maxReputation[2])
|
|
if reference.maxReputation[1] == reference.minReputation[1] then
|
|
local msg = "Requires a standing between"
|
|
if minOffset ~= 0 then msg = msg .. " " .. minOffset end
|
|
msg = msg .. " " .. app.GetFactionStandingText(minStandingId) .. " and"
|
|
if maxOffset ~= 0 then msg = msg .. " " .. maxOffset end
|
|
msg = msg .. " " .. app.GetFactionStandingText(maxStandingId) .. " with " .. (GetFactionInfoByID(reference.minReputation[1]) or "the opposite faction") .. ".";
|
|
GameTooltip:AddLine(msg);
|
|
else
|
|
local msg = "Requires a minimum standing of"
|
|
if minOffset ~= 0 then msg = msg .. " " .. minOffset end
|
|
msg = msg .. " " .. app.GetFactionStandingText(minStandingId) .. " with " .. (GetFactionInfoByID(reference.minReputation[1]) or "the opposite faction") .. "."
|
|
GameTooltip:AddLine(msg);
|
|
|
|
msg = "Requires a standing lower than"
|
|
if maxOffset ~= 0 then msg = msg .. " " .. maxOffset end
|
|
msg = msg .. " " .. app.GetFactionStandingText(maxStandingId) .. " with " .. (GetFactionInfoByID(reference.maxReputation[1]) or "the opposite faction") .. "."
|
|
GameTooltip:AddLine(msg);
|
|
end
|
|
end
|
|
if reference.illusionID and app.Settings:GetTooltipSetting("illusionID") then GameTooltip:AddDoubleLine(L["ILLUSION_ID"], tostring(reference.illusionID)); end
|
|
if reference.objectID and app.Settings:GetTooltipSetting("objectID") then GameTooltip:AddDoubleLine(L["OBJECT_ID"], tostring(reference.objectID)); end
|
|
if reference.speciesID and app.Settings:GetTooltipSetting("speciesID") then GameTooltip:AddDoubleLine(L["SPECIES_ID"], tostring(reference.speciesID)); end
|
|
if reference.spellID then
|
|
if app.Settings:GetTooltipSetting("spellID") then GameTooltip:AddDoubleLine(L["SPELL_ID"], tostring(reference.spellID) .. " (" .. (app.GetSpellName(reference.spellID, reference.rank) or "??") .. ")"); end
|
|
|
|
-- If the item is a recipe, then show which characters know this recipe.
|
|
if not reference.collectible and app.Settings:GetTooltipSetting("KnownBy") then
|
|
local knownBy = {};
|
|
for _,character in pairs(ATTCharacterData) do
|
|
if character.ActiveSkills and not character.ignored then
|
|
local skills = character.ActiveSkills[reference.spellID];
|
|
if skills then tinsert(knownBy, { character, skills[1], skills[2] }); end
|
|
end
|
|
end
|
|
if #knownBy > 0 then
|
|
app.Sort(knownBy, function(a, b)
|
|
return a[2] > b[2];
|
|
end);
|
|
GameTooltip:AddLine("|c" .. app.Colors.TooltipDescription .. "Known by:|r");
|
|
for i,data in ipairs(knownBy) do
|
|
local character = data[1];
|
|
GameTooltip:AddDoubleLine(" " .. string.gsub(character and character.text or "???", "-" .. GetRealmName(), ""), data[2] .. " / " .. data[3]);
|
|
end
|
|
|
|
end
|
|
end
|
|
end
|
|
if reference.flightPathID and app.Settings:GetTooltipSetting("flightPathID") then GameTooltip:AddDoubleLine(L["FLIGHT_PATH_ID"], tostring(reference.flightPathID)); end
|
|
if reference.mapID and app.Settings:GetTooltipSetting("mapID") then GameTooltip:AddDoubleLine(L["MAP_ID"], tostring(reference.mapID)); end
|
|
if reference.explorationID and app.Settings:GetTooltipSetting("explorationID") then GameTooltip:AddDoubleLine(L["EXPLORATION_ID"], tostring(reference.explorationID)); end
|
|
if reference.artID and app.Settings:GetTooltipSetting("artID") then GameTooltip:AddDoubleLine(L["ART_ID"], tostring(reference.artID)); end
|
|
--if reference.hash then GameTooltip:AddDoubleLine("Hash", tostring(reference.hash)); end
|
|
if reference.coords and app.Settings:GetTooltipSetting("Coordinates") then
|
|
local currentMapID, j, str = app.CurrentMapID, 0;
|
|
for i,coord in ipairs(reference.coords) do
|
|
local x, y = coord[1], coord[2];
|
|
local mapID = coord[3] or currentMapID;
|
|
if mapID ~= currentMapID then
|
|
str = app.GetMapName(mapID) or "??";
|
|
if app.Settings:GetTooltipSetting("mapID") then
|
|
str = str .. " (" .. mapID .. ")";
|
|
end
|
|
str = str .. ": ";
|
|
else
|
|
str = "";
|
|
end
|
|
GameTooltip:AddDoubleLine(j == 0 and "Coordinates" or " ",
|
|
str.. GetNumberWithZeros(math.floor(x * 10) * 0.1, 1) .. ", " .. GetNumberWithZeros(math.floor(y * 10) * 0.1, 1), 1, 1, 1, 1, 1, 1);
|
|
j = j + 1;
|
|
if j > 8 then
|
|
break;
|
|
end
|
|
end
|
|
end
|
|
if reference.coord and app.Settings:GetTooltipSetting("Coordinates") then
|
|
GameTooltip:AddDoubleLine("Coordinate",
|
|
GetNumberWithZeros(math.floor(reference.coord[1] * 10) * 0.1, 1) .. ", " ..
|
|
GetNumberWithZeros(math.floor(reference.coord[2] * 10) * 0.1, 1), 1, 1, 1, 1, 1, 1);
|
|
end
|
|
if reference.providers then
|
|
local counter = 0;
|
|
for i,provider in pairs(reference.providers) do
|
|
local providerType = provider[1];
|
|
local providerID = provider[2] or 0;
|
|
local providerString = UNKNOWN;
|
|
if providerType == "o" then
|
|
providerString = app.ObjectNames[providerID] or reference.text or ("Object: " .. RETRIEVING_DATA)
|
|
if app.Settings:GetTooltipSetting("objectID") then
|
|
providerString = providerString .. ' (' .. providerID .. ')';
|
|
end
|
|
elseif providerType == "n" then
|
|
providerString = (providerID > 0 and app.NPCNameFromID[providerID]) or ("Creature: " .. RETRIEVING_DATA)
|
|
if app.Settings:GetTooltipSetting("creatureID") then
|
|
providerString = providerString .. ' (' .. providerID .. ')';
|
|
end
|
|
elseif providerType == "i" then
|
|
local _,name,_,_,_,_,_,_,_,icon = GetItemInfo(providerID);
|
|
providerString = (icon and ("|T" .. icon .. ":0|t") or "") .. (name or ("Item: " .. RETRIEVING_DATA));
|
|
if app.Settings:GetTooltipSetting("itemID") then
|
|
providerString = providerString .. ' (' .. providerID .. ')';
|
|
end
|
|
end
|
|
GameTooltip:AddDoubleLine(counter == 0 and "Provider(s)" or " ", providerString);
|
|
counter = counter + 1;
|
|
end
|
|
end
|
|
|
|
if not reference.itemID then
|
|
if reference.lore and app.Settings:GetTooltipSetting("Lore") then
|
|
local lore = reference.lore;
|
|
if not linesByText[lore] then
|
|
local r,g,b = HexToRGB(app.Colors.TooltipLore);
|
|
GameTooltip:AddLine(lore, r, g, b, 1);
|
|
end
|
|
end
|
|
local description = reference.description;
|
|
if description and app.Settings:GetTooltipSetting("Descriptions") then
|
|
if not linesByText[description] then
|
|
local r,g,b = HexToRGB(app.Colors.TooltipDescription);
|
|
GameTooltip:AddLine(description, r, g, b, 1);
|
|
end
|
|
if reference.maps then
|
|
local description,maps,umaps,name = "Maps: ",{},{};
|
|
for i=1,#reference.maps,1 do
|
|
name = app.GetMapName(reference.maps[i]);
|
|
if name and not umaps[name] then
|
|
umaps[name] = true;
|
|
tinsert(maps, name);
|
|
end
|
|
end
|
|
for i,name in ipairs(maps) do
|
|
if i > 1 then description = description .. ", "; end
|
|
description = description .. name;
|
|
end
|
|
GameTooltip:AddLine(" ", 1, 1, 1, 1);
|
|
local r,g,b = HexToRGB(app.Colors.TooltipDescription);
|
|
GameTooltip:AddLine(description, r, g, b, 1);
|
|
end
|
|
elseif reference.maps then
|
|
local description,maps,umaps,name = "Maps: ",{},{};
|
|
for i=1,#reference.maps,1 do
|
|
name = app.GetMapName(reference.maps[i]);
|
|
if name and not umaps[name] then
|
|
umaps[name] = true;
|
|
tinsert(maps, name);
|
|
end
|
|
end
|
|
for i,name in ipairs(maps) do
|
|
if i > 1 then description = description .. ", "; end
|
|
description = description .. name;
|
|
end
|
|
local r,g,b = HexToRGB(app.Colors.TooltipDescription);
|
|
GameTooltip:AddLine(description, r, g, b, 1);
|
|
end
|
|
if reference.nextEvent then
|
|
local timeStrings = app.Modules.Events.GetEventTimeStrings(reference.nextEvent);
|
|
if timeStrings then
|
|
local r,g,b = HexToRGB(app.Colors.TooltipDescription);
|
|
for i,timeString in ipairs(timeStrings) do
|
|
local left, right = strsplit(DESCRIPTION_SEPARATOR, timeString);
|
|
if right then
|
|
GameTooltip:AddDoubleLine(left, right, r, g, b, r, g, b, 1);
|
|
else
|
|
GameTooltip:AddLine(left, r, g, b, 1);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
local awp, rwp = GetRelativeValue(reference, "awp"), reference.rwp;
|
|
if rwp then
|
|
local rwpString = GetRemovedWithPatchString(rwp);
|
|
if not linesByText[rwpString] then
|
|
local r,g,b = HexToRGB(app.Colors.RemovedWithPatch);
|
|
GameTooltip:AddLine(rwpString, r, g, b, 1);
|
|
end
|
|
end
|
|
if awp and ((rwp or (reference.u and reference.u < 3)) or awp >= app.GameBuildVersion) then
|
|
local awpString = GetAddedWithPatchString(awp, awp and rwp and awp > rwp);
|
|
if awpString and not linesByText[awpString] then
|
|
local r,g,b = HexToRGB(app.Colors.AddedWithPatch);
|
|
GameTooltip:AddLine(awpString, r, g, b, 1);
|
|
end
|
|
end
|
|
if reference.questID and not reference.objectiveID then
|
|
app.AddQuestObjectivesToTooltip(GameTooltip, reference);
|
|
end
|
|
if reference.u then
|
|
local reason = L["UNOBTAINABLE_ITEM_REASONS"][reference.u];
|
|
if reason and (not reason[5] or app.GameBuildVersion < reason[5]) then GameTooltip:AddLine(reason[2], 1, 1, 1, true); end
|
|
end
|
|
if reference.e then
|
|
local reason = app.Modules.Events.GetEventTooltipNoteForGroup(reference);
|
|
if reason then
|
|
local left, right = strsplit(DESCRIPTION_SEPARATOR, reason);
|
|
if right then
|
|
GameTooltip:AddDoubleLine(left, right, 0.4, 0.8, 1, 0.4, 0.8, 1, 1);
|
|
else
|
|
GameTooltip:AddLine(left, 0.4, 0.8, 1, 1);
|
|
end
|
|
end
|
|
end
|
|
if reference.sym then GameTooltip:AddLine("Right click to view more information.", 0.8, 0.8, 1, true); end
|
|
end
|
|
if reference.titleID then
|
|
if app.Settings:GetTooltipSetting("titleID") then GameTooltip:AddDoubleLine(L["TITLE_ID"], tostring(reference.titleID)); end
|
|
AttachTooltipSearchResults(GameTooltip, 1, "titleID:" .. reference.titleID, SearchForField, "titleID", reference.titleID);
|
|
end
|
|
if reference.questID and app.Settings:GetTooltipSetting("questID") then GameTooltip:AddDoubleLine(L["QUEST_ID"], tostring(reference.questID)); end
|
|
if reference.qgs and app.Settings:GetTooltipSetting("QuestGivers") then
|
|
if app.Settings:GetTooltipSetting("creatureID") then
|
|
for i,qg in ipairs(reference.qgs) do
|
|
GameTooltip:AddDoubleLine(i == 1 and L["QUEST_GIVER"] or " ", tostring(qg > 0 and app.NPCNameFromID[qg] or "") .. " (" .. qg .. ")");
|
|
end
|
|
else
|
|
for i,qg in ipairs(reference.qgs) do
|
|
GameTooltip:AddDoubleLine(i == 1 and L["QUEST_GIVER"] or " ", tostring(qg > 0 and app.NPCNameFromID[qg] or qg));
|
|
end
|
|
end
|
|
end
|
|
if reference.crs then
|
|
if app.Settings:GetTooltipSetting("creatureID") then
|
|
for i,cr in ipairs(reference.crs) do
|
|
GameTooltip:AddDoubleLine(i == 1 and CREATURE or " ", tostring(cr > 0 and app.NPCNameFromID[cr] or "") .. " (" .. cr .. ")");
|
|
end
|
|
else
|
|
for i,cr in ipairs(reference.crs) do
|
|
GameTooltip:AddDoubleLine(i == 1 and CREATURE or " ", tostring(cr > 0 and app.NPCNameFromID[cr] or cr));
|
|
end
|
|
end
|
|
end
|
|
if reference.c and app.Settings:GetTooltipSetting("ClassRequirements") then
|
|
local str = "";
|
|
for i,cl in ipairs(reference.c) do
|
|
local info = C_CreatureInfo.GetClassInfo(cl);
|
|
if info then
|
|
if i > 1 then str = str .. ", "; end
|
|
str = str .. info.className;
|
|
end
|
|
end
|
|
GameTooltip:AddDoubleLine("Classes", str);
|
|
end
|
|
if app.Settings:GetTooltipSetting("RaceRequirements") then
|
|
if reference.races then
|
|
local str = "";
|
|
for i,race in ipairs(reference.races) do
|
|
local info = C_CreatureInfo.GetRaceInfo(race);
|
|
if info then
|
|
if i > 1 then str = str .. ", "; end
|
|
str = str .. info.raceName;
|
|
end
|
|
end
|
|
GameTooltip:AddDoubleLine("Races", str);
|
|
elseif reference.r and reference.r > 0 then
|
|
GameTooltip:AddDoubleLine("Races", (reference.r == 2 and ITEM_REQ_ALLIANCE) or (reference.r == 1 and ITEM_REQ_HORDE) or "Unknown");
|
|
end
|
|
end
|
|
if reference.isDaily then GameTooltip:AddLine("This can be completed daily.");
|
|
elseif reference.isWeekly then GameTooltip:AddLine("This can be completed weekly.");
|
|
elseif reference.isMontly then GameTooltip:AddLine("This can be completed monthly.");
|
|
elseif reference.isYearly then GameTooltip:AddLine("This can be completed yearly.");
|
|
elseif reference.repeatable then GameTooltip:AddLine("This can be repeated multiple times."); end
|
|
if reference.pvp and not reference.itemID then GameTooltip:AddLine(L["REQUIRES_PVP"], 1, 1, 1, 1, true); end
|
|
if not GameTooltipModel:TrySetModel(reference) then
|
|
local texture = reference.preview or reference.icon;
|
|
if texture then
|
|
if reference.explorationID and reference.maphash and reference.preview then
|
|
local width, height, offsetX, offsetY = strsplit(":", reference.maphash);
|
|
GameTooltipIcon:SetSize(tonumber(width) or 72,tonumber(height) or 72);
|
|
else
|
|
GameTooltipIcon:SetSize(72,72);
|
|
end
|
|
GameTooltipIcon.icon:SetTexture(texture);
|
|
local texcoord = reference.texcoord;
|
|
if texcoord then
|
|
GameTooltipIcon.icon:SetTexCoord(texcoord[1], texcoord[2], texcoord[3], texcoord[4]);
|
|
else
|
|
GameTooltipIcon.icon:SetTexCoord(0, 1, 0, 1);
|
|
end
|
|
GameTooltipIcon:Show();
|
|
end
|
|
end
|
|
if reference.displayID and app.Settings:GetTooltipSetting("displayID") then
|
|
GameTooltip:AddDoubleLine("Display ID", reference.displayID);
|
|
end
|
|
if reference.modelID and app.Settings:GetTooltipSetting("modelID") then
|
|
GameTooltip:AddDoubleLine("Model ID", reference.modelID);
|
|
end
|
|
if reference.cost then
|
|
if type(reference.cost) == "table" then
|
|
local _, name, icon, amount;
|
|
for k,v in pairs(reference.cost) do
|
|
_ = v[1];
|
|
if _ == "g" then
|
|
GameTooltip:AddDoubleLine(k == 1 and "Cost" or " ", GetCoinTextureString(v[2]));
|
|
else
|
|
if _ == "i" then
|
|
local item = app.CreateItem(v[2]);
|
|
name = item.name;
|
|
icon = item.icon;
|
|
elseif _ == "c" then
|
|
local currency = app.CreateCurrencyClass(v[2]);
|
|
name = currency.text;
|
|
icon = currency.icon;
|
|
end
|
|
name = (icon and ("|T" .. icon .. ":0|t") or "") .. (name or RETRIEVING_DATA);
|
|
_ = (v[3] or 1);
|
|
if _ > 1 then
|
|
name = _ .. "x " .. name;
|
|
end
|
|
GameTooltip:AddDoubleLine(k == 1 and "Cost" or " ", name);
|
|
end
|
|
end
|
|
else
|
|
GameTooltip:AddDoubleLine("Cost", GetCoinTextureString(reference.cost));
|
|
end
|
|
end
|
|
if app.Settings:GetTooltipSetting("Progress") then
|
|
local right = (app.Settings:GetTooltipSetting("ShowIconOnly") and GetProgressTextForRow or GetProgressTextForTooltip)(reference);
|
|
if right and right ~= "" and right ~= "---" then
|
|
GameTooltipTextRight1:SetText(right);
|
|
GameTooltipTextRight1:Show();
|
|
end
|
|
end
|
|
|
|
-- Show Breadcrumb information
|
|
if reference.isBreadcrumb then
|
|
GameTooltip:AddLine("This is a breadcrumb quest.");
|
|
end
|
|
|
|
GameTooltip:AddDoubleLine("Type", reference.__type);
|
|
|
|
-- Show lockout information about an Instance (Raid or Dungeon)
|
|
local locks = reference.locks;
|
|
if locks then
|
|
if locks.encounters then
|
|
GameTooltip:AddDoubleLine("Resets", date("%c", locks.reset));
|
|
for encounterIter,encounter in pairs(locks.encounters) do
|
|
GameTooltip:AddDoubleLine(" " .. encounter.name, GetCompletionIcon(encounter.isKilled));
|
|
end
|
|
else
|
|
if reference.isLockoutShared and locks.shared then
|
|
GameTooltip:AddDoubleLine("Shared", date("%c", locks.shared.reset));
|
|
for encounterIter,encounter in pairs(locks.shared.encounters) do
|
|
GameTooltip:AddDoubleLine(" " .. encounter.name, GetCompletionIcon(encounter.isKilled));
|
|
end
|
|
else
|
|
for key,value in pairs(locks) do
|
|
if key == "shared" then
|
|
-- Skip
|
|
else
|
|
GameTooltip:AddDoubleLine(Colorize(GetDifficultyInfo(key), app.DifficultyColors[key] or app.Colors.DefaultDifficulty), date("%c", value.reset));
|
|
for encounterIter,encounter in pairs(value.encounters) do
|
|
GameTooltip:AddDoubleLine(" " .. encounter.name, GetCompletionIcon(encounter.isKilled));
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if reference.OnTooltip then reference:OnTooltip(); end
|
|
|
|
if reference.questID and app.Settings:GetTooltipSetting("SummarizeThings") then
|
|
if not reference.repeatable and app.Settings:GetTooltipSetting("Show:OtherCharacterQuests") then
|
|
local incompletes, realmName = {}, GetRealmName();
|
|
for guid,character in pairs(ATTCharacterData) do
|
|
if not character.ignored and character.realm == realmName
|
|
and (not reference.r or (character.factionID and reference.r == character.factionID))
|
|
and (not reference.races or (character.raceID and contains(reference.races, character.raceID)))
|
|
and (not reference.c or (character.classID and contains(reference.c, character.classID)))
|
|
and (character.Quests and not character.Quests[reference.questID]) then
|
|
incompletes[guid] = character;
|
|
end
|
|
end
|
|
incompletes[app.GUID] = nil;
|
|
local desc, j = "", 0;
|
|
for guid,character in pairs(incompletes) do
|
|
if j > 0 then desc = desc .. ", "; end
|
|
desc = desc .. (character.text or guid);
|
|
j = j + 1;
|
|
end
|
|
if j > 0 then
|
|
GameTooltip:AddLine("Incomplete on " .. string.gsub(desc, "-" .. realmName, ""), 1, 1, 1, true);
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Show Quest Prereqs
|
|
local isDebugMode = app.Settings:Get("DebugMode");
|
|
if reference.sourceQuests and (isDebugMode or not reference.saved) then
|
|
local currentMapID, prereqs, bc = app.CurrentMapID, {}, {};
|
|
for i,sourceQuestID in ipairs(reference.sourceQuests) do
|
|
if sourceQuestID > 0 and (isDebugMode or not IsQuestFlaggedCompleted(sourceQuestID)) then
|
|
local sqs = SearchForField("questID", sourceQuestID);
|
|
if #sqs > 0 then
|
|
local bestMatch = nil;
|
|
for j,sq in ipairs(sqs) do
|
|
if sq.questID == sourceQuestID and not sq.objectiveID then
|
|
if isDebugMode or (app.RecursiveClassAndRaceFilter(sq) and not IsQuestFlaggedCompleted(sourceQuestID)) then
|
|
if sq.sourceQuests then
|
|
-- Always prefer the source quest with additional source quest data.
|
|
bestMatch = sq;
|
|
elseif not sq.itemID and (not bestMatch or not bestMatch.sourceQuests) then
|
|
-- Otherwise try to find the version of the quest that isn't an item.
|
|
bestMatch = sq;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if bestMatch then
|
|
if bestMatch.isBreadcrumb then
|
|
tinsert(bc, bestMatch);
|
|
else
|
|
tinsert(prereqs, bestMatch);
|
|
end
|
|
end
|
|
else
|
|
tinsert(prereqs, app.CreateQuest(sourceQuestID));
|
|
end
|
|
end
|
|
end
|
|
|
|
if prereqs and #prereqs > 0 then
|
|
GameTooltip:AddLine("This quest has an incomplete prerequisite quest that you need to complete first.");
|
|
for i,prereq in ipairs(prereqs) do
|
|
local text = " " .. prereq.questID .. ": " .. (prereq.text or RETRIEVING_DATA);
|
|
local mapID = GetBestMapForGroup(prereq, currentMapID);
|
|
if mapID and mapID ~= currentMapID then text = text .. " (" .. app.GetMapName(mapID) .. ")"; end
|
|
GameTooltip:AddDoubleLine(text, GetCompletionIcon(IsQuestFlaggedCompleted(prereq.questID)));
|
|
end
|
|
end
|
|
if bc and #bc > 0 then
|
|
GameTooltip:AddLine("This quest has a breadcrumb quest that you may be unable to complete after completing this one.");
|
|
for i,prereq in ipairs(bc) do
|
|
local text = " " .. prereq.questID .. ": " .. (prereq.text or RETRIEVING_DATA);
|
|
local mapID = GetBestMapForGroup(prereq, currentMapID);
|
|
if mapID and mapID ~= currentMapID then text = text .. " (" .. app.GetMapName(mapID) .. ")"; end
|
|
GameTooltip:AddDoubleLine(text, GetCompletionIcon(IsQuestFlaggedCompleted(prereq.questID)));
|
|
end
|
|
end
|
|
end
|
|
|
|
if reference.g then
|
|
-- If we're at the Auction House
|
|
if (AuctionFrame and AuctionFrame:IsShown()) or (AuctionHouseFrame and AuctionHouseFrame:IsShown()) then
|
|
GameTooltip:AddLine(L[(self.index > 0 and "OTHER_ROW_INSTRUCTIONS_AH") or "TOP_ROW_INSTRUCTIONS_AH"], 1, 1, 1);
|
|
else
|
|
GameTooltip:AddLine(L[(self.index > 0 and "OTHER_ROW_INSTRUCTIONS") or "TOP_ROW_INSTRUCTIONS"], 1, 1, 1);
|
|
end
|
|
end
|
|
GameTooltip:Show();
|
|
end
|
|
end
|
|
local function RowOnLeave(self)
|
|
if GameTooltip then
|
|
GameTooltip:ClearLines();
|
|
GameTooltip:Hide();
|
|
GameTooltipIcon.icon.Background:Hide();
|
|
GameTooltipIcon.icon.Border:Hide();
|
|
GameTooltipIcon:Hide();
|
|
GameTooltipModel:Hide();
|
|
end
|
|
end
|
|
CreateRow = function(self)
|
|
local row = CreateFrame("Button", nil, self);
|
|
row.index = #self.rows;
|
|
if row.index == 0 then
|
|
-- This means relative to the parent.
|
|
row:SetPoint("TOPLEFT");
|
|
row:SetPoint("TOPRIGHT");
|
|
else
|
|
-- This means relative to the row above this one.
|
|
row:SetPoint("TOPLEFT", self.rows[row.index], "BOTTOMLEFT");
|
|
row:SetPoint("TOPRIGHT", self.rows[row.index], "BOTTOMRIGHT");
|
|
end
|
|
tinsert(self.rows, row);
|
|
|
|
-- Setup highlighting and event handling
|
|
row:SetHighlightTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight", "ADD");
|
|
row:RegisterForClicks("LeftButtonDown","RightButtonDown");
|
|
row:SetScript("OnClick", RowOnClick);
|
|
row:SetScript("OnEnter", RowOnEnter);
|
|
row:SetScript("OnLeave", RowOnLeave);
|
|
row:EnableMouse(true);
|
|
|
|
-- Label is the text information you read.
|
|
row.Label = row:CreateFontString(nil, "ARTWORK", "GameFontNormal");
|
|
row.Label:SetJustifyH("LEFT");
|
|
row.Label:SetPoint("BOTTOM");
|
|
row.Label:SetPoint("TOP");
|
|
row:SetHeight(select(2, row.Label:GetFont()) + 4);
|
|
|
|
-- Summary is the completion summary information. (percentage text)
|
|
row.Summary = row:CreateFontString(nil, "ARTWORK", "GameFontNormal");
|
|
row.Summary:SetJustifyH("CENTER");
|
|
row.Summary:SetPoint("BOTTOM");
|
|
row.Summary:SetPoint("RIGHT");
|
|
row.Summary:SetPoint("TOP");
|
|
|
|
-- Background is used by the Map Highlight functionality.
|
|
row.Background = row:CreateTexture(nil, "BACKGROUND");
|
|
row.Background:SetPoint("LEFT", 4, 0);
|
|
row.Background:SetPoint("BOTTOM");
|
|
row.Background:SetPoint("RIGHT");
|
|
row.Background:SetPoint("TOP");
|
|
row.Background:SetTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight");
|
|
|
|
-- Indicator is used by the Instance Saves functionality.
|
|
row.Indicator = row:CreateTexture(nil, "ARTWORK");
|
|
row.Indicator:SetPoint("BOTTOM");
|
|
row.Indicator:SetPoint("TOP");
|
|
row.Indicator:SetWidth(row:GetHeight());
|
|
|
|
-- Texture is the icon.
|
|
row.Texture = row:CreateTexture(nil, "ARTWORK");
|
|
row.Texture:SetPoint("BOTTOM");
|
|
row.Texture:SetPoint("TOP");
|
|
row.Texture:SetWidth(row:GetHeight());
|
|
row.Texture.Background = row:CreateTexture(nil, "BACKGROUND");
|
|
row.Texture.Background:SetPoint("BOTTOM");
|
|
row.Texture.Background:SetPoint("TOP");
|
|
row.Texture.Background:SetWidth(row:GetHeight());
|
|
row.Texture.Border = row:CreateTexture(nil, "BORDER");
|
|
row.Texture.Border:SetPoint("BOTTOM");
|
|
row.Texture.Border:SetPoint("TOP");
|
|
row.Texture.Border:SetWidth(row:GetHeight());
|
|
|
|
-- Clear the Row Data Initially
|
|
SetRowData(self, row, nil);
|
|
return row;
|
|
end
|
|
|
|
-- Window Creation
|
|
app.Windows = {};
|
|
local defaultNoEntriesRow = {
|
|
text = "No data was found.",
|
|
preview = app.asset("Discord_2_128"),
|
|
description = "If you believe this was in error, try activating 'Debug Mode'. One of your filters may be restricting the visibility of the group.",
|
|
};
|
|
local AllWindowSettings;
|
|
local function ApplySettingsForWindow(self, windowSettings)
|
|
if windowSettings.scale then self:SetScale(windowSettings.scale); end
|
|
if windowSettings.movable then
|
|
self:ClearAllPoints();
|
|
if windowSettings.x then
|
|
self:SetPoint(windowSettings.point or "CENTER", windowSettings.relativeTo or UIParent, windowSettings.relativePoint or "CENTER", windowSettings.x, windowSettings.y);
|
|
else
|
|
self:SetPoint("CENTER", UIParent, "CENTER");
|
|
end
|
|
end
|
|
if windowSettings.width then
|
|
self:SetSize(windowSettings.width, windowSettings.height);
|
|
end
|
|
self:SetMovable(windowSettings.movable);
|
|
self:SetResizable(windowSettings.resizable);
|
|
self:SetVisible(windowSettings.visible);
|
|
end
|
|
local function BuildSettingsForWindow(self, windowSettings)
|
|
local scale = self:GetScale();
|
|
if scale then
|
|
windowSettings.scale = scale;
|
|
local point, relativeTo, relativePoint, xOfs, yOfs = self:GetPoint()
|
|
if xOfs then
|
|
windowSettings.width = self:GetWidth();
|
|
windowSettings.height = self:GetHeight();
|
|
windowSettings.x = xOfs;
|
|
windowSettings.y = yOfs;
|
|
windowSettings.point = point;
|
|
windowSettings.relativePoint = relativePoint;
|
|
windowSettings.relativeTo = relativeTo and relativeTo:GetName();
|
|
end
|
|
else
|
|
windowSettings.scale = 1;
|
|
windowSettings.x = 0;
|
|
windowSettings.y = 0;
|
|
end
|
|
windowSettings.visible = not not self:IsVisible();
|
|
windowSettings.movable = not not self:IsMovable();
|
|
windowSettings.resizable = not not self:IsResizable();
|
|
end
|
|
local function ClearSettingsForWindow(self)
|
|
if not AllWindowSettings then return; end
|
|
AllWindowSettings[self.Suffix] = nil;
|
|
end
|
|
local function LoadSettingsForWindow(self)
|
|
if not AllWindowSettings then return; end
|
|
local name = self.Suffix;
|
|
local settings = AllWindowSettings[name];
|
|
if not settings then
|
|
settings = {};
|
|
AllWindowSettings[name] = settings;
|
|
end
|
|
self.Settings = settings;
|
|
self:Load();
|
|
end
|
|
local function LoadSettingsForWindows(windowSettings)
|
|
if AllWindowSettings then
|
|
return;
|
|
end
|
|
AllWindowSettings = windowSettings;
|
|
local dynamicWindows = {};
|
|
for name, settings in pairs(windowSettings) do
|
|
if settings.dynamic then
|
|
if settings.visible then
|
|
dynamicWindows[name] = settings;
|
|
else
|
|
windowSettings[name] = nil;
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Load all of the windows other than Prime.
|
|
local primeWindow = app.Windows.Prime;
|
|
app.Windows.Prime = nil;
|
|
for name, window in pairs(app.Windows) do
|
|
LoadSettingsForWindow(window);
|
|
dynamicWindows[name] = nil;
|
|
end
|
|
|
|
-- Okay, now load Prime last.
|
|
app.Windows.Prime = primeWindow;
|
|
LoadSettingsForWindow(primeWindow);
|
|
dynamicWindows.Prime = nil;
|
|
|
|
for name,settings in pairs(dynamicWindows) do
|
|
settings.visible = false;
|
|
app:CreateMiniListFromSource(settings.key, settings.id, settings.sourcePath);
|
|
end
|
|
end
|
|
app:RegisterEvent("PLAYER_LOGOUT");
|
|
app.events.PLAYER_LOGOUT = function()
|
|
for _, window in pairs(app.Windows) do
|
|
window:Save();
|
|
end
|
|
end;
|
|
|
|
local function SetWindowData(self, data)
|
|
if self.data ~= data then
|
|
self.data = data;
|
|
self:DelayedRebuild();
|
|
end
|
|
end
|
|
local function ProcessGroup(data, object)
|
|
if app.VisibilityFilter(object) then
|
|
data[#data + 1] = object;
|
|
if object.g and object.expanded then
|
|
for i=1,#object.g,1 do
|
|
ProcessGroup(data, object.g[i]);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
local function UpdateWindow(self, force, trigger)
|
|
-- If this window doesn't have data, do nothing.
|
|
local data = self.data;
|
|
if not data then
|
|
self:DelayedRebuild();
|
|
return;
|
|
end
|
|
if not self.rowData then
|
|
self.rowData = {};
|
|
else
|
|
wipe(self.rowData);
|
|
end
|
|
self.forceFullDataRefresh = self.forceFullDataRefresh or force or trigger;
|
|
if force or self:IsShown() then
|
|
data.expanded = true;
|
|
if self.forceFullDataRefresh then
|
|
local rows = self.Container.rows;
|
|
for i=1,#rows,1 do
|
|
SetRowData(self, rows[i], nil);
|
|
end
|
|
data.progress = 0;
|
|
data.total = 0;
|
|
--local lastUpdate = GetTimePreciseSec();
|
|
if not (data.OnUpdate and data:OnUpdate()) then
|
|
UpdateGroups(data, data.g);
|
|
|
|
-- Check to see if the data changed.
|
|
if self.data ~= data then
|
|
print(self.Suffix, app.Colorize("DATA CHANGED!", RGBToHex(255, 120, 120)));
|
|
return;
|
|
end
|
|
else
|
|
print(self.Suffix, "Returned true!!! Skipping the rest of this UpdateWindows call");
|
|
return;
|
|
end
|
|
self.forceFullDataRefresh = nil;
|
|
--print("UpdateGroups RESULT", (GetTimePreciseSec() - lastUpdate) * 10000);
|
|
end
|
|
ProcessGroup(self.rowData, data);
|
|
|
|
-- Does this user have everything?
|
|
if data.total and data.total > 0 then
|
|
if data.total <= data.progress then
|
|
if #self.rowData < 1 then
|
|
data.back = 1;
|
|
tinsert(self.rowData, data);
|
|
end
|
|
if self.missingData then
|
|
self.missingData = nil;
|
|
--print("UNSETTING MISSING DATA", trigger, self.AllowCompleteSound);
|
|
if trigger and self.AllowCompleteSound then
|
|
--print("PLAY COMPLETE SOUND", self.data.text);
|
|
app:PlayCompleteSound();
|
|
end
|
|
end
|
|
if not self.ignoreNoEntries then
|
|
local noentries = self.noEntriesRow or defaultNoEntriesRow;
|
|
noentries.parent = self.data;
|
|
tinsert(self.rowData, noentries);
|
|
end
|
|
else
|
|
self.missingData = true;
|
|
end
|
|
else
|
|
self.missingData = nil;
|
|
end
|
|
return true;
|
|
end
|
|
end
|
|
local function UpdateWindowColor(self, suffix)
|
|
self:SetBackdropBorderColor(1, 1, 1, 1);
|
|
self:SetBackdropColor(0, 0, 0, 1);
|
|
end
|
|
if app.Debugging then
|
|
function app:RedrawWindows(source)
|
|
app:StartATTCoroutine("RedrawWindows", function()
|
|
coroutine.yield();
|
|
coroutine.yield();
|
|
print("RedrawWindows: ", source);
|
|
local lastUpdate = GetTimePreciseSec();
|
|
for name, window in pairs(app.Windows) do
|
|
window:Redraw();
|
|
end
|
|
print("RedrawWindows: ", (GetTimePreciseSec() - lastUpdate) * 10000);
|
|
end);
|
|
end
|
|
function app:RefreshWindows(source)
|
|
print("RefreshWindows: ", source);
|
|
local lastUpdate = GetTimePreciseSec();
|
|
for name, window in pairs(app.Windows) do
|
|
window:Refresh();
|
|
end
|
|
print("RefreshWindows: ", (GetTimePreciseSec() - lastUpdate) * 10000);
|
|
end
|
|
function app:UpdateWindows(source, force, trigger)
|
|
print("UpdateWindows: ", source, force, trigger);
|
|
if trigger then trigger = source; end
|
|
local lastUpdate = GetTimePreciseSec();
|
|
for name, window in pairs(app.Windows) do
|
|
window:Update(force, trigger);
|
|
end
|
|
print("UpdateWindows: ", (GetTimePreciseSec() - lastUpdate) * 10000);
|
|
end
|
|
else
|
|
function app:RedrawWindows(source)
|
|
for name, window in pairs(app.Windows) do
|
|
window:Redraw();
|
|
end
|
|
end
|
|
function app:RefreshWindows(source)
|
|
for name, window in pairs(app.Windows) do
|
|
window:Refresh();
|
|
end
|
|
end
|
|
function app:UpdateWindows(source, force, trigger)
|
|
if trigger then trigger = source; end
|
|
for name, window in pairs(app.Windows) do
|
|
local window_oldUpdate = window.Update;
|
|
window.UpdatePending = true;
|
|
window.Update = function(self, ...)
|
|
local result = window_oldUpdate(self, ...);
|
|
self.Update = window_oldUpdate;
|
|
self.UpdatePending = nil;
|
|
return result;
|
|
end
|
|
end
|
|
for name, window in pairs(app.Windows) do
|
|
if window.UpdatePending then
|
|
window:Update(force, trigger);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
function app:UpdateWindowColors()
|
|
for suffix, window in pairs(app.Windows) do
|
|
UpdateWindowColor(window, suffix);
|
|
end
|
|
end
|
|
|
|
local refreshDataCooldown = 5;
|
|
local refreshFromTrigger;
|
|
local currentlyRefreshingData = false;
|
|
local function RefreshData(source, trigger)
|
|
wipe(searchCache);
|
|
refreshDataCooldown = 5;
|
|
if trigger then
|
|
--print("REFRESH_DATA", source, trigger);
|
|
trigger = source;
|
|
end
|
|
refreshFromTrigger = refreshFromTrigger or trigger;
|
|
if currentlyRefreshingData then return; end
|
|
app:StartATTCoroutine("RefreshData", function()
|
|
currentlyRefreshingData = true;
|
|
|
|
-- While the player is in combat, wait for combat to end.
|
|
while InCombatLockdown() do coroutine.yield(); end
|
|
|
|
-- Wait 1/2 second. For multiple simultaneous requests, each one will reapply the delay.
|
|
while refreshDataCooldown > 0 do
|
|
refreshDataCooldown = refreshDataCooldown - 1;
|
|
coroutine.yield();
|
|
end
|
|
|
|
-- Send an Update to the Windows to Rebuild their Row Data
|
|
if app.forceFullDataRefresh then
|
|
app.forceFullDataRefresh = nil;
|
|
|
|
-- Execute the OnRecalculate handlers.
|
|
for i,handler in ipairs(app.EventHandlers.OnRecalculate) do
|
|
handler();
|
|
end
|
|
app:UpdateWindows(source, true, refreshFromTrigger);
|
|
else
|
|
app:UpdateWindows(source, nil, refreshFromTrigger);
|
|
end
|
|
refreshFromTrigger = nil;
|
|
|
|
-- Send a message to your party members.
|
|
local data = (app.CurrentCharacter and app.CurrentCharacter.PrimeData) or app:GetDataCache();
|
|
msg = "A\t" .. app.Version .. "\t" .. (data.progress or 0) .. "\t" .. (data.total or 0) .. "\t" .. data.modeString;
|
|
if app.lastProgressUpdateMessage ~= msg then
|
|
SendGroupMessage(msg);
|
|
SendGuildMessage(msg);
|
|
app.lastProgressUpdateMessage = msg;
|
|
end
|
|
wipe(searchCache);
|
|
currentlyRefreshingData = nil;
|
|
end);
|
|
end
|
|
function app:RefreshDataCompletely(source, trigger)
|
|
app.forceFullDataRefresh = true;
|
|
RefreshData("RefreshDataCompletely:" .. source, trigger);
|
|
end
|
|
function app:RefreshDataQuietly(source, trigger)
|
|
RefreshData("RefreshDataQuietly:" .. source, trigger);
|
|
end
|
|
local function SetWindowVisible(self, show)
|
|
if show then
|
|
self:Show();
|
|
else
|
|
self:Hide();
|
|
end
|
|
end
|
|
local function ToggleWindow(self)
|
|
self:SetVisible(not self:IsVisible());
|
|
end
|
|
local BuildCategory = function(self, headers, searchResults, inst)
|
|
local sources, header, headerType = {}, self;
|
|
for j,o in ipairs(searchResults) do
|
|
if not o.u or o.u ~= 1 then
|
|
MergeClone(sources, o);
|
|
if o.parent then
|
|
if not o.sourceQuests then
|
|
local questID = GetRelativeValue(o, "questID");
|
|
if questID then
|
|
if not inst.sourceQuests then
|
|
inst.sourceQuests = {};
|
|
end
|
|
if not contains(inst.sourceQuests, questID) then
|
|
tinsert(inst.sourceQuests, questID);
|
|
end
|
|
else
|
|
local sourceQuests = GetRelativeValue(o, "sourceQuests");
|
|
if sourceQuests then
|
|
if not inst.sourceQuests then
|
|
inst.sourceQuests = {};
|
|
for k,questID in ipairs(sourceQuests) do
|
|
tinsert(inst.sourceQuests, questID);
|
|
end
|
|
else
|
|
for k,questID in ipairs(sourceQuests) do
|
|
if not contains(inst.sourceQuests, questID) then
|
|
tinsert(inst.sourceQuests, questID);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if GetRelativeValue(o, "isHolidayCategory") then
|
|
headerType = "holiday";
|
|
elseif GetRelativeValue(o, "isPromotionCategory") then
|
|
headerType = "promo";
|
|
elseif GetRelativeValue(o, "isPVPCategory") then
|
|
headerType = "pvp";
|
|
elseif GetRelativeValue(o, "isEventCategory") then
|
|
headerType = "event";
|
|
elseif GetRelativeValue(o, "isWorldDropCategory") or o.parent.headerID == app.HeaderConstants.COMMON_BOSS_DROPS then
|
|
headerType = "drop";
|
|
elseif o.parent.npcID then
|
|
headerType = GetDeepestRelativeValue(o, "headerID") or o.parent.parent.headerID == app.HeaderConstants.VENDORS and app.HeaderConstants.VENDORS or "drop";
|
|
elseif GetRelativeValue(o, "isCraftedCategory") then
|
|
headerType = "crafted";
|
|
elseif o.parent.achievementID then
|
|
headerType = app.HeaderConstants.ACHIEVEMENTS;
|
|
else
|
|
headerType = GetDeepestRelativeValue(o, "headerID") or "drop";
|
|
if headerType == true then -- Seriously don't do this...
|
|
headerType = "drop";
|
|
end
|
|
end
|
|
local coords = GetRelativeValue(o, "coords");
|
|
if coords then
|
|
if not inst.coords then
|
|
inst.coords = { unpack(coords) };
|
|
else
|
|
for i,coord in ipairs(coords) do
|
|
tinsert(inst.coords, coord);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
local count = #sources;
|
|
if count == 0 then return nil; end
|
|
if count == 1 then
|
|
for key,value in pairs(sources[1]) do
|
|
inst[key] = value;
|
|
end
|
|
elseif count > 1 then
|
|
-- Find the most accessible version of the thing.
|
|
app.Sort(sources, app.SortDefaults.Accessibility);
|
|
for key,value in pairs(sources[1]) do
|
|
inst[key] = value;
|
|
end
|
|
end
|
|
|
|
-- Determine the type of header to put the thing into.
|
|
if not headerType then headerType = "drop"; end
|
|
header = headers[headerType];
|
|
if not header then
|
|
if headerType == "holiday" then
|
|
header = app.CreateNPC(app.HeaderConstants.HOLIDAYS);
|
|
elseif headerType == "promo" then
|
|
header = {};
|
|
header.text = BATTLE_PET_SOURCE_8;
|
|
header.icon = app.asset("Category_Promo");
|
|
elseif headerType == "pvp" then
|
|
header = {};
|
|
header.text = PVP;
|
|
header.icon = app.asset("Category_PvP");
|
|
elseif headerType == "event" then
|
|
header = {};
|
|
header.text = BATTLE_PET_SOURCE_7;
|
|
header.icon = app.asset("Category_Event");
|
|
elseif headerType == "drop" then
|
|
header = {};
|
|
header.text = BATTLE_PET_SOURCE_1;
|
|
header.icon = app.asset("Category_WorldDrops");
|
|
elseif headerType == "crafted" then
|
|
header = {};
|
|
header.text = LOOT_JOURNAL_LEGENDARIES_SOURCE_CRAFTED_ITEM;
|
|
header.icon = app.asset("Category_Crafting");
|
|
elseif type(headerType) == "number" then
|
|
header = app.CreateNPC(headerType);
|
|
else
|
|
print("Unhandled Header Type", headerType);
|
|
end
|
|
if not headers[headerType] then
|
|
headers[headerType] = header;
|
|
tinsert(self.g, header);
|
|
header.parent = self;
|
|
header.g = {};
|
|
end
|
|
end
|
|
inst.parent = header;
|
|
inst.progress = nil;
|
|
inst.total = nil;
|
|
inst.g = nil;
|
|
MergeObject(header.g, inst);
|
|
return inst;
|
|
end
|
|
function app:GetWindow(suffix, settings)
|
|
local window = app.Windows[suffix];
|
|
if not window and settings then
|
|
-- Create the window instance.
|
|
window = CreateFrame("FRAME", nil, settings.parent or UIParent, BackdropTemplateMixin and "BackdropTemplate");
|
|
app.Windows[suffix] = window;
|
|
window.Suffix = suffix;
|
|
window.SetData = SetWindowData;
|
|
window.BuildCategory = BuildCategory;
|
|
window.AllowCompleteSound = settings.AllowCompleteSound;
|
|
window:SetScript("OnMouseDown", StartMovingOrSizing);
|
|
window:SetScript("OnMouseUp", StopMovingOrSizing);
|
|
window:SetScript("OnHide", StopMovingOrSizing);
|
|
window:SetBackdrop(backdrop);
|
|
window:SetClampedToScreen(true);
|
|
window:SetToplevel(true);
|
|
window:EnableMouse(true);
|
|
window:SetMovable(true);
|
|
window:SetResizable(true);
|
|
if window.SetResizeBounds then
|
|
window:SetResizeBounds(96, 32);
|
|
else
|
|
window:SetMinResize(96, 32);
|
|
end
|
|
window:SetSize(300, 300);
|
|
window:Hide();
|
|
|
|
if ATTClassicSettings then
|
|
if suffix == "Prime" then
|
|
window:SetScale(app.Settings:GetTooltipSetting("MainListScale"));
|
|
else
|
|
window:SetScale(app.Settings:GetTooltipSetting("MiniListScale"));
|
|
end
|
|
end
|
|
|
|
-- Whether or not to debug things
|
|
local debugging = settings.Debugging;--app.Debugging and window.Suffix == "Prime";
|
|
|
|
-- Load / Save, which allows windows to keep track of key pieces of information.
|
|
window.ClearSettings = ClearSettingsForWindow;
|
|
if not settings.IgnoreSettings then
|
|
local defaults = {};
|
|
BuildSettingsForWindow(window, defaults);
|
|
if settings.Defaults then
|
|
for key,value in pairs(settings.Defaults) do
|
|
defaults[key] = value;
|
|
end
|
|
end
|
|
window.Load = function(self)
|
|
local windowSettings = self.Settings;
|
|
if not windowSettings then
|
|
return;
|
|
end
|
|
setmetatable(windowSettings, { __index = defaults });
|
|
if settings.OnLoad then
|
|
settings.OnLoad(self, windowSettings);
|
|
end
|
|
ApplySettingsForWindow(self, windowSettings);
|
|
end
|
|
window.RecordSettings = function(self)
|
|
local windowSettings = self.Settings;
|
|
if windowSettings then
|
|
BuildSettingsForWindow(self, windowSettings);
|
|
end
|
|
return windowSettings;
|
|
end
|
|
window.Save = function(self)
|
|
local windowSettings = self:RecordSettings();
|
|
if windowSettings and settings.OnSave then
|
|
settings.OnSave(self, windowSettings);
|
|
end
|
|
end
|
|
else
|
|
local doNothing = function()
|
|
-- Do nothing
|
|
end;
|
|
window.Load = settings.OnLoad or doNothing;
|
|
window.RecordSettings = doNothing;
|
|
window.Save = settings.OnSave or doNothing;
|
|
end
|
|
|
|
|
|
|
|
-- Visible, which overrides the default functions and gives the addon the ability to recieve information about it.
|
|
local visible, oldShow, oldHide = false, window.Show, window.Hide;
|
|
window.Show = function(self)
|
|
if not visible then
|
|
visible = true;
|
|
oldShow(self);
|
|
if not self.data then
|
|
self:Rebuild();
|
|
else
|
|
self:Update();
|
|
end
|
|
if settings.OnShow then
|
|
settings.OnShow(self);
|
|
end
|
|
self:RecordSettings();
|
|
end
|
|
end
|
|
window.Hide = function(self)
|
|
if visible then
|
|
visible = false;
|
|
oldHide(self);
|
|
if settings.OnHide then
|
|
settings.OnHide(self);
|
|
end
|
|
self:RecordSettings();
|
|
end
|
|
end
|
|
window.SetVisible = SetWindowVisible;
|
|
window.Toggle = ToggleWindow;
|
|
|
|
-- Phase 1: Rebuild, which prepares the data for row data generation (first pass filters checking)
|
|
-- NOTE: You can return true from the rebuild function to call the default on your new group data.
|
|
window.DefaultRebuild = function(self)
|
|
BuildGroups(self.data);
|
|
end
|
|
local onRebuild = settings.OnRebuild;
|
|
if onRebuild then
|
|
if debugging then
|
|
window.ForceRebuild = function(self)
|
|
print("ForceRebuild: " .. suffix);
|
|
local lastUpdate = GetTimePreciseSec();
|
|
local response = onRebuild(self);
|
|
if self.data then
|
|
if response then self:DefaultRebuild(); end
|
|
print("ForceRebuild (DATA): " .. suffix, (GetTimePreciseSec() - lastUpdate) * 10000);
|
|
self:ForceUpdate(true);
|
|
else
|
|
print("ForceRebuild (NO DATA): " .. suffix, (GetTimePreciseSec() - lastUpdate) * 10000);
|
|
end
|
|
end
|
|
window.Rebuild = function(self)
|
|
print("Rebuild: " .. suffix);
|
|
local lastUpdate = GetTimePreciseSec();
|
|
local response = onRebuild(self);
|
|
if self.data then
|
|
if response then self:DefaultRebuild(); end
|
|
print("Rebuild (DATA): " .. suffix, (GetTimePreciseSec() - lastUpdate) * 10000);
|
|
self:Update(true);
|
|
else
|
|
print("Rebuild (NO DATA): " .. suffix, (GetTimePreciseSec() - lastUpdate) * 10000);
|
|
end
|
|
end
|
|
else
|
|
window.ForceRebuild = function(self)
|
|
local response = onRebuild(self);
|
|
if self.data then
|
|
if response then self:DefaultRebuild(); end
|
|
self:ForceUpdate(true);
|
|
end
|
|
end
|
|
window.Rebuild = function(self)
|
|
local response = onRebuild(self);
|
|
if self.data then
|
|
if response then self:DefaultRebuild(); end
|
|
self:Update(true);
|
|
end
|
|
end
|
|
end
|
|
else
|
|
if debugging then
|
|
window.ForceRebuild = function(self)
|
|
if self.data then
|
|
print("ForceRebuild: " .. suffix);
|
|
local lastUpdate = GetTimePreciseSec();
|
|
self:DefaultRebuild();
|
|
print("ForceRebuild: " .. suffix, (GetTimePreciseSec() - lastUpdate) * 10000);
|
|
self:ForceUpdate(true);
|
|
end
|
|
end
|
|
window.Rebuild = function(self)
|
|
if self.data then
|
|
print("Rebuild: " .. suffix);
|
|
local lastUpdate = GetTimePreciseSec();
|
|
self:DefaultRebuild();
|
|
print("Rebuild: " .. suffix, (GetTimePreciseSec() - lastUpdate) * 10000);
|
|
self:Update(true);
|
|
end
|
|
end
|
|
else
|
|
window.ForceRebuild = function(self)
|
|
if self.data then
|
|
self:DefaultRebuild();
|
|
self:ForceUpdate(true);
|
|
end
|
|
end
|
|
window.Rebuild = function(self)
|
|
if self.data then
|
|
self:DefaultRebuild();
|
|
self:Update(true);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Phase 2: Update, which takes the prepared data and revalidates it.
|
|
local OnUpdate = settings.OnUpdate or UpdateWindow;
|
|
window.DefaultUpdate = UpdateWindow;
|
|
if settings.Silent then
|
|
if debugging then
|
|
window.ForceUpdate = function(self, force, trigger)
|
|
print("ForceUpdate: " .. suffix, force, trigger);
|
|
local lastUpdate = GetTimePreciseSec();
|
|
local result = OnUpdate(self, force, trigger);
|
|
print("ForceUpdate: " .. suffix, (GetTimePreciseSec() - lastUpdate) * 10000);
|
|
self:Refresh();
|
|
return result;
|
|
end
|
|
window.Update = function(self, force, trigger)
|
|
if self:IsShown() then
|
|
print("UpdateWindow: " .. suffix, force, trigger);
|
|
local lastUpdate = GetTimePreciseSec();
|
|
local result = OnUpdate(self, force, trigger);
|
|
print("UpdateWindow: " .. suffix, (GetTimePreciseSec() - lastUpdate) * 10000);
|
|
self:Refresh();
|
|
return result;
|
|
else
|
|
self.forceFullDataRefresh = self.forceFullDataRefresh or force or trigger;
|
|
end
|
|
end
|
|
else
|
|
window.ForceUpdate = function(self, force, trigger)
|
|
local result = OnUpdate(self, force, trigger);
|
|
self:Refresh();
|
|
return result;
|
|
end
|
|
window.Update = function(self, force, trigger)
|
|
if self:IsShown() then
|
|
local result = OnUpdate(self, force, trigger);
|
|
self:Refresh();
|
|
return result;
|
|
else
|
|
self.forceFullDataRefresh = self.forceFullDataRefresh or force or trigger;
|
|
end
|
|
end
|
|
end
|
|
elseif debugging then
|
|
window.ForceUpdate = function(self, force, trigger)
|
|
print("ForceUpdate: " .. suffix, force, trigger);
|
|
local lastUpdate = GetTimePreciseSec();
|
|
local result = OnUpdate(self, force, trigger);
|
|
print("ForceUpdate: " .. suffix, (GetTimePreciseSec() - lastUpdate) * 10000);
|
|
self:Refresh();
|
|
return result;
|
|
end
|
|
window.Update = function(self, force, trigger)
|
|
print("UpdateWindow: " .. suffix, force, trigger);
|
|
local lastUpdate = GetTimePreciseSec();
|
|
local result = OnUpdate(self, force, trigger);
|
|
print("UpdateWindow: " .. suffix, (GetTimePreciseSec() - lastUpdate) * 10000);
|
|
self:Refresh();
|
|
return result;
|
|
end
|
|
else
|
|
window.ForceUpdate = function(self, force, trigger)
|
|
local result = OnUpdate(self, force, trigger);
|
|
self:Refresh();
|
|
return result;
|
|
end
|
|
window.Update = function(self, force, trigger)
|
|
local result = OnUpdate(self, force, trigger);
|
|
self:Refresh();
|
|
return result;
|
|
end
|
|
end
|
|
|
|
-- Phase 3: Refresh, which simply refreshes the rows as they are with the row data.
|
|
local defaultOnRefresh = UpdateVisibleRowData;
|
|
local onRefresh = settings.OnRefresh;
|
|
if onRefresh then
|
|
if debugging then
|
|
window.Refresh = function(self)
|
|
print("Refresh: " .. suffix);
|
|
local lastUpdate = GetTimePreciseSec();
|
|
if onRefresh(self) then defaultOnRefresh(self); end
|
|
print("Refresh: " .. suffix, (GetTimePreciseSec() - lastUpdate) * 10000);
|
|
end
|
|
else
|
|
window.Refresh = function(self)
|
|
if onRefresh(self) then defaultOnRefresh(self); end
|
|
end
|
|
end
|
|
else
|
|
if debugging then
|
|
window.Refresh = function(self)
|
|
print("Refresh: " .. suffix);
|
|
local lastUpdate = GetTimePreciseSec();
|
|
defaultOnRefresh(self);
|
|
print("Refresh: " .. suffix, (GetTimePreciseSec() - lastUpdate) * 10000);
|
|
end
|
|
else
|
|
window.Refresh = defaultOnRefresh;
|
|
end
|
|
end
|
|
|
|
-- Phase 4: Redraw, which only updates the rows that already have row data visually.
|
|
window.Redraw = function(self)
|
|
if self:IsShown() then
|
|
RedrawVisibleRowData(self);
|
|
end
|
|
end
|
|
|
|
local delays = {};
|
|
window.DelayedCall = function(self, method, delay, force)
|
|
delays[method] = delay or 60;
|
|
window:StartATTCoroutine("DelayedCall::" .. method, function()
|
|
while delays[method] > 0 do
|
|
coroutine.yield();
|
|
delays[method] = delays[method] - 1;
|
|
end
|
|
while InCombatLockdown() do
|
|
coroutine.yield();
|
|
end
|
|
window[method](window, force);
|
|
end);
|
|
end
|
|
window.DelayedRebuild = function(self)
|
|
self:DelayedCall("Rebuild", 1);
|
|
end
|
|
window.DelayedRefresh = function(self)
|
|
self:DelayedCall("Refresh", 5);
|
|
end
|
|
window.DelayedUpdate = function(self, force)
|
|
self:DelayedCall("Update", 60, force);
|
|
end
|
|
|
|
-- The Row Container. This contains all of the row frames.
|
|
local container = CreateFrame("FRAME", nil, window);
|
|
container:SetPoint("TOPLEFT", window, "TOPLEFT", 2, -6);
|
|
container:SetPoint("BOTTOM", window, "BOTTOM", 0, 6);
|
|
window.Container = container;
|
|
UpdateWindowColor(window, suffix);
|
|
container.rows = {};
|
|
container:Show();
|
|
|
|
local topright = window:CreateTexture(nil, "OVERLAY")
|
|
topright:SetTexture(251963) -- Interface\\PaperDollInfoFrame\\UI-GearManager-Border
|
|
topright:SetPoint("TOPRIGHT", window, "TOPRIGHT", -2, -2);
|
|
topright:SetTexCoord(0.7, 0.745, 0.04, 0.4)
|
|
topright:SetSize(20, 20);
|
|
window.TopRight = topright;
|
|
|
|
-- The Close Button. It's assigned as a local variable so you can change how it behaves.
|
|
local closeButton = CreateFrame("Button", nil, window, "UIPanelCloseButton");
|
|
closeButton:SetPoint("TOPRIGHT", window, "TOPRIGHT", 0, -1);
|
|
closeButton:SetScript("OnClick", function() window:Toggle(); end);
|
|
closeButton:SetSize(24, 24);
|
|
window.CloseButton = closeButton;
|
|
|
|
-- The Scroll Bar.
|
|
window.CurrentIndex = 0;
|
|
local scrollbar = CreateFrame("Slider", nil, window, "UIPanelScrollBarTemplate");
|
|
container:SetPoint("RIGHT", scrollbar, "LEFT", -2, 0);
|
|
scrollbar:SetPoint("TOP", window, "TOP", 0, -40);
|
|
scrollbar:SetPoint("BOTTOMRIGHT", window, "BOTTOMRIGHT", -4, 36);
|
|
scrollbar:SetScript("OnValueChanged", function(self, value)
|
|
if window.CurrentIndex ~= value then
|
|
window.CurrentIndex = value;
|
|
window:Refresh();
|
|
end
|
|
end);
|
|
scrollbar.back = scrollbar:CreateTexture(nil, "BACKGROUND");
|
|
scrollbar.back:SetColorTexture(0.1,0.1,0.1,1)
|
|
scrollbar.back:SetAllPoints(scrollbar);
|
|
scrollbar:SetMinMaxValues(1, 1);
|
|
scrollbar:SetValueStep(1);
|
|
scrollbar:SetValue(1);
|
|
scrollbar:SetObeyStepOnDrag(true);
|
|
scrollbar:SetWidth(16);
|
|
scrollbar:EnableMouseWheel(true);
|
|
window:SetScript("OnMouseWheel", function(self, delta)
|
|
scrollbar:SetValue(window.CurrentIndex - delta);
|
|
end);
|
|
window.SetMinMaxValues = function(self, displayedValue, totalValue)
|
|
scrollbar:SetMinMaxValues(1, math.max(1, totalValue - displayedValue));
|
|
end
|
|
|
|
-- The Corner Grip. (this isn't actually used, but it helps indicate to players that they can do something)
|
|
local grip = window:CreateTexture(nil, "ARTWORK");
|
|
grip:SetTexture(app.asset("grip"));
|
|
grip:SetSize(16, 16);
|
|
grip:SetTexCoord(0,1,0,1);
|
|
grip:SetPoint("BOTTOMRIGHT", -5, 5);
|
|
window:EnableMouseWheel(true);
|
|
window.ScrollBar = scrollbar;
|
|
|
|
-- Setup the Event Handlers
|
|
local handlers = {};
|
|
window:SetScript("OnEvent", function(self, e, ...)
|
|
if debugging then print(e, ...); end
|
|
local handler = handlers[e];
|
|
if handler then
|
|
handler(self, ...);
|
|
else
|
|
self:Update();
|
|
end
|
|
end);
|
|
if not settings.IgnoreQuestUpdates then
|
|
local delayedRefresh = function(self)
|
|
self:DelayedRefresh();
|
|
end;
|
|
handlers.BAG_UPDATE_DELAYED = delayedRefresh;
|
|
handlers.QUEST_WATCH_UPDATE = delayedRefresh;
|
|
handlers.QUEST_ITEM_UPDATE = delayedRefresh;
|
|
window:RegisterEvent("QUEST_WATCH_UPDATE");
|
|
window:RegisterEvent("QUEST_ITEM_UPDATE");
|
|
window:RegisterEvent("BAG_UPDATE_DELAYED");
|
|
local delayedUpdateWithTrigger = function(self)
|
|
self:Redraw();
|
|
self:DelayedUpdate(true);
|
|
end;
|
|
handlers.QUEST_ACCEPTED = delayedUpdateWithTrigger;
|
|
handlers.QUEST_REMOVED = delayedUpdateWithTrigger;
|
|
window:RegisterEvent("QUEST_ACCEPTED");
|
|
window:RegisterEvent("QUEST_REMOVED");
|
|
local delayedUpdate = function(self)
|
|
self:DelayedUpdate();
|
|
end;
|
|
handlers.QUEST_TURNED_IN = delayedUpdate;
|
|
handlers.QUEST_LOG_UPDATE = delayedUpdate;
|
|
window:RegisterEvent("QUEST_TURNED_IN");
|
|
window:RegisterEvent("QUEST_LOG_UPDATE");
|
|
end
|
|
if settings.OnInit then
|
|
settings.OnInit(window, handlers);
|
|
end
|
|
LoadSettingsForWindow(window);
|
|
end
|
|
return window;
|
|
end
|
|
function app:BuildFlatSearchFilteredResponse(groups, filter, t)
|
|
if groups then
|
|
for i,group in ipairs(groups) do
|
|
if filter(group) then
|
|
tinsert(t, CloneReference(group));
|
|
elseif group.g then
|
|
app:BuildFlatSearchFilteredResponse(group.g, filter, t);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
function app:BuildFlatSearchResponse(groups, field, value, t)
|
|
if groups then
|
|
for i,group in ipairs(groups) do
|
|
local v = group[field];
|
|
if v and (v == value or (field == "requireSkill" and app.SpellIDToSkillID[app.SpecializationSpellIDs[v] or 0] == value)) then
|
|
tinsert(t, CloneReference(group));
|
|
elseif group.g then
|
|
app:BuildFlatSearchResponse(group.g, field, value, t);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
function app:BuildFlatSearchResponseForField(groups, field, t)
|
|
if groups then
|
|
for i,group in ipairs(groups) do
|
|
if group[field] then
|
|
tinsert(t, CloneReference(group));
|
|
elseif group.g then
|
|
app:BuildFlatSearchResponseForField(group.g, field, t);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
function app:BuildSearchFilteredResponse(groups, filter)
|
|
if groups then
|
|
local t;
|
|
for i,group in ipairs(groups) do
|
|
local response = app:BuildSearchFilteredResponse(group.g, filter);
|
|
if response then
|
|
if not t then t = {}; end
|
|
tinsert(t, setmetatable({g=response}, { __index = group }));
|
|
elseif filter(group) then
|
|
if not t then t = {}; end
|
|
tinsert(t, CloneReference(group));
|
|
end
|
|
end
|
|
return t;
|
|
end
|
|
end
|
|
function app:BuildSearchResponse(groups, field, value)
|
|
if groups then
|
|
local t;
|
|
for i,group in ipairs(groups) do
|
|
local response = app:BuildSearchResponse(group.g, field, value);
|
|
if response then
|
|
if not t then t = {}; end
|
|
tinsert(t, setmetatable({g=response}, { __index = group }));
|
|
else
|
|
local v = group[field];
|
|
if v and (v == value or (field == "requireSkill" and app.SpellIDToSkillID[app.SpecializationSpellIDs[v] or 0] == value)) then
|
|
if not t then t = {}; end
|
|
tinsert(t, CloneReference(group));
|
|
end
|
|
end
|
|
end
|
|
return t;
|
|
end
|
|
end
|
|
function app:BuildSearchResponseForField(groups, field)
|
|
if groups then
|
|
local t;
|
|
for i,group in ipairs(groups) do
|
|
local response = app:BuildSearchResponseForField(group.g, field);
|
|
if response then
|
|
if not t then t = {}; end
|
|
tinsert(t, setmetatable({g=response}, { __index = group }));
|
|
elseif group[field] then
|
|
if not t then t = {}; end
|
|
tinsert(t, CloneReference(group));
|
|
end
|
|
end
|
|
return t;
|
|
end
|
|
end
|
|
|
|
-- Dynamic Popouts for Quest Chains and other Groups
|
|
local function OnInitForPopout(self, group)
|
|
if group.questID or group.sourceQuests then
|
|
local mainQuest = CloneReference(group);
|
|
if group.parent then mainQuest.sourceParent = group.parent; end
|
|
if mainQuest.sym then
|
|
mainQuest.collectible = true;
|
|
mainQuest.visible = true;
|
|
mainQuest.progress = 0;
|
|
mainQuest.total = 0;
|
|
if not mainQuest.g then
|
|
local resolved = ResolveSymbolicLink(group);
|
|
if resolved then
|
|
for i=#resolved,1,-1 do
|
|
resolved[i] = CreateObject(resolved[i]);
|
|
end
|
|
mainQuest.g = resolved;
|
|
end
|
|
else
|
|
local resolved = ResolveSymbolicLink(group);
|
|
if resolved then
|
|
MergeObjects(mainQuest.g, resolved);
|
|
end
|
|
end
|
|
end
|
|
|
|
if questID then mainQuest.collectible = true; end
|
|
local g = { mainQuest };
|
|
|
|
-- Check to see if Source Quests are listed elsewhere.
|
|
if questID and not group.sourceQuests then
|
|
local searchResults = SearchForField("questID", questID);
|
|
if #searchResults > 1 then
|
|
for i=1,#searchResults,1 do
|
|
local searchResult = searchResults[i];
|
|
if searchResult.questID == questID and searchResult.sourceQuests then
|
|
searchResult = CloneReference(searchResult);
|
|
searchResult.collectible = true;
|
|
searchResult.g = g;
|
|
mainQuest = searchResult;
|
|
g = { mainQuest };
|
|
break;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Show Quest Prereqs
|
|
if mainQuest.sourceQuests then
|
|
local breakafter = 0;
|
|
local sourceQuests, sourceQuest, subSourceQuests, prereqs = mainQuest.sourceQuests;
|
|
while sourceQuests and #sourceQuests > 0 do
|
|
subSourceQuests = {}; prereqs = {};
|
|
for i,sourceQuestID in ipairs(sourceQuests) do
|
|
sourceQuest = sourceQuestID < 1 and SearchForField("creatureID", math.abs(sourceQuestID)) or SearchForField("questID", sourceQuestID);
|
|
if #sourceQuest > 0 then
|
|
local found = nil;
|
|
for i=1,#sourceQuest,1 do
|
|
-- Only care about the first search result.
|
|
local sq = sourceQuest[i];
|
|
if sq and sq.questID and not sq.objectiveID then
|
|
questID = sq.questID;
|
|
if sq.parent and sq.parent.questID == questID then
|
|
sq = sq.parent;
|
|
end
|
|
if app.GroupFilter(sq) then
|
|
if app.RecursiveClassAndRaceFilter(sq) and questID == sourceQuestID then
|
|
if not found or (not found.sourceQuests and sq.sourceQuests) then
|
|
found = sq;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if found then
|
|
sourceQuest = CloneReference(found);
|
|
sourceQuest.collectible = true;
|
|
sourceQuest.visible = true;
|
|
sourceQuest.hideText = true;
|
|
if found.sourceQuests and #found.sourceQuests > 0 and (not found.saved or app.CollectedItemVisibilityFilter(sourceQuest)) then
|
|
-- Mark the sub source quest IDs as marked (as the same sub quest might point to 1 source quest ID)
|
|
for j, subsourceQuests in ipairs(found.sourceQuests) do
|
|
subSourceQuests[subsourceQuests] = true;
|
|
end
|
|
end
|
|
else
|
|
sourceQuest = nil;
|
|
end
|
|
elseif sourceQuestID > 0 then
|
|
-- Create a Quest Object.
|
|
sourceQuest = app.CreateQuest(sourceQuestID, { ['visible'] = true, ['collectible'] = true, ['hideText'] = true });
|
|
else
|
|
-- Create a NPC Object.
|
|
sourceQuest = app.CreateNPC(math.abs(sourceQuestID), { ['visible'] = true, ['hideText'] = true });
|
|
end
|
|
|
|
-- If the quest was valid, attach it.
|
|
if sourceQuest then tinsert(prereqs, sourceQuest); end
|
|
end
|
|
|
|
-- Convert the subSourceQuests table into an array
|
|
sourceQuests = {};
|
|
if #prereqs > 0 then
|
|
for sourceQuestID,i in pairs(subSourceQuests) do
|
|
tinsert(sourceQuests, tonumber(sourceQuestID));
|
|
end
|
|
tinsert(prereqs, {
|
|
["text"] = "Upon Completion",
|
|
["description"] = "The above quests need to be completed before being able to complete the quest(s) listed below.",
|
|
["icon"] = "Interface\\Icons\\Spell_Holy_MagicalSentry.blp",
|
|
["visible"] = true,
|
|
["expanded"] = true,
|
|
["hideText"] = true,
|
|
["g"] = g,
|
|
});
|
|
g = prereqs;
|
|
breakafter = breakafter + 1;
|
|
if breakafter >= 100 then
|
|
app.print("Likely just broke out of an infinite source quest loop. Please report this to the ATT Discord!");
|
|
break;
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Clean up the recursive hierarchy. (this removed duplicates)
|
|
sourceQuests = {};
|
|
prereqs = g;
|
|
local orig = g;
|
|
while prereqs and #prereqs > 0 do
|
|
for i=#prereqs,1,-1 do
|
|
local o = prereqs[i];
|
|
if o.key then
|
|
sourceQuest = o.key .. o[o.key];
|
|
if sourceQuests[sourceQuest] then
|
|
-- Already exists in the hierarchy. Uh oh.
|
|
tremove(prereqs, i);
|
|
else
|
|
sourceQuests[sourceQuest] = true;
|
|
end
|
|
end
|
|
end
|
|
|
|
if #prereqs > 1 then
|
|
prereqs = prereqs[#prereqs];
|
|
if prereqs then prereqs = prereqs.g; end
|
|
orig = prereqs;
|
|
else
|
|
prereqs = prereqs[#prereqs];
|
|
if prereqs then prereqs = prereqs.g; end
|
|
orig[#orig].g = prereqs;
|
|
end
|
|
end
|
|
|
|
-- Clean up standalone "Upon Completion" headers.
|
|
prereqs = g;
|
|
repeat
|
|
local n = #prereqs;
|
|
local lastprereq = prereqs[n];
|
|
if lastprereq.text == "Upon Completion" and n > 1 then
|
|
tremove(prereqs, n);
|
|
local g = prereqs[n-1].g;
|
|
if not g then
|
|
g = {};
|
|
prereqs[n-1].g = g;
|
|
end
|
|
if lastprereq.g then
|
|
for i,data in ipairs(lastprereq.g) do
|
|
tinsert(g, data);
|
|
end
|
|
end
|
|
prereqs = g;
|
|
else
|
|
prereqs = lastprereq.g;
|
|
end
|
|
until not prereqs or #prereqs < 1;
|
|
end
|
|
self.data = {
|
|
text = "Quest Chain Requirements",
|
|
icon = "Interface\\Icons\\Spell_Holy_MagicalSentry.blp",
|
|
description = "The following quests need to be completed before being able to complete the final quest.",
|
|
hideText = true,
|
|
g = g,
|
|
};
|
|
elseif group.sym then
|
|
self.data = CloneReference(group);
|
|
self.data.collectible = true;
|
|
self.data.visible = true;
|
|
self.data.progress = 0;
|
|
self.data.total = 0;
|
|
if not self.data.g then
|
|
local resolved = ResolveSymbolicLink(group);
|
|
if resolved then
|
|
for i=#resolved,1,-1 do
|
|
resolved[i] = CreateObject(resolved[i]);
|
|
end
|
|
self.data.g = resolved;
|
|
end
|
|
else
|
|
local resolved = ResolveSymbolicLink(group);
|
|
if resolved then
|
|
MergeObjects(self.data.g, resolved);
|
|
end
|
|
end
|
|
elseif group.g then
|
|
-- This is already a container with accurate numbers.
|
|
self.data = group;
|
|
else
|
|
-- This is a standalone item
|
|
group.visible = true;
|
|
if not group.g and (group.itemID or group.currencyID) then
|
|
local cmd = group.link or group.key .. ":" .. group[group.key];
|
|
group = GetCachedSearchResults(cmd, SearchForLink, cmd);
|
|
end
|
|
self.data = group;
|
|
end
|
|
|
|
-- Clone the data and then insert it into the Raw Data table.
|
|
self.data = CloneReference(self.data);
|
|
self.data.hideText = true;
|
|
self.data.visible = true;
|
|
self.data.indent = 0;
|
|
self.data.total = 0;
|
|
self.data.progress = 0;
|
|
|
|
-- If this is an achievement, build the criteria within it if possible.
|
|
local achievementID = group.achievementID;
|
|
if achievementID then
|
|
local searchResults = SearchForField("achievementID", achievementID);
|
|
if #searchResults > 0 then
|
|
for i=1,#searchResults,1 do
|
|
local searchResult = searchResults[i];
|
|
if searchResult.achievementID == achievementID and searchResult.criteriaID then
|
|
if not self.data.g then self.data.g = {}; end
|
|
MergeObject(self.data.g, CloneReference(searchResult));
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
--[[
|
|
local currencyID = group.currencyID;
|
|
if currencyID and not self.data.usedtobuy then
|
|
local searchResults = SearchForField("currencyIDAsCost", currencyID);
|
|
if #searchResults > 0 then
|
|
local usedtobuy = {};
|
|
usedtobuy.g = {};
|
|
usedtobuy.text = "Used to Buy";
|
|
usedtobuy.icon = "Interface\\Icons\\INV_Misc_Coin_01";
|
|
usedtobuy.description = "This tooltip dynamically calculates the total number you need based on what is still visible below this header.";
|
|
usedtobuy.OnTooltip = function(t)
|
|
local total = 0;
|
|
for _,o in ipairs(t.g) do
|
|
if o.visible then
|
|
if o.cost then
|
|
for k,v in ipairs(o.cost) do
|
|
if v[1] == "c" and v[2] == currencyID then
|
|
total = total + (v[3] or 1);
|
|
end
|
|
end
|
|
end
|
|
if o.providers then
|
|
for k,v in ipairs(o.providers) do
|
|
if v[1] == "c" and v[2] == currencyID then
|
|
total = total + (v[3] or 1);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
GameTooltip:AddDoubleLine("Total Needed", total);
|
|
end
|
|
MergeObjects(usedtobuy.g, searchResults);
|
|
if not self.data.g then self.data.g = {}; end
|
|
tinsert(self.data.g, usedtobuy);
|
|
self.data.usedtobuy = usedtobuy;
|
|
end
|
|
end
|
|
|
|
local itemID = group.itemID;
|
|
if itemID and not self.data.tradedin then
|
|
local searchResults = SearchForField("itemIDAsCost", itemID);
|
|
if #searchResults > 0 then
|
|
local tradedin = {};
|
|
tradedin.g = {};
|
|
tradedin.text = "Used For";
|
|
tradedin.icon = "Interface\\Icons\\INV_Misc_Coin_01";
|
|
tradedin.description = "This tooltip dynamically calculates the total number you need based on what is still visible below this header.";
|
|
tradedin.OnTooltip = function(t)
|
|
local total = 0;
|
|
for _,o in ipairs(t.g) do
|
|
if o.visible then
|
|
if o.cost then
|
|
for k,v in ipairs(o.cost) do
|
|
if v[1] == "i" and v[2] == itemID then
|
|
total = total + (v[3] or 1);
|
|
end
|
|
end
|
|
end
|
|
if o.providers then
|
|
for k,v in ipairs(o.providers) do
|
|
if v[1] == "i" and v[2] == itemID then
|
|
total = total + (v[3] or 1);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
GameTooltip:AddDoubleLine("Total Needed", total);
|
|
end
|
|
MergeObjects(tradedin.g, searchResults);
|
|
if not self.data.g then self.data.g = {}; end
|
|
tinsert(self.data.g, tradedin);
|
|
self.data.tradedin = tradedin;
|
|
end
|
|
end
|
|
]]--
|
|
|
|
if self.data.key then
|
|
if group.cost and type(group.cost) == "table" then
|
|
local costGroup = {
|
|
["text"] = "Cost",
|
|
["description"] = "The following contains all of the relevant items or currencies needed to acquire this.",
|
|
["icon"] = "Interface\\Icons\\INV_Misc_Coin_02",
|
|
["OnUpdate"] = app.AlwaysShowUpdate,
|
|
["g"] = {},
|
|
};
|
|
local costItem;
|
|
for i,c in ipairs(group.cost) do
|
|
costItem = nil;
|
|
if c[1] == "c" then
|
|
costItem = app.CreateCurrencyClass(c[2]);
|
|
elseif c[1] == "i" then
|
|
costItem = app.CreateItem(c[2]);
|
|
end
|
|
if costItem then
|
|
costItem = CloneReference(costItem);
|
|
costItem.visible = true;
|
|
costItem.OnUpdate = app.AlwaysShowUpdate;
|
|
MergeObject(costGroup.g, costItem);
|
|
end
|
|
end
|
|
if #costGroup.g > 0 then
|
|
if not self.data.g then self.data.g = {}; end
|
|
MergeObject(self.data.g, costGroup, 1);
|
|
end
|
|
end
|
|
|
|
if group.providers or group.qgs or group.crs then
|
|
local sourceGroup = {
|
|
["text"] = "Sources",
|
|
["description"] = "The following contains all of the relevant sources.",
|
|
["icon"] = "Interface\\Icons\\INV_Misc_Coin_02",
|
|
["OnUpdate"] = app.AlwaysShowUpdate,
|
|
["g"] = {},
|
|
};
|
|
local sourceItem;
|
|
if group.providers then
|
|
for _,p in ipairs(group.providers) do
|
|
sourceItem = nil;
|
|
if p[1] == "n" then
|
|
sourceItem = app.CreateNPC(p[2]);
|
|
elseif p[1] == "o" then
|
|
sourceItem = app.CreateObject(p[2]);
|
|
elseif p[1] == "i" then
|
|
sourceItem = app.CreateItem(p[2]);
|
|
end
|
|
if sourceItem then
|
|
sourceItem.visible = true;
|
|
sourceItem.OnUpdate = app.AlwaysShowUpdate;
|
|
MergeObject(sourceGroup.g, sourceItem);
|
|
end
|
|
end
|
|
end
|
|
if group.crs then
|
|
for _,cr in ipairs(group.crs) do
|
|
sourceItem = app.CreateNPC(cr);
|
|
sourceItem.visible = true;
|
|
sourceItem.OnUpdate = app.AlwaysShowUpdate;
|
|
MergeObject(sourceGroup.g, sourceItem);
|
|
end
|
|
end
|
|
if group.qgs then
|
|
for _,qg in ipairs(group.qgs) do
|
|
sourceItem = app.CreateNPC(qg);
|
|
sourceItem.visible = true;
|
|
sourceItem.OnUpdate = app.AlwaysShowUpdate;
|
|
MergeObject(sourceGroup.g, sourceItem);
|
|
end
|
|
end
|
|
if #sourceGroup.g > 0 then
|
|
if not self.data.g then self.data.g = {}; end
|
|
MergeObject(self.data.g, sourceGroup, 1);
|
|
end
|
|
end
|
|
end
|
|
|
|
BuildGroups(self.data);
|
|
UpdateGroups(self.data, self.data.g);
|
|
end
|
|
function app:CreateMiniListForGroup(group)
|
|
-- Is this an achievement criteria or lacking some achievement information?
|
|
local achievementID = group.achievementID;
|
|
if achievementID and (group.criteriaID or not group.g) then
|
|
local searchResults = SearchForField("achievementID", achievementID);
|
|
if #searchResults > 0 then
|
|
local bestResult;
|
|
for i=1,#searchResults,1 do
|
|
local searchResult = searchResults[i];
|
|
if searchResult.achievementID == achievementID and not searchResult.criteriaID then
|
|
if not bestResult or searchResult.g then
|
|
bestResult = searchResult;
|
|
end
|
|
end
|
|
end
|
|
if bestResult then group = bestResult; end
|
|
end
|
|
end
|
|
|
|
-- Is this a quest object or objective?
|
|
local questID, parent = group.questID, group.parent;
|
|
if questID and parent and parent.questID == questID then
|
|
group = parent;
|
|
end
|
|
|
|
-- Pop Out Functionality! :O
|
|
local popout = app:GetWindow(BuildSourceTextForDynamicPath(group), {
|
|
Silent = true,
|
|
AllowCompleteSound = true,
|
|
--Debugging = true,
|
|
OnInit = function(self)
|
|
OnInitForPopout(self, (group.OnPopout and group:OnPopout()) or group);
|
|
end,
|
|
OnLoad = function(self, settings)
|
|
settings.dynamic = true;
|
|
settings.sourcePath = self.Suffix;
|
|
|
|
-- This might be something we can rebuild
|
|
local key = group.key;
|
|
if key then
|
|
settings.key = key;
|
|
settings.id = group[key];
|
|
end
|
|
end,
|
|
OnSave = function(self, settings)
|
|
if not settings.visible then
|
|
self:ClearSettings();
|
|
end
|
|
end,
|
|
});
|
|
if IsAltKeyDown() then
|
|
AddTomTomWaypoint(popout.data, false);
|
|
else
|
|
if not popout.data.expanded then
|
|
ExpandGroupsRecursively(popout.data, true, true);
|
|
end
|
|
popout:SetVisible(true);
|
|
end
|
|
return popout;
|
|
end
|
|
local function SearchForSourcePath(g, hashes, level, count)
|
|
if g then
|
|
local hash = hashes[level];
|
|
if hash then
|
|
for i,o in ipairs(g) do
|
|
if (o.hash or o.name or o.text) == hash then
|
|
if level == count then return o; end
|
|
return SearchForSourcePath(o.g, hashes, level + 1, count);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
function app:CreateMiniListFromSource(key, id, sourcePath)
|
|
-- If we provided the original source path, then we can find the exact element to popout.
|
|
if sourcePath then
|
|
local hashes = { strsplit(">", sourcePath) };
|
|
local ref = SearchForSourcePath(app:GetDataCache().g, hashes, 2, #hashes);
|
|
if ref then
|
|
app:CreateMiniListForGroup(ref);
|
|
return;
|
|
end
|
|
end
|
|
|
|
-- Without this it can't be recovered. :(
|
|
if key and id then
|
|
if sourcePath then
|
|
-- Try to find an exact match.
|
|
local searchResults = SearchForField(key, id);
|
|
if #searchResults > 0 then
|
|
for i,ref in ipairs(searchResults) do
|
|
if BuildSourceTextForDynamicPath(ref) == sourcePath then
|
|
app:CreateMiniListForGroup(ref);
|
|
return;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Search for the Link in the database
|
|
local cmd = key .. ":" .. id;
|
|
local ref = GetCachedSearchResults(cmd, SearchForLink, cmd);
|
|
if ref then
|
|
app:CreateMiniListForGroup(ref);
|
|
return;
|
|
end
|
|
|
|
-- Search for the field/value pair everywhere in the DB.
|
|
local t = {};
|
|
app:BuildFlatSearchResponse(app:GetDataCache().g, key, id, t);
|
|
if t and #t > 0 then
|
|
local ref = #t == 1 and t[1] or CreateObject({ hash = key .. id, key = key, [key] = id, g = t });
|
|
if ref then
|
|
app:CreateMiniListForGroup(ref);
|
|
return;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Create the Primary Collection Window (this allows you to save the size and location)
|
|
app:GetWindow("Prime", {
|
|
parent = UIParent,
|
|
Silent = true,
|
|
AllowCompleteSound = true,
|
|
Defaults = {
|
|
["y"] = 20,
|
|
["x"] = 0,
|
|
["scale"] = 1.2,
|
|
["width"] = 360,
|
|
["height"] = 600,
|
|
["visible"] = false,
|
|
["point"] = "CENTER",
|
|
["relativePoint"] = "CENTER",
|
|
},
|
|
OnInit = function(self)
|
|
app.ToggleMainList = function()
|
|
self:Toggle();
|
|
end
|
|
|
|
SLASH_ATTPRIME1 = "/allthethings";
|
|
SLASH_ATTPRIME2 = "/att";
|
|
SLASH_ATTPRIME3 = "/attc";
|
|
SLASH_ATTPRIME4 = "/things";
|
|
SLASH_ATTPRIME5 = "/attmain";
|
|
SlashCmdList["ATTPRIME"] = function(cmd)
|
|
if cmd and strlen(cmd) > 0 then
|
|
-- Search for the Link in the database
|
|
cmd = string.lower(cmd);
|
|
local group = GetCachedSearchResults(cmd, SearchForLink, cmd);
|
|
if group then app:CreateMiniListForGroup(group); end
|
|
else
|
|
-- Default command
|
|
self:Toggle();
|
|
end
|
|
end
|
|
|
|
end,
|
|
OnLoad = function(self, settings)
|
|
if not settings.visible then
|
|
self:ForceRebuild();
|
|
end
|
|
end,
|
|
OnRebuild = function(self)
|
|
-- Prime's data is built elsewhere.
|
|
self.data = app:GetDataCache();
|
|
return false;
|
|
end,
|
|
OnUpdate = function(self, ...)
|
|
self.DefaultUpdate(self, ...);
|
|
|
|
-- Write the current character's progress.
|
|
local rootData = self.data;
|
|
app.CurrentCharacter.PrimeData = {
|
|
progress = rootData.progress,
|
|
total = rootData.total,
|
|
modeString = rootData.modeString,
|
|
};
|
|
end
|
|
});
|
|
|
|
|
|
|
|
local function RefreshLocationCoroutine()
|
|
-- Wait a second, will ya? The position detection is BAD.
|
|
for i=1,30,1 do coroutine.yield(); end
|
|
|
|
-- Acquire the new map ID.
|
|
local mapID = app.GetCurrentMapID();
|
|
while not mapID do
|
|
coroutine.yield();
|
|
mapID = app.GetCurrentMapID();
|
|
end
|
|
app.CurrentMapID = mapID;
|
|
local window = app:GetWindow("CurrentInstance");
|
|
if window then window:SetMapID(mapID); end
|
|
end
|
|
local function SortForMiniList(a,b)
|
|
-- If either object doesn't exist
|
|
if a then
|
|
if not b then
|
|
return true;
|
|
end
|
|
elseif b then
|
|
return false;
|
|
else
|
|
-- neither a or b exists, equality returns false
|
|
return false;
|
|
end
|
|
|
|
if a.isRaid then
|
|
if not b.isRaid then
|
|
return true;
|
|
end
|
|
elseif b.isRaid then
|
|
return false;
|
|
elseif b.maps or b.mapID then
|
|
if not (a.maps or a.mapID) then
|
|
return true;
|
|
end
|
|
elseif a.maps then
|
|
return false;
|
|
end
|
|
|
|
-- Any two similar-type groups with text
|
|
return tostring(a.name or a.text) < tostring(b.name or b.text);
|
|
end
|
|
local function RefreshLocation()
|
|
app:StartATTCoroutine("RefreshLocation", RefreshLocationCoroutine);
|
|
end
|
|
|
|
local CachedMapData = setmetatable({}, {
|
|
__index = function(cachedMapData, mapID)
|
|
local results = SearchForField("mapID", mapID);
|
|
if #results > 0 then
|
|
-- Simplify the returned groups
|
|
local groups = {};
|
|
local headers = setmetatable({}, {
|
|
__index = function(t, headerID)
|
|
for i=1,#groups,1 do
|
|
local o = groups[i];
|
|
if o.headerID == headerID then
|
|
if not o.g then o.g = {}; end
|
|
t[headerID] = o;
|
|
return o;
|
|
end
|
|
end
|
|
|
|
local o = app.CreateNPC(headerID);
|
|
tinsert(groups, o);
|
|
t[headerID] = o;
|
|
o.g = {};
|
|
return o;
|
|
end
|
|
});
|
|
local function MergeIntoHeader(headerID, o)
|
|
MergeObject(headers[headerID].g, o);
|
|
end
|
|
|
|
local header = {};
|
|
header.mapID = mapID;
|
|
header.g = groups;
|
|
for i, group in ipairs(results) do
|
|
local clone = {};
|
|
for key,value in pairs(group) do
|
|
if key == "maps" then
|
|
local maps = {};
|
|
for i,mapID in ipairs(value) do
|
|
tinsert(maps, mapID);
|
|
end
|
|
clone[key] = maps;
|
|
elseif key == "g" then
|
|
local g = {};
|
|
for i,o in ipairs(value) do
|
|
o = CloneReference(o);
|
|
ExpandGroupsRecursively(o, false);
|
|
tinsert(g, o);
|
|
end
|
|
clone[key] = g;
|
|
else
|
|
clone[key] = value;
|
|
end
|
|
end
|
|
local c = GetRelativeValue(group, "c");
|
|
if c then clone.c = c; end
|
|
local r = GetRelativeValue(group, "r");
|
|
if r then clone.r = r; end
|
|
setmetatable(clone, getmetatable(group));
|
|
|
|
local key = group.key;
|
|
if (key == "mapID" or key == "instanceID") or ((key == "headerID" or key == "npcID") and (group.maps and (mapID < 0 and contains(group.maps, mapID)))) then
|
|
header.key = key;
|
|
header[key] = group[key];
|
|
MergeObject({header}, clone);
|
|
elseif key == "criteriaID" then
|
|
clone.achievementID = group.achievementID;
|
|
MergeIntoHeader(app.HeaderConstants.ACHIEVEMENTS, clone);
|
|
elseif key == "achievementID" then
|
|
MergeIntoHeader(app.HeaderConstants.ACHIEVEMENTS, clone);
|
|
elseif key == "questID" then
|
|
MergeIntoHeader(app.HeaderConstants.QUESTS, clone);
|
|
elseif key == "factionID" then
|
|
MergeIntoHeader(app.HeaderConstants.FACTIONS, clone);
|
|
elseif key == "explorationID" then
|
|
MergeIntoHeader(app.HeaderConstants.EXPLORATION, clone);
|
|
elseif key == "flightPathID" then
|
|
MergeIntoHeader(app.HeaderConstants.FLIGHT_PATHS, clone);
|
|
elseif key == "itemID" or key == "spellID" then
|
|
if GetRelativeField(group, "headerID", app.HeaderConstants.ZONE_DROPS) then
|
|
MergeIntoHeader(app.HeaderConstants.ZONE_DROPS, clone);
|
|
else
|
|
local requireSkill = GetRelativeValue(group, "requireSkill");
|
|
if requireSkill then
|
|
MergeObject(groups, app.CreateProfession(requireSkill, { g = { clone } }));
|
|
else
|
|
local headerID = GetRelativeValue(group, "headerID");
|
|
if headerID then
|
|
MergeIntoHeader(headerID, clone);
|
|
else
|
|
MergeObject(groups, clone);
|
|
end
|
|
end
|
|
end
|
|
elseif key == "headerID" then
|
|
MergeObject(groups, clone);
|
|
else
|
|
local headerID = GetRelativeValue(group, "headerID");
|
|
if headerID then
|
|
MergeIntoHeader(headerID, clone);
|
|
else
|
|
MergeObject(groups, clone);
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Swap out the map data for the header.
|
|
results = ((results.classID and app.CreateCharacterClass) or (header.key == "instanceID" and app.CreateInstance) or app.CreateMap)(header[header.key], header);
|
|
ExpandGroupsRecursively(results, true);
|
|
results.visible = true;
|
|
results.expanded = true;
|
|
results.mapID = mapID;
|
|
results.back = 1;
|
|
results.indent = 0;
|
|
|
|
local difficultyID = (IsInInstance() and select(3, GetInstanceInfo())) or (EJ_GetDifficulty and EJ_GetDifficulty()) or 0;
|
|
if difficultyID ~= 0 then
|
|
for _,row in ipairs(header.g) do
|
|
if row.difficultyID or row.difficulties then
|
|
if (row.difficultyID or -1) == difficultyID or (row.difficulties and containsValue(row.difficulties, difficultyID)) then
|
|
if not row.expanded then ExpandGroupsRecursively(row, true, true); expanded = true; end
|
|
elseif row.expanded then
|
|
ExpandGroupsRecursively(row, false, true);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Sort the list, but not for instances.
|
|
if not results.instanceID then
|
|
app.Sort(groups, SortForMiniList);
|
|
end
|
|
|
|
-- Check to see completion...
|
|
BuildGroups(results);
|
|
cachedMapData[mapID] = results;
|
|
return results;
|
|
else
|
|
-- If we don't have any map data on this area, report it to the chat window.
|
|
print("No map found for this location ", app.GetMapName(mapID), " [", mapID, "]");
|
|
|
|
local mapInfo = C_Map_GetMapInfo(mapID);
|
|
if mapInfo then
|
|
local mapPath = mapInfo.name or ("Map ID #" .. mapID);
|
|
mapID = mapInfo.parentMapID;
|
|
while mapID do
|
|
mapInfo = C_Map_GetMapInfo(mapID);
|
|
if mapInfo then
|
|
mapPath = (mapInfo.name or ("Map ID #" .. mapID)) .. " > " .. mapPath;
|
|
mapID = mapInfo.parentMapID;
|
|
else
|
|
break;
|
|
end
|
|
end
|
|
print("Path: ", mapPath);
|
|
end
|
|
print("Please report this to the ATT Discord! Thanks! ", app.Version);
|
|
end
|
|
end
|
|
});
|
|
app:GetWindow("CurrentInstance", {
|
|
parent = UIParent,
|
|
Silent = true,
|
|
AllowCompleteSound = true,
|
|
Defaults = {
|
|
["y"] = 0,
|
|
["x"] = 0,
|
|
["scale"] = 0.7,
|
|
["width"] = 360,
|
|
["height"] = 176,
|
|
["visible"] = true,
|
|
["point"] = "BOTTOMRIGHT",
|
|
["relativePoint"] = "BOTTOMRIGHT",
|
|
},
|
|
OnInit = function(self, handlers)
|
|
SLASH_ATTMINILIST1 = "/attmini";
|
|
SLASH_ATTMINILIST2 = "/attminilist";
|
|
app.ToggleMiniListForCurrentZone = function() self:Toggle(); end;
|
|
SlashCmdList["ATTMINILIST"] = app.ToggleMiniListForCurrentZone;
|
|
|
|
local delayedUpdate = function()
|
|
self:DelayedUpdate(true);
|
|
end;
|
|
handlers.QUEST_TURNED_IN = delayedUpdate;
|
|
handlers.QUEST_LOG_UPDATE = delayedUpdate;
|
|
handlers.ZONE_CHANGED = RefreshLocation;
|
|
handlers.ZONE_CHANGED_INDOORS = RefreshLocation;
|
|
handlers.ZONE_CHANGED_NEW_AREA = RefreshLocation;
|
|
handlers.PLAYER_DIFFICULTY_CHANGED = function()
|
|
wipe(CachedMapData);
|
|
self:Rebuild();
|
|
end
|
|
self.SetMapID = function(self, mapID)
|
|
if mapID ~= self.mapID then
|
|
self.mapID = mapID;
|
|
self:Rebuild();
|
|
end
|
|
end
|
|
end,
|
|
OnLoad = function(self, settings)
|
|
self:RegisterEvent("ZONE_CHANGED");
|
|
self:RegisterEvent("ZONE_CHANGED_INDOORS");
|
|
self:RegisterEvent("ZONE_CHANGED_NEW_AREA");
|
|
pcall(self.RegisterEvent, self, "PLAYER_DIFFICULTY_CHANGED");
|
|
self:SetMapID(settings.mapID or app.CurrentMapID or app.GetCurrentMapID());
|
|
RefreshLocation();
|
|
end,
|
|
OnSave = function(self, settings)
|
|
settings.mapID = self.mapID;
|
|
end,
|
|
OnRebuild = function(self)
|
|
self.data = CachedMapData[self.mapID];
|
|
end,
|
|
});
|
|
|
|
|
|
-- Uncomment this section if you need to enable Debugger:
|
|
--[[
|
|
app:GetWindow("Debugger", {
|
|
parent = UIParent,
|
|
Silent = true,
|
|
OnInit = function(self, handlers)
|
|
self.AddObject = function(self, info)
|
|
-- Bubble Up the Maps
|
|
local mapInfo;
|
|
local mapID = app.CurrentMapID;
|
|
if mapID then
|
|
local pos = C_Map_GetPlayerMapPosition(mapID, "player");
|
|
if pos then
|
|
local px, py = pos:GetXY();
|
|
info.coord = { px * 100, py * 100, mapID };
|
|
end
|
|
repeat
|
|
mapInfo = C_Map_GetMapInfo(mapID);
|
|
if mapInfo then
|
|
info = { ["mapID"] = mapInfo.mapID, ["g"] = { info } };
|
|
mapID = mapInfo.parentMapID
|
|
end
|
|
until not mapInfo or not mapID;
|
|
end
|
|
|
|
MergeClone(self.data.g, info);
|
|
MergeObject(self.rawData, info);
|
|
self:Update();
|
|
end
|
|
self.data = {
|
|
['text'] = "Session History",
|
|
['icon'] = app.asset("WindowIcon_RaidAssistant"),
|
|
["description"] = "This keeps a visual record of all of the quests, maps, loot, and vendors that you have come into contact with since the session was started.",
|
|
['visible'] = true,
|
|
['expanded'] = true,
|
|
['back'] = 1,
|
|
['options'] = {
|
|
{
|
|
['text'] = "Clear History",
|
|
['icon'] = "Interface\\Icons\\Ability_Rogue_FeignDeath.blp",
|
|
["description"] = "Click this to fully clear this window.\n\nNOTE: If you click this by accident, use the dynamic Restore Buttons that this generates to reapply the data that was cleared.\n\nWARNING: If you reload the UI, the data stored in the Reload Button will be lost forever!",
|
|
['visible'] = true,
|
|
['count'] = 0,
|
|
['OnClick'] = function(row, button)
|
|
local copy = {};
|
|
for i,o in ipairs(self.rawData) do
|
|
tinsert(copy, o);
|
|
end
|
|
if #copy < 1 then
|
|
app.print("There is nothing to clear.");
|
|
return true;
|
|
end
|
|
row.ref.count = row.ref.count + 1;
|
|
tinsert(self.data.options, {
|
|
['text'] = "Restore Button " .. row.ref.count,
|
|
['icon'] = app.asset("Button_Reroll"),
|
|
["description"] = "Click this to restore your cleared data.\n\nNOTE: Each Restore Button houses different data.\n\nWARNING: This data will be lost forever when you reload your UI!",
|
|
['visible'] = true,
|
|
['data'] = copy,
|
|
['OnClick'] = function(row, button)
|
|
for i,info in ipairs(row.ref.data) do
|
|
MergeClone(self.data.g, info);
|
|
MergeObject(self.rawData, info);
|
|
end
|
|
self:Update();
|
|
return true;
|
|
end,
|
|
});
|
|
wipe(self.rawData);
|
|
wipe(self.data.g);
|
|
for i=#self.data.options,1,-1 do
|
|
tinsert(self.data.g, 1, self.data.options[i]);
|
|
end
|
|
self:Update();
|
|
return true;
|
|
end,
|
|
},
|
|
},
|
|
['g'] = {},
|
|
};
|
|
self.rawData = {};
|
|
|
|
-- Setup Event Handlers and register for events
|
|
self:SetScript("OnEvent", function(self, e, ...)
|
|
--print(e, ...);
|
|
if e == "ADDON_LOADED" then
|
|
-- Only execute for this addon.
|
|
local addonName = ...;
|
|
if addonName ~= appName then return; end
|
|
self:UnregisterEvent("ADDON_LOADED");
|
|
if not ATTClassicDebugData then
|
|
ATTClassicDebugData = app.GetDataMember("Debugger", {});
|
|
app.SetDataMember("Debugger", nil);
|
|
end
|
|
self.rawData = ATTClassicDebugData;
|
|
self.data.g = CreateObject(self.rawData);
|
|
for i=#self.data.options,1,-1 do
|
|
tinsert(self.data.g, 1, self.data.options[i]);
|
|
end
|
|
self:Update();
|
|
elseif e == "ZONE_CHANGED" or e == "ZONE_CHANGED_NEW_AREA" then
|
|
-- Bubble Up the Maps
|
|
local mapInfo, info;
|
|
local mapID = app.CurrentMapID;
|
|
if mapID then
|
|
repeat
|
|
info = { ["mapID"] = mapID, ["g"] = info and { info } or nil };
|
|
mapInfo = C_Map_GetMapInfo(mapID);
|
|
if mapInfo then
|
|
mapID = mapInfo.parentMapID;
|
|
end
|
|
until not mapInfo or not mapID;
|
|
|
|
MergeClone(self.data.g, info);
|
|
MergeObject(self.rawData, info);
|
|
self:Update();
|
|
end
|
|
elseif e == "MERCHANT_SHOW" or e == "MERCHANT_UPDATE" then
|
|
C_Timer.After(0.6, function()
|
|
local guid = UnitGUID("npc");
|
|
local ty, zero, server_id, instance_id, zone_uid, npcID, spawn_uid;
|
|
if guid then ty, zero, server_id, instance_id, zone_uid, npcID, spawn_uid = strsplit("-",guid); end
|
|
if npcID then
|
|
npcID = tonumber(npcID);
|
|
|
|
-- Ignore vendor mount...
|
|
if npcID == 62822 then
|
|
return true;
|
|
end
|
|
|
|
local numItems = GetMerchantNumItems();
|
|
--print("MERCHANT DETAILS", ty, npcID, numItems);
|
|
|
|
local rawGroups = {};
|
|
for i=1,numItems,1 do
|
|
local link = GetMerchantItemLink(i);
|
|
if link then
|
|
local name, texture, cost, quantity, numAvailable, isPurchasable, isUsable, extendedCost = GetMerchantItemInfo(i);
|
|
if extendedCost then
|
|
cost = {};
|
|
local itemCount = GetMerchantItemCostInfo(i);
|
|
for j=1,itemCount,1 do
|
|
local itemTexture, itemValue, itemLink = GetMerchantItemCostItem(i, j);
|
|
if itemLink then
|
|
-- print(" ", itemValue, itemLink, gsub(itemLink, "\124", "\124\124"));
|
|
local m = itemLink:match("currency:(%d+)");
|
|
if m then
|
|
-- Parse as a CURRENCY.
|
|
tinsert(cost, {"c", tonumber(m), itemValue});
|
|
else
|
|
-- Parse as an ITEM.
|
|
tinsert(cost, {"i", tonumber(itemLink:match("item:(%d+)")), itemValue});
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Parse as an ITEM LINK.
|
|
tinsert(rawGroups, {["itemID"] = tonumber(link:match("item:(%d+)")), ["cost"] = cost});
|
|
end
|
|
end
|
|
|
|
local info = { [(ty == "GameObject") and "objectID" or "npcID"] = npcID };
|
|
info.faction = UnitFactionGroup("npc");
|
|
info.text = UnitName("npc");
|
|
info.g = rawGroups;
|
|
self:AddObject(info);
|
|
end
|
|
end);
|
|
elseif e == "GOSSIP_SHOW" then
|
|
local guid = UnitGUID("npc");
|
|
if guid then
|
|
local type, zero, server_id, instance_id, zone_uid, npcID, spawn_uid = strsplit("-",guid);
|
|
if npcID then
|
|
npcID = tonumber(npcID);
|
|
--print("GOSSIP_SHOW", type, npcID);
|
|
if type == "GameObject" then
|
|
info = { ["objectID"] = npcID, ["text"] = UnitName("npc") };
|
|
else
|
|
info = { ["npcID"] = npcID };
|
|
info.name = UnitName("npc");
|
|
end
|
|
info.faction = UnitFactionGroup("npc");
|
|
self:AddObject(info);
|
|
end
|
|
end
|
|
elseif e == "QUEST_DETAIL" then
|
|
local questStartItemID = ...;
|
|
local questID = GetQuestID();
|
|
if questID == 0 then return false; end
|
|
local npc = "questnpc";
|
|
local guid = UnitGUID(npc);
|
|
if not guid then
|
|
npc = "npc";
|
|
guid = UnitGUID(npc);
|
|
end
|
|
local type, zero, server_id, instance_id, zone_uid, npcID, spawn_uid;
|
|
if guid then type, zero, server_id, instance_id, zone_uid, npcID, spawn_uid = strsplit("-",guid); end
|
|
-- print("QUEST_DETAIL", questStartItemID, " => Quest #", questID, type, npcID);
|
|
|
|
local rawGroups = {};
|
|
for i=1,GetNumQuestRewards(),1 do
|
|
local link = GetQuestItemLink("reward", i);
|
|
if link then tinsert(rawGroups, { ["itemID"] = GetItemInfoInstant(link) }); end
|
|
end
|
|
for i=1,GetNumQuestChoices(),1 do
|
|
local link = GetQuestItemLink("choice", i);
|
|
if link then tinsert(rawGroups, { ["itemID"] = GetItemInfoInstant(link) }); end
|
|
end
|
|
for i=1,GetNumQuestLogRewardSpells(questID),1 do
|
|
local texture, name, isTradeskillSpell, isSpellLearned, hideSpellLearnText, isBoostSpell, garrFollowerID, genericUnlock, spellID = GetQuestLogRewardSpell(i, questID);
|
|
if spellID then
|
|
if isTradeskillSpell then
|
|
tinsert(rawGroups, { ["recipeID"] = spellID, ["name"] = name });
|
|
else
|
|
tinsert(rawGroups, { ["spellID"] = spellID, ["name"] = name });
|
|
end
|
|
end
|
|
end
|
|
|
|
local info = { ["questID"] = questID, ["description"] = GetQuestText(), ["objectives"] = GetObjectiveText(), ["g"] = rawGroups };
|
|
if questStartItemID and questStartItemID > 0 then info.itemID = questStartItemID; end
|
|
if npcID then
|
|
npcID = tonumber(npcID);
|
|
if type == "GameObject" then
|
|
info = { ["objectID"] = npcID, ["text"] = UnitName(npc), ["g"] = { info } };
|
|
else
|
|
info.qgs = {npcID};
|
|
info.name = UnitName(npc);
|
|
end
|
|
info.faction = UnitFactionGroup(npc);
|
|
end
|
|
self:AddObject(info);
|
|
elseif e == "CHAT_MSG_LOOT" then
|
|
local msg, player, a, b, c, d, e, f, g, h, i, j, k, l = ...;
|
|
local itemString = string.match(msg, "item[%-?%d:]+");
|
|
if itemString then
|
|
self:AddObject({ ["itemID"] = GetItemInfoInstant(itemString) });
|
|
end
|
|
end
|
|
end);
|
|
self:RegisterEvent("ADDON_LOADED");
|
|
self:RegisterEvent("GOSSIP_SHOW");
|
|
self:RegisterEvent("QUEST_DETAIL");
|
|
self:RegisterEvent("TRADE_SKILL_LIST_UPDATE");
|
|
self:RegisterEvent("ZONE_CHANGED_NEW_AREA");
|
|
self:RegisterEvent("ZONE_CHANGED");
|
|
self:RegisterEvent("MERCHANT_SHOW");
|
|
self:RegisterEvent("MERCHANT_UPDATE");
|
|
self:RegisterEvent("CHAT_MSG_LOOT");
|
|
--self:RegisterAllEvents();
|
|
end,
|
|
OnUpdate = function(self, ...)
|
|
-- Update the window and all of its row data
|
|
if self.data.OnUpdate then self.data.OnUpdate(self.data); end
|
|
for i,g in ipairs(self.data.g) do
|
|
if g.OnUpdate then g.OnUpdate(g); end
|
|
end
|
|
self.data.index = 0;
|
|
self.data.back = 1;
|
|
self.data.indent = 0;
|
|
BuildGroups(self.data);
|
|
UpdateWindow(self, true);
|
|
end
|
|
});
|
|
]]--
|
|
app:GetWindow("ItemFilter", {
|
|
parent = UIParent,
|
|
Silent = true,
|
|
OnUpdate = function(self, ...)
|
|
if not self.initialized then
|
|
self.initialized = true;
|
|
self.dirty = true;
|
|
|
|
-- Item Filter
|
|
local actions = {
|
|
['text'] = "Item Filters",
|
|
['icon'] = app.asset("Category_ItemSets"),
|
|
["description"] = "You can search the ATT Database by using a item filter.",
|
|
['visible'] = true,
|
|
['expanded'] = true,
|
|
['back'] = 1,
|
|
['OnUpdate'] = function(data)
|
|
if not self.dirty then return nil; end
|
|
self.dirty = nil;
|
|
|
|
local g = {};
|
|
tinsert(g, 1, data.setItemFilter);
|
|
if #data.results > 0 then
|
|
for i,result in ipairs(data.results) do
|
|
tinsert(g, result);
|
|
end
|
|
end
|
|
data.g = g;
|
|
if #g > 0 then
|
|
for i,entry in ipairs(g) do
|
|
entry.indent = nil;
|
|
end
|
|
data.indent = 0;
|
|
data.visible = true;
|
|
BuildGroups(data);
|
|
app.UpdateGroups(data, data.g);
|
|
if not data.expanded then
|
|
data.expanded = true;
|
|
ExpandGroupsRecursively(data, true);
|
|
end
|
|
end
|
|
|
|
-- Update the groups without forcing Debug Mode.
|
|
local visibilityFilter = app.VisibilityFilter;
|
|
app.VisibilityFilter = app.ObjectVisibilityFilter;
|
|
BuildGroups(self.data);
|
|
UpdateWindow(self, true);
|
|
app.VisibilityFilter = visibilityFilter;
|
|
end,
|
|
['g'] = {},
|
|
['results'] = {},
|
|
['setItemFilter'] = {
|
|
['text'] = "Set Item Filter",
|
|
['icon'] = "Interface\\Icons\\INV_MISC_KEY_12",
|
|
['description'] = "Click this to change the item filter you want to search for within ATT.",
|
|
['visible'] = true,
|
|
['OnClick'] = function(row, button)
|
|
app:ShowPopupDialogWithEditBox("Which Item Filter would you like to search for?", "", function(text)
|
|
text = string.lower(text);
|
|
local f = tonumber(text);
|
|
if tostring(f) ~= text then
|
|
-- The string form did not match, the filter must have been by name.
|
|
for id,filter in pairs(L["FILTER_ID_TYPES"]) do
|
|
if string.match(string.lower(filter), text) then
|
|
f = tonumber(id);
|
|
break;
|
|
end
|
|
end
|
|
end
|
|
if f then
|
|
self.data.results = app:BuildSearchResponse(app:GetDataCache().g, "f", f);
|
|
row.ref.f = f;
|
|
self.dirty = true;
|
|
end
|
|
wipe(searchCache);
|
|
self:Update();
|
|
end);
|
|
return true;
|
|
end,
|
|
['OnUpdate'] = app.AlwaysShowUpdate,
|
|
},
|
|
};
|
|
|
|
self.Reset = function()
|
|
self.data = actions;
|
|
end
|
|
self:Reset();
|
|
end
|
|
|
|
-- Update the window and all of its row data
|
|
if self.data.OnUpdate then self.data.OnUpdate(self.data, self); end
|
|
UpdateWindow(self, true);
|
|
end
|
|
});
|
|
app:GetWindow("ItemFinder", {
|
|
parent = UIParent,
|
|
Silent = true,
|
|
OnUpdate = function(self, ...)
|
|
if not self.initialized then
|
|
self.initialized = true;
|
|
self.data = {
|
|
text = "Item Finder",
|
|
icon = app.asset("WindowIcon_RaidAssistant"),
|
|
description = "This is a contribution debug tool. NOT intended to be used by the majority of the player base.\n\nUsing this tool will lag your WoW every 5 seconds. Not sure why - likely a bad Blizzard Database thing.",
|
|
visible = true,
|
|
expanded = true,
|
|
progress = 0,
|
|
total = 0,
|
|
back = 1,
|
|
currentItemID = 60000,
|
|
minimumItemID = 0,
|
|
g = {
|
|
{
|
|
text = "Update Now",
|
|
icon = app.asset("Button_Reroll"),
|
|
description = "Click this to update the listing. Doing so shall remove all invalid, grey, or white items.",
|
|
visible = true,
|
|
fails = 0,
|
|
OnClick = function(row, button)
|
|
self:Update(true);
|
|
return true;
|
|
end,
|
|
OnUpdate = app.AlwaysShowUpdate,
|
|
},
|
|
},
|
|
OnUpdate = function(header)
|
|
local g = header.g;
|
|
if g then
|
|
local count = #g;
|
|
if count > 0 then
|
|
for i=count,1,-1 do
|
|
if g[i].collected then
|
|
tremove(g, i);
|
|
end
|
|
end
|
|
end
|
|
for count=#g,100 do
|
|
local i = header.currentItemID - 1;
|
|
if i > header.minimumItemID then
|
|
header.currentItemID = i;
|
|
tinsert(g, app.CreateItemHarvester(i, {
|
|
parent = header
|
|
}));
|
|
end
|
|
end
|
|
self:DelayedUpdate(true);
|
|
self.delayRemaining = 1;
|
|
end
|
|
end
|
|
};
|
|
end
|
|
self.data.progress = 0;
|
|
self.data.total = 0;
|
|
UpdateGroups(self.data, self.data.g);
|
|
UpdateWindow(self, ...);
|
|
if self.data.OnUpdate then self.data.OnUpdate(self.data); end
|
|
end
|
|
});
|
|
app:GetWindow("Tradeskills", {
|
|
parent = UIParent,
|
|
Silent = true,
|
|
AllowCompleteSound = true,
|
|
OnInit = function(self, handlers)
|
|
SLASH_ATTSKILLS1 = "/attskills";
|
|
SLASH_ATTSKILLS2 = "/atttradeskill";
|
|
SLASH_ATTSKILLS3 = "/attprofession";
|
|
SLASH_ATTSKILLS4 = "/attprof";
|
|
SlashCmdList["ATTSKILLS"] = function(cmd)
|
|
self:Toggle();
|
|
end
|
|
self:SetMovable(false);
|
|
self:SetClampedToScreen(false);
|
|
self.wait = 5;
|
|
self.cache = {};
|
|
self.header = {
|
|
['text'] = "Profession List",
|
|
['icon'] = "Interface\\Icons\\INV_Scroll_04",
|
|
["description"] = "Open your professions to cache them.",
|
|
['visible'] = true,
|
|
['expanded'] = true,
|
|
["indent"] = 0,
|
|
['back'] = 1,
|
|
['g'] = { },
|
|
};
|
|
self.data = self.header;
|
|
self.previousCraftSkillID = 0;
|
|
self.previousTradeSkillID = 0;
|
|
self.CacheRecipes = function(self)
|
|
-- If it's not yours, don't take credit for it.
|
|
if IsTradeSkillLinked and IsTradeSkillLinked() then
|
|
return;
|
|
end
|
|
if C_TradeSkillUI then
|
|
if (C_TradeSkillUI.IsTradeSkillLinked and C_TradeSkillUI.IsTradeSkillLinked())
|
|
or (C_TradeSkillUI.IsTradeSkillGuild and C_TradeSkillUI.IsTradeSkillGuild()) then
|
|
return;
|
|
end
|
|
end
|
|
|
|
-- Cache Learned Spells
|
|
local skillCache = SearchForFieldContainer("spellID");
|
|
if skillCache then
|
|
-- Cache learned recipes and reagents
|
|
local reagentCache = app.GetDataMember("Reagents", {});
|
|
local learned, craftSkillID, tradeSkillID = 0, 0, 0;
|
|
rawset(app.SpellNameToSpellID, 0, nil);
|
|
app.GetSpellName(0);
|
|
|
|
if CraftFrame and CraftFrame:IsVisible() then
|
|
-- Crafting Skills (Enchanting and Beast Training Only)
|
|
local craftSkillName, craftSkillLevel, craftSkillMaxLevel = GetCraftDisplaySkillLine();
|
|
if craftSkillName and craftSkillName ~= "UNKNOWN" then
|
|
local shouldShowSpellRanks = craftSkillLevel and craftSkillLevel ~= math.max(300, craftSkillMaxLevel);
|
|
craftSkillID = app.SpellNameToSpellID[craftSkillName] or 0;
|
|
if craftSkillID == 0 then
|
|
app.print("Could not find spellID for", craftSkillName, GetLocale(), "! Please report this to the ATT Discord!");
|
|
end
|
|
elseif CraftFrameTitleText then
|
|
craftSkillName = CraftFrameTitleText:GetText();
|
|
craftSkillID = app.SpellNameToSpellID[craftSkillName] or 0;
|
|
if craftSkillID == 0 then
|
|
app.print("Could not find spellID for", craftSkillName, GetLocale(), "! Please report this to the ATT Discord!");
|
|
end
|
|
else
|
|
craftSkillID = 0;
|
|
end
|
|
end
|
|
|
|
if TradeSkillFrame and TradeSkillFrame:IsVisible() then
|
|
-- Trade Skills (Non-Enchanting)
|
|
local tradeSkillName, tradeSkillLevel, tradeSkillMaxLevel = GetTradeSkillLine();
|
|
if tradeSkillName and tradeSkillName ~= "UNKNOWN" then
|
|
local shouldShowSpellRanks = tradeSkillLevel and tradeSkillLevel ~= math.max(300, tradeSkillMaxLevel);
|
|
tradeSkillID = app.SpellNameToSpellID[tradeSkillName] or 0;
|
|
if tradeSkillID == 2656 then -- Smelting, point this to Mining.
|
|
tradeSkillID = 2575;
|
|
elseif tradeSkillID == 0 then
|
|
app.print("Could not find spellID for", tradeSkillName, GetLocale(), "! Please report this to the ATT Discord!");
|
|
end
|
|
else
|
|
tradeSkillID = 0;
|
|
end
|
|
end
|
|
|
|
if Skillet and Skillet.currentTrade and tradeSkillID == 0 and craftSkillID == 0 then
|
|
if Skillet.isCraft then
|
|
craftSkillID = Skillet.currentTrade;
|
|
else
|
|
tradeSkillID = Skillet.currentTrade;
|
|
end
|
|
end
|
|
|
|
if craftSkillID ~= 0 then
|
|
local spellName = GetSpellInfo(craftSkillID);
|
|
for _,spellID in pairs(app.SkillIDToSpellID) do
|
|
if GetSpellInfo(spellID) == spellName then
|
|
craftSkillID = spellID;
|
|
break;
|
|
end
|
|
end
|
|
|
|
local numberOfCrafts, spellID = GetNumCrafts();
|
|
for craftIndex = 1,numberOfCrafts do
|
|
spellID = 0;
|
|
local craftName, craftSubSpellName, craftType, numAvailable, isExpanded, trainingPointCost, requiredLevel = GetCraftInfo(craftIndex);
|
|
if craftType == "optimal" or craftType == "medium" or craftType == "easy" or craftType == "trivial" or craftType == "used" or craftType == "none" then
|
|
spellID = craftSubSpellName and (select(7, GetSpellInfo(craftName, craftSubSpellName)) or app.SpellNameToSpellID[craftName .. " (" .. craftSubSpellName .. ")"]) or app.SpellNameToSpellID[craftName];
|
|
if spellID then
|
|
if spellID == 44153 then spellID = 44155; -- Fix the Flying Machine spellID.
|
|
elseif spellID == 44151 then spellID = 44157; -- Fix the Turbo Flying Machine spellID.
|
|
elseif spellID == 20583 then spellID = 24492; end -- Fix rank 1 Nature Resistance.
|
|
app.CurrentCharacter.SpellRanks[spellID] = shouldShowSpellRanks and app.CraftTypeToCraftTypeID(craftType) or nil;
|
|
if not app.CurrentCharacter.Spells[spellID] then
|
|
app.SetCollectedForSubType(nil, "Spells", "Recipes", spellID, true);
|
|
learned = learned + 1;
|
|
end
|
|
if not skillCache[spellID] then
|
|
app.print("Missing " .. craftName .. " (Spell ID #" .. spellID .. ") in ATT Database. Please report it!");
|
|
skillCache[spellID] = { {} };
|
|
end
|
|
else
|
|
app.print("Missing " .. craftName .. " spellID in ATT Database. Please report it!");
|
|
end
|
|
|
|
if craftType ~= "none" then
|
|
-- Attempt to harvest the item associated with this craft.
|
|
GameTooltip.SetCraftSpell(ATTCNPCHarvester, craftIndex);
|
|
local link, craftedItemID = select(2, ATTCNPCHarvester:GetItem());
|
|
if link then craftedItemID = GetItemInfoInstant(link); end
|
|
|
|
-- Cache the Reagents used to make this item.
|
|
for i=1,GetCraftNumReagents(craftIndex) do
|
|
local itemID = GetItemInfoInstant(GetCraftReagentItemLink(craftIndex, i));
|
|
if itemID then
|
|
-- Make sure a cache table exists for this item.
|
|
local _, _, reagentCount = GetCraftReagentInfo(craftIndex, i);
|
|
if not reagentCache[itemID] then reagentCache[itemID] = { {}, {} }; end
|
|
|
|
-- Index 1: The Recipe Skill IDs
|
|
if spellID then reagentCache[itemID][1][spellID] = reagentCount; end
|
|
|
|
-- Index 2: The Crafted Item IDs
|
|
if craftedItemID then reagentCache[itemID][2][craftedItemID] = reagentCount; end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if tradeSkillID ~= 0 then
|
|
local spellName = GetSpellInfo(tradeSkillID);
|
|
for _,spellID in pairs(app.SkillIDToSpellID) do
|
|
if GetSpellInfo(spellID) == spellName then
|
|
tradeSkillID = spellID;
|
|
break;
|
|
end
|
|
end
|
|
local numTradeSkills = GetNumTradeSkills();
|
|
for skillIndex = 1,numTradeSkills do
|
|
local skillName, skillType, numAvailable, isExpanded = GetTradeSkillInfo(skillIndex);
|
|
if skillType == "optimal" or skillType == "medium" or skillType == "easy" or skillType == "trivial" or skillType == "used" or skillType == "none" then
|
|
local spellID = app.SpellNameToSpellID[skillName];
|
|
if spellID then
|
|
if spellID == 44153 then spellID = 44155; -- Fix the Flying Machine spellID.
|
|
elseif spellID == 44151 then spellID = 44157; -- Fix the Turbo Flying Machine spellID.
|
|
elseif spellID == 61451 then spellID = 60969; -- Fix the Flying Carpet spellID.
|
|
elseif spellID == 61309 then spellID = 60971; -- Fix the Magnificent Flying Carpet spellID.
|
|
elseif spellID == 75596 then spellID = 75597; -- Fix the Frosty Flying Carpet spellID.
|
|
elseif spellID == 20583 then spellID = 24492; end -- Fix rank 1 Nature Resistance.
|
|
app.CurrentCharacter.SpellRanks[spellID] = shouldShowSpellRanks and app.CraftTypeToCraftTypeID(skillType) or nil;
|
|
if not app.CurrentCharacter.Spells[spellID] then
|
|
app.SetCollectedForSubType(nil, "Spells", "Recipes", spellID, true);
|
|
learned = learned + 1;
|
|
end
|
|
|
|
if not skillCache[spellID] then
|
|
app.print("Missing " .. (skillName or "[??]") .. " (Spell ID #" .. spellID .. ") in ATT Database. Please report it!");
|
|
skillCache[spellID] = { {} };
|
|
end
|
|
else
|
|
app.print("Missing " .. (skillName or "[??]") .. " spellID in ATT Database. Please report it!");
|
|
end
|
|
|
|
-- Cache the Reagents used to make this item.
|
|
local tradeSkillItemLink = GetTradeSkillItemLink(skillIndex);
|
|
if tradeSkillItemLink then
|
|
local craftedItemID = GetItemInfoInstant(tradeSkillItemLink);
|
|
for i=1,GetTradeSkillNumReagents(skillIndex) do
|
|
local reagentCount = select(3, GetTradeSkillReagentInfo(skillIndex, i));
|
|
local itemID = GetItemInfoInstant(GetTradeSkillReagentItemLink(skillIndex, i));
|
|
|
|
-- Make sure a cache table exists for this item.
|
|
-- Index 1: The Recipe Skill IDs
|
|
-- Index 2: The Crafted Item IDs
|
|
if not reagentCache[itemID] then reagentCache[itemID] = { {}, {} }; end
|
|
if spellID then reagentCache[itemID][1][spellID] = reagentCount; end
|
|
if craftedItemID then reagentCache[itemID][2][craftedItemID] = reagentCount; end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Open the Tradeskill list for this Profession
|
|
if app.Categories.Professions and (craftSkillID ~= 0 or tradeSkillID ~= 0) and (craftSkillID ~= self.previousCraftSkillID or tradeSkillID ~= self.previousTradeSkillID) then
|
|
self.previousCraftSkillID = craftSkillID;
|
|
self.previousTradeSkillID = tradeSkillID;
|
|
local g = {};
|
|
for i,group in ipairs(app.Categories.Professions) do
|
|
if group.spellID == craftSkillID or group.spellID == tradeSkillID then
|
|
local cache = self.cache[group.spellID];
|
|
if not cache then
|
|
cache = CloneReference(group);
|
|
self.cache[group.spellID] = cache;
|
|
local searchResults = ResolveSymbolicLink(group);
|
|
if searchResults and #searchResults then
|
|
for j,o in ipairs(searchResults) do
|
|
tinsert(cache.g, o);
|
|
end
|
|
end
|
|
end
|
|
tinsert(g, cache);
|
|
end
|
|
end
|
|
if #g > 0 then
|
|
if #g == 1 then
|
|
self.data = g[1];
|
|
else
|
|
self.data = self.header;
|
|
self.data.g = g;
|
|
for i,entry in ipairs(g) do
|
|
entry.indent = nil;
|
|
end
|
|
end
|
|
self.data.indent = 0;
|
|
self.data.visible = true;
|
|
if not self.data.expanded then
|
|
self.data.expanded = true;
|
|
ExpandGroupsRecursively(self.data, true);
|
|
end
|
|
self:Rebuild();
|
|
end
|
|
end
|
|
|
|
-- If something new was "learned", then refresh the data.
|
|
if learned > 0 then
|
|
app.print("Cached " .. learned .. " known recipes!");
|
|
app:RefreshDataQuietly("TradeSkills::CacheRecipes", true);
|
|
end
|
|
end
|
|
end
|
|
self.RefreshRecipes = function(self)
|
|
if app.Settings.Collectibles.Recipes then
|
|
self.wait = 5;
|
|
app:StartATTCoroutine("RefreshingRecipes", function()
|
|
while self.wait > 0 do
|
|
self.wait = self.wait - 1;
|
|
coroutine.yield();
|
|
end
|
|
while not self:IsVisible() do
|
|
coroutine.yield();
|
|
end
|
|
|
|
wipe(searchCache);
|
|
self:CacheRecipes();
|
|
end);
|
|
end
|
|
end
|
|
|
|
-- Skillet support.
|
|
self.SkilletSupported = nil;
|
|
|
|
-- TSM Shenanigans
|
|
self.TSMCraftingVisible = nil;
|
|
self.SetTSMCraftingVisible = function(self, visible)
|
|
visible = not not visible;
|
|
if visible == self.TSMCraftingVisible then
|
|
return;
|
|
end
|
|
self.TSMCraftingVisible = visible;
|
|
self:UpdateFrameVisibility();
|
|
app:StartATTCoroutine("UpdateTradeSkills", function()
|
|
while InCombatLockdown() do coroutine.yield(); end
|
|
coroutine.yield();
|
|
self:Update();
|
|
end);
|
|
end
|
|
self.UpdateDefaultFrameVisibility = function(self)
|
|
-- Skillet compatibility
|
|
if SkilletFrame then
|
|
if not self.SkilletSupported then
|
|
Skillet["ATTC"] = { ["Update"] = function() self:Update(); end };
|
|
Skillet:RegisterUpdatePlugin("ATTC");
|
|
self.SkilletSupported = true;
|
|
end
|
|
self:SetParent(SkilletFrame);
|
|
self:SetPoint("TOPLEFT", SkilletFrame, "TOPRIGHT", 0, 0);
|
|
self:SetPoint("BOTTOMLEFT", SkilletFrame, "BOTTOMRIGHT", 0, 0);
|
|
self:SetMovable(false);
|
|
return true;
|
|
elseif CraftFrame and CraftFrame:IsVisible() then
|
|
-- Default Alignment on the Craft UI.
|
|
self:ClearAllPoints();
|
|
self:SetPoint("TOPLEFT", CraftFrame, "TOPRIGHT", -37, -11);
|
|
self:SetPoint("BOTTOMLEFT", CraftFrame, "BOTTOMRIGHT", -37, 72);
|
|
self:SetMovable(false);
|
|
return true;
|
|
elseif TradeSkillFrame and TradeSkillFrame:IsVisible() then
|
|
-- Default Alignment on the TradeSkill UI.
|
|
self:ClearAllPoints();
|
|
self:SetPoint("TOPLEFT", TradeSkillFrame, "TOPRIGHT", -37, -11);
|
|
self:SetPoint("BOTTOMLEFT", TradeSkillFrame, "BOTTOMRIGHT", -37, 72);
|
|
self:SetMovable(false);
|
|
return true;
|
|
end
|
|
end
|
|
self.UpdateFrameVisibility = function(self)
|
|
self:SetMovable(true);
|
|
self:ClearAllPoints();
|
|
if self.TSMCraftingVisible and self.cachedTSMFrame then
|
|
if self.cachedTSMFrame.queue and self.cachedTSMFrame.queue:IsShown() then
|
|
self:SetPoint("TOPLEFT", self.cachedTSMFrame.queue, "TOPRIGHT", 0, 0);
|
|
self:SetPoint("BOTTOMLEFT", self.cachedTSMFrame.queue, "BOTTOMRIGHT", 0, 0);
|
|
else
|
|
self:SetPoint("TOPLEFT", self.cachedTSMFrame, "TOPRIGHT", 0, 0);
|
|
self:SetPoint("BOTTOMLEFT", self.cachedTSMFrame, "BOTTOMRIGHT", 0, 0);
|
|
end
|
|
self:SetMovable(false);
|
|
return true;
|
|
elseif self:UpdateDefaultFrameVisibility() then
|
|
return true;
|
|
else
|
|
self:SetMovable(false);
|
|
app:StartATTCoroutine("TSMWHY", function()
|
|
while InCombatLockdown() or not TradeSkillFrame do coroutine.yield(); end
|
|
app:StartATTCoroutine("TSMWHYPT2", function()
|
|
local thing = self.TSMCraftingVisible;
|
|
self.TSMCraftingVisible = nil;
|
|
self:SetTSMCraftingVisible(thing);
|
|
end);
|
|
end);
|
|
end
|
|
end
|
|
|
|
-- Setup Event Handlers and register for events
|
|
local updateTradeSkill = function()
|
|
-- If it's not yours, don't take credit for it.
|
|
if IsTradeSkillLinked and IsTradeSkillLinked() then
|
|
return;
|
|
end
|
|
if C_TradeSkillUI then
|
|
if (C_TradeSkillUI.IsTradeSkillLinked and C_TradeSkillUI.IsTradeSkillLinked())
|
|
or (C_TradeSkillUI.IsTradeSkillGuild and C_TradeSkillUI.IsTradeSkillGuild()) then
|
|
return;
|
|
end
|
|
end
|
|
if self.TSMCraftingVisible == nil then
|
|
self:SetTSMCraftingVisible(false);
|
|
end
|
|
self:UpdateFrameVisibility();
|
|
if app.Settings:GetTooltipSetting("Auto:ProfessionList") then
|
|
self:SetVisible(true);
|
|
end
|
|
RefreshSkills();
|
|
self:RefreshRecipes();
|
|
end
|
|
handlers.CRAFT_SHOW = updateTradeSkill;
|
|
handlers.TRADE_SKILL_SHOW = updateTradeSkill;
|
|
pcall(self.RegisterEvent, self, "CRAFT_SHOW");
|
|
self:RegisterEvent("TRADE_SKILL_SHOW");
|
|
|
|
local tradeSkillClose = function()
|
|
app:StartATTCoroutine("TSMWHY3", function()
|
|
self:RefreshRecipes();
|
|
if not self:UpdateFrameVisibility() then
|
|
self:SetVisible(false);
|
|
end
|
|
end);
|
|
end
|
|
handlers.CRAFT_CLOSE = tradeSkillClose;
|
|
handlers.TRADE_SKILL_CLOSE = tradeSkillClose;
|
|
pcall(self.RegisterEvent, self, "CRAFT_CLOSE");
|
|
self:RegisterEvent("TRADE_SKILL_CLOSE");
|
|
|
|
local newSpellLearned = function(self, spellID)
|
|
if spellID then
|
|
if not app.CurrentCharacter.Spells[spellID] then
|
|
local searchResults, spell = SearchForField("spellID", spellID);
|
|
if #searchResults > 0 then
|
|
spell = searchResults[1];
|
|
for i=2,#searchResults,1 do
|
|
local searchResult = searchResults[i];
|
|
if not searchResult.itemID then
|
|
spell = searchResult;
|
|
end
|
|
end
|
|
else
|
|
spell = app.CreateSpell(spellID);
|
|
end
|
|
if spell.f == app.FilterConstants.RECIPES then
|
|
app.SetCollectedForSubType(spell, "Spells", "Recipes", spellID, true);
|
|
else
|
|
app.SetCollected(spell, "Spells", spellID, true);
|
|
end
|
|
app:RefreshDataQuietly("NEW_SPELL_LEARNED", true);
|
|
else
|
|
self:RefreshRecipes();
|
|
end
|
|
end
|
|
end
|
|
handlers.NEW_RECIPE_LEARNED = newSpellLearned;
|
|
handlers.LEARNED_SPELL_IN_TAB = newSpellLearned;
|
|
self:RegisterEvent("LEARNED_SPELL_IN_TAB");
|
|
self:RegisterEvent("NEW_RECIPE_LEARNED");
|
|
|
|
-- Default Update refreshes
|
|
pcall(self.RegisterEvent, self, "CRAFT_UPDATE");
|
|
self:RegisterEvent("TRADE_SKILL_LIST_UPDATE");
|
|
self:RegisterEvent("SKILL_LINES_CHANGED");
|
|
end,
|
|
OnUpdate = function(self, ...)
|
|
if TSM_API and TSMAPI_FOUR then
|
|
if not self.cachedTSMFrame then
|
|
for i,f in ipairs({UIParent:GetChildren()}) do
|
|
if f.headerBgCenter then
|
|
self.cachedTSMFrame = f;
|
|
local oldSetVisible = f.SetVisible;
|
|
local oldShow = f.Show;
|
|
local oldHide = f.Hide;
|
|
f.SetVisible = function(s, visible)
|
|
oldSetVisible(s, visible);
|
|
self:SetTSMCraftingVisible(visible);
|
|
end
|
|
f.Hide = function(s)
|
|
oldHide(s);
|
|
self:SetTSMCraftingVisible(false);
|
|
end
|
|
f.Show = function(s)
|
|
oldShow(s);
|
|
self:SetTSMCraftingVisible(true);
|
|
end
|
|
if self.gettinMadAtDumbNamingConventions then
|
|
TSMAPI_FOUR.UI.NewElement = self.OldNewElement;
|
|
self.gettinMadAtDumbNamingConventions = nil;
|
|
self.OldNewElement = nil;
|
|
end
|
|
self:SetTSMCraftingVisible(f:IsShown());
|
|
return;
|
|
end
|
|
end
|
|
if not self.gettinMadAtDumbNamingConventions then
|
|
self.gettinMadAtDumbNamingConventions = true;
|
|
self.OldNewElement = TSMAPI_FOUR.UI.NewElement;
|
|
TSMAPI_FOUR.UI.NewElement = function(...)
|
|
app:StartATTCoroutine("UpdateTradeSkills", function()
|
|
while InCombatLockdown() do coroutine.yield(); end
|
|
coroutine.yield();
|
|
self:Update();
|
|
end);
|
|
return self.OldNewElement(...);
|
|
end
|
|
end
|
|
end
|
|
elseif TSMCraftingTradeSkillFrame then
|
|
if not self.cachedTSMFrame then
|
|
local f = TSMCraftingTradeSkillFrame;
|
|
self.cachedTSMFrame = f;
|
|
local oldSetVisible = f.SetVisible;
|
|
local oldShow = f.Show;
|
|
local oldHide = f.Hide;
|
|
f.SetVisible = function(s, visible)
|
|
oldSetVisible(s, visible);
|
|
self:SetTSMCraftingVisible(visible);
|
|
end
|
|
f.Hide = function(s)
|
|
oldHide(s);
|
|
self:SetTSMCraftingVisible(false);
|
|
end
|
|
f.Show = function(s)
|
|
oldShow(s);
|
|
self:SetTSMCraftingVisible(true);
|
|
end
|
|
if f.queueBtn then
|
|
local setScript = f.queueBtn.SetScript;
|
|
f.queueBtn.SetScript = function(s, e, callback)
|
|
if e == "OnClick" then
|
|
setScript(s, e, function(...)
|
|
if callback then callback(...); end
|
|
|
|
local thing = self.TSMCraftingVisible;
|
|
self.TSMCraftingVisible = nil;
|
|
self:SetTSMCraftingVisible(thing);
|
|
end);
|
|
else
|
|
setScript(s, e, callback);
|
|
end
|
|
end
|
|
f.queueBtn:SetScript("OnClick", f.queueBtn:GetScript("OnClick"));
|
|
end
|
|
self:SetTSMCraftingVisible(f:IsShown());
|
|
return;
|
|
end
|
|
end
|
|
UpdateWindow(self, ...);
|
|
end,
|
|
});
|
|
|
|
|
|
-- Addon Message Handling
|
|
app:RegisterEvent("CHAT_MSG_ADDON");
|
|
app.events.CHAT_MSG_ADDON = function(prefix, text, channel, sender, target, zoneChannelID, localID, name, instanceID, ...)
|
|
if prefix == "ATTC" then
|
|
--print(prefix, text, channel, sender, target, zoneChannelID, localID, name, instanceID, ...)
|
|
local args = { strsplit("\t", text) };
|
|
local cmd = args[1];
|
|
if cmd then
|
|
local a = args[2];
|
|
if cmd == "?" then -- Query Request
|
|
local response;
|
|
if a then
|
|
if a == "a" then
|
|
response = a;
|
|
for i=3,#args,1 do
|
|
local b = tonumber(args[i]);
|
|
response = response .. "\t" .. b .. "\t" .. (app.CurrentCharacter.Achievements[b] and 1 or 0);
|
|
end
|
|
elseif a == "e" then
|
|
response = a;
|
|
for i=3,#args,1 do
|
|
local b = tonumber(args[i]);
|
|
response = response .. "\t" .. b .. "\t" .. (app.CurrentCharacter.Exploration[b] and 1 or 0);
|
|
end
|
|
elseif a == "f" then
|
|
response = a;
|
|
for i=3,#args,1 do
|
|
local b = tonumber(args[i]);
|
|
response = response .. "\t" .. b .. "\t" .. (app.CurrentCharacter.Factions[b] and 1 or 0);
|
|
end
|
|
elseif a == "fp" then
|
|
response = a;
|
|
for i=3,#args,1 do
|
|
local b = tonumber(args[i]);
|
|
response = response .. "\t" .. b .. "\t" .. (app.CurrentCharacter.FlightPaths[b] and 1 or 0);
|
|
end
|
|
elseif a == "p" then
|
|
response = a;
|
|
for i=3,#args,1 do
|
|
local b = tonumber(args[i]);
|
|
response = response .. "\t" .. b .. "\t" .. (app.CurrentCharacter.BattlePets[b] and 1 or 0);
|
|
end
|
|
elseif a == "q" then
|
|
response = a;
|
|
for i=3,#args,1 do
|
|
local b = tonumber(args[i]);
|
|
response = response .. "\t" .. b .. "\t" .. (IsQuestFlaggedCompleted(b) and 1 or 0);
|
|
end
|
|
--[[
|
|
elseif a == "s" then
|
|
response = "s";
|
|
for i=3,#args,1 do
|
|
local b = tonumber(args[i]);
|
|
response = response .. "\t" .. b .. "\t" .. (ATTAccountWideData.Sources[b] or 0);
|
|
end
|
|
]]--
|
|
elseif a == "sp" then
|
|
response = a;
|
|
for i=3,#args,1 do
|
|
local b = tonumber(args[i]);
|
|
response = response .. "\t" .. b .. "\t" .. (app.CurrentCharacter.Spells[b] and 1 or 0);
|
|
end
|
|
elseif a == "t" then
|
|
response = a;
|
|
for i=3,#args,1 do
|
|
local b = tonumber(args[i]);
|
|
response = response .. "\t" .. b .. "\t" .. (app.CurrentCharacter.Titles[b] and 1 or 0);
|
|
end
|
|
elseif a == "toy" then
|
|
response = a;
|
|
for i=3,#args,1 do
|
|
local b = tonumber(args[i]);
|
|
response = response .. "\t" .. b .. "\t" .. (ATTAccountWideData.Toys[b] and 1 or 0);
|
|
end
|
|
end
|
|
else
|
|
local character = app.CurrentCharacter;
|
|
if character then
|
|
local data = character.PrimeData;
|
|
if data then
|
|
response = "ATTC\t" .. (data.progress or 0) .. "\t" .. (data.total or 0) .. "\t" .. data.modeString;
|
|
end
|
|
end
|
|
end
|
|
if response then SendResponseMessage("!\t" .. response, sender); end
|
|
elseif cmd == "!" then -- Query Response
|
|
if a == "ATTC" then
|
|
print(target .. ": " .. GetProgressColorText(tonumber(args[3]), tonumber(args[4])) .. " " .. args[5]);
|
|
end
|
|
elseif cmd == "to" then -- To Command
|
|
local myName = UnitName("player");
|
|
local name,server = strsplit("-", a);
|
|
if myName == name and (not server or GetRealmName() == server) then
|
|
app.events.CHAT_MSG_ADDON(prefix, strsub(text, 5 + strlen(a)), "WHISPER", sender);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
SLASH_ATTGUILD1 = "/attguild";
|
|
SlashCmdList["ATTGUILD"] = function(cmd)
|
|
C_ChatInfo.SendAddonMessage("ATTC", "?", "GUILD");
|
|
end
|
|
SLASH_ATTRAID1 = "/attraid";
|
|
SlashCmdList["ATTRAID"] = function(cmd)
|
|
C_ChatInfo.SendAddonMessage("ATTC", "?", "RAID");
|
|
end
|
|
SLASH_ATTYELL1 = "/attyell";
|
|
SLASH_ATTYELL2 = "/attrohduh";
|
|
SlashCmdList["ATTYELL"] = function(cmd)
|
|
C_ChatInfo.SendAddonMessage("ATTC", "?", "YELL");
|
|
end
|
|
SLASH_ATTWHO1 = "/attu";
|
|
SLASH_ATTWHO2 = "/attyou";
|
|
SLASH_ATTWHO3 = "/attwho";
|
|
SlashCmdList["ATTWHO"] = function(cmd)
|
|
local name,server = UnitName("target");
|
|
if name then
|
|
if UnitIsPlayer("target") then
|
|
SendResponseMessage("?", server and (name .. "-" .. server) or name);
|
|
else
|
|
local cmd = "creatureid:" .. select(6, strsplit("-", UnitGUID("target")));
|
|
local group = GetCachedSearchResults(cmd, SearchForLink, cmd);
|
|
if group then app:CreateMiniListForGroup(group); end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Game Events that trigger computation updates.
|
|
app.events.PLAYER_LEVEL_UP = function(newLevel)
|
|
app.Level = newLevel;
|
|
app:RefreshDataCompletely("PLAYER_LEVEL_UP");
|
|
end
|
|
|
|
-- Startup Event
|
|
app:RegisterEvent("ADDON_LOADED");
|
|
app:RegisterEvent("VARIABLES_LOADED");
|
|
app.events.ADDON_LOADED = function(addonName)
|
|
-- Only execute for this addon.
|
|
if addonName ~= appName then return; end
|
|
app:UnregisterEvent("ADDON_LOADED");
|
|
|
|
AllTheThingsAD = _G["AllTheThingsAD"]; -- For account-wide data.
|
|
if not AllTheThingsAD then
|
|
AllTheThingsAD = _G["ATTClassicAD"];
|
|
if AllTheThingsAD then
|
|
_G["ATTClassicAD"] = nil;
|
|
else
|
|
AllTheThingsAD = { };
|
|
end
|
|
_G["AllTheThingsAD"] = AllTheThingsAD;
|
|
end
|
|
app:UpdateWindowColors();
|
|
LibStub:GetLibrary("LibDataBroker-1.1"):NewDataObject(L["TITLE"], {
|
|
type = "launcher",
|
|
icon = app.asset("logo_32x32"),
|
|
OnClick = AllTheThings_MinimapButtonOnClick,
|
|
OnEnter = AllTheThings_MinimapButtonOnEnter,
|
|
OnLeave = AllTheThings_MinimapButtonOnLeave,
|
|
});
|
|
|
|
-- Cache the Localized Category Data
|
|
AllTheThingsAD.LocalizedCategoryNames = setmetatable(AllTheThingsAD.LocalizedCategoryNames or {}, { __index = app.CategoryNames });
|
|
app.CategoryNames = nil;
|
|
|
|
-- Cache the Localized Flight Path Data
|
|
AllTheThingsAD.LocalizedFlightPathNames = setmetatable(AllTheThingsAD.LocalizedFlightPathNames or {}, { __index = app.FlightPathNames });
|
|
app.FlightPathDB = 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.GarrisonBuildings then currentCharacter.GarrisonBuildings = {}; end
|
|
if not currentCharacter.Lockouts then currentCharacter.Lockouts = {}; end
|
|
if not currentCharacter.Quests then currentCharacter.Quests = {}; end
|
|
if not currentCharacter.RWP then currentCharacter.RWP = {}; 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.Toys then currentCharacter.Toys = {}; end
|
|
|
|
-- Update timestamps.
|
|
local now = time();
|
|
local timeStamps = currentCharacter.TimeStamps;
|
|
if not timeStamps then
|
|
timeStamps = {};
|
|
currentCharacter.TimeStamps = timeStamps;
|
|
end
|
|
for key,value in pairs(currentCharacter) do
|
|
if type(value) == "table" and not timeStamps[key] then
|
|
timeStamps[key] = now;
|
|
end
|
|
end
|
|
currentCharacter.lastPlayed = now;
|
|
app.CurrentCharacter = currentCharacter;
|
|
|
|
-- Convert over the deprecated Characters table.
|
|
local characters = GetDataMember("Characters");
|
|
if characters then
|
|
for guid,text in pairs(characters) do
|
|
if not characterData[guid] then
|
|
characterData[guid] = { ["text"] = text };
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Convert over the deprecated DeathsPerCharacter table.
|
|
local deathsPerCharacter = GetDataMember("DeathsPerCharacter");
|
|
if deathsPerCharacter then
|
|
for guid,deaths in pairs(deathsPerCharacter) do
|
|
local character = characterData[guid];
|
|
if not character then
|
|
character = { ["guid"] = guid };
|
|
characterData[guid] = character;
|
|
end
|
|
character.Deaths = deaths;
|
|
end
|
|
end
|
|
|
|
-- Convert over the deprecated ActiveSkillsPerCharacter table.
|
|
local activeSkillsPerCharacter = GetDataMember("ActiveSkillsPerCharacter");
|
|
if activeSkillsPerCharacter then
|
|
for guid,skills in pairs(activeSkillsPerCharacter) do
|
|
local character = characterData[guid];
|
|
if not character then
|
|
character = { ["guid"] = guid };
|
|
characterData[guid] = character;
|
|
end
|
|
character.ActiveSkills = skills;
|
|
end
|
|
end
|
|
|
|
-- Convert over the deprecated CollectedFlightPathsPerCharacter table.
|
|
local collectedFlightPathsPerCharacter = GetDataMember("CollectedFlightPathsPerCharacter");
|
|
if collectedFlightPathsPerCharacter then
|
|
for guid,flightPaths in pairs(collectedFlightPathsPerCharacter) do
|
|
local character = characterData[guid];
|
|
if not character then
|
|
character = { ["guid"] = guid };
|
|
characterData[guid] = character;
|
|
end
|
|
character.FlightPaths = flightPaths;
|
|
end
|
|
end
|
|
|
|
-- Convert over the deprecated CollectedFactionsPerCharacter table.
|
|
local collectedFactionsPerCharacter = GetDataMember("CollectedFactionsPerCharacter");
|
|
if collectedFactionsPerCharacter then
|
|
for guid,factions in pairs(collectedFactionsPerCharacter) do
|
|
local character = characterData[guid];
|
|
if not character then
|
|
character = { ["guid"] = guid };
|
|
characterData[guid] = character;
|
|
end
|
|
character.Factions = factions;
|
|
end
|
|
end
|
|
|
|
-- Convert over the deprecated lockouts table.
|
|
local lockouts = GetDataMember("lockouts");
|
|
if lockouts then
|
|
for guid,locks in pairs(lockouts) do
|
|
local character = characterData[guid];
|
|
if not character then
|
|
character = { ["guid"] = guid };
|
|
characterData[guid] = character;
|
|
end
|
|
character.Lockouts = locks;
|
|
end
|
|
end
|
|
|
|
-- Convert over the deprecated CollectedQuestsPerCharacter table.
|
|
local collectedQuestsPerCharacter = GetDataMember("CollectedQuestsPerCharacter");
|
|
if collectedQuestsPerCharacter then
|
|
for guid,quests in pairs(collectedQuestsPerCharacter) do
|
|
local character = characterData[guid];
|
|
if not character then
|
|
character = { ["guid"] = guid };
|
|
characterData[guid] = character;
|
|
end
|
|
character.Quests = quests;
|
|
end
|
|
end
|
|
|
|
-- Convert over the deprecated CollectedSpellsPerCharacter table.
|
|
local collectedSpellsPerCharacter = GetDataMember("CollectedSpellsPerCharacter");
|
|
if collectedSpellsPerCharacter then
|
|
for guid,spells in pairs(collectedSpellsPerCharacter) do
|
|
local character = characterData[guid];
|
|
if not character then
|
|
character = { ["guid"] = guid };
|
|
characterData[guid] = character;
|
|
end
|
|
character.Spells = spells;
|
|
end
|
|
end
|
|
|
|
-- Convert over the deprecated SpellRanksPerCharacter table.
|
|
local spellRanksPerCharacter = GetDataMember("SpellRanksPerCharacter");
|
|
if spellRanksPerCharacter then
|
|
for guid,ranks in pairs(spellRanksPerCharacter) do
|
|
local character = characterData[guid];
|
|
if not character then
|
|
character = { ["guid"] = guid };
|
|
characterData[guid] = character;
|
|
end
|
|
character.SpellRanks = ranks;
|
|
end
|
|
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.GarrisonBuildings then accountWideData.GarrisonBuildings = {}; end
|
|
if not accountWideData.Illusions then accountWideData.Illusions = {}; end
|
|
if not accountWideData.Quests then accountWideData.Quests = {}; end
|
|
if not accountWideData.RWP then accountWideData.RWP = {}; end
|
|
if not accountWideData.Spells then accountWideData.Spells = {}; end
|
|
if not accountWideData.Titles then accountWideData.Titles = {}; end
|
|
if not accountWideData.Toys then accountWideData.Toys = {}; end
|
|
|
|
-- Account Wide Settings
|
|
local accountWideSettings = app.Settings.AccountWide;
|
|
local function SetAccountCollected(t, field, id, collected)
|
|
local container = accountWideData[field];
|
|
local oldstate = container[id];
|
|
if collected then
|
|
if not oldstate then
|
|
local now = time();
|
|
timeStamps[field] = now;
|
|
currentCharacter.lastPlayed = now;
|
|
AddToCollection(t);
|
|
container[id] = 1;
|
|
end
|
|
return 1;
|
|
elseif oldstate then
|
|
local now = time();
|
|
timeStamps[field] = now;
|
|
currentCharacter.lastPlayed = now;
|
|
RemoveFromCollection(t);
|
|
container[id] = nil;
|
|
end
|
|
end
|
|
local function SetAccountCollectedForSubType(t, field, subtype, id, collected)
|
|
local container = accountWideData[field];
|
|
local oldstate = container[id];
|
|
if collected then
|
|
if not oldstate then
|
|
local now = time();
|
|
timeStamps[field] = now;
|
|
currentCharacter.lastPlayed = now;
|
|
AddToCollection(t);
|
|
container[id] = 1;
|
|
end
|
|
return 1;
|
|
elseif oldstate then
|
|
local now = time();
|
|
timeStamps[field] = now;
|
|
currentCharacter.lastPlayed = now;
|
|
RemoveFromCollection(t);
|
|
container[id] = nil;
|
|
end
|
|
end
|
|
local function SetCollected(t, field, id, collected)
|
|
local container = currentCharacter[field];
|
|
local oldstate = container[id];
|
|
if collected then
|
|
if not oldstate then
|
|
if t and not (accountWideSettings[field] and accountWideData[field][id]) then
|
|
--print("SetCollected", field, id, accountWideSettings[field], accountWideData[field][id]);
|
|
AddToCollection(t);
|
|
end
|
|
container[id] = 1;
|
|
accountWideData[field][id] = 1;
|
|
local now = time();
|
|
timeStamps[field] = now;
|
|
currentCharacter.lastPlayed = now;
|
|
end
|
|
return 1;
|
|
elseif oldstate then
|
|
container[id] = nil;
|
|
local now = time();
|
|
timeStamps[field] = now;
|
|
currentCharacter.lastPlayed = now;
|
|
for guid,other in pairs(characterData) do
|
|
local otherContainer = other[field];
|
|
if otherContainer and otherContainer[id] then
|
|
accountWideData[field][id] = 1;
|
|
return accountWideSettings[field] and 2;
|
|
end
|
|
end
|
|
if accountWideData[field][id] then
|
|
RemoveFromCollection(t);
|
|
accountWideData[field][id] = nil;
|
|
end
|
|
elseif accountWideSettings[field] and accountWideData[field][id] then
|
|
return 2;
|
|
end
|
|
end
|
|
local function SetCollectedForSubType(t, field, subtype, id, collected)
|
|
local container = currentCharacter[field];
|
|
local oldstate = container[id];
|
|
if collected then
|
|
if not oldstate then
|
|
if t and not (accountWideSettings[subtype] and accountWideData[field][id]) then
|
|
--print("SetCollectedForSubType", field, subtype, id, accountWideSettings[subtype], accountWideData[field][id]);
|
|
AddToCollection(t);
|
|
end
|
|
container[id] = 1;
|
|
accountWideData[field][id] = 1;
|
|
local now = time();
|
|
timeStamps[field] = now;
|
|
currentCharacter.lastPlayed = now;
|
|
end
|
|
return 1;
|
|
elseif oldstate then
|
|
container[id] = nil;
|
|
local now = time();
|
|
timeStamps[field] = now;
|
|
currentCharacter.lastPlayed = now;
|
|
for guid,other in pairs(characterData) do
|
|
local otherContainer = other[field];
|
|
if otherContainer and otherContainer[id] then
|
|
accountWideData[field][id] = 1;
|
|
return accountWideSettings[subtype] and 2;
|
|
end
|
|
end
|
|
if accountWideData[field][id] then
|
|
RemoveFromCollection(t);
|
|
accountWideData[field][id] = nil;
|
|
end
|
|
elseif accountWideSettings[subtype] and accountWideData[field][id] then
|
|
return 2;
|
|
end
|
|
end
|
|
app.SetAccountCollected = SetAccountCollected;
|
|
app.SetAccountCollectedForSubType = SetAccountCollectedForSubType;
|
|
app.SetCollected = SetCollected;
|
|
app.SetCollectedForSubType = SetCollectedForSubType;
|
|
|
|
-- Convert over the deprecated account wide tables.
|
|
local data = GetDataMember("Deaths");
|
|
if data then accountWideData.Deaths = data; end
|
|
data = GetDataMember("CollectedFactions");
|
|
if data then accountWideData.Factions = data; end
|
|
data = GetDataMember("CollectedFlightPaths");
|
|
if data then accountWideData.FlightPaths = data; end
|
|
data = GetDataMember("CollectedQuests");
|
|
if data then accountWideData.Quests = data; end
|
|
data = GetDataMember("CollectedSpells");
|
|
if data then accountWideData.Spells = data; end
|
|
|
|
|
|
-- Check to see if we have a leftover ItemDB cache
|
|
GetDataMember("GroupQuestsByGUID", {});
|
|
GetDataMember("ValidSuffixesPerItemID", {});
|
|
|
|
-- Clean up settings
|
|
local oldsettings = {};
|
|
for i,key in ipairs({
|
|
"GroupQuestsByGUID",
|
|
"LocalizedCategoryNames",
|
|
"LocalizedFlightPathNames",
|
|
"Position",
|
|
"Reagents",
|
|
"SoftReserves",
|
|
"SoftReservePersistence",
|
|
"ValidSuffixesPerItemID",
|
|
}) do
|
|
oldsettings[key] = AllTheThingsAD[key];
|
|
end
|
|
wipe(AllTheThingsAD);
|
|
for key,value in pairs(oldsettings) do
|
|
AllTheThingsAD[key] = value;
|
|
end
|
|
|
|
-- Wipe the Debugger Data
|
|
ATTClassicDebugData = nil;
|
|
|
|
-- Tooltip Settings
|
|
app.Settings:Initialize();
|
|
end
|
|
app.events.VARIABLES_LOADED = function()
|
|
app:StartATTCoroutine("Startup", function()
|
|
coroutine.yield();
|
|
|
|
-- Prepare the Sound Pack!
|
|
app.Audio:ReloadSoundPack();
|
|
|
|
-- Cache some things
|
|
app.CurrentMapID = app.GetCurrentMapID();
|
|
|
|
-- Mark all previously completed quests.
|
|
if C_QuestLog_GetAllCompletedQuestIDs then
|
|
local completedQuests = C_QuestLog_GetAllCompletedQuestIDs();
|
|
if completedQuests and #completedQuests > 0 then
|
|
for i,questID in ipairs(completedQuests) do
|
|
CompletedQuests[questID] = true;
|
|
end
|
|
end
|
|
else
|
|
GetQuestsCompleted(CompletedQuests);
|
|
end
|
|
wipe(DirtyQuests);
|
|
app.events.UPDATE_INSTANCE_INFO();
|
|
C_ChatInfo.RegisterAddonMessagePrefix("ATTC");
|
|
|
|
-- Setup the Saved Variables if they aren't already.
|
|
local savedVariables = AllTheThingsSavedVariables;
|
|
if not AllTheThingsSavedVariables then
|
|
savedVariables = {};
|
|
AllTheThingsSavedVariables = savedVariables;
|
|
end
|
|
local windowSettings = savedVariables.Windows;
|
|
if not windowSettings then
|
|
windowSettings = {};
|
|
savedVariables.Windows = windowSettings;
|
|
end
|
|
LoadSettingsForWindows(windowSettings);
|
|
|
|
-- Now that all the windows are loaded, cache flight paths!
|
|
app.CacheFlightPathData();
|
|
|
|
-- Execute the OnReady handlers.
|
|
for i,handler in ipairs(app.EventHandlers.OnReady) do
|
|
handler();
|
|
end
|
|
|
|
-- Mark that we're ready now!
|
|
app.IsReady = true;
|
|
end);
|
|
end
|