View Javadoc

1   package cz.cuni.amis.pogamut.ut2004.agent.module.sensor;
2   
3   import java.util.ArrayList;
4   import java.util.Collection;
5   import java.util.Collections;
6   import java.util.HashMap;
7   import java.util.List;
8   import java.util.Map;
9   import java.util.logging.Level;
10  import java.util.logging.Logger;
11  
12  import cz.cuni.amis.pogamut.base.agent.module.SensorModule;
13  import cz.cuni.amis.pogamut.base.communication.worldview.IWorldView;
14  import cz.cuni.amis.pogamut.base.communication.worldview.event.IWorldEventListener;
15  import cz.cuni.amis.pogamut.base.communication.worldview.object.IWorldObjectEvent;
16  import cz.cuni.amis.pogamut.base.communication.worldview.object.IWorldObjectEventListener;
17  import cz.cuni.amis.pogamut.base.communication.worldview.object.event.WorldObjectUpdatedEvent;
18  import cz.cuni.amis.pogamut.base3d.worldview.object.Location;
19  import cz.cuni.amis.pogamut.unreal.communication.messages.UnrealId;
20  import cz.cuni.amis.pogamut.ut2004.bot.IUT2004BotController;
21  import cz.cuni.amis.pogamut.ut2004.bot.impl.UT2004Bot;
22  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.BeginMessage;
23  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.FlagInfo;
24  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.GameInfo;
25  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.InitedMessage;
26  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.Mutator;
27  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.NavPoint;
28  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.PlayerScore;
29  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.Self;
30  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.TeamScore;
31  import cz.cuni.amis.pogamut.ut2004.communication.translator.shared.events.MutatorListObtained;
32  import cz.cuni.amis.utils.exception.PogamutException;
33  
34  /**
35   * Memory module specialized on general info about the game.
36   * <p><p>
37   * It is designed to be initialized inside {@link IUT2004BotController#prepareBot(UT2004Bot)} method call
38   * and may be used since {@link IUT2004BotController#botInitialized(cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.GameInfo, cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.ConfigChange, cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.InitedMessage)}
39   * is called.
40   *
41   * @author Juraj 'Loque' Simlovic
42   * @author Jimmy
43   */
44  public class Game extends SensorModule<UT2004Bot>
45  {
46  	/**
47  	 * Enums for game types that shields you from Unreal's string ids of game types.
48  	 * @author Jimmy
49  	 *
50  	 */
51  	public enum GameType
52  	{
53  		/** Classic death-match: Kill or get killed. You're on you own! */
54  		BotDeathMatch,
55  		/** Team death-match: Strategic team killing. Shoot opponents only. */
56  		BotTeamGame,
57  		/** Capture the Flag! Raid the enemy base, steal their flag. */
58  		BotCTFGame,
59  		/** Bombing run. Play soccer in UT2004, either kick ball or shoot. */
60  		BotBombingRun,
61  		/** Double domination. Take control of specific spots on the map. */
62  		BotDoubleDomination,
63  		/** This type of game is not supported. */
64  		Unknown;
65  
66  		/**
67  		 * Tedious work this is.. Let's do it once, shall we?
68  		 *
69  		 * @param type Name of the type of the game type.
70  		 * @return Game type associated with given name.
71  		 */
72  		public static GameType getType(String type)
73  		{
74  			if (type.equalsIgnoreCase("BotDeathMatch"))       return BotDeathMatch;
75  			if (type.equalsIgnoreCase("BotTeamGame"))         return BotTeamGame;
76  			if (type.equalsIgnoreCase("BotCTFGame"))          return BotCTFGame;
77  			if (type.equalsIgnoreCase("BotBombingRun"))       return BotBombingRun;
78  			if (type.equalsIgnoreCase("BotDoubleDomination")) return BotDoubleDomination;
79  			return Unknown;
80  		}
81  	}
82  	
83  	/**
84  	 * Returns original {@link GameInfo} message.
85  	 * 
86  	 * @return
87  	 */
88  	public GameInfo getGameInfo() {
89  		return lastGameInfo;
90  	}
91  
92  	/**
93  	 * Retreives the type of the game.
94  	 *
95  	 * @return Type of the game.
96  	 */
97  	public GameType getGameType()
98  	{
99  		// retreive from GameInfo object and translate
100                 if (lastGameInfo == null) return null;
101 		return GameType.getType(lastGameInfo.getGametype());
102 	}
103 
104 	/*========================================================================*/
105 
106 	/**
107 	 * Retreives the name of current map.
108 	 *
109 	 * @return Name of the current map.
110 	 */
111 	public String getMapName()
112 	{
113 		// retreive from GameInfo object
114         if (lastGameInfo == null) return null;
115 		return lastGameInfo.getLevel();
116 	}
117 	
118 	/**
119 	 * Tells, whether the UT2004 is currently running map with name 'name'.
120 	 * @param name
121 	 * @return
122 	 */
123 	public boolean isMapName(String name) {
124 		if (lastGameInfo == null) return false;
125 		if (name == null) return false;
126 		return lastGameInfo.getLevel().toLowerCase().equals(name.toLowerCase());
127 	}
128 	
129 	/**
130 	 * It returns 'objectId' prefixed with "{@link Game#getMapName()}.".
131 	 * <p><p>
132      * Note that you may pass prefixed objectId into this method, it will detect it that and auto-correct upper/lower case of this existing prefix if needed. 
133      * Also you may use it as a validation feature because
134      * this method will raise an exception if the prefix does not match the current map name.
135 	 * 
136 	 * @param objectId will be auto-prefixed (if enabled, which is default)
137 	 * @return
138 	 */
139 	public String getPrefixed(String objectId) {
140 		// auto prefixing is enabled
141 		if (getMapName() == null) {
142 			throw new PogamutException("GameInfo was not received yet, can't prefix '" + objectId + "'.", this);
143 		}
144 		if (objectId.toLowerCase().startsWith(mapNameLowerChar + ".")) {
145 			// already prefixed!
146 			if (!objectId.startsWith(getMapName())) {
147 				// but wrong upper/lower case detected, replace!
148 				objectId = getMapName() + objectId.substring(mapNameLowerChar.length());
149 			}
150 			// correctly prefixed, just return it
151 			return objectId;
152 		} else {
153 			// not correctly prefixed, check whether there is any prefix at all?
154 			if (objectId.contains(".")) {
155 				// yes there is -> map name validation fails!
156 				throw new PogamutException("id '" + objectId + "' is already prefixed with '" + objectId.substring(0, objectId.indexOf(".")) + "' which is different from current map name '" + getMapName() + "', map name validation fails!", this);
157 			}
158 			// no there is not ... so prefix it!
159 			return getMapName() + "." + objectId;
160 		}
161 	}
162 	
163 	/**
164 	 * It returns 'objectId' prefixed with "{@link Game#getMapName()}.".
165 	 * <p><p>
166      * Note that you may pass prefixed objectId into this method, it will detect it that and auto-correct upper/lower case of this existing prefix if needed. 
167      * Also you may use it as a validation feature because
168      * this method will raise an exception if the prefix does not match the current map name.
169 	 * 
170 	 * @param objectId will be auto-prefixed (if enabled, which is default)
171 	 * @return objectId prefixed and converted to {@link UnrealId}
172 	 */
173 	public UnrealId getPrefixedId(String objectId) {
174 		// auto prefixing is enabled
175 		if (getMapName() == null) {
176 			throw new PogamutException("GameInfo was not received yet, can't prefix '" + objectId + "'.", this);
177 		}
178 		if (objectId.toLowerCase().startsWith(mapNameLowerChar + ".")) {
179 			// already prefixed!
180 			if (!objectId.startsWith(getMapName())) {
181 				// but wrong upper/lower case detected, replace!
182 				objectId = getMapName() + objectId.substring(mapNameLowerChar.length());
183 			}
184 			// correctly prefixed, just return it
185 			return UnrealId.get(objectId);
186 		} else {
187 			// not correctly prefixed, check whether there is any prefix at all?
188 			if (objectId.contains(".")) {
189 				// yes there is -> map name validation fails!
190 				throw new PogamutException("id '" + objectId + "' is already prefixed with '" + objectId.substring(0, objectId.indexOf(".")) + "' which is different from current map name '" + getMapName() + "', map name validation fails!", this);
191 			}
192 			// no there is not ... so prefix it!
193 			return UnrealId.get(getMapName() + "." + objectId);
194 		}
195 	}
196 
197 	/*========================================================================*/
198 
199 	/**
200 	 * Retreives current game time, since the game started.
201 	 *
202 	 * @return Current game timestamp. IN SECONDS!
203 	 */
204 	public double getTime()
205 	{
206 		// retreive from last BeginMessage object
207         if (lastBeginMessage == null) return 0;
208 		return lastBeginMessage.getTime();
209 	}
210 	
211 	/**
212 	 * Retrieves time-delta since the last batch update.
213 	 * 
214 	 * @return time-delta IN SECONDS
215 	 */
216 	public double getTimeDelta() {
217 		return timeDelta;
218 	}
219 	
220 	/**
221 	 * Retreives time limit for the game.
222 	 *
223 	 * <p>Note: Then the time limit is reached and the game is tie, special
224 	 * game modes might be turned on, e.g. <i>sudden death overtime</i>.
225 	 * Depends on the game type and game settings.
226 	 *
227 	 * @return Time limit of the game.
228 	 *
229 	 * @see getRemainingTime()
230 	 */
231 	public Double getTimeLimit()
232 	{
233 		// retreive from GameInfo object
234                 if (lastGameInfo == null) return null;
235 		return lastGameInfo.getTimeLimit();
236 	}
237 
238 	/**
239 	 * Retreives time remaining for the game.
240 	 *
241 	 * <p>Note: Then the time limit is reached and the game is tie, special
242 	 * game modes might be turned on, e.g. <i>sudden death overtime</i>.
243 	 * Depends on the game type and game settings.
244 	 *
245 	 * @return Time limit of the game.
246 	 *
247 	 * @see getTime()
248 	 * @see getTimeLimit()
249 	 *
250 	 * @todo Test, whether it is correct..
251 	 */
252 	public Double getRemainingTime()
253 	{
254 		// derive from the time limit and current time
255                 if (getTimeLimit() == null) return null;
256 		return getTimeLimit() - getTime();
257 	}
258 
259  	/*========================================================================*/
260 
261 	/**
262 	 * <i>BotDeathMatch only:</i><p>
263 	 * Number of points (e.g. kills) needed to win the game.
264 	 *
265 	 * @return Frag limit of the game.
266 	 *
267 	 * @see getTeamScoreLimit()
268 	 */
269 	public Integer getFragLimit()
270 	{
271 		// retreive from GameInfo object
272         if (lastGameInfo == null) return null;
273 		return lastGameInfo.getFragLimit();
274 	}
275 
276 	/**
277 	 * <i>BotTeamGame, BotCTFGame, BotBombingRun, BotDoubleDomination only:</i><p>
278 	 * Number of points a team needs to win the game.
279 	 *
280 	 * @return Team score limit of the game.
281 	 *
282 	 * @see getFragLimit()
283 	 */
284 	public Integer getTeamScoreLimit()
285 	{
286 		// retreive from GameInfo object
287 		// we have to cast double to int because UT2004 exports it as double (duuno why)
288         if (lastGameInfo == null) return null;
289 		return (int)lastGameInfo.getGoalTeamScore();
290 	}
291 
292 	/*========================================================================*/
293 
294 	/**
295 	 * Retrieves FlagInfo object representing the Flag for input team in BotCTFGame.
296          * If current game is not BotCTFGame, returns null!
297 	 *
298 	 * @param team to get the flag for.
299 	 * @return FlagInfo object representing the team flag.
300 	 */
301 	public FlagInfo getCTFFlag(int team) {
302                 Collection<FlagInfo> flags = worldView.getAll(FlagInfo.class).values();
303                 for (FlagInfo flag : flags) {
304                     if (flag.getTeam() == team) {
305                         return flag;
306                     }
307                 }
308                 return null;
309 	}
310 
311 	/**
312 	 * Retrieves all FlagInfo objects in the game (by default two) in BotCTFGame.
313          * If current game is not BotCTFGame, returns empty collection!
314 	 *
315 	 * @return Collection of FlagInfo objects representing the flags in the game.
316 	 */
317 	public Collection<FlagInfo> getCTFFlags() {
318 		return worldView.getAll(FlagInfo.class).values();
319 	}
320 
321 	/**
322 	 * Retrieves all domination points in the game (by default two) in BotDoubleDomination.
323          * Domination points are normal NavPoints, but with flag isDomPoint set to true.
324          * Moreover, Domination points have attribute DomPointController exported, holding
325          * the team that controls the point.
326 	 *
327 	 * @return Collection of NavPoint objects representing the domination points in the game.
328 	 */
329 	public Collection<NavPoint> getDominationPoints() {
330                 Collection<NavPoint> domPoints = new ArrayList();
331                 Collection<NavPoint> navPoints = worldView.getAll(NavPoint.class).values();
332                 for (NavPoint nav : navPoints) {
333                     if (nav.isDomPoint()) {
334                         domPoints.add(nav);
335                     }
336                 }
337 		return domPoints;
338 	}
339 
340 	/*========================================================================*/
341 
342 	/**
343 	 * Returns unmodifiable map with team scores.
344 	 * <p><p>
345 	 * Map is unsynchronized! If you want to iterate it over, use synchronized statement over the map.
346 	 * 
347 	 * @return all known team scores
348 	 */
349 	public Map<Integer, TeamScore> getTeamScores() {
350 		return Collections.unmodifiableMap(lastTeamScore);
351 	}
352 	
353 	/**
354 	 * Retrieves teams team score.
355 	 * <p><p>
356 	 * Team score is usually rising by achieving team goals, e.g. killing opponents, capturing flags, controlling domination points, etc. Note: Team score might
357 	 * decrease, when opposing teams score points themselves, based on map, game type and game settings.
358 	 * <p><p>
359 	 * Note that if {@link Integer#MIN_VALUE} is returned, it means that the score is unknown.
360 	 * 
361 	 * @param team to get the team score for. 
362 	 * @return teams team score.
363 	 */
364 	public int getTeamScore(int team) {
365 		TeamScore teamScore = lastTeamScore.get(team);
366 		if (teamScore == null) {
367 			return Integer.MIN_VALUE;
368 		}
369 		// Retrieve from TeamScore object
370 		return teamScore.getScore();
371 	}
372 	
373 	/**
374 	 * Tells whether the team score for 'team' is known.
375 	 * 
376 	 * @param team
377 	 * @return
378 	 */
379 	public boolean isTeamScoreKnown(int team) {
380 		return lastTeamScore.containsKey(team);
381 	}
382 	
383 	/**
384 	 * Returns unmodifiable map with player scores. (Note that from {@link PlayerScore} message you can get also {@link PlayerScore#getDeaths()}.)
385 	 * <p><p>
386 	 * Map is unsynchronized! If you want to iterate it over, use synchronized statement over the map.
387 	 * 
388 	 * @return all known player scores
389 	 */
390 	public Map<UnrealId, PlayerScore> getPlayerScores() {
391 		return Collections.unmodifiableMap(lastPlayerScore);
392 	}
393 	
394 	/**
395 	 * Retreives agent score.
396 	 * <p><p>
397 	 * Agent score is usually rising by achieving some goals, e.g. killing opponents, capturing flags, controlling domination points, etc. Note: Agent score
398 	 * might decrease upon suicides, based on map, game type and game settings.
399 	 * <p><p>
400 	 * Note that if {@link Integer#MIN_VALUE} is returned, than the score is unknown.
401 	 * 
402 	 * @param id id of the player
403 	 * @return Current agent score.
404 	 */
405 	public int getPlayerScore(UnrealId id) {
406 		PlayerScore score = lastPlayerScore.get(id);
407 		if (score != null) {
408 			return score.getScore();
409 		}
410 		return Integer.MIN_VALUE;
411 	}
412 	
413 	/**
414 	 * Tells whether the player score for 'player' is known.
415 	 * 
416 	 * @param player
417 	 * @return
418 	 */
419 	public boolean isPlayerScoreKnown(UnrealId player) {
420 		return lastPlayerScore.containsKey(player);
421 	}
422 
423 	/**
424 	 * Retreives number of deaths the agent took.
425 	 * 
426 	 * <p><p>
427 	 * A death is counted, whenever the agent dies.
428 	 * 
429 	 * <p><p>
430 	 * Note that if {@link Integer#MIN_VALUE} is returned, than the number of deaths is unknown.
431 	 * 
432 	 * @return Number of deaths the agent took.
433 	 */
434 	public int getPlayerDeaths(UnrealId id) {
435 		// retreive from PlayerScore object
436 		PlayerScore score = lastPlayerScore.get(id);
437 		if (score == null) {
438 			return Integer.MIN_VALUE;
439 		}
440 		return score.getDeaths();
441 	}
442 	
443 	/**
444 	 * Tells whether the number of deaths for 'player' is known.
445 	 * 
446 	 * @param player
447 	 * @return
448 	 */
449 	public boolean isPlayerDeathsKnown(UnrealId player) {
450 		return lastPlayerScore.containsKey(player);
451 	}
452 	
453 	/*========================================================================*/
454 	
455 	/**
456 	 * <i>BotTeamGame, BotCTFGame, BotDoubleDomination only:</i><p>
457 	 * Retrieves number of teams in the game.
458 	 *
459 	 * <p> Team numbers start from 0.  Usually, there are just two teams: 0 and 1.
460 	 *
461 	 * @return Number of teams in the game.
462 	 */
463 	public Integer getMaxTeams()
464 	{
465 		// retreive from GameInfo object
466                 if (lastGameInfo == null) return null;
467 		return lastGameInfo.getMaxTeams();
468 	}
469 
470 	/**
471 	 * <i>BotTeamGame, BotCTFGame, BotDoubleDomination only:</i><p>
472 	 * Retreives maximum number of players per team.
473 	 *
474 	 * @return Maximum number of players per team.
475 	 */
476 	public Integer getMaxTeamSize()
477 	{
478 		// retreive from GameInfo object
479                 if (lastGameInfo == null) return null;
480 		return lastGameInfo.getMaxTeamSize();
481 	}
482 
483 	/*========================================================================*/
484 
485 	/**
486 	 * Retreives starting level of health. This is the level of health the
487 	 * players spawn with into the game.
488 	 *
489 	 * @return Starting level of health.
490 	 *
491 	 * @see getMaxHealth()
492 	 * @see getFullHealth()
493 	 */
494 	public Integer getStartHealth()
495 	{
496 		// retreive from InitedMessage object
497                 if (lastInitedMessage == null) return null;
498 		return lastInitedMessage.getHealthStart();
499 	}
500 
501 	/**
502 	 * Retreives maximum level of <i>non-boosted</i> health. This is the level
503 	 * achievable by foraging standard health kits.
504 	 *
505 	 * @return Maximum level of <i>non-boosted</i> health.
506 	 *
507 	 * @see getStartHealth()
508 	 * @see getMaxHealth()
509 	 */
510 	public Integer getFullHealth()
511 	{
512 		// retreive from InitedMessage object
513                 if (lastInitedMessage == null) return null;
514 		return lastInitedMessage.getHealthFull();
515 	}
516 
517 	/**
518 	 * Retreives maximum level of <i>boosted</i> health. This is the total
519 	 * maximum health achievable by any means of health kits, super health,
520 	 * or health vials.
521 	 *
522 	 * @return Maximum level of <i>boosted</i> health.
523 	 *
524 	 * @see getStartHealth()
525 	 * @see getFullHealth()
526 	 */
527 	public Integer getMaxHealth()
528 	{
529 		// retreive from InitedMessage object
530                 if (lastInitedMessage == null) return null;
531 		return lastInitedMessage.getHealthMax();
532 	}
533 
534 	/*========================================================================*/
535 
536 	/**
537 	 * Retreives maximum level of combined armor. The armor consist of two
538 	 * parts, which are summed together into combined armor value. However,
539 	 * each part is powered-up by different item (either by <i>small shield</i>
540 	 * or by <i>super-shield</i>).
541 	 *
542 	 * @return Maximum level of combined armor.
543 	 *
544 	 * @see getMaxLowArmor()
545 	 * @see getMaxHighArmor()
546 	 */
547 	public Integer getMaxArmor()
548 	{
549 		// retreive from InitedMessage object
550                 if (lastInitedMessage == null) return null;
551 		return lastInitedMessage.getShieldStrengthMax();
552 	}
553 
554 	/**
555 	 * Retreives maximum level of low armor. The armor consist of two
556 	 * parts, which are summed together into combined armor value. However,
557 	 * each part is powered-up by different item (either by <i>small shield</i>
558 	 * or by <i>super-shield</i>).
559 	 *
560 	 * <p>Low armor is powered-up by <i>small shield</i>.
561 	 *
562 	 * @return Maximum level of low armor.
563 	 *
564 	 * @see getMaxArmor()
565 	 * @see getMaxHighArmor()
566 	 */
567 	public int getMaxLowArmor()
568 	{
569 		// FIXME[js]: Where do we retreive the max low-armor info?
570 		return 50;
571 	}
572 
573 	/**
574 	 * Retreives maximum level of high armor. The armor consist of two
575 	 * parts, which are summed together into combined armor value. However,
576 	 * each part is powered-up by different item (either by <i>small shield</i>
577 	 * or by <i>super-shield</i>).
578 	 *
579 	 * <p>High armor is powered-up by <i>super-shield</i>.
580 	 *
581 	 * @return Maximum level of high armor.
582 	 *
583 	 * @see getMaxArmor()
584 	 * @see getMaxLowArmor()
585 	 */
586 	public int getMaxHighArmor()
587 	{
588 		// FIXME[js]: Where do we retreive the max high-armor info?
589 		return 100;
590 	}
591 
592 	/*========================================================================*/
593 
594 	/**
595 	 * Retreives starting level of adrenaline. This is the level of adrenaline
596 	 * the players spawn with into the game.
597 	 *
598 	 * @return Starting level of adrenaline.
599 	 */
600 	public Integer getStartAdrenaline()
601 	{
602 		// retreive from InitedMessage object
603 		// ut2004 exports it as double, must cast to int, ut's weirdness
604                 if (lastInitedMessage == null) return null;
605 		return (int)lastInitedMessage.getAdrenalineStart();
606 	}
607 
608 	/**
609 	 * Retreives target level of adrenaline that need to be gained to start
610 	 * special bonus actions.
611 	 *
612 	 * <p>Once the agent's adrenaline reaches this designated level, it can be
613 	 * used to start special bonus booster-actions like <i>invisibility</i>,
614 	 * <i>speed</i>, <i>booster</i>, etc. The adrenaline is then spent on the
615 	 * invoked action.
616 	 *
617 	 * @return Maximum level of adrenaline that can be gained.
618 	 */
619 	public Integer getTargetAdrenaline()
620 	{
621 		// retreive from InitedMessage object
622 		// ut2004 exports it as double, must cast to int, ut's weirdness
623                 if (lastInitedMessage == null) return null;
624 		return (int)lastInitedMessage.getAdrenalineMax();
625 	}
626 
627 	/**
628 	 * Retreives maximum level of adrenaline that can be gained.
629 	 *
630 	 * @return Maximum level of adrenaline that can be gained.
631 	 */
632 	public Integer getMaxAdrenaline()
633 	{
634 		// retreive from InitedMessage object
635 		// FIXME[js]: Return type!
636                 if (lastInitedMessage == null) return null;
637 		return (int)lastInitedMessage.getAdrenalineMax();
638 	}
639 
640 	/*========================================================================*/
641 
642 	/**
643 	 * Tells, whether the weapons stay on pick-up points, even when they are
644 	 * picked-up by players.
645 	 *
646 	 * <p>If so, each weapon type can be picked up from pick-up points only
647 	 * once. If the player already has the weapon the pick-up point offers, he
648 	 * can not pick it up. Also, each weapon pick-up point always contains its
649 	 * associated weapon.
650 	 *
651 	 * <p>If not, weapons can be picked up from pick-up points repeatedly.
652 	 * If the player already has the weapon the pick-up point offers, the
653 	 * pick-up will simply replenish ammo for that weapon. Also, upon each
654 	 * pick-up by a player, the offered weapon disappears (it is "taken" by
655 	 * that player). Weapons respawn on empty pick-up points after a while.
656 	 *
657 	 * @return True, if weapons stay on pick-up points.
658 	 */
659 	public Boolean getWeaponsStay()
660 	{
661 		// retreive from GameInfo object
662                 if (lastGameInfo == null) return null;
663 		return lastGameInfo.isWeaponStay();
664 	}
665 
666 	/*========================================================================*/
667 
668 	/**
669 	 * Retreives the maximum number of multi-jumping combos.
670 	 *
671 	 * <p>Note: Multi-jump combos are currently limited to double-jumps for
672 	 * bots.
673 	 *
674 	 * @return Maximum number of multi-jumping combos.
675 	 */
676 	public Integer getMaxMultiJump()
677 	{
678 		// retreive from InitedMessage object
679                 if (lastInitedMessage == null) return null;
680 		return lastInitedMessage.getMaxMultiJump();
681 	}
682 
683 	/*========================================================================*/
684 
685 	/**
686 	 * Returns list of mutators that are active in the current game.
687 	 * 
688 	 * @return Current game's mutators
689 	 */
690 	public List<Mutator> getMutators()
691 	{
692                 if (lastMutatorListObtained == null) return null;
693 		return lastMutatorListObtained.getMutators();
694 	}
695 
696 	/*========================================================================*/
697 
698 	/**
699 	 * Tells, whether the game is paused or running. When the game is paused,
700 	 * nobody can move or do anything (usually except posting text messages).
701 	 *
702 	 * @return True, if the game is paused. False otherwise.
703 	 *
704 	 * @see areBotsPaused()
705 	 */
706 	public Boolean isPaused()
707 	{
708 		// retreive from GameInfo object
709                 if (lastGameInfo == null) return null;
710 		return lastGameInfo.isGamePaused();
711 	}
712 
713 	/**
714 	 * Tells, whether the bots are paused or running. When the bots are paused,
715 	 * but the game is not paused as well, human controlled players can move.
716 	 * The bots are standing still and can do nothing  (usually except posting
717 	 * text messages).
718 	 *
719 	 * @return True, if the bots are paused. False otherwise.
720 	 *
721 	 * @see isPaused()
722 	 */
723 	public Boolean isBotsPaused()
724 	{
725 		// retreive from GameInfo object
726                 if (lastGameInfo == null) return null;
727 		return lastGameInfo.isBotsPaused();
728 	}
729 	
730 	// ========================
731 	// CAPTURE THE FLAG METHODS
732 	// ========================
733 
734     /**
735      * Returns a map indexed by team numbers, holding all flags in the game.
736      * In non-Capture the Flag (CTF) gametypes the result map will be empty.
737      *
738      * @return Map containing all the flags in the game indexed by owner team number.
739      */
740 	public Map<Integer, FlagInfo> getAllCTFFlags()
741 	{
742 		return allCTFFlags;
743 	}
744 
745         /**
746          * Returns a collection of all the flags in the game.
747          * In non-Capture the Flag (CTF) gametypes the result collection will be empty.
748          *
749          * @return Collection containing all the flags in the game.
750          */
751 	public Collection<FlagInfo> getAllCTFFlagsCollection()
752 	{		
753 		return allCTFFlags.values();
754 	}
755 	
756 	/**
757 	 * Returns flag (if known) for the 'team'.
758 	 * @param team
759 	 * @return
760 	 */
761 	public FlagInfo getFlag(int team) {
762 		return allCTFFlags.get(team);
763 	}
764 	
765 	/**
766 	 * Returns {@link FlagInfo} that describes MY-team flag. Returns flag only if the info is known.
767 	 * @return
768 	 */
769 	public FlagInfo getMyFlag() {
770 		if (self == null) return null;
771 		return allCTFFlags.get(self.getTeam());
772 	}
773 	
774 	/**
775 	 * Returns {@link FlagInfo} that describes ENEMY-team flag. Returns flag only if the info is known.
776 	 * @return
777 	 */
778 	public FlagInfo getEnemyFlag() {
779 		if (self == null) return null;
780 		return getFlag(self.getTeam() == 0 ? 1 : 0);
781 	}
782 	
783 	/**
784 	 * Returns location of the base for 'red' (team == 0) or 'blue' (team == 1) team.
785 	 * @param team
786 	 * @return
787 	 */
788 	public Location getFlagBase(int team) {
789 		if (lastGameInfo == null) return null;
790 		if (team == 0) return lastGameInfo.getRedBaseLocation();
791 		else return lastGameInfo.getBlueBaseLocation();
792 	}
793 	
794 	/**
795 	 * Returns location of MY flag base (where I should carry enemy flag to).
796 	 * @return
797 	 */
798 	public Location getMyFlagBase() {
799 		if (self == null) return null;
800 		return getFlagBase(self.getTeam());
801 	}
802 	
803 	/**
804 	 * Returns location of ENEMY flag base (where the flag resides as default, until stolen).
805 	 * @return
806 	 */
807 	public Location getEnemyFlagBase() {
808 		if (self == null) return null;
809 		return getFlagBase(self.getTeam() == 0 ? 1 : 0);
810 	}
811 
812 	/*========================================================================*/
813 
814 	/** Most rescent message containing info about the game. */
815 	GameInfo lastGameInfo = null;
816 
817 	/** Most rescent message containing info about the game frame. */
818 	InitedMessage lastInitedMessage = null;
819 
820 	/** Most rescent message containing info about the game frame. */
821 	BeginMessage lastBeginMessage = null;
822 	
823 	/** Most recent info about game's mutators. */
824 	MutatorListObtained lastMutatorListObtained = null;
825 
826     /** All flags in the game - will be filled only in CTF games */
827     Map<Integer, FlagInfo> allCTFFlags = new HashMap();
828     
829     /** Most rescent message containing info about the player's score. */
830 	Map<UnrealId, PlayerScore> lastPlayerScore = null;
831 
832 	/** Most rescent message containing info about the player team's score. */
833 	Map<Integer, TeamScore> lastTeamScore = null;
834 	
835 	/** Information about self. */
836 	Self self;
837 	
838 	/*========================================================================*/
839 
840 	/**
841 	 * {@link Self} listener.
842 	 */
843 	private class SelfListener implements IWorldObjectEventListener<Self, WorldObjectUpdatedEvent<Self>>
844 	{
845 		private IWorldView worldView;
846 
847 		/**
848 		 * Constructor. Registers itself on the given WorldView object.
849 		 * @param worldView WorldView object to listent to.
850 		 */
851 		public SelfListener(IWorldView worldView)
852 		{
853 			this.worldView = worldView;
854 			worldView.addObjectListener(Self.class, WorldObjectUpdatedEvent.class, this);
855 		}
856 
857 		@Override
858 		public void notify(WorldObjectUpdatedEvent<Self> event) {
859 			self = event.getObject();			
860 		}
861 	}
862 
863 	/** {@link Self} listener */
864 	private SelfListener selfListener;
865 	
866 
867 	/*========================================================================*/
868 
869 	/**
870 	 * GameInfo listener.
871 	 */
872 	private class GameInfoListener implements IWorldObjectEventListener<GameInfo, IWorldObjectEvent<GameInfo>>
873 	{
874 		@Override
875 		public void notify(IWorldObjectEvent<GameInfo> event)
876 		{
877 			lastGameInfo = event.getObject();
878 			mapNameLowerChar = lastGameInfo.getLevel().toLowerCase();
879 		}
880 
881 		/**
882 		 * Constructor. Registers itself on the given WorldView object.
883 		 * @param worldView WorldView object to listent to.
884 		 */
885 		public GameInfoListener(IWorldView worldView)
886 		{
887 			worldView.addObjectListener(GameInfo.class, this);
888 		}
889 	}
890 
891 	/** GameInfo listener */
892 	GameInfoListener gameInfoListener;
893 
894 	String mapNameLowerChar = "";
895 	
896 	/*========================================================================*/
897 
898 	/**
899 	 * InitedMessage listener.
900 	 */
901 	private class InitedMessageListener implements IWorldObjectEventListener<InitedMessage, WorldObjectUpdatedEvent<InitedMessage>>
902 	{
903 		@Override
904 		public void notify(WorldObjectUpdatedEvent<InitedMessage> event)
905 		{
906 			lastInitedMessage = event.getObject();
907 		}
908 
909 		/**
910 		 * Constructor. Registers itself on the given WorldView object.
911 		 * @param worldView WorldView object to listent to.
912 		 */
913 		public InitedMessageListener(IWorldView worldView)
914 		{
915 			worldView.addObjectListener(InitedMessage.class, WorldObjectUpdatedEvent.class, this);
916 		}
917 	}
918 
919 	/** InitedMessage listener */
920 	InitedMessageListener initedMessageListener;
921 
922 	/*========================================================================*/
923 
924 	private double timeDelta = -1;
925 	
926 	/**
927 	 * BeginMessage listener.
928 	 */
929 	private class BeginMessageListener implements IWorldEventListener<BeginMessage>
930 	{
931 		@Override
932 		public void notify(BeginMessage event)
933 		{
934 			if (lastBeginMessage != null) {
935 				timeDelta = lastBeginMessage.getTime() - event.getTime();
936 			}
937 			lastBeginMessage = event;
938 		}
939 
940 		/**
941 		 * Constructor. Registers itself on the given WorldView object.
942 		 * @param worldView WorldView object to listent to.
943 		 */
944 		public BeginMessageListener(IWorldView worldView)
945 		{
946 			worldView.addEventListener(BeginMessage.class, this);
947 		}
948 	}
949 
950 	/** BeginMessage listener */
951 	BeginMessageListener beginMessageListener;
952 
953 	/*========================================================================*/
954 	
955 	/**
956 	 * MutatorListObtained listener.
957 	 */
958 	private class MutatorListObtainedListener implements IWorldEventListener<MutatorListObtained>
959 	{
960 		@Override
961 		public void notify(MutatorListObtained event)
962 		{
963 			lastMutatorListObtained = event;
964 		}
965 
966 		/**
967 		 * Constructor. Registers itself on the given WorldView object.
968 		 * @param worldView WorldView object to listent to.
969 		 */
970 		public MutatorListObtainedListener(IWorldView worldView)
971 		{
972 			worldView.addEventListener(MutatorListObtained.class, this);
973 		}
974 	}
975 
976 	/** MutatorListObtained listener */
977 	MutatorListObtainedListener mutatorListObtainedListener;
978 
979 	/*========================================================================*/
980 
981 	/**
982 	 * FlagInfo object listener.
983 	 */
984 	private class FlagInfoObjectListener implements IWorldObjectEventListener<FlagInfo,WorldObjectUpdatedEvent<FlagInfo>>
985 	{
986 		/**
987          * Save flag in our HashMap.
988          * 
989          * @param event
990          */
991 		public void notify(WorldObjectUpdatedEvent<FlagInfo> event)
992 		{
993 			allCTFFlags.put(event.getObject().getTeam(), event.getObject());
994 		}
995 
996 		/**
997 		 * Constructor. Registers itself on the given WorldView object.
998 		 * @param worldView WorldView object to listent to.
999 		 */
1000 		public FlagInfoObjectListener(IWorldView worldView)
1001 		{
1002 			worldView.addObjectListener(FlagInfo.class, WorldObjectUpdatedEvent.class, this);
1003 		}        
1004 	}
1005 
1006 	/** FlagInfo object listener */
1007 	FlagInfoObjectListener flagInfoObjectListener;
1008 
1009 	/*========================================================================*/
1010 	
1011 	/**
1012 	 * PlayerScore listener.
1013 	 */
1014 	private class PlayerScoreListener implements IWorldEventListener<PlayerScore>
1015 	{
1016 		@Override
1017 		public void notify(PlayerScore event)
1018 		{
1019 			synchronized(lastPlayerScore) {
1020 				lastPlayerScore.put(event.getId(), event);
1021 			}
1022 		}
1023 
1024 		/**
1025 		 * Constructor. Registers itself on the given WorldView object.
1026 		 * @param worldView WorldView object to listent to.
1027 		 */
1028 		public PlayerScoreListener(IWorldView worldView)
1029 		{
1030 			worldView.addEventListener(PlayerScore.class, this);
1031 		}
1032 	}
1033 
1034 	/** PlayerScore listener */
1035 	private PlayerScoreListener playerScoreListener;
1036 
1037 	/*========================================================================*/
1038 	
1039 	/**
1040 	 * TeamScore listener.
1041 	 */
1042 	private class TeamScoreListener implements IWorldObjectEventListener<TeamScore, WorldObjectUpdatedEvent<TeamScore>>
1043 	{
1044 		/**
1045 		 * Constructor. Registers itself on the given WorldView object.
1046 		 * @param worldView WorldView object to listent to.
1047 		 */
1048 		public TeamScoreListener(IWorldView worldView)
1049 		{
1050 			worldView.addObjectListener(TeamScore.class, WorldObjectUpdatedEvent.class, this);
1051 		}
1052 
1053 		@Override
1054 		public void notify(WorldObjectUpdatedEvent<TeamScore> event) {
1055 			synchronized(lastTeamScore) {
1056 				lastTeamScore.put(event.getObject().getTeam(), event.getObject());
1057 			}
1058 		}
1059 	}
1060 
1061 	/** TeamScore listener */
1062 	private TeamScoreListener teamScoreListener;
1063 	
1064 	/*========================================================================*/	
1065 
1066 	/**
1067 	 * Constructor. Setups the memory module based on bot's world view.
1068 	 * @param bot owner of the module that is using it
1069 	 */
1070 	public Game(UT2004Bot bot) {
1071 		this(bot, null);
1072 	}
1073 	
1074 	/**
1075 	 * Constructor. Setups the memory module based on bot's world view.
1076 	 * @param bot owner of the module that is using it
1077 	 * @param log Logger to be used for logging runtime/debug info. If <i>null</i>, the module creates its own logger.
1078 	 */
1079 	public Game(UT2004Bot bot, Logger log)
1080 	{
1081 		super(bot, log);
1082 
1083 		// create listeners
1084 		gameInfoListener            = new GameInfoListener(worldView);
1085 		beginMessageListener        = new BeginMessageListener(worldView);
1086 		initedMessageListener       = new InitedMessageListener(worldView);
1087 		mutatorListObtainedListener = new MutatorListObtainedListener(worldView);
1088         flagInfoObjectListener      = new FlagInfoObjectListener(worldView);
1089         playerScoreListener         = new PlayerScoreListener(worldView);
1090 		teamScoreListener           = new TeamScoreListener(worldView);
1091 		lastPlayerScore             = new HashMap<UnrealId, PlayerScore>();
1092 		lastTeamScore               = new HashMap<Integer, TeamScore>();
1093 		selfListener                = new SelfListener(worldView);
1094 		mapNameLowerChar = "";
1095         
1096         cleanUp();
1097 	}
1098 	
1099 	@Override
1100 	protected void cleanUp() {
1101 		super.cleanUp();
1102 		lastGameInfo = null;
1103 		lastInitedMessage = null;
1104 		lastBeginMessage = null;
1105 		lastMutatorListObtained = null;
1106 		synchronized(lastPlayerScore) {		
1107 			lastPlayerScore.clear();
1108 		}
1109 		synchronized(lastTeamScore) {
1110 			lastTeamScore.clear();
1111 		}
1112 		timeDelta = -1;
1113 	}
1114 	
1115 }