mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-11 23:04:52 +09:00
Merged dev into master
This commit is contained in:
commit
de1a51610d
8 files changed
+399
-272
No files matched your search
@@ -48,7 +48,7 @@ There are no requirements for other mods, they can be ran in any configuration a
|
||||
|
||||
You can change the name and path expected in config.json if wanted.
|
||||
|
||||
3. node client.js [instancename]
|
||||
3. node client.js start [instancename]
|
||||
|
||||
Repeat step 3 for more servers on one machine. You should be able to find its port by looking at the console output or at the slave section on master:8080
|
||||
|
||||
@@ -59,3 +59,25 @@ Repeat step 3 for more servers on one machine. You should be able to find its po
|
||||
2. Drop it into ./factorio/mods
|
||||
|
||||
3. Run factorio and connect to slave as a normal MP game.
|
||||
|
||||
#Ubuntu setup
|
||||
|
||||
Master and all slaves:
|
||||
|
||||
sudo curl -sL https://deb.nodesource.com/setup_7.x | sudo -E bash - && sudo apt install -y git nodejs && git clone https://github.com/Danielv123/factorioClusterio.git && cd factorioClusterio && npm install
|
||||
|
||||
downloads and installs nodejs, git and clusterio.
|
||||
|
||||
**Master**
|
||||
|
||||
node master.js
|
||||
|
||||
**Slave**
|
||||
|
||||
Download factorio with curl. Make sure to insert the correct version number.
|
||||
|
||||
Start the slave with node client.js
|
||||
|
||||
curl -o factorio.tar.gz -L https://www.factorio.com/get-download/0.14.21/headless/linux64 && tar -xvzf factorio.tar.gz
|
||||
|
||||
node client.js start [instancename]
|
||||
@@ -1,42 +1,83 @@
|
||||
var fs = require('fs');
|
||||
var needle = require("needle");
|
||||
var child_process = require('child_process');
|
||||
var path = require('path')
|
||||
|
||||
// require config.json
|
||||
var config = require('./config');
|
||||
// Functions
|
||||
var deleteFolderRecursive = function (path) {
|
||||
if (fs.existsSync(path)) {
|
||||
fs.readdirSync(path).forEach(function (file, index) {
|
||||
var curPath = path + "/" + file;
|
||||
if (fs.lstatSync(curPath).isDirectory()) { // recurse
|
||||
deleteFolderRecursive(curPath);
|
||||
} else { // delete file
|
||||
fs.unlinkSync(curPath);
|
||||
}
|
||||
});
|
||||
fs.rmdirSync(path);
|
||||
}
|
||||
};
|
||||
|
||||
var instance = process.argv[2];
|
||||
if(!instance){
|
||||
console.error("Usage: node client.js [instance name]")
|
||||
function getDirectories(srcpath) {
|
||||
return fs.readdirSync(srcpath).filter(function (file) {
|
||||
return fs.statSync(path.join(srcpath, file)).isDirectory();
|
||||
});
|
||||
}
|
||||
if (!fs.existsSync("./instances/")) {
|
||||
fs.mkdirSync("instances");
|
||||
}
|
||||
var instance = process.argv[3];
|
||||
var instancedirectory = './instances/' + instance;
|
||||
var command = process.argv[2];
|
||||
// handle commandline parameters
|
||||
if (!command || command == "help" || command == "--help") {
|
||||
console.error("Usage: ")
|
||||
console.error("node client.js start [instance name]")
|
||||
console.error("node client.js list")
|
||||
console.error("node client.js delete [instance]")
|
||||
process.exit(1)
|
||||
}
|
||||
if(!fs.existsSync("./instances/")){
|
||||
fs.mkdirSync("instances");
|
||||
}
|
||||
var instancedirectory = './instances/'+instance;
|
||||
if (!fs.existsSync(instancedirectory)){
|
||||
fs.mkdirSync(instancedirectory);
|
||||
fs.mkdirSync(instancedirectory + "/script-output/");
|
||||
fs.writeFileSync(instancedirectory + "/script-output/output.txt", "")
|
||||
fs.writeFileSync(instancedirectory + "/script-output/orders.txt", "")
|
||||
fs.writeFileSync(instancedirectory + "/script-output/txbuffer.txt", "")
|
||||
fs.mkdirSync(instancedirectory + "/mods/")
|
||||
fs.symlinkSync('../../../clusterio_0.0.3',instancedirectory + "/mods/clusterio_0.0.3",'junction')
|
||||
fs.writeFileSync(instancedirectory + "/config.ini", "[path]\r\n\
|
||||
} else if (command == "list") {
|
||||
console.dir(getDirectories("./instances/"));
|
||||
process.exit(1)
|
||||
} else if (command == "delete") {
|
||||
if (!process.argv[3]) {
|
||||
console.error("Usage: node client.js delete [instance]");
|
||||
process.exit(1)
|
||||
} else if (typeof process.argv[3] == "string" && fs.existsSync("./instances/" + process.argv[3]) && process.argv[3] != "/" && process.argv[3] != "") {
|
||||
deleteFolderRecursive("./instances/" + process.argv[3]);
|
||||
console.log("Deleted instance " + process.argv[3])
|
||||
process.exit(1)
|
||||
} else {
|
||||
console.error("Instance not found: " + process.argv[3]);
|
||||
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...")
|
||||
fs.mkdirSync(instancedirectory);
|
||||
fs.mkdirSync(instancedirectory + "/script-output/");
|
||||
fs.writeFileSync(instancedirectory + "/script-output/output.txt", "")
|
||||
fs.writeFileSync(instancedirectory + "/script-output/orders.txt", "")
|
||||
fs.writeFileSync(instancedirectory + "/script-output/txbuffer.txt", "")
|
||||
fs.mkdirSync(instancedirectory + "/mods/")
|
||||
fs.symlinkSync('../../../clusterio_0.0.3', instancedirectory + "/mods/clusterio_0.0.3", 'junction')
|
||||
fs.writeFileSync(instancedirectory + "/config.ini", "[path]\r\n\
|
||||
read-data=__PATH__executable__\\..\\..\\data\r\n\
|
||||
write-data=__PATH__executable__\\..\\..\\..\\instances\\"+instance+"\r\n\
|
||||
write-data=__PATH__executable__\\..\\..\\..\\instances\\" + instance + "\r\n\
|
||||
");
|
||||
|
||||
var instconf = {
|
||||
"factorioPort": Math.floor(Math.random()*65535),
|
||||
"clientPort": Math.floor(Math.random()*65535),
|
||||
"factorioPort": Math.floor(Math.random() * 65535),
|
||||
"clientPort": Math.floor(Math.random() * 65535),
|
||||
"clientPassword": Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 8),
|
||||
}
|
||||
console.log(instconf)
|
||||
fs.writeFileSync(instancedirectory + "/config.json", JSON.stringify(instconf, null, 4));
|
||||
|
||||
var serversettings = {
|
||||
"name": config.username +"'s clusterio "+instance,
|
||||
"name": config.username + "'s clusterio " + instance,
|
||||
"description": config.description,
|
||||
"tags": ["clusterio"],
|
||||
"max_players": "20",
|
||||
@@ -45,60 +86,58 @@ write-data=__PATH__executable__\\..\\..\\..\\instances\\"+instance+"\r\n\
|
||||
"token": config.usertoken,
|
||||
"game_password": config.game_password,
|
||||
"verify_user_identity": config.verify_user_identity,
|
||||
"admins":[config.username],
|
||||
"allow_commands":config.allow_commands,
|
||||
"autosave_interval":10,
|
||||
"autosave_slots":5,
|
||||
"afk_autokick_interval":0,
|
||||
"auto_pause":config.auto_pause
|
||||
"admins": [config.username],
|
||||
"allow_commands": config.allow_commands,
|
||||
"autosave_interval": 10,
|
||||
"autosave_slots": 5,
|
||||
"afk_autokick_interval": 0,
|
||||
"auto_pause": config.auto_pause
|
||||
}
|
||||
fs.writeFileSync(instancedirectory + "/server-settings.json", JSON.stringify(serversettings, null, 4));
|
||||
|
||||
var createSave = child_process.spawnSync(
|
||||
'./' + config.factorioDirectory + '/bin/x64/factorio.exe',
|
||||
[
|
||||
'-c',instancedirectory+'/config.ini',
|
||||
'--create',instancedirectory+'/save.zip',
|
||||
'./' + config.factorioDirectory + '/bin/x64/factorio', [
|
||||
'-c', instancedirectory + '/config.ini',
|
||||
'--create', instancedirectory + '/save.zip',
|
||||
]
|
||||
)
|
||||
console.log("Instance created!")
|
||||
}
|
||||
|
||||
var instanceconfig = require(instancedirectory+'/config');
|
||||
var instanceconfig = require(instancedirectory + '/config');
|
||||
|
||||
|
||||
process.on('SIGINT', function() {
|
||||
console.log("Caught interrupt signal");
|
||||
//serverprocess.stdin.write('/quit')
|
||||
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.exe',
|
||||
[
|
||||
'-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']
|
||||
}
|
||||
'./' + 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']
|
||||
}
|
||||
)
|
||||
|
||||
serverprocess.on('close', (code) => {
|
||||
console.log(`child process exited with code ${code}`);
|
||||
process.exit();
|
||||
console.log(`child process exited with code ${code}`);
|
||||
process.exit();
|
||||
});
|
||||
|
||||
serverprocess.stdout.on('data', (chunk) => {
|
||||
console.log('OUT: '+chunk);
|
||||
console.log('OUT: ' + chunk);
|
||||
})
|
||||
|
||||
serverprocess.stderr.on('data', (chunk) => {
|
||||
console.log('ERR: '+chunk);
|
||||
console.log('ERR: ' + chunk);
|
||||
})
|
||||
|
||||
// connect us to the server with rcon
|
||||
@@ -113,14 +152,16 @@ var client = new Rcon({
|
||||
|
||||
// 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);
|
||||
setTimeout(() => {
|
||||
client.connect();
|
||||
}, 5000);
|
||||
|
||||
client.on('authenticated', function() {
|
||||
client.on('authenticated', function () {
|
||||
console.log('Authenticated!');
|
||||
}).on('connected', function() {
|
||||
}).on('connected', function () {
|
||||
console.log('Connected!');
|
||||
getID();
|
||||
}).on('disconnected', function() {
|
||||
}).on('disconnected', function () {
|
||||
console.log('Disconnected!');
|
||||
// now reconnect
|
||||
client.connect();
|
||||
@@ -133,117 +174,133 @@ 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()
|
||||
time: Date.now(),
|
||||
rconPort: instanceconfig.clientPort,
|
||||
rconPassword: instanceconfig.clientPassword,
|
||||
serverPort: instanceconfig.factorioPort,
|
||||
unique: instanceconfig.clientPassword.hashCode()
|
||||
}
|
||||
require('getmac').getMac(function(err,mac){
|
||||
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) {
|
||||
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)
|
||||
}
|
||||
} );
|
||||
});
|
||||
})
|
||||
|
||||
}
|
||||
// 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);
|
||||
});
|
||||
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", "")
|
||||
}
|
||||
}
|
||||
})
|
||||
// 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])};
|
||||
}
|
||||
}
|
||||
})(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);
|
||||
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);
|
||||
});
|
||||
}
|
||||
// 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() {
|
||||
})
|
||||
// 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])
|
||||
};
|
||||
}
|
||||
}
|
||||
})(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);
|
||||
}
|
||||
// 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]) {
|
||||
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++) {
|
||||
for (i = 0; i < response.body.length; i++) {
|
||||
frameset[i] = response.body[i].frame;
|
||||
}
|
||||
// console.log(frameset);
|
||||
@@ -253,27 +310,27 @@ setInterval(function() {
|
||||
});
|
||||
// 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]) {
|
||||
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]) {
|
||||
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) {
|
||||
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.
|
||||
}
|
||||
});
|
||||
@@ -284,12 +341,12 @@ setInterval(function() {
|
||||
}, 1000)
|
||||
|
||||
// simple string hasher
|
||||
String.prototype.hashCode = function(){
|
||||
String.prototype.hashCode = function () {
|
||||
var hash = 0;
|
||||
if (this.length == 0) return hash;
|
||||
for (i = 0; i < this.length; i++) {
|
||||
char = this.charCodeAt(i);
|
||||
hash = ((hash<<5)-hash)+char;
|
||||
hash = ((hash << 5) - hash) + char;
|
||||
hash = hash & hash; // Convert to 32bit integer
|
||||
}
|
||||
return hash;
|
||||
|
||||
Whitespace-only changes.
@@ -45,6 +45,7 @@ db.items.additem = function(object) {
|
||||
}
|
||||
var slaves = {}
|
||||
// world ID management
|
||||
// slaves post here to tell the server they exist
|
||||
app.post("/getID", function(req,res) {
|
||||
// request.body should be an object
|
||||
// {rconPort, rconPassword, serverPort, mac, time}
|
||||
@@ -55,7 +56,6 @@ app.post("/getID", function(req,res) {
|
||||
console.log("Slave: " + req.body.mac + " : " + req.body.serverPort)
|
||||
}
|
||||
});
|
||||
|
||||
// endpoint for getting information about all our slaves
|
||||
app.get("/slaves", function(req, res) {
|
||||
res.header("Access-Control-Allow-Origin", "*");
|
||||
|
||||
+3
-1
@@ -4,7 +4,8 @@
|
||||
"description": "Factorio cluster???",
|
||||
"main": "master.js",
|
||||
"scripts": {
|
||||
"test": "nodemon master.js"
|
||||
"test": "nodemon master.js",
|
||||
"start": "echo Run node client.js [instance] to start slaves && node master.js"
|
||||
},
|
||||
"keywords": [
|
||||
"factorio"
|
||||
@@ -13,6 +14,7 @@
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"body-parser": "^1.15.2",
|
||||
"canvasjs": "^1.8.1",
|
||||
"child_process": "^1.0.2",
|
||||
"express": "^4.14.0",
|
||||
"fs": "0.0.2",
|
||||
|
||||
+4
-4
@@ -2,7 +2,7 @@
|
||||
<head>
|
||||
<title>Clusterio</title>
|
||||
<link rel="stylesheet" type="text/css" href="style.css">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/0.2.0/Chart.min.js" type="text/javascript"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/canvasjs/1.7.0/canvasjs.js" type="text/javascript"></script>
|
||||
<script src="main.js"></script>
|
||||
</head>
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
<div id="storage-menu" onclick="display('storage');" class="button-black menuItem">
|
||||
<p>Storage</p>
|
||||
</div>
|
||||
<div id="production-menu" onclick="display('production');" class="button-black menuItem">
|
||||
<!--<div id="production-menu" onclick="display('production');" class="button-black menuItem">
|
||||
<p>Production</p>
|
||||
</div>
|
||||
</div>-->
|
||||
<div id="slaves-menu" onclick="display('slaves');" class="button-black menuItem">
|
||||
<p>Slaves</p>
|
||||
</div>
|
||||
@@ -36,7 +36,7 @@
|
||||
<div id="legend">
|
||||
|
||||
</div>
|
||||
<canvas id="productionChart" width="400" height="150"></canvas>
|
||||
<div id="productionChart" style="height: 300px; width: 100%;"></div>
|
||||
</div>
|
||||
<div id="slaves">
|
||||
<h2>Slaves</h2>
|
||||
|
||||
+144
-101
@@ -1,9 +1,10 @@
|
||||
|
||||
var g = {}
|
||||
contents = {
|
||||
"iron-plate":100,
|
||||
"copper-plate":7312,
|
||||
}
|
||||
// nice functions
|
||||
// hash a string to a hash
|
||||
function djb2(str){
|
||||
var hash = 5381;
|
||||
for (var i = 0; i < str.length; i++) {
|
||||
@@ -11,7 +12,7 @@ function djb2(str){
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
// hash a string to a color
|
||||
function hashColor(str) {
|
||||
var hash = djb2(str);
|
||||
var r = (hash & 0xFF0000) >> 16;
|
||||
@@ -19,21 +20,23 @@ function hashColor(str) {
|
||||
var b = hash & 0x0000FF;
|
||||
return "#" + ("0" + r.toString(16)).substr(-2) + ("0" + g.toString(16)).substr(-2) + ("0" + b.toString(16)).substr(-2);
|
||||
}
|
||||
|
||||
// exactly what you would expect it to, returns String
|
||||
function capitalizeFirstLetter(string) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
}
|
||||
|
||||
// function to draw data we recieve from ajax requests
|
||||
// hide the HTML object passed as argument #1
|
||||
function hideThis(object) {
|
||||
object.style.visibility = "hidden";
|
||||
}
|
||||
// function to sort arrays of objects after a keys value
|
||||
function sortByKey(array, key) {
|
||||
return array.sort(function(a, b) {
|
||||
var x = Number(a[key]); var y = Number(b[key]);
|
||||
return ((x < y) ? 1 : ((x > y) ? -1 : 0));
|
||||
});
|
||||
}
|
||||
// function to draw data we recieve from ajax requests
|
||||
function drawcontents(data) {
|
||||
var data = sortByKey(data, "count");
|
||||
result = "<table>";
|
||||
@@ -65,88 +68,126 @@ function display(page) {
|
||||
}
|
||||
}
|
||||
|
||||
// Function to redraw charts in case they bug out
|
||||
function drawcharts() {
|
||||
// create chart of items in master storage
|
||||
ctx = document.querySelector("#contentGraph").getContext('2d');
|
||||
PieChart = new Chart(ctx);
|
||||
|
||||
// production chart
|
||||
// https://codepen.io/statuswoe/pen/hyldD
|
||||
var count = 20;
|
||||
var data = {
|
||||
labels : ["1","2","3","4","5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20"],
|
||||
datasets : [
|
||||
{
|
||||
fillColor : "rgba(220,220,220,0.5)",
|
||||
strokeColor : "rgba(220,220,220,1)",
|
||||
pointColor : "rgba(220,220,220,1)",
|
||||
pointStrokeColor : "#fff",
|
||||
title:"one",
|
||||
data : [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
|
||||
},
|
||||
{
|
||||
fillColor : "rgba(151,187,205,0.5)",
|
||||
strokeColor : "rgba(151,187,205,1)",
|
||||
pointColor : "rgba(151,187,205,1)",
|
||||
pointStrokeColor : "#fff",
|
||||
title:"two",
|
||||
data : [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
|
||||
}
|
||||
]
|
||||
}
|
||||
// this is ugly, don't judge me
|
||||
var updateData = function(oldData){
|
||||
if(piedataOld[0]){
|
||||
//console.log(piedata)
|
||||
var labels = oldData["labels"];
|
||||
var dataSetA = oldData["datasets"][0]["data"];
|
||||
var dataSetB = oldData["datasets"][1]["data"];
|
||||
|
||||
labels.shift();
|
||||
count++;
|
||||
labels.push(count.toString());
|
||||
// this is where we generate new data
|
||||
var newDataA = piedata[0].value - piedataOld[0].value;
|
||||
var newDataB = piedata[1].value - piedataOld[1].value;
|
||||
// not quite sure how this works, where is dataSetA placed in data/the graph?
|
||||
dataSetA.push(newDataA);
|
||||
dataSetB.push(newDataB);
|
||||
dataSetA.shift();
|
||||
dataSetB.shift();
|
||||
}
|
||||
};
|
||||
|
||||
// Not sure why the scaleOverride isn't working...
|
||||
var optionsNoAnimation = {
|
||||
animation : false,
|
||||
//Boolean - If we want to override with a hard coded scale
|
||||
scaleOverride : true,
|
||||
//** Required if scaleOverride is true **
|
||||
//Number - The number of steps in a hard coded scale
|
||||
scaleSteps : 20,
|
||||
//Number - The value jump in the hard coded scale
|
||||
scaleStepWidth : 10,
|
||||
//Number - The scale starting value
|
||||
scaleStartValue : 0
|
||||
}
|
||||
|
||||
//Get the context of the canvas element we want to select
|
||||
var ctx = document.getElementById("productionChart").getContext("2d");
|
||||
var optionsNoAnimation = {animation : false}
|
||||
productionChart = new Chart(ctx);
|
||||
productionChart.Line(data, optionsNoAnimation);
|
||||
|
||||
setInterval(function(){
|
||||
updateData(data);
|
||||
productionChart.Line(data, optionsNoAnimation);
|
||||
},1000);
|
||||
}
|
||||
|
||||
// setTimeout is used to put this processing in the back of the queue, after the HTML canvas is done
|
||||
setTimeout(function(){
|
||||
drawcharts();
|
||||
},10)
|
||||
window.onload = function () {
|
||||
display("storage")
|
||||
// dataPoints
|
||||
var dataPoints1 = [];
|
||||
var dataPoints2 = [];
|
||||
|
||||
var chart = new CanvasJS.Chart("productionChart",{
|
||||
zoomEnabled: true,
|
||||
title: {
|
||||
text: "Production"
|
||||
},
|
||||
toolTip: {
|
||||
shared: true
|
||||
},
|
||||
legend: {
|
||||
verticalAlign: "top",
|
||||
horizontalAlign: "center",
|
||||
fontSize: 14,
|
||||
fontWeight: "bold",
|
||||
fontFamily: "calibri",
|
||||
fontColor: "dimGrey"
|
||||
},
|
||||
axisX: {
|
||||
title: "Shows items in network"
|
||||
},
|
||||
axisY:{
|
||||
prefix: '',
|
||||
includeZero: true
|
||||
},
|
||||
data: [{
|
||||
// dataSeries1
|
||||
type: "line",
|
||||
xValueType: "dateTime",
|
||||
showInLegend: true,
|
||||
name: "Company A",
|
||||
dataPoints: dataPoints1
|
||||
},
|
||||
{
|
||||
// dataSeries2
|
||||
type: "line",
|
||||
xValueType: "dateTime",
|
||||
showInLegend: true,
|
||||
name: "Company B" ,
|
||||
dataPoints: dataPoints2
|
||||
}],
|
||||
legend:{
|
||||
cursor:"pointer",
|
||||
itemclick : function(e) {
|
||||
if (typeof(e.dataSeries.visible) === "undefined" || e.dataSeries.visible) {
|
||||
e.dataSeries.visible = false;
|
||||
}
|
||||
else {
|
||||
e.dataSeries.visible = true;
|
||||
}
|
||||
chart.render();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
var updateInterval = 10000;
|
||||
// initial value
|
||||
var yValue1 = 640;
|
||||
var yValue2 = 604;
|
||||
|
||||
var time = new Date;
|
||||
time.setHours(9);
|
||||
time.setMinutes(30);
|
||||
time.setSeconds(00);
|
||||
time.setMilliseconds(00);
|
||||
// starting at 9.30 am
|
||||
|
||||
var updateChart = function (count) {
|
||||
count = count || 1;
|
||||
|
||||
// count is number of times loop runs to generate random dataPoints.
|
||||
|
||||
for (var i = 0; i < count; i++) {
|
||||
|
||||
// add interval duration to time
|
||||
time.setTime(time.getTime()+ updateInterval);
|
||||
|
||||
|
||||
// generating random values
|
||||
var deltaY1 = .5 + Math.random() *(-.5-.5);
|
||||
var deltaY2 = .5 + Math.random() *(-.5-.5);
|
||||
|
||||
// adding random value and rounding it to two digits.
|
||||
yValue1 = Math.round((yValue1 + deltaY1)*100)/100;
|
||||
yValue2 = Math.round((yValue2 + deltaY2)*100)/100;
|
||||
|
||||
// pushing the new values
|
||||
dataPoints1.push({
|
||||
x: time.getTime(),
|
||||
y: yValue1
|
||||
});
|
||||
dataPoints2.push({
|
||||
x: time.getTime(),
|
||||
y: yValue2
|
||||
});
|
||||
|
||||
|
||||
};
|
||||
|
||||
// updating legend text with updated with y Value
|
||||
chart.options.data[0].legendText = " Company A $" + yValue1;
|
||||
chart.options.data[1].legendText = " Company B $" + yValue2;
|
||||
|
||||
chart.render();
|
||||
|
||||
};
|
||||
|
||||
// generates first set of dataPoints
|
||||
updateChart(3000);
|
||||
|
||||
// update chart after specified interval
|
||||
setInterval(function(){updateChart()}, updateInterval);
|
||||
}
|
||||
|
||||
var piedata = {};
|
||||
var piedataOld = {};
|
||||
@@ -172,40 +213,41 @@ setInterval(function() {
|
||||
label: data[i].name,
|
||||
}
|
||||
}
|
||||
PieChart.Pie(piedata, {
|
||||
animation: false,
|
||||
legend: {
|
||||
display: true,
|
||||
labels: {
|
||||
fontColor: 'rgb(255, 99, 132)'
|
||||
}
|
||||
},
|
||||
hover: {
|
||||
mode: "label",
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
xmlhttp.open("GET", "inventory", true);
|
||||
xmlhttp.send();
|
||||
}, 500)
|
||||
// get all slaves recently connected to master
|
||||
Date.prototype.yyyymmdd = function() { // http://stackoverflow.com/questions/3066586/get-string-in-yyyymmdd-format-from-js-date-object
|
||||
Date.prototype.yyyymmdd = function(time) { // http://stackoverflow.com/questions/3066586/get-string-in-yyyymmdd-format-from-js-date-object
|
||||
var mm = this.getMonth() + 1; // getMonth() is zero-based
|
||||
var dd = this.getDate();
|
||||
return [this.getFullYear(), !mm[1] && '0', mm, !dd[1] && '0', dd].join(''); // padding
|
||||
if(mm<10)mm = "0"+mm
|
||||
if(dd<10)dd = "0"+dd
|
||||
console.log(this.getFullYear())
|
||||
return this.getFullYear()+""+mm+""+dd+ ""; // padding
|
||||
};
|
||||
var date = new Date();
|
||||
|
||||
// ask master for slaves
|
||||
setInterval(function() {
|
||||
var xmlhttp = new XMLHttpRequest();
|
||||
xmlhttp.onreadystatechange = function() {
|
||||
if(xmlhttp.readyState == 4 && xmlhttp.status == 200) {
|
||||
var slaveData = JSON.parse(xmlhttp.responseText);
|
||||
var HTML = "<p>Latest connections:</p>"
|
||||
var HTML = "<p>Current connections:</p>"
|
||||
for(i=0;i<Object.keys(slaveData).length;i++){
|
||||
var key = Object.keys(slaveData)[i]
|
||||
// Date.getYear.getmonth.getDay
|
||||
HTML += "<div class='slaveBox'><h2>ID: " + slaveData[key].unique + "</h2><p>Last seen: "+date.yyyymmdd(slaveData[key].time)+"</p><p>Port: "+slaveData[key].serverPort+"</p><p>Host: "+slaveData[key].mac+"</p></div>"
|
||||
if(Date.now() - slaveData[key].time < 12000) {
|
||||
// Display ISO 6801 compliant date to please Zarthus
|
||||
// maybe include an option to use y-ymd-ymd-y for Trangar compatibility as well
|
||||
var seenDate = date.yyyymmdd(slaveData[key].time)
|
||||
if(g.trangarTime == true){
|
||||
var seenDate = (seenDate+"")[0]+"-"+(seenDate+"")[1]+(seenDate+"")[4]+(seenDate+"")[6]+"-"+(seenDate+"")[2]+(seenDate+"")[5]+(seenDate+"")[7]+"-"+(seenDate+"")[3]
|
||||
}
|
||||
HTML += "<div class='slaveBox'><h2>ID: " + slaveData[key].unique + "</h2><p>Last seen: "+seenDate+"</p><p>Port: "+slaveData[key].serverPort+"</p><p>Host: "+slaveData[key].mac+"</p></div>"
|
||||
}
|
||||
}
|
||||
document.querySelector("#slaves > #display").innerHTML = HTML
|
||||
}
|
||||
@@ -216,6 +258,7 @@ setInterval(function() {
|
||||
|
||||
// image data
|
||||
// key is the name of the item in the database, value is the name of the image on wiki.factorio.com/images/*
|
||||
// this list has to include all entities that doesn't just follow the simple capitalize first letter convention
|
||||
var imagedata = {
|
||||
["empty-barrel"]: "Barrel-empty",
|
||||
["transport-belt"]: "Basic-transport-belt",
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
#body > div {
|
||||
display:none;
|
||||
}
|
||||
#body > #production {
|
||||
display:block;
|
||||
}
|
||||
#contents {
|
||||
width:300px;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user