mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-12 23:04:52 +09:00
Add option to pass map settings to Factorio
Add options to pass map seed, map exchange string, map gen settings and/or map settings when creating a save either through the create save request or the load scenario request.
This commit is contained in:
1 parent
4a8a4c8227
commit
dd216318b0
12 files changed
+462
-59
No files matched your search
+46
-2
@@ -25,6 +25,7 @@ const libSharedCommands = require("@clusterio/lib/shared_commands");
|
||||
const { ConsoleTransport, levels, logger } = require("@clusterio/lib/logging");
|
||||
const libLoggingUtils = require("@clusterio/lib/logging_utils");
|
||||
const libHelpers = require("@clusterio/lib/helpers");
|
||||
const libFactorio = require("@clusterio/lib/factorio");
|
||||
|
||||
|
||||
function print(...content) {
|
||||
@@ -314,15 +315,50 @@ instanceCommands.add(new libCommand.Command({
|
||||
},
|
||||
}));
|
||||
|
||||
async function loadMapSettings(args) {
|
||||
let seed = args.seed !== undefined ? args.seed : null;
|
||||
let mapGenSettings = null;
|
||||
let mapSettings = null;
|
||||
if (args.mapExchangeString) {
|
||||
let parsed = libFactorio.readMapExchangeString(args.mapExchangeString);
|
||||
mapGenSettings = parsed.map_gen_settings;
|
||||
mapSettings = parsed.map_settings;
|
||||
}
|
||||
if (args.mapGenSettings) {
|
||||
mapGenSettings = JSON.parse(await fs.readFile(args.mapGenSettings));
|
||||
}
|
||||
if (args.mapSettings) {
|
||||
mapSettings = JSON.parse(await fs.readFile(args.mapSettings));
|
||||
}
|
||||
|
||||
return {
|
||||
seed,
|
||||
mapGenSettings,
|
||||
mapSettings,
|
||||
};
|
||||
}
|
||||
|
||||
instanceCommands.add(new libCommand.Command({
|
||||
definition: ["create-save <instance>", "Create a new save on an instance", (yargs) => {
|
||||
definition: ["create-save <instance> [name]", "Create a new save on an instance", (yargs) => {
|
||||
yargs.positional("instance", { describe: "Instance to create on", type: "string" });
|
||||
yargs.positional("name", { describe: "Name of save to create.", type: "string", default: "world.zip" });
|
||||
yargs.options({
|
||||
"seed": { describe: "Seed to use, takes precedence over map-gen-settings", nargs: 1, type: "number" },
|
||||
"map-exchange-string": { describe: "Map exchange string to use for the save", nargs: 1, type: "string" },
|
||||
"map-gen-settings": { describe: "path to file to use for map-gen-settings", nargs: 1, type: "string" },
|
||||
"map-settings": { describe: "path to file to use for map-settings", nargs: 1, type: "string" },
|
||||
});
|
||||
}],
|
||||
handler: async function(args, control) {
|
||||
let instanceId = await libCommand.resolveInstance(control, args.instance);
|
||||
let { seed, mapGenSettings, mapSettings } = await loadMapSettings(args);
|
||||
await control.setLogSubscriptions({ instance_ids: [instanceId] });
|
||||
let response = await libLink.messages.createSave.send(control, {
|
||||
instance_id: instanceId,
|
||||
name: args.name,
|
||||
seed,
|
||||
map_gen_settings: mapGenSettings,
|
||||
map_settings: mapSettings,
|
||||
});
|
||||
},
|
||||
}));
|
||||
@@ -364,15 +400,23 @@ instanceCommands.add(new libCommand.Command({
|
||||
yargs.positional("instance", { describe: "Instance to start", type: "string" });
|
||||
yargs.positional("scenario", { describe: "Scenario to load", type: "string" });
|
||||
yargs.options({
|
||||
"seed": { describe: "Seed to use, takes precedence over map-gen-settings", nargs: 1, type: "number" },
|
||||
"map-exchange-string": { describe: "Map exchange string to use for the save", nargs: 1, type: "string" },
|
||||
"map-gen-settings": { describe: "path to file to use for map-gen-settings", nargs: 1, type: "string" },
|
||||
"map-settings": { describe: "path to file to use for map-settings", nargs: 1, type: "string" },
|
||||
"keep-open": { describe: "Keep console open", nargs: 0, type: "boolean", default: false },
|
||||
});
|
||||
}],
|
||||
handler: async function(args, control) {
|
||||
let instanceId = await libCommand.resolveInstance(control, args.instance);
|
||||
await control.setLogSubscriptions({ instance_ids: [instanceId] });
|
||||
let { seed, mapGenSettings, mapSettings } = await loadMapSettings(args);
|
||||
let response = await libLink.messages.loadScenario.send(control, {
|
||||
instance_id: instanceId,
|
||||
scenario: args.scenario || null,
|
||||
scenario: args.scenario,
|
||||
seed,
|
||||
map_gen_settings: mapGenSettings,
|
||||
map_settings: mapSettings,
|
||||
});
|
||||
control.keepOpen = args.keepOpen;
|
||||
},
|
||||
|
||||
@@ -829,17 +829,26 @@ class FactorioServer extends events.EventEmitter {
|
||||
*
|
||||
* @param {string} name -
|
||||
* Name of the save to create. Should end with ".zip".
|
||||
* @param {?number=} seed - Seed to pass via --map-gen-seed
|
||||
* @param {?object=} mapGenSettings -
|
||||
* Map get settings to pass via --map-gen-settings.
|
||||
* @param {?object=} mapSettings -
|
||||
* Map setting to pass via --map-settings.
|
||||
*/
|
||||
async create(name) {
|
||||
async create(name, seed = null, mapGenSettings = null, mapSettings = null) {
|
||||
this._check(["init"]);
|
||||
this._state = "create";
|
||||
|
||||
await this._writeConfigIni();
|
||||
await this._writeMapSettings(mapGenSettings, mapSettings);
|
||||
this._server = child_process.spawn(
|
||||
this.binaryPath(),
|
||||
[
|
||||
"--config", this.writePath("config.ini"),
|
||||
"--create", this.writePath("saves", name),
|
||||
...(seed !== null ? ["--map-gen-seed", seed] : []),
|
||||
...(mapGenSettings !== null ? ["--map-gen-settings", this.writePath("map-gen-settings.json")] : []),
|
||||
...(mapSettings !== null ? ["--map-settings", this.writePath("map-settings.json")] : []),
|
||||
...(this.verboseLogging ? ["--verbose"] : []),
|
||||
],
|
||||
{
|
||||
@@ -903,17 +912,26 @@ class FactorioServer extends events.EventEmitter {
|
||||
* argument to start the given scenario.
|
||||
*
|
||||
* @param {string} scenario - Name of the scenario to run.
|
||||
* @param {?number=} seed - Seed to pass via --map-gen-seed
|
||||
* @param {?object=} mapGenSettings -
|
||||
* Map get settings to pass via --map-gen-settings.
|
||||
* @param {?object=} mapSettings -
|
||||
* Map setting to pass via --map-settings.
|
||||
*/
|
||||
async startScenario(scenario) {
|
||||
async startScenario(scenario, seed = null, mapGenSettings = null, mapSettings = null) {
|
||||
this._check(["init"]);
|
||||
this._state = "running";
|
||||
|
||||
await this._writeConfigIni();
|
||||
await this._writeMapSettings(mapGenSettings, mapSettings);
|
||||
this._server = child_process.spawn(
|
||||
this.binaryPath(),
|
||||
[
|
||||
"--config", this.writePath("config.ini"),
|
||||
"--start-server-load-scenario", scenario,
|
||||
...(seed !== null ? ["--map-gen-seed", seed] : []),
|
||||
...(mapGenSettings !== null ? ["--map-gen-settings", this.writePath("map-gen-settings.json")] : []),
|
||||
...(mapSettings !== null ? ["--map-settings", this.writePath("map-settings.json")] : []),
|
||||
"--port", this.gamePort,
|
||||
"--rcon-port", this.rconPort,
|
||||
"--rcon-password", this.rconPassword,
|
||||
@@ -1153,6 +1171,19 @@ class FactorioServer extends events.EventEmitter {
|
||||
});
|
||||
await fs.outputFile(this.writePath("config.ini"), content);
|
||||
}
|
||||
|
||||
async _writeMapSettings(mapGenSettings, mapSettings) {
|
||||
if (mapGenSettings) {
|
||||
await fs.outputFile(
|
||||
this.writePath("map-gen-settings.json"), JSON.stringify(mapGenSettings, null, 4)
|
||||
);
|
||||
}
|
||||
if (mapSettings) {
|
||||
await fs.outputFile(
|
||||
this.writePath("map-settings.json"), JSON.stringify(mapSettings, null, 4)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -498,6 +498,12 @@ messages.createSave = new Request({
|
||||
links: ["control-master", "master-slave", "slave-instance"],
|
||||
permission: "core.instance.create_save",
|
||||
forwardTo: "instance",
|
||||
requestProperties: {
|
||||
"name": { type: "string" },
|
||||
"seed": { type: ["integer", "null"] },
|
||||
"map_gen_settings": { type: ["object", "null"] },
|
||||
"map_settings": { type: ["object", "null"] },
|
||||
},
|
||||
});
|
||||
|
||||
messages.loadScenario = new Request({
|
||||
@@ -507,6 +513,9 @@ messages.loadScenario = new Request({
|
||||
forwardTo: "instance",
|
||||
requestProperties: {
|
||||
"scenario": { type: "string" },
|
||||
"seed": { type: ["integer", "null"] },
|
||||
"map_gen_settings": { type: ["object", "null"] },
|
||||
"map_settings": { type: ["object", "null"] },
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
+10
-5
@@ -516,14 +516,17 @@ class Instance extends libLink.Link {
|
||||
* scenario.
|
||||
*
|
||||
* @param {String} scenario - Name of scenario to load.
|
||||
* @param {?number} seed - seed to use.
|
||||
* @param {?object} mapGenSettings - MapGenSettings to use.
|
||||
* @param {?object} mapSettings - MapSettings to use.
|
||||
*/
|
||||
async startScenario(scenario) {
|
||||
async startScenario(scenario, seed, mapGenSettings, mapSettings) {
|
||||
this.server.on("rcon-ready", () => {
|
||||
this.logger.verbose("RCON connection established");
|
||||
});
|
||||
|
||||
this.server.on("exit", () => this.notifyExit());
|
||||
await this.server.startScenario(scenario);
|
||||
await this.server.startScenario(scenario, seed, mapGenSettings, mapSettings);
|
||||
|
||||
await libPlugin.invokeHook(this.plugins, "onStart");
|
||||
|
||||
@@ -678,15 +681,16 @@ class Instance extends libLink.Link {
|
||||
throw err;
|
||||
}
|
||||
|
||||
let { scenario, seed, map_gen_settings, map_settings } = message.data;
|
||||
try {
|
||||
await this.startScenario(message.data.scenario);
|
||||
await this.startScenario(scenario, seed, map_gen_settings, map_settings);
|
||||
} catch (err) {
|
||||
await this.stop();
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async createSaveRequestHandler() {
|
||||
async createSaveRequestHandler(message) {
|
||||
this.notifyStatus("creating_save");
|
||||
try {
|
||||
this.logger.verbose("Writing server-settings.json");
|
||||
@@ -702,7 +706,8 @@ class Instance extends libLink.Link {
|
||||
}
|
||||
|
||||
this.server.on("exit", () => this.notifyExit());
|
||||
await this.server.create("world");
|
||||
let { name, seed, map_gen_settings, map_settings } = message.data;
|
||||
await this.server.create(name, seed, map_gen_settings, map_settings);
|
||||
this.logger.info("Successfully created save");
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,9 @@
|
||||
"@babel/preset-react": "^7.12.13",
|
||||
"@clusterio/lib": "^2.0.0-alpha.6",
|
||||
"antd": "^4.6.5",
|
||||
"assert": "^2.0.0",
|
||||
"babel-loader": "^8.2.2",
|
||||
"browserify-zlib": "^0.2.0",
|
||||
"buffer": "^6.0.2",
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
"css-loader": "^5.1.0",
|
||||
@@ -33,6 +35,7 @@
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"style-loader": "^2.0.0",
|
||||
"util": "^0.12.3",
|
||||
"webpack": "^5.36.2"
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
import React, { useContext, useState } from "react";
|
||||
import { Button, Col, Form, Input, Modal, Row, Typography } from "antd";
|
||||
|
||||
import libLink from "@clusterio/lib/link";
|
||||
import { readMapExchangeString } from "@clusterio/lib/factorio/exchange_string";
|
||||
|
||||
import ControlContext from "./ControlContext";
|
||||
import { notifyErrorHandler } from "../util/notify";
|
||||
|
||||
|
||||
export default function CreateSaveModal(props) {
|
||||
let [visible, setVisible] = useState(false);
|
||||
let [creatingSave, setCreatingSave] = useState(false);
|
||||
let [form] = Form.useForm();
|
||||
let control = useContext(ControlContext);
|
||||
|
||||
function createSave() {
|
||||
let values = form.getFieldsValue();
|
||||
let name = values.saveName || "world.zip";
|
||||
let seed = values.seed === undefined ? null : Number.parseInt(values.seed, 10);
|
||||
let mapGenSettings = null;
|
||||
let mapSettings = null;
|
||||
if (values.exchangeString && values.exchangeString.trim()) {
|
||||
let result;
|
||||
try {
|
||||
result = readMapExchangeString(values.exchangeString);
|
||||
} catch (err) {
|
||||
form.setFields([{ name: "exchangeString", errors: [err.message] }]);
|
||||
return;
|
||||
}
|
||||
form.setFields([{ name: "exchangeString", errors: [] }]);
|
||||
mapGenSettings = result.map_gen_settings;
|
||||
mapSettings = result.map_settings;
|
||||
}
|
||||
if (values.mapGenSettings && values.mapGenSettings.trim()) {
|
||||
try {
|
||||
mapGenSettings = JSON.parse(values.mapGenSettings);
|
||||
} catch (err) {
|
||||
form.setFields([{ name: "mapGenSettings", errors: [err.message] }]);
|
||||
return;
|
||||
}
|
||||
form.setFields([{ name: "mapGenSettings", errors: [] }]);
|
||||
}
|
||||
if (values.mapSettings && values.mapSettings.trim()) {
|
||||
try {
|
||||
mapSettings = JSON.parse(values.mapGenSettings);
|
||||
} catch (err) {
|
||||
form.setFields([{ name: "mapSettings", errors: [err.message] }]);
|
||||
return;
|
||||
}
|
||||
form.setFields([{ name: "mapSettings", errors: [] }]);
|
||||
}
|
||||
|
||||
setCreatingSave(true);
|
||||
libLink.messages.createSave.send(control, {
|
||||
instance_id: props.instance.id,
|
||||
name,
|
||||
seed,
|
||||
map_gen_settings: mapGenSettings,
|
||||
map_settings: mapSettings,
|
||||
}).then(() => {
|
||||
setVisible(false);
|
||||
}).catch(
|
||||
notifyErrorHandler("Error creating save")
|
||||
).finally(() => {
|
||||
setCreatingSave(false);
|
||||
});
|
||||
}
|
||||
|
||||
function convertExchangeString() {
|
||||
let exchangeString = form.getFieldValue("exchangeString");
|
||||
let result;
|
||||
try {
|
||||
result = readMapExchangeString(exchangeString);
|
||||
} catch (err) {
|
||||
form.setFields([{ name: "exchangeString", errors: [err.message] }]);
|
||||
return;
|
||||
}
|
||||
form.setFields([{ name: "exchangeString", errors: [], value: "" }]);
|
||||
form.setFieldsValue({
|
||||
mapGenSettings: JSON.stringify(result.map_gen_settings, null, 4),
|
||||
mapSettings: JSON.stringify(result.map_settings, null, 4),
|
||||
});
|
||||
}
|
||||
|
||||
function handleCancel() {
|
||||
setVisible(false);
|
||||
}
|
||||
|
||||
return <>
|
||||
<Button
|
||||
loading={creatingSave}
|
||||
disabled={props.instance.status !== "stopped"}
|
||||
onClick={() => {
|
||||
setVisible(true);
|
||||
}}
|
||||
>
|
||||
Create save
|
||||
</Button>
|
||||
<Modal
|
||||
title="Create Save"
|
||||
width={560}
|
||||
okText="Create"
|
||||
visible={visible}
|
||||
confirmLoading={creatingSave}
|
||||
onOk={createSave}
|
||||
onCancel={handleCancel}
|
||||
destroyOnClose
|
||||
>
|
||||
<Form form={form} layout="vertical">
|
||||
<Form.Item name="saveName" label="Save Name">
|
||||
<Input placeholder="world.zip" />
|
||||
</Form.Item>
|
||||
<Form.Item name="seed" label="Seed">
|
||||
<Input type="number" />
|
||||
</Form.Item>
|
||||
<Form.Item name="exchangeString" label="Map Exchange String">
|
||||
<Input.TextArea allowClear={true} autoSize={{ minRows: 3 }} />
|
||||
</Form.Item>
|
||||
<div style={{ textAlign: "right", marginTop: -8 }}>
|
||||
<Button onClick={() => convertExchangeString()}>Convert to settings</Button>
|
||||
</div>
|
||||
<Form.Item
|
||||
name="mapGenSettings"
|
||||
label="Map Gen Settings"
|
||||
tooltip="JSON data to pass to Factorio with --map-gen-settings, overrides Map Exchange String"
|
||||
>
|
||||
<Input.TextArea allowClear={true} autoSize={{ minRows: 1 }} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="mapSettings"
|
||||
label="Map Settings"
|
||||
tooltip="JSON data to pass to Factorio with --map-settings, overrides Map Exchange String"
|
||||
>
|
||||
<Input.TextArea allowClear={true} autoSize={{ minRows: 1 }} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</>;
|
||||
}
|
||||
@@ -12,6 +12,8 @@ import InstanceConsole from "./InstanceConsole";
|
||||
import InstanceRcon from "./InstanceRcon";
|
||||
import AssignInstanceModal from "./AssignInstanceModal";
|
||||
import StartStopInstanceButton from "./StartStopInstanceButton";
|
||||
import CreateSaveModal from "./CreateSaveModal";
|
||||
import LoadScenarioModal from "./LoadScenarioModal";
|
||||
import { notifyErrorHandler } from "../util/notify";
|
||||
import { useInstance } from "../model/instance";
|
||||
import { useSlave } from "../model/slave";
|
||||
@@ -29,8 +31,6 @@ export default function InstanceViewPage(props) {
|
||||
let [instance] = useInstance(instanceId);
|
||||
let [slave] = useSlave(Number(instance["assigned_slave"]));
|
||||
|
||||
let [creatingSave, setCreatingSave] = useState(false);
|
||||
|
||||
let nav = [{ name: "Instances", path: "/instances" }, { name: instance.name || "Unknown" }];
|
||||
if (instance.loading) {
|
||||
return <PageLayout nav={nav}><Spin size="large" /></PageLayout>;
|
||||
@@ -45,19 +45,8 @@ export default function InstanceViewPage(props) {
|
||||
|
||||
let instanceButtons = <Space>
|
||||
<StartStopInstanceButton instance={instance} />
|
||||
{instance.status === "stopped" && <Button
|
||||
loading={creatingSave}
|
||||
onClick={() => {
|
||||
setCreatingSave(true);
|
||||
libLink.messages.createSave.send(
|
||||
control, { instance_id: instanceId }
|
||||
).catch(notifyErrorHandler("Error creating save")).finally(() => {
|
||||
setCreatingSave(false);
|
||||
});
|
||||
}}
|
||||
>
|
||||
Create save
|
||||
</Button>}
|
||||
<CreateSaveModal instance={instance} />
|
||||
<LoadScenarioModal instance={instance} />
|
||||
<Popconfirm
|
||||
title="Permanently delete instance and server saves?"
|
||||
okText="Delete"
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
import React, { useContext, useState } from "react";
|
||||
import { Button, Col, Form, Input, Modal, Row, Typography } from "antd";
|
||||
|
||||
import libLink from "@clusterio/lib/link";
|
||||
import { readMapExchangeString } from "@clusterio/lib/factorio/exchange_string";
|
||||
|
||||
import ControlContext from "./ControlContext";
|
||||
import { notifyErrorHandler } from "../util/notify";
|
||||
|
||||
|
||||
export default function LoadScenarioModal(props) {
|
||||
let [visible, setVisible] = useState(false);
|
||||
let [loadingScenario, setLoadingScenario] = useState(false);
|
||||
let [form] = Form.useForm();
|
||||
let control = useContext(ControlContext);
|
||||
|
||||
function loadScenario() {
|
||||
let values = form.getFieldsValue();
|
||||
let scenario = values.scenario || "base/freeplay";
|
||||
let seed = values.seed === undefined ? null : Number.parseInt(values.seed, 10);
|
||||
let mapGenSettings = null;
|
||||
let mapSettings = null;
|
||||
if (values.exchangeString && values.exchangeString.trim()) {
|
||||
let result;
|
||||
try {
|
||||
result = readMapExchangeString(values.exchangeString);
|
||||
} catch (err) {
|
||||
form.setFields([{ name: "exchangeString", errors: [err.message] }]);
|
||||
return;
|
||||
}
|
||||
form.setFields([{ name: "exchangeString", errors: [] }]);
|
||||
mapGenSettings = result.map_gen_settings;
|
||||
mapSettings = result.map_settings;
|
||||
}
|
||||
if (values.mapGenSettings && values.mapGenSettings.trim()) {
|
||||
try {
|
||||
mapGenSettings = JSON.parse(values.mapGenSettings);
|
||||
} catch (err) {
|
||||
form.setFields([{ name: "mapGenSettings", errors: [err.message] }]);
|
||||
return;
|
||||
}
|
||||
form.setFields([{ name: "mapGenSettings", errors: [] }]);
|
||||
}
|
||||
if (values.mapSettings && values.mapSettings.trim()) {
|
||||
try {
|
||||
mapSettings = JSON.parse(values.mapGenSettings);
|
||||
} catch (err) {
|
||||
form.setFields([{ name: "mapSettings", errors: [err.message] }]);
|
||||
return;
|
||||
}
|
||||
form.setFields([{ name: "mapSettings", errors: [] }]);
|
||||
}
|
||||
|
||||
setLoadingScenario(true);
|
||||
libLink.messages.loadScenario.send(control, {
|
||||
instance_id: props.instance.id,
|
||||
scenario,
|
||||
seed,
|
||||
map_gen_settings: mapGenSettings,
|
||||
map_settings: mapSettings,
|
||||
}).then(() => {
|
||||
setVisible(false);
|
||||
}).catch(
|
||||
notifyErrorHandler("Error creating save")
|
||||
).finally(() => {
|
||||
setLoadingScenario(false);
|
||||
});
|
||||
}
|
||||
|
||||
function convertExchangeString() {
|
||||
let exchangeString = form.getFieldValue("exchangeString");
|
||||
let result;
|
||||
try {
|
||||
result = readMapExchangeString(exchangeString);
|
||||
} catch (err) {
|
||||
form.setFields([{ name: "exchangeString", errors: [err.message] }]);
|
||||
return;
|
||||
}
|
||||
form.setFields([{ name: "exchangeString", errors: [], value: "" }]);
|
||||
form.setFieldsValue({
|
||||
mapGenSettings: JSON.stringify(result.map_gen_settings, null, 4),
|
||||
mapSettings: JSON.stringify(result.map_settings, null, 4),
|
||||
});
|
||||
}
|
||||
|
||||
function handleCancel() {
|
||||
setVisible(false);
|
||||
}
|
||||
|
||||
return <>
|
||||
<Button
|
||||
loading={loadingScenario}
|
||||
disabled={props.instance.status !== "stopped"}
|
||||
onClick={() => {
|
||||
setVisible(true);
|
||||
}}
|
||||
>
|
||||
Load scenario
|
||||
</Button>
|
||||
<Modal
|
||||
title="Load Scenario"
|
||||
width={560}
|
||||
okText="Load"
|
||||
visible={visible}
|
||||
confirmLoading={loadingScenario}
|
||||
onOk={loadScenario}
|
||||
onCancel={handleCancel}
|
||||
destroyOnClose
|
||||
>
|
||||
<Form form={form} layout="vertical">
|
||||
<Form.Item name="scenario" label="Scenario">
|
||||
<Input placeholder="base/freeplay" />
|
||||
</Form.Item>
|
||||
<Form.Item name="seed" label="Seed">
|
||||
<Input type="number" />
|
||||
</Form.Item>
|
||||
<Form.Item name="exchangeString" label="Map Exchange String">
|
||||
<Input.TextArea allowClear={true} autoSize={{ minRows: 3 }} />
|
||||
</Form.Item>
|
||||
<div style={{ textAlign: "right", marginTop: -8 }}>
|
||||
<Button onClick={() => convertExchangeString()}>Convert to settings</Button>
|
||||
</div>
|
||||
<Form.Item
|
||||
name="mapGenSettings"
|
||||
label="Map Gen Settings"
|
||||
tooltip="JSON data to pass to Factorio with --map-gen-settings, overrides Map Exchange String"
|
||||
>
|
||||
<Input.TextArea allowClear={true} autoSize={{ minRows: 1 }} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="mapSettings"
|
||||
label="Map Settings"
|
||||
tooltip="JSON data to pass to Factorio with --map-settings, overrides Map Exchange String"
|
||||
>
|
||||
<Input.TextArea allowClear={true} autoSize={{ minRows: 1 }} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</>;
|
||||
}
|
||||
@@ -92,8 +92,13 @@ module.exports = (env = {}) => ({
|
||||
"util": require.resolve("util/"),
|
||||
"os": require.resolve("os-browserify/browser"),
|
||||
|
||||
// Required for zlib
|
||||
"assert": require.resolve("assert"),
|
||||
"stream": require.resolve("stream-browserify"),
|
||||
|
||||
"events": require.resolve("events/"),
|
||||
"path": require.resolve("path-browserify"),
|
||||
"zlib": require.resolve("browserify-zlib"),
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -7,6 +7,7 @@ const events = require("events");
|
||||
const libLink = require("@clusterio/lib/link");
|
||||
const libUsers = require("@clusterio/lib/users");
|
||||
|
||||
const testStrings = require("../lib/factorio/test_strings");
|
||||
const {
|
||||
TestControl, TestControlConnector, url, controlToken, slowTest,
|
||||
get, exec, execCtl, sendRcon, getControl, instancesDir,
|
||||
@@ -184,7 +185,7 @@ describe("Integration of Clusterio", function() {
|
||||
"afk-auto-kick", "Kick if AFK for more than 2 minutes.",
|
||||
],
|
||||
[
|
||||
"allow_commands", true,
|
||||
"allow_commands", "true",
|
||||
"allow-commands", "Allow Lua commands: Yes.",
|
||||
],
|
||||
[
|
||||
@@ -315,6 +316,33 @@ describe("Integration of Clusterio", function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe("instance load-scenario", function() {
|
||||
it("starts the instance with the given settings", async function() {
|
||||
slowTest(this);
|
||||
await execCtl("instance config set 44 factorio.enable_save_patching false");
|
||||
await execCtl("instance config set 44 player_auth.load_plugin false");
|
||||
await execCtl("instance config set 44 research_sync.load_plugin false");
|
||||
await execCtl("instance config set 44 statistics_exporter.load_plugin false");
|
||||
await execCtl("instance config set 44 subspace_storage.load_plugin false");
|
||||
try {
|
||||
let exchangeString = testStrings.modified.replace(/[\n\r]+/g, "");
|
||||
let args = `base/freeplay --seed 1234 --map-exchange-string "${exchangeString}"`;
|
||||
await execCtl(`instance load-scenario test ${args}`);
|
||||
await checkInstanceStatus(44, "running");
|
||||
await sendRcon(44, '/c game.print("disable achievements")');
|
||||
await sendRcon(44, '/c game.print("disable achievements")');
|
||||
assert.equal(await sendRcon(44, "/c rcon.print(game.default_map_gen_settings.seed)"), "1234\n");
|
||||
assert.equal(await sendRcon(44, "/c rcon.print(game.map_settings.pollution.ageing)"), "1.5\n");
|
||||
assert.equal(
|
||||
await sendRcon(44, "/c rcon.print(game.difficulty_settings.research_queue_setting)"), "never\n"
|
||||
);
|
||||
|
||||
} finally {
|
||||
await execCtl("instance stop test");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("instance delete", function() {
|
||||
it("deletes the instance", async function() {
|
||||
slowTest(this);
|
||||
|
||||
@@ -4,37 +4,8 @@ const zlib = require("zlib");
|
||||
|
||||
const libFactorio = require("@clusterio/lib/factorio");
|
||||
|
||||
const testStrings = require("./test_strings");
|
||||
|
||||
let defaultString = `
|
||||
>>>eNpjZGBkUGQAgwZ7EOZgSc5PzIHxQJgrOb+gILVIN78oFVmYM
|
||||
7moNCVVNz8TVXFqXmpupW5SYjGKYo7Movw8dBNYi0vy81BFSopSU
|
||||
4uRRbhLixLzMktz0fUyMF5imubZ0CLHAML/6xkU/v8HYSDrAdAvI
|
||||
MzA2ABRCRSDAdbknMy0NAYGBUcgdgJJMzIyVousc39YNcWeEaJGz
|
||||
wHK+AAVOZAEE/GEMfwccEqpwBgmSOYYg8FnJAbE0hKgFVBVHA4IB
|
||||
kSyBSTJyNj7duuC78cu2DH+Wfnxkm9Sgj2joavIuw9G6+yAkuwgf
|
||||
zLBiVkzQWAnzCsMMDMf2EOlbtoznj0DAm/sGVlBOkRAhIMFkDjgz
|
||||
czAKMAHZC3oARIKMgwwp9nBjBFxYEwDg28wnzyGMS7bo/sDGBA2I
|
||||
MPlQMQJEAG2EO4yRgjTod+B0UEeJiuJUALUb8SA7IYUhA9Pwqw9j
|
||||
GQ/mkMwIwLZH2giKg5YooELZGEKnHjBDHcNMDwvsMN4DvMdGJlBD
|
||||
JCqL0AxCA8kAzMKQgs4gIObmQEBgGmjrO/lHgDxa6G4<<<
|
||||
`;
|
||||
|
||||
let testString = `
|
||||
>>>eNp1UzFs00AUvZ8mbRIEypCBSlCCyFAhOQqBKULxlQUxMCCxgnCc
|
||||
S2rh2On5LAgMZOjAgISEurQLXSmCBTGwRWKhEkgIJraiMgBiaGmFOiC
|
||||
FO5/PsUz6pfv+9/6/99+/kwEBOo2k6c83NhrZtOkaNkIDXa286fZ6hG
|
||||
ouJaJIwTmT+i2iuZYdQ3GeOKTb15qGJ4oxQsMglbWo60iGYcSQ8Zjrh
|
||||
GUhwighHpcRSBHIEZ8ajuV35dmBwIPK1DRxmMX6ADnqmre1Rb/D86UF
|
||||
hNYX1lZncZpZNkGpmQ41PE87J6RFTe4YjND4hPDq943NwfIcEmv0AJV
|
||||
GI7F4tMXViYVgECgCjoWWPm66DqOurXmEMcvp1A3/br1pGV5Oq1ZqVW
|
||||
Hzk0ralCz5xDH79a5vM6tnW4Rma5XgQPVk8kTXtTzmU5Jg1g6tm0hfr
|
||||
ZwPLGPaVrstL6p0SYwEAPeLLy9/vbeigxysgsPgIESGTYVcCYMnL/Bh
|
||||
qbRKoQsq2NFBdt+LBbIp4y3CqiweBzK5LJIAu4vbD18f7Dfg77PdT1e
|
||||
bt3S4/q2w5J39IbQfFW+TitzaqrA3ahSkOLf0MPVFhw/vhf3SYUqcOC
|
||||
PczkoKQfXaNILCMb5df8Rd6QRS0hqKpoihHdgfNcm2Cj7ryTnKGC4K8
|
||||
jnh3gmXQRElVwYyxI8x4FMqOzsu4edrKK6hNZ5wU7V9G+ufEFL+7yHi
|
||||
cySQMp7wDHnRsBW571ORGn6fH2fUDj/FwV0iUbXPMbmTP7akkt8ChiL
|
||||
/BPceEs3j1M3iz71/ObT9tA==<<<`;
|
||||
|
||||
// Simple linear congruential random number generator
|
||||
class Random {
|
||||
@@ -52,10 +23,10 @@ class Random {
|
||||
describe("lib/factorio/exchange_string", function() {
|
||||
describe("readMapExchangeString", function() {
|
||||
it("should parse a valid string", function() {
|
||||
let result = libFactorio.readMapExchangeString(defaultString);
|
||||
let result = libFactorio.readMapExchangeString(testStrings.default);
|
||||
assert.equal(result.map_gen_settings.seed, 1234567890);
|
||||
|
||||
result = libFactorio.readMapExchangeString(testString);
|
||||
result = libFactorio.readMapExchangeString(testStrings.modified);
|
||||
assert.equal(result.checksum, 4092204126);
|
||||
});
|
||||
|
||||
@@ -66,7 +37,7 @@ describe("lib/factorio/exchange_string", function() {
|
||||
);
|
||||
|
||||
assert.throws(
|
||||
() => libFactorio.readMapExchangeString(defaultString.slice(0, 100)),
|
||||
() => libFactorio.readMapExchangeString(testStrings.default.slice(0, 100)),
|
||||
new Error("Not a map exchange string")
|
||||
);
|
||||
|
||||
@@ -90,7 +61,7 @@ describe("lib/factorio/exchange_string", function() {
|
||||
);
|
||||
}
|
||||
|
||||
let pastEnd = Buffer.from(defaultString.replace(/><\n/g, ""), "base64");
|
||||
let pastEnd = Buffer.from(testStrings.default.replace(/><\n/g, ""), "base64");
|
||||
// eslint-disable-next-line node/no-sync
|
||||
pastEnd = zlib.deflateSync(Buffer.concat([zlib.inflateSync(pastEnd), Buffer.from("junk")]));
|
||||
assert.throws(
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
"use strict";
|
||||
// Exchange strings for testing with
|
||||
|
||||
let default_ = `
|
||||
>>>eNpjZGBkUGQAgwZ7EOZgSc5PzIHxQJgrOb+gILVIN78oFVmYM
|
||||
7moNCVVNz8TVXFqXmpupW5SYjGKYo7Movw8dBNYi0vy81BFSopSU
|
||||
4uRRbhLixLzMktz0fUyMF5imubZ0CLHAML/6xkU/v8HYSDrAdAvI
|
||||
MzA2ABRCRSDAdbknMy0NAYGBUcgdgJJMzIyVousc39YNcWeEaJGz
|
||||
wHK+AAVOZAEE/GEMfwccEqpwBgmSOYYg8FnJAbE0hKgFVBVHA4IB
|
||||
kSyBSTJyNj7duuC78cu2DH+Wfnxkm9Sgj2joavIuw9G6+yAkuwgf
|
||||
zLBiVkzQWAnzCsMMDMf2EOlbtoznj0DAm/sGVlBOkRAhIMFkDjgz
|
||||
czAKMAHZC3oARIKMgwwp9nBjBFxYEwDg28wnzyGMS7bo/sDGBA2I
|
||||
MPlQMQJEAG2EO4yRgjTod+B0UEeJiuJUALUb8SA7IYUhA9Pwqw9j
|
||||
GQ/mkMwIwLZH2giKg5YooELZGEKnHjBDHcNMDwvsMN4DvMdGJlBD
|
||||
JCqL0AxCA8kAzMKQgs4gIObmQEBgGmjrO/lHgDxa6G4<<<
|
||||
`;
|
||||
|
||||
let modified = `
|
||||
>>>eNp1UzFs00AUvZ8mbRIEypCBSlCCyFAhOQqBKULxlQUxMCCxgnCc
|
||||
S2rh2On5LAgMZOjAgISEurQLXSmCBTGwRWKhEkgIJraiMgBiaGmFOiC
|
||||
FO5/PsUz6pfv+9/6/99+/kwEBOo2k6c83NhrZtOkaNkIDXa286fZ6hG
|
||||
ouJaJIwTmT+i2iuZYdQ3GeOKTb15qGJ4oxQsMglbWo60iGYcSQ8Zjrh
|
||||
GUhwighHpcRSBHIEZ8ajuV35dmBwIPK1DRxmMX6ADnqmre1Rb/D86UF
|
||||
hNYX1lZncZpZNkGpmQ41PE87J6RFTe4YjND4hPDq943NwfIcEmv0AJV
|
||||
GI7F4tMXViYVgECgCjoWWPm66DqOurXmEMcvp1A3/br1pGV5Oq1ZqVW
|
||||
Hzk0ralCz5xDH79a5vM6tnW4Rma5XgQPVk8kTXtTzmU5Jg1g6tm0hfr
|
||||
ZwPLGPaVrstL6p0SYwEAPeLLy9/vbeigxysgsPgIESGTYVcCYMnL/Bh
|
||||
qbRKoQsq2NFBdt+LBbIp4y3CqiweBzK5LJIAu4vbD18f7Dfg77PdT1e
|
||||
bt3S4/q2w5J39IbQfFW+TitzaqrA3ahSkOLf0MPVFhw/vhf3SYUqcOC
|
||||
PczkoKQfXaNILCMb5df8Rd6QRS0hqKpoihHdgfNcm2Cj7ryTnKGC4K8
|
||||
jnh3gmXQRElVwYyxI8x4FMqOzsu4edrKK6hNZ5wU7V9G+ufEFL+7yHi
|
||||
cySQMp7wDHnRsBW571ORGn6fH2fUDj/FwV0iUbXPMbmTP7akkt8ChiL
|
||||
/BPceEs3j1M3iz71/ObT9tA==<<<`;
|
||||
|
||||
module.exports = {
|
||||
default: default_,
|
||||
modified,
|
||||
};
|
||||
Reference in new issue
Block a user