Enemies

Components

Enemy Envelope

The enemy envelope is the bag that contains all components for a new enemy of that type. It will not be shown to the players, so a regular bag is sufficient, no extra model needed.

The bag must contain the Enemy Bag, the Stats-Sheet and the Ability Deck in any order.

The bag itself will never be placed on the table by the mod, so it shouldn’t contain and scripts as those would never be executed. It’s sole function is to group all objects belonging to a single enemy type.

Properties

Name

Full name of the enemy.

Tags

Enemy Envelope

Enemy Bag

This is an infinite bag containing the Figure for the enemy. It will be placed on the monster mat and new enemies will be spawned from the bag.

Properties

Name

Full name of the enemy followed by " Standee", e.g. "Bandit Archer Standee"

Lua Script

The Enemy Bag is currently the best place to register the enemy. This is typically done in two steps.

First the enemy’s ability deck has to be registered. This is done by calling EnemyApi.registerEnemyAbilityDeck. It takes the name of the ability deck and the list of abilities it includes. The abilities can have two attributes:

  • An image with the link to the image for this ability card. This value isn’t used for 1.3 yet, so it can also be ignored. It will be used for future versions, e.g. to the show the active ability card of an enemy in the initiative tracker.

  • A spawn element, describing which object the ability can spawn (like traps or summoning other enemies). Documentation about the proper attributes for this element can be found here

This part should be skipped, if the enemy uses an ability deck that is already registered in the game. This is the case for all decks from the Gloomhaven base game.

Example 1. Registration for the "Archer" enemy ability deck
api/GloomApi.ttslua
local EnemyApi = require("api.EnemyApi")
local R = require("api.Resource")

EnemyApi.registerEnemyAbilityDeck("Archer", {
  abilities = {
    [1] = {
      image = "https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/archer/ma-ar-1.png",
    },
    -- abilities 2 till 6 would be here too
    [7] = {
      image = "https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/archer/ma-ar-7.png",
      spawn = {
        {
          element = {
            type = R.ElementType.Trap,
            name = "Spike Trap",
            damage = 3,
          },
        }
      },
    },
    [8] = {
      image = "https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/archer/ma-ar-8.png",
    },
  }
}

Once the ability deck is registered, the actual enemy type has to be registered, to connect the enemy to the ability deck. This is done by calling EnemyApi.registerEnemy. This function takes the name of the enemy type and two named parameters:

  • icon is the link to an image for an icon of this enemy type. This is currently used in the Context Menu, when the enemy can be summoned by other enemies.

  • abilityDeck is the name of a previously registered ability deck. This tells the mod that this enemy uses the ability deck with this name.

Example 2. Registration of the "Bandit Archer" enemy.
api/GloomApi.ttslua
local EnemyApi = require("api.EnemyApi")

EnemyApi.registerEnemy("Bandit Archer", {
    icon = "http://cloud-3.steamusercontent.com/ugc/1688272925075790807/F181B084F5CA91BC0E0F75C682C6993C4BCBDA43/",
    abilityDeck = "Archer",
})

If the enemy is a boss enemy, another API has to be used instead: EnemyApi.registerBossEnemy. Since all bosses use the same ability deck, this is where their abilities are defined (e.g. which elements they can spawn). The spawn element works the same as for registerEnemyAbilityDeck and details can be found here.

api/GloomApi.ttslua
local EnemyApi = require("api.EnemyApi")
local R = require("api.Resource")

EnemyApi.registerBossEnemy("Bandit Commander", {
  icon = "http://cloud-3.steamusercontent.com/ugc/1688272925075790870/264D1A7A6C93A37894B204DD6CDB555F11B8B5BD/",
  spawn = {
    {
      element = {
        type = R.ElementType.Enemy,
        name = "Living Bones",
      },
    },
  },
})

Figure

Name

Full name of the enemy type.

Description

Must be empty.

Tags

Enemy (will also be set by the script).

Lua Script

Put the following Lua script on the Figure. The value for FrameOffset defines where the HP will be placed at. Adjust the value to fit the model. You can use this tool to easily try out different values.

Figures/Monster.ttslua
FrameOffset = 260

require("Figures.Monster")

Xml Ui

Figures/Monster.xml
<Include src="Figures/Monster.xml"/>

Stats-Sheet

Properties

Name

Full name of the enemy followed by " Stat Sheet", e.g. "Bandit Archer Stat Sheet".

Tags

MonsterStatSheet

Lua Script

Within the script of the Stats-Sheet the stats for the different levels are defined as well as how many figures can be present for this enemy type.

To define the maximum number of enemies, declare a variable named count. Put the following script on the Stats-Sheet and adjust the values accordingly.

Example 3. Defining the maximum number of figures.
StatSheet.ttslua
count = 4

require("StatSheet")

The stats per level are declared using the variable named stats. This variable is a table of stat entries, one per level. You can use a numeric index, to more easily set the values per level.

Example 4. General structure of the stats variable
StatSheet.ttslua
stats = {
  [0] = {
    -- Level 0 stats go here
  [1] = {
    -- Level 1 stats go here
  }
  -- etc.
}

require("StatSheet")

The entry for each level has an attribute named orientation and the attributes normal and elite. In case of a boss enemy the level has an attribute boss instead of normal and elite.

The orientation attribute defines the rotation of the Stat-Sheets object that shows that stats for this level. This is used by the mod to figure out which level is currently set on the Stats-Sheet by checking the current orientation of the object with the entries defined here.

StatSheet.ttslua
stats = {
  [0] = {
    orientation = {0, 180, 0},
    normal = {
      -- stats for normal version go here
    },
    elite = {
      -- stats for elite version go here
    },
    boss = {
      -- this is used by bosses instead of the above two
    }
  },
}

require("StatSheet")

Within normal, elite or boss the actual stats have to be defined. Checkout Stats for the available attributes.

Example 5. More complete example of the Stats-Sheet script.
StatSheet.ttslua
-- maximum number of standees for this enemy type
count = 4;

-- stats per level
stats = {
  [0] = {
    -- this is the orientation the stat that shows the level
    orientation = {0, 180, 0},
    -- stats for the normal version
    normal = {
      health = 8,
      move = 3,
      attack = 2,
      range = 0,
      attributes = {}
    },
    -- stats for the elite version
    elite = {
      health = 13,
      move = 3,
      attack = 3,
      range = 0,
      attributes = {}
    }
  },
  -- same for level 1
  [1] = {
    orientation = {0, 90, 0},
    normal = {
      health = 9,
      move = 3,
      attack = 2,
      range = 0,
      attributes = {
        "Poison"
      }
    },
    elite = {
      health = 15,
      move = 3,
      attack = 3,
      range = 0,
      attributes = {
        "Wound"
      }
    }
  },
  -- level 2 and 3 omitted
  [4] = {
    -- level 4 is the same, but now the back of the stat sheet is used,
    -- so the rotation on the z-axis has to be changed to account for that
    orientation = {0, 180, 180},
    normal = {
      health = 16,
      move = 3,
      attack = 4,
      range = 0,
      attributes = {
        "Poison"
      }
    },
    elite = {
      health = 24,
      move = 4,
      attack = 4,
      range = 0,
      attributes = {
        "Poison",
        "Wound"
      }
    }
  },
  -- and so on until level 7
}

require("StatSheet");

Ability Deck

The Ability Deck contains the different Ability Cards.

Properties

Name

Name of the Ability Deck (e.g. "Archer").

Ability Card

Each Ability Card within the Ability Deck needs to have a special naming structure and the Monster Ability Card tag.

Name

Name of the Ability Deck (e.g. "Archer").

Description

Definition of the initiative (see below).

The description field is used to encode the initiative values(s) and the action(s) the enemy takes.

Each entry has to start with an initiative value followed by a colon and a space. The initiative value needs to have a leading zero if it’s below 10.

After the colon, the textual description can be added. This is used in the initiative tracker to show what the enemy is doing. Multiple actions can be defined by using a semicolon. Those will show up as separate lines in the initiative tracker.

31: Move +0; Attack +0

For abilities that shuffle the ability cards, add shuffle after the initiative.

15 shuffle: Move +1; Attack +1

If the ability grants multiple actions at different initiatives, you can use new lines. Each line will add an entry to the initiative tracker.

03: Attack +0
11: Move +0
20: Attack +0

To use icons in the initiative tracker, you must change your player color to black and update the GM Note instead of the Description field. You can use the following text for icons:

  • {e.Attack}

  • {e.Move}

  • {e.Heal}

  • {e.Loot}

  • {e.Shield}

  • {e.Retaliate}

  • {e.Range}

  • {e.Target}

  • {e.Damage}

  • {e.Jump}

  • {e.Fly}

  • {e.Teleport}

  • {e.Generate Fire}

  • {e.Generate Ice}

  • {e.Generate Air}

  • {e.Generate Earth}

  • {e.Generate Light}

  • {e.Generate Dark}

  • {e.Generate Any}

  • {e.Consume Fire}

  • {e.Consume Ice}

  • {e.Consume Air}

  • {e.Consume Earth}

  • {e.Consume Light}

  • {e.Consume Dark}

  • {e.Consume Any}

You can also include an icon for any custom registered effect or condition using {e.<Effect>} or {c.<Condition>} respectively.

03: {e.Attack}+0
11: {e.Move}+0
55: {c.Poison}

You can add other custom conditions/effects, as long as they are already part of the Markazi Gloom Color font by Dimon-II. To add them, the registerCondition and registerEffect functions allow a new entry called renderedMarkup which takes any string. Whatever you put there will be shown in the preview whenever the markup for this Condition/Effect is used. There are already helper via the new UiApi where you only need to provide the glyph and color used for the condition. E.g. for a condition, all you need to do is to find the correct glyph in the font and insert it into the first parameter of UiApi.conditionText \u{<Glyph>}. The color for the condition as hex value is the second parameter.

Example 6. How to add Safeguard as a custom condition:
local UiApi = require("api.UiApi")

ConditionApi.registerCondition("Safeguard", {
  image = "...",
  immunity = { ... },
  renderedMarkup = UiApi.conditionText("\u{E09D}", "#5B9977"),
})

You can include an AoE "Area" which will display an AoE pattern in the initiative tracker. This can be designated using {area.<Rows>} with specific syntax to create the intended pattern:

  • s will create a grey "self" hex

  • t will create a red "enemy" hex

  • a will create a blue "ally" hex

  • e will create a blank space with no hex

  • _ will begin a new row, every even row will be automatically intented by half a hex

Example 7. Wind Demon’s ability with two areas:
-- first row: empty hex and two target hexes
-- second row: the self hex and a target hex
{area.ett_st}
-- second area
{area.ett_stt_ett}
gh ma wd 5

Stats

Stats for enemies and summons have the following structure.

local stats = {
  -- The maximum health values
  health = 8,
  -- The base move value
  move = 3,
  -- The base attack value
  attack = 2,
  -- The base range value
  range = 0,
  -- A list of extra effect like retaliate shield
  attributes = { },
  -- A list of immunities
  immunities = { },
  -- A text that will be shown below the healthbar
  -- This can be used for describing what a variable means, e.g. on a boss
  -- like, "V is the number of ..."
  text = "",
}

Health

This attribute takes a single value. For Boss enemies this can also be a string in the form of "8xC", which will calculate the value based on player count. A Formula is currently not supported.

Move

The move attribute can either be a simple value (a number or string like above), or a table with the attributes value and type. The attribute value determines how for the figure can move and the type determines the movement type (e.g. for flying enemies). The value for this field can the name of any registered Effect, but generally should be "Fly", "Jump", or "Teleport".

-- A flying enemy
move = {
  value = 3,
  type = "Fly"
}

Attack

The attack attribute functions similar. It can take a simple value or a table with the attributes value and area. The attribute value determines the attack value of the figure the area attribute is the name of an Effect. The image for this Effect will be shown next to the attack value. Its purpose is to show the attack area for this figure, but it could be used for any other effect as well.

-- An enemy with a special attack area
attack = {
  value = 3,
  area = "Area X"
}

Immunities

The immunities field is a list of Effects this figure is immune to.

immunities = { "Poison", "Pull", "Curse", "Stun" }

Attributes

The attributes field describes extra attributes for the enemy, like if it has a shield value, or retaliated damage or always applies a certain condition. It’s a list that can contain three kinds of different values:

  • A simple string. This is the name of any registered Effect, e.g. "Generate Air", "Muddle" or "Curse"

  • A name, value pair. The name is the name of any registered effect and the value is the value for this Effect, e.g. "Shield = 2" or "Retaliate = 2". This will show the image for the Effect followed by a text with this value.

  • A name with a table that has the attributes value and range. This is used for retaliated damage that has a range. It will show the image for the Retaliate Effect and it’s value, followed by the image for Range and it’s value. While this is currently only used for Retaliate, this format can be used for any other Effect as well.

attributes = {
  -- the name of an Effect
  "Generate Air",
  -- the name of an Effect and it's value
  Shield = 2,
  -- short for for Retaliate, if the range is 1
  Retaliate = 2,
  -- special form for Retaliate, with a range greater than 1
  Retaliate = {
    value = 2,
    range = 2
  },
}

Text

This text will be shown below the figure and can be used to describe special variables or specific effects for this figure.