1 QuestHelper_File
["objective.lua"] = "Development Version"
2 QuestHelper_Loadtime
["objective.lua"] = GetTime()
6 name
= "user_manual_ignored",
8 friendly_reason
= QHText("FILTERED_USER"),
9 AddException
= function(self
, node
)
10 QH_Route_UnignoreNode(node
, self
) -- there isn't really state with this one
14 function QuestHelper
:AddObjectiveOptionsToMenu(obj
, menu
)
15 local submenu
= self
:CreateMenu()
17 local pri
= (QH_Route_GetClusterPriority(obj
.cluster
) or 0) + 3
19 local name
= QHText("PRIORITY"..i
)
20 local item
= self
:CreateMenuItem(submenu
, name
)
24 tex
= self
:CreateIconTexture(item
, 10)
26 tex
= self
:CreateIconTexture(item
, 12)
27 tex
:SetVertexColor(1, 1, 1, 0)
30 item
:AddTexture(tex
, true)
31 item
:SetFunction(QH_Route_SetClusterPriority
, obj
.cluster
, i
- 3)
34 self
:CreateMenuItem(menu
, QHText("PRIORITY")):SetSubmenu(submenu
)
36 --[[if self.sharing then
37 submenu = self:CreateMenu()
38 local item = self:CreateMenuItem(submenu, QHText("SHARING_ENABLE"))
39 local tex = self:CreateIconTexture(item, 10)
40 if not obj.want_share then tex:SetVertexColor(1, 1, 1, 0) end
41 item:AddTexture(tex, true)
42 item:SetFunction(obj.Share, obj)
44 local item = self:CreateMenuItem(submenu, QHText("SHARING_DISABLE"))
45 local tex = self:CreateIconTexture(item, 10)
46 if obj.want_share then tex:SetVertexColor(1, 1, 1, 0) end
47 item:AddTexture(tex, true)
48 item:SetFunction(obj.Unshare, obj)
50 self:CreateMenuItem(menu, QHText("SHARING")):SetSubmenu(submenu)
53 --self:CreateMenuItem(menu, "(No options available)")
55 if not obj
.map_suppress_ignore
then
56 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.
58 if obj
.map_custom_menu
then
59 obj
.map_custom_menu(menu
)
62 if obj
.cluster
and #obj
.cluster
> 1 and QH_Route_Ignored_Cluster_Active(obj
.cluster
) > 1 then
63 self
:CreateMenuItem(menu
, QHText("IGNORE_LOCATION")):SetFunction(QH_Route_IgnoreNode
, obj
, UserIgnored
)
69 local function ObjectiveCouldBeFirst(self
)
70 if (self
.user_ignore
== nil and self
.auto_ignore
) or self
.user_ignore
then
74 for i
, j
in pairs(self
.after
) do
83 local function DefaultObjectiveKnown(self
)
84 if self
.user_ignore
== nil then
85 if (self
.filter_zone
and QuestHelper_Pref
.filter_zone
) or
86 (self
.filter_done
and QuestHelper_Pref
.filter_done
) or
87 (self
.filter_level
and QuestHelper_Pref
.filter_level
) or
88 (self
.filter_blocked
and QuestHelper_Pref
.filter_blocked
) or
89 (self
.filter_watched
and QuestHelper_Pref
.filter_watched
) then
92 elseif self
.user_ignore
then
96 for i
, j
in pairs(self
.after
) do
97 if i
.watched
and not i
:Known() then -- Need to know how to do everything before this objective.
105 local function ObjectiveReason(self
, short
)
106 local reason
, rc
= nil, 0
108 for r
, c
in pairs(self
.reasons
) do
109 if not reason
or c
> rc
or (c
== rc
and r
> reason
) then
115 if not reason
then reason
= "Do some extremely secret unspecified something." end
117 if not short
and self
.pos
and self
.pos
[6] then
118 reason
= reason
.. "\n" .. self
.pos
[6]
124 local function Uses(self
, obj
, text
)
125 if self
== obj
then return end -- You cannot use yourself. A purse is not food.
126 local uses
, used
= self
.uses
, obj
.used
129 uses
= QuestHelper
:CreateTable("uses")
134 used
= QuestHelper
:CreateTable("used")
138 if not uses
[obj
] then
145 local function DoMarkUsed(self
)
146 -- Objectives should call 'self:Uses(objective, text)' to mark objectives they use by don't directly depend on.
147 -- This information is used in tooltips.
148 -- text is passed to QHFormat with the name of the objective being used.
151 local function MarkUsed(self
)
152 if not self
.marked_used
then
156 self
.marked_used
= self
.marked_used
+ 1
160 local function MarkUnused(self
)
161 assert(self
.marked_used
)
163 if self
.marked_used
== 1 then
164 local uses
= self
.uses
167 for obj
in pairs(uses
) do
172 QuestHelper
:ReleaseTable(uses
)
177 assert(not next(self
.used
))
178 QuestHelper
:ReleaseTable(self
.used
)
182 self
.marked_used
= nil
184 self
.marked_used
= self
.marked_used
- 1
188 local function DummyObjectiveKnown(self
)
189 return (self
.o
.pos
or self
.fb
.pos
) and DefaultObjectiveKnown(self
)
192 local function ItemKnown(self
)
193 if not DefaultObjectiveKnown(self
) then return false end
195 if self
.o
.vendor
then
196 for i
, npc
in ipairs(self
.o
.vendor
) do
197 local n
= self
.qh
:GetObjective("monster", npc
)
198 local faction
= n
.o
.faction
or n
.fb
.faction
199 if (not faction
or faction
== self
.qh
.faction
) and n
:Known() then
205 if self
.fb
.vendor
then
206 for i
, npc
in ipairs(self
.fb
.vendor
) do
207 local n
= self
.qh
:GetObjective("monster", npc
)
208 local faction
= n
.o
.faction
or n
.fb
.faction
209 if (not faction
or faction
== self
.qh
.faction
) and n
:Known() then
215 if self
.o
.pos
or self
.fb
.pos
then
219 if self
.o
.drop
then for monster
in pairs(self
.o
.drop
) do
220 if self
.qh
:GetObjective("monster", monster
):Known() then
225 if self
.fb
.drop
then for monster
in pairs(self
.fb
.drop
) do
226 if self
.qh
:GetObjective("monster", monster
):Known() then
231 if self
.o
.contained
then for item
in pairs(self
.o
.contained
) do
232 if self
.qh
:GetObjective("item", item
):Known() then
237 if self
.fb
.contained
then for item
in pairs(self
.fb
.contained
) do
238 if self
.qh
:GetObjective("item", item
):Known() then
244 local item
=self
.quest
.o
.item
245 item
= item
and item
[self
.obj
]
252 for monster
in pairs(item
.drop
) do
253 if self
.qh
:GetObjective("monster", monster
):Known() then
260 item
=self
.quest
.fb
.item
261 item
= item
and item
[self
.obj
]
267 for monster
in pairs(item
.drop
) do
268 if self
.qh
:GetObjective("monster", monster
):Known() then
279 local function ObjectiveAppendPositions(self
, objective
, weight
, why
, restrict
)
282 if self
.o
.pos
then for i
, p
in ipairs(self
.o
.pos
) do
283 high
= math
.max(high
, p
[4])
286 if self
.fb
.pos
then for i
, p
in ipairs(self
.fb
.pos
) do
287 high
= math
.max(high
, p
[4])
292 if self
.o
.pos
then for i
, p
in ipairs(self
.o
.pos
) do
293 if not restrict
or not self
.qh
:Disallowed(p
[1]) then
294 objective
:AddLoc(p
[1], p
[2], p
[3], p
[4]*high
, why
)
298 if self
.fb
.pos
then for i
, p
in ipairs(self
.fb
.pos
) do
299 if not restrict
or not self
.qh
:Disallowed(p
[1]) then
300 objective
:AddLoc(p
[1], p
[2], p
[3], p
[4]*high
, why
)
306 local function ObjectivePrepareRouting(self
, anywhere
)
307 self
.setup_count
= self
.setup_count
+ 1
308 if not self
.setup
then
315 self
.d
= QuestHelper
:CreateTable("objective.d")
316 self
.p
= QuestHelper
:CreateTable("objective.p")
317 self
.nm
= QuestHelper
:CreateTable("objective.nm")
318 self
.nm2
= QuestHelper
:CreateTable("objective.nm2")
319 self
.nl
= QuestHelper
:CreateTable("objective.nl")
320 self
.distance_cache
= QuestHelper
:CreateTable("objective.distance_cache")
323 self
:AppendPositions(self
, 1, nil, true)
325 if not next(self
.p
) then
326 QuestHelper
:TextOut(QHFormat("INACCESSIBLE_OBJ", self
.obj
or "whatever it was you just requested"))
332 self
:AppendPositions(self
, 1, nil, false)
335 self
:FinishAddLoc(args
)
339 local function ItemAppendPositions(self
, objective
, weight
, why
, restrict
)
340 why2
= why
and why
.."\n" or ""
342 if self
.o
.vendor
then for i
, npc
in ipairs(self
.o
.vendor
) do
343 local n
= self
.qh
:GetObjective("monster", npc
)
344 local faction
= n
.o
.faction
or n
.fb
.faction
345 if (not faction
or faction
== self
.qh
.faction
) then
346 n
:AppendPositions(objective
, 1, why2
..QHFormat("OBJECTIVE_PURCHASE", npc
), restrict
)
350 if self
.fb
.vendor
then for i
, npc
in ipairs(self
.fb
.vendor
) do
351 local n
= self
.qh
:GetObjective("monster", npc
)
352 local faction
= n
.o
.faction
or n
.fb
.faction
353 if (not faction
or faction
== self
.qh
.faction
) then
354 n
:AppendPositions(objective
, 1, why2
..QHFormat("OBJECTIVE_PURCHASE", npc
), restrict
)
358 if next(objective
.p
, nil) then
359 -- If we have points from vendors, then always use vendors. I don't want it telling you to killing the
360 -- towns people just because you had to talk to them anyway, and it saves walking to the store.
364 if self
.o
.drop
then for monster
, count
in pairs(self
.o
.drop
) do
365 local m
= self
.qh
:GetObjective("monster", monster
)
366 m
:AppendPositions(objective
, m
.o
.looted
and count
/m
.o
.looted
or 1, why2
..QHFormat("OBJECTIVE_SLAY", monster
), restrict
)
369 if self
.fb
.drop
then for monster
, count
in pairs(self
.fb
.drop
) do
370 local m
= self
.qh
:GetObjective("monster", monster
)
371 m
:AppendPositions(objective
, m
.fb
.looted
and count
/m
.fb
.looted
or 1, why2
..QHFormat("OBJECTIVE_SLAY", monster
), restrict
)
374 if self
.o
.contained
then for item
, count
in pairs(self
.o
.contained
) do
375 local i
= self
.qh
:GetObjective("item", item
)
376 i
:AppendPositions(objective
, i
.o
.opened
and count
/i
.o
.opened
or 1, why2
..QHFormat("OBJECTIVE_LOOT", item
), restrict
)
379 if self
.fb
.contained
then for item
, count
in pairs(self
.fb
.contained
) do
380 local i
= self
.qh
:GetObjective("item", item
)
381 i
:AppendPositions(objective
, i
.fb
.opened
and count
/i
.fb
.opened
or 1, why2
..QHFormat("OBJECTIVE_LOOT", item
), restrict
)
384 if self
.o
.pos
then for i
, p
in ipairs(self
.o
.pos
) do
385 if not restrict
or not self
.qh
:Disallowed(p
[1]) then
386 objective
:AddLoc(p
[1], p
[2], p
[3], p
[4], why
)
390 if self
.fb
.pos
then for i
, p
in ipairs(self
.fb
.pos
) do
391 if not restrict
or not self
.qh
:Disallowed(p
[1]) then
392 objective
:AddLoc(p
[1], p
[2], p
[3], p
[4], why
)
397 local item_list
=self
.quest
.o
.item
399 local data
= item_list
[self
.obj
]
400 if data
and data
.drop
then
401 for monster
, count
in pairs(data
.drop
) do
402 local m
= self
.qh
:GetObjective("monster", monster
)
403 m
:AppendPositions(objective
, m
.o
.looted
and count
/m
.o
.looted
or 1, why2
..QHFormat("OBJECTIVE_SLAY", monster
), restrict
)
405 elseif data
and data
.pos
then
406 for i
, p
in ipairs(data
.pos
) do
407 if not restrict
or not self
.qh
:Disallowed(p
[1]) then
408 objective
:AddLoc(p
[1], p
[2], p
[3], p
[4], why
)
414 item_list
=self
.quest
.fb
.item
416 local data
= item_list
[self
.obj
]
417 if data
and data
.drop
then
418 for monster
, count
in pairs(data
.drop
) do
419 local m
= self
.qh
:GetObjective("monster", monster
)
420 m
:AppendPositions(objective
, m
.fb
.looted
and count
/m
.fb
.looted
or 1, why2
..QHFormat("OBJECTIVE_SLAY", monster
), restrict
)
422 elseif data
and data
.pos
then
423 for i
, p
in ipairs(data
.pos
) do
424 if not restrict
or not self
.qh
:Disallowed(p
[1]) then
425 objective
:AddLoc(p
[1], p
[2], p
[3], p
[4], why
)
433 local function ItemDoMarkUsed(self
)
434 if self
.o
.vendor
then for i
, npc
in ipairs(self
.o
.vendor
) do
435 local n
= self
.qh
:GetObjective("monster", npc
)
436 local faction
= n
.o
.faction
or n
.fb
.faction
437 if (not faction
or faction
== self
.qh
.faction
) then
438 self
:Uses(n
, "TOOLTIP_PURCHASE")
442 if self
.fb
.vendor
then for i
, npc
in ipairs(self
.fb
.vendor
) do
443 local n
= self
.qh
:GetObjective("monster", npc
)
444 local faction
= n
.o
.faction
or n
.fb
.faction
445 if (not faction
or faction
== self
.qh
.faction
) then
446 self
:Uses(n
, "TOOLTIP_PURCHASE")
450 if self
.o
.drop
then for monster
, count
in pairs(self
.o
.drop
) do
451 self
:Uses(self
.qh
:GetObjective("monster", monster
), "TOOLTIP_SLAY")
454 if self
.fb
.drop
then for monster
, count
in pairs(self
.fb
.drop
) do
455 self
:Uses(self
.qh
:GetObjective("monster", monster
), "TOOLTIP_SLAY")
458 if self
.o
.contained
then for item
, count
in pairs(self
.o
.contained
) do
459 self
:Uses(self
.qh
:GetObjective("item", item
), "TOOLTIP_LOOT")
462 if self
.fb
.contained
then for item
, count
in pairs(self
.fb
.contained
) do
463 self
:Uses(self
.qh
:GetObjective("item", item
), "TOOLTIP_LOOT")
467 local item_list
=self
.quest
.o
.item
469 local data
= item_list
[self
.obj
]
470 if data
and data
.drop
then
471 for monster
, count
in pairs(data
.drop
) do
472 self
:Uses(self
.qh
:GetObjective("monster", monster
), "TOOLTIP_SLAY")
477 item_list
=self
.quest
.fb
.item
479 local data
= item_list
[self
.obj
]
480 if data
and data
.drop
then
481 for monster
, count
in pairs(data
.drop
) do
482 self
:Uses(self
.qh
:GetObjective("monster", monster
), "TOOLTIP_SLAY")
512 local function AddLoc(self
, index
, x
, y
, w
, why
)
513 assert(not self
.setup
)
516 local pair
= QuestHelper_ZoneLookup
[index
]
517 if not pair
then return end -- that zone doesn't exist! We require more vespene gas. Not enough rage!
518 local c
, z
= pair
[1], pair
[2]
519 x
, y
= self
.qh
.Astrolabe
:TranslateWorldMapPosition(c
, z
, x
, y
, c
, 0)
521 x
= x
* self
.qh
.continent_scales_x
[c
]
522 y
= y
* self
.qh
.continent_scales_y
[c
]
523 local list
= self
.qh
.zone_nodes
[index
]
525 local points
= self
.p
[list
]
527 points
= QuestHelper
:CreateTable("objective.p[zone] (objective nodes per-zone)")
528 self
.p
[list
] = points
531 for i
, p
in pairs(points
) do
532 local u
, v
= x
-p
[3], y
-p
[4]
533 if u
*u
+v
*v
< 25 then -- Combine points within a threshold of 5 seconds travel time.
534 p
[3] = (p
[3]*p
[5]+x
*w
)/(p
[5]+w
)
535 p
[4] = (p
[4]*p
[5]+y
*w
)/(p
[5]+w
)
544 local new
= QuestHelper
:CreateTable("objective.p[zone] (possible objective node)")
545 new
[1], new
[2], new
[3], new
[4], new
[5], new
[6], new
[7] = list
, nil, x
, y
, w
, why
, w
546 table.insert(points
, new
)
550 local function FinishAddLoc(self
, args
)
553 for z
, pl
in pairs(self
.p
) do
554 for i
, p
in ipairs(pl
) do
562 if not self
.zones
then
563 -- Not using CreateTable, because it will not be released when routing is complete.
566 -- We could remove the already known zones, but I'm operating under the assumtion that locations will only be added,
567 -- not removed, so this isn't necessary.
570 -- Remove probably useless locations.
571 for z
, pl
in pairs(self
.p
) do
572 local remove_zone
= true
575 if pl
[i
][5] < mx
*0.2 then
576 QuestHelper
:ReleaseTable(pl
[i
])
584 QuestHelper
:ReleaseTable(self
.p
[z
])
587 self
.zones
[z
.i
] = true
591 local node_map
= self
.nm
592 local node_list
= self
.nl
594 for list
, pl
in pairs(self
.p
) do
595 local dist
= self
.d
[list
]
600 dist
= QuestHelper
:CreateTable("self.d[list]")
604 for i
, point
in ipairs(pl
) do
605 point
[5] = mx
/point
[5] -- Will become 1 for the most desired location, and become larger and larger for less desireable locations.
607 point
[2] = QuestHelper
:CreateTable("possible objective node to zone edge cache")
609 for i
, node
in ipairs(list
) do
610 QuestHelper
: Assert(type(point
[3]) == "number", string.format("p3 %s", tostring(point
[3])))
611 QuestHelper
: Assert(type(point
[4]) == "number", string.format("p4 %s", tostring(point
[4])))
612 QuestHelper
: Assert(type(node
.x
) == "number", string.format("nx %s", tostring(node
.x
)))
613 QuestHelper
: Assert(type(node
.y
) == "number", string.format("ny %s", tostring(node
.y
)))
614 local u
, v
= point
[3]-node
.x
, point
[4]-node
.y
615 local d
= math
.sqrt(u
*u
+v
*v
)
620 if d
*point
[5] < dist
[i
][1]*dist
[i
][2] then
621 dist
[i
][1], dist
[i
][2] = d
, point
[5]
622 node_map
[node
] = point
625 local pair
= QuestHelper
:CreateTable()
626 pair
[1], pair
[2] = d
, point
[5]
629 if not node_map
[node
] then
630 table.insert(node_list
, node
)
631 node_map
[node
] = point
633 u
, v
= node_map
[node
][3]-node
.x
, node_map
[node
][4]-node
.y
635 if dist
[i
][1]*dist
[i
][2] < math
.sqrt(u
*u
+v
*v
)*node_map
[node
][5] then
636 node_map
[node
] = point
644 -- 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.
645 --if not args or not args.failable then
646 -- if #node_list == 0 and QuestHelper:IsWrath() then QuestHelper:Error(self.cat.."/"..self.obj..": zero nodes!") end
649 assert(not self
.setup
)
651 table.insert(self
.qh
.prepared_objectives
, self
)
654 local function GetPosition(self
)
660 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)
662 -- Note: Pos is the starting point, the objective is the destination. These are different data formats - "self" can be a set of points.
663 -- 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.
664 local function ObjectiveTravelTime(self
, pos
, nocache
)
667 -- The caching is pretty obvious.
670 assert(pos
~= QuestHelper
.pos
)
672 pos
.key
= math
.random()..""
675 cached
= self
.distance_cache
[key
]
677 if not QH_TESTCACHE
then
678 return unpack(cached
)
683 local graph
= self
.qh
.world_graph
686 graph
:PrepareSearch()
688 -- 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.
689 for z
, l
in pairs(self
.d
) do
690 for i
, n
in ipairs(z
) do
692 n
.e
, n
.w
= unpack(l
[i
])
694 elseif n
.e
* n
.w
< l
[i
][1]*l
[i
][2] then
695 n
.e
, n
.w
= unpack(l
[i
])
701 for i
, n
in ipairs(pos
[1]) do
702 graph
:AddStartNode(n
, d
[i
], nl
)
705 local e
= graph
:DoSearch(nl
)
707 -- d changes datatype here. I hate this codebase. Hell, e probably changes datatype also! yaaaay. what does .nm mean? what does .d mean?
711 -- There's something going on with weighting here that I don't understand
712 local l
= self
.p
[pos
[1]]
714 local x
, y
= pos
[3], pos
[4]
717 for i
, n
in ipairs(l
) do
718 local u
, v
= x
-n
[3], y
-n
[4]
719 local d2
= math
.sqrt(u
*u
+v
*v
)
722 d
, e
, score
= d2
, n
, s
729 assert( not cached
or (cached
[1] == d
and cached
[2] == e
))
730 if not QH_TESTCACHE
or not cached
then
731 local new
= self
.qh
:CreateTable()
732 new
[1], new
[2] = d
, e
733 self
.distance_cache
[key
] = new
734 self
.qh
:CacheRegister(self
)
737 if self
.distance_cache
and self
.distance_cache
[key
] then
738 assert(self
.distance_cache
[key
][1] == d
)
745 -- Note: pos1 is the starting point, pos2 is the ending point, the objective is somewhere between them.
746 -- 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?
747 local function ObjectiveTravelTime2(self
, pos1
, pos2
, nocache
)
750 -- caching is pretty simple as usual
753 assert(pos1
~= QuestHelper
.pos
)
754 assert(pos2
~= QuestHelper
.pos
)
755 -- We don't want to cache distances involving the player's current position, as that would spam the table
757 pos1
.key
= math
.random()..""
760 pos2
.key
= math
.random()..""
762 key
= pos1
.key
..pos2
.key
763 cached
= self
.distance_cache
[key
]
765 if not QH_TESTCACHE
then
766 return unpack(cached
)
771 local graph
= self
.qh
.world_graph
774 -- This is the standard pos1-to-self code that we're used to seeing . . .
775 graph
:PrepareSearch()
777 for z
, l
in pairs(self
.d
) do
778 for i
, n
in ipairs(z
) do
780 n
.e
, n
.w
= unpack(l
[i
])
782 elseif n
.e
* n
.w
< l
[i
][1]*l
[i
][2] then
783 n
.e
, n
.w
= unpack(l
[i
])
789 for i
, n
in ipairs(pos1
[1]) do
790 graph
:AddStartNode(n
, d
[i
], nl
)
793 graph
:DoFullSearch(nl
)
795 graph
:PrepareSearch()
797 -- . . . and here's where it gets wonky
798 -- Now, we need to figure out how long it takes to get to each node.
799 for z
, point_list
in pairs(self
.p
) do
801 -- Will also consider min distance.
802 local x
, y
= pos1
[3], pos1
[4]
804 for i
, p
in ipairs(point_list
) do
805 local a
, b
= p
[3]-x
, p
[4]-y
806 local u
, v
= p
[3], p
[4]
807 local d
= math
.sqrt(a
*a
+b
*b
)
810 for i
, n
in ipairs(z
) do
812 local bleh
= math
.sqrt(a
*a
+b
*b
)+n
.g
821 for i
, p
in ipairs(point_list
) do
822 local x
, y
= p
[3], p
[4]
827 for i
, n
in ipairs(z
) do
828 local a
, b
= n
.x
-x
, n
.y
-y
829 local d2
= math
.sqrt(a
*a
+b
*b
)+n
.g
831 if not score
or s
< score
then
842 for i
, n
in ipairs(pos2
[1]) do
850 for z
, l
in pairs(self
.d
) do
851 for i
, n
in ipairs(z
) do
852 local x
, y
= n
.x
, n
.y
856 for i
, p
in ipairs(self
.p
[z
]) do
857 local a
, b
= x
-p
[3], y
-p
[4]
858 d
= p
[7]+math
.sqrt(a
*a
+b
*b
)
860 if not bs
or s
< bs
then
866 -- Using score instead of distance, because we want nodes we're not really interested in to be less likely to get chosen.
867 graph
:AddStartNode(n
, bs
, el
)
871 local e
= graph
:DoSearch(pos2
[1])
874 local d2
= e
.g
+e
.e
-e
.p
.g
+(e
.p
.g
/nm
[e
.p
][5]-nm
[e
.p
][7])
877 local total
= (d
+d2
)*e
[5]
880 local x
, y
= pos2
[3], pos2
[4]
881 for i
, p
in ipairs(self
.p
[el
]) do
882 local a
, b
= x
-p
[3], y
-p
[4]
883 local c
= math
.sqrt(a
*a
+b
*b
)
884 local t
= (p
[7]+c
)*p
[5]
886 total
, d
, d2
, e
= t
, p
[7], c
, p
891 -- 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)
892 d
= QuestHelper
:ComputeTravelTime(pos1
, e
)
893 d2
= QuestHelper
:ComputeTravelTime(e
, pos2
)
897 assert( not cached
or (cached
[1] == d
and cached
[2] == d2
and cached
[3] == e
))
898 if not QH_TESTCACHE
or not cached
then
899 local new
= self
.qh
:CreateTable("ObjectiveTravelTime2 cache")
900 new
[1], new
[2], new
[3] = d
, d2
, e
901 self
.distance_cache
[key
] = new
902 self
.qh
:CacheRegister(self
)
905 if self
.distance_cache
and self
.distance_cache
[key
] then
906 assert(self
.distance_cache
[key
][1] == d
and self
.distance_cache
[key
][2] == d2
)
910 --[[if pos1 and pos2 then -- Debug code so I can maybe actually fix the problems someday
911 QuestHelper:TextOut("Beginning dumping here")
913 local laxa = QuestHelper:ComputeTravelTime(pos1, e, true)
914 if math.abs(laxa-d) >= 0.0001 then
915 QuestHelper:TextOut(QuestHelper:StringizeTable(pos1))
916 QuestHelper:TextOut(QuestHelper:StringizeRecursive(pos1, 2))
917 QuestHelper:TextOut(QuestHelper:StringizeTable(e))
918 QuestHelper:TextOut(QuestHelper:StringizeTable(e[1]))
919 QuestHelper:TextOut(QuestHelper:StringizeTable(e[2]))
920 QuestHelper:TextOut(QuestHelper:StringizeRecursive(e[1], 2))]]
921 --QuestHelper:Assert(math.abs(laxa-d) < 0.0001, "Compare: "..laxa.." vs "..d) -- wonky commenting is thanks to the de-assert script, fix later
923 local laxb = QuestHelper:ComputeTravelTime(e, pos2, true)
924 if math.abs(laxb-d2) >= 0.0001 then
925 QuestHelper:TextOut(QuestHelper:StringizeTable(pos2))
926 QuestHelper:TextOut(QuestHelper:StringizeTable(e))
927 QuestHelper:TextOut(QuestHelper:StringizeTable(e[1]))
928 QuestHelper:TextOut(QuestHelper:StringizeTable(e[2]))
929 QuestHelper:TextOut(QuestHelper:StringizeRecursive(e[1], 2))]]
930 --QuestHelper:Assert(math.abs(laxa-d) < 0.0001, "Compare: "..laxb.." vs "..d2)
937 local function DoneRouting(self
)
938 assert(self
.setup_count
> 0)
941 if self
.setup_count
== 1 then
943 QuestHelper
:ReleaseObjectivePathingInfo(self
)
944 for i
, obj
in ipairs(self
.qh
.prepared_objectives
) do
946 table.remove(self
.qh
.prepared_objectives
, i
)
951 self
.setup_count
= self
.setup_count
- 1
955 local function IsObjectiveWatched(self
)
956 -- Check if an objective is being watched. Note that this is an external query, not a simple Selector.
959 if self
.cat
== "quest" then
960 info
= QuestHelper
.quest_log
[self
]
962 info
= QuestHelper
.quest_log
[self
.quest
]
966 local index
= info
.index
969 -- UberQuest has it's own way of tracking quests.
970 local uq_settings
= UberQuest_Config
[UnitName("player")]
972 local list
= uq_settings
.selected
974 return list
[GetQuestLogTitle(index
)]
978 return IsQuestWatched(index
)
987 local next_objective_id
= 0
989 local function ObjectiveShare(self
)
990 self
.want_share
= true
993 local function ObjectiveUnshare(self
)
994 self
.want_share
= false
997 QuestHelper
.default_objective_param
=
999 CouldBeFirst
=ObjectiveCouldBeFirst
,
1002 DoMarkUsed
=DoMarkUsed
,
1004 MarkUnused
=MarkUnused
,
1006 DefaultKnown
=DefaultObjectiveKnown
,
1007 Known
=DummyObjectiveKnown
,
1008 Reason
=ObjectiveReason
,
1010 AppendPositions
=ObjectiveAppendPositions
,
1011 PrepareRouting
=ObjectivePrepareRouting
,
1013 FinishAddLoc
=FinishAddLoc
,
1014 DoneRouting
=DoneRouting
,
1016 Position
=GetPosition
,
1017 TravelTime
=ObjectiveTravelTime
,
1018 TravelTime2
=ObjectiveTravelTime2
,
1020 IsWatched
=IsObjectiveWatched
,
1022 Share
=ObjectiveShare
, -- Invoke to share this objective with your peers.
1023 Unshare
=ObjectiveUnshare
, -- Invoke to stop sharing this objective.
1026 QuestHelper
.default_objective_item_param
=
1029 AppendPositions
= ItemAppendPositions
,
1030 DoMarkUsed
= ItemDoMarkUsed
1033 for key
, value
in pairs(QuestHelper
.default_objective_param
) do
1034 if not QuestHelper
.default_objective_item_param
[key
] then
1035 QuestHelper
.default_objective_item_param
[key
] = value
1039 QuestHelper
.default_objective_meta
= { __index
= QuestHelper
.default_objective_param
}
1040 QuestHelper
.default_objective_item_meta
= { __index
= QuestHelper
.default_objective_item_param
}
1042 function QuestHelper
:NewObjectiveObject()
1043 next_objective_id
= next_objective_id
+1
1047 id
=next_objective_id
,
1049 want_share
=false, -- True if we want this objective shared.
1050 is_sharing
=false, -- Set to true if we've told other users about this objective.
1052 user_ignore
=nil, -- When nil, will use filters. Will ignore, when true, always show (if known).
1054 priority
=3, -- A hint as to what priority the quest should have. Should be 1, 2, 3, 4, or 5.
1055 real_priority
=3, -- This will be set to the priority routing actually decided to assign it.
1066 before
={}, -- List of objectives that this objective must appear before.
1067 after
={}, -- List of objectives that this objective must appear after.
1069 -- Routing related junk.
1071 --[[ Will be created as needed.
1074 nm=nil, -- Maps nodes to their nearest zone/list/x/y position.
1075 nm2=nil, -- Maps nodes to their nears position, but dynamically set in TravelTime2.
1076 nl=nil, -- List of all the nodes we need to consider.
1077 location=nil, -- Will be set to the best position for the node.
1078 pos=nil, -- Zone node list, distance list, x, y, reason.
1080 }, QuestHelper
.default_objective_meta
)
1083 local explicit_support_warning_given
= false
1085 function QuestHelper
:GetObjective(category
, objective
)
1086 local objective_list
= self
.objective_objects
[category
]
1088 if not objective_list
then
1090 self
.objective_objects
[category
] = objective_list
1093 local objective_object
= objective_list
[objective
]
1095 if not objective_object
then
1096 if category
== "quest" then
1097 local level
, hash
, name
= string.match(objective
, "^(%d+)/(%d*)/(.*)$")
1099 level
, name
= string.match(objective
, "^(%d+)/(.*)$")
1105 if hash
== "" then hash
= nil end
1106 objective_object
= self
:GetQuest(name
, tonumber(level
), tonumber(hash
))
1107 objective_list
[objective
] = objective_object
1108 return objective_object
1111 objective_object
= self
:NewObjectiveObject()
1113 objective_object
.cat
= category
1114 objective_object
.obj
= objective
1116 if category
== "item" then
1117 setmetatable(objective_object
, QuestHelper
.default_objective_item_meta
)
1118 objective_object
.icon_id
= 2
1119 elseif category
== "monster" then
1120 objective_object
.icon_id
= 1
1121 elseif category
== "object" then
1122 objective_object
.icon_id
= 3
1123 elseif category
== "event" then
1124 objective_object
.icon_id
= 4
1125 elseif category
== "loc" then
1126 objective_object
.icon_id
= 6
1127 elseif category
== "reputation" then
1128 objective_object
.icon_id
= 5
1129 elseif category
== "player" then
1130 objective_object
.icon_id
= 1 -- not ideal, will improve later
1132 if not explicit_support_warning_given
then
1133 self
:TextOut("FIXME: Objective type '"..category
.."' for objective '"..objective
.."' isn't explicitly supported yet; hopefully the dummy handler will do something sensible.")
1134 explicit_support_warning_given
= true
1138 objective_list
[objective
] = objective_object
1140 if category
== "loc" then
1141 -- Loc is special, we don't store it, and construct it from the string.
1142 -- Don't have any error checking here, will assume it's correct.
1144 local _
, _
, c
, z
, x
, y
= string.find(objective
,"^(%d+),(%d+),([%d%.]+),([%d%.]+)$")
1147 _
, _
, i
, x
, y
= string.find(objective
,"^(%d+),([%d%.]+),([%d%.]+)$")
1149 i
= QuestHelper_IndexLookup
[c
][z
]
1152 objective_object
.o
= {pos
={{tonumber(i
),tonumber(x
),tonumber(y
),1}}}
1153 objective_object
.fb
= {}
1155 objective_list
= QuestHelper_Objectives_Local
[category
]
1156 if not objective_list
then
1158 QuestHelper_Objectives_Local
[category
] = objective_list
1160 objective_object
.o
= objective_list
[objective
]
1161 if not objective_object
.o
then
1162 objective_object
.o
= {}
1163 objective_list
[objective
] = objective_object
.o
1165 local l
= QuestHelper_StaticData
[self
.locale
]
1167 objective_list
= l
.objective
[category
]
1168 if objective_list
then
1169 objective_object
.fb
= objective_list
[objective
]
1172 if not objective_object
.fb
then
1173 objective_object
.fb
= {}
1176 -- TODO: If we have some other source of information (like LightHeaded) add its data to objective_object.fb
1181 return objective_object
1184 function QuestHelper
:AppendObjectivePosition(objective
, i
, x
, y
, w
)
1185 if not i
then return end -- We don't have a player position. We have a pile of poop. Enjoy your poop.
1187 local pos
= objective
.o
.pos
1189 if objective
.o
.drop
or objective
.o
.contained
then
1190 return -- If it's dropped by a monster, don't record the position we got the item at.
1192 objective
.o
.pos
= self
:AppendPosition({}, i
, x
, y
, w
)
1194 self
:AppendPosition(pos
, i
, x
, y
, w
)
1198 function QuestHelper
:AppendObjectiveDrop(objective
, monster
, count
)
1199 local drop
= objective
.o
.drop
1201 drop
[monster
] = (drop
[monster
] or 0)+(count
or 1)
1203 objective
.o
.drop
= {[monster
] = count
or 1}
1204 objective
.o
.pos
= nil -- If it's dropped by a monster, then forget the position we found it at.
1208 function QuestHelper
:AppendItemObjectiveDrop(item_object
, item_name
, monster_name
, count
)
1209 local quest
= self
:ItemIsForQuest(item_object
, item_name
)
1210 if quest
and not item_object
.o
.vendor
and not item_object
.o
.drop
and not item_object
.o
.pos
then
1211 self
:AppendQuestDrop(quest
, item_name
, monster_name
, count
)
1213 if not item_object
.o
.drop
and not item_object
.o
.pos
then
1214 self
:PurgeQuestItem(item_object
, item_name
)
1216 self
:AppendObjectiveDrop(item_object
, monster_name
, count
)
1220 function QuestHelper
:AppendItemObjectivePosition(item_object
, item_name
, i
, x
, y
)
1221 local quest
= self
:ItemIsForQuest(item_object
, item_name
)
1222 if quest
and not item_object
.o
.vendor
and not item_object
.o
.drop
and not item_object
.o
.pos
then
1223 self
:AppendQuestPosition(quest
, item_name
, i
, x
, y
)
1225 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
1226 -- Just learned that this item doesn't depend on a quest to drop, remove any quest references to it.
1227 self
:PurgeQuestItem(item_object
, item_name
)
1229 self
:AppendObjectivePosition(item_object
, i
, x
, y
)
1233 function QuestHelper
:AppendItemObjectiveContainer(objective
, container_name
, count
)
1234 local container
= objective
.o
.contained
1236 container
[container_name
] = (container
[container_name
] or 0)+(count
or 1)
1238 objective
.o
.contained
= {[container_name
] = count
or 1}
1239 objective
.o
.pos
= nil -- Forget the position.
1243 function QuestHelper
:AddObjectiveWatch(objective
, reason
)
1244 if not objective
.reasons
then
1245 objective
.reasons
= {}
1248 if not next(objective
.reasons
, nil) then
1249 objective
.watched
= true
1250 objective
:MarkUsed()
1252 objective
.filter_blocked
= false
1253 for obj
in pairs(objective
.swap_after
or objective
.after
) do
1255 objective
.filter_blocked
= true
1260 for obj
in pairs(objective
.swap_before
or objective
.before
) do
1262 obj
.filter_blocked
= true
1266 if self
.to_remove
[objective
] then
1267 self
.to_remove
[objective
] = nil
1269 self
.to_add
[objective
] = true
1273 objective
.reasons
[reason
] = (objective
.reasons
[reason
] or 0) + 1
1276 function QuestHelper
:RemoveObjectiveWatch(objective
, reason
)
1277 if objective
.reasons
[reason
] == 1 then
1278 objective
.reasons
[reason
] = nil
1279 if not next(objective
.reasons
, nil) then
1280 objective
:MarkUnused()
1281 objective
.watched
= false
1283 for obj
in pairs(objective
.swap_before
or objective
.before
) do
1285 obj
.filter_blocked
= false
1286 for obj2
in pairs(obj
.swap_after
or obj
.after
) do
1287 if obj2
.watched
then
1288 obj
.filter_blocked
= true
1295 if self
.to_add
[objective
] then
1296 self
.to_add
[objective
] = nil
1298 self
.to_remove
[objective
] = true
1302 objective
.reasons
[reason
] = objective
.reasons
[reason
] - 1
1306 function QuestHelper
:ObjectiveObjectDependsOn(objective
, needs
)
1307 assert(objective
~= needs
) -- If this was true, ObjectiveIsKnown would get in an infinite loop.
1308 -- TODO: Needs sanity checking, especially now that dependencies can be assigned by remote users.
1311 -- We store the new relationships in objective.swap_[before|after],
1312 -- creating and copying them from objective.[before|after],
1313 -- the routing coroutine will check for those, swap them, and release the originals
1314 -- when it gets to a safe place to do so.
1316 if not (objective
.swap_after
or objective
.after
)[needs
] then
1317 if objective
.peer
then
1318 for u
, l
in pairs(objective
.peer
) do
1319 -- Make sure other users know that the dependencies for this objective changed.
1320 objective
.peer
[u
] = math
.min(l
, 1)
1324 if not objective
.swap_after
then
1325 objective
.swap_after
= self
:CreateTable("swap_after")
1326 for key
,value
in pairs(objective
.after
) do objective
.swap_after
[key
] = value
end
1329 if not needs
.swap_before
then
1330 needs
.swap_before
= self
:CreateTable("swap_before")
1331 for key
,value
in pairs(needs
.before
) do needs
.swap_before
[key
] = value
end
1334 if needs
.watched
then
1335 objective
.filter_blocked
= true
1338 objective
.swap_after
[needs
] = true
1339 needs
.swap_before
[objective
] = true
1343 function QuestHelper
:IgnoreObjective(objective
)
1344 if self
.user_objectives
[objective
] then
1345 self
:RemoveObjectiveWatch(objective
, self
.user_objectives
[objective
])
1346 self
.user_objectives
[objective
] = nil
1348 objective
.user_ignore
= true
1351 --self:ForceRouteUpdate()
1354 function QuestHelper
:SetObjectivePriority(objective
, level
)
1355 level
= math
.min(5, math
.max(1, math
.floor((tonumber(level
) or 3)+0.5)))
1356 if level
~= objective
.priority
then
1357 objective
.priority
= level
1358 if objective
.peer
then
1359 for u
, l
in pairs(objective
.peer
) do
1360 -- Peers don't know about this new priority.
1361 objective
.peer
[u
] = math
.min(l
, 2)
1364 --self:ForceRouteUpdate()
1368 local function CalcObjectivePriority(obj
)
1369 local priority
= obj
.priority
1371 for o
in pairs(obj
.before
) do
1373 priority
= math
.min(priority
, CalcObjectivePriority(o
))
1380 local function ApplyBlockPriority(obj
, level
)
1381 for o
in pairs(obj
.before
) do
1383 ApplyBlockPriority(o
, level
)
1387 if obj
.priority
< level
then QuestHelper
:SetObjectivePriority(obj
, level
) end
1390 function QuestHelper
:SetObjectivePriorityPrompt(objective
, level
)
1391 self
:SetObjectivePriority(objective
, level
)
1392 if CalcObjectivePriority(objective
) ~= level
then
1393 local menu
= self
:CreateMenu()
1394 self
:CreateMenuTitle(menu
, QHText("IGNORED_PRIORITY_TITLE"))
1395 self
:CreateMenuItem(menu
, QHText("IGNORED_PRIORITY_FIX")):SetFunction(ApplyBlockPriority
, objective
, level
)
1396 self
:CreateMenuItem(menu
, QHText("IGNORED_PRIORITY_IGNORE")):SetFunction(self
.nop
)
1401 function QuestHelper
:SetObjectiveProgress(objective
, user
, have
, need
)
1402 if have
and need
then
1403 local list
= objective
.progress
1405 list
= self
:CreateTable("objective.progress")
1406 objective
.progress
= list
1409 local user_progress
= list
[user
]
1410 if not user_progress
then
1411 user_progress
= self
:CreateTable("objective.progress[user]")
1412 list
[user
] = user_progress
1416 local a
, b
= tonumber(have
), tonumber(need
)
1427 user_progress
[1], user_progress
[2], user_progress
[3] = have
, need
, pct
1429 if objective
.progress
then
1430 if objective
.progress
[user
] then
1431 self
:ReleaseTable(objective
.progress
[user
])
1432 objective
.progress
[user
] = nil
1434 if not next(objective
.progress
, nil) then
1435 self
:ReleaseTable(objective
.progress
)
1436 objective
.progress
= nil