Merge pull request #24 from Danielv123/dev

Circuit network support, closes #22
This commit is contained in:
Daniel authored and GitHub committed 2016-10-09 11:39:15 +02:00
commit e5314f6ace
9 files changed
+358 -85

No files matched your search

+11 -1
View File
@@ -1,6 +1,14 @@
#factorioClusterio
- The first ever mod allowing you to transfer items between worlds
Features:
- Entities to send/recieve items
- Cross dimensional storage
- Sending of liquids
- Sending of circuit network signals
#Windows setup
@@ -42,3 +50,5 @@ Once finished, do
[factorioDirectory]/bin/x64/factorio.exe [flags]
node client.js
See launchEverything.bat for example command.
+58 -8
View File
@@ -12,11 +12,19 @@ var client = new Rcon({
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/");
}
fs.writeFileSync(config.factorioDirectory + "/script-output/output.txt", "")
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", "");
client.on('authenticated', function() {
console.log('Authenticated!');
@@ -30,7 +38,8 @@ client.on('authenticated', function() {
// set some globals
confirmedOrders = [];
lastSignalCheck = Date.now();
// provide items --------------------------------------------------------------
// trigger when something happens to output.txt
fs.watch(config.factorioDirectory + "/script-output/output.txt", "utf8", function(eventType, filename) {
// get array of lines in file
@@ -52,7 +61,7 @@ fs.watch(config.factorioDirectory + "/script-output/output.txt", "utf8", functio
}
}
})
// request items --------------------------------------------------------------
setInterval(function() {
// get array of lines in file
items = fs.readFileSync(config.factorioDirectory + "/script-output/orders.txt", "utf8").split("\n");
@@ -61,7 +70,7 @@ setInterval(function() {
fs.writeFileSync(config.factorioDirectory + "/script-output/orders.txt", "");
// prepare a package of all our requested items in a more tranfer friendly format
var preparedPackage = {};
for(i = 2;i < items.length; i++) {
for(i = 0;i < items.length; i++) {
(function(i){
if(items[i]) {
items[i] = items[i].split(" ");
@@ -97,8 +106,49 @@ setInterval(function() {
client.exec("/silent-command remote.call('clusterio', 'importMany', '" + sadas + "')");
}
}, 3000)
/*
fs.watch(config.factorioDirectory + "/script-output/orders.txt", "utf8", function(eventType, filename) {
// COMBINATOR SIGNALS ---------------------------------------------------------
// 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 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");
// if we actually got anything from the file, proceed and reset file
if(signals[0]) {
fs.writeFileSync(config.factorioDirectory + "/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)
+7 -1
View File
@@ -18,9 +18,15 @@ 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"
OUTPUT_FILE = "output.txt"
ORDER_FILE = "orders.txt"
TX_BUFFER_FILE = "txbuffer.txt"
UPATE_RATE = 60
UPATE_RATE = 60
CIRCUIT_UPATE_RATE = 5
+156 -42
View File
@@ -43,12 +43,17 @@ function AddEntity(entity)
--add the chests to a lists if these chests so they can be interated over
global.outputTanks[HashPosition(entity.position)] = entity
entity.active = false
elseif entity.name == TX_COMBINATOR_NAME then
table.insert(global.txControls, entity.get_or_create_control_behavior())
elseif entity.name == RX_COMBINATOR_NAME then
table.insert(global.rxControls, entity.get_or_create_control_behavior())
entity.operable=false
end
end
function OnKilledEntity(event)
local entity = event.entity
if entity.type ~= "entity-ghost" then
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[HashPosition(entity.position)] = nil
@@ -85,23 +90,29 @@ end)
script.on_event(defines.events.on_tick, function(event)
global.inputChests = global.inputChests or {}
global.outputChests = global.outputChests or {}
global.inputTanks = global.inputTanks or {}
global.outputTanks = global.outputTanks or {}
global.outputList = global.outputList or {}
global.inputList = global.inputList or {}
global.rxControls = global.rxControls or {}
global.txControls = global.txControls or {}
-- TX Combinators must run every tick to catch single pulses
HandleTXCombinators()
local todo = game.tick % UPATE_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
@@ -114,6 +125,14 @@ script.on_event(defines.events.on_tick, function(event)
ExportInputList()
elseif todo == 5 then
ExportOutputList()
end
local rxstate = game.tick % CIRCUIT_UPATE_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)
@@ -131,12 +150,12 @@ function Reset()
global.outputList = {}
global.inputList = {}
global.itemStorage = {}
global.inputChests = {}
global.outputChests = {}
global.inputTanks = {}
global.outputTanks = {}
AddAllEntitiesOfName(INPUT_CHEST_NAME)
AddAllEntitiesOfName(OUTPUT_CHEST_NAME)
AddAllEntitiesOfName(INPUT_TANK_NAME)
@@ -171,7 +190,7 @@ function HandleInputTanks()
v.fluidbox[1] = fluid
end
end
end
function HandleOutputChests()
@@ -195,7 +214,7 @@ function HandleOutputChests()
--insert the missing items
local insertedItemsCount = chestInventory.insert(simpleItemStack)
local itemsNotInsertedCount = itemCountAllowedToInsert - insertedItemsCount
if itemsNotInsertedCount > 0 then
GiveItemsToStorage(requestItem.name, itemsNotInsertedCount)
end
@@ -214,16 +233,15 @@ 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
@@ -237,11 +255,11 @@ function HandleOutputTanks()
AddItemToOutputList(fluid.type, fluidToRequestAmount)
end
end
v.fluidbox[1] = fluid
end
v.fluidbox[1] = fluid
end
end
end
function AddItemToInputList(itemName, itemCount)
@@ -261,9 +279,9 @@ function ExportInputList()
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
--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)
end
@@ -276,9 +294,9 @@ function ExportOutputList()
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
--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)
end
@@ -296,7 +314,7 @@ function RequestItemsFromStorage(itemName, itemCount)
--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
@@ -311,10 +329,110 @@ 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
else
table.remove(global.txControls,i)
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
table.insert(frame,{count=game.tick,name="signal-srctick",type="virtual"})
game.write_file(TX_BUFFER_FILE, json:encode(frame).."\n", true)
-- 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
else
table.remove(global.rxControls,i)
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
else
table.remove(global.rxControls,i)
end
end
end
--[[ Remote Thing ]]--
remote.add_interface("clusterio",
remote.add_interface("clusterio",
{
import = function(itemName, itemCount)
import = function(itemName, itemCount)
GiveItemsToStorage(itemName, itemCount)
end,
importMany = function(jsonString)
@@ -332,23 +450,19 @@ remote.add_interface("clusterio",
end
game.print(items)
end,
reset = Reset
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,
})
+97 -25
View File
@@ -18,10 +18,10 @@ function MakeLogisticEntity(entity, name, pictureFilename, pictureTablePath, ico
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
@@ -70,7 +70,7 @@ function AddEntityToTech(techName, name)
type = "unlock-recipe",
recipe = name
}
--if a new table for the effects is made then the effects has to be attached to the
--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
@@ -86,22 +86,10 @@ MakeLogisticEntity(table.deepcopy(data.raw["container"]["iron-chest"]),
--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(
@@ -121,10 +109,10 @@ fluidCreator.fluid_boxes =
pipe_covers = pipecoverspictures(),
base_area = 250,
base_level = 1,
pipe_connections =
pipe_connections =
{
{
type="output", position = {0, 2}
{
type="output", position = {0, 2}
}
}
},
@@ -157,12 +145,96 @@ for k,v in pairs(data.raw.fluid) do
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]"
},]]
}
-- 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
},
}
+2 -2
View File
@@ -4,8 +4,8 @@
"title": "Clusterio",
"version": "0.0.2",
"date": "19-08-2016",
"author": "Danielv123 & Keyboardhack",
"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."
}
}
+5 -2
View File
@@ -3,10 +3,13 @@ put-chest=Put chest
get-chest=Get chest
put-tank=Put tank
get-tank=Get tank
put-combinator=Transmit combinator
get-combinator=Receive combinator
[item-name]
put-chest=Put chest
get-chest=Get chest
put-tank=Put tank
get-tank=Get tank
get-tank=Get tank
put-combinator=Transmit combinator
get-combinator=Receive combinator
-4
View File
@@ -1,4 +0,0 @@
{"name":"raw-fish","count":"2","_id":"a0TVkMwP8M99LLbp"}
{"name":"iron-ore","count":25823,"_id":"bEcyT728rLO8JvqY"}
{"name":"electronic-circuit","count":200,"_id":"n3JBn8ksl9YIkCWy"}
{"name":"coal","count":10406,"_id":"qnEjvk1EypjWAEQk"}
+22
View File
@@ -24,6 +24,8 @@ app.use(express.static('bower_components/jquery/dist'));
var Datastore = require('nedb');
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.items.additem = function(object) {
db.items.findOne({name:object.name}, function (err, doc) {
@@ -84,6 +86,26 @@ app.post("/remove", function(req, res) {
}
})
});
// circuit stuff
app.post("/setSignal", function(req,res) {
if(typeof req.body == "object" && req.body.time){
db.signals.insert(req.body);
// console.log("signal set")
}
});
app.post("/readSignal", function(req,res) {
// request.body should be an object
// {since:UNIXTIMESTAMP,}
// we should send an array of all signals since then
db.signals.find({time:{$gte: req.body.since}}, function (err, docs) {
// $gte means greater than or equal to, meaning we only get entries newer than the timestamp
res.send(docs);
// console.log(docs)
});
});
// endpoint for getting an inventory of what we got
app.get("/inventory", function(req, res) {
res.header("Access-Control-Allow-Origin", "*");