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
20 local function RegisterQuestData(category
, location
, GetQuestLogWhateverInfo
)
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)))
38 local complete_suffix
= string.gsub(string.gsub(string.gsub(ERR_QUEST_OBJECTIVE_COMPLETE_S
, "%%s", ""), "%)", "%%)"), "%(", "%%(")
40 QuestHelper
:TextOut("^.*: (%d+)/(%d+)(" .. complete_suffix
.. ")?$")
43 -- qlookup[questname][objectivename] = {{qid = qid, objid = objid}}
46 local function ScanQuests()
56 if not GetQuestLogTitle(index
) then break end
58 local qlink
= GetQuestLink(index
)
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))
71 if not selected
then selected
= GetQuestLogSelection() end
72 SelectQuestLogEntry(index
)
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))
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
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 >:(
134 if selected
then SelectQuestLogEntry(selected
) end -- abort abort bzzt bzzt bzzt awoooooooga dive, dive, dive
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
158 local function Init()
162 local function LogChanged()
166 local function WatchUpdate() -- we're currently ignoring the ID of the quest that was updated for simplicity's sake.
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")
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()
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
210 if UnitLevel("player") == 1 then
213 if foverride
then foverride
= true end
216 if first
then deebey
= ScanQuests() first
= false end
217 if not changed
then return end
220 local tim
= GetTime()
222 local noobey
= ScanQuests()
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
232 if QuestHelper:TableSize(deebey) ~= QuestHelper:TableSize(noobey) then
233 QuestHelper:TextOut(string.format("%d %d", QuestHelper:TableSize(deebey), QuestHelper:TableSize(noobey)))
236 while #eventy
> 0 and eventy
[1].time
< GetTime() - 1 do table.remove(eventy
, 1) end -- slurp
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
)
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
256 if debug_output
then QuestHelper
:TextOut(string.format("Dropped! Questid %d", k
)) end
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
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
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
282 for k
, v
in pairs(eventy
) do token
= token
.. v
.event
end
284 token
= token
.. "L" .. GetLoc() .. "l"
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
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
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)
344 if r
== 255 and g
== 210 and b
== 0 and a
== 255 then
345 if not qs
then qs
= line
end
347 if qs
and not qe
then qe
= line
end
352 if qs
and not qe
then qe
= line
end
353 if qe
then qe
= qe
- 1 end
358 QH_filter_hints
= true
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
]
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
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
)