Ruleset - Scripting

Overview

Scripting is the mechanism that allows advanced interface logic and game system automation to be performed within a ruleset. Scripts can be used to extend the capabilities of any window class or window control, in addition to supporting global packages for shared logic.

Lua scripts can either be global, attached to a window class or attached to a window control. Global scripts must be named, and are available to be used within any other script by that name. Every window instance will get a unique instance of the Lua script attached to a window class. Every window control will get a unique instance of the Lua script attached to a window control definition. Also, the FG client provides a number of built-in global scripts to allow interaction with the client (Common, DB, Debug, ...). If the script is defined as part of an object definition, then the script operates within the context of that FG object, and any APIs associated with that FG object can be called directly.

Additionally, there are a number of objects that can be requested through the FG API that can be interacted with outside of window instances and window controls. This includes: database nodes, drag object instances, token instances, and widgets.

Fantasy Grounds uses the Lua scripting language. Every object within the FG interface or accessible through the FG API has a specific Lua API that is defined, and almost every object within the FG API can be created dynamically through scripts.

Using the Ruleset API Reference

The ruleset reference documentation details each of the scriptable FG object environments, as well as the global packages built-into the FG client. Each object and package has a unique set of APIs relevant to the type of object or package.

Here are how the API sections are broken down:

  • Packages: Built-in global packages.

  • Objects: FG Lua objects only accessible through the API.

  • Elements: FG Lua objects that can be defined as assets or controls, and which allow provide scripting environments

  • Assets and Settings: Other assets or settings that can be defined in a ruleset.

Using Scripts

Script blocks defined in rulesets can be contained in three locations: window classes, controls and global script packages.

In all cases, script blocks are defined using <script> tags. The body of the script block can be specified as the text contents of the tag, or in an external file referred to using the "file" attribute.

Example

1 <script file="scripts/pointerselection.lua" />

If the script is given as the text content of the tag, the entire block is processed as a single line due to XML processing details. In this case, the following restrictions apply.

  • Any error messages printed on the console will always point to line 1, generally making them less useful

  • The "--[[ ]]" comment syntax must be used instead of the "--" syntax

  • XML special characters such as "<" and ">" must be escaped not to interfere with XML processing

Scripts in Window Classes

Script blocks in window classes are applied to each window instance created based on the class, and extend the windowinstance FG Lua interface. The script tag should be located as a direct child of the <windowclass> tag, as illustrated by the following abbreviated example.

1 2 3 4 5 6 7 <windowclass name="charsheet_skilllistitem"> ... <script file="scripts/charsheet_skilllistitem.lua" /> <sheetdata> ... </sheetdata> </windowclass>

Scripts in Controls

Script blocks for individual controls are applied to the control in each window instance based on the containing window class. The control scripts extend the interface of the control's type. The <script> tag should be a direct child of the control definition tag.

1 2 3 4 5 6 7 8 9 10 <numberfield name="..."> ... <script> function onValueChanged() if getValue() < 0 then setValue(0); end end </script> </numberfield>

Global Script Packages

Script packages are globally accessible generic script constructs similar to Lua standard library packages. They can be accessed from other script blocks by name, which must be defined as the "name" attribute to the <script> tag. Specifying the name is not mandatory - if it is omitted, the contents of the script can still be executed using the onInit function.

1 <script name="ModifierStack" file="scripts/modifierstack.lua" />

Script Block Scope

The global scope in the script environment is not available to user defined scripts. Instead, each script block receives its own environment and the entire block is evaluated and executed treating that environment as global. This means that all functions and variables defined in a script block can be used inside the block as if they were global, and all other script blocks are incapable of directly modifying another block's variables.

To access other script blocks, such as other controls in the same window, many script block environments contain special variables that can be used to access related environments. These are detailed for each element separately in the reference documentation, but the most common are summarized below.

  • [control_name] = Window script environments have a variable named the same as a control for each of its controls pointing to the environment of the control's script block (as long as the control is named).

  • window = Control script environments have a variable called "window" pointing to the environment of the parent window.

  • windowlist = Windows in a window list control (windowlist) have a variable called "windowlist" pointing to the window list control's environment.

  • parentcontrol = The window contained in a container control (subwindow) has a "parentcontrol" variable pointing to the environment of container control.

  • subwindow = The container control (subwindow) containing a window has a "subwindow" variable pointing to the environment of the contained window.

  • super = When scripts are layered, then the super variable is defined and can access a script which this object is based on.

  • self = Always refers to the outermost object with all layers applied, even if called from a lower layer.

  • [package_name] = Any built-in packages (see FG API reference) or any global scripts defined for the ruleset.

Accessing XML Parameters from Scripts

It is possible to access the XML tags given in a window class or control definition from inside their respective script blocks. This is useful for the separation of script logic from the configuration parameters used, such as icon names or data base field names used.

Each tag under the control definition is presented as a table value with the same name. For each unique child tag name, a table is created linked with a key equal to the child tag name. Then, any child tags with that name are added to the table with integer indexes. Nested tags with children are included as nested tables. Any tag with no child tags is represented as a string value if it has text contents, or the boolean value true if the containing tag is singular or empty.

Attributes are not accessible in this manner.

Example

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <genericcontrol> <states> <empty> <icon>emptyicon</icon> </empty> <normal> <icon>normalicon</icon> </normal> </states> <flags> <first /> <second /> </flags> <label>Control label</label> </genericcontrol>

becomes:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 states = { [1] = { empty = { [1] = { icon = { [1] = "emptyicon" } } }, normal = { [1] = { icon = { [1] = "normalicon" } } } } } flags = { [1] = { first = { [1] = true; } }, [2] = { second = { [1] = true; } } } label = { [1] = "Control label" }

Using Events

Many elements contain event functions, indicated as such with the "event" denomination in the Reference API document. These functions are automatically called by the system when certain events occur.

To take advantage of events, a script block needs to be created extending the element and defining the event function. If the function is present, it is called when the conditions for the event are fulfilled. No further actions need to be taken for the event to work.

The example in the Script Blocks in Controls section above contains an example of an event definition.

Using Handlers

Some script interfaces define handler functions, identified by the denomination "handler" in the reference documentation. A handler is different from an event in three regards. First, any number of scripts can receive a single event fired on a handler. Second, a handler requires explicit registration to operate on a user defined function. Third, the user defined handler function does not need to be located in the script block extending an object defining a handler function, but can be in any script environment.

To register handler events on a user defined function, use the assignment operator on a handler as if it were a function variable. The following example defines a handler function (onSourceUpdate) and sets the onUpdate handler of a database node to point to that function.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 -- Create a handler function function onSourceUpdate(source) setValue(calculateSources()); end function addSource(name) local node = window.getDatabaseNode().createChild(name, "number"); if node then sources[name] = node; -- Assign the handler node.onUpdate = sourceUpdate; hasSources = true; end end

Technically, the assignment performed is not a regular assignment operation. A handler can receive one handler function for each script block environment. Therefore, a single data base node could be monitored by a number of different individual functions, e.g. in different controls.

The assignment is done based on the environment of the function being assigned as the handler. This leads to a very nice and simple way of using handlers in most cases. The case that requires special attention is one in which the handler needs to be reset, i.e. removed. Such situations must be handled by assigning another function to receive the handler events. Typically, this function is one that does nothing. The simplest way to accomplish this is to pass in an inline function.

1 node.onUpdate = function() end;

Registries

There are two registries available in the scripting environment. Registries are persistent tables of data that are stored when the campaign session is closed, and restored when it is restarted. All the data in the registries is specific to the local installation and is not delivered over the network to connected users. These facts make the registries ideal for storing user preference data, and data related to the state of script constructs.

Registries may contain tables, numbers, strings and booleans. Other data types will not be preserved, and using them might lead to unpredictable behavior.

The campaign registry table (CampaignRegistry) is specific to a campaign, and is not loaded or visible from other campaigns. The campaign registry is the preferred position for any data that links to or operates on data from the campaign database. It is also entirely specific and controlled by the ruleset used in the campaign.

The global registry table (GlobalRegistry) is shared by all rulesets and campaigns used with the installation on a single computer.

Warning: When editing the global registry; to avoid interference with other rulesets, always attempt to include a subtable identifying the ruleset or context the values in the global registry are stored for. A convenient method is to create a table identified with the key obtained from User.getRulesetName API function and use that table for the actual data.

Fantasy Grounds Specific Lua Changes

As with most scripting environments embedded within applications, the Fantasy Grounds Lua scripting environment has limited access to Lua variables, functions and libraries that may cause security concerns. This is referred to as the FG sandbox.

The following global variables and functions are NOT available in the FG sandbox:

  • _G

  • dofile

  • getfenv

  • getmetatable

  • load

  • loadfile

  • rawequal

  • rawget

  • rawset

  • setfenv

  • setmetatable

The following standard libraries are NOT available in the FG sandbox

  • io

  • os (except os.clock, os.date, os.time, and os.difftime)

  • debug

  • package

The following functions have been changed in the FG sandbox:

  • print - Sends output to the FG application console

  • type - Extended to cover FG Lua object types

General Lua Programming

For more information on Lua programming, please review the Lua Programming Language Home Page.