Compare commits
2 Commits
cf67ceb474
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 30fe4b83a9 | |||
| 26a135a2ca |
+43
-5
@@ -1785,16 +1785,36 @@ function QuestieDB.GetQuest(questId, ...) -- /dump QuestieDB.GetQuest(867)
|
|||||||
end
|
end
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
-- Build a minimal live-fallback quest from the quest log so the tracker still works
|
-- Build a minimal live-fallback quest from the quest log so the tracker still works.
|
||||||
|
-- The override that got us here is partial by nature -- a Learner record of a single
|
||||||
|
-- field, a correction -- but whatever it does carry beats guessing, and the quest object
|
||||||
|
-- built here is cached for the session, so anything left blank stays blank.
|
||||||
local logEntry = QuestLogCache.GetQuest(questId)
|
local logEntry = QuestLogCache.GetQuest(questId)
|
||||||
if not logEntry then return nil end
|
if not logEntry then return nil end
|
||||||
|
local function NonEmpty(text)
|
||||||
|
if text and text ~= "" then return text end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
local overrideName = NonEmpty(overrideData[QuestieDB.questKeys.name])
|
||||||
|
local overrideLevel = overrideData[QuestieDB.questKeys.questLevel]
|
||||||
|
local overrideZone = overrideData[QuestieDB.questKeys.zoneOrSort]
|
||||||
|
local cachedTitle = NonEmpty(logEntry.title)
|
||||||
|
local liveTitle
|
||||||
|
if (not overrideName) and (not cachedTitle) and GetQuestLogIndexByID and GetQuestLogTitle then
|
||||||
|
-- QuestLogCache is a snapshot and can be missing the title of a quest the client
|
||||||
|
-- has since filled in, which is what leaves a quest showing as its own id.
|
||||||
|
local questLogIndex = GetQuestLogIndexByID(questId)
|
||||||
|
if questLogIndex and questLogIndex > 0 then
|
||||||
|
liveTitle = NonEmpty(GetQuestLogTitle(questLogIndex))
|
||||||
|
end
|
||||||
|
end
|
||||||
local fallback = {
|
local fallback = {
|
||||||
Id = questId,
|
Id = questId,
|
||||||
name = logEntry.title or tostring(questId),
|
name = overrideName or cachedTitle or liveTitle or tostring(questId),
|
||||||
level = logEntry.level or 0,
|
level = overrideLevel or logEntry.level or 0,
|
||||||
questLevel = logEntry.level or 0,
|
questLevel = overrideLevel or logEntry.level or 0,
|
||||||
requiredLevel = 0,
|
requiredLevel = 0,
|
||||||
zoneOrSort = 0,
|
zoneOrSort = overrideZone or 0,
|
||||||
questFlags = 0,
|
questFlags = 0,
|
||||||
specialFlags = 0,
|
specialFlags = 0,
|
||||||
Starts = { CreatureStarts = {}, ObjectStarts = {}, ItemStarts = {} },
|
Starts = { CreatureStarts = {}, ObjectStarts = {}, ItemStarts = {} },
|
||||||
@@ -1916,6 +1936,24 @@ function QuestieDB.GetQuest(questId, ...) -- /dump QuestieDB.GetQuest(867)
|
|||||||
if learnerRecord.objIndex and not QO.objIndex then QO.objIndex = learnerRecord.objIndex end
|
if learnerRecord.objIndex and not QO.objIndex then QO.objIndex = learnerRecord.objIndex end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- A record that never captured a name -- a Learner entry written from an objective update, an
|
||||||
|
-- override carrying a single key -- leaves QO.name nil, and this object is cached for the rest
|
||||||
|
-- of the session, so everything downstream ends up printing the quest id. Ask the client.
|
||||||
|
if ((not QO.name) or QO.name == "") and GetQuestLogIndexByID and GetQuestLogTitle then
|
||||||
|
local questLogIndex = GetQuestLogIndexByID(questId)
|
||||||
|
if questLogIndex and questLogIndex > 0 then
|
||||||
|
local logTitle = GetQuestLogTitle(questLogIndex)
|
||||||
|
if logTitle and logTitle ~= "" then
|
||||||
|
QO.name = logTitle
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Same story for the zone: partial records leave it nil, and callers all treat it as a number
|
||||||
|
-- (`quest.zoneOrSort > 0` errors outright on nil). 0 is the value everything already reads as
|
||||||
|
-- "no zone on file", which sends the tracker to the quest log for one.
|
||||||
|
QO.zoneOrSort = QO.zoneOrSort or 0
|
||||||
|
|
||||||
local questLevel, requiredLevel = QuestieLib.GetTbcLevel(questId)
|
local questLevel, requiredLevel = QuestieLib.GetTbcLevel(questId)
|
||||||
QO.level = questLevel
|
QO.level = questLevel
|
||||||
QO.requiredLevel = requiredLevel
|
QO.requiredLevel = requiredLevel
|
||||||
|
|||||||
@@ -63,6 +63,22 @@ local function GetWrappedWidth(label)
|
|||||||
return label:GetWidth()
|
return label:GetWidth()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- A quest built from partial data can reach the tracker with no name, or with the id standing in
|
||||||
|
-- for one, and it is cached that way for the session. Ask the quest log before printing a number.
|
||||||
|
local function GetDisplayableQuestName(quest)
|
||||||
|
local questName = quest.name
|
||||||
|
|
||||||
|
if (not questName) or questName == "" or questName == tostring(quest.Id) then
|
||||||
|
questName = TrackerUtils:GetQuestLogTitleById(quest.Id) or questName
|
||||||
|
end
|
||||||
|
|
||||||
|
if (not questName) or questName == "" then
|
||||||
|
questName = tostring(quest.Id)
|
||||||
|
end
|
||||||
|
|
||||||
|
return questName
|
||||||
|
end
|
||||||
|
|
||||||
local LSM30 = LibStub and LibStub("LibSharedMedia-3.0", true)
|
local LSM30 = LibStub and LibStub("LibSharedMedia-3.0", true)
|
||||||
|
|
||||||
-- Local Vars
|
-- Local Vars
|
||||||
@@ -1031,7 +1047,7 @@ function QuestieTracker:Update()
|
|||||||
|
|
||||||
if quest.isFallback or quest._isLogFallback then
|
if quest.isFallback or quest._isLogFallback then
|
||||||
-- Quest not in DB: use the name stored on the fallback object
|
-- Quest not in DB: use the name stored on the fallback object
|
||||||
local questName = quest.name or tostring(quest.Id)
|
local questName = GetDisplayableQuestName(quest)
|
||||||
if Questie.db.profile.trackerShowQuestLevel and quest.level and quest.level > 0 then
|
if Questie.db.profile.trackerShowQuestLevel and quest.level and quest.level > 0 then
|
||||||
questName = "[" .. quest.level .. "] " .. questName
|
questName = "[" .. quest.level .. "] " .. questName
|
||||||
end
|
end
|
||||||
@@ -1065,7 +1081,7 @@ function QuestieTracker:Update()
|
|||||||
-- which queries the static DB only: for a quest with no DB entry it
|
-- which queries the static DB only: for a quest with no DB entry it
|
||||||
-- silently defaults to level 1, and Ascension's scaling then scales
|
-- silently defaults to level 1, and Ascension's scaling then scales
|
||||||
-- that wrong base instead of the quest's real level.
|
-- that wrong base instead of the quest's real level.
|
||||||
local fallbackName = quest.name or tostring(quest.Id)
|
local fallbackName = GetDisplayableQuestName(quest)
|
||||||
if Questie.db.profile.trackerShowQuestLevel and quest.level and quest.level > 0 then
|
if Questie.db.profile.trackerShowQuestLevel and quest.level and quest.level > 0 then
|
||||||
fallbackName = QuestieLib:GetQuestString(quest.Id, fallbackName, quest.level, false)
|
fallbackName = QuestieLib:GetQuestString(quest.Id, fallbackName, quest.level, false)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -685,9 +685,14 @@ local questLogHeaders = {}
|
|||||||
-- and this is what says whether the player still has a given quest.
|
-- and this is what says whether the player still has a given quest.
|
||||||
local questLogQuestIds = {}
|
local questLogQuestIds = {}
|
||||||
|
|
||||||
|
-- questId -> its row in the quest log, so re-reading a quest's leaderboard costs a lookup
|
||||||
|
-- instead of another walk. Verified before use, since the log renumbers on every change.
|
||||||
|
local questLogIndexes = {}
|
||||||
|
|
||||||
local function BuildQuestLogHeaders()
|
local function BuildQuestLogHeaders()
|
||||||
local headers = {}
|
local headers = {}
|
||||||
local questIds = {}
|
local questIds = {}
|
||||||
|
local indexes = {}
|
||||||
local header
|
local header
|
||||||
|
|
||||||
for i = 1, (GetNumQuestLogEntries and GetNumQuestLogEntries() or 0) do
|
for i = 1, (GetNumQuestLogEntries and GetNumQuestLogEntries() or 0) do
|
||||||
@@ -698,6 +703,7 @@ local function BuildQuestLogHeaders()
|
|||||||
end
|
end
|
||||||
elseif logId then
|
elseif logId then
|
||||||
questIds[logId] = true
|
questIds[logId] = true
|
||||||
|
indexes[logId] = i
|
||||||
if header then
|
if header then
|
||||||
headers[logId] = header
|
headers[logId] = header
|
||||||
end
|
end
|
||||||
@@ -706,6 +712,87 @@ local function BuildQuestLogHeaders()
|
|||||||
|
|
||||||
questLogHeaders = headers
|
questLogHeaders = headers
|
||||||
questLogQuestIds = questIds
|
questLogQuestIds = questIds
|
||||||
|
questLogIndexes = indexes
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Returns the quest's current row in the quest log, or nil if the player no longer has it.
|
||||||
|
local function GetQuestLogIndexForQuest(questId)
|
||||||
|
local index = questLogIndexes[questId]
|
||||||
|
if index then
|
||||||
|
local _, _, _, isHeader, _, _, _, logId = GetQuestLogTitle(index)
|
||||||
|
if (not isHeader) and logId == questId then
|
||||||
|
return index
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, (GetNumQuestLogEntries and GetNumQuestLogEntries() or 0) do
|
||||||
|
local _, _, _, isHeader, _, _, _, logId = GetQuestLogTitle(i)
|
||||||
|
if (not isHeader) and logId == questId then
|
||||||
|
questLogIndexes[questId] = i
|
||||||
|
return i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 3.3.5 hands over a leaderboard line as one string -- "Icefang slain: 3/8" -- while the tracker
|
||||||
|
-- prints the description and the counts separately, so they have to come back apart here.
|
||||||
|
-- Objectives with nothing to count arrive as bare text and stand in as a single 0/1 step.
|
||||||
|
local function ParseLeaderBoardText(text, finished)
|
||||||
|
-- Greedy on purpose: a description of its own may hold a colon, and the counter is the last one.
|
||||||
|
local description, collected, needed = string.match(text, "^(.*):%s*(%d+)%s*/%s*(%d+)%s*$")
|
||||||
|
if not description then
|
||||||
|
-- Servers writing their own objective strings do not always keep the colon.
|
||||||
|
description, collected, needed = string.match(text, "^(.-)%s*(%d+)%s*/%s*(%d+)%s*$")
|
||||||
|
end
|
||||||
|
|
||||||
|
if (not description) or description == "" then
|
||||||
|
return text, (finished and 1 or 0), 1
|
||||||
|
end
|
||||||
|
|
||||||
|
collected = tonumber(collected) or 0
|
||||||
|
needed = tonumber(needed) or 1
|
||||||
|
if needed <= 0 then
|
||||||
|
needed = 1
|
||||||
|
end
|
||||||
|
|
||||||
|
return description, collected, needed
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Fills `objectives` from the quest's leaderboard, in place: the tracker hands these very tables
|
||||||
|
-- to the lines it draws, so a redraw has to update them rather than swap in new ones.
|
||||||
|
local function ReadFallbackObjectives(questLogIndex, questId, objectives)
|
||||||
|
local numObjectives = (GetNumQuestLeaderBoards and GetNumQuestLeaderBoards(questLogIndex)) or 0
|
||||||
|
|
||||||
|
for j = 1, numObjectives do
|
||||||
|
local text, objectiveType, finished = GetQuestLogLeaderBoard(j, questLogIndex)
|
||||||
|
if text then
|
||||||
|
local description, collected, needed = ParseLeaderBoardText(text, finished)
|
||||||
|
local objective = objectives[j]
|
||||||
|
if not objective then
|
||||||
|
objective = {}
|
||||||
|
objectives[j] = objective
|
||||||
|
end
|
||||||
|
|
||||||
|
objective.questId = questId
|
||||||
|
objective.Index = j
|
||||||
|
objective.Description = description
|
||||||
|
objective.text = text
|
||||||
|
objective.Collected = collected
|
||||||
|
objective.Needed = needed
|
||||||
|
objective.Completed = (finished or collected >= needed) and true or false
|
||||||
|
objective.baseType = objectiveType
|
||||||
|
-- Nothing here came from the DB, so map/tooltip code has to leave it alone.
|
||||||
|
objective.Type = "fallback"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for j = #objectives, numObjectives + 1, -1 do
|
||||||
|
objectives[j] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
return objectives
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Returns the header title string, or nil if the quest is not in the log under one.
|
-- Returns the header title string, or nil if the quest is not in the log under one.
|
||||||
@@ -722,7 +809,13 @@ local function _GetZoneName(zoneOrSort, questId, zoneNameOverride)
|
|||||||
if zoneNameOverride and zoneNameOverride ~= "" then
|
if zoneNameOverride and zoneNameOverride ~= "" then
|
||||||
return zoneNameOverride
|
return zoneNameOverride
|
||||||
end
|
end
|
||||||
if not zoneOrSort then return "Unknown Zone" end
|
|
||||||
|
-- A quest assembled from a partial record -- a Learner entry that never captured the field,
|
||||||
|
-- an override carrying a single key -- reaches here with no zoneOrSort at all. That is the
|
||||||
|
-- same "nothing to look up" case as 0, and the quest log below still knows where the client
|
||||||
|
-- files the quest, so it must not short-circuit to Unknown Zone ahead of that.
|
||||||
|
zoneOrSort = zoneOrSort or 0
|
||||||
|
|
||||||
local zoneName
|
local zoneName
|
||||||
local sortObj = Questie.db.profile.trackerSortObjectives
|
local sortObj = Questie.db.profile.trackerSortObjectives
|
||||||
if sortObj == "byZone" or sortObj == "byZoneComplete" or sortObj == "byZoneCompleteReversed" or sortObj == "byZonePlayerProximity" or sortObj == "byZonePlayerProximityReversed" then
|
if sortObj == "byZone" or sortObj == "byZoneComplete" or sortObj == "byZoneCompleteReversed" or sortObj == "byZonePlayerProximity" or sortObj == "byZonePlayerProximityReversed" then
|
||||||
@@ -778,67 +871,102 @@ local function _GetZoneName(zoneOrSort, questId, zoneNameOverride)
|
|||||||
return zoneName
|
return zoneName
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- The client's own title for a quest, or nil if the player does not have it. Quest objects can
|
||||||
|
-- reach the tracker without a usable name -- a questDataOverrides entry that carries no name
|
||||||
|
-- field, a QuestLogCache row read before the client had filled the title in -- and since those
|
||||||
|
-- objects are cached for the session, the name never repairs itself. The log always knows.
|
||||||
|
function TrackerUtils:GetQuestLogTitleById(questId)
|
||||||
|
local questLogIndex = GetQuestLogIndexForQuest(questId)
|
||||||
|
if not questLogIndex then return nil end
|
||||||
|
|
||||||
|
local title = GetQuestLogTitle(questLogIndex)
|
||||||
|
if title and title ~= "" then
|
||||||
|
return title
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- IsComplete must be a method (called as quest:IsComplete()), and it reads the state the last
|
||||||
|
-- refresh stored rather than closing over the state at build time -- these quests outlive many
|
||||||
|
-- redraws, and a captured value would still claim the quest is unfinished after it is turned in.
|
||||||
|
local function FallbackQuestIsComplete(self)
|
||||||
|
if self.logIsComplete == 1 or self.logIsComplete == true then
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
if QuestiePlayer.currentQuestlog[self.Id] and IsQuestFlaggedCompleted and IsQuestFlaggedCompleted(self.Id) then
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Re-reads everything the quest log owns: title, level, completion and the objectives. The
|
||||||
|
-- tracker keeps fallback quests between draws, so without this they stay frozen at whatever
|
||||||
|
-- the log said when the quest was first seen.
|
||||||
|
---@return boolean @false if the player no longer has the quest
|
||||||
|
function TrackerUtils:RefreshFallbackQuest(quest)
|
||||||
|
if not quest then return false end
|
||||||
|
|
||||||
|
local questLogIndex = GetQuestLogIndexForQuest(quest.Id)
|
||||||
|
if not questLogIndex then return false end
|
||||||
|
|
||||||
|
local title, level, _, _, _, isComplete = GetQuestLogTitle(questLogIndex)
|
||||||
|
if title and title ~= "" then
|
||||||
|
quest.name = title
|
||||||
|
end
|
||||||
|
if level and level > 0 then
|
||||||
|
quest.level = level
|
||||||
|
end
|
||||||
|
|
||||||
|
quest.logIsComplete = isComplete
|
||||||
|
quest.isComplete = (isComplete == 1)
|
||||||
|
|
||||||
|
ReadFallbackObjectives(questLogIndex, quest.Id, quest.Objectives)
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
-- Returns nil if the quest is not currently in the quest log.
|
-- Returns nil if the quest is not currently in the quest log.
|
||||||
function TrackerUtils:BuildFallbackQuest(questId)
|
function TrackerUtils:BuildFallbackQuest(questId)
|
||||||
for i = 1, GetNumQuestLogEntries() do
|
local questLogIndex = GetQuestLogIndexForQuest(questId)
|
||||||
local title, level, _, isHeader, _, isComplete, _, logQuestId = GetQuestLogTitle(i)
|
if not questLogIndex then return nil end
|
||||||
if not isHeader and logQuestId == questId then
|
|
||||||
-- Parse objectives from the leaderboard
|
|
||||||
local objectives = {}
|
|
||||||
local numObj = GetNumQuestLeaderBoards and GetNumQuestLeaderBoards(i) or 0
|
|
||||||
for j = 1, numObj do
|
|
||||||
local text, _, finished = GetQuestLogLeaderBoard(j, i)
|
|
||||||
if text then
|
|
||||||
-- Parse "Description: X/Y" or just "Description"
|
|
||||||
local collected, needed = string.match(text, ":.-(%d+)/(%d+)%s*$")
|
|
||||||
collected = tonumber(collected) or (finished and 1 or 0)
|
|
||||||
needed = tonumber(needed) or 1
|
|
||||||
objectives[j] = {
|
|
||||||
text = text,
|
|
||||||
Needed = needed,
|
|
||||||
Collected = collected,
|
|
||||||
Finished = finished or (collected >= needed),
|
|
||||||
Type = "fallback",
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Walk backwards from i in the quest log to find the zone header.
|
-- Walk backwards from the quest in the log to find the zone header.
|
||||||
-- This is the canonical 3.3.5 method: zone headers sit above their quests.
|
-- This is the canonical 3.3.5 method: zone headers sit above their quests.
|
||||||
local zoneText = nil
|
local zoneText = nil
|
||||||
for h = i, 1, -1 do
|
for h = questLogIndex, 1, -1 do
|
||||||
local hTitle, _, _, hIsHeader = GetQuestLogTitle(h)
|
local hTitle, _, _, hIsHeader = GetQuestLogTitle(h)
|
||||||
if hIsHeader and hTitle and hTitle ~= "" then
|
if hIsHeader and hTitle and hTitle ~= "" then
|
||||||
zoneText = hTitle
|
zoneText = hTitle
|
||||||
break
|
break
|
||||||
end
|
|
||||||
end
|
|
||||||
local zoneId = (zoneText and GetAreaIdByZoneName(zoneText)) or 0
|
|
||||||
local zoneNameOverride = nil
|
|
||||||
if zoneText and (not zoneId or zoneId == 0) then
|
|
||||||
zoneNameOverride = zoneText
|
|
||||||
end
|
|
||||||
|
|
||||||
local quest = {
|
|
||||||
Id = questId,
|
|
||||||
name = title or ("Quest " .. questId),
|
|
||||||
level = level or 0,
|
|
||||||
zoneOrSort = zoneId,
|
|
||||||
zoneName = zoneText,
|
|
||||||
zoneNameOverride = zoneNameOverride,
|
|
||||||
Objectives = objectives,
|
|
||||||
SpecialObjectives = {},
|
|
||||||
isFallback = true,
|
|
||||||
}
|
|
||||||
-- IsComplete must be a method (called as quest:IsComplete())
|
|
||||||
quest.IsComplete = function(self)
|
|
||||||
return (isComplete == 1 or (QuestiePlayer.currentQuestlog[questId] and IsQuestFlaggedCompleted and IsQuestFlaggedCompleted(questId))) and 1 or 0
|
|
||||||
end
|
|
||||||
|
|
||||||
return quest
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return nil
|
local zoneId = (zoneText and GetAreaIdByZoneName(zoneText)) or 0
|
||||||
|
local zoneNameOverride = nil
|
||||||
|
if zoneText and (not zoneId or zoneId == 0) then
|
||||||
|
zoneNameOverride = zoneText
|
||||||
|
end
|
||||||
|
|
||||||
|
local quest = {
|
||||||
|
Id = questId,
|
||||||
|
name = "Quest " .. questId,
|
||||||
|
level = 0,
|
||||||
|
zoneOrSort = zoneId,
|
||||||
|
zoneName = zoneText,
|
||||||
|
zoneNameOverride = zoneNameOverride,
|
||||||
|
Objectives = {},
|
||||||
|
SpecialObjectives = {},
|
||||||
|
isFallback = true,
|
||||||
|
IsComplete = FallbackQuestIsComplete,
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Same read the tracker does on every later draw, so a quest built here and one refreshed
|
||||||
|
-- from the cache carry exactly the same fields.
|
||||||
|
TrackerUtils:RefreshFallbackQuest(quest)
|
||||||
|
|
||||||
|
return quest
|
||||||
end
|
end
|
||||||
|
|
||||||
function TrackerUtils:GetSortedQuestIds()
|
function TrackerUtils:GetSortedQuestIds()
|
||||||
@@ -921,6 +1049,11 @@ function TrackerUtils:GetSortedQuestIds()
|
|||||||
if fallback then
|
if fallback then
|
||||||
TrackerUtils._fallbackQuests[qid] = fallback
|
TrackerUtils._fallbackQuests[qid] = fallback
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
-- Cached by an earlier draw, so its objectives and completion state are as
|
||||||
|
-- old as the cache. Nothing else updates them -- these quests have no DB
|
||||||
|
-- entry, so QuestieQuest's populate path skips them entirely.
|
||||||
|
TrackerUtils:RefreshFallbackQuest(fallback)
|
||||||
end
|
end
|
||||||
if fallback then
|
if fallback then
|
||||||
quest = fallback
|
quest = fallback
|
||||||
|
|||||||
Reference in New Issue
Block a user