Build your custom system without any line of code !
⚠️ Formula changesThe formula syntax has changed, and some specific syntax has been deprecated. Here is a summary of the changes :
$<colName>
becomessameRow('<colName>')
<dynamicTableKey>(<columnName>=<filterValue>)$<targetColumn>
becomesfetchFromDynamicTable('<dynamicTableKey>', '<targetColumn>', '<columnName>', '<filterValue>')
<dynamicTableKey>$<targetColumn>
becomesfetchFromDynamicTable('<dynamicTableKey>', '<targetColumn>')
@<property>
becomesref(<property>)
@$<colName>
becomesref(sameRow('<colName>'))
The legacy syntax will be available, while deprecated (meaning unsupported) for all Foundry v10 duration, and will be removed in Foundry v11. Any compted legacy syntax will trigger a warning in the Console, allowing you to update your formulas more easily.
This system is intended to easily build sheets in FoundryVTT with dynamic display and roll capabilities, in a game agnostic way.
If you have any issue, request or question, please report it here. You can also ask on the FoundryVTT Discord, I am @LinkedFluuuush there !
For an up-to-date Roadmap, please see ROADMAP.md on the develop branch.
If you want to contribute to the project, please see CONTRIBUTING.md.
If you seek to extend the system or use it in macros, you can visit the API.
If you like this system and want to support me, you can buy me a coffee !
Seeking for existing templates working with the Custom System Builder ? Look here if you can find something suiting you !
If you made sheets for Custom System Builder and would like to add them to the library, please send them to me by Discord or by creating an issue !
- This system tinkers with initiative rolls and calculations, it might cause problems with modules modifying initiative as well.
[[TOC]]
This system defines two Actor types : Templates (_template
) and Characters (character
). The _template
type is for
sheet-building, and the character
type uses a template to represent a Character. Templates can not be filled out, and
are not supposed to be. Their sole purpose is to build and edit sheet structures, in an easy-to-use way. Character
sheets are where the magic happens.
Sheets are divided in 2 main parts :
- The header, located under the actor's name and to the right of the character's profile picture
- The body, under both the header and the profile picture
The header and the body section both act in the same way, which is why they will be covered by the same section of this doc.
The term key
is used many times in this doc. It refers to an identifier of fields or tabs. It must always be a string
composed of letters (upper and lowercase), numbers and underscores only. Keys must be unique throughout the sheet.
To create a basic sheet, you will want to create a _template
type actor. Template actors name should be unique, as
they will be used to link a template to a Character later on. Once your _template
is created, its sheet opens, looking
like this.
To add a component to the sheet structure, click the +
icon in the section you want to add the component in. It will
open a pop-up dialog asking the details of your new component.
You must first choose your component's type, and you can add a key. The key will be used for various purposes, including data-saving for the characters and HTML class added to the components in the resulting sheets.
See here for more information on CSS enhancing.
The components' documentation is here.
Once your component has been added to the sheet, you can click on it to edit its configuration.
New buttons are available at the bottom of the pop-up dialog, allowing you to delete the component, or move it inside its container.
When your template is complete, you can associate it to a character. Create an actor with type character, and its sheet will open, looking like this.
On the top right of the sheet, a drop-down list allows you to select the template to use, and a button which applies the template. If you modify your template, you will need to click this button again to update the character sheet. This prevents any mistake deleting fields from the character sheets. This action is available only to GMs and Assistants.
The reload action is also available in the context menu on the characters list, though it just reloads the current template and does not allow for template changing.
REMINDER : The key is sometimes optional, and MUST be unique through ALL the sheet to work properly if set. The key must be composed of letters (upper and lowercase), numbers and underscores only.
You can add CSS classes on every component, which will be added to the rendered component on the sheet. See here for more information on CSS enhancing.
Each component can be limited by the current player role and permission. To see a component, the user's role AND permission on the sheet must be above or equal to the minimum role and permission configured on the element. This allows you to create GM Only Components or Tabs, and limit the view of non owner players on their companions sheets for example.
Each component can have a tooltip, which is a little text displayed when hovering the mouse over the component for some time.
Labels are simple text rendered in the sheet. They can be dynamic and can trigger a chat message.
- Key : optional. If set, allows the label text to be re-used in formulas by referring to the key.
- Label style : You can choose the style of the label between title, subtitle, bold text and default. This will only affect the label's rendering.
- Size : Specifies the size of the field. 4 options are available :
- Auto : The field will take all the available space, resizing automatically.
- Small / Medium / Large : The field will have a fixed size, depending on your choice.
- Label text : The label's displayed text. Can use formulas.
- Label prefix : An optional prefix for the label. Is not used in the label's saved value. Can use formulas.
- Label suffix : An optional suffix for the label. Is not used in the label's saved value. Can use formulas.
- Label icon : You can add an icon in front of the label. Use a valid fontawesome icon name,
like
dice-d20
. - Label roll message : If set, will post this text into a Chat Message on a click on the Label. Can use formulas.
- Label alternative roll message : If set, will post this text into a Chat Message on a click on the Label with Shift key pressed. Can use formulas. This allows for a standard roll to be set and an advanced one for special cases.
Text fields are one-lined fields to input any string you want.
- Key : mandatory - will not save otherwise
- Label : If set, will display this label next to the field on the sheet
- Size : Specifies the size of the field. 4 options are available :
- Auto : The field will take all the available space, resizing automatically.
- Small / Medium / Large : The field will have a fixed size, depending on your choice.
- Allowed character list : Specifies a list of characters authorized in the field. Inputs with other characters will be rejected.
- Maximum length : sets the maximum number of characters the text field can accept.
- Default value : If set, the field will have this value by default
Number fields are one-lined fields to input numbers.
- Key : mandatory - will not save otherwise
- Label : If set, will display this label next to the field on the sheet
- Size : Specifies the size of the field. 4 options are available :
- Auto : The field will take all the available space, resizing automatically.
- Small / Medium / Large : The field will have a fixed size, depending on your choice.
- Allow decimal number : Allows decimal numbers to be entered. Accepted numbers are integer only by default.
- Minimum value : sets the minimum value (inclusive) accepted in the field. This can be a formulas.
- Maximum value : sets the maximum value (inclusive) accepted in the field. This can be a formulas. If a maximum value is set, the field will automatically be added to the possible Attribute Bars.
- Default value : If set, the field will have this value by default
- Allow relative modification : If set, when a player enters '+x' or '-y', the operation will be done relative to the current value of the field, instead of directly setting the new value.
- Show field controls : If set, Number field will have a '+' and '-' button shown when mouse is over them to increment and decrement the value.
Rich text areas are fields which can contain enriched HTML data. They come with a WYSIWYG editor.
- Key : mandatory - will not save otherwise
- Label : If set, will display this label next to the field on the sheet
- Default value : If set, the field will have this value by default
- Style : Three styles are available
- In sheet editor : Rich text will be displayed on the sheet, with a button to open the editor directly within the sheet
- Dialog editor : Rich text will be displayed on the sheet, with a button to open a pop-up dialog containing the editor
- Icon only : Only an icon to open a pop-up dialog containing the editor will be displayed on the sheet, for tight spaces
Checkboxes are checkboxes. Sometimes it's that simple.
- Key : mandatory - will not save otherwise
- Label : If set, will display this label next to the checkbox on the sheet
- Size : Specifies the size of the field. 4 options are available :
- Auto : The field will take all the available space, resizing automatically.
- Small / Medium / Large : The field will have a fixed size, depending on your choice.
Drop-down lists are fields with limited options.
- Key : mandatory - will not save otherwise
- Label : If set, will display this label next to the field on the sheet
- Use a dynamic table column as the options : If checked, you will be able to use columns from a dynamic table to populate your dropdown list.
- Options : The value list displayed in the sheet. Enter one key and one Label per row. Each option must have a key.
- Dynamic table key : The source dynamic table's key.
- Column from the dynamic table to use as option key : The key of the column in the dynamic table to use as options keys.
- Column from the dynamic table to use as option label (optional) : The key of the column in the dynamic table to use as options labels.
- Default value : Optional. If set, the drop-down list will select the
key
entered as default value. If not set, the drop-down list will include an empty option at the beginning of the list.
Panels are invisible containers designed to display other components in a specific layout.
- Key : optional
- Panel layout : Sets the future components layout in the panel
- Vertical : Components will display vertically, one after the other, taking all the available width.
- Horizontal : Components will display horizontally, from left to right.
- Grid : Components will display horizontally, in lines of n components.
- Panel alignment : Sets the alignment of the components
- Center : Will center all components in their virtual column
- Left : Will place components on the left, leaving a space on the right of the panel
- Right : Will place components on the right, leaving a space on the left of the panel
- Justify : Will center components in their columns, but will not leave space in the left and right of the panel.
Once created, the panel will look like this in the template editor :
The +
button is used to add new components to the panel, in the same way as the body and header. The panel's header
indicates the panel's key for reference, and will not be shown in the definitive sheets, nor will be the outline. The
panel's header is used to edit the panel's settings.
Tabbed Panels are containers designed to display other components in predefined tabs.
- Key : optional
Once created, the panel will look like this in the template editor :
The panel's header indicates the panel's key for reference, and will not be shown in the definitive sheets, nor will be the outline. The panel's header is used to edit the panel's settings.
To add a tab to your sheet, click the +
icon in the tab selector section. This opens a pop-up dialog to let you enter
the tab details.
You must enter the tab name, which can be anything, and the tab key.
You can edit the currently selected tab name and key with the edit button on the right, as well as delete it. Additionally, you can reorder your tabs using the arrows on the left and right of the tab name.
Tables are strict containers with specified column and row counts.
- Key : optional
- Row count : How many rows must the table have ?
- Column count : How many columns must the table have ?
- Table layout : This is a code to specify the columns alignment. Each column has 1 letter in the code, which indicates
if the column should be aligned to the left (
l
), to the right (r
) or centered (c
).
Once created, the table will look like this in the template editor :
The +
button are used to add new components into the table's cells, in the same way as the tabs and header. The
table's header indicates the table's key for reference, and will not be shown in the definitive sheets, nor will be the
outline. The table's header is used to edit the table's settings.
Dynamic tables are a component allowing players to make custom lists in their sheets with a layout specification.
- Key : mandatory - will not save otherwise
- Make column name bold : If set, column names will be bold on the table, to emphasize the head of the table.
- Show confirmation dialog on row delete : If set, a confirmation dialog will be shown on click on the delete row button, to avoid accidental row deletion
Once created, the dynamic table will look like this in the template editor :
The +
button is used to create new columns to the dynamic table. The dialog that opens is the same as panels, tabs and
headers components, with two additional configuration settings :
- Alignment : How the column should be aligned in the dynamic table
- Column name : If set, will be displayed as the column's name
Item containers are components designed to display the items applied to an actor. Items can be filtered in the container to ensure sheet coherence, and specific information can be added next to the items.
- Key : mandatory
- Display column names : If set, column names will be displayed on the table. Can be unset to refine the items display.
- Make column name bold : If set, column names will be bold on the table, to emphasize the head of the table.
- Show item delete button : If set, each item will be accompanied by a delete button, allowing players to delete their own items.
- Show confirmation dialog on item delete : If set, a confirmation dialog will be shown on click on the delete row button, to avoid accidental item deletion.
- Filter items
- Select templates : You can select which item templates are eligible to be displayed in this container. This is a first filter for the container.
- Add filters : You can add more filters to the displayed items. For each filter, you must enter a component's key
present on the item, an operator and a value to check against. The value can not be a formula, it must be a fixed
value. The
is
operator can be used to check any value. For example, to filter on checked checkboxes, you would use<key> is true
.
- Item reference column label : Every item container displays at least one column, which is a reference to the item. This setting allows you to configure its label.
- Item reference column alignment : This setting allows you to configure the reference column alignment.
Once created, the item container will look like this in the template editor :
The +
button is used to create new columns to the item container. The dialog that opens is the same as dynamic table
components, but with only Labels available. You can only configure labels in item containers, no value can be entered in
this component.
As with any labels, you can use formulas in the Label text and Roll Messages. The keys used in the formulas will
reference the parent keys, but you can use the item keys by adding item.
in front of the key. For example, if a
weapon's damage value is based on the character's STRMod
and an internal damage
value, you can display it in the
item container with the formula ${STRMod + item.damage}$
.
Lastly : If no additional columns are set and Display column names
is unchecked in the Item container configuration,
the items will be displayed as a vertical list without the table formatting.
In Label text or Roll Messages, you can use Formulas to enhance your sheets.
To mark a formula, inside your text, use the delimiters ${
and }$
. Inside thoses delimiters, you can use the keys
you defined for any label, text field, rich text area or checkbox. You can also use mathematical operators, as well as
common mathematical functions, such as floor()
, ceil()
or abs()
. Formulas are parsed
with the math.js library, so you can use any expression valid in this library. Here
is a summary of the syntax
and a summary of the available functions.
For example, the modifier formula for Strength in DnD can be expressed as ${floor((STR - 10) / 2)}$
, assuming the
Strength input was given the key STR
.
In addition to the math.js library, a few specific functions are available in the system.
⚠️ When to use quoted strings and when to use direct field key ?In the following functions, you'll see use of quoted
''
field keys and unquoted ones. The difference between the two forms is that unquoted field keys will be replaced by their value at runtime. For example, for anhp
field with value10
,function(hp)
would be equivalent tofunction('10')
. For theref
function for example, this is a problem :ref(attribute)
aims at getting the value of the field referenced by theattribute
field. Ifattribute
's value isstr
,ref(attribute)
is equivalent toref('str')
, and will return the value of the fieldstr
. Andref('attribute')
will return the stringstr
.Examples of this are given in the function's description, please read them carefully !
fetchFromDynamicTable(dynamicTableKey : String, targetColumn : String, [filterColumn : String], [filterValue : String | Number | boolean]) : Matrix
This function is used to retrieve values from a dynamic table. Values can be filtered by a different column.
Arguments
- dynamicTableKey : String
The key to the dynamic table to get the value from
- targetColumn : String
The column key from which to get the values
- (optional) filterColumn : String
An optional column key to filter the results.
- (optional) filterValue : String | Number | boolean
The value to filter the results. If not provided, nothing will be returned.
Returns A Matrix containing all the column values where filter is met.
Examples
- Fetch all values
Suppose you have the following Dynamic Table, whose key is equipment
:
Name (key name ) |
Weight (key weight ) |
Location (key location ) |
---|---|---|
Sword | 50 | Backpack |
Deck of cards | 2 | |
Shield | 30 | Backpack |
fetchFromDynamicTable('equipment', 'weight')
: Will return a Matrix containing all the weights from the Dynamic Table
equipment, here : [50, 2, 30]
- Fetch values with filter
With the same table, you can add a filter to get only the Backpack items.
fetchFromDynamicTable('equipment', 'weight', 'location', 'Backpack')
: Will return a Matrix containing all the weights
from the Dynamic Table
equipment, where location
is equal to Backpack
, here : [50, 30]
- Filter values with another field
Suppose you have the following Dynamic Table, whose key is skills
:
Name (key name ) |
Level (key skill_level ) |
---|---|
Craft | 5 |
Lock-pick | 2 |
Hide | 7 |
Craft | 7 |
fetchFromDynamicTable('skills', 'value', 'name', selectSkill)
: Will return a Matrix containing all the values from
the Dynamic Table skills, for the rows which name is equal to the value of the field selectSKill
(Notice selectSkill
does not have quotes around it, meaning it is a field in the sheet).
If selectSkill
's value is 'Lock-pick', this will return [2]
.
If selectSkill
's value is 'Craft', this will return [5, 7]
.
first(list : Array<T> | Matrix<T>, [fallbackValue : *]) : T
Returns the first value of an Array or a Matrix.
Arguments
- list : Array | Matrix
An array or a matrix containing elements of type T (usually String, Number or boolean).
- (optional) fallbackValue : *
This value will be returned if list is empty or null.
Returns The first value of list, or fallbackValue if list is empty or null.
Examples
With the skills
Dynamic table defined in the previous part.
first(fetchFromDynamicTable('skills', 'value', 'name', 'Craft'), 0)
: Will get the value of the first Craft skill if
the skills Dynamic Table, or 0 if Craft is not in the table. Here, this will return 5
.
You will need to use first
to get values as Numbers, even if the Dynamic Table's filter returns only one row. The
function fetchFromDynamicTable
returns a Matrix, which is not suited for other operations.
sameRow(columnName: String, [fallbackValue: *]) : String | Number | boolean
Returns the value of the column with the key columnName
in the current row in a Dynamic Table.
Arguments
- columnName : String
The key of the column to target.
- (optional) fallbackValue : *
The value to return if the column is not found or does not contain data.
Returns the value of the column with the key columnName
in the current row in a Dynamic Table, or fallbackValue
if the column does not exist or does not contain data.
Examples
Suppose you have the following Dynamic Table, whose key is skills
:
Name (key name ) |
Base (key base_value ) |
Bonus (key bonus_value ) |
---|---|---|
Craft | 5 | 3 |
Lock-pick | 2 | 4 |
Hide | 7 | 2 |
Craft | 7 | 3 |
If your skill level is the base_value
+ the bonus_value
, you can automate this calculation by
using sameRow('base_value', 0) + sameRow('bonus_value', 0)
. These
functions will get the value of respectively base_value
and bonus_value
in the same row as the Formula being
computed, or 0
if the value is undefined. This would get you the following Dynamic Table.
Name (key name ) |
Base (key base_value ) |
Bonus (key bonus_value ) |
Level (key skill_level ) |
---|---|---|---|
Craft | 5 | 3 | ${sameRow('base_value', 0) + sameRow('bonus_value', 0)$} => 8 |
Lock-pick | 2 | ${sameRow('base_value', 0) + sameRow('bonus_value', 0)$} => 2 |
|
Hide | 5 | ${sameRow('base_value', 0) + sameRow('bonus_value', 0)$} => 5 |
|
Craft | 7 | 3 | ${sameRow('base_value', 0) + sameRow('bonus_value', 0)$} => 10 |
ref(valueRef : String, [fallbackValue : *]) : *
Returns the value of the field with the key in valueRef.
Arguments
- valueRef : String
The key of the field to get the value from.
- (optional) fallbackValue : *
The value to return if the key referenced has no value.
Returns the value of the field which has the key pointed by valueRef, or fallbackValue of valueRef does not point to a valid field.
Examples
ref('hp')
Returns the value of the field with the key hp
ref(currentArmor)
Returns the value of the field targeted by currentArmor
. If currentArmor
is a field
containing lightArmorProtection
for example, this will return the value of the lightArmorProtection
field.
ref(sameRow('attribute'))
Returns the value of the field targeted by the column attribute
in the current row of a
Dynamic Table.
replace(text : String, pattern : String, replacement : String) : String
The replace()
function replaces the first occurrence of the pattern in the text with the replacement.
Arguments
- text : String
The text you want to modify. It can also be a key to a field.
- pattern : String
The pattern which will be replaced.
- replacement : String
The replacement text, which will replace the pattern.
Returns the new text with the replacement.
Examples
replace('My test test', 'test', 'dog')
Replaces the first occurance of 'test' with 'dog'. Output: My dog test
.
replace(MyInputText, 'random', '')
With 'MyInputText' = This is a random sentence. Very random!
=>
Returns: This is a sentence. Very random!
replaceAll(text : String, pattern : String, replacement : String) : String
The replaceAll()
function replaces all occurrences of the pattern in the text with the replacement.
Arguments
- text : String
The text you want to modify. It can also be a key to a field.
- pattern : String
The pattern which will be replaced.
- replacement : String
The replacement text, which will replace the pattern.
Returns the new text with the replacement.
Examples
replaceAll('My test test', 'test', 'dog')
Replaces all occurances of 'test' with 'dog'. Output: My dog dog
.
replaceAll(MyInputText, 'random', '')
With 'MyInputText' = This is a random sentence. Very random!
=>
Returns: This is a sentence. Very !
recalculate(formula : String) : String | Number | boolean
This function is used to recalculate a value. This can be used for custom formulas from a Text Field or a Rich Text Area.
Arguments
- formula : String
The formula which should be recalculated. It needs to contain ${}$
as usual. Can be a key to a field with a formula.
Returns the result of the calculation.
Examples
recalculate('${Strength + 3}$')
With 'Strength' = 7
=> Returns: 10
recalculate(MyInputFieldKey)
With 'MyInputFieldKey' = ${round(Strength / 2)}$
and 'Strength' = 5
=> Returns: 3
You can add conditions to your formulas using the ternary operator : <condition> ? <value if true> : <value if false>
.
The condition part is evaluated as a boolean :
- You can use comparison operators like
==
for equality>
and>=
, respectively 'greater than' and 'greater than or equals'<
and<=
, respectively 'lower than' and 'lower than or equals'!=
for 'not equals'
- Any number higher than 0 will be considered true
- You can directly use the key of a checkbox to test it is checked
- Example :
${ checkboxKey ? 'Checked' : 'Not checked' }$
- Example :
⚠️ Some common syntax in other programming languages won't work: For example, you can not use the ' NOT'!
operator.
You can add text to your formulas to add flavor to your sheets and messages. To use text, you must enclose it in
quotes '
. If you wish to use a quote in the text, you can prefix it by a backslash \
, it will be parsed correctly.
For example : ${'You can\'t use this item !'}$
You can concatenate predefined text and field values by using the function concat()
. If you do so, field values must
be enclosed in the string()
function to work correctly. For
example : concat('Deal ', string(strMod+dmgMod), ' damage to the target.')
.
You can use rolls in your formulas. To do so, inside the formula markers, use brackets ([
and ]
) to define your
rolls. In roll expressions, you can also use keys, but you must surround them with colons. Rolls are performed using
FoundryVTT Roll API.
For example, to roll for Shadowrun's Strength, you can define the following formula : ${[:STR:d6cs>=5]}$
.
Roll operations can be defined inside the roll, or outside. In terms of pure result, ${[1d20 + 3]}$
and ${[1d20] + 3}$
are equivalent, but I would recommend using the latter.
You can also use Roll Tables, using the syntax [#<rollTableName>]
. Just put a #
before your roll table name, inside
brackets, and the system will roll its default formula and integrate the result in the message.
You can customize the Roll Table formula by adding a Roll formula in this
syntax : [#<rollTableName>|<rollFormula>]
. The formula must follow roll formula syntax, with ::
to delimit the keys.
This formula can be a Dice roll or a constant value, the latter drawing always the same result from de Roll Table.
When used in chat messages, formulas are explained by a small box displayed on click.
⚠️ Rolls can be used only in messages, not in label values.
ℹ️ The roll system is compatible with the Dice so Nice module !
In many cases you will want to re-use formula results in a chat message, to display additional information. You can
assign a formula result to a variable using the operator :=
. You can then reuse the variable as a key later on in the
same message.
For example, using Warhammer's SL system :
Rolling ${roll:=[1d100]}$.
SL = ${SL:=floor(WS/10) - floor(roll/10)}$.
Damage : ${9 + SL}$
You can ask for user inputs in formula using the syntax ?{<varName>}
. This will open a pop-up dialog during formula
computing asking for all the inputs in the formula.
You can add default values for inputs by adding it after a vertical bar, like so : ?{<varName>|<defaultValue>}
.
The defaultValue
can be a formula, without the enclosing ${}$
. If defaultValue
is a character string, it should be
enclosed by quotes '
.
The user inputs variables can be reused later on as keys in the same message.
⚠️ User inputs can be used only in messages, not in label values.
When using formulas in chat-messages, you can hide some formulas to perform preliminary calculations assigned to
reusable variables. To do so, prefix your formula with #
, for example :
${#testResult := target - roll}$
⚠️ Formula hiding can be used only in messages, not in label values.
When using formulas in chat-messages, you can use the Formula's result as is, without it being enclosed in the clickable
box with the explanations. This can be useful if you want to call a macro with parameters calculated from Formulas. To
do so, prefix your formula with !
, for example :
/myMacro ${!target - roll}$
⚠️ Explanation hiding can be used only in messages, not in label values.
Okay, we now know how to build a basic character sheet, and how to augment it with automatic calculations and generated chat messages. Let's dig more and enter the wonderful world of Items !
An Item in FoundryVTT represents everything that is not a character for which you want to have a sheet. It can be actual items like weaponry or armours, or more abstract concepts like species or classes. Item can be attributed to actors or other items by dragging and dropping them onto other sheets. Caution : This attribution process creates a clone of said item, so any changes to an item's values won't be reported on attributed versions of the item, and vice versa.
To create items, you need to create a _equippableItemTemplate
for the item. This works just like the actor's template
creation, so I won't be detailing it any further.
Once the template is created, you can create an equippableItem
and select its template just like you do for actors.
The sheet renders, and you can fill the fields in.
On each equippableItem
sheet, you will find a Make item unique
checkbox. Checking it will prevent anyone from adding
multiple instances of the item to an actor sheet.
On the equippableItem
sheet, you can find a Configure item modifiers
button. This button opens a window allowing you
to create modifiers which will be applied to any actor this item is assigned to.
When creating a modifier, you must enter 4 configuration data :
- Priority : This indicated the order in which the modifiers will be applied, should multiple modifiers affect the same property. Lower values will be applied first. If multiple modifiers have the same priority, they are ordered by their operator : set are applied first, then multiplications, then divisions, then addition and finally subtraction. If multiple modifiers share the same priority and operation, their order is irrelevant as the mathematical result will be the same.
- Key : This is the key of the property to modify in the owner actor. This must be the key of a number containing Label (except for Set-operations). Modifiers don't work on inputs. The key must also be a first-level Label, it cannot be a reference to a Dynamic Table field.
- Operator : This indicates which operation the modifier should do : Addition (
+
), Subtraction (-
), Multiplication (x
), Division (/
) or Set (=
). The Set-operation sets the field to the modifier value with no other operation. - Formula : This is the value of the modifier. This can contain formulas, which will be calculated from the item's data. This must be a number in the end for the computation to work. Text values can only be used with the Set-operation. There is also the possibility to not set a value if the formula result is '' (empty string), meaning that '' (empty string) would leave the referenced key untouched.
There is another item type in Custom System Builder : the sub template. This item type can be used to create reusable template parts that can be added to other templates. The creation of this item is the same as any other templates, except it has no header. It also can not be used to create a definitive item.
When created, it can be dragged and drop on any other template (actor template, equippable item template and sub templates). When dragging on a sheet, a grey outline will show you where the template can be dropped. Once dropped, each component defined on the subtemplate will be copied to the final template as is.
⚠️ This creates a copy of the elements. Any further modification on the subtemplate will not replicate to the definitive template. You will need to drop it again on the sheet to re-copy the components.
The Custom System Builder offers some convenient chat message extensions to write your own macros !
Character properties are provided directly at the top of the RollData object. This means that you can use them directly with FoundryVTT roll system, like described here.
These properties are also available directly at the top of the TokenData object, meaning anything asking for a token variable can receive a property path.
You can directly include a property inside a chat message by using the following syntax : @{<actor>|<propertyKey>}
.
Actor can be one of the following :
selected
, which will reference the selected token on the map or, if no character is selected, the default player's character.target
, which will reference the targeted token on the map.- Any character name, which will reference this character directly.
The referenced properties can be anything included in the roll data. You must use the property's key to reference it.
You can additionally reference a specific row in dynamic table by using the following
syntax : @{<actor>|<dynamicTableKey>(<filterColumn>=<filterValue>).<targetColumn>
.
This will retrieve the value of <targetColumn>
in the first row of the Dynamic Table where <filterColumn>
is equal
to <filterValue>
.
For example, if you have a Skill table and want to retrieve the score of the Lock picking
skill, you can
use skill(name=Lock picking).score
.
⚠️ This feature needs the module _chatcommands installed and with version at least 1.4.0
You can roll a predefined roll from a chat message with the /sheetRoll
chat command. It is used with the key to the
label containing the roll you want to trigger. For example : /sheetRoll str
to trigger the roll associated with
the str
label.
To trigger dynamic table rolls, you must use a filter like the property reference, for
example : /sheetRoll skill(name=Lock picking).score
to trigger the roll associated with the label in the score
column of the dynamic table skill
, on the row which name
is Lock picking
.
The roll will be triggered for the currently selected character on the map, or the default player's character if no character is selected.
When right-clicking on a roll on a Character sheet, a context menu will appear, with options regarding macros. Three options are available :
- Add as macro
- This will show a dialog to create the new macro, asking for its name and place in the macro hotbar. When created, the macro configuration window will open for more configuration.
- Copy chat command
- This will copy the
/sheetRoll
command for this roll into your clipboard, allowing you to reuse it later.
- This will copy the
- Copy macro script
- This will copy a basic script into your clipboard to use the roll in a script-type macro. This script is customizable to your needs.
This menu is only available on Labels which have keys.
In a macro, you can use some of this system's features to enhance your game.
With the actor
object, which references the current actor, you can use the roll
method to trigger a chat roll.
actor.roll(rollCode: string, [{ postMessage: boolean }]): ComputablePhrase
rollCode
is the code used with/sheetRoll
postMessage
: iftrue
, will automatically post the message to the chat
The ComputablePhrase object returned by the method is detailed below.
The ComputablePhrase
class is the core of this system's formulas. Its purpose is to compute the formulas entered in
Labels and Rolls. And it is available in macros !
You can use it like the examples under. For more information, please refer to the API.
let phrase = new ComputablePhrase("This character's strength is ${str}$ (Mod : ${floor(str-10)/2}$).");
// Computing phrase with only one prop
await phrase.compute({str: 12});
// phrase.result returns the fully computed phrase as a String
console.log(phrase.result);
let phrase = new ComputablePhrase("Rolling for Weapon Skill<br/>Target : ${target:=WS}$<br/>Roll : ${roll:=[1d100]}$ -- ${roll <= target ? (roll % 11 == 0 ? 'Critical success !' : 'Success') : (roll % 11 == 0 ? 'Critical fail !' : 'Fail')}$<br/>Result : ${floor(target/10) - floor(roll/10)}$ SL (${target - roll}$)");
// Computing phrase with actor rollData
await phrase.compute(actor.getRollData());
// buildPhrase is a working string in which each formula has been replaced by a unique alphanumeric identifier
let buildPhrase = phrase.buildPhrase;
// values are the computed formulas of the phrase, indexed by the same unique alphanumeric identifier
let values = phrase.values;
// Rebuilding the phrase and setting each computed value bold
for (let key in values) {
buildPhrase = buildPhrase.replace(
key,
'<b>' + values[key].result + '</b>'
);
}
console.log(buildPhrase);
You can configure various display options in the template, with the Configure sheet display
button.
You can configure the sheet default display width and height, the profile picture size and the header fields placement.
You can add simple attribute bars by adding Number fields with a maximum value parameter. But what if your attribute bar's value is automatically calculated ?
Then you just need to click the Configure Attribute Bars button in the template, and add you attribute bar settings there. Each bar must have a Name, a Value and a Maximum. The Value and the Maximum can both be formulas !
Active effects are added to a token by a submenu in the token view. By clicking on an icon, you can add an active effect to the character represented by the token. And now, you can add modifiers triggered by Active Effects !
In the template sheet, click the Configure active effects
button. From here, a window opens with all the active
effects available to be modified. For each active effect, you can set any number of modifier you want, acting the same
way as item modifiers.
You can customize your sheets with your own CSS. You can add a CSS file in the system configuration. The path must lead to a CSS file, starting from Foundry's Data Directory.
In the system settings, you can set your custom initiative formula. This formula must be formatted like a roll formula,
returning only an integer. The ${}$
must not be set around the formula.
By default, chat messages with rolls will follow FoundryVTT's roll visibility's system, meaning that the messages will always be visible by everyone but
- When Public Roll is selected, the roll & formulas data will be visibile by everyone.
- When Private GM Roll is selected, the roll & formulas data will be visible by the GM and the message's author, other players will see a question mark instead.
- When Blind GM Roll is selected, the roll & formulas data will be visible by the GM only, other players will see a question mark instead.
- When Self Roll is selected, the roll & formulas data will be visible by the author only, other players will see a question mark instead.
In the system's settings, you can expand the roll visibility to the whole messages posted using the sheets. The message will be whispered to its recipients only, and not polluting the chat Log. Players will still be able to show the message to everyone using the right-click > Show to everyone option, and the roll and formulas data will follow the rules above.
You can add an icon next to rollable labels to make them more visible. To do so, in the system configuration enter a
valid fontawesome icon name, like dice-d20
, to show this icon next to all rollable items.
You can configure diagonal movement measurement in the system settings. You have 4 choices for the measurement rule :
- Equidistant : Diagonal movement has the same value as straight movement on the scenes
- Alternating : Diagonal movement alternates between 1 and 2 times the straight movement (Pathfinder style)
- Euclidian : Diagonal movement is calculated as the hypotenuse of the triangle formed with the straight path needed for the movement.
- Custom : You can enter a custom multiplier for diagonal movement, which will be applied to the straight movement value.
You can export your templates using the button in the settings tab, and reimport them in another instance using the Import button. This will import every template configuration. If the receiving instance has templates with names existing in the export file, the templates will be overwritten.
You can extend the system by adding your own component types to it, from a module. To do so, you must create a class
inheriting the Component
class. Two additional abstract classes are present in the base system : InputComponent
,
designed to represent Components which can receive user inputs and Container
, designed to represent Components holding
other Components.
Once your class is developed, you can add it to the system by
calling componentFactory.addComponentType('<componentTechnicalName>', <Class>);
in the Hook customSystemBuilderReady
.
Example :
Hooks.once('customSystemBuilderReady', () => {
componentFactory.addComponentType('myAwesomeComponent', AwesomeComponent);
});
Please refer to the API for Component implementation.
Please let me know by an issue if you implement a Component you think should be available to all ! Link your implementation, and I'll most likely add it to the system !
Thanks to Seregras for the Sandbox System Builder which inspired me very much building this system. I hope I will not cause too much trouble to them, after building a "concurrent" system.
Thanks to the FoundryVTT dev community for their help and reactivity on the Discord !
Thanks to Martin1522 for his help on the code :)
Thanks to Graham Wallis, RafaelC, ForjaSalvaje, darleth, RoxanneClark, Havoc, Nephilim, Gcolgate, Martin1522, Gabryel, RomainMoreau and Jessica for the Coffees !