1 QuestHelper_File
["objective.lua"] = "Development Version"
2 QuestHelper_Loadtime
["objective.lua"] = GetTime()
4 local function ObjectiveCouldBeFirst(self
)
5 if (self
.user_ignore
== nil and self
.auto_ignore
) or self
.user_ignore
then
9 for i
, j
in pairs(self
.after
) do
18 local function DefaultObjectiveKnown(self
)
19 if self
.user_ignore
== nil then
20 if (self
.filter_zone
and QuestHelper_Pref
.filter_zone
) or
21 (self
.filter_done
and QuestHelper_Pref
.filter_done
) or
22 (self
.filter_level
and QuestHelper_Pref
.filter_level
) or
23 (self
.filter_blocked
and QuestHelper_Pref
.filter_blocked
) or
24 (self
.filter_watched
and QuestHelper_Pref
.filter_watched
) then
27 elseif self
.user_ignore
then
31 for i
, j
in pairs(self
.after
) do
32 if i
.watched
and not i
:Known() then -- Need to know how to do everything before this objective.
40 local function ObjectiveReason(self
, short
)
41 local reason
, rc
= nil, 0
43 for r
, c
in pairs(self
.reasons
) do
44 if not reason
or c
> rc
or (c
== rc
and r
> reason
) then
50 if not reason
then reason
= "Do some extremely secret unspecified something." end
52 if not short
and self
.pos
and self
.pos
[6] then
53 reason
= reason
.. "\n" .. self
.pos
[6]
59 local function Uses(self
, obj
, text
)
60 if self
== obj
then return end -- You cannot use yourself. A purse is not food.
61 local uses
, used
= self
.uses
, obj
.used
64 uses
= QuestHelper
:CreateTable("uses")
69 used
= QuestHelper
:CreateTable("used")
80 local function DoMarkUsed(self
)
81 -- Objectives should call 'self:Uses(objective, text)' to mark objectives they use by don't directly depend on.
82 -- This information is used in tooltips.
83 -- text is passed to QHFormat with the name of the objective being used.
86 local function MarkUsed(self
)
87 if not self
.marked_used
then
91 self
.marked_used
= self
.marked_used
+ 1
95 local function MarkUnused(self
)
96 assert(self
.marked_used
)
98 if self
.marked_used
== 1 then
99 local uses
= self
.uses
102 for obj
in pairs(uses
) do
107 QuestHelper
:ReleaseTable(uses
)
112 assert(not next(self
.used
))
113 QuestHelper
:ReleaseTable(self
.used
)
117 self
.marked_used
= nil
119 self
.marked_used
= self
.marked_used
- 1
123 local function DummyObjectiveKnown(self
)
124 return (self
.o
.pos
or self
.fb
.pos
) and DefaultObjectiveKnown(self
)
127 local function ItemKnown(self
)
128 if not DefaultObjectiveKnown(self
) then return false end
130 if self
.o
.vendor
then
131 for i
, npc
in ipairs(self
.o
.vendor
) do
132 local n
= self
.qh
:GetObjective("monster", npc
)
133 local faction
= n
.o
.faction
or n
.fb
.faction
134 if (not faction
or faction
== self
.qh
.faction
) and n
:Known() then
140 if self
.fb
.vendor
then
141 for i
, npc
in ipairs(self
.fb
.vendor
) do
142 local n
= self
.qh
:GetObjective("monster", npc
)
143 local faction
= n
.o
.faction
or n
.fb
.faction
144 if (not faction
or faction
== self
.qh
.faction
) and n
:Known() then
150 if self
.o
.pos
or self
.fb
.pos
then
154 if self
.o
.drop
then for monster
in pairs(self
.o
.drop
) do
155 if self
.qh
:GetObjective("monster", monster
):Known() then
160 if self
.fb
.drop
then for monster
in pairs(self
.fb
.drop
) do
161 if self
.qh
:GetObjective("monster", monster
):Known() then
166 if self
.o
.contained
then for item
in pairs(self
.o
.contained
) do
167 if self
.qh
:GetObjective("item", item
):Known() then
172 if self
.fb
.contained
then for item
in pairs(self
.fb
.contained
) do
173 if self
.qh
:GetObjective("item", item
):Known() then
179 local item
=self
.quest
.o
.item
180 item
= item
and item
[self
.obj
]
187 for monster
in pairs(item
.drop
) do
188 if self
.qh
:GetObjective("monster", monster
):Known() then
195 item
=self
.quest
.fb
.item
196 item
= item
and item
[self
.obj
]
202 for monster
in pairs(item
.drop
) do
203 if self
.qh
:GetObjective("monster", monster
):Known() then
214 local function ObjectiveAppendPositions(self
, objective
, weight
, why
, restrict
)
217 if self
.o
.pos
then for i
, p
in ipairs(self
.o
.pos
) do
218 high
= math
.max(high
, p
[4])
221 if self
.fb
.pos
then for i
, p
in ipairs(self
.fb
.pos
) do
222 high
= math
.max(high
, p
[4])
227 if self
.o
.pos
then for i
, p
in ipairs(self
.o
.pos
) do
228 if not restrict
or not self
.qh
:Disallowed(p
[1]) then
229 objective
:AddLoc(p
[1], p
[2], p
[3], p
[4]*high
, why
)
233 if self
.fb
.pos
then for i
, p
in ipairs(self
.fb
.pos
) do
234 if not restrict
or not self
.qh
:Disallowed(p
[1]) then
235 objective
:AddLoc(p
[1], p
[2], p
[3], p
[4]*high
, why
)
241 local function ObjectivePrepareRouting(self
, anywhere
)
242 self
.setup_count
= self
.setup_count
+ 1
243 if not self
.setup
then
250 self
.d
= QuestHelper
:CreateTable("objective.d")
251 self
.p
= QuestHelper
:CreateTable("objective.p")
252 self
.nm
= QuestHelper
:CreateTable("objective.nm")
253 self
.nm2
= QuestHelper
:CreateTable("objective.nm2")
254 self
.nl
= QuestHelper
:CreateTable("objective.nl")
255 self
.distance_cache
= QuestHelper
:CreateTable("objective.distance_cache")
258 self
:AppendPositions(self
, 1, nil, true)
260 if not next(self
.p
) then
261 QuestHelper
:TextOut(QHFormat("INACCESSIBLE_OBJ", self
.obj
or "whatever it was you just requested"))
267 self
:AppendPositions(self
, 1, nil, false)
270 self
:FinishAddLoc(args
)
274 local function ItemAppendPositions(self
, objective
, weight
, why
, restrict
)
275 why2
= why
and why
.."\n" or ""
277 if self
.o
.vendor
then for i
, npc
in ipairs(self
.o
.vendor
) do
278 local n
= self
.qh
:GetObjective("monster", npc
)
279 local faction
= n
.o
.faction
or n
.fb
.faction
280 if (not faction
or faction
== self
.qh
.faction
) then
281 n
:AppendPositions(objective
, 1, why2
..QHFormat("OBJECTIVE_PURCHASE", npc
), restrict
)
285 if self
.fb
.vendor
then for i
, npc
in ipairs(self
.fb
.vendor
) do
286 local n
= self
.qh
:GetObjective("monster", npc
)
287 local faction
= n
.o
.faction
or n
.fb
.faction
288 if (not faction
or faction
== self
.qh
.faction
) then
289 n
:AppendPositions(objective
, 1, why2
..QHFormat("OBJECTIVE_PURCHASE", npc
), restrict
)
293 if next(objective
.p
, nil) then
294 -- If we have points from vendors, then always use vendors. I don't want it telling you to killing the
295 -- towns people just because you had to talk to them anyway, and it saves walking to the store.
299 if self
.o
.drop
then for monster
, count
in pairs(self
.o
.drop
) do
300 local m
= self
.qh
:GetObjective("monster", monster
)
301 m
:AppendPositions(objective
, m
.o
.looted
and count
/m
.o
.looted
or 1, why2
..QHFormat("OBJECTIVE_SLAY", monster
), restrict
)
304 if self
.fb
.drop
then for monster
, count
in pairs(self
.fb
.drop
) do
305 local m
= self
.qh
:GetObjective("monster", monster
)
306 m
:AppendPositions(objective
, m
.fb
.looted
and count
/m
.fb
.looted
or 1, why2
..QHFormat("OBJECTIVE_SLAY", monster
), restrict
)
309 if self
.o
.contained
then for item
, count
in pairs(self
.o
.contained
) do
310 local i
= self
.qh
:GetObjective("item", item
)
311 i
:AppendPositions(objective
, i
.o
.opened
and count
/i
.o
.opened
or 1, why2
..QHFormat("OBJECTIVE_LOOT", item
), restrict
)
314 if self
.fb
.contained
then for item
, count
in pairs(self
.fb
.contained
) do
315 local i
= self
.qh
:GetObjective("item", item
)
316 i
:AppendPositions(objective
, i
.fb
.opened
and count
/i
.fb
.opened
or 1, why2
..QHFormat("OBJECTIVE_LOOT", item
), restrict
)
319 if self
.o
.pos
then for i
, p
in ipairs(self
.o
.pos
) do
320 if not restrict
or not self
.qh
:Disallowed(p
[1]) then
321 objective
:AddLoc(p
[1], p
[2], p
[3], p
[4], why
)
325 if self
.fb
.pos
then for i
, p
in ipairs(self
.fb
.pos
) do
326 if not restrict
or not self
.qh
:Disallowed(p
[1]) then
327 objective
:AddLoc(p
[1], p
[2], p
[3], p
[4], why
)
332 local item_list
=self
.quest
.o
.item
334 local data
= item_list
[self
.obj
]
335 if data
and data
.drop
then
336 for monster
, count
in pairs(data
.drop
) do
337 local m
= self
.qh
:GetObjective("monster", monster
)
338 m
:AppendPositions(objective
, m
.o
.looted
and count
/m
.o
.looted
or 1, why2
..QHFormat("OBJECTIVE_SLAY", monster
), restrict
)
340 elseif data
and data
.pos
then
341 for i
, p
in ipairs(data
.pos
) do
342 if not restrict
or not self
.qh
:Disallowed(p
[1]) then
343 objective
:AddLoc(p
[1], p
[2], p
[3], p
[4], why
)
349 item_list
=self
.quest
.fb
.item
351 local data
= item_list
[self
.obj
]
352 if data
and data
.drop
then
353 for monster
, count
in pairs(data
.drop
) do
354 local m
= self
.qh
:GetObjective("monster", monster
)
355 m
:AppendPositions(objective
, m
.fb
.looted
and count
/m
.fb
.looted
or 1, why2
..QHFormat("OBJECTIVE_SLAY", monster
), restrict
)
357 elseif data
and data
.pos
then
358 for i
, p
in ipairs(data
.pos
) do
359 if not restrict
or not self
.qh
:Disallowed(p
[1]) then
360 objective
:AddLoc(p
[1], p
[2], p
[3], p
[4], why
)
368 local function ItemDoMarkUsed(self
)
369 if self
.o
.vendor
then for i
, npc
in ipairs(self
.o
.vendor
) do
370 local n
= self
.qh
:GetObjective("monster", npc
)
371 local faction
= n
.o
.faction
or n
.fb
.faction
372 if (not faction
or faction
== self
.qh
.faction
) then
373 self
:Uses(n
, "TOOLTIP_PURCHASE")
377 if self
.fb
.vendor
then for i
, npc
in ipairs(self
.fb
.vendor
) do
378 local n
= self
.qh
:GetObjective("monster", npc
)
379 local faction
= n
.o
.faction
or n
.fb
.faction
380 if (not faction
or faction
== self
.qh
.faction
) then
381 self
:Uses(n
, "TOOLTIP_PURCHASE")
385 if self
.o
.drop
then for monster
, count
in pairs(self
.o
.drop
) do
386 self
:Uses(self
.qh
:GetObjective("monster", monster
), "TOOLTIP_SLAY")
389 if self
.fb
.drop
then for monster
, count
in pairs(self
.fb
.drop
) do
390 self
:Uses(self
.qh
:GetObjective("monster", monster
), "TOOLTIP_SLAY")
393 if self
.o
.contained
then for item
, count
in pairs(self
.o
.contained
) do
394 self
:Uses(self
.qh
:GetObjective("item", item
), "TOOLTIP_LOOT")
397 if self
.fb
.contained
then for item
, count
in pairs(self
.fb
.contained
) do
398 self
:Uses(self
.qh
:GetObjective("item", item
), "TOOLTIP_LOOT")
402 local item_list
=self
.quest
.o
.item
404 local data
= item_list
[self
.obj
]
405 if data
and data
.drop
then
406 for monster
, count
in pairs(data
.drop
) do
407 self
:Uses(self
.qh
:GetObjective("monster", monster
), "TOOLTIP_SLAY")
412 item_list
=self
.quest
.fb
.item
414 local data
= item_list
[self
.obj
]
415 if data
and data
.drop
then
416 for monster
, count
in pairs(data
.drop
) do
417 self
:Uses(self
.qh
:GetObjective("monster", monster
), "TOOLTIP_SLAY")
447 local function AddLoc(self
, index
, x
, y
, w
, why
)
448 assert(not self
.setup
)
451 local pair
= QuestHelper_ZoneLookup
[index
]
452 if not pair
then return end -- that zone doesn't exist! We require more vespene gas. Not enough rage!
453 local c
, z
= pair
[1], pair
[2]
454 x
, y
= self
.qh
.Astrolabe
:TranslateWorldMapPosition(c
, z
, x
, y
, c
, 0)
456 x
= x
* self
.qh
.continent_scales_x
[c
]
457 y
= y
* self
.qh
.continent_scales_y
[c
]
458 local list
= self
.qh
.zone_nodes
[index
]
460 local points
= self
.p
[list
]
462 points
= QuestHelper
:CreateTable("objective.p[zone] (objective nodes per-zone)")
463 self
.p
[list
] = points
466 for i
, p
in pairs(points
) do
467 local u
, v
= x
-p
[3], y
-p
[4]
468 if u
*u
+v
*v
< 25 then -- Combine points within a threshold of 5 seconds travel time.
469 p
[3] = (p
[3]*p
[5]+x
*w
)/(p
[5]+w
)
470 p
[4] = (p
[4]*p
[5]+y
*w
)/(p
[5]+w
)
479 local new
= QuestHelper
:CreateTable("objective.p[zone] (possible objective node)")
480 new
[1], new
[2], new
[3], new
[4], new
[5], new
[6], new
[7] = list
, nil, x
, y
, w
, why
, w
481 table.insert(points
, new
)
485 local function FinishAddLoc(self
, args
)
488 for z
, pl
in pairs(self
.p
) do
489 for i
, p
in ipairs(pl
) do
497 if not self
.zones
then
498 -- Not using CreateTable, because it will not be released when routing is complete.
501 -- We could remove the already known zones, but I'm operating under the assumtion that locations will only be added,
502 -- not removed, so this isn't necessary.
505 -- Remove probably useless locations.
506 for z
, pl
in pairs(self
.p
) do
507 local remove_zone
= true
510 if pl
[i
][5] < mx
*0.2 then
511 QuestHelper
:ReleaseTable(pl
[i
])
519 QuestHelper
:ReleaseTable(self
.p
[z
])
522 self
.zones
[z
.i
] = true
526 local node_map
= self
.nm
527 local node_list
= self
.nl
529 for list
, pl
in pairs(self
.p
) do
530 local dist
= self
.d
[list
]
535 dist
= QuestHelper
:CreateTable("self.d[list]")
539 for i
, point
in ipairs(pl
) do
540 point
[5] = mx
/point
[5] -- Will become 1 for the most desired location, and become larger and larger for less desireable locations.
542 point
[2] = QuestHelper
:CreateTable("possible objective node to zone edge cache")
544 for i
, node
in ipairs(list
) do
545 QuestHelper
: Assert(type(point
[3]) == "number", string.format("p3 %s", tostring(point
[3])))
546 QuestHelper
: Assert(type(point
[4]) == "number", string.format("p4 %s", tostring(point
[4])))
547 QuestHelper
: Assert(type(node
.x
) == "number", string.format("nx %s", tostring(node
.x
)))
548 QuestHelper
: Assert(type(node
.y
) == "number", string.format("ny %s", tostring(node
.y
)))
549 local u
, v
= point
[3]-node
.x
, point
[4]-node
.y
550 local d
= math
.sqrt(u
*u
+v
*v
)
555 if d
*point
[5] < dist
[i
][1]*dist
[i
][2] then
556 dist
[i
][1], dist
[i
][2] = d
, point
[5]
557 node_map
[node
] = point
560 local pair
= QuestHelper
:CreateTable()
561 pair
[1], pair
[2] = d
, point
[5]
564 if not node_map
[node
] then
565 table.insert(node_list
, node
)
566 node_map
[node
] = point
568 u
, v
= node_map
[node
][3]-node
.x
, node_map
[node
][4]-node
.y
570 if dist
[i
][1]*dist
[i
][2] < math
.sqrt(u
*u
+v
*v
)*node_map
[node
][5] then
571 node_map
[node
] = point
579 -- Disabled because we're having some data sanity issues. This should be solved at buildtime, but I'm leery of mucking with the build system right now, so it isn't. Re-enable later.
580 --if not args or not args.failable then
581 -- if #node_list == 0 and QuestHelper:IsWrath() then QuestHelper:Error(self.cat.."/"..self.obj..": zero nodes!") end
584 assert(not self
.setup
)
586 table.insert(self
.qh
.prepared_objectives
, self
)
589 local function GetPosition(self
)
595 local QH_TESTCACHE
= nil -- make this "true" or something if you want to test caching (i.e. recalculate everything, then verify that the cache is valid)
597 -- Note: Pos is the starting point, the objective is the destination. These are different data formats - "self" can be a set of points.
598 -- More annotation here, if you're trying to learn the codebase. This function is a more complicated version of QH:ComputeTravelTime, so refer to that for information first before reading this one.
599 local function ObjectiveTravelTime(self
, pos
, nocache
)
602 -- The caching is pretty obvious.
605 assert(pos
~= QuestHelper
.pos
)
607 pos
.key
= math
.random()..""
610 cached
= self
.distance_cache
[key
]
612 if not QH_TESTCACHE
then
613 return unpack(cached
)
618 local graph
= self
.qh
.world_graph
621 graph
:PrepareSearch()
623 -- This is quite similar to the same "create nodes for all zone links" in ComputeTravelTime except that it's creating nodes for all zone links for a set of possible destinations. I'm not sure if the weighting is backwards. It might be.
624 for z
, l
in pairs(self
.d
) do
625 for i
, n
in ipairs(z
) do
627 n
.e
, n
.w
= unpack(l
[i
])
629 elseif n
.e
* n
.w
< l
[i
][1]*l
[i
][2] then
630 n
.e
, n
.w
= unpack(l
[i
])
636 for i
, n
in ipairs(pos
[1]) do
637 graph
:AddStartNode(n
, d
[i
], nl
)
640 local e
= graph
:DoSearch(nl
)
642 -- d changes datatype here. I hate this codebase. Hell, e probably changes datatype also! yaaaay. what does .nm mean? what does .d mean?
646 -- There's something going on with weighting here that I don't understand
647 local l
= self
.p
[pos
[1]]
649 local x
, y
= pos
[3], pos
[4]
652 for i
, n
in ipairs(l
) do
653 local u
, v
= x
-n
[3], y
-n
[4]
654 local d2
= math
.sqrt(u
*u
+v
*v
)
657 d
, e
, score
= d2
, n
, s
664 assert( not cached
or (cached
[1] == d
and cached
[2] == e
))
665 if not QH_TESTCACHE
or not cached
then
666 local new
= self
.qh
:CreateTable()
667 new
[1], new
[2] = d
, e
668 self
.distance_cache
[key
] = new
669 self
.qh
:CacheRegister(self
)
672 if self
.distance_cache
and self
.distance_cache
[key
] then
673 assert(self
.distance_cache
[key
][1] == d
)
680 -- Note: pos1 is the starting point, pos2 is the ending point, the objective is somewhere between them.
681 -- Yet more annotation! This one is based off ObjectiveTravelTime. Yes, it's nasty that there are three (edit: four) functions with basically the same goal. Have I mentioned this codebase kind of sucks?
682 local function ObjectiveTravelTime2(self
, pos1
, pos2
, nocache
)
685 -- caching is pretty simple as usual
688 assert(pos1
~= QuestHelper
.pos
)
689 assert(pos2
~= QuestHelper
.pos
)
690 -- We don't want to cache distances involving the player's current position, as that would spam the table
692 pos1
.key
= math
.random()..""
695 pos2
.key
= math
.random()..""
697 key
= pos1
.key
..pos2
.key
698 cached
= self
.distance_cache
[key
]
700 if not QH_TESTCACHE
then
701 return unpack(cached
)
706 local graph
= self
.qh
.world_graph
709 -- This is the standard pos1-to-self code that we're used to seeing . . .
710 graph
:PrepareSearch()
712 for z
, l
in pairs(self
.d
) do
713 for i
, n
in ipairs(z
) do
715 n
.e
, n
.w
= unpack(l
[i
])
717 elseif n
.e
* n
.w
< l
[i
][1]*l
[i
][2] then
718 n
.e
, n
.w
= unpack(l
[i
])
724 for i
, n
in ipairs(pos1
[1]) do
725 graph
:AddStartNode(n
, d
[i
], nl
)
728 graph
:DoFullSearch(nl
)
730 graph
:PrepareSearch()
732 -- . . . and here's where it gets wonky
733 -- Now, we need to figure out how long it takes to get to each node.
734 for z
, point_list
in pairs(self
.p
) do
736 -- Will also consider min distance.
737 local x
, y
= pos1
[3], pos1
[4]
739 for i
, p
in ipairs(point_list
) do
740 local a
, b
= p
[3]-x
, p
[4]-y
741 local u
, v
= p
[3], p
[4]
742 local d
= math
.sqrt(a
*a
+b
*b
)
745 for i
, n
in ipairs(z
) do
747 local bleh
= math
.sqrt(a
*a
+b
*b
)+n
.g
756 for i
, p
in ipairs(point_list
) do
757 local x
, y
= p
[3], p
[4]
762 for i
, n
in ipairs(z
) do
763 local a
, b
= n
.x
-x
, n
.y
-y
764 local d2
= math
.sqrt(a
*a
+b
*b
)+n
.g
766 if not score
or s
< score
then
777 for i
, n
in ipairs(pos2
[1]) do
785 for z
, l
in pairs(self
.d
) do
786 for i
, n
in ipairs(z
) do
787 local x
, y
= n
.x
, n
.y
791 for i
, p
in ipairs(self
.p
[z
]) do
792 local a
, b
= x
-p
[3], y
-p
[4]
793 d
= p
[7]+math
.sqrt(a
*a
+b
*b
)
795 if not bs
or s
< bs
then
801 -- Using score instead of distance, because we want nodes we're not really interested in to be less likely to get chosen.
802 graph
:AddStartNode(n
, bs
, el
)
806 local e
= graph
:DoSearch(pos2
[1])
809 local d2
= e
.g
+e
.e
-e
.p
.g
+(e
.p
.g
/nm
[e
.p
][5]-nm
[e
.p
][7])
812 local total
= (d
+d2
)*e
[5]
815 local x
, y
= pos2
[3], pos2
[4]
816 for i
, p
in ipairs(self
.p
[el
]) do
817 local a
, b
= x
-p
[3], y
-p
[4]
818 local c
= math
.sqrt(a
*a
+b
*b
)
819 local t
= (p
[7]+c
)*p
[5]
821 total
, d
, d2
, e
= t
, p
[7], c
, p
826 -- grim stabilization hack, since obviously the numbers it generates are only vaguely based in reality. This should be fixed and removed ASAP (you know, once I figure out WTF this thing is doing)
827 d
= QuestHelper
:ComputeTravelTime(pos1
, e
)
828 d2
= QuestHelper
:ComputeTravelTime(e
, pos2
)
832 assert( not cached
or (cached
[1] == d
and cached
[2] == d2
and cached
[3] == e
))
833 if not QH_TESTCACHE
or not cached
then
834 local new
= self
.qh
:CreateTable("ObjectiveTravelTime2 cache")
835 new
[1], new
[2], new
[3] = d
, d2
, e
836 self
.distance_cache
[key
] = new
837 self
.qh
:CacheRegister(self
)
840 if self
.distance_cache
and self
.distance_cache
[key
] then
841 assert(self
.distance_cache
[key
][1] == d
and self
.distance_cache
[key
][2] == d2
)
845 --[[if pos1 and pos2 then -- Debug code so I can maybe actually fix the problems someday
846 QuestHelper:TextOut("Beginning dumping here")
848 local laxa = QuestHelper:ComputeTravelTime(pos1, e, true)
849 if math.abs(laxa-d) >= 0.0001 then
850 QuestHelper:TextOut(QuestHelper:StringizeTable(pos1))
851 QuestHelper:TextOut(QuestHelper:StringizeRecursive(pos1, 2))
852 QuestHelper:TextOut(QuestHelper:StringizeTable(e))
853 QuestHelper:TextOut(QuestHelper:StringizeTable(e[1]))
854 QuestHelper:TextOut(QuestHelper:StringizeTable(e[2]))
855 QuestHelper:TextOut(QuestHelper:StringizeRecursive(e[1], 2))]]
856 --QuestHelper:Assert(math.abs(laxa-d) < 0.0001, "Compare: "..laxa.." vs "..d) -- wonky commenting is thanks to the de-assert script, fix later
858 local laxb = QuestHelper:ComputeTravelTime(e, pos2, true)
859 if math.abs(laxb-d2) >= 0.0001 then
860 QuestHelper:TextOut(QuestHelper:StringizeTable(pos2))
861 QuestHelper:TextOut(QuestHelper:StringizeTable(e))
862 QuestHelper:TextOut(QuestHelper:StringizeTable(e[1]))
863 QuestHelper:TextOut(QuestHelper:StringizeTable(e[2]))
864 QuestHelper:TextOut(QuestHelper:StringizeRecursive(e[1], 2))]]
865 --QuestHelper:Assert(math.abs(laxa-d) < 0.0001, "Compare: "..laxb.." vs "..d2)
872 local function DoneRouting(self
)
873 assert(self
.setup_count
> 0)
876 if self
.setup_count
== 1 then
878 QuestHelper
:ReleaseObjectivePathingInfo(self
)
879 for i
, obj
in ipairs(self
.qh
.prepared_objectives
) do
881 table.remove(self
.qh
.prepared_objectives
, i
)
886 self
.setup_count
= self
.setup_count
- 1
890 local function IsObjectiveWatched(self
)
891 -- Check if an objective is being watched. Note that this is an external query, not a simple Selector.
894 if self
.cat
== "quest" then
895 info
= QuestHelper
.quest_log
[self
]
897 info
= QuestHelper
.quest_log
[self
.quest
]
901 local index
= info
.index
904 -- UberQuest has it's own way of tracking quests.
905 local uq_settings
= UberQuest_Config
[UnitName("player")]
907 local list
= uq_settings
.selected
909 return list
[GetQuestLogTitle(index
)]
913 return IsQuestWatched(index
)
922 local next_objective_id
= 0
924 local function ObjectiveShare(self
)
925 self
.want_share
= true
928 local function ObjectiveUnshare(self
)
929 self
.want_share
= false
932 QuestHelper
.default_objective_param
=
934 CouldBeFirst
=ObjectiveCouldBeFirst
,
937 DoMarkUsed
=DoMarkUsed
,
939 MarkUnused
=MarkUnused
,
941 DefaultKnown
=DefaultObjectiveKnown
,
942 Known
=DummyObjectiveKnown
,
943 Reason
=ObjectiveReason
,
945 AppendPositions
=ObjectiveAppendPositions
,
946 PrepareRouting
=ObjectivePrepareRouting
,
948 FinishAddLoc
=FinishAddLoc
,
949 DoneRouting
=DoneRouting
,
951 Position
=GetPosition
,
952 TravelTime
=ObjectiveTravelTime
,
953 TravelTime2
=ObjectiveTravelTime2
,
955 IsWatched
=IsObjectiveWatched
,
957 Share
=ObjectiveShare
, -- Invoke to share this objective with your peers.
958 Unshare
=ObjectiveUnshare
, -- Invoke to stop sharing this objective.
961 QuestHelper
.default_objective_item_param
=
964 AppendPositions
= ItemAppendPositions
,
965 DoMarkUsed
= ItemDoMarkUsed
968 for key
, value
in pairs(QuestHelper
.default_objective_param
) do
969 if not QuestHelper
.default_objective_item_param
[key
] then
970 QuestHelper
.default_objective_item_param
[key
] = value
974 QuestHelper
.default_objective_meta
= { __index
= QuestHelper
.default_objective_param
}
975 QuestHelper
.default_objective_item_meta
= { __index
= QuestHelper
.default_objective_item_param
}
977 function QuestHelper
:NewObjectiveObject()
978 next_objective_id
= next_objective_id
+1
982 id
=next_objective_id
,
984 want_share
=false, -- True if we want this objective shared.
985 is_sharing
=false, -- Set to true if we've told other users about this objective.
987 user_ignore
=nil, -- When nil, will use filters. Will ignore, when true, always show (if known).
989 priority
=3, -- A hint as to what priority the quest should have. Should be 1, 2, 3, 4, or 5.
990 real_priority
=3, -- This will be set to the priority routing actually decided to assign it.
1001 before
={}, -- List of objectives that this objective must appear before.
1002 after
={}, -- List of objectives that this objective must appear after.
1004 -- Routing related junk.
1006 --[[ Will be created as needed.
1009 nm=nil, -- Maps nodes to their nearest zone/list/x/y position.
1010 nm2=nil, -- Maps nodes to their nears position, but dynamically set in TravelTime2.
1011 nl=nil, -- List of all the nodes we need to consider.
1012 location=nil, -- Will be set to the best position for the node.
1013 pos=nil, -- Zone node list, distance list, x, y, reason.
1015 }, QuestHelper
.default_objective_meta
)
1018 local explicit_support_warning_given
= false
1020 function QuestHelper
:GetObjective(category
, objective
)
1021 local objective_list
= self
.objective_objects
[category
]
1023 if not objective_list
then
1025 self
.objective_objects
[category
] = objective_list
1028 local objective_object
= objective_list
[objective
]
1030 if not objective_object
then
1031 if category
== "quest" then
1032 local level
, hash
, name
= string.match(objective
, "^(%d+)/(%d*)/(.*)$")
1034 level
, name
= string.match(objective
, "^(%d+)/(.*)$")
1040 if hash
== "" then hash
= nil end
1041 objective_object
= self
:GetQuest(name
, tonumber(level
), tonumber(hash
))
1042 objective_list
[objective
] = objective_object
1043 return objective_object
1046 objective_object
= self
:NewObjectiveObject()
1048 objective_object
.cat
= category
1049 objective_object
.obj
= objective
1051 if category
== "item" then
1052 setmetatable(objective_object
, QuestHelper
.default_objective_item_meta
)
1053 objective_object
.icon_id
= 2
1054 elseif category
== "monster" then
1055 objective_object
.icon_id
= 1
1056 elseif category
== "object" then
1057 objective_object
.icon_id
= 3
1058 elseif category
== "event" then
1059 objective_object
.icon_id
= 4
1060 elseif category
== "loc" then
1061 objective_object
.icon_id
= 6
1062 elseif category
== "reputation" then
1063 objective_object
.icon_id
= 5
1064 elseif category
== "player" then
1065 objective_object
.icon_id
= 1 -- not ideal, will improve later
1067 if not explicit_support_warning_given
then
1068 self
:TextOut("FIXME: Objective type '"..category
.."' for objective '"..objective
.."' isn't explicitly supported yet; hopefully the dummy handler will do something sensible.")
1069 explicit_support_warning_given
= true
1073 objective_list
[objective
] = objective_object
1075 if category
== "loc" then
1076 -- Loc is special, we don't store it, and construct it from the string.
1077 -- Don't have any error checking here, will assume it's correct.
1079 local _
, _
, c
, z
, x
, y
= string.find(objective
,"^(%d+),(%d+),([%d%.]+),([%d%.]+)$")
1082 _
, _
, i
, x
, y
= string.find(objective
,"^(%d+),([%d%.]+),([%d%.]+)$")
1084 i
= QuestHelper_IndexLookup
[c
][z
]
1087 objective_object
.o
= {pos
={{tonumber(i
),tonumber(x
),tonumber(y
),1}}}
1088 objective_object
.fb
= {}
1090 objective_list
= QuestHelper_Objectives_Local
[category
]
1091 if not objective_list
then
1093 QuestHelper_Objectives_Local
[category
] = objective_list
1095 objective_object
.o
= objective_list
[objective
]
1096 if not objective_object
.o
then
1097 objective_object
.o
= {}
1098 objective_list
[objective
] = objective_object
.o
1100 local l
= QuestHelper_StaticData
[self
.locale
]
1102 objective_list
= l
.objective
[category
]
1103 if objective_list
then
1104 objective_object
.fb
= objective_list
[objective
]
1107 if not objective_object
.fb
then
1108 objective_object
.fb
= {}
1111 -- TODO: If we have some other source of information (like LightHeaded) add its data to objective_object.fb
1116 return objective_object
1119 function QuestHelper
:AppendObjectivePosition(objective
, i
, x
, y
, w
)
1120 if not i
then return end -- We don't have a player position. We have a pile of poop. Enjoy your poop.
1122 local pos
= objective
.o
.pos
1124 if objective
.o
.drop
or objective
.o
.contained
then
1125 return -- If it's dropped by a monster, don't record the position we got the item at.
1127 objective
.o
.pos
= self
:AppendPosition({}, i
, x
, y
, w
)
1129 self
:AppendPosition(pos
, i
, x
, y
, w
)
1133 function QuestHelper
:AppendObjectiveDrop(objective
, monster
, count
)
1134 local drop
= objective
.o
.drop
1136 drop
[monster
] = (drop
[monster
] or 0)+(count
or 1)
1138 objective
.o
.drop
= {[monster
] = count
or 1}
1139 objective
.o
.pos
= nil -- If it's dropped by a monster, then forget the position we found it at.
1143 function QuestHelper
:AppendItemObjectiveDrop(item_object
, item_name
, monster_name
, count
)
1144 local quest
= self
:ItemIsForQuest(item_object
, item_name
)
1145 if quest
and not item_object
.o
.vendor
and not item_object
.o
.drop
and not item_object
.o
.pos
then
1146 self
:AppendQuestDrop(quest
, item_name
, monster_name
, count
)
1148 if not item_object
.o
.drop
and not item_object
.o
.pos
then
1149 self
:PurgeQuestItem(item_object
, item_name
)
1151 self
:AppendObjectiveDrop(item_object
, monster_name
, count
)
1155 function QuestHelper
:AppendItemObjectivePosition(item_object
, item_name
, i
, x
, y
)
1156 local quest
= self
:ItemIsForQuest(item_object
, item_name
)
1157 if quest
and not item_object
.o
.vendor
and not item_object
.o
.drop
and not item_object
.o
.pos
then
1158 self
:AppendQuestPosition(quest
, item_name
, i
, x
, y
)
1160 if not item_object
.o
.vendor
and not item_object
.o
.drop
and not item_object
.o
.contained
and not item_object
.o
.pos
then
1161 -- Just learned that this item doesn't depend on a quest to drop, remove any quest references to it.
1162 self
:PurgeQuestItem(item_object
, item_name
)
1164 self
:AppendObjectivePosition(item_object
, i
, x
, y
)
1168 function QuestHelper
:AppendItemObjectiveContainer(objective
, container_name
, count
)
1169 local container
= objective
.o
.contained
1171 container
[container_name
] = (container
[container_name
] or 0)+(count
or 1)
1173 objective
.o
.contained
= {[container_name
] = count
or 1}
1174 objective
.o
.pos
= nil -- Forget the position.
1178 function QuestHelper
:AddObjectiveWatch(objective
, reason
)
1179 if not objective
.reasons
then
1180 objective
.reasons
= {}
1183 if not next(objective
.reasons
, nil) then
1184 objective
.watched
= true
1185 objective
:MarkUsed()
1187 objective
.filter_blocked
= false
1188 for obj
in pairs(objective
.swap_after
or objective
.after
) do
1190 objective
.filter_blocked
= true
1195 for obj
in pairs(objective
.swap_before
or objective
.before
) do
1197 obj
.filter_blocked
= true
1201 if self
.to_remove
[objective
] then
1202 self
.to_remove
[objective
] = nil
1204 self
.to_add
[objective
] = true
1208 objective
.reasons
[reason
] = (objective
.reasons
[reason
] or 0) + 1
1211 function QuestHelper
:RemoveObjectiveWatch(objective
, reason
)
1212 if objective
.reasons
[reason
] == 1 then
1213 objective
.reasons
[reason
] = nil
1214 if not next(objective
.reasons
, nil) then
1215 objective
:MarkUnused()
1216 objective
.watched
= false
1218 for obj
in pairs(objective
.swap_before
or objective
.before
) do
1220 obj
.filter_blocked
= false
1221 for obj2
in pairs(obj
.swap_after
or obj
.after
) do
1222 if obj2
.watched
then
1223 obj
.filter_blocked
= true
1230 if self
.to_add
[objective
] then
1231 self
.to_add
[objective
] = nil
1233 self
.to_remove
[objective
] = true
1237 objective
.reasons
[reason
] = objective
.reasons
[reason
] - 1
1241 function QuestHelper
:ObjectiveObjectDependsOn(objective
, needs
)
1242 assert(objective
~= needs
) -- If this was true, ObjectiveIsKnown would get in an infinite loop.
1243 -- TODO: Needs sanity checking, especially now that dependencies can be assigned by remote users.
1246 -- We store the new relationships in objective.swap_[before|after],
1247 -- creating and copying them from objective.[before|after],
1248 -- the routing coroutine will check for those, swap them, and release the originals
1249 -- when it gets to a safe place to do so.
1251 if not (objective
.swap_after
or objective
.after
)[needs
] then
1252 if objective
.peer
then
1253 for u
, l
in pairs(objective
.peer
) do
1254 -- Make sure other users know that the dependencies for this objective changed.
1255 objective
.peer
[u
] = math
.min(l
, 1)
1259 if not objective
.swap_after
then
1260 objective
.swap_after
= self
:CreateTable("swap_after")
1261 for key
,value
in pairs(objective
.after
) do objective
.swap_after
[key
] = value
end
1264 if not needs
.swap_before
then
1265 needs
.swap_before
= self
:CreateTable("swap_before")
1266 for key
,value
in pairs(needs
.before
) do needs
.swap_before
[key
] = value
end
1269 if needs
.watched
then
1270 objective
.filter_blocked
= true
1273 objective
.swap_after
[needs
] = true
1274 needs
.swap_before
[objective
] = true
1278 local UserIgnored
= {
1279 name
= "user_manual_ignored",
1281 friendly_reason
= QHText("FILTERED_USER"),
1282 AddException
= function(self
, node
)
1283 QH_Route_UnignoreNode(node
, self
) -- there isn't really state with this one
1287 function QuestHelper
:AddObjectiveOptionsToMenu(obj
, menu
)
1288 local submenu
= self
:CreateMenu()
1290 local pri
= (QH_Route_GetClusterPriority(obj
.cluster
) or 0) + 3
1292 local name
= QHText("PRIORITY"..i
)
1293 local item
= self
:CreateMenuItem(submenu
, name
)
1297 tex
= self
:CreateIconTexture(item
, 10)
1299 tex
= self
:CreateIconTexture(item
, 12)
1300 tex
:SetVertexColor(1, 1, 1, 0)
1303 item
:AddTexture(tex
, true)
1304 item
:SetFunction(QH_Route_SetClusterPriority
, obj
.cluster
, i
- 3)
1307 self
:CreateMenuItem(menu
, QHText("PRIORITY")):SetSubmenu(submenu
)
1309 --[[if self.sharing then
1310 submenu = self:CreateMenu()
1311 local item = self:CreateMenuItem(submenu, QHText("SHARING_ENABLE"))
1312 local tex = self:CreateIconTexture(item, 10)
1313 if not obj.want_share then tex:SetVertexColor(1, 1, 1, 0) end
1314 item:AddTexture(tex, true)
1315 item:SetFunction(obj.Share, obj)
1317 local item = self:CreateMenuItem(submenu, QHText("SHARING_DISABLE"))
1318 local tex = self:CreateIconTexture(item, 10)
1319 if obj.want_share then tex:SetVertexColor(1, 1, 1, 0) end
1320 item:AddTexture(tex, true)
1321 item:SetFunction(obj.Unshare, obj)
1323 self:CreateMenuItem(menu, QHText("SHARING")):SetSubmenu(submenu)
1326 --self:CreateMenuItem(menu, "(No options available)")
1328 if not obj
.map_suppress_ignore
then
1329 self
:CreateMenuItem(menu
, QHText("IGNORE")):SetFunction(function () if obj
.cluster
then for _
, v
in ipairs(obj
.cluster
) do QH_Route_IgnoreNode(v
, UserIgnored
) end end end) -- There is probably a nasty race condition here. I'm not entirely happy about it.
1331 if obj
.map_custom_menu
then
1332 obj
.map_custom_menu(menu
)
1336 self
:CreateMenuItem(menu
, QHText("IGNORE_LOCATION")):SetFunction(QH_Route_IgnoreNode
, obj
, UserIgnored
)
1340 function QuestHelper
:IgnoreObjective(objective
)
1341 if self
.user_objectives
[objective
] then
1342 self
:RemoveObjectiveWatch(objective
, self
.user_objectives
[objective
])
1343 self
.user_objectives
[objective
] = nil
1345 objective
.user_ignore
= true
1348 --self:ForceRouteUpdate()
1351 function QuestHelper
:SetObjectivePriority(objective
, level
)
1352 level
= math
.min(5, math
.max(1, math
.floor((tonumber(level
) or 3)+0.5)))
1353 if level
~= objective
.priority
then
1354 objective
.priority
= level
1355 if objective
.peer
then
1356 for u
, l
in pairs(objective
.peer
) do
1357 -- Peers don't know about this new priority.
1358 objective
.peer
[u
] = math
.min(l
, 2)
1361 --self:ForceRouteUpdate()
1365 local function CalcObjectivePriority(obj
)
1366 local priority
= obj
.priority
1368 for o
in pairs(obj
.before
) do
1370 priority
= math
.min(priority
, CalcObjectivePriority(o
))
1377 local function ApplyBlockPriority(obj
, level
)
1378 for o
in pairs(obj
.before
) do
1380 ApplyBlockPriority(o
, level
)
1384 if obj
.priority
< level
then QuestHelper
:SetObjectivePriority(obj
, level
) end
1387 function QuestHelper
:SetObjectivePriorityPrompt(objective
, level
)
1388 self
:SetObjectivePriority(objective
, level
)
1389 if CalcObjectivePriority(objective
) ~= level
then
1390 local menu
= self
:CreateMenu()
1391 self
:CreateMenuTitle(menu
, QHText("IGNORED_PRIORITY_TITLE"))
1392 self
:CreateMenuItem(menu
, QHText("IGNORED_PRIORITY_FIX")):SetFunction(ApplyBlockPriority
, objective
, level
)
1393 self
:CreateMenuItem(menu
, QHText("IGNORED_PRIORITY_IGNORE")):SetFunction(self
.nop
)
1398 function QuestHelper
:SetObjectiveProgress(objective
, user
, have
, need
)
1399 if have
and need
then
1400 local list
= objective
.progress
1402 list
= self
:CreateTable("objective.progress")
1403 objective
.progress
= list
1406 local user_progress
= list
[user
]
1407 if not user_progress
then
1408 user_progress
= self
:CreateTable("objective.progress[user]")
1409 list
[user
] = user_progress
1413 local a
, b
= tonumber(have
), tonumber(need
)
1424 user_progress
[1], user_progress
[2], user_progress
[3] = have
, need
, pct
1426 if objective
.progress
then
1427 if objective
.progress
[user
] then
1428 self
:ReleaseTable(objective
.progress
[user
])
1429 objective
.progress
[user
] = nil
1431 if not next(objective
.progress
, nil) then
1432 self
:ReleaseTable(objective
.progress
)
1433 objective
.progress
= nil