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
- 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.
- Walk into a café, open the action list and order espressos until the scene plays.
- If nothing happens, open
Saved/Logs/Underlives.logand search forespresso. A Lua syntax error names the file and the line. See Testing and debugging.
Scenes and actions are read once per session. After editing a file, restart the game to see the change.
Where to go next
- Make the scene talk: Dialogue and choices.
- Involve other people: People.
- Remember things between scenes: State and memory.
- Change what the base game already does: Load order and overrides.