QuestHelper_File["collect_quest.lua"] = "1.4.0" QuestHelper_Loadtime["collect_quest.lua"] = GetTime() local debug_output = false if QuestHelper_File["collect_quest.lua"] == "Development Version" then debug_output = true end local IsMonsterGUID local GetMonsterType local GetQuestType local GetItemType local GetLoc local GetSpecBolus local QHCQ local deebey local function RegisterQuestData(category, location, GetQuestLogWhateverInfo) local index = 1 local localspot while true do local ilink = GetQuestLogItemLink(category, index) if not ilink then break end if not localspot then if not location["items_" .. category] then location["items_" .. category] = {} end localspot = location["items_" .. category] end local name, tex, num, qual, usa = GetQuestLogWhateverInfo(index) localspot[GetItemType(ilink)] = num --QuestHelper:TextOut(string.format("%s:%d - %d %s %s", category, index, num, tostring(ilink), tostring(name))) index = index + 1 end end local complete_suffix = string.gsub(string.gsub(string.gsub(ERR_QUEST_OBJECTIVE_COMPLETE_S, "%%s", ""), "%)", "%%)"), "%(", "%%(") function pin() QuestHelper:TextOut("^.*: (%d+)/(%d+)(" .. complete_suffix .. ")?$") end -- qlookup[questname][objectivename] = {{qid = qid, objid = objid}} local qlookups = {} local function ScanQuests() local selected local index = 1 local dbx = {} qlookups = {} while true do if not GetQuestLogTitle(index) then break end local qlink = GetQuestLink(index) if qlink then --QuestHelper:TextOut(qlink) --QuestHelper:TextOut(string.gsub(qlink, "|", "||")) local id, level = GetQuestType(qlink) local title, _, tag, groupcount, _, _, _, daily = GetQuestLogTitle(index) if not qlookups[title] then qlookups[title] = {} end -- gronk --QuestHelper:TextOut(string.format("%s - %d %d", qlink, id, level)) if not QHCQ[id] then --if true then if not selected then selected = GetQuestLogSelection() end SelectQuestLogEntry(index) QHCQ[id] = {} QHCQ[id].level = level RegisterQuestData("reward", QHCQ[id], GetQuestLogRewardInfo) RegisterQuestData("choice", QHCQ[id], GetQuestLogChoiceInfo) --QuestHelper:TextOut(string.format("%d", GetNumQuestLeaderBoards(index))) for i = 1, GetNumQuestLeaderBoards(index) do local desc, type = GetQuestLogLeaderBoard(i, index) QHCQ[id][string.format("criteria_%d_text", i)] = desc QHCQ[id][string.format("criteria_%d_type", i)] = type --QuestHelper:TextOut(string.format("%s, %s", desc, type)) end QHCQ[id].name = title QHCQ[id].tag = tag QHCQ[id].groupcount = (groupcount or -1) QHCQ[id].daily = (not not daily) if GetQuestLogSpecialItemInfo then local typ = GetQuestLogSpecialItemInfo(index) if typ then typ = GetItemType(typ) end QHCQ[id].special_item = typ or false end end dbx[id] = {} --QuestHelper:TextOut(string.format("%d", GetNumQuestLeaderBoards(index))) for i = 1, GetNumQuestLeaderBoards(index) do local desc, typ, done = GetQuestLogLeaderBoard(i, index) -- Some quests have faulty objectives where there is literally NO information... Ignore those objectives. if desc and typ ~= "log" then if not qlookups[title][desc] then qlookups[title][desc] = {} end table.insert(qlookups[title][desc], {qid = id, obj = i}) -- If we wanted to parse everything here, we'd do something very complicated. -- Fundamentally, we don't. We only care if numeric values change or if something goes from "not done" to "done". -- Luckily, the patterns are identical in all cases for this (I think.) local have, needed = string.match(desc, "^.*: (%d+)/(%d+)$") have = tonumber(have) needed = tonumber(needed) --[[QuestHelper:TextOut(desc) QuestHelper:TextOut("^.*: (%d+)/(%d+)(" .. complete_suffix .. ")?$") QuestHelper:TextOut(string.gsub(desc, complete_suffix, "")) QuestHelper:TextOut(string.format("%s %s", tostring(have), tostring(needed)))]] if not have or not needed then have = done and 1 or 0 needed = 1 -- okay so we don't really use this unless we're debugging, shut up >:( end dbx[id][i] = have end end end index = index + 1 end if selected then SelectQuestLogEntry(selected) end -- abort abort bzzt bzzt bzzt awoooooooga dive, dive, dive return dbx end local eventy = {} local function Looted(message) local ltype = GetItemType(message, true) -- Oddly, we get an ltype that has a nil value once in a while, so we ignore it and return. -- Only seems to occur when rolling for something. if ltype == nil then return end -- Just in case... if type(ltype) ~= "number" then error(string.format("Expected a number but got a %s.", type(ltype)) .. "The value is:" .. ltype) end table.insert(eventy, {time = GetTime(), event = string.format("I%di", ltype)}) --if debug_output then QuestHelper:TextOut(string.format("Added event %s", string.format("I%di", ltype))) end end local function Combat(_, event, _, _, _, guid) if event ~= "UNIT_DIED" then return end if not IsMonsterGUID(guid) then return end local mtype = GetMonsterType(guid, true) table.insert(eventy, {time = GetTime(), event = string.format("M%dm", mtype)}) --if debug_output then QuestHelper:TextOut(string.format("Added event %s", string.format("M%dm", mtype))) end end local changed = false local first = true local function Init() first = true end local function LogChanged() changed = true end local function WatchUpdate() -- we're currently ignoring the ID of the quest that was updated for simplicity's sake. changed = true end local function AppendMember(tab, key, dat) tab[key] = (tab[key] or "") .. dat end local function StartOrEnd(se, id) local targuid = UnitGUID("target") local chunk = "" if targuid and IsMonsterGUID(targuid) then chunk = string.format("M%dm", GetMonsterType(targuid)) end chunk = chunk .. GetLoc() AppendMember(QHCQ[id], se, chunk) AppendMember(QHCQ[id], se .. "_spec", GetSpecBolus()) end local abandoncomplete = "" local abandoncomplete_timestamp = nil local GetQuestReward_Orig = GetQuestReward GetQuestReward = function (...) abandoncomplete = "complete" abandoncomplete_timestamp = GetTime() GetQuestReward_Orig(...) end local AbandonQuest_Orig = AbandonQuest AbandonQuest = function () abandoncomplete = "abandon" abandoncomplete_timestamp = GetTime() AbandonQuest_Orig() end local function UpdateQuests() do -- this should once and for all fix this issue local foverride = true for _, _ in pairs(deebey) do foverride = false end if UnitLevel("player") == 1 then foverride = false end if foverride then foverride = true end end if first then deebey = ScanQuests() first = false end if not changed then return end changed = false local tim = GetTime() local noobey = ScanQuests() local traverse = {} local dsize, nsize = QuestHelper:TableSize(deebey), QuestHelper:TableSize(noobey) for k, _ in pairs(deebey) do traverse[k] = true end for k, _ in pairs(noobey) do traverse[k] = true end --[[ if QuestHelper:TableSize(deebey) ~= QuestHelper:TableSize(noobey) then QuestHelper:TextOut(string.format("%d %d", QuestHelper:TableSize(deebey), QuestHelper:TableSize(noobey))) end]] while #eventy > 0 and eventy[1].time < GetTime() - 1 do table.remove(eventy, 1) end -- slurp local token local debugtok local diffs = 0 for k, _ in pairs(traverse) do if not deebey[k] then -- Quest was acquired if debug_output then QuestHelper:TextOut(string.format("Acquired! Questid %d", k)) end StartOrEnd("start", k) diffs = diffs + 1 elseif not noobey[k] then -- Quest was dropped or completed if abandoncomplete == "complete" and abandoncomplete_timestamp + 30 >= GetTime() then if debug_output then QuestHelper:TextOut(string.format("Completed! Questid %d", k)) end StartOrEnd("end", k) abandoncomplete = "" else if debug_output then QuestHelper:TextOut(string.format("Dropped! Questid %d", k)) end end diffs = diffs + 1 else QuestHelper: Assert(#deebey[k] == #noobey[k], string.format("%d vs %d, %d", #deebey[k], #noobey[k], k)) for i = 1, #deebey[k] do --[[ if not (noobey[k][i] >= deebey[k][i]) then QuestHelper:TextOut(string.format("%s, %s", type(noobey[k][i]), type(deebey[k][i]))) QuestHelper:TextOut(string.format("%d, %d", noobey[k][i], deebey[k][i])) for index = 1, 100 do local qlink = GetQuestLink(index) if qlink then qlink = GetQuestType(qlink) end if qlink == k then QuestHelper:TextOut(GetQuestLogLeaderBoard(i, index)) end end end 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. if noobey[k][i] > deebey[k][i] then if not token then token = "" for k, v in pairs(eventy) do token = token .. v.event end debugtok = token token = token .. "L" .. GetLoc() .. "l" end local ttok = token if noobey[k][i] - 1 ~= deebey[k][i] then ttok = string.format("C%dc", noobey[k][i] - deebey[k][i]) .. ttok end AppendMember(QHCQ[k], string.format("criteria_%d_satisfied", i), ttok) 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 diffs = diffs + 1 end end end end deebey = noobey --QuestHelper: Assert(diffs <= 5, string.format("excessive quest diffs - delta is %d, went from %d to %d", diffs, dsize, nsize)) --QuestHelper:TextOut(string.format("done in %f", GetTime() - tim)) end local enable_quest_hints = GetBuildInfo():match("0%.1%..*") or (GetBuildInfo():match("3%..*") and not GetBuildInfo():match("3%.0%..*")) QH_filter_hints = false local function MouseoverUnit() QH_filter_hints = false if not enable_quest_hints then return end if GameTooltip:GetUnit() and UnitExists("mouseover") and UnitIsVisible("mouseover") and not UnitIsPlayer("mouseover") and not UnitPlayerControlled("mouseover") then local guid = UnitGUID("mouseover") if not IsMonsterGUID(guid) then return end guid = GetMonsterType(guid) if GetQuestLogSpecialItemInfo then for _, v in pairs(qlookups) do for _, block in pairs(v) do for _, tv in ipairs(block) do if not QHCQ[tv.qid][string.format("criteria_%d_monster_true", tv.obj)] then QHCQ[tv.qid][string.format("criteria_%d_monster_true", tv.obj)] = {} QHCQ[tv.qid][string.format("criteria_%d_monster_false", tv.obj)] = {} end 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 end end end local line = 2 local qs local qe while _G["GameTooltipTextLeft" .. line] and _G["GameTooltipTextLeft" .. line]:IsShown() do local r, g, b, a = _G["GameTooltipTextLeft" .. line]:GetTextColor() 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) --print(r, g, b, a) if r == 255 and g == 210 and b == 0 and a == 255 then if not qs then qs = line end else if qs and not qe then qe = line end end line = line + 1 end if qs and not qe then qe = line end if qe then qe = qe - 1 end if qs and qe then local cquest = nil QH_filter_hints = true for i = qs, qe do local lin = _G["GameTooltipTextLeft" .. i]:GetText() if cquest and cquest[lin] then local titem_block = cquest[lin] for _, titem in pairs(titem_block) do 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 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 end elseif qlookups[lin] then cquest = qlookups[lin] else QH_filter_hints = false --QuestHelper: Assert() end end end end end end function QH_Collect_Quest_Init(QHCData, API) if not QHCData.quest then QHCData.quest = {} end QHCQ = QHCData.quest GetQuestType = API.Utility_GetQuestType GetItemType = API.Utility_GetItemType IsMonsterGUID = API.Utility_IsMonsterGUID GetMonsterType = API.Utility_GetMonsterType GetSpecBolus = API.Utility_GetSpecBolus QuestHelper: Assert(GetQuestType) QuestHelper: Assert(GetItemType) QuestHelper: Assert(IsMonsterGUID) QuestHelper: Assert(GetMonsterType) QuestHelper: Assert(GetSpecBolus) GetLoc = API.Callback_LocationBolusCurrent QuestHelper: Assert(GetLoc) deebey = ScanQuests() QH_Event("UNIT_QUEST_LOG_CHANGED", LogChanged) QH_Event("QUEST_LOG_UPDATE", UpdateQuests) QH_Event("QUEST_WATCH_UPDATE", WatchUpdate) QH_Event("CHAT_MSG_LOOT", Looted) QH_Event("COMBAT_LOG_EVENT_UNFILTERED", Combat) API.Registrar_TooltipHook(MouseoverUnit) -- Here's a pile of events that seem to trigger during startup that also don't seem like would trigger while questing. -- We'll lose a few quest updates from this, but that's OK. QH_Event("PLAYER_ENTERING_WORLD", Init) QH_Event("UNIT_MODEL_CHANGED", Init) QH_Event("GUILDBANK_UPDATE_WITHDRAWMONEY", Init) QH_Event("UPDATE_TICKET", Init) end