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.event.IWorldEventListener;
7   import cz.cuni.amis.pogamut.base.communication.worldview.object.IWorldObject;
8   import cz.cuni.amis.pogamut.base3d.worldview.object.ILocated;
9   import cz.cuni.amis.pogamut.base3d.worldview.object.Location;
10  import cz.cuni.amis.pogamut.unreal.communication.messages.UnrealId;
11  import cz.cuni.amis.pogamut.ut2004.agent.module.sensor.AgentInfo;
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.pogamut.ut2004.communication.messages.gbinfomessages.WallCollision;
20  import cz.cuni.amis.pogamut.ut2004.utils.LinkFlag;
21  import cz.cuni.amis.pogamut.ut2004.utils.UnrealUtils;
22  import cz.cuni.amis.utils.NullCheck;
23  
24  /**
25   * Responsible for direct running to location.
26   *
27   * <p>This class commands the agent directly to the given location. Silently
28   * tries to resolve incidental collisions, troubling pits, obstacles, etc.
29   * In other words, give me a destination and you'll be there in no time.</p>
30   *
31   * <h4>Precise jumper</h4>
32   *
33   * Most of the incident running problems and troubles can be solved by precise
34   * single-jumping or double-jumping. This class calculates the best spots for
35   * initiating such jumps and then follows jump sequences in order to nicely
36   * jump and then land exactly as it was desired.
37   *
38   * <h4>Pogamut troubles</h4>
39   *
40   * This class was supposed to use autotrace rays to scan the space and ground
41   * in from of the agent. However, results of depending on these traces were
42   * much worst than jumping whenever possible. Therefore, no autotrace is being
43   * used and the agent simply jumps a lot. Some human players do that as well.
44   * See {@link #runToLocation } for details.
45   *
46   * <h4>Speed</h4>
47   *
48   * The agent does not ever try to run faster than with speed of <i>1.0</i> as
49   * it is used by most of <i>body.runTo*()</i> methods. Anyway, speeding is not
50   * available to common players (AFAIK), so why should this agent cheat?
51   *
52   * <h4>Focus</h4>
53   *
54   * This class works with destination location as well as agent focal point.
55   * Since the agent can look at something else rather than the destination,
56   * this running API is also suitable for engaging in combat or escaping from
57   * battles.
58   * 
59   * <h4>Assumptions</h4>
60   * Following constants have been found out by playing around with UT2004:
61   * <ul>
62   * <li>single jump can jump 60 units up at max</li>
63   * <li>double jump can jump 120 units up at max, UT2004 can overcome obstacle of 5 units while jumping, so we should be able to get 125 units UP</li>
64   * <li>if not jumping, bot can overcome step 37 units high at max</li>
65   * <li>full speed is 440 units/sec</li>
66   * <li>second jump will always give us full speed of 445 units/sec</li>
67   * <li>single jump (if no falling/step is involved) takes 1 secs, i.e., it moves us forward the same amount of units as is our current velocity</li>
68   * <li>double jump (if no falling/step is involved) takes 1.5 secs, i.e., it moves us forward delay*our velocity + 1*445 forward</li>
69   * <li>how high we can jump with single/double does not depends on our velocity - we will always reach the peak of the jump</li>
70   * <li>we reach peak of the single jump in 0.5s</li>
71   * </ul> 
72   *
73   * @author Jimmy, Knight
74   * @author Juraj Simlovic [jsimlo@matfyz.cz]
75   */
76  public class KefikRunner implements IUT2004PathRunner {
77      
78  	// MAINTAINED CONTEXT
79  	
80  	/**
81       * Number of steps we have taken.
82       */
83      private int runnerStep = 0;
84  
85      /**
86       * Jumping sequence of a single-jumps.
87       */
88      private int jumpStep = 0;
89  
90      /**
91       * Collision counter.
92       */
93      private int collisionNum = 0;
94      
95      /**
96       * Collision location.
97       */
98      private Location collisionSpot = null;
99      
100     // COMPUTED CONTEXT OF THE runToLocation
101     
102     /**
103      * Current distance to the target, recalculated every {@link KefikRunner#runToLocation(Location, Location, ILocated, NavPointNeighbourLink, boolean)} invocation.
104      */
105     private double distance;
106     
107     /**
108      * Current 2D distance (only in x,y) to the target, recalculated every {@link KefikRunner#runToLocation(Location, Location, ILocated, NavPointNeighbourLink, boolean)} invocation.
109      */
110     private double distance2D;
111 
112     /**
113      * Current Z distance to the target (positive => target is higher than us, negative => target is lower than us), recalculated every {@link KefikRunner#runToLocation(Location, Location, ILocated, NavPointNeighbourLink, boolean)} invocation.
114      */
115     private double distanceZ;
116     
117     /**
118      * Current velocity of the bot, recalculated every {@link KefikRunner#runToLocation(Location, Location, ILocated, NavPointNeighbourLink, boolean)} invocation.
119      */
120     private double velocity;
121     
122     /**
123      * Current velocity in Z-coord (positive, we're going up / negative, we're going down), recalculated every {@link KefikRunner#runToLocation(Location, Location, ILocated, NavPointNeighbourLink, boolean)} invocation.
124      */
125     private double velocityZ;
126     
127     /**
128      * Whether the jump is required somewhere along the link, recalculated every {@link KefikRunner#runToLocation(Location, Location, ILocated, NavPointNeighbourLink, boolean)} invocation.
129      */
130     private boolean jumpRequired;
131     
132     /**
133      * In case of fall ({@link KefikRunner#distanceZ} < 0), how far can we get with normal fall.
134      */
135     private double fallDistance;
136     
137     // CONTEXT PASSED INTO runToLocation
138     
139     /**
140      * Current context of the {@link KefikRunner#runToLocation(Location, Location, Location, ILocated, NavPointNeighbourLink, boolean)}.
141      */
142     private Location runningFrom;
143     
144     /**
145      * Current context of the {@link KefikRunner#runToLocation(Location, Location, Location, ILocated, NavPointNeighbourLink, boolean)}.
146      */
147     private Location firstLocation;
148     
149     /**
150      * Current context of the {@link KefikRunner#runToLocation(Location, Location, Location, ILocated, NavPointNeighbourLink, boolean)}.
151      */
152     private Location secondLocation;
153     
154     /**
155      * Current context of the {@link KefikRunner#runToLocation(Location, Location, Location, ILocated, NavPointNeighbourLink, boolean)}.
156      */
157     private ILocated focus;
158     
159     /**
160      * Current context of the {@link KefikRunner#runToLocation(Location, Location, Location, ILocated, NavPointNeighbourLink, boolean)}.
161      */
162     private NavPointNeighbourLink link;
163     
164     /**
165      * Current context of the {@link KefikRunner#runToLocation(Location, Location, Location, ILocated, NavPointNeighbourLink, boolean)}.
166      */
167     private boolean reachable;
168     
169     /** Last received wall colliding event */
170     protected WallCollision lastCollidingEvent = null;    
171     /** If we have collided in last second we will signal it */
172     private static final double WALL_COLLISION_THRESHOLD = 1;
173     
174     /*========================================================================*/
175     
176     /**
177      * Returns link the bot is currently running on ... might not exist, always check against NULL!
178      */
179     public NavPointNeighbourLink getLink() {
180     	return link;
181     }
182     
183     /*========================================================================*/
184 
185     /**
186      * Initializes direct running to the given destination.
187      */
188     public void reset()
189     {
190         // reset working info
191         runnerStep = 0;
192         jumpStep = 0;
193         collisionNum = 0;
194         collisionSpot = null;
195         lastCollidingEvent = null;
196         distance = 0;
197         distance2D = 0;
198         distanceZ = 0;
199         velocity = 0;
200         velocityZ = 0;
201         jumpRequired = false;
202     }
203 
204     /*========================================================================*/
205    
206     private void debug(String message) {
207     	if (log.isLoggable(Level.FINER)) log.finer("Runner: " + message);
208     }
209     
210     /**
211      * Return how far the normal falling will get us. (Using guessed consts...)
212      * @param distanceZ
213      * @return
214      */
215     private double getFallDistance(double distanceZ) {
216     	distanceZ = Math.abs(distanceZ);
217     	if (distanceZ == 60) return 160;
218     	if (distanceZ < 60) return 2.66667*distanceZ;
219     	return 1.3714 * distanceZ + 35.527;
220     }
221     
222     /**
223      * Returns how far the jump will get at max.
224      * 
225      * @param doubleJump
226      * @param jumpDelay
227      * @param jumpForce
228      * @param distanceZ to be jumped to (fallen to)
229      * @return
230      */
231     private double getMaxJumpDistance(boolean doubleJump, double jumpDelay, double jumpForce, double distanceZ, double velocity) {
232     	if (doubleJump) {
233     		jumpForce = Math.min(UnrealUtils.FULL_DOUBLEJUMP_FORCE, jumpForce);
234     	} else {
235     		jumpForce = Math.min(UnrealUtils.FULL_JUMP_FORCE, jumpForce);
236     	}
237     	jumpDelay = Math.min(0.75, jumpDelay);
238     	
239     	if (distanceZ >= -5) {
240     		// jumping up
241     		if (doubleJump) {
242     			return velocity * jumpDelay + (jumpForce / UnrealUtils.FULL_DOUBLEJUMP_FORCE) * 400 * (1 + jumpDelay);
243     		} else {
244     			return velocity * jumpForce;
245     		}
246     	} else {
247     		// falling down
248     		return getFallDistance(distanceZ) + getMaxJumpDistance(doubleJump, jumpDelay, jumpForce, 0, velocity);
249     	}
250     }
251     
252     /**
253      * Returns how far the jump will get us when we want to jump to the height of 'distanceZ'.
254      * <p><p>
255      * Assumes 'distanceZ' > 0
256      * 
257      * @param doubleJump whether we're using double jump
258      * @param jumpDelay (max 0.75)
259      * @param jumpForce (max {@link UnrealUtils#FULL_DOUBLEJUMP_FORCE} / {@link UnrealUtils#FULL_JUMP_FORCE})
260      * @param distanceZ to be jumped to (must be > 0)
261      * @return
262      */
263     private double getJumpUpDistance(boolean doubleJump, double jumpDelay, double jumpForce, double distanceZ, double velocity) {
264     	double jumpForceHeight;
265     	
266     	double result;
267     	
268     	if (doubleJump) {
269     		// COUNTING FOR DOUBLE JUMP
270     		
271     		jumpDelay = Math.min(0.75, jumpDelay);
272     		jumpForce = Math.min(UnrealUtils.FULL_DOUBLEJUMP_FORCE, jumpForce);
273     		
274     		// how high the jump force can get us (in distanceZ units)
275     		jumpForceHeight = (jumpForce / UnrealUtils.FULL_DOUBLEJUMP_FORCE) * 125;
276     		
277     		// total time of the jump in case of jumping to 'distanceZ = 0'
278     		double totalTimeOfTheJump = (jumpForce / UnrealUtils.FULL_DOUBLEJUMP_FORCE) + jumpDelay;
279     		
280     		if (jumpForceHeight > distanceZ) {
281     			// we're OK
282     			result = 
283     				// distance traveled when ascending with first jump
284     				velocity * jumpDelay
285     				// distance traveled when ascending with second jump
286       			  + UnrealUtils.MAX_VELOCITY * ((totalTimeOfTheJump-jumpDelay)/2)
287     			    // distance traveled when falling to 'distanceZ'
288     			  + UnrealUtils.MAX_VELOCITY * (((totalTimeOfTheJump-jumpDelay)/2) * (1-distanceZ/jumpForceHeight)); 
289     		} else {
290     			// we're doomed, we should return the distance of the peak of the jump
291     			result =
292     				// distance traveled when ascending with first jump
293     				velocity * jumpDelay
294     				// distance traveled when ascending with second jump
295     			  + ((totalTimeOfTheJump-jumpDelay)/2) * UnrealUtils.MAX_VELOCITY;
296     		}
297     	
298     	} else {
299     		// COUNTING FOR SINGLE JUMP
300     	
301     		jumpForce = Math.min(UnrealUtils.FULL_JUMP_FORCE, jumpForce);
302     		
303     		// how high the jump force can get us (in distanceZ units)
304     		jumpForceHeight = (jumpForce / UnrealUtils.FULL_JUMP_FORCE) * 55;
305     		
306     		// total time of the jump in case of jumping to 'distanceZ = 0'
307     		double totalTimeOfTheJump = jumpForce / UnrealUtils.FULL_JUMP_FORCE;
308     		
309     		if (jumpForceHeight > distanceZ) {
310     			// we're OK
311     			result = 
312         			   // distance we will travel when ascending
313     				   velocity * (totalTimeOfTheJump/2)
314     				   // distance we will travel when falling to the 'distanceZ' height
315     			     + velocity * ((totalTimeOfTheJump/2) * (1 - (distanceZ / jumpForceHeight)));
316     		} else {
317     			// we're doomed, we should return the PEAK of the jump
318     			result = velocity * (totalTimeOfTheJump/2);
319     		}
320     	}
321     	
322     	return result;
323     }
324     
325     /**
326      * Handles running directly to the specified location.
327      *
328      * <h4>Pogamut troubles</h4>
329      *
330      * <p>Reachchecks are buggy (they ignore most of the pits). Autotrace rays
331      * are buggy (they can not be used to scan the ground). Now, how's the agent
332      * supposed to travel along a map full of traps, when he is all blind, his
333      * guide-dogs are stupid and blind as well and his white walking stick is
334      * twisted?</p>
335      *
336      * <p>There is only one thing certain here (besides death and taxes): No
337      * navpoint is ever placed above a pit or inside map geometry. But, navpoint
338      * positions are usually the only places where we know the ground is safe.
339      * So, due to all this, the agent tries to jump whenever possible and still
340      * suitable for landing each jump on a navpoint. This still helps overcome
341      * most of the map troubles. Though it is counter-productive at times.</p>
342      *
343      * @param firstLocation Location to which to run.
344      * @param secondLocation Location where to continue (may be null).
345      * @param focus Location to which to look.
346      * @param reachable Whether the location is reachable.
347      * @return True, if no problem occured.
348      */
349     @Override
350     public boolean runToLocation(Location runningFrom, Location firstLocation, Location secondLocation, ILocated focus, NavPointNeighbourLink navPointsLink, boolean reachable)
351     {
352         // take another step
353         runnerStep++;
354     	
355     	// save context
356     	this.runningFrom = runningFrom;
357     	this.firstLocation = firstLocation;
358     	this.secondLocation = secondLocation;
359     	this.focus = focus;
360     	this.link = navPointsLink;
361     	this.reachable = reachable;
362     	
363         
364         // compute additional context
365         distance = memory.getLocation().getDistance(firstLocation);
366         distance2D = memory.getLocation().getDistance2D(firstLocation);
367         distanceZ = firstLocation.getDistanceZ(memory.getLocation());
368         if (distanceZ >= 0) fallDistance = 0;
369         else fallDistance = getFallDistance(distanceZ);
370         velocity = memory.getVelocity().size();
371         velocityZ = memory.getVelocity().z;
372         jumpRequired = 	
373         				!reachable ||
374         				(link != null 
375                           && (((link.getFlags() & LinkFlag.JUMP.get()) != 0) 
376           		              || (link.isForceDoubleJump())
377           		              || (link.getNeededJump() != null)
378           		             )
379           		        )
380         ; 
381     	
382         // DEBUG LOG
383         
384         if (log != null && log.isLoggable(Level.FINER)) {
385         	debug("KefikRunner!");
386         	debug("running to    = " + firstLocation + " and than to " + secondLocation + " and focusing to " + focus);
387         	debug("bot position  = " + memory.getLocation());
388         	debug("distance      = " + distance);
389         	debug("distance2D    = " + distance2D);
390     		debug("distanceZ     = " + distanceZ);
391     		debug("fallDistance  = " + fallDistance);
392     		debug("velocity      = " + velocity);
393     		debug("velocityZ     = " + velocityZ);
394     		debug("jumpRequired  = " + jumpRequired 
395     									+ (!reachable ? " NOT_REACHABLE" : "") 
396     									+ (link == null ? 
397     											"" 
398     										  : ( 
399     											    (link.getFlags() & LinkFlag.JUMP.get()) != 0 ? " JUMP_FLAG" : "") + (link.isForceDoubleJump() ? " DOUBLE_JUMP_FORCED" : "") + (link.getNeededJump() != null ? " AT[" + link.getNeededJump() + "]" : ""
400     											)
401     									  )   
402     			 );
403     		debug("reachable     = " + reachable);
404     		if (link != null) {
405     			debug("link          = " + link);
406     		} else {
407     			debug("LINK NOT PRESENT");
408     		}
409     		debug("collisionNum  = " + collisionNum);
410     		debug("collisionSpot = " + collisionSpot);
411     		debug("jumpStep      = " + jumpStep);
412     		debug("runnerStep    = " + runnerStep);
413         }
414         
415         // DELIBERATION
416         
417         if (runnerStep <= 1) {
418         	debug("FIRST STEP - start running towards new location");
419             move(firstLocation, secondLocation, focus);
420         }
421         
422         // are we jumping already?
423         if (jumpStep > 0)
424         {
425         	debug("we're already jumping");
426             return iterateJumpSequence();
427         }
428         
429         // collision experienced?
430         if (isColliding())
431         {
432         	debug("sensing collision");
433             // try to resolve it
434             return resolveCollision();
435         } else {
436         	if (collisionSpot != null || collisionNum != 0) {
437         		debug("no collision, clearing collision data");
438         		collisionNum = 0;
439         		collisionSpot = null;
440         	}
441         }
442         
443         if (velocity < 5 && runnerStep > 1) {
444         	debug("velocity is zero and we're in the middle of running");
445         	if (link != null && (link.getFromNavPoint().isLiftCenter() || link.getFromNavPoint().isLiftExit())) {
446         		if (link.getFromNavPoint().isLiftCenter()) {
447         			debug("we're standing on the lift center, ok");
448         		} else {
449         			debug("we're standing on the lift exit, ok");
450         		}
451         	} else {
452         		debug("and we're not standing on the lift center");
453         		return initJump(true);
454         	}
455         }
456         
457         // check jump
458         if (jumpRequired) {
459         	debug("jump is required");
460 	        return resolveJump();
461         }
462         
463         // just continue with ordinary run
464         debug("keeping running to the target");
465         move(firstLocation, secondLocation, focus);
466         
467         return true;
468     }
469     
470     /*========================================================================*/
471 
472     /**
473      * Decision has been made, we need to jump ({@link KefikRunner#jumpRequired} is true (!reachable || jump flag / needed jump / force double jump) and no collision has interrupted us) ... 
474      * but we do not know from where/when and even if we should jump yet.
475      * <p><p>
476      * This methods checks whether it is a right time to initiate a jump sequence based on various distances.
477      * <p><p>
478      * Due to inevitability of ensuring of landing on destination locations,
479      * jumps may only be started, when it is appropriate. This method decides,
480      * whether jump is appropriate.
481      *
482      * @return True, if no problem occured.
483      */
484     private boolean resolveJump()
485     {    		
486     	debug("resolveJump(): called");
487     	
488     	// cut the jumping distance2D of the next jump, this is to allow to
489         // jump more than once per one runner request, while ensuring that
490         // the last jump will always land exactly on the destination..
491         int jumpDistance2D = ((int)distance2D) % 1000;
492                 
493 	    debug("resolveJump(): jumpDistance2D = " + jumpDistance2D);
494 	    
495 	    // follow the deliberation about the situation we're currently in
496 	    boolean jumpIndicated = false;      // whether we should jump now
497 	    boolean mustJumpIfIndicated = false; // whether we MUST jump NOW
498 	            
499         boolean goingToJump = false;
500         
501         // deliberation, whether we may jump
502         
503         if (link != null &&
504         	(	((link.getFlags() & LinkFlag.JUMP.get()) != 0) 
505           		              || (link.isForceDoubleJump())
506           		              || (link.getNeededJump() != null)
507            )) {
508         	debug("resolveJump(): deliberation - jumping condition present");
509         	jumpIndicated = true;
510         }
511         
512         if (jumpDistance2D < 250) {
513         	debug("resolveJump(): we've missed all jumping opportunities (jumpDistance2D < 250)");
514         	if (runnerStep > 1) {
515         		debug("resolveJump(): and runnerStep > 1, if indicated we will be forced to jump right now");
516                 mustJumpIfIndicated = true;
517             } else {            	
518             	debug("resolveJump(): but runnerStep <= 1, can't force jump yet");
519             }
520         }
521         
522         debug("resolveJump(): jumpIndicated       = " + jumpIndicated);
523         debug("resolveJump(): mustJumpIfIndicated = " + mustJumpIfIndicated);
524         
525         if (jumpIndicated && mustJumpIfIndicated) {
526         	if (distanceZ > 0) {
527         		debug("resolveJump(): we MUST jump!");
528         		return prepareJump(true); // true == forced jump
529         	} else {
530         		debug("resolveJump(): we MUST fall down with a jump!");
531         		return prepareJump(true); // true == forced jump
532         	}
533         } else
534         if (jumpIndicated) {
535         	debug("resolveJump(): we should jump");
536         	return prepareJump(false); // false == we're not forcing to jump immediately         	
537         } else {
538         	debug("resolveJump(): we do not need to jump, waiting to reach the right spot to jump from");
539         	// otherwise, wait for the right double-jump distance2D
540         	// meanwhile: keep running to the location..
541         	move(firstLocation, secondLocation, focus);
542         	return true;
543         }
544     }
545     
546     /*========================================================================*/
547     
548     /**
549      * This method is called from {@link KefikRunner#resolveJump()} that has decided that the jump is necessary to reach the 
550      * the target (it is already known that distanceZ > 0).
551      * <p><p>
552      * jumpForced == true ... we will try to run no matter what
553      * <p><p>
554      * jumpForced == false ... we will check whether the time is right for jumping assessing the {@link KefikRunner#distanceZ}.
555      * 
556      * @return whether we should reach the target
557      */
558     private boolean prepareJump(boolean jumpForced) {
559     	debug("prepareJump(): called");    	
560     	
561     	Location direction = Location.sub(firstLocation, memory.getLocation()).setZ(0);
562     	direction = direction.getNormalized();
563 	    Location velocityDir = new Location(memory.getVelocity().asVector3d()).setZ(0);
564 	    velocityDir = velocityDir.getNormalized();
565 	    Double jumpAngleDeviation = Math.acos(direction.dot(velocityDir));
566 	    
567 	    boolean angleSuitable = !jumpAngleDeviation.isNaN() && jumpAngleDeviation < (Math.PI / 9);
568 	    
569 	    debug("prepareJump(): jumpAngleDeviation = " + jumpAngleDeviation);
570 	    debug("prepareJump(): angleSuitable      = " + angleSuitable);
571     	
572     	if (jumpForced) {
573     		debug("prepareJump(): jump is forced, bypassing jump checks!");
574     	} else {
575 	    	debug("prepareJump(): jump is not forced, checking jump conditions");
576 	    	
577 	    	
578 	    	if (velocity < 200 && distance2D > getMaxJumpDistance(true, UnrealUtils.FULL_DOUBLEJUMP_DELAY, UnrealUtils.FULL_DOUBLEJUMP_FORCE, distanceZ, velocity)) {
579 	    		debug("prepareJump(): velocity is too low for jump (velocity < 200) and target is too far away to jump there with double jump");
580 	    		debug("prepareJump(): proceeding with the straight movement to gain speed");
581 	    		move(firstLocation, secondLocation, focus);
582 	    		return true;
583 	    	}
584 	    	
585 	    	if (!angleSuitable) {
586 	    		debug("prepareJump(): angle is not suitable for jumping (angle > 20 degrees)");
587 	    		debug("prepareJump(): proceeding with the straight movement to gain speed");
588 	    		move(firstLocation, secondLocation, focus);
589 	    		return true;
590 	    	}
591 	    	
592 	    	debug("prepareJump(): velocity & angle is OK!");
593 	    }
594     	
595 		if (distanceZ >= 0) {
596     		debug("prepareJump(): JUMP (distanceZ >= 0)");
597         	return initJump(jumpForced);
598     	} else {
599     		debug("prepareFall(): FALL (distanceZ < 0)");
600         	return initFall(jumpForced);
601     	}
602     }
603     
604     /*========================================================================*/
605 
606     private double adjustJumpForce(double distanceSafeZone, boolean doubleJump, double jumpForce, double jumpDelay) {
607     	double distanceJumped = getJumpUpDistance(doubleJump, jumpDelay, jumpForce, distanceZ, velocity);
608     	debug("initJump(): adjusting jumpForce...");
609 		while (distanceJumped-distanceSafeZone < distance2D && // jump distance is still not enough 
610 			   (    (doubleJump && jumpForce < UnrealUtils.FULL_DOUBLEJUMP_FORCE) 
611 			    || (!doubleJump && jumpForce < UnrealUtils.FULL_JUMP_FORCE)) // and we still have a room to make the jump longer
612 			   ) {
613 			jumpForce += 10;
614 			distanceJumped = getJumpUpDistance(doubleJump, jumpDelay, jumpForce, distanceZ, velocity);
615 		}
616 		// clamp the jumpForce
617 		if (doubleJump) {
618 			jumpForce = Math.min(jumpForce, UnrealUtils.FULL_DOUBLEJUMP_FORCE);
619 		} else {
620 			jumpForce = Math.min(jumpForce, UnrealUtils.FULL_JUMP_FORCE);
621 		}
622 		debug("initJump(): jumpForce = " + jumpForce);
623 		return jumpForce;
624     }
625     
626     /**
627      * We have to jump up (distanceZ > 0) if there is a possibility that we get there by jumping
628      * (i.e., params for jump exists that should get us there) or 'jumpForced is true'.
629      * <p><p>
630      * Decides whether we need single / double jump and computes
631      * the best args for jump command according to current velocity.
632      * 
633      * @param jumpForced
634      */
635     private boolean initJump(boolean jumpForced) {
636     	debug("initJump(): called");
637     	
638     	boolean shouldJump = true;
639     	
640     	boolean doubleJump = true;
641     	double jumpForce = UnrealUtils.FULL_DOUBLEJUMP_FORCE;
642     	double jumpDelay = UnrealUtils.FULL_DOUBLEJUMP_DELAY;
643     	
644     	if (distanceZ > 130) {
645     		debug("initJump(): jump could not be made (distanceZ = " + distanceZ + " > 130)");
646     		if (jumpForced) {
647     			debug("initJump(): but jump is being forced!");
648     		} else {
649     			debug("initJump(): jump is not forced ... we will wait till the bot reach the right jumping spot");
650     			move(firstLocation, secondLocation, focus);
651             	jumpStep = 0; // we have not performed the JUMP
652             	return true;
653     		}
654     	}
655     	
656     	
657     	//here we try to determine if single jump is enough - we check if we are colliding, distanceZ and NeededJump.getZ attribute!
658     	if (collisionNum == 0 && distanceZ < 55 && distance2D < velocity * 0.85 
659     			&& (link == null || link.getNeededJump() == null || link.getNeededJump().getZ() <= UnrealUtils.FULL_JUMP_FORCE)  ) {
660     		debug("initJump(): single jump suffices (distanceZ < 55 && distance2D = " + distance2D + " < " + (velocity*0.85) +" = velocity * 0.85) && (link.getNeededJump == null ||  link.getNeededJump().getZ() <= UnrealUtils.FULL_JUMP_FORCE ))");
661     		doubleJump = false;
662     		jumpForce = UnrealUtils.FULL_JUMP_FORCE;
663     	}
664     	
665     	double jumpUp_force = 0;
666     	if (doubleJump) {
667     		if (collisionNum != 0) //when colliding, make maximum double jump!
668     			jumpUp_force = UnrealUtils.FULL_DOUBLEJUMP_FORCE;
669     		else
670     			jumpUp_force = UnrealUtils.FULL_DOUBLEJUMP_FORCE * ((distanceZ+5) / 110);//this constant 110 makes magic! careful!
671     		jumpUp_force = Math.min(jumpUp_force, UnrealUtils.FULL_DOUBLEJUMP_FORCE);
672     	} else {
673     		jumpUp_force = UnrealUtils.FULL_JUMP_FORCE * ((distanceZ+5) / 55);
674     		// JUMP FORCE SHOULD BE ALWAYS OK HERE AS WE'VE CHECKED distanceZ BEFORE!
675     		jumpUp_force = Math.min(jumpUp_force, UnrealUtils.FULL_JUMP_FORCE);
676     	}
677     	//Potential new Heuristics - if NeededJump is not null we will set our jump according to it 
678     	//(and increase it by 100 otherwise the bot will not make it)!
679     	//it didn't seem to help, commented for now
680     	/*if (collisionNum == 0 && (link != null && link.getNeededJump() != null)) {    		
681     		jumpUp_force = link.getNeededJump().getZ() + 100;
682     		if (jumpUp_force > UnrealUtils.FULL_JUMP_FORCE)
683     			doubleJump = true;    		
684     	}*/   	
685     	
686     	debug("initJump(): minimum force to jump to height " + distanceZ + " with " + (doubleJump ? "double" : "single") + " is " + jumpUp_force);
687     	double distanceSafeZone = 0;    	
688     	debug("initJump(): adjusting force to match jumping distance = " + distance2D + " = distance2D (safe zone = " + distanceSafeZone + ")");
689     	jumpForce = adjustJumpForce(distanceSafeZone, doubleJump, jumpUp_force, jumpDelay);
690     	double distanceJumped = getJumpUpDistance(doubleJump, jumpDelay, jumpForce, distanceZ, velocity);    	
691 		if (distanceJumped-distanceSafeZone < distance2D) {
692 			debug("initJump(): too short! (distanceJumped-" + distanceSafeZone + " = " + (distanceJumped-distanceSafeZone) + " < " + distance2D + " = distance2D)");
693    			if (!doubleJump) {
694    				debug("initJump(): trying double jump");
695    				doubleJump = true;
696 				jumpUp_force = UnrealUtils.FULL_DOUBLEJUMP_FORCE * ((distanceZ+5) / 125);
697 	    		jumpUp_force = Math.min(jumpUp_force, UnrealUtils.FULL_DOUBLEJUMP_FORCE);
698     	    	debug("initJump(): minimum force to jump to height " + distanceZ + " with double jump is " + jumpUp_force);
699     	    	debug("initJump(): adjusting force to match jumping distance = " + distance2D + " = distance2D (safe zone = " + distanceSafeZone + ")");
700     	    	jumpForce = adjustJumpForce(distanceSafeZone, doubleJump, jumpUp_force, jumpDelay);
701     	    	distanceJumped = getMaxJumpDistance(doubleJump, jumpDelay, jumpForce, distanceZ, velocity);    
702     	    	if (distanceJumped-distanceSafeZone < distance2D) {
703     	    		debug("initJump(): still too short! (distanceJumped-" + distanceSafeZone + " = " + (distanceJumped-distanceSafeZone) + " < " + distance2D + " = distance2D)");
704     	    		shouldJump = false;
705     	    	} else {
706     	    		debug("initJump(): distance ok (distanceJumped-" + distanceSafeZone + " = " + (distanceJumped-distanceSafeZone) + " >= " + distance2D + " = distance2D)");
707     	    		shouldJump = true;
708     	    	}
709     		} else {
710     			shouldJump = false;
711     		}	    		
712 		} else {
713 			debug("initJump(): distance ok (distanceJumped-" + distanceSafeZone + " = " + (distanceJumped-distanceSafeZone) + " >= " + distance2D + " = distance2D)");
714     		shouldJump = true;
715 		}
716 		
717 		if (shouldJump || jumpForced) {
718 			if (jumpForced && !shouldJump) {
719 				debug("initJump(): we should not be jumping, but jump is FORCED!");
720 			}
721 			jumpStep = 1; // we have performed the JUMP
722 	   		return jump(true, jumpDelay, jumpForce);
723 		} else {
724 			debug("initJump(): jump is not forced ... we will wait till the bot reach the right jumping spot");
725 			move(firstLocation, secondLocation, focus);
726         	jumpStep = 0; // we have not performed the JUMP
727         	return true;
728 		}
729     }
730     
731     /**
732      * We have to jump to fall down (distanceZ < 0) right now. Decides whether we need single / double jump and computes
733      * the best args for jump command according to current velocity.
734      */
735     private boolean initFall(boolean jumpForced) {
736     	debug("initFall(): called");
737     	
738     	jumpStep = 1;
739     	
740     	log.finer("Runner.initDoubleJumpSequence(): FALLING DOWN! Adjusting parameters of the jump for falling...");
741     	// we're going to fall, thus we have to be careful not to overjump the target
742     	
743     	// remainind distance for which we need jumping
744     	double remainingDistance2D = distance2D - fallDistance;
745     	
746     	debug("initFall(): distance2D          = " + distance2D);
747     	debug("initFall(): falling will get us = " + fallDistance + " further");
748     	debug("initFall(): remainingDistance2D = " + remainingDistance2D);
749     	
750     	// FULL DOUBLE JUMP
751     	boolean doubleJump = true;
752     	double jumpZ = 705;    		
753     	
754     	// single jump will get us about 300 forward
755     	// double jump will get us about 450 forward
756     	// -- note that above two constants taking into account also a jump itself (it gets us higher so falling down will take us further),
757     	//    theoretically, we should compute much more complex equation but this seems to work OK
758     	if (remainingDistance2D < velocity) {
759     		debug("initFall(): single jump suffices (remainingDistance2D < velocity)");
760     		doubleJump = false;
761     		jumpZ = 340 * remainingDistance2D / 300;
762     	} else
763     	if (remainingDistance2D < 450) {
764     		log.finer("initFall(): smaller double jump is needed (remainingDistance2D < 450)");
765     		doubleJump = true;
766     		jumpZ = 340 + 365 * (remainingDistance2D - 220) * 150;
767     	} else {
768     		log.finer("Runner.initDoubleJumpSequence(): full double jump is needed (remainingDistance2D > 450)");
769     		doubleJump = true;
770     		jumpZ = 705; 
771     	}
772     	
773     	return jump(doubleJump, 0.39, jumpZ);
774     }
775     
776     /*========================================================================*/    
777     
778     /**
779      * Perform jump right here and now with provided args.
780      */
781     private boolean jump(boolean doubleJump, double delay, double force) {
782     	if (doubleJump) {
783     		debug("DOUBLE JUMPING (delay = " + delay + ", force = " + force + ")");
784     	} else {
785     		debug("JUMPING (delay = " + delay + ", force = " + force + ")");
786     	}
787     	body.jump(doubleJump, delay, force);
788     	
789     	return true;
790     }
791     
792     private void move(ILocated firstLocation, ILocated secondLocation, ILocated focus) {
793     	Move move = new Move();
794     	if (firstLocation != null) {
795     		move.setFirstLocation(firstLocation.getLocation());
796     	}
797     	if (secondLocation != null) {
798     		move.setSecondLocation(secondLocation.getLocation());
799     	}
800     	
801     	if (focus != null) {
802     		if (focus instanceof Player) {
803     			move.setFocusTarget((UnrealId)((IWorldObject)focus).getId());
804     		} else {	
805     			move.setFocusLocation(focus.getLocation());
806     		}
807     	}
808     	
809     	log.finer("MOVING: " + move);    	
810     	bot.getAct().act(move);
811     }
812     
813     /*========================================================================*/
814     
815     /**
816      * Tries to resolve collisions.
817      *
818      * <p>Only continuous collisions are resolved, first by a double jump, then
819      * by a single-jump.</p>
820      *
821      * @return True, if no problem occured.
822      */
823     private boolean resolveCollision()
824     {
825         // are we colliding at a new spot?
826         if (
827             // no collision yet
828             (collisionSpot == null)
829             // or the last collision is far away
830             || (memory.getLocation().getDistance2D(collisionSpot) > 120)
831         ) {
832             // setup new collision spot info
833         	if (log != null && log.isLoggable(Level.FINER)) log.finer("Runner.resolveCollision(): collision");
834             collisionSpot = memory.getLocation();
835             collisionNum = 1;
836             // meanwhile: keep running to the location..
837             move(firstLocation, secondLocation, focus);
838             return true;
839         }
840         // so, we were already colliding here before..
841         // try to solve the problem according to how long we're here..
842         else { 
843             return initJump(true);
844         }
845     }
846 
847     /*========================================================================*/
848 
849     /**
850      * Follows single-jump sequence steps.
851      * @return True, if no problem occured.
852      */
853     private boolean iterateJumpSequence()
854     {
855     	debug("iterateJumpSequence(): called");
856         // what phase of the jump sequence?
857         switch (jumpStep) {
858             // the first phase: wait for the jump
859             case 1:
860                 // did the agent started the jump already?
861                 if (velocityZ > 100)
862                 {
863                 	debug("iterateJumpSequence(): jumping in progress (velocityZ > 100), increasing jumpStep");
864                     jumpStep++;
865                 }
866                 // meanwhile: just wait for the jump to start
867                 debug("iterateJumpSequence(): issuing move command to the target (just to be sure)");
868                 move(firstLocation, secondLocation, focus);
869                 return true;
870 
871             //  the last phase: finish the jump
872             default:
873                 // did the agent started to fall already
874                 if (velocityZ <= 0.01)
875                 {
876                 	debug("iterateJumpSequence(): jump ascension has ended (velocityZ < 0.01)");
877                     jumpStep = 0;
878                 }
879                 debug("iterateJumpSequence(): continuing movement to the target");
880                 move(firstLocation, secondLocation, focus);
881                 return true;
882         }
883         
884         
885     }
886 
887     /*========================================================================*/
888     
889     protected boolean isColliding() {
890     	if (lastCollidingEvent == null) return false;
891     	debug("isColliding():"+"(memory.getTime():" + memory.getTime() + " - (lastCollidingEvent.getSimTime() / 1000):" + (lastCollidingEvent.getSimTime() / 1000) +" <= WALL_COLLISION_THRESHOLD:" + WALL_COLLISION_THRESHOLD +  " )");
892     	if (memory.getTime() - (lastCollidingEvent.getSimTime() / 1000) <= WALL_COLLISION_THRESHOLD ) {
893     		debug("isColliding():return true;");
894     		return true;
895     	}
896     	
897     	return false;
898     }
899 
900     
901     /**
902      * Our custom listener for WallCollision messages.      
903      */
904     IWorldEventListener<WallCollision> myCollisionsListener = new IWorldEventListener<WallCollision>() {
905 		@Override
906 		public void notify(WallCollision event) {
907 			lastCollidingEvent = event;						
908 		}		
909 		
910 	};
911     
912     
913     /*========================================================================*/
914 
915     /** Agent's bot. */
916     protected UT2004Bot bot;
917     /** Loque memory. */
918     protected AgentInfo memory;
919     /** Agent's body. */
920     protected AdvancedLocomotion body;
921     /** Agent's log. */
922     protected Logger log;
923 
924 
925     /*========================================================================*/
926 
927     /**
928      * Constructor.
929      * 
930      * @param bot Agent's bot.
931      * @param memory Loque memory.
932      */
933     public KefikRunner(UT2004Bot bot, AgentInfo agentInfo, AdvancedLocomotion locomotion, Logger log) {
934         // setup reference to agent
935     	NullCheck.check(bot, "bot");
936     	this.bot = bot;
937         NullCheck.check(agentInfo, "agentInfo");
938         this.memory = agentInfo;
939         NullCheck.check(locomotion, "locomotion");
940         this.body = locomotion;        
941         
942         //registering listener for wall collisions
943         bot.getWorldView().addEventListener(WallCollision.class, myCollisionsListener);
944         
945         this.log = log;
946         if (this.log == null) {
947         	log = bot.getLogger().getCategory(this.getClass().getSimpleName());
948         }
949     }
950     
951 }