1 QuestHelper_File
["objective.lua"] = "Development Version"
3 local function ObjectiveCouldBeFirst(self
)
4 if (self
.user_ignore
== nil and self
.auto_ignore
) or self
.user_ignore
then
8 for i
, j
in pairs(self
.after
) do
17 local function DefaultObjectiveKnown(self
)
18 if self
.user_ignore
== nil then
19 if (self
.filter_zone
and QuestHelper_Pref
.filter_zone
) or
20 (self
.filter_done
and QuestHelper_Pref
.filter_done
) or
21 (self
.filter_level
and QuestHelper_Pref
.filter_level
) or
22 (self
.filter_blocked
and QuestHelper_Pref
.filter_blocked
) or
23 (self
.filter_watched
and QuestHelper_Pref
.filter_watched
) then
26 elseif self
.user_ignore
then
30 for i
, j
in pairs(self
.after
) do
31 if i
.watched
and not i
:Known() then -- Need to know how to do everything before this objective.
39 local function ObjectiveReason(self
, short
)
40 local reason
, rc
= nil, 0
42 for r
, c
in pairs(self
.reasons
) do
43 if not reason
or c
> rc
or (c
== rc
and r
> reason
) then
49 if not reason
then reason
= "Do some extremely secret unspecified something." end
51 if not short
and self
.pos
and self
.pos
[6] then
52 reason
= reason
.. "\n" .. self
.pos
[6]
58 local function Uses(self
, obj
, text
)
59 if self
== obj
then return end -- You cannot use yourself. A purse is not food.
60 local uses
, used
= self
.uses
, obj
.used
63 uses
= QuestHelper
:CreateTable("uses")
68 used
= QuestHelper
:CreateTable("used")
79 local function DoMarkUsed(self
)
80 -- Objectives should call 'self:Uses(objective, text)' to mark objectives they use by don't directly depend on.
81 -- This information is used in tooltips.
82 -- text is passed to QHFormat with the name of the objective being used.
85 local function MarkUsed(self
)
86 if not self
.marked_used
then
90 self
.marked_used
= self
.marked_used
+ 1
94 local function MarkUnused(self
)
95 assert(self
.marked_used
)
97 if self
.marked_used
== 1 then
98 local uses
= self
.uses
101 for obj
in pairs(uses
) do
106 QuestHelper
:ReleaseTable(uses
)
111 assert(not next(self
.used
))
112 QuestHelper
:ReleaseTable(self
.used
)
116 self
.marked_used
= nil
118 self
.marked_used
= self
.marked_used
- 1
122 local function DummyObjectiveKnown(self
)
123 return (self
.o
.pos
or self
.fb
.pos
) and DefaultObjectiveKnown(self
)
126 local function ItemKnown(self
)
127 if not DefaultObjectiveKnown(self
) then return false end
129 if self
.o
.vendor
then
130 for i
, npc
in ipairs(self
.o
.vendor
) do
131 local n
= self
.qh
:GetObjective("monster", npc
)
132 local faction
= n
.o
.faction
or n
.fb
.faction
133 if (not faction
or faction
== self
.qh
.faction
) and n
:Known() then
139 if self
.fb
.vendor
then
140 for i
, npc
in ipairs(self
.fb
.vendor
) do
141 local n
= self
.qh
:GetObjective("monster", npc
)
142 local faction
= n
.o
.faction
or n
.fb
.faction
143 if (not faction
or faction
== self
.qh
.faction
) and n
:Known() then
149 if self
.o
.pos
or self
.fb
.pos
then
153 if self
.o
.drop
then for monster
in pairs(self
.o
.drop
) do
154 if self
.qh
:GetObjective("monster", monster
):Known() then
159 if self
.fb
.drop
then for monster
in pairs(self
.fb
.drop
) do
160 if self
.qh
:GetObjective("monster", monster
):Known() then
165 if self
.o
.contained
then for item
in pairs(self
.o
.contained
) do
166 if self
.qh
:GetObjective("item", item
):Known() then
171 if self
.fb
.contained
then for item
in pairs(self
.fb
.contained
) do
172 if self
.qh
:GetObjective("item", item
):Known() then
178 local item
=self
.quest
.o
.item
179 item
= item
and item
[self
.obj
]
186 for monster
in pairs(item
.drop
) do
187 if self
.qh
:GetObjective("monster", monster
):Known() then
194 item
=self
.quest
.fb
.item
195 item
= item
and item
[self
.obj
]
201 for monster
in pairs(item
.drop
) do
202 if self
.qh
:GetObjective("monster", monster
):Known() then
213 local function ObjectiveAppendPositions(self
, objective
, weight
, why
, restrict
)
216 if self
.o
.pos
then for i
, p
in ipairs(self
.o
.pos
) do
217 high
= math
.max(high
, p
[4])
220 if self
.fb
.pos
then for i
, p
in ipairs(self
.fb
.pos
) do
221 high
= math
.max(high
, p
[4])
226 if self
.o
.pos
then for i
, p
in ipairs(self
.o
.pos
) do
227 if not restrict
or not self
.qh
:Disallowed(p
[1]) then
228 objective
:AddLoc(p
[1], p
[2], p
[3], p
[4]*high
, why
)
232 if self
.fb
.pos
then for i
, p
in ipairs(self
.fb
.pos
) do
233 if not restrict
or not self
.qh
:Disallowed(p
[1]) then
234 objective
:AddLoc(p
[1], p
[2], p
[3], p
[4]*high
, why
)
240 local function ObjectivePrepareRouting(self
, anywhere
)
241 self
.setup_count
= self
.setup_count
+ 1
242 if not self
.setup
then
249 self
.d
= QuestHelper
:CreateTable("objective.d")
250 self
.p
= QuestHelper
:CreateTable("objective.p")
251 self
.nm
= QuestHelper
:CreateTable("objective.nm")
252 self
.nm2
= QuestHelper
:CreateTable("objective.nm2")
253 self
.nl
= QuestHelper
:CreateTable("objective.nl")
254 self
.distance_cache
= QuestHelper
:CreateTable("objective.distance_cache")
257 self
:AppendPositions(self
, 1, nil, true)
259 if not next(self
.p
) then
260 QuestHelper
:TextOut(QHFormat("INACCESSIBLE_OBJ", self
.obj
or "whatever it was you just requested"))
266 self
:AppendPositions(self
, 1, nil, false)
269 self
:FinishAddLoc(args
)
273 local function ItemAppendPositions(self
, objective
, weight
, why
, restrict
)
274 why2
= why
and why
.."\n" or ""
276 if self
.o
.vendor
then for i
, npc
in ipairs(self
.o
.vendor
) do
277 local n
= self
.qh
:GetObjective("monster", npc
)
278 local faction
= n
.o
.faction
or n
.fb
.faction
279 if (not faction
or faction
== self
.qh
.faction
) then
280 n
:AppendPositions(objective
, 1, why2
..QHFormat("OBJECTIVE_PURCHASE", npc
), restrict
)
284 if self
.fb
.vendor
then for i
, npc
in ipairs(self
.fb
.vendor
) do
285 local n
= self
.qh
:GetObjective("monster", npc
)
286 local faction
= n
.o
.faction
or n
.fb
.faction
287 if (not faction
or faction
== self
.qh
.faction
) then
288 n
:AppendPositions(objective
, 1, why2
..QHFormat("OBJECTIVE_PURCHASE", npc
), restrict
)
292 if next(objective
.p
, nil) then
293 -- If we have points from vendors, then always use vendors. I don't want it telling you to killing the
294 -- towns people just because you had to talk to them anyway, and it saves walking to the store.
298 if self
.o
.drop
then for monster
, count
in pairs(self
.o
.drop
) do
299 local m
= self
.qh
:GetObjective("monster", monster
)
300 m
:AppendPositions(objective
, m
.o
.looted
and count
/m
.o
.looted
or 1, why2
..QHFormat("OBJECTIVE_SLAY", monster
), restrict
)
303 if self
.fb
.drop
then for monster
, count
in pairs(self
.fb
.drop
) do
304 local m
= self
.qh
:GetObjective("monster", monster
)
305 m
:AppendPositions(objective
, m
.fb
.looted
and count
/m
.fb
.looted
or 1, why2
..QHFormat("OBJECTIVE_SLAY", monster
), restrict
)
308 if self
.o
.contained
then for item
, count
in pairs(self
.o
.contained
) do
309 local i
= self
.qh
:GetObjective("item", item
)
310 i
:AppendPositions(objective
, i
.o
.opened
and count
/i
.o
.opened
or 1, why2
..QHFormat("OBJECTIVE_LOOT", item
), restrict
)
313 if self
.fb
.contained
then for item
, count
in pairs(self
.fb
.contained
) do
314 local i
= self
.qh
:GetObjective("item", item
)
315 i
:AppendPositions(objective
, i
.fb
.opened
and count
/i
.fb
.opened
or 1, why2
..QHFormat("OBJECTIVE_LOOT", item
), restrict
)
318 if self
.o
.pos
then for i
, p
in ipairs(self
.o
.pos
) do
319 if not restrict
or not self
.qh
:Disallowed(p
[1]) then
320 objective
:AddLoc(p
[1], p
[2], p
[3], p
[4], why
)
324 if self
.fb
.pos
then for i
, p
in ipairs(self
.fb
.pos
) do
325 if not restrict
or not self
.qh
:Disallowed(p
[1]) then
326 objective
:AddLoc(p
[1], p
[2], p
[3], p
[4], why
)
331 local item_list
=self
.quest
.o
.item
333 local data
= item_list
[self
.obj
]
334 if data
and data
.drop
then
335 for monster
, count
in pairs(data
.drop
) do
336 local m
= self
.qh
:GetObjective("monster", monster
)
337 m
:AppendPositions(objective
, m
.o
.looted
and count
/m
.o
.looted
or 1, why2
..QHFormat("OBJECTIVE_SLAY", monster
), restrict
)
339 elseif data
and data
.pos
then
340 for i
, p
in ipairs(data
.pos
) do
341 if not restrict
or not self
.qh
:Disallowed(p
[1]) then
342 objective
:AddLoc(p
[1], p
[2], p
[3], p
[4], why
)
348 item_list
=self
.quest
.fb
.item
350 local data
= item_list
[self
.obj
]
351 if data
and data
.drop
then
352 for monster
, count
in pairs(data
.drop
) do
353 local m
= self
.qh
:GetObjective("monster", monster
)
354 m
:AppendPositions(objective
, m
.fb
.looted
and count
/m
.fb
.looted
or 1, why2
..QHFormat("OBJECTIVE_SLAY", monster
), restrict
)
356 elseif data
and data
.pos
then
357 for i
, p
in ipairs(data
.pos
) do
358 if not restrict
or not self
.qh
:Disallowed(p
[1]) then
359 objective
:AddLoc(p
[1], p
[2], p
[3], p
[4], why
)
367 local function ItemDoMarkUsed(self
)
368 if self
.o
.vendor
then for i
, npc
in ipairs(self
.o
.vendor
) do
369 local n
= self
.qh
:GetObjective("monster", npc
)
370 local faction
= n
.o
.faction
or n
.fb
.faction
371 if (not faction
or faction
== self
.qh
.faction
) then
372 self
:Uses(n
, "TOOLTIP_PURCHASE")
376 if self
.fb
.vendor
then for i
, npc
in ipairs(self
.fb
.vendor
) do
377 local n
= self
.qh
:GetObjective("monster", npc
)
378 local faction
= n
.o
.faction
or n
.fb
.faction
379 if (not faction
or faction
== self
.qh
.faction
) then
380 self
:Uses(n
, "TOOLTIP_PURCHASE")
384 if self
.o
.drop
then for monster
, count
in pairs(self
.o
.drop
) do
385 self
:Uses(self
.qh
:GetObjective("monster", monster
), "TOOLTIP_SLAY")
388 if self
.fb
.drop
then for monster
, count
in pairs(self
.fb
.drop
) do
389 self
:Uses(self
.qh
:GetObjective("monster", monster
), "TOOLTIP_SLAY")
392 if self
.o
.contained
then for item
, count
in pairs(self
.o
.contained
) do
393 self
:Uses(self
.qh
:GetObjective("item", item
), "TOOLTIP_LOOT")
396 if self
.fb
.contained
then for item
, count
in pairs(self
.fb
.contained
) do
397 self
:Uses(self
.qh
:GetObjective("item", item
), "TOOLTIP_LOOT")
401 local item_list
=self
.quest
.o
.item
403 local data
= item_list
[self
.obj
]
404 if data
and data
.drop
then
405 for monster
, count
in pairs(data
.drop
) do
406 self
:Uses(self
.qh
:GetObjective("monster", monster
), "TOOLTIP_SLAY")
411 item_list
=self
.quest
.fb
.item
413 local data
= item_list
[self
.obj
]
414 if data
and data
.drop
then
415 for monster
, count
in pairs(data
.drop
) do
416 self
:Uses(self
.qh
:GetObjective("monster", monster
), "TOOLTIP_SLAY")
446 local function AddLoc(self
, index
, x
, y
, w
, why
)
447 assert(not self
.setup
)
450 local pair
= QuestHelper_ZoneLookup
[index
]
451 if not pair
then return end -- that zone doesn't exist! We require more vespene gas. Not enough rage!
452 local c
, z
= pair
[1], pair
[2]
453 x
, y
= self
.qh
.Astrolabe
:TranslateWorldMapPosition(c
, z
, x
, y
, c
, 0)
455 x
= x
* self
.qh
.continent_scales_x
[c
]
456 y
= y
* self
.qh
.continent_scales_y
[c
]
457 local list
= self
.qh
.zone_nodes
[index
]
459 local points
= self
.p
[list
]
461 points
= QuestHelper
:CreateTable("objective.p[zone] (objective nodes per-zone)")
462 self
.p
[list
] = points
465 for i
, p
in pairs(points
) do
466 local u
, v
= x
-p
[3], y
-p
[4]
467 if u
*u
+v
*v
< 25 then -- Combine points within a threshold of 5 seconds travel time.
468 p
[3] = (p
[3]*p
[5]+x
*w
)/(p
[5]+w
)
469 p
[4] = (p
[4]*p
[5]+y
*w
)/(p
[5]+w
)
478 local new
= QuestHelper
:CreateTable("objective.p[zone] (possible objective node)")
479 new
[1], new
[2], new
[3], new
[4], new
[5], new
[6], new
[7] = list
, nil, x
, y
, w
, why
, w
480 table.insert(points
, new
)
484 local function FinishAddLoc(self
, args
)
487 for z
, pl
in pairs(self
.p
) do
488 for i
, p
in ipairs(pl
) do
496 if not self
.zones
then
497 -- Not using CreateTable, because it will not be released when routing is complete.
500 -- We could remove the already known zones, but I'm operating under the assumtion that locations will only be added,
501 -- not removed, so this isn't necessary.
504 -- Remove probably useless locations.
505 for z
, pl
in pairs(self
.p
) do
506 local remove_zone
= true
509 if pl
[i
][5] < mx
*0.2 then
510 QuestHelper
:ReleaseTable(pl
[i
])
518 QuestHelper
:ReleaseTable(self
.p
[z
])
521 self
.zones
[z
.i
] = true
525 local node_map
= self
.nm
526 local node_list
= self
.nl
528 for list
, pl
in pairs(self
.p
) do
529 local dist
= self
.d
[list
]
534 dist
= QuestHelper
:CreateTable("self.d[list]")
538 for i
, point
in ipairs(pl
) do
539 point
[5] = mx
/point
[5] -- Will become 1 for the most desired location, and become larger and larger for less desireable locations.
541 point
[2] = QuestHelper
:CreateTable("possible objective node to zone edge cache")
543 for i
, node
in ipairs(list
) do
544 local u
, v
= point
[3]-node
.x
, point
[4]-node
.y
545 local d
= math
.sqrt(u
*u
+v
*v
)
550 if d
*point
[5] < dist
[i
][1]*dist
[i
][2] then
551 dist
[i
][1], dist
[i
][2] = d
, point
[5]
552 node_map
[node
] = point
555 local pair
= QuestHelper
:CreateTable()
556 pair
[1], pair
[2] = d
, point
[5]
559 if not node_map
[node
] then
560 table.insert(node_list
, node
)
561 node_map
[node
] = point
563 u
, v
= node_map
[node
][3]-node
.x
, node_map
[node
][4]-node
.y
565 if dist
[i
][1]*dist
[i
][2] < math
.sqrt(u
*u
+v
*v
)*node_map
[node
][5] then
566 node_map
[node
] = point
574 -- 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.
575 --if not args or not args.failable then
576 -- if #node_list == 0 and QuestHelper:IsWrath() then QuestHelper:Error(self.cat.."/"..self.obj..": zero nodes!") end
579 assert(not self
.setup
)
581 table.insert(self
.qh
.prepared_objectives
, self
)
584 local function GetPosition(self
)
590 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)
592 -- Note: Pos is the starting point, the objective is the destination. These are different data formats - "self" can be a set of points.
593 -- 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.
594 local function ObjectiveTravelTime(self
, pos
, nocache
)
597 -- The caching is pretty obvious.
600 assert(pos
~= QuestHelper
.pos
)
602 pos
.key
= math
.random()..""
605 cached
= self
.distance_cache
[key
]
607 if not QH_TESTCACHE
then
608 return unpack(cached
)
613 local graph
= self
.qh
.world_graph
616 graph
:PrepareSearch()
618 -- 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.
619 for z
, l
in pairs(self
.d
) do
620 for i
, n
in ipairs(z
) do
622 n
.e
, n
.w
= unpack(l
[i
])
624 elseif n
.e
* n
.w
< l
[i
][1]*l
[i
][2] then
625 n
.e
, n
.w
= unpack(l
[i
])
631 for i
, n
in ipairs(pos
[1]) do
632 graph
:AddStartNode(n
, d
[i
], nl
)
635 local e
= graph
:DoSearch(nl
)
637 -- d changes datatype here. I hate this codebase. Hell, e probably changes datatype also! yaaaay. what does .nm mean? what does .d mean?
641 -- There's something going on with weighting here that I don't understand
642 local l
= self
.p
[pos
[1]]
644 local x
, y
= pos
[3], pos
[4]
647 for i
, n
in ipairs(l
) do
648 local u
, v
= x
-n
[3], y
-n
[4]
649 local d2
= math
.sqrt(u
*u
+v
*v
)
652 d
, e
, score
= d2
, n
, s
659 assert( not cached
or (cached
[1] == d
and cached
[2] == e
))
660 if not QH_TESTCACHE
or not cached
then
661 local new
= self
.qh
:CreateTable()
662 new
[1], new
[2] = d
, e
663 self
.distance_cache
[key
] = new
664 self
.qh
:CacheRegister(self
)
667 if self
.distance_cache
and self
.distance_cache
[key
] then
668 assert(self
.distance_cache
[key
][1] == d
)
675 -- Note: pos1 is the starting point, pos2 is the ending point, the objective is somewhere between them.
676 -- 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?
677 local function ObjectiveTravelTime2(self
, pos1
, pos2
, nocache
)
680 -- caching is pretty simple as usual
683 assert(pos1
~= QuestHelper
.pos
)
684 assert(pos2
~= QuestHelper
.pos
)
685 -- We don't want to cache distances involving the player's current position, as that would spam the table
687 pos1
.key
= math
.random()..""
690 pos2
.key
= math
.random()..""
692 key
= pos1
.key
..pos2
.key
693 cached
= self
.distance_cache
[key
]
695 if not QH_TESTCACHE
then
696 return unpack(cached
)
701 local graph
= self
.qh
.world_graph
704 -- This is the standard pos1-to-self code that we're used to seeing . . .
705 graph
:PrepareSearch()
707 for z
, l
in pairs(self
.d
) do
708 for i
, n
in ipairs(z
) do
710 n
.e
, n
.w
= unpack(l
[i
])
712 elseif n
.e
* n
.w
< l
[i
][1]*l
[i
][2] then
713 n
.e
, n
.w
= unpack(l
[i
])
719 for i
, n
in ipairs(pos1
[1]) do
720 graph
:AddStartNode(n
, d
[i
], nl
)
723 graph
:DoFullSearch(nl
)
725 graph
:PrepareSearch()
727 -- . . . and here's where it gets wonky
728 -- Now, we need to figure out how long it takes to get to each node.
729 for z
, point_list
in pairs(self
.p
) do
731 -- Will also consider min distance.
732 local x
, y
= pos1
[3], pos1
[4]
734 for i
, p
in ipairs(point_list
) do
735 local a
, b
= p
[3]-x
, p
[4]-y
736 local u
, v
= p
[3], p
[4]
737 local d
= math
.sqrt(a
*a
+b
*b
)
740 for i
, n
in ipairs(z
) do
742 local bleh
= math
.sqrt(a
*a
+b
*b
)+n
.g
751 for i
, p
in ipairs(point_list
) do
752 local x
, y
= p
[3], p
[4]
757 for i
, n
in ipairs(z
) do
758 local a
, b
= n
.x
-x
, n
.y
-y
759 local d2
= math
.sqrt(a
*a
+b
*b
)+n
.g
761 if not score
or s
< score
then
772 for i
, n
in ipairs(pos2
[1]) do
780 for z
, l
in pairs(self
.d
) do
781 for i
, n
in ipairs(z
) do
782 local x
, y
= n
.x
, n
.y
786 for i
, p
in ipairs(self
.p
[z
]) do
787 local a
, b
= x
-p
[3], y
-p
[4]
788 d
= p
[7]+math
.sqrt(a
*a
+b
*b
)
790 if not bs
or s
< bs
then
796 -- Using score instead of distance, because we want nodes we're not really interested in to be less likely to get chosen.
797 graph
:AddStartNode(n
, bs
, el
)
801 local e
= graph
:DoSearch(pos2
[1])
804 local d2
= e
.g
+e
.e
-e
.p
.g
+(e
.p
.g
/nm
[e
.p
][5]-nm
[e
.p
][7])
807 local total
= (d
+d2
)*e
[5]
810 local x
, y
= pos2
[3], pos2
[4]
811 for i
, p
in ipairs(self
.p
[el
]) do
812 local a
, b
= x
-p
[3], y
-p
[4]
813 local c
= math
.sqrt(a
*a
+b
*b
)
814 local t
= (p
[7]+c
)*p
[5]
816 total
, d
, d2
, e
= t
, p
[7], c
, p
821 -- 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)
822 d
= QuestHelper
:ComputeTravelTime(pos1
, e
)
823 d2
= QuestHelper
:ComputeTravelTime(e
, pos2
)
827 assert( not cached
or (cached
[1] == d
and cached
[2] == d2
and cached
[3] == e
))
828 if not QH_TESTCACHE
or not cached
then
829 local new
= self
.qh
:CreateTable("ObjectiveTravelTime2 cache")
830 new
[1], new
[2], new
[3] = d
, d2
, e
831 self
.distance_cache
[key
] = new
832 self
.qh
:CacheRegister(self
)
835 if self
.distance_cache
and self
.distance_cache
[key
] then
836 assert(self
.distance_cache
[key
][1] == d
and self
.distance_cache
[key
][2] == d2
)
840 --[[if pos1 and pos2 then -- Debug code so I can maybe actually fix the problems someday
841 QuestHelper:TextOut("Beginning dumping here")
843 local laxa = QuestHelper:ComputeTravelTime(pos1, e, true)
844 if math.abs(laxa-d) >= 0.0001 then
845 QuestHelper:TextOut(QuestHelper:StringizeTable(pos1))
846 QuestHelper:TextOut(QuestHelper:StringizeRecursive(pos1, 2))
847 QuestHelper:TextOut(QuestHelper:StringizeTable(e))
848 QuestHelper:TextOut(QuestHelper:StringizeTable(e[1]))
849 QuestHelper:TextOut(QuestHelper:StringizeTable(e[2]))
850 QuestHelper:TextOut(QuestHelper:StringizeRecursive(e[1], 2))]]
851 --QuestHelper:Assert(math.abs(laxa-d) < 0.0001, "Compare: "..laxa.." vs "..d) -- wonky commenting is thanks to the de-assert script, fix later
853 local laxb = QuestHelper:ComputeTravelTime(e, pos2, true)
854 if math.abs(laxb-d2) >= 0.0001 then
855 QuestHelper:TextOut(QuestHelper:StringizeTable(pos2))
856 QuestHelper:TextOut(QuestHelper:StringizeTable(e))
857 QuestHelper:TextOut(QuestHelper:StringizeTable(e[1]))
858 QuestHelper:TextOut(QuestHelper:StringizeTable(e[2]))
859 QuestHelper:TextOut(QuestHelper:StringizeRecursive(e[1], 2))]]
860 --QuestHelper:Assert(math.abs(laxa-d) < 0.0001, "Compare: "..laxb.." vs "..d2)
867 local function DoneRouting(self
)
868 assert(self
.setup_count
> 0)
871 if self
.setup_count
== 1 then
873 QuestHelper
:ReleaseObjectivePathingInfo(self
)
874 for i
, obj
in ipairs(self
.qh
.prepared_objectives
) do
876 table.remove(self
.qh
.prepared_objectives
, i
)
881 self
.setup_count
= self
.setup_count
- 1
885 local function IsObjectiveWatched(self
)
886 -- Check if an objective is being watched. Note that this is an external query, not a simple Selector.
889 if self
.cat
== "quest" then
890 info
= QuestHelper
.quest_log
[self
]
892 info
= QuestHelper
.quest_log
[self
.quest
]
896 local index
= info
.index
899 -- UberQuest has it's own way of tracking quests.
900 local uq_settings
= UberQuest_Config
[UnitName("player")]
902 local list
= uq_settings
.selected
904 return list
[GetQuestLogTitle(index
)]
908 return IsQuestWatched(index
)
917 local next_objective_id
= 0
919 local function ObjectiveShare(self
)
920 self
.want_share
= true
923 local function ObjectiveUnshare(self
)
924 self
.want_share
= false
927 QuestHelper
.default_objective_param
=
929 CouldBeFirst
=ObjectiveCouldBeFirst
,
932 DoMarkUsed
=DoMarkUsed
,
934 MarkUnused
=MarkUnused
,
936 DefaultKnown
=DefaultObjectiveKnown
,
937 Known
=DummyObjectiveKnown
,
938 Reason
=ObjectiveReason
,
940 AppendPositions
=ObjectiveAppendPositions
,
941 PrepareRouting
=ObjectivePrepareRouting
,
943 FinishAddLoc
=FinishAddLoc
,
944 DoneRouting
=DoneRouting
,
946 Position
=GetPosition
,
947 TravelTime
=ObjectiveTravelTime
,
948 TravelTime2
=ObjectiveTravelTime2
,
950 IsWatched
=IsObjectiveWatched
,
952 Share
=ObjectiveShare
, -- Invoke to share this objective with your peers.
953 Unshare
=ObjectiveUnshare
, -- Invoke to stop sharing this objective.
956 QuestHelper
.default_objective_item_param
=
959 AppendPositions
= ItemAppendPositions
,
960 DoMarkUsed
= ItemDoMarkUsed
963 for key
, value
in pairs(QuestHelper
.default_objective_param
) do
964 if not QuestHelper
.default_objective_item_param
[key
] then
965 QuestHelper
.default_objective_item_param
[key
] = value
969 QuestHelper
.default_objective_meta
= { __index
= QuestHelper
.default_objective_param
}
970 QuestHelper
.default_objective_item_meta
= { __index
= QuestHelper
.default_objective_item_param
}
972 function QuestHelper
:NewObjectiveObject()
973 next_objective_id
= next_objective_id
+1
977 id
=next_objective_id
,
979 want_share
=false, -- True if we want this objective shared.
980 is_sharing
=false, -- Set to true if we've told other users about this objective.
982 user_ignore
=nil, -- When nil, will use filters. Will ignore, when true, always show (if known).
984 priority
=3, -- A hint as to what priority the quest should have. Should be 1, 2, 3, 4, or 5.
985 real_priority
=3, -- This will be set to the priority routing actually decided to assign it.
996 before
={}, -- List of objectives that this objective must appear before.
997 after
={}, -- List of objectives that this objective must appear after.
999 -- Routing related junk.
1001 --[[ Will be created as needed.
1004 nm=nil, -- Maps nodes to their nearest zone/list/x/y position.
1005 nm2=nil, -- Maps nodes to their nears position, but dynamically set in TravelTime2.
1006 nl=nil, -- List of all the nodes we need to consider.
1007 location=nil, -- Will be set to the best position for the node.
1008 pos=nil, -- Zone node list, distance list, x, y, reason.
1010 }, QuestHelper
.default_objective_meta
)
1013 function QuestHelper
:GetObjective(category
, objective
)
1014 local objective_list
= self
.objective_objects
[category
]
1016 if not objective_list
then
1018 self
.objective_objects
[category
] = objective_list
1021 local objective_object
= objective_list
[objective
]
1023 if not objective_object
then
1024 if category
== "quest" then
1025 local level
, hash
, name
= string.match(objective
, "^(%d+)/(%d*)/(.*)$")
1027 level
, name
= string.match(objective
, "^(%d+)/(.*)$")
1033 if hash
== "" then hash
= nil end
1034 objective_object
= self
:GetQuest(name
, tonumber(level
), tonumber(hash
))
1035 objective_list
[objective
] = objective_object
1036 return objective_object
1039 objective_object
= self
:NewObjectiveObject()
1041 objective_object
.cat
= category
1042 objective_object
.obj
= objective
1044 if category
== "item" then
1045 setmetatable(objective_object
, QuestHelper
.default_objective_item_meta
)
1046 objective_object
.icon_id
= 2
1047 elseif category
== "monster" then
1048 objective_object
.icon_id
= 1
1049 elseif category
== "object" then
1050 objective_object
.icon_id
= 3
1051 elseif category
== "event" then
1052 objective_object
.icon_id
= 4
1053 elseif category
== "loc" then
1054 objective_object
.icon_id
= 6
1055 elseif category
== "reputation" then
1056 objective_object
.icon_id
= 5
1058 self
:TextOut("FIXME: Objective type '"..category
.."' for objective '"..objective
.."' isn't explicitly supported yet; hopefully the dummy handler will do something sensible.")
1061 objective_list
[objective
] = objective_object
1063 if category
== "loc" then
1064 -- Loc is special, we don't store it, and construct it from the string.
1065 -- Don't have any error checking here, will assume it's correct.
1067 local _
, _
, c
, z
, x
, y
= string.find(objective
,"^(%d+),(%d+),([%d%.]+),([%d%.]+)$")
1070 _
, _
, i
, x
, y
= string.find(objective
,"^(%d+),([%d%.]+),([%d%.]+)$")
1072 i
= QuestHelper_IndexLookup
[c
][z
]
1075 objective_object
.o
= {pos
={{tonumber(i
),tonumber(x
),tonumber(y
),1}}}
1076 objective_object
.fb
= {}
1078 objective_list
= QuestHelper_Objectives_Local
[category
]
1079 if not objective_list
then
1081 QuestHelper_Objectives_Local
[category
] = objective_list
1083 objective_object
.o
= objective_list
[objective
]
1084 if not objective_object
.o
then
1085 objective_object
.o
= {}
1086 objective_list
[objective
] = objective_object
.o
1088 local l
= QuestHelper_StaticData
[self
.locale
]
1090 objective_list
= l
.objective
[category
]
1091 if objective_list
then
1092 objective_object
.fb
= objective_list
[objective
]
1095 if not objective_object
.fb
then
1096 objective_object
.fb
= {}
1099 -- TODO: If we have some other source of information (like LightHeaded) add its data to objective_object.fb
1104 return objective_object
1107 function QuestHelper
:AppendObjectivePosition(objective
, i
, x
, y
, w
)
1108 local pos
= objective
.o
.pos
1110 if objective
.o
.drop
or objective
.o
.contained
then
1111 return -- If it's dropped by a monster, don't record the position we got the item at.
1113 objective
.o
.pos
= self
:AppendPosition({}, i
, x
, y
, w
)
1115 self
:AppendPosition(pos
, i
, x
, y
, w
)
1119 function QuestHelper
:AppendObjectiveDrop(objective
, monster
, count
)
1120 local drop
= objective
.o
.drop
1122 drop
[monster
] = (drop
[monster
] or 0)+(count
or 1)
1124 objective
.o
.drop
= {[monster
] = count
or 1}
1125 objective
.o
.pos
= nil -- If it's dropped by a monster, then forget the position we found it at.
1129 function QuestHelper
:AppendItemObjectiveDrop(item_object
, item_name
, monster_name
, count
)
1130 local quest
= self
:ItemIsForQuest(item_object
, item_name
)
1131 if quest
and not item_object
.o
.vendor
and not item_object
.o
.drop
and not item_object
.o
.pos
then
1132 self
:AppendQuestDrop(quest
, item_name
, monster_name
, count
)
1134 if not item_object
.o
.drop
and not item_object
.o
.pos
then
1135 self
:PurgeQuestItem(item_object
, item_name
)
1137 self
:AppendObjectiveDrop(item_object
, monster_name
, count
)
1141 function QuestHelper
:AppendItemObjectivePosition(item_object
, item_name
, i
, x
, y
)
1142 local quest
= self
:ItemIsForQuest(item_object
, item_name
)
1143 if quest
and not item_object
.o
.vendor
and not item_object
.o
.drop
and not item_object
.o
.pos
then
1144 self
:AppendQuestPosition(quest
, item_name
, i
, x
, y
)
1146 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
1147 -- Just learned that this item doesn't depend on a quest to drop, remove any quest references to it.
1148 self
:PurgeQuestItem(item_object
, item_name
)
1150 self
:AppendObjectivePosition(item_object
, i
, x
, y
)
1154 function QuestHelper
:AppendItemObjectiveContainer(objective
, container_name
, count
)
1155 local container
= objective
.o
.contained
1157 container
[container_name
] = (container
[container_name
] or 0)+(count
or 1)
1159 objective
.o
.contained
= {[container_name
] = count
or 1}
1160 objective
.o
.pos
= nil -- Forget the position.
1164 function QuestHelper
:AddObjectiveWatch(objective
, reason
)
1165 if not objective
.reasons
then
1166 objective
.reasons
= {}
1169 if not next(objective
.reasons
, nil) then
1170 objective
.watched
= true
1171 objective
:MarkUsed()
1173 objective
.filter_blocked
= false
1174 for obj
in pairs(objective
.swap_after
or objective
.after
) do
1176 objective
.filter_blocked
= true
1181 for obj
in pairs(objective
.swap_before
or objective
.before
) do
1183 obj
.filter_blocked
= true
1187 if self
.to_remove
[objective
] then
1188 self
.to_remove
[objective
] = nil
1190 self
.to_add
[objective
] = true
1194 objective
.reasons
[reason
] = (objective
.reasons
[reason
] or 0) + 1
1197 function QuestHelper
:RemoveObjectiveWatch(objective
, reason
)
1198 if objective
.reasons
[reason
] == 1 then
1199 objective
.reasons
[reason
] = nil
1200 if not next(objective
.reasons
, nil) then
1201 objective
:MarkUnused()
1202 objective
.watched
= false
1204 for obj
in pairs(objective
.swap_before
or objective
.before
) do
1206 obj
.filter_blocked
= false
1207 for obj2
in pairs(obj
.swap_after
or obj
.after
) do
1208 if obj2
.watched
then
1209 obj
.filter_blocked
= true
1216 if self
.to_add
[objective
] then
1217 self
.to_add
[objective
] = nil
1219 self
.to_remove
[objective
] = true
1223 objective
.reasons
[reason
] = objective
.reasons
[reason
] - 1
1227 function QuestHelper
:ObjectiveObjectDependsOn(objective
, needs
)
1228 assert(objective
~= needs
) -- If this was true, ObjectiveIsKnown would get in an infinite loop.
1229 -- TODO: Needs sanity checking, especially now that dependencies can be assigned by remote users.
1232 -- We store the new relationships in objective.swap_[before|after],
1233 -- creating and copying them from objective.[before|after],
1234 -- the routing coroutine will check for those, swap them, and release the originals
1235 -- when it gets to a safe place to do so.
1237 if not (objective
.swap_after
or objective
.after
)[needs
] then
1238 if objective
.peer
then
1239 for u
, l
in pairs(objective
.peer
) do
1240 -- Make sure other users know that the dependencies for this objective changed.
1241 objective
.peer
[u
] = math
.min(l
, 1)
1245 if not objective
.swap_after
then
1246 objective
.swap_after
= self
:CreateTable("swap_after")
1247 for key
,value
in pairs(objective
.after
) do objective
.swap_after
[key
] = value
end
1250 if not needs
.swap_before
then
1251 needs
.swap_before
= self
:CreateTable("swap_before")
1252 for key
,value
in pairs(needs
.before
) do needs
.swap_before
[key
] = value
end
1255 if needs
.watched
then
1256 objective
.filter_blocked
= true
1259 objective
.swap_after
[needs
] = true
1260 needs
.swap_before
[objective
] = true
1264 function QuestHelper
:AddObjectiveOptionsToMenu(obj
, menu
)
1265 local submenu
= self
:CreateMenu()
1268 local name
= QHText("PRIORITY"..i
)
1269 local item
= self
:CreateMenuItem(submenu
, name
)
1272 if obj
.priority
== i
then
1273 tex
= self
:CreateIconTexture(item
, 10)
1274 elseif obj
.real_priority
== i
then
1275 tex
= self
:CreateIconTexture(item
, 8)
1277 tex
= self
:CreateIconTexture(item
, 12)
1278 tex
:SetVertexColor(1, 1, 1, 0)
1281 item
:AddTexture(tex
, true)
1282 item
:SetFunction(self
.SetObjectivePriorityPrompt
, self
, obj
, i
)
1285 self
:CreateMenuItem(menu
, QHText("PRIORITY")):SetSubmenu(submenu
)
1287 if self
.sharing
then
1288 submenu
= self
:CreateMenu()
1289 local item
= self
:CreateMenuItem(submenu
, QHText("SHARING_ENABLE"))
1290 local tex
= self
:CreateIconTexture(item
, 10)
1291 if not obj
.want_share
then tex
:SetVertexColor(1, 1, 1, 0) end
1292 item
:AddTexture(tex
, true)
1293 item
:SetFunction(obj
.Share
, obj
)
1295 local item
= self
:CreateMenuItem(submenu
, QHText("SHARING_DISABLE"))
1296 local tex
= self
:CreateIconTexture(item
, 10)
1297 if obj
.want_share
then tex
:SetVertexColor(1, 1, 1, 0) end
1298 item
:AddTexture(tex
, true)
1299 item
:SetFunction(obj
.Unshare
, obj
)
1301 self
:CreateMenuItem(menu
, QHText("SHARING")):SetSubmenu(submenu
)
1304 self
:CreateMenuItem(menu
, QHText("IGNORE")):SetFunction(self
.IgnoreObjective
, self
, obj
)
1307 function QuestHelper
:IgnoreObjective(objective
)
1308 if self
.user_objectives
[objective
] then
1309 self
:RemoveObjectiveWatch(objective
, self
.user_objectives
[objective
])
1310 self
.user_objectives
[objective
] = nil
1312 objective
.user_ignore
= true
1315 --self:ForceRouteUpdate()
1318 function QuestHelper
:SetObjectivePriority(objective
, level
)
1319 level
= math
.min(5, math
.max(1, math
.floor((tonumber(level
) or 3)+0.5)))
1320 if level
~= objective
.priority
then
1321 objective
.priority
= level
1322 if objective
.peer
then
1323 for u
, l
in pairs(objective
.peer
) do
1324 -- Peers don't know about this new priority.
1325 objective
.peer
[u
] = math
.min(l
, 2)
1328 --self:ForceRouteUpdate()
1332 local function CalcObjectivePriority(obj
)
1333 local priority
= obj
.priority
1335 for o
in pairs(obj
.before
) do
1337 priority
= math
.min(priority
, CalcObjectivePriority(o
))
1344 local function ApplyBlockPriority(obj
, level
)
1345 for o
in pairs(obj
.before
) do
1347 ApplyBlockPriority(o
, level
)
1351 if obj
.priority
< level
then QuestHelper
:SetObjectivePriority(obj
, level
) end
1354 function QuestHelper
:SetObjectivePriorityPrompt(objective
, level
)
1355 self
:SetObjectivePriority(objective
, level
)
1356 if CalcObjectivePriority(objective
) ~= level
then
1357 local menu
= self
:CreateMenu()
1358 self
:CreateMenuTitle(menu
, QHText("IGNORED_PRIORITY_TITLE"))
1359 self
:CreateMenuItem(menu
, QHText("IGNORED_PRIORITY_FIX")):SetFunction(ApplyBlockPriority
, objective
, level
)
1360 self
:CreateMenuItem(menu
, QHText("IGNORED_PRIORITY_IGNORE")):SetFunction(self
.nop
)
1365 function QuestHelper
:SetObjectiveProgress(objective
, user
, have
, need
)
1366 if have
and need
then
1367 local list
= objective
.progress
1369 list
= self
:CreateTable("objective.progress")
1370 objective
.progress
= list
1373 local user_progress
= list
[user
]
1374 if not user_progress
then
1375 user_progress
= self
:CreateTable("objective.progress[user]")
1376 list
[user
] = user_progress
1380 local a
, b
= tonumber(have
), tonumber(need
)
1391 user_progress
[1], user_progress
[2], user_progress
[3] = have
, need
, pct
1393 if objective
.progress
then
1394 if objective
.progress
[user
] then
1395 self
:ReleaseTable(objective
.progress
[user
])
1396 objective
.progress
[user
] = nil
1398 if not next(objective
.progress
, nil) then
1399 self
:ReleaseTable(objective
.progress
)
1400 objective
.progress
= nil