1 QuestHelper_File
["routing.lua"] = "Development Version"
4 QuestHelper
.Routing
= {}
5 local Routing
= QuestHelper
.Routing
6 Routing
.qh
= QuestHelper
9 local improve_margin
= 1e-8
14 local coroutine_running
= false
15 local coroutine_stop_time
= 0
17 function QuestHelper
:yieldIfNeeded()
18 if coroutine_running
then
19 -- Check if we've run our alotted time
20 if GetTime() > coroutine_stop_time
then
21 -- As a safety, reset stop time to 0. If somehow we fail to set it next time,
22 -- we'll be sure to yield promptly.
23 coroutine_stop_time
= 0
29 local function CalcObjectivePriority(obj
)
30 local priority
= obj
.priority
32 for o
in pairs(obj
.before
) do
34 priority
= math
.min(priority
, CalcObjectivePriority(o
))
38 obj
.real_priority
= priority
44 Routing
.Route
= Route
-- Make it available as a member
46 -- This should pass on all routes. If it does not, *things need to be fixed*. No, commenting tests out is not an adequate response - this *must* pass. Eventually this will get rolled into the unsucky Route class.
47 function Route
:sanity()
51 if QuestHelper
.Error
then
52 assert = function(a
, b
)
54 QuestHelper
:TextOut("Route:sanity(): id="..self
.id
.."; best_route="..Routing
.best_route
.id
)
55 QuestHelper
:Error(b
or "Assertion Failed")
62 --QuestHelper:TextOut(QuestHelper:StringizeTable(self))
64 --QuestHelper:TextOut(tostring(i))
65 --QuestHelper:TextOut(QuestHelper:StringizeTable(self[i]))
66 --QuestHelper:TextOut(tostring(self[i].len))
71 --QuestHelper:TextOut("sd: "..l.." rd: "..self.distance)
72 assert(math
.abs(l
-self
.distance
) < 0.0001, string.format("compare %f vs %f", l
, self
.distance
))
74 for i
, info
in ipairs(self
) do
75 assert(self
.index
[info
.obj
] == i
)
79 for obj
, i
in pairs(self
.index
) do
80 assert(self
[i
].obj
== obj
)
84 local l
= QuestHelper
:ComputeTravelTime(self
[i
].pos
, self
[i
+1].pos
, true)
85 assert(math
.abs(l
-self
[i
].len
) < 0.0001, "Compare at "..i
..": "..l
.." vs "..self
[i
].len
)
91 function Route
:findObjectiveRange(obj
, passes
)
95 table.insert(lines, string.format("QuestHelper objectiverange for %s (pri %d)", obj.obj, obj.real_priority))
97 table.insert(lines, string.format("%d %d %d --- %d %d %d (vs %s, %d)", obj.real_priority > self[i].obj.real_priority and 1 or 0, obj.after[self[i].obj] and 1 or 0, self[i].obj.before[obj] and 1 or 0, obj.real_priority < self[i].obj.real_priority and 1 or 0, obj.before[self[i].obj] and 1 or 0, self[i].obj.after[obj] and 1 or 0, self[i].obj.obj, self[i].obj.real_priority))
102 if obj
.real_priority
> self
[mn
].obj
.real_priority
or obj
.after
[self
[mn
].obj
] or self
[mn
].obj
.before
[obj
] then break end
106 mn
= mn
+ 1 -- we went too far, actually
109 while mx
< #self
+ 1 do
110 if obj
.real_priority
< self
[mx
].obj
.real_priority
or obj
.before
[self
[mx
].obj
] or self
[mx
].obj
.after
[obj
] then break end
114 --table.insert(lines, string.format("temp results is %d %d", mn, mx))
116 if mx
< mn
then -- well, technically, there's no place we can put this. So we guess wildly. Eventually it'll sanify itself. We hope.
117 local mid
= math
.ceil((mx
+ mn
) / 2)
122 --table.insert(lines, string.format("overall: %d %d", mn, mx))
125 if passes and passes > 90 then
126 for k, v in pairs(lines) do QuestHelper:TextOut(v) end
127 QuestHelper:TextOut(string.format("overall: %d %d", mn, mx))
132 local omn, omx = self:OldFindObjectiveRange(obj)
134 if mn ~= omn or mx ~= omx then
135 for k, v in pairs(lines) do QuestHelper:TextOut(v) end
136 QuestHelper:TextOut(string.format("overall: %d %d vs %d %d", mn, mx, omn, omx))
137 lolcrash = (lolcrash or 0) + 1
144 function Route
:addObjectiveFast(obj
)
145 assert(self
:sanity())
146 local indexes
= self
.index
148 local info
= QuestHelper
:CreateTable()
149 assert(not indexes
[obj
])
157 d
, info
.pos
= obj
:TravelTime(self
[0].pos
, true)
163 local player_pos
= QuestHelper
.pos
164 local pos
= obj
.location
165 local c
, x
, y
= pos
[1].c
, pos
[3], pos
[4]
167 local mn
, mx
= self
:findObjectiveRange(obj
)
170 for i
= mn
, math
.min(mx
, len
) do
171 local p
= self
[i
].pos
173 local u
, v
= p
[3]-x
, p
[4]-y
175 if not index
or d2
< distsqr
then
176 index
, distsqr
= i
, d2
182 -- No nodes with the same continent already.
183 -- If the same continent as the player, add to start of list, otherwise add to end of the list.
184 index
= c
== player_pos
[1].c
and mx
or mx
187 -- The next question, do I insert at that point, or do I insert after it?
188 if index
~= mx
and index
<= len
then
189 local p1
= self
[index
].pos
197 p0
= self
[index
-1].pos
200 local oldstart
, newstart
203 local u
, v
= p0
[3]-x
, p0
[4]-y
204 newstart
= math
.sqrt(u
*u
+v
*v
)
205 u
, v
= p0
[3]-p1
[3], p0
[4]-p1
[4]
206 oldstart
= math
.sqrt(u
*u
+v
*v
)
214 p2
= self
[index
+1].pos
218 if p2
and p2
[1].c
== c
then
219 local u
, v
= p2
[3]-x
, p2
[4]-y
220 newend
= math
.sqrt(u
*u
+v
*v
)
221 u
, v
= p2
[3]-p1
[3], p2
[4]-p1
[4]
222 oldend
= math
.sqrt(u
*u
+v
*v
)
228 if oldstart
+newend
< newstart
+oldend
then
235 QuestHelper
:yieldIfNeeded((mn
-mx
+3)*0.05) -- The above checks don't require much effort.
238 local previnfo
= self
[index
-1]
241 d
, info
.pos
= obj
:TravelTime(previnfo
.pos
)
243 QuestHelper
:yieldIfNeeded(0.5)
245 self
.distance
= self
.distance
+ d
249 local previnfo
= self
[index
-1]
250 d1
, d2
, info
.pos
= obj
:TravelTime2(previnfo
.pos
, self
[index
].pos
, previnfo
.no_cache
)
253 self
.distance
= self
.distance
+ (d1
- previnfo
.len
+ d2
)
256 QuestHelper
:yieldIfNeeded(1)
259 -- Finally, insert the objective.
260 table.insert(self
, index
, info
)
263 -- Fix indexes of shifted elements.
264 for i
= index
+1,len
+1 do
265 local obj
= self
[i
].obj
266 assert(indexes
[obj
] == i
-1)
270 assert(self
:sanity())
275 function Route
:addObjectiveBest(obj
, old_index
, old_distance
)
276 assert(self
:sanity())
278 local indexes
= self
.index
280 local info
= QuestHelper
:CreateTable()
281 assert(not indexes
[obj
])
287 self
.distance
, info
.pos
= obj
:TravelTime(self
[0].pos
, true)
289 self
[0].len
= self
.distance
294 local sanityfixed
= nil -- If we've done something to improve the sanity of the overall path, i.e. force the path into a situation where it's no longer trying to turn in quests before the quest has been completed, then we definitely want to accept this path overall. Before, this wasn't a problem, since this function was so unstable that it would randomly change the path anyway, and it doesn't *break* things once they're fixed. Now that we have a check that this function actually *improves* the path, we need a slightly more complicated definition of "improve". Ideally, that shouldn't be necessary, but for now it is (until, at least, this function actually puts things in the best location, and that will have to wait until the get-optimal-path functions actually get optimal paths.)
296 local best_index
, best_delta
, best_d1
, best_d2
, best_p
297 local no_cache
, prev_pos
, prev_len
298 local mn
, mx
= self
:findObjectiveRange(obj
)
300 if old_index
and mn
<= old_index
and old_index
<= mx
then
301 -- We're trying to re-evaluate it, and it could remain in the same place.
302 -- So that place is our starting best known place.
303 best_index
, best_delta
= old_index
, old_distance
- self
.distance
305 if best_delta
< 0 then
306 -- Somehow, removing the objective actually made the route worse...
307 -- Just re-figure things from scratch.
308 -- TODO: THIS SHOULD NEVER HAPPEN dear god find out what's causing this and stop it
309 --QuestHelper:TextOut("made route worse wtf")
310 best_index
, best_delta
= nil, nil
314 local pinfo
= self
[mn
-1]
315 no_cache
, prev_pos
, prev_len
= pinfo
.no_cache
, pinfo
.pos
, pinfo
.len
317 for i
= mn
, math
.min(mx
, len
) do
318 assert(prev_pos
== self
[i
-1].pos
)
323 local d1
, d2
, p
= obj
:TravelTime2(prev_pos
, pos
, no_cache
)
325 QuestHelper
:yieldIfNeeded(1)
327 local delta
= d1
+ d2
- prev_len
329 if not best_index
or ((delta
+ improve_margin
) < best_delta
) or ((i
== best_index
) and not best_d1
) then
331 -- * First item we reach
332 -- * Better than previous best
333 -- * We're looking at our best already. But we just got here; how could this be best?
334 -- If this was our prior location and we didn't find anything better earlier in the route,
335 -- that's how. Save the specifics, 'cause we didn't compute them when setting up.
336 best_index
, best_delta
, best_d1
, best_d2
, best_p
= i
, delta
, d1
, d2
, p
346 assert(prev_pos
== self
[len
].pos
)
347 local delta
, p
= obj
:TravelTime(prev_pos
, no_cache
)
349 QuestHelper
:yieldIfNeeded(.5)
351 if not best_index
or ((delta
+ improve_margin
) < best_delta
) or ((mx
== best_index
) and not best_d1
) then
354 self
[len
].len
= delta
355 self
.distance
= self
.distance
+ delta
356 table.insert(self
, info
)
359 assert(self
:sanity())
368 local pinfo
= self
[best_index
-1]
369 self
.distance
= self
.distance
+ (best_d1
- pinfo
.len
+ best_d2
)
372 table.insert(self
, best_index
, info
)
374 -- QuestHelper:Assert(math.abs(QuestHelper:ComputeTravelTime(self[best_index-1].pos, self[best_index].pos) - self[best_index-1].len) < 0.0001, "aaaaargh")
375 --[[ -- I don't think this is necessary now that TravelTime2 explicitly does this internally, but I'm keeping it anyway.
376 self.distance = self.distance - self[best_index-1].len
377 self[best_index-1].len = QuestHelper:ComputeTravelTime(self[best_index-1].pos, self[best_index].pos, true)
378 self.distance = self.distance + self[best_index-1].len
381 indexes
[obj
] = best_index
383 for i
= best_index
+1,len
+1 do
384 assert(indexes
[self
[i
].obj
] == i
-1)
385 indexes
[self
[i
].obj
] = i
388 if not old_index
or (mn
> old_index
or old_index
> mx
) and mn
<= best_index
and best_index
<= mx
then
389 -- if we didn't have an old index, or our old index was out of bounds and our best index is in bounds, then we've done something Majorly Good and we should be using this path even if the old one was faster
393 assert(self
:sanity())
395 return best_index
, sanityfixed
398 function Route
:removeObjective(obj
)
399 assert(self
:sanity())
401 local indexes
= self
.index
402 local index
= indexes
[obj
]
403 local old_distance
= self
.distance
406 local info
= self
[index
]
407 assert(info
.obj
== obj
)
410 Removing end item: subtract last distance, nothing to recalculate
411 Removing other item: recalculate location of next objective, between prior position and objective after next
412 Special case: if there is no location after next, just recalc location of next objective
414 if index
== #self
then
415 self
.distance
= self
.distance
- self
[index
-1].len
416 self
[index
-1].len
= 0
418 local pinfo
= self
[index
-1]
419 local info1
= self
[index
+1]
420 local info2
= self
[index
+2]
421 local no_cache
= (index
== 1)
426 d1
, d2
, info1
.pos
= info1
.obj
:TravelTime2(pinfo
.pos
, info2
.pos
, no_cache
)
427 QuestHelper
:yieldIfNeeded(1)
428 self
.distance
= self
.distance
- pinfo
.len
- info
.len
- info1
.len
+ d1
+ d2
431 d1
, info1
.pos
= info1
.obj
:TravelTime(pinfo
.pos
, no_cache
)
432 QuestHelper
:yieldIfNeeded(0.5)
433 self
.distance
= self
.distance
- pinfo
.len
- info
.len
+ d1
439 QuestHelper
:ReleaseTable(info
)
441 table.remove(self
, index
)
443 for i
= index
,#self
do
444 -- Fix indexes of shifted elements.
445 local obj
= self
[i
].obj
446 assert(indexes
[obj
] == i
+1)
450 assert(self
:sanity())
451 -- assert(self.distance <= old_distance)
459 function Route
:breed(route_map
)
460 local indexes
= self
.index
466 local prev_pos
= QuestHelper
.pos
467 assert(self
[0].pos
== prev_pos
)
469 -- Pick which objective goes first, selecting from first objective of each route,
470 -- and scaling by the route's fitness and distance from player, with a random adjustment factor.
471 -- While we're at it, record some data about the fitness of adjacent objectives
472 for route
in pairs(route_map
) do
473 assert(route
:sanity())
474 local fit
= route
.fitness
475 local pos
= route
[1].pos
478 if prev_pos
[1].c
== pos
[1].c
then
479 local u
, v
= prev_pos
[3]-pos
[3], prev_pos
[4]-pos
[4]
480 w
= math
.sqrt(u
*u
+v
*v
)
485 w
= fit
* math
.random() / w
487 if not info
or w
> r
then
488 info
, r
= route
[1], w
492 local obj
= route
[i
].obj
493 local tbl
= links
[obj
]
495 tbl
= QuestHelper
:CreateTable()
500 local info
= route
[i
-1]
501 local obj2
= info
.obj
502 tbl
[info
] = (tbl
[info
] or 0) + fit
506 local info
= route
[i
+1]
507 local obj2
= info
.obj
508 if obj
.real_priority
<= obj2
.real_priority
or obj
.before
[obj2
] then
509 tbl
[info
] = (tbl
[info
] or 0) + fit
514 QuestHelper
:yieldIfNeeded(0.01*len
)
517 -- Record info for the 'Player Position' objective, so we don't mess it up later
518 seen
[self
[0].obj
] = self
[0].pos
520 -- Record the objective that we chose to put first
523 seen
[obj
] = info
.pos
-- Save its position, because we don't want to clobber any of the info objects yet
529 -- Scan the rest of the places in the route, and pick objectives to go there
534 -- Scan the list of scores from the prior objective
535 for i
, weight
in pairs(last
) do
537 -- Only consider an item if we have scores for that item
540 if prev_pos
[1].c
== pos
[1].c
then
541 local u
, v
= prev_pos
[3]-pos
[3], prev_pos
[4]-pos
[4]
542 w
= math
.sqrt(u
*u
+v
*v
)
547 w
= weight
* math
.random() / w
549 if not info
or w
> r
then
556 -- In case we had no valid scores, scan the remaining objectives and score by distance
558 for obj
in pairs(links
) do
561 if prev_pos
[1] == pos
[1] then
563 local u
, v
= prev_pos
[3]-pos
[3], prev_pos
[4]-pos
[4]
564 w
= math
.sqrt(u
*u
+v
*v
)
565 elseif prev_pos
[1].c
== pos
[1].c
then
566 -- Same continent. -- Assume twices as long.
567 local u
, v
= prev_pos
[3]-pos
[3], prev_pos
[4]-pos
[4]
568 w
= 2*math
.sqrt(u
*u
+v
*v
)
570 -- Different continent. Assume fixed value of 5 minutes.
574 w
= math
.random() / w
576 if not info
or w
> r
then
577 local route
= next(route_map
)
578 info
, r
= route
[route
.index
[obj]]
, w
585 -- Add the selected item to the route
590 assert(info
.obj
== obj
)
592 -- Get the scores table for this objective, clear it out, discard the scores from the prior objective, and save these scores for next time around
593 local link
= links
[obj
]
595 QuestHelper
:ReleaseTable(last
)
598 QuestHelper
:yieldIfNeeded(0.01*c
)
601 -- Clean up the last table
602 QuestHelper
:ReleaseTable(last
)
604 -- Now that we've got our objectives lined up, fill in the info objects with the positions we saved
605 for obj
, i
in pairs(indexes
) do
608 info
.obj
, info
.pos
= obj
, seen
[obj
]
612 -- Now randomly randomize some of the route (aka mutation)
613 while math
.random() > 0.3 do
614 local l
= math
.floor(math
.random()^
1.6*(len
-1))+1
615 local i
= math
.random(1, len
-l
)
618 -- Reverse a chunk of the route
620 self
[i
+k
], self
[j
-k
] = self
[j
-k
], self
[i
+k
]
624 -- But wait, after all that some objectives might violate the rules. Make sure the route follows
627 -- There's some horrifying ugly here. The "before" and "after" links are not properly updated simultaneously. This means that X can be flagged as after Y without Y being flagged as before X. Making things worse (because, oh man, things had to be made worse!) this means that X might have a lower priority than Y despite needing to happen before it. Urgh.
628 -- Since the entire thing is internally inconsistent anyway, we're just gonna try to consistentize it.
630 local valid_items
= {}
631 for k
, v
in ipairs(self
) do
632 valid_items
[v
.obj
] = true
634 for k
, v
in ipairs(self
) do
635 for b
in pairs(v
.obj
.before
) do
636 if valid_items
[b
] then
637 b
.after
[v
.obj
] = true
640 for a
in pairs(v
.obj
.after
) do
641 if valid_items
[a
] then
642 a
.before
[v
.obj
] = true
647 -- Because priorities might have been changed in here, we next make absolutely sure we have up-to-date priorities.
648 for k
, v
in ipairs(self
) do
649 CalcObjectivePriority(v
.obj
)
652 -- Have I mentioned I hate this codebase yet?
658 local invalid_passes
= 0
659 --local output_strings = {}
662 invalid_passes
= invalid_passes
+ 1
664 --[[if invalid_passes >= 100 then
665 for k, v in pairs(output_strings) do
666 QuestHelper:TextOut(v)
670 QuestHelper
: Assert(invalid_passes
<= 100, "Too many mutation passes needed to preserve sanity, something has gone Horribly Wrong, please report this as a bug (you will probably need to restart WoW for QH to continue working, sorry about that)") -- space is so it works in the real code
674 --[[for i = 1, #self do
675 local mn, mx = self:findObjectiveRange(self[i].obj, invalid_passes)
676 table.insert(output_strings, string.format("%d is mn mx %d %d (%s)", i, mn, mx, self[i].obj.obj))
679 -- Make sure all the objectives have valid positions in the list.
681 local mn
, mx
, tabi
= self
:findObjectiveRange(info
.obj
, invalid_passes
)
682 --if invalid_passes > 90 then for k, v in pairs(tabi) do table.insert(output_strings, v) end end
684 -- In theory, 'i' shouldn't be increased here, as the next
685 -- element will be shifted down into the current position.
687 -- However, it is possible for an infinite loop to be created
688 -- by this, with a small range of objectives constantly
691 -- So, I mark the route as invalid and go through it another time.
692 -- It's probably still possible to get into an infinite loop,
693 -- but it seems much less likely.
695 table.insert(self
, mn
, info
)
696 table.remove(self
, i
)
698 --table.insert(output_strings, string.format("shifting %d into %d", i, mn))
700 table.remove(self
, i
)
701 table.insert(self
, mx
, info
)
703 --table.insert(output_strings, string.format("shifting %d into %d", i, mx))
707 --table.insert(output_strings, "pass done")
710 -- Now that we've chosen a route, re-calculate the cost of each leg of the route
712 local prev_info
= self
[0]
713 local next_info
= self
[1]
714 local prev_pos
= prev_info
.pos
715 local next_pos
= next_info
.pos
719 QuestHelper
:yieldIfNeeded(0.03*len
)
724 local info
= next_info
725 next_info
= self
[i
+1]
726 next_pos
= next_info
.pos
728 indexes
[info
.obj
] = i
730 d1
, d2
, pos
= info
.obj
:TravelTime2(prev_pos
, next_pos
, prev_info
.no_cache
)
732 QuestHelper
:yieldIfNeeded(1)
737 distance
= distance
+ d1
743 self
.distance
= distance
+ prev_info
.len
745 indexes
[self
[len
].obj
] = len
748 assert(self
:sanity())
751 function Route
:pathResetBegin()
752 for i
, info
in ipairs(self
) do
754 info
[1], info
[2], info
[3] = pos
[1].c
, pos
[3], pos
[4]
758 function Route
:pathResetEnd()
759 for i
, info
in ipairs(self
) do
760 -- Try to find a new position for this objective, near where we had it originally.
763 local a
, b
, c
= info
[1], info
[2], info
[3]
765 for z
, pl
in pairs(info
.obj
.p
) do
766 for i
, point
in ipairs(pl
) do
767 if a
== point
[1].c
then
768 local x
, y
= b
-point
[3], c
-point
[4]
770 if not p
or d2
< d
then
777 -- Assuming that there will still be positions on the same continents as before, i.e., locations are only added and not removed.
783 self
:recalculateDistances()
786 function Route
:recalculateDistances()
789 for i
= 0, #self
-1 do
790 self
[i
].len
= QuestHelper
:ComputeTravelTime(self
[i
].pos
, self
[i
+1].pos
)
791 self
.distance
= self
.distance
+ self
[i
].len
795 function Routing
:RoutingSetup()
796 Routing
.map_walker
= self
.qh
:CreateWorldMapWalker()
797 Routing
.add_swap
= {}
800 local routes
= Routing
.routes
801 local pos
= QuestHelper
.pos
802 local PlayerObjective
= self
.qh
:NewObjectiveObject() -- Pseudo-objective which reflects player's current position. Always at index 0 of each route.
803 PlayerObjective
.pos
= pos
804 PlayerObjective
.cat
= "loc" -- A special case of a location
805 PlayerObjective
.obj
= "Player's current position" -- Player shouldn't see this, so no need to localize
806 PlayerObjective
.icon_id
= 6 -- Don't think we'll need these; just filling them in for completeness
807 PlayerObjective
.o
= {pos
=pos
}
808 PlayerObjective
.fb
= {}
810 for i
= 1,15 do -- Create some empty routes to use for our population.
811 local new_rt
= { index
={ [PlayerObjective
]=0 },
813 [0]={ obj
=PlayerObjective
, pos
=pos
, len
=0, no_cache
=true }, -- Player's current position is always objective #0
814 id
=i
-- So I can keep track of which route is which; only for debugging.
816 setmetatable(new_rt
, Route
)
817 routes
[new_rt
] = true
820 -- All the routes are the same right now, so it doesn't matter which we're considering the best.
821 self
.best_route
= next(routes
)
822 self
.recheck_position
= 1
826 function Routing
:RouteUpdateRoutine()
827 local qh
= QuestHelper
828 local map_walker
= Routing
.map_walker
829 local minimap_dodad
= qh
.minimap_dodad
831 local route
= qh
.route
832 local to_add
, to_remove
, add_swap
= qh
.to_add
, qh
.to_remove
, self
.add_swap
834 local routes
= self
.routes
837 local best_route
= self
.best_route
839 local last_cache_clear
= GetTime()
841 ------ EVIL HACK OF DEBUG
844 while GetTime() < last_cache_clear
+ 5 do
849 -- We know the player will be at the target location at target_time, so fudge the numbers
850 -- to pretend we're traveling there.
852 pos
[1], pos
[3], pos
[4] = qh
.target
[1], qh
.target
[3], qh
.target
[4]
853 local extra_time
= math
.max(0, qh
.target_time
-time())
854 for i
, t
in ipairs(qh
.target
[2]) do
855 pos
[2][i
] = t
+extra_time
858 if not pos
[1] -- Need a valid position, in case the player was dead when they loaded the game.
859 or not UnitIsDeadOrGhost("player") then
860 -- Don't update the player's position if they're dead, assume they'll be returning to their corpse.
861 pos
[3], pos
[4] = qh
.Astrolabe
:TranslateWorldMapPosition(qh
.c
, qh
.z
, qh
.x
, qh
.y
, qh
.c
, 0)
864 pos
[1] = qh
.zone_nodes
[qh
.i
]
865 pos
[3], pos
[4] = pos
[3] * qh
.continent_scales_x
[qh
.c
], pos
[4] * qh
.continent_scales_y
[qh
.c
]
867 for i
, n
in ipairs(pos
[1]) do
869 for i
, j
in pairs(n
) do qh
:TextOut("[%q]=%s %s", i
, type(j
), tostring(j
) or "???") end
873 local a
, b
= n
.x
-pos
[3], n
.y
-pos
[4]
874 pos
[2][i
] = math
.sqrt(a
*a
+b
*b
)
879 local obj
= next(to_add
)
881 QuestHelper
:TextOut("dbghack")
882 QuestHelper
:TextOut(QuestHelper
:StringizeTable(to_add
))
883 obj
.filter_zone
= false
884 obj
.filter_watched
= false
885 QuestHelper
:TextOut(QuestHelper
:StringizeTable(obj
))
886 QuestHelper
:TextOut(tostring(obj
:Known()))
888 QuestHelper
:TextOut(QuestHelper
:StringizeTable(obj
))
890 QuestHelper
:TextOut("o")
891 QuestHelper
:TextOut(QuestHelper
:StringizeTable(obj
.o
))
893 QuestHelper
:TextOut("pp")
894 QuestHelper
:TextOut(QuestHelper
:StringizeTable(obj
.pos
))
895 QuestHelper
:TextOut(QuestHelper
:StringizeTable(obj
.p
))
896 QuestHelper
:TextOut(tostring(obj
:Known()))
898 local index
= best_route
:addObjectiveBest(obj
)
899 obj
.pos
= best_route
[index
].pos
901 QuestHelper
:TextOut(QuestHelper
:StringizeTable(obj
.pos
))
903 QuestHelper
:TextOut(qh
:ComputeTravelTime(pos
, obj
.pos
))
908 ------ EVIL HACK OF DEBUG
911 -- Clear caches out a bit
912 if GetTime() + 15 >= last_cache_clear
then
914 last_cache_clear
= GetTime()
917 -- Update the player's position data.
919 -- We know the player will be at the target location at target_time, so fudge the numbers
920 -- to pretend we're traveling there.
922 pos
[1], pos
[3], pos
[4] = qh
.target
[1], qh
.target
[3], qh
.target
[4]
923 local extra_time
= math
.max(0, qh
.target_time
-time())
924 for i
, t
in ipairs(qh
.target
[2]) do
925 pos
[2][i
] = t
+extra_time
928 if not pos
[1] -- Need a valid position, in case the player was dead when they loaded the game.
929 or not UnitIsDeadOrGhost("player") then
930 -- Don't update the player's position if they're dead, assume they'll be returning to their corpse.
931 pos
[3], pos
[4] = qh
.Astrolabe
:TranslateWorldMapPosition(qh
.c
, qh
.z
, qh
.x
, qh
.y
, qh
.c
, 0)
934 pos
[1] = qh
.zone_nodes
[qh
.i
]
935 pos
[3], pos
[4] = pos
[3] * qh
.continent_scales_x
[qh
.c
], pos
[4] * qh
.continent_scales_y
[qh
.c
]
937 for i
, n
in ipairs(pos
[1]) do
939 for i
, j
in pairs(n
) do qh
:TextOut("[%q]=%s %s", i
, type(j
), tostring(j
) or "???") end
943 local a
, b
= n
.x
-pos
[3], n
.y
-pos
[4]
944 pos
[2][i
] = math
.sqrt(a
*a
+b
*b
)
949 local changed
= false
952 if self
.recheck_position
> #route
then self
.recheck_position
= 1 end
953 local o
= route
[self
.recheck_position
]
956 o
.filter_zone
= o
.zones
[pos
[1].i
] == nil
957 o
.filter_watched
= not o
:IsWatched()
959 if not o
:Known() then
960 -- Objective was probably made to depend on an objective that we don't know about yet.
961 -- We add it to both lists, because although we need to remove it, we need it added again when we can.
962 -- This creates an inconsistancy, but it'll get fixed in the removal loop before anything has a chance to
968 if o
.swap_before
then
969 qh
:ReleaseTable(o
.before
)
970 o
.before
= o
.swap_before
975 qh
:ReleaseTable(o
.after
)
976 o
.after
= o
.swap_after
980 if o
.is_sharing
~= o
.want_share
then
981 o
.is_sharing
= o
.want_share
984 qh
:DoShareObjective(o
)
986 qh
:DoUnshareObjective(o
)
990 CalcObjectivePriority(o
)
992 -- Make sure the objective in best_route is still in a valid position.
993 -- Won't worry about other routes, they should forced into valid configurations by breeding.
995 -- This is a temporary, horrible hack - we want to do a "best" test without actually clobbering our old route, so we're making a new temporary one to jam our route in for now. In theory, AddObjectiveBest should, I don't know, add it in the *best place*, but at the moment it does not necessarily, thus this nastiness.
997 setmetatable(aobt
, getmetatable(best_route
))
998 for k
, v
in pairs(best_route
) do
1002 for k
, v
in ipairs(best_route
) do
1004 aobt
[k
] = QuestHelper
:CreateTable("AOBT idiocy")
1005 for t
, q
in pairs(v
) do
1008 aobt
.index
[aobt
[k
].obj
] = k
1011 aobt
[0] = QuestHelper
:CreateTable("AOBT idiocy")
1012 for t
, q
in pairs(best_route
[0]) do
1016 -- Actually duplicating a route is irritatingly hard (this is another thing which will be fixed eventually dammit)
1018 assert(aobt
:sanity())
1019 assert(best_route
:sanity())
1021 local old_distance
, old_index
= best_route
.distance
, best_route
:removeObjective(o
)
1022 local old_real_distance
= (best_route
.distance
or 0) + (best_route
[1] and qh
:ComputeTravelTime(pos
, best_route
[1].pos
) or 0) -- part of hack
1023 local new_index
, sanityfixed
= best_route
:addObjectiveBest(o
, old_index
, old_distance
)
1024 local new_real_distance
= (best_route
.distance
or 0) + (best_route
[1] and qh
:ComputeTravelTime(pos
, best_route
[1].pos
) or 0) -- part of hack
1025 -- not sure if best_route.distance can ever be nil or not, I was just getting errors I couldn't find for a while and ended up with that test included when I fixed the real error
1027 if new_real_distance
< old_real_distance
or sanityfixed
then -- More of the temporary hack
1028 -- If we're using the new path . . .
1030 if old_index
> new_index
then
1031 old_index
, new_index
= new_index
, old_index
1034 for i
= math
.max(1, old_index
-1), new_index
do
1035 local info
= best_route
[i
]
1036 local obj
= info
.obj
1042 if old_index
~= new_index
then
1043 if old_index
== 1 then
1044 minimap_dodad
:SetObjective(route
[1])
1050 -- . . . release our backup path
1051 for k
, v
in ipairs(aobt
) do QuestHelper
:ReleaseTable(v
) end
1052 QuestHelper
:ReleaseTable(aobt
[0])
1053 else -- hack (everything in this conditional besides the above chunk is a horrible hack)
1054 -- If we're using the old path . . .
1055 -- . . . release the *internals* of the new path, then copy everything over. Eugh.
1056 for k
, v
in ipairs(best_route
) do QuestHelper
:ReleaseTable(v
) end
1057 QuestHelper
:ReleaseTable(best_route
[0])
1058 while #best_route
> 0 do table.remove(best_route
) end
1059 for k
, v
in pairs(aobt
) do best_route
[k
] = v
end -- hack
1060 setmetatable(aobt
, Route
)
1061 assert(best_route
:sanity())
1064 -- this chunk of code used to live up by old_index ~= new_index, but it obviously no longer does. should probably be moved back once Best works again
1065 -- Maybe the bug he's referring to is the one I'm fighting with in this chunk of code? Hey dude, if you find a bug, *fix the damn bug don't work around it*
1066 --if old_index == new_index then
1067 -- We don't advance recheck_position unless the node doesn't get moved.
1068 -- TODO: As the this code is apparently bugged, it's gotten into an infinite loop of constantly swapping
1069 -- and hence never advancing. As a work around for now, we'll always advance.
1071 self
.recheck_position
= self
.recheck_position
+ 1
1076 -- Remove any waypoints if needed.
1078 local obj
= next(to_remove
)
1079 if not obj
then break end
1080 to_remove
[obj
] = nil
1082 if obj
.is_sharing
then
1083 obj
.is_sharing
= false
1084 qh
:DoUnshareObjective(obj
)
1087 for r
in pairs(routes
) do
1088 if r
== best_route
then
1089 local index
= r
:removeObjective(obj
)
1090 table.remove(route
, index
)
1092 minimap_dodad
:SetObjective(route
[1])
1095 r
:removeObjective(obj
)
1104 -- Add any waypoints if needed
1106 local obj
= next(to_add
)
1107 if not obj
then break end
1110 obj
.filter_zone
= obj
.zones
and obj
.zones
[pos
[1].i
] == nil
1111 obj
.filter_watched
= not obj
:IsWatched()
1114 obj
:PrepareRouting()
1116 obj
.filter_zone
= obj
.zones
[pos
[1].i
] == nil
1118 if obj
.filter_zone
and QuestHelper_Pref
.filter_zone
then
1119 -- Not going to add it, wrong zone.
1121 add_swap
[obj
] = true
1123 if not obj
.is_sharing
and obj
.want_share
then
1124 obj
.is_sharing
= true
1125 qh
:DoShareObjective(obj
)
1128 CalcObjectivePriority(obj
)
1130 for r
in pairs(routes
) do
1131 if r
== best_route
then
1132 local index
= r
:addObjectiveBest(obj
)
1133 obj
.pos
= r
[index
].pos
1134 table.insert(route
, index
, obj
)
1136 minimap_dodad
:SetObjective(route
[1])
1139 r
:addObjectiveFast(obj
)
1146 add_swap
[obj
] = true
1150 for obj
in pairs(add_swap
) do
1151 -- If one of the objectives we were considering adding was removed, it would be in both lists.
1152 -- That would be bad. We can't remove it because we haven't actually added it yet, so
1153 -- handle that special case here.
1154 if to_remove
[obj
] then
1155 to_remove
[obj
] = nil
1161 to_add
, add_swap
= add_swap
, to_add
1163 self
.add_swap
= add_swap
1165 if #best_route
> 1 then
1166 -- If there is 2 or more objectives, randomly combine routes to (hopefully) create something better than we had before.
1168 -- Calculate best_route first, so that if other routes are identical, we don't risk swapping with them and
1169 -- updating the map_walker.
1170 local best
, max_fitness
= best_route
, 1/(qh
:ComputeTravelTime(pos
, best_route
[1].pos
) + best_route
.distance
)
1171 best_route
.fitness
= max_fitness
1173 qh
:yieldIfNeeded(.3)
1175 for r
in pairs(routes
) do
1176 if r
~= best_route
then
1177 local fit
= 1/(qh
:ComputeTravelTime(pos
, r
[1].pos
)+r
.distance
)
1179 if fit
> max_fitness
then
1180 best
, max_fitness
= r
, fit
1182 qh
:yieldIfNeeded(.3)
1186 local to_breed
, score
1188 for r
in pairs(routes
) do
1190 local s
= math
.random()*r
.fitness
1191 if not to_breed
or s
< score
then
1192 to_breed
, score
= r
, s
1197 to_breed
:breed(routes
)
1199 if 1/(qh
:ComputeTravelTime(pos
, to_breed
[1].pos
)+to_breed
.distance
+improve_margin
) > max_fitness
then
1203 qh
:yieldIfNeeded(.3)
1205 if best
~= best_route
then
1207 assert(best
:sanity())
1208 assert(best_route
:sanity())
1211 self
.best_route
= best_route
1213 for i
, info
in ipairs(best
) do
1214 local obj
= info
.obj
1219 minimap_dodad
:SetObjective(route
[1])
1225 if qh
.defered_flight_times
then
1226 qh
:buildFlightTimes()
1227 qh
.defered_flight_times
= false
1228 assert(qh
.defered_graph_reset
)
1231 if qh
.defered_graph_reset
then
1232 qh
:yieldIfNeeded(10)
1234 for r
in pairs(routes
) do
1238 qh
.graph_in_limbo
= true
1240 qh
.graph_in_limbo
= false
1241 qh
.defered_graph_reset
= false
1243 for r
in pairs(routes
) do
1247 for i
, info
in ipairs(best_route
) do
1248 local obj
= info
.obj
1252 best_route
:recalculateDistances()
1254 minimap_dodad
:SetObjective(route
[1])
1256 QuestHelper
:SetTargetLocationRecalculate()
1258 for r
in pairs(routes
) do
1263 qh
:yieldIfNeeded(10)
1267 map_walker
:RouteChanged()
1270 assert(#route
== #best_route
)
1272 if route_pass
> 0 then
1273 route_pass
= route_pass
- 1
1276 -- temporary hack to cause more errors
1277 --qh.defered_graph_reset = true
1278 --qh.defered_flight_times = true
1286 function QuestHelper
:RunCoroutine()
1287 if coroutine
.status(update_route
) ~= "dead" then
1288 coroutine_running
= true
1289 -- At perf = 100%, we will run 5 ms / frame.
1290 coroutine_stop_time
= GetTime() + 4e-3 * QuestHelper_Pref
.perf_scale
* ((route_pass
> 0) and 5 or 1)
1291 local state
, err
= coroutine
.resume(update_route
, self
)
1292 coroutine_running
= false
1294 self
:TextOut("|cffff0000The routing co-routine just exploded|r: |cffffff77"..tostring(err
).."|r")
1295 QuestHelper_ErrorCatcher_ExplicitError(err
, "", "(Routing error)\n")
1300 function QuestHelper
:ForceRouteUpdate(passes
)
1301 route_pass
= math
.max(2, passes
or 1)
1304 function Routing
:Initialize()
1307 if coroutine
.coco
then
1308 -- coco allows yielding across c boundries, which allows me to use xpcall to get
1309 -- stack traces for coroutines without calls to yield resulting in thermal nuclear meltdown.
1311 -- This isn't part of WoW, I was using it in my driver program: Development/routetest
1313 update_route
= coroutine
.create(
1315 local state
, err
= xpcall(
1317 Routing
:RouteUpdateRoutine()
1321 return tostring(err
).."\n"..debugstack(2)
1323 return debug
.traceback(tostring(err
), 2)
1332 update_route
= coroutine
.create(function() Routing
:RouteUpdateRoutine() end)