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