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