View Javadoc

1   /*
2    * Copyright (C) 2014 AMIS research group, Faculty of Mathematics and Physics, Charles University in Prague, Czech Republic
3    *
4    * This program is free software: you can redistribute it and/or modify
5    * it under the terms of the GNU General Public License as published by
6    * the Free Software Foundation, either version 3 of the License, or
7    * (at your option) any later version.
8    *
9    * This program is distributed in the hope that it will be useful,
10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   * GNU General Public License for more details.
13   *
14   * You should have received a copy of the GNU General Public License
15   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16   */
17  package cz.cuni.amis.pogamut.ut2004.agent.navigation.navmesh.pathfollowing;
18  
19  import cz.cuni.amis.pogamut.base.communication.worldview.IWorldView;
20  import cz.cuni.amis.pogamut.base.communication.worldview.object.IWorldObjectEventListener;
21  import cz.cuni.amis.pogamut.base.communication.worldview.object.event.WorldObjectUpdatedEvent;
22  import cz.cuni.amis.pogamut.base.utils.math.DistanceUtils;
23  import cz.cuni.amis.pogamut.base3d.worldview.object.ILocated;
24  import cz.cuni.amis.pogamut.base3d.worldview.object.Location;
25  import cz.cuni.amis.pogamut.ut2004.agent.module.sensor.AgentInfo;
26  import cz.cuni.amis.pogamut.ut2004.agent.module.sensor.NavPoints;
27  import cz.cuni.amis.pogamut.ut2004.agent.navigation.AbstractUT2004PathNavigator;
28  import cz.cuni.amis.pogamut.ut2004.agent.navigation.IUT2004PathRunner;
29  import cz.cuni.amis.pogamut.ut2004.agent.navigation.NavigationState;
30  import cz.cuni.amis.pogamut.ut2004.bot.command.AdvancedLocomotion;
31  import cz.cuni.amis.pogamut.ut2004.bot.impl.UT2004Bot;
32  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.Mover;
33  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.NavPoint;
34  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.NavPointNeighbourLink;
35  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.Self;
36  import cz.cuni.amis.pogamut.ut2004.utils.UnrealUtils;
37  import cz.cuni.amis.utils.NullCheck;
38  import cz.cuni.amis.utils.SafeEquals;
39  import cz.cuni.amis.utils.exception.PogamutException;
40  
41  import java.util.Iterator;
42  import java.util.List;
43  import java.util.logging.Level;
44  import java.util.logging.Logger;
45  
46  /**
47   * Runner for navigation with navigation mesh. Evolved from
48   * {@link LoqueNavigator}. Is suited for accelerated navigation logic cycle, has
49   * finer switching conditions.
50   *
51   * @author Bogo
52   * @param <PATH_ELEMENT>
53   */
54  public class NavMeshNavigator<PATH_ELEMENT extends ILocated> extends AbstractUT2004PathNavigator<PATH_ELEMENT> {
55  
56      /**
57       * Current navigation destination.
58       */
59      private Location navigDestination = null;
60  
61      /**
62       * Current stage of the navigation.
63       */
64      private Stage navigStage = Stage.COMPLETED;
65  
66      /**
67       * Current focus of the bot, if null, provide default focus.
68       * <p>
69       * <p>
70       * Filled at the beginning of the
71       * {@link NavMeshNavigator#navigate(ILocated, int)}.
72       */
73      private ILocated focus = null;
74  
75      /**
76       * {@link Self} listener.
77       */
78      private class SelfListener implements IWorldObjectEventListener<Self, WorldObjectUpdatedEvent<Self>> {
79  
80          private IWorldView worldView;
81  
82          /**
83           * Constructor. Registers itself on the given WorldView object.
84           *
85           * @param worldView WorldView object to listen to.
86           */
87          public SelfListener(IWorldView worldView) {
88              this.worldView = worldView;
89              worldView.addObjectListener(Self.class, WorldObjectUpdatedEvent.class, this);
90          }
91  
92          @Override
93          public void notify(WorldObjectUpdatedEvent<Self> event) {
94              self = event.getObject();
95          }
96      }
97  
98      /**
99       * {@link Self} listener
100      */
101     private SelfListener selfListener;
102 
103     /*========================================================================*/
104     /**
105      * Distance, which is considered as close enough for considering the bot to
106      * be AT LOCATION/NAV POINT.
107      *
108      * If greater than 50, navigation will failed on DM-Flux2 when navigating
109      * between health vials in one of map corers.
110      */
111     public static final int CLOSE_ENOUGH = 40;
112 
113     /*========================================================================*/
114     @Override
115     protected void navigate(ILocated focus, int pathElementIndex) {
116         if (log != null && log.isLoggable(Level.FINE)) {
117             log.log(Level.FINE, "Navigator.navigate(): Current stage {0}", navigStage);
118         }
119         this.focus = focus;
120         switch (navigStage = keepNavigating()) {
121             case AWAITING_MOVER:
122             case RIDING_MOVER:
123                 setBotWaiting(true);
124                 break;
125             case TELEPORT:
126             case NAVIGATING:
127             case REACHING:
128                 setBotWaiting(false);
129                 break;
130 
131             case TIMEOUT:
132             case CRASHED:
133             case CANCELED:
134                 if (log != null && log.isLoggable(Level.WARNING)) {
135                     log.log(Level.WARNING, "Navigation {0}", navigStage);
136                 }
137                 executor.stuck();
138                 return;
139 
140             case COMPLETED:
141                 executor.targetReached();
142                 break;
143         }
144         if (log != null && log.isLoggable(Level.FINEST)) {
145             log.log(Level.FINEST, "Navigator.navigate(): Next stage {0}", navigStage);
146         }
147     }
148 
149     @Override
150     public void reset() {
151         // reinitialize the navigator's values
152 
153         navigCurrentLocation = null;
154         navigCurrentNode = null;
155         navigCurrentLink = null;
156         navigDestination = null;
157         navigIterator = null;
158         navigLastLocation = null;
159         navigLastNode = null;
160         navigNextLocation = null;
161         navigNextNode = null;
162         navigNextLocationOffset = 0;
163         navigStage = Stage.COMPLETED;
164         setBotWaiting(false);
165 
166         resetNavigMoverVariables();
167     }
168 
169     @Override
170     public void newPath(List<PATH_ELEMENT> path, ILocated focus) {
171         // prepare for running along new path
172         reset();
173 
174         // 1) obtain the destination
175         Location dest = path.get(path.size() - 1).getLocation();
176 
177         // 2) init the navigation
178         initPathNavigation(dest, path, focus);
179         
180         // 3) start the navigation
181         navigate(focus);
182     }
183 
184     @Override
185     public void pathExtended(List<PATH_ELEMENT> path, int currentPathIndex) {
186         if (path == null || path.isEmpty()) {
187             throw new RuntimeException("path is null or 0-sized!");
188         }
189         navigDestination = path.get(path.size() - 1).getLocation();
190         navigIterator = path.iterator();
191 
192         int newOffset = -currentPathIndex;
193         for (int i = 0; i < path.size() && i < currentPathIndex + navigNextLocationOffset && navigIterator.hasNext(); ++i) {
194             ++newOffset;
195             navigIterator.next();
196         }
197         log.fine("PATH EXTEND ... curr index " + currentPathIndex + ", old offset " + navigNextLocationOffset + ", new offset " + newOffset + ", path size " + path.size());
198         navigNextLocationOffset = newOffset;
199     }
200 
201     @Override
202     public NavPointNeighbourLink getCurrentLink() {
203         return navigCurrentLink;
204     }
205 
206     /*========================================================================*/
207     /**
208      * Initializes direct navigation to the specified destination.
209      *
210      * @param dest Destination of the navigation.
211      */
212     protected void initDirectNavigation(Location dest) {
213         // calculate destination distance
214         int distance = (int) memory.getLocation().getDistance(dest);
215         // init the navigation
216         if (log != null && log.isLoggable(Level.FINE)) {
217             log.log(
218                     Level.FINE,"Navigator.initDirectNavigation(): initializing direct navigation, distance {0}"
219             , distance);
220         }
221         // init direct navigation
222         initDirectly(dest);
223     }
224 
225     /*========================================================================*/
226     /**
227      * Initializes navigation to the specified destination along specified path.
228      *
229      * @param dest Destination of the navigation.
230      * @param path Navigation path to the destination.
231      */
232     protected void initPathNavigation(Location dest, List<PATH_ELEMENT> path, ILocated focus) {
233         // init the navigation
234         if (log != null && log.isLoggable(Level.FINE)) {
235             log.log(
236                     Level.FINE,"Navigator.initPathNavigation(): initializing path navigation, nodes {0}"
237             , path.size());
238         }
239         // init path navigation
240         if (!initAlongPath(dest, path)) {
241             // do it directly then..
242             initDirectNavigation(dest);
243         }
244     }
245 
246     /*========================================================================*/
247     /**
248      * Navigates with the current navigation request.
249      *
250      * @return Stage of the navigation progress.
251      */
252     protected Stage keepNavigating() {
253         // is there any point in navigating further?
254         if (navigStage.terminated) {
255             return navigStage;
256         }
257 
258         if (log != null && log.isLoggable(Level.FINE)) {
259             if (navigLastNode != null) {
260                 log.fine("Navigator.keepNavigating(): From " + NavPoints.describe(navigLastNode));
261             } else if (navigLastLocation != null) {
262                 log.fine("Navigator.keepNavigating(): From " + navigLastLocation);
263             }
264             if (navigCurrentNode != null) {
265                 log.fine("Navigator.keepNavigating(): To   " + NavPoints.describe(navigCurrentNode));
266             } else if (navigCurrentLocation != null) {
267                 log.fine("Navigator.keepNavigating(): To   " + navigCurrentLocation);
268             }            
269         }
270 
271         // try to navigate
272         switch (navigStage) {
273             case REACHING:
274                 navigStage = navigDirectly();
275                 break;
276             default:
277                 navigStage = navigAlongPath();
278                 break;
279         }
280 
281         // return the stage
282         if (log != null && log.isLoggable(Level.FINEST)) {
283             log.finest("Navigator.keepNavigating(): In stage " + navigStage);
284         }
285         return navigStage;
286     }
287 
288     // ==============
289     // ==============
290     // NAVIG DIRECTLY
291     // ==============
292     // ==============
293     
294     /**
295      * Initializes direct navigation to given destination.
296      *
297      * @param dest Destination of the navigation.
298      * @return Next stage of the navigation progress.
299      */
300     private Stage initDirectly(Location dest) {
301         // setup navigation info
302         navigDestination = dest;
303         // init runner
304         runner.reset();
305         // reset navigation stage
306         return navigStage = Stage.REACHING;
307     }
308 
309     /**
310      * Tries to navigate the agent directly to the navig destination.
311      *
312      * @return Next stage of the navigation progress.
313      */
314     private Stage navigDirectly() {
315         // get the distance from the target
316         int distance = (int) memory.getLocation().getDistance(navigDestination);
317 
318         // are we there yet?
319         if (distance <= CLOSE_ENOUGH) {
320             if (log != null && log.isLoggable(Level.FINE)) {
321                 log.fine("Navigator.navigDirectly(): destination close enough: " + distance);
322             }
323             return Stage.COMPLETED;
324         }
325 
326         // run to that location..
327         if (!runner.runToLocation(navigLastLocation, navigDestination, null, (focus == null ? navigDestination : focus), null, true, false)) {
328             if (log != null && log.isLoggable(Level.FINE)) {
329                 log.fine("Navigator.navigDirectly(): direct navigation failed");
330             }
331             return Stage.CRASHED;
332         }
333 
334         // well, we're still running
335         if (log != null && log.isLoggable(Level.FINEST)) {
336             log.finer("Navigator.navigDirectly(): traveling directly, distance = " + distance);
337         }
338         return navigStage;
339     }
340 
341     // ================
342     // ================
343     // NAVIG ALONG PATH 
344     // ================
345     // ================    
346     
347     /**
348      * Iterator through navigation path.
349      */
350     private Iterator<PATH_ELEMENT> navigIterator = null;
351 
352     /**
353      * How many path elements we have iterated over before selecting the current
354      * {@link NavMeshNavigator#navigNextLocation}.
355      */
356     private int navigNextLocationOffset = 0;
357 
358     /**
359      * Last location in the path (the one the agent already reached).
360      */
361     private Location navigLastLocation = null;
362 
363     /**
364      * If {@link NavMeshNavigator#navigLastLocation} is a {@link NavPoint} or has
365      * NavPoint near by, its instance is written here (null otherwise).
366      */
367     private NavPoint navigLastNode = null;
368 
369     /**
370      * Current node in the path (the one the agent is running to).
371      */
372     private Location navigCurrentLocation = null;
373 
374     /**
375      * If {@link NavMeshNavigator#navigCurrentLocation} is a {@link NavPoint} or
376      * has NavPoint near by, its instance is written here (null otherwise).
377      */
378     private NavPoint navigCurrentNode = null;
379 
380     /**
381      * If moving between two NavPoints {@link NavPoint} the object
382      * {@link NeighbourLink} holding infomation about the link (if any) will be
383      * stored here (null otherwise).
384      */
385     private NavPointNeighbourLink navigCurrentLink = null;
386 
387     /**
388      * Next node in the path (the one being prepared).
389      */
390     private Location navigNextLocation = null;
391 
392     /**
393      * If {@link NavMeshNavigator#navigNextLocation} is a {@link NavPoint} or has
394      * NavPoint near by, its instance is written here (null otherwise).
395      */
396     private NavPoint navigNextNode = null;
397 
398     /**
399      * Initializes navigation along path.
400      *
401      * @param dest Destination of the navigation.
402      * @param path Path of the navigation.
403      * @return True, if the navigation is successfuly initialized.
404      */
405     private boolean initAlongPath(Location dest, List<PATH_ELEMENT> path) {
406         // setup navigation info
407         navigDestination = dest;
408         navigIterator = path.iterator();
409         // reset current node
410         navigCurrentLocation = bot.getLocation();
411         navigCurrentNode = DistanceUtils.getNearest(bot.getWorldView().getAll(NavPoint.class).values(), bot.getLocation(), 40);
412         // prepare next node
413         prepareNextNode();
414         // reset navigation stage
415         navigStage = Stage.NAVIGATING;
416         // reset node navigation info
417         return switchToNextNode();
418     }
419 
420     /**
421      * Tries to navigate the agent safely along the navigation path.
422      *
423      * @return Next stage of the navigation progress.
424      */
425     private Stage navigAlongPath() {
426         // get the distance from the destination
427         int totalDistance = (int) memory.getLocation().getDistance(navigDestination);
428 
429         // are we there yet?
430         if (totalDistance <= CLOSE_ENOUGH) {
431             log.log(Level.FINEST, "Navigator.navigAlongPath(): destination close enough: {0}", totalDistance);
432             return Stage.COMPLETED;
433         }
434 
435         // navigate
436         if (navigStage.mover) {
437             log.fine("Navigator.navigAlongPath(): MOVER");
438             return navigMover();
439         } else if (navigStage.teleport) {
440             log.fine("Navigator.navigAlongPath(): TELEPORT");
441             return navigThroughTeleport();
442         } else {
443             log.fine("Navigator.navigAlongPath(): STANDARD");
444             return navigToCurrentNode(true, false); // USE FOCUS, normal navigation
445         }
446     }
447     
448     // ==================================
449     // ==================================
450     // SWITCHING TO THE NEXT NODE ON PATH
451     // ==================================
452     // ==================================
453     
454     /**
455      * Prepares next navigation node in path.
456      * <p>
457      * <p>
458      * If necessary just recalls
459      * {@link NavMeshNavigator#prepareNextNodeTeleporter()}.
460      */
461     private void prepareNextNode() {
462         if (navigCurrentNode != null && navigCurrentNode.isTeleporter()) {
463             // current node is a teleporter! ...
464             prepareNextNodeTeleporter();
465             return;
466         }
467 
468         // retreive the next node, if there are any left
469         // note: there might be null nodes along the path!
470         ILocated located = null;
471         navigNextLocation = null;
472         navigNextNode = null;
473         navigNextLocationOffset = 0;
474         while ((located == null) && navigIterator.hasNext()) {
475             // get next node in the path
476             located = navigIterator.next();
477             navigNextLocationOffset += 1;
478         }
479 
480         // did we get the next node?
481         if (located == null) {
482             navigNextLocationOffset = 0;
483             return;
484         }
485 
486         if (executor.getPathElementIndex() + navigNextLocationOffset >= executor.getPath().size()) {
487             navigNextLocationOffset = 0; // WTF?
488         }
489 
490         // obtain next location
491         navigNextLocation = located.getLocation();
492         // obtain navpoint instance for a given location
493         navigNextNode = getNavPoint(located);
494     }
495 
496     /**
497      * Prepares next node in the path assuming the currently pursued node is a
498      * teleporter.
499      */
500     private void prepareNextNodeTeleporter() {
501         // Retrieve the next node, if there are any left
502         // note: there might be null nodes along the path!
503         ILocated located = null;
504         navigNextLocation = null;
505         navigNextLocationOffset = 0;
506         boolean nextTeleporterFound = false;
507         while ((located == null) && navigIterator.hasNext()) {
508             // get next node in the path
509             located = navigIterator.next();
510             navigNextLocationOffset += 1;
511             if (located == null) {
512                 continue;
513             }
514             navigNextNode = getNavPoint(located);
515             if (navigNextNode != null && navigNextNode.isTeleporter()) {
516                 // next node is 
517                 if (!nextTeleporterFound) {
518                     // ignore first teleporter as it is the other end of the teleporter we're currently trying to enter
519                     located = null;
520                 }
521                 nextTeleporterFound = true;
522             } else {
523                 break;
524             }
525         }
526 
527         // did we get the next node?
528         if (located == null) {
529             navigNextLocationOffset = 0;
530             return;
531         }
532 
533         if (executor.getPathElementIndex() + navigNextLocationOffset >= executor.getPath().size()) {
534             navigNextLocationOffset = 0; // WTF?
535         }
536 
537         // obtain next location
538         navigNextLocation = located.getLocation();
539         // obtain navpoint instance for a given location
540         navigNextNode = getNavPoint(located);
541     }
542 
543     /**
544      * Initializes next navigation node in path.
545      *
546      * @return True, if the navigation node is successfully switched.
547      */
548     private boolean switchToNextNode() {
549         if (log != null && log.isLoggable(Level.FINER)) {
550             log.finer("Navigator.switchToNextNode(): switching!");
551         }
552 
553         // move the current node into last node
554         navigLastLocation = navigCurrentLocation;
555         navigLastNode = navigCurrentNode;
556 
557         // get the next prepared node
558         if (null == (navigCurrentLocation = navigNextLocation)) {
559             // no nodes left there..
560             if (log != null && log.isLoggable(Level.FINER)) {
561                 log.finer("Navigator.switchToNextNode(): no nodes left");
562             }
563             navigCurrentNode = null;
564             return false;
565         }
566         // rewrite the navpoint as well
567         navigCurrentNode = navigNextNode;
568 
569         // store current NavPoint link
570         navigCurrentLink = getNavPointsLink(navigLastNode, navigCurrentNode);
571 
572         if (navigCurrentLink == null) {
573             getNavPointsLink(navigLastNode, navigCurrentNode);
574             if (log.isLoggable(Level.INFO)) {
575                 log.info("No link information...");
576             }
577         }
578 
579         // ensure that the last node is not null
580         if (navigLastLocation == null) {
581             navigLastLocation = bot.getLocation();
582             navigLastNode = navigCurrentNode;
583         }
584 
585         // get next node distance
586         int localDistance = (int) memory.getLocation().getDistance(navigCurrentLocation.getLocation());
587 
588         // is this next node a teleporter?
589         if (navigCurrentNode != null && navigCurrentNode.isTeleporter()) {
590             navigStage = Stage.TeleporterStage();
591         } // is this next node a mover?
592         else if (navigCurrentNode != null && navigCurrentNode.isLiftCenter()) {
593             // setup mover sequence
594             navigStage = Stage.FirstMoverStage();
595             resetNavigMoverVariables();
596         } // are we still moving on mover? 
597         else if (navigStage.mover) {
598             navigStage = navigStage.next();
599             // init the runner
600             runner.reset();
601         } else if (navigStage.teleport) {
602             navigStage = navigStage.next();
603             // init the runner
604             runner.reset();
605         } // no movers & teleports
606         else {
607             // init the runner
608             runner.reset();
609         }
610 
611         // switch to next node
612         if (log != null && log.isLoggable(Level.FINE)) {
613             if (navigCurrentNode != null) {
614                 log.fine(
615                         "Navigator.switchToNextNode(): switch to next node " + navigCurrentNode.getId().getStringId()
616                         + ", distance " + localDistance
617                         + ", reachable true"
618                         + ", mover " + navigStage.mover
619                 );
620                 // we do not have extra information about the location we're going to reach
621             } else {
622                 log.log(Level.FINE, "Navigator.switchToNextNode(): switch to next location {0}, distance {1}, mover {2}", new Object[]{navigCurrentLocation, localDistance, navigStage.mover});
623             }
624         }
625 
626         // tell the executor that we have moved in the path to the next element
627         if (executor.getPathElementIndex() < 0) {
628             executor.switchToAnotherPathElement(0);
629         } else {
630             if (navigNextLocationOffset > 0) {
631                 executor.switchToAnotherPathElement(executor.getPathElementIndex() + navigNextLocationOffset);
632             } else {
633                 executor.switchToAnotherPathElement(executor.getPathElementIndex());
634             }
635         }
636         navigNextLocationOffset = 0;
637 
638         prepareNextNode();
639 
640         if (localDistance < 20) {
641         	log.log(Level.FINER, "Navigator.switchToNextNode(): next location too near, switching again!");
642             return switchToNextNode();
643         }
644 
645         return true;
646     }
647 
648     // ================
649     // ================
650     // MOVER NAVIGATION
651     // ================
652     // ================    
653 
654     private int navigMoverRideUpCount;
655 
656     private int navigMoverRideDownCount;
657 
658     private Boolean navigMoverIsRidingUp;
659 
660     private Boolean navigMoverIsRidingDown;
661     
662     private Boolean navigMoverGettingBackRunnerReset;
663     
664     private Boolean navigMoverGettingToLiftCenterRunnerReset;
665     
666     private void resetNavigMoverVariables() {
667         navigMoverIsRidingUp = null;
668         navigMoverIsRidingDown = null;
669         navigMoverRideUpCount = 0;
670         navigMoverRideDownCount = 0;
671         navigMoverGettingBackRunnerReset = false;
672         navigMoverGettingToLiftCenterRunnerReset = false;
673     }
674 
675     private void checkMoverMovement(Mover mover) {
676         // ASSUMING THAT MOVER IS ALWAYS ... riding fully UP, riding fully DOWN (or vice versa) and passing all possible exits
677         if (mover.getVelocity().z > 0) {
678             // mover is riding UP
679             if (navigMoverIsRidingUp == null) {            	
680                 navigMoverIsRidingUp = true;
681                 navigMoverIsRidingDown = false;
682                 navigMoverRideUpCount = 1;
683                 navigMoverRideDownCount = 0;
684                 log.fine("Navigator.checkMoverMovement(): MOVER RIDING UP (1)");
685             } else if (navigMoverIsRidingDown) {
686                 navigMoverIsRidingUp = true;
687                 navigMoverIsRidingDown = false;
688                 ++navigMoverRideUpCount;
689                 log.fine("Navigator.checkMoverMovement(): MOVER RIDING UP (" + navigMoverRideUpCount + ")");
690             }
691         } else if (mover.getVelocity().z < 0) {
692             // mover is riding DOWN
693             if (navigMoverIsRidingDown == null) {
694                 navigMoverIsRidingUp = false;
695                 navigMoverIsRidingDown = true;
696                 navigMoverRideUpCount = 0;
697                 navigMoverRideDownCount = 1;
698                 log.fine("Navigator.checkMoverMovement(): MOVER RIDING DOWN (1)");
699             } else if (navigMoverIsRidingUp) {
700                 navigMoverIsRidingUp = false;
701                 navigMoverIsRidingDown = true;
702                 ++navigMoverRideDownCount;
703                 log.fine("Navigator.checkMoverMovement(): MOVER RIDING DOWN (" + navigMoverRideDownCount + ")");
704             }
705         }
706     }
707 
708     /**
709      * Tries to navigate the agent safely along mover navigation nodes.
710      *
711      * <h4>Pogamut troubles</h4>
712      *
713      * Since the engine does not send enough reasonable info about movers and
714      * their frames, the agent relies completely and only on the associated
715      * navigation points. Fortunately, LiftCenter navigation points move with
716      * movers.
717      *
718      * <p>
719      * Well, do not get too excited. Pogamut seems to update the position of
720      * LiftCenter navpoint from time to time, but it's not frequent enough for
721      * correct and precise reactions while leaving lifts.</p>
722      *
723      * @return Next stage of the navigation progress.
724      */
725     private Stage navigMover() {
726         Stage stage = navigStage;
727 
728         if (navigCurrentNode == null) {
729             if (log != null && log.isLoggable(Level.WARNING)) {
730                 log.warning("Navigator.navigMover(" + stage + "): can't navigate through the mover without the navpoint instance (navigCurrentNode == null)");
731             }
732             return Stage.CRASHED;
733         }
734 
735         Mover mover = (Mover) bot.getWorldView().get(navigCurrentNode.getMover());
736         if (mover == null) {
737             if (log != null && log.isLoggable(Level.WARNING)) {
738                 log.warning("Navigator.navigMover(" + stage + "): can't navigate through the mover as current node does not represent a mover (moverId == null): " + navigCurrentNode);
739             }
740             return Stage.CRASHED;
741         }
742         checkMoverMovement(mover);
743 
744         // update navigCurrentLocation as the mover might have moved
745         navigCurrentLocation = navigCurrentNode.getLocation();
746 
747         if (navigNextNode != null) {
748             // update navigNextLocation as the mover might have moved
749             navigNextLocation = navigNextNode.getLocation();
750         }
751 
752         log.fine("Navigator.navigMover(" + stage + "): SELF " + memory.getLocation());
753         log.fine("Navigator.navigMover(" + stage + "): CURR " + NavPoints.describe(navigCurrentNode));
754         log.fine("Navigator.navigMover(" + stage + "): NEXT " + NavPoints.describe(navigNextNode));
755         log.fine("Navigator.navigMover(" + stage + "):      " + NavPoints.describe(mover));
756 
757         // get horizontal distance from the mover center node ... always POSITIVE
758         int hDistance = (int) memory.getLocation().getDistance2D(navigCurrentLocation.getLocation());
759         // get vertical distance from the mover center node ... +/- ... negative -> mover is below us, positive -> mover is above us
760         int zDistance = (int) navigCurrentLocation.getLocation().getDistanceZ(memory.getLocation());
761         // whether mover is riding UP
762         boolean moverRidingUp = mover.getVelocity().z > 0;
763         // whether mover is riding DOWN
764         boolean moverRidingDown = mover.getVelocity().z < 0;
765         // whether mover is standing still
766         boolean moverStandingStill = Math.abs(mover.getVelocity().z) < Location.DISTANCE_ZERO;
767         // get 2D distance to the mover
768         int moverHDistance = (int) memory.getLocation().getDistance2D(mover.getLocation());
769         int moverZDistance = (int) mover.getLocation().getDistanceZ(memory.getLocation());
770         
771         log.finer("Navigator.navigMover(" + stage + "): CURR  hDist:" + hDistance + ", zDist:" + zDistance);
772         log.finer("Navigator.navigMover(" + stage + "): MOVER hDist:" + moverHDistance + ", zDist:" + moverZDistance + ", " + (moverRidingUp ? "riding UP" : (moverRidingDown ? "riding DOWN" : moverStandingStill ? "standing STILL" : " movement unknown")));
773 
774         // --------------
775         // --------------
776         // AWAITING MOVER
777         // --------------
778         // --------------
779         
780         if (navigStage == Stage.AWAITING_MOVER) {
781         	// SHOULD WE WAIT FOR THE MOVER?
782             boolean waitForTheMover = false;
783             
784             if (moverHDistance < 50 && moverZDistance > 100 && moverRidingUp) {
785             	log.fine("Navigator.navigMover(" + stage + "): we are UNDER the mover and it is RIDING UP ... assuming waiting position");
786             	waitForTheMover = true; 
787             } else
788             if (moverZDistance > 10) {
789             	log.fine("Navigator.navigMover(" + stage + "): mover is not in correct position ... assuming waiting position");
790             	waitForTheMover = true;
791             } else
792             if (zDistance > 20 && moverRidingUp) {
793             	log.fine("Navigator.navigMover(" + stage + "): mover is riding up, we won't make it to the center ... assuming waiting position");
794             	waitForTheMover = true;
795             }
796             
797             if (waitForTheMover) {
798             	// => WAIT FOR THE MOVER
799             	if (memory.atLocation(navigLastLocation, 50)) {
800             		// AT WAITING POSITION...
801             		if (navigMoverGettingBackRunnerReset) {
802                     	navigMoverGettingBackRunnerReset = false;
803                     	runner.reset();
804                     }
805             		body.turnTo(navigCurrentLocation);
806             		return navigStage;
807             	}            	
808             	if (!navigMoverGettingBackRunnerReset) {
809                 	runner.reset();
810                  	navigMoverGettingBackRunnerReset = true;
811                 }
812             	if (run(null, navigLastLocation, null, null, navigCurrentLocation, true) == NavigateResult.CRASHED) {
813                      if (log != null && log.isLoggable(Level.FINE)) {
814                          log.fine("Navigator.navigMover(" + stage + "): navigation to wait-for-mover node failed");
815                      }
816                      return Stage.CRASHED;
817                 }
818                 return navigStage;
819             }
820             if (navigMoverGettingBackRunnerReset) {
821             	navigMoverGettingBackRunnerReset = false;
822             	runner.reset();
823             }
824 
825             // MOVER HAS ARRIVED (at least that what we're thinking so...)
826             if (log != null && log.isLoggable(Level.FINER)) {
827                 log.finer("Navigator.navigMover(" + stage + "): mover arrived");
828             }
829 
830             // LET'S MOVE TO THE LIFT CENTER (do not use focus)
831             return navigToCurrentNode(false, true);
832         }
833         if (navigMoverGettingBackRunnerReset) {
834         	navigMoverGettingBackRunnerReset = false;
835         	runner.reset();
836         }
837         
838         // ------------
839         // ------------
840         // RIDING MOVER
841         // ------------
842         // ------------
843         
844         if (navigStage == Stage.RIDING_MOVER) {
845             if (navigMoverRideDownCount > 2 || navigMoverRideUpCount > 2) {
846                 // we're riding up & down without any effect ... failure :(
847                 if (log != null && log.isLoggable(Level.FINE)) {
848                     log.fine("Navigator.navigThroughMover(" + stage + "): navigation to mover exit node failed, we've rided twice up & down and there was no place suitable to exit the mover in order to get to get to " + navigCurrentNode);
849                 }
850                 return Stage.CRASHED;
851             }
852 
853             if (hDistance > 600) {
854                 if (log != null && log.isLoggable(Level.WARNING)) {
855                     log.warning("Navigator.navigThroughMover(" + stage + "): navigation to mover exit node failed, the node is too far, hDistance " + hDistance + " > 600, unsupported (weird navigation graph link)");
856                 }
857                 return Stage.CRASHED;
858             }
859 
860             // wait for the mover to ride us up/down
861             if (Math.abs(zDistance) > 50) {
862                 // run to the last node, the one we're waiting on
863                 log.finer("Navigator.navigMover(" + stage + "): riding the mover");
864                 
865                 //WE MUST NOT USE FOCUS! We have to see the mover. TODO: provide turning behavior, i.e., turn to desired focus once in a time
866                 if (moverHDistance < 35) {
867                 	log.finer("Navigator.navigMover(" + stage + "): at lift-center, looking towards exit");
868                 	if (navigMoverGettingToLiftCenterRunnerReset) {
869                 		navigMoverGettingToLiftCenterRunnerReset = false;
870                 		runner.reset();
871                 	}
872                 	body.turnTo(navigCurrentLocation);
873                 } else {
874                 	log.finer("Navigator.navigMover(" + stage + "): reaching lift-center, looking towards exit");
875                 	if (!navigMoverGettingToLiftCenterRunnerReset) {
876                 		navigMoverGettingToLiftCenterRunnerReset = true;
877                 		runner.reset();
878                 	}                	
879                 	// RUN: get back to the mover, look towars exit, do not jump
880 		            if (run(null, mover.getLocation(), null, null, navigCurrentLocation, true) == NavigateResult.CRASHED) {
881 		                log.fine("Navigator.navigMover(" + stage + "): navigation to last node failed");
882 		                return Stage.CRASHED;
883 		            }
884                 }
885                 // and keep waiting for the mover to go to the correct position
886 
887                 return navigStage;
888             }
889             if (navigMoverGettingToLiftCenterRunnerReset) {
890         		navigMoverGettingToLiftCenterRunnerReset = false;
891         		runner.reset();
892         	}
893 
894             // MOVER HAS ARRIVED TO POSITION FOR EXIT (at least that what we're thinking so...)
895             if (log != null && log.isLoggable(Level.FINER)) {
896                 log.finer("Navigator.navigMover(" + stage + "): exiting the mover");
897             }
898 
899             // LET'S MOVE TO THE LIFT EXIT (do not use focus, may jump)
900             return navigToCurrentNode(false, false);
901         } else {
902             if (log != null && log.isLoggable(Level.WARNING)) {
903                 log.warning("Navigator.navigThroughMover(" + stage + "): invalid stage, neither AWAITING_MOVER nor RIDING MOVER");
904             }
905             return Stage.CRASHED;
906         }
907 
908     }
909 
910     // =====================    
911     // =====================
912     // TELEPORTER NAVIGATION
913     // =====================
914     // =====================
915     
916     /**
917      * Tries to navigate the agent safely to the current navigation node.
918      *
919      * @return Next stage of the navigation progress.
920      */
921     private Stage navigThroughTeleport() {
922         if (navigCurrentNode != null) {
923             // update location of the current place we're reaching
924             navigCurrentLocation = navigCurrentNode.getLocation();
925         }
926 
927         if (navigNextNode != null) {
928             // update location of the Next place we're reaching
929             navigNextLocation = navigNextNode.getLocation();
930         }
931 
932         // Now we have to check whether we have not already been teleported
933         int localDistance2_1 = Integer.MAX_VALUE;
934         int localDistance2_2 = Integer.MAX_VALUE;
935         for (NavPointNeighbourLink link : navigCurrentNode.getOutgoingEdges().values()) {
936             if (link.getToNavPoint().isTeleporter()) {
937                 localDistance2_1 = (int) memory.getLocation().getDistance(link.getToNavPoint().getLocation());
938                 localDistance2_2 = (int) memory.getLocation().getDistance(Location.add(link.getToNavPoint().getLocation(), new Location(0, 0, 100)));
939 
940                 // are we close enough to switch to the OTHER END of the teleporter?
941                 if ((localDistance2_1 < 200) || (localDistance2_2 < 200)) {
942                     // yes we are! we already passed the teleporter, so DO NOT APPEAR DUMB and DO NOT TRY TO RUN BACK 
943                     if (log != null && log.isLoggable(Level.FINE)) {
944                         log.fine("Navigator.navigThroughTeleport(): at the other end of teleport, switching...");
945                     }
946                     // ... better to switch navigation to the next node
947                     if (!switchToNextNode()) {
948                         // switch to the direct navigation
949                         if (log != null && log.isLoggable(Level.FINE)) {
950                             log.fine("Navigator.navigThroughTeleport(): switch to direct navigation");
951                         }
952                         initDirectly(navigDestination);
953                     }
954                     return keepNavigating();
955                 }
956             }
957         }
958         
959         // We have not reached the teleport yet, continue running to the current node (teleporter)
960         return navigToCurrentNode(true, false);
961     }
962     
963     // =======================================
964     // LOWEST NAVIGATE METHOD - INVOKES RUNNER
965     // =======================================
966     
967     /**
968      * Tries to navigate the agent safely to the current navigation node.
969      *
970      * @return Next stage of the navigation progress.
971      */
972     private Stage navigToCurrentNode(boolean useFocus, boolean forceNoJump) {
973     	NavigateResult result = run(navigLastLocation, navigCurrentLocation, navigCurrentLink, navigNextLocation, useFocus ? focus : navigCurrentLocation, forceNoJump); 
974     	switch (result) {
975     	case RUNNING:  
976     		return navigStage;
977     	case CRASHED:  
978     		return Stage.CRASHED;
979     	case REACHED: 
980     		if (!switchToNextNode()) {
981                 initDirectly(navigDestination);
982             } 
983             return keepNavigating();
984     	}
985     	throw new PogamutException("Unhandled NavigateResult." + result, this);
986     }
987     
988     private enum NavigateResult {
989     	REACHED,
990     	RUNNING,
991     	CRASHED
992     }
993     
994     /**
995      * Tries to navigate the agent safely to the 'ilocFirst'.
996      */
997     private NavigateResult run(ILocated ilocPrevious, ILocated ilocFirst, NavPointNeighbourLink linkFirst, ILocated ilocSecond, ILocated ilocFocus, boolean forceNoJump) {
998     	//NavPoint npPrevious = (ilocPrevious instanceof NavPoint ? (NavPoint)ilocPrevious : null);
999     	NavPoint npFirst = getNavPoint(ilocFirst);
1000     	NavPoint npSecond = getNavPoint(ilocSecond);
1001     	
1002     	// 1. WRITE DOWN LOCATIONS TO TRAVEL TO    
1003     	Location locPrevious = ilocPrevious == null ? null : ilocPrevious.getLocation();
1004     	Location locCurrent = memory.getLocation();
1005     	Location locFirst = ilocFirst.getLocation();
1006     	Location locSecond = ilocSecond == null ? null : ilocSecond.getLocation();
1007     	
1008     	// 2. DECIDE ON SECOND LOCATION
1009     	if (npFirst != null) {
1010     		if (npFirst.isLiftCenter() || npFirst.isTeleporter()) {
1011     			// do not continue further when reaching LIFT-CENTER or TELEPORT
1012     			locSecond = null;
1013     			if (npFirst.isLiftCenter()) {
1014     				log.fine("Navigator.run(): Reaching LIFT-CENTER");
1015     			}
1016     			if (npFirst.isTeleporter()) {
1017     				log.fine("Navigator.run(): Reaching TELEPORTER");
1018     			}
1019     		}
1020     	}
1021     	if (npSecond != null) {
1022     		if (npSecond.isLiftCenter()) {
1023     			// do not continue on LIFT-CENTER, must be handled specifically with correct timing
1024     			locSecond = null;
1025     			log.fine("Navigator.run(): Will NOT continue to LIFT-CENTER");
1026     		}
1027     		if (npFirst != null && npFirst.isLiftExit() && npSecond.isLiftExit()) {
1028     			// do not continue immediately from LIFT-CENTER to LIFT-EXIT as we will need to wait for the mover to arrive to correct position
1029     			locSecond = null;
1030     			log.fine("Navigator.run(): Will NOT continue to LIFT-EXIT");
1031     		}
1032     	}
1033 
1034     	// 3. COUNT DISTANCES TO THE FIRST LOCATION
1035         int distToFirst   = (int) locCurrent.getDistance(locFirst);
1036         int dist2DToFirst = (int) locCurrent.getDistance2D(locFirst);
1037         int distZToFirst  = (int) Math.abs(locCurrent.getDistanceZ(locFirst));
1038         
1039         // 4. DECIDE UPON LOCATION-REACHED CONDITIONS
1040         double distTest = 50;
1041         double distTestZ = 115;
1042         
1043         if (npFirst != null) {
1044         	// ARE WE REACHING JUMP PAD?
1045         	if (npFirst.isJumpPad()) {
1046 	        	// Enlarge the testing distance as the jump pad has a bigger radius because of jump pad area of effect 
1047 	        	distTest = 70;
1048 	        	// Do not care about Z for Jumppads, as agent can be already in air
1049 	        	distZToFirst = 0;
1050 	        	log.fine("Navigator.run(): Reaching JUMP-PAD");
1051         	} else
1052         	// ARE WE REACHING LIFT-CENTER?
1053         	if (npFirst.isLiftCenter()) {
1054         		// Use more accurate constant for LIFT-CENTER
1055         		distTest = 30;
1056         	}        	
1057         }
1058         
1059         // 5. CHECK WHETHER WE HAVE REACHED THE FIRST LOCATION
1060         if (distToFirst < distTest) { // we are precisely on the location
1061         	log.fine("Navigator.run(): REACHED " + locFirst + ", dist == " + distToFirst + " < " + distTest + " == test-precision");
1062         	// => WE HAVE REACHED THE FIRST LOCATION
1063         	return NavigateResult.REACHED;
1064 
1065         }
1066         if (distZToFirst < distTestZ && dist2DToFirst < distTest) { // point location is a bit off in Z but we're standing on/below the location
1067         	log.fine("Navigator.run(): REACHED " + locFirst + ", distZ == " + distZToFirst + " < " + distTestZ + " == test-z-precision, dist2D == " + dist2DToFirst + " < " + distTest + " == test-precision");
1068         	// => WE HAVE REACHED THE FIRST LOCATION
1069         	return NavigateResult.REACHED;
1070         }
1071         
1072         // 6. CHECK FOR OPPORTUNISTIC SWITCH
1073         if (locPrevious != null && locSecond != null) {
1074         	Location navigDirection = locFirst.sub(locPrevious).getNormalized();
1075         	Location altDirection = locSecond.sub(locCurrent).getNormalized();
1076         	double angle = Math.acos(navigDirection.dot(altDirection));
1077         	
1078         	double correction = 50;
1079             double zMin = Math.min(locFirst.z, locSecond.z) - correction;
1080             double zMax = Math.max(locFirst.z, locSecond.z) + correction;
1081             boolean isZok = zMin < locCurrent.z && locCurrent.z < zMax;
1082             
1083             double distToSecond = locCurrent.getDistance2D(locSecond);
1084         
1085             if (angle < Math.PI / 3 && isZok) {
1086             	if (distToSecond < distTest) {
1087             		log.log(Level.FINE, "Navigator.run(): REACHED next " + locSecond + " reached, dist == " + distToSecond + " < " + distTest + " == test-precision");
1088             		return NavigateResult.REACHED;
1089             	}
1090             }
1091         }
1092         
1093         // 7. RUN TO THE LOCATION
1094         if (!runner.runToLocation(memory.getLocation(), locFirst, locSecond, ilocFocus, linkFirst, true, forceNoJump)) {
1095         	// CRASH!
1096         	log.log(Level.INFO, "Navigator.run(): Runner CRASHED!");
1097         	return NavigateResult.CRASHED;
1098         }
1099         
1100         return NavigateResult.RUNNING;
1101     }
1102     
1103     // =========
1104     // =========
1105     // UTILITIES
1106     // =========
1107     // =========
1108     
1109     /**
1110      * Returns {@link NavPoint} instance for a given location. If there is no
1111      * navpoint in the vicinity of {@link NavMeshNavigator#CLOSE_ENOUGH} null is
1112      * returned.
1113      *
1114      * @param location
1115      * @return
1116      */
1117     protected NavPoint getNavPoint(ILocated location) {
1118     	if (location == null) return null;
1119         if (location instanceof NavPoint) {
1120             return (NavPoint) location;
1121         }
1122         NavPoint np = DistanceUtils.getNearest(main.getWorldView().getAll(NavPoint.class).values(), location);
1123         if (np.getLocation().getDistance(location.getLocation()) < CLOSE_ENOUGH) {
1124             return np;
1125         }
1126         return null;
1127     }
1128     
1129     /**
1130      * Gets the link with movement information between two navigation points.
1131      * Holds information about how we can traverse from the start to the end
1132      * navigation point.
1133      *
1134      * @return NavPointNeighbourLink or null
1135      */
1136     private NavPointNeighbourLink getNavPointsLink(NavPoint start, NavPoint end) {
1137         if (start == null) {
1138             //if start NavPoint is not provided, we try to find some
1139             NavPoint tmp = getNavPoint(memory.getLocation());
1140             if (tmp != null) {
1141                 start = tmp;
1142             } else {
1143                 return null;
1144             }
1145         }
1146         if (end == null) {
1147             return null;
1148         }
1149 
1150         if (end.getIncomingEdges().containsKey(start.getId())) {
1151             return end.getIncomingEdges().get(start.getId());
1152         }
1153 
1154         return null;
1155     }
1156     
1157 
1158     /*========================================================================*/
1159     /**
1160      * Enum of types of terminating navigation stages.
1161      */
1162     private enum TerminatingStageType {
1163 
1164         /**
1165          * Terminating with success.
1166          */
1167         SUCCESS(false),
1168         /**
1169          * Terminating with failure.
1170          */
1171         FAILURE(true);
1172 
1173         /**
1174          * Whether the terminating with failure.
1175          */
1176         public boolean failure;
1177 
1178         /**
1179          * Constructor.
1180          *
1181          * @param failure Whether the terminating with failure.
1182          */
1183         private TerminatingStageType(boolean failure) {
1184             this.failure = failure;
1185         }
1186     };
1187 
1188     /**
1189      * Enum of types of mover navigation stages.
1190      */
1191     private enum MoverStageType {
1192 
1193         /**
1194          * Waiting for mover.
1195          */
1196         WAITING,
1197         /**
1198          * Riding mover.
1199          */
1200         RIDING;
1201     };
1202 
1203     /**
1204      * Enum of types of mover navigation stages.
1205      */
1206     private enum TeleportStageType {
1207 
1208         /**
1209          * Next navpoint is a teleport
1210          */
1211         GOING_THROUGH;
1212     };
1213 
1214     /**
1215      * All stages the navigation can come to.
1216      */
1217     public enum Stage {
1218 
1219         /**
1220          * Running directly to the destination.
1221          */
1222         REACHING() {
1223                     protected Stage next() {
1224                         return this;
1225                     }
1226                 },
1227         /**
1228          * Navigating along the path.
1229          */
1230         NAVIGATING() {
1231                     protected Stage next() {
1232                         return this;
1233                     }
1234                 },
1235         /**
1236          * Waiting for a mover to arrive.
1237          */
1238         AWAITING_MOVER(MoverStageType.WAITING) {
1239                     protected Stage next() {
1240                         return RIDING_MOVER;
1241                     }
1242                 },
1243         /**
1244          * Waiting for a mover to ferry.
1245          */
1246         RIDING_MOVER(MoverStageType.RIDING) {
1247                     protected Stage next() {
1248                         return NAVIGATING;
1249                     }
1250                 },
1251         /**
1252          * Navigation cancelled by outer force.
1253          */
1254         CANCELED(TerminatingStageType.FAILURE) {
1255                     protected Stage next() {
1256                         return this;
1257                     }
1258                 },
1259         /**
1260          * Navigation timeout reached.
1261          */
1262         TIMEOUT(TerminatingStageType.FAILURE) {
1263                     protected Stage next() {
1264                         return this;
1265                     }
1266                 },
1267         /**
1268          * Navigation failed because of troublesome obstacles.
1269          */
1270         CRASHED(TerminatingStageType.FAILURE) {
1271                     protected Stage next() {
1272                         return this;
1273                     }
1274                 },
1275         /**
1276          * Navigation finished successfully.
1277          */
1278         COMPLETED(TerminatingStageType.SUCCESS) {
1279                     protected Stage next() {
1280                         return this;
1281                     }
1282                 },
1283         /**
1284          * We're going through the teleport.
1285          */
1286         TELEPORT(TeleportStageType.GOING_THROUGH) {
1287                     protected Stage next() {
1288                         return NAVIGATING;
1289                     }
1290                 ;
1291         };
1292         
1293 
1294         /*====================================================================*/
1295 
1296         /**
1297          * Running through the mover.
1298          */
1299         private boolean mover;
1300         /**
1301          * Whether the nagivation is terminated.
1302          */
1303         public boolean terminated;
1304         /**
1305          * Whether the navigation has failed.
1306          */
1307         public boolean failure;
1308         /**
1309          * We're going through the teleport.
1310          */
1311         public boolean teleport;
1312 
1313         /*====================================================================*/
1314         /**
1315          * Constructor: Not finished, not failed
1316          */
1317         private Stage() {
1318             this.mover = false;
1319             this.teleport = false;
1320             this.terminated = false;
1321             this.failure = false;
1322         }
1323 
1324         private Stage(TeleportStageType type) {
1325             this.mover = false;
1326             this.teleport = true;
1327             this.failure = false;
1328             this.terminated = false;
1329         }
1330 
1331         /**
1332          * Constructor: mover.
1333          *
1334          * @param type Type of mover navigation stage.
1335          */
1336         private Stage(MoverStageType type) {
1337             this.mover = true;
1338             this.teleport = false;
1339             this.terminated = false;
1340             this.failure = false;
1341         }
1342 
1343         /**
1344          * Constructor: terminating.
1345          *
1346          * @param type Type of terminating navigation stage.
1347          */
1348         private Stage(TerminatingStageType type) {
1349             this.mover = false;
1350             this.teleport = false;
1351             this.terminated = true;
1352             this.failure = type.failure;
1353         }
1354 
1355         /*====================================================================*/
1356         /**
1357          * Retreives the next step of navigation sequence the stage belongs to.
1358          *
1359          * @return The next step of navigation sequence. Note: Some stages are
1360          * not part of any logical navigation sequence. In such cases, this
1361          * method simply returns the same stage.
1362          */
1363         protected abstract Stage next();
1364 
1365         /*====================================================================*/
1366         /**
1367          * Returns the first step of mover sequence.
1368          *
1369          * @return The first step of mover sequence.
1370          */
1371         protected static Stage FirstMoverStage() {
1372             return AWAITING_MOVER;
1373         }
1374 
1375         /**
1376          * Returns the first step of the teleporter sequence.
1377          *
1378          * @return
1379          */
1380         protected static Stage TeleporterStage() {
1381             return Stage.TELEPORT;
1382         }
1383     }
1384 
1385     /*========================================================================*/
1386     /**
1387      * Default: Loque Runner.
1388      */
1389     private IUT2004PathRunner runner;
1390 
1391     /*========================================================================*/
1392     /**
1393      * Agent's main.
1394      */
1395     protected UT2004Bot main;
1396     /**
1397      * Loque memory.
1398      */
1399     protected AgentInfo memory;
1400     /**
1401      * Agent's body.
1402      */
1403     protected AdvancedLocomotion body;
1404     /**
1405      * Agent's log.
1406      */
1407     protected Logger log;
1408 
1409 
1410     /**
1411      * Constructor.
1412      *
1413      * @param bot
1414      * @param move
1415      * @param info
1416      * @param runner
1417      * @param log
1418      */
1419     public NavMeshNavigator(UT2004Bot bot, AgentInfo info, AdvancedLocomotion move, IUT2004PathRunner runner, Logger log) {
1420         // setup reference to agent
1421         this.main = bot;
1422         this.memory = info;
1423         this.body = move;
1424         this.log = log;
1425 
1426         this.selfListener = new SelfListener(bot.getWorldView());
1427 
1428         // save runner object
1429         this.runner = runner;
1430         NullCheck.check(this.runner, "runner");
1431     }
1432 
1433     @Override
1434     public Logger getLog() {
1435         return log;
1436     }
1437 
1438 }