reorganization
This commit is contained in:
135
apis/base64.lua
Normal file
135
apis/base64.lua
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
-- Base64 Encoder / Decoder
|
||||||
|
-- By KillaVanilla
|
||||||
|
-- see: http://www.computercraft.info/forums2/index.php?/topic/12450-killavanillas-various-apis/
|
||||||
|
|
||||||
|
Base64 = { }
|
||||||
|
|
||||||
|
local alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||||
|
|
||||||
|
local function sixBitToBase64(input)
|
||||||
|
return string.sub(alphabet, input+1, input+1)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function base64ToSixBit(input)
|
||||||
|
for i=1, 64 do
|
||||||
|
if input == string.sub(alphabet, i, i) then
|
||||||
|
return i-1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function octetToBase64(o1, o2, o3)
|
||||||
|
local i1 = sixBitToBase64(bit.brshift(bit.band(o1, 0xFC), 2))
|
||||||
|
local i2 = "A"
|
||||||
|
local i3 = "="
|
||||||
|
local i4 = "="
|
||||||
|
if o2 then
|
||||||
|
i2 = sixBitToBase64(bit.bor( bit.blshift(bit.band(o1, 3), 4), bit.brshift(bit.band(o2, 0xF0), 4) ))
|
||||||
|
if not o3 then
|
||||||
|
i3 = sixBitToBase64(bit.blshift(bit.band(o2, 0x0F), 2))
|
||||||
|
else
|
||||||
|
i3 = sixBitToBase64(bit.bor( bit.blshift(bit.band(o2, 0x0F), 2), bit.brshift(bit.band(o3, 0xC0), 6) ))
|
||||||
|
end
|
||||||
|
else
|
||||||
|
i2 = sixBitToBase64(bit.blshift(bit.band(o1, 3), 4))
|
||||||
|
end
|
||||||
|
if o3 then
|
||||||
|
i4 = sixBitToBase64(bit.band(o3, 0x3F))
|
||||||
|
end
|
||||||
|
|
||||||
|
return i1..i2..i3..i4
|
||||||
|
end
|
||||||
|
|
||||||
|
-- octet 1 needs characters 1/2
|
||||||
|
-- octet 2 needs characters 2/3
|
||||||
|
-- octet 3 needs characters 3/4
|
||||||
|
|
||||||
|
local function base64ToThreeOctet(s1)
|
||||||
|
local c1 = base64ToSixBit(string.sub(s1, 1, 1))
|
||||||
|
local c2 = base64ToSixBit(string.sub(s1, 2, 2))
|
||||||
|
local c3 = 0
|
||||||
|
local c4 = 0
|
||||||
|
local o1 = 0
|
||||||
|
local o2 = 0
|
||||||
|
local o3 = 0
|
||||||
|
if string.sub(s1, 3, 3) == "=" then
|
||||||
|
c3 = nil
|
||||||
|
c4 = nil
|
||||||
|
elseif string.sub(s1, 4, 4) == "=" then
|
||||||
|
c3 = base64ToSixBit(string.sub(s1, 3, 3))
|
||||||
|
c4 = nil
|
||||||
|
else
|
||||||
|
c3 = base64ToSixBit(string.sub(s1, 3, 3))
|
||||||
|
c4 = base64ToSixBit(string.sub(s1, 4, 4))
|
||||||
|
end
|
||||||
|
o1 = bit.bor( bit.blshift(c1, 2), bit.brshift(bit.band( c2, 0x30 ), 4) )
|
||||||
|
if c3 then
|
||||||
|
o2 = bit.bor( bit.blshift(bit.band(c2, 0x0F), 4), bit.brshift(bit.band( c3, 0x3C ), 2) )
|
||||||
|
else
|
||||||
|
o2 = nil
|
||||||
|
end
|
||||||
|
if c4 then
|
||||||
|
o3 = bit.bor( bit.blshift(bit.band(c3, 3), 6), c4 )
|
||||||
|
else
|
||||||
|
o3 = nil
|
||||||
|
end
|
||||||
|
return o1, o2, o3
|
||||||
|
end
|
||||||
|
|
||||||
|
local function splitIntoBlocks(bytes)
|
||||||
|
local blockNum = 1
|
||||||
|
local blocks = {}
|
||||||
|
for i=1, #bytes, 3 do
|
||||||
|
blocks[blockNum] = {bytes[i], bytes[i+1], bytes[i+2]}
|
||||||
|
--[[
|
||||||
|
if #blocks[blockNum] < 3 then
|
||||||
|
for j=#blocks[blockNum]+1, 3 do
|
||||||
|
table.insert(blocks[blockNum], 0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
]]
|
||||||
|
blockNum = blockNum+1
|
||||||
|
end
|
||||||
|
return blocks
|
||||||
|
end
|
||||||
|
|
||||||
|
function Base64.encode(bytes)
|
||||||
|
local blocks = splitIntoBlocks(bytes)
|
||||||
|
local output = ""
|
||||||
|
for i=1, #blocks do
|
||||||
|
output = output..octetToBase64( unpack(blocks[i]) )
|
||||||
|
end
|
||||||
|
return output
|
||||||
|
end
|
||||||
|
|
||||||
|
function Base64.decode(str)
|
||||||
|
local bytes = {}
|
||||||
|
local blocks = {}
|
||||||
|
local blockNum = 1
|
||||||
|
for i=1, #str, 4 do
|
||||||
|
blocks[blockNum] = string.sub(str, i, i+3)
|
||||||
|
blockNum = blockNum+1
|
||||||
|
end
|
||||||
|
for i=1, #blocks do
|
||||||
|
local o1, o2, o3 = base64ToThreeOctet(blocks[i])
|
||||||
|
table.insert(bytes, o1)
|
||||||
|
table.insert(bytes, o2)
|
||||||
|
table.insert(bytes, o3)
|
||||||
|
if (i % 1000) == 0 then
|
||||||
|
os.sleep(0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- Remove padding:
|
||||||
|
--[[
|
||||||
|
for i=#bytes, 1, -1 do
|
||||||
|
if bytes[i] ~= 0 then
|
||||||
|
break
|
||||||
|
else
|
||||||
|
bytes[i] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
]]
|
||||||
|
return bytes
|
||||||
|
end
|
||||||
|
|
||||||
|
return Base64
|
||||||
613
apis/blocks.lua
Normal file
613
apis/blocks.lua
Normal file
@@ -0,0 +1,613 @@
|
|||||||
|
local class = require('class')
|
||||||
|
local Util = require('util')
|
||||||
|
local TableDB = require('tableDB')
|
||||||
|
local JSON = require('json')
|
||||||
|
|
||||||
|
-- see https://github.com/Khroki/MCEdit-Unified/blob/master/pymclevel/minecraft.yaml
|
||||||
|
-- see https://github.com/Khroki/MCEdit-Unified/blob/master/Items/minecraft/blocks.json
|
||||||
|
|
||||||
|
--[[-- nameDB --]]--
|
||||||
|
local nameDB = TableDB({
|
||||||
|
fileName = 'blocknames.db'
|
||||||
|
})
|
||||||
|
function nameDB:load(dir, blockDB)
|
||||||
|
self.fileName = fs.combine(dir, self.fileName)
|
||||||
|
if fs.exists(self.fileName) then
|
||||||
|
TableDB.load(self)
|
||||||
|
end
|
||||||
|
self.blockDB = blockDB
|
||||||
|
end
|
||||||
|
|
||||||
|
function nameDB:getName(id, dmg)
|
||||||
|
return self:lookupName(id, dmg) or id .. ':' .. dmg
|
||||||
|
end
|
||||||
|
|
||||||
|
function nameDB:lookupName(id, dmg)
|
||||||
|
-- is it in the name db ?
|
||||||
|
local name = self:get({ id, dmg })
|
||||||
|
if name then
|
||||||
|
return name
|
||||||
|
end
|
||||||
|
|
||||||
|
-- is it in the block db ?
|
||||||
|
for _,v in pairs(self.blockDB.data) do
|
||||||
|
if v.strId == id and v.dmg == dmg then
|
||||||
|
return v.name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[-- blockDB --]]--
|
||||||
|
local blockDB = TableDB()
|
||||||
|
|
||||||
|
function blockDB:load()
|
||||||
|
|
||||||
|
local dir = fs.getDir(shell.getRunningProgram())
|
||||||
|
local blocks = JSON.decodeFromFile(fs.combine(dir, 'etc/blocks.json'))
|
||||||
|
|
||||||
|
if not blocks then
|
||||||
|
error('Unable to read blocks.json')
|
||||||
|
end
|
||||||
|
|
||||||
|
for strId, block in pairs(blocks) do
|
||||||
|
strId = 'minecraft:' .. strId
|
||||||
|
if type(block.name) == 'string' then
|
||||||
|
self:add(block.id, 0, block.name, strId, block.place)
|
||||||
|
else
|
||||||
|
for nid,name in pairs(block.name) do
|
||||||
|
self:add(block.id, nid - 1, name, strId, block.place)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function blockDB:lookup(id, dmg)
|
||||||
|
if not id then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
return self.data[id .. ':' .. dmg]
|
||||||
|
end
|
||||||
|
|
||||||
|
function blockDB:add(id, dmg, name, strId, place)
|
||||||
|
local key = id .. ':' .. dmg
|
||||||
|
|
||||||
|
TableDB.add(self, key, {
|
||||||
|
id = id,
|
||||||
|
dmg = dmg,
|
||||||
|
name = name,
|
||||||
|
strId = strId,
|
||||||
|
place = place,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[-- placementDB --]]--
|
||||||
|
-- in memory table that expands the standardBlock and blockType tables for each item/dmg/placement combination
|
||||||
|
local placementDB = TableDB()
|
||||||
|
|
||||||
|
function placementDB:load(sbDB, btDB)
|
||||||
|
|
||||||
|
for k,blockType in pairs(sbDB.data) do
|
||||||
|
local bt = btDB.data[blockType]
|
||||||
|
if not bt then
|
||||||
|
error('missing block type: ' .. blockType)
|
||||||
|
end
|
||||||
|
local id, dmg = string.match(k, '(%d+):*(%d+)')
|
||||||
|
self:addSubsForBlockType(tonumber(id), tonumber(dmg), bt)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function placementDB:load2(sbDB, btDB)
|
||||||
|
|
||||||
|
for k,v in pairs(sbDB.data) do
|
||||||
|
if v.place then
|
||||||
|
local bt = btDB.data[v.place]
|
||||||
|
if not bt then
|
||||||
|
error('missing block type: ' .. v.place)
|
||||||
|
end
|
||||||
|
local id, dmg = string.match(k, '(%d+):*(%d+)')
|
||||||
|
self:addSubsForBlockType(tonumber(id), tonumber(dmg), bt)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- special case for quartz pillars
|
||||||
|
self:addSubsForBlockType(155, 2, btDB.data['quartz-pillar'])
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function placementDB:addSubsForBlockType(id, dmg, bt)
|
||||||
|
for _,sub in pairs(bt) do
|
||||||
|
local odmg = sub.odmg
|
||||||
|
if type(sub.odmg) == 'string' then
|
||||||
|
odmg = dmg + tonumber(string.match(odmg, '+(%d+)'))
|
||||||
|
end
|
||||||
|
|
||||||
|
local b = blockDB:lookup(id, dmg)
|
||||||
|
local strId = tostring(id)
|
||||||
|
if b then
|
||||||
|
strId = b.strId
|
||||||
|
end
|
||||||
|
|
||||||
|
self:add(
|
||||||
|
id,
|
||||||
|
odmg,
|
||||||
|
sub.sid or strId,
|
||||||
|
sub.sdmg or dmg,
|
||||||
|
sub.dir,
|
||||||
|
sub.extra)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function placementDB:add(id, dmg, sid, sdmg, direction, extra)
|
||||||
|
if direction and #direction == 0 then
|
||||||
|
direction = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local entry = {
|
||||||
|
oid = id, -- numeric ID
|
||||||
|
odmg = dmg, -- dmg with placement info
|
||||||
|
id = sid, -- string ID
|
||||||
|
dmg = sdmg, -- dmg without placement info
|
||||||
|
direction = direction,
|
||||||
|
}
|
||||||
|
if extra then
|
||||||
|
Util.merge(entry, extra)
|
||||||
|
end
|
||||||
|
|
||||||
|
self.data[id .. ':' .. dmg] = entry
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[-- BlockTypeDB --]]--
|
||||||
|
local blockTypeDB = TableDB()
|
||||||
|
|
||||||
|
function blockTypeDB:addTemp(blockType, subs)
|
||||||
|
local bt = self.data[blockType]
|
||||||
|
if not bt then
|
||||||
|
bt = { }
|
||||||
|
self.data[blockType] = bt
|
||||||
|
end
|
||||||
|
for _,sub in pairs(subs) do
|
||||||
|
table.insert(bt, {
|
||||||
|
odmg = sub[1],
|
||||||
|
sid = sub[2],
|
||||||
|
sdmg = sub[3],
|
||||||
|
dir = sub[4],
|
||||||
|
extra = sub[5]
|
||||||
|
})
|
||||||
|
end
|
||||||
|
self.dirty = true
|
||||||
|
end
|
||||||
|
|
||||||
|
function blockTypeDB:load()
|
||||||
|
|
||||||
|
blockTypeDB:addTemp('stairs', {
|
||||||
|
{ 0, nil, 0, 'east-up' },
|
||||||
|
{ 1, nil, 0, 'west-up' },
|
||||||
|
{ 2, nil, 0, 'south-up' },
|
||||||
|
{ 3, nil, 0, 'north-up' },
|
||||||
|
{ 4, nil, 0, 'east-down' },
|
||||||
|
{ 5, nil, 0, 'west-down' },
|
||||||
|
{ 6, nil, 0, 'south-down' },
|
||||||
|
{ 7, nil, 0, 'north-down' },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('gate', {
|
||||||
|
{ 0, nil, 0, 'north' },
|
||||||
|
{ 1, nil, 0, 'east' },
|
||||||
|
{ 2, nil, 0, 'south' },
|
||||||
|
{ 3, nil, 0, 'west' },
|
||||||
|
{ 4, nil, 0, 'north' },
|
||||||
|
{ 5, nil, 0, 'east' },
|
||||||
|
{ 6, nil, 0, 'south' },
|
||||||
|
{ 7, nil, 0, 'west' },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('pumpkin', {
|
||||||
|
{ 0, nil, 0, 'north-block' },
|
||||||
|
{ 1, nil, 0, 'east-block' },
|
||||||
|
{ 2, nil, 0, 'south-block' },
|
||||||
|
{ 3, nil, 0, 'west-block' },
|
||||||
|
{ 4, nil, 0, 'north-block' },
|
||||||
|
{ 5, nil, 0, 'east-block' },
|
||||||
|
{ 6, nil, 0, 'south-block' },
|
||||||
|
{ 7, nil, 0, 'west-block' },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('anvil', {
|
||||||
|
{ 0, nil, 0, 'south' },
|
||||||
|
{ 1, nil, 0, 'east' },
|
||||||
|
{ 2, nil, 0, 'south'},
|
||||||
|
{ 3, nil, 0, 'east' },
|
||||||
|
{ 4, nil, 0, 'south' },
|
||||||
|
{ 5, nil, 0, 'east' },
|
||||||
|
{ 6, nil, 0, 'east' },
|
||||||
|
{ 7, nil, 0, 'south' },
|
||||||
|
{ 8, nil, 0, 'south' },
|
||||||
|
{ 9, nil, 0, 'east' },
|
||||||
|
{ 10, nil, 0, 'east' },
|
||||||
|
{ 11, nil, 0, 'south' },
|
||||||
|
{ 12, nil, 0 },
|
||||||
|
{ 13, nil, 0 },
|
||||||
|
{ 14, nil, 0 },
|
||||||
|
{ 15, nil, 0 },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('bed', {
|
||||||
|
{ 0, nil, 0, 'south' },
|
||||||
|
{ 1, nil, 0, 'west' },
|
||||||
|
{ 2, nil, 0, 'north' },
|
||||||
|
{ 3, nil, 0, 'east' },
|
||||||
|
{ 4, nil, 0, 'south' },
|
||||||
|
{ 5, nil, 0, 'west' },
|
||||||
|
{ 6, nil, 0, 'north' },
|
||||||
|
{ 7, nil, 0, 'east' },
|
||||||
|
{ 8, 'minecraft:air', 0 },
|
||||||
|
{ 9, 'minecraft:air', 0 },
|
||||||
|
{ 10, 'minecraft:air', 0 },
|
||||||
|
{ 11, 'minecraft:air', 0 },
|
||||||
|
{ 12, 'minecraft:air', 0 },
|
||||||
|
{ 13, 'minecraft:air', 0 },
|
||||||
|
{ 14, 'minecraft:air', 0 },
|
||||||
|
{ 15, 'minecraft:air', 0 },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('comparator', {
|
||||||
|
{ 0, nil, 0, 'south' },
|
||||||
|
{ 1, nil, 0, 'west' },
|
||||||
|
{ 2, nil, 0, 'north' },
|
||||||
|
{ 3, nil, 0, 'east' },
|
||||||
|
{ 4, nil, 0, 'south' },
|
||||||
|
{ 5, nil, 0, 'west' },
|
||||||
|
{ 6, nil, 0, 'north' },
|
||||||
|
{ 7, nil, 0, 'east' },
|
||||||
|
{ 8, nil, 0, 'south' },
|
||||||
|
{ 9, nil, 0, 'west' },
|
||||||
|
{ 10, nil, 0, 'north' },
|
||||||
|
{ 11, nil, 0, 'east' },
|
||||||
|
{ 12, nil, 0, 'south' },
|
||||||
|
{ 13, nil, 0, 'west' },
|
||||||
|
{ 14, nil, 0, 'north' },
|
||||||
|
{ 15, nil, 0, 'east' },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('quartz-pillar', {
|
||||||
|
{ 2, nil, 2 },
|
||||||
|
{ 3, nil, 2, 'north-south-block' },
|
||||||
|
{ 4, nil, 2, 'east-west-block' }, -- should be east-west-block
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('hay-bale', {
|
||||||
|
{ 0, nil, 0 },
|
||||||
|
{ 4, nil, 0, 'east-west-block' }, -- should be east-west-block
|
||||||
|
{ 8, nil, 0, 'north-south-block' },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('button', {
|
||||||
|
{ 1, nil, 0, 'west-block' },
|
||||||
|
{ 2, nil, 0, 'east-block' },
|
||||||
|
{ 3, nil, 0, 'north-block' },
|
||||||
|
{ 4, nil, 0, 'south-block' },
|
||||||
|
{ 5, nil, 0 }, -- block top
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('cauldron', {
|
||||||
|
{ 0, nil, 0 },
|
||||||
|
{ 1, nil, 0 },
|
||||||
|
{ 2, nil, 0 },
|
||||||
|
{ 3, nil, 0 },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('dispenser', {
|
||||||
|
{ 0, nil, 0, 'wrench-down' },
|
||||||
|
{ 1, nil, 0, 'wrench-up' },
|
||||||
|
{ 2, nil, 0, 'south' },
|
||||||
|
{ 3, nil, 0, 'north' },
|
||||||
|
{ 4, nil, 0, 'east' },
|
||||||
|
{ 5, nil, 0, 'west' },
|
||||||
|
{ 9, nil, 0 },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('end_rod', {
|
||||||
|
{ 0, nil, 0, 'wrench-down' },
|
||||||
|
{ 1, nil, 0, 'wrench-up' },
|
||||||
|
{ 2, nil, 0, 'south-block-flip' },
|
||||||
|
{ 3, nil, 0, 'north-block-flip' },
|
||||||
|
{ 4, nil, 0, 'east-block-flip' },
|
||||||
|
{ 5, nil, 0, 'west-block-flip' },
|
||||||
|
{ 9, nil, 0 },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('hopper', {
|
||||||
|
{ 0, nil, 0 },
|
||||||
|
{ 1, nil, 0 },
|
||||||
|
{ 2, nil, 0, 'south-block' },
|
||||||
|
{ 3, nil, 0, 'north-block' },
|
||||||
|
{ 4, nil, 0, 'east-block' },
|
||||||
|
{ 5, nil, 0, 'west-block' },
|
||||||
|
{ 8, nil, 0 },
|
||||||
|
{ 9, nil, 0 },
|
||||||
|
{ 10, nil, 0 },
|
||||||
|
{ 11, nil, 0, 'south-block' },
|
||||||
|
{ 12, nil, 0, 'north-block' },
|
||||||
|
{ 13, nil, 0, 'east-block' },
|
||||||
|
{ 14, nil, 0, 'west-block' },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('mobhead', {
|
||||||
|
{ 0, nil, 0 },
|
||||||
|
{ 1, nil, 0 },
|
||||||
|
{ 2, nil, 0, 'south-block' },
|
||||||
|
{ 3, nil, 0, 'north-block' },
|
||||||
|
{ 4, nil, 0, 'west-block' },
|
||||||
|
{ 5, nil, 0, 'east-block' },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('rail', {
|
||||||
|
{ 0, nil, 0, 'south' },
|
||||||
|
{ 1, nil, 0, 'east' },
|
||||||
|
{ 2, nil, 0, 'east' },
|
||||||
|
{ 3, nil, 0, 'east' },
|
||||||
|
{ 4, nil, 0, 'south' },
|
||||||
|
{ 5, nil, 0, 'south' },
|
||||||
|
{ 6, nil, 0, 'east' },
|
||||||
|
{ 7, nil, 0, 'south' },
|
||||||
|
{ 8, nil, 0, 'east' },
|
||||||
|
{ 9, nil, 0, 'south' },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('adp-rail', {
|
||||||
|
{ 0, nil, 0, 'south' },
|
||||||
|
{ 1, nil, 0, 'east' },
|
||||||
|
{ 2, nil, 0, 'east' },
|
||||||
|
{ 3, nil, 0, 'east' },
|
||||||
|
{ 4, nil, 0, 'south' },
|
||||||
|
{ 5, nil, 0, 'south' },
|
||||||
|
{ 8, nil, 0, 'south' },
|
||||||
|
{ 9, nil, 0, 'east' },
|
||||||
|
{ 10, nil, 0, 'east' },
|
||||||
|
{ 11, nil, 0, 'east' },
|
||||||
|
{ 12, nil, 0, 'south' },
|
||||||
|
{ 13, nil, 0, 'south' },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('signpost', {
|
||||||
|
{ 0, nil, 0, 'north' },
|
||||||
|
{ 1, nil, 0, 'north', { facing = 1 } },
|
||||||
|
{ 2, nil, 0, 'north', { facing = 2 } },
|
||||||
|
{ 3, nil, 0, 'north', { facing = 3 } },
|
||||||
|
{ 4, nil, 0, 'east' },
|
||||||
|
{ 5, nil, 0, 'east', { facing = 1 } },
|
||||||
|
{ 6, nil, 0, 'east', { facing = 2 } },
|
||||||
|
{ 7, nil, 0, 'east', { facing = 3 } },
|
||||||
|
{ 8, nil, 0, 'south' },
|
||||||
|
{ 9, nil, 0, 'south', { facing = 1 } },
|
||||||
|
{ 10, nil, 0, 'south', { facing = 2 } },
|
||||||
|
{ 11, nil, 0, 'south', { facing = 3 } },
|
||||||
|
{ 12, nil, 0, 'west' },
|
||||||
|
{ 13, nil, 0, 'west', { facing = 1 } },
|
||||||
|
{ 14, nil, 0, 'west', { facing = 2 } },
|
||||||
|
{ 15, nil, 0, 'west', { facing = 3 } },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('vine', {
|
||||||
|
{ 0, nil, 0 },
|
||||||
|
{ 1, nil, 0, 'south-block-vine' },
|
||||||
|
{ 2, nil, 0, 'west-block-vine' },
|
||||||
|
{ 3, nil, 0, 'south-block-vine' },
|
||||||
|
{ 4, nil, 0, 'north-block-vine' },
|
||||||
|
{ 5, nil, 0, 'south-block-vine' },
|
||||||
|
{ 6, nil, 0, 'north-block-vine' },
|
||||||
|
{ 7, nil, 0, 'south-block-vine' },
|
||||||
|
{ 8, nil, 0, 'east-block-vine' },
|
||||||
|
{ 9, nil, 0, 'south-block-vine' },
|
||||||
|
{ 10, nil, 0, 'east-block-vine' },
|
||||||
|
{ 11, nil, 0, 'east-block-vine' },
|
||||||
|
{ 12, nil, 0, 'east-block-vine' },
|
||||||
|
{ 13, nil, 0, 'east-block-vine' },
|
||||||
|
{ 14, nil, 0, 'east-block-vine' },
|
||||||
|
{ 15, nil, 0, 'east-block-vine' },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('torch', {
|
||||||
|
{ 0, nil, 0 },
|
||||||
|
{ 1, nil, 0, 'west-block' },
|
||||||
|
{ 2, nil, 0, 'east-block' },
|
||||||
|
{ 3, nil, 0, 'north-block' },
|
||||||
|
{ 4, nil, 0, 'south-block' },
|
||||||
|
{ 5, nil, 0 },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('tripwire', {
|
||||||
|
{ 0, nil, 0, 'north-block' },
|
||||||
|
{ 1, nil, 0, 'east-block' },
|
||||||
|
{ 2, nil, 0, 'south-block' },
|
||||||
|
{ 3, nil, 0, 'west-block' },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('trapdoor', {
|
||||||
|
{ 0, nil, 0, 'south-block' },
|
||||||
|
{ 1, nil, 0, 'north-block' },
|
||||||
|
{ 2, nil, 0, 'east-block' },
|
||||||
|
{ 3, nil, 0, 'west-block' },
|
||||||
|
{ 4, nil, 0, 'south-block' },
|
||||||
|
{ 5, nil, 0, 'north-block' },
|
||||||
|
{ 6, nil, 0, 'east-block' },
|
||||||
|
{ 7, nil, 0, 'west-block' },
|
||||||
|
{ 8, nil, 0, 'south-block' },
|
||||||
|
{ 9, nil, 0, 'north-block' },
|
||||||
|
{ 10, nil, 0, 'east-block' },
|
||||||
|
{ 11, nil, 0, 'west-block' },
|
||||||
|
{ 12, nil, 0, 'south-block' },
|
||||||
|
{ 13, nil, 0, 'north-block' },
|
||||||
|
{ 14, nil, 0, 'east-block' },
|
||||||
|
{ 15, nil, 0, 'west-block' },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('piston', { -- piston placement is broken in 1.7 -- need to add work around
|
||||||
|
{ 0, nil, 0, 'piston-down' },
|
||||||
|
{ 1, nil, 0, 'piston-up' },
|
||||||
|
{ 2, nil, 0, 'piston-north' },
|
||||||
|
{ 3, nil, 0, 'piston-south' },
|
||||||
|
{ 4, nil, 0, 'piston-west' },
|
||||||
|
{ 5, nil, 0, 'piston-east' },
|
||||||
|
{ 8, nil, 0, 'piston-down' },
|
||||||
|
{ 9, nil, 0, 'piston-up' },
|
||||||
|
{ 10, nil, 0, 'piston-north' },
|
||||||
|
{ 11, nil, 0, 'piston-south' },
|
||||||
|
{ 12, nil, 0, 'piston-west' },
|
||||||
|
{ 13, nil, 0, 'piston-east' },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('lever', {
|
||||||
|
{ 0, nil, 0, 'up' },
|
||||||
|
{ 1, nil, 0, 'west-block' },
|
||||||
|
{ 2, nil, 0, 'east-block' },
|
||||||
|
{ 3, nil, 0, 'north-block' },
|
||||||
|
{ 4, nil, 0, 'south-block' },
|
||||||
|
{ 5, nil, 0, 'north' },
|
||||||
|
{ 6, nil, 0, 'west' },
|
||||||
|
{ 7, nil, 0, 'up' },
|
||||||
|
{ 8, nil, 0, 'up' },
|
||||||
|
{ 9, nil, 0, 'west-block' },
|
||||||
|
{ 10, nil, 0, 'east-block' },
|
||||||
|
{ 11, nil, 0, 'north-block' },
|
||||||
|
{ 12, nil, 0, 'south-block' },
|
||||||
|
{ 13, nil, 0, 'north' },
|
||||||
|
{ 14, nil, 0, 'west' },
|
||||||
|
{ 15, nil, 0, 'up' },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('wallsign-ladder', {
|
||||||
|
{ 0, nil, 0 },
|
||||||
|
{ 1, nil, 0 },
|
||||||
|
{ 2, nil, 0, 'south-block' },
|
||||||
|
{ 3, nil, 0, 'north-block' },
|
||||||
|
{ 4, nil, 0, 'east-block' },
|
||||||
|
{ 5, nil, 0, 'west-block' },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('chest-furnace', {
|
||||||
|
{ 0, nil, 0 },
|
||||||
|
{ 2, nil, 0, 'south' },
|
||||||
|
{ 3, nil, 0, 'north' },
|
||||||
|
{ 4, nil, 0, 'east' },
|
||||||
|
{ 5, nil, 0, 'west' },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('repeater', {
|
||||||
|
{ 0, nil, 0, 'north' },
|
||||||
|
{ 1, nil, 0, 'east' },
|
||||||
|
{ 2, nil, 0, 'south' },
|
||||||
|
{ 3, nil, 0, 'west' },
|
||||||
|
{ 4, nil, 0, 'north' },
|
||||||
|
{ 5, nil, 0, 'east' },
|
||||||
|
{ 6, nil, 0, 'south' },
|
||||||
|
{ 7, nil, 0, 'west' },
|
||||||
|
{ 8, nil, 0, 'north' },
|
||||||
|
{ 9, nil, 0, 'east' },
|
||||||
|
{ 10, nil, 0, 'south' },
|
||||||
|
{ 11, nil, 0, 'west' },
|
||||||
|
{ 12, nil, 0, 'north' },
|
||||||
|
{ 13, nil, 0, 'east' },
|
||||||
|
{ 14, nil, 0, 'south' },
|
||||||
|
{ 15, nil, 0, 'west' },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('flatten', {
|
||||||
|
{ 0, nil, 0 },
|
||||||
|
{ 1, nil, 0 },
|
||||||
|
{ 2, nil, 0 },
|
||||||
|
{ 3, nil, 0 },
|
||||||
|
{ 4, nil, 0 },
|
||||||
|
{ 5, nil, 0 },
|
||||||
|
{ 6, nil, 0 },
|
||||||
|
{ 7, nil, 0 },
|
||||||
|
{ 8, nil, 0 },
|
||||||
|
{ 9, nil, 0 },
|
||||||
|
{ 10, nil, 0 },
|
||||||
|
{ 11, nil, 0 },
|
||||||
|
{ 12, nil, 0 },
|
||||||
|
{ 13, nil, 0 },
|
||||||
|
{ 14, nil, 0 },
|
||||||
|
{ 15, nil, 0 },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('sapling', {
|
||||||
|
{ '+0', nil, nil },
|
||||||
|
{ '+8', nil, nil },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('leaves', {
|
||||||
|
{ '+0', nil, nil },
|
||||||
|
{ '+4', nil, nil },
|
||||||
|
{ '+8', nil, nil },
|
||||||
|
{ '+12', nil, nil },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('slab', {
|
||||||
|
{ '+0', nil, nil, 'bottom' },
|
||||||
|
{ '+8', nil, nil, 'top' },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('largeplant', {
|
||||||
|
{ '+0', nil, nil, 'east-door', { twoHigh = true } }, -- should use a generic double tall keyword
|
||||||
|
{ '+8', 'minecraft:air', 0 },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('wood', {
|
||||||
|
{ '+0', nil, nil },
|
||||||
|
{ '+4', nil, nil, 'east-west-block' },
|
||||||
|
{ '+8', nil, nil, 'north-south-block' },
|
||||||
|
{ '+12', nil, nil },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('door', {
|
||||||
|
{ 0, nil, 0, 'east-door', { twoHigh = true } },
|
||||||
|
{ 1, nil, 0, 'south-door', { twoHigh = true } },
|
||||||
|
{ 2, nil, 0, 'west-door', { twoHigh = true } },
|
||||||
|
{ 3, nil, 0, 'north-door', { twoHigh = true } },
|
||||||
|
{ 4, nil, 0, 'east-door', { twoHigh = true } },
|
||||||
|
{ 5, nil, 0, 'south-door', { twoHigh = true } },
|
||||||
|
{ 6, nil, 0, 'west-door', { twoHigh = true } },
|
||||||
|
{ 7, nil, 0, 'north-door', { twoHigh = true } },
|
||||||
|
{ 8,'minecraft:air', 0 },
|
||||||
|
{ 9,'minecraft:air', 0 },
|
||||||
|
{ 10,'minecraft:air', 0 },
|
||||||
|
{ 11,'minecraft:air', 0 },
|
||||||
|
{ 12,'minecraft:air', 0 },
|
||||||
|
{ 13,'minecraft:air', 0 },
|
||||||
|
{ 14,'minecraft:air', 0 },
|
||||||
|
{ 15,'minecraft:air', 0 },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('cocoa', {
|
||||||
|
{ 0, nil, 0, 'south-block' },
|
||||||
|
{ 1, nil, 0, 'west-block' },
|
||||||
|
{ 2, nil, 0, 'north-block' },
|
||||||
|
{ 3, nil, 0, 'east-block' },
|
||||||
|
{ 4, nil, 0, 'south-block' },
|
||||||
|
{ 5, nil, 0, 'west-block' },
|
||||||
|
{ 6, nil, 0, 'north-block' },
|
||||||
|
{ 7, nil, 0, 'east-block' },
|
||||||
|
{ 8, nil, 0, 'south-block' },
|
||||||
|
{ 9, nil, 0, 'west-block' },
|
||||||
|
{ 10, nil, 0, 'north-block' },
|
||||||
|
{ 11, nil, 0, 'east-block' },
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
local Blocks = class()
|
||||||
|
function Blocks:init(args)
|
||||||
|
|
||||||
|
Util.merge(self, args)
|
||||||
|
self.blockDB = blockDB
|
||||||
|
self.nameDB = nameDB
|
||||||
|
|
||||||
|
blockDB:load()
|
||||||
|
-- standardBlockDB:load()
|
||||||
|
blockTypeDB:load()
|
||||||
|
nameDB:load(self.dir, blockDB)
|
||||||
|
-- placementDB:load(standardBlockDB, blockTypeDB)
|
||||||
|
placementDB:load2(blockDB, blockTypeDB)
|
||||||
|
|
||||||
|
-- _G._b = blockDB
|
||||||
|
-- _G._s = standardBlockDB
|
||||||
|
-- _G._bt = blockTypeDB
|
||||||
|
-- _G._p = placementDB
|
||||||
|
|
||||||
|
-- Util.writeTable('pb1.lua', placementDB.data)
|
||||||
|
|
||||||
|
-- placementDB.data = { }
|
||||||
|
|
||||||
|
-- Util.writeTable('pb2.lua', placementDB.data)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- for an ID / dmg (with placement info) - return the correct block (without the placment info embedded in the dmg)
|
||||||
|
function Blocks:getPlaceableBlock(id, dmg)
|
||||||
|
|
||||||
|
local p = placementDB:get({id, dmg})
|
||||||
|
if p then
|
||||||
|
return Util.shallowCopy(p)
|
||||||
|
end
|
||||||
|
|
||||||
|
local b = blockDB:get({id, dmg})
|
||||||
|
if b then
|
||||||
|
return { id = b.strId, dmg = b.dmg }
|
||||||
|
end
|
||||||
|
|
||||||
|
b = blockDB:get({id, 0})
|
||||||
|
if b then
|
||||||
|
return { id = b.strId, dmg = b.dmg }
|
||||||
|
end
|
||||||
|
|
||||||
|
return { id = id, dmg = dmg }
|
||||||
|
end
|
||||||
|
|
||||||
|
return Blocks
|
||||||
111
apis/chestAdapter.lua
Normal file
111
apis/chestAdapter.lua
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
local class = require('class')
|
||||||
|
local Logger = require('logger')
|
||||||
|
|
||||||
|
local ChestProvider = class()
|
||||||
|
|
||||||
|
function ChestProvider:init(args)
|
||||||
|
|
||||||
|
args = args or { }
|
||||||
|
|
||||||
|
self.stacks = {}
|
||||||
|
self.name = 'chest'
|
||||||
|
self.direction = args.direction or 'up'
|
||||||
|
self.wrapSide = args.wrapSide or 'bottom'
|
||||||
|
self.p = peripheral.wrap(self.wrapSide)
|
||||||
|
end
|
||||||
|
|
||||||
|
function ChestProvider:isValid()
|
||||||
|
return self.p and self.p.getAllStacks
|
||||||
|
end
|
||||||
|
|
||||||
|
function ChestProvider:refresh()
|
||||||
|
if self.p then
|
||||||
|
self.p.condenseItems()
|
||||||
|
self.stacks = self.p.getAllStacks(false)
|
||||||
|
local t = { }
|
||||||
|
for _,s in ipairs(self.stacks) do
|
||||||
|
local key = s.id .. ':' .. s.dmg
|
||||||
|
if t[key] and t[key].qty < 64 then
|
||||||
|
t[key].max_size = t[key].qty
|
||||||
|
else
|
||||||
|
t[key] = {
|
||||||
|
qty = s.qty
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for _,s in ipairs(self.stacks) do
|
||||||
|
local key = s.id .. ':' .. s.dmg
|
||||||
|
if t[key].max_size then
|
||||||
|
s.max_size = t[key].qty
|
||||||
|
else
|
||||||
|
s.max_size = 64
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return self.stacks
|
||||||
|
end
|
||||||
|
|
||||||
|
function ChestProvider:getItemInfo(id, dmg)
|
||||||
|
local item = { id = id, dmg = dmg, qty = 0, max_size = 64 }
|
||||||
|
for _,stack in pairs(self.stacks) do
|
||||||
|
if stack.id == id and stack.dmg == dmg then
|
||||||
|
item.name = stack.display_name
|
||||||
|
item.qty = item.qty + stack.qty
|
||||||
|
item.max_size = stack.max_size
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if item.name then
|
||||||
|
return item
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function ChestProvider:craft(id, dmg, qty)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function ChestProvider:craftItems(items)
|
||||||
|
end
|
||||||
|
|
||||||
|
function ChestProvider:provide(item, qty, slot)
|
||||||
|
if self.p then
|
||||||
|
self.stacks = self.p.getAllStacks(false)
|
||||||
|
for key,stack in pairs(self.stacks) do
|
||||||
|
if stack.id == item.id and stack.dmg == item.dmg then
|
||||||
|
local amount = math.min(qty, stack.qty)
|
||||||
|
self.p.pushItemIntoSlot(self.direction, key, amount, slot)
|
||||||
|
qty = qty - amount
|
||||||
|
if qty <= 0 then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function ChestProvider:extract(slot, qty)
|
||||||
|
if self.p then
|
||||||
|
self.p.pushItem(self.direction, slot, qty)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function ChestProvider:insert(slot, qty)
|
||||||
|
if self.p then
|
||||||
|
local s, m = pcall(function() self.p.pullItem(self.direction, slot, qty) end)
|
||||||
|
if not s and m then
|
||||||
|
print('chestProvider:pullItem')
|
||||||
|
print(m)
|
||||||
|
Logger.log('chestProvider', 'Insert failed, trying again')
|
||||||
|
sleep(1)
|
||||||
|
s, m = pcall(function() self.p.pullItem(self.direction, slot, qty) end)
|
||||||
|
if not s and m then
|
||||||
|
print('chestProvider:pullItem')
|
||||||
|
print(m)
|
||||||
|
Logger.log('chestProvider', 'Insert failed again')
|
||||||
|
else
|
||||||
|
Logger.log('chestProvider', 'Insert successful')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return ChestProvider
|
||||||
140
apis/chestAdapter18.lua
Normal file
140
apis/chestAdapter18.lua
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
local class = require('class')
|
||||||
|
local Util = require('util')
|
||||||
|
local itemDB = require('itemDB')
|
||||||
|
local Peripheral = require('peripheral')
|
||||||
|
|
||||||
|
local ChestAdapter = class()
|
||||||
|
|
||||||
|
local keys = Util.transpose({
|
||||||
|
'damage',
|
||||||
|
'displayName',
|
||||||
|
'maxCount',
|
||||||
|
'maxDamage',
|
||||||
|
'name',
|
||||||
|
'nbtHash',
|
||||||
|
})
|
||||||
|
|
||||||
|
function ChestAdapter:init(args)
|
||||||
|
local defaults = {
|
||||||
|
items = { },
|
||||||
|
name = 'chest',
|
||||||
|
direction = 'up',
|
||||||
|
wrapSide = 'bottom',
|
||||||
|
}
|
||||||
|
Util.merge(self, defaults)
|
||||||
|
Util.merge(self, args)
|
||||||
|
|
||||||
|
local chest = Peripheral.getBySide(self.wrapSide)
|
||||||
|
if not chest then
|
||||||
|
chest = Peripheral.getByMethod('list')
|
||||||
|
end
|
||||||
|
if chest then
|
||||||
|
Util.merge(self, chest)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function ChestAdapter:isValid()
|
||||||
|
return not not self.list
|
||||||
|
end
|
||||||
|
|
||||||
|
function ChestAdapter:getCachedItemDetails(item, k)
|
||||||
|
local key = { item.name, item.damage, item.nbtHash }
|
||||||
|
|
||||||
|
local detail = itemDB:get(key)
|
||||||
|
if not detail then
|
||||||
|
pcall(function() detail = self.getItemMeta(k) end)
|
||||||
|
if not detail then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
-- NOT SUFFICIENT
|
||||||
|
if detail.name ~= item.name then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
for _,k in ipairs(Util.keys(detail)) do
|
||||||
|
if not keys[k] then
|
||||||
|
detail[k] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
itemDB:add(key, detail)
|
||||||
|
end
|
||||||
|
if detail then
|
||||||
|
return Util.shallowCopy(detail)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function ChestAdapter:refresh(throttle)
|
||||||
|
return self:listItems(throttle)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- provide a consolidated list of items
|
||||||
|
function ChestAdapter:listItems(throttle)
|
||||||
|
self.cache = { }
|
||||||
|
local items = { }
|
||||||
|
|
||||||
|
throttle = throttle or Util.throttle()
|
||||||
|
|
||||||
|
for k,v in pairs(self.list()) do
|
||||||
|
local key = table.concat({ v.name, v.damage, v.nbtHash }, ':')
|
||||||
|
|
||||||
|
local entry = self.cache[key]
|
||||||
|
if not entry then
|
||||||
|
entry = self:getCachedItemDetails(v, k)
|
||||||
|
if entry then
|
||||||
|
entry.count = 0
|
||||||
|
self.cache[key] = entry
|
||||||
|
table.insert(items, entry)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if entry then
|
||||||
|
entry.count = entry.count + v.count
|
||||||
|
end
|
||||||
|
throttle()
|
||||||
|
end
|
||||||
|
|
||||||
|
itemDB:flush()
|
||||||
|
|
||||||
|
return items
|
||||||
|
end
|
||||||
|
|
||||||
|
function ChestAdapter:getItemInfo(name, damage, nbtHash)
|
||||||
|
if not self.cache then
|
||||||
|
self:listItems()
|
||||||
|
end
|
||||||
|
local key = table.concat({ name, damage, nbtHash }, ':')
|
||||||
|
return self.cache[key]
|
||||||
|
end
|
||||||
|
|
||||||
|
function ChestAdapter:craft(name, damage, qty)
|
||||||
|
end
|
||||||
|
|
||||||
|
function ChestAdapter:craftItems(items)
|
||||||
|
end
|
||||||
|
|
||||||
|
function ChestAdapter:provide(item, qty, slot, direction)
|
||||||
|
local stacks = self.list()
|
||||||
|
for key,stack in pairs(stacks) do
|
||||||
|
if stack.name == item.name and stack.damage == item.damage then
|
||||||
|
local amount = math.min(qty, stack.count)
|
||||||
|
if amount > 0 then
|
||||||
|
self.pushItems(direction or self.direction, key, amount, slot)
|
||||||
|
end
|
||||||
|
qty = qty - amount
|
||||||
|
if qty <= 0 then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function ChestAdapter:extract(slot, qty, toSlot)
|
||||||
|
self.pushItems(self.direction, slot, qty, toSlot)
|
||||||
|
end
|
||||||
|
|
||||||
|
function ChestAdapter:insert(slot, qty)
|
||||||
|
self.pullItems(self.direction, slot, qty)
|
||||||
|
end
|
||||||
|
|
||||||
|
return ChestAdapter
|
||||||
870
apis/deflatelua.lua
Normal file
870
apis/deflatelua.lua
Normal file
@@ -0,0 +1,870 @@
|
|||||||
|
--[[
|
||||||
|
|
||||||
|
LUA MODULE
|
||||||
|
|
||||||
|
compress.deflatelua - deflate (and gunzip/zlib) implemented in Lua.
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
|
||||||
|
local DEFLATE = require 'compress.deflatelua'
|
||||||
|
-- uncompress gzip file
|
||||||
|
local fh = assert(io.open'foo.txt.gz', 'rb')
|
||||||
|
local ofh = assert(io.open'foo.txt', 'wb')
|
||||||
|
DEFLATE.gunzip {input=fh, output=ofh}
|
||||||
|
fh:close(); ofh:close()
|
||||||
|
-- can also uncompress from string including zlib and raw DEFLATE formats.
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
|
||||||
|
This is a pure Lua implementation of decompressing the DEFLATE format,
|
||||||
|
including the related zlib and gzip formats.
|
||||||
|
|
||||||
|
Note: This library only supports decompression.
|
||||||
|
Compression is not currently implemented.
|
||||||
|
|
||||||
|
API
|
||||||
|
|
||||||
|
Note: in the following functions, input stream `fh` may be
|
||||||
|
a file handle, string, or an iterator function that returns strings.
|
||||||
|
Output stream `ofh` may be a file handle or a function that
|
||||||
|
consumes one byte (number 0..255) per call.
|
||||||
|
|
||||||
|
DEFLATE.inflate {input=fh, output=ofh}
|
||||||
|
|
||||||
|
Decompresses input stream `fh` in the DEFLATE format
|
||||||
|
while writing to output stream `ofh`.
|
||||||
|
DEFLATE is detailed in http://tools.ietf.org/html/rfc1951 .
|
||||||
|
|
||||||
|
DEFLATE.gunzip {input=fh, output=ofh, disable_crc=disable_crc}
|
||||||
|
|
||||||
|
Decompresses input stream `fh` with the gzip format
|
||||||
|
while writing to output stream `ofh`.
|
||||||
|
`disable_crc` (defaults to `false`) will disable CRC-32 checking
|
||||||
|
to increase speed.
|
||||||
|
gzip is detailed in http://tools.ietf.org/html/rfc1952 .
|
||||||
|
|
||||||
|
DEFLATE.inflate_zlib {input=fh, output=ofh, disable_crc=disable_crc}
|
||||||
|
|
||||||
|
Decompresses input stream `fh` with the zlib format
|
||||||
|
while writing to output stream `ofh`.
|
||||||
|
`disable_crc` (defaults to `false`) will disable CRC-32 checking
|
||||||
|
to increase speed.
|
||||||
|
zlib is detailed in http://tools.ietf.org/html/rfc1950 .
|
||||||
|
|
||||||
|
DEFLATE.adler32(byte, crc) --> rcrc
|
||||||
|
|
||||||
|
Returns adler32 checksum of byte `byte` (number 0..255) appended
|
||||||
|
to string with adler32 checksum `crc`. This is internally used by
|
||||||
|
`inflate_zlib`.
|
||||||
|
ADLER32 in detailed in http://tools.ietf.org/html/rfc1950 .
|
||||||
|
|
||||||
|
COMMAND LINE UTILITY
|
||||||
|
|
||||||
|
A `gunziplua` command line utility (in folder `bin`) is also provided.
|
||||||
|
This mimicks the *nix `gunzip` utility but is a pure Lua implementation
|
||||||
|
that invokes this library. For help do
|
||||||
|
|
||||||
|
gunziplua -h
|
||||||
|
|
||||||
|
DEPENDENCIES
|
||||||
|
|
||||||
|
Requires 'digest.crc32lua' (used for optional CRC-32 checksum checks).
|
||||||
|
https://github.com/davidm/lua-digest-crc32lua
|
||||||
|
|
||||||
|
Will use a bit library ('bit', 'bit32', 'bit.numberlua') if available. This
|
||||||
|
is not that critical for this library but is required by digest.crc32lua.
|
||||||
|
|
||||||
|
'pythonic.optparse' is only required by the optional `gunziplua`
|
||||||
|
command-line utilty for command line parsing.
|
||||||
|
https://github.com/davidm/lua-pythonic-optparse
|
||||||
|
|
||||||
|
INSTALLATION
|
||||||
|
|
||||||
|
Copy the `compress` directory into your LUA_PATH.
|
||||||
|
|
||||||
|
REFERENCES
|
||||||
|
|
||||||
|
[1] DEFLATE Compressed Data Format Specification version 1.3
|
||||||
|
http://tools.ietf.org/html/rfc1951
|
||||||
|
[2] GZIP file format specification version 4.3
|
||||||
|
http://tools.ietf.org/html/rfc1952
|
||||||
|
[3] http://en.wikipedia.org/wiki/DEFLATE
|
||||||
|
[4] pyflate, by Paul Sladen
|
||||||
|
http://www.paul.sladen.org/projects/pyflate/
|
||||||
|
[5] Compress::Zlib::Perl - partial pure Perl implementation of
|
||||||
|
Compress::Zlib
|
||||||
|
http://search.cpan.org/~nwclark/Compress-Zlib-Perl/Perl.pm
|
||||||
|
|
||||||
|
LICENSE
|
||||||
|
|
||||||
|
(c) 2008-2011 David Manura. Licensed under the same terms as Lua (MIT).
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
(end license)
|
||||||
|
--]]
|
||||||
|
|
||||||
|
local M = {_TYPE='module', _NAME='compress.deflatelua', _VERSION='0.3.20111128'}
|
||||||
|
|
||||||
|
local assert = assert
|
||||||
|
local error = error
|
||||||
|
local ipairs = ipairs
|
||||||
|
local pairs = pairs
|
||||||
|
local print = print
|
||||||
|
local require = require
|
||||||
|
local tostring = tostring
|
||||||
|
local type = type
|
||||||
|
local setmetatable = setmetatable
|
||||||
|
local io = io
|
||||||
|
local math = math
|
||||||
|
local table_sort = table.sort
|
||||||
|
local math_max = math.max
|
||||||
|
local string_char = string.char
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Requires the first module listed that exists, else raises like `require`.
|
||||||
|
If a non-string is encountered, it is returned.
|
||||||
|
Second return value is module name loaded (or '').
|
||||||
|
--]]
|
||||||
|
local function requireany(...)
|
||||||
|
local errs = {}
|
||||||
|
for i = 1, select('#', ...) do local name = select(i, ...)
|
||||||
|
if type(name) ~= 'string' then return name, '' end
|
||||||
|
local ok, mod = pcall(require, name)
|
||||||
|
if ok then return mod, name end
|
||||||
|
errs[#errs+1] = mod
|
||||||
|
end
|
||||||
|
error(table.concat(errs, '\n'), 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
--local crc32 = require "digest.crc32lua" . crc32_byte
|
||||||
|
--local bit, name_ = requireany('bit', 'bit32', 'bit.numberlua', nil)
|
||||||
|
local bit
|
||||||
|
local crc32
|
||||||
|
|
||||||
|
local DEBUG = false
|
||||||
|
|
||||||
|
-- Whether to use `bit` library functions in current module.
|
||||||
|
-- Unlike the crc32 library, it doesn't make much difference in this module.
|
||||||
|
local NATIVE_BITOPS = (bit ~= nil)
|
||||||
|
|
||||||
|
local band, lshift, rshift
|
||||||
|
if NATIVE_BITOPS then
|
||||||
|
band = bit.band
|
||||||
|
lshift = bit.lshift
|
||||||
|
rshift = bit.rshift
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function warn(s)
|
||||||
|
io.stderr:write(s, '\n')
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function debug(...)
|
||||||
|
print('DEBUG', ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function runtime_error(s, level)
|
||||||
|
level = level or 1
|
||||||
|
error({s}, level+1)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function make_outstate(outbs)
|
||||||
|
local outstate = {}
|
||||||
|
outstate.outbs = outbs
|
||||||
|
outstate.window = {}
|
||||||
|
outstate.window_pos = 1
|
||||||
|
return outstate
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function output(outstate, byte)
|
||||||
|
-- debug('OUTPUT:', s)
|
||||||
|
local window_pos = outstate.window_pos
|
||||||
|
outstate.outbs(byte)
|
||||||
|
outstate.window[window_pos] = byte
|
||||||
|
outstate.window_pos = window_pos % 32768 + 1 -- 32K
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function noeof(val)
|
||||||
|
return assert(val, 'unexpected end of file')
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function hasbit(bits, bit)
|
||||||
|
return bits % (bit + bit) >= bit
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function memoize(f)
|
||||||
|
local mt = {}
|
||||||
|
local t = setmetatable({}, mt)
|
||||||
|
function mt:__index(k)
|
||||||
|
local v = f(k)
|
||||||
|
t[k] = v
|
||||||
|
return v
|
||||||
|
end
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- small optimization (lookup table for powers of 2)
|
||||||
|
local pow2 = memoize(function(n) return 2^n end)
|
||||||
|
|
||||||
|
--local tbits = memoize(
|
||||||
|
-- function(bits)
|
||||||
|
-- return memoize( function(bit) return getbit(bits, bit) end )
|
||||||
|
-- end )
|
||||||
|
|
||||||
|
|
||||||
|
-- weak metatable marking objects as bitstream type
|
||||||
|
local is_bitstream = setmetatable({}, {__mode='k'})
|
||||||
|
|
||||||
|
|
||||||
|
-- DEBUG
|
||||||
|
-- prints LSB first
|
||||||
|
--[[
|
||||||
|
local function bits_tostring(bits, nbits)
|
||||||
|
local s = ''
|
||||||
|
local tmp = bits
|
||||||
|
local function f()
|
||||||
|
local b = tmp % 2 == 1 and 1 or 0
|
||||||
|
s = s .. b
|
||||||
|
tmp = (tmp - b) / 2
|
||||||
|
end
|
||||||
|
if nbits then
|
||||||
|
for i=1,nbits do f() end
|
||||||
|
else
|
||||||
|
while tmp ~= 0 do f() end
|
||||||
|
end
|
||||||
|
|
||||||
|
return s
|
||||||
|
end
|
||||||
|
--]]
|
||||||
|
|
||||||
|
local function bytestream_from_file(fh)
|
||||||
|
local o = {}
|
||||||
|
function o:read()
|
||||||
|
local sb = fh:read(1)
|
||||||
|
if sb then return sb:byte() end
|
||||||
|
end
|
||||||
|
return o
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function bytestream_from_string(s)
|
||||||
|
local i = 1
|
||||||
|
local o = {}
|
||||||
|
function o:read()
|
||||||
|
local by
|
||||||
|
if i <= #s then
|
||||||
|
by = s:byte(i)
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
return by
|
||||||
|
end
|
||||||
|
return o
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function bytestream_from_function(f)
|
||||||
|
local i = 0
|
||||||
|
local buffer = ''
|
||||||
|
local o = {}
|
||||||
|
function o:read()
|
||||||
|
return f()
|
||||||
|
-- i = i + 1
|
||||||
|
-- if i > #buffer then
|
||||||
|
-- buffer = f()
|
||||||
|
-- if not buffer then return end
|
||||||
|
-- i = 1
|
||||||
|
-- end
|
||||||
|
-- return buffer:byte(i,i)
|
||||||
|
end
|
||||||
|
return o
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function bitstream_from_bytestream(bys)
|
||||||
|
local buf_byte = 0
|
||||||
|
local buf_nbit = 0
|
||||||
|
local o = {}
|
||||||
|
|
||||||
|
function o:nbits_left_in_byte()
|
||||||
|
return buf_nbit
|
||||||
|
end
|
||||||
|
|
||||||
|
if NATIVE_BITOPS then
|
||||||
|
function o:read(nbits)
|
||||||
|
nbits = nbits or 1
|
||||||
|
while buf_nbit < nbits do
|
||||||
|
local byte = bys:read()
|
||||||
|
if not byte then return end -- note: more calls also return nil
|
||||||
|
buf_byte = buf_byte + lshift(byte, buf_nbit)
|
||||||
|
buf_nbit = buf_nbit + 8
|
||||||
|
end
|
||||||
|
local bits
|
||||||
|
if nbits == 0 then
|
||||||
|
bits = 0
|
||||||
|
elseif nbits == 32 then
|
||||||
|
bits = buf_byte
|
||||||
|
buf_byte = 0
|
||||||
|
else
|
||||||
|
bits = band(buf_byte, rshift(0xffffffff, 32 - nbits))
|
||||||
|
buf_byte = rshift(buf_byte, nbits)
|
||||||
|
end
|
||||||
|
buf_nbit = buf_nbit - nbits
|
||||||
|
return bits
|
||||||
|
end
|
||||||
|
else
|
||||||
|
function o:read(nbits)
|
||||||
|
nbits = nbits or 1
|
||||||
|
while buf_nbit < nbits do
|
||||||
|
local byte = bys:read()
|
||||||
|
if not byte then return end -- note: more calls also return nil
|
||||||
|
buf_byte = buf_byte + pow2[buf_nbit] * byte
|
||||||
|
buf_nbit = buf_nbit + 8
|
||||||
|
end
|
||||||
|
local m = pow2[nbits]
|
||||||
|
local bits = buf_byte % m
|
||||||
|
buf_byte = (buf_byte - bits) / m
|
||||||
|
buf_nbit = buf_nbit - nbits
|
||||||
|
return bits
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
is_bitstream[o] = true
|
||||||
|
|
||||||
|
return o
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function get_bitstream(o)
|
||||||
|
local bs
|
||||||
|
if is_bitstream[o] then
|
||||||
|
return o
|
||||||
|
elseif io.type(o) == 'file' then
|
||||||
|
bs = bitstream_from_bytestream(bytestream_from_file(o))
|
||||||
|
elseif type(o) == 'string' then
|
||||||
|
bs = bitstream_from_bytestream(bytestream_from_string(o))
|
||||||
|
elseif type(o) == 'function' then
|
||||||
|
bs = bitstream_from_bytestream(bytestream_from_function(o))
|
||||||
|
else
|
||||||
|
runtime_error 'unrecognized type'
|
||||||
|
end
|
||||||
|
return bs
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function get_obytestream(o)
|
||||||
|
local bs
|
||||||
|
if io.type(o) == 'file' then
|
||||||
|
bs = function(sbyte) o:write(string_char(sbyte)) end
|
||||||
|
elseif type(o) == 'function' then
|
||||||
|
bs = o
|
||||||
|
else
|
||||||
|
runtime_error('unrecognized type: ' .. tostring(o))
|
||||||
|
end
|
||||||
|
return bs
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function HuffmanTable(init, is_full)
|
||||||
|
local t = {}
|
||||||
|
if is_full then
|
||||||
|
for val,nbits in pairs(init) do
|
||||||
|
if nbits ~= 0 then
|
||||||
|
t[#t+1] = {val=val, nbits=nbits}
|
||||||
|
--debug('*',val,nbits)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
for i=1,#init-2,2 do
|
||||||
|
local firstval, nbits, nextval = init[i], init[i+1], init[i+2]
|
||||||
|
--debug(val, nextval, nbits)
|
||||||
|
if nbits ~= 0 then
|
||||||
|
for val=firstval,nextval-1 do
|
||||||
|
t[#t+1] = {val=val, nbits=nbits}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table_sort(t, function(a,b)
|
||||||
|
return a.nbits == b.nbits and a.val < b.val or a.nbits < b.nbits
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- assign codes
|
||||||
|
local code = 1 -- leading 1 marker
|
||||||
|
local nbits = 0
|
||||||
|
for i,s in ipairs(t) do
|
||||||
|
if s.nbits ~= nbits then
|
||||||
|
code = code * pow2[s.nbits - nbits]
|
||||||
|
nbits = s.nbits
|
||||||
|
end
|
||||||
|
s.code = code
|
||||||
|
--debug('huffman code:', i, s.nbits, s.val, code, bits_tostring(code))
|
||||||
|
code = code + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local minbits = math.huge
|
||||||
|
local look = {}
|
||||||
|
for i,s in ipairs(t) do
|
||||||
|
minbits = math.min(minbits, s.nbits)
|
||||||
|
look[s.code] = s.val
|
||||||
|
end
|
||||||
|
|
||||||
|
--for _,o in ipairs(t) do
|
||||||
|
-- debug(':', o.nbits, o.val)
|
||||||
|
--end
|
||||||
|
|
||||||
|
-- function t:lookup(bits) return look[bits] end
|
||||||
|
|
||||||
|
local msb = NATIVE_BITOPS and function(bits, nbits)
|
||||||
|
local res = 0
|
||||||
|
for i=1,nbits do
|
||||||
|
res = lshift(res, 1) + band(bits, 1)
|
||||||
|
bits = rshift(bits, 1)
|
||||||
|
end
|
||||||
|
return res
|
||||||
|
end or function(bits, nbits)
|
||||||
|
local res = 0
|
||||||
|
for i=1,nbits do
|
||||||
|
local b = bits % 2
|
||||||
|
bits = (bits - b) / 2
|
||||||
|
res = res * 2 + b
|
||||||
|
end
|
||||||
|
return res
|
||||||
|
end
|
||||||
|
|
||||||
|
local tfirstcode = memoize(
|
||||||
|
function(bits) return pow2[minbits] + msb(bits, minbits) end)
|
||||||
|
|
||||||
|
function t:read(bs)
|
||||||
|
local code = 1 -- leading 1 marker
|
||||||
|
local nbits = 0
|
||||||
|
while 1 do
|
||||||
|
if nbits == 0 then -- small optimization (optional)
|
||||||
|
code = tfirstcode[noeof(bs:read(minbits))]
|
||||||
|
nbits = nbits + minbits
|
||||||
|
else
|
||||||
|
local b = noeof(bs:read())
|
||||||
|
nbits = nbits + 1
|
||||||
|
code = code * 2 + b -- MSB first
|
||||||
|
--[[NATIVE_BITOPS
|
||||||
|
code = lshift(code, 1) + b -- MSB first
|
||||||
|
--]]
|
||||||
|
end
|
||||||
|
--debug('code?', code, bits_tostring(code))
|
||||||
|
local val = look[code]
|
||||||
|
if val then
|
||||||
|
--debug('FOUND', val)
|
||||||
|
return val
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function parse_gzip_header(bs)
|
||||||
|
-- local FLG_FTEXT = 2^0
|
||||||
|
local FLG_FHCRC = 2^1
|
||||||
|
local FLG_FEXTRA = 2^2
|
||||||
|
local FLG_FNAME = 2^3
|
||||||
|
local FLG_FCOMMENT = 2^4
|
||||||
|
|
||||||
|
local id1 = bs:read(8)
|
||||||
|
local id2 = bs:read(8)
|
||||||
|
if id1 ~= 31 or id2 ~= 139 then
|
||||||
|
runtime_error 'not in gzip format'
|
||||||
|
end
|
||||||
|
local cm = bs:read(8) -- compression method
|
||||||
|
local flg = bs:read(8) -- FLaGs
|
||||||
|
local mtime = bs:read(32) -- Modification TIME
|
||||||
|
local xfl = bs:read(8) -- eXtra FLags
|
||||||
|
local os = bs:read(8) -- Operating System
|
||||||
|
|
||||||
|
if DEBUG then
|
||||||
|
debug("CM=", cm)
|
||||||
|
debug("FLG=", flg)
|
||||||
|
debug("MTIME=", mtime)
|
||||||
|
-- debug("MTIME_str=",os.date("%Y-%m-%d %H:%M:%S",mtime)) -- non-portable
|
||||||
|
debug("XFL=", xfl)
|
||||||
|
debug("OS=", os)
|
||||||
|
end
|
||||||
|
|
||||||
|
if not os then runtime_error 'invalid header' end
|
||||||
|
|
||||||
|
if hasbit(flg, FLG_FEXTRA) then
|
||||||
|
local xlen = bs:read(16)
|
||||||
|
local extra = 0
|
||||||
|
for i=1,xlen do
|
||||||
|
extra = bs:read(8)
|
||||||
|
end
|
||||||
|
if not extra then runtime_error 'invalid header' end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function parse_zstring(bs)
|
||||||
|
repeat
|
||||||
|
local by = bs:read(8)
|
||||||
|
if not by then runtime_error 'invalid header' end
|
||||||
|
until by == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
if hasbit(flg, FLG_FNAME) then
|
||||||
|
parse_zstring(bs)
|
||||||
|
end
|
||||||
|
|
||||||
|
if hasbit(flg, FLG_FCOMMENT) then
|
||||||
|
parse_zstring(bs)
|
||||||
|
end
|
||||||
|
|
||||||
|
if hasbit(flg, FLG_FHCRC) then
|
||||||
|
local crc16 = bs:read(16)
|
||||||
|
if not crc16 then runtime_error 'invalid header' end
|
||||||
|
-- IMPROVE: check CRC. where is an example .gz file that
|
||||||
|
-- has this set?
|
||||||
|
if DEBUG then
|
||||||
|
debug("CRC16=", crc16)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function parse_zlib_header(bs)
|
||||||
|
local cm = bs:read(4) -- Compression Method
|
||||||
|
local cinfo = bs:read(4) -- Compression info
|
||||||
|
local fcheck = bs:read(5) -- FLaGs: FCHECK (check bits for CMF and FLG)
|
||||||
|
local fdict = bs:read(1) -- FLaGs: FDICT (present dictionary)
|
||||||
|
local flevel = bs:read(2) -- FLaGs: FLEVEL (compression level)
|
||||||
|
local cmf = cinfo * 16 + cm -- CMF (Compresion Method and flags)
|
||||||
|
local flg = fcheck + fdict * 32 + flevel * 64 -- FLaGs
|
||||||
|
|
||||||
|
if cm ~= 8 then -- not "deflate"
|
||||||
|
runtime_error("unrecognized zlib compression method: " + cm)
|
||||||
|
end
|
||||||
|
if cinfo > 7 then
|
||||||
|
runtime_error("invalid zlib window size: cinfo=" + cinfo)
|
||||||
|
end
|
||||||
|
local window_size = 2^(cinfo + 8)
|
||||||
|
|
||||||
|
if (cmf*256 + flg) % 31 ~= 0 then
|
||||||
|
runtime_error("invalid zlib header (bad fcheck sum)")
|
||||||
|
end
|
||||||
|
|
||||||
|
if fdict == 1 then
|
||||||
|
runtime_error("FIX:TODO - FDICT not currently implemented")
|
||||||
|
local dictid_ = bs:read(32)
|
||||||
|
end
|
||||||
|
|
||||||
|
return window_size
|
||||||
|
end
|
||||||
|
|
||||||
|
local function parse_huffmantables(bs)
|
||||||
|
local hlit = bs:read(5) -- # of literal/length codes - 257
|
||||||
|
local hdist = bs:read(5) -- # of distance codes - 1
|
||||||
|
local hclen = noeof(bs:read(4)) -- # of code length codes - 4
|
||||||
|
|
||||||
|
local ncodelen_codes = hclen + 4
|
||||||
|
local codelen_init = {}
|
||||||
|
local codelen_vals = {
|
||||||
|
16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}
|
||||||
|
for i=1,ncodelen_codes do
|
||||||
|
local nbits = bs:read(3)
|
||||||
|
local val = codelen_vals[i]
|
||||||
|
codelen_init[val] = nbits
|
||||||
|
end
|
||||||
|
local codelentable = HuffmanTable(codelen_init, true)
|
||||||
|
|
||||||
|
local function decode(ncodes)
|
||||||
|
local init = {}
|
||||||
|
local nbits
|
||||||
|
local val = 0
|
||||||
|
while val < ncodes do
|
||||||
|
local codelen = codelentable:read(bs)
|
||||||
|
--FIX:check nil?
|
||||||
|
local nrepeat
|
||||||
|
if codelen <= 15 then
|
||||||
|
nrepeat = 1
|
||||||
|
nbits = codelen
|
||||||
|
--debug('w', nbits)
|
||||||
|
elseif codelen == 16 then
|
||||||
|
nrepeat = 3 + noeof(bs:read(2))
|
||||||
|
-- nbits unchanged
|
||||||
|
elseif codelen == 17 then
|
||||||
|
nrepeat = 3 + noeof(bs:read(3))
|
||||||
|
nbits = 0
|
||||||
|
elseif codelen == 18 then
|
||||||
|
nrepeat = 11 + noeof(bs:read(7))
|
||||||
|
nbits = 0
|
||||||
|
else
|
||||||
|
error 'ASSERT'
|
||||||
|
end
|
||||||
|
for i=1,nrepeat do
|
||||||
|
init[val] = nbits
|
||||||
|
val = val + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local huffmantable = HuffmanTable(init, true)
|
||||||
|
return huffmantable
|
||||||
|
end
|
||||||
|
|
||||||
|
local nlit_codes = hlit + 257
|
||||||
|
local ndist_codes = hdist + 1
|
||||||
|
|
||||||
|
local littable = decode(nlit_codes)
|
||||||
|
local disttable = decode(ndist_codes)
|
||||||
|
|
||||||
|
return littable, disttable
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local tdecode_len_base
|
||||||
|
local tdecode_len_nextrabits
|
||||||
|
local tdecode_dist_base
|
||||||
|
local tdecode_dist_nextrabits
|
||||||
|
local function parse_compressed_item(bs, outstate, littable, disttable)
|
||||||
|
local val = littable:read(bs)
|
||||||
|
--debug(val, val < 256 and string_char(val))
|
||||||
|
if val < 256 then -- literal
|
||||||
|
output(outstate, val)
|
||||||
|
elseif val == 256 then -- end of block
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
if not tdecode_len_base then
|
||||||
|
local t = {[257]=3}
|
||||||
|
local skip = 1
|
||||||
|
for i=258,285,4 do
|
||||||
|
for j=i,i+3 do t[j] = t[j-1] + skip end
|
||||||
|
if i ~= 258 then skip = skip * 2 end
|
||||||
|
end
|
||||||
|
t[285] = 258
|
||||||
|
tdecode_len_base = t
|
||||||
|
--for i=257,285 do debug('T1',i,t[i]) end
|
||||||
|
end
|
||||||
|
if not tdecode_len_nextrabits then
|
||||||
|
local t = {}
|
||||||
|
if NATIVE_BITOPS then
|
||||||
|
for i=257,285 do
|
||||||
|
local j = math_max(i - 261, 0)
|
||||||
|
t[i] = rshift(j, 2)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
for i=257,285 do
|
||||||
|
local j = math_max(i - 261, 0)
|
||||||
|
t[i] = (j - (j % 4)) / 4
|
||||||
|
end
|
||||||
|
end
|
||||||
|
t[285] = 0
|
||||||
|
tdecode_len_nextrabits = t
|
||||||
|
--for i=257,285 do debug('T2',i,t[i]) end
|
||||||
|
end
|
||||||
|
local len_base = tdecode_len_base[val]
|
||||||
|
local nextrabits = tdecode_len_nextrabits[val]
|
||||||
|
local extrabits = bs:read(nextrabits)
|
||||||
|
local len = len_base + extrabits
|
||||||
|
|
||||||
|
if not tdecode_dist_base then
|
||||||
|
local t = {[0]=1}
|
||||||
|
local skip = 1
|
||||||
|
for i=1,29,2 do
|
||||||
|
for j=i,i+1 do t[j] = t[j-1] + skip end
|
||||||
|
if i ~= 1 then skip = skip * 2 end
|
||||||
|
end
|
||||||
|
tdecode_dist_base = t
|
||||||
|
--for i=0,29 do debug('T3',i,t[i]) end
|
||||||
|
end
|
||||||
|
if not tdecode_dist_nextrabits then
|
||||||
|
local t = {}
|
||||||
|
if NATIVE_BITOPS then
|
||||||
|
for i=0,29 do
|
||||||
|
local j = math_max(i - 2, 0)
|
||||||
|
t[i] = rshift(j, 1)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
for i=0,29 do
|
||||||
|
local j = math_max(i - 2, 0)
|
||||||
|
t[i] = (j - (j % 2)) / 2
|
||||||
|
end
|
||||||
|
end
|
||||||
|
tdecode_dist_nextrabits = t
|
||||||
|
--for i=0,29 do debug('T4',i,t[i]) end
|
||||||
|
end
|
||||||
|
local dist_val = disttable:read(bs)
|
||||||
|
local dist_base = tdecode_dist_base[dist_val]
|
||||||
|
local dist_nextrabits = tdecode_dist_nextrabits[dist_val]
|
||||||
|
local dist_extrabits = bs:read(dist_nextrabits)
|
||||||
|
local dist = dist_base + dist_extrabits
|
||||||
|
|
||||||
|
--debug('BACK', len, dist)
|
||||||
|
for i=1,len do
|
||||||
|
local pos = (outstate.window_pos - 1 - dist) % 32768 + 1 -- 32K
|
||||||
|
output(outstate, assert(outstate.window[pos], 'invalid distance'))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function parse_block(bs, outstate)
|
||||||
|
local bfinal = bs:read(1)
|
||||||
|
local btype = bs:read(2)
|
||||||
|
|
||||||
|
local BTYPE_NO_COMPRESSION = 0
|
||||||
|
local BTYPE_FIXED_HUFFMAN = 1
|
||||||
|
local BTYPE_DYNAMIC_HUFFMAN = 2
|
||||||
|
local BTYPE_RESERVED_ = 3
|
||||||
|
|
||||||
|
if DEBUG then
|
||||||
|
debug('bfinal=', bfinal)
|
||||||
|
debug('btype=', btype)
|
||||||
|
end
|
||||||
|
|
||||||
|
if btype == BTYPE_NO_COMPRESSION then
|
||||||
|
bs:read(bs:nbits_left_in_byte())
|
||||||
|
local len = bs:read(16)
|
||||||
|
local nlen_ = noeof(bs:read(16))
|
||||||
|
|
||||||
|
for i=1,len do
|
||||||
|
local by = noeof(bs:read(8))
|
||||||
|
output(outstate, by)
|
||||||
|
end
|
||||||
|
elseif btype == BTYPE_FIXED_HUFFMAN or btype == BTYPE_DYNAMIC_HUFFMAN then
|
||||||
|
local littable, disttable
|
||||||
|
if btype == BTYPE_DYNAMIC_HUFFMAN then
|
||||||
|
littable, disttable = parse_huffmantables(bs)
|
||||||
|
else
|
||||||
|
littable = HuffmanTable {0,8, 144,9, 256,7, 280,8, 288,nil}
|
||||||
|
disttable = HuffmanTable {0,5, 32,nil}
|
||||||
|
end
|
||||||
|
|
||||||
|
repeat
|
||||||
|
local is_done = parse_compressed_item(
|
||||||
|
bs, outstate, littable, disttable)
|
||||||
|
until is_done
|
||||||
|
else
|
||||||
|
runtime_error 'unrecognized compression type'
|
||||||
|
end
|
||||||
|
|
||||||
|
return bfinal ~= 0
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function M.inflate(t)
|
||||||
|
local bs = get_bitstream(t.input)
|
||||||
|
local outbs = get_obytestream(t.output)
|
||||||
|
local outstate = make_outstate(outbs)
|
||||||
|
|
||||||
|
repeat
|
||||||
|
local is_final = parse_block(bs, outstate)
|
||||||
|
until is_final
|
||||||
|
end
|
||||||
|
local inflate = M.inflate
|
||||||
|
|
||||||
|
|
||||||
|
function M.gunzip(t)
|
||||||
|
local bs = get_bitstream(t.input)
|
||||||
|
local outbs = get_obytestream(t.output)
|
||||||
|
local disable_crc = t.disable_crc
|
||||||
|
if disable_crc == nil then disable_crc = false end
|
||||||
|
|
||||||
|
parse_gzip_header(bs)
|
||||||
|
|
||||||
|
local data_crc32 = 0
|
||||||
|
|
||||||
|
inflate{input=bs, output=
|
||||||
|
disable_crc and outbs or
|
||||||
|
function(byte)
|
||||||
|
data_crc32 = crc32(byte, data_crc32)
|
||||||
|
outbs(byte)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
bs:read(bs:nbits_left_in_byte())
|
||||||
|
|
||||||
|
local expected_crc32 = bs:read(32)
|
||||||
|
local isize = bs:read(32) -- ignored
|
||||||
|
if DEBUG then
|
||||||
|
debug('crc32=', expected_crc32)
|
||||||
|
debug('isize=', isize)
|
||||||
|
end
|
||||||
|
if not disable_crc and data_crc32 then
|
||||||
|
if data_crc32 ~= expected_crc32 then
|
||||||
|
runtime_error('invalid compressed data--crc error')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if bs:read() then
|
||||||
|
warn 'trailing garbage ignored'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function M.adler32(byte, crc)
|
||||||
|
local s1 = crc % 65536
|
||||||
|
local s2 = (crc - s1) / 65536
|
||||||
|
s1 = (s1 + byte) % 65521
|
||||||
|
s2 = (s2 + s1) % 65521
|
||||||
|
return s2*65536 + s1
|
||||||
|
end -- 65521 is the largest prime smaller than 2^16
|
||||||
|
|
||||||
|
|
||||||
|
function M.inflate_zlib(t)
|
||||||
|
local bs = get_bitstream(t.input)
|
||||||
|
local outbs = get_obytestream(t.output)
|
||||||
|
local disable_crc = t.disable_crc
|
||||||
|
if disable_crc == nil then disable_crc = false end
|
||||||
|
|
||||||
|
local window_size_ = parse_zlib_header(bs)
|
||||||
|
|
||||||
|
local data_adler32 = 1
|
||||||
|
|
||||||
|
inflate{input=bs, output=
|
||||||
|
disable_crc and outbs or
|
||||||
|
function(byte)
|
||||||
|
data_adler32 = M.adler32(byte, data_adler32)
|
||||||
|
outbs(byte)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
bs:read(bs:nbits_left_in_byte())
|
||||||
|
|
||||||
|
local b3 = bs:read(8)
|
||||||
|
local b2 = bs:read(8)
|
||||||
|
local b1 = bs:read(8)
|
||||||
|
local b0 = bs:read(8)
|
||||||
|
local expected_adler32 = ((b3*256 + b2)*256 + b1)*256 + b0
|
||||||
|
if DEBUG then
|
||||||
|
debug('alder32=', expected_adler32)
|
||||||
|
end
|
||||||
|
if not disable_crc then
|
||||||
|
if data_adler32 ~= expected_adler32 then
|
||||||
|
runtime_error('invalid compressed data--crc error')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if bs:read() then
|
||||||
|
warn 'trailing garbage ignored'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
return M
|
||||||
39
apis/itemDB.lua
Normal file
39
apis/itemDB.lua
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
local Util = require('util')
|
||||||
|
local TableDB = require('tableDB')
|
||||||
|
|
||||||
|
local itemDB = TableDB({ fileName = 'usr/etc/items.db' })
|
||||||
|
|
||||||
|
function itemDB:get(key)
|
||||||
|
|
||||||
|
local item = TableDB.get(self, key)
|
||||||
|
|
||||||
|
if item then
|
||||||
|
return item
|
||||||
|
end
|
||||||
|
|
||||||
|
if key[2] ~= 0 then
|
||||||
|
item = TableDB.get(self, { key[1], 0, key[3] })
|
||||||
|
if item and item.maxDamage > 0 then
|
||||||
|
item = Util.shallowCopy(item)
|
||||||
|
item.damage = key[2]
|
||||||
|
item.displayName = string.format('%s (damage: %d)', item.displayName, item.damage)
|
||||||
|
return item
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function itemDB:add(key, item)
|
||||||
|
|
||||||
|
if item.maxDamage > 0 then
|
||||||
|
key = { key[1], 0, key[3] }
|
||||||
|
end
|
||||||
|
TableDB.add(self, key, item)
|
||||||
|
end
|
||||||
|
|
||||||
|
function itemDB:makeKey(item)
|
||||||
|
return { item.name, item.damage, item.nbtHash }
|
||||||
|
end
|
||||||
|
|
||||||
|
itemDB:load()
|
||||||
|
|
||||||
|
return itemDB
|
||||||
167
apis/me.lua
Normal file
167
apis/me.lua
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
local Util = require('util')
|
||||||
|
|
||||||
|
local ME = {
|
||||||
|
jobList = { }
|
||||||
|
}
|
||||||
|
|
||||||
|
function ME.setDevice(device)
|
||||||
|
ME.p = device
|
||||||
|
--Util.merge(ME, ME.p)
|
||||||
|
|
||||||
|
if not device then
|
||||||
|
error('ME device not attached')
|
||||||
|
end
|
||||||
|
|
||||||
|
for k,v in pairs(ME.p) do
|
||||||
|
if not ME[k] then
|
||||||
|
ME[k] = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function ME.isAvailable()
|
||||||
|
return not Util.empty(ME.getAvailableItems())
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Strip off color prefix
|
||||||
|
local function safeString(text)
|
||||||
|
|
||||||
|
local val = text:byte(1)
|
||||||
|
|
||||||
|
if val < 32 or val > 128 then
|
||||||
|
|
||||||
|
local newText = {}
|
||||||
|
for i = 4, #text do
|
||||||
|
local val = text:byte(i)
|
||||||
|
newText[i - 3] = (val > 31 and val < 127) and val or 63
|
||||||
|
end
|
||||||
|
return string.char(unpack(newText))
|
||||||
|
end
|
||||||
|
|
||||||
|
return text
|
||||||
|
end
|
||||||
|
|
||||||
|
function ME.getAvailableItems()
|
||||||
|
local items
|
||||||
|
pcall(function()
|
||||||
|
items = ME.p.getAvailableItems('all')
|
||||||
|
for k,v in pairs(items) do
|
||||||
|
v.id = v.item.id
|
||||||
|
v.name = safeString(v.item.display_name)
|
||||||
|
v.qty = v.item.qty
|
||||||
|
v.dmg = v.item.dmg
|
||||||
|
v.max_dmg = v.item.max_dmg
|
||||||
|
v.nbt_hash = v.item.nbt_hash
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
return items or { }
|
||||||
|
end
|
||||||
|
|
||||||
|
function ME.getItemCount(id, dmg, nbt_hash, ignore_dmg)
|
||||||
|
|
||||||
|
local fingerprint = {
|
||||||
|
id = id,
|
||||||
|
nbt_hash = nbt_hash,
|
||||||
|
}
|
||||||
|
|
||||||
|
if not ignore_dmg or ignore_dmg ~= 'yes' then
|
||||||
|
fingerprint.dmg = dmg or 0
|
||||||
|
end
|
||||||
|
|
||||||
|
local item = ME.getItemDetail(fingerprint, false)
|
||||||
|
|
||||||
|
if item then
|
||||||
|
return item.qty
|
||||||
|
end
|
||||||
|
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
function ME.extract(id, dmg, nbt_hash, qty, direction, slot)
|
||||||
|
dmg = dmg or 0
|
||||||
|
qty = qty or 1
|
||||||
|
direction = direction or 'up'
|
||||||
|
return pcall(function()
|
||||||
|
local fingerprint = {
|
||||||
|
dmg = dmg,
|
||||||
|
id = id,
|
||||||
|
nbt_hash = nbt_hash
|
||||||
|
}
|
||||||
|
return ME.exportItem(fingerprint, direction, qty, slot)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function ME.insert(slot, qty, direction)
|
||||||
|
direction = direction or 'up'
|
||||||
|
return ME.pullItem(direction, slot, qty)
|
||||||
|
end
|
||||||
|
|
||||||
|
function ME.isCrafting()
|
||||||
|
local cpus = ME.p.getCraftingCPUs() or { }
|
||||||
|
for k,v in pairs(cpus) do
|
||||||
|
if v.busy then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function ME.isCPUAvailable()
|
||||||
|
local cpus = ME.p.getCraftingCPUs() or { }
|
||||||
|
local available = false
|
||||||
|
|
||||||
|
for cpu,v in pairs(cpus) do
|
||||||
|
if not v.busy then
|
||||||
|
available = true
|
||||||
|
elseif not ME.jobList[cpu] then -- something else is crafting something (don't know what)
|
||||||
|
return false -- return false since we are in an unknown state
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return available
|
||||||
|
end
|
||||||
|
|
||||||
|
function ME.getJobList()
|
||||||
|
|
||||||
|
local cpus = ME.p.getCraftingCPUs() or { }
|
||||||
|
for cpu,v in pairs(cpus) do
|
||||||
|
if not v.busy then
|
||||||
|
ME.jobList[cpu] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return ME.jobList
|
||||||
|
end
|
||||||
|
|
||||||
|
function ME.craft(id, dmg, nbt_hash, qty)
|
||||||
|
local cpus = ME.p.getCraftingCPUs() or { }
|
||||||
|
for cpu,v in pairs(cpus) do
|
||||||
|
if not v.busy then
|
||||||
|
ME.p.requestCrafting({
|
||||||
|
id = id,
|
||||||
|
dmg = dmg or 0,
|
||||||
|
nbt_hash = nbt_hash,
|
||||||
|
},
|
||||||
|
qty or 1,
|
||||||
|
cpu
|
||||||
|
)
|
||||||
|
|
||||||
|
os.sleep(0) -- tell it to craft, yet it doesn't show busy - try waiting a cycle...
|
||||||
|
cpus = ME.p.getCraftingCPUs() or { }
|
||||||
|
if not cpus[cpu].busy then
|
||||||
|
-- print('sleeping again')
|
||||||
|
os.sleep(.1) -- sigh
|
||||||
|
cpus = ME.p.getCraftingCPUs() or { }
|
||||||
|
end
|
||||||
|
|
||||||
|
-- not working :(
|
||||||
|
if cpus[cpu].busy then
|
||||||
|
ME.jobList[cpu] = { id = id, dmg = dmg, qty = qty, nbt_hash = nbt_hash }
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
break -- only need to try the first available cpu
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
return ME
|
||||||
157
apis/meAdapter.lua
Normal file
157
apis/meAdapter.lua
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
local class = require('class')
|
||||||
|
local Util = require('util')
|
||||||
|
local Peripheral = require('peripheral')
|
||||||
|
|
||||||
|
local MEProvider = class()
|
||||||
|
|
||||||
|
function MEProvider:init(args)
|
||||||
|
local defaults = {
|
||||||
|
items = { },
|
||||||
|
name = 'ME',
|
||||||
|
}
|
||||||
|
Util.merge(self, defaults)
|
||||||
|
Util.merge(self, args)
|
||||||
|
|
||||||
|
if self.side then
|
||||||
|
local mep = peripheral.wrap('bottom')
|
||||||
|
if mep then
|
||||||
|
Util.merge(self, mep)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local mep = Peripheral.getByMethod('getAvailableItems')
|
||||||
|
if mep then
|
||||||
|
Util.merge(self, mep)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local sides = {
|
||||||
|
top = 'down',
|
||||||
|
bottom = 'up',
|
||||||
|
east = 'west',
|
||||||
|
west = 'east',
|
||||||
|
north = 'south',
|
||||||
|
south = 'north',
|
||||||
|
}
|
||||||
|
self.oside = sides[self.direction or self.side]
|
||||||
|
end
|
||||||
|
|
||||||
|
function MEProvider:isValid()
|
||||||
|
return self.getAvailableItems and self.getAvailableItems()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Strip off color prefix
|
||||||
|
local function safeString(text)
|
||||||
|
|
||||||
|
local val = text:byte(1)
|
||||||
|
|
||||||
|
if val < 32 or val > 128 then
|
||||||
|
|
||||||
|
local newText = {}
|
||||||
|
for i = 4, #text do
|
||||||
|
local val = text:byte(i)
|
||||||
|
newText[i - 3] = (val > 31 and val < 127) and val or 63
|
||||||
|
end
|
||||||
|
return string.char(unpack(newText))
|
||||||
|
end
|
||||||
|
|
||||||
|
return text
|
||||||
|
end
|
||||||
|
|
||||||
|
local convertNames = {
|
||||||
|
name = 'id',
|
||||||
|
damage = 'dmg',
|
||||||
|
maxCount = 'max_size',
|
||||||
|
count = 'qty',
|
||||||
|
displayName = 'display_name',
|
||||||
|
maxDamage = 'max_dmg',
|
||||||
|
}
|
||||||
|
|
||||||
|
local function convertItem(item)
|
||||||
|
for k,v in pairs(convertNames) do
|
||||||
|
item[k] = item[v]
|
||||||
|
item[v] = nil
|
||||||
|
end
|
||||||
|
item.displayName = safeString(item.displayName)
|
||||||
|
end
|
||||||
|
|
||||||
|
function MEProvider:refresh()
|
||||||
|
self.items = self.getAvailableItems('all')
|
||||||
|
for _,v in pairs(self.items) do
|
||||||
|
Util.merge(v, v.item)
|
||||||
|
convertItem(v)
|
||||||
|
end
|
||||||
|
return self.items
|
||||||
|
end
|
||||||
|
|
||||||
|
function MEProvider:listItems()
|
||||||
|
self:refresh()
|
||||||
|
return self.items
|
||||||
|
end
|
||||||
|
|
||||||
|
function MEProvider:getItemInfo(name, damage)
|
||||||
|
|
||||||
|
for key,item in pairs(self.items) do
|
||||||
|
if item.name == name and item.damage == damage then
|
||||||
|
return item
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function MEProvider:craft(name, damage, count)
|
||||||
|
|
||||||
|
self:refresh()
|
||||||
|
|
||||||
|
local item = self:getItemInfo(name, damage)
|
||||||
|
|
||||||
|
if item and item.is_craftable then
|
||||||
|
|
||||||
|
self.requestCrafting({ id = name, dmg = damage }, count)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function MEProvider:craftItems(items)
|
||||||
|
local cpus = self.getCraftingCPUs() or { }
|
||||||
|
local count = 0
|
||||||
|
|
||||||
|
for _,cpu in pairs(cpus) do
|
||||||
|
if cpu.busy then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for _,item in pairs(items) do
|
||||||
|
if count >= #cpus then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
if self:craft(item.name, item.damage, item.count) then
|
||||||
|
count = count + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function MEProvider:provide(item, count, slot)
|
||||||
|
return pcall(function()
|
||||||
|
self.exportItem({
|
||||||
|
id = item.name,
|
||||||
|
dmg = item.damage
|
||||||
|
}, self.oside, count, slot)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function MEProvider:insert(slot, count)
|
||||||
|
local s, m = pcall(function() self.pullItem(self.oside, slot, count) end)
|
||||||
|
if not s and m then
|
||||||
|
print('MEProvider:pullItem')
|
||||||
|
print(m)
|
||||||
|
sleep(1)
|
||||||
|
s, m = pcall(function() self.pullItem(self.oside, slot, count) end)
|
||||||
|
if not s and m then
|
||||||
|
print('MEProvider:pullItem')
|
||||||
|
print(m)
|
||||||
|
read()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return MEProvider
|
||||||
106
apis/message.lua
Normal file
106
apis/message.lua
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
local Event = require('event')
|
||||||
|
local Logger = require('logger')
|
||||||
|
|
||||||
|
local Message = { }
|
||||||
|
|
||||||
|
local messageHandlers = {}
|
||||||
|
|
||||||
|
function Message.enable()
|
||||||
|
if not device.wireless_modem.isOpen(os.getComputerID()) then
|
||||||
|
device.wireless_modem.open(os.getComputerID())
|
||||||
|
end
|
||||||
|
if not device.wireless_modem.isOpen(60000) then
|
||||||
|
device.wireless_modem.open(60000)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if device and device.wireless_modem then
|
||||||
|
Message.enable()
|
||||||
|
end
|
||||||
|
|
||||||
|
Event.on('device_attach', function(event, deviceName)
|
||||||
|
if deviceName == 'wireless_modem' then
|
||||||
|
Message.enable()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
function Message.addHandler(type, f)
|
||||||
|
table.insert(messageHandlers, {
|
||||||
|
type = type,
|
||||||
|
f = f,
|
||||||
|
enabled = true
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
function Message.removeHandler(h)
|
||||||
|
for k,v in pairs(messageHandlers) do
|
||||||
|
if v == h then
|
||||||
|
messageHandlers[k] = nil
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Event.on('modem_message',
|
||||||
|
function(event, side, sendChannel, replyChannel, msg, distance)
|
||||||
|
if msg and msg.type then -- filter out messages from other systems
|
||||||
|
local id = replyChannel
|
||||||
|
Logger.log('modem_receive', { id, msg.type })
|
||||||
|
--Logger.log('modem_receive', msg.contents)
|
||||||
|
for k,h in pairs(messageHandlers) do
|
||||||
|
if h.type == msg.type then
|
||||||
|
-- should provide msg.contents instead of message - type is already known
|
||||||
|
h.f(h, id, msg, distance)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
function Message.send(id, msgType, contents)
|
||||||
|
if not device.wireless_modem then
|
||||||
|
error('No modem attached', 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
if id then
|
||||||
|
Logger.log('modem_send', { tostring(id), msgType })
|
||||||
|
device.wireless_modem.transmit(id, os.getComputerID(), {
|
||||||
|
type = msgType, contents = contents
|
||||||
|
})
|
||||||
|
else
|
||||||
|
Logger.log('modem_send', { 'broadcast', msgType })
|
||||||
|
device.wireless_modem.transmit(60000, os.getComputerID(), {
|
||||||
|
type = msgType, contents = contents
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Message.broadcast(t, contents)
|
||||||
|
if not device.wireless_modem then
|
||||||
|
error('No modem attached', 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
Message.send(nil, t, contents)
|
||||||
|
-- Logger.log('rednet_send', { 'broadcast', t })
|
||||||
|
-- rednet.broadcast({ type = t, contents = contents })
|
||||||
|
end
|
||||||
|
|
||||||
|
function Message.waitForMessage(msgType, timeout, fromId)
|
||||||
|
local timerId = os.startTimer(timeout)
|
||||||
|
repeat
|
||||||
|
local e, side, _id, id, msg, distance = os.pullEvent()
|
||||||
|
if e == 'modem_message' then
|
||||||
|
if msg and msg.type and msg.type == msgType then
|
||||||
|
if not fromId or id == fromId then
|
||||||
|
return e, id, msg, distance
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
until e == 'timer' and side == timerId
|
||||||
|
end
|
||||||
|
|
||||||
|
function Message.enableWirelessLogging()
|
||||||
|
Logger.setWirelessLogging()
|
||||||
|
end
|
||||||
|
|
||||||
|
return Message
|
||||||
143
apis/refinedAdapter.lua
Normal file
143
apis/refinedAdapter.lua
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
local class = require('class')
|
||||||
|
local Util = require('util')
|
||||||
|
local Peripheral = require('peripheral')
|
||||||
|
local itemDB = require('itemDB')
|
||||||
|
|
||||||
|
local RefinedAdapter = class()
|
||||||
|
|
||||||
|
local keys = {
|
||||||
|
'damage',
|
||||||
|
'displayName',
|
||||||
|
'maxCount',
|
||||||
|
'maxDamage',
|
||||||
|
'name',
|
||||||
|
'nbtHash',
|
||||||
|
}
|
||||||
|
|
||||||
|
function RefinedAdapter:init(args)
|
||||||
|
local defaults = {
|
||||||
|
items = { },
|
||||||
|
name = 'refinedStorage',
|
||||||
|
}
|
||||||
|
Util.merge(self, defaults)
|
||||||
|
Util.merge(self, args)
|
||||||
|
|
||||||
|
local controller = Peripheral.getByType('refinedstorage:controller')
|
||||||
|
if controller then
|
||||||
|
Util.merge(self, controller)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function RefinedAdapter:isValid()
|
||||||
|
return not not self.listAvailableItems
|
||||||
|
end
|
||||||
|
|
||||||
|
function RefinedAdapter:isOnline()
|
||||||
|
return self.getNetworkEnergyStored() > 0
|
||||||
|
end
|
||||||
|
|
||||||
|
function RefinedAdapter:getCachedItemDetails(item)
|
||||||
|
local key = { item.name, item.damage, item.nbtHash }
|
||||||
|
|
||||||
|
local detail = itemDB:get(key)
|
||||||
|
if not detail then
|
||||||
|
detail = self.findItem(item)
|
||||||
|
if detail then
|
||||||
|
local meta
|
||||||
|
pcall(function() meta = detail.getMetadata() end)
|
||||||
|
if not meta then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
Util.merge(detail, meta)
|
||||||
|
|
||||||
|
local t = { }
|
||||||
|
for _,k in pairs(keys) do
|
||||||
|
t[k] = detail[k]
|
||||||
|
end
|
||||||
|
|
||||||
|
detail = t
|
||||||
|
itemDB:add(key, detail)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if detail then
|
||||||
|
return Util.shallowCopy(detail)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function RefinedAdapter:listItems()
|
||||||
|
local items = { }
|
||||||
|
local list
|
||||||
|
|
||||||
|
pcall(function()
|
||||||
|
list = self.listAvailableItems()
|
||||||
|
end)
|
||||||
|
|
||||||
|
if list then
|
||||||
|
|
||||||
|
local throttle = Util.throttle()
|
||||||
|
|
||||||
|
for _,v in pairs(list) do
|
||||||
|
local item = self:getCachedItemDetails(v)
|
||||||
|
if item then
|
||||||
|
item.count = v.count
|
||||||
|
table.insert(items, item)
|
||||||
|
end
|
||||||
|
throttle()
|
||||||
|
end
|
||||||
|
itemDB:flush()
|
||||||
|
end
|
||||||
|
|
||||||
|
return items
|
||||||
|
end
|
||||||
|
|
||||||
|
function RefinedAdapter:getItemInfo(fingerprint)
|
||||||
|
|
||||||
|
local key = { fingerprint.name, fingerprint.damage, fingerprint.nbtHash }
|
||||||
|
|
||||||
|
local item = itemDB:get(key)
|
||||||
|
if not item then
|
||||||
|
return self:getCachedItemDetails(fingerprint)
|
||||||
|
end
|
||||||
|
|
||||||
|
local detail = self.findItem(item)
|
||||||
|
if detail then
|
||||||
|
item.count = detail.count
|
||||||
|
return item
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function RefinedAdapter:isCrafting(item)
|
||||||
|
for _,task in pairs(self.getCraftingTasks()) do
|
||||||
|
local output = task.getPattern().outputs[1]
|
||||||
|
if output.name == item.name and
|
||||||
|
output.damage == item.damage and
|
||||||
|
output.nbtHash == item.nbtHash then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function RefinedAdapter:craft(item, qty)
|
||||||
|
local detail = self.findItem(item)
|
||||||
|
if detail then
|
||||||
|
return detail.craft(qty)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function RefinedAdapter:craftItems(items)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function RefinedAdapter:provide(item, qty, slot)
|
||||||
|
end
|
||||||
|
|
||||||
|
function RefinedAdapter:extract(slot, qty)
|
||||||
|
-- self.pushItems(self.direction, slot, qty)
|
||||||
|
end
|
||||||
|
|
||||||
|
function RefinedAdapter:insert(slot, qty)
|
||||||
|
-- self.pullItems(self.direction, slot, qty)
|
||||||
|
end
|
||||||
|
|
||||||
|
return RefinedAdapter
|
||||||
1175
apis/schematic.lua
Normal file
1175
apis/schematic.lua
Normal file
File diff suppressed because it is too large
Load Diff
54
apis/tableDB.lua
Normal file
54
apis/tableDB.lua
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
local class = require('class')
|
||||||
|
local Util = require('util')
|
||||||
|
|
||||||
|
local TableDB = class()
|
||||||
|
function TableDB:init(args)
|
||||||
|
local defaults = {
|
||||||
|
fileName = '',
|
||||||
|
dirty = false,
|
||||||
|
data = { },
|
||||||
|
tabledef = { },
|
||||||
|
}
|
||||||
|
Util.merge(defaults, args)
|
||||||
|
Util.merge(self, defaults)
|
||||||
|
end
|
||||||
|
|
||||||
|
function TableDB:load()
|
||||||
|
local table = Util.readTable(self.fileName)
|
||||||
|
if table then
|
||||||
|
self.data = table.data
|
||||||
|
self.tabledef = table.tabledef
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function TableDB:add(key, entry)
|
||||||
|
if type(key) == 'table' then
|
||||||
|
key = table.concat(key, ':')
|
||||||
|
end
|
||||||
|
self.data[key] = entry
|
||||||
|
self.dirty = true
|
||||||
|
end
|
||||||
|
|
||||||
|
function TableDB:get(key)
|
||||||
|
if type(key) == 'table' then
|
||||||
|
key = table.concat(key, ':')
|
||||||
|
end
|
||||||
|
return self.data[key]
|
||||||
|
end
|
||||||
|
|
||||||
|
function TableDB:remove(key)
|
||||||
|
self.data[key] = nil
|
||||||
|
self.dirty = true
|
||||||
|
end
|
||||||
|
|
||||||
|
function TableDB:flush()
|
||||||
|
if self.dirty then
|
||||||
|
Util.writeTable(self.fileName, {
|
||||||
|
-- tabledef = self.tabledef,
|
||||||
|
data = self.data,
|
||||||
|
})
|
||||||
|
self.dirty = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return TableDB
|
||||||
36
apps/base64dl.lua
Normal file
36
apps/base64dl.lua
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
requireInjector(getfenv(1))
|
||||||
|
|
||||||
|
Base64 = require('base64')
|
||||||
|
|
||||||
|
local args = { ... }
|
||||||
|
|
||||||
|
if not args[2] then
|
||||||
|
error('Syntax: base64dl <file name> <url>')
|
||||||
|
end
|
||||||
|
|
||||||
|
local c = http.get(args[2])
|
||||||
|
|
||||||
|
if not c then
|
||||||
|
error('unable to open url')
|
||||||
|
end
|
||||||
|
|
||||||
|
local data = c.readAll()
|
||||||
|
c.close()
|
||||||
|
|
||||||
|
print('size: ' .. #data)
|
||||||
|
local decoded = Base64.decode(data)
|
||||||
|
print('decoded: ' .. #decoded)
|
||||||
|
|
||||||
|
local file = io.open(shell.resolve(args[1]), "wb")
|
||||||
|
if not file then
|
||||||
|
error('Unable to open ' .. args[1], 2)
|
||||||
|
end
|
||||||
|
for k,b in ipairs(decoded) do
|
||||||
|
if (k % 1000) == 0 then
|
||||||
|
os.sleep(0)
|
||||||
|
end
|
||||||
|
file:write(b)
|
||||||
|
end
|
||||||
|
|
||||||
|
file:close()
|
||||||
|
print('done')
|
||||||
2172
apps/builder.lua
Normal file
2172
apps/builder.lua
Normal file
File diff suppressed because it is too large
Load Diff
939
apps/chestManager.lua
Normal file
939
apps/chestManager.lua
Normal file
@@ -0,0 +1,939 @@
|
|||||||
|
requireInjector(getfenv(1))
|
||||||
|
|
||||||
|
local ChestAdapter = require('chestAdapter18')
|
||||||
|
local Config = require('config')
|
||||||
|
local Craft = require('turtle.craft')
|
||||||
|
local Event = require('event')
|
||||||
|
local itemDB = require('itemDB')
|
||||||
|
local Peripheral = require('peripheral')
|
||||||
|
local RefinedAdapter = require('refinedAdapter')
|
||||||
|
local Terminal = require('terminal')
|
||||||
|
local UI = require('ui')
|
||||||
|
local Util = require('util')
|
||||||
|
|
||||||
|
multishell.setTitle(multishell.getCurrent(), 'Resource Manager')
|
||||||
|
|
||||||
|
-- 3 wide monitor (any side of turtle)
|
||||||
|
|
||||||
|
-- Config location is /sys/config/resourceManager
|
||||||
|
-- adjust directions in that file if needed
|
||||||
|
|
||||||
|
local config = {
|
||||||
|
trashDirection = 'up', -- trash /chest in relation to chest
|
||||||
|
turtleDirection = 'down', -- turtle in relation to chest
|
||||||
|
}
|
||||||
|
|
||||||
|
Config.load('resourceManager', config)
|
||||||
|
|
||||||
|
local controller = RefinedAdapter()
|
||||||
|
if not controller:isValid() then
|
||||||
|
-- error('Refined storage controller not found')
|
||||||
|
controller = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local chestAdapter = ChestAdapter({ direction = 'west', wrapSide = 'back' })
|
||||||
|
local turtleChestAdapter = ChestAdapter({ direction = 'up', wrapSide = 'bottom' })
|
||||||
|
|
||||||
|
local RESOURCE_FILE = 'usr/etc/resources.db'
|
||||||
|
local RECIPES_FILE = 'sys/etc/recipes.db'
|
||||||
|
|
||||||
|
local jobListGrid
|
||||||
|
local craftingPaused = false
|
||||||
|
local recipes = Util.readTable(RECIPES_FILE) or { }
|
||||||
|
local resources = Util.readTable(RESOURCE_FILE) or { }
|
||||||
|
|
||||||
|
Craft.setRecipes(recipes)
|
||||||
|
|
||||||
|
for _,r in pairs(resources) do
|
||||||
|
r.maxDamage = nil
|
||||||
|
r.displayName = nil
|
||||||
|
r.count = nil
|
||||||
|
r.lname = nil
|
||||||
|
r.has_recipe = nil
|
||||||
|
|
||||||
|
if not r.ignoreDamage then
|
||||||
|
r.ignoreDamage = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
if not r.auto then
|
||||||
|
r.auto = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
Util.writeTable(RESOURCE_FILE, resources)
|
||||||
|
|
||||||
|
local function getItem(items, inItem, ignoreDamage)
|
||||||
|
for _,item in pairs(items) do
|
||||||
|
if item.name == inItem.name then
|
||||||
|
if ignoreDamage then
|
||||||
|
return item
|
||||||
|
elseif item.damage == inItem.damage and item.nbtHash == inItem.nbtHash then
|
||||||
|
return item
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function splitKey(key)
|
||||||
|
local t = Util.split(key, '(.-):')
|
||||||
|
local item = { }
|
||||||
|
if #t[#t] > 2 then
|
||||||
|
item.nbtHash = table.remove(t)
|
||||||
|
end
|
||||||
|
item.damage = tonumber(table.remove(t))
|
||||||
|
item.name = table.concat(t, ':')
|
||||||
|
return item
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getItemQuantity(items, item)
|
||||||
|
item = getItem(items, item)
|
||||||
|
if item then
|
||||||
|
return item.count
|
||||||
|
end
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getItemDetails(items, item)
|
||||||
|
local cItem = getItem(items, item)
|
||||||
|
if cItem then
|
||||||
|
return cItem
|
||||||
|
end
|
||||||
|
cItem = itemDB:get(itemDB:makeKey(item))
|
||||||
|
if cItem then
|
||||||
|
return { count = 0, maxCount = cItem.maxCount }
|
||||||
|
end
|
||||||
|
return { count = 0, maxCount = 64 }
|
||||||
|
end
|
||||||
|
|
||||||
|
local function uniqueKey(item)
|
||||||
|
return table.concat({ item.name, item.damage, item.nbtHash }, ':')
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getName(item)
|
||||||
|
local detail = itemDB:get(itemDB:makeKey(item))
|
||||||
|
if detail then
|
||||||
|
return detail.displayName
|
||||||
|
end
|
||||||
|
return item.name .. ':' .. item.damage
|
||||||
|
end
|
||||||
|
|
||||||
|
local function mergeResources(t)
|
||||||
|
for _,v in pairs(resources) do
|
||||||
|
local item = getItem(t, v)
|
||||||
|
if item then
|
||||||
|
Util.merge(item, v)
|
||||||
|
else
|
||||||
|
item = Util.shallowCopy(v)
|
||||||
|
item.count = 0
|
||||||
|
table.insert(t, item)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for k in pairs(recipes) do
|
||||||
|
local v = splitKey(k)
|
||||||
|
local item = getItem(t, v)
|
||||||
|
if not item then
|
||||||
|
item = Util.shallowCopy(v)
|
||||||
|
item.count = 0
|
||||||
|
table.insert(t, item)
|
||||||
|
end
|
||||||
|
item.has_recipe = true
|
||||||
|
end
|
||||||
|
|
||||||
|
for _,v in pairs(t) do
|
||||||
|
if not v.displayName then
|
||||||
|
v.displayName = getName(v)
|
||||||
|
end
|
||||||
|
v.lname = v.displayName:lower()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function filterItems(t, filter)
|
||||||
|
if filter then
|
||||||
|
local r = {}
|
||||||
|
filter = filter:lower()
|
||||||
|
for k,v in pairs(t) do
|
||||||
|
if string.find(v.lname, filter) then
|
||||||
|
table.insert(r, v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return r
|
||||||
|
end
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
local function sumItems3(ingredients, items, summedItems, count)
|
||||||
|
|
||||||
|
local canCraft = 0
|
||||||
|
for _,key in pairs(ingredients) do
|
||||||
|
local item = splitKey(key)
|
||||||
|
local summedItem = summedItems[key]
|
||||||
|
if not summedItem then
|
||||||
|
summedItem = Util.shallowCopy(item)
|
||||||
|
summedItem.recipe = recipes[key]
|
||||||
|
summedItem.count = getItemQuantity(items, summedItem)
|
||||||
|
summedItems[key] = summedItem
|
||||||
|
end
|
||||||
|
summedItem.count = summedItem.count - count
|
||||||
|
if summedItem.recipe and summedItem.count < 0 then
|
||||||
|
local need = math.ceil(-summedItem.count / summedItem.recipe.count)
|
||||||
|
summedItem.count = 0
|
||||||
|
sumItems3(summedItem.recipe.ingredients, items, summedItems, need)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function isGridClear()
|
||||||
|
for i = 1, 16 do
|
||||||
|
if turtle.getItemCount(i) ~= 0 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function clearGrid()
|
||||||
|
for i = 1, 16 do
|
||||||
|
local count = turtle.getItemCount(i)
|
||||||
|
if count > 0 then
|
||||||
|
chestAdapter:insert(i, count)
|
||||||
|
if turtle.getItemCount(i) ~= 0 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function addCraftingRequest(item, craftList, count)
|
||||||
|
local key = uniqueKey(item)
|
||||||
|
local request = craftList[key]
|
||||||
|
if not craftList[key] then
|
||||||
|
request = { name = item.name, damage = item.damage, nbtHash = nbtHash, count = 0 }
|
||||||
|
request.displayName = getName(request)
|
||||||
|
craftList[key] = request
|
||||||
|
end
|
||||||
|
request.count = request.count + count
|
||||||
|
end
|
||||||
|
|
||||||
|
local function craftItem(recipe, items, originalItem, craftList, count)
|
||||||
|
|
||||||
|
if craftingPaused or not device.workbench or not isGridClear() then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local toCraft = Craft.getCraftableAmount(recipe, count, items)
|
||||||
|
|
||||||
|
if toCraft > 0 then
|
||||||
|
Craft.craftRecipe(recipe, toCraft, chestAdapter)
|
||||||
|
clearGrid()
|
||||||
|
items = chestAdapter:listItems()
|
||||||
|
end
|
||||||
|
|
||||||
|
count = count - toCraft
|
||||||
|
|
||||||
|
if count > 0 then
|
||||||
|
local summedItems = { }
|
||||||
|
sumItems3(recipe.ingredients, items, summedItems, math.ceil(count / recipe.count))
|
||||||
|
|
||||||
|
for key,ingredient in pairs(summedItems) do
|
||||||
|
if not ingredient.recipe and ingredient.count < 0 then
|
||||||
|
addCraftingRequest(ingredient, craftList, -ingredient.count)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function craftItems(craftList, allItems)
|
||||||
|
|
||||||
|
for _,key in pairs(Util.keys(craftList)) do
|
||||||
|
local item = craftList[key]
|
||||||
|
local recipe = recipes[key]
|
||||||
|
if recipe then
|
||||||
|
craftItem(recipe, allItems, item, craftList, item.count)
|
||||||
|
allItems = chestAdapter:listItems() -- refresh counts
|
||||||
|
elseif item.rsControl then
|
||||||
|
item.status = 'Activated'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for key,item in pairs(craftList) do
|
||||||
|
|
||||||
|
if not recipes[key] then
|
||||||
|
if not controller then
|
||||||
|
item.status = '(no recipe)'
|
||||||
|
else
|
||||||
|
if controller:isCrafting(item) then
|
||||||
|
item.status = '(crafting)'
|
||||||
|
else
|
||||||
|
|
||||||
|
local count = item.count
|
||||||
|
while count >= 1 do -- try to request smaller quantities until successful
|
||||||
|
local s, m = pcall(function()
|
||||||
|
item.status = '(no recipe)'
|
||||||
|
if not controller:craft(item, count) then
|
||||||
|
item.status = '(missing ingredients)'
|
||||||
|
error('failed')
|
||||||
|
end
|
||||||
|
item.status = '(crafting)'
|
||||||
|
end)
|
||||||
|
if s then
|
||||||
|
break -- successfully requested crafting
|
||||||
|
end
|
||||||
|
count = math.floor(count / 2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function jobMonitor(jobList)
|
||||||
|
|
||||||
|
local mon = Peripheral.getByType('monitor')
|
||||||
|
|
||||||
|
if mon then
|
||||||
|
mon = UI.Device({
|
||||||
|
device = mon,
|
||||||
|
textScale = .5,
|
||||||
|
})
|
||||||
|
else
|
||||||
|
mon = UI.Device({
|
||||||
|
device = Terminal.getNullTerm(term.current())
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
jobListGrid = UI.Grid({
|
||||||
|
parent = mon,
|
||||||
|
sortColumn = 'displayName',
|
||||||
|
columns = {
|
||||||
|
{ heading = 'Qty', key = 'count', width = 6 },
|
||||||
|
{ heading = 'Crafting', key = 'displayName', width = mon.width / 2 - 10 },
|
||||||
|
{ heading = 'Status', key = 'status', width = mon.width - 10 },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getAutocraftItems()
|
||||||
|
local craftList = { }
|
||||||
|
|
||||||
|
for _,res in pairs(resources) do
|
||||||
|
|
||||||
|
if res.auto then
|
||||||
|
res.count = 4 -- this could be higher to increase autocrafting speed
|
||||||
|
local key = uniqueKey(res)
|
||||||
|
craftList[key] = res
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return craftList
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getItemWithQty(items, res, ignoreDamage)
|
||||||
|
|
||||||
|
local item = getItem(items, res, ignoreDamage)
|
||||||
|
|
||||||
|
if item then
|
||||||
|
|
||||||
|
if ignoreDamage then
|
||||||
|
local count = 0
|
||||||
|
|
||||||
|
for _,v in pairs(items) do
|
||||||
|
if item.name == v.name and item.nbtHash == v.nbtHash then
|
||||||
|
if item.maxDamage > 0 or item.damage == v.damage then
|
||||||
|
count = count + v.count
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
item.count = count
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return item
|
||||||
|
end
|
||||||
|
|
||||||
|
local function watchResources(items)
|
||||||
|
|
||||||
|
local craftList = { }
|
||||||
|
|
||||||
|
for k, res in pairs(resources) do
|
||||||
|
local item = getItemWithQty(items, res, res.ignoreDamage)
|
||||||
|
if not item then
|
||||||
|
item = {
|
||||||
|
damage = res.damage,
|
||||||
|
nbtHash = res.nbtHash,
|
||||||
|
name = res.name,
|
||||||
|
displayName = getName(res),
|
||||||
|
count = 0
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
if res.limit and item.count > res.limit then
|
||||||
|
chestAdapter:provide(res, item.count - res.limit, nil, config.trashDirection)
|
||||||
|
|
||||||
|
elseif res.low and item.count < res.low then
|
||||||
|
if res.ignoreDamage then
|
||||||
|
item.damage = 0
|
||||||
|
end
|
||||||
|
local key = uniqueKey(res)
|
||||||
|
craftList[key] = {
|
||||||
|
damage = item.damage,
|
||||||
|
nbtHash = item.nbtHash,
|
||||||
|
count = res.low - item.count,
|
||||||
|
name = item.name,
|
||||||
|
displayName = item.displayName,
|
||||||
|
status = '',
|
||||||
|
rsControl = res.rsControl,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
if res.rsControl and res.rsDevice and res.rsSide then
|
||||||
|
pcall(function()
|
||||||
|
device[res.rsDevice].setOutput(res.rsSide, item.count < res.low)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return craftList
|
||||||
|
end
|
||||||
|
|
||||||
|
local itemPage = UI.Page {
|
||||||
|
backgroundColor = colors.lightGray,
|
||||||
|
titleBar = UI.TitleBar {
|
||||||
|
title = 'Limit Resource',
|
||||||
|
previousPage = true,
|
||||||
|
event = 'form_cancel',
|
||||||
|
backgroundColor = colors.green
|
||||||
|
},
|
||||||
|
displayName = UI.Window {
|
||||||
|
x = 2, y = 2, width = UI.term.width - 4, height = 3,
|
||||||
|
},
|
||||||
|
form = UI.Form {
|
||||||
|
x = 4, y = 5, height = 8, rex = -4,
|
||||||
|
[1] = UI.TextEntry {
|
||||||
|
width = 7,
|
||||||
|
backgroundColor = colors.gray,
|
||||||
|
backgroundFocusColor = colors.gray,
|
||||||
|
formLabel = 'Min', formKey = 'low', help = 'Craft if below min'
|
||||||
|
},
|
||||||
|
[2] = UI.TextEntry {
|
||||||
|
width = 7,
|
||||||
|
backgroundColor = colors.gray,
|
||||||
|
backgroundFocusColor = colors.gray,
|
||||||
|
formLabel = 'Max', formKey = 'limit', help = 'Eject if above max'
|
||||||
|
},
|
||||||
|
[3] = UI.Chooser {
|
||||||
|
width = 7,
|
||||||
|
formLabel = 'Autocraft', formKey = 'auto',
|
||||||
|
nochoice = 'No',
|
||||||
|
choices = {
|
||||||
|
{ name = 'Yes', value = true },
|
||||||
|
{ name = 'No', value = false },
|
||||||
|
},
|
||||||
|
help = 'Craft until out of ingredients'
|
||||||
|
},
|
||||||
|
[4] = UI.Chooser {
|
||||||
|
width = 7,
|
||||||
|
formLabel = 'Ignore Dmg', formKey = 'ignore_dmg',
|
||||||
|
nochoice = 'No',
|
||||||
|
choices = {
|
||||||
|
{ name = 'Yes', value = true },
|
||||||
|
{ name = 'No', value = false },
|
||||||
|
},
|
||||||
|
help = 'Ignore damage of item'
|
||||||
|
},
|
||||||
|
[5] = UI.Chooser {
|
||||||
|
width = 7,
|
||||||
|
formLabel = 'RS Control', formKey = 'rsControl',
|
||||||
|
nochoice = 'No',
|
||||||
|
choices = {
|
||||||
|
{ name = 'Yes', value = true },
|
||||||
|
{ name = 'No', value = false },
|
||||||
|
},
|
||||||
|
help = 'Control via redstone'
|
||||||
|
},
|
||||||
|
[6] = UI.Chooser {
|
||||||
|
width = 25,
|
||||||
|
formLabel = 'RS Device', formKey = 'rsDevice',
|
||||||
|
--choices = devices,
|
||||||
|
help = 'Redstone Device'
|
||||||
|
},
|
||||||
|
[7] = UI.Chooser {
|
||||||
|
width = 10,
|
||||||
|
formLabel = 'RS Side', formKey = 'rsSide',
|
||||||
|
--nochoice = 'No',
|
||||||
|
choices = {
|
||||||
|
{ name = 'up', value = 'up' },
|
||||||
|
{ name = 'down', value = 'down' },
|
||||||
|
{ name = 'east', value = 'east' },
|
||||||
|
{ name = 'north', value = 'north' },
|
||||||
|
{ name = 'west', value = 'west' },
|
||||||
|
{ name = 'south', value = 'south' },
|
||||||
|
},
|
||||||
|
help = 'Output side'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
statusBar = UI.StatusBar { }
|
||||||
|
}
|
||||||
|
|
||||||
|
function itemPage.displayName:draw()
|
||||||
|
local item = self.parent.item
|
||||||
|
local str = string.format('Name: %s\nDamage: %d', item.displayName, item.damage)
|
||||||
|
if item.nbtHash then
|
||||||
|
str = str .. string.format('\n%s', item.nbtHash)
|
||||||
|
end
|
||||||
|
self:setCursorPos(1, 1)
|
||||||
|
self:print(str)
|
||||||
|
end
|
||||||
|
|
||||||
|
function itemPage:enable(item)
|
||||||
|
self.item = item
|
||||||
|
|
||||||
|
self.form:setValues(item)
|
||||||
|
self.titleBar.title = item.name
|
||||||
|
|
||||||
|
local devices = self.form[6].choices
|
||||||
|
Util.clear(devices)
|
||||||
|
for _,device in pairs(device) do
|
||||||
|
if device.setOutput then
|
||||||
|
table.insert(devices, { name = device.name, value = device.name })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if Util.size(devices) == 0 then
|
||||||
|
table.insert(devices, { name = 'None found', values = '' })
|
||||||
|
end
|
||||||
|
|
||||||
|
UI.Page.enable(self)
|
||||||
|
self:focusFirst()
|
||||||
|
end
|
||||||
|
|
||||||
|
function itemPage:eventHandler(event)
|
||||||
|
if event.type == 'form_cancel' then
|
||||||
|
UI:setPreviousPage()
|
||||||
|
|
||||||
|
elseif event.type == 'focus_change' then
|
||||||
|
self.statusBar:setStatus(event.focused.help)
|
||||||
|
self.statusBar:draw()
|
||||||
|
|
||||||
|
elseif event.type == 'form_complete' then
|
||||||
|
local values = self.form.values
|
||||||
|
local keys = { 'name', 'auto', 'low', 'limit', 'damage',
|
||||||
|
'nbtHash', 'ignoreDamage',
|
||||||
|
'rsControl', 'rsDevice', 'rsSide', }
|
||||||
|
|
||||||
|
local filtered = { }
|
||||||
|
for _,key in pairs(keys) do
|
||||||
|
filtered[key] = values[key]
|
||||||
|
end
|
||||||
|
filtered.low = tonumber(filtered.low)
|
||||||
|
filtered.limit = tonumber(filtered.limit)
|
||||||
|
|
||||||
|
--filtered.ignoreDamage = filtered.ignoreDamage == true
|
||||||
|
--filtered.auto = filtered.auto == true
|
||||||
|
--filtered.rsControl = filtered.rsControl == true
|
||||||
|
|
||||||
|
if filtered.auto ~= true then
|
||||||
|
filtered.auto = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
if filtered.rsControl ~= true then
|
||||||
|
filtered.rsControl = nil
|
||||||
|
filtered.rsSide = nil
|
||||||
|
filtered.rsDevice = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
if values.ignoreDamage == true then
|
||||||
|
filtered.damage = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
resources[uniqueKey(filtered)] = filtered
|
||||||
|
Util.writeTable(RESOURCE_FILE, resources)
|
||||||
|
|
||||||
|
UI:setPreviousPage()
|
||||||
|
|
||||||
|
else
|
||||||
|
return UI.Page.eventHandler(self, event)
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local listingPage = UI.Page {
|
||||||
|
menuBar = UI.MenuBar {
|
||||||
|
buttons = {
|
||||||
|
{ text = 'Learn', event = 'learn' },
|
||||||
|
{ text = 'Forget', event = 'forget' },
|
||||||
|
{ text = 'Craft', event = 'craft' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
grid = UI.Grid {
|
||||||
|
y = 2, height = UI.term.height - 2,
|
||||||
|
columns = {
|
||||||
|
{ heading = 'Name', key = 'displayName' , width = 22 },
|
||||||
|
{ heading = 'Qty', key = 'count' , width = 5 },
|
||||||
|
{ heading = 'Min', key = 'low' , width = 4 },
|
||||||
|
{ heading = 'Max', key = 'limit' , width = 4 },
|
||||||
|
},
|
||||||
|
sortColumn = 'displayName',
|
||||||
|
},
|
||||||
|
statusBar = UI.StatusBar {
|
||||||
|
backgroundColor = colors.gray,
|
||||||
|
width = UI.term.width,
|
||||||
|
filterText = UI.Text {
|
||||||
|
x = 2, width = 6,
|
||||||
|
value = 'Filter',
|
||||||
|
},
|
||||||
|
filter = UI.TextEntry {
|
||||||
|
x = 9, width = 19,
|
||||||
|
limit = 50,
|
||||||
|
},
|
||||||
|
refresh = UI.Button {
|
||||||
|
x = 31, width = 8,
|
||||||
|
text = 'Refresh',
|
||||||
|
event = 'refresh',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
accelerators = {
|
||||||
|
r = 'refresh',
|
||||||
|
q = 'quit',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function listingPage.grid:getRowTextColor(row, selected)
|
||||||
|
if row.is_craftable then
|
||||||
|
return colors.yellow
|
||||||
|
end
|
||||||
|
if row.has_recipe then
|
||||||
|
if selected then
|
||||||
|
return colors.blue
|
||||||
|
end
|
||||||
|
return colors.lightBlue
|
||||||
|
end
|
||||||
|
return UI.Grid:getRowTextColor(row, selected)
|
||||||
|
end
|
||||||
|
|
||||||
|
function listingPage.grid:getDisplayValues(row)
|
||||||
|
row = Util.shallowCopy(row)
|
||||||
|
row.count = Util.toBytes(row.count)
|
||||||
|
if row.low then
|
||||||
|
row.low = Util.toBytes(row.low)
|
||||||
|
end
|
||||||
|
if row.limit then
|
||||||
|
row.limit = Util.toBytes(row.limit)
|
||||||
|
end
|
||||||
|
return row
|
||||||
|
end
|
||||||
|
|
||||||
|
function listingPage.statusBar:draw()
|
||||||
|
return UI.Window.draw(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
function listingPage.statusBar.filter:eventHandler(event)
|
||||||
|
if event.type == 'mouse_rightclick' then
|
||||||
|
self.value = ''
|
||||||
|
self:draw()
|
||||||
|
local page = UI:getCurrentPage()
|
||||||
|
page.filter = nil
|
||||||
|
page:applyFilter()
|
||||||
|
page.grid:draw()
|
||||||
|
page:setFocus(self)
|
||||||
|
end
|
||||||
|
return UI.TextEntry.eventHandler(self, event)
|
||||||
|
end
|
||||||
|
|
||||||
|
function listingPage:eventHandler(event)
|
||||||
|
if event.type == 'quit' then
|
||||||
|
UI:exitPullEvents()
|
||||||
|
|
||||||
|
elseif event.type == 'grid_select' then
|
||||||
|
local selected = event.selected
|
||||||
|
UI:setPage('item', selected)
|
||||||
|
|
||||||
|
elseif event.type == 'refresh' then
|
||||||
|
self:refresh()
|
||||||
|
self.grid:draw()
|
||||||
|
self.statusBar.filter:focus()
|
||||||
|
|
||||||
|
elseif event.type == 'learn' then
|
||||||
|
UI:setPage('learn')
|
||||||
|
|
||||||
|
elseif event.type == 'craft' then
|
||||||
|
UI:setPage('craft')
|
||||||
|
|
||||||
|
elseif event.type == 'forget' then
|
||||||
|
local item = self.grid:getSelected()
|
||||||
|
if item then
|
||||||
|
local key = uniqueKey(item)
|
||||||
|
|
||||||
|
if recipes[key] then
|
||||||
|
recipes[key] = nil
|
||||||
|
Util.writeTable(RECIPES_FILE, recipes)
|
||||||
|
end
|
||||||
|
|
||||||
|
if resources[key] then
|
||||||
|
resources[key] = nil
|
||||||
|
Util.writeTable(RESOURCE_FILE, resources)
|
||||||
|
end
|
||||||
|
|
||||||
|
self.statusBar:timedStatus('Forgot: ' .. item.name, 3)
|
||||||
|
self:refresh()
|
||||||
|
self.grid:draw()
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif event.type == 'text_change' then
|
||||||
|
self.filter = event.text
|
||||||
|
if #self.filter == 0 then
|
||||||
|
self.filter = nil
|
||||||
|
end
|
||||||
|
self:applyFilter()
|
||||||
|
self.grid:draw()
|
||||||
|
self.statusBar.filter:focus()
|
||||||
|
|
||||||
|
else
|
||||||
|
UI.Page.eventHandler(self, event)
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function listingPage:enable()
|
||||||
|
self:refresh()
|
||||||
|
self:setFocus(self.statusBar.filter)
|
||||||
|
UI.Page.enable(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
function listingPage:refresh()
|
||||||
|
self.allItems = chestAdapter:listItems()
|
||||||
|
mergeResources(self.allItems)
|
||||||
|
self:applyFilter()
|
||||||
|
end
|
||||||
|
|
||||||
|
function listingPage:applyFilter()
|
||||||
|
local t = filterItems(self.allItems, self.filter)
|
||||||
|
self.grid:setValues(t)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- without duck antenna
|
||||||
|
local function getTurtleInventoryOld()
|
||||||
|
local inventory = { }
|
||||||
|
for i = 1,16 do
|
||||||
|
if turtle.getItemCount(i) > 0 then
|
||||||
|
turtle.select(i)
|
||||||
|
local item = turtle.getItemDetail()
|
||||||
|
inventory[i] = {
|
||||||
|
name = item.name,
|
||||||
|
damage = item.damage,
|
||||||
|
count = item.count,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return inventory
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getTurtleInventory()
|
||||||
|
local inventory = { }
|
||||||
|
for i = 1,16 do
|
||||||
|
local qty = turtle.getItemCount(i)
|
||||||
|
if qty > 0 then
|
||||||
|
turtleChestAdapter:insert(i, qty)
|
||||||
|
local items = turtleChestAdapter:listItems()
|
||||||
|
_, inventory[i] = next(items)
|
||||||
|
turtleChestAdapter:extract(1, qty, i)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return inventory
|
||||||
|
end
|
||||||
|
|
||||||
|
local function filter(t, filter)
|
||||||
|
local keys = Util.keys(t)
|
||||||
|
for _,key in pairs(keys) do
|
||||||
|
if not Util.key(filter, key) then
|
||||||
|
t[key] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function learnRecipe(page)
|
||||||
|
local recipe = { }
|
||||||
|
local ingredients = getTurtleInventory()
|
||||||
|
if ingredients then
|
||||||
|
turtle.select(1)
|
||||||
|
if device.workbench and turtle.craft() then
|
||||||
|
recipe = getTurtleInventory()
|
||||||
|
if recipe and recipe[1] then
|
||||||
|
clearGrid()
|
||||||
|
|
||||||
|
local key = uniqueKey(recipe[1])
|
||||||
|
local newRecipe = {
|
||||||
|
count = recipe[1].count,
|
||||||
|
ingredients = ingredients,
|
||||||
|
}
|
||||||
|
if recipe[1].maxCount ~= 64 then
|
||||||
|
newRecipe.maxCount = recipe[1].maxCount
|
||||||
|
end
|
||||||
|
|
||||||
|
for k,ingredient in pairs(ingredients) do
|
||||||
|
ingredients[k] = uniqueKey(ingredient)
|
||||||
|
end
|
||||||
|
|
||||||
|
recipes[key] = newRecipe
|
||||||
|
|
||||||
|
Util.writeTable(RECIPES_FILE, recipes)
|
||||||
|
|
||||||
|
local displayName = getName(recipe[1])
|
||||||
|
|
||||||
|
listingPage.statusBar.filter:setValue(displayName)
|
||||||
|
listingPage.statusBar:timedStatus('Learned: ' .. displayName, 3)
|
||||||
|
listingPage.filter = displayName
|
||||||
|
listingPage:refresh()
|
||||||
|
listingPage.grid:draw()
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
else
|
||||||
|
page.statusBar:timedStatus('Failed to craft', 3)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
page.statusBar:timedStatus('No recipe defined', 3)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local learnPage = UI.Dialog {
|
||||||
|
height = 7, width = UI.term.width - 6,
|
||||||
|
backgroundColor = colors.lightGray,
|
||||||
|
title = 'Learn Recipe',
|
||||||
|
idField = UI.Text {
|
||||||
|
x = 5,
|
||||||
|
y = 3,
|
||||||
|
width = UI.term.width - 10,
|
||||||
|
value = 'Place recipe in turtle'
|
||||||
|
},
|
||||||
|
accept = UI.Button {
|
||||||
|
rx = -13, ry = -2,
|
||||||
|
text = 'Ok', event = 'accept',
|
||||||
|
},
|
||||||
|
cancel = UI.Button {
|
||||||
|
rx = -8, ry = -2,
|
||||||
|
text = 'Cancel', event = 'cancel'
|
||||||
|
},
|
||||||
|
statusBar = UI.StatusBar {
|
||||||
|
status = 'Crafting paused'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function learnPage:enable()
|
||||||
|
craftingPaused = true
|
||||||
|
self:focusFirst()
|
||||||
|
UI.Dialog.enable(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
function learnPage:disable()
|
||||||
|
craftingPaused = false
|
||||||
|
UI.Dialog.disable(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
function learnPage:eventHandler(event)
|
||||||
|
if event.type == 'cancel' then
|
||||||
|
UI:setPreviousPage()
|
||||||
|
elseif event.type == 'accept' then
|
||||||
|
if learnRecipe(self) then
|
||||||
|
UI:setPreviousPage()
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return UI.Dialog.eventHandler(self, event)
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local craftPage = UI.Dialog {
|
||||||
|
height = 6, width = UI.term.width - 10,
|
||||||
|
backgroundColor = colors.lightGray,
|
||||||
|
title = 'Enter amount to craft',
|
||||||
|
idField = UI.TextEntry {
|
||||||
|
x = 15,
|
||||||
|
y = 3,
|
||||||
|
width = 10,
|
||||||
|
limit = 6,
|
||||||
|
value = '1',
|
||||||
|
backgroundColor = colors.black,
|
||||||
|
backgroundFocusColor = colors.black,
|
||||||
|
},
|
||||||
|
accept = UI.Button {
|
||||||
|
rx = -7, ry = -1,
|
||||||
|
backgroundColor = colors.green,
|
||||||
|
text = '+', event = 'accept',
|
||||||
|
},
|
||||||
|
cancel = UI.Button {
|
||||||
|
rx = -3, ry = -1,
|
||||||
|
backgroundColor = colors.red,
|
||||||
|
backgroundFocusColor = colors.red,
|
||||||
|
text = '\215', event = 'cancel'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
function craftPage:draw()
|
||||||
|
UI.Dialog.draw(self)
|
||||||
|
self:write(6, 3, 'Quantity')
|
||||||
|
end
|
||||||
|
|
||||||
|
function craftPage:enable()
|
||||||
|
craftingPaused = true
|
||||||
|
self:focusFirst()
|
||||||
|
UI.Dialog.enable(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
function craftPage:disable()
|
||||||
|
craftingPaused = false
|
||||||
|
UI.Dialog.disable(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
function craftPage:eventHandler(event)
|
||||||
|
if event.type == 'cancel' then
|
||||||
|
UI:setPreviousPage()
|
||||||
|
elseif event.type == 'accept' then
|
||||||
|
|
||||||
|
else
|
||||||
|
return UI.Dialog.eventHandler(self, event)
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
UI:setPages({
|
||||||
|
listing = listingPage,
|
||||||
|
item = itemPage,
|
||||||
|
learn = learnPage,
|
||||||
|
craft = craftPage,
|
||||||
|
})
|
||||||
|
|
||||||
|
UI:setPage(listingPage)
|
||||||
|
listingPage:setFocus(listingPage.statusBar.filter)
|
||||||
|
|
||||||
|
clearGrid()
|
||||||
|
jobMonitor()
|
||||||
|
jobListGrid:draw()
|
||||||
|
jobListGrid:sync()
|
||||||
|
|
||||||
|
Event.onInterval(5, function()
|
||||||
|
|
||||||
|
if not craftingPaused then
|
||||||
|
local items = chestAdapter:listItems()
|
||||||
|
if Util.size(items) == 0 then
|
||||||
|
jobListGrid.parent:clear()
|
||||||
|
jobListGrid.parent:centeredWrite(math.ceil(jobListGrid.parent.height/2), 'No items in system')
|
||||||
|
jobListGrid:sync()
|
||||||
|
|
||||||
|
else
|
||||||
|
local craftList = watchResources(items)
|
||||||
|
jobListGrid:setValues(craftList)
|
||||||
|
--jobListGrid:draw()
|
||||||
|
--jobListGrid:sync()
|
||||||
|
craftItems(craftList, items)
|
||||||
|
jobListGrid:update()
|
||||||
|
jobListGrid:draw()
|
||||||
|
jobListGrid:sync()
|
||||||
|
craftList = getAutocraftItems(items) -- autocrafted items don't show on job monitor
|
||||||
|
craftItems(craftList, items)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
UI:pullEvents()
|
||||||
|
jobListGrid.parent:reset()
|
||||||
101
apps/logMonitor.lua
Normal file
101
apps/logMonitor.lua
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
requireInjector(getfenv(1))
|
||||||
|
|
||||||
|
local Event = require('event')
|
||||||
|
local Message = require('message')
|
||||||
|
local UI = require('ui')
|
||||||
|
local Util = require('util')
|
||||||
|
|
||||||
|
multishell.setTitle(multishell.getCurrent(), 'Log Monitor')
|
||||||
|
|
||||||
|
if not device.wireless_modem then
|
||||||
|
error('Wireless modem is required')
|
||||||
|
end
|
||||||
|
device.wireless_modem.open(59998)
|
||||||
|
|
||||||
|
local ids = { }
|
||||||
|
local messages = { }
|
||||||
|
local terminal = UI.term
|
||||||
|
|
||||||
|
if device.openperipheral_bridge then
|
||||||
|
|
||||||
|
UI.Glasses = require('glasses')
|
||||||
|
|
||||||
|
terminal = UI.Glasses({
|
||||||
|
x = 4,
|
||||||
|
y = 175,
|
||||||
|
height = 40,
|
||||||
|
width = 64,
|
||||||
|
textScale = .5,
|
||||||
|
backgroundOpacity = .65,
|
||||||
|
|
||||||
|
})
|
||||||
|
elseif device.monitor then
|
||||||
|
terminal = UI.Device({
|
||||||
|
deviceType = 'monitor',
|
||||||
|
textScale = .5
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
terminal:clear()
|
||||||
|
|
||||||
|
function getClient(id)
|
||||||
|
if not ids[id] then
|
||||||
|
ids[id] = {
|
||||||
|
titleBar = UI.TitleBar({ title = 'ID: ' .. id, parent = terminal }),
|
||||||
|
scrollingText = UI.ScrollingText({ parent = terminal })
|
||||||
|
}
|
||||||
|
local clientCount = Util.size(ids)
|
||||||
|
local clientHeight = math.floor((terminal.height - clientCount) / clientCount)
|
||||||
|
terminal:clear()
|
||||||
|
local y = 1
|
||||||
|
for k,v in pairs(ids) do
|
||||||
|
v.titleBar.y = y
|
||||||
|
y = y + 1
|
||||||
|
v.scrollingText.height = clientHeight
|
||||||
|
v.scrollingText.y = y
|
||||||
|
y = y + clientHeight
|
||||||
|
v.scrollingText:clear()
|
||||||
|
|
||||||
|
v.titleBar:draw()
|
||||||
|
v.scrollingText:draw()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return ids[id]
|
||||||
|
end
|
||||||
|
|
||||||
|
Event.on('logMessage', function()
|
||||||
|
local t = { }
|
||||||
|
while #messages > 0 do
|
||||||
|
local msg = messages[1]
|
||||||
|
table.remove(messages, 1)
|
||||||
|
local client = getClient(msg.id)
|
||||||
|
client.scrollingText:appendLine(string.format('%d %s', math.floor(os.clock()), msg.text))
|
||||||
|
t[msg.id] = client
|
||||||
|
end
|
||||||
|
for _,client in pairs(t) do
|
||||||
|
client.scrollingText:draw()
|
||||||
|
end
|
||||||
|
terminal:sync()
|
||||||
|
end)
|
||||||
|
|
||||||
|
Message.addHandler('log', function(h, id, msg)
|
||||||
|
table.insert(messages, { id = id, text = msg.contents })
|
||||||
|
os.queueEvent('logMessage')
|
||||||
|
end)
|
||||||
|
|
||||||
|
Event.on('monitor_touch', function()
|
||||||
|
terminal:reset()
|
||||||
|
ids = { }
|
||||||
|
end)
|
||||||
|
|
||||||
|
Event.on('mouse_click', function()
|
||||||
|
terminal:reset()
|
||||||
|
ids = { }
|
||||||
|
end)
|
||||||
|
|
||||||
|
Event.on('char', function()
|
||||||
|
Event.exitPullEvents()
|
||||||
|
end)
|
||||||
|
|
||||||
|
Event.pullEvents(logWriter)
|
||||||
|
terminal:reset()
|
||||||
25
apps/mirror.lua
Normal file
25
apps/mirror.lua
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
requireInjector(getfenv(1))
|
||||||
|
|
||||||
|
local Terminal = require('terminal')
|
||||||
|
|
||||||
|
local args = { ... }
|
||||||
|
local mon = device[table.remove(args, 1) or 'monitor']
|
||||||
|
if not mon then
|
||||||
|
error('mirror: Invalid device')
|
||||||
|
end
|
||||||
|
|
||||||
|
mon.clear()
|
||||||
|
mon.setTextScale(.5)
|
||||||
|
mon.setCursorPos(1, 1)
|
||||||
|
|
||||||
|
local oterm = Terminal.copy(term.current())
|
||||||
|
Terminal.mirror(term.current(), mon)
|
||||||
|
|
||||||
|
term.current().getSize = mon.getSize
|
||||||
|
|
||||||
|
if #args > 0 then
|
||||||
|
shell.run(unpack(args))
|
||||||
|
Terminal.copy(oterm, term.current())
|
||||||
|
|
||||||
|
mon.setCursorBlink(false)
|
||||||
|
end
|
||||||
86
apps/mirrorClient.lua
Normal file
86
apps/mirrorClient.lua
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
requireInjector(getfenv(1))
|
||||||
|
|
||||||
|
local Event = require('event')
|
||||||
|
local Logger = require('logger')
|
||||||
|
local Socket = require('socket')
|
||||||
|
local Terminal = require('terminal')
|
||||||
|
local Util = require('util')
|
||||||
|
|
||||||
|
Logger.setScreenLogging()
|
||||||
|
|
||||||
|
local remoteId
|
||||||
|
local args = { ... }
|
||||||
|
if #args == 1 then
|
||||||
|
remoteId = tonumber(args[1])
|
||||||
|
else
|
||||||
|
print('Enter host ID')
|
||||||
|
remoteId = tonumber(read())
|
||||||
|
end
|
||||||
|
|
||||||
|
if not remoteId then
|
||||||
|
error('Syntax: mirrorClient <host ID>')
|
||||||
|
end
|
||||||
|
|
||||||
|
local function wrapTerm(socket)
|
||||||
|
local methods = { 'blit', 'clear', 'clearLine', 'setCursorPos', 'write',
|
||||||
|
'setTextColor', 'setTextColour', 'setBackgroundColor',
|
||||||
|
'setBackgroundColour', 'scroll', 'setCursorBlink', }
|
||||||
|
|
||||||
|
socket.term = multishell.term
|
||||||
|
socket.oldTerm = Util.shallowCopy(socket.term)
|
||||||
|
|
||||||
|
for _,k in pairs(methods) do
|
||||||
|
socket.term[k] = function(...)
|
||||||
|
if not socket.queue then
|
||||||
|
socket.queue = { }
|
||||||
|
Event.onTimeout(0, function()
|
||||||
|
if socket.queue then
|
||||||
|
socket:write(socket.queue)
|
||||||
|
socket.queue = nil
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
table.insert(socket.queue, {
|
||||||
|
f = k,
|
||||||
|
args = { ... },
|
||||||
|
})
|
||||||
|
socket.oldTerm[k](...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
while true do
|
||||||
|
print('connecting...')
|
||||||
|
local socket
|
||||||
|
|
||||||
|
while true do
|
||||||
|
socket = Socket.connect(remoteId, 5901)
|
||||||
|
if socket then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
os.sleep(3)
|
||||||
|
end
|
||||||
|
|
||||||
|
print('connected')
|
||||||
|
|
||||||
|
wrapTerm(socket)
|
||||||
|
|
||||||
|
os.queueEvent('term_resize')
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local e = Event.pullEvent()
|
||||||
|
if e[1] == 'terminate' then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
if not socket.connected then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for k,v in pairs(socket.oldTerm) do
|
||||||
|
socket.term[k] = v
|
||||||
|
end
|
||||||
|
|
||||||
|
socket:close()
|
||||||
|
|
||||||
|
end
|
||||||
53
apps/mirrorHost.lua
Normal file
53
apps/mirrorHost.lua
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
requireInjector(getfenv(1))
|
||||||
|
|
||||||
|
local Event = require('event')
|
||||||
|
local Logger = require('logger')
|
||||||
|
local Socket = require('socket')
|
||||||
|
|
||||||
|
Logger.setScreenLogging()
|
||||||
|
|
||||||
|
local args = { ... }
|
||||||
|
local mon = device[args[1] or 'monitor']
|
||||||
|
|
||||||
|
if not mon then
|
||||||
|
error('Monitor not attached')
|
||||||
|
end
|
||||||
|
|
||||||
|
mon.setBackgroundColor(colors.black)
|
||||||
|
mon.clear()
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local socket = Socket.server(5901)
|
||||||
|
|
||||||
|
print('mirror: connection from ' .. socket.dhost)
|
||||||
|
|
||||||
|
Event.addRoutine(function()
|
||||||
|
while true do
|
||||||
|
local data = socket:read()
|
||||||
|
if not data then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
for _,v in ipairs(data) do
|
||||||
|
mon[v.f](unpack(v.args))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- ensure socket is connected
|
||||||
|
Event.onInterval(3, function(h)
|
||||||
|
if not socket:ping() then
|
||||||
|
Event.off(h)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
while true do
|
||||||
|
Event.pullEvent()
|
||||||
|
if not socket.connected then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
print('connection lost')
|
||||||
|
|
||||||
|
socket:close()
|
||||||
|
end
|
||||||
337
apps/pickup.lua
Normal file
337
apps/pickup.lua
Normal file
@@ -0,0 +1,337 @@
|
|||||||
|
requireInjector(getfenv(1))
|
||||||
|
|
||||||
|
local Event = require('event')
|
||||||
|
local GPS = require('gps')
|
||||||
|
local Logger = require('logger')
|
||||||
|
local MEProvider = require('meProvider')
|
||||||
|
local Point = require('point')
|
||||||
|
local Socket = require('socket')
|
||||||
|
local Util = require('util')
|
||||||
|
|
||||||
|
if not device.wireless_modem then
|
||||||
|
error('Modem is required')
|
||||||
|
end
|
||||||
|
|
||||||
|
Logger.setWirelessLogging()
|
||||||
|
|
||||||
|
if not turtle then
|
||||||
|
error('Can only be run on a turtle')
|
||||||
|
end
|
||||||
|
|
||||||
|
local blocks = { }
|
||||||
|
local meProvider = MEProvider()
|
||||||
|
local items = { }
|
||||||
|
|
||||||
|
local pickups = Util.readTable('pickup.tbl') or { }
|
||||||
|
local cells = Util.readTable('cells.tbl') or { }
|
||||||
|
local refills = Util.readTable('refills.tbl') or { }
|
||||||
|
local fluids = Util.readTable('fluids.tbl') or { }
|
||||||
|
local chestPt = turtle.loadLocation('chest')
|
||||||
|
local chargePt = turtle.loadLocation('charge')
|
||||||
|
|
||||||
|
local fuel = {
|
||||||
|
item = {
|
||||||
|
id = 'minecraft:coal',
|
||||||
|
dmg = 0,
|
||||||
|
},
|
||||||
|
qty = 64
|
||||||
|
}
|
||||||
|
|
||||||
|
local slots
|
||||||
|
|
||||||
|
turtle.setMoveCallback(function(action, pt)
|
||||||
|
if slots then
|
||||||
|
for _,slot in pairs(slots) do
|
||||||
|
if turtle.getItemCount(slot.index) ~= slot.qty then
|
||||||
|
printError('Slots changed')
|
||||||
|
Event.exitPullEvents()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
function refuel()
|
||||||
|
if turtle.getFuelLevel() < 5000 then
|
||||||
|
print('refueling')
|
||||||
|
turtle.status = 'refueling'
|
||||||
|
gotoPoint(chestPt, true)
|
||||||
|
dropOff(chestPt)
|
||||||
|
while turtle.getFuelLevel() < 5000 do
|
||||||
|
turtle.select(1)
|
||||||
|
meProvider:provide(fuel.item, fuel.qty, 1)
|
||||||
|
turtle.refuel(64)
|
||||||
|
print(turtle.getFuelLevel())
|
||||||
|
os.sleep(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function pickUp(pt)
|
||||||
|
turtle.status = 'picking up'
|
||||||
|
gotoPoint(pt, true)
|
||||||
|
while true do
|
||||||
|
if not turtle.selectOpenSlot() then
|
||||||
|
dropOff(chestPt)
|
||||||
|
gotoPoint(pt, true)
|
||||||
|
end
|
||||||
|
turtle.select(1)
|
||||||
|
if not turtle.suckDown(64) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function dropOff(pt)
|
||||||
|
if turtle.selectSlotWithItems() then
|
||||||
|
gotoPoint(pt, true)
|
||||||
|
turtle.emptyInventory(turtle.dropDown)
|
||||||
|
if pt == chestPt then
|
||||||
|
print('refreshing items')
|
||||||
|
items = meProvider:refresh()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function gotoPoint(pt, doDetect)
|
||||||
|
slots = turtle.getInventory()
|
||||||
|
while not turtle.pathfind(pt, blocks) do
|
||||||
|
if turtle.abort then
|
||||||
|
error('aborted')
|
||||||
|
end
|
||||||
|
turtle.status = 'blocked'
|
||||||
|
os.sleep(5)
|
||||||
|
end
|
||||||
|
|
||||||
|
if doDetect and not turtle.detectDown() then
|
||||||
|
printError('Missing target')
|
||||||
|
Event.exitPullEvents()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function checkCell(pt)
|
||||||
|
if not turtle.selectOpenSlot() then
|
||||||
|
dropOff(chestPt)
|
||||||
|
end
|
||||||
|
|
||||||
|
print('checking cell')
|
||||||
|
turtle.status = 'recharging'
|
||||||
|
gotoPoint(pt, true)
|
||||||
|
local c = peripheral.wrap('bottom')
|
||||||
|
local energy = c.getMaxEnergyStored() -
|
||||||
|
c.getEnergyStored()
|
||||||
|
if energy > 20000 then
|
||||||
|
print('charging cell')
|
||||||
|
turtle.selectOpenSlot()
|
||||||
|
turtle.digDown()
|
||||||
|
gotoPoint(chargePt, true)
|
||||||
|
turtle.dropDown()
|
||||||
|
os.sleep(energy / 20000)
|
||||||
|
turtle.suckDown()
|
||||||
|
print('replacing cell')
|
||||||
|
gotoPoint(pt)
|
||||||
|
if not turtle.placeDown() then
|
||||||
|
error('could not place down cell')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function fluid(points)
|
||||||
|
print('checking fluid')
|
||||||
|
turtle.status = 'fluiding'
|
||||||
|
gotoPoint(points.source, true)
|
||||||
|
turtle.select(1)
|
||||||
|
turtle.digDown()
|
||||||
|
gotoPoint(points.target)
|
||||||
|
if not turtle.placeDown() then
|
||||||
|
error('could not place fluid container')
|
||||||
|
end
|
||||||
|
os.sleep(5)
|
||||||
|
turtle.digDown()
|
||||||
|
gotoPoint(points.source)
|
||||||
|
turtle.placeDown()
|
||||||
|
end
|
||||||
|
|
||||||
|
function refill(entry)
|
||||||
|
dropOff(chestPt)
|
||||||
|
|
||||||
|
turtle.status = 'refilling'
|
||||||
|
gotoPoint(chestPt)
|
||||||
|
for _,item in pairs(entry.items) do
|
||||||
|
meProvider:provide(item, tonumber(item.qty), turtle.selectOpenSlot())
|
||||||
|
end
|
||||||
|
|
||||||
|
if turtle.selectSlotWithItems() then
|
||||||
|
if entry.point then
|
||||||
|
dropOff(entry.point)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function oldRefill(points)
|
||||||
|
gotoPoint(points.source)
|
||||||
|
repeat until not turtle.suckDown(64)
|
||||||
|
if points.target then
|
||||||
|
dropOff(points.target)
|
||||||
|
end
|
||||||
|
if points.targets then
|
||||||
|
for k,target in pairs(points.targets) do
|
||||||
|
dropOff(target)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
dropOff(points.source)
|
||||||
|
dropOff(chestPt)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function makeKey(pt)
|
||||||
|
return string.format('%d:%d:%d', pt.x, pt.y, pt.z)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function pickupHost(socket)
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local data = socket:read()
|
||||||
|
if not data then
|
||||||
|
print('pickup: closing connection to ' .. socket.dhost)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
print('command: ' .. data.type)
|
||||||
|
|
||||||
|
if data.type == 'pickup' then
|
||||||
|
local key = makeKey(data.point)
|
||||||
|
pickups[key] = data.point
|
||||||
|
Util.writeTable('pickup.tbl', pickups)
|
||||||
|
socket:write( { type = "response", response = 'added' })
|
||||||
|
|
||||||
|
elseif data.type == 'items' then
|
||||||
|
socket:write( { type = "response", response = items })
|
||||||
|
|
||||||
|
elseif data.type == 'refill' then
|
||||||
|
local key = makeKey(data.entry.point)
|
||||||
|
refills[key] = data.entry
|
||||||
|
Util.writeTable('refills.tbl', refills)
|
||||||
|
socket:write( { type = "response", response = 'added' })
|
||||||
|
|
||||||
|
elseif data.type == 'setPickup' then
|
||||||
|
chestPt = data.point
|
||||||
|
-- fix
|
||||||
|
turtle.storeLocation('chest', chestPt)
|
||||||
|
socket:write( { type = "response", response = 'Location set' })
|
||||||
|
|
||||||
|
elseif data.type == 'setRecharge' then
|
||||||
|
chargePt = data.point
|
||||||
|
-- fix
|
||||||
|
turtle.storeLocation('charge', chargePt)
|
||||||
|
socket:write( { type = "response", response = 'Location set' })
|
||||||
|
|
||||||
|
elseif data.type == 'charge' then
|
||||||
|
local key = makeKey(data.point)
|
||||||
|
cells[key] = data.point
|
||||||
|
Util.writeTable('cells.tbl', cells)
|
||||||
|
socket:write( { type = "response", response = 'added' })
|
||||||
|
|
||||||
|
elseif data.type == 'fluid' then
|
||||||
|
|
||||||
|
elseif data.type == 'clear' then
|
||||||
|
local key = makeKey(data.point)
|
||||||
|
refills[key] = nil
|
||||||
|
cells[key] = nil
|
||||||
|
fluids[key] = nil
|
||||||
|
pickups[key] = nil
|
||||||
|
|
||||||
|
Util.writeTable('refills.tbl', refills)
|
||||||
|
Util.writeTable('cells.tbl', cells)
|
||||||
|
Util.writeTable('fluids.tbl', fluids)
|
||||||
|
Util.writeTable('pickup.tbl', pickups)
|
||||||
|
|
||||||
|
socket:write( { type = "response", response = 'cleared' })
|
||||||
|
else
|
||||||
|
print('unknown command')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Event.addRoutine(function()
|
||||||
|
while true do
|
||||||
|
print('waiting for connection on port 5222')
|
||||||
|
local socket = Socket.server(5222)
|
||||||
|
|
||||||
|
print('pickup: connection from ' .. socket.dhost)
|
||||||
|
|
||||||
|
Event.addRoutine(function() pickupHost(socket) end)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
local function eachEntry(t, fn)
|
||||||
|
|
||||||
|
local keys = Util.keys(t)
|
||||||
|
for _,key in pairs(keys) do
|
||||||
|
if t[key] then
|
||||||
|
if turtle.abort then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
fn(t[key])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function eachClosestEntry(t, fn)
|
||||||
|
|
||||||
|
local points = { }
|
||||||
|
for k,v in pairs(t) do
|
||||||
|
v = Util.shallowCopy(v)
|
||||||
|
v.key = k
|
||||||
|
table.insert(points, v)
|
||||||
|
end
|
||||||
|
|
||||||
|
while not Util.empty(points) do
|
||||||
|
local closest = Point.closest(turtle.point, points)
|
||||||
|
if turtle.abort then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if t[closest.key] then
|
||||||
|
fn(closest)
|
||||||
|
end
|
||||||
|
for k,v in pairs(points) do
|
||||||
|
if v.key == closest.key then
|
||||||
|
table.remove(points, k)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Event.addRoutine(function()
|
||||||
|
|
||||||
|
while true do
|
||||||
|
if chestPt then
|
||||||
|
eachClosestEntry(pickups, pickUp)
|
||||||
|
eachEntry(refills, refill)
|
||||||
|
refuel()
|
||||||
|
end
|
||||||
|
eachEntry(fluids, fluid)
|
||||||
|
if chargePt then
|
||||||
|
eachEntry(cells, checkCell)
|
||||||
|
end
|
||||||
|
print('sleeping')
|
||||||
|
turtle.status = 'sleeping'
|
||||||
|
if turtle.abort then
|
||||||
|
printError('aborted')
|
||||||
|
break
|
||||||
|
end
|
||||||
|
os.sleep(60)
|
||||||
|
end
|
||||||
|
|
||||||
|
Event.exitPullEvents()
|
||||||
|
end)
|
||||||
|
|
||||||
|
turtle.run(function()
|
||||||
|
|
||||||
|
if not turtle.enableGPS() then
|
||||||
|
error('turtle: No GPS found')
|
||||||
|
end
|
||||||
|
|
||||||
|
refuel()
|
||||||
|
Event.pullEvents()
|
||||||
|
|
||||||
|
end)
|
||||||
231
apps/pickupRemote.lua
Normal file
231
apps/pickupRemote.lua
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
if not device.wireless_modem then
|
||||||
|
error('Wireless modem is required')
|
||||||
|
end
|
||||||
|
|
||||||
|
requireInjector(getfenv(1))
|
||||||
|
|
||||||
|
local Event = require('event')
|
||||||
|
local GPS = require('gps')
|
||||||
|
local Socket = require('socket')
|
||||||
|
local UI = require('ui')
|
||||||
|
local Util = require('util')
|
||||||
|
|
||||||
|
multishell.setTitle(multishell.getCurrent(), 'Pickup Remote')
|
||||||
|
|
||||||
|
local id
|
||||||
|
|
||||||
|
local mainPage = UI.Page({
|
||||||
|
menu = UI.Menu({
|
||||||
|
centered = true,
|
||||||
|
y = 2,
|
||||||
|
menuItems = {
|
||||||
|
{ prompt = 'Pickup', event = 'pickup', help = 'Pickup items from this location' },
|
||||||
|
{ prompt = 'Charge cell', event = 'charge', help = 'Recharge this cell' },
|
||||||
|
{ prompt = 'Refill', event = 'refill', help = 'Recharge this cell' },
|
||||||
|
{ prompt = 'Set pickup location', event = 'setPickup', help = 'Recharge this cell' },
|
||||||
|
{ prompt = 'Set recharge location', event = 'setRecharge', help = 'Recharge this cell' },
|
||||||
|
{ prompt = 'Clear', event = 'clear', help = 'Remove this location' },
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
statusBar = UI.StatusBar(),
|
||||||
|
accelerators = {
|
||||||
|
q = 'quit',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
local refillPage = UI.Page({
|
||||||
|
menuBar = UI.MenuBar({
|
||||||
|
y = 1,
|
||||||
|
buttons = {
|
||||||
|
{ text = 'Done', event = 'done', help = 'Pickup items from this location' },
|
||||||
|
{ text = 'Back', event = 'back', help = 'Recharge this cell' },
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
grid1 = UI.ScrollingGrid({
|
||||||
|
columns = {
|
||||||
|
{ heading = 'Name', key = 'name', width = UI.term.width-9 },
|
||||||
|
{ heading = 'Qty', key = 'fQty', width = 5 },
|
||||||
|
},
|
||||||
|
sortColumn = 'name',
|
||||||
|
height = 8,
|
||||||
|
y = 3,
|
||||||
|
}),
|
||||||
|
grid2 = UI.ScrollingGrid({
|
||||||
|
columns = {
|
||||||
|
{ heading = 'Name', key = 'name', width = UI.term.width-9 },
|
||||||
|
{ heading = 'Qty', key = 'qty', width = 5 },
|
||||||
|
},
|
||||||
|
sortColumn = 'name',
|
||||||
|
height = 4,
|
||||||
|
y = 12,
|
||||||
|
}),
|
||||||
|
statusBar = UI.StatusBar(),
|
||||||
|
accelerators = {
|
||||||
|
q = 'quit',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
refillPage.menuBar:add({
|
||||||
|
filter = UI.TextEntry({
|
||||||
|
x = UI.term.width-10,
|
||||||
|
width = 10,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
local function sendCommand(cmd)
|
||||||
|
local socket = Socket.connect(id, 5222)
|
||||||
|
if not socket then
|
||||||
|
mainPage.statusBar:timedStatus('Unable to connect', 3)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
socket:write(cmd)
|
||||||
|
local m = socket:read(3)
|
||||||
|
socket:close()
|
||||||
|
if m then
|
||||||
|
return m.response
|
||||||
|
end
|
||||||
|
mainPage.statusBar:timedStatus('No response', 3)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getPoint()
|
||||||
|
local gpt = GPS.getPoint()
|
||||||
|
if not gpt then
|
||||||
|
mainPage.statusBar:timedStatus('Unable to get location', 3)
|
||||||
|
end
|
||||||
|
return gpt
|
||||||
|
end
|
||||||
|
|
||||||
|
function refillPage:eventHandler(event)
|
||||||
|
|
||||||
|
if event.type == 'grid_select' then
|
||||||
|
|
||||||
|
local item = {
|
||||||
|
name = event.selected.name,
|
||||||
|
id = event.selected.id,
|
||||||
|
dmg = event.selected.dmg,
|
||||||
|
qty = 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
local dialog = UI.Dialog({
|
||||||
|
x = 1,
|
||||||
|
width = UI.term.width,
|
||||||
|
text = UI.Text({ x = 3, y = 3, value = 'Quantity' }),
|
||||||
|
textEntry = UI.TextEntry({ x = 14, y = 3 })
|
||||||
|
})
|
||||||
|
|
||||||
|
dialog.eventHandler = function(self, event)
|
||||||
|
if event.type == 'accept' then
|
||||||
|
local l = tonumber(self.textEntry.value)
|
||||||
|
if l and l <= 1024 and l > 0 then
|
||||||
|
item.qty = self.textEntry.value
|
||||||
|
table.insert(refillPage.grid2.values, item)
|
||||||
|
refillPage.grid2:update()
|
||||||
|
UI:setPreviousPage()
|
||||||
|
else
|
||||||
|
self.statusBar:timedStatus('Invalid Quantity', 3)
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
return UI.Dialog.eventHandler(self, event)
|
||||||
|
end
|
||||||
|
|
||||||
|
dialog.titleBar.title = item.name
|
||||||
|
dialog:setFocus(dialog.textEntry)
|
||||||
|
UI:setPage(dialog)
|
||||||
|
|
||||||
|
elseif event.type == 'text_change' then
|
||||||
|
local text = event.text
|
||||||
|
if #text == 0 then
|
||||||
|
self.grid1.values = self.allItems
|
||||||
|
else
|
||||||
|
self.grid1.values = { }
|
||||||
|
for _,item in pairs(self.allItems) do
|
||||||
|
if string.find(item.lname, text) then
|
||||||
|
table.insert(self.grid1.values, item)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
--self.grid:adjustWidth()
|
||||||
|
self.grid1:update()
|
||||||
|
self.grid1:setIndex(1)
|
||||||
|
self.grid1:draw()
|
||||||
|
|
||||||
|
elseif event.type == 'back' then
|
||||||
|
UI:setPreviousPage()
|
||||||
|
|
||||||
|
elseif event.type == 'done' then
|
||||||
|
UI:setPage(mainPage)
|
||||||
|
local pt = getPoint()
|
||||||
|
if pt then
|
||||||
|
local response = sendCommand({ type = 'refill', entry = { point = pt, items = self.grid2.values } })
|
||||||
|
if response then
|
||||||
|
mainPage.statusBar:timedStatus(response, 3)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif event.type == 'grid_focus_row' then
|
||||||
|
self.statusBar:setStatus(event.selected.id .. ':' .. event.selected.dmg)
|
||||||
|
self.statusBar:draw()
|
||||||
|
end
|
||||||
|
|
||||||
|
return UI.Page.eventHandler(self, event)
|
||||||
|
end
|
||||||
|
|
||||||
|
function refillPage:enable()
|
||||||
|
for _,item in pairs(self.allItems) do
|
||||||
|
item.lname = string.lower(item.name)
|
||||||
|
item.fQty = Util.toBytes(item.qty)
|
||||||
|
end
|
||||||
|
|
||||||
|
self.grid1:setValues(self.allItems)
|
||||||
|
|
||||||
|
self.menuBar.filter.value = ''
|
||||||
|
self.menuBar.filter.pos = 1
|
||||||
|
self:setFocus(self.menuBar.filter)
|
||||||
|
UI.Page.enable(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
function mainPage:eventHandler(event)
|
||||||
|
|
||||||
|
if event.type == 'quit' then
|
||||||
|
Event.exitPullEvents()
|
||||||
|
|
||||||
|
elseif event.type == 'refill' then
|
||||||
|
local response = sendCommand({ type = 'items' })
|
||||||
|
if response then
|
||||||
|
refillPage.allItems = response
|
||||||
|
refillPage.grid2:setValues({ })
|
||||||
|
UI:setPage(refillPage)
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif event.type == 'pickup' or event.type == 'setPickup' or
|
||||||
|
event.type == 'setRecharge' or event.type == 'charge' or
|
||||||
|
event.type == 'clear' then
|
||||||
|
local pt = getPoint()
|
||||||
|
if pt then
|
||||||
|
local response = sendCommand({ type = event.type, point = pt })
|
||||||
|
if response then
|
||||||
|
self.statusBar:timedStatus(response, 3)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
return UI.Page.eventHandler(self, event)
|
||||||
|
end
|
||||||
|
|
||||||
|
local args = { ... }
|
||||||
|
if #args == 1 then
|
||||||
|
id = tonumber(args[1])
|
||||||
|
end
|
||||||
|
|
||||||
|
if not id then
|
||||||
|
error('Syntax: pickupRemote <turtle ID>')
|
||||||
|
end
|
||||||
|
|
||||||
|
UI:setPage(mainPage)
|
||||||
|
|
||||||
|
Event.pullEvents()
|
||||||
|
UI.term:reset()
|
||||||
538
apps/recorder.lua
Normal file
538
apps/recorder.lua
Normal file
@@ -0,0 +1,538 @@
|
|||||||
|
-- +---------------------+------------+---------------------+
|
||||||
|
-- | | | |
|
||||||
|
-- | | RecGif | |
|
||||||
|
-- | | | |
|
||||||
|
-- +---------------------+------------+---------------------+
|
||||||
|
|
||||||
|
local version = "Version 1.1.6"
|
||||||
|
|
||||||
|
-- Records your terminal and saves the result as an animating GIF.
|
||||||
|
-- http://www.computercraft.info/forums2/index.php?/topic/24840-recgif/
|
||||||
|
|
||||||
|
-- ----------------------------------------------------------
|
||||||
|
|
||||||
|
-- Original code by Bomb Bloke
|
||||||
|
-- Modified to integrate with opus os
|
||||||
|
|
||||||
|
requireInjector(getfenv(1))
|
||||||
|
|
||||||
|
local Util = require('util')
|
||||||
|
|
||||||
|
local recTerm, oldTerm, arg, showInput, skipLast, lastDelay, curInput = {}, Util.shallowCopy(multishell.term), {...}, false, false, 2, ""
|
||||||
|
local curBlink, oldBlink, tTerm, buffer, colourNum, xPos, yPos, oldXPos, oldYPos, tCol, bCol, xSize, ySize = false, false, {}, {}, {}, 1, 1, 1, 1, colours.white, colours.black, oldTerm.getSize()
|
||||||
|
local greys, buttons = {["0"] = true, ["7"] = true, ["8"] = true, ["f"] = true}, {"l", "r", "m"}
|
||||||
|
local charW, charH, chars, resp
|
||||||
|
local filename
|
||||||
|
|
||||||
|
local calls = { }
|
||||||
|
local curCalls = { delay = 0 }
|
||||||
|
local callListCount = 0
|
||||||
|
local callCount = 0
|
||||||
|
|
||||||
|
local function showSyntax()
|
||||||
|
print('Gif Recorder by Bomb Bloke\n')
|
||||||
|
print('Syntax: recGif [-i] [-s] [-ld:<delay>] filename')
|
||||||
|
print(' -i : show input')
|
||||||
|
print(' -s : skip last')
|
||||||
|
print(' -ld : last delay')
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = #arg, 1, -1 do
|
||||||
|
local curArg = arg[i]:lower()
|
||||||
|
|
||||||
|
if curArg == "-i" then
|
||||||
|
showInput, ySize = true, ySize + 1
|
||||||
|
table.remove(arg, i)
|
||||||
|
elseif curArg == "-s" then
|
||||||
|
skipLast = true
|
||||||
|
table.remove(arg, i)
|
||||||
|
elseif curArg:sub(1, 4) == "-ld:" then
|
||||||
|
curArg = tonumber(curArg:sub(5))
|
||||||
|
if curArg then lastDelay = curArg end
|
||||||
|
table.remove(arg, i)
|
||||||
|
elseif curArg == '-?' then
|
||||||
|
showSyntax()
|
||||||
|
return
|
||||||
|
elseif i ~= #arg then
|
||||||
|
showSyntax()
|
||||||
|
printError('\nInvalid argument')
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
print('Press control-p to stop recording')
|
||||||
|
|
||||||
|
local filename = arg[#arg]
|
||||||
|
if not filename then
|
||||||
|
print('Enter file name:')
|
||||||
|
filename = read()
|
||||||
|
end
|
||||||
|
|
||||||
|
if #filename == 0 then
|
||||||
|
showSyntax()
|
||||||
|
print()
|
||||||
|
error('Invalid file name')
|
||||||
|
end
|
||||||
|
|
||||||
|
print('Initializing...')
|
||||||
|
|
||||||
|
-- don't pollute global env
|
||||||
|
-- convert these to require style apis
|
||||||
|
local function loadAPI(url, env)
|
||||||
|
local apiEnv = Util.shallowCopy(env)
|
||||||
|
apiEnv.shell = nil
|
||||||
|
apiEnv.multishell = nil
|
||||||
|
setmetatable(apiEnv, { __index = _G })
|
||||||
|
local fn = Util.loadUrl(url, apiEnv)
|
||||||
|
fn()
|
||||||
|
return apiEnv
|
||||||
|
end
|
||||||
|
|
||||||
|
bbpack = loadAPI('http://pastebin.com/raw/PdrJjb5S', getfenv(1))
|
||||||
|
GIF = loadAPI('http://pastebin.com/raw/5uk9uRjC', getfenv(1))
|
||||||
|
|
||||||
|
Util.runUrl(getfenv(1), 'http://pastebin.com/raw/cUYTGbpb', 'get', 'Y0eLUPtr')
|
||||||
|
|
||||||
|
local function snooze()
|
||||||
|
local myEvent = tostring({})
|
||||||
|
os.queueEvent(myEvent)
|
||||||
|
os.pullEvent(myEvent)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function safeString(text)
|
||||||
|
local newText = {}
|
||||||
|
|
||||||
|
for i = 1, #text do
|
||||||
|
local val = text:byte(i)
|
||||||
|
newText[i] = (val > 31 and val < 127) and val or 63
|
||||||
|
end
|
||||||
|
|
||||||
|
return string.char(unpack(newText))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function safeCol(text, subst)
|
||||||
|
local newText = {}
|
||||||
|
|
||||||
|
for i = 1, #text do
|
||||||
|
local val = text:sub(i, i)
|
||||||
|
newText[i] = greys[val] and val or subst
|
||||||
|
end
|
||||||
|
|
||||||
|
return table.concat(newText)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Build a terminal that records stuff:
|
||||||
|
|
||||||
|
recTerm = multishell.term
|
||||||
|
|
||||||
|
for key, func in pairs(oldTerm) do
|
||||||
|
recTerm[key] = function(...)
|
||||||
|
local result = { func(...) }
|
||||||
|
|
||||||
|
if callCount == 0 then
|
||||||
|
os.queueEvent('capture_frame')
|
||||||
|
end
|
||||||
|
callCount = callCount + 1
|
||||||
|
curCalls[callCount] = { key, ... }
|
||||||
|
return unpack(result)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local tabId = multishell.getCurrent()
|
||||||
|
|
||||||
|
multishell.addHotkey(25, function()
|
||||||
|
os.queueEvent('recorder_stop')
|
||||||
|
end)
|
||||||
|
|
||||||
|
local tabs = multishell.getTabs()
|
||||||
|
for _,tab in pairs(tabs) do
|
||||||
|
if tab.isOverview then
|
||||||
|
multishell.hideTab(tabId)
|
||||||
|
multishell.setFocus(tab.tabId)
|
||||||
|
os.queueEvent('term_resize')
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local curTime = os.clock() - 1
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local event = { os.pullEventRaw() }
|
||||||
|
|
||||||
|
if event[1] == 'recorder_stop' or event[1] == 'terminate' then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
if event[1] == 'capture_frame' then
|
||||||
|
local newTime = os.clock()
|
||||||
|
|
||||||
|
if callListCount > 0 then
|
||||||
|
calls[callListCount].delay = (newTime - curTime)
|
||||||
|
end
|
||||||
|
|
||||||
|
curTime = newTime
|
||||||
|
callListCount = callListCount + 1
|
||||||
|
calls[callListCount] = curCalls
|
||||||
|
|
||||||
|
curCalls, callCount = { delay = 0 }, 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
multishell.removeHotkey(25)
|
||||||
|
|
||||||
|
for k,fn in pairs(oldTerm) do
|
||||||
|
multishell.term[k] = fn
|
||||||
|
end
|
||||||
|
|
||||||
|
multishell.unhideTab(tabId)
|
||||||
|
multishell.setFocus(tabId)
|
||||||
|
|
||||||
|
if #calls[#calls] == 0 then calls[#calls] = nil end
|
||||||
|
if skipLast and #calls > 1 then calls[#calls] = nil end
|
||||||
|
|
||||||
|
calls[#calls].delay = lastDelay
|
||||||
|
|
||||||
|
print(string.format("Encoding %d frames...", #calls))
|
||||||
|
--Util.writeTable('tmp/raw.txt', calls)
|
||||||
|
|
||||||
|
-- Perform a quick re-parse of the recorded data (adding frames for when the cursor blinks):
|
||||||
|
|
||||||
|
do
|
||||||
|
local callListCount, tempCalls, blink, oldBlink, curBlink, blinkDelay = 1, {}, false, false, true, 0
|
||||||
|
|
||||||
|
for i = 1, #calls - 1 do
|
||||||
|
curCalls = calls[i]
|
||||||
|
tempCalls[callListCount] = curCalls
|
||||||
|
for j = 1, #curCalls do if curCalls[j][1] == "setCursorBlink" then blink = curCalls[j][2] end end
|
||||||
|
|
||||||
|
if blink then
|
||||||
|
if blinkDelay == 0 then
|
||||||
|
curCalls[#curCalls + 1] = {"toggleCur", curBlink}
|
||||||
|
blinkDelay, curBlink = 0.4, not curBlink
|
||||||
|
end
|
||||||
|
|
||||||
|
while tempCalls[callListCount].delay > blinkDelay do
|
||||||
|
local remainder = tempCalls[callListCount].delay - blinkDelay
|
||||||
|
tempCalls[callListCount].delay = blinkDelay
|
||||||
|
callListCount = callListCount + 1
|
||||||
|
tempCalls[callListCount] = {{"toggleCur", curBlink}, ["delay"] = remainder}
|
||||||
|
blinkDelay, curBlink = 0.4, not curBlink
|
||||||
|
end
|
||||||
|
|
||||||
|
blinkDelay = blinkDelay - tempCalls[callListCount].delay
|
||||||
|
else
|
||||||
|
if oldBlink then curCalls[#curCalls + 1] = {"toggleCur", false} end
|
||||||
|
blinkDelay = (curCalls.delay - blinkDelay) % 0.4
|
||||||
|
end
|
||||||
|
|
||||||
|
callListCount, oldBlink = callListCount + 1, blink
|
||||||
|
end
|
||||||
|
|
||||||
|
tempCalls[callListCount] = calls[#calls]
|
||||||
|
tempCalls[callListCount][#tempCalls[callListCount] + 1] = {"toggleCur", false}
|
||||||
|
|
||||||
|
calls, curCalls = tempCalls, nil
|
||||||
|
end
|
||||||
|
|
||||||
|
snooze()
|
||||||
|
|
||||||
|
-- Load font data:
|
||||||
|
do
|
||||||
|
local ascii, counter = GIF.toPaintutils(GIF.flattenGIF(GIF.loadGIF("ascii.gif"))), 0
|
||||||
|
local newFont, ybump, xbump = #ascii ~= #ascii[1], 0, 0
|
||||||
|
charW, charH, chars = newFont and #ascii[1] / 16 or #ascii[1] * 3 / 64, #ascii / 16, {}
|
||||||
|
|
||||||
|
for yy = 0, newFont and 15 or 7 do
|
||||||
|
for xx = 0, 15 do
|
||||||
|
local newChar, length = {}, 0
|
||||||
|
|
||||||
|
-- Place in 2d grid of bools:
|
||||||
|
for y = 1, charH do
|
||||||
|
local newRow = {}
|
||||||
|
|
||||||
|
for x = 1, charW do
|
||||||
|
local set = ascii[y + ybump][x + xbump] == 1
|
||||||
|
if set and x > length then length = x end
|
||||||
|
newRow[x] = set
|
||||||
|
end
|
||||||
|
|
||||||
|
newChar[y] = newRow
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Center:
|
||||||
|
if not newFont then for y = 1, charH do for x = 1, math.floor((charW - length) / 2) do table.insert(newChar[y], 1, false) end end end
|
||||||
|
|
||||||
|
chars[counter] = newChar
|
||||||
|
counter, xbump = counter + 1, xbump + (newFont and charW or charH)
|
||||||
|
end
|
||||||
|
xbump, ybump = 0, ybump + charH
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
snooze()
|
||||||
|
|
||||||
|
-- Terminal data translation:
|
||||||
|
|
||||||
|
do
|
||||||
|
local hex, counter = "0123456789abcdef", 1
|
||||||
|
|
||||||
|
for i = 1, 16 do
|
||||||
|
colourNum[counter] = hex:sub(i, i)
|
||||||
|
counter = counter * 2
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for y = 1, ySize do
|
||||||
|
buffer[y] = {}
|
||||||
|
for x = 1, xSize do buffer[y][x] = {" ", colourNum[tCol], colourNum[bCol]} end
|
||||||
|
end
|
||||||
|
|
||||||
|
if showInput then for x = 1, xSize do buffer[ySize][x][3] = colourNum[colours.lightGrey] end end
|
||||||
|
|
||||||
|
tTerm.blit = function(text, fgCol, bgCol)
|
||||||
|
if xPos > xSize or xPos + #text - 1 < 1 or yPos < 1 or yPos > ySize then return end
|
||||||
|
|
||||||
|
if not _HOST then text = safeString(text) end
|
||||||
|
|
||||||
|
if not term.isColour() then
|
||||||
|
fgCol = safeCol(fgCol, "0")
|
||||||
|
bgCol = safeCol(bgCol, "f")
|
||||||
|
end
|
||||||
|
|
||||||
|
if xPos < 1 then
|
||||||
|
text = text:sub(2 - xPos)
|
||||||
|
fgCol = fgCol:sub(2 - xPos)
|
||||||
|
bgCol = bgCol:sub(2 - xPos)
|
||||||
|
xPos = 1
|
||||||
|
end
|
||||||
|
|
||||||
|
if xPos + #text - 1 > xSize then
|
||||||
|
text = text:sub(1, xSize - xPos + 1)
|
||||||
|
fgCol = fgCol:sub(1, xSize - xPos + 1)
|
||||||
|
bgCol = bgCol:sub(1, xSize - xPos + 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
for x = 1, #text do
|
||||||
|
buffer[yPos][xPos + x - 1][1] = text:sub(x, x)
|
||||||
|
buffer[yPos][xPos + x - 1][2] = fgCol:sub(x, x)
|
||||||
|
buffer[yPos][xPos + x - 1][3] = bgCol:sub(x, x)
|
||||||
|
end
|
||||||
|
|
||||||
|
xPos = xPos + #text
|
||||||
|
end
|
||||||
|
|
||||||
|
tTerm.write = function(text)
|
||||||
|
text = tostring(text)
|
||||||
|
tTerm.blit(text, string.rep(colourNum[tCol], #text), string.rep(colourNum[bCol], #text))
|
||||||
|
end
|
||||||
|
|
||||||
|
tTerm.clearLine = function()
|
||||||
|
local oldXPos = xPos
|
||||||
|
|
||||||
|
xPos = 1
|
||||||
|
tTerm.write(string.rep(" ", xSize))
|
||||||
|
|
||||||
|
xPos = oldXPos
|
||||||
|
end
|
||||||
|
|
||||||
|
tTerm.clear = function()
|
||||||
|
local oldXPos, oldYPos = xPos, yPos
|
||||||
|
|
||||||
|
for y = 1, ySize do
|
||||||
|
xPos, yPos = 1, y
|
||||||
|
tTerm.write(string.rep(" ", xSize))
|
||||||
|
end
|
||||||
|
|
||||||
|
xPos, yPos = oldXPos, oldYPos
|
||||||
|
end
|
||||||
|
|
||||||
|
tTerm.setCursorPos = function(x, y)
|
||||||
|
xPos, yPos = math.floor(x), math.floor(y)
|
||||||
|
end
|
||||||
|
|
||||||
|
tTerm.setTextColour = function(col)
|
||||||
|
tCol = col
|
||||||
|
end
|
||||||
|
|
||||||
|
tTerm.setTextColor = function(col)
|
||||||
|
tCol = col
|
||||||
|
end
|
||||||
|
|
||||||
|
tTerm.setBackgroundColour = function(col)
|
||||||
|
bCol = col
|
||||||
|
end
|
||||||
|
|
||||||
|
tTerm.setBackgroundColor = function(col)
|
||||||
|
bCol = col
|
||||||
|
end
|
||||||
|
|
||||||
|
tTerm.scroll = function(lines)
|
||||||
|
if math.abs(lines) < ySize then
|
||||||
|
local oldXPos, oldYPos = xPos, yPos
|
||||||
|
|
||||||
|
for y = 1, ySize do
|
||||||
|
if y + lines > 0 and y + lines <= ySize then
|
||||||
|
for x = 1, xSize do
|
||||||
|
xPos, yPos = x, y
|
||||||
|
tTerm.blit(buffer[y + lines][x][1], buffer[y + lines][x][2], buffer[y + lines][x][3])
|
||||||
|
end
|
||||||
|
else
|
||||||
|
yPos = y
|
||||||
|
tTerm.clearLine()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
xPos, yPos = oldXPos, oldYPos
|
||||||
|
else tTerm.clear() end
|
||||||
|
end
|
||||||
|
|
||||||
|
tTerm.toggleCur = function(newBlink)
|
||||||
|
curBlink = newBlink
|
||||||
|
end
|
||||||
|
|
||||||
|
tTerm.newInput = function(input)
|
||||||
|
local oldTC, oldBC, oldX, oldY = tCol, bCol, xPos, yPos
|
||||||
|
tCol, bCol, xPos, yPos, ySize, input = colours.grey, colours.lightGrey, 1, ySize + 1, ySize + 1, input .. " "
|
||||||
|
|
||||||
|
while #curInput + #input + 1 > xSize do curInput = curInput:sub(curInput:find(" ") + 1) end
|
||||||
|
curInput = curInput .. input .. " "
|
||||||
|
tTerm.clearLine()
|
||||||
|
tTerm.write(curInput)
|
||||||
|
|
||||||
|
tCol, bCol, xPos, yPos, ySize = oldTC, oldBC, oldX, oldY, ySize - 1
|
||||||
|
end
|
||||||
|
|
||||||
|
tTerm.key = function(key)
|
||||||
|
tTerm.newInput((not keys.getName(key)) and "unknownKey" or keys.getName(key))
|
||||||
|
end
|
||||||
|
|
||||||
|
tTerm.mouse_click = function(button, x, y)
|
||||||
|
tTerm.newInput(buttons[button] .. "C@" .. tostring(x) .. "x" .. tostring(y))
|
||||||
|
end
|
||||||
|
|
||||||
|
local image = {["width"] = xSize * charW, ["height"] = ySize * charH}
|
||||||
|
|
||||||
|
for i = 1, #calls do
|
||||||
|
local xMin, yMin, xMax, yMax, oldBuffer, curCalls, changed = xSize + 1, ySize + 1, 0, 0, {}, calls[i], false
|
||||||
|
calls[i] = nil
|
||||||
|
|
||||||
|
for y = 1, ySize do
|
||||||
|
oldBuffer[y] = {}
|
||||||
|
for x = 1, xSize do oldBuffer[y][x] = {buffer[y][x][1], buffer[y][x][2], buffer[y][x][3], buffer[y][x][4]} end
|
||||||
|
end
|
||||||
|
|
||||||
|
snooze()
|
||||||
|
|
||||||
|
if showInput then ySize = ySize - 1 end
|
||||||
|
for j = 1, #curCalls do if tTerm[curCalls[j][1]] then tTerm[curCalls[j][1]](unpack(curCalls[j], 2)) end end
|
||||||
|
if showInput then ySize = ySize + 1 end
|
||||||
|
|
||||||
|
if i > 1 then
|
||||||
|
for yy = 1, ySize do for xx = 1, xSize do if buffer[yy][xx][1] ~= oldBuffer[yy][xx][1] or (buffer[yy][xx][2] ~= oldBuffer[yy][xx][2] and buffer[yy][xx][1] ~= " ") or buffer[yy][xx][3] ~= oldBuffer[yy][xx][3] then
|
||||||
|
changed = true
|
||||||
|
if xx < xMin then xMin = xx end
|
||||||
|
if xx > xMax then xMax = xx end
|
||||||
|
if yy < yMin then yMin = yy end
|
||||||
|
if yy > yMax then yMax = yy end
|
||||||
|
end end end
|
||||||
|
else xMin, yMin, xMax, yMax, changed = 1, 1, xSize, ySize, true end
|
||||||
|
|
||||||
|
if oldBlink and (xPos ~= oldXPos or yPos ~= oldYPos or not curBlink) and oldXPos > 0 and oldYPos > 0 and oldXPos <= xSize and oldYPos <= ySize then
|
||||||
|
changed = true
|
||||||
|
if oldXPos < xMin then xMin = oldXPos end
|
||||||
|
if oldXPos > xMax then xMax = oldXPos end
|
||||||
|
if oldYPos < yMin then yMin = oldYPos end
|
||||||
|
if oldYPos > yMax then yMax = oldYPos end
|
||||||
|
buffer[oldYPos][oldXPos][4] = false
|
||||||
|
end
|
||||||
|
|
||||||
|
if curBlink and (xPos ~= oldXPos or yPos ~= oldYPos or not oldBlink) and xPos > 0 and yPos > 0 and xPos <= xSize and yPos <= ySize then
|
||||||
|
changed = true
|
||||||
|
if xPos < xMin then xMin = xPos end
|
||||||
|
if xPos > xMax then xMax = xPos end
|
||||||
|
if yPos < yMin then yMin = yPos end
|
||||||
|
if yPos > yMax then yMax = yPos end
|
||||||
|
buffer[yPos][xPos][4] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
oldBlink, oldXPos, oldYPos = curBlink, xPos, yPos
|
||||||
|
|
||||||
|
local thisFrame = {
|
||||||
|
["xstart"] = (xMin - 1) * charW,
|
||||||
|
["ystart"] = (yMin - 1) * charH,
|
||||||
|
["xend"] = (xMax - xMin + 1) * charW,
|
||||||
|
["yend"] = (yMax - yMin + 1) * charH,
|
||||||
|
["delay"] = curCalls.delay,
|
||||||
|
["disposal"] = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
for y = 1, (yMax - yMin + 1) * charH do
|
||||||
|
local row = {}
|
||||||
|
for x = 1, (xMax - xMin + 1) * charW do row[x] = " " end
|
||||||
|
thisFrame[y] = row
|
||||||
|
end
|
||||||
|
|
||||||
|
snooze()
|
||||||
|
|
||||||
|
for yy = yMin, yMax do
|
||||||
|
local yBump = (yy - yMin) * charH
|
||||||
|
|
||||||
|
for xx = xMin, xMax do if buffer[yy][xx][1] ~= oldBuffer[yy][xx][1] or (buffer[yy][xx][2] ~= oldBuffer[yy][xx][2] and buffer[yy][xx][1] ~= " ") or buffer[yy][xx][3] ~= oldBuffer[yy][xx][3] or buffer[yy][xx][4] ~= oldBuffer[yy][xx][4] or i == 1 then
|
||||||
|
local thisChar, thisT, thisB, xBump = chars[buffer[yy][xx][1]:byte()], buffer[yy][xx][2], buffer[yy][xx][3], (xx - xMin) * charW
|
||||||
|
if thisChar then
|
||||||
|
for y = 1, charH do
|
||||||
|
for x = 1, charW do
|
||||||
|
local ch = thisChar[y][x] and thisT or thisB
|
||||||
|
thisFrame[y + yBump][x + xBump] = ch
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if buffer[yy][xx][4] then
|
||||||
|
thisT, thisChar = colourNum[tCol], chars[95]
|
||||||
|
for y = 1, charH do for x = 1, charW do if thisChar[y][x] then thisFrame[y + yBump][x + xBump] = thisT end end end
|
||||||
|
end
|
||||||
|
end end
|
||||||
|
|
||||||
|
for y = yBump + 1, yBump + charH do
|
||||||
|
local skip, chars, row = 0, {}, {}
|
||||||
|
|
||||||
|
for x = 1, #thisFrame[y] do
|
||||||
|
if thisFrame[y][x] == " " then
|
||||||
|
if #chars > 0 then
|
||||||
|
row[#row + 1] = table.concat(chars)
|
||||||
|
chars = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
skip = skip + 1
|
||||||
|
else
|
||||||
|
if skip > 0 then
|
||||||
|
row[#row + 1] = skip
|
||||||
|
skip = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
chars[#chars + 1] = thisFrame[y][x]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if #chars > 0 then row[#row + 1] = table.concat(chars) end
|
||||||
|
thisFrame[y] = row
|
||||||
|
end
|
||||||
|
|
||||||
|
snooze()
|
||||||
|
end
|
||||||
|
|
||||||
|
if changed then
|
||||||
|
image[#image + 1] = thisFrame
|
||||||
|
else
|
||||||
|
image[#image].delay = image[#image].delay + curCalls.delay
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
buffer = nil
|
||||||
|
|
||||||
|
GIF.saveGIF(image, filename)
|
||||||
|
|
||||||
|
fs.delete('ascii.gif')
|
||||||
|
|
||||||
|
print("Encode complete")
|
||||||
517
apps/shapes.lua
Normal file
517
apps/shapes.lua
Normal file
@@ -0,0 +1,517 @@
|
|||||||
|
requireInjector(getfenv(1))
|
||||||
|
|
||||||
|
local GPS = require('gps')
|
||||||
|
local Socket = require('socket')
|
||||||
|
local UI = require('ui')
|
||||||
|
local Util = require('util')
|
||||||
|
|
||||||
|
multishell.setTitle(multishell.getCurrent(), 'Shapes')
|
||||||
|
|
||||||
|
local args = { ... }
|
||||||
|
local turtleId = args[1] or error('Supply turtle ID')
|
||||||
|
turtleId = tonumber(turtleId)
|
||||||
|
|
||||||
|
local script = [[
|
||||||
|
|
||||||
|
requireInjector(getfenv(1))
|
||||||
|
|
||||||
|
local GPS = require('gps')
|
||||||
|
local ChestAdapter = require('chestAdapter18')
|
||||||
|
local Point = require('point')
|
||||||
|
local Util = require('util')
|
||||||
|
|
||||||
|
local itemAdapter
|
||||||
|
|
||||||
|
function dumpInventory()
|
||||||
|
|
||||||
|
for i = 1, 16 do
|
||||||
|
local qty = turtle.getItemCount(i)
|
||||||
|
if qty > 0 then
|
||||||
|
itemAdapter:insert(i, qty)
|
||||||
|
end
|
||||||
|
if turtle.getItemCount(i) ~= 0 then
|
||||||
|
print('Adapter is full or missing - make space or replace')
|
||||||
|
print('Press enter to continue')
|
||||||
|
read()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
turtle.select(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function refuel()
|
||||||
|
while turtle.getFuelLevel() < 4000 do
|
||||||
|
print('Refueling')
|
||||||
|
turtle.select(1)
|
||||||
|
|
||||||
|
itemAdapter:provide({ name = 'minecraft:coal', damage = 0 }, 64, 1)
|
||||||
|
if turtle.getItemCount(1) == 0 then
|
||||||
|
print('Out of fuel, add fuel to chest/ME system')
|
||||||
|
turtle.status = 'waiting'
|
||||||
|
os.sleep(5)
|
||||||
|
else
|
||||||
|
turtle.refuel(64)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function goto(pt)
|
||||||
|
while not turtle.gotoPoint(pt) do
|
||||||
|
print('stuck')
|
||||||
|
os.sleep(5)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function pathTo(pt)
|
||||||
|
while not turtle.pathfind(pt) do
|
||||||
|
print('stuck')
|
||||||
|
os.sleep(5)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function resupply()
|
||||||
|
|
||||||
|
if data.suppliesPt then
|
||||||
|
pathTo(data.suppliesPt)
|
||||||
|
|
||||||
|
itemAdapter = ChestAdapter({ direction = 'up', wrapSide = 'bottom' })
|
||||||
|
dumpInventory()
|
||||||
|
refuel()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function makePlane(y)
|
||||||
|
local pt = { x = math.min(data.startPt.x, data.endPt.x),
|
||||||
|
ex = math.max(data.startPt.x, data.endPt.x),
|
||||||
|
z = math.min(data.startPt.z, data.endPt.z),
|
||||||
|
ez = math.max(data.startPt.z, data.endPt.z) }
|
||||||
|
|
||||||
|
local blocks = { }
|
||||||
|
for z = pt.z, pt.ez do
|
||||||
|
for x = pt.x, pt.ex do
|
||||||
|
table.insert(blocks, { x = x, y = y, z = z })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return blocks
|
||||||
|
end
|
||||||
|
|
||||||
|
local function optimizeRoute(plane, ptb)
|
||||||
|
|
||||||
|
local maxDistance = 99999999
|
||||||
|
|
||||||
|
local function getNearestNeighbor(p, pt, threshold)
|
||||||
|
local key, block, heading
|
||||||
|
local moves = maxDistance
|
||||||
|
|
||||||
|
local function getMoves(b, k)
|
||||||
|
local distance = math.abs(pt.x - b.x) + math.abs(pt.z - b.z)
|
||||||
|
|
||||||
|
if distance < moves then
|
||||||
|
-- this operation is expensive - only run if distance is close
|
||||||
|
local c, h = Point.calculateMoves(pt, b, distance)
|
||||||
|
if c < moves then
|
||||||
|
block = b
|
||||||
|
key = k
|
||||||
|
moves = c
|
||||||
|
heading = h
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function blockReady(b)
|
||||||
|
return not b.u
|
||||||
|
end
|
||||||
|
|
||||||
|
local mid = pt.index
|
||||||
|
local forward = mid + 1
|
||||||
|
local backward = mid - 1
|
||||||
|
while forward <= #p or backward > 0 do
|
||||||
|
if forward <= #p then
|
||||||
|
local b = p[forward]
|
||||||
|
if blockReady(b) then
|
||||||
|
getMoves(b, forward)
|
||||||
|
if moves <= threshold then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
if moves < maxDistance and math.abs(b.z - pt.z) > moves and pt.index > 0 then
|
||||||
|
forward = #p
|
||||||
|
end
|
||||||
|
end
|
||||||
|
forward = forward + 1
|
||||||
|
end
|
||||||
|
if backward > 0 then
|
||||||
|
local b = p[backward]
|
||||||
|
if blockReady(b) then
|
||||||
|
getMoves(b, backward)
|
||||||
|
if moves <= threshold then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
if moves < maxDistance and math.abs(pt.z - b.z) > moves then
|
||||||
|
backward = 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
backward = backward - 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
pt.x = block.x
|
||||||
|
pt.z = block.z
|
||||||
|
pt.y = block.y
|
||||||
|
pt.heading = heading
|
||||||
|
pt.index = key
|
||||||
|
block.u = true
|
||||||
|
return block
|
||||||
|
end
|
||||||
|
|
||||||
|
local throttle = Util.throttle()
|
||||||
|
local t = { }
|
||||||
|
ptb.index = 0
|
||||||
|
local threshold = 0
|
||||||
|
for i = 1, #plane do
|
||||||
|
local b = getNearestNeighbor(plane, ptb, threshold)
|
||||||
|
table.insert(t, b)
|
||||||
|
throttle()
|
||||||
|
threshold = 1
|
||||||
|
end
|
||||||
|
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
local function clear()
|
||||||
|
|
||||||
|
local pt = Util.shallowCopy(data.startPt)
|
||||||
|
pt.y = math.min(data.startPt.y, data.endPt.y)
|
||||||
|
pt.heading = 0
|
||||||
|
|
||||||
|
local osy = pt.y
|
||||||
|
local sy = osy + 1
|
||||||
|
local ey = math.max(data.startPt.y, data.endPt.y)
|
||||||
|
local firstPlane = true
|
||||||
|
|
||||||
|
resupply()
|
||||||
|
|
||||||
|
while true do
|
||||||
|
|
||||||
|
if sy > ey then
|
||||||
|
sy = ey
|
||||||
|
end
|
||||||
|
|
||||||
|
local plane = makePlane(sy)
|
||||||
|
plane = optimizeRoute(plane, pt)
|
||||||
|
|
||||||
|
if firstPlane then
|
||||||
|
turtle.pathfind(plane[1])
|
||||||
|
turtle.setPolicy(turtle.policies.digAttack)
|
||||||
|
firstPlane = false
|
||||||
|
end
|
||||||
|
|
||||||
|
for _,b in ipairs(plane) do
|
||||||
|
turtle.gotoPoint(b)
|
||||||
|
if sy < ey then
|
||||||
|
turtle.digUp()
|
||||||
|
end
|
||||||
|
if sy > osy then
|
||||||
|
turtle.digDown()
|
||||||
|
end
|
||||||
|
if turtle.abort then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if turtle.abort then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
if sy + 1 >= ey then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
sy = sy + 3
|
||||||
|
end
|
||||||
|
turtle.setPolicy(turtle.policies.none)
|
||||||
|
resupply()
|
||||||
|
end
|
||||||
|
|
||||||
|
turtle.run(function()
|
||||||
|
turtle.status = 'Clearing'
|
||||||
|
|
||||||
|
if turtle.enableGPS() then
|
||||||
|
|
||||||
|
local pt = Util.shallowCopy(turtle.point)
|
||||||
|
local s, m = pcall(clear)
|
||||||
|
pathTo(pt)
|
||||||
|
|
||||||
|
if not s and m then
|
||||||
|
error(m)
|
||||||
|
read()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
]]
|
||||||
|
|
||||||
|
local levelScript = [[
|
||||||
|
|
||||||
|
requireInjector(getfenv(1))
|
||||||
|
|
||||||
|
local Point = require('point')
|
||||||
|
local Util = require('util')
|
||||||
|
|
||||||
|
local checkedNodes = { }
|
||||||
|
local nodes = { }
|
||||||
|
local box = { }
|
||||||
|
|
||||||
|
local function inBox(pt, box)
|
||||||
|
return pt.x >= box.x and
|
||||||
|
pt.y >= box.y and
|
||||||
|
pt.z >= box.z and
|
||||||
|
pt.x <= box.ex and
|
||||||
|
pt.y <= box.ey and
|
||||||
|
pt.z <= box.ez
|
||||||
|
end
|
||||||
|
|
||||||
|
local function toKey(pt)
|
||||||
|
return table.concat({ pt.x, pt.y, pt.z }, ':')
|
||||||
|
end
|
||||||
|
|
||||||
|
local function addNode(node)
|
||||||
|
|
||||||
|
for i = 0, 5 do
|
||||||
|
local hi = turtle.getHeadingInfo(i)
|
||||||
|
local testNode = { x = node.x + hi.xd, y = node.y + hi.yd, z = node.z + hi.zd }
|
||||||
|
|
||||||
|
if inBox(testNode, box) then
|
||||||
|
local key = toKey(testNode)
|
||||||
|
if not checkedNodes[key] then
|
||||||
|
nodes[key] = testNode
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function dig(action)
|
||||||
|
|
||||||
|
local directions = {
|
||||||
|
top = 'up',
|
||||||
|
bottom = 'down',
|
||||||
|
}
|
||||||
|
|
||||||
|
-- convert to up, down, north, south, east, west
|
||||||
|
local direction = directions[action.side] or
|
||||||
|
turtle.getHeadingInfo(turtle.point.heading).direction
|
||||||
|
|
||||||
|
local hi = turtle.getHeadingInfo(direction)
|
||||||
|
local node = { x = turtle.point.x + hi.xd, y = turtle.point.y + hi.yd, z = turtle.point.z + hi.zd }
|
||||||
|
if inBox(node, box) then
|
||||||
|
|
||||||
|
local key = toKey(node)
|
||||||
|
checkedNodes[key] = true
|
||||||
|
nodes[key] = nil
|
||||||
|
|
||||||
|
if action.dig() then
|
||||||
|
addNode(node)
|
||||||
|
repeat until not action.dig() -- sand, etc
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function move(action)
|
||||||
|
if action == 'turn' then
|
||||||
|
dig(turtle.getAction('forward'))
|
||||||
|
elseif action == 'up' then
|
||||||
|
dig(turtle.getAction('up'))
|
||||||
|
elseif action == 'down' then
|
||||||
|
dig(turtle.getAction('down'))
|
||||||
|
elseif action == 'back' then
|
||||||
|
dig(turtle.getAction('up'))
|
||||||
|
dig(turtle.getAction('down'))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getAdjacentPoint(pt)
|
||||||
|
local t = { }
|
||||||
|
table.insert(t, pt)
|
||||||
|
for i = 0, 5 do
|
||||||
|
local hi = turtle.getHeadingInfo(i)
|
||||||
|
local heading
|
||||||
|
if i < 4 then
|
||||||
|
heading = (hi.heading + 2) % 4
|
||||||
|
end
|
||||||
|
table.insert(t, { x = pt.x + hi.xd, z = pt.z + hi.zd, y = pt.y + hi.yd, heading = heading })
|
||||||
|
end
|
||||||
|
|
||||||
|
return Point.closest2(turtle.getPoint(), t)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function level()
|
||||||
|
|
||||||
|
box.x = math.min(data.startPt.x, data.endPt.x)
|
||||||
|
box.y = math.min(data.startPt.y, data.endPt.y)
|
||||||
|
box.z = math.min(data.startPt.z, data.endPt.z)
|
||||||
|
box.ex = math.max(data.startPt.x, data.endPt.x)
|
||||||
|
box.ey = math.max(data.startPt.y, data.endPt.y)
|
||||||
|
box.ez = math.max(data.startPt.z, data.endPt.z)
|
||||||
|
|
||||||
|
turtle.pathfind(data.firstPt)
|
||||||
|
|
||||||
|
turtle.setPolicy("attack", { dig = dig }, "assuredMove")
|
||||||
|
turtle.setMoveCallback(move)
|
||||||
|
|
||||||
|
repeat
|
||||||
|
local key = toKey(turtle.point)
|
||||||
|
|
||||||
|
checkedNodes[key] = true
|
||||||
|
nodes[key] = nil
|
||||||
|
|
||||||
|
dig(turtle.getAction('down'))
|
||||||
|
dig(turtle.getAction('up'))
|
||||||
|
dig(turtle.getAction('forward'))
|
||||||
|
|
||||||
|
print(string.format('%d nodes remaining', Util.size(nodes)))
|
||||||
|
|
||||||
|
if Util.size(nodes) == 0 then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
local node = Point.closest2(turtle.point, nodes)
|
||||||
|
node = getAdjacentPoint(node)
|
||||||
|
if not turtle.gotoPoint(node) then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
until turtle.abort
|
||||||
|
|
||||||
|
turtle.resetState()
|
||||||
|
end
|
||||||
|
|
||||||
|
local s, m = turtle.run(function()
|
||||||
|
turtle.status = 'Leveling'
|
||||||
|
|
||||||
|
if turtle.enableGPS() then
|
||||||
|
|
||||||
|
local pt = Util.shallowCopy(turtle.point)
|
||||||
|
local s, m = pcall(level)
|
||||||
|
turtle.pathfind(pt)
|
||||||
|
|
||||||
|
if not s and m then
|
||||||
|
error(m)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
if not s then
|
||||||
|
error(m)
|
||||||
|
end
|
||||||
|
]]
|
||||||
|
|
||||||
|
|
||||||
|
local data = Util.readTable('/usr/config/shapes') or { }
|
||||||
|
|
||||||
|
local page = UI.Page {
|
||||||
|
titleBar = UI.TitleBar { title = 'Shapes' },
|
||||||
|
info = UI.Window { x = 5, y = 3, height = 1 },
|
||||||
|
startCoord = UI.Button { x = 2, y = 6, text = 'Start ', event = 'startCoord' },
|
||||||
|
endCoord = UI.Button { x = 2, y = 8, text = 'End ', event = 'endCoord' },
|
||||||
|
supplies = UI.Button { x = 2, y = 10, text = 'Supplies', event = 'supplies' },
|
||||||
|
first = UI.Button { x = 2, y = 11, text = 'First', event = 'firstCoord' },
|
||||||
|
cancel = UI.Button { rx = 2, ry = -2, text = 'Abort', event = 'cancel' },
|
||||||
|
begin = UI.Button { rx = -7, ry = -2, text = 'Begin', event = 'begin' },
|
||||||
|
accelerators = { q = 'quit' },
|
||||||
|
notification = UI.Notification(),
|
||||||
|
statusBar = UI.StatusBar(),
|
||||||
|
}
|
||||||
|
|
||||||
|
function page.info:draw()
|
||||||
|
|
||||||
|
local function size(a, b)
|
||||||
|
return (math.abs(a.x - b.x) + 1) *
|
||||||
|
(math.abs(a.y - b.y) + 1) *
|
||||||
|
(math.abs(a.z - b.z) + 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
self:clear()
|
||||||
|
if not data.startPt then
|
||||||
|
self:write(1, 1, 'Set starting corner')
|
||||||
|
elseif not data.endPt then
|
||||||
|
self:write(1, 1, 'Set ending corner')
|
||||||
|
else
|
||||||
|
self:write(1, 1, 'Blocks: ' .. size(data.startPt, data.endPt))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function page:getPoint()
|
||||||
|
local pt = GPS.getPoint()
|
||||||
|
if not pt then
|
||||||
|
self.notification:error('GPS not available')
|
||||||
|
end
|
||||||
|
return pt
|
||||||
|
end
|
||||||
|
|
||||||
|
function page:runFunction(id, script)
|
||||||
|
|
||||||
|
Util.writeFile('script.tmp', script)
|
||||||
|
self.notification:info('Connecting')
|
||||||
|
local fn, msg = loadstring(script, 'script')
|
||||||
|
if not fn then
|
||||||
|
self.notification:error('Error in script')
|
||||||
|
debug(msg)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local socket = Socket.connect(id, 161)
|
||||||
|
if not socket then
|
||||||
|
self.notification:error('Unable to connect')
|
||||||
|
return
|
||||||
|
end
|
||||||
|
socket:write({ type = 'script', args = script })
|
||||||
|
socket:close()
|
||||||
|
|
||||||
|
self.notification:success('Sent')
|
||||||
|
end
|
||||||
|
|
||||||
|
function page:eventHandler(event)
|
||||||
|
if event.type == 'startCoord' then
|
||||||
|
data.startPt = self:getPoint()
|
||||||
|
if data.startPt then
|
||||||
|
self.statusBar:setStatus('starting corner set')
|
||||||
|
Util.writeTable('/usr/config/shapes', data)
|
||||||
|
end
|
||||||
|
self:draw()
|
||||||
|
elseif event.type == 'endCoord' then
|
||||||
|
data.endPt = self:getPoint()
|
||||||
|
if data.endPt then
|
||||||
|
self.statusBar:setStatus('ending corner set')
|
||||||
|
Util.writeTable('/usr/config/shapes', data)
|
||||||
|
end
|
||||||
|
self:draw()
|
||||||
|
elseif event.type == 'firstCoord' then
|
||||||
|
data.firstPt = self:getPoint()
|
||||||
|
if data.firstPt then
|
||||||
|
self.statusBar:setStatus('first point set')
|
||||||
|
Util.writeTable('/usr/config/shapes', data)
|
||||||
|
end
|
||||||
|
self:draw()
|
||||||
|
elseif event.type == 'supplies' then
|
||||||
|
data.suppliesPt = self:getPoint()
|
||||||
|
if data.suppliesPt then
|
||||||
|
self.statusBar:setStatus('supplies location set')
|
||||||
|
Util.writeTable('/usr/config/shapes', data)
|
||||||
|
end
|
||||||
|
elseif event.type == 'begin' then
|
||||||
|
if data.startPt and data.endPt then
|
||||||
|
local s = 'local data = ' .. textutils.serialize(data) .. levelScript
|
||||||
|
self:runFunction(turtleId, s)
|
||||||
|
else
|
||||||
|
self.notification:error('Corners not set')
|
||||||
|
end
|
||||||
|
self.statusBar:setStatus('')
|
||||||
|
elseif event.type == 'cancel' then
|
||||||
|
self:runFunction(turtleId, 'turtle.abortAction()')
|
||||||
|
self.statusBar:setStatus('')
|
||||||
|
else
|
||||||
|
return UI.Page.eventHandler(self, event)
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
UI:setPage(page)
|
||||||
|
|
||||||
|
UI:pullEvents()
|
||||||
|
UI.term:reset()
|
||||||
633
apps/simpleMiner.lua
Normal file
633
apps/simpleMiner.lua
Normal file
@@ -0,0 +1,633 @@
|
|||||||
|
requireInjector(getfenv(1))
|
||||||
|
|
||||||
|
local Logger = require('logger')
|
||||||
|
local Point = require('point')
|
||||||
|
local Util = require('util')
|
||||||
|
|
||||||
|
if device and device.wireless_modem then
|
||||||
|
Logger.setWirelessLogging()
|
||||||
|
end
|
||||||
|
|
||||||
|
local args = { ... }
|
||||||
|
local options = {
|
||||||
|
chunks = { arg = 'c', type = 'number', value = -1,
|
||||||
|
desc = 'Number of chunks to mine' },
|
||||||
|
depth = { arg = 'd', type = 'number', value = 9000,
|
||||||
|
desc = 'Mining depth' },
|
||||||
|
-- enderChest = { arg = 'e', type = 'flag', value = false,
|
||||||
|
-- desc = 'Use ender chest' },
|
||||||
|
resume = { arg = 'r', type = 'flag', value = false,
|
||||||
|
desc = 'Resume mining' },
|
||||||
|
fortunePick = { arg = 'p', type = 'string', value = nil,
|
||||||
|
desc = 'Pick to use with CCTweaks toolhost' },
|
||||||
|
setTrash = { arg = 's', type = 'flag', value = false,
|
||||||
|
desc = 'Set trash items' },
|
||||||
|
help = { arg = 'h', type = 'flag', value = false,
|
||||||
|
desc = 'Displays the options' },
|
||||||
|
}
|
||||||
|
|
||||||
|
local fortuneBlocks = {
|
||||||
|
[ 'minecraft:redstone_ore' ] = true,
|
||||||
|
[ 'minecraft:lapis_ore' ] = true,
|
||||||
|
[ 'minecraft:coal_ore' ] = true,
|
||||||
|
[ 'minecraft:diamond_ore' ] = true,
|
||||||
|
[ 'minecraft:emerald_ore' ] = true,
|
||||||
|
}
|
||||||
|
|
||||||
|
local MIN_FUEL = 7500
|
||||||
|
local LOW_FUEL = 1500
|
||||||
|
local MAX_FUEL = 100000
|
||||||
|
|
||||||
|
if not term.isColor() then
|
||||||
|
MAX_FUEL = 20000
|
||||||
|
end
|
||||||
|
|
||||||
|
local mining = {
|
||||||
|
diameter = 1,
|
||||||
|
chunkIndex = 0,
|
||||||
|
chunks = -1,
|
||||||
|
}
|
||||||
|
|
||||||
|
local trash
|
||||||
|
local boreDirection
|
||||||
|
|
||||||
|
function getChunkCoordinates(diameter, index, x, z)
|
||||||
|
local dirs = { -- circumference of grid
|
||||||
|
{ xd = 0, zd = 1, heading = 1 }, -- south
|
||||||
|
{ xd = -1, zd = 0, heading = 2 },
|
||||||
|
{ xd = 0, zd = -1, heading = 3 },
|
||||||
|
{ xd = 1, zd = 0, heading = 0 } -- east
|
||||||
|
}
|
||||||
|
-- always move east when entering the next diameter
|
||||||
|
if index == 0 then
|
||||||
|
dirs[4].x = x + 16
|
||||||
|
dirs[4].z = z
|
||||||
|
return dirs[4]
|
||||||
|
end
|
||||||
|
dir = dirs[math.floor(index / (diameter - 1)) + 1]
|
||||||
|
dir.x = x + dir.xd * 16
|
||||||
|
dir.z = z + dir.zd * 16
|
||||||
|
return dir
|
||||||
|
end
|
||||||
|
|
||||||
|
function getBoreLocations(x, z)
|
||||||
|
|
||||||
|
local locations = {}
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local a = math.abs(z)
|
||||||
|
local b = math.abs(x)
|
||||||
|
|
||||||
|
if x > 0 and z > 0 or
|
||||||
|
x < 0 and z < 0 then
|
||||||
|
-- rotate coords
|
||||||
|
a = math.abs(x)
|
||||||
|
b = math.abs(z)
|
||||||
|
end
|
||||||
|
if (a % 5 == 0 and b % 5 == 0) or
|
||||||
|
(a % 5 == 2 and b % 5 == 1) or
|
||||||
|
(a % 5 == 4 and b % 5 == 2) or
|
||||||
|
(a % 5 == 1 and b % 5 == 3) or
|
||||||
|
(a % 5 == 3 and b % 5 == 4) then
|
||||||
|
table.insert(locations, { x = x, z = z, y = 0 })
|
||||||
|
end
|
||||||
|
if z % 2 == 0 then -- forward dir
|
||||||
|
if (x + 1) % 16 == 0 then
|
||||||
|
z = z + 1
|
||||||
|
else
|
||||||
|
x = x + 1
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if (x - 1) % 16 == 15 then
|
||||||
|
if (z + 1) % 16 == 0 then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
z = z + 1
|
||||||
|
else
|
||||||
|
x = x - 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return locations
|
||||||
|
end
|
||||||
|
|
||||||
|
-- get the bore location closest to the miner
|
||||||
|
local function getClosestLocation(points, b)
|
||||||
|
local key = 1
|
||||||
|
local leastMoves = 9000
|
||||||
|
for k,pt in pairs(points) do
|
||||||
|
|
||||||
|
local moves = Point.calculateMoves(turtle.point, pt)
|
||||||
|
|
||||||
|
if moves < leastMoves then
|
||||||
|
key = k
|
||||||
|
leastMoves = moves
|
||||||
|
if leastMoves == 0 then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return table.remove(points, key)
|
||||||
|
end
|
||||||
|
|
||||||
|
function getCornerOf(c)
|
||||||
|
return math.floor(c.x / 16) * 16, math.floor(c.z / 16) * 16
|
||||||
|
end
|
||||||
|
|
||||||
|
function nextChunk()
|
||||||
|
|
||||||
|
local x, z = getCornerOf({ x = mining.x, z = mining.z })
|
||||||
|
local points = math.pow(mining.diameter, 2) - math.pow(mining.diameter-2, 2)
|
||||||
|
mining.chunkIndex = mining.chunkIndex + 1
|
||||||
|
|
||||||
|
if mining.chunkIndex >= points then
|
||||||
|
mining.diameter = mining.diameter + 2
|
||||||
|
mining.chunkIndex = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
if mining.chunks ~= -1 then
|
||||||
|
local chunks = math.pow(mining.diameter-2, 2) + mining.chunkIndex
|
||||||
|
if chunks >= mining.chunks then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local nc = getChunkCoordinates(mining.diameter, mining.chunkIndex, x, z)
|
||||||
|
mining.locations = getBoreLocations(nc.x, nc.z)
|
||||||
|
|
||||||
|
-- enter next chunk
|
||||||
|
mining.x = nc.x
|
||||||
|
mining.z = nc.z
|
||||||
|
|
||||||
|
Util.writeTable('mining.progress', mining)
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function addTrash()
|
||||||
|
|
||||||
|
if not trash then
|
||||||
|
trash = { }
|
||||||
|
end
|
||||||
|
|
||||||
|
local slots = turtle.getFilledSlots()
|
||||||
|
|
||||||
|
for k,slot in pairs(slots) do
|
||||||
|
trash[slot.iddmg] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
trash['minecraft:bucket:0'] = nil
|
||||||
|
Util.writeTable('mining.trash', trash)
|
||||||
|
end
|
||||||
|
|
||||||
|
function log(text)
|
||||||
|
print(text)
|
||||||
|
Logger.log('mineWorker', text)
|
||||||
|
end
|
||||||
|
|
||||||
|
function status(status)
|
||||||
|
turtle.status = status
|
||||||
|
log(status)
|
||||||
|
end
|
||||||
|
|
||||||
|
function refuel()
|
||||||
|
if turtle.getFuelLevel() < MIN_FUEL then
|
||||||
|
local oldStatus = turtle.status
|
||||||
|
status('refueling')
|
||||||
|
|
||||||
|
if turtle.select'minecraft:coal:0') then
|
||||||
|
local qty = turtle.getItemCount()
|
||||||
|
print('refueling ' .. qty)
|
||||||
|
turtle.refuel(qty)
|
||||||
|
end
|
||||||
|
if turtle.getFuelLevel() < MIN_FUEL then
|
||||||
|
log('desperate fueling')
|
||||||
|
|
||||||
|
turtle.eachFilledSlot(function(slot)
|
||||||
|
if turtle.getFuelLevel() < MIN_FUEL then
|
||||||
|
turtle.select(slot.index)
|
||||||
|
turtle.refuel(64)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
log('Fuel: ' .. turtle.getFuelLevel())
|
||||||
|
status(oldStatus)
|
||||||
|
end
|
||||||
|
|
||||||
|
turtle.select(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
function enderChestUnload()
|
||||||
|
log('unloading')
|
||||||
|
turtle.select(1)
|
||||||
|
if not Util.tryTimed(5, function()
|
||||||
|
turtle.digDown()
|
||||||
|
return turtle.placeDown()
|
||||||
|
end) then
|
||||||
|
log('placedown failed')
|
||||||
|
else
|
||||||
|
turtle.reconcileInventory(slots, turtle.dropDown)
|
||||||
|
|
||||||
|
turtle.select(1)
|
||||||
|
turtle.drop(64)
|
||||||
|
turtle.digDown()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function safeGoto(x, z, y, h)
|
||||||
|
local oldStatus = turtle.status
|
||||||
|
while not turtle.pathfind({ x = x, z = z, y = y, heading = h }) do
|
||||||
|
--status('stuck')
|
||||||
|
if turtle.abort then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
--os.sleep(1)
|
||||||
|
end
|
||||||
|
turtle.status = oldStatus
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function safeGotoY(y)
|
||||||
|
local oldStatus = turtle.status
|
||||||
|
while not turtle.gotoY(y) do
|
||||||
|
status('stuck')
|
||||||
|
if turtle.abort then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
os.sleep(1)
|
||||||
|
end
|
||||||
|
turtle.status = oldStatus
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function makeWalkableTunnel(action, tpt, pt)
|
||||||
|
if action ~= 'turn' and not Point.compare(tpt, { x = 0, z = 0 }) then -- not at source
|
||||||
|
if not Point.compare(tpt, pt) then -- not at dest
|
||||||
|
local r, block = turtle.inspectUp()
|
||||||
|
if r and not turtle.isTurtleAtSide('top') then
|
||||||
|
if block.name ~= 'minecraft:cobblestone' and
|
||||||
|
block.name ~= 'minecraft:chest' then
|
||||||
|
turtle.digUp()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function normalChestUnload()
|
||||||
|
local oldStatus = turtle.status
|
||||||
|
status('unloading')
|
||||||
|
local pt = Util.shallowCopy(turtle.point)
|
||||||
|
safeGotoY(0)
|
||||||
|
|
||||||
|
turtle.setMoveCallback(function(action, tpt)
|
||||||
|
makeWalkableTunnel(action, tpt, { x = pt.x, z = pt.z })
|
||||||
|
end)
|
||||||
|
|
||||||
|
safeGoto(0, 0)
|
||||||
|
if not turtle.detectUp() then
|
||||||
|
error('no chest')
|
||||||
|
end
|
||||||
|
local slots = turtle.getFilledSlots()
|
||||||
|
for _,slot in pairs(slots) do
|
||||||
|
if not trash[slot.iddmg] and
|
||||||
|
slot.iddmg ~= 'minecraft:bucket:0' and
|
||||||
|
slot.id ~= 'minecraft:diamond_pickaxe' and
|
||||||
|
slot.id ~= 'cctweaks:toolHost' then
|
||||||
|
if slot.id ~= options.fortunePick.value then
|
||||||
|
turtle.select(slot.index)
|
||||||
|
turtle.dropUp(64)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
turtle.select(1)
|
||||||
|
safeGoto(pt.x, pt.z, 0, pt.heading)
|
||||||
|
|
||||||
|
turtle.clearMoveCallback()
|
||||||
|
|
||||||
|
safeGotoY(pt.y)
|
||||||
|
status(oldStatus)
|
||||||
|
end
|
||||||
|
|
||||||
|
function ejectTrash()
|
||||||
|
|
||||||
|
local cobbleSlotCount = 0
|
||||||
|
|
||||||
|
turtle.eachFilledSlot(function(slot)
|
||||||
|
if slot.iddmg == 'minecraft:cobblestone:0' then
|
||||||
|
cobbleSlotCount = cobbleSlotCount + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
if trash[slot.iddmg] then
|
||||||
|
-- retain 1 slot with cobble in order to indicate active mining
|
||||||
|
if slot.iddmg ~= 'minecraft:cobblestone:0' or cobbleSlotCount > 1 then
|
||||||
|
turtle.select(slot.index)
|
||||||
|
turtle.dropDown(64)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function mineable(action)
|
||||||
|
local r, block = action.inspect()
|
||||||
|
if not r then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
if block.name == 'minecraft:chest' then
|
||||||
|
collectDrops(action.suck)
|
||||||
|
end
|
||||||
|
|
||||||
|
if turtle.getFuelLevel() < (MAX_FUEL - 1000) then
|
||||||
|
if block.name == 'minecraft:lava' or block.name == 'minecraft:flowing_lava' then
|
||||||
|
if turtle.select('minecraft:bucket:0') then
|
||||||
|
if action.place() then
|
||||||
|
log('Lava! ' .. turtle.getFuelLevel())
|
||||||
|
turtle.refuel()
|
||||||
|
log(turtle.getFuelLevel())
|
||||||
|
end
|
||||||
|
turtle.select(1)
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if action.side == 'bottom' then
|
||||||
|
return block.name
|
||||||
|
end
|
||||||
|
|
||||||
|
if trash[block.name .. ':0'] then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
return block.name
|
||||||
|
end
|
||||||
|
|
||||||
|
function fortuneDig(action, blockName)
|
||||||
|
if options.fortunePick.value and fortuneBlocks[blockName] then
|
||||||
|
turtle.select('cctweaks:toolHost')
|
||||||
|
turtle.equipRight()
|
||||||
|
turtle.select(options.fortunePick.value)
|
||||||
|
repeat until not turtle.dig()
|
||||||
|
turtle.select('minecraft:diamond_pickaxe')
|
||||||
|
turtle.equipRight()
|
||||||
|
turtle.select(1)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function mine(action)
|
||||||
|
local blockName = mineable(action)
|
||||||
|
if blockName then
|
||||||
|
checkSpace()
|
||||||
|
--collectDrops(action.suck)
|
||||||
|
if not fortuneDig(action, blockName) then
|
||||||
|
action.dig()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function bore()
|
||||||
|
|
||||||
|
local loc = turtle.point
|
||||||
|
local level = loc.y
|
||||||
|
|
||||||
|
turtle.select(1)
|
||||||
|
status('boring down')
|
||||||
|
boreDirection = 'down'
|
||||||
|
|
||||||
|
while true do
|
||||||
|
if turtle.abort then
|
||||||
|
status('aborting')
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
if loc.y <= -mining.depth then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
if turtle.point.y < -2 then
|
||||||
|
-- turtle.setDigPolicy(turtle.digPolicies.turtleSafe)
|
||||||
|
end
|
||||||
|
|
||||||
|
mine(turtle.getAction('down'))
|
||||||
|
if not Util.tryTimed(3, turtle.down) then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
if loc.y < level - 1 then
|
||||||
|
mine(turtle.getAction('forward'))
|
||||||
|
turtle.turnRight()
|
||||||
|
mine(turtle.getAction('forward'))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
boreDirection = 'up'
|
||||||
|
status('boring up')
|
||||||
|
|
||||||
|
turtle.turnRight()
|
||||||
|
mine(turtle.getAction('forward'))
|
||||||
|
|
||||||
|
turtle.turnRight()
|
||||||
|
mine(turtle.getAction('forward'))
|
||||||
|
|
||||||
|
turtle.turnLeft()
|
||||||
|
|
||||||
|
while true do
|
||||||
|
if turtle.abort then
|
||||||
|
status('aborting')
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
if turtle.point.y > -2 then
|
||||||
|
-- turtle.setDigPolicy(turtle.digPolicies.turtleSafe)
|
||||||
|
end
|
||||||
|
|
||||||
|
while not Util.tryTimed(3, turtle.up) do
|
||||||
|
status('stuck')
|
||||||
|
end
|
||||||
|
if turtle.status == 'stuck' then
|
||||||
|
status('boring up')
|
||||||
|
end
|
||||||
|
|
||||||
|
if loc.y >= level - 1 then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
mine(turtle.getAction('forward'))
|
||||||
|
turtle.turnLeft()
|
||||||
|
mine(turtle.getAction('forward'))
|
||||||
|
end
|
||||||
|
|
||||||
|
if turtle.getFuelLevel() < LOW_FUEL then
|
||||||
|
refuel()
|
||||||
|
local veryMinFuel = Point.turtleDistance(turtle.point, { x = 0, y = 0, z = 0}) + 512
|
||||||
|
if turtle.getFuelLevel() < veryMinFuel then
|
||||||
|
log('Not enough fuel to continue')
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function checkSpace()
|
||||||
|
if turtle.getItemCount(16) > 0 then
|
||||||
|
refuel()
|
||||||
|
local oldStatus = turtle.status
|
||||||
|
status('condensing')
|
||||||
|
ejectTrash()
|
||||||
|
turtle.condense()
|
||||||
|
local lastSlot = 16
|
||||||
|
if boreDirection == 'down' then
|
||||||
|
lastSlot = 15
|
||||||
|
end
|
||||||
|
if turtle.getItemCount(lastSlot) > 0 then
|
||||||
|
unload()
|
||||||
|
end
|
||||||
|
status(oldStatus)
|
||||||
|
turtle.select(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function collectDrops(suckAction)
|
||||||
|
for i = 1, 50 do
|
||||||
|
if not suckAction() then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
checkSpace()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Point.compare(pta, ptb)
|
||||||
|
if pta.x == ptb.x and pta.z == ptb.z then
|
||||||
|
if pta.y and ptb.y then
|
||||||
|
return pta.y == ptb.y
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function inspect(action, name)
|
||||||
|
local r, block = action.inspect()
|
||||||
|
if r and block.name == name then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function boreCommand()
|
||||||
|
local pt = getClosestLocation(mining.locations, turtle.point)
|
||||||
|
|
||||||
|
turtle.setMoveCallback(function(action, tpt)
|
||||||
|
makeWalkableTunnel(action, tpt, pt)
|
||||||
|
end)
|
||||||
|
|
||||||
|
safeGotoY(0)
|
||||||
|
safeGoto(pt.x, pt.z, 0)
|
||||||
|
|
||||||
|
turtle.clearMoveCallback()
|
||||||
|
|
||||||
|
-- location is either mined, currently being mined or is the
|
||||||
|
-- dropoff point for a turtle
|
||||||
|
if inspect(turtle.getAction('up'), 'minecraft:cobblestone') or
|
||||||
|
inspect(turtle.getAction('up'), 'minecraft:chest') or
|
||||||
|
inspect(turtle.getAction('down'), 'minecraft:cobblestone') then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
turtle.digUp()
|
||||||
|
turtle.placeUp('minecraft:cobblestone:0')
|
||||||
|
|
||||||
|
local success = bore()
|
||||||
|
|
||||||
|
safeGotoY(0) -- may have aborted
|
||||||
|
turtle.digUp()
|
||||||
|
|
||||||
|
if success then
|
||||||
|
turtle.placeDown('minecraft:cobblestone:0') -- cap with cobblestone to indicate this spot was mined out
|
||||||
|
end
|
||||||
|
|
||||||
|
return success
|
||||||
|
end
|
||||||
|
|
||||||
|
if not Util.getOptions(options, args) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
mining.depth = options.depth.value
|
||||||
|
mining.chunks = options.chunks.value
|
||||||
|
|
||||||
|
unload = normalChestUnload
|
||||||
|
--if options.enderChest.value then
|
||||||
|
-- unload = enderChestUnload
|
||||||
|
--end
|
||||||
|
|
||||||
|
mining.x = 0
|
||||||
|
mining.z = 0
|
||||||
|
mining.locations = getBoreLocations(0, 0)
|
||||||
|
trash = Util.readTable('mining.trash')
|
||||||
|
|
||||||
|
if options.resume.value then
|
||||||
|
mining = Util.readTable('mining.progress')
|
||||||
|
elseif fs.exists('mining.progress') then
|
||||||
|
print('use -r to resume')
|
||||||
|
read()
|
||||||
|
end
|
||||||
|
|
||||||
|
if not trash or options.setTrash.value then
|
||||||
|
print('Add trash blocks, press enter when ready')
|
||||||
|
read()
|
||||||
|
addTrash()
|
||||||
|
end
|
||||||
|
|
||||||
|
if not turtle.getSlot('minecraft:bucket:0') or
|
||||||
|
not turtle.getSlot('minecraft:cobblestone:0') then
|
||||||
|
print('Add bucket and cobblestone, press enter when ready')
|
||||||
|
read()
|
||||||
|
end
|
||||||
|
|
||||||
|
if options.fortunePick.value then
|
||||||
|
local s = turtle.getSlot(options.fortunePick.value)
|
||||||
|
if not s then
|
||||||
|
error('fortunePick not found: ' .. options.fortunePick.value)
|
||||||
|
end
|
||||||
|
if not turtle.getSlot('cctweaks:toolHost:0') then
|
||||||
|
error('CCTweaks tool host not found')
|
||||||
|
end
|
||||||
|
trash[s.iddmg] = nil
|
||||||
|
trash['minecraft:diamond_pickaxe:0'] = nil
|
||||||
|
trash['cctweaks:toolHost:0'] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
_G._p = trash
|
||||||
|
|
||||||
|
local function main()
|
||||||
|
repeat
|
||||||
|
while #mining.locations > 0 do
|
||||||
|
status('searching')
|
||||||
|
if not boreCommand() then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
Util.writeTable('mining.progress', mining)
|
||||||
|
end
|
||||||
|
until not nextChunk()
|
||||||
|
end
|
||||||
|
|
||||||
|
turtle.run(function()
|
||||||
|
turtle.reset()
|
||||||
|
turtle.setPolicy(turtle.policies.digAttack)
|
||||||
|
turtle.setDigPolicy(turtle.digPolicies.turtleSafe)
|
||||||
|
|
||||||
|
unload()
|
||||||
|
status('mining')
|
||||||
|
|
||||||
|
local s, m = pcall(function() main() end)
|
||||||
|
if not s and m then
|
||||||
|
printError(m)
|
||||||
|
end
|
||||||
|
|
||||||
|
safeGotoY(0)
|
||||||
|
safeGoto(0, 0, 0, 0)
|
||||||
|
unload()
|
||||||
|
turtle.reset()
|
||||||
|
end)
|
||||||
174
apps/storageActivity.lua
Normal file
174
apps/storageActivity.lua
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
requireInjector(getfenv(1))
|
||||||
|
|
||||||
|
local ChestAdapter = require('chestAdapter18')
|
||||||
|
local Event = require('event')
|
||||||
|
local MEAdapter = require('meAdapter')
|
||||||
|
local RefinedAdapter = require('refinedAdapter')
|
||||||
|
local UI = require('ui')
|
||||||
|
local Util = require('util')
|
||||||
|
|
||||||
|
local storage = RefinedAdapter()
|
||||||
|
if not storage:isValid() then
|
||||||
|
storage = MEAdapter()
|
||||||
|
if not storage:isValid() then
|
||||||
|
storage = ChestAdapter()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not storage:isValid() then
|
||||||
|
error('Not connected to a storage device')
|
||||||
|
end
|
||||||
|
|
||||||
|
multishell.setTitle(multishell.getCurrent(), 'Storage Activity')
|
||||||
|
UI:configure('StorageActivity', ...)
|
||||||
|
|
||||||
|
local changedPage = UI.Page({
|
||||||
|
grid = UI.Grid({
|
||||||
|
columns = {
|
||||||
|
{ heading = 'Qty', key = 'count', width = 5 },
|
||||||
|
{ heading = 'Change', key = 'change', width = 6 },
|
||||||
|
{ heading = 'Name', key = 'displayName', width = UI.term.width - 15 },
|
||||||
|
},
|
||||||
|
sortColumn = 'displayName',
|
||||||
|
rey = -6,
|
||||||
|
}),
|
||||||
|
buttons = UI.Window({
|
||||||
|
ry = -4,
|
||||||
|
height = 5,
|
||||||
|
backgroundColor = colors.gray,
|
||||||
|
prevButton = UI.Button({
|
||||||
|
event = 'previous',
|
||||||
|
backgroundColor = colors.lightGray,
|
||||||
|
x = 2,
|
||||||
|
y = 2,
|
||||||
|
height = 3,
|
||||||
|
width = 5,
|
||||||
|
text = ' < '
|
||||||
|
}),
|
||||||
|
resetButton = UI.Button({
|
||||||
|
event = 'reset',
|
||||||
|
backgroundColor = colors.lightGray,
|
||||||
|
x = 8,
|
||||||
|
y = 2,
|
||||||
|
height = 3,
|
||||||
|
rex = -8,
|
||||||
|
text = 'Reset'
|
||||||
|
}),
|
||||||
|
nextButton = UI.Button({
|
||||||
|
event = 'next',
|
||||||
|
backgroundColor = colors.lightGray,
|
||||||
|
rx = -5,
|
||||||
|
y = 2,
|
||||||
|
height = 3,
|
||||||
|
width = 5,
|
||||||
|
text = ' > '
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
accelerators = {
|
||||||
|
q = 'quit',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function changedPage.grid:getDisplayValues(row)
|
||||||
|
row = Util.shallowCopy(row)
|
||||||
|
|
||||||
|
local ind = '+'
|
||||||
|
if row.change < 0 then
|
||||||
|
ind = ''
|
||||||
|
end
|
||||||
|
row.change = ind .. Util.toBytes(row.change)
|
||||||
|
row.count = Util.toBytes(row.count)
|
||||||
|
|
||||||
|
return row
|
||||||
|
end
|
||||||
|
|
||||||
|
function changedPage:eventHandler(event)
|
||||||
|
|
||||||
|
if event.type == 'reset' then
|
||||||
|
self.lastItems = nil
|
||||||
|
self.grid:setValues({ })
|
||||||
|
self.grid:clear()
|
||||||
|
self.grid:draw()
|
||||||
|
|
||||||
|
elseif event.type == 'next' then
|
||||||
|
self.grid:nextPage()
|
||||||
|
|
||||||
|
elseif event.type == 'previous' then
|
||||||
|
self.grid:previousPage()
|
||||||
|
|
||||||
|
elseif event.type == 'quit' then
|
||||||
|
Event.exitPullEvents()
|
||||||
|
|
||||||
|
else
|
||||||
|
return UI.Page.eventHandler(self, event)
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function uniqueKey(item)
|
||||||
|
return table.concat({ item.name, item.damage, item.nbtHash }, ':')
|
||||||
|
end
|
||||||
|
|
||||||
|
function changedPage:refresh()
|
||||||
|
local t = storage:listItems()
|
||||||
|
|
||||||
|
if not t or Util.empty(t) then
|
||||||
|
self:clear()
|
||||||
|
self:centeredWrite(math.ceil(self.height/2), 'Communication failure')
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
for k,v in pairs(t) do
|
||||||
|
t[k] = Util.shallowCopy(v)
|
||||||
|
end
|
||||||
|
|
||||||
|
if not self.lastItems then
|
||||||
|
self.lastItems = t
|
||||||
|
self.grid:setValues({ })
|
||||||
|
else
|
||||||
|
local changedItems = {}
|
||||||
|
for _,v in pairs(self.lastItems) do
|
||||||
|
found = false
|
||||||
|
for k2,v2 in pairs(t) do
|
||||||
|
if uniqueKey(v) == uniqueKey(v2) then
|
||||||
|
if v.count ~= v2.count then
|
||||||
|
local c = Util.shallowCopy(v2)
|
||||||
|
c.lastCount = v.count
|
||||||
|
table.insert(changedItems, c)
|
||||||
|
end
|
||||||
|
table.remove(t, k2)
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- New item
|
||||||
|
if not found then
|
||||||
|
local c = Util.shallowCopy(v)
|
||||||
|
c.lastCount = v.count
|
||||||
|
c.count = 0
|
||||||
|
table.insert(changedItems, c)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- No items left
|
||||||
|
for k,v in pairs(t) do
|
||||||
|
v.lastCount = 0
|
||||||
|
table.insert(changedItems, v)
|
||||||
|
end
|
||||||
|
|
||||||
|
for k,v in pairs(changedItems) do
|
||||||
|
v.change = v.count - v.lastCount
|
||||||
|
end
|
||||||
|
|
||||||
|
self.grid:setValues(changedItems)
|
||||||
|
end
|
||||||
|
self.grid:draw()
|
||||||
|
end
|
||||||
|
|
||||||
|
Event.onInterval(5, function()
|
||||||
|
changedPage:refresh()
|
||||||
|
changedPage:sync()
|
||||||
|
end)
|
||||||
|
|
||||||
|
UI:setPage(changedPage)
|
||||||
|
UI:pullEvents()
|
||||||
901
apps/storageManager.lua
Normal file
901
apps/storageManager.lua
Normal file
@@ -0,0 +1,901 @@
|
|||||||
|
requireInjector(getfenv(1))
|
||||||
|
|
||||||
|
local Config = require('config')
|
||||||
|
local Event = require('event')
|
||||||
|
local Logger = require('logger')
|
||||||
|
local ME = require('me')
|
||||||
|
local UI = require('ui')
|
||||||
|
local Util = require('util')
|
||||||
|
|
||||||
|
-- Must be a crafty turtle with duck antenna !
|
||||||
|
-- 3 wide monitor (any side of turtle)
|
||||||
|
|
||||||
|
-- Config location is /sys/config/storageMonitor
|
||||||
|
-- adjust directions in that file if needed
|
||||||
|
|
||||||
|
local config = {
|
||||||
|
trashDirection = 'up', -- trash /chest in relation to interface
|
||||||
|
turtleDirection = 'down', -- turtle in relation to interface
|
||||||
|
noCraftingStorage = 'false' -- no ME crafting (or ability to tell if powered - use with caution)
|
||||||
|
}
|
||||||
|
|
||||||
|
Config.load('storageMonitor', config)
|
||||||
|
|
||||||
|
if not device.tileinterface then
|
||||||
|
error('ME interface not found')
|
||||||
|
end
|
||||||
|
|
||||||
|
local duckAntenna
|
||||||
|
|
||||||
|
if device.workbench then
|
||||||
|
|
||||||
|
local oppositeSide = {
|
||||||
|
[ 'left' ] = 'right',
|
||||||
|
[ 'right' ] = 'left'
|
||||||
|
}
|
||||||
|
|
||||||
|
local duckAntennaSide = oppositeSide[device.workbench.side]
|
||||||
|
duckAntenna = peripheral.wrap(duckAntennaSide)
|
||||||
|
end
|
||||||
|
--if not device.monitor then
|
||||||
|
-- error('Monitor not found')
|
||||||
|
--end
|
||||||
|
|
||||||
|
ME.setDevice(device.tileinterface)
|
||||||
|
|
||||||
|
local jobListGrid
|
||||||
|
local craftingPaused = false
|
||||||
|
|
||||||
|
multishell.setTitle(multishell.getCurrent(), 'Storage Manager')
|
||||||
|
|
||||||
|
Logger.disable()
|
||||||
|
|
||||||
|
function getItem(items, inItem, ignore_dmg)
|
||||||
|
for _,item in pairs(items) do
|
||||||
|
if item.id == inItem.id then
|
||||||
|
if ignore_dmg and ignore_dmg == 'yes' then
|
||||||
|
return item
|
||||||
|
elseif item.dmg == inItem.dmg and item.nbt_hash == inItem.nbt_hash then
|
||||||
|
return item
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function uniqueKey(item)
|
||||||
|
local key = item.id .. ':' .. item.dmg
|
||||||
|
if item.nbt_hash then
|
||||||
|
key = key .. ':' .. item.nbt_hash
|
||||||
|
end
|
||||||
|
return key
|
||||||
|
end
|
||||||
|
|
||||||
|
function mergeResources(t)
|
||||||
|
local resources = Util.readTable('resource.limits')
|
||||||
|
resources = resources or { }
|
||||||
|
|
||||||
|
for _,item in pairs(t) do
|
||||||
|
item.has_recipe = false
|
||||||
|
end
|
||||||
|
|
||||||
|
for _,v in pairs(resources) do
|
||||||
|
local item = getItem(t, v)
|
||||||
|
if item then
|
||||||
|
item.limit = tonumber(v.limit)
|
||||||
|
item.low = tonumber(v.low)
|
||||||
|
item.auto = v.auto
|
||||||
|
item.ignore_dmg = v.ignore_dmg
|
||||||
|
else
|
||||||
|
v.qty = 0
|
||||||
|
v.limit = tonumber(v.limit)
|
||||||
|
v.low = tonumber(v.low)
|
||||||
|
v.auto = v.auto
|
||||||
|
v.ignore_dmg = v.ignore_dmg
|
||||||
|
table.insert(t, v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
recipes = Util.readTable('recipes') or { }
|
||||||
|
|
||||||
|
for _,v in pairs(recipes) do
|
||||||
|
local item = getItem(t, v)
|
||||||
|
if item then
|
||||||
|
item.has_recipe = true
|
||||||
|
else
|
||||||
|
v.qty = 0
|
||||||
|
v.limit = nil
|
||||||
|
v.low = nil
|
||||||
|
v.has_recipe = true
|
||||||
|
v.auto = 'no'
|
||||||
|
v.ignore_dmg = 'no'
|
||||||
|
v.has_recipe = 'true'
|
||||||
|
table.insert(t, v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function filterItems(t, filter)
|
||||||
|
local r = {}
|
||||||
|
if filter then
|
||||||
|
filter = filter:lower()
|
||||||
|
for k,v in pairs(t) do
|
||||||
|
if string.find(v.lname, filter) then
|
||||||
|
table.insert(r, v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
return r
|
||||||
|
end
|
||||||
|
|
||||||
|
function sumItems(items)
|
||||||
|
local t = {}
|
||||||
|
|
||||||
|
for _,item in pairs(items) do
|
||||||
|
local key = uniqueKey(item)
|
||||||
|
local summedItem = t[key]
|
||||||
|
if summedItem then
|
||||||
|
summedItem.qty = summedItem.qty + item.qty
|
||||||
|
else
|
||||||
|
summedItem = Util.shallowCopy(item)
|
||||||
|
t[key] = summedItem
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
function isGridClear()
|
||||||
|
for i = 1, 16 do
|
||||||
|
if turtle.getItemCount(i) ~= 0 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function clearGrid()
|
||||||
|
for i = 1, 16 do
|
||||||
|
local count = turtle.getItemCount(i)
|
||||||
|
if count > 0 then
|
||||||
|
ME.insert(i, count, config.turtleDirection)
|
||||||
|
if turtle.getItemCount(i) ~= 0 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function turtleCraft(recipe, originalItem)
|
||||||
|
|
||||||
|
for k,v in pairs(recipe.ingredients) do
|
||||||
|
|
||||||
|
-- ugh
|
||||||
|
local dmg = v.dmg
|
||||||
|
|
||||||
|
if v.max_dmg and v.max_dmg > 0 then
|
||||||
|
local item = ME.getItemDetail({ id = v.id, nbt_hash = v.nbt_hash }, false)
|
||||||
|
if item then
|
||||||
|
dmg = item.dmg
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not ME.extract(v.id, dmg, v.nbt_hash, v.qty, config.turtleDirection, k) then
|
||||||
|
clearGrid()
|
||||||
|
originalItem.status = v.name .. ' (extract failed)'
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not turtle.craft() then
|
||||||
|
clearGrid()
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
clearGrid()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function craftItem(items, recipes, item, originalItem, itemList)
|
||||||
|
|
||||||
|
local key = uniqueKey(item)
|
||||||
|
local recipe = recipes[key]
|
||||||
|
|
||||||
|
if recipe then
|
||||||
|
|
||||||
|
if not isGridClear() then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local summedItems = sumItems(recipe.ingredients)
|
||||||
|
|
||||||
|
for i = 1, math.ceil(item.qty / recipe.qty) do
|
||||||
|
|
||||||
|
local failed = false -- try to craft all components (use all CPUs available)
|
||||||
|
|
||||||
|
for _,ingredient in pairs(summedItems) do
|
||||||
|
local ignore_dmg = 'no'
|
||||||
|
if ingredient.max_dmg and ingredient.max_dmg > 0 then
|
||||||
|
ignore_dmg = 'yes'
|
||||||
|
end
|
||||||
|
local qty = ME.getItemCount(ingredient.id, ingredient.dmg, ingredient.nbt_hash, ignore_dmg)
|
||||||
|
if qty < ingredient.qty then
|
||||||
|
originalItem.status = ingredient.name .. ' (crafting)'
|
||||||
|
ingredient.qty = ingredient.qty - qty
|
||||||
|
if not craftItem(items, recipes, ingredient, originalItem, itemList) then
|
||||||
|
failed = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if failed then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
if not failed and not turtleCraft(recipe, originalItem) then
|
||||||
|
Logger.debug('turtle failed to craft ' .. item.name)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
local meItem = getItem(items, item)
|
||||||
|
if not meItem or not meItem.is_craftable then
|
||||||
|
|
||||||
|
if item.id == originalItem.id and item.dmg == originalItem.dmg then
|
||||||
|
originalItem.status = '(not craftable)'
|
||||||
|
else
|
||||||
|
originalItem.status = item.name .. ' (missing)'
|
||||||
|
end
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
if item.id == originalItem.id and item.dmg == originalItem.dmg then
|
||||||
|
item.meCraft = true
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- find it in the list of items to be crafted
|
||||||
|
for _,v in pairs(itemList) do
|
||||||
|
if v.id == item.id and v.dmg == item.dmg and v.nbt_hash == item.nbt_hash then
|
||||||
|
v.qty = item.qty + v.qty
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- add to the item list
|
||||||
|
table.insert(itemList, {
|
||||||
|
id = item.id,
|
||||||
|
dmg = item.dmg,
|
||||||
|
nbt_hash = item.nbt_hash,
|
||||||
|
qty = item.qty,
|
||||||
|
name = item.name,
|
||||||
|
meCraft = true,
|
||||||
|
status = ''
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function craftItems(itemList)
|
||||||
|
|
||||||
|
local recipes = Util.readTable('recipes') or { }
|
||||||
|
local items = ME.getAvailableItems()
|
||||||
|
|
||||||
|
-- turtle craft anything we can, build up list for ME items
|
||||||
|
local keys = Util.keys(itemList)
|
||||||
|
for _,key in pairs(keys) do
|
||||||
|
local item = itemList[key]
|
||||||
|
craftItem(items, recipes, item, item, itemList)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- second pass is to request crafting from ME with aggregated items
|
||||||
|
for _,item in pairs(itemList) do
|
||||||
|
if item.meCraft then
|
||||||
|
|
||||||
|
local alreadyCrafting = false
|
||||||
|
local jobList = ME.getJobList()
|
||||||
|
|
||||||
|
for _,v in pairs(jobList) do
|
||||||
|
if v.id == item.id and v.dmg == item.dmg and v.nbt_hash == item.nbt_hash then
|
||||||
|
alreadyCrafting = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if alreadyCrafting then
|
||||||
|
item.status = '(crafting)'
|
||||||
|
elseif not ME.isCPUAvailable() then
|
||||||
|
item.status = '(waiting)'
|
||||||
|
else
|
||||||
|
item.status = '(failed)'
|
||||||
|
|
||||||
|
local qty = item.qty
|
||||||
|
while qty >= 1 do -- try to request smaller quantities until successful
|
||||||
|
if ME.craft(item.id, item.dmg, item.nbt_hash, qty) then
|
||||||
|
item.status = '(crafting)'
|
||||||
|
break -- successfully requested crafting
|
||||||
|
end
|
||||||
|
qty = math.floor(qty / 2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- AE 1 (obsolete)
|
||||||
|
function isCrafting(jobList, id, dmg)
|
||||||
|
for _, job in pairs(jobList) do
|
||||||
|
if job.id == id and job.dmg == dmg then
|
||||||
|
return job
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local nullDevice = {
|
||||||
|
setCursorPos = function(...) end,
|
||||||
|
write = function(...) end,
|
||||||
|
getSize = function() return 13, 20 end,
|
||||||
|
isColor = function() return false end,
|
||||||
|
setBackgroundColor = function(...) end,
|
||||||
|
setTextColor = function(...) end,
|
||||||
|
clear = function(...) end,
|
||||||
|
}
|
||||||
|
|
||||||
|
local function jobMonitor(jobList)
|
||||||
|
|
||||||
|
local mon
|
||||||
|
|
||||||
|
if device.monitor then
|
||||||
|
mon = UI.Device({
|
||||||
|
deviceType = 'monitor',
|
||||||
|
textScale = .5,
|
||||||
|
})
|
||||||
|
else
|
||||||
|
mon = UI.Device({
|
||||||
|
device = nullDevice
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
jobListGrid = UI.Grid({
|
||||||
|
parent = mon,
|
||||||
|
sortColumn = 'name',
|
||||||
|
columns = {
|
||||||
|
{ heading = 'Qty', key = 'qty', width = 6 },
|
||||||
|
{ heading = 'Crafting', key = 'name', width = mon.width / 2 - 10 },
|
||||||
|
{ heading = 'Status', key = 'status', width = mon.width - 10 },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
function getAutocraftItems(items)
|
||||||
|
local t = Util.readTable('resource.limits') or { }
|
||||||
|
local itemList = { }
|
||||||
|
|
||||||
|
for _,res in pairs(t) do
|
||||||
|
|
||||||
|
if res.auto and res.auto == 'yes' then
|
||||||
|
res.qty = 4 -- this could be higher to increase autocrafting speed
|
||||||
|
table.insert(itemList, res)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return itemList
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getItemWithQty(items, res, ignore_dmg)
|
||||||
|
|
||||||
|
local item = getItem(items, res, ignore_dmg)
|
||||||
|
|
||||||
|
if item then
|
||||||
|
|
||||||
|
if ignore_dmg and ignore_dmg == 'yes' then
|
||||||
|
local qty = 0
|
||||||
|
|
||||||
|
for _,v in pairs(items) do
|
||||||
|
if item.id == v.id and item.nbt_hash == v.nbt_hash then
|
||||||
|
if item.max_dmg > 0 or item.dmg == v.dmg then
|
||||||
|
qty = qty + v.qty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
item.qty = qty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return item
|
||||||
|
end
|
||||||
|
|
||||||
|
function watchResources(items)
|
||||||
|
|
||||||
|
local itemList = { }
|
||||||
|
|
||||||
|
local t = Util.readTable('resource.limits') or { }
|
||||||
|
for k, res in pairs(t) do
|
||||||
|
local item = getItemWithQty(items, res, res.ignore_dmg)
|
||||||
|
res.limit = tonumber(res.limit)
|
||||||
|
res.low = tonumber(res.low)
|
||||||
|
if not item then
|
||||||
|
item = {
|
||||||
|
id = res.id,
|
||||||
|
dmg = res.dmg,
|
||||||
|
nbt_hash = res.nbt_hash,
|
||||||
|
name = res.name,
|
||||||
|
qty = 0
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
if res.limit and item.qty > res.limit then
|
||||||
|
Logger.debug("Purging " .. item.qty-res.limit .. " " .. res.name)
|
||||||
|
if not ME.extract(item.id, item.dmg, item.nbt_hash, item.qty - res.limit, config.trashDirection) then
|
||||||
|
Logger.debug('Failed to purge ' .. res.name)
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif res.low and item.qty < res.low then
|
||||||
|
if res.ignore_dmg and res.ignore_dmg == 'yes' then
|
||||||
|
item.dmg = 0
|
||||||
|
end
|
||||||
|
table.insert(itemList, {
|
||||||
|
id = item.id,
|
||||||
|
dmg = item.dmg,
|
||||||
|
nbt_hash = item.nbt_hash,
|
||||||
|
qty = res.low - item.qty,
|
||||||
|
name = item.name,
|
||||||
|
status = ''
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return itemList
|
||||||
|
end
|
||||||
|
|
||||||
|
itemPage = UI.Page {
|
||||||
|
backgroundColor = colors.lightGray,
|
||||||
|
titleBar = UI.TitleBar {
|
||||||
|
title = 'Limit Resource',
|
||||||
|
previousPage = true,
|
||||||
|
event = 'form_cancel',
|
||||||
|
backgroundColor = colors.green
|
||||||
|
},
|
||||||
|
idField = UI.Text {
|
||||||
|
x = 5, y = 3, width = UI.term.width - 10,
|
||||||
|
},
|
||||||
|
form = UI.Form {
|
||||||
|
x = 4, y = 4, height = 8, rex = -4,
|
||||||
|
[1] = UI.TextEntry {
|
||||||
|
width = 7,
|
||||||
|
backgroundColor = colors.gray,
|
||||||
|
backgroundFocusColor = colors.gray,
|
||||||
|
formLabel = 'Min', formKey = 'low', help = 'Craft if below min'
|
||||||
|
},
|
||||||
|
[2] = UI.TextEntry {
|
||||||
|
width = 7,
|
||||||
|
backgroundColor = colors.gray,
|
||||||
|
backgroundFocusColor = colors.gray,
|
||||||
|
formLabel = 'Max', formKey = 'limit', help = 'Eject if above max'
|
||||||
|
},
|
||||||
|
[3] = UI.Chooser {
|
||||||
|
width = 7,
|
||||||
|
formLabel = 'Autocraft', formKey = 'auto',
|
||||||
|
nochoice = 'No',
|
||||||
|
choices = {
|
||||||
|
{ name = 'Yes', value = 'yes' },
|
||||||
|
{ name = 'No', value = 'no' },
|
||||||
|
},
|
||||||
|
help = 'Craft until out of ingredients'
|
||||||
|
},
|
||||||
|
[4] = UI.Chooser {
|
||||||
|
width = 7,
|
||||||
|
formLabel = 'Ignore Dmg', formKey = 'ignore_dmg',
|
||||||
|
nochoice = 'No',
|
||||||
|
choices = {
|
||||||
|
{ name = 'Yes', value = 'yes' },
|
||||||
|
{ name = 'No', value = 'no' },
|
||||||
|
},
|
||||||
|
help = 'Ignore damage of item'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
statusBar = UI.StatusBar { }
|
||||||
|
}
|
||||||
|
|
||||||
|
function itemPage:enable()
|
||||||
|
UI.Page.enable(self)
|
||||||
|
self:focusFirst()
|
||||||
|
end
|
||||||
|
|
||||||
|
function itemPage:eventHandler(event)
|
||||||
|
if event.type == 'form_cancel' then
|
||||||
|
UI:setPreviousPage()
|
||||||
|
|
||||||
|
elseif event.type == 'focus_change' then
|
||||||
|
self.statusBar:setStatus(event.focused.help)
|
||||||
|
self.statusBar:draw()
|
||||||
|
|
||||||
|
elseif event.type == 'form_complete' then
|
||||||
|
local values = self.form.values
|
||||||
|
local t = Util.readTable('resource.limits') or { }
|
||||||
|
for k,v in pairs(t) do
|
||||||
|
if v.id == values.id and v.dmg == values.dmg then
|
||||||
|
table.remove(t, k)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local keys = { 'name', 'auto', 'id', 'low', 'dmg', 'max_dmg', 'nbt_hash', 'limit', 'ignore_dmg' }
|
||||||
|
local filtered = { }
|
||||||
|
for _,key in pairs(keys) do
|
||||||
|
filtered[key] = values[key]
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(t, filtered)
|
||||||
|
Util.writeTable('resource.limits', t)
|
||||||
|
UI:setPreviousPage()
|
||||||
|
|
||||||
|
else
|
||||||
|
return UI.Page.eventHandler(self, event)
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
listingPage = UI.Page {
|
||||||
|
menuBar = UI.MenuBar {
|
||||||
|
buttons = {
|
||||||
|
{ text = 'Learn', event = 'learn' },
|
||||||
|
{ text = 'Forget', event = 'forget' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
grid = UI.Grid {
|
||||||
|
y = 2, height = UI.term.height - 2,
|
||||||
|
columns = {
|
||||||
|
{ heading = 'Name', key = 'name' , width = 22 },
|
||||||
|
{ heading = 'Qty', key = 'qty' , width = 5 },
|
||||||
|
{ heading = 'Min', key = 'low' , width = 4 },
|
||||||
|
{ heading = 'Max', key = 'limit', width = 4 },
|
||||||
|
},
|
||||||
|
sortColumn = 'name',
|
||||||
|
},
|
||||||
|
statusBar = UI.StatusBar {
|
||||||
|
backgroundColor = colors.gray,
|
||||||
|
width = UI.term.width,
|
||||||
|
filterText = UI.Text {
|
||||||
|
x = 2, width = 6,
|
||||||
|
value = 'Filter',
|
||||||
|
},
|
||||||
|
filter = UI.TextEntry {
|
||||||
|
x = 9, width = 19,
|
||||||
|
limit = 50,
|
||||||
|
},
|
||||||
|
refresh = UI.Button {
|
||||||
|
x = 31, width = 8,
|
||||||
|
text = 'Refresh',
|
||||||
|
event = 'refresh',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
accelerators = {
|
||||||
|
r = 'refresh',
|
||||||
|
q = 'quit',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function listingPage.grid:getRowTextColor(row, selected)
|
||||||
|
if row.is_craftable then
|
||||||
|
return colors.yellow
|
||||||
|
end
|
||||||
|
if row.has_recipe then
|
||||||
|
if selected then
|
||||||
|
return colors.blue
|
||||||
|
end
|
||||||
|
return colors.lightBlue
|
||||||
|
end
|
||||||
|
return UI.Grid:getRowTextColor(row, selected)
|
||||||
|
end
|
||||||
|
|
||||||
|
function listingPage.grid:getDisplayValues(row)
|
||||||
|
row = Util.shallowCopy(row)
|
||||||
|
row.qty = Util.toBytes(row.qty)
|
||||||
|
if row.low then
|
||||||
|
row.low = Util.toBytes(row.low)
|
||||||
|
end
|
||||||
|
if row.limit then
|
||||||
|
row.limit = Util.toBytes(row.limit)
|
||||||
|
end
|
||||||
|
return row
|
||||||
|
end
|
||||||
|
|
||||||
|
function listingPage.statusBar:draw()
|
||||||
|
return UI.Window.draw(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
function listingPage.statusBar.filter:eventHandler(event)
|
||||||
|
if event.type == 'mouse_rightclick' then
|
||||||
|
self.value = ''
|
||||||
|
self:draw()
|
||||||
|
local page = UI:getCurrentPage()
|
||||||
|
page.filter = nil
|
||||||
|
page:applyFilter()
|
||||||
|
page.grid:draw()
|
||||||
|
page:setFocus(self)
|
||||||
|
end
|
||||||
|
return UI.TextEntry.eventHandler(self, event)
|
||||||
|
end
|
||||||
|
|
||||||
|
function listingPage:eventHandler(event)
|
||||||
|
if event.type == 'quit' then
|
||||||
|
Event.exitPullEvents()
|
||||||
|
|
||||||
|
elseif event.type == 'grid_select' then
|
||||||
|
local selected = event.selected
|
||||||
|
itemPage.form:setValues(selected)
|
||||||
|
itemPage.titleBar.title = selected.name
|
||||||
|
itemPage.idField.value = selected.id
|
||||||
|
UI:setPage('item')
|
||||||
|
|
||||||
|
elseif event.type == 'refresh' then
|
||||||
|
self:refresh()
|
||||||
|
self.grid:draw()
|
||||||
|
|
||||||
|
elseif event.type == 'learn' then
|
||||||
|
if not duckAntenna then
|
||||||
|
self.statusBar:timedStatus('Missing peripherals', 3)
|
||||||
|
else
|
||||||
|
UI:setPage('craft')
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif event.type == 'forget' then
|
||||||
|
local item = self.grid:getSelected()
|
||||||
|
if item then
|
||||||
|
local recipes = Util.readTable('recipes') or { }
|
||||||
|
local key = uniqueKey(item)
|
||||||
|
local recipe = recipes[key]
|
||||||
|
|
||||||
|
if recipe then
|
||||||
|
recipes[key] = nil
|
||||||
|
Util.writeTable('recipes', recipes)
|
||||||
|
end
|
||||||
|
|
||||||
|
local resources = Util.readTable('resource.limits') or { }
|
||||||
|
for k,v in pairs(resources) do
|
||||||
|
if v.id == item.id and v.dmg == item.dmg then
|
||||||
|
table.remove(resources, k)
|
||||||
|
Util.writeTable('resource.limits', resources)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
self.statusBar:timedStatus('Forgot: ' .. item.name, 3)
|
||||||
|
self:refresh()
|
||||||
|
self.grid:draw()
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif event.type == 'text_change' then
|
||||||
|
self.filter = event.text
|
||||||
|
if #self.filter == 0 then
|
||||||
|
self.filter = nil
|
||||||
|
end
|
||||||
|
self:applyFilter()
|
||||||
|
self.grid:draw()
|
||||||
|
self.statusBar.filter:focus()
|
||||||
|
|
||||||
|
else
|
||||||
|
UI.Page.eventHandler(self, event)
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function listingPage:enable()
|
||||||
|
self:refresh()
|
||||||
|
self:setFocus(self.statusBar.filter)
|
||||||
|
UI.Page.enable(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
function listingPage:refresh()
|
||||||
|
self.allItems = ME.getAvailableItems('all')
|
||||||
|
|
||||||
|
mergeResources(self.allItems)
|
||||||
|
|
||||||
|
Util.each(self.allItems, function(item)
|
||||||
|
item.lname = item.name:lower()
|
||||||
|
end)
|
||||||
|
|
||||||
|
self:applyFilter()
|
||||||
|
end
|
||||||
|
|
||||||
|
function listingPage:applyFilter()
|
||||||
|
local t = filterItems(self.allItems, self.filter)
|
||||||
|
self.grid:setValues(t)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- without duck antenna
|
||||||
|
local function getTurtleInventory()
|
||||||
|
local inventory = { }
|
||||||
|
for i = 1,16 do
|
||||||
|
if turtle.getItemCount(i) > 0 then
|
||||||
|
turtle.select(i)
|
||||||
|
local item = turtle.getItemDetail()
|
||||||
|
inventory[i] = {
|
||||||
|
id = item.name,
|
||||||
|
dmg = item.damage,
|
||||||
|
qty = item.count,
|
||||||
|
name = item.name,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return inventory
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Strip off color prefix
|
||||||
|
local function safeString(text)
|
||||||
|
|
||||||
|
local val = text:byte(1)
|
||||||
|
|
||||||
|
if val < 32 or val > 128 then
|
||||||
|
|
||||||
|
local newText = {}
|
||||||
|
for i = 4, #text do
|
||||||
|
local val = text:byte(i)
|
||||||
|
newText[i - 3] = (val > 31 and val < 127) and val or 63
|
||||||
|
end
|
||||||
|
return string.char(unpack(newText))
|
||||||
|
end
|
||||||
|
|
||||||
|
return text
|
||||||
|
end
|
||||||
|
|
||||||
|
local function filter(t, filter)
|
||||||
|
local keys = Util.keys(t)
|
||||||
|
for _,key in pairs(keys) do
|
||||||
|
if not Util.key(filter, key) then
|
||||||
|
t[key] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function learnRecipe(page)
|
||||||
|
local t = Util.readTable('recipes') or { }
|
||||||
|
local recipe = { }
|
||||||
|
local ingredients = duckAntenna.getAllStacks(false) -- getTurtleInventory()
|
||||||
|
if ingredients then
|
||||||
|
turtle.select(1)
|
||||||
|
if turtle.craft() then
|
||||||
|
recipe = duckAntenna.getAllStacks(false) -- getTurtleInventory()
|
||||||
|
if recipe and recipe[1] then
|
||||||
|
recipe = recipe[1]
|
||||||
|
local key = uniqueKey(recipe)
|
||||||
|
|
||||||
|
clearGrid()
|
||||||
|
|
||||||
|
recipe.name = safeString(recipe.display_name)
|
||||||
|
filter(recipe, { 'name', 'id', 'dmg', 'nbt_hash', 'qty', 'max_size' })
|
||||||
|
|
||||||
|
for _,ingredient in pairs(ingredients) do
|
||||||
|
ingredient.name = safeString(ingredient.display_name)
|
||||||
|
filter(ingredient, { 'name', 'id', 'dmg', 'nbt_hash', 'qty', 'max_size', 'max_dmg' })
|
||||||
|
|
||||||
|
if ingredient.max_dmg > 0 then -- let's try this...
|
||||||
|
ingredient.dmg = 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
recipe.ingredients = ingredients
|
||||||
|
recipe.ignore_dmg = 'no'
|
||||||
|
|
||||||
|
t[key] = recipe
|
||||||
|
|
||||||
|
Util.writeTable('recipes', t)
|
||||||
|
listingPage.statusBar.filter:setValue(recipe.name)
|
||||||
|
listingPage.statusBar:timedStatus('Learned: ' .. recipe.name, 3)
|
||||||
|
listingPage.filter = recipe.name
|
||||||
|
listingPage:refresh()
|
||||||
|
listingPage.grid:draw()
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
else
|
||||||
|
page.statusBar:timedStatus('Failed to craft', 3)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
page.statusBar:timedStatus('No recipe defined', 3)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
craftPage = UI.Dialog {
|
||||||
|
height = 7, width = UI.term.width - 6,
|
||||||
|
backgroundColor = colors.lightGray,
|
||||||
|
titleBar = UI.TitleBar {
|
||||||
|
title = 'Learn Recipe',
|
||||||
|
previousPage = true,
|
||||||
|
},
|
||||||
|
idField = UI.Text {
|
||||||
|
x = 5,
|
||||||
|
y = 3,
|
||||||
|
width = UI.term.width - 10,
|
||||||
|
value = 'Place recipe in turtle'
|
||||||
|
},
|
||||||
|
accept = UI.Button {
|
||||||
|
rx = -13, ry = -2,
|
||||||
|
text = 'Ok', event = 'accept',
|
||||||
|
},
|
||||||
|
cancel = UI.Button {
|
||||||
|
rx = -8, ry = -2,
|
||||||
|
text = 'Cancel', event = 'cancel'
|
||||||
|
},
|
||||||
|
statusBar = UI.StatusBar {
|
||||||
|
status = 'Crafting paused'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function craftPage:enable()
|
||||||
|
craftingPaused = true
|
||||||
|
self:focusFirst()
|
||||||
|
UI.Dialog.enable(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
function craftPage:disable()
|
||||||
|
craftingPaused = false
|
||||||
|
UI.Dialog.disable(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
function craftPage:eventHandler(event)
|
||||||
|
if event.type == 'cancel' then
|
||||||
|
UI:setPreviousPage()
|
||||||
|
elseif event.type == 'accept' then
|
||||||
|
if learnRecipe(self) then
|
||||||
|
UI:setPreviousPage()
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return UI.Dialog.eventHandler(self, event)
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
UI:setPages({
|
||||||
|
listing = listingPage,
|
||||||
|
item = itemPage,
|
||||||
|
craft = craftPage,
|
||||||
|
})
|
||||||
|
|
||||||
|
UI:setPage(listingPage)
|
||||||
|
listingPage:setFocus(listingPage.statusBar.filter)
|
||||||
|
|
||||||
|
clearGrid()
|
||||||
|
jobMonitor()
|
||||||
|
jobListGrid:draw()
|
||||||
|
jobListGrid:sync()
|
||||||
|
|
||||||
|
Event.onInterval(5, function()
|
||||||
|
|
||||||
|
if not craftingPaused then
|
||||||
|
|
||||||
|
local items = ME.getAvailableItems()
|
||||||
|
|
||||||
|
if Util.size(items) == 0 then
|
||||||
|
jobListGrid.parent:clear()
|
||||||
|
jobListGrid.parent:centeredWrite(math.ceil(jobListGrid.parent.height/2), 'No items in system')
|
||||||
|
jobListGrid:sync()
|
||||||
|
|
||||||
|
elseif config.noCraftingStorage ~= 'true' and #ME.getCraftingCPUs() <= 0 then -- only way to determine if AE is online
|
||||||
|
jobListGrid.parent:clear()
|
||||||
|
jobListGrid.parent:centeredWrite(math.ceil(jobListGrid.parent.height/2), 'Power failure')
|
||||||
|
jobListGrid:sync()
|
||||||
|
|
||||||
|
else
|
||||||
|
local itemList = watchResources(items)
|
||||||
|
jobListGrid:setValues(itemList)
|
||||||
|
jobListGrid:draw()
|
||||||
|
jobListGrid:sync()
|
||||||
|
craftItems(itemList)
|
||||||
|
jobListGrid:update()
|
||||||
|
jobListGrid:draw()
|
||||||
|
jobListGrid:sync()
|
||||||
|
|
||||||
|
itemList = getAutocraftItems(items) -- autocrafted items don't show on job monitor
|
||||||
|
craftItems(itemList)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
UI:pullEvents()
|
||||||
|
jobListGrid.parent:reset()
|
||||||
438
apps/supplier.lua
Normal file
438
apps/supplier.lua
Normal file
@@ -0,0 +1,438 @@
|
|||||||
|
requireInjector(getfenv(1))
|
||||||
|
|
||||||
|
local Event = require('event')
|
||||||
|
local Logger = require('logger')
|
||||||
|
local MEProvider = require('meProvider')
|
||||||
|
local Message = require('message')
|
||||||
|
local Point = require('point')
|
||||||
|
local TableDB = require('tableDB')
|
||||||
|
local Util = require('util')
|
||||||
|
|
||||||
|
--[[
|
||||||
|
A supplier turtle for the builder turtle. For larger builds, use
|
||||||
|
ender modems.
|
||||||
|
|
||||||
|
Setup:
|
||||||
|
|
||||||
|
1. chest or ME interface at level 0 (bottom of build area)
|
||||||
|
2. builder turtle on top facing the build area
|
||||||
|
3. If facing the build turtle, the supplier turtle is to the right
|
||||||
|
pointing at the chest/interface
|
||||||
|
]]--
|
||||||
|
|
||||||
|
local ChestProvider = require('chestProvider')
|
||||||
|
if Util.getVersion() == 1.8 then
|
||||||
|
ChestProvider = require('chestProvider18')
|
||||||
|
end
|
||||||
|
|
||||||
|
if not device.wireless_modem then
|
||||||
|
error('No wireless modem detected')
|
||||||
|
end
|
||||||
|
|
||||||
|
Logger.filter('modem_send', 'event', 'ui')
|
||||||
|
Logger.setWirelessLogging()
|
||||||
|
|
||||||
|
local __BUILDER_ID = 6
|
||||||
|
local itemInfoDB
|
||||||
|
|
||||||
|
local Builder = {
|
||||||
|
version = '1.70',
|
||||||
|
ccVersion = nil,
|
||||||
|
slots = { },
|
||||||
|
index = 1,
|
||||||
|
fuelItem = { id = 'minecraft:coal', dmg = 0 },
|
||||||
|
resupplying = true,
|
||||||
|
ready = true,
|
||||||
|
}
|
||||||
|
|
||||||
|
--[[-- maxStackDB --]]--
|
||||||
|
local maxStackDB = TableDB({
|
||||||
|
fileName = 'maxstack.db',
|
||||||
|
tabledef = {
|
||||||
|
autokeys = false,
|
||||||
|
type = 'simple',
|
||||||
|
columns = {
|
||||||
|
{ label = 'Key', type = 'key', length = 8 },
|
||||||
|
{ label = 'Quantity', type = 'number', length = 2 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function maxStackDB:get(id, dmg)
|
||||||
|
return self.data[id .. ':' .. dmg] or 64
|
||||||
|
end
|
||||||
|
|
||||||
|
function Builder:dumpInventory()
|
||||||
|
|
||||||
|
local success = true
|
||||||
|
|
||||||
|
for i = 1, 16 do
|
||||||
|
local qty = turtle.getItemCount(i)
|
||||||
|
if qty > 0 then
|
||||||
|
self.itemProvider:insert(i, qty)
|
||||||
|
end
|
||||||
|
if turtle.getItemCount(i) ~= 0 then
|
||||||
|
success = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
turtle.select(1)
|
||||||
|
|
||||||
|
return success
|
||||||
|
end
|
||||||
|
|
||||||
|
function Builder:dumpInventoryWithCheck()
|
||||||
|
while not self:dumpInventory() do
|
||||||
|
Builder:log('Unable to dump inventory')
|
||||||
|
print('Provider is full or missing - make space or replace')
|
||||||
|
print('Press enter to continue')
|
||||||
|
--turtle.setHeading(0)
|
||||||
|
self.ready = false
|
||||||
|
read()
|
||||||
|
end
|
||||||
|
self.ready = true
|
||||||
|
end
|
||||||
|
|
||||||
|
function Builder:autocraft(supplies)
|
||||||
|
local t = { }
|
||||||
|
|
||||||
|
for i,s in pairs(supplies) do
|
||||||
|
local key = s.id .. ':' .. s.dmg
|
||||||
|
local item = t[key]
|
||||||
|
if not item then
|
||||||
|
item = {
|
||||||
|
id = s.id,
|
||||||
|
dmg = s.dmg,
|
||||||
|
qty = 0,
|
||||||
|
}
|
||||||
|
t[key] = item
|
||||||
|
end
|
||||||
|
item.qty = item.qty + (s.need-s.qty)
|
||||||
|
end
|
||||||
|
|
||||||
|
Builder.itemProvider:craftItems(t)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Builder:refuel()
|
||||||
|
while turtle.getFuelLevel() < 4000 and self.fuelItem do
|
||||||
|
Builder:log('Refueling')
|
||||||
|
turtle.select(1)
|
||||||
|
self.itemProvider:provide(self.fuelItem, 64, 1)
|
||||||
|
if turtle.getItemCount(1) == 0 then
|
||||||
|
Builder:log('Out of fuel, add coal to chest/ME system')
|
||||||
|
--turtle.setHeading(0)
|
||||||
|
os.sleep(5)
|
||||||
|
else
|
||||||
|
turtle.refuel(64)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Builder:log(...)
|
||||||
|
Logger.log('supplier', ...)
|
||||||
|
Util.print(...)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Builder:getSupplies()
|
||||||
|
|
||||||
|
Builder.itemProvider:refresh()
|
||||||
|
|
||||||
|
local t = { }
|
||||||
|
for _,s in ipairs(self.slots) do
|
||||||
|
if s.need > 0 then
|
||||||
|
local item = Builder.itemProvider:getItemInfo(s.id, s.dmg)
|
||||||
|
if item then
|
||||||
|
if item.name then
|
||||||
|
s.name = item.name
|
||||||
|
end
|
||||||
|
|
||||||
|
local qty = math.min(s.need-s.qty, item.qty)
|
||||||
|
|
||||||
|
if qty + s.qty > item.max_size then
|
||||||
|
maxStackDB:add({ s.id, s.dmg }, item.max_size)
|
||||||
|
maxStackDB.dirty = true
|
||||||
|
maxStackDB:flush()
|
||||||
|
qty = item.max_size
|
||||||
|
s.need = qty
|
||||||
|
end
|
||||||
|
if qty > 0 then
|
||||||
|
self.itemProvider:provide(item, qty, s.index)
|
||||||
|
s.qty = turtle.getItemCount(s.index)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if s.qty < s.need then
|
||||||
|
table.insert(t, s)
|
||||||
|
local name = s.name or s.id .. ':' .. s.dmg
|
||||||
|
local item = itemInfoDB:get({ s.id, s.dmg })
|
||||||
|
if item then
|
||||||
|
name = item.displayName
|
||||||
|
end
|
||||||
|
|
||||||
|
Builder:log('Need %d %s', s.need - s.qty, name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
local function moveTowardsX(dx)
|
||||||
|
|
||||||
|
local direction = dx - turtle.point.x
|
||||||
|
local move
|
||||||
|
|
||||||
|
if direction == 0 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
if direction > 0 and turtle.point.heading == 0 or
|
||||||
|
direction < 0 and turtle.point.heading == 2 then
|
||||||
|
move = turtle.forward
|
||||||
|
else
|
||||||
|
move = turtle.back
|
||||||
|
end
|
||||||
|
|
||||||
|
return move()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function moveTowardsZ(dz)
|
||||||
|
|
||||||
|
local direction = dz - turtle.point.z
|
||||||
|
local move
|
||||||
|
|
||||||
|
if direction == 0 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
if direction > 0 and turtle.point.heading == 1 or
|
||||||
|
direction < 0 and turtle.point.heading == 3 then
|
||||||
|
move = turtle.forward
|
||||||
|
else
|
||||||
|
move = turtle.back
|
||||||
|
end
|
||||||
|
|
||||||
|
return move()
|
||||||
|
end
|
||||||
|
|
||||||
|
function Builder:finish()
|
||||||
|
|
||||||
|
Builder.resupplying = true
|
||||||
|
Builder.ready = false
|
||||||
|
if turtle.gotoLocation('supplies') then
|
||||||
|
turtle.setHeading(1)
|
||||||
|
os.sleep(.1) -- random 'Computer is not connected' error...
|
||||||
|
Builder:dumpInventory()
|
||||||
|
Event.exitPullEvents()
|
||||||
|
print('Finished')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Builder:gotoBuilder()
|
||||||
|
|
||||||
|
if Builder.lastPoint then
|
||||||
|
turtle.status = 'tracking'
|
||||||
|
while true do
|
||||||
|
local pt = Point.copy(Builder.lastPoint)
|
||||||
|
pt.y = pt.y + 3
|
||||||
|
if turtle.point.y ~= pt.y then
|
||||||
|
turtle.gotoY(pt.y)
|
||||||
|
else
|
||||||
|
local distance = Point.turtleDistance(turtle.point, pt)
|
||||||
|
if distance <= 3 then
|
||||||
|
Builder:log('Synchronized')
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
if turtle.point.heading % 2 == 0 then
|
||||||
|
if turtle.point.x == pt.x then
|
||||||
|
turtle.headTowardsZ(pt.z)
|
||||||
|
moveTowardsZ(pt.z)
|
||||||
|
else
|
||||||
|
moveTowardsX(pt.x)
|
||||||
|
end
|
||||||
|
elseif turtle.point.z ~= pt.z then
|
||||||
|
moveTowardsZ(pt.z)
|
||||||
|
else
|
||||||
|
turtle.headTowardsX(pt.x)
|
||||||
|
moveTowardsX(pt.x)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Message.addHandler('builder',
|
||||||
|
function(h, id, msg, distance)
|
||||||
|
if not id or id ~= __BUILDER_ID then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if not Builder.resupplying then
|
||||||
|
local pt = msg.contents
|
||||||
|
pt.y = pt.y + 3
|
||||||
|
|
||||||
|
turtle.status = 'supervising'
|
||||||
|
turtle.gotoYfirst(pt)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
Message.addHandler('supplyList',
|
||||||
|
function(h, id, msg, distance)
|
||||||
|
if not id or id ~= __BUILDER_ID then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
turtle.status = 'resupplying'
|
||||||
|
Builder.resupplying = true
|
||||||
|
Builder.slots = msg.contents.slots
|
||||||
|
Builder.slotUid = msg.contents.uid
|
||||||
|
|
||||||
|
Builder:log('Received supply list ' .. Builder.slotUid)
|
||||||
|
|
||||||
|
os.sleep(0)
|
||||||
|
if not turtle.gotoLocation('supplies') then
|
||||||
|
Builder:log('Failed to go to supply location')
|
||||||
|
self.ready = false
|
||||||
|
Event.exitPullEvents()
|
||||||
|
end
|
||||||
|
turtle.setHeading(1)
|
||||||
|
os.sleep(.2) -- random 'Computer is not connected' error...
|
||||||
|
Builder:dumpInventoryWithCheck()
|
||||||
|
Builder:refuel()
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local supplies = Builder:getSupplies()
|
||||||
|
if #supplies == 0 then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
Builder:autocraft(supplies)
|
||||||
|
turtle.status = 'waiting'
|
||||||
|
os.sleep(5)
|
||||||
|
end
|
||||||
|
Builder:log('Got all supplies')
|
||||||
|
os.sleep(0)
|
||||||
|
Builder:gotoBuilder()
|
||||||
|
Builder.resupplying = false
|
||||||
|
end)
|
||||||
|
|
||||||
|
Message.addHandler('needSupplies',
|
||||||
|
function(h, id, msg, distance)
|
||||||
|
if not id or id ~= __BUILDER_ID then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if Builder.resupplying or msg.contents.uid ~= Builder.slotUid then
|
||||||
|
|
||||||
|
Builder:log('No supplies ready')
|
||||||
|
|
||||||
|
Message.send(__BUILDER_ID, 'gotSupplies')
|
||||||
|
else
|
||||||
|
turtle.status = 'supplying'
|
||||||
|
Builder:log('Supplying')
|
||||||
|
os.sleep(0)
|
||||||
|
|
||||||
|
local pt = msg.contents.point
|
||||||
|
pt.y = turtle.getPoint().y
|
||||||
|
pt.heading = nil
|
||||||
|
if not turtle.gotoYfirst(pt) then -- location of builder
|
||||||
|
Builder.resupplying = true
|
||||||
|
Message.send(__BUILDER_ID, 'gotSupplies')
|
||||||
|
os.sleep(0)
|
||||||
|
if not turtle.gotoLocation('supplies') then
|
||||||
|
Builder:log('failed to go to supply location')
|
||||||
|
--self.ready = false
|
||||||
|
Event.exitPullEvents()
|
||||||
|
end
|
||||||
|
turtle.setHeading(1)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
pt.y = pt.y - 2 -- location where builder should go for the chest to be above
|
||||||
|
|
||||||
|
turtle.select(15)
|
||||||
|
turtle.placeDown()
|
||||||
|
os.sleep(.1) -- random computer not connected error
|
||||||
|
local p = ChestProvider({ direction = 'up', wrapSide = 'bottom' })
|
||||||
|
for i = 1, 16 do
|
||||||
|
p:insert(i, 64)
|
||||||
|
end
|
||||||
|
|
||||||
|
Message.send(__BUILDER_ID, 'gotSupplies', { supplies = true, point = pt })
|
||||||
|
|
||||||
|
Message.waitForMessage('thanks', 5, __BUILDER_ID)
|
||||||
|
--os.sleep(0)
|
||||||
|
|
||||||
|
--p.condenseItems()
|
||||||
|
for i = 1, 16 do
|
||||||
|
p:extract(i, 64)
|
||||||
|
end
|
||||||
|
turtle.digDown()
|
||||||
|
turtle.status = 'waiting'
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
Message.addHandler('finished',
|
||||||
|
function(h, id)
|
||||||
|
if not id or id ~= __BUILDER_ID then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
Builder:finish()
|
||||||
|
end)
|
||||||
|
|
||||||
|
Event.on('turtle_abort',
|
||||||
|
function()
|
||||||
|
turtle.abort = false
|
||||||
|
turtle.status = 'aborting'
|
||||||
|
Builder:finish()
|
||||||
|
end)
|
||||||
|
|
||||||
|
local function onTheWay() -- parallel routine
|
||||||
|
while true do
|
||||||
|
local e, side, _id, id, msg, distance = os.pullEvent('modem_message')
|
||||||
|
if Builder.ready then
|
||||||
|
if id == __BUILDER_ID and msg and msg.type then
|
||||||
|
if msg.type == 'needSupplies' then
|
||||||
|
Message.send(__BUILDER_ID, 'gotSupplies', { supplies = true })
|
||||||
|
elseif msg.type == 'builder' then
|
||||||
|
Builder.lastPoint = msg.contents
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local args = {...}
|
||||||
|
if #args < 2 then
|
||||||
|
error('syntax: <builder id> <facing>')
|
||||||
|
end
|
||||||
|
|
||||||
|
__BUILDER_ID = tonumber(args[1])
|
||||||
|
|
||||||
|
maxStackDB:load()
|
||||||
|
|
||||||
|
itemInfoDB = TableDB({
|
||||||
|
fileName = 'items.db'
|
||||||
|
})
|
||||||
|
|
||||||
|
itemInfoDB:load()
|
||||||
|
|
||||||
|
Builder.itemProvider = MEProvider({ direction = args[2] })
|
||||||
|
if not Builder.itemProvider:isValid() then
|
||||||
|
local sides = {
|
||||||
|
east = 'west',
|
||||||
|
west = 'east',
|
||||||
|
north = 'south',
|
||||||
|
south = 'north',
|
||||||
|
}
|
||||||
|
|
||||||
|
Builder.itemProvider = ChestProvider({ direction = sides[args[2]], wrapSide = 'front' })
|
||||||
|
if not Builder.itemProvider:isValid() then
|
||||||
|
error('A chest or ME interface must be in front of turtle')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
turtle.run(function()
|
||||||
|
turtle.setPoint({ x = -1, z = -2, y = -1, heading = 1 })
|
||||||
|
|
||||||
|
turtle.saveLocation('supplies')
|
||||||
|
|
||||||
|
Event.pullEvents(onTheWay)
|
||||||
|
end)
|
||||||
89
apps/t.lua
Normal file
89
apps/t.lua
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
function doCommand(command, moves)
|
||||||
|
|
||||||
|
local function format(value)
|
||||||
|
if type(value) == 'boolean' then
|
||||||
|
if value then return 'true' end
|
||||||
|
return 'false'
|
||||||
|
end
|
||||||
|
if type(value) ~= 'table' then
|
||||||
|
return value
|
||||||
|
end
|
||||||
|
local str
|
||||||
|
for k,v in pairs(value) do
|
||||||
|
if not str then
|
||||||
|
str = '{ '
|
||||||
|
else
|
||||||
|
str = str .. ', '
|
||||||
|
end
|
||||||
|
str = str .. k .. '=' .. tostring(v)
|
||||||
|
end
|
||||||
|
if str then
|
||||||
|
str = str .. ' }'
|
||||||
|
else
|
||||||
|
str = '{ }'
|
||||||
|
end
|
||||||
|
|
||||||
|
return str
|
||||||
|
end
|
||||||
|
|
||||||
|
local function runCommand(fn, arg)
|
||||||
|
local r = { fn(arg) }
|
||||||
|
if r[2] then
|
||||||
|
print(format(r[1]) .. ': ' .. format(r[2]))
|
||||||
|
elseif r[1] then
|
||||||
|
print(format(r[1]))
|
||||||
|
end
|
||||||
|
return r[1]
|
||||||
|
end
|
||||||
|
|
||||||
|
local cmds = {
|
||||||
|
[ 's' ] = turtle.select,
|
||||||
|
[ 'rf' ] = turtle.refuel,
|
||||||
|
[ 'gh' ] = function() turtle.pathfind({ x = 0, y = 0, z = 0, heading = 0}) end,
|
||||||
|
}
|
||||||
|
|
||||||
|
local repCmds = {
|
||||||
|
[ 'u' ] = turtle.up,
|
||||||
|
[ 'd' ] = turtle.down,
|
||||||
|
[ 'f' ] = turtle.forward,
|
||||||
|
[ 'r' ] = turtle.turnRight,
|
||||||
|
[ 'l' ] = turtle.turnLeft,
|
||||||
|
[ 'ta' ] = turtle.turnAround,
|
||||||
|
[ 'DD' ] = turtle.digDown,
|
||||||
|
[ 'DU' ] = turtle.digUp,
|
||||||
|
[ 'D' ] = turtle.dig,
|
||||||
|
[ 'p' ] = turtle.place,
|
||||||
|
[ 'pu' ] = turtle.placeUp,
|
||||||
|
[ 'pd' ] = turtle.placeDown,
|
||||||
|
[ 'b' ] = turtle.back,
|
||||||
|
[ 'gfl' ] = turtle.getFuelLevel,
|
||||||
|
[ 'gp' ] = turtle.getPoint,
|
||||||
|
[ 'R' ] = function() turtle.setPoint({x = 0, y = 0, z = 0, heading = 0}) return turtle.point end
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmds[command] then
|
||||||
|
runCommand(cmds[command], moves)
|
||||||
|
elseif repCmds[command] then
|
||||||
|
for i = 1, moves do
|
||||||
|
if not runCommand(repCmds[command]) then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local args = {...}
|
||||||
|
|
||||||
|
if #args > 0 then
|
||||||
|
doCommand(args[1], args[2] or 1)
|
||||||
|
else
|
||||||
|
print('Enter command (q to quit):')
|
||||||
|
while true do
|
||||||
|
local cmd = read()
|
||||||
|
if cmd == 'q' then break
|
||||||
|
end
|
||||||
|
args = { }
|
||||||
|
cmd:gsub('%w+', function(w) table.insert(args, w) end)
|
||||||
|
doCommand(args[1], args[2] or 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
743
apps/treefarm.lua
Normal file
743
apps/treefarm.lua
Normal file
@@ -0,0 +1,743 @@
|
|||||||
|
requireInjector(getfenv(1))
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Requirements:
|
||||||
|
Place turtle against an oak tree or oak sapling
|
||||||
|
Area around turtle must be flat and can only be dirt or grass
|
||||||
|
(10 blocks in each direction from turtle)
|
||||||
|
Turtle must have: crafting table, chest
|
||||||
|
Turtle must have a pick equipped on the left side
|
||||||
|
|
||||||
|
Optional:
|
||||||
|
Add additional sapling types that can grow with a single sapling
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
If the turtle does not get any saplings from the initial tree, place
|
||||||
|
down another sapling in front of the turtle.
|
||||||
|
|
||||||
|
The program will be able to survive server restarts as long as it has
|
||||||
|
created the cobblestone line. If the program is stopped before that time,
|
||||||
|
place the turtle in the original position before restarting the program.
|
||||||
|
]]--
|
||||||
|
|
||||||
|
local ChestAdapter = require('chestAdapter18')
|
||||||
|
local Craft = require('turtle.craft')
|
||||||
|
local Level = require('turtle.level')
|
||||||
|
local Pathing = require('turtle.pathfind')
|
||||||
|
local Point = require('point')
|
||||||
|
local Util = require('util')
|
||||||
|
|
||||||
|
local FUEL_BASE = 0
|
||||||
|
local FUEL_DIRE = FUEL_BASE + 10
|
||||||
|
local FUEL_GOOD = FUEL_BASE + 2000
|
||||||
|
|
||||||
|
local MIN_CHARCOAL = 24
|
||||||
|
local MAX_SAPLINGS = 32
|
||||||
|
|
||||||
|
local GRID_WIDTH = 8
|
||||||
|
local GRID_LENGTH = 10
|
||||||
|
local GRID = {
|
||||||
|
TL = { x = 8, y = 0, z = -8 },
|
||||||
|
TR = { x = 8, y = 0, z = 8 },
|
||||||
|
BL = { x = -10, y = 0, z = -8 },
|
||||||
|
BR = { x = -10, y = 0, z = 8 },
|
||||||
|
}
|
||||||
|
|
||||||
|
local HOME_PT = { x = 0, y = 0, z = 0, heading = 0 }
|
||||||
|
|
||||||
|
local DIG_BLACKLIST = {
|
||||||
|
[ 'minecraft:furnace' ] = true,
|
||||||
|
[ 'minecraft:lit_furnace' ] = true,
|
||||||
|
[ 'minecraft:chest' ] = true,
|
||||||
|
}
|
||||||
|
|
||||||
|
local COBBLESTONE = 'minecraft:cobblestone:0'
|
||||||
|
local CHARCOAL = 'minecraft:coal:1'
|
||||||
|
local OAK_LOG = 'minecraft:log:0'
|
||||||
|
local OAK_PLANK = 'minecraft:planks:0'
|
||||||
|
local CHEST = 'minecraft:chest:0'
|
||||||
|
local FURNACE = 'minecraft:furnace:0'
|
||||||
|
local SAPLING = 'minecraft:sapling:0'
|
||||||
|
local STONE = 'minecraft:stone:0'
|
||||||
|
local TORCH = 'minecraft:torch:0'
|
||||||
|
local DIRT = 'minecraft:dirt:0'
|
||||||
|
local APPLE = 'minecraft:apple:0'
|
||||||
|
local STICK = 'minecraft:stick:0'
|
||||||
|
local CRAFTING_TABLE = 'minecraft:crafting_table:0'
|
||||||
|
|
||||||
|
local ALL_SAPLINGS = {
|
||||||
|
SAPLING
|
||||||
|
}
|
||||||
|
|
||||||
|
local state = Util.readTable('usr/config/treefarm') or {
|
||||||
|
trees = {
|
||||||
|
{ x = 1, y = 0, z = 0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
local clock = os.clock()
|
||||||
|
local recipes = Util.readTable('sys/etc/recipes.db') or { }
|
||||||
|
|
||||||
|
Craft.setRecipes(recipes)
|
||||||
|
|
||||||
|
local function inspect(fn)
|
||||||
|
local s, item = fn()
|
||||||
|
if s and item then
|
||||||
|
return item.name .. ':' .. item.metadata
|
||||||
|
end
|
||||||
|
return 'minecraft:air:0'
|
||||||
|
end
|
||||||
|
|
||||||
|
local function setState(key, value)
|
||||||
|
state[key] = value
|
||||||
|
Util.writeTable('usr/config/treefarm', state)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function refuel()
|
||||||
|
if turtle.getFuelLevel() < FUEL_GOOD then
|
||||||
|
local charcoal = turtle.getItemCount(CHARCOAL)
|
||||||
|
if charcoal > 1 then
|
||||||
|
turtle.refuel(CHARCOAL, math.min(charcoal - 1, MIN_CHARCOAL / 2))
|
||||||
|
print('fuel: ' .. turtle.getFuelLevel())
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function safePlaceBlock(item)
|
||||||
|
|
||||||
|
if turtle.placeUp(item) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local s, m = turtle.inspectUp()
|
||||||
|
if s and not DIG_BLACKLIST[m.name] then
|
||||||
|
turtle.digUp()
|
||||||
|
return turtle.placeUp(item)
|
||||||
|
end
|
||||||
|
|
||||||
|
turtle.forward()
|
||||||
|
return turtle.placeUp(item)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function craftItem(item, qty)
|
||||||
|
|
||||||
|
local success
|
||||||
|
|
||||||
|
if safePlaceBlock(CHEST) then
|
||||||
|
|
||||||
|
if turtle.equip('left', 'minecraft:crafting_table') then
|
||||||
|
|
||||||
|
local chestAdapter = ChestAdapter({
|
||||||
|
wrapSide = 'top',
|
||||||
|
direction = 'down',
|
||||||
|
})
|
||||||
|
if not chestAdapter:isValid() then
|
||||||
|
print('invalid chestAdapter')
|
||||||
|
read()
|
||||||
|
end
|
||||||
|
|
||||||
|
Util.print('Crafting %d %s', (qty or 1), item)
|
||||||
|
success = Craft.craftRecipe(recipes[item], qty or 1, chestAdapter)
|
||||||
|
|
||||||
|
repeat until not turtle.suckUp()
|
||||||
|
end
|
||||||
|
turtle.equip('left', 'minecraft:diamond_pickaxe')
|
||||||
|
turtle.digUp()
|
||||||
|
end
|
||||||
|
|
||||||
|
return success
|
||||||
|
end
|
||||||
|
|
||||||
|
local function cook(item, count, result, fuel, fuelCount)
|
||||||
|
|
||||||
|
setState('cooking', true)
|
||||||
|
|
||||||
|
fuel = fuel or CHARCOAL
|
||||||
|
fuelCount = fuelCount or math.ceil(count / 8)
|
||||||
|
Util.print('Making %d %s', count, result)
|
||||||
|
|
||||||
|
turtle.dropForwardAt(state.furnace, fuel, fuelCount)
|
||||||
|
turtle.dropDownAt(state.furnace, item, count)
|
||||||
|
|
||||||
|
count = count + turtle.getItemCount(result)
|
||||||
|
turtle.select(1)
|
||||||
|
turtle.pathfind(Point.below(state.furnace))
|
||||||
|
repeat
|
||||||
|
os.sleep(1)
|
||||||
|
turtle.suckUp()
|
||||||
|
until turtle.getItemCount(result) >= count
|
||||||
|
|
||||||
|
setState('cooking')
|
||||||
|
end
|
||||||
|
|
||||||
|
local function makeSingleCharcoal()
|
||||||
|
|
||||||
|
local slots = turtle.getSummedInventory()
|
||||||
|
|
||||||
|
if not state.furnace or
|
||||||
|
slots[CHARCOAL] or
|
||||||
|
not slots[OAK_LOG] or
|
||||||
|
slots[OAK_LOG].count < 2 then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
turtle.faceAgainst(state.furnace)
|
||||||
|
if craftItem(OAK_PLANK) then
|
||||||
|
cook(OAK_LOG, 1, CHARCOAL, OAK_PLANK, 1)
|
||||||
|
turtle.refuel(OAK_PLANK)
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function makeCharcoal()
|
||||||
|
|
||||||
|
local slots = turtle.getSummedInventory()
|
||||||
|
|
||||||
|
if not state.furnace or
|
||||||
|
not slots[CHARCOAL] or
|
||||||
|
slots[CHARCOAL].count >= MIN_CHARCOAL then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getLogSlot(slots)
|
||||||
|
local maxslot = { count = 0 }
|
||||||
|
for k,slot in pairs(slots) do
|
||||||
|
if string.match(k, 'minecraft:log') then
|
||||||
|
if slot.count > maxslot.count then
|
||||||
|
maxslot = slot
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return maxslot
|
||||||
|
end
|
||||||
|
|
||||||
|
repeat
|
||||||
|
local slots = turtle.getSummedInventory()
|
||||||
|
local charcoal = slots[CHARCOAL].count
|
||||||
|
local slot = getLogSlot(slots)
|
||||||
|
|
||||||
|
if slot.count < 8 then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
local toCook = math.min(charcoal, math.floor(slot.count / 8))
|
||||||
|
toCook = math.min(toCook, math.floor((MIN_CHARCOAL + 8 - charcoal) / 8))
|
||||||
|
toCook = toCook * 8
|
||||||
|
|
||||||
|
cook(slot.key, toCook, CHARCOAL)
|
||||||
|
|
||||||
|
until charcoal + toCook >= MIN_CHARCOAL
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function emptyFurnace()
|
||||||
|
if state.cooking then
|
||||||
|
|
||||||
|
print('Emptying furnace')
|
||||||
|
|
||||||
|
turtle.suckDownAt(state.furnace)
|
||||||
|
turtle.suckForwardAt(state.furnace)
|
||||||
|
turtle.suckUpAt(state.furnace)
|
||||||
|
setState('cooking')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getCobblestone(count)
|
||||||
|
|
||||||
|
local slots = turtle.getSummedInventory()
|
||||||
|
|
||||||
|
if not slots[COBBLESTONE] or slots[COBBLESTONE].count < count then
|
||||||
|
|
||||||
|
print('Collecting cobblestone')
|
||||||
|
|
||||||
|
slots[COBBLESTONE] = true
|
||||||
|
slots[DIRT] = true
|
||||||
|
|
||||||
|
local pt = Point.copy(GRID.BR)
|
||||||
|
pt.x = GRID.BR.x + 2
|
||||||
|
pt.z = GRID.BR.z - 2
|
||||||
|
|
||||||
|
turtle.pathfind(pt)
|
||||||
|
|
||||||
|
repeat
|
||||||
|
turtle.select(1)
|
||||||
|
turtle.digDown()
|
||||||
|
turtle.down()
|
||||||
|
for i = 1, 4 do
|
||||||
|
if inspect(turtle.inspect) == STONE then
|
||||||
|
turtle.dig()
|
||||||
|
end
|
||||||
|
turtle.turnRight()
|
||||||
|
end
|
||||||
|
|
||||||
|
for item in pairs(turtle.getSummedInventory()) do
|
||||||
|
if not slots[item] then
|
||||||
|
turtle.drop(item)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
until turtle.getItemCount(COBBLESTONE) >= count
|
||||||
|
|
||||||
|
turtle.gotoPoint(pt)
|
||||||
|
turtle.placeDown(DIRT)
|
||||||
|
|
||||||
|
turtle.drop(DIRT)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function createFurnace()
|
||||||
|
|
||||||
|
if not state.furnace then
|
||||||
|
if turtle.getFuelLevel() < FUEL_BASE + 100 then
|
||||||
|
return true -- try again later
|
||||||
|
end
|
||||||
|
print('Adding a furnace')
|
||||||
|
getCobblestone(8)
|
||||||
|
|
||||||
|
if craftItem(FURNACE) then
|
||||||
|
turtle.drop(COBBLESTONE)
|
||||||
|
local furnacePt = { x = GRID.BL.x + 2, y = 1, z = GRID.BL.z + 2 }
|
||||||
|
turtle.placeAt(furnacePt, FURNACE)
|
||||||
|
setState('furnace', furnacePt)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function createPerimeter()
|
||||||
|
|
||||||
|
if not state.perimeter then
|
||||||
|
if not state.furnace or
|
||||||
|
turtle.getFuelLevel() < FUEL_BASE + 500 or
|
||||||
|
turtle.getItemCount(OAK_LOG) == 0 or
|
||||||
|
not craftItem(OAK_PLANK, 2) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
print('Creating a perimeter')
|
||||||
|
|
||||||
|
getCobblestone(GRID_WIDTH * 2 + 1)
|
||||||
|
cook(COBBLESTONE, 2, STONE, OAK_PLANK, 2)
|
||||||
|
turtle.refuel(OAK_PLANK)
|
||||||
|
|
||||||
|
turtle.pathfind(GRID.BL)
|
||||||
|
turtle.digDown()
|
||||||
|
turtle.placeDown(STONE)
|
||||||
|
|
||||||
|
turtle.setMoveCallback(function()
|
||||||
|
local target = COBBLESTONE
|
||||||
|
if math.abs(turtle.point.x) == GRID_LENGTH and
|
||||||
|
math.abs(turtle.point.z) == GRID_WIDTH then
|
||||||
|
target = STONE
|
||||||
|
end
|
||||||
|
|
||||||
|
if inspect(turtle.inspectDown) ~= target then
|
||||||
|
turtle.digDown()
|
||||||
|
turtle.placeDown(target)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
turtle.pathfind(GRID.BR)
|
||||||
|
|
||||||
|
turtle.clearMoveCallback()
|
||||||
|
turtle.drop(COBBLESTONE)
|
||||||
|
turtle.drop(DIRT)
|
||||||
|
|
||||||
|
setState('perimeter', true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function createChests()
|
||||||
|
if state.chest_1 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if state.perimeter and
|
||||||
|
turtle.getFuelLevel() > FUEL_GOOD and
|
||||||
|
Craft.canCraft(CHEST, 4, turtle.getSummedInventory()) then
|
||||||
|
|
||||||
|
print('Adding storage')
|
||||||
|
if craftItem(CHEST, 4) then
|
||||||
|
|
||||||
|
local pt = Point.copy(GRID.BL)
|
||||||
|
pt.x = pt.x + 1
|
||||||
|
pt.y = pt.y - 1
|
||||||
|
|
||||||
|
for i = 1, 2 do
|
||||||
|
pt.z = pt.z + 1
|
||||||
|
|
||||||
|
turtle.digDownAt(pt)
|
||||||
|
turtle.placeDown(CHEST)
|
||||||
|
|
||||||
|
pt.z = pt.z + 1
|
||||||
|
|
||||||
|
turtle.digDownAt(pt)
|
||||||
|
turtle.placeDown(CHEST)
|
||||||
|
|
||||||
|
setState('chest_' .. i, Util.shallowCopy(pt))
|
||||||
|
|
||||||
|
pt.z = pt.z + 1
|
||||||
|
end
|
||||||
|
turtle.drop(DIRT)
|
||||||
|
turtle.refuel(OAK_PLANK)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function dropOffItems()
|
||||||
|
|
||||||
|
if state.chest_1 then
|
||||||
|
local slots = turtle.getSummedInventory()
|
||||||
|
|
||||||
|
if state.chest_1 and
|
||||||
|
slots[CHARCOAL] and
|
||||||
|
slots[CHARCOAL].count >= MIN_CHARCOAL and
|
||||||
|
(turtle.getItemCount('minecraft:log') > 0 or
|
||||||
|
turtle.getItemCount('minecraft:log2') > 0) then
|
||||||
|
|
||||||
|
print('Storing logs')
|
||||||
|
turtle.pathfind(Point.above(state.chest_1))
|
||||||
|
turtle.dropDown('minecraft:log')
|
||||||
|
turtle.dropDown('minecraft:log2')
|
||||||
|
end
|
||||||
|
|
||||||
|
if slots[APPLE] then
|
||||||
|
print('Storing apples')
|
||||||
|
turtle.dropDownAt(state.chest_2, APPLE)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function eatSaplings()
|
||||||
|
|
||||||
|
local slots = turtle.getSummedInventory()
|
||||||
|
|
||||||
|
for _, sapling in pairs(ALL_SAPLINGS) do
|
||||||
|
if slots[sapling] and slots[sapling].count > MAX_SAPLINGS then
|
||||||
|
turtle.refuel(sapling, slots[sapling].count - MAX_SAPLINGS)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function placeTorches()
|
||||||
|
if state.torches then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if turtle.getFuelLevel() > 100 and
|
||||||
|
Craft.canCraft(TORCH, 4, turtle.getSummedInventory()) then
|
||||||
|
|
||||||
|
print('Placing torches')
|
||||||
|
|
||||||
|
if craftItem(TORCH, 4) then
|
||||||
|
local pts = { }
|
||||||
|
for x = -4, 4, 8 do
|
||||||
|
for z = -4, 4, 8 do
|
||||||
|
table.insert(pts, { x = x, y = 0, z = z })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
Point.eachClosest(turtle.point, pts, function(pt)
|
||||||
|
turtle.placeAt(pt, TORCH)
|
||||||
|
end)
|
||||||
|
turtle.refuel(STICK)
|
||||||
|
turtle.refuel(OAK_PLANK)
|
||||||
|
setState('torches', true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function randomSapling()
|
||||||
|
|
||||||
|
local sapling = SAPLING
|
||||||
|
|
||||||
|
if #state.trees > 1 then
|
||||||
|
ALL_SAPLINGS = { }
|
||||||
|
|
||||||
|
local slots = turtle.getFilledSlots()
|
||||||
|
for _, slot in pairs(slots) do
|
||||||
|
if slot.name == 'minecraft:sapling' then
|
||||||
|
table.insert(ALL_SAPLINGS, slot.key)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
sapling = ALL_SAPLINGS[math.random(1, #ALL_SAPLINGS)]
|
||||||
|
end
|
||||||
|
|
||||||
|
return sapling
|
||||||
|
end
|
||||||
|
|
||||||
|
local function fellTree(pt)
|
||||||
|
|
||||||
|
local function desperateRefuel(min)
|
||||||
|
if turtle.getFuelLevel() < min then
|
||||||
|
local logs = turtle.getItemCount(OAK_LOG)
|
||||||
|
if logs > 0 then
|
||||||
|
if craftItem(OAK_PLANK, math.min(8, logs * 4)) then
|
||||||
|
turtle.refuel(OAK_PLANK)
|
||||||
|
print('fuel: ' .. turtle.getFuelLevel())
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
turtle.setMoveCallback(function() desperateRefuel(FUEL_DIRE) end)
|
||||||
|
|
||||||
|
desperateRefuel(FUEL_DIRE)
|
||||||
|
|
||||||
|
if turtle.digUpAt(Point.above(pt)) then
|
||||||
|
Level(
|
||||||
|
{ x = GRID_WIDTH-1, y = 1, z = GRID_WIDTH-1 },
|
||||||
|
{ x = -(GRID_WIDTH-1), y = 50, z = -(GRID_WIDTH-1) },
|
||||||
|
Point.above(pt))
|
||||||
|
end
|
||||||
|
|
||||||
|
desperateRefuel(FUEL_BASE + 100)
|
||||||
|
turtle.clearMoveCallback()
|
||||||
|
turtle.setPolicy("attack")
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function fell()
|
||||||
|
|
||||||
|
local pts = Util.shallowCopy(state.trees)
|
||||||
|
|
||||||
|
local pt = table.remove(pts, math.random(1, #pts))
|
||||||
|
|
||||||
|
-- give the pathfinder hints about what to avoid (state.trees)
|
||||||
|
if not turtle.faceAgainst(pt, { blocks = Util.shallowCopy(state.trees) }) or
|
||||||
|
not string.match(inspect(turtle.inspect), 'minecraft:log') then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
print('Chopping')
|
||||||
|
|
||||||
|
local fuel = turtle.getFuelLevel()
|
||||||
|
|
||||||
|
-- push this point to the start of this list
|
||||||
|
table.insert(pts, 1, pt)
|
||||||
|
|
||||||
|
Point.eachClosest(turtle.point, pts, function(pt)
|
||||||
|
if turtle.faceAgainst(pt, { blocks = Util.shallowCopy(state.trees) }) and
|
||||||
|
string.match(inspect(turtle.inspect), 'minecraft:log') then
|
||||||
|
turtle.dig()
|
||||||
|
fellTree(pt)
|
||||||
|
end
|
||||||
|
turtle.placeAt(pt, randomSapling())
|
||||||
|
turtle.select(1)
|
||||||
|
end)
|
||||||
|
|
||||||
|
print('Used ' .. (fuel - turtle.getFuelLevel()) .. ' fuel')
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function moreTrees()
|
||||||
|
|
||||||
|
if #state.trees > 1 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if not state.chest_1 or turtle.getItemCount('minecraft:sapling') < 15 then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
print('Adding more trees')
|
||||||
|
|
||||||
|
local singleTree = state.trees[1]
|
||||||
|
|
||||||
|
state.trees = { }
|
||||||
|
for x = -2, 2, 1 do
|
||||||
|
for z = -2, 2, 2 do
|
||||||
|
table.insert(state.trees, { x = x, y = 0, z = z })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
turtle.digAt(singleTree)
|
||||||
|
fellTree(singleTree)
|
||||||
|
|
||||||
|
setState('trees', state.trees)
|
||||||
|
|
||||||
|
Point.eachClosest(turtle.point, state.trees, function(pt)
|
||||||
|
turtle.placeDownAt(pt, randomSapling())
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function getTurtleFacing(block)
|
||||||
|
local directions = {
|
||||||
|
[5] = 2,
|
||||||
|
[3] = 3,
|
||||||
|
[4] = 0,
|
||||||
|
[2] = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
if not safePlaceBlock(block) then
|
||||||
|
error('unable to place chest above')
|
||||||
|
end
|
||||||
|
local _, bi = turtle.inspectUp()
|
||||||
|
turtle.digUp()
|
||||||
|
return directions[bi.metadata]
|
||||||
|
end
|
||||||
|
|
||||||
|
function saveTurtleFacing()
|
||||||
|
if not state.facing then
|
||||||
|
setState('facing', getTurtleFacing(CHEST))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function findGround()
|
||||||
|
print('Locating ground level')
|
||||||
|
turtle.setPoint(HOME_PT)
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local s, block = turtle.inspectDown()
|
||||||
|
|
||||||
|
if not s then block = { name = 'minecraft:air', metadata = 0 } end
|
||||||
|
b = block.name .. ':' .. block.metadata
|
||||||
|
|
||||||
|
if b == 'minecraft:dirt:0' or
|
||||||
|
b == 'minecraft:grass:0' or
|
||||||
|
block.name == 'minecraft:chest' then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
if b == COBBLESTONE or b == STONE then
|
||||||
|
error('lost')
|
||||||
|
end
|
||||||
|
|
||||||
|
if b == TORCH or b == FURNACE then
|
||||||
|
turtle.forward()
|
||||||
|
else
|
||||||
|
turtle.digDown()
|
||||||
|
turtle.down()
|
||||||
|
end
|
||||||
|
|
||||||
|
if turtle.point.y < -20 then
|
||||||
|
error('lost')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
turtle.setPoint(HOME_PT)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function findHome()
|
||||||
|
|
||||||
|
if not state.perimeter then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
print('Determining location')
|
||||||
|
|
||||||
|
turtle.point.heading = getTurtleFacing(CHEST)
|
||||||
|
turtle.setHeading(state.facing)
|
||||||
|
turtle.point.heading = 0
|
||||||
|
|
||||||
|
local pt = Point.copy(turtle.point)
|
||||||
|
|
||||||
|
while inspect(turtle.inspectDown) ~= COBBLESTONE do
|
||||||
|
pt.x = pt.x - 1
|
||||||
|
turtle.pathfind(pt)
|
||||||
|
if pt.x < -20 then
|
||||||
|
error('lost')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
while inspect(turtle.inspectDown) == COBBLESTONE do
|
||||||
|
pt.z = pt.z - 1
|
||||||
|
turtle.pathfind(pt)
|
||||||
|
if pt.z < -20 then
|
||||||
|
error('lost')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
turtle.setPoint({
|
||||||
|
x = -(GRID_LENGTH),
|
||||||
|
y = 0,
|
||||||
|
z = -GRID_WIDTH,
|
||||||
|
heading = turtle.point.heading
|
||||||
|
})
|
||||||
|
|
||||||
|
-- when pathfinding - don't leave this box
|
||||||
|
Pathing.setBox({
|
||||||
|
x = GRID.TL.x,
|
||||||
|
y = GRID.TL.y,
|
||||||
|
z = GRID.TL.z,
|
||||||
|
ex = GRID.BR.x,
|
||||||
|
ey = 5,
|
||||||
|
ez = GRID.BR.z,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
local function updateClock()
|
||||||
|
|
||||||
|
local ONE_HOUR = 50
|
||||||
|
|
||||||
|
if os.clock() - clock > ONE_HOUR then
|
||||||
|
clock = os.clock()
|
||||||
|
else
|
||||||
|
print('sleeping for ' .. math.floor(ONE_HOUR - (os.clock() - clock)))
|
||||||
|
os.sleep(ONE_HOUR - (os.clock() - clock))
|
||||||
|
clock = os.clock()
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function startupCheck()
|
||||||
|
local slots = turtle.getSummedInventory()
|
||||||
|
|
||||||
|
if not slots[CHEST] or not slots[CRAFTING_TABLE] then
|
||||||
|
error('A chest and crafting table must be in inventory')
|
||||||
|
end
|
||||||
|
|
||||||
|
if state.facing and not state.perimeter then
|
||||||
|
print('Perimeter has not been established.')
|
||||||
|
print('Enter to continue if turtle is in the original starting position.')
|
||||||
|
read()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local tasks = {
|
||||||
|
{ desc = 'Startup check', fn = startupCheck },
|
||||||
|
{ desc = 'Finding ground', fn = findGround },
|
||||||
|
{ desc = 'Determine facing', fn = saveTurtleFacing },
|
||||||
|
{ desc = 'Finding home', fn = findHome },
|
||||||
|
{ desc = 'Emptying furnace', fn = emptyFurnace },
|
||||||
|
{ desc = 'Adding trees', fn = moreTrees },
|
||||||
|
{ desc = 'Chopping', fn = fell },
|
||||||
|
{ desc = 'Snacking', fn = eatSaplings },
|
||||||
|
{ desc = 'Creating chest', fn = createChests },
|
||||||
|
{ desc = 'Creating furnace', fn = createFurnace },
|
||||||
|
{ desc = 'Making charcoal', fn = makeSingleCharcoal },
|
||||||
|
{ desc = 'Making charcoal', fn = makeCharcoal },
|
||||||
|
{ desc = 'Creating perimeter', fn = createPerimeter },
|
||||||
|
{ desc = 'Placing torches', fn = placeTorches },
|
||||||
|
{ desc = 'Refueling', fn = refuel },
|
||||||
|
{ desc = 'Dropping off items', fn = dropOffItems },
|
||||||
|
{ desc = 'Condensing', fn = turtle.condense },
|
||||||
|
{ desc = 'Sleeping', fn = updateClock },
|
||||||
|
}
|
||||||
|
|
||||||
|
local s, m = turtle.run(function()
|
||||||
|
|
||||||
|
turtle.setPolicy("attack")
|
||||||
|
|
||||||
|
while not turtle.abort do
|
||||||
|
print('fuel: ' .. turtle.getFuelLevel())
|
||||||
|
for _,task in ipairs(Util.shallowCopy(tasks)) do
|
||||||
|
--print(task.desc)
|
||||||
|
turtle.status = task.desc
|
||||||
|
turtle.select(1)
|
||||||
|
if not task.fn() then
|
||||||
|
Util.filterInplace(tasks, function(v) return v.fn ~= task.fn end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
if not s then
|
||||||
|
error('Failed')
|
||||||
|
end
|
||||||
1370
etc/blocks.json
Normal file
1370
etc/blocks.json
Normal file
File diff suppressed because it is too large
Load Diff
2108
etc/recipes.db
Normal file
2108
etc/recipes.db
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user