1
0
Bifurcation 0
Ce dépôt a été archivé le 2020-03-15. Vous pouvez voir ses fichiers ou le cloner, mais pas ouvrir de ticket ou de demandes d'ajout, ni soumettre de changements.
questhelperredux/QuestHelper/director_quest.lua
Nathanial.C.Jones 965e8589c9 Everything SEEMS to work, less of course the db. Something I did before screwed everything up.
I implemented a QuestHelper_GetTime function for the next time Blizzard decides to fiddle with the time functions. It returns debugprofilestop() / 1000, to exactly match the precision of GetTime().

I also re-removed references to Cartographer from the rollback.
2012-01-01 03:01:18 +00:00

1066 lignes
35 Kio
Lua

local GetTime = QuestHelper_GetTime
QuestHelper_File["director_quest.lua"] = "4.0.1.$svnversion$"
QuestHelper_Loadtime["director_quest.lua"] = GetTime()
local debug_output = false
if QuestHelper_File["director_quest.lua"] == "Development Version" then debug_output = true end
--[[
Little bit of explanation here.
The db layer dumps things out in DB format. This isn't immediately usable for our routing engine. We convert this to an intermediate "metaobjective" format that the routing engine can use, as well as copying anything that needs to be copied. This also allows us to modify our metaobjective tables as we see fit, rather than doing nasty stuff to keep the original objectives intact.
It's worth mentioning that, completely accidentally, everything it requests from the DB is deallocated rapidly - it doesn't keep any references to the original DB objects around. This is unintentional, but kind of neat. It's not worth preserving, but it doesn't really have to be "fixed" either.
]]
local function copy(tab)
local tt = {}
for _, v in ipairs(tab) do
table.insert(tt, v)
end
return tt
end
local function copy_without_last(tab)
local tt = {}
for _, v in ipairs(tab) do
table.insert(tt, v)
end
table.remove(tt)
return tt
end
local function AppendObjlinks(target, source, tooltips, icon, last_name, map_lines, tooltip_lines, seen)
if not seen then seen = {} end
if not map_lines then map_lines = {} end
if not tooltip_lines then tooltip_lines = {} end
QuestHelper: Assert(not seen[source])
if seen[source] then return end
seen[source] = true
if source.loc then
if target then
for m, v in ipairs(source.loc) do
QuestHelper: Assert(target)
QuestHelper: Assert(QuestHelper_ParentLookup)
-- Ugly database hack
if v.p == 26 then v.p = 48 end -- Alterac Mountains merged to Hillsbrad Foothills
if v.p == 38 then v.p = 168 end -- Ditto Stranglethorn
-- end hack
QuestHelper: Assert(QuestHelper_ParentLookup[v.p], v.p)
table.insert(target, {loc = {x = v.x, y = v.y, c = QuestHelper_ParentLookup[v.p], p = v.p}, path_desc = copy(map_lines), icon_id = icon or 6})
end
end
target = nil -- if we have a "source" as well, then we want to plow through it for tooltip data, but we don't want to add targets for it
end
for _, v in ipairs(source) do
local dbgi = DB_GetItem(v.sourcetype, v.sourceid, nil, true)
local licon
--print(v.sourcetype, v.sourceid, v.type)
if v.sourcetype == "monster" then
table.insert(map_lines, QHFormat("OBJECTIVE_SLAY", dbgi.name or QHText("OBJECTIVE_UNKNOWN_MONSTER")))
table.insert(tooltip_lines, 1, QHFormat("TOOLTIP_SLAY", source.name or "nothing"))
licon = 1
elseif v.sourcetype == "item" then
table.insert(map_lines, QHFormat("OBJECTIVE_LOOT", dbgi.name or QHText("OBJECTIVE_ITEM_UNKNOWN")))
table.insert(tooltip_lines, 1, QHFormat("TOOLTIP_LOOT", source.name or "nothing"))
licon = 2
elseif v.sourcetype == "object" then
table.insert(map_lines, QHFormat("OBJECTIVE_OPEN", dbgi.name or QHText("OBJECTIVE_ITEM_UNKNOWN")))
table.insert(tooltip_lines, 1, QHFormat("TOOLTIP_OPEN", source.name or "nothing"))
licon = 2
else
table.insert(map_lines, string.format("unknown %s (%s/%s)", tostring(dbgi.name), tostring(v.sourcetype), tostring(v.sourceid)))
table.insert(tooltip_lines, 1, string.format("unknown %s (%s/%s)", tostring(last_name), tostring(v.sourcetype), tostring(v.sourceid)))
licon = 3
end
tooltips[string.format("%s@@%s", v.sourcetype, v.sourceid)] = copy_without_last(tooltip_lines)
AppendObjlinks(target, dbgi, tooltips, icon or licon, source.name, map_lines, tooltip_lines, seen)
table.remove(tooltip_lines, 1)
table.remove(map_lines)
DB_ReleaseItem(dbgi)
end
seen[source] = false
end
local function horribledupe(from)
if not from then return nil end
local rv = {}
for k, v in pairs(from) do
if k == "__owner" then
elseif type(v) == "table" then
rv[k] = horribledupe(v)
else
rv[k] = v
end
end
return rv
end
local quest_list = setmetatable({}, {__mode="k"})
local QuestCriteriaWarningBroadcast
local function GetQuestMetaobjective(questid, lbcount, qindex)
if not quest_list[questid] then
local q = DB_GetItem("quest", questid, true, true)
if not lbcount then
QuestHelper: TextOut("Missing lbcount, guessing wildly")
if q and q.criteria then
lbcount = 0
for k, v in ipairs(q.criteria) do
lbcount = math.max(lbcount, k)
end
else
lbcount = 0 -- heh
end
end
-- just doublechecking here
if not QuestCriteriaWarningBroadcast and q and q.criteria then for k, v in pairs(q.criteria) do
if type(k) == "number" and k > lbcount then
--QuestHelper:TextOut(string.format("Too many stored objectives for this quest, please report on the Questhelper homepage (%s %s %s)", questid, lbcount, k)) -- we're just going to hide this for now
if qindex then
QuestHelper_ErrorCatcher_ExplicitError(false, string.format("Too many stored objectives (%s %s %s %s)", questid, lbcount, k, select(1, GetQuestLogTitle(qindex))))
else
QuestHelper_ErrorCatcher_ExplicitError(false, string.format("Too many stored objectives (%s %s %s %s)", questid, lbcount, k, v))
end
QuestCriteriaWarningBroadcast = true
end
end end
local ite = {type_quest = {__backlink = ite}} -- we don't want to mutate the existing quest data. backlink exists only for nasty GC reasons
ite.desc = string.format("Quest %s", q and q.name or "(unknown)") -- this gets changed later anyway
for i = 1, lbcount do
local ttx = {}
local desc, typ, done = nil, nil, nil
if qindex > 0 then
desc, typ, done = GetQuestLogLeaderBoard(i, qindex)
end
if qindex < 0 or (desc and typ ~= "log") then -- Ignore if no description.
--QuestHelper:TextOut(string.format("critty %d %d", k, c.loc and #c.loc or -1))
--[[
if done then
print(string.format("Quest %s, Objective %s('%s') is done.", qindex, i, desc))
else
print(string.format("Quest %s, Objective %s('%s') is not done.", qindex, i, desc))
end
]]
ttx.tooltip_canned = {}
if q and q.criteria and q.criteria[i] then
--print("Appending criteria", questid, i)
AppendObjlinks(ttx, q.criteria[i], ttx.tooltip_canned)
--print("Done")
if debug_output and q.criteria[i].loc and #q.criteria[i] > 0 then
QuestHelper:TextOut(string.format("Wackyquest %d/%d", questid, i))
end
ttx.solid = horribledupe(q.criteria[i].solid)
end
if #ttx == 0 then
table.insert(ttx, {loc = {x = 5000, y = 5000, c = 0, p = 2}, icon_id = 7, type_quest_unknown = true, map_desc = {"Unknown"}}) -- this is Ashenvale, for no particularly good reason
ttx.type_quest_unknown = true
end
for idx, v in ipairs(ttx) do
v.desc = string.format("Criteria %d", i)
v.why = ite
v.cluster = ttx
v.type_quest = ite.type_quest
end
for k, v in pairs(ttx.tooltip_canned) do
ttx.tooltip_canned[k] = {ttx.tooltip_canned[k], ttx} -- we're gonna be handing out this table to other modules, so this isn't as dumb as it looks
end
ite[i] = ttx
end
end
do
local ttx = {type_quest_finish = true}
--QuestHelper:TextOut(string.format("finny %d", q.finish.loc and #q.finish.loc or -1))
if q and q.finish and q.finish.loc then
ttx.solid = horribledupe(q.finish.solid)
for m, v in ipairs(q.finish.loc) do
--print(v.rc, v.rz)
--print(QuestHelper_IndexLookup[v.rc])
--print(QuestHelper_IndexLookup[v.rc][v.rz])
-- Ugly database hack
if v.p == 26 then v.p = 48 end
if v.p == 38 then v.p = 168 end
-- end hack
table.insert(ttx, {desc = "Turn in quest", why = ite, loc = {x = v.x, y = v.y, c = QuestHelper_ParentLookup[v.p], p = v.p}, tracker_hidden = true, cluster = ttx, icon_id = 7, type_quest = ite.type_quest})
end
end
if #ttx == 0 then
table.insert(ttx, {desc = "Turn in quest", why = ite, loc = {x = 5000, y = 5000, c = 0, p = 2}, tracker_hidden = true, cluster = ttx, icon_id = 7, type_quest = ite.type_quest, type_quest_unknown = true}) -- this is Ashenvale, for no particularly good reason
ttx.type_quest_unknown = true
end
ite.finish = ttx
end
quest_list[questid] = ite
if q then DB_ReleaseItem(q) end
end
return quest_list[questid]
end
local function GetQuestType(link)
return tonumber(string.match(link,
"^|cff%x%x%x%x%x%x|Hquest:(%d+):[%d-]+|h%[[^%]]*%]|h|r$"
)), tonumber(string.match(link,
"^|cff%x%x%x%x%x%x|Hquest:%d+:([%d-]+)|h%[[^%]]*%]|h|r$"
))
end
local update = true
local function UpdateTrigger()
update = true
end
-- It's possible that things end up garbage-collected and we end up with different tables than we expect. This is something that the entire system is kind of prone to. The solution's pretty easy - we just have to store them ourselves while we're using them.
local active_db = {}
local objective_parse_table = {
item = function (txt) return QuestHelper:convertPattern(QUEST_OBJECTS_FOUND)(txt) end,
object = function (txt) return QuestHelper:convertPattern(QUEST_OBJECTS_FOUND)(txt) end, -- why does this even exist
monster = function (txt) return QuestHelper:convertPattern(QUEST_MONSTERS_KILLED)(txt) end,
event = function (txt, done) return txt, (done and 1 or 0), 1 end, -- It appears that events are only used for things which can only happen once.
reputation = function (txt) return QuestHelper:convertPattern(QUEST_FACTION_NEEDED)(txt) end, -- :ughh:
player = function (txt) return QuestHelper:convertPattern(QUEST_MONSTERS_KILLED)(txt) end, -- We're using monsters here in the hopes that it follows the same pattern. I'd rather not try to find the exact right version of "player" in the locale files, though PLAYER might be it.
spell = function (txt, done) return txt, (done and 1 or 0), 1 end, -- It appears that spells are only used for learning a spell.
}
local function objective_parse(typ, txt, done)
QuestHelper:Assert(typ, "We don't have a type")
if ( objective_parse_table[typ] == nil ) then
QuestHelper_ErrorCatcher_ExplicitError(false, "We don't know about type '" .. tostring(typ) .. "'")
return
end
QuestHelper:Assert(objective_parse_table[typ], "We don't know about type '" .. tostring(typ) .. "'")
local pt, target, have, need = typ, objective_parse_table[typ](txt, done)
if not target then
-- well, that didn't work
target, have, need = string.match(txt, "^%s*(.-)%s*:%s*(.-)%s*/%s*(.-)%s*$")
pt = "fallback"
--QuestHelper:TextOut(string.format("%s rebecomes %s/%s/%s", tostring(title), tostring(target), tostring(have), tostring(need)))
end
if not target then
target, have, need = string.match(txt, "^%s*(.-)%s*$"), (done and 1 or 0), 1
--QuestHelper:TextOut(string.format("%s rerebecomes %s/%s/%s", tostring(title), tostring(target), tostring(have), tostring(need)))
end
QuestHelper: Assert(target) -- This will fail repeatedly. Come on. We all know it.
QuestHelper: Assert(have)
QuestHelper: Assert(need) -- As will these.
if tonumber(have) then have = tonumber(have) end
if tonumber(need) then need = tonumber(need) end
return pt, target, have, need
end
local function clamp(v)
if v < 0 then return 0 elseif v > 255 then return 255 else return v end
end
local function colorlerp(position, r1, g1, b1, r2, g2, b2)
local antip = 1 - position
return string.format("|cff%02x%02x%02x", clamp((r1 * antip + r2 * position) * 255), clamp((g1 * antip + g2 * position) * 255), clamp((b1 * antip + b2 * position) * 255))
end
-- We're just gonna do the same thing QH originally did - red->yellow->green.
local function difficulty_color(position)
if position < 0 then position = 0 end
if position > 1 then position = 1 end
return (position < 0.5) and colorlerp(position * 2, 1, 0, 0, 1, 1, 0) or colorlerp(position * 2 - 1, 1, 1, 0, 0, 1, 0)
end
local function MakeQuestTitle(title, level)
local plevel = UnitLevel("player") -- meh, should probably cache this, buuuuut
local grayd
if plevel >= 60 then
grayd = 9
elseif plevel >= 40 then
grayd = plevel / 5 + 1
else
grayd = plevel / 10 + 5
end
local isgray = (plevel - floor(grayd) >= level)
local ccode = isgray and "|cffb0b0b0" or difficulty_color(1 - ((level - plevel) / grayd + 1) / 2)
local qlevel = string.format("[%d] ", level)
local ret = title
if QuestHelper_Pref.track_level then ret = qlevel .. ret end
if QuestHelper_Pref.track_qcolour then ret = ccode .. ret end
return ret
end
local function MakeQuestObjectiveTitle(progress, target)
if not progress then return nil end
local player = UnitName("player")
local pt, pd = 0, 0
for _, v in pairs(progress) do
pt = pt + 1
if v[3] == 1 then pd = pd + 1 end
end
local ccode
local status
local party
local party_show = false
local party_compact = false
if progress[player] then
local have, need = tonumber(progress[player][1]), tonumber(progress[player][2])
ccode = difficulty_color(progress[player][3])
if have and need then
if need > 1 then
status = string.format("%d/%d", have, need)
party_compact = true
end
else
status = string.format("%s/%s", progress[player][1], progress[player][2])
party_compact = true
end
if pt > 1 then party_show = true end
elseif pt == 0 then
ccode = difficulty_color(1) -- probably just in the process of being removed from the tracker
status = "Complete"
else
ccode = difficulty_color(pd / pt)
party_show = true
end
if party_show then
if party_compact then
party = string.format("(P: %d/%d)", pd, pt)
else
party = string.format("Party %d/%d", pd, pt)
end
end
if QuestHelper_Pref.track_ocolour then
target = ccode .. target
end
if status or party then
target = target .. ":"
end
if status then
target = target .. " " .. status
end
if party then
target = target .. " " .. party
end
return target
end
local function Clicky(index)
ShowUIPanel(QuestLogFrame)
QuestLog_SetSelection(index)
QuestLog_Update()
end
local dontknow = {
name = "director_quest_unknown_objective",
no_exception = true,
no_disable = true,
friendly_reason = QHText("UNKNOWN_OBJ"),
}
-- InsertedItem[item] = {"list", "of", "reasons"}
local InsertedItems = {}
local TooltipType = {}
local Unknowning = {}
local Unknowned = {}
local in_pass = nil
local function SetTooltip(item, typ)
--print("stt", item, typ, item.tooltip_defer_questobjective)
if TooltipType[item] == typ and typ ~= "defer" and not item.tooltip_defer_questobjective_last then return end
if TooltipType[item] == "defer" and typ == "defer" and (not item.tooltip_defer_questobjective_last or item.tooltip_defer_questobjective_last == item.tooltip_defer_questobjective) then return end -- sigh
if TooltipType[item] == "canned" then
QuestHelper: Assert(item.tooltip_canned)
QH_Tooltip_Canned_Remove(item.tooltip_canned)
elseif TooltipType[item] == "defer" then
QuestHelper: Assert(item.tooltip_defer_questname_last)
--print("remove", item.tooltip_defer_questname_last, item.tooltip_defer_questobjective_last, item.tooltip_defer_questobjective)
if item.tooltip_defer_questobjective_last then
QH_Tooltip_Defer_Remove(item.tooltip_defer_questname_last, item.tooltip_defer_questobjective_last, item.tooltip_defer_token_last)
else
QH_Tooltip_Defer_Remove(item.tooltip_defer_questname_last, item.tooltip_defer_questobjective, item.tooltip_defer_token_last)
end
elseif TooltipType[item] == nil then
else
QuestHelper: Assert(false)
end
item.tooltip_defer_questobjective_last = nil
item.tooltip_defer_questname_last = nil -- if it was anything, it is not now
item.tooltip_defer_token_last = nil
if typ == "canned" then
QuestHelper: Assert(item.tooltip_canned)
QH_Tooltip_Canned_Add(item.tooltip_canned)
elseif typ == "defer" then
QuestHelper: Assert(not not item.tooltip_defer_questobjective == not item.type_quest_finish) -- hmmm
--print("add", item.tooltip_defer_questname, item.tooltip_defer_questobjective)
QuestHelper: Assert(item.tooltip_defer_questname)
item.tooltip_defer_token_last = {{}, item}
QH_Tooltip_Defer_Add(item.tooltip_defer_questname, item.tooltip_defer_questobjective, item.tooltip_defer_token_last)
item.tooltip_defer_questname_last = item.tooltip_defer_questname
item.tooltip_defer_questobjective_last = item.tooltip_defer_questobjective
elseif typ == nil then
else
QuestHelper: Assert(false)
end
TooltipType[item] = typ
end
local function StartInsertionPass(id)
QuestHelper: Assert(not in_pass)
in_pass = id
QH_Timeslice_PushUnyieldable()
for k, v in pairs(InsertedItems) do
v[id] = nil
if k.progress then
k.progress[id] = nil
local desc = MakeQuestObjectiveTitle(k.progress, k.target)
for _, v in ipairs(k) do
v.tracker_desc = desc or "(no description available)"
end
end
-- if these are needed to remove, they'll be stored in last, and this way they'll be obliterated if the user doesn't have that actual quest
if id == UnitName("player") then
k.tooltip_defer_questname = nil
k.tooltip_defer_questobjective = nil
end
end
end
local function RefreshItem(id, item, required)
--if not required and math.random() < 0.2 then return false end -- ha ha bzzzzt
QuestHelper: Assert(in_pass == id)
local added = false
if not InsertedItems[item] then
QH_Route_ClusterAdd(item)
--QH_Route_SetClusterPriority(item, math.random(5))
added = true
InsertedItems[item] = {}
end
InsertedItems[item][id] = true
if item.tooltip_defer_questname then
SetTooltip(item, "defer")
elseif item.tooltip_canned then
SetTooltip(item, "canned")
else
SetTooltip(item, nil)
end
if item.type_quest_unknown then table.insert(Unknowning, item) end
local desc = MakeQuestObjectiveTitle(item.progress, item.target)
for _, v in ipairs(item) do
v.tracker_desc = desc or "(no description available)"
end
return added
end
local function EndInsertionPass(id)
QuestHelper: Assert(in_pass == id)
local rem = QuestHelper:CreateTable("ip rem")
for k, v in pairs(InsertedItems) do
local has = false
for _, _ in pairs(v) do
has = true
break
end
if not has then
QH_Tracker_Unpin(k[1], true)
QH_Route_ClusterRemove(k)
rem[k] = true
SetTooltip(k, nil)
end
end
QH_Tracker_Rescan()
for k, _ in pairs(rem) do
InsertedItems[k] = nil
end
QuestHelper:ReleaseTable(rem)
-- this is all so we don't spam the system with multiple ignores, since that currently causes an early routing exit
for k in pairs(Unknowned) do
Unknowned[k] = false
end
for _, v in ipairs(Unknowning) do
if Unknowned[v] == nil then
QH_Route_IgnoreCluster(v, dontknow)
end
Unknowned[v] = true
end
while table.remove(Unknowning) do end
local need_rescan = false
local new_unknowned = QuestHelper:CreateTable("unk")
for k, v in pairs(Unknowned) do
if v then new_unknowned[k] = true end
end
QuestHelper:ReleaseTable(Unknowned)
Unknowned = new_unknowned
QH_Timeslice_PopUnyieldable()
in_pass = nil
--QH_Tooltip_Defer_Dump()
end
function QuestProcessor(user_id, db, title, level, group, variety, groupsize, watched, complete, lbcount, timed)
db.desc = title
db.tracker_desc = MakeQuestTitle(title, level)
db.type_quest.objectives = lbcount
db.type_quest.level = level
db.type_quest.done = (complete == 1)
db.type_quest.variety = variety
db.type_quest.groupsize = groupsize
db.type_quest.title = title
local turnin
local turnin_new
-- This is our "quest turnin" objective, which is currently being handled separately for no particularly good reason.
if db.finish and #db.finish > 0 then
for _, v in ipairs(db.finish) do
v.map_highlight = (complete == 1)
end
turnin = db.finish
--print("turnin:", turnin.tooltip_defer_questname)
if RefreshItem(user_id, turnin, true) then
turnin_new = true
for k, v in ipairs(turnin) do
v.tracker_clicked = function () Clicky(lindex) end
v.map_desc = {QHFormat("OBJECTIVE_REASON_TURNIN", title)}
end
end
if watched ~= "(ignore)" then QH_Tracker_SetPin(db.finish[1], watched, true) end
end
-- These are the individual criteria of the quest. Remember that each criteria can be represented by multiple routing objectives.
for i = 1, lbcount do
if db[i] then
local pt, pd, have, need = objective_parse(db[i].temp_typ, db[i].temp_desc, db[i].temp_done)
local dline
if pt == "item" or pt == "object" then
dline = QHFormat("OBJECTIVE_REASON", QHText("ACQUIRE_VERB"), pd, title)
elseif pt == "monster" then
dline = QHFormat("OBJECTIVE_REASON", QHText("SLAY_VERB"), pd, title)
else
dline = QHFormat("OBJECTIVE_REASON_FALLBACK", pd, title)
end
if not db[i].progress then
db[i].progress = {}
end
if type(have) == "number" and type(need) == "number" then
db[i].progress[db[i].temp_person] = {have, need, have / need}
else
db[i].progress[db[i].temp_person] = {have, need, db[i].temp_done and 1 or 0} -- it's only used for the coloring anyway
end
local _, target = objective_parse(db[i].temp_typ, db[i].temp_desc)
db[i].target = target
db[i].desc = QHFormat("TOOLTIP_QUEST", title)
for k, v in ipairs(db[i]) do
v.desc = db[i].temp_desc
v.tracker_clicked = db.tracker_clicked
v.progress = db[i].progress
if v.path_desc then
v.map_desc = copy(v.path_desc)
v.map_desc[1] = dline
else
v.map_desc = {dline}
end
end
-- This is the snatch of code that actually adds it to routing.
if not db[i].temp_done and #db[i] > 0 then
if RefreshItem(user_id, db[i]) then
if turnin then QH_Route_ClusterRequires(turnin, db[i]) end
end
if watched ~= "(ignore)" then QH_Tracker_SetPin(db[i][1], watched, true) end
end
db[i].temp_desc, db[i].temp_typ, db[i].temp_done = nil, nil, nil
end
end
if turnin_new and timed then
QH_Route_SetClusterPriority(turnin, -1)
end
end
function SerItem(item)
local rtx
if type(item) == "boolean" then
rtx = "b" .. (item and "t" or "f")
elseif type(item) == "number" then
rtx = "n" .. tostring(item)
elseif type(item) == "string" then
rtx = "s" .. item:gsub("\\", "\\\\"):gsub(":", "\\;")
elseif type(item) == "nil" then
rtx = "0"
else
print(type(item), item)
QuestHelper: Assert()
end
return rtx
end
function DeSerItem(item)
local t = item:sub(1, 1)
local d = item:sub(2)
if t == "b" then
return (d == "t")
elseif t == "n" then
return tonumber(d)
elseif t == "s" then
return d:gsub("\\;", ":"):gsub("\\\\", "\\")
elseif t == "0" then
return nil
else
QuestHelper: Assert()
end
end
local function Serialize(...)
local sx
for i = 1, select("#", ...) do
if sx then sx = sx .. ":" else sx = "" end
sx = sx .. SerItem(select(i, ...))
end
QuestHelper: Assert(sx)
return sx
end
local function SAM(msg, chattype, target)
--QuestHelper: TextOut(string.format("%s/%s: %s", chattype, tostring(target), msg))
local thresh = 245
local msgsize = 240
if #msg > thresh then
for i = 1, #msg, msgsize do
local prefx = "x:"
if i == 1 then prefx = "v:" elseif i + msgsize > #msg then prefx = "X:" end
SAM(prefx .. msg:sub(i, i + msgsize - 1), chattype, target)
end
else
ChatThrottleLib:SendAddonMessage("BULK", "QHpr", msg, chattype, target, "QHpr")
end
end
-- sigh.
function is_uncached(typ, txt, done)
if not txt then return true end
if txt == "" then return true end
if txt:match("^ : %d+/%d+$") then return true end
local _, target = objective_parse(typ, txt, done)
if target == "" or target == " " then return true end
return false
end
-- qid, chunk
local current_chunks = {}
-- "log" is a synthetic objective that Blizzard tossed in for god only knows what reason, so we just pretend it doesn't exist
local function GetEffectiveNumQuestLeaderBoards(index)
local v = GetNumQuestLeaderBoards(index)
if v ~= 1 then return v end
if select(2, GetQuestLogLeaderBoard(1, index)) == "log" then
return 0
end
return 1
end
-- Here's the core update function
function QH_UpdateQuests(force)
if not DB_Ready() then return end
QH_Timeslice_PushUnyieldable()
if update or force then -- Sometimes (usually) we don't actually update
--local index = 1
local player = UnitName("player")
if not player then return end -- bzzt, try again later
StartInsertionPass(player)
local next_chunks = {}
local first = true
local numEntries, numQuests = GetNumQuestLogEntries()
-- This begins the main update loop that loops through all of the quests
for index=1,numEntries do
local title, level, variety, groupsize, _, _, complete = GetQuestLogTitle(index)
if title then
title = title:match("%[.*%] (.*)") or title
local qlink = GetQuestLink(index)
if qlink then -- If we don't have a quest link, it's not really a quest
local id = GetQuestType(qlink)
--if first then id = 13836 else id = nil end
if id then -- If we don't have a *valid* quest link, give up
local lbcount = GetEffectiveNumQuestLeaderBoards(index)
local db = GetQuestMetaobjective(id, lbcount, index) -- This generates the above-mentioned metaobjective, including doing the database lookup.
QuestHelper: Assert(db)
local watched = IsQuestWatched(index)
-- The section in here, in other words, is: we have a metaobjective (which may be a new one, or may not be), and we're trying to attach things to our routing engine. Now is where the real work begins! (many conditionals deep)
local lindex = index
db.tracker_clicked = function () Clicky(lindex) end
db.type_quest.index = index
local timidx = 1
while true do
local timer = GetQuestIndexForTimer(timidx)
if not timer then timidx = nil break end
if timer == index then break end
timidx = timidx + 1
end
local timed = not not timidx
--print(id, title, level, groupsize, variety, groupsize, complete, timed)
local chunk = "q:" .. Serialize(id, title, level, groupsize, variety, groupsize, complete, timed)
for i = 1, lbcount do
local temp_desc, temp_typ, temp_done = GetQuestLogLeaderBoard(i, index)
if temp_desc and temp_typ ~= "log" then
QuestHelper: Assert(db[i])
db[i].temp_desc, db[i].temp_typ, db[i].temp_done = GetQuestLogLeaderBoard(i, index)
--[[if not db[i].temp_desc or is_uncached(db[i].temp_typ, db[i].temp_desc, db[i].temp_done) then
db[i].temp_desc = string.format("(missing description %d)", i)
end]]
db[i].temp_person = player
db[i].tooltip_defer_questname = title
db[i].tooltip_defer_questobjective = db[i].temp_desc -- yoink
QuestHelper: Assert(db[i].tooltip_defer_questobjective) -- hmmm
chunk = chunk .. ":" .. Serialize(db[i].temp_desc, db[i].temp_typ, db[i].temp_done)
end
end
db.finish.tooltip_defer_questname = title -- we're using this as our fallback right now
next_chunks[id] = chunk
QuestProcessor(player, db, title, level, groupsize, variety, groupsize, watched, complete, lbcount, timed)
end
first = false
end
end
end
EndInsertionPass(player)
QH_Route_Filter_Rescan(nil, true) -- 'cause filters may also change, but let's not bother getting too excited about it
if not QuestHelper_Pref.solo and QuestHelper_Pref.share then
for k, v in pairs(next_chunks) do
if current_chunks[k] ~= v then
SAM(v, "PARTY")
end
end
for k, v in pairs(current_chunks) do
if not next_chunks[k] then
SAM(string.format("q:n%d", k), "PARTY")
end
end
end
current_chunks = next_chunks
--update = false
end
QH_Timeslice_PopUnyieldable()
end
-- comm_packets[user][qid] = data
local comm_packets = {}
local function RefreshUserComms(user)
StartInsertionPass(user)
if comm_packets[user] then for _, dat in pairs(comm_packets[user]) do
local id, title, level, group, variety, groupsize, complete, timed = dat[1], dat[2], dat[3], dat[4], dat[5], dat[6], dat[7], dat[8]
local objstart = 9
local obj = {}
while true do
if dat[#obj * 3 + objstart] == nil and dat[#obj * 3 + objstart + 1] == nil and dat[#obj * 3 + objstart + 2] == nil then break end
table.insert(obj, {dat[#obj * 3 + objstart], dat[#obj * 3 + objstart + 1], dat[#obj * 3 + objstart + 2]})
end
local lbcount = #obj
local db = GetQuestMetaobjective(id, lbcount, -1) -- This generates the above-mentioned metaobjective, including doing the database lookup.
QuestHelper: Assert(db)
for i = 1, lbcount do
db[i].temp_desc, db[i].temp_typ, db[i].temp_done, db[i].temp_person = obj[i][1], obj[i][2], obj[i][3], user
end
QuestProcessor(user, db, title, level, group, variety, groupsize, "(ignore)", complete, lbcount, false)
end end
EndInsertionPass(user)
QH_Route_Filter_Rescan() -- 'cause filters may also change
end
function QH_InsertCommPacket(user, data)
local q, chunk = data:match("([^:]+):(.*)")
if q ~= "q" then return end
local dat = {}
local idx = 1
for item in chunk:gmatch("([^:]+)") do
dat[idx] = DeSerItem(item)
idx = idx + 1
end
if not comm_packets[user] then comm_packets[user] = {} end
if idx == 2 then
comm_packets[user][dat[1]] = nil
else
comm_packets[user][dat[1]] = dat
end
-- refresh the comms
RefreshUserComms(user)
end
local function QH_DumpCommUser(user)
comm_packets[user] = nil
RefreshUserComms(user)
end
QH_Event("PLAYER_ENTERING_WORLD", UpdateTrigger)
QH_Event("UNIT_QUEST_LOG_CHANGED", UpdateTrigger)
QH_Event("QUEST_LOG_UPDATE", QH_UpdateQuests)
-- We don't return anything here, but I don't think that's actually an issue - those functions don't return anything anyway. Someday I'll regret writing this. Delay because of beql which is a bitch.
QH_AddNotifier(GetTime() + 5, function ()
local aqw_orig = AddQuestWatch
AddQuestWatch = function(...)
aqw_orig(...)
QH_UpdateQuests(true)
end
local rqw_orig = RemoveQuestWatch
RemoveQuestWatch = function(...)
rqw_orig(...)
QH_UpdateQuests(true)
end
end)
-- We seem to end up out of sync sometimes. Why? I'm not sure. Maybe my current events aren't reliable. So let's just scan every five seconds and see what happens, scanning is fast and efficient anyway.
--[[local function autonotify()
QH_UpdateQuests(true)
QH_AddNotifier(GetTime() + 5, autonotify)
end
QH_AddNotifier(GetTime() + 30, autonotify)]]
local old_playerlist = {}
function QH_Questcomm_Sync()
if not (not QuestHelper_Pref.solo and QuestHelper_Pref.share) then
old_playerlist = {}
return
end
local playerlist = {}
--[[if GetNumRaidMembers() > 0 then
for i = 1, 40 do
local liv = UnitName(string.format("raid%d", i))
if liv then playerlist[liv] = true end
end
elseif]] if GetNumPartyMembers() > 0 then
-- we is in a party
for i = 1, 4 do
local targ = string.format("party%d", i)
local liv, relm = UnitName(targ)
if liv and not relm and liv ~= UNKNOWNOBJECT and UnitIsConnected(targ) then playerlist[liv] = true end
end
end
playerlist[UnitName("player")] = nil
local additions = {}
for k, v in pairs(playerlist) do
if not old_playerlist[k] then
--print("new player:", k)
table.insert(additions, k)
end
end
local removals = {}
for k, v in pairs(old_playerlist) do
if not playerlist[k] then
--print("lost player:", k)
table.insert(removals, k)
end
end
old_playerlist = playerlist
for _, v in ipairs(removals) do
QH_DumpCommUser(v)
end
if #additions == 0 then return end
if #additions == 1 then
SAM("syn:2", "WHISPER", additions[1])
else
SAM("syn:2", "PARTY")
end
end
local aku = {}
local newer_reported = false
local older_reported = false
function QH_Questcomm_Msg(data, from)
if data:match("syn:0") then
QH_DumpCommUser(from)
return
end
if QuestHelper_Pref.solo then return end
--print("received", from, data)
do
local cont = true
local key, value = data:match("(.):(.*)")
if key == "v" then
aku[from] = value
elseif key == "x" then
if aku[from] then
aku[from] = aku[from] .. value
end
elseif key == "X" then
if aku[from] then
aku[from] = aku[from] .. value
data = aku[from]
aku[from] = nil
cont = true
end
else
cont = true
end
if not cont then return end
end
--print("packet received", from, data)
if data:match("syn:.*") then
local synv = data:match("syn:([0-9]*)")
if synv then synv = tonumber(synv) end
if synv and synv ~= 2 then
if synv > 2 and not newer_reported then
QuestHelper:TextOut(QHFormat("PEER_NEWER", from))
newer_reported = true
elseif synv < 2 and not older_reported then
QuestHelper:TextOut(QHFormat("PEER_OLDER", from))
older_reported = true
end
end
if synv and synv >= 2 then
SAM("hello:2", "WHISPER", from)
end
elseif data == "hello:2" or data == "retrieve:2" then
if data == "hello:2" then SAM("retrieve:2", "WHISPER", from) end -- requests their info as well, needed to deal with UI reloading/logon/logoff properly
for k, v in pairs(current_chunks) do
SAM(v, "WHISPER", from)
end
else
if old_playerlist[from] then
QH_InsertCommPacket(from, data)
end
end
end
function QuestHelper:SetShare(flag)
if flag then
QH_Questcomm_Sync()
else
SAM("syn:0", "PARTY")
local cpb = comm_packets
comm_packets = {}
for k in pairs(cpb) do RefreshUserComms(k) end
end
end