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 function CalcObjectivePriority(obj
)
15 local priority
= obj
.priority
17 for o
in pairs(obj
.before
) do
19 priority
= math
.min(priority
, CalcObjectivePriority(o
))
23 obj
.real_priority
= priority
29 Routing
.Route
= Route
-- Make it available as a member
31 -- 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.
32 function Route
:sanity()
36 if QuestHelper
.Error
then
37 assert = function(a
, b
)
39 QuestHelper
:TextOut("Route:sanity(): id="..self
.id
.."; best_route="..Routing
.best_route
.id
)
40 QuestHelper
:Error(b
or "Assertion Failed")
47 --QuestHelper:TextOut(QuestHelper:StringizeTable(self))
49 --QuestHelper:TextOut(tostring(i))
50 --QuestHelper:TextOut(QuestHelper:StringizeTable(self[i]))
51 --QuestHelper:TextOut(tostring(self[i].len))
56 --QuestHelper:TextOut("sd: "..l.." rd: "..self.distance)
57 assert(math
.abs(l
-self
.distance
) < 0.0001, string.format("compare %f vs %f", l
, self
.distance
))
59 for i
, info
in ipairs(self
) do
60 assert(self
.index
[info
.obj
] == i
)
64 for obj
, i
in pairs(self
.index
) do
65 assert(self
[i
].obj
== obj
)
69 local l
= QuestHelper
:ComputeTravelTime(self
[i
].pos
, self
[i
+1].pos
, true)
70 assert(math
.abs(l
-self
[i
].len
) < 0.0001, "Compare at "..i
..": "..l
.." vs "..self
[i
].len
)
76 function Route
:findObjectiveRange(obj
, passes
)
80 table.insert(lines, string.format("QuestHelper objectiverange for %s (pri %d)", obj.obj, obj.real_priority))
82 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))
87 if obj
.real_priority
> self
[mn
].obj
.real_priority
or obj
.after
[self
[mn
].obj
] or self
[mn
].obj
.before
[obj
] then break end
91 mn
= mn
+ 1 -- we went too far, actually
94 while mx
< #self
+ 1 do
95 if obj
.real_priority
< self
[mx
].obj
.real_priority
or obj
.before
[self
[mx
].obj
] or self
[mx
].obj
.after
[obj
] then break end
99 --table.insert(lines, string.format("temp results is %d %d", mn, mx))
101 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.
102 local mid
= math
.ceil((mx
+ mn
) / 2)
107 --table.insert(lines, string.format("overall: %d %d", mn, mx))
110 if passes and passes > 90 then
111 for k, v in pairs(lines) do QuestHelper:TextOut(v) end
112 QuestHelper:TextOut(string.format("overall: %d %d", mn, mx))
117 local omn, omx = self:OldFindObjectiveRange(obj)
119 if mn ~= omn or mx ~= omx then
120 for k, v in pairs(lines) do QuestHelper:TextOut(v) end
121 QuestHelper:TextOut(string.format("overall: %d %d vs %d %d", mn, mx, omn, omx))
122 lolcrash = (lolcrash or 0) + 1
129 function Route
:addObjectiveFast(obj
)
130 assert(self
:sanity())
131 local indexes
= self
.index
133 local info
= QuestHelper
:CreateTable()
134 assert(not indexes
[obj
])
142 d
, info
.pos
= obj
:TravelTime(self
[0].pos
, true)
148 local player_pos
= QuestHelper
.pos
149 local pos
= obj
.location
150 local c
, x
, y
= pos
[1].c
, pos
[3], pos
[4]
152 local mn
, mx
= self
:findObjectiveRange(obj
)
155 for i
= mn
, math
.min(mx
, len
) do
156 local p
= self
[i
].pos
158 local u
, v
= p
[3]-x
, p
[4]-y
160 if not index
or d2
< distsqr
then
161 index
, distsqr
= i
, d2
167 -- No nodes with the same continent already.
168 -- If the same continent as the player, add to start of list, otherwise add to end of the list.
169 index
= c
== player_pos
[1].c
and mx
or mx
172 -- The next question, do I insert at that point, or do I insert after it?
173 if index
~= mx
and index
<= len
then
174 local p1
= self
[index
].pos
182 p0
= self
[index
-1].pos
185 local oldstart
, newstart
188 local u
, v
= p0
[3]-x
, p0
[4]-y
189 newstart
= math
.sqrt(u
*u
+v
*v
)
190 u
, v
= p0
[3]-p1
[3], p0
[4]-p1
[4]
191 oldstart
= math
.sqrt(u
*u
+v
*v
)
199 p2
= self
[index
+1].pos
203 if p2
and p2
[1].c
== c
then
204 local u
, v
= p2
[3]-x
, p2
[4]-y
205 newend
= math
.sqrt(u
*u
+v
*v
)
206 u
, v
= p2
[3]-p1
[3], p2
[4]-p1
[4]
207 oldend
= math
.sqrt(u
*u
+v
*v
)
213 if oldstart
+newend
< newstart
+oldend
then
220 QH_Timeslice_Yield() -- The above checks don't require much effort.
223 local previnfo
= self
[index
-1]
226 d
, info
.pos
= obj
:TravelTime(previnfo
.pos
)
230 self
.distance
= self
.distance
+ d
234 local previnfo
= self
[index
-1]
235 d1
, d2
, info
.pos
= obj
:TravelTime2(previnfo
.pos
, self
[index
].pos
, previnfo
.no_cache
)
238 self
.distance
= self
.distance
+ (d1
- previnfo
.len
+ d2
)
244 -- Finally, insert the objective.
245 table.insert(self
, index
, info
)
248 -- Fix indexes of shifted elements.
249 for i
= index
+1,len
+1 do
250 local obj
= self
[i
].obj
251 assert(indexes
[obj
] == i
-1)
255 assert(self
:sanity())
260 function Route
:addObjectiveBest(obj
, old_index
, old_distance
)
261 assert(self
:sanity())
263 local indexes
= self
.index
265 local info
= QuestHelper
:CreateTable()
266 assert(not indexes
[obj
])
272 self
.distance
, info
.pos
= obj
:TravelTime(self
[0].pos
, true)
274 self
[0].len
= self
.distance
279 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.)
281 local best_index
, best_delta
, best_d1
, best_d2
, best_p
282 local no_cache
, prev_pos
, prev_len
283 local mn
, mx
= self
:findObjectiveRange(obj
)
285 if old_index
and mn
<= old_index
and old_index
<= mx
then
286 -- We're trying to re-evaluate it, and it could remain in the same place.
287 -- So that place is our starting best known place.
288 best_index
, best_delta
= old_index
, old_distance
- self
.distance
290 if best_delta
< 0 then
291 -- Somehow, removing the objective actually made the route worse...
292 -- Just re-figure things from scratch.
293 -- TODO: THIS SHOULD NEVER HAPPEN dear god find out what's causing this and stop it
294 --QuestHelper:TextOut("made route worse wtf")
295 best_index
, best_delta
= nil, nil
299 local pinfo
= self
[mn
-1]
300 no_cache
, prev_pos
, prev_len
= pinfo
.no_cache
, pinfo
.pos
, pinfo
.len
302 for i
= mn
, math
.min(mx
, len
) do
303 assert(prev_pos
== self
[i
-1].pos
)
308 local d1
, d2
, p
= obj
:TravelTime2(prev_pos
, pos
, no_cache
)
312 local delta
= d1
+ d2
- prev_len
314 if not best_index
or ((delta
+ improve_margin
) < best_delta
) or ((i
== best_index
) and not best_d1
) then
316 -- * First item we reach
317 -- * Better than previous best
318 -- * We're looking at our best already. But we just got here; how could this be best?
319 -- If this was our prior location and we didn't find anything better earlier in the route,
320 -- that's how. Save the specifics, 'cause we didn't compute them when setting up.
321 best_index
, best_delta
, best_d1
, best_d2
, best_p
= i
, delta
, d1
, d2
, p
331 assert(prev_pos
== self
[len
].pos
)
332 local delta
, p
= obj
:TravelTime(prev_pos
, no_cache
)
336 if not best_index
or ((delta
+ improve_margin
) < best_delta
) or ((mx
== best_index
) and not best_d1
) then
339 self
[len
].len
= delta
340 self
.distance
= self
.distance
+ delta
341 table.insert(self
, info
)
344 assert(self
:sanity())
353 local pinfo
= self
[best_index
-1]
354 self
.distance
= self
.distance
+ (best_d1
- pinfo
.len
+ best_d2
)
357 table.insert(self
, best_index
, info
)
359 -- QuestHelper:Assert(math.abs(QuestHelper:ComputeTravelTime(self[best_index-1].pos, self[best_index].pos) - self[best_index-1].len) < 0.0001, "aaaaargh")
360 --[[ -- I don't think this is necessary now that TravelTime2 explicitly does this internally, but I'm keeping it anyway.
361 self.distance = self.distance - self[best_index-1].len
362 self[best_index-1].len = QuestHelper:ComputeTravelTime(self[best_index-1].pos, self[best_index].pos, true)
363 self.distance = self.distance + self[best_index-1].len
366 indexes
[obj
] = best_index
368 for i
= best_index
+1,len
+1 do
369 assert(indexes
[self
[i
].obj
] == i
-1)
370 indexes
[self
[i
].obj
] = i
373 if not old_index
or (mn
> old_index
or old_index
> mx
) and mn
<= best_index
and best_index
<= mx
then
374 -- 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
378 assert(self
:sanity())
380 return best_index
, sanityfixed
383 function Route
:removeObjective(obj
)
384 assert(self
:sanity())
386 local indexes
= self
.index
387 local index
= indexes
[obj
]
388 local old_distance
= self
.distance
391 local info
= self
[index
]
392 assert(info
.obj
== obj
)
395 Removing end item: subtract last distance, nothing to recalculate
396 Removing other item: recalculate location of next objective, between prior position and objective after next
397 Special case: if there is no location after next, just recalc location of next objective
399 if index
== #self
then
400 self
.distance
= self
.distance
- self
[index
-1].len
401 self
[index
-1].len
= 0
403 local pinfo
= self
[index
-1]
404 local info1
= self
[index
+1]
405 local info2
= self
[index
+2]
406 local no_cache
= (index
== 1)
411 d1
, d2
, info1
.pos
= info1
.obj
:TravelTime2(pinfo
.pos
, info2
.pos
, no_cache
)
413 self
.distance
= self
.distance
- pinfo
.len
- info
.len
- info1
.len
+ d1
+ d2
416 d1
, info1
.pos
= info1
.obj
:TravelTime(pinfo
.pos
, no_cache
)
418 self
.distance
= self
.distance
- pinfo
.len
- info
.len
+ d1
424 QuestHelper
:ReleaseTable(info
)
426 table.remove(self
, index
)
428 for i
= index
,#self
do
429 -- Fix indexes of shifted elements.
430 local obj
= self
[i
].obj
431 assert(indexes
[obj
] == i
+1)
435 assert(self
:sanity())
436 -- assert(self.distance <= old_distance)
444 function Route
:breed(route_map
)
445 local indexes
= self
.index
451 local prev_pos
= QuestHelper
.pos
452 assert(self
[0].pos
== prev_pos
)
454 -- Pick which objective goes first, selecting from first objective of each route,
455 -- and scaling by the route's fitness and distance from player, with a random adjustment factor.
456 -- While we're at it, record some data about the fitness of adjacent objectives
457 for route
in pairs(route_map
) do
458 assert(route
:sanity())
459 local fit
= route
.fitness
460 local pos
= route
[1].pos
463 if prev_pos
[1].c
== pos
[1].c
then
464 local u
, v
= prev_pos
[3]-pos
[3], prev_pos
[4]-pos
[4]
465 w
= math
.sqrt(u
*u
+v
*v
)
470 w
= fit
* math
.random() / w
472 if not info
or w
> r
then
473 info
, r
= route
[1], w
477 local obj
= route
[i
].obj
478 local tbl
= links
[obj
]
480 tbl
= QuestHelper
:CreateTable()
485 local info
= route
[i
-1]
486 local obj2
= info
.obj
487 tbl
[info
] = (tbl
[info
] or 0) + fit
491 local info
= route
[i
+1]
492 local obj2
= info
.obj
493 if obj
.real_priority
<= obj2
.real_priority
or obj
.before
[obj2
] then
494 tbl
[info
] = (tbl
[info
] or 0) + fit
502 -- Record info for the 'Player Position' objective, so we don't mess it up later
503 seen
[self
[0].obj
] = self
[0].pos
505 -- Record the objective that we chose to put first
508 seen
[obj
] = info
.pos
-- Save its position, because we don't want to clobber any of the info objects yet
514 -- Scan the rest of the places in the route, and pick objectives to go there
519 -- Scan the list of scores from the prior objective
520 for i
, weight
in pairs(last
) do
522 -- Only consider an item if we have scores for that item
525 if prev_pos
[1].c
== pos
[1].c
then
526 local u
, v
= prev_pos
[3]-pos
[3], prev_pos
[4]-pos
[4]
527 w
= math
.sqrt(u
*u
+v
*v
)
532 w
= weight
* math
.random() / w
534 if not info
or w
> r
then
541 -- In case we had no valid scores, scan the remaining objectives and score by distance
543 for obj
in pairs(links
) do
546 if prev_pos
[1] == pos
[1] then
548 local u
, v
= prev_pos
[3]-pos
[3], prev_pos
[4]-pos
[4]
549 w
= math
.sqrt(u
*u
+v
*v
)
550 elseif prev_pos
[1].c
== pos
[1].c
then
551 -- Same continent. -- Assume twices as long.
552 local u
, v
= prev_pos
[3]-pos
[3], prev_pos
[4]-pos
[4]
553 w
= 2*math
.sqrt(u
*u
+v
*v
)
555 -- Different continent. Assume fixed value of 5 minutes.
559 w
= math
.random() / w
561 if not info
or w
> r
then
562 local route
= next(route_map
)
563 info
, r
= route
[route
.index
[obj]]
, w
570 -- Add the selected item to the route
575 assert(info
.obj
== obj
)
577 -- 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
578 local link
= links
[obj
]
580 QuestHelper
:ReleaseTable(last
)
586 -- Clean up the last table
587 QuestHelper
:ReleaseTable(last
)
589 -- Now that we've got our objectives lined up, fill in the info objects with the positions we saved
590 for obj
, i
in pairs(indexes
) do
593 info
.obj
, info
.pos
= obj
, seen
[obj
]
597 -- Now randomly randomize some of the route (aka mutation)
598 while math
.random() > 0.3 do
599 local l
= math
.floor(math
.random()^
1.6*(len
-1))+1
600 local i
= math
.random(1, len
-l
)
603 -- Reverse a chunk of the route
605 self
[i
+k
], self
[j
-k
] = self
[j
-k
], self
[i
+k
]
609 -- But wait, after all that some objectives might violate the rules. Make sure the route follows
612 -- 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.
613 -- Since the entire thing is internally inconsistent anyway, we're just gonna try to consistentize it.
615 local valid_items
= {}
616 for k
, v
in ipairs(self
) do
617 valid_items
[v
.obj
] = true
619 for k
, v
in ipairs(self
) do
620 for b
in pairs(v
.obj
.before
) do
621 if valid_items
[b
] then
622 b
.after
[v
.obj
] = true
625 for a
in pairs(v
.obj
.after
) do
626 if valid_items
[a
] then
627 a
.before
[v
.obj
] = true
632 -- Because priorities might have been changed in here, we next make absolutely sure we have up-to-date priorities.
633 for k
, v
in ipairs(self
) do
634 CalcObjectivePriority(v
.obj
)
637 -- Have I mentioned I hate this codebase yet?
643 local invalid_passes
= 0
644 --local output_strings = {}
647 invalid_passes
= invalid_passes
+ 1
649 --[[if invalid_passes >= 100 then
650 for k, v in pairs(output_strings) do
651 QuestHelper:TextOut(v)
655 if invalid_passes
>= 100 then
657 QuestHelper
.mutation_passes_exceeded
= true
660 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
664 --[[for i = 1, #self do
665 local mn, mx = self:findObjectiveRange(self[i].obj, invalid_passes)
666 table.insert(output_strings, string.format("%d is mn mx %d %d (%s)", i, mn, mx, self[i].obj.obj))
669 -- Make sure all the objectives have valid positions in the list.
671 local mn
, mx
, tabi
= self
:findObjectiveRange(info
.obj
, invalid_passes
)
672 --if invalid_passes > 90 then for k, v in pairs(tabi) do table.insert(output_strings, v) end end
674 -- In theory, 'i' shouldn't be increased here, as the next
675 -- element will be shifted down into the current position.
677 -- However, it is possible for an infinite loop to be created
678 -- by this, with a small range of objectives constantly
681 -- So, I mark the route as invalid and go through it another time.
682 -- It's probably still possible to get into an infinite loop,
683 -- but it seems much less likely.
685 table.insert(self
, mn
, info
)
686 table.remove(self
, i
)
688 --table.insert(output_strings, string.format("shifting %d into %d", i, mn))
690 table.remove(self
, i
)
691 table.insert(self
, mx
, info
)
693 --table.insert(output_strings, string.format("shifting %d into %d", i, mx))
697 --table.insert(output_strings, "pass done")
700 -- Now that we've chosen a route, re-calculate the cost of each leg of the route
702 local prev_info
= self
[0]
703 local next_info
= self
[1]
704 local prev_pos
= prev_info
.pos
705 local next_pos
= next_info
.pos
714 local info
= next_info
715 next_info
= self
[i
+1]
716 next_pos
= next_info
.pos
718 indexes
[info
.obj
] = i
720 d1
, d2
, pos
= info
.obj
:TravelTime2(prev_pos
, next_pos
, prev_info
.no_cache
)
727 distance
= distance
+ d1
733 self
.distance
= distance
+ prev_info
.len
735 indexes
[self
[len
].obj
] = len
738 assert(self
:sanity())
741 function Route
:pathResetBegin()
742 for i
, info
in ipairs(self
) do
744 info
[1], info
[2], info
[3] = pos
[1].c
, pos
[3], pos
[4]
748 function Route
:pathResetEnd()
749 for i
, info
in ipairs(self
) do
750 -- Try to find a new position for this objective, near where we had it originally.
753 local a
, b
, c
= info
[1], info
[2], info
[3]
755 for z
, pl
in pairs(info
.obj
.p
) do
756 for i
, point
in ipairs(pl
) do
757 if a
== point
[1].c
then
758 local x
, y
= b
-point
[3], c
-point
[4]
760 if not p
or d2
< d
then
767 -- Assuming that there will still be positions on the same continents as before, i.e., locations are only added and not removed.
773 self
:recalculateDistances()
776 function Route
:recalculateDistances()
779 for i
= 0, #self
-1 do
780 self
[i
].len
= QuestHelper
:ComputeTravelTime(self
[i
].pos
, self
[i
+1].pos
)
781 self
.distance
= self
.distance
+ self
[i
].len
785 function Routing
:RoutingSetup()
786 Routing
.map_walker
= self
.qh
:CreateWorldMapWalker()
787 Routing
.add_swap
= {}
790 local routes
= Routing
.routes
791 local pos
= QuestHelper
.pos
792 local PlayerObjective
= self
.qh
:NewObjectiveObject() -- Pseudo-objective which reflects player's current position. Always at index 0 of each route.
793 PlayerObjective
.pos
= pos
794 PlayerObjective
.cat
= "loc" -- A special case of a location
795 PlayerObjective
.obj
= "Player's current position" -- Player shouldn't see this, so no need to localize
796 PlayerObjective
.icon_id
= 6 -- Don't think we'll need these; just filling them in for completeness
797 PlayerObjective
.o
= {pos
=pos
}
798 PlayerObjective
.fb
= {}
800 for i
= 1,15 do -- Create some empty routes to use for our population.
801 local new_rt
= { index
={ [PlayerObjective
]=0 },
803 [0]={ obj
=PlayerObjective
, pos
=pos
, len
=0, no_cache
=true }, -- Player's current position is always objective #0
804 id
=i
-- So I can keep track of which route is which; only for debugging.
806 setmetatable(new_rt
, Route
)
807 routes
[new_rt
] = true
810 -- All the routes are the same right now, so it doesn't matter which we're considering the best.
811 self
.best_route
= next(routes
)
812 self
.recheck_position
= 1
816 function Routing
:RouteUpdateRoutine()
817 local qh
= QuestHelper
818 local map_walker
= Routing
.map_walker
819 local minimap_dodad
= qh
.minimap_dodad
821 local route
= qh
.route
822 local to_add
, to_remove
, add_swap
= qh
.to_add
, qh
.to_remove
, self
.add_swap
824 local routes
= self
.routes
827 local best_route
= self
.best_route
829 local last_cache_clear
= GetTime()
831 ------ EVIL HACK OF DEBUG
834 while GetTime() < last_cache_clear
+ 5 do
839 -- We know the player will be at the target location at target_time, so fudge the numbers
840 -- to pretend we're traveling there.
842 pos
[1], pos
[3], pos
[4] = qh
.target
[1], qh
.target
[3], qh
.target
[4]
843 local extra_time
= math
.max(0, qh
.target_time
-time())
844 for i
, t
in ipairs(qh
.target
[2]) do
845 pos
[2][i
] = t
+extra_time
848 if not pos
[1] -- Need a valid position, in case the player was dead when they loaded the game.
849 or not UnitIsDeadOrGhost("player") then
850 -- Don't update the player's position if they're dead, assume they'll be returning to their corpse.
851 pos
[3], pos
[4] = qh
.Astrolabe
:TranslateWorldMapPosition(qh
.c
, qh
.z
, qh
.x
, qh
.y
, qh
.c
, 0)
854 pos
[1] = qh
.zone_nodes
[qh
.i
]
855 pos
[3], pos
[4] = pos
[3] * qh
.continent_scales_x
[qh
.c
], pos
[4] * qh
.continent_scales_y
[qh
.c
]
857 for i
, n
in ipairs(pos
[1]) do
859 for i
, j
in pairs(n
) do qh
:TextOut("[%q]=%s %s", i
, type(j
), tostring(j
) or "???") end
863 local a
, b
= n
.x
-pos
[3], n
.y
-pos
[4]
864 pos
[2][i
] = math
.sqrt(a
*a
+b
*b
)
869 local obj
= next(to_add
)
871 QuestHelper
:TextOut("dbghack")
872 QuestHelper
:TextOut(QuestHelper
:StringizeTable(to_add
))
873 obj
.filter_zone
= false
874 obj
.filter_watched
= false
875 QuestHelper
:TextOut(QuestHelper
:StringizeTable(obj
))
876 QuestHelper
:TextOut(tostring(obj
:Known()))
878 QuestHelper
:TextOut(QuestHelper
:StringizeTable(obj
))
880 QuestHelper
:TextOut("o")
881 QuestHelper
:TextOut(QuestHelper
:StringizeTable(obj
.o
))
883 QuestHelper
:TextOut("pp")
884 QuestHelper
:TextOut(QuestHelper
:StringizeTable(obj
.pos
))
885 QuestHelper
:TextOut(QuestHelper
:StringizeTable(obj
.p
))
886 QuestHelper
:TextOut(tostring(obj
:Known()))
888 local index
= best_route
:addObjectiveBest(obj
)
889 obj
.pos
= best_route
[index
].pos
891 QuestHelper
:TextOut(QuestHelper
:StringizeTable(obj
.pos
))
893 QuestHelper
:TextOut(qh
:ComputeTravelTime(pos
, obj
.pos
))
898 ------ EVIL HACK OF DEBUG
901 -- Clear caches out a bit
902 if GetTime() + 15 >= last_cache_clear
then
904 last_cache_clear
= GetTime()
907 -- Update the player's position data.
909 -- We know the player will be at the target location at target_time, so fudge the numbers
910 -- to pretend we're traveling there.
912 pos
[1], pos
[3], pos
[4] = qh
.target
[1], qh
.target
[3], qh
.target
[4]
913 local extra_time
= math
.max(0, qh
.target_time
-time())
914 for i
, t
in ipairs(qh
.target
[2]) do
915 pos
[2][i
] = t
+extra_time
918 if not pos
[1] -- Need a valid position, in case the player was dead when they loaded the game.
919 or not UnitIsDeadOrGhost("player") then
920 -- Don't update the player's position if they're dead, assume they'll be returning to their corpse.
921 pos
[3], pos
[4] = qh
.Astrolabe
:TranslateWorldMapPosition(qh
.c
, qh
.z
, qh
.x
, qh
.y
, qh
.c
, 0)
924 pos
[1] = qh
.zone_nodes
[qh
.i
]
925 pos
[3], pos
[4] = pos
[3] * qh
.continent_scales_x
[qh
.c
], pos
[4] * qh
.continent_scales_y
[qh
.c
]
927 for i
, n
in ipairs(pos
[1]) do
929 for i
, j
in pairs(n
) do qh
:TextOut("[%q]=%s %s", i
, type(j
), tostring(j
) or "???") end
933 local a
, b
= n
.x
-pos
[3], n
.y
-pos
[4]
934 pos
[2][i
] = math
.sqrt(a
*a
+b
*b
)
939 local changed
= false
942 if self
.recheck_position
> #route
then self
.recheck_position
= 1 end
943 local o
= route
[self
.recheck_position
]
946 o
.filter_zone
= o
.zones
[pos
[1].i
] == nil
947 o
.filter_watched
= not o
:IsWatched()
949 if not o
:Known() then
950 -- Objective was probably made to depend on an objective that we don't know about yet.
951 -- We add it to both lists, because although we need to remove it, we need it added again when we can.
952 -- This creates an inconsistancy, but it'll get fixed in the removal loop before anything has a chance to
958 if o
.swap_before
then
959 qh
:ReleaseTable(o
.before
)
960 o
.before
= o
.swap_before
965 qh
:ReleaseTable(o
.after
)
966 o
.after
= o
.swap_after
970 if o
.is_sharing
~= o
.want_share
then
971 o
.is_sharing
= o
.want_share
974 qh
:DoShareObjective(o
)
976 qh
:DoUnshareObjective(o
)
980 CalcObjectivePriority(o
)
982 -- Make sure the objective in best_route is still in a valid position.
983 -- Won't worry about other routes, they should forced into valid configurations by breeding.
985 -- 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.
987 setmetatable(aobt
, getmetatable(best_route
))
988 for k
, v
in pairs(best_route
) do
992 for k
, v
in ipairs(best_route
) do
994 aobt
[k
] = QuestHelper
:CreateTable("AOBT idiocy")
995 for t
, q
in pairs(v
) do
998 aobt
.index
[aobt
[k
].obj
] = k
1001 aobt
[0] = QuestHelper
:CreateTable("AOBT idiocy")
1002 for t
, q
in pairs(best_route
[0]) do
1006 -- Actually duplicating a route is irritatingly hard (this is another thing which will be fixed eventually dammit)
1008 assert(aobt
:sanity())
1009 assert(best_route
:sanity())
1011 local old_distance
, old_index
= best_route
.distance
, best_route
:removeObjective(o
)
1012 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
1013 local new_index
, sanityfixed
= best_route
:addObjectiveBest(o
, old_index
, old_distance
)
1014 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
1015 -- 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
1017 if new_real_distance
< old_real_distance
or sanityfixed
then -- More of the temporary hack
1018 -- If we're using the new path . . .
1020 if old_index
> new_index
then
1021 old_index
, new_index
= new_index
, old_index
1024 for i
= math
.max(1, old_index
-1), new_index
do
1025 local info
= best_route
[i
]
1026 local obj
= info
.obj
1032 if old_index
~= new_index
then
1033 if old_index
== 1 then
1034 minimap_dodad
:SetObjective(route
[1])
1040 -- . . . release our backup path
1041 for k
, v
in ipairs(aobt
) do QuestHelper
:ReleaseTable(v
) end
1042 QuestHelper
:ReleaseTable(aobt
[0])
1043 else -- hack (everything in this conditional besides the above chunk is a horrible hack)
1044 -- If we're using the old path . . .
1045 -- . . . release the *internals* of the new path, then copy everything over. Eugh.
1046 for k
, v
in ipairs(best_route
) do QuestHelper
:ReleaseTable(v
) end
1047 QuestHelper
:ReleaseTable(best_route
[0])
1048 while #best_route
> 0 do table.remove(best_route
) end
1049 for k
, v
in pairs(aobt
) do best_route
[k
] = v
end -- hack
1050 setmetatable(aobt
, Route
)
1051 assert(best_route
:sanity())
1054 -- 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
1055 -- 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*
1056 --if old_index == new_index then
1057 -- We don't advance recheck_position unless the node doesn't get moved.
1058 -- TODO: As the this code is apparently bugged, it's gotten into an infinite loop of constantly swapping
1059 -- and hence never advancing. As a work around for now, we'll always advance.
1061 self
.recheck_position
= self
.recheck_position
+ 1
1066 -- Remove any waypoints if needed.
1068 local obj
= next(to_remove
)
1069 if not obj
then break end
1070 to_remove
[obj
] = nil
1072 if obj
.is_sharing
then
1073 obj
.is_sharing
= false
1074 qh
:DoUnshareObjective(obj
)
1077 for r
in pairs(routes
) do
1078 if r
== best_route
then
1079 local index
= r
:removeObjective(obj
)
1080 table.remove(route
, index
)
1082 minimap_dodad
:SetObjective(route
[1])
1085 r
:removeObjective(obj
)
1094 -- Add any waypoints if needed
1096 local obj
= next(to_add
)
1097 if not obj
then break end
1100 obj
.filter_zone
= obj
.zones
and obj
.zones
[pos
[1].i
] == nil
1101 obj
.filter_watched
= not obj
:IsWatched()
1104 obj
:PrepareRouting()
1106 obj
.filter_zone
= obj
.zones
[pos
[1].i
] == nil
1108 if obj
.filter_zone
and QuestHelper_Pref
.filter_zone
then
1109 -- Not going to add it, wrong zone.
1111 add_swap
[obj
] = true
1113 if not obj
.is_sharing
and obj
.want_share
then
1114 obj
.is_sharing
= true
1115 qh
:DoShareObjective(obj
)
1118 CalcObjectivePriority(obj
)
1120 for r
in pairs(routes
) do
1121 if r
== best_route
then
1122 local index
= r
:addObjectiveBest(obj
)
1123 obj
.pos
= r
[index
].pos
1124 table.insert(route
, index
, obj
)
1126 minimap_dodad
:SetObjective(route
[1])
1129 r
:addObjectiveFast(obj
)
1136 add_swap
[obj
] = true
1140 for obj
in pairs(add_swap
) do
1141 -- If one of the objectives we were considering adding was removed, it would be in both lists.
1142 -- That would be bad. We can't remove it because we haven't actually added it yet, so
1143 -- handle that special case here.
1144 if to_remove
[obj
] then
1145 to_remove
[obj
] = nil
1151 to_add
, add_swap
= add_swap
, to_add
1153 self
.add_swap
= add_swap
1155 if #best_route
> 1 then
1156 -- If there is 2 or more objectives, randomly combine routes to (hopefully) create something better than we had before.
1158 -- Calculate best_route first, so that if other routes are identical, we don't risk swapping with them and
1159 -- updating the map_walker.
1160 local best
, max_fitness
= best_route
, 1/(qh
:ComputeTravelTime(pos
, best_route
[1].pos
) + best_route
.distance
)
1161 best_route
.fitness
= max_fitness
1163 QH_Timeslice_Yield()
1165 for r
in pairs(routes
) do
1166 if r
~= best_route
then
1167 local fit
= 1/(qh
:ComputeTravelTime(pos
, r
[1].pos
)+r
.distance
)
1169 if fit
> max_fitness
then
1170 best
, max_fitness
= r
, fit
1172 QH_Timeslice_Yield()
1176 local to_breed
, score
1178 for r
in pairs(routes
) do
1180 local s
= math
.random()*r
.fitness
1181 if not to_breed
or s
< score
then
1182 to_breed
, score
= r
, s
1187 to_breed
:breed(routes
)
1189 if 1/(qh
:ComputeTravelTime(pos
, to_breed
[1].pos
)+to_breed
.distance
+improve_margin
) > max_fitness
then
1193 QH_Timeslice_Yield()
1195 if best
~= best_route
then
1197 assert(best
:sanity())
1198 assert(best_route
:sanity())
1201 self
.best_route
= best_route
1203 for i
, info
in ipairs(best
) do
1204 local obj
= info
.obj
1209 minimap_dodad
:SetObjective(route
[1])
1215 if qh
.defered_flight_times
then
1216 qh
:buildFlightTimes()
1217 qh
.defered_flight_times
= false
1218 assert(qh
.defered_graph_reset
)
1221 if qh
.defered_graph_reset
then
1222 QH_Timeslice_Yield()
1224 for r
in pairs(routes
) do
1228 qh
.graph_in_limbo
= true
1230 qh
.graph_in_limbo
= false
1231 qh
.defered_graph_reset
= false
1233 for r
in pairs(routes
) do
1237 for i
, info
in ipairs(best_route
) do
1238 local obj
= info
.obj
1242 best_route
:recalculateDistances()
1244 minimap_dodad
:SetObjective(route
[1])
1246 QuestHelper
:SetTargetLocationRecalculate()
1248 for r
in pairs(routes
) do
1253 QH_Timeslice_Yield()
1257 map_walker
:RouteChanged()
1260 assert(#route
== #best_route
)
1262 -- temporary hack to cause more errors
1263 --qh.defered_graph_reset = true
1264 --qh.defered_flight_times = true
1266 QH_Timeslice_Yield()
1270 function Routing
:Initialize()
1273 QH_Timeslice_Add(function() Routing
:RouteUpdateRoutine() end, 0, "routing")
1274 QH_Timeslice_Toggle("routing", false)
1277 if coroutine.coco then
1278 -- coco allows yielding across c boundries, which allows me to use xpcall to get
1279 -- stack traces for coroutines without calls to yield resulting in thermal nuclear meltdown.
1281 -- This isn't part of WoW, I was using it in my driver program: Development/routetest
1283 update_route = coroutine.create(
1285 local state, err = xpcall(
1287 Routing:RouteUpdateRoutine()
1291 return tostring(err).."\n"..debugstack(2)
1293 return debug.traceback(tostring(err), 2)
1302 update_route = coroutine.create(function() Routing:RouteUpdateRoutine() end)