Compare commits

..
Author SHA1 Message Date
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 35101a7e32 Merge pull request #10 from Danielv123/master
sync
2019-07-26 18:55:46 +02:00
101 changed files with 719 additions and 2183 deletions

No files matched your search

+2 -4
View File
@@ -11,8 +11,9 @@ pids
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
# Coverage directories used by tools like nyc and codecov
coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
@@ -25,7 +26,6 @@ build/Release
dist/
node_modules/*
!node_modules/_app
Factorio*
/factorio
@@ -63,7 +63,5 @@ sharedPlugins/scenario_loader/scenarios
# File used for marking plugins as 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'
+1 -1
View File
@@ -1,6 +1,6 @@
language: node_js
node_js:
- "9"
- 10
before_install:
- sudo apt-get -qq update
- sudo apt-get install -y curl git tar
+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.
+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 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
WORKDIR factorioClusterio
+53 -36
View File
@@ -8,6 +8,13 @@
Discord for development/support/play: https://discord.gg/5XuDkje
## Important notice
This is the stable branch of the project. The
[master branch](http://github.com/clusterio/factorioClusterio/tree/master)
is undergoing significant changes that breaks compatibility to plugins and
existing installations and is not recommended for use.
### Ways to support me/the project:
* Contribute with code/documentation. Always nice to move the project forward
@@ -88,19 +95,21 @@ NodeJS does not support EOL ubuntu releases. Make sure you are on the most recen
Master and all slaves:
sudo curl -sL https://deb.nodesource.com/setup_9.x | sudo -E bash -
sudo apt install -y nodejs python-dev git wget curl tar build-essential
sudo npm install pm2 -g
git clone https://github.com/Danielv123/factorioClusterio.git
wget -qO - https://deb.nodesource.com/setup_10.x | sudo -E bash -
sudo apt install -y nodejs python-dev git build-essential
git clone -b 1.2.x https://github.com/Danielv123/factorioClusterio.git
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
npm install
npm install --only=production
cp config.json.dist config.json
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.
Pretty much all the blank fields should be filled in, except on the master where a few can be omitted.
@@ -113,11 +122,12 @@ Pretty much all the blank fields should be filled in, except on the master where
**Master**
node master.js
Or with pm2 (it's recommened to run it without pm2 first):
pm2 start master.js --name master
OR
node master.js
**Server Host**
@@ -159,7 +169,7 @@ Game Client = The people connecting to the server
**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/
@@ -167,28 +177,47 @@ reboot when you are done, then proceed to the next steps. *reboots matter*
**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
3. Some of the instructions are outdated. If you get stuck somewhere, look at the Ubuntu section.
2. Obtain Factorio by either of these two methods:
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**
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. Type `node client.js start [instancename]` to create a new instance.
4. Run `node client.js start [instancename]` to create a new instance. Repeat it again to start the 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.
@@ -230,18 +259,6 @@ According to [this link](https://askubuntu.com/questions/839520/open-port-443-fo
sudo setcap 'cap_net_bind_service=+ep' $(readlink -f $(which node))
### InitializationError: First argument must be an abstract-leveldown compliant store
do:
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
If you follow the ubuntu guide on WSL (Windows Subsystem for Linux, Bash on Ubuntu on Windows specifically), you will find that the website works on localhost and on your local ip, but not on the global ip. This is also true when you correctly port-forwarded the correct ports. Even when routing this server through nginx in WSL, the issue persists. Then, on a hunch, I tried to run nginx from windows itself and found that this DID work. It came to me that the only usage difference between the 2 versions of nginx is that I got a Windows Firewall popup.
@@ -283,7 +300,7 @@ node client.js list
```
git pull
npm install
npm install --only=production
```
3. Download the latest version of the factorioClusterioMod from its github repo
+104 -104
View File
@@ -1,4 +1,5 @@
const fs = require('fs-extra');
const Tail = require('tail').Tail;
const https = require('follow-redirects').https;
const needle = require("needle");
const child_process = require('child_process');
@@ -6,19 +7,18 @@ const path = require('path');
const syncRequest = require('sync-request');
const request = require("request");
const ncp = require('ncp').ncp;
const Rcon = require('rcon-client').Rcon;
const hashFiles = require('hash-files');
const _ = require('underscore');
const Rcon = require('rcon-client-fork').Rcon;
const deepmerge = require("deepmerge");
const getMac = require('getmac').getMac;
const rmdirSync = require('rmdir-sync');
const ioClient = require("socket.io-client");
// internal libraries
const objectOps = require("./lib/objectOps.js");
const fileOps = require("_app/fileOps");
const stringUtils = require("./lib/stringUtils.js");
const configManager = require("./lib/manager/configManager.js");
const objectOps = require("lib/objectOps.js");
const fileOps = require("lib/fileOps");
const stringUtils = require("lib/stringUtils.js");
const configManager = require("lib/manager/configManager.js");
const hashFile = require('lib/hash').hashFile;
// argument parsing
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 \
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.");
process.exitCode = 1;
return;
}
const needleOptionsWithTokenAuthHeader = {
compressed:true,
headers: {
'x-access-token': config.masterAuthToken
},
@@ -187,7 +190,7 @@ if (!command || command == "help" || command == "--help") {
if(tool == "mods"){
(async function(){try{
// 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
if(action == "list"){
@@ -227,7 +230,7 @@ if (!command || command == "help" || command == "--help") {
}
} else if(tool == "plugins"){
(async function(){try{
const pluginManager = require("./lib/manager/pluginManager.js")(config);
const pluginManager = require("lib/manager/pluginManager.js")(config);
if(action == "list"){
await pluginManager.listPlugins();
} else if(action == "add" || action == "install" || action == "download"){
@@ -296,11 +299,10 @@ if (!command || command == "help" || command == "--help") {
fs.mkdirSync(instancedirectory + "/saves/");
fs.writeFileSync(instancedirectory + "/script-output/output.txt", "");
fs.writeFileSync(instancedirectory + "/script-output/orders.txt", "");
fs.writeFileSync(instancedirectory + "/script-output/txbuffer.txt", "");
fs.mkdirSync(instancedirectory + "/mods/");
fs.mkdirSync(instancedirectory + "/instanceMods/");
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)
});
@@ -358,7 +360,8 @@ write-data=${ path.resolve(config.instanceDirectory, instance) }\r\n
'--rcon-port', Number(process.env.RCONPORT) || instconf.clientPort,
'--rcon-password', instconf.clientPassword,
], {
'stdio': ['pipe', 'pipe', 'pipe']
'stdio': ['pipe', 'pipe', 'pipe'],
'detached': process.platform === "linux",
}
);
factorio.stdout.on("data", data => {
@@ -470,7 +473,8 @@ write-data=${ path.resolve(config.instanceDirectory, instance) }\r\n
'--server-settings', instancedirectory + '/server-settings.json',
'--port', args.port || Number(process.env.FACTORIOPORT) || instanceconfig.factorioPort
], {
'stdio': ['pipe', 'pipe', 'pipe']
'stdio': ['pipe', 'pipe', 'pipe'],
'detached': process.platform === "linux",
}
);
@@ -561,12 +565,18 @@ write-data=${ path.resolve(config.instanceDirectory, instance) }\r\n
}
// ensure instancemanagement only ever runs once
_.once(instanceManagement);
var _instanceInitialized;
async function instanceManagement(instanceconfig) {
if (_instanceInitialized) return;
_instanceInitialized = true;
console.log("Started instanceManagement();");
/* 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 => {
console.log("SOCKET | registering slave!");
socket.emit("registerSlave", {
@@ -576,15 +586,12 @@ async function instanceManagement(instanceconfig) {
setInterval(B=> socket.emit("heartbeat"), 10000);
// 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 plugins = [];
for(let i = 0; i < pluginsToLoad.length; i++){
let pluginLoadStarted = Date.now();
let log = function(message) {
console.log("Clusterio | "+ pluginsToLoad[i].name + " | " + message);
}
let combinedConfig = deepmerge(instanceconfig,config,{clone:true});
let pluginConfig = pluginsToLoad[i];
@@ -596,7 +603,7 @@ async function instanceManagement(instanceconfig) {
let pluginClass = require(path.resolve(pluginConfig.pluginPath, "index.js"));
plugins[i] = new pluginClass(combinedConfig, async function(data, callback){
if(data && data.toString('utf8')[0] != "/") {
log(data.toString('utf8'));
console.log("Clusterio | "+ pluginsToLoad[i].name + " | " + data.toString('utf8'));
return true;
} else if (data && data.toString('utf8')[0] == "/"){
return messageInterface(data.toString('utf8'), callback);
@@ -622,37 +629,50 @@ async function instanceManagement(instanceconfig) {
global.subscribedFiles[pluginConfig.scriptOutputFileSubscription] = true;
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 (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
);
}
}
}
if(!pluginConfig.fileReadDelay || pluginConfig.fileReadDelay == 0) {
// only wipe the file on restart for now, should most likely be rotated during runtime too
fs.writeFileSync(instancedirectory + "/script-output/" + pluginConfig.scriptOutputFileSubscription, "");
let tail = new Tail(instancedirectory + "/script-output/" + pluginConfig.scriptOutputFileSubscription);
tail.on("line", function (data) {
plugins[i].scriptOutput(data);
});
} else {
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 (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`);
} else {
// this plugin doesn't have a client portion. Maybe it runs on the master only?
@@ -675,7 +695,7 @@ async function instanceManagement(instanceconfig) {
mods:modHashes,
instanceName: instance,
info: instanceconfig.info,
}
};
if(playerCount){
payload.playerCount = playerCount.replace(/(\r\n\t|\n|\r\t)/gm, "");
} else {
@@ -717,19 +737,19 @@ async function instanceManagement(instanceconfig) {
setTimeout(function(){hashMods(instance, uploadMods)}, 5000);
function uploadMods(modHashes) {
// [{modName:string,hash:string}, ... ]
for(let i=0;i<modHashes.length;i++){
let payload = {
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.");
if(response && body && body == "found") {
console.log("master has mod "+modHashes[i].modName);
} else if (response && body && typeof body == "string") {
let mod = response.body;
if(config.uploadModsToMaster){
if (config.uploadModsToMaster) {
// [{modName:string,hash:string}, ... ]
for(let i=0;i<modHashes.length;i++){
let payload = {
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.");
if(response && body && body == "found") {
console.log("master has mod "+modHashes[i].modName);
} else if (response && body && typeof body == "string") {
let mod = response.body;
console.log("Sending mod: " + mod);
// Send mods master says it wants
// response.body is a string which is a modName.zip
@@ -746,11 +766,11 @@ async function instanceManagement(instanceconfig) {
});
var form = req.form();
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 ---------------------------------------------------------------------
@@ -761,42 +781,22 @@ function hashMods(instanceName, callback) {
if(!callback) {
throw new Error("ERROR in function hashMods NO CALLBACK");
}
function callback2(hash, modName){
hashedMods[hashedMods.length] = {
modName: modName,
hash: hash,
}
// check if this callback has ran once for each mod
if(hashedMods.length == instanceMods.length) {
callback(hashedMods);
}
//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
let modsDir = config.instanceDirectory+"/"+instanceName+"/mods/";
function hashMod(name) {
if (path.extname(name) != ".zip") {
// Can't hash unzipped mods, return null that's filtered out later
return null;
} else {
return hashFile(modsDir+name).then(hash => (
{modName: name, hash: hash}
));
}
}
for(let i=0; i<instanceMods.length; i++){
let path = config.instanceDirectory+"/"+instanceName+"/mods/"+instanceMods[i];
let name = instanceMods[i];
let options = {
files:path,
}
// 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;
}
});
}
let promises = fs.readdirSync(modsDir).map(hashMod);
Promise.all(promises).then(hashes => {
// Remove null entries from hashMod
callback(hashes.filter(entry => entry !== null));
});
}
+2 -2
View File
@@ -14,7 +14,7 @@
"masterPort": 8080,
"__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",
"sslPrivKey": "database/certificates/cert.key",
@@ -50,7 +50,7 @@
"__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,
"useNeuralNetDoleDivider": true,
"uploadModsToMaster": true,
"uploadModsToMaster": false,
"msBetweenCommands": 10,
"allowRemoteCommandExecution": 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
File renamed without changes.
@@ -32,7 +32,7 @@ module.exports = function(config){
}
} else {
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);
throw err;
}
File renamed without changes.
File renamed without changes.
@@ -15,9 +15,8 @@ const child_process = require("child_process");
const path = require("path");
const mkdirp = require("mkdirp");
const appRootPath = require("app-root-path");
const fileOps = require("_app/fileOps");
const fileOps = require("lib/fileOps");
module.exports = class factorioServer {
constructor({
@@ -165,4 +164,4 @@ module.exports = class factorioServer {
});
});
}
}
}
@@ -2,7 +2,6 @@ const assert = require("assert");
const fs = require("fs");
const path = require("path");
const appRootPath = require("app-root-path");
const parallel = require("mocha.parallel");
const mkdirp = require("mkdirp");
@@ -75,7 +74,7 @@ describe("class factorioServer/index.js", ()=>{
describe(".createMap(mapName.zip)", ()=>{
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)
let relativePath = __dirname.replace(appRootPath, "").substr(1);
let relativePath = path.relative('.', __dirname);
let server = new factorioServer({
instanceDirectory: path.join(relativePath, "_testData_createMap"),
instanceName: "Unit testing instance",
@@ -93,7 +92,7 @@ describe("class factorioServer/index.js", ()=>{
});
it("supports custom filenames", async function(){
this.timeout(20000);
let relativePath = __dirname.replace(appRootPath, "").substr(1);
let relativePath = path.relative('.', __dirname);
let server = new factorioServer({
instanceDirectory: path.join(relativePath, "_testData_createMap_2"),
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 fs = require("fs");
var fileOps = require("_app/fileOps");
var fileOps = require("lib/fileOps");
var generateSSLcert = require("./generateSSLcert.js")
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");
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
*/
const rightpad = require("right-pad");
const asTable = require("as-table").configure({delimiter: ' | '});
const moment = require("moment");
const syncRequest = require("sync-request");
@@ -13,7 +12,7 @@ const factorioAPI = require("node-factorio-api");
const sLog = require("single-line-log").stdout;
const query = require('cli-interact').getYesNo;
const fileOps = require("_app/fileOps");
const fileOps = require("lib/fileOps");
// const config = require("./../../config");
-1
View File
@@ -56,7 +56,6 @@ function download(url, file, callback){
}
var directories = [
"database/linvodb",
"sharedMods",
"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
-- 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
hotpatch_tools.static_mod('freeplay', '1.0.0', [===[
hotpatch_tools.static_mod('freeplay', '0.17.50', [===[
local util = require("util")
local silo_script = require("silo-script")
@@ -127,6 +127,7 @@ remote.add_interface("freeplay",
global.chart_distance = tonumber(value)
end
})
]===])
-- Multi-file test
+1 -1
View File
@@ -1,5 +1,5 @@
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
+23 -23
View File
@@ -1,5 +1,5 @@
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
@@ -86,7 +86,6 @@ on_gui_click_handlers = {
--on_gui_click_handlers['hotpatch-menu.IDE'](e)
--do return end
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
@@ -100,23 +99,20 @@ on_gui_click_handlers = {
end
menu.visible = not menu.visible
end,
['hotpatch-menu'] = function(e)
['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)
if e.element.name ~= 'hotpatch-menu' then
local menu = left['hotpatch-menu']
menu.visible = not menu.visible
on_gui_click_handlers[e.element.name](e)
end
end,
['hotpatch-menu.IDE'] = 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 IDE = center['hotpatch-IDE']
if not IDE then
IDE = center.add{type = 'frame', name = 'hotpatch-IDE', direction = 'vertical', caption = 'Hotpatch IDE', style = mod_gui.frame_style}
@@ -155,8 +151,6 @@ on_gui_click_handlers = {
end,
['hotpatch-menu.console'] = 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 hotpatch_console = center['hotpatch-console']
@@ -192,9 +186,6 @@ on_gui_click_handlers = {
end,
['hotpatch-console-run'] = 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 hotpatch_console = center['hotpatch-console']
@@ -252,8 +243,6 @@ on_gui_click_handlers = {
end,
['hotpatch-IDE-file'] = 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 element = e.element
@@ -296,8 +285,6 @@ end)
on_gui_selection_state_changed_handlers = {
['hotpatch-console-mod-selector'] = 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 element = e.element
@@ -316,8 +303,6 @@ on_gui_selection_state_changed_handlers = {
end,
['hotpatch-IDE-mod-selector'] = 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 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
+100 -89
View File
@@ -15,6 +15,9 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
log('Hotpatch runtime initializing...')
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}
-- 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_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)
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
local static_mods = {}
-- static_mods[i] = {}
@@ -127,12 +133,6 @@ local function static_mod(name, version, code, files)
table.insert(static_mods, mod)
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
-- 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)
@@ -334,7 +334,7 @@ local function debug_log(message, mod_name, stack_level)
local class = 'hotpatch.info'
local log_type = (mod_name and 'hotpatch.log-mod') or 'hotpatch.log'
local severity
if type(message) == 'table' then
if type(message) == 'table' then
severity = message[1]:match('.-%-([^%-]*)%.')
if not severity then
severity = message[1]:match('.-%-.-%-(.*)%.')
@@ -346,7 +346,7 @@ local function debug_log(message, mod_name, stack_level)
else
severity = 'always'
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_log_to_console_only then
if debug_log_to_RCON then
@@ -365,15 +365,15 @@ debug_log({'hotpatch-info.logging-enabled'})
-- track _ENV accesses
setmetatable(_ENV, {
__index = function(_, k)
debug_log({'hotpatch-trace.nil-var-access', k}, nil, 3)
return nil
end,
__newindex = function(t, k, v)
debug_log({'hotpatch-trace.nil-var-assignment', k}, nil, 3)
rawset(t,k,v)
end,
__metatable = false
__index = function(_, k)
debug_log({'hotpatch-trace.nil-var-access', k}, nil, 3)
return nil
end,
__newindex = function(t, k, v)
debug_log({'hotpatch-trace.nil-var-assignment', k}, nil, 3)
rawset(t,k,v)
end,
__metatable = false
})
debug_log({'hotpatch-info.metatable-installed'})
@@ -395,7 +395,7 @@ local libraries = {
for k, v in pairs(libraries) do
debug_log({'hotpatch-info.loading-library', v})
loaded_libraries[k] = require(v)
loaded_libraries[k] = require(v)
end
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 mod = {}
if index then
-- TODO: notify about installing over top of existing mod
mod = global.mods[index]
debug_log('attempt to install mod that is already installed, reinstalling: ' .. mod_name)
mod = table.remove(global.mods, index)
table.insert(global.mods, mod)
else
--next free index
table.insert(global.mods, mod)
@@ -614,20 +615,20 @@ load_mod = function(installed_index)
-- mods private package.loaded
local loaded = {}
-- copy the current environment
for k,v in pairs(_ENV) do
env[k] = v
end
for k,v in pairs(_ENV) do
env[k] = v
end
-- copy package.loaded
for k,v in pairs(_ENV.package.loaded) do
loaded[k] = v
end
loaded[k] = v
end
loaded._G = env
-- so many ways to escape sandboxes...
for k,v in pairs(_ENV.package) do
pack[k] = v
end
pack[k] = v
end
pack.loaded = loaded
env.package = pack
loaded.package = pack
@@ -681,15 +682,15 @@ load_mod = function(installed_index)
-- I blame Nexela for this
path = path:gsub('/', '.')
path = path:gsub('\\', '.')
local alt_path = ''
local alt_path = ''
if env.package._current_path_in_package then
alt_path = env.package._current_path_in_package .. path
end
if mod_obj.loaded_files[path] then
debug_log({'hotpatch-trace.cached-load-require', path}, mod_name)
return mod_obj.loaded_files[path]
elseif mod_obj.loaded_files[alt_path] then
debug_log({'hotpatch-trace.cached-load-require', alt_path}, mod_name)
elseif mod_obj.loaded_files[alt_path] then
debug_log({'hotpatch-trace.cached-load-require', alt_path}, mod_name)
return mod_obj.loaded_files[alt_path]
else
local oldbase = env.package._current_path_in_package
@@ -711,7 +712,7 @@ load_mod = function(installed_index)
error(err)
end
end
file = global.mods[installed_index].files[alt_path]
file = global.mods[installed_index].files[alt_path]
if file then
debug_log({'hotpatch-trace.load-require', alt_path}, mod_name)
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)
env.package._current_path_in_package = oldbase
local lib = package.loaded[path]
if not lib then
debug_log({'hotpatch-error.path-not-found', path}, mod_name, 3)
local lib = package.loaded[path]
if not lib then
debug_log({'hotpatch-error.path-not-found', path}, mod_name, 3)
error(path .. ' not found')
end
end
return lib
end
end
@@ -808,10 +809,6 @@ reset_mod = function(loaded_index)
local install_index = find_installed_mod(mod.name)
mod = global.mods[install_index]
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)
end
@@ -833,15 +830,38 @@ reset_mod_events = function(loaded_index)
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)
local mod = loaded_mods[loaded_index]
if mod then
local mod_name = mod.name
local old_global = table.deepcopy(mod.env.global)
if strict_mode then
--mod.env.global = {}
end
local old_global = mod.env.global
mod.env.global = wrap_table(old_global)
debug_log({'hotpatch-info.running'}, mod_name)
local success, result = xpcall(mod.code, debug.traceback)
@@ -856,26 +876,8 @@ run_mod = function(loaded_index)
return false
end
if strict_mode then
--if mod.env.global ~= {} then
--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.env.global = old_global
old_global = nil
mod.running = true
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
local success, result = xpcall(mod.on_init, debug.traceback)
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)
return false
end
@@ -1018,13 +1020,16 @@ mod_on_load = function(loaded_index)
if mod then
debug_log({'hotpatch-trace.mod-on-load'}, mod.name)
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)
if not success then
debug_log({'hotpatch-error.on-load-failed'}, mod_name)
debug_log(result, mod_name)
return false
end
mod.env.global = old_global
end
register_mod_events(loaded_index)
return true
@@ -1040,7 +1045,7 @@ mod_on_configuration_changed = function(loaded_index, config)
if mod.on_configuration_changed then
local success, result = xpcall(mod.on_configuration_changed, debug.traceback, config)
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)
return false
end
@@ -1189,21 +1194,27 @@ on_load = function()
end
-- run mods which loaded successfully
local failed_mods = {}
local failed_mods = {}
--log('before')
--log(serpent.block(global))
for i = 1, #loaded_mods do
if run_mod(i) then
if not mod_on_load(i) then
table.insert(failed_mods, i)
end
else
table.insert(failed_mods, i)
end
if not mod_on_load(i) then
table.insert(failed_mods, i)
end
else
table.insert(failed_mods, i)
end
end
--log('after')
--log(serpent.block(global))
-- unload mods which failed to run
for i = 1, #failed_mods do
unload_mod(failed_mods[i])
end
-- 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.on-load'}})
@@ -1211,16 +1222,16 @@ end
on_configuration_changed = function(config)
debug_log({'hotpatch-info.on-configuration-changed'})
local failed_mods = {}
local failed_mods = {}
for i = 1, #loaded_mods do
if not mod_on_configuration_changed(i, config) then
table.insert(failed_mods, i)
end
table.insert(failed_mods, i)
end
end
for i = 1, #failed_mods do
unload_mod(failed_mods[i])
end
for i = 1, #failed_mods do
unload_mod(failed_mods[i])
end
debug_log({'hotpatch-info.complete', {'hotpatch-info.on-configuration-changed'}})
end
@@ -1324,7 +1335,7 @@ local mod_tools = setmetatable({
static_mod = static_mod, -- (name, version, code, files)
set_debug_level = function(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,
},{
__index = mod_tools_internal,
@@ -1,5 +1,5 @@
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
@@ -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=Това е режимът на свободна игра. Задачата ви е да изстреляте ракета в космоса. За това ще е нужно да построите ракетна шахта и изстреляте ракетата със спътник. Ще трябва да проучите развити технологии, които ще отключат ракетната шахта. Започнете с малко и постепенно автоматизирайте процеса, като не забравяте да се защитавате от местните форми на живот.
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.
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.
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.
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ų.
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.
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.
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=自由模式
+114 -116
View File
@@ -1,7 +1,6 @@
/**
Clusterio master server. Facilitates communication between slaves through
a webserver, storing data related to slaves like production graphs and
combinator signals.
a webserver, storing data related to slaves like production graphs.
@module clusterioMaster
@author Danielv123
@@ -26,10 +25,6 @@ process.on('unhandledRejection', r => console.log(r));
// configgy stuff
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
const args = require('minimist')(process.argv.slice(2));
@@ -40,14 +35,13 @@ const deepmerge = require("deepmerge");
const path = require("path");
const fs = require("fs-extra");
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const crypto = require('crypto');
const base64url = require('base64url');
const moment = require("moment");
const request = require("request")
const request = require("request");
// constants
console.log(`Requiring config from ${args.config || './config'}`)
console.log(`Requiring config from ${args.config || './config'}`);
const config = require(args.config || './config');
config.databaseDirectory = args.databaseDirectory || config.databaseDirectory || "./database";
const masterModFolder = path.join(config.databaseDirectory, "/masterMods/");
@@ -55,12 +49,10 @@ mkdirp.sync(config.databaseDirectory);
mkdirp.sync(masterModFolder);
// homebrew modules
const getFactorioLocale = require("./lib/getFactorioLocale");
const stringUtils = require("./lib/stringUtils");
const fileOps = require("_app/fileOps");
const getFactorioLocale = require("lib/getFactorioLocale");
// homemade express middleware for token auth
const authenticate = require("./lib/authenticate")(config);
const authenticate = require("lib/authenticate")(config);
/** Sync */
function randomStringAsBase64Url(size) {
@@ -68,7 +60,7 @@ function randomStringAsBase64Url(size) {
}
if (!fs.existsSync("secret-api-token.txt")) {
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, {
expiresIn: 86400*365 // expires in 1 year
}));
@@ -90,7 +82,9 @@ const fileUpload = require('express-fileupload');
var app = express();
app.use(cookieParser());
app.use(bodyParser.json());
app.use(bodyParser.json({
limit: '10mb',
}));
app.use(bodyParser.urlencoded({
parameterLimit: 100000,
limit: '10mb',
@@ -176,18 +170,13 @@ const prometheusUPSGauge = new Prometheus.Gauge({
help: 'UPS of the current server',
labelNames: ["instanceID", "instanceName"],
});
const prometheusTickGauge = new Prometheus.Gauge({
name: prometheusPrefix+'tick_gauge',
help: 'Servers current gametick',
labelNames: ["instanceID", "instanceName"],
});
const prometheusMasterInventoryGauge = new Prometheus.Gauge({
name: prometheusPrefix+'master_inventory_gauge',
help: 'Amount of items stored on master',
labelNames: ["itemName"],
});
setInterval(()=>{
fs.writeFileSync("database/items.json", JSON.stringify(db.items));
fs.writeFileSync("database/items.json", JSON.stringify(db.items));
},config.autosaveInterval || 60000);
/**
GET Prometheus metrics endpoint. Returns performance and usage metrics in a prometheus readable format.
@@ -198,7 +187,7 @@ GET Prometheus metrics endpoint. Returns performance and usage metrics in a prom
app.get('/metrics', (req, res) => {
endpointHitCounter.labels(req.route.path).inc();
res.set('Content-Type', Prometheus.register.contentType);
/// gather some static metrics
registerMoreMetrics();
res.end(Prometheus.register.metrics());
@@ -248,7 +237,7 @@ const db = {};
} catch (e){
db.items = {};
}
})()
})();
db.items.addItem = function(object) {
if(object.name == "addItem" || object.name == "removeItem") {
@@ -262,7 +251,7 @@ db.items.addItem = function(object) {
}
return true;
}
}
};
db.items.removeItem = function(object) {
if(object.name == "addItem" || object.name == "removeItem") {
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;
}
}
};
// store slaves and inventory in a .json full of JSON data
process.on('SIGINT', shutdown); // ctrl + c
@@ -308,8 +297,8 @@ async function shutdown() {
console.log("Clusterio cleanly exited in "+(Date.now()-exitStartTime)+"ms");
process.exit(0);
} catch(e) {
console.log(e)
console.log("Clusterio failed to exit cleanly. Time elapsed: "+(Date.now()-exitStartTime)+"ms")
console.log(e);
console.log("Clusterio failed to exit cleanly. Time elapsed: "+(Date.now()-exitStartTime)+"ms");
process.exit(1)
}
}
@@ -351,7 +340,7 @@ app.post("/api/editSlaveMeta", authenticate.middleware, function(req,res) {
endpointHitCounter.labels(req.route.path).inc();
// request.body should be an object
// {instanceID, pass, meta:{x,y,z}}
if(req.body && req.body.instanceID && req.body.meta){
if(slaves[req.body.instanceID]){
if(!slaves[req.body.instanceID].meta) {
@@ -360,8 +349,8 @@ app.post("/api/editSlaveMeta", authenticate.middleware, function(req,res) {
slaves[req.body.instanceID].meta = deepmerge(slaves[req.body.instanceID].meta, req.body.meta, {clone:true});
let metaPortion = JSON.stringify(req.body.meta);
if(metaPortion.length > 50) {
metaPortion = metaPortion.substring(0,20) + "...";
}
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);
res.sendStatus(200);
} else {
@@ -387,7 +376,7 @@ app.post("/api/getSlaveMeta", function (req, res) {
}
res.send(JSON.stringify(slaves[req.body.instanceID].meta))
} else {
res.status(404)
res.status(404);
res.send('{"status": 404, "info": "Slave not registered"}')
}
} else {
@@ -440,8 +429,8 @@ app.post("/api/uploadMod", authenticate.middleware, function(req,res) {
});
} else {
res.send('No files were uploaded.');
return;
}
}
});
/**
Prepare the slaveCache for both /api/slaves calls
@@ -480,15 +469,15 @@ POST endpoint for getting the rconPasswords of all our slaves
@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);
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);
});
/*
var recievedItemStatistics = new averagedTimeSeries({
@@ -559,20 +548,20 @@ app.post("/api/place", authenticate.middleware, function(req, res) {
name:req.body.name,
count:req.body.count
});
// Attempt confirming
res.send("success");
} else {
res.send("failure");
}
});
const routes_api_remove = require("./routes/api/remove.js")
var neuralDole = null
const routes_api_remove = require("./routes/api/remove.js");
var neuralDole = null;
if(config.useNeuralNetDoleDivider)//Only initialize neural network when it's enabled, otherwise it might override gauge
neuralDole=new routes_api_remove.neuralDole({
items: db.items, gaugePrefix: prometheusPrefix
})
});
/**
POST endpoint to remove items from DB when client orders items.
@@ -597,7 +586,7 @@ app.post("/api/remove", authenticate.middleware, function(req, res) {
object.instanceName = "unknown";
}
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);
if((item === undefined)
||((
@@ -613,7 +602,7 @@ app.post("/api/remove", authenticate.middleware, function(req, res) {
let numberToRemove = Math.min(Math.abs(Number(object.count)),Number(item));
db.items.removeItem({count: numberToRemove, name: object.name});
res.send({count: numberToRemove, name: object.name});
// track statistics and do graphing things
prometheusImportGauge.labels(object.instanceID, object.name).inc(Number(numberToRemove) || 0);
let sentItemStatistics = sentItemStatisticsBySlaveID[object.instanceID];
@@ -702,19 +691,19 @@ app.get("/api/inventoryAsObject", function(req, res) {
*/
// wrap a request in an promise
async function downloadPage(url) {
return new Promise((resolve, reject) => {
request(url, (error, response, body) => {
resolve(body);
});
});
}
async function downloadPage(url) {
return new Promise((resolve, reject) => {
request(url, (error, response, body) => {
resolve(body);
});
});
}
app.get("/api/modmeta", async function(req, res) {
endpointHitCounter.labels(req.route.path).inc();
res.header("Access-Control-Allow-Origin", "*");
res.setHeader('Content-Type', 'application/json');
let modData = await downloadPage("https://mods.factorio.com/api/mods/" + req.query.modname);
res.header("Access-Control-Allow-Origin", "*");
res.setHeader('Content-Type', 'application/json');
let modData = await downloadPage("https://mods.factorio.com/api/mods/" + req.query.modname);
res.send(modData);
});
@@ -731,36 +720,36 @@ Requires x-access-token header to be set. Find you api token in secret-api-token
*/
app.post("/api/runCommand", authenticate.middleware, function(req,res) {
endpointHitCounter.labels(req.route.path).inc();
// validate request.body
let body = req.body;
// validate request.body
let body = req.body;
if (
body.broadcast
&& body.command
&& typeof body.command == "string"
&& body.command[0] == "/")
{
let instanceResponses = [];
for (let instanceID in wsSlaves) {
// skip loop if the property is from prototype
if (!wsSlaves.hasOwnProperty(instanceID)) continue;
wsSlaves[instanceID].runCommand(body.command);
}
res.status(200).send({auth: true, message: "success", response: "Cluster wide messaging initiated"});
} else if (
body.instanceID
&& wsSlaves[body.instanceID]
&& body.command
&& typeof body.command == "string"
&& body.command[0] == "/")
{
// 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"});
}
if (
body.broadcast
&& body.command
&& typeof body.command == "string"
&& body.command[0] == "/")
{
let instanceResponses = [];
for (let instanceID in wsSlaves) {
// skip loop if the property is from prototype
if (!wsSlaves.hasOwnProperty(instanceID)) continue;
wsSlaves[instanceID].runCommand(body.command);
}
res.status(200).send({auth: true, message: "success", response: "Cluster wide messaging initiated"});
} else if (
body.instanceID
&& wsSlaves[body.instanceID]
&& body.command
&& typeof body.command == "string"
&& body.command[0] == "/")
{
// 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.
@@ -797,16 +786,17 @@ if(args.masterPort || config.masterPort){
}
/* Websockets for remoteMap */
var io = require("socket.io")(server);
var io = require("socket.io")();
const ioMetrics = require("socket.io-prometheus");
ioMetrics(io);
var slaveMappers = {};
class slaveMapper {
constructor(instanceID, socket) {
this.instanceID = instanceID;
this.socket = socket;
this.lastBeat = Date.now();
this.socket.on("heartbeat", () => {
prometheusWsUsageCounter.labels('heartbeat', this.instanceID).inc();
// we aren't ready to die yet apparently
@@ -821,7 +811,7 @@ class slaveMapper {
prometheusWsUsageCounter.labels('sendEntity', this.instanceID).inc();
Object.keys(mapRequesters).forEach(requesterName => {
let requester = mapRequesters[requesterName];
if(requester.instanceID == this.instanceID){
// this mapRequester is listening to this slaveMapper, so we send it updates
requester.socket.emit("displayEntity", entity);
@@ -830,6 +820,8 @@ class slaveMapper {
});
}
}
var mapRequesters = {};
class mapRequester {
constructor(requesterID, socket, instanceID){
@@ -837,7 +829,7 @@ class mapRequester {
this.socket = socket;
this.instanceID = instanceID;
this.lastBeat = Date.now();
this.socket.on("heartbeat", () => {
prometheusWsUsageCounter.labels('heartbeat', "other").inc();
// we aren't ready to die yet apparently
@@ -869,6 +861,8 @@ class mapRequester {
});
}
}
/* Websockets for send and recieve combinators */
var wsSlaves = {};
class wsSlave {
@@ -876,19 +870,11 @@ class wsSlave {
this.instanceID = instanceID;
this.socket = socket;
this.lastBeat = Date.now();
this.socket.on("heartbeat", () => {
prometheusWsUsageCounter.labels('heartbeat', this.instanceID).inc();
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
this.commandsWaitingForReturn = {};
this.socket.on("runCommandReturnValue", resp => {
@@ -898,7 +884,7 @@ class wsSlave {
delete this.commandsWaitingForReturn[resp.commandID];
}
});
this.socket.on("gameChat", data => {
prometheusWsUsageCounter.labels('gameChat', this.instanceID).inc();
if(typeof data === "object"){
@@ -909,7 +895,7 @@ class wsSlave {
});
}
});
this.socket.on("alert", alert => {
prometheusWsUsageCounter.labels('alert', this.instanceID).inc();
if(typeof alert === "object"){
@@ -927,6 +913,19 @@ class wsSlave {
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) {
// cleanup dead sockets from disconnected people
let terminatedConnections = 0;
@@ -941,10 +940,10 @@ io.on('connection', function (socket) {
});
});
if(terminatedConnections > 0) console.log("SOCKET | There are currently "+currentConnections+" websocket connections, deleting "+terminatedConnections+" on timeout");
// tell our friend that we are listening
setTimeout(()=>socket.emit('hello', { hello: 'world' }), 5000);
/* initial processing for remoteMap */
socket.on('registerSlaveMapper', function (data) {
prometheusWsUsageCounter.labels('registerSlaveMapper', "other").inc();
@@ -959,7 +958,7 @@ io.on('connection', function (socket) {
socket.emit("mapRequesterReady", true);
console.log("remoteMap | SOCKET registered map requester for "+data.instanceID);
});
/* Websockets for send and recieve combinators */
socket.on("registerSlave", function(data) {
prometheusWsUsageCounter.labels('registerSlave', "other").inc();
@@ -981,17 +980,9 @@ io.on('connection', function (socket) {
});
// 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(){
const pluginManager = require("./lib/manager/pluginManager.js")(config);
let startPluginLoad = Date.now();
const pluginManager = require("lib/manager/pluginManager.js")(config);
let plugins = [];
let pluginsToLoad = await pluginManager.getPlugins();
for(let i = 0; i < pluginsToLoad.length; i++){
@@ -1012,7 +1003,7 @@ async function getPlugins(){
}),
pluginConfig,
});
console.log("Loaded plugin "+pluginConfig.name+" in "+(Date.now() - pluginStartedLoading)+"ms");
}
}
@@ -1021,8 +1012,15 @@ async function getPlugins(){
let plugin = plugins[i];
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;
}
masterPlugins = await getPlugins();
io.attach(server);
module.exports = app;
})();
+17 -25
View File
@@ -1,35 +1,31 @@
{
"name": "clusterio",
"version": "1.7.0",
"version": "1.2.4",
"description": "Interserver communication for factorio",
"repository": "https://github.com/Danielv123/factorioClusterio",
"main": "master.js",
"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",
"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",
"bundle-dependencies": "bundle-dependencies",
"snyk-protect": "snyk protect",
"prepublish": "npm run snyk-protect"
"bundle-dependencies": "bundle-dependencies"
},
"keywords": [
"factorio"
],
"author": "Danielv123",
"license": "ISC",
"license": "MIT",
"engines": {
"node": ">=10"
},
"dependencies": {
"app-root-path": "^2.0.1",
"as-table": "^1.0.31",
"averaged-timeseries": "^1.4.0",
"base64url": "^3.0.0",
"bcrypt": "^2.0.1",
"bcrypt": "=3.0.6",
"bcrypt-promise": "^2.0.0",
"bcryptjs": "^2.4.3",
"body-parser": "^1.18.2",
"cache-require-paths": "^0.3.0",
"cli": "^1.0.1",
"cli-interact": "^0.1.9",
"codecov": "^3.0.1",
@@ -43,10 +39,9 @@
"follow-redirects": "^1.5.0",
"fs-extra": "^6.0.1",
"getmac": "^1.4.0",
"hash-files": "^1.1.1",
"html5-validator": "^1.0.7",
"ini": "^1.3.5",
"jsonwebtoken": "^8.2.1",
"lib": "file:./lib",
"luamin": "^1.0.4",
"minimist": "^1.2.0",
"mkdirp": "^0.5.1",
@@ -54,33 +49,30 @@
"moment": "^2.22.2",
"ncp": "^2.0.0",
"needle": "^2.2.4",
"node-factorio-api": "^0.3.1-alpha",
"node-factorio-api": "^0.3.8",
"node-forge": "^0.7.1",
"prom-client": "^10.2.3",
"rcon-client": "git+https://gitlab.com/Hornwitser/rcon-client.git",
"rcon-client-fork": "^3.2.0",
"request": "^2.83.0",
"right-pad": "^1.0.1",
"rmdir-sync": "^1.0.1",
"sanitizer": "^0.1.3",
"simple-git": "^1.96.0",
"single-line-log": "^1.1.2",
"snyk": "^1.134.2",
"socket.io": "^2.0.4",
"socket.io-client": "^2.0.4",
"socket.io-prometheus": "^0.2.1",
"sync-request": "^4.0.1",
"typescript-collections": "^1.3.2",
"underscore": "^1.8.3"
"tail": "^2.0.2",
"typescript-collections": "^1.3.2"
},
"devDependencies": {
"documentation": "^5.4.0",
"istanbul": "^0.4.5",
"html5-validator": "^1.0.7",
"mocha": "^3.5.3",
"mocha-sinon": "^2.0.0",
"mocha.parallel": "^0.15.5",
"nock": "^9.1.6",
"nyc": "^14.1.1",
"sinon": "^2.4.1",
"supertest": "^3.0.0"
},
"snyk": true
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
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');
-149
View File
@@ -1,149 +0,0 @@
--[[
HUGE SHOUTOUT TO HORNWITSER FOR WRITING THIS SEXY AS MODLOADER <3
and for not getting mad when i coppyed it :)
]]
local modules = {
require("pdnc"),
require("doomsday"),
require("gui"),
}
script.on_init(function()
for i,mod in ipairs(modules) do
if mod.on_init then
mod.on_init()
end
end
end)
script.on_load(function()
for i,mod in ipairs(modules) do
if mod.on_load then
mod.on_load()
end
end
end)
script.on_configuration_changed(function(data)
for i,mod in ipairs(modules) do
if mod.on_configuration_changed then
mod.on_configuration_changed(data)
end
end
end)
local function register_tick_handlers()
local ticks = {}
for i,mod in ipairs(modules) do
print(mod.on_nth_ticks)
if mod.on_nth_ticks then
mod_ticks = mod.on_nth_ticks
for t,f in pairs(mod_ticks) do
if not ticks[t] then
ticks[t] = {}
end
table.insert(ticks[t], f)
end
end
end
for t,fs in pairs(ticks) do
script.on_nth_tick(t, function(event_data)
for i,f in ipairs(fs) do
f(event_data)
end
end)
end
end
register_tick_handlers()
local function register_events()
local events = {}
for i,mod in ipairs(modules) do
if mod.get_events then
mod_events = mod.get_events()
for e,f in pairs(mod_events) do
if not events[e] then
events[e] = {}
end
table.insert(events[e], f)
end
end
end
for e,fs in pairs(events) do
script.on_event(e, function(event_data)
for i,f in ipairs(fs) do
f(event_data)
end
end)
end
end
register_events()
for i,mod in ipairs(modules) do
if mod.add_remote_interfaces then
mod.add_remote_interfaces()
end
end
for i,mod in ipairs(modules) do
if mod.add_commands then
mod.add_commands()
end
end
--[[
-- This is a example of what to put at the end of your code, ie doomsday.lua
-- replace all the EXAMPLE 's with any name you want, they have to be all the same
-- replace tick with number if ticks and function with the function name to call
-- everywhere there is a blank space is where you put stuff in
local EXAMPLE_init = {}
local script_events = {
--place the here what you would normaly use Event.register for
-- Event.register(defines.events.on_player_created, testfunction)
-- is the same as
-- [defines.events.on_player_created] = testfunction,
-- where testfunction is | local function testfunction() { }
--[Event] = function,
--put stuff here
}
EXAMPLE_init.on_nth_ticks = {
--place the here what you would normaly use
--[tick] = function,
--put stuff here
}
EXAMPLE_init.on_init = function() -- this runs when Event.core_events.init
log("EXAMPLE init")
--put stuff here
global.EXAMPLE_data = global.EXAMPLE_data or script_data -- NO TOUCHY
end
EXAMPLE_init.on_load = function() -- this runs when Event.core_events.load
log("EXAMPLE load")
--put stuff here
script_data = global.EXAMPLE_data or script_data -- NO TOUCHY
end
EXAMPLE_init.get_events = function()
return script_events
end
return EXAMPLE_init
]]
-251
View File
@@ -1,251 +0,0 @@
-- doomsday module. Requires the programmable day-night cycle (pdnc) module to work.
-- made by Zr4g0n
-- this module currently has issues with the event module and 'on nth tick'.
require("pdnc") --is this the best way to do this?
global.doomsday_enabled = true
global.doomsday = global.doomsday or {} -- used to check if this exists.
global.doomsday_start = 8.75 -- in ingame days. Use n.75 to make sure doomsday is at midnight.
global.doomsday_pollution = 20 -- amount to be applied per tick
global.doomsday_surface = 1
global.doomsday_enable_players_online_compensator = false
global.doomsday_current_fuzzy_playercount = 1.5 -- start assuming 1.5 players! :V
global.doomsday_use_early_death = false
global.doomsday_early_death_has_happened = false
global.doomsday_use_different_spawn = false
global.doomsday_different_spawn = {x = -2000, y = -500}
global.doomsday_has_happened = false
function doomsday_status()
stats = {
"Time Left: " .. doomsday_time_left(),
"global.doomsday_start: " .. global.doomsday_start,
"global.doomsday_pollution: " .. global.doomsday_pollution,
"global.doomsday_surface: " .. global.doomsday_surface,
"global.doomsday_enabled: " .. pdnc_bool_to_string(global.doomsday_enabled),
"global.doomsday_enable_players_online_compensator: " .. pdnc_bool_to_string(global.doomsday_enable_players_online_compensator),
"global.doomsday_current_fuzzy_playercount: " .. global.doomsday_current_fuzzy_playercount,
"global.doomsday_use_early_death: " .. pdnc_bool_to_string(global.doomsday_use_early_death),
"global.doomsday_early_death_has_happened: " .. pdnc_bool_to_string(global.doomsday_early_death_has_happened),
"global.doomsday_use_different_spawn: " .. pdnc_bool_to_string(global.doomsday_use_different_spawn),
"global.doomsday_different_spawn: x = " .. global.doomsday_different_spawn.x .. ", y = " .. global.doomsday_different_spawn.y,
"doomsday_has_happened: " .. pdnc_bool_to_string(global.doomsday_has_happened),
}
return stats
end
function doomsday_console_status()
--logs to console, used for debuging
log("Time Left: " .. doomsday_time_left())
log("global.doomsday_start: " .. global.doomsday_start)
log("global.doomsday_pollution: " .. global.doomsday_pollution)
log("global.doomsday_surface: " .. global.doomsday_surface)
log("global.doomsday_enabled: " .. pdnc_bool_to_string(global.doomsday_enabled))
log("global.doomsday_enable_players_online_compensator: " .. pdnc_bool_to_string(global.doomsday_enable_players_online_compensator))
log("global.doomsday_current_fuzzy_playercount: " .. global.doomsday_current_fuzzy_playercount)
log("global.doomsday_use_early_death: " .. pdnc_bool_to_string(global.doomsday_use_early_death))
log("global.doomsday_early_death_has_happened: " .. pdnc_bool_to_string(global.doomsday_early_death_has_happened))
log("global.doomsday_use_different_spawn: " .. pdnc_bool_to_string(global.doomsday_use_different_spawn))
log("global.doomsday_different_spawn: x = " .. global.doomsday_different_spawn.x .. ", y = " .. global.doomsday_different_spawn.y)
log("doomsday_has_happened: " .. pdnc_bool_to_string(global.doomsday_has_happened))
return stats
end
function doomsday_toggle()
global.doomsday_enabled = not global.doomsday_enabled
doomsday_status()
end
function doomsday_setup()
if global.doomsday_use_different_spawn then
doomsday_activate_different_spawn()
end
end
function doomsday_early_death()
local radius = 512
local pollution = 2500
doomsday_pollute(radius*0.66,pollution,12)
doomsday_pollute(radius*1.00,pollution,24)
doomsday_pollute(radius*1.50,pollution,36)
global.doomsday_early_death_has_happened = true
end
function doomsday_activate_different_spawn()
game.forces["player"].set_spawn_position(global.doomsday_different_spawn, game.surfaces[global.doomsday_surface])
end
function doomsday_core()
local current_time = game.tick / game.surfaces[global.doomsday_surface].ticks_per_day
local x = current_time * 6.2831853 --2pi
local returnvalue = 0
if(global.doomsday_enable_players_online_compensator)then
doomsday_players_online_compensator()
end
if global.doomsday_use_early_death
and (current_time > 1)
and not global.doomsday_early_death_has_happened then
doomsday_early_death()
end
local x = current_time * 6.2831853 --2pi
local returnvalue = 0
if (current_time < global.doomsday_start) then
returnvalue = math.pow(pdnc_c_boxy(x), (1 + current_time / 4))
-- days become darker over time towards n^6.125
elseif (current_time < global.doomsday_start + 1) then
returnvalue = doomsday_pollution_zero_hour(current_time)
if not global.doomsday_has_happened then
global.doomsday_has_happened = true
log("Doomsday activated at tick: " .. game.tick)
end
else
global.pdnc_enable_brightness_limit = true
returnvalue = math.pow(pdnc_c_boxy(x), 6.125)--*0.5
end
return pdnc_scaler(returnvalue)
end
function doomsday_pollution_zero_hour(current_time)
local radius = 128 --make global
local pollution = global.doomsday_pollution -- total pollution applied per tick
local nodes = 5 -- the number of nodes to spread
doomsday_pollute(radius*0.66,pollution,nodes*0.66)
doomsday_pollute(radius*1.00,pollution,nodes*1.00)
doomsday_pollute(radius*1.50,pollution,nodes*1.50)
return math.pow(((global.doomsday_start + 1) - current_time), 7)
end
function doomsday_normal_curve(x)
return (1+ ((math.sin(x) + (0.111 * math.sin(3 * x))) * 1.124859392575928))/2
-- magic numbers to make it scale to (-1, 1)
end
function doomsday_pollute(radius,pollution,nodes) -- spawn a ring of pollution based on radius, amount of pollution and number of points in the ring.
local p = global.pdnc_stepsize * pollution -- needed to make it 'step size independant'
p = p / nodes
local position = {x = 0.0, y = 0.0}
if global.doomsday_use_different_spawn then
position = global.doomsday_different_spawn
end
--game.surfaces[global.doomsday_surface].pollute(position, p) --circle + center point
local step = (math.pi * 2) / (nodes - 1)
for i=0, (nodes - 1) do
position = {x = math.sin(step*i)*radius, y = math.cos(step*i)*radius}
game.surfaces[global.doomsday_surface].pollute(position, p)
end
end
function doomsday_time_left()
if (global.doomsday_start > 0)then
local ticks_until_doomsday = game.surfaces[global.doomsday_surface].ticks_per_day * global.doomsday_start
local ticks = ticks_until_doomsday - game.tick
if (ticks >= 0) then
local seconds = math.floor(ticks/ 60)
local minutes = math.floor(seconds / 60)
local hours = math.floor(minutes / 60)
local days = math.floor(hours / 24)
return(string.format("-" .. "%d:%02d:%02d:%02d", hours, minutes % 60, seconds % 60, ticks % 60))
else
ticks = ticks * -1
local seconds = math.floor(ticks / 60)
local minutes = math.floor(seconds / 60)
local hours = math.floor(minutes / 60)
local days = math.floor(hours / 24)
return(string.format("%d:%02d:%02d:%02d", hours, minutes % 60, seconds % 60, ticks % 60) .. " ago...")
end
else
return("Nothing to see here, move along! No doomsday here, nope!")
end
end
function doomsday_time_left_with_ticks()
local tick = (((game.surfaces[global.doomsday_surface].ticks_per_day * global.doomsday_start) - game.tick)%60)
return doomsday_time_left() .. ":" .. string.format("%02d", tick)
end
function doomsday_players_online_compensator()
local target = #game.connected_players
local current = global.doomsday_current_fuzzy_playercount
local step_size = 0.02 -- (at 21 ticks per iteration, that's 17.5sec per player change.
if(current < 0)then
game.print("Error; 'current' number of players' is less than 0!")
end
if(target < 0)then
game.print("Error; 'number of players' is less than 0!")
end
if(current == target) then
-- do nothing, this should be most of the time
elseif ( current < target) then
current = current + step_size
elseif ( current > target) then
current = current - step_size
else
-- do nothing?
end
local modifier = 20/(0.1 * current * current + 6) -- Nice, smooth curve that should hopefully give boost equal to the playercount!
game.forces.player.character_running_speed_modifier = modifier
global.doomsday_current_fuzzy_playercount = current
end
--[[
function reduce_brightness(n)
global.pdnc_max_brightness = 1 - ((global.pdnc_current_time / global.pdnc_doomsday_start)*n)
if(global.pdnc_max_brightness < n) then
global.pdnc_max_brightness = n
end
end
]]
-- This is a example of what to put at the end of your code, ie doomsday.lua
-- replaze EXAMPLE with any name you want
-- replace tick with number if ticks and function with the function name to call
-- everywhere there is a blank space is where you put stuff in
local doomsday_init = {}
local script_events = {
--place the here what you would normaly use Event.register for
-- Event.register(defines.events.on_player_created, testfunction)
-- is the same as
-- [defines.events.on_player_created] = testfunction,
-- where testfunction is | local functuin testfunction() { }
--[Event] = function,
--put stuff here
}
doomsday_init.on_nth_ticks = {
--place the here what you would normaly use
--[tick] = function,
--put stuff here
--[60] = doomsday_console_status, -- prints status to console ever second
}
doomsday_init.on_init = function() -- this runs when Event.core_events.init
log("doomsday init")
--put stuff here
doomsday_setup()
global.doomsday_data = global.doomsday_data or script_data -- NO TOUCHY
end
doomsday_init.on_load = function() -- this runs when Event.core_events.load
log("doomsday load")
--put stuff here
script_data = global.doomsday_data or script_data -- NO TOUCHY
end
doomsday_init.get_events = function()
return script_events
end
return doomsday_init
--Event.register(-global.pdnc_stepsize, doomsday_pdnc_program) --intentionally using the PDNC stepsize so the functions sync this can be skipped, since it's called from pdnc!
--Event.register(Event.core_events.init, doomsday_setup)
-266
View File
@@ -1,266 +0,0 @@
local mod_gui = require 'mod-gui'
-- ADDS DOOMSDAY STATS BUTTON
local function gui_update_DOOM(player)
local gui = mod_gui.get_frame_flow(player)
local frame = gui.doomsday_stats
if not frame then
return
end
frame.clear()
for i,stat in ipairs(doomsday_status()) do
frame.add{
type = "label",
caption = stat
}
end
end
local function toggle_frame_DOOM(player)
local gui = mod_gui.get_frame_flow(player)
local frame = gui.doomsday_stats
if frame then
frame.destroy()
return
end
frame = gui.add{
type = "frame",
name = "doomsday_stats",
direction = "vertical",
caption = "Doomsday stats",
style = mod_gui.frame_style,
}
frame.style.horizontally_stretchable = false
frame.style.vertically_stretchable = false
gui_update_DOOM(player)
end
local function get_sprite_button_DOOM(player)
local button_flow = mod_gui.get_button_flow(player)
button_DOOM = button_flow.doomsday_stats_button
if button_DOOM then
button_DOOM.destroy()
end
if player.admin then
button_DOOM = button_flow.add{
type = "sprite-button",
name = "doomsday_stats_button",
sprite = "item/raw-fish",
style = mod_gui.button_style,
tooltip = "Show debug stats for doomsday",
}
end
end
--ADDS PDNC STATS BUTTON
local function gui_update_PDNC(player)
local gui = mod_gui.get_frame_flow(player)
local frame_PDNC = gui.PDNC_stats
if not frame_PDNC then
return
end
frame_PDNC.clear()
for i,stat in ipairs(pdnc_extended_status()) do
frame_PDNC.add{
type = "label",
caption = stat
}
end
end
local function toggle_frame_PDNC(player)
local gui = mod_gui.get_frame_flow(player)
local frame_PDNC = gui.PDNC_stats
if frame_PDNC then
frame_PDNC.destroy()
return
end
frame_PDNC = gui.add{
type = "frame",
name = "PDNC_stats",
direction = "vertical",
caption = "PDNC stats",
style = mod_gui.frame_style,
}
frame_PDNC.style.horizontally_stretchable = false
frame_PDNC.style.vertically_stretchable = false
gui_update_PDNC(player)
end
local function get_sprite_button_PDNC(player)
local button_flow = mod_gui.get_button_flow(player)
local button_PDNC = button_flow.PDNC_stats_button
if button_PDNC then
button_PDNC.destroy()
end
if player.admin then
button_PDNC = button_flow.add{
type = "sprite-button",
name = "PDNC_stats_button",
sprite = "item/raw-fish",
style = mod_gui.button_style,
tooltip = "Show debug stats for PDNC",
}
end
end
-- ADDS DOOMSDAY TIME LEFT COUNTER
local function gui_update_counter(player)
local gui = player.gui.screen --mod_gui.get_frame_flow(player)
local frame_counter = gui.doomsday_counter
if not frame_counter then
return
end
frame_counter.clear()
frame_counter.add{
type = "label",
caption = doomsday_time_left()
}
frame_counter.location = {player.display_resolution.width/2 - 59, 10}
frame_counter.visible = true
end
local function toggle_frame_counter(player)
--local player = player
local gui = player.gui.screen --mod_gui.get_frame_flow(player)
local frame_counter = gui.doomsday_counter
-- if frame_counter then
-- frame_counter.destroy()
-- return
-- end
frame_counter = gui.add{
type = "frame",
name = "doomsday_counter",
direction = "horizontal",
--caption = "Doomsday counter",
style = mod_gui.frame_style,
}
--frame_counter.style.horizontally_stretchable = false
frame_counter.style.vertically_stretchable = false
frame_counter.visible = false
gui_update_counter(player)
end
local function update_buttons(event)
local player = game.players[event.player_index]
if not (player and player.valid) then
return
end
get_sprite_button_DOOM(player)
get_sprite_button_PDNC(player)
-- Make sure the frames are not visible to players
if not player.admin then
local gui = mod_gui.get_frame_flow(player)
local frame_PDNC = gui.PDNC_stats
if frame_PDNC then
frame_PDNC.destroy()
end
local frame_DOOM = gui.doomsday_stats
if frame_DOOM then
frame_DOOM.destroy()
end
end
end
-- ON GUI CLICK AND OTHERS
local function on_gui_click(event)
local gui = event.element
local player = game.players[event.player_index]
if not (player and player.valid and gui and gui.valid) then
return
end
if gui.name == "doomsday_stats_button" then
toggle_frame_DOOM(player)
end
if gui.name == "PDNC_stats_button" then
toggle_frame_PDNC(player)
end
end
local function on_player_created(event)
local player = game.players[event.player_index]
if not (player and player.valid) then
return
end
update_buttons(event)
toggle_frame_counter(player)
end
local function gui_update_all()
for _, player in pairs(game.players) do
if player and player.valid then
gui_update_DOOM(player)
gui_update_PDNC(player)
gui_update_counter(player)
end
end
end
local function on_gui_tick(event)
gui_update_all()
end
local doomsdaygui_init = {}
local script_events = {
--place the here what you would normaly use Event.register for
-- Event.register(defines.events.on_player_created, testfunction)
-- is the same as
-- [defines.events.on_player_created] = testfunction,
-- where testfunction is | local functuin testfunction() { }
--[Event] = function,
--put stuff here
[defines.events.on_gui_click] = on_gui_click,
[defines.events.on_player_created] = on_player_created,
[defines.events.on_player_promoted] = update_buttons,
[defines.events.on_player_demoted] = update_buttons,
}
doomsdaygui_init.on_nth_ticks = {
--place the here what you would normaly use
--[tick] = function,
--put stuff here
[1] = on_gui_tick,
}
doomsdaygui_init.on_init = function() -- this runs when Event.core_events.init
log("doomsdaygui init")
--put stuff here
global.doomsdaygui_data = global.doomsdaygui_data or script_data -- NO TOUCHY
end
doomsdaygui_init.on_load = function() -- this runs when Event.core_events.load
log("doomsdaygui load")
--put stuff here
script_data = global.doomsdaygui_data or script_data -- NO TOUCHY
end
doomsdaygui_init.get_events = function()
return script_events
end
return doomsdaygui_init
-309
View File
@@ -1,309 +0,0 @@
-- Third itteration of programmable day-night cycle for Factorio
-- Developed by Zr4g0n with help
-- Features needed:
-- Set 'maximum brightness' from rocket launches cluster wide
-- Set 'time until doomsday' to any time, in minutes.
-- Set 'evolution target' to any number [0.0, 1.0]
-- Changing day-night cycle based on days
-- Handle 'next time' elegantly
-- lamps enable darkness: [0.595 - 0.425] scaled to [0.0 - 0.85] range from [0.0 - 1.0] range
global.pdnc = global.pdnc or {}
global.pdnc_enabled = true
global.pdnc_stepsize = 53 -- also used for script.on_nth_tick. Cannot be changed at runtime!
global.pdnc_surface = 1
global.pdnc_current_time = 0
global.pdnc_current_point = {x = 0, y = 1.0}
global.pdnc_last_point = {x = -1, y = 0.0}
global.pdnc_max_brightness = 0.5 -- for clusterio
global.pdnc_debug = false
global.pdnc_enable_brightness_limit = false
global.pdnc_enable_rocket_darkness = false
global.pdnc_rockets_launched = 0
global.pdnc_rockets_launched_step_size = 0.025
global.pdnc_rockets_launched_smooth = 0
global.pdnc_min_per_day = 15.0
function pdnc_setup()
--game.surfaces[global.pdnc_surface].ticks_per_day = pdnc_min_to_ticks(10.0)
doomsday_setup()
end
-- Returns true if the player is an admin
function check_admin(ctx)
local player = game.players[ctx.player_index]
if not player.admin then
player.print("Only admins can use /"..ctx.name)
return false
end
return true
end
function pdnc_toggle_debug(ctx)
if not check_admin(ctx) then return end
global.pdnc_debug = not global.pdnc_debug
pdnc_print_status(ctx)
end
function pdnc_toggle(ctx)
if not check_admin(ctx) then return end
global.pdnc_enabled = not global.pdnc_enabled
pdnc_print_status(ctx)
end
function pdnc_print_status(ctx)
local player = game.players[ctx.player_index]
if(global.pdnc_enabled)then
player.print("PDNC is enabled")
else
player.print("PDNC is disabled")
end
if(global.pdnc_debug)then
player.print("PDNC debug is enabled")
pdnc_extended_status()
else
player.print("PDNC debug is disabled")
end
end
function pdnc_extended_status()
stats = {
"global.pdnc_stepsize: " .. global.pdnc_stepsize,
"global.pdnc_surface: " .. global.pdnc_surface,
"global.pdnc_current_time: " .. global.pdnc_current_time,
"global.pdnc_max_brightness: " .. global.pdnc_max_brightness,
"global.pdnc_enable_brightness_limit: " .. pdnc_bool_to_string(global.pdnc_enable_brightness_limit),
"global.pdnc_enable_rocket_darkness: " .. pdnc_bool_to_string(global.pdnc_enable_rocket_darkness),
"global.pdnc_rockets_launched: " .. global.pdnc_rockets_launched,
"global.pdnc_rockets_launched_step_size: " .. global.pdnc_rockets_launched_step_size,
"global.pdnc_rockets_launched_smooth: " .. global.pdnc_rockets_launched_smooth,
"ticks per day: " .. game.surfaces[global.pdnc_surface].ticks_per_day,
"current tick: " .. game.tick,
}
return stats
end
function pdnc_bool_to_string(b)
if(b)then
return "true"
else
return "false"
end
end
function pdnc_core()
if(global.pdnc_enabled)then
doomsday_core()
local current_surface = game.surfaces[global.pdnc_surface]
pdnc_freeze_check(current_surface)
current_surface.ticks_per_day = pdnc_min_to_ticks(global.pdnc_min_per_day) -- move this somewhere else; doesn't need to run every nth tick!
global.pdnc_current_time = game.tick / current_surface.ticks_per_day
-- get current Y coordinate. X = 0 always.
local current_point = {x = global.pdnc_current_time, y = pdnc_program(global.pdnc_current_time)}
-- get next Y coordinate, X = 'step size'
local next_point = {x = (global.pdnc_current_time + (global.pdnc_stepsize/current_surface.ticks_per_day)), y = pdnc_program(global.pdnc_current_time + (global.pdnc_stepsize/current_surface.ticks_per_day))}
local top_point = pdnc_intersection_top(current_point, next_point)
local bot_point = pdnc_intersection_bot(current_point, next_point)
-- the order is dusk - evening - morning - dawn. They *must* be in that order and they cannot be equal
if(top_point < bot_point) then -- dusk -> evening
pdnc_cleanup_last_tick(current_surface)
current_surface.evening = bot_point - current_point.x
current_surface.dusk = top_point - current_point.x
elseif(top_point > bot_point) then -- morning -> dawn
pdnc_cleanup_last_tick(current_surface)
current_surface.morning = bot_point - current_point.x
current_surface.dawn = top_point - current_point.x
elseif(top_point == bot_point) then
pdnc_debug_message("PDNC: Top and bot point equal")
-- no cleanup is done here
-- if the points are equal, use last value until not equal
-- this should never be reached unless the pdnc_program() is broken.
else
pdnc_debug_message("Top and bot not different nor equal. probably a NaN error")
pdnc_debug_message("bot_point: " .. bot_point)
pdnc_debug_message("top_point: " .. top_point)
-- this should never be reached.
end
else
end
end
function pdnc_cleanup_last_tick(current_surface)
current_surface.daytime = 0
-- must be in this spesific order to
-- preserve the order at all times
-- dusk < evening < morning < dawn.
-- DO NOT CHANGE THE ORDER!
current_surface.dusk = -999999999999
current_surface.dawn = 999999999999
current_surface.evening = -999999999998
current_surface.morning = 999999999998
end
function pdnc_disable_and_reset(ctx)
if not check_admin(ctx) then return end
local current_surface = game.surfaces[global.pdnc_surface]
pdnc_cleanup_last_tick(current_surface)
-- DO NOT CHANGE THIS ORDER!
current_surface.evening = 0.45
current_surface.morning = 0.55
current_surface.dusk = 0.25
current_surface.dawn = 0.75
current_surface.ticks_per_day = 25000
global.pdnc_enabled = false
pdnc_debug_message("PDNC Programable Day-Night Cycle disabled, normal day-night cycle enabled")
end
function pdnc_freeze_check(current_surface)
if(current_surface.freeze_daytime)then
current_surface.freeze_daytime = false
game.print("Can't use freeze_daytime while programmable day-night cycle is active; time has been unfrozen")
end
end
function pdnc_program(x)
return pdnc_scaler(pdnc_c_boxy(x*6.28318530717958647692)) -- that's Tau, aka 2xPi
end
function pdnc_c_boxy(x)
return pdnc_normalize((math.sin(x) + (0.111 * math.sin(3 * x))) * 1.124859392575928)
-- magic numbers to make it scale to (-1, 1)
end
function pdnc_normalize(n)
return (n + 1)/2
end
function pdnc_scaler(r) -- a bit messy, but simplifies a lot elsewhere
if(pdnc_check_valid(r, "pdnc_scaler"))then
local a = 1
local b = 1
if(global.pdnc_enable_brightness_limit) then
a = global.pdnc_max_brightness
end
if(global.pdnc_enable_rocket_darkness) then
b = 1 - pdnc_rocket_launch_darkness()
end
return r * 0.85 * a * b -- 0 -> 0.85 is the 'brightness range' of factorio
end
end
function pdnc_intersection (s1, e1, s2, e2)
local d = (s1.x - e1.x) * (s2.y - e2.y) - (s1.y - e1.y) * (s2.x - e2.x)
local a = s1.x * e1.y - s1.y * e1.x
local b = s2.x * e2.y - s2.y * e2.x
local x = (a * (s2.x - e2.x) - (s1.x - e1.x) * b) / d
--local y = (a * (s2.y - e2.y) - (s1.y - e1.y) * b) / d
return x--, y
end
function pdnc_intersection_top (s2, e2)
local s1, e1 = {x = -999999999, y = 0.85}, {x = 999999999, y = 0.85}
return pdnc_intersection (s1, e1, s2, e2)
end
function pdnc_intersection_bot (s2, e2)
local s1, e1 = {x = -999999999, y = 0.0}, {x = 999999999, y = 0.0}
return pdnc_intersection (s1, e1, s2, e2)
end
function pdnc_set_max_brightness(n)
if(pdnc_check_valid(n, "pdnc_set_max_brightness")) then
global.pdnc_max_brightness = n
pdnc_debug_message("global.pdnc_max_brightness set to " .. global.pdnc_max_brightness)
end
end
function pdnc_min_to_ticks(m)
return 60*60*m
end
function pdnc_rocket_launch_counter()
global.pdnc_rockets_launched = 1 + global.pdnc_rockets_launched
end
function pdnc_rocket_launch_darkness()
if (global.pdnc_rockets_launched_smooth < global.pdnc_rockets_launched)then
global.pdnc_rockets_launched_smooth = global.pdnc_rockets_launched_step_size + global.pdnc_rockets_launched_smooth
end
return (1 - (50/(global.pdnc_rockets_launched_smooth+50)))
end
function pdnc_check_valid(n, s)
if (n == nil) then
pdnc_debug_message(s .. " set to nil! Set to 1.0 instead")
return false
elseif (n < 0) then
pdnc_debug_message(s .. " cannot be " .. n .. " limited to 0.0 instead")
return false
elseif (n > 1) then
pdnc_debug_message(s .. " cannot be " .. n .. " limited to 1.0 instead")
return false
elseif (n ~= n) then
pdnc_debug_message(s .. " cannot be " .. n .. " since it's not a valid number!")
else return true
end
end
function pdnc_debug_message(s)
if(global.pdnc_debug) then
game.print(s)
end
end
local PDNC_init = {}
local script_events = {
--place the here what you would normaly use Event.register for
-- Event.register(defines.events.on_player_created, testfunction)
-- is the same as
-- [defines.events.on_player_created] = testfunction,
-- where testfunction is | local functuin testfunction() { }
--[Event] = function,
--put stuff here
}
PDNC_init.on_nth_ticks = {
--place the here what you would normaly use
--[tick] = function,
--put stuff here
[global.pdnc_stepsize] = pdnc_core,
}
PDNC_init.add_commands = function()
commands.add_command("pdnc", "gives PDNC's status", pdnc_print_status)
commands.add_command("pdnc_toggle", "toggles pdnc", pdnc_toggle)
commands.add_command("pdnc_toggle_debug", "toggles pdnc debug mode", pdnc_toggle_debug)
commands.add_command("pdnc_disable_and_reset", "Disabled pdnc and resets the dnc to the vanilla default", pdnc_disable_and_reset)
end
PDNC_init.on_init = function() -- this runs when Event.core_events.init
log("PDNC init")
--put stuff here
global.PDNC_data = global.PDNC_data or script_data -- NO TOUCHY
end
PDNC_init.on_load = function() -- this runs when Event.core_events.load
log("PDNC load")
--put stuff here
script_data = global.PDNC_data or script_data -- NO TOUCHY
end
PDNC_init.get_events = function()
return script_events
end
return PDNC_init
+4 -109
View File
@@ -1,9 +1,9 @@
const needle = require("needle");
const fs = require("fs-extra");
const objectOps = require("./../../lib/objectOps.js");
const fileOps = require("_app/fileOps");
const stringUtils = require("./../../lib/stringUtils.js");
const objectOps = require("lib/objectOps.js");
const fileOps = require("lib/fileOps");
const stringUtils = require("lib/stringUtils.js");
const pluginConfig = require("./config");
const COMPRESS_LUA = false;
@@ -35,61 +35,6 @@ module.exports = class remoteCommands {
}
})().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 --------------------------------------------------------------
// trigger when something happens to output.txt
fs.watch(instancedirectory + "/script-output/output.txt", function (eventType, filename) {
@@ -232,56 +177,6 @@ module.exports = class remoteCommands {
setTimeout(function(){
messageInterface("/silent-command remote.call('clusterio','setWorldID',"+that.config.unique+")")
}, 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){
this.commandCache = this.commandCache || {};
@@ -307,7 +202,7 @@ module.exports = class remoteCommands {
return new Promise((resolve, reject) => {
let instance = this.instances[instanceID];
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) {
console.log("Unable to get JSON master/api/slaves, master might be unaccessible");
} else if (response && response.body) {
+1 -1
View File
@@ -55,7 +55,7 @@ module.exports = class remoteCommands {
return new Promise((resolve, reject) => {
let instance = this.instances[instanceID];
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) {
console.log("Unable to get JSON master/api/slaves, master might be unaccessible");
} 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",
// We'll send everything in this file to your stdin. Beware.
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 pluginConfig = require("./config")
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="{"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='+"'"+'{"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) {
// success?
@@ -2,7 +2,7 @@ const assert = require("assert");
const child_process = require("child_process");
const functions = require("./interfacingFunctions");
const luamin = require("luamin");
const isFactorioCommand = require("_app/isFactorioCommand");
const isFactorioCommand = require("lib/isFactorioCommand");
const nock = require("nock");
// sinon does weird prototype manipulation and so doen't need to be saved to anything.
@@ -1,3 +1,4 @@
-- WARNING: The commands in this file is not in sync with interfacingFunctions.js
-- Lua cheatsheet
/c game.write_file("test.txt",serpent.block(game.players["Danielv123"].get_quickbar().get_contents()))
View File
Whitespace-only changes.
-100
View File
@@ -1,100 +0,0 @@
var mkdirp = require("mkdirp-promise");
var mkdirpSync = require("mkdirp").sync;
var fs = require("fs");
var events = require("events");
var path = require("path");
var objectOps = require("./../../lib/objectOps");
var LinvoDB = require("linvodb3");
// LinvoDB.dbPath = "./database/linvodb/";
let allowLogging = true;
let log = string => (allowLogging)? console.log(string):""
module.exports = class chunkStore {
constructor(name, chunkSize = 32, path = "./chunkStore/"){
if(!name || (typeof name != "string" && typeof name != "number")) throw new Error("chunkstore requires a name string as first param");
name = name.toString();
this.name = name;
this.chunkSize = chunkSize;
this.path = (path[path.length] == "/") ? path : path+"/";
mkdirpSync(path+name);
LinvoDB.dbPath = this.path;
this.db = new LinvoDB(name, {}, {});
this.eventEmitter = new events.EventEmitter();
}
setEntity(x/**{number}*/, y/**{number}*/, newEntity/**{object}*/){
return new Promise((resolve, reject) => {
x = Math.floor(x);
y = Math.floor(y);
this.db.find({x, y}, (err, docs) => {
if(typeof newEntity == "object"){
if(err) reject(err);
let entityDoc = {x, y, entity: newEntity};
if(docs && docs[0] && docs[0]._id){
entityDoc._id = docs[0]._id;
}
this.db.save(entityDoc, (err,docs) => {
if(err) reject(err);
resolve(docs);
this.eventEmitter.emit("change", docs)
});
} else {
docs.forEach(doc => {
doc.remove(()=>{
// removed :)
});
});
resolve();
this.eventEmitter.emit("change", {x,y});
}
});
});
}
getEntity(x, y){
return new Promise((resolve, reject) => {
this.db.find({x:Math.floor(x), y:Math.floor(y)}, (err, docs) => {
if(err) reject(err);
resolve(docs);
});
});
}
onEntityChange(eventhandler/**function*/){
if(typeof eventhandler == "function") {
this.eventEmitter.on("change", eventhandler);
}
}
getChunk(x, y){
return new Promise((resolve, reject) => {
/*
// make new chunk in case we can't find an existing one
let chunk = {
dataObject: {},
position:{x,y}, // fancy ES6 feature that generates to {x:x, y:y}
chunkSize:this.chunkSize
};
fs.readFile(this.path+this.name+"/"+x+"/"+y, (err, chunkFile) => {
if(err){
log(err)
resolve(chunk);
} else {
if(objectOps.isJSON(chunkFile)) {
log("returned from file")
let chunk = JSON.parse(chunkFile);
resolve(chunk);
} else {
log("file failed")
resolve(chunk);
}
}
}); // add x and y values as well and we are done?
*/
});
}
}
@@ -1,64 +0,0 @@
var assert = require("assert");
var chunkStore = require("./chunkStore.js");
describe("remoteMap/chunkStore.js", () => {
// define some values for our test
const inserterX = 14.5; // rounds to 15
const inserterY = -17.5; // rounds to -17, +64 would be 47
const inserterName = "inserter";
let store // make our store elevated
it("is a constructor", ()=>{
assert.equal(typeof chunkStore, "function");
// create a new store and elevate it
store = new chunkStore("910239"/* instanceID, use defaults for the rest */, undefined, "./database/unitTests/chunkStore/");
assert.equal(store.name, "910239");
});
it("allows you to store the locations of entities", done => {
store.setEntity(inserterX, inserterY, {name:inserterName, otherData:"can be arbitrary"}).then(() => {
done();
// nothing to do here really
});
});
it("returns entities at positions you query for", done => {
setTimeout(()=>{
store.getEntity(inserterX, inserterY).then(entities => {
entity = entities[0]
// console.log(entity)
assert.equal(entity.x, 14);
assert.equal(entity.y, -18);
assert.equal(entity.entity.name, "inserter");
assert.equal(entity.entity.otherData, "can be arbitrary");
done();
});
},300);
});
it("deletes entities when not provided with an object as 3rd arg", done => {
store.setEntity(inserterX, inserterY).then(entity => {
assert(entity === undefined);
setTimeout(()=>{
store.getEntity(inserterX, inserterY).then(entities => {
// console.log(entities)
assert.equal(entities.length, 0, "Why are we getting results, it should be empty?");
done();
});
},300);
});
});
describe("validates input by throwing when it is invalid", ()=>{
it("throws if called without a string as the first parameter", ()=>{
assert.throws(()=>{
let throwingChunkStore = new chunkStore(undefined, undefined, "asdasda");
});
assert.throws(()=>{
let throwingChunkStore = new chunkStore({}, undefined, "asdasdas");
});
});
it("converts numbers to strings automatically if they are provided as the first parameter", ()=>{
assert.ok(()=>{
let nonThrowingChunkStore = new chunkStore(42, undefined, "./database/unitTests/chunkStore/");
});
});
});
});
-16
View File
@@ -1,16 +0,0 @@
/*
Config for clusterio remote mapping plugin. Will handle writing out new map data, aggregating it and sending it to the master (or something) for
display/interaction.
*/
var modInfo = require("./info.json");
module.exports = {
// Name of package. For display somewhere I guess.
name: modInfo.name,
version: modInfo.version,
// Binary entrypoint for plugin. Don't let it crash. Stdout is sent to game as server chat (run /c commands from here for interface)
// Make sure its cross platform somehow.
binary: "nodePackage",
description: modInfo.description,
// We'll send everything in this file to your stdin. Beware.
scriptOutputFileSubscription: "remoteMap.txt",
}
-145
View File
@@ -1,145 +0,0 @@
-- MIT License
--
-- Copyright (c) 2017 Florian Jung
--
-- Permission is hereby granted, free of charge, to any person obtaining a
-- copy of this factorio lua stub and associated
-- documentation files (the "Software"), to deal in the Software without
-- restriction, including without limitation the rights to use, copy, modify,
-- merge, publish, distribute, sublicense, and/or sell copies of the
-- Software, and to permit persons to whom the Software is furnished to do
-- so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in
-- all copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
-- THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-- FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-- DEALINGS IN THE SOFTWARE.
function write_file(data)
stringsToWriteNextTick = stringsToWriteNextTick .. data .. '\n'
-- game.write_file("remoteMap.txt", data, true)
end
function dump_cached_writes()
game.write_file("remoteMap.txt", stringsToWriteNextTick, true, 0)
end
function complain(text)
if false then -- only print during development
print(text)
game.forces["player"].print(text)
end
end
local todo_next_tick = {}
must_write_initstuff = true
function on_tick(event)
dump_cached_writes()
stringsToWriteNextTick = ''
if must_write_initstuff then
must_write_initstuff = false
log("I must write init stuff!")
writeout_initial_stuff()
end
if #todo_next_tick > 0 then
complain("on_tick executing "..#todo_next_tick.." stored callbacks")
for _,func in ipairs(todo_next_tick) do
func()
end
todo_next_tick = {}
end
end
function writeout_initial_stuff()
log("Writing out initial stuff...")
writeout_objects(game.surfaces['nauvis'], {left_top={x=-64,y=-64},right_bottom={x=64,y=64}})
end
function writeout_objects(surface, area)
--if my_client_id ~= 1 then return end
for idx, ent in pairs(surface.find_entities(area)) do
if area.left_top.x <= ent.position.x and ent.position.x < area.right_bottom.x and area.left_top.y <= ent.position.y and ent.position.y < area.right_bottom.y then
if ent.prototype.collision_mask ~= nil and ent.prototype.collision_mask['player-layer'] then
-- line = line .. ent.name.." "..ent.position.x.." "..ent.position.y.."\n"
-- local line = ent.name..","..ent.position.x..","..ent.position.y
-- if ent.supports_direction then
-- line = line .. ",rot="..ent.direction
-- end
-- write_file(line)
on_some_entity_created(nil, ent)
end
end
end
--write_file(header..table.concat(lines,"").."\n")
--write_file(table.concat(lines,"").."\n")
end
function on_chunk_generated(event)
local area = event.area
local surface = event.surface
--print("chunk generated at ("..area.left_top.x..","..area.left_top.y..") -- ("..area.right_bottom.x..","..area.right_bottom.y..")")
if surface ~= game.surfaces['nauvis'] then -- we only support one surface
return
end
-- writeout_resources(surface, area)
writeout_objects(surface, area)
-- writeout_tiles(surface, area)
end
function on_some_entity_created(event, entp)
local ent
if entp then
ent = entp
else
ent = event.entity or event.created_entity or nil
end
if ent == nil then
complain("wtf, on_some_entity_created has nil entity")
return
end
local entityData = ent.name..","..ent.position.x..","..ent.position.y
if ent.supports_direction then
entityData = entityData .. ",rot="..ent.direction
end
-- log(entityData)
write_file(entityData)
--writeout_objects(ent.surface, {left_top={x=math.floor(ent.position.x), y=math.floor(ent.position.y)}, right_bottom={x=math.floor(ent.position.x)+1, y=math.floor(ent.position.y)+1}})
--complain("on_some_entity_created: "..ent.name.." at "..ent.position.x..","..ent.position.y)
end
function on_some_entity_deleted(event)
local ent = event.entity
if ent == nil then
complain("wtf, on_some_entity_deleted has nil entity")
return
end
-- we can't do this now, because the entity still exists at this point. instead, we schedule the writeout for the next tick
--local surface = ent.surface
--local area = {left_top={x=math.floor(ent.position.x), y=math.floor(ent.position.y)}, right_bottom={x=math.floor(ent.position.x)+1, y=math.floor(ent.position.y)+1}}
--table.insert(todo_next_tick, function () writeout_objects(surface, area ) end)
write_file("deleted,"..ent.position.x..","..ent.position.y)
--complain("on_some_entity_deleted: "..ent.name.." at "..ent.position.x..","..ent.position.y)
end
script.on_event(defines.events.on_tick, on_tick)
script.on_event(defines.events.on_chunk_generated, on_chunk_generated)
script.on_event(defines.events.on_biter_base_built, on_some_entity_created) --entity
script.on_event(defines.events.on_built_entity, on_some_entity_created) --created_entity
script.on_event(defines.events.on_robot_built_entity, on_some_entity_created) --created_entity
script.on_event(defines.events.on_player_rotated_entity, on_some_entity_created) --created_entity
script.on_event(defines.events.on_entity_died, on_some_entity_deleted) --entity
script.on_event(defines.events.on_player_mined_entity, on_some_entity_deleted) --entity
script.on_event(defines.events.on_robot_mined_entity, on_some_entity_deleted) --entity
script.on_event(defines.events.on_resource_depleted, on_some_entity_deleted) --entity
@@ -1,5 +0,0 @@
/c
local toDelete = game.surfaces[1].find_entities({{3,7},{4,8}})
for i, entity in pairs(toDelete) do
entity.die()
end
-130
View File
@@ -1,130 +0,0 @@
const needle = require("needle");
const ioClient = require("socket.io-client");
const chunkStore = require("./chunkStore.js");
const moduleConfig = require("./config"); // not to be confused with clusterio config. This config is private to this plugin.
class remoteMap {
constructor(slaveConfig, messageInterface){
this.config = slaveConfig;
this.messageInterface = messageInterface;
// initialize chunk database
this.chunkMap = new chunkStore(this.config.unique, 64, "./database/chunkStore/");
this.chunkMap.onEntityChange(entity => {
// this.messageInterface("Entity changed: ")
// this.messageInterface(entity);
// check valid coordinates (dunno what could go wrong, but can never be too sure)
if(entity && entity.x !== undefined && !isNaN(Number(entity.x)) && entity.y !== undefined && !isNaN(Number(entity.y))){
this.socket.emit("sendEntity", entity);
}
});
// set up websocket communication and handle requests from web interface users (which are going through master)
// socket should be a global
this.socket = ioClient("http://"+this.config.masterIP+":"+this.config.masterPort);
this.socket.on("hello", data => {
this.socket.emit("registerSlaveMapper", {instanceID: this.config.unique});
setInterval(()=>{
this.socket.emit("heartbeat"); // send our heartbeat to prevent being assumed dead
},10000);
});
this.socket.on("getChunk", req => {
this.messageInterface("getChunk is not supported!");
/*this.chunkMap.getChunk(req.x, req.y).then(chunk => {
chunk.requesterID = req.requesterID;
this.socket.emit("sendChunk", chunk);
});*/
});
this.socket.on("getEntity", req => {
// check for valid coordinates
if(req.x !== undefined && !isNaN(Number(req.x)) && req.y !== undefined && !isNaN(Number(req.y))){
this.chunkMap.getEntity(req.x, req.y).then(entities => {
if(entities && entities.length > 0){
entities.forEach(entity => {
this.socket.emit("sendEntity", entity);
});
}
});
} else {
this.messageInterface("socket.getEntity triggered called with invalid parameters");
}
});
// both placing and deleting entities via the web interface
this.socket.on("placeEntity", req => {
// game.player.surface.create_entity{name="small-ship-wreck", position={game.player.position.x-2, game.player.position.y+1}, direction=..., force=...}
this.messageInterface("placing "+JSON.stringify(req));
if(req.name && typeof req.name == "string" && req.position && !isNaN(Number(req.position.x)) && !isNaN(Number(req.position.y))){
if(req.name == "deleted"){ // {{'+req.position.x+','+req.position.y+'},{'+req.position.x+','+req.position.y+'}}
// delete entity from game world using LUA command
this.messageInterface('/silent-command local toDelete = game.surfaces[1].find_entities({{'+req.position.x+','+req.position.y+'},{'+(req.position.x+1)+','+(req.position.y+1)+'}}) for i, entity in pairs(toDelete) do entity.die() end');
// this position is now empty, delete whatever was there from DB
this.deleteEntity(req.position.x, req.position.y);
} else {
let command = '/silent-command game.surfaces[1].create_entity{name="'+req.name+'", position={'+Number(req.position.x)+', '+Number(req.position.y)+'}, force="neutral"'
if(req.direction) command += ', direction='+req.direction;
command += '}';
this.messageInterface(command); // execute command
// add entity to the chunkStore database because create_entity does not trigger any events
let name = req.name;
let entity = {name};
if(typeof req.direction == "string"){
entity.rot = req.direction;
}
this.addEntity(req.position.x, req.position.y, entity);
}
}
});
this.messageInterface("/silent-command game.print('"+moduleConfig.name+" version "+moduleConfig.version+" enabled')");
}
addEntity(x,y,entity){ // add entity to database
this.chunkMap.setEntity(Number(x), Number(y), entity).then(data => {
this.messageInterface("Added "+entity.name+" to data "+Number(x)+", "+Number(y));
}).catch((err)=>this.messageInterface(err));
}
deleteEntity(x,y){ // remove entity from database
// this position is now empty, delete whatever was there from DB
this.chunkMap.setEntity(x, y, "delete this entity").then(() => {
this.messageInterface("Deleted entity at "+x+", "+y);
}).catch((err)=>this.messageInterface(err));
}
scriptOutput(data){
if(data){
this.messageInterface(data);
// express-transport-belt,-35.5,-14.5,[...]
data = data.split(",");
if(data && data[0] == undefined){
this.messageInterface("empty: ");
this.messageInterface(data);
return
}
let name = data[0];
let xPos = data[1];
let yPos = data[2];
if(xPos && yPos && name){
if(name == "deleted"){
// this position is now empty, delete whatever was there from DB
this.deleteEntity(xPos, yPos);
} else {
let entity = {name};
for(let i = 3; i < data.length; i++){
if(data[i].includes("=")){
let kv = data[i].split("=");
entity[kv[0]] = kv[1];
}
}
// save this entity in the database
this.addEntity(xPos, yPos, entity);
/* this.chunkMap.setEntity(xPos, yPos, entity).then(data => {
this.messageInterface("Added "+name+" to data "+xPos+", "+yPos);
}).catch((err)=>this.messageInterface(err)); */
}
}
}
}
}
module.exports = remoteMap;
-9
View File
@@ -1,9 +0,0 @@
var assert = require("assert");
var remoteMap = require("./index.js");
describe("remoteMap/index.js", () => {
it("exports a single class (or at least a function)", ()=>{
assert.equal(typeof remoteMap, "function");
});
});
-11
View File
@@ -1,11 +0,0 @@
{
"name": "remoteMap",
"title": "Clusterio remote map",
"version": "2.2.0",
"date": "18-11-2017",
"author": "Danielv123",
"dependencies": ["base >= 0.15"],
"factorio_version": "0.16",
"description": "Clusterio plugin for mapping and building through the browser. No more need to start game, download and catching up for every game, also mobile factorio confirmed?"
}
+12 -5
View File
@@ -11,15 +11,22 @@ local function updateTech()
end
end
if game.forces['player'].technologies['{tech_name}'] then
local force = game.forces['player']
local tech = force.technologies['{tech_name}']
if tech then
if 1 == {tech_infinite} then
if (game.forces['player'].technologies['{tech_name}'].level ~= level) then
game.forces['player'].technologies['{tech_name}'].level = {tech_level}
if (tech.level ~= level) then
tech.level = {tech_level}
if tech == force.current_research then
force.research_progress = {tech_progress}
else
force.set_saved_technology_progress(tech.name, {tech_progress});
end
updateTech()
end
else
if not game.forces['player'].technologies['{tech_name}'].researched then
game.forces['player'].technologies['{tech_name}'].researched = true
if not tech.researched then
tech.researched = true
updateTech()
end
end
+91 -60
View File
@@ -169,21 +169,24 @@ class ResearchSync {
})
}
clear_contribution_to_researched_techs() {
// if between updates tech was researched
if (!this.prev_research)
return
for (let [name, research] of Object.entries(this.research)) {
let researched
if (research.infinite)
researched = this.prev_research[name].level < research.level
else
researched = this.prev_research[name].researched < research.researched
clear_contribution_to_researched_techs() {
for (let [name, tech] of Object.entries(this.research)) {
if (!Object.hasOwnProperty.call(this.prev_research, name)) {
continue;
}
if (researched)
research.contribution = 0
}
}
let researched;
if (tech.infinite) {
researched = this.prev_research[name].level < tech.level;
} else {
researched = this.prev_research[name].researched < tech.researched;
}
if (researched) {
tech.contribution = 0;
}
}
}
get_cluster_techs(slavesData) {
let cluster_techs = {}
@@ -193,7 +196,7 @@ class ResearchSync {
if (isNaN(node_tech.researched) || isNaN(node_tech.level) || isNaN(node_tech.infinite))
continue
if (cluster_techs[name]) {
if (Object.hasOwnProperty.call(cluster_techs, name)) {
if (cluster_techs[name].infinite === 1 && cluster_techs[name].level < node_tech.level) {
cluster_techs[name].level = node_tech.level
} else if (node_tech.researched > cluster_techs[name].researched) {
@@ -208,65 +211,90 @@ class ResearchSync {
}
recount_cluster_research_progress(slaves_data, cluster_researches) {
for (let [name, research] of Object.entries(cluster_researches))
research.progress = this.research[name].contribution
for (let [name, tech] of Object.entries(cluster_researches)) {
if (Object.hasOwnProperty.call(this.research, name)) {
tech.progress = this.research[name].contribution
} else {
tech.progress = 0;
}
}
for (let slave_data of slaves_data) {
for (let [name, research] of Object.entries(slave_data.meta.research)) {
if (!cluster_researches[name])
continue
if (isNaN(cluster_researches[name].progress)
|| isNaN(research.contribution)
|| isNaN(research.level)
|| isNaN(cluster_researches[name].level))
continue
if (cluster_researches[name].level === research.level)
cluster_researches[name].progress += research.contribution
for (let [name, tech] of Object.entries(slave_data.meta.research)) {
if (!Object.hasOwnProperty.call(cluster_researches, name)) {
continue;
}
if (
isNaN(cluster_researches[name].progress)
|| isNaN(tech.contribution)
|| isNaN(tech.level)
|| isNaN(cluster_researches[name].level)
) {
continue;
}
if (cluster_researches[name].level === tech.level) {
cluster_researches[name].progress += tech.contribution;
}
}
}
for (let [name, research] of Object.entries(cluster_researches)) {
if (research.progress > 1) {
research.progress = null
research.researched = 1
for (let [name, tech] of Object.entries(cluster_researches)) {
if (Object.hasOwnProperty.call(this.research, name) && tech.progress > 1) {
tech.progress = null
tech.researched = 1
this.research[name].contribution = 0
research.contribution = 0
if (this.research[name].level >= research.level)
research.level = this.research[name].level + 1
tech.contribution = 0
if (this.research[name].level >= tech.level)
tech.level = this.research[name].level + 1
}
}
}
filter_researched_techs(cluster_researches) {
let local_researches = this.research
let result = {};
for (let key in local_researches) {
if (!cluster_researches[key])
continue
if (isNaN(cluster_researches[key].researched) || isNaN(cluster_researches[key].level))
for (let [name, tech] of Object.entries(this.research)) {
if (!Object.hasOwnProperty.call(cluster_researches, name)) {
continue;
}
if (isNaN(cluster_researches[name].researched) || isNaN(cluster_researches[name].level))
continue
let researched
if (local_researches[key].infinite)
researched = local_researches[key].level < cluster_researches[key].level
else
researched = local_researches[key].researched < cluster_researches[key].researched
if (tech.infinite) {
researched = tech.level < cluster_researches[name].level;
} else {
researched = tech.researched < cluster_researches[name].researched;
}
if (researched)
result[key] = cluster_researches[key]
result[name] = cluster_researches[name]
}
return result;
}
filter_updated_techs(cluster_techs, to_research) {
let local_techs = this.research
let result = {}
for (let name in local_techs) {
if (name in to_research || !cluster_techs[name])
continue
for (let [name, tech] of Object.entries(this.research)) {
if (Object.hasOwnProperty.call(to_research, name) || !Object.hasOwnProperty.call(cluster_techs, name)) {
continue;
}
if (isNaN(cluster_techs[name].progress))
continue
if (local_techs[name].progress < cluster_techs[name].progress)
// Ignore researched techs
if (!tech.infinite && tech.researched) {
continue;
}
// Do not update progress for infinite techs with a local level that's higher.
if (tech.infinite && tech.level > cluster_techs[name].level) {
continue;
}
if (tech.progress < cluster_techs[name].progress)
result[name] = cluster_techs[name]
}
return result
@@ -274,8 +302,9 @@ class ResearchSync {
research_technologies(to_research) {
for (let name of Object.keys(to_research))
if (!this.research[name])
delete to_research[name]
if (!Object.hasOwnProperty.call(this.research, name)) {
delete to_research[name];
}
const notify = Object.keys(to_research).length === 1
for (let [name, tech] of Object.entries(to_research)) {
@@ -283,9 +312,9 @@ class ResearchSync {
this.research[name].progress = null
let command = this.functions.enableResearch;
command = command.replace(/{tech_name}/g, name);
command = command.replace(/{tech_researched}/g, tech.researched);
command = command.replace(/{tech_level}/g, tech.level);
command = command.replace(/{tech_infinite}/g, tech.infinite);
command = command.replace(/{tech_progress}/g, tech.progress)
command = command.replace(/{notify}/g, notify);
this.messageInterface(command);
let log_message = tech.infinite
@@ -299,8 +328,9 @@ class ResearchSync {
update_technologies_progress(to_update) {
for (let [name, tech] of Object.entries(to_update)) {
if (!this.research[name])
continue
if (!Object.hasOwnProperty.call(this.research, name)) {
continue;
}
let progress = this.research[name].progress
if (progress === null)
progress = 'nil'
@@ -317,11 +347,10 @@ class ResearchSync {
}
print_own_contribution() {
if (!this.prev_research)
return
for (let [name, tech] of Object.entries(this.research)) {
if (!this.prev_research[name])
continue
if (!Object.hasOwnProperty.call(this.prev_research, name)) {
continue;
}
let diff = this.research[name].contribution - this.prev_research[name].contribution
if (Math.abs(diff) > Number.EPSILON * 1000)
this.log(`Own research ${name}: ${this.research[name].progress} += ${diff}`)
@@ -346,8 +375,10 @@ class ResearchSync {
if (isNaN(level) || isNaN(researched))
return
this.prev_research[name] = this.research[name]
if (!this.prev_research[name]) {
if (Object.hasOwnProperty.call(this.research, name)) {
this.prev_research[name] = this.research[name];
} else {
this.prev_research[name] = {
researched: null,
level: null,
+1 -1
View File
@@ -1,6 +1,6 @@
const assert = require("assert");
const isFactorioCommand = require("_app/isFactorioCommand");
const isFactorioCommand = require("lib/isFactorioCommand");
const researchSync = require("./index.js");
+1 -1
View File
@@ -1,6 +1,6 @@
const fs = require("fs-extra");
const path = require("path");
const clusterTools = require("_app/clusterTools")();
const clusterTools = require("lib/clusterTools")();
module.exports = class remoteCommands {
constructor(mergedConfig, messageInterface, extras){
+3 -3
View File
@@ -9,9 +9,9 @@ module.exports = class remoteCommands {
this.messageInterface = messageInterface;
this.config = mergedConfig;
this.socket = extras.socket;
const pluginManager = require("./../../lib/manager/pluginManager.js")(this.config);
const configManager = require("./../../lib/manager/configManager.js");
// const modManager = require("./../../lib/manager/modManager.js")(this.config);
const pluginManager = require("lib/manager/pluginManager.js")(this.config);
const configManager = require("lib/manager/configManager.js");
// const modManager = require("lib/manager/modManager.js")(this.config);
this.socket.on("hello", () => {
this.socket.emit("registerServerManager");
});
+1 -1
View File
@@ -2,7 +2,7 @@ const needle = require("needle");
const fs = require("fs-extra");
const path = require("path");
const clusterTools = require("_app/clusterTools")();
const clusterTools = require("lib/clusterTools")();
const pluginConfig = require("./config");
module.exports = class remoteCommands {
@@ -3,7 +3,7 @@ const path = require("path");
class masterPlugin {
constructor({config, socketio, express, Prometheus}){
const authenticate = require("./../../lib/authenticate")(config);
const authenticate = require("lib/authenticate")(config);
this.config = config;
this.io = socketio;
Loaded 100 of 101 files, more files were not shown because too many files have changed in this diff. Show more