Compare commits

..
Author SHA1 Message Date
Daniel Vestøl eb3bf980d3 Fixed remoteMap stuff 2019-04-22 13:40:50 +02:00
Daniel Vestøl 82141ac9d7 Fixed scenario_loader 2019-04-20 18:00:01 +02:00
Daniel Vestøl b7ce4a18a4 Better error for plugin file subscriptions 2019-04-20 17:59:27 +02:00
Daniel Vestøl 303f9d2999 Merge branch 'master' into remoteMap 2019-04-20 16:53:28 +02:00
Daniel d7bfc2193e Merge pull request #151 from axapaxa123/master
Neural Dole v2
2019-04-12 11:54:12 -07:00
axapaxa123 ce9e1d876a Updated neural network and interface 2019-04-10 01:14:39 +02:00
axapaxa123 86d4f3dba4 Longer accounting period, moved creation of gauge 2019-04-10 01:13:06 +02:00
axapaxa123 0fe5633926 Moved gauge creation, 0 items don't fail requests 2019-04-10 01:11:32 +02:00
axapaxa123 595b4e5ac9 Update master.js 2019-04-03 14:40:42 +02:00
axapaxa123 7d2ac5808f Update dole_nn_base.js 2019-04-03 14:40:11 +02:00
axapaxa123 fddafaec09 Update remove.js 2019-04-03 14:39:29 +02:00
Daniel Vestøl abf4b5d926 Updated freeplay script to 0.17 version 2019-03-31 17:56:34 +02:00
Daniel Vestøl 00dfae8bd1 Fixed another bug in scenarioLoader 2019-03-24 20:34:53 +01:00
Daniel Vestøl 2ef6c93b05 Use old doledivision as default, new NN based dole is optional 2019-03-24 20:18:03 +01:00
Daniel Vestøl 2165acb171 Merge branch 'neuralDoleDivision' 2019-03-24 20:16:32 +01:00
Daniel Vestøl 8357fa23ce Fixed bug in scenarioLoader 2019-03-24 20:14:47 +01:00
Daniel Vestøl 94d434c8ef Initial release of neural net based dole divider 2019-03-23 00:14:23 +01:00
Daniel Vestøl 56cf131c70 Moved most of /api/remove to its own file 2019-03-22 19:29:56 +01:00
Daniel Vestøl a2be560b8b Updated hotpatch 2019-03-13 20:13:03 +01:00
Daniel a3e33f6164 Merge pull request #148 from Danielv123/snyk-fix-9pjp3n
[Snyk] Fix for 7 vulnerable dependencies
2019-03-10 06:23:51 -07:00
Daniel 01e96a4dcb Merge pull request #149 from wchristian/patch-1
independant -> independent
2019-03-06 22:19:20 -08:00
Christian Walde (Mithaldu) d189a82494 independant -> independent 2019-03-07 05:03:45 +01:00
Daniel Vestøl c15709ddc1 0.17 update: Fixed hotpatch and scenario_loader 2019-02-27 11:05:39 +01:00
Daniel Vestøl 9f3978e0b5 Merge branch 'master' into remoteMap 2019-02-27 11:02:55 +01:00
snyk-bot 3f29a722bb fix: .snyk & package.json to reduce vulnerabilities
The following vulnerabilities are fixed with a Snyk patch:
- https://snyk.io/vuln/npm:hawk:20160119
- https://snyk.io/vuln/npm:http-signature:20150122
- https://snyk.io/vuln/npm:mime:20170907
- https://snyk.io/vuln/npm:minimatch:20160620
- https://snyk.io/vuln/npm:request:20160119
- https://snyk.io/vuln/npm:tunnel-agent:20170305
- https://snyk.io/vuln/npm:uglify-js:20151024
2019-02-25 06:48:23 +00:00
Daniel Vestøl 103409b0cd Added setting disableFairItemDistribution to disable dole division factor 2019-02-18 19:49:49 +01:00
Daniel 7e0b131557 Merge pull request #147 from joshricker/patch-1
Remove command splitting
2019-02-18 10:27:45 +01:00
joshricker f9a9722215 Remove command length.
Remove command length for better compatible with large mod packs.
2019-02-18 03:26:11 -06:00
Daniel Vestøl e731fcb4a9 Fixed bug with negative coordinates in remoteMap 2019-02-08 09:16:57 +01:00
Daniel Vestøl 9377eb9544 Updated remoteMap scenario 2019-02-05 20:42:09 +01:00
Daniel Vestøl f6afa488d1 Partially fixed broken web API test, removed old endpoint 2019-02-04 13:19:06 +01:00
Daniel Vestøl 936600c98b Merge branch 'master' into remoteMap 2019-02-04 13:02:20 +01:00
Daniel Vestøl 78061df0c2 Removed dead asset source from remoteMap 2.0 2019-02-04 13:02:07 +01:00
Daniel Vestøl 0024b84745 Added license note for Angels Icons 2019-02-04 12:50:20 +01:00
Daniel Vestøl 7352ee3751 Restructured scenario_loader a bit 2019-02-04 12:49:53 +01:00
Daniel Vestøl 3a87aa2959 Initial draft of remoteMap 3.0 2019-02-04 12:48:48 +01:00
Daniel df7100ecba Merge pull request #146 from Teoxoy/master
Added logo
2019-02-01 07:48:53 +01:00
Daniel df1875f24d Update README.md 2019-02-01 07:48:33 +01:00
Teoxoy d9afb92ade added logo 2019-02-01 00:05:25 +01:00
Daniel Vestøl 09f476ec40 Added support for 3625 Angels item icons 2019-01-31 14:34:02 +01:00
Daniel Vestøl 846853813a Added rcon tool and better prometheus monitoring for websockets 2019-01-05 14:35:43 +01:00
Daniel Vestøl 064ee0827a Updated Hotpatch to 1.1.11, might have fixed some desync issues 2019-01-02 13:50:18 +01:00
Daniel Vestøl d859cb554a Merge branch 'search' 2018-12-17 22:15:07 +01:00
Daniel Vestøl eaa8d5373c Readded whitespaces 2018-12-17 22:14:51 +01:00
Heilig, Eric 6855923e58 formatting 2018-12-17 14:06:33 -07:00
Heilig, Eric a8fb5197fd allow searching for multiple criteria separated by space 2018-12-17 14:01:38 -07:00
Daniel abc94a16c1 Merge pull request #143 from J-Lassauw/ui_overhaul
Removed Duplicate entity draw entry
2018-12-15 18:59:15 +01:00
J-Lassauw ceaf69810b Removed Duplicate entity draw entry 2018-12-15 17:57:07 +00:00
Daniel Vestøl 2efb628884 Merge remote-tracking branch 'origin/dev' 2018-12-15 16:48:41 +01:00
Daniel d4600c960e Merge pull request #142 from J-Lassauw/ui_overhaul
Web UI Overhaul
2018-12-15 16:47:38 +01:00
J-Lassauw 81fa5abae0 Small fix to make sure old pages using the side bar go to the new nav 2018-12-14 14:30:36 +00:00
J-Lassauw dc9d8bf987 Added fetch mod meta functionality
Fixed bug in settings with new UI
Re-added mod nav options
2018-12-13 16:56:09 +00:00
J-Lassauw 1788d72f8f Updated node details and settings style
Fixed nav spacing.
Added a header to the storage table.
2018-12-11 22:31:30 +00:00
J-Lassauw 659b071eb6 Started web UI overhaul
Added bootstrap
Broke some functionality with additional mods
2018-12-09 15:05:52 +00:00
Daniel Vestøl 274c5af9f5 Added new auth function to lib/authenticate 2018-12-09 15:59:11 +01:00
Daniel Vestøl 8c0a4b1a9e . 2018-11-27 21:00:12 +01:00
Daniel Vestøl 3b9dd6dcc8 Fixed bug with serverManagers web interface 2018-11-21 07:35:51 +01:00
Daniel Vestøl 9b6f8cf652 Fixed error related to getting playerCount 2018-11-17 16:09:46 +01:00
Daniel Vestøl ce13ad50e0 Fixed desync when viewing scenario code in hotpatch 2018-11-17 11:05:10 +01:00
Daniel Vestøl 18c486855d Revamped scenario loader for cross platform compat 2018-11-15 21:37:42 +01:00
Daniel Vestøl 12a0ebf8d1 Scenario loading has big issues with paths, this is a bandaid 2018-11-14 22:17:09 +01:00
Daniel Vestøl 081d9a5bea Made scenario loading less insanely spammy 2018-11-13 21:28:13 +01:00
Daniel Vestøl 1e868b4ff4 Changed hotpatch scenario dir for visibility, made scenario loading recursive 2018-11-13 21:24:42 +01:00
Daniel Vestøl 58b609cbd0 Fixed portable instances 2018-11-10 17:29:32 +01:00
Daniel Vestøl 2c6b8becd4 Added setting disableImportsOfEverythingExceptElectricity 2018-11-07 19:24:04 +01:00
Daniel Vestøl 1676dc80db Merge branch 'dependencyRemoval' 2018-10-23 11:13:36 +02:00
Daniel Vestøl 52d35ab955 Added new plugin for reporting production/kills/construction statistics to Prometheus 2018-10-23 11:10:59 +02:00
Daniel Vestøl aaf4d8b6f1 Removed graphing functionality that utilized linvodb 2018-10-23 11:10:26 +02:00
Daniel Vestøl 8766529e5b Removed linvodb/leveldown dependency 2018-10-22 11:06:21 +02:00
Daniel Vestøl 1b518964fc Updated readme 2018-10-18 21:30:23 +02:00
Daniel Vestøl c4a2b50c08 Fixed some broken statistics 2018-10-17 21:50:32 +02:00
Daniel Vestøl 3f4309fab0 Added pushgateway for prometheus statistics 2018-10-12 23:28:10 +02:00
Daniel Vestøl 2855268e34 Attempted fix for breaking /slaves/ 2018-10-11 14:47:13 +02:00
Daniel Vestøl ffe42c523a Added trainTeleports to plugin list in readme 2018-10-04 19:33:36 +02:00
Daniel db20e9c780 Merge pull request #138 from jakedraddy/master
Add plugin list to readme (with discordchat plugin).
2018-10-01 23:39:15 +02:00
Jacob Draddy 02a401c6c3 Add plugin list to readme (with discordchat plugin). 2018-10-01 17:34:40 -04:00
Daniel Vestøl 94ac9ca624 Added scenario_loader plugin, updated hotpatch (new saves now gives the starting items) 2018-09-29 15:21:35 +02:00
Daniel Vestøl e2566d2d1c [big change] Added serverManager plugin 2018-09-27 14:35:41 +02:00
Daniel Vestøl 852311e84c Plugin loading race condition, resolves #136 2018-09-27 13:48:41 +02:00
Daniel Vestøl 810e4a27b5 Removed snyk 2018-09-27 10:35:25 +02:00
Daniel Vestøl 8a84f4087a Bugfix and usability improvements 2018-09-23 09:17:07 +02:00
Daniel Vestøl 087ba8dabe Improved docs, disabled remoteMap by default 2018-09-09 19:55:09 +02:00
Daniel Vestøl 07b9d0a0f5 Fixed getMac spam in VMs, added log rotation 2018-08-23 10:04:19 +02:00
Daniel Vestøl 857a6824fc Added analytics 2018-08-16 14:52:26 +02:00
Daniel Vestøl 765146b559 Plugin loading now respects disabled plugins 2018-08-16 14:52:09 +02:00
Daniel Vestøl ffa2d0b0fb Fixed windows crash on ctrl+c preventing factorio from saving 2018-08-16 12:19:18 +02:00
Daniel Vestøl a76e07c07b Enable and disable plugins from the commandline (non functional as of yet) 2018-08-15 23:15:01 +02:00
Daniel Vestøl e71c183b48 Collet http performance statistics for plugins as well 2018-08-14 13:05:17 +02:00
Daniel Vestøl 1faa3671e4 configManager.js - more useful return values 2018-08-11 19:59:35 +02:00
Daniel Vestøl 5630f7f7f6 Rewrote master plugin loading 2018-08-10 17:17:19 +02:00
Daniel Vestøl da268d57e7 Serverside rendering of /slaves for more plugin functionality 2018-08-10 15:51:41 +02:00
Daniel Vestøl 8518cd54c6 Introduce more tools for use by plugins 2018-08-10 15:50:46 +02:00
Daniel 3a7527f053 Merge pull request #135 from Valansch/master
Readme typo
2018-08-01 14:21:26 +02:00
Valansch cac481307e Fixed typo in README 2018-08-01 13:54:37 +02:00
Daniel Vestøl 9461978a6e Reduced log spam 2018-07-30 13:30:03 +02:00
Daniel Vestøl 4fed43bad6 [big change] Moved core clusterio Lua mod functionality into its own plugin 2018-07-28 13:45:05 +02:00
Daniel f7f2ce6897 Merge pull request #133 from Gerkiz/patch-1
Added docker support
2018-07-25 21:58:03 +02:00
Gerkiz 49124021a8 Added docker support
Docker doesn't really do anything with the  '/quit'.
However, changing from 'quit' to 'serverprocess.kill("SIGINT");' solved this issue.
The node exits with:
'[dumb-init] Child exited with status 0. Goodbye.'
2018-07-25 21:47:03 +02:00
Daniel Vestøl 329f721b11 Fixed invalid json in config.json.dist 2018-07-25 02:09:00 +02:00
Daniel Vestøl 8abeb26dce Commandline tools to install/uninstall plugins from github 2018-07-25 01:14:43 +02:00
Daniel Vestøl 958cdfa15a Fixed node client.js manage * mods * 2018-07-24 23:14:02 +02:00
Daniel Vestøl d72a45cc66 Added autosaveInterval to the config.json.dist 2018-07-24 23:01:51 +02:00
Daniel Vestøl d913d59d72 Handle instance creation failing gracefully upon lacking internet 2018-07-24 23:00:42 +02:00
Daniel Vestøl 9ce4f064ad Add favicon to master (I think, just a quick-fix) 2018-07-24 22:59:40 +02:00
Godmave d9638b58ea #add configurable items autosave (default 1 minute, config.autosaveInterval) 2018-07-23 19:16:53 +02:00
Godmave 260f8a0bb5 Merge pull request #5 from Danielv123/master
Merge pull request #131 from Godmave/master
2018-07-23 19:13:44 +02:00
Daniel b12f12a91f Merge pull request #131 from Godmave/master
Fix inventory import
2018-07-23 15:19:33 +02:00
Godmave 7784fecb12 #add inventory items up instead of overwriting them 2018-07-21 00:21:27 +02:00
Godmave 68637d78b8 Merge pull request #3 from Danielv123/master
Get current
2018-07-20 23:45:50 +02:00
Daniel dc52d64c7e Merge pull request #123 from Danielv123/snyk-fix-y7mchf
[Snyk] Fix for 1 vulnerable dependencies
2018-07-18 10:42:34 +02:00
Daniel Vestøl adbf28d6bd Made travis run against node 9 instead of 10 2018-07-18 10:33:39 +02:00
Daniel Vestøl 42037fc451 client.js now adds Hotpatch to new maps 2018-07-18 10:30:35 +02:00
Daniel Vestøl 8e9bcc6a62 Untracked package-lock.json 2018-07-18 10:20:20 +02:00
Daniel 789e9d37c8 Merge pull request #130 from Sir3lit/patch-1
This will build correctly
2018-07-18 09:56:45 +02:00
Sir3lit 5c2eb009e2 This will build correctly
The build is successful :)
2018-07-18 02:06:19 -04:00
Daniel f91ff41dff Merge pull request #129 from Godmave/master
Add hotpatch
2018-07-17 18:10:16 +02:00
Godmave 94b2bb5463 #add hotpatch to master 2018-07-16 23:10:56 +02:00
Godmave 002dae9911 Merge pull request #2 from Danielv123/master
get latest
2018-07-16 23:06:15 +02:00
Daniel Vestøl e73a6052fe Instance creation with external instance paths are now working 2018-07-15 17:43:26 +02:00
Daniel Vestøl 724248b09b Merge branch 'master' into #85 2018-07-15 16:10:28 +02:00
Daniel Vestøl 3557b2d0ad Merge remote-tracking branch 'origin/master' 2018-07-15 00:02:11 +02:00
Daniel 1d5d8abd12 Merge pull request #128 from hintss/patch-1
fix typo in log message
2018-07-14 21:44:57 +02:00
Daniel Vestøl 98131ee66e Very big changes to help custom config paths 2018-07-13 22:56:10 +02:00
Daniel Vestøl 509a13062e Merge branch '#85' 2018-07-13 22:55:38 +02:00
Daniel Vestøl 02c545c650 Small change allowing plugins to add sidebar entries 2018-07-13 22:54:58 +02:00
hintss af42b1fcd3 fix typo in log message 2018-07-13 01:36:12 -07:00
Daniel 5dcbf45487 Merge pull request #127 from chrisgbk/master
Fix race condition in node client download
2018-07-10 13:20:35 +02:00
chrisgbk 9501861285 Fix race condition in node client download
Fix race condition in node client download
2018-07-10 04:23:40 -06:00
snyk-bot f8bf979327 fix: .snyk & package.json to reduce vulnerabilities
The following vulnerabilities are fixed with a Snyk patch:
- https://snyk.io/vuln/npm:lodash:20180130
2018-07-03 06:07:59 +00:00
Daniel Vestøl 6962324a45 Tried to fix path problem in the master db 2018-06-28 15:34:31 +02:00
Daniel Vestøl cf0c3b012a Added config settings for globalChat (broadcast all messages) 2018-06-27 11:40:20 +02:00
Daniel ca49564fb7 Update README.md 2018-06-27 00:18:36 +02:00
Daniel Vestøl f13d90aa58 Merge branch '#85' 2018-06-27 00:13:27 +02:00
Daniel Vestøl 9721071ffb Merge branch 'master' into #85 2018-06-27 00:12:41 +02:00
Daniel Vestøl a77aeb9e5b Improved readme 2018-06-27 00:11:28 +02:00
Daniel dbd9876b8b Merge pull request #120 from Alauraa/patch-2
Updated to reflect Bug #118
2018-06-24 19:19:08 +02:00
Daniel cfa52dbcca Clarified a few things 2018-06-24 19:18:52 +02:00
Alaura 8f8436a809 Updated to reflect Bug #118
Updated to reflect Bug #118 

Adds the node client.js manage shared mods add clusterio To line 95
2018-06-24 11:48:59 -05:00
Daniel Vestøl bf19001055 Fixed --config, is probably unstable on client.js 2018-06-24 16:48:08 +02:00
Daniel Vestøl 443e5dc286 Arguments: --port --masterPort --sslPort --config --rcon-port --rcon-password 2018-06-24 16:46:31 +02:00
Daniel Vestøl 8267dae10e #85 update config.ini with absolute paths 2018-06-24 15:00:48 +02:00
Daniel Vestøl d177fb7304 client.js exits after creating and instance, better comments 2018-06-24 14:24:36 +02:00
Daniel Vestøl 7d99162072 Merge branch '#85' 2018-06-24 13:57:24 +02:00
Daniel 2a8228c5a9 Merge pull request #117 from Alauraa/patch-1
Updated Readme with npmPostinstall.js.
2018-06-23 13:56:39 +02:00
Alaura b86d4fa2ab Updated Readme with npmPostinstall.js.
Updated Readme with npmPostinstall.js.

Made it so after you npm install, it adds all the required files the server needs and allows you to run it using node. Without errors.
2018-06-23 06:55:43 -05:00
Daniel Vestøl b783ccf2cb Changed default config values 2018-06-20 14:24:15 +02:00
Daniel Vestøl 010e5271ea Might have fixed bug related to mod uploads 2018-06-19 23:58:56 +02:00
Daniel Vestøl 447766c752 Fixed mod manager not exiting after downloading mods 2018-06-16 15:41:06 +02:00
Daniel Vestøl e4c276054b Don't spam internal errors when server is paused/shutting down 2018-06-13 20:59:39 +02:00
Daniel Vestøl b7f76bf451 resolves #90 - Added ups and gametick reporting to prometheus (/metrics) 2018-06-12 23:01:42 +02:00
Daniel Vestøl 780feb7eb8 Prevented \n in playerCount, webinterface function to get queryString params added 2018-06-10 09:53:22 +02:00
Daniel Vestøl b98cb9def6 Added icon to web interface, made by z4rg0n 2018-06-09 12:45:47 +02:00
Daniel Vestøl 1b6b0abaeb Decreased master load time by 30%, web postJSON now returns a promise 2018-06-09 00:00:18 +02:00
Daniel Vestøl ea8f819bd5 Fixes for last commit 2018-06-08 13:57:52 +02:00
Daniel Vestøl 94bbf62c26 Added support for both https AND http, config is now untracked by git 2018-06-08 09:33:19 +02:00
Daniel Vestøl a9532dd0d3 Merge branch 'master' into https 2018-06-08 09:11:38 +02:00
Daniel Vestøl 034db54152 Added lots of web interface related stuff for external plugin 2018-06-06 21:33:08 +02:00
Daniel Vestøl 17b5967b0f Added mention about masterAuthToken in the readme 2018-06-06 12:57:17 +02:00
Daniel Vestøl 226e157f69 Added onExit handler to masterPlugins (see description)
To use the onExit handler, simply add a method called onExit to your exported class. If it returns a promise, the master will wait for it to resolve.
2018-06-06 12:54:11 +02:00
Daniel Vestøl f72ea7357e Removed debug line that put auth info in the logs 2018-06-03 17:52:21 +02:00
Danielv123 812265d0fd messageInterface now returns a promise as well 2018-05-31 21:18:05 +02:00
Danielv123 da6f96a34b UPSdisplay now uses rcon return value instead of game.write_file 2018-05-31 21:15:34 +02:00
Danielv123 88e8ab454f Possibility of having master-only plugins 2018-05-31 21:03:05 +02:00
Danielv123 e87c702cf2 Added plugin functionality to master 2018-05-31 21:02:47 +02:00
Danielv123 e6c706a83e globalChat no longer breaks when the master is restarted 2018-05-26 13:16:59 +02:00
Danielv123 47b74a07d1 Merge remote-tracking branch 'origin/master' into dev 2018-05-26 12:52:40 +02:00
Daniel 0ee2c5c2f6 Resolves #103 2018-05-26 12:50:15 +02:00
Danielv123 e73c5055af !info to see servername (issue #101) 2018-05-21 00:00:42 +02:00
Danielv123 165cfffecb Use rcon.print instead of writefile for playercount reports 2018-05-13 09:58:25 +02:00
Danielv123 ce6dc3af28 messageInterface now takes a second arg for rcon return values (from rcon.print()) 2018-05-10 14:15:03 +02:00
Danielv123 1d9b6a26a6 Attempted fix to #97 2018-05-10 10:33:26 +02:00
Daniel db89099b2d Merge pull request #95 from Elbrasch/master
Issue #86
2018-05-09 11:12:48 +02:00
Stefan Asbeck 973f0b0e0a Create new random masterAuthSecret if secret-api-token.txt doesn't exist. 2018-05-08 22:04:02 +02:00
Danielv123 0b81fb6351 Made factorio command ratelimiting configurable 2018-05-07 18:11:46 +02:00
Danielv123 ac55b1c442 Prometheus track dole factor for imports 2018-05-06 20:45:52 +02:00
Danielv123 f07b6160bb Merge branch 'master' into dev 2018-05-06 16:07:51 +02:00
Daniel 480e2af03e Merge pull request #93 from Elbrasch/master
fixes issue #87 "Mod manager downloads oldest version of mod availabl…
2018-05-06 16:06:48 +02:00
Stefan Asbeck 8fe908aa5d fixes issue #87 "Mod manager downloads oldest version of mod available" by downloading the latest version
added factorio version to config file instead of hardcoding it.
2018-05-06 15:55:23 +02:00
Danielv123 cf1a97ac9f Added caching to /api/slaves (default 5s) 2018-05-06 14:30:03 +02:00
Danielv123 6dd25cb1e8 Don't assume playercount is 0 just because a command fails 2018-05-06 10:32:11 +02:00
Danielv123 ce24a40d8c Prometheus now tracks import/export stats PROPERLY 2018-05-06 10:23:55 +02:00
Danielv123 f5d1847975 Revert "Prometheus now tracks import/export statistics"
This reverts commit f580f06064.
2018-05-06 10:21:49 +02:00
Danielv123 f580f06064 Prometheus now tracks import/export statistics 2018-05-06 10:21:06 +02:00
Danielv123 f5e908eb5d Revert "Fix outdated leveldown dependency"
This reverts commit a6d98db6a9.
2018-05-05 14:34:50 +02:00
Danielv123 a6d98db6a9 Fix outdated leveldown dependency 2018-05-05 13:52:36 +02:00
Danielv123 b49a37e438 Use compression on master 2018-05-05 12:47:16 +02:00
Danielv123 c02bb6d58f #85 made instance directory configurable in config.json 2018-05-04 17:50:20 +02:00
Danielv123 a0e98ecb00 Format numbers with , (1,000) 2018-05-02 20:37:55 +02:00
Danielv123 6816db0628 More detailed Websocket usage statistics 2018-05-02 12:39:33 +02:00
psihius be73f2f3a5 Changed command splitting logic from json data to lua tables 2018-05-01 21:36:38 +03:00
Daniel d07112e85e PR #83 from Valansch: Shout displays instance name now 2018-05-01 14:32:27 +02:00
Valansch 1fede26564 Shout displays instance name now 2018-05-01 12:22:19 +02:00
MCRcortex c7e2308a33 Added commandBuffer 2018-05-01 08:23:49 +10:00
MCRcortex 5c064e4609 Merge pull request #1 from Danielv123/dev
Dev
2018-05-01 08:10:37 +10:00
Danielv123 6514462a1d Increased max command size for imports 2018-04-30 23:47:31 +02:00
Danielv123 1e0c275968 Use rcon instead of stdin on linux as well 2018-04-30 23:34:09 +02:00
Danielv123 968a306c5a Fixed issue where slaves would not register when ran on VM withou MAC 2018-04-30 23:05:55 +02:00
Danielv123 47430b5d28 Remove super exessive logging on master
Also added better prometheus endpoint logging
2018-04-30 14:30:01 +02:00
Danielv123 6e5585c86c Merge remote-tracking branch 'MCRcortex/dev' into dev 2018-04-30 14:26:14 +02:00
MCRcortex 9c9db7882c enabled inventoryFrame command splitting 2018-04-30 22:10:13 +10:00
MCRcortex d6369ba295 reverted stuff to prev commit
reverted stuff back to prev commit until the mod has been updated
2018-04-30 21:46:45 +10:00
MCRcortex f4e03899e7 fixed bug 2018-04-30 21:28:45 +10:00
MCRcortex 0afc3d7e78 split receiveInventory into multiple commands 2018-04-30 20:44:29 +10:00
Danielv123 355e8974a4 Added more websocket utilization metrics 2018-04-30 12:09:01 +02:00
MCRcortex 89ad2baa26 fixed bug
fixed so that it doesnt send when cmd=="["
2018-04-30 19:32:43 +10:00
MCRcortex 57f7dec956 recomments stuff 2018-04-30 18:41:02 +10:00
MCRcortex 5b59abb8e3 changeds timings 2018-04-30 18:38:48 +10:00
MCRcortex 66b64eae1b started fixing receiveInventory 2018-04-30 18:37:08 +10:00
Danielv123 f15ce3fb23 No longer auto install clusterio 1.0.0 2018-04-29 21:45:22 +02:00
Daniel 41541b01f0 Merge pull request #80 from Danielv123/snyk-fix-c5e06e84
[Snyk Update] New fixes for 4 vulnerable dependency paths
2018-04-29 11:46:54 +02:00
MCRcortex c5b55f27b7 fixed brakets v2 2018-04-29 19:42:19 +10:00
MCRcortex a67a1d3d1d fixed brakets 2018-04-29 19:30:44 +10:00
MCRcortex bcd1ec79cb fixed to get items 2018-04-29 18:51:08 +10:00
MCRcortex 594c994560 Tried to fix desyncs further
based on factorio networking stuff
2018-04-29 13:52:11 +10:00
MCRcortex f10d6bbfb5 tried to fix desyncs 2018-04-29 10:29:35 +10:00
Danielv123 1184e64467 Attempt to be even more long command friendly 2018-04-28 19:51:23 +02:00
Danielv123 02ab34dbf4 Don't remove all spaces from !shouts 2018-04-28 15:04:15 +02:00
Danielv123 fc1f3bd077 Attempted fix for command corruption 2018-04-28 14:47:53 +02:00
Danielv123 6a7042c0aa Improved performance of /api/getFactorioLocale with caching 2018-04-28 14:16:34 +02:00
Danielv123 2aff63e4a1 Strip all non a-z 0-9 characters from !shout commands 2018-04-28 03:10:57 +02:00
Danielv123 e38aed08bf Sanitizing !shout chat 2018-04-28 02:19:42 +02:00
Danielv123 1bd87c4cc9 Fixed crash on !shout 2018-04-27 21:58:03 +02:00
Danielv123 dc19457471 Update to master to support server wide alerts 2018-04-27 21:23:36 +02:00
Danielv123 84aaae05c2 Merge remote-tracking branch 'origin/dev' into dev 2018-04-27 11:37:15 +02:00
snyk-bot 73ce30ded4 fix: package.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/npm:hoek:20180212

Latest report for danielv123/factorioclusterio:
https://snyk.io/test/github/danielv123/factorioclusterio
2018-04-27 05:19:56 +00:00
Danielv123 1b00fbb328 Fixed crash when master was not there and we still got some sort of response 2018-04-27 06:22:32 +02:00
Daniel 29c38fc4d0 Merge pull request #79 from psihius/patch-1
README.md update: adjusted command
2018-04-27 06:18:59 +02:00
Arvids Godjuks 2e1f7b7f6d README.md update: adjusted command
Adjusted pm2 start command to have correct instance names in the process list
2018-04-26 23:58:26 +03:00
Danielv123 61e944b061 Attempted to add https support 2018-04-26 18:53:06 +02:00
Danielv123 ce0deef83b Websockets for listening to factorio logs and global chat plugin
You can now !shout things across the cluster
2018-04-26 18:33:29 +02:00
Daniel 641f980741 Merge pull request #77 from psihius/dev
Cluster wide command execution
2018-04-26 16:48:50 +02:00
psihius ebf2fd2204 Added cluster wide command execution + fixed some checks failing due to type mismatch 2018-04-26 15:40:47 +03:00
Danielv123 20e66cbd40 Token auth in researchSync and UPSdisplay 2018-04-25 12:55:03 +02:00
Danielv123 a26189947d Token auth in inventoryImports 2018-04-25 12:50:33 +02:00
Danielv123 0b0b2bc1a9 Token auth for all destructive endpoints 2018-04-25 12:44:31 +02:00
Daniel 4b7ad9b8b8 Split install command onto multiple lines 2018-04-25 09:28:21 +02:00
Daniel f63fd20179 Merge pull request #76 from psihius/dev
README.md update
2018-04-23 10:53:08 +02:00
psihius 8cc52663e5 Adjusted readme install string - nodejs 9 and added python-dev and build-essential packages as those can be missing on some cloud images 2018-04-23 10:56:52 +03:00
Danielv123 8f2d22cbd8 Only wrote files on the server, might resolve some crashes 2018-04-21 08:05:40 +02:00
psihius bf9886d7ee Merge branch 'dev' of https://github.com/Danielv123/factorioClusterio into fork-dev 2018-04-19 23:33:27 +03:00
psihius 03511fc240 Hardened NaN checks, added file read delay for research-sync so factorio has time to finish writing to the file 2018-04-19 23:33:09 +03:00
Danielv123 2111e337c9 Fix crash when starting client.js without master 2018-04-19 18:41:30 +02:00
Danielv123 0672004cbd researchSync: Partially fixed failing test (it still fails) 2018-04-19 17:51:46 +02:00
psihius ed9b4bbafa Changed research sync to work with /api/slaves so the instance can ignore it's own research tree and enhanced research enabling with proper messages and sound alert, added checks so we don't flip values that haven't changed 2018-04-19 18:29:05 +03:00
psihius 9a66542270 Fixed so local research is never disabled 2018-04-18 23:27:53 +03:00
psihius 1c8deea2dc Fixed research sync for levels and repeated execution 2018-04-18 10:38:44 +03:00
psihius 19913af056 Changed research transfer data from array to objects 2018-04-17 23:37:34 +03:00
psihius 718315849c Made sure data is converted to integers 2018-04-17 22:44:56 +03:00
psihius 1731426f49 Removed unnesecesary json parsing and adjusted the expression for better style 2018-04-17 22:07:18 +03:00
psihius bb06723918 Changed some research fields and added a check remoteResearch key existence 2018-04-17 21:48:34 +03:00
psihius 0edb4caff9 Typo fix 2018-04-17 14:26:09 +03:00
psihius c7cd922541 Updated research dump and enabling with a fix for infinite-research 2018-04-17 13:57:36 +03:00
Danielv123 0dee243bf9 crash fix 2018-04-16 22:16:22 +02:00
Danielv123 2ab812d5a9 prometheus track count of items in inventory 2018-04-16 10:49:09 +02:00
Danielv123 5076b4e8e1 Increased slave database limits due to demands from researchSync 2018-04-16 08:48:54 +02:00
Danielv123 b7b0dd9d85 prometheus track playercounts 2018-04-16 08:35:10 +02:00
Danielv123 cf7af19370 Don't send corrupt factorio file outputs to plugins 2018-04-16 06:43:22 +02:00
Danielv123 4d5b3b855c Export production stats to prometheus 2018-04-15 23:04:35 +02:00
Danielv123 2b5cbf00e4 Fix error ion /nodeDetails sometimes making the display weird 2018-04-15 21:25:19 +02:00
Danielv123 aeeb09a975 Fixed weird thing about rebinding 'this' 2018-04-15 21:15:58 +02:00
Danielv123 7915b71e10 typo 2018-04-15 21:07:49 +02:00
Danielv123 2c2bbb71b4 inventoryImports: Fixed chat errors when players are dead 2018-04-15 12:02:46 +02:00
Danielv123 6c966e2c29 fixed issue with server browser token not being applied 2018-04-14 11:18:06 +02:00
Danielv123 6bbd6b8717 researchSync: Fixed crash when master contact is lost 2018-04-14 11:17:38 +02:00
Danielv123 1d0d9765bd researchSync: Fixed importing research 2018-04-13 20:18:47 +02:00
Danielv123 c9bd26a01e researchSync: Fixed sending commands with newlines 2018-04-13 18:31:01 +02:00
Danielv123 2d5c0c3bbf More tests for remoteMap 2018-04-13 16:37:57 +02:00
Danielv123 f131a83103 moved isFactorioCommand, more tests, researchSync stability 2018-04-13 12:32:09 +02:00
Danielv123 181cb1179c Fixed stack size exceeded bug 2018-04-13 10:35:55 +02:00
Danielv123 a8dfd22eeb . 2018-04-13 09:12:46 +02:00
Danielv123 23d31435d7 Fixed bug in remoteCommands 2018-04-13 09:08:02 +02:00
Danielv123 c8f2a67845 researchSync: Tried to avoid errors when research doesn't exist 2018-04-12 07:49:40 +02:00
Danielv123 7a4ea69cbb researchSync removed node-lua dependency 2018-04-11 21:38:28 +02:00
Danielv123 12bf445579 Added endpoint docs for endpoints introduced with researchSync 2018-04-11 15:39:25 +02:00
Danielv123 51e41835ee Merge branch 'dev' into pr/71 2018-04-11 15:22:42 +02:00
Danielv123 3af343c5a0 Merged researchSync 2018-04-11 15:20:15 +02:00
Danielv123 2daddde511 Improved auto generated endpoint documentation 2018-04-09 11:40:39 +02:00
Danielv123 43c1f2ae93 remoteCommands added plugin and master API endpoint for executing things 2018-04-09 11:25:57 +02:00
Danielv123 be49deb170 Changed import delay from 3 to 1 second 2018-04-09 09:14:15 +02:00
Danielv123 6b29b2a430 Autodownload latest clusterio on npm install 2018-04-09 09:13:17 +02:00
Danielv123 fe2bf75b89 Fixed issue where oldest version of mod is downloaded thanks to changes in undocumented factorio API 2018-04-04 16:32:51 +02:00
Danielv123 26483d63d3 remoteMap draw better trees 2018-04-02 18:27:36 +02:00
Danielv123 50d14394a6 Fixed error occasionally crashing client when plugins use lots of IO 2018-04-02 14:59:58 +02:00
Danielv123 cd4887e5cf Fixed getMac and launching instance with no mods 2018-04-02 12:14:06 +02:00
Danielv123 cbee07e451 No more external dependencies in web interface 2018-03-30 15:48:37 +02:00
Danielv123 f697d2a74c Multithreaded createMap, improved isValidInstance reliablity 2018-03-30 14:39:39 +02:00
Danielv123 507c0afa98 factorioServer relative paths instead of absolute 2018-03-30 11:07:26 +02:00
Danielv123 d9d8905d02 , 2018-03-29 19:42:19 +02:00
Danielv123 dfa5d9ad16 still trying to fix paths 2018-03-29 19:31:56 +02:00
Danielv123 9a0ce97746 factorioServer: Tried adding function for map creation, failed. 2018-03-29 14:40:07 +02:00
Danielv123 e55bf71431 fileOps: Fixed crash on invalid filenames, added test for it 2018-03-29 14:39:39 +02:00
Danielv123 e1ab5b4d91 More functionality in factorioServer and added unit tests 2018-03-28 21:00:38 +02:00
Danielv123 fff1989897 Moved fileOps to _app/fileOps 2018-03-28 20:57:57 +02:00
Danielv123 802c21f41e started work on extracting server launching and IO systems 2018-03-27 20:50:10 +02:00
Danielv123 e694593fdb remoteMap yellow transport belts 2018-03-26 16:15:26 +02:00
Danielv123 0aa25544b0 remoteMap prescan spawn and also scan newly generated chunks 2018-03-26 16:11:56 +02:00
Danielv123 2ab163ab68 remoteMap don't error on broken image links 2018-03-26 16:09:15 +02:00
Danielv123 486890480d Improved reliablity of plugins that burst write 2018-03-26 15:27:52 +02:00
Danielv123 47637f5511 remoteMap animated radar 2018-03-26 14:03:57 +02:00
Danielv123 fb3d27abef remoteMap added grass background 2018-03-26 13:25:03 +02:00
Danielv123 d0444ae00d remoteMap fixed animated belt alignements (not yellow) 2018-03-26 12:37:16 +02:00
Danielv123 8a1f440485 Added more prometheus monitoring to websockets 2018-03-26 11:03:28 +02:00
Danielv123 465ab9aeb3 master Added performance and usage logging for prometheus 2018-03-04 20:00:55 +01:00
Danielv123 dc62e714d6 remoteMap animated belts (yellows are bugged) 2018-03-03 21:58:31 +01:00
Danielv123 0b07698b78 remoteMap More high res entities 2018-03-02 00:03:42 +01:00
Danielv123 d1c2816669 remoteMap chem plant, oil refinery, splitters, underneathies, inserters 2018-02-28 23:50:57 +01:00
Danielv123 3499411290 remoteMap stone/steel furnace scaling, inserter rotations 2018-02-28 21:51:59 +01:00
Danielv123 e6d734d4eb remoteMap place rotated items, fix selection box offset 2018-02-28 18:01:34 +01:00
Danielv123 f5eef7c1bb remoteMap placing and removing entities 2018-02-26 23:28:51 +01:00
Danielv123 4b286f7b08 remoteMap mouse selection overlay 2018-02-26 11:26:29 +01:00
Danielv123 7f6f1d07d7 remoteMap resolve issue with shrinking viewport when having low FPS 2018-02-25 21:58:00 +01:00
Danielv123 619b7972b9 remoteMap move speed setting and wasd controls 2018-02-25 19:42:56 +01:00
Danielv123 909fc79bd2 remoteMap smooth and diagonal scrolling 2018-02-25 18:26:46 +01:00
Danielv123 6476c9e68a Ignored steam in /nodeDetails prod graph 2018-02-25 14:22:42 +01:00
Danielv123 eae1a76341 remoteMap: 60fps, option to pause when window is deselected to save CPU 2018-02-25 14:08:30 +01:00
Danielv123 551a2ea6d7 Fixed failing tests and clutter generated by chunkStore test 2018-02-25 13:16:59 +01:00
Danielv123 cde0d3265b Fixed remoteMap not deleting things properly 2018-02-25 12:41:15 +01:00
Danielv123 72d5f06381 Nuking forrests with remoteMap no longer produces lagspikes 2018-02-25 12:40:52 +01:00
Danielv123 dd9130dc8b Fixed remoteMap perf issues by making map and tilesize config (and tiny) 2018-02-25 00:09:24 +01:00
theCatsPaw d1a295ae4e researchsync: every server can now research 2018-02-23 23:02:44 +01:00
Danielv123 718c807699 Updated gitignore 2018-02-22 21:43:17 +01:00
Danielv123 c4fb313aef Added time estimate etc to the github mod downloading 2018-02-22 21:37:31 +01:00
Danielv123 1fcd91342f Made progress bar for github mod downloads 2018-02-22 21:00:35 +01:00
Danielv123 29bffa472b Fixed modManager add and remove functions 2018-02-22 19:13:47 +01:00
theCatsPaw 8935d03b4a researchSync: client 2018-02-21 22:26:33 +01:00
theCatsPaw e64db6b236 researchSync: master 2018-02-21 20:54:02 +01:00
Danielv123 64a29c004c Fixed modManager to work in 0.16 2018-02-18 19:50:35 +01:00
Danielv123 9e63086d6d Fixed more remoteMap things, I think it was rotations or something? 2018-02-18 18:17:20 +01:00
Danielv123 6f2262de52 Using spritesheets for more entities 2018-02-06 13:11:35 +01:00
Danielv123 d5d02aa80d Using real textures of belts from spritesheet instead of icons 2018-02-05 17:51:06 +01:00
Danielv123 1a434b0537 Made combinator worldID setting silent 2018-02-05 17:49:12 +01:00
Danielv123 fc874b4e4e Send worldID to circuits 2018-01-30 07:53:28 +01:00
Danielv123 f5fa8a4ca9 Cache getMac 2018-01-30 07:53:11 +01:00
Danielv123 7b661781f2 ws combinators now working 2018-01-19 22:02:50 +01:00
Danielv123 1ab3c49772 Attempted to fix combinator transmission 2018-01-19 20:30:15 +01:00
Danielv123 501807884f Fixed rebroadcasting every frame up to 500 times 2018-01-18 18:00:33 +01:00
Danielv123 b2afea666e initial attempt at converting combinators to use websockets 2018-01-18 17:42:22 +01:00
Danielv123 fdd8a8299b Fixed SSL test failing 2018-01-15 17:16:14 +01:00
Danielv123 0688184aaf Made sure all web dependencies load over HTTPS 2018-01-14 22:20:53 +01:00
Danielv123 864395c8aa Fixed private key generation 2018-01-14 22:17:25 +01:00
Danielv123 a307cd63a3 Auto generate ssl cert and download spritesheet on npm install 2018-01-14 21:50:47 +01:00
Danielv123 071a2e6dbe Added function to generate SSL certificate (unused, as of yet) 2018-01-14 21:39:45 +01:00
Danielv123 322a1711ab Cleaned up remoteMap drawing code using ES6 modules 2018-01-14 00:14:44 +01:00
Danielv123 ced0aa4522 remoteMap proper size rendering for chemical-plant 2018-01-13 15:04:18 +01:00
Danielv123 712b33b4a7 #59 improved helptext for new features 2018-01-11 22:07:18 +01:00
Danielv123 4b0bedbacf #59 fixed downloading from github repos using 'github releases' 2018-01-11 21:58:17 +01:00
Danielv123 5d72832402 #59 added node client.js manage mods shared/instance remove modname 2018-01-11 21:44:23 +01:00
Danielv123 5eaa94f4ac remoteMap added proper scaling to radar 2018-01-10 11:13:18 +01:00
Danielv123 1d92fe21c9 Added solar-panel and accumulator to remoteMap with correct sizes 2018-01-09 11:55:56 +01:00
Danielv123 9ce7cf5f28 Fixed typo (maybe) 2018-01-09 11:33:14 +01:00
Danielv123 68b0d3b75a Untested code to draw entities with correct sizes (hardcoded sizes) 2018-01-09 11:27:46 +01:00
Danielv123 b5a9ac3212 Fixed remoteMap trying to connect to localhost 2018-01-08 15:36:50 +01:00
Danielv123 625f88c63e node client.js manage [instancename] mods download [modname] 2018-01-08 15:01:10 +01:00
Danielv123 4d3924b339 . 2018-01-07 13:49:34 +01:00
Danielv123 a182a1525d protected nodePackage type plugins against missing script-output files 2018-01-07 13:48:24 +01:00
Danielv123 605d58443d Removed unused dependencies 2018-01-07 11:38:19 +01:00
Danielv123 2efe339fef Fixed some folders not getting created on fresh installs, throwing a few errors 2018-01-07 10:57:12 +01:00
Danielv123 618abb0ab9 1.7.1 2018-01-05 21:33:06 +01:00
Danielv123 66a583ad29 removed logging from remoteMap 2018-01-05 21:32:21 +01:00
Danielv123 894d5c467f 1.7.0 2018-01-05 21:28:58 +01:00
Danielv123 0f246ca9d3 #59 added error handling when not having put token in config.json 2018-01-05 21:26:22 +01:00
Danielv123 52cc98cc95 #61 fixed remoteMap deletions 2018-01-05 21:25:26 +01:00
Danielv123 a78a80f395 Prevented race condition in test from affecting result 2018-01-04 19:58:42 +01:00
Danielv123 8029067cb2 #61 un-inverted map movement keys 2018-01-04 19:46:10 +01:00
Danielv123 618e62de6e Fixed client typo causing crash and tests failing on CI 2018-01-04 19:28:03 +01:00
Danielv123 3f8a685f19 resolves #66 added setting to not upload mods to master 2018-01-04 18:54:30 +01:00
Danielv123 5dc71ad46e Setting to reduce verbosity of logs (don't log item transfers) 2018-01-04 18:50:09 +01:00
Danielv123 df60455d5e #61 improved master performance and socket cleanup 2018-01-04 18:42:26 +01:00
Danielv123 b9912f09f9 #61 Link to remoteMap on /slaves and /nodeDetails if LUAmod is detected 2018-01-04 18:22:37 +01:00
Danielv123 84cded0cf9 #61 dynamically load more map when moving around 2018-01-04 15:08:45 +01:00
Danielv123 0b00f065f7 #61 Pan map with WASD 2018-01-04 14:43:25 +01:00
Danielv123 0c365c0090 #61 Render entities with rotations
This code didn't even error once, it just worked. Which is nice.
2018-01-04 14:43:01 +01:00
Danielv123 51bb9f4d71 Live updates to map as things are placed or removed 2018-01-03 18:34:27 +01:00
Danielv123 9ae20886b3 Entity based mapping has gotten to the renderer 2018-01-03 14:17:33 +01:00
Danielv123 1b405b8701 Refactored chunkStore to use linvodb3, broke mapping (ofc) 2018-01-02 17:00:50 +01:00
Danielv123 cd2d00d224 Merge branch 'master' into dev 2017-12-28 11:58:37 +01:00
Danielv123 0feaab8314 Cleaned up readme, added TOC, introduction and command cheatsheet 2017-12-28 11:58:14 +01:00
Danielv123 be5dfdf38b Merge branch 'master' into dev 2017-12-28 11:46:04 +01:00
Danielv123 853b7d1026 Attempted to fix remoteMap race condition, not working yet 2017-12-16 14:26:20 +01:00
Danielv123 f9db49734c Cleaner test output, better promise error logging 2017-12-16 14:25:38 +01:00
Danielv123 2c38c37c25 Fixed tests failing without good reason 2017-12-14 10:58:51 +01:00
Danielv123 0df3c0c14f Better error handling in remoteMap 2017-12-12 21:43:03 +01:00
Danielv123 51c644203e #61 Basic canvas rendering is now working 2017-12-11 17:44:11 +01:00
Danielv123 eba982836c #61 Remote map chunk storage and equesting working, waiting for renderer 2017-12-10 20:55:53 +01:00
Danielv123 8fce9a2cfe #61 chunkStore is getting filled/emptied on demand 2017-12-10 14:07:37 +01:00
Danielv123 b1f35c9b72 Fixed UPSdisplay 2017-12-10 11:38:56 +01:00
Danielv123 44b68c92d5 Big changes to plugin system, fixed inventoryImports 2017-12-10 11:27:02 +01:00
Danielv123 36ccf22e02 More informative error when master is unavailable 2017-12-09 14:49:11 +01:00
Danielv123 7682a51437 More informative error when launching instance without savefile 2017-12-09 14:25:56 +01:00
Daniel 1d790388c2 Updated install command 2017-12-09 14:21:37 +01:00
Danielv123 f522f19cb5 fixed #65 2017-12-09 14:05:17 +01:00
Danielv123 9496fa70e0 #61 without ES6 features to support node 6.10 2017-12-03 18:51:31 +01:00
Danielv123 132c7999f3 Merge remote-tracking branch 'origin/master' into dev 2017-12-03 18:37:28 +01:00
Daniel e3e255bbb0 #61 might be fixed now 2017-12-03 18:30:40 +01:00
Danielv123 566d61755e Fixed inventoryCombinator attempting to output 2360791303, resolves #62
Because it was above 2^31
2017-11-29 09:07:21 +01:00
Danielv123 ffe2df55c4 #61 added webpage to master, some more plugin work 2017-11-29 09:02:11 +01:00
Danielv123 227ac87018 housekeeping 2017-11-29 09:01:27 +01:00
Danielv123 cdddfe6e30 #61 Completed chunkStore for now, time to work on filling it 2017-11-22 18:03:20 +01:00
Danielv123 e2a795528f Change name from slaveID to instanceID for consistency accross endpoints 2017-11-21 21:39:41 +01:00
Danielv123 7ece1af073 #61 start of map store/retrieve, communication channel using websockets 2017-11-21 11:06:57 +01:00
Danielv123 2bcacb2513 #61 extracting place data from game
Still need to extract remove data in the LUA portion and store it
2017-11-20 10:29:52 +01:00
Danielv123 dc0a722e3d More docs 2017-11-19 13:02:24 +01:00
Danielv123 6ecdde8cbc resolves #60 sharedMods doesn't affect instance mods
Might have broken backwards compat???
2017-11-15 12:55:49 +01:00
Danielv123 682856c0a5 Implemented editing configs using node client.js manager #59 2017-11-14 13:06:57 +01:00
Danielv123 1dd58c27e0 undotted 2017-11-14 12:54:22 +01:00
Danielv123 e007ffd30a . 2017-11-13 21:57:18 +01:00
Danielv123 8146a313f3 #59 interface for editing config + more docs 2017-11-11 23:51:52 +01:00
Danielv123 906b192683 More work on command line tools #59 2017-11-11 16:40:19 +01:00
Danielv123 bd1304365c 1.6.0 2017-11-10 16:51:30 +01:00
Danielv123 eca8723506 Sorted documentation better, "npm run livedocs" to browse the docs 2017-11-09 17:09:59 +01:00
Danielv123 9662df356a Fix crash due to leftover code
F
2017-11-08 22:30:47 +01:00
Danielv123 7d7c2515f4 minor locale fix 2017-11-08 22:26:28 +01:00
Danielv123 490f039d83 Yes, its more JSDOCs 2017-11-08 16:47:32 +01:00
Danielv123 02dc688161 Documented more things with JSDOC 2017-11-07 16:29:43 +01:00
Danielv123 96d60e5428 Attempted to add some JSDOC docs, npm run docs/livedocs 2017-11-06 23:37:21 +01:00
Danielv123 2fd47434bb Added search functionality to mod manager #59 2017-11-06 21:12:25 +01:00
Danielv123 88ff90262d Updated default config 2017-11-06 21:09:19 +01:00
Danielv123 a3195ad8ce More details in mod list 2017-11-06 20:00:19 +01:00
Danielv123 8d63bb896a Big charting performance increase for master 2017-11-06 19:53:12 +01:00
Danielv123 b016f36667 Fixed graph on /nodeDetails 2017-11-05 21:18:59 +01:00
Danielv123 7046ba58d3 Import/export line on same graph on storage page to save space 2017-11-05 21:11:12 +01:00
Danielv123 0735aa2db4 More work on listing mods nicely with node client.js manager 2017-11-05 17:50:14 +01:00
Danielv123 c901275f38 'node client.js manage shared list' now lists shared mods 2017-11-04 20:07:25 +01:00
Danielv123 f507b503e9 Updated packages 2017-11-03 23:05:19 +01:00
Danielv123 8cf85f3ecc More mod manager stuff, node client.js list is more informative
Now shows nice table with name and port to connect to
2017-11-03 22:54:52 +01:00
Danielv123 1eeb289669 Coverage % now includes new untested modManager 2017-11-02 21:29:21 +01:00
Danielv123 c3b1e66646 #59 Starting work on the mod manager 2017-11-01 21:48:55 +01:00
Danielv123 b847b1a934 1.5.0 2017-11-01 20:06:25 +01:00
Danielv123 be7fdb692f Cleaned up logs on slave to be more informative 2017-11-01 18:59:08 +01:00
Danielv123 e75931c4f3 Improved place stats identification 2017-11-01 18:53:39 +01:00
Danielv123 01b317006c Made some slow tests run in parallel 2017-11-01 16:56:57 +01:00
Danielv123 2319ce01a2 Fixed UPSdisplay 2017-11-01 16:56:37 +01:00
Danielv123 cfbf281b42 #58 added even more tests for /api/remove 2017-10-31 20:48:06 +01:00
Danielv123 7185bd4fe1 More consistent return values for /api/remove 2017-10-31 20:47:42 +01:00
Danielv123 ea603c61fd #58 added test for /api/remove 2017-10-31 20:04:59 +01:00
Danielv123 0dd6315abc Fixed export statistics being logged under "unknown" 2017-10-29 23:37:46 +01:00
Danielv123 bfbe7e7d1c Added tests for /api/inventory and /api/place 2017-10-29 23:09:37 +01:00
Danielv123 42e2816b56 Better title for storage graphs 2017-10-29 12:29:16 +01:00
Danielv123 fe704b46e9 Itemstats on storage page respects offlineSlaves setting 2017-10-29 12:22:50 +01:00
Danielv123 a7474fdb86 Give processes a better name, clusterioMaster and clusterioClient 2017-10-29 09:37:13 +01:00
Danielv123 869b5f9ced sentItemStats for master is now working as well 2017-10-28 22:13:40 +02:00
Danielv123 3d4c788788 recievedItemStats for master is working 2017-10-27 09:23:18 +02:00
Danielv123 04aba1eef1 Added debug tools for adding/removing items 2017-10-26 21:17:59 +02:00
Danielv123 f886fe3e95 Updated averaged-timeseries module 2017-10-26 21:07:30 +02:00
Danielv123 1510c86944 1.4.0 2017-10-26 09:01:06 +02:00
Danielv123 15f3e88408 Merge remote-tracking branch 'origin/master' into storageIOstats 2017-10-25 11:25:05 +02:00
Danielv123 a1db24bd91 Added the first tests for master.js 2017-10-25 11:24:57 +02:00
Danielv123 0e1a7cdb4a Made index confirm to HTML5 spec 2017-10-25 11:24:35 +02:00
Daniel 598c2961a8 Updated badges 2017-10-24 22:00:13 +02:00
Danielv123 e91c25557e Data collection related to storage IO and graphing 2017-10-22 15:03:29 +02:00
Danielv123 994bd09fa7 Increased statistics performance by lowering rate, untested :'( 2017-10-22 15:00:48 +02:00
Danielv123 d858f1da69 Final fix for crash on closing slave related to getMac 2017-09-19 19:51:00 +02:00
Danielv123 c7744b5189 Gather more information about who adds stuff to the cluster 2017-09-19 19:48:50 +02:00
Danielv123 339f38c1b0 Moved string hashing function away from main files 2017-09-19 08:35:55 +02:00
Danielv123 27665b8e93 More travis stuff 2017-09-18 16:15:22 +02:00
Danielv123 43f1e89e89 Tell travis to download factorio 2017-09-18 14:41:44 +02:00
Danielv123 bb1eef93c2 Changed test commands 2017-09-18 14:37:10 +02:00
Danielv123 1538235e0f Added travis-ci builds maybe? 2017-09-18 14:36:52 +02:00
Danielv123 1b0da523ca Added coverage badge 2017-09-18 09:28:45 +02:00
Danielv123 f9a0703725 Fixed inventory combinator 2017-09-17 16:45:06 +02:00
Danielv123 201912fdea Use linvodb3 for storing flows to save memory 2017-09-17 08:47:57 +02:00
Danielv123 43656f931e Added fluid barrel graphics to inventory display 2017-09-17 07:52:18 +02:00
Danielv123 b06c61b5a9 . 2017-09-17 07:44:01 +02:00
Danielv123 d6ce94176b Someone changed the name of the power armor mk2 to MK2
These wiki guys, can't they ever decide?
2017-09-17 07:42:23 +02:00
Danielv123 6a251be318 Attempted to fix imports 2017-09-16 23:51:24 +02:00
Danielv123 10e585b307 New item database working 2017-09-16 16:47:33 +02:00
Danielv123 8742e34d9f Tried to fix occasional crash when saving related to getMac 2017-09-16 16:47:16 +02:00
Danielv123 08143b7b84 Fixed database corruption problems 2017-09-16 15:55:28 +02:00
Danielv123 2a07591173 Stop throwing errors 2017-09-16 15:55:14 +02:00
Danielv123 39462e925c Changed masters item storage engine 2017-09-16 10:57:11 +02:00
Daniel 2aa3900ceb Update README.md 2017-09-12 21:51:35 +02:00
Danielv123 dcd46acdf2 resolves #57, issue was related to the way DDRs dole factor works.
I hope it works at least.
2017-09-12 09:13:52 +02:00
Daniel a881f4713d Merge pull request #56 from DDR0/dole-factor
Another branch, built on top of my dev branch, which contains a scarce resource sharing implementation.
2017-08-30 11:18:58 +02:00
Daniel c52fd3f966 Merge pull request #55 from DDR0/dev
ui improvements for the web interface storage tab
2017-08-30 11:12:01 +02:00
David Roberts ce6ab38a72 Initial stab at making the master server dole out resources more fairly when running a deficit. 2017-08-29 23:35:50 -07:00
David Roberts 49887012f7 Tweaked slaveDisplay.js to use 92% less data to draw a chart. This is projected to speed up chart loading significantly, from 3.4s to about 0.5s.
- Added copper-cable to the list of hidables on the chart, and removed copper-wire which isn't a thing.
- Don't display items as hidable that aren't being produced. It's confusing.
- Added /getTimelineStats, because /getStats is used by another view that I don't feel like converting to not require the 0-production data points.
2017-08-29 16:25:33 -07:00
David Roberts 000b2fc147 Added search functionality back in. It's much snappier now.
- Is now case-insensitive.
- And regex.
2017-08-29 13:57:13 -07:00
David Roberts bdf4225dec Clean up web interface storage tab.
- Use diffing algorithm so you can select text and images.
	- This uses textcontent, plugs a vulnerability where you could inject arbitrary javascript if you could upload a modded item with html in the name, eg, "iron-plate<img src=\"\" onerror=\"console.log('hi')\">".
- Fall back to transparent png if item image not found.
	- This stops the flashing box when the image does not exist.
- (internal) Simplify `sortByKey(array, key)`.
2017-08-29 13:25:15 -07:00
Danielv123 d01914f7b2 BUGFIX: unknown symbol near "/"
2017-08-21 11:00:57 Cannot execute command. Error: [string "local a='{"players":{'local b=false;for c,d i..."]:2: unexpected symbol near '/' kept appearing in chat, lagging the server and occasionally causing dropouts. I believe this was magically fixed
2017-08-21 11:16:22 +02:00
Danielv123 512b3c66a5 Added fake terminal to /nodeDetails
It doesn't do anything yet, but it should allow sending commands to servers and watching their output.
2017-07-13 11:59:38 +02:00
Danielv123 e3b8926e35 Housekeeping 2017-07-11 19:48:00 +02:00
Danielv123 2fc7de3983 DDMMYY is better than MMDDYY 2017-07-11 19:47:06 +02:00
Danielv123 6334ceb252 Fix last seen date on /slaves 2017-07-11 18:16:11 +02:00
Danielv123 a350b39e2f . 2017-07-11 13:15:56 +02:00
Danielv123 cb1071fc33 fix race condition 2017-07-11 13:14:13 +02:00
Danielv123 53af3849cc Fix ddosing wiki.factorio.com with 404 picture requests 2017-07-11 13:10:58 +02:00
Danielv123 8acbd707f9 GET /api/getFactorioLocale now returns entire base en locale 2017-07-11 12:02:47 +02:00
Danielv123 5d294c3a62 Change extension fron .ejs to .html 2017-07-10 14:57:48 +02:00
Danielv123 39dcb43199 Don't open rcon port on linux because its not needed 2017-07-10 14:33:07 +02:00
Danielv123 12fd4f28a2 Updated readme to use correct tar flags 2017-07-10 13:41:03 +02:00
Danielv123 d394426958 critical bugfix 2017-07-10 13:33:18 +02:00
Danielv123 dd9e8d5029 No comment. 2017-07-09 17:18:54 +02:00
Danielv123 7ce788e146 Typo. This is unproffessional. 2017-07-09 17:16:27 +02:00
Danielv123 cddbc547f8 Fixed. Again. 2017-07-09 17:15:14 +02:00
Danielv123 ba389859ba Merge branch 'master' into dev 2017-07-09 17:12:10 +02:00
Danielv123 492d678f94 Fixed icons after wiki links changed 2017-07-09 17:03:41 +02:00
Danielv123 4047f66144 Merge branch 'dev' 2017-07-09 17:02:12 +02:00
Danielv123 14bec5d225 Fixed icons after wiki links changed 2017-07-09 17:02:07 +02:00
Danielv123 f3bb8c58e5 Merge branch 'dev' 2017-06-20 20:10:51 +02:00
Danielv123 5de0b6319c Error message when started without instanceName
And some test coverage
2017-06-20 20:05:54 +02:00
Danielv123 e6da33582c Cleanup, more coverage 2017-06-17 17:40:46 +02:00
Danielv123 d2f122936e Tests for handleInventory(), 93% avg coverage 2017-06-17 17:24:24 +02:00
Danielv123 2a1badf7cb Lots of tests 2017-06-15 17:14:29 +02:00
Danielv123 8ee3109fbc NPM5.0.1, housekeeping 2017-06-15 17:14:01 +02:00
Danielv123 fdf8a4b4b1 Housekeeping 2017-06-14 21:52:38 +02:00
Danielv123 c4ead8c699 Working inventory imports resolves #50 2017-06-14 21:46:30 +02:00
Danielv123 f9a2e8519b Hide steam from production graph 2017-06-14 15:08:34 +02:00
Danielv123 c24cb51f13 . 2017-06-09 06:58:06 +02:00
Danielv123 0ada84e153 #50 nearing completion, needs taping together and testing
Includes great unit test coverage (npm run cover reports ~85%)
2017-06-07 20:40:03 +02:00
Danielv123 e5b6a94524 less clutter on npm run cover 2017-06-07 20:33:58 +02:00
Danielv123 00dd22e59f Slight improvements to existing tests 2017-06-07 20:33:20 +02:00
Danielv123 913887fd2c Removed \n from UPSdisplay 2017-06-07 07:26:14 +02:00
Danielv123 dbf209de61 Tests, 90% coverage 2017-06-06 11:12:05 +02:00
Danielv123 a7fcde60b8 Reporting for multiple players 2017-06-06 10:45:44 +02:00
Danielv123 6466e12504 working on multi player reports 2017-06-05 12:52:40 +02:00
Danielv123 53cd6c846a inventoryImport now reads inventory successfully 2017-06-03 15:07:26 +02:00
Danielv123 a09cdfb1de possibly fixed rare crash on connect in UPSdisplay 2017-06-03 15:06:27 +02:00
Danielv123 ee133761f7 fixed slaves not registering on windows 2017-06-03 11:48:07 +02:00
Danielv123 18a40ca806 More lua experimenting 2017-06-02 22:29:28 +02:00
Danielv123 103f01b6a2 inventory reports, work on #50 2017-06-01 13:25:59 +02:00
Danielv123 0930000246 Merge branch 'dev' 2017-05-31 18:04:25 +02:00
Danielv123 a0facf9ea8 Hover text for online indicator 2017-05-30 09:30:05 +02:00
Danielv123 18b8b16f02 decreased logging on master 2017-05-29 12:39:35 +02:00
Danielv123 4155ea7916 Save slaves between master reboots 2017-05-29 11:23:53 +02:00
Danielv123 21d87a8cd1 Housekeeping 2017-05-26 21:17:24 +02:00
Danielv123 844bfebcd7 UPS display plugin that shows on master 2017-05-26 19:50:33 +02:00
Danielv123 960cd79be3 fix /api/editSlaveMeta creating new slaves when it shouldn't 2017-05-26 15:25:49 +02:00
Danielv123 bafc8dfc44 /inventory/ now has a search field 2017-05-26 09:25:07 +02:00
Danielv123 b520d9611b Red/green/yellow dot to tell slave status on /nodeDetails/ 2017-05-26 09:06:04 +02:00
Danielv123 74348be0a8 slaveDetails prod chart, playerCount, last online date 2017-05-24 22:34:03 +02:00
Danielv123 70ecdf010c Renamed lib because thats reasonable 2017-05-24 22:33:15 +02:00
Danielv123 f8749fb8d4 Made tests linux compatible 2017-05-23 15:45:34 +02:00
Danielv123 4dee4ed84e Extended gitignore, removed database 2017-05-23 15:35:17 +02:00
Danielv123 4969c6041b Fixed style that broke on other pages 2017-05-23 15:28:07 +02:00
Danielv123 7536469649 Link to nodeDetails from /slaves/ 2017-05-23 14:22:01 +02:00
Danielv123 5c13e3107d /nodeDetails?slaveID=xxx page for detailed info about a single slave
and massive UI overhaul which looks like shit.
2017-05-23 11:56:24 +02:00
Danielv123 ecc5b1c6ce tests for fileOps.deleteFolder[...], istanbul for code coverage 2017-05-22 13:28:00 +02:00
Danielv123 3d4fbfb3fa tests for fileOps.getNewestFile() 2017-05-22 10:59:39 +02:00
Danielv123 2695f0a443 Suppressed NPM errors 2017-05-22 10:59:16 +02:00
Danielv123 4f77c1b473 housekeeping 2017-05-21 22:20:20 +02:00
Danielv123 5f3a160b75 Moved file functions to internal lib with some tests as well 2017-05-21 22:00:30 +02:00
Danielv123 d9e897836b test cleanup 2017-05-20 20:30:25 +02:00
Danielv123 6e95c86fb1 moved libs to ./lib/ 2017-05-20 20:17:02 +02:00
Danielv123 cd44b3572b Added tests using mocha 2017-05-20 19:40:47 +02:00
Danielv123 336eb07cab /api/editSlaveMeta allows you to attach arbitrary metadata to slaves
You will need its unique ID and rcon password though.
2017-05-19 11:05:36 +02:00
Danielv123 ba626295a5 Only pipe plugin stdout if it starts with /, else log to console 2017-05-19 09:09:33 +02:00
Danielv123 930d3352cc Fixed problem 2017-05-19 07:24:09 +02:00
Danielv123 924edc7af7 Allow starting plugins with arguments 2017-05-19 07:22:36 +02:00
Danielv123 efbc4fbb87 Semicolons for consistency 2017-05-18 21:16:11 +02:00
Danielv123 85d7246bca Add semicolons because they should be there 2017-05-18 21:13:13 +02:00
Danielv123 625f0eec3b Merge branch 'dev' 2017-04-24 19:50:14 +02:00
Danielv123 4a2e62386f Fixed missing callback :S 2017-04-23 10:30:09 +02:00
Danielv123 5d51e9e80a stdIn mode for linuxes to solve problem in rcon lib resolves #42 2017-04-22 21:47:11 +02:00
Danielv123 31e4bab159 Less logs 2017-04-21 10:21:47 +02:00
Danielv123 78334fc7e0 Less logs 2017-04-21 10:08:02 +02:00
Danielv123 538c4f575b ; not : 2017-04-21 10:05:14 +02:00
Danielv123 1c91727c8a docker 2017-04-21 10:02:39 +02:00
Danielv123 6449fef1e1 Merge branch 'master' into dev 2017-04-21 09:22:48 +02:00
Danielv123 4db55a8857 Add reconnect when factorio complains about payload size
Sometimes the rcon lib messes up and delivers a 4x package that makes factorio stop responding to rcon and throw an error. We catch that error and attempt to reconnect.

Couldn't parse RCON data: Maximum payload size exceeded
2017-04-21 09:20:22 +02:00
Danielv123 9971b9f5b3 Add more logging to isolate case where slave stops reporting to master 2017-04-13 13:45:49 +02:00
Danielv123 8e0abf5e2d or is || not | 2017-04-08 16:52:33 +02:00
Danielv123 1719ff9b07 . 2017-04-08 16:37:38 +02:00
Danielv123 f208540856 Clone dev branch instead of master
This caused me a lot of headaches. Sigh.
2017-04-08 16:13:59 +02:00
Danielv123 c9d56b8940 Debug 2017-04-08 10:32:21 +02:00
Danielv123 8613c1deef Fixed dockerfile 2017-04-07 10:35:24 +02:00
Danielv123 ae72814527 More docker trying to fix environent variables 2017-04-07 10:17:46 +02:00
Danielv123 af345fb2ae More docker 2017-04-07 08:40:46 +02:00
Danielv123 80251eacae Log factorio stdout if on linux 2017-04-06 21:13:06 +02:00
Danielv123 2e4eb9332d Report instance name when asking for items 2017-04-06 20:57:15 +02:00
Danielv123 432c4e462d Setting to disable automatic storage screen updates 2017-04-06 20:56:42 +02:00
Daniel e18bc13abe Add more pictures to storage page 2017-04-04 09:35:10 +02:00
Danielv123 41a6e88006 Show how many are online 2017-04-03 13:31:09 +02:00
Danielv123 8979fd28b3 Merge branch 'dev' 2017-04-02 15:59:21 +02:00
Danielv123 e8d8217dc5 Show friendly instance name instead of ID 2017-04-02 15:59:10 +02:00
Danielv123 a21df015a8 Merge branch 'dev' 2017-04-02 15:17:27 +02:00
Danielv123 2278176608 Suppressed error 2017-04-02 15:17:17 +02:00
Danielv123 0e10e2ebfa . 2017-04-02 15:16:54 +02:00
Danielv123 06e431c01a Fixed markdown headers in readme 2017-03-31 22:36:24 +02:00
Danielv123 ff9fac842e Added docker for running master server 2017-03-31 22:34:39 +02:00
Danielv123 e90bbb745a Merge branch 'dev' 2017-03-31 16:25:47 +02:00
Danielv123 f5516891e3 Fixed missing sync-request 2017-03-31 16:25:25 +02:00
Danielv123 93b163f3a4 adding lots of let instead of globals to see what happens with the leaks
#48 is the one
2017-03-30 21:50:24 +02:00
Danielv123 9fe4ebdacd Stop overloading the legend by using a whitelist 2017-03-17 16:00:03 +01:00
Danielv123 3186fa8fb7 Button to show all datasets
Also #46
2017-03-17 10:55:34 +01:00
Danielv123 8967189fd2 Setting to disable production graph for performance
Related to #45
2017-03-17 09:24:53 +01:00
Danielv123 3f699e7ab5 Button to hide all datasets
resolves #46
2017-03-17 09:20:09 +01:00
Danielv123 dc2c04741a Fixed inventory using dead link to API 2017-03-16 17:56:10 +01:00
Danielv123 bd5956c9ed Add mod icons by link 2017-03-16 17:55:15 +01:00
Danielv123 e156864ccd Setting to display offline slaves 2017-03-16 13:32:08 +01:00
Danielv123 1e287502f8 Fix messy graph bug due to unsorted x axis 2017-03-16 13:31:49 +01:00
Danielv123 e801be3c77 Added fancy settings menu 2017-03-16 13:11:19 +01:00
Danielv123 fedf9f6df1 Show legends and toggle display of datasets 2017-03-16 09:25:28 +01:00
Danielv123 5abeb8c9a6 Fixed bug with negative production 2017-03-16 09:24:39 +01:00
Danielv123 a243181856 Moved API endpoints to /api/ 2017-03-16 09:23:59 +01:00
Danielv123 7b71185bb8 Avoid extreme negatives in stat reporting 2017-03-13 18:46:43 +01:00
Danielv123 5cf91e7a3d Production logging from 5 to 60 seconds 2017-03-13 18:21:51 +01:00
Danielv123 480a176fc3 Zoomable graphs 2017-03-12 19:14:33 +01:00
Danielv123 cd49d2582f Show disconnected slaves for up to 2 minutes 2017-03-12 17:04:04 +01:00
Danielv123 71753c29a5 Production graph display on website
resolves #35
2017-03-12 17:01:05 +01:00
Danielv123 14b9ca0068 Merge remote-tracking branch 'origin/master' into dev 2017-03-12 09:33:20 +01:00
Danielv123 1fc2de231b Show material type in graph 2017-03-10 21:14:20 +01:00
Danielv123 1f06d3eb48 Display of all graphs working
Also added proper clock to the X axis so its readable
#35
2017-03-10 21:08:04 +01:00
Danielv123 11e3bf7879 Working chart for single item #35 2017-03-10 20:36:21 +01:00
Danielv123 548436cbde Non working canvasjs chart for #35 2017-03-10 11:15:46 +01:00
Danielv123 dcc17e2fa8 Master has POST endpoint for flows data
#35 still not sure how to do the graphs on clientside though
2017-03-10 09:27:36 +01:00
Daniel 153aba3e0f Update README.md 2017-03-08 12:54:34 +01:00
Danielv123 dc103d1bed Fixed inventory
Yeah, I broke that in last commit. So it did change *something*
2017-03-08 12:46:58 +01:00
Danielv123 9edf4dc18e Overhauled entire website with 0 change in looks
So I can finally work on implementing #35 but it feels like I did nothing :(
2017-03-08 11:32:01 +01:00
Danielv123 cc6ff973b5 Production graph DB for master
#35
2017-03-07 11:19:40 +01:00
Danielv123 03bb6a8063 Client.js now gathers production graph information 2017-03-06 20:37:02 +01:00
Danielv123 adff48d612 Merged dev into master 2017-03-02 15:01:06 +01:00
Danielv123 1f5c2a1977 Refix because last fix was moot 2017-03-02 14:19:27 +01:00
Danielv123 d4968e0894 Fix getting trolled by partial autosaves (.tmp.zip) 2017-03-02 13:47:02 +01:00
Danielv123 36bfd6450b Autosave loading fixed again 2017-03-02 13:31:51 +01:00
Danielv123 e21332591f Load autosaves but bug with factorio-current.log
resoves #44
2017-03-02 12:37:34 +01:00
Danielv123 3a04019241 Reduce logs 2017-03-02 11:50:18 +01:00
Danielv123 e6b3d55e70 Merged dev into master 2017-03-02 00:18:17 +01:00
Danielv123 77d7def6ef Fixed instance creation, server reports playercount 2017-03-02 00:18:04 +01:00
Danielv123 88238ead27 Merged branch master into dev 2017-02-28 16:31:02 +01:00
Danielv123 dd0213b4d1 Removed plugins from main repo 2017-02-28 16:29:41 +01:00
Daniel b7b8d7d254 Added server browser password for fox 2017-02-27 23:06:44 +01:00
Daniel 0264037731 Update README.md 2017-02-27 21:29:00 +01:00
Danielv123 dda2538578 Plugins can now listen to files, resolves #41 2017-02-27 08:42:08 +01:00
Danielv123 ccf54bec18 Removed junk, added comments 2017-02-26 18:31:58 +01:00
Danielv123 d73bfbc1eb #41 Basic plugins can now send commands to game 2017-02-26 16:48:58 +01:00
Danielv123 27148053ae Basic plugin implementation 2017-01-29 23:34:56 +01:00
Danielv123 b37e158774 Merged dev into master 2017-01-28 17:43:23 +01:00
Danielv123 f435a792de Avoid crashing on every 2nd launch due to #40 fix 2017-01-28 17:43:02 +01:00
Danielv123 4f1cfce39c Fix crash on slow startup, resolves #40 2017-01-28 17:24:30 +01:00
Danielv123 f1284264fe Merged dev into master 2017-01-16 23:24:38 +01:00
Danielv123 e610d23525 asdasdas readme 2017-01-16 23:23:05 +01:00
Danielv123 1655b4b689 Windows readme updated to use bat installer 2017-01-16 23:17:48 +01:00
Danielv123 bc2cfb1cb1 Fixed missing dependency 2017-01-16 23:13:37 +01:00
Danielv123 8a29276f58 Merged branch master into dev 2017-01-16 22:16:09 +01:00
Daniel 0288e9b2c2 Update README.md 2017-01-16 21:22:29 +01:00
Danielv123 22a1372e32 Better mod loading, service auth 2017-01-16 21:21:13 +01:00
Daniel a6ec00bb18 Added notice about not working on old ubuntus 2017-01-16 20:38:21 +01:00
Danielv123 a718dbeb53 Merged dev into master 2017-01-10 11:11:45 +01:00
Danielv123 91438c32d9 Client can download mods from master 2017-01-08 22:18:04 +01:00
Danielv123 fd577054c5 Instances uploads their mods to master 2017-01-08 21:00:24 +01:00
Danielv123 e961ca6d56 Fixed mod hashing to avoid dupes 2017-01-07 23:37:10 +01:00
Danielv123 a49f98b228 Stop sending rcon passwords because security and stuff 2017-01-05 13:19:45 +01:00
Danielv123 0f7916229a Function to get a hash from an instances mods 2017-01-05 13:19:21 +01:00
Danielv123 e16d7b43ed Gitignore and require moved to top 2017-01-04 10:19:17 +01:00
Danielv123 12d1631922 Fixed downloading of mods with https 2017-01-03 20:21:15 +01:00
Danielv123 f589273c32 Mod seperated from slave, node client.js download 2017-01-03 17:44:35 +01:00
Danielv123 c33ca4abec Client.js now transmits IP needed to connect to slaves 2016-12-27 11:15:53 +01:00
Danielv123 26a9c4f1ef Fixed virtual signals 2016-12-27 11:14:53 +01:00
Danielv123 a10fa69def Inventory combinator can now transmit all virtuals 2016-12-27 10:47:02 +01:00
Danielv123 21cbc098f7 Merged dev into master 2016-12-26 23:26:12 +01:00
Danielv123 a70342fd82 Added unixtime signal to inventory combinator 2016-12-26 23:25:59 +01:00
Daniel 7c1caebccc Finalized readme 2016-12-22 17:01:47 +01:00
Danielv123 b3ee475a6c Missing , 2016-12-22 16:57:41 +01:00
Danielv123 c9d5803e26 Update to support 0.14.10+ 2016-12-22 16:53:12 +01:00
Danielv123 ceff284ba5 Changed slashes to fix linux support 2016-12-22 16:40:49 +01:00
Danielv123 de1a51610d Merged dev into master 2016-12-22 16:23:59 +01:00
Daniel ace440dbef Removed .exe to allow launching on linuxes 2016-12-22 16:21:14 +01:00
Daniel af92716c77 Ubuntu install instructions added 2016-12-22 16:09:20 +01:00
Danielv123 aac101bad7 prettify 2016-12-21 14:40:43 +01:00
Danielv123 cb77717650 Starting instances now require "start" as second param, instance 3rd 2016-12-21 14:33:34 +01:00
Danielv123 ba05b14e6f Maintainability, avoid bugs when not exiting after special commands 2016-12-21 14:16:29 +01:00
Danielv123 a896b032aa Added list and delete commands to client.js for managing instances
client.js list
client.js delete [instance]
also adde --help to show usage
2016-12-21 14:08:21 +01:00
Danielv123 964bc76b53 Merged branch master into dev 2016-12-21 11:08:32 +01:00
Danielv123 1c5bb9e8ab Consistent styling 2016-12-19 13:23:53 +01:00
Danielv123 187c05640f Argument 2016-12-19 13:21:36 +01:00
Daniel de95872773 Licensed as MIT, resolves #39 2016-12-02 23:16:22 +01:00
Danielv123 eb786ff146 Slave display now only shows connected slaves, autoupdates, trangartime
Added hidden option to show dates in trangartime y-ymd-ymd-y instead of ISO 6801

Ctrl+shift+i
g.trangarTime = true
2016-11-07 13:33:15 +01:00
Danielv123 37fdbb68d1 Remove currently non functioning graph from nav, start on storage page 2016-11-07 11:14:25 +01:00
Danielv123 afb00ab3fb Production chart partially added and hidden 2016-11-07 09:26:58 +01:00
Danielv123 b66df4d233 Merged branch master into dev 2016-10-28 16:19:16 +02:00
Daniel 1e6a46d8b3 Update README.md 2016-10-28 16:15:43 +02:00
Danielv123 dd0a216cb5 Merged dev into master 2016-10-28 16:01:14 +02:00
Danielv123 09d511d0c8 No longer displays all subpages by default, performance on tab inactive 2016-10-28 10:51:46 +02:00
Danielv123 85274220e7 Webpage now shows recently connected slaves, resolves #33 2016-10-28 09:51:42 +02:00
Danielv123 a90d78213a Basic serverside slaveawareness 2016-10-27 22:41:25 +02:00
Danielv123 f6de6e026b Decreased logging, automatic configuring of multiple instances
When creating a new instance, it will now generate a random port for the instance, RCON and an RCON password.

Current problems are that its not checking of the port is already taken by something else; if it is, the erver either won't start or RCON will fuck up.
2016-10-27 09:21:45 +02:00
Danielv123 64009a7e16 Valid JSON config file 2016-10-26 09:05:43 +02:00
Danielv123 656c05a30b Changed default config 2016-10-26 08:57:50 +02:00
Danielv123 2234d41655 Housekeeping, more config settings 2016-10-24 09:31:30 +02:00
Danielv123 234e6eba4c Merged branch instances into dev 2016-10-24 08:59:47 +02:00
Thomas Deal 0927bec070 client.js now launches it's own server instance 2016-10-23 17:38:57 -04:00
Danielv123 8fd13a7d04 Merged dev into master 2016-10-21 16:58:48 +02:00
Danielv123 12ca57af61 More icon exceptions 2016-10-21 16:00:40 +02:00
Danielv123 82f52c6e21 Item list sort after amount 2016-10-21 15:39:39 +02:00
Danielv123 8562bccd5f Betterer icons 2016-10-21 11:14:04 +02:00
Danielv123 3ea2530dab Moar icons :D 2016-10-21 11:04:48 +02:00
Danielv123 59ddb289a8 Removed jumping related to icons 2016-10-21 09:22:35 +02:00
Danielv123 6b6ffe5904 #8 Working on icons in masterGUI 2016-10-20 22:47:36 +02:00
Danielv123 3d38bba455 Merged origin/master into master 2016-10-20 22:45:59 +02:00
Danielv123 0133e36f8a Merged dev into master 2016-10-20 22:45:47 +02:00
Danielv123 b30d9fa190 Merged branch master into dev 2016-10-20 19:34:12 +02:00
Thomas Deal 854ed8fdd0 Report fluids correctly in Inventory Combinator 2016-10-20 13:31:33 -04:00
Daniel 528165367c Merge pull request #31 from Danielv123/dev
Resolves #30 adds inventory combinator
2016-10-20 13:27:07 +02:00
Danielv123 67fef2e543 Resolves #30 2016-10-20 13:25:52 +02:00
Danielv123 52b3e48eb8 Merged branch master into dev 2016-10-20 09:17:11 +02:00
Danielv123 cf93377625 Merged branch master into dev 2016-10-20 09:17:01 +02:00
Thomas Deal 67e5916790 Remove Lost bracket 2016-10-19 22:26:56 -04:00
Thomas Deal cc65d8c3c2 Locale updates 2016-10-19 21:45:24 -04:00
Thomas Deal bf2d16669d Support setting world ID, and if set, output it on the Inventory Combinator, and include it in transmitted frames. 2016-10-19 21:42:09 -04:00
Thomas Deal 16ec26984a Typo Fixes, Uncomment virtual signals. 2016-10-19 21:20:12 -04:00
Thomas Deal 67e192a78f More consistent handling of creation/destruction of entities. Uses entity.unit_number instead of HashPosition. Rebuilds all tables on initial install and on mod version bump. 2016-10-19 20:41:10 -04:00
Thomas Deal 5e65fa5208 Add Inventory Combinator which reports the last inventory reported by the external code. 2016-10-19 20:11:54 -04:00
Thomas Deal 09feb085ef Spelling fix 2016-10-19 16:54:54 -04:00
Thomas Deal 7b44e830f8 Write files only to the server by default, changeable via setFilePlayer() 2016-10-19 16:52:55 -04:00
Daniel 2013eb5449 Update README.md 2016-10-19 18:19:58 +02:00
Danielv123 f896e0dc74 Merged dev into master 2016-10-15 15:35:27 +02:00
Daniel 23e0aef34e Merge pull request #29 from TheAIBot/master
Fixed bug where you couldn't make lubricant and sul acid.

closes #28
2016-10-15 15:17:00 +02:00
TheAIBot 5abaed00aa fixed bug where you couldn't make lubricant and sul acid. 2016-10-15 08:43:45 +02:00
TheAIBot 3bea31cd45 Merge pull request #4 from Danielv123/dev
Dev
2016-10-14 16:43:14 +02:00
Danielv123 9d80769a12 MasterGUI basic production chart
Work on #8
2016-10-13 13:25:45 +02:00
Danielv123 6b35a3eb47 MasterGUI navigation added 2016-10-11 09:30:34 +02:00
TheAIBot 2c66b25162 Merge pull request #3 from Danielv123/master
update
2016-10-10 00:47:19 +02:00
TheAIBot 851fbe28e7 Merge remote-tracking branch 'refs/remotes/Danielv123/master' 2016-10-10 00:44:47 +02:00
Danielv123 4b3e5b0917 Merged dev into master 2016-10-09 22:28:53 +02:00
Danielv123 640a8d4d21 Removed argument to improve compat with older node versions
Resolves #25
2016-10-09 22:28:20 +02:00
Daniel e5314f6ace Merge pull request #24 from Danielv123/dev
Circuit network support, closes #22
2016-10-09 11:39:15 +02:00
Danielv123 511e31f24f Merge branch 'master'
Conflicts:
	README.md
2016-10-09 11:37:24 +02:00
Danielv123 8f426a1362 Updated readme 2016-10-09 11:02:50 +02:00
Danielv123 a2d970fad6 Use the many option for signals instead 2016-10-07 23:59:15 +02:00
Danielv123 87cfe1bc46 Sending signals over Clusterio now works
Closes #19
2016-10-07 20:29:35 +02:00
Danielv123 a3aafd8b1d JS part of signals kinda finished but it aint working 2016-10-07 17:54:23 +02:00
Danielv123 20c591bf71 Removed DB from repo 2016-10-06 17:49:16 +02:00
Danielv123 b123d485d2 Slave sending combinator signals finished
So working on #22.
2016-10-06 17:47:03 +02:00
Daniel dfa8b573df Closes #21 2016-10-03 21:07:58 +02:00
Daniel 3fde2be222 Solved #21 2016-10-03 21:06:31 +02:00
Danielv123 9011665a80 Merged branch master into dev 2016-10-02 11:41:45 +02:00
Daniel 134c8e4d92 Merge pull request #20 from Danielv123/dev
The end of all desync problems and chat spamming
2016-10-02 10:21:56 +02:00
Danielv123 9b14da470e Use silent-command to avoid spamming chat. Thanks HanziQ! 2016-10-02 10:20:05 +02:00
Thomas Deal f9d6d9e001 Add Transmit/Receive Combinators. Transmit combinators are Deciders which also output into txbuffer.txt. Receive Combinators are Constants which replay frames received from external sources via remote calls to receiveFrame and recieveMany. 2016-10-01 21:20:18 -04:00
Daniel 4f8ad5eec6 Merge pull request #18 from Danielv123/dev
MP desync issues fixed, prerealease 1
2016-10-01 23:35:36 +02:00
Danielv123 6d12bac32a Merged branch master into dev 2016-10-01 19:03:02 +02:00
TheAIBot 090910aeef Merge remote-tracking branch 'refs/remotes/Danielv123/master' 2016-10-01 02:46:06 +02:00
TheAIBot 7e67ae5068 fixed up the last things about the output tank 2016-10-01 02:44:45 +02:00
TheAIBot 9ca013590d fixed merge error 2016-10-01 02:27:08 +02:00
TheAIBot 72cef84df5 Merge remote-tracking branch 'refs/remotes/origin/json-parser'
# Conflicts:
#	clusterio_0.0.2/control.lua
2016-10-01 02:26:29 +02:00
TheAIBot d22b7e3de4 multiplayer fixed 2016-10-01 02:26:08 +02:00
Danielv123 5670bdb77a Working on adding charts to master GUI 2016-09-30 23:23:01 +02:00
Danielv123 379f8398c7 Merged branch dev into dev 2016-09-30 19:27:40 +02:00
Danielv123 e33109703d Minor minor 2016-09-30 19:27:10 +02:00
Danielv123 181e570972 Master webpage syntax 2016-09-30 19:16:46 +02:00
TheAIBot 9391b33cc9 Merge remote-tracking branch 'refs/remotes/Danielv123/master' 2016-09-30 18:24:44 +02:00
Danielv123 aa93af6d06 More cleanup 2016-09-29 09:16:01 +02:00
Danielv123 db47320951 Removed outdated script 2016-09-29 09:15:42 +02:00
Daniel d820c483e6 Merge pull request #15 from Danielv123/dev
Prerelease: Solved #10 and solved #14
2016-09-27 11:26:01 +02:00
Danielv123 da9d2c14dc Solved #14 and #10, preparing for prerelease 2016-09-27 11:24:55 +02:00
Danielv123 1e65eb40c5 Changed up the tranfer format, everything works! 2016-09-26 08:07:19 +02:00
Danielv123 06a4d49884 WIP, still not working 2016-09-09 08:25:39 +02:00
Danielv123 129ef8d2f8 Tried to cache rcon requests, ended up with dupe machine
Me needs help, I don't understand where the game is getting free items from.
2016-08-25 20:50:00 +02:00
Daniel e326ae093a Merge pull request #13 from TheAIBot/json-parser
Added Json parser for remote call ImportMany
2016-08-25 13:02:47 +02:00
TheAIBot f3c6fdaa3b added json parser for importMany 2016-08-23 22:51:54 +02:00
TheAIBot b3aa96cddf Merge remote-tracking branch 'refs/remotes/Danielv123/master' into json-parser 2016-08-23 22:49:27 +02:00
Daniel a12d818cdb Revert "Merge pull request #11 from TheAIBot/master"
This reverts commit 3252eefcfc.
2016-08-23 21:45:08 +02:00
Daniel 3252eefcfc Merge pull request #11 from TheAIBot/master
fix desync
2016-08-23 21:32:56 +02:00
TheAIBot 11ff85ca74 fixed desync 2016-08-23 21:12:02 +02:00
TheAIBot 62c07c77f2 initial tank chose fluid 2016-08-23 20:31:43 +02:00
TheAIBot c9d5509c85 Merge pull request #2 from Danielv123/master
update
2016-08-23 20:15:47 +02:00
Danielv123 258946fe51 Added sample server-settings.json to repo 2016-08-23 19:42:34 +02:00
Danielv123 410b3e5e53 Added webpage to display network contents 2016-08-23 19:31:27 +02:00
Danielv123 8ea74821b4 webserver and API endpoint for getting network contents 2016-08-23 14:16:24 +02:00
TheAIBot f2f832333c Merge pull request #1 from Danielv123/master
update
2016-08-21 10:48:03 +02:00
TheAIBot 2fe0844fd8 Merge remote-tracking branch 'refs/remotes/Danielv123/master' 2016-08-21 10:43:48 +02:00
Danielv123 fcb9c5add5 Changed name of mod and descriptions, updated readme.md 2016-08-19 16:42:30 +02:00
Danielv123 0e9fb267c4 Working on #7 configs 2016-08-17 22:52:44 +02:00
Danielv123 601d5edb27 Cleanup, readme 2016-08-17 18:31:43 +02:00
Danielv123 44e311feea Fixed js, items are now created correctly and dupe/loss glitches removed 2016-08-17 09:20:14 +02:00
Danielv123 440ecfad30 Everything works! (using rcon) 2016-08-16 12:41:55 +02:00
Daniel 74253908d5 Merge pull request #6 from TheAIBot/master
added UPDATE_RATE to config
2016-08-16 11:54:10 +02:00
TheAIBot 4897f019f6 added UPDATE_RATE to config 2016-08-16 11:43:18 +02:00
Daniel a3f931fbec Merge pull request #5 from TheAIBot/master
added tank, made code faster and added another remote call
2016-08-16 10:36:02 +02:00
TheAIBot 7beef11f08 added tank, made code faster and added another remote call 2016-08-16 02:03:11 +02:00
Daniel 1a9fc933d4 Merge pull request #3 from TheAIBot/master
Only use as many resources as given
2016-08-14 11:06:25 +02:00
TheAIBot ba98a0cfe0 removed uneeded print statements 2016-08-14 10:59:31 +02:00
TheAIBot e5366ac431 Mod will now only use as many resources as it has been given 2016-08-14 10:55:53 +02:00
TheAIBot 1f58458302 Merge remote-tracking branch 'refs/remotes/Danielv123/master' 2016-08-13 22:21:53 +02:00
Danielv123 5bbb961f54 Integration of last merges changes 2016-08-13 15:54:01 +02:00
Daniel a25d3be98d Merge pull request #1 from TheAIBot/master
Fits perfectly, thank you so much!
2016-08-13 11:28:15 +02:00
TheAIBot 9b0349d518 using chests 2016-08-13 10:26:55 +02:00
Danielv123 ba3d01e2fe Added my bat file for launching everything (most things) 2016-08-12 21:15:37 +02:00
Danielv123 d80619bab8 added the lua mod too 2016-08-12 21:02:44 +02:00
Danielv123 1f707d0aad Lua side does not work, JS seems to be fine 2016-08-12 21:01:50 +02:00
Danielv123 95a04c1221 Initial commit 2016-08-12 20:54:14 +02:00
3850 changed files with 13815 additions and 174 deletions

No files matched your search

+70
View File
@@ -0,0 +1,70 @@
# Logs
logs
*.log
npm-debug.log*
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
dist/
node_modules/*
!node_modules/_app
Factorio*
/factorio
/instances
/sharedMods
/database
database/items.db
config.json
package-lock.json
secret-api-token.txt
mod_cache.json
static/pictures/spritesheet.js
static/pictures/spritesheet.png
static/external
saves
# weird pattern to ignore sharedPlugins except not ignore some core modules
!sharedPlugins/
sharedPlugins/*
!sharedPlugins/inventoryImports/
!sharedPlugins/UPSdisplay/
!sharedPlugins/remoteMap/
!sharedPlugins/remoteCommands/
!sharedPlugins/researchSync/
!sharedPlugins/globalChat/
!sharedPlugins/clusterioMod/
!sharedPlugins/serverManager/
sharedPlugins/serverManager/database/*
sharedPlugins/serverManager/config.json
sharedPlugins/serverManager/secret-api-token.txt
!sharedPlugins/scenario_loader/
sharedPlugins/scenario_loader/scenarios
!sharedPlugins/statisticsExporter/
# File used for marking plugins as disabled
sharedPlugins/*/DISABLED
\.cache-require-paths\.json
\.vs/
bin/
+45
View File
@@ -0,0 +1,45 @@
# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
version: v1.13.3
ignore: {}
# patches apply the minimum changes required to fix a vulnerability
patch:
'npm:lodash:20180130':
- html5-validator > caporal > cli-table2 > lodash:
patched: '2018-07-03T06:07:55.532Z'
'npm:hawk:20160119':
- node-factorio-api > documentation > highlight.js > gear-lib > less > request > hawk:
patched: '2019-02-25T06:48:21.845Z'
'npm:http-signature:20150122':
- node-factorio-api > documentation > highlight.js > gear-lib > less > request > http-signature:
patched: '2019-02-25T06:48:21.845Z'
'npm:mime:20170907':
- node-factorio-api > documentation > highlight.js > gear-lib > mime:
patched: '2019-02-25T06:48:21.845Z'
- node-factorio-api > documentation > highlight.js > gear-lib > less > mime:
patched: '2019-02-25T06:48:21.845Z'
- node-factorio-api > documentation > highlight.js > gear-lib > less > request > form-data > mime:
patched: '2019-02-25T06:48:21.845Z'
'npm:minimatch:20160620':
- node-factorio-api > documentation > highlight.js > gear > liftoff > findup-sync > glob > minimatch:
patched: '2019-02-25T06:48:21.845Z'
- node-factorio-api > documentation > highlight.js > gear-lib > gear > liftoff > findup-sync > glob > minimatch:
patched: '2019-02-25T06:48:21.845Z'
- node-factorio-api > documentation > highlight.js > gear-lib > glob > minimatch:
patched: '2019-02-25T06:48:21.845Z'
- node-factorio-api > documentation > highlight.js > gear-lib > jslint > glob > minimatch:
patched: '2019-02-25T06:48:21.845Z'
- node-factorio-api > documentation > highlight.js > gear-lib > jshint > cli > glob > minimatch:
patched: '2019-02-25T06:48:21.845Z'
- node-factorio-api > documentation > highlight.js > gear-lib > jshint > minimatch:
patched: '2019-02-25T06:48:21.845Z'
'npm:request:20160119':
- node-factorio-api > documentation > highlight.js > gear-lib > less > request:
patched: '2019-02-25T06:48:21.845Z'
'npm:tunnel-agent:20170305':
- node-factorio-api > documentation > highlight.js > gear-lib > less > request > tunnel-agent:
patched: '2019-02-25T06:48:21.845Z'
'npm:uglify-js:20151024':
- node-factorio-api > documentation > highlight.js > gear-lib > handlebars > uglify-js:
patched: '2019-02-25T06:48:21.845Z'
- node-factorio-api > documentation > highlight.js > gear-lib > uglify-js:
patched: '2019-02-25T06:48:21.845Z'
+10
View File
@@ -0,0 +1,10 @@
language: node_js
node_js:
- "9"
before_install:
- sudo apt-get -qq update
- sudo apt-get install -y curl git tar
- curl -o factorio.tar.gz -L https://www.factorio.com/get-download/latest/headless/linux64
- tar -xvf factorio.tar.gz
script:
- npm run cover
+3
View File
@@ -0,0 +1,3 @@
{
"CurrentProjectSetting": null
}
+8
View File
@@ -0,0 +1,8 @@
{
"ExpandedNodes": [
"",
"\\sharedPlugins"
],
"SelectedNode": "\\.gitignore",
"PreviewInSolutionExplorer": false
}
File diff suppressed because it is too large. Load diff
BIN
View File
Binary file not shown.
+21
View File
@@ -0,0 +1,21 @@
FROM node:9.11.2
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 cd factorioClusterio && curl -o factorio.tar.gz -L https://www.factorio.com/get-download/latest/headless/linux64 && tar -xf factorio.tar.gz
WORKDIR factorioClusterio
RUN mkdir instances sharedMods
RUN cp config.json.dist config.json
RUN node client.js download
LABEL maintainer "Sir3lit@gmail.com"
EXPOSE 8080 34167
VOLUME /factorioClusterio/instances
VOLUME /factorioClusterio/sharedMods
VOLUME /factorioClusterio/sharedPlugins
CMD RCONPORT="$RCONPORT" FACTORIOPORT="$FACTORIOPORT" MODE="$MODE" node $MODE\.js start $INSTANCE
+26
View File
@@ -0,0 +1,26 @@
This product uses the MIT License
Copyright (c) 2016 Daniel Vestøl
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software 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.
The icons for angels modded items located in /static/pictures/ are licensed
by https://mods.factorio.com/user/Arch666Angel under CC BY-NC-ND 4.0
https://creativecommons.org/licenses/by-nc-nd/4.0/
+284 -14
View File
@@ -1,18 +1,288 @@
# Plugin page layout screenshots <img src="./logo.svg" width="100%" align="right">
These screenshots render the real `PluginsPage`, `PluginVersions`, and `SiteLayout` components with synthetic controller/host responses. <br/>
They are layout evidence, not a live cluster installation or restart test. <br/>
The fixture has two machines, a pending awf_oarc update and web load error, an Auto Restart plugin without a host entrypoint, a Global Chat update, and an up-to-date Research Sync plugin. <br/>
Before: `61f2b0117263516b592bba0b397e887065de5c4d`. # factorioClusterio
After: `8229db75`.
Mobile viewport: 390 x 844 CSS pixels.
Desktop viewport: 1440 x 900 CSS pixels.
Both use Edge through Playwright, device scale factor 1, dark theme, the same data and default sorting.
The screenshots are kept on a separate evidence branch so they are not part of the code PR's diff. Discord for development/support/play: https://discord.gg/5XuDkje
To reproduce, put `main.tsx`, `index.html`, `serve.mjs`, `capture.mjs` and `check.mjs` in `temp/plugin-layout/` of a Clusterio checkout. ### Ways to support me/the project:
Run `pnpm install --no-frozen-lockfile`, `npm install --prefix temp/plugin-layout --no-package-lock --no-save playwright`, and `node temp/plugin-layout/serve.mjs` from the repository root.
With Microsoft Edge installed, run `node temp/plugin-layout/capture.mjs before` at the baseline or `node temp/plugin-layout/capture.mjs after` at the fixed commit. * Contribute with code/documentation. Always nice to move the project forward
Run `node temp/plugin-layout/check.mjs` at the fixed commit for viewport bounds, search, sorting, navigation, account menu and checklist checks.
* Support me monetarily on [patreon](https://www.patreon.com/danielv123) or paypal: danielv@live.no
### Table of contents
* [Introduction & methodology](#introduction)
* [Ubuntu setup](#ubuntu-setup)
* [Windows setup](#windows-setup)
* [Optional plugins](#Plugins)
* [Common problems](#Common-problems)
* [Command cheatsheet](#cheatsheet)
## Introduction
Features:
- Entities to send/recieve items
- Cross dimensional storage
- Sending of liquids
- Sending of circuit network signals
- Inventory combinator to display item levels in the cluster (and epoch time)
- Reporting of graphs and UPS on master interface (Also has extensive Prometheus reporting)
Optional extras (see [Plugins](#Plugins))
- Have your inventory synchronize across servers
- Teleport trans from the border of one world to te next
- Show in-game chat in discord
Connection diagram:
![http://i.imgur.com/7FdVfgB.png](http://i.imgur.com/7FdVfgB.png)
There can be any number of clients connected to each slave, and any number of slaves connected to a master but there can only be one master server in each cluster.
**How does it work?**
Traditional factorio mods have always been limited by the games deterministic design. This gives us a very bug free and predictable game, but doesn't allow us cool stuff such as internet communication.
Clusterio exploits one of the games logging features, game.write_file and RCON to communicate between servers. Sending an item from one server to another takes this path:
1. server1: Chest has stuff in it, write the contents to a file and delete them from the game world
2. client.js on server1: File has stuff in it, parse and send to the master for storage
3. master: server1 gave us stuff, store it in the storage and write some statistics
4. server2: get-chest is empty, write a request to file
5. client.js on server2: Request file has stuff in it, parse and send a request to master for more items of that type
6. master: server2 asked for stuff, check if we have enough and how much demand there is, then send however much is appropriate back
7. client.js on server2: We were allowed to import x of item y, run command /c remote.call("clusterio", "importMany", "{'copper-plate':120}")
This process works the same for both items and liquids, independent on what mods are used. Yes, modded items are fully supported.
Clusterio can also do a few other neat things, such as giving you access to epoch time, syncing player inventories between servers, keeping track of playtime (playerManager plugin), teleporting trains between servers (trainTeleports) and exporting tons of factorio related statistics to Prometheus for graphing in grafana.
## Ubuntu setup
NodeJS does not support EOL ubuntu releases. Make sure you are on the most recent LTS release or newer.
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
cd factorioClusterio
curl -o factorio.tar.gz -L https://www.factorio.com/get-download/latest/headless/linux64
tar -xf factorio.tar.gz
npm install
cp config.json.dist config.json
node ./lib/npmPostinstall.js
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.
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.
* You get the `masterAuthToken` from `secret-api-token.txt` in the master install dir after running the master twice.
* You get your factorio matchmaking token from factorio.com
* The `masterAuthSecret` should never be touched unless you want to invalidate everyones authentication tokens
**Master**
pm2 start master.js --name master
OR
node master.js
**Server Host**
To download the mod for all its non vanilla features and items, (optional, but very recommended)
node client.js manage shared mods add clusterio
To create a new instance (its own save, set of mods and config files)
node client.js start [instancename]
To launch an instance with pm2
pm2 start --name [instancename] client.js -- start [instancename]
use `nano config.json` to change settings.
**Ubuntu with Docker**
Clusterio has *very* limited support for using docker.
sudo docker build -t clusterio --no-cache --force-rm factorioClusterio
sudo docker run --name master -e MODE=master -p 1234:8080 -d -it --restart=unless-stopped danielvestol/clusterio
sudo docker run --name slave -e MODE=client -e INSTANCE=world1 -v /srv/clusterio/instances:/factorioClusterio/instances -p 1235:34167 -it --restart=unless-stopped danielvestol/clusterio
The -v flag is used to specify the instance directory. Your instances (save files etc) will be stored there.
## Windows setup
Clusterio is built up of multiple parts. Here is a quick guide:
Master = master.js
Server host (Slave) = client.js + factorio server
Game Client = The people connecting to the server
**Requirements**
download and install nodeJS 8 or 9 from http://nodejs.org
download and install git from https://git-scm.com/
reboot when you are done, then proceed to the next steps. *reboots matter*
**Master**
1. download and run https://puu.sh/toFHl/01eebbb333.bat
2. Copy config.json.dist to 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.
4. Run `node client.js manage shared mods add clusterio`
5. type `node master.js` to start the server
**Server Host**
1. download and run https://puu.sh/toFHl/01eebbb333.bat
2. Copy config.json.dist to config.json
3. Follow the instructions given.
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.
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.
Change `masterIP `to something like `31.152.123.14` (provided by master server owner)
Change `masterPort` to something like `8080` (provided by master server owner)
Change `masterAuthToken` to the value found in `secret-api-token.txt` on the master server
Repeat step 4 for more servers on one machine. You should be able to find its port by looking at the slave section on master:8080 (the web interface)
**GameClient**
Fancy game client that does the following steps automatically, but is really old so be warned: [clusterioClient](https://github.com/Danielv123/factorioClusterioClient)
1. Download the same version of the mod as the slave is running from [the mod portal](https://mods.factorio.com/mods/Danielv123/clusterio) or [github](https://github.com/Danielv123/factorioClusterioMod
2. Drop it into ./factorio/mods
3. Run factorio and connect to slave as a normal MP game. You will find the port number to connect to at http://[masterAddress]:8080
## Plugins
Here are the known Clusterio plugins in the wild:
1. [Player Manager](https://github.com/Danielv123/playerManager) - Adds player management to the Web UI and shared inventory handling (beta)
2. [DiscordChat](https://github.com/jakedraddy/ClusterioDiscordChat) - Logs in-game chat/joins/leave messages on all instances to a Discord webhook.
3. [TrainTeleports](https://github.com/Godmave/clusterioTrainTeleports) - Allows you to teleport cargotrains between servers.
## Common problems
### Cannot find module: `/../../config`
Copy your config.json.dist to config.json and configure it.
### EACCESS [...] LISTEN 443
Some systems don't let non root processes listen to ports below 1000. Either run with `sudo` or change config.json to use higher port numbers.
### InitializationError: First argument must be an abstract-leveldown compliant store
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.
TLDR: the tested fix is:
- open your windows firewall and go to advanced settings
- click on inbound rules and click on new rule...
- select port and click next >
- select TCP and select specific local ports and type in the ports that you want to open (comma separated) and click next > 3 times
- give the rule a name (like 'web server' or something), give it a description (optionally) and click finish
### Other fixes for other potential problems:
Sometimes the install fails. Try `node ./lib/npmPostinstall.js` to complete it.
## Cheatsheet
**To create a new instance/start it**
node client.js start [instanceName]
**Other instance management tools:**
```
node client.js delete [instanceName]
node client.js list
```
**To update clusterio to the latest version:**
1. Download the latest zip version of factorio for your platform manually from factorio.com. Place it in the project root folder and call it "factorio" (folder name is specified in config.json)
2. Grab the latest version of the repo
```
git pull
npm install
```
3. Download the latest version of the factorioClusterioMod from its github repo
```
node client.js manage shared mods add clusterio
```
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.
-14
View File
@@ -1,14 +0,0 @@
import { chromium } from "playwright";
const browser = await chromium.launch({ channel: "msedge", headless: true });
const page = await browser.newPage();
page.on("pageerror", error => console.error(error));
for (const [label, width, height] of [["desktop", 1440, 900], ["mobile", 390, 844]]) {
await page.setViewportSize({ width, height });
await page.goto("http://127.0.0.1:5173/plugins");
await page.getByRole("button", { name: "Update to 0.3.1", exact: true }).waitFor();
await page.evaluate(() => document.fonts.ready);
await page.screenshot({ path: `temp/plugin-layout/${process.argv[2]}-${label}.png` });
console.log(label, await page.evaluate(() => ({ width: innerWidth, scrollWidth: document.documentElement.scrollWidth,
rowHeights: [...document.querySelectorAll(".ant-table-tbody > tr")].map(row => row.getBoundingClientRect().height) })));
}
await browser.close();
-57
View File
@@ -1,57 +0,0 @@
import assert from "node:assert/strict";
import { chromium } from "playwright";
const browser = await chromium.launch({ channel: "msedge", headless: true });
const page = await browser.newPage();
const errors = [];
page.on("pageerror", error => errors.push(error.message));
for (const width of [320, 390, 575, 767, 768, 1024, 1440]) {
await page.setViewportSize({ width, height: 900 });
await page.goto("http://127.0.0.1:5173/plugins");
await page.getByRole("button", { name: "Update to 0.3.1", exact: true }).waitFor();
const dimensions = await page.evaluate(() => ({
width: innerWidth, scrollWidth: document.documentElement.scrollWidth,
columns: document.querySelectorAll("thead th").length,
brand: document.querySelector(".site-layout-brand").getBoundingClientRect().height,
overflow: [...document.querySelectorAll(".plugin-mobile-details, .plugin-mobile-details .ant-btn")]
.filter(element => element.scrollWidth > element.clientWidth + 1).length,
}));
assert.equal(dimensions.scrollWidth, width);
assert.equal(dimensions.columns, width < 768 ? 1 : 3);
assert.equal(dimensions.overflow, 0);
assert.ok(dimensions.brand < 50);
console.log("Layout", width, dimensions);
}
for (const width of [390, 1440]) {
await page.setViewportSize({ width, height: 900 });
await page.goto("http://127.0.0.1:5173/plugins");
const update = page.getByRole("button", { name: "Update to 2.1.0", exact: true }).filter({ hasNot: page.locator("[disabled]") });
await page.locator(".ant-table-row").filter({ hasText: "Global Chat" }).getByRole("button").click();
await page.getByRole("dialog", { name: "Update Global Chat 2.1.0" }).waitFor();
assert.equal(new URL(page.url()).pathname, "/plugins");
await page.getByRole("button", { name: "Cancel", exact: true }).click();
await page.getByRole("button", { name: "Restart pending (2)" }).click();
await page.getByRole("dialog", { name: "Restart machines with a pending restart" }).waitFor();
await page.getByRole("button", { name: "Cancel", exact: true }).click();
await page.getByRole("button", { name: "Account menu for Cooldude2606" }).click();
await page.getByRole("menuitem", { name: "Cooldude2606" }).waitFor();
await page.keyboard.press("Escape");
await page.locator(".page-header-title").click();
await page.locator(".ant-table-filter-trigger").click();
await page.getByPlaceholder("Search plugins").fill("Global");
assert.equal(await page.locator(".ant-table-row").count(), 1);
await page.getByPlaceholder("Search plugins").press("Enter");
await page.reload();
await page.getByRole("link", { name: "Global Chat", exact: true }).waitFor();
assert.equal(await page.locator(".ant-table-row").count(), 1);
await page.getByRole("link", { name: "Global Chat", exact: true }).click();
await page.getByText("Plugin navigation target").waitFor();
await page.goto("http://127.0.0.1:5173/plugins");
await page.getByRole("columnheader", { name: /Name/ }).click();
await page.waitForFunction(() => document.querySelector(".ant-table-row")?.textContent.startsWith("Research Sync"));
assert.match(await page.locator(".ant-table-row").first().innerText(), /Research Sync/);
await page.locator(".ant-table-row").first().click();
await page.getByText("Plugin navigation target").waitFor();
console.log("Interactions passed", width);
}
assert.deepEqual(errors, []);
await browser.close();
+799
View File
@@ -0,0 +1,799 @@
const fs = require('fs-extra');
const https = require('follow-redirects').https;
const needle = require("needle");
const child_process = require('child_process');
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 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");
// argument parsing
const args = require('minimist')(process.argv.slice(2));
// require config.json
var config = require(args.config || './config');
var global = {};
if (!fs.existsSync(config.instanceDirectory)) {
fs.mkdirSync(config.instanceDirectory);
}
if (!fs.existsSync("./sharedPlugins/")) {
fs.mkdirSync("sharedPlugins");
}
if (!fs.existsSync("./sharedMods/")) {
fs.mkdirSync("sharedMods");
}
const instance = process.argv[3];
const instancedirectory = config.instanceDirectory + '/' + instance;
const command = process.argv[2];
// Set the process title, shows up as the title of the CMD window on windows
// and as the process name in ps/top on linux.
process.title = "clusterioClient "+instance;
// add better stack traces on promise rejection
process.on('unhandledRejection', r => console.log(r));
// make sure we have the master access token (can't write to master without it since clusterio 2.0)
if(!config.masterAuthToken || typeof config.masterAuthToken !== "string"){
console.error("ERROR invalid config!");
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.");
}
const needleOptionsWithTokenAuthHeader = {
headers: {
'x-access-token': config.masterAuthToken
},
};
var instanceInfo = {};
var commandBuffer=[];
// messageInterface Management
setInterval(function(){
let command=commandBuffer.shift();
if(command){
messageInterfaceInternal(command[0], command[1], command[2], command[3]);
}
},config.msBetweenCommands || 50);
// function to handle sending commands into the game
async function messageInterfaceInternal(command, callback, resolve, reject) {
// try to save us if you send a buffer instead of string
if(typeof command == "object") {
command = command.toString('utf8');
}
if(false && process.platform == "linux" && typeof command == "string" && serverprocess) {
/*
to send to stdin, use:
serverprocess.stdin.write("/c command;\n")
*/
serverprocess.stdin.write(command+"\n");
if(typeof callback == "function"){
callback();
}
resolve();
} else if(typeof command == "string" && client && client.send && typeof client.send == "function") {
try {
let str = await client.send(command+"\n");
if(typeof callback == "function") callback(str)
resolve(str)
} catch (err) {
console.log("RCON failed, server might be paused or shutting down");
// console.log(err);
// reject(err);
if(typeof callback == "function"){
callback();
}
reject(err)
}
}
}
function messageInterface(command, callback) {
return new Promise((resolve,reject) => {
commandBuffer.push([command,callback, resolve, reject]);
});
}
// handle commandline parameters
if (!command || command == "help" || command == "--help") {
console.error("Usage: ");
console.error("node client.js start [instance name]");
console.error("node client.js list");
console.error("node client.js delete [instance]");
console.error("To download the latest version of the Clusterio lua mod, do");
console.error("node client.js manage shared mods download clusterio");
console.error("To download a clusterio plugin, do");
console.error("node client.js manage shared plugins install https://github.com/Danielv123/playerManager");
console.error("For more management options, do");
console.error("node client.js manage");
process.exit(1);
} else if (command == "list") {
let instanceNames = fileOps.getDirectoriesSync(config.instanceDirectory);
instanceNames.unshift("Name:");
let longestInstanceName = 0;
// determine longest instance name
instanceNames.forEach(function(instance){
if(instance.length > longestInstanceName) longestInstanceName = instance.length;
});
let displayLines = [];
// push name coloumn to array
instanceNames.forEach(function(instance){
while(instance.length < longestInstanceName+1){
instance += " ";
}
displayLines.push("| "+ instance + "|");
});
// create port colloumn
let factorioPorts = [];
instanceNames.forEach(function(instance){
let factorioPort;
if(instance.includes("Name:")){
factorioPort = "Port:"
} else {
factorioPort = require(path.resolve(config.instanceDirectory, instance, 'config')).factorioPort;
}
factorioPorts.push(factorioPort);
});
factorioPorts.forEach((port, index) => {
let longestPort = 0;
factorioPorts.forEach((port, index) => {
if(port.toString().length > longestPort) longestPort = port.toString().length;
});
while(port.toString().length < longestPort){
port += " ";
}
factorioPorts[index] = port;
});
instanceNames.forEach(function(instance, index){
displayLines[index] += " " + factorioPorts[index] + " |";
});
displayLines.forEach(line => console.log(line));
process.exit(0);
} else if (command == "manage"){
// console.log("Launching mod manager");
//const fullUsage = 'node client.js manage [instance, "shared"] ["mods", "config"] ...';
function usage(instance, tool, action){
if(tool && tool == "mods"){
console.log('node client.js manage '+instance+' '+tool+' ["list", "search", "add", "remove", "update"]');
} else if(tool && tool == "config") {
console.log('node client.js manage '+instance+' '+tool+' ["list", "edit"]');
} else if(tool && tool == "plugins") {
console.log(`node client.js manage ${instance} ${tool} ["list", "add", "remove"]`);
} else {
console.log('node client.js manage '+(instance || '[instance, "shared"]') +' '+ (tool || '["mods", "config", "plugins"]') + ' ...');
}
}
const tool = process.argv[4] || "";
const action = process.argv[5] || "";
if(instance){
if(tool == "mods"){
(async function(){try{
// do require down here to reduce master load time
const modManager = require("./lib/manager/modManager.js")(config);
// allow managing mods
if(action == "list"){
console.log(await modManager.listMods(instance));
} else if(action == "search"){
console.log(await modManager.findMods(process.argv[6]));
} else if(action == "add" || action == "download"){
await modManager.addMod(process.argv[6], instance);
} else if(action == "remove" || action == "rm" || action == "delete"){
await modManager.removeMod(process.argv[6], instance);
} else if(action == "update"){
await modManager.updateAllMods();
} else {
usage(instance, tool);
}
process.exit(0);
}catch(e){
console.log("Got error from modManager:")
console.log(e);
}})();
} else if(tool == "config"){
// allow managing the config
if(action == "list" || action == "show" || action == "display"){
configManager.displayConfig(instance);
} else if(action == "edit"){
let newConfigValue = "";
process.argv.forEach((arg, i)=>{
if(i >= 8){
newConfigValue += " "+arg;
} else if(i >= 7){
newConfigValue += arg;
}
});
configManager.editConfig(instance, process.argv[6], newConfigValue);
} else {
usage(instance, tool);
}
} else if(tool == "plugins"){
(async function(){try{
const pluginManager = require("./lib/manager/pluginManager.js")(config);
if(action == "list"){
await pluginManager.listPlugins();
} else if(action == "add" || action == "install" || action == "download"){
let status = await pluginManager.addPlugin(process.argv[6]);
} else if(action == "remove" || action == "uninstall" || action == "delete"){
let status = await pluginManager.removePlugin(process.argv[6]);
if(status && status.msg) console.log(status.msg);
} else if(action == "enable"){
let status = await pluginManager.enablePlugin(process.argv[6]);
if(status && status.msg) console.log(status.msg);
} else if(action == "disable"){
let status = await pluginManager.disablePlugin(process.argv[6]);
if(status && status.msg) console.log(status.msg);
}
process.exit(0);
}catch(e){
console.error(e);
process.exit(1);
}})();
} else {
usage(instance);
}
} else {
console.log('Usage:');
usage(instance);
}
// process.exit(0);
} else if (command == "delete") {
if (!process.argv[3]) {
console.error("Usage: node client.js delete [instance]");
process.exit(1);
} else if (typeof process.argv[3] == "string" && fs.existsSync(config.instanceDirectory+"/" + process.argv[3]) && process.argv[3] != "/" && process.argv[3] != "") {
fileOps.deleteFolderRecursiveSync(path.resolve(config.instanceDirectory, process.argv[3])); // TODO: Check if this can cause i-craft users to format their server by using wrong paths
console.log("Deleted instance " + process.argv[3]);
process.exit(0);
} else {
console.error("Instance not found: " + process.argv[3]);
process.exit(0);
}
} else if (command == "download") {
console.log("Downloading mods...");
// get JSON data about releases
let res = syncRequest('GET', 'https://api.github.com/repos/Danielv123/factorioClusterioMod/releases', {"headers":{"User-Agent":"Fuck you for requiring user agents!"}});
let url = JSON.parse(res.getBody())[0].assets[0].browser_download_url;
let name = JSON.parse(res.getBody())[0].assets[0].name;
if(url) {
console.log(url);
let file = fs.createWriteStream("sharedMods/"+name);
https.get(url, function(response) {
response.on('end', function () {
console.log("Downloaded "+name);
process.exit(0);
});
response.pipe(file);
}).end();
}
} else if (command == "start" && instance === undefined) {
console.error("ERROR: No instanceName provided!");
console.error("Usage: node client.js start [instanceName]");
process.exit(0);
} else if (command == "start" && typeof instance == "string" && instance != "/" && !fs.existsSync(instancedirectory)) {
// if instance does not exist, create it
console.log("Creating instance...");
fs.mkdirSync(instancedirectory);
fs.mkdirSync(instancedirectory + "/script-output/");
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 => {
if (err) console.error(err)
});
// fs.symlinkSync('../../../sharedMods', instancedirectory + "/mods", 'junction') // This is broken because it can only take a file as first argument, not a folder
fs.writeFileSync(instancedirectory + `/config.ini`, `[path]\r\n
read-data=${ path.resolve(config.factorioDirectory, "data") }\r\n
write-data=${ path.resolve(config.instanceDirectory, instance) }\r\n
`);
// this line is probably not needed anymore but Im not gonna remove it
fs.copySync('sharedMods', path.join(instancedirectory, "mods"));
let instconf = {
"factorioPort": args.port || process.env.FACTORIOPORT || Math.floor(Math.random() * 65535),
"clientPort": args["rcon-port"] || process.env.RCONPORT || Math.floor(Math.random() * 65535),
"__comment_clientPassword": "This is the rcon password. Its also used for making an instanceID. Make sure its unique and not blank.",
"clientPassword": args["rcon-password"] || Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 8),
}
console.log("Clusterio | Created instance with settings:")
console.log(instconf);
// create instance config
fs.writeFileSync(instancedirectory + "/config.json", JSON.stringify(instconf, null, 4));
let name = "Clusterio instance: " + instance;
if (config.username) {
name = config.username + "'s clusterio " + instance;
}
let serversettings = {
"name": name,
"description": config.description,
"tags": ["clusterio"],
"max_players": "20",
"visibility": config.visibility,
"username": config.username,
"token": config.token,
"game_password": config.game_password,
"verify_user_identity": config.verify_user_identity,
"admins": [config.username],
"allow_commands": config.allow_commands,
"autosave_interval": 10,
"autosave_slots": 5,
"afk_autokick_interval": 0,
"auto_pause": config.auto_pause,
};
fs.writeFileSync(instancedirectory + "/server-settings.json", JSON.stringify(serversettings, null, 4));
console.log("Server settings: "+JSON.stringify(serversettings, null, 4));
console.log("Creating save .....");
let factorio = child_process.spawn(
'./' + config.factorioDirectory + '/bin/x64/factorio', [
'-c', instancedirectory + '/config.ini',
// '--create', instancedirectory + '/saves/save.zip',
'--start-server-load-scenario', 'Hotpatch',
'--server-settings', instancedirectory + '/server-settings.json',
'--rcon-port', Number(process.env.RCONPORT) || instconf.clientPort,
'--rcon-password', instconf.clientPassword,
], {
'stdio': ['pipe', 'pipe', 'pipe']
}
);
factorio.stdout.on("data", data => {
data = data.toString("utf8").replace(/(\r\n\t|\n|\r\t)/gm,"");
console.log(data);
if(data.includes("Starting RCON interface")){
let client = new Rcon();
client.connect({
host: 'localhost',
port: Number(process.env.RCONPORT) || instconf.clientPort,
password: instconf.clientPassword,
timeout: 5000
});
client.onDidAuthenticate(() => {
console.log('Clusterio | RCON Authenticated!');
});
client.onDidConnect(() => {
console.log('Clusterio | RCON Connected, starting save');
client.send("/c game.server_save('hotpachSave')");
});
}
if(data.includes("Saving finished")){
console.log("Map saved as hotpachSave.zip, exiting...");
console.log("Instance created!")
process.exit(0);
}
if(data.includes("Downloading from auth server failed")){
console.error("Instance creation failed, unable to establish auth server connection.");
console.error("Deleting broken instance...");
fileOps.deleteFolderRecursiveSync(path.resolve(config.instanceDirectory, process.argv[3]));
process.exit(0);
}
});
} else if (command == "start" && typeof instance == "string" && instance != "/" && fs.existsSync(instancedirectory)){(async () => {
// Exit if no instance specified (it should be, just a safeguard);
if(instancedirectory != config.instanceDirectory+"/undefined"){
var instanceconfig = require(path.resolve(instancedirectory,'config'));
instanceconfig.unique = stringUtils.hashCode(instanceconfig.clientPassword);
if(process.env.FACTORIOPORT){
instanceconfig.factorioPort = process.env.FACTORIOPORT;
}
if(process.env.RCONPORT){
instanceconfig.rconPort = process.env.RCONPORT;
}
} else {
process.exit(1);
}
console.log("Deleting .tmp.zip files");
let savefiles = fs.readdirSync(path.join(instancedirectory,"saves"));
for(i = 0; i < savefiles.length; i++){
if(savefiles[i].substr(savefiles[i].length - 8, 8) == ".tmp.zip") {
fs.unlinkSync(path.resolve(instancedirectory, "saves", savefiles[i]));
}
}
console.log("Clusterio | Rotating old logs...");
// clean old log file to avoid crash
try{
let logPath = path.join(instancedirectory,'factorio-current.log');
let stat = await fs.stat(logPath);
console.log(stat)
console.log(stat.isFile())
if(stat.isFile()){
let logFilename = `factorio-${Math.floor(Date.parse(stat.mtime)/1000)}.log`;
await fs.rename(logPath, path.join(instancedirectory, logFilename));
console.log(`Log rotated as ${logFilename}`);
}
}catch(e){}
// Math.floor(Date.now()/1000)
// (new Date).toGMTString()
// move mods from ./sharedMods to the instances mod directory
try{fs.mkdirSync(path.join(instancedirectory, "instanceMods"));}catch(e){}
try{rmdirSync(path.join(instancedirectory, "mods"));}catch(e){}
try {
// mods directory that will be emptied (deleted) when closing the server to facilitate seperation of instanceMods and sharedMods
fs.mkdirSync(path.join(instancedirectory, "mods"));
} catch(e){}
console.log("Clusterio | Moving shared mods from sharedMods/ to instance/mods...");
fs.copySync('sharedMods', path.join(instancedirectory, "mods"));
console.log("Clusterio | Moving instance specific mods from instance/instanceMods to instance/mods...");
fs.copySync(path.join(instancedirectory, "instanceMods"), path.join(instancedirectory, "mods"));
// Set paths for factorio so it reads and writes from the correct place even if the instance is imported from somewhere else
fs.writeFileSync(instancedirectory + `/config.ini`, `[path]\r\n
read-data=${ path.resolve(config.factorioDirectory, "data") }\r\n
write-data=${ path.resolve(config.instanceDirectory, instance) }\r\n
`);
// Spawn factorio server
//var serverprocess = child_process.exec(commandline);
fileOps.getNewestFile(instancedirectory + "/saves/", fs.readdirSync(instancedirectory + "/saves/"),function(err, latestSave) {
if(err) {
console.error("ERROR!");
console.error("Your savefile seems to be missing. This might because you created an instance without having factorio\
installed and configured properly. Try installing factorio and adding your savefile to instances/[instancename]/saves/\n");
throw err;
}
// implicit global
serverprocess = child_process.spawn(
'./' + config.factorioDirectory + '/bin/x64/factorio', [
'-c', instancedirectory + '/config.ini',
'--start-server', latestSave.file,
'--rcon-port', args["rcon-port"] || Number(process.env.RCONPORT) || instanceconfig.clientPort,
'--rcon-password', args["rcon-password"] || instanceconfig.clientPassword,
'--server-settings', instancedirectory + '/server-settings.json',
'--port', args.port || Number(process.env.FACTORIOPORT) || instanceconfig.factorioPort
], {
'stdio': ['pipe', 'pipe', 'pipe']
}
);
serverprocess.on('close', code => {
console.log(`child process exited with code ${code}`);
process.exit();
});
serverprocess.stdout.on("data", data => {
// log("Stdout: " + data);
if(data.toString('utf8').includes("Couldn't parse RCON data: Maximum payload size exceeded")){
console.error("ERROR: RCON CONNECTION BROKE DUE TO TOO LARGE PACKET!");
console.error("Attempting reconnect...");
client.disconnect();
client.connect();
}
// we have to do this to make logs visible on linux and in powershell. Causes log duplication for people with CMD.
console.log('Fact: ' + data.toString("utf8").replace("\n", ""));
});
serverprocess.stderr.on('data', (chunk) => {
console.log('ERR: ' + chunk);
});
// connect to the server with rcon
if(true || process.platform != "linux"){
// IP, port, password
client = new Rcon();
// check the logfile to see if the RCON interface is running as there is no way to continue without it
// we read the log every 2 seconds and stop looping when we start connecting to factorio
function checkRcon() {
fs.readFile(instancedirectory+"/factorio-current.log", function (err, data) {
// if (err) console.error(err);
if(data && data.indexOf('Starting RCON interface') > 0){
client.connect({
host: 'localhost',
port: args["rcon-port"] || Number(process.env.RCONPORT) || instanceconfig.clientPort,
password: args["rcon-password"] || instanceconfig.clientPassword,
timeout: 5000
});
} else {
setTimeout(function(){
checkRcon();
},5000);
}
});
}
setTimeout(checkRcon, 5000);
client.onDidAuthenticate(() => {
console.log('Clusterio | RCON Authenticated!');
instanceManagement(instanceconfig); // start using rcons
});
client.onDidConnect(() => {
console.log('Clusterio | RCON Connected!');
// getID();
});
client.onDidDisconnect(() => {
console.log('Clusterio | RCON Disconnected!');
// process.exit(0); // exit because RCON disconnecting is undefined behaviour and we rather just wanna restart now
});
process.on('SIGINT', function () {
console.log("Caught interrupt signal, disconnecting RCON");
client.disconnect().then(()=>{
console.log("Rcon disconnected, Sending ^C");
// We don't actually do this on windows, because ctrl+c in windows CMD sends it to all subprocesses as well. Doing it twice will abort factorios save.
if(process.platform == "linux"){
serverprocess.kill("SIGINT");
}
});
});
} else if(process.platform == "linux"){
// don't open an RCON connection and just use stdio instead, does not work on windows.
instanceManagement(instanceconfig);
process.on('SIGINT', function () {
console.log("Caught interrupt signal, sending ^C");
serverprocess.kill("SIGINT");
});
}
// set some globals
confirmedOrders = [];
lastSignalCheck = Date.now();
});
})()} else {
console.error("Invalid arguments, quitting.");
process.exit(1);
}
// ensure instancemanagement only ever runs once
_.once(instanceManagement);
async function instanceManagement(instanceconfig) {
console.log("Started instanceManagement();");
/* Open websocket connection to master */
var socket = ioClient("http://"+config.masterIP+":"+config.masterPort);
socket.on("hello", data => {
console.log("SOCKET | registering slave!");
socket.emit("registerSlave", {
instanceID: instanceconfig.unique,
});
});
setInterval(B=> socket.emit("heartbeat"), 10000);
// load plugins and execute onLoad event
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];
if(!global.subscribedFiles) {
global.subscribedFiles = {};
}
if(pluginConfig.binary == "nodePackage" && pluginConfig.enabled){
// require plugin class and execute it
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'));
return true;
} else if (data && data.toString('utf8')[0] == "/"){
return messageInterface(data.toString('utf8'), callback);
}
}, { // extra functions to pass in object. Should have done it like this from the start, but won't break backwards compat.
socket, // socket.io connection to master (and ES6 destructuring, yay)
});
if(plugins[i].factorioOutput && typeof plugins[i].factorioOutput === "function"){
// when factorio logs a line, send it to the plugin. This includes things like autosaves, chat, errors etc
serverprocess.stdout.on("data", data => plugins[i].factorioOutput(data.toString()));
}
if(pluginConfig.scriptOutputFileSubscription && typeof pluginConfig.scriptOutputFileSubscription == "string"){
if(global.subscribedFiles[pluginConfig.scriptOutputFileSubscription]) {
// please choose a unique file to subscribe to. If you need plugins to share this interface, set up a direct communication
// between those plugins instead.
throw "FATAL ERROR IN " + pluginConfig.name + " FILE ALREADY SUBSCRIBED " + pluginConfig.scriptOutputFileSubscription;
}
if (!fs.existsSync(instancedirectory + "/script-output/" + pluginConfig.scriptOutputFileSubscription)) {
// Do something
fs.writeFileSync(instancedirectory + "/script-output/" + pluginConfig.scriptOutputFileSubscription, "");
}
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){
throw new Error(`Plugin ${pluginConfig.name} has a fileOutputSubscription but plugin.scriptOutput is not a function`)
}
}
}
},
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?
}
}
// world IDs ------------------------------------------------------------------
hashMods(instance, function(modHashes){
setInterval(getID, 10000);
getID();
function getID() {
messageInterface("/silent-command rcon.print(#game.connected_players)", function(playerCount) {
var payload = {
time: Date.now(),
rconPort: instanceconfig.clientPort,
rconPassword: instanceconfig.clientPassword,
serverPort: instanceconfig.factorioPort,
unique: instanceconfig.unique,
publicIP: config.publicIP, // IP of the server should be global for all instances, so we pull that straight from the config
mods:modHashes,
instanceName: instance,
}
if(playerCount){
payload.playerCount = playerCount.replace(/(\r\n\t|\n|\r\t)/gm, "");
} else {
payload.playerCount = 0;
}
function callback(err, mac) {
if (err) {
mac = "unknown";
console.log("##### getMac crashed, but we don't really give a shit because we are probably closing down #####");
}
global.mac = mac;
payload.mac = mac;
// console.log("Registered our presence with master "+config.masterIP+" at " + payload.time);
needle.post(config.masterIP + ":" + config.masterPort + '/api/getID', payload, needleOptionsWithTokenAuthHeader, function (err, response, body) {
if (err && err.code != "ECONNRESET"){
console.error("We got problems, something went wrong when contacting master"+config.masterIP+" at " + payload.time);
console.error(err);
} else if (response && response.body) {
// In the future we might be interested in whether or not we actually manage to send it, but honestly I don't care.
if(response.body !== "ok") {
console.log("Got no \"ok\" while registering our precense with master "+config.masterIP+" at " + payload.time);
console.log(response.body);
}
}
});
}
if(global.mac){
callback(undefined, global.mac);
} else {
getMac(callback);
}
});
}
});
// Mod uploading and management -----------------------------------------------
// get mod names and hashes
// string: instance, function: callback
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){
console.log("Sending mod: " + mod);
// Send mods master says it wants
// response.body is a string which is a modName.zip
var req = request.post({url: "http://"+config.masterIP + ":" + config.masterPort + '/api/uploadMod',
headers: {
"x-access-token": config.masterAuthToken,
},
}, function (err, resp, body) {
if (err) {
console.error(new Error("Unable to contact master server /api/uploadMod! Please check your config.json."));
} else {
console.log('URL: ' + body);
}
});
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")
}
}
});
}
}
} // END OF INSTANCE START ---------------------------------------------------------------------
// string, function
// returns [{modName:string,hash:string}, ... ]
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
}
}
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;
}
});
}
}
+33
View File
@@ -0,0 +1,33 @@
const needle = require("needle");
const config = require("./config.json");
const masterIP = `${config.masterIP}:${config.masterPort}`;
const token = config.masterAuthToken
let commandArray = process.argv;
commandArray.shift();
commandArray.shift();
const needleOptionsWithTokenAuthHeader = {
headers: {
'x-access-token': token,
},
};
let command = commandArray.join(" ");
let killBiters = `/silent-command local surface=game.surfaces[1] for key, entity in pairs(surface.find_entities_filtered({force="enemy"})) do entity.destroy() end`;
(async ()=>{
needle.post(masterIP+"/api/runCommand", {
broadcast:true,
command,
}, needleOptionsWithTokenAuthHeader, (err, resp) => {
console.log(resp.body)
});
// setInterval(()=>{
// needle.post(masterIP+"/api/runCommand", {
// broadcast:true,
// command: killBiters,
// }, needleOptionsWithTokenAuthHeader, (err, resp) => {
// console.log(resp.body)
// });
// }, 1000*60*5);
})();
+63
View File
@@ -0,0 +1,63 @@
{
"__comment": "Where factorio folder. Should be in the same folder as this config file.",
"factorioDirectory": "./factorio",
"instanceDirectory": "./instances",
"databaseDirectory": "./database",
"__comment1": "IP players should connect to to join your servers, should be portforwarded and stuff",
"publicIP": "localhost",
"__comment2": "IP the master web server runs at, can be ignored on the master system",
"masterIP": "localhost",
"__commentMasterPort": "HTTP port for the master to listen to",
"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,
"sslCert": "database/certificates/cert.crt",
"sslPrivKey": "database/certificates/cert.key",
"__commentMasterAuthToken": "Authentication token to connect to the master server. Find it in secret-api-token.txt on the master.",
"masterAuthToken": "",
"__commentAuthSecret": "This is a long random string used for generating authentication tokens. Do not share. Regenerates when secret-api-token.txt is deleted.",
"masterAuthSecret": "",
"__comment3": "Your factorio.com username for factorio MP services, also username will be admin on new instances",
"username": "",
"__comment4": "Token from your factorio.com profile page. Required for MP and mod manager services.",
"token": "",
"__comment7": "Used for communication with the mod portal, not needed if you manage mods by hand",
"factorio_version": "0.16.41",
"__comment5": "General user settings",
"verify_user_identity": true,
"auto_pause": false,
"allow_commands": "admins-only",
"game_password": "",
"description": "This server runs Clusterio",
"visibility": {
"public": true,
"lan": true
},
"__comment6": "Statistic gathering statistics, may affect server performance (master memory usage especially)",
"itemStats": {
"maxEntries":60,
"entriesPerSecond": 0.01666666667
},
"logItemTransfers": false,
"__comment6.5": "When the master is undersupplied, we have 3 different modes to 'fairly' distribute items. First to ask gets all, dole division using weird formula or experimental neural net tries to make stuff as fair as possible. Feel free to play around.",
"disableFairItemDistribution": false,
"useNeuralNetDoleDivider": false,
"uploadModsToMaster": true,
"msBetweenCommands": 10,
"allowRemoteCommandExecution": true,
"enableCrossServerShout": true,
"mirrorAllChat": false,
"__comment7": "Autosave interval for items in the master storage in milliseconds. Defaults to one minute when set to 0 or not set.",
"autosaveInterval": 600000,
"disablePrometheusPushgateway": false,
"__comment8": "This setting only affects the master. It makes every import for things that arent 'electricity' return a maximum of 0. Effectively disables teleport chests.",
"disableImportsOfEverythingExceptElectricity": false
}
+10
View File
@@ -0,0 +1,10 @@
toc:
- name: Master Server API
- clusterioMaster
- name: Type declarations
- itemStack
- name: Internal modules
description: |
Here are some helper functions for navigation.
- modManager
- getFactorioLocale
-2
View File
@@ -1,2 +0,0 @@
<!doctype html>
<html><head><meta name="viewport" content="width=device-width, initial-scale=1"><title>Plugin layout fixture</title></head><body><div id="root"></div><script>window.webRoot = "/";</script><script type="module" src="/temp/plugin-layout/main.tsx"></script></body></html>
+28
View File
@@ -0,0 +1,28 @@
const jwt = require('jsonwebtoken');
// const config = require("./../config");
module.exports = function(config){
return {
middleware: function (req, res, next) {
var token = req.headers['x-access-token'];
if(!token) return res.status(401).send({ auth: false, message: 'No token provided.', endpoint: req.route.path});
jwt.verify(token, config.masterAuthSecret, function(err, decoded) {
if(err) return res.status(500).send({ auth: false, message: 'Failed to authenticate token.', endpoint: req.route.path});
next();
});
},
check: async function(token){
return new Promise((resolve, reject) => {
jwt.verify(token, config.masterAuthSecret, function(err, decoded) {
if(err){
resolve({ok:false, msg:err})
} else {
resolve({ok:true, msg:"Successfully authenticated"});
}
});
});
}
}
};
+118
View File
@@ -0,0 +1,118 @@
//Sigmoids...
function sigmoid0(x)
{
return x/(1+Math.abs(x));
}
function sigmoid1(x)
{
if (x>=0.0) {
return 1.0
} else {
return 0.0
}
}
function sigmoid2(x)
{
if (x>=0.0){
return x
} else {
return 0.0
}
}
function sigmoid3(x)
{
return x;
}
//DOSE - Doses items for item X slave Y
//Numreq - number of items that are requested for item X for slave Y
//Instore - number of items for item X in master gauge (currently)
//store_last_tick - Number of items for item X in master gauge at time of last tick
//dole - Current dole for item X
//carry - carry for item X slave Y
//prev_req - Numreq for previous request for item X slave Y
//* doleinfo_last - Object with 2 values for last "tick period": numreq - total number or requests for item, numslave - number of slaves requesting that item
//Returns: Array of 3 elements
//0.Number of items to give in that dose
//1.New dole for item X
//2.New carry for item X slave Y
function Dose(numreq,instore,store_last_tick,dole,carry,prev_req,numreq_total_adj)
{
numreq=Number(numreq);
instore=Number(instore);
var instore_adj=instore;
if (instore_adj==0) instore_adj=0.1;
var b=Math.trunc(prev_req/instore_adj*numreq);
var inp1=[numreq,instore_adj,store_last_tick/numreq,dole,carry];//0..4
var inp2=[prev_req/numreq,numreq/numreq_total_adj,store_last_tick/numreq_total_adj,prev_req/instore_adj,b];//5..9
var inputs=inp1.concat(inp2);
var internal=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
var outputs=[0,0,0];
//NN: Please don't touch :), code is generated
outputs[0]=inputs[2]*3.101320028;
outputs[0]=outputs[0]+inputs[7]*0.5471593142;
outputs[0]=outputs[0]+inputs[3]*2.691268921;
outputs[0]=outputs[0]+outputs[1]*-1.64255929;
outputs[0]=outputs[0]+inputs[6]*0.7746122479;
outputs[0]=sigmoid0(outputs[0]);
outputs[1]=inputs[2]*-0.9946503043;
outputs[1]=outputs[1]+inputs[1]*0.2128598392;
outputs[1]=sigmoid0(outputs[1]);
outputs[2]=sigmoid2(outputs[2]);
outputs[0]=Number(outputs[0]);
if (outputs[0]<0) outputs[0]=0;
outputs[0]=outputs[0]*numreq;
var res1=Math.round(outputs[0]);
if (res1>numreq) res1=numreq;
if (res1>instore) res1=instore;
if (res1<0) res1=0;
carry=sigmoid0(outputs[2]);
return [res1,sigmoid0(outputs[1]),carry];
}
//TICK - Updates values for item X, should be called once for each item for each round of all slaves requesting that item
//Instore - number of items for item X in master gauge (currently)
//dole - Current dole for item X
//store_last_tick - Number of items for item X in master gauge at time of last tick (not this one)
//* doleinfo_last - Object with 2 values for last "tick period": numreq - total number or requests for item, numslave - number of slaves requesting that item
//* doleinfo_new - Object with 2 values for new "tick period": numreq - total number or requests for item, numslave - number of slaves requesting that item
//Returns: Array of 2 elements
//0.New dole for item X
//1.Guessed percentage average of items supplied in last 10 ticks
function Tick(instore,dole,store_last_tick,numreq_total_adj)
{
var instore_adj=instore;
if (instore_adj==0) instore_adj=0.1;
var inputs=[instore,dole,store_last_tick/instore_adj,store_last_tick/numreq_total_adj];
var internal=[0,0,0,0,0];
var outputs=[0,0];
//NN: Please don't touch :), code is generated
internal[2]=sigmoid0(internal[2]);
outputs[0]=internal[2]*0.4049594998;
outputs[0]=outputs[0]+1.0*-5.026565075;
outputs[0]=outputs[0]+inputs[0]*0.9100194573;
outputs[0]=outputs[0]+inputs[1]*-1.063795686;
outputs[0]=sigmoid1(outputs[0]);
outputs[1]=outputs[0]*1.049500823;
outputs[1]=outputs[1]*inputs[0]*3.437163115;
outputs[1]=sigmoid0(outputs[1]);
avg=outputs[1];
if (numreq_total_adj<0.11) avg=1;//When no item's are requested, result is 0.1 ; In that case there is no deficit, so return 1 = 100% demand fulfilled
if (avg>1) avg=1;
if (avg<0) avg=0;
return [sigmoid0(outputs[0]),avg];
}
module.exports = {Dose,Tick}
+49
View File
@@ -0,0 +1,49 @@
var forge = require("node-forge");
forge.options.usePureJavaScript = true;
var fs = require("fs");
var mkdirp = require("mkdirp");
var pki = forge.pki;
var rsa = forge.pki.rsa;
var startTime, endTime;
module.exports = function({
bits = 2048,
certificatePath = "database/certificate",
doLogging = false,
} = {}){
function log(t){
if(doLogging) console.log("lib/generateSSLcert.js | "+t);
}
log("Generating SSL certificate with "+bits+" bits at "+certificatePath);
startTime = Date.now();
var keypair = rsa.generateKeyPair({bits: bits, e: 0x10001});
endTime = Date.now();
log("Generated 2048bit keypair in: "+(endTime-startTime)+" ms");
startTime = Date.now();
var cert = pki.createCertificate();
cert.publicKey = keypair.publicKey;
cert.serialNumber = "01";
cert.validity.notBefore = new Date();
cert.validity.notAfter = new Date(Date.now()+(1000*60*60*24*365)); // valid for one year
// self-sign certificate
cert.sign(keypair.privateKey);
// Convert certificate to pem type and write it to a file
if(certificatePath[certificatePath.length-1] != "/") certificatePath += "/";
if(!fs.existsSync(certificatePath)) {
mkdirp.sync(certificatePath);
console.log("Created directory: "+certificatePath);
}
fs.writeFileSync(certificatePath+ "cert.key", pki.privateKeyToPem(keypair.privateKey));
fs.writeFileSync(certificatePath+ "cert.crt", pki.certificateToPem(cert));
endTime = Date.now();
log("Generated certificate in: "+(endTime-startTime)+" ms");
}
+25
View File
@@ -0,0 +1,25 @@
var assert = require("assert");
var fs = require("fs");
var fileOps = require("_app/fileOps");
var generateSSLcert = require("./generateSSLcert.js")
describe("generateSSLcert.js(options)", ()=>{
it("Creates a folder with a .crt and .key file in it", ()=>{
let certPath = "lib/certTestPlsDelete";
assert(!fs.existsSync(certPath)); // if it already exsits, the test might pass because of leftover data (which is bad)
generateSSLcert({
// bits:512, // supported, but I just use the default of 2048 for this test
certificatePath:certPath,
doLogging: false,
});
assert(fs.existsSync(certPath));
assert(fs.existsSync(certPath+"/cert.key")); // private key
assert(fs.existsSync(certPath+"/cert.crt")); // certificate file
// remove certificates and data from the test
fileOps.deleteFolderRecursiveSync(certPath);
}).timeout(10000);
});
+53
View File
@@ -0,0 +1,53 @@
/**
Module for grabbing factorio locale from a local factorio install
@module getFactorioLocale
*/
const fs = require("fs");
const ini = require("ini");
const cache = {};
/**
Gets raw factorio locale from the .ini file.
Not available outside of module through exports.
@memberof getFactorioLocale
@inner
@param {string} factorioDirectory
@param {string} languageCode
@param {function} callback
*/
function getLocale(factorioDirectory, languageCode, callback){
var localeFile = factorioDirectory + '/data/base/locale/' + languageCode + '/base.cfg';
fs.readFile(localeFile, "utf8", (err, rawLocale) => {
callback(err, rawLocale);
});
}
/**
Gets factorios locale as an object, does not respect mods.
@param {string} factorioDirectory
@param {string} languageCode
@param {function} callback
*/
module.exports.asObject = function getLocaleAsObject(factorioDirectory, languageCode, callback){
if(typeof factorioDirectory != "string" || typeof languageCode != "string" || typeof callback != "function"){
throw new Error("Error: wrong parameters provided");
}
if(!cache[languageCode]){
getLocale(factorioDirectory, languageCode, (err, rawLocale) => {
if(err){
callback(err);
} else {
let processedLocale = ini.parse(rawLocale);
cache[languageCode] = processedLocale;
callback(undefined, processedLocale);
}
});
} else {
callback(undefined, cache[languageCode]);
}
}
+74
View File
@@ -0,0 +1,74 @@
const assert = require("assert");
const getFactorioLocale = require("./getFactorioLocale");
describe("getFactorioLocale.js", () => {
describe("asObject()", () => {
it("requires 2 strings and a function as parameters", () => {
let validate = (err) => {
return err == "Error: wrong parameters provided"
}
let message = "getFactorioLocale is responding incorrectly to invalid arguments"
let callback = (err,object) => true;
assert.throws(() => getFactorioLocale.asObject(), validate, message);
assert.throws(() => getFactorioLocale.asObject("hello", "I forgot my callback"), validate, message);
assert.throws(() => getFactorioLocale.asObject(undefined, undefined, callback), validate, message);
// the proper way to do it
assert.doesNotThrow(() => getFactorioLocale.asObject("factorio_0.15.27", "en", callback), validate, message);
});
it("returns a nested object with base game locale information", (done) => {
getFactorioLocale.asObject("factorio", "en", callback);
function callback(err, object){
assert.equal(typeof object, "object");
assert.equal(object["entity-name"]["fish"], "Fish");
assert.equal(object["entity-name"]["small-lamp"], "Lamp");
Object.keys(object).forEach(key => {
// first level of the nested object is always an object
assert.equal(typeof object[key], "object");
Object.keys(object[key]).forEach(key2 => {
// second level of the nested object is always a string, nearly always truthy
if(key2 != "so-long-and-thanks-for-all-the-fish"){
assert.ok(object[key][key2]);
assert.equal(typeof object[key][key2], "string");
}
});
});
done();
}
});
it("has some duplicate keys", (done) => {
getFactorioLocale.asObject("factorio", "en", callback);
function callback(err, object){
let arrayOfKeys = [];
// collect all keys in a single array
Object.keys(object).forEach(key => {
arrayOfKeys.push(key);
Object.keys(object[key]).forEach(key2 => {
arrayOfKeys.push(key2);
});
});
// check for duplicates
let duplicateKeys = [];
let i = 0;
arrayOfKeys.forEach(key => {
let o = 0;
arrayOfKeys.forEach(key2 => {
if(i != o && key == key2){
duplicateKeys.push(key);
}
o++
});
i++;
});
// we got 704 but this is a very unexact number
assert(duplicateKeys.length > 10)
done();
}
});
});
});
+17
View File
@@ -0,0 +1,17 @@
const fileOps = require("_app/fileOps");
// const config = require("./../config");
module.exports = function(config) {
return {
sync: function(){
let instances;
try {
instances = fileOps.getDirectoriesSync(config.instanceDirectory);
} catch (e) {
// there are no instances created yet, probably missing the folder
instances = [];
}
return instances;
},
}
}
+69
View File
@@ -0,0 +1,69 @@
/**
Provides tools for managing both masters and slaves config files.
@module configManager
*/
// require modules
const asTable = require("as-table").configure({delimiter: ' | '});
const fs = require("fs");
// internal requires
var config = require("./../../config");
const objectOps = require("./../objectOps.js");
/**
console.log()s each entry in the config
@param {string} instance Either undefined, "shared" or the instance to show the config for.
*/
module.exports.displayConfig = function(instance){
console.log(asTable(this.getConfig(instance)));
}
module.exports.getConfig = async function(instance){
if(!instance || instance == "shared"){
let configArray = [];
Object.keys(config).forEach(key=>{
if(key != "__comment"){
if(typeof config[key] == "object"){
configArray.push({entry: key, value: JSON.stringify(config[key])});
} else configArray.push({entry: key, value: config[key]});
}
});
return configArray;
}
}
module.exports.editConfig = function(instance, entry, newValue){
if(!instance || instance == "shared"){
if(config[entry] && newValue){
if(objectOps.isJSON(newValue)){
config[entry] = JSON.parse(newValue);
console.log("Changed config entry "+entry+" from "+JSON.stringify(config[entry])+" to "+JSON.stringify(newValue));
// console.log("new config file:");
// console.log(JSON.stringify(config, null, " "))
fs.writeFileSync("config.json", JSON.stringify(config, null, 4));
return {
ok:true,
msg:`Changed config entry ${entry} from Object to ${JSON.stringify(newValue)}`,
}
} else {
config[entry] = newValue;
console.log("Changed config entry "+entry+" from "+config[entry]+" to '"+newValue+"'");
// console.log("new config file:");
// console.log(JSON.stringify(config, null, " "))
fs.writeFileSync("config.json", JSON.stringify(config, null, 4));
return {
ok:true,
msg:`Changed config entry ${entry} from ***** to '${newValue}'`,
};
}
} else {
return {
ok:false,
msg:`No such config parameter`,
}
}
} else {
return {
ok:false,
msg:`Editing config for single instances is not yet supported`,
}
}
};
View File
Whitespace-only changes.
+2
View File
@@ -0,0 +1,2 @@
const fs = require("fs-extra");
const needle = require("needle");
+278
View File
@@ -0,0 +1,278 @@
/**
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");
const fs = require("fs-extra");
const https = require('follow-redirects').https;
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 config = require("./../../config");
function ellipse(str, max){
return str.length > (max - 3) ? str.substring(0,max-3) + '...' : str;
}
// https://stackoverflow.com/questions/15900485/correct-way-to-convert-size-in-bytes-to-kb-mb-gb-in-javascript
function formatBytes(a,b){if(0==a)return"0 Bytes";var c=1024,d=b||2,e=["Bytes","KB","MB","GB","TB","PB","EB","ZB","YB"],f=Math.floor(Math.log(a)/Math.log(c));return parseFloat((a/Math.pow(c,f)).toFixed(d))+" "+e[f]};
module.exports = function(config){
if(!config) config = require("./../../config");
const instances = require("./../getInstances.js")(config).sync();
return {
/**
Lists mods for an instance as a table. Warning, handles printing internally.
@param {string} instance - Name of the instance or "shared"
@example
var modManager = require("modManager")(config);
modManager.listMods("testInstance");
// console.logs table of mods installed on instance
modManager.listMods("shared");
// console.logs mods located in /sharedMods as a nice table
*/
listMods: function(instance){
return new Promise((resolve, reject) => {
if(instance && instances.includes(instance)){
factorioAPI.init(false, config.instanceDirectory+'/'+instance+'/instanceMods', config.instanceDirectory+'/'+instance+'/saves', config.factorio_version);
factorioAPI.authenticate({
username: config.username,
token: config.token,
require_ownership: false
}).then(token => {
console.log("* Currently installed:");
factorioAPI.readModZips().then(list => {
console.log(asTable(list.map(mod => {
return {
name:(mod.title || mod.name),
version:mod.version,
date:mod.date,
factorio_ver:mod.factorio_version,
author:mod.author,
}
})));
});
// Had issues earlier due to bug in node-factorio-api, see https://github.com/Danacus/node-factorio-api/issues/2
// bug is resolved, thanks to @Danacus and @brotherpot
factorioAPI.getModsFromSaves().then(list => {
console.log("* Mods used in save:");
console.log(asTable(list[0].mods))
});
}).catch(reason => {
console.log("Failed to authenticate to factorio.com! Please check your connection and the username+token used for authentication in config.json");
resolve();
});
} else {
// list shared mods in /sharedMods/
factorioAPI.init(false, 'sharedMods/', '', config.factorio_version);
factorioAPI.authenticate({
username: config.username,
token: config.token,
require_ownership: false
}).then(() => {
console.log("\r\n")
console.log("* Mods shared by all instances:")
factorioAPI.getInstalledMods().then(mods => {
console.log(asTable(mods));
resolve();
});
}).catch(e => {
reject(new Error(e));
});
}
// get JSON data about GH releases
/*let res = syncRequest('GET', 'https://api.github.com/repos/Danielv123/factorioClusterioMod/releases', {"headers":{"User-Agent":"Fuck you for requiring user agents!"}});
let url = JSON.parse(res.getBody())[0].assets[0].browser_download_url;
let name = JSON.parse(res.getBody())[0].assets[0].name;
console.log(JSON.parse(res.getBody()))
*/
});
},
/**
Searches mods.factorio.com for mods matching a search term and prints
the results to console as a neat looking table
@param {string} searchTerm What to search for
*/
findMods: function(searchTerm){
return new Promise((resolve, reject) => {
factorioAPI.init(false);
factorioAPI.authenticate({
username: config.username,
token: config.token,
require_ownership: false
}).then(token => {
factorioAPI.searchMods(searchTerm).then(mods => {
let result = asTable(mods.reverse().map(mod => {
return {
title:ellipse(mod.title, 50),
name:ellipse(mod.name, 30),
downloads:mod.downloads_count,
updated: moment(mod.updated_at).format("DD.MM.YY"),
latest_version: mod.latest_release.version,
owner:mod.owner,
//license: mod.license_name,
}
}));
resolve(result);
// console.log(result);
});
}).catch(e => {
if(e.contains("Insufficient information")) console.log("We are missing some parameters required for logging in! Make sure your factorio username and token are specified in config.json.");
reject(new Error(e));
});
});
},
addMod: function(term, instance){
return new Promise((resolve, reject) => {
if(instance == "shared"){ // modDirectory has to end in /
var modDirectory = "sharedMods/";
} else {
var modDirectory = config.instanceDirectory+"/"+instance+"/instanceMods/";
}
if(term && term.length > 0){
if(term.includes("mods.factorio.com/mods/")){ // https://mods.factorio.com/mods/Danielv123/ChooChoo
// treat it as a link download
factorioAPI.init(false, modDirectory, "", config.factorio_version);
factorioAPI.authenticate({
username: config.username,
token: config.token,
require_ownership: false
}).then(() => {
factorioAPI.searchMods(term.split("/")[term.split("/").length-1]).then(body => {
console.log("Im gonna download: "+body[0].name + " in version: " + body[0].latest_release.version);
factorioAPI.downloadMod( {name: body[0].name, version: body[0].latest_release.version}).then(()=>{
console.log("Mod downloaded");
resolve();
});
});
});
} else if(term.includes("github.com")){ // https://github.com/Danielv123/factorioClusterioMod
// treat it as a github releases download
let apiURL = "https://api.github.com/repos/" + term.split("/")[3] + "/" + term.split("/")[4] + "/releases"; // https://api.github.com/repos/Danielv123/factorioClusterioMod/releases
let res = syncRequest('GET', apiURL, {"headers":{"User-Agent":"Fuck you for requiring user agents!"}});
let url = JSON.parse(res.getBody())[0].assets[0].browser_download_url;
let name = JSON.parse(res.getBody())[0].assets[0].name;
if(url) {
console.log("Downloading " + url);
let file = fs.createWriteStream(modDirectory + name);
let bytesDownloaded = 0;
let downloadStartTime = Date.now();
https.get(url, function(response) {
let bytesToDownload = response.headers['content-length'];
response.pipe(file);
response.on("data", chunk => {
bytesDownloaded += chunk.length;
let downloadTimeElapsed = Date.now() - downloadStartTime;
let bytesPerSecond = bytesDownloaded / (downloadTimeElapsed/1000);
let timeToComplete = Math.floor((bytesToDownload - bytesDownloaded)/bytesPerSecond);
sLog("Downloading "+name+" "+(bytesDownloaded/bytesToDownload*100).toPrecision(3)+"% ("+formatBytes(bytesDownloaded)+" / "+formatBytes(bytesToDownload)+")\r\n"+
"about "+timeToComplete+" seconds remaining ("+formatBytes(bytesPerSecond)+"/s)");
}).on("end", x => {
sLog.clear();
console.log(" Downloaded in "+moment.utc(moment.duration(Date.now()-downloadStartTime).as('milliseconds')).format('mm:ss')); // because sLog.clear() doesn't seem to do the job of ending the line properly
resolve();
});
});
}
} else {
// treat it as a search and download the top result
factorioAPI.init(false, modDirectory, "", config.factorio_version);
factorioAPI.authenticate({
username: config.username,
token: config.token,
require_ownership: false
}).then(() => {
factorioAPI.searchMods(term).then(body => {
console.log(body)
console.log("Im gonna download: "+body[0].name + " in version: " + body[0].latest_release.version);
factorioAPI.downloadMod( {name: body[0].name, version: body[0].latest_release.version}).then(()=>{
console.log("Mod downloaded");
resolve();
});
});
});
}
} else {
reject(new Error("Invalid search term"));
}
});
},
removeMod: function removeMod(term, instance, force = false){
return new Promise((resolve, reject) => {
if(instance == "shared"){ // modDirectory has to end in /
var modDirectory = "sharedMods/";
} else {
var modDirectory = config.instanceDirectory+"/"+instance+"/instanceMods/";
}
if(term && typeof term == "string" && term.length > 0){
let mods = fs.readdirSync(modDirectory);
let modsMatchingFilter = mods.filter(mod => mod.toLowerCase().includes(term.toLowerCase()));
if(modsMatchingFilter.length < 1){
console.log("No mods found matching search");
} else if(modsMatchingFilter.length == 1){
console.log("Deleting "+modsMatchingFilter[0]);
fs.unlinkSync(modDirectory+modsMatchingFilter[0]);
console.log("Deleted.");
} else if(modsMatchingFilter.length > 1){
console.log("Found multiple mods");
modsMatchingFilter.forEach(mod => console.log(mod));
if(query("Delete all mods matching filter?")){
let modsToDelete = modsMatchingFilter.length;
for(let i = 0; i < modsToDelete; i++){
fs.unlinkSync(modDirectory+modsMatchingFilter[i]);
console.log("Deleted "+modsMatchingFilter[i]);
}
} else console.log("Did not delete mods.")
}
resolve();
} else {
reject(new Error("Invalid search term"));
}
});
},
// 'https://api.github.com/repos/Danielv123/factorioClusterioMod/releases'
/**
Download mod from github repo by looking for the first binary download in
the latest release, saving it to /sharedMods
@param {string} url a github repo URL, for example https://api.github.com/repos/Danielv123/factorioClusterioMod/releases. Must link to /releases
*/
downloadFromGitURL: function(url){
console.log("Downloading mod from "+url);
// get JSON data about releases
let res = syncRequest('GET', url, {"headers":{"User-Agent":"Please don't require setting a user agent"}});
let downloadUrl = JSON.parse(res.getBody())[0].assets[0].browser_download_url;
let name = JSON.parse(res.getBody())[0].assets[0].name;
console.log(JSON.parse(res.getBody()))
if(downloadUrl) {
console.log(downloadUrl);
let file = fs.createWriteStream("sharedMods/"+name);
https.get(downloadUrl, function(response) {
response.pipe(file);
console.log("Downloaded "+name);
});
}
},
}
}
/*
module.exports.findUpdates = function(){
console.log("Downloading mods...");
// get JSON data about releases
let res = syncRequest('GET', 'https://api.github.com/repos/Danielv123/factorioClusterioMod/releases', {"headers":{"User-Agent":"Fuck you for requiring user agents!"}});
let url = JSON.parse(res.getBody())[0].assets[0].browser_download_url;
let name = JSON.parse(res.getBody())[0].assets[0].name;
console.log(JSON.parse(res.getBody()))
}*/
View File
Whitespace-only changes.
+116
View File
@@ -0,0 +1,116 @@
const fs = require("fs-extra");
const path = require("path");
const git = require("simple-git")("./sharedPlugins");
module.exports = function(config){
if(!config) config = require("./../../config");
return {
listPlugins: async function(){
let plugins = await this.getPlugins();
plugins.forEach(plug => {
console.log(`${plug.name}`.padEnd(22).substr(0,22)+` - ${plug.description}`.substr(0,80-22));
});
},
getPlugins: async function(){
let directories = await fs.readdir("./sharedPlugins");
let returnArray = [];
for(let i in directories){
let dir = directories[i];
let pluginContents = await fs.readdir(path.join("./sharedPlugins", dir));
// console.log(pluginContents)
if(pluginContents.includes("config.js") || pluginContents.includes("config.json")){
try{
var pluginConfig = require(path.join("./../../sharedPlugins", dir, "config"));
pluginConfig.pluginPath = path.join("./sharedPlugins", dir);
}catch(e){
console.log(e)
}
// check if plugin is enabled
try{
await fs.stat(path.join(pluginConfig.pluginPath, "DISABLED"))
pluginConfig.enabled = false;
}catch(e){
pluginConfig.enabled = true;
}
returnArray.push(pluginConfig);
}
}
return returnArray;
},
addPlugin: function(path){
return new Promise((resolve, reject) => {
if(path.includes("git")){ // we are decently sure this is a git repo
git.clone(path, data => resolve(data))
}
});
},
removePlugin: async function(name){
let plugins = await this.getPlugins();
for(let i in plugins){
let plug = plugins[i];
if(plug.name.includes(name)){
// console.log(`Removing plugin ${plug.name}`);
await fs.remove(plug.pluginPath);
return {
ok:true,
msg:`Deleted plugin ${plug.name}`
}
}
}
return {
ok:false,
msg:`Plugin matching "${name}" not found`,
}
},
enablePlugin: async function(name, instance){
let plugins = await this.getPlugins();
let plugin = plugins[findInArray("name", name, plugins)[0]];
if(plugin){
let files = await fs.readdir(plugin.pluginPath);
if(files.includes("DISABLED")){
await fs.unlink(path.join(plugin.pluginPath, "DISABLED"));
return {
ok:true,
msg:`Globally enabled plugin ${name}`,
}
} else {
return {
ok:true,
msg:`Plugin ${name} already enabled`,
}
}
} else {
return {
ok:false,
msg:`Found no excact match for plugin ${name}`,
}
}
},
disablePlugin: async function(name, instance){
let plugins = await this.getPlugins();
let plugin = plugins[findInArray("name", name, plugins)[0]];
if(plugin){
await fs.writeFile(path.join(plugin.pluginPath, "DISABLED"), "");
return {
ok:true,
msg:`Plugin ${name} is now disabled on this host`,
}
} else {
return {
ok:false,
msg:`Found no excact match for plugin ${name}`,
}
}
}
}
}
function findInArray(key, value, array){
let indexes = [];
for(let i in array){
if(array[i][key] && array[i][key] === value) indexes.push(i);
}
return indexes;
}
+136
View File
@@ -0,0 +1,136 @@
var mkdirp = require("mkdirp");
var http = require('http');
var https = require('https');
var fs = require('fs');
var modManager = require("./manager/modManager.js");
// modManager.addMod("clusterio", "shared");
function makeDirectory(path){
return new Promise((resolve, reject) => {
mkdirp(path, function(err){
if(err){
console.error("Unable to mkdirp "+path);
console.error(err);
resolve(false);
} else {
console.log("mkdirp "+path);
resolve(true);
}
});
});
}
function download(url, file, callback){
return new Promise((resolve, reject) => {
let pathArray = file.split("/");
pathArray.pop(); // remove filename from the end
if(pathArray.length >= 1){
// this might be an invalid path!
fs.stat(pathArray.join("/"), (err, stat) => {
if(err){
makeDirectory(pathArray.join("/")).then(()=>{
doDownload(url, file, callback);
});
} else {
doDownload(url, file, callback);
}
});
} else {
doDownload(url, file, callback);
}
function doDownload(url, file, callback){
var writeStream = fs.createWriteStream(file);
let protocol = http;
if(url.split(":")[0] == "https") protocol = https;
var request = protocol.get(url, function(response) {
response.pipe(writeStream);
response.on("end", () => {
if(callback && typeof callback == "function") callback(file);
console.log("Downloaded "+file+" from "+url);
resolve();
});
});
}
});
}
var directories = [
"database/linvodb",
"sharedMods",
"instances",
];
let downloadList = [
// {
// url: "http://fbpviewer.trakos.pl/images/spritesheet.json",
// file: "static/pictures/spritesheet.js",
// callback: function(file){
// fs.writeFileSync("static/pictures/spritesheet.js", "export default\n"+fs.readFileSync("static/pictures/spritesheet.js"));
// },
// },
// {
// url: "http://fbpviewer.trakos.pl/images/spritesheet.png",
// file: "static/pictures/spritesheet.png",
// },
{
url: "https://code.jquery.com/jquery.js",
file: "static/external/jquery.js",
},
{
url: "https://code.jquery.com/ui/1.12.1/jquery-ui.js",
file: "static/external/jquery-ui.js",
},
{
url: "https://cdnjs.cloudflare.com/ajax/libs/canvasjs/1.7.0/canvasjs.js",
file: "static/external/canvasjs.js",
},
{
url: "https://momentjs.com/downloads/moment.min.js",
file: "static/external/moment.min.js",
},
{
url: "https://craig.global.ssl.fastly.net/js/mousetrap/mousetrap.min.js",
file: "static/external/mousetrap.min.js",
},
{
url: "https://fonts.googleapis.com/css?family=Roboto",
file: "static/external/css/Roboto.css",
},
{
url: "https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.0.4/socket.io.js",
file: "static/external/socket.io.js",
},
]
// initiate folder creations and downloads
processMkdirRecursive(processDownloadRecursive);
function processMkdirRecursive(callback){
let item = directories.shift();
makeDirectory(item).then(()=>{
if(directories.length == 0){
if(callback) callback();
} else {
processMkdirRecursive(callback);
}
});
}
function processDownloadRecursive(callback){
let item = downloadList.shift();
download(item.url, item.file, item.callback).then(()=>{
if(downloadList.length == 0){
if(callback) callback();
} else {
processDownloadRecursive(callback);
}
});
}
// generate SSL certificates if they are missing
if(!fs.existsSync("database/certificates") || !fs.existsSync("database/certificates/cert.crt") || !fs.existsSync("database/certificates/cert.key")){
var generateSSLcert = require("./generateSSLcert.js");
generateSSLcert({
certificatePath: "database/certificates",
doLogging: true,
});
}
+44
View File
@@ -0,0 +1,44 @@
/**
Helper functions related to objects
@module objectOps
*/
/**
Deep clones an object, is actually just JSON.parse(JSON.stringify()) but looks cleaner.
@param {object} obj to clone
@returns {object}
@example
a = {"thingy":"with a value"};
b = a;
a == b; // true
c = objectOps.deepclone(a);
a == c; // false
*/
module.exports.deepclone = function(obj){
return JSON.parse(JSON.stringify(obj));
}
/**
Checks if a string is valid JSON using try/catch. If passed an object it will always return true.
@param {string} string to verify
@returns {boolean}
@example
objectOps.isJSON("99 bottles of beer"); // false
objectOps.isJSON("99"); // true
objectOps.isJSON({"thingy":"that is an object"}); // true
objectOps.isJSON(JSON.stringify({"thingy":"that is an object"})); // true
*/
module.exports.isJSON = function(string){
let stringIsJson = false;
let x
try {
x = JSON.parse(string);
} catch (e){
return false;
}
if(typeof x == "object" || typeof string == "object"){
stringIsJson = true;
}
return stringIsJson;
}
+40
View File
@@ -0,0 +1,40 @@
var assert = require('assert');
var objectOps = require('./objectOps');
describe("objectOps.js", function(){
/*describe("objectOps.clone()", function(){
it("clones javascript objects", function(){
var object1 = {hello:"world", cat:{legs:4, name:"Kitty", colors:["brown", "yellow", "purple"]}};
var object2 = {};
object2 = objectOps.clone(object1);
assert.equal(object2.cat.name, object1.cat.name)
assert.equal(object1.hello, object2.hello)
});
it("does not deep clone objects", function(){
var obj1 = {hello:"world", cat:{legs:4, name:"Kitty", colors:["brown", "yellow", "purple"]}};
var obj2 = {};
obj2 = objectOps.clone(obj1);
obj1.cat.colors = "black";
assert.equal(obj1.cat.colors, "black");
assert.notEqual(obj2.cat.colors[1], "yellow");
});
});*/
describe("objectOps.deepclone()", function(){
it("deep clones javascript objects", function(){
var obj1 = {hello:"world", cat:{legs:4, name:"Kitty", colors:["brown", "yellow", "purple"]}};
var obj2 = {};
obj2 = objectOps.deepclone(obj1);
obj1.cat.colors = "black";
assert.equal(obj1.cat.colors, "black");
assert.equal(obj2.cat.colors[1], "yellow");
});
it("throws on non JSON parameters", function(){
assert.throws(function(){
let y = objectOps.deepclone(objectOps.deepclone);
});
});
});
});
+153
View File
@@ -0,0 +1,153 @@
--[[
Copyright 2018 Chrisgbk
Permission is hereby granted, free of charge, to any person obtaining a copy of this software 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.
]]
-- MIT License, https://opensource.org/licenses/MIT
-- Hotpatchable control.lua for scenarios
-- Supports multiple simultaneously loaded softmods
-- This is a WIP
-- Version 1.1 alpha
-- probably some performance improvements to be made
_ENV.debug_settings = {
level = 'info',
log_to_console_only = false,
log_to_RCON = false,
log_on_tick = false,
}
local hotpatch_tools = require 'hotpatch.mod-tools'
require 'hotpatch.remote-interface'
require 'hotpatch.commands'
require 'hotpatch.gui'
-- mod code goes here
-- This is the Factorio Freeplay scenario
-- This code, and the freeplay locale files in the zip file, is the property of Wube
-- 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', [===[
local util = require("util")
local silo_script = require("silo-script")
local created_items = function()
return
{
["iron-plate"] = 8,
["wood"] = 1,
["pistol"] = 1,
["firearm-magazine"] = 10,
["burner-mining-drill"] = 1,
["stone-furnace"] = 1
}
end
local respawn_items = function()
return
{
["pistol"] = 1,
["firearm-magazine"] = 10
}
end
for k,v in pairs(silo_script.get_events()) do
script.on_event(k, v)
end
script.on_event(defines.events.on_player_created, function(event)
local player = game.players[event.player_index]
util.insert_safe(player, global.created_items)
local r = global.chart_distance or 200
player.force.chart(player.surface, {{player.position.x - r, player.position.y - r}, {player.position.x + r, player.position.y + r}})
if not global.skip_intro then
if game.is_multiplayer() then
player.print({"msg-intro"})
else
game.show_message_dialog{text = {"msg-intro"}}
end
end
silo_script.on_event(event)
end)
script.on_event(defines.events.on_player_respawned, function(event)
local player = game.players[event.player_index]
util.insert_safe(player, global.respawn_items)
silo_script.on_event(event)
end)
script.on_configuration_changed(function(event)
global.created_items = global.created_items or created_items()
global.respawn_items = global.respawn_items or respawn_items()
silo_script.on_configuration_changed(event)
end)
script.on_load(function()
silo_script.on_load()
end)
script.on_init(function()
global.created_items = created_items()
global.respawn_items = respawn_items()
silo_script.on_init()
end)
silo_script.add_remote_interface()
silo_script.add_commands()
remote.add_interface("freeplay",
{
get_created_items = function()
return global.created_items
end,
set_created_items = function(map)
global.created_items = map
end,
get_respawn_items = function()
return global.respawn_items
end,
set_respawn_items = function(map)
global.respawn_items = map
end,
set_skip_intro = function(bool)
global.skip_intro = bool
end,
set_chart_distance = function(value)
global.chart_distance = tonumber(value)
end
})
]===])
-- Multi-file test
--[[
local files = {}
files['testfolder.anothertest.test'] = [===[
script.on_event(defines.events.on_player_changed_position, function(e) game.print('position changed') end)
]===]
files['testfolder.test'] = [===[
require 'anothertest.test'
]===]
files['test'] = [===[
require 'testfolder.test'
]===]
hotpatch_tools.static_mod('require-test', '1.0.0', [===[
require 'test'
]===], files)
--]]
-- end of mod code
@@ -0,0 +1,135 @@
local hotpatch_tools = require 'hotpatch.mod-tools'
hotpatch_tools.static_mod('hotpatch-commands', '1.0.1', [===[
--[[
Copyright 2018 Chrisgbk
Permission is hereby granted, free of charge, to any person obtaining a copy of this software 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.
]]
-- MIT License, https://opensource.org/licenses/MIT
local hotpatch_tools = require 'hotpatch.mod-tools'
--load private API
-- mod installation/uninstallation support functions
-- These take a mod NAME as a first argument
local install_mod = hotpatch_tools.install_mod
local find_installed_mod = hotpatch_tools.find_installed_mod
local install_mod_file = hotpatch_tools.install_mod_file
local uninstall_mod = hotpatch_tools.uninstall_mod
-- mod interaction functions
-- These take a LOADED INDEX as a first argument, except load_mod, which takes an INSTALLED INDEX
local load_mod = hotpatch_tools.load_mod
local find_loaded_mod = hotpatch_tools.find_loaded_mod
local run_mod = hotpatch_tools.run_mod
local reset_mod = hotpatch_tools.reset_mod
local reset_mod_events = hotpatch_tools.reset_mod_events
local register_mod_events = hotpatch_tools.register_mod_events
local unload_mod = hotpatch_tools.unload_mod
-- internal callbacks when a mod registers events
local register_event = hotpatch_tools.register_event
local register_nth_tick = hotpatch_tools.register_nth_tick
local register_on_tick = hotpatch_tools.register_on_tick
-- mod bootstrap functions
-- These take a LOADED INDEX as a first argument
local mod_on_init = hotpatch_tools.mod_on_init
local mod_on_load = hotpatch_tools.mod_on_load
local mod_on_configuration_changed = hotpatch_tools.mod_on_configuration_changed
local static_mods = hotpatch_tools.static_mods
local console = hotpatch_tools.console
local debug_log = hotpatch_tools.debug_log
local loaded_mods = hotpatch_tools.loaded_mods
local installed_mods = hotpatch_tools.installed_mods
local help_commands = {
help = '/hotpatch help\nYou got this far, you probably know all you need to know',
list = '/hotpatch list [type]\nLists \'installed\', \'loaded\' or \'running\' mods',
}
local sub_commands
sub_commands = {
help = function(player_index, param)
local caller = (player_index and game.players[player_index]) or console
if (not param) or (param == '') then
caller.print('Usage: /hotpatch [command] [parameters]')
caller.print('Available commands:')
for k, _ in pairs(sub_commands) do caller.print(k) end
caller.print('Use /hotpatch help [command] for help on a particular command')
else
local text = help_commands[param]
if text then
caller.print(text)
end
end
end,
list = function(player_index, param)
local caller = (player_index and game.players[player_index]) or console
if not param or (param == '') then
caller.print('Installed mods:')
for k, v in pairs(installed_mods) do
caller.print(table.concat{'[', k, '] ', v.name, ' ', v.version})
end
caller.print('Loaded/Running mods:')
for k, v in pairs(loaded_mods) do
caller.print(table.concat{'[', k, '] ', v.name, ' ', v.version, ' ', (v.loaded and 'loaded') or 'not loaded', ' ', (v.running and 'running') or 'not running'})
end
else
end
end,
}
local admin_commands = {
reinstall = function(player_index, param)
local caller = (player_index and game.players[player_index]) or console
local mod
local installed_index
local loaded_index
for i = 1, #static_mods do
mod = static_mods[i]
if mod.name:match('^hotpatch%-.*$') then
loaded_index = find_loaded_mod(mod.name)
if loaded_index then
unload_mod(loaded_index)
end
install_mod(mod.name, mod.version, mod.code, mod.files)
installed_index = find_installed_mod(mod.name)
load_mod(installed_index)
loaded_index = find_loaded_mod(mod.name)
run_mod(loaded_index)
mod_on_init(loaded_index)
end
end
end,
}
_ENV.commands.add_command('hotpatch', 'Commands for hotpatch. Run /hotpatch help for details.', function(e)
local caller = (e.player_index and game.players[e.player_index]) or console
if (not e.parameter) or (e.parameter == '') then
sub_commands['help'](e.player_index)
return
end
local sub_command, rest = e.parameter:match('(.-)%s(.*)')
if not sub_command then
sub_command = e.parameter
end
local f = sub_commands[sub_command]
if f then f(e.player_index, rest) end
if caller.admin then
f = admin_commands[sub_command]
if f then f(e.player_index, rest) end
end
end)
--]===])
return true
+377
View File
@@ -0,0 +1,377 @@
local hotpatch_tools = require 'hotpatch.mod-tools'
hotpatch_tools.static_mod('hotpatch-gui', '1.0.4', [===[
--[[
Copyright 2018 Chrisgbk
Permission is hereby granted, free of charge, to any person obtaining a copy of this software 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.
]]
-- MIT License, https://opensource.org/licenses/MIT
local hotpatch_tools = require 'hotpatch.mod-tools'
local mod_gui = require 'mod-gui'
--load private API
-- mod installation/uninstallation support functions
-- These take a mod NAME as a first argument
local install_mod = hotpatch_tools.install_mod
local find_installed_mod = hotpatch_tools.find_installed_mod
local install_mod_file = hotpatch_tools.install_mod_file
local uninstall_mod = hotpatch_tools.uninstall_mod
-- mod interaction functions
-- These take a LOADED INDEX as a first argument, except load_mod, which takes an INSTALLED INDEX
local load_mod = hotpatch_tools.load_mod
local find_loaded_mod = hotpatch_tools.find_loaded_mod
local run_mod = hotpatch_tools.run_mod
local reset_mod = hotpatch_tools.reset_mod
local reset_mod_events = hotpatch_tools.reset_mod_events
local register_mod_events = hotpatch_tools.register_mod_events
local unload_mod = hotpatch_tools.unload_mod
-- internal callbacks when a mod registers events
local register_event = hotpatch_tools.register_event
local register_nth_tick = hotpatch_tools.register_nth_tick
local register_on_tick = hotpatch_tools.register_on_tick
-- mod bootstrap functions
-- These take a LOADED INDEX as a first argument
local mod_on_init = hotpatch_tools.mod_on_init
local mod_on_load = hotpatch_tools.mod_on_load
local mod_on_configuration_changed = hotpatch_tools.mod_on_configuration_changed
local static_mods = hotpatch_tools.static_mods
local console = hotpatch_tools.console
local debug_log = hotpatch_tools.debug_log
local loaded_mods = hotpatch_tools.loaded_mods
local installed_mods = hotpatch_tools.installed_mods
script.on_event(defines.events.on_player_joined_game, 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 button = top['hotpatch-button']
if button then
button.destroy()
end
local menu = left['hotpatch-menu']
if menu then
menu.destroy()
end
local IDE = center['hotpatch-IDE']
if IDE then
IDE.destroy()
end
local main = center['hotpatch-main']
if main then
main.destroy()
end
button = top.add{type = 'sprite-button', name = 'hotpatch-button', sprite='utility/heat_exchange_indication', tooltip = 'Hotpatch', style = mod_gui.button_style}
end)
local on_gui_click_handlers
local on_gui_selection_state_changed_handlers
on_gui_click_handlers = {
['hotpatch-button'] = function(e)
--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
local menu = left['hotpatch-menu']
if not menu then
menu = left.add{type = 'frame', name = 'hotpatch-menu', direction = 'vertical'}
menu.add{type = 'button', name = 'hotpatch-menu.IDE', caption = 'Mod IDE', tooltip = 'Open debugging GUI'}
menu.add{type = 'button', name = 'hotpatch-menu.console', caption = 'Mod Console', tooltip = 'Open debugging console'}
menu.visible = true
return
end
menu.visible = not menu.visible
end,
['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,
['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}
local top_flow = IDE.add{type = 'flow', name = 'hotpatch-IDE-top', direction = 'horizontal'}
top_flow.add{type = 'label', name = 'hotpatch-IDE-mod-label', caption = 'Mod: '}
local IDE_dropdown = top_flow.add{type = 'drop-down', name = 'hotpatch-IDE-mod-selector'}
top_flow.add{type = 'label', name = 'hotpatch-IDE-mod-version', caption = 'Version: No mod selected'}
local IDE_table = IDE.add{type = 'table', name = 'hotpatch-IDE-table', column_count = 2}
local files = IDE_table.add{type = 'scroll-pane', name = 'hotpatch-IDE-files', direction='vertical'}
files.style.height = 600
files.vertical_scroll_policy = 'always'
files = files.add{type = 'table', name = 'hotpatch-IDE-files-table', column_count = 1}
local code = IDE_table.add{type = 'text-box', name = 'hotpatch-IDE-code'}
code.word_wrap = true
files.style.width = 400
files.style.cell_padding = 0
files.style.top_padding = 0
files.style.bottom_padding = 0
code.style.width = 600
code.style.height = 600
IDE.visible = false
end
local IDE_dropdown = IDE['hotpatch-IDE-top']['hotpatch-IDE-mod-selector']
IDE_dropdown.clear_items()
for _, v in ipairs(installed_mods) do
IDE_dropdown.add_item(v.name)
end
IDE.visible = not IDE.visible
if IDE.visible then
player.opened = IDE
else
player.opened = nil
end
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']
if not hotpatch_console then
hotpatch_console = center.add{type = 'frame', name = 'hotpatch-console', direction = 'vertical', caption = 'Hotpatch Console'}
local top_flow = hotpatch_console.add{type = 'flow', name = 'hotpatch-console-top', direction = 'horizontal'}
top_flow.add{type = 'label', name = 'hotpatch-console-mod-label', caption = 'Mod: '}
local console_dropdown = top_flow.add{type = 'drop-down', name = 'hotpatch-console-mod-selector'}
top_flow.add{type = 'label', name = 'hotpatch-console-mod-version', caption = 'Version: No mod selected'}
local bottom_flow = hotpatch_console.add{type = 'flow', name = 'hotpatch-console-bottom', direction = 'vertical'}
local output = bottom_flow.add{type = 'text-box', name = 'hotpatch-console-output'}
local bottom_input_flow = bottom_flow.add{type = 'flow', name = 'hotpatch-console-bottom-input', direction = 'horizontal'}
local input = bottom_input_flow.add{type = 'textfield', name = 'hotpatch-console-input'}
bottom_input_flow.add{type = 'button', name = 'hotpatch-console-run', caption = 'Run', tooltip = 'Run Lua Code'}
output.style.width = 800
output.style.height = 600
input.style.width = 600
hotpatch_console.visible = false
end
local console_dropdown = hotpatch_console['hotpatch-console-top']['hotpatch-console-mod-selector']
console_dropdown.clear_items()
for _, v in ipairs(loaded_mods) do
console_dropdown.add_item(v.name)
end
hotpatch_console.visible = not hotpatch_console.visible
if hotpatch_console.visible then
player.opened = hotpatch_console
else
player.opened = nil
end
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']
local output = hotpatch_console['hotpatch-console-bottom']['hotpatch-console-output']
local input = hotpatch_console['hotpatch-console-bottom']['hotpatch-console-bottom-input']['hotpatch-console-input']
local console_dropdown = hotpatch_console['hotpatch-console-top']['hotpatch-console-mod-selector']
if not player.admin then
output.text = table.concat{output.text, '\n', 'Only admins!'}
return
end
if not (console_dropdown.selected_index > 0) then
output.text = table.concat{output.text, '\n', 'Select a mod!'}
return
end
local mod_name = console_dropdown.items[console_dropdown.selected_index]
local loaded_index = find_loaded_mod(mod_name)
local env = loaded_mods[loaded_index].env
local old_print = env.print
local old_log = env.log
env.print = function(...)
--local count = select('#', ...)
local t = table.pack(...)
for k, v in pairs(t) do
t[k] = tostring(v)
end
local text = table.concat(t, '\t')
output.text = table.concat{output.text, '\n', text}
end
env.log = function(text)
output.text = table.concat{output.text, '\n', text}
end
local success
local code, err = load(input.text, input.text, 't', env)
if code then
success, err = pcall(code)
if not success then
output.text = table.concat{output.text, '\n', err}
end
else
output.text = table.concat{output.text, '\n', err}
end
env.print = old_print
env.log = old_log
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
local previous
for _, v in pairs(center['hotpatch-IDE']['hotpatch-IDE-table']['hotpatch-IDE-files']['hotpatch-IDE-files-table'].children) do
if table.compare(v.style.font_color or {}, {r=1.0, g=1.0, b=0.0, a=1.0}) then
previous = v
break
end
end
if previous then
previous.style.font_color = {r=1.0, g=1.0, b=1.0, a=1.0}
end
element.style.font_color = {r=1.0, g=1.0, b=0.0, a=1.0}
local file = element.name:match('.-%.(.*)')
local selected = center['hotpatch-IDE']['hotpatch-IDE-top']['hotpatch-IDE-mod-selector']
local mod_name = selected.items[selected.selected_index]
local code = center['hotpatch-IDE']['hotpatch-IDE-table']['hotpatch-IDE-code']
local index = find_installed_mod(mod_name)
if index then
local mod = installed_mods[index]
if file == 'control' then
code.text = mod.code
else
code.text = mod.files[file]
end
end
end,
}
script.on_event(defines.events.on_gui_click, function(e)
local element = e.element
if element.valid then
local name = element.name:match('([^%.]*)%.?.-')
local handler = on_gui_click_handlers[name]
if handler then handler(e) end
end
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
local name = element.items[element.selected_index]
if name then
local index = find_loaded_mod(name)
if index then
local mod = loaded_mods[index]
local version_label = center['hotpatch-console']['hotpatch-console-top']['hotpatch-console-mod-version']
version_label.caption = 'Version: ' .. mod.version
local hotpatch_console = center['hotpatch-console']
local output = hotpatch_console['hotpatch-console-bottom']['hotpatch-console-output']
output.text = ''
end
end
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
local name = element.items[element.selected_index]
if name then
local index = find_installed_mod(name)
if index then
local mod = installed_mods[index]
local version_label = center['hotpatch-IDE']['hotpatch-IDE-top']['hotpatch-IDE-mod-version']
version_label.caption = 'Version: ' .. mod.version
local list = center['hotpatch-IDE']['hotpatch-IDE-table']['hotpatch-IDE-files']['hotpatch-IDE-files-table']
list.clear()
local file = list.add{type = 'label', style = 'hoverable_bold_label', caption = 'control', name = 'hotpatch-IDE-file.control'}
file.style.bottom_padding = 0
file.style.top_padding = 0
for k, _ in pairs(mod.files) do
file = list.add{type = 'label', style = 'hoverable_bold_label', caption = k, name = 'hotpatch-IDE-file.' .. k}
file.style.bottom_padding = 0
file.style.top_padding = 0
end
local code = center['hotpatch-IDE']['hotpatch-IDE-table']['hotpatch-IDE-code']
code.text = ''
end
end
end,
}
script.on_event(defines.events.on_gui_selection_state_changed, function(e)
local element = e.element
local handler = on_gui_selection_state_changed_handlers[element.name]
if handler then handler(e) end
end)
script.on_event(defines.events.on_gui_closed, function(e)
local player = game.players[e.player_index]
local element = e.element
if e.gui_type == defines.gui_type.custom then
if element and element.valid then
if element.name == 'hotpatch-IDE' then
if element.visible then
player.play_sound{path = "utility/gui_click"}
end
element.visible = false
elseif element.name == 'hotpatch-console' then
if element.visible then
player.play_sound{path = "utility/gui_click"}
end
element.visible = false
end
end
end
end)
--]===])
return true
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,322 @@
local hotpatch_tools = require 'hotpatch.mod-tools'
hotpatch_tools.static_mod('hotpatch-remote-interface', '1.0.2', [===[
--[[
Copyright 2018 Chrisgbk
Permission is hereby granted, free of charge, to any person obtaining a copy of this software 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.
]]
-- MIT License, https://opensource.org/licenses/MIT
local hotpatch_tools = require 'hotpatch.mod-tools'
--load private API
-- mod installation/uninstallation support functions
-- These take a mod NAME as a first argument
local install_mod = hotpatch_tools.install_mod
local find_installed_mod = hotpatch_tools.find_installed_mod
local install_mod_file = hotpatch_tools.install_mod_file
local uninstall_mod = hotpatch_tools.uninstall_mod
-- mod interaction functions
-- These take a LOADED INDEX as a first argument, except load_mod, which takes an INSTALLED INDEX
local load_mod = hotpatch_tools.load_mod
local find_loaded_mod = hotpatch_tools.find_loaded_mod
local run_mod = hotpatch_tools.run_mod
local reset_mod = hotpatch_tools.reset_mod
local reset_mod_events = hotpatch_tools.reset_mod_events
local register_mod_events = hotpatch_tools.register_mod_events
local unload_mod = hotpatch_tools.unload_mod
-- internal callbacks when a mod registers events
local register_event = hotpatch_tools.register_event
local register_nth_tick = hotpatch_tools.register_nth_tick
local register_on_tick = hotpatch_tools.register_on_tick
-- mod bootstrap functions
-- These take a LOADED INDEX as a first argument
local mod_on_init = hotpatch_tools.mod_on_init
local mod_on_load = hotpatch_tools.mod_on_load
local mod_on_configuration_changed = hotpatch_tools.mod_on_configuration_changed
local static_mods = hotpatch_tools.static_mods
local console = hotpatch_tools.console
local debug_log = hotpatch_tools.debug_log
local loaded_mods = hotpatch_tools.loaded_mods
local installed_mods = hotpatch_tools.installed_mods
local remote_interface = {}
--IMPORTANT: WHEN COPY PASTING CODE TO CONSOLE THE CODE MUST HAVE SINGLE LINE COMMENTS REMOVED
-- FACTORIO CONSOLE STRIPS LINEFEEDS WHICH MAKES ALL THE CODE BECOME COMMENTED OUT
--TODO: gate these behind admin permissions; if (game.player and game.player.admin) should work for console commands?
debug_log({'hotpatch-info.remote-installing'})
remote_interface['install'] = function(mod_name, mod_version, mod_code, mod_files, only_install)
-- this installs a new mod and runs on_init, then registers events
-- Note that mods may expect that certain events haven't been called yet when their on_init is ran
-- This may prevent them from functioning properly, without manually calling the events they expect
-- examples: on_player_created
local caller = game.player or console
if caller.admin then
if mod_files then
for k,_ in pairs(mod_files) do
if k == 'control' then
caller.print('ERROR: control.lua loaded twice for mod ' .. mod_name)
return
end
end
end
local installed_index = find_installed_mod(mod_name)
local loaded_index = find_loaded_mod(mod_name)
if installed_index then
local old_version = installed_mods[installed_index].version
if old_version then
debug_log({'hotpatch-warning.already-exists', mod_name, old_version})
debug_log({'hotpatch-warning.reinstalling', mod_name, old_version})
end
if loaded_index then
unload_mod(loaded_index)
end
end
install_mod(mod_name, mod_version, mod_code, mod_files)
if not only_install then
installed_index = find_installed_mod(mod_name)
if installed_index then
if not load_mod(installed_index) then
caller.print('compilation failed for mod ' .. mod_name)
return
end
loaded_index = find_loaded_mod(mod_name)
if loaded_index then
if not run_mod(loaded_index) then
caller.print('execution failed for mod ' .. mod_name)
unload_mod(loaded_index)
return
end
if not mod_on_init(loaded_index) then
caller.print('on_init failed for mod ' .. mod_name)
unload_mod(loaded_index)
return
end
-- TODO: notify all mods
-- TODO: determine vanilla behaviour and replicate it
if not mod_on_configuration_changed(loaded_index, {mod_changes = {mod_name={new_version=mod_version}}}) then
caller.print('on_configuration_changed failed for mod ' .. mod_name)
unload_mod(loaded_index)
return
end
else
end
else
end
end
else
caller.print('You must be an admin to run this command.')
end
end
remote_interface['run'] = function(mod_name)
-- this runs on_init, then registers events
-- Note that mods may expect that certain events haven't been called yet when their on_init is ran
-- This may prevent them from functioning properly, without manually calling the events they expect
-- examples: on_player_created
local caller = game.player or console
if caller.admin then
local installed_index = find_installed_mod(mod_name)
local loaded_index = find_loaded_mod(mod_name)
local mod
local version
if installed_index and not loaded_index then
if not load_mod(installed_index) then
caller.print('compilation failed for mod ' .. mod_name)
return
end
loaded_index = find_loaded_mod(mod_name)
else
if loaded_index then
mod = loaded_mods[loaded_index]
version = mod.version
debug_log({'hotpatch-warning.already-loaded', mod_name, version})
debug_log({'hotpatch-warning.reinitializing', mod_name, version})
end
end
if loaded_index then
if not run_mod(loaded_index) then
caller.print('execution failed for mod ' .. mod_name)
unload_mod(loaded_index)
return
end
if not mod_on_init(loaded_index) then
caller.print('on_init failed for mod ' .. mod_name)
unload_mod(loaded_index)
return
end
-- TODO: notify all mods
-- TODO: determine vanilla behaviour and replicate it
if not mod_on_configuration_changed(loaded_index, {mod_changes = {mod_name={new_version=version}}}) then
caller.print('on_configuration_changed failed for mod ' .. mod_name)
unload_mod(loaded_index)
return
end
end
else
caller.print('You must be an admin to run this command.')
end
end
remote_interface['update'] = function(mod_name, mod_version, mod_code, mod_files)
-- this updates an existing mod
-- the current mods events are de-registered, the new code is installed, on_load is triggered, and then events are registered
-- finally, the mod is informed of the update, so it can run migrations from the previous version
-- TODO: validation
local caller = game.player or console
if caller.admin then
if mod_files then
for k,_ in pairs(mod_files) do
if k == 'control' then
caller.print('ERROR: control.lua loaded twice for mod ' .. mod_name)
return
end
end
end
local installed_index = find_installed_mod(mod_name)
if installed_index then
;
end
local loaded_index = find_loaded_mod(mod_name)
local old_version
if loaded_index then
old_version = loaded_mods[loaded_index].version
unload_mod(loaded_index)
end
install_mod(mod_name, mod_version, mod_code, mod_files)
installed_index = find_installed_mod(mod_name)
if not load_mod(installed_index) then
caller.print('compilation failed for mod ' .. mod_name)
return
end
loaded_index = find_loaded_mod(mod_name)
if not run_mod(loaded_index) then
caller.print('execution failed for mod ' .. mod_name)
unload_mod(loaded_index)
return
end
if old_version then
if not mod_on_load(loaded_index) then
caller.print('on_load failed for mod ' .. mod_name)
unload_mod(loaded_index)
return
end
-- The mod must do any migrations here
-- TODO: notify all mods
if not mod_on_configuration_changed(loaded_index, {mod_changes = {mod_name={old_version=old_version, new_version=mod_version}}}) then
caller.print('on_configuration_changed failed for mod ' .. mod_name)
unload_mod(loaded_index)
return
end
else
-- first time install
if not mod_on_init(loaded_index) then
caller.print('on_init failed for mod ' .. mod_name)
unload_mod(loaded_index)
return
end
-- TODO: notify all mods
if not mod_on_configuration_changed(loaded_index, {mod_changes = {mod_name={new_version=mod_version}}}) then
caller.print('on_configuration_changed failed for mod ' .. mod_name)
unload_mod(loaded_index)
return
end
end
else
caller.print('You must be an admin to run this command.')
end
end
remote_interface['install_mod_file'] = function(mod_name, mod_file, mod_file_code)
local caller = game.player or console
if caller.admin then
install_mod_file(mod_name, mod_file, mod_file_code)
else
caller.print('You must be an admin to run this command.')
end
end
remote_interface['exec'] = function(mod_name, exec_code)
local caller = game.player or console
if caller.admin then
local loaded_index = find_loaded_mod(mod_name)
local env = loaded_mods[loaded_index].env
local success
local code, err = load(exec_code, exec_code, 't', env)
if code then
success, err = pcall(code)
if not success then
debug_log(err)
end
else
debug_log(err)
end
else
caller.print('You must be an admin to run this command.')
end
end
--TODO: most of this function
remote_interface['clean'] = function(mod_name)
-- Removes ALL mod data and reinitializes
-- doesn't remove things a mod may add like surfaces, etc
local caller = game.player or console
if caller.admin then
--mod_reset(mod_name)
--run_mod(mod_name)
--mod_on_init(mod_name)
local loaded_index = find_loaded_mod(mod_name)
local mod = loaded_mods[loaded_index]
local version = mod.version
mod_on_configuration_changed(loaded_index, {mod_changes = {mod_name={new_version=version}}})
else
caller.print('You must be an admin to run this command.')
end
end
remote_interface['uninstall'] = function(mod_name)
-- Uninstalls a mod
local caller = game.player or console
if caller.admin then
uninstall_mod(mod_name)
else
caller.print('You must be an admin to run this command.')
end
end
remote.add_interface('hotpatch', remote_interface)
debug_log({'hotpatch-info.complete', {'hotpatch-info.remote-installing'}})
--]===])
--[[
remote.call('hotpatch, 'install', 'test', '1.0.0', [===[
script.on_event(defines.events.on_player_changed_position, function(e)
game.print('changed position')
end
]===]
local updated_code = ....
remote.call('hotpatch', 'update', 'test', '1.0.1', updated_code)
]]
return true
@@ -0,0 +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.
@@ -0,0 +1,3 @@
msg-intro=.نييلصألا ناكسلا نم كسفن ةيامح ىسنت الو ةياهنلا ىلإ لصتل كتقيرطب لمعأو ،رفصلا نم أدبتس .خيراوصلا عماوص حتف لجأ نم ةمدقتم ايجولونكت نع ثحبلل جاتحت فوس .ةيعانصلا رامقألاب خيراوصلا قالطإو خيراوصلا عماوص ءانب قيرط نع كلذب مايقلا عيطتست .ءاضفلل اخوراص قلطت نا يه كتمهم .Factorio ـل رحلا بعللا وه اذه
@@ -0,0 +1,3 @@
msg-intro=Гэта рэжым свабоднай гульні Factorio. Вашая мэта - запусціць ракету ў космас. Дзеля гэтага вам трэба пабудаваць ракетную шахту і саму ракету з спадарожнікам. Вы мусіце даследваць перадавыя тэхналогіі, каб навучыцца будаваць ракетную шахту. Пачынайце з малога, працуйце над аўтаматызацыяй вытворчасці, даследуйце новыя тэхналогіі і не забывайцеся на абарону ад "тутэйшых"!
@@ -0,0 +1,3 @@
msg-intro=Това е режимът на свободна игра. Задачата ви е да изстреляте ракета в космоса. За това ще е нужно да построите ракетна шахта и изстреляте ракетата със спътник. Ще трябва да проучите развити технологии, които ще отключат ракетната шахта. Започнете с малко и постепенно автоматизирайте процеса, като не забравяте да се защитавате от местните форми на живот.
@@ -0,0 +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.
@@ -0,0 +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.
@@ -0,0 +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.
@@ -0,0 +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.
@@ -0,0 +1,3 @@
msg-intro=Αυτό είναι το ελεύθερο παιχνίδι του Factorio. Ο στόχος σου είναι να εκτοξεύσεις έναν πύραυλο στο διάστημα. Αυτό το κάνεις κατασκευάζοντας ένα σιλό πυραύλου και εκτοξεύοντας έναν πυραύλο με δορυφόρο. Θα πρέπει να ερευνήσεις προηγμένες τεχνολογίες για να ξεκλειδώσεις το σιλό πυραύλου. Ξεκινάς περιορισμένος, δούλεψε με αυτοματισμούς για την άνοδο σου και μην ξεχνάς να προστατέψεις τον εαυτό σου από τους ντόπιους.
@@ -0,0 +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.
@@ -0,0 +1,81 @@
[hotpatch]
log=@__1__:__2__] __3__
log-mod=@__1__:__2__][__4__] __3__
severe=SEVERE ERROR: __1__
error=ERROR: __1__
warning=WARNING: __1__
info=INFO: __1__
verbose=INFO: __1__
trace=TRACE: __1__
[hotpatch-info]
logging-enabled=Logging enabled
metatable-installed=_ENV metatable installed
complete=__1__ Complete!
on-init=initializing...
on-load=loading...
on-configuration-changed=mod configuration changed...
installing-included-mods=installing included mods...
loading-installed-mods=loading installed mods...
loading-libs=loading Factorio.data.core.lualib...
loading-library=loading library: __1__
uninstalling=Uninstalling mod...
installing=Installing version __1__...
installing-file=Installing file __1__...
script-shim=setting up mod script shim...
setting-env=setting up mod _ENV...
loading=loading...
unloading=unloading...
running=running...
must-be-admin=You must be an admin to run this command
remote-installing=installing remote interface
[hotpatch-trace]
nil-var-access=_ENV nil variable access: __1__
nil-var-assignment=_ENV variable assignment: __1__
event-registering=registering events...
on-tick-event-registered=registered on_tick event...
on-event-registered=registered event __1__...
on-nth-tick-event-registered=registered nth_tick event __1__...
event-running=running event __1__...
nth-tick-event-running=running nth_event __1__...
mod-on-init=running on_init...
mod-on-load=running on_load...
mod-on-configuration-changed=running on_configuration_changed...
adding-event=adding event __1__
adding-nth-tick-event=adding nth_tick event __1__
caching-event=caching event: __1__...
caching-nth-tick-event=caching nth_tick event: __1__...
event-processing=processing event __1__...
nth-tick-event-processing=processing nth_tick event __1__...
nth-tick-handler-added=added nth_tick handler: __1__
nth-tick-handler-removed=removed nth_tick handler: __1__
on-tick-handler-added=added on_tick handler
on-tick-handler-removed=removed on_tick handler
on-event-handler-added=added event handler: __1__
on-event-handler-removed=removed event handler: __1__
cached-load-require=loading cached require'd file: __1__...
load-require=loading require'd file: __1__...
load-core-lib=loading from Factorio.data.core.lualib: __1__...
[hotpatch-warning]
contains-comments=mod code contains comments!
contains-comments-no-lf=mod contains comments and no linefeed!
contains-comments-console=comments from console will comment out the entire code!
reset-events-not-running=tried to reset events for mod that isn't running!
remote-interface-exists=remote interface __1__ already exists, removing...
command-exists=command __1__ already exists, removing...
already-exists=mod already exists: __1__ __2__
reinstalling=reinstalling mod in-place: __1__ __2__
already-loaded=mod already loaded: __1__ __2__
reinitializing=reinitializing: __1__ __2__
[hotpatch-error]
invalid-API-access=Invalid API access: __1__
not-installed=mod not installed, cannot install file for mod that does not exist
compilation-failed=compilation failed for mod
execution-failed=execution failed for mod
path-not-found=path '__1__' not found
on-init-failed=on_init failed
on-load-failed=on_load failed
on-configuration-changed-failed=on_configuration_changed failed
@@ -0,0 +1,3 @@
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.
@@ -0,0 +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.
@@ -0,0 +1,3 @@
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.
@@ -0,0 +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.
@@ -0,0 +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.
@@ -0,0 +1,3 @@
msg-intro=.םיימוקמה םירוציה ינפמ םכמצע לע ןגהל וחכשת לאו ,יטמוטוא ןוכימ תועצמאב םכלש הישעתה תא וביחרה ,ןטקב וליחתה .ןקתמה תיינב תא רשפאל ידכ תומדקתמ תויגולונכט רוקחל םכילע היהי .ןייוול ליכמה ליט ותועצמאב רגשלו םיליט רוגיש ןקתמ תונבל םכילע .ללחל ליט רגשל איה םכתמישמ .ויִרֹוטְקַפ לש ישפוחה שיחרתה והז
@@ -0,0 +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.
@@ -0,0 +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.
@@ -0,0 +1,3 @@
msg-intro=これは、Factorioのフリープレイです。ロケットサイロを建造し、衛星を載せたロケットを宇宙へ打ち上げるのがあなたの任務です。ロケットサイロをアンロックするためには高度な技術研究をする必要があります。小規模に始め、自動化しつつ進み、原住生物から身を守ることも忘れないでください。
@@ -0,0 +1,3 @@
msg-intro=Factorio 자유플레이입니다. 당신의 목표는 우주로 로켓을 발사하는 것입니다. 로켓 격납고를 건설하고 인공위성을 탑재한 로켓을 발사하여 이 목표를 달성하세요. 로켓 격납고의 잠금을 해제하기 위해 고급 기술들을 연구할 필요가 있습니다. 작은 것부터 시작해 자동화로 차근차근 발전하고, 토착 생물로부터 자신을 보호하는 것도 잊지 마세요.
@@ -0,0 +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ų.
@@ -0,0 +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...
@@ -0,0 +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.
@@ -0,0 +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.
@@ -0,0 +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.
@@ -0,0 +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.
@@ -0,0 +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.
@@ -0,0 +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.
@@ -0,0 +1,3 @@
msg-intro=Это режим свободной игры Factorio. Ваша задача запустить спутник в космос. Для его запуска потребуется создать ракету и ракетную шахту. Начните с малого, работайте над автоматизацией производства, исследуйте новые технологии и не забывайте о защите от местных форм жизни.
@@ -0,0 +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.
@@ -0,0 +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.
@@ -0,0 +1,2 @@
msg-intro=Ово је Factorio freeplay. Твој задатак је да лансираш ракету са сателитом у свемир. Уради ово грађом ракетног силоса. Треба да истражиш пуно напредних технологија да би откључао ракетни силос. Почни мало, а онда напредуј са аутоматизацијом и не заборави да се заштитиш од ванземаљаца.
@@ -0,0 +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.
@@ -0,0 +1,3 @@
msg-intro=นี้คือโหมดอิสระของ Factorio ภารกิจของคุณคือส่งจรวดสู่อวกาศ ซึ่งทำได้โดยการสร้างไซโลจรวดและส่งจรวดขึ้นไปพร้อมกับดาวเทียม คุณต้องวิจัยค้นคว้าเทคโนโลยีขั้นสูงเพื่อปลดล็อคไซโลจรวด เริ่มจากเล็กๆไปเรื่อยๆด้วยระบบอัตโนมัติและอย่าลืมป้องกันตัวเองจากคนพื้นเมืองด้วย
@@ -0,0 +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.
@@ -0,0 +1,3 @@
msg-intro=Це один з типів гри в Factorio - "Вільна гра". Ваше завдання - запустити ракету у космічний простір. Зробіть це, побудувавши ракетну шахту та запустивши ракету зі супутником. Вам потрібно дослідити передові технології, щоб мати змогу побудувати ракетну шахту. Починайте з малого, розвивайтеся за допомогою автоматизації, але не забувайте і про оборону!
@@ -0,0 +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.
@@ -0,0 +1,3 @@
msg-intro=欢迎来到异星工厂标准模式,你的目标是要把火箭发射到太空。为此你需要研发一系列高等科技,解锁并修建火箭发射井,直到把携带有卫星的火箭发射出去。从小作坊生产起步,逐渐建立起属于自己的自动化大工厂吧!同时,别忘了防范异星虫子的骚扰 ...
@@ -0,0 +1,3 @@
msg-intro=這是異星工廠的自由模式。你的任務是發射火箭,探索外太空。請建造一個火箭發射井,並發射攜帶著衛星的火箭。你會需要研究大量的進階科技以解鎖火箭發射井。從頭開始,靠著自動化科技的協助向目標邁進時,也別忘記從外星住民手的中保護你自己啊!
+14
View File
@@ -0,0 +1,14 @@
function hashCode(string){
if(typeof string !== "string") throw "ERROR stringUtils: Not a string";
var hash = 0;
if (string.length == 0) return hash;
for (let i = 0; i < string.length; i++) {
char = string.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // Convert to 32bit integer
}
return hash;
}
module.exports = {
hashCode: hashCode,
}
+48
View File
@@ -0,0 +1,48 @@
const assert = require("assert");
const stringUtils = require("./stringUtils");
describe("stringUtils.js", function(){
describe("hashCode", function(){
it("Produces a 32bit signed int hash from a string", function(){
let x = stringUtils.hashCode("whatever");
let y = stringUtils.hashCode(" a much longer string to prove this works for long strings as well and also special characters &!¤(#)¤");
assert(typeof x == "number", "hashCode should be returning a 32 bit signed int");
assert(x.toString() == "1934383586");
assert(y.toString() == "1648886132");
});
let permutations = 1000;
it("Rarely produces same hash for different strings ("+permutations+" hashes with collision check)", function(){
let keyHashPairs = [];
for(let i = 0; i < permutations; i++){
let string = Math.random().toString();
keyHashPairs.push({
key:string,
hash:stringUtils.hashCode(string),
});
}
for(let i = 0; i < permutations; i++){
for(let o = 0; o < permutations; o++){
assert(i == o || keyHashPairs[o].key != keyHashPairs[i].key, "Woha, Math.random() generated 2 equal numbers???");
assert(i == o || keyHashPairs[o].hash != keyHashPairs[i].hash, "Hash collision, as long at it doesn't happen again its fine.");
}
}
});
it("Returns 0 for 0 length strings", function(){
let x = stringUtils.hashCode("");
assert.deepEqual(x, 0);
});
it("Throws when supplied with anything other than a string", function(){
assert.throws(function(){
stringUtils.hashCode([]);
}, "ERROR: Not a string")
assert.throws(function(){
stringUtils.hashCode({});
}, "ERROR: Not a string")
assert.throws(function(){
stringUtils.hashCode(0);
}, "ERROR: Not a string")
});
});
});
+11
View File
@@ -0,0 +1,11 @@
/**
An item with a count specified
@typedef {object} itemStack
@property {string} name The name of the item in the stack
@property {(number|string)} count The amount of items in the stack
@example
itemStack = {name:"iron-plate", count:"12"}
itemStack = {name:"copper-plate", count:972}
*/
+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 154 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"><path d="M105.009,19.309l-3.12,0l-1.68,-1.68l0,-10.293l2.64,-2.64c2.029,0 7.281,0 9.996,0l1.68,1.68l0,6.2l-0.96,0.96l-7.596,-0.003l0,4.816l-0.96,0.953l0,0.007Zm6.876,0l-3.12,0l0,-3.944l0.96,-0.96l4.8,0l0,2.212l-2.64,2.692Zm-63.405,0l-9.917,0l-1.68,-1.68l0,-10.293l2.64,-2.64l3.118,0l0.002,9.827l2.718,0l0.002,-8.867l0.96,-0.96l3.117,0l1.68,1.68l0,10.293l-2.64,2.64Zm-15.652,0l-9.918,0l-1.68,-1.68l0,-10.293l2.64,-2.64l3.118,0l0.002,9.827l8.478,0.009l0,2.137l-2.64,2.64Zm-15.712,0l-9.996,0l-1.68,-1.68l0,-10.293l2.64,-2.64c2.029,0 7.281,0 9.996,0l1.68,1.68c0,1.028 0,2.147 0,2.147l-0.96,0.942l0,0.018l-7.596,0l0,5.04l8.556,0l0,2.146l-2.64,2.64Zm47.185,0l-9.997,0l-1.68,-1.68l0,-2.142l0.96,-0.96l7.597,-0.002l0,-0.993l-8.557,0l0,-6.196l1.68,-1.68l0,0l0.96,-0.96c2.029,0 7.282,0 9.997,0l1.68,1.68c0,1.028 0,2.147 0,2.147l-0.96,0.96l-7.597,0.002l0,0.957l8.557,0l0,6.227l-0.96,0.96l0,0l-1.68,1.68Zm12.444,0l-3.12,0l-1.68,-1.68l0,-6.216l0.96,-0.96l6.48,0l0,6.216l-2.64,2.64Zm19.325,0l-9.996,0l-1.68,-1.68l0,-10.293l2.64,-2.64c2.029,0 7.281,0 9.996,0l1.68,1.68c0,1.028 0,2.147 0,2.147l-1.086,0.96l-7.47,0l0,0.959l5.841,0l0,2.13l-0.96,0.96l-4.881,0l0,0.991l8.556,0l0,2.146l-2.64,2.64Zm20.065,-7.896l0.96,-0.96l6.481,0l0,5.953l-2.64,2.903l-3.121,0l-1.68,-1.943l0,-5.953Zm21.411,7.896l-2.872,0l0,-1.178c-1.382,-0.123 -2.749,-0.431 -4.028,-0.977l-0.056,-0.024l-1.307,0.804l-2.03,-1.25l1.277,-0.786c-0.81,-0.666 -1.487,-1.522 -1.761,-2.534l-1.749,0l0,-1.768l0.96,-0.96l1.141,0c0.332,-0.616 0.809,-1.156 1.35,-1.61l-1.198,-0.78l0.94,-0.93l2.03,-1.25l1.227,0.755c0.949,-0.424 2.033,-0.741 3.204,-0.922l0,-0.248l0.013,0l0.947,-0.96l2.872,0l0,1.116c1.505,0.156 2.889,0.535 4.059,1.079l1.332,-0.82l2.03,1.25l-1.12,1.059c0.701,0.671 1.181,1.437 1.377,2.261l1.976,0l0,1.768l-0.96,0.962l-0.002,-0.002l-1.327,0c-0.228,0.624 -0.547,1.236 -1.053,1.681l1.109,0.679l-0.968,0.978l-0.015,-0.004l-2.007,1.236l-1.411,-0.868c-0.958,0.432 -1.978,0.728 -3.02,0.908l0,0.375l-0.96,0.93l0,0.03Zm-15.17,-7.656l-6.001,0l0,5.624l1.55,1.792l2.901,0l1.55,-1.792l0,-5.624Zm-44.191,0l-6,0l0,5.877l1.54,1.539l2.921,0l1.539,-1.539l0,-5.877Zm35.14,3.952l-4.32,0l0,3.464l2.778,0l1.542,-1.59l0,-1.874Zm-96.308,-9.709l-9.798,0l-1.539,1.54l0,10.094l1.539,1.539l9.798,0l1.539,-1.539l0,-1.807l-8.556,0l0,-6.48l8.556,0l0,-1.807l-1.539,-1.54Zm8.771,0l-2.778,0l-1.54,1.54l0,10.094l1.54,1.539l9.719,0l1.539,-1.539l0,-1.799l-8.478,-0.008l-0.002,-9.827Zm15.653,0l-2.779,0l-1.539,1.54l0,10.094l1.539,1.539l9.719,0l1.539,-1.539l0,-10.094l-1.539,-1.54l-2.778,0l-0.003,9.827l-4.157,0l-0.002,-9.827Zm54.53,0l-9.798,0l-1.539,1.54l0,10.094l1.539,1.539l9.798,0l1.539,-1.539l0,-1.807l-8.556,0l0,-2.431l5.841,0l0,-1.65l-5.841,0l0,-2.399l8.556,0l0,-1.807l-1.539,-1.54Zm-31.77,0l-9.797,0l-0.82,0.82l-0.72,0.72l0,5.856l8.557,0l0,2.432l-8.557,0.003l0,1.803l1.54,1.539l9.797,0l1.54,-1.539l0,-5.888l-8.557,0l0,-2.396l8.557,-0.003l0,-1.807l-1.54,-1.54Zm47.585,0l-9.798,0l-1.539,1.54l0,10.094l1.539,1.539l2.781,0l0,-5.777l8.556,0.004l0,-5.86l-1.539,-1.54Zm28.324,7.453c0.327,-0.363 0.421,-0.95 0.212,-1.53c-0.459,-1.279 -2.339,-2.021 -4.278,-2.122c-0.058,0 -0.058,0 -0.115,0c-2.792,0.201 -3.386,1.31 -3.433,1.781c-0.151,1.505 1.994,2.74 4.45,2.74c1.269,0 2.7,-0.357 3.164,-0.869Zm-31.101,-1.707l0,-2.398l-4.24,0.002l0,2.396l4.24,0Zm-3.04,-2.157l0,0.957l2.8,0l0,-0.958l-2.8,0.001Zm-24.048,0.012l-13.471,0l0,-2.161l2.64,-2.64l10.111,0l1.68,1.68c0,1.034 0,2.161 0,2.161l-0.96,0.96Zm40.606,-0.048l-6.392,0l0,-2.113l2.64,-2.64l4.801,-0.005l0,3.798l-1.049,0.96Zm-42.386,-3.553l-9.912,0l-1.539,1.54l0,1.821l12.991,0l0,-1.821l-1.54,-1.54Zm37.774,0l-1.54,1.54l0,1.773l6.001,0l0,-3.318c-1.487,0.002 -2.974,0.003 -4.461,0.005Z" style="fill:#141414;"/><path d="M73.625,19.309l3.12,0l-3.12,0Zm47.201,-0.24l-2.901,0l-1.55,-1.792l0,-5.624l6.001,0l0,5.624l-1.55,1.792Zm-44.18,0l-2.921,0l-1.54,-1.539l0,-5.877l6,0l0,5.877l-1.539,1.539Zm60.66,0l-2.392,0l0,-1.157l-0.288,-0.026c-1.392,-0.131 -2.761,-0.465 -4.055,-1.026l-1.288,0.792l-1.572,-0.968l1.175,-0.724l0.005,-0.076l-0.25,-0.205c-0.598,-0.5 -1.12,-1.098 -1.438,-1.808c-0.159,-0.355 -0.249,-0.743 -0.25,-0.747l-1.693,0l0,-1.288l1.676,0l0.056,-0.226c0.27,-1.047 1.011,-1.878 1.84,-2.574l-0.005,-0.072l-1.116,-0.688l1.572,-0.968l1.208,0.743l0.228,-0.1c1.335,-0.574 2.76,-0.86 4.195,-0.988l0,-1.072l2.392,0l0,1.092l0.284,0.03c1.394,0.155 2.76,0.518 4.031,1.105l1.316,-0.81l1.572,0.968l-1.259,0.776l0.236,0.276c0.559,0.497 1.039,1.085 1.324,1.77c0.148,0.356 0.225,0.734 0.226,0.738l1.924,0l0,1.288l-1.941,0l-0.059,0.219c-0.367,1.3 -1.768,2.53 -1.768,2.53l1.317,0.811l-1.572,0.968l-1.394,-0.858lLine truncated

After

Width:  |  Height:  |  Size: 6.3 KiB

-63
View File
@@ -1,63 +0,0 @@
import React from "react";
import { createRoot } from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import { ConfigProvider, theme } from "antd";
import { PluginDetails } from "@clusterio/lib";
import SiteLayout from "../../packages/web_ui/src/components/SiteLayout.tsx";
import PluginsPage from "../../packages/web_ui/src/components/PluginsPage.tsx";
import ControlContext from "../../packages/web_ui/src/components/ControlContext.tsx";
import "../../packages/web_ui/src/style.css";
const specs = [
["awf_oarc", "awf_oarc", "0.2.16", "0.3.1"],
["auto_restart", "ExpGaming - Auto Restart", "7.0.5", "7.0.5"],
["global_chat", "Global Chat", "2.0.0", "2.1.0"],
["research_sync", "Research Sync", "2.1.0", "2.1.0"],
];
const plugins = specs.map(([name, title, version, latest]) => new PluginDetails(
name, title, version, "loaded", undefined, `@fixture/${name}`,
name === "awf_oarc" ? latest : version, `@fixture/${name}`,
));
const hostPlugins = plugins.map(plugin => plugin.name === "auto_restart"
? new PluginDetails(plugin.name, plugin.title, plugin.version, "noEntrypoint", undefined,
plugin.npmPackage, plugin.version, plugin.dependencyName) : plugin);
const store = entries => {
const snapshot = [new Map(entries), true, null];
return { subscribe: () => () => {}, getSnapshot: () => snapshot };
};
const meta = plugins.map(p => ({ name: p.name, version: p.version, npmPackage: p.npmPackage,
enabled: true, web: p.name === "awf_oarc" ? { error: "Fixture: web module could not load" } : {} }));
const originalFetch = window.fetch;
window.fetch = (url, ...rest) => String(url).endsWith("api/plugins")
? Promise.resolve(new Response(JSON.stringify(meta))) : originalFetch(url, ...rest);
const permissions = ["core.plugin.list", "core.plugin.search", "core.plugin.update", "core.plugin.install",
"core.controller.restart", "core.host.restart"];
const control = {
accountName: "Cooldude2606", accountRoles: [{ permissions }],
onAccountUpdate() {}, offAccountUpdate() {}, connector: {},
plugins: new Map(),
pluginInfos: new Map(specs.map(([name, title]) => [name, { name, title }])),
hosts: store([[1, { id: 1, name: "Host 1", connected: true }]]),
systems: store([["controller", { plugins, canRestart: true }], [1, { plugins: hostPlugins, canRestart: true }]]),
instances: store([]), users: store([]),
pages: [
...["Controller", "Hosts", "Instances", "Users"].map(title => ({ path: `/${title.toLowerCase()}`,
sidebarName: title, content: <div>{title}</div> })),
{ path: "/plugins", sidebarName: "Plugins", content: <PluginsPage /> },
{ path: "/plugins/:name/view", content: <div>Plugin navigation target</div> },
],
async send(request) {
if (request.constructor.name === "PluginLatestVersionsRequest") {
return { versions: Object.fromEntries(specs.map(([name, , , latest]) => [`@fixture/${name}`, latest])) };
}
throw new Error(`Unexpected fixture request: ${request.constructor.name}`);
},
async sendTo(address, request) {
if (request.constructor.name === "PluginListRequest") { return address === "controller" ? plugins : hostPlugins; }
throw new Error(`Unexpected fixture request: ${request.constructor.name}`);
},
};
window.fixtureControl = control;
createRoot(document.getElementById("root")).render(<ConfigProvider theme={{ algorithm: theme.darkAlgorithm }}>
<ControlContext.Provider value={control}><BrowserRouter><SiteLayout /></BrowserRouter></ControlContext.Provider>
</ConfigProvider>);
+1003
View File
File diff suppressed because it is too large. Load diff
+173
View File
@@ -0,0 +1,173 @@
var app = require('./master.js'),
assert = require('assert'),
request = require('supertest');
const validateHTML = require('html5-validator');
var parallel = require('mocha.parallel');
describe('Master server endpoint testing', function() {
describe('#GET /api/getFactorioLocale', function() {
it('should get the basegame factorio locale', function(done) {
request(app).get('/api/getFactorioLocale').end(function(err, res) {
assert.equal(res.statusCode, 200);
let object = res.body;
// test that it is actual factorio locale, copied from getFactorioLocale.spec.js
assert.equal(typeof object, "object");
assert.equal(object["entity-name"]["fish"], "Fish");
assert.equal(object["entity-name"]["small-lamp"], "Lamp");
Object.keys(object).forEach(key => {
// first level of the nested object is always an object
assert.equal(typeof object[key], "object");
Object.keys(object[key]).forEach(key2 => {
// second level of the nested object is always a string, nearly always truthy
if(key2 != "so-long-and-thanks-for-all-the-fish"){
assert.ok(object[key][key2]);
assert.equal(typeof object[key][key2], "string");
}
});
});
done();
});
});
});
// describe("#GET /api/")
parallel("#GET static website data", function() {
this.timeout(6000);
it("sends some HTML when accessing /", function(done){
this.timeout(6000);
request(app).get("/").end(function(err,res){
assert.equal(res.statusCode, 200);
validateHTML(res.text).then(result => {
assert(result.messages.length === 1, "there are HTML errors on the page, please fix: "+JSON.stringify(result.messages));
done();
});
});
});
it("sends some HTML when accessing /nodes",function(done){
this.timeout(6000);
request(app).get("/nodes").end(function(err,res){
assert.equal(res.statusCode, 200);
validateHTML(res.text).then(result => {
assert(result.messages.length === 1, "there are HTML errors on the page, please fix: "+JSON.stringify(result.messages));
done();
});
});
});
it("sends some HTML when accessing /settings",function(done){
this.timeout(6000);
request(app).get("/settings").end(function(err,res){
assert.equal(res.statusCode, 200);
validateHTML(res.text).then(result => {
assert(result.messages.length === 1, "there are HTML errors on the page, please fix: "+JSON.stringify(result.messages));
done();
});
});
});
it("sends some HTML when accessing /nodeDetails",function(done){
this.timeout(6000);
request(app).get("/nodeDetails").end(function(err,res){
assert.equal(res.statusCode, 200);
validateHTML(res.text).then(result => {
assert(result.messages.length === 1, "there are HTML errors on the page, please fix: "+JSON.stringify(result.messages));
done();
});
});
});
});
let persistentMaster = request(app);
describe("#POST /api/place", function(){
it("adds an itemStack to the masters inventory", function(done){
persistentMaster.post("/api/place")
.send({
name:"steel-plate",
count:20,
instanceName:"unitTest"
})
.end(function(err,res){
assert(!err)
assert.equal(res.text, "success", "something went wrong with the request")
done();
});
});
});
describe("#GET /api/inventory", function(){
it("returns the masters current inventory", function(done){
persistentMaster.get("/api/inventory").end(function(err,res){
assert.equal(res.statusCode, 200);
let inventory = JSON.parse(res.text);
assert.equal(typeof inventory, "object", "Inventory should be an object");
assert(inventory.length >= 1, "There should be at least 1 entry in the inventory");
let contains20SteelPlate = false;
inventory.forEach(itemStack => {
if(itemStack.name == "steel-plate" && itemStack.count >= 20) contains20SteelPlate = true;
});
assert(contains20SteelPlate, "Please ensure there are at least 20 steel plate in the inventory")
done();
});
});
});
parallel("#POST /api/remove", function(){
it("returns an itemStack of how many items were removed", function(done){
persistentMaster.post("/api/remove")
.send({
name:"steel-plate",
count:10
})
.end(function(err,res){
assert(!err);
assert.equal(res.body.count, 10, "Something is wrong with the response, maybe the format changed slightly or you didn't have enough steel?");
done();
});
});
it("returns an empty itemStack if you try to request addItem or removeItem", function(done){
let casesRan = 0;
persistentMaster.post("/api/remove")
.send({
name:"addItem",
count:10
})
.end(function(err,res){
assert(!err);
assert.equal(res.text, '{"name":"addItem","count":0}', "When there are none of the item, you should get 0 back. addItem and removeItem should always return 0");
isDone();
});
persistentMaster.post("/api/remove")
.send({
name:"removeItem",
count:10
})
.end(function(err,res){
assert(!err);
assert.equal(res.text, '{"name":"removeItem","count":0}', "When there are none of the item, you should get 0 back. addItem and removeItem should always return 0");
isDone();
});
function isDone(){
if(++casesRan == 2) done();
}
});
it("returns an empty itemStack if you don't have any of the item you request", function(done){
persistentMaster.post("/api/remove")
.send({
name:"imaginaryItem",
count:999999
})
.end(function(err,res){
assert(!err);//{"name":"imaginaryItem","count":0}
assert.equal(res.body.name, "imaginaryItem", "Make sure body.name is the item we asked for");
assert.equal(res.body.count, 0, "Count should be 0 since we are asking for something that does not exist anywhere");
assert.equal(Object.keys(res.body).length, 2, "name and count should be the only keys on this object");
done();
});
});
});
});
BIN
View File
Binary file not shown.
+9
View File
@@ -0,0 +1,9 @@
module.exports = async function checkHotpatchInstallation(messageInterface){
let yn = await messageInterface("/silent-command if remote.interfaces['hotpatch'] then rcon.print('true') else rcon.print('false') end");
yn = yn.replace(/(\r\n\t|\n|\r\t)/gm, "");
if(yn == "true"){
return true;
} else if(yn == "false"){
return false;
}
}
+16
View File
@@ -0,0 +1,16 @@
const fs = require("fs-extra");
const getLua = require("./getLua.js");
let commandCache;
module.exports = async function getCommand(file, COMPRESS = true){
commandCache = commandCache || {};
if(!commandCache[file]){
commandCache[file] = await getLua(file);
return commandCache[file];
} else if(typeof commandCache[file] == "string"){
return commandCache[file];
} else {
throw new Error("Command not found");
}
}
+40
View File
@@ -0,0 +1,40 @@
const needle = require("needle");
module.exports = function(config){
const instances = [];
if(config){
return function getInstanceName(instanceID){
return new Promise((resolve, reject) => {
let instance = instances[instanceID];
if(!instance){
needle.get(config.masterIP+":"+config.masterPort+ '/api/slaves', (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) {
if(Buffer.isBuffer(response.body)) {console.log(response.body.toString("utf-8")); reject(new Error())}
try {
for (let index in response.body)
instances[index] = response.body[index].instanceName;
} catch (e){
console.log(e);
return null;
}
instance = instances[instanceID]
if (!instance) instance = instanceID; //somehow the master doesn't know the instance
resolve(instance);
}
});
} else {
resolve(instance);
}
});
}
} else {
return async function thisAintGonnaWork(){
let err = new Error("getInstanceName from _app/clusterTools needs to be called with {config} to work");
console.log(err);
throw err;
}
}
}
+40
View File
@@ -0,0 +1,40 @@
const fs = require("fs-extra");
module.exports = function getLua(filePath, COMPRESS_LUA = true){
return new Promise((resolve, reject) => {
fs.readFile(filePath, "utf8", (err, contents) => {
if(err){
reject(err);
} else {
// split content into lines
contents = contents.split(/\r?\n/);
// join those lines after making them safe again
contents = contents.reduce((acc, val) => {
val = val.replace(/\\/g ,'\\\\');
// remove leading and trailing spaces
val = val.trim();
// escape single quotes
val = val.replace(/'/g ,'\\\'');
// remove single line comments
let singleLineCommentPosition = val.indexOf("--");
let multiLineCommentPosition = val.indexOf("--[[");
if(multiLineCommentPosition === -1 && singleLineCommentPosition !== -1) {
val = val.substr(0, singleLineCommentPosition);
}
return acc + val + '\\n';
}, ""); // need the "" or it will not process the first row, potentially leaving a single line comment in that disables the whole code
// console.log(contents);
// this takes about 46 ms to minify train_stop_tracking.lua in my tests on an i3 6100u
if(COMPRESS_LUA) contents = require("luamin").minify(contents);
resolve(contents);
}
});
});
}
+41
View File
@@ -0,0 +1,41 @@
/**
Functions frequently needed from different places in the cluster. Ex:
* Loading Lua scripts
* Checking if hotpatch is installed
* Getting an instance's displayname from their ID
*/
module.exports = ({config} = {}) => ({
/**
async function to get Lua from a file, remove comments, minify and return.
arg1 is the file path
arg2 is whether or not to compress the Lua. Makes debugging harder, but reduces bandwidth when many players are online. Recommended for polling commands. Defaults to true.
Promise resolves with a string of Lua.
*/
getLua: require("./getLua.js"),
/**
async function to check whether hotpatch is installed in a server.
First argument is the servers async messageInterface for rcon return values
Promise resolves with boolean.
*/
checkHotpatchInstallation: require("./checkHotpatchInstallation.js"),
/**
async function to get an instances displayname from their instanceID
arg1 is the instanceID
Promise resolves with a string, either the displayName or instanceID if not found.
*/
getInstanceName: require("./getInstanceName.js")(config),
/**
async function same as getLua, but using an in memory cache to lower FS operations.
arg1 is the file path
arg2 is whether or not to compress, default true
Promise resolves with a string of Lua.
*/
getCommand: require("./getCommand.js"),
})
+168
View File
@@ -0,0 +1,168 @@
/**
class factorioServer
This class has the responsiblity of :
* spawning a factorio server
* Handling input via stdin or RCON
*/
const fs = require("fs");
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");
module.exports = class factorioServer {
constructor({
factorioDirectory = "factorio",
instanceDirectory = "instances/data",
instanceName = "Default server name",
factorioPort = Math.floor(Math.random() * 65535),
serversettings = {},
onProgress = function(type, a, b, c){
if(type == "progress") console.log("Progress: ",a," of ",b," unit: ",c);
if(type == "log") console.log(a);
},
} = {}){
this.factorioDirectory = factorioDirectory;
this.instanceDirectory = instanceDirectory;
this.instanceName = instanceName;
this.factorioPort = factorioPort;
this.serversettings = Object.assign({
"name": "Clusterio instance: " + this.instanceName,
"description": "Public factorio server running clusterio",
"tags": ["clusterio"],
"max_players": "20",
"visibility": "public",
"username": "",
"token": "",
"game_password": "",
"verify_user_identity": true,
"admins": ["Danielv123"],
"allow_commands": true,
"autosave_interval": 10,
"autosave_slots": 5,
"afk_autokick_interval": 0,
"auto_pause": false,
}, serversettings);
this.onProgress = (type, a, b, c) => {
if(typeof onProgress == "function") onProgress(type, a, b, c);
};
}
async initialize(){
if(!await this.isValidInstance()){
await this.createInstance();
}
return true;
}
async createInstance(path = this.instanceDirectory){
if(await this.isValidInstance(path)){
throw new Error("Instance already exists!");
}
// create folders
let folders = [
"/script-output/",
"/saves/",
"/mods/",
"/instanceMods/",
];
mkdirp.sync(path);
folders.forEach(folder => mkdirp.sync(path + folder));
// create files
let files = [
{name: "/script-output/output.txt", content: ""},
{name: "/script-output/orders.txt", content: ""},
{name: "/script-output/txbuffer.txt", content: ""},
{name: "/config.ini", content: "[path]\r\n\ read-data=__PATH__executable__/../../data\r\n\ write-data=__PATH__executable__/../../../" + this.instanceDirectory + "\r\n\ "},
{name: "/config.json", content: JSON.stringify({
factorioPort: this.factorioPort,
clientPassword: Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 8),
}, null, 4)},
{name: "/server-settings.json", content: JSON.stringify(this.serversettings, null, 4)},
];
files.forEach(file => {
fs.writeFile(path + file.name, file.content, doneWritingHandler);
// console.log("Created: ",path+file.name);
});
let filesToWrite = files.length;
function doneWritingHandler(){
if(!--filesToWrite){
return path;
}
}
}
async deleteInstance(path = this.instanceDirectory){
if(await this.isValidInstance(path)){
await fileOps.deleteFolderRecursive(path);
return true;
} else {
return false;
}
}
isValidInstance(path = this.instanceDirectory){
return new Promise((resolve, reject) => {
fs.stat(path, (err, stat) => {
if(err){
// console.log(err)
resolve(false);
} else {
// the instance folder exists, do some more checks
fs.readdir(path, (err, files) => {
if(files && Array.isArray(files)){
let foldersThatShouldBeThere = [
"script-output",
"saves",
"mods",
"instanceMods",
];
let instanceValidity = true;
foldersThatShouldBeThere.forEach(folder => {
if(!files.includes(folder)){
instanceValidity = false;
}
});
resolve(instanceValidity);
}
});
}
});
});
}
createMap(name = "map.zip"){
return new Promise((resolve, reject) => {
this.isValidInstance().then(isValid => {
if(!isValid){
return createInstance();
}
}).then(()=>{
this.onProgress("log", "Starting map creation");
// console.log(this.factorioDirectory + '/bin/x64/factorio')
let proc = child_process.spawn(
path.join(this.factorioDirectory, 'bin', 'x64', 'factorio'), [
'-c', path.join(this.instanceDirectory,'config.ini'),
'--create', path.join(this.instanceDirectory, 'saves',name),
'--server-settings', path.join(this.instanceDirectory, 'server-settings.json'),
],{
cwd:process.cwd(),
}
);
// console.log(proc.stdout.toString("utf8"));
proc.stdout.on("data", data => this.onProgress("log", data.toString("utf8")));
proc.on("close", code => {
this.onProgress("log", "Map created! "+code);
resolve();
});
});
});
}
}
+114
View File
@@ -0,0 +1,114 @@
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");
const factorioServer = require("./index.js");
describe("class factorioServer/index.js", ()=>{
it("is a class (or at least a function)", ()=>{
assert.equal(typeof factorioServer, "function");
});
describe(".deleteInstance(instanceName)", ()=>{
it("deletes an instance, defaulting to the classes instanceDirectory", async () => {
let server = new factorioServer({
instanceDirectory: path.resolve(__dirname, "_testData_deleteInstance"),
instanceName: "Unit testing instance",
});
await server.initialize();
await server.deleteInstance();
assert.equal(await server.isValidInstance(), false);
return true;
});
});
describe(".isValidInstance(instanceName)", ()=>{
it("tells us whether an instance exists or not", async () => {
let server = new factorioServer({
instanceDirectory: path.join(__dirname, "_testData_isValidInstance"),
instanceName: "Unit testing instance",
});
await server.deleteInstance();
let status = await server.isValidInstance();
assert.equal(status, false);
let x = await server.initialize(); // creates our instance
let status2 = await server.isValidInstance();
assert.equal(status2, true);
await server.deleteInstance(); // clean up folder
return true;
});
it("does not get tricked by an empty folder", async () => {
let server = new factorioServer({
instanceDirectory: path.join(__dirname, "_testData_isValidInstance_2"),
instanceName: "Unit testing instance",
});
assert.equal(await server.isValidInstance(), false);
mkdirp.sync(path.join(__dirname, "_testData_isValidInstance_2")); // create empty folder simulating instance
assert.equal(await server.isValidInstance(), false);
await server.initialize(); // create a proper instance
assert.equal(await server.isValidInstance(), true);
await server.deleteInstance(); // delete the instance and ensure we see that it is in fact deleted
assert.equal(await server.isValidInstance(), false);
return true;
}).timeout(5000);
});
describe(".initialize()", ()=>{
it("creates an instance", done => {
let server = new factorioServer({
instanceDirectory: path.resolve(__dirname, "_testData_initialize"),
instanceName: "Unit testing instance",
});
server.initialize().then(()=>{
return server.deleteInstance();
}).then(() => {
done();
}).catch(e => console.log(e));
});
});
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 server = new factorioServer({
instanceDirectory: path.join(relativePath, "_testData_createMap"),
instanceName: "Unit testing instance",
onProgress: false,
});
await server.initialize();
let maps = fs.readdirSync(path.join(relativePath, "_testData_createMap", "saves"));
assert.equal(maps.length, 0);
await server.createMap();
let maps2 = fs.readdirSync(path.join(relativePath, "_testData_createMap", "saves"));
assert(maps2.includes("map.zip"));
await server.deleteInstance();
return true;
});
it("supports custom filenames", async function(){
this.timeout(20000);
let relativePath = __dirname.replace(appRootPath, "").substr(1);
let server = new factorioServer({
instanceDirectory: path.join(relativePath, "_testData_createMap_2"),
instanceName: "Unit testing instance",
onProgress: false,
});
await server.initialize();
let maps = fs.readdirSync(path.join(relativePath, "_testData_createMap_2", "saves"));
assert.equal(maps.length, 0);
await server.createMap("mapWithCustomName.zip");
let maps2 = fs.readdirSync(path.join(relativePath, "_testData_createMap_2", "saves"));
assert(maps2.includes("mapWithCustomName.zip"));
await server.deleteInstance();
return true;
});
});
});
+120
View File
@@ -0,0 +1,120 @@
const fs = require("fs");
const path = require("path");
// gets newest file in a directory
// dir is directory
// files is array of filenames
// callback(err, filename string);
var getNewestFile = function(dir, files, callback) {
if (!callback) throw new Error("Missing callback");
if (!files || (files && files.length === 0)) {
callback(new Error("No files"));
return;
}
// we are appending the file name to the directory, without the
// "/" we would get paths like /folderfile.txt instead of /folder/file.txt
if(dir[dir.length-1] != "/"){
dir += "/";
}
var newest = { file: files[0] };
var checked = 0;
fs.stat(dir + newest.file, function(err, stats) {
if(err) {
callback(err);
} else {
newest.mtime = stats.mtime;
for (var i = 0; i < files.length; i++) {
var file = files[i];
(function(file) {
fs.stat(file, function(err, stats) {
++checked;
if (stats.mtime.getTime() > newest.mtime.getTime()) {
newest = { file : file, mtime : stats.mtime };
}
if (checked == files.length) {
callback(undefined,newest);
}
});
})(dir + file);
}
}
});
}
// get names of all directories in directory
var getDirectoriesSync = function(srcpath) {
return fs.readdirSync(srcpath).filter(function (file) {
return fs.statSync(path.join(srcpath, file)).isDirectory();
});
}
// get names of all files in a directory
var getFileNamesSync = function(srcpath) {
return fs.readdirSync(srcpath).filter(function (file) {
return !fs.statSync(path.join(srcpath, file)).isDirectory();
});
}
// does what it says on the tin
var deleteFolderRecursiveSync = function(path) {
if (fs.existsSync(path)) {
fs.readdirSync(path).forEach(function (file, index) {
var curPath = path + "/" + file;
if (fs.lstatSync(curPath).isDirectory()) { // recurse
deleteFolderRecursiveSync(curPath);
} else { // delete file
fs.unlinkSync(curPath);
}
});
fs.rmdirSync(path);
return true;
} else {
return false;
}
};
var deleteFolderRecursive = function(path){
return new Promise((resolve, reject) => {
fs.readdir(path, (err, files) => {
if(err){
reject(err);
} else {
let thingsToDelete = files.length;
files.forEach(file => {
let curPath = path + "/" + file;
fs.stat(curPath, (err, stat) => {
if(!err){
if(stat.isDirectory()){
// recurse
deleteFolderRecursive(curPath).then(() => {
whenFilesDeleted();
}).catch(e => console.log(e));
} else {
fs.unlink(curPath, () => {
// console.log("Deleted "+curPath);
whenFilesDeleted();
});
}
} else {
resolve(false);
}
});
});
whenFilesDeleted();
function whenFilesDeleted(){
if(!thingsToDelete--){// var-- instead of --var because we are calling the function once too much and need the delay
fs.rmdir(path, () => {
resolve();
// console.log("Deleted "+path);
});
}
}
}
});
});
}
module.exports = {
getNewestFile,
getDirectoriesSync,
deleteFolderRecursiveSync,
deleteFolderRecursive,
getFileNamesSync,
}
+135
View File
@@ -0,0 +1,135 @@
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const fileOps = require('./fileOps');
// create folders for testing
function setupTestingEnv(){
if (!fs.existsSync(path.resolve(__dirname, "database"))){
fs.mkdirSync(path.resolve(__dirname, "database"));
}
if (!fs.existsSync(path.resolve(__dirname, "test"))){
fs.mkdirSync(path.resolve(__dirname, "test"));
}
if (!fs.existsSync(path.resolve(__dirname, "test/folder"))){
fs.mkdirSync(path.resolve(__dirname, "test/folder"));
}
if (!fs.existsSync(path.resolve(__dirname, "test/another folder"))){
fs.mkdirSync(path.resolve(__dirname, "test/another folder"));
}
if (!fs.existsSync(path.resolve(__dirname, "test/file.txt"))){
fs.writeFileSync(path.resolve(__dirname, "test/file.txt"), "contents");
}
if (!fs.existsSync(path.resolve(__dirname, "test/another file.txt"))){
fs.writeFileSync(path.resolve(__dirname, "test/another file.txt"), "more contents");
}
// we need this xfile to test a branch in fileOps.getNewestFile()s propagation
if (!fs.existsSync(path.resolve(__dirname, "test/xfile.txt"))){
fs.writeFileSync(path.resolve(__dirname, "test/xfile.txt"), "contents");
}
}
setupTestingEnv();
setupTestingEnv(); // run twice to get branch coverage
describe("fileOps.js", function(){
describe("fileOps.getDirectoriesSync()", function(){
it("gets an array of directory names from a path", function(){
let directories = fileOps.getDirectoriesSync(path.resolve(__dirname, "test"));
// because there are no arrays for some reason
assert(typeof directories == "object");
// they are sorted alphabetically because thats the way it is
assert(directories[0] == "another folder" && directories[1] == "folder");
});
it("does not return any filenames", function(){
let directories = fileOps.getDirectoriesSync(path.resolve(__dirname, "test"));
// this is a function to return directories, not files. So yeah.
assert(!directories.includes("file.txt") && !directories.includes("another file.txt"));
});
it("throws when lacking first argument", function(){
assert.throws(function(){
let files = fileOps.getDirectoriesSync();
}, Error);
});
});
describe("fileOps.getFileNamesSync(path)", function(){
it("gets names of files in directory", function(){
let files = fileOps.getFileNamesSync(path.resolve(__dirname, "test"));
assert(files.length > 0);
assert(files.includes("file.txt") && files.includes("another file.txt"));
});
it("throws when lacking first argument", function(){
assert.throws(function(){
let files = fileOps.getFileNamesSync();
}, Error);
});
});
describe("fileOps.getNewestFile()", function(){
it("gets the newest file in a directory async", function(done){
fileOps.getNewestFile(path.resolve(__dirname, "test/"), fileOps.getFileNamesSync(path.resolve(__dirname, "test")), function(err, newest){
assert(newest && typeof newest.file == "string");
done();
});
});
it("throws if callback is missing", function(){
assert.throws(function(){
fileOps.getNewestFile("test", fileOps.getFileNamesSync(path.resolve(__dirname, "test")));
}, Error);
});
it("executes callback(err) if no files are provided", function(done){
fileOps.getNewestFile(path.resolve(__dirname, "test"), [], function(err, newest){
assert(err && typeof err == "object");
done();
});
});
it("executes callback(err) if launched without a directory", function(done){
fileOps.getNewestFile("", fileOps.getFileNamesSync(path.resolve(__dirname, "test")), function(err, newest){
assert(err);
done();
});
});
it("executes callback(err) when you do stupid shit", function(done){
fileOps.getNewestFile({thisWontWork: "but not crash either"}, {lolz:"yeah..."}, function(err, newest){
assert(err);
// ENOENT: no such file or directory, stat 'C:\**\factorioClusterio\lib\test\undefined'
done();
});
});
});
describe("fileOps.deleteFolderRecursiveSync(path)", function(){
it("deletes a folder and all files and folders in it", function(){
let x = fileOps.deleteFolderRecursiveSync(path.resolve(__dirname, "test"));
assert.throws(function(){
fs.statSync(path.resolve(__dirname, "test"));
}, Error);
assert(x);
// give it some extra time because its sometimes very slow
}).timeout(5000);
it("returns false if you give it an invalid path", function(){
let x = fileOps.deleteFolderRecursiveSync("This isn't a real path");
assert(!x);
});
});
describe("fileOps.deleteFolderRecursive(path)", function(){
it("deletes a folder and all files and folders in it, just like sync version", async function(){
setupTestingEnv();
assert(fs.statSync(path.resolve(__dirname, "test")));
let x = await fileOps.deleteFolderRecursive(path.resolve(__dirname, "test"));
assert.throws(function(){
fs.statSync(path.resolve(__dirname, "test"));
}, Error);
assert(x === undefined, "Promise version of this function does not have a return value upon success");
return true;
}).timeout(5000);
it("throws like a promise does if you give it an invalid path", async function(){
let x = await fileOps.deleteFolderRecursive("Look this path is fake!")
.catch(e => {
assert(e);
assert.equal(e.code, "ENOENT");
return true;
});
});
});
});
Loaded 100 of 3850 files, more files were not shown because too many files have changed in this diff. Show more