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.

3854 lines
131 KiB

local BtWQuests = BtWQuests;
local L = BtWQuests.L;
local OUTDATED_LEVEL = 110;
local INTERFACE_NUMBER = select(4, GetBuildInfo())
local wipe = table.wipe
local format = string.format
local lower = string.lower
local gsub = string.gsub
local gmatch = string.gmatch
local concat = string.concat
local LE_EXPANSION_LEVEL_CURRENT = LE_EXPANSION_LEVEL_CURRENT or 0;
--@REMOVE AFTER 9.0
local GetLogIndexForQuestID = C_QuestLog.GetLogIndexForQuestID
local GetQuestWatchType = C_QuestLog.GetQuestWatchType
local AddQuestWatch = C_QuestLog.AddQuestWatch
local RemoveQuestWatch = C_QuestLog.RemoveQuestWatch
if INTERFACE_NUMBER < 90000 then
GetLogIndexForQuestID = GetQuestLogIndexByID
function GetQuestWatchType(questID)
return IsQuestWatched(GetLogIndexForQuestID(questID)) and 0 or nil
end
AddQuestWatch = AddQuestWatchForQuestID
RemoveQuestWatch = RemoveQuestWatchForQuestID
end
-- [[ Helper functions ]]
function BtWQuestsItem_GetItems(item, character)
if item.items ~= nil then
return item.items
end
if item.type == "chain" then
return BtWQuests.Database.Chains[item.id].items
elseif item.type == "category" then
return BtWQuests.Database.Categories[item.id].items
elseif item.type == "expansion" then
return BtWQuests.Database.Expansions[item.id].items
end
return nil
end
function BtWQuestsItem_Active(item, character)
if item.type == "quest" then
local ids = item.ids or {item.id}
for _,id in ipairs(ids) do
if character:IsQuestActive(id) then
return true
end
end
elseif item.type == "time" then
return GetServerTime() < item.time
end
return false
end
function BtWQuestsItem_ActiveOrCompleted(item, character)
if item.type == "quest" then
local ids = item.ids or {item.id}
for _,id in ipairs(ids) do
if character:IsQuestActive(id) or character:IsQuestCompleted(id) then
return true
end
end
end
return false
end
--[[
relationship = {
breadcrumb = 52065,
blockers = {51711, 51752},
},
]]
function BtWQuestsItem_RelationshipBlockersVisible(item, character)
local blockers = item.relationship.blockers or {item.relationship.blocker}
for _,questID in ipairs(blockers) do
if character:IsQuestActive(questID) or character:IsQuestCompleted(questID) then
return false
end
end
return true
end
function BtWQuestsItem_RelationshipSourceVisible(item, character)
if character:IsQuestCompleted(item.relationship.breadcrumb) then
return true
end
return BtWQuestsItem_RelationshipBlockersVisible(item, character)
end
function BtWQuestsItem_RelationshipSourceActive(item, character)
return character:IsQuestActive(item.relationship.breadcrumb) or character:IsQuestCompleted(item.relationship.breadcrumb)
end
local function GetVariation(database, item, character)
if item.variations == nil then
return item;
end
if character ~= nil then
for _,variation in ipairs(item.variations) do
if not variation.initialized then
variation.initialized = true;
for k,v in pairs(item) do
if k ~= "variations" and variation[k] == nil then
variation[k] = v;
end
end
end
local details = database:GetItemType(variation.type);
if details:IsValidForCharacter(database, variation, character) then
return variation;
end
end
end
local variation = item.variations[#item.variations];
if not variation.initialized then
variation.initialized = true;
for k,v in pairs(item) do
if k ~= "variations" and variation[k] == nil then
variation[k] = v;
end
end
end
return variation
end
local function CheckTargetStatus(target, item, character)
assert(target ~= nil, format("Missing Data %s - %d", item.type, item.id or 0))
if item.status ~= nil then
for _,status in ipairs(item.status) do
if status == "available" and target:IsAvailable(character) then
return true
elseif status == "active" and target:IsActive(character) then
return true
elseif status == "completed" and target:IsCompleted(character) then
return true
elseif status == "notactive" and not target:IsActive(character) then
return true
elseif status == "notcompleted" and not target:IsCompleted(character) then
return true
end
end
elseif item.active == true then
if target:IsActive(character) then
return true
end
elseif item.active == false then
if not target:IsActive(character) then
return true
end
elseif item.completed == false then
if not target:IsCompleted(character) then
return true
end
else
if target:IsCompleted(character) then
return true
end
end
return false
end
local function CheckPetStatus(id, item, character)
if item.status == 'summon' then
local guid = C_PetJournal.GetSummonedPetGUID()
if not guid then
return false;
end
return C_PetJournal.GetPetInfoByPetID(guid) == id;
else
return select(1, C_PetJournal.GetNumCollectedInfo(id)) > 0
end
end
local function CheckMountStatus(id, item, character)
return select(11, C_MountJournal.GetMountInfoByID(id))
end
local function CheckItemStatus(id, item, character)
return GetItemCount(id) > 0
end
local function CheckQuestStatus(id, item, character)
if item.status ~= nil then
for _,status in ipairs(item.status) do
if status == "pending" and (not character:IsQuestActive(id) and not character:IsQuestCompleted(id)) then
return true
elseif status == "active" and character:IsQuestActive(id) then
return true
elseif status == "completed" and character:IsQuestCompleted(id) then
return true
elseif status == "notactive" and not character:IsQuestActive(id) then
return true
elseif status == "notcompleted" and not character:IsQuestCompleted(id) then
return true
end
end
elseif item.active == true then
if character:IsQuestActive(id) then
return true
end
elseif item.active == false then
if not character:IsQuestActive(id) then
return true
end
elseif item.completed == false then
if not character:IsQuestCompleted(id) then
return true
end
else
if character:IsQuestCompleted(id) then
return true
end
end
return false
end
local function CheckChainStatus(id, item, character)
if item.status ~= nil then
for _,status in ipairs(item.status) do
if status == "pending" and (not character:IsChainActive(id) and not character:IsChainCompleted(id)) then
return true
elseif status == "active" and character:IsChainActive(id) then
return true
elseif status == "completed" and character:IsChainCompleted(id) then
return true
elseif status == "notactive" and not character:IsChainActive(id) then
return true
elseif status == "notcompleted" and not character:IsChainCompleted(id) then
return true
end
end
elseif item.active == true then
if character:IsChainActive(id) then
return true
end
elseif item.active == false then
if not character:IsChainActive(id) then
return true
end
elseif item.completed == false then
if not character:IsChainCompleted(id) then
return true
end
else
if character:IsChainCompleted(id) then
return true
end
end
return false
end
local function CheckCategoryStatus(id, item, character)
if item.status ~= nil then
for _,status in ipairs(item.status) do
if status == "pending" and (not character:IsCategoryActive(id) and not character:IsCategoryCompleted(id)) then
return true
elseif status == "active" and character:IsCategoryActive(id) then
return true
elseif status == "completed" and character:IsCategoryCompleted(id) then
return true
elseif status == "notactive" and not character:IsCategoryActive(id) then
return true
elseif status == "notcompleted" and not character:IsCategoryCompleted(id) then
return true
end
end
return false
elseif item.active == true then
if character:IsCategoryActive(id) then
return true
end
elseif item.active == false then
if not character:IsCategoryActive(id) then
return true
end
elseif item.completed == false then
if not character:IsCategoryCompleted(id) then
return true
end
else
if character:IsCategoryCompleted(id) then
return true
end
end
return false
end
local function CheckStatusCount(amount, item)
local count = item.count or 1
local lessthan = item.lessthan and true or false
local morethan = item.morethan and true or false
local notequals = item.notequals and true or false
local equals = item.equals and true or false
local morethanorequals = not lessthan and not morethan and not notequals and not equals
if lessthan and amount < count then
return true
elseif morethan and amount > count then
return true
elseif notequals and amount ~= count then
return true
elseif equals and amount == count then
return true
elseif morethanorequals and amount >= count then
return true
end
return false
end
local function StatusCompleted(item, character, callback)
local amount = 0
if item.ids then
for _,id in ipairs(item.ids) do
if callback(id, item, character) then
amount = amount + 1
end
end
else
if callback(item.id, item, character) then
amount = amount + 1
end
end
return CheckStatusCount(amount, item)
end
local function TableOnClick(tbl, item)
if tbl[1] ~= nil then
for _,v in ipairs(tbl) do
TableOnClick(v, item)
end
else
if tbl.type == "category" then
BtWQuestsFrame:SelectCategory(tbl.id, tbl.scrollTo)
elseif tbl.type == "chain" then
BtWQuestsFrame:SelectChain(tbl.id, tbl.scrollTo)
elseif tbl.type == "coords" then
BtWQuests_ShowMapWithWaypoint(tbl.mapID, tbl.x, tbl.y, tbl.name or item:GetName())
end
end
end
local timezones = {
[BTWQUESTS_REGION_ID_US] = {
["Aegwynn"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Aerie Peak"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Agamaggan"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Aggramar"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Akama"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Alexstrasza"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Alleria"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Altar of Storms"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Alterac Mountains"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Aman'Thul"] = BTWQUESTS_TIMEZONE_ID_AUSTRALIA_MELBOURN,
["Andorhal"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Anetheron"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Antonidas"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Anub'arak"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Anvilmar"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Arathor"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Archimonde"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Area 52"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Argent Dawn"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Arthas"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Arygos"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Auchindoun"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Azgalor"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Azjol-Nerub"] = BTWQUESTS_TIMEZONE_ID_AMERICA_DENVER,
["Azralon"] = BTWQUESTS_TIMEZONE_ID_AMERICA_SAOPAULO,
["Azshara"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Azuremyst"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Baelgun"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Balnazzar"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Barthilas"] = BTWQUESTS_TIMEZONE_ID_AUSTRALIA_MELBOURN,
["Black Dragonflight"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Blackhand"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Blackrock"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Blackwater Raiders"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Blackwing Lair"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Blade's Edge"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Bladefist"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Bleeding Hollow"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Blood Furnace"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Bloodhoof"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Bloodscalp"] = BTWQUESTS_TIMEZONE_ID_AMERICA_DENVER,
["Bonechewer"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Borean Tundra"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Boulderfist"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Bronzebeard"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Burning Blade"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Burning Legion"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Caelestrasz"] = BTWQUESTS_TIMEZONE_ID_AUSTRALIA_MELBOURN,
["Cairne"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Cenarion Circle"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Cenarius"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Cho'gall"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Chromaggus"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Coilfang"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Crushridge"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Daggerspine"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Dalaran"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Dalvengyr"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Dark Iron"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Darkspear"] = BTWQUESTS_TIMEZONE_ID_AMERICA_DENVER,
["Darrowmere"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Dath'Remar"] = BTWQUESTS_TIMEZONE_ID_AUSTRALIA_MELBOURN,
["Dawnbringer"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Deathwing"] = BTWQUESTS_TIMEZONE_ID_AMERICA_DENVER,
["Demon Soul"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Dentarg"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Destromath"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Dethecus"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Detheroc"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Doomhammer"] = BTWQUESTS_TIMEZONE_ID_AMERICA_DENVER,
["Draenor"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Dragonblight"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Dragonmaw"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Drak'Tharon"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Drak'thul"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Draka"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Drakkari"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Dreadmaul"] = BTWQUESTS_TIMEZONE_ID_AUSTRALIA_MELBOURN,
["Drenden"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Dunemaul"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Durotan"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Duskwood"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Earthen Ring"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Echo Isles"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Eitrigg"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Eldre'Thalas"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Elune"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Emerald Dream"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Eonar"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Eredar"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Executus"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Exodar"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Farstriders"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Feathermoon"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Fenris"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Firetree"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Fizzcrank"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Frostmane"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Frostmourne"] = BTWQUESTS_TIMEZONE_ID_AUSTRALIA_MELBOURN,
["Frostwolf"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Galakrond"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Gallywix"] = BTWQUESTS_TIMEZONE_ID_AMERICA_SAOPAULO,
["Garithos"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Garona"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Garrosh"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Ghostlands"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Gilneas"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Gnomeregan"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Goldrinn"] = BTWQUESTS_TIMEZONE_ID_AMERICA_SAOPAULO,
["Gorefiend"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Gorgonnash"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Greymane"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Grizzly Hills"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Gul'dan"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Gundrak"] = BTWQUESTS_TIMEZONE_ID_AUSTRALIA_MELBOURN,
["Gurubashi"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Hakkar"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Haomarush"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Hellscream"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Hydraxis"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Hyjal"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Icecrown"] = BTWQUESTS_TIMEZONE_ID_AMERICA_DENVER,
["Illidan"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Jaedenar"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Jubei'Thos"] = BTWQUESTS_TIMEZONE_ID_AUSTRALIA_MELBOURN,
["Kael'thas"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Kalecgos"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Kargath"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Kel'Thuzad"] = BTWQUESTS_TIMEZONE_ID_AMERICA_DENVER,
["Khadgar"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Khaz Modan"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Khaz'goroth"] = BTWQUESTS_TIMEZONE_ID_AUSTRALIA_MELBOURN,
["Kil'jaeden"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Kilrogg"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Kirin Tor"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Korgath"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Korialstrasz"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Kul Tiras"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Laughing Skull"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Lethon"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Lightbringer"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Lightning's Blade"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Lightninghoof"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Llane"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Lothar"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Madoran"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Maelstrom"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Magtheridon"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Maiev"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Mal'Ganis"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Malfurion"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Malorne"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Malygos"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Mannoroth"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Medivh"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Misha"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Mok'Nathal"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Moon Guard"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Moonrunner"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Mug'thol"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Muradin"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Nagrand"] = BTWQUESTS_TIMEZONE_ID_AUSTRALIA_MELBOURN,
["Nathrezim"] = BTWQUESTS_TIMEZONE_ID_AMERICA_DENVER,
["Nazgrel"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Nazjatar"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Nemesis"] = BTWQUESTS_TIMEZONE_ID_AMERICA_SAOPAULO,
["Ner'zhul"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Nesingwary"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Nordrassil"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Norgannon"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Onyxia"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Perenolde"] = BTWQUESTS_TIMEZONE_ID_AMERICA_DENVER,
["Proudmoore"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Quel'Thalas"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Quel'dorei"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Ragnaros"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Ravencrest"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Ravenholdt"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Rexxar"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Rivendare"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Runetotem"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Sargeras"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Saurfang"] = BTWQUESTS_TIMEZONE_ID_AUSTRALIA_MELBOURN,
["Scarlet Crusade"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Scilla"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Sen'jin"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Sentinels"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Shadow Council"] = BTWQUESTS_TIMEZONE_ID_AMERICA_DENVER,
["Shadowmoon"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Shadowsong"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Shandris"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Shattered Halls"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Shattered Hand"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Shu'halo"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Silver Hand"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Silvermoon"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Sisters of Elune"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Skullcrusher"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Skywall"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Smolderthorn"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Spinebreaker"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Spirestone"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Staghelm"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Steamwheedle Cartel"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Stonemaul"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Stormrage"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Stormreaver"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Stormscale"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Suramar"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Tanaris"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Terenas"] = BTWQUESTS_TIMEZONE_ID_AMERICA_DENVER,
["Terokkar"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Thaurissan"] = BTWQUESTS_TIMEZONE_ID_AUSTRALIA_MELBOURN,
["The Forgotten Coast"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["The Scryers"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["The Underbog"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["The Venture Co"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Thorium Brotherhood"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Thrall"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Thunderhorn"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Thunderlord"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Tichondrius"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Tol Barad"] = BTWQUESTS_TIMEZONE_ID_AMERICA_SAOPAULO,
["Tortheldrin"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Trollbane"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Turalyon"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Twisting Nether"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Uldaman"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Uldum"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Undermine"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Ursin"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Uther"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Vashj"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Vek'nilash"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Velen"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Warsong"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Whisperwind"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Wildhammer"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Windrunner"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Winterhoof"] = BTWQUESTS_TIMEZONE_ID_AMERICA_CHICAGO,
["Wyrmrest Accord"] = BTWQUESTS_TIMEZONE_ID_AMERICA_LOSANGELAS,
["Ysera"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Ysondre"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Zangarmarsh"] = BTWQUESTS_TIMEZONE_ID_AMERICA_DENVER,
["Zul'jin"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
["Zuluhed"] = BTWQUESTS_TIMEZONE_ID_AMERICA_NEWYORK,
},
[BTWQUESTS_REGION_ID_KOREA] = BTWQUESTS_TIMEZONE_ID_ASIA_SEOUL,
[BTWQUESTS_REGION_ID_EUROPE] = BTWQUESTS_TIMEZONE_ID_EUROPE_PARIS,
[BTWQUESTS_REGION_ID_TAIWAN] = BTWQUESTS_TIMEZONE_ID_ASIA_TAIPEI,
}
function BtWQuests_GetTimeZone(region, realm)
if region == nil then
region = GetCurrentRegion()
elseif type(region) == "string" then
return BtWQuests_GetTimeZone(nil, region)
end
if realm == nil then
realm = GetRealmName()
end
if type(timezones[region]) == "table" then
return timezones[region][realm]
else
return timezones[region]
end
end
local function GetPlayerPosition(targetMapID)
local sourceMapID = C_Map.GetBestMapForUnit("player")
local sourceCoords = C_Map.GetPlayerMapPosition(sourceMapID, "player")
if sourceMapID == targetMapID then
return sourceCoords
else
local continentID, coords = C_Map.GetWorldPosFromMapPos(sourceMapID, sourceCoords)
if coords == nil then
return nil
end
local _, coords = C_Map.GetMapPosFromWorldPos(continentID, coords, targetMapID)
if coords == nil then
return nil
end
return coords
end
end
function BtWQuests_GetBestLocation(database, locations, relativeMapID) -- , relativeX, relativeY Should these be added?
if relativeMapID == nil then
local possibleLocations = {}
for mapID in pairs(locations) do
local _, item = BtWQuests_GetBestLocation(database, locations, mapID)
if item then
possibleLocations[#possibleLocations+1] = item
end
end
table.sort(possibleLocations, function (a, b)
return (a.distanceSq or 0) < (b.distanceSq or 0)
end)
local result = possibleLocations[1]
return result.mapID, result
end
if locations[relativeMapID] == nil then -- This'll take a while
local sourceMapID, sourceCoords = BtWQuests_GetBestLocation(database, locations)
if sourceCoords == nil then
return nil
end
local continentID, coords = C_Map.GetWorldPosFromMapPos(sourceMapID, sourceCoords)
if coords == nil then
return nil
end
local _, coords = C_Map.GetMapPosFromWorldPos(continentID, coords, relativeMapID)
if coords == nil then
return nil
end
return relativeMapID, coords
else
local filtered = database:FilterItems(locations[relativeMapID], BtWQuestsCharacters:GetPlayer())
local result = filtered[1]
local resultDistanceSq
if #filtered > 1 then
local playerPos = GetPlayerPosition(relativeMapID)
if playerPos then
resultDistanceSq = CalculateDistanceSq(result.x, result.y, playerPos.x, playerPos.y)
for i=2,#filtered do
local item = filtered[i]
local itemDistanceSq = CalculateDistanceSq(item.x, item.y, playerPos.x, playerPos.y)
if itemDistanceSq < resultDistanceSq then
result = item
resultDistanceSq = itemDistanceSq
end
end
end
end
if not result then
return nil
end
result = CreateVector2D(result.x, result.y)
result.mapID = relativeMapID
result.distanceSq = resultDistanceSq
return relativeMapID, result
end
end
local ConditionMixin = {}
function ConditionMixin:EvalFor(character)
return self.database:EvalRequirement(self, self, character);
end
local DataMixin = {};
function DataMixin:GetID()
return self.id;
end
function DataMixin:GetName()
return self.database:EvalText(self.name, self)
end
function DataMixin:GetUserdata()
return self.userdata
end
function DataMixin:IsValidForCharacter(character)
return self.database:IsItemValidForCharacter(self, character);
end
function DataMixin:Visible(character)
if self.visible ~= nil then
return self.database:EvalRequirement(self.visible, self, character);
end
return true;
end
function DataMixin:IsAvailable(character)
assert(character ~= nil);
if self:IsCompleted(character) then
return false;
end
if self:IsActive(character) then
return false;
end
if self.prerequisites ~= nil then
return self.database:EvalRequirement(self.prerequisites, self, character);
end
return true;
end
function DataMixin:IsActive(character)
assert(character ~= nil);
if self:IsCompleted(character) then
return false;
end
if self.active ~= nil then
return self.database:EvalRequirement(self.active, self, character, true);
end
if self.prerequisites ~= nil then
return self.database:EvalRequirement(self.prerequisites, self, character);
end
return true;
end
function DataMixin:IsCompleted(character)
if self.completed ~= nil then
return self.database:EvalRequirement(self.completed, self, character);
end
return false
end
function DataMixin:GetPrerequisites()
if self.prerequisitesItems == nil then
self.prerequisitesItems = {}
if self.prerequisites then
if self.prerequisites[1] == nil then
self.prerequisitesItems[#self.prerequisitesItems+1] = self.database:CreateItem(-1, self.prerequisites, item);
else
for _,prerequisite in ipairs(self.prerequisites) do
self.prerequisitesItems[#self.prerequisitesItems+1] = self.database:CreateItem(-1, prerequisite, item);
end
end
end
end
return self.prerequisitesItems, self.hasLowPriorityPrerequisites;
end
function DataMixin:GetRestrictions()
if self.restrictionsItems == nil then
self.restrictionsItems = {}
if self.restrictions then
local restrictions = self.restrictions
if type(restrictions) == "number" then
restrictions = self.database:GetConditionByID(restrictions)
end
if restrictions[1] == nil then
self.restrictionsItems[#self.restrictionsItems+1] = self.database:CreateItem(-1, restrictions, item);
else
for _,restriction in ipairs(restrictions) do
self.restrictionsItems[#self.restrictionsItems+1] = self.database:CreateItem(-1, restriction, item);
end
end
end
end
return self.restrictionsItems;
end
function DataMixin:GetRewards()
if self.rewardsItems == nil then
self.rewardsItems = {}
if self.rewards then
for _,reward in ipairs(self.rewards) do
self.rewardsItems[#self.rewardsItems+1] = self.database:CreateItem(-1, reward, self, self);
end
end
end
return self.rewardsItems;
end
local QuestMixin = CreateFromMixins(DataMixin);
function QuestMixin:GetContentTuningID()
return self.contentTuningID or -1
end
function QuestMixin:GetLevel()
return self.level or -1
end
function QuestMixin:GetRequiredLevel()
return self.requiredLevel or -1
end
function QuestMixin:GetMaxLevel()
return self.maxLevel or 255
end
function QuestMixin:GetLevelFlag()
return self.levelFlag or 0
end
if INTERFACE_NUMBER < 90000 then
function QuestMixin:GetLink()
if self.link == nil then
self.link = format("\124cffffff00\124Hquest:%d:%d:%d:%d:%d\124h[%s]\124h\124r", self:GetID(), self:GetLevel(), self:GetRequiredLevel(), self:GetMaxLevel(), self:GetLevelFlag(), self:GetName())
end
return self.link
end
else
function QuestMixin:GetLink()
if self.link == nil then
self.link = format("\124cffffff00\124Hquest:%d:%d\124h[%s]\124h\124r", self:GetID(), self:GetContentTuningID(), self:GetName())
end
return self.link
end
end
function QuestMixin:IsActive(character)
return character:IsQuestActive(self.id)
end
function QuestMixin:IsCompleted(character)
return character:IsQuestCompleted(self.id)
end
function QuestMixin:GetUserdata()
-- if self.userdata == nil then
-- self.userdata = self.item.userdata or {}
-- self.userdata.link = self.userdata.link or self:GetLink()
-- end
return self.userdata
end
function QuestMixin:GetSource(character)
if self.source ~= nil then
return self.database:CreateItem(-1, self.source, self, self);
end
return nil;
end
function QuestMixin:HasTarget()
return self.target ~= nil or self.targets ~= nil
end
function QuestMixin:GetTargetMapID()
return (self.target and self.target.uiMapID) or (self.targets and self.targets.mapID)
end
function QuestMixin:HasObjectives()
return self.objectives ~= nil
end
function QuestMixin:GetCurrentObjectiveMapID()
for index,objective in ipairs(C_QuestLog.GetQuestObjectives(self:GetID())) do
if not objective.finished then
return self.objectives[index].uiMapID or self.objectives[index].mapID
end
end
end
local MissionMixin = CreateFromMixins(DataMixin);
function MissionMixin:IsBreadcrumb()
return true
end
function MissionMixin:IsAvailable(character)
local mission = C_Garrison.GetBasicMissionInfo(self.id or self.ids[1])
return mission ~= nil
end
function MissionMixin:IsActive(character)
local mission = C_Garrison.GetBasicMissionInfo(self.id or self.ids[1])
return mission and mission.inProgress or false
end
function MissionMixin:IsCompleted(character)
return false
end
local NPCMixin = CreateFromMixins(DataMixin);
function NPCMixin:IsBreadcrumb()
return true
end
function NPCMixin:IsAvailable(character)
return false
end
function NPCMixin:IsActive(character)
return false
end
function NPCMixin:IsCompleted(character)
return false
end
function NPCMixin:GetLocation(...)
if self.locations == nil then
return nil
end
return BtWQuests_GetBestLocation(self.database, self.locations, ...)
end
local ObjectMixin = CreateFromMixins(NPCMixin);
local ChainMixin = CreateFromMixins(DataMixin);
function ChainMixin:GetSubtext(character, small)
if self:IsCompleted(character) then
return L["BTWQUESTS_COMPLETED"]
elseif self:IsActive(character) then
return L["BTWQUESTS_ACTIVE"]
elseif self:IsAvailable(character) then
return L["BTWQUESTS_AVAILABLE"]
end
end
function ChainMixin:GetLink()
if self.link == nil then
self.link = format("\124cffffff00\124Hbtwquests:chain:%s\124h[%s]\124h\124r", self:GetID(), self:GetName())
end
return self.link
end
function ChainMixin:GetExpansion()
return self.expansion
end
function ChainMixin:GetCategory()
return self.category
end
function ChainMixin:GetNumPrerequisites()
if self.prerequisites == nil then
return 0
end
if self.prerequisites[1] == nil then
return 1
end
return #self.prerequisites
end
function ChainMixin:GetNumItems()
return #self.items
end
-- Check if the character is ignoring this chain
function ChainMixin:IsIgnored(character)
return character:IsChainIgnored(self:GetID())
end
function ChainMixin:IsAvailable(character)
assert(character ~= nil);
if self:IsIgnored(character) then
return false
end
return DataMixin.IsAvailable(self, character);
end
function ChainMixin:IsActive(character)
assert(character ~= nil);
if self:IsIgnored(character) then
return false
end
return DataMixin.IsActive(self, character);
end
function ChainMixin:IsMajor()
return self.major == true
end
function ChainMixin:GetButtonImage()
if type(self.buttonImage) ~= "table" then
return self.buttonImage
end
return self.buttonImage.texture, unpack(self.buttonImage.texCoords)
end
function ChainMixin:GetListImage()
if type(self.listImage) ~= "table" then
return self.listImage
end
return self.listImage.texture, unpack(self.listImage.texCoords)
end
function ChainMixin:GetItem(index, character)
local index = tonumber(index);
if index == nil or self.items[index] == nil then
return nil;
end
local item = GetVariation(self.database, self.items[index], character)
local result = self.database:CreateItem(index, item, self, self);
if result:GetType() == "chain" and result:IsEmbed() then
local connections = result:GetConnections();
if connections then
if connections[1] ~= nil and type(connections[1]) ~= "table" then
connections[result:GetNumItems()] = {connections[1]};
connections[1] = nil;
end
end
end
return result;
end
function ChainMixin:GetNextItem(character)
for i = 1,self:GetNumItems() do
local item = self:GetItem(i, character)
if item and item:IsValidForCharacter(character) and not item:IsAside(character) and not item:IsBreadcrumb(character) and item:Visible(character) and not item:IsCompleted(character) then
return item
end
end
end
function ChainMixin:GetAlternative(character)
if self.alternatives == nil then
return nil
end
for _,v in ipairs(self.alternatives) do
if self.database:GetChainByID(v):IsValidForCharacter(character) then
return v;
end
end
return nil;
end
local CategoryMixin = CreateFromMixins(DataMixin);
function CategoryMixin:GetProgress(character)
local majorProgress = 0
local majorTotal = 0
local minorProgress = 0
local minorTotal = 0
for _,v in ipairs(self.items) do
if v.type == 'chain' then
if not character:IsChainIgnored(v.id) and self.database:IsItemValidForCharacter(v, character) then
local chain = self.database:GetChainByID(v.id);
if v.major or (chain and chain:IsMajor()) then
if self.database:IsChainCompleted(v.id, character) then
majorProgress = majorProgress + 1
end
majorTotal = majorTotal + 1
else
if self.database:IsChainCompleted(v.id, character) then
minorProgress = minorProgress + 1
end
minorTotal = minorTotal + 1
end
end
elseif v.type == 'category' then
if not character:IsCategoryIgnored(v.id) then
if self.database:IsCategoryCompleted(v.id, character) then
minorProgress = minorProgress + 1
end
minorTotal = minorTotal + 1
end
end
end
if majorTotal == 0 then
majorProgress = minorProgress
majorTotal = minorTotal
minorProgress = 0
minorTotal = 0
end
if minorTotal == 0 then
return majorProgress, majorTotal
end
return majorProgress, majorTotal, minorProgress, minorTotal
end
function CategoryMixin:GetSubtext(character, small)
if self:IsCompleted(character) then
return L["BTWQUESTS_COMPLETED"]
end
local majorProgress, majorTotal, minorProgress, minorTotal = self:GetProgress(character)
if minorTotal == nil then
return string.format(L["BTWQUESTS_PROGRESS"], majorProgress, majorTotal)
elseif small then
return string.format(L["BTWQUESTS_PROGRESS"], minorProgress + majorProgress, minorTotal + majorTotal)
else
return string.format(L["BTWQUESTS_PROGRESS_SIDE"], majorProgress, majorTotal, minorProgress + majorProgress, minorTotal + majorTotal)
end
end
function CategoryMixin:GetExpansion()
return self.expansion
end
function CategoryMixin:GetParent()
return self.parent
end
function CategoryMixin:GetLink()
if self.link == nil then
self.link = format("\124cffffff00\124Hbtwquests:category:%s\124h[%s]\124h\124r", self:GetID(), self:GetName())
end
return self.link
end
function CategoryMixin:GetItemList(character, noHeaders, filterCompleted, filterIgnored, filterMajor, includeChildren)
local major = {}
local others = {}
local completed = {}
local ignored = {}
local header = nil
local index = 1
local item = self:GetItem(index, character)
while item do
if item:IsValidForCharacter(character) and item:Visible(character) then
if item:GetType() == "header" then
header = item
else
if includeChildren and item:GetType() == "category" then
local children = item:GetItemList(character, noHeaders, false, false, false, includeChildren)
if filterIgnored and character:IsCategoryIgnored(item:GetID()) then
for _,child in ipairs(children) do
table.insert(ignored, child)
end
else
for _,child in ipairs(children) do
if filterIgnored and child:GetType() == "chain" and character:IsChainIgnored(child:GetID()) then
table.insert(ignored, child)
elseif filterCompleted and child:IsCompleted(character) then
table.insert(completed, child)
elseif filterMajor and child:GetType() == "chain" and child:IsMajor() then
table.insert(major, child)
else
table.insert(others, child)
end
end
end
elseif filterIgnored and item:GetType() == "category" and character:IsCategoryIgnored(item:GetID()) then
table.insert(ignored, item)
elseif filterIgnored and item:GetType() == "chain" and character:IsChainIgnored(item:GetID()) then
table.insert(ignored, item)
elseif filterCompleted and item:IsCompleted(character) then
table.insert(completed, item)
elseif filterMajor and item:GetType() == "chain" and item:IsMajor() then
table.insert(major, item)
else
if header ~= nil then
if not noHeaders then
table.insert(others, header)
end
header = nil
end
table.insert(others, item)
end
end
end
index = index + 1
item = self:GetItem(index, character)
end
local results = {}
if #major > 0 then
if not noHeaders then
table.insert(results, self.database:CreateItem(-1, {type = "header", name = L["BTWQUESTS_MAJOR"]}, self, self))
end
for _,item in ipairs(major) do
table.insert(results, item)
end
end
for _,item in ipairs(others) do
table.insert(results, item)
end
if #completed > 0 then
if not noHeaders then
table.insert(results, self.database:CreateItem(-1, {type = "header", name = L["BTWQUESTS_COMPLETED"]}, self, self))
end
for _,item in ipairs(completed) do
table.insert(results, item)
end
end
if #ignored > 0 then
if not noHeaders then
table.insert(results, self.database:CreateItem(-1, {type = "header", name = L["BTWQUESTS_IGNORED"]}, self, self))
end
for _,item in ipairs(ignored) do
table.insert(results, item)
end
end
return results
end
function CategoryMixin:GetItem(index, character)
local index = tonumber(index)
if index == nil or self.items == nil or self.items[index] == nil then
return nil
end
local item = GetVariation(self.database, self.items[index], character)
return self.database:CreateItem(index, item, self, self);
end
function CategoryMixin:IsMajor()
return self.major == true
end
function CategoryMixin:GetButtonImage()
if type(self.buttonImage) ~= "table" then
return self.buttonImage
end
return self.buttonImage.texture, unpack(self.buttonImage.texCoords)
end
function CategoryMixin:GetListImage()
if type(self.listImage) ~= "table" then
return self.listImage
end
return self.listImage.texture, unpack(self.listImage.texCoords)
end
function CategoryMixin:IsCompleted(character)
for _,v in ipairs(self.items) do
if v.type == 'chain' then
local item = self.database:GetChainByID(v.id)
if not character:IsChainIgnored(v.id) and item:IsValidForCharacter(character) and not item:IsCompleted(character) then
return false
end
elseif v.type == 'category' then
local item = self.database:GetCategoryByID(v.id)
if not character:IsCategoryIgnored(v.id) and not item:IsCompleted(character) then
return false
end
end
end
return true
end
function CategoryMixin:IsActive(character)
return false
end
function CategoryMixin:GetAlternative(character)
if self.alternatives == nil then
return nil
end
for _,v in ipairs(self.alternatives) do
if self.database:GetCategoryByID(v):IsValidForCharacter(character) then
return v;
end
end
return nil;
end
local ExpansionMixin = CreateFromMixins(CategoryMixin);
function ExpansionMixin:GetLink()
if self.link == nil then
self.link = format("\124cffffff00\124Hbtwquests:expansion:%s\124h[%s]\124h\124r", self:GetID(), self:GetName())
end
return self.link
end
function ExpansionMixin:GetImage()
if type(self.image) ~= "table" then
return self.image
end
return self.image.texture, unpack(self.image.texCoords)
end
-- Gets 3 items about the expansions
function ExpansionMixin:GetMajorItems(character)
local actives, available, upcoming = {}, {}, {}
local items = self:GetItemList(character, true, true, true, true, true)
for _,item in ipairs(items) do
if item:IsActive(character) then
actives[#actives+1] = item
if #actives == 3 then
break
end
elseif item:IsAvailable(character) then
available[#available+1] = item
elseif not item:IsCompleted(character) then
upcoming[#upcoming+1] = item
end
end
local items = actives
for _,item in ipairs(available) do
if #items == 3 then
break
end
items[#items+1] = item
end
for _,item in ipairs(upcoming) do
if #items == 3 then
break
end
items[#items+1] = item
end
return {items[1], items[2], items[3]}
end
function ExpansionMixin:IsCompleted()
return GetAccountExpansionLevel() >= self.id
end
function ExpansionMixin:IsLoaded()
if type(self.addons) ~= "table" or next(self.addons) == nil then
return true
end
for addon in pairs(self.addons) do
if not IsAddOnLoaded(addon) then
return false
end
end
return true
end
function ExpansionMixin:SupportAutoLoad()
return type(self.addons) == "table" and next(self.addons) ~= nil
end
function ExpansionMixin:IsAutoLoad()
if type(self.addons) ~= "table" or next(self.addons) == nil then
return true
end
for addon in pairs(self.addons) do
if not BtWQuests_AutoLoad[addon] then
return false
end
end
return true
end
function ExpansionMixin:SetAutoLoad(value)
if type(self.addons) ~= "table" or next(self.addons) == nil then
return
end
for addon in pairs(self.addons) do
BtWQuests_AutoLoad[addon] = value
end
end
function ExpansionMixin:Load()
wipe(self.database.questCache);
for addon in pairs(self.addons) do
LoadAddOn(addon)
end
end
local ItemMixin = {};
function ItemMixin:EqualsItem(database, item, other)
local type = self:GetType(database, item)
if type ~= other.type then
return false
end
if type == "chain" or type == "category" or type == "npc" or type == "quest" or type == "achievement" or type == "mission" or type == "faction" or type == "race" or type == "class" then
return tonumber(self:GetID(database, item)) == tonumber(other.id)
elseif type == "level" then
return tonumber(self:GetLevel(database, item)) == tonumber(other.level)
else
return false
end
end
function ItemMixin:GetType(database, item)
return item.type;
end
function ItemMixin:GetID(database, item, index)
if item.id then
return item.id;
end
local index = index or 1;
if item.ids and item.ids[index] then
return item.ids[index];
end
return nil;
end
function ItemMixin:GetVariation(database, item, character)
if item.variations == nil then
return nil;
end
return database:CreateItem(item.index, GetVariation(database, item, character), item, self:GetRoot(database, item));
end
function ItemMixin:IsValidForCharacter(database, item, character)
if item.restrictions ~= nil then
return database:IsItemValidForCharacter(item, character);
end
return true;
end
function ItemMixin:Visible(database, item, character, showAll)
if item.visible ~= nil then
return (showAll or not item.lowPriority) and database:EvalRequirement(item.visible, item, character);
end
return (showAll or not item.lowPriority);
end
function ItemMixin:GetName(database, item, character)
if item.name then
return database:EvalText(item.name, item, character);
end
return "Unnamed"
end
function ItemMixin:GetSubtext(database, item, character, small)
if item.subtext then
return database:EvalText(item.subtext, item, character);
end
end
function ItemMixin:GetAlternative(database, item, character)
if item.alternatives ~= nil then
for _,v in ipairs(item.alternatives) do
if database:IsItemValidForCharacter({type = item.type, id = v}, character) then
return v;
end
end
end
return nil;
end
function ItemMixin:IsAvailable(database, item, character)
if self:IsCompleted(database, item, character) then
return false;
end
if self:IsActive(database, item, character) then
return false;
end
if item.prerequisites ~= nil then
return database:EvalRequirement(item.prerequisites, item, character);
end
return true
end
function ItemMixin:IsActive(database, item, character)
if self:IsCompleted(database, item, character) then
return false;
end
if item.active ~= nil then
return database:EvalRequirement(item.active, item, character, true);
end
return false;
end
function ItemMixin:IsCompleted(database, item, character, ...)
if item.completed ~= nil then
return database:EvalRequirement(item.completed, item, character);
end
return false
end
function ItemMixin:IsBreadcrumb(database, item, character)
if item.breadcrumb ~= nil then
return item.breadcrumb;
end
return false
end
function ItemMixin:IsAside(database, item, character)
if item.aside ~= nil then
return item.aside;
end
return false
end
function ItemMixin:OnClick(database, item, character, ...)
if type(item.onClick) == "table" then
return TableOnClick(item.onClick, database:CreateItem(0, item), character, ...);
elseif type(item.onClick) == "function" then
return item.onClick(item, character, ...);
end
return nil;
end
function ItemMixin:OnEnter(database, item, character, ...)
if item.onEnter ~= nil then
return item.onEnter(item, character, ...);
end
return nil;
end
function ItemMixin:OnLeave(database, item, character, ...)
if item.onLeave ~= nil then
return item.onLeave(item, character, ...);
end
return nil;
end
function ItemMixin:GetPrerequisites(database, item)
if item.prerequisites ~= nil then
local result = {}
for _,prerequisite in ipairs(item.prerequisites) do
result[#result+1] = database:CreateItem(-1, prerequisite, item, self:GetRoot(database, item));
end
return result, item.hasLowPriorityPrerequisites;
end
end
function ItemMixin:GetRewards(database, item)
if item.rewards ~= nil then
local result = {}
for _,reward in ipairs(item.rewards) do
result[#result+1] = database:CreateItem(-1, reward, item, self:GetRoot(database, item));
end
return result;
end
end
function ItemMixin:GetDifficulty(database, item)
return item.difficulty;
end
function ItemMixin:GetTagID(database, item)
return item.tagID;
end
function ItemMixin:GetUserdata(database, item)
if item.userdata ~= nil then
return item.userdata;
end
return nil;
end
function ItemMixin:GetStatus(database, item, character)
if item.status ~= nil then
return database:EvalText(item.status, item, character)
end
if self:IsCompleted(database, item, character) then
return "complete"
end
if self:IsBreadcrumb(database, item, character) and self:HasConnections(database, item, character) then
local completed = false
local index = 1
local connection = self:GetConnection(database, item, index, character)
while connection do
if connection:IsValidForCharacter(character) and connection:Visible(character) and connection:GetStatus(character) ~= nil then
completed = true
break
end
index = index + 1
connection = self:GetConnection(database, item, index, character)
end
if completed then
return "complete"
end
end
if self:IsActive(database, item, character) then
return "active"
end
return nil
end
function ItemMixin:GetX(database, item)
return item.x;
end
function ItemMixin:GetY(database, item)
return item.y
end
function ItemMixin:GetParent(database, item)
return item.parent
end
function ItemMixin:GetRoot(database, item)
return item.root
end
function ItemMixin:GetIndex(database, item)
return item.index
end
function ItemMixin:HasConnections(database, item)
return item.connections and #item.connections > 0
end
function ItemMixin:GetConnection(database, item, index, character, overrideConnections, overrideChain)
local index = tonumber(index)
if overrideConnections then
local connections = overrideConnections;
if index == nil or connections == nil or connections[index] == nil then
return nil;
end
local connection = tostring(connections[index]);
local match = string.gmatch(connection, "[^%.]+");
local result = overrideChain:GetItem(tonumber(match()), character);
for value in match do
result = result:GetItem(tonumber(value), character);
end
while result and result:GetType() == "chain" and result:IsEmbed() do
result = result:GetItem(1, character);
end
return result;
else
local connections = item.connections;
if index == nil or connections == nil or connections[index] == nil then
return nil;
end
local connection = tostring(connections[index]);
local match = string.gmatch(connection, "[^%.]+");
local result = self:GetRoot(database, item):GetItem(item.index + tonumber(match()), character);
for value in match do
result = result:GetItem(tonumber(value), character);
end
while result and result:GetType() == "chain" and result:IsEmbed() do
result = result:GetItem(1, character);
end
return result;
end
end
function ItemMixin:GetConnections(database, item)
return item.connections;
end
function ItemMixin:GetAtlas(database, item)
return item.atlas
end
function ItemMixin:GetSource(database, item, character)
if item.source ~= nil then
return database:CreateItem(-1, item.source, item, self:GetRoot(database, item));
end
return nil;
end
local TargetItemMixin = CreateFromMixins(ItemMixin);
function TargetItemMixin:GetTargetType(database, item)
return item.type;
end
function TargetItemMixin:GetTarget(database, item, index)
local type = self:GetTargetType(database, item);
if type == nil or not database:HasDataType(type) then
return nil;
end
if item.id then
return database:GetData(type, item.id);
end
local index = index or 1;
if item.ids and item.ids[index] then
return database:GetData(type, item.ids[index]);
end
return nil;
end
function TargetItemMixin:TargetCount(database, item)
return item.ids and #item.ids or 1;
end
function TargetItemMixin:IsValidForCharacter(database, item, character)
if item.restrictions ~= nil then
return ItemMixin.IsValidForCharacter(self, database, item, character);
end
local target = self:GetTarget(database, item);
if target then
return target:IsValidForCharacter(character);
end
return true;
end
function TargetItemMixin:Visible(database, item, character, showAll)
if not showAll and item.lowPriority then
return false
end
if item.visible ~= nil then
return ItemMixin.Visible(self, database, item, character);
end
local target = self:GetTarget(database, item);
if target then
return target:Visible(character);
end
return true;
end
function TargetItemMixin:GetName(database, item, character)
if item.name then
return ItemMixin.GetName(self, database, item, character);
end
local target = self:GetTarget(database, item);
if target then
return target:GetName(character);
end
return "Unnamed"
end
function TargetItemMixin:GetSubtext(database, item, character, small)
if item.subtext then
return ItemMixin.GetSubtext(self, database, item, character);
end
local target = self:GetTarget(database, item);
if target then
return target:GetSubtext(character, small);
end
end
function TargetItemMixin:GetAlternative(database, item, character)
if item.alternatives ~= nil then
return ItemMixin.GetAlternative(self, database, item, character);
end
local target = self:GetTarget(database, item);
if target then
return target:GetAlternative(character);
end
end
function TargetItemMixin:IsAvailable(database, item, character)
if item.prerequisites ~= nil then
return ItemMixin.IsAvailable(self, database, item, character);
end
for i=1,self:TargetCount(database, item) do
local target = self:GetTarget(database, item, i);
if target and target:IsAvailable(character) then
return true
end
end
return false
end
function TargetItemMixin:IsActive(database, item, character)
if item.active ~= nil then
return ItemMixin.IsActive(self, database, item, character);
end
for i=1,self:TargetCount(database, item) do
local target = self:GetTarget(database, item, i);
if target and target:IsActive(character) then
return true
end
end
return false
end
function TargetItemMixin:IsCompleted(database, item, character, ...)
if item.completed ~= nil then
return ItemMixin.IsCompleted(self, database, item, character);
end
local type = self:GetTargetType(database, item);
if type == nil or not database:HasDataType(type) then
return false;
end
local amount = 0
if item.ids then
for _,id in ipairs(item.ids) do
if CheckTargetStatus(database:GetData(type, id), item, character) then
amount = amount + 1
end
end
else
if CheckTargetStatus(database:GetData(type, item.id), item, character) then
amount = amount + 1
end
end
return CheckStatusCount(amount, item)
end
function TargetItemMixin:GetPrerequisites(database, item)
if item.prerequisites ~= nil then
return ItemMixin.GetPrerequisites(self, database, item, character);
end
local target = self:GetTarget(database, item);
if target then
return target:GetPrerequisites(character);
end
end
function TargetItemMixin:GetRewards(database, item)
if item.rewards ~= nil then
return ItemMixin.GetRewards(self, database, item, character);
end
local target = self:GetTarget(database, item);
if target then
return target:GetRewards(character);
end
end
local HeaderItemMixin = CreateFromMixins(ItemMixin);
local QuestItemMixin = CreateFromMixins(TargetItemMixin);
-- Using this instead of the target system because some quests wont be in our database
function QuestItemMixin:IsCompleted(database, item, character, ...)
if item.completed ~= nil then
return ItemMixin.IsCompleted(self, database, item, character)
end
return StatusCompleted(item, character, CheckQuestStatus)
end
function QuestItemMixin:GetContentTuningID(database, item)
return item.contentTuningID or self:GetTarget(database, item):GetContentTuningID();
end
function QuestItemMixin:GetLevel(database, item)
return item.level or self:GetTarget(database, item):GetLevel();
end
function QuestItemMixin:GetRequiredLevel(database, item)
return item.requiredLevel or self:GetTarget(database, item):GetRequiredLevel();
end
function QuestItemMixin:GetMaxLevel(database, item)
return item.requiredLevel or self:GetTarget(database, item):GetMaxLevel();
end
function QuestItemMixin:GetLevelFlag(database, item)
return item.levelFlag or self:GetTarget(database, item):GetLevelFlag();
end
if INTERFACE_NUMBER < 90000 then
function QuestItemMixin:GetLink(database, item)
return format("\124cffffff00\124Hquest:%d:%d:%d:%d:%d\124h[%s]\124h\124r", self:GetID(database, item), self:GetLevel(database, item), self:GetRequiredLevel(database, item), self:GetMaxLevel(database, item), self:GetLevelFlag(database, item), self:GetName(database, item));
end
else
function QuestItemMixin:GetLink(database, item)
return format("\124cffffff00\124Hquest:%d:%d\124h[%s]\124h\124r", self:GetID(database, item), self:GetContentTuningID(database, item), self:GetName(database, item));
end
end
function QuestItemMixin:OnClick(database, item, character, button, frame, tooltip)
if item.onClick ~= nil then
return ItemMixin.OnClick(self, database, item, character, button, frame, tooltip)
end
if self:GetTarget(database, item) and ChatEdit_TryInsertChatLink(self:GetLink(database, item)) then
return
end
local questID = self:GetID(database, item)
local questLogIndex = GetLogIndexForQuestID(questID)
if IsModifiedClick("QUESTWATCHTOGGLE") then
if GetQuestWatchType(questID) ~= nil then
RemoveQuestWatch(questID)
else
AddQuestWatch(questID)
end
return
end
if questLogIndex and questLogIndex ~= 0 then
if BtWQuestsFrame:SelectFromLink(self:GetLink(database, item)) then
return
end
else
local source = self:GetSource(database, item, character)
if source then
local mapID, coords = source:GetLocation()
if mapID and coords then
BtWQuests_ShowMapWithWaypoint(mapID, coords.x, coords.y, source:GetName())
end
end
end
end
function QuestItemMixin:OnEnter(database, item, character, button, frame, tooltip)
if item.onEnter ~= nil then
return ItemMixin.OnEnter(self, database, item, character, button, frame, tooltip)
end
if tooltip ~= nil then
local target = self:GetTarget(database, item)
local userdata = self:GetUserdata(database, item)
local link = userdata and userdata.link or (target and self:GetLink(database, item))
if link then
tooltip:SetPoint("TOPLEFT", button, "TOPRIGHT")
tooltip:SetOwner(button, "ANCHOR_PRESERVE");
tooltip:SetHyperlink(link, character)
end
end
end
function QuestItemMixin:OnLeave(database, item, character, button, frame, tooltip)
if item.onLeave ~= nil then
return ItemMixin.OnLeave(self, database, item, character, button, frame, tooltip)
end
if tooltip ~= nil then
tooltip:Hide()
end
end
function QuestItemMixin:GetSource(database, item, character)
if item.source ~= nil then
return database:CreateItem(-1, item.source, item, self:GetRoot(database, item));
end
local target = self:GetTarget(database, item);
if target then
return target:GetSource(character);
end
return nil;
end
local ExpansionItemMixin = CreateFromMixins(TargetItemMixin);
local CategoryItemMixin = CreateFromMixins(TargetItemMixin);
function CategoryItemMixin:GetLink(database, item)
return self:GetTarget(database, item):GetLink();
end
function CategoryItemMixin:IsMajor(database, item)
return item.major or self:GetTarget(database, item):IsMajor();
end
function CategoryItemMixin:GetItemList(database, item, ...)
return self:GetTarget(database, item):GetItemList(...);
end
function CategoryItemMixin:GetButtonImage(database, item)
if item.buttonImage == nil then
return self:GetTarget(database, item):GetButtonImage();
end
if type(item.buttonImage) ~= "table" then
return item.buttonImage
end
return item.buttonImage.texture, unpack(item.buttonImage.texCoords)
end
function CategoryItemMixin:GetListImage(database, item)
if item.listImage == nil then
return self:GetTarget(database, item):GetListImage();
end
if type(item.listImage) ~= "table" then
return item.listImage
end
return item.listImage.texture, unpack(item.listImage.texCoords)
end
function CategoryItemMixin:OnClick(database, item, character, button, frame, tooltip)
if ChatEdit_TryInsertChatLink(self:GetLink(database, item)) then
return
end
local userdata = self:GetUserdata(database, item)
if frame:SelectFromLink(self:GetLink(database, item), userdata and userdata.scrollTo) then
PlaySound(SOUNDKIT.IG_SPELLBOOK_OPEN)
return
end
tooltip:Hide()
end
function CategoryItemMixin:OnEnter(database, item, character, button, frame, tooltip)
end
function CategoryItemMixin:OnLeave(database, item, character, button, frame, tooltip)
tooltip:Hide()
end
local ChainItemMixin = CreateFromMixins(TargetItemMixin);
function ChainItemMixin:GetName(database, item, character)
local name = TargetItemMixin.GetName(self, database, item, character)
local uptoType = type(item.upto)
if uptoType == "table" then
elseif uptoType == "number" then
local quest = database:GetQuestByID(item.upto)
if quest then
return string.format(L["UP_TO"], name, quest:GetName())
end
end
return name
end
function ChainItemMixin:GetLink(database, item)
return self:GetTarget(database, item):GetLink();
end
function ChainItemMixin:IsMajor(database, item)
if item.major ~= nil then
return item.major;
end
return self:GetTarget(database, item):IsMajor();
end
function ChainItemMixin:GetButtonImage(database, item)
if item.buttonImage == nil then
return self:GetTarget(database, item):GetButtonImage();
end
if type(item.buttonImage) ~= "table" then
return item.buttonImage
end
return item.buttonImage.texture, unpack(item.buttonImage.texCoords)
end
function ChainItemMixin:GetListImage(database, item)
if item.listImage == nil then
return self:GetTarget(database, item):GetListImage();
end
if type(item.listImage) ~= "table" then
return item.listImage
end
return item.listImage.texture, unpack(item.listImage.texCoords)
end
function ChainItemMixin:GetItem(database, item, index, character)
return self:GetTarget(database, item):GetItem(index, character);
end
function ChainItemMixin:GetNumItems(database, item)
return self:GetTarget(database, item):GetNumItems();
end
function ChainItemMixin:OnClick(database, item, character, button, frame, tooltip)
if ChatEdit_TryInsertChatLink(self:GetLink(database, item)) then
return
end
local userdata = self:GetUserdata(database, item)
if frame:SelectFromLink(self:GetLink(database, item), userdata and userdata.scrollTo) then
return
end
tooltip:Hide()
end
function ChainItemMixin:OnEnter(database, item, character, button, frame, tooltip)
local userdata = self:GetUserdata(database, item)
local link = userdata and userdata.link or self:GetLink(database, item)
tooltip:SetPoint("TOPLEFT", button, "TOPRIGHT");
tooltip:SetOwner(button, "ANCHOR_PRESERVE");
tooltip:SetHyperlink(link, character);
end
function ChainItemMixin:OnLeave(database, item, character, button, frame, tooltip)
tooltip:Hide()
end
function ChainItemMixin:IsEmbed(database, item)
return item.embed
end
function ChainItemMixin:IsCompleted(database, item, character, ...)
if item.completed ~= nil then
return ItemMixin.IsCompleted(self, database, item, character);
end
local uptoType = type(item.upto)
if uptoType == "table" then
elseif uptoType == "number" then
return character:IsQuestCompleted(item.upto)
end
return TargetItemMixin.IsCompleted(self, database, item, character, ...)
end
local MissionItemMixin = CreateFromMixins(TargetItemMixin);
function MissionItemMixin:IsBreadcrumb()
return true
end
local NPCItemMixin = CreateFromMixins(TargetItemMixin);
function NPCItemMixin:GetTargetType()
return "npc"
end
function NPCItemMixin:GetName(database, item, character)
return string.format(L["BTWQUESTS_GO_TO"], TargetItemMixin.GetName(self, database, item, character))
end
function NPCItemMixin:IsBreadcrumb(database, item, character)
if item.breadcrumb ~= nil then
return item.breadcrumb;
end
return true
end
function NPCItemMixin:GetLocation(database, item, ...)
if item.locations ~= nil then
return BtWQuests_GetBestLocation(database, item.locations, ...)
end
local target = self:GetTarget(database, item);
if target then
return target:GetLocation(...);
end
return nil;
end
function NPCItemMixin:OnClick(database, item, character, button, frame, tooltip)
local mapID, coords = self:GetLocation(database, item)
if mapID and coords then
BtWQuests_ShowMapWithWaypoint(mapID, coords.x, coords.y, self:GetName(database, item))
end
end
local KillItemMixin = CreateFromMixins(NPCItemMixin);
function KillItemMixin:GetName(database, item, character)
return string.format(L["BTWQUESTS_KILL"], TargetItemMixin.GetName(self, database, item, character))
end
local TalkItemMixin = CreateFromMixins(NPCItemMixin);
function TalkItemMixin:GetName(database, item, character)
return string.format(L["BTWQUESTS_TALK_TO"], TargetItemMixin.GetName(self, database, item, character))
end
local ObjectItemMixin = CreateFromMixins(NPCItemMixin);
function ObjectItemMixin:GetTargetType()
return "object"
end
local LootItemMixin = CreateFromMixins(ObjectItemMixin);
function LootItemMixin:GetName(database, item, character)
return string.format(L["BTWQUESTS_LOOT"], TargetItemMixin.GetName(self, database, item, character))
end
local LevelItemMixin = CreateFromMixins(ItemMixin);
function LevelItemMixin:GetName(database, item, character)
if item.name then
return ItemMixin.GetName(self, database, item, character);
end
return string.format(L["LEVEL_TO"], item.level);
end
function LevelItemMixin:IsActive(database, item, character)
if self:IsCompleted(database, item, character) then
return false;
end
return true
end
function LevelItemMixin:IsCompleted(database, item, character)
if item.atmost then
return character:AtmostLevel(item.level);
else
return character:AtleastLevel(item.level);
end
end
local ExperienceItemMixin = CreateFromMixins(ItemMixin);
function ExperienceItemMixin:GetName(database, item, character)
if item.name then
return ItemMixin.GetName(self, database, item, character);
end
local amount;
if item.amounts then
local level = math.min(math.max(character:GetLevel(), item.minLevel), item.maxLevel) - item.minLevel + 1
if level > #item.amounts then
if character:GetLevel() > OUTDATED_LEVEL and item.outdated then
amount = item.outdated;
else
amount = item.amounts[#item.amounts];
end
else
amount = item.amounts[level];
end
else
amount = item.amount;
end
local modifier = 1 + character:GetXPModifier();
if character:IsWarModeDesired() and not item.noWarModeBonus then
modifier = modifier + (character:GetWarModeRewardBonus() * 0.01);
end
return format(GAIN_EXPERIENCE, math.floor(amount * modifier + .5))
end
function ExperienceItemMixin:Visible(database, item, character)
return character:GetLevel() < MAX_PLAYER_LEVEL
end
function ExperienceItemMixin:IsActive(database, item, character)
return true
end
function ExperienceItemMixin:IsCompleted(database, item, character)
return false
end
local Races = {}
for i=1,100 do
local race = C_CreatureInfo.GetRaceInfo(i);
if race then
Races[race.clientFileString] = race
end
end
local RaceItemMixin = CreateFromMixins(ItemMixin);
function RaceItemMixin:GetName(database, item, character, variation)
local name
if item.name then
name = ItemMixin.GetName(self, database, item, character);
else
local race = Races[item.id] or C_CreatureInfo.GetRaceInfo(item.id);
name = race and race.raceName
end
return name
end
function RaceItemMixin:IsCompleted(database, item, character)
if item.id then
return character:IsRace(item.id);
else
return character:InRaces(item.ids);
end
end
local ClassItemMixin = CreateFromMixins(ItemMixin);
function ClassItemMixin:GetName(dcatabase, item, character, variation)
local name
if item.name then
name = ItemMixin.GetName(self, database, item, character);
else
local class = C_CreatureInfo.GetClassInfo(item.id);
name = class and class.className
end
return name
end
function ClassItemMixin:IsCompleted(database, item, character)
if item.id then
return character:IsClass(item.id);
else
return character:InClasses(item.ids);
end
end
local FactionItemMixin = CreateFromMixins(ItemMixin);
function FactionItemMixin:GetName(database, item, character, variation)
local name
if item.name then
name = ItemMixin.GetName(self, database, item, character);
elseif item.id == "Horde" then
name = FACTION_HORDE
elseif item.id == "Alliance" then
name = FACTION_ALLIANCE
end
return name
end
function FactionItemMixin:IsCompleted(database, item, character)
return character:IsFaction(item.id);
end
local ReputationItemMixin = CreateFromMixins(ItemMixin);
function ReputationItemMixin:GetName(database, item, character, variation)
local name
if item.name then
name = ItemMixin.GetName(self, database, item, character);
end
local factionName, standing, barMin, _, value = (character or BtWQuestsCharacters:GetPlayer()):GetFactionInfoByID(item.id)
if factionName == nil then
factionName = L["UNKNOWN"]
end
if item.standing == nil then
if item.amount ~= nil then
local amount = item.amount
-- if character:IsRace(BTWQUESTS_RACE_ID_HUMAN) then
-- amount = amount * 1.1
-- end
name = string.format(L["REPUTATION_WITH"], amount, factionName)
elseif variation == "reward" or variation == "prerequisite" then
name = string.format(L["BTWQUESTS_PREFIX"], L["BTWQUESTS_FACTION"], factionName)
else
name = factionName;
end
else
local gender = character:GetSex()
local standingText = getglobal("FACTION_STANDING_LABEL" .. item.standing .. (gender == 3 and "_FEMALE" or ""))
if item.amount ~= nil then
name = string.format(name or L["BTWQUESTS_REPUTATION_AMOUNT_STANDING"], item.amount, standingText, factionName)
else
name = string.format(name or L["BTWQUESTS_REPUTATION_STANDING"], standingText, factionName)
end
end
return name
end
function ReputationItemMixin:IsActive(database, item, character)
assert(character ~= nil);
if self:IsCompleted(database, item, character) then
return false;
end
if item.active ~= nil then
return database:EvalRequirement(item.active, self, character, true);
end
return true
end
function ReputationItemMixin:IsCompleted(database, item, character)
local function Callback(id, item, character)
local factionName, standing, barMin, _, value = character:GetFactionInfoByID(item.id)
if standing == nil then
return false
elseif item.amount ~= nil then
return standing > item.standing or (standing == item.standing and value - barMin >= item.amount)
else
return standing >= item.standing
end
end
return StatusCompleted(item, character, Callback)
end
local FriendshipItemMixin = CreateFromMixins(ItemMixin);
function FriendshipItemMixin:GetName(database, item, character)
local name
if item.name then
name = ItemMixin.GetName(self, database, item, character);
end
return name
end
function FriendshipItemMixin:IsActive(database, item, character)
assert(character ~= nil);
if self:IsCompleted(database, item, character) then
return false;
end
if item.active ~= nil then
return database:EvalRequirement(item.active, self, character, true);
end
return true
end
function FriendshipItemMixin:IsCompleted(database, item, character)
local factionInfo = character:GetFriendshipReputation(item.id)
return (factionInfo and factionInfo.standing or 0) >= item.amount
end
local AchievementItemMixin = CreateFromMixins(ItemMixin);
function AchievementItemMixin:GetName(database, item, character)
if item.name then
return ItemMixin.GetName(self, database, item, character);
end
local id = self:GetID(database, item);
if item.criteria then
return select(1, GetAchievementCriteriaInfo(id, item.criteria))
elseif item.criteriaId then
return select(1, GetAchievementCriteriaInfoByID(id, item.criteriaId))
elseif item.reward then
return select(11, GetAchievementInfo(id))
else
return select(2, GetAchievementInfo(id))
end
end
function AchievementItemMixin:IsActive(database, item, character)
return true
end
function AchievementItemMixin:IsCompleted(database, item, character)
if item.criteria then
if item.completed == false then
return not select(3, character:GetAchievementCriteriaInfo(item.id, item.criteria))
else
return select(3, character:GetAchievementCriteriaInfo(item.id, item.criteria))
end
elseif item.criteriaId then
if item.completed == false then
return not select(3, character:GetAchievementCriteriaInfoByID(item.id, item.criteriaId))
else
return select(3, character:GetAchievementCriteriaInfoByID(item.id, item.criteriaId))
end
elseif item.anyone then
if item.completed == false then
return not select(4, GetAchievementInfo(item.id))
else
return select(4, GetAchievementInfo(item.id))
end
else
if item.completed == false then
return not select(13, character:GetAchievementInfo(item.id))
else
return select(13, character:GetAchievementInfo(item.id))
end
end
end
local MoneyItemMixin = CreateFromMixins(ItemMixin);
function MoneyItemMixin:GetName(database, item, character)
if item.name then
return ItemMixin.GetName(self, database, item, character);
end
if item.amounts then
local level = math.min(math.max(character:GetLevel(), item.minLevel), item.maxLevel) - item.minLevel + 1
return GetCoinTextureString(item.amounts[level])
end
return GetCoinTextureString(item.amount)
end
function MoneyItemMixin:IsCompleted(database, item, character)
return false
end
function MoneyItemMixin:IsActive(database, item, character)
return true
end
local CurrencyItemMixin = CreateFromMixins(ItemMixin);
function CurrencyItemMixin:GetName(database, item, character)
if item.name then
return ItemMixin.GetName(self, database, item, character);
end
local name = L["BTWQUESTS_CURRENCY"];
local info = C_CurrencyInfo.GetBasicCurrencyInfo(item.id, item.amount);
return format(name, info.icon, info.displayAmount, info.name);
end
function CurrencyItemMixin:IsCompleted(database, item, character)
return character:GetCurrencyQuantity(item.id) >= item.amount
end
function CurrencyItemMixin:IsActive(database, item, character)
return true
end
local TimeItemMixin = CreateFromMixins(ItemMixin);
function TimeItemMixin:GetName(database, item, character)
if item.name then
return ItemMixin.GetName(self, database, item, character);
end
local total,days,hours,minutes,seconds = difftime(item.time, GetServerTime())
if total <= 0 then
return L["BTWQUESTS_PASSED"]
end
days = floor(total / 86400)
total = total % 86400
hours = floor(total / 3600)
total = total % 3600
minutes = floor(total / 60)
seconds = total % 60
if days ~= nil and days ~= 0 then
return string.format(L["BTWQUESTS_COUNTDOWN_DHM"], days, hours, minutes)
elseif hours ~= nil and hours ~= 0 then
return string.format(L["BTWQUESTS_COUNTDOWN_HMS"], hours, minutes, seconds)
elseif minutes ~= nil and minutes ~= 0 then
return string.format(L["BTWQUESTS_COUNTDOWN_MS"], minutes, seconds)
else
return string.format(L["BTWQUESTS_COUNTDOWN_S"], seconds)
end
end
function TimeItemMixin:IsCompleted(database, item, character)
return GetServerTime() >= item.time
end
function TimeItemMixin:IsActive(database, item, character)
return true
end
local TimeZoneItemMixin = CreateFromMixins(ItemMixin);
function TimeZoneItemMixin:IsCompleted(database, item, character)
return BtWQuests_GetTimeZone(character:GetRealm()) == item.timezone
end
local CoordsItemMixin = CreateFromMixins(ItemMixin);
function CoordsItemMixin:IsBreadcrumb(database, item, character)
if item.breadcrumb ~= nil then
return item.breadcrumb;
end
return true
end
function CoordsItemMixin:GetLocation(database, item, relativeMapID, ...)
if item.locations ~= nil then
return BtWQuests_GetBestLocation(database, item.locations, relativeMapID, ...)
end
if relativeMapID == nil or item.mapID == relativeMapID then
return item.mapID, CreateVector2D(item.x, item.y)
else
local sourceMapID, sourceCoords = self:GetLocation(database, item)
if sourceCoords == nil then
return nil
end
local continentID, coords = C_Map.GetWorldPosFromMapPos(sourceMapID, sourceCoords)
if coords == nil then
return nil
end
local _, coords = C_Map.GetMapPosFromWorldPos(continentID, coords, relativeMapID)
if coords == nil then
return nil
end
return relativeMapID, coords
end
end
function CoordsItemMixin:OnClick(database, item, character, button, frame, tooltip)
local mapID, coords = self:GetLocation(database, item)
if mapID and coords then
BtWQuests_ShowMapWithWaypoint(mapID, coords.x, coords.y, self:GetName(database, item))
end
end
local PetItemMixin = CreateFromMixins(ItemMixin);
function PetItemMixin:GetName(database, item, character, variation)
if item.name then
return ItemMixin.GetName(self, database, item, character);
end
local id = self:GetID(database, item)
local name = C_PetJournal.GetPetInfoBySpeciesID(id) or ""
if variation == "reward" or variation == "prerequisite" then
return string.format(L["BTWQUESTS_PREFIX"], L["BTWQUESTS_PET"], name)
elseif item.status == 'summon' then
return string.format(L["BTWQUESTS_SUMMON"], name)
else
return name
end
end
function PetItemMixin:IsCompleted(database, item, character)
return StatusCompleted(item, character, CheckPetStatus)
end
local MountItemMixin = CreateFromMixins(ItemMixin);
function MountItemMixin:GetName(database, item, character, variation)
if item.name then
return ItemMixin.GetName(self, database, item, character);
end
local id = self:GetID(database, item)
local name = C_MountJournal.GetMountInfoByID(id) or ""
if variation == "reward" then
return string.format(L["BTWQUESTS_PREFIX"], L["BTWQUESTS_MOUNT"], name)
else
return name
end
end
function MountItemMixin:IsCompleted(database, item, character)
return StatusCompleted(item, character, CheckMountStatus)
end
local ToyItemMixin = CreateFromMixins(ItemMixin);
function ToyItemMixin:GetName(database, item, character, variation)
if item.name then
return ItemMixin.GetName(self, database, item, character);
end
local name = select(2, C_ToyBox.GetToyInfo(item.id or item.ids[1])) or ""
if variation == "reward" then
return format(L["BTWQUESTS_PREFIX"], L["BTWQUESTS_TOY"], name)
else
return name
end
end
function ToyItemMixin:IsCompleted(database, item, character)
return StatusCompleted(item, character, PlayerHasToy)
end
local AuraItemMixin = CreateFromMixins(ItemMixin);
function AuraItemMixin:IsCompleted(database, item, character)
local id = self:GetID(database, item);
local index = 1
local name, _, count, _, _, _, _, _, _, spellId = UnitAura("player", index)
while name do
if spellId == id then
return true
end
index = index + 1
name, _, count, _, _, _, _, _, _, spellId = UnitAura("player", index)
end
end
local ProfessionItemMixin = CreateFromMixins(ItemMixin);
function ProfessionItemMixin:GetName(database, item, character, variation)
if item.name then
return ItemMixin.GetName(self, database, item, character);
end
local id = self:GetID(database, item)
local name = C_TradeSkillUI.GetTradeSkillDisplayName(id)
if item.level then
return string.format(L["BTWQUESTS_SKILL_LEVEL"], item.level, name or id)
else
return name or id
end
end
function ProfessionItemMixin:IsCompleted(database, item, character)
local id = self:GetID(database, item)
if item.level then
local level, maxLevel = character:GetSkillInfo(id)
return level >= item.level
else
local level, maxLevel = character:GetSkillInfo(id)
if level ~= 0 then
return true
else
return character:HasProfession(id) -- Fallback
end
end
end
local ItemItemMixin = CreateFromMixins(ItemMixin);
function ItemItemMixin:GetName(database, item, character, variation)
if item.name then
return ItemMixin.GetName(self, database, item, character);
end
local id = self:GetID(database, item);
local name = GetItemInfo(id);
if variation == "reward" then
return name or L["UNKNOWN"];
else
return string.format(L["BTWQUESTS_COLLECT"], name or L["UNKNOWN"]);
end
end
function ItemItemMixin:IsCompleted(database, item, character)
if character:IsPlayer() then
return StatusCompleted(item, character, CheckItemStatus);
end
end
local EquippedItemMixin = CreateFromMixins(ItemMixin);
function EquippedItemMixin:GetName(database, item, character, variation)
if item.name then
return ItemMixin.GetName(self, database, item, character);
end
local id = self:GetID(database, item);
local name = GetItemInfo(id);
return string.format(L["BTWQUESTS_EQUIP"], name);
end
function EquippedItemMixin:IsCompleted(database, item, character)
if character:IsPlayer() then
return StatusCompleted(item, character, IsEquippedItem);
end
end
local QuestLineItemMixin = CreateFromMixins(ItemMixin);
function QuestLineItemMixin:IsCompleted(database, item, character)
if item.questID and (character:IsQuestActive(item.questID) or character:IsQuestCompleted(item.questID)) then
return true
end
if character:IsPlayer() then
local questLines = C_QuestLine.GetAvailableQuestLines(item.mapID)
for _,questLine in ipairs(questLines) do
if questLine.questLineID == item.id then
if item.questID == nil then
return true
else
return questLine.questID == item.questID
end
end
end
end
return false
end
local FollowerItemMixin = CreateFromMixins(ItemMixin);
function FollowerItemMixin:GetName(database, item, character)
local follower = C_Garrison.GetFollowerInfo(item.id)
return follower and follower.name
end
function FollowerItemMixin:IsCompleted(database, item, character)
return false
end
local GarrisonTalentTreeItemMixin = CreateFromMixins(ItemMixin);
function GarrisonTalentTreeItemMixin:GetName(database, item, character)
local info = C_Garrison.GetTalentTreeInfo(item.id)
if item.rank then
return string.format(L["RANK"], info and info.title or L["UNKNOWN"], item.rank)
else
return info and info.title or L["UNKNOWN"]
end
end
function GarrisonTalentTreeItemMixin:IsActive(database, item, character)
local treeInfo = C_Garrison.GetTalentTreeInfo(item.id);
for _,talent in ipairs(treeInfo.talents) do
if talent.tier + 1 == item.rank then
return talent.isBeingResearched
end
end
return false
end
function GarrisonTalentTreeItemMixin:IsCompleted(database, item, character)
local rank = C_Garrison.GetTalentPointsSpentInTalentTree(item.id)
if item.rank then
return item.rank <= rank
else
return rank >= 0
end
end
local GarrisonTalentItemMixin = CreateFromMixins(ItemMixin);
function GarrisonTalentItemMixin:GetName(database, item, character)
local info = C_Garrison.GetTalentInfo(item.id)
if item.rank then
return string.format(L["RESEARCH_RANK"], info and info.name or L["UNKNOWN"], item.rank)
else
return string.format(L["RESEARCH"], info and info.name or L["UNKNOWN"])
end
end
function GarrisonTalentItemMixin:IsActive(database, item, character)
local info = C_Garrison.GetTalentInfo(item.id);
return info.isBeingResearched
end
function GarrisonTalentItemMixin:IsCompleted(database, item, character)
local info = C_Garrison.GetTalentInfo(item.id);
if item.rank then
return item.rank <= info.talentRank
else
return info.researched
end
end
local CampaignItemMixin = CreateFromMixins(ItemMixin);
function CampaignItemMixin:GetName(database, item, character)
local info = C_CampaignInfo.GetCampaignInfo(item.id)
return info and info.name or L["UNKNOWN"]
end
local AreaItemMixin = CreateFromMixins(CoordsItemMixin);
function AreaItemMixin:GetName(database, item, character)
return string.format(L["BTWQUESTS_GO_TO"], (C_Map.GetAreaInfo(item.id)))
end
local ChromieTimeItemMixin = CreateFromMixins(ItemMixin);
function ChromieTimeItemMixin:IsCompleted(database, item, character)
local chromieId = character:GetChromieTimeID()
if item.id then
return item.id == chromieId
else
return chromieId >= 0
end
end
local DatabaseItemMetatable = {};
function DatabaseItemMetatable.__index(tbl, key)
local details;
if tbl.item.type ~= nil then
details = tbl.database.ItemTypes[tbl.item.type];
else
details = ItemMixin;
end
return details[key] and function (self, ...)
return details[key](details, tbl.database, tbl.item, ...);
end
end
local function CreateTable(database, mixin)
local target, sources = {}, {};
setmetatable(target, {
__index = function (self, key)
if key ~= nil then
local tbl;
for _,source in ipairs(sources) do
if source[key] then
tbl = Mixin({database = database, id = key}, source[key], mixin);
break;
end
end
self[key] = tbl;
return tbl;
end
end,
});
return target, sources;
end
local function SplitRanges(...)
local i = 0
local tbl = {...}
return function ()
i = i + 1
local range = tbl[i]
if not range then
return nil
end
local from, to = strsplit("-", range, 2)
if to == nil then
to = from
end
from = tonumber(from)
to = tonumber(to)
assert(from and to, "Range number be number-number")
return from, to
end
end
local function GetRanges(str)
return SplitRanges(strsplit(",", str))
end
local ConditionCacheChildMetatable = {
}
local Database = {};
function Database:Init()
do
local database = self
self.ConditionCache = setmetatable({}, {
__index = function (self, character)
if type(character) == "table" then
local result = setmetatable({}, {
__index = function (self, id)
if type(id) == "number" then
local result = database:GetData("condition", id):EvalFor(character);
self[id] = result
return result
end
end
});
self[character] = result
return result
end
end
});
end
self.DataTypes = {};
self.ItemTypes = {};
self.buckets = {};
self.questCache = {};
self.QuestIDToItem = {};
self.MapIDToItem = {};
self.Continents = {};
end
function Database:RegisterDataType(dataType, mixin)
self.DataTypes[dataType] = mixin;
self[dataType], self[dataType.."List"] = CreateTable(self, mixin);
self[dataType.."Ranges"] = {}
end
function Database:AddData(dataType, id, item)
assert(self[dataType] ~= nil, format("Missing data type %s", dataType));
self[dataType][tonumber(id)] = CreateFromMixins({database = self, id = tonumber(id), items = {}}, item, self.DataTypes[dataType]);
return self[dataType][tonumber(id)];
end
function Database:AddDataTable(dataType, items)
assert(self[dataType] ~= nil, format("Missing data type %s", dataType));
local list = self[dataType.."List"];
list[#list+1] = items;
end
function Database:UpdateDataTable(dataType, items)
assert(self[dataType] ~= nil, format("Missing data type %s", dataType));
local list = self[dataType.."List"];
for _,listItems in pairs(list) do
for itemID,item in pairs(items) do
if listItems[itemID] then
Mixin(listItems[itemID], item);
end
end
end
end
function Database:HasDataType(dataType)
return self[dataType] ~= nil;
end
function Database:GetData(dataType, id)
assert(self[dataType] ~= nil, format("Missing data type %s", dataType));
return self[dataType][tonumber(id)];
end
function Database:AddDataRanges(dataType, str, target)
assert(self[dataType] ~= nil, format("Missing data type %s", dataType));
assert(target ~= nil, format("Must have a target"))
local tbl = self[dataType.."Ranges"];
for from,to in GetRanges(str) do
tbl[#tbl+1] = {from=from,to=to,target=target}
end
table.sort(tbl, function (a, b)
return a.from < b.from
end)
end
function Database:LoadExpansionForDataID(dataType, id)
assert(self[dataType] ~= nil, format("Missing data type %s", dataType));
local tbl = self[dataType.."Ranges"];
id = tonumber(id)
for _,item in ipairs(tbl) do
if item.from <= id and item.to >= id then
self:GetExpansionByID(item.target):Load()
end
end
end
function Database:RegisterItemType(itemType, mixin)
self.ItemTypes[itemType] = mixin;
end
function Database:GetItemType(itemType)
return itemType and self.ItemTypes[itemType] or ItemMixin;
end
function Database:CreateItem(index, item, parent, root)
item.index = index;
item.parent = parent;
item.root = root or parent;
local result = {database = self, item = item};
setmetatable(result, DatabaseItemMetatable);
return result;
end
function Database:GetItemTypeDetails(itemType)
local details = self.ItemTypes[itemType];
assert(details ~= nil, format("Unknown item type %s", itemType));
return details;
end
function Database:IsItemValidForCharacter(item, character) -- In effect its the same as ItemMixin:IsValidForCharacter
if item == nil then
return false;
end
if item.restrictions ~= nil and not self:EvalRequirement(item.restrictions, item, character) then
return false;
end
return true;
end
function Database:FilterItems(items, character, tbl)
tbl = tbl or {}
if items[1] == nil then
if self:IsItemValidForCharacter(items, character) then
tbl[#tbl+1] = items
end
else
for _,item in ipairs(items) do
if self:IsItemValidForCharacter(item, character) then
tbl[#tbl+1] = item
end
end
end
return tbl
end
-- /dump BtWQuestsDatabase:EvalRequirement()
function Database:EvalRequirement(requirement, item, character, one)
if type(requirement) == "boolean" then
return requirement
elseif type(requirement) == "number" then
return self:EvalCondition(requirement, character)
elseif type(requirement) == "function" then
return self:EvalRequirement(requirement(item, character), item, character)
elseif type(requirement) == "table" then
if requirement[1] ~= nil then
one = one and true or false -- Should we only require 1 item to be true
local filtered = {}
for _, v in ipairs(requirement) do
if self:IsItemValidForCharacter(v, character) then
table.insert(filtered, v)
end
end
for _, v in ipairs(filtered) do
if self:EvalItemRequirement(v, character) == one then
return one
end
end
return not one
else
return self:EvalItemRequirement(requirement, character)
end
end
assert(requirement == nil, "Invalid requirement type " .. type(requirement))
end
function Database:EvalItemRequirement(item, character)
local item = GetVariation(self, item, character)
if item.type == "quest" then
local ids = item.ids or {item.id}
local amount = 0
for _,id in ipairs(ids) do
if CheckQuestStatus(id, item, character) then
amount = amount + 1
end
end
return CheckStatusCount(amount, item)
elseif item.type == "chain" then
local ids = item.ids or {item.id}
local amount = 0
for _,id in ipairs(ids) do
if CheckChainStatus(id, item, character) then
amount = amount + 1
end
end
return CheckStatusCount(amount, item)
elseif item.type == "category" then
local ids = item.ids or {item.id}
local amount = 0
for _,id in ipairs(ids) do
if CheckCategoryStatus(id, item, character) then
amount = amount + 1
end
end
return CheckStatusCount(amount, item)
elseif item.type ~= nil then
assert(self.ItemTypes[item.type] ~= nil, format("Unknown type %s", item.type));
return self.ItemTypes[item.type]:IsCompleted(self, item, character);
else
return self:EvalRequirement(item.onEval or item.completed, item, character)
end
end
function Database:EvalText(text, item, character)
local result = text;
if text == nil then
result = "Unnamed"
elseif type(text) == "function" then
result = self:EvalText(text(item, character), item, character)
elseif type(text) == "table" then
if text[1] ~= nil then
result = "Unnamed"
for _,t in ipairs(text) do
if self:IsItemValidForCharacter(t, character) then
result = self:EvalItemName(t, character)
break;
end
end
else
result = self:EvalItemName(text, character)
end
end
return result ~= nil and tostring(result) or nil;
end
function Database:EvalItemName(item, character)
if item == nil then
return "Unnamed"
end
if item.name then
return self:EvalText(item.name, item, character)
end
if self.DataTypes[item.type] then
local item = self:GetData(item.type, item.id or item.ids[1]);
if item == nil then
return "Unnamed"
end
return item:GetName();
end
assert(self.ItemTypes[item.type] ~= nil, format("Unknown type %s", item.type));
return self.ItemTypes[item.type]:GetName(self, item, character);
end
function Database:AddCondition(id, item)
return self:AddData("condition", id, item);
end
function Database:AddConditionTable(items)
self:AddDataTable("condition", items);
end
function Database:GetConditionByID(id)
return self:GetData("condition", id);
end
function Database:EvalCondition(id, character)
return self.ConditionCache[character][id];
end
do
local eventHandler = CreateFrame("Frame")
eventHandler:SetScript("OnEvent", function ()
wipe(Database.ConditionCache[BtWQuestsCharacters:GetPlayer()])
end)
function Database:RegisterConditionClearCacheEvent(event)
eventHandler:RegisterEvent(event)
end
end
-- Expansion
function Database:AddExpansion(id, item)
local expansion = self:GetData("expansion", id);
if not expansion then
if item.name == nil then
item.name = L['BTWQUESTS_EXPANSION_NAME' .. id];
end
expansion = self:AddData("expansion", id, item);
end
return expansion;
end
function Database:AddExpansionsTable(items)
self:AddDataTable("expansion", items);
end
function Database:GetExpansionByID(id)
return self:GetData("expansion", id);
end
function Database:AddExpansionItem(id, t)
local expansion = self:GetData("expansion", id);
table.insert(expansion.items, t);
end
function Database:AddExpansionItems(id, t)
for _,v in ipairs(t) do
self:AddExpansionItem(id, v);
end
end
function Database:HasMultipleExpansion()
local first = next(self.expansion)
return first ~= nil and next(self.expansion, first) ~= nil
end
function Database:GetFirstExpansion()
return (select(2, next(self.expansion)))
end
function Database:GetLoadedExpansion()
local loadedExpansion = nil
for i=0,LE_EXPANSION_LEVEL_CURRENT do
local expansion = self:GetExpansionByID(i);
if expansion and expansion:IsLoaded() then
loadedExpansion = loadedExpansion == nil and expansion or false
end
end
return loadedExpansion
end
function Database:HasExpansion(id)
local expansion = self:GetData("expansion", id);
return expansion ~= nil -- and expansion.items ~= nil and #expansion.items > 0;
end
function Database:GetExpansionList()
local items = {}
for i=0,LE_EXPANSION_LEVEL_CURRENT do
local expansion = self:GetExpansionByID(i);
if expansion then
items[#items+1] = expansion;
end
end
return items
end
local chromieTimeExpansionMap = {
[5] = 0,
[6] = 1,
[7] = 2,
[8] = 4,
[9] = 5,
[10] = 6,
}
function Database:GetBestExpansionForCharacter(character)
local first = next(self.expansion)
if next(self.expansion, first) == nil then
return first
end
-- Do fancy chromie time stuff here
local chromieTimeID = character:GetChromieTimeID()
local expansion = chromieTimeExpansionMap[chromieTimeID]
if self:HasExpansion(expansion) then
return expansion
end
-- Not in chromie time so use player level
local playerLevel = character:GetLevel()
expansion = GetExpansionForLevel(playerLevel)
if self:HasExpansion(expansion) then
return expansion
end
-- Find the best expansion closest to the players current level
for variance = 1,GetNumExpansions()-1 do
if expansion+variance < GetNumExpansions() and self:HasExpansion(expansion+variance) then
return expansion+variance
end
if expansion-variance >= 0 and self:HasExpansion(expansion-variance) then
return expansion-variance
end
end
return first
end
function Database:AddCategory(id, item)
return self:AddData("category", id, item);
end
function Database:AddCategoriesTable(items)
self:AddDataTable("category", items);
end
function Database:GetCategoryByID(id)
return self:GetData("category", id);
end
function Database:IsCategoryActive(id, character)
local item = self:GetCategoryByID(id)
if item == nil then
return nil
end
return item:IsActive(character)
end
function Database:IsCategoryCompleted(id, character)
local item = self:GetCategoryByID(id)
if item == nil then
return nil
end
return item:IsCompleted(character)
end
function Database:GetCategoryName(id)
if not id then
return nil;
end
local item = self:GetCategoryByID(id);
if not item then
return nil;
end
return item:GetName();
end
function Database:LoadCategory(id)
self:LoadExpansionForDataID("category", id)
return self:GetData("category", id);
end
function Database:AddCategoryRanges(str, target)
self:AddDataRanges("category", str, target)
end
function Database:AddChain(id, item)
return self:AddData("chain", id, item);
end
function Database:AddChainsTable(items)
self:AddDataTable("chain", items);
end
function Database:GetChainByID(id)
return self:GetData("chain", id);
end
function Database:IsChainActive(chainID, character)
local chain = self:GetChainByID(chainID)
if chain == nil then
return nil
end
return chain:IsActive(character)
end
function Database:IsChainCompleted(chainID, character)
local chain = self:GetChainByID(chainID)
if chain == nil then
return nil
end
return chain:IsCompleted(character)
end
function Database:GetChainName(id)
if not id then
return nil;
end
local item = self:GetChainByID(id);
if not item then
return nil;
end
return item:GetName();
end
function Database:LoadChain(id)
self:LoadExpansionForDataID("chain", id)
return self:GetData("chain", id);
end
function Database:AddChainRanges(str, target)
self:AddDataRanges("chain", str, target)
end
function Database:AddQuest(id, item)
return self:AddData("quest", id, item);
end
function Database:AddQuestsTable(items)
self:AddDataTable("quest", items);
end
function Database:UpdateQuestsTable(items)
self:UpdateDataTable("quest", items);
end
function Database:GetQuestByID(id)
return self:GetData("quest", id);
end
function Database:GetQuestName(id)
if not id then
return nil;
end
local quest = self:GetQuestByID(id);
if not quest then
return nil;
end
return quest:GetName();
end
function Database:AddQuestItem(id, t, replace)
if self.QuestIDToItem[id] == nil or replace then
self.QuestIDToItem[id] = {}
end
table.insert(self.QuestIDToItem[id], t)
end
function Database:AddQuestItems(id, t, replace)
if self.QuestIDToItem[id] == nil or replace then
self.QuestIDToItem[id] = {}
end
for _,v in ipairs(t) do
self:AddQuestItem(id, v)
end
end
function Database:AddQuestItemsForOtherChain(chainID, otherChain, replace)
local chain = self:GetChainByID(otherChain)
assert(chain ~= nil)
local items = chain.items
local index = 1
local item = items[index]
while item do
if item[1] ~= nil then
for _,subitem in ipairs(item) do
local target = {
type = "chain",
id = chainID,
restrictions = subitem.restrictions
}
local ids = subitem.ids or {subitem.id}
for _,id in ipairs(ids) do
self:AddQuestItem(id, target, replace)
end
end
elseif item.type == "quest" then
local target = {
type = "chain",
id = chainID,
restrictions = item.restrictions
}
local ids = item.ids or {item.id}
for _,id in ipairs(ids) do
self:AddQuestItem(id, target, replace)
end
end
index = index + 1
item = items[index]
end
end
function Database:AddQuestItemsForChain(chainID, replace)
local chain = self:GetChainByID(chainID)
assert(chain ~= nil)
local items = chain.items
local index = 1
local item = items[index]
while item do
if item[1] ~= nil then
for _,subitem in ipairs(item) do
local target = {
type = "chain",
id = chainID,
restrictions = subitem.restrictions
}
local ids = subitem.ids or {subitem.id}
for _,id in ipairs(ids) do
self:AddQuestItem(id, target, replace)
end
end
elseif item.type == "quest" then
local target = {
type = "chain",
id = chainID,
restrictions = item.restrictions
}
local ids = item.ids or {item.id}
for _,id in ipairs(ids) do
self:AddQuestItem(id, target, replace)
end
end
index = index + 1
item = items[index]
end
end
function Database:GetQuestItem(questID, character)
questID = tonumber(questID)
local item = self.QuestIDToItem[questID]
if item == nil then
return nil
end
for i = 1,#item do
if self:IsValidForCharacter(item[i], character) then
return self:CreateItem(0, item[i]);
end
end
return nil
end
function Database:AddMission(id, item)
return self:AddData("mission", id, item);
end
function Database:AddMissionsTable(items)
self:AddDataTable("mission", items);
end
function Database:UpdateMissionsTable(items)
self:UpdateDataTable("mission", items);
end
function Database:GetMissionByID(id)
return self:GetData("mission", id);
end
function Database:GetMissionName(id)
if not id then
return nil;
end
local item = self:GetMissionByID(id);
if not item then
return nil;
end
return item:GetName();
end
function Database:AddNPC(id, item)
return self:AddData("npc", id, item);
end
function Database:AddNPCsTable(items)
self:AddDataTable("npc", items);
end
function Database:UpdateNPCsTable(items)
self:UpdateDataTable("npc", items);
end
function Database:GetNPCByID(id)
return self:GetData("npc", id);
end
function Database:AddObject(id, item)
return self:AddData("object", id, item);
end
function Database:AddObjectsTable(items)
self:AddDataTable("object", items);
end
function Database:UpdateObjectsTable(items)
self:UpdateDataTable("object", items);
end
function Database:GetObjectByID(id)
return self:GetData("object", id);
end
-- Search
function Database:AddSearchBucket(key, t)
if self.buckets[key] == nil then
self.buckets[key] = {}
end
local u = self.buckets[key]
for _,v in ipairs(t) do
u[#u+1] = v
-- table.insert(u,v)
end
end
function Database:AddSearchBuckets(t)
for k,v in pairs(t) do
self:AddSearchBucket(k,v)
end
end
function Database:SearchScore(a, b)
local startChar, endChar = b:find(a)
if not startChar then
return 0
end
return (endChar - startChar + 1) / b:len()
end
function Database:SearchTargetExists(item)
local _, itemType, itemID = strsplit(':', item.link)
if itemType == "expansion" then
return self:GetExpansionByID(tonumber(itemID)) ~= nil
elseif itemType == "category" then
return self:GetCategoryByID(tonumber(itemID)) ~= nil
elseif itemType == "chain" then
return self:GetChainByID(tonumber(itemID)) ~= nil
end
return true
end
local keywords, results, keywordCharacters = {}, {}, {}
function Database:Search(tbl, query, character)
local tbl = tbl or {};
local query = string.gsub(query:lower(), "[,.?:;!'\"%-%(%)]", "")
if self.questCache[character] == nil then
self.questCache[character] = {}
end
if self.questCache[character][query] ~= nil then
for _,v in ipairs(self.questCache[character][query]) do
tbl[#tbl+1] = v;
end
return tbl;
end
local prefixlist
local start = ""
for character in gmatch(query, "[\32-\127\192-\247][\128-\191]*") do
start = start .. character
if self.buckets[start] ~= nil then
prefixlist = self.buckets[start]
break
end
end
wipe(keywords);
for keyword in string.gmatch(query, "[^%s]+") do
keywords[#keywords+1] = keyword
end
wipe(results);
if prefixlist == nil then
return results
end
local item
for i=1,#prefixlist do
item = prefixlist[i]
if results[item] == nil and self:IsItemValidForCharacter(item, character) and self:SearchTargetExists(item) then
results[item] = 0
end
end
local keyword, result
for k=1,#keywords do
keyword = keywords[k]
-- Filter items based on other keywords
for item in pairs(results) do
if item.keywords == nil and item.name ~= nil then
item.keywords = gsub(lower(item.name), "[,.?:;!'\"%-%(%)]", "")
end
if type(item.keywords) == "string" then
local keywords = item.keywords
result = {};
for keyword in gmatch(keywords, "[^%s]+") do
wipe(keywordCharacters)
for character in gmatch(keyword, "[\32-\127\192-\247][\128-\191]*") do
keywordCharacters[#keywordCharacters + 1] = character
end
for i=1,#keywordCharacters do
for j=#keywordCharacters,i,-1 do
local word = table.concat(keywordCharacters, "", i, j)
result[word] = (result[word] or 0) + ((j - i + 1) / #keywordCharacters) / ((result[word] or 0) + 1)
end
end
end
item.keywords = result;
end
results[item] = results[item] + (item.keywords[keyword] or 0)
end
end
wipe(tbl);
for item,score in pairs(results) do
if score > 0 then
tbl[#tbl+1] = {
name = item.name,
item = item,
score = score,
}
end
end
table.sort(tbl, function (a, b)
if a.score == b.score then
return (a.item.type or "") < (b.item.type or "")
end
return a.score > b.score
end)
self.questCache[character][query] = {unpack(tbl)}
return tbl
end
-- Map
function Database:AddMap(id, t, replace)
if t[1] ~= nil then
for _,v in ipairs(t) do
self:AddMap(id, v)
end
else
if self.MapIDToItem[id] ~= nil then
if replace then
self.MapIDToItem[id] = {t}
else
if self.MapIDToItem[id][1] == nil then
self.MapIDToItem[id] = {self.MapIDToItem[id]}
end
table.insert(self.MapIDToItem[id], t)
end
else
self.MapIDToItem[id] = t
end
end
end
function Database:AddMapRecursive(id, t, force, replace)
self:AddMap(id, t, replace)
local children = C_Map.GetMapChildrenInfo(id, nil, true) or {}
for _,map in ipairs(children) do
if force or self.MapIDToItem[map.mapID] == nil then
self:AddMap(map.mapID, t, replace)
end
end
end
function Database:GetMapItemByID(mapID, character)
local item = self.MapIDToItem[mapID]
if item == nil then
return nil
end
if item[1] ~= nil then
for i = 1, #item do
if self:IsItemValidForCharacter(item[i], character) then
return item[i]
end
end
elseif self:IsItemValidForCharacter(item, character) then
return item
end
return nil
end
-- Adds quest chains to a list based on the continent they are on, later used to display map indicators
function Database:AddContinentItem(id, t)
local mapInfo = C_Map.GetMapInfo(id)
while mapInfo and mapInfo.mapType ~= Enum.UIMapType.Continent and mapInfo.mapType ~= Enum.UIMapType.World and mapInfo.mapType ~= Enum.UIMapType.Cosmic do
id = mapInfo.parentMapID
mapInfo = C_Map.GetMapInfo(id)
end
assert(mapInfo ~= nil, string.format("Continent %d doesnt exist", id))
if self.Continents[id] == nil then
self.Continents[id] = {t}
else
table.insert(self.Continents[id], t)
end
end
function Database:AddContinentItems(id, t)
local mapInfo = C_Map.GetMapInfo(id)
while mapInfo and mapInfo.mapType ~= Enum.UIMapType.Continent and mapInfo.mapType ~= Enum.UIMapType.World and mapInfo.mapType ~= Enum.UIMapType.Cosmic do
id = mapInfo.parentMapID
mapInfo = C_Map.GetMapInfo(id)
end
assert(mapInfo ~= nil, string.format("Continent %d doesnt exist", id))
if self.Continents[id] == nil then
self.Continents[id] = {}
end
for _,item in ipairs(t) do
if item.type == "chain" then
assert(self:GetChainByID(item.id) ~= nil, string.format("Chain %d doesnt exist", item.id))
end
table.insert(self.Continents[id], item)
end
end
function Database:GetAvailableMapItems(mapID, character)
local continentID = mapID
local mapInfo = C_Map.GetMapInfo(continentID);
while mapInfo and mapInfo.mapType ~= Enum.UIMapType.Continent and mapInfo.mapType ~= Enum.UIMapType.World and mapInfo.mapType ~= Enum.UIMapType.Cosmic do
continentID = mapInfo.parentMapID
mapInfo = C_Map.GetMapInfo(continentID);
end
local result = {}
if mapInfo and self.Continents[continentID] then
local items = self.Continents[continentID]
for _,item in ipairs(items) do
local chain = self:GetChainByID(item.id)
assert(chain ~= nil, string.format("Missing chain %d on map %d within continent %d", item.id, mapID, continentID))
if chain:IsValidForCharacter(character) and not chain:IsCompleted(character) and (chain:IsAvailable(character) or chain:IsActive(character)) then
local item = chain:GetNextItem(character)
if item and not item:IsActive(character) then
local source = item:GetSource(character)
if source ~= nil then
local _, coords = source:GetLocation(mapID)
if coords ~= nil then
local x, y = coords:GetXY()
if x >= 0 and x <= 1 and y >= 0 and y <= 1 then -- Within the map
table.insert(result, {
chainID = chain:GetID(),
chainName = chain:GetName(),
itemName = item:GetName(character),
x = x,
y = y,
})
end
end
end
end
end
end
end
return result
end
-- Achievement
function BtWQuests_GetAchievementName(achievementID)
return select(2, GetAchievementInfo(achievementID))
end
-- Achievement info isnt always loaded so sometimes need to delay reading achievement names
function BtWQuests_GetAchievementNameDelayed(achievementID)
return function ()
return select(2, GetAchievementInfo(achievementID))
end
end
-- The first return value for GetAchievementCriteriaInfo is the name anyway so no need for the overhead of using another function
BtWQuests_GetAchievementCriteriaName = GetAchievementCriteriaInfo
-- Achievement info isnt always loaded so sometimes need to delay reading achievement names
function BtWQuests_GetAchievementCriteriaNameDelayed(achievementID, criteriaIndex)
return function ()
return BtWQuests_GetAchievementCriteriaName(achievementID, criteriaIndex)
end
end
function BtWQuests_GetAchievementCriteriaFullName(achievementID, criteriaIndex)
return string.format("%s: %s", BtWQuests_GetAchievementName(achievementID), BtWQuests_GetAchievementCriteriaName(criteriaIndex))
end
-- Achievement info isnt always loaded so sometimes need to delay reading achievement names
function BtWQuests_GetAchievementCriteriaFullNameDelayed(achievementID, criteriaIndex)
return function ()
return BtWQuests_GetAchievementCriteriaFullName(achievementID, criteriaIndex)
end
end
function BtWQuests.GetAreaName(areaID)
return C_Map.GetAreaInfo(areaID)
end
function BtWQuests.GetMapName(mapID)
return ((C_Map.GetMapInfo(mapID) or {}).name or "Unnamed")
end
BtWQuests_GetAreaName = BtWQuests.GetAreaName;
BtWQuests_GetMapName = BtWQuests.GetMapName;
Database.ItemMixin = ItemMixin
Database:Init();
Database:RegisterDataType("condition", ConditionMixin);
Database:RegisterDataType("expansion", ExpansionMixin);
Database:RegisterDataType("category", CategoryMixin);
Database:RegisterDataType("chain", ChainMixin);
Database:RegisterDataType("quest", QuestMixin);
Database:RegisterDataType("npc", NPCMixin);
Database:RegisterDataType("object", ObjectMixin);
Database:RegisterDataType("mission", MissionMixin);
Database:RegisterItemType("header", HeaderItemMixin);
Database:RegisterItemType("quest", QuestItemMixin);
Database:RegisterItemType("expansion", ExpansionItemMixin);
Database:RegisterItemType("category", CategoryItemMixin);
Database:RegisterItemType("chain", ChainItemMixin);
Database:RegisterItemType("mission", MissionItemMixin);
Database:RegisterItemType("npc", NPCItemMixin);
Database:RegisterItemType("kill", KillItemMixin);
Database:RegisterItemType("talk", TalkItemMixin);
Database:RegisterItemType("object", ObjectItemMixin);
Database:RegisterItemType("loot", LootItemMixin);
Database:RegisterItemType("level", LevelItemMixin);
Database:RegisterItemType("experience", ExperienceItemMixin);
Database:RegisterItemType("race", RaceItemMixin);
Database:RegisterItemType("class", ClassItemMixin);
Database:RegisterItemType("faction", FactionItemMixin);
Database:RegisterItemType("reputation", ReputationItemMixin);
Database:RegisterItemType("friendship", FriendshipItemMixin);
Database:RegisterItemType("achievement", AchievementItemMixin);
Database:RegisterItemType("money", MoneyItemMixin);
Database:RegisterItemType("currency", CurrencyItemMixin);
Database:RegisterItemType("time", TimeItemMixin);
Database:RegisterItemType("timezone", TimeZoneItemMixin);
Database:RegisterItemType("coords", CoordsItemMixin);
Database:RegisterItemType("pet", PetItemMixin);
Database:RegisterItemType("mount", MountItemMixin);
Database:RegisterItemType("toy", ToyItemMixin);
Database:RegisterItemType("aura", AuraItemMixin);
Database:RegisterItemType("profession", ProfessionItemMixin);
Database:RegisterItemType("item", ItemItemMixin);
Database:RegisterItemType("equipped", EquippedItemMixin);
Database:RegisterItemType("questline", QuestLineItemMixin);
Database:RegisterItemType("follower", FollowerItemMixin);
Database:RegisterItemType("garrisontalenttree", GarrisonTalentTreeItemMixin);
Database:RegisterItemType("garrisontalent", GarrisonTalentItemMixin);
Database:RegisterItemType("campaign", CampaignItemMixin);
Database:RegisterItemType("spell", ItemMixin); -- Is just used to track with rewards spells are used
Database:RegisterItemType("area", AreaItemMixin);
Database:RegisterItemType("chromietime", ChromieTimeItemMixin);
Database:AddCondition(-1, { type = "chromietime" });
Database:AddCondition(923, { type = "faction", id = "Horde" });
Database:AddCondition(924, { type = "faction", id = "Alliance" });
Database:RegisterConditionClearCacheEvent("QUEST_ACCEPTED")
Database:RegisterConditionClearCacheEvent("QUEST_AUTOCOMPLETE")
Database:RegisterConditionClearCacheEvent("QUEST_COMPLETE")
Database:RegisterConditionClearCacheEvent("QUEST_FINISHED")
Database:RegisterConditionClearCacheEvent("QUEST_TURNED_IN")
Database:RegisterConditionClearCacheEvent("QUEST_WATCH_LIST_CHANGED")
Database:RegisterConditionClearCacheEvent("QUEST_WATCH_UPDATE")
if INTERFACE_NUMBER >= 70200 then
Database:RegisterConditionClearCacheEvent("QUEST_LOG_CRITERIA_UPDATE")
end
if C_QuestSession then
Database:RegisterConditionClearCacheEvent("QUEST_SESSION_JOINED")
Database:RegisterConditionClearCacheEvent("QUEST_SESSION_LEFT")
end
BtWQuestsDatabase = Database;
BtWQuests.Database = Database;