Skip to main content

System creation

Contribute on GitHub License: CC BY-NC-SA 4.0

Prerequisites

In order to create systems for the app, you will need a couple things. These might seem technical, but if you need help a quick Google (or question to your AI of choice) might help a lot:

  1. A copy of the RPG Companion App (you can download it from here).
  2. A cool text editor (any text editor will work, but Visual Studio Code is recommended as you will be able to use some special snippets that we've created for you, to make your task easier).
  3. The RPG Companion App dev tool, which will help you test your system with the app.
    a. You need to have the app in "Developer Mode" to be able to develop systems. You can toggle this in the app's settings.
  4. A Wi-Fi network, which you should have both your laptop and phone connected to.

That's it! I know it seems complicated, but this was the hardest part.

Prefer starting from a template?

You can clone a working folder structure and example system.rpg.json from the Open Systems Repository:
github.com/blastervla/rpg-companion-app-systems

If you choose to use Visual Studio Code, optionally, you can go through the following steps to make your development experience a bit better:

  1. Open up the editor
  2. Use the command palette (cmd + option + p) to and choose the option Preferences: Open Keyboard Shortcuts (JSON).
  3. Insert the following block within the brackets:
    {
    "key": "shift+cmd+i",
    "command": "editor.action.insertSnippet",
    "when": "!editorReadonly"
    }
  4. Once again, open up the command palette and choose Snippets: Configure Snippets. Choose New Global Snippets file.
  5. Name the file rpg.json.
  6. Copy and paste the contents of the following json to the new created file.
    rpg.json snippets file content
    {
    // Place your global snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and
    // description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope
    // is left empty or omitted, the snippet gets applied to all languages. The prefix is what is
    // used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
    // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders.
    // Placeholders with the same ids are connected.
    // Example:
    // "Print to console": {
    // "scope": "javascript,typescript",
    // "prefix": "log",
    // "body": [
    // "console.log('$1');",
    // "$2"
    // ],
    // "description": "Log output to console"
    // }
    "statStat": {
    "prefix": "stat",
    "scope": "json",
    "body": [
    "{",
    "\t\"type\": \"stat\",",
    "\t\"stat\": \"\"",
    "}"
    ]
    },
    "definedStat": {
    "prefix": "statDefined",
    "scope": "json",
    "body": [
    "{",
    "\t\"type\": \"defined\",",
    "\t\"stat\": \"\"",
    "}"
    ]
    },
    "metaStatStat": {
    "prefix": "metaStat",
    "scope": "json",
    "body": [
    "{",
    "\t\"type\": \"metaStat\",",
    "\t\"meta_stat\": {}",
    "}"
    ]
    },
    "defaultIfNullStat": {
    "prefix": "statdefaultIfNull",
    "body": [
    "{",
    "\t\"type\": \"defaultIfNull\",",
    "\t\"components\": {},",
    "\t\"default_components\": {}",
    "}"
    ]
    },
    "mapperStat": {
    "prefix": "statmap",
    "body": [
    "{",
    "\t\"type\": \"map\",",
    "\t\"components\": {},",
    "\t\"mapper\": {}",
    "}"
    ]
    },
    "flatMapStat": {
    "prefix": "statFlatMap",
    "body": [
    "{",
    "\t\"type\": \"flatMap\",",
    "\t\"components\": {},",
    "\t\"mapper\": {}",
    "}"
    ]
    },
    "filterStat": {
    "prefix": "statfilter",
    "body": [
    "{",
    "\t\"type\": \"filter\",",
    "\t\"components\": {},",
    "\t\"filter\": {}",
    "}"
    ]
    },
    "findFirstStat": {
    "prefix": "statfindFirst",
    "body": [
    "{",
    "\t\"type\": \"findFirst\",",
    "\t\"components\": {},",
    "\t\"filter\": {}",
    "}"
    ]
    },
    "sortByStat": {
    "prefix": "statsortby",
    "body": [
    "{",
    "\t\"type\": \"sortBy\",",
    "\t\"order\": ?\"asc|desc\",",
    "\t\"sort_by_value_key\": ?\"\\$sortByValue\",",
    "\t\"components\": {},",
    "\t\"sort_by_selector\": {}",
    "}"
    ]
    },
    "enumeratedNameStat": {
    "prefix": "statEnumeratedName",
    "scope": "json",
    "body": [
    "{",
    "\t\"type\": \"enumeratedName\",",
    "\t\"enumerated_type\": \"\",",
    "\t\"id\": {}",
    "}"
    ]
    },
    "enumeratedAbbreviationStat": {
    "prefix": "statEnumeratedAbbreviation",
    "scope": "json",
    "body": [
    "{",
    "\t\"type\": \"enumeratedAbbreviation\",",
    "\t\"enumerated_type\": \"\",",
    "\t\"id\": {}",
    "}"
    ]
    },
    "joinToStringStat": {
    "prefix": "statJoinToString",
    "scope": "json",
    "body": [
    "{",
    "\t\"type\": \"joinToString\",",
    "\t\"separator\": \"\",",
    "\t\"components\": {}",
    "}"
    ]
    },
    "splitStat": {
    "prefix": "statSplit",
    "scope": "json",
    "body": [
    "{",
    "\t\"type\": \"split\",",
    "\t\"separator\": \"\",",
    "\t\"components\": {}",
    "}"
    ]
    },
    "concatStat": {
    "prefix": "statConcat",
    "scope": "json",
    "body": [
    "{",
    "\t\"type\": \"concat\",",
    "\t\"components\": {}",
    "}"
    ]
    },
    "isEmptyStat": {
    "prefix": "statIsEmpty",
    "scope": "json",
    "body": [
    "{",
    "\t\"type\": \"isEmpty\",",
    "\t\"components\": {}",
    "}"
    ]
    },
    "lengthStat": {
    "prefix": "statLength",
    "scope": "json",
    "body": [
    "{",
    "\t\"type\": \"length\",",
    "\t\"components\": {}",
    "}"
    ]
    },
    "trimStat": {
    "prefix": "statTrim",
    "scope": "json",
    "body": [
    "{",
    "\t\"type\": \"trim\",",
    "\t\"components\": {}",
    "}"
    ]
    },
    "containsStat": {
    "prefix": "statContains",
    "scope": "json",
    "body": [
    "{",
    "\t\"type\": \"contains\",",
    "\t\"haystack\": {},",
    "\t\"needle\": {}",
    "}"
    ]
    },
    "maxStat": {
    "prefix": "statMax",
    "scope": "json",
    "body": [
    "{",
    "\t\"type\": \"max\",",
    "\t\"components\": {}",
    "}"
    ]
    },
    "minStat": {
    "prefix": "statMin",
    "scope": "json",
    "body": [
    "{",
    "\t\"type\": \"min\",",
    "\t\"components\": {}",
    "}"
    ]
    },
    "copyResourceStat": {
    "prefix": "statCopyResource",
    "scope": "json",
    "body": [
    "{",
    "\t\"type\": \"copyResource\",",
    "\t\"components\": {}",
    "}"
    ]
    },
    "addStat": {
    "prefix": "statAdd",
    "scope": "json",
    "body": [
    "{",
    "\t\"type\": \"add\",",
    "\t\"components\": {}",
    "}"
    ]
    },
    "subtractStat": {
    "prefix": "statSubtract",
    "scope": "json",
    "body": [
    "{",
    "\t\"type\": \"subtract\",",
    "\t\"components\": {}",
    "}"
    ]
    },
    "multiplyStat": {
    "prefix": "statMultiply",
    "scope": "json",
    "body": [
    "{",
    "\t\"type\": \"multiply\",",
    "\t\"components\": {}",
    "}"
    ]
    },
    "divideStat": {
    "prefix": "statDivide",
    "scope": "json",
    "body": [
    "{",
    "\t\"type\": \"divide\",",
    "\t\"rounding_method\": \"down|up|closest|none\",",
    "\t\"components\": {}",
    "}"
    ]
    },
    "equalsStat": {
    "prefix": "statEquals",
    "scope": "json",
    "body": [
    "{",
    "\t\"type\": \"equals\",",
    "\t\"components\": {}",
    "}"
    ]
    },
    "andStat": {
    "prefix": "statAnd",
    "scope": "json",
    "body": [
    "{",
    "\t\"type\": \"and\",",
    "\t\"components\": {}",
    "}"
    ]
    },
    "orStat": {
    "prefix": "statOr",
    "scope": "json",
    "body": [
    "{",
    "\t\"type\": \"or\",",
    "\t\"components\": {}",
    "}"
    ]
    },
    "notEqualsStat": {
    "prefix": "statNotEquals",
    "scope": "json",
    "body": [
    "{",
    "\t\"type\": \"notEquals\",",
    "\t\"components\": {}",
    "}"
    ]
    },
    "notNullStat": {
    "prefix": "statNotNull",
    "scope": "json",
    "body": [
    "{",
    "\t\"type\": \"notNull\",",
    "\t\"components\": {}",
    "}"
    ]
    },
    "isNullStat": {
    "prefix": "statIsNull",
    "scope": "json",
    "body": [
    "{",
    "\t\"type\": \"isNull\",",
    "\t\"components\": {}",
    "}"
    ]
    },
    "notStat": {
    "prefix": "statNot",
    "scope": "json",
    "body": [
    "{",
    "\t\"type\": \"not\",",
    "\t\"components\": {}",
    "}"
    ]
    },
    "greaterThanEqualsStat": {
    "prefix": "statGTE",
    "scope": "json",
    "body": [
    "{",
    "\t\"type\": \"greaterThanEquals\",",
    "\t\"components\": {}",
    "}"
    ]
    },
    "greaterThanStat": {
    "prefix": "statGT",
    "scope": "json",
    "body": [
    "{",
    "\t\"type\": \"greaterThan\",",
    "\t\"components\": {}",
    "}"
    ]
    },
    "lessThanEqualsStat": {
    "prefix": "statLTE",
    "scope": "json",
    "body": [
    "{",
    "\t\"type\": \"lessThanEquals\",",
    "\t\"components\": {}",
    "}"
    ]
    },
    "lessThanStat": {
    "prefix": "statLT",
    "scope": "json",
    "body": [
    "{",
    "\t\"type\": \"lessThan\",",
    "\t\"components\": {}",
    "}"
    ]
    },
    "statToNum": {
    "prefix": "statToNum",
    "scope": "json",
    "body": [
    "{",
    "\t\"type\": \"toNum\",",
    "\t\"components\": {}",
    "}"
    ]
    },
    "statToSignedString": {
    "prefix": "statToSignedString",
    "scope": "json",
    "body": [
    "{",
    "\t\"type\": \"toSignedString\",",
    "\t\"components\": {}",
    "}"
    ]
    },
    "statToLowerCase": {
    "prefix": "statToLowerCase",
    "scope": "json",
    "body": [
    "{",
    "\t\"type\": \"toLowerCase\",",
    "\t\"components\": {}",
    "}"
    ]
    },
    "statToUpperCase": {
    "prefix": "statToUpperCase",
    "scope": "json",
    "body": [
    "{",
    "\t\"type\": \"toUpperCase\",",
    "\t\"components\": {}",
    "}"
    ]
    },
    "statReplace": {
    "prefix": "statreplace",
    "body": [
    "{",
    "\t\"type\": \"replace\",",
    "\t\"string\": {},",
    "\t\"occurrence\": {},",
    "\t\"replace\": {}",
    "}"
    ]
    },
    "statRoll": {
    "prefix": "statRoll",
    "body": [
    "{",
    "\t\"type\": \"roll\",",
    "\t\"formula\": {}",
    "}"
    ]
    },
    "statRollable": {
    "prefix": "statRollable",
    "body": [
    "{",
    "\t\"type\": \"rollable\",",
    "\t\"formulas\": {},",
    "\t\"text\": {}",
    "}"
    ]
    },
    "statList": {
    "prefix": "statlist",
    "scope": "json",
    "body": [
    "{",
    "\t\"type\": \"list\",",
    "\t\"components\": []",
    "}"
    ]
    },
    "statFlatAppend": {
    "prefix": "statFlatAppend",
    "scope": "json",
    "body": [
    "{",
    "\t\"type\": \"flatAppend\",",
    "\t\"components\": []",
    "}"
    ]
    },
    "statConstant": {
    "prefix": "statconst",
    "scope": "json",
    "body": [
    "{",
    "\t\"type\": \"constant\",",
    "\t\"value\": ,",
    "\t\"value_type\": ",
    "}"
    ]
    },
    "statWhen": {
    "prefix": "statwhen",
    "scope": "json",
    "body": [
    "{",
    "\t\"type\": \"when\",",
    "\t\"clauses\": [",
    "\t\t{",
    "\t\t\t\"condition\": {},",
    "\t\t\t\"components\": {}",
    "\t\t},",
    "\t\t{",
    "\t\t\t\"condition\": {",
    "\t\t\t\t\"type\": \"constant\",",
    "\t\t\t\t\"value\": true,",
    "\t\t\t\t\"value_type\": \"bool\"",
    "\t\t\t},",
    "\t\t\t\"components\": {}",
    "\t\t}",
    "\t]",
    "},"
    ]
    },
    "whenClause": {
    "prefix": "wclause",
    "scope": "json",
    "body": [
    "{",
    "\t\"condition\": {},",
    "\t\"components\": {}",
    "},"
    ]
    },
    "baseStat": {
    "prefix": "base",
    "body": [
    "{",
    "\t\"id\": \"\",",
    "\t\"name\": \"\",",
    "\t\"type\": \"base\",",
    "\t\"value_type\": \"\",",
    "\t\"default_value\": ",
    "},"
    ]
    },
    "calcStat": {
    "prefix": "calc",
    "body": [
    "{",
    "\t\"id\": \"\",",
    "\t\"name\": \"\",",
    "\t\"type\": \"calculated\",",
    "\t\"value_type\": \"\",",
    "\t\"components\": {}",
    "},"
    ]
    },
    "textView": {
    "prefix": "vtext",
    "body": [
    "{",
    "\t\"id\": \"\",",
    "\t\"type\": \"text\",",
    "\t\"text\": ?{},",
    "\t\"edit_stat\": ?,",
    "\t\"style\": \"text|title|section|subhead|headline|caption\",",
    "\t\"max_length\": ?,",
    "\t\"signed\": ?false,",
    "\t\"hint\": ?{},",
    "\t\"label\": ?{},",
    "\t\"bold\": ?,",
    "\t\"color\": ?{},",
    "\t\"resource_id\": ?,",
    "\t\"resource_instance_ids_stat\": ?,",
    "\t\"resource_display_stat\": ?",
    "},"
    ]
    },
    "diffTextView": {
    "prefix": "vdiffText",
    "body": [
    "{",
    "\t\"id\": \"\",",
    "\t\"type\": \"diffText\",",
    "\t\"original_text\": {},",
    "\t\"new_text\": {},",
    "\t\"added_color\": ?\"\",",
    "\t\"deleted_color\": ?\"\"",
    "},"
    ]
    },
    "listView": {
    "prefix": "vlist",
    "body": [
    "{",
    "\t\"id\": \"\",",
    "\t\"type\": \"list\",",
    "\t\"padding\": ?,",
    "\t\"alignment\": ?\"start|center|end\",",
    "\t\"subviews\": []",
    "},"
    ]
    },
    "statView": {
    "prefix": "vStat",
    "body": [
    "{",
    "\t\"id\": \"\",",
    "\t\"type\": \"stat\",",
    "\t\"stat\": \"\",",
    "\t\"edit_stat\": ?\"\",",
    "\t\"change_amount\": ?1,",
    "\t\"min_value_allowed\": ?0,",
    "\t\"max_value_allowed\": ?999999,",
    "\t\"calculated_min_value_allowed\": ?{},",
    "\t\"calculated_max_value_allowed\": ?{},",
    "\t\"on_tap_event\": ?{},",
    "\t\"on_long_tap_event\": ?{},",
    "\t\"bottom_sheet_view\": ?{},",
    "\t\"bottom_sheet_in_edit_mode\": ?false,",
    "\t\"sheet_title\": {}",
    "\t\"stat_color\": ?{}",
    "\t\"name_color\": ?{}",
    "\t\"style\": ?\"block|list\"",
    "\t\"always_show_sign\": ?false",
    "}"
    ]
    },
    "resourceSectionView": {
    "prefix": "vResSection",
    "body": [
    "{",
    "\t\"id\": \"\",",
    "\t\"type\": \"resourceSection\",",
    "\t\"resource_stat\": \"\",",
    "\t\"resource_id\": \"\",",
    "\t\"header_view\": ?{},",
    "\t\"resource_set_stat\": ?\"\",",
    "\t\"contentItemMinWidth\": ?1,",
    "\t\"alignment\": ?\"start|center|end\",",
    "\t\"read_only\": ?false,",
    "\t\"tap_displays_resource\": ?true",
    "},"
    ]
    },
    "sectionView": {
    "prefix": "vSection",
    "body": [
    "{",
    "\t\"id\": \"\",",
    "\t\"type\": \"section\",",
    "\t\"header\": [],",
    "\t\"content\": [],",
    "\t\"show_divider\": false,",
    "\t\"content_item_min_width\": ?100,",
    "\t\"collapsible\": ?false,",
    "\t\"start_collapsed\": ?false,",
    "\t\"always_show_collapse_icon\": ?false,",
    "\t\"alignment\": ?\"start|center|end\"",
    "}"
    ]
    },
    "avatarSectionView": {
    "prefix": "vAvatarSection",
    "body": [
    "{",
    "\t\"id\": \"\",",
    "\t\"type\": \"avatarSection\",",
    "\t\"bottom_left_stack\": ?[],",
    "\t\"bottom_right_stack\": ?[],",
    "\t\"center_content\": [],",
    "\t\"bottom_left_button_title\": ?{},",
    "\t\"bottom_left_button_event\": ?{},",
    "\t\"bottom_right_button_title\": ?{},",
    "\t\"bottom_right_button_event\": ?{}",
    "}"
    ]
    },
    "avatarView": {
    "prefix": "vAvatar",
    "body": [
    "{",
    "\t\"id\": \"\",",
    "\t\"type\": \"avatar\",",
    "\t\"photo_stat\": \"\",",
    "\t\"name_stat\": \"\",",
    "\t\"size\": \"massive|medium|small\",",
    "\t\"interactable\": ?false",
    "}"
    ]
    },
    "selectView": {
    "prefix": "vselect",
    "body": [
    "{",
    "\t\"id\": \"\",",
    "\t\"type\": \"select\",",
    "\t\"stat\": \"\",",
    "\t\"options\": ?\"\",",
    "\t\"allowed_options\": ?{},",
    "\t\"resource_options\": ?{},",
    "\t\"max_selection_amount\": ?{},",
    "\t\"options_display_stat\": ?\"\",",
    "\t\"multi_select\": ?false",
    "\t\"style\": ?\"dropdown|list\"",
    "},"
    ]
    },
    "checkboxView": {
    "prefix": "vcheck",
    "body": [
    "{",
    "\t\"id\": \"\",",
    "\t\"type\": \"checkbox\",",
    "\t\"stat\": \"\",",
    "\t\"label\": ?,",
    "\t\"label_alignment\": ?\"left|right\",",
    "\t\"style\": ?\"checkbox|switcher\",",
    "\t\"should_auto_save\": ?false",
    "},"
    ]
    },
    "compositeView": {
    "prefix": "vcomp",
    "body": [
    "{",
    "\t\"id\": \"\",",
    "\t\"type\": \"composite\",",
    "\t\"row_only\": ?true,",
    "\t\"alignment\": ?\"start|center|end\",",
    "\t\"subviews\": []",
    "},"
    ]
    },
    "resourceArrayView": {
    "prefix": "vresarr",
    "body": [
    "{",
    "\t\"id\": \"\",",
    "\t\"type\": \"resourceArray\",",
    "\t\"resource_id\": \"\",",
    "\t\"stat\": \"\",",
    "\t\"view_type\": \"list|display|edit\",",
    "\t\"auto_add_first\": ?true|false,",
    "\t\"left_view\": ?,",
    "\t\"pop_up_type\": ?\"bottomSheet|window\",",
    "\t\"pop_up_button_text\": ?{},",
    "\t\"hide_line\": ?false",
    "},"
    ]
    },
    "iconView": {
    "prefix": "vIcon",
    "body": [
    "{",
    "\t\"id\": \"\",",
    "\t\"type\": \"icon\",",
    "\t\"name\": \"\",",
    "\t\"size\": \"massive|xLarge|large|medium|small|xSmall\",",
    "\t\"color\": ?\"\"",
    "},"
    ]
    },
    "menuButtonView": {
    "prefix": "vMenuButton",
    "body": [
    "{",
    "\t\"id\": \"\",",
    "\t\"type\": \"menuButton\",",
    "\t\"icon_name\": \"\",",
    "\t\"icon_size\": \"massive|xLarge|large|medium|small|xSmall\",",
    "\t\"icon_color\": ?\"\",",
    "\t\"options\": \"\",",
    "\t\"allowedoptions\": ?{}",
    "},"
    ]
    },
    "resourceView": {
    "prefix": "vreso",
    "body": [
    "{",
    "\t\"id\": \"\",",
    "\t\"type\": \"resource\",",
    "\t\"view_type\": \"display|edit|list\",",
    "\t\"resource_id\": \"\",",
    "\t\"stat\": \"\",",
    "\t\"create_if_empty\": ?false",
    "},"
    ]
    },
    "popUpButtonView": {
    "prefix": "vPopUp",
    "body": [
    "{",
    "\t\"id\": \"\",",
    "\t\"type\": \"popUpButton\",",
    "\t\"text\": ?{},",
    "\t\"icon\": ?{},",
    "\t\"pop_up_view\": {},",
    "\t\"pop_up_type\": \"bottomSheet|alert\",",
    "\t\"dismissible\": ?true|false,",
    "\t\"pop_up_title\": {},",
    "\t\"display_cancel_button\": ?true,",
    "\t\"cancel_text\": ?{},",
    "\t\"display_save_button\": ?false,",
    "\t\"save_text\": ?{},",
    "\t\"should_persist_on_save\": ?false,",
    "\t\"extra_buttons\": ?[{\"text\": {}, \"event\": {}}],",
    "\t\"in_edit_mode\": ?false",
    "},"
    ]
    },
    "selectResourcesView": {
    "prefix": "vselres",
    "body": [
    "{",
    "\t\"id\": \"\",",
    "\t\"type\": \"selectResources\",",
    "\t\"stat\": \"\",",
    "\t\"resource_id\": \"\",",
    "\t\"view_type\": ?\"list|icon\",",
    "\t\"should_append\": ?false,",
    "\t\"should_save_on_selection\": ?false,",
    "\t\"create_in_place\": ?false,",
    "\t\"creation_pop_up_type\": ?\"bottomSheet|window\",",
    "\t\"filters\": ?{}",
    "}"
    ]
    },
    "tickerView": {
    "prefix": "vtick",
    "body": [
    "{",
    "\t\"id\": \"\",",
    "\t\"type\": \"ticker\",",
    "\t\"stat\": \"\",",
    "\t\"style\": ?\"text|title|section|subhead|headline|caption\",",
    "\t\"button_location\": ?\"left|right|sideToSide\",",
    "\t\"button_look\": ?\"plusMinus|upDown\",",
    "\t\"should_auto_save\": ?false,",
    "\t\"change_amount\": ?1,",
    "\t\"min_value_allowed\": ?-99999,",
    "\t\"max_value_allowed\": ?99999,",
    "\t\"label\": ?{},",
    "\t\"label_alignment\": ?\"left|right\",",
    "\t\"edit_bottom_sheet_title\": ?{}",
    "},"
    ]
    },
    "chipView": {
    "prefix": "vChip",
    "body": [
    "{",
    "\t\"id\": \"\",",
    "\t\"type\": \"chip\",",
    "\t\"text\": {},",
    "\t\"on_tap_event\": ?{},",
    "\t\"background_color\": ?\"\",",
    "\t\"border_color\": ?\"\",",
    "\t\"style\": ?\"compact|fullSize\",",
    "\t\"resource_id\": ?\"\",",
    "\t\"resource_instance_ids_stat\": ?\"\",",
    "\t\"resource_display_stat\": ?\"\"",
    "},"
    ]
    },
    "buttonView": {
    "prefix": "vbutt",
    "body": [
    "{",
    "\t\"id\": \"\",",
    "\t\"type\": \"button\",",
    "\t\"text\": {},",
    "\t\"event\": ?{},",
    "\t\"event_names\": ?{}",
    "},"
    ]
    },
    "collapsibleView": {
    "prefix": "vCollapsible",
    "body": [
    "{",
    "\t\"id\": \"\",",
    "\t\"type\": \"collapsible\",",
    "\t\"title\": {},",
    "\t\"subtitle\": ?{},",
    "\t\"start_expanded\": ?false,",
    "\t\"subviews\": []",
    "}"
    ]
    },
    "viewPagerView": {
    "prefix": "vviewpager",
    "body": [
    "{",
    "\t\"id\": \"\",",
    "\t\"type\": \"viewPager\",",
    "\t\"names\": {returns list[string]},",
    "\t\"color\": ?{returns string},",
    "\t\"subviews\": []",
    "}"
    ]
    },
    "listItemView": {
    "prefix": "vlistItem",
    "body": [
    "{",
    "\t\"id\": \"\",",
    "\t\"type\": \"listItem\",",
    "\t\"title\": {},",
    "\t\"title_color\": {},",
    "\t\"subtitle\": {},",
    "\t\"subtitle_color\": {},",
    "\t\"first_line_extra\": {},",
    "\t\"first_line_extra_color\": {},",
    "\t\"second_line_extra\": {},",
    "\t\"second_line_extra_color\": {},",
    "\t\"chips\": {}",
    "}"
    ]
    },
    "spacerView": {
    "prefix": "vSpacer",
    "body": [
    "{",
    "\t\"id\": \"\",",
    "\t\"type\": \"spacer\"",
    "},"
    ]
    },
    "dividerView": {
    "prefix": "vDivider",
    "body": [
    "{",
    "\t\"id\": \"\",",
    "\t\"type\": \"divider\",",
    "\t\"vertical\": ?false",
    "},"
    ]
    },
    "addToStatEffect": {
    "prefix": "eAddToStat",
    "body": [
    "{",
    "\t\"type\": \"addToStat\",",
    "\t\"stat\": ?\"\",",
    "\t\"meta_stat\": ?{},",
    "\t\"value\": {},",
    "\t\"aggregation_type\": ?\"min|max|sum|set|none\",",
    "\t\"calculated_aggregation_type\": ?{}",
    "}"
    ]
    },
    "setStatEffect": {
    "prefix": "eSetStat",
    "body": [
    "{",
    "\t\"type\": \"setStat\",",
    "\t\"stat\": ?\"\",",
    "\t\"meta_stat\": ?{},",
    "\t\"new_value\": {},",
    "\t\"aggregation_type\": ?\"min|max|sum|set|none\",",
    "\t\"calculated_aggregation_type\": ?{}",
    "}"
    ]
    },
    "whenEffect": {
    "prefix": "eWhen",
    "scope": "json",
    "body": [
    "{",
    "\t\"type\": \"when\",",
    "\t\"clauses\": [",
    "\t\t{",
    "\t\t\t\"condition\": {},",
    "\t\t\t\"effect\": {}",
    "\t\t},",
    "\t\t{",
    "\t\t\t\"condition\": {},",
    "\t\t\t\"effect\": {}",
    "\t\t}",
    "\t]",
    "}"
    ]
    },
    "eWhenClause": {
    "prefix": "ewclause",
    "scope": "json",
    "body": [
    "{",
    "\t\"condition\": {},",
    "\t\"effect\": {}",
    "},"
    ]
    },
    "delayedEffect": {
    "prefix": "eDelayed",
    "scope": "json",
    "body": [
    "{",
    "\t\"type\": \"delayed\",",
    "\t\"millis\": 100,",
    "\t\"effect\": {}",
    "}"
    ]
    },
    "dismissResourceEffect": {
    "prefix": "eDismissResource",
    "scope": "json",
    "body": [
    "{",
    "\t\"type\": \"dismissResource\",",
    "\t\"id\": ?{}",
    "}"
    ]
    },
    "fireEventEffect": {
    "prefix": "efire",
    "body": [
    "{",
    "\t\"type\": \"fireEvent\",",
    "\t\"event\": ?{},",
    "\t\"event_names\": ?{return list}",
    "}"
    ]
    },
    "forwardEventEffect": {
    "prefix": "eforward",
    "body": [
    "{",
    "\t\"type\": \"forwardEvent\",",
    "\t\"new_event_names\": ?{return list}",
    "}"
    ]
    },
    "sequenceEffect": {
    "prefix": "eseq",
    "body": [
    "{",
    "\t\"type\": \"sequence\",",
    "\t\"effects\": []",
    "}"
    ]
    },
    "forEachEffect": {
    "prefix": "eforEach",
    "body": [
    "{",
    "\t\"type\": \"forEach\",",
    "\t\"components\": {},",
    "\t\"apply\": {},",
    "\t\"for_each_value_key\": ?\"\"",
    "}"
    ]
    },
    "showResourceEffect": {
    "prefix": "eShowResource",
    "body": [
    "{",
    "\t\"type\": \"showResource\",",
    "\t\"resource\": {}",
    "}"
    ]
    },
    "showMessageEffect": {
    "prefix": "eShowMessage",
    "body": [
    "{",
    "\t\"type\": \"showMessage\",",
    "\t\"message\": {},",
    "\t\"message_type\": ?\"info|error|success\"",
    "}"
    ]
    },
    "showPopUpEffect": {
    "prefix": "eshowPopUp",
    "body": [
    "{",
    "\t\"type\": \"showPopUp\",",
    "\t\"pop_up_view\": {},",
    "\t\"pop_up_type\": \"bottomSheet|alert\",",
    "\t\"dismissible\": ?true|false,",
    "\t\"pop_up_title\": {},",
    "\t\"display_cancel_button\": ?true,",
    "\t\"cancel_text\": ?{},",
    "\t\"display_save_button\": ?false,",
    "\t\"save_text\": ?{},",
    "\t\"should_persist_on_save\": ?false,",
    "\t\"extra_buttons\": ?[{\"text\": {}, \"event\": {}}],",
    "\t\"in_edit_mode\": ?false",
    "}"
    ]
    },
    "rollEffect": {
    "prefix": "eRoll",
    "body": [
    "{",
    "\t\"type\": \"roll\",",
    "\t\"rolls\": {},",
    "\t\"on_result_effect\": ?{}",
    "}"
    ]
    },
    "mechanic": {
    "prefix": "mech",
    "body": [
    "{",
    "\t\"id\": \"\",",
    "\t\"name\": ?\"\",",
    "\t\"description\": ?\"\",",
    "\t\"event_names\": ?\"\",",
    "\t\"calculated_event_names\": ?{returns list},",
    "\t\"revert_event_names\": ?\"\",",
    "\t\"calculated_revert_event_names\": ?{returns list},",
    "\t\"revert_on_remove\": ?{returns bool},",
    "\t\"effects\": {}",
    "}"
    ]
    },
    "searchFilters": {
    "prefix": "searchFilter",
    "body": [
    "{",
    "\t\"name\": \"\",",
    "\t\"stat\": \"\",",
    "\t\"true_name\": ?\"\",",
    "\t\"false_name\": ?\"\",",
    "\t\"options\": ?\"\",",
    "\t\"options_display_stat\": ?\"\"",
    "}"
    ]
    },
    }
  7. You can utilize the Snippets: Configure Snippets option and choose the rpg.json.json.code-snippets option whenever you want to update / enhance these if needed.

The system

You will be mainly working with the system definition called system.rpg.json.
There are now two supported workflows:

  • Monolithic (original): keep everything in a single system.rpg.json file at the system root.
  • Split (new, optional): place a system/ folder inside your system and split large sections into smaller files and folders. The Dev Tool will compose these parts into a single system at build time.

Both approaches are supported. If both exist, the Dev Tool prefers the split folder.

The system file structure

You can host multiple systems side-by-side under a top-level systems folder.

Monolithic (legacy)

systems
|—system1
| |—system.rpg.json
| |—resources/ # legacy instances folder (see below)
|—system2
|—...
systems
|—system1
| |—system/
| | |—system.rpg.json # optional base (can be empty/minimal)
| | |—character_stats.rpg.json # list
| | |—mechanics/ # multiple files (each a mechanic object)
| | |—enumerated_types/ # multiple files (each a type object)
| | |—character_creation_flow/ # ordered files
| | |—character_sheet_sections/ # ordered files
| | |—resources/ # resource DEFINITIONS (schemas, views, stats, mechanics)
| | | |—armor/
| | | | |—index.rpg.json
| | | | |—stats.rpg.json # array of stats
| | | | |—mechanics/ # files → mechanic objects
| | | | |—display_view.rpg.json
| | | | |—edit_view.rpg.json
| | | | |—list_view.rpg.json
| | | | |—search_item_view.rpg.json
| | | |—... (other resources)
| |
| |—resource_instances/ # PRE-MADE RESOURCE INSTANCES (exported from the app)
| | |—armor_armor_of_invulnerability.rpg.json
| | |—...
|
|—system2
|—...

Notes

  • system/ holds definitions (what a resource is: stats, views, mechanics, etc.).
  • resource_instances/ holds instances (your prebuilt content).
  • For backwards compatibility, if resource_instances/ is missing the Dev Tool will fall back to resources/ at the system root (legacy name for instances).

Split mode: how files and folders map to JSON keys

When you use the system/ folder, the Dev Tool composes the final JSON by merging keys from files and folders.

1) Top-level files in system/

A file named <key>.rpg.json (or .json) sets/merges the value of key in the final system.
Examples:

  • character_stats.rpg.json → contributes to system.character_stats
  • mechanics.rpg.json → contributes to system.mechanics (if you prefer a single file instead of a folder)

Tip: system.rpg.json inside system/ is optional and can hold any keys not provided by other files/folders (e.g., id, name, abbreviation, version, min_app_version). If a key appears in both, they are merged according to the rules below.

2) Top-level folders in system/

A folder named <key>/ becomes the value of key built from its contents.

  • If index.rpg.json (or index.json) is an ARRAY: the folder is treated as an array that begins with the items in index.*. Child files/folders are appended (child arrays are concatenated).
  • If there is NO index.* array and ALL immediate children are JSON files (no subfolders): the folder is treated as an array made by concatenating each file’s content (in lexicographic filename order).
  • Otherwise: the folder is treated as an object.
    • Each file is wrapped under its base filename (without extension).
      Example: display_view.rpg.json"display_view": { ... }
    • Each subfolder is wrapped under its folder name.
      Example: mechanics/"mechanics": [ ... ] or { ... } depending on its own internal rules (see below).

3) Nested folders (recursion)

The same rules apply recursively inside subfolders (resources/armor/…, etc.).


Merge rules (how multiple parts combine)

  • Objects (maps): deep-merged by key. When two files provide the same key, values are merged recursively.
  • Arrays (lists):
    • By default, arrays are concatenated (stable: earlier files first).
    • For specific keys, items are merged by id (keeping first-seen order):
      • resources (i.e., resource definitions under system/resources)
      • stats (e.g., resource stats lists)
        If an array item is an object with an id, items with the same id are deep-merged instead of duplicated.

Ordering-sensitive arrays:

  • character_creation_flow and character_sheet_sections keep the lexicographic file order when treated as arrays. A good way to enforce the order you want is by prefixing file names with xx_, where xx is the number in the order (e.g: 00_status_section, 02_abilities_section, etc)

Resource definitions vs resource instances

  • Resource definitions live under system/resources/<resource_id>/… and describe what a resource is (its stats, mechanics, and views like display_view, edit_view, list_view, search_item_view).

    • Example layout for armor:
      system/resources/armor/
      index.rpg.json # resource metadata (id, name, plural, color, filters, etc.)
      stats.rpg.json # array of stat definitions
      mechanics/ # each file is one mechanic object
      display_view.rpg.json
      edit_view.rpg.json
      list_view.rpg.json
      search_item_view.rpg.json
  • Resource instances live under resource_instances/ at the system root (or legacy resources/ if you haven’t migrated the folder name). These are the pre-made entries you export from the app (e.g., specific armors, spells, monsters). The Dev Tool bundles them into resources.rpg.gzip + an index automatically.

Filename note (instances): the Dev Tool normalizes instance filenames and ensures there’s exactly one leading <type>_ prefix. If an instance name already begins with <type>_, one occurrence is removed before the tool prefixes again, keeping filenames stable across rebuilds.


Where do I get the resourceX.rpg.json files?

Once you create your system and run it in your copy of the RPG Companion App, you can use the app to create pre-made resources. Use the share button on the top-left of the resources screen (orange section of the app) to export a gzip with your resources. Unzip it and copy the resulting files into the system’s resource_instances/ folder.

If you’re keeping a legacy system, you can still use the old resources/ folder name for instances; the Dev Tool will accept it as a fallback. New systems should prefer resource_instances/.

How do I test the system?

If you have enabled "Developer Mode" in the app, you can use the RPG Companion App dev tool to get your system to your app and try it out. Just select the input folder to be your systems folder, click on "Start server" and wait for the system to build, then tap on the "Update systems" button in either the app's home page or the app's settings.

With split systems, the Dev Tool automatically composes system/ into a single system.rpg at build time and bundles resource_instances/ (or legacy resources/) into the resources index.

I see pop-ups with problems in my system.

A lot of the information in the pop up will help you diagnose what the issue is. However, if you still need help and are feeling lost, please feel free to contact Vlad (the app developer) if you need any help or run into issues. He's always happy to help. You can reach him at feedback@blastervla.com.

What next?

Once you're done working with your system, you can host it by following the instructions in Hosting a system

Contributing your system

If you’d like to share your system with the community:

  1. Ensure it builds locally and loads in the app (using the Dev Tool).
  2. Follow the folder structure in this guide.
  3. Open a Pull Request to the Open Systems Repository: github.com/blastervla/rpg-companion-app-systems

Your contributions will be shared under CC BY-NC-SA 4.0 so everyone can improve and remix non-commercially.