GAMEON

Hands-on experiment building microservices and cloud native applications

Enhancing a room with items

Where we learn about how to add items to your room.

Overview

This adventure teaches you about adding virtual items to your room in Game On, taking you through understanding the required parts of the protocol and some ideas on how to handle commands. You’ll learn how to have items show up in your Room Description and how to support simple commands that interact with them.

Why Virtual Items?

Game On presents itself to users as a text-based adventure game. Within text adventures, the concepts of items and inventory are common. The classic /take sword and /kill dragon with sword are great examples of how to make a room feel more like an interactive experience and less like a technology demonstration.

For example, when you visit RecRoom in Game On (/sos, /teleport RecRoom), you see how it uses items to construct a puzzle that keep users in the room entertained. Items in rooms are used in many ways, you could have them represent remote services, or just be a signpost with instructions for how to interact with your room.

In this adventure, we add a Button to the room and have it respond when the user pushes it.

Prerequisites

This adventure doesn’t require any prerequisites and may be attempted directly after completing the Java Sample Room.

Walkthrough

Protocol

Game On Rooms support the concepts of 'items' via the websocket protocol. This means there is a difference between sending a Room Description that has the items embedded in the description text..

And a description that leaves them out and declares them to the room via the protocol..

To understand how to do this, we need to look at the Game On Websocket Protocol..

The particular message we are interested in is the 'location' message, which carries the Room Description and other information back to Game On. It is this "other information" that we may use to tell the game about items in the room.

The location message is documented like this:

player,<playerId>,{
    "type": "location",
    "name": "Room name",
    "fullName": "Room's descriptive full name",
    "description": "Lots of text about what the room looks like",
    "exits": {
        "shortDirection" : "currentDescription for Player",
        "N" :  "a dark entranceway"
    },
    "commands": {
        "/custom" : "Description of what command does"
    },
    "roomInventory": ["itemA","itemB"]
}

At the end is the "roomInventory" field which may be used to pass an Array of Strings that represent the items in the room. When the game renders the list in the web interface, it looks similar to this:

Room's descriptive full name
----------------------------
Lots of text about what the room looks like

You notice:
o itemA
o itemB

The itemA, and itemB have been called out by the the game’s web interface, because they were present in the roomInventory part of the location message from the Room.

Java Sample Room

Within the sample room, the location message is built from details in the RoomDescription object, by the RoomImplementation object, using the Message object’s utility method createLocationMessage.

Here we see the method createLocationMessage building up the response to send..

public static Message createLocationMessage(String userId, RoomDescription roomDescription) {

    JsonObjectBuilder payload = Json.createObjectBuilder();
    payload.add(TYPE, "location");
    payload.add("name", roomDescription.getName());
    payload.add("fullName", roomDescription.getFullName());
    payload.add("description", roomDescription.getDescription());

    // convert map of commands into JsonObject
    JsonObject commands = roomDescription.getCommands();
    if ( !commands.isEmpty()) {
        payload.add("commands", commands);
    }

    // Convert list of items into json array
    JsonArray inventory = roomDescription.getInventory();
    if ( !inventory.isEmpty()) {
        payload.add("roomInventory", inventory);
    }

    return new Message(Target.player, userId, payload.build().toString());
}

We see that the location message is built as JSON and the items are being read from the RoomDescription object passed as an argument.

The RoomDescription object offers the methods

   public void addItem(String itemName);
   public void removeItem(String itemName);
   public JsonArray getInventory();

Which would allow a Room implementation to add/remove items from the room, and thus from the description returned to the user.

Over in the RoomImplementation we see the simple processCommand method, that parses the input from the user and carries out the appropriate action. In this case, we’re interested in the /look command, which should trigger a location response.

Sure enough, there within the switch statement, we see a location message being built & returned to the user.

  case "/look":
  case "/examine":
      // See RoomCommandsTest#testHandle*Look*

      // Treat look and examine the same (though you could make them do different things)
      if ( remainder == null || remainder.contains("room") ) {
          // This is looking at or examining the entire room. Send the player location message,
          // which includes the room description and inventory
          endpoint.sendMessage(session, Message.createLocationMessage(userId, roomDescription));
      } else {
          endpoint.sendMessage(session,
                  Message.createSpecificEvent(userId, LOOK_UNKNOWN));
      }
      break;

If we wanted to add additional behavior, perhaps to support /examine itemName, this is where we could add it. Either as an extension to the switch block handling /examine and /look, or via an entirely new command. If the item were a button, we might like to add /push button as a command and send an appropriate response.

Let’s look at adding that button now.

Adding our own item.

Firstly, find the postConstruct method in the roomImplementation, and before the last log statement, add..

roomDescription.addItem("button");

Then locate the switch statement within the processCommand method. Add a little code so that the 'else' block in the /look and /examine case, that used to look like:

Existing Sample Code
  } else {
      endpoint.sendMessage(session,
              Message.createSpecificEvent(userId, LOOK_UNKNOWN));
  }

is updated to look like:

Replaced Sample Code
  } else {
      if(remainder.contains("button"){
          endpoint.sendMessage(session,
              Message.createBroadcastEvent(username+" examines the button",
                              userId, "It's a big red button, you are very tempted to..."));
      }else{
          endpoint.sendMessage(session,
              Message.createSpecificEvent(userId, LOOK_UNKNOWN));
      }
  }

Finally, lets add a little code to handle the /push command for our button. Go back to that postConstruct method, and below your addItem("button") line add:

roomDescription.addCommand("/push","Pushes an item, like, a button?");

That causes the room description to add our custom command to the location response, so any user doing /help in the room will see /push described as a command.

Now, back in the switch statement within the processCommand method, add a new switch block that looks like…​

case "/push":
    // Handle the push command, response depends if user pushes button, or anything else.
    if ( remainder.contains("button") ) {
            endpoint.sendMessage(session,
                Message.createBroadcastEvent(username+" pushes the button. Nothing Happens. Surprising.",
                                              userId, "You push the big red button."));

    } else {
        endpoint.sendMessage(session, Message.createSpecificEvent(userId, "What do you want to push?));
    }
    break;

That bit is invoked when the first word of the input is /push with remainder set to whatever the rest of the command was. If the user did /push button or /push the button etc, we’ll send them a message saying they pushed the button and send everyone else a message saying Nothing Happened. If the user only does /push by itself, we prompt them they should probably say what they want to push.

Suggested extensions

  • Add a novelty 'mystical fortune telling ball' that gives random fortunes when shaken.
  • The parsing approach here is crude, consider how you could design a framework to support multiple items, each offering their own commands, and help text, and having an effect.
  • Could you add/remove an item to the room dynamically at runtime? (remember the caching in RoomDescription)
    • Perhaps via new /additem and /removeitem commands?
    • Perhaps an object that appears based on the name of the player joining the room ?

Conclusion

Items and commands are important parts of the Game On protocol and are designed to improve the end user experience with your room. You should now have a general understanding of the steps required to add items and handle them with commands.

Suggested further adventures