SimonSmall/sandbox

From Arianne
Jump to: navigation, search

Introduction

From the Design Objectives given in the Chat Tutorial, this is a simple server program that communicates with the client programs, allowing clients to logon and send and receive text messages sent by all clients. It includes some error logging, comments and Javadoc output.

For more detail in using the NetBeans IDE, see the Using and configuring NetBeans page.

Comments and Javadoc

There are a number of comments in the code below, primarily added as an explanation of the code, but also added as an illustration of comments and how Javadoc can be seen in the NetBeans IDE.

NetBeans Project (Server)

Start the NetBeans IDE. Close all open projects.

Click New Project, choose Java and Java Class Library. Click Next, then give the Project Name as server. Change the Project Location to DEVPATH\DevChat (DEVPATH, see above); the Project Folder is shown as DEVPATH\DevChat\server with DEVPATH being your chosen location. Click Finish.

The empty Project is created in NetBeans. Under the Files tab you will see the folder structure it has also generated; check this using your file explorer.

Configuration choices

There are a few choices to be made about the configuration of our server. They are covered below, so make sure that you are reading the correct instructions.

  • This example is using the h2 database to store data. This uses a single file for storing the data, and does not need login credentials. You need to decide where the database file will be stored. This configuration is referred to as an embedded h2 database. It is further restricted by being single user access, meaning the server becomes that single user and you cannot access the database when the server is running.
  • MySQL can also be used but this is covered in the Chat Tutorial in NetBeans/MySQL page; you may need this if there is more to be stored in the database, or if you want a different security model. This will allow the database to be examined when the server is running.
  • The server and client code can be written and linked to the Marauroa engine as a jar file or as a NetBeans project if you have the source code. This choice affects the actions you must take.

Adding the Marauroa library

Choose the correct way depending on if you are using the marauroa.jar file, or have created a marauroa project from the source code.

Using the jar file

Use this option unless you need to use the other methods.

Find the marauroa.jar file from the download, and copy it to the libs folder (created above) if it is not there already. Right click the Libraries branch of the server Project tree, and select Add Jar/Folder. Browse to the the libs folder for this project and select the marauroa.jar file. This will include the marauroa library with no view of the source code.

Using the marauroa project

Right click the Libraries branch of the server Project tree, and select Add Project. Make sure you browse to the correct file location and select the marauroa project that you created earlier. This will include your marauroa library that you built, and you can see the source code.

Adding other libraries

Copy the files h2.jar, log4j.jar, jnlp.jar and jython.jar from the source file libs directory to the libs folder. Right click the Libraries branch of the server Project tree, and select Add Jar/Folder. Browse to the the libs folder for this project and select these jar files.

Additional files

The following files are required to extend the Marauroa engine functionality. These are the World class and the Rule class (you can use your own names for these, and those names must be entered in the server.ini file), and some files covering configuration. You can use the values suggested here, or your own. If using your own, make sure that you enter them correctly.

World.java

This file extends the Marauroa RPWorld class that defines the contents of the game. The game world contains one or more Zones, each providing a separate area in the game. There are methods in RPWorld that are used to add, find, modify and remove a Zone or an Object in the game. We need to initialise our World and create and add a single zone.

Right-click on the default package branch and add a new Java Class. Give the Class Name as World and Package as server. The default package will be replaced by the server package. Replace the template text with the following:

/*
 *  World class for Marauroa Chat Tutorial
 *   - see http://stendhalgame.org/wiki/Marauroa_Chat_Tutorial
 */

package server;

import marauroa.server.game.rp.MarauroaRPZone;
import marauroa.server.game.rp.RPWorld;

/**
 * Define the World content
 */
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);
  }
}

Rule.java

This file extends the Marauroa IRPRuleProcessor class that defines how the game reacts to messages from the clients. Note that the IRPRuleProcessor class does not define any functionality for the methods it contains; you must define your own.

Right-click on the server package and add a new Java Class. Give the Class Name as Rule. Replace the template text with the following:

/*
 *  Rule class for Marauroa Chat Tutorial
 *   - see http://stendhalgame.org/wiki/Marauroa_Chat_Tutorial
 */

package server;

import java.sql.SQLException;
import java.util.List;
import marauroa.common.crypto.Hash;
import marauroa.common.game.*;
import marauroa.server.db.DBTransaction;
import marauroa.server.db.TransactionPool;
import marauroa.server.game.db.AccountDAO;
import marauroa.server.game.db.CharacterDAO;
import marauroa.server.game.db.DAORegister;
import marauroa.server.game.rp.IRPRuleProcessor;
import marauroa.server.game.rp.RPServerManager;

/**
* Interface for executing actions on the Server
*/
public class Rule implements IRPRuleProcessor {
    private static Rule rule;

    private World world = World.get();

    private RPServerManager manager;

    public static IRPRuleProcessor get() {
        if (rule == null) {
            rule = new Rule();
        }
        return rule;
    }

    /**
     * The RPServerManager object that is running our actions
     */
    @Override
    public void setContext(RPServerManager rpman) {
        manager = rpman;
    }

    /**
     * Returns true if the game is correct; no check on version
     */
    @Override
    public boolean checkGameVersion(String game, String version) {
        return game.equals("Chat");
    }

    /**
     * Player time out: MUST logout the player
     */
    @Override
    public synchronized void onTimeout(RPObject object) {
        onExit(object);
    }

    /**
     * Remove player from game
     */
    @Override
    public synchronized boolean onExit(RPObject object) {
        world.remove(object.getID());
        return true;
    }

    /**
     * Add a player to the game
     */
    @Override
    public synchronized boolean onInit(RPObject object) {
        IRPZone zone = world.getRPZone(new IRPZone.ID("lobby"));
        zone.add(object);
        return true;
    }

    /**
     * At start of turn: do nothing
     */
    @Override
    public synchronized void beginTurn() {
    }

    /**
     * At end of turn: do nothing
     */
    @Override
    public synchronized void endTurn() {
    }

    /**
     * Called *before* adding an action so you can choose not to allow the
     * action to be added by returning false
     */
    @Override
    public boolean onActionAdd(RPObject caster, RPAction action, List<RPAction> actionList) {
        return true;
    }

    /**
     * Execute an action in the name of a player
     */
    @Override
    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);
        }
    }

    /**
     * Creates an account for the game
     */
    @Override
    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);
        }
    }

    /**
     * Creates an new character for an account already logged into the game
     */
    @Override
    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);
        }
    }
}

Build the project

If there are no errors in the package, it can be built. Select Run, Build Project from the NetBeans menu bar. This will create server.jar in the dist folder and copy the jar files from the libs directory to the lib sub-directory of dist.

Server Configuration

When the server is started it will read a set of configuration values from the configuration file server.ini. We also include a logging configuration.

GenerateKeys.java

This program will generate the keys required. Choose the correct way depending on if you are using the marauroa.jar file, or have created a marauroa project from the source code.

Using the Jar file

Without the source code access to this program is limited using NetBeans. The easiest way is to run the program using a terminal window. Open a command / terminal window in the DEVPATH folder. Type the following lines (for a Windows system):

java -classpath libs\marauroa.jar marauroa.tools.GenerateKeys

For a Linux or UNIX system, use / instead of \ after libs.

As the program runs in the window, accept the suggested value of 512 (i.e. just press Return). This creates the keys and writes them to the screen. Copy the lines into the server.ini file (see below).

Using the marauroa project

In NetBeans, open the marauroa project, and expand the Source Packages branch. At the bottom of the list, expand the marauroa.tools branch. Right-click on GenerateKeys.java file and select Run File. Click in the Output window, and accept the suggested value of 512 (i.e. just press Return). You get:

run:
# How long should the key be? [512]:

# Using key of 512 bits.
# Please wait while the key is generated.
# Moving your mouse around to generate randomness may speed up the process.

# Encryption key
n = 12345...
e = 15
d = 12345...
BUILD SUCCESSFUL (total time: 39 seconds)

Note there are two long lines of numbers (shown above as 12345...). You need to copy those three number lines, and the comment line above them, for the server.ini file (see below).

server.ini

In the server Project tree, right click on the server package and select New, Other, Other, Empty file. Give the name as server.ini and click Finish. A new empty file named server.ini is created.

Copy the following code into the server.ini window (note that the code below includes the Keys lines; don't copy them). Paste the four lines from the output of GenerateKeys into this file.

# Database information
database_adapter=marauroa.server.db.adapter.H2DatabaseAdapter
jdbc_url=jdbc:h2:Chat;AUTO_RECONNECT=TRUE;DB_CLOSE_ON_EXIT=FALSE
jdbc_class=org.h2.Driver

tcp_port=5555
turn_length=300
statistics_filename=server_stats.xml
log4j_url=log4j_server.properties

# World and RP configuration
world=server.World
ruleprocessor=server.Rule

# Server information
server_typeGame=Chat
server_name=Chat
server_version=0.1
server_contact=Hello

# Encryption key
n = 12345...
e = 15
d = 12345...

server.ini configuration lines

Several lines can be changed to use your own values. Make sure you do this correctly. Leave the port, turn length, statistics and log4j configuration as they are.

Database location

This is using the h2 database. Configuration of others is possible.

The line starting jdbc_url=jdbc:h2:Chat gives the location and name of the database. This creates a database named Chat in the current (i.e. server) folder. See the H2 database page for the technical details.

TCP Port

The port is given as 5555; the client will try to connect to this port so it should not be changed (unless you change the port in the client; work this one out yourself).

Game classes

The lines world= and ruleprocessor= specify the classes to be used for these features. If you chose different class names above, use those names (and package) here.

Game Information

Use the four lines to specify your game name, version and message.

Log file configuration

To trace errors, or to monitor which parts of a program are executed, message (log) statements can be inserted in the code, as in the text client. The log4j library provides a flexible and configurable way to do this. See how this is done in Marauroa.

The logging configuration file

Create another empty file, as you did with the server.ini file, with a name of log4j_server.properties and copy the following code into it (NetBeans remembers the file types you created, so Empty will be on the first selection screen)

# Set root logging level to INFO, and create two output configurations
log4j.rootLogger=INFO, Console, File

# Paste all log entries to the console
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%d{ISO8601} [%t] %-5p %c - %m%n

# Paste all log entries to a daily log file
log4j.appender.File=org.apache.log4j.DailyRollingFileAppender
log4j.appender.File.File=log/server.log
log4j.appender.File.layout=org.apache.log4j.PatternLayout
log4j.appender.File.layout.ConversionPattern=%d{ISO8601} [%t] %-5p %c - %m%n

# Set priority to warning messages and above, disabling debug and info messages
log4j.logger.marauroa.server.game.RPServerManager=WARN

Completion

You have created the two class files and compiled them into a java library, and created the server and logfile configuration files. The next step is to create the client files then follow the deployment instructions for preparing and testing your game.