1 QuestHelper_File
["director_quest.lua"] = "Development Version"
2 QuestHelper_Loadtime
["director_quest.lua"] = GetTime()
4 local debug_output
= false
5 if QuestHelper_File
["director_quest.lua"] == "Development Version" then debug_output
= true end
9 Little bit of explanation here.
11 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.
13 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.
17 local function copy(tab
)
19 for _
, v
in ipairs(tab
) do
25 local function copy_without_last(tab
)
27 for _
, v
in ipairs(tab
) do
34 local function AppendObjlinks(target
, source
, tooltips
, icon
, last_name
, map_lines
, tooltip_lines
, seen
)
35 if not seen
then seen
= {} end
36 if not map_lines
then map_lines
= {} end
37 if not tooltip_lines
then tooltip_lines
= {} end
39 QuestHelper
: Assert(not seen
[source
])
41 if seen
[source
] then return end
46 for m
, v
in ipairs(source
.loc
) do
47 QuestHelper
: Assert(target
)
48 QuestHelper
: Assert(QuestHelper_ParentLookup
)
49 QuestHelper
: Assert(QuestHelper_ParentLookup
[v
.p
], v
.p
)
50 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})
54 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
57 for _
, v
in ipairs(source
) do
58 local dbgi
= DB_GetItem(v
.sourcetype
, v
.sourceid
, nil, true)
61 --print(v.sourcetype, v.sourceid, v.type)
63 if v
.sourcetype
== "monster" then
64 table.insert(map_lines
, QHFormat("OBJECTIVE_SLAY", dbgi
.name
or QHText("OBJECTIVE_UNKNOWN_MONSTER")))
65 table.insert(tooltip_lines
, 1, QHFormat("TOOLTIP_SLAY", source
.name
or "nothing"))
67 elseif v
.sourcetype
== "item" then
68 table.insert(map_lines
, QHFormat("OBJECTIVE_LOOT", dbgi
.name
or QHText("OBJECTIVE_ITEM_UNKNOWN")))
69 table.insert(tooltip_lines
, 1, QHFormat("TOOLTIP_LOOT", source
.name
or "nothing"))
71 elseif v
.sourcetype
== "object" then
72 table.insert(map_lines
, QHFormat("OBJECTIVE_OPEN", dbgi
.name
or QHText("OBJECTIVE_ITEM_UNKNOWN")))
73 table.insert(tooltip_lines
, 1, QHFormat("TOOLTIP_OPEN", source
.name
or "nothing"))
76 table.insert(map_lines
, string.format("unknown %s (%s/%s)", tostring(dbgi
.name
), tostring(v
.sourcetype
), tostring(v
.sourceid
)))
77 table.insert(tooltip_lines
, 1, string.format("unknown %s (%s/%s)", tostring(last_name
), tostring(v
.sourcetype
), tostring(v
.sourceid
)))
81 tooltips
[string.format("%s@@%s", v
.sourcetype
, v
.sourceid
)] = copy_without_last(tooltip_lines
)
83 AppendObjlinks(target
, dbgi
, tooltips
, icon
or licon
, source
.name
, map_lines
, tooltip_lines
, seen
)
84 table.remove(tooltip_lines
, 1)
85 table.remove(map_lines
)
94 local function horribledupe(from
)
95 if not from
then return nil end
98 for k
, v
in pairs(from
) do
99 if k
== "__owner" then
100 elseif type(v
) == "table" then
101 rv
[k
] = horribledupe(v
)
110 local quest_list
= setmetatable({}, {__mode
="k"})
112 local QuestCriteriaWarningBroadcast
114 local function GetQuestMetaobjective(questid
, lbcount
)
115 if not quest_list
[questid
] then
116 local q
= DB_GetItem("quest", questid
, true, true)
119 QuestHelper
: TextOut("Missing lbcount, guessing wildly")
120 if q
and q
.criteria
then
122 for k
, v
in ipairs(q
.criteria
) do
123 lbcount
= math
.max(lbcount
, k
)
130 -- just doublechecking here
131 if not QuestCriteriaWarningBroadcast
and q
and q
.criteria
then for k
, v
in pairs(q
.criteria
) do
132 if type(k
) == "number" and k
> lbcount
then
133 --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
134 QuestHelper_ErrorCatcher_ExplicitError(false, string.format("Too many stored objectives (%s %s %s)", questid
, lbcount
, k
))
135 QuestCriteriaWarningBroadcast
= true
139 local ite
= {type_quest
= {__backlink
= ite
}} -- we don't want to mutate the existing quest data. backlink exists only for nasty GC reasons
140 ite
.desc
= string.format("Quest %s", q
and q
.name
or "(unknown)") -- this gets changed later anyway
142 for i
= 1, lbcount
do
144 --QuestHelper:TextOut(string.format("critty %d %d", k, c.loc and #c.loc or -1))
146 ttx
.tooltip_canned
= {}
148 if q
and q
.criteria
and q
.criteria
[i
] then
149 --print("Appending criteria", questid, i)
150 AppendObjlinks(ttx
, q
.criteria
[i
], ttx
.tooltip_canned
)
153 if debug_output
and q
.criteria
[i
].loc
and #q
.criteria
[i
] > 0 then
154 QuestHelper
:TextOut(string.format("Wackyquest %d/%d", questid
, i
))
157 ttx
.solid
= horribledupe(q
.criteria
[i
].solid
)
161 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
162 ttx
.type_quest_unknown
= true
165 for idx
, v
in ipairs(ttx
) do
166 v
.desc
= string.format("Criteria %d", i
)
169 v
.type_quest
= ite
.type_quest
172 for k
, v
in pairs(ttx
.tooltip_canned
) do
173 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
180 local ttx
= {type_quest_finish
= true}
181 --QuestHelper:TextOut(string.format("finny %d", q.finish.loc and #q.finish.loc or -1))
182 if q
and q
.finish
and q
.finish
.loc
then
183 ttx
.solid
= horribledupe(q
.finish
.solid
)
184 for m
, v
in ipairs(q
.finish
.loc
) do
186 --print(QuestHelper_IndexLookup[v.rc])
187 --print(QuestHelper_IndexLookup[v.rc][v.rz])
188 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
})
193 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
194 ttx
.type_quest_unknown
= true
200 quest_list
[questid
] = ite
202 if q
then DB_ReleaseItem(q
) end
205 return quest_list
[questid
]
209 local function GetQuestType(link
)
210 return tonumber(string.match(link
,
211 "^|cff%x%x%x%x%x%x|Hquest:(%d+):[%d-]+|h%[[^%]]*%]|h|r$"
212 )), tonumber(string.match(link
,
213 "^|cff%x%x%x%x%x%x|Hquest:%d+:([%d-]+)|h%[[^%]]*%]|h|r$"
218 local function UpdateTrigger()
222 -- 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.
225 local objective_parse_table
= {
226 item
= function (txt
) return QuestHelper
:convertPattern(QUEST_OBJECTS_FOUND
)(txt
) end,
227 object
= function (txt
) return QuestHelper
:convertPattern(QUEST_OBJECTS_FOUND
)(txt
) end, -- why does this even exist
228 monster
= function (txt
) return QuestHelper
:convertPattern(QUEST_MONSTERS_KILLED
)(txt
) end,
229 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.
230 reputation
= function (txt
) return QuestHelper
:convertPattern(QUEST_FACTION_NEEDED
)(txt
) end, -- :ughh:
231 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.
234 local function objective_parse(typ
, txt
, done
)
235 local pt
, target
, have
, need
= typ
, objective_parse_table
[typ
](txt
, done
)
238 -- well, that didn't work
239 target
, have
, need
= string.match(txt
, "^%s*(.-)%s*:%s*(.-)%s*/%s*(.-)%s*$")
241 --QuestHelper:TextOut(string.format("%s rebecomes %s/%s/%s", tostring(title), tostring(target), tostring(have), tostring(need)))
245 target
, have
, need
= string.match(txt
, "^%s*(.-)%s*$"), (done
and 1 or 0), 1
246 --QuestHelper:TextOut(string.format("%s rerebecomes %s/%s/%s", tostring(title), tostring(target), tostring(have), tostring(need)))
249 QuestHelper
: Assert(target
) -- This will fail repeatedly. Come on. We all know it.
250 QuestHelper
: Assert(have
)
251 QuestHelper
: Assert(need
) -- As will these.
253 if tonumber(have
) then have
= tonumber(have
) end
254 if tonumber(need
) then need
= tonumber(need
) end
256 return pt
, target
, have
, need
259 local function clamp(v
)
260 if v
< 0 then return 0 elseif v
> 255 then return 255 else return v
end
263 local function colorlerp(position
, r1
, g1
, b1
, r2
, g2
, b2
)
264 local antip
= 1 - position
265 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))
268 -- We're just gonna do the same thing QH originally did - red->yellow->green.
269 local function difficulty_color(position
)
270 if position
< 0 then position
= 0 end
271 if position
> 1 then position
= 1 end
272 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)
275 local function MakeQuestTitle(title
, level
)
276 local plevel
= UnitLevel("player") -- meh, should probably cache this, buuuuut
281 elseif plevel
>= 40 then
282 grayd
= plevel
/ 5 + 1
284 grayd
= plevel
/ 10 + 5
287 local isgray
= (plevel
- floor(grayd
) >= level
)
289 local ccode
= isgray
and "|cffb0b0b0" or difficulty_color(1 - ((level
- plevel
) / grayd
+ 1) / 2)
290 local qlevel
= string.format("[%d] ", level
)
293 if QuestHelper_Pref
.track_level
then ret
= qlevel
.. ret
end
294 if QuestHelper_Pref
.track_qcolour
then ret
= ccode
.. ret
end
299 local function MakeQuestObjectiveTitle(progress
, target
)
300 if not progress
then return nil end
302 local player
= UnitName("player")
305 for _
, v
in pairs(progress
) do
307 if v
[3] == 1 then pd
= pd
+ 1 end
313 local party_show
= false
314 local party_compact
= false
316 if progress
[player
] then
317 local have
, need
= tonumber(progress
[player
][1]), tonumber(progress
[player
][2])
319 ccode
= difficulty_color(progress
[player
][3])
321 if have
and need
then
323 status
= string.format("%d/%d", have
, need
)
327 status
= string.format("%s/%s", progress
[player
][1], progress
[player
][2])
331 if pt
> 1 then party_show
= true end
333 ccode
= difficulty_color(1) -- probably just in the process of being removed from the tracker
336 ccode
= difficulty_color(pd
/ pt
)
342 if party_compact
then
343 party
= string.format("(P: %d/%d)", pd
, pt
)
345 party
= string.format("Party %d/%d", pd
, pt
)
349 if QuestHelper_Pref
.track_ocolour
then
350 target
= ccode
.. target
353 if status
or party
then
354 target
= target
.. ":"
358 target
= target
.. " " .. status
362 target
= target
.. " " .. party
368 local function Clicky(index
)
369 ShowUIPanel(QuestLogFrame
)
370 QuestLog_SetSelection(index
)
375 name
= "director_quest_unknown_objective",
378 friendly_reason
= QHText("UNKNOWN_OBJ"),
381 -- InsertedItem[item] = {"list", "of", "reasons"}
382 local InsertedItems
= {}
383 local TooltipType
= {}
384 local Unknowning
= {}
388 local function SetTooltip(item
, typ
)
389 --print("stt", item, typ, item.tooltip_defer_questobjective)
390 if TooltipType
[item
] == typ
and typ
~= "defer" and not item
.tooltip_defer_questobjective_last
then return end
391 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
393 if TooltipType
[item
] == "canned" then
394 QuestHelper
: Assert(item
.tooltip_canned
)
395 QH_Tooltip_Canned_Remove(item
.tooltip_canned
)
396 elseif TooltipType
[item
] == "defer" then
397 QuestHelper
: Assert(item
.tooltip_defer_questname_last
)
398 --print("remove", item.tooltip_defer_questname_last, item.tooltip_defer_questobjective_last, item.tooltip_defer_questobjective)
399 if item
.tooltip_defer_questobjective_last
then
400 QH_Tooltip_Defer_Remove(item
.tooltip_defer_questname_last
, item
.tooltip_defer_questobjective_last
, item
.tooltip_defer_token_last
)
402 QH_Tooltip_Defer_Remove(item
.tooltip_defer_questname_last
, item
.tooltip_defer_questobjective
, item
.tooltip_defer_token_last
)
404 elseif TooltipType
[item
] == nil then
406 QuestHelper
: Assert(false)
409 item
.tooltip_defer_questobjective_last
= nil
410 item
.tooltip_defer_questname_last
= nil -- if it was anything, it is not now
411 item
.tooltip_defer_token_last
= nil
413 if typ
== "canned" then
414 QuestHelper
: Assert(item
.tooltip_canned
)
415 QH_Tooltip_Canned_Add(item
.tooltip_canned
)
416 elseif typ
== "defer" then
417 QuestHelper
: Assert(not not item
.tooltip_defer_questobjective
== not item
.type_quest_finish
) -- hmmm
418 --print("add", item.tooltip_defer_questname, item.tooltip_defer_questobjective)
419 QuestHelper
: Assert(item
.tooltip_defer_questname
)
420 item
.tooltip_defer_token_last
= {{}, item
}
421 QH_Tooltip_Defer_Add(item
.tooltip_defer_questname
, item
.tooltip_defer_questobjective
, item
.tooltip_defer_token_last
)
422 item
.tooltip_defer_questname_last
= item
.tooltip_defer_questname
423 item
.tooltip_defer_questobjective_last
= item
.tooltip_defer_questobjective
424 elseif typ
== nil then
426 QuestHelper
: Assert(false)
428 TooltipType
[item
] = typ
431 local function StartInsertionPass(id
)
432 QuestHelper
: Assert(not in_pass
)
434 QH_Timeslice_PushUnyieldable()
435 for k
, v
in pairs(InsertedItems
) do
440 local desc
= MakeQuestObjectiveTitle(k
.progress
, k
.target
)
441 for _
, v
in ipairs(k
) do
442 v
.tracker_desc
= desc
or "(no description available)"
446 -- 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
447 if id
== UnitName("player") then
448 k
.tooltip_defer_questname
= nil
449 k
.tooltip_defer_questobjective
= nil
453 local function RefreshItem(id
, item
, required
)
454 --if not required and math.random() < 0.2 then return false end -- ha ha bzzzzt
456 QuestHelper
: Assert(in_pass
== id
)
458 if not InsertedItems
[item
] then
459 QH_Route_ClusterAdd(item
)
460 --QH_Route_SetClusterPriority(item, math.random(5))
462 InsertedItems
[item
] = {}
464 InsertedItems
[item
][id
] = true
466 if item
.tooltip_defer_questname
then
467 SetTooltip(item
, "defer")
468 elseif item
.tooltip_canned
then
469 SetTooltip(item
, "canned")
471 SetTooltip(item
, nil)
474 if item
.type_quest_unknown
then table.insert(Unknowning
, item
) end
476 local desc
= MakeQuestObjectiveTitle(item
.progress
, item
.target
)
477 for _
, v
in ipairs(item
) do
478 v
.tracker_desc
= desc
or "(no description available)"
483 local function EndInsertionPass(id
)
484 QuestHelper
: Assert(in_pass
== id
)
485 local rem
= QuestHelper
:CreateTable("ip rem")
486 for k
, v
in pairs(InsertedItems
) do
488 for _
, _
in pairs(v
) do
493 QH_Tracker_Unpin(k
[1], true)
494 QH_Route_ClusterRemove(k
)
503 for k
, _
in pairs(rem
) do
504 InsertedItems
[k
] = nil
506 QuestHelper
:ReleaseTable(rem
)
508 -- this is all so we don't spam the system with multiple ignores, since that currently causes an early routing exit
509 for k
in pairs(Unknowned
) do
512 for _
, v
in ipairs(Unknowning
) do
513 if Unknowned
[v
] == nil then
514 QH_Route_IgnoreCluster(v
, dontknow
)
518 while table.remove(Unknowning
) do end
519 local need_rescan
= false
520 local new_unknowned
= QuestHelper
:CreateTable("unk")
521 for k
, v
in pairs(Unknowned
) do
522 if v
then new_unknowned
[k
] = true end
524 QuestHelper
:ReleaseTable(Unknowned
)
525 Unknowned
= new_unknowned
527 QH_Timeslice_PopUnyieldable()
530 --QH_Tooltip_Defer_Dump()
533 function QuestProcessor(user_id
, db
, title
, level
, group
, variety
, groupsize
, watched
, complete
, lbcount
, timed
)
535 db
.tracker_desc
= MakeQuestTitle(title
, level
)
537 db
.type_quest
.objectives
= lbcount
538 db
.type_quest
.level
= level
539 db
.type_quest
.done
= (complete
== 1)
540 db
.type_quest
.variety
= variety
541 db
.type_quest
.groupsize
= groupsize
542 db
.type_quest
.title
= title
547 -- This is our "quest turnin" objective, which is currently being handled separately for no particularly good reason.
548 if db
.finish
and #db
.finish
> 0 then
549 for _
, v
in ipairs(db
.finish
) do
550 v
.map_highlight
= (complete
== 1)
554 --print("turnin:", turnin.tooltip_defer_questname)
555 if RefreshItem(user_id
, turnin
, true) then
557 for k
, v
in ipairs(turnin
) do
558 v
.tracker_clicked
= function () Clicky(lindex
) end
560 v
.map_desc
= {QHFormat("OBJECTIVE_REASON_TURNIN", title
)}
563 if watched
~= "(ignore)" then QH_Tracker_SetPin(db
.finish
[1], watched
, true) end
566 -- These are the individual criteria of the quest. Remember that each criteria can be represented by multiple routing objectives.
567 for i
= 1, lbcount
do
569 local pt
, pd
, have
, need
= objective_parse(db
[i
].temp_typ
, db
[i
].temp_desc
, db
[i
].temp_done
)
571 if pt
== "item" or pt
== "object" then
572 dline
= QHFormat("OBJECTIVE_REASON", QHText("ACQUIRE_VERB"), pd
, title
)
573 elseif pt
== "monster" then
574 dline
= QHFormat("OBJECTIVE_REASON", QHText("SLAY_VERB"), pd
, title
)
576 dline
= QHFormat("OBJECTIVE_REASON_FALLBACK", pd
, title
)
579 if not db
[i
].progress
then
583 if type(have
) == "number" and type(need
) == "number" then
584 db
[i
].progress
[db
[i
].temp_person
] = {have
, need
, have
/ need
}
586 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
589 local _
, target
= objective_parse(db
[i
].temp_typ
, db
[i
].temp_desc
)
590 db
[i
].target
= target
592 db
[i
].desc
= QHFormat("TOOLTIP_QUEST", title
)
594 for k
, v
in ipairs(db
[i
]) do
595 v
.desc
= db
[i
].temp_desc
596 v
.tracker_clicked
= db
.tracker_clicked
598 v
.progress
= db
[i
].progress
601 v
.map_desc
= copy(v
.path_desc
)
602 v
.map_desc
[1] = dline
608 -- This is the snatch of code that actually adds it to routing.
609 if not db
[i
].temp_done
and #db
[i
] > 0 then
610 if RefreshItem(user_id
, db
[i
]) then
611 if turnin
then QH_Route_ClusterRequires(turnin
, db
[i
]) end
613 if watched
~= "(ignore)" then QH_Tracker_SetPin(db
[i
][1], watched
, true) end
616 db
[i
].temp_desc
, db
[i
].temp_typ
, db
[i
].temp_done
= nil, nil, nil
620 if turnin_new
and timed
then
621 QH_Route_SetClusterPriority(turnin
, -1)
625 function SerItem(item
)
627 if type(item
) == "boolean" then
628 rtx
= "b" .. (item
and "t" or "f")
629 elseif type(item
) == "number" then
630 rtx
= "n" .. tostring(item
)
631 elseif type(item
) == "string" then
632 rtx
= "s" .. item
:gsub("\\", "\\\\"):gsub(":", "\\;")
633 elseif type(item
) == "nil" then
636 print(type(item
), item
)
637 QuestHelper
: Assert()
642 function DeSerItem(item
)
643 local t
= item
:sub(1, 1)
644 local d
= item
:sub(2)
650 return d
:gsub("\\;", ":"):gsub("\\\\", "\\")
654 QuestHelper
: Assert()
658 local function Serialize(...)
660 for i
= 1, select("#", ...) do
661 if sx
then sx
= sx
.. ":" else sx
= "" end
662 sx
= sx
.. SerItem(select(i
, ...))
664 QuestHelper
: Assert(sx
)
668 local function SAM(msg
, chattype
, target
)
669 --QuestHelper: TextOut(string.format("%s/%s: %s", chattype, tostring(target), msg))
673 if #msg
> thresh
then
674 for i
= 1, #msg
, msgsize
do
676 if i
== 1 then prefx
= "v:" elseif i
+ msgsize
> #msg
then prefx
= "X:" end
677 SAM(prefx
.. msg
:sub(i
, i
+ msgsize
- 1), chattype
, target
)
680 ChatThrottleLib
:SendAddonMessage("BULK", "QHpr", msg
, chattype
, target
, "QHpr")
685 function is_uncached(typ
, txt
, done
)
686 if not txt
then return true end
687 if txt
== "" then return true end
688 if txt
:match("^ : %d+/%d+$") then return true end
689 local _
, target
= objective_parse(typ
, txt
, done
)
690 if target
== "" or target
== " " then return true end
695 local current_chunks
= {}
697 -- "log" is a synthetic objective that Blizzard tossed in for god only knows what reason, so we just pretend it doesn't exist
698 local function GetEffectiveNumQuestLeaderBoards(index
)
699 local v
= GetNumQuestLeaderBoards(index
)
700 if v
~= 1 then return v
end
701 if select(2, GetQuestLogLeaderBoard(1, index
)) == "log" then return 0 end
705 -- Here's the core update function
706 function QH_UpdateQuests(force
)
707 if not DB_Ready() then return end
708 QH_Timeslice_PushUnyieldable()
710 if update
or force
then -- Sometimes (usually) we don't actually update
713 local player
= UnitName("player")
714 if not player
then return end -- bzzt, try again later
715 StartInsertionPass(player
)
717 local next_chunks
= {}
721 -- This begins the main update loop that loops through all of the quests
723 local title
, level
, variety
, groupsize
, _
, _
, complete
= GetQuestLogTitle(index
)
724 if not title
then break end
726 title
= title
:match("%[.*%] (.*)") or title
728 local qlink
= GetQuestLink(index
)
729 if qlink
then -- If we don't have a quest link, it's not really a quest
730 local id
= GetQuestType(qlink
)
731 --if first then id = 13836 else id = nil end
732 if id
then -- If we don't have a *valid* quest link, give up
733 local lbcount
= GetEffectiveNumQuestLeaderBoards(index
)
734 local db
= GetQuestMetaobjective(id
, lbcount
) -- This generates the above-mentioned metaobjective, including doing the database lookup.
736 QuestHelper
: Assert(db
)
738 local watched
= IsQuestWatched(index
)
740 -- 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)
742 db
.tracker_clicked
= function () Clicky(lindex
) end
744 db
.type_quest
.index
= index
748 local timer
= GetQuestIndexForTimer(timidx
)
749 if not timer
then timidx
= nil break end
750 if timer
== index
then break end
753 local timed
= not not timidx
755 --print(id, title, level, groupsize, variety, groupsize, complete, timed)
756 local chunk
= "q:" .. Serialize(id
, title
, level
, groupsize
, variety
, groupsize
, complete
, timed
)
757 for i
= 1, lbcount
do
758 QuestHelper
: Assert(db
[i
])
759 db
[i
].temp_desc
, db
[i
].temp_typ
, db
[i
].temp_done
= GetQuestLogLeaderBoard(i
, index
)
760 --[[if not db[i].temp_desc or is_uncached(db[i].temp_typ, db[i].temp_desc, db[i].temp_done) then
761 db[i].temp_desc = string.format("(missing description %d)", i)
763 db
[i
].temp_person
= player
765 db
[i
].tooltip_defer_questname
= title
766 db
[i
].tooltip_defer_questobjective
= db
[i
].temp_desc
-- yoink
767 QuestHelper
: Assert(db
[i
].tooltip_defer_questobjective
) -- hmmm
769 chunk
= chunk
.. ":" .. Serialize(db
[i
].temp_desc
, db
[i
].temp_typ
, db
[i
].temp_done
)
772 db
.finish
.tooltip_defer_questname
= title
-- we're using this as our fallback right now
774 next_chunks
[id
] = chunk
776 QuestProcessor(player
, db
, title
, level
, groupsize
, variety
, groupsize
, watched
, complete
, lbcount
, timed
)
783 EndInsertionPass(player
)
785 QH_Route_Filter_Rescan(nil, true) -- 'cause filters may also change, but let's not bother getting too excited about it
787 if not QuestHelper_Pref
.solo
and QuestHelper_Pref
.share
then
788 for k
, v
in pairs(next_chunks
) do
789 if current_chunks
[k
] ~= v
then
794 for k
, v
in pairs(current_chunks
) do
795 if not next_chunks
[k
] then
796 SAM(string.format("q:n%d", k
), "PARTY")
801 current_chunks
= next_chunks
805 QH_Timeslice_PopUnyieldable()
808 -- comm_packets[user][qid] = data
809 local comm_packets
= {}
811 local function RefreshUserComms(user
)
812 StartInsertionPass(user
)
814 if comm_packets
[user
] then for _
, dat
in pairs(comm_packets
[user
]) do
815 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]
820 if dat
[#obj
* 3 + objstart
] == nil and dat
[#obj
* 3 + objstart
+ 1] == nil and dat
[#obj
* 3 + objstart
+ 2] == nil then break end
821 table.insert(obj
, {dat
[#obj
* 3 + objstart
], dat
[#obj
* 3 + objstart
+ 1], dat
[#obj
* 3 + objstart
+ 2]})
825 local db
= GetQuestMetaobjective(id
, lbcount
) -- This generates the above-mentioned metaobjective, including doing the database lookup.
827 QuestHelper
: Assert(db
)
829 for i
= 1, lbcount
do
830 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
833 QuestProcessor(user
, db
, title
, level
, group
, variety
, groupsize
, "(ignore)", complete
, lbcount
, false)
836 EndInsertionPass(user
)
838 QH_Route_Filter_Rescan() -- 'cause filters may also change
841 function QH_InsertCommPacket(user
, data
)
842 local q
, chunk
= data
:match("([^:]+):(.*)")
843 if q
~= "q" then return end
847 for item
in chunk
:gmatch("([^:]+)") do
848 dat
[idx
] = DeSerItem(item
)
852 if not comm_packets
[user
] then comm_packets
[user
] = {} end
854 comm_packets
[user
][dat
[1]]
= nil
856 comm_packets
[user
][dat
[1]]
= dat
860 RefreshUserComms(user
)
863 local function QH_DumpCommUser(user
)
864 comm_packets
[user
] = nil
865 RefreshUserComms(user
)
868 QH_Event("PLAYER_ENTERING_WORLD", UpdateTrigger
)
869 QH_Event("UNIT_QUEST_LOG_CHANGED", UpdateTrigger
)
870 QH_Event("QUEST_LOG_UPDATE", QH_UpdateQuests
)
872 -- 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.
873 QH_AddNotifier(GetTime() + 5, function ()
874 local aqw_orig
= AddQuestWatch
875 AddQuestWatch
= function(...)
877 QH_UpdateQuests(true)
879 local rqw_orig
= RemoveQuestWatch
880 RemoveQuestWatch
= function(...)
882 QH_UpdateQuests(true)
886 -- 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.
887 --[[local function autonotify()
888 QH_UpdateQuests(true)
889 QH_AddNotifier(GetTime() + 5, autonotify)
891 QH_AddNotifier(GetTime() + 30, autonotify)]]
893 local old_playerlist
= {}
895 function QH_Questcomm_Sync()
896 if not (not QuestHelper_Pref
.solo
and QuestHelper_Pref
.share
) then
901 local playerlist
= {}
902 --[[if GetNumRaidMembers() > 0 then
904 local liv = UnitName(string.format("raid%d", i))
905 if liv then playerlist[liv] = true end
907 elseif]] if GetNumPartyMembers() > 0 then
910 local targ
= string.format("party%d", i
)
911 local liv
, relm
= UnitName(targ
)
912 if liv
and not relm
and liv
~= UNKNOWNOBJECT
and UnitIsConnected(targ
) then playerlist
[liv
] = true end
915 playerlist
[UnitName("player")] = nil
918 for k
, v
in pairs(playerlist
) do
919 if not old_playerlist
[k
] then
920 --print("new player:", k)
921 table.insert(additions
, k
)
926 for k
, v
in pairs(old_playerlist
) do
927 if not playerlist
[k
] then
928 --print("lost player:", k)
929 table.insert(removals
, k
)
933 old_playerlist
= playerlist
935 for _
, v
in ipairs(removals
) do
939 if #additions
== 0 then return end
941 if #additions
== 1 then
942 SAM("syn:2", "WHISPER", additions
[1])
944 SAM("syn:2", "PARTY")
950 local newer_reported
= false
951 local older_reported
= false
952 function QH_Questcomm_Msg(data
, from
)
953 if data
:match("syn:0") then
954 QH_DumpCommUser(from
)
957 if QuestHelper_Pref
.solo
then return end
959 --print("received", from, data)
963 local key
, value
= data
:match("(.):(.*)")
966 elseif key
== "x" then
968 aku
[from
] = aku
[from
] .. value
970 elseif key
== "X" then
972 aku
[from
] = aku
[from
] .. value
981 if not cont
then return end
984 --print("packet received", from, data)
985 if data
:match("syn:.*") then
986 local synv
= data
:match("syn:([0-9]*)")
987 if synv
then synv
= tonumber(synv
) end
988 if synv
and synv
~= 2 then
989 if synv
> 2 and not newer_reported
then
990 QuestHelper
:TextOut(QHFormat("PEER_NEWER", from
))
991 newer_reported
= true
992 elseif synv
< 2 and not older_reported
then
993 QuestHelper
:TextOut(QHFormat("PEER_OLDER", from
))
994 older_reported
= true
998 if synv
and synv
>= 2 then
999 SAM("hello:2", "WHISPER", from
)
1001 elseif data
== "hello:2" or data
== "retrieve:2" then
1002 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
1004 for k
, v
in pairs(current_chunks
) do
1005 SAM(v
, "WHISPER", from
)
1008 if old_playerlist
[from
] then
1009 QH_InsertCommPacket(from
, data
)
1014 function QuestHelper
:SetShare(flag
)
1018 SAM("syn:0", "PARTY")
1019 local cpb
= comm_packets
1021 for k
in pairs(cpb
) do RefreshUserComms(k
) end