First mod
Folder
DrillingRigSpeed/
info.json
init.lua The folder name is free: the mod is identified by name in the manifest. The game runs init.lua. Load your other Lua from it with require.
info.json
{
"name": "DrillingRigSpeed",
"version": "1.0",
"title": "Drilling Rig Speed",
"author": "your name",
"evospace_version": "0.20.1",
"dependencies": ["Vanilla>=0.20.1", "VanillaLib>=0.20.1"],
"description": "Halves the extraction time of a drilling rig with every level."
} | Field | Meaning |
|---|---|
| name | Mod identity. The only required field. A second mod with the same name is refused. |
| version | Mod version. Other mods test it in their dependencies. |
| title | Name in the mod list. |
| author | Author in the mod list. |
| evospace_version | Game version the mod was written for. |
| dependencies | Mod names with an optional version test: >=, <=, =, >, <. Prefix ? for optional, ! for incompatible. |
| description | Description in the mod list. |
Dependencies set the load order and what require can reach. The example depends on VanillaLib to read vlib, and on Vanilla to change blocks that Vanilla registers.
init.lua
The example halves a drilling rig's extraction time with every level. Vanilla keeps one time for all tiers.
local Vlib = require('vlib')
local DrillingRigSpeed = {}
local function ticks_for(level)
return math.floor(Vlib.mining.drill.ticks / 2 ^ level + 0.5)
end
function DrillingRigSpeed.post_init()
for _, proto in pairs(db:objects()) do
local block = StaticBlock.cast(proto)
local rig = block ~= nil and block.logic ~= nil and block.logic:is_child_of(DrillingRig.get_class())
local base = rig and block.logic_init or nil
if base ~= nil then
block.logic_init = function(self)
base(self)
local drill = DrillingMachineBase.cast(self)
drill.ticks_per_item = ticks_for(drill.static_block.level)
end
print('DrillingRigSpeed: ' .. proto.name .. ' -> ' .. ticks_for(block.level) .. ' ticks per item')
end
end
end
db:mod(DrillingRigSpeed) Loading stages
Every block, item, recipe and research is a prototype in one database. db:mod takes a table with up to three functions that change it. Mods run in dependency order: all pre_init, then all init, then all post_init.
| Stage | What exists |
|---|---|
| pre_init | Runs before prototypes are read. Register settings here. |
| init | Register your own blocks, items, recipes, research and structures. Prototypes of mods you depend on already exist. |
| post_init | All prototypes of all mods are registered. Change prototypes of other mods here. |
Example, step by step
- db:objects() returns every prototype. StaticBlock.cast returns nil for prototypes that are not blocks.
- block.logic is the class that simulates the block. is_child_of matches every tier of drilling rig, and only those: the pumpjack is a drilling machine of another class.
- A block prototype carries three hooks: block.logic_init sets up the simulated block, block.actor_init its actor, block.tooltip its description.
- The old logic_init is called first, so vanilla builds the machine with its inventories and accessors. The mod then overwrites one field.
- logic_init runs once per placed block. self is the block, self.static_block is its prototype, and static_block.level is the tier level.
Text and translations
Strings live in .ini files in Loc/<language>/. The language codes are en, de-DE, es-ES, fr-FR, hu-HU, ja-JP, ko-KR, pl-PL, pt-BR, ru-RU, zh-CN. The game reads every .ini file in the folder; file names do not matter. A [section] line starts a table, and each Key=Text line below it adds one string. In Lua, Loc.new("Key", "section") refers to a string.
A translation mod needs no init.lua:
ChineseTranslation/
info.json
Loc/
zh-CN/
text.ini {
"name": "ChineseTranslation",
"version": "1.0",
"title": "简体中文",
"author": "your name",
"evospace_version": "0.22.1",
"dependencies": ["VanillaQuests", "VanillaTips"],
"description": "Simplified Chinese for the game, its quests and its tips."
} [machines]
Furnace=加热器
[quests]
Welcome=欢迎来到 Evospace - The game loads its own strings first, then the strings of each mod in dependency order. A later string with the same section and key replaces an earlier one.
- Strings of the selected language are loaded after the English ones. A key with no translation shows the English text. A mod's English string does not replace the game's translation.
- Quest and tip strings belong to the VanillaQuests and VanillaTips mods. List these mods in dependencies so the translation loads after them; otherwise their strings may replace yours. The rest of the game's strings need no dependency.
Install
Put the folder in Content/Mods inside the game folder, next to Vanilla. The game reads it at startup and names it in the log.
Logs
Mods load once at startup. Restart the game after an edit. Load errors and print output go to the log; print_warn and print_err write at the warning and error levels. The previous run is EvospaceGameplay.log.1.
| System | Log |
|---|---|
| Windows | %LOCALAPPDATA%\Evospace\Saved\Logs\EvospaceGameplay.log |
| macOS | ~/Library/Application Support/Epic/Evospace/Saved/Logs/EvospaceGameplay.log |
Common failures, each logged by name: info.json without name; a dependency that is missing or of the wrong version; require for a module of a mod that is not a declared dependency; a hook that is not a function.
API reference
Classes, fields and functions are listed in the Lua API reference. The same list ships as type definitions in Evospace/lua-api: clone it and set Lua.workspace.library to its library folder for completion and signatures.
Content/Mods/Vanilla/Blocks holds one file per machine, each of them a logic_init. The prototypes — items, blocks, recipes, research — are JSON in Content/Generated, read before any mod runs.
Publishing
Main menu, then Mods: point the form at the mod folder and Steam makes the Workshop entry. Uploading again replaces everything the Workshop holds for that mod. A subscriber gets the mod after restarting the game.