Made purge command reset the locale of the saved data, and added files for 0.44 release.
[QuestHelper.git] / objective.lua
blob9656f0983a5ecd278e1c205993cf3d1e1b478fe7
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 function QuestHelper:NewObjectiveObject()
631 next_objective_id = next_objective_id+1
632 return
634 qh=self,
635 id=next_objective_id,
637 CouldBeFirst=ObjectiveCouldBeFirst,
639 DefaultKnown=DefaultObjectiveKnown,
640 Known=DummyObjectiveKnown,
641 Reason=ObjectiveReason,
643 AppendPositions=ObjectiveAppendPositions,
644 PrepareRouting=ObjectivePrepareRouting,
645 AddLoc=AddLoc,
646 FinishAddLoc=FinishAddLoc,
647 DoneRouting=DoneRouting,
649 Position=GetPosition,
650 TravelTime=ComputeTravelTime,
651 TravelTime2=ComputeTravelTime2,
653 Share=ObjectiveShare, -- Invoke to share this objective with your peers.
654 Unshare=ObjectiveUnshare, -- Invoke to stop sharing this objective.
655 want_share=false, -- True if we want this objective shared.
656 is_sharing=false, -- Set to true if we've told other users about this objective.
658 user_ignore=nil, -- When nil, will use filters. Will ignore, when true, always show (if known).
660 priority=3, -- A hint as to what priority the quest should have. Should be 1, 2, 3, 4, or 5.
661 real_priority=3, -- This will be set to the priority routing actually decided to assign it.
663 setup_count=0,
665 icon_id=12,
666 icon_bg=14,
668 match_zone=false,
669 match_level=false,
670 match_done=false,
672 before={}, -- List of objectives that this objective must appear before.
673 after={}, -- List of objectives that this objective must appear after.
675 -- Routing related junk.
677 --[[ Will be created as needed.
678 d=nil,
679 p=nil,
680 nm=nil, -- Maps nodes to their nearest zone/list/x/y position.
681 nm2=nil, -- Maps nodes to their nears position, but dynamically set in TravelTime2.
682 nl=nil, -- List of all the nodes we need to consider.
683 location=nil, -- Will be set to the best position for the node.
684 pos=nil, -- Zone node list, distance list, x, y, reason.
685 sop=nil ]]
689 function QuestHelper:GetObjective(category, objective)
690 local objective_list = self.objective_objects[category]
692 if not objective_list then
693 objective_list = {}
694 self.objective_objects[category] = objective_list
697 local objective_object = objective_list[objective]
699 if not objective_object then
700 if category == "quest" then
701 local _, _, level, hash, name = string.find(objective, "^(%d+)/(%d*)/(.*)$")
702 if not level then
703 _, _, level, name = string.find(objective, "^(%d+)/(.*)$")
704 if not level then
705 name = objective
709 if hash == "" then hash = nil end
710 objective_object = self:GetQuest(name, tonumber(level), tonumber(hash))
711 objective_list[objective] = objective_object
712 return objective_object
715 objective_object = self:NewObjectiveObject()
717 objective_object.cat = category
718 objective_object.obj = objective
720 if category == "item" then
721 objective_object.Known = ItemKnown
722 objective_object.AppendPositions = ItemAppendPositions
723 objective_object.icon_id = 2
724 elseif category == "monster" then
725 objective_object.icon_id = 1
726 elseif category == "object" then
727 objective_object.icon_id = 3
728 elseif category == "event" then
729 objective_object.icon_id = 4
730 elseif category == "loc" then
731 objective_object.icon_id = 6
732 elseif category == "reputation" then
733 objective_object.icon_id = 5
734 else
735 self:TextOut("FIXME: Objective type '"..category.."' for objective '"..objective.."' isn't explicitly supported yet; hopefully the dummy handler will do something sensible.")
738 objective_list[objective] = objective_object
740 if category == "loc" then
741 -- Loc is special, we don't store it, and construct it from the string.
742 -- Don't have any error checking here, will assume it's correct.
743 local i
744 local _, _, c, z, x, y = string.find(objective,"^(%d+),(%d+),([%d%.]+),([%d%.]+)$")
746 if not y then
747 _, _, i, x, y = string.find(objective,"^(%d+),([%d%.]+),([%d%.]+)$")
748 else
749 i = QuestHelper_IndexLookup[c][z]
752 objective_object.o = {pos={{tonumber(i),tonumber(x),tonumber(y),1}}}
753 objective_object.fb = {}
754 else
755 objective_list = QuestHelper_Objectives[category]
756 if not objective_list then
757 objective_list = {}
758 QuestHelper_Objectives[category] = objective_list
760 objective_object.o = objective_list[objective]
761 if not objective_object.o then
762 objective_object.o = {}
763 objective_list[objective] = objective_object.o
765 local l = QuestHelper_StaticData[self.locale]
766 if l then
767 objective_list = l.objective[category]
768 if objective_list then
769 objective_object.fb = objective_list[objective]
772 if not objective_object.fb then
773 objective_object.fb = {}
776 -- TODO: If we have some other source of information (like LightHeaded) add its data to objective_object.fb
781 return objective_object
784 function QuestHelper:AppendObjectivePosition(objective, i, x, y, w)
785 local pos = objective.o.pos
786 if not pos then
787 if objective.o.drop or objective.o.contained then
788 return -- If it's dropped by a monster, don't record the position we got the item at.
790 objective.o.pos = self:AppendPosition({}, i, x, y, w)
791 else
792 self:AppendPosition(pos, i, x, y, w)
796 function QuestHelper:AppendObjectiveDrop(objective, monster, count)
797 local drop = objective.o.drop
798 if drop then
799 drop[monster] = (drop[monster] or 0)+(count or 1)
800 else
801 objective.o.drop = {[monster] = count or 1}
802 objective.o.pos = nil -- If it's dropped by a monster, then forget the position we found it at.
806 function QuestHelper:AppendItemObjectiveDrop(item_object, item_name, monster_name, count)
807 local quest = self:ItemIsForQuest(item_object, item_name)
808 if quest and not item_object.o.vendor and not item_object.o.drop and not item_object.o.pos then
809 self:AppendQuestDrop(quest, item_name, monster_name, count)
810 else
811 if not item_object.o.drop and not item_object.o.pos then
812 self:PurgeQuestItem(item_object, item_name)
814 self:AppendObjectiveDrop(item_object, monster_name, count)
818 function QuestHelper:AppendItemObjectivePosition(item_object, item_name, i, x, y)
819 local quest = self:ItemIsForQuest(item_object, item_name)
820 if quest and not item_object.o.vendor and not item_object.o.drop and not item_object.o.pos then
821 self:AppendQuestPosition(quest, item_name, i, x, y)
822 else
823 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
824 -- Just learned that this item doesn't depend on a quest to drop, remove any quest references to it.
825 self:PurgeQuestItem(item_object, item_name)
827 self:AppendObjectivePosition(item_object, i, x, y)
831 function QuestHelper:AppendItemObjectiveContainer(objective, container_name, count)
832 local container = objective.o.contained
833 if container then
834 container[container_name] = (container[container_name] or 0)+(count or 1)
835 else
836 objective.o.contained = {[container_name] = count or 1}
837 objective.o.pos = nil -- Forget the position.
841 function QuestHelper:AddObjectiveWatch(objective, reason)
842 if not objective.reasons then
843 objective.reasons = {}
846 if not next(objective.reasons, nil) then
847 objective.watched = true
848 if self.to_remove[objective] then
849 self.to_remove[objective] = nil
850 else
851 self.to_add[objective] = true
855 objective.reasons[reason] = (objective.reasons[reason] or 0) + 1
858 function QuestHelper:RemoveObjectiveWatch(objective, reason)
859 if objective.reasons[reason] == 1 then
860 objective.reasons[reason] = nil
861 if not next(objective.reasons, nil) then
862 objective.watched = false
863 if self.to_add[objective] then
864 self.to_add[objective] = nil
865 else
866 self.to_remove[objective] = true
869 else
870 objective.reasons[reason] = objective.reasons[reason] - 1
874 function QuestHelper:ObjectiveObjectDependsOn(objective, needs)
875 assert(objective ~= needs) -- If this was true, ObjectiveIsKnown would get in an infinite loop.
876 -- TODO: Needs sanity checking, especially now that dependencies can be assigned by remote users.
878 if not objective.after[needs] then
879 if objective.peer then
880 for u, l in pairs(objective.peer) do
881 -- Make sure other users know that the dependencies for this objective changed.
882 objective.peer[u] = math.min(l, 1)
885 objective.after[needs] = true
886 needs.before[objective] = true
890 function QuestHelper:AddObjectiveOptionsToMenu(obj, menu)
891 local submenu = self:CreateMenu()
893 for i = 1,5 do
894 local name = QHText("PRIORITY"..i)
895 local item = self:CreateMenuItem(submenu, name)
896 local tex
898 if obj.priority == i then
899 tex = self:CreateIconTexture(item, 10)
900 elseif obj.real_priority == i then
901 tex = self:CreateIconTexture(item, 8)
902 else
903 tex = self:CreateIconTexture(item, 12)
904 tex:SetVertexColor(1, 1, 1, 0)
907 item:AddTexture(tex, true)
908 item:SetFunction(self.SetObjectivePriorityPrompt, self, obj, i)
911 self:CreateMenuItem(menu, QHText("PRIORITY")):SetSubmenu(submenu)
913 if self.sharing then
914 submenu = self:CreateMenu(QHText("SHARING"))
915 local item = self:CreateMenuItem(submenu, QHText("ENABLE"))
916 local tex = self:CreateIconTexture(item, 10)
917 if not obj.want_share then tex:SetVertexColor(1, 1, 1, 0) end
918 item:AddTexture(tex, true)
919 item:SetFunction(obj.Share, obj)
921 local item = self:CreateMenuItem(submenu, QHText("DISABLE"))
922 local tex = self:CreateIconTexture(item, 10)
923 if obj.want_share then tex:SetVertexColor(1, 1, 1, 0) end
924 item:AddTexture(tex, true)
925 item:SetFunction(obj.Unshare, obj)
927 self:CreateMenuItem(menu, QHText("SHARING")):SetSubmenu(submenu)
930 self:CreateMenuItem(menu, QHText("IGNORE")):SetFunction(self.IgnoreObjective, self, obj)
933 function QuestHelper:IgnoreObjective(objective)
934 if self.user_objectives[objective] then
935 self:RemoveObjectiveWatch(objective, self.user_objectives[objective])
936 self.user_objectives[objective] = nil
937 else
938 objective.user_ignore = true
941 --self:ForceRouteUpdate()
944 function QuestHelper:SetObjectivePriority(objective, level)
945 level = math.min(5, math.max(1, math.floor((tonumber(level) or 3)+0.5)))
946 if level ~= objective.priority then
947 objective.priority = level
948 if objective.peer then
949 for u, l in pairs(objective.peer) do
950 -- Peers don't know about this new priority.
951 objective.peer[u] = math.min(l, 2)
954 --self:ForceRouteUpdate()
958 local function CalcObjectivePriority(obj)
959 local priority = obj.priority
961 for o in pairs(obj.before) do
962 if o.watched then
963 priority = math.min(priority, CalcObjectivePriority(o))
967 return priority
970 local function ApplyBlockPriority(obj, level)
971 for o in pairs(obj.before) do
972 if o.watched then
973 ApplyBlockPriority(o, level)
977 if obj.priority < level then QuestHelper:SetObjectivePriority(obj, level) end
980 function QuestHelper:SetObjectivePriorityPrompt(objective, level)
981 self:SetObjectivePriority(objective, level)
982 if CalcObjectivePriority(objective) ~= level then
983 local menu = self:CreateMenu()
984 self:CreateMenuTitle(menu, QHText("IGNORED_PRIORITY_TITLE"))
985 self:CreateMenuItem(menu, QHText("IGNORED_PRIORITY_FIX")):SetFunction(ApplyBlockPriority, objective, level)
986 self:CreateMenuItem(menu, QHText("IGNORED_PRIORITY_IGNORE")):SetFunction(self.nop)
987 menu:ShowAtCursor()
991 function QuestHelper:SetObjectiveProgress(objective, user, have, need)
992 if have and need then
993 local list = objective.progress
994 if not list then
995 list = self:CreateTable()
996 objective.progress = list
999 local user_progress = list[user]
1000 if not user_progress then
1001 user_progress = self:CreateTable()
1002 list[user] = user_progress
1005 local pct = 0
1006 local a, b = tonumber(have), tonumber(need)
1007 if a and b then
1008 if b ~= 0 then
1009 pct = a/b
1010 elseif a == 0 then
1011 pct = 1
1013 elseif a == b then
1014 pct = 1
1017 user_progress[1], user_progress[2], user_progress[3] = have, need, pct
1018 else
1019 if objective.progress then
1020 if objective.progress[user] then
1021 self:ReleaseTable(objective.progress[user])
1022 objective.progress[user] = nil
1024 if not next(objective.progress, nil) then
1025 self:ReleaseTable(objective.progress)
1026 objective.progress = nil