Marauroa Chat Tutorial/Text Client

From Arianne
Jump to: navigation, search


Marauroa Tutorial


Code

In order to create a client using Marauroa frameword you should extend the marauroa.client.ClientFramework class with your logic. Here is the source code for the chat client

import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.LinkedList;

import marauroa.client.ClientFramework;
import marauroa.client.net.IPerceptionListener;
import marauroa.client.net.PerceptionHandler;
import marauroa.common.game.RPAction;
import marauroa.common.game.RPObject;
import marauroa.common.net.message.MessageS2CPerception;
import marauroa.common.net.message.TransferContent;

public class Client extends ClientFramework {
  private PerceptionHandler handler;
  private static Client client;
  private Map<RPObject.ID, RPObject> worldObjects;
  private String[] availableCharacters;
  private LinkedList<String> quotes = new LinkedList<String>();
  
  public static Client get() {
    if (client == null) {
      client = new Client();
    }
    return client;
  }

  protected Client() {
    super("log4j.properties");
    worldObjects = new HashMap<RPObject.ID, RPObject>();
    handler = new PerceptionHandler(new PerceptionListener());
  }

  public String[] getAvailableCharacters() {
    return availableCharacters;
  }

  public String popQuote() {
    if (quotes.isEmpty()) {
      return null;
    }
    return quotes.pop();
  }
  
  public void sendMessage(String text) {
    RPAction action;
    action = new RPAction();
    action.put("type", "chat");
    action.put("text", text);
    send(action);
  }
  
  @Override
  protected void onPerception(MessageS2CPerception message) {
    try {
      handler.apply(message, worldObjects);
    } catch (java.lang.Exception e) {
      // Something weird happened while applying perception
    }
  }

  @Override
  protected List<TransferContent> onTransferREQ(List<TransferContent> items) {
    return items;
  }

  @Override
  protected void onTransfer(List<TransferContent> items) {
  }

  @Override
  protected void onAvailableCharacters(String[] characters) {
    availableCharacters = characters;
  }

  @Override
  protected void onServerInfo(String[] info) {
    for (String s : info) {
      quotes.add(s);
    }
  }

  @Override
  protected String getGameName() {
    return "Chat";
  }

  @Override
  protected String getVersionNumber() {
    return "0.5";
  }

  @Override
  protected void onPreviousLogins(List<String> previousLogins) {
  }
  
  class PerceptionListener implements IPerceptionListener {
    @Override
    public boolean onAdded(RPObject object) {
      if (object.has("text")) {
        quotes.add("*" + object.get("from") + "* : " + object.get("text"));
      }
      return false;
    }

    @Override
    public boolean onModifiedAdded(RPObject object, RPObject changes) {
      return false;
    }

    @Override
    public boolean onModifiedDeleted(RPObject object, RPObject changes) {
      return false;
    }

    @Override
    public boolean onDeleted(RPObject object) {
      return false;
    }

    @Override
    public boolean onMyRPObject(RPObject added, RPObject deleted) {
      return false;
    }

    @Override
    public void onSynced() {
    }

    @Override
    public void onUnsynced() {
    }

    @Override
    public void onException(Exception e, MessageS2CPerception perception) {
      e.printStackTrace();
      System.exit(-1);
    }

    @Override
    public boolean onClear() {
      return false;
    }

    @Override
    public void onPerceptionBegin(byte type, int timestamp) {
    }

    @Override
    public void onPerceptionEnd(byte type, int timestamp) {
    }
  }
}

This is mostly a boilerplate code. We claim that we are "Chat" client, version "0.5" As you remember, our server will accept any "Chat" client, without version restrictions.

What you should note is that we use a Marauroa-provided perception handler, see onPerception. Still we introduce our own perception listener to be able to take actions when objects are added, modified, deleted.

PerceptionListener is our implementation for this. We only use onAdded handler, which allows us to react when new object appears, i.e. add the new chat message to the list of messages. The false values returned in other handlers are very important, because they show Marauroa that you would like to continue current action (e.g. adding an object).

All the messages received are stored in the quotes list. One can access stored messages one by one with a popQuote() method.

Finally, you can send a message with a SendMessage method. It constructs an RPAction understandable by server and sends it.

Marauroa frameword provides the main method for server, so that you don't need to care about execution loop. It is not true for server, so we will also need to implement the main module of our client. Here is a very easy solution

import marauroa.common.game.RPObject;

public class Test {
  public static void main(String[] args) {
    boolean runClient = true;
    Client client = Client.get();
    try {
      client.connect("localhost", 5555);
      if (args.length == 3) {
        client.createAccount(args[0], args[1], args[2]);
      }
      client.login(args[0], args[1]);
      if (client.getAvailableCharacters().length == 0) {
        RPObject character = new RPObject();
        client.createCharacter(args[0], character);
      }
      client.chooseCharacter(args[0]);
    } catch (Exception e) {
      runClient = false;
    }
    int i = 0;
    while (runClient) {
      i++;
      client.loop(0);
      if (i % 100 == 50) {
        client.sendMessage("test" + i);
      }
      String s = client.popQuote();
      while (s != null) {
        System.out.println(s);
        s = client.popQuote();
      }
      try {
        Thread.sleep(100);
      } catch (InterruptedException e) {
        runClient = false;
      }
    }
  }
}

Our main method does a couple of things. First of all, we create a new account if three command-line arguments are provided (login, password, email). Otherwise we just login with login and password specified.

Then it is time to select a character. If server reports that no characters are available for this account, then we create a new one with the same name as account name. Note, that it is not possible to select a character directly in the onAvailableCharacters handler of the Client class.

A heart-beat loop is started afterwards. At each step we invoke loop(0), where floating point parameter means nothing at this point. Once in 100 steps we send a message to server. There is no interactive way to control messages, still it is amazing to see chat created by your own client.

Deployment

Running a client is very simple, all you need is a bunch of jars (database is not required on the client side) and compiled client code. I use following line for compilation

javac -cp marauroa.jar;log4j.jar;. *.java

Make sure that required jars are in the current directory. To run use

java -cp marauroa.jar;log4j.jar;. Test login password

Again, on Linux & MacOSX, all ";" have to be replaced with ":".

Don't forget to replace login and password with the actual ones. To create a new account just add a third command-line parameter (that should be an email, but no validation at the moment).

Output

Ideally you should see something like

>java -cp marauroa.jar;log4j.jar;. Test test1 test1
Cannot find log4j.properties in classpath. Using default properties.
*test1* : test50
*test1* : test150
*test1* : test250
...

Note the message about log4j.properties: you can create that file in order to configure the Log4J usage inside the Marauroa framework.

Next Steps

In the next section of this tutorial, we will write a Swing client with a graphical user interface.