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