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 	 * @todo Test, whether it is correct..
205 	 */
206 	public double getTime()
207 	{
208 		// retreive from last BeginMessage object
209                 if (lastBeginMessage == null) return 0;
210 		return lastBeginMessage.getTime();
211 	}
212 
213 	/**
214 	 * Retreives time limit for the game.
215 	 *
216 	 * <p>Note: Then the time limit is reached and the game is tie, special
217 	 * game modes might be turned on, e.g. <i>sudden death overtime</i>.
218 	 * Depends on the game type and game settings.
219 	 *
220 	 * @return Time limit of the game.
221 	 *
222 	 * @see getRemainingTime()
223 	 */
224 	public Double getTimeLimit()
225 	{
226 		// retreive from GameInfo object
227                 if (lastGameInfo == null) return null;
228 		return lastGameInfo.getTimeLimit();
229 	}
230 
231 	/**
232 	 * Retreives time remaining for the game.
233 	 *
234 	 * <p>Note: Then the time limit is reached and the game is tie, special
235 	 * game modes might be turned on, e.g. <i>sudden death overtime</i>.
236 	 * Depends on the game type and game settings.
237 	 *
238 	 * @return Time limit of the game.
239 	 *
240 	 * @see getTime()
241 	 * @see getTimeLimit()
242 	 *
243 	 * @todo Test, whether it is correct..
244 	 */
245 	public Double getRemainingTime()
246 	{
247 		// derive from the time limit and current time
248                 if (getTimeLimit() == null) return null;
249 		return getTimeLimit() - getTime();
250 	}
251 
252  	/*========================================================================*/
253 
254 	/**
255 	 * <i>BotDeathMatch only:</i><p>
256 	 * Number of points (e.g. kills) needed to win the game.
257 	 *
258 	 * @return Frag limit of the game.
259 	 *
260 	 * @see getTeamScoreLimit()
261 	 */
262 	public Integer getFragLimit()
263 	{
264 		// retreive from GameInfo object
265         if (lastGameInfo == null) return null;
266 		return lastGameInfo.getFragLimit();
267 	}
268 
269 	/**
270 	 * <i>BotTeamGame, BotCTFGame, BotBombingRun, BotDoubleDomination only:</i><p>
271 	 * Number of points a team needs to win the game.
272 	 *
273 	 * @return Team score limit of the game.
274 	 *
275 	 * @see getFragLimit()
276 	 */
277 	public Integer getTeamScoreLimit()
278 	{
279 		// retreive from GameInfo object
280 		// we have to cast double to int because UT2004 exports it as double (duuno why)
281         if (lastGameInfo == null) return null;
282 		return (int)lastGameInfo.getGoalTeamScore();
283 	}
284 
285 	/*========================================================================*/
286 
287 	/**
288 	 * Retrieves FlagInfo object representing the Flag for input team in BotCTFGame.
289          * If current game is not BotCTFGame, returns null!
290 	 *
291 	 * @param team to get the flag for.
292 	 * @return FlagInfo object representing the team flag.
293 	 */
294 	public FlagInfo getCTFFlag(int team) {
295                 Collection<FlagInfo> flags = worldView.getAll(FlagInfo.class).values();
296                 for (FlagInfo flag : flags) {
297                     if (flag.getTeam() == team) {
298                         return flag;
299                     }
300                 }
301                 return null;
302 	}
303 
304 	/**
305 	 * Retrieves all FlagInfo objects in the game (by default two) in BotCTFGame.
306          * If current game is not BotCTFGame, returns empty collection!
307 	 *
308 	 * @return Collection of FlagInfo objects representing the flags in the game.
309 	 */
310 	public Collection<FlagInfo> getCTFFlags() {
311 		return worldView.getAll(FlagInfo.class).values();
312 	}
313 
314 	/**
315 	 * Retrieves all domination points in the game (by default two) in BotDoubleDomination.
316          * Domination points are normal NavPoints, but with flag isDomPoint set to true.
317          * Moreover, Domination points have attribute DomPointController exported, holding
318          * the team that controls the point.
319 	 *
320 	 * @return Collection of NavPoint objects representing the domination points in the game.
321 	 */
322 	public Collection<NavPoint> getDominationPoints() {
323                 Collection<NavPoint> domPoints = new ArrayList();
324                 Collection<NavPoint> navPoints = worldView.getAll(NavPoint.class).values();
325                 for (NavPoint nav : navPoints) {
326                     if (nav.isDomPoint()) {
327                         domPoints.add(nav);
328                     }
329                 }
330 		return domPoints;
331 	}
332 
333 	/*========================================================================*/
334 
335 	/**
336 	 * Returns unmodifiable map with team scores.
337 	 * <p><p>
338 	 * Map is unsynchronized! If you want to iterate it over, use synchronized statement over the map.
339 	 * 
340 	 * @return all known team scores
341 	 */
342 	public Map<Integer, TeamScore> getTeamScores() {
343 		return Collections.unmodifiableMap(lastTeamScore);
344 	}
345 	
346 	/**
347 	 * Retrieves teams team score.
348 	 * <p><p>
349 	 * Team score is usually rising by achieving team goals, e.g. killing opponents, capturing flags, controlling domination points, etc. Note: Team score might
350 	 * decrease, when opposing teams score points themselves, based on map, game type and game settings.
351 	 * <p><p>
352 	 * Note that if {@link Integer#MIN_VALUE} is returned, it means that the score is unknown.
353 	 * 
354 	 * @param team to get the team score for. 
355 	 * @return teams team score.
356 	 */
357 	public int getTeamScore(int team) {
358 		TeamScore teamScore = lastTeamScore.get(team);
359 		if (teamScore == null) {
360 			return Integer.MIN_VALUE;
361 		}
362 		// Retrieve from TeamScore object
363 		return teamScore.getScore();
364 	}
365 	
366 	/**
367 	 * Tells whether the team score for 'team' is known.
368 	 * 
369 	 * @param team
370 	 * @return
371 	 */
372 	public boolean isTeamScoreKnown(int team) {
373 		return lastTeamScore.containsKey(team);
374 	}
375 	
376 	/**
377 	 * Returns unmodifiable map with player scores. (Note that from {@link PlayerScore} message you can get also {@link PlayerScore#getDeaths()}.)
378 	 * <p><p>
379 	 * Map is unsynchronized! If you want to iterate it over, use synchronized statement over the map.
380 	 * 
381 	 * @return all known player scores
382 	 */
383 	public Map<UnrealId, PlayerScore> getPlayerScores() {
384 		return Collections.unmodifiableMap(lastPlayerScore);
385 	}
386 	
387 	/**
388 	 * Retreives agent score.
389 	 * <p><p>
390 	 * Agent score is usually rising by achieving some goals, e.g. killing opponents, capturing flags, controlling domination points, etc. Note: Agent score
391 	 * might decrease upon suicides, based on map, game type and game settings.
392 	 * <p><p>
393 	 * Note that if {@link Integer#MIN_VALUE} is returned, than the score is unknown.
394 	 * 
395 	 * @param id id of the player
396 	 * @return Current agent score.
397 	 */
398 	public int getPlayerScore(UnrealId id) {
399 		PlayerScore score = lastPlayerScore.get(id);
400 		if (score != null) {
401 			return score.getScore();
402 		}
403 		return Integer.MIN_VALUE;
404 	}
405 	
406 	/**
407 	 * Tells whether the player score for 'player' is known.
408 	 * 
409 	 * @param player
410 	 * @return
411 	 */
412 	public boolean isPlayerScoreKnown(UnrealId player) {
413 		return lastPlayerScore.containsKey(player);
414 	}
415 
416 	/**
417 	 * Retreives number of deaths the agent took.
418 	 * 
419 	 * <p><p>
420 	 * A death is counted, whenever the agent dies.
421 	 * 
422 	 * <p><p>
423 	 * Note that if {@link Integer#MIN_VALUE} is returned, than the number of deaths is unknown.
424 	 * 
425 	 * @return Number of deaths the agent took.
426 	 */
427 	public int getPlayerDeaths(UnrealId id) {
428 		// retreive from PlayerScore object
429 		PlayerScore score = lastPlayerScore.get(id);
430 		if (score == null) {
431 			return Integer.MIN_VALUE;
432 		}
433 		return score.getDeaths();
434 	}
435 	
436 	/**
437 	 * Tells whether the number of deaths for 'player' is known.
438 	 * 
439 	 * @param player
440 	 * @return
441 	 */
442 	public boolean isPlayerDeathsKnown(UnrealId player) {
443 		return lastPlayerScore.containsKey(player);
444 	}
445 	
446 	/*========================================================================*/
447 	
448 	/**
449 	 * <i>BotTeamGame, BotCTFGame, BotDoubleDomination only:</i><p>
450 	 * Retrieves number of teams in the game.
451 	 *
452 	 * <p> Team numbers start from 0.  Usually, there are just two teams: 0 and 1.
453 	 *
454 	 * @return Number of teams in the game.
455 	 */
456 	public Integer getMaxTeams()
457 	{
458 		// retreive from GameInfo object
459                 if (lastGameInfo == null) return null;
460 		return lastGameInfo.getMaxTeams();
461 	}
462 
463 	/**
464 	 * <i>BotTeamGame, BotCTFGame, BotDoubleDomination only:</i><p>
465 	 * Retreives maximum number of players per team.
466 	 *
467 	 * @return Maximum number of players per team.
468 	 */
469 	public Integer getMaxTeamSize()
470 	{
471 		// retreive from GameInfo object
472                 if (lastGameInfo == null) return null;
473 		return lastGameInfo.getMaxTeamSize();
474 	}
475 
476 	/*========================================================================*/
477 
478 	/**
479 	 * Retreives starting level of health. This is the level of health the
480 	 * players spawn with into the game.
481 	 *
482 	 * @return Starting level of health.
483 	 *
484 	 * @see getMaxHealth()
485 	 * @see getFullHealth()
486 	 */
487 	public Integer getStartHealth()
488 	{
489 		// retreive from InitedMessage object
490                 if (lastInitedMessage == null) return null;
491 		return lastInitedMessage.getHealthStart();
492 	}
493 
494 	/**
495 	 * Retreives maximum level of <i>non-boosted</i> health. This is the level
496 	 * achievable by foraging standard health kits.
497 	 *
498 	 * @return Maximum level of <i>non-boosted</i> health.
499 	 *
500 	 * @see getStartHealth()
501 	 * @see getMaxHealth()
502 	 */
503 	public Integer getFullHealth()
504 	{
505 		// retreive from InitedMessage object
506                 if (lastInitedMessage == null) return null;
507 		return lastInitedMessage.getHealthFull();
508 	}
509 
510 	/**
511 	 * Retreives maximum level of <i>boosted</i> health. This is the total
512 	 * maximum health achievable by any means of health kits, super health,
513 	 * or health vials.
514 	 *
515 	 * @return Maximum level of <i>boosted</i> health.
516 	 *
517 	 * @see getStartHealth()
518 	 * @see getFullHealth()
519 	 */
520 	public Integer getMaxHealth()
521 	{
522 		// retreive from InitedMessage object
523                 if (lastInitedMessage == null) return null;
524 		return lastInitedMessage.getHealthMax();
525 	}
526 
527 	/*========================================================================*/
528 
529 	/**
530 	 * Retreives maximum level of combined armor. The armor consist of two
531 	 * parts, which are summed together into combined armor value. However,
532 	 * each part is powered-up by different item (either by <i>small shield</i>
533 	 * or by <i>super-shield</i>).
534 	 *
535 	 * @return Maximum level of combined armor.
536 	 *
537 	 * @see getMaxLowArmor()
538 	 * @see getMaxHighArmor()
539 	 */
540 	public Integer getMaxArmor()
541 	{
542 		// retreive from InitedMessage object
543                 if (lastInitedMessage == null) return null;
544 		return lastInitedMessage.getShieldStrengthMax();
545 	}
546 
547 	/**
548 	 * Retreives maximum level of low armor. The armor consist of two
549 	 * parts, which are summed together into combined armor value. However,
550 	 * each part is powered-up by different item (either by <i>small shield</i>
551 	 * or by <i>super-shield</i>).
552 	 *
553 	 * <p>Low armor is powered-up by <i>small shield</i>.
554 	 *
555 	 * @return Maximum level of low armor.
556 	 *
557 	 * @see getMaxArmor()
558 	 * @see getMaxHighArmor()
559 	 */
560 	public int getMaxLowArmor()
561 	{
562 		// FIXME[js]: Where do we retreive the max low-armor info?
563 		return 50;
564 	}
565 
566 	/**
567 	 * Retreives maximum level of high armor. The armor consist of two
568 	 * parts, which are summed together into combined armor value. However,
569 	 * each part is powered-up by different item (either by <i>small shield</i>
570 	 * or by <i>super-shield</i>).
571 	 *
572 	 * <p>High armor is powered-up by <i>super-shield</i>.
573 	 *
574 	 * @return Maximum level of high armor.
575 	 *
576 	 * @see getMaxArmor()
577 	 * @see getMaxLowArmor()
578 	 */
579 	public int getMaxHighArmor()
580 	{
581 		// FIXME[js]: Where do we retreive the max high-armor info?
582 		return 100;
583 	}
584 
585 	/*========================================================================*/
586 
587 	/**
588 	 * Retreives starting level of adrenaline. This is the level of adrenaline
589 	 * the players spawn with into the game.
590 	 *
591 	 * @return Starting level of adrenaline.
592 	 */
593 	public Integer getStartAdrenaline()
594 	{
595 		// retreive from InitedMessage object
596 		// ut2004 exports it as double, must cast to int, ut's weirdness
597                 if (lastInitedMessage == null) return null;
598 		return (int)lastInitedMessage.getAdrenalineStart();
599 	}
600 
601 	/**
602 	 * Retreives target level of adrenaline that need to be gained to start
603 	 * special bonus actions.
604 	 *
605 	 * <p>Once the agent's adrenaline reaches this designated level, it can be
606 	 * used to start special bonus booster-actions like <i>invisibility</i>,
607 	 * <i>speed</i>, <i>booster</i>, etc. The adrenaline is then spent on the
608 	 * invoked action.
609 	 *
610 	 * @return Maximum level of adrenaline that can be gained.
611 	 */
612 	public Integer getTargetAdrenaline()
613 	{
614 		// retreive from InitedMessage object
615 		// ut2004 exports it as double, must cast to int, ut's weirdness
616                 if (lastInitedMessage == null) return null;
617 		return (int)lastInitedMessage.getAdrenalineMax();
618 	}
619 
620 	/**
621 	 * Retreives maximum level of adrenaline that can be gained.
622 	 *
623 	 * @return Maximum level of adrenaline that can be gained.
624 	 */
625 	public Integer getMaxAdrenaline()
626 	{
627 		// retreive from InitedMessage object
628 		// FIXME[js]: Return type!
629                 if (lastInitedMessage == null) return null;
630 		return (int)lastInitedMessage.getAdrenalineMax();
631 	}
632 
633 	/*========================================================================*/
634 
635 	/**
636 	 * Tells, whether the weapons stay on pick-up points, even when they are
637 	 * picked-up by players.
638 	 *
639 	 * <p>If so, each weapon type can be picked up from pick-up points only
640 	 * once. If the player already has the weapon the pick-up point offers, he
641 	 * can not pick it up. Also, each weapon pick-up point always contains its
642 	 * associated weapon.
643 	 *
644 	 * <p>If not, weapons can be picked up from pick-up points repeatedly.
645 	 * If the player already has the weapon the pick-up point offers, the
646 	 * pick-up will simply replenish ammo for that weapon. Also, upon each
647 	 * pick-up by a player, the offered weapon disappears (it is "taken" by
648 	 * that player). Weapons respawn on empty pick-up points after a while.
649 	 *
650 	 * @return True, if weapons stay on pick-up points.
651 	 */
652 	public Boolean getWeaponsStay()
653 	{
654 		// retreive from GameInfo object
655                 if (lastGameInfo == null) return null;
656 		return lastGameInfo.isWeaponStay();
657 	}
658 
659 	/*========================================================================*/
660 
661 	/**
662 	 * Retreives the maximum number of multi-jumping combos.
663 	 *
664 	 * <p>Note: Multi-jump combos are currently limited to double-jumps for
665 	 * bots.
666 	 *
667 	 * @return Maximum number of multi-jumping combos.
668 	 */
669 	public Integer getMaxMultiJump()
670 	{
671 		// retreive from InitedMessage object
672                 if (lastInitedMessage == null) return null;
673 		return lastInitedMessage.getMaxMultiJump();
674 	}
675 
676 	/*========================================================================*/
677 
678 	/**
679 	 * Returns list of mutators that are active in the current game.
680 	 * 
681 	 * @return Current game's mutators
682 	 */
683 	public List<Mutator> getMutators()
684 	{
685                 if (lastMutatorListObtained == null) return null;
686 		return lastMutatorListObtained.getMutators();
687 	}
688 
689 	/*========================================================================*/
690 
691 	/**
692 	 * Tells, whether the game is paused or running. When the game is paused,
693 	 * nobody can move or do anything (usually except posting text messages).
694 	 *
695 	 * @return True, if the game is paused. False otherwise.
696 	 *
697 	 * @see areBotsPaused()
698 	 */
699 	public Boolean isPaused()
700 	{
701 		// retreive from GameInfo object
702                 if (lastGameInfo == null) return null;
703 		return lastGameInfo.isGamePaused();
704 	}
705 
706 	/**
707 	 * Tells, whether the bots are paused or running. When the bots are paused,
708 	 * but the game is not paused as well, human controlled players can move.
709 	 * The bots are standing still and can do nothing  (usually except posting
710 	 * text messages).
711 	 *
712 	 * @return True, if the bots are paused. False otherwise.
713 	 *
714 	 * @see isPaused()
715 	 */
716 	public Boolean isBotsPaused()
717 	{
718 		// retreive from GameInfo object
719                 if (lastGameInfo == null) return null;
720 		return lastGameInfo.isBotsPaused();
721 	}
722 	
723 	// ========================
724 	// CAPTURE THE FLAG METHODS
725 	// ========================
726 
727     /**
728      * Returns a map indexed by team numbers, holding all flags in the game.
729      * In non-Capture the Flag (CTF) gametypes the result map will be empty.
730      *
731      * @return Map containing all the flags in the game indexed by owner team number.
732      */
733 	public Map<Integer, FlagInfo> getAllCTFFlags()
734 	{
735 		return allCTFFlags;
736 	}
737 
738         /**
739          * Returns a collection of all the flags in the game.
740          * In non-Capture the Flag (CTF) gametypes the result collection will be empty.
741          *
742          * @return Collection containing all the flags in the game.
743          */
744 	public Collection<FlagInfo> getAllCTFFlagsCollection()
745 	{		
746 		return allCTFFlags.values();
747 	}
748 	
749 	/**
750 	 * Returns flag (if known) for the 'team'.
751 	 * @param team
752 	 * @return
753 	 */
754 	public FlagInfo getFlag(int team) {
755 		return allCTFFlags.get(team);
756 	}
757 	
758 	/**
759 	 * Returns {@link FlagInfo} that describes MY-team flag. Returns flag only if the info is known.
760 	 * @return
761 	 */
762 	public FlagInfo getMyFlag() {
763 		if (self == null) return null;
764 		return allCTFFlags.get(self.getTeam());
765 	}
766 	
767 	/**
768 	 * Returns {@link FlagInfo} that describes ENEMY-team flag. Returns flag only if the info is known.
769 	 * @return
770 	 */
771 	public FlagInfo getEnemyFlag() {
772 		if (self == null) return null;
773 		return getFlag(self.getTeam() == 0 ? 1 : 0);
774 	}
775 	
776 	/**
777 	 * Returns location of the base for 'red' (team == 0) or 'blue' (team == 1) team.
778 	 * @param team
779 	 * @return
780 	 */
781 	public Location getFlagBase(int team) {
782 		if (lastGameInfo == null) return null;
783 		if (team == 0) return lastGameInfo.getRedBaseLocation();
784 		else return lastGameInfo.getBlueBaseLocation();
785 	}
786 	
787 	/**
788 	 * Returns location of MY flag base (where I should carry enemy flag to).
789 	 * @return
790 	 */
791 	public Location getMyFlagBase() {
792 		if (self == null) return null;
793 		return getFlagBase(self.getTeam());
794 	}
795 	
796 	/**
797 	 * Returns location of ENEMY flag base (where the flag resides as default, until stolen).
798 	 * @return
799 	 */
800 	public Location getEnemyFlagBase() {
801 		if (self == null) return null;
802 		return getFlagBase(self.getTeam() == 0 ? 1 : 0);
803 	}
804 
805 	/*========================================================================*/
806 
807 	/** Most rescent message containing info about the game. */
808 	GameInfo lastGameInfo = null;
809 
810 	/** Most rescent message containing info about the game frame. */
811 	InitedMessage lastInitedMessage = null;
812 
813 	/** Most rescent message containing info about the game frame. */
814 	BeginMessage lastBeginMessage = null;
815 	
816 	/** Most recent info about game's mutators. */
817 	MutatorListObtained lastMutatorListObtained = null;
818 
819     /** All flags in the game - will be filled only in CTF games */
820     Map<Integer, FlagInfo> allCTFFlags = new HashMap();
821     
822     /** Most rescent message containing info about the player's score. */
823 	Map<UnrealId, PlayerScore> lastPlayerScore = null;
824 
825 	/** Most rescent message containing info about the player team's score. */
826 	Map<Integer, TeamScore> lastTeamScore = null;
827 	
828 	/** Information about self. */
829 	Self self;
830 	
831 	/*========================================================================*/
832 
833 	/**
834 	 * {@link Self} listener.
835 	 */
836 	private class SelfListener implements IWorldObjectEventListener<Self, WorldObjectUpdatedEvent<Self>>
837 	{
838 		private IWorldView worldView;
839 
840 		/**
841 		 * Constructor. Registers itself on the given WorldView object.
842 		 * @param worldView WorldView object to listent to.
843 		 */
844 		public SelfListener(IWorldView worldView)
845 		{
846 			this.worldView = worldView;
847 			worldView.addObjectListener(Self.class, WorldObjectUpdatedEvent.class, this);
848 		}
849 
850 		@Override
851 		public void notify(WorldObjectUpdatedEvent<Self> event) {
852 			self = event.getObject();			
853 		}
854 	}
855 
856 	/** {@link Self} listener */
857 	private SelfListener selfListener;
858 	
859 
860 	/*========================================================================*/
861 
862 	/**
863 	 * GameInfo listener.
864 	 */
865 	private class GameInfoListener implements IWorldObjectEventListener<GameInfo, IWorldObjectEvent<GameInfo>>
866 	{
867 		@Override
868 		public void notify(IWorldObjectEvent<GameInfo> event)
869 		{
870 			lastGameInfo = event.getObject();
871 			mapNameLowerChar = lastGameInfo.getLevel().toLowerCase();
872 		}
873 
874 		/**
875 		 * Constructor. Registers itself on the given WorldView object.
876 		 * @param worldView WorldView object to listent to.
877 		 */
878 		public GameInfoListener(IWorldView worldView)
879 		{
880 			worldView.addObjectListener(GameInfo.class, this);
881 		}
882 	}
883 
884 	/** GameInfo listener */
885 	GameInfoListener gameInfoListener;
886 
887 	String mapNameLowerChar = "";
888 	
889 	/*========================================================================*/
890 
891 	/**
892 	 * InitedMessage listener.
893 	 */
894 	private class InitedMessageListener implements IWorldObjectEventListener<InitedMessage, WorldObjectUpdatedEvent<InitedMessage>>
895 	{
896 		@Override
897 		public void notify(WorldObjectUpdatedEvent<InitedMessage> event)
898 		{
899 			lastInitedMessage = event.getObject();
900 		}
901 
902 		/**
903 		 * Constructor. Registers itself on the given WorldView object.
904 		 * @param worldView WorldView object to listent to.
905 		 */
906 		public InitedMessageListener(IWorldView worldView)
907 		{
908 			worldView.addObjectListener(InitedMessage.class, WorldObjectUpdatedEvent.class, this);
909 		}
910 	}
911 
912 	/** InitedMessage listener */
913 	InitedMessageListener initedMessageListener;
914 
915 	/*========================================================================*/
916 
917 	/**
918 	 * BeginMessage listener.
919 	 */
920 	private class BeginMessageListener implements IWorldEventListener<BeginMessage>
921 	{
922 		@Override
923 		public void notify(BeginMessage event)
924 		{
925 			lastBeginMessage = event;
926 		}
927 
928 		/**
929 		 * Constructor. Registers itself on the given WorldView object.
930 		 * @param worldView WorldView object to listent to.
931 		 */
932 		public BeginMessageListener(IWorldView worldView)
933 		{
934 			worldView.addEventListener(BeginMessage.class, this);
935 		}
936 	}
937 
938 	/** BeginMessage listener */
939 	BeginMessageListener beginMessageListener;
940 
941 	/*========================================================================*/
942 	
943 	/**
944 	 * MutatorListObtained listener.
945 	 */
946 	private class MutatorListObtainedListener implements IWorldEventListener<MutatorListObtained>
947 	{
948 		@Override
949 		public void notify(MutatorListObtained event)
950 		{
951 			lastMutatorListObtained = event;
952 		}
953 
954 		/**
955 		 * Constructor. Registers itself on the given WorldView object.
956 		 * @param worldView WorldView object to listent to.
957 		 */
958 		public MutatorListObtainedListener(IWorldView worldView)
959 		{
960 			worldView.addEventListener(MutatorListObtained.class, this);
961 		}
962 	}
963 
964 	/** MutatorListObtained listener */
965 	MutatorListObtainedListener mutatorListObtainedListener;
966 
967 	/*========================================================================*/
968 
969 	/**
970 	 * FlagInfo object listener.
971 	 */
972 	private class FlagInfoObjectListener implements IWorldObjectEventListener<FlagInfo,WorldObjectUpdatedEvent<FlagInfo>>
973 	{
974 		/**
975          * Save flag in our HashMap.
976          * 
977          * @param event
978          */
979 		public void notify(WorldObjectUpdatedEvent<FlagInfo> event)
980 		{
981 			allCTFFlags.put(event.getObject().getTeam(), event.getObject());
982 		}
983 
984 		/**
985 		 * Constructor. Registers itself on the given WorldView object.
986 		 * @param worldView WorldView object to listent to.
987 		 */
988 		public FlagInfoObjectListener(IWorldView worldView)
989 		{
990 			worldView.addObjectListener(FlagInfo.class, WorldObjectUpdatedEvent.class, this);
991 		}        
992 	}
993 
994 	/** FlagInfo object listener */
995 	FlagInfoObjectListener flagInfoObjectListener;
996 
997 	/*========================================================================*/
998 	
999 	/**
1000 	 * PlayerScore listener.
1001 	 */
1002 	private class PlayerScoreListener implements IWorldEventListener<PlayerScore>
1003 	{
1004 		@Override
1005 		public void notify(PlayerScore event)
1006 		{
1007 			synchronized(lastPlayerScore) {
1008 				lastPlayerScore.put(event.getId(), event);
1009 			}
1010 		}
1011 
1012 		/**
1013 		 * Constructor. Registers itself on the given WorldView object.
1014 		 * @param worldView WorldView object to listent to.
1015 		 */
1016 		public PlayerScoreListener(IWorldView worldView)
1017 		{
1018 			worldView.addEventListener(PlayerScore.class, this);
1019 		}
1020 	}
1021 
1022 	/** PlayerScore listener */
1023 	private PlayerScoreListener playerScoreListener;
1024 
1025 	/*========================================================================*/
1026 	
1027 	/**
1028 	 * TeamScore listener.
1029 	 */
1030 	private class TeamScoreListener implements IWorldObjectEventListener<TeamScore, WorldObjectUpdatedEvent<TeamScore>>
1031 	{
1032 		/**
1033 		 * Constructor. Registers itself on the given WorldView object.
1034 		 * @param worldView WorldView object to listent to.
1035 		 */
1036 		public TeamScoreListener(IWorldView worldView)
1037 		{
1038 			worldView.addObjectListener(TeamScore.class, WorldObjectUpdatedEvent.class, this);
1039 		}
1040 
1041 		@Override
1042 		public void notify(WorldObjectUpdatedEvent<TeamScore> event) {
1043 			synchronized(lastTeamScore) {
1044 				lastTeamScore.put(event.getObject().getTeam(), event.getObject());
1045 			}
1046 		}
1047 	}
1048 
1049 	/** TeamScore listener */
1050 	private TeamScoreListener teamScoreListener;
1051 	
1052 	/*========================================================================*/	
1053 
1054 	/**
1055 	 * Constructor. Setups the memory module based on bot's world view.
1056 	 * @param bot owner of the module that is using it
1057 	 */
1058 	public Game(UT2004Bot bot) {
1059 		this(bot, null);
1060 	}
1061 	
1062 	/**
1063 	 * Constructor. Setups the memory module based on bot's world view.
1064 	 * @param bot owner of the module that is using it
1065 	 * @param log Logger to be used for logging runtime/debug info. If <i>null</i>, the module creates its own logger.
1066 	 */
1067 	public Game(UT2004Bot bot, Logger log)
1068 	{
1069 		super(bot, log);
1070 
1071 		// create listeners
1072 		gameInfoListener            = new GameInfoListener(worldView);
1073 		beginMessageListener        = new BeginMessageListener(worldView);
1074 		initedMessageListener       = new InitedMessageListener(worldView);
1075 		mutatorListObtainedListener = new MutatorListObtainedListener(worldView);
1076         flagInfoObjectListener      = new FlagInfoObjectListener(worldView);
1077         playerScoreListener         = new PlayerScoreListener(worldView);
1078 		teamScoreListener           = new TeamScoreListener(worldView);
1079 		lastPlayerScore             = new HashMap<UnrealId, PlayerScore>();
1080 		lastTeamScore               = new HashMap<Integer, TeamScore>();
1081 		selfListener                = new SelfListener(worldView);
1082 		mapNameLowerChar = "";
1083         
1084         cleanUp();
1085 	}
1086 	
1087 	@Override
1088 	protected void cleanUp() {
1089 		super.cleanUp();
1090 		lastGameInfo = null;
1091 		lastInitedMessage = null;
1092 		lastBeginMessage = null;
1093 		lastMutatorListObtained = null;
1094 		synchronized(lastPlayerScore) {		
1095 			lastPlayerScore.clear();
1096 		}
1097 		synchronized(lastTeamScore) {
1098 			lastTeamScore.clear();
1099 		}
1100 	}
1101 	
1102 }