Mod Development

From GM Forge
Jump to: navigation, search

This page is intended to guide you through an overview of a GM Forge modification to understand its structure and basic design paradigms.

Technologies

GM Forge provides an open platform for mod development. It is built as an Electron app, hosting your gaming session using Node.js. Within the GM Forge application, mods can be easily created using a standard set of web development technologies:

  • JSON - JSON is used for storing data, system templates, world files, and compendium packs are all stored in JSON format.
  • HTML - HTML is used to define structure and content for any interfaces your mod generates.
  • JavaScript - JavaScript is used to dynamically process your input data (JSON), control rendering behavior, and provide iteractivity to users.
  • CSS - CSS is used to style your sheet, modifying it's layout and appearance.

Mod Structure

The root directory for GM Forge application files is:

Steam/steamapps/common/GM Forge - Virtual Tabletop/public

For the remainder of this documentation, we will use the term public to refer to the root directory. The standard structure for a GM Forge modification is to create a directory within the workshop folder of this public directory. For example:

Steam/steamapps/common/GM Forge - Virtual Tabletop/public/workshop/my-gmforge-mod

It is recommended to use this structure for your mod even if you don't intend to publish your modification back to the Steam Workshop. Within this mod directory, you can include the following named sub-directories, each of which serves a recommended purpose:

  • css - Include stylesheet files here that you intend to load for customizing the appearance of your mod.
  • fonts - Include any custom font families you intend to define. Most web-compatible font types are supported.
  • images - Include static image assets you use within your mod.
  • scripts - Include JavaScript files within this directory. Any files within this directory with the .js file extension will be automatically loaded upon app initialization if your mod is whitelisted.
  • html - HTML template files that will be loaded and rendered by the mod.
  • packs - If your mod provides pre-defined compendium packs, include those .json files in the packs directory.
  • sounds - Include any audio files or sound effects required by your mod in this sounds directory.
  • templates - A mod may define a game system template, if your mod provides a game system the .json template for that system should be placed here.
  • worlds - A mod may include some pre-populated world files, place any such world files in the worlds directory.
  • content - Arbitrary additional content that does not fit into one of the above categories.

Hello World

Now that you understand the basic structure of a GM Forge mod, we can create a simple modification that will run when the application is loaded. We can create a test mod by creating the following file (and it's parent directory structure)

Steam/steamapps/common/GM Forge - Virtual Tabletop/public/workshop/my-gmforge-mod/scripts/hello.js

Inside this hello.js file, write the following code:

alert("Hello GM Forge!");

After saving the file, if you load a game in GM Forge (you may need to enable your mod, if you use whitelisting) you will see a pop-up alert message that confirms that your mod files are being processed successfully by the application!

System Templates

A game system template defines a set of rules and a standard data model for implementing a game system in GM Forge. Note that many systems already have implementations so be sure to check the Steam Workshop as a starting place, however, if you want to implement a system from scratch, you will need to define a template file. Template files are JSON data structures that teach GM Forge the basic information it needs to know about your game system. Your template file should live within the templates folder of your mod directory. For example:

Steam/steamapps/common/GM Forge - Virtual Tabletop/public/workshop/my-gmforge-mod/templates/my-mod-system.json

The basic structure of a template file is the following:

{
  "info": {
    "name": {
      "current": "My Custom Game System"
    },
    "img": {
      "current": "/images/my-logo.png"
    }
  },
  "identifier": "my_mod_system",
  "build": "v2",
  "version": 1,
  "initiative": {},
  "actors": {},
  "elements": {},
  "page": {},
  "security": {},
  "constants": {},
  "tables": {},
  "tags": {},
  "dice": {},
  "display": {},
  "effects": {},
  "grid": {},
  "actions": {}
}

This structure provides an overview of the top-level structure of the template file. Each of these JSON objects (i.e. initiative, actors, elements, etc...) has it's own data structure within which you can define how your game system works. We will go into details on each of those sections later.

NOTE: The build version controls whether your game system is designed for the original (v1) GM Forge data model or the updated (August, 2018) v2 data model.

For details on the data structure for each of the template file sections, please refer to the following subpages (TODO)

  • initiative
  • actors
  • elements
  • page
  • security
  • constants
  • tables
  • tags
  • dice
  • display
  • effects
  • grid
  • actions

Entities, Actors, and Elements

Worlds in GM Forge are composed of many different entities. Entities are basically "things" that are present in your world. It will be helpful to have a basic understanding of the terminology used across four major categories of entities:

  1. Maps - Maps are areas within your world. Maps can contain tokens which are mapped to actors.
  2. Actors - Actors are a general category most frequently used for player or non-player characters, but could also include other concepts like vehicles, ships, or tactical units (for example). In general, actors are the entities who take actions within a world.
  3. Elements - Elements are a general category that encompasses the types of things that an actor might possess. These could be as straightforward as items like weapons or armor, or more abstract like talents, class features, or abilities.
  4. Pages - Pages are notes which provide narrative, handouts, or organizational functionality to describe the world and its encounters.

UI Definitions and Render Functions

Each interface elements in GM Forge is typically referred to as an app. These apps are displayed to the client by injecting HTML into the DOM. The HTML that is rendered by each app is defined by the display configuration of your world template.

For actors (characters) or elements (items), the HTML that is displayed is controlled by the content key for each entity type. For example, consider an example world template that defines Character as a type of actor. The following data defined within the world-builder template would render a simple paragraph of text for each character asset.

"display": {
  "actors": {
    "Character": {
      "content": "<div class="my-custom-ui"><h1>My Character UI</h1><p>This is a UI rendered directly from the world template.</p></div>"
    }
  }
}

Since the HTML you will define for your UI will undoubtedly grow far more complicated than this simple example, a powerful method for having complete control over the rendering of a template is to define a ui-name for your render. This relies upon the custom <ui> HTML element which has special significance in GM Forge. Replace the content for Character with:

"content": "<ui name='MY_CHARACTER_UI'/>"

When GM Forge renders the <ui> HTML tag, it will substitute this tag with the result returned by a sync.render() function which can be defined inside a JavaScript file included with your mod. A render is defined for a certain UI name by defining a function which returns a jQuery HTML object which is inserted as the content for the UI element. For example, in our hello.js (or another) file, we can define a render function as:

sync.render("MY_CHARACTER_UI", function(obj, app, scope) {
    let html = `
    <div class="my-custom-ui">
        <h1>My Character UI</h1>
        <p>This is a UI rendered directly from the world template.</p>
    </div>`;
    return $(html);
});

While this seems like a more complex way to achieve the same result, as the HTML required to create a dynamic and interactive character sheet grows in complexity, relying upon render functions can be far more powerful as it allows for the HTML to be constructed programmatically.

Including CSS Styles

To include a custom stylesheet which can be used to customize the appearance of your mod you should append the CSS file to the DOM using jQuery defined in one of your mod script files. For example, suppose you have defined the file:

css/my-styles.css

To include this CSS file within the GM Forge client, simply include the following JavaScript:

$('body').append('<link rel="stylesheet" href="css/my-styles.css" type="text/css" />');

Be aware, the style rules defined inside your stylesheet will apply to the entire GM Forge app, the best practice when defining custom CSS rules is to namespace your mod UI elements so you can easily target only HTML inside your own UI. For example, instead of defining a rule like:

h1 {
  font-size: 48px;
  color: blue; 
}

It would be preferable to include a class tag on the outer-most HTML element of any UI structures you define so you can namespace your styling rules, for example:

.my-custom-ui h1 {
  font-size: 48px;
  color: blue;
}

Defining HTML in a Separate File

To better organize your mod, you may want to define HTML templates in separate files to separate your data processing logic from the HTML template used to present that data to the user. For example, instead of the above example which writes raw HTML directly inside the sync.render function, you might store that same HTML in a separate file under the html directory of your mod, for example html/my-custom-ui.html with the contents:

<div class="my-custom-ui">
        <h1>My Character UI</h1>
        <p>This is a UI rendered directly from the world template.</p>
</div>

The following is a simple helper function that may be useful in loading HTML content into JavaScript.

loadTemplate = function(path) {
    let html = $.get({
        url: path,
        dataType: 'html',
        async: false
    }).responseText;
    return html;
}

To unlock greater performance benefits, you can cache the content of each template you use:

HTML_TEMPLATE_CACHE = {};

loadTemplate = function(path) {
    if ( path in HTML_TEMPLATE_CACHE ) return HTML_TEMPLATE_CACHE[path];
    let html = $.get({
        url: path,
        dataType: 'html',
        async: false
    }).responseText;
    HTML_TEMPLATE_CACHE[path] = html;
    return html;
}

Using this HTML loader, we can simplify our sync.render function to separate the underlying HTML from the rendering logic.

sync.render("MY_CHARACTER_UI", function(obj, app, scope) {
    let html = $(loadTemplate("html/my-custom-ui.html"));
    return html;
});

Including Special GM Forge HTML Elements

GM Forge supports a number of custom HTML tags which are documented here: http://wiki.gmforge.io/index.php/HTML_Breakdown. You can include these custom HTML tags in your own templates, that can be used to make your character sheets dynamic. If you include these tags in your sheet, an extra step is needed in order to activate and parse them. Suppose you augmented your HTML from the previous example to include a special input tag.

<div class="my-custom-ui">
    <label for="charname">Character Name:</label>
    <input name="charname" type="text" target="info.name"/>
    <p>This is a UI rendered directly from the world template.</p>
</div>

You can load this template using the same logic used above, but a final step is needed to activate the GM Forge handlers. This step "forwards" your HTML template on to a GM Forge UI render which will translate it into a dynamic HTML sheet.

sync.render("MY_CHARACTER_UI", function(obj, app, scope) {
    let html = loadTemplate("html/my-custom-ui.html");
    let rendered = sync.render("ui_processUI")(obj, app, {display : html});
    return rendered;
});

Registering Event Listeners

In order to provide an interactive user experience with your mods UI, you will likely want to include jQuery event handlers to take certain actions. If you are defining HTML directly within the world template, you will need to add these events as delegated event handlers since you cannot bind them to the rendered HTML directly at the time your scripts are loaded. The following is an example of a delegated on-click event:

$( document ).on("click", ".my-custom-ui", function() {
    alert("The UI was clicked!");
});

On the other hand, if you are defining a sync.render function, you can attach event listeners to your HTML directly before returning it, for example:

sync.render("MY_CHARACTER_UI", function(obj, app, scope) {

    // Load your custom HTML template
    let html = loadTemplate("html/my-custom-ui.html");

    // Process GM Forge rendering rules
    html = sync.render("ui_processUI")(obj, app, {display : html});

    // Attach an event listener before returning
    html.click(function() {
        alert("The UI was clicked!");
    });

    // Return the HTML with a click event listener attached
    return html;
});