Quick start
This page loads the 5E SRD pack, computes a character’s sheet, changes the character and rolls an attack. The other guides explain each step in depth.
Install
Section titled “Install”npm install @opencharacter/engine @opencharacter/5e-srd-5.1@opencharacter/engineis the TypeScript reference engine. It has no runtime dependencies on Node, so it runs in browsers, servers and workers. It ships as TypeScript source, so use a runner or bundler that compiles TypeScript (tsx, Vite, Bun, Deno).@opencharacter/5e-srd-5.1is one content pack: its rules, content and English text.
Load, compute, change, roll
Section titled “Load, compute, change, roll”import { applyChanges, computeSheet, loadRuleset, startAction } from '@opencharacter/engine';import type { CharacterFile } from '@opencharacter/engine';import srd from '@opencharacter/5e-srd-5.1';import en from '@opencharacter/5e-srd-5.1/i18n/en';
// 1. Load the packs into a ruleset. Do this once and keep the result.const loaded = loadRuleset([srd]);if (!loaded.ok) throw new Error(JSON.stringify(loaded.errors));const { ruleset } = loaded;
// 2. A character file holds only base state: inputs, owned items, picks. The CharacterFile type// catches a misspelled or missing property before anything runs.let character: CharacterFile = { specVersion: '0.1.0', id: 'quick-start', packs: [{ id: '5e-srd-5.1', version: '0.1.0' }], values: { '5e-srd-5.1:abilities.str.base': 16, '5e-srd-5.1:abilities.dex.base': 12, }, inventory: [{ id: 'sword', item: '5e-srd-5.1:weapons.longsword', equipped: true }],};
// 3. Compute the sheet. Every value is derived here, never saved.const sheet = computeSheet(ruleset, character);const mod = sheet.values['5e-srd-5.1:abilities.str.mod'];console.log(en['fields.abilities.str.mod.name'], mod?.value); // Strength modifier 3
// 4. Change the character through the engine, never by editing the file.const result = applyChanges(ruleset, character, [ { type: 'set', field: '5e-srd-5.1:abilities.str.base', value: 18 },]);if (!result.ok) throw new Error(JSON.stringify(result.errors));character = result.character; // save this
// 5. Roll an attack with the sword. The engine rolls the dice itself by default.const attack = startAction(ruleset, character, { action: { id: '5e-srd-5.1:weapon-attack', entry: 'sword' }, params: { targetAc: 13 },});if ('status' in attack && attack.status === 'finished') { console.log(attack.outcome); // e.g. { result: 'hit', data: { critical: false, attack: 17 } } console.log(attack.proposedChanges); // damage for the target, for your app to apply}What happened:
loadRulesetmerged the pack, resolved every reference and checked every formula. The ruleset is plain JSON; cache it.- The character is a plain object in the
character file format. Every ID in it is qualified
with its pack (
5e-srd-5.1:…). computeSheetreturned the Strength modifier (and every other value) with a breakdown of where it came from. It also listed the actions the character can take now, includingweapon-attackon the equipped sword.applyChangesreturned a new character. It never changes the one you pass in, so the old one is still there for undo.startActionbuilt the attack roll, rolled it, decided hit or miss and proposed damage for the target. Applying the damage is up to your app (Other characters).
The text came from the pack’s English file, by key. The engine never returns display text (Text and translation).
Test data
Section titled “Test data”The example characters in examples/characters/ are good test
data: 5e-tamsin.yaml exercises effects, templates, competing options and Bless, and
5e-goblin.yaml is a monster.
- Implementing OCF: how much of OCF your app needs, and where the engine runs.
- Packs and rulesets: getting packs and loading them properly.
- Storing characters: what to save and how.