View Javadoc

1   package cz.cuni.amis.pogamut.ut2004.agent.module.sensor;
2   
3   import cz.cuni.amis.pogamut.base.communication.worldview.object.IWorldObjectEvent;
4   
5   import java.io.File;
6   import java.io.FileNotFoundException;
7   import java.util.Formatter;
8   import java.util.Map;
9   import java.util.logging.Logger;
10  
11  import cz.cuni.amis.pogamut.base.agent.IObservingAgent;
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.IWorldObjectEventListener;
16  import cz.cuni.amis.pogamut.base.communication.worldview.object.IWorldObjectListener;
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.analyzer.UT2004AnalyzerObserver;
21  import cz.cuni.amis.pogamut.ut2004.analyzer.stats.UT2004AnalyzerObsStats;
22  import cz.cuni.amis.pogamut.ut2004.bot.IUT2004BotController;
23  import cz.cuni.amis.pogamut.ut2004.bot.impl.UT2004Bot;
24  import cz.cuni.amis.pogamut.ut2004.communication.messages.ItemType;
25  import cz.cuni.amis.pogamut.ut2004.communication.messages.ItemType.Category;
26  import cz.cuni.amis.pogamut.ut2004.communication.messages.UT2004ItemType;
27  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbcommands.Shoot;
28  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.BeginMessage;
29  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.BotKilled;
30  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.ControlMessage;
31  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.EndMessage;
32  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.FlagInfo;
33  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.GameRestarted;
34  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.ItemPickedUp;
35  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.PlayerKilled;
36  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.PlayerScore;
37  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.Self;
38  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.TeamScore;
39  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.TeamScoreMessage;
40  import cz.cuni.amis.utils.FilePath;
41  import cz.cuni.amis.utils.NullCheck;
42  import cz.cuni.amis.utils.exception.PogamutException;
43  import cz.cuni.amis.utils.exception.PogamutIOException;
44  import cz.cuni.amis.utils.maps.LazyMap;
45  
46  /**
47   * Memory module specialized on collecting various statistics about the bot such as number of deaths, suicides,
48   * etc.
49   * <p><p>
50   * The module abides {@link GameRestarted} (clear stats upon the restart) and can be set to export collected 
51   * data into certain file. 
52   * 
53   * @author Jimmy
54   */
55  public class AgentStats extends SensorModule<IObservingAgent> {
56  
57  	protected boolean observer = false;
58  	
59  	/**
60  	 * Whether the module is being used inside observer. Default: FALSE.
61  	 * @return
62  	 */
63  	public boolean isObserver() {
64  		return observer;
65  	}
66  	
67  	/**
68  	 * Sets whether the module is being used inside observer. Default: FALSE.
69  	 * @param observer
70  	 */
71  	public void setObserver(boolean observer) {
72  		this.observer = observer;
73  	}
74  	
75  	/**
76  	 * Returns Id of the bot, sensitive to {@link AgentStats#isObserver()}.
77  	 * @return
78  	 */
79  	public UnrealId getBotId() {
80  		if (self == null) return null;		
81  		return self.getBotId();
82  	}
83  
84  	/**
85  	 * Contains statistics about bot that was KILLED BY OBSERVED BOT.
86  	 */
87  	protected Map<UnrealId, Integer> killed = new LazyMap<UnrealId, Integer>() {
88  
89  		@Override
90  		protected Integer create(UnrealId key) {
91  			return 0;
92  		}
93  		
94  	};
95  	
96  	/**
97  	 * Contains statistics about bots that KILLED OBSERVED BOT. Does not contain "suicidies", i.e., value for the key
98  	 * that is this bot ID.
99  	 */
100 	protected Map<UnrealId, Integer> killedBy = new LazyMap<UnrealId, Integer>() {
101 
102 		@Override
103 		protected Integer create(UnrealId key) {
104 			return 0;
105 		}
106 		
107 	};
108 	
109 	 /** 
110 	  * Most rescent message containing info about the player's score. 
111 	  **/
112 	protected Map<UnrealId, PlayerScore> playerScores = new LazyMap<UnrealId, PlayerScore>() {
113 
114 		@Override
115 		protected PlayerScore create(UnrealId key) {
116 			return new PlayerScore(currentUT2004Time, key, 0, 0);
117 		}
118 		
119 	};
120 	
121 	/** 
122 	 * Most rescent message containing info about the player team's score. 
123 	 **/
124 	protected Map<Integer, TeamScore> teamScores = new LazyMap<Integer, TeamScore>() {
125 
126 		@Override
127 		protected TeamScore create(Integer key) {
128 			return new TeamScoreMessage();
129 		}
130 		
131 	};
132 
133 	
134 	/**
135      * How many times the observed bot has died.
136      */
137     protected int deaths = 0;
138     
139     /**
140      * How many times this bot has comitted suicide.
141      */
142     protected int suicides = 0;
143         
144     /**
145      * How many times was this bot killed by other players.
146      */
147     protected int killedByOthers = 0;
148     
149     /**
150      * How many times this bot has killed other players.
151      */
152     protected int killedOthers = 0;
153     
154     /**
155      * Sum of the bot's travelled distance.
156      */
157     protected double travelledDistance = 0.0;
158     
159     /**
160      * Whether the bot is shooting.
161      */
162     protected boolean shooting = false;
163     
164     /**
165      * For how long in total the bot was shooting.
166      */
167     protected double timeShooting = 0;
168     
169     /**
170      * For how long in total the bot was moving (had non-zero (&gt; 1) velocity)
171      */
172     protected double timeMoving = 0;
173     
174     /**
175      * For how long the bot was shooting with a certain weapon, truly counting the number of seconds the {@link Shoot} command
176      * has been effective for the bot.
177      */
178     protected Map<ItemType, Double> weaponsUsedTime = new LazyMap<ItemType, Double>() {
179 
180 		@Override
181 		protected Double create(ItemType key) {
182 			return (double)0;
183 		}
184     	
185     };
186 
187 	/**
188 	 * How many items the bot has collected according to their item type.
189 	 */
190 	protected Map<ItemType, Integer> itemsCollected = new LazyMap<ItemType, Integer>() {
191 
192 		@Override
193 		protected Integer create(ItemType key) {
194 			return 0;
195 		}
196 		
197 	};
198 	
199 	/**
200 	 * How many items according to its {@link Category} the bot has collected.
201 	 */
202 	protected Map<ItemType.Category, Integer> itemsByCategoryCollected = new LazyMap<ItemType.Category, Integer>() {
203 
204 		@Override
205 		protected Integer create(ItemType.Category key) {
206 			return 0;
207 		}
208 		
209 	};
210 	
211 	/**
212 	 * Total number of items the bot has collected.
213 	 */
214 	protected int totalItemsCollected = 0;
215 	
216 	/**
217 	 * How many other players this bot has killed during its single life.
218 	 */
219 	protected int numberOfPlayersKilledWithoutDeath = 0;
220 	
221 	/**
222 	 * How many times the bot had double damage.
223 	 */
224 	protected int doubleDamageCount = 0;
225 	
226 	/**
227 	 * Total number of seconds the bot had double damage.
228 	 */
229 	protected double doubleDamageTime = 0;
230 	
231 	////
232 	//
233 	// LOGGING
234 	//
235 	////
236 
237 	/**
238 	 * Outputs stats headers into the 'output'.
239 	 * 
240 	 * @param output
241 	 */
242 	public void outputHeader(Formatter output) {
243 		if (output == null) return;
244 		synchronized(output) {
245 			output.format("MatchTime;UT2004Time;Health;Armor;Adrenaline;Score;Deaths;Suicides;Killed;WasKilled;NumKillsWithoutDeath;"
246 					//      1            1.1       2      3      4         5      6       7       8       9        10          
247 					     +"Team;TeamScore;"
248 					//      11     12
249 					     +"ItemsCollect;WeaponsCollect;AmmoCollect;HealthCollect;ArmorCollect;ShieldCollect;AdrenalineCollect;OtherCollect;"				     
250 	                //        13          14              15             16           17             18           19              20
251 					     +"TimeMoving;TimeShooting;DoubleDamageCount;DoubleDamageTime;TraveledDistance;"
252 					//         21           22               23              24              25     
253 					     +"Combo;HasDoubleDamage;IsShooting;Velocity;Location_x;Location_y;Location_z");
254 				    //      26         27          28          29         30         31      32
255 			// WEAPON USED
256 			for (ItemType weapon : ItemType.Category.WEAPON.getTypes()) {
257 				output.format(";" + fixSemicolon(weapon.getName()).replace(".", "_") + "_TimeUsed");
258 			}
259 			// EVENTS
260 			output.format(";Event;EventParams...\n");
261 			output.flush();
262 		}
263 	}       
264 	
265 	/**
266 	 * Outputs stats line into 'output' appending 'eventOutput' at the end (also separated by semicolons)
267 	 * 
268 	 * @param output
269 	 * @param eventOutput
270 	 */
271 	public void outputStatLine(Formatter output, double time, String... eventOutput) {
272 		if (output == null) return;
273 		synchronized(output) {
274 			output.format("%.3f;%.3f;%d;%d;%d;%d;%d;%d;%d;%d;%d;%d;%d;%d;%d;%d;%d;%d;%d;%d;%d;%.3f;%.3f;%d;%.3f;%.3f;%s;%d;%d;%.3f;%.3f;%.3f;%.3f", 
275 					      // 1  1.1   2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20  21   22  23  24  25   26 27 28  29   30   31   32  
276 					           time,                                      // 1
277 					           getCurrentUT2004Time(),                    // 1.1
278 					           (self == null ? 0 : self.getHealth()),     // 2
279 					           (self == null ? 0 : self.getArmor()),      // 3
280 					           (self == null ? 0 : self.getAdrenaline()), // 4
281 					           getScore(),           // 5
282 					           deaths,               // 6
283 					           suicides,             // 7
284 					           killedOthers,         // 8
285 					           killedByOthers,       // 9
286 					           numberOfPlayersKilledWithoutDeath,     // 10
287 					           (self == null ? 255 : self.getTeam()), // 11
288 					           getTeamScore(),       // 12
289 					           totalItemsCollected,  // 13
290 					           itemsByCategoryCollected.get(ItemType.Category.WEAPON),     // 14
291 					           itemsByCategoryCollected.get(ItemType.Category.AMMO),       // 15
292 					           itemsByCategoryCollected.get(ItemType.Category.HEALTH),     // 16
293 					           itemsByCategoryCollected.get(ItemType.Category.ARMOR),      // 17
294 					           itemsByCategoryCollected.get(ItemType.Category.SHIELD),     // 18
295 					           itemsByCategoryCollected.get(ItemType.Category.ADRENALINE), // 19
296 					           itemsByCategoryCollected.get(ItemType.Category.OTHER),      // 20
297 					           timeMoving,           // 21
298 					           timeShooting,         // 22
299 					           doubleDamageCount,    // 23
300 					           doubleDamageTime,     // 24
301 					           travelledDistance,    // 25
302 					           (self == null ? "" : fixSemicolon(self.getCombo())),    // 26
303 					           (self == null ? 0 : self.getUDamageTime() > 0 ? 1 : 0), // 27
304 					           (self == null ? 0 : (self.isShooting() || self.isAltFiring()) ? 1 : 0), // 28
305 					           (self == null ? (double)0 : self.getVelocity().size()), // 29
306 					           (self == null ? (double)0 : self.getLocation().x),      // 30
307 					           (self == null ? (double)0 : self.getLocation().y),      // 31
308 					           (self == null ? (double)0 : self.getLocation().z)       // 32
309 			);
310 			// WEAPON USED
311 			for (ItemType weapon : ItemType.Category.WEAPON.getTypes()) {
312 				output.format(";%.3f", weaponsUsedTime.get(weapon));
313 			}
314 			// EVENTS
315 			for (String event : eventOutput) {
316 				output.format(";%s", fixSemicolon(event));
317 			}
318 			output.format("\n");
319 	        output.flush();
320 		}
321 	}
322 	
323 	/**
324 	 * Outputs stats line with event IFF logging (i.e., {@link AgentStats#outputFile} is not null, was initialized by {@link AgentStats#startOutput(String)} or {@link AgentStats#startOutput(String, boolean)}).
325 	 * @param eventOutput
326 	 */
327 	protected void outputStatLine(double time, String... eventOutput) {
328 		if (!isLogging()) return;
329 		if (outputFile == null) return;
330 		outputStatLine(outputFile, time, eventOutput);		
331 	}
332 	
333 	protected String fixSemicolon(String text) {
334 		if (text == null) return "";
335 		return text.replace(";", "_");
336 	}
337 	
338 	////
339 	//
340 	// RESET STATS
341 	//
342 	////
343 	
344 	protected Object statsMutex = new Object();
345 	
346 	/**
347 	 * Reset all stats, DOES NOT RESET {@link AgentStats#getCurrentMatchTime()} (use {@link AgentStats#resetMatchTime()} for that purpose separately).
348 	 */
349 	public void resetStats() {
350 		synchronized (statsMutex) {
351 			self = null;
352 			deaths = 0;
353 			suicides = 0;
354 			killedOthers = 0;
355 			killedByOthers = 0;
356 			numberOfPlayersKilledWithoutDeath = 0;
357 			totalItemsCollected = 0;
358 			synchronized (itemsByCategoryCollected) {
359 				itemsByCategoryCollected.clear();
360 			}
361 			timeMoving = 0;
362 			timeShooting = 0;
363 			doubleDamageCount = 0;
364 			doubleDamageTime = 0;
365 			travelledDistance = 0;
366 			synchronized (itemsCollected) {
367 				itemsCollected.clear();
368 			}
369 			synchronized (weaponsUsedTime) {
370 				weaponsUsedTime.clear();
371 			}
372 			synchronized (playerScores) {
373 				playerScores.clear();
374 			}
375 			
376 			// UTILITY STUFF
377 			playerKilledInRow = 0;
378 			lastLocation = null;
379 		}
380 	}
381 	
382 	/**
383 	 * Resets match time to ZERO again (if is initialized, i.e., {@link AgentStats#getCurrentMatchTime()} > 0).
384 	 */
385 	public void resetMatchTime() {
386 		synchronized(statsMutex) {
387 			if (getCurrentMatchTime() > 0) {
388 				matchStartTime = getCurrentMatchTime();
389 			}
390 		}
391 	}
392 	
393 	////
394 	//
395 	// OUTPUT OF THE STATS
396 	//
397 	////
398 	
399 	/**
400 	 * Path to output as passed by the user.
401 	 */
402 	protected String pathToOutput = null;
403 	
404 	/**
405 	 * Concrete file we're currently using.
406 	 */
407 	protected File fileToOutput = null;
408 	
409 	/**
410 	 * Formatter that is used to output strings into the {@link UT2004AnalyzerObserver#observerFile}. 
411 	 */
412     protected Formatter outputFile = null;
413 	
414     /**
415      * Whether the object is currently logging (it may be false while the match is being restarted).
416      * @return
417      */
418 	public boolean isOutputting() {
419 		return isLogging() && outputFile != null;
420 	}
421 	
422 	/**
423 	 * Returns the output path as supplied in {@link AgentStats#startOutput(String)} or {@link AgentStats#startOutput(String, boolean)}.
424 	 * <p><p>
425 	 * For concretely used file for the output, use {@link AgentStats#getOutputFile()}. 
426 	 * @return
427 	 */
428 	public String getOutputPath() {
429 		return pathToOutput;
430 	}
431 	
432 	/**
433 	 * Actually used file for outputting of stats.
434 	 * @return
435 	 */
436 	public File getOutputFile() {
437 		return fileToOutput;
438 	}
439 
440 	/**
441 	 * Starts or redirect logging to 'pathToFile'. If it targets dir, throws an exception. Existing file will be overwritten.
442 	 * 
443 	 * @see setOutputPath(String, boolean)
444 	 * 
445 	 * @param pathToFile (target file format is .csv, should not need to end with any extension, will be supplied)
446 	 */
447 	public void startOutput(String pathToFile) {
448 		startOutput(pathToFile, false);
449 	}
450 
451 	/**
452 	 * Stops outputting of stats into file, nullify {@link AgentStats#outputFile} and {@link AgentStats#fileToOutput}. 
453 	 * <p><p>
454 	 * After the call:
455 	 * <ul>
456 	 * <li>{@link AgentStats#isOutputting()} will report false</li>
457 	 * <li>{@link AgentStats#getOutputFile()} will report null</li>
458 	 * <li>{@link AgentStats#getOutputPath()} will still be reported the last path passed into {@link AgentStats#startOutput(String)} or {@link AgentStats#startOutput(String, boolean)}</li>
459 	 * </ul>
460 	 */
461 	public void stopOutput() {
462 		if (outputFile == null) return;
463 		synchronized(outputFile) {
464 			try {
465 				outputFile.close();
466 			} catch (Exception e) {				
467 			}
468 			outputFile = null;
469 			fileToOutput = null;
470 		}
471 	}
472 	
473 	/**
474 	 * Returns {@link File} for a given 'pathToFile'. Target file is either non-existent or is file (otherwise throws an exception).
475 	 * <p><p>
476 	 * If 'seekAlternatives' is true, the method will try to find another file if 'pathToFile' already exists by appending "_000", "_001", ...
477 	 * If alternative filename is found, it is returned. Otherwise throws an exception.
478 	 * 
479 	 * @param pathToFile
480 	 * @param seekAlternatives
481 	 * @return
482 	 */
483 	protected File getOutputFile(String pathToFile, boolean seekAlternatives) {
484 		NullCheck.check(pathToFile, "pathToFile");
485 		
486 		if (!seekAlternatives) {
487 			File file = new File(pathToFile);
488 			if (!file.exists() || file.isFile()) {
489 				return file;
490 			}
491 			throw new PogamutException("Can't output stats into " + file.getAbsolutePath() + ", invalid location.", this);
492 		}
493 		
494 		String fragment;
495 		String rest;
496 		if (pathToFile.contains(".")) {
497 			fragment = pathToFile.substring(0, pathToFile.lastIndexOf("."));
498 			rest = pathToFile.substring(pathToFile.lastIndexOf("."));			
499 		} else {
500 			fragment = pathToFile;
501 			rest = ".csv";
502 		}
503 		for (int i = 0; i < 1000; ++i) {
504 			String num = String.valueOf(i);
505 			while (num.length() < 3) {
506 				num = "0" + num;
507 			}
508     		String fileName = fragment + "_" + num + rest;
509     		File file = new File(fileName);
510     		if (file.exists()) continue;
511     		return file;
512     	}
513     	throw new PogamutException("No suitable filename for stats to the: " + pathToFile + "...", this);
514 	}
515 	
516 	/**
517 	 * Starts or redirect logging to 'pathToFile'. If it targets dir, throws an exception. Existing file will be overwritten.
518 	 * <p><p>
519 	 * If 'seekAlternatives' is true, the method will try to find another file if 'pathToFile' already exists by appending "_000", "_001", ...
520 	 * If alternative filename is found, the log is redirected there. Otherwise throws an exception.
521 	 * 
522 	 * @param pathToFile (target file format is .csv, should not need to end with any extension, will be supplied)
523 	 * @param seekAlternatives whether to try other file (using suffixes to file name '_000', '_001', ..., '_999'
524 	 */
525 	public void startOutput(String pathToFile, boolean seekAlternatives) {
526 		stopOutput();		
527 		this.pathToOutput = pathToFile;
528 		fileToOutput = getOutputFile(pathToFile, seekAlternatives);
529 		FilePath.makeDirsToFile(fileToOutput);
530 		try {
531 			outputFile = new Formatter(fileToOutput);
532 		} catch (FileNotFoundException e) {
533 			throw new PogamutIOException("Could not start logging into '" + fileToOutput.getAbsolutePath() + "' due to: " + e.getMessage(), e, this);
534 		}		
535 		outputHeader(outputFile);		
536 	}
537 	
538 	////
539 	//
540 	// STATS GETTERS
541 	//
542 	////
543 	
544 	/**
545 	 * Returns map that counts how many time your bot kills other bot (according to opponent bot ID), i.e., under
546 	 * the key (other bot ID) is "how many time your bot has billed the other bot".
547 	 */
548 	public Map<UnrealId, Integer> getKilled() {
549 		return killed;
550 	}
551 
552 	/**
553 	 * Returns map with scores of respective players in the game.
554 	 * @return
555 	 */
556 	public Map<UnrealId, PlayerScore> getPlayerScores() {
557 		return playerScores;
558 	}
559 
560 	/**
561 	 * Returns map with scores of respective teams in the game.
562 	 * @return
563 	 */
564 	public Map<Integer, TeamScore> getTeamScores() {
565 		return teamScores;
566 	}
567 
568 	/**
569 	 * Current match time - note that this might different from the current GB2004 time because the match time
570 	 * is reset upon {@link GameRestarted}.
571 	 * <p><p>
572 	 * Returns negative number if it can't be evaluated, i.e., the match has not been started (and we're waiting for it,
573 	 * that happens only if you have used {@link AgentStats#setLogBeforeMatchRestart(boolean)}), or we do not have enough data.
574 	 * 
575 	 * @return
576 	 */
577 	public double getCurrentMatchTime() {
578 		if (isLogging() && currentUT2004Time > 0) {
579 			// assumes that UT2004 time is running in Secs (it should...)
580 			return currentUT2004Time - matchStartTime + ((System.currentTimeMillis() - currentSystemTime) / 1000);
581 		} else {
582 			return -1;
583 		}
584 	}
585 	
586 	/**
587 	 * Returns current UT2004 time (without any deltas, i.e., this is completely driven by the time from {@link BeginMessage}s).
588 	 * <p><p>
589 	 * Returns negative number if it can't be evaluated.
590 	 * 
591 	 * @return
592 	 */
593 	public double getCurrentUT2004Time() {
594 		return currentUT2004Time;
595 	}
596 
597 	/**
598 	 * How many other bots your bot has killed so far.
599 	 * @return
600 	 */
601 	public int getKilledOthers() {
602 		return killedOthers;
603 	}
604 
605 	/**
606 	 * Whether the bot is currently shooting
607 	 * @return
608 	 */
609 	public boolean isShooting() {
610 		return shooting;
611 	}
612 
613 	/**
614 	 * For how long your bot was shooting (in total). In seconds.
615 	 * @return
616 	 */
617 	public double getTimeShooting() {
618 		return timeShooting;
619 	}
620 
621 	/**
622 	 * For how long your bot was moving (in total). In seconds.
623 	 * @return
624 	 */
625 	public double getTimeMoving() {
626 		return timeMoving;
627 	}
628 
629 	/**
630 	 * For how long your bot was using a certain weapon (in total). In seconds.
631 	 * @return
632 	 */
633 	public Map<ItemType, Double> getWeaponsUsedTime() {
634 		return weaponsUsedTime;
635 	}
636 
637 	/**
638 	 * How many items (per {@link ItemType}) your bot has collected so far (in total).
639 	 * @return
640 	 */
641 	public Map<ItemType, Integer> getItemsCollected() {
642 		return itemsCollected;
643 	}
644 
645 	/**
646 	 * How many items (per respective {@link Category}) your bot has collected so far.
647 	 * @return
648 	 */
649 	public Map<ItemType.Category, Integer> getItemsByCategoryCollected() {
650 		return itemsByCategoryCollected;
651 	}
652 
653 	/**
654 	 * Total number of items the bot collected (including items received when the bot respawns).
655 	 * @return
656 	 */
657 	public int getTotalItemsCollected() {
658 		return totalItemsCollected;
659 	}
660 
661 	/**
662 	 * The biggest number of other bots killed in a row (without being killed).
663 	 * @return
664 	 */
665 	public int getNumberOfPlayersKilledWithoutDeath() {
666 		return numberOfPlayersKilledWithoutDeath;
667 	}
668 
669 	/**
670 	 * How many times your bot had double damage so far.
671 	 * @return
672 	 */
673 	public int getDoubleDamageCount() {
674 		return doubleDamageCount;
675 	}
676 
677 	/**
678 	 * For how long (in total) your bot had double damage. In seconds.
679 	 * @return
680 	 */
681 	public double getDoubleDamageTime() {
682 		return doubleDamageTime;
683 	}
684 
685 	/**
686 	 * Who has killed your bot the most? This map holds number according to killers' ides.
687 	 * @return
688 	 */
689 	public Map<UnrealId, Integer> getKilledBy() {
690 		return killedBy;
691 	}
692 
693 	/**
694 	 * How many times in total your bot has died (regardless the type of death).
695 	 * @return
696 	 */
697 	public int getDeaths() {
698 		return deaths;
699 	}
700 
701 	/**
702 	 * How big distance your bot has travelled so far. In UT-Units.
703 	 * @return
704 	 */
705 	public double getTravelledDistance() {
706 		return travelledDistance;
707 	}
708 
709 	/**
710 	 * How many times your bot has committed suicide.
711 	 * @return
712 	 */
713 	public int getSuicides() {
714 		return suicides;
715 	}
716 
717 	/**
718 	 * Current score of your bot.
719 	 * @return
720 	 */
721 	public int getScore() {
722 		return self == null ? 0 : playerScores.get(getBotId()).getScore();
723 	}
724 	
725 	/**
726 	 * Current score of the bot's team.
727 	 * @return
728 	 */
729 	public int getTeamScore() {
730 		return self == null ? 0 : teamScores.get(self.getTeam()).getScore();
731 	}
732 
733 	/**
734 	 * How many times your bot has been killed by other bots.
735 	 * @return
736 	 */
737 	public int getKilledByOthers() {
738 		return killedByOthers;
739 	}
740 	
741 	////
742 	//
743 	// UTILITY GETTERS
744 	//
745 	////
746 	
747 	/**
748 	 * Whether we have already received at least two {@link BeginMessage} in order to have all time-vars initialized
749 	 * so we may collect all stats.
750 	 */
751 	public boolean isTimeInitialized() {
752 		return previousUT2004Time > 0 && currentUT2004Time > 0;
753 	}
754 
755 	/**
756 	 * Returns the global UT2004 time that we're considering as a start-of-match.
757 	 * @return
758 	 */
759 	public double getMatchStartTime() {
760 		return matchStartTime;
761 	}
762 	
763 	//// 
764 	//
765 	// LISTENERS
766 	//
767 	////
768     
769 	/*========================================================================*/
770 
771 	/**
772 	 * BeginMessage listener.
773 	 */
774 	private class ControlMessageListener implements IWorldEventListener<ControlMessage>
775 	{
776 		@Override
777 		public void notify(ControlMessage event)
778 		{
779 			synchronized(statsMutex) {
780 				outputStatLine(getCurrentMatchTime(), event.getType(), String.valueOf(event.getPF1()), String.valueOf(event.getPF2()), String.valueOf(event.getPF3()), String.valueOf(event.getPI1()), String.valueOf(event.getPI2()), String.valueOf(event.getPI3()), String.valueOf(event.getPS1()), String.valueOf(event.getPS2()), String.valueOf(event.getPS3()));
781 			}
782 		}
783 
784 		/**
785 		 * Constructor. Registers itself on the given WorldView object.
786 		 * @param worldView WorldView object to listent to.
787 		 */
788 		public ControlMessageListener(IWorldView worldView)
789 		{
790 			worldView.addEventListener(ControlMessage.class, this);
791 		}
792 	}
793 
794 	/** BeginMessage listener */
795 	private ControlMessageListener controlMessageListener;
796 	
797 	/*========================================================================*/
798 
799 
800 	/**
801 	 * BeginMessage listener.
802 	 */
803 	private class BeginMessageListener implements IWorldEventListener<BeginMessage>
804 	{
805 		@Override
806 		public void notify(BeginMessage event)
807 		{
808 			synchronized(statsMutex) {
809 				lastBeginMessage = event;
810 				
811 				if (currentUT2004Time <= 0) {
812 					if (isLogBeforeMatchRestart()) {
813 						matchStartTime = event.getTime();
814 					}
815 				}
816 				
817 				previousUT2004Time = currentUT2004Time;
818 				currentUT2004Time = event.getTime();
819 				ut2004TimeDelta = currentUT2004Time - previousUT2004Time;
820 				
821 				previousSystemTime = currentSystemTime;
822 				currentSystemTime = System.currentTimeMillis();
823 				systemTimeDelta = currentSystemTime - previousSystemTime;
824 			}
825 		}
826 
827 		/**
828 		 * Constructor. Registers itself on the given WorldView object.
829 		 * @param worldView WorldView object to listent to.
830 		 */
831 		public BeginMessageListener(IWorldView worldView)
832 		{
833 			worldView.addEventListener(BeginMessage.class, this);
834 		}
835 	}
836 
837 	/** BeginMessage listener */
838 	private BeginMessageListener beginMessageListener;
839 	
840 	/** Most rescent message containing info about the game frame. */
841 	private BeginMessage lastBeginMessage = null;
842 	
843 	/** Previous time in UT2004 */
844 	private double previousUT2004Time = -1;
845 	
846 	/** Previous {@link System#currentTimeMillis()} when {@link BeginMessage} was received. */
847 	private long previousSystemTime = -1;
848 	
849 	/** Current time in UT2004 */
850 	private double currentUT2004Time = -1;
851 	
852 	/** Current (== last) {@link System#currentTimeMillis()} when {@link BeginMessage} was received. */
853 	private long currentSystemTime = -1;
854 	
855 	/**
856 	 * How many time has passed between last two {@link BeginMessage}.
857 	 */
858 	private double ut2004TimeDelta = -1;
859 	
860 	/**
861 	 * How many millis has passed between last two {@link BeginMessage}.
862 	 */
863 	private long systemTimeDelta = -1;
864 	
865 	/*========================================================================*/
866 
867 	/**
868 	 * EndMessage listener.
869 	 */
870 	private class EndMessageListener implements IWorldEventListener<EndMessage>
871 	{
872 		@Override
873 		public void notify(EndMessage event)
874 		{
875 			synchronized(statsMutex) {
876 				lastEndMessage = event;
877 				updateStats(event);
878 			}
879 		}
880 
881 		/**
882 		 * Constructor. Registers itself on the given WorldView object.
883 		 * @param worldView WorldView object to listent to.
884 		 */
885 		public EndMessageListener(IWorldView worldView)
886 		{
887 			worldView.addEventListener(EndMessage.class, this);
888 		}
889 	}
890 
891 	/** EndMessage listener */
892 	EndMessageListener endMessageListener;
893 	
894 	/** Most rescent message containing info about the game frame. */
895 	EndMessage lastEndMessage = null;
896 
897 	/*========================================================================*/
898 	
899 	/**
900 	 * PlayerScore listener.
901 	 */
902 	private class PlayerScoreListener implements IWorldEventListener<PlayerScore>
903 	{
904 		@Override
905 		public void notify(PlayerScore event)
906 		{
907 			synchronized(statsMutex) {
908 				synchronized(playerScores) {
909 					playerScores.put(event.getId(), event);
910 				}
911 			}
912 		}
913 
914 		/**
915 		 * Constructor. Registers itself on the given WorldView object.
916 		 * @param worldView WorldView object to listent to.
917 		 */
918 		public PlayerScoreListener(IWorldView worldView)
919 		{
920 			worldView.addEventListener(PlayerScore.class, this);
921 		}
922 	}
923 
924 	/** PlayerScore listener */
925 	private PlayerScoreListener playerScoreListener;
926 	
927 	/*========================================================================*/
928 	
929 	/**
930 	 * TeamScore listener.
931 	 */
932 	private class TeamScoreListener implements IWorldObjectEventListener<TeamScore, WorldObjectUpdatedEvent<TeamScore>>
933 	{
934 		/**
935 		 * Constructor. Registers itself on the given WorldView object.
936 		 * @param worldView WorldView object to listent to.
937 		 */
938 		public TeamScoreListener(IWorldView worldView)
939 		{
940 			worldView.addObjectListener(TeamScore.class, WorldObjectUpdatedEvent.class, this);
941 		}
942 
943 		@Override
944 		public void notify(WorldObjectUpdatedEvent<TeamScore> event) {
945 			synchronized(statsMutex) {
946 				synchronized(teamScores) {
947 					teamScores.put(event.getObject().getTeam(), event.getObject());
948 				}
949 			}
950 		}
951 	}
952 
953 	/** TeamScore listener */
954 	private TeamScoreListener teamScoreListener;
955 		
956 	/*========================================================================*/	
957 	
958 	/**
959 	 * Self listener.
960 	 */
961 	private class SelfListener implements IWorldObjectEventListener<Self, WorldObjectUpdatedEvent<Self>>
962 	{
963 		/**
964 		 * Constructor. Registers itself on the given WorldView object.
965 		 * @param worldView WorldView object to listent to.
966 		 */
967 		public SelfListener(IWorldView worldView)
968 		{
969 			worldView.addObjectListener(Self.class, WorldObjectUpdatedEvent.class, this);
970 		}
971 
972 		@Override
973 		public void notify(WorldObjectUpdatedEvent<Self> event) {
974 			synchronized(statsMutex) {
975 				self = event.getObject();
976 			}
977 		}
978 	}
979 	
980 	/** Self listener */
981 	private SelfListener selfListener;
982 	
983 	/** Last self received */
984 	private Self self = null;
985 	
986 	/*========================================================================*/	
987 	
988 	/**
989 	 * BotKilled listener.
990 	 */
991 	private class BotKilledListener implements IWorldEventListener<BotKilled>
992 	{
993 		/**
994 		 * Constructor. Registers itBotKilled on the given WorldView object.
995 		 * @param worldView WorldView object to listent to.
996 		 */
997 		public BotKilledListener(IWorldView worldView)
998 		{
999 			worldView.addEventListener(BotKilled.class, this);
1000 		}
1001 
1002 		@Override
1003 		public void notify(BotKilled event) {
1004 			botKilled(event);
1005 		}
1006 	}
1007 	
1008 	/** BotKilled listener */
1009 	private BotKilledListener botKilledListener;
1010 		
1011 	/*========================================================================*/	
1012 	
1013 	/**
1014 	 * PlayerKilled listener.
1015 	 */
1016 	private class PlayerKilledListener implements IWorldEventListener<PlayerKilled>
1017 	{
1018 		/**
1019 		 * Constructor. Registers itPlayerKilled on the given WorldView object.
1020 		 * @param worldView WorldView object to listent to.
1021 		 */
1022 		public PlayerKilledListener(IWorldView worldView)
1023 		{
1024 			worldView.addEventListener(PlayerKilled.class, this);
1025 		}
1026 
1027 		@Override
1028 		public void notify(PlayerKilled event) {
1029 			playerKilled(event);
1030 		}
1031 	}
1032 	
1033 	/** PlayerKilled listener */
1034 	private PlayerKilledListener playerKilledListener;
1035 	
1036 	/*========================================================================*/	
1037 	
1038 	/**
1039 	 * GameRestarted listener.
1040 	 */
1041 	private class GameRestartedListener implements IWorldEventListener<GameRestarted>
1042 	{
1043 		/**
1044 		 * Constructor. Registers itGameRestarted on the given WorldView object.
1045 		 * @param worldView WorldView object to listent to.
1046 		 */
1047 		public GameRestartedListener(IWorldView worldView)
1048 		{
1049 			worldView.addEventListener(GameRestarted.class, this);
1050 		}
1051 
1052 		@Override
1053 		public void notify(GameRestarted event) {
1054 			gameRestarted(event);
1055 		}
1056 	}
1057 	
1058 	/** GameRestarted listener */
1059 	private GameRestartedListener gameRestartedListener;
1060 	
1061 	/*========================================================================*/	
1062 	
1063 	/**
1064 	 * ItemPickedUp listener.
1065 	 */
1066 	private class ItemPickedUpListener implements IWorldEventListener<ItemPickedUp>
1067 	{
1068 		/**
1069 		 * Constructor. Registers itItemPickedUp on the given WorldView object.
1070 		 * @param worldView WorldView object to listent to.
1071 		 */
1072 		public ItemPickedUpListener(IWorldView worldView)
1073 		{
1074 			worldView.addEventListener(ItemPickedUp.class, this);
1075 		}
1076 
1077 		@Override
1078 		public void notify(ItemPickedUp event) {
1079 			itemPickedUp(event);
1080 		}
1081 	}
1082 	
1083 	/** ItemPickedUp listener */
1084 	private ItemPickedUpListener itemPickedUpListener;
1085 	
1086 	/*========================================================================*/
1087 	
1088 	/**
1089 	 * ItemPickedUp listener.
1090 	 */
1091 	private class FlagListener implements IWorldObjectListener<FlagInfo>
1092 	{
1093 		/**
1094 		 * Constructor. Registers itItemPickedUp on the given WorldView object.
1095 		 * @param worldView WorldView object to listent to.
1096 		 */
1097 		public FlagListener(IWorldView worldView)
1098 		{
1099 			worldView.addObjectListener(FlagInfo.class, this);
1100 		}
1101 
1102                 @Override
1103                 public void notify(IWorldObjectEvent<FlagInfo> event) {
1104                     FlagInfo t = event.getObject();
1105                     flagInfo(t);
1106                 }
1107 	}
1108 	
1109 	/** ItemPickedUp listener */
1110 	private FlagListener flagListener;
1111 	
1112 	/*========================================================================*/	
1113 	
1114 	////
1115 	//
1116 	// GAME RESTART HANDLING
1117 	//
1118 	////
1119 	
1120 	/**
1121 	 * Global UT2004 time when the match was started.
1122 	 */
1123 	private double matchStartTime = 0;
1124 	
1125 	/**
1126 	 * Whether we should be logging. Default: TRUE... set to FALSE with {@link AgentStats#setLogBeforeMatchRestart(boolean)} with arg. TRUE.
1127 	 * <p><p>
1128 	 * Never use directly to decide whether you should collect stats, always use {@link AgentStats#isLogging()}.
1129 	 */
1130 	private boolean shouldLog = true;
1131 	
1132 	/**
1133 	 * Should we log something before {@link GameRestarted}? Default: TRUE. 
1134 	 */
1135 	private boolean logBeforeMatchRestart = true;
1136 
1137 	/**
1138 	 * Whether the object is currently collecting stats. 
1139 	 * <p><p>
1140 	 * This depends on three things: 
1141 	 * <ol>
1142 	 * <li>we have to have {@link AgentStats#isTimeInitialized()}</li>
1143 	 * <li>and we should not be waiting for {@link GameRestarted}, i.e., you have used {@link AgentStats#setLogBeforeMatchRestart(boolean)}</li>
1144 	 * <li>we have already received bot's {@link Self}</li>
1145 	 * </ol>
1146 	 * 
1147 	 * @return
1148 	 */
1149 	public boolean isLogging() {
1150 		return isTimeInitialized() && shouldLog && self != null;
1151 	}
1152 
1153 	/**
1154 	 * Should we log something before {@link GameRestarted}? Default: TRUE.
1155 	 * @return
1156 	 */
1157 	public boolean isLogBeforeMatchRestart() {
1158 		return logBeforeMatchRestart;
1159 	}
1160 
1161 	/**
1162 	 * Sets whether we should collect stats before {@link GameRestarted} event. Default: FALSE.
1163 	 * <p><p>
1164 	 * Best to be utilized in {@link IUT2004BotController#prepareBot(UT2004Bot)}.
1165 	 * 
1166 	 * @param logBeforeMatchRestart
1167 	 */
1168 	public void setLogBeforeMatchRestart(boolean logBeforeMatchRestart) {
1169 		this.logBeforeMatchRestart = logBeforeMatchRestart;
1170 		if (this.logBeforeMatchRestart) {
1171 			shouldLog = true;
1172 		} else {
1173 			shouldLog = false;
1174 		}
1175 	}
1176 
1177 	protected void gameRestarted(GameRestarted event) {
1178 		synchronized(statsMutex) {
1179 			if (event.isFinished()) {
1180 				shouldLog = true;
1181 				resetStats();
1182 				matchStartTime = currentUT2004Time;
1183 				outputStatLine(0, "GAME_RESTARTED");
1184 			}
1185 		}
1186 	}
1187 	
1188 	////
1189 	//
1190 	// EVENT HANDLING
1191 	//
1192 	////
1193 	
1194 	protected int playerKilledInRow = 0;
1195 	
1196 	protected void botKilled(BotKilled event) {
1197 		synchronized(statsMutex) {
1198 			if (!isLogging()) return;
1199 			++deaths;
1200 			if (event.getKiller() == null || (event.getKiller().equals(getBotId())) || (self != null && event.getKiller().equals(self.getBotId()))) {
1201 				++suicides;			
1202 			} else {
1203 				++killedByOthers;
1204 				synchronized(killedBy) {
1205 					killedBy.put(event.getKiller(), killedBy.get(event.getKiller())+1);
1206 				}
1207 			}
1208 			
1209 			// CLEANING UP
1210 			playerKilledInRow = 0;
1211 			lastLocation = null;
1212 			
1213 			// OUTPUTTING STATS
1214 			if (event.getKiller() == null || (self != null && event.getKiller().equals(getBotId()))) {
1215 				outputStatLine(getCurrentMatchTime(), "BOT_KILLED", "SUICIDE", event.getDamageType());
1216 			} else {
1217 				outputStatLine(getCurrentMatchTime(), "BOT_KILLED", event.getKiller().getStringId(), event.getDamageType());
1218 			}
1219 		}
1220 	}
1221 	
1222 	protected void playerKilled(PlayerKilled event) {
1223 		synchronized(statsMutex) {
1224 			if (!isLogging()) return;
1225 			UnrealId killer = event.getKiller();
1226 			UnrealId me = getBotId();
1227 			if (event.getId().equals(me)) {
1228 				// this is handled elsewhere!
1229 				return;
1230 			}
1231 	    	if (killer == null || (!killer.equals(me))) {
1232 				// the player has committed suicide or was killed by other bot
1233 			} else {
1234 				// WE HAVE KILLED THE BOT!
1235 				++killedOthers;					
1236 				++playerKilledInRow;
1237 				if (playerKilledInRow > numberOfPlayersKilledWithoutDeath) {
1238 					numberOfPlayersKilledWithoutDeath = playerKilledInRow;
1239 				}
1240 				synchronized(killed) {
1241 					killed.put(event.getId(), killed.get(event.getId())+1);
1242 				}
1243 				outputStatLine(getCurrentMatchTime(), "PLAYER_KILLED", event.getId().getStringId(), event.getDamageType());
1244 			}	
1245 		}
1246 	}
1247         
1248         private String team0FlagState;
1249         private String team1FlagState;
1250 	
1251 	protected void flagInfo(FlagInfo event) {
1252 		synchronized(statsMutex) {
1253 			if (!isLogging()) return;
1254                         String flagState;
1255                         if(event.getTeam() == 0 )
1256                             flagState = team0FlagState;
1257                         else
1258                             flagState = team1FlagState;
1259                         
1260                         //if the state has change, log it
1261                         if(flagState != null && !flagState.equals(event.getState())){
1262                             if(event.getState().toLowerCase().equals("home"))
1263                             {
1264                                 if(flagState.toLowerCase().equals("held"))
1265                                     outputStatLine(getCurrentMatchTime(), "FLAG_CAPTURED", event.getTeam().toString(), event.getHolder()!=null?event.getHolder().getStringId():"");
1266                                 else if(flagState.toLowerCase().equals("dropped"))
1267                                     outputStatLine(getCurrentMatchTime(), "FLAG_RETURNED", event.getTeam().toString(), event.getHolder()!=null?event.getHolder().getStringId():"");
1268                             }else if(event.getState().toLowerCase().equals("held"))
1269                                 outputStatLine(getCurrentMatchTime(), "FLAG_PICKEDUP".toUpperCase(), event.getTeam().toString(), event.getHolder()!=null?event.getHolder().getStringId():"");
1270                             else // only drop left here, but just to be sure
1271                                 outputStatLine(getCurrentMatchTime(), "FLAG_"+event.getState().toUpperCase(), event.getTeam().toString(), event.getHolder()!=null?event.getHolder().getStringId():"");
1272                         }
1273                         
1274                         if(event.getTeam() == 0 )
1275                             team0FlagState = event.getState();
1276                         else
1277                             team1FlagState = event.getState();
1278 		}
1279 	}
1280 	
1281 	protected void itemPickedUp(ItemPickedUp event) {
1282 		synchronized(statsMutex) {
1283 			if (!isLogging()) return;
1284 			if (event.getType() == UT2004ItemType.U_DAMAGE_PACK) {
1285 				++doubleDamageCount;
1286 			}
1287 			synchronized(itemsCollected) {
1288 				itemsCollected.put(event.getType(), itemsCollected.get(event.getType()) + 1);
1289 			}
1290 			synchronized(itemsByCategoryCollected) {
1291 				itemsByCategoryCollected.put(event.getType().getCategory(), itemsByCategoryCollected.get(event.getType().getCategory())+1);
1292 			}
1293 			outputStatLine(getCurrentMatchTime(), "ITEM_PICKEDUP", event.getType().getName(), event.getType().getCategory().name);
1294 		}
1295 	}
1296 	
1297 	////
1298 	//
1299 	// UPDATE STATS (EndMessage event)
1300 	//
1301 	////
1302 	
1303 	protected Location lastLocation;
1304 	
1305 	/**
1306 	 * Called when {@link EndMessage} is received, writes another line into the {@link UT2004AnalyzerObsStats#outputFile}.
1307 	 */
1308     protected void updateStats(EndMessage event) {
1309     	synchronized(statsMutex) {
1310 	    	if (self == null) {
1311 	    		log.warning("EndMessage received but no SELF was received.");
1312 	    		return;
1313 	    	}
1314 	    	if (!isLogging()) return;
1315 	    	
1316 	    	if (self.getVelocity().size() > 1) {
1317 	    		timeMoving += ut2004TimeDelta;
1318 	    	}
1319 	    	if (self.isShooting()) {
1320 	    		timeShooting += ut2004TimeDelta;
1321 	    		ItemType weapon = UT2004ItemType.getWeapon(UnrealId.get(self.getWeapon()));
1322 	    		if (weapon == null) {
1323 	    			log.warning("Unrecognized weapon of id: " + self.getWeapon());
1324 	    		} else {
1325 	    			synchronized(weaponsUsedTime) {
1326 	    				weaponsUsedTime.put(weapon, weaponsUsedTime.get(weapon) + ut2004TimeDelta);
1327 	    			}
1328 	    		}
1329 	    	}
1330 	    	if (self.getUDamageTime() > 0) {
1331 	    		doubleDamageTime += ut2004TimeDelta;
1332 	    	}
1333 	    	if (lastLocation != null) {
1334 	    		travelledDistance += lastLocation.getDistance(self.getLocation());
1335 	    	}
1336 	    	lastLocation = self.getLocation();
1337 	    	
1338 	    	outputStatLine(getCurrentMatchTime());
1339     	}
1340     }
1341     
1342     ////
1343     //
1344     // LIFECYCLE MANAGEMENT
1345     //
1346     ////
1347     
1348     @Override
1349     protected void start(boolean startToPaused) {
1350     	super.start(startToPaused);
1351     	resetStats();
1352     	resetMatchTime();
1353     }
1354 	
1355     @Override
1356     protected void stop() {
1357     	super.stop();
1358     	synchronized(statsMutex) {
1359 	    	stopOutput();
1360     	}
1361     }
1362     
1363     @Override
1364     protected void kill() {
1365     	super.kill();
1366     	synchronized(statsMutex) {
1367     		stopOutput();
1368     	}    	
1369     }
1370 
1371     ////
1372     // 
1373     // CONSTRUCTOR
1374     //
1375     ////
1376 
1377 	/**
1378 	 * Constructor. Setups the memory module based on bot's world view.
1379 	 * @param bot owner of the module that is using it
1380 	 */
1381 	public AgentStats(IObservingAgent bot) {
1382 		this(bot, null);
1383 	}
1384 	
1385 	/**
1386 	 * Constructor. Setups the memory module based on bot's world view.
1387 	 * @param bot owner of the module that is using it
1388 	 * @param log Logger to be used for logging runtime/debug info. If <i>null</i>, the module creates its own logger.
1389 	 */
1390 	public AgentStats(IObservingAgent bot, Logger log)
1391 	{
1392 		super(bot, log);
1393 
1394 		// create listeners
1395 		controlMessageListener = new ControlMessageListener(worldView);
1396 		beginMessageListener   = new BeginMessageListener(worldView);
1397 		endMessageListener     = new EndMessageListener(worldView);
1398 		selfListener           = new SelfListener(worldView);
1399 		botKilledListener      = new BotKilledListener(worldView);
1400 		playerKilledListener   = new PlayerKilledListener(worldView);
1401 		itemPickedUpListener   = new ItemPickedUpListener(worldView);
1402 		flagListener           = new FlagListener(worldView);
1403 		gameRestartedListener  = new GameRestartedListener(worldView);
1404 		playerScoreListener    = new PlayerScoreListener(worldView);
1405 		teamScoreListener      = new TeamScoreListener(worldView);
1406         cleanUp();
1407 	}
1408 	
1409 }