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"))
72 table.insert(map_lines
, string.format("unknown %s (%s/%s)", tostring(dbgi
.name
), tostring(v
.sourcetype
), tostring(v
.sourceid
)))
73 table.insert(tooltip_lines
, 1, string.format("unknown %s (%s/%s)", tostring(last_name
), tostring(v
.sourcetype
), tostring(v
.sourceid
)))
77 tooltips
[string.format("%s@@%s", v
.sourcetype
, v
.sourceid
)] = copy_without_last(tooltip_lines
)
79 AppendObjlinks(target
, dbgi
, tooltips
, icon
or licon
, source
.name
, map_lines
, tooltip_lines
, seen
)
80 table.remove(tooltip_lines
, 1)
81 table.remove(map_lines
)
90 local function horribledupe(from
)
91 if not from
then return nil end
94 for k
, v
in pairs(from
) do
95 if k
== "__owner" then
96 elseif type(v
) == "table" then
97 rv
[k
] = horribledupe(v
)
106 local quest_list
= setmetatable({}, {__mode
="k"})
108 local QuestCriteriaWarningBroadcast
110 local function GetQuestMetaobjective(questid
, lbcount
)
111 if not quest_list
[questid
] then
112 local q
= DB_GetItem("quest", questid
, true, true)
115 QuestHelper
: TextOut("Missing lbcount, guessing wildly")
116 if q
and q
.criteria
then
118 for k
, v
in ipairs(q
.criteria
) do
119 lbcount
= math
.max(lbcount
, k
)
126 -- just doublechecking here
127 if not QuestCriteriaWarningBroadcast
and q
and q
.criteria
then for k
, v
in pairs(q
.criteria
) do
128 if type(k
) == "number" and k
> lbcount
then
129 --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
130 QuestHelper_ErrorCatcher_ExplicitError(false, string.format("Too many stored objectives (%s %s %s)", questid
, lbcount
, k
))
131 QuestCriteriaWarningBroadcast
= true
135 ite
= {type_quest
= {__backlink
= ite
}} -- we don't want to mutate the existing quest data. backlink exists only for nasty GC reasons
136 ite
.desc
= string.format("Quest %s", q
and q
.name
or "(unknown)") -- this gets changed later anyway
138 for i
= 1, lbcount
do
140 --QuestHelper:TextOut(string.format("critty %d %d", k, c.loc and #c.loc or -1))
142 ttx
.tooltip_canned
= {}
144 if q
and q
.criteria
and q
.criteria
[i
] then
145 --print("Appending criteria", questid, i)
146 AppendObjlinks(ttx
, q
.criteria
[i
], ttx
.tooltip_canned
)
149 if debug_output
and q
.criteria
[i
].loc
and #q
.criteria
[i
] > 0 then
150 QuestHelper
:TextOut(string.format("Wackyquest %d/%d", questid
, i
))
153 ttx
.solid
= horribledupe(q
.criteria
[i
].solid
)
157 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
158 ttx
.type_quest_unknown
= true
161 for idx
, v
in ipairs(ttx
) do
162 v
.desc
= string.format("Criteria %d", i
)
165 v
.type_quest
= ite
.type_quest
168 for k
, v
in pairs(ttx
.tooltip_canned
) do
169 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
176 local ttx
= {type_quest_finish
= true}
177 --QuestHelper:TextOut(string.format("finny %d", q.finish.loc and #q.finish.loc or -1))
178 if q
and q
.finish
and q
.finish
.loc
then
179 ttx
.solid
= horribledupe(q
.finish
.solid
)
180 for m
, v
in ipairs(q
.finish
.loc
) do
182 --print(QuestHelper_IndexLookup[v.rc])
183 --print(QuestHelper_IndexLookup[v.rc][v.rz])
184 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
})
189 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
190 ttx
.type_quest_unknown
= true
196 quest_list
[questid
] = ite
198 if q
then DB_ReleaseItem(q
) end
201 return quest_list
[questid
]
205 local function GetQuestType(link
)
206 return tonumber(string.match(link
,
207 "^|cff%x%x%x%x%x%x|Hquest:(%d+):[%d-]+|h%[[^%]]*%]|h|r$"
208 )), tonumber(string.match(link
,
209 "^|cff%x%x%x%x%x%x|Hquest:%d+:([%d-]+)|h%[[^%]]*%]|h|r$"
214 local function UpdateTrigger()
218 -- 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.
221 local objective_parse_table
= {
222 item
= function (txt
) return QuestHelper
:convertPattern(QUEST_OBJECTS_FOUND
)(txt
) end,
223 object
= function (txt
) return QuestHelper
:convertPattern(QUEST_OBJECTS_FOUND
)(txt
) end, -- why does this even exist
224 monster
= function (txt
) return QuestHelper
:convertPattern(QUEST_MONSTERS_KILLED
)(txt
) end,
225 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.
226 reputation
= function (txt
) return QuestHelper
:convertPattern(QUEST_FACTION_NEEDED
)(txt
) end, -- :ughh:
227 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.
230 local function objective_parse(typ
, txt
, done
)
231 local pt
, target
, have
, need
= typ
, objective_parse_table
[typ
](txt
, done
)
234 -- well, that didn't work
235 target
, have
, need
= string.match(txt
, "^%s*(.-)%s*:%s*(.-)%s*/%s*(.-)%s*$")
237 --QuestHelper:TextOut(string.format("%s rebecomes %s/%s/%s", tostring(title), tostring(target), tostring(have), tostring(need)))
241 target
, have
, need
= string.match(txt
, "^%s*(.-)%s*$"), (done
and 1 or 0), 1
242 --QuestHelper:TextOut(string.format("%s rerebecomes %s/%s/%s", tostring(title), tostring(target), tostring(have), tostring(need)))
245 QuestHelper
: Assert(target
) -- This will fail repeatedly. Come on. We all know it.
246 QuestHelper
: Assert(have
)
247 QuestHelper
: Assert(need
) -- As will these.
249 if tonumber(have
) then have
= tonumber(have
) end
250 if tonumber(need
) then need
= tonumber(need
) end
252 return pt
, target
, have
, need
255 local function clamp(v
)
256 if v
< 0 then return 0 elseif v
> 255 then return 255 else return v
end
259 local function colorlerp(position
, r1
, g1
, b1
, r2
, g2
, b2
)
260 local antip
= 1 - position
261 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))
264 -- We're just gonna do the same thing QH originally did - red->yellow->green.
265 local function difficulty_color(position
)
266 if position
< 0 then position
= 0 end
267 if position
> 1 then position
= 1 end
268 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)
271 local function MakeQuestTitle(title
, level
)
272 local plevel
= UnitLevel("player") -- meh, should probably cache this, buuuuut
277 elseif plevel
>= 40 then
278 grayd
= plevel
/ 5 + 1
280 grayd
= plevel
/ 10 + 5
283 local isgray
= (plevel
- floor(grayd
) >= level
)
285 local ccode
= isgray
and "|cffb0b0b0" or difficulty_color(1 - ((level
- plevel
) / grayd
+ 1) / 2)
286 local qlevel
= string.format("[%d] ", level
)
289 if QuestHelper_Pref
.track_level
then ret
= qlevel
.. ret
end
290 if QuestHelper_Pref
.track_qcolour
then ret
= ccode
.. ret
end
295 local function MakeQuestObjectiveTitle(progress
, target
)
296 if not progress
then return nil end
298 local player
= UnitName("player")
301 for _
, v
in pairs(progress
) do
303 if v
[3] == 1 then pd
= pd
+ 1 end
309 local party_show
= false
310 local party_compact
= false
312 if progress
[player
] then
313 local have
, need
= tonumber(progress
[player
][1]), tonumber(progress
[player
][2])
315 ccode
= difficulty_color(progress
[player
][3])
317 if have
and need
then
319 status
= string.format("%d/%d", have
, need
)
323 status
= string.format("%s/%s", progress
[player
][1], progress
[player
][2])
327 if pt
> 1 then party_show
= true end
329 ccode
= difficulty_color(1) -- probably just in the process of being removed from the tracker
332 ccode
= difficulty_color(pd
/ pt
)
338 if party_compact
then
339 party
= string.format("(P: %d/%d)", pd
, pt
)
341 party
= string.format("Party %d/%d", pd
, pt
)
345 if QuestHelper_Pref
.track_ocolour
then
346 target
= ccode
.. target
349 if status
or party
then
350 target
= target
.. ":"
354 target
= target
.. " " .. status
358 target
= target
.. " " .. party
364 local function Clicky(index
)
365 ShowUIPanel(QuestLogFrame
)
366 QuestLog_SetSelection(index
)
371 name
= "director_quest_unknown_objective",
374 friendly_reason
= QHText("UNKNOWN_OBJ"),
377 -- InsertedItem[item] = {"list", "of", "reasons"}
378 local InsertedItems
= {}
379 local TooltipType
= {}
380 local Unknowning
= {}
383 local function SetTooltip(item
, typ
)
384 --print("stt", item, typ, item.tooltip_defer_questobjective)
385 if TooltipType
[item
] == typ
and typ
~= "defer" and not item
.tooltip_defer_questobjective_last
then return end
386 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
388 if TooltipType
[item
] == "canned" then
389 QuestHelper
: Assert(item
.tooltip_canned
)
390 QH_Tooltip_Canned_Remove(item
.tooltip_canned
)
391 elseif TooltipType
[item
] == "defer" then
392 QuestHelper
: Assert(item
.tooltip_defer_questname_last
)
393 --print("remove", item.tooltip_defer_questname_last, item.tooltip_defer_questobjective_last, item.tooltip_defer_questobjective)
394 if item
.tooltip_defer_questobjective_last
then
395 QH_Tooltip_Defer_Remove(item
.tooltip_defer_questname_last
, item
.tooltip_defer_questobjective_last
, item
.tooltip_defer_token_last
)
397 QH_Tooltip_Defer_Remove(item
.tooltip_defer_questname_last
, item
.tooltip_defer_questobjective
, item
.tooltip_defer_token_last
)
399 elseif TooltipType
[item
] == nil then
401 QuestHelper
: Assert(false)
404 item
.tooltip_defer_questobjective_last
= nil
405 item
.tooltip_defer_questname_last
= nil -- if it was anything, it is not now
406 item
.tooltip_defer_token_last
= nil
408 if typ
== "canned" then
409 QuestHelper
: Assert(item
.tooltip_canned
)
410 QH_Tooltip_Canned_Add(item
.tooltip_canned
)
411 elseif typ
== "defer" then
412 QuestHelper
: Assert(not not item
.tooltip_defer_questobjective
== not item
.type_quest_finish
) -- hmmm
413 --print("add", item.tooltip_defer_questname, item.tooltip_defer_questobjective)
414 QuestHelper
: Assert(item
.tooltip_defer_questname
)
415 item
.tooltip_defer_token_last
= {{}, item
}
416 QH_Tooltip_Defer_Add(item
.tooltip_defer_questname
, item
.tooltip_defer_questobjective
, item
.tooltip_defer_token_last
)
417 item
.tooltip_defer_questname_last
= item
.tooltip_defer_questname
418 item
.tooltip_defer_questobjective_last
= item
.tooltip_defer_questobjective
419 elseif typ
== nil then
421 QuestHelper
: Assert(false)
423 TooltipType
[item
] = typ
426 local function StartInsertionPass(id
)
427 QuestHelper
: Assert(not in_pass
)
429 QH_Timeslice_PushUnyieldable()
430 for k
, v
in pairs(InsertedItems
) do
435 local desc
= MakeQuestObjectiveTitle(k
.progress
, k
.target
)
436 for _
, v
in ipairs(k
) do
437 v
.tracker_desc
= desc
or "(no description available)"
441 -- 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
442 if id
== UnitName("player") then
443 k
.tooltip_defer_questname
= nil
444 k
.tooltip_defer_questobjective
= nil
448 local function RefreshItem(id
, item
, required
)
449 --if not required and math.random() < 0.2 then return false end -- ha ha bzzzzt
451 QuestHelper
: Assert(in_pass
== id
)
453 if not InsertedItems
[item
] then
454 QH_Route_ClusterAdd(item
)
455 --QH_Route_SetClusterPriority(item, math.random(5))
457 InsertedItems
[item
] = {}
459 InsertedItems
[item
][id
] = true
461 if item
.tooltip_defer_questname
then
462 SetTooltip(item
, "defer")
463 elseif item
.tooltip_canned
then
464 SetTooltip(item
, "canned")
466 SetTooltip(item
, nil)
469 if item
.type_quest_unknown
then table.insert(Unknowning
, item
) end
471 local desc
= MakeQuestObjectiveTitle(item
.progress
, item
.target
)
472 for _
, v
in ipairs(item
) do
473 v
.tracker_desc
= desc
or "(no description available)"
478 local function EndInsertionPass(id
)
479 QuestHelper
: Assert(in_pass
== id
)
480 local rem
= QuestHelper
:CreateTable("ip rem")
481 for k
, v
in pairs(InsertedItems
) do
483 for _
, _
in pairs(v
) do
488 QH_Tracker_Unpin(k
[1], true)
489 QH_Route_ClusterRemove(k
)
498 for k
, _
in pairs(rem
) do
499 InsertedItems
[k
] = nil
501 QuestHelper
:ReleaseTable(rem
)
503 for _
, v
in ipairs(Unknowning
) do
504 QH_Route_IgnoreCluster(v
, dontknow
)
506 while table.remove(Unknowning
) do end
508 QH_Timeslice_PopUnyieldable()
511 --QH_Tooltip_Defer_Dump()
514 function QuestProcessor(user_id
, db
, title
, level
, group
, variety
, groupsize
, watched
, complete
, lbcount
, timed
)
516 db
.tracker_desc
= MakeQuestTitle(title
, level
)
518 db
.type_quest
.objectives
= lbcount
519 db
.type_quest
.level
= level
520 db
.type_quest
.done
= (complete
== 1)
521 db
.type_quest
.variety
= variety
522 db
.type_quest
.groupsize
= groupsize
523 db
.type_quest
.title
= title
528 -- This is our "quest turnin" objective, which is currently being handled separately for no particularly good reason.
529 if db
.finish
and #db
.finish
> 0 then
530 for _
, v
in ipairs(db
.finish
) do
531 v
.map_highlight
= (complete
== 1)
535 --print("turnin:", turnin.tooltip_defer_questname)
536 if RefreshItem(user_id
, turnin
, true) then
538 for k
, v
in ipairs(turnin
) do
539 v
.tracker_clicked
= function () Clicky(lindex
) end
541 v
.map_desc
= {QHFormat("OBJECTIVE_REASON_TURNIN", title
)}
544 if watched
~= "(ignore)" then QH_Tracker_SetPin(db
.finish
[1], watched
, true) end
547 -- These are the individual criteria of the quest. Remember that each criteria can be represented by multiple routing objectives.
548 for i
= 1, lbcount
do
550 local pt
, pd
, have
, need
= objective_parse(db
[i
].temp_typ
, db
[i
].temp_desc
, db
[i
].temp_done
)
552 if pt
== "item" or pt
== "object" then
553 dline
= QHFormat("OBJECTIVE_REASON", QHText("ACQUIRE_VERB"), pd
, title
)
554 elseif pt
== "monster" then
555 dline
= QHFormat("OBJECTIVE_REASON", QHText("SLAY_VERB"), pd
, title
)
557 dline
= QHFormat("OBJECTIVE_REASON_FALLBACK", pd
, title
)
560 if not db
[i
].progress
then
564 if type(have
) == "number" and type(need
) == "number" then
565 db
[i
].progress
[db
[i
].temp_person
] = {have
, need
, have
/ need
}
567 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
570 local _
, target
= objective_parse(db
[i
].temp_typ
, db
[i
].temp_desc
)
571 db
[i
].target
= target
573 db
[i
].desc
= QHFormat("TOOLTIP_QUEST", title
)
575 for k
, v
in ipairs(db
[i
]) do
576 v
.desc
= db
[i
].temp_desc
577 v
.tracker_clicked
= db
.tracker_clicked
579 v
.progress
= db
[i
].progress
582 v
.map_desc
= copy(v
.path_desc
)
583 v
.map_desc
[1] = dline
589 -- This is the snatch of code that actually adds it to routing.
590 if not db
[i
].temp_done
and #db
[i
] > 0 then
591 if RefreshItem(user_id
, db
[i
]) then
592 if turnin
then QH_Route_ClusterRequires(turnin
, db
[i
]) end
594 if watched
~= "(ignore)" then QH_Tracker_SetPin(db
[i
][1], watched
, true) end
597 db
[i
].temp_desc
, db
[i
].temp_typ
, db
[i
].temp_done
= nil, nil, nil
601 if turnin_new
and timed
then
602 QH_Route_SetClusterPriority(turnin
, -1)
606 function SerItem(item
)
608 if type(item
) == "boolean" then
609 rtx
= "b" .. (item
and "t" or "f")
610 elseif type(item
) == "number" then
611 rtx
= "n" .. tostring(item
)
612 elseif type(item
) == "string" then
613 rtx
= "s" .. item
:gsub("\\", "\\\\"):gsub(":", "\\;")
614 elseif type(item
) == "nil" then
617 print(type(item
), item
)
618 QuestHelper
: Assert()
623 function DeSerItem(item
)
624 local t
= item
:sub(1, 1)
625 local d
= item
:sub(2)
631 return d
:gsub("\\;", ":"):gsub("\\\\", "\\")
635 QuestHelper
: Assert()
639 local function Serialize(...)
641 for i
= 1, select("#", ...) do
642 if sx
then sx
= sx
.. ":" else sx
= "" end
643 sx
= sx
.. SerItem(select(i
, ...))
645 QuestHelper
: Assert(sx
)
649 local function SAM(msg
, chattype
, target
)
650 --QuestHelper: TextOut(string.format("%s/%s: %s", chattype, tostring(target), msg))
654 if #msg
> thresh
then
655 for i
= 1, #msg
, msgsize
do
657 if i
== 1 then prefx
= "v:" elseif i
+ msgsize
> #msg
then prefx
= "X:" end
658 SAM(prefx
.. msg
:sub(i
, i
+ msgsize
- 1), chattype
, target
)
661 ChatThrottleLib
:SendAddonMessage("BULK", "QHpr", msg
, chattype
, target
, "QHpr")
666 function is_uncached(typ
, txt
, done
)
667 if not txt
then return true end
668 if txt
== "" then return true end
669 if txt
:match("^ : %d+/%d+$") then return true end
670 local _
, target
= objective_parse(typ
, txt
, done
)
671 if target
== "" or target
== " " then return true end
676 local current_chunks
= {}
678 -- "log" is a synthetic objective that Blizzard tossed in for god only knows what reason, so we just pretend it doesn't exist
679 local function GetEffectiveNumQuestLeaderBoards(index
)
680 local v
= GetNumQuestLeaderBoards(index
)
681 if v
~= 1 then return v
end
682 if select(2, GetQuestLogLeaderBoard(1, index
)) == "log" then return 0 end
686 -- Here's the core update function
687 function QH_UpdateQuests(force
)
688 if not DB_Ready() then return end
689 QH_Timeslice_PushUnyieldable()
691 if update
or force
then -- Sometimes (usually) we don't actually update
694 local player
= UnitName("player")
695 StartInsertionPass(player
)
697 local next_chunks
= {}
701 -- This begins the main update loop that loops through all of the quests
703 local title
, level
, variety
, groupsize
, _
, _
, complete
= GetQuestLogTitle(index
)
704 if not title
then break end
706 title
= title
:match("%[.*%] (.*)") or title
708 local qlink
= GetQuestLink(index
)
709 if qlink
then -- If we don't have a quest link, it's not really a quest
710 local id
= GetQuestType(qlink
)
711 --if first then id = 13836 else id = nil end
712 if id
then -- If we don't have a *valid* quest link, give up
713 local lbcount
= GetEffectiveNumQuestLeaderBoards(index
)
714 local db
= GetQuestMetaobjective(id
, lbcount
) -- This generates the above-mentioned metaobjective, including doing the database lookup.
716 QuestHelper
: Assert(db
)
718 local watched
= IsQuestWatched(index
)
720 -- 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)
722 db
.tracker_clicked
= function () Clicky(lindex
) end
724 db
.type_quest
.index
= index
728 local timer
= GetQuestIndexForTimer(timidx
)
729 if not timer
then timidx
= nil break end
730 if timer
== index
then break end
733 local timed
= not not timidx
735 --print(id, title, level, groupsize, variety, groupsize, complete, timed)
736 local chunk
= "q:" .. Serialize(id
, title
, level
, groupsize
, variety
, groupsize
, complete
, timed
)
737 for i
= 1, lbcount
do
738 QuestHelper
: Assert(db
[i
])
739 db
[i
].temp_desc
, db
[i
].temp_typ
, db
[i
].temp_done
= GetQuestLogLeaderBoard(i
, index
)
740 --[[if not db[i].temp_desc or is_uncached(db[i].temp_typ, db[i].temp_desc, db[i].temp_done) then
741 db[i].temp_desc = string.format("(missing description %d)", i)
743 db
[i
].temp_person
= player
745 db
[i
].tooltip_defer_questname
= title
746 db
[i
].tooltip_defer_questobjective
= db
[i
].temp_desc
-- yoink
747 QuestHelper
: Assert(db
[i
].tooltip_defer_questobjective
) -- hmmm
749 chunk
= chunk
.. ":" .. Serialize(db
[i
].temp_desc
, db
[i
].temp_typ
, db
[i
].temp_done
)
752 db
.finish
.tooltip_defer_questname
= title
-- we're using this as our fallback right now
754 next_chunks
[id
] = chunk
756 QuestProcessor(player
, db
, title
, level
, groupsize
, variety
, groupsize
, watched
, complete
, lbcount
, timed
)
763 EndInsertionPass(player
)
765 QH_Route_Filter_Rescan() -- 'cause filters may also change
767 if not QuestHelper_Pref
.solo
and QuestHelper_Pref
.share
then
768 for k
, v
in pairs(next_chunks
) do
769 if current_chunks
[k
] ~= v
then
774 for k
, v
in pairs(current_chunks
) do
775 if not next_chunks
[k
] then
776 SAM(string.format("q:n%d", k
), "PARTY")
781 current_chunks
= next_chunks
784 QH_Timeslice_PopUnyieldable()
787 -- comm_packets[user][qid] = data
788 local comm_packets
= {}
790 local function RefreshUserComms(user
)
791 StartInsertionPass(user
)
793 if comm_packets
[user
] then for _
, dat
in pairs(comm_packets
[user
]) do
794 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]
799 if dat
[#obj
* 3 + objstart
] == nil and dat
[#obj
* 3 + objstart
+ 1] == nil and dat
[#obj
* 3 + objstart
+ 2] == nil then break end
800 table.insert(obj
, {dat
[#obj
* 3 + objstart
], dat
[#obj
* 3 + objstart
+ 1], dat
[#obj
* 3 + objstart
+ 2]})
804 local db
= GetQuestMetaobjective(id
, lbcount
) -- This generates the above-mentioned metaobjective, including doing the database lookup.
806 QuestHelper
: Assert(db
)
808 for i
= 1, lbcount
do
809 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
812 QuestProcessor(user
, db
, title
, level
, group
, variety
, groupsize
, "(ignore)", complete
, lbcount
, false)
815 EndInsertionPass(user
)
817 QH_Route_Filter_Rescan() -- 'cause filters may also change
820 function QH_InsertCommPacket(user
, data
)
821 local q
, chunk
= data
:match("([^:]+):(.*)")
822 if q
~= "q" then return end
826 for item
in chunk
:gmatch("([^:]+)") do
827 dat
[idx
] = DeSerItem(item
)
831 if not comm_packets
[user
] then comm_packets
[user
] = {} end
833 comm_packets
[user
][dat
[1]]
= nil
835 comm_packets
[user
][dat
[1]]
= dat
839 RefreshUserComms(user
)
842 local function QH_DumpCommUser(user
)
843 comm_packets
[user
] = nil
844 RefreshUserComms(user
)
847 QH_Event("PLAYER_ENTERING_WORLD", UpdateTrigger
)
848 QH_Event("UNIT_QUEST_LOG_CHANGED", UpdateTrigger
)
849 QH_Event("QUEST_LOG_UPDATE", QH_UpdateQuests
)
851 -- 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.
852 QH_AddNotifier(GetTime() + 5, function ()
853 local aqw_orig
= AddQuestWatch
854 AddQuestWatch
= function(...)
856 QH_UpdateQuests(true)
858 local rqw_orig
= RemoveQuestWatch
859 RemoveQuestWatch
= function(...)
861 QH_UpdateQuests(true)
865 -- 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.
866 --[[local function autonotify()
867 QH_UpdateQuests(true)
868 QH_AddNotifier(GetTime() + 5, autonotify)
870 QH_AddNotifier(GetTime() + 30, autonotify)]]
872 local old_playerlist
= {}
874 function QH_Questcomm_Sync()
875 if not (not QuestHelper_Pref
.solo
and QuestHelper_Pref
.share
) then
880 local playerlist
= {}
881 --[[if GetNumRaidMembers() > 0 then
883 local liv = UnitName(string.format("raid%d", i))
884 if liv then playerlist[liv] = true end
886 elseif]] if GetNumPartyMembers() > 0 then
889 local targ
= string.format("party%d", i
)
890 local liv
, relm
= UnitName(targ
)
891 if liv
and not relm
and liv
~= UNKNOWNOBJECT
and UnitIsConnected(targ
) then playerlist
[liv
] = true end
894 playerlist
[UnitName("player")] = nil
897 for k
, v
in pairs(playerlist
) do
898 if not old_playerlist
[k
] then
899 --print("new player:", k)
900 table.insert(additions
, k
)
905 for k
, v
in pairs(old_playerlist
) do
906 if not playerlist
[k
] then
907 --print("lost player:", k)
908 table.insert(removals
, k
)
912 old_playerlist
= playerlist
914 for _
, v
in ipairs(removals
) do
918 if #additions
== 0 then return end
920 if #additions
== 1 then
921 SAM("syn:2", "WHISPER", additions
[1])
923 SAM("syn:2", "PARTY")
929 local newer_reported
= false
930 local older_reported
= false
931 function QH_Questcomm_Msg(data
, from
)
932 if data
:match("syn:0") then
933 QH_DumpCommUser(from
)
936 if QuestHelper_Pref
.solo
then return end
938 --print("received", from, data)
942 local key
, value
= data
:match("(.):(.*)")
945 elseif key
== "x" then
947 aku
[from
] = aku
[from
] .. value
949 elseif key
== "X" then
951 aku
[from
] = aku
[from
] .. value
960 if not cont
then return end
963 --print("packet received", from, data)
964 if data
:match("syn:.*") then
965 local synv
= data
:match("syn:([0-9]*)")
966 if synv
then synv
= tonumber(synv
) end
967 if synv
and synv
~= 2 then
968 if synv
> 2 and not newer_reported
then
969 QuestHelper
:TextOut(QHFormat("PEER_NEWER", from
))
970 newer_reported
= true
971 elseif synv
< 2 and not older_reported
then
972 QuestHelper
:TextOut(QHFormat("PEER_OLDER", from
))
973 older_reported
= true
977 if synv
and synv
>= 2 then
978 SAM("hello:2", "WHISPER", from
)
980 elseif data
== "hello:2" or data
== "retrieve:2" then
981 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
983 for k
, v
in pairs(current_chunks
) do
984 SAM(v
, "WHISPER", from
)
987 if old_playerlist
[from
] then
988 QH_InsertCommPacket(from
, data
)
993 function QuestHelper
:SetShare(flag
)
997 SAM("syn:0", "PARTY")
998 local cpb
= comm_packets
1000 for k
in pairs(cpb
) do RefreshUserComms(k
) end