diff --git a/.gitignore b/.gitignore index 687b2488..cca9e56a 100644 --- a/.gitignore +++ b/.gitignore @@ -56,8 +56,6 @@ sharedPlugins/* sharedPlugins/serverManager/database/* sharedPlugins/serverManager/config.json sharedPlugins/serverManager/secret-api-token.txt -!sharedPlugins/scenario_loader/ -sharedPlugins/scenario_loader/scenarios !sharedPlugins/statisticsExporter/ # File used for marking plugins as disabled diff --git a/CHANGELOG.md b/CHANGELOG.md index f77e9fb0..9cc793b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,17 @@ Changelog Version 2.0.0 ------------- +### Major Features + +- Replaced Hotpatch with save patching. Removed the Hotpatch scenario and + the depency on it for getting code into the game. Added a save patcher than + runs before starting up Factorio that patches in lua modules based on the + event_loader lib into the savegame. Regular freeplay games can now be + used with Clusterio and will be compatible without having to convert them + to Hotpatch. + +### Changes + - Fixed sslCert and sslPrivKey entries being ignored in the config. - Changed ssl key creation to be done at startup instead of on npm install. - Added error handling during master startup. @@ -38,6 +49,8 @@ Version 2.0.0 - Mods are no longer copied from the per instance instanceMods directory. If you need per instance mods you can now place them directly in the mods directory inside the instance folder. +- Hotpatch scenarios and code loading is no longer compatible with Clusterio. + Breaks playerManager, serverSelect, and tranTeleports. Version 1.2.2 diff --git a/client.js b/client.js index d4dd56db..147fe83c 100644 --- a/client.js +++ b/client.js @@ -296,10 +296,6 @@ async function createInstance(config, args, instance) { await fs.ensureDir(instance.path()); await fs.ensureDir(instance.path("script-output")); - let target = path.join("lib", "scenarios"); - let link = instance.path("scenarios"); - await fs.symlink(path.relative(path.dirname(link), target), link, 'junction'); - await symlinkMods(instance, "sharedMods", console); let instconf = { "id": Math.random() * 2 ** 31 | 0, @@ -356,29 +352,7 @@ async function createInstance(config, args, instance) { console.log("Fact: " + output.message); }); - server.on('rcon-ready', function() { - console.log("Clusterio | RCON connection established"); - }); - - let waitUntilSaved = new Promise((resolve, reject) => { - let checkSaved = (output) => { - if (output.type === 'log' && output.message === "Saving finished") { - resolve(); - server.off('output', checkSaved); - } else if (output.message === "Goodbye") { - reject(new Error("Server failed to save")); - server.off('output', checkSaved); - } - }; - - server.on('output', checkSaved); - }); - - await server.startScenario("Hotpatch"); - await server.sendRcon("/c game.server_save('hotpatchSave')"); - await waitUntilSaved; - - await server.stop(); + await server.create("world"); console.log("Clusterio | Successfully created instance"); } @@ -430,6 +404,41 @@ async function startInstance(config, args, instance) { ); } + // Patch save with lua modules from plugins + console.log("Clusterio | Patching save"); + + // For now it's assumed that all files in the lua folder of a plugin is + // to be patched in under the name of the plugin and loaded for all + // plugins that are not disabled. This will most likely change in the + // future when the plugin refactor is done. + let modules = []; + for (let pluginName of await fs.readdir("sharedPlugins")) { + let pluginDir = path.join("sharedPlugins", pluginName); + if (await fs.pathExists(path.join(pluginDir, "DISABLED"))) { + continue; + } + + if (!await fs.pathExists(path.join(pluginDir, "lua"))) { + continue; + } + + let module = { + "name": pluginName, + "files": [], + }; + + for (let fileName of await fs.readdir(path.join(pluginDir, "lua"))) { + module["files"].push({ + path: pluginName+"/"+fileName, + content: await fs.readFile(path.join(pluginDir, "lua", fileName)), + load: true, + }); + } + + modules.push(module); + } + await factorio.patch(instance.path("saves", latestSave), modules); + let options = { gamePort: args.port || Number(process.env.FACTORIOPORT) || instanceconfig.factorioPort, rconPort: args["rcon-port"] || Number(process.env.RCONPORT) || instanceconfig.clientPort, diff --git a/docs/developing-for-clusterio.md b/docs/developing-for-clusterio.md new file mode 100644 index 00000000..5cf4958f --- /dev/null +++ b/docs/developing-for-clusterio.md @@ -0,0 +1,209 @@ +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 between Clusterio and Factorio](#communicating-between-clusterio-and-factorio) +- [Communicating with Clusterio](#communicating-with-clusterio) +- [Clusterio Modules](#clusterio-modules) +- [Clusterio Plugins](#clusterio-plugins) + + +Factorio Mods +------------- + +Factorio mods that do not interact with Clusterio should not have any +conciderations 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 registring 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 breif 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 registring +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 registring 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 bar 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 registring 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 registring 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.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 between Clusterio and Factorio +-------------------------------------------- + +There is currently no standard interface for moving data between +Clusterio and Factorio. It's mostly a mix of RCON commands initiated +from Clusterio plugins and writing out files with `game.write_file` that +is picked up and parsed by plugins. + + +Communicating with Clusterio +---------------------------- + +There's a poorly to virtuarly undocumented HTTP interface that can be +used to communicate with Clusterio to do things like get the status of +instances, grab items from the store, run commands on instances, etc, +etc. For now you can read the source code, and maybe ask on the Discord +for some pointers if you get stuck. + + +Clusterio Modules +----------------- + +Modules are primarily used by plugins to inject code into Factorio +games with the save patcher. The save patcher puts modules into the +`modules` folder of the Factorio save and adds code to `control.lua` to +load the modules by requiring it and passing the result of the require +call to the `add_lib` function of the event_handler lib. See the +section on the [event_handler interface](#event_handler-interface) for a +detailed description on how this works. + +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. Instead you will have to initialize whatever +global variable you need when you first use them. + +Currently any files in a folder named `lua` in a Clusterio plugin is +assumed to be a module and will be patched into the save before starting +up Factorio. This will most likely change with the planned plugin +restructuring. + + +Clusterio Plugins +----------------- + +The plugin interface is undergoing a major rewrite, and will be +documented once that is done. See +[issue #220](https://github.com/clusterio/factorioClusterio/issues/220) +for details. diff --git a/docs/how-it-works.md b/docs/how-it-works.md new file mode 100644 index 00000000..27cf549c --- /dev/null +++ b/docs/how-it-works.md @@ -0,0 +1,15 @@ +Save Patching +------------- + +In order to efficiently get data in and out of running Factorio servers +and to provide custom interfaces Clusterio uses save +patching[1] to add code to the game. The save patching is +based on the built in +[event_handler](https://github.com/wube/factorio-data/blob/master/core/lualib/event_handler.lua) +library to Factorio and requires scenarios to be coded specifically for +it. Mods on the other hand work without modifications. + +1: Before version 2.0 of Clusterio this was done at runtime +with a patcher called Hotpatch, but due to the difficulty in supporting +run time patching and the lack of documentation it was replace it with +save patching instead. diff --git a/lib/clusterTools/checkHotpatchInstallation.js b/lib/clusterTools/checkHotpatchInstallation.js deleted file mode 100644 index 4ffb825f..00000000 --- a/lib/clusterTools/checkHotpatchInstallation.js +++ /dev/null @@ -1,9 +0,0 @@ -module.exports = async function checkHotpatchInstallation(messageInterface){ - let yn = await messageInterface("/silent-command if remote.interfaces['hotpatch'] then rcon.print('true') else rcon.print('false') end"); - yn = yn.replace(/(\r\n\t|\n|\r\t)/gm, ""); - if(yn == "true"){ - return true; - } else if(yn == "false"){ - return false; - } -} diff --git a/lib/clusterTools/index.js b/lib/clusterTools/index.js index 40475890..b0dd54ef 100644 --- a/lib/clusterTools/index.js +++ b/lib/clusterTools/index.js @@ -2,7 +2,6 @@ Functions frequently needed from different places in the cluster. Ex: * Loading Lua scripts - * Checking if hotpatch is installed * Getting an instance's displayname from their ID */ @@ -16,13 +15,6 @@ module.exports = ({config} = {}) => ({ Promise resolves with a string of Lua. */ getLua: require("./getLua.js"), - /** - async function to check whether hotpatch is installed in a server. - - First argument is the servers async messageInterface for rcon return values - Promise resolves with boolean. - */ - checkHotpatchInstallation: require("./checkHotpatchInstallation.js"), /** async function to get an instances displayname from their instanceID diff --git a/lib/factorio/index.js b/lib/factorio/index.js index f682bbd2..1e03bc70 100644 --- a/lib/factorio/index.js +++ b/lib/factorio/index.js @@ -1,4 +1,5 @@ module.exports = { ...require('lib/factorio/locale'), + ...require('lib/factorio/patch'), ...require('lib/factorio/server'), }; diff --git a/lib/factorio/patch.js b/lib/factorio/patch.js new file mode 100644 index 00000000..235d53b5 --- /dev/null +++ b/lib/factorio/patch.js @@ -0,0 +1,172 @@ +/** + * Library for patching Factorio saves with scenario code. + * @author Hornwitser + */ + +"use strict"; +const events = require("events"); +const fs = require("fs-extra"); +const jszip = require("jszip"); + +const hash = require("lib/hash"); + + +const knownScenarios = { + // First seen in 0.17.63 + "4e866186ebe297f1038fd325b09df1a1f5e2fdd1": { + "name": "freeplay", + "modules": [ + "freeplay", + "silo-script", + ], + }, +}; + +/** + * Returns the root folder in the zip file + * + * Returns the name of the folder that all files in the zip file is + * contained in. Throws an error if there are multiple such folders. + * + * @returns {string} name of the root folder. + */ +function findRoot(zip) { + let root = null; + zip.forEach((relativePath, file) => { + let index = relativePath.indexOf("/"); + if (index === -1) { + throw new Error(`Zip contains file '${relativePath}' in root dir`); + } + + let pathRoot = relativePath.slice(0, index); + if (root === null) { + root = pathRoot; + } else if (root !== pathRoot) { + throw new Error(`Zip contains multiple root folders`); + } + }); + + if (root === null) { + throw new Error(`Empty zip file`); + } + + return root; +} + + +/** + * Generates control.lua code for loading the Clusterio modules + * + * @param {Object} patchInfo - The patch info files's json content + */ +function generateLoader(patchInfo) { + let lines = [ + '-- Auto generated scenario module loader created by Clusterio', + '-- Modifications to this file will be lost when loaded in Clusterio', + '', + 'local event_handler = require("event_handler")', + '', + '-- Scenario modules', + ]; + + for (let moduleName of patchInfo["scenario"]["modules"]) { + lines.push('event_handler.add_lib(require("'+moduleName+'"))'); + } + + lines.push(...[ + '', + '-- Clusterio modules', + ]); + + for (let module of patchInfo["modules"]) { + for (let file of module["files"]) { + if (file["load"]) { + let requirePath = "modules/" + file["path"].slice(0, -4); + lines.push('event_handler.add_lib(require("'+requirePath+'"))'); + } + } + } + + // End last line with a newline + lines.push(''); + + return lines.join('\n'); +} + +/** + * Patch a save with the given modules + * + * Adds the modules given by the modules parameter to the save located + * at savePath and rewrites the control.lua in the save to load the + * modules that were added. Will also remove any previous module + * located in the save. + * + * @param {string} savePath - Path to the Factorio save to patch. + * @param {Array} modules - Description of the modules to patch. + */ +async function patch(savePath, modules) { + let zip = await jszip.loadAsync(await fs.readFile(savePath)); + let root = zip.folder(findRoot(zip)); + + let patchInfoFile = root.file("clusterio.json"); + let patchInfo; + if (patchInfoFile !== null) { + let content = await patchInfoFile.async("string"); + patchInfo = JSON.parse(content); + + // No info file present, try to detect if it's a known compatible scenario. + } else { + let controlStream = root.file("control.lua").nodeStream("nodebuffer"); + let controlHash = await hash.hashStream(controlStream); + + if (controlHash in knownScenarios) { + patchInfo = { + "scenario": knownScenarios[controlHash], + }; + } else { + throw new Error(`Unable to patch save, unknown scenario (${controlHash})`); + } + } + + // Remove any existing modules from the save + patchInfo["modules"] = []; + for (let file of root.file(/^modules\//)) { + zip.remove(file.name); + } + + // Add the modules to the save. + let modulesDir = root.folder("modules"); + for (let module of modules) { + let moduleInfo = { + "name": module.name, + "files": [], + } + + for (let file of module.files) { + modulesDir.file(file.path, file.content); + moduleInfo["files"].push({ + "path": file.path, + "load": Boolean(file.load), + }); + } + + patchInfo["modules"].push(moduleInfo); + } + + // Add loading code and patch info + root.file("control.lua", generateLoader(patchInfo)); + root.file("clusterio.json", JSON.stringify(patchInfo, null, 4)); + + // Write back the save + let stream = zip.generateNodeStream({compression: "DEFLATE"}); + let pipe = stream.pipe(fs.createWriteStream(savePath)); + await events.once(pipe, "finish"); +} + +module.exports = { + patch, + + // For testing only + _findRoot: findRoot, + _generateLoader: generateLoader, +} diff --git a/lib/scenarios/Hotpatch/control.lua b/lib/scenarios/Hotpatch/control.lua deleted file mode 100644 index de8e4b27..00000000 --- a/lib/scenarios/Hotpatch/control.lua +++ /dev/null @@ -1,154 +0,0 @@ ---[[ - -Copyright 2018 Chrisgbk -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -]] --- MIT License, https://opensource.org/licenses/MIT - --- Hotpatchable control.lua for scenarios --- Supports multiple simultaneously loaded softmods --- This is a WIP --- Version 1.1 alpha --- probably some performance improvements to be made - -_ENV.debug_settings = { - level = 'info', - log_to_console_only = false, - log_to_RCON = false, - log_on_tick = false, -} - -local hotpatch_tools = require 'hotpatch.mod-tools' -require 'hotpatch.remote-interface' -require 'hotpatch.commands' -require 'hotpatch.gui' - --- mod code goes here - --- This is the Factorio Freeplay scenario --- This code, and the freeplay locale files in the zip file, is the property of Wube --- As this is part of the base mod, the developers have given permission for modders to use/adapt the assets therein, for modding Factorio --- Any other use would require licensing/permission from Wube --- If you don't want to run this, either comment this out before creating your scenario, OR uninstall freeplay at runtime -hotpatch_tools.static_mod('freeplay', '0.17.50', [===[ -local util = require("util") -local silo_script = require("silo-script") - -local created_items = function() - return - { - ["iron-plate"] = 8, - ["wood"] = 1, - ["pistol"] = 1, - ["firearm-magazine"] = 10, - ["burner-mining-drill"] = 1, - ["stone-furnace"] = 1 - } -end - -local respawn_items = function() - return - { - ["pistol"] = 1, - ["firearm-magazine"] = 10 - } -end - -for k,v in pairs(silo_script.get_events()) do - script.on_event(k, v) -end - -script.on_event(defines.events.on_player_created, function(event) - local player = game.players[event.player_index] - util.insert_safe(player, global.created_items) - - local r = global.chart_distance or 200 - player.force.chart(player.surface, {{player.position.x - r, player.position.y - r}, {player.position.x + r, player.position.y + r}}) - - if not global.skip_intro then - if game.is_multiplayer() then - player.print({"msg-intro"}) - else - game.show_message_dialog{text = {"msg-intro"}} - end - end - - silo_script.on_event(event) -end) - -script.on_event(defines.events.on_player_respawned, function(event) - local player = game.players[event.player_index] - util.insert_safe(player, global.respawn_items) - silo_script.on_event(event) -end) - -script.on_configuration_changed(function(event) - global.created_items = global.created_items or created_items() - global.respawn_items = global.respawn_items or respawn_items() - silo_script.on_configuration_changed(event) -end) - -script.on_load(function() - silo_script.on_load() -end) - -script.on_init(function() - global.created_items = created_items() - global.respawn_items = respawn_items() - silo_script.on_init() -end) - -silo_script.add_remote_interface() -silo_script.add_commands() - -remote.add_interface("freeplay", -{ - get_created_items = function() - return global.created_items - end, - set_created_items = function(map) - global.created_items = map - end, - get_respawn_items = function() - return global.respawn_items - end, - set_respawn_items = function(map) - global.respawn_items = map - end, - set_skip_intro = function(bool) - global.skip_intro = bool - end, - set_chart_distance = function(value) - global.chart_distance = tonumber(value) - end -}) - -]===]) - --- Multi-file test ---[[ -local files = {} - -files['testfolder.anothertest.test'] = [===[ - script.on_event(defines.events.on_player_changed_position, function(e) game.print('position changed') end) -]===] - -files['testfolder.test'] = [===[ - require 'anothertest.test' -]===] - -files['test'] = [===[ - require 'testfolder.test' -]===] - -hotpatch_tools.static_mod('require-test', '1.0.0', [===[ - require 'test' -]===], files) ---]] - --- end of mod code \ No newline at end of file diff --git a/lib/scenarios/Hotpatch/hotpatch/commands.lua b/lib/scenarios/Hotpatch/hotpatch/commands.lua deleted file mode 100644 index fb6226f3..00000000 --- a/lib/scenarios/Hotpatch/hotpatch/commands.lua +++ /dev/null @@ -1,135 +0,0 @@ -local hotpatch_tools = require 'hotpatch.mod-tools' -hotpatch_tools.static_mod('hotpatch-commands', '1.0.2', [===[ ---[[ - -Copyright 2018 Chrisgbk -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -]] --- MIT License, https://opensource.org/licenses/MIT - -local hotpatch_tools = require 'hotpatch.mod-tools' - ---load private API --- mod installation/uninstallation support functions --- These take a mod NAME as a first argument -local install_mod = hotpatch_tools.install_mod -local find_installed_mod = hotpatch_tools.find_installed_mod -local install_mod_file = hotpatch_tools.install_mod_file -local uninstall_mod = hotpatch_tools.uninstall_mod - --- mod interaction functions --- These take a LOADED INDEX as a first argument, except load_mod, which takes an INSTALLED INDEX -local load_mod = hotpatch_tools.load_mod -local find_loaded_mod = hotpatch_tools.find_loaded_mod -local run_mod = hotpatch_tools.run_mod -local reset_mod = hotpatch_tools.reset_mod -local reset_mod_events = hotpatch_tools.reset_mod_events -local register_mod_events = hotpatch_tools.register_mod_events -local unload_mod = hotpatch_tools.unload_mod - --- internal callbacks when a mod registers events -local register_event = hotpatch_tools.register_event -local register_nth_tick = hotpatch_tools.register_nth_tick -local register_on_tick = hotpatch_tools.register_on_tick - --- mod bootstrap functions --- These take a LOADED INDEX as a first argument -local mod_on_init = hotpatch_tools.mod_on_init -local mod_on_load = hotpatch_tools.mod_on_load -local mod_on_configuration_changed = hotpatch_tools.mod_on_configuration_changed - -local static_mods = hotpatch_tools.static_mods -local console = hotpatch_tools.console -local debug_log = hotpatch_tools.debug_log -local loaded_mods = hotpatch_tools.loaded_mods -local installed_mods = hotpatch_tools.installed_mods - -local help_commands = { - help = '/hotpatch help\nYou got this far, you probably know all you need to know', - list = '/hotpatch list [type]\nLists \'installed\', \'loaded\' or \'running\' mods', -} - -local sub_commands -sub_commands = { - help = function(player_index, param) - local caller = (player_index and game.players[player_index]) or console - if (not param) or (param == '') then - caller.print('Usage: /hotpatch [command] [parameters]') - caller.print('Available commands:') - for k, _ in pairs(sub_commands) do caller.print(k) end - caller.print('Use /hotpatch help [command] for help on a particular command') - else - local text = help_commands[param] - if text then - caller.print(text) - end - end - end, - list = function(player_index, param) - local caller = (player_index and game.players[player_index]) or console - if not param or (param == '') then - caller.print('Installed mods:') - for k, v in pairs(installed_mods) do - caller.print(table.concat{'[', k, '] ', v.name, ' ', v.version}) - end - caller.print('Loaded/Running mods:') - for k, v in pairs(loaded_mods) do - caller.print(table.concat{'[', k, '] ', v.name, ' ', v.version, ' ', (v.loaded and 'loaded') or 'not loaded', ' ', (v.running and 'running') or 'not running'}) - end - else - - end - end, -} - -local admin_commands = { - reinstall = function(player_index, param) - local caller = (player_index and game.players[player_index]) or console - local mod - local installed_index - local loaded_index - for i = 1, #static_mods do - mod = static_mods[i] - if mod.name:match('^hotpatch%-.*$') then - loaded_index = find_loaded_mod(mod.name) - if loaded_index then - unload_mod(loaded_index) - end - install_mod(mod.name, mod.version, mod.code, mod.files) - installed_index = find_installed_mod(mod.name) - load_mod(installed_index) - loaded_index = find_loaded_mod(mod.name) - run_mod(loaded_index) - mod_on_init(loaded_index) - end - end - end, -} - -_ENV.commands.add_command('hotpatch', 'Commands for hotpatch. Run /hotpatch help for details.', function(e) - local caller = (e.player_index and game.players[e.player_index]) or console - if (not e.parameter) or (e.parameter == '') then - sub_commands['help'](e.player_index) - return - end - local sub_command, rest = e.parameter:match('(.-)%s(.*)') - if not sub_command then - sub_command = e.parameter - end - - local f = sub_commands[sub_command] - if f then f(e.player_index, rest) end - if caller.admin then - f = admin_commands[sub_command] - if f then f(e.player_index, rest) end - end -end) - ---]===]) - -return true \ No newline at end of file diff --git a/lib/scenarios/Hotpatch/hotpatch/gui.lua b/lib/scenarios/Hotpatch/hotpatch/gui.lua deleted file mode 100644 index 30f8cb98..00000000 --- a/lib/scenarios/Hotpatch/hotpatch/gui.lua +++ /dev/null @@ -1,377 +0,0 @@ -local hotpatch_tools = require 'hotpatch.mod-tools' -hotpatch_tools.static_mod('hotpatch-gui', '1.0.6', [===[ ---[[ - -Copyright 2018 Chrisgbk -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -]] --- MIT License, https://opensource.org/licenses/MIT - -local hotpatch_tools = require 'hotpatch.mod-tools' -local mod_gui = require 'mod-gui' - ---load private API --- mod installation/uninstallation support functions --- These take a mod NAME as a first argument -local install_mod = hotpatch_tools.install_mod -local find_installed_mod = hotpatch_tools.find_installed_mod -local install_mod_file = hotpatch_tools.install_mod_file -local uninstall_mod = hotpatch_tools.uninstall_mod - --- mod interaction functions --- These take a LOADED INDEX as a first argument, except load_mod, which takes an INSTALLED INDEX -local load_mod = hotpatch_tools.load_mod -local find_loaded_mod = hotpatch_tools.find_loaded_mod -local run_mod = hotpatch_tools.run_mod -local reset_mod = hotpatch_tools.reset_mod -local reset_mod_events = hotpatch_tools.reset_mod_events -local register_mod_events = hotpatch_tools.register_mod_events -local unload_mod = hotpatch_tools.unload_mod - --- internal callbacks when a mod registers events -local register_event = hotpatch_tools.register_event -local register_nth_tick = hotpatch_tools.register_nth_tick -local register_on_tick = hotpatch_tools.register_on_tick - --- mod bootstrap functions --- These take a LOADED INDEX as a first argument -local mod_on_init = hotpatch_tools.mod_on_init -local mod_on_load = hotpatch_tools.mod_on_load -local mod_on_configuration_changed = hotpatch_tools.mod_on_configuration_changed - -local static_mods = hotpatch_tools.static_mods -local console = hotpatch_tools.console -local debug_log = hotpatch_tools.debug_log -local loaded_mods = hotpatch_tools.loaded_mods -local installed_mods = hotpatch_tools.installed_mods - -script.on_event(defines.events.on_player_joined_game, function(e) - local player = game.players[e.player_index] - local top = mod_gui.get_button_flow(player) - local left = mod_gui.get_frame_flow(player) - local center = player.gui.center - - local button = top['hotpatch-button'] - if button then - button.destroy() - end - local menu = left['hotpatch-menu'] - if menu then - menu.destroy() - end - - local IDE = center['hotpatch-IDE'] - if IDE then - IDE.destroy() - end - - local main = center['hotpatch-main'] - if main then - main.destroy() - end - - button = top.add{type = 'sprite-button', name = 'hotpatch-button', sprite='utility/heat_exchange_indication', tooltip = 'Hotpatch', style = mod_gui.button_style} -end) - -local on_gui_click_handlers -local on_gui_selection_state_changed_handlers - -on_gui_click_handlers = { - ['hotpatch-button'] = function(e) - --on_gui_click_handlers['hotpatch-menu.IDE'](e) - --do return end - local player = game.players[e.player_index] - local left = mod_gui.get_frame_flow(player) - local center = player.gui.center - - local menu = left['hotpatch-menu'] - if not menu then - menu = left.add{type = 'frame', name = 'hotpatch-menu', direction = 'vertical'} - menu.add{type = 'button', name = 'hotpatch-menu.IDE', caption = 'Mod IDE', tooltip = 'Open debugging GUI'} - menu.add{type = 'button', name = 'hotpatch-menu.console', caption = 'Mod Console', tooltip = 'Open debugging console'} - menu.visible = true - return - end - menu.visible = not menu.visible - end, - ['hotpatch-menu'] = function(e) - local player = game.players[e.player_index] - local left = mod_gui.get_frame_flow(player) - - if e.element.name ~= 'hotpatch-menu' then - local menu = left['hotpatch-menu'] - menu.visible = not menu.visible - on_gui_click_handlers[e.element.name](e) - end - end, - ['hotpatch-menu.IDE'] = function(e) - local player = game.players[e.player_index] - local center = player.gui.center - - local IDE = center['hotpatch-IDE'] - if not IDE then - IDE = center.add{type = 'frame', name = 'hotpatch-IDE', direction = 'vertical', caption = 'Hotpatch IDE', style = mod_gui.frame_style} - local top_flow = IDE.add{type = 'flow', name = 'hotpatch-IDE-top', direction = 'horizontal'} - top_flow.add{type = 'label', name = 'hotpatch-IDE-mod-label', caption = 'Mod: '} - local IDE_dropdown = top_flow.add{type = 'drop-down', name = 'hotpatch-IDE-mod-selector'} - top_flow.add{type = 'label', name = 'hotpatch-IDE-mod-version', caption = 'Version: No mod selected'} - local IDE_table = IDE.add{type = 'table', name = 'hotpatch-IDE-table', column_count = 2} - local files = IDE_table.add{type = 'scroll-pane', name = 'hotpatch-IDE-files', direction='vertical'} - files.style.height = 600 - files.vertical_scroll_policy = 'always' - files = files.add{type = 'table', name = 'hotpatch-IDE-files-table', column_count = 1} - local code = IDE_table.add{type = 'text-box', name = 'hotpatch-IDE-code'} - code.word_wrap = true - files.style.width = 400 - files.style.cell_padding = 0 - files.style.top_padding = 0 - files.style.bottom_padding = 0 - code.style.width = 600 - code.style.height = 600 - IDE.visible = false - end - - local IDE_dropdown = IDE['hotpatch-IDE-top']['hotpatch-IDE-mod-selector'] - IDE_dropdown.clear_items() - for _, v in ipairs(installed_mods) do - IDE_dropdown.add_item(v.name) - end - - IDE.visible = not IDE.visible - if IDE.visible then - player.opened = IDE - else - player.opened = nil - end - end, - ['hotpatch-menu.console'] = function(e) - local player = game.players[e.player_index] - local center = player.gui.center - - local hotpatch_console = center['hotpatch-console'] - if not hotpatch_console then - hotpatch_console = center.add{type = 'frame', name = 'hotpatch-console', direction = 'vertical', caption = 'Hotpatch Console'} - local top_flow = hotpatch_console.add{type = 'flow', name = 'hotpatch-console-top', direction = 'horizontal'} - top_flow.add{type = 'label', name = 'hotpatch-console-mod-label', caption = 'Mod: '} - local console_dropdown = top_flow.add{type = 'drop-down', name = 'hotpatch-console-mod-selector'} - top_flow.add{type = 'label', name = 'hotpatch-console-mod-version', caption = 'Version: No mod selected'} - local bottom_flow = hotpatch_console.add{type = 'flow', name = 'hotpatch-console-bottom', direction = 'vertical'} - local output = bottom_flow.add{type = 'text-box', name = 'hotpatch-console-output'} - local bottom_input_flow = bottom_flow.add{type = 'flow', name = 'hotpatch-console-bottom-input', direction = 'horizontal'} - local input = bottom_input_flow.add{type = 'textfield', name = 'hotpatch-console-input'} - bottom_input_flow.add{type = 'button', name = 'hotpatch-console-run', caption = 'Run', tooltip = 'Run Lua Code'} - output.style.width = 800 - output.style.height = 600 - input.style.width = 600 - hotpatch_console.visible = false - end - - local console_dropdown = hotpatch_console['hotpatch-console-top']['hotpatch-console-mod-selector'] - console_dropdown.clear_items() - for _, v in ipairs(loaded_mods) do - console_dropdown.add_item(v.name) - end - - hotpatch_console.visible = not hotpatch_console.visible - if hotpatch_console.visible then - player.opened = hotpatch_console - else - player.opened = nil - end - end, - ['hotpatch-console-run'] = function(e) - local player = game.players[e.player_index] - local center = player.gui.center - - local hotpatch_console = center['hotpatch-console'] - local output = hotpatch_console['hotpatch-console-bottom']['hotpatch-console-output'] - local input = hotpatch_console['hotpatch-console-bottom']['hotpatch-console-bottom-input']['hotpatch-console-input'] - local console_dropdown = hotpatch_console['hotpatch-console-top']['hotpatch-console-mod-selector'] - - if not player.admin then - output.text = table.concat{output.text, '\n', 'Only admins!'} - return - end - - if not (console_dropdown.selected_index > 0) then - output.text = table.concat{output.text, '\n', 'Select a mod!'} - return - end - - local mod_name = console_dropdown.items[console_dropdown.selected_index] - local loaded_index = find_loaded_mod(mod_name) - local env = loaded_mods[loaded_index].env - local old_print = env.print - local old_log = env.log - - - - - - env.print = function(...) - --local count = select('#', ...) - local t = table.pack(...) - for k, v in pairs(t) do - t[k] = tostring(v) - end - local text = table.concat(t, '\t') - output.text = table.concat{output.text, '\n', text} - end - - env.log = function(text) - output.text = table.concat{output.text, '\n', text} - end - - local success - local code, err = load(input.text, input.text, 't', env) - if code then - success, err = pcall(code) - if not success then - output.text = table.concat{output.text, '\n', err} - end - else - output.text = table.concat{output.text, '\n', err} - end - - env.print = old_print - env.log = old_log - end, - ['hotpatch-IDE-file'] = function(e) - local player = game.players[e.player_index] - local center = player.gui.center - - local element = e.element - local previous - for _, v in pairs(center['hotpatch-IDE']['hotpatch-IDE-table']['hotpatch-IDE-files']['hotpatch-IDE-files-table'].children) do - if table.compare(v.style.font_color or {}, {r=1.0, g=1.0, b=0.0, a=1.0}) then - previous = v - break - end - end - if previous then - previous.style.font_color = {r=1.0, g=1.0, b=1.0, a=1.0} - end - element.style.font_color = {r=1.0, g=1.0, b=0.0, a=1.0} - local file = element.name:match('.-%.(.*)') - local selected = center['hotpatch-IDE']['hotpatch-IDE-top']['hotpatch-IDE-mod-selector'] - local mod_name = selected.items[selected.selected_index] - local code = center['hotpatch-IDE']['hotpatch-IDE-table']['hotpatch-IDE-code'] - local index = find_installed_mod(mod_name) - if index then - local mod = installed_mods[index] - if file == 'control' then - code.text = mod.code - else - code.text = mod.files[file] - end - end - end, -} - -script.on_event(defines.events.on_gui_click, function(e) - local element = e.element - if element.valid then - local name = element.name:match('([^%.]*)%.?.-') - local handler = on_gui_click_handlers[name] - if handler then handler(e) end - end -end) - -on_gui_selection_state_changed_handlers = { - ['hotpatch-console-mod-selector'] = function(e) - local player = game.players[e.player_index] - local center = player.gui.center - - local element = e.element - local name = element.items[element.selected_index] - if name then - local index = find_loaded_mod(name) - if index then - local mod = loaded_mods[index] - local version_label = center['hotpatch-console']['hotpatch-console-top']['hotpatch-console-mod-version'] - version_label.caption = 'Version: ' .. mod.version - local hotpatch_console = center['hotpatch-console'] - local output = hotpatch_console['hotpatch-console-bottom']['hotpatch-console-output'] - output.text = '' - end - end - end, - ['hotpatch-IDE-mod-selector'] = function(e) - local player = game.players[e.player_index] - local center = player.gui.center - - local element = e.element - - local name = element.items[element.selected_index] - if name then - local index = find_installed_mod(name) - if index then - local mod = installed_mods[index] - local version_label = center['hotpatch-IDE']['hotpatch-IDE-top']['hotpatch-IDE-mod-version'] - version_label.caption = 'Version: ' .. mod.version - local list = center['hotpatch-IDE']['hotpatch-IDE-table']['hotpatch-IDE-files']['hotpatch-IDE-files-table'] - list.clear() - local file = list.add{type = 'label', style = 'hoverable_bold_label', caption = 'control', name = 'hotpatch-IDE-file.control'} - file.style.bottom_padding = 0 - file.style.top_padding = 0 - for k, _ in pairs(mod.files) do - file = list.add{type = 'label', style = 'hoverable_bold_label', caption = k, name = 'hotpatch-IDE-file.' .. k} - file.style.bottom_padding = 0 - file.style.top_padding = 0 - end - local code = center['hotpatch-IDE']['hotpatch-IDE-table']['hotpatch-IDE-code'] - code.text = '' - end - end - end, -} - -script.on_event(defines.events.on_gui_selection_state_changed, function(e) - local element = e.element - local handler = on_gui_selection_state_changed_handlers[element.name] - if handler then handler(e) end -end) - -script.on_event(defines.events.on_gui_closed, function(e) - local player = game.players[e.player_index] - local element = e.element - if e.gui_type == defines.gui_type.custom then - if element and element.valid then - if element.name == 'hotpatch-IDE' then - if element.visible then - player.play_sound{path = "utility/gui_click"} - end - element.visible = false - elseif element.name == 'hotpatch-console' then - if element.visible then - player.play_sound{path = "utility/gui_click"} - end - element.visible = false - end - end - end -end) - ---]===]) - ---[===[ --- keeping this as a reminder -['hotpatch-menu'] = function(e) - local player = game.players[e.player_index] - local top = mod_gui.get_button_flow(player) - local left = mod_gui.get_frame_flow(player) - local center = player.gui.center - - local menu = left['hotpatch-menu'] - menu.visible = not menu.visible - - on_gui_click_handlers[e.element.name](e) - end, ---]===] - -return true \ No newline at end of file diff --git a/lib/scenarios/Hotpatch/hotpatch/mod-tools.lua b/lib/scenarios/Hotpatch/hotpatch/mod-tools.lua deleted file mode 100644 index 91c46776..00000000 --- a/lib/scenarios/Hotpatch/hotpatch/mod-tools.lua +++ /dev/null @@ -1,1349 +0,0 @@ ---[[ - -Copyright 2018 Chrisgbk -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -]] --- MIT License, https://opensource.org/licenses/MIT - --- Hotpatch-MultiMod: a tool to load multiple scenarios side-by-side, with support for both static loading and dynamic loading, as well as run-time patching - -log('Hotpatch runtime initializing...') -local util = require 'util' - -local compat_mode = false -- enable some compatibility settings, which can help some mods load -local strict_mode = false -- causes hotpatch to hard-stop on mods doing bad things - -local debug_levels = {['disabled'] = -1, ['severe'] = 1, ['error'] = 2, ['warning'] = 3, ['info'] = 4, ['verbose'] = 5, ['trace'] = 6} - --- configuration options; these should be exposed via API one day -local debug_level = (tonumber(_ENV.debug_settings.level) or debug_levels[_ENV.debug_settings.level]) or 0 -local debug_log_to_console_only = _ENV.debug_settings.log_to_console_only -local debug_log_to_RCON = _ENV.debug_settings.log_to_RCON --only affects when log_to_console_only is in effect -local debug_log_on_tick = _ENV.debug_settings.log_on_tick - --- convenience object(rcon.print also prints to stdout when called from server console) -local console = {name = 'Console', admin = true, print = function(...) rcon.print(...) end, color = {1,1,1,1}} - --- mapping of events to names for logging -local event_names = {} -for k,v in pairs(defines.events) do - event_names[v] = k -end - --- these represent the mods as statically packaged with the scenario -local static_mods = {} --- static_mods[i] = {} --- static_mods[i].name = '' --- static_mods[i].version = '' --- static_mods[i].code = '' --- static_mods[i].files = {} - --- installed mods are represented with: --- global.mods = {} --- global.mods[i] = {} --- global.mods[i].name = '' --- global.mods[i].version = '' --- global.mods[i].code = '' --- global.mods[i].files = {} --- global.mods[i].global = {} - --- loaded/running mods are represented with: -local loaded_mods = {} -- this holds a reference to the mods internal object --- loaded_mods[i] = mod_obj_template - --- internal mod object: -local mod_obj_template = { - name = '', -- name of the current mod - version = '', - env = {}, -- environment of the mod - loaded = false, - running = false, - on_init = nil, -- called once after first install, or when specifically requested to be re-ran (good practice is to never request a re-run) - on_load = nil, -- called every time the scenario loads from disk - on_configuration_changed = nil, -- called every time the external mod configuration changes, OR when the hotpatch mod configuration changes - on_tick = nil, -- cache the on-tick event handler, because it is ID 0, which causes it to be stored in the hash part, which causes a 50% increase in access time - on_event = {}, -- list of on_event handlers registered - on_nth_tick = {}, -- list of on_nth_tick handlers registered - loaded_files = {}, -- list of files loaded by require() that were installed into the virtual file system -} - --- this is for the core factorio libraries; hotpatch MUST load these now, as they cannot be dynamically loaded due to factorio limitation -local loaded_libraries = {} - --- mod installation/uninstallation support functions --- These take a mod NAME as a first argument -local install_mod -local find_installed_mod -local install_mod_file -local uninstall_mod - --- mod interaction functions --- These take a LOADED INDEX as a first argument, except load_mod, which takes an INSTALLED INDEX -local load_mod -local find_loaded_mod -local run_mod -local reset_mod -local reset_mod_events -local register_mod_events -local unload_mod - --- internal callbacks when a mod registers events -local register_event -local register_nth_tick -local register_on_tick - --- mod bootstrap functions --- These take a LOADED INDEX as a first argument -local mod_on_init -local mod_on_load -local mod_on_configuration_changed - --- local event handler to proxy to mods, these call the mods events -local on_event -local on_nth_tick - -local register_all_events - --- core handlers -local on_init -- installs static mods, loads, runs, and calls mod_on_init -local on_load -- loads, runs, and calls mod_on_load -local on_configuration_changed -- calls mod_on_configuration_changed - --- this is dual-purpose, handles core needs and mod on_tick -local on_tick - --- this installs a new mod statically, only installed during on_init --- Installed mod will still be updatable -local function static_mod(name, version, code, files) - local mod = {} - mod.name = name - mod.version = version - mod.code = code - mod.files = {} - if files then - for k, v in pairs(files) do - mod.files[k] = v - end - end - table.insert(static_mods, mod) -end - --- This scenario contains an embedded locale, but it's unavailable until this file finishes loading --- ie: calls to log{...} will fail right now, but will work in events --- Rseding91 fixed it for 0.17 (https://forums.factorio.com/viewtopic.php?f=23&t=60767) --- I'll make it work myself in 0.16 ¯\_(ツ)_/¯ -local static_cfg = [[ -[hotpatch] -log=@__1__:__2__] __3__ -log-mod=@__1__:__2__][__4__] __3__ -severe=SEVERE ERROR: __1__ -error=ERROR: __1__ -warning=WARNING: __1__ -info=INFO: __1__ -verbose=INFO: __1__ -trace=TRACE: __1__ - -[hotpatch-info] -logging-enabled=Logging enabled -metatable-installed=_ENV metatable installed -complete=__1__ Complete! -on-init=initializing... -on-load=loading... -on-configuration-changed=mod configuration changed... -installing-included-mods=installing included mods... -loading-installed-mods=loading installed mods... -loading-libs=loading Factorio.data.core.lualib... -loading-library=loading library: __1__ -uninstalling=Uninstalling mod... -installing=Installing version __1__... -installing-file=Installing file __1__... -script-shim=setting up mod script shim... -setting-env=setting up mod _ENV... -loading=loading... -unloading=unloading... -running=running... -must-be-admin=You must be an admin to run this command -remote-installing=installing remote interface - -[hotpatch-trace] -nil-var-access=_ENV nil variable access: __1__ -nil-var-assignment=_ENV variable assignment: __1__ -event-registering=registering events... -on-tick-event-registered=registered on_tick event... -on-event-registered=registered event __1__... -on-nth-tick-event-registered=registered nth_tick event __1__... -event-running=running event __1__... -nth-tick-event-running=running nth_event __1__... -mod-on-init=running on_init... -mod-on-load=running on_load... -mod-on-configuration-changed=running on_configuration_changed... -adding-event=adding event __1__ -adding-nth-tick-event=adding nth_tick event __1__ -caching-event=caching event: __1__... -caching-nth-tick-event=caching nth_tick event: __1__... -event-processing=processing event __1__... -nth-tick-event-processing=processing nth_tick event __1__... -nth-tick-handler-added=added nth_tick handler: __1__ -nth-tick-handler-removed=removed nth_tick handler: __1__ -on-tick-handler-added=added on_tick handler -on-tick-handler-removed=removed on_tick handler -on-event-handler-added=added event handler: __1__ -on-event-handler-removed=removed event handler: __1__ -cached-load-require=loading cached require'd file: __1__... -load-require=loading require'd file: __1__... -load-core-lib=loading from Factorio.data.core.lualib: __1__... - -[hotpatch-warning] -contains-comments=mod code contains comments! -contains-comments-no-lf=mod contains comments and no linefeed! -contains-comments-console=comments from console will comment out the entire code! -reset-events-not-running=tried to reset events for mod that isn't running! -remote-interface-exists=remote interface __1__ already exists, removing... -command-exists=command __1__ already exists, removing... -already-loaded=mod already loaded -reinitializing=reinitializing... -already-exists=mod already exists -reinstalling=reinstalling mod in-place - -[hotpatch-error] -invalid-API-access=Invalid API access: __1__ -not-installed=mod not installed, cannot install file for mod that does not exist -compilation-failed=compilation failed for mod -execution-failed=execution failed for mod - -[test-pluralization] -test=__1:(^1$)=singular;(^[2-9]$)=plural single digit;([1-2][0-9]$)=plural ends with double digit <30;(.*)=fallback case%; plural with embedded %;;__ -]] - -local function build_locale(ini) - local t = {} - local section = '' - local temp - - -- line must end with a linefeed - single line file with no LF will fail - if not ini:match('[\r\n]+$') then - ini = ini .. '\n' - end - - -- for each non-empty line do - local key, value - for l in ini:gmatch('[\r\n]*(.-)[\r\n]+') do - -- header? - temp = l:match('^%[(.-)%].*$') - if temp then - section = temp - temp = nil - else - --key=value - key, value = l:match('^(.-)=(.+)$') - t[table.concat{section, '.', key}] = value - end - end - return t -end - -local static_locale = build_locale(static_cfg) - -local function escape(s) - return (s:gsub('([%^%$%(%)%%%.%[%]%*%+%-%?])', '%%%1')) -end - -local function unescape(s) - return (s:gsub('(%%)', '')) -end - -local function static_translate(t, recursive) - -- only translate tables - if type(t) ~= 'table' then return t end - -- only translate tables that have a string as the first item - local k = t[1] - if type(k) ~= 'string' then return t end - - -- make a copy, don't destroy the original table, after we copy we can translate in place - if not recursive then - t = table.deepcopy(t) - end - -- translate any arguments as well - local v - for i = 2, #t do - v = t[i] - if type(v) == 'table' then - t[i] = static_translate(v, true) - end - end - -- special case: whitespace token causes concatenation with that token - -- slightly better than factorio, where only '' is supported - if k:find('^%s*$') then table.remove(t, 1) return table.concat(t, k) end - local pattern = static_locale[k] - -- if not translatable return normal table ref; factorio does following instead: - -- if not pattern then return 'Unknown key: ' .. k end - -- by returning the table we pass off to factorio runtime translation, where available - if not pattern then return t end - -- substitution of parameters: use literal value of parameter n - -- __n__ - local result = (pattern:gsub('__(%d+)__', function(s) return tostring(t[tonumber(s)+1]) end)) - - -- re-substitution engine: match value of parameter n to provide additional translation; use for pluralization - -- __n:(pattern-1)=substitution-1;(pattern-2)=substitution-2;...(pattern-i)=substitution-i;__ - for n, p in result:gmatch('__(%d+)(:.-;)__') do - for x, y in p:gmatch('%((.-)%)=(.-[^%%]);') do - if t[tonumber(n)+1]:match(x) then - result = result:gsub(table.concat{'__', n, escape(p), '__'}, unescape(y)) - break - end - end - end - - return result -end - --- override print to make it support our translation efforts --- still doesn't support Factorio locales, because devs don't patch it like I do --- this means unknown keys will be printed as 'table: 0x...' -local real_print = print -local print = function(...) - if select('#', ...) == 1 then - real_print(static_translate(...)) - else - local t = table.pack(...) - table.insert(t, 1, '\t') - real_print(static_translate(t)) - end -end - --- override log to make it support our translation efforts --- any unknown keys are passed to Factorio to translate - -local hidden = load([===[ - local real_log = log; local static_translate = select(1, ...); local log = function(...); if select('#', ...) == 1 then real_log(static_translate(...)) else local t = table.pack(...); table.insert(t, 1, '\t'); real_log(static_translate(t)) end end return log -]===], '[HOTPATCH') -local hidden_log = hidden(static_translate) - --- logs localized data -local function debug_log(message, mod_name, stack_level) - if debug_level > -1 then - if not stack_level then stack_level = 2 end - local di = debug.getinfo(stack_level) - local line = di.currentline - local file = (di.source:gsub('%@.*/', '')) - local class = 'hotpatch.info' - local log_type = (mod_name and 'hotpatch.log-mod') or 'hotpatch.log' - local severity - if type(message) == 'table' then - severity = message[1]:match('.-%-([^%-]*)%.') - if not severity then - severity = message[1]:match('.-%-.-%-(.*)%.') - if not severity then - severity = 'always' - end - end - class = table.concat{'hotpatch', '.', severity} - else - severity = 'always' - end - local level = ((severity == 'always') and 0) or debug_levels[severity] - if debug_level >= level then - if debug_log_to_console_only then - if debug_log_to_RCON then - rcon.print{log_type, file, line, {class, message}, mod_name} - end - print{log_type, file, line, {class, message}, mod_name} - - else - hidden_log{log_type, file, line, {class, message}, mod_name} - end - end - end -end - -debug_log({'hotpatch-info.logging-enabled'}) - --- track _ENV accesses -setmetatable(_ENV, { - __index = function(_, k) - debug_log({'hotpatch-trace.nil-var-access', k}, nil, 3) - return nil - end, - __newindex = function(t, k, v) - debug_log({'hotpatch-trace.nil-var-assignment', k}, nil, 3) - rawset(t,k,v) - end, - __metatable = false -}) - -debug_log({'hotpatch-info.metatable-installed'}) - --- load all possible libraries -debug_log({'hotpatch-info.loading-libs'}) - -local libraries = { - 'camera', - 'flying_tags', - 'math3d', - 'mod-gui', - 'noise', - 'production-score', - 'silo-script', - 'story', - 'util', -} - -for k, v in pairs(libraries) do - debug_log({'hotpatch-info.loading-library', v}) - loaded_libraries[k] = require(v) -end - -debug_log({'hotpatch-info.complete', {'hotpatch-info.loading-libs'}}) - -find_installed_mod = function(mod_name) - local mod - local mods = global.mods - for i = 1, #mods do - mod = mods[i] - if mod.name == mod_name then - return i - end - end - return nil -end - -find_loaded_mod = function(mod_name) - local mod - for i = 1, #loaded_mods do - mod = loaded_mods[i] - if mod.name == mod_name then - return i - end - end - return nil -end - -uninstall_mod = function(mod_name) - local index = find_installed_mod(mod_name) - if not index then - -- TODO: notify that mod doesn't exist - return - end - local loaded_index = find_loaded_mod(mod_name) - - if loaded_index then - unload_mod(loaded_index) - end - - - debug_log({'hotpatch-info.uninstalling', mod_name}) - - table.remove(global.mods, index) -end - -install_mod_file = function(mod_name, mod_file, mod_file_code) - local index = find_installed_mod(mod_name) - if not index then - debug_log({'hotpatch-error.not-installed'}) - return - end - local mod = global.mods[index] - - debug_log({'hotpatch-info.installing-file', mod_file}, mod_name) - - mod_file = mod_file:gsub('/', '.') - mod_file = mod_file:gsub('\\', '.') - mod_file_code = mod_file_code:gsub('\t', ' ') - mod.files[mod_file] = mod_file_code -end - -install_mod = function(mod_name, mod_version, mod_code, mod_files) - local index = find_installed_mod(mod_name) - local mod = {} - if index then - -- TODO: notify about installing over top of existing mod - mod = global.mods[index] - else - --next free index - table.insert(global.mods, mod) - end - debug_log({'hotpatch-info.installing', mod_version}, mod_name) - if mod_code:find('--', 1, true) then - - debug_log({'hotpatch-warning.contains-comments'}, mod_name) - if not mod_code:find("\n", 1, true) then - debug_log({'hotpatch-warning.contains-comments-no-lf'}, mod_name) - end - debug_log({'hotpatch-warning.contains-comments-console'}, mod_name) - end - - mod_code = mod_code:gsub('\t', ' ') - - mod.name = mod_name - mod.files = mod.files or {} - mod.code = mod_code - mod.version = mod_version - mod.global = mod.global or {} - - if mod_files then - for k,v in pairs(mod_files) do - install_mod_file(mod_name, k, v) - end - end -end - -load_mod = function(installed_index) - local mod = global.mods[installed_index] - local mod_name = mod.name - if mod.code then - -- TODO: integrity check, verify each mods global table to detect guaranteed desync mods - local loaded_index = find_loaded_mod(mod_name) - if loaded_index then - --TODO notify that mod was already loaded - unload_mod(loaded_index) - end - - local mod_obj = { - name = mod_name, -- name of the current mod - version = mod.version, - env = {}, -- environment of the mod - loaded = false, - running = false, - on_init = nil, -- called once after first install, or when specifically requested to be re-ran (good practice is to never request a re-run) - on_load = nil, -- called every time the scenario loads from disk - on_configuration_changed = nil, -- called every time the external mod configuration changes, OR when the hotpatch mod configuration changes - on_tick = nil, -- cache the on-tick event handler, because it is ID 0, which causes it to be stored in the hash part, which causes a 50% increase in access time - on_event = {}, -- list of on_event handlers registered - on_nth_tick = {}, -- list of on_nth_tick handlers registered - loaded_files = {}, -- list of files loaded by require() that were installed into the virtual file system - } - - debug_log({'hotpatch-info.script-shim'}, mod_name) - --mods private script table/shim - local mod_script = {} - - mod_script.on_init = function(f) - mod_obj.on_init = f - end - mod_script.on_load = function(f) - mod_obj.on_load = f - end - mod_script.on_configuration_changed = function(f) - mod_obj.on_configuration_changed = f - end - if not compat_mode then - mod_script.on_event = function(event, f) - if event == defines.events.on_tick then - mod_obj.on_tick = f - else - mod_obj.on_event[event] = f - end - end - mod_script.on_nth_tick = function(tick, f) - if tick then - if type(tick) == 'table' then - for _, v in pairs(tick) do - mod_script.on_nth_tick(v, f) - end - return - end - mod_obj.on_nth_tick[tick] = f - else - mod_obj.on_nth_tick = {} - end - end - else - mod_script.on_event = function(event, f) - if event == defines.events.on_tick then - mod_obj.on_tick = f - if mod_obj.running then - register_on_tick(mod_name) - end - else - mod_obj.on_event[event] = f - if mod_obj.running then - register_event(mod_name, event) - end - end - end - mod_script.on_nth_tick = function(tick, f) - if tick then - if type(tick) == 'table' then - for _, v in pairs(tick) do - mod_script.on_nth_tick(v, f) - end - return - end - mod_obj.on_nth_tick[tick] = f - if mod_obj.running then - register_nth_tick(mod_name, tick) - end - else - local mod_on_nth_tick = mod_obj.on_nth_tick - mod_obj.on_nth_tick = {} - if mod_obj.running then - for _, v in pairs(mod_on_nth_tick) do - register_nth_tick(mod_name, v) - end - end - end - end - end - mod_script.generate_event_name = function() - return script.generate_event_name() - end - mod_script.get_event_handler = function(event) - return mod_obj.on_event[event] - end - mod_script.raise_event = function(event, table) - script.raise_event(event, table) - end - --TODO: replace these with mod-provided versions, so multi-mod aware softmods can easily detect other loaded softmods - mod_script.get_event_order = function() - return script.get_event_order() - end - mod_script.mod_name = function() - return script.mod_name() - end - - debug_log({'hotpatch-info.setting-env'}, mod_name) - -- mods private env - local env = mod_obj.env - -- mods private package - local pack = {} - -- mods private package.loaded - local loaded = {} - -- copy the current environment - for k,v in pairs(_ENV) do - env[k] = v - end - -- copy package.loaded - for k,v in pairs(_ENV.package.loaded) do - loaded[k] = v - end - loaded._G = env - - -- so many ways to escape sandboxes... - - for k,v in pairs(_ENV.package) do - pack[k] = v - end - pack.loaded = loaded - env.package = pack - loaded.package = pack - - env.script = mod_script - env.global = mod.global - env._G = env - - env['remote'] = { - add_interface = function(name, functions) - if remote.interfaces[name] then - debug_log({'hotpatch-warning.remote-interface-exists', name}, mod_name) - remote.remove_interface(name) - end - remote.add_interface(name, functions) - end, - remove_interface = function(name) - return remote.remove_interface(name) - end, - call = function(...) - return remote.call(...) - end, - interfaces = setmetatable({}, { - __index = function(_, k) return remote.interfaces[k] end, - __pairs = function(t) local function iter(t, k) local v; k, v = next(remote.interfaces, k); if v then return k, t[k] end; end; return iter, t, nil end - }) - } - env['commands'] = { - add_command = function(name, help, func) - if commands.commands[name] then - debug_log({'hotpatch-warning.command-exists', name}, mod_name) - commands.remove_command(name) - end - commands.add_command(name, help, func) - end, - remove_command = function(name) - return commands.remove_command(name) - end, - commands = setmetatable({}, { - __index = function(_, k) return commands.commands[k] end, - __pairs = function(t) local function iter(t, k) local v; k, v = next(commands.commands, k); if v then return k, t[k] end; end; return iter, t, nil end - }), - game_commands = setmetatable({}, { - __index = function(_, k) return commands.game_commands[k] end, - __pairs = function(t) local function iter(t, k) local v; k, v = next(commands.game_commands, k); if v then return k, t[k] end; end; return iter, t, nil end - }) - } - - - env.require = function(path) - -- I blame Nexela for this - path = path:gsub('/', '.') - path = path:gsub('\\', '.') - local alt_path = '' - if env.package._current_path_in_package then - alt_path = env.package._current_path_in_package .. path - end - if mod_obj.loaded_files[path] then - debug_log({'hotpatch-trace.cached-load-require', path}, mod_name) - return mod_obj.loaded_files[path] - elseif mod_obj.loaded_files[alt_path] then - debug_log({'hotpatch-trace.cached-load-require', alt_path}, mod_name) - return mod_obj.loaded_files[alt_path] - else - local oldbase = env.package._current_path_in_package - env.package._current_path_in_package = path:match('.+%..+%.') - if not env.package._current_path_in_package then - env.package._current_path_in_package = path:match('.+%.') - end - local file = global.mods[installed_index].files[path] - if file then - debug_log({'hotpatch-trace.load-require', path}, mod_name) - local code, err = load(file, '[' .. mod_name .. '] ' .. path .. '.lua', 'bt', env) - if code then - local result = code() - mod_obj.loaded_files[path] = result or true - env.package._current_path_in_package = oldbase - return mod_obj.loaded_files[path] - else - debug_log(err, nil, 3) - error(err) - end - end - file = global.mods[installed_index].files[alt_path] - if file then - debug_log({'hotpatch-trace.load-require', alt_path}, mod_name) - local code, err = load(file, '[' .. mod_name .. '] ' .. alt_path .. '.lua', 'bt', env) - if code then - local result = code() - mod_obj.loaded_files[alt_path] = result or true - env.package._current_path_in_package = oldbase - return mod_obj.loaded_files[alt_path] - else - debug_log(err, nil, 3) - error(err) - end - end - - debug_log({'hotpatch-trace.load-core-lib', path}, mod_name) - env.package._current_path_in_package = oldbase - local lib = package.loaded[path] - if not lib then - debug_log({'hotpatch-error.path-not-found', path}, mod_name, 3) - error(path .. ' not found') - end - return lib - end - end - - env['load'] = function(l, s, m, e) - return load(l, s, m, e or env) - end - env['loadstring'] = env['load'] - - - env['game'] = setmetatable({}, { - __index = function(_, k) return game[k] end, - __pairs = function(t) local function iter(t, k) local v; k, v = next(game, k); if v then return k, t[k] end; end; return iter, t, nil end - }) - - local mt = {} - mt.__index = function(_, k) - debug_log({'hotpatch-trace.nil-var-access', k}, nil, 3) - return nil - end - mt.__newindex = function(t, k, v) - debug_log({'hotpatch-trace.nil-var-assignment', k}, nil, 3) - rawset(t,k,v) - end - -- Don't let mods break this - mt.__metatable = false - setmetatable(env, mt) - - --load/run code - debug_log({'hotpatch-info.loading'}, mod_name) - - local mod_code, message = load(mod.code, '[' .. mod_name .. '] control.lua', 'bt', env) - if not mod_code then - debug_log({'hotpatch-error.compilation-failed'}, mod_name) - if game and game.player then - game.player.print(message) - end - debug_log(message, mod_name) - return false - end - - mod_obj.code = mod_code - mod_obj.loaded = true - table.insert(loaded_mods, mod_obj) - return true - end - return false -end - -unload_mod = function(loaded_index) - local mod = loaded_mods[loaded_index] - if not mod then - --TODO notify that mod isn't loaded - return - end - local mod_name = mod.name - -- TODO - -- stop mod running, unregister handlers from being called - debug_log({'hotpatch-info.unloading'}, mod_name) - -- TODO: - mod.loaded = false - mod.running = false - table.remove(loaded_mods, loaded_index) -end - - ---TODO: pretty much all of this routine --- This should unregister events and clear the globals -reset_mod = function(loaded_index) - local new_global = {} - local mod = loaded_mods[loaded_index] - mod.mod_env.global = new_global - local install_index = find_installed_mod(mod.name) - mod = global.mods[install_index] - mod.global = new_global - - reset_mod_events(loaded_index) -end - -reset_mod_events = function(loaded_index) - --local loaded_index = find_loaded_mod(mod_name) - local loaded_mod = loaded_mods[loaded_index] - - if not loaded_mod then - --debug_log({'hotpatch-warning.reset-events-not-running'}, mod_name) - else - loaded_mod.on_event = {} - loaded_mod.on_nth_tick = {} - loaded_mod.on_init = nil - loaded_mod.on_load = nil - loaded_mod.on_configuration_changed = nil - loaded_mod.on_tick = nil - register_all_events() - end -end - -local wrap_table -wrap_table = function(t, path) - local mt = { - __index = function(_, k) - local v = rawget(t, k) - if type(v) == 'table' then - if not v.__self then - local p = (path or 'global') .. '[' .. tostring(k) .. ']' - return wrap_table(v, p) - end - end - return v - end, - __newindex = function(_, k, v) - local k_type = type(k) - local wrap = ((k_type == 'string') and '"') or '' - local p = (path or 'global') .. '[' .. wrap .. tostring(k) .. wrap .. ']' - debug_log('WARN: Assignment to global table when none was expected: '.. p, nil, 3) - rawset(t, k, v) - end - } - return setmetatable({}, mt) -end - - -run_mod = function(loaded_index) - local mod = loaded_mods[loaded_index] - if mod then - local mod_name = mod.name - local old_global = mod.env.global - mod.env.global = wrap_table(old_global) - - debug_log({'hotpatch-info.running'}, mod_name) - - local success, result = xpcall(mod.code, debug.traceback) - if not success then - debug_log({'hotpatch-error.execution-failed'}, mod_name) - debug_log(result, mod_name) - --local caller = (game and game.player) or console - if game and game.player then - game.player.print(result) - end - - return false - end - - mod.env.global = old_global - old_global = nil - - mod.running = true - debug_log({'hotpatch-info.complete', {'hotpatch-info.running'}}, mod_name) - - --load complete, start notifying on event subscriptions - if not compat_mode then - mod.env.script.on_event = function(event, f) - if event == defines.events.on_tick then - mod.on_tick = f - register_on_tick(mod_name) - else - mod.on_event[event] = f - register_event(mod_name, event) - end - end - mod.env.script.on_nth_tick = function(tick, f) - if tick then - if type(tick) == 'table' then - for _, v in pairs(tick) do - mod.env.script.on_nth_tick(v, f) - end - return - end - mod.on_nth_tick[tick] = f - register_nth_tick(mod_name, tick) - else - local mod_on_nth_tick = mod.on_nth_tick - mod.on_nth_tick = {} - for _, v in pairs(mod_on_nth_tick) do - register_nth_tick(mod_name, v) - end - end - end - end - return true - end - return false -end - --- Note: might be able to optimize this a bit --- event handlers to call into mods requested event handlers -on_event = function(event) - local event_name = (event_names[event.name] or event.name) - local f - debug_log({'hotpatch-trace.event-processing', event_name}) - local mod - for i = 1, #loaded_mods do - mod = loaded_mods[i] - f = mod.on_event[event.name] - if f then - debug_log({'hotpatch-trace.event-running', event_name}, mod.name) - f(event) - end - end -end - -if debug_log_on_tick then - on_nth_tick = function(event) - local tick = event.nth_tick - local f - debug_log({'hotpatch-trace.nth-tick-event-processing', tick}) - local mod - for i = 1, #loaded_mods do - mod = loaded_mods[i] - f = mod.on_nth_tick[event.nth_tick] - if f then - debug_log({'hotpatch-trace.nth-tick-event-running', tick}, mod.name) - f(event) - end - end - end -else - on_nth_tick = function(event) - local tick = event.nth_tick - local f - local mod - for i = 1, #loaded_mods do - mod = loaded_mods[i] - f = mod.on_nth_tick[event.nth_tick] - if f then - debug_log({'hotpatch-trace.nth-tick-event-running', tick}, mod.name) - f(event) - end - end - end -end - -register_all_events = function() - --unregister all events - script.on_event(defines.events, nil) - script.on_nth_tick(nil, nil) - --re-register all mod events - for i = 1, #loaded_mods do - register_mod_events(i) - end -end - -register_mod_events = function(loaded_index) - - local mod = loaded_mods[loaded_index] - local mod_name = mod.name - debug_log({'hotpatch-trace.event-registering'}, mod_name) - if mod.on_tick then - debug_log({'hotpatch-trace.on-tick-event-registered'}, mod_name) - script.on_event(defines.events.on_tick, on_tick) - end - for k,_ in pairs(mod.on_event) do - local event_name = (event_names[k] or k) - debug_log({'hotpatch-trace.on-event-registered', event_name}, mod_name) - script.on_event(k, on_event) - end - for k,_ in pairs(mod.on_nth_tick) do - debug_log({'hotpatch-trace.on-nth-tick-event-registered', k}, mod_name) - script.on_nth_tick(k, on_nth_tick) - end -end - -mod_on_init = function(loaded_index) - local mod = loaded_mods[loaded_index] - - if mod then - debug_log({'hotpatch-trace.mod-on-init'}, mod.name) - if mod.on_init then - local success, result = xpcall(mod.on_init, debug.traceback) - if not success then - debug_log({'hotpatch-error.on-init-failed'}, mod_name) - debug_log(result, mod_name) - return false - end - end - register_mod_events(loaded_index) - return true - end - return false -end - -mod_on_load = function(loaded_index) - local mod = loaded_mods[loaded_index] - - if mod then - debug_log({'hotpatch-trace.mod-on-load'}, mod.name) - if mod.on_load then - local old_global = mod.env.global - mod.env.global = wrap_table(table.deepcopy(old_global)) - local success, result = xpcall(mod.on_load, debug.traceback) - if not success then - debug_log({'hotpatch-error.on-load-failed'}, mod_name) - debug_log(result, mod_name) - return false - end - mod.env.global = old_global - - end - register_mod_events(loaded_index) - return true - end - return false -end - -mod_on_configuration_changed = function(loaded_index, config) - local mod = loaded_mods[loaded_index] - - if mod then - debug_log({'hotpatch-trace.mod-on-configuration-changed'}, mod.name) - if mod.on_configuration_changed then - local success, result = xpcall(mod.on_configuration_changed, debug.traceback, config) - if not success then - debug_log({'hotpatch-error.on-configuration-changed-failed'}, mod_name) - debug_log(result, mod_name) - return false - end - end - return true - end - return false -end - --- callbacks from mods to tell hotpatch when to enable handlers - -register_on_tick = function(mod_name) - local found_event - -- - local mod - for i = 1, #loaded_mods do - mod = loaded_mods[i] - local f = mod.on_tick - if f then - found_event = true - break - end - end - debug_log({'hotpatch-trace.on-tick-event-registered'}, mod_name) - if found_event then - debug_log({'hotpatch-trace.on-tick-handler-added'}) - else - debug_log({'hotpatch-trace.on-tick-handler-removed'}) - end -end - -register_nth_tick = function(mod_name, nth_tick) - local found_event - -- - local mod - for i = 1, #loaded_mods do - mod = loaded_mods[i] - local f = mod.on_nth_tick[nth_tick] - if f then - found_event = true - break - end - end - debug_log({'hotpatch-trace.on-nth-tick-event-registered', nth_tick}, mod_name) - if found_event then - debug_log({'hotpatch-trace.nth-tick-handler-added', nth_tick}) - script.on_nth_tick(nth_tick, on_nth_tick) - else - debug_log({'hotpatch-trace.nth-tick-handler-removed', nth_tick}) - script.on_nth_tick(nth_tick, nil) - end -end - -register_event = function(mod_name, event_name) - local found_event - local mod - for i = 1, #loaded_mods do - mod = loaded_mods[i] - local f = mod.on_event[event_name] - if f then found_event = true break end - end - debug_log({'hotpatch-trace.on-event-registered', (event_names[event_name] or event_name)}, mod_name) - if found_event then - if script.get_event_handler(event_name) then - -- handler already installed - return - else - debug_log({'hotpatch-trace.on-event-handler-added', (event_names[event_name] or event_name)}) - script.on_event(event_name, on_event) - end - else - debug_log({'hotpatch-trace.on-event-handler-removed', (event_names[event_name] or event_name)}) - script.on_event(event_name, nil) - end -end - --- Core registration - -on_init = function() - hidden = load([===[ - return function(...) log(...) end - ]===], '[HOTPATCH') - hidden_log = hidden() - debug_log({'hotpatch-info.on-init'}) - --juuuuust in case - global.mods = global.mods or {} - - debug_log({'hotpatch-info.installing-included-mods'}) - local mod - for i = 1, #static_mods do - mod = static_mods[i] - install_mod(mod.name, mod.version, mod.code, mod.files) - end - -- TODO: fix mod error loading behaviours - for i = 1, #global.mods do - load_mod(i) - end - - for i = 1, #loaded_mods do - run_mod(i) - mod_on_init(i) - end - debug_log({'hotpatch-info.complete', {'hotpatch-info.installing-included-mods'}}) - debug_log({'hotpatch-info.complete', {'hotpatch-info.on-init'}}) -end - -on_load = function() - hidden = load([===[ - return function(...) log(...) end - ]===], '[HOTPATCH') - hidden_log = hidden() - debug_log({'hotpatch-info.on-load'}) - debug_log({'hotpatch-info.loading-installed-mods'}) - - if global.globals then - error('Upgrading from Hotpatch 1.0.X to 1.1.0 is not currently supported!') - - -- untested migration: use a tick to do the migration - script.on_event(defines.events.on_tick, function(e) - global.mods = {} - local mod - for k, v in pairs(global.mod_version) do - mod = {} - mod.name = k - mod.version = v - mod.code = global.mod_code[k] - mod.files = global.mod_files[k] - mod.global = global.globals[k] - table.insert(global.mods, mod) - end - global.mod_version = nil - global.mod_code = nil - global.mod_files = nil - global.globals = nil - - on_load() -- jump back into loading - on_tick(e) -- need to test if factorio auto calls this or not after assigning the handler - script.on_event(defines.events.on_tick, on_tick) -- restore old on_tick handler - end) - return - end - - -- load installed mods - for i = 1, #global.mods do - load_mod(i) - end - - -- run mods which loaded successfully - local failed_mods = {} - --log('before') - --log(serpent.block(global)) - - for i = 1, #loaded_mods do - if run_mod(i) then - if not mod_on_load(i) then - table.insert(failed_mods, i) - end - else - table.insert(failed_mods, i) - end - end - - --log('after') - --log(serpent.block(global)) - - -- unload mods which failed to run - for i = 1, #failed_mods do - unload_mod(failed_mods[i]) - end - - debug_log({'hotpatch-info.complete', {'hotpatch-info.loading-installed-mods'}}) - debug_log({'hotpatch-info.complete', {'hotpatch-info.on-load'}}) -end - -on_configuration_changed = function(config) - debug_log({'hotpatch-info.on-configuration-changed'}) - local failed_mods = {} - for i = 1, #loaded_mods do - if not mod_on_configuration_changed(i, config) then - table.insert(failed_mods, i) - end - end - - for i = 1, #failed_mods do - unload_mod(failed_mods[i]) - end - debug_log({'hotpatch-info.complete', {'hotpatch-info.on-configuration-changed'}}) -end - -if debug_log_on_tick then - on_tick = function(e) - debug_log({'hotpatch-trace.event-processing', 'on_tick'}) - local mod - local f - for i = 1, #loaded_mods do - mod = loaded_mods[i] - f = mod.on_tick - if f then - debug_log({'hotpatch-trace.event-running', 'on_tick'}, mod.name) - f(e) - end - end - end -else - on_tick = function(e) - local mod - local f - for i = 1, #loaded_mods do - mod = loaded_mods[i] - f = mod.on_tick - if f then - f(e) - end - end - end -end - - -script.on_init(on_init) -script.on_load(on_load) -script.on_configuration_changed(on_configuration_changed) -script.on_event(defines.events.on_tick, on_tick) - ---private API, don't use this -local mod_tools_internal = setmetatable({ - -- mod installation/uninstallation support functions - -- These take a mod NAME as a first argument - install_mod = install_mod, - find_installed_mod = find_installed_mod, - install_mod_file = install_mod_file, - uninstall_mod = uninstall_mod, - - -- mod interaction functions - -- These take a LOADED INDEX as a first argument, except load_mod, which takes an INSTALLED INDEX - load_mod = load_mod, - find_loaded_mod = find_loaded_mod, - run_mod = run_mod, - reset_mod = reset_mod, - reset_mod_events = reset_mod_events, - register_mod_events = register_mod_events, - unload_mod = unload_mod, - - -- internal callbacks when a mod registers events - register_event = register_event, - register_nth_tick = register_nth_tick, - register_on_tick = register_on_tick, - - -- mod bootstrap functions - -- These take a LOADED INDEX as a first argument - mod_on_init = mod_on_init, - mod_on_load = mod_on_load, - mod_on_configuration_changed = mod_on_configuration_changed, - - static_mods = static_mods, - console = console, - debug_log = debug_log, - loaded_mods = loaded_mods, - loaded_libraries = loaded_libraries, - installed_mods = setmetatable({}, { - __index = function(_, k) - return global.mods[k] - end, - __newindex = function(_, __, ___) - -- do nothing - end, - __len = function(_) - return #global.mods - end, - __pairs = function(t) local function iter(_, k) local v; v = global.mods[k+1]; if v then return k+1, v end; end; return iter, t, 0 end, - __ipairs = function(t) local function iter(_, k) local v; v = global.mods[k+1]; if v then return k+1, v end; end; return iter, t, 0 end, - __metatable = false, - }) -},{ - __index = function(_, k) - debug_log({'hotpatch-error.invalid-API-access', k}, nil, 3) - end, - __newindex = function(_, __, ___) - -- do nothing - end, - -- Don't let mods muck around - __metatable = false, -}) - ---public API -local mod_tools = setmetatable({ - -- most code should use static_mod - static_mod = static_mod, -- (name, version, code, files) - set_debug_level = function(level) - --debug_level = tonumber(_ENV.debug_settings.level) or debug_levels[_ENV.debug_settings.level] - debug_level = tonumber(level) or debug_levels[level] - end, -},{ - __index = mod_tools_internal, - __newindex = function(_, __, ___) - -- do nothing, read-only table - end, - -- Don't let mods muck around - __metatable = false, -}) - - -return mod_tools \ No newline at end of file diff --git a/lib/scenarios/Hotpatch/hotpatch/remote-interface.lua b/lib/scenarios/Hotpatch/hotpatch/remote-interface.lua deleted file mode 100644 index 8c6c6e01..00000000 --- a/lib/scenarios/Hotpatch/hotpatch/remote-interface.lua +++ /dev/null @@ -1,322 +0,0 @@ -local hotpatch_tools = require 'hotpatch.mod-tools' -hotpatch_tools.static_mod('hotpatch-remote-interface', '1.0.3', [===[ ---[[ - -Copyright 2018 Chrisgbk -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -]] --- MIT License, https://opensource.org/licenses/MIT - -local hotpatch_tools = require 'hotpatch.mod-tools' - ---load private API --- mod installation/uninstallation support functions --- These take a mod NAME as a first argument -local install_mod = hotpatch_tools.install_mod -local find_installed_mod = hotpatch_tools.find_installed_mod -local install_mod_file = hotpatch_tools.install_mod_file -local uninstall_mod = hotpatch_tools.uninstall_mod - --- mod interaction functions --- These take a LOADED INDEX as a first argument, except load_mod, which takes an INSTALLED INDEX -local load_mod = hotpatch_tools.load_mod -local find_loaded_mod = hotpatch_tools.find_loaded_mod -local run_mod = hotpatch_tools.run_mod -local reset_mod = hotpatch_tools.reset_mod -local reset_mod_events = hotpatch_tools.reset_mod_events -local register_mod_events = hotpatch_tools.register_mod_events -local unload_mod = hotpatch_tools.unload_mod - --- internal callbacks when a mod registers events -local register_event = hotpatch_tools.register_event -local register_nth_tick = hotpatch_tools.register_nth_tick -local register_on_tick = hotpatch_tools.register_on_tick - --- mod bootstrap functions --- These take a LOADED INDEX as a first argument -local mod_on_init = hotpatch_tools.mod_on_init -local mod_on_load = hotpatch_tools.mod_on_load -local mod_on_configuration_changed = hotpatch_tools.mod_on_configuration_changed - -local static_mods = hotpatch_tools.static_mods -local console = hotpatch_tools.console -local debug_log = hotpatch_tools.debug_log -local loaded_mods = hotpatch_tools.loaded_mods -local installed_mods = hotpatch_tools.installed_mods - -local remote_interface = {} - ---IMPORTANT: WHEN COPY PASTING CODE TO CONSOLE THE CODE MUST HAVE SINGLE LINE COMMENTS REMOVED --- FACTORIO CONSOLE STRIPS LINEFEEDS WHICH MAKES ALL THE CODE BECOME COMMENTED OUT ---TODO: gate these behind admin permissions; if (game.player and game.player.admin) should work for console commands? - -debug_log({'hotpatch-info.remote-installing'}) - -remote_interface['install'] = function(mod_name, mod_version, mod_code, mod_files, only_install) - -- this installs a new mod and runs on_init, then registers events - -- Note that mods may expect that certain events haven't been called yet when their on_init is ran - -- This may prevent them from functioning properly, without manually calling the events they expect - -- examples: on_player_created - local caller = game.player or console - if caller.admin then - if mod_files then - for k,_ in pairs(mod_files) do - if k == 'control' then - caller.print('ERROR: control.lua loaded twice for mod ' .. mod_name) - return - end - end - end - local installed_index = find_installed_mod(mod_name) - local loaded_index = find_loaded_mod(mod_name) - if installed_index then - local old_version = installed_mods[installed_index].version - if old_version then - debug_log({'hotpatch-warning.already-exists', mod_name, old_version}) - debug_log({'hotpatch-warning.reinstalling', mod_name, old_version}) - end - if loaded_index then - unload_mod(loaded_index) - end - end - - install_mod(mod_name, mod_version, mod_code, mod_files) - if not only_install then - installed_index = find_installed_mod(mod_name) - if installed_index then - if not load_mod(installed_index) then - caller.print('compilation failed for mod ' .. mod_name) - return - end - loaded_index = find_loaded_mod(mod_name) - if loaded_index then - if not run_mod(loaded_index) then - caller.print('execution failed for mod ' .. mod_name) - unload_mod(loaded_index) - return - end - if not mod_on_init(loaded_index) then - caller.print('on_init failed for mod ' .. mod_name) - unload_mod(loaded_index) - return - end - -- TODO: notify all mods - -- TODO: determine vanilla behaviour and replicate it - if not mod_on_configuration_changed(loaded_index, {mod_changes = {mod_name={new_version=mod_version}}}) then - caller.print('on_configuration_changed failed for mod ' .. mod_name) - unload_mod(loaded_index) - return - end - else - end - else - end - end - else - caller.print('You must be an admin to run this command.') - end -end - -remote_interface['run'] = function(mod_name) - -- this runs on_init, then registers events - -- Note that mods may expect that certain events haven't been called yet when their on_init is ran - -- This may prevent them from functioning properly, without manually calling the events they expect - -- examples: on_player_created - local caller = game.player or console - if caller.admin then - local installed_index = find_installed_mod(mod_name) - local loaded_index = find_loaded_mod(mod_name) - local mod - local version - if installed_index and not loaded_index then - if not load_mod(installed_index) then - caller.print('compilation failed for mod ' .. mod_name) - return - end - loaded_index = find_loaded_mod(mod_name) - else - if loaded_index then - mod = loaded_mods[loaded_index] - version = mod.version - debug_log({'hotpatch-warning.already-loaded', mod_name, version}) - debug_log({'hotpatch-warning.reinitializing', mod_name, version}) - end - end - - if loaded_index then - if not run_mod(loaded_index) then - caller.print('execution failed for mod ' .. mod_name) - unload_mod(loaded_index) - return - end - if not mod_on_init(loaded_index) then - caller.print('on_init failed for mod ' .. mod_name) - unload_mod(loaded_index) - return - end - -- TODO: notify all mods - -- TODO: determine vanilla behaviour and replicate it - if not mod_on_configuration_changed(loaded_index, {mod_changes = {mod_name={new_version=version}}}) then - caller.print('on_configuration_changed failed for mod ' .. mod_name) - unload_mod(loaded_index) - return - end - end - else - caller.print('You must be an admin to run this command.') - end -end - -remote_interface['update'] = function(mod_name, mod_version, mod_code, mod_files) - -- this updates an existing mod - -- the current mods events are de-registered, the new code is installed, on_load is triggered, and then events are registered - -- finally, the mod is informed of the update, so it can run migrations from the previous version - -- TODO: validation - local caller = game.player or console - if caller.admin then - if mod_files then - for k,_ in pairs(mod_files) do - if k == 'control' then - caller.print('ERROR: control.lua loaded twice for mod ' .. mod_name) - return - end - end - end - local installed_index = find_installed_mod(mod_name) - if installed_index then - ; - end - local loaded_index = find_loaded_mod(mod_name) - local old_version - if loaded_index then - old_version = loaded_mods[loaded_index].version - unload_mod(loaded_index) - end - install_mod(mod_name, mod_version, mod_code, mod_files) - installed_index = find_installed_mod(mod_name) - if not load_mod(installed_index) then - caller.print('compilation failed for mod ' .. mod_name) - return - end - loaded_index = find_loaded_mod(mod_name) - if not run_mod(loaded_index) then - caller.print('execution failed for mod ' .. mod_name) - unload_mod(loaded_index) - return - end - if old_version then - if not mod_on_load(loaded_index) then - caller.print('on_load failed for mod ' .. mod_name) - unload_mod(loaded_index) - return - end - -- The mod must do any migrations here - -- TODO: notify all mods - if not mod_on_configuration_changed(loaded_index, {mod_changes = {mod_name={old_version=old_version, new_version=mod_version}}}) then - caller.print('on_configuration_changed failed for mod ' .. mod_name) - unload_mod(loaded_index) - return - end - else - -- first time install - if not mod_on_init(loaded_index) then - caller.print('on_init failed for mod ' .. mod_name) - unload_mod(loaded_index) - return - end - -- TODO: notify all mods - if not mod_on_configuration_changed(loaded_index, {mod_changes = {mod_name={new_version=mod_version}}}) then - caller.print('on_configuration_changed failed for mod ' .. mod_name) - unload_mod(loaded_index) - return - end - end - else - caller.print('You must be an admin to run this command.') - end -end - -remote_interface['install_mod_file'] = function(mod_name, mod_file, mod_file_code) - local caller = game.player or console - if caller.admin then - install_mod_file(mod_name, mod_file, mod_file_code) - else - caller.print('You must be an admin to run this command.') - end -end - -remote_interface['exec'] = function(mod_name, exec_code) - local caller = game.player or console - if caller.admin then - local loaded_index = find_loaded_mod(mod_name) - local env = loaded_mods[loaded_index].env - - local success - local code, err = load(exec_code, exec_code, 't', env) - if code then - success, err = pcall(code) - if not success then - debug_log(err) - end - else - debug_log(err) - end - else - caller.print('You must be an admin to run this command.') - end -end - ---TODO: most of this function -remote_interface['clean'] = function(mod_name) - -- Removes ALL mod data and reinitializes - -- doesn't remove things a mod may add like surfaces, etc - local caller = game.player or console - if caller.admin then - --mod_reset(mod_name) - --run_mod(mod_name) - --mod_on_init(mod_name) - local loaded_index = find_loaded_mod(mod_name) - local mod = loaded_mods[loaded_index] - local version = mod.version - mod_on_configuration_changed(loaded_index, {mod_changes = {mod_name={new_version=version}}}) - else - caller.print('You must be an admin to run this command.') - end -end - -remote_interface['uninstall'] = function(mod_name) - -- Uninstalls a mod - local caller = game.player or console - if caller.admin then - uninstall_mod(mod_name) - else - caller.print('You must be an admin to run this command.') - end -end - -remote.add_interface('hotpatch', remote_interface) - -debug_log({'hotpatch-info.complete', {'hotpatch-info.remote-installing'}}) - ---]===]) - ---[[ - -remote.call('hotpatch, 'install', 'test', '1.0.0', [===[ -script.on_event(defines.events.on_player_changed_position, function(e) - game.print('changed position') -end -]===] - -local updated_code = .... -remote.call('hotpatch', 'update', 'test', '1.0.1', updated_code) - - -]] - -return true \ No newline at end of file diff --git a/lib/scenarios/Hotpatch/locale/af/freeplay.cfg b/lib/scenarios/Hotpatch/locale/af/freeplay.cfg deleted file mode 100644 index 32c30879..00000000 --- a/lib/scenarios/Hotpatch/locale/af/freeplay.cfg +++ /dev/null @@ -1,3 +0,0 @@ -msg-intro=Hierdie is Factorio vryspel. Jou taak is om 'n vuurpyl in die ruimte in te stuur. Doen dit deur 'n vuurpyl te bou en hom te lanseer. Jy sal baie gevorderde tegnologiëe moet navors om die Vuurpyl Silo te ontsluit. Begin klein, werk jou pad op met outomatisering en moenie vergeet om jouself te beskerm teen die inboorlinge nie. -scenario-name=Vryspel - diff --git a/lib/scenarios/Hotpatch/locale/ar/freeplay.cfg b/lib/scenarios/Hotpatch/locale/ar/freeplay.cfg deleted file mode 100644 index 9715b027..00000000 --- a/lib/scenarios/Hotpatch/locale/ar/freeplay.cfg +++ /dev/null @@ -1,3 +0,0 @@ -msg-intro=.بكوكلا اذه ناكس م كسفن يمحت نا ىسنت الو ,ةيكيتاموتوألا ربع كقيرط ينبتسو ,رفصلا نم أدبتس .كلت خيراوصلا قالطإ ةطحم حتف عيطتست فيكل ةروطتملا ايجولونكتلل ثاحبأب مايقلا ىلإ جاتحتس .اهنم خوراص قالطإ مث خيراوص قالطإ ةطحم ءانب قيرط نع كلذب مايقلا كنكمي ,ءاضفلا ىلإ اخوراص قلطت نا يه كتمهم ,factorioـل رحلا بعللا روط وه اذه -scenario-name=رحلا بعللا - diff --git a/lib/scenarios/Hotpatch/locale/be/freeplay.cfg b/lib/scenarios/Hotpatch/locale/be/freeplay.cfg deleted file mode 100644 index 875a23b7..00000000 --- a/lib/scenarios/Hotpatch/locale/be/freeplay.cfg +++ /dev/null @@ -1,3 +0,0 @@ -msg-intro=Гэта рэжым свабоднай гульні Factorio. Вашая мэта - запусціць ракету ў космас. Дзеля гэтага вам трэба пабудаваць ракетную шахту і саму ракету са спадарожнікам. Вы мусіце даследаваць перадавыя тэхналогіі, каб навучыцца будаваць ракетную шахту. Пачынайце з малога, працуйце над аўтаматызацыяй вытворчасці, даследуйце новыя тэхналогіі і не забывайцеся на абарону ад «тутэйшых». -scenario-name=Свабодная гульня - diff --git a/lib/scenarios/Hotpatch/locale/bg/freeplay.cfg b/lib/scenarios/Hotpatch/locale/bg/freeplay.cfg deleted file mode 100644 index ee1ddd3d..00000000 --- a/lib/scenarios/Hotpatch/locale/bg/freeplay.cfg +++ /dev/null @@ -1,3 +0,0 @@ -msg-intro=Това е режимът на свободна игра. Задачата ви е да изстреляте ракета в космоса. За това ще е нужно да построите ракетна шахта и изстреляте ракетата със спътник. Ще трябва да проучите развити технологии, които ще отключат ракетната шахта. Започнете с малко и постепенно автоматизирайте процеса, като не забравяте да се защитавате от местните форми на живот. -scenario-name=Свободна игра - diff --git a/lib/scenarios/Hotpatch/locale/ca/freeplay.cfg b/lib/scenarios/Hotpatch/locale/ca/freeplay.cfg deleted file mode 100644 index eb6743b3..00000000 --- a/lib/scenarios/Hotpatch/locale/ca/freeplay.cfg +++ /dev/null @@ -1,3 +0,0 @@ -msg-intro=Aquest és el mode lliure del Factorio. La vostra tasca és construir un coet i enviar-lo a l'espai. Ho haureu de fer construint un centre de coets espacials i llençant un coet. Necessitareu investigar tecnologies avançades per poder-ho fer. Aneu pas a pas, avanceu cap a l'automatització i no oblideu protegir-vos dels natius. -scenario-name=Mode lliure - diff --git a/lib/scenarios/Hotpatch/locale/cs/freeplay.cfg b/lib/scenarios/Hotpatch/locale/cs/freeplay.cfg deleted file mode 100644 index d4835915..00000000 --- a/lib/scenarios/Hotpatch/locale/cs/freeplay.cfg +++ /dev/null @@ -1,3 +0,0 @@ -msg-intro=Toto je volná hra Factorio. Tvým úkolem je vypustit raketu do vesmíru. Postav raketové silo a vypusť raketu. Pro odemknutí raketového sila budeš muset vyzkoumat pokročilé technologie. Začni v malém, postupuj s automatizací a nezapomeň se chránit před domorodci. -scenario-name=Volná hra - diff --git a/lib/scenarios/Hotpatch/locale/da/freeplay.cfg b/lib/scenarios/Hotpatch/locale/da/freeplay.cfg deleted file mode 100644 index 3b0551f0..00000000 --- a/lib/scenarios/Hotpatch/locale/da/freeplay.cfg +++ /dev/null @@ -1,3 +0,0 @@ -msg-intro=Dette er Factorio freeplay. Din opgave er at affyre en raket ud i rummet. Gør dette ved at konstruere en raketsilo og affyr en raket. Du bliver nød til at undersøge avancerede teknologier for at låse op for raketsiloen. Start småt og arbejd din vej op med automatisering og glem ikke at beskytte dig selv fra de indfødte. -scenario-name=Fri lej - diff --git a/lib/scenarios/Hotpatch/locale/de/freeplay.cfg b/lib/scenarios/Hotpatch/locale/de/freeplay.cfg deleted file mode 100644 index bf061ec8..00000000 --- a/lib/scenarios/Hotpatch/locale/de/freeplay.cfg +++ /dev/null @@ -1,3 +0,0 @@ -msg-intro=Du befindest dich im freien Spiel Modus von Factorio. Ziel ist es, eine Rakete ins Weltall zu schicken. Baue hierzu das Raketensilo um die Rakete starten zu können. Um das Raketensilo freischalten zu können wirst du fortgeschrittene Technologien erforschen müssen. Fange klein an und erforsche die Automation. Vergiss aber nicht, dich vor den Ureinwohner zu schützen. -scenario-name=Freies Spiel - diff --git a/lib/scenarios/Hotpatch/locale/el/freeplay.cfg b/lib/scenarios/Hotpatch/locale/el/freeplay.cfg deleted file mode 100644 index b892c3dc..00000000 --- a/lib/scenarios/Hotpatch/locale/el/freeplay.cfg +++ /dev/null @@ -1,3 +0,0 @@ -msg-intro=Αυτό είναι το ελεύθερο παιχνίδι του Factorio. Ο στόχος σας είναι να εκτοξεύσετε έναν πύραυλο στο διάστημα. Αυτό το κάνετε κατασκευάζοντας ένα σιλό πυραύλου και εκτοξεύοντας έναν πύραυλο. Θα πρέπει να ερευνήσετε προηγμένες τεχνολογίες για να ξεκλειδώσετε το σιλό πυραύλου. Ξεκινήστε χαμηλά, φτάστε ψηλά χρησιμοποιώντας αυτοματισμούς και μην ξεχάσετε να προστατέψετε τον εαυτό σας από τους ντόπιους. -scenario-name=Ελεύθερο παιχνίδι - diff --git a/lib/scenarios/Hotpatch/locale/en/freeplay.cfg b/lib/scenarios/Hotpatch/locale/en/freeplay.cfg deleted file mode 100644 index f29f8fb9..00000000 --- a/lib/scenarios/Hotpatch/locale/en/freeplay.cfg +++ /dev/null @@ -1,2 +0,0 @@ -msg-intro=This is the Factorio freeplay. Your task is to launch a rocket into space. Do this by constructing a rocket silo and launching a rocket. You will need to research advanced technologies in order to unlock the rocket silo. Start small, work your way up with automation and don't forget to protect yourself from the natives. -scenario-name=Freeplay diff --git a/lib/scenarios/Hotpatch/locale/en/hotpatch.cfg b/lib/scenarios/Hotpatch/locale/en/hotpatch.cfg deleted file mode 100644 index 50c9f6f8..00000000 --- a/lib/scenarios/Hotpatch/locale/en/hotpatch.cfg +++ /dev/null @@ -1,81 +0,0 @@ -[hotpatch] -log=@__1__:__2__] __3__ -log-mod=@__1__:__2__][__4__] __3__ -severe=SEVERE ERROR: __1__ -error=ERROR: __1__ -warning=WARNING: __1__ -info=INFO: __1__ -verbose=INFO: __1__ -trace=TRACE: __1__ - -[hotpatch-info] -logging-enabled=Logging enabled -metatable-installed=_ENV metatable installed -complete=__1__ Complete! -on-init=initializing... -on-load=loading... -on-configuration-changed=mod configuration changed... -installing-included-mods=installing included mods... -loading-installed-mods=loading installed mods... -loading-libs=loading Factorio.data.core.lualib... -loading-library=loading library: __1__ -uninstalling=Uninstalling mod... -installing=Installing version __1__... -installing-file=Installing file __1__... -script-shim=setting up mod script shim... -setting-env=setting up mod _ENV... -loading=loading... -unloading=unloading... -running=running... -must-be-admin=You must be an admin to run this command -remote-installing=installing remote interface - -[hotpatch-trace] -nil-var-access=_ENV nil variable access: __1__ -nil-var-assignment=_ENV variable assignment: __1__ -event-registering=registering events... -on-tick-event-registered=registered on_tick event... -on-event-registered=registered event __1__... -on-nth-tick-event-registered=registered nth_tick event __1__... -event-running=running event __1__... -nth-tick-event-running=running nth_event __1__... -mod-on-init=running on_init... -mod-on-load=running on_load... -mod-on-configuration-changed=running on_configuration_changed... -adding-event=adding event __1__ -adding-nth-tick-event=adding nth_tick event __1__ -caching-event=caching event: __1__... -caching-nth-tick-event=caching nth_tick event: __1__... -event-processing=processing event __1__... -nth-tick-event-processing=processing nth_tick event __1__... -nth-tick-handler-added=added nth_tick handler: __1__ -nth-tick-handler-removed=removed nth_tick handler: __1__ -on-tick-handler-added=added on_tick handler -on-tick-handler-removed=removed on_tick handler -on-event-handler-added=added event handler: __1__ -on-event-handler-removed=removed event handler: __1__ -cached-load-require=loading cached require'd file: __1__... -load-require=loading require'd file: __1__... -load-core-lib=loading from Factorio.data.core.lualib: __1__... - -[hotpatch-warning] -contains-comments=mod code contains comments! -contains-comments-no-lf=mod contains comments and no linefeed! -contains-comments-console=comments from console will comment out the entire code! -reset-events-not-running=tried to reset events for mod that isn't running! -remote-interface-exists=remote interface __1__ already exists, removing... -command-exists=command __1__ already exists, removing... -already-exists=mod already exists: __1__ __2__ -reinstalling=reinstalling mod in-place: __1__ __2__ -already-loaded=mod already loaded: __1__ __2__ -reinitializing=reinitializing: __1__ __2__ - -[hotpatch-error] -invalid-API-access=Invalid API access: __1__ -not-installed=mod not installed, cannot install file for mod that does not exist -compilation-failed=compilation failed for mod -execution-failed=execution failed for mod -path-not-found=path '__1__' not found -on-init-failed=on_init failed -on-load-failed=on_load failed -on-configuration-changed-failed=on_configuration_changed failed \ No newline at end of file diff --git a/lib/scenarios/Hotpatch/locale/eo/freeplay.cfg b/lib/scenarios/Hotpatch/locale/eo/freeplay.cfg deleted file mode 100644 index da339854..00000000 --- a/lib/scenarios/Hotpatch/locale/eo/freeplay.cfg +++ /dev/null @@ -1,2 +0,0 @@ -scenario-name=Libera Ludado - diff --git a/lib/scenarios/Hotpatch/locale/es-ES/freeplay.cfg b/lib/scenarios/Hotpatch/locale/es-ES/freeplay.cfg deleted file mode 100644 index f0e4be64..00000000 --- a/lib/scenarios/Hotpatch/locale/es-ES/freeplay.cfg +++ /dev/null @@ -1,3 +0,0 @@ -msg-intro=Este es el modo de juego libre de Factorio. Tu tarea es la de lanzar un cohete al espacio. Logra esto construyendo un Silo de Cohetes y lanzando un cohete con un satélite. Necesitarás investigar tecnologías avanzadas para poder desbloquear el Silo de Cohetes. Comienza poco a poco, trabaja en tu sistema de automatización y no te olvides de protegerte de los nativos. -scenario-name=Modo libre - diff --git a/lib/scenarios/Hotpatch/locale/et/freeplay.cfg b/lib/scenarios/Hotpatch/locale/et/freeplay.cfg deleted file mode 100644 index 6341de8b..00000000 --- a/lib/scenarios/Hotpatch/locale/et/freeplay.cfg +++ /dev/null @@ -1,2 +0,0 @@ -scenario-name=Vabamäng - diff --git a/lib/scenarios/Hotpatch/locale/fi/freeplay.cfg b/lib/scenarios/Hotpatch/locale/fi/freeplay.cfg deleted file mode 100644 index e5c8f090..00000000 --- a/lib/scenarios/Hotpatch/locale/fi/freeplay.cfg +++ /dev/null @@ -1,3 +0,0 @@ -msg-intro=Tämä on Factorion vapaapelimuoto. Tavoitteesi on lähettää satelliitti ulkoavaruuteen rakentamalla rakettisiilo ja laukaisemalla raketti. Rakettisiilon voit rakentaa vasta, kun olet kehittänyt siihen tarvittavat huipputeknologiat. Aloita pienestä ja automatisoi kaikki tarvittava, äläkä unohda suojella itseäsi alkuasukkailta. -scenario-name=Vapaapeli - diff --git a/lib/scenarios/Hotpatch/locale/fr/freeplay.cfg b/lib/scenarios/Hotpatch/locale/fr/freeplay.cfg deleted file mode 100644 index 78bc166a..00000000 --- a/lib/scenarios/Hotpatch/locale/fr/freeplay.cfg +++ /dev/null @@ -1,3 +0,0 @@ -msg-intro=Il s'agit du mode de jeu libre de Factorio. Votre tâche consiste à lancer une fusée dans l'espace. Faites ceci en construisant une fusée et en la lançant dans l'espace. Vous aurez besoin de rechercher des technologies de pointe afin de débloquer le silo. Commencez petit, évoluez en automatisant et n'oubliez pas de vous protéger contre les autochtones. -scenario-name=Jeu libre - diff --git a/lib/scenarios/Hotpatch/locale/he/freeplay.cfg b/lib/scenarios/Hotpatch/locale/he/freeplay.cfg deleted file mode 100644 index d6f328d6..00000000 --- a/lib/scenarios/Hotpatch/locale/he/freeplay.cfg +++ /dev/null @@ -1,3 +0,0 @@ -msg-intro=.םיימוקמה םירוציה ינפמ םכמצע לע ןגהל וחכשת לאו ,יטמוטוא ןוכימ תועצמאב םכלש הישעתה תא וביחרה ,ןטקב וליחתה .ןקתמה תיינב תא רשפאל ידכ תומדקתמ תויגולונכט רוקחל םכילע היהי .ליט ותועצמאב רגשלו םיליט רוגיש ןקתמ תונבל םכילע .ללחל ליט רגשל איה םכתמישמ .ויִרֹוטְקַפ לש ישפוחה שיחרתה והז -scenario-name=ישפוח קחשמ - diff --git a/lib/scenarios/Hotpatch/locale/hu/freeplay.cfg b/lib/scenarios/Hotpatch/locale/hu/freeplay.cfg deleted file mode 100644 index 45583a78..00000000 --- a/lib/scenarios/Hotpatch/locale/hu/freeplay.cfg +++ /dev/null @@ -1,3 +0,0 @@ -msg-intro=Ez a Factorio szabad játék módja. A feladatod az, hogy kilőj egy rakétát az űr felderítésére. Ehhez egy Rakétasilót kell építened, melyből aztán egy műholddal felszerelt rakétát kell kilőnöd. A Rakétasiló kifejlesztéséhez különféle technológiákat kell fejlesztened. Kezdj kicsiben és küzdd fel magad az automatizálásban. Ne felejts el védekezni a honos fajok ellen. -scenario-name=Szabad játék - diff --git a/lib/scenarios/Hotpatch/locale/it/freeplay.cfg b/lib/scenarios/Hotpatch/locale/it/freeplay.cfg deleted file mode 100644 index 564aab9a..00000000 --- a/lib/scenarios/Hotpatch/locale/it/freeplay.cfg +++ /dev/null @@ -1,3 +0,0 @@ -msg-intro=Questa è la modalità libera di Factorio. Il tuo obiettivo è quello di lanciare un razzo nello spazio e per farlo dovrai costruire un silo per razzi. Avrai bisogno di fare ricerche avanzate per sbloccare il silo. Inizia con poco, lavora sull'automazione e non dimenticarti di difenderti dai nativi. -scenario-name=Gioco libero - diff --git a/lib/scenarios/Hotpatch/locale/ja/freeplay.cfg b/lib/scenarios/Hotpatch/locale/ja/freeplay.cfg deleted file mode 100644 index 2cb436fb..00000000 --- a/lib/scenarios/Hotpatch/locale/ja/freeplay.cfg +++ /dev/null @@ -1,3 +0,0 @@ -msg-intro=これは、Factorioのフリープレイです。ロケットサイロを建造し、ロケットを宇宙へ打ち上げるのがあなたの任務です。ロケットサイロの建造を行うためには高度な技術研究を行う必要があります。小規模に始め、自動化しつつ進み、原住生物から身を守ることも忘れないでください。 -scenario-name=フリープレイ - diff --git a/lib/scenarios/Hotpatch/locale/ko/freeplay.cfg b/lib/scenarios/Hotpatch/locale/ko/freeplay.cfg deleted file mode 100644 index 52568dca..00000000 --- a/lib/scenarios/Hotpatch/locale/ko/freeplay.cfg +++ /dev/null @@ -1,3 +0,0 @@ -msg-intro=Factorio 자유플레이입니다. 당신의 목표는 우주로 로켓을 발사하는 것입니다. 로켓 격납고를 건설하고 로켓을 발사하여 이 목표를 달성하세요. 로켓 격납고의 잠금을 해제하기 위해 고급 기술들을 연구할 필요가 있습니다. 작은 것부터 시작해 자동화로 차근차근 발전하고, 토착 생물로부터 자신을 보호하는 것도 잊지 마세요. -scenario-name=자유플레이 - diff --git a/lib/scenarios/Hotpatch/locale/lt/freeplay.cfg b/lib/scenarios/Hotpatch/locale/lt/freeplay.cfg deleted file mode 100644 index ff193212..00000000 --- a/lib/scenarios/Hotpatch/locale/lt/freeplay.cfg +++ /dev/null @@ -1,3 +0,0 @@ -msg-intro=Tai Factorio laisvas žaidimas. Jūsų užduotis - paleisti raketą į kosmosą. Tam reikia pastatyti Raketos šachtą ir paleisti raketą su palydovu. Reikės atrasti pažangias technologijas kad atrakintumėte Raketos šachtą. Pradėkite palengva, tobulinkite automatiką ir neužmirškite apsisaugoti nuo vietinių. -scenario-name=Žaisti laisva forma - diff --git a/lib/scenarios/Hotpatch/locale/lv/freeplay.cfg b/lib/scenarios/Hotpatch/locale/lv/freeplay.cfg deleted file mode 100644 index 3211d461..00000000 --- a/lib/scenarios/Hotpatch/locale/lv/freeplay.cfg +++ /dev/null @@ -1,2 +0,0 @@ -scenario-name=Brīvspēle - diff --git a/lib/scenarios/Hotpatch/locale/nl/freeplay.cfg b/lib/scenarios/Hotpatch/locale/nl/freeplay.cfg deleted file mode 100644 index 7cb4f314..00000000 --- a/lib/scenarios/Hotpatch/locale/nl/freeplay.cfg +++ /dev/null @@ -1,3 +0,0 @@ -msg-intro=Dit is Factorio vrij spel. Je taak is om een raket te lanceren in de ruimte. Bouw hiervoor een raket-silo en lanceer een raket. Je zult geavanceerde technologieën moeten onderzoeken om de raket-silo vrij te spelen. Begin op kleine schaal, breidt uit en automatiseer je fabriek en vergeet niet jezelf te beschermen tegen de inheemse bewoners. -scenario-name=Freeplay - diff --git a/lib/scenarios/Hotpatch/locale/no/freeplay.cfg b/lib/scenarios/Hotpatch/locale/no/freeplay.cfg deleted file mode 100644 index 67e21780..00000000 --- a/lib/scenarios/Hotpatch/locale/no/freeplay.cfg +++ /dev/null @@ -1,3 +0,0 @@ -msg-intro=Dette er Factorio freeplay. Din oppgave er å sende en rakett til verdensrommet. Bygg en rakettsilo og send opp en rakett med en satellitt. Du blir nødt til å utforske avanserte teknologier for å få tilgang til rakettsiloen. Begynn i det små, jobb deg oppover med hjelp av automatisering, men ikke glem å beskytte deg mot de innfødte. -scenario-name=Frittspill - diff --git a/lib/scenarios/Hotpatch/locale/pl/freeplay.cfg b/lib/scenarios/Hotpatch/locale/pl/freeplay.cfg deleted file mode 100644 index 55b21b60..00000000 --- a/lib/scenarios/Hotpatch/locale/pl/freeplay.cfg +++ /dev/null @@ -1,3 +0,0 @@ -msg-intro=Oto swobodna rozgrywka Factorio. Twoim zadaniem jest wysłanie rakiety w kosmos. Dokonaj tego przez skonstruowanie silosu rakietowego i wystrzelenie rakiety. Żeby odblokować silos rakietowy będziesz musiał odkryć zaawansowane technologie. Rozpocznij od podstaw, rozwijaj się wykorzystując automatyzację i nie zapomnij o obronie przed rodzimymi mieszkańcami planety. -scenario-name=Gra swobodna - diff --git a/lib/scenarios/Hotpatch/locale/pt-BR/freeplay.cfg b/lib/scenarios/Hotpatch/locale/pt-BR/freeplay.cfg deleted file mode 100644 index 86fd8546..00000000 --- a/lib/scenarios/Hotpatch/locale/pt-BR/freeplay.cfg +++ /dev/null @@ -1,3 +0,0 @@ -msg-intro=Este é o modo livre de Factorio. Sua tarefa é lançar um foguete no espaço. Faça isso construindo um silo de foguetes e lançando um foguete. Você precisará pesquisar tecnologias avançadas para desbloquear o silo de foguetes. Comece pequeno, trabalhe seu caminho com automação e não se esqueça de se proteger dos nativos. -scenario-name=Jogo livre - diff --git a/lib/scenarios/Hotpatch/locale/pt-PT/freeplay.cfg b/lib/scenarios/Hotpatch/locale/pt-PT/freeplay.cfg deleted file mode 100644 index 54dcac04..00000000 --- a/lib/scenarios/Hotpatch/locale/pt-PT/freeplay.cfg +++ /dev/null @@ -1,3 +0,0 @@ -msg-intro=Este é o modo livre do Factorio. O teu objetivo é lançar um foguetão para o espaço. Faz isto, contruindo um silo de foguetão e lançando-o.\nVais precisar de pesquisar tecnologias avançadas para desploquear o silo de foguetão. Começa simples, e trabalha e evolui com automação mas não te esqueças de proteger do nativos. -scenario-name=Modo Livre - diff --git a/lib/scenarios/Hotpatch/locale/ro/freeplay.cfg b/lib/scenarios/Hotpatch/locale/ro/freeplay.cfg deleted file mode 100644 index 7af1cdb4..00000000 --- a/lib/scenarios/Hotpatch/locale/ro/freeplay.cfg +++ /dev/null @@ -1,3 +0,0 @@ -msg-intro=Acesta este modul Factorio freeplay. Sarcina ta este de a lansa o racheta în spațiu. Fă acest lucru prin construirea unui siloz de rachete și lansarea unei rachete. Va trebui să cercetezi tehnologiile avansate pentru a debloca silozul de rachete. Începe mic, avansează cu automatizarea și nu uita să te protejezi de nativi. -scenario-name=Freeplay - diff --git a/lib/scenarios/Hotpatch/locale/ru/freeplay.cfg b/lib/scenarios/Hotpatch/locale/ru/freeplay.cfg deleted file mode 100644 index 39c89361..00000000 --- a/lib/scenarios/Hotpatch/locale/ru/freeplay.cfg +++ /dev/null @@ -1,3 +0,0 @@ -msg-intro=Это режим свободной игры Factorio. Ваша задача - запустить ракету в космос. Для её запуска потребуется исследовать новые технологии и создать ракетную шахту. Начните с малого, работайте над автоматизацией производства и не забывайте о защите от местных форм жизни. -scenario-name=Свободная игра - diff --git a/lib/scenarios/Hotpatch/locale/sk/freeplay.cfg b/lib/scenarios/Hotpatch/locale/sk/freeplay.cfg deleted file mode 100644 index 7f988507..00000000 --- a/lib/scenarios/Hotpatch/locale/sk/freeplay.cfg +++ /dev/null @@ -1,3 +0,0 @@ -msg-intro=Toto je voľná hra Factorio. Tvojou úlohou je vypustiť raketu do vesmíru. Postav raketové silo a vypusti raketu. Pre odomknutie raketového sila budeš musieť vyskúmať pokročilé technológie. Začni v malom, postupuj s automatizáciou a nezabnudni sa chrániť pred domácimi. -scenario-name=Voľná hra - diff --git a/lib/scenarios/Hotpatch/locale/sl/freeplay.cfg b/lib/scenarios/Hotpatch/locale/sl/freeplay.cfg deleted file mode 100644 index 0056dca2..00000000 --- a/lib/scenarios/Hotpatch/locale/sl/freeplay.cfg +++ /dev/null @@ -1,3 +0,0 @@ -msg-intro=To je Factorio freeplay. Vaša naloga je poslati raketo v vesolje. To storite tako, da zgradite silos za raketo in jo skupaj s satelitom izstrelite v vesolje. Raziskati bo potrebno napredne tehnologije, da bi lahko odklenili Raketni silos. Začnete z majhnim, nato nadgradite vse s pomočjo avtomatizacije in ne pozabite, zaščitite se pred domorodci. -scenario-name=Prosta igra - diff --git a/lib/scenarios/Hotpatch/locale/sr/freeplay.cfg b/lib/scenarios/Hotpatch/locale/sr/freeplay.cfg deleted file mode 100644 index 3a449cd4..00000000 --- a/lib/scenarios/Hotpatch/locale/sr/freeplay.cfg +++ /dev/null @@ -1,2 +0,0 @@ -msg-intro=Ово је Factorio freeplay. Твој задатак је да лансираш ракету са сателитом у свемир. Уради ово грађом ракетног силоса. Треба да истражиш пуно напредних технологија да би откључао ракетни силос. Почни мало, а онда напредуј са аутоматизацијом и не заборави да се заштитиш од ванземаљаца. - diff --git a/lib/scenarios/Hotpatch/locale/sv-SE/freeplay.cfg b/lib/scenarios/Hotpatch/locale/sv-SE/freeplay.cfg deleted file mode 100644 index 1393d87d..00000000 --- a/lib/scenarios/Hotpatch/locale/sv-SE/freeplay.cfg +++ /dev/null @@ -1,3 +0,0 @@ -msg-intro=Det här är Factorio frispel. Ditt uppdrag är att avfyra en raket ut i rymden. Gör det genom att tillverka en raket silo och avfyra en raket med en satellit. Du kommer att behöva forska fram avancerade teknologier för att kunna låsa upp raket silon. Börja smått, jobba dig uppåt med hjälp av automatisering och glöm inte att försvara dig själv mot infödingarna. -scenario-name=Frispel - diff --git a/lib/scenarios/Hotpatch/locale/th/freeplay.cfg b/lib/scenarios/Hotpatch/locale/th/freeplay.cfg deleted file mode 100644 index 1bc349e1..00000000 --- a/lib/scenarios/Hotpatch/locale/th/freeplay.cfg +++ /dev/null @@ -1,2 +0,0 @@ -scenario-name=โหมดอิสระ - diff --git a/lib/scenarios/Hotpatch/locale/tr/freeplay.cfg b/lib/scenarios/Hotpatch/locale/tr/freeplay.cfg deleted file mode 100644 index 3fede9d0..00000000 --- a/lib/scenarios/Hotpatch/locale/tr/freeplay.cfg +++ /dev/null @@ -1,3 +0,0 @@ -msg-intro=Bu Factorio'nun serbest oynanışıdır. Göreviniz uzaya bir roket göndermek. Bunu roket rampası inşa edip ve Roket fırlatarak yapınız. Roket rampasını açmak için gelişmiş teknolojileri araştırmanız gerekecek. Ufaktan başlayın ve otomasyonla büyüyün, ve yerlilerden kendiniz korumayı unutmayın. -scenario-name=Serbest Oyun - diff --git a/lib/scenarios/Hotpatch/locale/uk/freeplay.cfg b/lib/scenarios/Hotpatch/locale/uk/freeplay.cfg deleted file mode 100644 index 895682d8..00000000 --- a/lib/scenarios/Hotpatch/locale/uk/freeplay.cfg +++ /dev/null @@ -1,3 +0,0 @@ -msg-intro=Це один з типів гри в Factorio — "Вільна гра". Ваше завдання — запустити ракету у космічний простір. Зробіть це, побудувавши ракетну шахту та запустивши ракету. Вам потрібно дослідити передові технології, щоб мати змогу побудувати ракетну шахту. Починайте з малого, розвивайтеся за допомогою автоматизації, але не забувайте і про оборону. -scenario-name=Вільна гра - diff --git a/lib/scenarios/Hotpatch/locale/vi/freeplay.cfg b/lib/scenarios/Hotpatch/locale/vi/freeplay.cfg deleted file mode 100644 index cd03ee2f..00000000 --- a/lib/scenarios/Hotpatch/locale/vi/freeplay.cfg +++ /dev/null @@ -1,3 +0,0 @@ -msg-intro=Đầy là chế độ chơi tự do của Factorio. Nhiệm vụ của bạn là phóng một chiếc tên lửa lên vũ trụ bằng cách xây dựng một bệ phóng. Bạn sẽ cần phải nghiên cứu nhiều công nghệ tiên tiến để có thể xây dựng bệ phóng đó. Hãy bắt đầu nhỏ, rồi phát triển từng bước bằng tự động hóa, và đừng quên bảo vệ chính mình khỏi những sinh vật ở đây. -scenario-name=Chơi tự do - diff --git a/lib/scenarios/Hotpatch/locale/zh-CN/freeplay.cfg b/lib/scenarios/Hotpatch/locale/zh-CN/freeplay.cfg deleted file mode 100644 index 629f4fae..00000000 --- a/lib/scenarios/Hotpatch/locale/zh-CN/freeplay.cfg +++ /dev/null @@ -1,3 +0,0 @@ -msg-intro=欢迎来到异星工厂标准模式,你的目标是要把火箭发射到太空。为此你需要研发一系列高等科技,修建火箭发射井,最后发射火箭。从小作坊生产起步,逐渐建立起属于自己的自动化大工厂吧!同时,别忘了防范异星虫子们的骚扰 ... -scenario-name=标准模式 - diff --git a/lib/scenarios/Hotpatch/locale/zh-TW/freeplay.cfg b/lib/scenarios/Hotpatch/locale/zh-TW/freeplay.cfg deleted file mode 100644 index 3d51fab2..00000000 --- a/lib/scenarios/Hotpatch/locale/zh-TW/freeplay.cfg +++ /dev/null @@ -1,3 +0,0 @@ -msg-intro=這是異星工廠的自由模式。你的任務是發射火箭探索外太空。請建造火箭發射井並發射火箭。你會需要研究大量的進階科技以解鎖火箭發射井。從頭開始,靠著自動化的協助邁向目標的同時,也別忘了從外星住民手中保護你自己。 -scenario-name=自由模式 - diff --git a/package.json b/package.json index 50420458..ed4a182e 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "getmac": "^1.4.0", "ini": "^1.3.5", "jsonwebtoken": "^8.2.1", + "jszip": "^3.2.2", "lib": "file:./lib", "luamin": "^1.0.4", "minimist": "^1.2.0", diff --git a/sharedPlugins/clusterioMod/index.js b/sharedPlugins/clusterioMod/index.js index 1a7648c1..ce4b57a7 100644 --- a/sharedPlugins/clusterioMod/index.js +++ b/sharedPlugins/clusterioMod/index.js @@ -3,11 +3,9 @@ const fs = require("fs-extra"); const objectOps = require("lib/objectOps.js"); const fileOps = require("lib/fileOps"); -const checkHotpatchInstallation = require("lib/clusterTools/checkHotpatchInstallation"); -const getLua = require("lib/clusterTools/getLua"); const pluginConfig = require("./config"); -const COMPRESS_LUA = false; + function ensureFileSync(path) { if (!fs.existsSync(path)) { @@ -39,16 +37,8 @@ module.exports = class remoteCommands { ensureFileSync(instancedirectory + "/script-output/output.txt"); ensureFileSync(instancedirectory + "/script-output/orders.txt"); ensureFileSync(instancedirectory + "/script-output/txbuffer.txt"); - - (async ()=>{ - let hotpatchInstallStatus = await checkHotpatchInstallation(this.messageInterface); - this.messageInterface("Hotpach installation status: "+hotpatchInstallStatus); - if(hotpatchInstallStatus){ - // let mainCode = await getLua("sharedPlugins/playerManager/lua/playerTracking.lua"); - // if(mainCode) var returnValue = await messageInterface("/silent-command remote.call('hotpatch', 'update', '"+pluginConfig.name+"', '"+pluginConfig.version+"', '"+mainCode+"')"); - } - })().catch(e => console.log(e)); - + + // flow/production statistics ------------------------------------------------------------ var oldFlowStats = false; var oldTimestamp; diff --git a/sharedPlugins/scenario_loader/config.js b/sharedPlugins/scenario_loader/config.js deleted file mode 100644 index 98acf05e..00000000 --- a/sharedPlugins/scenario_loader/config.js +++ /dev/null @@ -1,10 +0,0 @@ -/* - Clusterio plugin for loading softmod scenarios using Hotpatch - To load a scenario, simply drop it inside sharedPlugins/scenario_loader/scenarios/ -*/ -module.exports = { - name: "scenario_loader", - version: "1.0.0", - binary: "nodePackage", - description: "Simple way to load scenarios using Hotpatch" -} diff --git a/sharedPlugins/scenario_loader/index.js b/sharedPlugins/scenario_loader/index.js deleted file mode 100644 index eb575593..00000000 --- a/sharedPlugins/scenario_loader/index.js +++ /dev/null @@ -1,107 +0,0 @@ -const fs = require("fs-extra"); -const path = require("path"); -const clusterTools = require("lib/clusterTools")(); - -module.exports = class remoteCommands { - constructor(mergedConfig, messageInterface, extras){ - this.messageInterface = messageInterface; - this.config = mergedConfig; - - (async () => { - let hotpatchInstallStatus = await clusterTools.checkHotpatchInstallation(messageInterface); - messageInterface("Hotpach installation status: "+hotpatchInstallStatus); - if(hotpatchInstallStatus){ - await this.loadScenariosFromFolder("scenarios") // located in project root folder (with sharedMods, sharedPlugins etc) - } - })(); - } - async loadScenariosFromFolder(scenarioDir){ - await fs.ensureDir(scenarioDir) - let scenarios = await fs.readdir(scenarioDir); - for(let i = 0; i < scenarios.length; i++){ - this.messageInterface(`Loading scenario ${scenarios[i]}`) - let startTime = Date.now(); - let scenPath = path.join(scenarioDir, scenarios[i]); - let stat = await fs.stat(scenPath); - if(stat.isDirectory){ - async function loadFiles(dir, rootdir){ - let returnFiles = []; - let entries = await fs.readdir(path.join(rootdir, dir)); - for(let o = 0; o < entries.length; o++){ - let stat = await fs.stat(path.join(rootdir, dir, entries[o])); - if(stat.isDirectory()){ - // recurse - let subFolder = await loadFiles(path.join(dir, entries[o]), rootdir); - returnFiles = returnFiles.concat(subFolder); - } else if(entries[o].split(".")[entries[o].split(".").length-1].toLowerCase() == "lua"){ - // if it is a Lua file, read its contents - // let data = await fs.readFile(path.join(rootdir, dir, entries[o])); - let data = await clusterTools.getLua(path.join(rootdir, dir, entries[o]), false); - returnFiles.push({ - name: path.join(dir, entries[o].split(".")[0]), - data, - }); - } - } - return returnFiles; - } - let fileMap = await loadFiles("", scenPath); - let files = {}; - fileMap.forEach(map => { - files[map.name] = map.data; - console.log(map.name); - }); - - let fileImportString = `\{`; - for(let k in files){ - let name = k.replace(/\\/g, '/'); - name = name.replace(/ /g, ''); - // make sure *not* to include control.lua as it is provided as a seperate argument - if(name != "control") fileImportString += `["${name}"] = '${files[k]}', `; - } - fileImportString += `\}`; - if(files.control) var returnValue = await this.messageInterface(`/silent-command remote.call('hotpatch', 'update', '${scenarios[i]}', '1.0.0', '${files.control}', ${fileImportString})`); - if(returnValue) this.messageInterface(returnValue); - this.messageInterface(`Loaded scenario ${scenarios[i]} in ${Math.floor(Date.now()-startTime)}ms`); - } - } - } -} - -// this function is bad in terms of scope since it relies on a global for return values, should be refactored. -var filesCollection = []; -const directoriesToSkip = ['bower_components', 'node_modules', 'www', 'platforms', '.git']; - -function readDirectorySynchronously(directory) { - var currentDirectorypath = /*path.join(__dirname + */directory//); - - var currentDirectory = fs.readdirSync(currentDirectorypath, 'utf8'); - - currentDirectory.forEach(file => { - var fileShouldBeSkipped = directoriesToSkip.indexOf(file) > -1; - var pathOfCurrentItem = path.join(/*__dirname + */directory + '/' + file); - if (!fileShouldBeSkipped && fs.statSync(pathOfCurrentItem).isFile()) { - filesCollection.push(pathOfCurrentItem); - } - else if (!fileShouldBeSkipped) { - var directorypath = path.join(directory, file); - readDirectorySynchronously(directorypath); - } - }); -} -class AsyncArray extends Array { - constructor(arr) { - super(arr) - this.data = arr; // In place of Array subclassing - } - async filterAsync(predicate) { - // Take a copy of the array, it might mutate by the time we've finished - const data = Array.from(this.data); - // Transform all the elements into an array of promises using the predicate - // as the promise - const result = await Promise.all(data.map((element, index) => predicate(element, index, data))); - return data.filter((element, index) => { - return result[index]; - }); - } -} diff --git a/sharedPlugins/scenario_loader/test/missing.js b/sharedPlugins/scenario_loader/test/missing.js deleted file mode 100644 index 923d82bf..00000000 --- a/sharedPlugins/scenario_loader/test/missing.js +++ /dev/null @@ -1,4 +0,0 @@ -// These are libraries that does not have any units test, they are -// required here in order to track coverage for them. - -require('../index'); diff --git a/test/integration/patch.js b/test/integration/patch.js new file mode 100644 index 00000000..87edc342 --- /dev/null +++ b/test/integration/patch.js @@ -0,0 +1,31 @@ +const assert = require("assert").strict; +const fs = require("fs-extra"); +const jszip = require("jszip"); +const path = require("path"); + +const factorio = require("lib/factorio"); + + +// The server integration test is required to run before this one +require("./server") + +describe("Integration of lib/factorio/patch", function() { + describe("patch()", function() { + let savePath = path.join("test", "temp", "integration", "saves", "test.zip"); + it("should patch a freeplay game", async function() { + await factorio.patch(savePath, [{ + name: "test", + files: [{ path: "test.lua", content: "-- test" }] + }]); + + let zip = await jszip.loadAsync(await fs.readFile(savePath)); + let content = await zip.file("test/modules/test.lua").async("string"); + assert.equal(content, "-- test"); + }); + it("should remove old modules in a save", async function() { + await factorio.patch(savePath, [{ name: "test", files: [] }]); + let zip = await jszip.loadAsync(await fs.readFile(savePath)); + assert.equal(zip.file("test/modules/test.lua"), null); + }); + }); +}); diff --git a/test/lib/factorio/patch.js b/test/lib/factorio/patch.js new file mode 100644 index 00000000..c9f3900f --- /dev/null +++ b/test/lib/factorio/patch.js @@ -0,0 +1,94 @@ +const assert = require("assert").strict; +const fs = require("fs-extra"); +const jszip = require("jszip"); +const path = require("path"); + +const factorio = require("lib/factorio"); + + +describe("lib/factorio", function() { + describe("findRoot()", function() { + it("should find the root of a zip", function() { + let zip = new jszip(); + zip.file("root/foo.txt", ""); + zip.file("root/bar.txt", ""); + + assert.equal(factorio._findRoot(zip), "root"); + }); + + it("should throw if files are at the root of the zip", function() { + let zip = new jszip(); + zip.file("root/foo.txt", ""); + zip.file("bar.txt", ""); + + assert.throws( + () => factorio._findRoot(zip), + new Error("Zip contains file 'bar.txt' in root dir") + ); + }); + + it("should throw if there are multiple root dirs", function() { + let zip = new jszip(); + zip.file("root-1/foo.txt", ""); + zip.file("root-2/bar.txt", ""); + + assert.throws( + () => factorio._findRoot(zip), + new Error("Zip contains multiple root folders") + ); + }); + + it("should throw if given an empty zip file", function() { + let zip = new jszip(); + + assert.throws( + () => factorio._findRoot(zip), + new Error("Empty zip file") + ); + }); + }); + + describe("generateLoader()", function() { + let reference = [ + '-- Auto generated scenario module loader created by Clusterio', + '-- Modifications to this file will be lost when loaded in Clusterio', + '', + 'local event_handler = require("event_handler")', + '', + '-- Scenario modules', + 'event_handler.add_lib(require("foo"))', + '', + '-- Clusterio modules', + 'event_handler.add_lib(require("modules/bar"))', + '', + ].join('\n'); + let patchInfo = { + "scenario": { "modules": ["foo"] }, + "modules": [{ + "name": "spam", + "files": [ + { "path": "bar.lua", "load": true }, + { "path": "excluded.lua", "load": false }, + ], + }], + }; + + it("should produce output matching the reference", function() { + assert.equal(factorio._generateLoader(patchInfo), reference); + }); + }); + + describe("patch()", function() { + it("should throw on unknown scenario", async function() { + let zip = new jszip(); + zip.file("world/control.lua", "-- unknown\n"); + let zipPath = path.join("test", "temp", "patch.zip"); + await fs.outputFile(zipPath, await zip.generateAsync({type: "nodebuffer"})) + + await assert.rejects( + factorio.patch(zipPath, []), + new Error("Unable to patch save, unknown scenario (3acc3be3861144e55604f5ac2f2555071885ebc4)") + ); + }); + }); +});