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
) then
25 elseif self
.user_ignore
then
29 for i
, j
in pairs(self
.after
) do
30 if i
.watched
and not i
:Known() then -- Need to know how to do everything before this objective.
38 local function ObjectiveReason(self
, short
)
39 local reason
, rc
= nil, 0
41 for r
, c
in pairs(self
.reasons
) do
42 if not reason
or c
> rc
or (c
== rc
and r
> reason
) then
48 if not reason
then reason
= "Do some extremely secret unspecified something." end
50 if not short
and self
.pos
and self
.pos
[6] then
51 reason
= reason
.. "\n" .. self
.pos
[6]
57 local function Uses(self
, obj
, text
)
59 local uses
, used
= self
.uses
, obj
.used
62 uses
= QuestHelper
:CreateTable()
67 used
= QuestHelper
:CreateTable()
78 local function DoMarkUsed(self
)
79 -- Objectives should call 'self:Uses(objective, text)' to mark objectives they use by don't directly depend on.
80 -- This information is used in tooltips.
81 -- text is passed to QHFormat with the name of the objective being used.
84 local function MarkUsed(self
)
85 if not self
.marked_used
then
89 self
.marked_used
= self
.marked_used
+ 1
93 local function MarkUnused(self
)
94 assert(self
.marked_used
)
96 if self
.marked_used
== 1 then
97 local uses
= self
.uses
100 for obj
in pairs(uses
) do
105 QuestHelper
:ReleaseTable(uses
)
110 assert(not next(self
.used
))
111 QuestHelper
:ReleaseTable(self
.used
)
115 self
.marked_used
= nil
117 self
.marked_used
= self
.marked_used
- 1
121 local function DummyObjectiveKnown(self
)
122 return (self
.o
.pos
or self
.fb
.pos
) and DefaultObjectiveKnown(self
)
125 local function ItemKnown(self
)
126 if not DefaultObjectiveKnown(self
) then return false end
128 if self
.o
.vendor
then
129 for i
, npc
in ipairs(self
.o
.vendor
) do
130 local n
= self
.qh
:GetObjective("monster", npc
)
131 local faction
= n
.o
.faction
or n
.fb
.faction
132 if (not faction
or faction
== self
.qh
.faction
) and n
:Known() then
138 if self
.fb
.vendor
then
139 for i
, npc
in ipairs(self
.fb
.vendor
) do
140 local n
= self
.qh
:GetObjective("monster", npc
)
141 local faction
= n
.o
.faction
or n
.fb
.faction
142 if (not faction
or faction
== self
.qh
.faction
) and n
:Known() then
148 if self
.o
.pos
or self
.fb
.pos
then
152 if self
.o
.drop
then for monster
in pairs(self
.o
.drop
) do
153 if self
.qh
:GetObjective("monster", monster
):Known() then
158 if self
.fb
.drop
then for monster
in pairs(self
.fb
.drop
) do
159 if self
.qh
:GetObjective("monster", monster
):Known() then
164 if self
.o
.contained
then for item
in pairs(self
.o
.contained
) do
165 if self
.qh
:GetObjective("item", item
):Known() then
170 if self
.fb
.contained
then for item
in pairs(self
.fb
.contained
) do
171 if self
.qh
:GetObjective("item", item
):Known() then
177 local item
=self
.quest
.o
.item
178 item
= item
and item
[self
.obj
]
185 for monster
in pairs(item
.drop
) do
186 if self
.qh
:GetObjective("monster", monster
):Known() then
193 item
=self
.quest
.fb
.item
194 item
= item
and item
[self
.obj
]
200 for monster
in pairs(item
.drop
) do
201 if self
.qh
:GetObjective("monster", monster
):Known() then
212 local function ObjectiveAppendPositions(self
, objective
, weight
, why
)
215 if self
.o
.pos
then for i
, p
in ipairs(self
.o
.pos
) do
216 high
= math
.max(high
, p
[4])
219 if self
.fb
.pos
then for i
, p
in ipairs(self
.fb
.pos
) do
220 high
= math
.max(high
, p
[4])
225 if self
.o
.pos
then for i
, p
in ipairs(self
.o
.pos
) do
226 objective
:AddLoc(p
[1], p
[2], p
[3], p
[4]*high
, why
)
229 if self
.fb
.pos
then for i
, p
in ipairs(self
.fb
.pos
) do
230 objective
:AddLoc(p
[1], p
[2], p
[3], p
[4]*high
, why
)
235 local function ObjectivePrepareRouting(self
)
236 self
.setup_count
= self
.setup_count
+ 1
237 if not self
.setup
then
244 self
.d
= QuestHelper
:CreateTable()
245 self
.p
= QuestHelper
:CreateTable()
246 self
.nm
= QuestHelper
:CreateTable()
247 self
.nm2
= QuestHelper
:CreateTable()
248 self
.nl
= QuestHelper
:CreateTable()
249 self
.distance_cache
= QuestHelper
:CreateTable()
251 self
:AppendPositions(self
, 1, nil)
256 local function ItemAppendPositions(self
, objective
, weight
, why
)
257 why2
= why
and why
.."\n" or ""
259 if self
.o
.vendor
then for i
, npc
in ipairs(self
.o
.vendor
) do
260 local n
= self
.qh
:GetObjective("monster", npc
)
261 local faction
= n
.o
.faction
or n
.fb
.faction
262 if (not faction
or faction
== self
.qh
.faction
) then
263 n
:AppendPositions(objective
, 1, why2
..QHFormat("OBJECTIVE_PURCHASE", npc
))
267 if self
.fb
.vendor
then for i
, npc
in ipairs(self
.fb
.vendor
) do
268 local n
= self
.qh
:GetObjective("monster", npc
)
269 local faction
= n
.o
.faction
or n
.fb
.faction
270 if (not faction
or faction
== self
.qh
.faction
) then
271 n
:AppendPositions(objective
, 1, why2
..QHFormat("OBJECTIVE_PURCHASE", npc
))
275 if next(objective
.p
, nil) then
276 -- If we have points from vendors, then always use vendors. I don't want it telling you to killing the
277 -- towns people just because you had to talk to them anyway, and it saves walking to the store.
281 if self
.o
.drop
then for monster
, count
in pairs(self
.o
.drop
) do
282 local m
= self
.qh
:GetObjective("monster", monster
)
283 m
:AppendPositions(objective
, m
.o
.looted
and count
/m
.o
.looted
or 1, why2
..QHFormat("OBJECTIVE_SLAY", monster
))
286 if self
.fb
.drop
then for monster
, count
in pairs(self
.fb
.drop
) do
287 local m
= self
.qh
:GetObjective("monster", monster
)
288 m
:AppendPositions(objective
, m
.fb
.looted
and count
/m
.fb
.looted
or 1, why2
..QHFormat("OBJECTIVE_SLAY", monster
))
291 if self
.o
.contained
then for item
, count
in pairs(self
.o
.contained
) do
292 local i
= self
.qh
:GetObjective("item", item
)
293 i
:AppendPositions(objective
, i
.o
.opened
and count
/i
.o
.opened
or 1, why2
..QHFormat("OBJECTIVE_LOOT", item
))
296 if self
.fb
.contained
then for item
, count
in pairs(self
.fb
.contained
) do
297 local i
= self
.qh
:GetObjective("item", item
)
298 i
:AppendPositions(objective
, i
.fb
.opened
and count
/i
.fb
.opened
or 1, why2
..QHFormat("OBJECTIVE_LOOT", item
))
301 if self
.o
.pos
then for i
, p
in ipairs(self
.o
.pos
) do
302 objective
:AddLoc(p
[1], p
[2], p
[3], p
[4], why
)
305 if self
.fb
.pos
then for i
, p
in ipairs(self
.fb
.pos
) do
306 objective
:AddLoc(p
[1], p
[2], p
[3], p
[4], why
)
310 local item_list
=self
.quest
.o
.item
312 local data
= item_list
[self
.obj
]
313 if data
and data
.drop
then
314 for monster
, count
in pairs(data
.drop
) do
315 local m
= self
.qh
:GetObjective("monster", monster
)
316 m
:AppendPositions(objective
, m
.o
.looted
and count
/m
.o
.looted
or 1, why2
..QHFormat("OBJECTIVE_SLAY", monster
))
318 elseif data
and data
.pos
then
319 for i
, p
in ipairs(data
.pos
) do
320 objective
:AddLoc(p
[1], p
[2], p
[3], p
[4], why
)
325 item_list
=self
.quest
.fb
.item
327 local data
= item_list
[self
.obj
]
328 if data
and data
.drop
then
329 for monster
, count
in pairs(data
.drop
) do
330 local m
= self
.qh
:GetObjective("monster", monster
)
331 m
:AppendPositions(objective
, m
.fb
.looted
and count
/m
.fb
.looted
or 1, why2
..QHFormat("OBJECTIVE_SLAY", monster
))
333 elseif data
and data
.pos
then
334 for i
, p
in ipairs(data
.pos
) do
335 objective
:AddLoc(p
[1], p
[2], p
[3], p
[4], why
)
342 local function ItemDoMarkUsed(self
)
343 if self
.o
.vendor
then for i
, npc
in ipairs(self
.o
.vendor
) do
344 local n
= self
.qh
:GetObjective("monster", npc
)
345 local faction
= n
.o
.faction
or n
.fb
.faction
346 if (not faction
or faction
== self
.qh
.faction
) then
347 self
:Uses(n
, "TOOLTIP_PURCHASE")
351 if self
.fb
.vendor
then for i
, npc
in ipairs(self
.fb
.vendor
) do
352 local n
= self
.qh
:GetObjective("monster", npc
)
353 local faction
= n
.o
.faction
or n
.fb
.faction
354 if (not faction
or faction
== self
.qh
.faction
) then
355 self
:Uses(n
, "TOOLTIP_PURCHASE")
359 if self
.o
.drop
then for monster
, count
in pairs(self
.o
.drop
) do
360 self
:Uses(self
.qh
:GetObjective("monster", monster
), "TOOLTIP_SLAY")
363 if self
.fb
.drop
then for monster
, count
in pairs(self
.fb
.drop
) do
364 self
:Uses(self
.qh
:GetObjective("monster", monster
), "TOOLTIP_SLAY")
367 if self
.o
.contained
then for item
, count
in pairs(self
.o
.contained
) do
368 self
:Uses(self
.qh
:GetObjective("item", item
), "TOOLTIP_LOOT")
371 if self
.fb
.contained
then for item
, count
in pairs(self
.fb
.contained
) do
372 self
:Uses(self
.qh
:GetObjective("item", item
), "TOOLTIP_LOOT")
376 local item_list
=self
.quest
.o
.item
378 local data
= item_list
[self
.obj
]
379 if data
and data
.drop
then
380 for monster
, count
in pairs(data
.drop
) do
381 self
:Uses(self
.qh
:GetObjective("monster", monster
), "TOOLTIP_SLAY")
386 item_list
=self
.quest
.fb
.item
388 local data
= item_list
[self
.obj
]
389 if data
and data
.drop
then
390 for monster
, count
in pairs(data
.drop
) do
391 self
:Uses(self
.qh
:GetObjective("monster", monster
), "TOOLTIP_SLAY")
421 local function AddLoc(self
, index
, x
, y
, w
, why
)
422 assert(not self
.setup
)
425 local pair
= QuestHelper_ZoneLookup
[index
]
426 local c
, z
= pair
[1], pair
[2]
427 x
, y
= self
.qh
.Astrolabe
:TranslateWorldMapPosition(c
, z
, x
, y
, c
, 0)
429 x
= x
* self
.qh
.continent_scales_x
[c
]
430 y
= y
* self
.qh
.continent_scales_y
[c
]
431 local list
= self
.qh
.zone_nodes
[index
]
433 local points
= self
.p
[list
]
435 points
= QuestHelper
:CreateTable()
436 self
.p
[list
] = points
439 for i
, p
in pairs(points
) do
440 local u
, v
= x
-p
[3], y
-p
[4]
441 if u
*u
+v
*v
< 25 then -- Combine points within a threshold of 5 seconds travel time.
442 p
[3] = (p
[3]*p
[5]+x
*w
)/(p
[5]+w
)
443 p
[4] = (p
[4]*p
[5]+y
*w
)/(p
[5]+w
)
452 local new
= QuestHelper
:CreateTable()
453 new
[1], new
[2], new
[3], new
[4], new
[5], new
[6], new
[7] = list
, nil, x
, y
, w
, why
, w
454 table.insert(points
, new
)
458 local function FinishAddLoc(self
)
461 for z
, pl
in pairs(self
.p
) do
462 for i
, p
in ipairs(pl
) do
470 -- Remove probably useless locations.
471 for z
, pl
in pairs(self
.p
) do
472 local remove_zone
= true
475 if pl
[i
][5] < mx
*0.2 then
476 QuestHelper
:ReleaseTable(pl
[i
])
484 QuestHelper
:ReleaseTable(self
.p
[z
])
489 local node_map
= self
.nm
490 local node_list
= self
.nl
492 for list
, pl
in pairs(self
.p
) do
493 local dist
= self
.d
[list
]
498 dist
= QuestHelper
:CreateTable()
502 for i
, point
in ipairs(pl
) do
503 point
[5] = mx
/point
[5] -- Will become 1 for the most desired location, and become larger and larger for less desireable locations.
505 point
[2] = QuestHelper
:CreateTable()
507 for i
, node
in ipairs(list
) do
508 local u
, v
= point
[3]-node
.x
, point
[4]-node
.y
509 local d
= math
.sqrt(u
*u
+v
*v
)
514 if d
*point
[5] < dist
[i
][1]*dist
[i
][2] then
515 dist
[i
][1], dist
[i
][2] = d
, point
[5]
516 node_map
[node
] = point
519 local pair
= QuestHelper
:CreateTable()
520 pair
[1], pair
[2] = d
, point
[5]
523 if not node_map
[node
] then
524 table.insert(node_list
, node
)
525 node_map
[node
] = point
527 u
, v
= node_map
[node
][3]-node
.x
, node_map
[node
][4]-node
.y
529 if dist
[i
][1]*dist
[i
][2] < math
.sqrt(u
*u
+v
*v
)*node_map
[node
][5] then
530 node_map
[node
] = point
538 if #node_list
== 0 then QuestHelper
:Error(self
.cat
.."/"..self
.obj
..": zero nodes!") end
540 assert(not self
.setup
)
542 table.insert(self
.qh
.prepared_objectives
, self
)
545 local function GetPosition(self
)
551 local function ComputeTravelTime(self
, pos
, nocache
)
557 pos
.key
= math
.random()..""
560 if self
.distance_cache
[key
] then
561 return unpack(self
.distance_cache
[key
])
565 local graph
= self
.qh
.world_graph
568 graph
:PrepareSearch()
570 for z
, l
in pairs(self
.d
) do
571 for i
, n
in ipairs(z
) do
573 n
.e
, n
.w
= unpack(l
[i
])
575 elseif n
.e
* n
.w
< l
[i
][1]*l
[i
][2] then
576 n
.e
, n
.w
= unpack(l
[i
])
582 for i
, n
in ipairs(pos
[1]) do
583 graph
:AddStartNode(n
, d
[i
], nl
)
586 local e
= graph
:DoSearch(nl
)
591 local l
= self
.p
[pos
[1]]
593 local x
, y
= pos
[3], pos
[4]
596 for i
, n
in ipairs(l
) do
597 local u
, v
= x
-n
[3], y
-n
[4]
598 local d2
= math
.sqrt(u
*u
+v
*v
)
601 d
, e
, score
= d2
, n
, s
608 local new
= self
.qh
:CreateTable()
609 new
[1], new
[2] = d
, e
610 self
.distance_cache
[key
] = new
615 local function ComputeTravelTime2(self
, pos1
, pos2
, nocache
)
620 -- We don't want to cache distances involving the player's current position, as that would spam the table
622 pos1
.key
= math
.random()..""
625 pos2
.key
= math
.random()..""
627 key
= pos1
.key
..pos2
.key
628 if self
.distance_cache
[key
] then
629 return unpack(self
.distance_cache
[key
])
633 local graph
= self
.qh
.world_graph
636 graph
:PrepareSearch()
638 for z
, l
in pairs(self
.d
) do
639 for i
, n
in ipairs(z
) do
641 n
.e
, n
.w
= unpack(l
[i
])
643 elseif n
.e
* n
.w
< l
[i
][1]*l
[i
][2] then
644 n
.e
, n
.w
= unpack(l
[i
])
650 for i
, n
in ipairs(pos1
[1]) do
651 graph
:AddStartNode(n
, d
[i
], nl
)
654 graph
:DoFullSearch(nl
)
656 graph
:PrepareSearch()
658 -- Now, we need to figure out how long it takes to get to each node.
659 for z
, point_list
in pairs(self
.p
) do
661 -- Will also consider min distance.
662 local x
, y
= pos1
[3], pos1
[4]
664 for i
, p
in ipairs(point_list
) do
665 local a
, b
= p
[3]-x
, p
[4]-y
666 local u
, v
= p
[3], p
[4]
667 local d
= math
.sqrt(a
*a
+b
*b
)
670 for i
, n
in ipairs(z
) do
672 local bleh
= math
.sqrt(a
*a
+b
*b
)+n
.g
681 for i
, p
in ipairs(point_list
) do
682 local x
, y
= p
[3], p
[4]
687 for i
, n
in ipairs(z
) do
688 local a
, b
= n
.x
-x
, n
.y
-y
689 local d2
= math
.sqrt(a
*a
+b
*b
)+n
.g
691 if not score
or s
< score
then
702 for i
, n
in ipairs(pos2
[1]) do
710 for z
, l
in pairs(self
.d
) do
711 for i
, n
in ipairs(z
) do
712 local x
, y
= n
.x
, n
.y
716 for i
, p
in ipairs(self
.p
[z
]) do
717 local a
, b
= x
-p
[3], y
-p
[4]
718 d
= p
[7]+math
.sqrt(a
*a
+b
*b
)
720 if not bs
or s
< bs
then
726 -- Using score instead of distance, because we want nodes we're not really interested in to be less likely to get chosen.
727 graph
:AddStartNode(n
, bs
, el
)
731 local e
= graph
:DoSearch(pos2
[1])
734 local d2
= e
.g
+e
.e
-e
.p
.g
+(e
.p
.g
/nm
[e
.p
][5]-nm
[e
.p
][7])
737 local total
= (d
+d2
)*e
[5]
740 local x
, y
= pos2
[3], pos2
[4]
741 for i
, p
in ipairs(self
.p
[el
]) do
742 local a
, b
= x
-p
[3], y
-p
[4]
743 local c
= math
.sqrt(a
*a
+b
*b
)
744 local t
= (p
[7]+c
)*p
[5]
746 total
, d
, d2
, e
= t
, p
[7], c
, p
753 local new
= self
.qh
:CreateTable()
754 new
[1], new
[2], new
[3] = d
, d2
, e
755 self
.distance_cache
[key
] = new
760 local function DoneRouting(self
)
761 assert(self
.setup_count
> 0)
764 if self
.setup_count
== 1 then
766 QuestHelper
:ReleaseObjectivePathingInfo(self
)
767 for i
, obj
in ipairs(self
.qh
.prepared_objectives
) do
769 table.remove(self
.qh
.prepared_objectives
, i
)
774 self
.setup_count
= self
.setup_count
- 1
778 local next_objective_id
= 0
780 local function ObjectiveShare(self
)
781 self
.want_share
= true
784 local function ObjectiveUnshare(self
)
785 self
.want_share
= false
788 QuestHelper
.default_objective_param
=
790 CouldBeFirst
=ObjectiveCouldBeFirst
,
793 DoMarkUsed
=DoMarkUsed
,
795 MarkUnused
=MarkUnused
,
797 DefaultKnown
=DefaultObjectiveKnown
,
798 Known
=DummyObjectiveKnown
,
799 Reason
=ObjectiveReason
,
801 AppendPositions
=ObjectiveAppendPositions
,
802 PrepareRouting
=ObjectivePrepareRouting
,
804 FinishAddLoc
=FinishAddLoc
,
805 DoneRouting
=DoneRouting
,
807 Position
=GetPosition
,
808 TravelTime
=ComputeTravelTime
,
809 TravelTime2
=ComputeTravelTime2
,
811 Share
=ObjectiveShare
, -- Invoke to share this objective with your peers.
812 Unshare
=ObjectiveUnshare
, -- Invoke to stop sharing this objective.
815 QuestHelper
.default_objective_item_param
=
818 AppendPositions
= ItemAppendPositions
,
819 DoMarkUsed
= ItemDoMarkUsed
822 for key
, value
in pairs(QuestHelper
.default_objective_param
) do
823 if not QuestHelper
.default_objective_item_param
[key
] then
824 QuestHelper
.default_objective_item_param
[key
] = value
828 QuestHelper
.default_objective_meta
= { __index
= QuestHelper
.default_objective_param
}
829 QuestHelper
.default_objective_item_meta
= { __index
= QuestHelper
.default_objective_item_param
}
831 function QuestHelper
:NewObjectiveObject()
832 next_objective_id
= next_objective_id
+1
836 id
=next_objective_id
,
838 want_share
=false, -- True if we want this objective shared.
839 is_sharing
=false, -- Set to true if we've told other users about this objective.
841 user_ignore
=nil, -- When nil, will use filters. Will ignore, when true, always show (if known).
843 priority
=3, -- A hint as to what priority the quest should have. Should be 1, 2, 3, 4, or 5.
844 real_priority
=3, -- This will be set to the priority routing actually decided to assign it.
855 before
={}, -- List of objectives that this objective must appear before.
856 after
={}, -- List of objectives that this objective must appear after.
858 -- Routing related junk.
860 --[[ Will be created as needed.
863 nm=nil, -- Maps nodes to their nearest zone/list/x/y position.
864 nm2=nil, -- Maps nodes to their nears position, but dynamically set in TravelTime2.
865 nl=nil, -- List of all the nodes we need to consider.
866 location=nil, -- Will be set to the best position for the node.
867 pos=nil, -- Zone node list, distance list, x, y, reason.
869 }, QuestHelper
.default_objective_meta
)
872 function QuestHelper
:GetObjective(category
, objective
)
873 local objective_list
= self
.objective_objects
[category
]
875 if not objective_list
then
877 self
.objective_objects
[category
] = objective_list
880 local objective_object
= objective_list
[objective
]
882 if not objective_object
then
883 if category
== "quest" then
884 local _
, _
, level
, hash
, name
= string.find(objective
, "^(%d+)/(%d*)/(.*)$")
886 _
, _
, level
, name
= string.find(objective
, "^(%d+)/(.*)$")
892 if hash
== "" then hash
= nil end
893 objective_object
= self
:GetQuest(name
, tonumber(level
), tonumber(hash
))
894 objective_list
[objective
] = objective_object
895 return objective_object
898 objective_object
= self
:NewObjectiveObject()
900 objective_object
.cat
= category
901 objective_object
.obj
= objective
903 if category
== "item" then
904 setmetatable(objective_object
, QuestHelper
.default_objective_item_meta
)
905 objective_object
.icon_id
= 2
906 elseif category
== "monster" then
907 objective_object
.icon_id
= 1
908 elseif category
== "object" then
909 objective_object
.icon_id
= 3
910 elseif category
== "event" then
911 objective_object
.icon_id
= 4
912 elseif category
== "loc" then
913 objective_object
.icon_id
= 6
914 elseif category
== "reputation" then
915 objective_object
.icon_id
= 5
917 self
:TextOut("FIXME: Objective type '"..category
.."' for objective '"..objective
.."' isn't explicitly supported yet; hopefully the dummy handler will do something sensible.")
920 objective_list
[objective
] = objective_object
922 if category
== "loc" then
923 -- Loc is special, we don't store it, and construct it from the string.
924 -- Don't have any error checking here, will assume it's correct.
926 local _
, _
, c
, z
, x
, y
= string.find(objective
,"^(%d+),(%d+),([%d%.]+),([%d%.]+)$")
929 _
, _
, i
, x
, y
= string.find(objective
,"^(%d+),([%d%.]+),([%d%.]+)$")
931 i
= QuestHelper_IndexLookup
[c
][z
]
934 objective_object
.o
= {pos
={{tonumber(i
),tonumber(x
),tonumber(y
),1}}}
935 objective_object
.fb
= {}
937 objective_list
= QuestHelper_Objectives
[category
]
938 if not objective_list
then
940 QuestHelper_Objectives
[category
] = objective_list
942 objective_object
.o
= objective_list
[objective
]
943 if not objective_object
.o
then
944 objective_object
.o
= {}
945 objective_list
[objective
] = objective_object
.o
947 local l
= QuestHelper_StaticData
[self
.locale
]
949 objective_list
= l
.objective
[category
]
950 if objective_list
then
951 objective_object
.fb
= objective_list
[objective
]
954 if not objective_object
.fb
then
955 objective_object
.fb
= {}
958 -- TODO: If we have some other source of information (like LightHeaded) add its data to objective_object.fb
963 return objective_object
966 function QuestHelper
:AppendObjectivePosition(objective
, i
, x
, y
, w
)
967 local pos
= objective
.o
.pos
969 if objective
.o
.drop
or objective
.o
.contained
then
970 return -- If it's dropped by a monster, don't record the position we got the item at.
972 objective
.o
.pos
= self
:AppendPosition({}, i
, x
, y
, w
)
974 self
:AppendPosition(pos
, i
, x
, y
, w
)
978 function QuestHelper
:AppendObjectiveDrop(objective
, monster
, count
)
979 local drop
= objective
.o
.drop
981 drop
[monster
] = (drop
[monster
] or 0)+(count
or 1)
983 objective
.o
.drop
= {[monster
] = count
or 1}
984 objective
.o
.pos
= nil -- If it's dropped by a monster, then forget the position we found it at.
988 function QuestHelper
:AppendItemObjectiveDrop(item_object
, item_name
, monster_name
, count
)
989 local quest
= self
:ItemIsForQuest(item_object
, item_name
)
990 if quest
and not item_object
.o
.vendor
and not item_object
.o
.drop
and not item_object
.o
.pos
then
991 self
:AppendQuestDrop(quest
, item_name
, monster_name
, count
)
993 if not item_object
.o
.drop
and not item_object
.o
.pos
then
994 self
:PurgeQuestItem(item_object
, item_name
)
996 self
:AppendObjectiveDrop(item_object
, monster_name
, count
)
1000 function QuestHelper
:AppendItemObjectivePosition(item_object
, item_name
, i
, x
, y
)
1001 local quest
= self
:ItemIsForQuest(item_object
, item_name
)
1002 if quest
and not item_object
.o
.vendor
and not item_object
.o
.drop
and not item_object
.o
.pos
then
1003 self
:AppendQuestPosition(quest
, item_name
, i
, x
, y
)
1005 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
1006 -- Just learned that this item doesn't depend on a quest to drop, remove any quest references to it.
1007 self
:PurgeQuestItem(item_object
, item_name
)
1009 self
:AppendObjectivePosition(item_object
, i
, x
, y
)
1013 function QuestHelper
:AppendItemObjectiveContainer(objective
, container_name
, count
)
1014 local container
= objective
.o
.contained
1016 container
[container_name
] = (container
[container_name
] or 0)+(count
or 1)
1018 objective
.o
.contained
= {[container_name
] = count
or 1}
1019 objective
.o
.pos
= nil -- Forget the position.
1023 function QuestHelper
:AddObjectiveWatch(objective
, reason
)
1024 if not objective
.reasons
then
1025 objective
.reasons
= {}
1028 if not next(objective
.reasons
, nil) then
1029 objective
.watched
= true
1030 objective
:MarkUsed()
1032 if self
.to_remove
[objective
] then
1033 self
.to_remove
[objective
] = nil
1035 self
.to_add
[objective
] = true
1039 objective
.reasons
[reason
] = (objective
.reasons
[reason
] or 0) + 1
1042 function QuestHelper
:RemoveObjectiveWatch(objective
, reason
)
1043 if objective
.reasons
[reason
] == 1 then
1044 objective
.reasons
[reason
] = nil
1045 if not next(objective
.reasons
, nil) then
1046 objective
:MarkUnused()
1047 objective
.watched
= false
1049 if self
.to_add
[objective
] then
1050 self
.to_add
[objective
] = nil
1052 self
.to_remove
[objective
] = true
1056 objective
.reasons
[reason
] = objective
.reasons
[reason
] - 1
1060 function QuestHelper
:ObjectiveObjectDependsOn(objective
, needs
)
1061 assert(objective
~= needs
) -- If this was true, ObjectiveIsKnown would get in an infinite loop.
1062 -- TODO: Needs sanity checking, especially now that dependencies can be assigned by remote users.
1065 -- We store the new relationships in objective.swap_[before|after],
1066 -- creating and copying them from objective.[before|after],
1067 -- the routing coroutine will check for those, swap them, and release the originals
1068 -- when it gets to a safe place to do so.
1070 if not (objective
.swap_after
or objective
.after
)[needs
] then
1071 if objective
.peer
then
1072 for u
, l
in pairs(objective
.peer
) do
1073 -- Make sure other users know that the dependencies for this objective changed.
1074 objective
.peer
[u
] = math
.min(l
, 1)
1078 if not objective
.swap_after
then
1079 objective
.swap_after
= self
:CreateTable()
1080 for key
,value
in pairs(objective
.after
) do objective
.swap_after
[key
] = value
end
1083 if not needs
.swap_before
then
1084 needs
.swap_before
= self
:CreateTable()
1085 for key
,value
in pairs(needs
.before
) do needs
.swap_before
[key
] = value
end
1088 objective
.swap_after
[needs
] = true
1089 needs
.swap_before
[objective
] = true
1093 function QuestHelper
:AddObjectiveOptionsToMenu(obj
, menu
)
1094 local submenu
= self
:CreateMenu()
1097 local name
= QHText("PRIORITY"..i
)
1098 local item
= self
:CreateMenuItem(submenu
, name
)
1101 if obj
.priority
== i
then
1102 tex
= self
:CreateIconTexture(item
, 10)
1103 elseif obj
.real_priority
== i
then
1104 tex
= self
:CreateIconTexture(item
, 8)
1106 tex
= self
:CreateIconTexture(item
, 12)
1107 tex
:SetVertexColor(1, 1, 1, 0)
1110 item
:AddTexture(tex
, true)
1111 item
:SetFunction(self
.SetObjectivePriorityPrompt
, self
, obj
, i
)
1114 self
:CreateMenuItem(menu
, QHText("PRIORITY")):SetSubmenu(submenu
)
1116 if self
.sharing
then
1117 submenu
= self
:CreateMenu(QHText("SHARING"))
1118 local item
= self
:CreateMenuItem(submenu
, QHText("ENABLE"))
1119 local tex
= self
:CreateIconTexture(item
, 10)
1120 if not obj
.want_share
then tex
:SetVertexColor(1, 1, 1, 0) end
1121 item
:AddTexture(tex
, true)
1122 item
:SetFunction(obj
.Share
, obj
)
1124 local item
= self
:CreateMenuItem(submenu
, QHText("DISABLE"))
1125 local tex
= self
:CreateIconTexture(item
, 10)
1126 if obj
.want_share
then tex
:SetVertexColor(1, 1, 1, 0) end
1127 item
:AddTexture(tex
, true)
1128 item
:SetFunction(obj
.Unshare
, obj
)
1130 self
:CreateMenuItem(menu
, QHText("SHARING")):SetSubmenu(submenu
)
1133 self
:CreateMenuItem(menu
, QHText("IGNORE")):SetFunction(self
.IgnoreObjective
, self
, obj
)
1136 function QuestHelper
:IgnoreObjective(objective
)
1137 if self
.user_objectives
[objective
] then
1138 self
:RemoveObjectiveWatch(objective
, self
.user_objectives
[objective
])
1139 self
.user_objectives
[objective
] = nil
1141 objective
.user_ignore
= true
1144 --self:ForceRouteUpdate()
1147 function QuestHelper
:SetObjectivePriority(objective
, level
)
1148 level
= math
.min(5, math
.max(1, math
.floor((tonumber(level
) or 3)+0.5)))
1149 if level
~= objective
.priority
then
1150 objective
.priority
= level
1151 if objective
.peer
then
1152 for u
, l
in pairs(objective
.peer
) do
1153 -- Peers don't know about this new priority.
1154 objective
.peer
[u
] = math
.min(l
, 2)
1157 --self:ForceRouteUpdate()
1161 local function CalcObjectivePriority(obj
)
1162 local priority
= obj
.priority
1164 for o
in pairs(obj
.before
) do
1166 priority
= math
.min(priority
, CalcObjectivePriority(o
))
1173 local function ApplyBlockPriority(obj
, level
)
1174 for o
in pairs(obj
.before
) do
1176 ApplyBlockPriority(o
, level
)
1180 if obj
.priority
< level
then QuestHelper
:SetObjectivePriority(obj
, level
) end
1183 function QuestHelper
:SetObjectivePriorityPrompt(objective
, level
)
1184 self
:SetObjectivePriority(objective
, level
)
1185 if CalcObjectivePriority(objective
) ~= level
then
1186 local menu
= self
:CreateMenu()
1187 self
:CreateMenuTitle(menu
, QHText("IGNORED_PRIORITY_TITLE"))
1188 self
:CreateMenuItem(menu
, QHText("IGNORED_PRIORITY_FIX")):SetFunction(ApplyBlockPriority
, objective
, level
)
1189 self
:CreateMenuItem(menu
, QHText("IGNORED_PRIORITY_IGNORE")):SetFunction(self
.nop
)
1194 function QuestHelper
:SetObjectiveProgress(objective
, user
, have
, need
)
1195 if have
and need
then
1196 local list
= objective
.progress
1198 list
= self
:CreateTable()
1199 objective
.progress
= list
1202 local user_progress
= list
[user
]
1203 if not user_progress
then
1204 user_progress
= self
:CreateTable()
1205 list
[user
] = user_progress
1209 local a
, b
= tonumber(have
), tonumber(need
)
1220 user_progress
[1], user_progress
[2], user_progress
[3] = have
, need
, pct
1222 if objective
.progress
then
1223 if objective
.progress
[user
] then
1224 self
:ReleaseTable(objective
.progress
[user
])
1225 objective
.progress
[user
] = nil
1227 if not next(objective
.progress
, nil) then
1228 self
:ReleaseTable(objective
.progress
)
1229 objective
.progress
= nil