There are two ways how to register listeners. Either you may annotate any method with one of the annotation
EventListener
, ObjectEventListener
, ObjectClassEventListener
,
ObjectClassEventListener
or ObjectListener
, or you may manually
register a listener via IWorldView
interface using one of the getWorldView().addEventListener()
or
getWorldView().addObjectListener()
methods.
To see how the listeners are registered on bot's world view, find
prepareBot()
method. In the body of this
method there is an example of manual listener registration.
public void prepareBot(UT2004Bot bot) { // register the botDamagedListener that we have previously created getWorldView().addEventListener(BotDamaged.class, botDamagedListener); }
Simillarly to getAct()
the
getWorldView()
returns the
IWorldView
implementation associated with this
bot. The first line adds a listener for BotDamaged
event,
this event is raised when the bot is hurt by somebody or something.
The listener itself is referenced by botDamagedListener
variable.
Now let's explore implementation of the listener that was just
registered. Click the botDamagedListener
variable while
holding Ctrl key to see the listener definition:
/** * Listener that is manually created and manually hooked to the {@link ResponsiveBot#getWorldView()} * via {@link IWorldView#addEventListener(Class, IWorldEventListener)} method * inside {@link ResponsiveBot#prepareBot(UT2004Bot)}. */ IWorldEventListener<BotDamaged> botDamagedListener = new IWorldEventListener<BotDamaged>() { @Override public void notify(BotDamaged event) { // the bot was injured - let's move around the level and hope that we will find a health pack // note that we have to acquire "SECOND" nearest navpoint, as the first one is the navpoint we're standing at NavPoint secondNav = DistanceUtils.getSecondNearest(getWorldView().getAll(NavPoint.class).values(), info.getLocation()); // always check even for improbable conditions if (secondNav == null) { // try to locate some navpoint move.turnVertical(30); } else { // move to it move.moveTo(secondNav); } } };
The botDamagedListener
variable is of type
IWorldEventListener
parameterized by the
BotDamaged
class.
IWorldEventListener
interface declares one
abstract method notify(...)
that is called each
time the event occurs with the event as the method's parameter. Body of
this method implements a way how to find the second nearest navpoint and run to it in hope
it will be reachable and far from the danger.
Now return back to the code block showing the listeners'
registration. You can press Alt + ← to get to the
previous position in the source code. Then again use Ctrl +
LMB to go to the botDamagedListener
definition.
Now for the second way of listener registration - more easier - annotations. There are three methods
that are called when some event occurs: bumped(Bumped event)
,
playerAppeared(WorldObjectAppearedEvent<Player> event)
and
playerUpdated(WorldObjectUpdatedEvent<Player> event)
. Let's check how this magic
- auto method calling - happens.
The class UT2004BotModuleController
that is an ancestor of the ResponsiveBot
(and all other examples as well) is auto-initializing the AnnotationListenerRegistrator
. Now,
because you know how we may manually hook a custom listener to the world view, you might already have guessed what
the registrator is doing. It simply iterates through all methods the ResponsiveBot
is declaring
and searches for methods annotated either with EventListener
or ObjectEventListener
or ObjectClassEventListener
or
ObjectClassEventListener
or ObjectListener
annotation (that is the
magical @EventLister that is present above bumped(Bumped event)
method for instance).
Every such annotation refers to a certain category of events (it corresponds with methods of the world view that
you may manually use to register a listener):
EventListener
- reacts to one arbitrary IWorldEvent
that
is defined via eventClass
field of the annotation. I.e., to BotDamaged
or Bumped
events.
ObjectClassListener
- reacts to all events that happens on the IWorldObject
of the certain class that is defined via objectClass
field of the annotation. I.e.,
it allows you to receive all events regarding some object (as desribed earlier in the text - WorldObjectFirstEncountered
, ...).
ObjectClassEventListener
- reacts to events of the certain class that happens
on any IWorldObject
of a certain class. I.e., you may for instance react only to
WorldObjectUpdated
events that happens over objects of the Player
class,
such as the playerUpdated(WorldObjectUpdatedEvent<Player> event)
method is doing.
ObjectListener
- reacts to all events that happens over the certain object
that is identified by the string id (of course this is hard to use as it is hard to obtain the correct
string id, but we leave it there for the completness of the annotation solution).
ObjectEventListener
- reacts to all events of the certain class that happens
over the certain object that is identified by the string id (again, hard to use, but...)
For the concrete implementations:
/** * Listener called when someone/something bumps into the bot. The bot * responds by moving in the opposite direction than the bump come from. * * We're using {@link EventListener} here that is registered by the {@link AnnotationListenerRegistrator} to listen * for {@link Bumped} events. */ @EventListener(eventClass = Bumped.class) protected void bumped(Bumped event) { // schema of the vector computations // // e<->a<------>t // | | v | // | | target - bot will be heading there // | getLocation() // event.getLocation() Location v = event.getLocation().sub(bot.getLocation()).scale(5); Location target = bot.getLocation().sub(v); // make the bot to go to the computed location while facing the bump source move.strafeTo(target, event.getLocation()); }
The bumped(Bumped event)
method is
annotated by the @EventListener(eventClass = Bumped.class)
that means
that the method gets called whenever the Bumped
message is sent by the GameBots2004
to the bot (note how the field eventClass
inside the annotation is used to specify which
event you want to listen to and then the same class bumped
appears in the method
declaration.
The method just performs simple vector arithmetic to obtain a vector where to run to to stay away from the source of the collision.
/** * Listener called when a player appears. * * We're using {@link ObjectClassEventListener} here that is registered by the {@link AnnotationListenerRegistrator} * to listen on all {@link WorldObjectAppearedEvent} that happens on any object of the class {@link Player}. I.e., * whenever the GameBots2004 sends an update about arbitrary {@link Player} in the game notifying us that the player * has become visible (it's {@link Player#isVisible()} is switched to true and the {@link WorldObjectAppearedEvent} * is generated), this method is called. */ @ObjectClassEventListener(eventClass = WorldObjectAppearedEvent.class, objectClass = Player.class) protected void playerAppeared(WorldObjectAppearedEvent<Player> event) { // greet player when he appears body.getCommunication().sendGlobalTextMessage("Hello " + event.getObject().getName() + "!"); }
The playerAppeared(WorldObjectAppearedEvent<Player> event)
method is
annotated by the @ObjectClassEventListener(eventClass = WorldObjectAppearedEvent.class, objectClass = Player.class)
again see how the fields eventClass
and objectClass
are used and how they correspond
with the method parameters declaration.
The method is truly simple. You can see that each time a player appears, he is greeted by our
bot. You should already be familiar with the
body
module that contains the most commands for the bot.
Now inspect the last listener:
/** * Listener called each time a player is updated. * * Again, we're using {@link ObjectClassEventListener} that is registered by the {@link AnnotationListenerRegistrator} * to listen on all {@link WorldObjectUpdatedEvent} that happens on any object of the class {@link Player}. I.e., * whenever the GameBots2004 sends an update about arbitrary {@link Player} in the game notifying us that some information * about the player has changed (the {@link WorldObjectUpdatedEvent} is generated), this method is called. */ @ObjectClassEventListener(eventClass = WorldObjectUpdatedEvent.class, objectClass = Player.class) protected void playerUpdated(WorldObjectUpdatedEvent<Player> event) { // Check whether the player is closer than 5 bot diameters. // Notice the use of the UnrealUtils class. // It contains many auxiliary constants and methods. Player player = event.getObject(); // First player objects are received in HandShake - at that time we don't have Self message yet or players location!! if (player.getLocation() == null || info.getLocation() == null) return; if (player.getLocation().getDistance(info.getLocation()) < (UnrealUtils.CHARACTER_COLLISION_RADIUS * 10)) { // If the player wasn't close enough the last time this listener was called, // then ask him what does he want. if (!wasCloseBefore) { body.getCommunication().sendGlobalTextMessage("What do you want " + player.getName() + "?"); // Set proximity flag to true. wasCloseBefore = true; } } else { // Otherwise set the proximity flag to false. wasCloseBefore = false; } }
This listener is called each time a player is updated, it uses the same annotation as the previous one
with different arguments. When the player that is visible changes it locatuion (or any other associated property)
this method gets called. When the update event
is raised we check whether the player is closer than certain threshold,
in this case UnrealUtils.CHARACTER_COLLISION_RADIUS * 10
.
If this condition holds and it didn't hold last time the listener was
called then it means that the player came closer to the bot than he was
a while before. In that case the bot will ask him what does he
want.