Merge pull request #301 from Hornwitser/custom-scenario

Add support for running custom scenarios
This commit is contained in:
Hornwitser authored and GitHub committed 2020-07-08 12:46:49 +02:00
commit 0802c5b29d
10 files changed
+150 -14

No files matched your search

+24 -1
View File
@@ -383,6 +383,7 @@ commands.push(new Command({
yargs.options({
"instance": { describe: "Instance to start", nargs: 1, type: "string", demandOption: true },
"save": { describe: "Save load, defaults to latest", nargs: 1, type: "string" },
"keep-open": { describe: "Keep console open", nargs: 0, type: "boolean", default: false },
});
}],
handler: async function(args, control) {
@@ -395,6 +396,24 @@ commands.push(new Command({
},
}));
commands.push(new Command({
definition: ["load-scenario", "Start instance by loading a scenario", (yargs) => {
yargs.options({
"instance": { describe: "Instance to start", nargs: 1, type: "string", demandOption: true },
"scenario": { describe: "Scenario to load", nargs: 1, type: "string" },
"keep-open": { describe: "Keep console open", nargs: 0, type: "boolean", default: false },
});
}],
handler: async function(args, control) {
let instanceId = await resolveInstance(control, args.instance);
await link.messages.setInstanceOutputSubscriptions.send(control, { instance_ids: [instanceId] });
let response = await link.messages.loadScenario.send(control, {
instance_id: instanceId,
scenario: args.scenario || null,
});
},
}));
commands.push(new Command({
definition: ["stop-instance", "Stop instance", (yargs) => {
yargs.options({
@@ -735,10 +754,12 @@ async function startControl() {
if (commands.has(commandName)) {
let command = commands.get(commandName);
let keepOpen = Boolean(args.keepOpen);
try {
await command.run(args, control);
} catch (err) {
keepOpen = false;
if (err instanceof errors.CommandError) {
console.error(`Error running command: ${err.message}`);
process.exitCode = 1;
@@ -752,7 +773,9 @@ async function startControl() {
}
} finally {
await control.shutdown();
if (!keepOpen) {
await control.shutdown();
}
}
}
}
+4
View File
@@ -9,6 +9,10 @@ based on the built in
library to Factorio and requires scenarios to be coded specifically for
it. Mods on the other hand work without modifications.
This can be disabled with the `factorio.enable_save_patching` config on
a per instance basis, however disabling it means that modules are not
loaded into the game and most plugins will not function.
<sub>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
+14
View File
@@ -195,6 +195,20 @@ where appropriate. See [Configuration System](configuration-system.md)
for more details on how this system works.
### Handling Invalid Configuration
If the plugin requires a certain feature to be enabled to function it
should throw an error during init if this is not the case. The most
common such feature is the save patching, which can be disabled to run
vanilla or scenarios not compatible with Clusterio. For example:
async init() {
if (!this.instance.config.get("factorio.enable_save_patching")) {
throw new Error("foo_frobber plugin requires save patching.");
}
}
Communicating with Factorio
---------------------------
+6
View File
@@ -239,6 +239,12 @@ FactorioGroup.define({
type: "string",
optional: true,
});
FactorioGroup.define({
name: "enable_save_patching",
description: "Patch saves with Lua code. Required for Clusterio integrations, lua modules, and most plugins.",
type: "boolean",
initial_value: true,
});
FactorioGroup.define({
name: "settings",
description: "Settings overridden in server-settings.json",
+10
View File
@@ -418,6 +418,16 @@ messages.createSave = new Request({
forwardTo: "instance",
});
messages.loadScenario = new Request({
type: "load_scenario",
links: ["control-master", "master-slave", "slave-instance"],
permission: "core.instance.load_scenario",
forwardTo: "instance",
requestProperties: {
"scenario": { type: "string" },
},
});
messages.exportData = new Request({
type: "export_data",
links: ["control-master", "master-slave", "slave-instance"],
+5
View File
@@ -318,6 +318,11 @@ definePermission({
title: "Start instance",
description: "Start instances.",
});
definePermission({
name: "core.instance.load_scenario",
title: "Load scenario",
description: "Start instances by loading a scenario.",
});
definePermission({
name: "core.instance.stop",
title: "Stop instance",
+4
View File
@@ -13,6 +13,10 @@ function unexpectedError(err) {
class InstancePlugin extends plugin.BaseInstancePlugin {
async init() {
if (!this.instance.config.get("factorio.enable_save_patching")) {
throw new Error("research_sync plugin requires save patching.");
}
this.instance.server.on("ipc-research_sync:contribution", (tech) => {
this.researchContribution(tech).catch(unexpectedError);
});
+6
View File
@@ -22,6 +22,12 @@ const instanceGameFlowStatistics = new Gauge(
class InstancePlugin extends plugin.BaseInstancePlugin {
async init() {
if (!this.instance.config.get("factorio.enable_save_patching")) {
throw new Error("statistics_exporter plugin requires save patching.");
}
}
async onMetrics() {
let string = await this.instance.server.sendRcon("/sc statistics_exporter.export()");
let stats;
+76 -13
View File
@@ -288,17 +288,11 @@ class Instance extends link.Link{
}
/**
* Prepare a save for starting
* Prepare instance for starting
*
* Writes server settings, links mods, creates a new save if no save is
* passed, and patches it with modules.
*
* @param {String|null} saveName -
* Save to prepare from the instance saves directory. Creates a new
* save if null.
* @returns {String} Name of the save prepared.
* Writes server settings and links mods.
*/
async prepare(saveName) {
async prepare() {
console.log("Clusterio | Writing server-settings.json");
await this.writeServerSettings();
@@ -315,7 +309,19 @@ class Instance extends link.Link{
}catch(e){}
await symlinkMods(this, "sharedMods", console);
}
/**
* Prepare a save for starting
*
* Creates a new save if no save is passed and patches it with modules.
*
* @param {String|null} saveName -
* Save to prepare from the instance saves directory. Creates a new
* save if null.
* @returns {String} Name of the save prepared.
*/
async prepareSave(saveName) {
// Use latest save if no save was specified
if (saveName === null) {
saveName = await fileOps.getNewestFile(
@@ -330,6 +336,10 @@ class Instance extends link.Link{
saveName = "world.zip";
}
if (!this.config.get("factorio.enable_save_patching")) {
return saveName;
}
// Patch save with lua modules from plugins
console.log("Clusterio | Patching save");
@@ -409,8 +419,33 @@ class Instance extends link.Link{
this.server.on("exit", () => this.notifyExit());
await this.server.start(saveName);
await this.server.disableAchievements();
await this.updateInstanceData();
if (this.config.get("factorio.enable_save_patching")) {
await this.server.disableAchievements();
await this.updateInstanceData();
}
await plugin.invokeHook(this.plugins, "onStart");
this._running = true;
link.messages.instanceStarted.send(this, { instance_id: this.config.get("instance.id") });
}
/**
* Start Factorio server by loading a scenario
*
* Launches the Factorio server for this instance with the given
* scenario.
*
* @param {String} scenario - Name of scenario to load.
*/
async startScenario(scenario) {
this.server.on("rcon-ready", () => {
console.log("Clusterio | RCON connection established");
});
this.server.on("exit", () => this.notifyExit());
await this.server.startScenario(scenario);
await plugin.invokeHook(this.plugins, "onStart");
@@ -483,14 +518,36 @@ class Instance extends link.Link{
async startInstanceRequestHandler(message) {
let saveName = message.data.save;
try {
saveName = await this.prepare(saveName);
await this.prepare();
saveName = await this.prepareSave(saveName);
} catch (err) {
this.notifyExit();
throw err;
}
try {
await this.start(saveName, this.config, this.socket);
await this.start(saveName);
} catch (err) {
await this.stop();
throw err;
}
}
async loadScenarioRequestHandler(message) {
if (this.config.get("factorio.enable_save_patching")) {
this.notifyExit();
throw new errors.RequestError("Load scenario cannot be used with save patching enabled");
}
try {
await this.prepare();
} catch (err) {
this.notifyExit();
throw err;
}
try {
await this.startScenario(message.data.scenario);
} catch (err) {
await this.stop();
throw err;
@@ -945,6 +1002,12 @@ class Slave extends link.Link {
return await request.send(instanceConnection, message.data);
}
async loadScenarioRequestHandler(message, request) {
let instanceId = message.data.instance_id;
let instanceConnection = await this._connectInstance(instanceId);
return await request.send(instanceConnection, message.data);
}
async createSaveRequestHandler(message, request) {
let instanceId = message.data.instance_id;
let instanceConnection = await this._connectInstance(instanceId);
+1
View File
@@ -68,6 +68,7 @@ class MockInstance extends link.Link {
this.config = {
get: (name) => {
if (name === "instance.id") { return 7357; }
if (name === "factorio.enable_save_patching") { return true; }
throw Error("Not implemented");
},
};