more debug for this weird problem
[QuestHelper.git] / collect_quest.lua
blobd0910412bae179ad92da1859b01f6a116f3a9446
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
14 local GetSpecBolus
16 local QHCQ
18 local deebey
20 local function RegisterQuestData(category, location, GetQuestLogWhateverInfo)
21 local index = 1
22 local localspot
23 while true do
24 local ilink = GetQuestLogItemLink(category, index)
25 if not ilink then break end
27 if not localspot then if not location["items_" .. category] then location["items_" .. category] = {} end localspot = location["items_" .. category] end
29 local name, tex, num, qual, usa = GetQuestLogWhateverInfo(index)
30 localspot[GetItemType(ilink)] = num
32 --QuestHelper:TextOut(string.format("%s:%d - %d %s %s", category, index, num, tostring(ilink), tostring(name)))
34 index = index + 1
35 end
36 end
38 local complete_suffix = string.gsub(string.gsub(string.gsub(ERR_QUEST_OBJECTIVE_COMPLETE_S, "%%s", ""), "%)", "%%)"), "%(", "%%(")
39 function pin()
40 QuestHelper:TextOut("^.*: (%d+)/(%d+)(" .. complete_suffix .. ")?$")
41 end
43 -- qlookup[questname][objectivename] = {{qid = qid, objid = objid}}
44 local qlookups = {}
46 local function ScanQuests()
48 local selected
49 local index = 1
51 local dbx = {}
53 qlookups = {}
55 while true do
56 if not GetQuestLogTitle(index) then break end
58 local qlink = GetQuestLink(index)
59 if qlink then
60 --QuestHelper:TextOut(qlink)
61 --QuestHelper:TextOut(string.gsub(qlink, "|", "||"))
62 local id, level = GetQuestType(qlink)
63 local title, _, tag, groupcount, _, _, _, daily = GetQuestLogTitle(index)
65 if not qlookups[title] then qlookups[title] = {} end -- gronk
67 --QuestHelper:TextOut(string.format("%s - %d %d", qlink, id, level))
69 if not QHCQ[id] then
70 --if true then
71 if not selected then selected = GetQuestLogSelection() end
72 SelectQuestLogEntry(index)
74 QHCQ[id] = {}
75 QHCQ[id].level = level
77 RegisterQuestData("reward", QHCQ[id], GetQuestLogRewardInfo)
78 RegisterQuestData("choice", QHCQ[id], GetQuestLogChoiceInfo)
80 --QuestHelper:TextOut(string.format("%d", GetNumQuestLeaderBoards(index)))
81 for i = 1, GetNumQuestLeaderBoards(index) do
82 local desc, type = GetQuestLogLeaderBoard(i, index)
83 QHCQ[id][string.format("criteria_%d_text", i)] = desc
84 QHCQ[id][string.format("criteria_%d_type", i)] = type
85 --QuestHelper:TextOut(string.format("%s, %s", desc, type))
86 end
88 QHCQ[id].name = title
89 QHCQ[id].tag = tag
90 QHCQ[id].groupcount = (groupcount or -1)
91 QHCQ[id].daily = (not not daily)
93 if GetQuestLogSpecialItemInfo then
94 local typ = GetQuestLogSpecialItemInfo(index)
95 if typ then typ = GetItemType(typ) end
96 QHCQ[id].special_item = typ or false
97 end
98 end
100 dbx[id] = {}
103 --QuestHelper:TextOut(string.format("%d", GetNumQuestLeaderBoards(index)))
105 for i = 1, GetNumQuestLeaderBoards(index) do
106 local desc, _, done = GetQuestLogLeaderBoard(i, index)
108 if not qlookups[title][desc] then qlookups[title][desc] = {} end
109 table.insert(qlookups[title][desc], {qid = id, obj = i})
111 -- If we wanted to parse everything here, we'd do something very complicated.
112 -- Fundamentally, we don't. We only care if numeric values change or if something goes from "not done" to "done".
113 -- Luckily, the patterns are identical in all cases for this (I think.)
114 local have, needed = string.match(desc, "^.*: (%d+)/(%d+)$")
115 have = tonumber(have)
116 needed = tonumber(needed)
118 --[[QuestHelper:TextOut(desc)
119 QuestHelper:TextOut("^.*: (%d+)/(%d+)(" .. complete_suffix .. ")?$")
120 QuestHelper:TextOut(string.gsub(desc, complete_suffix, ""))
121 QuestHelper:TextOut(string.format("%s %s", tostring(have), tostring(needed)))]]
122 if not have or not needed then
123 have = done and 1 or 0
124 needed = 1 -- okay so we don't really use this unless we're debugging, shut up >:(
127 dbx[id][i] = have
131 index = index + 1
134 if selected then SelectQuestLogEntry(selected) end -- abort abort bzzt bzzt bzzt awoooooooga dive, dive, dive
136 return dbx
139 local eventy = {}
141 local function Looted(message)
142 local ltype = GetItemType(message, true)
143 table.insert(eventy, {time = GetTime(), event = string.format("I%di", ltype)})
144 --if debug_output then QuestHelper:TextOut(string.format("Added event %s", string.format("I%di", ltype))) end
147 local function Combat(_, event, _, _, _, guid)
148 if event ~= "UNIT_DIED" then return end
149 if not IsMonsterGUID(guid) then return end
150 local mtype = GetMonsterType(guid, true)
151 table.insert(eventy, {time = GetTime(), event = string.format("M%dm", mtype)})
152 --if debug_output then QuestHelper:TextOut(string.format("Added event %s", string.format("M%dm", mtype))) end
155 local changed = false
156 local first = true
158 local function Init()
159 first = true
162 local function LogChanged()
163 changed = true
166 local function WatchUpdate() -- we're currently ignoring the ID of the quest that was updated for simplicity's sake.
167 changed = true
170 local function AppendMember(tab, key, dat)
171 tab[key] = (tab[key] or "") .. dat
174 local function StartOrEnd(se, id)
175 local targuid = UnitGUID("target")
176 local chunk = ""
177 if targuid and IsMonsterGUID(targuid) then
178 chunk = string.format("M%dm", GetMonsterType(targuid))
180 chunk = chunk .. GetLoc()
182 AppendMember(QHCQ[id], se, chunk)
183 AppendMember(QHCQ[id], se .. "_spec", GetSpecBolus())
186 local abandoncomplete = ""
187 local abandoncomplete_timestamp = nil
189 local GetQuestReward_Orig = GetQuestReward
190 GetQuestReward = function (...)
191 abandoncomplete = "complete"
192 abandoncomplete_timestamp = GetTime()
193 GetQuestReward_Orig(...)
196 local AbandonQuest_Orig = AbandonQuest
197 AbandonQuest = function ()
198 abandoncomplete = "abandon"
199 abandoncomplete_timestamp = GetTime()
200 AbandonQuest_Orig()
203 local function UpdateQuests()
205 do -- this should once and for all fix this issue
206 local foverride = true
207 for _, _ in pairs(deebey) do
208 foverride = false
210 if UnitLevel("player") == 1 then
211 foverride = false
213 if foverride then foverride = true end
216 if first then deebey = ScanQuests() first = false end
217 if not changed then return end
218 changed = false
220 local tim = GetTime()
222 local noobey = ScanQuests()
224 local traverse = {}
226 local dsize, nsize = QuestHelper:TableSize(deebey), QuestHelper:TableSize(noobey)
228 for k, _ in pairs(deebey) do traverse[k] = true end
229 for k, _ in pairs(noobey) do traverse[k] = true end
231 --[[
232 if QuestHelper:TableSize(deebey) ~= QuestHelper:TableSize(noobey) then
233 QuestHelper:TextOut(string.format("%d %d", QuestHelper:TableSize(deebey), QuestHelper:TableSize(noobey)))
234 end]]
236 while #eventy > 0 and eventy[1].time < GetTime() - 1 do table.remove(eventy, 1) end -- slurp
237 local token
238 local debugtok
240 local diffs = 0
242 for k, _ in pairs(traverse) do
243 if not deebey[k] then
244 -- Quest was acquired
245 if debug_output then QuestHelper:TextOut(string.format("Acquired! Questid %d", k)) end
246 StartOrEnd("start", k)
247 diffs = diffs + 1
249 elseif not noobey[k] then
250 -- Quest was dropped or completed
251 if abandoncomplete == "complete" and abandoncomplete_timestamp + 30 >= GetTime() then
252 if debug_output then QuestHelper:TextOut(string.format("Completed! Questid %d", k)) end
253 StartOrEnd("end", k)
254 abandoncomplete = ""
255 else
256 if debug_output then QuestHelper:TextOut(string.format("Dropped! Questid %d", k)) end
259 diffs = diffs + 1
261 else
262 QuestHelper: Assert(#deebey[k] == #noobey[k], string.format("%d vs %d, %d", #deebey[k], #noobey[k], k))
263 for i = 1, #deebey[k] do
265 --[[
266 if not (noobey[k][i] >= deebey[k][i]) then
267 QuestHelper:TextOut(string.format("%s, %s", type(noobey[k][i]), type(deebey[k][i])))
268 QuestHelper:TextOut(string.format("%d, %d", noobey[k][i], deebey[k][i]))
269 for index = 1, 100 do
270 local qlink = GetQuestLink(index)
271 if qlink then qlink = GetQuestType(qlink) end
272 if qlink == k then
273 QuestHelper:TextOut(GetQuestLogLeaderBoard(i, index))
277 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.
279 if noobey[k][i] > deebey[k][i] then
280 if not token then
281 token = ""
282 for k, v in pairs(eventy) do token = token .. v.event end
283 debugtok = token
284 token = token .. "L" .. GetLoc() .. "l"
287 local ttok = token
288 if noobey[k][i] - 1 ~= deebey[k][i] then
289 ttok = string.format("C%dc", noobey[k][i] - deebey[k][i]) .. ttok
292 AppendMember(QHCQ[k], string.format("criteria_%d_satisfied", i), ttok)
294 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
295 diffs = diffs + 1
301 deebey = noobey
303 --QuestHelper: Assert(diffs <= 5, string.format("excessive quest diffs - delta is %d, went from %d to %d", diffs, dsize, nsize))
304 --QuestHelper:TextOut(string.format("done in %f", GetTime() - tim))
307 local enable_quest_hints = GetBuildInfo():match("0%.1%..*") or (GetBuildInfo():match("3%..*") and not GetBuildInfo():match("3%.0%..*"))
308 QH_filter_hints = false
310 local function MouseoverUnit()
311 QH_filter_hints = false
312 if not enable_quest_hints then return end
314 if GameTooltip:GetUnit() and UnitExists("mouseover") and UnitIsVisible("mouseover") and not UnitIsPlayer("mouseover") and not UnitPlayerControlled("mouseover") then
315 local guid = UnitGUID("mouseover")
317 if not IsMonsterGUID(guid) then return end
319 guid = GetMonsterType(guid)
321 if GetQuestLogSpecialItemInfo then
322 for _, v in pairs(qlookups) do
323 for _, block in pairs(v) do
324 for _, tv in ipairs(block) do
325 if not QHCQ[tv.qid][string.format("criteria_%d_monster_true", tv.obj)] then
326 QHCQ[tv.qid][string.format("criteria_%d_monster_true", tv.obj)] = {}
327 QHCQ[tv.qid][string.format("criteria_%d_monster_false", tv.obj)] = {}
330 QHCQ[tv.qid][string.format("criteria_%d_monster_false", tv.obj)][guid] = (QHCQ[tv.qid][string.format("criteria_%d_monster_false", tv.obj)][guid] or 0) + 1
335 local line = 2
336 local qs
337 local qe
339 while _G["GameTooltipTextLeft" .. line] and _G["GameTooltipTextLeft" .. line]:IsShown() do
340 local r, g, b, a = _G["GameTooltipTextLeft" .. line]:GetTextColor()
341 r, g, b, a = math.floor(r * 255 + 0.5), math.floor(g * 255 + 0.5), math.floor(b * 255 + 0.5), math.floor(a * 255 + 0.5)
342 --print(r, g, b, a)
344 if r == 255 and g == 210 and b == 0 and a == 255 then
345 if not qs then qs = line end
346 else
347 if qs and not qe then qe = line end
349 line = line + 1
352 if qs and not qe then qe = line end
353 if qe then qe = qe - 1 end
355 if qs and qe then
356 local cquest = nil
358 QH_filter_hints = true
360 for i = qs, qe do
361 local lin = _G["GameTooltipTextLeft" .. i]:GetText()
363 if cquest and cquest[lin] then
364 local titem_block = cquest[lin]
365 for _, titem in pairs(titem_block) do
366 QHCQ[titem.qid][string.format("criteria_%d_monster_false", titem.obj)][guid] = (QHCQ[titem.qid][string.format("criteria_%d_monster_false", titem.obj)][guid] or 0) - 1
367 QHCQ[titem.qid][string.format("criteria_%d_monster_true", titem.obj)][guid] = (QHCQ[titem.qid][string.format("criteria_%d_monster_true", titem.obj)][guid] or 0) + 1
369 elseif qlookups[lin] then
370 cquest = qlookups[lin]
371 else
372 QH_filter_hints = false
373 --QuestHelper: Assert()
381 function QH_Collect_Quest_Init(QHCData, API)
382 if not QHCData.quest then QHCData.quest = {} end
383 QHCQ = QHCData.quest
385 GetQuestType = API.Utility_GetQuestType
386 GetItemType = API.Utility_GetItemType
387 IsMonsterGUID = API.Utility_IsMonsterGUID
388 GetMonsterType = API.Utility_GetMonsterType
389 GetSpecBolus = API.Utility_GetSpecBolus
390 QuestHelper: Assert(GetQuestType)
391 QuestHelper: Assert(GetItemType)
392 QuestHelper: Assert(IsMonsterGUID)
393 QuestHelper: Assert(GetMonsterType)
394 QuestHelper: Assert(GetSpecBolus)
396 GetLoc = API.Callback_LocationBolusCurrent
397 QuestHelper: Assert(GetLoc)
399 deebey = ScanQuests()
401 QH_Event("UNIT_QUEST_LOG_CHANGED", LogChanged)
402 QH_Event("QUEST_LOG_UPDATE", UpdateQuests)
403 QH_Event("QUEST_WATCH_UPDATE", WatchUpdate)
405 QH_Event("CHAT_MSG_LOOT", Looted)
406 QH_Event("COMBAT_LOG_EVENT_UNFILTERED", Combat)
408 API.Registrar_TooltipHook(MouseoverUnit)
410 -- Here's a pile of events that seem to trigger during startup that also don't seem like would trigger while questing.
411 -- We'll lose a few quest updates from this, but that's OK.
412 QH_Event("PLAYER_ENTERING_WORLD", Init)
413 QH_Event("UNIT_MODEL_CHANGED", Init)
414 QH_Event("GUILDBANK_UPDATE_WITHDRAWMONEY", Init)
415 QH_Event("UPDATE_TICKET", Init)