From a10fa69defe1dada526c299032252fc176a69bee Mon Sep 17 00:00:00 2001 From: Danielv123 Date: Tue, 27 Dec 2016 10:47:02 +0100 Subject: [PATCH 01/11] Inventory combinator can now transmit all virtuals --- clusterio_0.0.3/control.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clusterio_0.0.3/control.lua b/clusterio_0.0.3/control.lua index ecc50cf7..ca717c0a 100644 --- a/clusterio_0.0.3/control.lua +++ b/clusterio_0.0.3/control.lua @@ -446,7 +446,7 @@ function UpdateInvCombinators() local fluids = game.fluid_prototypes if global.invdata then for name,count in pairs(global.invdata) do - if name == "signal-unixtime" then + if virtuals[name] then invframe[#invframe+1] = {count=count,index=#invframe+1,signal={name=name,type="virtual"}} elseif fluids[name] then invframe[#invframe+1] = {count=count,index=#invframe+1,signal={name=name,type="fluid"}} From 26a9c4f1ef423710ed3ee65b52d5d5d0406e87c3 Mon Sep 17 00:00:00 2001 From: Danielv123 Date: Tue, 27 Dec 2016 11:14:53 +0100 Subject: [PATCH 02/11] Fixed virtual signals --- clusterio_0.0.3/control.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/clusterio_0.0.3/control.lua b/clusterio_0.0.3/control.lua index ca717c0a..d7fa6675 100644 --- a/clusterio_0.0.3/control.lua +++ b/clusterio_0.0.3/control.lua @@ -444,6 +444,7 @@ function UpdateInvCombinators() local items = game.item_prototypes local fluids = game.fluid_prototypes + local virtuals = game.virtual_signal_prototypes if global.invdata then for name,count in pairs(global.invdata) do if virtuals[name] then From c33ca4abecf9c1ceb60dd5a3edfbcd645e6d7df5 Mon Sep 17 00:00:00 2001 From: Danielv123 Date: Tue, 27 Dec 2016 11:15:53 +0100 Subject: [PATCH 03/11] Client.js now transmits IP needed to connect to slaves --- client.js | 3 ++- config.json | 3 +++ master.js | 5 +++-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/client.js b/client.js index f5c13ac0..42f9a041 100644 --- a/client.js +++ b/client.js @@ -179,7 +179,8 @@ function getID() { rconPort: instanceconfig.clientPort, rconPassword: instanceconfig.clientPassword, serverPort: instanceconfig.factorioPort, - unique: instanceconfig.clientPassword.hashCode() + unique: instanceconfig.clientPassword.hashCode(), + publicIP: config.publicIP, // IP of the server should be global for all instances, so we pull that straight from the config } require('getmac').getMac(function (err, mac) { if (err) throw err diff --git a/config.json b/config.json index 49d838a3..f5c44cd5 100644 --- a/config.json +++ b/config.json @@ -2,6 +2,9 @@ "__comment": "Where factorio folder. Should be in the same folder as this config file.", "factorioDirectory": "factorio", + "__comment": "IP players should connect to to join your servers, should be portforwarded and stuff", + "publicIP": "192.168.10.1", + "__comment": "IP the master web server runs at, can be ignored on the master system", "masterIP": "localhost", "masterPort": 8080, diff --git a/master.js b/master.js index 5131370f..c5b31dcb 100644 --- a/master.js +++ b/master.js @@ -52,10 +52,11 @@ app.post("/getID", function(req,res) { // time us a unix timestamp we can use to check for how long the server has been unresponsive // we should save that somewhere and give appropriate response if(req.body){ - slaves[req.body.unique] = req.body - console.log("Slave: " + req.body.mac + " : " + req.body.serverPort) + slaves[req.body.unique] = req.body; + console.log("Slave: " + req.body.mac + " : " + req.body.serverPort+" at " + req.body.publicIP); } }); + // endpoint for getting information about all our slaves app.get("/slaves", function(req, res) { res.header("Access-Control-Allow-Origin", "*"); From f589273c324446f11b716383a701c22e04a824a4 Mon Sep 17 00:00:00 2001 From: Danielv123 Date: Tue, 3 Jan 2017 17:44:35 +0100 Subject: [PATCH 04/11] Mod seperated from slave, node client.js download --- client.js | 458 ++++++------ clusterio_0.0.3/config.lua | 32 - clusterio_0.0.3/control.lua | 518 -------------- clusterio_0.0.3/data.lua | 279 -------- clusterio_0.0.3/graphics/icons/none.png | Bin 227 -> 0 bytes clusterio_0.0.3/info.json | 11 - clusterio_0.0.3/json.lua | 851 ----------------------- clusterio_0.0.3/locale/en/item-names.cfg | 22 - package.json | 3 +- 9 files changed, 244 insertions(+), 1930 deletions(-) delete mode 100644 clusterio_0.0.3/config.lua delete mode 100644 clusterio_0.0.3/control.lua delete mode 100644 clusterio_0.0.3/data.lua delete mode 100644 clusterio_0.0.3/graphics/icons/none.png delete mode 100644 clusterio_0.0.3/info.json delete mode 100644 clusterio_0.0.3/json.lua delete mode 100644 clusterio_0.0.3/locale/en/item-names.cfg diff --git a/client.js b/client.js index 42f9a041..cb0b8bf6 100644 --- a/client.js +++ b/client.js @@ -2,6 +2,7 @@ var fs = require('fs'); var needle = require("needle"); var child_process = require('child_process'); var path = require('path') +var syncRequest = require('sync-request'); // require config.json var config = require('./config'); @@ -28,6 +29,9 @@ function getDirectories(srcpath) { if (!fs.existsSync("./instances/")) { fs.mkdirSync("instances"); } +if (!fs.existsSync("./sharedMods/")) { + fs.mkdirSync("sharedMods"); +} var instance = process.argv[3]; var instancedirectory = './instances/' + instance; var command = process.argv[2]; @@ -37,6 +41,8 @@ if (!command || command == "help" || command == "--help") { 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 download") process.exit(1) } else if (command == "list") { console.dir(getDirectories("./instances/")); @@ -53,6 +59,20 @@ if (!command || command == "help" || command == "--help") { console.error("Instance not found: " + process.argv[3]); process.exit(1) } +} 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 out = fs.createWriteStream('sharedMods/' + name); + needle.get(url).pipe(out).on('finish', function() { + console.log("Downloaded " + name); + process.exit(1); + }); + } } else if (command == "start" && typeof instance == "string" && instance != "/" && !fs.existsSync(instancedirectory)) { // if instance does not exist, create it console.log("Creating instance...") @@ -62,7 +82,7 @@ if (!command || command == "help" || command == "--help") { fs.writeFileSync(instancedirectory + "/script-output/orders.txt", "") fs.writeFileSync(instancedirectory + "/script-output/txbuffer.txt", "") fs.mkdirSync(instancedirectory + "/mods/") - fs.symlinkSync('../../../clusterio_0.0.3', instancedirectory + "/mods/clusterio_0.0.3", 'junction') + fs.symlinkSync('../../../sharedMods', instancedirectory + "/mods", 'junction') fs.writeFileSync(instancedirectory + "/config.ini", "[path]\r\n\ read-data=__PATH__executable__/../../data\r\n\ write-data=__PATH__executable__/../../../instances/" + instance + "\r\n\ @@ -103,245 +123,251 @@ write-data=__PATH__executable__/../../../instances/" + instance + "\r\n\ ] ) console.log("Instance created!") -} - -var instanceconfig = require(instancedirectory + '/config'); - - -process.on('SIGINT', function () { - console.log("Caught interrupt signal"); - //serverprocess.stdin.write('/quit') -}); - - -//var serverprocess = child_process.exec(commandline) -var serverprocess = child_process.spawn( - './' + config.factorioDirectory + '/bin/x64/factorio', [ - '-c', instancedirectory + '/config.ini', - '--start-server', instancedirectory + '/save.zip', - '--rcon-port', instanceconfig.clientPort, - '--rcon-password', instanceconfig.clientPassword, - '--server-settings', instancedirectory + '/server-settings.json', - '--port', instanceconfig.factorioPort - ], { - 'stdio': ['pipe', 'pipe', 'pipe'] +} else if (command == "start" && typeof instance == "string" && instance != "/" && !fs.existsSync(instancedirectory)) { + // Exit if no instance specified (it should be, just a safeguard) + if(instancedirectory != "./instances/undefined"){ + var instanceconfig = require(instancedirectory + '/config'); + } else { + process.exit(1) } -) -serverprocess.on('close', (code) => { - console.log(`child process exited with code ${code}`); - process.exit(); -}); -serverprocess.stdout.on('data', (chunk) => { - console.log('OUT: ' + chunk); -}) -serverprocess.stderr.on('data', (chunk) => { - console.log('ERR: ' + chunk); -}) + process.on('SIGINT', function () { + console.log("Caught interrupt signal"); + //serverprocess.stdin.write('/quit') + }); -// connect us to the server with rcon -// IP, port, password -var Rcon = require('simple-rcon'); -var client = new Rcon({ - host: 'localhost', - port: instanceconfig.clientPort, - password: instanceconfig.clientPassword, - timeout: 0 -}); -// wait a few seconds to let the server finish starting before connecting rcon -//TODO: catch '2.033 Info RemoteCommandProcessor.cpp:97: Starting RCON interface at port 35002' from stdout maybe? -setTimeout(() => { - client.connect(); -}, 5000); + //var serverprocess = child_process.exec(commandline) + var serverprocess = child_process.spawn( + './' + config.factorioDirectory + '/bin/x64/factorio', [ + '-c', instancedirectory + '/config.ini', + '--start-server', instancedirectory + '/save.zip', + '--rcon-port', instanceconfig.clientPort, + '--rcon-password', instanceconfig.clientPassword, + '--server-settings', instancedirectory + '/server-settings.json', + '--port', instanceconfig.factorioPort + ], { + 'stdio': ['pipe', 'pipe', 'pipe'] + } + ) -client.on('authenticated', function () { - console.log('Authenticated!'); -}).on('connected', function () { - console.log('Connected!'); - getID(); -}).on('disconnected', function () { - console.log('Disconnected!'); - // now reconnect - client.connect(); -}); + serverprocess.on('close', (code) => { + console.log(`child process exited with code ${code}`); + process.exit(); + }); -// set some globals -confirmedOrders = []; -lastSignalCheck = Date.now(); + serverprocess.stdout.on('data', (chunk) => { + console.log('OUT: ' + chunk); + }) -// world IDs ------------------------------------------------------------------ -function getID() { - var payload = { - time: Date.now(), - rconPort: instanceconfig.clientPort, - rconPassword: instanceconfig.clientPassword, - serverPort: instanceconfig.factorioPort, - unique: instanceconfig.clientPassword.hashCode(), - publicIP: config.publicIP, // IP of the server should be global for all instances, so we pull that straight from the config + serverprocess.stderr.on('data', (chunk) => { + console.log('ERR: ' + chunk); + }) + + // connect us to the server with rcon + // IP, port, password + var Rcon = require('simple-rcon'); + var client = new Rcon({ + host: 'localhost', + port: instanceconfig.clientPort, + password: instanceconfig.clientPassword, + timeout: 0 + }); + + // wait a few seconds to let the server finish starting before connecting rcon + //TODO: catch '2.033 Info RemoteCommandProcessor.cpp:97: Starting RCON interface at port 35002' from stdout maybe? + setTimeout(() => { + client.connect(); + }, 5000); + + client.on('authenticated', function () { + console.log('Authenticated!'); + }).on('connected', function () { + console.log('Connected!'); + getID(); + }).on('disconnected', function () { + console.log('Disconnected!'); + // now reconnect + client.connect(); + }); + + // set some globals + confirmedOrders = []; + lastSignalCheck = Date.now(); + + // world IDs ------------------------------------------------------------------ + function getID() { + var payload = { + time: Date.now(), + rconPort: instanceconfig.clientPort, + rconPassword: instanceconfig.clientPassword, + serverPort: instanceconfig.factorioPort, + unique: instanceconfig.clientPassword.hashCode(), + publicIP: config.publicIP, // IP of the server should be global for all instances, so we pull that straight from the config + } + require('getmac').getMac(function (err, mac) { + if (err) throw err + payload.mac = mac + console.log(payload) + needle.post(config.masterIP + ":" + config.masterPort + '/getID', payload, function (err, response, body) { + 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. + console.log(response.body) + } + }); + }) } - require('getmac').getMac(function (err, mac) { - if (err) throw err - payload.mac = mac - console.log(payload) - needle.post(config.masterIP + ":" + config.masterPort + '/getID', payload, function (err, response, body) { - 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. - console.log(response.body) + setInterval(getID, 10000) + // provide items -------------------------------------------------------------- + // trigger when something happens to output.txt + fs.watch(instancedirectory + "/script-output/output.txt", function (eventType, filename) { + // get array of lines in file + items = fs.readFileSync(instancedirectory + "/script-output/output.txt", "utf8").split("\n"); + // if you found anything, reset the file + if (items[0]) { + fs.writeFileSync(instancedirectory + "/script-output/output.txt", "") } - }); - }) -} -setInterval(getID, 10000) - // provide items -------------------------------------------------------------- - // trigger when something happens to output.txt -fs.watch(instancedirectory + "/script-output/output.txt", function (eventType, filename) { - // get array of lines in file - items = fs.readFileSync(instancedirectory + "/script-output/output.txt", "utf8").split("\n"); - // if you found anything, reset the file - if (items[0]) { - fs.writeFileSync(instancedirectory + "/script-output/output.txt", "") - } - for (i = 0; i < items.length; i++) { - if (items[i]) { - g = items[i].split(" "); - g[0] = g[0].replace("\u0000", ""); - // console.log("exporting " + JSON.stringify(g)); - // send our entity and count to the master for him to keep track of - needle.post(config.masterIP + ":" + config.masterPort + '/place', { - name: g[0], - count: g[1] - }, - function (err, resp, body) { - // console.log(body); - }); - } - } - }) - // request items -------------------------------------------------------------- -setInterval(function () { - // get array of lines in file - items = fs.readFileSync(instancedirectory + "/script-output/orders.txt", "utf8").split("\n"); - // if we actually got anything from the file, proceed and reset file - if (items[0]) { - fs.writeFileSync(instancedirectory + "/script-output/orders.txt", ""); - // prepare a package of all our requested items in a more tranfer friendly format - var preparedPackage = {}; for (i = 0; i < items.length; i++) { - (function (i) { - if (items[i]) { - items[i] = items[i].split(" "); - items[i][0] = items[i][0].replace("\u0000", ""); - items[i][0] = items[i][0].replace(",", ""); - if (preparedPackage[items[i][0]]) { - if (typeof Number(preparedPackage[items[i][0]].count) == "number" && typeof Number(items[i][1]) == "number") { - preparedPackage[items[i][0]] = { - "name": items[i][0], - "count": Number(preparedPackage[items[i][0]].count) + Number(items[i][1]) - }; + if (items[i]) { + g = items[i].split(" "); + g[0] = g[0].replace("\u0000", ""); + // console.log("exporting " + JSON.stringify(g)); + // send our entity and count to the master for him to keep track of + needle.post(config.masterIP + ":" + config.masterPort + '/place', { + name: g[0], + count: g[1] + }, + function (err, resp, body) { + // console.log(body); + }); + } + } + }) + // request items -------------------------------------------------------------- + setInterval(function () { + // get array of lines in file + items = fs.readFileSync(instancedirectory + "/script-output/orders.txt", "utf8").split("\n"); + // if we actually got anything from the file, proceed and reset file + if (items[0]) { + fs.writeFileSync(instancedirectory + "/script-output/orders.txt", ""); + // prepare a package of all our requested items in a more tranfer friendly format + var preparedPackage = {}; + for (i = 0; i < items.length; i++) { + (function (i) { + if (items[i]) { + items[i] = items[i].split(" "); + items[i][0] = items[i][0].replace("\u0000", ""); + items[i][0] = items[i][0].replace(",", ""); + if (preparedPackage[items[i][0]]) { + if (typeof Number(preparedPackage[items[i][0]].count) == "number" && typeof Number(items[i][1]) == "number") { + preparedPackage[items[i][0]] = { + "name": items[i][0], + "count": Number(preparedPackage[items[i][0]].count) + Number(items[i][1]) + }; + } else if (typeof Number(items[i][1]) == "number") { + preparedPackage[items[i][0]] = { + "name": items[i][0], + "count": Number(items[i][1]) + }; + } } else if (typeof Number(items[i][1]) == "number") { preparedPackage[items[i][0]] = { "name": items[i][0], "count": Number(items[i][1]) }; } - } else if (typeof Number(items[i][1]) == "number") { - preparedPackage[items[i][0]] = { - "name": items[i][0], - "count": Number(items[i][1]) - }; } - } - })(i); - } - // request our items, one item at a time - for (i = 0; i < Object.keys(preparedPackage).length; i++) { - console.log(preparedPackage[Object.keys(preparedPackage)[i]]) - needle.post(config.masterIP + ":" + config.masterPort + '/remove', preparedPackage[Object.keys(preparedPackage)[i]], function (err, response, body) { - if (response && response.body && typeof response.body == "object") { - // buffer confirmed orders - confirmedOrders[confirmedOrders.length] = { - [response.body.name]: response.body.count - } - } - }); - } - // if we got some confirmed orders - // console.log("Importing " + confirmedOrders.length + " items! " + JSON.stringify(confirmedOrders)); - sadas = JSON.stringify(confirmedOrders) - confirmedOrders = []; - // send our RCON command with whatever we got - client.exec("/silent-command remote.call('clusterio', 'importMany', '" + sadas + "')"); - } - }, 3000) - // COMBINATOR SIGNALS --------------------------------------------------------- - // get inventory from Master and RCON it to our slave -setInterval(function () { - needle.get(config.masterIP + ":" + config.masterPort + '/inventory', function (err, response, body) { - if (response && response.body) { - // Take the inventory we (hopefully) got and turn it into the format LUA accepts - // console.log(response.body) - var inventory = response.body; - var inventoryFrame = {}; - for (i = 0; i < inventory.length; i++) { - inventoryFrame[inventory[i].name] = Number(inventory[i].count); + })(i); } - inventoryFrame["signal-unixtime"] = Math.floor(Date.now()/1000); - console.log("RCONing inventory! " + JSON.stringify(inventoryFrame)); - client.exec("/silent-command remote.call('clusterio', 'receiveInventory', '" + JSON.stringify(inventoryFrame) + "')"); - } - }); - }, 1000) - // send any signals the slave has been told to send -setInterval(function () { - // Fetch combinator signals from the server - needle.post(config.masterIP + ":" + config.masterPort + '/readSignal', { - since: lastSignalCheck - }, function (err, response, body) { - if (response && response.body && typeof response.body == "object" && response.body[0]) { - // Take the new combinator frames and compress them so we can use a single command - frameset = []; - for (i = 0; i < response.body.length; i++) { - frameset[i] = response.body[i].frame; - } - // console.log(frameset); - // Send all our compressed frames - client.exec("/silent-command remote.call('clusterio', 'receiveMany', '" + JSON.stringify(frameset) + "')"); - } - }); - // after fetching all the latest frames, we take a timestamp. During the next iteration, we fetch all frames submitted after this. - lastSignalCheck = Date.now(); - - // get outbound frames from file and send to master - // get array of lines in file, each line should correspond to a JSON encoded frame - signals = fs.readFileSync(instancedirectory + "/script-output/txbuffer.txt", "utf8").split("\n"); - // if we actually got anything from the file, proceed and reset file - if (signals[0]) { - fs.writeFileSync(instancedirectory + "/script-output/txbuffer.txt", ""); - // loop through all our frames - for (i = 0; i < signals.length; i++) { - (function (i) { - if (signals[i]) { - // signals[i] is a JSON array called a "frame" of signals. We timestamp it for storage on master - // then we unpack and RCON in this.frame to the game later. - framepart = JSON.parse(signals[i]) - doneframe = { - time: Date.now(), - frame: framepart, // thats our array of objects(single signals) - } - // console.log(doneframe) - needle.post(config.masterIP + ":" + config.masterPort + '/setSignal', doneframe, function (err, response, body) { - 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. + // request our items, one item at a time + for (i = 0; i < Object.keys(preparedPackage).length; i++) { + console.log(preparedPackage[Object.keys(preparedPackage)[i]]) + needle.post(config.masterIP + ":" + config.masterPort + '/remove', preparedPackage[Object.keys(preparedPackage)[i]], function (err, response, body) { + if (response && response.body && typeof response.body == "object") { + // buffer confirmed orders + confirmedOrders[confirmedOrders.length] = { + [response.body.name]: response.body.count + } } }); } - })(i); + // if we got some confirmed orders + // console.log("Importing " + confirmedOrders.length + " items! " + JSON.stringify(confirmedOrders)); + sadas = JSON.stringify(confirmedOrders) + confirmedOrders = []; + // send our RCON command with whatever we got + client.exec("/silent-command remote.call('clusterio', 'importMany', '" + sadas + "')"); + } + }, 3000) + // COMBINATOR SIGNALS --------------------------------------------------------- + // get inventory from Master and RCON it to our slave + setInterval(function () { + needle.get(config.masterIP + ":" + config.masterPort + '/inventory', function (err, response, body) { + if (response && response.body) { + // Take the inventory we (hopefully) got and turn it into the format LUA accepts + // console.log(response.body) + var inventory = response.body; + var inventoryFrame = {}; + for (i = 0; i < inventory.length; i++) { + inventoryFrame[inventory[i].name] = Number(inventory[i].count); + } + inventoryFrame["signal-unixtime"] = Math.floor(Date.now()/1000); + console.log("RCONing inventory! " + JSON.stringify(inventoryFrame)); + client.exec("/silent-command remote.call('clusterio', 'receiveInventory', '" + JSON.stringify(inventoryFrame) + "')"); + } + }); + }, 1000) + // send any signals the slave has been told to send + setInterval(function () { + // Fetch combinator signals from the server + needle.post(config.masterIP + ":" + config.masterPort + '/readSignal', { + since: lastSignalCheck + }, function (err, response, body) { + if (response && response.body && typeof response.body == "object" && response.body[0]) { + // Take the new combinator frames and compress them so we can use a single command + frameset = []; + for (i = 0; i < response.body.length; i++) { + frameset[i] = response.body[i].frame; + } + // console.log(frameset); + // Send all our compressed frames + client.exec("/silent-command remote.call('clusterio', 'receiveMany', '" + JSON.stringify(frameset) + "')"); + } + }); + // after fetching all the latest frames, we take a timestamp. During the next iteration, we fetch all frames submitted after this. + lastSignalCheck = Date.now(); + + // get outbound frames from file and send to master + // get array of lines in file, each line should correspond to a JSON encoded frame + signals = fs.readFileSync(instancedirectory + "/script-output/txbuffer.txt", "utf8").split("\n"); + // if we actually got anything from the file, proceed and reset file + if (signals[0]) { + fs.writeFileSync(instancedirectory + "/script-output/txbuffer.txt", ""); + // loop through all our frames + for (i = 0; i < signals.length; i++) { + (function (i) { + if (signals[i]) { + // signals[i] is a JSON array called a "frame" of signals. We timestamp it for storage on master + // then we unpack and RCON in this.frame to the game later. + framepart = JSON.parse(signals[i]) + doneframe = { + time: Date.now(), + frame: framepart, // thats our array of objects(single signals) + } + // console.log(doneframe) + needle.post(config.masterIP + ":" + config.masterPort + '/setSignal', doneframe, function (err, response, body) { + 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. + } + }); + } + })(i); + } } - } -}, 1000) + }, 1000) +} // END OF INSTANCE START --------------------------------------------------------------------- // simple string hasher String.prototype.hashCode = function () { diff --git a/clusterio_0.0.3/config.lua b/clusterio_0.0.3/config.lua deleted file mode 100644 index 9034df28..00000000 --- a/clusterio_0.0.3/config.lua +++ /dev/null @@ -1,32 +0,0 @@ ---the chest that a player put things in -INPUT_CHEST_NAME = "put-chest" -INPUT_CHEST_PICTURE_PATH = nil -INPUT_CHEST_ICON_PATH = nil - ---the chest that the player get things from -OUTPUT_CHEST_NAME = "get-chest" -OUTPUT_CHEST_PICTURE_PATH = nil -OUTPUT_CHEST_ICON_PATH = nil - ---the tank that a player put things in -INPUT_TANK_NAME = "put-tank" -INPUT_TANK_PICTURE_PATH = nil -INPUT_TANK_ICON_PATH = nil - ---the tank that the player get things from -OUTPUT_TANK_NAME = "get-tank" -OUTPUT_TANK_PICTURE_PATH = nil -OUTPUT_TANK_ICON_PATH = nil - -MAX_RX_BUFFER_SIZE = 40 -- slightly more than one second -RX_COMBINATOR_NAME = "get-combinator" -TX_COMBINATOR_NAME = "put-combinator" -INV_COMBINATOR_NAME = "inventory-combinator" - -OUTPUT_FILE = "output.txt" -ORDER_FILE = "orders.txt" -TX_BUFFER_FILE = "txbuffer.txt" - - -UPDATE_RATE = 60 -CIRCUIT_UPDATE_RATE = 5 diff --git a/clusterio_0.0.3/control.lua b/clusterio_0.0.3/control.lua deleted file mode 100644 index d7fa6675..00000000 --- a/clusterio_0.0.3/control.lua +++ /dev/null @@ -1,518 +0,0 @@ -require("config") -local json = require("json") - -function OnBuiltEntity(event) - local entity = event.created_entity - --only add entities that are not ghosts - if entity.type ~= "entity-ghost" then - AddEntity(entity) - end -end - -function AddAllEntitiesOfName(name) - for k, surface in pairs(game.surfaces) do - AddEntities(surface.find_entities_filtered({["name"] = name})) - end -end - -function AddEntities(entities) - for k, entity in pairs(entities) do - AddEntity(entity) - end -end - -function AddEntity(entity) - if entity.name == INPUT_CHEST_NAME then - --add the chests to a lists if these chests so they can be interated over - global.inputChests[entity.unit_number] = entity - elseif entity.name == OUTPUT_CHEST_NAME then - --add the chests to a lists if these chests so they can be interated over - global.outputChests[entity.unit_number] = entity - elseif entity.name == INPUT_TANK_NAME then - --add the chests to a lists if these chests so they can be interated over - global.inputTanks[entity.unit_number] = entity - elseif entity.name == OUTPUT_TANK_NAME then - --add the chests to a lists if these chests so they can be interated over - global.outputTanks[entity.unit_number] = entity - entity.active = false - elseif entity.name == TX_COMBINATOR_NAME then - global.txControls[entity.unit_number] = entity.get_or_create_control_behavior() - elseif entity.name == RX_COMBINATOR_NAME then - global.rxControls[entity.unit_number] = entity.get_or_create_control_behavior() - entity.operable=false - elseif entity.name == INV_COMBINATOR_NAME then - global.invControls[entity.unit_number] = entity.get_or_create_control_behavior() - entity.operable=false - end -end - -function OnKilledEntity(event) - local entity = event.entity - if entity.type ~= "entity-ghost" then - --remove the entities from the tables as they are dead - if entity.name == INPUT_CHEST_NAME then - global.inputChests[entity.unit_number] = nil - elseif entity.name == OUTPUT_CHEST_NAME then - global.outputChests[entity.unit_number] = nil - elseif entity.name == INPUT_TANK_NAME then - global.inputTanks[entity.unit_number] = nil - elseif entity.name == OUTPUT_TANK_NAME then - global.outputTanks[entity.unit_number] = nil - elseif entity.name == TX_COMBINATOR_NAME then - global.txControls[entity.unit_number] = nil - elseif entity.name == RX_COMBINATOR_NAME then - global.rxControls[entity.unit_number] = nil - elseif entity.name == INV_COMBINATOR_NAME then - global.invControls[entity.unit_number] = nil - end - end -end - ---[[ Thing Creation Events ]]-- -script.on_event(defines.events.on_built_entity, function(event) - OnBuiltEntity(event) -end) -script.on_event(defines.events.on_robot_built_entity, function(event) - OnBuiltEntity(event) -end) - - - ---[[ Thing Killing Events ]]-- -script.on_event(defines.events.on_entity_died, function(event) - OnKilledEntity(event) -end) -script.on_event(defines.events.on_robot_pre_mined, function(event) - OnKilledEntity(event) -end) -script.on_event(defines.events.on_preplayer_mined_item, function(event) - OnKilledEntity(event) -end) - - -script.on_init(function() - Reset() -end) - -script.on_configuration_changed(function(data) - if data.mod_changes and data.mod_changes["clusterio"] then - Reset() - end -end) - -script.on_event(defines.events.on_tick, function(event) - -- TX Combinators must run every tick to catch single pulses - HandleTXCombinators() - - local todo = game.tick % UPDATE_RATE - - local onlinePlayers = GetOnlinePlayerCount() - - if global.previousPlayerCount == nil or global.previousPlayerCount ~= onlinePlayers then - Reset() - end - global.previousPlayerCount = onlinePlayers - - if todo == 0 then - HandleInputChests() - elseif todo == 1 then - HandleInputTanks() - elseif todo == 2 then - HandleOutputChests() - elseif todo == 3 then - HandleOutputTanks() - elseif todo == 4 then - ExportInputList() - elseif todo == 5 then - ExportOutputList() - end - - local rxstate = game.tick % CIRCUIT_UPDATE_RATE - -- RX Combinators are set and then cleared on sequential ticks to create pulses - if rxstate == 0 then - SetRXCombinators() - elseif rxstate == 1 then - ClearRXCombinators() - end -end) - -function GetOnlinePlayerCount() - local onlinePlayers = 0 - for k, player in pairs(game.players) do - if player.connected then - onlinePlayers = onlinePlayers + 1 - end - end - return onlinePlayers -end - -function Reset() - global.outputList = {} - global.inputList = {} - global.itemStorage = {} - - global.inputChests = {} - global.outputChests = {} - - global.inputTanks = {} - global.outputTanks = {} - - global.rxControls = {} - global.txControls = {} - global.invControls = {} - - AddAllEntitiesOfName(INPUT_CHEST_NAME) - AddAllEntitiesOfName(OUTPUT_CHEST_NAME) - - AddAllEntitiesOfName(INPUT_TANK_NAME) - AddAllEntitiesOfName(OUTPUT_TANK_NAME) - - AddAllEntitiesOfName(RX_COMBINATOR_NAME) - AddAllEntitiesOfName(TX_COMBINATOR_NAME) - AddAllEntitiesOfName(INV_COMBINATOR_NAME) - game.print("reset") -end - -function HandleInputChests() - for k, v in pairs(global.inputChests) do - if v.valid then - --get the content of the chest - local items = v.get_inventory(defines.inventory.chest).get_contents() - --write everything to the file - for itemName, itemCount in pairs(items) do - AddItemToInputList(itemName, itemCount) - end - -- clear the inventory - v.get_inventory(defines.inventory.chest).clear() - end - end -end - -function HandleInputTanks() - for k, v in pairs(global.inputTanks) do - if v.valid then - --get the content of the chest - local fluid = v.fluidbox[1] - if fluid ~= nil and math.floor(fluid.amount) > 0 then - AddItemToInputList(fluid.type, math.floor(fluid.amount)) - fluid.amount = fluid.amount - math.floor(fluid.amount) - end - v.fluidbox[1] = fluid - end - end - -end - -function HandleOutputChests() - local simpleItemStack = {} - for k, v in pairs(global.outputChests) do - if v.valid and not v.to_be_deconstructed(v.force) then - --get the inventory here once for faster execution - local chestInventory = v.get_inventory(defines.inventory.chest) - for i = 1, 10 do - --the item the chest wants - local requestItem = v.get_request_slot(i) - if requestItem ~= nil then - local itemsInChest = chestInventory.get_item_count(requestItem.name) - --if there isn't enough items in the chest - if itemsInChest < requestItem.count then - local additionalItemRequiredCount = requestItem.count - itemsInChest - local itemCountAllowedToInsert = RequestItemsFromStorage(requestItem.name, additionalItemRequiredCount) - if itemCountAllowedToInsert > 0 then - simpleItemStack.name = requestItem.name - simpleItemStack.count = itemCountAllowedToInsert - --insert the missing items - local insertedItemsCount = chestInventory.insert(simpleItemStack) - local itemsNotInsertedCount = itemCountAllowedToInsert - insertedItemsCount - - if itemsNotInsertedCount > 0 then - GiveItemsToStorage(requestItem.name, itemsNotInsertedCount) - end - else - local missingItems = additionalItemRequiredCount - itemCountAllowedToInsert - AddItemToOutputList(requestItem.name, missingItems) - end - end - end - end - end - end -end - -function HandleOutputTanks() - local MAX_FLUID_AMOUNT = 1000 - for k,v in pairs(global.outputTanks) do - --.recipe.products[1].name - if v.recipe ~= nil then - local fluidName = v.recipe.products[1].name - - --either get the fluid or reset it to the requested fluid - local fluid = v.fluidbox[1] or {type = fluidName, amount = 0} - if fluid.type ~= fluidName then - fluid = {type = fluidName, amount = 0} - end - - --if any fluid is missing then request the fluid - --from store and give either what it's missing or - --the rest of the liquid in the system - local missingFluid = math.max(math.ceil(MAX_FLUID_AMOUNT - fluid.amount), 0) - if missingFluid > 0 then - local fluidToInsert = RequestItemsFromStorage(fluidName, missingFluid) - if fluidToInsert > 0 then - fluid.amount = fluid.amount + fluidToInsert - else - local fluidToRequestAmount = missingFluid - fluidToInsert - AddItemToOutputList(fluid.type, fluidToRequestAmount) - end - end - - v.fluidbox[1] = fluid - end -end -end - - -function AddItemToInputList(itemName, itemCount) - global.inputList[itemName] = (global.inputList[itemName] or 0) + itemCount -end - -function AddItemToOutputList(itemName, itemCount) - global.outputList[itemName] = (global.outputList[itemName] or 0) + itemCount -end - - - -function ExportInputList() - local exportStrings = {} - for k,v in pairs(global.inputList) do - exportStrings[#exportStrings + 1] = k.." "..v.."\n" - end - global.inputList = {} - if #exportStrings > 0 then - - --only write to file once as i/o is slow - --it's much faster to concatenate all the lines with table.concat - --instead of doing it with the .. operator - game.write_file(OUTPUT_FILE, table.concat(exportStrings), true, global.write_file_player or 0) - end -end - -function ExportOutputList() - local exportStrings = {} - for k,v in pairs(global.outputList) do - exportStrings[#exportStrings + 1] = k.." "..v.."\n" - end - global.outputList = {} - if #exportStrings > 0 then - - --only write to file once as i/o is slow - --it's much faster to concatenate all the lines with table.concat - --instead of doing it with the .. operator - game.write_file(ORDER_FILE, table.concat(exportStrings), true, global.write_file_player or 0) - end -end - - -function RequestItemsFromStorage(itemName, itemCount) - --if result is nil then there is no items in storage - --which means that no items can be given - if global.itemStorage[itemName] == nil then - return 0 - end - --if the number of items in storage is lower than the number of items - --requested then take the number of items there are left otherwise take the requested amount - local itemsTakenFromStorage = math.min(global.itemStorage[itemName], itemCount) - global.itemStorage[itemName] = global.itemStorage[itemName] - itemsTakenFromStorage - - return itemsTakenFromStorage -end - -function GiveItemsToStorage(itemName, itemCount) - --if this is called for the first time for an item then the result - --is nil. if that's the case then set the result to 0 so it can - --be used in arithmetic operations - global.itemStorage[itemName] = global.itemStorage[itemName] or 0 - global.itemStorage[itemName] = global.itemStorage[itemName] + itemCount -end - - - -function AddFrameToRXBuffer(frame) - -- Add a frame to the buffer. return remaining space in buffer - local validsignals = { - ["virtual"] = game.virtual_signal_prototypes, - ["fluid"] = game.fluid_prototypes, - ["item"] = game.item_prototypes - } - - global.rxBuffer = global.rxBuffer or {} - - -- if buffer is full, drop frame - if #global.rxBuffer >= MAX_RX_BUFFER_SIZE then return 0 end - - -- frame = {{count=42,name="signal-grey",type="virtual"},{...},...} - local signals = {} - local index = 1 - - for _,signal in pairs(frame) do - if validsignals[signal.type] and validsignals[signal.type][signal.name] then - signals[index] = - { - index=index, - count=signal.count, - signal={ name=signal.name, type=signal.type } - } - index = index + 1 - --TODO: break if too many? - --TODO: error token on mismatched signals? maybe mismatch1-n signals? - end - end - - if index > 1 then table.insert(global.rxBuffer,signals) end - - return MAX_RX_BUFFER_SIZE - #global.rxBuffer -end - -function HandleTXCombinators() - -- Check all TX Combinators, and if condition satisfied, add frame to transmit buffer - - -- frame = {{count=42,name="signal-grey",type="virtual"},{...},...} - local signals = {["item"]={},["virtual"]={},["fluid"]={}} - for i,txControl in pairs(global.txControls) do - if txControl.valid then - local frame = txControl.signals_last_tick - if frame then - for _,signal in pairs(frame) do - signals[signal.signal.type][signal.signal.name]= - (signals[signal.signal.type][signal.signal.name] or 0) + signal.count - end - end - end - end - - local frame = {} - for type,arr in pairs(signals) do - for name,count in pairs(arr) do - table.insert(frame,{count=count,name=name,type=type}) - end - end - - if #frame > 0 then - if global.worldID then - table.insert(frame,1,{count=global.worldID,name="signal-srcid",type="virtual"}) - end - table.insert(frame,{count=game.tick,name="signal-srctick",type="virtual"}) - game.write_file(TX_BUFFER_FILE, json:encode(frame).."\n", true, global.write_file_player or 0) - - -- Loopback for testing - --AddFrameToRXBuffer(frame) - - end -end - -function SetRXCombinators() - -- if the RX buffer is not empty, get a frame from it and output on all RX Combinators - if global.rxBuffer and #global.rxBuffer > 0 then - local frame = table.remove(global.rxBuffer) - for i,rxControl in pairs(global.rxControls) do - if rxControl.valid then - rxControl.parameters={parameters=frame} - rxControl.enabled=true - end - end - end -end - -function ClearRXCombinators() - -- Clear all RX Combinators. - -- This makes them emit pulses, which are easier to - -- detect than slowly changing continusous signals. - for i,rxControl in pairs(global.rxControls) do - if rxControl.valid then - rxControl.enabled=false - end - end -end - -function UpdateInvCombinators() - -- Update all inventory Combinators - -- Prepare a frame from the last inventory report, plus any virtuals - local invframe = {} - if global.worldID then - table.insert(invframe,{count=global.worldID,index=#invframe+1,signal={name="signal-localid",type="virtual"}}) - end - - local items = game.item_prototypes - local fluids = game.fluid_prototypes - local virtuals = game.virtual_signal_prototypes - if global.invdata then - for name,count in pairs(global.invdata) do - if virtuals[name] then - invframe[#invframe+1] = {count=count,index=#invframe+1,signal={name=name,type="virtual"}} - elseif fluids[name] then - invframe[#invframe+1] = {count=count,index=#invframe+1,signal={name=name,type="fluid"}} - elseif items[name] then - invframe[#invframe+1] = {count=count,index=#invframe+1,signal={name=name,type="item"}} - end - end - end - - for i,invControl in pairs(global.invControls) do - if invControl.valid then - invControl.parameters={parameters=invframe} - invControl.enabled=true - end - end - -end - ---[[ Remote Thing ]]-- -remote.add_interface("clusterio", -{ - import = function(itemName, itemCount) - GiveItemsToStorage(itemName, itemCount) - end, - importMany = function(jsonString) - local items = json:decode(jsonString) - for k, item in pairs(items) do - for itemName, itemCount in pairs(item) do - GiveItemsToStorage(itemName, itemCount) - end - end - end, - printStorage = function() - local items = "" - for itemName, itemCount in pairs(global.itemStorage) do - items = items.."\n"..itemName..": "..tostring(itemCount) - end - game.print(items) - end, - reset = Reset, - receiveFrame = function(jsonframe) - local frame = json:decode(jsonframe) - -- frame = {tick=123456,frame={{count=42,name="signal-grey",type="virtual"},{...},...}} - return AddFrameToRXBuffer(frame) - end, - receiveMany = function(jsonframes) - local frames = json:decode(jsonframes) - local buffer - for _,frame in pairs(frames) do - buffer = AddFrameToRXBuffer(frame) - if buffer==0 then return 0 end - end - return buffer - end, - setFilePlayer = function(i) - global.write_file_player = i - end, - receiveInventory = function(jsoninvdata) - local invdata = json:decode(jsoninvdata) - -- invdata = {["iron-plates"]=1234,["copper-plates"]=5678,...} - global.invdata = invdata - UpdateInvCombinators() - end, - setWorldID = function(newid) - global.worldID = newid - UpdateInvCombinators() - end -}) diff --git a/clusterio_0.0.3/data.lua b/clusterio_0.0.3/data.lua deleted file mode 100644 index e7744ad9..00000000 --- a/clusterio_0.0.3/data.lua +++ /dev/null @@ -1,279 +0,0 @@ -require("util") -require("config") - -function ChangePictureFilename(entity, path, newFilename) - if newFilename ~= nil then - local filenamePath = entity[path[1]] - for i = 2, #path do - filenamePath = filenamePath[path[i]] - end - filenamePath.filename = newFilename - end -end - -function MakeLogisticEntity(entity, name, pictureFilename, pictureTablePath, iconPath) - entity.name = name - entity.minable.result = name - --if no picture is defined then use the default one - ChangePictureFilename(entity, pictureTablePath, pictureFilename) - --if no icon is defined then use the default one - entity.icon = iconPath or entity.icon - - -- add the entity to a technology so it can be unlocked - --local wasAddedToTech = AddEntityToTech("construction-robotics", name) - - data:extend( - { - -- add the entity - entity, - -- add the recipe for the entity - { - type = "recipe", - name = name, - --if the recipe was succesfully attached to the tech then the recipe - --shouldn't be enabled to begin with. - --but if the recipe isn't attached to a tech then it should - --be enabled to begin with because otherwise the player can never use the item ingame - enabled = true, - ingredients = - { - {"steel-chest", 1}, - {"electronic-circuit", 3}, - {"advanced-circuit", 1} - }, - result = name, - requester_paste_multiplier = 4 - }, - { - type = "item", - name = name, - icon = entity.icon, - flags = {"goes-to-quickbar"}, - subgroup = "storage", - order = "a[items]-b["..name.."]", - place_result = name, - stack_size = 50 - } - }) - return entity -end - ---adds a recipe to a tech and returns true or if that fails returns false -function AddEntityToTech(techName, name) - --can't add the recipe to the tech if it doesn't exist - if data.raw["technology"][techName] ~= nil then - local effects = data.raw["technology"][techName].effects - --if another mod removed the effects or made it nil then make a new table to put the recipe in - effects = effects or {} - --insert the recipe as an unlock when the research is done - effects[#effects + 1] = { - type = "unlock-recipe", - recipe = name - } - --if a new table for the effects is made then the effects has to be attached to the - -- tech again because the table won't otherwise be owned by the tech - data.raw["technology"][techName].effects = effects - return true - end - return false -end - - ---make chests -MakeLogisticEntity(table.deepcopy(data.raw["logistic-container"]["logistic-chest-requester"]), OUTPUT_CHEST_NAME, OUTPUT_CHEST_PICTURE_PATH, { "picture" }, OUTPUT_CHEST_ICON_PATH) -MakeLogisticEntity(table.deepcopy(data.raw["container"]["iron-chest"]), INPUT_CHEST_NAME, INPUT_CHEST_PICTURE_PATH, { "picture" }, INPUT_CHEST_ICON_PATH) - ---make tanks -MakeLogisticEntity(table.deepcopy(data.raw["storage-tank"]["storage-tank"]), INPUT_TANK_NAME, INPUT_TANK_PICTURE_PATH, { "pictures", "picture", "sheet" }, INPUT_TANK_ICON_PATH) - --------------------------------------------------------- ---[[This section is purely to create the output tank]]-- --------------------------------------------------------- - -local CRAFING_FLUID_CATEGORY_NAME = "crafting-fluids" - -data:extend( -{ - { - type = "recipe-category", - name = CRAFING_FLUID_CATEGORY_NAME - } -}) - -local fluidCreator = MakeLogisticEntity(table.deepcopy(data.raw["assembling-machine"]["assembling-machine-3"]), OUTPUT_TANK_NAME, OUTPUT_TANK_PICTURE_PATH, { "animation" }, OUTPUT_TANK_ICON_PATH) -fluidCreator.fluid_boxes = -{ - { - production_type = "output", - pipe_picture = assembler3pipepictures(), - pipe_covers = pipecoverspictures(), - base_area = 250, - base_level = 1, - pipe_connections = - { - { - type="output", position = {0, 2} - } - } - }, - off_when_no_fluid_recipe = false -} -fluidCreator.crafting_categories = {CRAFING_FLUID_CATEGORY_NAME} -fluidCreator.energy_usage = "1kW" -fluidCreator.ingredient_count = 1 -fluidCreator.module_specification.module_slots = 0 - -for k,v in pairs(data.raw.fluid) do - data:extend( - { - { - type = "recipe", - name = ("get-"..v.name), - icon = v.icon, - category = CRAFING_FLUID_CATEGORY_NAME, - --localised_name = {v.name}, - energy_required = 1, - subgroup = "barrel", - order = "b[fill-crude-oil-barrel]", - enabled = true, - ingredients = {}, - results= - { - {type="fluid", name=v.name, amount=-1} - } - } - }) -end - - --- Virtual signals -data:extend{ - { - type = "item-subgroup", - name = "virtual-signal-clusterio", - group = "signals", - order = "e" - }, - { - type = "virtual-signal", - name = "signal-srctick", - icon = "__base__/graphics/icons/signal/signal_grey.png", - subgroup = "virtual-signal-clusterio", - order = "e[clusterio]-[1srctick]" - }, - { - type = "virtual-signal", - name = "signal-srcid", - icon = "__base__/graphics/icons/signal/signal_grey.png", - subgroup = "virtual-signal-clusterio", - order = "e[clusterio]-[2srcid]" - }, - { - type = "virtual-signal", - name = "signal-localid", - icon = "__base__/graphics/icons/signal/signal_grey.png", - subgroup = "virtual-signal-clusterio", - order = "e[clusterio]-[3localid]" - }, - { - type = "virtual-signal", - name = "signal-unixtime", - icon = "__base__/graphics/icons/signal/signal_grey.png", - subgroup = "virtual-signal-clusterio", - order = "e[clusterio]-[4unixtime]" - }, -} - --- TX Combinator -local tx = table.deepcopy(data.raw["decider-combinator"]["decider-combinator"]) -tx.name = TX_COMBINATOR_NAME -tx.minable.result = TX_COMBINATOR_NAME -data:extend{ - tx, - { - type = "item", - name = TX_COMBINATOR_NAME, - icon = tx.icon, - flags = {"goes-to-quickbar"}, - subgroup = "storage", - place_result=TX_COMBINATOR_NAME, - order = "a[items]-b["..TX_COMBINATOR_NAME.."]", - stack_size = 50, - }, - { - type = "recipe", - name = TX_COMBINATOR_NAME, - enabled = true, -- TODO do this on a tech somewhere - ingredients = - { - {"decider-combinator", 1}, - {"electronic-circuit", 3}, - {"advanced-circuit", 1} - }, - result = TX_COMBINATOR_NAME, - requester_paste_multiplier = 1 - }, -} - --- RX Combinator -local rx = table.deepcopy(data.raw["constant-combinator"]["constant-combinator"]) -rx.name = RX_COMBINATOR_NAME -rx.minable.result = RX_COMBINATOR_NAME -rx.item_slot_count = 100 -data:extend{ - rx, - { - type = "item", - name = RX_COMBINATOR_NAME, - icon = rx.icon, - flags = {"goes-to-quickbar"}, - subgroup = "storage", - place_result=RX_COMBINATOR_NAME, - order = "a[items]-b["..RX_COMBINATOR_NAME.."]", - stack_size = 50, - }, - { - type = "recipe", - name = RX_COMBINATOR_NAME, - enabled = true, -- TODO do this on a tech somewhere - ingredients = - { - {"constant-combinator", 1}, - {"electronic-circuit", 3}, - {"advanced-circuit", 1} - }, - result = RX_COMBINATOR_NAME, - requester_paste_multiplier = 1 - }, -} --- Inventory Combinator -local inv = table.deepcopy(data.raw["constant-combinator"]["constant-combinator"]) -inv.name = INV_COMBINATOR_NAME -inv.minable.result = INV_COMBINATOR_NAME -inv.item_slot_count = 100 -data:extend{ - inv, - { - type = "item", - name = INV_COMBINATOR_NAME, - icon = inv.icon, - flags = {"goes-to-quickbar"}, - subgroup = "storage", - place_result=INV_COMBINATOR_NAME, - order = "a[items]-b["..INV_COMBINATOR_NAME.."]", - stack_size = 50, - }, - { - type = "recipe", - name = INV_COMBINATOR_NAME, - enabled = true, -- TODO do this on a tech somewhere - ingredients = - { - {"constant-combinator", 1}, - {"electronic-circuit", 3}, - {"advanced-circuit", 1} - }, - result = INV_COMBINATOR_NAME, - requester_paste_multiplier = 1 - }, -} diff --git a/clusterio_0.0.3/graphics/icons/none.png b/clusterio_0.0.3/graphics/icons/none.png deleted file mode 100644 index 67c44794f42288a265000d5c791fbd33533f3c08..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 227 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}Ea{HEjtmSN z`?>!lvI6;>1s;*b3=DjSL74G){)!Z!pj3%#L`iUdT1k0gQ7S_~VrE{6o}X)oLYc9i zsh**M!Iz?iKvhAWE{-7)?nh53GBOzOux$Kg{?}NbhFNiEZoHxQ8KKQbbV#H&SAPim WvTO_Yubn{C89ZJ6T-G@yGywn(9!O9C diff --git a/clusterio_0.0.3/info.json b/clusterio_0.0.3/info.json deleted file mode 100644 index 08fd900d..00000000 --- a/clusterio_0.0.3/info.json +++ /dev/null @@ -1,11 +0,0 @@ - -{ -"name": "clusterio", -"title": "Clusterio", -"version": "0.0.3", -"date": "19-08-2016", -"author": "Danielv123 & Keyboardhack & justarandomgeek", -"dependencies": ["base >= 0.13"], -"factorio_version": "0.14", -"description": "Adds 2 chests to transfer items between saves and servers over the internet." -} diff --git a/clusterio_0.0.3/json.lua b/clusterio_0.0.3/json.lua deleted file mode 100644 index cf98a19e..00000000 --- a/clusterio_0.0.3/json.lua +++ /dev/null @@ -1,851 +0,0 @@ --- Simple JSON encoding and decoding in pure Lua. --- --- Copyright 2010-2016 Jeffrey Friedl --- http://regex.info/blog/ --- Latest version: http://regex.info/blog/lua/json --- --- This code is released under a Creative Commons CC-BY "Attribution" License: --- http://creativecommons.org/licenses/by/3.0/deed.en_US --- --- It can be used for any purpose so long as the copyright notice above, --- the web-page links above, and the 'AUTHOR_NOTE' string below are --- maintained. Enjoy. --- -local VERSION = 20160728.17 -- version history at end of file -local AUTHOR_NOTE = "-[ JSON.lua package by Jeffrey Friedl (http://regex.info/blog/lua/json) version 20160728.17 ]-" - --- --- The 'AUTHOR_NOTE' variable exists so that information about the source --- of the package is maintained even in compiled versions. It's also --- included in OBJDEF below mostly to quiet warnings about unused variables. --- -local OBJDEF = { - VERSION = VERSION, - AUTHOR_NOTE = AUTHOR_NOTE, -} - -local default_pretty_indent = " " -local default_pretty_options = { pretty = true, align_keys = false, indent = default_pretty_indent } - -local isArray = { __tostring = function() return "JSON array" end } isArray.__index = isArray -local isObject = { __tostring = function() return "JSON object" end } isObject.__index = isObject - -function OBJDEF:newArray(tbl) - return setmetatable(tbl or {}, isArray) -end - -function OBJDEF:newObject(tbl) - return setmetatable(tbl or {}, isObject) -end - - - - -local function getnum(op) - return type(op) == 'number' and op or op.N -end - -local isNumber = { - __index = isNumber, - - __tostring = function(T) return T.S end, - __unm = function(op) return getnum(op) end, - - __concat = function(op1, op2) return tostring(op1) .. tostring(op2) end, - __add = function(op1, op2) return getnum(op1) + getnum(op2) end, - __sub = function(op1, op2) return getnum(op1) - getnum(op2) end, - __mul = function(op1, op2) return getnum(op1) * getnum(op2) end, - __div = function(op1, op2) return getnum(op1) / getnum(op2) end, - __mod = function(op1, op2) return getnum(op1) % getnum(op2) end, - __pow = function(op1, op2) return getnum(op1) ^ getnum(op2) end, - __lt = function(op1, op2) return getnum(op1) < getnum(op2) end, - __eq = function(op1, op2) return getnum(op1) == getnum(op2) end, - __le = function(op1, op2) return getnum(op1) <= getnum(op2) end, -} - -function OBJDEF:asNumber(item) - - if getmetatable(item) == isNumber then - -- it's already a JSON number object. - return item - elseif type(item) == 'table' and type(item.S) == 'string' and type(item.N) == 'number' then - -- it's a number-object table that lost its metatable, so give it one - return setmetatable(item, isNumber) - else - -- the normal situation... given a number or a string representation of a number.... - local holder = { - S = tostring(item), -- S is the representation of the number as a string, which remains precise - N = tonumber(item), -- N is the number as a Lua number. - } - return setmetatable(holder, isNumber) - end -end - -local function unicode_codepoint_as_utf8(codepoint) - -- - -- codepoint is a number - -- - if codepoint <= 127 then - return string.char(codepoint) - - elseif codepoint <= 2047 then - -- - -- 110yyyxx 10xxxxxx <-- useful notation from http://en.wikipedia.org/wiki/Utf8 - -- - local highpart = math.floor(codepoint / 0x40) - local lowpart = codepoint - (0x40 * highpart) - return string.char(0xC0 + highpart, - 0x80 + lowpart) - - elseif codepoint <= 65535 then - -- - -- 1110yyyy 10yyyyxx 10xxxxxx - -- - local highpart = math.floor(codepoint / 0x1000) - local remainder = codepoint - 0x1000 * highpart - local midpart = math.floor(remainder / 0x40) - local lowpart = remainder - 0x40 * midpart - - highpart = 0xE0 + highpart - midpart = 0x80 + midpart - lowpart = 0x80 + lowpart - - -- - -- Check for an invalid character (thanks Andy R. at Adobe). - -- See table 3.7, page 93, in http://www.unicode.org/versions/Unicode5.2.0/ch03.pdf#G28070 - -- - if ( highpart == 0xE0 and midpart < 0xA0 ) or - ( highpart == 0xED and midpart > 0x9F ) or - ( highpart == 0xF0 and midpart < 0x90 ) or - ( highpart == 0xF4 and midpart > 0x8F ) - then - return "?" - else - return string.char(highpart, - midpart, - lowpart) - end - - else - -- - -- 11110zzz 10zzyyyy 10yyyyxx 10xxxxxx - -- - local highpart = math.floor(codepoint / 0x40000) - local remainder = codepoint - 0x40000 * highpart - local midA = math.floor(remainder / 0x1000) - remainder = remainder - 0x1000 * midA - local midB = math.floor(remainder / 0x40) - local lowpart = remainder - 0x40 * midB - - return string.char(0xF0 + highpart, - 0x80 + midA, - 0x80 + midB, - 0x80 + lowpart) - end -end - -function OBJDEF:onDecodeError(message, text, location, etc) - if text then - if location then - message = string.format("%s at char %d of: %s", message, location, text) - else - message = string.format("%s: %s", message, text) - end - end - - if etc ~= nil then - message = message .. " (" .. OBJDEF:encode(etc) .. ")" - end - - if self.assert then - self.assert(false, message) - else - assert(false, message) - end -end - -OBJDEF.onDecodeOfNilError = OBJDEF.onDecodeError -OBJDEF.onDecodeOfHTMLError = OBJDEF.onDecodeError - -function OBJDEF:onEncodeError(message, etc) - if etc ~= nil then - message = message .. " (" .. OBJDEF:encode(etc) .. ")" - end - - if self.assert then - self.assert(false, message) - else - assert(false, message) - end -end - -local function grok_number(self, text, start, options) - -- - -- Grab the integer part - -- - local integer_part = text:match('^-?[1-9]%d*', start) - or text:match("^-?0", start) - - if not integer_part then - self:onDecodeError("expected number", text, start, options.etc) - end - - local i = start + integer_part:len() - - -- - -- Grab an optional decimal part - -- - local decimal_part = text:match('^%.%d+', i) or "" - - i = i + decimal_part:len() - - -- - -- Grab an optional exponential part - -- - local exponent_part = text:match('^[eE][-+]?%d+', i) or "" - - i = i + exponent_part:len() - - local full_number_text = integer_part .. decimal_part .. exponent_part - - if options.decodeNumbersAsObjects then - return OBJDEF:asNumber(full_number_text), i - end - - -- - -- If we're told to stringify under certain conditions, so do. - -- We punt a bit when there's an exponent by just stringifying no matter what. - -- I suppose we should really look to see whether the exponent is actually big enough one - -- way or the other to trip stringification, but I'll be lazy about it until someone asks. - -- - if (options.decodeIntegerStringificationLength - and - (integer_part:len() >= options.decodeIntegerStringificationLength or exponent_part:len() > 0)) - - or - - (options.decodeDecimalStringificationLength - and - (decimal_part:len() >= options.decodeDecimalStringificationLength or exponent_part:len() > 0)) - then - return full_number_text, i -- this returns the exact string representation seen in the original JSON - end - - - - local as_number = tonumber(full_number_text) - - if not as_number then - self:onDecodeError("bad number", text, start, options.etc) - end - - return as_number, i -end - - -local function grok_string(self, text, start, options) - - if text:sub(start,start) ~= '"' then - self:onDecodeError("expected string's opening quote", text, start, options.etc) - end - - local i = start + 1 -- +1 to bypass the initial quote - local text_len = text:len() - local VALUE = "" - while i <= text_len do - local c = text:sub(i,i) - if c == '"' then - return VALUE, i + 1 - end - if c ~= '\\' then - VALUE = VALUE .. c - i = i + 1 - elseif text:match('^\\b', i) then - VALUE = VALUE .. "\b" - i = i + 2 - elseif text:match('^\\f', i) then - VALUE = VALUE .. "\f" - i = i + 2 - elseif text:match('^\\n', i) then - VALUE = VALUE .. "\n" - i = i + 2 - elseif text:match('^\\r', i) then - VALUE = VALUE .. "\r" - i = i + 2 - elseif text:match('^\\t', i) then - VALUE = VALUE .. "\t" - i = i + 2 - else - local hex = text:match('^\\u([0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF])', i) - if hex then - i = i + 6 -- bypass what we just read - - -- We have a Unicode codepoint. It could be standalone, or if in the proper range and - -- followed by another in a specific range, it'll be a two-code surrogate pair. - local codepoint = tonumber(hex, 16) - if codepoint >= 0xD800 and codepoint <= 0xDBFF then - -- it's a hi surrogate... see whether we have a following low - local lo_surrogate = text:match('^\\u([dD][cdefCDEF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF])', i) - if lo_surrogate then - i = i + 6 -- bypass the low surrogate we just read - codepoint = 0x2400 + (codepoint - 0xD800) * 0x400 + tonumber(lo_surrogate, 16) - else - -- not a proper low, so we'll just leave the first codepoint as is and spit it out. - end - end - VALUE = VALUE .. unicode_codepoint_as_utf8(codepoint) - - else - - -- just pass through what's escaped - VALUE = VALUE .. text:match('^\\(.)', i) - i = i + 2 - end - end - end - - self:onDecodeError("unclosed string", text, start, options.etc) -end - -local function skip_whitespace(text, start) - - local _, match_end = text:find("^[ \n\r\t]+", start) -- [http://www.ietf.org/rfc/rfc4627.txt] Section 2 - if match_end then - return match_end + 1 - else - return start - end -end - -local grok_one -- assigned later - -local function grok_object(self, text, start, options) - - if text:sub(start,start) ~= '{' then - self:onDecodeError("expected '{'", text, start, options.etc) - end - - local i = skip_whitespace(text, start + 1) -- +1 to skip the '{' - - local VALUE = self.strictTypes and self:newObject { } or { } - - if text:sub(i,i) == '}' then - return VALUE, i + 1 - end - local text_len = text:len() - while i <= text_len do - local key, new_i = grok_string(self, text, i, options) - - i = skip_whitespace(text, new_i) - - if text:sub(i, i) ~= ':' then - self:onDecodeError("expected colon", text, i, options.etc) - end - - i = skip_whitespace(text, i + 1) - - local new_val, new_i = grok_one(self, text, i, options) - - VALUE[key] = new_val - - -- - -- Expect now either '}' to end things, or a ',' to allow us to continue. - -- - i = skip_whitespace(text, new_i) - - local c = text:sub(i,i) - - if c == '}' then - return VALUE, i + 1 - end - - if text:sub(i, i) ~= ',' then - self:onDecodeError("expected comma or '}'", text, i, options.etc) - end - - i = skip_whitespace(text, i + 1) - end - - self:onDecodeError("unclosed '{'", text, start, options.etc) -end - -local function grok_array(self, text, start, options) - if text:sub(start,start) ~= '[' then - self:onDecodeError("expected '['", text, start, options.etc) - end - - local i = skip_whitespace(text, start + 1) -- +1 to skip the '[' - local VALUE = self.strictTypes and self:newArray { } or { } - if text:sub(i,i) == ']' then - return VALUE, i + 1 - end - - local VALUE_INDEX = 1 - - local text_len = text:len() - while i <= text_len do - local val, new_i = grok_one(self, text, i, options) - - -- can't table.insert(VALUE, val) here because it's a no-op if val is nil - VALUE[VALUE_INDEX] = val - VALUE_INDEX = VALUE_INDEX + 1 - - i = skip_whitespace(text, new_i) - - -- - -- Expect now either ']' to end things, or a ',' to allow us to continue. - -- - local c = text:sub(i,i) - if c == ']' then - return VALUE, i + 1 - end - if text:sub(i, i) ~= ',' then - self:onDecodeError("expected comma or '['", text, i, options.etc) - end - i = skip_whitespace(text, i + 1) - end - self:onDecodeError("unclosed '['", text, start, options.etc) -end - - -grok_one = function(self, text, start, options) - -- Skip any whitespace - start = skip_whitespace(text, start) - - if start > text:len() then - self:onDecodeError("unexpected end of string", text, nil, options.etc) - end - - if text:find('^"', start) then - return grok_string(self, text, start, options.etc) - - elseif text:find('^[-0123456789 ]', start) then - return grok_number(self, text, start, options) - - elseif text:find('^%{', start) then - return grok_object(self, text, start, options) - - elseif text:find('^%[', start) then - return grok_array(self, text, start, options) - - elseif text:find('^true', start) then - return true, start + 4 - - elseif text:find('^false', start) then - return false, start + 5 - - elseif text:find('^null', start) then - return nil, start + 4 - - else - self:onDecodeError("can't parse JSON", text, start, options.etc) - end -end - -function OBJDEF:decode(text, etc, options) - -- - -- If the user didn't pass in a table of decode options, make an empty one. - -- - if type(options) ~= 'table' then - options = {} - end - - -- - -- If they passed in an 'etc' argument, stuff it into the options. - -- (If not, any 'etc' field in the options they passed in remains to be used) - -- - if etc ~= nil then - options.etc = etc - end - - - if type(self) ~= 'table' or self.__index ~= OBJDEF then - OBJDEF:onDecodeError("JSON:decode must be called in method format", nil, nil, options.etc) - end - - if text == nil then - self:onDecodeOfNilError(string.format("nil passed to JSON:decode()"), nil, nil, options.etc) - elseif type(text) ~= 'string' then - self:onDecodeError(string.format("expected string argument to JSON:decode(), got %s", type(text)), nil, nil, options.etc) - end - - if text:match('^%s*$') then - return nil - end - - if text:match('^%s*<') then - -- Can't be JSON... we'll assume it's HTML - self:onDecodeOfHTMLError(string.format("html passed to JSON:decode()"), text, nil, options.etc) - end - - -- - -- Ensure that it's not UTF-32 or UTF-16. - -- Those are perfectly valid encodings for JSON (as per RFC 4627 section 3), - -- but this package can't handle them. - -- - if text:sub(1,1):byte() == 0 or (text:len() >= 2 and text:sub(2,2):byte() == 0) then - self:onDecodeError("JSON package groks only UTF-8, sorry", text, nil, options.etc) - end - - -- - -- apply global options - -- - if options.decodeNumbersAsObjects == nil then - options.decodeNumbersAsObjects = self.decodeNumbersAsObjects - end - if options.decodeIntegerStringificationLength == nil then - options.decodeIntegerStringificationLength = self.decodeIntegerStringificationLength - end - if options.decodeDecimalStringificationLength == nil then - options.decodeDecimalStringificationLength = self.decodeDecimalStringificationLength - end - - local success, value = pcall(grok_one, self, text, 1, options) - - if success then - return value - else - -- if JSON:onDecodeError() didn't abort out of the pcall, we'll have received the error message here as "value", so pass it along as an assert. - if self.assert then - self.assert(false, value) - else - assert(false, value) - end - -- and if we're still here, return a nil and throw the error message on as a second arg - return nil, value - end -end - -local function backslash_replacement_function(c) - if c == "\n" then - return "\\n" - elseif c == "\r" then - return "\\r" - elseif c == "\t" then - return "\\t" - elseif c == "\b" then - return "\\b" - elseif c == "\f" then - return "\\f" - elseif c == '"' then - return '\\"' - elseif c == '\\' then - return '\\\\' - else - return string.format("\\u%04x", c:byte()) - end -end - -local chars_to_be_escaped_in_JSON_string - = '[' - .. '"' -- class sub-pattern to match a double quote - .. '%\\' -- class sub-pattern to match a backslash - .. '%z' -- class sub-pattern to match a null - .. '\001' .. '-' .. '\031' -- class sub-pattern to match control characters - .. ']' - - -local LINE_SEPARATOR_as_utf8 = unicode_codepoint_as_utf8(0x2028) -local PARAGRAPH_SEPARATOR_as_utf8 = unicode_codepoint_as_utf8(0x2029) -local function json_string_literal(value, options) - local newval = value:gsub(chars_to_be_escaped_in_JSON_string, backslash_replacement_function) - if options.stringsAreUtf8 then - -- - -- This feels really ugly to just look into a string for the sequence of bytes that we know to be a particular utf8 character, - -- but utf8 was designed purposefully to make this kind of thing possible. Still, feels dirty. - -- I'd rather decode the byte stream into a character stream, but it's not technically needed so - -- not technically worth it. - -- - newval = newval:gsub(LINE_SEPARATOR_as_utf8, '\\u2028'):gsub(PARAGRAPH_SEPARATOR_as_utf8,'\\u2029') - end - return '"' .. newval .. '"' -end - -local function object_or_array(self, T, etc) - -- - -- We need to inspect all the keys... if there are any strings, we'll convert to a JSON - -- object. If there are only numbers, it's a JSON array. - -- - -- If we'll be converting to a JSON object, we'll want to sort the keys so that the - -- end result is deterministic. - -- - local string_keys = { } - local number_keys = { } - local number_keys_must_be_strings = false - local maximum_number_key - - for key in pairs(T) do - if type(key) == 'string' then - table.insert(string_keys, key) - elseif type(key) == 'number' then - table.insert(number_keys, key) - if key <= 0 or key >= math.huge then - number_keys_must_be_strings = true - elseif not maximum_number_key or key > maximum_number_key then - maximum_number_key = key - end - else - self:onEncodeError("can't encode table with a key of type " .. type(key), etc) - end - end - - if #string_keys == 0 and not number_keys_must_be_strings then - -- - -- An empty table, or a numeric-only array - -- - if #number_keys > 0 then - return nil, maximum_number_key -- an array - elseif tostring(T) == "JSON array" then - return nil - elseif tostring(T) == "JSON object" then - return { } - else - -- have to guess, so we'll pick array, since empty arrays are likely more common than empty objects - return nil - end - end - - table.sort(string_keys) - - local map - if #number_keys > 0 then - -- - -- If we're here then we have either mixed string/number keys, or numbers inappropriate for a JSON array - -- It's not ideal, but we'll turn the numbers into strings so that we can at least create a JSON object. - -- - - if self.noKeyConversion then - self:onEncodeError("a table with both numeric and string keys could be an object or array; aborting", etc) - end - - -- - -- Have to make a shallow copy of the source table so we can remap the numeric keys to be strings - -- - map = { } - for key, val in pairs(T) do - map[key] = val - end - - table.sort(number_keys) - - -- - -- Throw numeric keys in there as strings - -- - for _, number_key in ipairs(number_keys) do - local string_key = tostring(number_key) - if map[string_key] == nil then - table.insert(string_keys , string_key) - map[string_key] = T[number_key] - else - self:onEncodeError("conflict converting table with mixed-type keys into a JSON object: key " .. number_key .. " exists both as a string and a number.", etc) - end - end - end - - return string_keys, nil, map -end - --- --- Encode --- --- 'options' is nil, or a table with possible keys: --- --- pretty -- If true, return a pretty-printed version. --- --- indent -- A string (usually of spaces) used to indent each nested level. --- --- align_keys -- If true, align all the keys when formatting a table. --- --- null -- If this exists with a string value, table elements with this value are output as JSON null. --- --- stringsAreUtf8 -- If true, consider Lua strings not as a sequence of bytes, but as a sequence of UTF-8 characters. --- (Currently, the only practical effect of setting this option is that Unicode LINE and PARAGRAPH --- separators, if found in a string, are encoded with a JSON escape instead of as raw UTF-8. --- The JSON is valid either way, but encoding this way, apparently, allows the resulting JSON --- to also be valid Java.) --- --- -local encode_value -- must predeclare because it calls itself -function encode_value(self, value, parents, etc, options, indent, for_key) - - -- - -- keys in a JSON object can never be null, so we don't even consider options.null when converting a key value - -- - if value == nil or (not for_key and options and options.null and value == options.null) then - return 'null' - - elseif type(value) == 'string' then - return json_string_literal(value, options) - - elseif type(value) == 'number' then - if value ~= value then - -- - -- NaN (Not a Number). - -- JSON has no NaN, so we have to fudge the best we can. This should really be a package option. - -- - return "null" - elseif value >= math.huge then - -- - -- Positive infinity. JSON has no INF, so we have to fudge the best we can. This should - -- really be a package option. Note: at least with some implementations, positive infinity - -- is both ">= math.huge" and "<= -math.huge", which makes no sense but that's how it is. - -- Negative infinity is properly "<= -math.huge". So, we must be sure to check the ">=" - -- case first. - -- - return "1e+9999" - elseif value <= -math.huge then - -- - -- Negative infinity. - -- JSON has no INF, so we have to fudge the best we can. This should really be a package option. - -- - return "-1e+9999" - else - return tostring(value) - end - - elseif type(value) == 'boolean' then - return tostring(value) - - elseif type(value) ~= 'table' then - self:onEncodeError("can't convert " .. type(value) .. " to JSON", etc) - - elseif getmetatable(value) == isNumber then - return tostring(value) - else - -- - -- A table to be converted to either a JSON object or array. - -- - local T = value - - if type(options) ~= 'table' then - options = {} - end - if type(indent) ~= 'string' then - indent = "" - end - - if parents[T] then - self:onEncodeError("table " .. tostring(T) .. " is a child of itself", etc) - else - parents[T] = true - end - - local result_value - - local object_keys, maximum_number_key, map = object_or_array(self, T, etc) - if maximum_number_key then - -- - -- An array... - -- - local ITEMS = { } - for i = 1, maximum_number_key do - table.insert(ITEMS, encode_value(self, T[i], parents, etc, options, indent)) - end - - if options.pretty then - result_value = "[ " .. table.concat(ITEMS, ", ") .. " ]" - else - result_value = "[" .. table.concat(ITEMS, ",") .. "]" - end - - elseif object_keys then - -- - -- An object - -- - local TT = map or T - - if options.pretty then - - local KEYS = { } - local max_key_length = 0 - for _, key in ipairs(object_keys) do - local encoded = encode_value(self, tostring(key), parents, etc, options, indent, true) - if options.align_keys then - max_key_length = math.max(max_key_length, #encoded) - end - table.insert(KEYS, encoded) - end - local key_indent = indent .. tostring(options.indent or "") - local subtable_indent = key_indent .. string.rep(" ", max_key_length) .. (options.align_keys and " " or "") - local FORMAT = "%s%" .. string.format("%d", max_key_length) .. "s: %s" - - local COMBINED_PARTS = { } - for i, key in ipairs(object_keys) do - local encoded_val = encode_value(self, TT[key], parents, etc, options, subtable_indent) - table.insert(COMBINED_PARTS, string.format(FORMAT, key_indent, KEYS[i], encoded_val)) - end - result_value = "{\n" .. table.concat(COMBINED_PARTS, ",\n") .. "\n" .. indent .. "}" - - else - - local PARTS = { } - for _, key in ipairs(object_keys) do - local encoded_val = encode_value(self, TT[key], parents, etc, options, indent) - local encoded_key = encode_value(self, tostring(key), parents, etc, options, indent, true) - table.insert(PARTS, string.format("%s:%s", encoded_key, encoded_val)) - end - result_value = "{" .. table.concat(PARTS, ",") .. "}" - - end - else - -- - -- An empty array/object... we'll treat it as an array, though it should really be an option - -- - result_value = "[]" - end - - parents[T] = false - return result_value - end -end - - -function OBJDEF:encode(value, etc, options) - if type(self) ~= 'table' or self.__index ~= OBJDEF then - OBJDEF:onEncodeError("JSON:encode must be called in method format", etc) - end - - -- - -- If the user didn't pass in a table of decode options, make an empty one. - -- - if type(options) ~= 'table' then - options = {} - end - - return encode_value(self, value, {}, etc, options) -end - -function OBJDEF:encode_pretty(value, etc, options) - if type(self) ~= 'table' or self.__index ~= OBJDEF then - OBJDEF:onEncodeError("JSON:encode_pretty must be called in method format", etc) - end - - -- - -- If the user didn't pass in a table of decode options, use the default pretty ones - -- - if type(options) ~= 'table' then - options = default_pretty_options - end - - return encode_value(self, value, {}, etc, options) -end - -function OBJDEF.__tostring() - return "JSON encode/decode package" -end - -OBJDEF.__index = OBJDEF - -function OBJDEF:new(args) - local new = { } - - if args then - for key, val in pairs(args) do - new[key] = val - end - end - - return setmetatable(new, OBJDEF) -end - -return OBJDEF:new() \ No newline at end of file diff --git a/clusterio_0.0.3/locale/en/item-names.cfg b/clusterio_0.0.3/locale/en/item-names.cfg deleted file mode 100644 index d7561293..00000000 --- a/clusterio_0.0.3/locale/en/item-names.cfg +++ /dev/null @@ -1,22 +0,0 @@ -[entity-name] -put-chest=Put chest -get-chest=Get chest -put-tank=Put tank -get-tank=Get tank -put-combinator=Transmit combinator -get-combinator=Receive combinator -inventory-combinator=Inventory combinator - -[item-name] -put-chest=Put chest -get-chest=Get chest -put-tank=Put tank -get-tank=Get tank -put-combinator=Transmit combinator -get-combinator=Receive combinator -inventory-combinator=Inventory combinator - -[virtual-signal-name] -signal-srctick=Source World Tick -signal-srcid=Source World ID -signal-localid=Local World ID diff --git a/package.json b/package.json index 829c3e21..f9f39430 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "nedb": "^1.8.0", "needle": "^1.1.0", "portfinder": "^1.0.9", - "simple-rcon": "^0.3.1" + "simple-rcon": "^0.3.1", + "sync-request": "^3.0.1" } } From 12d1631922969b42f446137d8731dc8bfe098c96 Mon Sep 17 00:00:00 2001 From: Danielv123 Date: Tue, 3 Jan 2017 20:21:15 +0100 Subject: [PATCH 05/11] Fixed downloading of mods with https --- client.js | 32 +++++++++++++++++++++----------- package.json | 3 +++ 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/client.js b/client.js index cb0b8bf6..f6c65d65 100644 --- a/client.js +++ b/client.js @@ -1,8 +1,10 @@ -var fs = require('fs'); +var fs = require('fs-extra'); +var https = require('follow-redirects').https; var needle = require("needle"); var child_process = require('child_process'); -var path = require('path') +var path = require('path'); var syncRequest = require('sync-request'); +var ncp = require('ncp').ncp; // require config.json var config = require('./config'); @@ -67,10 +69,10 @@ if (!command || command == "help" || command == "--help") { let name = JSON.parse(res.getBody())[0].assets[0].name; if(url) { console.log(url); - let out = fs.createWriteStream('sharedMods/' + name); - needle.get(url).pipe(out).on('finish', function() { - console.log("Downloaded " + name); - process.exit(1); + var file = fs.createWriteStream("sharedMods/"+name); + var request = https.get(url, function(response) { + response.pipe(file); + console.log("Downloaded "+name) }); } } else if (command == "start" && typeof instance == "string" && instance != "/" && !fs.existsSync(instancedirectory)) { @@ -82,7 +84,7 @@ if (!command || command == "help" || command == "--help") { fs.writeFileSync(instancedirectory + "/script-output/orders.txt", "") fs.writeFileSync(instancedirectory + "/script-output/txbuffer.txt", "") fs.mkdirSync(instancedirectory + "/mods/") - fs.symlinkSync('../../../sharedMods', instancedirectory + "/mods", 'junction') + // 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__executable__/../../data\r\n\ write-data=__PATH__executable__/../../../instances/" + instance + "\r\n\ @@ -123,14 +125,20 @@ write-data=__PATH__executable__/../../../instances/" + instance + "\r\n\ ] ) console.log("Instance created!") -} else if (command == "start" && typeof instance == "string" && instance != "/" && !fs.existsSync(instancedirectory)) { +} else if (command == "start" && typeof instance == "string" && instance != "/" && fs.existsSync(instancedirectory)) { // Exit if no instance specified (it should be, just a safeguard) if(instancedirectory != "./instances/undefined"){ var instanceconfig = require(instancedirectory + '/config'); } else { process.exit(1) } - + + // move mods from ./sharedMods to the instances mod directory + console.log("Moving shared mods...") + //ncp("./sharedMods/", instancedirectory + "/mods", function (err) { + + + fs.copySync('sharedMods', instancedirectory + "/mods") process.on('SIGINT', function () { @@ -138,7 +146,7 @@ write-data=__PATH__executable__/../../../instances/" + instance + "\r\n\ //serverprocess.stdin.write('/quit') }); - + // Spawn factorio server //var serverprocess = child_process.exec(commandline) var serverprocess = child_process.spawn( './' + config.factorioDirectory + '/bin/x64/factorio', [ @@ -166,7 +174,7 @@ write-data=__PATH__executable__/../../../instances/" + instance + "\r\n\ console.log('ERR: ' + chunk); }) - // connect us to the server with rcon + // connect to the server with rcon // IP, port, password var Rcon = require('simple-rcon'); var client = new Rcon({ @@ -380,3 +388,5 @@ String.prototype.hashCode = function () { } return hash; } + + diff --git a/package.json b/package.json index f9f39430..79b26230 100644 --- a/package.json +++ b/package.json @@ -17,9 +17,12 @@ "canvasjs": "^1.8.1", "child_process": "^1.0.2", "express": "^4.14.0", + "follow-redirects": "^1.2.1", "fs": "0.0.2", + "fs-extra": "^1.0.0", "getmac": "^1.2.1", "mkdirp": "^0.5.1", + "ncp": "^2.0.0", "nedb": "^1.8.0", "needle": "^1.1.0", "portfinder": "^1.0.9", From e16d7b43ed10fd2897fb8dbba56555f6d3f22e15 Mon Sep 17 00:00:00 2001 From: Danielv123 Date: Wed, 4 Jan 2017 10:19:17 +0100 Subject: [PATCH 06/11] Gitignore and require moved to top --- .gitignore | 1 + client.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index a9392b83..fc0725a7 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,4 @@ node_modules Factorio* /factorio /instances +/sharedMods diff --git a/client.js b/client.js index f6c65d65..b04fca05 100644 --- a/client.js +++ b/client.js @@ -5,6 +5,7 @@ var child_process = require('child_process'); var path = require('path'); var syncRequest = require('sync-request'); var ncp = require('ncp').ncp; +var Rcon = require('simple-rcon'); // require config.json var config = require('./config'); @@ -176,7 +177,6 @@ write-data=__PATH__executable__/../../../instances/" + instance + "\r\n\ // connect to the server with rcon // IP, port, password - var Rcon = require('simple-rcon'); var client = new Rcon({ host: 'localhost', port: instanceconfig.clientPort, From 0f7916229abf0bab5456527f89b8844a2935206c Mon Sep 17 00:00:00 2001 From: Danielv123 Date: Thu, 5 Jan 2017 13:19:21 +0100 Subject: [PATCH 07/11] Function to get a hash from an instances mods --- client.js | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++-- package.json | 1 + 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/client.js b/client.js index b04fca05..1458dd60 100644 --- a/client.js +++ b/client.js @@ -6,6 +6,7 @@ var path = require('path'); var syncRequest = require('sync-request'); var ncp = require('ncp').ncp; var Rcon = require('simple-rcon'); +var hashFiles = require('hash-files'); // require config.json var config = require('./config'); @@ -388,5 +389,67 @@ String.prototype.hashCode = function () { } return hash; } - - +// string, function +// [{modname:string,hash:string}, ... ] +function hashMods(instanceName, callback) { + if(!callback) { + throw "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 == mods.length + instanceMods.length) { + callback(hashedMods); + } + //console.log(modname) + } + let hashedMods = []; + var i = 0; + let mods = fs.readdirSync("./sharedMods/") + let instanceMods = fs.readdirSync("./instances/"+instanceName+"/mods/") + + for(o=0;o Date: Thu, 5 Jan 2017 13:19:45 +0100 Subject: [PATCH 08/11] Stop sending rcon passwords because security and stuff --- master.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/master.js b/master.js index c5b31dcb..6c56db23 100644 --- a/master.js +++ b/master.js @@ -61,7 +61,12 @@ app.post("/getID", function(req,res) { app.get("/slaves", function(req, res) { res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); - res.send(slaves) + let copyOfSlaves = slaves + // filter out the rcon password because thats kindof not a safe thing to share + for(key in copyOfSlaves) { + copyOfSlaves[key].rconPassword = "hidden"; + } + res.send(copyOfSlaves) }); // endpoint to send items to From e961ca6d56721429b92d83ef98ae8ab42c95f5f0 Mon Sep 17 00:00:00 2001 From: Danielv123 Date: Sat, 7 Jan 2017 23:37:10 +0100 Subject: [PATCH 09/11] Fixed mod hashing to avoid dupes --- client.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/client.js b/client.js index 1458dd60..56e1f66a 100644 --- a/client.js +++ b/client.js @@ -401,27 +401,27 @@ function hashMods(instanceName, callback) { hash: hash, } // check if this callback has ran once for each mod - if(hashedMods.length == mods.length + instanceMods.length) { + if(hashedMods.length == /*mods.length*/ + instanceMods.length) { callback(hashedMods); } //console.log(modname) } let hashedMods = []; var i = 0; - let mods = fs.readdirSync("./sharedMods/") + /*let mods = fs.readdirSync("./sharedMods/")*/ let instanceMods = fs.readdirSync("./instances/"+instanceName+"/mods/") - for(o=0;o Date: Sun, 8 Jan 2017 21:00:24 +0100 Subject: [PATCH 10/11] Instances uploads their mods to master --- client.js | 250 ++++++++++++++++++++++++++++----------------------- master.js | 54 +++++++++-- package.json | 3 +- 3 files changed, 187 insertions(+), 120 deletions(-) diff --git a/client.js b/client.js index 56e1f66a..48b51110 100644 --- a/client.js +++ b/client.js @@ -4,6 +4,7 @@ var needle = require("needle"); var child_process = require('child_process'); var path = require('path'); var syncRequest = require('sync-request'); +var request = require("request") var ncp = require('ncp').ncp; var Rcon = require('simple-rcon'); var hashFiles = require('hash-files'); @@ -36,9 +37,9 @@ if (!fs.existsSync("./instances/")) { if (!fs.existsSync("./sharedMods/")) { fs.mkdirSync("sharedMods"); } -var instance = process.argv[3]; -var instancedirectory = './instances/' + instance; -var command = process.argv[2]; +const instance = process.argv[3]; +const instancedirectory = './instances/' + instance; +const command = process.argv[2]; // handle commandline parameters if (!command || command == "help" || command == "--help") { console.error("Usage: ") @@ -207,6 +208,7 @@ write-data=__PATH__executable__/../../../instances/" + instance + "\r\n\ lastSignalCheck = Date.now(); // world IDs ------------------------------------------------------------------ + setInterval(getID, 10000); function getID() { var payload = { time: Date.now(), @@ -228,107 +230,147 @@ write-data=__PATH__executable__/../../../instances/" + instance + "\r\n\ }); }) } - setInterval(getID, 10000) - // provide items -------------------------------------------------------------- - // trigger when something happens to output.txt - fs.watch(instancedirectory + "/script-output/output.txt", function (eventType, filename) { - // get array of lines in file - items = fs.readFileSync(instancedirectory + "/script-output/output.txt", "utf8").split("\n"); - // if you found anything, reset the file - if (items[0]) { - fs.writeFileSync(instancedirectory + "/script-output/output.txt", "") + // 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(i=0;i { + console.log(file); + if(file == req.body.modName) { + found = true; + } + }); + if(!found) { + // we don't have mod, plz send + res.send(req.body.modName) + } else { + res.send("found") + } + res.end() +}); +app.post("/uploadMod", function(req,res) { + console.log("/uploadMod") + + if (!req.files) { + res.send('No files were uploaded.'); + return; + } else { + console.log(req.files.file) + req.files.file.mv('./database/masterMods/'+req.files.file.name, function(err) { + if (err) { + res.status(500).send(err); + } else { + res.send('File uploaded!'); + } + }); + } +}); // endpoint for getting information about all our slaves app.get("/slaves", function(req, res) { res.header("Access-Control-Allow-Origin", "*"); @@ -77,7 +121,7 @@ app.post("/place", function(req, res) { // save items we get db.items.additem(req.body) // Attempt confirming - res.send("success"); + res.end("success"); }); // endpoint to remove items from DB when client orders items diff --git a/package.json b/package.json index 9261da04..94494409 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "master.js", "scripts": { "test": "nodemon master.js", - "start": "echo Run node client.js [instance] to start slaves && node master.js" + "start": "echo Run node client.js start [instance] to start slaves && node master.js" }, "keywords": [ "factorio" @@ -27,6 +27,7 @@ "nedb": "^1.8.0", "needle": "^1.1.0", "portfinder": "^1.0.9", + "request": "^2.79.0", "simple-rcon": "^0.3.1", "sync-request": "^3.0.1" } From 91438c32d996a49c78396d60baecd01762ebd712 Mon Sep 17 00:00:00 2001 From: Danielv123 Date: Sun, 8 Jan 2017 22:18:04 +0100 Subject: [PATCH 11/11] Client can download mods from master --- client.js | 50 ++++++++++++++++++++++++++++---------------------- master.js | 13 ++++--------- 2 files changed, 32 insertions(+), 31 deletions(-) diff --git a/client.js b/client.js index 48b51110..04e55aed 100644 --- a/client.js +++ b/client.js @@ -196,7 +196,7 @@ write-data=__PATH__executable__/../../../instances/" + instance + "\r\n\ console.log('Authenticated!'); }).on('connected', function () { console.log('Connected!'); - getID(); + // getID(); }).on('disconnected', function () { console.log('Disconnected!'); // now reconnect @@ -208,28 +208,34 @@ write-data=__PATH__executable__/../../../instances/" + instance + "\r\n\ lastSignalCheck = Date.now(); // world IDs ------------------------------------------------------------------ - setInterval(getID, 10000); - function getID() { - var payload = { - time: Date.now(), - rconPort: instanceconfig.clientPort, - rconPassword: instanceconfig.clientPassword, - serverPort: instanceconfig.factorioPort, - unique: instanceconfig.clientPassword.hashCode(), - publicIP: config.publicIP, // IP of the server should be global for all instances, so we pull that straight from the config + hashMods(instance, function(modHashes){ + setInterval(getID, 10000); + getID() + function getID() { + var payload = { + time: Date.now(), + rconPort: instanceconfig.clientPort, + rconPassword: instanceconfig.clientPassword, + serverPort: instanceconfig.factorioPort, + unique: instanceconfig.clientPassword.hashCode(), + publicIP: config.publicIP, // IP of the server should be global for all instances, so we pull that straight from the config + mods:modHashes + } + require('getmac').getMac(function (err, mac) { + if (err) throw err + payload.mac = mac + console.log(payload) + needle.post(config.masterIP + ":" + config.masterPort + '/getID', payload, function (err, response, body) { + 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. + console.log(response.body) + } + }); + }) } - require('getmac').getMac(function (err, mac) { - if (err) throw err - payload.mac = mac - console.log(payload) - needle.post(config.masterIP + ":" + config.masterPort + '/getID', payload, function (err, response, body) { - 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. - console.log(response.body) - } - }); - }) - } + }) + + // Mod uploading and management ----------------------------------------------- // get mod names and hashes // string: instance, function: callback diff --git a/master.js b/master.js index 46914ff8..53051391 100644 --- a/master.js +++ b/master.js @@ -3,6 +3,7 @@ const masterModFolder = "./database/masterMods/" // Library for create folder recursively if it does not exist const mkdirp = require("mkdirp"); +mkdirp.sync("./database"); mkdirp.sync(masterModFolder); const fs = require("fs") @@ -22,10 +23,8 @@ app.use(fileUpload()); // Set folder to serve static content from (the website) app.use(express.static('static')); -// Set convenient paths to 3rd party libs like metroUI and jquery from their bower installs -// Use this to make it nice an easy to change without touching the HTML in production -app.use(express.static('bower_components/metro-dist')); -app.use(express.static('bower_components/jquery/dist')); +// mod downloads +app.use(express.static(masterModFolder)); // set up database var Datastore = require('nedb'); @@ -66,12 +65,9 @@ app.post("/getID", function(req,res) { // mod management // should handle uploading and checking if mods are uploaded app.post("/checkMod", function(req,res) { - console.log("Checking mod") let files = fs.readdirSync(masterModFolder) let found = false; - console.log(files) files.forEach(file => { - console.log(file); if(file == req.body.modName) { found = true; } @@ -85,8 +81,6 @@ app.post("/checkMod", function(req,res) { res.end() }); app.post("/uploadMod", function(req,res) { - console.log("/uploadMod") - if (!req.files) { res.send('No files were uploaded.'); return; @@ -97,6 +91,7 @@ app.post("/uploadMod", function(req,res) { res.status(500).send(err); } else { res.send('File uploaded!'); + console.log("Uploaded mod: " + req.files.file.name) } }); }