Skip to content

Server Side API

One of the most important aspects of server side development is accessing the server APIs. These provide a large number of functionalities that can be composed together to build your game logic.

SmartFoxServer's API are split in 4 different categories:

  • Base API: provide the main functionalities to manage Rooms, Users, moderation, delayed tasks and more
  • Game API: dedicated to advanced match making and user invitations
  • MMO API: provide the MMO-related functionalities
  • Buddy List API: dedicated to managing buddies and buddy lists

We have seen in the previous chapters that all Extensions inherit from the SFSExtension base class. This in turn provides access to all the API and a number of other useful methods:

  • getApi()
  • getGameApi()
  • getMMOApi()
  • getBuddyApi()

These provide access to the API we just described but there's also a few more useful ones:

  • getParentZone(): gives access to the parent Zone
  • getParentRoom(): gives access to the parent Room, if the Extension is deployed at Room level
  • getCurrentFolder(): provides the relative path to the Extension's own folder, e.g. extensions/{name-of-extension}/
  • getLogger(): provides the Extension's logger if you need a finer grained logging system
  • trace(): a quick way to log messages, using the default INFO log level

API usage examples

Below you'll find several examples of how to use the API in your server-side code.

Creating Rooms

Here's an example of how to check if a certain Room exists in the current Zone. If it doesn't exist, we'll create it.

public class MyExtension extends SFSExtension
{
    static final String LOBBY_ROOM_NAME = "LobbyRoom"; 

    @Override
    public void init()
    {
        var theRoom = getParentZone().getRoomByName(LOBBY_ROOM_NAME);

        if (theRoom == null) 
        {
            var crs = new CreateRoomSettings();
            crs.setName(LOBBY_ROOM_NAME);
            crs.setMaxUsers(120);
            crs.setMaxSpectators(0);

            try
            {
                getApi().createRoom(getParentZone(), crs, null, false, null, false, false);
            }
            catch (SFSCreateRoomException e)
            {
                trace("Failed to create Lobby room: " + e.getMessage());
            }
        }
    }
}

Normally the createRoom() method takes 3 parameters, like this:

getApi().createRoom(getParentZone(), crs, null);

with the current Zone, the room settings and the owner of the Room. Passing null as the owner will make it a server-owned Room.

However in this example we're using extra arguments to tell the server we don't want to generate any events after creating the Room. The reason for this is that at init() time the server is not yet ready to communicate with the outside world, so we don't want to send any updates as there are no clients.

The full method signature is this:

createRoom(zone, settings, owner, joinIt, roomToLeave, fireClientEvent, fireServerEvent) 

We already know about the first three arguments. The remaining are as follows:

  • joinIt (bool) true if you want the Room to be joined immediately by its owner
  • roomToLeave (Room) a Room that the owner should leave after the autojoin is complete (if needed, null otherwise)
  • fireClientEvent (bool) send an update to all interested clients
  • fireServerEvent (bool) triggers a server side ROOM_ADDED event

Creating NPCs and joining

In this example we'll add a waiter NPC in every Room that supports it: we start with a list of Rooms that should have a waiter and check if they exist at runtime. For each Room we find we'll spawn a different NPC, give it a unique name, and join it in the Room.

public class MyExtension extends SFSExtension
{
    List<String> roomsWithWaiters = List.of(
        "16-bit Bar", 
        "Retro Lounge", 
        "Bitmap Cocktail Room"
    );
    int count = 1;

    @Override
    public void init()
    {
        var zone = getParentZone();

        for (String roomName : roomsWithWaiters) 
        {
            var theRoom = zone.getRoomByName(roomName);

            if (theRoom != null) 
            {
                try
                {
                    var waiter = getApi().createNPC("Waiter" + count, zone);
                    getApi().joinRoom(waiter, theRoom);
                    count++;
                }
                catch (Exception ex)
                {
                    getLogger().warn("Failed creating NPC: " + ex.getMessage());
                }
            }
        }
    }
}

NPCs can be useful to add interactivity in games and social apps. In SmartFoxserver they are represented by regular User objects (as any other connected user) but they can be controlled from the server side to interact in all kind of ways with other clients. You'll learn more about NPCs in the more advanced articles.

Setting UserVariables on login

In the following example we want to set a number of UserVariables as soon as the User has logged in the Zone. This can be done by listening to the USER_JOIN_ZONE event.

public class MyExtension extends SFSExtension
{   
    @Override
    public void init()
    {
        addEventHandler(SFSEventType.USER_JOIN_ZONE, this::onJoinZone);
    }

    private void onJoinZone(ISFSEvent event) throws SFSException
    {
        var targetUser = (User) event.getParameter(SFSEventParam.USER);

        var userVars = List.of(
            (UserVariable) new SFSUserVariable("avatar", "kermit.png"),
            new SFSUserVariable("ctry", "Spain"),
            new SFSUserVariable("online", true)
        );

        getApi().setUserVariables(targetUser, userVars);
    }
}

The event will trigger every time a new User is logged in the current Zone. Our handler prepares a List of UserVariables and calls the setUserVariables() method will also trigger a client side event to all interested Users. In this case the interested User will be all clients that are joined in the same Room(s) joined by the targetUser.

Best practices

Here are a few important recommendations for writing server side code correctly:

Always search the API first

SmartFoxServer provides many data classes that represent various entities such as: Zone, Room, User, Session etc. They are mainly found under the com.smartfoxserver.entities package.

In the vast majority of cases these data classes should be used for reading properties only, rather than writing them. For example making direct changes to the values of a Zone or Room will only cause a local change but no update will be sent to clients.

An example of this is setting User Variables for a client. If you consult the javadoc, you will notice that the User object exposes a setVariable method which you might want to use. However this will only affect the server-side, and no updates will be sent to the clients (which is necessary to keep the correct synchronization).

The proper way to proceed is to use the API setUserVariables method that can automatically update all the necessary clients and also trigger a server-side event that can be caught by another part of the Extension.

In other words, avoid this:Dont

User kermit = getParentZone().getUserByName("Kermit");
var userVars = List.of(
    (UserVariable) new SFSUserVariable("nickname", "The Frog")
);

kermit.setVariables(userVars);

Instead, do this:Do

User kermit = getParentZone().getUserByName("Kermit");
var userVars = List.of(
    (UserVariable) new SFSUserVariable("nickname", "The Frog")
);

getApi().setUserVariables(kermit, userVars);

Room Extensions

Room Extensions are great to handle game logic because their scope is the Room itself, so they can deal with what's happening in a specific game and ignore the rest. Also each Room can run a different Extension, depending on its designation.

Zone Extensions

Zone Extensions are useful when dealing with wider scope tasks, especially those that are not tied to a specific Room. For example: creating or editing accounts, customizing User profiles, managing match-making, dealing with Buddies lists and so forth.

Sharing data across extensions

Sometimes objects need to be shared among Room and Zone Extensions. Since Extension are separated by class loaders it's best to plan the data sharing in advance. Our suggestion is to define a game model in a separate project, then use it as a dependency/library for all Extensions used by the game. The model classes should be deployed to the extensions/_shared/ folder to be able to share them across all Extensions.

For more details see here