Skip to content

Generator Syntax

Steve Christensen edited this page Jul 4, 2017 · 15 revisions

Overview

Most generators in the app are completely driven by data files rather than code.

The generator files are YaML syntax, which should be easy for humans to edit with most text editors. Adventuresmith reads these files using a UTF-8 charset.

A couple nice YaML references (note: the first two include syntax related to other systems that aren't applicable):

The template language used is Mustache, the implementation is jmustache. You can do strange and wonderful things with templates, and likely break the app.

I learn best by following examples. So let's dive in!

Adventuresmith-cli Installation

To create & run generators on your desktop, install adventuresmith-cli. Here is its wiki page https://github.com/stevesea/Adventuresmith/wiki/adventuresmith-cli

Overly Simple Example

---
templates:
- "{{male_name}}"
- "{{female_name}}"

tables:
  male_name:
  - Bob
  - Roger
  female_name:
  - Sally
  - Margaret

What does the above mean?

  • templates is a Rangemap (more on that later) containing one or more templates that'll be evaluated when the generator is run. In this example, we're as likely to select a male name as a female name.
  • tables is a Map of Rangemaps. In this example, we create two tables 'male_name' and 'female_name'.

When the generator is run, a single item from 'templates' is chosen. All entries in 'tables' are added to the generator context and the template is evaluated. Any keys in the mustache template will be looked up in the context. Evaluating a key which is a Rangemap results in a single item from the Rangemap being selected.

I save the above to a file 'overly_simple_example.yml'. I run the generator ten times by adding -i 10 to the adventuresmith-cli commandline:

> .\bin\adventuresmith-cli run -i 10 overly_simple_example.yml 
INFO  Running generator: tmp.yml
Sally
Margaret
Bob
Roger
Margaret
Roger
Bob
Margaret
Roger
Roger

Generator Syntax

Rangemaps

In terms of YaML syntax, Adventuresmith Rangemaps are just lists of Strings, and each string can be prefixed with an IntRange. Overlapping ranges are not permitted and will be flagged as errors. Holes in the accumulated set of ranges are also not permitted, and will be flagged as errors

example 1:

the following two rangemaps are equivalent

    tables:
       heritage_ex1:
       - 1..3, human    # ranges should be of form X..Y, where X and Y are integers, and X<=Y
       - 4, halfling    # ranges can be a single entry. this becomes range X..X
       - dwarf          # or none.  This becomes X=last line's max+1 ... X..X
       - 6, elf

       heritage_ex2:
       - human
       - human
       - human
       - halfling
       - dwarf
       - elf

example 2 - string spanning multiple lines

YaML makes it easy to create multi-line strings, here's how that looks as a value within a rangemap:

    reveal:
    - |
      1..3,
      The workings of a ritual or spell
      <br/>
      <br/><strong><small>Magical Effect:</small></strong> {{magic}}
      <br/><strong><small>Limit:</small></strong> {{limit}}
    - "4, An artifice or technique lost to antiquity"
    - "5, The workings or secrets of a mysterious object"
    - "6, The history of a people <small>({{people}})</small>"
    - "7, The weakness/motive/origins/lair of an established threat"
    - "8, The hazards/secrets/history/location of a mysterious place"
    - "9, The true nature of an origin (the one you first rolled, or another)"
    - "10, The location/powers/history/secrets of a major arcana"
    - "11, The truth about a person/event/creature of legend"
    - "12, A resource that might be exploited"

Generator File Schema

templates (required)

Templates is a Rangemap, and is the only required element of an Adventuresmith generator file. In the above example, if I wanted female names to happen more frequently, I could do something like:

templates:
- "1, {{male_name}}"
- "2..4, {{female_name}}"

tables

tables is a Map of Rangemaps. Each key within tables becomes a key within the generator context. To pick a single item from a rangemap, simply use the double-curly-brace Mustache syntax {{key}}

tables:
  male_name:
  - Bob
  - Roger
  female_name:
  - Sally
  - Margaret

nested_tables

nested_tables is a Map of Map of Rangemaps. It's mostly handy for organization of tables, and providing a namespace for avoiding naming collisions when importing other generator files. nested_tables Rangemaps can be referenced with syntax like {{key.subkey}}

Here's the overly-simple-example re-written to use a nested table:

---
templates:
- "{{name.male}}"
- "{{name.female}}"

nested_tables:
  name:
    male:
    - Bob
    - Roger
    female:
    - Sally
    - Margaret

imports

your generator file can include other generator files by using an 'imports' element. Imports is a list of Strings.

# a list of other input data files that should be loaded into the context.
# they _must_ be located next to the current file
imports:
- names

The context passed to the template processor is the current file, plus any tables defined in the imported tables. Tables are applied to the context in reverse order of entry in the imports list, and then this file's tables.

Files are not merged. If there are any naming collisions, an error will be thrown. Names must be unique across all entries in these files -- definitions, tables, nested_tables must not include items with the same name.

definitions

Sometimes your template just needs data, and you don't want to randomly select it.

Examples: labels you repeat a lot within the output, shared sub-templates, etc

For those cases, you can use definitions. the definitions element is a Map of anything (maps, lists, strings, etc)

Here's an example from The Perilous Wilds

definitions:
  detail_config:
    labels:
      ability: Ability
      activity: Activity
      adjective: Adjective

    select_and_label:
      abilities: <strong><small>{{detail_config.labels.ability}}:</small></strong> {{details.abilities}}
      activities: <strong><small>{{detail_config.labels.activity}}:</small></strong> {{details.activities}}
      adjectives: <strong><small>{{detail_config.labels.adjective}}:</small></strong> {{details.adjectives}}

nested_tables:
  details:
    abilities:
    - 1, bless/curse
    - 2, entangle/trap/snare
    - 3, poison/disease
    - 4, paralyze/petrify
    - 5, mimic/camouflage
    - 6, seduce/hypnotize
    - 7, dissolve/disintegrate
    - "8, {{details.magic_types}}"
    - 9, drain life/magic
    - "10, immunity: {{details.elements}}"
    - 11, read/control minds
    - "12, {{>pickN: 2 details.abilities}}"

In templates that include this file, they can use the key {{detail_config.labels.ability}}

Recursion

The template is repeatedly processed a fixed number of times, until all {{}} keys are evaluated.

Other generators include those Perilous Wilds details definitions above, and use them inside a template like:

{{detail_config.select_and_label.abilities}}

the first time that's evaluated, that becomes <strong><small>{{detail_config.labels.ability}}:</small></strong> {{details.abilities}}

the second time that's evaluated, it might become <strong><small>Ability:</small></strong> mimic/camouflage

Translations

Similar to a Java ResourceBundle, generator files are looked up based on the requested locale and the current default locale. The closest match to the request will be used.

For example, if fr_FR is the desired locale (French/France) and the default Locale is en_US (English/United States), and adventuresmith is trying to find a file named 'spells.yml', it will will look for files in the following order:

  • spells.fr_FR.yml
  • spells.fr.yml
  • spells.en_US.yml
  • spells.en.yml
  • spells.yml

Adventuresmith/Mustache Partials Reference

Adventuresmith generators use some non-standard Mustache syntax and behavior, but it felt cleaner and easier to read than other alternatives I explored. One oddity you'll run into right away is how I use Mustache partials for custom functions (e.g. {{>roll: 3d6+2}} or {{>pickN: 3 forms}})

{{>roll: <dicestr>}} and {{>rollN: <n> <dicestr> <delim>}}

This an RPG generator, we need to roll dice.

      {{>roll: 2d6+1}}             results in 2d6+1 roll.
      {{>roll: 2*(1d4+2d20)}}      it can get silly
      {{>rollN: 2 2*(1d4+2d20)}}   be silly twice, separated by a comma

{{>pick: <dice> <key>}}

result is using given dice roll to select 1 item from the given context key

example: {{>pick: 1d12 weapon}}

The perilous wilds generators frequently use this to select a sub-range of a specific table.

from the PW Discovery generator, it uses this syntax to be selective about what sort of creature may have built a dwelling (human or humanoid, and never beast/monster)

    - |
      4,
      <h3>Structure - Dwelling</h3>
      <strong><small>Built by:</small></strong> {{>pick: 1d4+4 creature.creature_no_tags}}
      <br/>
      <br/>{{structure.dwelling}}

{{>pickN: <dice/#/variable> <key> <delim>}}

result is picking # of unique items from key, and separating them with the given delimeter

     {{>pickN: 2 weapon}}
              picks 2 weapons, outputs each comma-separated

     {{>pickN: 1d2+2 weapon}}
              picks 2-4 weapons, outputs each comma-separated

     {{>pickN: 1d2+2 weapon ;&nbsp;}}
              same as above, but separated with semi-colon (followed by a non-breaking space)

Advanced

%[[<key>]]% syntax

Sometimes you need want to process a template after getting the processed results of a different template. As discussed in the recursion section above, the generator runs the template some # of times (or until all '{{' are gone). There's actually another processing step -- replacing any '%[[]]%' pairs with '{{}}', then re-running the template processor.

Example 1: titlecase

In the Holmesian Random Names generator,

---
templates:
- "{{holmesian_names.template}}"

nested_tables:
  holmesian_names:
    template:
    - "1..5, %[[>titleCase: {{holmesian_names.pick_num_syllables}}]]% {{holmesian_names.pick_titles}}"
    - "6..8, %[[>titleCase: {{holmesian_names.pick_num_syllables}}]]% %[[>titleCase: {{holmesian_names.pick_num_syllables}}]]% {{holmesian_names.pick_titles}}"
    pick_num_syllables:
    - 1-10, {{holmesian_names.syllables}}
    - 11-70, {{holmesian_names.syllables}}{{holmesian_names.syllables}}
    - 71-90, {{holmesian_names.syllables}}{{holmesian_names.syllables}}{{holmesian_names.syllables}}
    - 91-100, {{holmesian_names.syllables}}{{holmesian_names.syllables}}{{holmesian_names.syllables}}{{holmesian_names.syllables}}
    pick_titles:
    - 1..2,
    - "{{holmesian_names.titles}}"
    syllables:
    - A
    - Ael
    - Af
    - Ak
    - Al
    - Am
    - An
    - Ar
    - Baf
    - Bar

step-by-step, the template might be processed like:

  • {{holmesian_names.template}}
  • %[[>titleCase: {{holmesian_names.pick_num_syllables}}]]% {{holmesian_names.pick_titles}}
  • %[[>titleCase: {{holmesian_names.syllables}}{{holmesian_names.syllables}}{{holmesian_names.syllables}}]]% {{holmesian_names.titles}}
  • %[[>titleCase: BerdVaKa]]% the Daring
  • {{>titleCase: BerdVaKa}} the Daring
  • Berdvaka the Daring

by using the %[[ ]]% syntax, we can create a portion of the template that's processed last, after all other processing has been done.

Example 2: Input configuration

In the Sharp Swords & Sinister Spells 'Spell Mishap' generator, the template looks like:

---
templates:
- |
  %[[>pick: {{input.chaosRisk}} spell_mishap]]%
  <br/>
  <br/><small><em>[chaos dR: {{input.chaosRisk}}]</em></small>

tables:
  spell_mishap:
  - 1, <strong>Step down dR</strong><br/><br/><strong>Power surge.</strong> No magic happens, but you regain the HP lost from the spell.
  - 2, <strong>Step down dR</strong><br/><br/><strong>Mistake.</strong> The effect is reversed or dramatically altered.
  - 3, <strong>Step down dR</strong><br/><br/><strong>Mutation.</strong> Your magic works, but leaves you with a scar, deformity, or oddity.
  - 4, <strong>Erasure.</strong> The magic works but you forget the spell until you have a full night's sleep.
  - 5, <strong>Drain.</strong> Lose points in a random stat equal to the initial HP cost. Recover 1 point per day.
  - 6, <strong>Pyrotechnics.</strong> Loud, flashy and mostly harmless. Unless something catches fire...
  - 7, <strong>Weak spell.</strong> Effects, area, number of targets, etc. are halved.
  - 8, <strong>Lack of control.</strong> Your magic has a negative and annoying side effect.
  - 9, <strong>Power leak.</strong> The magic works if you pay the HP cost again.
  - 10, <strong>Delayed action.</strong> The magic takes effect... d4 turns from now.
  - 11, <strong>Bad aim.</strong> The spell affects another target of your choice.
  - 12, <strong>BÄM!</strong> The effects, number of targets, or size of the spell are doubled.

input.chaosRisk is an input variable from the metadata file:

---
name: Spell Mishap
input:
  displayTemplate: "Chaos dR: {{chaosRisk}}"
  params:
  - name: chaosRisk
    uiName: Chaos Risk Die
    helpText: The Chaos Risk Die represents magic’s stability in the area
    defaultValue: 1d12
    values:
    - 1d12
    - 1d10
    - 1d8
    - 1d6
    - 1d4

But, to get the variable's value we need to evaluate it with {{input.chaosRisk}}. But, plain Mustache template processing doesn't let us put it in a template like: {{>pick: {{input.chaosRisk}} spell_mishap}}

Step-by-step, the template processing looks like:

  • user-selected chaosRisk: 1d10
  • pick a template: %[[>pick: {{input.chaosRisk}} spell_mishap]]%
  • processing pass #1: %[[>pick: 1d10 spell_mishap]]%
  • processing pass #2: all curly-braces gone, replace square braces: {{>pick: 1d10 spell_mishap}}
  • processing pass #3: pick from spell_mishap table with 1d10 (rolls 9): <strong>Power leak.</strong> The magic works if you pay the HP cost again.
  • processing pass #4: no curly-braces, or %[[, return result.
Example 3: ensure processing order, saving state

Here's a more complicated example... The %[[ syntax is used when reading a state variable that's used to select one of the 'nested_tables' entries.

templates:
- "{{pick_role}}"

definitions:
  npc_template: |
    <strong>%[[{{>get: role}}.name]]% - %[[{{>get: role}}.role]]%</strong>
    <br/><br/>%[[{{>get: role}}.appearance]]%
    <br/><br/><strong>Goal:</strong> %[[{{>get: role}}.goal]]%
    <br/><br/><strong>Weapon:</strong> %[[{{>get: role}}.weapon]]%

tables:
  pick_role:
  - |
    {{>set: role enforcer}}
    %[[npc_template]]%
  - |
    {{>set: role gearhead}}
    %[[npc_template]]%
  - |
    {{>set: role stalkers}}
    %[[npc_template]]%
  - |
    {{>set: role fixers}}
    %[[npc_template]]%
  - |
    {{>set: role dog_handlers}}
    %[[npc_template]]%
  - |
    {{>set: role chroniclers}}
    %[[npc_template]]%
  - |
    {{>set: role bosses}}
    %[[npc_template]]%
  - |
    {{>set: role slaves}}
    %[[npc_template]]%

nested_tables:
  enforcer:
    role:
    - Enforcer
    name:
    - Hugust
    - Ingrit
    - Lenny
    - Marl
    - Nelma
    - Rebeth
    appearance:
    - Hulking posture, ape-like arms, grunts
    - Short and stocky, hoarse voice
    - Wiry, skinny and muscular
    - Fat and with a wheezing voice
    - Grotesquely tall, abnormal face
    - Weathered and scarred, hissing voice
  • "{{pick_role}}"
  • {{>set: role enforcer}} %[[npc_template]]% -> this sets a variable in the template state: 'role' to the string 'enforcer'
  • %[[npc_template]]%
  • {{npc_template}}
  • evaluated to:
    <strong>%[[{{>get: role}}.name]]% - %[[{{>get: role}}.role]]%</strong>
    <br/><br/>%[[{{>get: role}}.appearance]]%
    <br/><br/><strong>Goal:</strong> %[[{{>get: role}}.goal]]%
    <br/><br/><strong>Weapon:</strong> %[[{{>get: role}}.weapon]]%
    
  • evaluated to: (the {{>get: role}} partial reads the 'role' state variable:
    <strong>%[[enforcer.name]]% - %[[enforcer.role]]%</strong>
    <br/><br/>%[[enforcer.appearance]]%
    <br/><br/><strong>Goal:</strong> %[[enforcer.goal]]%
    <br/><br/><strong>Weapon:</strong> %[[enforcer.weapon]]%
    
  • evaluated to:
    <strong>{{enforcer.name}} - {{enforcer.role}}</strong>
    <br/><br/>{{enforcer.appearance}}
    <br/><br/><strong>Goal:</strong> {{enforcer.goal}}
    <br/><br/><strong>Weapon:</strong> {{enforcer.weapon}}
    
  • evaluated to:
    <strong>Lenny - Enforcer</strong>
    <br/><br/>Short and stocky, hoarse voice
    <br/><br/><strong>Goal:</strong> Please the Boss
    <br/><br/><strong>Weapon:</strong> Spiked bat
    

{{>add: <variable> <val>}}

{{>set: <variable> <val>}}

{{>accum: <list-variable> <val>}}

{{>remove: <list-variable> <val>}}

{{>get: <variable> [<delim>]}}