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