1 QuestHelper_File
["routing.lua"] = "Development Version"
2 QuestHelper_Loadtime
["routing.lua"] = GetTime()
5 QuestHelper
.Routing
= {}
6 local Routing
= QuestHelper
.Routing
7 Routing
.qh
= QuestHelper
10 local improve_margin
= 1e-8
15 local function CalcObjectivePriority(obj
)
16 local priority
= obj
.priority
18 for o
in pairs(obj
.before
) do
20 priority
= math
.min(priority
, CalcObjectivePriority(o
))
24 obj
.real_priority
= priority
30 Routing
.Route
= Route
-- Make it available as a member
32 -- 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.
33 function Route
:sanity()
37 if QuestHelper
.Error
then
38 assert = function(a
, b
)
40 QuestHelper
:TextOut("Route:sanity(): id="..self
.id
.."; best_route="..Routing
.best_route
.id
)
41 QuestHelper
:Error(b
or "Assertion Failed")
48 --QuestHelper:TextOut(QuestHelper:StringizeTable(self))
50 --QuestHelper:TextOut(tostring(i))
51 --QuestHelper:TextOut(QuestHelper:StringizeTable(self[i]))
52 --QuestHelper:TextOut(tostring(self[i].len))
57 --QuestHelper:TextOut("sd: "..l.." rd: "..self.distance)
58 assert(math
.abs(l
-self
.distance
) < 0.0001, string.format("compare %f vs %f", l
, self
.distance
))
60 for i
, info
in ipairs(self
) do
61 assert(self
.index
[info
.obj
] == i
)
65 for obj
, i
in pairs(self
.index
) do
66 assert(self
[i
].obj
== obj
)
70 local l
= QuestHelper
:ComputeTravelTime(self
[i
].pos
, self
[i
+1].pos
, true)
71 assert(math
.abs(l
-self
[i
].len
) < 0.0001, "Compare at "..i
..": "..l
.." vs "..self
[i
].len
)
77 function Route
:findObjectiveRange(obj
, passes
)
81 table.insert(lines, string.format("QuestHelper objectiverange for %s (pri %d)", obj.obj, obj.real_priority))
83 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))
88 if obj
.real_priority
> self
[mn
].obj
.real_priority
or obj
.after
[self
[mn
].obj
] or self
[mn
].obj
.before
[obj
] then break end
92 mn
= mn
+ 1 -- we went too far, actually
95 while mx
< #self
+ 1 do
96 if obj
.real_priority
< self
[mx
].obj
.real_priority
or obj
.before
[self
[mx
].obj
] or self
[mx
].obj
.after
[obj
] then break end
100 --table.insert(lines, string.format("temp results is %d %d", mn, mx))
102 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.
103 local mid
= math
.ceil((mx
+ mn
) / 2)
108 --table.insert(lines, string.format("overall: %d %d", mn, mx))
111 if passes and passes > 90 then
112 for k, v in pairs(lines) do QuestHelper:TextOut(v) end
113 QuestHelper:TextOut(string.format("overall: %d %d", mn, mx))
118 local omn, omx = self:OldFindObjectiveRange(obj)
120 if mn ~= omn or mx ~= omx then
121 for k, v in pairs(lines) do QuestHelper:TextOut(v) end
122 QuestHelper:TextOut(string.format("overall: %d %d vs %d %d", mn, mx, omn, omx))
123 lolcrash = (lolcrash or 0) + 1
130 function Route
:addObjectiveFast(obj
)
131 assert(self
:sanity())
132 local indexes
= self
.index
134 local info
= QuestHelper
:CreateTable()
135 assert(not indexes
[obj
])
143 d
, info
.pos
= obj
:TravelTime(self
[0].pos
, true)
149 local player_pos
= QuestHelper
.pos
150 local pos
= obj
.location
151 local c
, x
, y
= pos
[1].c
, pos
[3], pos
[4]
153 local mn
, mx
= self
:findObjectiveRange(obj
)
156 for i
= mn
, math
.min(mx
, len
) do
157 local p
= self
[i
].pos
159 local u
, v
= p
[3]-x
, p
[4]-y
161 if not index
or d2
< distsqr
then
162 index
, distsqr
= i
, d2
168 -- No nodes with the same continent already.
169 -- If the same continent as the player, add to start of list, otherwise add to end of the list.
170 index
= c
== player_pos
[1].c
and mx
or mx
173 -- The next question, do I insert at that point, or do I insert after it?
174 if index
~= mx
and index
<= len
then
175 local p1
= self
[index
].pos
183 p0
= self
[index
-1].pos
186 local oldstart
, newstart
189 local u
, v
= p0
[3]-x
, p0
[4]-y
190 newstart
= math
.sqrt(u
*u
+v
*v
)
191 u
, v
= p0
[3]-p1
[3], p0
[4]-p1
[4]
192 oldstart
= math
.sqrt(u
*u
+v
*v
)
200 p2
= self
[index
+1].pos
204 if p2
and p2
[1].c
== c
then
205 local u
, v
= p2
[3]-x
, p2
[4]-y
206 newend
= math
.sqrt(u
*u
+v
*v
)
207 u
, v
= p2
[3]-p1
[3], p2
[4]-p1
[4]
208 oldend
= math
.sqrt(u
*u
+v
*v
)
214 if oldstart
+newend
< newstart
+oldend
then
221 QH_Timeslice_Yield() -- The above checks don't require much effort.
224 local previnfo
= self
[index
-1]
227 d
, info
.pos
= obj
:TravelTime(previnfo
.pos
)
231 self
.distance
= self
.distance
+ d
235 local previnfo
= self
[index
-1]
236 d1
, d2
, info
.pos
= obj
:TravelTime2(previnfo
.pos
, self
[index
].pos
, previnfo
.no_cache
)
239 self
.distance
= self
.distance
+ (d1
- previnfo
.len
+ d2
)
245 -- Finally, insert the objective.
246 table.insert(self
, index
, info
)
249 -- Fix indexes of shifted elements.
250 for i
= index
+1,len
+1 do
251 local obj
= self
[i
].obj
252 assert(indexes
[obj
] == i
-1)
256 assert(self
:sanity())
261 function Route
:addObjectiveBest(obj
, old_index
, old_distance
)
262 assert(self
:sanity())
264 local indexes
= self
.index
266 local info
= QuestHelper
:CreateTable()
267 assert(not indexes
[obj
])
273 self
.distance
, info
.pos
= obj
:TravelTime(self
[0].pos
, true)
275 self
[0].len
= self
.distance
280 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.)
282 local best_index
, best_delta
, best_d1
, best_d2
, best_p
283 local no_cache
, prev_pos
, prev_len
284 local mn
, mx
= self
:findObjectiveRange(obj
)
286 if old_index
and mn
<= old_index
and old_index
<= mx
then
287 -- We're trying to re-evaluate it, and it could remain in the same place.
288 -- So that place is our starting best known place.
289 best_index
, best_delta
= old_index
, old_distance
- self
.distance
291 if best_delta
< 0 then
292 -- Somehow, removing the objective actually made the route worse...
293 -- Just re-figure things from scratch.
294 -- TODO: THIS SHOULD NEVER HAPPEN dear god find out what's causing this and stop it
295 --QuestHelper:TextOut("made route worse wtf")
296 best_index
, best_delta
= nil, nil
300 local pinfo
= self
[mn
-1]
301 no_cache
, prev_pos
, prev_len
= pinfo
.no_cache
, pinfo
.pos
, pinfo
.len
303 for i
= mn
, math
.min(mx
, len
) do
304 assert(prev_pos
== self
[i
-1].pos
)
309 local d1
, d2
, p
= obj
:TravelTime2(prev_pos
, pos
, no_cache
)
313 local delta
= d1
+ d2
- prev_len
315 if not best_index
or ((delta
+ improve_margin
) < best_delta
) or ((i
== best_index
) and not best_d1
) then
317 -- * First item we reach
318 -- * Better than previous best
319 -- * We're looking at our best already. But we just got here; how could this be best?
320 -- If this was our prior location and we didn't find anything better earlier in the route,
321 -- that's how. Save the specifics, 'cause we didn't compute them when setting up.
322 best_index
, best_delta
, best_d1
, best_d2
, best_p
= i
, delta
, d1
, d2
, p
332 assert(prev_pos
== self
[len
].pos
)
333 local delta
, p
= obj
:TravelTime(prev_pos
, no_cache
)
337 if not best_index
or ((delta
+ improve_margin
) < best_delta
) or ((mx
== best_index
) and not best_d1
) then
340 self
[len
].len
= delta
341 self
.distance
= self
.distance
+ delta
342 table.insert(self
, info
)
345 assert(self
:sanity())
354 local pinfo
= self
[best_index
-1]
355 self
.distance
= self
.distance
+ (best_d1
- pinfo
.len
+ best_d2
)
358 table.insert(self
, best_index
, info
)
360 -- QuestHelper:Assert(math.abs(QuestHelper:ComputeTravelTime(self[best_index-1].pos, self[best_index].pos) - self[best_index-1].len) < 0.0001, "aaaaargh")
361 --[[ -- I don't think this is necessary now that TravelTime2 explicitly does this internally, but I'm keeping it anyway.
362 self.distance = self.distance - self[best_index-1].len
363 self[best_index-1].len = QuestHelper:ComputeTravelTime(self[best_index-1].pos, self[best_index].pos, true)
364 self.distance = self.distance + self[best_index-1].len
367 indexes
[obj
] = best_index
369 for i
= best_index
+1,len
+1 do
370 assert(indexes
[self
[i
].obj
] == i
-1)
371 indexes
[self
[i
].obj
] = i
374 if not old_index
or (mn
> old_index
or old_index
> mx
) and mn
<= best_index
and best_index
<= mx
then
375 -- 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
379 assert(self
:sanity())
381 return best_index
, sanityfixed
384 function Route
:removeObjective(obj
)
385 assert(self
:sanity())
387 local indexes
= self
.index
388 local index
= indexes
[obj
]
389 local old_distance
= self
.distance
392 local info
= self
[index
]
393 assert(info
.obj
== obj
)
396 Removing end item: subtract last distance, nothing to recalculate
397 Removing other item: recalculate location of next objective, between prior position and objective after next
398 Special case: if there is no location after next, just recalc location of next objective
400 if index
== #self
then
401 self
.distance
= self
.distance
- self
[index
-1].len
402 self
[index
-1].len
= 0
404 local pinfo
= self
[index
-1]
405 local info1
= self
[index
+1]
406 local info2
= self
[index
+2]
407 local no_cache
= (index
== 1)
412 d1
, d2
, info1
.pos
= info1
.obj
:TravelTime2(pinfo
.pos
, info2
.pos
, no_cache
)
414 self
.distance
= self
.distance
- pinfo
.len
- info
.len
- info1
.len
+ d1
+ d2
417 d1
, info1
.pos
= info1
.obj
:TravelTime(pinfo
.pos
, no_cache
)
419 self
.distance
= self
.distance
- pinfo
.len
- info
.len
+ d1
425 QuestHelper
:ReleaseTable(info
)
427 table.remove(self
, index
)
429 for i
= index
,#self
do
430 -- Fix indexes of shifted elements.
431 local obj
= self
[i
].obj
432 assert(indexes
[obj
] == i
+1)
436 assert(self
:sanity())
437 -- assert(self.distance <= old_distance)
445 function Route
:breed(route_map
)
446 local indexes
= self
.index
452 local prev_pos
= QuestHelper
.pos
453 assert(self
[0].pos
== prev_pos
)
455 -- Pick which objective goes first, selecting from first objective of each route,
456 -- and scaling by the route's fitness and distance from player, with a random adjustment factor.
457 -- While we're at it, record some data about the fitness of adjacent objectives
458 for route
in pairs(route_map
) do
459 assert(route
:sanity())
460 local fit
= route
.fitness
461 local pos
= route
[1].pos
464 if prev_pos
[1].c
== pos
[1].c
then
465 local u
, v
= prev_pos
[3]-pos
[3], prev_pos
[4]-pos
[4]
466 w
= math
.sqrt(u
*u
+v
*v
)
471 w
= fit
* math
.random() / w
473 if not info
or w
> r
then
474 info
, r
= route
[1], w
478 local obj
= route
[i
].obj
479 local tbl
= links
[obj
]
481 tbl
= QuestHelper
:CreateTable()
486 local info
= route
[i
-1]
487 local obj2
= info
.obj
488 tbl
[info
] = (tbl
[info
] or 0) + fit
492 local info
= route
[i
+1]
493 local obj2
= info
.obj
494 if obj
.real_priority
<= obj2
.real_priority
or obj
.before
[obj2
] then
495 tbl
[info
] = (tbl
[info
] or 0) + fit
503 -- Record info for the 'Player Position' objective, so we don't mess it up later
504 seen
[self
[0].obj
] = self
[0].pos
506 -- Record the objective that we chose to put first
509 seen
[obj
] = info
.pos
-- Save its position, because we don't want to clobber any of the info objects yet
515 -- Scan the rest of the places in the route, and pick objectives to go there
520 -- Scan the list of scores from the prior objective
521 for i
, weight
in pairs(last
) do
523 -- Only consider an item if we have scores for that item
526 if prev_pos
[1].c
== pos
[1].c
then
527 local u
, v
= prev_pos
[3]-pos
[3], prev_pos
[4]-pos
[4]
528 w
= math
.sqrt(u
*u
+v
*v
)
533 w
= weight
* math
.random() / w
535 if not info
or w
> r
then
542 -- In case we had no valid scores, scan the remaining objectives and score by distance
544 for obj
in pairs(links
) do
547 if prev_pos
[1] == pos
[1] then
549 local u
, v
= prev_pos
[3]-pos
[3], prev_pos
[4]-pos
[4]
550 w
= math
.sqrt(u
*u
+v
*v
)
551 elseif prev_pos
[1].c
== pos
[1].c
then
552 -- Same continent. -- Assume twices as long.
553 local u
, v
= prev_pos
[3]-pos
[3], prev_pos
[4]-pos
[4]
554 w
= 2*math
.sqrt(u
*u
+v
*v
)
556 -- Different continent. Assume fixed value of 5 minutes.
560 w
= math
.random() / w
562 if not info
or w
> r
then
563 local route
= next(route_map
)
564 info
, r
= route
[route
.index
[obj]]
, w
571 -- Add the selected item to the route
576 assert(info
.obj
== obj
)
578 -- 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
579 local link
= links
[obj
]
581 QuestHelper
:ReleaseTable(last
)
587 -- Clean up the last table
588 QuestHelper
:ReleaseTable(last
)
590 -- Now that we've got our objectives lined up, fill in the info objects with the positions we saved
591 for obj
, i
in pairs(indexes
) do
594 info
.obj
, info
.pos
= obj
, seen
[obj
]
598 -- Now randomly randomize some of the route (aka mutation)
599 while math
.random() > 0.3 do
600 local l
= math
.floor(math
.random()^
1.6*(len
-1))+1
601 local i
= math
.random(1, len
-l
)
604 -- Reverse a chunk of the route
606 self
[i
+k
], self
[j
-k
] = self
[j
-k
], self
[i
+k
]
610 -- But wait, after all that some objectives might violate the rules. Make sure the route follows
613 -- 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.
614 -- Since the entire thing is internally inconsistent anyway, we're just gonna try to consistentize it.
616 local valid_items
= {}
617 for k
, v
in ipairs(self
) do
618 valid_items
[v
.obj
] = true
620 for k
, v
in ipairs(self
) do
621 for b
in pairs(v
.obj
.before
) do
622 if valid_items
[b
] then
623 b
.after
[v
.obj
] = true
626 for a
in pairs(v
.obj
.after
) do
627 if valid_items
[a
] then
628 a
.before
[v
.obj
] = true
633 -- Because priorities might have been changed in here, we next make absolutely sure we have up-to-date priorities.
634 for k
, v
in ipairs(self
) do
635 CalcObjectivePriority(v
.obj
)
638 -- Have I mentioned I hate this codebase yet?
644 local invalid_passes
= 0
645 --local output_strings = {}
648 invalid_passes
= invalid_passes
+ 1
650 --[[if invalid_passes >= 100 then
651 for k, v in pairs(output_strings) do
652 QuestHelper:TextOut(v)
656 if invalid_passes
>= 100 then
658 QuestHelper
.mutation_passes_exceeded
= true
661 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
665 --[[for i = 1, #self do
666 local mn, mx = self:findObjectiveRange(self[i].obj, invalid_passes)
667 table.insert(output_strings, string.format("%d is mn mx %d %d (%s)", i, mn, mx, self[i].obj.obj))
670 -- Make sure all the objectives have valid positions in the list.
672 local mn
, mx
, tabi
= self
:findObjectiveRange(info
.obj
, invalid_passes
)
673 --if invalid_passes > 90 then for k, v in pairs(tabi) do table.insert(output_strings, v) end end
675 -- In theory, 'i' shouldn't be increased here, as the next
676 -- element will be shifted down into the current position.
678 -- However, it is possible for an infinite loop to be created
679 -- by this, with a small range of objectives constantly
682 -- So, I mark the route as invalid and go through it another time.
683 -- It's probably still possible to get into an infinite loop,
684 -- but it seems much less likely.
686 table.insert(self
, mn
, info
)
687 table.remove(self
, i
)
689 --table.insert(output_strings, string.format("shifting %d into %d", i, mn))
691 table.remove(self
, i
)
692 table.insert(self
, mx
, info
)
694 --table.insert(output_strings, string.format("shifting %d into %d", i, mx))
698 --table.insert(output_strings, "pass done")
701 -- Now that we've chosen a route, re-calculate the cost of each leg of the route
703 local prev_info
= self
[0]
704 local next_info
= self
[1]
705 local prev_pos
= prev_info
.pos
706 local next_pos
= next_info
.pos
715 local info
= next_info
716 next_info
= self
[i
+1]
717 next_pos
= next_info
.pos
719 indexes
[info
.obj
] = i
721 d1
, d2
, pos
= info
.obj
:TravelTime2(prev_pos
, next_pos
, prev_info
.no_cache
)
728 distance
= distance
+ d1
734 self
.distance
= distance
+ prev_info
.len
736 indexes
[self
[len
].obj
] = len
739 assert(self
:sanity())
742 function Route
:pathResetBegin()
743 for i
, info
in ipairs(self
) do
745 info
[1], info
[2], info
[3] = pos
[1].c
, pos
[3], pos
[4]
749 function Route
:pathResetEnd()
750 for i
, info
in ipairs(self
) do
751 -- Try to find a new position for this objective, near where we had it originally.
754 local a
, b
, c
= info
[1], info
[2], info
[3]
756 for z
, pl
in pairs(info
.obj
.p
) do
757 for i
, point
in ipairs(pl
) do
758 if a
== point
[1].c
then
759 local x
, y
= b
-point
[3], c
-point
[4]
761 if not p
or d2
< d
then
768 -- Assuming that there will still be positions on the same continents as before, i.e., locations are only added and not removed.
774 self
:recalculateDistances()
777 function Route
:recalculateDistances()
780 for i
= 0, #self
-1 do
781 self
[i
].len
= QuestHelper
:ComputeTravelTime(self
[i
].pos
, self
[i
+1].pos
)
782 self
.distance
= self
.distance
+ self
[i
].len
786 function Routing
:RoutingSetup()
787 Routing
.map_walker
= self
.qh
:CreateWorldMapWalker()
788 Routing
.add_swap
= {}
791 local routes
= Routing
.routes
792 local pos
= QuestHelper
.pos
793 local PlayerObjective
= self
.qh
:NewObjectiveObject() -- Pseudo-objective which reflects player's current position. Always at index 0 of each route.
794 PlayerObjective
.pos
= pos
795 PlayerObjective
.cat
= "loc" -- A special case of a location
796 PlayerObjective
.obj
= "Player's current position" -- Player shouldn't see this, so no need to localize
797 PlayerObjective
.icon_id
= 6 -- Don't think we'll need these; just filling them in for completeness
798 PlayerObjective
.o
= {pos
=pos
}
799 PlayerObjective
.fb
= {}
801 for i
= 1,15 do -- Create some empty routes to use for our population.
802 local new_rt
= { index
={ [PlayerObjective
]=0 },
804 [0]={ obj
=PlayerObjective
, pos
=pos
, len
=0, no_cache
=true }, -- Player's current position is always objective #0
805 id
=i
-- So I can keep track of which route is which; only for debugging.
807 setmetatable(new_rt
, Route
)
808 routes
[new_rt
] = true
811 -- All the routes are the same right now, so it doesn't matter which we're considering the best.
812 self
.best_route
= next(routes
)
813 self
.recheck_position
= 1
817 function Routing
:RouteUpdateRoutine()
818 local qh
= QuestHelper
819 local map_walker
= Routing
.map_walker
820 local minimap_dodad
= qh
.minimap_dodad
822 local route
= qh
.route
823 local to_add
, to_remove
, add_swap
= qh
.to_add
, qh
.to_remove
, self
.add_swap
825 local routes
= self
.routes
828 local best_route
= self
.best_route
830 local last_cache_clear
= GetTime()
832 ------ EVIL HACK OF DEBUG
835 while GetTime() < last_cache_clear
+ 5 do
840 -- We know the player will be at the target location at target_time, so fudge the numbers
841 -- to pretend we're traveling there.
843 pos
[1], pos
[3], pos
[4] = qh
.target
[1], qh
.target
[3], qh
.target
[4]
844 local extra_time
= math
.max(0, qh
.target_time
-time())
845 for i
, t
in ipairs(qh
.target
[2]) do
846 pos
[2][i
] = t
+extra_time
849 if not pos
[1] -- Need a valid position, in case the player was dead when they loaded the game.
850 or not UnitIsDeadOrGhost("player") then
851 -- Don't update the player's position if they're dead, assume they'll be returning to their corpse.
852 pos
[3], pos
[4] = qh
.Astrolabe
:TranslateWorldMapPosition(qh
.c
, qh
.z
, qh
.x
, qh
.y
, qh
.c
, 0)
855 pos
[1] = qh
.zone_nodes
[qh
.i
]
856 pos
[3], pos
[4] = pos
[3] * qh
.continent_scales_x
[qh
.c
], pos
[4] * qh
.continent_scales_y
[qh
.c
]
858 for i
, n
in ipairs(pos
[1]) do
860 for i
, j
in pairs(n
) do qh
:TextOut("[%q]=%s %s", i
, type(j
), tostring(j
) or "???") end
864 local a
, b
= n
.x
-pos
[3], n
.y
-pos
[4]
865 pos
[2][i
] = math
.sqrt(a
*a
+b
*b
)
870 local obj
= next(to_add
)
872 QuestHelper
:TextOut("dbghack")
873 QuestHelper
:TextOut(QuestHelper
:StringizeTable(to_add
))
874 obj
.filter_zone
= false
875 obj
.filter_watched
= false
876 QuestHelper
:TextOut(QuestHelper
:StringizeTable(obj
))
877 QuestHelper
:TextOut(tostring(obj
:Known()))
879 QuestHelper
:TextOut(QuestHelper
:StringizeTable(obj
))
881 QuestHelper
:TextOut("o")
882 QuestHelper
:TextOut(QuestHelper
:StringizeTable(obj
.o
))
884 QuestHelper
:TextOut("pp")
885 QuestHelper
:TextOut(QuestHelper
:StringizeTable(obj
.pos
))
886 QuestHelper
:TextOut(QuestHelper
:StringizeTable(obj
.p
))
887 QuestHelper
:TextOut(tostring(obj
:Known()))
889 local index
= best_route
:addObjectiveBest(obj
)
890 obj
.pos
= best_route
[index
].pos
892 QuestHelper
:TextOut(QuestHelper
:StringizeTable(obj
.pos
))
894 QuestHelper
:TextOut(qh
:ComputeTravelTime(pos
, obj
.pos
))
899 ------ EVIL HACK OF DEBUG
902 -- Clear caches out a bit
903 if GetTime() + 15 >= last_cache_clear
then
905 last_cache_clear
= GetTime()
908 -- Update the player's position data.
910 -- We know the player will be at the target location at target_time, so fudge the numbers
911 -- to pretend we're traveling there.
913 pos
[1], pos
[3], pos
[4] = qh
.target
[1], qh
.target
[3], qh
.target
[4]
914 local extra_time
= math
.max(0, qh
.target_time
-time())
915 for i
, t
in ipairs(qh
.target
[2]) do
916 pos
[2][i
] = t
+extra_time
919 if not pos
[1] -- Need a valid position, in case the player was dead when they loaded the game.
920 or not UnitIsDeadOrGhost("player") then
921 -- Don't update the player's position if they're dead, assume they'll be returning to their corpse.
922 pos
[3], pos
[4] = qh
.Astrolabe
:TranslateWorldMapPosition(qh
.c
, qh
.z
, qh
.x
, qh
.y
, qh
.c
, 0)
925 pos
[1] = qh
.zone_nodes
[qh
.i
]
926 pos
[3], pos
[4] = pos
[3] * qh
.continent_scales_x
[qh
.c
], pos
[4] * qh
.continent_scales_y
[qh
.c
]
928 for i
, n
in ipairs(pos
[1]) do
930 for i
, j
in pairs(n
) do qh
:TextOut("[%q]=%s %s", i
, type(j
), tostring(j
) or "???") end
934 local a
, b
= n
.x
-pos
[3], n
.y
-pos
[4]
935 pos
[2][i
] = math
.sqrt(a
*a
+b
*b
)
940 local changed
= false
943 if self
.recheck_position
> #route
then self
.recheck_position
= 1 end
944 local o
= route
[self
.recheck_position
]
947 o
.filter_zone
= o
.zones
[pos
[1].i
] == nil
948 o
.filter_watched
= not o
:IsWatched()
950 if not o
:Known() then
951 -- Objective was probably made to depend on an objective that we don't know about yet.
952 -- We add it to both lists, because although we need to remove it, we need it added again when we can.
953 -- This creates an inconsistancy, but it'll get fixed in the removal loop before anything has a chance to
959 if o
.swap_before
then
960 qh
:ReleaseTable(o
.before
)
961 o
.before
= o
.swap_before
966 qh
:ReleaseTable(o
.after
)
967 o
.after
= o
.swap_after
971 if o
.is_sharing
~= o
.want_share
then
972 o
.is_sharing
= o
.want_share
975 qh
:DoShareObjective(o
)
977 qh
:DoUnshareObjective(o
)
981 CalcObjectivePriority(o
)
983 -- Make sure the objective in best_route is still in a valid position.
984 -- Won't worry about other routes, they should forced into valid configurations by breeding.
986 -- 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.
988 setmetatable(aobt
, getmetatable(best_route
))
989 for k
, v
in pairs(best_route
) do
993 for k
, v
in ipairs(best_route
) do
995 aobt
[k
] = QuestHelper
:CreateTable("AOBT idiocy")
996 for t
, q
in pairs(v
) do
999 aobt
.index
[aobt
[k
].obj
] = k
1002 aobt
[0] = QuestHelper
:CreateTable("AOBT idiocy")
1003 for t
, q
in pairs(best_route
[0]) do
1007 -- Actually duplicating a route is irritatingly hard (this is another thing which will be fixed eventually dammit)
1009 assert(aobt
:sanity())
1010 assert(best_route
:sanity())
1012 local old_distance
, old_index
= best_route
.distance
, best_route
:removeObjective(o
)
1013 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
1014 local new_index
, sanityfixed
= best_route
:addObjectiveBest(o
, old_index
, old_distance
)
1015 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
1016 -- 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
1018 if new_real_distance
< old_real_distance
or sanityfixed
then -- More of the temporary hack
1019 -- If we're using the new path . . .
1021 if old_index
> new_index
then
1022 old_index
, new_index
= new_index
, old_index
1025 for i
= math
.max(1, old_index
-1), new_index
do
1026 local info
= best_route
[i
]
1027 local obj
= info
.obj
1033 if old_index
~= new_index
then
1034 if old_index
== 1 then
1035 minimap_dodad
:SetObjective(route
[1])
1041 -- . . . release our backup path
1042 for k
, v
in ipairs(aobt
) do QuestHelper
:ReleaseTable(v
) end
1043 QuestHelper
:ReleaseTable(aobt
[0])
1044 else -- hack (everything in this conditional besides the above chunk is a horrible hack)
1045 -- If we're using the old path . . .
1046 -- . . . release the *internals* of the new path, then copy everything over. Eugh.
1047 for k
, v
in ipairs(best_route
) do QuestHelper
:ReleaseTable(v
) end
1048 QuestHelper
:ReleaseTable(best_route
[0])
1049 while #best_route
> 0 do table.remove(best_route
) end
1050 for k
, v
in pairs(aobt
) do best_route
[k
] = v
end -- hack
1051 setmetatable(aobt
, Route
)
1052 assert(best_route
:sanity())
1055 -- 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
1056 -- 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*
1057 --if old_index == new_index then
1058 -- We don't advance recheck_position unless the node doesn't get moved.
1059 -- TODO: As the this code is apparently bugged, it's gotten into an infinite loop of constantly swapping
1060 -- and hence never advancing. As a work around for now, we'll always advance.
1062 self
.recheck_position
= self
.recheck_position
+ 1
1067 -- Remove any waypoints if needed.
1069 local obj
= next(to_remove
)
1070 if not obj
then break end
1071 to_remove
[obj
] = nil
1073 if obj
.is_sharing
then
1074 obj
.is_sharing
= false
1075 qh
:DoUnshareObjective(obj
)
1078 for r
in pairs(routes
) do
1079 if r
== best_route
then
1080 local index
= r
:removeObjective(obj
)
1081 table.remove(route
, index
)
1083 minimap_dodad
:SetObjective(route
[1])
1086 r
:removeObjective(obj
)
1095 -- Add any waypoints if needed
1097 local obj
= next(to_add
)
1098 if not obj
then break end
1101 obj
.filter_zone
= obj
.zones
and obj
.zones
[pos
[1].i
] == nil
1102 obj
.filter_watched
= not obj
:IsWatched()
1105 obj
:PrepareRouting()
1107 obj
.filter_zone
= obj
.zones
[pos
[1].i
] == nil
1109 if obj
.filter_zone
and QuestHelper_Pref
.filter_zone
then
1110 -- Not going to add it, wrong zone.
1112 add_swap
[obj
] = true
1114 if not obj
.is_sharing
and obj
.want_share
then
1115 obj
.is_sharing
= true
1116 qh
:DoShareObjective(obj
)
1119 CalcObjectivePriority(obj
)
1121 for r
in pairs(routes
) do
1122 if r
== best_route
then
1123 local index
= r
:addObjectiveBest(obj
)
1124 obj
.pos
= r
[index
].pos
1125 table.insert(route
, index
, obj
)
1127 minimap_dodad
:SetObjective(route
[1])
1130 r
:addObjectiveFast(obj
)
1137 add_swap
[obj
] = true
1141 for obj
in pairs(add_swap
) do
1142 -- If one of the objectives we were considering adding was removed, it would be in both lists.
1143 -- That would be bad. We can't remove it because we haven't actually added it yet, so
1144 -- handle that special case here.
1145 if to_remove
[obj
] then
1146 to_remove
[obj
] = nil
1152 to_add
, add_swap
= add_swap
, to_add
1154 self
.add_swap
= add_swap
1156 if #best_route
> 1 then
1157 -- If there is 2 or more objectives, randomly combine routes to (hopefully) create something better than we had before.
1159 -- Calculate best_route first, so that if other routes are identical, we don't risk swapping with them and
1160 -- updating the map_walker.
1161 local best
, max_fitness
= best_route
, 1/(qh
:ComputeTravelTime(pos
, best_route
[1].pos
) + best_route
.distance
)
1162 best_route
.fitness
= max_fitness
1164 QH_Timeslice_Yield()
1166 for r
in pairs(routes
) do
1167 if r
~= best_route
then
1168 local fit
= 1/(qh
:ComputeTravelTime(pos
, r
[1].pos
)+r
.distance
)
1170 if fit
> max_fitness
then
1171 best
, max_fitness
= r
, fit
1173 QH_Timeslice_Yield()
1177 local to_breed
, score
1179 for r
in pairs(routes
) do
1181 local s
= math
.random()*r
.fitness
1182 if not to_breed
or s
< score
then
1183 to_breed
, score
= r
, s
1188 to_breed
:breed(routes
)
1190 if 1/(qh
:ComputeTravelTime(pos
, to_breed
[1].pos
)+to_breed
.distance
+improve_margin
) > max_fitness
then
1194 QH_Timeslice_Yield()
1196 if best
~= best_route
then
1198 assert(best
:sanity())
1199 assert(best_route
:sanity())
1202 self
.best_route
= best_route
1204 for i
, info
in ipairs(best
) do
1205 local obj
= info
.obj
1210 minimap_dodad
:SetObjective(route
[1])
1216 if qh
.defered_flight_times
then
1217 qh
:buildFlightTimes()
1218 qh
.defered_flight_times
= false
1219 assert(qh
.defered_graph_reset
)
1222 if qh
.defered_graph_reset
then
1223 QH_Timeslice_Yield()
1225 for r
in pairs(routes
) do
1229 qh
.graph_in_limbo
= true
1231 qh
.graph_in_limbo
= false
1232 qh
.defered_graph_reset
= false
1234 for r
in pairs(routes
) do
1238 for i
, info
in ipairs(best_route
) do
1239 local obj
= info
.obj
1243 best_route
:recalculateDistances()
1245 minimap_dodad
:SetObjective(route
[1])
1247 QuestHelper
:SetTargetLocationRecalculate()
1249 for r
in pairs(routes
) do
1254 QH_Timeslice_Yield()
1258 map_walker
:RouteChanged()
1261 assert(#route
== #best_route
)
1263 -- temporary hack to cause more errors
1264 --qh.defered_graph_reset = true
1265 --qh.defered_flight_times = true
1267 QH_Timeslice_Yield()
1271 function Routing
:Initialize()
1274 QH_Timeslice_Add(function() Routing
:RouteUpdateRoutine() end, "routing")
1275 QH_Timeslice_Toggle("routing", false)
1278 if coroutine.coco then
1279 -- coco allows yielding across c boundries, which allows me to use xpcall to get
1280 -- stack traces for coroutines without calls to yield resulting in thermal nuclear meltdown.
1282 -- This isn't part of WoW, I was using it in my driver program: Development/routetest
1284 update_route = coroutine.create(
1286 local state, err = xpcall(
1288 Routing:RouteUpdateRoutine()
1292 return tostring(err).."\n"..debugstack(2)
1294 return debug.traceback(tostring(err), 2)
1303 update_route = coroutine.create(function() Routing:RouteUpdateRoutine() end)