Merged dev into master

This commit is contained in:
Danielv123 committed 2016-10-28 16:01:14 +02:00
commit dd0a216cb5
10 files changed
+275 -92

No files matched your search

+3 -1
View File
@@ -26,4 +26,6 @@ build/Release
# Dependency directory
# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
node_modules
Factorio*
Factorio*
/factorio
/instances
+155 -29
View File
@@ -1,35 +1,125 @@
var fs = require('fs');
var needle = require("needle");
var child_process = require('child_process');
// require config.json
var config = require('./config');
var instance = process.argv[2];
if(!instance){
console.error("Usage: node client.js [instance name]")
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\
read-data=__PATH__executable__\\..\\..\\data\r\n\
write-data=__PATH__executable__\\..\\..\\..\\instances\\"+instance+"\r\n\
");
var instconf = {
"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,
"description": config.description,
"tags": ["clusterio"],
"max_players": "20",
"visibility": "lan",
"username": config.username,
"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
}
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',
]
)
}
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.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']
}
)
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);
})
// connect us to the server with rcon
// IP, port, password
var Rcon = require('simple-rcon');
var client = new Rcon({
host: config.clientIP,
port: config.clientPort,
password: config.clientPassword,
host: 'localhost',
port: instanceconfig.clientPort,
password: instanceconfig.clientPassword,
timeout: 0
}).connect();
});
if (!fs.existsSync(config.factorioDirectory)){
console.error("FATAL ERROR: config.factorioDirectory DOES NOT EXIST, PLEASE UPDATE CONFIG.JSON");
process.exit(1);
}
// make sure we got the files we need
if (!fs.existsSync(config.factorioDirectory + "/script-output/")){
fs.mkdirSync(config.factorioDirectory + "/script-output/");
}
if (!fs.existsSync(config.factorioDirectory + "/script-output/")){
fs.writeFileSync(config.factorioDirectory + "/script-output/output.txt", "")
}
fs.writeFileSync(config.factorioDirectory + "/script-output/orders.txt", "")
fs.writeFileSync(config.factorioDirectory + "/script-output/txbuffer.txt", "");
// 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
@@ -39,22 +129,45 @@ client.on('authenticated', function() {
// 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()
}
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)
}
} );
})
}
// provide items --------------------------------------------------------------
// trigger when something happens to output.txt
fs.watch(config.factorioDirectory + "/script-output/output.txt", function(eventType, filename) {
fs.watch(instancedirectory + "/script-output/output.txt", function(eventType, filename) {
// get array of lines in file
items = fs.readFileSync(config.factorioDirectory + "/script-output/output.txt", "utf8").split("\n");
items = fs.readFileSync(instancedirectory + "/script-output/output.txt", "utf8").split("\n");
// if you found anything, reset the file
if(items[0]) {
fs.writeFileSync(config.factorioDirectory + "/script-output/output.txt", "")
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));
// 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]},
needle.post(config.masterIP + ":" + config.masterPort + '/place', {name:g[0], count:g[1]},
function(err, resp, body){
// console.log(body);
});
@@ -64,10 +177,10 @@ fs.watch(config.factorioDirectory + "/script-output/output.txt", function(eventT
// request items --------------------------------------------------------------
setInterval(function() {
// get array of lines in file
items = fs.readFileSync(config.factorioDirectory + "/script-output/orders.txt", "utf8").split("\n");
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(config.factorioDirectory + "/script-output/orders.txt", "");
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++) {
@@ -99,7 +212,7 @@ setInterval(function() {
});
}
// if we got some confirmed orders
console.log("Importing " + confirmedOrders.length + " items! " + JSON.stringify(confirmedOrders));
// console.log("Importing " + confirmedOrders.length + " items! " + JSON.stringify(confirmedOrders));
sadas = JSON.stringify(confirmedOrders)
confirmedOrders = [];
// send our RCON command with whatever we got
@@ -118,7 +231,7 @@ setInterval(function() {
for(i = 0;i<inventory.length;i++) {
inventoryFrame[inventory[i].name] = Number(inventory[i].count);
}
console.log("RCONing inventory! " + JSON.stringify(inventoryFrame));
// console.log("RCONing inventory! " + JSON.stringify(inventoryFrame));
client.exec("/silent-command remote.call('clusterio', 'receiveInventory', '" + JSON.stringify(inventoryFrame) + "')");
}
});
@@ -141,11 +254,12 @@ 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(config.factorioDirectory + "/script-output/txbuffer.txt", "utf8").split("\n");
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(config.factorioDirectory + "/script-output/txbuffer.txt", "");
fs.writeFileSync(instancedirectory + "/script-output/txbuffer.txt", "");
// loop through all our frames
for(i = 0;i < signals.length; i++) {
(function(i){
@@ -167,4 +281,16 @@ setInterval(function() {
})(i);
}
}
}, 1000)
}, 1000)
// simple string hasher
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 & hash; // Convert to 32bit integer
}
return hash;
}
+15 -8
View File
@@ -1,12 +1,19 @@
{
"__comment": "",
"__comment": "IP and port and password the factorio server runs at for rcon connection",
"clientIP": "localhost",
"clientPort": "12345",
"clientPassword": "123",
"__comment": "Where factorio folder. Should be in the same folder as this config file.",
"factorioDirectory": "factorio 0.13.9",
"factorioDirectory": "factorio",
"__comment": "IP the master web server runs at, can be ignored on the master system",
"masterIP": "localhost",
"masterPort": 8080
}
"masterPort": 8080,
"__comment": "credentials for factorio MP services",
"username": "",
"usertoken": "",
"__comment": "General user settings",
"verify_user_identity": true,
"auto_pause": false,
"allow_commands": "admins-only",
"game_password": "",
"description": "This server runs Clusterio"
}
-5
View File
@@ -1,5 +0,0 @@
cd "Factorio_0.13.17"
@RD /S /Q "temp"
cd "bin/x64/"
start factorio.exe --start-server testmap.zip --rcon-port 12345 --rcon-password 123 --server-settings server-settings.json
pause
+20
View File
@@ -26,6 +26,7 @@ db = {};
db.items = new Datastore({ filename: 'database/items.db', autoload: true });
db.signals = new Datastore({ filename: 'database/signals.db', autoload: true, inMemoryOnly: true});
db.signals.ensureIndex({ fieldName: 'time', expireAfterSeconds: 3600 }, function (err) {});
// db.slaves = new Datastore({ filename: 'database/slaves.db', autoload: true, inMemoryOnly: false});
db.items.additem = function(object) {
db.items.findOne({name:object.name}, function (err, doc) {
@@ -42,6 +43,25 @@ db.items.additem = function(object) {
}
})
}
var slaves = {}
// world ID management
app.post("/getID", function(req,res) {
// request.body should be an object
// {rconPort, rconPassword, serverPort, mac, time}
// 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)
}
});
// endpoint for getting information about all our slaves
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)
});
// endpoint to send items to
app.post("/place", function(req, res) {
+3
View File
@@ -13,11 +13,14 @@
"license": "ISC",
"dependencies": {
"body-parser": "^1.15.2",
"child_process": "^1.0.2",
"express": "^4.14.0",
"fs": "0.0.2",
"getmac": "^1.2.1",
"mkdirp": "^0.5.1",
"nedb": "^1.8.0",
"needle": "^1.1.0",
"portfinder": "^1.0.9",
"simple-rcon": "^0.3.1"
}
}
-23
View File
@@ -1,23 +0,0 @@
{
"name": "Name of the game as it will appear in the game listing",
"description": "Description of the game that will appear in the listing",
"tags": ["game", "tags"],
"max_players": "0",
"_comment_visibility": ["public: Game will be published on the official Factorio matching server",
"lan: Game will be broadcast on LAN",
"hidden: Game will not be published anywhere"],
"visibility": "public",
"_comment_credentials": "Your factorio.com login credentials. Required for games with visibility public",
"username": "",
"password": "",
"_comment_token": "Authentication token. May be used instead of 'password' above.",
"token": "",
"game_password": "",
"_comment_verify_user_identity": "When set to true, the server will only allow clients that have a valid Factorio.com account",
"verify_user_identity": false
}
+9
View File
@@ -20,6 +20,9 @@
<div id="production-menu" onclick="display('production');" class="button-black menuItem">
<p>Production</p>
</div>
<div id="slaves-menu" onclick="display('slaves');" class="button-black menuItem">
<p>Slaves</p>
</div>
</div>
<div id="body">
<div id="storage">
@@ -35,5 +38,11 @@
</div>
<canvas id="productionChart" width="400" height="150"></canvas>
</div>
<div id="slaves">
<h2>Slaves</h2>
<div id="display">
</div>
</div>
</div>
</body>
+51 -26
View File
@@ -51,6 +51,7 @@ function drawcontents(data) {
}
// handle the navigation buttons
currentPage = ""
function display(page) {
var pages = document.querySelector("#body").childNodes;
for(i=0;i<pages.length;i++){
@@ -60,6 +61,7 @@ function display(page) {
}
if(typeof page == "string" && document.querySelector("#" + page)) {
document.querySelector("#" + page).style.display = "block";
currentPage = page;
}
}
@@ -95,22 +97,24 @@ function drawcharts() {
}
// this is ugly, don't judge me
var updateData = function(oldData){
//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();
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...
@@ -144,13 +148,17 @@ setTimeout(function(){
drawcharts();
},10)
var piedata = false;
var piedata = {};
var piedataOld = {};
// get cluster inventory from master
setInterval(function() {
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function() {
if(xmlhttp.readyState == 4 && xmlhttp.status == 200) {
data = JSON.parse(xmlhttp.responseText);
drawcontents(data);
if(currentPage = "storage"){
drawcontents(data);
}
// render our piechart with up to date information
// items-in-network-chart
if(piedata) {
@@ -181,16 +189,34 @@ setInterval(function() {
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
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
};
var date = new Date();
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>"
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>"
}
document.querySelector("#slaves > #display").innerHTML = HTML
}
}
xmlhttp.open("GET", "slaves", true);
xmlhttp.send();
}, 500)
// image data
// key is the name of the item in the database, value is the name of the image on wiki.factorio.com/images/*
var imagedata = {
["raw-wood"]: "Raw-wood",
["alien-artifact"]: "Alien-artifact",
["heavy-oil"]: "Heavy-oil",
["engine-unit"]: "Engine-unit",
["electric-engine-unit"]: "Electric-engine-unit",
["flying-robot-frame"]: "Flying-robot-frame",
["alien-science_pack"]: "Alien-science-pack",
["empty-barrel"]: "Barrel-empty",
["transport-belt"]: "Basic-transport-belt",
["underground-belt"]: "Basic-transport-belt-to-ground",
@@ -224,5 +250,4 @@ var imagedata = {
["piercing-shotgun-shells"]: "Piercing-shotgun-shell",
["accumulator"]: "Basic-accumulator",
["beacon"]: "Basic-beacon",
}
+19
View File
@@ -16,6 +16,9 @@
#body {
margin-left:200px;
}
#body > div {
display:none;
}
#contents {
width:300px;
}
@@ -30,6 +33,22 @@
#contentGraph {
float:left;
}
/* Slave stuff */
.slaveBox {
float:left;
width:200px;
height:110px;
background-color:#EEE;
border-radius: 10px;
margin-left:5px;
margin-right:5px;
}
.slaveBox > p {
margin:5px;
}
.slaveBox > h2 {
margin:5px;
}
.button-black {
width:100%;
height:40px;