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
)
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 objective
:AddLoc(p
[1], p
[2], p
[3], p
[4]*high
, why
)
230 if self
.fb
.pos
then for i
, p
in ipairs(self
.fb
.pos
) do
231 objective
:AddLoc(p
[1], p
[2], p
[3], p
[4]*high
, why
)
236 local function ObjectivePrepareRouting(self
, args
)
237 self
.setup_count
= self
.setup_count
+ 1
238 if not self
.setup
then
245 self
.d
= QuestHelper
:CreateTable("objective.d")
246 self
.p
= QuestHelper
:CreateTable("objective.p")
247 self
.nm
= QuestHelper
:CreateTable("objective.nm")
248 self
.nm2
= QuestHelper
:CreateTable("objective.nm2")
249 self
.nl
= QuestHelper
:CreateTable("objective.nl")
250 self
.distance_cache
= QuestHelper
:CreateTable("objective.distance_cache")
252 self
:AppendPositions(self
, 1, nil)
253 self
:FinishAddLoc(args
)
257 local function ItemAppendPositions(self
, objective
, weight
, why
)
258 why2
= why
and why
.."\n" or ""
260 if self
.o
.vendor
then for i
, npc
in ipairs(self
.o
.vendor
) do
261 local n
= self
.qh
:GetObjective("monster", npc
)
262 local faction
= n
.o
.faction
or n
.fb
.faction
263 if (not faction
or faction
== self
.qh
.faction
) then
264 n
:AppendPositions(objective
, 1, why2
..QHFormat("OBJECTIVE_PURCHASE", npc
))
268 if self
.fb
.vendor
then for i
, npc
in ipairs(self
.fb
.vendor
) do
269 local n
= self
.qh
:GetObjective("monster", npc
)
270 local faction
= n
.o
.faction
or n
.fb
.faction
271 if (not faction
or faction
== self
.qh
.faction
) then
272 n
:AppendPositions(objective
, 1, why2
..QHFormat("OBJECTIVE_PURCHASE", npc
))
276 if next(objective
.p
, nil) then
277 -- If we have points from vendors, then always use vendors. I don't want it telling you to killing the
278 -- towns people just because you had to talk to them anyway, and it saves walking to the store.
282 if self
.o
.drop
then for monster
, count
in pairs(self
.o
.drop
) do
283 local m
= self
.qh
:GetObjective("monster", monster
)
284 m
:AppendPositions(objective
, m
.o
.looted
and count
/m
.o
.looted
or 1, why2
..QHFormat("OBJECTIVE_SLAY", monster
))
287 if self
.fb
.drop
then for monster
, count
in pairs(self
.fb
.drop
) do
288 local m
= self
.qh
:GetObjective("monster", monster
)
289 m
:AppendPositions(objective
, m
.fb
.looted
and count
/m
.fb
.looted
or 1, why2
..QHFormat("OBJECTIVE_SLAY", monster
))
292 if self
.o
.contained
then for item
, count
in pairs(self
.o
.contained
) do
293 local i
= self
.qh
:GetObjective("item", item
)
294 i
:AppendPositions(objective
, i
.o
.opened
and count
/i
.o
.opened
or 1, why2
..QHFormat("OBJECTIVE_LOOT", item
))
297 if self
.fb
.contained
then for item
, count
in pairs(self
.fb
.contained
) do
298 local i
= self
.qh
:GetObjective("item", item
)
299 i
:AppendPositions(objective
, i
.fb
.opened
and count
/i
.fb
.opened
or 1, why2
..QHFormat("OBJECTIVE_LOOT", item
))
302 if self
.o
.pos
then for i
, p
in ipairs(self
.o
.pos
) do
303 objective
:AddLoc(p
[1], p
[2], p
[3], p
[4], why
)
306 if self
.fb
.pos
then for i
, p
in ipairs(self
.fb
.pos
) do
307 objective
:AddLoc(p
[1], p
[2], p
[3], p
[4], why
)
311 local item_list
=self
.quest
.o
.item
313 local data
= item_list
[self
.obj
]
314 if data
and data
.drop
then
315 for monster
, count
in pairs(data
.drop
) do
316 local m
= self
.qh
:GetObjective("monster", monster
)
317 m
:AppendPositions(objective
, m
.o
.looted
and count
/m
.o
.looted
or 1, why2
..QHFormat("OBJECTIVE_SLAY", monster
))
319 elseif data
and data
.pos
then
320 for i
, p
in ipairs(data
.pos
) do
321 objective
:AddLoc(p
[1], p
[2], p
[3], p
[4], why
)
326 item_list
=self
.quest
.fb
.item
328 local data
= item_list
[self
.obj
]
329 if data
and data
.drop
then
330 for monster
, count
in pairs(data
.drop
) do
331 local m
= self
.qh
:GetObjective("monster", monster
)
332 m
:AppendPositions(objective
, m
.fb
.looted
and count
/m
.fb
.looted
or 1, why2
..QHFormat("OBJECTIVE_SLAY", monster
))
334 elseif data
and data
.pos
then
335 for i
, p
in ipairs(data
.pos
) do
336 objective
:AddLoc(p
[1], p
[2], p
[3], p
[4], why
)
343 local function ItemDoMarkUsed(self
)
344 if self
.o
.vendor
then for i
, npc
in ipairs(self
.o
.vendor
) do
345 local n
= self
.qh
:GetObjective("monster", npc
)
346 local faction
= n
.o
.faction
or n
.fb
.faction
347 if (not faction
or faction
== self
.qh
.faction
) then
348 self
:Uses(n
, "TOOLTIP_PURCHASE")
352 if self
.fb
.vendor
then for i
, npc
in ipairs(self
.fb
.vendor
) do
353 local n
= self
.qh
:GetObjective("monster", npc
)
354 local faction
= n
.o
.faction
or n
.fb
.faction
355 if (not faction
or faction
== self
.qh
.faction
) then
356 self
:Uses(n
, "TOOLTIP_PURCHASE")
360 if self
.o
.drop
then for monster
, count
in pairs(self
.o
.drop
) do
361 self
:Uses(self
.qh
:GetObjective("monster", monster
), "TOOLTIP_SLAY")
364 if self
.fb
.drop
then for monster
, count
in pairs(self
.fb
.drop
) do
365 self
:Uses(self
.qh
:GetObjective("monster", monster
), "TOOLTIP_SLAY")
368 if self
.o
.contained
then for item
, count
in pairs(self
.o
.contained
) do
369 self
:Uses(self
.qh
:GetObjective("item", item
), "TOOLTIP_LOOT")
372 if self
.fb
.contained
then for item
, count
in pairs(self
.fb
.contained
) do
373 self
:Uses(self
.qh
:GetObjective("item", item
), "TOOLTIP_LOOT")
377 local item_list
=self
.quest
.o
.item
379 local data
= item_list
[self
.obj
]
380 if data
and data
.drop
then
381 for monster
, count
in pairs(data
.drop
) do
382 self
:Uses(self
.qh
:GetObjective("monster", monster
), "TOOLTIP_SLAY")
387 item_list
=self
.quest
.fb
.item
389 local data
= item_list
[self
.obj
]
390 if data
and data
.drop
then
391 for monster
, count
in pairs(data
.drop
) do
392 self
:Uses(self
.qh
:GetObjective("monster", monster
), "TOOLTIP_SLAY")
422 local function AddLoc(self
, index
, x
, y
, w
, why
)
423 assert(not self
.setup
)
426 local pair
= QuestHelper_ZoneLookup
[index
]
427 if not pair
then return end -- that zone doesn't exist! We require more vespene gas. Not enough rage!
428 local c
, z
= pair
[1], pair
[2]
429 x
, y
= self
.qh
.Astrolabe
:TranslateWorldMapPosition(c
, z
, x
, y
, c
, 0)
431 x
= x
* self
.qh
.continent_scales_x
[c
]
432 y
= y
* self
.qh
.continent_scales_y
[c
]
433 local list
= self
.qh
.zone_nodes
[index
]
435 local points
= self
.p
[list
]
437 points
= QuestHelper
:CreateTable("objective.p[zone] (objective nodes per-zone)")
438 self
.p
[list
] = points
441 for i
, p
in pairs(points
) do
442 local u
, v
= x
-p
[3], y
-p
[4]
443 if u
*u
+v
*v
< 25 then -- Combine points within a threshold of 5 seconds travel time.
444 p
[3] = (p
[3]*p
[5]+x
*w
)/(p
[5]+w
)
445 p
[4] = (p
[4]*p
[5]+y
*w
)/(p
[5]+w
)
454 local new
= QuestHelper
:CreateTable("objective.p[zone] (possible objective node)")
455 new
[1], new
[2], new
[3], new
[4], new
[5], new
[6], new
[7] = list
, nil, x
, y
, w
, why
, w
456 table.insert(points
, new
)
460 local function FinishAddLoc(self
, args
)
463 for z
, pl
in pairs(self
.p
) do
464 for i
, p
in ipairs(pl
) do
472 if not self
.zones
then
473 -- Not using CreateTable, because it will not be released when routing is complete.
476 -- We could remove the already known zones, but I'm operating under the assumtion that locations will only be added,
477 -- not removed, so this isn't necessary.
480 -- Remove probably useless locations.
481 for z
, pl
in pairs(self
.p
) do
482 local remove_zone
= true
485 if pl
[i
][5] < mx
*0.2 then
486 QuestHelper
:ReleaseTable(pl
[i
])
494 QuestHelper
:ReleaseTable(self
.p
[z
])
497 self
.zones
[z
.i
] = true
501 local node_map
= self
.nm
502 local node_list
= self
.nl
504 for list
, pl
in pairs(self
.p
) do
505 local dist
= self
.d
[list
]
510 dist
= QuestHelper
:CreateTable("self.d[list]")
514 for i
, point
in ipairs(pl
) do
515 point
[5] = mx
/point
[5] -- Will become 1 for the most desired location, and become larger and larger for less desireable locations.
517 point
[2] = QuestHelper
:CreateTable("possible objective node to zone edge cache")
519 for i
, node
in ipairs(list
) do
520 local u
, v
= point
[3]-node
.x
, point
[4]-node
.y
521 local d
= math
.sqrt(u
*u
+v
*v
)
526 if d
*point
[5] < dist
[i
][1]*dist
[i
][2] then
527 dist
[i
][1], dist
[i
][2] = d
, point
[5]
528 node_map
[node
] = point
531 local pair
= QuestHelper
:CreateTable()
532 pair
[1], pair
[2] = d
, point
[5]
535 if not node_map
[node
] then
536 table.insert(node_list
, node
)
537 node_map
[node
] = point
539 u
, v
= node_map
[node
][3]-node
.x
, node_map
[node
][4]-node
.y
541 if dist
[i
][1]*dist
[i
][2] < math
.sqrt(u
*u
+v
*v
)*node_map
[node
][5] then
542 node_map
[node
] = point
550 if not args
or not args
.failable
then
551 if #node_list
== 0 then QuestHelper
:Error(self
.cat
.."/"..self
.obj
..": zero nodes!") end
554 assert(not self
.setup
)
556 table.insert(self
.qh
.prepared_objectives
, self
)
559 local function GetPosition(self
)
565 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)
567 -- Note: Pos is the starting point, the objective is the destination. These are different data formats - "self" can be a set of points.
568 -- 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.
569 local function ObjectiveTravelTime(self
, pos
, nocache
)
572 -- The caching is pretty obvious.
575 assert(pos
~= QuestHelper
.pos
)
577 pos
.key
= math
.random()..""
580 cached
= self
.distance_cache
[key
]
582 if not QH_TESTCACHE
then
583 return unpack(cached
)
588 local graph
= self
.qh
.world_graph
591 graph
:PrepareSearch()
593 -- 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.
594 for z
, l
in pairs(self
.d
) do
595 for i
, n
in ipairs(z
) do
597 n
.e
, n
.w
= unpack(l
[i
])
599 elseif n
.e
* n
.w
< l
[i
][1]*l
[i
][2] then
600 n
.e
, n
.w
= unpack(l
[i
])
606 for i
, n
in ipairs(pos
[1]) do
607 graph
:AddStartNode(n
, d
[i
], nl
)
610 local e
= graph
:DoSearch(nl
)
612 -- d changes datatype here. I hate this codebase. Hell, e probably changes datatype also! yaaaay. what does .nm mean? what does .d mean?
616 -- There's something going on with weighting here that I don't understand
617 local l
= self
.p
[pos
[1]]
619 local x
, y
= pos
[3], pos
[4]
622 for i
, n
in ipairs(l
) do
623 local u
, v
= x
-n
[3], y
-n
[4]
624 local d2
= math
.sqrt(u
*u
+v
*v
)
627 d
, e
, score
= d2
, n
, s
634 assert( not cached
or (cached
[1] == d
and cached
[2] == e
))
635 if not QH_TESTCACHE
or not cached
then
636 local new
= self
.qh
:CreateTable()
637 new
[1], new
[2] = d
, e
638 self
.distance_cache
[key
] = new
639 self
.qh
:CacheRegister(self
)
646 -- Note: pos1 is the starting point, pos2 is the ending point, the objective is somewhere between them.
647 -- 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?
648 local function ObjectiveTravelTime2(self
, pos1
, pos2
, nocache
)
651 -- caching is pretty simple as usual
654 assert(pos1
~= QuestHelper
.pos
)
655 assert(pos2
~= QuestHelper
.pos
)
656 -- We don't want to cache distances involving the player's current position, as that would spam the table
658 pos1
.key
= math
.random()..""
661 pos2
.key
= math
.random()..""
663 key
= pos1
.key
..pos2
.key
664 cached
= self
.distance_cache
[key
]
666 if not QH_TESTCACHE
then
667 return unpack(cached
)
672 local graph
= self
.qh
.world_graph
675 -- This is the standard pos1-to-self code that we're used to seeing . . .
676 graph
:PrepareSearch()
678 for z
, l
in pairs(self
.d
) do
679 for i
, n
in ipairs(z
) do
681 n
.e
, n
.w
= unpack(l
[i
])
683 elseif n
.e
* n
.w
< l
[i
][1]*l
[i
][2] then
684 n
.e
, n
.w
= unpack(l
[i
])
690 for i
, n
in ipairs(pos1
[1]) do
691 graph
:AddStartNode(n
, d
[i
], nl
)
694 graph
:DoFullSearch(nl
)
696 graph
:PrepareSearch()
698 -- . . . and here's where it gets wonky
699 -- Now, we need to figure out how long it takes to get to each node.
700 for z
, point_list
in pairs(self
.p
) do
702 -- Will also consider min distance.
703 local x
, y
= pos1
[3], pos1
[4]
705 for i
, p
in ipairs(point_list
) do
706 local a
, b
= p
[3]-x
, p
[4]-y
707 local u
, v
= p
[3], p
[4]
708 local d
= math
.sqrt(a
*a
+b
*b
)
711 for i
, n
in ipairs(z
) do
713 local bleh
= math
.sqrt(a
*a
+b
*b
)+n
.g
722 for i
, p
in ipairs(point_list
) do
723 local x
, y
= p
[3], p
[4]
728 for i
, n
in ipairs(z
) do
729 local a
, b
= n
.x
-x
, n
.y
-y
730 local d2
= math
.sqrt(a
*a
+b
*b
)+n
.g
732 if not score
or s
< score
then
743 for i
, n
in ipairs(pos2
[1]) do
751 for z
, l
in pairs(self
.d
) do
752 for i
, n
in ipairs(z
) do
753 local x
, y
= n
.x
, n
.y
757 for i
, p
in ipairs(self
.p
[z
]) do
758 local a
, b
= x
-p
[3], y
-p
[4]
759 d
= p
[7]+math
.sqrt(a
*a
+b
*b
)
761 if not bs
or s
< bs
then
767 -- Using score instead of distance, because we want nodes we're not really interested in to be less likely to get chosen.
768 graph
:AddStartNode(n
, bs
, el
)
772 local e
= graph
:DoSearch(pos2
[1])
775 local d2
= e
.g
+e
.e
-e
.p
.g
+(e
.p
.g
/nm
[e
.p
][5]-nm
[e
.p
][7])
778 local total
= (d
+d2
)*e
[5]
781 local x
, y
= pos2
[3], pos2
[4]
782 for i
, p
in ipairs(self
.p
[el
]) do
783 local a
, b
= x
-p
[3], y
-p
[4]
784 local c
= math
.sqrt(a
*a
+b
*b
)
785 local t
= (p
[7]+c
)*p
[5]
787 total
, d
, d2
, e
= t
, p
[7], c
, p
792 -- 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)
793 d
= QuestHelper
:ComputeTravelTime(pos1
, e
)
794 d2
= QuestHelper
:ComputeTravelTime(e
, pos2
)
798 assert( not cached
or (cached
[1] == d
and cached
[2] == d2
and cached
[3] == e
))
799 if not QH_TESTCACHE
or not cached
then
800 local new
= self
.qh
:CreateTable("ObjectiveTravelTime2 cache")
801 new
[1], new
[2], new
[3] = d
, d2
, e
802 self
.distance_cache
[key
] = new
803 self
.qh
:CacheRegister(self
)
807 --[[if pos1 and pos2 then -- Debug code so I can maybe actually fix the problems someday
808 QuestHelper:TextOut("Beginning dumping here")
810 local laxa = QuestHelper:ComputeTravelTime(pos1, e, true)
811 if math.abs(laxa-d) >= 0.0001 then
812 QuestHelper:TextOut(QuestHelper:StringizeTable(pos1))
813 QuestHelper:TextOut(QuestHelper:StringizeRecursive(pos1, 2))
814 QuestHelper:TextOut(QuestHelper:StringizeTable(e))
815 QuestHelper:TextOut(QuestHelper:StringizeTable(e[1]))
816 QuestHelper:TextOut(QuestHelper:StringizeTable(e[2]))
817 QuestHelper:TextOut(QuestHelper:StringizeRecursive(e[1], 2))]]
818 --QuestHelper:Assert(math.abs(laxa-d) < 0.0001, "Compare: "..laxa.." vs "..d) -- wonky commenting is thanks to the de-assert script, fix later
820 local laxb = QuestHelper:ComputeTravelTime(e, pos2, true)
821 if math.abs(laxb-d2) >= 0.0001 then
822 QuestHelper:TextOut(QuestHelper:StringizeTable(pos2))
823 QuestHelper:TextOut(QuestHelper:StringizeTable(e))
824 QuestHelper:TextOut(QuestHelper:StringizeTable(e[1]))
825 QuestHelper:TextOut(QuestHelper:StringizeTable(e[2]))
826 QuestHelper:TextOut(QuestHelper:StringizeRecursive(e[1], 2))]]
827 --QuestHelper:Assert(math.abs(laxa-d) < 0.0001, "Compare: "..laxb.." vs "..d2)
834 local function DoneRouting(self
)
835 assert(self
.setup_count
> 0)
838 if self
.setup_count
== 1 then
840 QuestHelper
:ReleaseObjectivePathingInfo(self
)
841 for i
, obj
in ipairs(self
.qh
.prepared_objectives
) do
843 table.remove(self
.qh
.prepared_objectives
, i
)
848 self
.setup_count
= self
.setup_count
- 1
852 local function IsObjectiveWatched(self
)
853 -- Check if an objective is being watched. Note that this is an external query, not a simple Selector.
856 if self
.cat
== "quest" then
857 info
= QuestHelper
.quest_log
[self
]
859 info
= QuestHelper
.quest_log
[self
.quest
]
863 local index
= info
.index
866 -- UberQuest has it's own way of tracking quests.
867 local uq_settings
= UberQuest_Config
[UnitName("player")]
869 local list
= uq_settings
.selected
871 return list
[GetQuestLogTitle(index
)]
875 return IsQuestWatched(index
)
884 local next_objective_id
= 0
886 local function ObjectiveShare(self
)
887 self
.want_share
= true
890 local function ObjectiveUnshare(self
)
891 self
.want_share
= false
894 QuestHelper
.default_objective_param
=
896 CouldBeFirst
=ObjectiveCouldBeFirst
,
899 DoMarkUsed
=DoMarkUsed
,
901 MarkUnused
=MarkUnused
,
903 DefaultKnown
=DefaultObjectiveKnown
,
904 Known
=DummyObjectiveKnown
,
905 Reason
=ObjectiveReason
,
907 AppendPositions
=ObjectiveAppendPositions
,
908 PrepareRouting
=ObjectivePrepareRouting
,
910 FinishAddLoc
=FinishAddLoc
,
911 DoneRouting
=DoneRouting
,
913 Position
=GetPosition
,
914 TravelTime
=ObjectiveTravelTime
,
915 TravelTime2
=ObjectiveTravelTime2
,
917 IsWatched
=IsObjectiveWatched
,
919 Share
=ObjectiveShare
, -- Invoke to share this objective with your peers.
920 Unshare
=ObjectiveUnshare
, -- Invoke to stop sharing this objective.
923 QuestHelper
.default_objective_item_param
=
926 AppendPositions
= ItemAppendPositions
,
927 DoMarkUsed
= ItemDoMarkUsed
930 for key
, value
in pairs(QuestHelper
.default_objective_param
) do
931 if not QuestHelper
.default_objective_item_param
[key
] then
932 QuestHelper
.default_objective_item_param
[key
] = value
936 QuestHelper
.default_objective_meta
= { __index
= QuestHelper
.default_objective_param
}
937 QuestHelper
.default_objective_item_meta
= { __index
= QuestHelper
.default_objective_item_param
}
939 function QuestHelper
:NewObjectiveObject()
940 next_objective_id
= next_objective_id
+1
944 id
=next_objective_id
,
946 want_share
=false, -- True if we want this objective shared.
947 is_sharing
=false, -- Set to true if we've told other users about this objective.
949 user_ignore
=nil, -- When nil, will use filters. Will ignore, when true, always show (if known).
951 priority
=3, -- A hint as to what priority the quest should have. Should be 1, 2, 3, 4, or 5.
952 real_priority
=3, -- This will be set to the priority routing actually decided to assign it.
963 before
={}, -- List of objectives that this objective must appear before.
964 after
={}, -- List of objectives that this objective must appear after.
966 -- Routing related junk.
968 --[[ Will be created as needed.
971 nm=nil, -- Maps nodes to their nearest zone/list/x/y position.
972 nm2=nil, -- Maps nodes to their nears position, but dynamically set in TravelTime2.
973 nl=nil, -- List of all the nodes we need to consider.
974 location=nil, -- Will be set to the best position for the node.
975 pos=nil, -- Zone node list, distance list, x, y, reason.
977 }, QuestHelper
.default_objective_meta
)
980 function QuestHelper
:GetObjective(category
, objective
)
981 local objective_list
= self
.objective_objects
[category
]
983 if not objective_list
then
985 self
.objective_objects
[category
] = objective_list
988 local objective_object
= objective_list
[objective
]
990 if not objective_object
then
991 if category
== "quest" then
992 local level
, hash
, name
= string.match(objective
, "^(%d+)/(%d*)/(.*)$")
994 level
, name
= string.match(objective
, "^(%d+)/(.*)$")
1000 if hash
== "" then hash
= nil end
1001 objective_object
= self
:GetQuest(name
, tonumber(level
), tonumber(hash
))
1002 objective_list
[objective
] = objective_object
1003 return objective_object
1006 objective_object
= self
:NewObjectiveObject()
1008 objective_object
.cat
= category
1009 objective_object
.obj
= objective
1011 if category
== "item" then
1012 setmetatable(objective_object
, QuestHelper
.default_objective_item_meta
)
1013 objective_object
.icon_id
= 2
1014 elseif category
== "monster" then
1015 objective_object
.icon_id
= 1
1016 elseif category
== "object" then
1017 objective_object
.icon_id
= 3
1018 elseif category
== "event" then
1019 objective_object
.icon_id
= 4
1020 elseif category
== "loc" then
1021 objective_object
.icon_id
= 6
1022 elseif category
== "reputation" then
1023 objective_object
.icon_id
= 5
1025 self
:TextOut("FIXME: Objective type '"..category
.."' for objective '"..objective
.."' isn't explicitly supported yet; hopefully the dummy handler will do something sensible.")
1028 objective_list
[objective
] = objective_object
1030 if category
== "loc" then
1031 -- Loc is special, we don't store it, and construct it from the string.
1032 -- Don't have any error checking here, will assume it's correct.
1034 local _
, _
, c
, z
, x
, y
= string.find(objective
,"^(%d+),(%d+),([%d%.]+),([%d%.]+)$")
1037 _
, _
, i
, x
, y
= string.find(objective
,"^(%d+),([%d%.]+),([%d%.]+)$")
1039 i
= QuestHelper_IndexLookup
[c
][z
]
1042 objective_object
.o
= {pos
={{tonumber(i
),tonumber(x
),tonumber(y
),1}}}
1043 objective_object
.fb
= {}
1045 objective_list
= QuestHelper_Objectives_Local
[category
]
1046 if not objective_list
then
1048 QuestHelper_Objectives_Local
[category
] = objective_list
1050 objective_object
.o
= objective_list
[objective
]
1051 if not objective_object
.o
then
1052 objective_object
.o
= {}
1053 objective_list
[objective
] = objective_object
.o
1055 local l
= QuestHelper_StaticData
[self
.locale
]
1057 objective_list
= l
.objective
[category
]
1058 if objective_list
then
1059 objective_object
.fb
= objective_list
[objective
]
1062 if not objective_object
.fb
then
1063 objective_object
.fb
= {}
1066 -- TODO: If we have some other source of information (like LightHeaded) add its data to objective_object.fb
1071 return objective_object
1074 function QuestHelper
:AppendObjectivePosition(objective
, i
, x
, y
, w
)
1075 local pos
= objective
.o
.pos
1077 if objective
.o
.drop
or objective
.o
.contained
then
1078 return -- If it's dropped by a monster, don't record the position we got the item at.
1080 objective
.o
.pos
= self
:AppendPosition({}, i
, x
, y
, w
)
1082 self
:AppendPosition(pos
, i
, x
, y
, w
)
1086 function QuestHelper
:AppendObjectiveDrop(objective
, monster
, count
)
1087 local drop
= objective
.o
.drop
1089 drop
[monster
] = (drop
[monster
] or 0)+(count
or 1)
1091 objective
.o
.drop
= {[monster
] = count
or 1}
1092 objective
.o
.pos
= nil -- If it's dropped by a monster, then forget the position we found it at.
1096 function QuestHelper
:AppendItemObjectiveDrop(item_object
, item_name
, monster_name
, count
)
1097 local quest
= self
:ItemIsForQuest(item_object
, item_name
)
1098 if quest
and not item_object
.o
.vendor
and not item_object
.o
.drop
and not item_object
.o
.pos
then
1099 self
:AppendQuestDrop(quest
, item_name
, monster_name
, count
)
1101 if not item_object
.o
.drop
and not item_object
.o
.pos
then
1102 self
:PurgeQuestItem(item_object
, item_name
)
1104 self
:AppendObjectiveDrop(item_object
, monster_name
, count
)
1108 function QuestHelper
:AppendItemObjectivePosition(item_object
, item_name
, i
, x
, y
)
1109 local quest
= self
:ItemIsForQuest(item_object
, item_name
)
1110 if quest
and not item_object
.o
.vendor
and not item_object
.o
.drop
and not item_object
.o
.pos
then
1111 self
:AppendQuestPosition(quest
, item_name
, i
, x
, y
)
1113 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
1114 -- Just learned that this item doesn't depend on a quest to drop, remove any quest references to it.
1115 self
:PurgeQuestItem(item_object
, item_name
)
1117 self
:AppendObjectivePosition(item_object
, i
, x
, y
)
1121 function QuestHelper
:AppendItemObjectiveContainer(objective
, container_name
, count
)
1122 local container
= objective
.o
.contained
1124 container
[container_name
] = (container
[container_name
] or 0)+(count
or 1)
1126 objective
.o
.contained
= {[container_name
] = count
or 1}
1127 objective
.o
.pos
= nil -- Forget the position.
1131 function QuestHelper
:AddObjectiveWatch(objective
, reason
)
1132 if not objective
.reasons
then
1133 objective
.reasons
= {}
1136 if not next(objective
.reasons
, nil) then
1137 objective
.watched
= true
1138 objective
:MarkUsed()
1140 objective
.filter_blocked
= false
1141 for obj
in pairs(objective
.swap_after
or objective
.after
) do
1143 objective
.filter_blocked
= true
1148 for obj
in pairs(objective
.swap_before
or objective
.before
) do
1150 obj
.filter_blocked
= true
1154 if self
.to_remove
[objective
] then
1155 self
.to_remove
[objective
] = nil
1157 self
.to_add
[objective
] = true
1161 objective
.reasons
[reason
] = (objective
.reasons
[reason
] or 0) + 1
1164 function QuestHelper
:RemoveObjectiveWatch(objective
, reason
)
1165 if objective
.reasons
[reason
] == 1 then
1166 objective
.reasons
[reason
] = nil
1167 if not next(objective
.reasons
, nil) then
1168 objective
:MarkUnused()
1169 objective
.watched
= false
1171 for obj
in pairs(objective
.swap_before
or objective
.before
) do
1173 obj
.filter_blocked
= false
1174 for obj2
in pairs(obj
.swap_after
or obj
.after
) do
1175 if obj2
.watched
then
1176 obj
.filter_blocked
= true
1183 if self
.to_add
[objective
] then
1184 self
.to_add
[objective
] = nil
1186 self
.to_remove
[objective
] = true
1190 objective
.reasons
[reason
] = objective
.reasons
[reason
] - 1
1194 function QuestHelper
:ObjectiveObjectDependsOn(objective
, needs
)
1195 assert(objective
~= needs
) -- If this was true, ObjectiveIsKnown would get in an infinite loop.
1196 -- TODO: Needs sanity checking, especially now that dependencies can be assigned by remote users.
1199 -- We store the new relationships in objective.swap_[before|after],
1200 -- creating and copying them from objective.[before|after],
1201 -- the routing coroutine will check for those, swap them, and release the originals
1202 -- when it gets to a safe place to do so.
1204 if not (objective
.swap_after
or objective
.after
)[needs
] then
1205 if objective
.peer
then
1206 for u
, l
in pairs(objective
.peer
) do
1207 -- Make sure other users know that the dependencies for this objective changed.
1208 objective
.peer
[u
] = math
.min(l
, 1)
1212 if not objective
.swap_after
then
1213 objective
.swap_after
= self
:CreateTable("swap_after")
1214 for key
,value
in pairs(objective
.after
) do objective
.swap_after
[key
] = value
end
1217 if not needs
.swap_before
then
1218 needs
.swap_before
= self
:CreateTable("swap_before")
1219 for key
,value
in pairs(needs
.before
) do needs
.swap_before
[key
] = value
end
1222 if needs
.watched
then
1223 objective
.filter_blocked
= true
1226 objective
.swap_after
[needs
] = true
1227 needs
.swap_before
[objective
] = true
1231 function QuestHelper
:AddObjectiveOptionsToMenu(obj
, menu
)
1232 local submenu
= self
:CreateMenu()
1235 local name
= QHText("PRIORITY"..i
)
1236 local item
= self
:CreateMenuItem(submenu
, name
)
1239 if obj
.priority
== i
then
1240 tex
= self
:CreateIconTexture(item
, 10)
1241 elseif obj
.real_priority
== i
then
1242 tex
= self
:CreateIconTexture(item
, 8)
1244 tex
= self
:CreateIconTexture(item
, 12)
1245 tex
:SetVertexColor(1, 1, 1, 0)
1248 item
:AddTexture(tex
, true)
1249 item
:SetFunction(self
.SetObjectivePriorityPrompt
, self
, obj
, i
)
1252 self
:CreateMenuItem(menu
, QHText("PRIORITY")):SetSubmenu(submenu
)
1254 if self
.sharing
then
1255 submenu
= self
:CreateMenu()
1256 local item
= self
:CreateMenuItem(submenu
, QHText("SHARING_ENABLE"))
1257 local tex
= self
:CreateIconTexture(item
, 10)
1258 if not obj
.want_share
then tex
:SetVertexColor(1, 1, 1, 0) end
1259 item
:AddTexture(tex
, true)
1260 item
:SetFunction(obj
.Share
, obj
)
1262 local item
= self
:CreateMenuItem(submenu
, QHText("SHARING_DISABLE"))
1263 local tex
= self
:CreateIconTexture(item
, 10)
1264 if obj
.want_share
then tex
:SetVertexColor(1, 1, 1, 0) end
1265 item
:AddTexture(tex
, true)
1266 item
:SetFunction(obj
.Unshare
, obj
)
1268 self
:CreateMenuItem(menu
, QHText("SHARING")):SetSubmenu(submenu
)
1271 self
:CreateMenuItem(menu
, QHText("IGNORE")):SetFunction(self
.IgnoreObjective
, self
, obj
)
1274 function QuestHelper
:IgnoreObjective(objective
)
1275 if self
.user_objectives
[objective
] then
1276 self
:RemoveObjectiveWatch(objective
, self
.user_objectives
[objective
])
1277 self
.user_objectives
[objective
] = nil
1279 objective
.user_ignore
= true
1282 --self:ForceRouteUpdate()
1285 function QuestHelper
:SetObjectivePriority(objective
, level
)
1286 level
= math
.min(5, math
.max(1, math
.floor((tonumber(level
) or 3)+0.5)))
1287 if level
~= objective
.priority
then
1288 objective
.priority
= level
1289 if objective
.peer
then
1290 for u
, l
in pairs(objective
.peer
) do
1291 -- Peers don't know about this new priority.
1292 objective
.peer
[u
] = math
.min(l
, 2)
1295 --self:ForceRouteUpdate()
1299 local function CalcObjectivePriority(obj
)
1300 local priority
= obj
.priority
1302 for o
in pairs(obj
.before
) do
1304 priority
= math
.min(priority
, CalcObjectivePriority(o
))
1311 local function ApplyBlockPriority(obj
, level
)
1312 for o
in pairs(obj
.before
) do
1314 ApplyBlockPriority(o
, level
)
1318 if obj
.priority
< level
then QuestHelper
:SetObjectivePriority(obj
, level
) end
1321 function QuestHelper
:SetObjectivePriorityPrompt(objective
, level
)
1322 self
:SetObjectivePriority(objective
, level
)
1323 if CalcObjectivePriority(objective
) ~= level
then
1324 local menu
= self
:CreateMenu()
1325 self
:CreateMenuTitle(menu
, QHText("IGNORED_PRIORITY_TITLE"))
1326 self
:CreateMenuItem(menu
, QHText("IGNORED_PRIORITY_FIX")):SetFunction(ApplyBlockPriority
, objective
, level
)
1327 self
:CreateMenuItem(menu
, QHText("IGNORED_PRIORITY_IGNORE")):SetFunction(self
.nop
)
1332 function QuestHelper
:SetObjectiveProgress(objective
, user
, have
, need
)
1333 if have
and need
then
1334 local list
= objective
.progress
1336 list
= self
:CreateTable("objective.progress")
1337 objective
.progress
= list
1340 local user_progress
= list
[user
]
1341 if not user_progress
then
1342 user_progress
= self
:CreateTable("objective.progress[user]")
1343 list
[user
] = user_progress
1347 local a
, b
= tonumber(have
), tonumber(need
)
1358 user_progress
[1], user_progress
[2], user_progress
[3] = have
, need
, pct
1360 if objective
.progress
then
1361 if objective
.progress
[user
] then
1362 self
:ReleaseTable(objective
.progress
[user
])
1363 objective
.progress
[user
] = nil
1365 if not next(objective
.progress
, nil) then
1366 self
:ReleaseTable(objective
.progress
)
1367 objective
.progress
= nil