# Developing for Clusterio This document describes the various different types of content that can be created for Factorio and/or Clusterio and how this is made compatible with Clusterio. ## Contents - [Factorio Mods](#factorio-mods) - [Factorio Scenarios](#factorio-scenarios) - [event_handler interface](#event_handler-interface) - [Communicating with Clusterio](#communicating-with-clusterio) - [Clusterio Modules](#clusterio-modules) - [Initialisation and recovery](#initialisation-and-recovery) - [Clusterio Lua API](#clusterio-lua-api) - [Compatibility library](#compatibility-library) - [Clusterio Plugins](#clusterio-plugins) ## Factorio Mods Factorio mods that do not interact with Clusterio should not have any considerations that need to be taken into account for them to work with Clusterio. ## Factorio Scenarios Clusterio patches saves and because of this scenarios have a few limitations and must explicitly mark themselves as compatible with Clusterio to work. The main limitation is that the [event_handler](https://github.com/wube/factorio-data/blob/master/core/lualib/event_handler.lua) lib must be used, and control.lua cannot contain any code other than calls to event_handler loading the relevant libs of the scenario. This is because the control.lua file will be overwritten by the save patcher. Scenarios also cannot use the event registering functions exposed by `script` such as `script.on_event` or `script.on_nth_tick` as using these will overwrite the handlers registered by the event_handler lib. A brief description of the usage of the event_handler library is provided in the [event_handler interface](#event_handler-interface) section. Once a scenario is made using the event_handler it should have a control.lua file that looks something like this: ```lua -- control.lua local event_handler = require("event_handler") event_handler.add_lib(require("module_foo")) event_handler.add_lib(require("module_bar")) ``` To make Clusterio recognize and correctly handle this scenario it needs to include a clusterio.json file with following content: ```json { "scenario": { "name": "example-scenario", "modules": [ "module_foo", "module_bar" ] } } ``` This tells Clusterio that it should load both module_foo and module_bar by passing the result of requiring them to the `add_lib` function when writing a new control.lua file to the save. ### event_handler interface **Note:** At the time of writing the event_handler lib has been a part of Factorio since at least 0.17.4, and was moved from base to core in 0.17.63 but is still not documented anywhere. A brief description is provided here for this reason. The event_handler lib provides a simple interface for registering multiple callbacks to the same events without having to be concerned with callback chaining or overwriting callbacks defined elsewhere. It works by taking over the task of registering the actual callbacks with Factorio, providing its own interface for the rest of the code to use. The sole function of interest exported by event_handler is `add_lib` which accepts a table with event handler callbacks definitions that it will register. The following entries are recognized by `add_lib`, all of which are optional: - `on_init`: Callback called when the callback of `script.on_init` is invoked. - `on_load`: Callback called when the callback of `script.on_load` is invoked. - `on_configuration_changed`: Callback called when the callback of `script.on_configuration_changed` is invoked. - `events`: Table mapping event ids as defined in `defines.events` to callbacks. For example the table `{ [defines.events.on_player_died] = foo }` will call foo with the usual `event` table as argument every time a player dies. - `on_nth_tick`: Table mapping the nth tick number to a callback. For example the table `{ [30] = bar }` will call bar every 30th tick. - `add_remote_interface`: Callback called before on_init/on_load callbacks[1]. It has no special meaning and receives no arguments but should be used for registering remote interfaces. - `add_commands`: Callback called before on_init/on_load callbacks[1]. It has no special meaning and receives no arguments but should be used for registering commands. 1: Before 0.17.69 these callbacks were called after on_init/on_load. The usual way to use `add_lib` is to define the table of events to register in a separate file and return it, then load it in control.lua via `require` before passing it to `add_lib`. For example a module could be defined as ```lua -- example.lua local function my_handler(event) local name = game.get_player(event.player_index).name game.print("It appears that " .. name .. " has died") end return { on_init = function() game.print("example_lib init") end, events = { [defines.events.on_player_died] = my_handler, }, } ``` And then in control.lua the following used to load it with `add_lib`: ```lua -- control.lua local event_handler = require("event_handler") event_handler.add_lib(require("example")) ``` Because the event_handler lib registers events itself you may not use `script.on_load` or `script.on_init` at all in your code and any usage of `script.on_configuration_changed`, `script.on_nth_tick` and/or `script.on_event` will cause the corresponding events registered with event_handler to break and should therefore not be used. There's also the `add_libraries` function exported by event_handler, which accepts a table and calls `add_lib` for each value in the table. ## Communicating with Clusterio Clusterio uses a homebrew protocol based on sending JSON payloads over a WebSocket connection. See [protocol.md](devs/protocol.md) for the implementation details of it. It's also possible to write a plugin for Clusterio that exposes a custom interface over HTTP, WebSocket or any other technology supported by Node.js. ## Clusterio Modules Modules are primarily used by plugins to inject code into Factorio games with the save patcher, though it's also possible to make stand alone modules that are loaded into the game if you don't need the capabilites of the plugin system. The save patcher puts modules into the `modules` folder of the Factorio save and adds code to `control.lua` to load the module according to the `load` and `require` options to the module.json file. Like with scenarios for Clusterio the [event_handler interface](#event_handler-interface) has to be used for any event subscriptions in modules. Stand alone modules are placed into the modules folder of Clusterio, plugin modules are located in the module folder of the plugin. In either case a `module.json` file is required and has the following structure: ```json { "name": "my_module", "version": "1.2.0", "dependencies": { "clusterio": "*", "foo": ">=0.4.2" }, "require": ["bar.lua"], "load": ["foo.lua"] } ``` The following entries are supported in the module.json file: - `name`: Name of the module, must match the folder the module is located in for stand alone modules or the name of the plugin the module is located in. - `version`: Version of the module. Must be compatible with [Semantic Versioning 2.0.0](https://semver.org/). In plugin modules this defaults to the version of the plugin otherwise it's required. - `dependencies`: Optional mapping of modules this module depends on and their version. In the example `>=0.4.2` means that the foo module must be at least version 0.4.2. See the [Ranges syntax](https://www.npmjs.com/package/semver#ranges) of the node-semver package for a full description of what operators are supported. The events for dependencies is invoked before the events of the dependent, and starting an instance will fail if the dependencies cannot be satisfied. If not specified it will default to depending on `clusterio`. Make sure to add `clusterio` to your dependencies if you add other dependencies and depend on the Lua API. - `require`: Lua files to require in `control.lua`. This should only really be needed if you want to make a global function available for use in Lua commands. The paths are relative to the module's own folder and should be specified using forward slashes as directory sepparators if it's located in a sub directory in the module. - `load`: Lua files to load with the `event_handler` lib, the result of requiring the file will be passed to the `add_lib` function of the `event_handler` lib in `control.lua`. See the section on the [event_handler interface](#event_handler-interface) for a detailed description on how the `event_handler` lib works. The paths are relative to the module's own folder and should be specified using forward slashes as directory sepparators if it's located in a sub directory in the module. It's recommended to use the new style of defining modules in Lua, as well as avoid the use of global variables as much as possible. This means always declearing your top level variables and functions local and exporting only the things you need in other files. You can require other files in your module by prefixing your require paths with `modules/your_module_name/`. It's also possible to require files from other modules this way too. Persistent state is shared by all save-patched modules and the scenario. Use `compat.script_data` to access the version-appropriate persistent table (`global` or `storage`), and keep your state under a unique module name. See the [compatibility library](#compatibility-library) below. ### Initialisation and recovery Because modules can be patched into an existing game you cannot rely on the `on_init` callback to be called in Clusterio Modules. Nor can you rely on the `on_configuration_changed` callback, as this is not called when level code changes. The Clusterio Lua API provides `on_server_startup` for setup and migrations, but some Factorio events can run before it. In particular, player-leaving, console-command, and configuration-change handlers must not assume startup has initialized their state. Use an idempotent state accessor in handlers that can run early. It should migrate any legacy namespace before creating fresh state, fill missing fields, and return the same state on repeated calls. A guard that returns early because the new namespace is absent can silently discard recovery work on an older save. See [player_sync's state accessor](../plugins/player_sync/module/get_script_data.lua) for an implementation that handles a renamed namespace and [the module template](../packages/create/templates/common/module/control.lua) for lifecycle setup. Use `on_load` only to restore local references and other operations permitted by Factorio's load lifecycle; do not create or migrate persistent state there. Perform state changes in startup or other events that permit them. Recovery mode skips normal Node plugin loading, so save data may still use an older plugin namespace when Lua handlers run. Test old and current saves, early events, repeated access, and save/load behavior on the relevant supported Factorio versions. ## Clusterio Lua API Clusterio provides a save patched module as well as a regular Factorio mod for interfacing with Clusterio. This includes tools sending data to plugins, script events to listen for, and an item serialization module. ### Compatibility library Use the compatibility helpers for APIs that differ between supported Factorio versions: ```lua -- In a save-patched module: local compat = require("modules/clusterio/compat") -- In a mod, use require("__clusterio_lib__/compat") instead. local function encode_example() local script_data = compat.script_data local encoded = compat.table_to_json({ example = true }) local decoded = compat.json_to_table(encoded) end ``` `script_data` selects the persistent state table, while the JSON helpers select the available runtime API. Access persistent state from the appropriate lifecycle callbacks, after initialisation or loading has made it available. The JSON helpers also work before runtime; other compatibility helpers may require `game` to be available. Legacy examples using `global` or `game.table_to_json` directly are version-specific. See [compat.lua](../packages/host/modules/clusterio/compat.lua) and the [Factorio support policy](decisions.md#factorio) before introducing version checks. ### clusterio_api library **require path** From a module `local clusterio_api = require("modules/clusterio/api")` From a mod `local clusterio_api = require("__clusterio_lib__/api")` Provides the main interface to Clusterio from within the game. When using the mod version you must call `init` before it'll function properly. #### init Mod version only Initialize the API. This should be called from both the `on_init` and `on_load` events in your mod, and before this is called certain features will not be available, most notably the events table. #### clusterio_api.events Table of events raised by clusterio. If the game is started outside of Clusterio then none of these events will be raised. In the mod version this table does not exist before the `init` function is called. ##### on_instance_updated Raised after the name and id of an instance has been updated. This may occur even if the id and name didn't change. Event data: - `instance_id`: the id of the instance. - `instance_name`: the name of the instance. ##### on_server_startup Raised when Clusterio is starting up the server. It can be used to initialize clusterio related features in a mod, and as a stand-in for the `on_init` and `on_configuration_changed` event for modules. It is invoked on the first tick the server runs after the save has been patched, before most other events but not all of them. See [initialisation and recovery](#initialisation-and-recovery) for handlers that can run earlier. Use this event to initialize and/or migrate the data structures you need. Keep in mind that both mods and Clusterio modules can switch from any version to any version so it should be able to handle both forwards and backwards migrations gracefully. #### clusterio_api.send_json(channel, data) Send JSON data to Clusterio over the given channel. The `data` argument should be a table that can be serialized with `compat.table_to_json`. Clusterio plugins can listen to channels and will receive an event with the data sent here. See the [Communicating with Factorio section](writing-plugins.md#communicating-with-factorio) in the Writing Plugins document for more information. If the game is started outside of Clusterio the data will be sent, but since there's no code following stdout to pick it up it will be lost. **Note**: Payloads greater than 4 MB will cause stuttering in the game. **Note**: This is not a binary safe way of sending data. Strings embedded into the tables sent must be valid UTF-8 text. Parameters: - `channel`: string identifying which channel to send it on. - `data`: table that can be converted to JSON with `compat.table_to_json` #### clusterio_api.send_udp(channel, data) Send data to Clusterio over the given channel using UDP instead of stdout. Clusterio plugins receive it as `udp-channel_name` events, see the [Communicating over UDP section](writing-plugins.md#communicating-over-udp) in the Writing Plugins document. Requires `factorio.enable_lua_udp` to be enabled on the instance and Factorio 2.1.10 or later, otherwise the data is silently dropped. The port to send to is passed to the module with a script command on startup, so `factorio.enable_script_commands` must be enabled as well. **Note**: Delivery is not guaranteed and payloads should be kept well below 32 kB. Parameters: - `channel`: string identifying which channel to send it on. - `data`: string with the payload to send, any bytes are allowed. ### serialize library **require path** From a module `local serialize = require("modules/clusterio/serialize")` From a mod `local serialize = require("__clusterio_lib__/serialize")` Serializes inventories and item stacks into a table that in turn can be converted into JSON with `compat.table_to_json`. Properly handles the extra data for most items. #### serialize.serialize_equipment_grid(LuaEquipmentGrid) Returns a table containing a serialized representation of the equipment grid passed. #### serialize.deserialize_equipment_grid(LuaEquipmentGrid, serialized_grid) Deserialize a previously serialized equipment grid into the target LuaEquipmentGrid. Overwrites all content in the target. #### serialize.serialize_item_stack(LuaItemStack, destination_table) Serialize the LuaItemStack passed into the target destination_table. The destination_table should be a new empty table. #### serialize.deserialize_item_stack(LuaItemStack, serialized_stack) Deserialize a previously serialized item stack into the target LuaItemStack. Overwrites the content of the item stack. #### serialize.serialize_inventory(LuaInventory) Returns a table containing a serialized representation of the inventory passed. #### serialize.deserialize_inventory(LuaInventory, serialized_inventory) Deserialize a previously serialized inventory into the target LuaInventory. Overwrites slots that have content in the serialized inventory. ## Clusterio Plugins See the [Writing Plugins](writing-plugins.md) document for details on the plugin interface and how to write plugins for Clusterio.