6d86a784e7
QuestieArrow._CollectQuestTargets checks quest.isComplete (the quest object field) to decide whether to collect finisher spawns or objective spawns. Without setting this field, the arrow fell through to objective collection and picked up the stale Cold Iron Key fake objective (CheckQuestSourceItem) which resolves to NPC 29323 at 39.8, 72.7. Now when QuestieDB.IsComplete returns 1, set quest.isComplete = true and quest.WasComplete = true before PopulateObjectiveNotes, so the arrow correctly routes to the finisher (Gretchen Fizzlespark) instead.
1729 lines
71 KiB
Lua
1729 lines
71 KiB
Lua
--- COMPATIBILITY ---
|
|
local IsQuestFlaggedCompleted = QuestieCompat.IsQuestFlaggedCompleted or C_QuestLog.IsQuestFlaggedCompleted
|
|
|
|
---@class QuestieQuest
|
|
local QuestieQuest = QuestieLoader:CreateModule("QuestieQuest")
|
|
---@type QuestieQuestPrivate
|
|
QuestieQuest.private = QuestieQuest.private or {}
|
|
local _QuestieQuest = QuestieQuest.private
|
|
-------------------------
|
|
--Import modules.
|
|
-------------------------
|
|
---@type QuestieProfessions
|
|
local QuestieProfessions = QuestieLoader:ImportModule("QuestieProfessions")
|
|
---@type QuestieReputation
|
|
local QuestieReputation = QuestieLoader:ImportModule("QuestieReputation")
|
|
---@type QuestieTooltips
|
|
local QuestieTooltips = QuestieLoader:ImportModule("QuestieTooltips")
|
|
---@type QuestieTracker
|
|
local QuestieTracker = QuestieLoader:ImportModule("QuestieTracker")
|
|
---@type QuestieDBMIntegration
|
|
local QuestieDBMIntegration = QuestieLoader:ImportModule("QuestieDBMIntegration")
|
|
---@type QuestieMap
|
|
local QuestieMap = QuestieLoader:ImportModule("QuestieMap")
|
|
---@type QuestieLib
|
|
local QuestieLib = QuestieLoader:ImportModule("QuestieLib")
|
|
---@type QuestiePlayer
|
|
local QuestiePlayer = QuestieLoader:ImportModule("QuestiePlayer")
|
|
---@type TaskQueue
|
|
local TaskQueue = QuestieLoader:ImportModule("TaskQueue")
|
|
---@type QuestieDB
|
|
local QuestieDB = QuestieLoader:ImportModule("QuestieDB")
|
|
---@type ZoneDB
|
|
local ZoneDB = QuestieLoader:ImportModule("ZoneDB")
|
|
---@type QuestieCombatQueue
|
|
local QuestieCombatQueue = QuestieLoader:ImportModule("QuestieCombatQueue")
|
|
---@type QuestieAnnounce
|
|
local QuestieAnnounce = QuestieLoader:ImportModule("QuestieAnnounce")
|
|
---@type QuestieMenu
|
|
local QuestieMenu = QuestieLoader:ImportModule("QuestieMenu")
|
|
---@type l10n
|
|
local l10n = QuestieLoader:ImportModule("l10n")
|
|
---@type QuestLogCache
|
|
local QuestLogCache = QuestieLoader:ImportModule("QuestLogCache")
|
|
---@type ThreadLib
|
|
local ThreadLib = QuestieLoader:ImportModule("ThreadLib")
|
|
---@type AvailableQuests
|
|
local AvailableQuests = QuestieLoader:ImportModule("AvailableQuests")
|
|
|
|
--- COMPATIBILITY ---
|
|
local C_Timer = QuestieCompat.C_Timer
|
|
local GetQuestsCompleted = QuestieCompat.GetQuestsCompleted
|
|
local xpcall = QuestieCompat.xpcall
|
|
|
|
--We should really try and squeeze out all the performance we can, especially in this.
|
|
local tostring = tostring;
|
|
local tinsert = table.insert;
|
|
local pairs = pairs;
|
|
local ipairs = ipairs;
|
|
local yield = coroutine.yield
|
|
local NewThread = ThreadLib.ThreadSimple
|
|
|
|
local NOP_FUNCTION = function()
|
|
end
|
|
local ERR_FUNCTION = function(err)
|
|
print(err)
|
|
print(debugstack())
|
|
end
|
|
|
|
-- forward declaration
|
|
local _UnloadAlreadySpawnedIcons
|
|
local _RegisterObjectiveTooltips, _DetermineIconsToDraw, _GetIconsSortedByDistance
|
|
local _DrawObjectiveIcons, _DrawObjectiveWaypoints
|
|
|
|
local HBD = QuestieCompat.HBD or LibStub("HereBeDragonsQuestie-2.0")
|
|
|
|
function QuestieQuest:Initialize()
|
|
Questie:Debug(Questie.DEBUG_INFO, "[QuestieQuest]: Getting all completed quests")
|
|
Questie.db.char.complete = GetQuestsCompleted()
|
|
|
|
QuestieProfessions:Update()
|
|
QuestieReputation:Update(true)
|
|
end
|
|
|
|
---@param category AutoBlacklistString
|
|
function QuestieQuest.ResetAutoblacklistCategory(category)
|
|
Questie:Debug(Questie.DEBUG_SPAM, "[QuestieQuest]: Resetting autoblacklist category", category)
|
|
for questId, questCategory in pairs(QuestieDB.autoBlacklist) do
|
|
if questCategory == category then
|
|
QuestieDB.autoBlacklist[questId] = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
function QuestieQuest:ToggleNotes(showIcons)
|
|
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieQuest:ToggleNotes] showIcons:", showIcons)
|
|
QuestieQuest:GetAllQuestIds() -- add notes that weren't added from previous hidden state
|
|
|
|
if showIcons then
|
|
_QuestieQuest:ShowQuestIcons()
|
|
_QuestieQuest:ShowManualIcons()
|
|
else
|
|
_QuestieQuest:HideQuestIcons()
|
|
_QuestieQuest:HideManualIcons()
|
|
end
|
|
end
|
|
|
|
function _QuestieQuest:ShowQuestIcons()
|
|
local trackerHiddenQuests = Questie.db.char.TrackerHiddenQuests
|
|
for questId, frameList in pairs(QuestieMap.questIdFrames) do
|
|
if (not trackerHiddenQuests) or (not trackerHiddenQuests[questId]) then -- Skip quests which are completely hidden from the Tracker menu
|
|
for _, frameName in pairs(frameList) do -- this may seem a bit expensive, but its actually really fast due to the order things are checked
|
|
---@type IconFrame
|
|
local icon = _G[frameName];
|
|
if not icon.data then
|
|
error("Desync! Icon has not been removed correctly, but has already been reset. Skipping frame \"" ..
|
|
frameName .. "\" for quest " .. questId)
|
|
else
|
|
local objectiveString = tostring(questId) .. " " .. tostring(icon.data.ObjectiveIndex)
|
|
if (not Questie.db.char.TrackerHiddenObjectives) or (not Questie.db.char.TrackerHiddenObjectives[objectiveString]) then
|
|
if icon ~= nil and icon.hidden and (not icon:ShouldBeHidden()) then
|
|
icon:FakeShow()
|
|
|
|
if icon.data.lineFrames then
|
|
for _, lineIcon in pairs(icon.data.lineFrames) do
|
|
lineIcon:FakeShow()
|
|
end
|
|
end
|
|
end
|
|
if (icon.data.QuestData.FadeIcons or (icon.data.ObjectiveData and icon.data.ObjectiveData.FadeIcons)) and icon.data.Type ~= "complete" then
|
|
icon:FadeOut()
|
|
else
|
|
icon:FadeIn()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function _QuestieQuest:ShowManualIcons()
|
|
for _, frameList in pairs(QuestieMap.manualFrames) do
|
|
for _, frameName in pairs(frameList) do
|
|
local icon = _G[frameName];
|
|
if icon ~= nil and icon.hidden and (not icon:ShouldBeHidden()) then -- check for function to make sure its a frame
|
|
icon:FakeShow()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function _QuestieQuest:HideQuestIcons()
|
|
for _, frameList in pairs(QuestieMap.questIdFrames) do
|
|
for _, frameName in pairs(frameList) do -- this may seem a bit expensive, but its actually really fast due to the order things are checked
|
|
local icon = _G[frameName];
|
|
if icon ~= nil and (not icon.hidden) and icon:ShouldBeHidden() then -- check for function to make sure its a frame
|
|
-- Hides Objective Icons
|
|
icon:FakeHide()
|
|
|
|
-- Hides Objective Tooltips
|
|
QuestieTooltips:RemoveQuest(icon.data.Id)
|
|
|
|
if icon.data.lineFrames then
|
|
for _, lineIcon in pairs(icon.data.lineFrames) do
|
|
lineIcon:FakeHide()
|
|
end
|
|
end
|
|
end
|
|
if (icon.data.QuestData.FadeIcons or (icon.data.ObjectiveData and icon.data.ObjectiveData.FadeIcons)) and icon.data.Type ~= "complete" then
|
|
icon:FadeOut()
|
|
else
|
|
icon:FadeIn()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function _QuestieQuest:HideManualIcons()
|
|
for _, frameList in pairs(QuestieMap.manualFrames) do
|
|
for _, frameName in pairs(frameList) do
|
|
local icon = _G[frameName];
|
|
if icon ~= nil and (not icon.hidden) and icon:ShouldBeHidden() then -- check for function to make sure its a frame
|
|
icon:FakeHide()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function QuestieQuest:ClearAllNotes()
|
|
for questId in pairs(QuestiePlayer.currentQuestlog) do
|
|
local quest = QuestieDB.GetQuest(questId)
|
|
|
|
if not quest then
|
|
return
|
|
end
|
|
|
|
for _, s in pairs(quest.Objectives) do
|
|
s.AlreadySpawned = {}
|
|
end
|
|
|
|
if next(quest.SpecialObjectives) then
|
|
for _, s in pairs(quest.SpecialObjectives) do
|
|
s.AlreadySpawned = {}
|
|
end
|
|
end
|
|
end
|
|
|
|
for _, frameList in pairs(QuestieMap.questIdFrames) do
|
|
for _, frameName in pairs(frameList) do
|
|
local icon = _G[frameName]
|
|
if icon and icon.Unload then
|
|
icon:Unload()
|
|
end
|
|
end
|
|
end
|
|
|
|
QuestieMap.questIdFrames = {}
|
|
end
|
|
|
|
function QuestieQuest:ClearAllToolTips()
|
|
for questId in pairs(QuestiePlayer.currentQuestlog) do
|
|
local quest = QuestieDB.GetQuest(questId)
|
|
|
|
if not quest then
|
|
return
|
|
end
|
|
|
|
if quest.Objectives then
|
|
for _, objective in pairs(quest.Objectives) do
|
|
if objective.hasRegisteredTooltips then
|
|
objective.hasRegisteredTooltips = false
|
|
end
|
|
|
|
if objective.registeredItemTooltips then
|
|
objective.registeredItemTooltips = false
|
|
end
|
|
end
|
|
end
|
|
|
|
if quest.ObjectiveData then
|
|
for _, objective in pairs(quest.ObjectiveData) do
|
|
if objective.hasRegisteredTooltips then
|
|
objective.hasRegisteredTooltips = false
|
|
end
|
|
|
|
if objective.registeredItemTooltips then
|
|
objective.registeredItemTooltips = false
|
|
end
|
|
end
|
|
end
|
|
|
|
if next(quest.SpecialObjectives) then
|
|
for _, objective in pairs(quest.SpecialObjectives) do
|
|
if objective.hasRegisteredTooltips then
|
|
objective.hasRegisteredTooltips = false
|
|
end
|
|
|
|
if objective.registeredItemTooltips then
|
|
objective.registeredItemTooltips = false
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
QuestieTooltips.lookupByKey = {}
|
|
QuestieTooltips.lookupKeyByQuestId = {}
|
|
end
|
|
|
|
-- This is only needed for SmoothReset(), normally special objectives don't need to update
|
|
---@param questId number
|
|
local function _UpdateSpecials(questId)
|
|
local quest = QuestieDB.GetQuest(questId)
|
|
if quest and next(quest.SpecialObjectives) then
|
|
for _, objective in pairs(quest.SpecialObjectives) do
|
|
local result, err = xpcall(QuestieQuest.PopulateObjective, ERR_FUNCTION, QuestieQuest, quest, 0, objective,
|
|
true)
|
|
if not result then
|
|
Questie:Error("[QuestieQuest]: [SpecialObjectives] " ..
|
|
l10n("There was an error populating objectives for %s %s %s %s", quest.name or "No quest name",
|
|
quest.Id or "No quest id", 0 or "No objective", err or "No error"));
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function QuestieQuest:SmoothReset()
|
|
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieQuest:SmoothReset]")
|
|
if QuestieQuest._isResetting then
|
|
QuestieQuest._resetAgain = true
|
|
return
|
|
end
|
|
QuestieQuest._isResetting = true
|
|
QuestieQuest._resetNeedsAvailables = false
|
|
|
|
-- bit of a hack (there has to be a better way to do logic like this
|
|
QuestieDBMIntegration:ClearAll()
|
|
local stepTable = {
|
|
function()
|
|
-- Wait until game cache has quest log okay.
|
|
return QuestLogCache.TestGameCache()
|
|
end,
|
|
function()
|
|
return #QuestieMap._mapDrawQueue == 0 and
|
|
#QuestieMap._minimapDrawQueue == 0 -- wait until draw queue is finished
|
|
end,
|
|
function()
|
|
QuestieQuest:ClearAllNotes()
|
|
QuestieQuest:ClearAllToolTips()
|
|
return true
|
|
end,
|
|
function()
|
|
QuestieMenu:OnLogin(true) -- remove icons
|
|
return true
|
|
end,
|
|
function()
|
|
return #QuestieMap._mapDrawQueue == 0 and
|
|
#QuestieMap._minimapDrawQueue == 0 -- wait until draw queue is finished
|
|
end,
|
|
function()
|
|
-- reset quest log
|
|
QuestiePlayer.currentQuestlog = {}
|
|
|
|
--- reset the blacklist
|
|
QuestieDB.autoBlacklist = {}
|
|
|
|
-- make sure complete db is correct
|
|
Questie.db.char.complete = GetQuestsCompleted()
|
|
QuestieProfessions:Update()
|
|
QuestieReputation:Update(true)
|
|
|
|
-- populate QuestiePlayer.currentQuestlog
|
|
QuestieQuest:GetAllQuestIdsNoObjectives()
|
|
QuestieQuest._nextRestQuest = next(QuestiePlayer.currentQuestlog)
|
|
return true
|
|
end,
|
|
function()
|
|
QuestieMenu:OnLogin()
|
|
return true
|
|
end,
|
|
function()
|
|
QuestieQuest._resetNeedsAvailables = true
|
|
AvailableQuests.CalculateAndDrawAll(function() QuestieQuest._resetNeedsAvailables = false end)
|
|
return true
|
|
end,
|
|
function()
|
|
for _ = 1, 64 do
|
|
if QuestieQuest._nextRestQuest then
|
|
QuestieQuest:UpdateQuest(QuestieQuest._nextRestQuest)
|
|
_UpdateSpecials(QuestieQuest._nextRestQuest)
|
|
QuestieQuest._nextRestQuest = next(QuestiePlayer.currentQuestlog, QuestieQuest._nextRestQuest)
|
|
else
|
|
QuestieCombatQueue:Queue(function()
|
|
C_Timer.After(2.0, function()
|
|
QuestieTracker:Update()
|
|
end)
|
|
end)
|
|
break
|
|
end
|
|
end
|
|
return not QuestieQuest._nextRestQuest
|
|
end,
|
|
function()
|
|
return (not QuestieQuest._resetNeedsAvailables) and #QuestieMap._mapDrawQueue == 0 and
|
|
#QuestieMap._minimapDrawQueue == 0
|
|
end,
|
|
function()
|
|
QuestieQuest._isResetting = nil
|
|
if QuestieQuest._resetAgain then
|
|
QuestieQuest._resetAgain = nil
|
|
QuestieQuest:SmoothReset()
|
|
end
|
|
return true
|
|
end
|
|
}
|
|
local step = 1
|
|
local ticker
|
|
ticker = C_Timer.NewTicker(0.01, function()
|
|
if stepTable[step]() then
|
|
step = step + 1
|
|
if not stepTable[step] then
|
|
ticker:Cancel()
|
|
end
|
|
end
|
|
if QuestieQuest._resetAgain and not QuestieQuest._resetNeedsAvailables then -- we can stop the current reset
|
|
ticker:Cancel()
|
|
QuestieQuest._resetAgain = nil
|
|
QuestieQuest._isResetting = nil
|
|
QuestieQuest:SmoothReset()
|
|
end
|
|
end)
|
|
end
|
|
|
|
---@param questId number
|
|
---@return boolean
|
|
function QuestieQuest:ShouldShowQuestNotes(questId)
|
|
if not Questie.db.profile.hideUntrackedQuestsMapIcons then
|
|
return true
|
|
end
|
|
|
|
local autoWatch = Questie.db.profile.autoTrackQuests
|
|
local trackedAuto = autoWatch and
|
|
(not Questie.db.char.AutoUntrackedQuests or not Questie.db.char.AutoUntrackedQuests[questId])
|
|
local trackedManual = not autoWatch and (Questie.db.char.TrackedQuests and Questie.db.char.TrackedQuests[questId])
|
|
return trackedAuto or trackedManual
|
|
end
|
|
|
|
function QuestieQuest:HideQuest(id)
|
|
Questie.db.char.hidden[id] = true
|
|
QuestieMap:UnloadQuestFrames(id)
|
|
QuestieTooltips:RemoveQuest(id)
|
|
end
|
|
|
|
function QuestieQuest:UnhideQuest(id)
|
|
Questie.db.char.hidden[id] = nil
|
|
AvailableQuests.CalculateAndDrawAll()
|
|
end
|
|
|
|
local allianceTournamentMarkerQuests = { [13684] = true, [13685] = true, [13688] = true, [13689] = true, [13690] = true,
|
|
[13593] = true, [13703] = true, [13704] = true, [13705] = true, [13706] = true }
|
|
local hordeTournamentMarkerQuests = { [13691] = true, [13693] = true, [13694] = true, [13695] = true, [13696] = true,
|
|
[13707] = true, [13708] = true, [13709] = true, [13710] = true, [13711] = true }
|
|
|
|
---@param questId number
|
|
function QuestieQuest:AcceptQuest(questId)
|
|
local quest = QuestieDB.GetQuest(questId)
|
|
|
|
if quest then
|
|
local complete = QuestieDB.IsComplete(questId)
|
|
-- If any of these flags exsist then this quest has already once been accepted and is probobly in a failed state
|
|
if (quest.WasComplete or quest.isComplete or complete == 0 or complete == -1) and (QuestiePlayer.currentQuestlog[questId]) then
|
|
Questie:Debug(Questie.DEBUG_INFO, "[QuestieQuest] Accepted Quest:", questId,
|
|
" Warning: This quest was once accepted and needs to be reset.")
|
|
|
|
-- Reset quest log
|
|
QuestiePlayer.currentQuestlog[questId] = nil
|
|
|
|
-- Reset quest objectives
|
|
quest.Objectives = {}
|
|
|
|
-- Reset quest flags
|
|
quest.WasComplete = nil
|
|
quest.isComplete = nil
|
|
|
|
-- Reset tooltips
|
|
QuestieTooltips:RemoveQuest(questId)
|
|
end
|
|
|
|
if not QuestiePlayer.currentQuestlog[questId] then
|
|
Questie:Debug(Questie.DEBUG_INFO, "[QuestieQuest] Accepted Quest:", questId)
|
|
|
|
QuestiePlayer.currentQuestlog[questId] = quest
|
|
|
|
if allianceTournamentMarkerQuests[questId] then
|
|
Questie.db.char.complete[13686] = true -- Alliance Tournament Eligibility Marker
|
|
elseif hordeTournamentMarkerQuests[questId] then
|
|
Questie.db.char.complete[13687] = true -- Horde Tournament Eligibility Marker
|
|
end
|
|
|
|
TaskQueue:Queue(
|
|
-- Get all the Frames for the quest and unload them, the available quest icon for example.
|
|
function() QuestieMap:UnloadQuestFrames(questId) end,
|
|
-- Make sure there isn't any lingering tooltip data hanging around in the quest table.
|
|
function() QuestieTooltips:RemoveQuest(questId) end,
|
|
function()
|
|
-- Re-accepted quest can be collapsed. Expand it. Especially dailies.
|
|
if Questie.db.char.collapsedQuests then
|
|
Questie.db.char.collapsedQuests[questId] = nil
|
|
end
|
|
-- Re-accepted quest can be untracked. Clear it. Especially timed quests.
|
|
if Questie.db.char.AutoUntrackedQuests[questId] then
|
|
Questie.db.char.AutoUntrackedQuests[questId] = nil
|
|
end
|
|
end,
|
|
function() QuestieQuest:PopulateQuestLogInfo(quest) end,
|
|
function()
|
|
-- This needs to happen after QuestieQuest:PopulateQuestLogInfo because that is the place where quest.Objectives is generated
|
|
Questie:SendMessage("QC_ID_BROADCAST_QUEST_UPDATE", questId)
|
|
end,
|
|
function() QuestieQuest:PopulateObjectiveNotes(quest) end,
|
|
function() AvailableQuests.CalculateAndDrawAll() end,
|
|
function()
|
|
QuestieCombatQueue:Queue(function()
|
|
QuestieTracker:Update()
|
|
end)
|
|
end
|
|
)
|
|
else
|
|
Questie:Debug(Questie.DEBUG_INFO, "[QuestieQuest] Accepted Quest:", questId,
|
|
" Warning: Quest already exists, not adding")
|
|
end
|
|
end
|
|
end
|
|
|
|
local allianceChampionMarkerQuests = { [13699] = true, [13713] = true, [13723] = true, [13724] = true, [13725] = true }
|
|
local hordeChampionMarkerQuests = { [13726] = true, [13727] = true, [13728] = true, [13729] = true, [13731] = true }
|
|
|
|
---@param questId number
|
|
function QuestieQuest:CompleteQuest(questId)
|
|
-- Skip quests which are turn in only and are not added to the quest log in the first place
|
|
local questLogEntry = QuestiePlayer.currentQuestlog[questId]
|
|
if questLogEntry then
|
|
-- Only reset flags if the entry is a table (not a legacy number)
|
|
if type(questLogEntry) == "table" then
|
|
questLogEntry.WasComplete = nil
|
|
questLogEntry.isComplete = nil
|
|
end
|
|
QuestiePlayer.currentQuestlog[questId] = nil;
|
|
end
|
|
|
|
-- Only quests that are daily quests or aren't repeatable should be marked complete,
|
|
-- otherwise objectives for repeatable quests won't track correctly - #1433
|
|
if QuestieCompat.Is335 then
|
|
QuestieCompat.SetQuestComplete(questId)
|
|
else
|
|
Questie.db.char.complete[questId] = (not QuestieDB.IsRepeatable(questId)) or QuestieDB.IsDailyQuest(questId) or
|
|
QuestieDB.IsWeeklyQuest(questId);
|
|
end
|
|
|
|
if allianceChampionMarkerQuests[questId] then
|
|
Questie.db.char.complete[13700] = true -- Alliance Champion Marker
|
|
Questie.db.char.complete[13686] = nil -- Alliance Tournament Eligibility Marker
|
|
elseif hordeChampionMarkerQuests[questId] then
|
|
Questie.db.char.complete[13701] = true -- Horde Champion Marker
|
|
Questie.db.char.complete[13687] = nil -- Horde Tournament Eligibility Marker
|
|
end
|
|
QuestieMap:UnloadQuestFrames(questId)
|
|
|
|
-- Clear the pending-complete guard now that frames are unloaded
|
|
if QuestiePlayer.pendingCompleteQuestIds then
|
|
QuestiePlayer.pendingCompleteQuestIds[questId] = nil
|
|
end
|
|
|
|
if (QuestieMap.questIdFrames[questId]) then
|
|
Questie:Error("Just removed all frames but the framelist seems to still be there!", questId)
|
|
end
|
|
|
|
-- Delayed verification to ensure all objective icons are removed
|
|
-- This handles race conditions where AvailableQuests might redraw icons or UnloadQuestFrames misses some
|
|
C_Timer.After(0.5, function()
|
|
if QuestieMap.questIdFrames[questId] then
|
|
Questie:Debug(Questie.DEBUG_INFO, "[QuestieQuest:CompleteQuest] Lingering frames detected for quest:",
|
|
questId, "- forcing cleanup")
|
|
QuestieMap:UnloadQuestFrames(questId)
|
|
end
|
|
end)
|
|
|
|
QuestieTooltips:RemoveQuest(questId)
|
|
QuestieTracker:RemoveQuest(questId)
|
|
QuestieCombatQueue:Queue(function()
|
|
QuestieTracker:Update()
|
|
end)
|
|
|
|
-- TODO: Should this be done first? Because CalculateAndDrawAll looks at QuestieMap.questIdFrames[QuestId] to add available
|
|
AvailableQuests.CalculateAndDrawAll()
|
|
|
|
Questie:Debug(Questie.DEBUG_INFO, "[QuestieQuest] Completed Quest:", questId)
|
|
end
|
|
|
|
---@param questId number
|
|
function QuestieQuest:AbandonedQuest(questId)
|
|
-- NOTE: Some servers remove quests without firing QUEST_REMOVED reliably.
|
|
-- Always cleanup frames/tracker even if the quest isn't currently in QuestiePlayer.currentQuestlog.
|
|
if QuestiePlayer.currentQuestlog then
|
|
QuestiePlayer.currentQuestlog[questId] = nil
|
|
end
|
|
|
|
QuestieMap:UnloadQuestFrames(questId)
|
|
|
|
local quest = QuestieDB.GetQuest(questId)
|
|
if quest then
|
|
-- Reset quest objectives
|
|
quest.Objectives = {}
|
|
|
|
-- Reset quest flags
|
|
quest.WasComplete = nil
|
|
quest.isComplete = nil
|
|
|
|
if allianceTournamentMarkerQuests[questId] then
|
|
Questie.db.char.complete[13686] = nil -- Alliance Tournament Eligibility Marker
|
|
elseif hordeTournamentMarkerQuests[questId] then
|
|
Questie.db.char.complete[13687] = nil -- Horde Tournament Eligibility Marker
|
|
end
|
|
|
|
local childQuests = QuestieDB.QueryQuestSingle(questId, "childQuests")
|
|
if childQuests then
|
|
for _, childQuestId in pairs(childQuests) do
|
|
Questie.db.char.complete[childQuestId] = nil
|
|
QuestLogCache.RemoveQuest(childQuestId)
|
|
end
|
|
end
|
|
end
|
|
|
|
AvailableQuests.UnloadUndoable()
|
|
|
|
QuestieTracker:RemoveQuest(questId)
|
|
QuestieTooltips:RemoveQuest(questId)
|
|
QuestieCombatQueue:Queue(function()
|
|
QuestieTracker:Update()
|
|
end)
|
|
|
|
AvailableQuests.CalculateAndDrawAll()
|
|
|
|
-- Delayed verification to ensure all objective icons are removed
|
|
-- This handles race conditions where QuestieQuest:UpdateQuest might redraw icons
|
|
-- just as we are abandoning the quest
|
|
C_Timer.After(0.5, function()
|
|
if QuestieMap.questIdFrames[questId] then
|
|
Questie:Debug(Questie.DEBUG_INFO, "[QuestieQuest:AbandonedQuest] Lingering frames detected for quest:",
|
|
questId, "- forcing cleanup")
|
|
QuestieMap:UnloadQuestFrames(questId)
|
|
end
|
|
end)
|
|
|
|
Questie:Debug(Questie.DEBUG_INFO, "[QuestieQuest] Abandoned Quest:", questId)
|
|
end
|
|
|
|
---@param questId number
|
|
function QuestieQuest:UpdateQuest(questId)
|
|
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieQuest:UpdateQuest]", questId)
|
|
|
|
---@type Quest
|
|
local quest = QuestieDB.GetQuest(questId)
|
|
|
|
local sourceItemId = (quest and tonumber(quest.sourceItemId)) or 0
|
|
|
|
if quest and (not Questie.db.char.complete[questId] or QuestiePlayer.currentQuestlog[questId]) then
|
|
-- Skip this update if the quest is mid-completion to avoid redrawing objective pins
|
|
-- that CompleteQuest is about to remove via UnloadQuestFrames.
|
|
if QuestiePlayer.pendingCompleteQuestIds and QuestiePlayer.pendingCompleteQuestIds[questId] then
|
|
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieQuest:UpdateQuest] Skipping - quest is pending completion:", questId)
|
|
return
|
|
end
|
|
QuestieQuest:PopulateQuestLogInfo(quest)
|
|
|
|
if QuestieQuest:ShouldShowQuestNotes(questId) then
|
|
QuestieQuest:UpdateObjectiveNotes(quest)
|
|
else
|
|
QuestieTooltips:RemoveQuest(questId)
|
|
end
|
|
|
|
local isComplete = QuestieDB.IsComplete(questId)
|
|
|
|
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieQuest:UpdateQuest] QuestDB:IsComplete() flag is: " .. isComplete)
|
|
|
|
if isComplete == 1 then
|
|
-- Quest is complete
|
|
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieQuest:UpdateQuest] Quest is: Complete!")
|
|
|
|
QuestieMap:UnloadQuestFrames(questId)
|
|
QuestieQuest:AddFinisher(quest)
|
|
quest.WasComplete = true
|
|
elseif isComplete == -1 then
|
|
-- Failed quests should be shown as available again
|
|
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieQuest:UpdateQuest] Quest has: Failed!")
|
|
|
|
QuestieMap:UnloadQuestFrames(questId)
|
|
QuestieTooltips:RemoveQuest(questId)
|
|
AvailableQuests.DrawAvailableQuest(quest)
|
|
|
|
-- Reset any collapsed quest flags
|
|
if Questie.db.char.collapsedQuests then
|
|
Questie.db.char.collapsedQuests[questId] = nil
|
|
end
|
|
elseif isComplete == 0 then
|
|
-- Quest was somehow reset back to incomplete after being completed (quest.WasComplete == true).
|
|
-- The "or" check looks for a sourceItemId then checks to see if it's NOT in the players bag.
|
|
-- Player destroyed quest items? Or some other quest mechanic removed the needed quest item.
|
|
-- Check if all objectives are already complete before treating a missing source item as a reset.
|
|
-- Some quests use consumable key items (e.g. Cold Iron Key for quest 12843). After using the key
|
|
-- the item leaves the bag, making CheckQuestSourceItem return false. Without this guard, Questie
|
|
-- would reset the quest and draw the key-drop NPC on the map even though all objectives are done.
|
|
local allObjectivesComplete = false
|
|
if quest.Objectives and #quest.Objectives > 0 then
|
|
local doneCount = 0
|
|
for i = 1, #quest.Objectives do
|
|
if quest.Objectives[i] and quest.Objectives[i].Completed == true then
|
|
doneCount = doneCount + 1
|
|
end
|
|
end
|
|
allObjectivesComplete = (doneCount == #quest.Objectives)
|
|
end
|
|
|
|
if quest and not allObjectivesComplete and (quest.WasComplete or (sourceItemId > 0 and QuestieQuest:CheckQuestSourceItem(questId) == false)) then
|
|
Questie:Debug(Questie.DEBUG_DEVELOP,
|
|
"[QuestieQuest:UpdateQuest] Quest was once complete or Quest Item(s) were removed. Resetting quest.")
|
|
|
|
-- Reset quest objectives
|
|
quest.Objectives = {}
|
|
|
|
-- Reset quest flags
|
|
quest.WasComplete = nil
|
|
quest.isComplete = nil
|
|
|
|
-- Reset tooltips
|
|
QuestieTooltips:RemoveQuest(questId)
|
|
|
|
QuestieQuest:CheckQuestSourceItem(questId, true)
|
|
QuestieMap:UnloadQuestFrames(questId)
|
|
|
|
-- Reset any collapsed quest flags
|
|
if Questie.db.char.collapsedQuests then
|
|
Questie.db.char.collapsedQuests[questId] = nil
|
|
end
|
|
|
|
QuestieQuest:PopulateQuestLogInfo(quest)
|
|
QuestieQuest:PopulateObjectiveNotes(quest)
|
|
AvailableQuests.CalculateAndDrawAll()
|
|
else
|
|
-- Sometimes objective(s) are all complete but the quest doesn't get flagged as "1". So far the only
|
|
-- quests I've found that does this are quests involving an item(s). Checks all objective(s) and if they
|
|
-- are all complete, simulate a "Complete Quest" so the quest finisher appears on the map.
|
|
if quest.Objectives and #quest.Objectives > 0 then
|
|
local numCompleteObjectives = 0
|
|
|
|
for i = 1, #quest.Objectives do
|
|
if quest.Objectives[i] and quest.Objectives[i].Completed and quest.Objectives[i].Completed == true then
|
|
numCompleteObjectives = numCompleteObjectives + 1
|
|
end
|
|
end
|
|
|
|
if numCompleteObjectives == #quest.Objectives then
|
|
Questie:Debug(Questie.DEBUG_DEVELOP,
|
|
"[QuestieQuest:UpdateQuest] All Quest Objective(s) are Complete! Manually setting quest to Complete!")
|
|
QuestieMap:UnloadQuestFrames(questId)
|
|
QuestieQuest:AddFinisher(quest)
|
|
quest.WasComplete = true
|
|
quest.isComplete = true
|
|
else
|
|
Questie:Debug(Questie.DEBUG_DEVELOP,
|
|
"[QuestieQuest:UpdateQuest] Quest Objective Status is: " ..
|
|
numCompleteObjectives .. ", out of: " .. #quest.Objectives .. ". No updates required.")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
Questie:SendMessage("QC_ID_BROADCAST_QUEST_UPDATE", questId)
|
|
end
|
|
end
|
|
|
|
---@param questId number
|
|
function QuestieQuest:SetObjectivesDirty(questId)
|
|
local quest = QuestieDB.GetQuest(questId)
|
|
|
|
if quest then
|
|
for _, objective in pairs(quest.Objectives) do
|
|
objective.isUpdated = false
|
|
end
|
|
end
|
|
end
|
|
|
|
--Run this if you want to update the entire table
|
|
function QuestieQuest:GetAllQuestIds()
|
|
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieQuest] Getting all quests")
|
|
|
|
QuestiePlayer.currentQuestlog = {}
|
|
|
|
for questId, data in pairs(QuestLogCache.questLog_DO_NOT_MODIFY) do -- DO NOT MODIFY THE RETURNED TABLE
|
|
local quest = QuestieDB.GetQuest(questId)
|
|
|
|
if not quest then
|
|
if not Questie._sessionWarnings[questId] then
|
|
if not Questie.IsSoD then
|
|
Questie:Error(l10n(
|
|
"The quest %s is missing from Questie's database. Please report this on GitHub or Discord!",
|
|
tostring(questId)))
|
|
end
|
|
Questie._sessionWarnings[questId] = true
|
|
end
|
|
|
|
QuestiePlayer.currentQuestlog[questId] = questId -- legacy behavior
|
|
else
|
|
local complete = QuestieDB.IsComplete(questId)
|
|
|
|
QuestiePlayer.currentQuestlog[questId] = quest
|
|
quest.LocalizedName = data.title
|
|
|
|
if complete == -1 then
|
|
QuestieQuest:UpdateQuest(questId)
|
|
else
|
|
-- Only draw the source item objective when the quest is not yet complete.
|
|
-- If the quest is complete (complete == 1), the source item was consumed during
|
|
-- the quest (e.g. Cold Iron Key for quest 12843) and should not draw its drop NPC.
|
|
if complete == 1 then
|
|
-- Mark the quest object as complete so QuestieArrow collects finisher spawns
|
|
-- instead of objective spawns. Without this, the arrow falls through to the
|
|
-- objective collection path and picks up any stale fake item objectives.
|
|
quest.isComplete = true
|
|
quest.WasComplete = true
|
|
else
|
|
QuestieQuest:CheckQuestSourceItem(questId, true)
|
|
end
|
|
QuestieQuest:PopulateQuestLogInfo(quest)
|
|
|
|
if QuestieQuest:ShouldShowQuestNotes(questId) then
|
|
QuestieQuest:PopulateObjectiveNotes(quest)
|
|
else
|
|
QuestieTooltips:RemoveQuest(questId)
|
|
end
|
|
end
|
|
|
|
Questie:Debug(Questie.DEBUG_INFO, "[QuestieQuest] Adding the quest", questId,
|
|
QuestiePlayer.currentQuestlog[questId])
|
|
end
|
|
end
|
|
|
|
QuestieCombatQueue:Queue(function()
|
|
QuestieTracker:Update()
|
|
end)
|
|
end
|
|
|
|
-- This checks and manually adds quest item tooltips for sourceItems
|
|
local function _AddSourceItemObjective(quest)
|
|
local sourceItemId = tonumber(quest.sourceItemId) or 0
|
|
if sourceItemId <= 0 then
|
|
return
|
|
end
|
|
|
|
local questObjectives = QuestieDB.QueryQuestSingle(quest.Id, "objectives")
|
|
local itemObjectives = questObjectives and questObjectives[3]
|
|
|
|
-- If sourceItemId is already part of an item objective, do nothing
|
|
if itemObjectives then
|
|
for _, itemObjectiveIndex in pairs(itemObjectives) do
|
|
for _, itemObjectiveId in pairs(itemObjectiveIndex) do
|
|
if itemObjectiveId == sourceItemId then
|
|
Questie:Debug(Questie.DEBUG_INFO,
|
|
"[QuestieQuest:_AddSourceItemObjective] This item is already part of a quest objective.")
|
|
return
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local itemName = QuestieDB.QueryItemSingle(sourceItemId, "name")
|
|
if not itemName then
|
|
return
|
|
end
|
|
|
|
Questie:Debug(Questie.DEBUG_INFO, "[QuestieQuest:_AddSourceItemObjective] Adding Source Item Id for:", sourceItemId)
|
|
|
|
local fakeObjective = {
|
|
Id = quest.Id,
|
|
IsSourceItem = true,
|
|
QuestData = quest,
|
|
Index = 1,
|
|
Needed = 1,
|
|
Collected = 1,
|
|
text = itemName,
|
|
Description = itemName
|
|
}
|
|
|
|
QuestieTooltips:RegisterObjectiveTooltip(quest.Id, "i_" .. sourceItemId, fakeObjective);
|
|
end
|
|
|
|
-- This checks and manually adds quest item tooltips for SpellItems
|
|
local function _AddSpellItemObjective(quest)
|
|
if not quest.SpellItemId then
|
|
return
|
|
end
|
|
|
|
local questObjectives = QuestieDB.QueryQuestSingle(quest.Id, "objectives")
|
|
local spellObjectives = questObjectives and questObjectives[6]
|
|
if not spellObjectives then
|
|
return
|
|
end
|
|
|
|
local depthIndex = 1 -- TODO: What is better for this?
|
|
local needed = (quest.Objectives and quest.Objectives[depthIndex] and quest.Objectives[depthIndex].Needed) or 0
|
|
local collected = (quest.Objectives and quest.Objectives[depthIndex] and quest.Objectives[depthIndex].Collected) or 0
|
|
local desc = (quest.Objectives and quest.Objectives[depthIndex] and quest.Objectives[depthIndex].Description) or ""
|
|
|
|
local fakeObjective = {
|
|
Id = quest.Id,
|
|
IsSourceItem = true,
|
|
QuestData = quest,
|
|
Index = 1,
|
|
Needed = needed,
|
|
Collected = collected,
|
|
text = nil,
|
|
Description = desc,
|
|
}
|
|
|
|
QuestieTooltips:RegisterObjectiveTooltip(quest.Id, "i_" .. quest.SpellItemId, fakeObjective);
|
|
end
|
|
|
|
|
|
-- This checks and manually adds quest item tooltips for requiredSourceItems
|
|
local function _AddRequiredSourceItemObjective(quest)
|
|
if not quest.requiredSourceItems then
|
|
return
|
|
end
|
|
|
|
local questObjectives = QuestieDB.QueryQuestSingle(quest.Id, "objectives")
|
|
local itemObjectives = questObjectives and questObjectives[3]
|
|
|
|
for index, requiredSourceItemId in pairs(quest.requiredSourceItems) do
|
|
local alreadyInObjectives = false
|
|
|
|
if itemObjectives then
|
|
for _, itemObjectiveIndex in pairs(itemObjectives) do
|
|
for _, itemObjectiveId in pairs(itemObjectiveIndex) do
|
|
if itemObjectiveId == requiredSourceItemId or quest.sourceItemId == requiredSourceItemId then
|
|
Questie:Debug(Questie.DEBUG_INFO,
|
|
"[QuestieQuest:_AddRequiredSourceItemObjective] This item is already part of a quest objective.")
|
|
alreadyInObjectives = true
|
|
break
|
|
end
|
|
end
|
|
if alreadyInObjectives then
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
if not alreadyInObjectives then
|
|
local itemName = QuestieDB.QueryItemSingle(requiredSourceItemId, "name")
|
|
if itemName then
|
|
Questie:Debug(Questie.DEBUG_INFO,
|
|
"[QuestieQuest:_AddRequiredSourceItemObjective] Adding Source Item Id for:", requiredSourceItemId)
|
|
|
|
local fakeObjective = {
|
|
Id = quest.Id,
|
|
IsRequiredSourceItem = true,
|
|
QuestData = quest,
|
|
Index = index,
|
|
text = itemName,
|
|
Description = itemName
|
|
}
|
|
|
|
QuestieTooltips:RegisterObjectiveTooltip(quest.Id, "i_" .. requiredSourceItemId, fakeObjective);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
|
|
function QuestieQuest:GetAllQuestIdsNoObjectives()
|
|
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieQuest] Getting all quests without objectives")
|
|
QuestiePlayer.currentQuestlog = {}
|
|
|
|
for questId, data in pairs(QuestLogCache.questLog_DO_NOT_MODIFY) do -- DO NOT MODIFY THE RETURNED TABLE
|
|
local quest = QuestieDB.GetQuest(questId)
|
|
|
|
if not quest then
|
|
if not Questie._sessionWarnings[questId] then
|
|
if not Questie.IsSoD then
|
|
Questie:Error(l10n(
|
|
"The quest %s is missing from Questie's database. Please report this on GitHub or Discord!",
|
|
tostring(questId)))
|
|
end
|
|
Questie._sessionWarnings[questId] = true
|
|
end
|
|
|
|
QuestiePlayer.currentQuestlog[questId] = questId
|
|
else
|
|
QuestiePlayer.currentQuestlog[questId] = quest
|
|
quest.LocalizedName = data.title
|
|
_AddSourceItemObjective(quest)
|
|
_AddRequiredSourceItemObjective(quest)
|
|
|
|
Questie:Debug(Questie.DEBUG_INFO, "[QuestieQuest] Adding the quest", questId,
|
|
QuestiePlayer.currentQuestlog[questId])
|
|
end
|
|
end
|
|
end
|
|
|
|
-- iterate all notes, update / remove as needed
|
|
---@param quest Quest
|
|
function QuestieQuest:UpdateObjectiveNotes(quest)
|
|
if (not QuestiePlayer.currentQuestlog) or (not QuestiePlayer.currentQuestlog[quest.Id]) then
|
|
return
|
|
end
|
|
|
|
Questie:Debug(Questie.DEBUG_INFO, "[QuestieQuest] UpdateObjectiveNotes:", quest.Id)
|
|
for objectiveIndex, objective in pairs(quest.Objectives) do
|
|
local result, err = xpcall(QuestieQuest.PopulateObjective, ERR_FUNCTION, QuestieQuest, quest, objectiveIndex,
|
|
objective, false)
|
|
if (not result) then
|
|
Questie:Debug(Questie.DEBUG_ELEVATED, "[QuestieQuest] There was an error populating objectives for",
|
|
quest.name, quest.Id, objectiveIndex, err)
|
|
end
|
|
end
|
|
|
|
if next(quest.SpecialObjectives) then
|
|
for _, objective in pairs(quest.SpecialObjectives) do
|
|
local result, err = xpcall(QuestieQuest.PopulateObjective, ERR_FUNCTION, QuestieQuest, quest, 0, objective,
|
|
true)
|
|
if not result then
|
|
Questie:Error("[QuestieQuest]: [SpecialObjectives] " ..
|
|
l10n("There was an error populating objectives for %s %s %s %s", quest.name or "No quest name",
|
|
quest.Id or "No quest id", 0 or "No objective", err or "No error"));
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- This function is used to check the players bags for an item that matches quest.sourceItemId.
|
|
-- A good example for this edge case is [18] The Price of Shoes (118) where upon acceptance, Verner's Note (1283) is given
|
|
-- to the player and the Quest is immediately flagged as Complete. If the note is destroyed then a slightly modified version
|
|
-- of QuestieDB.IsComplete() that uses this function, returns zero allowing the quest updates to properly set the quests state.
|
|
---@param questId number @QuestID
|
|
---@param makeObjective boolean @If set to true, then this will create an incomplete objective for the missing quest item
|
|
---@return boolean @Returns true if quest.sourceItemId matches an item in a players bag
|
|
function QuestieQuest:CheckQuestSourceItem(questId, makeObjective)
|
|
local quest = QuestieDB.GetQuest(questId)
|
|
local sourceItem = true
|
|
|
|
-- Ascension/custom quests may omit sourceItemId (nil). Normalize to 0.
|
|
local sourceItemId = (quest and tonumber(quest.sourceItemId)) or 0
|
|
|
|
if quest and sourceItemId > 0 then
|
|
for bag = -2, 4 do
|
|
local numSlots = QuestieCompat.GetContainerNumSlots(bag) or 0
|
|
for slot = 1, numSlots do
|
|
local itemId = select(10, QuestieCompat.GetContainerItemInfo(bag, slot))
|
|
if itemId == sourceItemId then
|
|
return true
|
|
end
|
|
end
|
|
|
|
sourceItem = false
|
|
end
|
|
|
|
-- If we are missing the sourceItem for zero objective quests then make an objective for it so the
|
|
-- player has a visual indication as to what item is missing and so the quest has a "tag" of some kind.
|
|
-- Also double check the quests leaderboard and make sure an objective doesn't already exist.
|
|
if (not sourceItem) and makeObjective and (not QuestieQuest:GetAllLeaderBoardDetails(quest.Id)[1]) then
|
|
local itemName = QuestieDB.QueryItemSingle(sourceItemId, "name") or ("Item " .. tostring(sourceItemId))
|
|
quest.Objectives = {
|
|
[1] = {
|
|
Description = itemName,
|
|
Type = "item",
|
|
Needed = 1,
|
|
Collected = 0,
|
|
Completed = false,
|
|
Id = sourceItemId,
|
|
questId = quest.Id
|
|
}
|
|
}
|
|
end
|
|
else
|
|
return true
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
local function _GetIconScaleForAvailable()
|
|
return Questie.db.profile.availableScale or 1.3
|
|
end
|
|
|
|
---@param quest Quest
|
|
function QuestieQuest:AddFinisher(quest)
|
|
--We should never ever add the quest if IsQuestFlaggedComplete true.
|
|
local questId = quest.Id
|
|
Questie:Debug(Questie.DEBUG_INFO, "[QuestieQuest] Adding finisher for quest", questId)
|
|
|
|
local complete = QuestieDB.IsComplete(questId)
|
|
|
|
if (QuestiePlayer.currentQuestlog[questId] and (IsQuestFlaggedCompleted(questId) == false) and (complete == 1 or complete == 0) and (not Questie.db.char.complete[questId])) then
|
|
local finisher, key
|
|
|
|
if quest.Finisher ~= nil then
|
|
if quest.Finisher.Type == "monster" then
|
|
finisher = QuestieDB:GetNPC(quest.Finisher.Id)
|
|
key = "m_" .. quest.Finisher.Id
|
|
elseif quest.Finisher.Type == "object" then
|
|
finisher = QuestieDB:GetObject(quest.Finisher.Id)
|
|
key = "o_" .. quest.Finisher.Id
|
|
else
|
|
Questie:Debug(Questie.DEBUG_CRITICAL, "[QuestieQuest] Unhandled finisher type:", quest.Finisher.Type,
|
|
questId, quest.name)
|
|
end
|
|
else
|
|
Questie:Debug(Questie.DEBUG_CRITICAL, "[QuestieQuest] Quest has no finisher:", questId, quest.name)
|
|
end
|
|
|
|
if finisher ~= nil then
|
|
-- Certain race conditions can occur when the NPC/Objects are both the Quest Starter and Quest Finisher
|
|
-- which can result in duplicate Quest Title tooltips appearing. DrawAvailableQuest() would have already
|
|
-- registered this NPC/Object so, the appropriate tooltip lines are already present. This checks and clears
|
|
-- any duplicate keys before registering the Quest Finisher.
|
|
|
|
-- Clear duplicate keys if they exist
|
|
if QuestieTooltips.lookupByKey[key] then
|
|
if QuestieTooltips:GetTooltip(key) ~= nil and #QuestieTooltips:GetTooltip(key) > 1 then
|
|
for ttline = 1, #QuestieTooltips:GetTooltip(key) do
|
|
for index, line in pairs(QuestieTooltips:GetTooltip(key)) do
|
|
if (ttline == index) then
|
|
Questie:Debug(Questie.DEBUG_DEVELOP,
|
|
"[QuestieQuest] AddFinisher - Removing duplicate Quest Title!")
|
|
|
|
-- Remove duplicate Quest Title
|
|
QuestieTooltips.lookupByKey[key][tostring(questId) .. " " .. finisher.name] = nil
|
|
|
|
-- Now check to see if the dup has a Special Objective
|
|
local objText = string.match(line, ".*|cFFcbcbcb.*")
|
|
|
|
if objText then
|
|
local objIndex
|
|
|
|
-- Grab the Special Objective index
|
|
if quest.SpecialObjectives[1] then
|
|
objIndex = quest.SpecialObjectives[1].Index
|
|
end
|
|
|
|
if objIndex then
|
|
Questie:Debug(Questie.DEBUG_DEVELOP,
|
|
"[QuestieQuest] AddFinisher - Removing Special Objective!")
|
|
|
|
-- Remove Special Objective Text
|
|
QuestieTooltips.lookupByKey[key][tostring(questId) .. " " .. objIndex] = nil
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
QuestieTooltips:RegisterQuestStartTooltip(questId, finisher.name, finisher.id, key)
|
|
|
|
local finisherIcons = {}
|
|
local finisherLocs = {}
|
|
|
|
for finisherZone, spawns in pairs(finisher.spawns or {}) do
|
|
if (finisherZone ~= nil and spawns ~= nil) then
|
|
for _, coords in ipairs(spawns) do
|
|
local data = {
|
|
Id = questId,
|
|
Icon = Questie.ICON_TYPE_COMPLETE,
|
|
GetIconScale = _GetIconScaleForAvailable,
|
|
IconScale = _GetIconScaleForAvailable(),
|
|
Type = "complete",
|
|
QuestData = quest,
|
|
Name = finisher.name,
|
|
IsObjectiveNote = false,
|
|
}
|
|
|
|
if QuestieDB.IsActiveEventQuest(quest.Id) then
|
|
data.Icon = Questie.ICON_TYPE_EVENTQUEST_COMPLETE
|
|
elseif QuestieDB.IsPvPQuest(quest.Id) then
|
|
data.Icon = Questie.ICON_TYPE_PVPQUEST_COMPLETE
|
|
elseif quest.IsRepeatable then
|
|
data.Icon = Questie.ICON_TYPE_REPEATABLE_COMPLETE
|
|
end
|
|
|
|
if (coords[1] == -1 or coords[2] == -1) then
|
|
local dungeonLocation = ZoneDB:GetDungeonLocation(finisherZone)
|
|
if dungeonLocation ~= nil then
|
|
for _, value in ipairs(dungeonLocation) do
|
|
local zone = value[1];
|
|
local x = value[2];
|
|
local y = value[3];
|
|
|
|
QuestieMap:DrawWorldIcon(data, zone, x, y)
|
|
end
|
|
end
|
|
else
|
|
local x = coords[1];
|
|
local y = coords[2];
|
|
|
|
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieQuest] Adding world icon as finisher:",
|
|
finisherZone, x, y)
|
|
finisherIcons[finisherZone] = QuestieMap:DrawWorldIcon(data, finisherZone, x, y)
|
|
|
|
if not finisherLocs[finisherZone] then
|
|
finisherLocs[finisherZone] = { x, y }
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if finisher.waypoints then
|
|
for zone, waypoints in pairs(finisher.waypoints) do
|
|
if (not ZoneDB.IsDungeonZone(zone)) then
|
|
if not finisherIcons[zone] and waypoints[1] and waypoints[1][1] and waypoints[1][1][1] then
|
|
local data = {
|
|
Id = questId,
|
|
Icon = Questie.ICON_TYPE_COMPLETE,
|
|
GetIconScale = _GetIconScaleForAvailable,
|
|
IconScale = _GetIconScaleForAvailable(),
|
|
Type = "complete",
|
|
QuestData = quest,
|
|
Name = finisher.name,
|
|
IsObjectiveNote = false,
|
|
}
|
|
|
|
if QuestieDB.IsActiveEventQuest(quest.Id) then
|
|
data.Icon = Questie.ICON_TYPE_EVENTQUEST_COMPLETE
|
|
elseif QuestieDB.IsPvPQuest(quest.Id) then
|
|
data.Icon = Questie.ICON_TYPE_PVPQUEST_COMPLETE
|
|
elseif quest.IsRepeatable then
|
|
data.Icon = Questie.ICON_TYPE_REPEATABLE_COMPLETE
|
|
end
|
|
|
|
finisherIcons[zone] = QuestieMap:DrawWorldIcon(data, zone, waypoints[1][1][1],
|
|
waypoints[1][1][2])
|
|
finisherLocs[zone] = { waypoints[1][1][1], waypoints[1][1][2] }
|
|
end
|
|
|
|
QuestieMap:DrawWaypoints(finisherIcons[zone], waypoints, zone)
|
|
end
|
|
end
|
|
end
|
|
else
|
|
Questie:Debug(Questie.DEBUG_CRITICAL, "[QuestieQuest] finisher or finisher.spawns == nil for questId",
|
|
questId)
|
|
end
|
|
end
|
|
end
|
|
|
|
---@param quest Quest
|
|
---@param objectiveIndex ObjectiveIndex
|
|
---@param objective QuestObjective
|
|
---@param blockItemTooltips any
|
|
function QuestieQuest:PopulateObjective(quest, objectiveIndex, objective, blockItemTooltips) -- must be p-called
|
|
Questie:Debug(Questie.DEBUG_INFO, "[QuestieQuest:PopulateObjective]", objective.Description)
|
|
|
|
if (not objective.Update) then
|
|
Questie:Debug(Questie.DEBUG_INFO, "[QuestieQuest:PopulateObjective] - Quest is already updated. --> Exiting!")
|
|
return
|
|
end
|
|
|
|
objective:Update()
|
|
local completed = objective.Completed
|
|
local objectiveData = quest.ObjectiveData[objective.Index] or
|
|
objective -- the reason for "or objective" is to handle "SpecialObjectives" aka non-listed objectives (demonic runestones for closing the portal)
|
|
|
|
if (not objective.spawnList or (not next(objective.spawnList))) and _QuestieQuest.objectiveSpawnListCallTable[objectiveData.Type] then
|
|
objective.spawnList = _QuestieQuest.objectiveSpawnListCallTable[objectiveData.Type](objective.Id, objective,
|
|
objectiveData);
|
|
end
|
|
|
|
-- Tooltips should always show.
|
|
-- For completed and uncompleted objectives
|
|
_RegisterObjectiveTooltips(objective, quest.Id, blockItemTooltips)
|
|
|
|
if completed then
|
|
_UnloadAlreadySpawnedIcons(objective)
|
|
return
|
|
end
|
|
|
|
if (not objective.Color) then
|
|
objective.Color = QuestieLib:ColorWheel()
|
|
end
|
|
|
|
if objective.spawnList and next(objective.spawnList) then
|
|
local maxPerType = 300
|
|
|
|
if Questie.db.profile.enableIconLimit and Questie.db.profile.iconLimit < maxPerType then
|
|
maxPerType = Questie.db.profile.iconLimit
|
|
end
|
|
|
|
local closestStarter = QuestieMap:FindClosestStarter()
|
|
local objectiveCenter = closestStarter[quest.Id]
|
|
|
|
local zoneCount = 0
|
|
local zones = {}
|
|
local objectiveZone
|
|
|
|
for _, spawnData in pairs(objective.spawnList) do
|
|
for zone in pairs(spawnData.Spawns) do
|
|
zones[zone] = true
|
|
end
|
|
end
|
|
|
|
for zone in pairs(zones) do
|
|
objectiveZone = zone
|
|
zoneCount = zoneCount + 1
|
|
end
|
|
|
|
if zoneCount == 1 then -- this objective happens in 1 zone, clustering should be relative to that zone
|
|
local x, y = HBD:GetWorldCoordinatesFromZone(0.5, 0.5, ZoneDB:GetUiMapIdByAreaId(objectiveZone))
|
|
objectiveCenter = { x = x, y = y }
|
|
end
|
|
|
|
local iconsToDraw, _ = _DetermineIconsToDraw(quest, objective, objectiveIndex, objectiveCenter)
|
|
local icon, iconPerZone = _DrawObjectiveIcons(quest.Id, iconsToDraw, objective, maxPerType)
|
|
_DrawObjectiveWaypoints(objective, icon, iconPerZone)
|
|
end
|
|
end
|
|
|
|
_RegisterObjectiveTooltips = function(objective, questId, blockItemTooltips)
|
|
Questie:Debug(Questie.DEBUG_INFO, "Registering objective tooltips for", objective.Description)
|
|
|
|
if objective.spawnList then
|
|
if (not objective.hasRegisteredTooltips) then
|
|
for id, spawnData in pairs(objective.spawnList) do
|
|
if spawnData.TooltipKey and (not objective.AlreadySpawned[id]) then
|
|
QuestieTooltips:RegisterObjectiveTooltip(questId, spawnData.TooltipKey, objective)
|
|
end
|
|
end
|
|
|
|
objective.hasRegisteredTooltips = true
|
|
end
|
|
else
|
|
-- No spawnList and no Id means there is nothing Questie can draw for this objective.
|
|
-- This covers server-tracked trigger objectives (e.g. "complete N quests in zone" for
|
|
-- quest 50150) which may have any objectiveType from the server, not just "event".
|
|
if not objective.Id then
|
|
objective.hasRegisteredTooltips = true
|
|
return
|
|
end
|
|
Questie:Error("[QuestieQuest]: [Tooltips] " ..
|
|
l10n("There was an error populating objectives for %s %s %s %s", objective.Description or "No objective text",
|
|
questId or "No quest id", 0 or "No objective", "No error"))
|
|
end
|
|
|
|
if (not objective.registeredItemTooltips) and objective.Type == "item" and (not blockItemTooltips) and objective.Id then
|
|
local itemName = QuestieDB.QueryItemSingle(objective.Id, "name")
|
|
|
|
if itemName then
|
|
QuestieTooltips:RegisterObjectiveTooltip(questId, "i_" .. objective.Id, objective)
|
|
end
|
|
|
|
objective.registeredItemTooltips = true
|
|
end
|
|
end
|
|
|
|
_UnloadAlreadySpawnedIcons = function(objective)
|
|
if next(objective.spawnList) then
|
|
for id, _ in pairs(objective.spawnList) do
|
|
local spawn = objective.AlreadySpawned[id]
|
|
if spawn then
|
|
for _, mapIcon in pairs(spawn.mapRefs) do
|
|
mapIcon:Unload()
|
|
end
|
|
for _, minimapIcon in pairs(spawn.minimapRefs) do
|
|
minimapIcon:Unload()
|
|
end
|
|
spawn.mapRefs = {}
|
|
spawn.minimapRefs = {}
|
|
end
|
|
end
|
|
objective.AlreadySpawned = {}
|
|
end
|
|
end
|
|
|
|
---@param quest Quest
|
|
---@param objective QuestObjective
|
|
---@param objectiveIndex ObjectiveIndex
|
|
---@param objectiveCenter {x:X, y:Y}
|
|
_DetermineIconsToDraw = function(quest, objective, objectiveIndex, objectiveCenter)
|
|
Questie:Debug(Questie.DEBUG_INFO, "[QuestieQuest:_DetermineIconsToDraw]")
|
|
|
|
local iconsToDraw = {}
|
|
local spawnItemId
|
|
|
|
for id, spawnData in pairs(objective.spawnList) do
|
|
if spawnData.ItemId then
|
|
spawnItemId = spawnData.ItemId
|
|
end
|
|
|
|
if (not objective.Icon) and spawnData.Icon then
|
|
objective.Icon = spawnData.Icon
|
|
end
|
|
|
|
if (not objective.AlreadySpawned[id]) and (not objective.Completed) and Questie.db.profile.enableObjectives then
|
|
local data = {
|
|
Id = quest.Id,
|
|
ObjectiveIndex = objectiveIndex,
|
|
QuestData = quest,
|
|
ObjectiveData = objective,
|
|
Icon = spawnData.Icon,
|
|
IconColor = quest.Color,
|
|
GetIconScale = spawnData.GetIconScale,
|
|
IconScale = spawnData.GetIconScale(),
|
|
Name = spawnData.Name,
|
|
Type = objective.Type,
|
|
ObjectiveTargetId = spawnData.Id
|
|
}
|
|
|
|
objective.AlreadySpawned[id] = {
|
|
data = data,
|
|
minimapRefs = {},
|
|
mapRefs = {},
|
|
}
|
|
|
|
for zone, spawns in pairs(spawnData.Spawns) do
|
|
local uiMapId = ZoneDB:GetUiMapIdByAreaId(zone)
|
|
for _, spawn in pairs(spawns) do
|
|
if (spawn[1] and spawn[2]) then
|
|
local drawIcon = {
|
|
AlreadySpawnedId = id,
|
|
data = data,
|
|
zone = zone,
|
|
AreaID = zone,
|
|
UiMapID = uiMapId,
|
|
x = spawn[1],
|
|
y = spawn[2],
|
|
worldX = 0,
|
|
worldY = 0,
|
|
distance = 0,
|
|
touched = nil, -- TODO change. This is meant to let lua reserve memory for all keys needed for sure.
|
|
}
|
|
local x, y, _ = HBD:GetWorldCoordinatesFromZone(drawIcon.x / 100, drawIcon.y / 100, uiMapId)
|
|
x = x or 0
|
|
y = y or 0
|
|
-- Cache world coordinates for clustering calculations
|
|
drawIcon.worldX = x
|
|
drawIcon.worldY = y
|
|
-- There are instances when X and Y are not in the same map such as in dungeons etc, we default to 0 if it is not set
|
|
-- This will create a distance of 0 but it doesn't matter.
|
|
local distance = QuestieLib:Euclid(objectiveCenter.x or 0, objectiveCenter.y or 0, x, y);
|
|
drawIcon.distance = distance or 0 -- cache for clustering
|
|
-- there can be multiple icons at same distance at different directions
|
|
--local distance = floor(distance)
|
|
local iconList = iconsToDraw[distance]
|
|
if iconList then
|
|
iconList[#iconList + 1] = drawIcon
|
|
else
|
|
iconsToDraw[distance] = { drawIcon }
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return iconsToDraw, spawnItemId
|
|
end
|
|
|
|
_DrawObjectiveIcons = function(questId, iconsToDraw, objective, maxPerType)
|
|
Questie:Debug(Questie.DEBUG_INFO, "[QuestieQuest:_DrawObjectiveIcons] Adding Icons for quest:", questId)
|
|
|
|
local spawnedIconCount = 0
|
|
local icon
|
|
local iconPerZone = {}
|
|
|
|
local range = Questie.db.profile.clusterLevelHotzone
|
|
|
|
local iconCount, orderedList = _GetIconsSortedByDistance(iconsToDraw)
|
|
|
|
if orderedList[1] and orderedList[1].Icon == Questie.ICON_TYPE_OBJECT then -- new clustering / limit code should prevent problems, always show all object notes
|
|
range = range * 0.2; -- Only use 20% of the default range.
|
|
end
|
|
|
|
local hotzones = QuestieMap.utils:CalcHotzones(orderedList, range, iconCount);
|
|
|
|
for i = 1, #hotzones do
|
|
local hotzone = hotzones[i]
|
|
if (spawnedIconCount > maxPerType) then
|
|
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieQuest] Too many icons for quest:", questId)
|
|
break;
|
|
end
|
|
|
|
--Any icondata will do because they are all the same
|
|
icon = hotzone[1];
|
|
|
|
local spawnsMapRefs = objective.AlreadySpawned[icon.AlreadySpawnedId].mapRefs
|
|
local spawnsMinimapRefs = objective.AlreadySpawned[icon.AlreadySpawnedId].minimapRefs
|
|
|
|
local centerX, centerY = QuestieMap.utils.CenterPoint(hotzone)
|
|
|
|
local dungeonLocation = ZoneDB:GetDungeonLocation(icon.zone)
|
|
|
|
if dungeonLocation and centerX == -1 and centerY == -1 then
|
|
if dungeonLocation[2] then -- We have more than 1 instance entrance (e.g. Blackrock dungeons)
|
|
local secondDungeonLocation = dungeonLocation[2]
|
|
|
|
icon.zone = secondDungeonLocation[1]
|
|
centerX = secondDungeonLocation[2]
|
|
centerY = secondDungeonLocation[3]
|
|
|
|
local iconMap, iconMini = QuestieMap:DrawWorldIcon(icon.data, icon.zone, centerX, centerY) -- clustering code takes care of duplicates as long as min-dist is more than 0
|
|
|
|
if iconMap and iconMini then
|
|
iconPerZone[icon.zone] = { iconMap, centerX, centerY }
|
|
spawnsMapRefs[#spawnsMapRefs + 1] = iconMap
|
|
spawnsMinimapRefs[#spawnsMinimapRefs + 1] = iconMini
|
|
end
|
|
|
|
spawnedIconCount = spawnedIconCount + 1;
|
|
end
|
|
|
|
local firstDungeonLocation = dungeonLocation[1]
|
|
icon.zone = firstDungeonLocation[1]
|
|
centerX = firstDungeonLocation[2]
|
|
centerY = firstDungeonLocation[3]
|
|
end
|
|
|
|
local iconMap, iconMini = QuestieMap:DrawWorldIcon(icon.data, icon.zone, centerX, centerY) -- clustering code takes care of duplicates as long as min-dist is more than 0
|
|
|
|
if iconMap and iconMini then
|
|
iconPerZone[icon.zone] = { iconMap, centerX, centerY }
|
|
spawnsMapRefs[#spawnsMapRefs + 1] = iconMap
|
|
spawnsMinimapRefs[#spawnsMinimapRefs + 1] = iconMini
|
|
end
|
|
|
|
spawnedIconCount = spawnedIconCount + 1;
|
|
end
|
|
|
|
return icon, iconPerZone
|
|
end
|
|
|
|
_GetIconsSortedByDistance = function(icons)
|
|
local iconCount = 0;
|
|
local orderedList = {}
|
|
local distances = {}
|
|
|
|
local i = 0
|
|
|
|
for distance in pairs(icons) do
|
|
i = i + 1
|
|
distances[i] = distance
|
|
end
|
|
|
|
table.sort(distances)
|
|
|
|
-- use the keys to retrieve the values in the sorted order
|
|
for distIndex = 1, #distances do
|
|
local iconsAtDisntace = icons[distances[distIndex]]
|
|
|
|
for iconIndex = 1, #iconsAtDisntace do
|
|
local icon = iconsAtDisntace[iconIndex]
|
|
|
|
iconCount = iconCount + 1
|
|
orderedList[iconCount] = icon
|
|
end
|
|
end
|
|
|
|
return iconCount, orderedList
|
|
end
|
|
|
|
_DrawObjectiveWaypoints = function(objective, icon, iconPerZone)
|
|
for _, spawnData in pairs(objective.spawnList) do -- spawnData.Name, spawnData.Spawns
|
|
if spawnData.Waypoints then
|
|
for zone, waypoints in pairs(spawnData.Waypoints) do
|
|
local firstWaypoint = waypoints[1][1]
|
|
|
|
if (not iconPerZone[zone]) and icon and firstWaypoint[1] ~= -1 and firstWaypoint[2] ~= -1 then -- spawn an icon in this zone for the mob
|
|
local iconMap, iconMini = QuestieMap:DrawWorldIcon(icon.data, zone, firstWaypoint[1],
|
|
firstWaypoint[2]) -- clustering code takes care of duplicates as long as min-dist is more than 0
|
|
|
|
if iconMap and iconMini then
|
|
iconPerZone[zone] = { iconMap, firstWaypoint[1], firstWaypoint[2] }
|
|
tinsert(objective.AlreadySpawned[icon.AlreadySpawnedId].mapRefs, iconMap);
|
|
tinsert(objective.AlreadySpawned[icon.AlreadySpawnedId].minimapRefs, iconMini);
|
|
end
|
|
end
|
|
|
|
local ipz = iconPerZone[zone]
|
|
|
|
if ipz then
|
|
QuestieMap:DrawWaypoints(ipz[1], waypoints, zone, spawnData.Hostile and { 1, 0.2, 0, 0.7 } or nil)
|
|
end
|
|
end
|
|
|
|
Questie:Debug(Questie.DEBUG_INFO, "[QuestieQuest:_DrawObjectiveWaypoints]")
|
|
end
|
|
end
|
|
end
|
|
|
|
---@param quest Quest
|
|
function QuestieQuest:PopulateObjectiveNotes(quest) -- this should be renamed to PopulateNotes as it also handles finishers now
|
|
if (not quest) then
|
|
return
|
|
end
|
|
|
|
if QuestieDB.IsComplete(quest.Id) == 1 then
|
|
Questie:Debug(Questie.DEBUG_INFO, "[QuestieQuest:PopulateObjectiveNotes] Quest Complete! Adding Finisher for:",
|
|
quest.Id)
|
|
|
|
QuestieQuest:UpdateQuest(quest.Id)
|
|
_AddSourceItemObjective(quest)
|
|
_AddRequiredSourceItemObjective(quest)
|
|
_AddSpellItemObjective(quest)
|
|
|
|
return
|
|
end
|
|
|
|
if (not quest.Color) then
|
|
quest.Color = QuestieLib:ColorWheel()
|
|
end
|
|
|
|
Questie:Debug(Questie.DEBUG_INFO, "[QuestieQuest:PopulateObjectiveNotes] Populating objectives for:", quest.Id)
|
|
|
|
QuestieQuest:UpdateObjectiveNotes(quest)
|
|
_AddSourceItemObjective(quest)
|
|
_AddRequiredSourceItemObjective(quest)
|
|
_AddSpellItemObjective(quest)
|
|
end
|
|
|
|
---@param quest Quest
|
|
---@return true?
|
|
function QuestieQuest:PopulateQuestLogInfo(quest)
|
|
if (not quest) then
|
|
return nil
|
|
end
|
|
|
|
Questie:Debug(Questie.DEBUG_INFO, "[QuestieQuest:PopulateQuestLogInfo] ", quest.Id)
|
|
|
|
local questLogEngtry = QuestLogCache.GetQuest(quest.Id) -- DO NOT MODIFY THE RETURNED TABLE
|
|
|
|
if (not questLogEngtry) then return end
|
|
|
|
if questLogEngtry.isComplete ~= nil and questLogEngtry.isComplete == 1 then
|
|
quest.isComplete = true
|
|
end
|
|
|
|
--Uses the category order to draw the quests and trusts the database order.
|
|
|
|
local questObjectives = QuestieQuest:GetAllLeaderBoardDetails(quest.Id) or {} -- DO NOT MODIFY THE RETURNED TABLE
|
|
|
|
for objectiveIndex, objective in pairs(questObjectives) do
|
|
if objective.type and string.len(objective.type) > 1 then
|
|
if (not quest.ObjectiveData) or (not quest.ObjectiveData[objectiveIndex]) then
|
|
Questie:Error(l10n("Missing objective data for quest "), quest.Id, " ", objective.text)
|
|
else
|
|
if not quest.Objectives[objectiveIndex] then
|
|
quest.Objectives[objectiveIndex] = {
|
|
Id = quest.ObjectiveData[objectiveIndex].Id,
|
|
Index = objectiveIndex,
|
|
questId = quest.Id,
|
|
_lastUpdate = 0,
|
|
Description = objective.text,
|
|
spawnList = {},
|
|
AlreadySpawned = {},
|
|
Update = _QuestieQuest.ObjectiveUpdate,
|
|
Coordinates = quest.ObjectiveData[objectiveIndex].Coordinates, -- Only for type "event"
|
|
RequiredRepValue = quest.ObjectiveData[objectiveIndex].RequiredRepValue
|
|
}
|
|
end
|
|
|
|
quest.Objectives[objectiveIndex]:Update()
|
|
end
|
|
end
|
|
|
|
if (not quest.Objectives[objectiveIndex]) or (not quest.Objectives[objectiveIndex].Id) then
|
|
Questie:Debug(Questie.DEBUG_DEVELOP,
|
|
"[QuestieQuest:PopulateQuestLogInfo] Error finding entry ID for objective", objectiveIndex,
|
|
objective.type, objective.text, "of questId:", quest.Id)
|
|
end
|
|
end
|
|
|
|
-- find special unlisted objectives
|
|
if next(quest.SpecialObjectives) then
|
|
for index, specialObjective in pairs(quest.SpecialObjectives) do
|
|
if (not specialObjective.Description) then
|
|
specialObjective.Description = "Special objective"
|
|
end
|
|
|
|
specialObjective.questId = quest.Id
|
|
|
|
if specialObjective.RealObjectiveIndex and quest.Objectives[specialObjective.RealObjectiveIndex] then
|
|
-- This specialObjective is an extraObjective and has a RealObjectiveIndex set
|
|
specialObjective.Completed = quest.Objectives[specialObjective.RealObjectiveIndex].Completed
|
|
specialObjective.Update = function(self)
|
|
self.Completed = quest.Objectives[self.RealObjectiveIndex].Completed
|
|
end
|
|
else
|
|
specialObjective.Update = NOP_FUNCTION
|
|
end
|
|
|
|
specialObjective.Index = 64 + index -- offset to not conflict with real objectives
|
|
specialObjective.AlreadySpawned = specialObjective.AlreadySpawned or {}
|
|
end
|
|
end
|
|
|
|
if #quest.Objectives == 0 and #quest.SpecialObjectives == 0 and ((quest.triggerEnd and #quest.triggerEnd > 0) or (quest.Finisher and quest.Finisher.Id ~= nil)) then
|
|
-- Some quests when picked up will be flagged isComplete == 0 but the quest.Objective table or quest.SpecialObjectives table is nil. This
|
|
-- check assumes the Quest should have been flagged questLogEngtry.isComplete == 1. We're specifically looking for a quest.triggerEnd or
|
|
-- a quest.Finisher.Id because this might throw an error if there is nothing to populate when we call QuestieQuest:AddFinisher().
|
|
QuestieMap:UnloadQuestFrames(quest.Id)
|
|
QuestieQuest:AddFinisher(quest)
|
|
quest.isComplete = true
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
---@param self QuestObjective @quest.Objectives[] entry
|
|
function _QuestieQuest.ObjectiveUpdate(self)
|
|
if self.isUpdated then
|
|
return
|
|
end
|
|
|
|
local questObjectives = QuestieQuest:GetAllLeaderBoardDetails(self.questId) -- DO NOT MODIFY THE RETURNED TABLE
|
|
|
|
if questObjectives and questObjectives[self.Index] then
|
|
local obj = questObjectives[self.Index] -- DO NOT EDIT THE TABLE
|
|
if (obj.type) then
|
|
-- fixes for api bug
|
|
local numFulfilled = obj.numFulfilled or 0
|
|
local numRequired = obj.numRequired or 0
|
|
local finished = obj.finished or false -- ensure its boolean false and not nil (hack)
|
|
|
|
self.Type = obj.type;
|
|
self.Description = obj.text
|
|
self.Collected = tonumber(numFulfilled);
|
|
self.Needed = tonumber(numRequired);
|
|
self.Completed = (self.Needed == self.Collected and self.Needed > 0) or
|
|
(finished and (self.Needed == 0 or (not self.Needed))) -- some objectives get removed on PLAYER_LOGIN because isComplete is set to true at random????
|
|
-- Mark objective updated
|
|
self.isUpdated = true
|
|
end
|
|
end
|
|
end
|
|
|
|
---@param questId number
|
|
---@return table<ObjectiveIndex, QuestLogCacheObjectiveData>|nil @DO NOT EDIT RETURNED TABLE
|
|
function QuestieQuest:GetAllLeaderBoardDetails(questId)
|
|
Questie:Debug(Questie.DEBUG_INFO, "[QuestieQuest:GetAllLeaderBoardDetails] for questId", questId)
|
|
|
|
local questObjectives = QuestLogCache.GetQuestObjectives(questId) -- DO NOT MODIFY THE RETURNED TABLE
|
|
if (not questObjectives) then return end
|
|
|
|
for _, objective in pairs(questObjectives) do -- DO NOT MODIFY THE RETURNED TABLE
|
|
-- TODO Move this to QuestEventHandler module or QuestieQuest:AcceptQuest( ) + QuestieQuest:UpdateQuest( ) (accept quest one required to register objectives without progress)
|
|
-- TODO After ^^^ moving remove this function and use "QuestLogCache.GetQuest(questId).objectives -- DO NOT MODIFY THE RETURNED TABLE" in place of it.
|
|
QuestieAnnounce:ObjectiveChanged(questId, objective.text, objective.numFulfilled, objective.numRequired)
|
|
end
|
|
|
|
return questObjectives
|
|
end
|
|
|
|
function QuestieQuest.DrawDailyQuest(questId)
|
|
if QuestieDB.IsDoable(questId) then
|
|
local quest = QuestieDB.GetQuest(questId)
|
|
AvailableQuests.DrawAvailableQuest(quest)
|
|
end
|
|
end
|
|
|
|
return QuestieQuest
|