View Javadoc

1   package cz.cuni.amis.pogamut.ut2004.agent.navigation;
2   
3   import java.util.ArrayList;
4   import java.util.List;
5   import java.util.logging.Level;
6   
7   import cz.cuni.amis.pogamut.base.agent.navigation.IPathExecutorState;
8   import cz.cuni.amis.pogamut.base.agent.navigation.IPathFuture;
9   import cz.cuni.amis.pogamut.base.agent.navigation.IStuckDetector;
10  import cz.cuni.amis.pogamut.base.communication.worldview.event.IWorldEventListener;
11  import cz.cuni.amis.pogamut.base.utils.logging.LogCategory;
12  import cz.cuni.amis.pogamut.base.utils.math.DistanceUtils;
13  import cz.cuni.amis.pogamut.base3d.worldview.object.ILocated;
14  import cz.cuni.amis.pogamut.base3d.worldview.object.Location;
15  import cz.cuni.amis.pogamut.ut2004.agent.module.sensor.AgentInfo;
16  import cz.cuni.amis.pogamut.ut2004.agent.navigation.floydwarshall.FloydWarshallMap;
17  import cz.cuni.amis.pogamut.ut2004.agent.navigation.loquenavigator.KefikRunner;
18  import cz.cuni.amis.pogamut.ut2004.agent.navigation.loquenavigator.LoqueNavigator;
19  import cz.cuni.amis.pogamut.ut2004.agent.navigation.stuckdetector.UT2004DistanceStuckDetector;
20  import cz.cuni.amis.pogamut.ut2004.agent.navigation.stuckdetector.UT2004PositionStuckDetector;
21  import cz.cuni.amis.pogamut.ut2004.agent.navigation.stuckdetector.UT2004TimeStuckDetector;
22  import cz.cuni.amis.pogamut.ut2004.bot.command.AdvancedLocomotion;
23  import cz.cuni.amis.pogamut.ut2004.bot.impl.UT2004Bot;
24  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.EndMessage;
25  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.Item;
26  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.NavPoint;
27  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.Player;
28  import cz.cuni.amis.utils.flag.FlagListener;
29  
30  /**
31   * Facade for navigation in UT2004. Method navigate() can be called both synchronously and asynchronously.
32   * Uses UT2004PathExecutor and FloydWarshallMap for navigating and planning.
33   * @author knight
34   */
35  public class UT2004Navigation {
36  
37  	/** Log used by this class. */
38  	protected LogCategory log;
39      /** UT2004PathExecutor that is used for the navigation. */
40      protected UT2004PathExecutor<ILocated> pathExecutor;
41      /** FloydWarshallMap that is used for path planning. */
42      protected FloydWarshallMap fwMap;    
43      /** UT2004Bot reference. */
44      protected UT2004Bot bot;
45      /** UT2004GetBackToNavGraph for returning bot back to the navigation graph. */
46      protected UT2004GetBackToNavGraph getBackToNavGraph;
47      /** UT2004RunStraight is used to run directly to player at some moment. */
48      protected UT2004RunStraight runStraight;
49      /** Location threshold for requesting of a new path or switching a path. */
50      protected static final int NEW_PATH_DISTANCE_THRESHOLD = 40;
51      /** Location threshold for checking whether we have arrived on target. For XY - 2D plane distance */
52      protected static final int ARRIVED_AT_LOCATION_XY_THRESHOLD = 50;
53      /** Location threshold for checking whether we have arrived on target. For Z distance. */
54      protected static final int ARRIVED_AT_LOCATION_Z_THRESHOLD = 100;
55      /** When PLAYER is further from currentTarget than this location, recompute the path */
56  	protected static final double PLAYER_DISTANCE_TRASHOLD = 600;
57  	/** We're managed to get to player */
58  	public static final double AT_PLAYER = 100;
59      
60      /**
61       * Listener for UT2004PathExecutor state. Not used right now.
62       */
63      FlagListener<IPathExecutorState> myUT2004PathExecutorStateListener = new FlagListener<IPathExecutorState>() {
64  
65          @Override
66          public void flagChanged(IPathExecutorState changedValue) {
67              switch (changedValue.getState()) {                
68                  case TARGET_REACHED:
69                  	targetReached();
70                      break;
71                  case PATH_COMPUTATION_FAILED:
72                  case STUCK:
73                  	stuck();
74                      break;
75              }
76          }
77      };
78      
79      protected IWorldEventListener<EndMessage> endMessageListener = new IWorldEventListener<EndMessage>() {		
80  		@Override
81  		public void notify(EndMessage event) {
82  			navigate();
83  		}
84  	};
85  	
86  
87      // ===========
88      // CONSTRUCTOR
89      // ===========
90      
91      /**
92       * Here you may specify any custom UT2004Navigation parts.
93       * 
94       * @param bot
95       * @param ut2004PathExecutor
96       * @param fwMap
97       * @param getBackOnPath
98       * @param runStraight 
99       */
100     public UT2004Navigation(UT2004Bot bot, UT2004PathExecutor ut2004PathExecutor, FloydWarshallMap fwMap, UT2004GetBackToNavGraph getBackOnPath, UT2004RunStraight runStraight) {
101         this.log = bot.getLogger().getCategory(this.getClass().getSimpleName());
102     	this.bot = bot;
103     	
104     	this.fwMap = fwMap;
105         this.pathExecutor = ut2004PathExecutor;
106         
107         this.getBackToNavGraph = getBackOnPath;
108         this.runStraight = runStraight;
109 
110         initListeners();
111     }
112     
113     /**
114      * Will auto-create all needed UT2004Navigation subparts.
115      * @param bot
116      * @param info
117      * @param move
118      */
119 	public UT2004Navigation(UT2004Bot bot, AgentInfo info, AdvancedLocomotion move) {
120     	this.log = bot.getLogger().getCategory(this.getClass().getSimpleName());
121      	this.bot = bot;    	
122     	this.fwMap = new FloydWarshallMap(bot);
123     	
124 		this.pathExecutor = 
125         	new UT2004PathExecutor<ILocated>(
126         		bot, 
127         		new LoqueNavigator<ILocated>(bot, 
128         			new KefikRunner(bot, info, move, bot.getLog()), 
129         		bot.getLog())
130         	);
131 		
132 		// add stuck detectors that watch over the path-following, if it (heuristicly) finds out that the bot has stuck somewhere,
133     	// it reports an appropriate path event and the path executor will stop following the path which in turn allows 
134     	// us to issue another follow-path command in the right time
135         this.pathExecutor.addStuckDetector(new UT2004TimeStuckDetector(bot, 3000, 100000)); // if the bot does not move for 3 seconds, considered that it is stuck
136         this.pathExecutor.addStuckDetector(new UT2004PositionStuckDetector(bot));           // watch over the position history of the bot, if the bot does not move sufficiently enough, consider that it is stuck
137         this.pathExecutor.addStuckDetector(new UT2004DistanceStuckDetector(bot));           // watch over distances to target
138         
139         this.getBackToNavGraph = new UT2004GetBackToNavGraph(bot, info, move);
140         this.runStraight = new UT2004RunStraight(bot, info, move);
141         
142         initListeners();
143 	}
144 
145     private void initListeners() {
146     	this.pathExecutor.getState().addListener(myUT2004PathExecutorStateListener);        
147         bot.getWorldView().addEventListener(EndMessage.class, endMessageListener);
148 	}
149 	
150 	// ======================
151     // PUBLIC INTERFACE
152     // ======================
153         
154 	/**
155      * True if UT2004PathExecutor is currently trying to get somewhere.
156      * @return
157      */
158     public boolean isNavigating() {
159         return pathExecutor.isExecuting();
160     }
161     
162     /**
163      * Sets focus of the bot when navigating (when using this object to run to some location target)!
164      * To reset focus call this method with null parameter.
165      * @param located
166      */
167     public void setFocus(ILocated located) {
168         pathExecutor.setFocus(located);
169         getBackToNavGraph.setFocus(located);
170         runStraight.setFocus(located);
171     }
172 
173     /**
174      * Stops navigation and resets the class.
175      * 
176      * Does NOT reset focus!
177      */
178     public void stopNavigation() {
179         reset(true);
180     }
181     
182     /**
183      * This method can be called periodically or asynchronously. Will move bot to input location.
184      * Uses UT2004PathExecutor and FloydWarshallMap.
185      * The bot will stop on bad input (location null).
186      * @param target target location
187      */
188     public void navigate(ILocated target) {
189     	if (target == null) {
190     		if (log != null && log.isLoggable(Level.WARNING)) log.warning("Cannot navigate to NULL target!");
191     		return;
192     	}
193     	
194     	if (target instanceof Player) {
195     		// USE DIFFERENT METHOD INSTEAD
196     		navigate((Player)target);
197     		return;
198     	}
199     	
200     	if (navigating) {
201     		if (currentTarget == target || currentTarget.getLocation().equals(target.getLocation())) {
202     			// just continue with current execution
203     			return;
204     		}    		
205     		// NEW TARGET!
206     		// => reset - stops pathExecutor as well, BUT DO NOT STOP getBackOnPath (we will need to do that eventually if needed, or it is not running)
207     		reset(false);
208     	}
209     	
210     	if (log != null && log.isLoggable(Level.FINE)) log.fine("Start navigating to: " + target);
211     	
212     	navigating = true;
213     	
214     	currentTarget = target;
215     	
216     	navigate();
217     }
218     
219     /**
220      * This method can be called periodically or asynchronously. Will move bot to input location.
221      * Uses UT2004PathExecutor and FloydWarshallMap.
222      * The bot will stop on bad input (location null).
223      * @param location target location
224      */
225     public void navigate(Player player) {
226     	if (player == null) {
227     		if (log != null && log.isLoggable(Level.WARNING)) log.warning("Cannot navigate to NULL player!");
228     		return;
229     	}
230     	
231     	if (navigating) {
232     		if (currentTarget == player) {
233     			// just continue with the execution
234     			return;
235     		}    		
236     		// NEW TARGET!
237     		// => reset - stops pathExecutor as well, BUT DO NOT STOP getBackOnPath (we will need to do that eventually if needed, or it is not running)
238     		reset(false);
239     	}
240     	
241     	if (log != null && log.isLoggable(Level.FINE)) log.fine("Start pursuing: " + player);
242     	
243     	navigating = true;
244     	
245     	currentTarget = player.getLocation();
246     	
247     	currentTargetPlayer = player;
248     	
249     	navigate();
250     }
251     
252     /**
253      * Returns nearest navigation point to input location. FloydWarshallMap works only
254      * on NavPoints.
255      * @param location
256      * @return
257      */
258     public NavPoint getNearestNavPoint(ILocated location) {
259     	if (location instanceof NavPoint) return (NavPoint)location;
260     	if (location instanceof Item) {
261     		if (((Item)location).getNavPoint() != null) return ((Item)location).getNavPoint();
262     	}
263     	return DistanceUtils.getNearest(bot.getWorldView().getAll(NavPoint.class).values(), location);        
264     }
265     
266     /**
267      * Returns COPY of current path in list. May take some time to fill up. Returns
268      * empty list if path not computed.
269      * @return
270      */
271     public List<ILocated> getCurrentPathCopy() {
272         List<ILocated> result = new ArrayList();
273         if (currentFuturePath != null) {
274             result.addAll(currentFuturePath.get());
275         }
276         return result;
277     }
278 
279     /**
280      * Returns current path as in IPathFuture object that is used by ut2004pathExecutor
281      * to navigate. Can be altered. May return null if path not computed!
282      * Be carefull when altering this during UT2004PathExecutor run - it may cause
283      * undesirable behavior.
284      * @return
285      */
286     public List<ILocated> getCurrentPathDirect() {
287         if (currentFuturePath != null) {
288             return currentFuturePath.get();
289         }
290         return null;
291     }
292     
293     /**
294      * Current POINT where the navigation is trying to get to.
295      * @return
296      */
297     public ILocated getCurrentTarget() {
298     	return currentTarget;
299     }
300     
301     /**
302      * If navigation is trying to get to some player, this method returns non-null player we're trying to get to.
303      * @return
304      */
305     public Player getCurrentTargetPlayer() {
306     	return currentTargetPlayer;
307     }
308     
309     /**
310      * Returns previous location we tried to get to (e.g. what was {@link UT2004Navigation#getCurrentTarget()} before
311      * another {@link UT2004Navigation#navigate(ILocated)} or {@link UT2004Navigation#navigate(Player)} was called.
312      * @return
313      */
314     public ILocated getLastTarget() {
315     	return lastTarget;
316     }
317     
318     /**
319      * If previous target was a player, returns non-null player we previously tried to get to 
320      * (e.g. what was {@link UT2004Navigation#getCurrentTargetPlayer()} before
321      * another {@link UT2004Navigation#navigate(ILocated)} or {@link UT2004Navigation#navigate(Player)} was called.
322      * @return
323      */
324     public ILocated getLastTargetPlayer() {
325     	return lastTarget;
326     }
327         
328     // ======================
329     // VARIABLES
330     // ======================
331     
332     /** Last location target. */
333     protected ILocated lastTarget = null;
334     /** Last location target. */
335     protected Player   lastTargetPlayer = null;
336     /** Current location target. */
337     protected ILocated currentTarget = null;
338     /** Current target is player (if not null) */
339     protected Player   currentTargetPlayer = null;
340     /** Navpoint we're running from (initial position when path executor has been triggered) */
341     protected NavPoint fromNavPoint;
342     /** Navpoint we're running to, nearest navpoint to currentTarget */
343 	protected NavPoint toNavPoint;    
344     /** Current path stored in IPathFuture object. */
345     protected IPathFuture currentFuturePath;
346     /** Whether navigation is running. */
347     protected boolean navigating = false;
348     /** We're running straight to the player. */
349 	protected boolean runningStraightToPlayer = false;
350 	/** Where run-straight failed. */
351 	protected Location runningStraightToPlayerFailedAt = null;
352 	
353     // ======================
354     // UTILITY METHODS
355     // ======================
356     
357     protected void navigate() {
358 		if (!navigating) return;
359 		
360 		if (log != null && log.isLoggable(Level.FINE)) {
361 			log.fine("NAVIGATING");
362 		}
363 		if (currentTargetPlayer != null) {
364 			if (log != null && log.isLoggable(Level.FINE)) log.fine("Pursuing " + currentTargetPlayer);
365 			navigatePlayer();
366 		} else {
367 			if (log != null && log.isLoggable(Level.FINE)) log.fine("Navigating to " + currentTarget);
368 			navigateLocation();
369 		}
370 	}
371     
372     private void navigateLocation() {
373     	if (pathExecutor.isExecuting()) {
374 			// Navigation is driven by Path Executor already...		
375 			if (log != null && log.isLoggable(Level.FINE)) log.fine("Path executor running");			
376 			return;
377 		}
378 		
379 		// PATH EXECUTOR IS NOT RUNNING
380 		// => we have not started to run along path yet
381 
382 		// ARE WE ON NAV-GRAPH?
383 		if (!getBackToNavGraph.isOnNavGraph()) {
384 			// NO!
385 			// => get back to navigation graph
386 			if (log != null && log.isLoggable(Level.FINE)) log.fine("Getting back to navigation graph");
387     		getBackToNavGraph.execute();
388     		return;
389     	}
390 		// YES!    	
391     	// ... getBackToNavGraph will auto-terminate itself when we manage to get back to graph
392     	
393     	fromNavPoint = getNearestNavPoint(bot.getLocation());
394     	toNavPoint   = getNearestNavPoint(currentTarget);
395     	
396     	if (log != null && log.isLoggable(Level.FINE)) log.fine("Running from " + fromNavPoint.getId().getStringId() + " to " + toNavPoint.getId().getStringId());
397     	
398     	currentFuturePath = fwMap.computePath(fromNavPoint, toNavPoint);
399     	
400     	// FLOYD-WARSHAL HAS ALL PATHS PRECOMPUTED...
401     	// => path is already ready ;)
402     	
403     	// TINKER THE PATH
404     	processPathFuture(currentFuturePath);
405     	// LET'S START RUNNING!
406     	pathExecutor.followPath(currentFuturePath);	
407 	}
408 
409 	private void navigatePlayer() {
410 		double vDistance = bot.getLocation().getDistanceZ(currentTargetPlayer.getLocation());
411 		double hDistance = bot.getLocation().getDistance2D(currentTargetPlayer.getLocation());
412 		
413 		if (hDistance < AT_PLAYER && vDistance < 50) {
414 			// player reached
415 			if (log != null && log.isLoggable(Level.FINE)) log.fine("Player reached");	
416 			if (pathExecutor.isExecuting()) {
417 				pathExecutor.getPath().set(pathExecutor.getPath().size()-1, bot.getLocation());
418 			} else {
419 				targetReached();
420 			}
421 			return;
422 		}
423 		
424 		if (hDistance < 400 && Math.abs(vDistance) < 50) {
425 			// RUN STRAIGHT			
426 			if (runningStraightToPlayer) {
427 				if (runStraight.isFailed()) {
428 					runningStraightToPlayer = false;
429 					runningStraightToPlayerFailedAt = bot.getLocation();
430 				}
431 			} else {
432 				if (runningStraightToPlayerFailedAt == null ||                           // we have not failed previously
433 					bot.getLocation().getDistance(runningStraightToPlayerFailedAt) > 500 // or place where we have failed is too distant
434 				){
435 					if (getBackToNavGraph.isExecuting()) {
436 						getBackToNavGraph.stop();
437 					}
438 					if (pathExecutor.isExecuting()) {
439 						pathExecutor.stop();
440 					}
441 					runningStraightToPlayer = true;
442 					runStraight.runStraight(currentTargetPlayer);
443 				}				
444 			}
445 			if (runningStraightToPlayer) {
446 				if (log != null && log.isLoggable(Level.FINE)) log.fine("Running straight to player");
447 				return;
448 			}
449 		} else {
450 			if (runningStraightToPlayer) {
451 				runningStraightToPlayer = false;
452 				runStraight.stop();				
453 			}
454 		}
455 		
456 		if (pathExecutor.isExecuting()) {
457 			// Navigation is driven by Path Executor already...			
458 			if (log != null && log.isLoggable(Level.FINE)) log.fine("Path executor running");
459 			// check distance between point we're navigating to and current player's location
460 			double distance = currentTarget.getLocation().getDistance(currentTargetPlayer.getLocation());
461 			if (distance > PLAYER_DISTANCE_TRASHOLD) {
462 				if (log != null && log.isLoggable(Level.FINE)) log.fine("Player moved " + distance + " from its original location, checking path...");
463 				// WE NEED TO CHECK ON PATH!					
464 				NavPoint newToNavPoint = getNearestNavPoint(currentTargetPlayer);
465 				if (newToNavPoint != toNavPoint) {
466 					// WE NEED TO ALTER THE PATH!
467 					if (log != null && log.isLoggable(Level.FINE)) log.fine("Replanning path to get to " + currentTargetPlayer);
468 					
469 					ILocated currentlyRunningTo = pathExecutor.getPathElement();
470 					if (currentlyRunningTo == null) currentlyRunningTo = bot.getLocation();
471 					fromNavPoint = getNearestNavPoint(currentlyRunningTo);
472 					toNavPoint   = newToNavPoint;
473 					currentTarget = currentTargetPlayer.getLocation();
474 					
475 					currentFuturePath = fwMap.computePath(fromNavPoint, toNavPoint);
476 					
477 					// TINKER THE PATH
478 			    	processPathFuture(currentFuturePath);
479 			    	// LET'S START RUNNING!
480 			    	pathExecutor.followPath(currentFuturePath);	
481 				} else {
482 					if (log != null && log.isLoggable(Level.FINE)) log.fine("Path remains the same");
483 				}
484 			}
485 			
486 			return;
487 		}
488 		
489 		// PATH EXECUTOR IS NOT RUNNING
490 		// => we have not started to run along path yet
491 
492 		// ARE WE ON NAV-GRAPH?
493 		if (!getBackToNavGraph.isOnNavGraph()) {
494 			// NO!
495 			// => get back to navigation graph
496 			if (log != null && log.isLoggable(Level.FINE)) log.fine("Getting back to navigation graph");
497     		getBackToNavGraph.execute();
498     		return;
499     	}
500 		// YES!    	
501     	// ... getBackToNavGraph will auto-terminate itself when we manage to get back to graph
502     	
503     	fromNavPoint = getNearestNavPoint(bot.getLocation());
504     	toNavPoint   = getNearestNavPoint(currentTarget);
505     	
506     	if (log != null && log.isLoggable(Level.FINE)) log.fine("Running from " + fromNavPoint.getId().getStringId() + " to " + toNavPoint.getId().getStringId());
507     	
508     	currentFuturePath = fwMap.computePath(fromNavPoint, toNavPoint);
509     	
510     	// FLOYD-WARSHAL HAS ALL PATHS PRECOMPUTED...
511     	// => path is already ready ;)
512     	
513     	// TINKER THE PATH
514     	processPathFuture(currentFuturePath);
515     	// LET'S START RUNNING!
516     	pathExecutor.followPath(currentFuturePath);	
517 	}
518 
519 	/**
520      * Checks if last path element is in close distance from our desired target and if not, we
521      * will add our desired target as the last path element.
522      * @param futurePath
523      */
524     protected void processPathFuture(IPathFuture futurePath) {
525         List<ILocated> pathList = futurePath.get();
526         
527         if (pathList == null) {
528         	// we failed to compute the path, e.g., path does not exist
529         	return;
530         }
531         
532         if (!pathList.isEmpty()) {
533             ILocated lastPathElement = pathList.get(pathList.size() - 1);
534             if (lastPathElement.getLocation().getDistance(currentTarget.getLocation()) > NEW_PATH_DISTANCE_THRESHOLD) {
535                 currentFuturePath.get().add(currentTarget);
536             }
537         } else {
538             currentFuturePath.get().add(currentTarget);
539         }
540     }
541         
542     protected void stuck() {
543     	// DAMN ...
544     	reset(true);
545 	}
546 
547 	protected void targetReached() {
548 		// COOL !!!
549 		reset(true);
550 	}
551     
552     protected void reset(boolean stopGetBackToNavGraph) {
553     	if (currentTarget != null) {
554     		lastTarget = currentTarget;
555     		lastTargetPlayer = currentTargetPlayer;
556     	}
557     	
558     	navigating = false;
559     	
560     	currentTarget = null;
561     	currentTargetPlayer = null;
562     	
563     	fromNavPoint = null;
564     	toNavPoint = null;
565     	
566     	currentFuturePath = null;
567     	
568     	runningStraightToPlayer = false;
569     	runningStraightToPlayerFailedAt = null;
570     	
571     	
572     	pathExecutor.stop();
573     	runStraight.stop();
574     	if (stopGetBackToNavGraph) getBackToNavGraph.stop();
575     }   
576     
577     // ============================
578     // TWEAKING / LISTENERS
579     // ============================
580 
581     /**
582      * Use this to register listener to various states of path execution - stuck, target reached, etc.
583      * See {@link IPathExecutorState#getState()}.
584      * 
585      * @param listener
586      */
587     public void addStrongNavigationListener(FlagListener<IPathExecutorState> listener) {
588         pathExecutor.getState().addStrongListener(listener);
589     }
590 
591     /**
592      * Removes path state listener.
593      * @param listener
594      */
595     public void removeStrongNavigationListener(FlagListener<IPathExecutorState> listener) {
596         pathExecutor.getState().removeListener(listener);
597     }
598 
599     /**
600      * Returns list of all stuck detectors.
601      * @return
602      */
603     public List<IStuckDetector> getStuckDetectors() {
604         return pathExecutor.getStuckDetectors();
605     }
606     
607     /**
608      * Adds stuck detector for UT2004PathExecutor.
609      * @param stuckDetector
610      */
611     public void addStuckDetector(IStuckDetector stuckDetector) {
612         pathExecutor.addStuckDetector(stuckDetector);
613     }
614 
615     /**
616      * Removes stuck detector for UT2004PathExecutor.
617      * @param stuckDetector
618      */
619     public void removeStuckDetector(IStuckDetector stuckDetector) {
620         pathExecutor.removeStuckDetector(stuckDetector);
621     }
622     
623 }