Libraries
Shared Lua code in Lua/*.lua, adding methods to every person, and building an API other mods can use.
Once a mod has more than a couple of scenes, the same code starts to repeat: a formula, a settings lookup, a helper that picks the right line. Put it in a library: a .lua file in your mod's Lua/ folder.
How libraries run
- Every
Lua/*.luafile of every enabled mod runs once, before the first scene of the session, in load order (and alphabetically within a mod). - They run in the same Lua world as scenes and UI scripts: a global a library defines is visible to every scene, every UI script and every other library that runs after it.
ModGameis available here: the same object scenes receive asGame. (Gameitself is only set while a scene runs.)
lua-- Lua/nightlife.lua
Nightlife = Nightlife or {}
Nightlife.DRINKS = {
{ id = "beer", price = 5, drunk = 8 },
{ id = "shot", price = 4, drunk = 15 },
}
function Nightlife.priceOf(id)
for _, d in ipairs(Nightlife.DRINKS) do
if d.id == id then return d.price end
end
return 0
end
And in any scene:
luaPlayer:modifyStat("money", -Nightlife.priceOf("shot"))
Rules for library code
One global table per mod. Put everything in a table named after your mod (Nightlife), never loose global functions. Other mods' code lives in the same world.
Write it so it can run twice. Start with Nightlife = Nightlife or {}, and build lists the same way (Nightlife.DRINKS = Nightlife.DRINKS or {...}) when other mods may have added to them. A library must not do anything when it loads (no stat changes, no messages): define things, nothing else.
Only .ulscene files are scenes: a library never shares a name with one.
No state in library locals. A local at the top of a library is shared by everyone and is not saved. Keep per-person data in person variables and world data in globals.
Adding methods to people
ActorMethods is a table the game consults when a person is asked for a method it does not have built in. Anything you add there becomes a method of every person: the player, NPCs, people other mods create.
lua-- Lua/nightlife.lua
local M = ActorMethods
function M.drunkenness(a)
return a:getStat("ynl_drunk") or 0
end
function M.drink(a, id)
for _, d in ipairs(Nightlife.DRINKS) do
if d.id == id then
a:modifyStat("ynl_drunk", d.drunk)
return true
end
end
return false
end
lua-- any scene, in any mod
if Actor:drunkenness() > 50 then
Game:Say(Actor, "Happy", "Heyyy, it's you!")
end
Player:drink("shot")
- The first argument (
aabove) is the person the method was called on. - Built-in methods always win: you can add methods, never replace
nameorgetStat. - Two mods adding the same method name overwrite each other (the later one wins), so prefix names that are not obviously yours.
Building an API other mods can use
The ul_Health and ul_Warrant modules are written as small public APIs: other mods say what happened, the module does the rest.
- Describe it in the header comment and in
MODULE_DESCRIPTION, so authors know what they can call. - Give people verbs, through
ActorMethods:Actor:infect("flu"),Player:crime(2). - Keep settings and registries on the global table:
Health.CONDITIONS,Health.addCondition{...}. - Let other mods extend your lists with an
add…function that replaces an entry with the same id instead of duplicating it. - Callers guard with
Game:isModEnabled, so their mod still runs without yours.
A worked example: ul_Health
ul_Health/Lua/health.lua (abridged):
luaHealth = Health or {}
-- A registry other mods extend with Health.addCondition{ id = "allergy", ... }
Health.CONDITIONS = Health.CONDITIONS or {
{ id = "cold", name = "Common cold", severity = 25, days = 3, random = 60 },
{ id = "flu", name = "Flu", severity = 60, days = 6, random = 20 },
}
function Health.addCondition(c)
for i, known in ipairs(Health.CONDITIONS) do
if known.id == c.id then Health.CONDITIONS[i] = c return end
end
table.insert(Health.CONDITIONS, c)
end
-- Settings in story globals: 0 = default, so an untouched save reads right
function Health.frequency()
local v = ModGame:getGlobal("ul_health_frequency")
return v == 0 and 1 or v
end
-- Verbs on every person; their state lives in their own vars, so it is saved with them
local M = ActorMethods
function M.isSick(a) return a:getVar("ul_health_condition") ~= nil end
function M.infect(a, id) --[[ … ]] end
function M.treat(a, kind) --[[ … ]] end
Another mod then writes, in a food-poisoning scene:
luaif Game:isModEnabled("ul_Health") then
Player:infect("food_poisoning")
end
Things that happen over time
Libraries are not called on a schedule: nothing tells them that a day started or an action finished. When something should change with time (an illness running its course, a wanted level cooling down), store the time it was last updated and catch up when you are next asked:
luafunction M.ynlSoberUp(a)
local now = ModGame:GetHoursPassed()
local last = a:getVar("ynl_drunk_checked") or now
local hours = now - last
if hours > 0 then
a:modifyStat("ynl_drunk", -5 * hours)
end
a:setVar("ynl_drunk_checked", now)
end
function M.drunkenness(a)
M.ynlSoberUp(a) -- every read is up to date
return a:getStat("ynl_drunk") or 0
end
This is what ul_Health's healthCheck does before every call. A HUD script's refresh also runs regularly while the map is on screen.
Event hooks for libraries (new day, action finished, scene ended, game loaded) do not exist yet. Until they do, the catch-up pattern above is the reliable way.