Tutorial body

Note: This tutorial can be applied to PogamutUT2004 and will mostly hold for PogamutUDK examples.

The outline:

Setting up the example

This example is installed by Pogamut UT2004 installer. In NetBeans click New Project -> Maven -> Project From Archetype -> Local Archetypes Catalog and select 01-responsive-bot-archetype project. Moreover, as Pogamut 3 has been fully mavenized you can try and run this example even without installing the Pogamut NetBeans plugin. However in that case you won't be able to use visualization as this is a part of Pogamut NetBeans plugin. To open up this example in NetBeans follow up the steps in Opening Pogamut Examples chapter (if the archetype is not present, follow "adding new Pogamut example project" section in the same chapter). This archetype information is below.

For UT2004 example:

  • Group Id: cz.cuni.amis.pogamut.ut2004.examples

  • Artifact Id: 01-responsive-bot-archetype

  • Version: 3.3.1

  • Repository:http://diana.ms.mff.cuni.cz:8081/artifactory/repo

For UDK example only change Group Id: to cz.cuni.amis.pogamut.udk.examples and Version: 3.2.5-SNAPSHOT . The rest remains the same.

Note: You will find up-to-date list of available archetypes in Pogamut Maven archetypes catalog

In previous tutorial we have shown how to issue commands using IAct interface or by command modules (body). In this tutorial we will learn how to create a simple bot that will be able to sense the surrounding world and to react to several kinds of events that can occur. The bot behavior will be completely event-driven, i.e., we won't utilize logic() method. The events we will react to are:

To handle these events we have to have some mechanism that will notify us about changes in the environment. In general, there are two+one ways how the bot can detect a change:

The listener design pattern should be used in favour of active waiting since it is usually more computationaly effective. However sometimes the underlying API doesn't provide a way for registering listeners on every possible event so active waiting is the only choice, this is usually true for all custom variables you declare inside your bot as Java does not allow you to sense changes on arbitrary variable. This tutorial present only the last two approaches, i.e., how to utilize listeners to various events.

Before we will inspect the source code we will observe bot's behavior in game:

  1. Start UT.

  2. If you are in spectator mode (this will happen if you start UT from Netbeans) then press Esc and click Join button, now you are connected as a standard player.

  3. Find the bot and move close to him, these messages will be printed:

    1. "Hello {YOUR_NAME}!"

    2. "What do you want {YOUR_NAME}?"

    If you don't see the messages, you have probably chat visualization turned off in UT2004. However, you will always see the messages in console (press tilde (~) character) or in the text bubble that can be turned on by pressing CTRL + U.

  4. Now bump to the bot, he will move away from you.

  5. If you do not have any weapon then find some and return back to the bot (you can switch between multiple weapons using mouse wheel or number keys on your keyboard).

  6. Shoot the bot, he will move to avoid the fire.

IWorldView abstraction

Before we will register our first listener, we have to understand Pogamut's abstraction of the world where the bot lives. Bot's view of the world is provided through the IWorldView interface. This interface serves as both bot's senses and a simple memory. The abstraction used by IWorldView the world is represented by:

  • Objects (IWorldObject) – eg. players, items etc.

  • Events (IWorldEvent) – eg. bot heard a noise, bot bumped to a wall etc. Events are divided into two categories:

    • Object events (IWorldObjectEvent) – there are five events of this type:

      • First encountered (WorldObjectFirstEncounteredEvent) – raised when the bot encounters some object (eg. Player) for the first time.

      • Appeared (WorldObjectAppearedEvent) – object entered bot's field of view.

      • Updated (WorldObjectUpdatedEvent) – object's state was updated.

      • Disappeared (WorldObjectDisappearedEvent) – object disappeared from bot's field of view.

      • Destroyed (WorldObjectDestroyedEvent) – object was destroyed (e.g., the player has been disconnected from the game).

    • Events not associated with any object (plain IWorldEvent) – example of such event can be HearNoise that is raised when the bot hears some noise or BotBumped that is sent whenever the bot bumps into something (or someone bumps to the bot).

IWorldView interface together with IAct represent the basic API for accessing the world, hence you should get familiar with them.

Registering listeners

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 in the example 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.