Compare commits

...
163 Commits
Author SHA1 Message Date
Hornwitser 22c1630ff0 Merge pull request #393 from Hornwitser/abandon-1.2.x
Abandon 1.2.x

[skip ci]
2021-06-18 13:59:20 +02:00
Hornwitser ed9966f65c Abandon the 1.2.x version
Development has effectively stopped on the 1.2.x branch and there's no
interest in keeping it going.  Put up a warning that this version is no
longer supported and direct people to go to main clusterio project page
instead.

[skip ci]
2021-06-17 16:12:15 +02:00
Hornwitser 248d9fcd3e Remove Travis integration
The Travis integration is completely broken for 1.2.x due to some issue
with the npm cache and the linked packages created in it by the master
branch.  Remove it from here.
2021-06-17 16:05:17 +02:00
Hornwitser 1c0db7e58a Fix crash on incorrect authentication token (#336)
Fix client.js exiting abruptly when the getID endpoint returns something
other than a string as the body, this happens on authentication failure.
2020-11-08 18:08:02 +01:00
Hornwitser 3d8cfef8bc Merge pull request #324 from Hornwitser/fix-log-spam
Fix 'Got no "ok"' log spam
2020-10-27 00:44:18 +01:00
Hornwitser 8803a737a5 Fix 'Got no "ok"' log spam
In 1bdf23d I incorrectly assumed that the response sent wouldn't matter
as it hadn't sent a response in ages.  That was not the case and the
client code checks to see if the response is "ok" while .sendStatus(200)
sends a response of "OK".  Fix log spam by doing a case insensitive
comparison of the response.
2020-10-23 15:58:56 +02:00
Hornwitser 320677194e Merge pull request #307 from mlyszczek/1.2.x
master.js: fix missing response for /api/getID
2020-08-27 11:05:01 +02:00
Michał Łyszczek 1bdf23d960 master.js: fix missing response for /api/getID
According to rfc2616#section-6:

> After receiving and interpreting a request message, a server
> responds with an HTTP response message.

That response was missing for /api/getID which could lead to
EMFILE, because needle module was waiting indefiniately for
response from server. Since client sends /api/getID once every 10
seconds, this piles up, ultimately reaching ulimit for opened file
descriptors.

Signed-off-by: Michał Łyszczek <michal.lyszczek@bofc.pl>
2020-08-27 04:05:52 +02:00
Hornwitser 7569c4a878 Release 1.2.4
Mark version 1.2.4 for release.
2019-12-08 00:54:48 +01:00
Arvids Godjuks 8ec7ec86d6 Merge pull request #266 from Hornwitser/fix-bcrypt-again
Fix bcrypt failing to install on Windows
2019-12-05 22:20:42 +01:00
Hornwitser 5fe98a0d60 Fix bcrypt failing to install on Windows
The maintainers of bcrypt have in their infinite wisdom decided to stop
distributing Windows binaries.  Their inner workings remain a mystery.
2019-12-05 19:28:16 +01:00
Arvids Godjuks 136bd24399 Merge pull request #262 from survaaki/1.2.x
Early config.uploadModsToMaster check so it does not do a bunch of checks
2019-11-25 23:10:59 +01:00
Survaaki c380fe269f Added changelog entry for uploadModsToMaster logging changes 2019-11-22 09:40:07 +00:00
survaaki 909e7d5ad6 early config.uploadModsToMaster check
Move check to start of function for config.uploadModsToMaster. When disabled, write one log line instead of one log line per mod.
2019-11-21 16:29:36 +10:00
Hornwitser 76cca59807 Merge pull request #260 from Hornwitser/various-fixes
Remove obsolete code and fix researchSync log spam
2019-11-20 16:43:07 +01:00
Hornwitser 3e7d4a8648 Remove obsolete flow statistics from clusterioMod
Remove the obsolete and broken statistics gather from the clusterioMod
plugin.  The statisticsExporter plugin already exports all of the
statistics (and more) that was exported here.
2019-11-20 03:07:32 +01:00
Hornwitser b0eb188e22 Fix researchSync updating researched technologies
Add check in filter_updated_techs that ignores already researched
technologies.
2019-11-20 02:56:01 +01:00
Hornwitser e70cfe7abd Remove broken combinator signaling
Remove the buggy and unstable combinator signaling implementation.
2019-11-19 20:58:15 +01:00
Hornwitser a9a3fb3fce Release 1.2.3
Mark version 1.2.3 for release.
2019-11-14 19:56:15 +01:00
Hornwitser 2b21bb36b6 Merge pull request #254 from Hornwitser/improve-windows-install-instructions
Update Windows install instructions
2019-11-14 19:18:20 +01:00
Hornwitser e1771ecb38 Update Windows install instructions
Remove the reference to the out of repo Windows install script and list
updated commands to install Clusterio stable in the readme itself.
2019-11-14 19:12:34 +01:00
Hornwitser 70f3ad5ebd Merge pull request #253 from Hornwitser/fix-hotpatch
Fix desync of the mods array in Hotpatch
2019-11-14 19:09:28 +01:00
Hornwitser 28b00ba5f6 Fix desync of the mods array in Hotpatch
When updating a mod Hotpatch updates the loaded_mods array but not the
global.mods array.  This causes events to be sent in a different order
on the client/server.  Fix by updating global.mods in install_mod.
2019-11-11 23:52:32 +01:00
Hornwitser 9d885d5f91 Merge pull request #252 from Hornwitser/fix-inventoryimports
Fix inventoryImports
2019-11-09 18:54:22 +01:00
Hornwitser d810b5646d Add fileReadDelay to inventoryImports config
Fix the inventoryImports client side code never receiving the script
output by specifying fileReadDelay and thereby getting the old
behaviour.  This broke with the introduction of the tail mode script
output mode which requires a newline to be written at the end of
the output.
2019-11-09 17:18:57 +01:00
Hornwitser 10414eb4a6 Fix inventoryImports command
Fix the inventory imports command trying to count up the removed quick
bar slots as well as using player_main instead of character_main.

Add warning that the lua commands file containing the unminified vesion
of the command has not been kept up to date.  There are multiple changes
that has been done previously so these have already diverged.
2019-11-09 17:15:36 +01:00
Hornwitser 3647ac7fc8 Merge pull request #249 from Hornwitser/fix-json-limit
Fix JSON payload limit being too low (1.2.x)
2019-10-26 18:09:53 +02:00
Hornwitser e75d9e967e Fix link in README.md
Fix the link to the master branch in README.md.
2019-10-26 17:45:25 +02:00
Hornwitser 25eb5e5d76 Increase limit of Json payloads to master server
The default limit of 100kb to JSON payloads of bodyParser can be
exceeded when running modded games with researchSync.  Increase the
limit to 10mb as a bandaid to fix this issue.
2019-10-26 17:45:02 +02:00
Hornwitser 3c5b108e4b Disable uploading of mods to master by default
Set uploadModsToMaster to false in config.json.dist.  This feature is
not necessary as the integrated mod portal in Factorio provides a better
method to synchronize mods.
2019-10-26 17:45:00 +02:00
Hornwitser 999c1b6c7a Release 1.2.2
Mark version 1.2.2 for release.
2019-10-13 14:53:31 +02:00
Hornwitser 61b1b1a106 Merge pull request #245 from Hornwitser/ease-installation
Ease installation
2019-10-13 14:48:30 +02:00
Hornwitser 8b4ec58459 Exit if masterAuthToken is missing
Exit client.js with a non-zero exit code if it's detected that the
masterAuthToken is missing in the config.
2019-10-12 23:39:40 +02:00
Hornwitser 6e26d32fed Recommend running without pm2 first
Recommend running master server without pm2 first as this makes
discovering any potential errors much simpler.
2019-10-12 23:36:43 +02:00
Hornwitser 74d9ec38dc Disable listening on SSL port by default
Set sslPort to 0 in the default config as this doesn't work properly in
1.2.x and also require elevated privileges to liste on.
2019-10-12 23:31:18 +02:00
Hornwitser dbef2bd2b3 Add -q option to wget
Avoid very confusing output of wget with it showing the progress for the
download at the same time as sudo asks for password input.
2019-10-12 23:25:20 +02:00
Arvids Godjuks e0b521d2fe Merge pull request #244 from Hornwitser/fix-node-install
Fix installation and instructions
2019-10-12 22:38:25 +02:00
Hornwitser 9a032aed2e Backport use Node.js version 10
Update instructions, Dockerfile and Travis CI to use Node.js 10.
Specify that Node.js version 10 or greater is needed in the engines
section of package.json (note that this is only advisory.)  And document
this as a breaking change in CHANGELOG.md.

This is a backport of 69a1ff7.
2019-10-12 22:15:53 +02:00
Hornwitser 72b56a00c7 Swap curl with wget in install instructions
Swap out curl with wget in the install instructions as wget is
pre-installed on Ubuntu.
2019-10-12 22:15:53 +02:00
Hornwitser c25bf23286 Reorder install instructions
Reorder the install pm2 step to be after the npm install step in the
instructions.  This is necessary to avoid npm creating some files in the
home directory owned as root and then failing the npm install step
because of this.
2019-10-12 22:15:53 +02:00
Hornwitser 9e983aff84 Reword development warning
Reword the warning at the start of the readme to be less intimidating,
as this is the correct version of the project that users are expected to
install.
2019-10-12 22:15:53 +02:00
Hornwitser 8973dcc6dd Fix bcrypt install failing on node v10
Update bcrypt to version 3.0.6, as versions below 3 are not supported on
node v10, and versions prior to 3.0.6 are not supported on node v12.
2019-10-12 22:15:53 +02:00
Arvids Godjuks 475128c8c4 Merge pull request #243 from Hornwitser/fix-researchsync
Try to fix researchSync
2019-10-12 21:48:09 +02:00
Hornwitser 0cdb5d0ce4 Fix crash in recount_cluster_research_progress
Check that the relevant research is present in the research mapping
before accessing it in recount_cluster_research_progress.
2019-10-12 17:07:08 +02:00
Hornwitser 075d54f708 Filter out lower levels when syncing research
Fix the next level of an infinite research getting updated with the
progress of the previous level from other nodes in the cluster by
filtering out infinite researches with a lower level than the local
level.
2019-10-12 17:07:08 +02:00
Hornwitser 62f3a99feb Set progress when completing infinite researches
Set the progress for the next level of an infinite research when
unlocking it via a script.  This is necessary when the research is
actively being researched  as Factorio will carry over the progress for
the current level into the next one when set via a script.

It's possible for the new progress to be non-zero if the next level was
on the research queue.
2019-10-12 17:07:08 +02:00
Hornwitser 3b1ed1a667 Rename research to tech
Use tech as the loop variable for researches as this is consistent with
the rest of the researchSync module.
2019-10-12 17:07:08 +02:00
Hornwitser 6a6274e85c Fix incorrect property checks in researchSync
Fix naive checks for properties being set of the form obj[key] into the
proper form Object.hasOwnProperty.call(obj, key).  The reason for this
is that if the key has a value of a built-in property like 'constructor'
then a check of the form obj[key] will always return true even if that
property hasn't been set on the object.
2019-10-12 17:07:07 +02:00
Arvids Godjuks a9cc743106 Merge pull request #240 from Hornwitser/release-1.2.1
Release 1.2.1
2019-09-20 14:18:52 +02:00
Hornwitser 2a7758f60d Release version 1.2.1
This release incorporates some minor bugfixes and a security fix for
an unauthenticated api in the master server.  See CHANGELOG.md for
details.
2019-09-18 18:20:00 +02:00
Hornwitser 7e2ca58a59 Fix license in package.json
Correct license entry being ISC instead of MIT in package.json.
2019-09-18 18:17:38 +02:00
Arvids Godjuks 718d4ed659 Merge pull request #239 from Hornwitser/fix-sigterm
Spawn factorio server deteached in client.js
2019-09-18 18:14:13 +02:00
Hornwitser 6b090cddbe Spawn factorio server deteached in client.js
Sending ^C via the terminal on Linux may cause SIGINT to be sent twice
to the factorio process, causing it to exit without completing the
saving.  This happens because the shell sends the interupt to all
members of the process group for the job and this includes both the
node client process and the factorio server process.

Avoid this situation by passing detached to spawn when the platform is
Linux, which causes the process to be created with its own process group
and therefore not targeted by a SIGINT from the shell.

This unfortunately does not work on Windows as we can't send SIGINT or
^C to a process there.  Doing subprocces.kill on Windows terminates the
process similarly to how SIGKILL works on Linux.
2019-09-18 18:11:49 +02:00
Arvids Godjuks 56c023f643 Merge pull request #232 from Hornwitser/update-node-factroio-api
Update node-factorio-api
2019-09-12 17:19:07 +02:00
Hornwitser 78de7236c5 Update node-factorio-api to 0.3.8
Fixes #229

Note that the versions 0.3.6 and 0.3.7 had issues with an old
mod_cache.json being present.
2019-09-12 16:52:44 +02:00
Arvids Godjuks 97c7a69c7a Merge pull request #230 from Hornwitser/auth-1.2.x
Add authentication to socket.io server
2019-09-11 15:19:18 +02:00
Hornwitser 0c45e57732 Add authentication to socket.io server
Authenticate connections made to the socket.io server via a token query
parameter that's validated in a similar manner to the x-access-token for
HTTP requests.
2019-09-10 23:51:48 +02:00
Arvids Godjuks 88eff17dfd Updated README.md 2019-09-04 16:20:50 +02:00
Daniel e1374d501e Use correct version in package.json 2019-08-30 18:06:42 +02:00
Godmave 3f2bd2653e #update to new hotpatch (#203)
- fixes freeplay scenario
- fixes that clicking the admin gui border kills the server
2019-08-10 11:31:03 +02:00
Godmave 5bbd879f34 Merge pull request #199 from Hornwitser/fix-getinstances
Fix broken require in lib/getInstances.js
2019-08-09 11:36:48 +02:00
Hornwitser 81aa2141b9 Fix broken require in lib/getInstances.js
The require change in f76c84c for lib/getInstances.js accidentally left
out the lib/ prefix causing it to break.
2019-08-09 11:06:48 +02:00
Arvids Godjuks 9c108dd4dd Merge pull request #197 from Hornwitser/remove-remotemap
Remove outdated remoteMap
2019-08-07 17:55:25 +03:00
Hornwitser 6415784e8a Remove outdated remoteMap
Remove the old and outdated remoteMap plugin.  Also removes the
directory for linvodb in databases and the troubleshooting step related
to linvodb.
2019-08-07 16:03:51 +02:00
Arvids Godjuks 206f3db042 Merge pull request #195 from Hornwitser/cover-fix
Fix the coverage and test scripts
2019-08-07 16:42:00 +03:00
Hornwitser fb28693dec Disable remoteMap tests
Disable the tests for the old and outdated remoteMap plugin as it breaks
the whole testing harness due to the linvodb3 dependency being missing.
2019-08-07 15:23:25 +02:00
Hornwitser 79ff52aa20 Replace istanbul with nyc
Use nyc instead of istanbul as the later has been deprecated and is no
longer being developed.  Fix the cover and test scripts to work again
with nyc and add the output dir for nyc into .gitignore.
2019-08-07 15:21:30 +02:00
Arvids Godjuks cb4616f47c Merge pull request #194 from Hornwitser/slim
Removing unused or excessive dependencies
2019-08-07 14:20:56 +03:00
Hornwitser bc7f3804c8 Remove right-pad
Remove unused dependency right-pad.
2019-08-07 10:18:14 +02:00
Hornwitser a452749caa Remove app-root-path
Resolve the relative path using path.relative instead of some hacky
string substitutions.
2019-08-07 10:18:14 +02:00
Hornwitser 1e8fb85122 Remove bcryptjs
Remove unused dependency bcryptjs.
2019-08-07 10:18:14 +02:00
Hornwitser 66c0238a3d Remove undescore
A 50 kb library is not needed to make a function run once, especially
not when it doesn't work.  _.once does not modify the function passed to
it to only run once it returns a new function that only runs once.
Since the result of once was discarded, it had no effect.

Remove underscore and implement instanceManagement running once properly
with 3 lines of code.
2019-08-07 10:18:14 +02:00
Hornwitser ca0281ae58 Remove pointless micro-optimization
This saved 30ms of the startup on my machine.  Even if it saved a second
it's still pretty pointless as we're not depending on the master server
startup being super fast.
2019-08-07 10:18:14 +02:00
Hornwitser 200c8dbdeb Use --only=production on npm install
Add --only=production flag to Dockerfile and the README entries
describing how to install nodes.  This will avoid pulling in a lot of
dependencies that are not needed for running master/slaves.
2019-08-07 10:18:14 +02:00
Hornwitser d0afcbf0ac Move html5-validator to devDependencies
html5-validator is only used for testing the master server web interface
and does not need to be included on production installs.
2019-08-07 10:18:14 +02:00
Hornwitser 13a069a798 Reimplement hashFile locally
Replace the 10 MB of dependencies that is hash-files with a homebrew
Promise based implementation of hashFile that uses crypto and fs.  This
somehow also ends up being the same amount of code as the one using
hash-files.
2019-08-07 10:18:14 +02:00
Hornwitser 0e849488e2 Remove snyk
It's not supposed to be installed as a dependency in the first place,
and this seems like the wrong solution to there being too many stinking
dependencies to keep track the security of.
2019-08-07 10:18:14 +02:00
Hornwitser 6264bd9888 Update node-factorio-api
The 0.3.5 release of node-factorio-api removed the dependency to
documentation, which itself pulls in another 500 dependencies.  Update
to 0.3.5 to avoid having this pulled in.
2019-08-07 10:18:14 +02:00
Hornwitser cbd1939a74 Stop pretending there's documentation
Remove docs scripts, documentation.yml and the documentation dependency.
2019-08-07 10:18:14 +02:00
Godmave 7cf9319624 Merge branch 'master' into master 2019-08-06 19:52:41 +02:00
Godmave de2141ec50 make masterPlugins visible to other things again 2019-08-06 19:45:18 +02:00
Godmave 0df1b8e1fd Add tailing of files and move socketio connect after plugin load (#191)
#add tailing of files to prevent lost data between factorio and clusterio
#move socketio connect to the bottom after the plugins are loaded 
  to prevent plugins not getting the connection event
#add some more transfer compression in different places
2019-08-05 07:47:57 +02:00
Godmave ec0243d7cf #resolve merge conflict 2019-08-04 17:54:58 +02:00
Godmave f32624586b #resolve merge conflict 2019-08-04 17:51:09 +02:00
Godmave 6ea47cb6af Merge branch 'master' into master 2019-08-04 17:47:32 +02:00
Godmave a204163a6c #replace stray whitespaces with tabs again 2019-08-04 17:26:50 +02:00
Hornwitser f76c84c3f1 Move node_modules/_app/ into lib/ as a package (#190)
Move the contents of node_modules/_app into lib/ as there shouldn't be
any content in node_modules.  This directory is automatically managed
and pruned by npm.

Add lib/ as a local package in package.json, this allows scripts
everywhere to require it's content as "lib/path" instead of relying on
local paths.  This shadows the StdLib Node.js Bindings package, but I
could care less.

Change all requires with lib as a local path and _app to use just lib.
2019-08-04 15:52:36 +02:00
Godmave 6caef9bda9 #add some more compression
#add tailing of files to prevent lost data between factorio and clusterio
#move socketio connect to the bottom after the plugins are loaded to prevent plugins not getting the connection event
2019-08-04 15:45:17 +02:00
Godmave 30cda4de3b Merge pull request #11 from Danielv123/master
merge
2019-08-04 15:43:09 +02:00
Hornwitser a13d4c6805 Use published version of rcon-client-fork (#188)
Switch to a version of rcon-client-fork that's published on npm.  Avoids
having to put build files in the git repo.
2019-07-30 16:28:43 +03:00
Godmave 56b805e19b Merge pull request #187 from Zeroto/master
Switch to rcon-client git dep with dep build output in it
2019-07-30 14:06:34 +02:00
Sander Homan 5fd3a6b188 Switch to rcon-client git dep with dep build output in it 2019-07-30 14:03:56 +02:00
psihius 9d1e60aa03 Remove Doomsday scenario, should be reworked as dynamic plugin. See doomsday-save-point for scenario code 2019-07-30 11:49:24 +02:00
Godmave d819644b01 Merge pull request #186 from Zeroto/master
Fix notifying about the same research being finished
2019-07-30 08:12:21 +02:00
Sander Homan 042230dc2c Fix notifying about the same research being finished
This is just a bandaid to fix a symptom. The underlying cause is still there, but at least it shouldn't spam the players anymore.
2019-07-29 20:31:42 +02:00
Hornwitser a00183e94c Switch to rcon-client fork (#183)
Switch to using fork of rcon-client where the commands and the responses
are not converted to ASCII before being sent over the wire.  Should fix
the issue with Russian text showing up as garbled letters in the chat as
well as the issue with train schedules using >= and <= conditions.

If rcon-client upstream is updated with the utf8 patch this commit can
be reverted with the version the patch was included in.
2019-07-28 14:42:44 +02:00
Arvids Godjuks c95d9f9464 Added "Doomsday" scenario (#184)
* Added doomsday scenario
2019-07-27 22:59:23 +03:00
Danielv123 a1237d8cfd Added CLI script for deleting players from playerManager 2019-07-26 23:55:33 +02:00
Godmave 35101a7e32 Merge pull request #10 from Danielv123/master
sync
2019-07-26 18:55:46 +02:00
Godmave 95d06293f5 Merge pull request #181 from Hornwitser/patch-1
Fix wrong argument order
2019-07-26 18:53:58 +02:00
Hornwitser 2714b09e5d Fix wrong argument order
Fix compression not applying because the order of the callback and
options arguments were swapped in the call to Needle.get for the
request_cluster_data function.
2019-07-26 18:51:16 +02:00
Godmave e733cfde03 Merge pull request #180 from Godmave/master
add compression to research sync
2019-07-25 23:23:57 +02:00
Godmave 008ea139c6 #add compression 2019-07-25 23:22:46 +02:00
psihius 3338c439dd Empty meta fix for querying new slave check 2019-07-25 22:20:34 +02:00
psihius b956438d8d INVALID REQUEST is not a valid json key for a variable, end up being "undefined" 2019-07-25 22:08:33 +02:00
Godmave cdbbdd41c7 Merge pull request #9 from Danielv123/master
sync
2019-07-23 20:34:51 +02:00
Master-Guy e2775adcc3 /api/rconPasswords fix & /api/slaves security fix (#179)
Fix for /api/slaves to not send rconPasswords
Fix for /api/rconPasswords to work properly and send partial information
2019-07-20 15:52:47 +02:00
Master-Guy d909b4aa47 Fix for /issues/177 (#178)
/api/editSlaveMeta: Removed unneeded rcon check
/api/getSlaveMeta: Removed useless password check
/api/checkMod: Added authenticate.middleware
/api/slaves: Renamed to /api/rconPasswords and changed to authenticate.middleware
/api/runCommand: Changed to authenticate.middleware
2019-07-20 14:33:54 +02:00
Master-Guy 49611a2062 /api/slaves POST (optimized) (#176)
* Adds a /api/slaves POST call that requires authentication. It also returns the rconPasswords, unlike the GET call.
2019-07-19 15:37:49 +03:00
AreYouScared ee6ab6ab72 Remove GPS pings in crosschat (#172)
* Remove GPS pings in crosschat

Cross-server gps pings are annoying, lets fix that

* Lets just do it the right way <3

Should be better

* Take 2

Let's try that again
2019-07-19 12:05:03 +02:00
Arvids Godjuks de92ca96b9 Merge pull request #173 from Master-Guy/master
Add "info" field between client and master
2019-07-18 18:15:37 +03:00
Arvids Godjuks 77281d0662 Merge pull request #174 from MrRozdor/log-research-sync
Add logging to researchSync plugin
2019-07-18 16:25:27 +03:00
TechnoStrife 2831c07000 Add logging to researchSync plugin 2019-07-17 23:21:57 +03:00
Master-Guy 6ac8c0711f JSON fix
Small fix for JSON
2019-07-17 19:23:53 +02:00
Master-Guy c9cbc48d95 2/2 Add "info" field between client and master
Adds a "info" field to config.json and allows the client to send the contents of this info field to the master, so it also shows up (i.e. with /api/slaves)
2019-07-17 18:40:01 +02:00
Master-Guy 50370bf858 1/2 Add "info" field between client and master
Adds a "info" field to config.json and allows the client to send the contents of this info field to the master, so it also shows up (i.e. with /api/slaves)
2019-07-17 18:35:02 +02:00
Godmave d1fd4c3565 Merge pull request #171 from MCRcortex/patch-1
added res.sendStatus(200); to stop client errors throwing
2019-07-17 14:07:14 +02:00
MCRcortex 8672afe940 added res.sendStatus(200); to stop client errors throwing
according to the docs
"The methods on the response object (res) in the following table can send a response to the client, and terminate the request-response cycle. If none of these methods are called from a route handler, the client request will be left hanging."
Last sentence is the critical one.
Sending a status code 200 should fix the issue
2019-07-17 22:04:29 +10:00
Godmave adf6763a0d Merge pull request #170 from MrRozdor/fix-researchsync
Fix researchSync that it counts progress only from techs on same level
2019-07-17 07:16:48 +02:00
TechnoStrife d48a824a69 Fix researchSync that it counts progress only from techs on same level 2019-07-17 00:31:15 +03:00
Godmave f28b73cf31 Merge pull request #169 from MrRozdor/spread_researching
Fix for migation to new researchSync version
2019-07-15 21:50:38 +02:00
TechnoStrife fec3cf5ddf Fix for migation to new researchSync version 2019-07-15 22:49:39 +03:00
Godmave c7cfb8ce70 Merge pull request #168 from MrRozdor/spread_researching
Make any node be able to research anything
2019-07-15 21:27:49 +02:00
TechnoStrife 93c210f0c7 Merge branch 'master' into spread_researching
# Conflicts:
#	sharedPlugins/researchSync/dumpResearch.lua
#	sharedPlugins/researchSync/index.js
2019-07-15 22:14:09 +03:00
TechnoStrife 251ec696a3 Fixes && debugging 2019-07-15 18:29:53 +03:00
Godmave 773a2c412e Merge pull request #167 from veladon/pr/3
fix for stats exporter console errors
2019-07-15 15:05:21 +02:00
veladon c0499e7f27 Merge branch 'master' into pr/3 2019-07-15 13:57:27 +01:00
veladon 0a65946bf4 fix for /api/logstats 2019-07-15 13:47:53 +01:00
veladon 233410add7 fix for stats exporter console errors (#166) 2019-07-15 13:49:49 +02:00
veladon fea9fb28ca Merge branch 'master' of https://github.com/veladon/factorioClusterio 2019-07-15 12:46:45 +01:00
veladon e1b510b441 fix for stats exporter console errors 2019-07-15 12:46:38 +01:00
veladon 34301eb381 Merge pull request #2 from Danielv123/master
merge
2019-07-15 12:44:43 +01:00
TechnoStrife 4a0956d174 Fix a bug causing all slaves data gets cleared on master startup
Cherry-picked from fix-slaves-data-cleanup-on-startup
2019-07-15 11:39:11 +03:00
TechnoStrife 3b814eeea4 Fix a bug causing all slaves data gets cleared on master startup (#165) 2019-07-15 10:34:02 +02:00
TechnoStrife 98c6795f8f Make researchSync work for any research on any node 2019-07-15 00:46:21 +03:00
Godmave 21dd80df9b Merge pull request #8 from Danielv123/master
sync
2019-07-14 20:54:46 +02:00
Arvids Godjuks 0b34f07954 Dump only enabled research (#164) 2019-07-14 17:48:20 +02:00
Daniel c978e8e748 Merge pull request #163 from psihius/master
Mass sync does not spam
2019-07-14 15:58:07 +02:00
psihius 71870daaba Made so mass tech sync does not make sounds or messages, only notifies at the end 2019-07-14 15:00:55 +02:00
Daniel 5a60f317ba Merge pull request #162 from psihius/master
Update to research sync plugin
2019-07-14 14:06:24 +02:00
psihius b666a909ab Formatting and logging adjustments 2019-07-14 01:32:34 +02:00
psihius 0afe35b67c Updated research sync to handle infinite science and hopefully stop it from apply same research in a loop annoying everyone 2019-07-14 01:25:48 +02:00
veladon 096ead5bc4 Merge pull request #1 from Danielv123/master
merge
2019-07-13 18:14:51 +01:00
TechnoStrife fa4c10dce2 Code refactor 2019-07-12 18:52:47 +03:00
Daniel Vestøl 1f8ac50950 Better error message for statisticsExporter 2019-07-09 22:50:28 +02:00
Godmave 10d5b0da9b Merge pull request #7 from Danielv123/master
pull updates
2019-07-06 19:57:53 +02:00
Daniel Vestøl 6c32271c56 Fixed rcon timeout in the right place, set to 200s for 1250kB commands 2019-07-06 12:44:31 +02:00
Daniel Vestøl a122f2b09e Increased rcon timeout to 20s, increased maxPending to 5 2019-07-06 00:46:22 +02:00
Daniel Vestøl f35be2f355 Set max pending rcon requests to 1 2019-07-05 23:42:51 +02:00
Daniel Vestøl d66d22a9cd Updated rcon-client 2019-07-05 23:07:25 +02:00
Godmave 7d6a5bb6d2 Merge pull request #6 from Danielv123/master
getting up to date
2019-06-24 18:51:21 +02:00
Daniel bf2e78d530 Merge pull request #159 from veladon/master
Added cluster model js to /lib/
2019-06-10 14:11:04 -07:00
veladon bf4b7fe1b8 Added cluster model js to /lib/ 2019-06-10 21:40:31 +01:00
Daniel 66ba706c56 Use neural net dole divider by default on new installs
See "useNeuralNetDoleDivider": true,

If this causes issues, simply set it to false in config.json. If you find normal dole division to work badly as well, set disableFairItemDistribution to true.
2019-06-02 15:27:50 +02:00
Daniel 79c9ac839e Merge pull request #158 from axapaxa123/master
New NN, added debt system
2019-06-02 06:26:11 -07:00
Daniel 7f59b200b1 Merge pull request #153 from veladon/master
player to character inventory renames and extra info for EACCESS problem
2019-06-02 06:24:23 -07:00
axapaxa123 d719a73328 New NN, added debt system 2019-06-02 15:13:49 +02:00
axapaxa123 616bb9cc8f Merge pull request #1 from Danielv123/master
Merge pull request #152 from axapaxa123/master
2019-06-02 15:04:32 +02:00
veladon 3ae55cbd27 Updated defines.inventory.player_<x> to defines.inventory.character_<x> as per 0.17.35 change to rename character entity type from "player" to "character". 2019-05-13 20:41:15 +01:00
Daniel 7ae9ff6649 Merge pull request #152 from axapaxa123/master
New NN, updated required data
2019-05-13 10:42:53 -07:00
axapaxa123 0b3007d22d New NN, updated required data 2019-05-13 19:22:55 +02:00
veladon bd7a116860 Added extra instructions to readme for the EACCESS common problem 2019-05-12 18:10:44 +01:00
Daniel Vestøl 0c9ed582cc Fixed scenario_loader 2019-04-22 13:45:09 +02:00
120 changed files with 1950 additions and 1371 deletions

No files matched your search

+4 -3
View File
@@ -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
@@ -25,7 +26,6 @@ build/Release
dist/ dist/
node_modules/* node_modules/*
!node_modules/_app
Factorio* Factorio*
/factorio /factorio
@@ -63,4 +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/
-45
View File
@@ -1,45 +0,0 @@
# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
version: v1.13.3
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'
'npm:hawk:20160119':
- node-factorio-api > documentation > highlight.js > gear-lib > less > request > hawk:
patched: '2019-02-25T06:48:21.845Z'
'npm:http-signature:20150122':
- node-factorio-api > documentation > highlight.js > gear-lib > less > request > http-signature:
patched: '2019-02-25T06:48:21.845Z'
'npm:mime:20170907':
- node-factorio-api > documentation > highlight.js > gear-lib > mime:
patched: '2019-02-25T06:48:21.845Z'
- node-factorio-api > documentation > highlight.js > gear-lib > less > mime:
patched: '2019-02-25T06:48:21.845Z'
- node-factorio-api > documentation > highlight.js > gear-lib > less > request > form-data > mime:
patched: '2019-02-25T06:48:21.845Z'
'npm:minimatch:20160620':
- node-factorio-api > documentation > highlight.js > gear > liftoff > findup-sync > glob > minimatch:
patched: '2019-02-25T06:48:21.845Z'
- node-factorio-api > documentation > highlight.js > gear-lib > gear > liftoff > findup-sync > glob > minimatch:
patched: '2019-02-25T06:48:21.845Z'
- node-factorio-api > documentation > highlight.js > gear-lib > glob > minimatch:
patched: '2019-02-25T06:48:21.845Z'
- node-factorio-api > documentation > highlight.js > gear-lib > jslint > glob > minimatch:
patched: '2019-02-25T06:48:21.845Z'
- node-factorio-api > documentation > highlight.js > gear-lib > jshint > cli > glob > minimatch:
patched: '2019-02-25T06:48:21.845Z'
- node-factorio-api > documentation > highlight.js > gear-lib > jshint > minimatch:
patched: '2019-02-25T06:48:21.845Z'
'npm:request:20160119':
- node-factorio-api > documentation > highlight.js > gear-lib > less > request:
patched: '2019-02-25T06:48:21.845Z'
'npm:tunnel-agent:20170305':
- node-factorio-api > documentation > highlight.js > gear-lib > less > request > tunnel-agent:
patched: '2019-02-25T06:48:21.845Z'
'npm:uglify-js:20151024':
- node-factorio-api > documentation > highlight.js > gear-lib > handlebars > uglify-js:
patched: '2019-02-25T06:48:21.845Z'
- node-factorio-api > documentation > highlight.js > gear-lib > uglify-js:
patched: '2019-02-25T06:48:21.845Z'
-10
View File
@@ -1,10 +0,0 @@
language: node_js
node_js:
- "9"
before_install:
- sudo apt-get -qq update
- sudo apt-get install -y curl git tar
- curl -o factorio.tar.gz -L https://www.factorio.com/get-download/latest/headless/linux64
- tar -xvf factorio.tar.gz
script:
- npm run cover
+71
View File
@@ -0,0 +1,71 @@
Changelog
=========
Version 1.2.4
-------------
- Removed broken remote combinator signaling.
- Fixed research sync endlessly updating already researched technologies
- Removed obsolete item/fluid statistics from clusterioMod
- Removed per mod upload logging when config.uploadModsToMaster disabled
- Fixed bcrypt failing to install on windows due to the new version not
having Windows binaries.
Version 1.2.3
-------------
- Disabled uploading mods to the master server by default as this is mostly
just a waste of bandwidth with the mod portal being integrated into the
game now.
- Fixed researchSync breaking with heavily modded games due to the tech tree
exceeding the default 100kb limit on JSON payloads.
- Fixed the command inventoryImport sends referencing player instead of
character, and trying to count the non-existant quick bar slots.
- Fixed inventoryImport never receiving the script output after the default
mode for reading script output changed to tail mode.
- Fixed desync caused by the mods and loaded mods arrays in Hotpatch getting
out of sync when plugins update their scenario mod code. To fix existing
games you will need copy lib/scenarios/Hotpatch/hotpatch/mod-tools.lua
over the existing mod-tools.lua in the save's hotpatch folder.
- Updated the Windows install instructions.
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
+19
View File
@@ -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)
});
+2 -2
View File
@@ -1,8 +1,8 @@
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
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 && curl -o factorio.tar.gz -L https://www.factorio.com/get-download/latest/headless/linux64 && tar -xf factorio.tar.gz
WORKDIR factorioClusterio WORKDIR factorioClusterio
+54 -34
View File
@@ -8,6 +8,12 @@
Discord for development/support/play: https://discord.gg/5XuDkje Discord for development/support/play: https://discord.gg/5XuDkje
# ABANDONED VERSION
**The 1.2.x version of Clusterio has been abandonded.
Please go to the [main branch of the project](https://github.com/clusterio/clusterio) and continue from there.
The information below is out of date and kept for archival purposes only.**
### 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
@@ -88,19 +94,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 +121,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 +168,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/
@@ -167,28 +176,47 @@ reboot when you are done, then proceed to the next steps. *reboots matter*
**Master** **Master**
1. download and run https://puu.sh/toFHl/01eebbb333.bat 1. Open PowerShell or Command prompt in the directory you want to install to and run the following commands.
2. Copy config.json.dist to config.json git clone -b 1.2.x https://github.com/clusterio/factorioClusterio
cd factorioClusterio
npm install --only=production
copy config.json.dist config.json
3. Follow the instructions in the bat file 2. Obtain Factorio by either of these two methods:
3. Some of the instructions are outdated. If you get stuck somewhere, look at the Ubuntu section.
4. Run `node client.js manage shared mods add clusterio` - Via the stand alone version on from their website
5. type `node master.js` to start the server 1. Download the MS Windows (64-bit zip package) from https://www.factorio.com/download
2. Open the zip file and drag the folder called "Factorio_0.17.x" into the factorioClusterio folder
3. Rename the folder to "factorio"
- Via steam installation
1. Locate the game files by right clicking the game in steam, selecting properties, then Local Files, then Browse local files.
2. Go to the parent folder of the folder that Steam opened and copy the Factorio folder into the factorioClusterio folder
3. Rename the folder to "factorio"
3. Open `config.json` with a text editor and configure as desired.
4. Run `node master.js` to generate the athentication token into secret-api-token.txt
5. Run `node master.js` again to start the master server.
**Server Host** **Server Host**
1. download and run https://puu.sh/toFHl/01eebbb333.bat 1. Do step 1 and 2 of the Master section above *OR* use the same folder that was created in that section.
2. Copy config.json.dist to config.json 2. Open `config.json` with a text editor and configure as desired. You will need to set masterAuthToken to string found in secret-api-token.txt on the master server.
3. Follow the instructions given. 3. Optionally run the command `node client.js manage shared mods add clusterio` to add the clusterio mod (needed for item teleports.)
3.5 Some of the instructions are outdated. If you get stuck somewhere, look at the Ubuntu section. 4. Run `node client.js start [instancename]` to create a new instance. Repeat it again to start the instance.
4. Type `node client.js start [instancename]` to create a new instance.
To connect to a master server running on a remote machine, open config.json with your favourite text editor (notepad++). You can also set it up to use the official server browser. To connect to a master server running on a remote machine, open config.json with your favourite text editor (notepad++). You can also set it up to use the official server browser.
@@ -226,17 +254,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 +299,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
+116 -111
View File
@@ -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"){
@@ -296,11 +299,10 @@ if (!command || command == "help" || command == "--help") {
fs.mkdirSync(instancedirectory + "/saves/"); fs.mkdirSync(instancedirectory + "/saves/");
fs.writeFileSync(instancedirectory + "/script-output/output.txt", ""); fs.writeFileSync(instancedirectory + "/script-output/output.txt", "");
fs.writeFileSync(instancedirectory + "/script-output/orders.txt", ""); fs.writeFileSync(instancedirectory + "/script-output/orders.txt", "");
fs.writeFileSync(instancedirectory + "/script-output/txbuffer.txt", "");
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 +319,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 +360,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 +473,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 +500,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 +514,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 +565,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 +586,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 +603,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 +629,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 +694,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 {
@@ -691,7 +716,7 @@ async function instanceManagement(instanceconfig) {
console.error(err); console.error(err);
} else if (response && response.body) { } else if (response && response.body) {
// In the future we might be interested in whether or not we actually manage to send it, but honestly I don't care. // In the future we might be interested in whether or not we actually manage to send it, but honestly I don't care.
if(response.body !== "ok") { if (typeof response.body !== "string" || response.body.toLowerCase() !== "ok") {
console.log("Got no \"ok\" while registering our precense with master "+config.masterIP+" at " + payload.time); console.log("Got no \"ok\" while registering our precense with master "+config.masterIP+" at " + payload.time);
console.log(response.body); console.log(response.body);
} }
@@ -712,19 +737,19 @@ async function instanceManagement(instanceconfig) {
setTimeout(function(){hashMods(instance, uploadMods)}, 5000); setTimeout(function(){hashMods(instance, uploadMods)}, 5000);
function uploadMods(modHashes) { function uploadMods(modHashes) {
// [{modName:string,hash:string}, ... ] if (config.uploadModsToMaster) {
for(let i=0;i<modHashes.length;i++){ // [{modName:string,hash:string}, ... ]
let payload = { for(let i=0;i<modHashes.length;i++){
modName: modHashes[i].modName, let payload = {
hash: modHashes[i].hash, modName: modHashes[i].modName,
} hash: modHashes[i].hash,
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."); needle.post(config.masterIP + ":" + config.masterPort + '/api/checkMod', payload, needleOptionsWithTokenAuthHeader, function (err, response, body) {
if(response && body && body == "found") { if(err) console.error("Unable to contact master server /api/checkMod! Please check your config.json.");
console.log("master has mod "+modHashes[i].modName); if(response && body && body == "found") {
} else if (response && body && typeof body == "string") { console.log("master has mod "+modHashes[i].modName);
let mod = response.body; } else if (response && body && typeof body == "string") {
if(config.uploadModsToMaster){ let mod = response.body;
console.log("Sending mod: " + mod); console.log("Sending mod: " + mod);
// Send mods master says it wants // Send mods master says it wants
// response.body is a string which is a modName.zip // response.body is a string which is a modName.zip
@@ -741,11 +766,11 @@ async function instanceManagement(instanceconfig) {
}); });
var form = req.form(); var form = req.form();
form.append('file', fs.createReadStream(config.instanceDirectory+"/"+instance+"/mods/"+mod)); form.append('file', fs.createReadStream(config.instanceDirectory+"/"+instance+"/mods/"+mod));
} else {
console.log("Not sending mod: " + mod + " to master because config.uploadModsToMaster is not enabled")
} }
} });
}); }
} else {
console.log("Bypassing sending mods to master because config.uploadModsToMaster is not enabled")
} }
} }
} // END OF INSTANCE START --------------------------------------------------------------------- } // END OF INSTANCE START ---------------------------------------------------------------------
@@ -756,42 +781,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 -3
View File
@@ -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",
@@ -49,8 +49,8 @@
"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.", "__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": false, "useNeuralNetDoleDivider": true,
"uploadModsToMaster": true, "uploadModsToMaster": false,
"msBetweenCommands": 10, "msBetweenCommands": 10,
"allowRemoteCommandExecution": true, "allowRemoteCommandExecution": true,
"enableCrossServerShout": true, "enableCrossServerShout": true,
-10
View File
@@ -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
+57
View File
@@ -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
+112
View File
@@ -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
+8
View File
@@ -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
+177
View File
@@ -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
+10
View File
@@ -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
+3
View File
@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=IApiSlaves.js.map
+3
View File
@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=IClusterioMasterProxy.js.map
+27
View File
@@ -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
+24
View File
@@ -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
+61
View File
@@ -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
+29
View File
@@ -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
+57
View File
@@ -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
+16
View File
@@ -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
+8
View File
@@ -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
+64
View File
@@ -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
+10
View File
@@ -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
+10
View File
@@ -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
+13
View File
@@ -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.
@@ -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.
+36 -35
View File
@@ -33,12 +33,14 @@ function sigmoid3(x)
//carry - carry for item X slave Y //carry - carry for item X slave Y
//prev_req - Numreq for previous request 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 //* 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 //Returns: Array of 3 elements
//0.Number of items to give in that dose //0.Number of items to give in that dose
//1.New dole for item X //1.New dole for item X
//2.New carry for item X slave Y //2.New carry for item X slave Y
function Dose(numreq,instore,store_last_tick,dole,carry,prev_req,numreq_total_adj) //3.New debt
function Dose(numreq,instore,store_last_tick,dole,carry,prev_req,numreq_total_adj,debt)
{ {
numreq=Number(numreq); numreq=Number(numreq);
instore=Number(instore); instore=Number(instore);
@@ -47,32 +49,37 @@ function Dose(numreq,instore,store_last_tick,dole,carry,prev_req,numreq_total_ad
var b=Math.trunc(prev_req/instore_adj*numreq); var b=Math.trunc(prev_req/instore_adj*numreq);
var inp1=[numreq,instore_adj,store_last_tick/numreq,dole,carry];//0..4 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];//5..9 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 inputs=inp1.concat(inp2);
var internal=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]; var internal=new Array(20).fill(0);
var outputs=[0,0,0]; var outputs=[0,0,0,0];
//NN: Please don't touch :), code is generated //NN: Please don't touch :), code is generated
outputs[0]=inputs[2]*3.101320028; outputs[0]=inputs[7]*0.8058582544;
outputs[0]=outputs[0]+inputs[7]*0.5471593142; outputs[0]=sigmoid2(outputs[0]);
outputs[0]=outputs[0]+inputs[3]*2.691268921;
outputs[0]=outputs[0]+outputs[1]*-1.64255929;
outputs[0]=outputs[0]+inputs[6]*0.7746122479;
outputs[0]=sigmoid0(outputs[0]);
outputs[1]=inputs[2]*-0.9946503043;
outputs[1]=outputs[1]+inputs[1]*0.2128598392;
outputs[1]=sigmoid0(outputs[1]); outputs[1]=sigmoid0(outputs[1]);
outputs[2]=sigmoid2(outputs[2]); outputs[2]=sigmoid0(outputs[2]);
outputs[3]=inputs[6]*0.2294657379;
outputs[3]=sigmoid0(outputs[3]);
outputs[0]=Number(outputs[0]); debt=debt || 0;
if (outputs[0]<0) outputs[0]=0; if (outputs[0]>0.99) outputs[0]=1.0;
outputs[0]=outputs[0]*numreq;
var res1=Math.round(outputs[0]); var res1=Number(outputs[0])*numreq+outputs[3]+debt;
if (res1>numreq) res1=numreq; var res_capped=Math.round(res1);
if (res1>instore) res1=instore;
if (res1<0) res1=0; if (res_capped>numreq) res_capped=numreq;
carry=sigmoid0(outputs[2]); if (res_capped>instore) res_capped=instore;
return [res1,sigmoid0(outputs[1]),carry]; 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 //TICK - Updates values for item X, should be called once for each item for each round of all slaves requesting that item
@@ -90,22 +97,16 @@ function Tick(instore,dole,store_last_tick,numreq_total_adj)
{ {
var instore_adj=instore; var instore_adj=instore;
if (instore_adj==0) instore_adj=0.1; if (instore_adj==0) instore_adj=0.1;
var inputs=[instore,dole,store_last_tick/instore_adj,store_last_tick/numreq_total_adj]; var inputs=[instore,dole,store_last_tick/instore_adj,store_last_tick/numreq_total_adj,store_last_tick,numreq_total_adj];
var internal=[0,0,0,0,0]; var internal=new Array(10).fill(0);
var outputs=[0,0]; var outputs=[0,0];
//NN: Please don't touch :), code is generated //NN: Please don't touch :), code is generated
internal[2]=sigmoid0(internal[2]); outputs[0]=inputs[3]*0.9364774823;
outputs[0]=internal[2]*0.4049594998; outputs[0]=outputs[0]*1.0*0.4590721726;
outputs[0]=outputs[0]+1.0*-5.026565075; outputs[0]=sigmoid2(outputs[0]);
outputs[0]=outputs[0]+inputs[0]*0.9100194573; outputs[1]=outputs[0]*1.828422785;
outputs[0]=outputs[0]+inputs[1]*-1.063795686; outputs[1]=sigmoid3(outputs[1]);
outputs[0]=sigmoid1(outputs[0]);
outputs[1]=outputs[0]*1.049500823;
outputs[1]=outputs[1]*inputs[0]*3.437163115;
outputs[1]=sigmoid0(outputs[1]);
avg=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 (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
@@ -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({
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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) {
+22
View File
@@ -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.
+1 -2
View File
@@ -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");
-1
View File
@@ -56,7 +56,6 @@ function download(url, file, callback){
} }
var directories = [ var directories = [
"database/linvodb",
"sharedMods", "sharedMods",
"instances", "instances",
]; ];
+5
View File
@@ -0,0 +1,5 @@
{
"name": "lib",
"description": "Internal library for factorioClusterio"
}
+2 -1
View File
@@ -35,7 +35,7 @@ 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 util = require("util")
local silo_script = require("silo-script") local silo_script = require("silo-script")
@@ -127,6 +127,7 @@ remote.add_interface("freeplay",
global.chart_distance = tonumber(value) global.chart_distance = tonumber(value)
end end
}) })
]===]) ]===])
-- Multi-file test -- Multi-file test
+1 -1
View File
@@ -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
+22 -22
View File
@@ -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
@@ -86,7 +86,6 @@ 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
@@ -100,21 +99,18 @@ on_gui_click_handlers = {
end end
menu.visible = not menu.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
local menu = left['hotpatch-menu'] if e.element.name ~= 'hotpatch-menu' then
menu.visible = not menu.visible local menu = left['hotpatch-menu']
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']
@@ -155,8 +151,6 @@ 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 hotpatch_console = center['hotpatch-console'] local hotpatch_console = center['hotpatch-console']
@@ -192,9 +186,6 @@ on_gui_click_handlers = {
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 hotpatch_console = center['hotpatch-console'] local hotpatch_console = center['hotpatch-console']
@@ -252,8 +243,6 @@ on_gui_click_handlers = {
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
@@ -296,8 +285,6 @@ 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
@@ -316,8 +303,6 @@ on_gui_selection_state_changed_handlers = {
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
@@ -374,4 +359,19 @@ 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
+98 -87
View File
@@ -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] = {}
@@ -127,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)
@@ -334,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('.-%-.-%-(.*)%.')
@@ -346,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
@@ -365,15 +365,15 @@ debug_log({'hotpatch-info.logging-enabled'})
-- track _ENV accesses -- track _ENV accesses
setmetatable(_ENV, { setmetatable(_ENV, {
__index = function(_, 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'})
@@ -395,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'}})
@@ -461,8 +461,9 @@ install_mod = function(mod_name, mod_version, mod_code, mod_files)
local index = find_installed_mod(mod_name) local index = find_installed_mod(mod_name)
local mod = {} local mod = {}
if index then if index then
-- TODO: notify about installing over top of existing mod debug_log('attempt to install mod that is already installed, reinstalling: ' .. mod_name)
mod = global.mods[index] mod = table.remove(global.mods, index)
table.insert(global.mods, mod)
else else
--next free index --next free index
table.insert(global.mods, mod) table.insert(global.mods, mod)
@@ -614,20 +615,20 @@ 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
@@ -681,15 +682,15 @@ load_mod = function(installed_index)
-- 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
@@ -711,7 +712,7 @@ load_mod = function(installed_index)
error(err) error(err)
end end
end end
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)
@@ -728,11 +729,11 @@ load_mod = function(installed_index)
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({'hotpatch-error.path-not-found', path}, mod_name, 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
@@ -808,10 +809,6 @@ 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
@@ -833,15 +830,38 @@ 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)
@@ -856,26 +876,8 @@ run_mod = function(loaded_index)
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,_ 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)
@@ -1001,7 +1003,7 @@ mod_on_init = function(loaded_index)
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({'hotpatch-error.on-init-failed'}, mod_name) debug_log({'hotpatch-error.on-init-failed'}, mod_name)
debug_log(result, mod_name) debug_log(result, mod_name)
return false return false
end end
@@ -1018,13 +1020,16 @@ mod_on_load = function(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({'hotpatch-error.on-load-failed'}, mod_name) debug_log({'hotpatch-error.on-load-failed'}, mod_name)
debug_log(result, mod_name) 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
@@ -1040,7 +1045,7 @@ mod_on_configuration_changed = function(loaded_index, config)
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({'hotpatch-error.on-configuration-changed-failed'}, mod_name) debug_log({'hotpatch-error.on-configuration-changed-failed'}, mod_name)
debug_log(result, mod_name) debug_log(result, mod_name)
return false return false
end end
@@ -1189,21 +1194,27 @@ on_load = function()
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 --log('after')
for i = 1, #failed_mods do --log(serpent.block(global))
unload_mod(failed_mods[i])
end -- 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'}})
@@ -1211,16 +1222,16 @@ 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, config) then if not mod_on_configuration_changed(i, config) then
table.insert(failed_mods, i) table.insert(failed_mods, i)
end end
end end
for i = 1, #failed_mods do for i = 1, #failed_mods do
unload_mod(failed_mods[i]) unload_mod(failed_mods[i])
end end
debug_log({'hotpatch-info.complete', {'hotpatch-info.on-configuration-changed'}}) debug_log({'hotpatch-info.complete', {'hotpatch-info.on-configuration-changed'}})
end end
@@ -1324,7 +1335,7 @@ local mod_tools = setmetatable({
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] debug_level = tonumber(level) or debug_levels[level]
end, end,
},{ },{
__index = mod_tools_internal, __index = mod_tools_internal,
@@ -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
@@ -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
@@ -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=Вільна гра
@@ -1,3 +1,3 @@
msg-intro=Đầy là chế độ freeplay của Factorio. Nhiệm cụ của bạn là phóng một chiếc tên lửa lên vũ trụ. Hãy xây dựng một bệ phóng tên lửa để phóng một chiếc tên lửa kèm theo vệ tinh. Bạn sẽ cần phải nghiên cứu nhiều công nghệ tiên tiến để có thể xây dựng bệ phóng tên lửa. Bắt đầu từ quy mô nhỏ, phát triển dần dần bằng tự động hóa, và đừng quên bảo vệ bản thân mình khỏi những sinh vật sinh sống ở đây. msg-intro=Đầy là chế độ chơi tự do của Factorio. Nhiệm vụ của bạn là phóng một chiếc tên lửa lên vũ trụ bằng cách xây dựng một bệ phóng. Bạn sẽ cần phải nghiên cứu nhiều công nghệ tiên tiến để có thể xây dựng bệ phóng đó. Hãy bắt đầu nhỏ, rồi phát triển từng bước bằng tự động hóa, và đừng quên bảo vệ chính mình khỏi những sinh vật ở đây.
scenario-name=Chơi tự do
@@ -1,3 +1,3 @@
msg-intro=欢迎来到异星工厂标准模式,你的目标是要把火箭发射到太空。为此你需要研发一系列高等科技,解锁并修建火箭发射井,直到把携带有卫星的火箭发射出去。从小作坊生产起步,逐渐建立起属于自己的自动化大工厂吧!同时,别忘了防范异星虫子的骚扰 ... msg-intro=欢迎来到异星工厂标准模式,你的目标是要把火箭发射到太空。为此你需要研发一系列高等科技,修建火箭发射井,最后发射火箭。从小作坊生产起步,逐渐建立起属于自己的自动化大工厂吧!同时,别忘了防范异星虫子们的骚扰 ...
scenario-name=标准模式
@@ -1,3 +1,3 @@
msg-intro=這是異星工廠的自由模式。你的任務是發射火箭,探索外太空。請建造一個火箭發射井,並發射攜帶著衛星的火箭。你會需要研究大量的進階科技以解鎖火箭發射井。從頭開始,靠著自動化科技的協助向目標邁進時,也別忘記從外星住民手的中保護你自己啊! msg-intro=這是異星工廠的自由模式。你的任務是發射火箭探索外太空。請建造火箭發射井並發射火箭。你會需要研究大量的進階科技以解鎖火箭發射井。從頭開始,靠著自動化的協助邁向目標的同時,也別忘了從外星住民手中保護你自己。
scenario-name=自由模式
+148 -124
View File
@@ -1,7 +1,6 @@
/** /**
Clusterio master server. Facilitates communication between slaves through Clusterio master server. Facilitates communication between slaves through
a webserver, storing data related to slaves like production graphs and a webserver, storing data related to slaves like production graphs.
combinator signals.
@module clusterioMaster @module clusterioMaster
@author Danielv123 @author Danielv123
@@ -26,10 +25,6 @@ process.on('unhandledRejection', r => console.log(r));
// configgy stuff // configgy stuff
debug = false; debug = false;
// reduces loadtime from 1700 ms to 1200 ms on my i3 + SSD
// tested by logging Date.now() here and when the webserver started listening
require('cache-require-paths');
// argument parsing // argument parsing
const args = require('minimist')(process.argv.slice(2)); const args = require('minimist')(process.argv.slice(2));
@@ -40,14 +35,13 @@ const deepmerge = require("deepmerge");
const path = require("path"); const path = require("path");
const fs = require("fs-extra"); const fs = require("fs-extra");
const jwt = require('jsonwebtoken'); const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const crypto = require('crypto'); const crypto = require('crypto');
const base64url = require('base64url'); const base64url = require('base64url');
const moment = require("moment"); const moment = require("moment");
const request = require("request") const request = require("request");
// constants // constants
console.log(`Requiring config from ${args.config || './config'}`) console.log(`Requiring config from ${args.config || './config'}`);
const config = require(args.config || './config'); const config = require(args.config || './config');
config.databaseDirectory = args.databaseDirectory || config.databaseDirectory || "./database"; config.databaseDirectory = args.databaseDirectory || config.databaseDirectory || "./database";
const masterModFolder = path.join(config.databaseDirectory, "/masterMods/"); const masterModFolder = path.join(config.databaseDirectory, "/masterMods/");
@@ -55,12 +49,10 @@ mkdirp.sync(config.databaseDirectory);
mkdirp.sync(masterModFolder); mkdirp.sync(masterModFolder);
// homebrew modules // homebrew modules
const getFactorioLocale = require("./lib/getFactorioLocale"); const getFactorioLocale = require("lib/getFactorioLocale");
const stringUtils = require("./lib/stringUtils");
const fileOps = require("_app/fileOps");
// homemade express middleware for token auth // homemade express middleware for token auth
const authenticate = require("./lib/authenticate")(config); const authenticate = require("lib/authenticate")(config);
/** Sync */ /** Sync */
function randomStringAsBase64Url(size) { function randomStringAsBase64Url(size) {
@@ -68,7 +60,7 @@ function randomStringAsBase64Url(size) {
} }
if (!fs.existsSync("secret-api-token.txt")) { if (!fs.existsSync("secret-api-token.txt")) {
config.masterAuthSecret = randomStringAsBase64Url(256); config.masterAuthSecret = randomStringAsBase64Url(256);
fs.writeFileSync("config.json",JSON.stringify(config, null, 4)); fs.writeFileSync("config.json",JSON.stringify(config, null, 4));
fs.writeFileSync("secret-api-token.txt", jwt.sign({ id: "api" }, config.masterAuthSecret, { fs.writeFileSync("secret-api-token.txt", jwt.sign({ id: "api" }, config.masterAuthSecret, {
expiresIn: 86400*365 // expires in 1 year expiresIn: 86400*365 // expires in 1 year
})); }));
@@ -90,7 +82,9 @@ const fileUpload = require('express-fileupload');
var app = express(); var app = express();
app.use(cookieParser()); app.use(cookieParser());
app.use(bodyParser.json()); app.use(bodyParser.json({
limit: '10mb',
}));
app.use(bodyParser.urlencoded({ app.use(bodyParser.urlencoded({
parameterLimit: 100000, parameterLimit: 100000,
limit: '10mb', limit: '10mb',
@@ -176,18 +170,13 @@ const prometheusUPSGauge = new Prometheus.Gauge({
help: 'UPS of the current server', help: 'UPS of the current server',
labelNames: ["instanceID", "instanceName"], labelNames: ["instanceID", "instanceName"],
}); });
const prometheusTickGauge = new Prometheus.Gauge({
name: prometheusPrefix+'tick_gauge',
help: 'Servers current gametick',
labelNames: ["instanceID", "instanceName"],
});
const prometheusMasterInventoryGauge = new Prometheus.Gauge({ const prometheusMasterInventoryGauge = new Prometheus.Gauge({
name: prometheusPrefix+'master_inventory_gauge', name: prometheusPrefix+'master_inventory_gauge',
help: 'Amount of items stored on master', help: 'Amount of items stored on master',
labelNames: ["itemName"], labelNames: ["itemName"],
}); });
setInterval(()=>{ setInterval(()=>{
fs.writeFileSync("database/items.json", JSON.stringify(db.items)); fs.writeFileSync("database/items.json", JSON.stringify(db.items));
},config.autosaveInterval || 60000); },config.autosaveInterval || 60000);
/** /**
GET Prometheus metrics endpoint. Returns performance and usage metrics in a prometheus readable format. GET Prometheus metrics endpoint. Returns performance and usage metrics in a prometheus readable format.
@@ -235,7 +224,7 @@ const db = {};
(function(){ (function(){
try{ try{
let x = fs.statSync(path.resolve(config.databaseDirectory, "/slaves.json")); let x = fs.statSync(path.resolve(config.databaseDirectory, "slaves.json"));
console.log(`loading slaves from 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"))); slaves = JSON.parse(fs.readFileSync(path.resolve(config.databaseDirectory, "slaves.json")));
} catch (e){ } catch (e){
@@ -248,7 +237,7 @@ const db = {};
} catch (e){ } catch (e){
db.items = {}; db.items = {};
} }
})() })();
db.items.addItem = function(object) { db.items.addItem = function(object) {
if(object.name == "addItem" || object.name == "removeItem") { if(object.name == "addItem" || object.name == "removeItem") {
@@ -262,7 +251,7 @@ db.items.addItem = function(object) {
} }
return true; return true;
} }
} };
db.items.removeItem = function(object) { db.items.removeItem = function(object) {
if(object.name == "addItem" || object.name == "removeItem") { if(object.name == "addItem" || object.name == "removeItem") {
console.error("Fuck you, that would screw everything up if you named your item that."); console.error("Fuck you, that would screw everything up if you named your item that.");
@@ -275,7 +264,7 @@ db.items.removeItem = function(object) {
} }
return true; return true;
} }
} };
// store slaves and inventory in a .json full of JSON data // store slaves and inventory in a .json full of JSON data
process.on('SIGINT', shutdown); // ctrl + c process.on('SIGINT', shutdown); // ctrl + c
@@ -308,8 +297,8 @@ async function shutdown() {
console.log("Clusterio cleanly exited in "+(Date.now()-exitStartTime)+"ms"); console.log("Clusterio cleanly exited in "+(Date.now()-exitStartTime)+"ms");
process.exit(0); process.exit(0);
} catch(e) { } catch(e) {
console.log(e) console.log(e);
console.log("Clusterio failed to exit cleanly. Time elapsed: "+(Date.now()-exitStartTime)+"ms") console.log("Clusterio failed to exit cleanly. Time elapsed: "+(Date.now()-exitStartTime)+"ms");
process.exit(1) process.exit(1)
} }
} }
@@ -340,6 +329,7 @@ app.post("/api/getID", authenticate.middleware, function(req,res) {
} }
// console.log("Slave registered: " + slaves[req.body.unique].mac + " : " + slaves[req.body.unique].serverPort+" at " + slaves[req.body.unique].publicIP + " with name " + slaves[req.body.unique].instanceName); // console.log("Slave registered: " + slaves[req.body.unique].mac + " : " + slaves[req.body.unique].serverPort+" at " + slaves[req.body.unique].publicIP + " with name " + slaves[req.body.unique].instanceName);
} }
res.sendStatus(200);
}); });
/** /**
POST Allows you to add metadata related to slaves for other tools, like owner data, descriptions or statistics. POST Allows you to add metadata related to slaves for other tools, like owner data, descriptions or statistics.
@@ -352,16 +342,18 @@ app.post("/api/editSlaveMeta", authenticate.middleware, function(req,res) {
// request.body should be an object // request.body should be an object
// {instanceID, pass, meta:{x,y,z}} // {instanceID, pass, meta:{x,y,z}}
if(req.body && req.body.instanceID && req.body.password && req.body.meta){ if(req.body && req.body.instanceID && req.body.meta){
// check for editing permissions if(slaves[req.body.instanceID]){
if(slaves[req.body.instanceID] && slaves[req.body.instanceID].rconPassword == req.body.password){ if(!slaves[req.body.instanceID].meta) {
if(!slaves[req.body.instanceID].meta){
slaves[req.body.instanceID].meta = {}; slaves[req.body.instanceID].meta = {};
} }
slaves[req.body.instanceID].meta = deepmerge(slaves[req.body.instanceID].meta, req.body.meta, {clone:true}); slaves[req.body.instanceID].meta = deepmerge(slaves[req.body.instanceID].meta, req.body.meta, {clone:true});
let metaPortion = JSON.stringify(req.body.meta); let metaPortion = JSON.stringify(req.body.meta);
if(metaPortion.length > 50) metaPortion = metaPortion.substring(0,20) + "..."; if(metaPortion.length > 50) {
metaPortion = metaPortion.substring(0,20) + "...";
}
// console.log("Updating slave "+slaves[req.body.instanceID].instanceName+": " + slaves[req.body.instanceID].mac + " : " + slaves[req.body.instanceID].serverPort+" at " + slaves[req.body.instanceID].publicIP, metaPortion); // console.log("Updating slave "+slaves[req.body.instanceID].instanceName+": " + slaves[req.body.instanceID].mac + " : " + slaves[req.body.instanceID].serverPort+" at " + slaves[req.body.instanceID].publicIP, metaPortion);
res.sendStatus(200);
} else { } else {
res.send("ERROR: Invalid instanceID or password") res.send("ERROR: Invalid instanceID or password")
} }
@@ -377,12 +369,20 @@ POST Get metadata from a single slave. For whenever you find the data returned b
app.post("/api/getSlaveMeta", function (req, res) { app.post("/api/getSlaveMeta", function (req, res) {
endpointHitCounter.labels(req.route.path).inc(); endpointHitCounter.labels(req.route.path).inc();
console.log("body", req.body); console.log("body", req.body);
if(req.body && req.body.instanceID && req.body.password){ if(req.body && req.body.instanceID){
console.log("returning meta for ", req.body.instanceID); if (slaves[req.body.instanceID]) {
res.send(JSON.stringify(slaves[req.body.instanceID].meta)) console.log("returning meta for ", req.body.instanceID);
if(!slaves[req.body.instanceID].meta) {
slaves[req.body.instanceID].meta = {};
}
res.send(JSON.stringify(slaves[req.body.instanceID].meta))
} else {
res.status(404);
res.send('{"status": 404, "info": "Slave not registered"}')
}
} else { } else {
res.status(400); res.status(400);
res.send('{"INVALID REQUEST":1}'); res.send('{"INVALID_REQUEST":1}');
} }
}); });
// mod management // mod management
@@ -393,7 +393,7 @@ POST Check if a mod has been uploaded to the master before. Only checks against
@instance @instance
@alias /api/checkMod @alias /api/checkMod
*/ */
app.post("/api/checkMod", function(req,res) { app.post("/api/checkMod", authenticate.middleware, function(req,res) {
endpointHitCounter.labels(req.route.path).inc(); endpointHitCounter.labels(req.route.path).inc();
let files = fs.readdirSync(masterModFolder); let files = fs.readdirSync(masterModFolder);
let found = false; let found = false;
@@ -430,41 +430,63 @@ app.post("/api/uploadMod", authenticate.middleware, function(req,res) {
}); });
} else { } else {
res.send('No files were uploaded.'); res.send('No files were uploaded.');
return;
} }
}); });
/** /**
Prepare the slaveCache for both /api/slaves calls
*/
let slaveCache = {
timestamp: Date.now(),
};
function getSlaves() {
if(!slaveCache.cache || Date.now() - slaveCache.timestamp > 5000) {
let copyOfSlaves = JSON.parse(JSON.stringify(slaves));
slaveCache.cache = copyOfSlaves;
slaveCache.timestamp = Date.now();
}
return slaveCache;
}
/**
GET endpoint for getting information about all our slaves GET endpoint for getting information about all our slaves
@memberof clusterioMaster @memberof clusterioMaster
@instance @instance
@alias /api/slaves @alias /api/slaves
*/ */
let slaveCache = {
timestamp: Date.now(),
};
app.get("/api/slaves", function(req, res) { app.get("/api/slaves", function(req, res) {
endpointHitCounter.labels(req.route.path).inc(); endpointHitCounter.labels(req.route.path).inc();
res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
if(!slaveCache.cache || Date.now() - slaveCache.timestamp > 5000){ let slaveCache = getSlaves();
let copyOfSlaves = JSON.parse(JSON.stringify(slaves)); for(key in slaveCache.cache) {
// filter out the rcon password because thats kindof not a safe thing to share slaveCache.cache[key].rconPassword = "hidden";
for(key in copyOfSlaves){ }
copyOfSlaves[key].rconPassword = "hidden"; res.send(slaveCache.cache);
} });
slaveCache.cache = copyOfSlaves; /**
slaveCache.timestamp = Date.now(); POST endpoint for getting the rconPasswords of all our slaves
@memberof clusterioMaster
@instance
@alias /api/rconPasswords
*/
app.post("/api/rconPasswords", authenticate.middleware, function(req, res) {
endpointHitCounter.labels(req.route.path).inc();
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
let slaveCache = getSlaves();
let validKeys = ['rconPort', 'rconPassword', 'publicIP'];
for(key in slaveCache.cache) {
slaveCache.cache[key] = {'publicIP': slaveCache.cache[key].publicIP, 'rconPort': slaveCache.cache[key].rconPort, 'rconPassword': slaveCache.cache[key].rconPassword};
} }
res.send(slaveCache.cache); res.send(slaveCache.cache);
}); });
var recievedItemStatisticsBySlaveID = {};
var sentItemStatisticsBySlaveID = {};
/* /*
var recievedItemStatistics = new averagedTimeSeries({ var recievedItemStatistics = new averagedTimeSeries({
maxEntries: config.itemStats.maxEntries, maxEntries: config.itemStats.maxEntries,
entriesPerSecond: config.itemStats.entriesPerSecond, entriesPerSecond: config.itemStats.entriesPerSecond,
}, console.log);*/ }, console.log);*/
var recievedItemStatisticsBySlaveID = {};
var sentItemStatisticsBySlaveID = {};
// //
/** /**
POST endpoint for storing items in master's inventory. POST endpoint for storing items in master's inventory.
@@ -534,13 +556,13 @@ app.post("/api/place", authenticate.middleware, function(req, res) {
res.send("failure"); res.send("failure");
} }
}); });
const routes_api_remove = require("./routes/api/remove.js") const routes_api_remove = require("./routes/api/remove.js");
var neuralDole = null var neuralDole = null;
if(config.useNeuralNetDoleDivider)//Only initialize neural network when it's enabled, otherwise it might override gauge if(config.useNeuralNetDoleDivider)//Only initialize neural network when it's enabled, otherwise it might override gauge
neuralDole=new routes_api_remove.neuralDole({ neuralDole=new routes_api_remove.neuralDole({
items: db.items, gaugePrefix: prometheusPrefix items: db.items, gaugePrefix: prometheusPrefix
}) });
/** /**
POST endpoint to remove items from DB when client orders items. POST endpoint to remove items from DB when client orders items.
@@ -565,7 +587,7 @@ app.post("/api/remove", authenticate.middleware, function(req, res) {
object.instanceName = "unknown"; object.instanceName = "unknown";
} }
if(slaves[object.instanceID]) object.instanceName = slaves[object.instanceID].instanceName; if(slaves[object.instanceID]) object.instanceName = slaves[object.instanceID].instanceName;
let item = db.items[object.name] let item = db.items[object.name];
// console.dir(doc); // console.dir(doc);
if((item === undefined) if((item === undefined)
||(( ||((
@@ -670,19 +692,19 @@ app.get("/api/inventoryAsObject", function(req, res) {
*/ */
// wrap a request in an promise // wrap a request in an promise
async function downloadPage(url) { async function downloadPage(url) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
request(url, (error, response, body) => { request(url, (error, response, body) => {
resolve(body); resolve(body);
}); });
}); });
} }
app.get("/api/modmeta", async function(req, res) { app.get("/api/modmeta", async function(req, res) {
endpointHitCounter.labels(req.route.path).inc(); endpointHitCounter.labels(req.route.path).inc();
res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Origin", "*");
res.setHeader('Content-Type', 'application/json'); res.setHeader('Content-Type', 'application/json');
let modData = await downloadPage("https://mods.factorio.com/api/mods/" + req.query.modname); let modData = await downloadPage("https://mods.factorio.com/api/mods/" + req.query.modname);
res.send(modData); res.send(modData);
}); });
@@ -697,45 +719,38 @@ Requires x-access-token header to be set. Find you api token in secret-api-token
@param {object} JSON {instanceID:19412312, broadcast:false, command:"/c game.print('hello')"} @param {object} JSON {instanceID:19412312, broadcast:false, command:"/c game.print('hello')"}
@returns {object} Status {auth: bool, message: "Informative error", data:{}} @returns {object} Status {auth: bool, message: "Informative error", data:{}}
*/ */
app.post("/api/runCommand", (req,res) => { app.post("/api/runCommand", authenticate.middleware, function(req,res) {
endpointHitCounter.labels(req.route.path).inc(); endpointHitCounter.labels(req.route.path).inc();
var token = req.headers['x-access-token']; // validate request.body
if(!token) return res.status(401).send({ auth: false, message: 'No token provided.' }); let body = req.body;
jwt.verify(token, config.masterAuthSecret, function(err, decoded) { if (
if(err) return res.status(500).send({ auth: false, message: 'Failed to authenticate token.' }); body.broadcast
&& body.command
// validate request.body && typeof body.command == "string"
let body = req.body; && body.command[0] == "/")
{
if ( let instanceResponses = [];
body.broadcast for (let instanceID in wsSlaves) {
&& body.command // skip loop if the property is from prototype
&& typeof body.command == "string" if (!wsSlaves.hasOwnProperty(instanceID)) continue;
&& body.command[0] == "/") wsSlaves[instanceID].runCommand(body.command);
{ }
let instanceResponses = []; res.status(200).send({auth: true, message: "success", response: "Cluster wide messaging initiated"});
for (let instanceID in wsSlaves) { } else if (
// skip loop if the property is from prototype body.instanceID
if (!wsSlaves.hasOwnProperty(instanceID)) continue; && wsSlaves[body.instanceID]
wsSlaves[instanceID].runCommand(body.command); && body.command
} && typeof body.command == "string"
res.status(200).send({auth: true, message: "success", response: "Cluster wide messaging initiated"}); && body.command[0] == "/")
} else if ( {
body.instanceID // execute command
&& wsSlaves[body.instanceID] wsSlaves[body.instanceID].runCommand(body.command, data => {
&& body.command res.status(200).send({auth: true, message: "success", data});
&& typeof body.command == "string" });
&& body.command[0] == "/") } else {
{ res.status(400).send({auth: true, message: "Error: invalid request.body"});
// execute command }
wsSlaves[body.instanceID].runCommand(body.command, data => {
res.status(200).send({auth: true, message: "success", data});
});
} else {
res.status(400).send({auth: true, message: "Error: invalid request.body"});
}
});
}); });
/** /**
GET endpoint. Returns factorio's base locale as a JSON object. GET endpoint. Returns factorio's base locale as a JSON object.
@@ -772,9 +787,10 @@ if(args.masterPort || config.masterPort){
} }
/* Websockets for remoteMap */ /* Websockets for remoteMap */
var io = require("socket.io")(server); var io = require("socket.io")();
const ioMetrics = require("socket.io-prometheus"); const ioMetrics = require("socket.io-prometheus");
ioMetrics(io); ioMetrics(io);
var slaveMappers = {}; var slaveMappers = {};
class slaveMapper { class slaveMapper {
constructor(instanceID, socket) { constructor(instanceID, socket) {
@@ -805,6 +821,8 @@ class slaveMapper {
}); });
} }
} }
var mapRequesters = {}; var mapRequesters = {};
class mapRequester { class mapRequester {
constructor(requesterID, socket, instanceID){ constructor(requesterID, socket, instanceID){
@@ -844,6 +862,8 @@ class mapRequester {
}); });
} }
} }
/* Websockets for send and recieve combinators */ /* Websockets for send and recieve combinators */
var wsSlaves = {}; var wsSlaves = {};
class wsSlave { class wsSlave {
@@ -856,14 +876,6 @@ class wsSlave {
prometheusWsUsageCounter.labels('heartbeat', this.instanceID).inc(); prometheusWsUsageCounter.labels('heartbeat', this.instanceID).inc();
this.lastBeat = Date.now(); this.lastBeat = Date.now();
}); });
this.socket.on("combinatorSignal", circuitFrameWithMeta => {
prometheusWsUsageCounter.labels('combinatorSignal', this.instanceID).inc();
if(circuitFrameWithMeta && typeof circuitFrameWithMeta == "object"){
Object.keys(wsSlaves).forEach(instanceID => {
wsSlaves[instanceID].socket.emit("processCombinatorSignal", circuitFrameWithMeta);
});
}
});
// handle command return values // handle command return values
this.commandsWaitingForReturn = {}; this.commandsWaitingForReturn = {};
this.socket.on("runCommandReturnValue", resp => { this.socket.on("runCommandReturnValue", resp => {
@@ -902,6 +914,19 @@ class wsSlave {
if(commandID) this.commandsWaitingForReturn[commandID] = {callback, timestamp: Date.now()}; if(commandID) this.commandsWaitingForReturn[commandID] = {callback, timestamp: Date.now()};
} }
} }
io.use((socket, next) => {
let token = socket.handshake.query.token;
authenticate.check(token).then((result) => {
if (result.ok) {
next();
} else {
console.error(`SOCKET | authentication failed for ${socket.handshake.address}`);
next(new Error(result.msg));
}
});
});
io.on('connection', function (socket) { io.on('connection', function (socket) {
// cleanup dead sockets from disconnected people // cleanup dead sockets from disconnected people
let terminatedConnections = 0; let terminatedConnections = 0;
@@ -956,17 +981,9 @@ io.on('connection', function (socket) {
}); });
// handle plugins on the master // handle plugins on the master
pluginManagement();
masterPlugins = [];
async function pluginManagement(){
let startPluginLoad = Date.now();
masterPlugins = await getPlugins();
console.log("All plugins loaded in "+(Date.now() - startPluginLoad)+"ms");
return;
}
async function getPlugins(){ async function getPlugins(){
const pluginManager = require("./lib/manager/pluginManager.js")(config); let startPluginLoad = Date.now();
const pluginManager = require("lib/manager/pluginManager.js")(config);
let plugins = []; let plugins = [];
let pluginsToLoad = await pluginManager.getPlugins(); let pluginsToLoad = await pluginManager.getPlugins();
for(let i = 0; i < pluginsToLoad.length; i++){ for(let i = 0; i < pluginsToLoad.length; i++){
@@ -996,8 +1013,15 @@ async function getPlugins(){
let plugin = plugins[i]; let plugin = plugins[i];
if(plugin.main.onLoadFinish && typeof plugin.main.onLoadFinish == "function") await plugin.main.onLoadFinish({plugins}); if(plugin.main.onLoadFinish && typeof plugin.main.onLoadFinish == "function") await plugin.main.onLoadFinish({plugins});
} }
console.log("All plugins loaded in "+(Date.now() - startPluginLoad)+"ms");
return plugins; return plugins;
} }
masterPlugins = await getPlugins();
io.attach(server);
module.exports = app; module.exports = app;
})(); })();
+18 -24
View File
@@ -1,35 +1,32 @@
{ {
"name": "clusterio", "name": "clusterio",
"version": "1.7.0", "version": "1.2.4",
"description": "Interserver communication for factorio", "description": "Interserver communication for factorio",
"repository": "https://github.com/Danielv123/factorioClusterio", "repository": "https://github.com/Danielv123/factorioClusterio",
"main": "master.js", "main": "master.js",
"scripts": { "scripts": {
"test": "./node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha -- \"./{{,node_modules/_app/**/},!(node_modules)/**/}*.spec.js\" -R spec || echo .", "test": "mocha -R spec -- \"./{,!(node_modules)/**/}*.spec.js\" || true",
"cover": "nyc --reporter=lcovonly npm run-script test && codecov -f coverage/lcov.info -t f7b38085-36eb-4d72-84e6-64721910171e",
"start": "echo Run node client.js start [instance] to start slaves && node master.js", "start": "echo Run node client.js start [instance] to start slaves && node master.js",
"cover": "./node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha -- \"./{{,node_modules/_app/**/},!(node_modules)/**/}*.spec.js\" -R spec || echo . && codecov -t f7b38085-36eb-4d72-84e6-64721910171e",
"docs": "documentation build index.js --config documentation.yml -f html -o docs",
"livedocs": "documentation serve --watch *.js lib/** --config documentation.yml",
"install": "node lib/npmPostinstall.js", "install": "node lib/npmPostinstall.js",
"bundle-dependencies": "bundle-dependencies", "bundle-dependencies": "bundle-dependencies"
"snyk-protect": "snyk protect",
"prepublish": "npm run snyk-protect"
}, },
"keywords": [ "keywords": [
"factorio" "factorio"
], ],
"author": "Danielv123", "author": "Danielv123",
"license": "ISC", "license": "MIT",
"engines": {
"node": ">=10"
},
"dependencies": { "dependencies": {
"app-root-path": "^2.0.1",
"as-table": "^1.0.31", "as-table": "^1.0.31",
"averaged-timeseries": "^1.4.0", "averaged-timeseries": "^1.4.0",
"base64url": "^3.0.0", "base64url": "^3.0.0",
"bcrypt": "^2.0.1", "bcrypt": "=3.0.6",
"bcrypt-promise": "^2.0.0", "bcrypt-promise": "^2.0.0",
"bcryptjs": "^2.4.3",
"body-parser": "^1.18.2", "body-parser": "^1.18.2",
"cache-require-paths": "^0.3.0", "cli": "^1.0.1",
"cli-interact": "^0.1.9", "cli-interact": "^0.1.9",
"codecov": "^3.0.1", "codecov": "^3.0.1",
"compression": "^1.7.2", "compression": "^1.7.2",
@@ -42,10 +39,9 @@
"follow-redirects": "^1.5.0", "follow-redirects": "^1.5.0",
"fs-extra": "^6.0.1", "fs-extra": "^6.0.1",
"getmac": "^1.4.0", "getmac": "^1.4.0",
"hash-files": "^1.1.1",
"html5-validator": "^1.0.7",
"ini": "^1.3.5", "ini": "^1.3.5",
"jsonwebtoken": "^8.2.1", "jsonwebtoken": "^8.2.1",
"lib": "file:./lib",
"luamin": "^1.0.4", "luamin": "^1.0.4",
"minimist": "^1.2.0", "minimist": "^1.2.0",
"mkdirp": "^0.5.1", "mkdirp": "^0.5.1",
@@ -53,12 +49,11 @@
"moment": "^2.22.2", "moment": "^2.22.2",
"ncp": "^2.0.0", "ncp": "^2.0.0",
"needle": "^2.2.4", "needle": "^2.2.4",
"node-factorio-api": "^0.3.1-alpha", "node-factorio-api": "^0.3.8",
"node-forge": "^0.7.1", "node-forge": "^0.7.1",
"prom-client": "^10.2.3", "prom-client": "^10.2.3",
"rcon-client": "^3.0.2", "rcon-client-fork": "^3.2.0",
"request": "^2.83.0", "request": "^2.83.0",
"right-pad": "^1.0.1",
"rmdir-sync": "^1.0.1", "rmdir-sync": "^1.0.1",
"sanitizer": "^0.1.3", "sanitizer": "^0.1.3",
"simple-git": "^1.96.0", "simple-git": "^1.96.0",
@@ -67,18 +62,17 @@
"socket.io-client": "^2.0.4", "socket.io-client": "^2.0.4",
"socket.io-prometheus": "^0.2.1", "socket.io-prometheus": "^0.2.1",
"sync-request": "^4.0.1", "sync-request": "^4.0.1",
"underscore": "^1.8.3", "tail": "^2.0.2",
"snyk": "^1.134.2" "typescript-collections": "^1.3.2"
}, },
"devDependencies": { "devDependencies": {
"documentation": "^5.4.0", "html5-validator": "^1.0.7",
"istanbul": "^0.4.5",
"mocha": "^3.5.3", "mocha": "^3.5.3",
"mocha-sinon": "^2.0.0", "mocha-sinon": "^2.0.0",
"mocha.parallel": "^0.15.5", "mocha.parallel": "^0.15.5",
"nock": "^9.1.6", "nock": "^9.1.6",
"nyc": "^14.1.1",
"sinon": "^2.4.1", "sinon": "^2.4.1",
"supertest": "^3.0.0" "supertest": "^3.0.0"
}, }
"snyk": true
} }
+5 -2
View File
@@ -1,6 +1,6 @@
const averagedTimeSeries = require("averaged-timeseries") const averagedTimeSeries = require("averaged-timeseries")
const doleNN = require("./../../lib/dole_nn_base.js") const doleNN = require("lib/dole_nn_base.js")
const Prometheus = require('prom-client'); const Prometheus = require('prom-client');
@@ -33,6 +33,7 @@ class neuralDole {
this.carry = {} this.carry = {}
this.lastRequest = {} this.lastRequest = {}
this.stats=[] this.stats=[]
this.debt= {}
setInterval(()=>{ setInterval(()=>{
for(let name in this.items){ for(let name in this.items){
@@ -71,7 +72,8 @@ class neuralDole {
this.dole[object.name], this.dole[object.name],
this.carry[object.name+" "+object.instanceID] || 0, this.carry[object.name+" "+object.instanceID] || 0,
this.lastRequest[object.name+"_"+object.instanceID+"_"+object.instanceName] || 0, this.lastRequest[object.name+"_"+object.instanceID+"_"+object.instanceName] || 0,
this.getRequestStats(object.name,5) this.getRequestStats(object.name,5),
this.debt[object.name+" "+object.instanceID] || 0
) )
if ((this.stats[object.name] || []).length>0) if ((this.stats[object.name] || []).length>0)
{ {
@@ -84,6 +86,7 @@ class neuralDole {
//2.New carry for item X slave Y //2.New carry for item X slave Y
this.dole[object.name] = magicData[1] this.dole[object.name] = magicData[1]
this.carry[object.name+" "+object.instanceID] = magicData[2] this.carry[object.name+" "+object.instanceID] = magicData[2]
this.debt[object.name+" "+object.instanceID] = magicData[3]
// Remove item from DB and send it // Remove item from DB and send it
if(this.items.removeItem({count: magicData[0], name: object.name})){ if(this.items.removeItem({count: magicData[0], name: object.name})){
+4 -109
View File
@@ -1,9 +1,9 @@
const needle = require("needle"); const needle = require("needle");
const fs = require("fs-extra"); const fs = require("fs-extra");
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 pluginConfig = require("./config"); const pluginConfig = require("./config");
const COMPRESS_LUA = false; const COMPRESS_LUA = false;
@@ -35,61 +35,6 @@ module.exports = class remoteCommands {
} }
})().catch(e => console.log(e)); })().catch(e => console.log(e));
// flow/production statistics ------------------------------------------------------------
var oldFlowStats = false;
var oldTimestamp;
var oldFlowStats;
setInterval(function(){
fs.readFile(instancedirectory + "/script-output/flows.txt", {encoding: "utf8"}, function(err, data) {
if(!err && data) {
let timestamp = Date.now();
data = data.split("\n");
let flowStats = [];
for(let i = 0; i < data.length; i++) {
// try catch to remove any invalid json
try{
flowStats[flowStats.length] = JSON.parse(data[i]);
} catch (e) {
// console.log(" invalid json: " + i);
// some lines of JSON are invalid but don't worry, we just filter em out
}
}
// fluids
let flowStat1 = flowStats[flowStats.length-1].flows.player.input_counts
// items
let flowStat2 = flowStats[flowStats.length-2].flows.player.input_counts
// merge fluid and item flows
let totalFlows = {};
for(let key in flowStat1) totalFlows[key] = flowStat1[key];
for(let key in flowStat2) totalFlows[key] = flowStat2[key];
if(oldFlowStats && totalFlows && oldTimestamp) {
let payload = objectOps.deepclone(totalFlows);
// change from total reported to per time unit
for(let key in oldFlowStats) {
// get production per minute
payload[key] = Math.floor((payload[key] - oldFlowStats[key])/(timestamp - oldTimestamp)*60000);
if(payload[key] < 0) {
payload[key] = 0;
}
}
for(let key in payload) {
if(payload[key] == '0') {
delete payload[key];
}
}
console.log("Recorded flows, copper plate since last time: " + payload["copper-plate"]);
needle.post(that.config.masterIP + ":" + that.config.masterPort + '/api/logStats', {timestamp: timestamp, instanceID: that.config.unique, data: payload}, needleOptionsWithTokenAuthHeader, function (err, response, body) {
// we did it, keep going
});
}
oldTimestamp = timestamp;
oldFlowStats = totalFlows;
fs.writeFileSync(instancedirectory + "/script-output/flows.txt", "");
}
});
// we don't need to update stats quickly as that could be expensive
}, 60000*5);
// provide items -------------------------------------------------------------- // provide items --------------------------------------------------------------
// trigger when something happens to output.txt // trigger when something happens to output.txt
fs.watch(instancedirectory + "/script-output/output.txt", function (eventType, filename) { fs.watch(instancedirectory + "/script-output/output.txt", function (eventType, filename) {
@@ -232,56 +177,6 @@ module.exports = class remoteCommands {
setTimeout(function(){ setTimeout(function(){
messageInterface("/silent-command remote.call('clusterio','setWorldID',"+that.config.unique+")") messageInterface("/silent-command remote.call('clusterio','setWorldID',"+that.config.unique+")")
}, 20000); }, 20000);
/* REMOTE SIGNALLING
* send any signals the slave has been told to send
* Fetch combinator signals from the server
*/
this.socket.on("processCombinatorSignal", circuitFrameWithMeta => {
if(circuitFrameWithMeta && typeof circuitFrameWithMeta == "object" && circuitFrameWithMeta.frame && Array.isArray(circuitFrameWithMeta.frame)){
messageInterface("/silent-command remote.call('clusterio', 'receiveFrame', '"+JSON.stringify(circuitFrameWithMeta.frame)+"')");
}
});
// get outbound frames from file and send to master
// get array of lines in file, each line should correspond to a JSON encoded frame
let signals = fs.readFileSync(instancedirectory + "/script-output/txbuffer.txt", "utf8").split("\n");
// if we actually got anything from the file, proceed and reset file
let readingTxBufferSoon = false;
let txBufferClearCounter = 0;
fs.watch(instancedirectory + "/script-output/txbuffer.txt", "utf-8", (eventType, filename) => {
if(!readingTxBufferSoon){ // use a 100ms delay to avoid messing with rapid sequential writes from factorio (I think that might be a problem maybe?)
readingTxBufferSoon = true;
setTimeout(()=>{
txBufferClearCounter++;
fs.readFile(instancedirectory + "/script-output/txbuffer.txt", "utf-8", (err, signals) => {
signals = signals.split("\n");
if (signals[0]) {
//if(txBufferClearCounter > 500){
fs.writeFileSync(instancedirectory + "/script-output/txbuffer.txt", "");
// txBufferClearCounter = 0;
//}
// loop through all our frames
for (let i = 0; i < signals.length; i++) {
if (signals[i] && objectOps.isJSON(signals[i])) {
// signals[i] is a JSON array called a "frame" of signals. We timestamp it for storage on master
// then we unpack and RCON in this.frame to the game later.
let framepart = JSON.parse(signals[i]);
let doneframe = {
time: Date.now(),
frame: framepart, // thats our array of objects(single signals);
}
// send to master using socket.io, opened at the top of instanceManagement()
this.socket.emit("combinatorSignal", doneframe);
} else {
// console.log("Invalid jsony: "+typeof signals[i])
}
}
}
});
readingTxBufferSoon = false;
},100);
}
});
} }
async getCommand(file){ async getCommand(file){
this.commandCache = this.commandCache || {}; this.commandCache = this.commandCache || {};
@@ -307,7 +202,7 @@ module.exports = class remoteCommands {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let instance = this.instances[instanceID]; let instance = this.instances[instanceID];
if(!instance){ if(!instance){
needle.get(this.config.masterIP+":"+this.config.masterPort+ '/api/slaves', (err, response) => { needle.get(this.config.masterIP+":"+this.config.masterPort+ '/api/slaves', { compressed: true }, (err, response) => {
if(err || response.statusCode != 200) { if(err || response.statusCode != 200) {
console.log("Unable to get JSON master/api/slaves, master might be unaccessible"); console.log("Unable to get JSON master/api/slaves, master might be unaccessible");
} else if (response && response.body) { } else if (response && response.body) {
+9 -6
View File
@@ -22,12 +22,15 @@ module.exports = class remoteCommands {
words.shift(); words.shift();
words.shift(); words.shift();
words.shift(); words.shift();
let message = words.join(" ").replace(/[";]/g, " ").replace(/[']/g,"").replace("/shout ", "").replace("!shout", ""); let message = words.join(" ").replace(/[";]/g, " ").replace(/[']/g,"").replace("/shout ", "").replace("!shout ","").replace(/\[gps=.*?\]/g, "").replace(/\[train=.*?\]/g, "");
let instance = await this.getInstanceName(data.instanceID); let instance = await this.getInstanceName(data.instanceID);
if(data.data.includes("[gps=")){
this.messageInterface("/silent-command game.print('[" + instance + "] " + message + "')"); //Nothing
} }else{
this.messageInterface("/silent-command game.print('[" + instance + "] " + message + "')");
}
}
} }
}); });
} }
@@ -52,7 +55,7 @@ module.exports = class remoteCommands {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let instance = this.instances[instanceID]; let instance = this.instances[instanceID];
if(!instance){ if(!instance){
needle.get(this.config.masterIP+":"+this.config.masterPort+ '/api/slaves', (err, response) => { needle.get(this.config.masterIP+":"+this.config.masterPort+ '/api/slaves', { compressed: true }, (err, response) => {
if(err || response.statusCode != 200) { if(err || response.statusCode != 200) {
console.log("Unable to get JSON master/api/slaves, master might be unaccessible"); console.log("Unable to get JSON master/api/slaves, master might be unaccessible");
} else if (response && response.body) { } else if (response && response.body) {
+1
View File
@@ -17,4 +17,5 @@ module.exports = {
description: "Clusterio plugin to fill character logistic slots with master imports", description: "Clusterio plugin to fill character logistic slots with master imports",
// We'll send everything in this file to your stdin. Beware. // We'll send everything in this file to your stdin. Beware.
scriptOutputFileSubscription: "inventoryImports.txt", scriptOutputFileSubscription: "inventoryImports.txt",
fileReadDelay: 100,
} }
@@ -1,4 +1,4 @@
const objectOps = require("../../lib/objectOps.js"); const objectOps = require("lib/objectOps.js");
const needle = require("needle"); const needle = require("needle");
const pluginConfig = require("./config") const pluginConfig = require("./config")
const isJson = require("./isJson") const isJson = require("./isJson")
@@ -141,7 +141,10 @@ function pollInventories(outputFile){
//console.log("/silent-command "+'local a=game.players["Danielv123"].get_inventory(defines.inventory.player_main).get_contents()local b=game.players["Danielv123"].get_quickbar().get_contents()local c={}for d=1,game.players["Danielv123"].force.character_logistic_slot_count do c[d]=game.players["Danielv123"].character.get_request_slot(d)end;local e="{inventory:{"for f,g in pairs(a)do e=e.."['+"'"+'"..f.."'+"'"+']:"..g..","end;for f,g in pairs(b)do e=e.."['+"'"+'"..f.."'+"'"+']:"..g..","end;e=e.."},requestSlots:{"for f,g in pairs(c)do e=e.."['+"'"+'"..g["name"].."'+"'"+']:"..g["count"]..", "end;e=e.."}}"game.write_file("'+pluginConfig.scriptOutputFileSubscription+'",e)') //console.log("/silent-command "+'local a=game.players["Danielv123"].get_inventory(defines.inventory.player_main).get_contents()local b=game.players["Danielv123"].get_quickbar().get_contents()local c={}for d=1,game.players["Danielv123"].force.character_logistic_slot_count do c[d]=game.players["Danielv123"].character.get_request_slot(d)end;local e="{inventory:{"for f,g in pairs(a)do e=e.."['+"'"+'"..f.."'+"'"+']:"..g..","end;for f,g in pairs(b)do e=e.."['+"'"+'"..f.."'+"'"+']:"..g..","end;e=e.."},requestSlots:{"for f,g in pairs(c)do e=e.."['+"'"+'"..g["name"].."'+"'"+']:"..g["count"]..", "end;e=e.."}}"game.write_file("'+pluginConfig.scriptOutputFileSubscription+'",e)')
// console.log("/silent-command "+'local a="{"for b,c in pairs(game.players)do local d=game.players[b].get_inventory(defines.inventory.player_main).get_contents()local e=game.players[b].get_quickbar().get_contents()local f={}for g=1,game.players[b].force.character_logistic_slot_count do f[g]=game.players[b].character.get_request_slot(g)end;a=a..b..":{inventory:{"for h,i in pairs(d)do a=a.."['+"'"+'"..h.."'+"'"+']:"..i..","end;for h,i in pairs(e)do a=a.."['+"'"+'"..h.."'+"'"+']:"..i..","end;a=a.."},requestSlots:{"for h,i in pairs(f)do a=a.."['+"'"+'"..i["name"].."'+"'"+']:"..i["count"]..", "end;a=a.."}},"end;a=a.."}"game.write_file("'+pluginConfig.scriptOutputFileSubscription+'",a)') // console.log("/silent-command "+'local a="{"for b,c in pairs(game.players)do local d=game.players[b].get_inventory(defines.inventory.player_main).get_contents()local e=game.players[b].get_quickbar().get_contents()local f={}for g=1,game.players[b].force.character_logistic_slot_count do f[g]=game.players[b].character.get_request_slot(g)end;a=a..b..":{inventory:{"for h,i in pairs(d)do a=a.."['+"'"+'"..h.."'+"'"+']:"..i..","end;for h,i in pairs(e)do a=a.."['+"'"+'"..h.."'+"'"+']:"..i..","end;a=a.."},requestSlots:{"for h,i in pairs(f)do a=a.."['+"'"+'"..i["name"].."'+"'"+']:"..i["count"]..", "end;a=a.."}},"end;a=a.."}"game.write_file("'+pluginConfig.scriptOutputFileSubscription+'",a)')
//return "/silent-command "+'local a="{"for b,c in pairs(game.players)do if c.connected then local d=game.players[b].get_inventory(defines.inventory.player_main).get_contents()local e=game.players[b].get_quickbar().get_contents()local f={}for g=1,game.players[b].force.character_logistic_slot_count do f[g]=game.players[b].character.get_request_slot(g)end;a=a..b..":{inventory:{"for h,i in pairs(d)do a=a.."['+"'"+'"..h.."'+"'"+']:"..i..","end;for h,i in pairs(e)do a=a.."['+"'"+'"..h.."'+"'"+']:"..i..","end;a=a.."},requestSlots:{"for h,i in pairs(f)do a=a.."['+"'"+'"..i["name"].."'+"'"+']:"..i["count"]..", "end;a=a.."}},"end end;a=a.."}"game.write_file("'+outputFile+'",a)' //return "/silent-command "+'local a="{"for b,c in pairs(game.players)do if c.connected then local d=game.players[b].get_inventory(defines.inventory.player_main).get_contents()local e=game.players[b].get_quickbar().get_contents()local f={}for g=1,game.players[b].force.character_logistic_slot_count do f[g]=game.players[b].character.get_request_slot(g)end;a=a..b..":{inventory:{"for h,i in pairs(d)do a=a.."['+"'"+'"..h.."'+"'"+']:"..i..","end;for h,i in pairs(e)do a=a.."['+"'"+'"..h.."'+"'"+']:"..i..","end;a=a.."},requestSlots:{"for h,i in pairs(f)do a=a.."['+"'"+'"..i["name"].."'+"'"+']:"..i["count"]..", "end;a=a.."}},"end end;a=a.."}"game.write_file("'+outputFile+'",a)'
return '/silent-command '+'local a='+"'"+'{"players":{'+"'"+'local b=false;for c,d in pairs(game.players)do if d.connected and d.character then if b then a=a..'+"'"+','+"'"+'else b=true end;local e=game.players[c].get_inventory(defines.inventory.player_main).get_contents()local f=game.players[c].get_quickbar().get_contents()local g={}local z={}for j,k in pairs(e)do if z[j] ~= nil then z[j] = z[j] + k else z[j] = k end end for j,k in pairs(f)do if z[j] ~= nil then z[j] = z[j] + k else z[j] = k end end for h=1,game.players[c].force.character_logistic_slot_count do g[h]=game.players[c].character.get_request_slot(h)end;a=a..'+"'"+'"'+"'"+'..c..'+"'"+'":{"inventory":{'+"'"+'local i=false;for j,k in pairs(z)do if i then a=a..'+"'"+','+"'"+'else i=true end;a=a..'+"'"+'"'+"'"+'..j..'+"'"+'":'+"'"+'..k end;a=a..'+"'"+'},"requestSlots":{'+"'"+'i=false;for j,k in pairs(g)do if i then a=a..'+"'"+','+"'"+'else i=true end;a=a..'+"'"+'"'+"'"+'..k['+"'"+'name'+"'"+']..'+"'"+'":'+"'"+'..k['+"'"+'count'+"'"+']end;a=a..'+"'"+'}}'+"'"+'end end;a=a.."}}"game.write_file("'+outputFile+'",a,true,0)' //return '/silent-command '+'local a='+"'"+'{"players":{'+"'"+'local b=false;for c,d in pairs(game.players)do if d.connected and d.character then if b then a=a..'+"'"+','+"'"+'else b=true end;local e=game.players[c].get_inventory(defines.inventory.player_main).get_contents()local f=game.players[c].get_quickbar().get_contents()local g={}local z={}for j,k in pairs(e)do if z[j] ~= nil then z[j] = z[j] + k else z[j] = k end end for j,k in pairs(f)do if z[j] ~= nil then z[j] = z[j] + k else z[j] = k end end for h=1,game.players[c].force.character_logistic_slot_count do g[h]=game.players[c].character.get_request_slot(h)end;a=a..'+"'"+'"'+"'"+'..c..'+"'"+'":{"inventory":{'+"'"+'local i=false;for j,k in pairs(z)do if i then a=a..'+"'"+','+"'"+'else i=true end;a=a..'+"'"+'"'+"'"+'..j..'+"'"+'":'+"'"+'..k end;a=a..'+"'"+'},"requestSlots":{'+"'"+'i=false;for j,k in pairs(g)do if i then a=a..'+"'"+','+"'"+'else i=true end;a=a..'+"'"+'"'+"'"+'..k['+"'"+'name'+"'"+']..'+"'"+'":'+"'"+'..k['+"'"+'count'+"'"+']end;a=a..'+"'"+'}}'+"'"+'end end;a=a.."}}"game.write_file("'+outputFile+'",a,true,0)'
// Warning: This command is not in sync with lua commands.lua
return `/silent-command local a='{"players":{'local b=false;for c,d in pairs(game.players)do if d.connected and d.character then if b then a=a..','else b=true end;local e=game.players[c].get_inventory(defines.inventory.character_main).get_contents()local g={}for h=1,game.players[c].force.character_logistic_slot_count do g[h]=game.players[c].character.get_request_slot(h)end;a=a..'"'..c..'":{"inventory":{'local i=false;for j,k in pairs(e)do if i then a=a..','else i=true end;a=a..'"'..j..'":'..k end;a=a..'},"requestSlots":{'i=false;for j,k in pairs(g)do if i then a=a..','else i=true end;a=a..'"'..k['name']..'":'..k['count']end;a=a..'}}'end end;a=a.."}}"game.write_file("${outputFile}",a,true,0)`
/* /*
needle.post(config.masterIP+':'+config.masterPort+'/api/editSlaveMeta', {instanceID: config.unique, password: config.clientPassword, meta: {UPS:UPS}}, function(err, resp) { needle.post(config.masterIP+':'+config.masterPort+'/api/editSlaveMeta', {instanceID: config.unique, password: config.clientPassword, meta: {UPS:UPS}}, function(err, resp) {
// success? // success?
@@ -2,7 +2,7 @@ const assert = require("assert");
const child_process = require("child_process"); const child_process = require("child_process");
const functions = require("./interfacingFunctions"); const functions = require("./interfacingFunctions");
const luamin = require("luamin"); const luamin = require("luamin");
const isFactorioCommand = require("_app/isFactorioCommand"); const isFactorioCommand = require("lib/isFactorioCommand");
const nock = require("nock"); const nock = require("nock");
// sinon does weird prototype manipulation and so doen't need to be saved to anything. // sinon does weird prototype manipulation and so doen't need to be saved to anything.
Loaded 100 of 120 files, more files were not shown because too many files have changed in this diff. Show more