Tuesday, May 17, 2011

Breaking into the Amazon cloud.

So last night I sat down with the intention of writing the code to allow for character select. Instead I started thinking about reworking my code to work with Photon instead of SmartFoxServer 2x. I still haven't made up my mind about either technology. I like how easily Hibernate works with NetBeans to quickly allow me to create my classes and get started. The counter is that I would like my messages and common code to be written once, instead of once in C# and Java. That battle will have to wait until later.

As I was saying. I sat down with the intention of working on the character select code. Instead I ran across a post about people using the Amazon cloud to host a Photon server, so I figured I've give it a look and see what all the fuss was about.

Amazon has a great starting deal. You can get a micro instance with a linux server with 613MB of memory, 8 GB of their EBS storage, an Elastic IP - an IP address that can be repointed to any instance you are running so if a server dies you can switch to a new one, and something like 10GB of output. All of this for free for an entire year.

I figured what the heck, sign up for an account, get a linux server to admin for a year. I can play around with the server and maybe do something cool. I have installed xampp and secured it following the linux instructions from apache friends. Next I'm going to see if it already has Java installed and if so, I'm going to look into putting on SmartFoxServer.

This is an interesting project I plan on exploring and maybe something will come of it. I'll just have to find out.

Monday, May 16, 2011

Character creation Part 2 - SmartFoxServer Extension

Just to start out with, we need to go ahead and add in the character id to the message sent to the client with the character list. To do this we go into [GameName]Character in the models package and add in the following line to the createSFSObject function:


        data.putLong("id", character.getId());

This now gets us the data we need on the client for when we select our character. While we are in the file we can go ahead and add the code to the loadFromSFSObject function:

    public void loadFromSFSObject(ISFSObject data) 
    {
        character.setLevel(BigInteger.valueOf(1));
        character.setPositionX(BigInteger.ZERO);
        character.setPositionY(BigInteger.ZERO);
        character.setCreatedAt(new java.util.Date());
        character.setUpdatedAt(new java.util.Date());
        
        for(String key : data.getKeys())
        {
            if(key.equalsIgnoreCase("characterName"))
                character.setName(data.getUtfString("characterName"));
            if(key.equalsIgnoreCase("sex"))
                character.setSex(data.getUtfString("sex"));
            if(key.equalsIgnoreCase("characterClass"))
                character.setClass1(data.getUtfString("characterClass"));
        }
    }

This just gets our data from our packet and fills in the character. Next we need to add the code to pass the message to a handler that will do something with this data after calling the function. So in [GameName]Extension.init add to the list of handlers:

        addRequestHandler("createCharacter", CharacterCreationHandler.class);

Next we need to create our handler:

public class CharacterCreationHandler extends BaseClientRequestHandler {

    @Override
    public void handleClientRequest(User u, ISFSObject data)
    {
        World world = RoomHelper.getWorld(this);
        [GameName]Account player = world.getPlayer(u);
        if (player.canAddCharacters())
        {
            EntityManager entityManager = (([GameName]Extension) this.getParentExtension()).getEntityManagerFactory().createEntityManager();
            entityManager.getTransaction().begin();
            [GameName]Character character = new [GameName]Character(new domain.domainname.gamename.persistence.[GameName]Character());
            character.loadFromSFSObject(data);

            Query q = entityManager.createNamedQuery("[GameName]Character.findByName");
            q.setParameter("name", character.getCharacter().getName());
            try
            {
                q.getSingleResult();
                SFSObject err = new SFSObject();
                err.putUtfString("error", "That name is taken.");
                send("error", err, u);

            }
            catch(NoResultException e)
            {
                SfGuardUser guard = entityManager.find(SfGuardUser.class, player.getGuardID());
                character.getCharacter().setUserId(guard);
                // No character with that name, go ahead and create it!
                entityManager.persist(character.getCharacter());
                entityManager.flush();
                entityManager.getTransaction().commit();
                send("characterCreated", new SFSObject(), u);
            }
        }
        else
        {
            SFSObject err = new SFSObject();
            err.putUtfString("error", "No empty character slots.");
            send("error", err, u);
        }
    }
}
This code begins by getting our entity manager and calling the loadFromSFSObject to create our character object. After that we check to see if the name is already used and if so, send back an error response. If we get a NoResultException it means that name is not in use and we can go ahead and persist the character. From there we send the response back to the client saying we created the character.

This concludes Character creation. Next time we will be covering our simple character select screen and getting us over to the game itself. Almost there!

Character creation Part 1 - Unity3d

So the last place we left off, we get to the character select screen and we read in all our characters. Now, I've had the code checked in for nearly a week to get to the character create screen and actually create a character and limit the user to their max characters and limit the names to be unique, including upper/lower case variants of the same name. Today I'm going to go over the changes to get us caught up.

First we will start with the character class in the Unity3d project. We want to allow the user to pick which character to log in with and the easiest way to do that is by the character id. So we need to read it out of the message. We also want to make sure it won't crash if we try to test it before it gets added to the message on the server. The follow code is added to the Character.LoadFromSFSObject()


            if (data.ContainsKey("id"))
            {
                newCharacter.id = data.GetLong("id");
            }
            else
            {
                Debug.Log("SFS Message did not contain character key");
            }

From here we will want to add in a button to go to our character create scene. Before we do that, we want to ensure we received our character list from the server. To do this, we will add a line of code after we add our  CharacterSelectHandler to the list of handlers:



            CharacterSelect.afterMessageRecieved += AfterCharacterSelect;

Now that we have the setup. we need to add the function. Its only job is to tell the GUI to display after we get our data back from the server:

    bool receivedCharacters = false;
    public void AfterCharacterSelect()
    {
        receivedCharacters = true;
    }

Now we can write our OnGUI function and check if we received the characters, if we did, we can display out button.

    void OnGUI()
    {
        if (receivedCharacters)
        {
            foreach (Character character in CharacterSelect.characterList)
            {

            }

            if (GUI.Button(new Rect(100, 165, 100, 25), "New Character") || (Event.current.type == EventType.keyDown && Event.current.character == '\n'))
            {
                UnregisterSFSSceneCallbacks();
                Application.LoadLevel("CharacterCreate");
            }
        }
        if (GUI.Button(new Rect(100, 195, 100, 25), "Back"))
        {
            UnregisterSFSSceneCallbacks();
            smartFox.Disconnect();
            Application.LoadLevel("Lobby");
        }
    }
This code will display our "back" button which will log us out and take us back to the lobby. After we get our character list we enable the new character button which loads our character create scene. Go ahead and create that now by creating a new scene, calling it CharacterCreate, saving it to our scenes folder and then adding it to the build settings so the game knows about the level.

Once the level is added lets go ahead and create a new script called CharacterCreateGUI which, like the CharacterSelectGUI will run our GUI as well as be responsible for sending and receiving messages to and from the server.

using UnityEngine;
using System.Collections;
using Sfs2X.Core;
using Sfs2X.Requests;
using Sfs2X.Entities.Data;
using Sfs2X.Logging;

public class CharacterCreateGUI : ConnectionHandler
{
    bool debugMessages = false;
    bool showErrorDialog = false;
    string characterName = "";
    string sex = "";
    string characterClass = "";

    CharacterCreateHandler CharacterCreate;
    ErrorHandler errorHandler;
    new void Awake()
    {
        base.Awake();
        if (smartFox.IsConnected)
        {
            CharacterCreate = new CharacterCreateHandler();
            errorHandler = new ErrorHandler();
            // Register callback delegate
            smartFox.AddEventListener(SFSEvent.CONNECTION_LOST, OnConnectionLost);
            smartFox.AddEventListener(SFSEvent.LOGOUT, OnLogout);

            smartFox.AddLogListener(LogLevel.DEBUG, OnDebugMessage);

            // We are ready to get the character list
        }
        else
        {
            Application.LoadLevel("Lobby");
        }
    }

    #region Connection Callbacks
    public void OnConnectionLost(BaseEvent evt)
    {
        // Display popup, when user hits ok, return to lobby.
        //loginErrorMessage = "Connection lost / no connection to server";
        Application.LoadLevel("Lobby");
    }

    void OnLogout(BaseEvent evt)
    {
        Application.LoadLevel("Lobby");
    }

    public void OnDebugMessage(BaseEvent evt)
    {
        string message = (string)evt.Params["message"];
        if (debugMessages)
        {
            Debug.Log("**** DEBUG ****" + message);
        }
    }
    #endregion

}

This gets us up and running with our new scene. Next we can add the GUI. I'll be honest, I really didn't take much time on this, its just thrown together to show how we are creating our character:

    void OnGUI()
    {

        if (showErrorDialog)
        {

        }

        GUI.Label(new Rect(120, 116, 100, 100), "Name: ");
        characterName = GUI.TextField(new Rect(200, 116, 200, 20), characterName, 25);

        GUI.Box(new Rect(10, 10, 100, 300), "Classes");

        if (GUI.Button(new Rect(20, 50, 80, 50), "Fighter"))
        {
            characterClass = "Fighter";
        }
        if (GUI.Button(new Rect(20, 110, 80, 50), "Mage"))
        {
            characterClass = "Mage";
        }
        if (GUI.Button(new Rect(20, 170, 80, 50), "Rogue"))
        {
            characterClass = "Rogue";
        }
        if (GUI.Button(new Rect(20, 230, 80, 50), "Cleric"))
        {
            characterClass = "Cleric";
        }

        if (GUI.Button(new Rect(150, 170, 80, 50), "Male"))
        {
            sex = "M";
        }
        if (GUI.Button(new Rect(240, 170, 80, 50), "Female"))
        {
            sex = "F";
        }

        if (GUI.Button(new Rect(200, 265, 100, 25), "Create") || (Event.current.type == EventType.keyDown && Event.current.character == '\n'))
        {
            if (!string.IsNullOrEmpty(characterName) && !string.IsNullOrEmpty(sex) && !string.IsNullOrEmpty(characterClass))
            {
                ISFSObject data = new SFSObject();
                data.PutUtfString("characterName", characterName);
                data.PutUtfString("sex", sex);
                data.PutUtfString("characterClass", characterClass);
                ExtensionRequest request = new ExtensionRequest("createCharacter", data);
                smartFox.Send(request);
            }
        }
    }
We have a piece of the GUI prepared for any errors we receive in the character creation process. This includes duplicate names, invalid names, or if we forget to disable the character create button when they have maxed out their character slots. I chose a simple 4 class, setup. As you can see we wait unless they clicked at least one of the classes, a sex, and chosen a name. You can easily default the starting sex/class and put in the corresponding code to switch models when each button is clicked. From here, when you click the Create button it assembles the packet and ships it off to the server. Now we need to add the code and new class for what happens when get our response.

In the awake function we add the following:

            CharacterCreate = new CharacterCreateHandler();
            errorHandler = new ErrorHandler();

            // Personal message handlers
            handlers.Add("characterCreated", CharacterCreate.HandleMessage);
            handlers.Add("error", errorHandler.HandleMessage);
 
            CharacterCreate.afterMessageRecieved += AfterCharacterCreated;
            errorHandler.afterMessageRecieved += AfterErrorReceived;

This sets us up for the functions and new classes, first the functions AfterCharacterCreated and AfterErrorReceived.

    public void AfterErrorReceived()
    {
        // get the error message and set the error "dialog" to appear with the message.
        showErrorDialog = true;
    }

    public void AfterCharacterCreated()
    {
        UnregisterSFSSceneCallbacks();
        Application.LoadLevel("CharacterSelect");
    }

Next go ahead and create the 2 classes ErrorHandler and CharacterCreateHandler:

class ErrorHandler: IMessageHandler
{
    string errorMessage = "";
    public override void OnHandleMessage(Sfs2X.Entities.Data.ISFSObject data)
    {
        errorMessage = data.GetUtfString("error");
    }

}

public class CharacterCreateHandler : IMessageHandler
{
    public override void OnHandleMessage(Sfs2X.Entities.Data.ISFSObject data)
    {
        Debug.Log("Character created successfully, going back to character select");
    }

}

This concludes everything we need in order to create a character. In Part 2 I will cover receiving the message, creating the character and returning either an error or the success message - CharacterCreated.

Wednesday, May 11, 2011

GitHub updated with character creation code

So before I post all the information about the character create code, I just wanted to let everyone know that GitHub has already been updated with the latest code. Head on over to https://github.com/dragagon/AegisBorn to find the most recent code including the code to create a character. If you really like my tutorials and feel inclined to give something back, I do take donations. As always stay tuned for posts concerning how I implement character creation and enforce the character limit and unique names.

Preparing for character creation - [GameName]Account changes

So I have migrated my code from OpenJPA to Hibernate. I then tested my code to create a character and it works, but there was a catch. Because of earlier tutorials, we were taking the SfGuardUser and caching them in our Account class. Then we were taking the characters listed in the GuardUser.[GameName]CharacterCollection and passing those to the client. The problem is that when you create a new character, that data needs to be refreshed as it is now stale. This was also taking up a large chunk of memory for something we only used a few pieces of data from - MaxCharacters, number of characters, and the list of characters. So rather than keep all that data, I have modified the Account class to only keep the data we need.

So lets begin. First I removed the SfGuardUser property and the getSfGuardUser() method. I updated the name of setSfGuardUser(SfGuardUser guard) to setSfGuardUserValues(SfGuardUser guard). I then created 3 new properties and getters for those properties:
    private long guardID;
    private int maxCharacters;
    private int numCharacters;



    public long getGuardID()
    {
        return guardID;
    }
    
    public int getMaxCharacters()
    {
        return maxCharacters;
    }
    
    public int getNumCharacters()
    {
        return numCharacters;
    }

Then I replaced the code inside setSfGuardUserValues and created a helper function canAddCharacters:

    public void setGuardUserValues(SfGuardUser guard)
    {
        maxCharacters = guard.get[GameName]UserProfileCollection().iterator().next().getCharacterSlots().intValue();
        guardID = guard.getId();
        numCharacters = guard.get[GameName]CharacterCollection().size();
    }

    public boolean canAddCharacters()
    {
        return numCharacters < maxCharacters;
    }
This change made it so that we keep only the data we need in the account class since we do not update the account data from the server. We do this from the website.

These changes cause several changes throughout the system as we reconcile the missing/changed functions. The first is in DBLoginHandler. After the trace we need to call setGuardUserValues instead of setGuardUser:

    trace("Result: " + result); 
    if (guard.getPassword().equalsIgnoreCase(result))
    {
        player.setGuardUserValues(guard);
        send("loginsuccess", new SFSObject(), u);
    }

After that we need to go into CharacterListHandler and make several modifications. We need to add an entity manager, get our SfGuardUser, and load the characters the user has as well as get the account max characters and pass them along to the player. Here is the new function:

@Override
    public void handleClientRequest(User u, ISFSObject data) {
        EntityManager entityManager = (([GameName]Extension) this.getParentExtension()).getEntityManagerFactory().createEntityManager();
        entityManager.getTransaction().begin();
        try {
            trace("got character list request");
            World world = RoomHelper.getWorld(this);
            [GameName]Account player = world.getPlayer(u);
                        
            ISFSObject outputObject = new SFSObject();
            SfGuardUser guard = entityManager.find(SfGuardUser.class, player.getGuardID());
            player.setGuardUserValues(guard);
            outputObject.putInt("maxCharacters", player.getMaxCharacters());
            ISFSObject characterList = new SFSObject();
            int i = 0;
            trace("loading characters");
            Cipher cipher = player.GetCipher();
            for ([GameName]Character character : guard.get[GameName]CharacterCollection()) {
                domain.domainname.gamename.models.[GameName]Character ABCharacter = new domain.domainname.gamename.models.[GameName]Character(character);
                trace("outputting character" + i);
                characterList.putSFSObject("character" + i++, ABCharacter.createSFSObject());
            }
            outputObject.putSFSObject("characters", characterList);
            send("characterlist", outputObject, u);
        } catch (Exception e) {
            trace(e);
        } finally {
            entityManager.getTransaction().rollback();
        }
    }

With all these changes in place, we now have the most up to date list of characters sent to the player. This will be important when we add the character creation handler and the game returns to the character select screen after creating a character.

Moving away from OpenJPA

So last night I had hoped to have all the code ready to talk about creating a character and i would have if it had not been for one problem. OpenJPA 2.0.1 and possibly 2.1.0 have a problem creating an entity with an auto-increment index in a MySQL table. Because of this, I have moved my code to Hibernate3 that comes with Netbeans 7.0.

To do this you will need to go into [SmartFoxInstallDir]/SFS2X/lib and remove:

  • derby-10.5.3.1_1.jar
  • geronimo-*.jar
  • openjpa-all-2.0.1.jar
  • serp-1.13.1.jar
Then you need to go into your Netbeans project, right click and go to properties, go to Libraries and click the SmartFoxServer library and remove everything except sfs2x.jar and sfs2x-core.jar and hit OK.

Then Add Library... and look for Hibernate JPA and add that.

You will need to go into Entities/META-INF/persistence.xml and change the dropdown from EclipseLink to Hibernate.

Rebuild your projects, copy over META-INF to [SmartFoxInstallDir]/SFS2X/META-INF, copy the new Entities.jar to [SmartFoxInstallDir]/SFS2X/lib and copy over the Extension.jar to [SmartFoxInstallDir]/SFS2X/extension/[GameName]Extension and reboot your sfs2x-service service. Lastly copy all the jars in [GameName]Extension/dist/lib to [SmartFoxInstallDir]/SFS2X/lib.

This will get you out of OpenJPA and into Hibernate and will save you a few hours of headaches.

Remember, you can find the latest code over at my github open repository: https://github.com/dragagon/AegisBorn

Tuesday, May 10, 2011

Minor modification to the MessageHandler

So today I am preparing for the ability to create a character and I had to go back and fix part of my message handler. If you notice, I create 2 delegates for before receiving and processing the message and after, but you never see those delegates get called. To do this we need to make a few minor modifications. First, we need to change the name of HandleMessage to OnHandleMessage. Make this same change in the CharacterSelectHandler. Next we will add a new function to IMessageHandler called HandleMessage and it will look like this:


        public void HandleMessage(ISFSObject data)
        {
            if (beforeMessageRecieved != null)
            {
                beforeMessageRecieved();
            }
            OnHandleMessage(data);
            if (afterMessageRecieved != null)
            {
                afterMessageRecieved();
            }
        }


This will check if any functions have been added to beforeMessageReceived and afterMessageReceived and call them when necessary.


In the next post you will see why this is important as we set up to display the characters, a new character button and a back button that will log us off the server and put us back to the lobby.

Adding a Character Class, Unity3d Implementation Part 3

So after the last 2 parts we have set up our entity, our model, our handler, and we are ready to receive the message. The next thing we will be doing is creating a generic message handler and extending it with a CharacterSelectHandler.

To begin you will want to open up your character select scene and adding an empty game object. I called mine CharacterSelectGUI. I then created a new CharacterSelectGUI script which I attach to the game object. We are then going to add our disconnect functions to the script:


    public void OnConnectionLost(BaseEvent evt)

    {
        // Display popup, when user hits ok, return to lobby.
        //loginErrorMessage = "Connection lost / no connection to server";
        Application.LoadLevel("Lobby");
    }

    void OnLogout(BaseEvent evt)
    {
        Application.LoadLevel("Lobby");
    }

    public void OnDebugMessage(BaseEvent evt)
    {
        string message = (string)evt.Params["message"];
        if(debugMessages)
        {
            Debug.Log("**** DEBUG ****" + message);
        }
    }
Next we will add the Awake function and its components.



    bool debugMessages = false;
    CharacterSelectHandler CharacterSelect;

    void Awake()
    {
        base.Awake();
        if(smartFox.IsConnected)
        {
            CharacterSelect = new CharacterSelectHandler();

            // Register callback delegate
    smartFox.AddEventListener(SFSEvent.CONNECTION_LOST, OnConnectionLost);
    smartFox.AddEventListener(SFSEvent.LOGOUT, OnLogout);
    smartFox.AddLogListener(LogLevel.DEBUG, OnDebugMessage);

            // We are ready to get the character list
            ISFSObject data = new SFSObject();
    ExtensionRequest request = new ExtensionRequest("getCharacters", data);
    smartFox.Send(request);
        }
        else
        {
            Application.LoadLevel("Lobby");
        }
    }
This will set us up so that we send the new message the server is looking for. The server should then respond with our list of characters.

In our common code section I will be creating an abstract class that will set up 2 delegates so we can handle things before and after a message is received as well as what to do when the message is actually received. Create a new class called IMessageHandler.cs in common/scripts and it will contain the following code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Sfs2X.Entities.Data;

    public abstract class IMessageHandler
    {
        public delegate void BeforeMessageRecieved();
        public BeforeMessageRecieved beforeMessageRecieved;

        public delegate void AfterMessageRecieved();
        public AfterMessageRecieved afterMessageRecieved;

        public abstract void HandleMessage(ISFSObject data);
    }
As you can see it is fairly simple. Next we will create a CharacterSelectHandler that will listen for our event back from the server containing a list of characters:

using System;
using Sfs2X.Entities.Data;
using System.Collections.Generic;
using UnityEngine;

public class CharacterSelectHandler : IMessageHandler
{
    public List characterList;
    public int maxCharacters;
    public CharacterSelectHandler ()
    {
        characterList = new List();
        maxCharacters = 0;
    }
    public override void HandleMessage(ISFSObject data)
    {
        maxCharacters = data.GetInt("maxCharacters");
        ISFSObject characters = data.GetSFSObject("characters");
        foreach (string key in characters.GetKeys())
        {
            Debug.Log("Adding character: " + key);
            characterList.Add(Character.LoadFromSFSObject(characters.GetSFSObject(key)));
        }

        Debug.Log("Max: " + maxCharacters);
        Debug.Log("Characters: " + characterList.Count);

    }
}

This class uses a class we haven't created yet called Character. It is what our client will use to show all of our character's data. Later we will create a RemoteCharacter class that will be for other players and NPCs as we won't be able to see all the data about those characters. Much like you can't see all the data in any other MMO.

using System;
using Sfs2X.Entities.Data;
using UnityEngine;

public class Character
{
string name;
int level;
public Character ()
{
}
public static Character LoadFromSFSObject(ISFSObject data)
{
Character newCharacter = new Character();
try
{
newCharacter.name = data.GetUtfString("name");
newCharacter.level = data.GetInt("level");
}
catch(Exception e)
{
Debug.Log(e);
}
return newCharacter;
}
}
For now I have limited it to name and level, later we will expand it to include the other important data contained in the entity [GameName]Character class. For now this will do. The last thing we need to do is to add the handler to the CharacterSelectGUI's awake function. Just after the Log Debug handler add:
            // Personal message handlers
            handlers.Add("characterlist", CharacterSelect.HandleMessage);

This concludes part 3. By now you should be able to log in to the server and get back a list of characters. Tomorrow we will delve into the logic to see if the user can create a character and moving to a character select screen where they can chose some simple data and create a character. Stay Tuned!

Monday, May 9, 2011

Adding a Character Class, SmartFoxServer Implementation Part 2

Now that we have the character table set up and we have the entity in place, we need to create a message to send to the client that details our characters we have on the server. In order to do this we need to create a couple things. First we will be creating a new package in the Extension called models. Now this might sound confusing but we are going to create a model that has the same name as our entity. This is because we need to extend the functionality of the entity without modifying the file. Why, you ask? Why not just modify the file? Well if we modify the [GameName]Character.java and later down the line we decide we have to add some stats to our character, when you regenerate the file, any modifications you made will be lost. Lets say you remember to do it every time? The one time you forget you will be kicking yourself. So do yourself a favor and set it up correctly now to save you that headache later.

In [GameName]Extension create a package domain.domainname.gamename.models and inside add a new class called [GameName]Character.java. Inside this class we will create a property that is our entity and a constructor that takes the entity as a parameter:

public domain.domainname.gamename.entities.[GameName]Character character;


public [GameName]Character(domain.domainname.gamename.entities.[GameName]Character character)
{
    this.character = character;
}


After that we are going to add 2 helper functions. These functions will help us by making SFSObjects from our class and turning SFSObjects back into our class so that we can quickly serialize and deserialize our objects:



    public void loadFromSFSObject(ISFSObject data)
    {


    }


    public ISFSObject createSFSObject()
    {
        ISFSObject data = new SFSObject();
        data.putUtfString("name", character.getName());
        data.putInt("level", character.getLevel().intValue());
        return data;
    }


Now that we have our class set up. It is time to create our handler and add it to our list of handlers. The handler class is as follows:



package com.cjrgaming.aegisborn.handlers;


import domain.domainname.gamename.entities.[GameName]Character;
import domain.domainname.gamename.simulation.[GameName]Account;
import domain.domainname.gamename.simulation.World;
import domain.domainname.gamename.util.RoomHelper;
import com.smartfoxserver.v2.entities.User;
import com.smartfoxserver.v2.entities.data.ISFSObject;
import com.smartfoxserver.v2.entities.data.SFSObject;
import com.smartfoxserver.v2.extensions.BaseClientRequestHandler;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import javax.crypto.Cipher;


/**
 *
 * @author richach7
 */
public class CharacterListHandler  extends BaseClientRequestHandler {


    @Override
    public void handleClientRequest(User u, ISFSObject data) {
        try {
            trace("got character list request");
                        World world = RoomHelper.getWorld(this);
            [GameName]Account player = world.getPlayer(u);
            


            trace("loading properties");
            Properties serverProps = new Properties();
            try
            {
                InputStream in = this.getClass().getResourceAsStream("/com/cjrgaming/aegisborn/ServerConfig.properties");
                serverProps.load(in);
            }
            catch(IOException e)
            {
                trace(e);
            }


            trace("loaded properties");
            ISFSObject outputObject = new SFSObject();
            outputObject.putInt("maxCharacters", Integer.parseInt(serverProps.getProperty("MaxCharacters", "0")));
            ISFSObject characterList = new SFSObject();
            int i = 0;
            trace("loading characters");
            Cipher cipher = player.GetCipher();
            for([GameName]Character character : player.getGuardUser().get[GameName]CharacterCollection())
            {
                domain.domainname.gamename.models.[GameName]Character ABCharacter = new domain.domainname.gamename.models.[GameName]Character(character);
                trace("outputting character"+i);
                characterList.putSFSObject("character"+i++, ABCharacter.createSFSObject());
            }
            outputObject.putSFSObject("characters", characterList);
            send("characterlist", outputObject, u);
        } catch (Exception e) {
            trace(e);
        }
    }
}
With this in place we can add it to our list of handlers by going into [GameName]Extension and adding the following line:

        addRequestHandler("getCharacters", CharacterListHandler.class);
With this in place, we are ready to add the code to Unity3d to ask the server for our list of characters. We will cover this in part 3.

As an aside, if you wish to follow along with this project as it is being worked on, I have added it to GitHub under an open source repository. Rather than charge people for all the code for this series, I've decided that if you like it, you can donate to me. Otherwise, why teach. GitHub Repository