Getting started

Your first mod

A stat, an action and a scene that reacts to it, in three small files. About twenty minutes.

We will build espresso_habit: the player can order an espresso at any café, each one feeds a new caffeine stat, and after enough of them a short scene plays.

It touches the three things most mods are made of: data (a stat), something to do (an action) and story (a scene).

1. Create the folder

Mods live in the Mods folder of the game directory (the game creates it on first launch). Make a folder for yours:

textMods/
└── espresso_habit/

The folder name is your mod's identity: it is what the load order, the mod list and other mods refer to. Use lowercase letters, digits and underscores, and prefix it with something of your own (yourname_) so it never collides with somebody else's mod. ul_ is taken: it marks the official modules that ship with the game.

2. Describe the mod

Create espresso_habit.ulmod inside the folder. Any file ending in .ulmod works; naming it after the folder is the convention.

iniMODULE_UNIQUEID: espresso_habit
MODULE_NAME: Espresso Habit
MODULE_AUTHOR: Your Name
MODULE_VERSION: 1.0.0
MODULE_CATEGORY: Gameplay
MODULE_DESCRIPTION: Order espressos at cafés. Too many and it shows.
MODULE_REQUIREMENTS: ul_Base

A folder without an .ulmod file is not a mod and is ignored. Optionally drop a thumbnail.png next to it for the mod list. Every key is described in Anatomy of a mod.

3. Add a stat

Create Stats/caffeine.ulstat:

luareturn {
	STAT_ID = "caffeine",
	STAT_NAME = "Caffeine",
	STAT_DESCRIPTION = "How wired you are. Wears off by itself.",
	PLAYER_ONLY = true,
	NPC_ONLY = false,
	HIGHER_BETTER = "false",
	STAT_MIN = 0,
	STAT_MAX = 100,
	DEFAULT_VALUE = 0,
	DAILY_CHANGE = -30,
}

Every person the stat applies to now has a caffeine value that starts at 0, stays between 0 and 100, and drops by 30 each game day. See Stats.

4. Add an action

Create Actions/food/order_espresso.ulaction:

luareturn {
	ACTION_UNIQUEID = "order_espresso",
	ACTION_NAME = "Order an espresso",
	WHERE = { "cafe", "coffee" },
	WHEN = { 7, 19 },
	MINUTES = { 5, 10 },
	TIMEOUT_MINUTES = 30,
	EFFECTS = {
		{ STAT = "caffeine", AMOUNT = 20, TRIGGER = "END" },
		{ STAT = "energy", AMOUNT = 10, TRIGGER = "END" },
		{ STAT = "money", AMOUNT = -3, TRIGGER = "START" },
	},
	CONDITIONS = {},
}

In any building tagged cafe or coffee, between 7:00 and 19:00, the player can now order an espresso. It takes 5 to 10 minutes, costs 3, and cannot be repeated for half an hour. WHERE uses the venue tags from the map, listed in Map and venues. The food folder is the category the action is listed under. See Actions.

5. Add a scene

Create Scenes/espresso_jitters.ulscene:

luareturn {
	WHAT = { "order_espresso" },
	WHERE = { "all" },
	WHEN = { 0, 24 },

	RUN = function(Game, Player, CurrentCompanion)
		if Player:getStat("caffeine") < 60 then
			return false -- not wired enough yet: let another scene have the turn
		end

		Game:SceneStart()
		Game:Narrate("My hands will not stop shaking. The cup rattles on its saucer.")
		Game:Choice(0, nil, "Order another one")
		Game:Choice(1, nil, "Switch to water")

		if Game:GetChoice() == 0 then
			Game:Narrate("Why stop now?")
			Player:modifyStat("caffeine", 20)
			Player:modifyStat("mood", -5)
		else
			Game:Narrate("A glass of water. Slowly, the room stops vibrating.")
			Player:modifyStat("caffeine", -20)
		end

		Game:timeout(12, "espresso_jitters")
		Game:SceneEnd()
	end,
}

Each time an action finishes, the game gathers every scene whose WHAT lists that action, whose WHERE matches where the player is and whose WHEN contains the current hour, and tries them in random order. Ours bows out with return false until caffeine reaches 60. Then it runs, and timeout keeps it from firing again for 12 game hours. See Scenes.

6. Try it

Your mod now looks like this:

textMods/espresso_habit/
├── espresso_habit.ulmod
├── Stats/caffeine.ulstat
├── Actions/food/order_espresso.ulaction
└── Scenes/espresso_jitters.ulscene
  1. Start the game. New mods are enabled automatically and added at the end of the load order. You can check in the Mods panel of the pause menu.
  2. Walk into a café, open the action list and order espressos until the scene plays.
  3. If nothing happens, open Saved/Logs/Underlives.log and search for espresso. A Lua syntax error names the file and the line. See Testing and debugging.
tip

Scenes and actions are read once per session. After editing a file, restart the game to see the change.

Where to go next