QBScript Reference
QBScript is a dynamically typed, interpreted language for automating Quest Bound rulesets. It uses Python-like structure (indentation-based blocks, simple syntax) with C-like operators (&&, ||, //, etc.), so it stays approachable for designers with some technical background.
Script types
Scripts can be attached to:
- Attributes — Reactive scripts that recompute values when dependencies change
- Actions — Event-driven scripts that run when triggered from the UI
- Items — Event handlers for equip, unequip, consume, etc.
- Archetypes — Event handlers for
on_addandon_removewhen an archetype is added to or removed from a character - Global — Utility modules that provide shared functions and variables
- Character Loader — Runs once at character creation only, before loading attribute defaults and archetype
on_addscript events. There can only be one character loader script per ruleset.
Character Loader
- Execution order: Right after every character is created. Character Creation -> Character Loader -> Default attribute values and all attribute scripts -> Archetype
on_addevents - Context: Character loader scripts have the same accessors as other scripts:
Owner(attributes, actions, items, inventory,Owner.archetypes,Owner.hasArchetype, add/remove archetype). The full script runs once; there is no named event handler. - Export/import: Stored in the
character_loaders/folder; if a ruleset already has a Character Loader, importing a second one is skipped with a warning.
Basic syntax
Strings
Use single or double quotes. Prefer double quotes when the text contains apostrophes.
column_key = 'Spells';
chart_name = 'Wizard Spells';
shop_name = "Tabby's Tavern";
String methods:
.toUpperCase()— returns the string in upper case.toLowerCase()— returns the string in lower case
String interpolation: Embed variables in any string with {{variable}}:
message = 'You have {{hp}} health';
announce('Damage: {{damage}}');
Variables
No keyword is needed; assignment creates or updates a variable.
hit_points = Owner.Attribute('Hit Points');
damage = 10;
name = 'Fireball';
Comments
// Single-line comment
/*
Multi-line
comment
*/
Operators
Math: + - * / ** (exponentiation, e.g. 2**3 = 8) / (integer division) % (modulo)
Comparison: > < >= <= == !=
Boolean: && (and) || (or) ! (not)
Arrays
list = [];
Array methods:
| Method | Return Value |
|---|---|
list.count() | Number of items |
list.first() | First item |
list.last() | Last item |
list.push(item) | Add item to end |
list.pop() | Remove and return last item |
list.random() | Random element |
list.filter() | Copy with only truthy values |
list.filterEmpty() | Copy with non-empty values (excludes '', null) |
list[index] | Access by zero-based index |
Loops
For-in over an array:
for arrow in arrows:
arrow.consume()
For-in over a number (0 to N-1):
for i in 10:
do_something()
Control flow
Indentation defines blocks; there is no end keyword. Parentheses around conditions are optional.
if condition:
// body
else if other_condition:
// body
else:
// body
Functions
Define with a name and parameters; there is no function keyword. Use return to provide a result
or halt execution.
calculateModifier(score):
return (score - 10) / 2
getMaxHP(con, level):
base = 10
return base + (con * 2) + (level * 5)
Built-in functions
Dice
| Function | Description |
|---|---|
roll(expression) | Roll dice from a string (e.g. '1d8', '2d6+4'). Returns a number. Expression can use interpolation: roll('{{level}}d4'). |
Examples:
roll('1d8');
roll('2d6+4');
damage = roll('1d8');
Math
| Function | Description |
|---|---|
floor(x) | Round down (e.g. floor(3.7) → 3) |
ceil(x) | Round up (e.g. ceil(3.2) → 4) |
round(x) | Round to nearest (e.g. round(3.5) → 4) |
abs(x) | Absolute value |
min(a, b) | Smaller of two values |
max(a, b) | Larger of two values |
Type conversion
| Function | Description |
|---|---|
number(x) | Convert to a number. Strings have commas stripped first (e.g. "1,000" → 1000, "3.14" → 3.14). |
text(x) | Convert to a string (e.g. text(42) → "42"; null become ""). |
UI and debugging
| Function | Description |
|---|---|
announce(message) | Show a message to the player. Arguments are joined with spaces. |
log(...) | Send output to the debug console and game log. Multiple arguments supported; strings are wrapped in quotes in the log. |
Accessing character data
Accessors
| Accessor | Meaning |
|---|---|
Owner | The character that initiated the script |
Ruleset | Ruleset-level entities (attributes, charts, items, actions) |
Self | The entity to which the script is attached — same as Owner.Attribute('<this attribute>') |
Getters
Use these to resolve entities by name:
| Call | Returns |
|---|---|
<Accessor>.Attribute('attribute name') | Attribute reference (character or ruleset) |
getAttr('attribute name) | Shorthand for Owner.Attribute('attribute name').value |
Owner.hasArchetype('archetype name') | Whether the character has the given archetype |
<Accessor>.Action('action name') | Action reference |
<Accessor>.Item('item name') | First matching item instance |
<Accessor>.Items('item name') | Array of matching item instances |
Ruleset.Chart('chart name') | Chart reference |
getChart('chart name') | Shorthand for Ruleset.Chart('chart name') |
Examples:
Owner.Attribute('Hit Points'); // Character's Hit Points
Owner.Action('Attack'); // Character's Attack action
Ruleset.Attribute('Strength'); // Attribute definition (ruleset)
Character (Owner)
Identity:
Owner.name— character's nameOwner.title— same asname
Archetypes:
Owner.archetypes- returns an array of all the character's archetype namesOwner.hasArchetype('archetype name')— whether the character has the given archetypeOwner.addArchetype('archetype name')- adds the archetype to the characterOwner.removeArchetype('archetype name')- removes the archetype from the character
Items:
Owner.Item('item name')— first matching itemOwner.Items('item name')— array of matching itemsOwner.hasItem('item name')— whether the character has at least oneOwner.addItem('item name', quantity)— add to inventory (quantity defaults to 1)Owner.removeItem('item name', quantity)— remove from inventoryOwner.setItem('item name', quantity)— set total quantity (consolidates to one stack; 0 removes all)
Attributes:
Owner.Attribute('attribute name')— character's attribute instancegetAttr('attribute name')— character's attribute instance's value, shorthand forOwner.Attribute('attribute name').value
Attribute API
Attribute scripts are reactive: they re-run when subscribed dependencies change and must return a value to set the attribute.
Reading and writing:
| Member | Description |
|---|---|
Owner.Attribute('attr name').value | Current value (use in expressions) |
Owner.Attribute('attr name').max | Maximum value |
Owner.Attribute('attr name').min | Minimum value |
Owner.Attribute('attr name').options | Current list of options (list-type attributes); character override or ruleset default |
Owner.Attribute('attr name').random | Returns a random option (list attributes) |
Owner.Attribute('attr name').set(value) | Set value |
Owner.Attribute('attr name').add(n) | Add to current value (numeric) |
Owner.Attribute('attr name').subtract(n) | Subtract (numeric) |
Owner.Attribute('attr name').multiply(n) | Multiply current value |
Owner.Attribute('attr name').divide(n) | Divide current value |
Owner.Attribute('attr name').setMax(n) | Set maximum value |
Owner.Attribute('attr name').setMin(n) | Set min value |
Owner.Attribute('attr name').setOptions(list) | Set the attribute's options (list-type); values are coerced to strings. |
Owner.Attribute('attr name').resetOptions() | Reset options to the ruleset attribute definition. |
Owner.Attribute('attr name').setRandom() | Sets to a random option (list attributes) |
Owner.Attribute('attr name').next() | Set to next option (list) |
Owner.Attribute('attr name').prev() | Set to previous option (list) |
You can assign an attribute to a variable for easier access.
attr = Owner.Attribute('attr name')
attr.set(10)
Attribute subscriptions (attribute scripts only): Declare dependencies so the script re-runs when they change. You can pass string literals or variables.
subscribe('attribute one', 'attribute two'); // Re-run when these change
subscribe('action name'); // Re-run when this action is activated
attr_name = 'Constitution';
subscribe(attr_name, 'Level');
Charts
Get a chart with getChart('chart name'), then use:
| Method | Description |
|---|---|
chart.get('column name') | All values in that column (array) |
chart.randomColumn() | All values from a random column |
chart.randomCell() | Value of a random cell |
chart.randomNonEmptyCell() | Value of a random non-empty cell |
chart.valueInColumn('column name') | Value from that column in the first data row (row immediately after the header row) |
chart.rowWhere('column name', value) | A row proxy for the first row where the given column equals value (or an empty row if not found); chain with .valueInColumn('other column') to read a value. |
chart.rowWhere('column name', value).valueInColumn('other column') | Convenience pattern: find a row by one column and read a value from another column in that same row (returns '' if the row is empty or the column is not found). |
Examples:
spell_damage = getChart('Spells').rowWhere('Spell Name', 'Fireball').valueInColumn('Damage');
xp_needed = getChart('Level Table').rowWhere('Level', 5).valueInColumn('XP Required');
Items
Reading item data:
item.title— item titleitem.description— item descriptionitem.count()— quantityitem.isEquipped— whether the item is equippeditem.isConsumable— whether the item is consumable
Custom properties
Custom properties are key-value fields (string, number, or boolean) you define on the ruleset. They can be attached to both characters and items. Scripts work with the current value for a specific character or a specific item instance.
Character custom properties:
Owner.getProperty('Level')— read a character custom property value (ornullif it is not set).Owner.setProperty('Level', 5)— set a character custom property value; changes are persisted with the character.
Item custom properties:
- Custom properties are defined on the ruleset item; each inventory instance can override the default value.
item.getProperty('Armor Value')— read the value for this specific inventory item (instance override if present, otherwise the item definition default).item.setProperty('Armor Value', 15)— set the value for this specific inventory item instance.- You can only call
setPropertyon a single item returned byOwner.Item('item name'); items returned fromOwner.Items('item name')are read-only for custom properties.
// Character-level custom property
level = Owner.getProperty('Level') ?? 1;
Owner.setProperty('Level', level + 1);
// Item-level custom property
armor = Owner.Item('Plate Mail');
if armor != null:
current = armor.getProperty('Armor Value') ?? 15;
armor.setProperty('Armor Value', current + 1);
Actions
Owner.Action('action name')— get action referenceaction.activate()— run the action’son_activate()
Quick reference
Ruleset:
Ruleset.Chart('chart name')Ruleset.Attribute('attribute name')Ruleset.Item('item name')Ruleset.Action('action name')
Attribute script: Use subscribe(...) and return <value>.
Item events: on_activate, on_equip, on_unequip, on_consume — each handler should end with return.
Archetype events: on_add() and on_remove() — run when the archetype is added to or removed from a character. Can alter attributes, add/remove items, etc.
Action events: on_activate() and optionally on_deactivate(). Include Target in the signature when the action needs a target character.
Global scripts: Mark a script as global; its variables and functions are available to all other scripts in the ruleset.