fingers crossed
[QuestHelper.git] / collect_quest.lua
blob227ccadeaf42f1009318093d0640982426d93046
1 QuestHelper_File["collect_quest.lua"] = "Development Version"
2 QuestHelper_Loadtime["collect_quest.lua"] = GetTime()
4 local debug_output = false
5 if QuestHelper_File["collect_quest.lua"] == "Development Version" then debug_output = true end
7 local IsMonsterGUID
8 local GetMonsterType
10 local GetQuestType
11 local GetItemType
13 local GetLoc
15 local QHCQ
17 local deebey
19 local function RegisterQuestData(category, location, GetQuestLogWhateverInfo)
20 local index = 1
21 local localspot
22 while true do
23 local ilink = GetQuestLogItemLink(category, index)
24 if not ilink then break end
26 if not localspot then if not location["items_" .. category] then location["items_" .. category] = {} end localspot = location["items_" .. category] end
28 local name, tex, num, qual, usa = GetQuestLogWhateverInfo(index)
29 localspot[GetItemType(ilink)] = num
31 --QuestHelper:TextOut(string.format("%s:%d - %d %s %s", category, index, num, tostring(ilink), tostring(name)))
33 index = index + 1
34 end
35 end
37 local complete_suffix = string.gsub(string.gsub(string.gsub(ERR_QUEST_OBJECTIVE_COMPLETE_S, "%%s", ""), "%)", "%%)"), "%(", "%%(")
38 function pin()
39 QuestHelper:TextOut("^.*: (%d+)/(%d+)(" .. complete_suffix .. ")?$")
40 end
42 local function ScanQuests() -- make it local once we've debugged it
44 local selected
45 local index = 1
47 local dbx = {}
49 while true do
50 if not GetQuestLogTitle(index) then break end
52 local qlink = GetQuestLink(index)
53 if qlink then
54 --QuestHelper:TextOut(qlink)
55 --QuestHelper:TextOut(string.gsub(qlink, "|", "||"))
56 local id, level = GetQuestType(qlink)
58 --QuestHelper:TextOut(string.format("%s - %d %d", qlink, id, level))
60 if not QHCQ[id] then
61 --if true then
62 if not selected then selected = GetQuestLogSelection() end
63 SelectQuestLogEntry(index)
65 QHCQ[id] = {}
66 QHCQ[id].level = level
68 RegisterQuestData("reward", QHCQ[id], GetQuestLogRewardInfo)
69 RegisterQuestData("choice", QHCQ[id], GetQuestLogChoiceInfo)
71 --QuestHelper:TextOut(string.format("%d", GetNumQuestLeaderBoards(index)))
72 for i = 1, GetNumQuestLeaderBoards(index) do
73 local desc, type = GetQuestLogLeaderBoard(i, index)
74 QHCQ[id][string.format("criteria_%d_text", i)] = desc
75 QHCQ[id][string.format("criteria_%d_type", i)] = type
76 --QuestHelper:TextOut(string.format("%s, %s", desc, type))
77 end
79 local title, _, tag, groupcount, _, _, _, daily = GetQuestLogTitle(index)
81 QHCQ[id].name = title
82 QHCQ[id].tag = tag
83 QHCQ[id].groupcount = (groupcount or -1)
84 QHCQ[id].daily = (not not daily)
85 end
87 dbx[id] = {}
90 --QuestHelper:TextOut(string.format("%d", GetNumQuestLeaderBoards(index)))
92 for i = 1, GetNumQuestLeaderBoards(index) do
93 local desc, _, done = GetQuestLogLeaderBoard(i, index)
95 -- If we wanted to parse everything here, we'd do something very complicated.
96 -- Fundamentally, we don't. We only care if numeric values change or if something goes from "not done" to "done".
97 -- Luckily, the patterns are identical in all cases for this (I think.)
98 local have, needed = string.match(desc, "^.*: (%d+)/(%d+)$")
99 have = tonumber(have)
100 needed = tonumber(needed)
102 --[[QuestHelper:TextOut(desc)
103 QuestHelper:TextOut("^.*: (%d+)/(%d+)(" .. complete_suffix .. ")?$")
104 QuestHelper:TextOut(string.gsub(desc, complete_suffix, ""))
105 QuestHelper:TextOut(string.format("%s %s", tostring(have), tostring(needed)))]]
106 if not have or not needed then
107 have = done and 1 or 0
108 needed = 1 -- okay so we don't really use this unless we're debugging, shut up >:(
111 dbx[id][i] = have
115 index = index + 1
118 if selected then SelectQuestLogEntry(selected) end -- abort abort bzzt bzzt bzzt awoooooooga dive, dive, dive
120 return dbx
123 local eventy = {}
125 local function Looted(message)
126 local ltype = GetItemType(message, true)
127 table.insert(eventy, {time = GetTime(), event = string.format("I%di", ltype)})
128 --if debug_output then QuestHelper:TextOut(string.format("Added event %s", string.format("I%di", ltype))) end
131 local function Combat(_, event, _, _, _, guid)
132 if event ~= "UNIT_DIED" then return end
133 if not IsMonsterGUID(guid) then return end
134 local mtype = GetMonsterType(guid, true)
135 table.insert(eventy, {time = GetTime(), event = string.format("M%dm", mtype)})
136 --if debug_output then QuestHelper:TextOut(string.format("Added event %s", string.format("M%dm", mtype))) end
139 local changed = false
140 local first = true
142 local function Init()
143 first = true
146 local function LogChanged()
147 changed = true
150 local function WatchUpdate() -- we're currently ignoring the ID of the quest that was updated for simplicity's sake.
151 changed = true
154 local function AppendMember(tab, key, dat)
155 tab[key] = (tab[key] or "") .. dat
158 local function StartOrEnd(se, id)
159 local targuid = UnitGUID("target")
160 local chunk = ""
161 if targuid and IsMonsterGUID(targuid) then
162 chunk = string.format("M%dm", GetMonsterType(targuid))
164 chunk = chunk .. GetLoc()
166 AppendMember(QHCQ[id], se, chunk)
169 local abandoncomplete = ""
170 local abandoncomplete_timestamp = nil
172 local GetQuestReward_Orig = GetQuestReward
173 GetQuestReward = function (...)
174 abandoncomplete = "complete"
175 abandoncomplete_timestamp = GetTime()
176 GetQuestReward_Orig(...)
179 local AbandonQuest_Orig = AbandonQuest
180 AbandonQuest = function ()
181 abandoncomplete = "abandon"
182 abandoncomplete_timestamp = GetTime()
183 AbandonQuest_Orig()
186 local function UpdateQuests()
187 if first then deebey = ScanQuests() first = false end
188 if not changed then return end
189 changed = false
191 local tim = GetTime()
193 local noobey = ScanQuests()
195 local traverse = {}
197 local dsize, nsize = QuestHelper:TableSize(deebey), QuestHelper:TableSize(noobey)
199 for k, _ in pairs(deebey) do traverse[k] = true end
200 for k, _ in pairs(noobey) do traverse[k] = true end
202 --[[
203 if QuestHelper:TableSize(deebey) ~= QuestHelper:TableSize(noobey) then
204 QuestHelper:TextOut(string.format("%d %d", QuestHelper:TableSize(deebey), QuestHelper:TableSize(noobey)))
205 end]]
207 while #eventy > 0 and eventy[1].time < GetTime() - 1 do table.remove(eventy, 1) end -- slurp
208 local token
209 local debugtok
211 local diffs = 0
213 for k, _ in pairs(traverse) do
214 if not deebey[k] then
215 -- Quest was acquired
216 if debug_output then QuestHelper:TextOut(string.format("Acquired! Questid %d", k)) end
217 StartOrEnd("start", k)
218 diffs = diffs + 1
220 elseif not noobey[k] then
221 -- Quest was dropped or completed
222 if abandoncomplete == "complete" and abandoncomplete_timestamp + 30 >= GetTime() then
223 if debug_output then QuestHelper:TextOut(string.format("Completed! Questid %d", k)) end
224 StartOrEnd("end", k)
225 abandoncomplete = ""
226 else
227 if debug_output then QuestHelper:TextOut(string.format("Dropped! Questid %d", k)) end
230 diffs = diffs + 1
232 else
233 QuestHelper: Assert(#deebey[k] == #noobey[k])
234 for i = 1, #deebey[k] do
236 --[[
237 if not (noobey[k][i] >= deebey[k][i]) then
238 QuestHelper:TextOut(string.format("%s, %s", type(noobey[k][i]), type(deebey[k][i])))
239 QuestHelper:TextOut(string.format("%d, %d", noobey[k][i], deebey[k][i]))
240 for index = 1, 100 do
241 local qlink = GetQuestLink(index)
242 if qlink then qlink = GetQuestType(qlink) end
243 if qlink == k then
244 QuestHelper:TextOut(GetQuestLogLeaderBoard(i, index))
248 QuestHelper: Assert(noobey[k][i] >= deebey[k][i]) -- man I hope this is true]] -- This entire section can fail if people throw away quest items, or if quest items have a duration that expires. Sigh.
250 if noobey[k][i] > deebey[k][i] then
251 if not token then
252 token = ""
253 for k, v in pairs(eventy) do token = token .. v.event end
254 debugtok = token
255 token = token .. "L" .. GetLoc() .. "l"
258 local ttok = token
259 if noobey[k][i] - 1 ~= deebey[k][i] then
260 ttok = string.format("C%dc", noobey[k][i] - deebey[k][i]) .. ttok
263 AppendMember(QHCQ[k], string.format("criteria_%d_satisfied", i), ttok)
265 if debug_output then QuestHelper:TextOut(string.format("Updated! Questid %d item %d count %d tok %s", k, i, noobey[k][i] - deebey[k][i], debugtok)) end
266 diffs = diffs + 1
272 deebey = noobey
274 QuestHelper: Assert(diffs <= 5, string.format("excessive quest diffs - delta is %d, went from %d to %d", diffs, dsize, nsize))
275 --QuestHelper:TextOut(string.format("done in %f", GetTime() - tim))
278 function QH_Collect_Quest_Init(QHCData, API)
279 if not QHCData.quest then QHCData.quest = {} end
280 QHCQ = QHCData.quest
282 GetQuestType = API.Utility_GetQuestType
283 GetItemType = API.Utility_GetItemType
284 IsMonsterGUID = API.Utility_IsMonsterGUID
285 GetMonsterType = API.Utility_GetMonsterType
286 QuestHelper: Assert(GetQuestType)
287 QuestHelper: Assert(GetItemType)
288 QuestHelper: Assert(IsMonsterGUID)
289 QuestHelper: Assert(GetMonsterType)
291 GetLoc = API.Callback_LocationBolusCurrent
292 QuestHelper: Assert(GetLoc)
294 deebey = ScanQuests()
296 API.Registrar_EventHook("UNIT_QUEST_LOG_CHANGED", LogChanged)
297 API.Registrar_EventHook("QUEST_LOG_UPDATE", UpdateQuests)
298 API.Registrar_EventHook("QUEST_WATCH_UPDATE", WatchUpdate)
300 API.Registrar_EventHook("CHAT_MSG_LOOT", Looted)
301 API.Registrar_EventHook("COMBAT_LOG_EVENT_UNFILTERED", Combat)
303 -- Here's a pile of events that seem to trigger during startup that also don't seem like would trigger while questing.
304 -- We'll lose a few quest updates from this, but that's OK.
305 API.Registrar_EventHook("PLAYER_ENTERING_WORLD", Init)
306 API.Registrar_EventHook("UNIT_MODEL_CHANGED", Init)
307 API.Registrar_EventHook("GUILDBANK_UPDATE_WITHDRAWMONEY", Init)
308 API.Registrar_EventHook("UPDATE_TICKET", Init)