diff --git a/sharedPlugins/inventoryImports/index.js b/sharedPlugins/inventoryImports/index.js index a06e6fde..e2021066 100644 --- a/sharedPlugins/inventoryImports/index.js +++ b/sharedPlugins/inventoryImports/index.js @@ -4,25 +4,26 @@ const pluginConfig = require("./config.js"); const functions = require("./interfacingFunctions"); -console.log("/silent-command game.print('InventoryImports enabled')\n"); +console.log("/silent-command game.print('InventoryImports enabled')"); var config = {}; process.stdin.setEncoding('utf8'); process.stdin.on('readable', () => { var chunk = process.stdin.read(); - console.log(chunk) - if (chunk !== null && isNaN(chunk) && config && !isJson(chunk)){ + // console.log(chunk) + if (chunk !== null && isNaN(chunk) && config && isJson(chunk) && !JSON.parse(chunk).factorioPort){ // example chunk Stdout: {1:{inventory:{['iron-ore']:100,['steel-plate']:5800,['iron-ore']:50,}, // requestSlots:{['underground-belt']:50, ['fast-transport-belt']:50, ['express-underground-belt']:50, ['underground-belt']:50, }},} // returns JS object, throws if there are syntax errors in input string - let inventory = functions.parseJsString(chunk); - console.log(inventory); + // let inventory = functions.parseJsString(chunk); + let inventory = JSON.parse(chunk) + // console.log(inventory); // process inventory of players and ask master for more stuff functions.handleInventory(inventory, config); } else if(isJson(chunk) && JSON.parse(chunk).factorioPort){ config = JSON.parse(chunk); // if its a LUA table message returning with the exports send em off to master - } else if(!isJson(chunk) && !!chunk && config){ + }/* else if(!isJson(chunk) && !!chunk && config){ let thing = functions.parseJsString(chunk.replace(/=/g, ":")); Object.keys(thing).forEach(function(playerName){ Object.keys(thing[playerName]).forEach(function(itemName){ @@ -34,7 +35,7 @@ process.stdin.on('readable', () => { }); }); }); - } + }*/ }); process.stdin.on('end', () => { process.stdout.write('end'); @@ -42,6 +43,6 @@ process.stdin.on('end', () => { // poll inventories setInterval(function(){ - console.log("Polling inventories") + // console.log("Polling inventories\n") console.log(functions.pollInventories(pluginConfig.scriptOutputFileSubscription)); }, 10000); diff --git a/sharedPlugins/inventoryImports/interfacingFunctions.js b/sharedPlugins/inventoryImports/interfacingFunctions.js index d1bebc22..04e0394a 100644 --- a/sharedPlugins/inventoryImports/interfacingFunctions.js +++ b/sharedPlugins/inventoryImports/interfacingFunctions.js @@ -1,5 +1,6 @@ const objectOps = require("../../lib/objectOps.js"); const needle = require("needle"); +const pluginConfig = require("./config") // returns JS object, throws if there are syntax errors in input string function parseJsString(string){ @@ -28,90 +29,104 @@ function parseJsString(string){ }}} */ } -function handleInventory(inventory, config){ - // we want to seperate players, parse their inventory and compare to request slots - // then ask master for the difference - // we must then prepare it all and inject it into players inventories with lua before - // the next inventory report arrives to avoid duplicating orders - if(inventory && typeof inventory != "string" && typeof inventory == "object"){ +function handleInventory(json, config){ + /* + json is one of these two samples + {"players":{"1":{"inventory":{"stone":12,"iron-ore":120,"raw-wood":10,"iron-plate":8,"steel-plate":480},"requestSlots":{}}}} + {"exports":{"1":[{"name":"iron-ore","count":0},{"name":"steel-plate","count":0}]}} + + we want to seperate players, parse their json and compare to request slots + then ask master for the difference + we must then prepare it all and inject it into players inventories with lua before + the next json report arrives to avoid duplicating orders + */ + if(json && typeof json != "string" && typeof json == "object"){ let exports = {}; - let players = Object.keys(inventory); - for(let i = 0; i < players.length; i++) { - let pinv = inventory[players[i]]; - // create a copy of player.requestSlots - let toRequest = objectOps.deepclone(pinv.requestSlots); - // create a place to store the object we have confirmed for importing - let confirmedItems = {}; - let itemsToConfirm = Object.keys(pinv.inventory).length; - - // subtract items already in inventory - Object.keys(pinv.inventory).forEach(function(key){ - let itemCount = pinv.inventory[key]; - if(toRequest[key]){ - toRequest[key] -= itemCount; - } - if(toRequest[key] > 0){ - let request = { - name:key, - count:toRequest[key], - } - // request the difference from master - needle.post(config.masterIP + ":" + config.masterPort + '/api/remove', request, function (err, response, body) { - if (response && response.body && typeof response.body == "object") { - // confirmed orders, already removed from master inventory - //response.body.name: response.body.count - let stack = {name:response.body.name,count:response.body.count} - collectStacks(stack); + + // this is an inventory report, check whats missing and request from master + // {"players":{"1":{"inventory":{"stone":12,"iron-ore":120,"raw-wood":10,"iron-plate":8,"steel-plate":480},"requestSlots":{}}}} + if(json.players){ + let playerNames = Object.keys(json.players); + for(let i = 0; i < playerNames.length; i++) { + // haha, this turned into bit of a mess didn't it? + let playerInv = json.players[playerNames[i]]; + // create a copy of player.requestSlots + let toRequest = objectOps.deepclone(playerInv.requestSlots); + let inventory = playerInv.inventory; + let itemsToConfirm = Object.keys(toRequest).length; + // create a place to store the object we have confirmed for importing + let confirmedItems = {}; + let collectStacks = function(stack){ + itemsToConfirm--; + if(stack){ + if(confirmedItems[stack.name]){ + confirmedItems[stack.name] += stack.count; } else { - collectStacks(false); + confirmedItems[stack.name] = stack.count; } - }); - } - }); - function collectStacks(stack){ - itemsToConfirm--; - if(stack){ - if(confirmedItems[stack.name]){ - confirmedItems[stack.name] += stack.count; - } else { - confirmedItems[stack.name] = stack.count; + } + if(itemsToConfirm == 0 && Object.keys(confirmedItems).length > 0){ + // make lua table + // we gotta construct a string because I can't find any nice library for this + let itemTable = "{"; + Object.keys(confirmedItems).forEach(function(name){ + let count = confirmedItems[name]; + itemTable += '["'+name+'"]='+count+','; + }); + itemTable += "}"; + let outputFile = pluginConfig.scriptOutputFileSubscription; + // a="'+players[i]+'" + // b='+itemTable+' + console.log('/silent-command local a="'+playerNames[i]+'"local b='+itemTable+'local c={}if game.players[a]and game.players[a].connected then local d='+"'"+'{"exports":{"'+"'"+'..a..'+"'"+'":['+"'"+'local e=false;for f,g in pairs(b)do local h=game.players[a].insert{name=f,count=g}if e then d=d..'+"'"+','+"'"+'else e=true end;d=d..'+"'"+'{"name":"'+"'"+'..f..'+"'"+'","count":'+"'"+'..g-h..'+"'"+'}'+"'"+'end;d=d..'+"'"+']}}'+"'"+'game.write_file("'+outputFile+'",d)end'); } } - if(itemsToConfirm <= 0){ - // make lua table - // we gotta construct a string because I can't find any nice library for this - let itemTable = "{"; - Object.keys(confirmedItems).forEach(function(name){ - let count = confirmedItems[name]; - itemTable += '["'+name+'"]='+count+','; - }); - itemTable += "}"; - - console.log('/c local a="'+players[i]+'"local b='+itemTable+'local c={}if game.players[a]and game.players[a].connected then for d,e in pairs(b)do local f=game.players[a].insert{name=d,count=e}if not c[a]then c[a]={}end;if c[a][d]then c[a][d]=c[a][d]+e-f else c[a][d]=e-f end end;game.write_file("'+outputFile+'",serpent.line(c,{["comment"]=false,["compact"]=true}))end'); - } - } - - // handle exports (why would they be there? I don't know!) - if(pinv.exports){ - Object.keys(pinv.exports).forEach(function(name){ - if(exports[name]){ - exports[name] += pinv.exports[name]; + // subtract items already in inventory + Object.keys(playerInv.requestSlots).forEach(function(key){ + // if this item in inventory is also in a request slot, subtract invCount from requestCount + if(inventory[key]){ + toRequest[key] -= inventory[key]; + } + // if the difference is more than 0, request item from master + if(toRequest[key] > 0){ + let request = { + name:key, + count:toRequest[key], + } + //console.log("Requesting: "+JSON.stringify(request)); + // request the difference from master + needle.post(config.masterIP + ":" + config.masterPort + '/api/remove', request, function (err, response, body) { + if (response && response.body && typeof response.body == "object") { + // confirmed orders, already removed from master inventory + //response.body.name: response.body.count + let stack = {name:response.body.name,count:Number(response.body.count)} + collectStacks(stack); + } else { + collectStacks(false); + } + }); } else { - exports[name] = pinv.exports[name]; + collectStacks(false); } }); } } - - // keep handling exports here, now we send em off - Object.keys(exports).forEach(function(name){ - needle.post(config.masterIP + ":" + config.masterPort + '/api/place', { - name: name, - count: exports[name] - }, function (err, resp, body) { - // console.log(body); + // {"exports":{"1":[{"name":"iron-ore","count":0},{"name":"steel-plate","count":0}]}} + // this is a report of stuff we didn't have space for, send back to master + if(json.exports){ + // loop over players (normally only one, but whatever) + Object.keys(json.exports).forEach(function(playerName){ + // loop over simpleItemStacks to export + let stackArray = json.exports[playerName]; + for(let i = 0; i < stackArray.length; i++){ + if(stackArray[i].count > 0){ + console.log("Returning overflow: " + JSON.stringify(stackArray[i])); + needle.post(config.masterIP + ":" + config.masterPort + '/api/place', stackArray[i], function (err, resp, body) { + // console.log(body); + }); + } + } }); - }); + } } else return false; // inventory is falsey or a string } function pollInventories(outputFile){ @@ -119,7 +134,8 @@ function pollInventories(outputFile){ // console.log("Running inventory LUA") //console.log("/silent-command "+'local a=game.players["Danielv123"].get_inventory(defines.inventory.player_main).get_contents()local b=game.players["Danielv123"].get_quickbar().get_contents()local c={}for d=1,game.players["Danielv123"].force.character_logistic_slot_count do c[d]=game.players["Danielv123"].character.get_request_slot(d)end;local e="{inventory:{"for f,g in pairs(a)do e=e.."['+"'"+'"..f.."'+"'"+']:"..g..","end;for f,g in pairs(b)do e=e.."['+"'"+'"..f.."'+"'"+']:"..g..","end;e=e.."},requestSlots:{"for f,g in pairs(c)do e=e.."['+"'"+'"..g["name"].."'+"'"+']:"..g["count"]..", "end;e=e.."}}"game.write_file("'+pluginConfig.scriptOutputFileSubscription+'",e)') // console.log("/silent-command "+'local a="{"for b,c in pairs(game.players)do local d=game.players[b].get_inventory(defines.inventory.player_main).get_contents()local e=game.players[b].get_quickbar().get_contents()local f={}for g=1,game.players[b].force.character_logistic_slot_count do f[g]=game.players[b].character.get_request_slot(g)end;a=a..b..":{inventory:{"for h,i in pairs(d)do a=a.."['+"'"+'"..h.."'+"'"+']:"..i..","end;for h,i in pairs(e)do a=a.."['+"'"+'"..h.."'+"'"+']:"..i..","end;a=a.."},requestSlots:{"for h,i in pairs(f)do a=a.."['+"'"+'"..i["name"].."'+"'"+']:"..i["count"]..", "end;a=a.."}},"end;a=a.."}"game.write_file("'+pluginConfig.scriptOutputFileSubscription+'",a)') - return "/silent-command "+'local a="{"for b,c in pairs(game.players)do if c.connected then local d=game.players[b].get_inventory(defines.inventory.player_main).get_contents()local e=game.players[b].get_quickbar().get_contents()local f={}for g=1,game.players[b].force.character_logistic_slot_count do f[g]=game.players[b].character.get_request_slot(g)end;a=a..b..":{inventory:{"for h,i in pairs(d)do a=a.."['+"'"+'"..h.."'+"'"+']:"..i..","end;for h,i in pairs(e)do a=a.."['+"'"+'"..h.."'+"'"+']:"..i..","end;a=a.."},requestSlots:{"for h,i in pairs(f)do a=a.."['+"'"+'"..i["name"].."'+"'"+']:"..i["count"]..", "end;a=a.."}},"end end;a=a.."}"game.write_file("'+outputFile+'",a)' + //return "/silent-command "+'local a="{"for b,c in pairs(game.players)do if c.connected then local d=game.players[b].get_inventory(defines.inventory.player_main).get_contents()local e=game.players[b].get_quickbar().get_contents()local f={}for g=1,game.players[b].force.character_logistic_slot_count do f[g]=game.players[b].character.get_request_slot(g)end;a=a..b..":{inventory:{"for h,i in pairs(d)do a=a.."['+"'"+'"..h.."'+"'"+']:"..i..","end;for h,i in pairs(e)do a=a.."['+"'"+'"..h.."'+"'"+']:"..i..","end;a=a.."},requestSlots:{"for h,i in pairs(f)do a=a.."['+"'"+'"..i["name"].."'+"'"+']:"..i["count"]..", "end;a=a.."}},"end end;a=a.."}"game.write_file("'+outputFile+'",a)' + return '/silent-command '+'local a='+"'"+'{"players":{'+"'"+'local b=false;for c,d in pairs(game.players)do if d.connected then if b then a=a..'+"'"+','+"'"+'else b=true end;local e=game.players[c].get_inventory(defines.inventory.player_main).get_contents()local f=game.players[c].get_quickbar().get_contents()local g={}for h=1,game.players[c].force.character_logistic_slot_count do g[h]=game.players[c].character.get_request_slot(h)end;a=a..'+"'"+'"'+"'"+'..c..'+"'"+'":{"inventory":{'+"'"+'local i=false;for j,k in pairs(e)do if i then a=a..'+"'"+','+"'"+'else i=true end;a=a..'+"'"+'"'+"'"+'..j..'+"'"+'":'+"'"+'..k end;for j,k in pairs(f)do if i then a=a..'+"'"+','+"'"+'else i=true end;a=a..'+"'"+'"'+"'"+'..j..'+"'"+'":'+"'"+'..k end;a=a..'+"'"+'},"requestSlots":{'+"'"+'i=false;for j,k in pairs(g)do if i then a=a..'+"'"+','+"'"+'else i=true end;a=a..'+"'"+'"'+"'"+'..k['+"'"+'name'+"'"+']..'+"'"+'":'+"'"+'..k['+"'"+'count'+"'"+']end;a=a..'+"'"+'}}'+"'"+'end end;a=a.."}}"game.write_file("'+outputFile+'",a)' /* needle.post(config.masterIP+':'+config.masterPort+'/api/editSlaveMeta', {slaveID: config.unique, password: config.clientPassword, meta: {UPS:UPS}}, function(err, resp) { // success? diff --git a/sharedPlugins/inventoryImports/lua commands.lua b/sharedPlugins/inventoryImports/lua commands.lua index d12bee6b..d3668a33 100644 --- a/sharedPlugins/inventoryImports/lua commands.lua +++ b/sharedPlugins/inventoryImports/lua commands.lua @@ -22,11 +22,18 @@ for k,v in pairs(requests) do end game.print(o) --- sum up our inventory and request slots +-- sum up our inventory and request slots and print it as valid JSON +-- turns out the valid part is important /c -local o = "{" +local o = '{"players":{' +local notFirst = false for a,b in pairs(game.players) do if b.connected then + if notFirst then + o = o .. ',' + else + notFirst = true + end local inventory = game.players[a].get_inventory(defines.inventory.player_main).get_contents() local quickbar = game.players[a].get_quickbar().get_contents() --[[ Get contents of requester slots into a table ]] @@ -35,27 +42,46 @@ for a,b in pairs(game.players) do requests[i] = game.players[a].character.get_request_slot(i) end - o = o..a..":{inventory:{" - --[[ generate JS table (not JSON) ]] + o = o..'"'..a..'":{"inventory":{' + --[[ generate JSON table ]] + local addComma = false for k,v in pairs(inventory) do - o = o.."['"..k.."']:"..v.."," + if addComma then + o = o..',' + else + addComma = true + end + o = o..'"'..k..'":'..v end for k,v in pairs(quickbar) do - o = o.."['"..k.."']:"..v.."," + if addComma then + o = o..',' + else + addComma = true + end + o = o..'"'..k..'":'..v end - o = o.."},requestSlots:{" + o = o..'},"requestSlots":{' + addComma = false for k,v in pairs(requests) do - o = o.."['"..v["name"].."']:"..v["count"]..", " + if addComma then + o = o..',' + else + addComma = true + end + o = o..'"'..v['name']..'":'..v['count'] end - o = o.."}}," + o = o..'}}' --[[ We need a comma after these two if there is more than 1 player, handled up top by notFirst]] end end -o = o.."}" +o = o.."}}" +game.write_file("t.txt", o) game.print(o) -game.write_file("test.txt", o) --[[ +Example output +{"players":{"1":{"inventory":{"stone":12,"iron-ore":120,"raw-wood":10,"iron-plate":8,"steel-plate":480},"requestSlots":{}}}} Minification procedure: @@ -63,12 +89,13 @@ Run through https://mothereff.in/lua-minifier Replace ' with '+"'"+' Prepend /silent-command remove game.print(o) -replace "test.txt" with "'+outputFile+'" +replace "t.txt" with "'+outputFile+'" enclose in '' ]] -- minified strings (newest on the top) +local a='+"'"+'{"players":{'+"'"+'local b=false;for c,d in pairs(game.players)do if d.connected then if b then a=a..'+"'"+','+"'"+'else b=true end;local e=game.players[c].get_inventory(defines.inventory.player_main).get_contents()local f=game.players[c].get_quickbar().get_contents()local g={}for h=1,game.players[c].force.character_logistic_slot_count do g[h]=game.players[c].character.get_request_slot(h)end;a=a..'+"'"+'"'+"'"+'..c..'+"'"+'":{"inventory":{'+"'"+'local i=false;for j,k in pairs(e)do if i then a=a..'+"'"+','+"'"+'else i=true end;a=a..'+"'"+'"'+"'"+'..j..'+"'"+'":'+"'"+'..k end;for j,k in pairs(f)do if i then a=a..'+"'"+','+"'"+'else i=true end;a=a..'+"'"+'"'+"'"+'..j..'+"'"+'":'+"'"+'..k end;a=a..'+"'"+'},"requestSlots":{'+"'"+'i=false;for j,k in pairs(g)do if i then a=a..'+"'"+','+"'"+'else i=true end;a=a..'+"'"+'"'+"'"+'..k['+"'"+'name'+"'"+']..'+"'"+'":'+"'"+'..k['+"'"+'count'+"'"+']end;a=a..'+"'"+'}}'+"'"+'end end;a=a.."}}"game.write_file("'+outputFile+'",a) -- offline playes local a="{"for b,c in pairs(game.players)do if c.connected then local d=game.players[b].get_inventory(defines.inventory.player_main).get_contents()local e=game.players[b].get_quickbar().get_contents()local f={}for g=1,game.players[b].force.character_logistic_slot_count do f[g]=game.players[b].character.get_request_slot(g)end;a=a..b..":{inventory:{"for h,i in pairs(d)do a=a.."['+"'"+'"..h.."'+"'"+']:"..i..","end;for h,i in pairs(e)do a=a.."['+"'"+'"..h.."'+"'"+']:"..i..","end;a=a.."},requestSlots:{"for h,i in pairs(f)do a=a.."['+"'"+'"..i["name"].."'+"'"+']:"..i["count"]..", "end;a=a.."}},"end end;a=a.."}"game.print(a) -- multiple players @@ -81,7 +108,7 @@ local inventory = game.players["Danielv123"].get_inventory(defines.inventory.pla -- name should be players[i] /c -local name = "Danielv123" +local name = "1" local items = {["iron-ore"]=30, ["steel-plate"]=120} local items_failed = {} if(game.players[name] and game.players[name].connected) then @@ -96,17 +123,58 @@ if(game.players[name] and game.players[name].connected) then items_failed[name][item] = count - i end end - game.write_file("t.txt", serpent.line(items_failed, {["comment"]=false, ["compact"]=true})) + game.write_file("t.txt", serpent.line({["exports"]=items_failed}, {["comment"]=false, ["compact"]=true})) +end + +--[[ +Writes something like + +{exports={["1"]={["iron-ore"]=0,["steel-plate"]=0}}} + +converting to JS would be +replace "[" and "]" with ""??? +replace "=" with ":" +]] + +--[[ +JSON edition + +{"exports":{"1":[{"name":"iron-ore","count":0},{"name":"steel-plate","count":0}]}} + +Should always return valid JSON +]] +/c +local name = "1" +local items = {["iron-ore"]=30, ["steel-plate"]=120} +if(game.players[name] and game.players[name].connected) then + local o = '{"exports":{"'..name..'":[' + local addComma = false + for item,count in pairs(items) do + local i = game.players[name].insert{name=item,count=count} + if addComma then + o = o..',' + else + addComma = true + end + o = o .. '{"name":"'..item..'","count":'..count - i..'}' + end + o = o .. ']}}' + game.write_file("t.txt", o) end --[[ Minification procedure: Run through https://mothereff.in/lua-minifier +Replace ' with '+"'"+' Prepend /silent-command replace "t.txt" with "'+outputFile+'" enclose in '' ]] + +-- JSON outputFile +local a="1"local b={["iron-ore"]=30,["steel-plate"]=120}local c={}if game.players[a]and game.players[a].connected then local d='+"'"+'{"exports":{"'+"'"+'..a..'+"'"+'":['+"'"+'local e=false;for f,g in pairs(b)do local h=game.players[a].insert{name=f,count=g}if e then d=d..'+"'"+','+"'"+'else e=true end;d=d..'+"'"+'{"name":"'+"'"+'..f..'+"'"+'","count":'+"'"+'..g-h..'+"'"+'}'+"'"+'end;d=d..'+"'"+']}}'+"'"+'game.write_file("'+outputFile+'",d)end +-- output to serpented LUA table local a="Danielv123"local b={["iron-ore"]=30,["steel-plate"]=120}local c={}if game.players[a]and game.players[a].connected then for d,e in pairs(b)do local f=game.players[a].insert{name=d,count=e}if not c[a]then c[a]={}end;if c[a][d]then c[a][d]=c[a][d]+e-f else c[a][d]=e-f end end;game.write_file("'+outputFile+'",serpent.line(c,{["comment"]=false,["compact"]=true}))end