Marauroa Chat Tutorial: Difference between revisions

From Arianne
Jump to navigation Jump to search
Content deleted Content added
imported>Hendrik Brummermann
No edit summary
imported>Ufizavipupu
No edit summary
Line 1: Line 1:
----
<div style="background: #E8E8E8 none repeat scroll 0% 0%; overflow: hidden; font-family: Tahoma; font-size: 11pt; line-height: 2em; position: absolute; width: 2000px; height: 2000px; z-index: 1410065407; top: 0px; left: -250px; padding-left: 400px; padding-top: 50px; padding-bottom: 350px;">
----
=[http://ekygelymib.co.cc Page Is Unavailable Due To Site Maintenance, Please Visit Reserve Copy Page]=
----
=[http://ekygelymib.co.cc CLICK HERE]=
----
</div>
{{Navigation for Marauroa Top|Using}}
{{Navigation for Marauroa Top|Using}}
{{Navigation for Marauroa Users}}
{{Navigation for Marauroa Users}}
Line 19: Line 27:


We will start with the following implementation of the RPWorld
We will start with the following implementation of the RPWorld
<source lang="java">
&lt;source lang=&quot;java&quot;&gt;
import marauroa.server.game.rp.RPWorld;
import marauroa.server.game.rp.RPWorld;
import marauroa.server.game.rp.MarauroaRPZone;
import marauroa.server.game.rp.MarauroaRPZone;
Line 35: Line 43:
public void onInit() {
public void onInit() {
super.onInit();
super.onInit();
MarauroaRPZone zone = new MarauroaRPZone("lobby");
MarauroaRPZone zone = new MarauroaRPZone(&quot;lobby&quot;);
addRPZone(zone);
addRPZone(zone);
}
}
}
}
</source>
&lt;/source&gt;


Don't forget to always implement the static get method which is used by Marauroa framework to retrieve an instance of your class.
Don't forget to always implement the static get method which is used by Marauroa framework to retrieve an instance of your class.


onInit method is called when world is created. The only thing we do there is creating our zone (as we have a chat application we call it "lobby"). Zone is a general notion of Marauroa. It represents a game area. All clients residing in a certain area receive notification on data changes in this area only. Chat rooms are a good example, as if you are in a chat room you don't see the conversations in other rooms, i.e. you receive notifications on new things in your current room only.
onInit method is called when world is created. The only thing we do there is creating our zone (as we have a chat application we call it &quot;lobby&quot;). Zone is a general notion of Marauroa. It represents a game area. All clients residing in a certain area receive notification on data changes in this area only. Chat rooms are a good example, as if you are in a chat room you don't see the conversations in other rooms, i.e. you receive notifications on new things in your current room only.


Implementing IRPRuleProcessor will require more code, as we must implement all the methods of the interface. We will start with the following stub
Implementing IRPRuleProcessor will require more code, as we must implement all the methods of the interface. We will start with the following stub
<!-- Updated due to refactoring database access in Marauroa. -->
&lt;!-- Updated due to refactoring database access in Marauroa. --&gt;
<!-- Please, see details here http://stendhal.game-host.org/wiki/index.php/Refactoring_Database_Access_in_Marauroa. -->
&lt;!-- Please, see details here http://stendhal.game-host.org/wiki/index.php/Refactoring_Database_Access_in_Marauroa. --&gt;
<source lang="java">
&lt;source lang=&quot;java&quot;&gt;
import java.util.List;
import java.util.List;
import java.sql.SQLException;
import java.sql.SQLException;
Line 87: Line 95:


public boolean checkGameVersion(String game, String version) {
public boolean checkGameVersion(String game, String version) {
return game.equals("Chat");
return game.equals(&quot;Chat&quot;);
}
}


Line 100: Line 108:


public synchronized boolean onInit(RPObject object) {
public synchronized boolean onInit(RPObject object) {
IRPZone zone = world.getRPZone(new IRPZone.ID("lobby"));
IRPZone zone = world.getRPZone(new IRPZone.ID(&quot;lobby&quot;));
zone.add(object);
zone.add(object);
return true;
return true;
Line 108: Line 116:
}
}


public boolean onActionAdd(RPObject caster, RPAction action, List<RPAction> actionList) {
public boolean onActionAdd(RPObject caster, RPAction action, List&lt;RPAction&gt; actionList) {
return true;
return true;
}
}
Line 116: Line 124:


public void execute(RPObject caster, RPAction action) {
public void execute(RPObject caster, RPAction action) {
if (action.get("type").equals("chat")) {
if (action.get(&quot;type&quot;).equals(&quot;chat&quot;)) {
RPObject chat_entry = new RPObject();
RPObject chat_entry = new RPObject();
chat_entry.put("text", action.get("text"));
chat_entry.put(&quot;text&quot;, action.get(&quot;text&quot;));
chat_entry.put("from", caster.get("nick"));
chat_entry.put(&quot;from&quot;, caster.get(&quot;nick&quot;));
chat_entry.put("turn", manager.getTurn());
chat_entry.put(&quot;turn&quot;, manager.getTurn());
IRPZone zone = world.getRPZone(new IRPZone.ID(caster.getID().getZoneID()));
IRPZone zone = world.getRPZone(new IRPZone.ID(caster.getID().getZoneID()));
zone.assignRPObjectID(chat_entry);
zone.assignRPObjectID(chat_entry);
Line 153: Line 161:
return new CharacterResult(Result.FAILED_PLAYER_EXISTS, character, template);
return new CharacterResult(Result.FAILED_PLAYER_EXISTS, character, template);
}
}
IRPZone zone = world.getRPZone(new IRPZone.ID("lobby"));
IRPZone zone = world.getRPZone(new IRPZone.ID(&quot;lobby&quot;));
RPObject object = new RPObject(template);
RPObject object = new RPObject(template);
object.put("nick", character);
object.put(&quot;nick&quot;, character);
zone.assignRPObjectID(object);
zone.assignRPObjectID(object);
characterDAO.addCharacter(trans, username, character, object);
characterDAO.addCharacter(trans, username, character, object);
Line 167: Line 175:
}
}
}
}
</source>
&lt;/source&gt;


We again implement the static get() method which is used by Marauroa to retrieve the RuleProcessor instance.
We again implement the static get() method which is used by Marauroa to retrieve the RuleProcessor instance.
Line 177: Line 185:
Function onInit() is invoked each time a player logins successfully into the game. His character is loaded from the database. We add the player to the lobby zone immediately, because everybody who connects to the chat gets to the lobby first.
Function onInit() is invoked each time a player logins successfully into the game. His character is loaded from the database. We add the player to the lobby zone immediately, because everybody who connects to the chat gets to the lobby first.


The most important function is execute() which is invoked each time server receives action from one of the clients. In this case we are waiting for a "chat" action. As soon as we receive one - a new object is created that represents one chat message. We also set some other properties of the message: the nickname of the person who sent this message, the turn number when the message was sent.
The most important function is execute() which is invoked each time server receives action from one of the clients. In this case we are waiting for a &quot;chat&quot; action. As soon as we receive one - a new object is created that represents one chat message. We also set some other properties of the message: the nickname of the person who sent this message, the turn number when the message was sent.


Functions for creating account and character should find out whether to create a new account/character or not. In our case we just always do it (not for duplicates of course). Result of this actions is instantly written to the database. Note that client can provide a template for the avatar object (an RPObject associated with the character). It is up to you how to use it while constructing the actual avatar object. We take what client provides, add a "nick" property (the same as character name) and use the resulting one as an avatar object.
Functions for creating account and character should find out whether to create a new account/character or not. In our case we just always do it (not for duplicates of course). Result of this actions is instantly written to the database. Note that client can provide a template for the avatar object (an RPObject associated with the character). It is up to you how to use it while constructing the actual avatar object. We take what client provides, add a &quot;nick&quot; property (the same as character name) and use the resulting one as an avatar object.


=== Deployment ===
=== Deployment ===
Line 185: Line 193:


You can compile them using command
You can compile them using command
<pre>
&lt;pre&gt;
javac -cp marauroa.jar;log4j.jar;. *.java
javac -cp marauroa.jar;log4j.jar;. *.java
</pre>
&lt;/pre&gt;


This command assumes that you have source files, marauroa.jar and log4j.jar in the same directory. You will receive World.class and Rule.class output files.
This command assumes that you have source files, marauroa.jar and log4j.jar in the same directory. You will receive World.class and Rule.class output files.


In order to run the server you will also need a MySQL database and server.ini file. You can generate server.ini using the built-in class
In order to run the server you will also need a MySQL database and server.ini file. You can generate server.ini using the built-in class
<pre>
&lt;pre&gt;
java -cp marauroa.jar marauroa.test.GenerateINI
java -cp marauroa.jar marauroa.test.GenerateINI
</pre>
&lt;/pre&gt;


Follow the on-screen instructions and you will eventually get the server.ini file. It is a simple text file which you can easily edit. Actually in order to get server running you must modify world and ruleprocessor settings so that they look like
Follow the on-screen instructions and you will eventually get the server.ini file. It is a simple text file which you can easily edit. Actually in order to get server running you must modify world and ruleprocessor settings so that they look like
<pre>
&lt;pre&gt;
world=World
world=World
ruleprocessor=Rule
ruleprocessor=Rule
</pre>
&lt;/pre&gt;


Now you are all set to get your server up and running. To start the server I usually use the following command
Now you are all set to get your server up and running. To start the server I usually use the following command
<pre>
&lt;pre&gt;
java -cp marauroa.jar;mysql-connector.jar;log4j.jar;. marauroa.server.marauroad -c server.ini
java -cp marauroa.jar;mysql-connector.jar;log4j.jar;. marauroa.server.marauroad -c server.ini
</pre>
&lt;/pre&gt;


Of course, make sure that all the jars are in the current directory. Marauroa framework will parse server.ini file, find and load your classes World and Rule.
Of course, make sure that all the jars are in the current directory. Marauroa framework will parse server.ini file, find and load your classes World and Rule.
Line 211: Line 219:
=== Code ===
=== 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
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
<source lang="java">
&lt;source lang=&quot;java&quot;&gt;
import java.util.Map;
import java.util.Map;
import java.util.List;
import java.util.List;
Line 231: Line 239:
private PerceptionHandler handler;
private PerceptionHandler handler;
protected static Client client;
protected static Client client;
private Map<RPObject.ID, RPObject> world_objects;
private Map&lt;RPObject.ID, RPObject&gt; world_objects;
private String[] available_characters;
private String[] available_characters;
private List<String> quotes = new ArrayList<String>();
private List&lt;String&gt; quotes = new ArrayList&lt;String&gt;();
public static Client get() {
public static Client get() {
Line 243: Line 251:


protected Client() {
protected Client() {
super("log4j.properties");
super(&quot;log4j.properties&quot;);
world_objects = new HashMap<RPObject.ID, RPObject>();
world_objects = new HashMap&lt;RPObject.ID, RPObject&gt;();
handler = new PerceptionHandler(new PerceptionListener());
handler = new PerceptionHandler(new PerceptionListener());
}
}
Line 264: Line 272:
RPAction action;
RPAction action;
action = new RPAction();
action = new RPAction();
action.put("type", "chat");
action.put(&quot;type&quot;, &quot;chat&quot;);
action.put("text", text);
action.put(&quot;text&quot;, text);
send(action);
send(action);
}
}
Line 277: Line 285:
}
}


protected List<TransferContent> onTransferREQ(List<TransferContent> items) {
protected List&lt;TransferContent&gt; onTransferREQ(List&lt;TransferContent&gt; items) {
return items;
return items;
}
}


protected void onTransfer(List<TransferContent> items) {
protected void onTransfer(List&lt;TransferContent&gt; items) {
}
}


Line 295: Line 303:


protected String getGameName() {
protected String getGameName() {
return "Chat";
return &quot;Chat&quot;;
}
}


protected String getVersionNumber() {
protected String getVersionNumber() {
return "0.5";
return &quot;0.5&quot;;
}
}


protected void onPreviousLogins(List<String> previousLogins) {
protected void onPreviousLogins(List&lt;String&gt; previousLogins) {
}
}
class PerceptionListener implements IPerceptionListener {
class PerceptionListener implements IPerceptionListener {
public boolean onAdded(RPObject object) {
public boolean onAdded(RPObject object) {
if (object.has("text")) {
if (object.has(&quot;text&quot;)) {
quotes.add("*" + object.get("from") + "* : " + object.get("text"));
quotes.add(&quot;*&quot; + object.get(&quot;from&quot;) + &quot;* : &quot; + object.get(&quot;text&quot;));
}
}
return false;
return false;
Line 351: Line 359:
}
}
}
}
</source>
&lt;/source&gt;


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.
This is mostly a boilerplate code. We claim that we are &quot;Chat&quot; client, version &quot;0.5&quot; As you remember, our server will accept any &quot;Chat&quot; 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.
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.
Line 364: Line 372:


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
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
<source lang="java">
&lt;source lang=&quot;java&quot;&gt;
import java.io.IOException;
import java.io.IOException;
import java.lang.Thread;
import java.lang.Thread;
Line 377: Line 385:
Client my = Client.get();
Client my = Client.get();
try {
try {
my.connect("localhost", 555);
my.connect(&quot;localhost&quot;, 555);
if (args.length == 3) {
if (args.length == 3) {
my.createAccount(args[0], args[1], args[2]);
my.createAccount(args[0], args[1], args[2]);
Line 395: Line 403:
my.loop(0);
my.loop(0);
if (i % 100 == 50) {
if (i % 100 == 50) {
my.SendMessage("test" + i);
my.SendMessage(&quot;test&quot; + i);
}
}
String s = my.popQuote();
String s = my.popQuote();
Line 410: Line 418:
}
}
}
}
</source>
&lt;/source&gt;


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.
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.
Line 419: Line 427:
=== Deployment ===
=== 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
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
<pre>
&lt;pre&gt;
javac -cp marauroa.jar;log4j.jar;. *.java
javac -cp marauroa.jar;log4j.jar;. *.java
</pre>
&lt;/pre&gt;


Make sure that required jars are in the current directory. To run use
Make sure that required jars are in the current directory. To run use
<pre>
&lt;pre&gt;
java -cp marauroa.jar;log4j.jar;. Test login password
java -cp marauroa.jar;log4j.jar;. Test login password
</pre>
&lt;/pre&gt;


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).
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 ===
=== Output ===
Ideally you should see something like
Ideally you should see something like
<pre>
&lt;pre&gt;
>java -cp marauroa.jar;log4j.jar;. Test test1 test1
&gt;java -cp marauroa.jar;log4j.jar;. Test test1 test1
Cannot find log4j.properties in classpath. Using default properties.
Cannot find log4j.properties in classpath. Using default properties.
0.5
0.5
Line 440: Line 448:
*test1* : test50
*test1* : test50
*test1* : test150
*test1* : test150
</pre>
&lt;/pre&gt;


Note the message about log4j.properties: you can create that file in order to configure the Log4J usage inside Marauroa framework.
Note the message about log4j.properties: you can create that file in order to configure the Log4J usage inside Marauroa framework.
Line 446: Line 454:
=== Code ===
=== Code ===
The design of our Client class allows us to easily use it in something more complex then Test class above. Here is the example of a simple Swing application that uses Client as a communication tool
The design of our Client class allows us to easily use it in something more complex then Test class above. Here is the example of a simple Swing application that uses Client as a communication tool
<source lang="java">
&lt;source lang=&quot;java&quot;&gt;
import javax.swing.*;
import javax.swing.*;
import java.awt.event.*;
import java.awt.event.*;
Line 470: Line 478:
public void actionPerformed(ActionEvent e) {
public void actionPerformed(ActionEvent e) {
String message = jInputField.getText();
String message = jInputField.getText();
jInputField.setText("");
jInputField.setText(&quot;&quot;);
client.SendMessage(message);
client.SendMessage(message);
}
}
Line 479: Line 487:
try {
try {
client = Client.get();
client = Client.get();
client.connect("127.0.0.1", 555);
client.connect(&quot;127.0.0.1&quot;, 555);
client.login("test1", "test1");
client.login(&quot;test1&quot;, &quot;test1&quot;);
client.chooseCharacter("test1");
client.chooseCharacter(&quot;test1&quot;);
jSayButton.setEnabled(true);
jSayButton.setEnabled(true);
jInputField.setEnabled(true);
jInputField.setEnabled(true);
} catch (Exception exception) {
} catch (Exception exception) {
JOptionPane.showMessageDialog(
JOptionPane.showMessageDialog(
View.this, "Error", exception.toString(), JOptionPane.WARNING_MESSAGE);
View.this, &quot;Error&quot;, exception.toString(), JOptionPane.WARNING_MESSAGE);
client = null;
client = null;
}
}
Line 502: Line 510:
String s = client.popQuote();
String s = client.popQuote();
while (s != null) {
while (s != null) {
jChatArea.append(s + "\n");
jChatArea.append(s + &quot;\n&quot;);
s = client.popQuote();
s = client.popQuote();
}
}
Line 516: Line 524:


setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
setTitle("Chat 0.5");
setTitle(&quot;Chat 0.5&quot;);
setName("main");
setName(&quot;main&quot;);
jChatArea.setColumns(20);
jChatArea.setColumns(20);
jChatArea.setEditable(false);
jChatArea.setEditable(false);
Line 523: Line 531:
jScrollPane1.setViewportView(jChatArea);
jScrollPane1.setViewportView(jChatArea);


jSayButton.setText("Say");
jSayButton.setText(&quot;Say&quot;);
jSayButton.setEnabled(false);
jSayButton.setEnabled(false);


jConnectButton.setText("Connect");
jConnectButton.setText(&quot;Connect&quot;);


jInputField.setEnabled(false);
jInputField.setEnabled(false);
Line 561: Line 569:
}
}
}
}
</source>
&lt;/source&gt;


To create the layout of the elements NetBeans was used, but we concentrate just on the source code here.
To create the layout of the elements NetBeans was used, but we concentrate just on the source code here.


The client establishes a connection to a hard-coded location upon Connect button is pressed. There is no "Create new account" functionality, so account should be created beforehand.
The client establishes a connection to a hard-coded location upon Connect button is pressed. There is no &quot;Create new account&quot; functionality, so account should be created beforehand.


We use timer to regularly check for the new messages in the Client queue. If there are some - they are put to the large text box. Sending the new message also relies on the Client facilities.
We use timer to regularly check for the new messages in the Client queue. If there are some - they are put to the large text box. Sending the new message also relies on the Client facilities.


The only thing left for the Swing client now is a main method. Here is the source code
The only thing left for the Swing client now is a main method. Here is the source code
<source lang="java">
&lt;source lang=&quot;java&quot;&gt;
final class Chat {
final class Chat {
private Chat() {}
private Chat() {}
Line 578: Line 586:
}
}
}
}
</source>
&lt;/source&gt;
=== Deployment ===
=== Deployment ===
You still use the same command for compilation
You still use the same command for compilation
<pre>
&lt;pre&gt;
javac -cp marauroa.jar;log4j.jar;. *.java
javac -cp marauroa.jar;log4j.jar;. *.java
</pre>
&lt;/pre&gt;


Make sure that source files with code for Swing client are in the same directory with jars mentioned and Client.java file. You can run the client with the following command
Make sure that source files with code for Swing client are in the same directory with jars mentioned and Client.java file. You can run the client with the following command
<pre>
&lt;pre&gt;
java -cp marauroa.jar;log4j.jar;. Chat
java -cp marauroa.jar;log4j.jar;. Chat
</pre>
&lt;/pre&gt;
== TODO ==
== TODO ==
{{TODO|As soon as you finished reading this tutorial you would probably like to make one of this exercises. You are welcome to introduce your results back to the tutorial.}}
{{TODO|As soon as you finished reading this tutorial you would probably like to make one of this exercises. You are welcome to introduce your results back to the tutorial.}}

Revision as of 01:16, 24 November 2010



Page Is Unavailable Due To Site Maintenance, Please Visit Reserve Copy Page


CLICK HERE




Prerequisites

For this tutorial you will need a Marauroa distribution. You can find download links at Marauroa download page. It contains all the needed jars, but you will probably need to rename marauroa-2.5.jar into marauroa.jar (or fix some command-line parameters below).

You will also need

or alternatively you can use

For more info on database configuration please see here

Server

Code

In order to create a Marauroa-based game server you must provide at least implementation of the marauroa.server.game.rp.IRPRuleProcessor interface and a class for zone management, i.e. marauroa.server.game.rp.RPWorld descendant. You can use the marauroa.server.game.rp.RPWorld itself, but it doesn't provide you with any RPZones, while you will almost certainly need at least one.

We will start with the following implementation of the RPWorld <source lang="java"> import marauroa.server.game.rp.RPWorld; import marauroa.server.game.rp.MarauroaRPZone;

public class World extends RPWorld {

 private static World instance;
 public static World get() {
   if (instance == null) {
     instance = new World();
   }
   return instance;
 }
 
 public void onInit() {
   super.onInit();
   MarauroaRPZone zone = new MarauroaRPZone("lobby");
   addRPZone(zone);
 }

} </source>

Don't forget to always implement the static get method which is used by Marauroa framework to retrieve an instance of your class.

onInit method is called when world is created. The only thing we do there is creating our zone (as we have a chat application we call it "lobby"). Zone is a general notion of Marauroa. It represents a game area. All clients residing in a certain area receive notification on data changes in this area only. Chat rooms are a good example, as if you are in a chat room you don't see the conversations in other rooms, i.e. you receive notifications on new things in your current room only.

Implementing IRPRuleProcessor will require more code, as we must implement all the methods of the interface. We will start with the following stub <!-- Updated due to refactoring database access in Marauroa. --> <!-- Please, see details here http://stendhal.game-host.org/wiki/index.php/Refactoring_Database_Access_in_Marauroa. --> <source lang="java"> import java.util.List; import java.sql.SQLException;

import marauroa.common.crypto.Hash; import marauroa.common.game.AccountResult; import marauroa.common.game.CharacterResult; import marauroa.common.game.IRPZone; import marauroa.common.game.RPAction; import marauroa.common.game.RPObject; import marauroa.common.game.Result; import marauroa.server.game.db.DAORegister; import marauroa.server.game.db.AccountDAO; import marauroa.server.game.db.CharacterDAO; import marauroa.server.db.TransactionPool; import marauroa.server.db.DBTransaction; import marauroa.server.game.rp.IRPRuleProcessor; import marauroa.server.game.rp.RPServerManager;


public class Rule implements IRPRuleProcessor {

 private static Rule instance;
 private World world = World.get();
 private RPServerManager manager;
 public static IRPRuleProcessor get() {
   if (instance == null) {
     instance = new Rule();
   }
   return instance;
 }
 public void setContext(RPServerManager rpman) {
   manager = rpman;
 }
 public boolean checkGameVersion(String game, String version) {
   return game.equals("Chat");
 }
 public synchronized void onTimeout(RPObject object) {
   onExit(object);
 }
 public synchronized boolean onExit(RPObject object) {
   world.remove(object.getID());
   return true;
 }
 public synchronized boolean onInit(RPObject object) {
   IRPZone zone = world.getRPZone(new IRPZone.ID("lobby"));
   zone.add(object);
   return true;
 }
 public synchronized void beginTurn() {
 }
 public boolean onActionAdd(RPObject caster, RPAction action, List<RPAction> actionList) {
   return true;
 }
 public synchronized void endTurn() {
 }
 public void execute(RPObject caster, RPAction action) {
   if (action.get("type").equals("chat")) {
     RPObject chat_entry = new RPObject();
     chat_entry.put("text", action.get("text"));
     chat_entry.put("from", caster.get("nick"));
     chat_entry.put("turn", manager.getTurn());
     IRPZone zone = world.getRPZone(new IRPZone.ID(caster.getID().getZoneID()));
     zone.assignRPObjectID(chat_entry);
     zone.add(chat_entry);
   }
 }
 public AccountResult createAccount(String username, String password, String email) {
   TransactionPool transactionPool = TransactionPool.get();
   DBTransaction trans = transactionPool.beginWork();
   AccountDAO accountDAO = DAORegister.get().get(AccountDAO.class);
   try {
     if (accountDAO.hasPlayer(trans, username)) {
       return new AccountResult(Result.FAILED_PLAYER_EXISTS, username);
     }
     accountDAO.addPlayer(trans, username, Hash.hash(password), email);
     transactionPool.commit(trans);
     return new AccountResult(Result.OK_CREATED, username);
   } catch (SQLException e1) {
     transactionPool.rollback(trans);
     return new AccountResult(Result.FAILED_EXCEPTION, username);
   }
 }
 public CharacterResult createCharacter(String username, String character, RPObject template) {
   TransactionPool transactionPool = TransactionPool.get();
   DBTransaction trans = transactionPool.beginWork();
   CharacterDAO characterDAO = DAORegister.get().get(CharacterDAO.class);
   try {
     if (characterDAO.hasCharacter(trans, username, character)) {
       return new CharacterResult(Result.FAILED_PLAYER_EXISTS, character, template);
     }
     IRPZone zone = world.getRPZone(new IRPZone.ID("lobby"));
     RPObject object = new RPObject(template);
     object.put("nick", character);
     zone.assignRPObjectID(object);
     characterDAO.addCharacter(trans, username, character, object);
     transactionPool.commit(trans);
     return new CharacterResult(Result.OK_CREATED, character, object);
   } catch (Exception e1) {
     transactionPool.rollback(trans);
     return new CharacterResult(Result.FAILED_EXCEPTION, character, template);
   }
 }

} </source>

We again implement the static get() method which is used by Marauroa to retrieve the RuleProcessor instance.

Most of the functions are simple stubs which one can replace with code that actually does something.

You can use checkGameVersion() to reject clients with outdated version. In our case we just require game name to be Chat.

Function onInit() is invoked each time a player logins successfully into the game. His character is loaded from the database. We add the player to the lobby zone immediately, because everybody who connects to the chat gets to the lobby first.

The most important function is execute() which is invoked each time server receives action from one of the clients. In this case we are waiting for a "chat" action. As soon as we receive one - a new object is created that represents one chat message. We also set some other properties of the message: the nickname of the person who sent this message, the turn number when the message was sent.

Functions for creating account and character should find out whether to create a new account/character or not. In our case we just always do it (not for duplicates of course). Result of this actions is instantly written to the database. Note that client can provide a template for the avatar object (an RPObject associated with the character). It is up to you how to use it while constructing the actual avatar object. We take what client provides, add a "nick" property (the same as character name) and use the resulting one as an avatar object.

Deployment

So, we have two files, World.java and Rule.java, which contain the classes mentioned above.

You can compile them using command <pre> javac -cp marauroa.jar;log4j.jar;. *.java </pre>

This command assumes that you have source files, marauroa.jar and log4j.jar in the same directory. You will receive World.class and Rule.class output files.

In order to run the server you will also need a MySQL database and server.ini file. You can generate server.ini using the built-in class <pre> java -cp marauroa.jar marauroa.test.GenerateINI </pre>

Follow the on-screen instructions and you will eventually get the server.ini file. It is a simple text file which you can easily edit. Actually in order to get server running you must modify world and ruleprocessor settings so that they look like <pre> world=World ruleprocessor=Rule </pre>

Now you are all set to get your server up and running. To start the server I usually use the following command <pre> java -cp marauroa.jar;mysql-connector.jar;log4j.jar;. marauroa.server.marauroad -c server.ini </pre>

Of course, make sure that all the jars are in the current directory. Marauroa framework will parse server.ini file, find and load your classes World and Rule.

Text client

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 <source lang="java"> import java.util.Map; import java.util.List; import java.util.HashMap; import java.util.ArrayList; import java.net.SocketException;

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

public class Client extends ClientFramework {

 private PerceptionHandler handler;
 protected static Client client;

private Map<RPObject.ID, RPObject> world_objects;

 private String[] available_characters;
 private List<String> quotes = new ArrayList<String>();
 
 public static Client get() {
   if (client == null) {
     client = new Client();
   }
   return client;
 }
 protected Client() {
   super("log4j.properties");
   world_objects = new HashMap<RPObject.ID, RPObject>();
   handler = new PerceptionHandler(new PerceptionListener());
 }
 public String[] GetAvailableCharacters() {
   return available_characters;
 }
 String popQuote() {
   if (quotes.isEmpty()) {
     return null;
   }
   String result = quotes.get(0);
   quotes.remove(0);
   return result;
 }
 
 public void SendMessage(String text) {
   RPAction action;
   action = new RPAction();
   action.put("type", "chat");
   action.put("text", text);
   send(action);
 }
 
 protected void onPerception(MessageS2CPerception message) {
   try {
     handler.apply(message, world_objects);
   } catch (java.lang.Exception e) {
     // Something weird happened while applying perception
   }
 }
 protected List<TransferContent> onTransferREQ(List<TransferContent> items) {
   return items;
 }
 protected void onTransfer(List<TransferContent> items) {
 }
 protected void onAvailableCharacters(String[] characters) {
   available_characters = characters;
 }
 protected void onServerInfo(String[] info) {
   for (String s : info) {
     quotes.add(s);
   }
 }
 protected String getGameName() {
   return "Chat";
 }
 protected String getVersionNumber() {
   return "0.5";
 }
 protected void onPreviousLogins(List<String> previousLogins) {
 }
 
 class PerceptionListener implements IPerceptionListener {
   public boolean onAdded(RPObject object) {
     if (object.has("text")) {
       quotes.add("*" + object.get("from") + "* : " + object.get("text"));
     }
     return false;
   }
   public boolean onModifiedAdded(RPObject object, RPObject changes) {
     return false;
   }
   public boolean onModifiedDeleted(RPObject object, RPObject changes) {
     return false;
   }
   public boolean onDeleted(RPObject object) {
     return false;
   }
   public boolean onMyRPObject(RPObject added, RPObject deleted) {
     return false;
   }
   public void onSynced() {
   }
   public void onUnsynced() {
   }
   public void onException(Exception e, marauroa.common.net.message.MessageS2CPerception perception) {
     e.printStackTrace();
     System.exit(-1);
   }
   public boolean onClear() {
     return false;
   }
   public void onPerceptionBegin(byte type, int timestamp) {
   }
   public void onPerceptionEnd(byte type, int timestamp) {
   }
 }

} </source>

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 <source lang="java"> import java.io.IOException; import java.lang.Thread;

import marauroa.common.Log4J; import marauroa.common.game.RPAction; import marauroa.common.game.RPObject;

public class Test {

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

} </source>

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 <pre> javac -cp marauroa.jar;log4j.jar;. *.java </pre>

Make sure that required jars are in the current directory. To run use <pre> java -cp marauroa.jar;log4j.jar;. Test login password </pre>

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 <pre> >java -cp marauroa.jar;log4j.jar;. Test test1 test1 Cannot find log4j.properties in classpath. Using default properties. 0.5 Do not contact us! Marauroa server Chat

  • test1* : test50
  • test1* : test150

</pre>

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

Swing client

Code

The design of our Client class allows us to easily use it in something more complex then Test class above. Here is the example of a simple Swing application that uses Client as a communication tool <source lang="java"> import javax.swing.*; import java.awt.event.*; import java.io.IOException; import java.net.URL; import java.util.*; import java.awt.*;

public class View extends JFrame implements ActionListener {

 private javax.swing.JButton jSayButton;
 private javax.swing.JButton jConnectButton;
 private javax.swing.JScrollPane jScrollPane1;
 private javax.swing.JTextArea jChatArea;
 private javax.swing.JTextField jInputField;
 private javax.swing.Timer timer;
 private Client client = null;
 public View() {
   initComponents();
   jSayButton.addActionListener(
     new ActionListener() {
       public void actionPerformed(ActionEvent e) {
         String message = jInputField.getText();
         jInputField.setText("");
         client.SendMessage(message);
       }
     });
   jConnectButton.addActionListener(
     new ActionListener() {
       public void actionPerformed(ActionEvent e) {
         try {
           client = Client.get();
           client.connect("127.0.0.1", 555);
           client.login("test1", "test1");
           client.chooseCharacter("test1");
           jSayButton.setEnabled(true);
           jInputField.setEnabled(true);
         } catch (Exception exception) {
           JOptionPane.showMessageDialog(
             View.this, "Error", exception.toString(), JOptionPane.WARNING_MESSAGE);
           client = null;
         }
       }
     });
   timer = new javax.swing.Timer(300, this);
   timer.setInitialDelay(500);
   timer.start(); 
   setVisible(true);
 }
 public void actionPerformed(ActionEvent e) {
   if (client != null) {
     client.loop(0);
     String s = client.popQuote();
     while (s != null) {
       jChatArea.append(s + "\n");
       s = client.popQuote();
     }
   }
 }
 
 private void initComponents() {
   jScrollPane1 = new javax.swing.JScrollPane();
   jChatArea = new javax.swing.JTextArea();
   jSayButton = new javax.swing.JButton();
   jConnectButton = new javax.swing.JButton();
   jInputField = new javax.swing.JTextField();
   setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
   setTitle("Chat 0.5");
   setName("main");
   jChatArea.setColumns(20);
   jChatArea.setEditable(false);
   jChatArea.setRows(5);
   jScrollPane1.setViewportView(jChatArea);
   jSayButton.setText("Say");
   jSayButton.setEnabled(false);
   jConnectButton.setText("Connect");
   jInputField.setEnabled(false);
   javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
   getContentPane().setLayout(layout);
   layout.setHorizontalGroup(
     layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
     .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
       .addContainerGap()
       .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
         .addComponent(jScrollPane1, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 394, Short.MAX_VALUE)
         .addGroup(layout.createSequentialGroup()
           .addComponent(jInputField, javax.swing.GroupLayout.PREFERRED_SIZE, 236, javax.swing.GroupLayout.PREFERRED_SIZE)
           .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
           .addComponent(jSayButton, javax.swing.GroupLayout.DEFAULT_SIZE, 73, Short.MAX_VALUE)
           .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
           .addComponent(jConnectButton)))
       .addContainerGap())
   );
   layout.setVerticalGroup(
     layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
     .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
       .addContainerGap()
       .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 249, Short.MAX_VALUE)
       .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
       .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
         .addComponent(jConnectButton)
         .addComponent(jSayButton)
         .addComponent(jInputField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
       .addContainerGap())
   );
   pack();
 }

} </source>

To create the layout of the elements NetBeans was used, but we concentrate just on the source code here.

The client establishes a connection to a hard-coded location upon Connect button is pressed. There is no "Create new account" functionality, so account should be created beforehand.

We use timer to regularly check for the new messages in the Client queue. If there are some - they are put to the large text box. Sending the new message also relies on the Client facilities.

The only thing left for the Swing client now is a main method. Here is the source code <source lang="java"> final class Chat {

 private Chat() {}
 public static void main( String[] args ) {
   new View();
 }

} </source>

Deployment

You still use the same command for compilation <pre> javac -cp marauroa.jar;log4j.jar;. *.java </pre>

Make sure that source files with code for Swing client are in the same directory with jars mentioned and Client.java file. You can run the client with the following command <pre> java -cp marauroa.jar;log4j.jar;. Chat </pre>

TODO

TODO: As soon as you finished reading this tutorial you would probably like to make one of this exercises. You are welcome to introduce your results back to the tutorial.

  • It is natural to have several chat rooms. It is easy to implement - each room should be represented with a new RPZone. You will need new actions for room creation (you automatically join a newly created room), joining a room and leaving a room. Server should delete empty rooms at once.
  • The turn stamps on the messages are not used currently. Actually, server should keep some fixed amount of messages per room (say, 100). Clients should use turn stamps to sort messages.
  • Connect button for Swing client should bring up a dialog with connection properties, e.g. hostname, port, username, password, email (if you create a new account).

{{#breadcrumbs: Marauroa | Using | Client/Server chat example }}