Tutorial body

In previous tutorial we have shown how to issue commands using IAct interface. 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. These kinds of events 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 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 tutorial presents both approaches.

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}?"

      <title></title>

      <title></title>

  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. In 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 (eg. player 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.

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

Registering listeners

To see how the listeners are registered on bot's world view, find postPrepareBot() method. In the body of this method there are three calls adding different listener types.

    protected void postPrepareBot(GameInfo info) {

        // register all listeners on the WorldView
        getWorldView().addEventListener(Bumped.class, bumpedHandler);
        getWorldView().addObjectListener(Player.class, WorldObjectAppearedEvent.class, playerAppearedHandler);
        getWorldView().addObjectListener(Player.class, WorldObjectUpdatedEvent.class, playerUpdatedHandler);
    }

Simillarly to getAct() the getWorldView() returns the IWorldView implementation associated with this bot. First line adds a listener for Bumped event, this event is raised when the bot hits a wall or some other obstacle. The listener itself is referenced by bumpedHandler variable. Second and third line adds object event listeners. Both listeners are on objects of type Player, the second listener will be called each time the player was updated (eg. when it moved), and the third when player enters bot's field of view. Note that besides add*Listener(...) methods there are also remove*Listener(...) methods that let you stop listening for an event.

Now let's explore implementation of listeners that were just registered. Click the bumpedHandler variable while holding Ctrl key to see the listener definition:

    /**
     * Listener called when someone/something bumps into the bot. The bot
     * responds by moving in the opposite direction than the bump come from.
     */
    IWorldEventListener<Bumped> bumpedHandler = new IWorldEventListener<Bumped>() {

        public void notify(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(getLocation()).scale(5);
            Location target = getLocation().sub(v);

            // make the bot to go to the computed location while facing the bump source
            getAct().act(new Move().setFirstLocation(target).setFocusTarget(event.getId()));
        }
    };

The bumpedHandler variable is of type IWorldEventListener parameterized by the Bumped 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 simple vector mathematics. Bot is made to move in an opposite direction to the object that caused the bump event.

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 playerAppearedHandler definition.

    /**
     * Listener called when a player appears.
     */
    IWorldObjectListener<Player, WorldObjectAppearedEvent<Player>> playerAppearedHandler =
        new IWorldObjectListener<Player, WorldObjectAppearedEvent<Player>>() {

        public void notify(WorldObjectAppearedEvent<Player> event) {
             // greet player when he appears
            getAct().act(new SendMessage().setText("Hello " + event.getObject().getName() + "!"));
        }

    };

You can see that each time a player appears, he is greeted by our bot. You should already be familiar with the getAct()... construct. The only new code is event.getObject().getName(). First method call – event.getObject() – returns an object that has just appeared, in this case Player instance, and the getName() call returns the player's name. The result is that the bot greets player with his name. Now inspect the last listener:

   /**
    * Listener called each time a player is updated.
    */
   IWorldObjectListener<Player, WorldObjectUpdatedEvent<Player>> playerUpdatedHandler =
       new IWorldObjectListener<Player, WorldObjectUpdatedEvent<Player>>() {

       /**
       * Flag indicating whether the player was also close to the bot last
       * time it was updated.
       */
       boolean wasCloseBefore = false;

       public void notify(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();
           if (player.getLocation().getDistance(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) {
                   getAct().act(
                           new SendMessage().setText("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, eg. when it changes location or any other associated property. 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.

Active waiting

Responsive bot's doLogic() method demonstrates the second approach that can be taken in event detection – active waiting. Active waiting follows this general schema:

  1. store value of variable X to variable lastX.

  2. periodically evaluate X != last, if it is true then:

    • raise event "X has changed",

    • lastX = X,

    • continue with step 2.

That is exaclty what happens in doLogic(), where we want to detect health decrease, when the health decreases the bot will respond by running away to a safe location. In order to fully understand this code we have to discuss querying of objects using the IWorldView interface.

Querying objects from IWorldView

So far we have used the IWorlView only for listening for changes, but the doLogic() method introduces another important use case – retrieving lists of various objects present in the world.

    protected void doLogic() throws PogamutException {
        1Self self = getWorldView().getSingle(Self.class);
        int health = self.getHealth();
        if (health < lastHealth) {
            // the bot was injured

            // find second nearest nav point and run to it
            double first = Double.MAX_VALUE;
            double second = Double.MAX_VALUE;
            double dist = 0;
            NavPoint fisrtNav = null, secondNav = null;

            for (NavPoint nav : 2getWorldView().getAll(NavPoint.class).values()) {
                dist = self.getLocation().getDistance(nav.getLocation());
                if (dist < first) {
                    second = first;
                    secondNav = fisrtNav;
                    first = dist;
                    fisrtNav = nav;
                } else {
                    if (dist < second) {
                        second = dist;
                        secondNav = nav;
                    }
                }
            }
            // always check even for improbable conditions
            if (secondNav == null) {
                throw new PogamutException("No second closest navpoint found. there are too few navpoints", this);
            }

            // issue the command that will make the bot run
            getAct().act(new Move().setFirstLocation(secondNav.getLocation()));
            lastHealth = health;
        }
    }

In this method we periodically check, whether the bot was injured (his health was decreased) and if so, we presume that this happened because someone fired on the bot and we will try to respond by running to the second closest known safe location. We choose the second location, because it is likely that we are standing on the first closest location so choosing the closest one will in most cases result in standing on the same place.

We can see two distinct uses of IWorldView for retrieving object:

???

We use call getWorldView().getSingle(Self.class) to get the only instance of Self object that should be present in the world view. If you will issue this call to get an object that has more instances in the world view an IllegalArgumentException will be thrown. The Self is one of the most important world objects. It represents the bot's body, it holds its location, velocity, rotation, name, health, adrenaline etc. We use it here to retrieve agent's health.

???

getWorldView().getAll(NavPoint.class).values() shows how to get a list of multiple objects of the same type. In this example we want to get the list of all NavPoints, getWorldView().getAll(NavPoint.class) returns a standard java.util.Map where the keys are UnrealIds and values are NavPoints. Finally values() call returns plain Collection with values present in the Map.