Scenarios

To start building a custom scenario use the Scenario Builder mod. The mod itself contains the information of how to use the builder to generate a scenario.

However, the builder currently only supports a subset of features the scenario definition is able to provide. E.g. it can’t set HP values for overlays, named enemies or loot effects from treasure chests. To add those features to a scenario, you can edit the generated scenario definition from the Scenario Builder. This, this section contains the documentation about the structure of the scenario definition. When you want to add or change some features, you need to understand this structure and make those changes accordingly.

In the Concrete use-cases section you can also find examples for specific use-cases to give you an idea on where to start.

This section is currently not well documented. A lot of features are missing…​

Basics

The scenario builder creates a token, that contains the scenario definition within its script. To adjust the scenario definition, the script has to be changed. The script on the tokens looks like this: ##

-- some more lines above this one
__bundle_register("__root", function(require, _LOADED, __bundle_register, __bundle_modules)
  -- this is where the actual code lies
  local ScenarioApi = require("api.ScenarioApi")

  function onLoad()
    -- when the token is placed on the table, the scenario will be added
    -- to the list of available scenarios
    -- the first parameter "Custom Scenario" here is a unique id across all
    -- scenarios. In the scenario setup this will be the prefix in the list,
    -- e.g. "SoX1 - The Name of SoX1 Scenario"
    ScenarioApi.registerScenario("Custom Scenario", {
      -- this is the scenario definition from the scenario builder
      -- it can be adjusted to add new features
    })

    return
  end
end)
__bundle_register("api.ScenarioApi", function(require, _LOADED, __bundle_register, __bundle_modules)
  -- more stuff after this one as well, that must be untouched

To change the scenario definition, change the content between {} in the function call to registerScenario. You should also change the scenario ID (the first parameter of the function call, e.g. Custom Scenario in the example above). This ID needs to be unique across all scenarios.

The ID can also be used to quickly spawn the scenario without using the Game Setup menu. Enter >spawn-scenario <scenario-id> into the chat window (e.g. >spawn-scenario Custom Scenario given the example above). This will delete everything in the scenario play area as well as all monsters and then spawn the new scenario after a short delay. While this is mainly used for developing purposes, there’s nothing stopping you from using it in your gaming sessions as well.

General scenario definition structure

Collapse the following example to get an overview of the overall scenario definition structure. The different parts are explained in more details in the following sections.

Example of all the general scenario definition structure
{
  -- The name of the scenario
  name = "Black Barrow",
  -- Optional icon which shows to the left of the scenario name in the Game Setup menu, typically for Solo Scenarios
  icon = "http://cloud-3.steamusercontent.com/ugc/111111111111111111/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/",
  -- The scenario book used by this scenario
  scenarioBook = {
    name = "Gloomhaven",
    page = 3,
  },
  -- A list of scenario conclusion tiles that can be revealed, when the scenario is finished
  scenarioConclusions = {
    {
      image = "http://cloud-3.steamusercontent.com/ugc/1752439086837779719/8C6A36C4FDB88BCB7B012F7121A2DB8E1876D0B3/",
      scale = { 1.50, 1.00, 1.50 },
      buttonLabel = "Reveal Conclusion (36)"
    },
  },
  -- Whether the overlays are placed on a horizontal or vertical grid
  gridType = ScenarioApi.GridType.Horizontal,
  -- Adjustments to monsters inside the scenario, e.g. a changed ability deck,
  -- another monster level or different stats
  monsters = {
    ["Bandit Guard"] = {
      all = {
        levelModifier = 1,
      }
    }
  },
  -- Registered extra content that should be placed when the scenario loads.
  -- Used as a fallback to include all kind of crazyness
  extraContent = {
    {
      name = "Extra Stuff",
      position = { 0, 3, 0 },
    }
  },
  -- The list of doors connecting the different rooms
  doorTiles = {
    {
      name = "Stone Door Horizontal",
      tiles = {
        {
          rooms = { 2, 3 },
          position = { x = -1.75, y = 1.82, z = 6.06 },
          rotation = { x = 0, y = 180, z = 0 },
          tokens = { "1" },
        }
      }
    },
    {
      name = "Stone Door Vertical",
      tiles = {
        --
      }
    }
  },
  -- The list of rooms in the scenario, each containing their overlays, monsters and map tiles
  rooms = {
    [1] = {
      -- The list of used map tiles
      mapTiles = {
        {
          tile = "L1",
          position = { x = 0.9, y = -3.77, z = -3.03 },
          rotation = { x = 0, y = 180, z = 0 }
        }
      },
      -- The list of scenario section tiles that will be placed for this room
      scenarioSections = {
        {
          image = "http://cloud-3.steamusercontent.com/ugc/1752439086836487818/4256434230672E8FBFFA1CBE7C1A93984DF2EA95/",
          position = { -28.44, 1.75, 21.98 },
          scale = { 0.69, 1.00, 0.69 },
          hidden = true,
          buttonLabel = "Section 02",
        },
      },
      -- The list of overlays that are start tiles
      startTiles = {
        name = "Stone Corridor 1",
        tiles = {
          {
            position = { x = -0.43, y = 1.93, z = -5.30 },
            rotation = { x = 0, y = 0, z = 0 }
          },
        }
      },
      -- The list of placed monsters
      monsters = {
        {
          name = "Bandit Guard",
          tiles = {
            {
              numCharacters = {
                [2] = ScenarioApi.MonsterLevel.Elite,
                [3] = nil,
                [4] = nil
              },
              position = { x = 0.87, y = 2.06, z = -1.51 },
            },
          }
        }
      },
      -- The list of placed overlays
      overlayTiles = {
        {
          bag = ScenarioApi.OverlayType.Trap,
          type = {
            {
              name = "Spike Trap",
              tiles = {
                {
                  position = { x = -0.43, y = 1.93, z = 6.81 },
                  rotation = { x = 0, y = 180, z = 0 },
                },
              }
            }
          }
        },
      },
    },
    [2] = {
      -- another room
    }
  },
  -- A list of all monster types that are included in this scenario (including summons)
  monsterList = {
    "Bandit Archer",
    "Bandit Guard",
    "Living Bones"
  }
}

Scenario books

The scenarioBook property defines which scenario book should be used for the scenario. It has a name property which must match the tag of a registered scenario book. The page property defines which page will be opened, inside the scenario book. This page will be offset by 1 as it assumes each scenario book has a front cover that is not included in the page numbering. E.g. if the scenario book is opened with a PDF viewer, the viewer would show page number 4, but the definition would include page number 3. If the scenario spawns two pages of the scenario book, the property pageCount can be set to 2. More than 2 pages are currently not supported and are probably never required anyway.

scenarioBook = {
  -- The scenario book to use
  name = "Gloomhaven",
  -- The page that will be opened inside the book
  page = 3,
  -- If 2 pages should be shown, set this value as well
  pageCount = 2,
}

Section Books

The sectionBook property defines which section book should be used for the scenario. It has a name property which must match the tag of a registered section book. The sections property defines which sections from the book are relevant for this scenario. The mod will use this information to figure out which pages it needs to spawn from the book. Each determined page will spawn as a separate book at a fixed position. Only the buttons for the defined sections will be active, all other sections in the book will simply be black without a button.

local scenario = {
  sectionBook = {
    -- the tag on the section book object
    name = "Gloomhaven",
    -- The list of sections that should be used for this scenario
    -- Determines which buttons will be shown and which pages will spawn
    sections = {
      -- the id values is the value shown in the button
      { id = "2A" },
      { id = "2B" },
      { id = "3A" },
    }
  }
}

Default Monster Settings

It’s possible to define extra attributes, stats, immunities or their used ability deck for all monsters of a given type as part of the scenario definition. It is possible to distinguish between normal and elite monsters as well. The general structure looks like this:

local scenario = {
  monsters = {
    -- the name of the monster as the key
    ["Bandit Guard"] = {
      all = {
        -- settings applied to all Bandit Guards
      },
      normal = {
        -- settings additionally applied to all normal Bandit Guards
      },
      elite = {
        -- settings additionally applied to all elite Bandit Guards
      }
    },
    -- another monster
    ["Bandit Archer"] = {
      -- same structure as above
    }
  }
  -- rest of scenario definition
}

The settings from all are applied to all monsters that are spawned for this type. Settings in normal/elite then are additionally applied to monsters of the given difficulty.

All entries follow the same structure and look like this:

all = {
  -- The ability deck to use. If not used it will use the monsters default
  -- ability deck
  abilityDeck = "Archer",
  -- A modifier that should be applied to the current scenario level.
  -- This will make the monsters stronger/weaker. It can be a Formula.
  levelModifier = 1,
  -- The current HP value for the monster. If hpMax is not used, this is also
  -- the maximum HP value.
  -- It can be a Formula. Use the variable "H" or "this" to reference the
  -- default HP value for this monster.
  hp = { "+", "H", 3 },
  -- The maximum HP value for the monster.
  -- It can be a Formula. Use the variable "H" or "this" to reference the
  -- default maximum HP value for this monster.
  hpMax = { "+", "this", 3 },
  -- Adjusted stats for the monster
  stats = {
    -- Adjusted immunities
    immunities = {
      -- Immunities that will be added to the monster.
      add = { "Stun", "Muddle", "Poison" },
      -- Immunities that will be removed from the monster.
      remove = { "Curse" },
    },
    attributes = {
      -- Attributes that will be added to the monster. If the monster already
      -- has the attribute, it will be overwritten with this definition.
      -- The format is the same as for general Stats.
      -- The values currently can not be Formula, only fixed values.
      add = {
        "Muddle",
        Pierce = 3,
        Retaliate = {
          value = 2,
          range = 2,
        },
      },
      -- Attributes that will be removed from the monster.
      remove = { "Shield", "Wound" },
    },
    -- The adjusted attack value. Can be a formula, where the variable "this"
    -- refers to the default attack value for this monster (at its current
    -- level).
    attack = { "+", "this", 1 },
    range = { "+", "this", 1 },
    -- The adjusted move value. Can be a formula, where the variable "this"
    -- refers to the default move value for this monster (at its current
    -- level).
    move = {
      type = "Flying",
    },
    -- The text that will show below the monster.
    text = "New Text",
  }
}

The same structure can be applied to an individual monster as well, e.g. to overwrite the stats of a single monster of a given type. The following example demonstrates this:

local scenario = {
  monsters = {
    ["Bandit Guard"] = {
      all = {
        stats = {
          immunities = { add = { "Poison" } }, }
      },
      elite = {
        stats = {
          immunities = { add = { "Wound" } },
        },
        attributes = { add = { "Muddle" } },
      }
    }
  },
  rooms = {
    [1] = {
      monsters = {
        {
          name = "Bandit Guard",
          tiles = {
            {
              -- This Bandit Guard will get the Position immunity from all
              numCharacters = { [2] = Scenario.MonsterLevel.Normal },
            },
            {
              -- This Bandit Guard will get the Wound immunity from elite
              -- and the Muddle attack effect from elite.
              -- The immunity from all is overwritten by the immunities from
              -- elite
              numCharacters = { [2] = Scenario.MonsterLevel.Elite },
            },
            {
              -- This Bandit Guard will get the Muddle attack effect from elite
              -- and a Stun immunity from its own definition.
              -- The immunities from all and elite are overwritten by its own
              -- definition
              numCharacters = { [2] = Scenario.MonsterLevel.Elite },
              stats = {
                immunities = { add = { "Stun" } },
              },
            },
          }
        }
      }
    }
  }
}
While it is possible to define the used ability deck on all levels presented above, it’s only useful to use it within the definition of all. It’s not possible to have the same monster type with different ability decks, so the first monster that spawns defines the used ability deck for all monsters of that type. And if the "Preload Enemies" option is used to prepare all monsters in a scenario at its start, the settings for those prepared monsters is only defined by all (as those don’t have a difficulty setting).

Figures

Similar to the Monsters option, you can spawn scenario specific figures which perform the same action each round. This is commonly used for named allies or unique basic enemies. These figures require a standee model which can be based on any existing summon or enemy, or you can register a custom figure (objects with the "Figure" tag included within a "Gloomhaven Custom Content" bag).

Figures
figures = {
  ["Mechanical Turret"] = {
    --name of the Summon, Enemy, or registered Figure object to use as a standee
    basedOn = "Ancient Artillery",
    --possible base values are "Normal" (White), "Elite" (Yellow), "Ally" (Blue), "Boss" (Red) and "Named" (Purple)
    base = "Normal",
    stats = {
      health = 10,
      attributes = {
        add = { Shield = 1 },
      },
      immunities = {
        add = { "Poison" },
      },
    },
    ability = {
      initiative = 50,
      ability = "{e.Attack}3 {e.Range}4",
    },
  },
},

When placing a figure within a room, you can also exclude it from spawning with different player counts.

Placed Figures
figures = {
  [1] = {
    name = "Mechanical Turret",
    position = { x = '15.15', y = '1.76', z = '-8.75', },
    rotation = { x = '0.00', y = '180.00', z = '0.00', },
    characterCount = {
      [2] = R.Remove,
      [3] = R.Remove,
    },
  },
},

Overlays

You can define the conditions and name for overlays, commonly used for traps:

Overlays
overlays = {
  --This is the original overlay tile's name.
  ["Bear Trap"] = {
    --These are the trap conditions applied when triggered.
    conditions = { "Damage" },
    --This is the new name given to all overlays of this type when spawned durign scenario setup.
    name = "Damage Trap",
  },
},

Objectives

You can add a Scenario Aid token to an object’s UI, or even include a health bar:

Objectives
overlayTiles = {
  {
    bag = R.ElementType.Obstacle,
    type = {
      {
        name = "Rock Column",
        tiles = {
          {
            position = { x = -1.31, y = 1.87, z = 9.85, },
            rotation = { x = 0, y = 0, z = 0, },
            name = "Summoning Stone",
            tokens = { "a" },
            hp = {"+", "C", 1},
          },
        },
      },
    },
  },
},

Actions

For a complete list of actions available, see Common > Actions: https://gloomhaven-tts-enhanced.github.io/public-scripts/custom/1.3/common/action.html.

Frequently used actions

You can add a Treasure action to an object (typically a treasure chest) to spawn the card defined as treasure:

Treasure
action = {
  name = "50",
  treasure = "Treasure 50",
},

You can set the chest as a scenario goal as well:

Treasure (G)
action = {
  treasure = "G",
},

You can add an action to an objective to act as a door. Triggering this can be used to reveal hidden rooms or spawn additional enemies:

Door/Rooms
action = {
  rooms = { 6, 2 }
}

Tags

You may need to add tags to an overlay or standee when it’s spawned so that you can build actions to interact with it. A common use is to remove the object as part of an action.

Adding tags to an object
overlayTiles = {
  {
    bag = R.ElementType.Obstacle,
    type = {
      {
        name = "Nest",
        tiles = {
          {
            position = { x = 3.94, y = 1.86, z = 6.82, },
            rotation = { x = 0, y = 0, z = 0, },
            tags = {"Scenario X Nest 1"},
          },
        },
      },
    },
  },
},

Randomly placed objects

Some scenarios require to place some objects randomly during setup, e.g. placing objective tokens face down or spawn the content of a random dungeon card. To handle this, you need to define a randomPools, and randomObjects within a room.

Random pools are defined at the top level of the scenario. Give them a name, and the type shown below. In the values attributes you can define the list of tokens you want to use.

Adding a pool of random tokens
randomPools = {
  {
    name = "Some name",
    type = ScenarioApi.RandomType.Tokens,
    values = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 },
  }
},

Then inside a room, you can add the new randomObjects property and add a list of things that can randomly be placed there. Use the property pool to reference the pool of random objects from above and the position where the object shall be placed.

Define the locations where the random objects may be spawned
rooms = {
  [1] = {
    randomObjects = {
      {
        -- one possible position for a token from Pool "Some name"
        pool = "Some name",
        position = { ... },
      },
      {
        -- another possible position for a token from Pool "Some name"
        pool = "Some name",
        position = { ... },
      },
      -- and so on
    }
  }
},

Extra content

As a fallback mechanism extra scenario components can be defined. This is meant to easily supply objects that are currently not supported through scripting (e.g. monsters that use different attack modifier decks). Those first have to be registered inside a Custom Content Box. Then inside the scenario definition, they can be references by their name and be placed at a specific position.

It’s possible to define the extra components on the top-level of the scenario and also on each room. When defined on the top-level the components will spawn, once the scenario is loaded. Components defined on a room are loaded once the rooms is opened.

Both entries are lists, so they can define multiple components.

local scenario = {
  -- can be defined on the top level
  extraContent = {
    -- one entry
    {
      -- The name of the registered extra component
      name = "Extra Stuff",
      -- The position where the content will spawn
      position = { 10, 2, 10 },
    },
    -- a second entry
    {
      -- ..
    },
  },
  rooms = {
    [2] = {
      -- Or as part of a room
      extraContent = {
        {
          name = "More Extra Stuff",
          position = { 10, 2, 10 },
        }
      }
    }
  }
}

Concrete use-cases

The examples in this section create a local variable named scenario. This contains the relevant part of the scenario definition for the example and is the part that would be passed to registerScenario.

Custom monster ability decks

Changes the ability deck monsters will use.

-- The Bandit Guards use the Archer ability deck instead of their own Guard deck
local scenario = {
  monsters = {
    ["Bandit Guard"] = {
      all = {
        abilityDeck = "Archer"
      }
    }
  }
}

Extra content

Loads additional components as a fall-back that are currently not supported with scripting (also known as "asshole bags").

local scenario = {
  -- This will be placed together with the scenario
  extraContent = {
    {
      name = "Extra Stuff",
      position = { 10, 2, 10 },
    }
  },
  rooms = {
    [2] = {
      -- This will be placed, once the room 2 will be opened
      extraContent = {
        {
          name = "More Extra Stuff",
          position = { 10, 2, 10 },
        }
      }
    }
  }
}