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