Built archives have commit hash and date added to version.
[QuestHelper.git] / routing.lua
blob0d9408896643486e4a957dc8524d6c8b864ae78c
1 local call_count = 0
3 local map_rpf = 25
4 local normal_rpf = 10
6 local function yieldIfNeeded()
7 if call_count == QuestHelper.Astrolabe.WorldMapVisible and map_rpf or normal_rpf then
8 call_count = 0
9 coroutine.yield()
10 else
11 call_count = call_count + 1
12 end
13 end
15 local function CalcObjectivePriority(obj)
16 local priority = obj.priority
18 for o in pairs(obj.before) do
19 if o.watched then
20 priority = math.min(priority, CalcObjectivePriority(o))
21 end
22 end
24 obj.real_priority = priority
25 return priority
26 end
28 function CalcObjectiveIJ(route, obj)
29 local i = 1
31 for p, o in ipairs(route) do
32 if obj.real_priority > o.real_priority or obj.after[o] then
33 i = p+1
34 elseif obj.real_priority < o.real_priority or obj.before[o] then
35 return i, p
36 end
37 end
39 return i, #route+1
40 end
42 function QuestHelper:RemoveIndexFromRoute(array, distance, extra, index)
43 if #array == 1 then
44 distance = 0
45 extra = 0
46 table.remove(array, 1)
47 elseif index == 1 then
48 distance = distance - array[1].len
49 extra = self:ComputeTravelTime(self.pos, array[2].pos)
50 table.remove(array, 1)
51 yieldIfNeeded()
52 elseif index == #array then
53 distance = distance - array[index-1].len
54 table.remove(array, index)
55 else
56 local a, b = array[index-1], table.remove(array, index)
57 distance = distance - a.len - b.len
58 a.len = self:ComputeTravelTime(a.pos, array[index].pos)
59 distance = distance + a.len
60 yieldIfNeeded()
61 end
63 return distance, extra
64 end
66 function QuestHelper:InsertObjectiveIntoRoute(array, distance, extra, objective)
67 -- array - Contains the path you want to insert into.
68 -- distance - How long is the path so far?
69 -- extra - How far is it from the player to the first node?
70 -- objective - Where are we trying to get to?
72 -- In addition, objective needs i and j set, for the min and max indexes it can be inserted into.
74 -- Returns:
75 -- index - The index to inserted into.
76 -- distance - The new length of the path.
77 -- extra - The new distance from the first node to the player.
79 if #array == 0 then
80 extra, objective.pos = objective:TravelTime(self.pos)
81 yieldIfNeeded()
82 table.insert(array, 1, objective)
83 return 1, 0, extra
84 end
86 local best_index, best_extra, best_total, best_len1, best_len2, bp
88 local low, high = CalcObjectiveIJ(array, objective)
90 if low == 1 then
91 best_index = 1
92 best_extra, best_len2, bp = objective:TravelTime2(self.pos, array[1].pos)
93 best_total = best_extra+distance+best_len2
94 elseif low == #array+1 then
95 local o = array[#array]
96 o.len, objective.pos = objective:TravelTime(array[#array].pos)
97 yieldIfNeeded()
98 table.insert(array, objective)
99 return #array, distance+o.len, extra
100 else
101 local a = array[low-1]
102 best_index = low
103 best_len1, best_len2, bp = objective:TravelTime2(a.pos, array[low].pos)
104 best_extra = extra
105 best_total = distance - a.len + best_len1 + best_len2 + extra
106 yieldIfNeeded()
109 local total = distance+extra
111 for i = low+1, math.min(#array, high) do
112 local a = array[i-1]
113 local l1, l2, p = objective:TravelTime2(a.pos, array[i].pos)
114 local d = total - a.len + l1 + l2
115 if d < best_total then
116 bp = p
117 best_len1 = l1
118 best_len2 = l2
119 best_extra = extra
120 best_total = d
121 best_index = i
123 yieldIfNeeded()
126 if high == #array+1 then
127 local l1, p = objective:TravelTime(array[#array].pos)
128 yieldIfNeeded()
129 local d = total + l1
130 if d < best_total then
131 objective.pos = p
132 array[#array].len = l1
133 table.insert(array, objective)
134 return #array, d-extra, extra
138 assert(bp)
139 objective.pos = bp
140 if best_index > 1 then array[best_index-1].len = best_len1 end
141 objective.len = best_len2
142 table.insert(array, best_index, objective)
143 return best_index, best_total-best_extra, best_extra
147 function QuestHelper:RemoveIndexFromRouteSOP(array, distance, extra, index)
148 if #array == 1 then
149 distance = 0
150 extra = 0
151 table.remove(array, 1)
152 elseif index == 1 then
153 distance = distance - array[1].nel
154 extra = self:ComputeTravelTime(self.pos, array[2].sop)
155 table.remove(array, 1)
156 yieldIfNeeded()
157 elseif index == #array then
158 distance = distance - array[index-1].nel
159 table.remove(array, index)
160 else
161 local a, b = array[index-1], table.remove(array, index)
162 distance = distance - a.nel - b.nel
163 a.nel = self:ComputeTravelTime(a.sop, array[index].sop)
164 distance = distance + a.nel
165 yieldIfNeeded()
168 return distance, extra
171 function QuestHelper:InsertObjectiveIntoRouteSOP(array, distance, extra, objective)
172 -- array - Contains the path you want to insert into.
173 -- distance - How long is the path so far?
174 -- extra - How far is it from the player to the first node?
175 -- objective - Where are we trying to get to?
177 -- In addition, objective needs i and j set, for the min and max indexes it can be inserted into.
179 -- Returns:
180 -- index - The index to inserted into.
181 -- distance - The new length of the path.
182 -- extra - The new distance from the first node to the player.
184 if #array == 0 then
185 extra, objective.sop = objective:TravelTime(self.pos)
186 yieldIfNeeded()
187 table.insert(array, 1, objective)
188 return 1, 0, extra
191 local best_index, best_extra, best_total, best_len1, best_len2, bp
193 local low, high = CalcObjectiveIJ(array, objective)
195 if low == 1 then
196 best_index = 1
197 best_extra, best_len2, bp = objective:TravelTime2(self.pos, array[1].sop)
198 best_total = best_extra+distance+best_len2
199 yieldIfNeeded()
200 elseif low == #array+1 then
201 local o = array[#array]
202 o.nel, objective.sop = objective:TravelTime(array[#array].sop)
203 yieldIfNeeded()
204 table.insert(array, objective)
205 return #array, distance+o.nel, extra
206 else
207 local a = array[low-1]
208 best_index = low
209 best_len1, best_len2, bp = objective:TravelTime2(a.sop, array[low].sop)
210 best_extra = extra
211 best_total = distance - a.nel + best_len1 + best_len2 + extra
212 yieldIfNeeded()
215 local total = distance+extra
217 for i = low+1, math.min(#array, high) do
218 local a = array[i-1]
219 local l1, l2, p = objective:TravelTime2(a.sop, array[i].sop)
220 local d = total - a.nel + l1 + l2
221 if d < best_total then
222 bp = p
223 best_len1 = l1
224 best_len2 = l2
225 best_extra = extra
226 best_total = d
227 best_index = i
229 yieldIfNeeded()
232 if high == #array+1 then
233 local l1, p = objective:TravelTime(array[#array].sop)
234 yieldIfNeeded()
235 local d = total + l1
236 if d < best_total then
237 objective.sop = p
238 array[#array].nel = l1
239 table.insert(array, objective)
240 return #array, d-extra, extra
244 assert(bp)
245 objective.sop = bp
246 if best_index > 1 then array[best_index-1].nel = best_len1 end
247 objective.nel = best_len2
248 table.insert(array, best_index, objective)
249 return best_index, best_total-best_extra, best_extra
252 local route_pass = 0
253 local map_walker
255 local function RouteUpdateRoutine(self)
256 map_walker = self:CreateWorldMapWalker()
257 local minimap_dodad = self:CreateMipmapDodad()
258 local swap_table = {}
259 local distance, extra, route, new_distance, new_extra, new_route, shuffle, insert, point = 0, 0, self.route, 0, 0, {}, {}, 0, nil
260 local recheck_pos, new_recheck_pos, new_local_minima = 1, 99999, true
262 self.minimap_dodad = minimap_dodad
264 while true do
265 for i,o in ipairs(route) do
266 o.filter_zone = o.location[1] ~= self.pos[1]
268 if not o:Known() then
269 -- Objective was probably made to depend on an objective that we don't know about yet.
270 -- We add it to both lists, because although we need to remove it, we need it added again when we can.
271 -- This creats an inconsistancy, but it'll get fixed in the removal loop before anything has a chance to
272 -- explode from it.
274 self.to_remove[o] = true
275 self.to_add[o] = true
276 else
277 CalcObjectivePriority(o)
279 if o.is_sharing ~= o.want_share then
280 o.is_sharing = o.want_share
282 if o.want_share then
283 self:DoShareObjective(o)
284 else
285 self:DoUnshareObjective(o)
291 local original_size = #route
293 -- Remove any waypoints if needed.
294 while true do
295 local obj = next(self.to_remove)
296 if not obj then break end
297 self.to_remove[obj] = nil
299 if obj.is_sharing then
300 obj.is_sharing = false
301 self:DoUnshareObjective(obj)
304 self:ReleaseTeleportInfo(obj.tele_pos)
305 self:ReleaseTeleportInfo(obj.tele_sop)
306 obj.tele_pos, obj.tele_sop = nil, nil
308 for i, o in ipairs(route) do
309 if o == obj then
310 if i == 1 then
311 if #route == 1 then
312 minimap_dodad:SetObjective(nil)
313 else
314 minimap_dodad:SetObjective(route[2])
318 if recheck_pos > i then recheck_pos = recheck_pos - 1 end
320 distance, extra = self:RemoveIndexFromRoute(route, distance, extra, i)
321 break
325 for i, o in ipairs(new_route) do
326 if o == obj then
327 if new_recheck_pos > i then new_recheck_pos = new_recheck_pos - 1 end
328 new_distance, new_extra = self:RemoveIndexFromRouteSOP(new_route, new_distance, new_extra, i)
329 break
333 obj:DoneRouting()
336 while true do
337 local obj = next(self.to_add)
338 if not obj then break end
339 self.to_add[obj] = nil
341 if obj:Known() then
342 obj:PrepareRouting()
344 obj.filter_zone = obj.location[1] ~= self.pos[1]
346 if obj.filter_zone and QuestHelper_Pref.filter_zone then
347 -- Not going to add it, wrong zone.
348 obj:DoneRouting()
349 swap_table[obj] = true
350 else
351 obj.tele_pos = self:CreateTeleportInfo()
352 obj.tele_sop = self:CreateTeleportInfo()
354 if not obj.is_sharing and obj.want_share then
355 obj.is_sharing = true
356 self:DoShareObjective(obj)
359 CalcObjectivePriority(obj)
361 if #route == 0 then
362 insert, distance, extra = self:InsertObjectiveIntoRoute(route, 0, 0, obj)
363 insert, new_distance, new_extra = self:InsertObjectiveIntoRouteSOP(new_route, 0, 0, obj)
365 minimap_dodad:SetObjective(obj)
366 else
367 insert, distance, extra = self:InsertObjectiveIntoRoute(route, distance, extra, obj)
369 if insert == 1 then
370 minimap_dodad:SetObjective(obj)
373 insert, new_distance, new_extra = self:InsertObjectiveIntoRouteSOP(new_route, new_distance, new_extra, obj)
376 else
377 swap_table[obj] = true
381 for obj in pairs(swap_table) do
382 -- If one of the objectives we were considering adding was removed, it would be in both lists.
383 -- That would be bad. We can't remove it because we haven't actually added it yet, so
384 -- handle that special case here.
385 if self.to_remove[obj] then
386 self.to_remove[obj] = nil
387 self.to_add[obj] = nil
391 self.to_add, swap_table = swap_table, self.to_add
393 -- If size decreased, all the old indexes need to be reset.
394 if #route < original_size then
395 for i=1,#route do
396 shuffle[i] = i
400 -- Append new indexes to shuffle.
401 for i=original_size+1,#route do
402 shuffle[i] = i
405 if #route > 0 then
406 if recheck_pos > #route then recheck_pos = 1 end
407 if new_recheck_pos > #route then
408 new_recheck_pos = 1
409 if new_local_minima then
410 -- Start try something new, we can't seem to get what we have to be any better.
412 for i=1,#route-1 do -- Shuffling the order we'll add the nodes in.
413 local r = math.random(i, #route)
414 if r ~= i then
415 shuffle[i], shuffle[r] = shuffle[r], shuffle[i]
419 while #new_route > 0 do
420 table.remove(new_route)
423 point = route[shuffle[1]]
424 insert, new_distance, new_extra = self:InsertObjectiveIntoRouteSOP(new_route, 0, 0, point)
426 -- Insert the rest of the points.
427 for i=2,#route do
428 point = route[shuffle[i]]
430 insert, new_distance, new_extra = self:InsertObjectiveIntoRouteSOP(new_route, new_distance, new_extra, point)
433 new_local_minima = true
436 point = route[recheck_pos]
437 self.limbo_node = point
438 distance, extra = self:RemoveIndexFromRoute(route, distance, extra, recheck_pos)
439 insert, distance, extra = self:InsertObjectiveIntoRoute(route, distance, extra, point)
440 self.limbo_node = nil
442 if insert == 1 or recheck_pos == 1 then
443 minimap_dodad:SetObjective(route[1])
446 point = new_route[new_recheck_pos]
447 new_distance, new_extra = self:RemoveIndexFromRouteSOP(new_route, new_distance, new_extra, new_recheck_pos)
448 insert, new_distance, new_extra = self:InsertObjectiveIntoRouteSOP(new_route, new_distance, new_extra, point)
449 if insert ~= new_recheck_pos then
450 new_local_minima = false
453 recheck_pos = recheck_pos + 1
454 new_recheck_pos = new_recheck_pos + 1
457 if new_distance+new_extra+0.001 < distance+extra then
458 for i, node in ipairs(route) do
459 node.len = node.nel
460 node.tele_pos, node.tele_sop = node.tele_sop, node.tele_pos
461 node.pos, node.sop = node.sop, node.pos
464 route, new_route = new_route, route
465 distance, new_distance = new_distance, distance
466 extra, new_extra = new_extra, extra
468 self.route = route
470 minimap_dodad:SetObjective(route[1])
472 for i=1,#route-1 do -- Shuffling the order we'll add the nodes in.
473 local r = math.random(i, #route)
474 if r ~= i then
475 shuffle[i], shuffle[r] = shuffle[r], shuffle[i]
479 while #new_route > 0 do
480 table.remove(new_route)
483 point = route[shuffle[1]]
484 insert, new_distance, new_extra = self:InsertObjectiveIntoRouteSOP(new_route, 0, 0, point)
486 -- Insert the rest of the points.
487 for i=2,#route do
488 point = route[shuffle[i]]
489 insert, new_distance, new_extra = self:InsertObjectiveIntoRouteSOP(new_route, new_distance, new_extra, point)
492 recheck_pos = new_recheck_pos
493 new_recheck_pos = 1
494 new_local_minima = true
497 -- Thats enough work for now, we'll continue next frame.
498 if route_pass > 0 then
499 route_pass = route_pass - 1
502 map_walker:RouteChanged()
504 call_count = 0
506 self:SetupTeleportInfo(self.teleport_info)
508 if self.defered_graph_reset then
509 self:ResetPathing()
510 self.defered_graph_reset = false
513 coroutine.yield()
517 function QuestHelper:ForceRouteUpdate(passes)
518 route_pass = math.max(2, passes or 0)
520 while route_pass ~= 0 do
521 if coroutine.status(self.update_route) == "dead" then
522 break
525 local state, err = coroutine.resume(self.update_route, self)
526 if not state then
527 self:TextOut("|cffff0000The routing co-routine just exploded|r: |cffffff77"..err.."|r")
528 break
533 QuestHelper.update_route = coroutine.create(RouteUpdateRoutine)