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:
Hornwitser committed 2021-05-21 18:39:19 +02:00
1 parent 4a8a4c8227
commit dd216318b0
12 files changed
+462 -59

No files matched your search

+46 -2
View File
@@ -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;
},
+33 -2
View File
@@ -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)
);
}
}
}
+9
View File
@@ -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
View File
@@ -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");
}
+3
View File
@@ -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>
</>;
}
+5
View File
@@ -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"),
},
},
});
+29 -1
View File
@@ -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);
+5 -34
View File
@@ -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(
+38
View File
@@ -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,
};