Lua scripting

Lua in ten minutes

Just enough Lua to read and write Underlives content, for people who have never programmed.

Scenes, actions, stats and most data files are written in Lua, a small language designed to be embedded in programs like games. Underlives uses Lua 5.3. You need very little of it to write content.

Values

lualocal name = "Mia"        -- text (a "string"), in double or single quotes
local age = 27            -- a number; 27 and 27.5 are both numbers
local married = false     -- true or false
local nothing = nil       -- "no value"

local creates a variable that exists only in the block where it is written. Use it for everything inside a scene.

-- starts a comment: the rest of the line is ignored.

Text

lualocal line = "Hello, " .. name .. "!"     -- .. joins text
local n = "You have " .. tostring(3) .. " messages"

Tables

A table is Lua's only data structure. It is both a list and a record.

lualocal places = { "cafe", "bar", "park" }   -- a list; places[1] is "cafe" (lists start at 1)
local stat = { STAT_ID = "mood", STAT_MAX = 100 }  -- a record; stat.STAT_ID is "mood"

Most Underlives files are one big table: the file says return { ... } and the game reads the fields.

Decisions

luaif age < 18 then
	Game:Narrate("Too young.")
elseif married then
	Game:Narrate("Already taken.")
else
	Game:Narrate("Why not?")
end

Comparisons: == equal, ~= not equal, <, >, <=, >=. Combine them with and, or, not.

warning

In Lua, only false and nil are false. 0 and "" count as true. if Player:getVar("met") then is true even when the value is 0; compare explicitly: if Player:getVar("met") == 1 then.

Loops

luafor i = 1, 3 do
	Game:Narrate("Knock " .. i)
end

for index, place in ipairs(places) do
	Game:Log(place)
end

while Player:getStat("energy") > 20 do
	Player:modifyStat("energy", -10)
end

Functions

lualocal function greet(person)
	return "Hi, " .. person:name()
end

Game:Narrate(greet(Actor))

Calling the game: the colon

Game objects are called with a colon, which passes the object itself along:

luaGame:Narrate("text")        -- right
Player:modifyStat("mood", 5)
Game.Narrate("text")        -- wrong: the function never learns which object it was called on

The objects you will use most:

Object What it is
Game The scene and the world: dialogue, choices, time, finding people, story state
Player The player, as a person
Actor, and any person you look up Other people
ModGame The same as Game, available in libraries and UI scripts

Function names are not case-sensitive: Game:SceneStart(), Game:sceneStart() and Game:scenestart() are the same call. The legacy content mixes spellings; pick one and stick to it.

What a mod cannot do

Players install mods with one click, so mod code runs in a sandbox. It cannot touch the player's computer, and one mod cannot break the game for the others.

  • Available: the Lua basics (pairs, ipairs, pcall, tostring, setmetatable on your own tables, load of text), and the string, table, math, coroutine and utf8 libraries.
  • Not available: io, os, debug, package, require, dofile, loadfile, collectgarbage, rawset, string.dump, and load of compiled (binary) chunks. To share code between files, put it in your mod's Lua/ folder: every file there is loaded before any scene runs.
  • The game's names are the game's: People.find = ..., People = nil, string.rep = nil or ActorMethods = {} raise an error ("belongs to the game"). Add your own globals and your own ActorMethods.myMethod freely.
  • Data files are only tables: a .ulstat, .ulaction, .ulroom and the like can use local values, .. and its own local functions, but no game function and no library at all.
  • Limits: a scene that runs too long without pausing for the player (an endless loop), or a script that allocates too much memory, stops with an error instead of freezing the game.

Where to learn more

  • Programming in Lua, the official book (the first edition is free online).
  • The base game itself: every scene in Content/Modules/ul_Base is readable Lua.