Mods, manifest and settings¶
Mod folder¶
<Game>\H7ModEngine\mods\
MyMod\
mod.lua -- manifest (optional)
main.lua -- runs when the mod loads (required)
util.lua -- extra files, loaded with mod.require("util")
A folder without main.lua is listed but never loads (Mod X has no main.lua; skipped in the log). A folder
whose mod.lua raises an error is skipped entirely.
Manifest¶
mod.lua returns a table. Every field is optional.
return {
name = "MyMod",
version = "1.0",
author = "You",
description = "What the mod does.",
category = "Combat",
load_after = { "OtherMod" },
enabled = true,
hidden = false,
settings = { ... },
}
| Field | Type | Default | |
|---|---|---|---|
name |
string | folder name | The mod's name: in the log, the Mods menu, H7ModEngine.ini, data\<name>.lua and every h7.* function that takes a mod name. Keep it stable |
version |
string | "" |
Shown in the Mods menu and the log |
author |
string | "" |
Shown in the Mods menu and the log |
description |
string | "" |
Shown in the Mods menu |
category |
string | "" |
Shown, and filterable, in the Mods menu |
load_after |
{ string, ... } |
{} |
Mods (by name) this one must load after, when they are installed |
enabled |
boolean | true |
false: off until the player turns it on. The player's choice (stored in the ini) overrides this |
hidden |
boolean | false |
true: not listed in the Mods menu (libraries, the ModManager itself) |
settings |
{ setting, ... } |
{} |
Values players change in the Mods menu, see Settings |
The manifest runs once when mods are discovered (at startup and on h7.reload()), in the shared
environment, so keep it to a plain return { ... }.
Load order. Mods load alphabetically by name (case-insensitive), except that each mod is moved after every
installed mod it lists in load_after. A cycle is logged as load_after cycle among remaining mods and the mods
in it load alphabetically. After all enabled mods have loaded, mods_loaded fires.
Settings¶
Each entry of the manifest's settings list declares one value players can change in the Mods menu:
settings = {
{ key = "multiplier", label = "Damage multiplier", default = 1.25, min = 0.25, max = 4, step = 0.25,
description = "All combat damage is multiplied by this." },
{ key = "gold", label = "Gold per day", default = 500, min = 0, max = 100000, control = "input" },
{ key = "log", label = "Log hits", default = true },
{ key = "who", label = "Applies to", options = { "Human player", "Everyone" }, default = "Human player" },
{ key = "greeting", label = "Greeting", type = "text", default = "Hello" },
}
| Field | ||
|---|---|---|
key |
string, required | Name used in code (mod.settings.key) and in the ini. Entries without a key are ignored |
label |
string | Shown in the Mods menu. Default: key |
description |
string | Shown in the Mods menu |
type |
"bool" | "number" | "choice" | "text" |
Default: inferred, bool for a boolean default, number for a number, choice when options is given, otherwise text |
default |
value | Value until the player changes it. Missing or invalid: false, min (or 0), the first option, or "" |
min, max |
number | Numbers: allowed range (values are clamped) |
step |
number | Numbers: values are rounded to a multiple of step, counted from min (or 0) |
options |
{ value, ... } |
Choices: the allowed values, in the order the menu cycles through them. Required for choice |
control |
"slider" | "input" | "arrows" |
Numbers: how the menu shows it. Default: slider with both min and max, otherwise input |
reload |
boolean | true: the mod restarts when the value changes (for values only read at load time) |
Invalid declarations (unknown type, a choice without options) are logged and skipped.
Values. Booleans come back as true/false, numbers as integers when whole, choices and text as strings.
Choices compare case-insensitively and are stored with the spelling of options. Booleans also accept
"true"/"false", "yes"/"no", "on"/"off", 1/0. Values are stored in H7ModEngine.ini, section
[Mod.<name>]; an invalid value there is logged and replaced by the default.
In the Mods menu numbers get a slider, a number box (Enter saves, Escape cancels) or arrows, booleans a check box, and choices arrows. Text settings are shown but can only be changed in code or in the ini. The panel shows up to five settings; further ones are listed as being in the ini.
The mod table¶
Every mod has a global mod table describing itself:
| Field | |
|---|---|
mod.name |
The mod's name (manifest name or folder name) |
mod.version |
Manifest version (or "") |
mod.author |
Manifest author (or "") |
mod.dir |
Full path of the mod folder, e.g. C:\...\H7ModEngine\mods\MyMod |
mod.require(name) |
See below |
mod.save(table), mod.load() |
See below |
mod.settings |
See below |
mod.require¶
Runs <mod folder>\<name>.lua in the mod's environment (it sees and sets the same globals as main.lua) and
returns what the file returns, or true if it returns nothing. The file runs once per load of the mod; later calls
return the cached result. name is a path relative to the mod folder without .lua: "util",
"lib/strings".
Errors: the file is missing or has a syntax error; any error it raises.
-- util.lua
local M = {}
function M.round(x) return math.floor(x + 0.5) end
return M
-- main.lua
local util = mod.require("util")
mod.save¶
Writes table to <Game>\H7ModEngine\data\<mod name>.lua, replacing what was saved before. Numbers, strings,
booleans and nested tables are kept; other values (functions, engine objects) become nil. Keys must be strings,
numbers or booleans. The data belongs to the mod, not to a save game.
Errors: table too deep to save (more than 32 levels, or a table that contains itself); could not write save
data.
mod.load¶
The table last saved with mod.save, or an empty table if there is none. The data file is read
without access to any functions.
mod.settings¶
The mod's settings. Reading gives the current value (nil for an unknown key); read it when you need
it rather than copying it at load time, since players can change it at any time. Assigning works like
h7.set_mod_setting: the value is validated and saved and
setting_changed fires. pairs(mod.settings) iterates key, value.
Errors (assigning): mod <name> has no setting '<key>'; invalid value for <name>.<key> (<type>).
h7.hook_post("MMH7Game.H7CombatArmy.GetKilledCreaturesXP", function(army, args, xp)
return math.floor(xp * mod.settings.multiplier + 0.5)
end)
Globals¶
Each mod has its own global environment. Globals a mod assigns stay inside it; reading a global it didn't assign
falls through to the shared environment (h7, string, math, ...). Two mods can both define the same global
names without clashing. _G still refers to the shared environment.
on_unload¶
If a mod defines a global function on_unload, it is called before the mod unloads (when it's disabled, hot
reloaded, or on h7.reload()). Use it to undo changes the game won't undo by itself, such as
property values you changed. Hooks, event handlers and timers the mod registered are removed after it returns;
you don't need to remove them yourself.
Loading, hot reload and errors¶
- Loading runs
main.luain the mod's environment. An error in it is logged (ERROR in MyMod/main.lua: ...) with a stack trace; whatever the mod registered before the error stays active. - About once a second, the engine checks each loaded mod's folder (including subfolders) for changed
.luafiles. When one changed, the mod is unloaded and loaded again. Fixing an error and saving the file therefore retries the mod. - New mod folders and mods turned on in the ini need
h7.reload()or a restart; mods turned on in the Mods menu or withh7.set_mod_enabledload on the next frame. - Errors in hooks, event handlers and timers are logged with the mod's name and a stack trace; the hook, handler or timer stays registered.
Shared libraries¶
The standard require(name) loads <Game>\H7ModEngine\lib\<name>.lua; as usual in Lua, dots in name are
folders (require("ui.list") loads lib\ui\list.lua). The library runs once, in the shared environment, and its result is shared by every mod. Use it for code
several mods share; use mod.require for a mod's own files.