View Javadoc

1   package cz.cuni.amis.pogamut.ut2004.tournament.deathmatch;
2   
3   import java.io.File;
4   import java.io.IOException;
5   import java.util.ArrayList;
6   import java.util.Collections;
7   import java.util.Comparator;
8   import java.util.Formatter;
9   import java.util.HashMap;
10  import java.util.List;
11  import java.util.Map;
12  import java.util.Map.Entry;
13  import java.util.concurrent.CountDownLatch;
14  import java.util.concurrent.TimeUnit;
15  import java.util.logging.Level;
16  
17  import cz.cuni.amis.pogamut.base.agent.state.level0.IAgentState;
18  import cz.cuni.amis.pogamut.base.agent.state.level1.IAgentStateDown;
19  import cz.cuni.amis.pogamut.base.agent.state.level1.IAgentStateUp;
20  import cz.cuni.amis.pogamut.base.communication.worldview.event.IWorldEventListener;
21  import cz.cuni.amis.pogamut.base.utils.guice.AdaptableProvider;
22  import cz.cuni.amis.pogamut.base.utils.logging.LogCategory;
23  import cz.cuni.amis.pogamut.unreal.communication.messages.UnrealId;
24  import cz.cuni.amis.pogamut.ut2004.agent.module.sensor.AgentStats;
25  import cz.cuni.amis.pogamut.ut2004.analyzer.IUT2004AnalyzerObserver;
26  import cz.cuni.amis.pogamut.ut2004.analyzer.UT2004Analyzer;
27  import cz.cuni.amis.pogamut.ut2004.analyzer.stats.UT2004AnalyzerObsStats;
28  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbcommands.StartPlayers;
29  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.PlayerScore;
30  import cz.cuni.amis.pogamut.ut2004.server.impl.UT2004Server;
31  import cz.cuni.amis.pogamut.ut2004.tournament.botexecution.UT2004BotExecution;
32  import cz.cuni.amis.pogamut.ut2004.tournament.match.UT2004BotConfig;
33  import cz.cuni.amis.pogamut.ut2004.tournament.match.UT2004Match;
34  import cz.cuni.amis.pogamut.ut2004.tournament.match.UT2004MatchResult;
35  import cz.cuni.amis.pogamut.ut2004.utils.UCCWrapper;
36  import cz.cuni.amis.utils.ExceptionToString;
37  import cz.cuni.amis.utils.FilePath;
38  import cz.cuni.amis.utils.exception.PogamutException;
39  import cz.cuni.amis.utils.exception.PogamutIOException;
40  import cz.cuni.amis.utils.exception.PogamutInterruptedException;
41  import cz.cuni.amis.utils.flag.FlagListener;
42  import cz.cuni.amis.utils.token.IToken;
43  
44  public class UT2004DeathMatch extends UT2004Match<UT2004DeathMatchConfig, UT2004DeathMatchResult> {
45  	
46  	private int targetFragCount;
47  
48  	public UT2004DeathMatch(UT2004DeathMatchConfig config, LogCategory log) {
49  		super(false, config, log);		
50  	}
51  	
52  	@Override
53  	protected void changeBotTeam(UT2004Server server, UnrealId botId, int desiredTeam) {
54  		// THERE IS NO NEED TO CHANGE BOT TEAM IN DEATHMATCH!
55  	}
56  
57  	@Override
58  	protected UT2004MatchResult waitMatchFinish(UCCWrapper ucc, UT2004Server server, UT2004Analyzer analyzer, Bots bots, long timeoutInMillis) {
59  		// usually the GB2004 dies out whenever match ends -> just wait till server does not fail + timeout + observe bots
60  		
61  		if (log != null && log.isLoggable(Level.WARNING)) {
62  			log.warning(config.getMatchId().getToken() + ": Waiting for the match to finish...");
63  		}
64  		
65  		if (config.getTimeLimit() * 60 * 1000 + 5 * 60 * 1000 > timeoutInMillis) {
66  			timeoutInMillis = config.getTimeLimit() * 60 * 1000 + 5 * 60 * 1000; // give additional 5 minutes to UT2004 to restart GB2004
67  		}
68  		
69  		Map<IToken, FlagListener<Boolean>> customBotObservers = new HashMap<IToken, FlagListener<Boolean>>(config.getBots().size());
70  		FlagListener<IAgentState> serverObs = null;
71  		FlagListener<Boolean> uccObs = null;
72  		IWorldEventListener<PlayerScore> scoresListener = null;
73  		
74  		final CountDownLatch waitLatch = new CountDownLatch(1);
75  		final AdaptableProvider<Boolean> oneOfBotsDiedOut = new AdaptableProvider<Boolean>(false);
76  		final AdaptableProvider<Boolean> serverDiedOut = new AdaptableProvider<Boolean>(false);
77  		final Map<UnrealId, PlayerScore> scores = new HashMap<UnrealId, PlayerScore>();
78  		
79  		boolean exception = false;
80  		
81  		try {
82  			scoresListener = new IWorldEventListener<PlayerScore>() {
83  
84  				@Override
85  				public void notify(PlayerScore event) {
86  					if (waitLatch == null || waitLatch.getCount() <= 0) {
87  						// DO NOT ACCEPT ANY MORE MESSAGES AFTER WE TERMINATE
88  						return;
89  					}
90  					scores.put(event.getId(), event);
91  					if (event.getScore() >= targetFragCount) {
92  						// TARGET SCORE REACHED BY ONE OF THE BOTS!
93  						waitLatch.countDown();
94  					}
95  				}
96  				
97  			};
98  			server.getWorldView().addEventListener(PlayerScore.class, scoresListener);
99  			
100 			for (UT2004BotConfig botConfig : config.getBots().values()) {
101 				FlagListener<Boolean> obs = new FlagListener<Boolean>() {
102 					@Override
103 					public void flagChanged(Boolean changedValue) {
104 						if (!changedValue) {
105 							// bot has died out
106 							oneOfBotsDiedOut.set(true);
107 							waitLatch.countDown();
108 						}
109 					}
110 				};
111 				
112 				bots.bots.get(botConfig.getBotId()).getRunning().addListener(obs);
113 				customBotObservers.put(botConfig.getBotId(), obs);
114 				if (!bots.bots.get(botConfig.getBotId()).getRunning().getFlag()) {
115 					// bot has died out
116 					oneOfBotsDiedOut.set(true);
117 					waitLatch.countDown();
118 					throw new PogamutException("One of custom bots died out from the start, failure!", log, this);
119 				}			
120 			}
121 			
122 			serverObs = new FlagListener<IAgentState>() {
123 	
124 				@Override
125 				public void flagChanged(IAgentState changedValue) {					
126 					if (changedValue instanceof IAgentStateDown) {
127 						// server has died out ... consider match to be over...
128 						serverDiedOut.set(true);
129 						waitLatch.countDown();
130 					}
131 				}
132 				
133 			};
134 			
135 			server.getState().addListener(serverObs);
136 			
137 			if (server.notInState(IAgentStateUp.class)) {
138 				// server has died out ... consider match to be over...
139 				serverDiedOut.set(true);
140 				waitLatch.countDown();
141 				throw new PogamutException("Server is dead from the start, failure!", log, this);
142 			}
143 			
144 			uccObs = new FlagListener<Boolean>() {
145 
146 				@Override
147 				public void flagChanged(Boolean changedValue) {
148 					if (changedValue) {
149 						// GAME IS ENDING!
150 						// Consider match to be over...
151 						serverDiedOut.set(true);
152 						waitLatch.countDown();
153 					}
154 				}
155 				
156 			};
157 			
158 			ucc.getGameEnding().addListener(uccObs);
159 			
160 			waitLatch.await(timeoutInMillis, TimeUnit.MILLISECONDS);
161 			if (waitLatch.getCount() > 0) {
162 				// TIMEOUT!
163 				throw new PogamutException("TIMEOUT! The match did not end in " + (timeoutInMillis / 1000) + " secs.", log, this);
164 			}
165 			
166 			bots.matchEnd = System.currentTimeMillis();
167 			
168 			// RESTORE THE CONFIG...
169 			getConfig().setFragLimit(targetFragCount); 
170 			
171 			// WHAT HAS HAPPENED?
172 			if (oneOfBotsDiedOut.get()) {
173 				// check whether the server is down as well... but let GB2004 to process it
174 				try {
175 					Thread.sleep(5000);
176 				} catch (InterruptedException e) {
177 					throw new PogamutInterruptedException("Interrupted while giving GB2004 time to tear down its connection.", log, this);
178 				}
179 				try {
180 					server.getAct().act(new StartPlayers());
181 				} catch (Exception e) {
182 					// YEP, server is down
183 					serverDiedOut.set(true);
184 				}
185 				if (!serverDiedOut.get()) {
186 					// NO SERVER IS STILL RUNNING
187 					log.warning("ONE OF BOTS HAS DIED OUT, BUT SERVER IS STILL RUNNING ... POSSIBLE MATCH FAILURE!");
188 				}
189 			}
190 			if (!serverDiedOut.get() && server.inState(IAgentStateUp.class)) {
191 				// Server is still running? ... This will likely to always happen as frag limit is targetFragCount+10 !!! 
192 				// Kill it...
193 				server.kill();
194 			}
195 			// server is DEAD -> assume that the match has ended
196 			
197 			// KILL UCC TO ENSURE NOTHING WILL CHANGE AFTER THAT
198 			if (ucc != null) {
199 				try {
200 					if (log != null && log.isLoggable(Level.INFO)) {
201 						log.info(config.getMatchId().getToken() + ": Killing UCC...");
202 					} 
203 				} catch (Exception e) {				
204 				}
205 				try {
206 					ucc.stop();
207 				} catch (Exception e) {					
208 				}
209 			}
210 			
211 			List<UnrealId> winners = new ArrayList<UnrealId>(1);
212 			int maxFrags = 0;
213 			
214 			// PROCESS THE RESULT
215 			for (Entry<UnrealId, IToken> entry : bots.unrealId2BotId.entrySet()) {
216 				PlayerScore playerScore = scores.get(entry.getKey());
217 				if (playerScore == null) {
218 					throw new PogamutException("Can't resolve the match result. One of the bot with botId '" + entry.getValue().getToken() + "' and corresponding unrealId '" + entry.getKey().getStringId() + "' has no score entry!", log, this);
219 				}
220 				if (playerScore.getScore() == maxFrags) {
221 					winners.add(entry.getKey());
222 				} else
223 				if (playerScore.getScore() > maxFrags) {
224 					maxFrags = playerScore.getScore();
225 					winners.clear();
226 					winners.add(entry.getKey());
227 				}
228 			}
229 			for (Entry<UnrealId, IToken> entry : bots.nativeUnrealId2BotId.entrySet()) {
230 				PlayerScore playerScore = scores.get(entry.getKey());
231 				if (playerScore == null) {
232 					throw new PogamutException("Can't resolve the match result. One of the bot with botId '" + entry.getValue().getToken() + "' and corresponding unrealId '" + entry.getKey().getStringId() + "' has no score entry!", log, this);
233 				}
234 				if (playerScore.getScore() == maxFrags) {
235 					winners.add(entry.getKey());
236 				} else
237 				if (playerScore.getScore() > maxFrags) {
238 					maxFrags = playerScore.getScore();
239 					winners.clear();
240 					winners.add(entry.getKey());
241 				}
242 			}
243 			
244 			if (winners.size() == 0) {
245 				// no one has reached FragLimit
246 				throw new PogamutException("There is no winner, impossible! **puzzled**", log, this);
247 			}
248 			if (winners.size() > 1) {
249 				StringBuffer sb = new StringBuffer();
250 				sb.append("There is more than one bot with highest score == " + maxFrags + ": ");
251 				boolean first = true;
252 				for (UnrealId id : winners) {
253 					if (first) first = false;
254 					else sb.append(", ");
255 					sb.append("Bot[botId=" + bots.getBotId(id) + ", unrealId=" + id.getStringId() + ", score=" + scores.get(id).getScore() + "]");
256 				}
257 				sb.append(".");
258 				if (log != null && log.isLoggable(Level.WARNING)) {
259 					log.warning(sb.toString());
260 				}
261 				
262 				throw new PogamutException(sb.toString(), log, this);
263 			}
264 			
265 			if (log != null && log.isLoggable(Level.WARNING)) {
266 				log.warning(config.getMatchId().getToken() + ": MATCH FINISHED!");
267 			}
268 			
269 			return processResults(ucc, server, analyzer, bots, winners, scores);
270 			
271 		} catch (Exception e) {
272 			exception = true;
273 			throw new PogamutException("Failed to perform the match!", e, log, this);
274 		} finally {
275 			for (Entry<IToken, FlagListener<Boolean>> entry : customBotObservers.entrySet()) {
276 				bots.bots.get(entry.getKey()).getRunning().removeListener(entry.getValue());
277 			}
278 			server.getState().removeListener(serverObs);
279 			server.getWorldView().removeEventListener(PlayerScore.class, scoresListener);
280 		}		
281 
282 	}
283 	
284 	protected UT2004DeathMatchResult processResults(UCCWrapper ucc, UT2004Server server, UT2004Analyzer analyzer, Bots bots, List<UnrealId> winners, Map<UnrealId, PlayerScore> finalScores) {
285 		
286 		if (log != null && log.isLoggable(Level.FINE)) {
287 			log.fine(config.getMatchId().getToken() + ": Processing results...");
288 		}
289 		
290 		UT2004DeathMatchResult result = new UT2004DeathMatchResult();
291 		
292 		result.setMatchTimeEnd(((double)bots.matchEnd - (double)bots.matchStart) / (1000));
293 		
294 		for (Entry<UnrealId, PlayerScore> entry : finalScores.entrySet()) {
295 			result.getFinalScores().put(bots.getBotId(entry.getKey()), entry.getValue());
296 		}
297 		
298 		for (Entry<IToken, IUT2004AnalyzerObserver> entry : bots.botObservers.entrySet()) {
299 			if (!(entry.getValue() instanceof UT2004AnalyzerObsStats)) {
300 				throw new PogamutException("There is an observer of wrong class, expecting UT2004AnalyzerObsStats, got " + entry.getValue().getClass().getSimpleName() + "!", log, this);
301 			}
302 			result.getBotObservers().put(entry.getKey(), (UT2004AnalyzerObsStats)entry.getValue());
303 		}
304 		
305 		List<IToken> botIds = config.getAllBotIds();
306 		for (IToken botId1 : botIds) {
307 			result.getTotalKills().put(botId1, 0);
308 			result.getWasKilled().put(botId1, 0);
309 			result.getSuicides().put(botId1, 0);
310 			for (IToken botId2 : botIds) {
311 				result.getKillCounts().put(botId1, botId2, 0);
312 			}
313 		}
314 		
315 		for (Entry<IToken, UT2004AnalyzerObsStats> entry : result.getBotObservers().entrySet()) {
316 			IToken botId = entry.getKey();
317 			UT2004AnalyzerObsStats obs = entry.getValue();
318 			AgentStats stats = obs.getStats();
319 			for (Entry<UnrealId, Integer> killed : stats.getKilled().entrySet()) {
320 				result.getKillCounts().get(botId).put(bots.getBotId(killed.getKey()), killed.getValue());				
321 			}
322 			for (Entry<UnrealId, Integer> killedBy : stats.getKilledBy().entrySet()) {
323 				if (bots.isNativeBot(killedBy.getKey())) {
324 					result.getKillCounts().get(bots.getBotId(killedBy.getKey())).put(botId, killedBy.getValue());
325 				}
326 			}
327 			result.getSuicides().put(botId, stats.getSuicides());
328 			result.getKillCounts().put(botId, botId, stats.getSuicides());
329 		}
330 		
331 		for (IToken nativeBotId1 : config.getNativeBots().keySet()) {
332 			for (IToken nativeBotId2 : config.getNativeBots().keySet()) {
333 				if (nativeBotId1 == nativeBotId2) continue;
334 				result.getKillCounts().get(nativeBotId1).put(nativeBotId2, 0);
335 			}
336 			result.getSuicides().put(nativeBotId1, 0);
337 		}
338 		
339 		for (IToken botId : botIds) {
340 			int totalKills = 0;
341 			int totalKilled = 0;
342 			for (IToken other : botIds) {
343 				if (botId == other) continue;
344 				totalKills += result.getKillCounts().get(botId, other);
345 				totalKilled += result.getKillCounts().get(other, botId);
346 			}
347 			result.getTotalKills().put(botId, totalKills);
348 			result.getWasKilled().put(botId, totalKilled);
349 			if (config.isNativeBot(botId)) {
350 				result.getSuicides().put(botId, result.getFinalScores().get(botId).getDeaths() - totalKilled);
351 			}
352 		}
353 		
354 		if (winners.size() <= 0) {
355 			throw new PogamutException("There is no winner, impossible! **puzzled**", log, this);
356 		} else 
357 		if (winners.size() == 1) {
358 			result.setWinnerBot(bots.getBotId(winners.get(0)));
359 		} else {
360 			result.setDraw(true);
361 		}
362 		
363 		if (log != null && log.isLoggable(Level.WARNING)) {
364 			log.warning(config.getMatchId().getToken() + ": Results processed, " + (result.isDraw() ? "DRAW!" : "WINNER is Bot[botId=" + result.getWinnerBot().getToken() + ", unrealId=" + bots.getUnrealId(result.getWinnerBot()).getStringId() + "]."));
365 		}
366 		
367 		return result;
368 	}
369 	
370 	protected void outputResults_step1(UT2004DeathMatchResult result, File outputDirectory) {
371 		if (log != null && log.isLoggable(Level.FINE)) {
372 			log.fine(config.getMatchId().getToken() + ": Outputting match result into CSV file...");
373 		}
374 		
375 		File file = new File(outputDirectory.getAbsolutePath() + File.separator + "match-" + config.getMatchId().getToken() + "-result.csv");
376 		FilePath.makeDirsToFile(file);
377 		try {
378 			Formatter writer = new Formatter(file);
379 			writer.format("MatchId;FragLimit;TimeLimit;FragEnd;TimeEnd;Winner\n");
380 			writer.format
381 					(
382 						"%s;%d;%d;%d;%.3f;%s",
383 						config.getMatchId().getToken(),
384 						config.getFragLimit(),
385 						config.getTimeLimit(),
386 						result.getWinnerBot() == null ?
387 							// DRAW
388 							(result.getFinalScores().size() == 0 ?
389 								0
390 								:
391 								result.getFinalScores().values().iterator().next().getScore()
392 							)
393 							:
394 							// HAS WINNER
395 							result.getFinalScores().get(result.getWinnerBot()).getScore(),
396 						result.getMatchTimeEnd(),
397 						result.isDraw() ? "DRAW" : String.valueOf(result.getWinnerBot().getToken())
398 					);
399 			try {
400 				writer.close();
401 			} catch (Exception e) {			
402 			}
403 		} catch (IOException e) {
404 			throw new PogamutIOException("Failed to write results!", e, log, this);
405 		}
406 		
407 		if (log != null && log.isLoggable(Level.INFO)) {
408 			log.info(config.getMatchId().getToken() + ": Match result output into " + file.getAbsolutePath() + ".");
409 		}
410 		
411 	}
412 	
413 	protected void outputResults_step2(UT2004DeathMatchResult result, File outputDirectory) {
414 		if (log != null && log.isLoggable(Level.FINE)) {
415 			log.fine(config.getMatchId().getToken() + ": Outputting match scores into CSV file...");
416 		}
417 		
418 		File file = new File(outputDirectory.getAbsolutePath() + File.separator + "match-" + config.getMatchId().getToken() + "-bot-scores.csv");
419 		FilePath.makeDirsToFile(file);
420 		try {
421 			Formatter writer = new Formatter(file);
422 			
423 			List<IToken> bots = new ArrayList<IToken>(config.getBots().keySet());
424 			List<IToken> nativeBots = new ArrayList<IToken>(config.getNativeBots().keySet());
425 			
426 			Collections.sort(bots, new Comparator<IToken>() {
427 				@Override
428 				public int compare(IToken o1, IToken o2) {
429 					return o1.getToken().compareTo(o2.getToken());
430 				}				
431 			});
432 			Collections.sort(nativeBots, new Comparator<IToken>() {
433 				@Override
434 				public int compare(IToken o1, IToken o2) {
435 					return o1.getToken().compareTo(o2.getToken());
436 				}				
437 			});
438 			result.setBots(bots);
439 			result.setNativeBots(nativeBots);
440 			
441 			writer.format("botId");
442 			writer.format(";score;kills;killedByOthers;deaths;suicides");
443 			for (IToken token : config.getAllBotIds()) {
444 				writer.format(";");
445 				writer.format(token.getToken());
446 			}
447 			
448 			for (IToken token : config.getAllBotIds()) {
449 				writer.format("\n");
450 				writer.format(token.getToken());
451 				writer.format(";%d", result.getFinalScores().get(token).getScore());
452 				writer.format(";%d", result.getTotalKills().get(token));
453 				writer.format(";%d", result.getWasKilled().get(token));
454 				writer.format(";%d", result.getFinalScores().get(token).getDeaths());
455 				writer.format(";%d", result.getSuicides().get(token));				
456 				for (IToken token2 : config.getAllBotIds()) {
457 					writer.format(";%d", result.getKillCounts().get(token).get(token2));
458 				}				
459 			}
460 			
461 			try {
462 				writer.close();
463 			} catch (Exception e) {			
464 			}
465 		} catch (IOException e) {
466 			throw new PogamutIOException("Failed to write results!", e, log, this);
467 		}
468 		
469 		if (log != null && log.isLoggable(Level.INFO)) {
470 			log.info(config.getMatchId().getToken() + ": Match scores output into " + file.getAbsolutePath() + ".");
471 		}
472 		
473 	}
474 	
475 	@Override
476 	protected void outputResults(UCCWrapper ucc, UT2004Server server, UT2004Analyzer analyzer, Bots bots, UT2004MatchResult result,	File outputDirectory) {
477 		if (!(result instanceof UT2004DeathMatchResult)) {
478 			throw new PogamutException("Can't out results! Expected results of class UT2004DeathMatchResult and got " + result.getClass().getSimpleName() + "!", log, this);
479 		}
480 		outputResults_step1((UT2004DeathMatchResult) result, outputDirectory);
481 		outputResults_step2((UT2004DeathMatchResult) result, outputDirectory);
482 	}
483 	
484 	@Override
485 	public UT2004DeathMatchResult execute() {
486 		try {
487 			if (log != null && log.isLoggable(Level.WARNING)) {
488 				log.warning(config.getMatchId().getToken() + ": Executing!");
489 			} 
490 		} catch (Exception e) {				
491 		}
492 		
493 		UCCWrapper ucc = null;
494 		UT2004Server server = null;
495 		Bots bots = null;
496 		UT2004Analyzer analyzer = null;
497 		String recordFileName = config.getMatchId().getToken() + "-replay-" + UT2004Match.getCurrentDate();
498 		boolean exception = false;
499 		
500 		// HACK!!!
501 		// We must set frag limit to actually BIGGER NUMBER because otherwise GB2004 would drop the connection sooner before telling us that some bot
502 		// has achieved required score :-/
503 		targetFragCount = getConfig().getFragLimit();
504 		getConfig().setFragLimit(targetFragCount + 10); 
505 		
506 		try {
507 			// STEP 0
508 			setupLogger();
509 			
510 			// STEP 1
511 			validate();
512 			
513 			// STEP 2
514 			createGB2004Ini();
515 			
516 			// STEP 3
517 			ucc = startUCC();
518 			
519 			// STEP 4
520 			server = startControlServer(ucc);
521 			
522 			// STEP 5
523 			bots = startBots(ucc, server);
524 			
525 			// STEP 6
526 			analyzer = startAnalyzer(ucc, bots, getOutputPath("bots"), config.isHumanLikeLogEnabled());
527 			
528 			// STEP 7
529 			matchIsAboutToBegin(ucc, server, analyzer, bots);
530 			
531 			// STEP 8
532 			restartMatch(server, bots);
533 			
534 			// STEP 9			
535 			recordReplay(server, recordFileName);
536 			
537 			// STEP 9.5
538 			UT2004DeathMatchResult result = (UT2004DeathMatchResult) waitMatchFinish(ucc, server, analyzer, bots, config.getTimeLimit() * 1000 + 60 * 1000);
539 			
540 			// STEP 11
541 			copyReplay(ucc, recordFileName, getOutputPath());
542 
543 			// STEP 12
544 			outputResults(ucc, server, analyzer, bots, result, getOutputPath());
545 			
546 			// STEP 13
547 			shutdownAll(ucc, server, analyzer, bots);
548 			
549 			ucc = null;
550 			server = null;
551 			analyzer = null;
552 			bots = null;
553 			
554 			// WE'RE DONE! ... all that is left is a possible cleanup...
555 			return result;
556 			
557 		} catch (Exception e) {
558 			if (log != null && log.isLoggable(Level.SEVERE)) {
559 				log.severe(ExceptionToString.process(config.getMatchId().getToken() + ": EXCEPTION!", e));
560 			}
561 			exception = true;
562 			if (e instanceof PogamutException) throw (PogamutException)e;
563 			throw new PogamutException(e, log, this);
564 		} finally {	
565 			// RESTORE THE CONFIG...
566 			getConfig().setFragLimit(targetFragCount); 
567 			try {
568 				if (log != null && log.isLoggable(Level.INFO)) {
569 					log.info(config.getMatchId().getToken() + ": Cleaning up...");
570 				} 
571 			} catch (Exception e) {				
572 			}
573 			
574 			if (ucc != null) {
575 				try {
576 					if (log != null && log.isLoggable(Level.INFO)) {
577 						log.info(config.getMatchId().getToken() + ": Killing UCC...");
578 					} 
579 				} catch (Exception e) {				
580 				}
581 				try {
582 					ucc.stop();
583 				} catch (Exception e) {					
584 				}
585 			}
586 			if (server != null) {
587 				try {
588 					if (log != null && log.isLoggable(Level.INFO)) {
589 						log.info(config.getMatchId().getToken() + ": Killing UT2004Server...");
590 					} 
591 				} catch (Exception e) {				
592 				}
593 				try {
594 					server.kill();
595 				} catch (Exception e) {					
596 				}
597 			}
598 			if (bots != null) {
599 				try {
600 					if (log != null && log.isLoggable(Level.INFO)) {
601 						log.info(config.getMatchId().getToken() + ": Killing Custom bots...");
602 					} 
603 				} catch (Exception e) {				
604 				}
605 				for (UT2004BotExecution exec : bots.bots.values()) {
606 					try {
607 						exec.stop();					
608 					} catch (Exception e) {					
609 					}
610 				}
611 				try {
612 					if (log != null && log.isLoggable(Level.INFO)) {
613 						log.info(config.getMatchId().getToken() + ": Killing Custom bot observers...");
614 					} 
615 				} catch (Exception e) {				
616 				}
617 				for (IUT2004AnalyzerObserver obs : bots.botObservers.values()) {
618 					try {
619 						obs.kill();
620 					} catch (Exception e) {						
621 					}
622 				}
623 			}
624 			if (analyzer != null) {
625 				try {
626 					if (log != null && log.isLoggable(Level.INFO)) {
627 						log.info(config.getMatchId().getToken() + ": Killing UT2004Analyzer...");
628 					} 
629 				} catch (Exception e) {				
630 				}
631 				try {
632 					analyzer.kill();
633 				} catch (Exception e) {					
634 				}
635 			}	
636 			
637 			try {
638 				// STEP 10
639 				restoreGB2004IniBackup();
640 			} catch (Exception e) {				
641 			}
642 			
643 			try {
644 				if (log != null && log.isLoggable(Level.WARNING)) {
645 					if (exception) {
646 						log.warning(config.getMatchId().getToken() + ": Cleaned up, MATCH FAILED!");
647 					} else { 
648 						log.warning(config.getMatchId().getToken() + ": Cleaned up, match finished successfully.");
649 					}
650 				} 
651 			} catch (Exception e) {				
652 			}
653 			try {
654 				closeLogger();
655 			} catch (Exception e) {
656 				
657 			}
658 		}
659 		
660 	}
661 
662 }