Repository navigation
Writing a Game System
Is This A Mimic(s)? is an inventory engine. Everything that belongs to one game comes from a game system: its item templates, features, states, money, weight unit, catalog, roll tables, its own words for a few things, and the panels above the inventory.
D&D 5e (SRD 5.1) comes with the app, written as a system (js/systems/dnd5e.js, js/systems/dnd5e-panels.js). So does a small made-up example deliberately unlike it, Slot Delve (js/systems/slot-delve.js): slots instead of pounds, the silver standard, armor points instead of Armor Class. Read the two side by side.
A system is a JSON file. Import it in Settings → Files → Import a file. Choose it for a campaign in the campaign's Edit. Export any system (Files → Game systems → Export) to get a file to start from.
{
"mimic": "system",
"format": 1,
"id": "my-game",
"name": "My Game",
"version": 1,
"description": "…",
"templates": [],
"features": [],
"commonFields": [],
"states": [],
"values": [],
"stats": {},
"campaignSettings": [],
"terminology": {},
"currency": {},
"weight": {},
"starterTemplates": [],
"panels": [],
"panelLib": "…",
"catalog": [],
"tables": [],
"glossary": {}
}-
mimic,format(1) andidare required. Files made by Pack Rat, the app this one replaced, are refused.-
idmust not be one the app already has: to change D&D 5e, export it and give your copy a new id. - Re-importing a file with the same id updates that system.
-
- Everything else is optional, apart from at least one template.
- A file the app can't use is refused when imported.
Templates decide an item's fields and what it can do. They nest: a template with a parent is a kind of that one.
- The top templates (no
parent) are what the catalog's list, the inventory filters and the colours show. Each has ahue. - The templates under one are its sub-filters, each in a shade of its hue (or its own
hue). Its own categories are sub-filters too. -
abstract: true: a template that only gathers others (Slot Delve's Arms & Armor; D&D 5e's Gear & Tools). Nothing is made with it. - A template's categories are the next level down: under Gear & Tools, Tools, then Artisan's Tools, Gaming Set… (each a little lighter or darker in Tools' shade).
- List them in the order they show, a template's ones after it.
- The only difference between a system's templates and a player's own is that players can change and delete their own. A player's can go under any template.
{ "id": "arms", "name": "Arms & Armor", "plural": "Arms & Armor", "hue": 5, "icon": "swords", "abstract": true }{
"id": "sd-weapon", "name": "Weapon", "plural": "Weapons", "parent": "arms", "icon": "swords",
"categories": ["Blade", "Club"], "categoryLabel": "Kind",
"fields": [{ "key": "reach", "label": "Reach", "kind": "text" }],
"features": { "attack": "main", "equippable": true, "stacks": false },
"defaults": { "hands": "1" },
"hide": ["description"],
"layers": { "identified": {} },
"triggers": []
}Fields
-
kindis one of:text,textarea,number,checkbox,select(withoptions, optionallabelsandsegmented),tags(withsuggestions),dice. - Every item also has name, icon, image, cost, weight, description and source, plus the system's
commonFields(D&D 5e: rarity). -
hideremoves description or source.
Features (what an item can do) are listed by key, as true (items start with it and each item can change it), "main" (always on, with its fields among the template's own) or false.
-
Core features, available in every system:
-
holds(a container) liquid-
deck(a set of pieces) -
pack(unpacks into items) -
writable(written in) pictureequippable-
stacks(on unlessfalse) chargesbundle
-
- A system's own features have the same shape:
{ "key": "protection", "label": "Armor", "equip": "worn", "hint": "Worn for armor points.",
"fields": [{ "key": "armorPoints", "label": "Armor points", "kind": "number" }] }- A feature with
flagis a yes/no field of the item itself (D&D 5e'sattunement). -
equipsays how an item with the feature is equipped:"wielded"(the sword button) or"worn"(the shield button, and "worn" on its row).
Icons: icon is the item icon used when an item has none of its own (see the icon picker for names).
Item types: an item's type is the id of the template it was made from. Items made from a copy of a template also keep template.
States are what an item in an inventory can be:
"states": [
{ "key": "attuned", "label": "Attuned", "who": "player", "requires": "attunement",
"limit": { "value": "attuneSlots", "default": 3, "mode": "warn",
"text": "You can attune to at most {limit} items", "label": "Attunement slots" } },
{ "key": "identified", "label": "Identified", "who": "gm" }
]-
who: the player switches"player"states with a checkbox on the item; only the GM switches"gm"ones. The GM can always override any state (On / Off / the player's choice). -
requires: only items with that feature (or a layer for the state) can have it."given": only items given it (a layer for it, from their editor's + Add a state…), as campaign states made in the item editor are.requiresProperty: and only those with a property starting with it ("Versatile"for D&D 5e's Two-handed). - A campaign can add states of its own (Settings → Campaigns → Item states); the system's can't be removed.
-
limit: how many of a character's items can have the state on.- The number is the character's Local value named
value(the GM sets it on the character's card, or in Controls → Values), elsedefault. -
modeis"warn"(allowed, with a warning) or"block". - The GM sets each character's limit on their card in the GM tab.
- The number is the character's Local value named
Layers are what an item becomes in each state. An item, or a template, has layers: { stateKey: { … } }:
"layers": {
"identified": { "name": "Ring of Protection", "cost": 350000, "acBonus": 1, "description": "…" },
"cursed": { "weight": 10, "locks": ["attuned"], "features": { "charges": true }, "maxCharges": 3 }
}- A layer holds only what changes: field values,
featuresit turns on, andlocks(states only the GM can change while it's on). - The item as it is now is the item plus the layers of its states that are on, in the order the system lists its states.
- Everything uses the item as it is now: names, weight, value, sale prices, features and panels.
Rules are on items or templates, under triggers:
"triggers": [
{ "when": "becomes", "test": [{ "value": "item.charges", "op": "=", "to": 0 }],
"do": [{ "set": "item.state.cursed", "to": 1 }], "message": "The wand crumbles to dust." },
{ "when": "while", "every": 1, "test": [{ "value": "item.equipped", "op": "=", "to": 1 }],
"do": [{ "add": "local.heat", "by": 1 }] },
{ "when": "acquired", "if": [{ "value": "item.state.identified", "op": "=", "to": 0 }],
"do": [{ "set": "item.state.cursed", "to": "on" }] }
]-
when:-
becomes: once, as all the conditions intestturn true (whatever changed them: the player, the GM, a trade, another rule, a value moving over time); -
while: everyeveryseconds (default 1) while they all hold; -
acquired: it enters a character's inventory (added, bought, given or traded); -
equipped,unequipped.
-
-
A condition:
{ "value": address, "op": op, "to": … }.-
op:=,!=,<,<=,>,>=, orbetween(withto2). -
to: a number,onoroff, one of the value'schoicesby name, or a formula. - Addresses:
item.state.<key>(1 on, 0 off),item.charges,item.qty,item.equipped,item.fill,item.worth,item.weight,char.count.<state>,char.limit.<state>,char.stat.<key>,char.carried,local.<name>(the item's Local value, else its character's),char.local.<name>,global.<name>.
-
-
if: optional conditions that must also hold when it fires. -
do: actions:-
{ "set": address, "to": … }: a state, charges or a Local value (toas for conditions); -
{ "add": address, "by": … }: charges or a Local value (negative takes away); -
{ "change": "local.<name>", "rate": n, "until": n }: a Local value moving over time (untiloptional); -
{ "message": text }. - Rules on items can't set Global values: those are the GM's (the GM's rule book can, see below).
-
-
Formulas: numbers and addresses,
+ - * / % ^, brackets, andmin max clamp abs round floor ceil sqrt:"local.heat / 10". -
message: shown to the player. -
An item's actions are rules too:
{ "when": "used", "button": "Draw a card", "do": [{ "take": "random" }] }gives the item a button with those words (in its details and item menu).{ "when": "signal", "signal": "Flame", "do": [...] }runs when a hotspot of its drawing asks for "Flame" (a hotspot layer of that name, with the action Run the item's rules). Only these two can use the actions that ask the person or open something:-
{ "take": "random" | "choose" | "all", "to": "window" }: one out at random, one you choose, or everything; straight into the inventory, or with"to": "window"into a window that shows what came out (Take one more, at random; Put back; Keep), as a set's draw does; -
{ "putIn": 1 }: choose something you carry to put in; -
{ "use": n }: the stack goes down by n (negative: up); -
{ "details": 1 }: show its details; -
{ "press": "<button>" }: use another of its actions. A set (deckCards) with nousedrules of its own has Draw a card (one at random) and Take all out.
-
-
appliesTo(optional): a rule an item carries applies to that item; it can also name a character ({ "character": id }), so it runs only for that character's copy. -
The campaign's rule book (GM → Controls → Rules, kept in the campaign, not the system) holds rules with any
appliesTo:{ character, item ("<catalog id>" or "template:<id>"), drawing }. One naming nothing is a world rule: it runs on the GM's device, may set Global values (set,add,changeonglobal.<name>) and open or close a shop ({ "shop": id, "open": 1 | 0 }). For the same event on the same item, the most specific rule runs and the less specific ones don't (a character + an item beats an item, an item beats its template).
Rules run on the device that owns the character, after every change, and while rules on a 4-a-second tick. A chain of state changes stops after 10 steps; a rule firing over and over (two rules undoing each other) is held back.
Global and Local values are numbers the GM sets (see the guide's GM Controls page). A system can declare the ones it uses, to give them a label and a shape in the GM's Values tab:
"values": [
{ "name": "storm", "scope": "global", "label": "Storm", "default": 0, "min": 0, "max": 1, "step": 0.1 },
{ "name": "curse", "scope": "local", "label": "Curse", "choices": ["None", "Uneasy", "Gripped", "Bound"] }
]-
scopeis"global"(one for the whole campaign) or"local"(a character's, or an item's). -
choicesnames each step (0, 1, 2…): the GM picks from a list. - A state's
limit.valueis a Local value too (see above).
Where equipped things go. Each slot has a count, and rules saying what goes in it (the same rules as a container's allowed contents: item, template (and those under it), category, feature, property, name), each taking size places; a rule with a state applies only while that state is on. An item goes in the first slot with a rule it matches. Equipping into a full slot asks what to take out (or to switch off a state that makes something take more room). Without slots, anything can be equipped.
"slots": [
{ "key": "hands", "label": "Hands", "count": 2, "rules": [
{ "feature": "weapon", "property": "Two-handed", "size": 2 },
{ "feature": "weapon", "state": "twoHanded", "size": 2 },
{ "feature": "weapon" },
{ "feature": "armor", "category": ["Shield"] } ] },
{ "key": "armor", "label": "Armor", "count": 1, "rules": [{ "feature": "armor" }] }
]An item can say its own: slot (a slot's key, or "none") and slotSize.
stats are the character values your panels use, with their starting values: { "str": 10, "con": 10 }.
- Panels read them as
state.character.statsand set them withMimic.request("setStat", { key, value }). - Values can be numbers, short strings, true/false, or short lists.
campaignSettings are the campaign rules settings your system uses. The only built-in one is D&D 5e's "encumbrance" (shown on the campaign's card in Settings → Campaigns, and in its Edit); leave it empty otherwise. A per-character number such as D&D 5e's carry multiplier belongs in your Character panel, as a stat (setStat).
terminology renames a few of the app's own words:
| Key | The app says | Slot Delve says |
|---|---|---|
character / characters
|
character(s) | adventurer(s) |
gm |
GM | Referee |
gameMaster |
Game master | Referee |
rollTable / rollTitle
|
Roll on a table | Roll a curio |
"currency": {
"coins": [
{ "key": "gp", "name": "gold", "value": 200 },
{ "key": "sp", "name": "silver", "value": 10 },
{ "key": "cp", "name": "copper", "value": 1, "color": "#c07a45" }
],
"change": ["sp", "cp"],
"show": "sp",
"perWeight": 100
},
"weight": { "unit": "slots", "one": "slot" }Coins
- List coins largest first. Each is worth
valueof the smallest, and one coin must be worth 1. - Prices (
cost) are in the smallest coin.
How money moves and shows
-
change: the coins shops pay out and give change in. -
show: the coin prices are shown in. Money that isn't decimal shows as coins ("4 sh 2 d"), not fractions. -
perWeight: how many coins weigh one unit (0: coins weigh nothing).
The party servers are sent these rules when the GM loads a campaign, and work out purchases, sales and change the same way the app does.
Weight: weight.unit names the unit weights and capacities are in; one is the singular.
catalog items are what Catalog → All items lists:
{ "id": "sd-sword", "type": "sd-weapon", "name": "Sword", "damage": "d8", "hands": "1", "cost": 200, "weight": 1 }- Ids must be unique and are kept by inventory copies, so don't change them between versions.
- Items can use any template field and feature field, plus
features({ "liquid": true }),layersandtriggers.
tables are roll tables (the roll button in the catalog):
{ "id": "sd-curios", "name": "Curios", "die": "d4", "book": "Slot Delve",
"entries": [{ "roll": 1, "id": "sd-curio-1" }] }Each entry's id is a catalog item.
glossary holds tooltips for property tags: { "Finesse": "Use Strength or Dexterity…" }.
starterTemplates are example templates players can add from Catalog → Templates.
showcase: false turns off the example items a new install's first character starts with
(a backpack whose picture fills as it's loaded, stacks whose pictures count them, a compass and a
totem following the GM's map, a pocket watch, a deck of cards: see js/showcase.js). They're
made from the system's container, consumable, ammunition, tool and gear templates where
it has them, else its first template. Leave it out to keep them.
Panels are your system's own screens:
-
"inventory"panels sit above the inventory (D&D 5e: Character and Equipment; Slot Delve: Adventurer and Weapons); -
"gm-player"panels give the summary on the GM's player cards.
"panels": [
{ "id": "adventurer", "slot": "inventory", "title": "Adventurer", "html": "<div id=\"root\"></div><script>…</script>" },
{ "id": "referee", "slot": "gm-player", "title": "Referee", "html": "…" }
],
"panelLib": "…script every panel gets first (shared rules)…"shows (optional, on an inventory panel): what the panel shows in place of the app's own. ["slots"]: the panel shows the equipment slots (from state.slots), so the app leaves out its line of slots above the inventory. D&D 5e's Equipment panel does this.
The sandbox: a panel is HTML and JavaScript running in a sandboxed frame. It can't reach the app's page, storage, cookies or the network; it only gets what the app sends it. It does get the app's stylesheet, so the app's classes (stats, stat, attack-grid, chip, muted…) look right in light and dark mode.
The Mimic object
-
Mimic.on("state", state => …): called with the character whenever anything changes.-
state.character:{ id, name, stats, coins }. -
state.items: one per inventory entry, as it is now:{ uid, name, qty, equipped, states, values, features, main, template, root, location, inHolder, item, icon }. -
state.totals:{ carried, coinsWorth, gearWorth, coins: [{ key, count }] }. -
state.limits: per limited state,{ count, limit }. -
state.slots: the equipment slots (seeslots), each{ key, label, count, used, items: [{ uid, size }] }: how many places, how many are taken, and by which items. -
state.values:{ global: { name: n }, local: { name: n } }: the Global values and the character's Locals (each item's own Locals are itsvalues). -
state.settings: the campaign's rules settings.
-
-
Mimic.request(action, args)asks for a change. The app checks each request first:-
setStat({ key, value }); -
equip({ uid, on }); -
setState({ uid, key, on }, with limits and locks applied); -
setValue({ name, uid?, value }): one of the character's Local values, or an item's own withuid;nullclears it. Players can't set states' limits (attuneSlots), only the GM can; -
changeQty({ uid, delta }); -
openItem({ uid }); -
openCoins.
-
-
Mimic.summary(parts)sets the panel's header.- Each part is
{ text, icon?, title?, warn?, closed? }or{ coins: true }(the coin purse). - Parts marked
closedonly show while the panel is collapsed.
- Each part is
-
gm-player panels use
Mimic.on("compute", state => ({ stats: [{ label, value, title?, sub?, warn? }], chips: [text], notes: [text] }))instead of"state". -
Helpers:
Mimic.h(tag, attrs, ...children),Mimic.icon(name),Mimic.markup(html)(an item'siconmarkup),Mimic.render(el, ...children)(a field being typed in stays as it is while the panel redraws: give inputs anaria-labelorname),Mimic.fmtMod(n),Mimic.glossary.
Writing panels as functions: the built-in systems write panels as JavaScript functions and put their source in the file ("(" + fn + ")();"), so they can be read and checked like any other code. Do the same, or write the HTML directly.
Is This A Mimic(s)? · Download · Web app · Screenshots show SRD 5.1 content only.
Playing
- Getting Started
- Your Inventory
- Containers
- Equipment
- Coins
- Sets and Drawing Cards
- Writing and Pictures
- Campaigns
Making things
Playing together
Everything else