Repository navigation
Expand file tree
/
Copy pathScan.lua
More file actions
135 lines (123 loc) · 5.05 KB
/
Copy pathScan.lua
File metadata and controls
135 lines (123 loc) · 5.05 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
-- /yroute scan: ask the server about every quest ID in Data/QuestIDs.lua and keep what it answers.
-- RequestLoadQuestByID answers with QUEST_DATA_LOAD_RESULT; a few requests are kept in flight at a
-- time so the server isn't flooded. Results go to HeadstartScanDB.scan, which a /reload writes to disk:
-- the saved file of the window's addon (Headstart_Options), which is only read in when that addon is
-- loaded - /headstart scan loads it - and not at every login.
local _, YR = ...
-- Where the results are kept (see the top of the file).
local function Store()
if type(_G.HeadstartScanDB) ~= "table" then _G.HeadstartScanDB = {} end
return _G.HeadstartScanDB
end
local IN_FLIGHT = 10
local TIMEOUT = 5 -- seconds before an unanswered request counts as "no answer"
local queue, pos, pending, ticker
local function Read(id)
local r = { t = C_QuestLog.GetTitleForQuestID(id) }
r.ql = C_QuestLog.GetQuestDifficultyLevel(id)
local ok, xp, baseXP = pcall(GetQuestLogRewardXP, id)
if ok then r.xp, r.bxp = xp, baseXP end
local okm, money = pcall(GetQuestLogRewardMoney, id)
if okm then r.m = money end
local tag = C_QuestLog.GetQuestTagInfo(id)
if tag and tag.tagName then r.tag = tag.tagName end
local group = C_QuestLog.GetSuggestedGroupSize(id)
if group and group > 0 then r.grp = group end
local objectives = C_QuestLog.GetQuestObjectives(id)
if objectives and #objectives > 0 then
r.obj = {}
for i, o in ipairs(objectives) do r.obj[i] = { o.text, o.type, o.numRequired } end
end
if C_QuestLog.IsQuestFlaggedCompleted(id) then r.done = true end
return r
end
local events -- made by the first scan; listens for the answers only while one runs
local function Finish()
if ticker then ticker:Cancel() ticker = nil end
events:UnregisterEvent("QUEST_DATA_LOAD_RESULT")
local s = Store().scan
s.finished = time()
local n, named = 0, 0
for _, r in pairs(s.quests) do
n = n + 1
if r.t then named = named + 1 end
end
YR.Print(("scan done: %d answers, %d with a name. /reload to write them to disk."):format(n, named))
end
local function Tick()
local now = GetTime()
for id, asked in pairs(pending) do
if now - asked > TIMEOUT then
pending[id] = nil
Store().scan.noAnswer[id] = true
end
end
local inFlight = 0
for _ in pairs(pending) do inFlight = inFlight + 1 end
while inFlight < IN_FLIGHT and pos <= #queue do
local id = queue[pos]
pos = pos + 1
pending[id] = now
inFlight = inFlight + 1
C_QuestLog.RequestLoadQuestByID(id)
end
if pos % 500 < IN_FLIGHT and pos > 1 then
YR.Print(("scanned %d of %d"):format(pos - 1, #queue))
end
if pos > #queue and inFlight == 0 then Finish() end
end
local function Answered(_, _, id, success)
if not (pending and pending[id]) then return end
pending[id] = nil
local s = Store().scan
if success then s.quests[id] = Read(id) else s.failed[id] = true end
end
function YR:StartScan()
if ticker then YR.Print("already scanning.") return end
-- the list of quest IDs to ask about (Data/QuestIDs.lua) is a developer's file, left out of the release
if type(YR.QUEST_IDS) ~= "table" then
YR.Print("the quest list for scanning isn't in this build of Headstart (Data/QuestIDs.lua, from the repository).")
return
end
queue = {}
for _, part in ipairs({ "route", "new", "rest" }) do
for _, id in ipairs(YR.QUEST_IDS[part]) do queue[#queue + 1] = id end
end
pos, pending = 1, {}
local _, class = UnitClass("player")
local _, race = UnitRace("player")
Store().scan = {
started = time(), build = select(2, GetBuildInfo()), char = YR.CharKey(),
level = UnitLevel("player"), class = class, race = race,
quests = {}, failed = {}, noAnswer = {},
}
-- nothing of the scan exists until one is asked for: no frame at a login that never scans
if not events then
events = CreateFrame("Frame")
events:SetScript("OnEvent", Answered)
end
events:RegisterEvent("QUEST_DATA_LOAD_RESULT")
ticker = C_Timer.NewTicker(0.1, Tick)
YR.Print(("scanning %d quests (quest XP is as seen at level %d)."):format(#queue, UnitLevel("player")))
end
--- Delete the scan's results (they stay in the saved file until then). Not while one is running.
function YR.DeleteScan()
if ticker or not Store().scan then return false end
Store().scan = nil
return true
end
function YR:StopScan()
if not ticker then return end
ticker:Cancel()
ticker = nil
events:UnregisterEvent("QUEST_DATA_LOAD_RESULT")
Store().scan.stopped = time()
YR.Print(("scan stopped at %d of %d. /reload to write what we have."):format(pos - 1, #queue))
end
function YR:ScanStatus()
local s = Store().scan
if not s then YR.Print("no scan yet.") return end
local n = 0
for _ in pairs(s.quests) do n = n + 1 end
YR.Print(("last scan: %d answers, %s."):format(n, ticker and "still running" or date("%Y-%m-%d %H:%M", s.started)))
end