mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-12 07:04:52 +09:00
Compare commits
153
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
999c1b6c7a | ||
|
|
61b1b1a106 | ||
|
|
8b4ec58459 | ||
|
|
6e26d32fed | ||
|
|
74d9ec38dc | ||
|
|
dbef2bd2b3 | ||
|
|
e0b521d2fe | ||
|
|
9a032aed2e | ||
|
|
72b56a00c7 | ||
|
|
c25bf23286 | ||
|
|
9e983aff84 | ||
|
|
8973dcc6dd | ||
|
|
475128c8c4 | ||
|
|
0cdb5d0ce4 | ||
|
|
075d54f708 | ||
|
|
62f3a99feb | ||
|
|
3b1ed1a667 | ||
|
|
6a6274e85c | ||
|
|
a9cc743106 | ||
|
|
2a7758f60d | ||
|
|
7e2ca58a59 | ||
|
|
718d4ed659 | ||
|
|
6b090cddbe | ||
|
|
56c023f643 | ||
|
|
78de7236c5 | ||
|
|
97c7a69c7a | ||
|
|
0c45e57732 | ||
|
|
88eff17dfd | ||
|
|
e1374d501e | ||
|
|
3f2bd2653e | ||
|
|
5bbd879f34 | ||
|
|
81aa2141b9 | ||
|
|
9c108dd4dd | ||
|
|
6415784e8a | ||
|
|
206f3db042 | ||
|
|
fb28693dec | ||
|
|
79ff52aa20 | ||
|
|
cb4616f47c | ||
|
|
bc7f3804c8 | ||
|
|
a452749caa | ||
|
|
1e8fb85122 | ||
|
|
66c0238a3d | ||
|
|
ca0281ae58 | ||
|
|
200c8dbdeb | ||
|
|
d0afcbf0ac | ||
|
|
13a069a798 | ||
|
|
0e849488e2 | ||
|
|
6264bd9888 | ||
|
|
cbd1939a74 | ||
|
|
7cf9319624 | ||
|
|
de2141ec50 | ||
|
|
0df1b8e1fd | ||
|
|
ec0243d7cf | ||
|
|
f32624586b | ||
|
|
6ea47cb6af | ||
|
|
a204163a6c | ||
|
|
f76c84c3f1 | ||
|
|
6caef9bda9 | ||
|
|
30cda4de3b | ||
|
|
a13d4c6805 | ||
|
|
56b805e19b | ||
|
|
5fd3a6b188 | ||
|
|
9d1e60aa03 | ||
|
|
d819644b01 | ||
|
|
042230dc2c | ||
|
|
a00183e94c | ||
|
|
c95d9f9464 | ||
|
|
a1237d8cfd | ||
|
|
35101a7e32 | ||
|
|
95d06293f5 | ||
|
|
2714b09e5d | ||
|
|
e733cfde03 | ||
|
|
008ea139c6 | ||
|
|
3338c439dd | ||
|
|
b956438d8d | ||
|
|
cdbbdd41c7 | ||
|
|
e2775adcc3 | ||
|
|
d909b4aa47 | ||
|
|
49611a2062 | ||
|
|
ee6ab6ab72 | ||
|
|
de92ca96b9 | ||
|
|
77281d0662 | ||
|
|
2831c07000 | ||
|
|
6ac8c0711f | ||
|
|
c9cbc48d95 | ||
|
|
50370bf858 | ||
|
|
d1fd4c3565 | ||
|
|
8672afe940 | ||
|
|
adf6763a0d | ||
|
|
d48a824a69 | ||
|
|
f28b73cf31 | ||
|
|
fec3cf5ddf | ||
|
|
c7cfb8ce70 | ||
|
|
93c210f0c7 | ||
|
|
251ec696a3 | ||
|
|
773a2c412e | ||
|
|
c0499e7f27 | ||
|
|
0a65946bf4 | ||
|
|
233410add7 | ||
|
|
fea9fb28ca | ||
|
|
e1b510b441 | ||
|
|
34301eb381 | ||
|
|
4a0956d174 | ||
|
|
3b814eeea4 | ||
|
|
98c6795f8f | ||
|
|
21dd80df9b | ||
|
|
0b34f07954 | ||
|
|
c978e8e748 | ||
|
|
71870daaba | ||
|
|
5a60f317ba | ||
|
|
b666a909ab | ||
|
|
0afe35b67c | ||
|
|
096ead5bc4 | ||
|
|
fa4c10dce2 | ||
|
|
1f8ac50950 | ||
|
|
10d5b0da9b | ||
|
|
6c32271c56 | ||
|
|
a122f2b09e | ||
|
|
f35be2f355 | ||
|
|
d66d22a9cd | ||
|
|
7d6a5bb6d2 | ||
|
|
bf2e78d530 | ||
|
|
bf4b7fe1b8 | ||
|
|
66ba706c56 | ||
|
|
79c9ac839e | ||
|
|
7f59b200b1 | ||
|
|
d719a73328 | ||
|
|
616bb9cc8f | ||
|
|
3ae55cbd27 | ||
|
|
7ae9ff6649 | ||
|
|
0b3007d22d | ||
|
|
bd7a116860 | ||
|
|
0c9ed582cc | ||
|
|
d7bfc2193e | ||
|
|
ce9e1d876a | ||
|
|
86d4f3dba4 | ||
|
|
0fe5633926 | ||
|
|
595b4e5ac9 | ||
|
|
7d2ac5808f | ||
|
|
fddafaec09 | ||
|
|
abf4b5d926 | ||
|
|
00dfae8bd1 | ||
|
|
2ef6c93b05 | ||
|
|
2165acb171 | ||
|
|
8357fa23ce | ||
|
|
94d434c8ef | ||
|
|
56cf131c70 | ||
|
|
a2be560b8b | ||
|
|
a3e33f6164 | ||
|
|
01e96a4dcb | ||
|
|
d189a82494 | ||
|
|
c15709ddc1 | ||
|
|
3f29a722bb |
No files matched your search
+5
-7
@@ -11,8 +11,9 @@ pids
|
|||||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
lib-cov
|
lib-cov
|
||||||
|
|
||||||
# Coverage directory used by tools like istanbul
|
# Coverage directories used by tools like nyc and codecov
|
||||||
coverage
|
coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
.grunt
|
.grunt
|
||||||
@@ -22,9 +23,9 @@ coverage
|
|||||||
|
|
||||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||||
build/Release
|
build/Release
|
||||||
|
dist/
|
||||||
|
|
||||||
node_modules/*
|
node_modules/*
|
||||||
!node_modules/_app
|
|
||||||
|
|
||||||
Factorio*
|
Factorio*
|
||||||
/factorio
|
/factorio
|
||||||
@@ -62,8 +63,5 @@ sharedPlugins/scenario_loader/scenarios
|
|||||||
# File used for marking plugins as disabled
|
# File used for marking plugins as disabled
|
||||||
sharedPlugins/*/DISABLED
|
sharedPlugins/*/DISABLED
|
||||||
|
|
||||||
\.cache-require-paths\.json
|
# IDE
|
||||||
|
.idea/
|
||||||
\.vs/
|
|
||||||
|
|
||||||
bin/
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
|
|
||||||
version: v1.12.0
|
|
||||||
ignore: {}
|
|
||||||
# patches apply the minimum changes required to fix a vulnerability
|
|
||||||
patch:
|
|
||||||
'npm:lodash:20180130':
|
|
||||||
- html5-validator > caporal > cli-table2 > lodash:
|
|
||||||
patched: '2018-07-03T06:07:55.532Z'
|
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
language: node_js
|
language: node_js
|
||||||
node_js:
|
node_js:
|
||||||
- "9"
|
- 10
|
||||||
before_install:
|
before_install:
|
||||||
- sudo apt-get -qq update
|
- sudo apt-get -qq update
|
||||||
- sudo apt-get install -y curl git tar
|
- sudo apt-get install -y curl git tar
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"CurrentProjectSetting": null
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"ExpandedNodes": [
|
||||||
|
"",
|
||||||
|
"\\sharedPlugins"
|
||||||
|
],
|
||||||
|
"SelectedNode": "\\.gitignore",
|
||||||
|
"PreviewInSolutionExplorer": false
|
||||||
|
}
|
||||||
File diff suppressed because it is too large.
Load diff
Binary file not shown.
@@ -0,0 +1,41 @@
|
|||||||
|
Changelog
|
||||||
|
=========
|
||||||
|
|
||||||
|
Version 1.2.2
|
||||||
|
-------------
|
||||||
|
|
||||||
|
- Fixed possible crash with modded technologies named the same as a built-in
|
||||||
|
Object prototype property in researchSync.
|
||||||
|
- Fixed progress of a current infinite tech carrying over to the next one
|
||||||
|
when researching it and another node completes it in researchSync.
|
||||||
|
- Fixed progress of a previous infinite tech from another node being applied
|
||||||
|
to the current one in researchSync.
|
||||||
|
- Fixed crash in researchSync when modded technologies are present only on some
|
||||||
|
nodes.
|
||||||
|
- Fixed install failing due to bcrypt version less than 3 not being supported
|
||||||
|
on node v10.
|
||||||
|
- Reordered install instructions to avoid problem with npm creating files owned
|
||||||
|
by root in the home directory.
|
||||||
|
- Swapped curl out with wget in the install instructions as the latter comes
|
||||||
|
pre-installed on Ubuntu.
|
||||||
|
|
||||||
|
### Breaking Changes
|
||||||
|
|
||||||
|
- Node.js versions below 10 are no longer supported.
|
||||||
|
|
||||||
|
|
||||||
|
Version 1.2.1
|
||||||
|
-------------
|
||||||
|
|
||||||
|
- Updated node-factorio-api to v0.3.8 to fix mod downloads randomly breaking
|
||||||
|
([#229][#229].)
|
||||||
|
- Fixed SIGINT being sent twice to Factorio server when interrupted by CTRL+C
|
||||||
|
on Linux ([#217][#217].)
|
||||||
|
- Fixed package.json incorrectly reporting the license as ISC.
|
||||||
|
|
||||||
|
[#217]: https://github.com/clusterio/factorioClusterio/issues/217
|
||||||
|
[#229]: https://github.com/clusterio/factorioClusterio/issues/229
|
||||||
|
|
||||||
|
### Breaking Changes
|
||||||
|
|
||||||
|
- Added authentication to the socket.io server running on the master.
|
||||||
@@ -1,5 +1,13 @@
|
|||||||
const needle = require("needle");
|
const needle = require("needle");
|
||||||
const config = require("./config.json");
|
const cli = require("cli");
|
||||||
|
const config = require("./../config.json");
|
||||||
|
|
||||||
|
let options = cli.parse()
|
||||||
|
cli.setUsage(`node cluster_rcon.js /c game.player.force = "player"`)
|
||||||
|
if(process.argv.length < 3){
|
||||||
|
cli.getUsage()
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
const masterIP = `${config.masterIP}:${config.masterPort}`;
|
const masterIP = `${config.masterIP}:${config.masterPort}`;
|
||||||
const token = config.masterAuthToken
|
const token = config.masterAuthToken
|
||||||
@@ -30,4 +38,4 @@ let killBiters = `/silent-command local surface=game.surfaces[1] for key, entity
|
|||||||
// console.log(resp.body)
|
// console.log(resp.body)
|
||||||
// });
|
// });
|
||||||
// }, 1000*60*5);
|
// }, 1000*60*5);
|
||||||
})();
|
})();
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
const needle = require("needle")
|
||||||
|
const config = require("./../config.json")
|
||||||
|
const cli = require("cli")
|
||||||
|
|
||||||
|
let options = cli.parse({
|
||||||
|
name: ["n", "Name of the player to delete, case sensitive", "string"],
|
||||||
|
token: [false, "Factorio auth token", "string", config.masterAuthToken]
|
||||||
|
})
|
||||||
|
if(!options.name){
|
||||||
|
cli.getUsage()
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
needle.post(config.masterIP+":"+config.masterPort+"/api/playerManager/deletePlayer", {
|
||||||
|
name: options.name,
|
||||||
|
token: options.token
|
||||||
|
}, (err, resp) => {
|
||||||
|
console.log(resp.body)
|
||||||
|
});
|
||||||
+6
-8
@@ -1,23 +1,21 @@
|
|||||||
FROM node:9.11.2
|
FROM node:10
|
||||||
RUN apt-get update && apt install git curl tar -y
|
RUN apt-get update && apt install git curl tar -y
|
||||||
RUN mkdir factorioClusterio
|
RUN mkdir factorioClusterio
|
||||||
|
|
||||||
#RUN git clone -b master https://github.com/Danielv123/factorioClusterio.git && cd factorioClusterio && npm install
|
RUN git clone -b master https://github.com/Danielv123/factorioClusterio.git && cd factorioClusterio && npm install --only=production
|
||||||
COPY . /factorioClusterio
|
RUN cd factorioClusterio && curl -o factorio.tar.gz -L https://www.factorio.com/get-download/latest/headless/linux64 && tar -xf factorio.tar.gz
|
||||||
RUN cd /factorioClusterio && npm install
|
|
||||||
RUN cd /factorioClusterio && curl -o factorio.tar.gz -L https://www.factorio.com/get-download/latest/headless/linux64 && tar -xf factorio.tar.gz && rm factorio.tar.gz
|
|
||||||
|
|
||||||
WORKDIR /factorioClusterio
|
WORKDIR factorioClusterio
|
||||||
RUN mkdir instances sharedMods
|
RUN mkdir instances sharedMods
|
||||||
RUN cp config.json.dist config.json
|
RUN cp config.json.dist config.json
|
||||||
|
|
||||||
RUN node client.js download
|
RUN node client.js download
|
||||||
|
|
||||||
LABEL maintainer "danielv@live.no"
|
LABEL maintainer "Sir3lit@gmail.com"
|
||||||
|
|
||||||
EXPOSE 8080 34167
|
EXPOSE 8080 34167
|
||||||
VOLUME /factorioClusterio/instances
|
VOLUME /factorioClusterio/instances
|
||||||
VOLUME /factorioClusterio/sharedMods
|
VOLUME /factorioClusterio/sharedMods
|
||||||
VOLUME /factorioClusterio/sharedPlugins
|
VOLUME /factorioClusterio/sharedPlugins
|
||||||
|
|
||||||
CMD RCONPORT="$RCONPORT" FACTORIOPORT="34167" MODE="$MODE" node $MODE\.js start $INSTANCE
|
CMD RCONPORT="$RCONPORT" FACTORIOPORT="$FACTORIOPORT" MODE="$MODE" node $MODE\.js start $INSTANCE
|
||||||
@@ -8,6 +8,13 @@
|
|||||||
|
|
||||||
Discord for development/support/play: https://discord.gg/5XuDkje
|
Discord for development/support/play: https://discord.gg/5XuDkje
|
||||||
|
|
||||||
|
## Important notice
|
||||||
|
|
||||||
|
This is the stable branch of the project. The
|
||||||
|
[master branch][http://github.com/clusterio/factorioClusterio/tree/master]
|
||||||
|
is undergoing significant changes that breaks compatibility to plugins and
|
||||||
|
existing installations and is not recommended for use.
|
||||||
|
|
||||||
### Ways to support me/the project:
|
### Ways to support me/the project:
|
||||||
|
|
||||||
* Contribute with code/documentation. Always nice to move the project forward
|
* Contribute with code/documentation. Always nice to move the project forward
|
||||||
@@ -78,7 +85,7 @@ Clusterio exploits one of the games logging features, game.write_file and RCON t
|
|||||||
|
|
||||||
7. client.js on server2: We were allowed to import x of item y, run command /c remote.call("clusterio", "importMany", "{'copper-plate':120}")
|
7. client.js on server2: We were allowed to import x of item y, run command /c remote.call("clusterio", "importMany", "{'copper-plate':120}")
|
||||||
|
|
||||||
This process works the same for both items and liquids, independant on what mods are used. Yes, modded items are fully supported.
|
This process works the same for both items and liquids, independent on what mods are used. Yes, modded items are fully supported.
|
||||||
|
|
||||||
Clusterio can also do a few other neat things, such as giving you access to epoch time, syncing player inventories between servers, keeping track of playtime (playerManager plugin), teleporting trains between servers (trainTeleports) and exporting tons of factorio related statistics to Prometheus for graphing in grafana.
|
Clusterio can also do a few other neat things, such as giving you access to epoch time, syncing player inventories between servers, keeping track of playtime (playerManager plugin), teleporting trains between servers (trainTeleports) and exporting tons of factorio related statistics to Prometheus for graphing in grafana.
|
||||||
|
|
||||||
@@ -88,19 +95,21 @@ NodeJS does not support EOL ubuntu releases. Make sure you are on the most recen
|
|||||||
|
|
||||||
Master and all slaves:
|
Master and all slaves:
|
||||||
|
|
||||||
sudo curl -sL https://deb.nodesource.com/setup_9.x | sudo -E bash -
|
wget -qO - https://deb.nodesource.com/setup_10.x | sudo -E bash -
|
||||||
sudo apt install -y nodejs python-dev git wget curl tar build-essential
|
sudo apt install -y nodejs python-dev git build-essential
|
||||||
sudo npm install pm2 -g
|
git clone -b 1.2.x https://github.com/Danielv123/factorioClusterio.git
|
||||||
git clone https://github.com/Danielv123/factorioClusterio.git
|
|
||||||
cd factorioClusterio
|
cd factorioClusterio
|
||||||
curl -o factorio.tar.gz -L https://www.factorio.com/get-download/latest/headless/linux64
|
wget -O factorio.tar.gz https://www.factorio.com/get-download/latest/headless/linux64
|
||||||
tar -xf factorio.tar.gz
|
tar -xf factorio.tar.gz
|
||||||
npm install
|
npm install --only=production
|
||||||
cp config.json.dist config.json
|
cp config.json.dist config.json
|
||||||
node ./lib/npmPostinstall.js
|
node ./lib/npmPostinstall.js
|
||||||
|
|
||||||
|
downloads and installs nodejs, git and clusterio. To specify a version, change "latest" in the link to a version number like 0.14.21.
|
||||||
|
|
||||||
downloads and installs nodejs, pm2, git and clusterio. To specify a version, change "latest" in the link to a version number like 0.14.21.
|
Optional step (if you want to use pm2):
|
||||||
|
|
||||||
|
sudo npm install pm2 -g
|
||||||
|
|
||||||
Now you need to edit the `config.json` file. If you skip this step nothing will work.
|
Now you need to edit the `config.json` file. If you skip this step nothing will work.
|
||||||
Pretty much all the blank fields should be filled in, except on the master where a few can be omitted.
|
Pretty much all the blank fields should be filled in, except on the master where a few can be omitted.
|
||||||
@@ -113,11 +122,12 @@ Pretty much all the blank fields should be filled in, except on the master where
|
|||||||
|
|
||||||
**Master**
|
**Master**
|
||||||
|
|
||||||
|
node master.js
|
||||||
|
|
||||||
|
Or with pm2 (it's recommened to run it without pm2 first):
|
||||||
|
|
||||||
pm2 start master.js --name master
|
pm2 start master.js --name master
|
||||||
|
|
||||||
OR
|
|
||||||
|
|
||||||
node master.js
|
|
||||||
|
|
||||||
**Server Host**
|
**Server Host**
|
||||||
|
|
||||||
@@ -159,7 +169,7 @@ Game Client = The people connecting to the server
|
|||||||
|
|
||||||
**Requirements**
|
**Requirements**
|
||||||
|
|
||||||
download and install nodeJS 8 or 9 from http://nodejs.org
|
download and install nodeJS 10 from http://nodejs.org
|
||||||
|
|
||||||
download and install git from https://git-scm.com/
|
download and install git from https://git-scm.com/
|
||||||
|
|
||||||
@@ -226,17 +236,9 @@ Copy your config.json.dist to config.json and configure it.
|
|||||||
|
|
||||||
Some systems don't let non root processes listen to ports below 1000. Either run with `sudo` or change config.json to use higher port numbers.
|
Some systems don't let non root processes listen to ports below 1000. Either run with `sudo` or change config.json to use higher port numbers.
|
||||||
|
|
||||||
### InitializationError: First argument must be an abstract-leveldown compliant store
|
According to [this link](https://askubuntu.com/questions/839520/open-port-443-for-a-node-web-app) if you manually installed node.js following the above instructions, you may need to run the following command to fix this issue:
|
||||||
|
|
||||||
do:
|
sudo setcap 'cap_net_bind_service=+ep' $(readlink -f $(which node))
|
||||||
|
|
||||||
npm install linvodb3@3.25.1
|
|
||||||
|
|
||||||
if that does not suffice do
|
|
||||||
|
|
||||||
npm install levelup@1.3.9
|
|
||||||
|
|
||||||
If it still doesn't work, come over to our support discord linked at the top of this readme.
|
|
||||||
|
|
||||||
### Portforwarding doesn't work on the master server when running under WSL
|
### Portforwarding doesn't work on the master server when running under WSL
|
||||||
|
|
||||||
@@ -279,7 +281,7 @@ node client.js list
|
|||||||
```
|
```
|
||||||
git pull
|
git pull
|
||||||
|
|
||||||
npm install
|
npm install --only=production
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Download the latest version of the factorioClusterioMod from its github repo
|
3. Download the latest version of the factorioClusterioMod from its github repo
|
||||||
|
|||||||
Binary file not shown.
@@ -1,4 +1,5 @@
|
|||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
|
const Tail = require('tail').Tail;
|
||||||
const https = require('follow-redirects').https;
|
const https = require('follow-redirects').https;
|
||||||
const needle = require("needle");
|
const needle = require("needle");
|
||||||
const child_process = require('child_process');
|
const child_process = require('child_process');
|
||||||
@@ -6,19 +7,18 @@ const path = require('path');
|
|||||||
const syncRequest = require('sync-request');
|
const syncRequest = require('sync-request');
|
||||||
const request = require("request");
|
const request = require("request");
|
||||||
const ncp = require('ncp').ncp;
|
const ncp = require('ncp').ncp;
|
||||||
const Rcon = require('rcon-client').Rcon;
|
const Rcon = require('rcon-client-fork').Rcon;
|
||||||
const hashFiles = require('hash-files');
|
|
||||||
const _ = require('underscore');
|
|
||||||
const deepmerge = require("deepmerge");
|
const deepmerge = require("deepmerge");
|
||||||
const getMac = require('getmac').getMac;
|
const getMac = require('getmac').getMac;
|
||||||
const rmdirSync = require('rmdir-sync');
|
const rmdirSync = require('rmdir-sync');
|
||||||
const ioClient = require("socket.io-client");
|
const ioClient = require("socket.io-client");
|
||||||
|
|
||||||
// internal libraries
|
// internal libraries
|
||||||
const objectOps = require("./lib/objectOps.js");
|
const objectOps = require("lib/objectOps.js");
|
||||||
const fileOps = require("_app/fileOps");
|
const fileOps = require("lib/fileOps");
|
||||||
const stringUtils = require("./lib/stringUtils.js");
|
const stringUtils = require("lib/stringUtils.js");
|
||||||
const configManager = require("./lib/manager/configManager.js");
|
const configManager = require("lib/manager/configManager.js");
|
||||||
|
const hashFile = require('lib/hash').hashFile;
|
||||||
|
|
||||||
// argument parsing
|
// argument parsing
|
||||||
const args = require('minimist')(process.argv.slice(2));
|
const args = require('minimist')(process.argv.slice(2));
|
||||||
@@ -53,8 +53,11 @@ if(!config.masterAuthToken || typeof config.masterAuthToken !== "string"){
|
|||||||
console.error("Master server now needs an access token for write operations. As clusterio slaves depends \
|
console.error("Master server now needs an access token for write operations. As clusterio slaves depends \
|
||||||
upon this, please add your token to config.json in the field named masterAuthToken. \
|
upon this, please add your token to config.json in the field named masterAuthToken. \
|
||||||
You can retrieve your auth token from the master in secret-api-token.txt after running it once.");
|
You can retrieve your auth token from the master in secret-api-token.txt after running it once.");
|
||||||
|
process.exitCode = 1;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
const needleOptionsWithTokenAuthHeader = {
|
const needleOptionsWithTokenAuthHeader = {
|
||||||
|
compressed:true,
|
||||||
headers: {
|
headers: {
|
||||||
'x-access-token': config.masterAuthToken
|
'x-access-token': config.masterAuthToken
|
||||||
},
|
},
|
||||||
@@ -187,7 +190,7 @@ if (!command || command == "help" || command == "--help") {
|
|||||||
if(tool == "mods"){
|
if(tool == "mods"){
|
||||||
(async function(){try{
|
(async function(){try{
|
||||||
// do require down here to reduce master load time
|
// do require down here to reduce master load time
|
||||||
const modManager = require("./lib/manager/modManager.js")(config);
|
const modManager = require("lib/manager/modManager.js")(config);
|
||||||
|
|
||||||
// allow managing mods
|
// allow managing mods
|
||||||
if(action == "list"){
|
if(action == "list"){
|
||||||
@@ -227,7 +230,7 @@ if (!command || command == "help" || command == "--help") {
|
|||||||
}
|
}
|
||||||
} else if(tool == "plugins"){
|
} else if(tool == "plugins"){
|
||||||
(async function(){try{
|
(async function(){try{
|
||||||
const pluginManager = require("./lib/manager/pluginManager.js")(config);
|
const pluginManager = require("lib/manager/pluginManager.js")(config);
|
||||||
if(action == "list"){
|
if(action == "list"){
|
||||||
await pluginManager.listPlugins();
|
await pluginManager.listPlugins();
|
||||||
} else if(action == "add" || action == "install" || action == "download"){
|
} else if(action == "add" || action == "install" || action == "download"){
|
||||||
@@ -300,7 +303,7 @@ if (!command || command == "help" || command == "--help") {
|
|||||||
fs.mkdirSync(instancedirectory + "/mods/");
|
fs.mkdirSync(instancedirectory + "/mods/");
|
||||||
fs.mkdirSync(instancedirectory + "/instanceMods/");
|
fs.mkdirSync(instancedirectory + "/instanceMods/");
|
||||||
fs.mkdirSync(instancedirectory + "/scenarios/");
|
fs.mkdirSync(instancedirectory + "/scenarios/");
|
||||||
ncp("./lib/scenarios", path.resolve(instancedirectory, "scenarios"), err => {
|
ncp("lib/scenarios", path.resolve(instancedirectory, "scenarios"), err => {
|
||||||
if (err) console.error(err)
|
if (err) console.error(err)
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -317,6 +320,7 @@ write-data=${ path.resolve(config.instanceDirectory, instance) }\r\n
|
|||||||
"clientPort": args["rcon-port"] || process.env.RCONPORT || Math.floor(Math.random() * 65535),
|
"clientPort": args["rcon-port"] || process.env.RCONPORT || Math.floor(Math.random() * 65535),
|
||||||
"__comment_clientPassword": "This is the rcon password. Its also used for making an instanceID. Make sure its unique and not blank.",
|
"__comment_clientPassword": "This is the rcon password. Its also used for making an instanceID. Make sure its unique and not blank.",
|
||||||
"clientPassword": args["rcon-password"] || Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 8),
|
"clientPassword": args["rcon-password"] || Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 8),
|
||||||
|
"info": {}
|
||||||
}
|
}
|
||||||
console.log("Clusterio | Created instance with settings:")
|
console.log("Clusterio | Created instance with settings:")
|
||||||
console.log(instconf);
|
console.log(instconf);
|
||||||
@@ -357,19 +361,22 @@ write-data=${ path.resolve(config.instanceDirectory, instance) }\r\n
|
|||||||
'--rcon-port', Number(process.env.RCONPORT) || instconf.clientPort,
|
'--rcon-port', Number(process.env.RCONPORT) || instconf.clientPort,
|
||||||
'--rcon-password', instconf.clientPassword,
|
'--rcon-password', instconf.clientPassword,
|
||||||
], {
|
], {
|
||||||
'stdio': ['pipe', 'pipe', 'pipe']
|
'stdio': ['pipe', 'pipe', 'pipe'],
|
||||||
|
'detached': process.platform === "linux",
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
factorio.stdout.on("data", data => {
|
factorio.stdout.on("data", data => {
|
||||||
data = data.toString("utf8").replace(/(\r\n\t|\n|\r\t)/gm,"");
|
data = data.toString("utf8").replace(/(\r\n\t|\n|\r\t)/gm,"");
|
||||||
console.log(data);
|
console.log(data);
|
||||||
if(data.includes("Starting RCON interface")){
|
if(data.includes("Starting RCON interface")){
|
||||||
let client = new Rcon();
|
let client = new Rcon({
|
||||||
|
packetResponseTimeout: 200000,
|
||||||
|
maxPending: 5
|
||||||
|
});
|
||||||
client.connect({
|
client.connect({
|
||||||
host: 'localhost',
|
host: 'localhost',
|
||||||
port: Number(process.env.RCONPORT) || instconf.clientPort,
|
port: Number(process.env.RCONPORT) || instconf.clientPort,
|
||||||
password: instconf.clientPassword,
|
password: instconf.clientPassword
|
||||||
timeout: 5000
|
|
||||||
});
|
});
|
||||||
client.onDidAuthenticate(() => {
|
client.onDidAuthenticate(() => {
|
||||||
console.log('Clusterio | RCON Authenticated!');
|
console.log('Clusterio | RCON Authenticated!');
|
||||||
@@ -467,7 +474,8 @@ write-data=${ path.resolve(config.instanceDirectory, instance) }\r\n
|
|||||||
'--server-settings', instancedirectory + '/server-settings.json',
|
'--server-settings', instancedirectory + '/server-settings.json',
|
||||||
'--port', args.port || Number(process.env.FACTORIOPORT) || instanceconfig.factorioPort
|
'--port', args.port || Number(process.env.FACTORIOPORT) || instanceconfig.factorioPort
|
||||||
], {
|
], {
|
||||||
'stdio': ['pipe', 'pipe', 'pipe']
|
'stdio': ['pipe', 'pipe', 'pipe'],
|
||||||
|
'detached': process.platform === "linux",
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -493,8 +501,10 @@ write-data=${ path.resolve(config.instanceDirectory, instance) }\r\n
|
|||||||
// connect to the server with rcon
|
// connect to the server with rcon
|
||||||
if(true || process.platform != "linux"){
|
if(true || process.platform != "linux"){
|
||||||
// IP, port, password
|
// IP, port, password
|
||||||
client = new Rcon();
|
client = new Rcon({
|
||||||
|
packetResponseTimeout: 200000, // 200s, should allow for commands up to 1250kB in length
|
||||||
|
maxPending: 5
|
||||||
|
});
|
||||||
// check the logfile to see if the RCON interface is running as there is no way to continue without it
|
// check the logfile to see if the RCON interface is running as there is no way to continue without it
|
||||||
// we read the log every 2 seconds and stop looping when we start connecting to factorio
|
// we read the log every 2 seconds and stop looping when we start connecting to factorio
|
||||||
function checkRcon() {
|
function checkRcon() {
|
||||||
@@ -505,7 +515,6 @@ write-data=${ path.resolve(config.instanceDirectory, instance) }\r\n
|
|||||||
host: 'localhost',
|
host: 'localhost',
|
||||||
port: args["rcon-port"] || Number(process.env.RCONPORT) || instanceconfig.clientPort,
|
port: args["rcon-port"] || Number(process.env.RCONPORT) || instanceconfig.clientPort,
|
||||||
password: args["rcon-password"] || instanceconfig.clientPassword,
|
password: args["rcon-password"] || instanceconfig.clientPassword,
|
||||||
timeout: 5000
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setTimeout(function(){
|
setTimeout(function(){
|
||||||
@@ -557,12 +566,18 @@ write-data=${ path.resolve(config.instanceDirectory, instance) }\r\n
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ensure instancemanagement only ever runs once
|
// ensure instancemanagement only ever runs once
|
||||||
_.once(instanceManagement);
|
var _instanceInitialized;
|
||||||
async function instanceManagement(instanceconfig) {
|
async function instanceManagement(instanceconfig) {
|
||||||
|
if (_instanceInitialized) return;
|
||||||
|
_instanceInitialized = true;
|
||||||
|
|
||||||
console.log("Started instanceManagement();");
|
console.log("Started instanceManagement();");
|
||||||
|
|
||||||
/* Open websocket connection to master */
|
/* Open websocket connection to master */
|
||||||
var socket = ioClient("http://"+config.masterIP+":"+config.masterPort);
|
var socket = ioClient("http://"+config.masterIP+":"+config.masterPort+"?token="+config.masterAuthToken);
|
||||||
|
socket.on("error", err => {
|
||||||
|
console.log("SOCKET | Error: ", err);
|
||||||
|
});
|
||||||
socket.on("hello", data => {
|
socket.on("hello", data => {
|
||||||
console.log("SOCKET | registering slave!");
|
console.log("SOCKET | registering slave!");
|
||||||
socket.emit("registerSlave", {
|
socket.emit("registerSlave", {
|
||||||
@@ -572,15 +587,12 @@ async function instanceManagement(instanceconfig) {
|
|||||||
setInterval(B=> socket.emit("heartbeat"), 10000);
|
setInterval(B=> socket.emit("heartbeat"), 10000);
|
||||||
|
|
||||||
// load plugins and execute onLoad event
|
// load plugins and execute onLoad event
|
||||||
const pluginManager = require("./lib/manager/pluginManager.js")(config);
|
const pluginManager = require("lib/manager/pluginManager.js")(config);
|
||||||
let pluginsToLoad = await pluginManager.getPlugins();
|
let pluginsToLoad = await pluginManager.getPlugins();
|
||||||
let plugins = [];
|
let plugins = [];
|
||||||
|
|
||||||
for(let i = 0; i < pluginsToLoad.length; i++){
|
for(let i = 0; i < pluginsToLoad.length; i++){
|
||||||
let pluginLoadStarted = Date.now();
|
let pluginLoadStarted = Date.now();
|
||||||
let log = function(message) {
|
|
||||||
console.log("Clusterio | "+ pluginsToLoad[i].name + " | " + message);
|
|
||||||
}
|
|
||||||
let combinedConfig = deepmerge(instanceconfig,config,{clone:true});
|
let combinedConfig = deepmerge(instanceconfig,config,{clone:true});
|
||||||
let pluginConfig = pluginsToLoad[i];
|
let pluginConfig = pluginsToLoad[i];
|
||||||
|
|
||||||
@@ -592,7 +604,7 @@ async function instanceManagement(instanceconfig) {
|
|||||||
let pluginClass = require(path.resolve(pluginConfig.pluginPath, "index.js"));
|
let pluginClass = require(path.resolve(pluginConfig.pluginPath, "index.js"));
|
||||||
plugins[i] = new pluginClass(combinedConfig, async function(data, callback){
|
plugins[i] = new pluginClass(combinedConfig, async function(data, callback){
|
||||||
if(data && data.toString('utf8')[0] != "/") {
|
if(data && data.toString('utf8')[0] != "/") {
|
||||||
log(data.toString('utf8'));
|
console.log("Clusterio | "+ pluginsToLoad[i].name + " | " + data.toString('utf8'));
|
||||||
return true;
|
return true;
|
||||||
} else if (data && data.toString('utf8')[0] == "/"){
|
} else if (data && data.toString('utf8')[0] == "/"){
|
||||||
return messageInterface(data.toString('utf8'), callback);
|
return messageInterface(data.toString('utf8'), callback);
|
||||||
@@ -618,37 +630,50 @@ async function instanceManagement(instanceconfig) {
|
|||||||
global.subscribedFiles[pluginConfig.scriptOutputFileSubscription] = true;
|
global.subscribedFiles[pluginConfig.scriptOutputFileSubscription] = true;
|
||||||
console.log("Clusterio | Registered file subscription on "+instancedirectory + "/script-output/" + pluginConfig.scriptOutputFileSubscription);
|
console.log("Clusterio | Registered file subscription on "+instancedirectory + "/script-output/" + pluginConfig.scriptOutputFileSubscription);
|
||||||
|
|
||||||
fs.watch(instancedirectory + "/script-output/" + pluginConfig.scriptOutputFileSubscription, fileChangeHandler);
|
|
||||||
// run once in case a plugin wrote out information before the plugin loaded fully
|
|
||||||
// delay, so the socket got enough time to connect
|
|
||||||
setTimeout(()=> {
|
|
||||||
fileChangeHandler(false, pluginConfig.scriptOutputFileSubscription);
|
|
||||||
}, 500);
|
|
||||||
// send file contents to plugin for processing
|
|
||||||
function fileChangeHandler(eventType, filename) {
|
|
||||||
if(filename != null){
|
|
||||||
setTimeout(
|
|
||||||
()=>{
|
|
||||||
// get array of lines in file
|
|
||||||
let stuff = fs.readFileSync(instancedirectory + "/script-output/" + filename, "utf8").split("\n");
|
|
||||||
|
|
||||||
// if you found anything, reset the file
|
if(!pluginConfig.fileReadDelay || pluginConfig.fileReadDelay == 0) {
|
||||||
if (stuff[0]) {
|
// only wipe the file on restart for now, should most likely be rotated during runtime too
|
||||||
fs.writeFileSync(instancedirectory + "/script-output/" + filename, "");
|
fs.writeFileSync(instancedirectory + "/script-output/" + pluginConfig.scriptOutputFileSubscription, "");
|
||||||
}
|
let tail = new Tail(instancedirectory + "/script-output/" + pluginConfig.scriptOutputFileSubscription);
|
||||||
for(let o = 0; o < stuff.length; o++) {
|
tail.on("line", function (data) {
|
||||||
if(stuff[o] && !stuff[o].includes('\u0000\u0000')) {
|
plugins[i].scriptOutput(data);
|
||||||
try{
|
});
|
||||||
plugins[i].scriptOutput(stuff[o]);
|
} else {
|
||||||
}catch(e){console.error(e)}
|
fs.watch(instancedirectory + "/script-output/" + pluginConfig.scriptOutputFileSubscription, fileChangeHandler);
|
||||||
}
|
// run once in case a plugin wrote out information before the plugin loaded fully
|
||||||
}
|
// delay, so the socket got enough time to connect
|
||||||
},
|
setTimeout(() => {
|
||||||
pluginConfig.fileReadDelay || 0
|
fileChangeHandler(false, pluginConfig.scriptOutputFileSubscription);
|
||||||
);
|
}, 500);
|
||||||
}
|
|
||||||
}
|
// send file contents to plugin for processing
|
||||||
}
|
function fileChangeHandler(eventType, filename) {
|
||||||
|
if (filename != null) {
|
||||||
|
setTimeout(
|
||||||
|
() => {
|
||||||
|
// get array of lines in file
|
||||||
|
let stuff = fs.readFileSync(instancedirectory + "/script-output/" + filename, "utf8").split("\n");
|
||||||
|
|
||||||
|
// if you found anything, reset the file
|
||||||
|
if (stuff[0]) {
|
||||||
|
fs.writeFileSync(instancedirectory + "/script-output/" + filename, "");
|
||||||
|
}
|
||||||
|
for (let o = 0; o < stuff.length; o++) {
|
||||||
|
if (stuff[o] && !stuff[o].includes('\u0000\u0000')) {
|
||||||
|
try {
|
||||||
|
plugins[i].scriptOutput(stuff[o]);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pluginConfig.fileReadDelay || 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
console.log(`Clusterio | Loaded plugin ${pluginsToLoad[i].name} in ${Date.now() - pluginLoadStarted}ms`);
|
console.log(`Clusterio | Loaded plugin ${pluginsToLoad[i].name} in ${Date.now() - pluginLoadStarted}ms`);
|
||||||
} else {
|
} else {
|
||||||
// this plugin doesn't have a client portion. Maybe it runs on the master only?
|
// this plugin doesn't have a client portion. Maybe it runs on the master only?
|
||||||
@@ -670,7 +695,8 @@ async function instanceManagement(instanceconfig) {
|
|||||||
publicIP: config.publicIP, // IP of the server should be global for all instances, so we pull that straight from the config
|
publicIP: config.publicIP, // IP of the server should be global for all instances, so we pull that straight from the config
|
||||||
mods:modHashes,
|
mods:modHashes,
|
||||||
instanceName: instance,
|
instanceName: instance,
|
||||||
}
|
info: instanceconfig.info,
|
||||||
|
};
|
||||||
if(playerCount){
|
if(playerCount){
|
||||||
payload.playerCount = playerCount.replace(/(\r\n\t|\n|\r\t)/gm, "");
|
payload.playerCount = playerCount.replace(/(\r\n\t|\n|\r\t)/gm, "");
|
||||||
} else {
|
} else {
|
||||||
@@ -717,7 +743,7 @@ async function instanceManagement(instanceconfig) {
|
|||||||
let payload = {
|
let payload = {
|
||||||
modName: modHashes[i].modName,
|
modName: modHashes[i].modName,
|
||||||
hash: modHashes[i].hash,
|
hash: modHashes[i].hash,
|
||||||
}
|
};
|
||||||
needle.post(config.masterIP + ":" + config.masterPort + '/api/checkMod', payload, needleOptionsWithTokenAuthHeader, function (err, response, body) {
|
needle.post(config.masterIP + ":" + config.masterPort + '/api/checkMod', payload, needleOptionsWithTokenAuthHeader, function (err, response, body) {
|
||||||
if(err) console.error("Unable to contact master server /api/checkMod! Please check your config.json.");
|
if(err) console.error("Unable to contact master server /api/checkMod! Please check your config.json.");
|
||||||
if(response && body && body == "found") {
|
if(response && body && body == "found") {
|
||||||
@@ -756,42 +782,22 @@ function hashMods(instanceName, callback) {
|
|||||||
if(!callback) {
|
if(!callback) {
|
||||||
throw new Error("ERROR in function hashMods NO CALLBACK");
|
throw new Error("ERROR in function hashMods NO CALLBACK");
|
||||||
}
|
}
|
||||||
function callback2(hash, modName){
|
|
||||||
hashedMods[hashedMods.length] = {
|
let modsDir = config.instanceDirectory+"/"+instanceName+"/mods/";
|
||||||
modName: modName,
|
function hashMod(name) {
|
||||||
hash: hash,
|
if (path.extname(name) != ".zip") {
|
||||||
}
|
// Can't hash unzipped mods, return null that's filtered out later
|
||||||
// check if this callback has ran once for each mod
|
return null;
|
||||||
if(hashedMods.length == instanceMods.length) {
|
} else {
|
||||||
callback(hashedMods);
|
return hashFile(modsDir+name).then(hash => (
|
||||||
}
|
{modName: name, hash: hash}
|
||||||
//console.log(modname);
|
));
|
||||||
}
|
|
||||||
let hashedMods = [];
|
|
||||||
/*let mods = fs.readdirSync("./sharedMods/")*/
|
|
||||||
let instanceMods = fs.readdirSync(config.instanceDirectory+"/"+instanceName+"/mods/");
|
|
||||||
if(instanceMods.length == 0){
|
|
||||||
callback({}); // there are no mods installed, return an empty object
|
|
||||||
}
|
|
||||||
for(let o=0;o<instanceMods.length;o++) {
|
|
||||||
if(path.extname(instanceMods[o]) != ".zip") {
|
|
||||||
instanceMods.splice(instanceMods.indexOf(instanceMods[o]), 1); // remove element from array, we can't hash unzipped mods
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for(let i=0; i<instanceMods.length; i++){
|
|
||||||
let path = config.instanceDirectory+"/"+instanceName+"/mods/"+instanceMods[i];
|
let promises = fs.readdirSync(modsDir).map(hashMod);
|
||||||
let name = instanceMods[i];
|
Promise.all(promises).then(hashes => {
|
||||||
let options = {
|
// Remove null entries from hashMod
|
||||||
files:path,
|
callback(hashes.filter(entry => entry !== null));
|
||||||
}
|
});
|
||||||
// options {files:[array of paths]}
|
|
||||||
hashFiles(options, function(error, hash) {
|
|
||||||
// hash will be a string if no error occurred
|
|
||||||
if(!error){
|
|
||||||
callback2(hash, name);
|
|
||||||
} else {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
+3
-1
@@ -14,7 +14,7 @@
|
|||||||
"masterPort": 8080,
|
"masterPort": 8080,
|
||||||
|
|
||||||
"__commentsslPort": "HTTPS port for the master to listen to. Set to 0 or an empty string to not listen for HTTPS.",
|
"__commentsslPort": "HTTPS port for the master to listen to. Set to 0 or an empty string to not listen for HTTPS.",
|
||||||
"sslPort": 443,
|
"sslPort": 0,
|
||||||
"sslCert": "database/certificates/cert.crt",
|
"sslCert": "database/certificates/cert.crt",
|
||||||
"sslPrivKey": "database/certificates/cert.key",
|
"sslPrivKey": "database/certificates/cert.key",
|
||||||
|
|
||||||
@@ -47,7 +47,9 @@
|
|||||||
"entriesPerSecond": 0.01666666667
|
"entriesPerSecond": 0.01666666667
|
||||||
},
|
},
|
||||||
"logItemTransfers": false,
|
"logItemTransfers": false,
|
||||||
|
"__comment6.5": "When the master is undersupplied, we have 3 different modes to 'fairly' distribute items. First to ask gets all, dole division using weird formula or experimental neural net tries to make stuff as fair as possible. Feel free to play around.",
|
||||||
"disableFairItemDistribution": false,
|
"disableFairItemDistribution": false,
|
||||||
|
"useNeuralNetDoleDivider": true,
|
||||||
"uploadModsToMaster": true,
|
"uploadModsToMaster": true,
|
||||||
"msBetweenCommands": 10,
|
"msBetweenCommands": 10,
|
||||||
"allowRemoteCommandExecution": true,
|
"allowRemoteCommandExecution": true,
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
toc:
|
|
||||||
- name: Master Server API
|
|
||||||
- clusterioMaster
|
|
||||||
- name: Type declarations
|
|
||||||
- itemStack
|
|
||||||
- name: Internal modules
|
|
||||||
description: |
|
|
||||||
Here are some helper functions for navigation.
|
|
||||||
- modManager
|
|
||||||
- getFactorioLocale
|
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const NodeInstance_1 = require("./NodeInstance");
|
||||||
|
const Point_1 = require("./Point");
|
||||||
|
const Grid_1 = require("./Grid");
|
||||||
|
/**
|
||||||
|
* Manages all nodes within the cluster
|
||||||
|
*/
|
||||||
|
class ClusterManager {
|
||||||
|
constructor() {
|
||||||
|
this.Grid = new Grid_1.Grid();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Generates a x*y grid of 1x1 nodes
|
||||||
|
* @param width How wide to make the node grid
|
||||||
|
* @param height How high to make the node grid
|
||||||
|
* @param nodeNamePrefix The prefix used for the initial node names. The name will be in the format <prefix>_x_y.
|
||||||
|
*/
|
||||||
|
GenerateBasicGridOfNodes(width, height, nodeNamePrefix) {
|
||||||
|
for (let x = 0; x < width; x++) {
|
||||||
|
for (let y = 0; y < height; y++) {
|
||||||
|
let newNode = new NodeInstance_1.NodeInstance(nodeNamePrefix + "-" + (x + 1) + "-" + (y + 1), new Point_1.Point(x, y), 1, 1);
|
||||||
|
this.Grid.AddNodeToGrid(newNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Assigns all nodes that currently do not have a server assigned to the given server
|
||||||
|
* @param server Server to assign all unassigned nodes to
|
||||||
|
*/
|
||||||
|
AssignAllUnassignedNodesToServer(server) {
|
||||||
|
this.CheckServerArray(server);
|
||||||
|
this.Grid.GetNodes()
|
||||||
|
.filter(node => { node.Server === null; })
|
||||||
|
.forEach(node => {
|
||||||
|
server.AddNodeInstance(node);
|
||||||
|
node.Server = server;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Assigns a node to a server
|
||||||
|
* @param x x co-ordinate of the node
|
||||||
|
* @param y y co-ordinate of the node
|
||||||
|
* @param serverName server name
|
||||||
|
*/
|
||||||
|
AssignNodeToServer(x, y, server) {
|
||||||
|
this._nodeArray[x][y].Server = server;
|
||||||
|
}
|
||||||
|
CheckServerArray(server) {
|
||||||
|
let s = this._servers[server.Name];
|
||||||
|
if (s == null) {
|
||||||
|
this._servers[server.Name] = server;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.ClusterManager = ClusterManager;
|
||||||
|
//# sourceMappingURL=ClusterManager.js.map
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const child_process = require("child_process");
|
||||||
|
const needle = require("needle");
|
||||||
|
const Helpers_1 = require("./Helpers");
|
||||||
|
const fs = require("fs");
|
||||||
|
const util = require("util");
|
||||||
|
/**
|
||||||
|
* Contains all interactions between the cluster model, the
|
||||||
|
* Clusterio master.js/client.js infrastructure and the operating system.
|
||||||
|
*/
|
||||||
|
class ClusterioMasterProxy {
|
||||||
|
constructor(masterIP, masterPort, masterAuthToken, serviceHostType) {
|
||||||
|
this._masterIP = `${masterIP}:${masterPort}`;
|
||||||
|
this._needleOptionsWithTokenAuthHeader = {
|
||||||
|
headers: {
|
||||||
|
'x-access-token': masterAuthToken,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
async RunRconCommand(instanceId, command, logDescription) {
|
||||||
|
console.log("Running command: " + logDescription);
|
||||||
|
let request = { instanceID: instanceId, command };
|
||||||
|
let response = await needle("post", this._masterIP + "/api/runCommand", request, this._needleOptionsWithTokenAuthHeader);
|
||||||
|
}
|
||||||
|
async GetSlaves() {
|
||||||
|
let response = await needle("get", this._masterIP + "/api/slaves", this._needleOptionsWithTokenAuthHeader);
|
||||||
|
let body = response.body;
|
||||||
|
let slaves = new Array();
|
||||||
|
Object.entries(body).forEach((keyValuePair, index, array) => {
|
||||||
|
let slave = keyValuePair[1];
|
||||||
|
slave.id = keyValuePair[0];
|
||||||
|
slaves.push(slave);
|
||||||
|
});
|
||||||
|
return slaves;
|
||||||
|
}
|
||||||
|
async CreateNodeInstancesOnLocalServer(nodes) {
|
||||||
|
await Helpers_1.asyncForEach(nodes, async (node) => {
|
||||||
|
await this.CreateNodeInstanceOnLocalServer(node);
|
||||||
|
});
|
||||||
|
await Helpers_1.asyncForEach(nodes, async (node) => {
|
||||||
|
await this.CreateTeleportZonesForANodeInstance(node);
|
||||||
|
});
|
||||||
|
await this.sleep(1000);
|
||||||
|
await Helpers_1.asyncForEach(nodes, async (node) => {
|
||||||
|
await this.CreateTeleportRestrictionsForANodeInstance(node);
|
||||||
|
await this.sleep(100);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async CreateNodeInstanceOnLocalServer(node) {
|
||||||
|
console.log(`Creating instance ${node.Name}...`);
|
||||||
|
await this.CreateNodeInstanceOnLocalServerUsingPm2(node);
|
||||||
|
console.log("Waiting for node to connect to cluster master...");
|
||||||
|
while (true) {
|
||||||
|
await this.sleep(1000);
|
||||||
|
//TODO: Use /api/getSlaveMeta/
|
||||||
|
let slaves = await this.GetSlaves();
|
||||||
|
let slave = slaves.find((slave, index, array) => slave.instanceName === node.Name);
|
||||||
|
if (slave != null) {
|
||||||
|
node.ClusterioWorldId = slave.id;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async CreateNodeInstanceOnLocalServerUsingPm2(node) {
|
||||||
|
let proc = child_process.spawn("pm2", ['start', '--name', node.Name, 'client.js', '--', 'start', node.Name]);
|
||||||
|
proc.stdout.on('data', (data) => { console.log(`stdout: ${data}`); });
|
||||||
|
proc.stderr.on('data', data => { console.log(`stderr: ${data}`); });
|
||||||
|
proc.on('error', error => { console.log(`error: ${error}`); });
|
||||||
|
}
|
||||||
|
async CreateNodeInstanceOnLocalServerUsingScreen(node) {
|
||||||
|
// first call of client.js start for a new node creates map from HotPatch scenario then exits.
|
||||||
|
let proc = child_process.spawn("screen", ['-dmS', node.Name, '-L', '-Logfile', node.Name + '.log', 'node', 'client.js', 'start', node.Name]);
|
||||||
|
proc.stdout.on('data', (data) => { console.log(`stdout: ${data}`); });
|
||||||
|
proc.stderr.on('data', data => { console.log(`stderr: ${data}`); });
|
||||||
|
proc.on('error', error => { console.log(`error: ${error}`); });
|
||||||
|
console.log("Waiting for creation of new map...");
|
||||||
|
// create awaiter for fs.readFile
|
||||||
|
const readFile = util.promisify(fs.readFile);
|
||||||
|
while (true) {
|
||||||
|
await this.sleep(1000);
|
||||||
|
let fileData = await readFile(node.Name + '.log');
|
||||||
|
if (fileData.includes('Instance created')) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// start node
|
||||||
|
proc = child_process.spawn("screen", ['-dmS', node.Name, '-L', '-Logfile', node.Name + '.log', 'node', 'client.js', 'start', node.Name]);
|
||||||
|
proc.stdout.on('data', data => { console.log(`stdout: ${data}`); });
|
||||||
|
proc.stderr.on('data', data => { console.log(`stderr: ${data}`); });
|
||||||
|
proc.on('error', error => { console.log(`error: ${error}`); });
|
||||||
|
}
|
||||||
|
async CreateTeleportZonesForANodeInstance(nodeInstance) {
|
||||||
|
await Helpers_1.asyncForEach(nodeInstance.TeleportZones.values(), async (zone) => {
|
||||||
|
let o = zone.GenerateTeleportZoneInFactorioCoordinates();
|
||||||
|
let cmd = `CreateZone('${o[0]}',${o[1]},${o[2]},${o[3]},${o[4]},${o[5]},true)`;
|
||||||
|
await this.RunRconCommand(nodeInstance.ClusterioWorldId, `/c remote.call('trainTeleports','runCode',\"${cmd}\")`, cmd);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async CreateTeleportRestrictionsForANodeInstance(nodeInstance) {
|
||||||
|
await Helpers_1.asyncForEach(nodeInstance.TeleportZones.values(), async (zone) => {
|
||||||
|
let o = zone.GenerateZoneRestriction();
|
||||||
|
let cmd = `CreateZoneRestriction(${o[0]},'${o[1]}','${o[2]}')`;
|
||||||
|
await this.RunRconCommand(nodeInstance.ClusterioWorldId, `/c remote.call('trainTeleports','runCode',\"${cmd}\")`, cmd);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
sleep(ms) {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.ClusterioMasterProxy = ClusterioMasterProxy;
|
||||||
|
//# sourceMappingURL=ClusterioMasterProxy.js.map
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
/** The multiplier used to convert from the grid's coordinate system to factorio's.
|
||||||
|
* 1000 means a 1x1 grid node will have a factorio map size of 1000x1000. */
|
||||||
|
exports.GridToFactorioCoordinateSystemMultiplier = 1000;
|
||||||
|
/** The width of teleport zones when they get created. */
|
||||||
|
exports.TeleportZoneWidth = 50;
|
||||||
|
//# sourceMappingURL=Config.js.map
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const typescript_collections_1 = require("typescript-collections");
|
||||||
|
const Point_1 = require("./Point");
|
||||||
|
const TeleportZone_1 = require("./TeleportZone");
|
||||||
|
const TeleportZoneDirection_1 = require("./TeleportZoneDirection");
|
||||||
|
class Grid {
|
||||||
|
constructor() {
|
||||||
|
this._nodes = new typescript_collections_1.Dictionary();
|
||||||
|
this._minX = 0;
|
||||||
|
this._minY = 0;
|
||||||
|
this._maxX = 0;
|
||||||
|
this._maxY = 0;
|
||||||
|
}
|
||||||
|
GetNodes() {
|
||||||
|
return this._nodes.values();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Adds a node to the grid
|
||||||
|
* @param topLeftCoordinate The top left x,y coordinate of the node
|
||||||
|
* @param nodeInstance
|
||||||
|
*/
|
||||||
|
AddNodeToGrid(nodeInstance) {
|
||||||
|
let topLeftCoordinate = nodeInstance.TopLeftCoordinate;
|
||||||
|
let pointsToSave = new Array();
|
||||||
|
// check all points that make up nodeInstance for existing nodes
|
||||||
|
for (let x = topLeftCoordinate.X; x < topLeftCoordinate.X + nodeInstance.Width; x++) {
|
||||||
|
for (let y = topLeftCoordinate.Y; y < topLeftCoordinate.Y + nodeInstance.Height; y++) {
|
||||||
|
let currentPoint = new Point_1.Point(x, y);
|
||||||
|
let existingNode = this._nodes.getValue(currentPoint);
|
||||||
|
if (existingNode != null) {
|
||||||
|
throw new Error("Cannot add node to grid - collision detected with node '" + existingNode.Name + "'");
|
||||||
|
}
|
||||||
|
pointsToSave.push(currentPoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// no existing nodes found to assign node to these points
|
||||||
|
for (let i = 0; i < pointsToSave.length; i++) {
|
||||||
|
this._nodes.setValue(pointsToSave[i], nodeInstance);
|
||||||
|
}
|
||||||
|
//TODO: Refactor
|
||||||
|
//#region Set up teleport zones
|
||||||
|
// check surrounding points for nodes
|
||||||
|
// check north points
|
||||||
|
let y = topLeftCoordinate.Y - 1;
|
||||||
|
for (let x = topLeftCoordinate.X; x < topLeftCoordinate.X + nodeInstance.Width; x++) {
|
||||||
|
let currentPoint = new Point_1.Point(x, y);
|
||||||
|
let existingNode = this._nodes.getValue(currentPoint);
|
||||||
|
// found a node - set up teleport zone
|
||||||
|
if (existingNode != null) {
|
||||||
|
// Create/Update teleport node in existing node to new node
|
||||||
|
let teleportZoneFromExistingToNew = existingNode.TeleportZones.getValue(nodeInstance.Key);
|
||||||
|
if (teleportZoneFromExistingToNew == null) {
|
||||||
|
teleportZoneFromExistingToNew = new TeleportZone_1.TeleportZone(existingNode, TeleportZoneDirection_1.TeleportZoneDirection.South, currentPoint, currentPoint, existingNode.TeleportZones.size(), nodeInstance);
|
||||||
|
existingNode.TeleportZones.setValue(nodeInstance.Key, teleportZoneFromExistingToNew);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
teleportZoneFromExistingToNew.BottomRightCoordinate = currentPoint;
|
||||||
|
}
|
||||||
|
// Create/Update teleport node in new node to existing node
|
||||||
|
let teleportZoneFromNewToExisting = nodeInstance.TeleportZones.getValue(existingNode.Key);
|
||||||
|
let pointInNewNode = new Point_1.Point(currentPoint.X, currentPoint.Y + 1);
|
||||||
|
if (teleportZoneFromNewToExisting == null) {
|
||||||
|
teleportZoneFromNewToExisting = new TeleportZone_1.TeleportZone(nodeInstance, TeleportZoneDirection_1.TeleportZoneDirection.North, pointInNewNode, pointInNewNode, nodeInstance.TeleportZones.size(), existingNode);
|
||||||
|
nodeInstance.TeleportZones.setValue(existingNode.Key, teleportZoneFromNewToExisting);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
teleportZoneFromNewToExisting.BottomRightCoordinate = currentPoint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// check south points
|
||||||
|
y = topLeftCoordinate.Y + nodeInstance.Height + 1;
|
||||||
|
for (let x = topLeftCoordinate.X; x < topLeftCoordinate.X + nodeInstance.Width; x++) {
|
||||||
|
let currentPoint = new Point_1.Point(x, y);
|
||||||
|
let existingNode = this._nodes.getValue(currentPoint);
|
||||||
|
// found a node - set up teleport zone
|
||||||
|
if (existingNode != null) {
|
||||||
|
// Create/Update teleport node in existing node to new node
|
||||||
|
let teleportZoneFromExistingToNew = existingNode.TeleportZones.getValue(nodeInstance.Key);
|
||||||
|
if (teleportZoneFromExistingToNew == null) {
|
||||||
|
teleportZoneFromExistingToNew = new TeleportZone_1.TeleportZone(existingNode, TeleportZoneDirection_1.TeleportZoneDirection.North, currentPoint, currentPoint, existingNode.TeleportZones.size(), nodeInstance);
|
||||||
|
existingNode.TeleportZones.setValue(nodeInstance.Key, teleportZoneFromExistingToNew);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
teleportZoneFromExistingToNew.BottomRightCoordinate = currentPoint;
|
||||||
|
}
|
||||||
|
// Create/Update teleport node in new node to existing node
|
||||||
|
let teleportZoneFromNewToExisting = nodeInstance.TeleportZones.getValue(existingNode.Key);
|
||||||
|
let pointInNewNode = new Point_1.Point(currentPoint.X, currentPoint.Y - 1);
|
||||||
|
if (teleportZoneFromNewToExisting == null) {
|
||||||
|
teleportZoneFromNewToExisting = new TeleportZone_1.TeleportZone(nodeInstance, TeleportZoneDirection_1.TeleportZoneDirection.South, pointInNewNode, pointInNewNode, nodeInstance.TeleportZones.size(), existingNode);
|
||||||
|
nodeInstance.TeleportZones.setValue(existingNode.Key, teleportZoneFromNewToExisting);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
teleportZoneFromNewToExisting.BottomRightCoordinate = currentPoint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// check west points
|
||||||
|
let x = topLeftCoordinate.X - 1;
|
||||||
|
for (let y = topLeftCoordinate.Y; y < topLeftCoordinate.Y + nodeInstance.Height; y++) {
|
||||||
|
let currentPoint = new Point_1.Point(x, y);
|
||||||
|
let existingNode = this._nodes.getValue(currentPoint);
|
||||||
|
// found a node - set up teleport zone
|
||||||
|
if (existingNode != null) {
|
||||||
|
// Create/Update teleport node in existing node to new node
|
||||||
|
let teleportZoneFromExistingToNew = existingNode.TeleportZones.getValue(nodeInstance.Key);
|
||||||
|
if (teleportZoneFromExistingToNew == null) {
|
||||||
|
teleportZoneFromExistingToNew = new TeleportZone_1.TeleportZone(existingNode, TeleportZoneDirection_1.TeleportZoneDirection.East, currentPoint, currentPoint, existingNode.TeleportZones.size(), nodeInstance);
|
||||||
|
existingNode.TeleportZones.setValue(nodeInstance.Key, teleportZoneFromExistingToNew);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
teleportZoneFromExistingToNew.BottomRightCoordinate = currentPoint;
|
||||||
|
}
|
||||||
|
// Create/Update teleport node in new node to existing node
|
||||||
|
let teleportZoneFromNewToExisting = nodeInstance.TeleportZones.getValue(existingNode.Key);
|
||||||
|
let pointInNewNode = new Point_1.Point(currentPoint.X + 1, currentPoint.Y);
|
||||||
|
if (teleportZoneFromNewToExisting == null) {
|
||||||
|
teleportZoneFromNewToExisting = new TeleportZone_1.TeleportZone(nodeInstance, TeleportZoneDirection_1.TeleportZoneDirection.West, pointInNewNode, pointInNewNode, nodeInstance.TeleportZones.size(), existingNode);
|
||||||
|
nodeInstance.TeleportZones.setValue(existingNode.Key, teleportZoneFromNewToExisting);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
teleportZoneFromNewToExisting.BottomRightCoordinate = currentPoint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// check east points
|
||||||
|
x = topLeftCoordinate.X + nodeInstance.Width + 1;
|
||||||
|
for (let y = topLeftCoordinate.Y; y < topLeftCoordinate.Y + nodeInstance.Height; y++) {
|
||||||
|
let currentPoint = new Point_1.Point(x, y);
|
||||||
|
let existingNode = this._nodes.getValue(currentPoint);
|
||||||
|
// found a node - set up teleport zone
|
||||||
|
if (existingNode != null) {
|
||||||
|
// Create/Update teleport node in existing node to new node
|
||||||
|
let teleportZoneFromExistingToNew = existingNode.TeleportZones.getValue(nodeInstance.Key);
|
||||||
|
if (teleportZoneFromExistingToNew == null) {
|
||||||
|
teleportZoneFromExistingToNew = new TeleportZone_1.TeleportZone(existingNode, TeleportZoneDirection_1.TeleportZoneDirection.West, currentPoint, currentPoint, existingNode.TeleportZones.size(), nodeInstance);
|
||||||
|
existingNode.TeleportZones.setValue(nodeInstance.Key, teleportZoneFromExistingToNew);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
teleportZoneFromExistingToNew.BottomRightCoordinate = currentPoint;
|
||||||
|
}
|
||||||
|
// Create/Update teleport node in new node to existing node
|
||||||
|
let teleportZoneFromNewToExisting = nodeInstance.TeleportZones.getValue(existingNode.Key);
|
||||||
|
let pointInNewNode = new Point_1.Point(currentPoint.X - 1, currentPoint.Y);
|
||||||
|
if (teleportZoneFromNewToExisting == null) {
|
||||||
|
teleportZoneFromNewToExisting = new TeleportZone_1.TeleportZone(nodeInstance, TeleportZoneDirection_1.TeleportZoneDirection.East, pointInNewNode, pointInNewNode, nodeInstance.TeleportZones.size(), existingNode);
|
||||||
|
nodeInstance.TeleportZones.setValue(existingNode.Key, teleportZoneFromNewToExisting);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
teleportZoneFromNewToExisting.BottomRightCoordinate = currentPoint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
this._nodes.setValue(topLeftCoordinate, nodeInstance);
|
||||||
|
this._minX = Math.min(this._minX, topLeftCoordinate.X);
|
||||||
|
this._minY = Math.min(this._minY, topLeftCoordinate.Y);
|
||||||
|
this._maxX = Math.max(this._maxX, topLeftCoordinate.X + nodeInstance.Width);
|
||||||
|
this._maxY = Math.max(this._maxY, topLeftCoordinate.Y + nodeInstance.Height);
|
||||||
|
}
|
||||||
|
get Width() {
|
||||||
|
return this._maxX - this._minX;
|
||||||
|
}
|
||||||
|
get Height() {
|
||||||
|
return this._maxY - this._minY;
|
||||||
|
}
|
||||||
|
get TopLeftCoordinate() {
|
||||||
|
return new Point_1.Point(this._minX, this._minY);
|
||||||
|
}
|
||||||
|
get BottomRightCoordinate() {
|
||||||
|
return new Point_1.Point(this._maxX, this._maxY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.Grid = Grid;
|
||||||
|
//# sourceMappingURL=Grid.js.map
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
//TODO: Make generic
|
||||||
|
async function asyncForEach(array, callback) {
|
||||||
|
for (let index = 0; index < array.length; index++) {
|
||||||
|
await callback(array[index], index, array);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.asyncForEach = asyncForEach;
|
||||||
|
//# sourceMappingURL=Helpers.js.map
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
//# sourceMappingURL=IApiSlaves.js.map
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
//# sourceMappingURL=IClusterioMasterProxy.js.map
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
console.log("started.");
|
||||||
|
const ClusterioMasterProxy_1 = require("./ClusterioMasterProxy");
|
||||||
|
const ClusterManager_1 = require("./ClusterManager");
|
||||||
|
const argv = require('minimist')(process.argv.slice(2));
|
||||||
|
let configPath = argv['configPath'];
|
||||||
|
if (configPath === undefined || configPath === null || configPath === "") {
|
||||||
|
throw new Error("configPath is null.");
|
||||||
|
}
|
||||||
|
;
|
||||||
|
let config = require(configPath);
|
||||||
|
let proxy = new ClusterioMasterProxy_1.ClusterioMasterProxy(config.masterIP, config.masterPort, config.masterAuthToken);
|
||||||
|
let manager = new ClusterManager_1.ClusterManager();
|
||||||
|
manager.GenerateBasicGridOfNodes(2, 2, 'GridLockTest_');
|
||||||
|
function sleep(ms) {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
// async function test() {
|
||||||
|
// await asyncForEach([1,2,3], async (v, i, a) => {
|
||||||
|
// console.log("test" + i);
|
||||||
|
// await sleep(5000);
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// test();
|
||||||
|
proxy.CreateNodeInstancesOnLocalServer(manager.Grid.GetNodes());
|
||||||
|
//# sourceMappingURL=ModelController.js.map
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
console.log("started.");
|
||||||
|
const ClusterioMasterProxy_1 = require("./ClusterioMasterProxy");
|
||||||
|
const ClusterManager_1 = require("./ClusterManager");
|
||||||
|
const argv = require('minimist')(process.argv.slice(2));
|
||||||
|
let configPath = argv['configPath'];
|
||||||
|
if (configPath === undefined || configPath === null || configPath === "") {
|
||||||
|
throw new Error("configPath is null.");
|
||||||
|
}
|
||||||
|
;
|
||||||
|
let config = require(configPath);
|
||||||
|
let proxy = new ClusterioMasterProxy_1.ClusterioMasterProxy(config.masterIP, config.masterPort, config.masterAuthToken);
|
||||||
|
function sleep(ms) {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
// main
|
||||||
|
(async () => {
|
||||||
|
let manager = new ClusterManager_1.ClusterManager();
|
||||||
|
manager.GenerateBasicGridOfNodes(2, 2, 'GridLockTest');
|
||||||
|
await proxy.CreateNodeInstancesOnLocalServer(manager.Grid.GetNodes());
|
||||||
|
return;
|
||||||
|
})();
|
||||||
|
//# sourceMappingURL=ModelDemo.js.map
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
console.log("Started.");
|
||||||
|
const ClusterioMasterProxy_1 = require("./ClusterioMasterProxy");
|
||||||
|
const ClusterManager_1 = require("./ClusterManager");
|
||||||
|
const argv = require('minimist')(process.argv.slice(2));
|
||||||
|
// arg: configPath
|
||||||
|
// description: should point to the clusterio config.json file.
|
||||||
|
let configPath = argv['configPath'];
|
||||||
|
if (configPath === undefined || configPath === null || configPath === "") {
|
||||||
|
configPath = "../../config.json";
|
||||||
|
}
|
||||||
|
;
|
||||||
|
let config = require(configPath);
|
||||||
|
// arg: mode
|
||||||
|
// description: should be GenerateBasicGridOfNodes for now. Can extend in future.
|
||||||
|
let mode = argv['mode'];
|
||||||
|
if (mode === undefined || mode === null || mode === "") {
|
||||||
|
throw new Error("mode is null. mode must be 'GenerateBasicGridOfNodes' (for now).");
|
||||||
|
}
|
||||||
|
;
|
||||||
|
// arg: serviceHostType
|
||||||
|
// description: what program to use to host the client.js instances.
|
||||||
|
let serviceHostType = argv['serviceHostType'];
|
||||||
|
if (serviceHostType === undefined || serviceHostType === null || serviceHostType === "") {
|
||||||
|
throw new Error("serviceHostType is null. serviceHostType must be either 'pm2' or 'screen'.");
|
||||||
|
}
|
||||||
|
;
|
||||||
|
if (serviceHostType != "pm2" && serviceHostType != "screen") {
|
||||||
|
throw new Error("serviceHostType must be either 'pm2' or 'screen'.");
|
||||||
|
}
|
||||||
|
// arg: width
|
||||||
|
// description: width of node grid
|
||||||
|
let width = argv['width'];
|
||||||
|
if (width === undefined || width === null || width === "") {
|
||||||
|
throw new Error("width is null. width must be an int > 0.");
|
||||||
|
}
|
||||||
|
;
|
||||||
|
// arg: height
|
||||||
|
// description: height of node grid
|
||||||
|
let height = argv['height'];
|
||||||
|
if (height === undefined || height === null || height === "") {
|
||||||
|
throw new Error("height is null. height must be an int > 0.");
|
||||||
|
}
|
||||||
|
;
|
||||||
|
// arg: namePrefix
|
||||||
|
// description: namePrefix of node grid
|
||||||
|
let namePrefix = argv['namePrefix'];
|
||||||
|
if (namePrefix === undefined || namePrefix === null || namePrefix === "") {
|
||||||
|
throw new Error("namePrefix is null.");
|
||||||
|
}
|
||||||
|
;
|
||||||
|
let proxy = new ClusterioMasterProxy_1.ClusterioMasterProxy(config.masterIP, config.masterPort, config.masterAuthToken, serviceHostType);
|
||||||
|
// main
|
||||||
|
(async () => {
|
||||||
|
let manager = new ClusterManager_1.ClusterManager();
|
||||||
|
manager.GenerateBasicGridOfNodes(width, height, namePrefix);
|
||||||
|
await proxy.CreateNodeInstancesOnLocalServer(manager.Grid.GetNodes());
|
||||||
|
return;
|
||||||
|
})();
|
||||||
|
//# sourceMappingURL=ModelRunner.js.map
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const Point_1 = require("./Point");
|
||||||
|
const typescript_collections_1 = require("typescript-collections");
|
||||||
|
/**
|
||||||
|
* An instance of a Factorio server. Multiple nodes can run on the same server.
|
||||||
|
*/
|
||||||
|
class NodeInstance {
|
||||||
|
constructor(name, topLeftCoordinate, width, height) {
|
||||||
|
this.TeleportZones = new typescript_collections_1.Dictionary();
|
||||||
|
this.Name = name;
|
||||||
|
this.Width = width;
|
||||||
|
this.Height = height;
|
||||||
|
this.TopLeftCoordinate = topLeftCoordinate;
|
||||||
|
}
|
||||||
|
get BottomRightCoordinate() {
|
||||||
|
return new Point_1.Point(this.TopLeftCoordinate.X + this.Width, this.TopLeftCoordinate.Y + this.Height);
|
||||||
|
}
|
||||||
|
// public AddTeleportZone(name: string, topLeftCoordinate: Point, bottomRightCoordinate: Point) {
|
||||||
|
// this.TeleportZones.push(new TeleportZone(this, topLeftCoordinate, bottomRightCoordinate, name));
|
||||||
|
// }
|
||||||
|
AddTeleportZonePoint() {
|
||||||
|
}
|
||||||
|
get Key() {
|
||||||
|
return this.Name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.NodeInstance = NodeInstance;
|
||||||
|
//# sourceMappingURL=NodeInstance.js.map
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
class Point {
|
||||||
|
/**
|
||||||
|
* Constructs a Point. Will not accept NaNs.
|
||||||
|
* @param x A number. Cannot be NaN.
|
||||||
|
* @param y A number. Cannot be NaN.
|
||||||
|
*/
|
||||||
|
constructor(x, y) {
|
||||||
|
this.toString = () => {
|
||||||
|
return "(" + this.X + "," + this.Y + ")";
|
||||||
|
};
|
||||||
|
if (Number.isNaN(x)) {
|
||||||
|
throw new Error("x is NaN. Will not construct Point.");
|
||||||
|
}
|
||||||
|
if (Number.isNaN(y)) {
|
||||||
|
throw new Error("y is NaN. Will not construct Point.");
|
||||||
|
}
|
||||||
|
this.X = x;
|
||||||
|
this.Y = y;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Constructs a Point from a string representation in the format (x,y). Will not accept NaNs.
|
||||||
|
* @param vectorAsString A string in the format (x,y). Will not accept NaNs.
|
||||||
|
*/
|
||||||
|
static FromString(vectorAsString) {
|
||||||
|
this.CheckChar('(', vectorAsString[0], vectorAsString);
|
||||||
|
this.CheckChar(')', vectorAsString[vectorAsString.length - 1], vectorAsString);
|
||||||
|
if (vectorAsString.indexOf(',') === -1) {
|
||||||
|
throw new Error("vectorAsString is an invalid format. Cannot find ','. fullString: " + vectorAsString);
|
||||||
|
}
|
||||||
|
let splitStr = vectorAsString.substr(1, vectorAsString.length - 2).split(',');
|
||||||
|
if (splitStr.length != 2) {
|
||||||
|
throw new Error("vectorAsString is an invalid format. Expecting string in format (x,y). fullString: " + vectorAsString);
|
||||||
|
}
|
||||||
|
let x = parseInt(splitStr[0]);
|
||||||
|
let y = parseInt(splitStr[1]);
|
||||||
|
let v = new Point(x, y);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
static CheckChar(expectedChar, actualChar, fullString) {
|
||||||
|
if (actualChar != expectedChar) {
|
||||||
|
throw new Error("vectorAsString is an invalid format. Expecting: '" + expectedChar + "'. Actual: '" + actualChar + "'. fullString: " + fullString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
equals(obj) {
|
||||||
|
return this.X === obj.X && this.Y === obj.Y;
|
||||||
|
}
|
||||||
|
Add(p) {
|
||||||
|
return new Point(this.X + p.X, this.Y + p.Y);
|
||||||
|
}
|
||||||
|
Subtract(p) {
|
||||||
|
return new Point(this.X - p.X, this.Y - p.Y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.Point = Point;
|
||||||
|
//# sourceMappingURL=Point.js.map
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
/**
|
||||||
|
* A server. Can contain multiple nodes (Factorio server instances).
|
||||||
|
*/
|
||||||
|
class Server {
|
||||||
|
constructor(name) {
|
||||||
|
this.Nodes = new Array();
|
||||||
|
this.Name = name;
|
||||||
|
}
|
||||||
|
AddNodeInstance(node) {
|
||||||
|
this.Nodes.push(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.Server = Server;
|
||||||
|
//# sourceMappingURL=Server.js.map
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
var ServiceHostType;
|
||||||
|
(function (ServiceHostType) {
|
||||||
|
ServiceHostType[ServiceHostType["pm2"] = 0] = "pm2";
|
||||||
|
ServiceHostType[ServiceHostType["screen"] = 1] = "screen";
|
||||||
|
})(ServiceHostType = exports.ServiceHostType || (exports.ServiceHostType = {}));
|
||||||
|
//# sourceMappingURL=ServiceHostType.js.map
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const Point_1 = require("./Point");
|
||||||
|
const TeleportZoneDirection_1 = require("./TeleportZoneDirection");
|
||||||
|
const Config = require("./Config");
|
||||||
|
class TeleportZone {
|
||||||
|
constructor(parentNode, direction, topLeftCoordinate, bottomRightCoordinate, index, targetNode) {
|
||||||
|
this.TopLeftCoordinate = topLeftCoordinate;
|
||||||
|
this.BottomRightCoordinate = bottomRightCoordinate;
|
||||||
|
this.ParentNode = parentNode;
|
||||||
|
this.Direction = direction;
|
||||||
|
this.Index = index;
|
||||||
|
this.TargetNode = targetNode;
|
||||||
|
}
|
||||||
|
get Name() {
|
||||||
|
return TeleportZoneDirection_1.TeleportZoneDirection[this.Direction] + " to " + this.TargetNode.Name;
|
||||||
|
}
|
||||||
|
// public AddRestriction(restrictingTeleportZone: TeleportZone) {
|
||||||
|
// this.Restrictions.push(new TeleportZoneRestriction(this, restrictingTeleportZone));
|
||||||
|
// }
|
||||||
|
/**
|
||||||
|
* Converts this teleport zone into Factorio map co-ordinates
|
||||||
|
* @returns [name, topleft.x, topleft.y, width, height, zoneIndex]
|
||||||
|
*/
|
||||||
|
GenerateTeleportZoneInFactorioCoordinates() {
|
||||||
|
// get where this zone is relative to the node itself
|
||||||
|
let topLeftRelativeCoordinate = this.TopLeftCoordinate.Subtract(this.ParentNode.TopLeftCoordinate);
|
||||||
|
// a grid coordinate point is equivalent to a 1x1 node, so bottomright = topleft.
|
||||||
|
// add (1,1) to translate from area representation to coordinates representing actual points.
|
||||||
|
let bottomRightRelativeCoordinate = this.BottomRightCoordinate
|
||||||
|
.Subtract(this.ParentNode.TopLeftCoordinate)
|
||||||
|
.Add(new Point_1.Point(1, 1));
|
||||||
|
// Factorio map is e.g. (-500,-500) to (500, 500) for a 1000x1000 node.
|
||||||
|
// We therefore need to offset our co-ordinates so that (0,0) is the center and not (width/2, height/2)
|
||||||
|
let m = Config.GridToFactorioCoordinateSystemMultiplier;
|
||||||
|
let factorioTeleportZoneTopLeft = new Point_1.Point(topLeftRelativeCoordinate.X * m - m / 2, topLeftRelativeCoordinate.Y * m - m / 2);
|
||||||
|
let factorioTeleportZoneBottomRight = new Point_1.Point(bottomRightRelativeCoordinate.X * m - m / 2, bottomRightRelativeCoordinate.Y * m - m / 2);
|
||||||
|
let zoneWidth = factorioTeleportZoneBottomRight.X - factorioTeleportZoneTopLeft.X;
|
||||||
|
let zoneHeight = factorioTeleportZoneBottomRight.Y - factorioTeleportZoneTopLeft.Y;
|
||||||
|
let tl = factorioTeleportZoneTopLeft;
|
||||||
|
let br = factorioTeleportZoneBottomRight;
|
||||||
|
let zw = Config.TeleportZoneWidth;
|
||||||
|
switch (this.Direction) {
|
||||||
|
case TeleportZoneDirection_1.TeleportZoneDirection.North:
|
||||||
|
return [this.Name, tl.X, tl.Y, zoneWidth, zw, this.Index + 1];
|
||||||
|
case TeleportZoneDirection_1.TeleportZoneDirection.West:
|
||||||
|
return [this.Name, tl.X, tl.Y, zw, zoneHeight, this.Index + 1];
|
||||||
|
case TeleportZoneDirection_1.TeleportZoneDirection.South:
|
||||||
|
return [this.Name, tl.X, br.Y - zw, zoneWidth, zw, this.Index + 1];
|
||||||
|
case TeleportZoneDirection_1.TeleportZoneDirection.East:
|
||||||
|
return [this.Name, br.X - zw, tl.Y, zw, zoneHeight, this.Index + 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Generates a zone restriction from this teleport zone to target zone
|
||||||
|
* @returns [zoneIndex, targetNodeName, targetZoneName]
|
||||||
|
*/
|
||||||
|
GenerateZoneRestriction() {
|
||||||
|
let targetZone = this.TargetNode.TeleportZones.getValue(this.ParentNode.Key);
|
||||||
|
return [this.Index + 1, this.TargetNode.Name, targetZone.Name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.TeleportZone = TeleportZone;
|
||||||
|
//# sourceMappingURL=TeleportZone.js.map
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
var TeleportZoneDirection;
|
||||||
|
(function (TeleportZoneDirection) {
|
||||||
|
TeleportZoneDirection[TeleportZoneDirection["North"] = 0] = "North";
|
||||||
|
TeleportZoneDirection[TeleportZoneDirection["East"] = 1] = "East";
|
||||||
|
TeleportZoneDirection[TeleportZoneDirection["South"] = 2] = "South";
|
||||||
|
TeleportZoneDirection[TeleportZoneDirection["West"] = 3] = "West";
|
||||||
|
})(TeleportZoneDirection = exports.TeleportZoneDirection || (exports.TeleportZoneDirection = {}));
|
||||||
|
//# sourceMappingURL=TeleportZoneDirection.js.map
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
class TeleportZoneRestriction {
|
||||||
|
constructor(parentTeleportZone, restrictingTeleportZone) {
|
||||||
|
this.ParentTeleportZone = parentTeleportZone;
|
||||||
|
this.RestrictingTeleportZone = restrictingTeleportZone;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.TeleportZoneRestriction = TeleportZoneRestriction;
|
||||||
|
//# sourceMappingURL=TeleportZoneRestriction.js.map
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
"use strict";
|
||||||
|
function __export(m) {
|
||||||
|
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
|
||||||
|
}
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
__export(require("./ClusterManager"));
|
||||||
|
__export(require("./NodeInstance"));
|
||||||
|
__export(require("./Server"));
|
||||||
|
__export(require("./TeleportZone"));
|
||||||
|
__export(require("./TeleportZoneRestriction"));
|
||||||
|
__export(require("./Point"));
|
||||||
|
__export(require("./Grid"));
|
||||||
|
//# sourceMappingURL=index.js.map
|
||||||
File renamed without changes.
File renamed without changes.
+1
-1
@@ -32,7 +32,7 @@ module.exports = function(config){
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return async function thisAintGonnaWork(){
|
return async function thisAintGonnaWork(){
|
||||||
let err = new Error("getInstanceName from _app/clusterTools needs to be called with {config} to work");
|
let err = new Error("getInstanceName from lib/clusterTools needs to be called with {config} to work");
|
||||||
console.log(err);
|
console.log(err);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,119 @@
|
|||||||
|
//Sigmoids...
|
||||||
|
|
||||||
|
function sigmoid0(x)
|
||||||
|
{
|
||||||
|
return x/(1+Math.abs(x));
|
||||||
|
}
|
||||||
|
function sigmoid1(x)
|
||||||
|
{
|
||||||
|
if (x>=0.0) {
|
||||||
|
return 1.0
|
||||||
|
} else {
|
||||||
|
return 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function sigmoid2(x)
|
||||||
|
{
|
||||||
|
if (x>=0.0){
|
||||||
|
return x
|
||||||
|
} else {
|
||||||
|
return 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function sigmoid3(x)
|
||||||
|
{
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
//DOSE - Doses items for item X slave Y
|
||||||
|
//Numreq - number of items that are requested for item X for slave Y
|
||||||
|
//Instore - number of items for item X in master gauge (currently)
|
||||||
|
//store_last_tick - Number of items for item X in master gauge at time of last tick
|
||||||
|
//dole - Current dole for item X
|
||||||
|
//carry - carry for item X slave Y
|
||||||
|
//prev_req - Numreq for previous request for item X slave Y
|
||||||
|
//* doleinfo_last - Object with 2 values for last "tick period": numreq - total number or requests for item, numslave - number of slaves requesting that item
|
||||||
|
//debt - debt for item X slave Y
|
||||||
|
|
||||||
|
//Returns: Array of 3 elements
|
||||||
|
//0.Number of items to give in that dose
|
||||||
|
//1.New dole for item X
|
||||||
|
//2.New carry for item X slave Y
|
||||||
|
//3.New debt
|
||||||
|
function Dose(numreq,instore,store_last_tick,dole,carry,prev_req,numreq_total_adj,debt)
|
||||||
|
{
|
||||||
|
numreq=Number(numreq);
|
||||||
|
instore=Number(instore);
|
||||||
|
var instore_adj=instore;
|
||||||
|
if (instore_adj==0) instore_adj=0.1;
|
||||||
|
|
||||||
|
var b=Math.trunc(prev_req/instore_adj*numreq);
|
||||||
|
var inp1=[numreq,instore_adj,store_last_tick/numreq,dole,carry];//0..4
|
||||||
|
var inp2=[prev_req/numreq,numreq/numreq_total_adj,store_last_tick/numreq_total_adj,prev_req/instore_adj,b,numreq_total_adj,store_last_tick,debt];//5..12
|
||||||
|
var inputs=inp1.concat(inp2);
|
||||||
|
var internal=new Array(20).fill(0);
|
||||||
|
var outputs=[0,0,0,0];
|
||||||
|
//NN: Please don't touch :), code is generated
|
||||||
|
|
||||||
|
outputs[0]=inputs[7]*0.8058582544;
|
||||||
|
outputs[0]=sigmoid2(outputs[0]);
|
||||||
|
outputs[1]=sigmoid0(outputs[1]);
|
||||||
|
outputs[2]=sigmoid0(outputs[2]);
|
||||||
|
outputs[3]=inputs[6]*0.2294657379;
|
||||||
|
outputs[3]=sigmoid0(outputs[3]);
|
||||||
|
|
||||||
|
debt=debt || 0;
|
||||||
|
if (outputs[0]>0.99) outputs[0]=1.0;
|
||||||
|
|
||||||
|
var res1=Number(outputs[0])*numreq+outputs[3]+debt;
|
||||||
|
var res_capped=Math.round(res1);
|
||||||
|
|
||||||
|
if (res_capped>numreq) res_capped=numreq;
|
||||||
|
if (res_capped>instore) res_capped=instore;
|
||||||
|
if (res_capped<0) res_capped=0;
|
||||||
|
|
||||||
|
res1=res1 || 0;
|
||||||
|
res_capped=res_capped || 0;//Safeguard against wrong values
|
||||||
|
debt=res1-res_capped;
|
||||||
|
debt_cap=Math.round(numreq*1.5)+5;
|
||||||
|
if (debt>debt_cap) debt=debt_cap;
|
||||||
|
|
||||||
|
carry=sigmoid0(outputs[2]*carry+outputs[0]) || 0;
|
||||||
|
return [res_capped,dole+outputs[1],carry,debt];
|
||||||
|
}
|
||||||
|
|
||||||
|
//TICK - Updates values for item X, should be called once for each item for each round of all slaves requesting that item
|
||||||
|
//Instore - number of items for item X in master gauge (currently)
|
||||||
|
//dole - Current dole for item X
|
||||||
|
//store_last_tick - Number of items for item X in master gauge at time of last tick (not this one)
|
||||||
|
//* doleinfo_last - Object with 2 values for last "tick period": numreq - total number or requests for item, numslave - number of slaves requesting that item
|
||||||
|
//* doleinfo_new - Object with 2 values for new "tick period": numreq - total number or requests for item, numslave - number of slaves requesting that item
|
||||||
|
|
||||||
|
|
||||||
|
//Returns: Array of 2 elements
|
||||||
|
//0.New dole for item X
|
||||||
|
//1.Guessed percentage average of items supplied in last 10 ticks
|
||||||
|
function Tick(instore,dole,store_last_tick,numreq_total_adj)
|
||||||
|
{
|
||||||
|
var instore_adj=instore;
|
||||||
|
if (instore_adj==0) instore_adj=0.1;
|
||||||
|
var inputs=[instore,dole,store_last_tick/instore_adj,store_last_tick/numreq_total_adj,store_last_tick,numreq_total_adj];
|
||||||
|
var internal=new Array(10).fill(0);
|
||||||
|
var outputs=[0,0];
|
||||||
|
//NN: Please don't touch :), code is generated
|
||||||
|
|
||||||
|
outputs[0]=inputs[3]*0.9364774823;
|
||||||
|
outputs[0]=outputs[0]*1.0*0.4590721726;
|
||||||
|
outputs[0]=sigmoid2(outputs[0]);
|
||||||
|
outputs[1]=outputs[0]*1.828422785;
|
||||||
|
outputs[1]=sigmoid3(outputs[1]);
|
||||||
|
|
||||||
|
avg=outputs[1];
|
||||||
|
if (numreq_total_adj<0.11) avg=1;//When no item's are requested, result is 0.1 ; In that case there is no deficit, so return 1 = 100% demand fulfilled
|
||||||
|
if (avg>1) avg=1;
|
||||||
|
if (avg<0) avg=0;
|
||||||
|
|
||||||
|
return [sigmoid0(outputs[0]),avg];
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {Dose,Tick}
|
||||||
@@ -15,9 +15,8 @@ const child_process = require("child_process");
|
|||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
|
||||||
const mkdirp = require("mkdirp");
|
const mkdirp = require("mkdirp");
|
||||||
const appRootPath = require("app-root-path");
|
|
||||||
|
|
||||||
const fileOps = require("_app/fileOps");
|
const fileOps = require("lib/fileOps");
|
||||||
|
|
||||||
module.exports = class factorioServer {
|
module.exports = class factorioServer {
|
||||||
constructor({
|
constructor({
|
||||||
@@ -165,4 +164,4 @@ module.exports = class factorioServer {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,6 @@ const assert = require("assert");
|
|||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
|
||||||
const appRootPath = require("app-root-path");
|
|
||||||
const parallel = require("mocha.parallel");
|
const parallel = require("mocha.parallel");
|
||||||
const mkdirp = require("mkdirp");
|
const mkdirp = require("mkdirp");
|
||||||
|
|
||||||
@@ -75,7 +74,7 @@ describe("class factorioServer/index.js", ()=>{
|
|||||||
describe(".createMap(mapName.zip)", ()=>{
|
describe(".createMap(mapName.zip)", ()=>{
|
||||||
it("creates a map file as instanceDirectory/saves/map.zip", async function(){
|
it("creates a map file as instanceDirectory/saves/map.zip", async function(){
|
||||||
this.timeout(20000); // these test often takes a lot of time in the map creation step (4500 ms on i3 dev system)
|
this.timeout(20000); // these test often takes a lot of time in the map creation step (4500 ms on i3 dev system)
|
||||||
let relativePath = __dirname.replace(appRootPath, "").substr(1);
|
let relativePath = path.relative('.', __dirname);
|
||||||
let server = new factorioServer({
|
let server = new factorioServer({
|
||||||
instanceDirectory: path.join(relativePath, "_testData_createMap"),
|
instanceDirectory: path.join(relativePath, "_testData_createMap"),
|
||||||
instanceName: "Unit testing instance",
|
instanceName: "Unit testing instance",
|
||||||
@@ -93,7 +92,7 @@ describe("class factorioServer/index.js", ()=>{
|
|||||||
});
|
});
|
||||||
it("supports custom filenames", async function(){
|
it("supports custom filenames", async function(){
|
||||||
this.timeout(20000);
|
this.timeout(20000);
|
||||||
let relativePath = __dirname.replace(appRootPath, "").substr(1);
|
let relativePath = path.relative('.', __dirname);
|
||||||
let server = new factorioServer({
|
let server = new factorioServer({
|
||||||
instanceDirectory: path.join(relativePath, "_testData_createMap_2"),
|
instanceDirectory: path.join(relativePath, "_testData_createMap_2"),
|
||||||
instanceName: "Unit testing instance",
|
instanceName: "Unit testing instance",
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,7 +1,7 @@
|
|||||||
var assert = require("assert");
|
var assert = require("assert");
|
||||||
var fs = require("fs");
|
var fs = require("fs");
|
||||||
|
|
||||||
var fileOps = require("_app/fileOps");
|
var fileOps = require("lib/fileOps");
|
||||||
var generateSSLcert = require("./generateSSLcert.js")
|
var generateSSLcert = require("./generateSSLcert.js")
|
||||||
|
|
||||||
describe("generateSSLcert.js(options)", ()=>{
|
describe("generateSSLcert.js(options)", ()=>{
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
import { getItemDatabase } from "./getItemDatabase";
|
|
||||||
import { getSlaveDatabase } from "./getSlaveDatabase";
|
|
||||||
|
|
||||||
export type db = {
|
|
||||||
items: {[name:string]: any},
|
|
||||||
slaves: Slaves,
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getDatabase(config: ClusterioConfig) {
|
|
||||||
let db: db = {
|
|
||||||
items: getItemDatabase(config),
|
|
||||||
slaves: getSlaveDatabase(config),
|
|
||||||
};
|
|
||||||
return db
|
|
||||||
}
|
|
||||||
Vendored
-6
@@ -1,6 +0,0 @@
|
|||||||
declare module "getFactorioLocale" {
|
|
||||||
interface callback {
|
|
||||||
(error: Error, factorioLocale?: any): void
|
|
||||||
}
|
|
||||||
export function asObject(factorioDirectory: string, languageCode: string, callback: callback): void
|
|
||||||
}
|
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
const fileOps = require("_app/fileOps");
|
const fileOps = require("lib/fileOps");
|
||||||
// const config = require("./../config");
|
// const config = require("./../config");
|
||||||
|
|
||||||
module.exports = function(config) {
|
module.exports = function(config) {
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
import * as fs from "fs"
|
|
||||||
import * as path from "path"
|
|
||||||
|
|
||||||
export function getItemDatabase(config: ClusterioConfig): {[name:string]: number | Function} {
|
|
||||||
var items: {[name: string]: number | Function} = {}
|
|
||||||
try{
|
|
||||||
let x = fs.statSync(path.resolve(config.databaseDirectory, "items.json"));
|
|
||||||
console.log(`loading items from ${path.resolve(config.databaseDirectory, "items.json")}`);
|
|
||||||
items = JSON.parse(fs.readFileSync(path.resolve(config.databaseDirectory, "items.json")).toString());
|
|
||||||
} catch (e){
|
|
||||||
items = {};
|
|
||||||
}
|
|
||||||
items.addItem = function addItem(object: item): boolean {
|
|
||||||
if(object.name == "addItem" || object.name == "removeItem") {
|
|
||||||
console.error("Fuck you, that would screw everything up if you named your item that.");
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
if(this[object.name] && Number(this[object.name]) != NaN){
|
|
||||||
this[object.name] = Number(this[object.name]) + Number(object.count);
|
|
||||||
} else {
|
|
||||||
this[object.name] = object.count;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
items.removeItem = function removeItem(object: item): boolean {
|
|
||||||
if(object.name == "addItem" || object.name == "removeItem") {
|
|
||||||
console.error("Fuck you, that would screw everything up if you named your item that.");
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
if(this[object.name] && Number(this[object.name]) != NaN){
|
|
||||||
this[object.name] = Number(this[object.name]) - Number(object.count);
|
|
||||||
} else {
|
|
||||||
this[object.name] = 0;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return items
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
import * as fs from "fs"
|
|
||||||
import * as path from "path"
|
|
||||||
|
|
||||||
export function getSlaveDatabase(config: ClusterioConfig): Slaves {
|
|
||||||
var slaves: Slaves = {};
|
|
||||||
try{
|
|
||||||
let x = fs.statSync(path.resolve(config.databaseDirectory, "/slaves.json"));
|
|
||||||
console.log(`loading slaves from path.resolve(config.databaseDirectory, "slaves.json")`);
|
|
||||||
slaves = JSON.parse(fs.readFileSync(path.resolve(config.databaseDirectory, "slaves.json"), "utf-8"));
|
|
||||||
} catch (e){
|
|
||||||
slaves = {};
|
|
||||||
}
|
|
||||||
return slaves
|
|
||||||
}
|
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
const crypto = require('crypto');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a promise that resolves to the SHA1 hash of the file given by path
|
||||||
|
*/
|
||||||
|
exports.hashFile = function(path) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
let hasher = crypto.createHash('sha1')
|
||||||
|
hasher.setEncoding('hex')
|
||||||
|
hasher.on('finish', function() {
|
||||||
|
resolve(hasher.read());
|
||||||
|
});
|
||||||
|
|
||||||
|
let stream = fs.createReadStream(path);
|
||||||
|
stream.on('error', function(error) {
|
||||||
|
// the docs doesn't say anything about what's passed on error
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
stream.pipe(hasher);
|
||||||
|
});
|
||||||
|
}
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,2 @@
|
|||||||
|
const fs = require("fs-extra");
|
||||||
|
const needle = require("needle");
|
||||||
@@ -3,7 +3,6 @@ Provides tools to manage mods in a clusterio install
|
|||||||
@module modManager
|
@module modManager
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const rightpad = require("right-pad");
|
|
||||||
const asTable = require("as-table").configure({delimiter: ' | '});
|
const asTable = require("as-table").configure({delimiter: ' | '});
|
||||||
const moment = require("moment");
|
const moment = require("moment");
|
||||||
const syncRequest = require("sync-request");
|
const syncRequest = require("sync-request");
|
||||||
@@ -13,7 +12,7 @@ const factorioAPI = require("node-factorio-api");
|
|||||||
const sLog = require("single-line-log").stdout;
|
const sLog = require("single-line-log").stdout;
|
||||||
const query = require('cli-interact').getYesNo;
|
const query = require('cli-interact').getYesNo;
|
||||||
|
|
||||||
const fileOps = require("_app/fileOps");
|
const fileOps = require("lib/fileOps");
|
||||||
// const config = require("./../../config");
|
// const config = require("./../../config");
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ function download(url, file, callback){
|
|||||||
}
|
}
|
||||||
|
|
||||||
var directories = [
|
var directories = [
|
||||||
"database/linvodb",
|
|
||||||
"sharedMods",
|
"sharedMods",
|
||||||
"instances",
|
"instances",
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"name": "lib",
|
||||||
|
"description": "Internal library for factorioClusterio"
|
||||||
|
}
|
||||||
|
|
||||||
@@ -35,54 +35,99 @@ require 'hotpatch.gui'
|
|||||||
-- As this is part of the base mod, the developers have given permission for modders to use/adapt the assets therein, for modding Factorio
|
-- 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
|
-- 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
|
-- 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', '1.0.0', [===[
|
hotpatch_tools.static_mod('freeplay', '0.17.50', [===[
|
||||||
|
local util = require("util")
|
||||||
local silo_script = require("silo-script")
|
local silo_script = require("silo-script")
|
||||||
local version = 1
|
|
||||||
|
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)
|
script.on_event(defines.events.on_player_created, function(event)
|
||||||
local player = game.players[event.player_index]
|
local player = game.players[event.player_index]
|
||||||
player.insert{name="iron-plate", count=8}
|
util.insert_safe(player, global.created_items)
|
||||||
player.insert{name="pistol", count=1}
|
|
||||||
player.insert{name="firearm-magazine", count=10}
|
local r = global.chart_distance or 200
|
||||||
player.insert{name="burner-mining-drill", count = 1}
|
player.force.chart(player.surface, {{player.position.x - r, player.position.y - r}, {player.position.x + r, player.position.y + r}})
|
||||||
player.insert{name="stone-furnace", count = 1}
|
|
||||||
player.force.chart(player.surface, {{player.position.x - 200, player.position.y - 200}, {player.position.x + 200, player.position.y + 200}})
|
if not global.skip_intro then
|
||||||
if (#game.players <= 1) then
|
if game.is_multiplayer() then
|
||||||
game.show_message_dialog{text = {"msg-intro"}}
|
player.print({"msg-intro"})
|
||||||
else
|
else
|
||||||
player.print({"msg-intro"})
|
game.show_message_dialog{text = {"msg-intro"}}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
silo_script.on_player_created(event)
|
|
||||||
|
silo_script.on_event(event)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
script.on_event(defines.events.on_player_respawned, function(event)
|
script.on_event(defines.events.on_player_respawned, function(event)
|
||||||
local player = game.players[event.player_index]
|
local player = game.players[event.player_index]
|
||||||
player.insert{name="pistol", count=1}
|
util.insert_safe(player, global.respawn_items)
|
||||||
player.insert{name="firearm-magazine", count=10}
|
silo_script.on_event(event)
|
||||||
end)
|
|
||||||
|
|
||||||
script.on_event(defines.events.on_gui_click, function(event)
|
|
||||||
silo_script.on_gui_click(event)
|
|
||||||
end)
|
|
||||||
|
|
||||||
script.on_init(function()
|
|
||||||
global.version = version
|
|
||||||
silo_script.on_init()
|
|
||||||
end)
|
|
||||||
|
|
||||||
script.on_event(defines.events.on_rocket_launched, function(event)
|
|
||||||
silo_script.on_rocket_launched(event)
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
script.on_configuration_changed(function(event)
|
script.on_configuration_changed(function(event)
|
||||||
if global.version ~= version then
|
global.created_items = global.created_items or created_items()
|
||||||
global.version = version
|
global.respawn_items = global.respawn_items or respawn_items()
|
||||||
end
|
|
||||||
silo_script.on_configuration_changed(event)
|
silo_script.on_configuration_changed(event)
|
||||||
end)
|
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_remote_interface()
|
||||||
silo_script.add_commands()
|
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
|
-- Multi-file test
|
||||||
@@ -98,7 +143,7 @@ files['testfolder.test'] = [===[
|
|||||||
]===]
|
]===]
|
||||||
|
|
||||||
files['test'] = [===[
|
files['test'] = [===[
|
||||||
require 'testfolder.test'
|
require 'testfolder.test'
|
||||||
]===]
|
]===]
|
||||||
|
|
||||||
hotpatch_tools.static_mod('require-test', '1.0.0', [===[
|
hotpatch_tools.static_mod('require-test', '1.0.0', [===[
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
local hotpatch_tools = require 'hotpatch.mod-tools'
|
local hotpatch_tools = require 'hotpatch.mod-tools'
|
||||||
hotpatch_tools.static_mod('hotpatch-commands', '1.0.1', [===[
|
hotpatch_tools.static_mod('hotpatch-commands', '1.0.2', [===[
|
||||||
--[[
|
--[[
|
||||||
|
|
||||||
Copyright 2018 Chrisgbk
|
Copyright 2018 Chrisgbk
|
||||||
@@ -61,11 +61,11 @@ sub_commands = {
|
|||||||
if (not param) or (param == '') then
|
if (not param) or (param == '') then
|
||||||
caller.print('Usage: /hotpatch [command] [parameters]')
|
caller.print('Usage: /hotpatch [command] [parameters]')
|
||||||
caller.print('Available commands:')
|
caller.print('Available commands:')
|
||||||
for k, v in pairs(sub_commands) do caller.print(k) end
|
for k, _ in pairs(sub_commands) do caller.print(k) end
|
||||||
caller.print('Use /hotpatch help [command] for help on a particular command')
|
caller.print('Use /hotpatch help [command] for help on a particular command')
|
||||||
else
|
else
|
||||||
local text = help_commands[param]
|
local text = help_commands[param]
|
||||||
if text then
|
if text then
|
||||||
caller.print(text)
|
caller.print(text)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -82,7 +82,7 @@ sub_commands = {
|
|||||||
caller.print(table.concat{'[', k, '] ', v.name, ' ', v.version, ' ', (v.loaded and 'loaded') or 'not loaded', ' ', (v.running and 'running') or 'not running'})
|
caller.print(table.concat{'[', k, '] ', v.name, ' ', v.version, ' ', (v.loaded and 'loaded') or 'not loaded', ' ', (v.running and 'running') or 'not running'})
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
@@ -113,7 +113,7 @@ local admin_commands = {
|
|||||||
|
|
||||||
_ENV.commands.add_command('hotpatch', 'Commands for hotpatch. Run /hotpatch help for details.', function(e)
|
_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
|
local caller = (e.player_index and game.players[e.player_index]) or console
|
||||||
if (not e.parameter) or (e.parameter == '') then
|
if (not e.parameter) or (e.parameter == '') then
|
||||||
sub_commands['help'](e.player_index)
|
sub_commands['help'](e.player_index)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@@ -130,6 +130,6 @@ _ENV.commands.add_command('hotpatch', 'Commands for hotpatch. Run /hotpatch help
|
|||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
]===])
|
--]===])
|
||||||
|
|
||||||
return true
|
return true
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
local hotpatch_tools = require 'hotpatch.mod-tools'
|
local hotpatch_tools = require 'hotpatch.mod-tools'
|
||||||
hotpatch_tools.static_mod('hotpatch-gui', '1.0.4', [===[
|
hotpatch_tools.static_mod('hotpatch-gui', '1.0.6', [===[
|
||||||
--[[
|
--[[
|
||||||
|
|
||||||
Copyright 2018 Chrisgbk
|
Copyright 2018 Chrisgbk
|
||||||
@@ -55,26 +55,26 @@ script.on_event(defines.events.on_player_joined_game, function(e)
|
|||||||
local top = mod_gui.get_button_flow(player)
|
local top = mod_gui.get_button_flow(player)
|
||||||
local left = mod_gui.get_frame_flow(player)
|
local left = mod_gui.get_frame_flow(player)
|
||||||
local center = player.gui.center
|
local center = player.gui.center
|
||||||
|
|
||||||
local button = top['hotpatch-button']
|
local button = top['hotpatch-button']
|
||||||
if button then
|
if button then
|
||||||
button.destroy()
|
button.destroy()
|
||||||
end
|
end
|
||||||
local menu = left['hotpatch-menu']
|
local menu = left['hotpatch-menu']
|
||||||
if menu then
|
if menu then
|
||||||
menu.destroy()
|
menu.destroy()
|
||||||
end
|
end
|
||||||
|
|
||||||
local IDE = center['hotpatch-IDE']
|
local IDE = center['hotpatch-IDE']
|
||||||
if IDE then
|
if IDE then
|
||||||
IDE.destroy()
|
IDE.destroy()
|
||||||
end
|
end
|
||||||
|
|
||||||
local main = center['hotpatch-main']
|
local main = center['hotpatch-main']
|
||||||
if main then
|
if main then
|
||||||
main.destroy()
|
main.destroy()
|
||||||
end
|
end
|
||||||
|
|
||||||
button = top.add{type = 'sprite-button', name = 'hotpatch-button', sprite='utility/heat_exchange_indication', tooltip = 'Hotpatch', style = mod_gui.button_style}
|
button = top.add{type = 'sprite-button', name = 'hotpatch-button', sprite='utility/heat_exchange_indication', tooltip = 'Hotpatch', style = mod_gui.button_style}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@@ -86,37 +86,33 @@ on_gui_click_handlers = {
|
|||||||
--on_gui_click_handlers['hotpatch-menu.IDE'](e)
|
--on_gui_click_handlers['hotpatch-menu.IDE'](e)
|
||||||
--do return end
|
--do return end
|
||||||
local player = game.players[e.player_index]
|
local player = game.players[e.player_index]
|
||||||
local top = mod_gui.get_button_flow(player)
|
|
||||||
local left = mod_gui.get_frame_flow(player)
|
local left = mod_gui.get_frame_flow(player)
|
||||||
local center = player.gui.center
|
local center = player.gui.center
|
||||||
|
|
||||||
local menu = left['hotpatch-menu']
|
local menu = left['hotpatch-menu']
|
||||||
if not menu then
|
if not menu then
|
||||||
menu = left.add{type = 'frame', name = 'hotpatch-menu', direction = 'vertical'}
|
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.IDE', caption = 'Mod IDE', tooltip = 'Open debugging GUI'}
|
||||||
menu.add{type = 'button', name = 'hotpatch-menu.console', caption = 'Mod Console', tooltip = 'Open debugging console'}
|
menu.add{type = 'button', name = 'hotpatch-menu.console', caption = 'Mod Console', tooltip = 'Open debugging console'}
|
||||||
menu.style.visible = true
|
menu.visible = true
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
menu.style.visible = not menu.style.visible
|
menu.visible = not menu.visible
|
||||||
end,
|
end,
|
||||||
['hotpatch-menu'] = function(e)
|
['hotpatch-menu'] = function(e)
|
||||||
local player = game.players[e.player_index]
|
local player = game.players[e.player_index]
|
||||||
local top = mod_gui.get_button_flow(player)
|
|
||||||
local left = mod_gui.get_frame_flow(player)
|
local left = mod_gui.get_frame_flow(player)
|
||||||
local center = player.gui.center
|
|
||||||
|
if e.element.name ~= 'hotpatch-menu' then
|
||||||
local menu = left['hotpatch-menu']
|
local menu = left['hotpatch-menu']
|
||||||
menu.style.visible = not menu.style.visible
|
menu.visible = not menu.visible
|
||||||
|
on_gui_click_handlers[e.element.name](e)
|
||||||
on_gui_click_handlers[e.element.name](e)
|
end
|
||||||
end,
|
end,
|
||||||
['hotpatch-menu.IDE'] = function(e)
|
['hotpatch-menu.IDE'] = function(e)
|
||||||
local player = game.players[e.player_index]
|
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 center = player.gui.center
|
||||||
|
|
||||||
local IDE = center['hotpatch-IDE']
|
local IDE = center['hotpatch-IDE']
|
||||||
if not IDE then
|
if not IDE then
|
||||||
IDE = center.add{type = 'frame', name = 'hotpatch-IDE', direction = 'vertical', caption = 'Hotpatch IDE', style = mod_gui.frame_style}
|
IDE = center.add{type = 'frame', name = 'hotpatch-IDE', direction = 'vertical', caption = 'Hotpatch IDE', style = mod_gui.frame_style}
|
||||||
@@ -132,22 +128,22 @@ on_gui_click_handlers = {
|
|||||||
local code = IDE_table.add{type = 'text-box', name = 'hotpatch-IDE-code'}
|
local code = IDE_table.add{type = 'text-box', name = 'hotpatch-IDE-code'}
|
||||||
code.word_wrap = true
|
code.word_wrap = true
|
||||||
files.style.width = 400
|
files.style.width = 400
|
||||||
files.style.cell_spacing = 0
|
files.style.cell_padding = 0
|
||||||
files.style.top_padding = 0
|
files.style.top_padding = 0
|
||||||
files.style.bottom_padding = 0
|
files.style.bottom_padding = 0
|
||||||
code.style.width = 600
|
code.style.width = 600
|
||||||
code.style.height = 600
|
code.style.height = 600
|
||||||
IDE.style.visible = false
|
IDE.visible = false
|
||||||
end
|
end
|
||||||
|
|
||||||
local IDE_dropdown = IDE['hotpatch-IDE-top']['hotpatch-IDE-mod-selector']
|
local IDE_dropdown = IDE['hotpatch-IDE-top']['hotpatch-IDE-mod-selector']
|
||||||
IDE_dropdown.clear_items()
|
IDE_dropdown.clear_items()
|
||||||
for k, v in ipairs(installed_mods) do
|
for _, v in ipairs(installed_mods) do
|
||||||
IDE_dropdown.add_item(v.name)
|
IDE_dropdown.add_item(v.name)
|
||||||
end
|
end
|
||||||
|
|
||||||
IDE.style.visible = not IDE.style.visible
|
IDE.visible = not IDE.visible
|
||||||
if IDE.style.visible then
|
if IDE.visible then
|
||||||
player.opened = IDE
|
player.opened = IDE
|
||||||
else
|
else
|
||||||
player.opened = nil
|
player.opened = nil
|
||||||
@@ -155,18 +151,16 @@ on_gui_click_handlers = {
|
|||||||
end,
|
end,
|
||||||
['hotpatch-menu.console'] = function(e)
|
['hotpatch-menu.console'] = function(e)
|
||||||
local player = game.players[e.player_index]
|
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 center = player.gui.center
|
||||||
|
|
||||||
local console = center['hotpatch-console']
|
local hotpatch_console = center['hotpatch-console']
|
||||||
if not console then
|
if not hotpatch_console then
|
||||||
console = center.add{type = 'frame', name = 'hotpatch-console', direction = 'vertical', caption = 'Hotpatch Console'}
|
hotpatch_console = center.add{type = 'frame', name = 'hotpatch-console', direction = 'vertical', caption = 'Hotpatch Console'}
|
||||||
local top_flow = console.add{type = 'flow', name = 'hotpatch-console-top', direction = 'horizontal'}
|
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: '}
|
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'}
|
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'}
|
top_flow.add{type = 'label', name = 'hotpatch-console-mod-version', caption = 'Version: No mod selected'}
|
||||||
local bottom_flow = console.add{type = 'flow', name = 'hotpatch-console-bottom', direction = 'vertical'}
|
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 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 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'}
|
local input = bottom_input_flow.add{type = 'textfield', name = 'hotpatch-console-input'}
|
||||||
@@ -174,53 +168,51 @@ on_gui_click_handlers = {
|
|||||||
output.style.width = 800
|
output.style.width = 800
|
||||||
output.style.height = 600
|
output.style.height = 600
|
||||||
input.style.width = 600
|
input.style.width = 600
|
||||||
|
hotpatch_console.visible = false
|
||||||
end
|
end
|
||||||
|
|
||||||
local console_dropdown = console['hotpatch-console-top']['hotpatch-console-mod-selector']
|
local console_dropdown = hotpatch_console['hotpatch-console-top']['hotpatch-console-mod-selector']
|
||||||
console_dropdown.clear_items()
|
console_dropdown.clear_items()
|
||||||
for k, v in ipairs(loaded_mods) do
|
for _, v in ipairs(loaded_mods) do
|
||||||
console_dropdown.add_item(v.name)
|
console_dropdown.add_item(v.name)
|
||||||
end
|
end
|
||||||
|
|
||||||
console.style.visible = not console.style.visible
|
hotpatch_console.visible = not hotpatch_console.visible
|
||||||
if console.style.visible then
|
if hotpatch_console.visible then
|
||||||
player.opened = console
|
player.opened = hotpatch_console
|
||||||
else
|
else
|
||||||
player.opened = nil
|
player.opened = nil
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
['hotpatch-console-run'] = function(e)
|
['hotpatch-console-run'] = function(e)
|
||||||
local player = game.players[e.player_index]
|
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 center = player.gui.center
|
||||||
|
|
||||||
local console = center['hotpatch-console']
|
local hotpatch_console = center['hotpatch-console']
|
||||||
local output = console['hotpatch-console-bottom']['hotpatch-console-output']
|
local output = hotpatch_console['hotpatch-console-bottom']['hotpatch-console-output']
|
||||||
local input = console['hotpatch-console-bottom']['hotpatch-console-bottom-input']['hotpatch-console-input']
|
local input = hotpatch_console['hotpatch-console-bottom']['hotpatch-console-bottom-input']['hotpatch-console-input']
|
||||||
local console_dropdown = console['hotpatch-console-top']['hotpatch-console-mod-selector']
|
local console_dropdown = hotpatch_console['hotpatch-console-top']['hotpatch-console-mod-selector']
|
||||||
|
|
||||||
if not player.admin then
|
if not player.admin then
|
||||||
output.text = table.concat{output.text, '\n', 'Only admins!'}
|
output.text = table.concat{output.text, '\n', 'Only admins!'}
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if not (console_dropdown.selected_index > 0) then
|
if not (console_dropdown.selected_index > 0) then
|
||||||
output.text = table.concat{output.text, '\n', 'Select a mod!'}
|
output.text = table.concat{output.text, '\n', 'Select a mod!'}
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local mod_name = console_dropdown.items[console_dropdown.selected_index]
|
local mod_name = console_dropdown.items[console_dropdown.selected_index]
|
||||||
local loaded_index = find_loaded_mod(mod_name)
|
local loaded_index = find_loaded_mod(mod_name)
|
||||||
local env = loaded_mods[loaded_index].env
|
local env = loaded_mods[loaded_index].env
|
||||||
local old_print = env.print
|
local old_print = env.print
|
||||||
local old_log = env.log
|
local old_log = env.log
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
env.print = function(...)
|
env.print = function(...)
|
||||||
--local count = select('#', ...)
|
--local count = select('#', ...)
|
||||||
local t = table.pack(...)
|
local t = table.pack(...)
|
||||||
@@ -230,33 +222,32 @@ on_gui_click_handlers = {
|
|||||||
local text = table.concat(t, '\t')
|
local text = table.concat(t, '\t')
|
||||||
output.text = table.concat{output.text, '\n', text}
|
output.text = table.concat{output.text, '\n', text}
|
||||||
end
|
end
|
||||||
|
|
||||||
env.log = function(text)
|
env.log = function(text)
|
||||||
output.text = table.concat{output.text, '\n', text}
|
output.text = table.concat{output.text, '\n', text}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local success
|
||||||
local code, err = load(input.text, input.text, 't', env)
|
local code, err = load(input.text, input.text, 't', env)
|
||||||
if code then
|
if code then
|
||||||
local success, err = pcall(code)
|
success, err = pcall(code)
|
||||||
if not success then
|
if not success then
|
||||||
output.text = table.concat{output.text, '\n', err}
|
output.text = table.concat{output.text, '\n', err}
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
output.text = table.concat{output.text, '\n', err}
|
output.text = table.concat{output.text, '\n', err}
|
||||||
end
|
end
|
||||||
|
|
||||||
env.print = old_print
|
env.print = old_print
|
||||||
env.log = old_log
|
env.log = old_log
|
||||||
end,
|
end,
|
||||||
['hotpatch-IDE-file'] = function(e)
|
['hotpatch-IDE-file'] = function(e)
|
||||||
local player = game.players[e.player_index]
|
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 center = player.gui.center
|
||||||
|
|
||||||
local element = e.element
|
local element = e.element
|
||||||
local previous
|
local previous
|
||||||
for k, v in pairs(center['hotpatch-IDE']['hotpatch-IDE-table']['hotpatch-IDE-files']['hotpatch-IDE-files-table'].children) do
|
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
|
if table.compare(v.style.font_color or {}, {r=1.0, g=1.0, b=0.0, a=1.0}) then
|
||||||
previous = v
|
previous = v
|
||||||
break
|
break
|
||||||
@@ -294,10 +285,8 @@ end)
|
|||||||
on_gui_selection_state_changed_handlers = {
|
on_gui_selection_state_changed_handlers = {
|
||||||
['hotpatch-console-mod-selector'] = function(e)
|
['hotpatch-console-mod-selector'] = function(e)
|
||||||
local player = game.players[e.player_index]
|
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 center = player.gui.center
|
||||||
|
|
||||||
local element = e.element
|
local element = e.element
|
||||||
local name = element.items[element.selected_index]
|
local name = element.items[element.selected_index]
|
||||||
if name then
|
if name then
|
||||||
@@ -306,20 +295,18 @@ on_gui_selection_state_changed_handlers = {
|
|||||||
local mod = loaded_mods[index]
|
local mod = loaded_mods[index]
|
||||||
local version_label = center['hotpatch-console']['hotpatch-console-top']['hotpatch-console-mod-version']
|
local version_label = center['hotpatch-console']['hotpatch-console-top']['hotpatch-console-mod-version']
|
||||||
version_label.caption = 'Version: ' .. mod.version
|
version_label.caption = 'Version: ' .. mod.version
|
||||||
local console = center['hotpatch-console']
|
local hotpatch_console = center['hotpatch-console']
|
||||||
local output = console['hotpatch-console-bottom']['hotpatch-console-output']
|
local output = hotpatch_console['hotpatch-console-bottom']['hotpatch-console-output']
|
||||||
output.text = ''
|
output.text = ''
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
['hotpatch-IDE-mod-selector'] = function(e)
|
['hotpatch-IDE-mod-selector'] = function(e)
|
||||||
local player = game.players[e.player_index]
|
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 center = player.gui.center
|
||||||
|
|
||||||
local element = e.element
|
local element = e.element
|
||||||
|
|
||||||
local name = element.items[element.selected_index]
|
local name = element.items[element.selected_index]
|
||||||
if name then
|
if name then
|
||||||
local index = find_installed_mod(name)
|
local index = find_installed_mod(name)
|
||||||
@@ -332,7 +319,7 @@ on_gui_selection_state_changed_handlers = {
|
|||||||
local file = list.add{type = 'label', style = 'hoverable_bold_label', caption = 'control', name = 'hotpatch-IDE-file.control'}
|
local file = list.add{type = 'label', style = 'hoverable_bold_label', caption = 'control', name = 'hotpatch-IDE-file.control'}
|
||||||
file.style.bottom_padding = 0
|
file.style.bottom_padding = 0
|
||||||
file.style.top_padding = 0
|
file.style.top_padding = 0
|
||||||
for k, v in pairs(mod.files) do
|
for k, _ in pairs(mod.files) do
|
||||||
file = list.add{type = 'label', style = 'hoverable_bold_label', caption = k, name = 'hotpatch-IDE-file.' .. k}
|
file = list.add{type = 'label', style = 'hoverable_bold_label', caption = k, name = 'hotpatch-IDE-file.' .. k}
|
||||||
file.style.bottom_padding = 0
|
file.style.bottom_padding = 0
|
||||||
file.style.top_padding = 0
|
file.style.top_padding = 0
|
||||||
@@ -356,20 +343,35 @@ script.on_event(defines.events.on_gui_closed, function(e)
|
|||||||
if e.gui_type == defines.gui_type.custom then
|
if e.gui_type == defines.gui_type.custom then
|
||||||
if element and element.valid then
|
if element and element.valid then
|
||||||
if element.name == 'hotpatch-IDE' then
|
if element.name == 'hotpatch-IDE' then
|
||||||
if element.style.visible then
|
if element.visible then
|
||||||
player.play_sound{path = "utility/gui_click"}
|
player.play_sound{path = "utility/gui_click"}
|
||||||
end
|
end
|
||||||
element.style.visible = false
|
element.visible = false
|
||||||
elseif element.name == 'hotpatch-console' then
|
elseif element.name == 'hotpatch-console' then
|
||||||
if element.style.visible then
|
if element.visible then
|
||||||
player.play_sound{path = "utility/gui_click"}
|
player.play_sound{path = "utility/gui_click"}
|
||||||
end
|
end
|
||||||
element.style.visible = false
|
element.visible = false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
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
|
return true
|
||||||
@@ -15,6 +15,9 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
|
|||||||
log('Hotpatch runtime initializing...')
|
log('Hotpatch runtime initializing...')
|
||||||
local util = require 'util'
|
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}
|
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
|
-- configuration options; these should be exposed via API one day
|
||||||
@@ -23,12 +26,15 @@ 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_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
|
local debug_log_on_tick = _ENV.debug_settings.log_on_tick
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
-- convenience object(rcon.print also prints to stdout when called from server console)
|
-- 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}}
|
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
|
-- these represent the mods as statically packaged with the scenario
|
||||||
local static_mods = {}
|
local static_mods = {}
|
||||||
-- static_mods[i] = {}
|
-- static_mods[i] = {}
|
||||||
@@ -67,7 +73,7 @@ local mod_obj_template = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
-- this is for the core factorio libraries; hotpatch MUST load these now, as they cannot be dynamically loaded due to factorio limitation
|
-- 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 = {}
|
local loaded_libraries = {}
|
||||||
|
|
||||||
-- mod installation/uninstallation support functions
|
-- mod installation/uninstallation support functions
|
||||||
-- These take a mod NAME as a first argument
|
-- These take a mod NAME as a first argument
|
||||||
@@ -101,6 +107,8 @@ local mod_on_configuration_changed
|
|||||||
local on_event
|
local on_event
|
||||||
local on_nth_tick
|
local on_nth_tick
|
||||||
|
|
||||||
|
local register_all_events
|
||||||
|
|
||||||
-- core handlers
|
-- core handlers
|
||||||
local on_init -- installs static mods, loads, runs, and calls mod_on_init
|
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_load -- loads, runs, and calls mod_on_load
|
||||||
@@ -125,12 +133,6 @@ local function static_mod(name, version, code, files)
|
|||||||
table.insert(static_mods, mod)
|
table.insert(static_mods, mod)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- mapping of events to names for logging
|
|
||||||
local event_names = {}
|
|
||||||
for k,v in pairs(defines.events) do
|
|
||||||
event_names[v] = k
|
|
||||||
end
|
|
||||||
|
|
||||||
-- This scenario contains an embedded locale, but it's unavailable until this file finishes loading
|
-- 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
|
-- 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)
|
-- Rseding91 fixed it for 0.17 (https://forums.factorio.com/viewtopic.php?f=23&t=60767)
|
||||||
@@ -222,12 +224,12 @@ local function build_locale(ini)
|
|||||||
local t = {}
|
local t = {}
|
||||||
local section = ''
|
local section = ''
|
||||||
local temp
|
local temp
|
||||||
|
|
||||||
-- line must end with a linefeed - single line file with no LF will fail
|
-- line must end with a linefeed - single line file with no LF will fail
|
||||||
if not ini:match('[\r\n]+$') then
|
if not ini:match('[\r\n]+$') then
|
||||||
ini = ini .. '\n'
|
ini = ini .. '\n'
|
||||||
end
|
end
|
||||||
|
|
||||||
-- for each non-empty line do
|
-- for each non-empty line do
|
||||||
local key, value
|
local key, value
|
||||||
for l in ini:gmatch('[\r\n]*(.-)[\r\n]+') do
|
for l in ini:gmatch('[\r\n]*(.-)[\r\n]+') do
|
||||||
@@ -261,7 +263,7 @@ local function static_translate(t, recursive)
|
|||||||
-- only translate tables that have a string as the first item
|
-- only translate tables that have a string as the first item
|
||||||
local k = t[1]
|
local k = t[1]
|
||||||
if type(k) ~= 'string' then return t end
|
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
|
-- make a copy, don't destroy the original table, after we copy we can translate in place
|
||||||
if not recursive then
|
if not recursive then
|
||||||
t = table.deepcopy(t)
|
t = table.deepcopy(t)
|
||||||
@@ -285,12 +287,12 @@ local function static_translate(t, recursive)
|
|||||||
-- substitution of parameters: use literal value of parameter n
|
-- substitution of parameters: use literal value of parameter n
|
||||||
-- __n__
|
-- __n__
|
||||||
local result = (pattern:gsub('__(%d+)__', function(s) return tostring(t[tonumber(s)+1]) end))
|
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
|
-- 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;__
|
-- __n:(pattern-1)=substitution-1;(pattern-2)=substitution-2;...(pattern-i)=substitution-i;__
|
||||||
for n, p in result:gmatch('__(%d+)(:.-;)__') do
|
for n, p in result:gmatch('__(%d+)(:.-;)__') do
|
||||||
for x, y in p:gmatch('%((.-)%)=(.-[^%%]);') do
|
for x, y in p:gmatch('%((.-)%)=(.-[^%%]);') do
|
||||||
if t[tonumber(n)+1]:match(x) then
|
if t[tonumber(n)+1]:match(x) then
|
||||||
result = result:gsub(table.concat{'__', n, escape(p), '__'}, unescape(y))
|
result = result:gsub(table.concat{'__', n, escape(p), '__'}, unescape(y))
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
@@ -324,7 +326,7 @@ local hidden_log = hidden(static_translate)
|
|||||||
|
|
||||||
-- logs localized data
|
-- logs localized data
|
||||||
local function debug_log(message, mod_name, stack_level)
|
local function debug_log(message, mod_name, stack_level)
|
||||||
if debug_level > -1 then
|
if debug_level > -1 then
|
||||||
if not stack_level then stack_level = 2 end
|
if not stack_level then stack_level = 2 end
|
||||||
local di = debug.getinfo(stack_level)
|
local di = debug.getinfo(stack_level)
|
||||||
local line = di.currentline
|
local line = di.currentline
|
||||||
@@ -332,7 +334,7 @@ local function debug_log(message, mod_name, stack_level)
|
|||||||
local class = 'hotpatch.info'
|
local class = 'hotpatch.info'
|
||||||
local log_type = (mod_name and 'hotpatch.log-mod') or 'hotpatch.log'
|
local log_type = (mod_name and 'hotpatch.log-mod') or 'hotpatch.log'
|
||||||
local severity
|
local severity
|
||||||
if type(message) == 'table' then
|
if type(message) == 'table' then
|
||||||
severity = message[1]:match('.-%-([^%-]*)%.')
|
severity = message[1]:match('.-%-([^%-]*)%.')
|
||||||
if not severity then
|
if not severity then
|
||||||
severity = message[1]:match('.-%-.-%-(.*)%.')
|
severity = message[1]:match('.-%-.-%-(.*)%.')
|
||||||
@@ -344,7 +346,7 @@ local function debug_log(message, mod_name, stack_level)
|
|||||||
else
|
else
|
||||||
severity = 'always'
|
severity = 'always'
|
||||||
end
|
end
|
||||||
local level = ((severity == 'always') and 0) or debug_levels[severity]
|
local level = ((severity == 'always') and 0) or debug_levels[severity]
|
||||||
if debug_level >= level then
|
if debug_level >= level then
|
||||||
if debug_log_to_console_only then
|
if debug_log_to_console_only then
|
||||||
if debug_log_to_RCON then
|
if debug_log_to_RCON then
|
||||||
@@ -363,15 +365,15 @@ debug_log({'hotpatch-info.logging-enabled'})
|
|||||||
|
|
||||||
-- track _ENV accesses
|
-- track _ENV accesses
|
||||||
setmetatable(_ENV, {
|
setmetatable(_ENV, {
|
||||||
__index = function(t, k)
|
__index = function(_, k)
|
||||||
debug_log({'hotpatch-trace.nil-var-access', k}, nil, 3)
|
debug_log({'hotpatch-trace.nil-var-access', k}, nil, 3)
|
||||||
return nil
|
return nil
|
||||||
end,
|
end,
|
||||||
__newindex = function(t, k, v)
|
__newindex = function(t, k, v)
|
||||||
debug_log({'hotpatch-trace.nil-var-assignment', k}, nil, 3)
|
debug_log({'hotpatch-trace.nil-var-assignment', k}, nil, 3)
|
||||||
rawset(t,k,v)
|
rawset(t,k,v)
|
||||||
end,
|
end,
|
||||||
__metatable = false
|
__metatable = false
|
||||||
})
|
})
|
||||||
|
|
||||||
debug_log({'hotpatch-info.metatable-installed'})
|
debug_log({'hotpatch-info.metatable-installed'})
|
||||||
@@ -382,7 +384,6 @@ debug_log({'hotpatch-info.loading-libs'})
|
|||||||
local libraries = {
|
local libraries = {
|
||||||
'camera',
|
'camera',
|
||||||
'flying_tags',
|
'flying_tags',
|
||||||
'inspect',
|
|
||||||
'math3d',
|
'math3d',
|
||||||
'mod-gui',
|
'mod-gui',
|
||||||
'noise',
|
'noise',
|
||||||
@@ -394,7 +395,7 @@ local libraries = {
|
|||||||
|
|
||||||
for k, v in pairs(libraries) do
|
for k, v in pairs(libraries) do
|
||||||
debug_log({'hotpatch-info.loading-library', v})
|
debug_log({'hotpatch-info.loading-library', v})
|
||||||
loaded_libraries[k] = require(v)
|
loaded_libraries[k] = require(v)
|
||||||
end
|
end
|
||||||
|
|
||||||
debug_log({'hotpatch-info.complete', {'hotpatch-info.loading-libs'}})
|
debug_log({'hotpatch-info.complete', {'hotpatch-info.loading-libs'}})
|
||||||
@@ -429,14 +430,14 @@ uninstall_mod = function(mod_name)
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
local loaded_index = find_loaded_mod(mod_name)
|
local loaded_index = find_loaded_mod(mod_name)
|
||||||
|
|
||||||
if loaded_index then
|
if loaded_index then
|
||||||
unload_mod(loaded_index)
|
unload_mod(loaded_index)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
debug_log({'hotpatch-info.uninstalling', mod_name})
|
debug_log({'hotpatch-info.uninstalling', mod_name})
|
||||||
|
|
||||||
table.remove(global.mods, index)
|
table.remove(global.mods, index)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -447,7 +448,7 @@ install_mod_file = function(mod_name, mod_file, mod_file_code)
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
local mod = global.mods[index]
|
local mod = global.mods[index]
|
||||||
|
|
||||||
debug_log({'hotpatch-info.installing-file', mod_file}, mod_name)
|
debug_log({'hotpatch-info.installing-file', mod_file}, mod_name)
|
||||||
|
|
||||||
mod_file = mod_file:gsub('/', '.')
|
mod_file = mod_file:gsub('/', '.')
|
||||||
@@ -475,7 +476,7 @@ install_mod = function(mod_name, mod_version, mod_code, mod_files)
|
|||||||
end
|
end
|
||||||
debug_log({'hotpatch-warning.contains-comments-console'}, mod_name)
|
debug_log({'hotpatch-warning.contains-comments-console'}, mod_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
mod_code = mod_code:gsub('\t', ' ')
|
mod_code = mod_code:gsub('\t', ' ')
|
||||||
|
|
||||||
mod.name = mod_name
|
mod.name = mod_name
|
||||||
@@ -483,7 +484,7 @@ install_mod = function(mod_name, mod_version, mod_code, mod_files)
|
|||||||
mod.code = mod_code
|
mod.code = mod_code
|
||||||
mod.version = mod_version
|
mod.version = mod_version
|
||||||
mod.global = mod.global or {}
|
mod.global = mod.global or {}
|
||||||
|
|
||||||
if mod_files then
|
if mod_files then
|
||||||
for k,v in pairs(mod_files) do
|
for k,v in pairs(mod_files) do
|
||||||
install_mod_file(mod_name, k, v)
|
install_mod_file(mod_name, k, v)
|
||||||
@@ -501,7 +502,7 @@ load_mod = function(installed_index)
|
|||||||
--TODO notify that mod was already loaded
|
--TODO notify that mod was already loaded
|
||||||
unload_mod(loaded_index)
|
unload_mod(loaded_index)
|
||||||
end
|
end
|
||||||
|
|
||||||
local mod_obj = {
|
local mod_obj = {
|
||||||
name = mod_name, -- name of the current mod
|
name = mod_name, -- name of the current mod
|
||||||
version = mod.version,
|
version = mod.version,
|
||||||
@@ -516,7 +517,7 @@ load_mod = function(installed_index)
|
|||||||
on_nth_tick = {}, -- list of on_nth_tick 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
|
loaded_files = {}, -- list of files loaded by require() that were installed into the virtual file system
|
||||||
}
|
}
|
||||||
|
|
||||||
debug_log({'hotpatch-info.script-shim'}, mod_name)
|
debug_log({'hotpatch-info.script-shim'}, mod_name)
|
||||||
--mods private script table/shim
|
--mods private script table/shim
|
||||||
local mod_script = {}
|
local mod_script = {}
|
||||||
@@ -541,7 +542,7 @@ load_mod = function(installed_index)
|
|||||||
mod_script.on_nth_tick = function(tick, f)
|
mod_script.on_nth_tick = function(tick, f)
|
||||||
if tick then
|
if tick then
|
||||||
if type(tick) == 'table' then
|
if type(tick) == 'table' then
|
||||||
for k, v in pairs(tick) do
|
for _, v in pairs(tick) do
|
||||||
mod_script.on_nth_tick(v, f)
|
mod_script.on_nth_tick(v, f)
|
||||||
end
|
end
|
||||||
return
|
return
|
||||||
@@ -551,12 +552,12 @@ load_mod = function(installed_index)
|
|||||||
mod_obj.on_nth_tick = {}
|
mod_obj.on_nth_tick = {}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
mod_script.on_event = function(event, f)
|
mod_script.on_event = function(event, f)
|
||||||
if event == defines.events.on_tick then
|
if event == defines.events.on_tick then
|
||||||
mod_obj.on_tick = f
|
mod_obj.on_tick = f
|
||||||
if mod_obj.running then
|
if mod_obj.running then
|
||||||
register_on_tick(mod_name, event)
|
register_on_tick(mod_name)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
mod_obj.on_event[event] = f
|
mod_obj.on_event[event] = f
|
||||||
@@ -568,7 +569,7 @@ load_mod = function(installed_index)
|
|||||||
mod_script.on_nth_tick = function(tick, f)
|
mod_script.on_nth_tick = function(tick, f)
|
||||||
if tick then
|
if tick then
|
||||||
if type(tick) == 'table' then
|
if type(tick) == 'table' then
|
||||||
for k, v in pairs(tick) do
|
for _, v in pairs(tick) do
|
||||||
mod_script.on_nth_tick(v, f)
|
mod_script.on_nth_tick(v, f)
|
||||||
end
|
end
|
||||||
return
|
return
|
||||||
@@ -578,11 +579,11 @@ load_mod = function(installed_index)
|
|||||||
register_nth_tick(mod_name, tick)
|
register_nth_tick(mod_name, tick)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
local on_nth_tick = mod_obj.on_nth_tick
|
local mod_on_nth_tick = mod_obj.on_nth_tick
|
||||||
mod_obj.on_nth_tick = {}
|
mod_obj.on_nth_tick = {}
|
||||||
if mod_obj.running then
|
if mod_obj.running then
|
||||||
for k, v in pairs(on_nth_tick) do
|
for _, v in pairs(mod_on_nth_tick) do
|
||||||
register_nth_tick(mod_name, tick)
|
register_nth_tick(mod_name, v)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -604,7 +605,7 @@ load_mod = function(installed_index)
|
|||||||
mod_script.mod_name = function()
|
mod_script.mod_name = function()
|
||||||
return script.mod_name()
|
return script.mod_name()
|
||||||
end
|
end
|
||||||
|
|
||||||
debug_log({'hotpatch-info.setting-env'}, mod_name)
|
debug_log({'hotpatch-info.setting-env'}, mod_name)
|
||||||
-- mods private env
|
-- mods private env
|
||||||
local env = mod_obj.env
|
local env = mod_obj.env
|
||||||
@@ -613,28 +614,28 @@ load_mod = function(installed_index)
|
|||||||
-- mods private package.loaded
|
-- mods private package.loaded
|
||||||
local loaded = {}
|
local loaded = {}
|
||||||
-- copy the current environment
|
-- copy the current environment
|
||||||
for k,v in pairs(_ENV) do
|
for k,v in pairs(_ENV) do
|
||||||
env[k] = v
|
env[k] = v
|
||||||
end
|
end
|
||||||
-- copy package.loaded
|
-- copy package.loaded
|
||||||
for k,v in pairs(_ENV.package.loaded) do
|
for k,v in pairs(_ENV.package.loaded) do
|
||||||
loaded[k] = v
|
loaded[k] = v
|
||||||
end
|
end
|
||||||
loaded._G = env
|
loaded._G = env
|
||||||
|
|
||||||
-- so many ways to escape sandboxes...
|
-- so many ways to escape sandboxes...
|
||||||
|
|
||||||
for k,v in pairs(_ENV.package) do
|
for k,v in pairs(_ENV.package) do
|
||||||
pack[k] = v
|
pack[k] = v
|
||||||
end
|
end
|
||||||
pack.loaded = loaded
|
pack.loaded = loaded
|
||||||
env.package = pack
|
env.package = pack
|
||||||
loaded.package = pack
|
loaded.package = pack
|
||||||
|
|
||||||
env.script = mod_script
|
env.script = mod_script
|
||||||
env.global = mod.global
|
env.global = mod.global
|
||||||
env._G = env
|
env._G = env
|
||||||
|
|
||||||
env['remote'] = {
|
env['remote'] = {
|
||||||
add_interface = function(name, functions)
|
add_interface = function(name, functions)
|
||||||
if remote.interfaces[name] then
|
if remote.interfaces[name] then
|
||||||
@@ -650,7 +651,7 @@ load_mod = function(installed_index)
|
|||||||
return remote.call(...)
|
return remote.call(...)
|
||||||
end,
|
end,
|
||||||
interfaces = setmetatable({}, {
|
interfaces = setmetatable({}, {
|
||||||
__index = function(t, k) return remote.interfaces[k] end,
|
__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
|
__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
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -666,29 +667,29 @@ load_mod = function(installed_index)
|
|||||||
return commands.remove_command(name)
|
return commands.remove_command(name)
|
||||||
end,
|
end,
|
||||||
commands = setmetatable({}, {
|
commands = setmetatable({}, {
|
||||||
__index = function(t, k) return commands.commands[k] end,
|
__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
|
__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({}, {
|
game_commands = setmetatable({}, {
|
||||||
__index = function(t, k) return commands.game_commands[k] end,
|
__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
|
__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)
|
env.require = function(path)
|
||||||
-- I blame Nexela for this
|
-- I blame Nexela for this
|
||||||
path = path:gsub('/', '.')
|
path = path:gsub('/', '.')
|
||||||
path = path:gsub('\\', '.')
|
path = path:gsub('\\', '.')
|
||||||
local alt_path = ''
|
local alt_path = ''
|
||||||
if env.package._current_path_in_package then
|
if env.package._current_path_in_package then
|
||||||
alt_path = env.package._current_path_in_package .. path
|
alt_path = env.package._current_path_in_package .. path
|
||||||
end
|
end
|
||||||
if mod_obj.loaded_files[path] then
|
if mod_obj.loaded_files[path] then
|
||||||
debug_log({'hotpatch-trace.cached-load-require', path}, mod_name)
|
debug_log({'hotpatch-trace.cached-load-require', path}, mod_name)
|
||||||
return mod_obj.loaded_files[path]
|
return mod_obj.loaded_files[path]
|
||||||
elseif mod_obj.loaded_files[alt_path] then
|
elseif mod_obj.loaded_files[alt_path] then
|
||||||
debug_log({'hotpatch-trace.cached-load-require', alt_path}, mod_name)
|
debug_log({'hotpatch-trace.cached-load-require', alt_path}, mod_name)
|
||||||
return mod_obj.loaded_files[alt_path]
|
return mod_obj.loaded_files[alt_path]
|
||||||
else
|
else
|
||||||
local oldbase = env.package._current_path_in_package
|
local oldbase = env.package._current_path_in_package
|
||||||
@@ -710,7 +711,7 @@ load_mod = function(installed_index)
|
|||||||
error(err)
|
error(err)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
local file = global.mods[installed_index].files[alt_path]
|
file = global.mods[installed_index].files[alt_path]
|
||||||
if file then
|
if file then
|
||||||
debug_log({'hotpatch-trace.load-require', alt_path}, mod_name)
|
debug_log({'hotpatch-trace.load-require', alt_path}, mod_name)
|
||||||
local code, err = load(file, '[' .. mod_name .. '] ' .. alt_path .. '.lua', 'bt', env)
|
local code, err = load(file, '[' .. mod_name .. '] ' .. alt_path .. '.lua', 'bt', env)
|
||||||
@@ -724,31 +725,31 @@ load_mod = function(installed_index)
|
|||||||
error(err)
|
error(err)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
debug_log({'hotpatch-trace.load-core-lib', path}, mod_name)
|
debug_log({'hotpatch-trace.load-core-lib', path}, mod_name)
|
||||||
env.package._current_path_in_package = oldbase
|
env.package._current_path_in_package = oldbase
|
||||||
local lib = package.loaded[path]
|
local lib = package.loaded[path]
|
||||||
if not lib then
|
if not lib then
|
||||||
debug_log(path .. ' not found', nil, 3)
|
debug_log({'hotpatch-error.path-not-found', path}, mod_name, 3)
|
||||||
error(path .. ' not found')
|
error(path .. ' not found')
|
||||||
end
|
end
|
||||||
return lib
|
return lib
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
env['load'] = function(l, s, m, e)
|
env['load'] = function(l, s, m, e)
|
||||||
return load(l, s, m, e or env)
|
return load(l, s, m, e or env)
|
||||||
end
|
end
|
||||||
env['loadstring'] = env['load']
|
env['loadstring'] = env['load']
|
||||||
|
|
||||||
|
|
||||||
env['game'] = setmetatable({}, {
|
env['game'] = setmetatable({}, {
|
||||||
__index = function(t, k) return game[k] end,
|
__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
|
__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 = {}
|
local mt = {}
|
||||||
mt.__index = function(t, k)
|
mt.__index = function(_, k)
|
||||||
debug_log({'hotpatch-trace.nil-var-access', k}, nil, 3)
|
debug_log({'hotpatch-trace.nil-var-access', k}, nil, 3)
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
@@ -762,7 +763,7 @@ load_mod = function(installed_index)
|
|||||||
|
|
||||||
--load/run code
|
--load/run code
|
||||||
debug_log({'hotpatch-info.loading'}, mod_name)
|
debug_log({'hotpatch-info.loading'}, mod_name)
|
||||||
|
|
||||||
local mod_code, message = load(mod.code, '[' .. mod_name .. '] control.lua', 'bt', env)
|
local mod_code, message = load(mod.code, '[' .. mod_name .. '] control.lua', 'bt', env)
|
||||||
if not mod_code then
|
if not mod_code then
|
||||||
debug_log({'hotpatch-error.compilation-failed'}, mod_name)
|
debug_log({'hotpatch-error.compilation-failed'}, mod_name)
|
||||||
@@ -807,21 +808,17 @@ reset_mod = function(loaded_index)
|
|||||||
local install_index = find_installed_mod(mod.name)
|
local install_index = find_installed_mod(mod.name)
|
||||||
mod = global.mods[install_index]
|
mod = global.mods[install_index]
|
||||||
mod.global = new_global
|
mod.global = new_global
|
||||||
--local mod_global = global.mod_global[mod_name]
|
|
||||||
--for k, v in pairs(mod_global) do
|
|
||||||
-- mod_global[k] = nil
|
|
||||||
--end
|
|
||||||
|
|
||||||
reset_mod_events(loaded_index)
|
reset_mod_events(loaded_index)
|
||||||
end
|
end
|
||||||
|
|
||||||
reset_mod_events = function(loaded_index)
|
reset_mod_events = function(loaded_index)
|
||||||
local loaded_index = find_loaded_mod(mod_name)
|
--local loaded_index = find_loaded_mod(mod_name)
|
||||||
|
local loaded_mod = loaded_mods[loaded_index]
|
||||||
if not loaded_index then
|
|
||||||
debug_log({'hotpatch-warning.reset-events-not-running'}, mod_name)
|
if not loaded_mod then
|
||||||
else
|
--debug_log({'hotpatch-warning.reset-events-not-running'}, mod_name)
|
||||||
local loaded_mod = loaded_mods[loaded_index]
|
else
|
||||||
loaded_mod.on_event = {}
|
loaded_mod.on_event = {}
|
||||||
loaded_mod.on_nth_tick = {}
|
loaded_mod.on_nth_tick = {}
|
||||||
loaded_mod.on_init = nil
|
loaded_mod.on_init = nil
|
||||||
@@ -832,17 +829,40 @@ reset_mod_events = function(loaded_index)
|
|||||||
end
|
end
|
||||||
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)
|
run_mod = function(loaded_index)
|
||||||
local mod = loaded_mods[loaded_index]
|
local mod = loaded_mods[loaded_index]
|
||||||
if mod then
|
if mod then
|
||||||
local mod_name = mod.name
|
local mod_name = mod.name
|
||||||
local old_global = table.deepcopy(mod.env.global)
|
local old_global = mod.env.global
|
||||||
|
mod.env.global = wrap_table(old_global)
|
||||||
if strict_mode then
|
|
||||||
--mod.env.global = {}
|
|
||||||
end
|
|
||||||
debug_log({'hotpatch-info.running'}, mod_name)
|
|
||||||
|
|
||||||
|
debug_log({'hotpatch-info.running'}, mod_name)
|
||||||
|
|
||||||
local success, result = xpcall(mod.code, debug.traceback)
|
local success, result = xpcall(mod.code, debug.traceback)
|
||||||
if not success then
|
if not success then
|
||||||
debug_log({'hotpatch-error.execution-failed'}, mod_name)
|
debug_log({'hotpatch-error.execution-failed'}, mod_name)
|
||||||
@@ -851,40 +871,22 @@ run_mod = function(loaded_index)
|
|||||||
if game and game.player then
|
if game and game.player then
|
||||||
game.player.print(result)
|
game.player.print(result)
|
||||||
end
|
end
|
||||||
|
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
if strict_mode then
|
mod.env.global = old_global
|
||||||
--if mod.env.global ~= {} then
|
old_global = nil
|
||||||
--TODO: error, mod touched global inappropriately during load
|
|
||||||
--end
|
|
||||||
--mod.env.global = old_global
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
-- if a mod is being loaded that already has global data, it must be restored after
|
|
||||||
-- running control.lua, discarding any changes made
|
|
||||||
if not table.compare(old_global, {}) then
|
|
||||||
for k,v in pairs(mod.env.global) do
|
|
||||||
mod.env.global[k] = nil
|
|
||||||
end
|
|
||||||
for k,v in pairs(old_global) do
|
|
||||||
mod.env.global[k] = v
|
|
||||||
end
|
|
||||||
--mod.env.global = old_global
|
|
||||||
end
|
|
||||||
old_global = nil
|
|
||||||
|
|
||||||
mod.running = true
|
mod.running = true
|
||||||
debug_log({'hotpatch-info.complete', {'hotpatch-info.running'}}, mod_name)
|
debug_log({'hotpatch-info.complete', {'hotpatch-info.running'}}, mod_name)
|
||||||
|
|
||||||
--load complete, start notifying on event subscriptions
|
--load complete, start notifying on event subscriptions
|
||||||
if not compat_mode then
|
if not compat_mode then
|
||||||
mod.env.script.on_event = function(event, f)
|
mod.env.script.on_event = function(event, f)
|
||||||
if event == defines.events.on_tick then
|
if event == defines.events.on_tick then
|
||||||
mod.on_tick = f
|
mod.on_tick = f
|
||||||
register_on_tick(mod_name, event)
|
register_on_tick(mod_name)
|
||||||
else
|
else
|
||||||
mod.on_event[event] = f
|
mod.on_event[event] = f
|
||||||
register_event(mod_name, event)
|
register_event(mod_name, event)
|
||||||
@@ -893,18 +895,18 @@ run_mod = function(loaded_index)
|
|||||||
mod.env.script.on_nth_tick = function(tick, f)
|
mod.env.script.on_nth_tick = function(tick, f)
|
||||||
if tick then
|
if tick then
|
||||||
if type(tick) == 'table' then
|
if type(tick) == 'table' then
|
||||||
for k, v in pairs(tick) do
|
for _, v in pairs(tick) do
|
||||||
mod_script.on_nth_tick(v, f)
|
mod.env.script.on_nth_tick(v, f)
|
||||||
end
|
end
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
mod.on_nth_tick[tick] = f
|
mod.on_nth_tick[tick] = f
|
||||||
register_nth_tick(mod_name, tick)
|
register_nth_tick(mod_name, tick)
|
||||||
else
|
else
|
||||||
local on_nth_tick = mod.on_nth_tick
|
local mod_on_nth_tick = mod.on_nth_tick
|
||||||
mod.on_nth_tick = {}
|
mod.on_nth_tick = {}
|
||||||
for k, v in pairs(on_nth_tick) do
|
for _, v in pairs(mod_on_nth_tick) do
|
||||||
register_nth_tick(mod_name, tick)
|
register_nth_tick(mod_name, v)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -924,7 +926,7 @@ on_event = function(event)
|
|||||||
for i = 1, #loaded_mods do
|
for i = 1, #loaded_mods do
|
||||||
mod = loaded_mods[i]
|
mod = loaded_mods[i]
|
||||||
f = mod.on_event[event.name]
|
f = mod.on_event[event.name]
|
||||||
if f then
|
if f then
|
||||||
debug_log({'hotpatch-trace.event-running', event_name}, mod.name)
|
debug_log({'hotpatch-trace.event-running', event_name}, mod.name)
|
||||||
f(event)
|
f(event)
|
||||||
end
|
end
|
||||||
@@ -940,7 +942,7 @@ if debug_log_on_tick then
|
|||||||
for i = 1, #loaded_mods do
|
for i = 1, #loaded_mods do
|
||||||
mod = loaded_mods[i]
|
mod = loaded_mods[i]
|
||||||
f = mod.on_nth_tick[event.nth_tick]
|
f = mod.on_nth_tick[event.nth_tick]
|
||||||
if f then
|
if f then
|
||||||
debug_log({'hotpatch-trace.nth-tick-event-running', tick}, mod.name)
|
debug_log({'hotpatch-trace.nth-tick-event-running', tick}, mod.name)
|
||||||
f(event)
|
f(event)
|
||||||
end
|
end
|
||||||
@@ -954,7 +956,7 @@ else
|
|||||||
for i = 1, #loaded_mods do
|
for i = 1, #loaded_mods do
|
||||||
mod = loaded_mods[i]
|
mod = loaded_mods[i]
|
||||||
f = mod.on_nth_tick[event.nth_tick]
|
f = mod.on_nth_tick[event.nth_tick]
|
||||||
if f then
|
if f then
|
||||||
debug_log({'hotpatch-trace.nth-tick-event-running', tick}, mod.name)
|
debug_log({'hotpatch-trace.nth-tick-event-running', tick}, mod.name)
|
||||||
f(event)
|
f(event)
|
||||||
end
|
end
|
||||||
@@ -962,7 +964,7 @@ else
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local register_all_events = function()
|
register_all_events = function()
|
||||||
--unregister all events
|
--unregister all events
|
||||||
script.on_event(defines.events, nil)
|
script.on_event(defines.events, nil)
|
||||||
script.on_nth_tick(nil, nil)
|
script.on_nth_tick(nil, nil)
|
||||||
@@ -981,12 +983,12 @@ register_mod_events = function(loaded_index)
|
|||||||
debug_log({'hotpatch-trace.on-tick-event-registered'}, mod_name)
|
debug_log({'hotpatch-trace.on-tick-event-registered'}, mod_name)
|
||||||
script.on_event(defines.events.on_tick, on_tick)
|
script.on_event(defines.events.on_tick, on_tick)
|
||||||
end
|
end
|
||||||
for k,v in pairs(mod.on_event) do
|
for k,_ in pairs(mod.on_event) do
|
||||||
local event_name = (event_names[k] or k)
|
local event_name = (event_names[k] or k)
|
||||||
debug_log({'hotpatch-trace.on-event-registered', event_name}, mod_name)
|
debug_log({'hotpatch-trace.on-event-registered', event_name}, mod_name)
|
||||||
script.on_event(k, on_event)
|
script.on_event(k, on_event)
|
||||||
end
|
end
|
||||||
for k,v in pairs(mod.on_nth_tick) do
|
for k,_ in pairs(mod.on_nth_tick) do
|
||||||
debug_log({'hotpatch-trace.on-nth-tick-event-registered', k}, mod_name)
|
debug_log({'hotpatch-trace.on-nth-tick-event-registered', k}, mod_name)
|
||||||
script.on_nth_tick(k, on_nth_tick)
|
script.on_nth_tick(k, on_nth_tick)
|
||||||
end
|
end
|
||||||
@@ -994,14 +996,14 @@ end
|
|||||||
|
|
||||||
mod_on_init = function(loaded_index)
|
mod_on_init = function(loaded_index)
|
||||||
local mod = loaded_mods[loaded_index]
|
local mod = loaded_mods[loaded_index]
|
||||||
|
|
||||||
if mod then
|
if mod then
|
||||||
debug_log({'hotpatch-trace.mod-on-init'}, mod.name)
|
debug_log({'hotpatch-trace.mod-on-init'}, mod.name)
|
||||||
if mod.on_init then
|
if mod.on_init then
|
||||||
local success, result = xpcall(mod.on_init, debug.traceback)
|
local success, result = xpcall(mod.on_init, debug.traceback)
|
||||||
if not success then
|
if not success then
|
||||||
debug_log('on_init failed for ' .. mod.name)
|
debug_log({'hotpatch-error.on-init-failed'}, mod_name)
|
||||||
debug_log(result)
|
debug_log(result, mod_name)
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -1013,17 +1015,20 @@ end
|
|||||||
|
|
||||||
mod_on_load = function(loaded_index)
|
mod_on_load = function(loaded_index)
|
||||||
local mod = loaded_mods[loaded_index]
|
local mod = loaded_mods[loaded_index]
|
||||||
|
|
||||||
if mod then
|
if mod then
|
||||||
debug_log({'hotpatch-trace.mod-on-load'}, mod.name)
|
debug_log({'hotpatch-trace.mod-on-load'}, mod.name)
|
||||||
if mod.on_load then
|
if mod.on_load then
|
||||||
--local old_global = table.deepcopy(mod.env.global)
|
local old_global = mod.env.global
|
||||||
|
mod.env.global = wrap_table(table.deepcopy(old_global))
|
||||||
local success, result = xpcall(mod.on_load, debug.traceback)
|
local success, result = xpcall(mod.on_load, debug.traceback)
|
||||||
if not success then
|
if not success then
|
||||||
debug_log('on_load failed for ' .. mod.name)
|
debug_log({'hotpatch-error.on-load-failed'}, mod_name)
|
||||||
debug_log(result)
|
debug_log(result, mod_name)
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
mod.env.global = old_global
|
||||||
|
|
||||||
end
|
end
|
||||||
register_mod_events(loaded_index)
|
register_mod_events(loaded_index)
|
||||||
return true
|
return true
|
||||||
@@ -1033,14 +1038,14 @@ end
|
|||||||
|
|
||||||
mod_on_configuration_changed = function(loaded_index, config)
|
mod_on_configuration_changed = function(loaded_index, config)
|
||||||
local mod = loaded_mods[loaded_index]
|
local mod = loaded_mods[loaded_index]
|
||||||
|
|
||||||
if mod then
|
if mod then
|
||||||
debug_log({'hotpatch-trace.mod-on-configuration-changed'}, mod.name)
|
debug_log({'hotpatch-trace.mod-on-configuration-changed'}, mod.name)
|
||||||
if mod.on_configuration_changed then
|
if mod.on_configuration_changed then
|
||||||
local success, result = xpcall(mod.on_configuration_changed, debug.traceback, config)
|
local success, result = xpcall(mod.on_configuration_changed, debug.traceback, config)
|
||||||
if not success then
|
if not success then
|
||||||
debug_log('on_configuration_changed failed for ' .. mod.name)
|
debug_log({'hotpatch-error.on-configuration-changed-failed'}, mod_name)
|
||||||
debug_log(result)
|
debug_log(result, mod_name)
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -1058,9 +1063,9 @@ register_on_tick = function(mod_name)
|
|||||||
for i = 1, #loaded_mods do
|
for i = 1, #loaded_mods do
|
||||||
mod = loaded_mods[i]
|
mod = loaded_mods[i]
|
||||||
local f = mod.on_tick
|
local f = mod.on_tick
|
||||||
if f then
|
if f then
|
||||||
found_event = true
|
found_event = true
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
debug_log({'hotpatch-trace.on-tick-event-registered'}, mod_name)
|
debug_log({'hotpatch-trace.on-tick-event-registered'}, mod_name)
|
||||||
@@ -1078,9 +1083,9 @@ register_nth_tick = function(mod_name, nth_tick)
|
|||||||
for i = 1, #loaded_mods do
|
for i = 1, #loaded_mods do
|
||||||
mod = loaded_mods[i]
|
mod = loaded_mods[i]
|
||||||
local f = mod.on_nth_tick[nth_tick]
|
local f = mod.on_nth_tick[nth_tick]
|
||||||
if f then
|
if f then
|
||||||
found_event = true
|
found_event = true
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
debug_log({'hotpatch-trace.on-nth-tick-event-registered', nth_tick}, mod_name)
|
debug_log({'hotpatch-trace.on-nth-tick-event-registered', nth_tick}, mod_name)
|
||||||
@@ -1126,7 +1131,7 @@ on_init = function()
|
|||||||
debug_log({'hotpatch-info.on-init'})
|
debug_log({'hotpatch-info.on-init'})
|
||||||
--juuuuust in case
|
--juuuuust in case
|
||||||
global.mods = global.mods or {}
|
global.mods = global.mods or {}
|
||||||
|
|
||||||
debug_log({'hotpatch-info.installing-included-mods'})
|
debug_log({'hotpatch-info.installing-included-mods'})
|
||||||
local mod
|
local mod
|
||||||
for i = 1, #static_mods do
|
for i = 1, #static_mods do
|
||||||
@@ -1137,7 +1142,7 @@ on_init = function()
|
|||||||
for i = 1, #global.mods do
|
for i = 1, #global.mods do
|
||||||
load_mod(i)
|
load_mod(i)
|
||||||
end
|
end
|
||||||
|
|
||||||
for i = 1, #loaded_mods do
|
for i = 1, #loaded_mods do
|
||||||
run_mod(i)
|
run_mod(i)
|
||||||
mod_on_init(i)
|
mod_on_init(i)
|
||||||
@@ -1153,10 +1158,10 @@ on_load = function()
|
|||||||
hidden_log = hidden()
|
hidden_log = hidden()
|
||||||
debug_log({'hotpatch-info.on-load'})
|
debug_log({'hotpatch-info.on-load'})
|
||||||
debug_log({'hotpatch-info.loading-installed-mods'})
|
debug_log({'hotpatch-info.loading-installed-mods'})
|
||||||
|
|
||||||
if global.globals then
|
if global.globals then
|
||||||
error('Upgrading from Hotpatch 1.0.X to 1.1.0 is not currently supported!')
|
error('Upgrading from Hotpatch 1.0.X to 1.1.0 is not currently supported!')
|
||||||
|
|
||||||
-- untested migration: use a tick to do the migration
|
-- untested migration: use a tick to do the migration
|
||||||
script.on_event(defines.events.on_tick, function(e)
|
script.on_event(defines.events.on_tick, function(e)
|
||||||
global.mods = {}
|
global.mods = {}
|
||||||
@@ -1174,52 +1179,58 @@ on_load = function()
|
|||||||
global.mod_code = nil
|
global.mod_code = nil
|
||||||
global.mod_files = nil
|
global.mod_files = nil
|
||||||
global.globals = nil
|
global.globals = nil
|
||||||
|
|
||||||
on_load() -- jump back into loading
|
on_load() -- jump back into loading
|
||||||
on_tick(e) -- need to test if factorio auto calls this or not after assigning the handler
|
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
|
script.on_event(defines.events.on_tick, on_tick) -- restore old on_tick handler
|
||||||
end)
|
end)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
-- load installed mods
|
-- load installed mods
|
||||||
for i = 1, #global.mods do
|
for i = 1, #global.mods do
|
||||||
load_mod(i)
|
load_mod(i)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- run mods which loaded successfully
|
-- run mods which loaded successfully
|
||||||
local failed_mods = {}
|
local failed_mods = {}
|
||||||
|
--log('before')
|
||||||
|
--log(serpent.block(global))
|
||||||
|
|
||||||
for i = 1, #loaded_mods do
|
for i = 1, #loaded_mods do
|
||||||
if run_mod(i) then
|
if run_mod(i) then
|
||||||
if not mod_on_load(i) then
|
if not mod_on_load(i) then
|
||||||
table.insert(failed_mods, i)
|
table.insert(failed_mods, i)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
table.insert(failed_mods, i)
|
table.insert(failed_mods, i)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- unload mods which failed to run
|
|
||||||
for i = 1, #failed_mods do
|
|
||||||
unload_mod(failed_mods[i])
|
|
||||||
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.loading-installed-mods'}})
|
||||||
debug_log({'hotpatch-info.complete', {'hotpatch-info.on-load'}})
|
debug_log({'hotpatch-info.complete', {'hotpatch-info.on-load'}})
|
||||||
end
|
end
|
||||||
|
|
||||||
on_configuration_changed = function(config)
|
on_configuration_changed = function(config)
|
||||||
debug_log({'hotpatch-info.on-configuration-changed'})
|
debug_log({'hotpatch-info.on-configuration-changed'})
|
||||||
local failed_mods = {}
|
local failed_mods = {}
|
||||||
for i = 1, #loaded_mods do
|
for i = 1, #loaded_mods do
|
||||||
if not mod_on_configuration_changed(i) then
|
if not mod_on_configuration_changed(i, config) then
|
||||||
table.insert(failed_mods, i)
|
table.insert(failed_mods, i)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, #failed_mods do
|
||||||
|
unload_mod(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'}})
|
debug_log({'hotpatch-info.complete', {'hotpatch-info.on-configuration-changed'}})
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1231,7 +1242,7 @@ if debug_log_on_tick then
|
|||||||
for i = 1, #loaded_mods do
|
for i = 1, #loaded_mods do
|
||||||
mod = loaded_mods[i]
|
mod = loaded_mods[i]
|
||||||
f = mod.on_tick
|
f = mod.on_tick
|
||||||
if f then
|
if f then
|
||||||
debug_log({'hotpatch-trace.event-running', 'on_tick'}, mod.name)
|
debug_log({'hotpatch-trace.event-running', 'on_tick'}, mod.name)
|
||||||
f(e)
|
f(e)
|
||||||
end
|
end
|
||||||
@@ -1244,7 +1255,7 @@ else
|
|||||||
for i = 1, #loaded_mods do
|
for i = 1, #loaded_mods do
|
||||||
mod = loaded_mods[i]
|
mod = loaded_mods[i]
|
||||||
f = mod.on_tick
|
f = mod.on_tick
|
||||||
if f then
|
if f then
|
||||||
f(e)
|
f(e)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -1286,31 +1297,31 @@ local mod_tools_internal = setmetatable({
|
|||||||
mod_on_init = mod_on_init,
|
mod_on_init = mod_on_init,
|
||||||
mod_on_load = mod_on_load,
|
mod_on_load = mod_on_load,
|
||||||
mod_on_configuration_changed = mod_on_configuration_changed,
|
mod_on_configuration_changed = mod_on_configuration_changed,
|
||||||
|
|
||||||
static_mods = static_mods,
|
static_mods = static_mods,
|
||||||
console = console,
|
console = console,
|
||||||
debug_log = debug_log,
|
debug_log = debug_log,
|
||||||
loaded_mods = loaded_mods,
|
loaded_mods = loaded_mods,
|
||||||
loaded_libraries = loaded_libraries,
|
loaded_libraries = loaded_libraries,
|
||||||
installed_mods = setmetatable({}, {
|
installed_mods = setmetatable({}, {
|
||||||
__index = function(t, k)
|
__index = function(_, k)
|
||||||
return global.mods[k]
|
return global.mods[k]
|
||||||
end,
|
end,
|
||||||
__newindex = function(t, k, v)
|
__newindex = function(_, __, ___)
|
||||||
-- do nothing
|
-- do nothing
|
||||||
end,
|
end,
|
||||||
__len = function(t)
|
__len = function(_)
|
||||||
return #global.mods
|
return #global.mods
|
||||||
end,
|
end,
|
||||||
__pairs = function(t) local function iter(t, k) local v; v = global.mods[k+1]; if v then return k+1, v end; end; return iter, t, 0 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(t, 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,
|
__metatable = false,
|
||||||
})
|
})
|
||||||
},{
|
},{
|
||||||
__index = function(t, k)
|
__index = function(_, k)
|
||||||
debug_log({'hotpatch-error.invalid-API-access', k}, nil, 3)
|
debug_log({'hotpatch-error.invalid-API-access', k}, nil, 3)
|
||||||
end,
|
end,
|
||||||
__newindex = function(t, k, v)
|
__newindex = function(_, __, ___)
|
||||||
-- do nothing
|
-- do nothing
|
||||||
end,
|
end,
|
||||||
-- Don't let mods muck around
|
-- Don't let mods muck around
|
||||||
@@ -1322,11 +1333,12 @@ local mod_tools = setmetatable({
|
|||||||
-- most code should use static_mod
|
-- most code should use static_mod
|
||||||
static_mod = static_mod, -- (name, version, code, files)
|
static_mod = static_mod, -- (name, version, code, files)
|
||||||
set_debug_level = function(level)
|
set_debug_level = function(level)
|
||||||
debug_level = tonumber(_ENV.debug_settings.level) or debug_levels[_ENV.debug_settings.level]
|
--debug_level = tonumber(_ENV.debug_settings.level) or debug_levels[_ENV.debug_settings.level]
|
||||||
|
debug_level = tonumber(level) or debug_levels[level]
|
||||||
end,
|
end,
|
||||||
},{
|
},{
|
||||||
__index = mod_tools_internal,
|
__index = mod_tools_internal,
|
||||||
__newindex = function(t, k, v)
|
__newindex = function(_, __, ___)
|
||||||
-- do nothing, read-only table
|
-- do nothing, read-only table
|
||||||
end,
|
end,
|
||||||
-- Don't let mods muck around
|
-- Don't let mods muck around
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
local hotpatch_tools = require 'hotpatch.mod-tools'
|
local hotpatch_tools = require 'hotpatch.mod-tools'
|
||||||
hotpatch_tools.static_mod('hotpatch-remote-interface', '1.0.2', [===[
|
hotpatch_tools.static_mod('hotpatch-remote-interface', '1.0.3', [===[
|
||||||
--[[
|
--[[
|
||||||
|
|
||||||
Copyright 2018 Chrisgbk
|
Copyright 2018 Chrisgbk
|
||||||
@@ -65,7 +65,7 @@ remote_interface['install'] = function(mod_name, mod_version, mod_code, mod_file
|
|||||||
local caller = game.player or console
|
local caller = game.player or console
|
||||||
if caller.admin then
|
if caller.admin then
|
||||||
if mod_files then
|
if mod_files then
|
||||||
for k,v in pairs(mod_files) do
|
for k,_ in pairs(mod_files) do
|
||||||
if k == 'control' then
|
if k == 'control' then
|
||||||
caller.print('ERROR: control.lua loaded twice for mod ' .. mod_name)
|
caller.print('ERROR: control.lua loaded twice for mod ' .. mod_name)
|
||||||
return
|
return
|
||||||
@@ -77,14 +77,14 @@ remote_interface['install'] = function(mod_name, mod_version, mod_code, mod_file
|
|||||||
if installed_index then
|
if installed_index then
|
||||||
local old_version = installed_mods[installed_index].version
|
local old_version = installed_mods[installed_index].version
|
||||||
if old_version then
|
if old_version then
|
||||||
debug_log('WARNING: mod already exists: ' .. mod_name .. ' ' .. old_version)
|
debug_log({'hotpatch-warning.already-exists', mod_name, old_version})
|
||||||
debug_log('WARNING: reinstalling mod in-place: ' .. mod_name .. ' ' .. mod_version)
|
debug_log({'hotpatch-warning.reinstalling', mod_name, old_version})
|
||||||
end
|
end
|
||||||
if loaded_index then
|
if loaded_index then
|
||||||
unload_mod(loaded_index)
|
unload_mod(loaded_index)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
install_mod(mod_name, mod_version, mod_code, mod_files)
|
install_mod(mod_name, mod_version, mod_code, mod_files)
|
||||||
if not only_install then
|
if not only_install then
|
||||||
installed_index = find_installed_mod(mod_name)
|
installed_index = find_installed_mod(mod_name)
|
||||||
@@ -131,6 +131,8 @@ remote_interface['run'] = function(mod_name)
|
|||||||
if caller.admin then
|
if caller.admin then
|
||||||
local installed_index = find_installed_mod(mod_name)
|
local installed_index = find_installed_mod(mod_name)
|
||||||
local loaded_index = find_loaded_mod(mod_name)
|
local loaded_index = find_loaded_mod(mod_name)
|
||||||
|
local mod
|
||||||
|
local version
|
||||||
if installed_index and not loaded_index then
|
if installed_index and not loaded_index then
|
||||||
if not load_mod(installed_index) then
|
if not load_mod(installed_index) then
|
||||||
caller.print('compilation failed for mod ' .. mod_name)
|
caller.print('compilation failed for mod ' .. mod_name)
|
||||||
@@ -139,13 +141,13 @@ remote_interface['run'] = function(mod_name)
|
|||||||
loaded_index = find_loaded_mod(mod_name)
|
loaded_index = find_loaded_mod(mod_name)
|
||||||
else
|
else
|
||||||
if loaded_index then
|
if loaded_index then
|
||||||
local mod = loaded_mods[loaded_index]
|
mod = loaded_mods[loaded_index]
|
||||||
local version = mod.version
|
version = mod.version
|
||||||
debug_log('WARNING: mod already loaded: ' .. mod_name .. ' ' .. version)
|
debug_log({'hotpatch-warning.already-loaded', mod_name, version})
|
||||||
debug_log('WARNING: reinitializing: ' .. mod_name .. ' ' .. version)
|
debug_log({'hotpatch-warning.reinitializing', mod_name, version})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if loaded_index then
|
if loaded_index then
|
||||||
if not run_mod(loaded_index) then
|
if not run_mod(loaded_index) then
|
||||||
caller.print('execution failed for mod ' .. mod_name)
|
caller.print('execution failed for mod ' .. mod_name)
|
||||||
@@ -164,7 +166,7 @@ remote_interface['run'] = function(mod_name)
|
|||||||
unload_mod(loaded_index)
|
unload_mod(loaded_index)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
caller.print('You must be an admin to run this command.')
|
caller.print('You must be an admin to run this command.')
|
||||||
end
|
end
|
||||||
@@ -178,7 +180,7 @@ remote_interface['update'] = function(mod_name, mod_version, mod_code, mod_files
|
|||||||
local caller = game.player or console
|
local caller = game.player or console
|
||||||
if caller.admin then
|
if caller.admin then
|
||||||
if mod_files then
|
if mod_files then
|
||||||
for k,v in pairs(mod_files) do
|
for k,_ in pairs(mod_files) do
|
||||||
if k == 'control' then
|
if k == 'control' then
|
||||||
caller.print('ERROR: control.lua loaded twice for mod ' .. mod_name)
|
caller.print('ERROR: control.lua loaded twice for mod ' .. mod_name)
|
||||||
return
|
return
|
||||||
@@ -186,6 +188,9 @@ remote_interface['update'] = function(mod_name, mod_version, mod_code, mod_files
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
local installed_index = find_installed_mod(mod_name)
|
local installed_index = find_installed_mod(mod_name)
|
||||||
|
if installed_index then
|
||||||
|
;
|
||||||
|
end
|
||||||
local loaded_index = find_loaded_mod(mod_name)
|
local loaded_index = find_loaded_mod(mod_name)
|
||||||
local old_version
|
local old_version
|
||||||
if loaded_index then
|
if loaded_index then
|
||||||
@@ -245,16 +250,40 @@ remote_interface['install_mod_file'] = function(mod_name, mod_file, mod_file_cod
|
|||||||
end
|
end
|
||||||
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
|
--TODO: most of this function
|
||||||
remote_interface['clean'] = function(mod_name)
|
remote_interface['clean'] = function(mod_name)
|
||||||
-- Removes ALL mod data and reinitializes
|
-- Removes ALL mod data and reinitializes
|
||||||
-- doesn't remove things a mod may add like surfaces, etc
|
-- doesn't remove things a mod may add like surfaces, etc
|
||||||
local caller = game.player or console
|
local caller = game.player or console
|
||||||
if caller.admin then
|
if caller.admin then
|
||||||
mod_reset(mod_name)
|
--mod_reset(mod_name)
|
||||||
run_mod(mod_name)
|
--run_mod(mod_name)
|
||||||
mod_on_init(mod_name)
|
--mod_on_init(mod_name)
|
||||||
mod_on_configuration_changed(mod_name, {mod_changes = {mod_name={new_version=mod_version}}})
|
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
|
else
|
||||||
caller.print('You must be an admin to run this command.')
|
caller.print('You must be an admin to run this command.')
|
||||||
end
|
end
|
||||||
@@ -274,13 +303,13 @@ remote.add_interface('hotpatch', remote_interface)
|
|||||||
|
|
||||||
debug_log({'hotpatch-info.complete', {'hotpatch-info.remote-installing'}})
|
debug_log({'hotpatch-info.complete', {'hotpatch-info.remote-installing'}})
|
||||||
|
|
||||||
]===])
|
--]===])
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
|
|
||||||
remote.call('hotpatch, 'install', 'test', '1.0.0', [===[
|
remote.call('hotpatch, 'install', 'test', '1.0.0', [===[
|
||||||
script.on_event(defines.events.on_player_changed_position, function(e)
|
script.on_event(defines.events.on_player_changed_position, function(e)
|
||||||
game.print('changed position')
|
game.print('changed position')
|
||||||
end
|
end
|
||||||
]===]
|
]===]
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
msg-intro=Hierdie is Factorio vryspel. Jou taak is om 'n vuurpyl in die buitenste ruimte in te stuur. Doen dit deur die bou van 'n Vuurpyl Silo en van daar af 'n vuurpyl te lanseer met a satelliet aan boord. 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.
|
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
|
||||||
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
msg-intro=.نييلصألا ناكسلا نم كسفن ةيامح ىسنت الو ةياهنلا ىلإ لصتل كتقيرطب لمعأو ،رفصلا نم أدبتس .خيراوصلا عماوص حتف لجأ نم ةمدقتم ايجولونكت نع ثحبلل جاتحت فوس .ةيعانصلا رامقألاب خيراوصلا قالطإو خيراوصلا عماوص ءانب قيرط نع كلذب مايقلا عيطتست .ءاضفلل اخوراص قلطت نا يه كتمهم .Factorio ـل رحلا بعللا وه اذه
|
msg-intro=.بكوكلا اذه ناكس م كسفن يمحت نا ىسنت الو ,ةيكيتاموتوألا ربع كقيرط ينبتسو ,رفصلا نم أدبتس .كلت خيراوصلا قالطإ ةطحم حتف عيطتست فيكل ةروطتملا ايجولونكتلل ثاحبأب مايقلا ىلإ جاتحتس .اهنم خوراص قالطإ مث خيراوص قالطإ ةطحم ءانب قيرط نع كلذب مايقلا كنكمي ,ءاضفلا ىلإ اخوراص قلطت نا يه كتمهم ,factorioـل رحلا بعللا روط وه اذه
|
||||||
|
scenario-name=رحلا بعللا
|
||||||
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
msg-intro=Гэта рэжым свабоднай гульні Factorio. Вашая мэта - запусціць ракету ў космас. Дзеля гэтага вам трэба пабудаваць ракетную шахту і саму ракету з спадарожнікам. Вы мусіце даследваць перадавыя тэхналогіі, каб навучыцца будаваць ракетную шахту. Пачынайце з малога, працуйце над аўтаматызацыяй вытворчасці, даследуйце новыя тэхналогіі і не забывайцеся на абарону ад "тутэйшых"!
|
msg-intro=Гэта рэжым свабоднай гульні Factorio. Вашая мэта - запусціць ракету ў космас. Дзеля гэтага вам трэба пабудаваць ракетную шахту і саму ракету са спадарожнікам. Вы мусіце даследаваць перадавыя тэхналогіі, каб навучыцца будаваць ракетную шахту. Пачынайце з малога, працуйце над аўтаматызацыяй вытворчасці, даследуйце новыя тэхналогіі і не забывайцеся на абарону ад «тутэйшых».
|
||||||
|
scenario-name=Свабодная гульня
|
||||||
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
msg-intro=Това е режимът на свободна игра. Задачата ви е да изстреляте ракета в космоса. За това ще е нужно да построите ракетна шахта и изстреляте ракетата със спътник. Ще трябва да проучите развити технологии, които ще отключат ракетната шахта. Започнете с малко и постепенно автоматизирайте процеса, като не забравяте да се защитавате от местните форми на живот.
|
msg-intro=Това е режимът на свободна игра. Задачата ви е да изстреляте ракета в космоса. За това ще е нужно да построите ракетна шахта и изстреляте ракетата със спътник. Ще трябва да проучите развити технологии, които ще отключат ракетната шахта. Започнете с малко и постепенно автоматизирайте процеса, като не забравяте да се защитавате от местните форми на живот.
|
||||||
|
scenario-name=Свободна игра
|
||||||
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
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 amb un satèl·lit. 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.
|
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
|
||||||
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
msg-intro=Toto je volná hra Factorio. Tvým úkolem je vypustit raketu do vesmíru. Postav raketové silo a vypusť raketu se satelitem. 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.
|
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
|
||||||
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
msg-intro=Det her er Factorio fri leg. Din opgave er at affyre en raket for at udforske rummet. For at gøre det skal du bygge en raket silo og affyre en raket med en satellit i. Du bliver nød til at udforske avancerede teknologier for at at kunne bygge en raket silo. Start småt, og arbejd din vej op med hjælp af automatisering, glem ikke at beskytte dig fra de indfødte.
|
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
|
||||||
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
msg-intro=Dies ist das freie Spiel von Factorio. Deine Aufgabe ist es, eine Rakete in den Weltraum zu starten. Dazu musst Du ein Raketensilo bauen und eine Rakete mit einem Satelliten starten. Du wirst erweiterte Technologien erforschen müssen, um das Raketensilo freizuschalten. Fange klein an, arbeite Dich durch Automatisierung hoch und vergiss nicht, Dich vor den Einheimischen zu schützen.
|
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
|
||||||
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
msg-intro=Αυτό είναι το ελεύθερο παιχνίδι του Factorio. Ο στόχος σου είναι να εκτοξεύσεις έναν πύραυλο στο διάστημα. Αυτό το κάνεις κατασκευάζοντας ένα σιλό πυραύλου και εκτοξεύοντας έναν πυραύλο με δορυφόρο. Θα πρέπει να ερευνήσεις προηγμένες τεχνολογίες για να ξεκλειδώσεις το σιλό πυραύλου. Ξεκινάς περιορισμένος, δούλεψε με αυτοματισμούς για την άνοδο σου και μην ξεχνάς να προστατέψεις τον εαυτό σου από τους ντόπιους.
|
msg-intro=Αυτό είναι το ελεύθερο παιχνίδι του Factorio. Ο στόχος σας είναι να εκτοξεύσετε έναν πύραυλο στο διάστημα. Αυτό το κάνετε κατασκευάζοντας ένα σιλό πυραύλου και εκτοξεύοντας έναν πύραυλο. Θα πρέπει να ερευνήσετε προηγμένες τεχνολογίες για να ξεκλειδώσετε το σιλό πυραύλου. Ξεκινήστε χαμηλά, φτάστε ψηλά χρησιμοποιώντας αυτοματισμούς και μην ξεχάσετε να προστατέψετε τον εαυτό σας από τους ντόπιους.
|
||||||
|
scenario-name=Ελεύθερο παιχνίδι
|
||||||
|
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
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 with a satellite. 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.
|
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
|
||||||
@@ -65,13 +65,17 @@ 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!
|
reset-events-not-running=tried to reset events for mod that isn't running!
|
||||||
remote-interface-exists=remote interface __1__ already exists, removing...
|
remote-interface-exists=remote interface __1__ already exists, removing...
|
||||||
command-exists=command __1__ already exists, removing...
|
command-exists=command __1__ already exists, removing...
|
||||||
already-loaded=mod already loaded
|
already-exists=mod already exists: __1__ __2__
|
||||||
reinitializing=reinitializing...
|
reinstalling=reinstalling mod in-place: __1__ __2__
|
||||||
already-exists=mod already exists
|
already-loaded=mod already loaded: __1__ __2__
|
||||||
reinstalling=reinstalling mod in-place
|
reinitializing=reinitializing: __1__ __2__
|
||||||
|
|
||||||
[hotpatch-error]
|
[hotpatch-error]
|
||||||
invalid-API-access=Invalid API access: __1__
|
invalid-API-access=Invalid API access: __1__
|
||||||
not-installed=mod not installed, cannot install file for mod that does not exist
|
not-installed=mod not installed, cannot install file for mod that does not exist
|
||||||
compilation-failed=compilation failed for mod
|
compilation-failed=compilation failed for mod
|
||||||
execution-failed=execution 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
|
||||||
@@ -1,3 +1,2 @@
|
|||||||
msg-intro=Tio estas la Factorio Libera Ludado. Via tasko estas ĵeti raketon en spacon. Fari tiun per konstruanta Raketo Silo kaj ĵeti raketon kun satelito. Vi bezonos esplori altnivelaj teknologioj por malŝlosi la Raketo Silo. Start malgranda, labori vian vojon supren per aŭtomatigo kaj ne forgesu protekti vin mem de la indiĝenoj.
|
scenario-name=Libera Ludado
|
||||||
|
|
||||||
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
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.
|
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
|
||||||
|
|
||||||
@@ -1,3 +1,2 @@
|
|||||||
msg-intro=See on Factorio vabamäng. Sinu eesmärgiks on saata rakett avakosmosesse. Seda saad teha ehitades Raketi Silotorni ja lennutades Raketti koos satelliidiga. Selleks pead õppima selgeks keerukad tehnoloogiaharud, et Raketi Silotorni lahti lukustada. Alusta väikselt ning liigu edasi automatiseerimise abil ja ära unusta ennast kaitsta kohalike eluvormide eest.
|
scenario-name=Vabamäng
|
||||||
|
|
||||||
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
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.
|
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
|
||||||
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
msg-intro=Il s'agit du mode de jeu libre de Factorio. Votre tâche consiste à lancer une fusée dans l'espace. Ceci en construisant un silo à fusée, et en lançant un satellite avec la fusée. 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.
|
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
|
||||||
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
msg-intro=.םיימוקמה םירוציה ינפמ םכמצע לע ןגהל וחכשת לאו ,יטמוטוא ןוכימ תועצמאב םכלש הישעתה תא וביחרה ,ןטקב וליחתה .ןקתמה תיינב תא רשפאל ידכ תומדקתמ תויגולונכט רוקחל םכילע היהי .ןייוול ליכמה ליט ותועצמאב רגשלו םיליט רוגיש ןקתמ תונבל םכילע .ללחל ליט רגשל איה םכתמישמ .ויִרֹוטְקַפ לש ישפוחה שיחרתה והז
|
msg-intro=.םיימוקמה םירוציה ינפמ םכמצע לע ןגהל וחכשת לאו ,יטמוטוא ןוכימ תועצמאב םכלש הישעתה תא וביחרה ,ןטקב וליחתה .ןקתמה תיינב תא רשפאל ידכ תומדקתמ תויגולונכט רוקחל םכילע היהי .ליט ותועצמאב רגשלו םיליט רוגיש ןקתמ תונבל םכילע .ללחל ליט רגשל איה םכתמישמ .ויִרֹוטְקַפ לש ישפוחה שיחרתה והז
|
||||||
|
scenario-name=ישפוח קחשמ
|
||||||
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
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.
|
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
|
||||||
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
msg-intro=Questa è la modalità libera di Factorio. Il tuo obiettivo è quello di lanciare il razzo nello spazio. Completalo costruendo un Silo per Razzi e lanciando il razzo con il satellite. Avrai bisogno di ricercare tecnologie avanzate per riuscire a sbloccare il Silo per Razzi. Inizia in piccolo, espandi con l'automazione e non dimenticare di proteggerti dai nativi.
|
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
|
||||||
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
msg-intro=これは、Factorioのフリープレイです。ロケットサイロを建造し、衛星を載せたロケットを宇宙へ打ち上げるのがあなたの任務です。ロケットサイロをアンロックするためには高度な技術研究をする必要があります。小規模に始め、自動化しつつ進み、原住生物から身を守ることも忘れないでください。
|
msg-intro=これは、Factorioのフリープレイです。ロケットサイロを建造し、ロケットを宇宙へ打ち上げるのがあなたの任務です。ロケットサイロの建造を行うためには高度な技術研究を行う必要があります。小規模に始め、自動化しつつ進み、原住生物から身を守ることも忘れないでください。
|
||||||
|
scenario-name=フリープレイ
|
||||||
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
msg-intro=Factorio 자유플레이입니다. 당신의 목표는 우주로 로켓을 발사하는 것입니다. 로켓 격납고를 건설하고 인공위성을 탑재한 로켓을 발사하여 이 목표를 달성하세요. 로켓 격납고의 잠금을 해제하기 위해 고급 기술들을 연구할 필요가 있습니다. 작은 것부터 시작해 자동화로 차근차근 발전하고, 토착 생물로부터 자신을 보호하는 것도 잊지 마세요.
|
msg-intro=Factorio 자유플레이입니다. 당신의 목표는 우주로 로켓을 발사하는 것입니다. 로켓 격납고를 건설하고 로켓을 발사하여 이 목표를 달성하세요. 로켓 격납고의 잠금을 해제하기 위해 고급 기술들을 연구할 필요가 있습니다. 작은 것부터 시작해 자동화로 차근차근 발전하고, 토착 생물로부터 자신을 보호하는 것도 잊지 마세요.
|
||||||
|
scenario-name=자유플레이
|
||||||
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
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ų.
|
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
|
||||||
|
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
msg-intro=Šis ir Factorio brīvais režīms. Tavs uzdevums ir palaist raķeti kosmosā. Lai to izdarītu, tev vajadzēs uzbūvēt palaišanas platformu un palaist raķeti ar satelītu. Tev būs jāizpēta sarežģītas tehnoloģijas, lai atbloķētu palaišanas platformu. Sāc no mazumiņa, strādā un neaizmirsti pasargāt sevi no šīs planētas iemītniekiem...
|
scenario-name=Brīvspēle
|
||||||
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
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 met een satelliet. 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.
|
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
|
||||||
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
msg-intro=Dette er Factorio frispilling. 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.
|
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
|
||||||
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
msg-intro=To jest swobodna rozgrywka Factorio. Twoim zadaniem jest wysłanie rakiety w kosmos. Skonstruuj silos rakietowy i wystrzel z niego rakietę z satelitą. Ż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.
|
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
|
||||||
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
msg-intro=Este é o modo livre do Factorio. Sua missão é lançar um foguete no espaço. Para fazer isso construa um centro de lançamento de foguetes e lance o foguete com um satélite. Você vai precisar pesquisar tecnologias avançadas para conseguir desbloquear um Centro de lançamento. Comece do começo, continue na automação e não se esqueça de se proteger dos nativos.
|
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
|
||||||
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
msg-intro=Este é o jogo livre Factorio. A tua tarefa é lançar um foguetão para explorar o espaço. Faça isto, construindo um Silo de Foguetão e lançar um foguetão com um satélite. Tu vais precisar de pesquisar tecnologias avançadas para poderes desbloquear o Silo de Foguetão. Começa simples, trabalha e evolui com a automação e não te esqueças de proteger-te dos nativos.
|
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
|
||||||
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
msg-intro=Acesta este modul Factorio freeplay. Sarcina ta este de a lansa o rachetă în spațiu. Fă acest lucru prin construirea unui siloz de rachete și lansarea unei rachete cu satelit. Va trebui să cercetezi tehnologiile avansate pentru a debloca silozul de rachete. Începe încet, avansează cu automatizarea și nu uita să te protejezi de inamici.
|
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
|
||||||
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
msg-intro=Это режим свободной игры Factorio. Ваша задача запустить спутник в космос. Для его запуска потребуется создать ракету и ракетную шахту. Начните с малого, работайте над автоматизацией производства, исследуйте новые технологии и не забывайте о защите от местных форм жизни.
|
msg-intro=Это режим свободной игры Factorio. Ваша задача - запустить ракету в космос. Для её запуска потребуется исследовать новые технологии и создать ракетную шахту. Начните с малого, работайте над автоматизацией производства и не забывайте о защите от местных форм жизни.
|
||||||
|
scenario-name=Свободная игра
|
||||||
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
msg-intro=Toto je voľná hra Factorio. Tvojou úlohou je vypustiť raketu do vesmíru. Postav raketové silo a vypusti raketu so satelitom. 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.
|
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
|
||||||
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
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.
|
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
|
||||||
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
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.
|
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
|
||||||
|
|
||||||
@@ -1,3 +1,2 @@
|
|||||||
msg-intro=นี้คือโหมดอิสระของ Factorio ภารกิจของคุณคือส่งจรวดสู่อวกาศ ซึ่งทำได้โดยการสร้างไซโลจรวดและส่งจรวดขึ้นไปพร้อมกับดาวเทียม คุณต้องวิจัยค้นคว้าเทคโนโลยีขั้นสูงเพื่อปลดล็อคไซโลจรวด เริ่มจากเล็กๆไปเรื่อยๆด้วยระบบอัตโนมัติและอย่าลืมป้องกันตัวเองจากคนพื้นเมืองด้วย
|
scenario-name=โหมดอิสระ
|
||||||
|
|
||||||
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
msg-intro=Bu Factorio'nun serbest oynanışı. Görevin roketleri uzaya yollamak. Bunu Roket Silosu yaparak ve uyduyla roket yollayarak yapacaksın. Roket Silo'sunu açmak için gelişmiş teknolojileri sırayla açmalısın. Yavaş başla, otomatikleş ve yerlilerden kendini korumayı untuma.
|
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
|
||||||
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
msg-intro=Це один з типів гри в Factorio - "Вільна гра". Ваше завдання - запустити ракету у космічний простір. Зробіть це, побудувавши ракетну шахту та запустивши ракету зі супутником. Вам потрібно дослідити передові технології, щоб мати змогу побудувати ракетну шахту. Починайте з малого, розвивайтеся за допомогою автоматизації, але не забувайте і про оборону!
|
msg-intro=Це один з типів гри в Factorio — "Вільна гра". Ваше завдання — запустити ракету у космічний простір. Зробіть це, побудувавши ракетну шахту та запустивши ракету. Вам потрібно дослідити передові технології, щоб мати змогу побудувати ракетну шахту. Починайте з малого, розвивайтеся за допомогою автоматизації, але не забувайте і про оборону.
|
||||||
|
scenario-name=Вільна гра
|
||||||
|
|
||||||
Loaded 100 of 142 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user