ShipWars Server Plugins HOWTO


by WolfPack Entertainment

October 24 2000

  1. Installing Plugins
  2. Loading Plugins
    1. Loading Upon Start Up
    2. Loading During Run Time
  3. Unloading Plugins
  4. Programming Your Own Plugins
    1. Hello Universe Plugin
    2. Compiling Plugins
    3. Rules and Procedures
    4. Timming
    5. Global Variables and Functions
    6. Object Manipulation
    7. Where To Get Help

This document describes how to install, load, and unload ShipWars Server plugins and how to program your own plugins.

Please note, currently only the Linux version of the ShipWars Server supports plugins.

Installing Plugins

Once you have obtained a plugin source (either from a third party or one of the sample plugins that came with the ShipWars Server), compile it and install it in acorrdance with the instructions provided by the third party.

Typically, you would first build the plugins by running make and then install them by running make install. This should place the compiled plugin binary in /home/swserv/plugins or possibly an alternate location if your ShipWars Server home directory is in a different location.

Make sure that you change the prefix install location in the Makefile or configure script as needed before installing.

Back to the top.

Loading Plugins

There are two ways to load plugins:

  1. At start up
  2. During run time

Loading Upon Start Up

Edit your ShipWars Server configuration file (usually located in /home/swserv/etc) and add the following at the end of the configuration file (note newer configuration files have a section marked off for plugins, put it there if you find such a section):


BeginPlugin
    Path = myplugin
    Flags =
    Arguments = -autostart
EndPlugin

Path specifies the path and file name of the plugin, if you do not specify an absolute path (a path starting with a / character) then the value contained in the parameter PluginsDir will be prefixed to the value you specify for Path.

The Flags parameter should be left blank for now, currently there are no settable flags.

Arguments contains a space seperated list of arguments you want to pass to the plugin upon startup. Note that each argument may not contain spaces or else it will be considered as two arguments. The argument -autostart must be specified as the first argument for any plugin designed to be loaded at startup.

Once you have added the plugins you want to load at startup, run the ShipWars Server as you normally do. Take note of any error messages during startup. When you shutdown the server, all plugins will be automatically unloaded (in reverse order from which they were loaded) before the server begins its standard shutdown sequence.

Loading During Run Time

To load a plugin when the ShipWars Server is currently running, use any ShipWars client capable of issuing server commands to connect to the universe in question. When connected, send the server command:

To see a list of currently loaded plugins and the usage of the plugin command.

Next make sure your vessel has a sufficient access level (access level of 0 is required on most universes to use the plugin command), then type:

Where myplugin is the file name of the plugin you want to load. If the file name specified is not an absolute path (a path starting with a / character), then the plugins directory will be prefixed to the file name that you specified.

Any arguments that follow will be passed to the plugin when it is loaded.

Back to the top.

Unloading Plugins

Plugins are automatically unloaded when:

The only way to manually unload a plugin is to use the server command plugin. First to see a listing of plugins currently loaded, type: Note that each plugin that is currently loaded has an associated ID (this is to distingush multiple loaded plugins of the same name). Then type: Where 12345 should be replaced with the ID of the plugin you want to unload.

Back to the top.

Programming Your Own Plugins

To begin writing your own plugins, you must first make sure you have the following:

  1. The latest ShipWars Server source (usually distributed with the standard XShipWars package).
  2. Linux or an OS that has loadable modules support for the ShipWars Server Plugins..
  3. Basic knowlage about the C programming language.
  4. Basic knowlage about the ShipWars universe.
  5. Basic knowlage about how to load, unload, and manage plugins.

Hello Universe Plugin

The ShipWars Server source comes with a sample plugin called hellouniverse. It is located in xsw#.#/server/plugins/samples, go to that directory and glance through hellouniverse.c. Get a general idea of it (don't worry about not being able to fully understand it right away).

Take note of the three primary functions, each of which is stated using #defines:

You should use these definations to declare your functions incase there are any changes in function declarations in future versions. To find out what these #defined values really are, read the very well commented header file xsw#.#/server/plugins.h. Note that xsw#.#/server/plugins/include/plugins.h is what your plugins need to #include, but this header file just basically #includes xsw#.#/server/plugins.h.

The golden rule is that each ShipWars Plugin needs three basic functions:

  1. An initializer function that is called once when the plugin is loaded.
  2. A management function which is called once per cycle (which is once each time the ShipWars Server loops).
  3. A shutdown function which is called just before the plugin is unlaoded.
Each of these functions will be explained in more detail in the
Rules and Procedures section.

Back to the top.

Compiling Plugins

Compiling a plugin is the same as compiling a regular shared library under Linux. Simply put -shared in your compiler command. For example, to compile the hellouniverse program you would type:

That would generate the hellouniverse plugin. If you want to install it, simply put it in /home/swserv/plugins.

You should recompile your plugins whenever you upgrade to a newer ShipWars Server version, just in case there is a change in the prototype declarations or structure members.

Back to the top.

Rules and Procedures

ShipWars Plugins follow the library procedural language, which basically says there needs to be three functions:

  1. An initialization function.
  2. A management function.
  3. A shutdown function.
Now in terms of ShipWars Plugins:

The initialization function (prototyped as SWPLUGIN_INIT_FUNCRTN SWPLUGIN_INIT_FUNC(SWPLUGIN_INIT_FUNCINPUT)) is the very first function that is executed when the plugin is loaded. This is your chance to allocate resources and set up global variables for the very first time. If this function returns 0, then the ShipWars Server will consider that this plugin was initialized properly. If this function returns -1, then the ShipWars server will abort the loading of this plugin and consider that an error has occured.

The management function (prototyped as SWPLUGIN_MANAGE_FUNCRTN SWPLUGIN_MANAGE_FUNC(SWPLUGIN_MANAGE_FUNCINPUT)) is called once per cycle (a cycle is each time the ShipWars Server loops), so this function is called very frequently. In this function, your plugin should perform routine tasks, such as checking it's reousrces for changed and see if it's time to perform schedualed activities (see the timming example for more information on schedualing and timming). If this function returns 0, then it will be called again on the next cycle. If this function returns -1, then the plugin will be unloaded.

The shutdown function (prototyped as SWPLUGIN_SHUTDOWN_FUNCRTN SWPLUGIN_SHUTDOWN_FUNC(SWPLUGIN_SHUTDOWN_FUNCINPUT)) is called just before this plugin is unloaded. This is your last chance to deallocate any resources (free memory) that this plugin has allocated. This function returns void.

Back to the top.

Timming

Timming in ShipWars is critically important, so here are some properties of timming you should be aware of:

Time compensation should be used in most physics calculations to keep things in sync. If the server, your program, or the comptuer starts to "lag" then common sense tells you that your time based calculations will be inaccurate. For instance if you have a speed calculating code speed = miles / time;, if things start to lag then the value of speed may become inaccurate. So to compensate for this you will need to calculate it as speed = miles / time * time_compensation;.

Lapsed time is the amount of time (in milliseconds) that was lost in the cycle due to lag. A theoretical cycle in the ShipWars system (without the interferance with lag) is 16 milliseconds. If there was no lag then the lapsed time would be 0 milliseconds, but in the real world it is not always the case so you should check the lapsed time if it is applicateable in your calculation.

Current time indicates the current time since midnight in milliseconds. This value is used very often, espessially in schedualing.

Schedualing specifies the next time to perform an action, this is used fairly often but difficult to explain at this point. There will be many examples demostrating schedualing below.

To introduce the beginner to timming smooth and gradually, we have an example source called timming.c. Look in the ShipWars Server source in the directory xsw#.#/server/plugins/samples/timming.c.

This sample source demostrates how to obtain time values and to perform schedualing. It prints a list of jokes and answers to each joke at specified intervals. Read through the timming.c source carefully and note how current time is fetched and checked against schedualed `next' times. Also note the need to reset timmers when the current time has cycled.


#include "../include/plugins.h"


int joke_code;
time_t last_millitime;
time_t next_joke;

static char *joke_list[] = {
        "Why did the chicken cross the star system?",
        "There was a Foster Farms Borg chasing him.",

        "How many Klingons does it take to screw in a light bulb?",
        "Bah! None, Klingons are not afraid of the dark!", 

        "Why did the Maqui shoot the Cardassian?",
        "Because he was there.",

        "What do Riker and Picard really do the in the Ready Room?",
        "They behave like mature non-gay adults (and you believe that?)",

        "Who was the very first Borg?",
        "Bill Gates!",

        "How come Mog never spoke in the DS9 series?",
        "He was in a huff ever since Dax rejected him."
};

/*
 *      This function is just for our plugin, it resets the `next'
 *      time when the current time of day has cycled.
 */
void ResetTimmers(void)
{
        next_joke = 0;

        return;
}

SWPLUGIN_INIT_FUNCRTN SWPLUGIN_INIT_FUNC(SWPLUGIN_INIT_FUNCINPUT)
{
        /* Upon initialization, reset our globals. */
        ResetTimmers();
        joke_code = 0;

        /* Get current time of day in milliseconds. */
        last_millitime = *(in->cur_millitime);


        return(0);
}

SWPLUGIN_MANAGE_FUNCRTN SWPLUGIN_MANAGE_FUNC(SWPLUGIN_MANAGE_FUNCINPUT)  
{
        int (*con_notify)(int, char *);
        time_t cur_millitime;
#define total_joke_lines        (sizeof(joke_list) / sizeof(char *))


        /* Get pointer to connection notify functino. */
        con_notify = in->con_notify_fptr;
  

        /* Get current time in milliseconds from ShipWars Server. */
        cur_millitime = *(in->cur_millitime);

        /* Now check if the time `cycled' */
        if(cur_millitime < last_millitime)
                ResetTimmers();

        last_millitime = cur_millitime;


        /* Is it time to print a joke or an answer? */
        if(next_joke <= cur_millitime)
        {
            if((joke_code >= 0) && (joke_code < total_joke_lines))
                con_notify(-1, joke_list[joke_code]);

            if(joke_code & 1)
            {
                /* Odd number (answer). */

                /* Schedual next time to print joke. */
                next_joke = cur_millitime + 10000;
            }
            else
            {
                /* Even number (joke). */

                /* Schedual next time to print answer. */
                next_joke = cur_millitime + 5000;
            }

            /* Increment joke code. */
            joke_code++;

            /* Done with our jokes? */
            if(joke_code > total_joke_lines)
            {
                /* Yup, we're all done! */
                joke_code = 0; 

                return(-1);     /* Have the server unload this plugin. */
            }
        }
        
        return(0);
}
            
SWPLUGIN_SHUTDOWN_FUNCRTN SWPLUGIN_SHUTDOWN_FUNC(SWPLUGIN_SHUTDOWN_FUNCINPUT)
{
        return;
}

Back to the top.

Global Variables and Functions

The ShipWars Server allows access to its global variables and functions to your plugin by way of the plugin_data_ref_struct structure (see xsw#.#/server/plugins.h for this prototype).

As of version 1.33d, the plugin_data_ref_struct structure contains:


typedef struct {

        time_t  *cur_millitime,
                *cur_systime,
                *lapsed_millitime;

        double  *time_compensation;

        xsw_object_struct ***xsw_object;
        int *total_objects;

        connection_struct ***connection;
        int *total_connections;
                                        
                                        
        /* Notifies connection (input 1) of message (input 2). */ 
        int (*con_notify_fptr)(int, char *);
  
        /* Checks if object is valid and non-garbage. First and second
         * inputs are the objects pointer array and total_objects
         * (respectivly) and the third input is the object index number.
         * Returns 0 if object is valid and non-zero if it's invalid.
         */
        int (*obj_is_garbage)(xsw_object_struct **, int, int);

        /* Creates a new object. It may change the values of the given
         * pointer to the objects pointer array and total objects 
         * (inputs 1 and 2). The type specified must not be garbage
         * (input 3). When in doubt, pass 1.
         * Returns the index number of the newly created object or -1
         * on error.
         */
        int (*obj_create)(xsw_object_struct ***, int *, int);

        /* Recycles the object specified by the index value (input 3) from
         * the given objects pointer array and total (inputs 1 and 2).
         */
        void (*obj_recycle)(xsw_object_struct ***, int *, int);

        /* Checks if the two objects (inputs 3 and 4) are valid and in the
         * same sector. The given objects pointer array and total are inputs
         * 1 and 2.
         */
        int (*obj_in_same_sector)(xsw_object_struct **, int, int, int);

        /* Checks if the two objects (inputs 3 and 4) are valid, in the
         * same sector, and in contact with each other.
         * The given objects pointer array and total are inputs 1 and 2.
         */
        int (*obj_in_contact)(xsw_object_struct **, int, int, int);
  
        /* Checks if the two objects (inputs 3 and 4) are valid, in the
         * same sector, and within the specified range (input 5) in
         * Real units. The given objects pointer array and total are inputs
         * 1 and 2.
         */
        int (*obj_in_range)(
                xsw_object_struct **, int,
                int, int,
                double          /* Distance in Real units. */
        );
         
        /* Checks if the two objects (inputs 3 and 4) are valid, in the
         * same sector, within the specified range (input 5) in
         * Real units, if the first object is at the specified bearing
         * to the second object and within the specified variance in
         * radians (inputs 6 and 7).
         * The given objects pointer array and total are inputs 1 and 2.  
         */
        int (*obj_in_vector_range)(
                xsw_object_struct **, int,
                int, int,
                double,         /* Distance in Screen units. */
                double, double  /* Bearing and bearing variance, in radians. */
        );
           
        /* Sends out values to all connections interested about the
         * specified object (input 3).
         * The given objects pointer array and total are inputs 1 and 2.
         */
        void (*obj_sync_clients)(xsw_object_struct **, int, int);
           
} plugin_data_ref_struct;

This structure contains a list of pointers to global variables and functions that your plugin is allow to access. See the timming example for some examples on how to obtain these pointers and their pointed to values.

A pointer to the plugin_data_ref_struct structure is passed to every one of your Plugin function that the ShipWars Server calls. Be forwarned that the address values in the plugin_data_ref_struct structure may change during execution, so your plugin functions should get the latest values at the beginning of the init, manage, and shutdown functions.

Back to the top.

Object Manipulation

Basic manipulation of objects is demostrated in an example called moveobj.c (which is also available from the ShipWars Server source at xsw#.#/server/plugins/samples/moveobj.c).

This example demostrates how to obtain the pointer to the objects list for the currently loaded universe, check certain values of each object in the list and manipulate its values (move the object in this example).


#include "../include/plugins.h"


#define TOTAL_MOVES     3
int move_code;

time_t last_millitime;
time_t next_movement;


/* See timming.c for why timmers have to be reset. */
void ResetTimmers(void)
{
        next_movement = 0;

        return;
}

SWPLUGIN_INIT_FUNCRTN SWPLUGIN_INIT_FUNC(SWPLUGIN_INIT_FUNCINPUT)   
{
        /* Upon initialization, reset our globals. */
        ResetTimmers();
        move_code = 0;

        /* Get current time of day in milliseconds. */
        last_millitime = *(in->cur_millitime);

        return(0);
}

SWPLUGIN_MANAGE_FUNCRTN SWPLUGIN_MANAGE_FUNC(SWPLUGIN_MANAGE_FUNCINPUT)
{
        xsw_object_struct ***xsw_object;
        int *total_objects;
        time_t cur_millitime;
        void (*obj_sync_clients)(xsw_object_struct **, int, int);


        /* Get pointer to objects array. */
        xsw_object = in->xsw_object;
        total_objects = in->total_objects;

        /* Get pointer to server functions. */
        obj_sync_clients = in->obj_sync_clients;
 
        /* Get current time in milliseconds from ShipWars Server. */
        cur_millitime = *(in->cur_millitime);
 
        /* Now check if the time `cycled' */
        if(cur_millitime < last_millitime)
                ResetTimmers();

        last_millitime = cur_millitime;


        /* Is it time to move objects? */
        if(next_movement <= cur_millitime)
        {
            int object_num;
            xsw_object_struct *obj_ptr;
            double x_offset, y_offset;


            /* Calculate movement offset (in Real units). */
            switch(move_code)
            {
              case 0:
                x_offset = 0;
                y_offset = 2.5;
                break;
        
              case 1:
                x_offset = 0;
                y_offset = -4.2;
                break;
        
             case 2:
                x_offset = 2.8;
                y_offset = 1.3;
                break;
            }

            /* Move each object. */
            for(object_num = 0; object_num < *total_objects; object_num++)
            {
                /* Get pointer to object from array. */
                obj_ptr = (*xsw_object)[object_num];

                /* Skip this object of it is not allocated. */
                if(obj_ptr == NULL)
                    continue;

                /* Skip this object if its of type garbage or error. */
                if(obj_ptr->type <= XSW_OBJ_TYPE_GARBAGE)
                    continue;

                /* Skip this object if its not a type HOME object. */
                if(obj_ptr->type != XSW_OBJ_TYPE_HOME)
                    continue;
              
                /* Move this object. */
                obj_ptr->x += x_offset;
                obj_ptr->y += y_offset;
        
                /* Update object position to clients. */
                obj_sync_clients(*xsw_object, *total_objects, object_num);
            }

            /* Increment move code. */
            move_code++;
                
            /* Finished? */
            if(move_code >= TOTAL_MOVES)
                return(-1);     /* Return -1 to indicate want to exit. */

            /* Schedual next movement in 2.5 seconds. */
            next_movement = cur_millitime + 2500;
        }

        return(0);
}

SWPLUGIN_SHUTDOWN_FUNCRTN SWPLUGIN_SHUTDOWN_FUNC(SWPLUGIN_SHUTDOWN_FUNCINPUT)
{
        return;
}

All timming was done in units of milliseconds, positions of objects were in Real units. If you are not sure about units in the ShipWars system, please read the building documents or consult someone familiar with universe building. Available XSW_OBJ_TYPE_* values are defined in xsw#.#/include/objects.h.

Back to the top.

Where To Get Help

Any question related to coding for ShipWars Server plugins can be asked on the ShipWars mailing list, there you can find many experianced coders who are intimatly familiar with coding such plugins.

Back to the top.