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