1 package cz.cuni.amis.pogamut.ut2004.bot.impl;
2
3 import java.util.concurrent.TimeUnit;
4 import java.util.logging.Level;
5 import java.util.logging.Logger;
6
7 import javax.management.InstanceAlreadyExistsException;
8 import javax.management.MBeanRegistrationException;
9 import javax.management.MBeanServer;
10 import javax.management.MalformedObjectNameException;
11 import javax.management.NotCompliantMBeanException;
12 import javax.management.ObjectName;
13
14 import com.google.inject.Inject;
15
16 import cz.cuni.amis.introspection.Folder;
17 import cz.cuni.amis.introspection.java.ReflectionObjectFolder;
18 import cz.cuni.amis.pogamut.base.agent.IAgentId;
19 import cz.cuni.amis.pogamut.base.agent.exceptions.AgentException;
20 import cz.cuni.amis.pogamut.base.agent.impl.AbstractAgent;
21 import cz.cuni.amis.pogamut.base.agent.jmx.AgentJMXComponents;
22 import cz.cuni.amis.pogamut.base.agent.jmx.adapter.AgentMBeanAdapter;
23 import cz.cuni.amis.pogamut.base.communication.command.IAct;
24 import cz.cuni.amis.pogamut.base.communication.worldview.IWorldView;
25 import cz.cuni.amis.pogamut.base.communication.worldview.event.IWorldEventListener;
26 import cz.cuni.amis.pogamut.base.communication.worldview.object.IWorldObjectEventListener;
27 import cz.cuni.amis.pogamut.base.communication.worldview.object.event.WorldObjectUpdatedEvent;
28 import cz.cuni.amis.pogamut.base.communication.worldview.react.EventReact;
29 import cz.cuni.amis.pogamut.base.component.bus.IComponentBus;
30 import cz.cuni.amis.pogamut.base.component.bus.event.BusAwareCountDownLatch;
31 import cz.cuni.amis.pogamut.base.component.exception.ComponentCantStartException;
32 import cz.cuni.amis.pogamut.base.utils.guice.AgentScoped;
33 import cz.cuni.amis.pogamut.base.utils.logging.IAgentLogger;
34 import cz.cuni.amis.pogamut.base3d.agent.AbstractAgent3D;
35 import cz.cuni.amis.pogamut.base3d.worldview.IVisionWorldView;
36 import cz.cuni.amis.pogamut.base3d.worldview.object.Location;
37 import cz.cuni.amis.pogamut.base3d.worldview.object.Rotation;
38 import cz.cuni.amis.pogamut.base3d.worldview.object.Velocity;
39 import cz.cuni.amis.pogamut.ut2004.bot.IUT2004Bot;
40 import cz.cuni.amis.pogamut.ut2004.bot.IUT2004BotController;
41 import cz.cuni.amis.pogamut.ut2004.bot.jmx.BotJMXMBeanAdapter;
42 import cz.cuni.amis.pogamut.ut2004.bot.params.UT2004BotParameters;
43 import cz.cuni.amis.pogamut.ut2004.bot.state.impl.BotStateHelloBotReceived;
44 import cz.cuni.amis.pogamut.ut2004.bot.state.impl.BotStateInited;
45 import cz.cuni.amis.pogamut.ut2004.bot.state.impl.BotStatePassword;
46 import cz.cuni.amis.pogamut.ut2004.bot.state.impl.BotStateSendingInit;
47 import cz.cuni.amis.pogamut.ut2004.bot.state.impl.BotStateSpawned;
48 import cz.cuni.amis.pogamut.ut2004.communication.messages.gbcommands.Configuration;
49 import cz.cuni.amis.pogamut.ut2004.communication.messages.gbcommands.DisconnectBot;
50 import cz.cuni.amis.pogamut.ut2004.communication.messages.gbcommands.Initialize;
51 import cz.cuni.amis.pogamut.ut2004.communication.messages.gbcommands.PasswordReply;
52 import cz.cuni.amis.pogamut.ut2004.communication.messages.gbcommands.Ready;
53 import cz.cuni.amis.pogamut.ut2004.communication.messages.gbcommands.Respawn;
54 import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.BotKilled;
55 import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.ConfigChange;
56 import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.EndMessage;
57 import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.GameInfo;
58 import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.HelloBotHandshake;
59 import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.InitedMessage;
60 import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.Password;
61 import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.Self;
62 import cz.cuni.amis.pogamut.ut2004.communication.translator.shared.events.InitCommandRequest;
63 import cz.cuni.amis.pogamut.ut2004.communication.translator.shared.events.ReadyCommandRequest;
64 import cz.cuni.amis.utils.ExceptionToString;
65 import cz.cuni.amis.utils.NullCheck;
66 import cz.cuni.amis.utils.exception.PogamutException;
67 import cz.cuni.amis.utils.exception.PogamutJMXException;
68
69
70
71
72
73
74
75
76 @AgentScoped
77 public class UT2004Bot<WORLD_VIEW extends IVisionWorldView, ACT extends IAct, CONTROLLER extends IUT2004BotController> extends AbstractAgent3D<WORLD_VIEW, ACT> implements IUT2004Bot {
78
79
80
81
82 private CONTROLLER controller;
83
84
85
86
87 private BusAwareCountDownLatch endMessageLatch;
88
89
90
91
92 private boolean botStoppedCalled = false;
93
94 private EventReact<HelloBotHandshake> helloBotReaction;
95
96
97
98
99 private UT2004BotParameters params;
100
101
102
103
104
105
106
107
108
109
110 @Inject
111 public UT2004Bot(UT2004BotParameters parameters, IComponentBus eventBus, IAgentLogger logger, IWorldView worldView, IAct act, IUT2004BotController init) {
112 super(parameters.getAgentId(), eventBus, logger, (WORLD_VIEW)worldView, (ACT)act);
113
114 this.params = parameters;
115 this.controller = (CONTROLLER) init;
116 NullCheck.check(this.controller, "init");
117 if (log.isLoggable(Level.FINER)) log.finer("Initializing the controller...");
118 this.controller.initializeController(this);
119 if (log.isLoggable(Level.FINER)) log.finer("Preparing the controller...");
120 this.controller.prepareBot(this);
121 if (log.isLoggable(Level.FINE)) log.fine("Controller initialized.");
122
123 helloBotReaction = new EventReact<HelloBotHandshake>(HelloBotHandshake.class, worldView) {
124 @Override
125 protected void react(HelloBotHandshake event) {
126 if (event.isServerFull()) throw new ComponentCantStartException("Server is full.", UT2004Bot.this);
127 }
128 };
129
130 getWorldView().addEventListener(ReadyCommandRequest.class, readyCommandRequestListener);
131 getWorldView().addEventListener(InitCommandRequest.class, initCommandRequestListener);
132 getWorldView().addEventListener(Password.class, passwordRequestedListener);
133 getWorldView().addObjectListener(InitedMessage.class, WorldObjectUpdatedEvent.class, initedMessageListener);
134 getWorldView().addEventListener(BotKilled.class, killedListener);
135
136
137
138 endMessageLatch = new BusAwareCountDownLatch(1, getEventBus(), getWorldView());
139 }
140
141
142
143
144
145 public CONTROLLER getController() {
146 return controller;
147 }
148
149
150
151
152
153
154
155
156
157 public UT2004BotParameters getParams() {
158 return params;
159 }
160
161
162
163
164
165
166
167 @Override
168 protected void startAgent() {
169 botStoppedCalled = false;
170 super.startAgent();
171 getWorldView().addEventListener(EndMessage.class, endListener);
172 if (log.isLoggable(Level.INFO)) log.info("Waiting for the handshake to finish for 60s.");
173 if (!endMessageLatch.await(60000, TimeUnit.MILLISECONDS)) {
174 throw new ComponentCantStartException("The bot did not received first EndMessage in 60 seconds.", this);
175 }
176 if (log.isLoggable(Level.INFO)) log.info("Handshake finished.");
177 }
178
179 @Override
180 protected void startPausedAgent() {
181 botStoppedCalled = false;
182 super.startPausedAgent();
183 getWorldView().addEventListener(EndMessage.class, endListener);
184 if (log.isLoggable(Level.INFO)) log.info("Waiting for the handshake to finish for 60s.");
185 if (!endMessageLatch.await(60000, TimeUnit.MILLISECONDS)) {
186 throw new ComponentCantStartException("The bot did not received first EndMessage in 60 seconds.", this);
187 }
188 if (log.isLoggable(Level.INFO)) log.info("Handshake finished.");
189 }
190
191 @Override
192 protected void preStopAgent() {
193 super.preStopAgent();
194 try {
195 tryDisconnect();
196 } catch (Exception e) {
197 }
198 }
199
200 @Override
201 protected void stopAgent() {
202 try {
203 if (!botStoppedCalled) {
204 botStoppedCalled = true;
205 controller.botShutdown();
206 }
207 } finally {
208 try {
209 removeBotDisconnector();
210 } finally {
211 try {
212 super.stopAgent();
213 } finally {
214 endMessageLatch = new BusAwareCountDownLatch(1, getEventBus(), getWorldView());
215 }
216 }
217 }
218 }
219
220 @Override
221 protected void preKillAgent() {
222 super.preKillAgent();
223 try {
224 tryDisconnect();
225 } catch (Exception e) {
226 }
227 }
228
229 @Override
230 protected void killAgent() {
231 try {
232 if (!botStoppedCalled) {
233 botStoppedCalled = true;
234 controller.botShutdown();
235 }
236 } finally {
237 try {
238 removeBotDisconnector();
239 } finally {
240 try {
241 super.killAgent();
242 } finally {
243 endMessageLatch = new BusAwareCountDownLatch(1, getEventBus(), getWorldView());
244 }
245 }
246 }
247 }
248
249
250
251
252 protected Thread botDisconnectorThread;
253
254
255
256
257 protected void tryDisconnect() {
258 try {
259 DisconnectBot cmd = new DisconnectBot();
260 try {
261 log.info("Sending " + cmd + " to destroy bot inside UT2004.");
262 } finally {
263 getAct().act(cmd);
264 Thread.sleep(1000);
265 }
266 } catch (Exception e) {
267 log.warning(ExceptionToString.process("Failed to disconnect the bot, we hope that GB2004 will remove it when the socket gets closed.", e));
268 }
269 }
270
271
272
273
274 protected void addBotDisconnector() {
275 if (botDisconnectorThread == null) {
276 botDisconnectorThread = new Thread(
277 new Runnable() {
278 @Override
279 public void run() {
280 tryDisconnect();
281 }
282 },
283 getName() + "-Disconnector"
284 );
285 try {
286 Runtime.getRuntime().addShutdownHook(botDisconnectorThread);
287 } catch (Exception e) {
288 throw new PogamutException("Failed to add BotDisconnectorThread as a JVM shutdown hook.", e, this);
289 }
290 }
291 }
292
293
294
295
296 protected void removeBotDisconnector() {
297 if (botDisconnectorThread != null) {
298 try {
299 Runtime.getRuntime().removeShutdownHook(botDisconnectorThread);
300 } catch (Exception e) {
301 log.warning(ExceptionToString.process("Failed to remove BotDisconnectorThread as a JVM shutdown hook.", e));
302 }
303 botDisconnectorThread = null;
304 }
305 }
306
307
308
309
310
311
312
313
314
315
316
317 protected void readyCommandRequested() {
318 getAct().act(new Ready());
319 }
320
321
322
323
324
325 private IWorldEventListener<ReadyCommandRequest> readyCommandRequestListener =
326 new IWorldEventListener<ReadyCommandRequest>() {
327
328 @Override
329 public void notify(ReadyCommandRequest event) {
330 controller.getLog().setLevel(Level.ALL);
331 setState(new BotStateHelloBotReceived("GameBots2004 greeted us, adding custom listeners onto the worldview."));
332 readyCommandRequested();
333 setState(new BotStateHelloBotReceived("READY sent, handshaking."));
334 }
335 };
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350 protected void initCommandRequested() {
351 Initialize initializeCommand = getController().getInitializeCommand();
352 if (initializeCommand == null) {
353 throw new AgentException("getBotInit().getInitializeCommand() method returned null message, can't initialize the agent!", log, this);
354 }
355 if(initializeCommand.getName() == null) {
356
357 initializeCommand.setName(getComponentId().getName().getFlag());
358 } else {
359
360 getComponentId().getName().setFlag(initializeCommand.getName());
361 }
362 if (initializeCommand.getTeam() == null) {
363 initializeCommand.setTeam(params.getTeam());
364 }
365 try {
366
367 initializeCommand.setJmx(getJMX().enableJMX());
368 } catch (Exception e) {
369 throw new PogamutJMXException("Error seting up JMX name of the agent.", e, log, this);
370 }
371
372 getAct().act(initializeCommand);
373 }
374
375
376
377
378
379 private IWorldEventListener<InitCommandRequest> initCommandRequestListener =
380 new IWorldEventListener<InitCommandRequest>() {
381 @Override
382 public void notify(InitCommandRequest event) {
383 setState(new BotStateSendingInit("Handshake over, sending INIT."));
384 initCommandRequested();
385 setState(new BotStateSendingInit("Handshake over, INIT sent."));
386 }
387 };
388
389
390
391
392
393
394
395
396
397
398
399 private IWorldEventListener<Password> passwordRequestedListener =
400 new IWorldEventListener<Password>() {
401 @Override
402 public void notify(Password event) {
403 setState(new BotStatePassword("Password requested by the world."));
404 PasswordReply passwordReply = getController().getPassword();
405 if (passwordReply == null) {
406 if (log.isLoggable(Level.WARNING)) log.warning("createPasswordReply() returned null");
407 passwordReply = new PasswordReply("");
408 }
409 if (log.isLoggable(Level.INFO)) log.info("Password required for the world, replying with '" + passwordReply.getPassword() + "'.");
410 getAct().act(passwordReply);
411 setState(new BotStatePassword("Password sent."));
412 }
413 };
414
415
416
417
418
419
420
421
422
423
424 private IWorldObjectEventListener<InitedMessage, WorldObjectUpdatedEvent<InitedMessage>> initedMessageListener =
425 new IWorldObjectEventListener<InitedMessage, WorldObjectUpdatedEvent<InitedMessage>>() {
426
427 @Override
428 public void notify(WorldObjectUpdatedEvent<InitedMessage> event) {
429 setState(new BotStateInited("InitedMessage received, calling botInitialized()."));
430 controller.botInitialized(getWorldView().getSingle(GameInfo.class), getWorldView().getSingle(ConfigChange.class), event.getObject());
431 setState(new BotStateInited("Bot initialized."));
432 }
433 };
434
435
436
437
438
439
440
441
442
443
444 private IWorldEventListener<BotKilled> killedListener =
445 new IWorldEventListener<BotKilled>() {
446 @Override
447 public void notify(BotKilled event) {
448 getController().botKilled(event);
449 }
450 };
451
452
453
454
455
456
457 private IWorldEventListener<EndMessage> endListener =
458 new IWorldEventListener<EndMessage>() {
459
460 @Override
461 public void notify(EndMessage event) {
462 setState(new BotStateSpawned("First batch of informations received - calling botSpawned()."));
463 controller.botFirstSpawn(getWorldView().getSingle(GameInfo.class), getWorldView().getSingle(ConfigChange.class), getWorldView().getSingle(InitedMessage.class), getWorldView().getSingle(Self.class));
464 setState(new BotStateSpawned("botSpawned() finished, finalizing controller initialization..."));
465 controller.finishControllerInitialization();
466 setState(new BotStateSpawned("finishControllerInitialization() finished, UT2004Bot is running."));
467 getWorldView().removeEventListener(EndMessage.class, this);
468 endMessageLatch.countDown();
469 }
470
471 };
472
473
474
475
476 public Location getLocation() {
477 Self self = getWorldView().getSingle(Self.class);
478 if (self != null) {
479 return self.getLocation();
480 }
481 return null;
482 }
483
484
485
486
487 public Rotation getRotation() {
488 Self self = getWorldView().getSingle(Self.class);
489 if (self != null) {
490 return self.getRotation();
491 }
492 return null;
493 }
494
495
496
497
498 public Velocity getVelocity() {
499 Self self = getWorldView().getSingle(Self.class);
500 if (self != null) {
501 return self.getVelocity();
502 }
503 return null;
504 }
505
506 public void respawn() throws PogamutException {
507 getAct().act(new Respawn());
508 }
509
510 @Override
511 protected AgentJMXComponents createAgentJMX() {
512 return new AgentJMXComponents<IUT2004Bot>(this) {
513
514 @Override
515 protected AgentMBeanAdapter createAgentMBean(ObjectName objectName, MBeanServer mbs) throws MalformedObjectNameException, InstanceAlreadyExistsException, InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException {
516 return new BotJMXMBeanAdapter(UT2004Bot.this, objectName, mbs);
517 }
518 };
519 }
520
521 public void setBoolConfigure(BoolBotParam param, boolean value) {
522 try {
523 Configuration configuration = new Configuration();
524
525 ConfigChange confCh = getWorldView().getSingle(ConfigChange.class);
526 configuration.copy(confCh);
527
528 param.set(configuration, value);
529 param.setField(confCh, value);
530 getAct().act(configuration);
531 } catch (Exception ex) {
532
533
534 Logger.getLogger(UT2004Bot.class.getName()).log(Level.SEVERE, null, ex);
535 }
536 }
537
538 public boolean getBoolConfigure(BoolBotParam param) {
539 try {
540 return param.get(getWorldView().getSingle(ConfigChange.class));
541 } catch (Exception ex) {
542
543
544 Logger.getLogger(UT2004Bot.class.getName()).log(Level.SEVERE, null, ex);
545 return false;
546 }
547 }
548
549 @Override
550 protected Folder createIntrospection() {
551 return new ReflectionObjectFolder(AbstractAgent.INTROSPECTION_ROOT_NAME, controller);
552 }
553
554 @Override
555 public WORLD_VIEW getWorldView() {
556 return super.getWorldView();
557 }
558
559 }