mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-15 23:24:52 +09:00
Merge pull request #301 from Hornwitser/custom-scenario
Add support for running custom scenarios
This commit is contained in:
10 files changed
+150
-14
No files matched your search
+24
-1
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
---------------------------
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"],
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
},
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user