Added changes made so far to changes.txt.
[QuestHelper.git] / objective.lua
blobe5239d6a7a61c0cbc1df5f1df66e717cfdeb0c56
1 local function ObjectiveCouldBeFirst(self)
2 if (self.user_ignore == nil and self.auto_ignore) or self.user_ignore then
3 return false
4 end
6 for i, j in pairs(self.after) do
7 if i.watched then
8 return false
9 end
10 end
12 return true
13 end
15 local function DefaultObjectiveKnown(self)
16 if self.user_ignore == nil then
17 if (self.filter_zone and QuestHelper_Pref.filter_zone) or
18 (self.filter_done and QuestHelper_Pref.filter_done) or
19 (self.filter_level and QuestHelper_Pref.filter_level) then
20 return false
21 end
22 elseif self.user_ignore then
23 return false
24 end
26 for i, j in pairs(self.after) do
27 if i.watched and not i:Known() then -- Need to know how to do everything before this objective.
28 return false
29 end
30 end
32 return true
33 end
35 local function ObjectiveReason(self, short)
36 local reason, rc = nil, 0
37 if self.reasons then
38 for r, c in pairs(self.reasons) do
39 if not reason or c > rc or (c == rc and r > reason) then
40 reason, rc = r, c
41 end
42 end
43 end
45 if not reason then reason = "Do some extremely secret unspecified something." end
47 if not short and self.pos and self.pos[6] then
48 reason = reason .. "\n" .. self.pos[6]
49 end
51 return reason
52 end
54 local function DummyObjectiveKnown(self)
55 return (self.o.pos or self.fb.pos) and DefaultObjectiveKnown(self)
56 end
58 local function ItemKnown(self)
59 if not DefaultObjectiveKnown(self) then return false end
61 if self.o.vendor then
62 for i, npc in ipairs(self.o.vendor) do
63 local n = self.qh:GetObjective("monster", npc)
64 local faction = n.o.faction or n.fb.faction
65 if (not faction or faction == self.qh.faction) and n:Known() then
66 return true
67 end
68 end
69 end
71 if self.fb.vendor then
72 for i, npc in ipairs(self.fb.vendor) do
73 local n = self.qh:GetObjective("monster", npc)
74 local faction = n.o.faction or n.fb.faction
75 if (not faction or faction == self.qh.faction) and n:Known() then
76 return true
77 end
78 end
79 end
81 if self.o.pos or self.fb.pos then
82 return true
83 end
85 if self.o.drop then for monster in pairs(self.o.drop) do
86 if self.qh:GetObjective("monster", monster):Known() then
87 return true
88 end
89 end end
91 if self.fb.drop then for monster in pairs(self.fb.drop) do
92 if self.qh:GetObjective("monster", monster):Known() then
93 return true
94 end
95 end end
97 if self.o.contained then for item in pairs(self.o.contained) do
98 if self.qh:GetObjective("item", item):Known() then
99 return true
101 end end
103 if self.fb.contained then for item in pairs(self.fb.contained) do
104 if self.qh:GetObjective("item", item):Known() then
105 return true
107 end end
109 if self.quest then
110 local item=self.quest.o.item
111 item = item and item[self.obj]
113 if item then
114 if item.pos then
115 return true
117 if item.drop then
118 for monster in pairs(item.drop) do
119 if self.qh:GetObjective("monster", monster):Known() then
120 return true
126 item=self.quest.fb.item
127 item = item and item[self.obj]
128 if item then
129 if item.pos then
130 return true
132 if item.drop then
133 for monster in pairs(item.drop) do
134 if self.qh:GetObjective("monster", monster):Known() then
135 return true
142 return false
145 local function ObjectiveAppendPositions(self, objective, weight, why)
146 local high = 0
148 if self.o.pos then for i, p in ipairs(self.o.pos) do
149 high = math.max(high, p[4])
150 end end
152 if self.fb.pos then for i, p in ipairs(self.fb.pos) do
153 high = math.max(high, p[4])
154 end end
156 high = weight/high
158 if self.o.pos then for i, p in ipairs(self.o.pos) do
159 objective:AddLoc(p[1], p[2], p[3], p[4]*high, why)
160 end end
162 if self.fb.pos then for i, p in ipairs(self.fb.pos) do
163 objective:AddLoc(p[1], p[2], p[3], p[4]*high, why)
164 end end
168 local function ObjectivePrepareRouting(self)
169 self.setup_count = self.setup_count + 1
170 if not self.setup then
171 assert(not self.d)
172 assert(not self.p)
173 assert(not self.nm)
174 assert(not self.nm2)
175 assert(not self.nl)
177 self.d = QuestHelper:CreateTable()
178 self.p = QuestHelper:CreateTable()
179 self.nm = QuestHelper:CreateTable()
180 self.nm2 = QuestHelper:CreateTable()
181 self.nl = QuestHelper:CreateTable()
183 self:AppendPositions(self, 1, nil)
184 self:FinishAddLoc()
188 local function ItemAppendPositions(self, objective, weight, why)
189 why2 = why and why.."\n" or ""
191 if self.o.vendor then for i, npc in ipairs(self.o.vendor) do
192 local n = self.qh:GetObjective("monster", npc)
193 local faction = n.o.faction or n.fb.faction
194 if (not faction or faction == self.qh.faction) then
195 n:AppendPositions(objective, 1, why2..QHFormat("OBJECTIVE_PURCHASE", npc))
197 end end
199 if self.fb.vendor then for i, npc in ipairs(self.fb.vendor) do
200 local n = self.qh:GetObjective("monster", npc)
201 local faction = n.o.faction or n.fb.faction
202 if (not faction or faction == self.qh.faction) then
203 n:AppendPositions(objective, 1, why2..QHFormat("OBJECTIVE_PURCHASE", npc))
205 end end
207 if next(objective.p, nil) then
208 -- If we have points from vendors, then always use vendors. I don't want it telling you to killing the
209 -- towns people just because you had to talk to them anyway, and it saves walking to the store.
210 return
213 if self.o.drop then for monster, count in pairs(self.o.drop) do
214 local m = self.qh:GetObjective("monster", monster)
215 m:AppendPositions(objective, m.o.looted and count/m.o.looted or 1, why2..QHFormat("OBJECTIVE_SLAY", monster))
216 end end
218 if self.fb.drop then for monster, count in pairs(self.fb.drop) do
219 local m = self.qh:GetObjective("monster", monster)
220 m:AppendPositions(objective, m.fb.looted and count/m.fb.looted or 1, why2..QHFormat("OBJECTIVE_SLAY", monster))
221 end end
223 if self.o.contained then for item, count in pairs(self.o.contained) do
224 local i = self.qh:GetObjective("item", item)
225 i:AppendPositions(objective, i.o.opened and count/i.o.opened or 1, why2..QHFormat("OBJECTIVE_LOOT", item))
226 end end
228 if self.fb.contained then for item, count in pairs(self.fb.contained) do
229 local i = self.qh:GetObjective("item", item)
230 i:AppendPositions(objective, i.fb.opened and count/i.fb.opened or 1, why2..QHFormat("OBJECTIVE_LOOT", item))
231 end end
233 if self.o.pos then for i, p in ipairs(self.o.pos) do
234 objective:AddLoc(p[1], p[2], p[3], p[4], why)
235 end end
237 if self.fb.pos then for i, p in ipairs(self.fb.pos) do
238 objective:AddLoc(p[1], p[2], p[3], p[4], why)
239 end end
241 if self.quest then
242 local item_list=self.quest.o.item
243 if item_list then
244 local data = item_list[self.obj]
245 if data and data.drop then
246 for monster, count in pairs(data.drop) do
247 local m = self.qh:GetObjective("monster", monster)
248 m:AppendPositions(objective, m.o.looted and count/m.o.looted or 1, why2..QHFormat("OBJECTIVE_SLAY", monster))
250 elseif data and data.pos then
251 for i, p in ipairs(data.pos) do
252 objective:AddLoc(p[1], p[2], p[3], p[4], why)
257 item_list=self.quest.fb.item
258 if item_list then
259 local data = item_list[self.obj]
260 if data and data.drop then
261 for monster, count in pairs(data.drop) do
262 local m = self.qh:GetObjective("monster", monster)
263 m:AppendPositions(objective, m.fb.looted and count/m.fb.looted or 1, why2..QHFormat("OBJECTIVE_SLAY", monster))
265 elseif data and data.pos then
266 for i, p in ipairs(data.pos) do
267 objective:AddLoc(p[1], p[2], p[3], p[4], why)
285 ---------------
299 local function AddLoc(self, index, x, y, w, why)
300 assert(not self.setup)
302 if w > 0 then
303 local pair = QuestHelper_ZoneLookup[index]
304 local c, z = pair[1], pair[2]
305 x, y = self.qh.Astrolabe:TranslateWorldMapPosition(c, z, x, y, c, 0)
307 x = x * self.qh.continent_scales_x[c]
308 y = y * self.qh.continent_scales_y[c]
309 local list = self.qh.zone_nodes[index]
311 local points = self.p[list]
312 if not points then
313 points = QuestHelper:CreateTable()
314 self.p[list] = points
317 for i, p in pairs(points) do
318 local u, v = x-p[3], y-p[4]
319 if u*u+v*v < 25 then -- Combine points within a threshold of 5 seconds travel time.
320 p[3] = (p[3]*p[5]+x*w)/(p[5]+w)
321 p[4] = (p[4]*p[5]+y*w)/(p[5]+w)
322 p[5] = p[5]+w
323 if w > p[7] then
324 p[6], p[7] = why, w
326 return
330 local new = QuestHelper:CreateTable()
331 new[1], new[2], new[3], new[4], new[5], new[6], new[7] = list, nil, x, y, w, why, w
332 table.insert(points, new)
336 local function FinishAddLoc(self)
337 local mx = 0
339 for z, pl in pairs(self.p) do
340 for i, p in ipairs(pl) do
341 if p[5] > mx then
342 self.location = p
343 mx = p[5]
348 -- Remove probably useless locations.
349 for z, pl in pairs(self.p) do
350 local remove_zone = true
351 local i = 1
352 while i <= #pl do
353 if pl[i][5] < mx*0.2 then
354 QuestHelper:ReleaseTable(pl[i])
355 table.remove(pl, i)
356 else
357 remove_zone = false
358 i = i + 1
361 if remove_zone then
362 QuestHelper:ReleaseTable(self.p[z])
363 self.p[z] = nil
367 local node_map = self.nm
368 local node_list = self.nl
370 for list, pl in pairs(self.p) do
371 local dist = self.d[list]
373 assert(not dist)
375 if not dist then
376 dist = QuestHelper:CreateTable()
377 self.d[list] = dist
380 for i, point in ipairs(pl) do
381 point[5] = mx/point[5] -- Will become 1 for the most desired location, and become larger and larger for less desireable locations.
383 point[2] = QuestHelper:CreateTable()
385 for i, node in ipairs(list) do
386 local u, v = point[3]-node.x, point[4]-node.y
387 local d = math.sqrt(u*u+v*v)
389 point[2][i] = d
391 if dist[i] then
392 if d*point[5] < dist[i][1]*dist[i][2] then
393 dist[i][1], dist[i][2] = d, point[5]
394 node_map[node] = point
396 else
397 local pair = QuestHelper:CreateTable()
398 pair[1], pair[2] = d, point[5]
399 dist[i] = pair
401 if not node_map[node] then
402 table.insert(node_list, node)
403 node_map[node] = point
404 else
405 u, v = node_map[node][3]-node.x, node_map[node][4]-node.y
407 if dist[i][1]*dist[i][2] < math.sqrt(u*u+v*v)*node_map[node][5] then
408 node_map[node] = point
416 if #node_list == 0 then QuestHelper:Error(self.cat.."/"..self.obj..": zero nodes!") end
418 assert(not self.setup)
419 self.setup = true
420 table.insert(self.qh.prepared_objectives, self)
423 local function GetPosition(self)
424 assert(self.setup)
426 return self.location
429 local function ComputeTravelTime(self, pos)
430 assert(self.setup)
432 local graph = self.qh.world_graph
433 local nl = self.nl
435 graph:PrepareSearch()
437 for z, l in pairs(self.d) do
438 for i, n in ipairs(z) do
439 if n.s == 0 then
440 n.e, n.w = unpack(l[i])
441 n.s = 3
442 elseif n.e * n.w < l[i][1]*l[i][2] then
443 n.e, n.w = unpack(l[i])
448 local d = pos[2]
449 for i, n in ipairs(pos[1]) do
450 graph:AddStartNode(n, d[i], nl)
453 local e = graph:DoSearch(nl)
455 d = e.g+e.e
456 e = self.nm[e]
458 local l = self.p[pos[1]]
459 if l then
460 local x, y = pos[3], pos[4]
461 local score = d*e[5]
463 for i, n in ipairs(l) do
464 local u, v = x-n[3], y-n[4]
465 local d2 = math.sqrt(u*u+v*v)
466 local s = d2*n[5]
467 if s < score then
468 d, e, score = d2, n, s
473 assert(e)
474 return d, e
477 local function ComputeTravelTime2(self, pos1, pos2)
478 assert(self.setup)
480 local graph = self.qh.world_graph
481 local nl = self.nl
483 graph:PrepareSearch()
485 for z, l in pairs(self.d) do
486 for i, n in ipairs(z) do
487 if n.s == 0 then
488 n.e, n.w = unpack(l[i])
489 n.s = 3
490 elseif n.e * n.w < l[i][1]*l[i][2] then
491 n.e, n.w = unpack(l[i])
496 local d = pos1[2]
497 for i, n in ipairs(pos1[1]) do
498 graph:AddStartNode(n, d[i], nl)
501 graph:DoFullSearch(nl)
503 graph:PrepareSearch()
505 -- Now, we need to figure out how long it takes to get to each node.
506 for z, point_list in pairs(self.p) do
507 if z == pos1[1] then
508 -- Will also consider min distance.
509 local x, y = pos1[3], pos1[4]
511 for i, p in ipairs(point_list) do
512 local a, b = p[3]-x, p[4]-y
513 local u, v = p[3], p[4]
514 local d = math.sqrt(a*a+b*b)
515 local w = p[5]
516 local score = d*w
517 for i, n in ipairs(z) do
518 a, b = n.x-u, n.y-v
519 local bleh = math.sqrt(a*a+b*b)+n.g
520 local s = bleh*w
521 if s < score then
522 d, score = bleh, d
525 p[7] = d
527 else
528 for i, p in ipairs(point_list) do
529 local x, y = p[3], p[4]
530 local w = p[5]
531 local d
532 local score
534 for i, n in ipairs(z) do
535 local a, b = n.x-x, n.y-y
536 local d2 = math.sqrt(a*a+b*b)+n.g
537 local s = d2*w
538 if not score or s < score then
539 d, score = d2, s
542 p[7] = d
547 d = pos2[2]
549 for i, n in ipairs(pos2[1]) do
550 n.e = d[i]
551 n.s = 3
554 local el = pos2[1]
555 local nm = self.nm2
557 for z, l in pairs(self.d) do
558 for i, n in ipairs(z) do
559 local x, y = n.x, n.y
560 local bp
561 local bg
562 local bs
563 for i, p in ipairs(self.p[z]) do
564 local a, b = x-p[3], y-p[4]
565 d = p[7]+math.sqrt(a*a+b*b)
566 s = d*p[5]
567 if not bs or s < bs then
568 bg, bp, bs = d, p, s
572 nm[n] = bp
573 -- Using score instead of distance, because we want nodes we're not really interested in to be less likely to get chosen.
574 graph:AddStartNode(n, bs, el)
578 local e = graph:DoSearch(pos2[1])
580 d = nm[e.p][7]
581 local d2 = e.g+e.e-e.p.g+(e.p.g/nm[e.p][5]-nm[e.p][7])
583 e = nm[e.p]
584 local total = (d+d2)*e[5]
586 if self.p[el] then
587 local x, y = pos2[3], pos2[4]
588 for i, p in ipairs(self.p[el]) do
589 local a, b = x-p[3], y-p[4]
590 local c = math.sqrt(a*a+b*b)
591 local t = (p[7]+c)*p[5]
592 if t < total then
593 total, d, d2, e = t, p[7], c, p
598 assert(e)
599 return d, d2, e
602 local function DoneRouting(self)
603 assert(self.setup_count > 0)
604 assert(self.setup)
606 if self.setup_count == 1 then
607 self.setup_count = 0
608 QuestHelper:ReleaseObjectivePathingInfo(self)
609 for i, obj in ipairs(self.qh.prepared_objectives) do
610 if o == obj then
611 table.remove(self.qh.prepared_objectives, i)
612 break
615 else
616 self.setup_count = self.setup_count - 1
620 local next_objective_id = 0
622 local function ObjectiveShare(self)
623 self.want_share = true
626 local function ObjectiveUnshare(self)
627 self.want_share = false
630 QuestHelper.default_objective_param =
632 CouldBeFirst=ObjectiveCouldBeFirst,
634 DefaultKnown=DefaultObjectiveKnown,
635 Known=DummyObjectiveKnown,
636 Reason=ObjectiveReason,
638 AppendPositions=ObjectiveAppendPositions,
639 PrepareRouting=ObjectivePrepareRouting,
640 AddLoc=AddLoc,
641 FinishAddLoc=FinishAddLoc,
642 DoneRouting=DoneRouting,
644 Position=GetPosition,
645 TravelTime=ComputeTravelTime,
646 TravelTime2=ComputeTravelTime2,
648 Share=ObjectiveShare, -- Invoke to share this objective with your peers.
649 Unshare=ObjectiveUnshare, -- Invoke to stop sharing this objective.
652 QuestHelper.default_objective_item_param =
654 Known = ItemKnown,
655 AppendPositions = ItemAppendPositions
658 for key, value in pairs(QuestHelper.default_objective_param) do
659 if not QuestHelper.default_objective_item_param[key] then
660 QuestHelper.default_objective_item_param[key] = value
664 QuestHelper.default_objective_meta = { __index = QuestHelper.default_objective_param }
665 QuestHelper.default_objective_item_meta = { __index = QuestHelper.default_objective_item_param }
667 function QuestHelper:NewObjectiveObject()
668 next_objective_id = next_objective_id+1
669 return
670 setmetatable({
671 qh=self,
672 id=next_objective_id,
674 want_share=false, -- True if we want this objective shared.
675 is_sharing=false, -- Set to true if we've told other users about this objective.
677 user_ignore=nil, -- When nil, will use filters. Will ignore, when true, always show (if known).
679 priority=3, -- A hint as to what priority the quest should have. Should be 1, 2, 3, 4, or 5.
680 real_priority=3, -- This will be set to the priority routing actually decided to assign it.
682 setup_count=0,
684 icon_id=12,
685 icon_bg=14,
687 match_zone=false,
688 match_level=false,
689 match_done=false,
691 before={}, -- List of objectives that this objective must appear before.
692 after={}, -- List of objectives that this objective must appear after.
694 -- Routing related junk.
696 --[[ Will be created as needed.
697 d=nil,
698 p=nil,
699 nm=nil, -- Maps nodes to their nearest zone/list/x/y position.
700 nm2=nil, -- Maps nodes to their nears position, but dynamically set in TravelTime2.
701 nl=nil, -- List of all the nodes we need to consider.
702 location=nil, -- Will be set to the best position for the node.
703 pos=nil, -- Zone node list, distance list, x, y, reason.
704 sop=nil ]]
705 }, QuestHelper.default_objective_meta)
708 function QuestHelper:GetObjective(category, objective)
709 local objective_list = self.objective_objects[category]
711 if not objective_list then
712 objective_list = {}
713 self.objective_objects[category] = objective_list
716 local objective_object = objective_list[objective]
718 if not objective_object then
719 if category == "quest" then
720 local _, _, level, hash, name = string.find(objective, "^(%d+)/(%d*)/(.*)$")
721 if not level then
722 _, _, level, name = string.find(objective, "^(%d+)/(.*)$")
723 if not level then
724 name = objective
728 if hash == "" then hash = nil end
729 objective_object = self:GetQuest(name, tonumber(level), tonumber(hash))
730 objective_list[objective] = objective_object
731 return objective_object
734 objective_object = self:NewObjectiveObject()
736 objective_object.cat = category
737 objective_object.obj = objective
739 if category == "item" then
740 setmetatable(objective_object, QuestHelper.default_objective_item_meta)
741 objective_object.icon_id = 2
742 elseif category == "monster" then
743 objective_object.icon_id = 1
744 elseif category == "object" then
745 objective_object.icon_id = 3
746 elseif category == "event" then
747 objective_object.icon_id = 4
748 elseif category == "loc" then
749 objective_object.icon_id = 6
750 elseif category == "reputation" then
751 objective_object.icon_id = 5
752 else
753 self:TextOut("FIXME: Objective type '"..category.."' for objective '"..objective.."' isn't explicitly supported yet; hopefully the dummy handler will do something sensible.")
756 objective_list[objective] = objective_object
758 if category == "loc" then
759 -- Loc is special, we don't store it, and construct it from the string.
760 -- Don't have any error checking here, will assume it's correct.
761 local i
762 local _, _, c, z, x, y = string.find(objective,"^(%d+),(%d+),([%d%.]+),([%d%.]+)$")
764 if not y then
765 _, _, i, x, y = string.find(objective,"^(%d+),([%d%.]+),([%d%.]+)$")
766 else
767 i = QuestHelper_IndexLookup[c][z]
770 objective_object.o = {pos={{tonumber(i),tonumber(x),tonumber(y),1}}}
771 objective_object.fb = {}
772 else
773 objective_list = QuestHelper_Objectives[category]
774 if not objective_list then
775 objective_list = {}
776 QuestHelper_Objectives[category] = objective_list
778 objective_object.o = objective_list[objective]
779 if not objective_object.o then
780 objective_object.o = {}
781 objective_list[objective] = objective_object.o
783 local l = QuestHelper_StaticData[self.locale]
784 if l then
785 objective_list = l.objective[category]
786 if objective_list then
787 objective_object.fb = objective_list[objective]
790 if not objective_object.fb then
791 objective_object.fb = {}
794 -- TODO: If we have some other source of information (like LightHeaded) add its data to objective_object.fb
799 return objective_object
802 function QuestHelper:AppendObjectivePosition(objective, i, x, y, w)
803 local pos = objective.o.pos
804 if not pos then
805 if objective.o.drop or objective.o.contained then
806 return -- If it's dropped by a monster, don't record the position we got the item at.
808 objective.o.pos = self:AppendPosition({}, i, x, y, w)
809 else
810 self:AppendPosition(pos, i, x, y, w)
814 function QuestHelper:AppendObjectiveDrop(objective, monster, count)
815 local drop = objective.o.drop
816 if drop then
817 drop[monster] = (drop[monster] or 0)+(count or 1)
818 else
819 objective.o.drop = {[monster] = count or 1}
820 objective.o.pos = nil -- If it's dropped by a monster, then forget the position we found it at.
824 function QuestHelper:AppendItemObjectiveDrop(item_object, item_name, monster_name, count)
825 local quest = self:ItemIsForQuest(item_object, item_name)
826 if quest and not item_object.o.vendor and not item_object.o.drop and not item_object.o.pos then
827 self:AppendQuestDrop(quest, item_name, monster_name, count)
828 else
829 if not item_object.o.drop and not item_object.o.pos then
830 self:PurgeQuestItem(item_object, item_name)
832 self:AppendObjectiveDrop(item_object, monster_name, count)
836 function QuestHelper:AppendItemObjectivePosition(item_object, item_name, i, x, y)
837 local quest = self:ItemIsForQuest(item_object, item_name)
838 if quest and not item_object.o.vendor and not item_object.o.drop and not item_object.o.pos then
839 self:AppendQuestPosition(quest, item_name, i, x, y)
840 else
841 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
842 -- Just learned that this item doesn't depend on a quest to drop, remove any quest references to it.
843 self:PurgeQuestItem(item_object, item_name)
845 self:AppendObjectivePosition(item_object, i, x, y)
849 function QuestHelper:AppendItemObjectiveContainer(objective, container_name, count)
850 local container = objective.o.contained
851 if container then
852 container[container_name] = (container[container_name] or 0)+(count or 1)
853 else
854 objective.o.contained = {[container_name] = count or 1}
855 objective.o.pos = nil -- Forget the position.
859 function QuestHelper:AddObjectiveWatch(objective, reason)
860 if not objective.reasons then
861 objective.reasons = {}
864 if not next(objective.reasons, nil) then
865 objective.watched = true
866 if self.to_remove[objective] then
867 self.to_remove[objective] = nil
868 else
869 self.to_add[objective] = true
873 objective.reasons[reason] = (objective.reasons[reason] or 0) + 1
876 function QuestHelper:RemoveObjectiveWatch(objective, reason)
877 if objective.reasons[reason] == 1 then
878 objective.reasons[reason] = nil
879 if not next(objective.reasons, nil) then
880 objective.watched = false
881 if self.to_add[objective] then
882 self.to_add[objective] = nil
883 else
884 self.to_remove[objective] = true
887 else
888 objective.reasons[reason] = objective.reasons[reason] - 1
892 function QuestHelper:ObjectiveObjectDependsOn(objective, needs)
893 assert(objective ~= needs) -- If this was true, ObjectiveIsKnown would get in an infinite loop.
894 -- TODO: Needs sanity checking, especially now that dependencies can be assigned by remote users.
896 if not objective.after[needs] then
897 if objective.peer then
898 for u, l in pairs(objective.peer) do
899 -- Make sure other users know that the dependencies for this objective changed.
900 objective.peer[u] = math.min(l, 1)
903 objective.after[needs] = true
904 needs.before[objective] = true
908 function QuestHelper:AddObjectiveOptionsToMenu(obj, menu)
909 local submenu = self:CreateMenu()
911 for i = 1,5 do
912 local name = QHText("PRIORITY"..i)
913 local item = self:CreateMenuItem(submenu, name)
914 local tex
916 if obj.priority == i then
917 tex = self:CreateIconTexture(item, 10)
918 elseif obj.real_priority == i then
919 tex = self:CreateIconTexture(item, 8)
920 else
921 tex = self:CreateIconTexture(item, 12)
922 tex:SetVertexColor(1, 1, 1, 0)
925 item:AddTexture(tex, true)
926 item:SetFunction(self.SetObjectivePriorityPrompt, self, obj, i)
929 self:CreateMenuItem(menu, QHText("PRIORITY")):SetSubmenu(submenu)
931 if self.sharing then
932 submenu = self:CreateMenu(QHText("SHARING"))
933 local item = self:CreateMenuItem(submenu, QHText("ENABLE"))
934 local tex = self:CreateIconTexture(item, 10)
935 if not obj.want_share then tex:SetVertexColor(1, 1, 1, 0) end
936 item:AddTexture(tex, true)
937 item:SetFunction(obj.Share, obj)
939 local item = self:CreateMenuItem(submenu, QHText("DISABLE"))
940 local tex = self:CreateIconTexture(item, 10)
941 if obj.want_share then tex:SetVertexColor(1, 1, 1, 0) end
942 item:AddTexture(tex, true)
943 item:SetFunction(obj.Unshare, obj)
945 self:CreateMenuItem(menu, QHText("SHARING")):SetSubmenu(submenu)
948 self:CreateMenuItem(menu, QHText("IGNORE")):SetFunction(self.IgnoreObjective, self, obj)
951 function QuestHelper:IgnoreObjective(objective)
952 if self.user_objectives[objective] then
953 self:RemoveObjectiveWatch(objective, self.user_objectives[objective])
954 self.user_objectives[objective] = nil
955 else
956 objective.user_ignore = true
959 --self:ForceRouteUpdate()
962 function QuestHelper:SetObjectivePriority(objective, level)
963 level = math.min(5, math.max(1, math.floor((tonumber(level) or 3)+0.5)))
964 if level ~= objective.priority then
965 objective.priority = level
966 if objective.peer then
967 for u, l in pairs(objective.peer) do
968 -- Peers don't know about this new priority.
969 objective.peer[u] = math.min(l, 2)
972 --self:ForceRouteUpdate()
976 local function CalcObjectivePriority(obj)
977 local priority = obj.priority
979 for o in pairs(obj.before) do
980 if o.watched then
981 priority = math.min(priority, CalcObjectivePriority(o))
985 return priority
988 local function ApplyBlockPriority(obj, level)
989 for o in pairs(obj.before) do
990 if o.watched then
991 ApplyBlockPriority(o, level)
995 if obj.priority < level then QuestHelper:SetObjectivePriority(obj, level) end
998 function QuestHelper:SetObjectivePriorityPrompt(objective, level)
999 self:SetObjectivePriority(objective, level)
1000 if CalcObjectivePriority(objective) ~= level then
1001 local menu = self:CreateMenu()
1002 self:CreateMenuTitle(menu, QHText("IGNORED_PRIORITY_TITLE"))
1003 self:CreateMenuItem(menu, QHText("IGNORED_PRIORITY_FIX")):SetFunction(ApplyBlockPriority, objective, level)
1004 self:CreateMenuItem(menu, QHText("IGNORED_PRIORITY_IGNORE")):SetFunction(self.nop)
1005 menu:ShowAtCursor()
1009 function QuestHelper:SetObjectiveProgress(objective, user, have, need)
1010 if have and need then
1011 local list = objective.progress
1012 if not list then
1013 list = self:CreateTable()
1014 objective.progress = list
1017 local user_progress = list[user]
1018 if not user_progress then
1019 user_progress = self:CreateTable()
1020 list[user] = user_progress
1023 local pct = 0
1024 local a, b = tonumber(have), tonumber(need)
1025 if a and b then
1026 if b ~= 0 then
1027 pct = a/b
1028 elseif a == 0 then
1029 pct = 1
1031 elseif a == b then
1032 pct = 1
1035 user_progress[1], user_progress[2], user_progress[3] = have, need, pct
1036 else
1037 if objective.progress then
1038 if objective.progress[user] then
1039 self:ReleaseTable(objective.progress[user])
1040 objective.progress[user] = nil
1042 if not next(objective.progress, nil) then
1043 self:ReleaseTable(objective.progress)
1044 objective.progress = nil