
Scripts are sets of instructions forming sequences of commands that can be used to implement program logic in rulesets.
Typical uses for scripts are calculations, operation based on a condition and context specific custom handling of user input.
About Lua
Lua 5.1 is the scripting language used by Fantasy Grounds. It is a simple procedural language with powerful data description
constructs. The name of the language is spelled "Lua" (not "LUA").
Some useful links for learning the Lua language and working with scripts are listed below.
Global Lua variables, functions and packages
Not all built-in Lua functions and standard libraries are available in the Fantasy Grounds scripting environment. The
following lists the variables and functions that are NOT available.
- _G
- dofile
- getfenv
- getmetatable
- load
- loadfile
- rawequal
- rawget
- rawset
- setfenv
- setmetatable
The following standard libraries are available:
In addition, the following functions have had their functionality slightly altered.
- The print function prints its input into the console. To access the console, use the "/console"
command, or see the console.log file in the application data folder.
- The type function has been extended to cover some custom data object types.
Registries
There are two registries available in the scripting environment. Registries are persistent tables of data that are stored when
the 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 global registry
The table GlobalRegistry is shared by all rulesets and campaigns used with the installation on a single computer.
Warning
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
and use that table for the actual data.
The campaign registry
The 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 to the ruleset used in the campaign.
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. The following exmple illustrates the latter.
<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
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.
- A window script environment has a variable named the same as a control for each of its controls, pointing to the environment of the
control script block
- A control has a variable called window pointing to the environment of the window's environment
- Windows in a windowlist have a variable called windowlist pointing to the window list control's
environment
- A window instance in a subwindow control has a subwindow variable pointing to the environment of the sub window
control
- The variables super and self are available in environments inheriting or being inherited by
other environments. See the templates section for more information.
- Script packages are globally available by their name (see below)
Script blocks in window classes
Script blocks in window classes are applied to each window instance created based on the class, and extend the windowinstance
interface.
The script tag should be located as a direct child of the <windowclass> tag, as illustrated by the following abbreviated example.
<windowclass name="charsheet_skilllistitem">
...
<script file="scripts/charsheet_skilllistitem.lua" />
<sheetdata>
...
</sheetdata>
</windowclass>
Script blocks 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.
<numberfield name="...">
...
<script>
function onValueChanged()
if getValue() < 0 then
setValue(0);
end
end
</script>
</numberfield>
Script packages
Script packages are globally accessible generic script constructs similar to Lua standard library packages. Their operation is detailed on the
reference page script.
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.
<script name="ModifierStack" file="scripts/modifierstack.lua" />
Accessing XML parameters from script
It is possible to access the XML tags given in the window class or control definition from inside 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. The child tags are each represented in the table with
integer indices. 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.
The following definition is provided as an example.
<genericcontrol>
<states>
<empty>
<icon>emptyicon</icon>
</empty>
<normal>
<icon>normalicon</icon>
</normal>
</states>
<flags>
<first />
<second />
</flags>
<label>Control label</label>
</genericcontrol>
The table value in the script environment could be written as:
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 the ruleset reference document
The ruleset reference documentation details the interfaces used for scripting, for the various objects
available. Packages and objects are interfaces available only via scripting. Many elements contain functions usable by
scripts. These are detailed in the "Interface" sections on the reference document pages.
Event functions
Many elements contain event functions, indicated as such with the "event" denomination in the reference 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 defining the event function. A function named according
to the event name should be defined. 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.
Handler functions
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 data base node to point
to that function.
function onSourceUpdate(source)
setValue(calculateSources());
end
function addSource(name)
local node = window.getDatabaseNode().createChild(name, "number");
if node then
sources[name] = node;
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.