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 local 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
= {}
384 local function SetTooltip(item
, typ
)
385 --print("stt", item, typ, item.tooltip_defer_questobjective)
386 if TooltipType
[item
] == typ
and typ
~= "defer" and not item
.tooltip_defer_questobjective_last
then return end
387 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
389 if TooltipType
[item
] == "canned" then
390 QuestHelper
: Assert(item
.tooltip_canned
)
391 QH_Tooltip_Canned_Remove(item
.tooltip_canned
)
392 elseif TooltipType
[item
] == "defer" then
393 QuestHelper
: Assert(item
.tooltip_defer_questname_last
)
394 --print("remove", item.tooltip_defer_questname_last, item.tooltip_defer_questobjective_last, item.tooltip_defer_questobjective)
395 if item
.tooltip_defer_questobjective_last
then
396 QH_Tooltip_Defer_Remove(item
.tooltip_defer_questname_last
, item
.tooltip_defer_questobjective_last
, item
.tooltip_defer_token_last
)
398 QH_Tooltip_Defer_Remove(item
.tooltip_defer_questname_last
, item
.tooltip_defer_questobjective
, item
.tooltip_defer_token_last
)
400 elseif TooltipType
[item
] == nil then
402 QuestHelper
: Assert(false)
405 item
.tooltip_defer_questobjective_last
= nil
406 item
.tooltip_defer_questname_last
= nil -- if it was anything, it is not now
407 item
.tooltip_defer_token_last
= nil
409 if typ
== "canned" then
410 QuestHelper
: Assert(item
.tooltip_canned
)
411 QH_Tooltip_Canned_Add(item
.tooltip_canned
)
412 elseif typ
== "defer" then
413 QuestHelper
: Assert(not not item
.tooltip_defer_questobjective
== not item
.type_quest_finish
) -- hmmm
414 --print("add", item.tooltip_defer_questname, item.tooltip_defer_questobjective)
415 QuestHelper
: Assert(item
.tooltip_defer_questname
)
416 item
.tooltip_defer_token_last
= {{}, item
}
417 QH_Tooltip_Defer_Add(item
.tooltip_defer_questname
, item
.tooltip_defer_questobjective
, item
.tooltip_defer_token_last
)
418 item
.tooltip_defer_questname_last
= item
.tooltip_defer_questname
419 item
.tooltip_defer_questobjective_last
= item
.tooltip_defer_questobjective
420 elseif typ
== nil then
422 QuestHelper
: Assert(false)
424 TooltipType
[item
] = typ
427 local function StartInsertionPass(id
)
428 QuestHelper
: Assert(not in_pass
)
430 QH_Timeslice_PushUnyieldable()
431 for k
, v
in pairs(InsertedItems
) do
436 local desc
= MakeQuestObjectiveTitle(k
.progress
, k
.target
)
437 for _
, v
in ipairs(k
) do
438 v
.tracker_desc
= desc
or "(no description available)"
442 -- 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
443 if id
== UnitName("player") then
444 k
.tooltip_defer_questname
= nil
445 k
.tooltip_defer_questobjective
= nil
449 local function RefreshItem(id
, item
, required
)
450 --if not required and math.random() < 0.2 then return false end -- ha ha bzzzzt
452 QuestHelper
: Assert(in_pass
== id
)
454 if not InsertedItems
[item
] then
455 QH_Route_ClusterAdd(item
)
456 --QH_Route_SetClusterPriority(item, math.random(5))
458 InsertedItems
[item
] = {}
460 InsertedItems
[item
][id
] = true
462 if item
.tooltip_defer_questname
then
463 SetTooltip(item
, "defer")
464 elseif item
.tooltip_canned
then
465 SetTooltip(item
, "canned")
467 SetTooltip(item
, nil)
470 if item
.type_quest_unknown
then table.insert(Unknowning
, item
) end
472 local desc
= MakeQuestObjectiveTitle(item
.progress
, item
.target
)
473 for _
, v
in ipairs(item
) do
474 v
.tracker_desc
= desc
or "(no description available)"
479 local function EndInsertionPass(id
)
480 QuestHelper
: Assert(in_pass
== id
)
481 local rem
= QuestHelper
:CreateTable("ip rem")
482 for k
, v
in pairs(InsertedItems
) do
484 for _
, _
in pairs(v
) do
489 QH_Tracker_Unpin(k
[1], true)
490 QH_Route_ClusterRemove(k
)
499 for k
, _
in pairs(rem
) do
500 InsertedItems
[k
] = nil
502 QuestHelper
:ReleaseTable(rem
)
504 -- this is all so we don't spam the system with multiple ignores, since that currently causes an early routing exit
505 for k
in pairs(Unknowned
) do
508 for _
, v
in ipairs(Unknowning
) do
509 if Unknowned
[v
] == nil then
510 QH_Route_IgnoreCluster(v
, dontknow
)
514 while table.remove(Unknowning
) do end
515 local need_rescan
= false
516 local new_unknowned
= QuestHelper
:CreateTable("unk")
517 for k
, v
in pairs(Unknowned
) do
518 if v
then new_unknowned
[k
] = true end
520 QuestHelper
:ReleaseTable(Unknowned
)
521 Unknowned
= new_unknowned
523 QH_Timeslice_PopUnyieldable()
526 --QH_Tooltip_Defer_Dump()
529 function QuestProcessor(user_id
, db
, title
, level
, group
, variety
, groupsize
, watched
, complete
, lbcount
, timed
)
531 db
.tracker_desc
= MakeQuestTitle(title
, level
)
533 db
.type_quest
.objectives
= lbcount
534 db
.type_quest
.level
= level
535 db
.type_quest
.done
= (complete
== 1)
536 db
.type_quest
.variety
= variety
537 db
.type_quest
.groupsize
= groupsize
538 db
.type_quest
.title
= title
543 -- This is our "quest turnin" objective, which is currently being handled separately for no particularly good reason.
544 if db
.finish
and #db
.finish
> 0 then
545 for _
, v
in ipairs(db
.finish
) do
546 v
.map_highlight
= (complete
== 1)
550 --print("turnin:", turnin.tooltip_defer_questname)
551 if RefreshItem(user_id
, turnin
, true) then
553 for k
, v
in ipairs(turnin
) do
554 v
.tracker_clicked
= function () Clicky(lindex
) end
556 v
.map_desc
= {QHFormat("OBJECTIVE_REASON_TURNIN", title
)}
559 if watched
~= "(ignore)" then QH_Tracker_SetPin(db
.finish
[1], watched
, true) end
562 -- These are the individual criteria of the quest. Remember that each criteria can be represented by multiple routing objectives.
563 for i
= 1, lbcount
do
565 local pt
, pd
, have
, need
= objective_parse(db
[i
].temp_typ
, db
[i
].temp_desc
, db
[i
].temp_done
)
567 if pt
== "item" or pt
== "object" then
568 dline
= QHFormat("OBJECTIVE_REASON", QHText("ACQUIRE_VERB"), pd
, title
)
569 elseif pt
== "monster" then
570 dline
= QHFormat("OBJECTIVE_REASON", QHText("SLAY_VERB"), pd
, title
)
572 dline
= QHFormat("OBJECTIVE_REASON_FALLBACK", pd
, title
)
575 if not db
[i
].progress
then
579 if type(have
) == "number" and type(need
) == "number" then
580 db
[i
].progress
[db
[i
].temp_person
] = {have
, need
, have
/ need
}
582 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
585 local _
, target
= objective_parse(db
[i
].temp_typ
, db
[i
].temp_desc
)
586 db
[i
].target
= target
588 db
[i
].desc
= QHFormat("TOOLTIP_QUEST", title
)
590 for k
, v
in ipairs(db
[i
]) do
591 v
.desc
= db
[i
].temp_desc
592 v
.tracker_clicked
= db
.tracker_clicked
594 v
.progress
= db
[i
].progress
597 v
.map_desc
= copy(v
.path_desc
)
598 v
.map_desc
[1] = dline
604 -- This is the snatch of code that actually adds it to routing.
605 if not db
[i
].temp_done
and #db
[i
] > 0 then
606 if RefreshItem(user_id
, db
[i
]) then
607 if turnin
then QH_Route_ClusterRequires(turnin
, db
[i
]) end
609 if watched
~= "(ignore)" then QH_Tracker_SetPin(db
[i
][1], watched
, true) end
612 db
[i
].temp_desc
, db
[i
].temp_typ
, db
[i
].temp_done
= nil, nil, nil
616 if turnin_new
and timed
then
617 QH_Route_SetClusterPriority(turnin
, -1)
621 function SerItem(item
)
623 if type(item
) == "boolean" then
624 rtx
= "b" .. (item
and "t" or "f")
625 elseif type(item
) == "number" then
626 rtx
= "n" .. tostring(item
)
627 elseif type(item
) == "string" then
628 rtx
= "s" .. item
:gsub("\\", "\\\\"):gsub(":", "\\;")
629 elseif type(item
) == "nil" then
632 print(type(item
), item
)
633 QuestHelper
: Assert()
638 function DeSerItem(item
)
639 local t
= item
:sub(1, 1)
640 local d
= item
:sub(2)
646 return d
:gsub("\\;", ":"):gsub("\\\\", "\\")
650 QuestHelper
: Assert()
654 local function Serialize(...)
656 for i
= 1, select("#", ...) do
657 if sx
then sx
= sx
.. ":" else sx
= "" end
658 sx
= sx
.. SerItem(select(i
, ...))
660 QuestHelper
: Assert(sx
)
664 local function SAM(msg
, chattype
, target
)
665 --QuestHelper: TextOut(string.format("%s/%s: %s", chattype, tostring(target), msg))
669 if #msg
> thresh
then
670 for i
= 1, #msg
, msgsize
do
672 if i
== 1 then prefx
= "v:" elseif i
+ msgsize
> #msg
then prefx
= "X:" end
673 SAM(prefx
.. msg
:sub(i
, i
+ msgsize
- 1), chattype
, target
)
676 ChatThrottleLib
:SendAddonMessage("BULK", "QHpr", msg
, chattype
, target
, "QHpr")
681 function is_uncached(typ
, txt
, done
)
682 if not txt
then return true end
683 if txt
== "" then return true end
684 if txt
:match("^ : %d+/%d+$") then return true end
685 local _
, target
= objective_parse(typ
, txt
, done
)
686 if target
== "" or target
== " " then return true end
691 local current_chunks
= {}
693 -- "log" is a synthetic objective that Blizzard tossed in for god only knows what reason, so we just pretend it doesn't exist
694 local function GetEffectiveNumQuestLeaderBoards(index
)
695 local v
= GetNumQuestLeaderBoards(index
)
696 if v
~= 1 then return v
end
697 if select(2, GetQuestLogLeaderBoard(1, index
)) == "log" then return 0 end
701 -- Here's the core update function
702 function QH_UpdateQuests(force
)
703 if not DB_Ready() then return end
704 QH_Timeslice_PushUnyieldable()
706 if update
or force
then -- Sometimes (usually) we don't actually update
709 local player
= UnitName("player")
710 StartInsertionPass(player
)
712 local next_chunks
= {}
716 -- This begins the main update loop that loops through all of the quests
718 local title
, level
, variety
, groupsize
, _
, _
, complete
= GetQuestLogTitle(index
)
719 if not title
then break end
721 title
= title
:match("%[.*%] (.*)") or title
723 local qlink
= GetQuestLink(index
)
724 if qlink
then -- If we don't have a quest link, it's not really a quest
725 local id
= GetQuestType(qlink
)
726 --if first then id = 13836 else id = nil end
727 if id
then -- If we don't have a *valid* quest link, give up
728 local lbcount
= GetEffectiveNumQuestLeaderBoards(index
)
729 local db
= GetQuestMetaobjective(id
, lbcount
) -- This generates the above-mentioned metaobjective, including doing the database lookup.
731 QuestHelper
: Assert(db
)
733 local watched
= IsQuestWatched(index
)
735 -- 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)
737 db
.tracker_clicked
= function () Clicky(lindex
) end
739 db
.type_quest
.index
= index
743 local timer
= GetQuestIndexForTimer(timidx
)
744 if not timer
then timidx
= nil break end
745 if timer
== index
then break end
748 local timed
= not not timidx
750 --print(id, title, level, groupsize, variety, groupsize, complete, timed)
751 local chunk
= "q:" .. Serialize(id
, title
, level
, groupsize
, variety
, groupsize
, complete
, timed
)
752 for i
= 1, lbcount
do
753 QuestHelper
: Assert(db
[i
])
754 db
[i
].temp_desc
, db
[i
].temp_typ
, db
[i
].temp_done
= GetQuestLogLeaderBoard(i
, index
)
755 --[[if not db[i].temp_desc or is_uncached(db[i].temp_typ, db[i].temp_desc, db[i].temp_done) then
756 db[i].temp_desc = string.format("(missing description %d)", i)
758 db
[i
].temp_person
= player
760 db
[i
].tooltip_defer_questname
= title
761 db
[i
].tooltip_defer_questobjective
= db
[i
].temp_desc
-- yoink
762 QuestHelper
: Assert(db
[i
].tooltip_defer_questobjective
) -- hmmm
764 chunk
= chunk
.. ":" .. Serialize(db
[i
].temp_desc
, db
[i
].temp_typ
, db
[i
].temp_done
)
767 db
.finish
.tooltip_defer_questname
= title
-- we're using this as our fallback right now
769 next_chunks
[id
] = chunk
771 QuestProcessor(player
, db
, title
, level
, groupsize
, variety
, groupsize
, watched
, complete
, lbcount
, timed
)
778 EndInsertionPass(player
)
780 QH_Route_Filter_Rescan(nil, true) -- 'cause filters may also change, but let's not bother getting too excited about it
782 if not QuestHelper_Pref
.solo
and QuestHelper_Pref
.share
then
783 for k
, v
in pairs(next_chunks
) do
784 if current_chunks
[k
] ~= v
then
789 for k
, v
in pairs(current_chunks
) do
790 if not next_chunks
[k
] then
791 SAM(string.format("q:n%d", k
), "PARTY")
796 current_chunks
= next_chunks
799 QH_Timeslice_PopUnyieldable()
802 -- comm_packets[user][qid] = data
803 local comm_packets
= {}
805 local function RefreshUserComms(user
)
806 StartInsertionPass(user
)
808 if comm_packets
[user
] then for _
, dat
in pairs(comm_packets
[user
]) do
809 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]
814 if dat
[#obj
* 3 + objstart
] == nil and dat
[#obj
* 3 + objstart
+ 1] == nil and dat
[#obj
* 3 + objstart
+ 2] == nil then break end
815 table.insert(obj
, {dat
[#obj
* 3 + objstart
], dat
[#obj
* 3 + objstart
+ 1], dat
[#obj
* 3 + objstart
+ 2]})
819 local db
= GetQuestMetaobjective(id
, lbcount
) -- This generates the above-mentioned metaobjective, including doing the database lookup.
821 QuestHelper
: Assert(db
)
823 for i
= 1, lbcount
do
824 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
827 QuestProcessor(user
, db
, title
, level
, group
, variety
, groupsize
, "(ignore)", complete
, lbcount
, false)
830 EndInsertionPass(user
)
832 QH_Route_Filter_Rescan() -- 'cause filters may also change
835 function QH_InsertCommPacket(user
, data
)
836 local q
, chunk
= data
:match("([^:]+):(.*)")
837 if q
~= "q" then return end
841 for item
in chunk
:gmatch("([^:]+)") do
842 dat
[idx
] = DeSerItem(item
)
846 if not comm_packets
[user
] then comm_packets
[user
] = {} end
848 comm_packets
[user
][dat
[1]]
= nil
850 comm_packets
[user
][dat
[1]]
= dat
854 RefreshUserComms(user
)
857 local function QH_DumpCommUser(user
)
858 comm_packets
[user
] = nil
859 RefreshUserComms(user
)
862 QH_Event("PLAYER_ENTERING_WORLD", UpdateTrigger
)
863 QH_Event("UNIT_QUEST_LOG_CHANGED", UpdateTrigger
)
864 QH_Event("QUEST_LOG_UPDATE", QH_UpdateQuests
)
866 -- 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.
867 QH_AddNotifier(GetTime() + 5, function ()
868 local aqw_orig
= AddQuestWatch
869 AddQuestWatch
= function(...)
871 QH_UpdateQuests(true)
873 local rqw_orig
= RemoveQuestWatch
874 RemoveQuestWatch
= function(...)
876 QH_UpdateQuests(true)
880 -- 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.
881 --[[local function autonotify()
882 QH_UpdateQuests(true)
883 QH_AddNotifier(GetTime() + 5, autonotify)
885 QH_AddNotifier(GetTime() + 30, autonotify)]]
887 local old_playerlist
= {}
889 function QH_Questcomm_Sync()
890 if not (not QuestHelper_Pref
.solo
and QuestHelper_Pref
.share
) then
895 local playerlist
= {}
896 --[[if GetNumRaidMembers() > 0 then
898 local liv = UnitName(string.format("raid%d", i))
899 if liv then playerlist[liv] = true end
901 elseif]] if GetNumPartyMembers() > 0 then
904 local targ
= string.format("party%d", i
)
905 local liv
, relm
= UnitName(targ
)
906 if liv
and not relm
and liv
~= UNKNOWNOBJECT
and UnitIsConnected(targ
) then playerlist
[liv
] = true end
909 playerlist
[UnitName("player")] = nil
912 for k
, v
in pairs(playerlist
) do
913 if not old_playerlist
[k
] then
914 --print("new player:", k)
915 table.insert(additions
, k
)
920 for k
, v
in pairs(old_playerlist
) do
921 if not playerlist
[k
] then
922 --print("lost player:", k)
923 table.insert(removals
, k
)
927 old_playerlist
= playerlist
929 for _
, v
in ipairs(removals
) do
933 if #additions
== 0 then return end
935 if #additions
== 1 then
936 SAM("syn:2", "WHISPER", additions
[1])
938 SAM("syn:2", "PARTY")
944 local newer_reported
= false
945 local older_reported
= false
946 function QH_Questcomm_Msg(data
, from
)
947 if data
:match("syn:0") then
948 QH_DumpCommUser(from
)
951 if QuestHelper_Pref
.solo
then return end
953 --print("received", from, data)
957 local key
, value
= data
:match("(.):(.*)")
960 elseif key
== "x" then
962 aku
[from
] = aku
[from
] .. value
964 elseif key
== "X" then
966 aku
[from
] = aku
[from
] .. value
975 if not cont
then return end
978 --print("packet received", from, data)
979 if data
:match("syn:.*") then
980 local synv
= data
:match("syn:([0-9]*)")
981 if synv
then synv
= tonumber(synv
) end
982 if synv
and synv
~= 2 then
983 if synv
> 2 and not newer_reported
then
984 QuestHelper
:TextOut(QHFormat("PEER_NEWER", from
))
985 newer_reported
= true
986 elseif synv
< 2 and not older_reported
then
987 QuestHelper
:TextOut(QHFormat("PEER_OLDER", from
))
988 older_reported
= true
992 if synv
and synv
>= 2 then
993 SAM("hello:2", "WHISPER", from
)
995 elseif data
== "hello:2" or data
== "retrieve:2" then
996 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
998 for k
, v
in pairs(current_chunks
) do
999 SAM(v
, "WHISPER", from
)
1002 if old_playerlist
[from
] then
1003 QH_InsertCommPacket(from
, data
)
1008 function QuestHelper
:SetShare(flag
)
1012 SAM("syn:0", "PARTY")
1013 local cpb
= comm_packets
1015 for k
in pairs(cpb
) do RefreshUserComms(k
) end