Write a core rule plugin
If you want to write a plugin for RPGFramework to support your favorite roleplaying system, you start by writing a plugin that provides the data models for a player characters and all relevant data (skills, spells, etc.) and provides mechanisms to encode or decode player characters to or from a byte buffer. In RPGFramework terms, this is called a “Core” plugin.
The following requirements must be met:
- The class representing a player character must implement the interface de.rpgframework.character.RuleSpecificCharacterObject
- Your main plugin class needs to implement the interfaces de.rpgframework.RulePlugin and de.rpgframework.core.CommandBusListener
- The JAR file you create for your plugin must provide the service de.rpgframework.RulePlugin, which of course points to your main plugin class.
The referenced classes are available in the rpgframework-api JARs, you find here.
1. Writing a player character class
The RPGFramework allows managing player characters. There are some aspects of a player character that are generic: a link to a PDF background story or a set of images describing the character. Other aspects are rule specific, like a data structure to store attributes, spells, skills, etc. When writing a core plugin, you basically tell the RPGFramework how to deal with the later by implementing the interface RuleSpecificCharacterObject.
Example player character class
import de.rpgframework.character.RuleSpecificCharacterObject; public class MyRPGCharacter implements RuleSpecificCharacterObject { private String name; private byte[] image; //------------------------------------------------------------------- public MyRPGCharacter() { } //------------------------------------------------------------------- /** * @see de.rpgframework.character.RuleSpecificCharacterObject#getImage() */ @Override public byte[] getImage() { return image; } //------------------------------------------------------------------- /** * @see de.rpgframework.character.RuleSpecificCharacterObject#getName() */ @Override public String getName() { return name; } //------------------------------------------------------------------- public void setName(String name) { this.name = name; } //------------------------------------------------------------------- public void setImage(byte[] image) { this.image = image; } }
You will notice there are only to methods to implement: getName() and getImage(). The later may return NULL, if you don’t want to deal with character images.
2. Writing a main class for the core plugin
Having defined a rule specific character class, you can continue by implementing the main class for your rule plugin, which implements the interface RulePlugin. On startup every RPGFramework application searches for all instances of this interfaces in the classpath and instantiates them. By just implementing the RulePlugin interface you allow the RPGFramework to access some management information about your plugin – e.g. what roleplaying system is implemented and what features are provided.
! Your plugin main class needs to have an empty constructor, since it is instantiated by the Java ServiceLoader
For a core plugin the required feature is PERSISTENCE, meaning serializing and deserializing player characters to a byte buffer (not to disk – this is done by the RPGFramework). The request to serialize or deserialize a character object is distributed via the CommandBus class, so in order to provide this functionality you also need to implement the CommandBusListener interface.
The RulePlugin interface you implement is a Generic interface, which needs to be parameterized with an extension of the RuleSpecificCharacterObject – your player class.
Example plugin main class
package org.example.myrpg; import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.List; import de.rpgframework.ConfigOption; import de.rpgframework.RulePlugin; import de.rpgframework.RulePluginFeatures; import de.rpgframework.core.CommandResult; import de.rpgframework.core.CommandType; import de.rpgframework.core.RoleplayingSystem; /** * @author prelle * */ public class MyRPGCorePlugin implements RulePlugin { ... methods explained in detail below ... }
2.1 Identify the roleplaying system this plugin covers
There are countless roleplaying systems out there. To prevent that plugin developers use different identifiers for the same roleplaying system, a predefined set of identifiers exist. Of course this list is incomplete. If you miss the system you are developing for, write a mail to genesis at rpgframework.de .
@Override public RoleplayingSystem getRules() { return RoleplayingSystem.CTHULU; }
There are some restrictions to the identifier:
- Every edition of a roleplaying system needs its own identifier.
- The identifier technically identifies a specific core rule plugin and its compatible fellow plugins. So if developer writes a Shadowrun 5th Edition core rule plugin and reserves the Identifier SHADWORUN5ED and you want to develop a concurring core rule plugin, you should reserve a different identifier.
2.2 The plugin identifier
The identifier is used to uniquely identify the plugin within the RoleplayingSystem.
By convention you should always return “CORE” here. The RPGFramework ensures that plugins with this ID are loaded first.
@Override public String getID() { return "CORE"; }
2.3 A human readable plugin name
This name is used when listing all loaded plugins. You may return a static String here, but I recommend that you define an implementation title attribute in your JARs MANIFEST and return it here.
@Override public String getReadableName() { if (this.getClass().getPackage().getImplementationTitle()!=null) return this.getClass().getPackage().getImplementationTitle(); return "My cool RPG basic data model"; }
2.4 Features provided by the plugin
A core rule plugin is required to provide the PERSISTENCE feature. If your plugin includes other features, you may add them here too.
private static List FEATURES = new ArrayList(); //------------------------------------------------------------------- static { FEATURES.add(RulePluginFeatures.PERSISTENCE); } //------------------------------------------------------------------- @Override public Collection getSupportedFeatures() { return FEATURES; }
2.5 Plugin configuration
Some plugin may be configurable by the user. This method returns the options that can be configured by instances of the RPGFrameworks ConfigOption . It is up to the plugin to evaluate and store these options.
If you don’t have any configuration options, just return NULL.
@Override public List getConfiguration() { return null; }
2.6 The “About” section
You can return an Java InputStream of a HTML document here.
@Override public InputStream getAboutHTML() { return ClassLoader.getSystemResourceAsStream("i18n/splittermond-print.html"); }
Use this document to name authors and state copyright information. ATTENTION: Due to some restrictions I could not sort out yet, normal HTML entities cannot be used here unless you defined them yourself in the DOCTYPE instruction.
Example of a About HTML
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE doc_type [ <!ENTITY nbsp " "> <!ENTITY amp "&"> <!ENTITY copy "©"> ]> <html> <head> <title>Splittermond PDF Writer</title> </head> <body> <p> © 2014 by Anja Prelle<br/> </p> <p> The following third party libraries are used: </p> <ul> <li><b><a href="https://github.com/ymasory/iText-4.2.0">iText 4.2.0</a></b>, © Bruno Lowagie, Paulo Soares <br/> License: : <a href="https://www.mozilla.org/MPL/2.0/">Mozilla Public License (MPL)</a></li> </ul> </body> </html>
In the end you’ll find this information in “Settings -> About”, where there is a single page with boxes for each plugin.
2.7 Initializing the plugin
After the plugin class is instantiated, the RPGFramework calls the init() method. Usually this is the hook to load all definitions (e.g. skills, spells) or whatever you need to do to set up your core rule framework.
In the end you should register your plugin to the CommandBus to receive requests to serialize or deserialize characters.
@Override public void init() { ... do whatever you need ... CommandBus.registerBusCommandListener(this); }
2.8 Listening to the command bus
This is the part where you really interact with applications using the RPGFramework. The CommandBus is the central hub to deliver requests to the plugins. This is usually done in two steps: If a part of the application needs a task done, it requests if there is a plugin that is willing to perform a command. If there is a positive response, the command itself is fired as an event on the Bus, along with the defined parameters (see the JavaDoc for CommandType).
Your job is to implement to methods:
- boolean willProcessCommand(..) that returns TRUE or FALSE, depending if your plugin is able to perform the task or not
- CommandResult handleCommand(..) to perform the action and eventually return the output.
Let us have a look at the CommandTypes ENCODE and DECODE. From the JavaDoc we know that it takes two arguments:
- The roleplaying system in question
- A byte array (DECODE) or a RuleSpecificCharacterObject (your MyRPGCharacter) (ENCODE)
and that it returns a RuleSpecificCharacterObject (DECODE) or an byte array (ENCODE)
//------------------------------------------------------------------- /** * @see de.rpgframework.core.CommandBusListener#willProcessCommand(java.lang.Object, de.rpgframework.core.CommandType, java.lang.Object[]) */ @Override public boolean willProcessCommand(Object src, CommandType type, Object... values) { switch (type) { case ENCODE: if (values[0]!=RoleplayingSystem.MYRPG) return false; if (values.length<2) return false; return (values[1] instanceof MyRPGCharacter); case DECODE: if (values[0]!=RoleplayingSystem.MYRPG) return false; if (values.length<2) return false; return (values[1] instanceof byte[]); default: return false; } } //------------------------------------------------------------------- /** * @see de.rpgframework.core.CommandBusListener#handleCommand(java.lang.Object, de.rpgframework.core.CommandType, java.lang.Object[]) */ @Override public CommandResult handleCommand(Object src, CommandType type, Object... values) { switch (type) { case ENCODE: MyRPGCharacter model = (MyRPGCharacter)values[1]; byte[] raw; try { raw = ... convert to byte array .. return new CommandResult(type, raw); } catch (DecodeEncodeException e) { return new CommandResult(type, false, e.toString()); } case DECODE: raw = (byte[])values[1]; try { model = .. convert from byte array ... return new CommandResult(type, model); } catch (DecodeEncodeException e) { return new CommandResult(type, false, e.toString()); } default: return new CommandResult(type, false, "Not supported"); } }
3. Installing the plugin
To use your plugin, simply put it in the library folder of the RPGFramework application. In case of GENESIS this is your installation directory and the “libs” subfolder.
If you start the application – for instance GENESIS – and go to the settings and choose “Plugins”, you get a list of all plugins sorted by RoleplayingSystem.
The plugin version is taken from the MANIFEST.MF attribute “Implementation-Version”, while the author is taken from the “Implementation-Vendor” attribute.