View Javadoc

1   package cz.cuni.amis.pogamut.ut2004.agent.navigation.loquenavigator;
2   
3   import java.util.logging.Level;
4   import java.util.logging.Logger;
5   
6   import cz.cuni.amis.pogamut.base.communication.worldview.object.IWorldObject;
7   import cz.cuni.amis.pogamut.base3d.worldview.object.ILocated;
8   import cz.cuni.amis.pogamut.base3d.worldview.object.Location;
9   import cz.cuni.amis.pogamut.unreal.communication.messages.UnrealId;
10  import cz.cuni.amis.pogamut.ut2004.agent.module.sensor.AgentInfo;
11  import cz.cuni.amis.pogamut.ut2004.agent.module.sensor.Players;
12  import cz.cuni.amis.pogamut.ut2004.agent.module.sensor.Senses;
13  import cz.cuni.amis.pogamut.ut2004.agent.navigation.IUT2004PathRunner;
14  import cz.cuni.amis.pogamut.ut2004.bot.command.AdvancedLocomotion;
15  import cz.cuni.amis.pogamut.ut2004.bot.impl.UT2004Bot;
16  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbcommands.Move;
17  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.NavPointNeighbourLink;
18  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.Player;
19  import cz.cuni.amis.utils.NullCheck;
20  
21  /**
22   * Responsible for direct running to location.
23   *
24   * <p>This class commands the agent directly to the given location. Silently
25   * tries to resolve incidental collisions, troubling pits, obstacles, etc.
26   * In other words, give me a destination and you'll be there in no time.</p>
27   *
28   * <h4>Precise jumper</h4>
29   *
30   * Most of the incident running problems and troubles can be solved by precise
31   * single-jumping or double-jumping. This class calculates the best spots for
32   * initiating such jumps and then follows jump sequences in order to nicely
33   * jump and then land exactly as it was desired.
34   *
35   * <h4>Pogamut troubles</h4>
36   *
37   * This class was supposed to use autotrace rays to scan the space and ground
38   * in from of the agent. However, results of depending on these traces were
39   * much worst than jumping whenever possible. Therefore, no autotrace is being
40   * used and the agent simply jumps a lot. Some human players do that as well.
41   * See {@link #runToLocation } for details.
42   *
43   * <h4>Speed</h4>
44   *
45   * The agent does not ever try to run faster than with speed of <i>1.0</i> as
46   * it is used by most of <i>body.runTo*()</i> methods. Anyway, speeding is not
47   * available to common players (AFAIK), so why should this agent cheat?
48   *
49   * <h4>Focus</h4>
50   *
51   * This class works with destination location as well as agent focal point.
52   * Since the agent can look at something else rather than the destination,
53   * this running API is also suitable for engaging in combat or escaping from
54   * battles.
55   *
56   * @author Juraj Simlovic [jsimlo@matfyz.cz]
57   */
58  public class LoqueRunner implements IUT2004PathRunner {
59      /**
60       * Number of steps we have taken.
61       */
62      private int runnerStep = 0;
63  
64      /**
65       * Jumping sequence of a single-jumps.
66       */
67      private int runnerSingleJump = 0;
68      /**
69       * Jumping sequence of a double-jumps.
70       */
71      private int runnerDoubleJump = 0;
72  
73      /**
74       * Collision counter.
75       */
76      private int collisionCount = 0;
77      
78      /**
79       * Collision location.
80       */
81      private Location collisionSpot = null;
82  
83      /*========================================================================*/
84  
85      /**
86       * Initializes direct running to the given destination.
87       */
88      public void reset()
89      {
90          // reset working info
91          runnerStep = 0;
92          runnerSingleJump = 0;
93          runnerDoubleJump = 0;
94          collisionCount = 0;
95          collisionSpot = null;
96      }
97  
98      /*========================================================================*/
99      
100     private Move addFocus(Move move, ILocated focus) {
101     	if (focus != null) {
102     		if (focus instanceof Player && ((IWorldObject)focus).getId() instanceof UnrealId) {
103     			move.setFocusTarget((UnrealId)((IWorldObject)focus).getId());
104     		} else {	
105     			move.setFocusLocation(focus.getLocation());
106     		}
107     	}
108     	return move;
109     }
110     
111     /**
112      * Handles running directly to the specified location.
113      *
114      * <h4>Pogamut troubles</h4>
115      *
116      * <p>Reachchecks are buggy (they ignore most of the pits). Autotrace rays
117      * are buggy (they can not be used to scan the ground). Now, how's the agent
118      * supposed to travel along a map full of traps, when he is all blind, his
119      * guide-dogs are stupid and blind as well and his white walking stick is
120      * twisted?</p>
121      *
122      * <p>There is only one thing certain here (besides death and taxes): No
123      * navpoint is ever placed above a pit or inside map geometry. But, navpoint
124      * positions are usually the only places where we know the ground is safe.
125      * So, due to all this, the agent tries to jump whenever possible and still
126      * suitable for landing each jump on a navpoint. This still helps overcome
127      * most of the map troubles. Though it is counter-productive at times.</p>
128      *
129      * @param fromLocation location we're running from, may be null
130      * @param firstLocation Location to which to run.
131      * @param secondLocation Location where to continue (may be null).
132      * @param focus Location to which to look.
133      * @param reachable Whether the location is reachable.
134      * @return True, if no problem occured.
135      */
136     public boolean runToLocation (Location fromLocation, Location firstLocation, Location secondLocation, ILocated focus, NavPointNeighbourLink navPointsLink, boolean reachable)
137     {
138 
139         if (log != null && log.isLoggable(Level.FINER)) {            
140      	            log.finer(
141 	                "Runner.runToLocation(): runnerStep is "
142 	                + runnerStep + ", reachable is " + reachable + ",  navPointsLink is" + navPointsLink
143 	            );            
144         }
145         
146         // take another step
147         runnerStep++;
148 
149         // wait for delayed start: this is usually used for waiting
150         // in order to ensure the previous runner request completion
151         if (runnerStep <= 0) {
152         	// TODO: [Jimmy] what's this? It is never effective, as we're increasing runnerStep in the previous statement
153             return true;
154         }
155 
156         // are we just starting a new runner request? the first step should
157         // always be like this, in order to gain speed/direction before jumps
158         if (runnerStep <= 1)
159         {
160             // start running to that location..
161             bot.getAct().act(addFocus(new Move().setFirstLocation(firstLocation).setSecondLocation(secondLocation), focus));
162 
163              // This heuristics works when the bot continues movement - e.g. when the jump is in the same direction as
164              // current velocity vector and we have already some speed
165              if ((navPointsLink != null) && (
166                     navPointsLink.isForceDoubleJump()
167                 || (navPointsLink.getNeededJump() != null)
168                 || (navPointsLink.getFlags() & 8) != 0
169                 )) {
170 
171                  // some jumplinks (R_Jump) are leading down, we jump only when we are going up or to approx. same level
172                  if (((navPointsLink.getFlags() & 8) == 0) || (memory.getLocation().z - 100 <= firstLocation.z)) { 
173                     Location direction = Location.sub(firstLocation, memory.getLocation()).getNormalized();
174                     Location velocityDir = new Location(memory.getVelocity().asVector3d()).getNormalized();
175                     Double result = Math.acos(direction.dot(velocityDir));
176 
177                     // we will jump if our speed is reasonable and our direction differs max. 20 degrees
178                     if (memory.getVelocity().size() > 200 && !result.isNaN() && result < (Math.PI / 9)) 
179                         return resolveJump(firstLocation, secondLocation, focus, navPointsLink, reachable);
180                  }
181              }                
182          
183             return true;
184         }
185 
186         // are we single-jumping already?
187         if (runnerSingleJump > 0)
188         {
189             // continue with the single-jump
190             return iterateSingleJumpSequence (firstLocation, secondLocation, focus, reachable);
191         }
192         // are we double-jumping already?
193         else if (runnerDoubleJump > 0)
194         {
195             // continue with the double-jump
196             return iterateDoubleJumpSequence (firstLocation, secondLocation, focus, reachable);
197         }
198         // collision experienced?
199         if (senses.isCollidingOnce())
200         {
201             // try to resolve it
202             return resolveCollision (firstLocation, secondLocation, focus, reachable);
203         }
204         // are we going to jump now?
205         else 
206         if 
207         (
208             // the agent is not jumping already
209             (runnerSingleJump == 0) && (runnerDoubleJump == 0)
210             &&
211             (
212                 // is the destination directly unreachable?
213                 !reachable
214                 // is there an unpleasant pit ahead?
215                 || (navPointsLink != null) &&
216                 (
217                     navPointsLink.isForceDoubleJump()
218                 || (navPointsLink.getNeededJump() != null)
219                 || (navPointsLink.getFlags() & 8) != 0
220                 )
221                 // note: see pogamut notes in javadoc above
222                 //|| true
223                 // is there an unpleasant wall ahead?
224                 // note: see pogamut notes in javadoc above
225                 //|| true
226                 // are we going to jump just because we want to show off?
227                 //|| (Math.random () < .03)
228             )
229         ) 
230         {
231             // try to start a jump
232             return resolveJump (firstLocation, secondLocation, focus, navPointsLink, reachable);
233         }
234 
235         // otherwise: just keep running to that location..
236         bot.getAct().act(addFocus(new Move().setFirstLocation(firstLocation).setSecondLocation(secondLocation), focus));
237 
238         if (log != null && log.isLoggable(Level.FINER)) {
239      	            log.finer(
240 	                "Runner.runToLocation(): issuing default move command to: " + firstLocation
241 	            );
242         }
243         
244         return true;
245     }
246 
247     /*========================================================================*/
248 
249     /**
250      * Tries to resolve collisions.
251      *
252      * <p>Only continuous collisions are resolved, first by a double jump, then
253      * by a single-jump.</p>
254      *
255      * @param firstLocation Location to which to run.
256      * @param secondLocation Location where to continue (may be null).
257      * @param focus Location to which to look.
258      * @param reachable Whether the location is reachable.
259      * @return True, if no problem occured.
260      */
261     private boolean resolveCollision (Location firstLocation, Location secondLocation, ILocated focus, boolean reachable)
262     {
263         // are we colliding at a new spot?
264         if (
265             // no collision yet
266             (collisionSpot == null)
267             // or the last collision is far away
268             || (memory.getLocation().getDistance2D(collisionSpot) > 120)
269         ) {
270             // setup new collision spot info
271         	if (log != null && log.isLoggable(Level.FINER)) 
272 	            log.finer(
273 	                "Runner.resolveCollision(): collision at "
274 	                + (int) memory.getLocation().getDistance2D(firstLocation)
275 	            );
276             collisionSpot = memory.getLocation();
277             collisionCount = 1;
278             // meanwhile: keep running to the location..
279             bot.getAct().act(addFocus(new Move().setFirstLocation(firstLocation).setSecondLocation(secondLocation), focus));
280             return true;
281         }
282         // so, we were already colliding here before..
283         // try to solve the problem according to how long we're here..
284         else 
285         	switch (collisionCount++ % 2) {
286             case 0:
287                 // ..first by a double jump sequnce
288             	if (log != null && log.isLoggable(Level.FINER)) 
289 	                log.finer(
290 	                    "Runner.resolveCollision(): repeated collision (" + collisionCount + "):"
291 	                    + " double-jumping at " + (int) memory.getLocation().getDistance2D(firstLocation)
292 	                );
293                 return initDoubleJumpSequence (firstLocation, secondLocation, focus, reachable);
294 
295             default:
296                 // ..then by a single-jump sequence
297             	if (log != null && log.isLoggable(Level.FINER)) 
298 	                log.finer(
299 	                    "Runner.resolveCollision(): repeated collision (" + collisionCount + "):"
300 	                    + " single-jumping at " + (int) memory.getLocation().getDistance2D(firstLocation)
301 	                );
302                 return initSingleJumpSequence (firstLocation, secondLocation, focus, reachable);
303         	}
304     }
305 
306     /*========================================================================*/
307 
308     /**
309      * Starts a new (single or double)-jump sequence based on the distance.
310      *
311      * <p>Due to inevitability of ensuring of landing on destination locations,
312      * jumps may only be started, when it is appropriate. This method decides,
313      * whether and which jump would be appropriate and the initiates jumping
314      * sequence.</p>
315      *
316      * @param firstLocation Location to which to run.
317      * @param secondLocation Location where to continue (may be null).
318      * @param focus Location to which to look.
319      * @param reachable Whether the location is reachable.
320      * @return True, if no problem occured.
321      */
322     private boolean resolveJump (Location firstLocation, Location secondLocation, ILocated focus, NavPointNeighbourLink navPointsLink, boolean reachable)
323     {    		
324         // get the distance of the target location
325         int distance = (int) memory.getLocation().getDistance2D(firstLocation);
326         // get the agent overall velocity
327         int velocity = (int) memory.getVelocity().size();
328 
329         // cut the jumping distance of the next jump.. this is to allow to
330         // jump more than once per one runner request, while ensuring that
331         // the last jump will always land exactly on the destination..
332         int jumpDistance = distance % 1000;
333                 
334         // get the agent z-distance (e.g. is the destination above/below?)..     
335         int zDistance = (int) firstLocation.getDistanceZ(memory.getLocation());
336         
337         // IF zDistance < 0 THEN WE'RE JUMPING DOWN!
338         
339         // adjust jumping distance for jumps into lower/higher positions
340         jumpDistance += Math.min (200, Math.max (-200, zDistance));
341         
342         log.finer("Runner.resolveJump: distance = " + distance + ", velocity = " + velocity + ", jumpDistance = " + jumpDistance + ", zDistance = " + zDistance);        
343         
344         // TODO: [Jimmy] test it!
345         boolean enforceDoubleJump = false;       
346         if ((navPointsLink != null) 
347         	 && 
348         	( (navPointsLink.getNeededJump() != null || (navPointsLink.getFlags() & 8 ) != 0) 
349               && (zDistance > 60 || jumpDistance > 380)
350             )
351            ){
352         	// we should jump && the zDistance between our position and the target is more than 60units
353         	// we won't ever make it with ordinary jump
354         	enforceDoubleJump = true;
355         	log.finest("Runner.resolveJump(): double jump indicated");
356         }
357 
358         // we already missed all jumping opportunities
359         if (jumpDistance < 370)
360         {
361             //TODO: test - michal bida
362             //We force jump when needed jump is true or when jump flag is set
363             if (navPointsLink != null) {
364                 if (navPointsLink.getNeededJump() != null || (navPointsLink.getFlags() & 8 ) != 0)
365                 	// TODO: [Jimmy] test it!
366                 	if (enforceDoubleJump) return initDoubleJumpSequence(firstLocation, secondLocation, focus, reachable);
367                     return initSingleJumpSequence (firstLocation, secondLocation, focus, reachable);
368             }
369             
370             // if it's reachable, don't worry, we'll make it no matter what
371             // if it's unreachable: well, are we waiting for the next jump?
372             if (reachable || (distance >= 1000))
373             {
374                 // just keep running to that location..
375                 bot.getAct().act(addFocus(new Move().setFirstLocation(firstLocation).setSecondLocation(secondLocation), focus));
376                 return true;
377             }
378             // otherwise: we should try to solve the situation here, since
379             // the destination is not reachable (i.e. there is an obstacle
380             // or a pit ahead).. however, the reachability checks does not
381             // work very well, and raycasting is broken too.. well, trying
382             // to resolve this situation by a random choice does not work
383             // either.. therefore, just keep running to that location and
384             // wait for success or timeout, whatever comes first..
385             bot.getAct().act(addFocus(new Move().setFirstLocation(firstLocation).setSecondLocation(secondLocation), focus));
386             return true;
387         }
388         // this is the right space for a single-jump
389         else if (jumpDistance < 470)
390         {
391         	// is double jump enforced?
392         	if (enforceDoubleJump) return initDoubleJumpSequence(firstLocation, secondLocation, focus, reachable);
393             // start a single-jump sequences        	
394             return initSingleJumpSequence (firstLocation, secondLocation, focus, reachable);
395         }
396         // we already missed the double-jump opportunity
397         // this is the space for waiting for a single-jump
398         else if (jumpDistance < 600)
399         {
400         	// but if the double jump is enforced... we should try that!
401         	if (enforceDoubleJump) {
402         		// even though we've missed the opportunity, the ordinary jump won't help us!
403         		// TODO: [Jimmy] may be we should fail?
404         		return initDoubleJumpSequence(firstLocation, secondLocation, focus, reachable);
405         	}
406 
407             // meanwhile: keep running to the location..
408             bot.getAct().act(addFocus(new Move().setFirstLocation(firstLocation).setSecondLocation(secondLocation), focus));
409             return true;
410         }
411         // this is the right space for double-jumping
412         // but only, if we have the necessary speed
413         else if ((jumpDistance < 700) && (velocity > 300))
414         {
415         	if (!enforceDoubleJump) {
416         		// we do not need double jump
417         		// so... don't use double jump when link is R_Jump (for that we need just single jump)
418 	            if (navPointsLink != null && (navPointsLink.getFlags() & 8 ) != 0) {
419 	                // meanwhile: keep running to the location..
420 	                bot.getAct().act(addFocus(new Move().setFirstLocation(firstLocation).setSecondLocation(secondLocation), focus));
421 	                return true;
422 	            }
423         	}
424         	// we truly need the double jump! single jump won't take us too the desired target!
425 	           
426             // start double-jump sequence by double-jump command
427             return initDoubleJumpSequence (firstLocation, secondLocation, focus, reachable);
428         }
429         // otherwise, wait for the right double-jump distance
430         // meanwhile: keep running to the location..
431         bot.getAct().act(addFocus(new Move().setFirstLocation(firstLocation).setSecondLocation(secondLocation), focus));
432         return true;
433     }
434 
435     /*========================================================================*/
436 
437     /**
438      * Initiates new single-jump sequence.
439      *
440      * <p>Single-jump sequences are used to ensure that no single-jump is ever
441      * turned accidentally into a semi-double-jump. Such mishaps could lead to
442      * overjumping the desired landing location.</p>
443      *
444      * @param firstLocation Location to which to run.
445      * @param secondLocation Location where to continue (may be null).
446      * @param focus Location to which to look.
447      * @param reachable Whether the location is reachable.
448      * @return True, if no problem occured.
449      */
450     private boolean initSingleJumpSequence (Location firstLocation, Location secondLocation, ILocated focus, boolean reachable)
451     {
452         // do not allow two jumping sequences
453         if ((runnerSingleJump > 0) || (runnerDoubleJump > 0))
454             throw new RuntimeException ("jumping sequence aleady started");
455 
456         log.finer("Runner.initSingleJumpSequence() !");
457         
458         // point to the destination
459         bot.getAct().act(addFocus(new Move().setFirstLocation(firstLocation).setSecondLocation(secondLocation), focus));
460         // issue jump command
461         body.jump ();
462         // and setup sequence
463         runnerSingleJump = 1;
464         return true;
465     }
466 
467     /**
468      * Follows single-jump sequence steps.
469      * @param firstLocation Location to which to run.
470      * @param secondLocation Location where to continue (may be null).
471      * @param focus Location to which to look.
472      * @param reachable Whether the location is reachable.
473      * @return True, if no problem occured.
474      */
475     private boolean iterateSingleJumpSequence (Location firstLocation, Location secondLocation, ILocated focus, boolean reachable)
476     {
477         // get the distance of the target location
478         int distance = (int) memory.getLocation().getDistance2D(firstLocation);
479         // get the agent vertical velocity (e.g. is the agent jumping/falling?)..
480         int zVelocity = (int) memory.getVelocity().z;
481 
482         // what phase of the single-jump sequence?
483         switch (runnerSingleJump)
484         {
485             // the first phase: wait for the jump
486             case 1:
487                 // did the agent started the jump already?
488                 if (zVelocity > 100)
489                 {
490                     // continue the jump sequence by waiting for a peak
491                 	if (log != null && log.isLoggable(Level.FINER)) log.finer("Runner.iterateSingleJumpSequence(): single-jump registered at " + distance + ", z-velo " + zVelocity);
492                     runnerSingleJump++;
493                 }
494                 // meanwhile: just wait for the jump to start
495                 bot.getAct().act(addFocus(new Move().setFirstLocation(firstLocation).setSecondLocation(secondLocation), focus));
496                 return true;
497 
498             //  the last phase: finish the jump
499             default:
500                 // did the agent started to fall already
501                 if (zVelocity <= 0)
502                 {
503                     // kill the single-jump sequence
504                 	if (log != null && log.isLoggable(Level.FINER)) log.finer("Runner.iterateSingleJumpSequence(): single-jump completed at " + distance + ", z-velo " + zVelocity);
505                     runnerSingleJump = 0;
506                 }
507                 // meanwhile: just wait for the jump to start
508                 bot.getAct().act(addFocus(new Move().setFirstLocation(firstLocation).setSecondLocation(secondLocation), focus));
509                 return true;
510         }
511     }
512 
513     /*========================================================================*/
514 
515     /**
516      * Initiates new double-jump sequence.
517      *
518      * <p>Double-jump sequences are used to ensure that the agent correctly
519      * claims the double-jump boost at the jump peak.</p>
520      *
521      * @param firstLocation Location to which to run.
522      * @param secondLocation Location where to continue (may be null).
523      * @param focus Location to which to look.
524      * @param reachable Whether the location is reachable.
525      * @return True, if no problem occured.
526      */
527     private boolean initDoubleJumpSequence (Location firstLocation, Location secondLocation, ILocated focus, boolean reachable)
528     {
529         // do not allow two jumping sequences
530         if ((runnerSingleJump > 0) || (runnerDoubleJump > 0))
531             throw new RuntimeException ("jumping sequence aleady started");
532 
533         // point to the destination
534         //body.strafeTo(firstLocation, focus);
535 
536         // init double jump parameters to perform FULL DOUBLE JUMP
537         boolean doubleJump = true;
538         double delay = 0.39;
539         double jumpZ = 680;        
540                 
541         // if we want to jump 70 units up we use -> delay 0.39 / jumpZ 680
542         double distanceZ = firstLocation.getDistanceZ(memory.getLocation());
543         double distance2D = firstLocation.getDistance2D(memory.getLocation());
544         log.finer("Runner.initDoubleJumpSequence(): disntane2D = " + distance2D + ", distanceZ = " + distanceZ);
545         if (distanceZ > 0) {
546         	log.finer("Runner.initDoubleJumpSequence(): JUMPING UP! Adjusting parameters of the jump...");
547         	
548         	double  jumpZ_up = 680 * distanceZ / 70;
549         	boolean doubleJump_up = jumpZ_up > 340;
550         	
551         	double  jumpZ_forward = 680;
552         	boolean doubleJump_forward = true;
553         	if (distance2D < 250) {
554         		// single jump suffice
555         		doubleJump_forward = false;
556         		jumpZ_forward = 340 * distance2D / 250;
557         	} else
558         	if (distance2D < 350) {
559         		// smaller double jump is needed
560         		jumpZ_forward = 340 + 340 * (distance2D - 250) / 100;
561         	}
562         	
563         	if (jumpZ_up > jumpZ_forward) {        		
564         		jumpZ = jumpZ_up;
565         		doubleJump = doubleJump_up;
566         		log.finer("Runner.initDoubleJumpSequence(): jumping up more than jumping forward, jumpZ_up = " + jumpZ_up + " > " + jumpZ_forward + " = jumpZ_forward");
567         	} else {
568         		jumpZ = jumpZ_forward;
569         		doubleJump = doubleJump_forward;
570         		log.finer("Runner.initDoubleJumpSequence(): jumping forward more than jumping up, jumpZ_up = " + jumpZ_up + " < " + jumpZ_forward + " = jumpZ_forward");
571         	}
572         
573         } else {
574         	log.finer("Runner.initDoubleJumpSequence(): FALLING DOWN! Adjusting parameters of the jump for falling...");
575         	// we're going to fall, thus we have to be careful not to overjump the target
576         	
577         	// if we would be just falling (without any jump) we would move in 2D about 451 units when falling 382 units down (normal gravity)
578         	double distanceTravelledByFalling = 451/382 * Math.abs(distanceZ);
579         	// remainind distance for which we need jumping
580         	double remainingDistance2D = distance2D - distanceTravelledByFalling;
581         	
582         	// single jump will get us about 300 forward
583         	// double jump will get us about 450 forward
584         	// -- note that above two constants taking into account also a jump itself (it gets us higher so falling down will take us further),
585         	//    theoretically, we should compute much more complex equation but this seems to work OK
586         	
587         	if (remainingDistance2D < 300) {
588         		log.finer("Runner.initDoubleJumpSequence(): single jump suffice, distance2D = " + distance2D + ", estimated distance travelled by just falling = " + distanceTravelledByFalling + ", remaining distance 2D to jump = " + remainingDistance2D);
589         		doubleJump = false;
590         		jumpZ = 340 * remainingDistance2D / 300;
591         	} else
592         	if (remainingDistance2D < 450) {
593         		log.finer("Runner.initDoubleJumpSequence(): smaller double jump is needed, distance2D = " + distance2D + ", estimated distance travelled by just falling = " + distanceTravelledByFalling + ", remaining distance 2D to jump = " + remainingDistance2D);
594         		jumpZ = 340 + 340 * (remainingDistance2D - 220) * 150;
595         	} else {
596         		log.finer("Runner.initDoubleJumpSequence(): full double jump is needed, distance2D = " + distance2D + ", estimated distance travelled by just falling = " + distanceTravelledByFalling + ", remaining distance 2D to jump = " + remainingDistance2D);
597         		// preform full DOUBLE JUMP
598         	}
599         }
600         
601         log.finer("Runner.initDoubleJumpSequence(): " + (doubleJump ? "double jumping, double jump delay = " + delay : "single jumping") + ", jumpZ = " + jumpZ);
602 	        
603 	    // make some inferation about the doubleJump parameters 
604 	        
605 	    // issue jump command
606 	    body.doubleJump(delay, jumpZ);
607 	    // and setup sequence
608 	    runnerDoubleJump = 1;
609 	    return true;        
610     }
611 
612     /**
613      * Follows double-jump sequence steps.
614      * @param firstLocation Location to which to run.
615      * @param secondLocation Location where to continue (may be null).
616      * @param focus Location to which to look.
617      * @param reachable Whether the location is reachable.
618      * @return True, if no problem occured.
619      */
620     private boolean iterateDoubleJumpSequence (Location firstLocation, Location secondLocation, ILocated focus, boolean reachable)
621     {
622         // get the distance of the target location
623         int distance = (int) memory.getLocation().getDistance2D(firstLocation);
624         // get the agent vertical velocity (e.g. is the agent jumping/falling?)..
625         int zVelocity = (int) memory.getVelocity().z;
626 
627         // what phase of the double-jump sequence?
628         switch (runnerDoubleJump)
629         {
630             // the first phase: wait for the jump
631             case 1:
632                 // did the agent started the jump already?
633                 if (zVelocity > 100)
634                 {
635                     // continue the double-jump sequence by waiting for a peak
636                     if (log != null && log.isLoggable(Level.FINER)) log.finer("Runner.iterateDoubleJumpSequence(): double-jump registered at " + distance + ", z-velo " + zVelocity);
637                     runnerDoubleJump++;
638                 }
639                 // meanwhile: just wait for the jump to start
640                 bot.getAct().act(addFocus(new Move().setFirstLocation(firstLocation).setSecondLocation(secondLocation), focus));
641                 return true;
642 
643             // the second phase: claim the extra boost at jump peak..
644             case 2:
645                 // is this the awaited jump peak?
646                 if (zVelocity < 150)
647                 {
648                     // continue the double-jump sequence by a single jump boost
649                     if (log != null && log.isLoggable(Level.FINER)) log.finer("Runner.iterateDoubleJumpSequence(): double-jump boost at " + distance + ", z-velo " + zVelocity);
650                     body.jump ();
651                     runnerDoubleJump++;
652                     return true;
653                 }
654                 // meanwhile: just wait for the jump peak
655                 bot.getAct().act(addFocus(new Move().setFirstLocation(firstLocation).setSecondLocation(secondLocation), focus));
656                 return true;
657 
658             // the last phase:  finish the double-jump
659             default:
660                 // did the agent started to fall already
661                 if (zVelocity <= 0)
662                 {
663                     // kill the doule-jump sequence
664                     runnerDoubleJump = 0;
665                 }
666                 // meanwhile: just wait for the agent to start falling
667                 bot.getAct().act(addFocus(new Move().setFirstLocation(firstLocation).setSecondLocation(secondLocation), focus));
668                 return true;
669         }
670     }
671 
672     /*========================================================================*/
673 
674     /** Agent's bot. */
675     protected UT2004Bot bot;
676     /** Loque memory. */
677     protected AgentInfo memory;
678     /** Agent's body. */
679     protected AdvancedLocomotion body;
680     /** Agent's log. */
681     protected Logger log;
682     /** Base agent's senses. */
683 	protected Senses senses;
684 
685     /*========================================================================*/
686 
687     /**
688      * Constructor.
689      * @param bot Agent's bot.
690      * @param memory Loque memory.
691      */
692     public LoqueRunner (UT2004Bot bot, AgentInfo agentInfo, AdvancedLocomotion locomotion, Logger log) {
693         // setup reference to agent
694     	NullCheck.check(bot, "bot");
695     	this.bot = bot;
696         NullCheck.check(agentInfo, "agentInfo");
697         this.memory = agentInfo;
698         NullCheck.check(locomotion, "locomotion");
699         this.body = new AdvancedLocomotion(bot, log);
700         this.senses = new Senses(bot, memory, new Players(bot), log);
701         this.log = log;
702     }
703     
704 }