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