Modding

First mod

Folder

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

info.json 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."
}
FieldMeaning
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.

init.lua lua
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.

StageWhat 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:

folder
ChineseTranslation/
    info.json
    Loc/
        zh-CN/
            text.ini
info.json json
{
    "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."
}
Loc/zh-CN/text.ini ini
[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.

SystemLog
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.