spaces->tab, equipper improvements, supertreefarm rewrite, follow improvements, sensor cleanup, milo multiple items allowed in recipes, remote canvas access
This commit is contained in:
@@ -15,37 +15,37 @@ local os = _G.os
|
||||
local alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||
|
||||
local function sixBitToBase64(input)
|
||||
return _sub(alphabet, input+1, input+1)
|
||||
return _sub(alphabet, input+1, input+1)
|
||||
end
|
||||
|
||||
local function base64ToSixBit(input)
|
||||
for i=1, 64 do
|
||||
if input == _sub(alphabet, i, i) then
|
||||
return i-1
|
||||
end
|
||||
end
|
||||
for i=1, 64 do
|
||||
if input == _sub(alphabet, i, i) then
|
||||
return i-1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function octetToBase64(o1, o2, o3)
|
||||
local i1 = sixBitToBase64(_brshift(_band(o1, 0xFC), 2))
|
||||
local i2
|
||||
local i3 = "="
|
||||
local i4 = "="
|
||||
if o2 then
|
||||
i2 = sixBitToBase64(_bor( _blshift(_band(o1, 3), 4), _brshift(_band(o2, 0xF0), 4) ))
|
||||
if not o3 then
|
||||
i3 = sixBitToBase64(_blshift(_band(o2, 0x0F), 2))
|
||||
else
|
||||
i3 = sixBitToBase64(_bor( _blshift(_band(o2, 0x0F), 2), _brshift(_band(o3, 0xC0), 6) ))
|
||||
end
|
||||
else
|
||||
i2 = sixBitToBase64(_blshift(_band(o1, 3), 4))
|
||||
end
|
||||
if o3 then
|
||||
i4 = sixBitToBase64(_band(o3, 0x3F))
|
||||
end
|
||||
local i1 = sixBitToBase64(_brshift(_band(o1, 0xFC), 2))
|
||||
local i2
|
||||
local i3 = "="
|
||||
local i4 = "="
|
||||
if o2 then
|
||||
i2 = sixBitToBase64(_bor( _blshift(_band(o1, 3), 4), _brshift(_band(o2, 0xF0), 4) ))
|
||||
if not o3 then
|
||||
i3 = sixBitToBase64(_blshift(_band(o2, 0x0F), 2))
|
||||
else
|
||||
i3 = sixBitToBase64(_bor( _blshift(_band(o2, 0x0F), 2), _brshift(_band(o3, 0xC0), 6) ))
|
||||
end
|
||||
else
|
||||
i2 = sixBitToBase64(_blshift(_band(o1, 3), 4))
|
||||
end
|
||||
if o3 then
|
||||
i4 = sixBitToBase64(_band(o3, 0x3F))
|
||||
end
|
||||
|
||||
return i1..i2..i3..i4
|
||||
return i1..i2..i3..i4
|
||||
end
|
||||
|
||||
-- octet 1 needs characters 1/2
|
||||
@@ -53,61 +53,61 @@ end
|
||||
-- octet 3 needs characters 3/4
|
||||
|
||||
local function base64ToThreeOctet(s1)
|
||||
local c1 = base64ToSixBit(_sub(s1, 1, 1))
|
||||
local c2 = base64ToSixBit(_sub(s1, 2, 2))
|
||||
local c3
|
||||
local c4
|
||||
local o1
|
||||
local o2
|
||||
local o3
|
||||
if _sub(s1, 3, 3) == "=" then
|
||||
c3 = nil
|
||||
c4 = nil
|
||||
elseif _sub(s1, 4, 4) == "=" then
|
||||
c3 = base64ToSixBit(_sub(s1, 3, 3))
|
||||
c4 = nil
|
||||
else
|
||||
c3 = base64ToSixBit(_sub(s1, 3, 3))
|
||||
c4 = base64ToSixBit(_sub(s1, 4, 4))
|
||||
end
|
||||
o1 = _bor( _blshift(c1, 2), _brshift(_band( c2, 0x30 ), 4) )
|
||||
if c3 then
|
||||
o2 = _bor( _blshift(_band(c2, 0x0F), 4), _brshift(_band( c3, 0x3C ), 2) )
|
||||
else
|
||||
o2 = nil
|
||||
end
|
||||
if c4 then
|
||||
o3 = _bor( _blshift(_band(c3, 3), 6), c4 )
|
||||
else
|
||||
o3 = nil
|
||||
end
|
||||
return o1, o2, o3
|
||||
local c1 = base64ToSixBit(_sub(s1, 1, 1))
|
||||
local c2 = base64ToSixBit(_sub(s1, 2, 2))
|
||||
local c3
|
||||
local c4
|
||||
local o1
|
||||
local o2
|
||||
local o3
|
||||
if _sub(s1, 3, 3) == "=" then
|
||||
c3 = nil
|
||||
c4 = nil
|
||||
elseif _sub(s1, 4, 4) == "=" then
|
||||
c3 = base64ToSixBit(_sub(s1, 3, 3))
|
||||
c4 = nil
|
||||
else
|
||||
c3 = base64ToSixBit(_sub(s1, 3, 3))
|
||||
c4 = base64ToSixBit(_sub(s1, 4, 4))
|
||||
end
|
||||
o1 = _bor( _blshift(c1, 2), _brshift(_band( c2, 0x30 ), 4) )
|
||||
if c3 then
|
||||
o2 = _bor( _blshift(_band(c2, 0x0F), 4), _brshift(_band( c3, 0x3C ), 2) )
|
||||
else
|
||||
o2 = nil
|
||||
end
|
||||
if c4 then
|
||||
o3 = _bor( _blshift(_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
|
||||
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
|
||||
local blocks = splitIntoBlocks(bytes)
|
||||
local output = ""
|
||||
for i=1, #blocks do
|
||||
output = output..octetToBase64( unpack(blocks[i]) )
|
||||
end
|
||||
return output
|
||||
end
|
||||
|
||||
local function Throttle()
|
||||
@@ -123,32 +123,32 @@ local function Throttle()
|
||||
end
|
||||
|
||||
function Base64.decode(str)
|
||||
local bytes = {}
|
||||
local blocks = {}
|
||||
local blockNum = 1
|
||||
local throttle = Throttle()
|
||||
for i=1, #str, 4 do
|
||||
blocks[blockNum] = _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)
|
||||
throttle()
|
||||
end
|
||||
-- Remove padding:
|
||||
--[[
|
||||
for i=#bytes, 1, -1 do
|
||||
if bytes[i] ~= 0 then
|
||||
break
|
||||
else
|
||||
bytes[i] = nil
|
||||
end
|
||||
end
|
||||
]]
|
||||
return bytes
|
||||
local bytes = {}
|
||||
local blocks = {}
|
||||
local blockNum = 1
|
||||
local throttle = Throttle()
|
||||
for i=1, #str, 4 do
|
||||
blocks[blockNum] = _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)
|
||||
throttle()
|
||||
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
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -9,10 +9,10 @@ local turtle = _G.turtle
|
||||
|
||||
local Builder = class()
|
||||
Util.merge(Builder, {
|
||||
isCommandComputer = not turtle,
|
||||
loc = { },
|
||||
index = 1,
|
||||
mode = 'build',
|
||||
isCommandComputer = not turtle,
|
||||
loc = { },
|
||||
index = 1,
|
||||
mode = 'build',
|
||||
})
|
||||
|
||||
local BUILDER_DIR = 'usr/builder'
|
||||
@@ -20,82 +20,82 @@ local BUILDER_DIR = 'usr/builder'
|
||||
local blockInfo = Blocks()
|
||||
|
||||
function Builder:getBlockCounts()
|
||||
local blocks = { }
|
||||
local blocks = { }
|
||||
|
||||
for k = self.index, #self.schematic.blocks do
|
||||
local b = self.schematic.blocks[k]
|
||||
local key = tostring(b.id) .. ':' .. b.dmg
|
||||
local block = blocks[key]
|
||||
if not block then
|
||||
block = Util.shallowCopy(b)
|
||||
block.qty = 0
|
||||
block.need = 0
|
||||
blocks[key] = block
|
||||
end
|
||||
block.need = block.need + 1
|
||||
end
|
||||
for k = self.index, #self.schematic.blocks do
|
||||
local b = self.schematic.blocks[k]
|
||||
local key = tostring(b.id) .. ':' .. b.dmg
|
||||
local block = blocks[key]
|
||||
if not block then
|
||||
block = Util.shallowCopy(b)
|
||||
block.qty = 0
|
||||
block.need = 0
|
||||
blocks[key] = block
|
||||
end
|
||||
block.need = block.need + 1
|
||||
end
|
||||
|
||||
return blocks
|
||||
return blocks
|
||||
end
|
||||
|
||||
function Builder:substituteBlocks(throttle)
|
||||
for _,b in pairs(self.schematic.blocks) do
|
||||
for _,b in pairs(self.schematic.blocks) do
|
||||
|
||||
-- replace schematic block type with substitution
|
||||
local pb = blockInfo:getPlaceableBlock(b.id, b.dmg)
|
||||
-- replace schematic block type with substitution
|
||||
local pb = blockInfo:getPlaceableBlock(b.id, b.dmg)
|
||||
|
||||
Util.merge(b, pb)
|
||||
Util.merge(b, pb)
|
||||
|
||||
b.odmg = pb.odmg or pb.dmg
|
||||
b.odmg = pb.odmg or pb.dmg
|
||||
|
||||
local sub = self.subDB:get({ b.id, b.dmg })
|
||||
if sub then
|
||||
b.id, b.dmg = self.subDB:extract(sub)
|
||||
end
|
||||
throttle()
|
||||
end
|
||||
local sub = self.subDB:get({ b.id, b.dmg })
|
||||
if sub then
|
||||
b.id, b.dmg = self.subDB:extract(sub)
|
||||
end
|
||||
throttle()
|
||||
end
|
||||
end
|
||||
|
||||
function Builder:reloadSchematic(throttle)
|
||||
self.schematic:reload(throttle)
|
||||
self:substituteBlocks(throttle)
|
||||
self.schematic:reload(throttle)
|
||||
self:substituteBlocks(throttle)
|
||||
end
|
||||
|
||||
function Builder:log(...)
|
||||
Util.print(...)
|
||||
Util.print(...)
|
||||
end
|
||||
|
||||
function Builder:dumpInventory()
|
||||
end
|
||||
|
||||
function Builder:logBlock(index, b)
|
||||
local bdir = b.direction or ''
|
||||
local logText = string.format('%d %s:%d (x:%d,z:%d:y:%d) %s',
|
||||
index, b.id, b.dmg, b.x, b.z, b.y, bdir)
|
||||
self:log(logText)
|
||||
-- self:log(b.index) -- unique identifier of block
|
||||
local bdir = b.direction or ''
|
||||
local logText = string.format('%d %s:%d (x:%d,z:%d:y:%d) %s',
|
||||
index, b.id, b.dmg, b.x, b.z, b.y, bdir)
|
||||
self:log(logText)
|
||||
-- self:log(b.index) -- unique identifier of block
|
||||
|
||||
if device.wireless_modem then
|
||||
Message.broadcast('builder', { x = b.x, y = b.y, z = b.z, heading = b.heading })
|
||||
end
|
||||
if device.wireless_modem then
|
||||
Message.broadcast('builder', { x = b.x, y = b.y, z = b.z, heading = b.heading })
|
||||
end
|
||||
end
|
||||
|
||||
function Builder:saveProgress(index)
|
||||
Util.writeTable(
|
||||
fs.combine(BUILDER_DIR, self.schematic.filename .. '.progress'),
|
||||
{ index = index, loc = self.loc }
|
||||
)
|
||||
Util.writeTable(
|
||||
fs.combine(BUILDER_DIR, self.schematic.filename .. '.progress'),
|
||||
{ index = index, loc = self.loc }
|
||||
)
|
||||
end
|
||||
|
||||
function Builder:loadProgress(filename)
|
||||
local progress = Util.readTable(fs.combine(BUILDER_DIR, filename))
|
||||
if progress then
|
||||
self.index = progress.index
|
||||
if self.index > #self.schematic.blocks then
|
||||
self.index = 1
|
||||
end
|
||||
self.loc = progress.loc or { }
|
||||
end
|
||||
local progress = Util.readTable(fs.combine(BUILDER_DIR, filename))
|
||||
if progress then
|
||||
self.index = progress.index
|
||||
if self.index > #self.schematic.blocks then
|
||||
self.index = 1
|
||||
end
|
||||
self.loc = progress.loc or { }
|
||||
end
|
||||
end
|
||||
|
||||
return Builder
|
||||
|
||||
@@ -8,77 +8,77 @@ local os = _G.os
|
||||
local read = _G.read
|
||||
|
||||
function Builder:begin()
|
||||
local direction = 1
|
||||
local last = #self.schematic.blocks
|
||||
local throttle = Util.throttle()
|
||||
local direction = 1
|
||||
local last = #self.schematic.blocks
|
||||
local throttle = Util.throttle()
|
||||
|
||||
local cx, cy, cz = commands.getBlockPosition()
|
||||
if self.loc.x then
|
||||
cx, cy, cz = self.loc.rx, self.loc.ry, self.loc.rz
|
||||
end
|
||||
local cx, cy, cz = commands.getBlockPosition()
|
||||
if self.loc.x then
|
||||
cx, cy, cz = self.loc.rx, self.loc.ry, self.loc.rz
|
||||
end
|
||||
|
||||
if self.mode == 'destroy' then
|
||||
direction = -1
|
||||
last = 1
|
||||
end
|
||||
if self.mode == 'destroy' then
|
||||
direction = -1
|
||||
last = 1
|
||||
end
|
||||
|
||||
for i = self.index, last, direction do
|
||||
self.index = i
|
||||
for i = self.index, last, direction do
|
||||
self.index = i
|
||||
|
||||
local b = self.schematic:getComputedBlock(i)
|
||||
local b = self.schematic:getComputedBlock(i)
|
||||
|
||||
if b.id ~= 'minecraft:air' then
|
||||
if b.id ~= 'minecraft:air' then
|
||||
|
||||
self:logBlock(self.index, b)
|
||||
self:logBlock(self.index, b)
|
||||
|
||||
local id = b.id
|
||||
if self.mode == 'destroy' then
|
||||
id = 'minecraft:air'
|
||||
end
|
||||
local id = b.id
|
||||
if self.mode == 'destroy' then
|
||||
id = 'minecraft:air'
|
||||
end
|
||||
|
||||
local function placeBlock(bid, dmg, x, y, z)
|
||||
local command = table.concat({
|
||||
"setblock",
|
||||
cx + x + 1,
|
||||
cy + y,
|
||||
cz + z + 1,
|
||||
bid,
|
||||
dmg,
|
||||
}, ' ')
|
||||
local function placeBlock(bid, dmg, x, y, z)
|
||||
local command = table.concat({
|
||||
"setblock",
|
||||
cx + x + 1,
|
||||
cy + y,
|
||||
cz + z + 1,
|
||||
bid,
|
||||
dmg,
|
||||
}, ' ')
|
||||
|
||||
commands.execAsync(command)
|
||||
commands.execAsync(command)
|
||||
|
||||
local result = { os.pullEvent("task_complete") }
|
||||
if not result[4] then
|
||||
Util.print(result[5])
|
||||
if self.mode ~= 'destroy' then
|
||||
read()
|
||||
end
|
||||
end
|
||||
end
|
||||
local result = { os.pullEvent("task_complete") }
|
||||
if not result[4] then
|
||||
Util.print(result[5])
|
||||
if self.mode ~= 'destroy' then
|
||||
read()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
placeBlock(id, b.odmg, b.x, b.y, b.z)
|
||||
placeBlock(id, b.odmg, b.x, b.y, b.z)
|
||||
|
||||
if b.twoHigh then
|
||||
local _, topBlock = self.schematic:findIndexAt(b.x, b.z, b.y + 1, true)
|
||||
if topBlock then
|
||||
placeBlock(id, topBlock.odmg, b.x, b.y + 1, b.z)
|
||||
end
|
||||
end
|
||||
if b.twoHigh then
|
||||
local _, topBlock = self.schematic:findIndexAt(b.x, b.z, b.y + 1, true)
|
||||
if topBlock then
|
||||
placeBlock(id, topBlock.odmg, b.x, b.y + 1, b.z)
|
||||
end
|
||||
end
|
||||
|
||||
if self.mode == 'destroy' then
|
||||
self:saveProgress(math.max(self.index, 1))
|
||||
else
|
||||
self:saveProgress(self.index + 1)
|
||||
end
|
||||
else
|
||||
throttle() -- sleep in case there are a large # of skipped blocks
|
||||
end
|
||||
end
|
||||
if self.mode == 'destroy' then
|
||||
self:saveProgress(math.max(self.index, 1))
|
||||
else
|
||||
self:saveProgress(self.index + 1)
|
||||
end
|
||||
else
|
||||
throttle() -- sleep in case there are a large # of skipped blocks
|
||||
end
|
||||
end
|
||||
|
||||
fs.delete(self.schematic.filename .. '.progress')
|
||||
print('Finished')
|
||||
Event.exitPullEvents()
|
||||
fs.delete(self.schematic.filename .. '.progress')
|
||||
print('Finished')
|
||||
Event.exitPullEvents()
|
||||
end
|
||||
|
||||
return Builder
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1136
builder/builder.lua
1136
builder/builder.lua
File diff suppressed because it is too large
Load Diff
@@ -10,390 +10,390 @@ local os = _G.os
|
||||
local turtle = _G.turtle
|
||||
|
||||
--[[
|
||||
A supplier turtle for the builder turtle. For larger builds, use
|
||||
ender modems.
|
||||
A supplier turtle for the builder turtle. For larger builds, use
|
||||
ender modems.
|
||||
|
||||
Setup:
|
||||
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
|
||||
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('core.chestProvider')
|
||||
if Util.getVersion() == 1.8 then
|
||||
ChestProvider = require('core.chestProvider18')
|
||||
ChestProvider = require('core.chestProvider18')
|
||||
end
|
||||
|
||||
if not device.wireless_modem then
|
||||
error('No wireless modem detected')
|
||||
error('No wireless modem detected')
|
||||
end
|
||||
|
||||
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,
|
||||
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 }
|
||||
}
|
||||
}
|
||||
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
|
||||
return self.data[id .. ':' .. dmg] or 64
|
||||
end
|
||||
|
||||
function Builder:dumpInventory()
|
||||
|
||||
local success = true
|
||||
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)
|
||||
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
|
||||
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
|
||||
_G.read()
|
||||
end
|
||||
self.ready = true
|
||||
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
|
||||
_G.read()
|
||||
end
|
||||
self.ready = true
|
||||
end
|
||||
|
||||
function Builder:autocraft(supplies)
|
||||
local t = { }
|
||||
local t = { }
|
||||
|
||||
for _,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
|
||||
for _,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)
|
||||
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
|
||||
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(...)
|
||||
Util.print(...)
|
||||
Util.print(...)
|
||||
end
|
||||
|
||||
function Builder:getSupplies()
|
||||
|
||||
Builder.itemProvider:refresh()
|
||||
Builder.itemProvider:refresh()
|
||||
|
||||
local t = { }
|
||||
for _,s in ipairs(self.slots) do
|
||||
if s.need > 0 then
|
||||
local item = Builder.itemProvider:getItemInfo(s)
|
||||
if item then
|
||||
if item.name then
|
||||
s.name = item.name
|
||||
end
|
||||
local t = { }
|
||||
for _,s in ipairs(self.slots) do
|
||||
if s.need > 0 then
|
||||
local item = Builder.itemProvider:getItemInfo(s)
|
||||
if item then
|
||||
if item.name then
|
||||
s.name = item.name
|
||||
end
|
||||
|
||||
local qty = math.min(s.need-s.qty, item.qty)
|
||||
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
|
||||
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
|
||||
Builder:log('Need %d %s', s.need - s.qty, name)
|
||||
end
|
||||
end
|
||||
|
||||
return t
|
||||
return t
|
||||
end
|
||||
|
||||
local function moveTowardsX(dx)
|
||||
|
||||
local direction = dx - turtle.point.x
|
||||
local move
|
||||
local direction = dx - turtle.point.x
|
||||
local move
|
||||
|
||||
if direction == 0 then
|
||||
return false
|
||||
end
|
||||
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
|
||||
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()
|
||||
return move()
|
||||
end
|
||||
|
||||
local function moveTowardsZ(dz)
|
||||
|
||||
local direction = dz - turtle.point.z
|
||||
local move
|
||||
local direction = dz - turtle.point.z
|
||||
local move
|
||||
|
||||
if direction == 0 then
|
||||
return false
|
||||
end
|
||||
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
|
||||
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()
|
||||
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
|
||||
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.setStatus('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 Builder.lastPoint then
|
||||
turtle.setStatus('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
|
||||
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(_, id, msg)
|
||||
if not id or id ~= __BUILDER_ID then
|
||||
return
|
||||
end
|
||||
function(_, id, msg)
|
||||
if not id or id ~= __BUILDER_ID then
|
||||
return
|
||||
end
|
||||
|
||||
if not Builder.resupplying then
|
||||
local pt = msg.contents
|
||||
pt.y = pt.y + 3
|
||||
if not Builder.resupplying then
|
||||
local pt = msg.contents
|
||||
pt.y = pt.y + 3
|
||||
|
||||
turtle.setStatus('supervising')
|
||||
turtle.gotoYfirst(pt)
|
||||
end
|
||||
end)
|
||||
turtle.setStatus('supervising')
|
||||
turtle.gotoYfirst(pt)
|
||||
end
|
||||
end)
|
||||
|
||||
Message.addHandler('supplyList',
|
||||
function(_, id, msg)
|
||||
if not id or id ~= __BUILDER_ID then
|
||||
return
|
||||
end
|
||||
function(_, id, msg)
|
||||
if not id or id ~= __BUILDER_ID then
|
||||
return
|
||||
end
|
||||
|
||||
turtle.setStatus('resupplying')
|
||||
Builder.resupplying = true
|
||||
Builder.slots = msg.contents.slots
|
||||
Builder.slotUid = msg.contents.uid
|
||||
turtle.setStatus('resupplying')
|
||||
Builder.resupplying = true
|
||||
Builder.slots = msg.contents.slots
|
||||
Builder.slotUid = msg.contents.uid
|
||||
|
||||
Builder:log('Received supply list ' .. Builder.slotUid)
|
||||
Builder:log('Received supply list ' .. Builder.slotUid)
|
||||
|
||||
os.sleep(0)
|
||||
if not turtle.gotoLocation('supplies') then
|
||||
Builder:log('Failed to go to supply location')
|
||||
Builder.ready = false
|
||||
Event.exitPullEvents()
|
||||
end
|
||||
turtle.setHeading(1)
|
||||
os.sleep(.2) -- random 'Computer is not connected' error...
|
||||
Builder:dumpInventoryWithCheck()
|
||||
Builder:refuel()
|
||||
os.sleep(0)
|
||||
if not turtle.gotoLocation('supplies') then
|
||||
Builder:log('Failed to go to supply location')
|
||||
Builder.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.setStatus('waiting')
|
||||
os.sleep(5)
|
||||
end
|
||||
Builder:log('Got all supplies')
|
||||
os.sleep(0)
|
||||
Builder:gotoBuilder()
|
||||
Builder.resupplying = false
|
||||
end)
|
||||
while true do
|
||||
local supplies = Builder:getSupplies()
|
||||
if #supplies == 0 then
|
||||
break
|
||||
end
|
||||
Builder:autocraft(supplies)
|
||||
turtle.setStatus('waiting')
|
||||
os.sleep(5)
|
||||
end
|
||||
Builder:log('Got all supplies')
|
||||
os.sleep(0)
|
||||
Builder:gotoBuilder()
|
||||
Builder.resupplying = false
|
||||
end)
|
||||
|
||||
Message.addHandler('needSupplies',
|
||||
function(_, id, msg)
|
||||
if not id or id ~= __BUILDER_ID then
|
||||
return
|
||||
end
|
||||
function(_, id, msg)
|
||||
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.setStatus('supplying')
|
||||
Builder:log('Supplying')
|
||||
os.sleep(0)
|
||||
if Builder.resupplying or msg.contents.uid ~= Builder.slotUid then
|
||||
Builder:log('No supplies ready')
|
||||
Message.send(__BUILDER_ID, 'gotSupplies')
|
||||
else
|
||||
turtle.setStatus('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')
|
||||
Event.exitPullEvents()
|
||||
end
|
||||
turtle.setHeading(1)
|
||||
return
|
||||
end
|
||||
pt.y = pt.y - 2 -- location where builder should go for the chest to be above
|
||||
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')
|
||||
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
|
||||
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.send(__BUILDER_ID, 'gotSupplies', { supplies = true, point = pt })
|
||||
|
||||
Message.waitForMessage('thanks', 5, __BUILDER_ID)
|
||||
--os.sleep(0)
|
||||
Message.waitForMessage('thanks', 5, __BUILDER_ID)
|
||||
--os.sleep(0)
|
||||
|
||||
--p.condenseItems()
|
||||
for i = 1, 16 do
|
||||
p:extract(i, 64)
|
||||
end
|
||||
turtle.digDown()
|
||||
turtle.setStatus('waiting')
|
||||
end
|
||||
end)
|
||||
--p.condenseItems()
|
||||
for i = 1, 16 do
|
||||
p:extract(i, 64)
|
||||
end
|
||||
turtle.digDown()
|
||||
turtle.setStatus('waiting')
|
||||
end
|
||||
end)
|
||||
|
||||
Message.addHandler('finished',
|
||||
function(_, id)
|
||||
if not id or id ~= __BUILDER_ID then
|
||||
return
|
||||
end
|
||||
Builder:finish()
|
||||
end)
|
||||
function(_, id)
|
||||
if not id or id ~= __BUILDER_ID then
|
||||
return
|
||||
end
|
||||
Builder:finish()
|
||||
end)
|
||||
|
||||
Event.on('turtle_abort',
|
||||
function()
|
||||
turtle.abort(false)
|
||||
turtle.setStatus('aborting')
|
||||
Builder:finish()
|
||||
end)
|
||||
function()
|
||||
turtle.abort(false)
|
||||
turtle.setStatus('aborting')
|
||||
Builder:finish()
|
||||
end)
|
||||
|
||||
local function onTheWay() -- parallel routine
|
||||
while true do
|
||||
local _, _, _, id, msg, _ = 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
|
||||
while true do
|
||||
local _, _, _, id, msg, _ = 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>')
|
||||
error('syntax: <builder id> <facing>')
|
||||
end
|
||||
|
||||
__BUILDER_ID = tonumber(args[1])
|
||||
@@ -401,30 +401,30 @@ __BUILDER_ID = tonumber(args[1])
|
||||
maxStackDB:load()
|
||||
|
||||
itemInfoDB = TableDB({
|
||||
fileName = 'items.db'
|
||||
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',
|
||||
}
|
||||
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
|
||||
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.setPoint({ x = -1, z = -2, y = -1, heading = 1 })
|
||||
|
||||
turtle.saveLocation('supplies')
|
||||
turtle.saveLocation('supplies')
|
||||
|
||||
Event.pullEvents(onTheWay)
|
||||
Event.pullEvents(onTheWay)
|
||||
end)
|
||||
|
||||
@@ -13,15 +13,15 @@ local function Syntax(msg)
|
||||
* Entity sensor
|
||||
* Introspection module
|
||||
]])
|
||||
error(msg)
|
||||
error(msg)
|
||||
end
|
||||
|
||||
local neural = device['neuralInterface'] or Syntax('Must be run on a neural interface')
|
||||
|
||||
local function assertModule(module, name)
|
||||
if not neural.hasModule(module) then
|
||||
Syntax('Missing: ' .. name)
|
||||
end
|
||||
if not neural.hasModule(module) then
|
||||
Syntax('Missing: ' .. name)
|
||||
end
|
||||
end
|
||||
assertModule('plethora:glasses', 'Overlay glasses')
|
||||
assertModule('plethora:sensor', 'Entity sensor')
|
||||
@@ -31,102 +31,102 @@ local BUILDER_DIR = 'usr/builder'
|
||||
|
||||
--[[-- SubDB --]]--
|
||||
local subDB = TableDB({
|
||||
fileName = fs.combine(BUILDER_DIR, 'sub.db'),
|
||||
fileName = fs.combine(BUILDER_DIR, 'sub.db'),
|
||||
})
|
||||
|
||||
function subDB:load()
|
||||
if fs.exists(self.fileName) then
|
||||
TableDB.load(self)
|
||||
elseif not Builder.isCommandComputer then
|
||||
self:seedDB()
|
||||
end
|
||||
if fs.exists(self.fileName) then
|
||||
TableDB.load(self)
|
||||
elseif not Builder.isCommandComputer then
|
||||
self:seedDB()
|
||||
end
|
||||
end
|
||||
|
||||
function subDB:seedDB()
|
||||
self.data = {
|
||||
[ "minecraft:redstone_wire:0" ] = "minecraft:redstone:0",
|
||||
[ "minecraft:wall_sign:0" ] = "minecraft:sign:0",
|
||||
[ "minecraft:standing_sign:0" ] = "minecraft:sign:0",
|
||||
[ "minecraft:potatoes:0" ] = "minecraft:potato:0",
|
||||
[ "minecraft:unlit_redstone_torch:0" ] = "minecraft:redstone_torch:0",
|
||||
[ "minecraft:powered_repeater:0" ] = "minecraft:repeater:0",
|
||||
[ "minecraft:unpowered_repeater:0" ] = "minecraft:repeater:0",
|
||||
[ "minecraft:carrots:0" ] = "minecraft:carrot:0",
|
||||
[ "minecraft:cocoa:0" ] = "minecraft:dye:3",
|
||||
[ "minecraft:unpowered_comparator:0" ] = "minecraft:comparator:0",
|
||||
[ "minecraft:powered_comparator:0" ] = "minecraft:comparator:0",
|
||||
[ "minecraft:piston_head:0" ] = "minecraft:air:0",
|
||||
[ "minecraft:piston_extension:0" ] = "minecraft:air:0",
|
||||
[ "minecraft:portal:0" ] = "minecraft:air:0",
|
||||
[ "minecraft:double_wooden_slab:0" ] = "minecraft:planks:0",
|
||||
[ "minecraft:double_wooden_slab:1" ] = "minecraft:planks:1",
|
||||
[ "minecraft:double_wooden_slab:2" ] = "minecraft:planks:2",
|
||||
[ "minecraft:double_wooden_slab:3" ] = "minecraft:planks:3",
|
||||
[ "minecraft:double_wooden_slab:4" ] = "minecraft:planks:4",
|
||||
[ "minecraft:double_wooden_slab:5" ] = "minecraft:planks:5",
|
||||
[ "minecraft:lit_redstone_lamp:0" ] = "minecraft:redstone_lamp:0",
|
||||
[ "minecraft:double_stone_slab:1" ] = "minecraft:sandstone:0",
|
||||
[ "minecraft:double_stone_slab:2" ] = "minecraft:planks:0",
|
||||
[ "minecraft:double_stone_slab:3" ] = "minecraft:cobblestone:0",
|
||||
[ "minecraft:double_stone_slab:4" ] = "minecraft:brick_block:0",
|
||||
[ "minecraft:double_stone_slab:5" ] = "minecraft:stonebrick:0",
|
||||
[ "minecraft:double_stone_slab:6" ] = "minecraft:nether_brick:0",
|
||||
[ "minecraft:double_stone_slab:7" ] = "minecraft:quartz_block:0",
|
||||
[ "minecraft:double_stone_slab:9" ] = "minecraft:sandstone:2",
|
||||
[ "minecraft:double_stone_slab2:0" ] = "minecraft:sandstone:0",
|
||||
[ "minecraft:stone_slab:2" ] = "minecraft:wooden_slab:0",
|
||||
[ "minecraft:wheat:0" ] = "minecraft:wheat_seeds:0",
|
||||
[ "minecraft:flowing_water:0" ] = "minecraft:air:0",
|
||||
[ "minecraft:lit_furnace:0" ] = "minecraft:furnace:0",
|
||||
[ "minecraft:wall_banner:0" ] = "minecraft:banner:0",
|
||||
[ "minecraft:standing_banner:0" ] = "minecraft:banner:0",
|
||||
[ "minecraft:tripwire:0" ] = "minecraft:string:0",
|
||||
[ "minecraft:pumpkin_stem:0" ] = "minecraft:pumpkin_seeds:0",
|
||||
}
|
||||
self.dirty = true
|
||||
self:flush()
|
||||
self.data = {
|
||||
[ "minecraft:redstone_wire:0" ] = "minecraft:redstone:0",
|
||||
[ "minecraft:wall_sign:0" ] = "minecraft:sign:0",
|
||||
[ "minecraft:standing_sign:0" ] = "minecraft:sign:0",
|
||||
[ "minecraft:potatoes:0" ] = "minecraft:potato:0",
|
||||
[ "minecraft:unlit_redstone_torch:0" ] = "minecraft:redstone_torch:0",
|
||||
[ "minecraft:powered_repeater:0" ] = "minecraft:repeater:0",
|
||||
[ "minecraft:unpowered_repeater:0" ] = "minecraft:repeater:0",
|
||||
[ "minecraft:carrots:0" ] = "minecraft:carrot:0",
|
||||
[ "minecraft:cocoa:0" ] = "minecraft:dye:3",
|
||||
[ "minecraft:unpowered_comparator:0" ] = "minecraft:comparator:0",
|
||||
[ "minecraft:powered_comparator:0" ] = "minecraft:comparator:0",
|
||||
[ "minecraft:piston_head:0" ] = "minecraft:air:0",
|
||||
[ "minecraft:piston_extension:0" ] = "minecraft:air:0",
|
||||
[ "minecraft:portal:0" ] = "minecraft:air:0",
|
||||
[ "minecraft:double_wooden_slab:0" ] = "minecraft:planks:0",
|
||||
[ "minecraft:double_wooden_slab:1" ] = "minecraft:planks:1",
|
||||
[ "minecraft:double_wooden_slab:2" ] = "minecraft:planks:2",
|
||||
[ "minecraft:double_wooden_slab:3" ] = "minecraft:planks:3",
|
||||
[ "minecraft:double_wooden_slab:4" ] = "minecraft:planks:4",
|
||||
[ "minecraft:double_wooden_slab:5" ] = "minecraft:planks:5",
|
||||
[ "minecraft:lit_redstone_lamp:0" ] = "minecraft:redstone_lamp:0",
|
||||
[ "minecraft:double_stone_slab:1" ] = "minecraft:sandstone:0",
|
||||
[ "minecraft:double_stone_slab:2" ] = "minecraft:planks:0",
|
||||
[ "minecraft:double_stone_slab:3" ] = "minecraft:cobblestone:0",
|
||||
[ "minecraft:double_stone_slab:4" ] = "minecraft:brick_block:0",
|
||||
[ "minecraft:double_stone_slab:5" ] = "minecraft:stonebrick:0",
|
||||
[ "minecraft:double_stone_slab:6" ] = "minecraft:nether_brick:0",
|
||||
[ "minecraft:double_stone_slab:7" ] = "minecraft:quartz_block:0",
|
||||
[ "minecraft:double_stone_slab:9" ] = "minecraft:sandstone:2",
|
||||
[ "minecraft:double_stone_slab2:0" ] = "minecraft:sandstone:0",
|
||||
[ "minecraft:stone_slab:2" ] = "minecraft:wooden_slab:0",
|
||||
[ "minecraft:wheat:0" ] = "minecraft:wheat_seeds:0",
|
||||
[ "minecraft:flowing_water:0" ] = "minecraft:air:0",
|
||||
[ "minecraft:lit_furnace:0" ] = "minecraft:furnace:0",
|
||||
[ "minecraft:wall_banner:0" ] = "minecraft:banner:0",
|
||||
[ "minecraft:standing_banner:0" ] = "minecraft:banner:0",
|
||||
[ "minecraft:tripwire:0" ] = "minecraft:string:0",
|
||||
[ "minecraft:pumpkin_stem:0" ] = "minecraft:pumpkin_seeds:0",
|
||||
}
|
||||
self.dirty = true
|
||||
self:flush()
|
||||
end
|
||||
|
||||
function subDB:add(s)
|
||||
TableDB.add(self, { s.id, s.dmg }, table.concat({ s.sid, s.sdmg }, ':'))
|
||||
self:flush()
|
||||
TableDB.add(self, { s.id, s.dmg }, table.concat({ s.sid, s.sdmg }, ':'))
|
||||
self:flush()
|
||||
end
|
||||
|
||||
function subDB:remove(s)
|
||||
-- TODO: tableDB.remove should take table key
|
||||
TableDB.remove(self, s.id .. ':' .. s.dmg)
|
||||
self:flush()
|
||||
-- TODO: tableDB.remove should take table key
|
||||
TableDB.remove(self, s.id .. ':' .. s.dmg)
|
||||
self:flush()
|
||||
end
|
||||
|
||||
function subDB:extract(s)
|
||||
local id, dmg = s:match('(.+):(%d+)')
|
||||
return id, tonumber(dmg)
|
||||
local id, dmg = s:match('(.+):(%d+)')
|
||||
return id, tonumber(dmg)
|
||||
end
|
||||
|
||||
function subDB:getSubstitutedItem(id, dmg)
|
||||
local sub = TableDB.get(self, { id, dmg })
|
||||
if sub then
|
||||
id, dmg = self:extract(sub)
|
||||
end
|
||||
return { id = id, dmg = dmg }
|
||||
local sub = TableDB.get(self, { id, dmg })
|
||||
if sub then
|
||||
id, dmg = self:extract(sub)
|
||||
end
|
||||
return { id = id, dmg = dmg }
|
||||
end
|
||||
|
||||
function subDB:lookupBlocksForSub(sid, sdmg)
|
||||
local t = { }
|
||||
for k,v in pairs(self.data) do
|
||||
local id, dmg = self:extract(v)
|
||||
if id == sid and dmg == sdmg then
|
||||
id, dmg = self:extract(k)
|
||||
t[k] = { id = id, dmg = dmg, sid = sid, sdmg = sdmg }
|
||||
end
|
||||
end
|
||||
return t
|
||||
local t = { }
|
||||
for k,v in pairs(self.data) do
|
||||
local id, dmg = self:extract(v)
|
||||
if id == sid and dmg == sdmg then
|
||||
id, dmg = self:extract(k)
|
||||
t[k] = { id = id, dmg = dmg, sid = sid, sdmg = sdmg }
|
||||
end
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
--[[-- startup logic --]]--
|
||||
local args = {...}
|
||||
if #args < 1 then
|
||||
error('supply file name')
|
||||
error('supply file name')
|
||||
end
|
||||
|
||||
subDB:load()
|
||||
@@ -142,18 +142,18 @@ Builder:substituteBlocks(Util.throttle())
|
||||
local cn = neural.canvas3d().create()
|
||||
local pos = neural.getMetaOwner().withinBlock
|
||||
|
||||
cn.recenter({-(pos.x + .5), -(pos.y + 2) + .5, -(pos.z + .5) })
|
||||
cn.recenter({-pos.x + .5, -(pos.y + 2) + .5, -pos.z + .5 })
|
||||
|
||||
for i = 1, #Builder.schematic.blocks do
|
||||
local b = Builder.schematic:getComputedBlock(i)
|
||||
if b.id ~= "minecraft:air" and b.id ~= 'minecraft:water' then
|
||||
local s, m = pcall(function()
|
||||
cn.addItem({ b.x, b.y, b.z }, b.id, b.dmg)
|
||||
end)
|
||||
if not s and m then
|
||||
_G.printError(m)
|
||||
end
|
||||
end
|
||||
local b = Builder.schematic:getComputedBlock(i)
|
||||
if b.id ~= "minecraft:air" and b.id ~= 'minecraft:water' then
|
||||
local s, m = pcall(function()
|
||||
cn.addItem({ b.x, b.y, b.z }, b.id, b.dmg)
|
||||
end)
|
||||
if not s and m then
|
||||
_G.printError(m)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
pcall(_G.read)
|
||||
|
||||
@@ -3,15 +3,15 @@ local fs = _G.fs
|
||||
local peripheral = _G.peripheral
|
||||
|
||||
if ccemux then
|
||||
-- add a System setup tab
|
||||
fs.mount('sys/apps/system/ccemux.lua', 'linkfs', 'packages/ccemux/system/ccemux.lua')
|
||||
-- add a System setup tab
|
||||
fs.mount('sys/apps/system/ccemux.lua', 'linkfs', 'packages/ccemux/system/ccemux.lua')
|
||||
|
||||
local Config = require('config')
|
||||
local Config = require('config')
|
||||
|
||||
for k,v in pairs(Config.load('ccemux')) do
|
||||
if not peripheral.getType(k) then
|
||||
ccemux.attach(k, v.type, v.args)
|
||||
end
|
||||
for k,v in pairs(Config.load('ccemux')) do
|
||||
if not peripheral.getType(k) then
|
||||
ccemux.attach(k, v.type, v.args)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -15,18 +15,18 @@ local REGISTRY_DIR = 'usr/.registry'
|
||||
-- FIX SOMEDAY
|
||||
local function registerApp(app, key)
|
||||
|
||||
app.key = SHA1.sha1(key)
|
||||
Util.writeTable(fs.combine(REGISTRY_DIR, app.key), app)
|
||||
os.queueEvent('os_register_app')
|
||||
app.key = SHA1.sha1(key)
|
||||
Util.writeTable(fs.combine(REGISTRY_DIR, app.key), app)
|
||||
os.queueEvent('os_register_app')
|
||||
end
|
||||
|
||||
local function unregisterApp(key)
|
||||
|
||||
local filename = fs.combine(REGISTRY_DIR, SHA1.sha1(key))
|
||||
if fs.exists(filename) then
|
||||
fs.delete(filename)
|
||||
os.queueEvent('os_register_app')
|
||||
end
|
||||
local filename = fs.combine(REGISTRY_DIR, SHA1.sha1(key))
|
||||
if fs.exists(filename) then
|
||||
fs.delete(filename)
|
||||
os.queueEvent('os_register_app')
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -40,366 +40,366 @@ local APP_DIR = 'usr/apps'
|
||||
|
||||
local sources = {
|
||||
|
||||
{ text = "STD Default",
|
||||
event = 'source',
|
||||
url = "http://pastebin.com/raw/zVws7eLq" }, --stock
|
||||
{ text = "STD Default",
|
||||
event = 'source',
|
||||
url = "http://pastebin.com/raw/zVws7eLq" }, --stock
|
||||
--[[
|
||||
{ text = "Discover",
|
||||
event = 'source',
|
||||
generateName = true,
|
||||
url = "http://pastebin.com/raw/9bXfCz6M" }, --owned by dannysmc95
|
||||
{ text = "Discover",
|
||||
event = 'source',
|
||||
generateName = true,
|
||||
url = "http://pastebin.com/raw/9bXfCz6M" }, --owned by dannysmc95
|
||||
|
||||
{ text = "Opus",
|
||||
event = 'source',
|
||||
url = "http://pastebin.com/raw/ajQ91Rmn" },
|
||||
{ text = "Opus",
|
||||
event = 'source',
|
||||
url = "http://pastebin.com/raw/ajQ91Rmn" },
|
||||
]]
|
||||
}
|
||||
|
||||
shell.setDir(APP_DIR)
|
||||
|
||||
local function downloadApp(app)
|
||||
local h
|
||||
local h
|
||||
|
||||
if type(app.url) == "table" then
|
||||
h = contextualGet(app.url[1])
|
||||
else
|
||||
h = http.get(app.url)
|
||||
end
|
||||
if type(app.url) == "table" then
|
||||
h = contextualGet(app.url[1])
|
||||
else
|
||||
h = http.get(app.url)
|
||||
end
|
||||
|
||||
if h then
|
||||
local contents = h.readAll()
|
||||
h:close()
|
||||
return contents
|
||||
end
|
||||
if h then
|
||||
local contents = h.readAll()
|
||||
h:close()
|
||||
return contents
|
||||
end
|
||||
end
|
||||
|
||||
local function runApp(app, checkExists, ...)
|
||||
|
||||
local path, fn
|
||||
local args = { ... }
|
||||
local path, fn
|
||||
local args = { ... }
|
||||
|
||||
if checkExists and fs.exists(fs.combine(APP_DIR, app.name)) then
|
||||
path = fs.combine(APP_DIR, app.name)
|
||||
else
|
||||
local program = downloadApp(app)
|
||||
if checkExists and fs.exists(fs.combine(APP_DIR, app.name)) then
|
||||
path = fs.combine(APP_DIR, app.name)
|
||||
else
|
||||
local program = downloadApp(app)
|
||||
|
||||
fn = function()
|
||||
fn = function()
|
||||
|
||||
if not program then
|
||||
error('Failed to download')
|
||||
end
|
||||
if not program then
|
||||
error('Failed to download')
|
||||
end
|
||||
|
||||
local fn = loadstring(program, app.name)
|
||||
local fn = loadstring(program, app.name)
|
||||
|
||||
if not fn then
|
||||
error('Failed to download')
|
||||
end
|
||||
if not fn then
|
||||
error('Failed to download')
|
||||
end
|
||||
|
||||
setfenv(fn, sandboxEnv)
|
||||
fn(unpack(args))
|
||||
end
|
||||
end
|
||||
setfenv(fn, sandboxEnv)
|
||||
fn(unpack(args))
|
||||
end
|
||||
end
|
||||
|
||||
multishell.openTab({
|
||||
title = app.name,
|
||||
env = sandboxEnv,
|
||||
path = path,
|
||||
fn = fn,
|
||||
focused = true,
|
||||
})
|
||||
multishell.openTab({
|
||||
title = app.name,
|
||||
env = sandboxEnv,
|
||||
path = path,
|
||||
fn = fn,
|
||||
focused = true,
|
||||
})
|
||||
|
||||
return true, 'Running program'
|
||||
return true, 'Running program'
|
||||
end
|
||||
|
||||
local installApp = function(app)
|
||||
|
||||
local program = downloadApp(app)
|
||||
if not program then
|
||||
return false, "Failed to download"
|
||||
end
|
||||
local program = downloadApp(app)
|
||||
if not program then
|
||||
return false, "Failed to download"
|
||||
end
|
||||
|
||||
local fullPath = fs.combine(APP_DIR, app.name)
|
||||
Util.writeFile(fullPath, program)
|
||||
return true, 'Installed as ' .. fullPath
|
||||
local fullPath = fs.combine(APP_DIR, app.name)
|
||||
Util.writeFile(fullPath, program)
|
||||
return true, 'Installed as ' .. fullPath
|
||||
end
|
||||
|
||||
local viewApp = function(app)
|
||||
|
||||
local program = downloadApp(app)
|
||||
if not program then
|
||||
return false, "Failed to download"
|
||||
end
|
||||
local program = downloadApp(app)
|
||||
if not program then
|
||||
return false, "Failed to download"
|
||||
end
|
||||
|
||||
Util.writeFile('/.source', program)
|
||||
shell.openForegroundTab('edit /.source')
|
||||
fs.delete('/.source')
|
||||
return true
|
||||
Util.writeFile('/.source', program)
|
||||
shell.openForegroundTab('edit /.source')
|
||||
fs.delete('/.source')
|
||||
return true
|
||||
end
|
||||
|
||||
local getSourceListing = function(source)
|
||||
local contents = http.get(source.url)
|
||||
if contents then
|
||||
local contents = http.get(source.url)
|
||||
if contents then
|
||||
|
||||
local fn = loadstring(contents.readAll(), source.text)
|
||||
contents.close()
|
||||
local fn = loadstring(contents.readAll(), source.text)
|
||||
contents.close()
|
||||
|
||||
local env = { std = { } }
|
||||
setmetatable(env, { __index = _G })
|
||||
setfenv(fn, env)
|
||||
fn()
|
||||
local env = { std = { } }
|
||||
setmetatable(env, { __index = _G })
|
||||
setfenv(fn, env)
|
||||
fn()
|
||||
|
||||
if env.contextualGet then
|
||||
contextualGet = env.contextualGet
|
||||
end
|
||||
if env.contextualGet then
|
||||
contextualGet = env.contextualGet
|
||||
end
|
||||
|
||||
source.storeURLs = env.std.storeURLs
|
||||
source.storeCatagoryNames = env.std.storeCatagoryNames
|
||||
source.storeURLs = env.std.storeURLs
|
||||
source.storeCatagoryNames = env.std.storeCatagoryNames
|
||||
|
||||
if source.storeURLs and source.storeCatagoryNames then
|
||||
for k,v in pairs(source.storeURLs) do
|
||||
if source.generateName then
|
||||
v.name = v.title:match('(%w+)')
|
||||
if not v.name or #v.name == 0 then
|
||||
v.name = tostring(k)
|
||||
else
|
||||
v.name = v.name:lower()
|
||||
end
|
||||
else
|
||||
v.name = k
|
||||
end
|
||||
v.categoryName = source.storeCatagoryNames[v.catagory]
|
||||
v.ltitle = v.title:lower()
|
||||
end
|
||||
end
|
||||
end
|
||||
if source.storeURLs and source.storeCatagoryNames then
|
||||
for k,v in pairs(source.storeURLs) do
|
||||
if source.generateName then
|
||||
v.name = v.title:match('(%w+)')
|
||||
if not v.name or #v.name == 0 then
|
||||
v.name = tostring(k)
|
||||
else
|
||||
v.name = v.name:lower()
|
||||
end
|
||||
else
|
||||
v.name = k
|
||||
end
|
||||
v.categoryName = source.storeCatagoryNames[v.catagory]
|
||||
v.ltitle = v.title:lower()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local appPage = UI.Page {
|
||||
menuBar = UI.MenuBar {
|
||||
menuBar = UI.MenuBar {
|
||||
-- showBackButton = not pocket,
|
||||
buttons = {
|
||||
{ text = '\027', event = 'back' },
|
||||
{ text = 'Install', event = 'install' },
|
||||
{ text = 'Run', event = 'run' },
|
||||
{ text = 'View', event = 'view' },
|
||||
{ text = 'Remove', event = 'uninstall', name = 'removeButton' },
|
||||
},
|
||||
},
|
||||
container = UI.Window {
|
||||
x = 2, y = 3, ex = -2, ey = -3,
|
||||
viewport = UI.Viewport(),
|
||||
},
|
||||
notification = UI.Notification(),
|
||||
accelerators = {
|
||||
q = 'back',
|
||||
backspace = 'back',
|
||||
},
|
||||
buttons = {
|
||||
{ text = '\027', event = 'back' },
|
||||
{ text = 'Install', event = 'install' },
|
||||
{ text = 'Run', event = 'run' },
|
||||
{ text = 'View', event = 'view' },
|
||||
{ text = 'Remove', event = 'uninstall', name = 'removeButton' },
|
||||
},
|
||||
},
|
||||
container = UI.Window {
|
||||
x = 2, y = 3, ex = -2, ey = -3,
|
||||
viewport = UI.Viewport(),
|
||||
},
|
||||
notification = UI.Notification(),
|
||||
accelerators = {
|
||||
q = 'back',
|
||||
backspace = 'back',
|
||||
},
|
||||
}
|
||||
|
||||
function appPage.container.viewport:draw()
|
||||
local app = self.parent.parent.app
|
||||
local str = string.format(
|
||||
'%s \nBy: %s \nCategory: %s\nFile name: %s\n\n%s',
|
||||
Ansi.yellow .. app.title .. Ansi.reset,
|
||||
app.creator,
|
||||
app.categoryName, app.name,
|
||||
Ansi.yellow .. app.description .. Ansi.reset)
|
||||
local app = self.parent.parent.app
|
||||
local str = string.format(
|
||||
'%s \nBy: %s \nCategory: %s\nFile name: %s\n\n%s',
|
||||
Ansi.yellow .. app.title .. Ansi.reset,
|
||||
app.creator,
|
||||
app.categoryName, app.name,
|
||||
Ansi.yellow .. app.description .. Ansi.reset)
|
||||
|
||||
self:clear()
|
||||
self:setCursorPos(1, 1)
|
||||
self:print(str)
|
||||
self.ymax = self.cursorY
|
||||
self:clear()
|
||||
self:setCursorPos(1, 1)
|
||||
self:print(str)
|
||||
self.ymax = self.cursorY
|
||||
|
||||
if appPage.notification.enabled then
|
||||
appPage.notification:draw()
|
||||
end
|
||||
if appPage.notification.enabled then
|
||||
appPage.notification:draw()
|
||||
end
|
||||
end
|
||||
|
||||
function appPage:enable(source, app)
|
||||
self.source = source
|
||||
self.app = app
|
||||
UI.Page.enable(self)
|
||||
self.source = source
|
||||
self.app = app
|
||||
UI.Page.enable(self)
|
||||
|
||||
self.container.viewport:setScrollPosition(0)
|
||||
if fs.exists(fs.combine(APP_DIR, app.name)) then
|
||||
self.menuBar.removeButton:enable('Remove')
|
||||
else
|
||||
self.menuBar.removeButton:disable('Remove')
|
||||
end
|
||||
self.container.viewport:setScrollPosition(0)
|
||||
if fs.exists(fs.combine(APP_DIR, app.name)) then
|
||||
self.menuBar.removeButton:enable('Remove')
|
||||
else
|
||||
self.menuBar.removeButton:disable('Remove')
|
||||
end
|
||||
end
|
||||
|
||||
function appPage:eventHandler(event)
|
||||
if event.type == 'back' then
|
||||
UI:setPreviousPage()
|
||||
if event.type == 'back' then
|
||||
UI:setPreviousPage()
|
||||
|
||||
elseif event.type == 'run' then
|
||||
self.notification:info('Running program', 3)
|
||||
self:sync()
|
||||
runApp(self.app, true)
|
||||
elseif event.type == 'run' then
|
||||
self.notification:info('Running program', 3)
|
||||
self:sync()
|
||||
runApp(self.app, true)
|
||||
|
||||
elseif event.type == 'view' then
|
||||
self.notification:info('Downloading program', 3)
|
||||
self:sync()
|
||||
viewApp(self.app)
|
||||
elseif event.type == 'view' then
|
||||
self.notification:info('Downloading program', 3)
|
||||
self:sync()
|
||||
viewApp(self.app)
|
||||
|
||||
elseif event.type == 'uninstall' then
|
||||
if self.app.runOnly then
|
||||
runApp(self.app, false, 'uninstall')
|
||||
else
|
||||
fs.delete(fs.combine(APP_DIR, self.app.name))
|
||||
self.notification:success("Uninstalled " .. self.app.name, 3)
|
||||
self:focusFirst(self)
|
||||
self.menuBar.removeButton:disable('Remove')
|
||||
self.menuBar:draw()
|
||||
elseif event.type == 'uninstall' then
|
||||
if self.app.runOnly then
|
||||
runApp(self.app, false, 'uninstall')
|
||||
else
|
||||
fs.delete(fs.combine(APP_DIR, self.app.name))
|
||||
self.notification:success("Uninstalled " .. self.app.name, 3)
|
||||
self:focusFirst(self)
|
||||
self.menuBar.removeButton:disable('Remove')
|
||||
self.menuBar:draw()
|
||||
|
||||
unregisterApp(self.app.creator .. '.' .. self.app.name)
|
||||
end
|
||||
unregisterApp(self.app.creator .. '.' .. self.app.name)
|
||||
end
|
||||
|
||||
elseif event.type == 'install' then
|
||||
self.notification:info("Installing", 3)
|
||||
self:sync()
|
||||
local s, m
|
||||
if self.app.runOnly then
|
||||
s,m = runApp(self.app, false)
|
||||
else
|
||||
s,m = installApp(self.app)
|
||||
end
|
||||
if s then
|
||||
self.notification:success(m, 3)
|
||||
elseif event.type == 'install' then
|
||||
self.notification:info("Installing", 3)
|
||||
self:sync()
|
||||
local s, m
|
||||
if self.app.runOnly then
|
||||
s,m = runApp(self.app, false)
|
||||
else
|
||||
s,m = installApp(self.app)
|
||||
end
|
||||
if s then
|
||||
self.notification:success(m, 3)
|
||||
|
||||
if not self.app.runOnly then
|
||||
self.menuBar.removeButton:enable('Remove')
|
||||
self.menuBar:draw()
|
||||
if not self.app.runOnly then
|
||||
self.menuBar.removeButton:enable('Remove')
|
||||
self.menuBar:draw()
|
||||
|
||||
local category = 'Apps'
|
||||
if self.app.catagoryName == 'Game' then
|
||||
category = 'Games'
|
||||
end
|
||||
local category = 'Apps'
|
||||
if self.app.catagoryName == 'Game' then
|
||||
category = 'Games'
|
||||
end
|
||||
|
||||
registerApp({
|
||||
run = fs.combine(APP_DIR, self.app.name),
|
||||
title = self.app.title,
|
||||
category = category,
|
||||
icon = self.app.icon,
|
||||
}, self.app.creator .. '.' .. self.app.name)
|
||||
end
|
||||
else
|
||||
self.notification:error(m, 3)
|
||||
end
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
registerApp({
|
||||
run = fs.combine(APP_DIR, self.app.name),
|
||||
title = self.app.title,
|
||||
category = category,
|
||||
icon = self.app.icon,
|
||||
}, self.app.creator .. '.' .. self.app.name)
|
||||
end
|
||||
else
|
||||
self.notification:error(m, 3)
|
||||
end
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local categoryPage = UI.Page {
|
||||
menuBar = UI.MenuBar {
|
||||
buttons = {
|
||||
{ text = 'Catalog', dropdown = sources },
|
||||
{ text = 'Category', name = 'categoryButton', dropdown = { } },
|
||||
},
|
||||
},
|
||||
grid = UI.ScrollingGrid {
|
||||
y = 2, ey = -2,
|
||||
columns = {
|
||||
{ heading = 'Title', key = 'title' },
|
||||
},
|
||||
sortColumn = 'title',
|
||||
},
|
||||
statusBar = UI.StatusBar(),
|
||||
accelerators = {
|
||||
l = 'lua',
|
||||
q = 'quit',
|
||||
},
|
||||
menuBar = UI.MenuBar {
|
||||
buttons = {
|
||||
{ text = 'Catalog', dropdown = sources },
|
||||
{ text = 'Category', name = 'categoryButton', dropdown = { } },
|
||||
},
|
||||
},
|
||||
grid = UI.ScrollingGrid {
|
||||
y = 2, ey = -2,
|
||||
columns = {
|
||||
{ heading = 'Title', key = 'title' },
|
||||
},
|
||||
sortColumn = 'title',
|
||||
},
|
||||
statusBar = UI.StatusBar(),
|
||||
accelerators = {
|
||||
l = 'lua',
|
||||
q = 'quit',
|
||||
},
|
||||
}
|
||||
|
||||
function categoryPage:setCategory(source, name, index)
|
||||
self.grid.values = { }
|
||||
for _,v in pairs(source.storeURLs) do
|
||||
if index == 0 or index == v.catagory then
|
||||
table.insert(self.grid.values, v)
|
||||
end
|
||||
end
|
||||
self.statusBar:setStatus(string.format('%s: %s', source.text, name))
|
||||
self.grid:update()
|
||||
self.grid:setIndex(1)
|
||||
self.grid.values = { }
|
||||
for _,v in pairs(source.storeURLs) do
|
||||
if index == 0 or index == v.catagory then
|
||||
table.insert(self.grid.values, v)
|
||||
end
|
||||
end
|
||||
self.statusBar:setStatus(string.format('%s: %s', source.text, name))
|
||||
self.grid:update()
|
||||
self.grid:setIndex(1)
|
||||
end
|
||||
|
||||
function categoryPage:setSource(source)
|
||||
|
||||
if not source.categoryMenu then
|
||||
if not source.categoryMenu then
|
||||
|
||||
self.statusBar:setStatus('Loading...')
|
||||
self.statusBar:draw()
|
||||
self:sync()
|
||||
self.statusBar:setStatus('Loading...')
|
||||
self.statusBar:draw()
|
||||
self:sync()
|
||||
|
||||
getSourceListing(source)
|
||||
getSourceListing(source)
|
||||
|
||||
if not source.storeURLs then
|
||||
error('Unable to download application list')
|
||||
end
|
||||
if not source.storeURLs then
|
||||
error('Unable to download application list')
|
||||
end
|
||||
|
||||
local buttons = { }
|
||||
for k,v in Util.spairs(source.storeCatagoryNames,
|
||||
function(a, b) return a:lower() < b:lower() end) do
|
||||
local buttons = { }
|
||||
for k,v in Util.spairs(source.storeCatagoryNames,
|
||||
function(a, b) return a:lower() < b:lower() end) do
|
||||
|
||||
if v ~= 'Operating System' then
|
||||
table.insert(buttons, {
|
||||
text = v,
|
||||
event = 'category',
|
||||
index = k,
|
||||
})
|
||||
end
|
||||
end
|
||||
if v ~= 'Operating System' then
|
||||
table.insert(buttons, {
|
||||
text = v,
|
||||
event = 'category',
|
||||
index = k,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
source.categoryMenu = UI.DropMenu({
|
||||
buttons = buttons,
|
||||
})
|
||||
source.index, source.name = Util.first(source.storeCatagoryNames)
|
||||
source.categoryMenu = UI.DropMenu({
|
||||
buttons = buttons,
|
||||
})
|
||||
source.index, source.name = Util.first(source.storeCatagoryNames)
|
||||
|
||||
categoryPage.menuBar.categoryButton:add({
|
||||
categoryMenu = source.categoryMenu
|
||||
})
|
||||
end
|
||||
categoryPage.menuBar.categoryButton:add({
|
||||
categoryMenu = source.categoryMenu
|
||||
})
|
||||
end
|
||||
|
||||
self.source = source
|
||||
self.menuBar.categoryButton.dropmenu = source.categoryMenu
|
||||
categoryPage:setCategory(source, source.name, source.index)
|
||||
self.source = source
|
||||
self.menuBar.categoryButton.dropmenu = source.categoryMenu
|
||||
categoryPage:setCategory(source, source.name, source.index)
|
||||
end
|
||||
|
||||
function categoryPage.grid:sortCompare(a, b)
|
||||
return a.ltitle < b.ltitle
|
||||
return a.ltitle < b.ltitle
|
||||
end
|
||||
|
||||
function categoryPage.grid:getRowTextColor(row, selected)
|
||||
if fs.exists(fs.combine(APP_DIR, row.name)) then
|
||||
return colors.orange
|
||||
end
|
||||
return UI.Grid:getRowTextColor(row, selected)
|
||||
if fs.exists(fs.combine(APP_DIR, row.name)) then
|
||||
return colors.orange
|
||||
end
|
||||
return UI.Grid:getRowTextColor(row, selected)
|
||||
end
|
||||
|
||||
function categoryPage:eventHandler(event)
|
||||
|
||||
if event.type == 'grid_select' or event.type == 'select' then
|
||||
UI:setPage(appPage, self.source, self.grid:getSelected())
|
||||
if event.type == 'grid_select' or event.type == 'select' then
|
||||
UI:setPage(appPage, self.source, self.grid:getSelected())
|
||||
|
||||
elseif event.type == 'category' then
|
||||
self:setCategory(self.source, event.button.text, event.button.index)
|
||||
self:setFocus(self.grid)
|
||||
self:draw()
|
||||
elseif event.type == 'category' then
|
||||
self:setCategory(self.source, event.button.text, event.button.index)
|
||||
self:setFocus(self.grid)
|
||||
self:draw()
|
||||
|
||||
elseif event.type == 'source' then
|
||||
self:setFocus(self.grid)
|
||||
self:setSource(event.button)
|
||||
self:draw()
|
||||
elseif event.type == 'source' then
|
||||
self:setFocus(self.grid)
|
||||
self:setSource(event.button)
|
||||
self:draw()
|
||||
|
||||
elseif event.type == 'quit' then
|
||||
UI:exitPullEvents()
|
||||
elseif event.type == 'quit' then
|
||||
UI:exitPullEvents()
|
||||
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
print("Retrieving catalog list")
|
||||
|
||||
@@ -10,196 +10,196 @@ local peripheral = _G.peripheral
|
||||
|
||||
--[[ -- PeripheralsPage -- ]] --
|
||||
local peripheralsPage = UI.Page {
|
||||
grid = UI.ScrollingGrid {
|
||||
ey = -2,
|
||||
columns = {
|
||||
{ heading = 'Type', key = 'type' },
|
||||
{ heading = 'Side', key = 'side' },
|
||||
},
|
||||
sortColumn = 'type',
|
||||
autospace = true,
|
||||
},
|
||||
statusBar = UI.StatusBar {
|
||||
values = 'Select peripheral',
|
||||
},
|
||||
accelerators = {
|
||||
q = 'quit',
|
||||
},
|
||||
grid = UI.ScrollingGrid {
|
||||
ey = -2,
|
||||
columns = {
|
||||
{ heading = 'Type', key = 'type' },
|
||||
{ heading = 'Side', key = 'side' },
|
||||
},
|
||||
sortColumn = 'type',
|
||||
autospace = true,
|
||||
},
|
||||
statusBar = UI.StatusBar {
|
||||
values = 'Select peripheral',
|
||||
},
|
||||
accelerators = {
|
||||
q = 'quit',
|
||||
},
|
||||
}
|
||||
|
||||
function peripheralsPage.grid:draw()
|
||||
local sides = peripheral.getNames()
|
||||
local sides = peripheral.getNames()
|
||||
|
||||
Util.clear(self.values)
|
||||
for _,side in pairs(sides) do
|
||||
table.insert(self.values, {
|
||||
type = peripheral.getType(side),
|
||||
side = side
|
||||
})
|
||||
end
|
||||
self:update()
|
||||
self:adjustWidth()
|
||||
UI.Grid.draw(self)
|
||||
Util.clear(self.values)
|
||||
for _,side in pairs(sides) do
|
||||
table.insert(self.values, {
|
||||
type = peripheral.getType(side),
|
||||
side = side
|
||||
})
|
||||
end
|
||||
self:update()
|
||||
self:adjustWidth()
|
||||
UI.Grid.draw(self)
|
||||
end
|
||||
|
||||
function peripheralsPage:updatePeripherals()
|
||||
if UI:getCurrentPage() == self then
|
||||
self.grid:draw()
|
||||
self:sync()
|
||||
end
|
||||
if UI:getCurrentPage() == self then
|
||||
self.grid:draw()
|
||||
self:sync()
|
||||
end
|
||||
end
|
||||
|
||||
function peripheralsPage:eventHandler(event)
|
||||
if event.type == 'quit' then
|
||||
Event.exitPullEvents()
|
||||
if event.type == 'quit' then
|
||||
Event.exitPullEvents()
|
||||
|
||||
elseif event.type == 'grid_select' then
|
||||
UI:setPage('methods', event.selected)
|
||||
elseif event.type == 'grid_select' then
|
||||
UI:setPage('methods', event.selected)
|
||||
|
||||
end
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
|
||||
--[[ -- MethodsPage -- ]] --
|
||||
local methodsPage = UI.Page {
|
||||
backgroundColor = colors.black,
|
||||
doc = UI.TextArea {
|
||||
backgroundColor = colors.black,
|
||||
x = 2, y = 2, ex = -1, ey = -7,
|
||||
},
|
||||
grid = UI.ScrollingGrid {
|
||||
y = -6, ey = -2,
|
||||
columns = {
|
||||
{ heading = 'Name', key = 'name', width = UI.term.width }
|
||||
},
|
||||
sortColumn = 'name',
|
||||
},
|
||||
statusBar = UI.StatusBar {
|
||||
status = 'q to return',
|
||||
},
|
||||
accelerators = {
|
||||
q = 'back',
|
||||
backspace = 'back',
|
||||
},
|
||||
backgroundColor = colors.black,
|
||||
doc = UI.TextArea {
|
||||
backgroundColor = colors.black,
|
||||
x = 2, y = 2, ex = -1, ey = -7,
|
||||
},
|
||||
grid = UI.ScrollingGrid {
|
||||
y = -6, ey = -2,
|
||||
columns = {
|
||||
{ heading = 'Name', key = 'name', width = UI.term.width }
|
||||
},
|
||||
sortColumn = 'name',
|
||||
},
|
||||
statusBar = UI.StatusBar {
|
||||
status = 'q to return',
|
||||
},
|
||||
accelerators = {
|
||||
q = 'back',
|
||||
backspace = 'back',
|
||||
},
|
||||
}
|
||||
|
||||
function methodsPage:enable(p)
|
||||
|
||||
self.peripheral = p or self.peripheral
|
||||
self.peripheral = p or self.peripheral
|
||||
|
||||
p = peripheral.wrap(self.peripheral.side)
|
||||
if p.getDocs then
|
||||
-- plethora
|
||||
self.grid.values = { }
|
||||
for k,v in pairs(p.getDocs()) do
|
||||
table.insert(self.grid.values, {
|
||||
name = k,
|
||||
doc = v,
|
||||
})
|
||||
end
|
||||
elseif not p.getAdvancedMethodsData then
|
||||
-- computercraft
|
||||
self.grid.values = { }
|
||||
for name in pairs(p) do
|
||||
table.insert(self.grid.values, {
|
||||
name = name,
|
||||
noext = true,
|
||||
})
|
||||
end
|
||||
else
|
||||
-- open peripherals
|
||||
self.grid.values = p.getAdvancedMethodsData()
|
||||
for name,f in pairs(self.grid.values) do
|
||||
f.name = name
|
||||
end
|
||||
end
|
||||
p = peripheral.wrap(self.peripheral.side)
|
||||
if p.getDocs then
|
||||
-- plethora
|
||||
self.grid.values = { }
|
||||
for k,v in pairs(p.getDocs()) do
|
||||
table.insert(self.grid.values, {
|
||||
name = k,
|
||||
doc = v,
|
||||
})
|
||||
end
|
||||
elseif not p.getAdvancedMethodsData then
|
||||
-- computercraft
|
||||
self.grid.values = { }
|
||||
for name in pairs(p) do
|
||||
table.insert(self.grid.values, {
|
||||
name = name,
|
||||
noext = true,
|
||||
})
|
||||
end
|
||||
else
|
||||
-- open peripherals
|
||||
self.grid.values = p.getAdvancedMethodsData()
|
||||
for name,f in pairs(self.grid.values) do
|
||||
f.name = name
|
||||
end
|
||||
end
|
||||
|
||||
self.grid:update()
|
||||
self.grid:setIndex(1)
|
||||
self.grid:update()
|
||||
self.grid:setIndex(1)
|
||||
|
||||
self.doc:setText(self:getDocumentation())
|
||||
self.doc:setText(self:getDocumentation())
|
||||
|
||||
self.statusBar:setStatus(self.peripheral.type)
|
||||
UI.Page.enable(self)
|
||||
self.statusBar:setStatus(self.peripheral.type)
|
||||
UI.Page.enable(self)
|
||||
|
||||
self:setFocus(self.grid)
|
||||
self:setFocus(self.grid)
|
||||
end
|
||||
|
||||
function methodsPage:eventHandler(event)
|
||||
if event.type == 'back' then
|
||||
UI:setPage(peripheralsPage)
|
||||
return true
|
||||
elseif event.type == 'grid_focus_row' then
|
||||
self.doc:setText(self:getDocumentation())
|
||||
end
|
||||
return UI.Page.eventHandler(self, event)
|
||||
if event.type == 'back' then
|
||||
UI:setPage(peripheralsPage)
|
||||
return true
|
||||
elseif event.type == 'grid_focus_row' then
|
||||
self.doc:setText(self:getDocumentation())
|
||||
end
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
|
||||
function methodsPage:getDocumentation()
|
||||
|
||||
local method = self.grid:getSelected()
|
||||
local method = self.grid:getSelected()
|
||||
|
||||
if method.noext then -- computercraft docs
|
||||
return 'No documentation'
|
||||
end
|
||||
if method.noext then -- computercraft docs
|
||||
return 'No documentation'
|
||||
end
|
||||
|
||||
if method.doc then -- plethora docs
|
||||
return Ansi.yellow .. method.doc
|
||||
end
|
||||
if method.doc then -- plethora docs
|
||||
return Ansi.yellow .. method.doc
|
||||
end
|
||||
|
||||
-- open peripherals docs
|
||||
local sb = { }
|
||||
if method.description then
|
||||
table.insert(sb, method.description .. '\n\n')
|
||||
end
|
||||
-- open peripherals docs
|
||||
local sb = { }
|
||||
if method.description then
|
||||
table.insert(sb, method.description .. '\n\n')
|
||||
end
|
||||
|
||||
if method.returnTypes ~= '()' then
|
||||
table.insert(sb, Ansi.yellow .. method.returnTypes .. ' ')
|
||||
end
|
||||
table.insert(sb, Ansi.blue .. method.name .. Ansi.reset .. '(')
|
||||
if method.returnTypes ~= '()' then
|
||||
table.insert(sb, Ansi.yellow .. method.returnTypes .. ' ')
|
||||
end
|
||||
table.insert(sb, Ansi.blue .. method.name .. Ansi.reset .. '(')
|
||||
|
||||
for k,arg in ipairs(method.args) do
|
||||
if arg.optional then
|
||||
table.insert(sb, Ansi.orange .. string.format('[%s]', arg.name))
|
||||
else
|
||||
table.insert(sb, Ansi.green .. arg.name)
|
||||
end
|
||||
if k < #method.args then
|
||||
table.insert(sb, ',')
|
||||
end
|
||||
end
|
||||
table.insert(sb, Ansi.reset .. ')')
|
||||
for k,arg in ipairs(method.args) do
|
||||
if arg.optional then
|
||||
table.insert(sb, Ansi.orange .. string.format('[%s]', arg.name))
|
||||
else
|
||||
table.insert(sb, Ansi.green .. arg.name)
|
||||
end
|
||||
if k < #method.args then
|
||||
table.insert(sb, ',')
|
||||
end
|
||||
end
|
||||
table.insert(sb, Ansi.reset .. ')')
|
||||
|
||||
Util.filterInplace(method.args, function(a) return #a.description > 0 end)
|
||||
if #method.args > 0 then
|
||||
table.insert(sb, '\n\n')
|
||||
for k,arg in ipairs(method.args) do
|
||||
if arg.optional then
|
||||
table.insert(sb, Ansi.orange)
|
||||
else
|
||||
table.insert(sb, Ansi.green)
|
||||
end
|
||||
table.insert(sb, arg.name .. Ansi.reset .. ': ' .. arg.description)
|
||||
if k ~= #method.args then
|
||||
table.insert(sb, '\n\n')
|
||||
end
|
||||
end
|
||||
end
|
||||
return table.concat(sb)
|
||||
Util.filterInplace(method.args, function(a) return #a.description > 0 end)
|
||||
if #method.args > 0 then
|
||||
table.insert(sb, '\n\n')
|
||||
for k,arg in ipairs(method.args) do
|
||||
if arg.optional then
|
||||
table.insert(sb, Ansi.orange)
|
||||
else
|
||||
table.insert(sb, Ansi.green)
|
||||
end
|
||||
table.insert(sb, arg.name .. Ansi.reset .. ': ' .. arg.description)
|
||||
if k ~= #method.args then
|
||||
table.insert(sb, '\n\n')
|
||||
end
|
||||
end
|
||||
end
|
||||
return table.concat(sb)
|
||||
end
|
||||
|
||||
Event.on('peripheral', function()
|
||||
peripheralsPage:updatePeripherals()
|
||||
peripheralsPage:updatePeripherals()
|
||||
end)
|
||||
|
||||
Event.on('peripheral_detach', function()
|
||||
peripheralsPage:updatePeripherals()
|
||||
peripheralsPage:updatePeripherals()
|
||||
end)
|
||||
|
||||
UI:setPage(peripheralsPage)
|
||||
|
||||
UI:setPages({
|
||||
methods = methodsPage,
|
||||
methods = methodsPage,
|
||||
})
|
||||
|
||||
UI:pullEvents()
|
||||
|
||||
@@ -10,230 +10,230 @@ local peripheral = _G.peripheral
|
||||
|
||||
local drives = { }
|
||||
peripheral.find('drive', function(n, v)
|
||||
if not drives.left then
|
||||
drives.left = Util.shallowCopy(v)
|
||||
drives.left.name = n
|
||||
else
|
||||
drives.right = Util.shallowCopy(v)
|
||||
drives.right.name = n
|
||||
end
|
||||
if not drives.left then
|
||||
drives.left = Util.shallowCopy(v)
|
||||
drives.left.name = n
|
||||
else
|
||||
drives.right = Util.shallowCopy(v)
|
||||
drives.right.name = n
|
||||
end
|
||||
end)
|
||||
|
||||
if not (drives.left and drives.right) then
|
||||
error('Two drives are required')
|
||||
error('Two drives are required')
|
||||
end
|
||||
|
||||
local COPY_LEFT = 1
|
||||
local COPY_RIGHT = 2
|
||||
local directions = {
|
||||
[ COPY_LEFT ] = { text = '-->>' },
|
||||
[ COPY_RIGHT ] = { text = '<<--' },
|
||||
[ COPY_LEFT ] = { text = '-->>' },
|
||||
[ COPY_RIGHT ] = { text = '<<--' },
|
||||
}
|
||||
|
||||
local config = Config.load('DiskCopy', {
|
||||
eject = true,
|
||||
automatic = false,
|
||||
copyDir = COPY_LEFT
|
||||
eject = true,
|
||||
automatic = false,
|
||||
copyDir = COPY_LEFT
|
||||
})
|
||||
|
||||
local page = UI.Page {
|
||||
linfo = UI.Window {
|
||||
x = 2, y = 2, ey = 5, width = 18,
|
||||
},
|
||||
rinfo = UI.Window {
|
||||
x = -19, y = 2, ey = 5, width = 18,
|
||||
},
|
||||
dir = UI.Button {
|
||||
x = 17, y = 7, width = 6,
|
||||
event = 'change_dir',
|
||||
},
|
||||
progress = UI.ProgressBar {
|
||||
x = 2, ex = -2, y = -4,
|
||||
backgroundColor = colors.black,
|
||||
},
|
||||
ejectText = UI.Text {
|
||||
x = 2, y = -2,
|
||||
value = 'Eject'
|
||||
},
|
||||
eject = UI.Checkbox {
|
||||
x = 8, y = -2,
|
||||
},
|
||||
automaticText = UI.Text {
|
||||
x = 12, y = -2,
|
||||
value = 'Copy automatically'
|
||||
},
|
||||
automatic = UI.Checkbox {
|
||||
x = 31, y = -2,
|
||||
},
|
||||
copyButton = UI.Button {
|
||||
x = -7, y = -2,
|
||||
text = 'Copy',
|
||||
event = 'copy',
|
||||
inactive = true,
|
||||
},
|
||||
warning = UI.Text {
|
||||
x = 2, ex = -2, y = -5,
|
||||
align = 'center',
|
||||
textColor = colors.orange,
|
||||
},
|
||||
notification = UI.Notification { },
|
||||
linfo = UI.Window {
|
||||
x = 2, y = 2, ey = 5, width = 18,
|
||||
},
|
||||
rinfo = UI.Window {
|
||||
x = -19, y = 2, ey = 5, width = 18,
|
||||
},
|
||||
dir = UI.Button {
|
||||
x = 17, y = 7, width = 6,
|
||||
event = 'change_dir',
|
||||
},
|
||||
progress = UI.ProgressBar {
|
||||
x = 2, ex = -2, y = -4,
|
||||
backgroundColor = colors.black,
|
||||
},
|
||||
ejectText = UI.Text {
|
||||
x = 2, y = -2,
|
||||
value = 'Eject'
|
||||
},
|
||||
eject = UI.Checkbox {
|
||||
x = 8, y = -2,
|
||||
},
|
||||
automaticText = UI.Text {
|
||||
x = 12, y = -2,
|
||||
value = 'Copy automatically'
|
||||
},
|
||||
automatic = UI.Checkbox {
|
||||
x = 31, y = -2,
|
||||
},
|
||||
copyButton = UI.Button {
|
||||
x = -7, y = -2,
|
||||
text = 'Copy',
|
||||
event = 'copy',
|
||||
inactive = true,
|
||||
},
|
||||
warning = UI.Text {
|
||||
x = 2, ex = -2, y = -5,
|
||||
align = 'center',
|
||||
textColor = colors.orange,
|
||||
},
|
||||
notification = UI.Notification { },
|
||||
}
|
||||
|
||||
function page:enable()
|
||||
Util.merge(self.dir, directions[config.copyDir])
|
||||
Util.merge(self.dir, directions[config.copyDir])
|
||||
|
||||
self.eject.value = config.eject
|
||||
self.automatic.value = config.automatic
|
||||
self.eject.value = config.eject
|
||||
self.automatic.value = config.automatic
|
||||
|
||||
self.dir.x = math.floor((self.width / 2) - 3) + 1
|
||||
self.dir.x = math.floor((self.width / 2) - 3) + 1
|
||||
|
||||
UI.Page.enable(self)
|
||||
UI.Page.enable(self)
|
||||
end
|
||||
|
||||
local function isValid(drive)
|
||||
return drive.isDiskPresent() and drive.getMountPath()
|
||||
return drive.isDiskPresent() and drive.getMountPath()
|
||||
end
|
||||
|
||||
local function needsLabel(drive)
|
||||
return drive.isDiskPresent() and not drive.getMountPath() and not drive.getAudioTitle()
|
||||
return drive.isDiskPresent() and not drive.getMountPath() and not drive.getAudioTitle()
|
||||
end
|
||||
|
||||
function page:drawInfo(drive, textArea)
|
||||
local function getLabel()
|
||||
return not drive.isDiskPresent() and 'empty' or
|
||||
not drive.getMountPath() and 'invalid' or
|
||||
drive.getDiskLabel() or 'unlabeled'
|
||||
end
|
||||
local function getLabel()
|
||||
return not drive.isDiskPresent() and 'empty' or
|
||||
not drive.getMountPath() and 'invalid' or
|
||||
drive.getDiskLabel() or 'unlabeled'
|
||||
end
|
||||
|
||||
local function getUsed()
|
||||
return isValid(drive) and fs.getSize(drive.getMountPath(), true) or 0
|
||||
end
|
||||
local function getUsed()
|
||||
return isValid(drive) and fs.getSize(drive.getMountPath(), true) or 0
|
||||
end
|
||||
|
||||
local function getFree()
|
||||
return isValid(drive) and fs.getFreeSpace(drive.getMountPath()) or 0
|
||||
end
|
||||
local function getFree()
|
||||
return isValid(drive) and fs.getFreeSpace(drive.getMountPath()) or 0
|
||||
end
|
||||
|
||||
textArea:setCursorPos(1, 1)
|
||||
textArea:print(string.format('Drive: %s%s%s\nLabel: %s%s%s\nUsed: %s%s%s\nFree: %s%s%s',
|
||||
Ansi.yellow, drive.name, Ansi.reset,
|
||||
isValid(drive) and Ansi.yellow or Ansi.orange, getLabel():sub(1, 10), Ansi.reset,
|
||||
Ansi.yellow, Util.toBytes(getUsed()), Ansi.reset,
|
||||
Ansi.yellow, Util.toBytes(getFree()), Ansi.reset))
|
||||
textArea:setCursorPos(1, 1)
|
||||
textArea:print(string.format('Drive: %s%s%s\nLabel: %s%s%s\nUsed: %s%s%s\nFree: %s%s%s',
|
||||
Ansi.yellow, drive.name, Ansi.reset,
|
||||
isValid(drive) and Ansi.yellow or Ansi.orange, getLabel():sub(1, 10), Ansi.reset,
|
||||
Ansi.yellow, Util.toBytes(getUsed()), Ansi.reset,
|
||||
Ansi.yellow, Util.toBytes(getFree()), Ansi.reset))
|
||||
end
|
||||
|
||||
function page:scan()
|
||||
local showWarning = needsLabel(drives.left) or needsLabel(drives.right)
|
||||
local valid = isValid(drives.left) and isValid(drives.right)
|
||||
local showWarning = needsLabel(drives.left) or needsLabel(drives.right)
|
||||
local valid = isValid(drives.left) and isValid(drives.right)
|
||||
|
||||
self.warning.value = showWarning and 'Computers must be labeled'
|
||||
self.copyButton.inactive = not valid
|
||||
self.warning.value = showWarning and 'Computers must be labeled'
|
||||
self.copyButton.inactive = not valid
|
||||
|
||||
self:draw()
|
||||
self.progress:centeredWrite(1, 'Analyzing Disks..')
|
||||
self.progress:sync()
|
||||
self:draw()
|
||||
self.progress:centeredWrite(1, 'Analyzing Disks..')
|
||||
self.progress:sync()
|
||||
|
||||
self:drawInfo(drives.left, self.linfo)
|
||||
self:drawInfo(drives.right, self.rinfo)
|
||||
self:drawInfo(drives.left, self.linfo)
|
||||
self:drawInfo(drives.right, self.rinfo)
|
||||
|
||||
self.progress:clear()
|
||||
self.progress:clear()
|
||||
end
|
||||
|
||||
function page:copy()
|
||||
local sdrive = config.copyDir == COPY_LEFT and drives.left or drives.right
|
||||
local tdrive = config.copyDir == COPY_LEFT and drives.right or drives.left
|
||||
local sdrive = config.copyDir == COPY_LEFT and drives.left or drives.right
|
||||
local tdrive = config.copyDir == COPY_LEFT and drives.right or drives.left
|
||||
|
||||
local throttle = Util.throttle()
|
||||
local sourceFiles, targetFiles = { }, { }
|
||||
local throttle = Util.throttle()
|
||||
local sourceFiles, targetFiles = { }, { }
|
||||
|
||||
local function getListing(mountPath, path, files)
|
||||
for _,f in pairs(fs.list(path)) do
|
||||
local file = fs.combine(path, f)
|
||||
if not fs.isReadOnly(file) then
|
||||
files[string.sub(file, #mountPath + 1)] = true
|
||||
if fs.isDir(file) then
|
||||
getListing(mountPath, file, files)
|
||||
end
|
||||
end
|
||||
end
|
||||
throttle()
|
||||
end
|
||||
local function getListing(mountPath, path, files)
|
||||
for _,f in pairs(fs.list(path)) do
|
||||
local file = fs.combine(path, f)
|
||||
if not fs.isReadOnly(file) then
|
||||
files[string.sub(file, #mountPath + 1)] = true
|
||||
if fs.isDir(file) then
|
||||
getListing(mountPath, file, files)
|
||||
end
|
||||
end
|
||||
end
|
||||
throttle()
|
||||
end
|
||||
|
||||
self.progress:centeredWrite(1, 'Computing..')
|
||||
self.progress:sync()
|
||||
self.progress:centeredWrite(1, 'Computing..')
|
||||
self.progress:sync()
|
||||
|
||||
getListing(sdrive.getMountPath(), sdrive.getMountPath(), sourceFiles)
|
||||
getListing(tdrive.getMountPath(), tdrive.getMountPath(), targetFiles)
|
||||
getListing(sdrive.getMountPath(), sdrive.getMountPath(), sourceFiles)
|
||||
getListing(tdrive.getMountPath(), tdrive.getMountPath(), targetFiles)
|
||||
|
||||
local copied = 0
|
||||
local totalFiles = Util.size(sourceFiles)
|
||||
local copied = 0
|
||||
local totalFiles = Util.size(sourceFiles)
|
||||
|
||||
local function rawCopy(source, target)
|
||||
if fs.isDir(source) then
|
||||
copied = copied + 1
|
||||
if not fs.exists(target) then
|
||||
fs.makeDir(target)
|
||||
end
|
||||
for _,f in pairs(fs.list(source)) do
|
||||
rawCopy(fs.combine(source, f), fs.combine(target, f))
|
||||
end
|
||||
local function rawCopy(source, target)
|
||||
if fs.isDir(source) then
|
||||
copied = copied + 1
|
||||
if not fs.exists(target) then
|
||||
fs.makeDir(target)
|
||||
end
|
||||
for _,f in pairs(fs.list(source)) do
|
||||
rawCopy(fs.combine(source, f), fs.combine(target, f))
|
||||
end
|
||||
|
||||
else
|
||||
if fs.exists(target) then
|
||||
fs.delete(target)
|
||||
end
|
||||
else
|
||||
if fs.exists(target) then
|
||||
fs.delete(target)
|
||||
end
|
||||
|
||||
fs.copy(source, target)
|
||||
copied = copied + 1
|
||||
self.progress.value = copied * 100 / totalFiles
|
||||
self.progress:draw()
|
||||
self.progress:sync()
|
||||
end
|
||||
throttle()
|
||||
end
|
||||
fs.copy(source, target)
|
||||
copied = copied + 1
|
||||
self.progress.value = copied * 100 / totalFiles
|
||||
self.progress:draw()
|
||||
self.progress:sync()
|
||||
end
|
||||
throttle()
|
||||
end
|
||||
|
||||
local function cleanup()
|
||||
for k in pairs(targetFiles) do
|
||||
if not sourceFiles[k] then
|
||||
fs.delete(fs.combine(tdrive.getMountPath(), k))
|
||||
end
|
||||
end
|
||||
end
|
||||
local function cleanup()
|
||||
for k in pairs(targetFiles) do
|
||||
if not sourceFiles[k] then
|
||||
fs.delete(fs.combine(tdrive.getMountPath(), k))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.progress:clear()
|
||||
rawCopy(sdrive.getMountPath(), tdrive.getMountPath())
|
||||
cleanup()
|
||||
self.progress:centeredWrite(1, 'Copy Complete', colors.lime, colors.black)
|
||||
self.progress:sync()
|
||||
self.progress:clear()
|
||||
rawCopy(sdrive.getMountPath(), tdrive.getMountPath())
|
||||
cleanup()
|
||||
self.progress:centeredWrite(1, 'Copy Complete', colors.lime, colors.black)
|
||||
self.progress:sync()
|
||||
|
||||
self.progress.value = 0
|
||||
self.progress:clear()
|
||||
self.progress.value = 0
|
||||
self.progress:clear()
|
||||
|
||||
self:scan()
|
||||
self:scan()
|
||||
|
||||
if config.eject then
|
||||
tdrive.ejectDisk()
|
||||
end
|
||||
if config.eject then
|
||||
tdrive.ejectDisk()
|
||||
end
|
||||
end
|
||||
|
||||
function page:eventHandler(event)
|
||||
if event.type == 'change_dir' then
|
||||
config.copyDir = (config.copyDir) % 2 + 1
|
||||
Util.merge(self.dir, directions[config.copyDir])
|
||||
Config.update('DiskCopy', config)
|
||||
self.dir:draw()
|
||||
if event.type == 'change_dir' then
|
||||
config.copyDir = (config.copyDir) % 2 + 1
|
||||
Util.merge(self.dir, directions[config.copyDir])
|
||||
Config.update('DiskCopy', config)
|
||||
self.dir:draw()
|
||||
|
||||
elseif event.type == 'copy' then
|
||||
self:copy()
|
||||
elseif event.type == 'copy' then
|
||||
self:copy()
|
||||
|
||||
elseif event.type == 'checkbox_change' then
|
||||
if event.element == self.eject then
|
||||
config.eject = not not event.checked
|
||||
elseif event.element == self.automatic then
|
||||
config.automatic = not not event.checked
|
||||
end
|
||||
elseif event.type == 'checkbox_change' then
|
||||
if event.element == self.eject then
|
||||
config.eject = not not event.checked
|
||||
elseif event.element == self.automatic then
|
||||
config.automatic = not not event.checked
|
||||
end
|
||||
|
||||
Config.update('DiskCopy', config)
|
||||
event.element:draw()
|
||||
Config.update('DiskCopy', config)
|
||||
event.element:draw()
|
||||
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
@@ -242,22 +242,22 @@ function page:eventHandler(event)
|
||||
end
|
||||
|
||||
Event.on("disk", function()
|
||||
page:scan()
|
||||
page:sync()
|
||||
page:scan()
|
||||
page:sync()
|
||||
|
||||
if config.automatic and not page.copyButton.inactive then
|
||||
page:copy()
|
||||
end
|
||||
if config.automatic and not page.copyButton.inactive then
|
||||
page:copy()
|
||||
end
|
||||
end)
|
||||
|
||||
Event.on("disk_eject", function()
|
||||
page:scan()
|
||||
page:sync()
|
||||
page:scan()
|
||||
page:sync()
|
||||
end)
|
||||
|
||||
Event.onTimeout(.2, function()
|
||||
page:scan()
|
||||
page:sync()
|
||||
page:scan()
|
||||
page:sync()
|
||||
end)
|
||||
|
||||
UI:setPage(page)
|
||||
|
||||
@@ -8,131 +8,131 @@ local os = _G.os
|
||||
UI:configure('Events', ...)
|
||||
|
||||
local page = UI.Page {
|
||||
menuBar = UI.MenuBar {
|
||||
buttons = {
|
||||
{ text = 'Filter', event = 'filter' },
|
||||
{ text = 'Reset', event = 'reset' },
|
||||
{ text = 'Pause ', event = 'toggle', name = 'pauseButton' },
|
||||
},
|
||||
},
|
||||
grid = UI.Grid {
|
||||
y = 2,
|
||||
columns = {
|
||||
{ key = 'event' },
|
||||
{ key = 'p1' },
|
||||
{ key = 'p2' },
|
||||
{ key = 'p3' },
|
||||
{ key = 'p4' },
|
||||
{ key = 'p5' },
|
||||
},
|
||||
autospace = true,
|
||||
disableHeader = true,
|
||||
},
|
||||
accelerators = {
|
||||
f = 'filter',
|
||||
p = 'toggle',
|
||||
r = 'reset',
|
||||
c = 'clear',
|
||||
q = 'quit',
|
||||
},
|
||||
filtered = { },
|
||||
menuBar = UI.MenuBar {
|
||||
buttons = {
|
||||
{ text = 'Filter', event = 'filter' },
|
||||
{ text = 'Reset', event = 'reset' },
|
||||
{ text = 'Pause ', event = 'toggle', name = 'pauseButton' },
|
||||
},
|
||||
},
|
||||
grid = UI.Grid {
|
||||
y = 2,
|
||||
columns = {
|
||||
{ key = 'event' },
|
||||
{ key = 'p1' },
|
||||
{ key = 'p2' },
|
||||
{ key = 'p3' },
|
||||
{ key = 'p4' },
|
||||
{ key = 'p5' },
|
||||
},
|
||||
autospace = true,
|
||||
disableHeader = true,
|
||||
},
|
||||
accelerators = {
|
||||
f = 'filter',
|
||||
p = 'toggle',
|
||||
r = 'reset',
|
||||
c = 'clear',
|
||||
q = 'quit',
|
||||
},
|
||||
filtered = { },
|
||||
}
|
||||
|
||||
function page:eventHandler(event)
|
||||
|
||||
if event.type == 'filter' then
|
||||
local entry = self.grid:getSelected()
|
||||
self.filtered[entry.event] = true
|
||||
if event.type == 'filter' then
|
||||
local entry = self.grid:getSelected()
|
||||
self.filtered[entry.event] = true
|
||||
|
||||
elseif event.type == 'toggle' then
|
||||
self.paused = not self.paused
|
||||
if self.paused then
|
||||
self.menuBar.pauseButton.text = 'Resume'
|
||||
else
|
||||
self.menuBar.pauseButton.text = 'Pause '
|
||||
end
|
||||
self.menuBar:draw()
|
||||
elseif event.type == 'toggle' then
|
||||
self.paused = not self.paused
|
||||
if self.paused then
|
||||
self.menuBar.pauseButton.text = 'Resume'
|
||||
else
|
||||
self.menuBar.pauseButton.text = 'Pause '
|
||||
end
|
||||
self.menuBar:draw()
|
||||
|
||||
elseif event.type == 'grid_select' then
|
||||
multishell.openTab({
|
||||
path = 'sys/apps/Lua.lua',
|
||||
args = { event.selected },
|
||||
focused = true,
|
||||
})
|
||||
elseif event.type == 'grid_select' then
|
||||
multishell.openTab({
|
||||
path = 'sys/apps/Lua.lua',
|
||||
args = { event.selected },
|
||||
focused = true,
|
||||
})
|
||||
|
||||
elseif event.type == 'reset' then
|
||||
self.filtered = { }
|
||||
self.grid:setValues({ })
|
||||
self.grid:draw()
|
||||
if self.paused then
|
||||
self:emit({ type = 'toggle' })
|
||||
end
|
||||
elseif event.type == 'reset' then
|
||||
self.filtered = { }
|
||||
self.grid:setValues({ })
|
||||
self.grid:draw()
|
||||
if self.paused then
|
||||
self:emit({ type = 'toggle' })
|
||||
end
|
||||
|
||||
elseif event.type == 'clear' then
|
||||
self.grid:setValues({ })
|
||||
self.grid:draw()
|
||||
elseif event.type == 'clear' then
|
||||
self.grid:setValues({ })
|
||||
self.grid:draw()
|
||||
|
||||
elseif event.type == 'quit' then
|
||||
UI:exitPullEvents()
|
||||
elseif event.type == 'quit' then
|
||||
UI:exitPullEvents()
|
||||
|
||||
--[[
|
||||
elseif event.type == 'focus_change' then
|
||||
if event.focused == self.grid then
|
||||
if not self.paused then
|
||||
self:emit({ type = 'toggle' })
|
||||
end
|
||||
end
|
||||
--]]
|
||||
--[[
|
||||
elseif event.type == 'focus_change' then
|
||||
if event.focused == self.grid then
|
||||
if not self.paused then
|
||||
self:emit({ type = 'toggle' })
|
||||
end
|
||||
end
|
||||
--]]
|
||||
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function page.grid:getDisplayValues(row)
|
||||
row = Util.shallowCopy(row)
|
||||
row = Util.shallowCopy(row)
|
||||
|
||||
local function tovalue(s)
|
||||
if type(s) == 'table' then
|
||||
return 'table'
|
||||
end
|
||||
return s
|
||||
end
|
||||
local function tovalue(s)
|
||||
if type(s) == 'table' then
|
||||
return 'table'
|
||||
end
|
||||
return s
|
||||
end
|
||||
|
||||
for k,v in pairs(row) do
|
||||
row[k] = tovalue(v)
|
||||
end
|
||||
for k,v in pairs(row) do
|
||||
row[k] = tovalue(v)
|
||||
end
|
||||
|
||||
return row
|
||||
return row
|
||||
end
|
||||
|
||||
function page.grid:draw()
|
||||
self:adjustWidth()
|
||||
UI.Grid.draw(self)
|
||||
self:adjustWidth()
|
||||
UI.Grid.draw(self)
|
||||
end
|
||||
|
||||
Event.addRoutine(function()
|
||||
|
||||
while true do
|
||||
local e = { os.pullEvent() }
|
||||
if not page.paused and not page.filtered[e[1]] then
|
||||
table.insert(page.grid.values, 1, {
|
||||
event = e[1],
|
||||
p1 = e[2],
|
||||
p2 = e[3],
|
||||
p3 = e[4],
|
||||
p4 = e[5],
|
||||
p5 = e[6],
|
||||
})
|
||||
if #page.grid.values > page.grid.height then
|
||||
table.remove(page.grid.values, #page.grid.values)
|
||||
end
|
||||
page.grid:update()
|
||||
page.grid:draw()
|
||||
page:sync()
|
||||
end
|
||||
end
|
||||
while true do
|
||||
local e = { os.pullEvent() }
|
||||
if not page.paused and not page.filtered[e[1]] then
|
||||
table.insert(page.grid.values, 1, {
|
||||
event = e[1],
|
||||
p1 = e[2],
|
||||
p2 = e[3],
|
||||
p3 = e[4],
|
||||
p4 = e[5],
|
||||
p5 = e[6],
|
||||
})
|
||||
if #page.grid.values > page.grid.height then
|
||||
table.remove(page.grid.values, #page.grid.values)
|
||||
end
|
||||
page.grid:update()
|
||||
page.grid:draw()
|
||||
page:sync()
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
UI:setPage(page)
|
||||
|
||||
@@ -15,32 +15,64 @@ local gpt = GPS.getPoint() or error('GPS not found')
|
||||
local pts, blocks
|
||||
|
||||
local page = UI.Page {
|
||||
mode = UI.Chooser {
|
||||
x = 13, y = -1,
|
||||
choices = {
|
||||
{ name = 'No breaking', value = 'digNone' },
|
||||
{ name = 'Destructive', value = 'turtleSafe' },
|
||||
},
|
||||
value = 'digNone',
|
||||
},
|
||||
grid = UI.ScrollingGrid {
|
||||
y = 2, ey = -2,
|
||||
columns = {
|
||||
{ heading = 'Label', key = 'label' },
|
||||
{ heading = 'Dist', key = 'distance' },
|
||||
{ heading = 'Status', key = 'status' },
|
||||
{ heading = 'Fuel', key = 'fuel' },
|
||||
},
|
||||
sortColumn = 'distance',
|
||||
autospace = true,
|
||||
},
|
||||
menuBar = UI.MenuBar {
|
||||
buttons = {
|
||||
{ text = 'Range', event = 'range' },
|
||||
{ text = 'Stop', event = 'stop' },
|
||||
},
|
||||
mode = UI.Chooser {
|
||||
x = -16,
|
||||
choices = {
|
||||
{ name = 'No breaking', value = 'digNone' },
|
||||
{ name = 'Destructive', value = 'turtleSafe' },
|
||||
},
|
||||
value = 'digNone',
|
||||
},
|
||||
},
|
||||
grid = UI.ScrollingGrid {
|
||||
y = 2, ey = -2,
|
||||
columns = {
|
||||
{ heading = 'Label', key = 'label' },
|
||||
{ heading = 'Dist', key = 'distance' },
|
||||
{ heading = 'Status', key = 'status' },
|
||||
{ heading = 'Fuel', key = 'fuel' },
|
||||
},
|
||||
sortColumn = 'distance',
|
||||
autospace = true,
|
||||
},
|
||||
range = UI.SlideOut {
|
||||
y = -7, height = 7,
|
||||
backgroundColor = colors.cyan,
|
||||
titleBar = UI.TitleBar {
|
||||
event = 'cancel',
|
||||
title = 'Enter range',
|
||||
},
|
||||
notice = UI.TextArea {
|
||||
x = 2, ex = -2, y = 3, ey = 4,
|
||||
value =
|
||||
[[Select all turtles within a specified range]],
|
||||
},
|
||||
entry = UI.TextEntry {
|
||||
y = 6, x = 2, ex = 10,
|
||||
limit = 4,
|
||||
shadowText = 'range',
|
||||
accelerators = {
|
||||
enter = 'select_range',
|
||||
},
|
||||
},
|
||||
button = UI.Button {
|
||||
x = 12, y = 6,
|
||||
text = 'Apply',
|
||||
event = 'select_range',
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
function page.grid:getRowTextColor(row, selected)
|
||||
if swarm.pool[row.id] then
|
||||
return colors.yellow
|
||||
end
|
||||
return UI.ScrollingGrid.getRowTextColor(self, row, selected)
|
||||
if swarm.pool[row.id] then
|
||||
return colors.yellow
|
||||
end
|
||||
return UI.ScrollingGrid.getRowTextColor(self, row, selected)
|
||||
end
|
||||
|
||||
function page.grid:getDisplayValues(row)
|
||||
@@ -55,130 +87,155 @@ function page.grid:getDisplayValues(row)
|
||||
end
|
||||
|
||||
function page:enable()
|
||||
local function update()
|
||||
local t = { }
|
||||
for _,v in pairs(network) do
|
||||
if v.fuel and v.active and v.fuel > 0 and v.distance then
|
||||
table.insert(t, v)
|
||||
end
|
||||
end
|
||||
self.grid:setValues(t)
|
||||
end
|
||||
local function update()
|
||||
local t = { }
|
||||
for _,v in pairs(network) do
|
||||
if v.fuel and v.active and v.fuel > 0 and v.distance then
|
||||
table.insert(t, v)
|
||||
end
|
||||
end
|
||||
self.grid:setValues(t)
|
||||
end
|
||||
|
||||
Event.onInterval(3, function()
|
||||
update()
|
||||
self.grid:draw()
|
||||
self:sync()
|
||||
end)
|
||||
Event.onInterval(3, function()
|
||||
update()
|
||||
self.grid:draw()
|
||||
self:sync()
|
||||
end)
|
||||
|
||||
update()
|
||||
update()
|
||||
|
||||
UI.Page.enable(self)
|
||||
UI.Page.enable(self)
|
||||
end
|
||||
|
||||
local function follow(member)
|
||||
local turtle = member.turtle
|
||||
turtle.reset()
|
||||
turtle.set({
|
||||
digPolicy = page.mode.value,
|
||||
status = 'Following',
|
||||
})
|
||||
local turtle = member.turtle
|
||||
turtle.reset()
|
||||
turtle.set({
|
||||
digPolicy = page.menuBar.mode.value,
|
||||
status = 'Following',
|
||||
})
|
||||
|
||||
if not turtle.enableGPS(nil, true) then
|
||||
error('turtle: No GPS found')
|
||||
end
|
||||
if not turtle.enableGPS(nil, true) then
|
||||
error('turtle: No GPS found')
|
||||
end
|
||||
|
||||
member.snmp = Socket.connect(member.id, 161)
|
||||
member.snmp.co = coroutine.running()
|
||||
member.snmp = Socket.connect(member.id, 161)
|
||||
member.snmp.co = coroutine.running()
|
||||
|
||||
local pt
|
||||
local pt
|
||||
|
||||
while true do
|
||||
while pt and Point.same(gpt, pt) do
|
||||
os.sleep(.5)
|
||||
end
|
||||
pt = Point.copy(gpt)
|
||||
while true do
|
||||
while pt and Point.same(gpt, pt) do
|
||||
os.sleep(.5)
|
||||
end
|
||||
pt = Point.copy(gpt)
|
||||
|
||||
local cpt = Point.closest(turtle.getPoint(), pts)
|
||||
local cpt = Point.closest(turtle.getPoint(), pts)
|
||||
|
||||
turtle.abort(false)
|
||||
if turtle.pathfind(cpt, { blocks = blocks }) then
|
||||
turtle.headTowards(pt)
|
||||
end
|
||||
end
|
||||
turtle.abort(false)
|
||||
if turtle.pathfind(cpt, { blocks = blocks }) then
|
||||
turtle.headTowards(pt)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function swarm:onRemove(member, status, message)
|
||||
if member.socket then
|
||||
pcall(function()
|
||||
member.turtle.set({ status = 'idle' })
|
||||
member.turtle.abort(true)
|
||||
end)
|
||||
end
|
||||
if member.snmp then
|
||||
member.snmp:close()
|
||||
member.snmp = nil
|
||||
end
|
||||
if not status then
|
||||
_G._syslog(message)
|
||||
end
|
||||
if member.socket then
|
||||
pcall(function()
|
||||
member.turtle.set({ status = 'idle' })
|
||||
member.turtle.abort(true)
|
||||
end)
|
||||
end
|
||||
if member.snmp then
|
||||
member.snmp:close()
|
||||
member.snmp = nil
|
||||
end
|
||||
if not status then
|
||||
_G._syslog(message)
|
||||
end
|
||||
end
|
||||
|
||||
function page:eventHandler(event)
|
||||
if event.type == 'grid_select' then
|
||||
if not swarm.pool[event.selected.id] then
|
||||
swarm:add(event.selected.id)
|
||||
swarm:run(follow)
|
||||
else
|
||||
swarm:remove(event.selected.id)
|
||||
end
|
||||
self.grid:draw()
|
||||
if event.type == 'grid_select' then
|
||||
if not swarm.pool[event.selected.id] then
|
||||
swarm:add(event.selected.id)
|
||||
swarm:run(follow)
|
||||
else
|
||||
swarm:remove(event.selected.id)
|
||||
end
|
||||
self.grid:draw()
|
||||
|
||||
elseif event.type == 'choice_change' then
|
||||
local script = string.format('turtle.set({ digPolicy = "%s"})', event.value)
|
||||
for _, member in pairs(swarm.pool) do
|
||||
member.snmp:write({ type = 'scriptEx', args = script })
|
||||
end
|
||||
elseif event.type == 'choice_change' then
|
||||
local script = string.format('turtle.set({ digPolicy = "%s"})', event.value)
|
||||
for _, member in pairs(swarm.pool) do
|
||||
member.snmp:write({ type = 'scriptEx', args = script })
|
||||
end
|
||||
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
elseif event.type == 'stop' then
|
||||
for id in pairs(swarm.pool) do
|
||||
swarm:remove(id)
|
||||
end
|
||||
|
||||
elseif event.type == 'range' then
|
||||
self.range:show()
|
||||
|
||||
elseif event.type == 'cancel' then
|
||||
self.range:hide()
|
||||
|
||||
elseif event.type == 'select_range' then
|
||||
local range = tonumber(self.range.entry.value)
|
||||
|
||||
if range and range > 0 then
|
||||
for id, v in pairs(network) do
|
||||
if not swarm.pool[id] then
|
||||
if v.fuel and v.active and v.fuel > 0 and v.distance and v.distance <= range then
|
||||
swarm:add(id)
|
||||
end
|
||||
end
|
||||
end
|
||||
swarm:run(follow)
|
||||
self.range:hide()
|
||||
end
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
Event.addRoutine(function()
|
||||
while true do
|
||||
local pt = GPS.getPoint()
|
||||
if not pts or (pt and not Point.same(pt, gpt)) then
|
||||
gpt = pt
|
||||
pts = {
|
||||
{ x = pt.x + 2, z = pt.z, y = pt.y },
|
||||
{ x = pt.x - 2, z = pt.z, y = pt.y },
|
||||
{ x = pt.x, z = pt.z + 2, y = pt.y },
|
||||
{ x = pt.x, z = pt.z - 2, y = pt.y },
|
||||
}
|
||||
blocks = { }
|
||||
while true do
|
||||
local pt = GPS.getPoint()
|
||||
if not pts or (pt and not Point.same(pt, gpt)) then
|
||||
gpt = pt
|
||||
pts = {
|
||||
{ x = pt.x + 2, z = pt.z, y = pt.y },
|
||||
{ x = pt.x - 2, z = pt.z, y = pt.y },
|
||||
{ x = pt.x, z = pt.z + 2, y = pt.y },
|
||||
{ x = pt.x, z = pt.z - 2, y = pt.y },
|
||||
}
|
||||
blocks = { }
|
||||
|
||||
local function addBlocks(tpt)
|
||||
table.insert(blocks, tpt)
|
||||
local apts = Point.adjacentPoints(tpt)
|
||||
for _,apt in pairs(apts) do
|
||||
table.insert(blocks, apt)
|
||||
end
|
||||
end
|
||||
local function addBlocks(tpt)
|
||||
table.insert(blocks, tpt)
|
||||
local apts = Point.adjacentPoints(tpt)
|
||||
for _,apt in pairs(apts) do
|
||||
table.insert(blocks, apt)
|
||||
end
|
||||
end
|
||||
|
||||
-- don't run into player
|
||||
addBlocks(pt)
|
||||
addBlocks(Point.above(pt))
|
||||
-- don't run into player
|
||||
addBlocks(pt)
|
||||
addBlocks(Point.above(pt))
|
||||
|
||||
for _, member in pairs(swarm.pool) do
|
||||
if member.snmp then
|
||||
member.snmp:write({ type = 'scriptEx', args = 'turtle.abort(true)' })
|
||||
end
|
||||
end
|
||||
end
|
||||
os.sleep(1)
|
||||
end
|
||||
for _, member in pairs(swarm.pool) do
|
||||
if member.snmp then
|
||||
member.snmp:write({ type = 'scriptEx', args = 'turtle.abort(true)' })
|
||||
end
|
||||
end
|
||||
end
|
||||
os.sleep(1)
|
||||
end
|
||||
end)
|
||||
|
||||
UI:setPage(page)
|
||||
|
||||
@@ -21,12 +21,12 @@ local config = { }
|
||||
Config.load('Turtles', config)
|
||||
|
||||
local options = {
|
||||
turtle = { arg = 'i', type = 'number', value = config.id or -1,
|
||||
desc = 'Turtle ID' },
|
||||
tab = { arg = 's', type = 'string', value = config.tab or 'Sel',
|
||||
desc = 'Selected tab to display' },
|
||||
help = { arg = 'h', type = 'flag', value = false,
|
||||
desc = 'Displays the options' },
|
||||
turtle = { arg = 'i', type = 'number', value = config.id or -1,
|
||||
desc = 'Turtle ID' },
|
||||
tab = { arg = 's', type = 'string', value = config.tab or 'Sel',
|
||||
desc = 'Selected tab to display' },
|
||||
help = { arg = 'h', type = 'flag', value = false,
|
||||
desc = 'Displays the options' },
|
||||
}
|
||||
|
||||
local SCRIPTS_PATH = 'packages/common/etc/scripts'
|
||||
@@ -35,343 +35,343 @@ local nullTerm = Terminal.getNullTerm(term.current())
|
||||
local socket
|
||||
|
||||
local page = UI.Page {
|
||||
coords = UI.Window {
|
||||
backgroundColor = colors.black,
|
||||
height = 3,
|
||||
},
|
||||
tabs = UI.Tabs {
|
||||
x = 1, y = 4, ey = -2,
|
||||
scripts = UI.ScrollingGrid {
|
||||
tabTitle = 'Run',
|
||||
backgroundColor = colors.cyan,
|
||||
columns = {
|
||||
{ heading = '', key = 'label' },
|
||||
},
|
||||
disableHeader = true,
|
||||
sortColumn = 'label',
|
||||
autospace = true,
|
||||
},
|
||||
turtles = UI.ScrollingGrid {
|
||||
tabTitle = 'Select',
|
||||
backgroundColor = colors.cyan,
|
||||
columns = {
|
||||
{ heading = 'label', key = 'label' },
|
||||
{ heading = 'Dist', key = 'distance' },
|
||||
{ heading = 'Status', key = 'status' },
|
||||
{ heading = 'Fuel', key = 'fuel' },
|
||||
},
|
||||
disableHeader = true,
|
||||
sortColumn = 'label',
|
||||
autospace = true,
|
||||
},
|
||||
inventory = UI.ScrollingGrid {
|
||||
backgroundColor = colors.cyan,
|
||||
tabTitle = 'Inv',
|
||||
columns = {
|
||||
{ heading = '', key = 'index', width = 2 },
|
||||
{ heading = '', key = 'qty', width = 2 },
|
||||
{ heading = 'Inventory', key = 'id', width = UI.term.width - 7 },
|
||||
},
|
||||
disableHeader = true,
|
||||
sortColumn = 'index',
|
||||
},
|
||||
coords = UI.Window {
|
||||
backgroundColor = colors.black,
|
||||
height = 3,
|
||||
},
|
||||
tabs = UI.Tabs {
|
||||
x = 1, y = 4, ey = -2,
|
||||
scripts = UI.ScrollingGrid {
|
||||
tabTitle = 'Run',
|
||||
backgroundColor = colors.cyan,
|
||||
columns = {
|
||||
{ heading = '', key = 'label' },
|
||||
},
|
||||
disableHeader = true,
|
||||
sortColumn = 'label',
|
||||
autospace = true,
|
||||
},
|
||||
turtles = UI.ScrollingGrid {
|
||||
tabTitle = 'Select',
|
||||
backgroundColor = colors.cyan,
|
||||
columns = {
|
||||
{ heading = 'label', key = 'label' },
|
||||
{ heading = 'Dist', key = 'distance' },
|
||||
{ heading = 'Status', key = 'status' },
|
||||
{ heading = 'Fuel', key = 'fuel' },
|
||||
},
|
||||
disableHeader = true,
|
||||
sortColumn = 'label',
|
||||
autospace = true,
|
||||
},
|
||||
inventory = UI.ScrollingGrid {
|
||||
backgroundColor = colors.cyan,
|
||||
tabTitle = 'Inv',
|
||||
columns = {
|
||||
{ heading = '', key = 'index', width = 2 },
|
||||
{ heading = '', key = 'qty', width = 2 },
|
||||
{ heading = 'Inventory', key = 'id', width = UI.term.width - 7 },
|
||||
},
|
||||
disableHeader = true,
|
||||
sortColumn = 'index',
|
||||
},
|
||||
--[[
|
||||
policy = UI.ScrollingGrid {
|
||||
tabTitle = 'Mod',
|
||||
backgroundColor = UI.TabBar.defaults.selectedBackgroundColor,
|
||||
columns = {
|
||||
{ heading = 'label', key = 'label' },
|
||||
},
|
||||
values = policies,
|
||||
disableHeader = true,
|
||||
sortColumn = 'label',
|
||||
autospace = true,
|
||||
},
|
||||
]]
|
||||
action = UI.Window {
|
||||
tabTitle = 'Action',
|
||||
backgroundColor = colors.cyan,
|
||||
moveUp = UI.Button {
|
||||
x = 5, y = 2,
|
||||
text = 'up',
|
||||
fn = 'turtle.up',
|
||||
},
|
||||
moveDown = UI.Button {
|
||||
x = 5, y = 4,
|
||||
text = 'dn',
|
||||
fn = 'turtle.down',
|
||||
},
|
||||
moveForward = UI.Button {
|
||||
x = 9, y = 3,
|
||||
text = 'f',
|
||||
fn = 'turtle.forward',
|
||||
},
|
||||
moveBack = UI.Button {
|
||||
x = 2, y = 3,
|
||||
text = 'b',
|
||||
fn = 'turtle.back',
|
||||
},
|
||||
turnLeft = UI.Button {
|
||||
x = 2, y = 6,
|
||||
text = 'lt',
|
||||
fn = 'turtle.turnLeft',
|
||||
},
|
||||
turnRight = UI.Button {
|
||||
x = 8, y = 6,
|
||||
text = 'rt',
|
||||
fn = 'turtle.turnRight',
|
||||
},
|
||||
info = UI.TextArea {
|
||||
x = 15, y = 2,
|
||||
inactive = true,
|
||||
}
|
||||
},
|
||||
},
|
||||
statusBar = UI.StatusBar {
|
||||
values = { },
|
||||
columns = {
|
||||
{ key = 'status' },
|
||||
{ key = 'distance', width = 6 },
|
||||
{ key = 'fuel', width = 6 },
|
||||
},
|
||||
},
|
||||
notification = UI.Notification(),
|
||||
accelerators = {
|
||||
q = 'quit',
|
||||
},
|
||||
policy = UI.ScrollingGrid {
|
||||
tabTitle = 'Mod',
|
||||
backgroundColor = UI.TabBar.defaults.selectedBackgroundColor,
|
||||
columns = {
|
||||
{ heading = 'label', key = 'label' },
|
||||
},
|
||||
values = policies,
|
||||
disableHeader = true,
|
||||
sortColumn = 'label',
|
||||
autospace = true,
|
||||
},
|
||||
]]
|
||||
action = UI.Window {
|
||||
tabTitle = 'Action',
|
||||
backgroundColor = colors.cyan,
|
||||
moveUp = UI.Button {
|
||||
x = 5, y = 2,
|
||||
text = 'up',
|
||||
fn = 'turtle.up',
|
||||
},
|
||||
moveDown = UI.Button {
|
||||
x = 5, y = 4,
|
||||
text = 'dn',
|
||||
fn = 'turtle.down',
|
||||
},
|
||||
moveForward = UI.Button {
|
||||
x = 9, y = 3,
|
||||
text = 'f',
|
||||
fn = 'turtle.forward',
|
||||
},
|
||||
moveBack = UI.Button {
|
||||
x = 2, y = 3,
|
||||
text = 'b',
|
||||
fn = 'turtle.back',
|
||||
},
|
||||
turnLeft = UI.Button {
|
||||
x = 2, y = 6,
|
||||
text = 'lt',
|
||||
fn = 'turtle.turnLeft',
|
||||
},
|
||||
turnRight = UI.Button {
|
||||
x = 8, y = 6,
|
||||
text = 'rt',
|
||||
fn = 'turtle.turnRight',
|
||||
},
|
||||
info = UI.TextArea {
|
||||
x = 15, y = 2,
|
||||
inactive = true,
|
||||
}
|
||||
},
|
||||
},
|
||||
statusBar = UI.StatusBar {
|
||||
values = { },
|
||||
columns = {
|
||||
{ key = 'status' },
|
||||
{ key = 'distance', width = 6 },
|
||||
{ key = 'fuel', width = 6 },
|
||||
},
|
||||
},
|
||||
notification = UI.Notification(),
|
||||
accelerators = {
|
||||
q = 'quit',
|
||||
},
|
||||
}
|
||||
|
||||
function page:enable(turtle)
|
||||
self.turtle = turtle
|
||||
UI.Page.enable(self)
|
||||
self.turtle = turtle
|
||||
UI.Page.enable(self)
|
||||
end
|
||||
|
||||
function page:runFunction(script, nowrap)
|
||||
for _ = 1, 2 do
|
||||
if not socket then
|
||||
socket = Socket.connect(self.turtle.id, 161)
|
||||
end
|
||||
for _ = 1, 2 do
|
||||
if not socket then
|
||||
socket = Socket.connect(self.turtle.id, 161)
|
||||
end
|
||||
|
||||
if socket then
|
||||
if not nowrap then
|
||||
script = 'turtle.run(' .. script .. ')'
|
||||
end
|
||||
if socket:write({ type = 'scriptEx', args = script }) then
|
||||
local t = socket:read(3)
|
||||
if t then
|
||||
return table.unpack(t)
|
||||
end
|
||||
return false, 'Socket timeout'
|
||||
end
|
||||
end
|
||||
socket = nil
|
||||
end
|
||||
self.notification:error('Unable to connect')
|
||||
if socket then
|
||||
if not nowrap then
|
||||
script = 'turtle.run(' .. script .. ')'
|
||||
end
|
||||
if socket:write({ type = 'scriptEx', args = script }) then
|
||||
local t = socket:read(3)
|
||||
if t then
|
||||
return table.unpack(t)
|
||||
end
|
||||
return false, 'Socket timeout'
|
||||
end
|
||||
end
|
||||
socket = nil
|
||||
end
|
||||
self.notification:error('Unable to connect')
|
||||
end
|
||||
|
||||
function page:runScript(scriptName)
|
||||
if self.turtle then
|
||||
self.notification:info('Connecting')
|
||||
self:sync()
|
||||
if self.turtle then
|
||||
self.notification:info('Connecting')
|
||||
self:sync()
|
||||
|
||||
local cmd = string.format('Script %d %s', self.turtle.id, scriptName)
|
||||
local ot = term.redirect(nullTerm)
|
||||
pcall(function() shell.run(cmd) end)
|
||||
term.redirect(ot)
|
||||
self.notification:success('Sent')
|
||||
end
|
||||
local cmd = string.format('Script %d %s', self.turtle.id, scriptName)
|
||||
local ot = term.redirect(nullTerm)
|
||||
pcall(function() shell.run(cmd) end)
|
||||
term.redirect(ot)
|
||||
self.notification:success('Sent')
|
||||
end
|
||||
end
|
||||
|
||||
function page.coords:draw()
|
||||
local t = self.parent.turtle
|
||||
self:clear()
|
||||
if t then
|
||||
self:setCursorPos(2, 2)
|
||||
local ind = 'GPS'
|
||||
if not t.point.gps then
|
||||
ind = 'REL'
|
||||
end
|
||||
self:print(string.format('%s : %d,%d,%d',
|
||||
ind, t.point.x, t.point.y, t.point.z))
|
||||
end
|
||||
local t = self.parent.turtle
|
||||
self:clear()
|
||||
if t then
|
||||
self:setCursorPos(2, 2)
|
||||
local ind = 'GPS'
|
||||
if not t.point.gps then
|
||||
ind = 'REL'
|
||||
end
|
||||
self:print(string.format('%s : %d,%d,%d',
|
||||
ind, t.point.x, t.point.y, t.point.z))
|
||||
end
|
||||
end
|
||||
|
||||
--[[ Inventory Tab ]]--
|
||||
function page.tabs.inventory:getRowTextColor(row, selected)
|
||||
if page.turtle and row.selected then
|
||||
return colors.yellow
|
||||
end
|
||||
return UI.ScrollingGrid.getRowTextColor(self, row, selected)
|
||||
if page.turtle and row.selected then
|
||||
return colors.yellow
|
||||
end
|
||||
return UI.ScrollingGrid.getRowTextColor(self, row, selected)
|
||||
end
|
||||
|
||||
function page.tabs.inventory:draw()
|
||||
local t = page.turtle
|
||||
Util.clear(self.values)
|
||||
if t then
|
||||
for _,v in ipairs(t.inventory) do
|
||||
if v.qty > 0 then
|
||||
table.insert(self.values, v)
|
||||
if v.index == t.slotIndex then
|
||||
v.selected = true
|
||||
end
|
||||
if v.id then
|
||||
v.id = itemDB:getName(v)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
self:adjustWidth()
|
||||
self:update()
|
||||
UI.ScrollingGrid.draw(self)
|
||||
local t = page.turtle
|
||||
Util.clear(self.values)
|
||||
if t then
|
||||
for _,v in ipairs(t.inventory) do
|
||||
if v.qty > 0 then
|
||||
table.insert(self.values, v)
|
||||
if v.index == t.slotIndex then
|
||||
v.selected = true
|
||||
end
|
||||
if v.id then
|
||||
v.id = itemDB:getName(v)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
self:adjustWidth()
|
||||
self:update()
|
||||
UI.ScrollingGrid.draw(self)
|
||||
end
|
||||
|
||||
function page.tabs.inventory:eventHandler(event)
|
||||
if event.type == 'grid_select' then
|
||||
local fn = string.format('turtle.select(%d)', event.selected.index)
|
||||
page:runFunction(fn)
|
||||
else
|
||||
return UI.ScrollingGrid.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
if event.type == 'grid_select' then
|
||||
local fn = string.format('turtle.select(%d)', event.selected.index)
|
||||
page:runFunction(fn)
|
||||
else
|
||||
return UI.ScrollingGrid.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function page.tabs.scripts:draw()
|
||||
|
||||
Util.clear(self.values)
|
||||
local files = fs.list(SCRIPTS_PATH)
|
||||
for _,path in pairs(files) do
|
||||
table.insert(self.values, { label = path, path = fs.combine(SCRIPTS_PATH, path) })
|
||||
end
|
||||
self:update()
|
||||
UI.ScrollingGrid.draw(self)
|
||||
Util.clear(self.values)
|
||||
local files = fs.list(SCRIPTS_PATH)
|
||||
for _,path in pairs(files) do
|
||||
table.insert(self.values, { label = path, path = fs.combine(SCRIPTS_PATH, path) })
|
||||
end
|
||||
self:update()
|
||||
UI.ScrollingGrid.draw(self)
|
||||
end
|
||||
|
||||
function page.tabs.scripts:eventHandler(event)
|
||||
if event.type == 'grid_select' then
|
||||
page:runScript(event.selected.label)
|
||||
else
|
||||
return UI.ScrollingGrid.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
if event.type == 'grid_select' then
|
||||
page:runScript(event.selected.label)
|
||||
else
|
||||
return UI.ScrollingGrid.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function page.tabs.turtles:getDisplayValues(row)
|
||||
row = Util.shallowCopy(row)
|
||||
if row.fuel then
|
||||
row.fuel = Util.toBytes(row.fuel)
|
||||
end
|
||||
if row.distance then
|
||||
row.distance = Util.round(row.distance, 1)
|
||||
end
|
||||
return row
|
||||
row = Util.shallowCopy(row)
|
||||
if row.fuel then
|
||||
row.fuel = Util.toBytes(row.fuel)
|
||||
end
|
||||
if row.distance then
|
||||
row.distance = Util.round(row.distance, 1)
|
||||
end
|
||||
return row
|
||||
end
|
||||
|
||||
function page.tabs.turtles:draw()
|
||||
Util.clear(self.values)
|
||||
for _,v in pairs(network) do
|
||||
if v.fuel then
|
||||
table.insert(self.values, v)
|
||||
end
|
||||
end
|
||||
self:update()
|
||||
UI.ScrollingGrid.draw(self)
|
||||
Util.clear(self.values)
|
||||
for _,v in pairs(network) do
|
||||
if v.fuel then
|
||||
table.insert(self.values, v)
|
||||
end
|
||||
end
|
||||
self:update()
|
||||
UI.ScrollingGrid.draw(self)
|
||||
end
|
||||
|
||||
function page.tabs.turtles:eventHandler(event)
|
||||
if event.type == 'grid_select' then
|
||||
page.turtle = event.selected
|
||||
config.id = event.selected.id
|
||||
Config.update('Turtles', config)
|
||||
multishell.setTitle(multishell.getCurrent(), page.turtle.label)
|
||||
if socket then
|
||||
socket:close()
|
||||
socket = nil
|
||||
end
|
||||
else
|
||||
return UI.ScrollingGrid.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
if event.type == 'grid_select' then
|
||||
page.turtle = event.selected
|
||||
config.id = event.selected.id
|
||||
Config.update('Turtles', config)
|
||||
multishell.setTitle(multishell.getCurrent(), page.turtle.label)
|
||||
if socket then
|
||||
socket:close()
|
||||
socket = nil
|
||||
end
|
||||
else
|
||||
return UI.ScrollingGrid.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function page.statusBar:draw()
|
||||
local t = self.parent.turtle
|
||||
if t then
|
||||
self.values.status = t.status
|
||||
self.values.distance = Util.round(t.distance, 2)
|
||||
self.values.fuel = Util.toBytes(t.fuel)
|
||||
end
|
||||
UI.StatusBar.draw(self)
|
||||
local t = self.parent.turtle
|
||||
if t then
|
||||
self.values.status = t.status
|
||||
self.values.distance = Util.round(t.distance, 2)
|
||||
self.values.fuel = Util.toBytes(t.fuel)
|
||||
end
|
||||
UI.StatusBar.draw(self)
|
||||
end
|
||||
|
||||
function page:showBlocks()
|
||||
local script = [[
|
||||
local function inspect(direction)
|
||||
local s,b = turtle['inspect' .. (direction or '')]()
|
||||
if not s then
|
||||
return 'minecraft:air:0'
|
||||
end
|
||||
return string.format('%s:%d', b.name, b.metadata)
|
||||
end
|
||||
local script = [[
|
||||
local function inspect(direction)
|
||||
local s,b = turtle['inspect' .. (direction or '')]()
|
||||
if not s then
|
||||
return 'minecraft:air:0'
|
||||
end
|
||||
return string.format('%s:%d', b.name, b.metadata)
|
||||
end
|
||||
|
||||
local bu, bf, bd = inspect('Up'), inspect(), inspect('Down')
|
||||
return string.format('%s\n%s\n%s', bu, bf, bd)
|
||||
]]
|
||||
local bu, bf, bd = inspect('Up'), inspect(), inspect('Down')
|
||||
return string.format('%s\n%s\n%s', bu, bf, bd)
|
||||
]]
|
||||
|
||||
local s, m = self:runFunction(script, true)
|
||||
self.tabs.action.info:setText(s or m)
|
||||
local s, m = self:runFunction(script, true)
|
||||
self.tabs.action.info:setText(s or m)
|
||||
end
|
||||
|
||||
function page:eventHandler(event)
|
||||
if event.type == 'quit' then
|
||||
UI:exitPullEvents()
|
||||
if event.type == 'quit' then
|
||||
UI:exitPullEvents()
|
||||
|
||||
elseif event.type == 'tab_select' then
|
||||
config.tab = event.button.text
|
||||
Config.update('Turtles', config)
|
||||
elseif event.type == 'tab_select' then
|
||||
config.tab = event.button.text
|
||||
Config.update('Turtles', config)
|
||||
|
||||
elseif event.type == 'button_press' then
|
||||
if event.button.fn then
|
||||
self:runFunction(event.button.fn, event.button.nowrap)
|
||||
self:showBlocks()
|
||||
elseif event.button.script then
|
||||
self:runScript(event.button.script)
|
||||
end
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
elseif event.type == 'button_press' then
|
||||
if event.button.fn then
|
||||
self:runFunction(event.button.fn, event.button.nowrap)
|
||||
self:showBlocks()
|
||||
elseif event.button.script then
|
||||
self:runScript(event.button.script)
|
||||
end
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function page:enable()
|
||||
UI.Page.enable(self)
|
||||
UI.Page.enable(self)
|
||||
-- self.tabs:activateTab(page.tabs.turtles)
|
||||
end
|
||||
|
||||
if not Util.getOptions(options, { ... }, true) then
|
||||
return
|
||||
return
|
||||
end
|
||||
|
||||
if options.turtle.value >= 0 then
|
||||
for _ = 1, 10 do
|
||||
page.turtle = _G.network[options.turtle.value]
|
||||
if page.turtle then
|
||||
break
|
||||
end
|
||||
os.sleep(1)
|
||||
end
|
||||
for _ = 1, 10 do
|
||||
page.turtle = _G.network[options.turtle.value]
|
||||
if page.turtle then
|
||||
break
|
||||
end
|
||||
os.sleep(1)
|
||||
end
|
||||
end
|
||||
|
||||
Event.onInterval(1, function()
|
||||
if page.turtle then
|
||||
local t = _G.network[page.turtle.id]
|
||||
page.turtle = t
|
||||
page:draw()
|
||||
page:sync()
|
||||
end
|
||||
if page.turtle then
|
||||
local t = _G.network[page.turtle.id]
|
||||
page.turtle = t
|
||||
page:draw()
|
||||
page:sync()
|
||||
end
|
||||
end)
|
||||
|
||||
if config.tab then
|
||||
page.tabs.tabBar:selectTab(config.tab)
|
||||
page.tabs.tabBar:selectTab(config.tab)
|
||||
end
|
||||
|
||||
UI:setPage(page)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
local c = function(shell, nIndex, sText)
|
||||
if nIndex == 1 then
|
||||
return _G.fs.complete(sText, shell.dir(), true, false)
|
||||
end
|
||||
if nIndex == 1 then
|
||||
return _G.fs.complete(sText, shell.dir(), true, false)
|
||||
end
|
||||
end
|
||||
|
||||
_ENV.shell.setCompletionFunction("packages/common/edit.lua", c)
|
||||
|
||||
45
common/canvasClient.lua
Normal file
45
common/canvasClient.lua
Normal file
@@ -0,0 +1,45 @@
|
||||
local Point = require('point')
|
||||
local Util = require('util')
|
||||
|
||||
local device = _G.device
|
||||
local os = _G.os
|
||||
local turtle = _G.turtle
|
||||
|
||||
local function convert(blocks, reference)
|
||||
if not reference then
|
||||
return blocks
|
||||
end
|
||||
|
||||
local rotated = {
|
||||
[0] = 0,
|
||||
[1] = 3,
|
||||
[2] = 2,
|
||||
[3] = 1,
|
||||
}
|
||||
return Util.reduce(blocks, function(acc, b)
|
||||
local c = Util.shallowCopy(b)
|
||||
Point.rotate(c, rotated[reference.heading])
|
||||
c.x = c.x + reference.x
|
||||
c.y = c.y + reference.y
|
||||
c.z = c.z + reference.z
|
||||
table.insert(acc, c)
|
||||
return acc
|
||||
end, { })
|
||||
end
|
||||
|
||||
local function broadcast(blocks, displayType, source)
|
||||
if device.wireless_modem then
|
||||
device.wireless_modem.transmit(3773, os.getComputerID(), {
|
||||
type = displayType,
|
||||
data = convert(blocks, source),
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
while true do
|
||||
local _, msg = os.pullEvent('canvas')
|
||||
|
||||
local reference = turtle and turtle.getState().reference
|
||||
|
||||
broadcast(msg.data, msg.type, reference)
|
||||
end
|
||||
2044
common/edit.lua
2044
common/edit.lua
File diff suppressed because it is too large
Load Diff
@@ -22,37 +22,37 @@ local scanner = device['plethora:scanner'] or
|
||||
-- hud
|
||||
local canvas = glasses and glasses.canvas()
|
||||
if canvas then
|
||||
local lh
|
||||
local lh
|
||||
|
||||
local function addText(x, y, text, color)
|
||||
local th = canvas.group.addText({ x, y }, text, color or 0xa0a0a0FF)
|
||||
lh = lh or th.getLineHeight()
|
||||
th.setShadow(true)
|
||||
th.setScale(.75)
|
||||
return th
|
||||
end
|
||||
local function addText(x, y, text, color)
|
||||
local th = canvas.group.addText({ x, y }, text, color or 0xa0a0a0FF)
|
||||
lh = lh or th.getLineHeight()
|
||||
th.setShadow(true)
|
||||
th.setScale(.75)
|
||||
return th
|
||||
end
|
||||
|
||||
canvas.group = canvas.addGroup({ 4, 90 })
|
||||
canvas.group.bg = canvas.group.addRectangle(0, 0, 80, 10, 0x40404080)
|
||||
canvas.group.addLines(
|
||||
{ 0, 0 },
|
||||
{ 80, 0 },
|
||||
{ 80, 10 },
|
||||
{ 0, 10 },
|
||||
{ 0, 0 },
|
||||
0x202020FF,
|
||||
2)
|
||||
addText(20, 2, 'Swarm Miner', 0xc0c0c0FF)
|
||||
canvas.group = canvas.addGroup({ 4, 90 })
|
||||
canvas.group.bg = canvas.group.addRectangle(0, 0, 80, 10, 0x40404080)
|
||||
canvas.group.addLines(
|
||||
{ 0, 0 },
|
||||
{ 80, 0 },
|
||||
{ 80, 10 },
|
||||
{ 0, 10 },
|
||||
{ 0, 0 },
|
||||
0x202020FF,
|
||||
2)
|
||||
addText(20, 2, 'Swarm Miner', 0xc0c0c0FF)
|
||||
|
||||
local y = 15
|
||||
addText(3, y, 'Turtles')
|
||||
canvas.turtles = addText(60, y, '')
|
||||
canvas.group.addLine({ 0, y + lh - 2 }, { 80, y + lh - 2 }, 0x404040FF, 4)
|
||||
local y = 15
|
||||
addText(3, y, 'Turtles')
|
||||
canvas.turtles = addText(60, y, '')
|
||||
canvas.group.addLine({ 0, y + lh - 2 }, { 80, y + lh - 2 }, 0x404040FF, 4)
|
||||
|
||||
y = y + lh + 5
|
||||
addText(3, y, 'Queue')
|
||||
canvas.queue = addText(60, y, '')
|
||||
canvas.group.addLine({ 0, y + lh - 2 }, { 80, y + lh - 2 }, 0x404040FF, 4)
|
||||
y = y + lh + 5
|
||||
addText(3, y, 'Queue')
|
||||
canvas.queue = addText(60, y, '')
|
||||
canvas.group.addLine({ 0, y + lh - 2 }, { 80, y + lh - 2 }, 0x404040FF, 4)
|
||||
end
|
||||
|
||||
-- container
|
||||
@@ -62,19 +62,19 @@ local box, offset
|
||||
local paused = false
|
||||
|
||||
local function inBox(pt)
|
||||
if not box or not box.ex then
|
||||
return true
|
||||
end
|
||||
return Point.inBox(pt, box)
|
||||
if not box or not box.ex then
|
||||
return true
|
||||
end
|
||||
return Point.inBox(pt, box)
|
||||
end
|
||||
|
||||
local function locate()
|
||||
for _ = 1, 3 do
|
||||
local pt = GPS.getPoint()
|
||||
if pt then
|
||||
return pt
|
||||
end
|
||||
end
|
||||
for _ = 1, 3 do
|
||||
local pt = GPS.getPoint()
|
||||
if pt then
|
||||
return pt
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local spt = GPS.getPoint() or error('GPS failure')
|
||||
@@ -88,7 +88,7 @@ local abort
|
||||
local function hijackTurtle(remoteId)
|
||||
local socket, msg = Socket.connect(remoteId, 188)
|
||||
|
||||
if not socket then
|
||||
if not socket then
|
||||
error(msg)
|
||||
end
|
||||
|
||||
@@ -111,240 +111,240 @@ local function hijackTurtle(remoteId)
|
||||
end
|
||||
|
||||
local function getNextPoint(turtle)
|
||||
if not paused then
|
||||
local pt = Point.closest(turtle.getPoint(), queue)
|
||||
if pt then
|
||||
turtle.pt = pt
|
||||
queue[pt.pkey] = nil
|
||||
return pt
|
||||
end
|
||||
end
|
||||
if not paused then
|
||||
local pt = Point.closest(turtle.getPoint(), queue)
|
||||
if pt then
|
||||
turtle.pt = pt
|
||||
queue[pt.pkey] = nil
|
||||
return pt
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function run(member, point)
|
||||
Event.addRoutine(function()
|
||||
local turtle, socket
|
||||
local _, m = pcall(function()
|
||||
member.active = true
|
||||
turtle, socket = hijackTurtle(member.id)
|
||||
Event.addRoutine(function()
|
||||
local turtle, socket
|
||||
local _, m = pcall(function()
|
||||
member.active = true
|
||||
turtle, socket = hijackTurtle(member.id)
|
||||
|
||||
local function emptySlots(retain, pt)
|
||||
local slots = turtle.getFilledSlots()
|
||||
for _,slot in pairs(slots) do
|
||||
if not retain[slot.key] then
|
||||
turtle.select(slot.index)
|
||||
if pt then
|
||||
turtle.dropAt(pt, 64)
|
||||
else
|
||||
turtle.dropUp(64)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
local function emptySlots(retain, pt)
|
||||
local slots = turtle.getFilledSlots()
|
||||
for _,slot in pairs(slots) do
|
||||
if not retain[slot.key] then
|
||||
turtle.select(slot.index)
|
||||
if pt then
|
||||
turtle.dropAt(pt, 64)
|
||||
else
|
||||
turtle.dropUp(64)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function dropOff()
|
||||
-- go to 2 above chest
|
||||
local topPoint = Point.copy(chestPoint)
|
||||
topPoint.y = topPoint.y + 2
|
||||
turtle.gotoY(topPoint.y)
|
||||
while not turtle.go(topPoint) do
|
||||
os.sleep(.5)
|
||||
end
|
||||
local function dropOff()
|
||||
-- go to 2 above chest
|
||||
local topPoint = Point.copy(chestPoint)
|
||||
topPoint.y = topPoint.y + 2
|
||||
turtle.gotoY(topPoint.y)
|
||||
while not turtle.go(topPoint) do
|
||||
os.sleep(.5)
|
||||
end
|
||||
|
||||
-- path to chest
|
||||
local box = Point.makeBox(
|
||||
{ x = chestPoint.x - 3, y = chestPoint.y + 3, z = chestPoint.z - 3 },
|
||||
{ x = chestPoint.x + 3, y = chestPoint.y, z = chestPoint.z + 3 }
|
||||
)
|
||||
turtle.set({
|
||||
movementStrategy = 'pathing',
|
||||
pathingBox = Point.normalizeBox(box),
|
||||
digPolicy = 'digNone',
|
||||
})
|
||||
while not turtle.moveAgainst(chestPoint) do
|
||||
os.sleep(.5)
|
||||
end
|
||||
emptySlots({ }, chestPoint)
|
||||
-- path to chest
|
||||
local box = Point.makeBox(
|
||||
{ x = chestPoint.x - 3, y = chestPoint.y + 3, z = chestPoint.z - 3 },
|
||||
{ x = chestPoint.x + 3, y = chestPoint.y, z = chestPoint.z + 3 }
|
||||
)
|
||||
turtle.set({
|
||||
movementStrategy = 'pathing',
|
||||
pathingBox = Point.normalizeBox(box),
|
||||
digPolicy = 'digNone',
|
||||
})
|
||||
while not turtle.moveAgainst(chestPoint) do
|
||||
os.sleep(.5)
|
||||
end
|
||||
emptySlots({ }, chestPoint)
|
||||
|
||||
-- path to 3 above chest
|
||||
turtle.pathfind(Point.above(topPoint))
|
||||
turtle.set({
|
||||
movementStrategy = 'goto',
|
||||
digPolicy = 'blacklist',
|
||||
})
|
||||
end
|
||||
-- path to 3 above chest
|
||||
turtle.pathfind(Point.above(topPoint))
|
||||
turtle.set({
|
||||
movementStrategy = 'goto',
|
||||
digPolicy = 'blacklist',
|
||||
})
|
||||
end
|
||||
|
||||
if turtle then
|
||||
turtles[member.id] = turtle
|
||||
if turtle then
|
||||
turtles[member.id] = turtle
|
||||
|
||||
turtle.reset()
|
||||
turtle.set({
|
||||
attackPolicy = 'attack',
|
||||
digPolicy = 'blacklist',
|
||||
blacklist = {
|
||||
'turtle',
|
||||
'chest',
|
||||
'shulker',
|
||||
},
|
||||
movementStrategy = 'goto',
|
||||
point = point,
|
||||
})
|
||||
turtle.select(1)
|
||||
turtle.reset()
|
||||
turtle.set({
|
||||
attackPolicy = 'attack',
|
||||
digPolicy = 'blacklist',
|
||||
blacklist = {
|
||||
'turtle',
|
||||
'chest',
|
||||
'shulker',
|
||||
},
|
||||
movementStrategy = 'goto',
|
||||
point = point,
|
||||
})
|
||||
turtle.select(1)
|
||||
|
||||
repeat
|
||||
local pt = getNextPoint(turtle)
|
||||
if pt then
|
||||
member.status = 'digging'
|
||||
repeat
|
||||
local pt = getNextPoint(turtle)
|
||||
if pt then
|
||||
member.status = 'digging'
|
||||
|
||||
if blockTypes[pt.key] == true then
|
||||
if turtle.moveAgainst(pt) then
|
||||
local index = turtle.selectOpenSlot()
|
||||
if turtle.digAt(pt, pt.name) then
|
||||
local slot = turtle.getSlot(index)
|
||||
if slot.count > 0 then
|
||||
blockTypes[pt.key] = slot.key
|
||||
if slot.key ~= pt.key then
|
||||
blockTypes[slot.key] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
turtle.select(1)
|
||||
else
|
||||
turtle.digAt(pt, pt.name)
|
||||
end
|
||||
if blockTypes[pt.key] == true then
|
||||
if turtle.moveAgainst(pt) then
|
||||
local index = turtle.selectOpenSlot()
|
||||
if turtle.digAt(pt, pt.name) then
|
||||
local slot = turtle.getSlot(index)
|
||||
if slot.count > 0 then
|
||||
blockTypes[pt.key] = slot.key
|
||||
if slot.key ~= pt.key then
|
||||
blockTypes[slot.key] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
turtle.select(1)
|
||||
else
|
||||
turtle.digAt(pt, pt.name)
|
||||
end
|
||||
|
||||
if turtle.getItemCount(15) > 0 then
|
||||
member.status = 'ejecting trash'
|
||||
emptySlots(blockTypes)
|
||||
turtle.condense()
|
||||
if turtle.getItemCount(15) > 0 then
|
||||
member.status = 'dropping off'
|
||||
if not chestPoint then
|
||||
member.abort = true
|
||||
member.status = 'full'
|
||||
else
|
||||
dropOff()
|
||||
end
|
||||
end
|
||||
turtle.select(1)
|
||||
end
|
||||
else
|
||||
member.status = 'waiting'
|
||||
os.sleep(1)
|
||||
end
|
||||
if member.fuel < 100 then
|
||||
member.status = 'out of fuel'
|
||||
break
|
||||
end
|
||||
until member.abort
|
||||
end
|
||||
if turtle.getItemCount(15) > 0 then
|
||||
member.status = 'ejecting trash'
|
||||
emptySlots(blockTypes)
|
||||
turtle.condense()
|
||||
if turtle.getItemCount(15) > 0 then
|
||||
member.status = 'dropping off'
|
||||
if not chestPoint then
|
||||
member.abort = true
|
||||
member.status = 'full'
|
||||
else
|
||||
dropOff()
|
||||
end
|
||||
end
|
||||
turtle.select(1)
|
||||
end
|
||||
else
|
||||
member.status = 'waiting'
|
||||
os.sleep(1)
|
||||
end
|
||||
if member.fuel < 100 then
|
||||
member.status = 'out of fuel'
|
||||
break
|
||||
end
|
||||
until member.abort
|
||||
end
|
||||
|
||||
emptySlots(blockTypes)
|
||||
emptySlots(blockTypes)
|
||||
|
||||
if chestPoint then
|
||||
dropOff()
|
||||
while not turtle.go(Point.above(spt)) do
|
||||
os.sleep(.5)
|
||||
end
|
||||
--if turtle.selectSlotWithQuantity(0) then
|
||||
--turtle.set({ digPolicy = 'dig' })
|
||||
--end
|
||||
while not turtle.go(spt) do
|
||||
os.sleep(.5)
|
||||
end
|
||||
else
|
||||
turtle.gotoY(spt.y)
|
||||
while not turtle.go(spt) do
|
||||
os.sleep(.5)
|
||||
end
|
||||
end
|
||||
end)
|
||||
if chestPoint then
|
||||
dropOff()
|
||||
while not turtle.go(Point.above(spt)) do
|
||||
os.sleep(.5)
|
||||
end
|
||||
--if turtle.selectSlotWithQuantity(0) then
|
||||
--turtle.set({ digPolicy = 'dig' })
|
||||
--end
|
||||
while not turtle.go(spt) do
|
||||
os.sleep(.5)
|
||||
end
|
||||
else
|
||||
turtle.gotoY(spt.y)
|
||||
while not turtle.go(spt) do
|
||||
os.sleep(.5)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
turtles[member.id] = nil
|
||||
member.status = m
|
||||
member.active = false
|
||||
if socket then
|
||||
socket:close()
|
||||
end
|
||||
end)
|
||||
turtles[member.id] = nil
|
||||
member.status = m
|
||||
member.active = false
|
||||
if socket then
|
||||
socket:close()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
local function drawContainer(pos)
|
||||
if canvas3d then
|
||||
canvas3d.clear()
|
||||
if canvas3d then
|
||||
canvas3d.clear()
|
||||
|
||||
local function addBox(b)
|
||||
canvas3d.addBox(
|
||||
b.x - offset.x + .25,
|
||||
b.y - offset.y + .25 ,
|
||||
b.z - offset.z + .25 ,
|
||||
.5, .5, .5).setDepthTested(false)
|
||||
end
|
||||
if box and box.ex then
|
||||
addBox({ x = box.x, y = box.y, z = box.z })
|
||||
addBox({ x = box.x, y = box.y, z = box.ez })
|
||||
addBox({ x = box.ex, y = box.y, z = box.z })
|
||||
addBox({ x = box.ex, y = box.y, z = box.ez })
|
||||
addBox({ x = box.x, y = box.ey, z = box.z })
|
||||
addBox({ x = box.x, y = box.ey, z = box.ez })
|
||||
addBox({ x = box.ex, y = box.ey, z = box.z })
|
||||
addBox({ x = box.ex, y = box.ey, z = box.ez })
|
||||
elseif box then
|
||||
canvas3d.recenter({ -(pos.x % 1), -(pos.y % 1), -(pos.z % 1) })
|
||||
addBox(box)
|
||||
end
|
||||
end
|
||||
local function addBox(b)
|
||||
canvas3d.addBox(
|
||||
b.x - offset.x + .25,
|
||||
b.y - offset.y + .25 ,
|
||||
b.z - offset.z + .25 ,
|
||||
.5, .5, .5).setDepthTested(false)
|
||||
end
|
||||
if box and box.ex then
|
||||
addBox({ x = box.x, y = box.y, z = box.z })
|
||||
addBox({ x = box.x, y = box.y, z = box.ez })
|
||||
addBox({ x = box.ex, y = box.y, z = box.z })
|
||||
addBox({ x = box.ex, y = box.y, z = box.ez })
|
||||
addBox({ x = box.x, y = box.ey, z = box.z })
|
||||
addBox({ x = box.x, y = box.ey, z = box.ez })
|
||||
addBox({ x = box.ex, y = box.ey, z = box.z })
|
||||
addBox({ x = box.ex, y = box.ey, z = box.ez })
|
||||
elseif box then
|
||||
canvas3d.recenter({ -(pos.x % 1), -(pos.y % 1), -(pos.z % 1) })
|
||||
addBox(box)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local pauseResume = {
|
||||
{ text = 'Pause', event = 'pause' },
|
||||
{ text = 'Resume', event = 'resume' },
|
||||
{ text = 'Pause', event = 'pause' },
|
||||
{ text = 'Resume', event = 'resume' },
|
||||
}
|
||||
local containerText = {
|
||||
[[Set a corner to contain mining area]],
|
||||
[[Set ending corner]],
|
||||
[[Set again to clear]],
|
||||
[[Set a corner to contain mining area]],
|
||||
[[Set ending corner]],
|
||||
[[Set again to clear]],
|
||||
}
|
||||
|
||||
local containTab = UI.Tab {
|
||||
tabTitle = 'Contain',
|
||||
button = UI.Button {
|
||||
x = 2, y = 2,
|
||||
text = 'Set corner',
|
||||
event = 'contain'
|
||||
},
|
||||
textArea = UI.TextArea {
|
||||
x = 2, y = 4,
|
||||
value = containerText[1],
|
||||
},
|
||||
tabTitle = 'Contain',
|
||||
button = UI.Button {
|
||||
x = 2, y = 2,
|
||||
text = 'Set corner',
|
||||
event = 'contain'
|
||||
},
|
||||
textArea = UI.TextArea {
|
||||
x = 2, y = 4,
|
||||
value = containerText[1],
|
||||
},
|
||||
}
|
||||
|
||||
local blocksTab = UI.Tab {
|
||||
tabTitle = 'Blocks',
|
||||
grid = UI.ScrollingGrid {
|
||||
y = 1,
|
||||
columns = {
|
||||
{ heading = 'Count', key = 'count', width = 6, align = 'right' },
|
||||
{ heading = 'Name', key = 'displayName' },
|
||||
},
|
||||
sortColumn = 'displayName',
|
||||
},
|
||||
tabTitle = 'Blocks',
|
||||
grid = UI.ScrollingGrid {
|
||||
y = 1,
|
||||
columns = {
|
||||
{ heading = 'Count', key = 'count', width = 6, align = 'right' },
|
||||
{ heading = 'Name', key = 'displayName' },
|
||||
},
|
||||
sortColumn = 'displayName',
|
||||
},
|
||||
}
|
||||
|
||||
local turtlesTab = UI.Tab {
|
||||
tabTitle = 'Turtles',
|
||||
grid = UI.ScrollingGrid {
|
||||
y = 1,
|
||||
values = pool,
|
||||
columns = {
|
||||
{ heading = 'ID', key = 'id', width = 5, },
|
||||
{ heading = ' Fuel', key = 'fuel', width = 5, align = 'right' },
|
||||
{ heading = ' Dist', key = 'distance', width = 5, align = 'right' },
|
||||
{ heading = 'Status', key = 'status' },
|
||||
},
|
||||
sortColumn = 'label',
|
||||
},
|
||||
tabTitle = 'Turtles',
|
||||
grid = UI.ScrollingGrid {
|
||||
y = 1,
|
||||
values = pool,
|
||||
columns = {
|
||||
{ heading = 'ID', key = 'id', width = 5, },
|
||||
{ heading = ' Fuel', key = 'fuel', width = 5, align = 'right' },
|
||||
{ heading = ' Dist', key = 'distance', width = 5, align = 'right' },
|
||||
{ heading = 'Status', key = 'status' },
|
||||
},
|
||||
sortColumn = 'label',
|
||||
},
|
||||
}
|
||||
|
||||
local page = UI.Page {
|
||||
@@ -354,264 +354,264 @@ local page = UI.Page {
|
||||
{ text = 'Abort', event = 'abort' },
|
||||
pauseResume[1],
|
||||
},
|
||||
},
|
||||
tabs = UI.Tabs {
|
||||
y = 2, ey = -2,
|
||||
[1] = blocksTab,
|
||||
[2] = turtlesTab,
|
||||
[3] = containTab,
|
||||
},
|
||||
info = UI.Window {
|
||||
y = -1,
|
||||
backgroundColor = colors.blue,
|
||||
}
|
||||
},
|
||||
tabs = UI.Tabs {
|
||||
y = 2, ey = -2,
|
||||
[1] = blocksTab,
|
||||
[2] = turtlesTab,
|
||||
[3] = containTab,
|
||||
},
|
||||
info = UI.Window {
|
||||
y = -1,
|
||||
backgroundColor = colors.blue,
|
||||
}
|
||||
}
|
||||
|
||||
function page.info:draw()
|
||||
self:clear()
|
||||
self:write(2, 1, 'Turtles: ' .. Util.size(turtles))
|
||||
if not chestPoint then
|
||||
self:write(16, 1, 'No chest')
|
||||
end
|
||||
self:write(28, 1, 'Queue: ' .. Util.size(queue))
|
||||
self:clear()
|
||||
self:write(2, 1, 'Turtles: ' .. Util.size(turtles))
|
||||
if not chestPoint then
|
||||
self:write(16, 1, 'No chest')
|
||||
end
|
||||
self:write(28, 1, 'Queue: ' .. Util.size(queue))
|
||||
end
|
||||
|
||||
function turtlesTab.grid:getDisplayValues(row)
|
||||
row = Util.shallowCopy(row)
|
||||
row.distance = row.distance and Util.round(row.distance, 1)
|
||||
row.fuel = row.fuel and row.fuel > 0 and Util.toBytes(row.fuel) or ''
|
||||
return row
|
||||
row.distance = row.distance and Util.round(row.distance, 1)
|
||||
row.fuel = row.fuel and row.fuel > 0 and Util.toBytes(row.fuel) or ''
|
||||
return row
|
||||
end
|
||||
|
||||
function page:scan()
|
||||
local gpt = GPS.getPoint()
|
||||
if not gpt then
|
||||
return
|
||||
end
|
||||
local rawBlocks = scanner:scan()
|
||||
local candidates = { }
|
||||
local gpt = GPS.getPoint()
|
||||
if not gpt then
|
||||
return
|
||||
end
|
||||
local rawBlocks = scanner:scan()
|
||||
local candidates = { }
|
||||
|
||||
self.totals = Util.reduce(rawBlocks,
|
||||
function(acc, b)
|
||||
b.key = table.concat({ b.name, b.metadata }, ':')
|
||||
local entry = acc[b.key]
|
||||
if not entry then
|
||||
b.displayName = itemDB:getName(b.key)
|
||||
b.count = 1
|
||||
acc[b.key] = b
|
||||
self.totals = Util.reduce(rawBlocks,
|
||||
function(acc, b)
|
||||
b.key = table.concat({ b.name, b.metadata }, ':')
|
||||
local entry = acc[b.key]
|
||||
if not entry then
|
||||
b.displayName = itemDB:getName(b.key)
|
||||
b.count = 1
|
||||
acc[b.key] = b
|
||||
else
|
||||
entry.count = entry.count + 1
|
||||
end
|
||||
entry.count = entry.count + 1
|
||||
end
|
||||
|
||||
if b.name == 'computercraft:turtle_advanced' or
|
||||
b.name == 'computercraft:turtle_expanded' or
|
||||
b.name == 'computercraft:turtle' then
|
||||
table.insert(candidates, b)
|
||||
end
|
||||
if b.name == 'computercraft:turtle_advanced' or
|
||||
b.name == 'computercraft:turtle_expanded' or
|
||||
b.name == 'computercraft:turtle' then
|
||||
table.insert(candidates, b)
|
||||
end
|
||||
|
||||
if b.name == 'minecraft:chest' or b.name:find('shulker') then
|
||||
chestPoint = b
|
||||
end
|
||||
if b.name == 'minecraft:chest' or b.name:find('shulker') then
|
||||
chestPoint = b
|
||||
end
|
||||
|
||||
-- add relevant blocks to queue
|
||||
b.x = gpt.x + b.x
|
||||
b.y = gpt.y + b.y
|
||||
b.z = gpt.z + b.z
|
||||
b.pkey = table.concat({ b.x, b.y, b.z }, ':')
|
||||
if blockTypes[b.key] and inBox(b) then
|
||||
if not Util.any(turtles, function(t)
|
||||
return t.pt and t.pt.pkey == b.pkey
|
||||
end) then
|
||||
queue[b.pkey] = b
|
||||
end
|
||||
else
|
||||
queue[b.pkey] = nil
|
||||
end
|
||||
return acc
|
||||
-- add relevant blocks to queue
|
||||
b.x = gpt.x + b.x
|
||||
b.y = gpt.y + b.y
|
||||
b.z = gpt.z + b.z
|
||||
b.pkey = table.concat({ b.x, b.y, b.z }, ':')
|
||||
if blockTypes[b.key] and inBox(b) then
|
||||
if not Util.any(turtles, function(t)
|
||||
return t.pt and t.pt.pkey == b.pkey
|
||||
end) then
|
||||
queue[b.pkey] = b
|
||||
end
|
||||
else
|
||||
queue[b.pkey] = nil
|
||||
end
|
||||
return acc
|
||||
end,
|
||||
{ })
|
||||
{ })
|
||||
|
||||
for _, b in pairs(candidates) do
|
||||
local v = scanner.getBlockMeta(b.x - gpt.x, b.y - gpt.y, b.z - gpt.z)
|
||||
if v and v.computer then
|
||||
local member = pool[v.computer.id]
|
||||
if not member then
|
||||
member = {
|
||||
id = v.computer.id,
|
||||
label = v.computer.label,
|
||||
}
|
||||
pool[v.computer.id] = member
|
||||
end
|
||||
for _, b in pairs(candidates) do
|
||||
local v = scanner.getBlockMeta(b.x - gpt.x, b.y - gpt.y, b.z - gpt.z)
|
||||
if v and v.computer then
|
||||
local member = pool[v.computer.id]
|
||||
if not member then
|
||||
member = {
|
||||
id = v.computer.id,
|
||||
label = v.computer.label,
|
||||
}
|
||||
pool[v.computer.id] = member
|
||||
end
|
||||
|
||||
member.fuel = v.turtle.fuel
|
||||
member.distance = 0
|
||||
member.fuel = v.turtle.fuel
|
||||
member.distance = 0
|
||||
|
||||
if not v.computer.isOn then
|
||||
member.status = 'Powered off'
|
||||
elseif v.turtle.fuel < 100 and not member.active then
|
||||
member.status = 'Not enough fuel'
|
||||
elseif not member.active and not member.abort then
|
||||
local pt = Point.copy(b)
|
||||
pt.heading = Point.facings[v.state.facing].heading
|
||||
run(member, pt)
|
||||
end
|
||||
end
|
||||
end
|
||||
if not v.computer.isOn then
|
||||
member.status = 'Powered off'
|
||||
elseif v.turtle.fuel < 100 and not member.active then
|
||||
member.status = 'Not enough fuel'
|
||||
elseif not member.active and not member.abort then
|
||||
local pt = Point.copy(b)
|
||||
pt.heading = Point.facings[v.state.facing].heading
|
||||
run(member, pt)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function blocksTab.grid:getDisplayValues(row)
|
||||
row = Util.shallowCopy(row)
|
||||
row.count = Util.toBytes(row.count) .. ' '
|
||||
return row
|
||||
return row
|
||||
end
|
||||
|
||||
function blocksTab.grid:getRowTextColor(row, selected)
|
||||
return blockTypes[row.key] and
|
||||
colors.yellow or
|
||||
UI.Grid.getRowTextColor(self, row, selected)
|
||||
return blockTypes[row.key] and
|
||||
colors.yellow or
|
||||
UI.Grid.getRowTextColor(self, row, selected)
|
||||
end
|
||||
|
||||
function blocksTab:eventHandler(event)
|
||||
if event.type == 'grid_select' then
|
||||
local key = event.selected.key
|
||||
if blockTypes[key] then
|
||||
for k,v in pairs(queue) do
|
||||
if v.key == key then
|
||||
queue[k] = nil
|
||||
end
|
||||
end
|
||||
blockTypes[key] = nil
|
||||
else
|
||||
blockTypes[key] = true
|
||||
end
|
||||
self.grid:draw()
|
||||
end
|
||||
local key = event.selected.key
|
||||
if blockTypes[key] then
|
||||
for k,v in pairs(queue) do
|
||||
if v.key == key then
|
||||
queue[k] = nil
|
||||
end
|
||||
end
|
||||
blockTypes[key] = nil
|
||||
else
|
||||
blockTypes[key] = true
|
||||
end
|
||||
self.grid:draw()
|
||||
end
|
||||
end
|
||||
|
||||
function page:eventHandler(event)
|
||||
if event.type == 'scan' then
|
||||
blocksTab.grid:setValues(self.totals)
|
||||
blocksTab.grid:draw()
|
||||
self.tabs:selectTab(blocksTab)
|
||||
if event.type == 'scan' then
|
||||
blocksTab.grid:setValues(self.totals)
|
||||
blocksTab.grid:draw()
|
||||
self.tabs:selectTab(blocksTab)
|
||||
|
||||
elseif event.type == 'pause' then
|
||||
paused = true
|
||||
Util.merge(event.button, pauseResume[2])
|
||||
event.button:draw()
|
||||
elseif event.type == 'pause' then
|
||||
paused = true
|
||||
Util.merge(event.button, pauseResume[2])
|
||||
event.button:draw()
|
||||
|
||||
elseif event.type == 'resume' then
|
||||
paused = false
|
||||
Util.merge(event.button, pauseResume[1])
|
||||
event.button:draw()
|
||||
elseif event.type == 'resume' then
|
||||
paused = false
|
||||
Util.merge(event.button, pauseResume[1])
|
||||
event.button:draw()
|
||||
|
||||
elseif event.type == 'contain' then
|
||||
local pt = { gps.locate() }
|
||||
local pos = {
|
||||
x = pt[1],
|
||||
y = pt[2],
|
||||
z = pt[3],
|
||||
}
|
||||
elseif event.type == 'contain' then
|
||||
local pt = { gps.locate() }
|
||||
local pos = {
|
||||
x = pt[1],
|
||||
y = pt[2],
|
||||
z = pt[3],
|
||||
}
|
||||
|
||||
if not box then
|
||||
offset = {
|
||||
x = math.floor(pos.x),
|
||||
y = math.floor(pos.y),
|
||||
z = math.floor(pos.z),
|
||||
}
|
||||
box = {
|
||||
x = math.floor(pos.x),
|
||||
y = math.floor(pos.y) - 1,
|
||||
z = math.floor(pos.z),
|
||||
}
|
||||
containTab.textArea.value = containerText[2]
|
||||
elseif not box.ex then
|
||||
box.ex = math.floor(pos.x)
|
||||
box.ey = math.floor(pos.y) - 1
|
||||
box.ez = math.floor(pos.z)
|
||||
box = Point.normalizeBox(box)
|
||||
containTab.textArea.value = containerText[3]
|
||||
else
|
||||
box = nil
|
||||
containTab.textArea.value = containerText[1]
|
||||
end
|
||||
if not box then
|
||||
offset = {
|
||||
x = math.floor(pos.x),
|
||||
y = math.floor(pos.y),
|
||||
z = math.floor(pos.z),
|
||||
}
|
||||
box = {
|
||||
x = math.floor(pos.x),
|
||||
y = math.floor(pos.y) - 1,
|
||||
z = math.floor(pos.z),
|
||||
}
|
||||
containTab.textArea.value = containerText[2]
|
||||
elseif not box.ex then
|
||||
box.ex = math.floor(pos.x)
|
||||
box.ey = math.floor(pos.y) - 1
|
||||
box.ez = math.floor(pos.z)
|
||||
box = Point.normalizeBox(box)
|
||||
containTab.textArea.value = containerText[3]
|
||||
else
|
||||
box = nil
|
||||
containTab.textArea.value = containerText[1]
|
||||
end
|
||||
|
||||
containTab.textArea:draw()
|
||||
drawContainer(pos)
|
||||
containTab.textArea:draw()
|
||||
drawContainer(pos)
|
||||
|
||||
elseif event.type == 'abort' then
|
||||
for _, v in pairs(pool) do
|
||||
v.abort = true
|
||||
v.status = 'aborting'
|
||||
end
|
||||
spt = Point.above(locate())
|
||||
abort = true
|
||||
end
|
||||
elseif event.type == 'abort' then
|
||||
for _, v in pairs(pool) do
|
||||
v.abort = true
|
||||
v.status = 'aborting'
|
||||
end
|
||||
spt = Point.above(locate())
|
||||
abort = true
|
||||
end
|
||||
|
||||
UI.Page.eventHandler(self, event)
|
||||
end
|
||||
|
||||
Event.onInterval(5, function()
|
||||
if not abort and not paused then
|
||||
if not abort and not paused then
|
||||
|
||||
--local meta = scanner.getMetaOwner()
|
||||
--if meta.isSneaking then
|
||||
page:scan()
|
||||
-- Sound.play('entity.bobber.throw', .6)
|
||||
--end
|
||||
end
|
||||
--local meta = scanner.getMetaOwner()
|
||||
--if meta.isSneaking then
|
||||
page:scan()
|
||||
-- Sound.play('entity.bobber.throw', .6)
|
||||
--end
|
||||
end
|
||||
end)
|
||||
|
||||
Event.onInterval(1, function()
|
||||
for id,v in pairs(network) do
|
||||
if v.fuel then
|
||||
if pool[id] then
|
||||
pool[id].fuel = v.fuel
|
||||
pool[id].distance = v.distance
|
||||
end
|
||||
end
|
||||
end
|
||||
for id,v in pairs(network) do
|
||||
if v.fuel then
|
||||
if pool[id] then
|
||||
pool[id].fuel = v.fuel
|
||||
pool[id].distance = v.distance
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if abort and Util.size(turtles) == 0 then
|
||||
Event.exitPullEvents()
|
||||
end
|
||||
if abort and Util.size(turtles) == 0 then
|
||||
Event.exitPullEvents()
|
||||
end
|
||||
|
||||
if turtlesTab.enabled then
|
||||
turtlesTab.grid:update()
|
||||
turtlesTab.grid:draw()
|
||||
end
|
||||
if turtlesTab.enabled then
|
||||
turtlesTab.grid:update()
|
||||
turtlesTab.grid:draw()
|
||||
end
|
||||
|
||||
page.info:draw()
|
||||
page.info:sync()
|
||||
page.info:draw()
|
||||
page.info:sync()
|
||||
|
||||
if canvas then
|
||||
canvas.turtles.setText(tostring(Util.size(turtles)))
|
||||
canvas.queue.setText(tostring(Util.size(queue)))
|
||||
end
|
||||
if canvas then
|
||||
canvas.turtles.setText(tostring(Util.size(turtles)))
|
||||
canvas.queue.setText(tostring(Util.size(queue)))
|
||||
end
|
||||
end)
|
||||
|
||||
Event.onTimeout(.1, function()
|
||||
page:scan()
|
||||
blocksTab.grid:setValues(page.totals)
|
||||
blocksTab.grid:draw()
|
||||
page:sync()
|
||||
page:scan()
|
||||
blocksTab.grid:setValues(page.totals)
|
||||
blocksTab.grid:draw()
|
||||
page:sync()
|
||||
end)
|
||||
|
||||
UI:setPage(page)
|
||||
|
||||
--[[
|
||||
Event.onTerminate(function()
|
||||
spt = Point.above(locate())
|
||||
for _, v in pairs(pool) do
|
||||
v.status = 'aborting'
|
||||
v.abort = true
|
||||
end
|
||||
abort = true
|
||||
spt = Point.above(locate())
|
||||
for _, v in pairs(pool) do
|
||||
v.status = 'aborting'
|
||||
v.abort = true
|
||||
end
|
||||
abort = true
|
||||
end)
|
||||
]]
|
||||
|
||||
Event.pullEvents()
|
||||
|
||||
if canvas then
|
||||
canvas3d.clear()
|
||||
canvas.group.remove()
|
||||
canvas3d.clear()
|
||||
canvas.group.remove()
|
||||
end
|
||||
|
||||
@@ -8,154 +8,154 @@ local os = _G.os
|
||||
local ChestAdapter = class()
|
||||
|
||||
local convertNames = {
|
||||
name = 'id',
|
||||
damage = 'dmg',
|
||||
maxCount = 'max_size',
|
||||
count = 'qty',
|
||||
displayName = 'display_name',
|
||||
maxDamage = 'max_dmg',
|
||||
nbtHash = 'nbt_hash',
|
||||
name = 'id',
|
||||
damage = 'dmg',
|
||||
maxCount = 'max_size',
|
||||
count = 'qty',
|
||||
displayName = 'display_name',
|
||||
maxDamage = 'max_dmg',
|
||||
nbtHash = 'nbt_hash',
|
||||
}
|
||||
|
||||
-- Strip off color prefix
|
||||
local function safeString(text)
|
||||
|
||||
local val = text:byte(1)
|
||||
local val = text:byte(1)
|
||||
|
||||
if val < 32 or val > 128 then
|
||||
if val < 32 or val > 128 then
|
||||
|
||||
local newText = {}
|
||||
for i = 4, #text do
|
||||
val = text:byte(i)
|
||||
newText[i - 3] = (val > 31 and val < 127) and val or 63
|
||||
end
|
||||
return string.char(unpack(newText))
|
||||
end
|
||||
local newText = {}
|
||||
for i = 4, #text do
|
||||
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
|
||||
return text
|
||||
end
|
||||
|
||||
local function convertItem(item)
|
||||
for k,v in pairs(convertNames) do
|
||||
item[k] = item[v]
|
||||
item[v] = nil
|
||||
end
|
||||
item.displayName = safeString(item.displayName)
|
||||
for k,v in pairs(convertNames) do
|
||||
item[k] = item[v]
|
||||
item[v] = nil
|
||||
end
|
||||
item.displayName = safeString(item.displayName)
|
||||
end
|
||||
|
||||
function ChestAdapter:init(args)
|
||||
local defaults = {
|
||||
name = 'chest',
|
||||
}
|
||||
Util.merge(self, defaults)
|
||||
Util.merge(self, args)
|
||||
local defaults = {
|
||||
name = 'chest',
|
||||
}
|
||||
Util.merge(self, defaults)
|
||||
Util.merge(self, args)
|
||||
|
||||
local chest
|
||||
if not self.side then
|
||||
chest = Peripheral.getByMethod('getAllStacks')
|
||||
else
|
||||
chest = Peripheral.getBySide(self.side)
|
||||
if chest and not chest.getAllStacks then
|
||||
chest = nil
|
||||
end
|
||||
end
|
||||
local chest
|
||||
if not self.side then
|
||||
chest = Peripheral.getByMethod('getAllStacks')
|
||||
else
|
||||
chest = Peripheral.getBySide(self.side)
|
||||
if chest and not chest.getAllStacks then
|
||||
chest = nil
|
||||
end
|
||||
end
|
||||
|
||||
if chest then
|
||||
Util.merge(self, chest)
|
||||
if chest then
|
||||
Util.merge(self, chest)
|
||||
|
||||
if chest.listAvailableItems then
|
||||
self.list = chest.listAvailableItems
|
||||
end
|
||||
end
|
||||
if chest.listAvailableItems then
|
||||
self.list = chest.listAvailableItems
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ChestAdapter:isValid()
|
||||
return not not self.getAllStacks
|
||||
return not not self.getAllStacks
|
||||
end
|
||||
|
||||
function ChestAdapter:refresh(throttle)
|
||||
return self:listItems(throttle)
|
||||
return self:listItems(throttle)
|
||||
end
|
||||
|
||||
-- provide a consolidated list of items
|
||||
function ChestAdapter:listItems(throttle)
|
||||
local cache = { }
|
||||
local items = { }
|
||||
throttle = throttle or Util.throttle()
|
||||
local cache = { }
|
||||
local items = { }
|
||||
throttle = throttle or Util.throttle()
|
||||
|
||||
-- getAllStacks sometimes fails
|
||||
local s, m = pcall(function()
|
||||
for _,v in pairs(self.getAllStacks(false)) do
|
||||
if v.qty > 0 then
|
||||
convertItem(v)
|
||||
local key = table.concat({ v.name, v.damage, v.nbtHash }, ':')
|
||||
-- getAllStacks sometimes fails
|
||||
local s, m = pcall(function()
|
||||
for _,v in pairs(self.getAllStacks(false)) do
|
||||
if v.qty > 0 then
|
||||
convertItem(v)
|
||||
local key = table.concat({ v.name, v.damage, v.nbtHash }, ':')
|
||||
|
||||
local entry = cache[key]
|
||||
if not entry then
|
||||
entry = itemDB:get(v) or itemDB:add(v)
|
||||
entry = Util.shallowCopy(entry)
|
||||
entry.count = 0
|
||||
cache[key] = entry
|
||||
table.insert(items, entry)
|
||||
end
|
||||
entry.count = entry.count + v.count
|
||||
throttle()
|
||||
end
|
||||
itemDB:flush()
|
||||
end
|
||||
end)
|
||||
if s then
|
||||
if not Util.empty(items) then
|
||||
self.cache = cache
|
||||
return items
|
||||
end
|
||||
else
|
||||
_G._syslog(m)
|
||||
end
|
||||
local entry = cache[key]
|
||||
if not entry then
|
||||
entry = itemDB:get(v) or itemDB:add(v)
|
||||
entry = Util.shallowCopy(entry)
|
||||
entry.count = 0
|
||||
cache[key] = entry
|
||||
table.insert(items, entry)
|
||||
end
|
||||
entry.count = entry.count + v.count
|
||||
throttle()
|
||||
end
|
||||
itemDB:flush()
|
||||
end
|
||||
end)
|
||||
if s then
|
||||
if not Util.empty(items) then
|
||||
self.cache = cache
|
||||
return items
|
||||
end
|
||||
else
|
||||
_G._syslog(m)
|
||||
end
|
||||
end
|
||||
|
||||
function ChestAdapter:getItemInfo(item)
|
||||
if not self.cache then
|
||||
self:listItems()
|
||||
end
|
||||
local key = table.concat({ item.name, item.damage, item.nbtHash }, ':')
|
||||
return self.cache[key]
|
||||
if not self.cache then
|
||||
self:listItems()
|
||||
end
|
||||
local key = table.concat({ item.name, item.damage, item.nbtHash }, ':')
|
||||
return self.cache[key]
|
||||
end
|
||||
|
||||
function ChestAdapter:provide(item, qty, slot, direction)
|
||||
pcall(function()
|
||||
for key,stack in Util.rpairs(self.getAllStacks(false)) do
|
||||
if stack.id == item.name and
|
||||
(not item.damage or stack.dmg == item.damage) and
|
||||
(not item.nbtHash or stack.nbt_hash == item.nbtHash) then
|
||||
local amount = math.min(qty, stack.qty)
|
||||
if amount > 0 then
|
||||
self.pushItemIntoSlot(direction or self.direction, key, amount, slot)
|
||||
end
|
||||
qty = qty - amount
|
||||
if qty <= 0 then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
pcall(function()
|
||||
for key,stack in Util.rpairs(self.getAllStacks(false)) do
|
||||
if stack.id == item.name and
|
||||
(not item.damage or stack.dmg == item.damage) and
|
||||
(not item.nbtHash or stack.nbt_hash == item.nbtHash) then
|
||||
local amount = math.min(qty, stack.qty)
|
||||
if amount > 0 then
|
||||
self.pushItemIntoSlot(direction or self.direction, key, amount, slot)
|
||||
end
|
||||
qty = qty - amount
|
||||
if qty <= 0 then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function ChestAdapter:extract(slot, qty, toSlot)
|
||||
if toSlot then
|
||||
self.pushItemIntoSlot(self.direction, slot, qty, toSlot)
|
||||
else
|
||||
self.pushItem(self.direction, slot, qty)
|
||||
end
|
||||
if toSlot then
|
||||
self.pushItemIntoSlot(self.direction, slot, qty, toSlot)
|
||||
else
|
||||
self.pushItem(self.direction, slot, qty)
|
||||
end
|
||||
end
|
||||
|
||||
function ChestAdapter:insert(slot, qty, toSlot)
|
||||
-- toSlot not tested ...
|
||||
local s, m = pcall(self.pullItem, self.direction, slot, qty, toSlot)
|
||||
if not s and m then
|
||||
os.sleep(1)
|
||||
pcall(self.pullItem, self.direction, slot, qty, toSlot)
|
||||
end
|
||||
-- toSlot not tested ...
|
||||
local s, m = pcall(self.pullItem, self.direction, slot, qty, toSlot)
|
||||
if not s and m then
|
||||
os.sleep(1)
|
||||
pcall(self.pullItem, self.direction, slot, qty, toSlot)
|
||||
end
|
||||
end
|
||||
|
||||
return ChestAdapter
|
||||
|
||||
@@ -6,159 +6,159 @@ local Peripheral = require('peripheral')
|
||||
local ChestAdapter = class()
|
||||
|
||||
function ChestAdapter:init(args)
|
||||
local defaults = {
|
||||
name = 'chest',
|
||||
adapter = 'ChestAdapter18'
|
||||
}
|
||||
Util.merge(self, defaults)
|
||||
Util.merge(self, args)
|
||||
local defaults = {
|
||||
name = 'chest',
|
||||
adapter = 'ChestAdapter18'
|
||||
}
|
||||
Util.merge(self, defaults)
|
||||
Util.merge(self, args)
|
||||
|
||||
local chest
|
||||
if not self.side then
|
||||
chest = Peripheral.getByMethod('list') or
|
||||
Peripheral.getByMethod('listAvailableItems')
|
||||
else
|
||||
chest = Peripheral.getBySide(self.side)
|
||||
if chest and not chest.list and not chest.listAvailableItems then
|
||||
chest = nil
|
||||
end
|
||||
end
|
||||
local chest
|
||||
if not self.side then
|
||||
chest = Peripheral.getByMethod('list') or
|
||||
Peripheral.getByMethod('listAvailableItems')
|
||||
else
|
||||
chest = Peripheral.getBySide(self.side)
|
||||
if chest and not chest.list and not chest.listAvailableItems then
|
||||
chest = nil
|
||||
end
|
||||
end
|
||||
|
||||
if chest then
|
||||
Util.merge(self, chest)
|
||||
if chest then
|
||||
Util.merge(self, chest)
|
||||
|
||||
if chest.listAvailableItems then
|
||||
self.list = chest.listAvailableItems
|
||||
end
|
||||
end
|
||||
if chest.listAvailableItems then
|
||||
self.list = chest.listAvailableItems
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ChestAdapter:isValid()
|
||||
return not not self.list
|
||||
return not not self.list
|
||||
end
|
||||
|
||||
-- handle both AE/RS and generic inventory
|
||||
function ChestAdapter:getItemDetails(index, item)
|
||||
if self.getItemMeta then
|
||||
local s, detail = pcall(self.getItemMeta, index)
|
||||
if not s or not detail or detail.name ~= item.name then
|
||||
return
|
||||
end
|
||||
return detail
|
||||
else
|
||||
local detail = self.findItems(item)
|
||||
if detail and #detail > 0 then
|
||||
return detail[1].getMetadata()
|
||||
end
|
||||
end
|
||||
if self.getItemMeta then
|
||||
local s, detail = pcall(self.getItemMeta, index)
|
||||
if not s or not detail or detail.name ~= item.name then
|
||||
return
|
||||
end
|
||||
return detail
|
||||
else
|
||||
local detail = self.findItems(item)
|
||||
if detail and #detail > 0 then
|
||||
return detail[1].getMetadata()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ChestAdapter:getCachedItemDetails(item, k)
|
||||
local cached = itemDB:get(item)
|
||||
if cached then
|
||||
return cached
|
||||
end
|
||||
local cached = itemDB:get(item)
|
||||
if cached then
|
||||
return cached
|
||||
end
|
||||
|
||||
local detail = self:getItemDetails(k, item)
|
||||
if detail then
|
||||
return itemDB:add(detail)
|
||||
end
|
||||
local detail = self:getItemDetails(k, item)
|
||||
if detail then
|
||||
return itemDB:add(detail)
|
||||
end
|
||||
end
|
||||
|
||||
function ChestAdapter:refresh(throttle)
|
||||
return self:listItems(throttle)
|
||||
return self:listItems(throttle)
|
||||
end
|
||||
|
||||
-- provide a consolidated list of items
|
||||
function ChestAdapter:listItems(throttle)
|
||||
for _ = 1, 5 do
|
||||
local list = self:listItemsInternal(throttle)
|
||||
if list then
|
||||
return list
|
||||
end
|
||||
end
|
||||
error('Error accessing inventory: ' .. self.direction)
|
||||
for _ = 1, 5 do
|
||||
local list = self:listItemsInternal(throttle)
|
||||
if list then
|
||||
return list
|
||||
end
|
||||
end
|
||||
error('Error accessing inventory: ' .. self.direction)
|
||||
end
|
||||
|
||||
function ChestAdapter:listItemsInternal(throttle)
|
||||
local cache = { }
|
||||
local items = { }
|
||||
throttle = throttle or Util.throttle()
|
||||
local cache = { }
|
||||
local items = { }
|
||||
throttle = throttle or Util.throttle()
|
||||
|
||||
for k,v in pairs(self.list()) do
|
||||
if v.count > 0 then
|
||||
local key = table.concat({ v.name, v.damage, v.nbtHash }, ':')
|
||||
for k,v in pairs(self.list()) do
|
||||
if v.count > 0 then
|
||||
local key = table.concat({ v.name, v.damage, v.nbtHash }, ':')
|
||||
|
||||
local entry = cache[key]
|
||||
if not entry then
|
||||
entry = self:getCachedItemDetails(v, k)
|
||||
if not entry then
|
||||
return -- Inventory has changed
|
||||
end
|
||||
entry = Util.shallowCopy(entry)
|
||||
entry.count = 0
|
||||
cache[key] = entry
|
||||
table.insert(items, entry)
|
||||
end
|
||||
local entry = cache[key]
|
||||
if not entry then
|
||||
entry = self:getCachedItemDetails(v, k)
|
||||
if not entry then
|
||||
return -- Inventory has changed
|
||||
end
|
||||
entry = Util.shallowCopy(entry)
|
||||
entry.count = 0
|
||||
cache[key] = entry
|
||||
table.insert(items, entry)
|
||||
end
|
||||
|
||||
if entry then
|
||||
entry.count = entry.count + v.count
|
||||
end
|
||||
throttle()
|
||||
end
|
||||
end
|
||||
itemDB:flush()
|
||||
if entry then
|
||||
entry.count = entry.count + v.count
|
||||
end
|
||||
throttle()
|
||||
end
|
||||
end
|
||||
itemDB:flush()
|
||||
|
||||
self.cache = cache
|
||||
return items
|
||||
self.cache = cache
|
||||
return items
|
||||
end
|
||||
|
||||
function ChestAdapter:getItemInfo(item)
|
||||
if not self.cache then
|
||||
self:listItems()
|
||||
end
|
||||
local key = table.concat({ item.name, item.damage, item.nbtHash }, ':')
|
||||
local items = self.cache or { }
|
||||
return items[key]
|
||||
if not self.cache then
|
||||
self:listItems()
|
||||
end
|
||||
local key = table.concat({ item.name, item.damage, item.nbtHash }, ':')
|
||||
local items = self.cache or { }
|
||||
return items[key]
|
||||
end
|
||||
|
||||
function ChestAdapter:getPercentUsed()
|
||||
if self.cache and self.getDrawerCount then
|
||||
return math.floor(Util.size(self.cache) / self.getDrawerCount() * 100)
|
||||
end
|
||||
return 0
|
||||
if self.cache and self.getDrawerCount then
|
||||
return math.floor(Util.size(self.cache) / self.getDrawerCount() * 100)
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
function ChestAdapter:provide(item, qty, slot, direction)
|
||||
local total = 0
|
||||
local total = 0
|
||||
|
||||
local _, m = pcall(function()
|
||||
local stacks = self.list()
|
||||
for key,stack in Util.rpairs(stacks) do
|
||||
if stack.name == item.name and
|
||||
stack.damage == item.damage and
|
||||
stack.nbtHash == item.nbtHash then
|
||||
local amount = math.min(qty, stack.count)
|
||||
if amount > 0 then
|
||||
amount = self.pushItems(direction or self.direction, key, amount, slot)
|
||||
end
|
||||
qty = qty - amount
|
||||
total = total + amount
|
||||
if qty <= 0 then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
return total, m
|
||||
local _, m = pcall(function()
|
||||
local stacks = self.list()
|
||||
for key,stack in Util.rpairs(stacks) do
|
||||
if stack.name == item.name and
|
||||
stack.damage == item.damage and
|
||||
stack.nbtHash == item.nbtHash then
|
||||
local amount = math.min(qty, stack.count)
|
||||
if amount > 0 then
|
||||
amount = self.pushItems(direction or self.direction, key, amount, slot)
|
||||
end
|
||||
qty = qty - amount
|
||||
total = total + amount
|
||||
if qty <= 0 then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
return total, m
|
||||
end
|
||||
|
||||
function ChestAdapter:extract(slot, qty, toSlot, direction)
|
||||
return self.pushItems(direction or self.direction, slot, qty, toSlot)
|
||||
return self.pushItems(direction or self.direction, slot, qty, toSlot)
|
||||
end
|
||||
|
||||
function ChestAdapter:insert(slot, qty, toSlot, direction)
|
||||
return self.pullItems(direction or self.direction, slot, qty, toSlot)
|
||||
return self.pullItems(direction or self.direction, slot, qty, toSlot)
|
||||
end
|
||||
|
||||
return ChestAdapter
|
||||
|
||||
@@ -6,277 +6,277 @@ local Util = require('util')
|
||||
local itemDB = TableDB({ fileName = 'usr/config/items.db' })
|
||||
|
||||
local function safeString(text)
|
||||
local val = text:byte(1)
|
||||
local val = text:byte(1)
|
||||
|
||||
if val < 32 or val > 128 then
|
||||
if val < 32 or val > 128 then
|
||||
|
||||
local newText = { }
|
||||
local skip = 0
|
||||
for i = 1, #text do
|
||||
val = text:byte(i)
|
||||
if val == 167 then
|
||||
skip = 2
|
||||
end
|
||||
if skip > 0 then
|
||||
skip = skip - 1
|
||||
else
|
||||
if val >= 32 and val <= 128 then
|
||||
newText[#newText + 1] = val
|
||||
end
|
||||
end
|
||||
end
|
||||
return string.char(unpack(newText))
|
||||
end
|
||||
local newText = { }
|
||||
local skip = 0
|
||||
for i = 1, #text do
|
||||
val = text:byte(i)
|
||||
if val == 167 then
|
||||
skip = 2
|
||||
end
|
||||
if skip > 0 then
|
||||
skip = skip - 1
|
||||
else
|
||||
if val >= 32 and val <= 128 then
|
||||
newText[#newText + 1] = val
|
||||
end
|
||||
end
|
||||
end
|
||||
return string.char(unpack(newText))
|
||||
end
|
||||
|
||||
return text
|
||||
return text
|
||||
end
|
||||
|
||||
function itemDB:makeKey(item)
|
||||
if not item then error('itemDB:makeKey: item is required', 2) end
|
||||
return table.concat({ item.name, item.damage or '*', item.nbtHash }, ':')
|
||||
if not item then error('itemDB:makeKey: item is required', 2) end
|
||||
return table.concat({ item.name, item.damage or '*', item.nbtHash }, ':')
|
||||
end
|
||||
|
||||
function itemDB:splitKey(key, item)
|
||||
item = item or { }
|
||||
item = item or { }
|
||||
|
||||
local t = Util.split(key, '(.-):')
|
||||
if #t[#t] > 8 then
|
||||
item.nbtHash = table.remove(t)
|
||||
end
|
||||
local damage = table.remove(t)
|
||||
if damage ~= '*' then
|
||||
item.damage = tonumber(damage)
|
||||
end
|
||||
item.name = table.concat(t, ':')
|
||||
local t = Util.split(key, '(.-):')
|
||||
if #t[#t] > 8 then
|
||||
item.nbtHash = table.remove(t)
|
||||
end
|
||||
local damage = table.remove(t)
|
||||
if damage ~= '*' then
|
||||
item.damage = tonumber(damage)
|
||||
end
|
||||
item.name = table.concat(t, ':')
|
||||
|
||||
return item
|
||||
return item
|
||||
end
|
||||
|
||||
function itemDB:get(key, populateFn)
|
||||
if not key then error('itemDB:get: key is required', 2) end
|
||||
if type(key) == 'string' then
|
||||
key = self:splitKey(key)
|
||||
else
|
||||
key = Util.shallowCopy(key)
|
||||
end
|
||||
if not key then error('itemDB:get: key is required', 2) end
|
||||
if type(key) == 'string' then
|
||||
key = self:splitKey(key)
|
||||
else
|
||||
key = Util.shallowCopy(key)
|
||||
end
|
||||
|
||||
local item = self:_get(key)
|
||||
if not item and populateFn then
|
||||
item = populateFn()
|
||||
if item then
|
||||
item = self:add(item)
|
||||
end
|
||||
end
|
||||
local item = self:_get(key)
|
||||
if not item and populateFn then
|
||||
item = populateFn()
|
||||
if item then
|
||||
item = self:add(item)
|
||||
end
|
||||
end
|
||||
|
||||
return item and Util.merge(key, item)
|
||||
return item and Util.merge(key, item)
|
||||
end
|
||||
|
||||
function itemDB:_get(key)
|
||||
if not key then error('itemDB:get: key is required', 2) end
|
||||
if type(key) == 'string' then
|
||||
key = self:splitKey(key)
|
||||
end
|
||||
if not key then error('itemDB:get: key is required', 2) end
|
||||
if type(key) == 'string' then
|
||||
key = self:splitKey(key)
|
||||
end
|
||||
|
||||
local item = TableDB.get(self, self:makeKey(key))
|
||||
if item then
|
||||
return item
|
||||
end
|
||||
local item = TableDB.get(self, self:makeKey(key))
|
||||
if item then
|
||||
return item
|
||||
end
|
||||
|
||||
-- try finding an item that has damage values
|
||||
if type(key.damage) == 'number' then
|
||||
item = TableDB.get(self, self:makeKey({ name = key.name, nbtHash = key.nbtHash }))
|
||||
if item and item.maxDamage then
|
||||
item = Util.shallowCopy(item)
|
||||
item.damage = key.damage
|
||||
if item.maxDamage > 0 and type(item.damage) == 'number' and item.damage > 0 then
|
||||
item.displayName = string.format('%s (damage: %s)', item.displayName, item.damage)
|
||||
end
|
||||
return item
|
||||
end
|
||||
else
|
||||
for k,item in pairs(self.data) do
|
||||
if key.name == item.name and
|
||||
key.nbtHash == key.nbtHash and
|
||||
item.maxDamage > 0 then
|
||||
item = Util.shallowCopy(item)
|
||||
item.nbtHash = key.nbtHash
|
||||
return item
|
||||
end
|
||||
end
|
||||
end
|
||||
-- try finding an item that has damage values
|
||||
if type(key.damage) == 'number' then
|
||||
item = TableDB.get(self, self:makeKey({ name = key.name, nbtHash = key.nbtHash }))
|
||||
if item and item.maxDamage then
|
||||
item = Util.shallowCopy(item)
|
||||
item.damage = key.damage
|
||||
if item.maxDamage > 0 and type(item.damage) == 'number' and item.damage > 0 then
|
||||
item.displayName = string.format('%s (damage: %s)', item.displayName, item.damage)
|
||||
end
|
||||
return item
|
||||
end
|
||||
else
|
||||
for k,item in pairs(self.data) do
|
||||
if key.name == item.name and
|
||||
key.nbtHash == key.nbtHash and
|
||||
item.maxDamage > 0 then
|
||||
item = Util.shallowCopy(item)
|
||||
item.nbtHash = key.nbtHash
|
||||
return item
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if key.nbtHash then
|
||||
item = self:get({ name = key.name, damage = key.damage })
|
||||
if key.nbtHash then
|
||||
item = self:get({ name = key.name, damage = key.damage })
|
||||
|
||||
if item and item.ignoreNBT then
|
||||
item = Util.shallowCopy(item)
|
||||
item.nbtHash = key.nbtHash
|
||||
item.damage = key.damage
|
||||
return item
|
||||
end
|
||||
end
|
||||
if item and item.ignoreNBT then
|
||||
item = Util.shallowCopy(item)
|
||||
item.nbtHash = key.nbtHash
|
||||
item.damage = key.damage
|
||||
return item
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function formatTime(t)
|
||||
local m = math.floor(t/60)
|
||||
local s = t % 60
|
||||
if s < 10 then
|
||||
s = '0' .. s
|
||||
end
|
||||
local m = math.floor(t/60)
|
||||
local s = t % 60
|
||||
if s < 10 then
|
||||
s = '0' .. s
|
||||
end
|
||||
|
||||
return m .. ':' .. s
|
||||
return m .. ':' .. s
|
||||
end
|
||||
|
||||
--[[
|
||||
If the base item contains an NBT hash, then the NBT hash uniquely
|
||||
identifies this item.
|
||||
If the base item contains an NBT hash, then the NBT hash uniquely
|
||||
identifies this item.
|
||||
]]--
|
||||
function itemDB:add(baseItem)
|
||||
local nItem = {
|
||||
name = baseItem.name,
|
||||
damage = baseItem.damage,
|
||||
nbtHash = baseItem.nbtHash,
|
||||
}
|
||||
local nItem = {
|
||||
name = baseItem.name,
|
||||
damage = baseItem.damage,
|
||||
nbtHash = baseItem.nbtHash,
|
||||
}
|
||||
-- if detail.maxDamage > 0 then
|
||||
-- nItem.damage = '*'
|
||||
-- end
|
||||
|
||||
nItem.displayName = safeString(baseItem.displayName)
|
||||
nItem.maxCount = baseItem.maxCount
|
||||
nItem.maxDamage = baseItem.maxDamage
|
||||
nItem.displayName = safeString(baseItem.displayName)
|
||||
nItem.maxCount = baseItem.maxCount
|
||||
nItem.maxDamage = baseItem.maxDamage
|
||||
|
||||
-- enchanted items
|
||||
if baseItem.enchantments then
|
||||
if nItem.name == 'minecraft:enchanted_book' then
|
||||
nItem.displayName = 'Book: '
|
||||
else
|
||||
nItem.displayName = nItem.displayName .. ': '
|
||||
end
|
||||
for k, v in ipairs(baseItem.enchantments) do
|
||||
if k > 1 then
|
||||
nItem.displayName = nItem.displayName .. ', '
|
||||
end
|
||||
nItem.displayName = nItem.displayName .. v.fullName
|
||||
end
|
||||
-- enchanted items
|
||||
if baseItem.enchantments then
|
||||
if nItem.name == 'minecraft:enchanted_book' then
|
||||
nItem.displayName = 'Book: '
|
||||
else
|
||||
nItem.displayName = nItem.displayName .. ': '
|
||||
end
|
||||
for k, v in ipairs(baseItem.enchantments) do
|
||||
if k > 1 then
|
||||
nItem.displayName = nItem.displayName .. ', '
|
||||
end
|
||||
nItem.displayName = nItem.displayName .. v.fullName
|
||||
end
|
||||
|
||||
-- turtles / computers / etc
|
||||
elseif baseItem.computer then
|
||||
-- a turtle's NBT is updated constantly
|
||||
-- update the cache with the new NBT
|
||||
if baseItem.computer.id then
|
||||
Map.removeMatches(self.data, { name = nItem.name, displayName = nItem.displayName })
|
||||
end
|
||||
nItem.displayName = baseItem.computer.label or baseItem.displayName
|
||||
-- turtles / computers / etc
|
||||
elseif baseItem.computer then
|
||||
-- a turtle's NBT is updated constantly
|
||||
-- update the cache with the new NBT
|
||||
if baseItem.computer.id then
|
||||
Map.removeMatches(self.data, { name = nItem.name, displayName = nItem.displayName })
|
||||
end
|
||||
nItem.displayName = baseItem.computer.label or baseItem.displayName
|
||||
|
||||
-- disks
|
||||
elseif baseItem.media then
|
||||
-- don't ignore nbt... as disks can be labeled
|
||||
if baseItem.media.recordTitle then
|
||||
nItem.displayName = nItem.displayName .. ': ' .. baseItem.media.recordTitle
|
||||
end
|
||||
-- disks
|
||||
elseif baseItem.media then
|
||||
-- don't ignore nbt... as disks can be labeled
|
||||
if baseItem.media.recordTitle then
|
||||
nItem.displayName = nItem.displayName .. ': ' .. baseItem.media.recordTitle
|
||||
end
|
||||
|
||||
-- potions
|
||||
elseif nItem.name == 'minecraft:potion' or nItem.name == 'minecraft:lingering_potion' then
|
||||
if baseItem.effects then
|
||||
local effect = baseItem.effects[1]
|
||||
if effect.amplifier == 1 then
|
||||
nItem.displayName = nItem.displayName .. ' II'
|
||||
end
|
||||
if effect.duration and effect.duration > 0 then
|
||||
nItem.displayName = string.format('%s (%s)', nItem.displayName, formatTime(effect.duration))
|
||||
end
|
||||
end
|
||||
-- potions
|
||||
elseif nItem.name == 'minecraft:potion' or nItem.name == 'minecraft:lingering_potion' then
|
||||
if baseItem.effects then
|
||||
local effect = baseItem.effects[1]
|
||||
if effect.amplifier == 1 then
|
||||
nItem.displayName = nItem.displayName .. ' II'
|
||||
end
|
||||
if effect.duration and effect.duration > 0 then
|
||||
nItem.displayName = string.format('%s (%s)', nItem.displayName, formatTime(effect.duration))
|
||||
end
|
||||
end
|
||||
|
||||
else
|
||||
for k,item in pairs(self.data) do
|
||||
if nItem.name == item.name and
|
||||
nItem.displayName == item.displayName then
|
||||
else
|
||||
for k,item in pairs(self.data) do
|
||||
if nItem.name == item.name and
|
||||
nItem.displayName == item.displayName then
|
||||
|
||||
if nItem.nbtHash ~= item.nbtHash and nItem.damage ~= item.damage then
|
||||
nItem.damage = '*'
|
||||
nItem.nbtHash = nil
|
||||
nItem.ignoreNBT = true
|
||||
self.data[k] = nil
|
||||
break
|
||||
elseif nItem.damage ~= item.damage then
|
||||
nItem.damage = '*'
|
||||
self.data[k] = nil
|
||||
break
|
||||
elseif nItem.nbtHash ~= item.nbtHash then
|
||||
nItem.nbtHash = nil
|
||||
nItem.ignoreNBT = true
|
||||
self.data[k] = nil
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if nItem.nbtHash ~= item.nbtHash and nItem.damage ~= item.damage then
|
||||
nItem.damage = '*'
|
||||
nItem.nbtHash = nil
|
||||
nItem.ignoreNBT = true
|
||||
self.data[k] = nil
|
||||
break
|
||||
elseif nItem.damage ~= item.damage then
|
||||
nItem.damage = '*'
|
||||
self.data[k] = nil
|
||||
break
|
||||
elseif nItem.nbtHash ~= item.nbtHash then
|
||||
nItem.nbtHash = nil
|
||||
nItem.ignoreNBT = true
|
||||
self.data[k] = nil
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
TableDB.add(self, self:makeKey(nItem), nItem)
|
||||
nItem = Util.shallowCopy(nItem)
|
||||
nItem.damage = baseItem.damage
|
||||
nItem.nbtHash = baseItem.nbtHash
|
||||
TableDB.add(self, self:makeKey(nItem), nItem)
|
||||
nItem = Util.shallowCopy(nItem)
|
||||
nItem.damage = baseItem.damage
|
||||
nItem.nbtHash = baseItem.nbtHash
|
||||
|
||||
return nItem
|
||||
return nItem
|
||||
end
|
||||
|
||||
-- Accepts: "minecraft:stick:0" or { name = 'minecraft:stick', damage = 0 }
|
||||
function itemDB:getName(item)
|
||||
if type(item) == 'string' then
|
||||
item = self:splitKey(item)
|
||||
end
|
||||
if type(item) == 'string' then
|
||||
item = self:splitKey(item)
|
||||
end
|
||||
|
||||
local detail = self:get(item)
|
||||
if detail then
|
||||
return detail.displayName
|
||||
end
|
||||
local detail = self:get(item)
|
||||
if detail then
|
||||
return detail.displayName
|
||||
end
|
||||
|
||||
-- fallback to nameDB
|
||||
local strId = self:makeKey(item)
|
||||
local name = nameDB.data[strId]
|
||||
if not name and not item.damage then
|
||||
name = nameDB.data[self:makeKey({ name = item.name, damage = 0, nbtHash = item.nbtHash })]
|
||||
end
|
||||
return name or strId
|
||||
-- fallback to nameDB
|
||||
local strId = self:makeKey(item)
|
||||
local name = nameDB.data[strId]
|
||||
if not name and not item.damage then
|
||||
name = nameDB.data[self:makeKey({ name = item.name, damage = 0, nbtHash = item.nbtHash })]
|
||||
end
|
||||
return name or strId
|
||||
end
|
||||
|
||||
function itemDB:getMaxCount(item)
|
||||
local detail = self:get(item)
|
||||
return detail and detail.maxCount or 64
|
||||
local detail = self:get(item)
|
||||
return detail and detail.maxCount or 64
|
||||
end
|
||||
|
||||
function itemDB:load()
|
||||
TableDB.load(self)
|
||||
TableDB.load(self)
|
||||
|
||||
for key,item in pairs(self.data) do
|
||||
self:splitKey(key, item)
|
||||
item.maxDamage = item.maxDamage or 0
|
||||
item.maxCount = item.maxCount or 64
|
||||
end
|
||||
for key,item in pairs(self.data) do
|
||||
self:splitKey(key, item)
|
||||
item.maxDamage = item.maxDamage or 0
|
||||
item.maxCount = item.maxCount or 64
|
||||
end
|
||||
end
|
||||
|
||||
function itemDB:flush()
|
||||
if self.dirty then
|
||||
if self.dirty then
|
||||
|
||||
local t = { }
|
||||
for k,v in pairs(self.data) do
|
||||
v = Util.shallowCopy(v)
|
||||
v.name = nil
|
||||
v.damage = nil
|
||||
v.nbtHash = nil
|
||||
local t = { }
|
||||
for k,v in pairs(self.data) do
|
||||
v = Util.shallowCopy(v)
|
||||
v.name = nil
|
||||
v.damage = nil
|
||||
v.nbtHash = nil
|
||||
v.count = nil -- wipe out previously saved counts - temporary
|
||||
if v.maxDamage == 0 then
|
||||
v.maxDamage = nil
|
||||
end
|
||||
if v.maxCount == 64 then
|
||||
v.maxCount = nil
|
||||
end
|
||||
t[k] = v
|
||||
end
|
||||
if v.maxDamage == 0 then
|
||||
v.maxDamage = nil
|
||||
end
|
||||
if v.maxCount == 64 then
|
||||
v.maxCount = nil
|
||||
end
|
||||
t[k] = v
|
||||
end
|
||||
|
||||
Util.writeTable(self.fileName, t)
|
||||
self.dirty = false
|
||||
end
|
||||
Util.writeTable(self.fileName, t)
|
||||
self.dirty = false
|
||||
end
|
||||
end
|
||||
|
||||
itemDB:load()
|
||||
|
||||
@@ -6,249 +6,249 @@ local Util = require('util')
|
||||
local os = _G.os
|
||||
|
||||
local convertNames = {
|
||||
name = 'id',
|
||||
damage = 'dmg',
|
||||
maxCount = 'max_size',
|
||||
count = 'qty',
|
||||
displayName = 'display_name',
|
||||
maxDamage = 'max_dmg',
|
||||
nbtHash = 'nbt_hash',
|
||||
name = 'id',
|
||||
damage = 'dmg',
|
||||
maxCount = 'max_size',
|
||||
count = 'qty',
|
||||
displayName = 'display_name',
|
||||
maxDamage = 'max_dmg',
|
||||
nbtHash = 'nbt_hash',
|
||||
}
|
||||
|
||||
-- Strip off color prefix
|
||||
local function safeString(text)
|
||||
|
||||
local val = text:byte(1)
|
||||
local val = text:byte(1)
|
||||
|
||||
if val < 32 or val > 128 then
|
||||
if val < 32 or val > 128 then
|
||||
|
||||
local newText = {}
|
||||
for i = 4, #text do
|
||||
val = text:byte(i)
|
||||
newText[i - 3] = (val > 31 and val < 127) and val or 63
|
||||
end
|
||||
return string.char(unpack(newText))
|
||||
end
|
||||
local newText = {}
|
||||
for i = 4, #text do
|
||||
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
|
||||
return text
|
||||
end
|
||||
|
||||
local function convertItem(item)
|
||||
for k,v in pairs(convertNames) do
|
||||
item[k] = item[v]
|
||||
item[v] = nil
|
||||
end
|
||||
item.displayName = safeString(item.displayName)
|
||||
for k,v in pairs(convertNames) do
|
||||
item[k] = item[v]
|
||||
item[v] = nil
|
||||
end
|
||||
item.displayName = safeString(item.displayName)
|
||||
end
|
||||
|
||||
local MEAdapter = class()
|
||||
|
||||
function MEAdapter:init(args)
|
||||
local defaults = {
|
||||
items = { },
|
||||
name = 'ME',
|
||||
jobList = { },
|
||||
}
|
||||
Util.merge(self, defaults)
|
||||
Util.merge(self, args)
|
||||
local defaults = {
|
||||
items = { },
|
||||
name = 'ME',
|
||||
jobList = { },
|
||||
}
|
||||
Util.merge(self, defaults)
|
||||
Util.merge(self, args)
|
||||
|
||||
local chest
|
||||
local chest
|
||||
|
||||
if not self.side then
|
||||
chest = Peripheral.getByMethod('getAvailableItems')
|
||||
else
|
||||
chest = Peripheral.getBySide(self.side)
|
||||
if chest and not chest.getAvailableItems then
|
||||
chest = nil
|
||||
end
|
||||
end
|
||||
if not self.side then
|
||||
chest = Peripheral.getByMethod('getAvailableItems')
|
||||
else
|
||||
chest = Peripheral.getBySide(self.side)
|
||||
if chest and not chest.getAvailableItems then
|
||||
chest = nil
|
||||
end
|
||||
end
|
||||
|
||||
if chest then
|
||||
Util.merge(self, chest)
|
||||
end
|
||||
if chest then
|
||||
Util.merge(self, chest)
|
||||
end
|
||||
end
|
||||
|
||||
function MEAdapter:isValid()
|
||||
return self.getAvailableItems and self.getAvailableItems()
|
||||
return self.getAvailableItems and self.getAvailableItems()
|
||||
end
|
||||
|
||||
function MEAdapter:refresh()
|
||||
self.items = nil
|
||||
local hasItems, failed
|
||||
self.items = nil
|
||||
local hasItems, failed
|
||||
|
||||
local s, m = pcall(function()
|
||||
self.items = self.getAvailableItems('all')
|
||||
for _,v in pairs(self.items) do
|
||||
Util.merge(v, v.item)
|
||||
convertItem(v)
|
||||
local s, m = pcall(function()
|
||||
self.items = self.getAvailableItems('all')
|
||||
for _,v in pairs(self.items) do
|
||||
Util.merge(v, v.item)
|
||||
convertItem(v)
|
||||
|
||||
-- if power has been interrupted, the list will still be returned
|
||||
-- but all items will have a 0 quantity
|
||||
-- ensure that the list is valid
|
||||
if not hasItems then
|
||||
hasItems = v.count > 0
|
||||
end
|
||||
-- if power has been interrupted, the list will still be returned
|
||||
-- but all items will have a 0 quantity
|
||||
-- ensure that the list is valid
|
||||
if not hasItems then
|
||||
hasItems = v.count > 0
|
||||
end
|
||||
|
||||
if not v.fingerprint then
|
||||
failed = true
|
||||
break
|
||||
end
|
||||
if not v.fingerprint then
|
||||
failed = true
|
||||
break
|
||||
end
|
||||
|
||||
if not itemDB:get(v) then
|
||||
itemDB:add(v, v)
|
||||
end
|
||||
end
|
||||
end)
|
||||
itemDB:flush()
|
||||
if not itemDB:get(v) then
|
||||
itemDB:add(v, v)
|
||||
end
|
||||
end
|
||||
end)
|
||||
itemDB:flush()
|
||||
|
||||
if not s and m then
|
||||
_G._syslog(m)
|
||||
end
|
||||
if not s and m then
|
||||
_G._syslog(m)
|
||||
end
|
||||
|
||||
if s and not failed and hasItems and self.items and not Util.empty(self.items) then
|
||||
return self.items
|
||||
end
|
||||
self.items = nil
|
||||
if s and not failed and hasItems and self.items and not Util.empty(self.items) then
|
||||
return self.items
|
||||
end
|
||||
self.items = nil
|
||||
end
|
||||
|
||||
function MEAdapter:listItems()
|
||||
self:refresh()
|
||||
return self.items
|
||||
self:refresh()
|
||||
return self.items
|
||||
end
|
||||
|
||||
function MEAdapter:getItemInfo(item)
|
||||
for _,i in pairs(self.items) do
|
||||
if item.name == i.name and
|
||||
item.damage == i.damage and
|
||||
item.nbtHash == i.nbtHash then
|
||||
return i
|
||||
end
|
||||
end
|
||||
for _,i in pairs(self.items) do
|
||||
if item.name == i.name and
|
||||
item.damage == i.damage and
|
||||
item.nbtHash == i.nbtHash then
|
||||
return i
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function MEAdapter:isCPUAvailable()
|
||||
local cpus = self.getCraftingCPUs() or { }
|
||||
local available = false
|
||||
local cpus = self.getCraftingCPUs() or { }
|
||||
local available = false
|
||||
|
||||
for cpu,v in pairs(cpus) do
|
||||
if not v.busy then
|
||||
available = true
|
||||
elseif not self.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
|
||||
for cpu,v in pairs(cpus) do
|
||||
if not v.busy then
|
||||
available = true
|
||||
elseif not self.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 MEAdapter:craft(item, count)
|
||||
if not self:isCPUAvailable() then
|
||||
return false
|
||||
end
|
||||
if not self:isCPUAvailable() then
|
||||
return false
|
||||
end
|
||||
|
||||
self:refresh()
|
||||
self:refresh()
|
||||
|
||||
item = self:getItemInfo(item)
|
||||
if item and item.is_craftable then
|
||||
item = self:getItemInfo(item)
|
||||
if item and item.is_craftable then
|
||||
|
||||
local cpus = self.getCraftingCPUs() or { }
|
||||
for cpu,v in pairs(cpus) do
|
||||
if not v.busy then
|
||||
self.requestCrafting({
|
||||
id = item.name,
|
||||
dmg = item.damage,
|
||||
nbt_hash = item.nbtHash,
|
||||
},
|
||||
count or 1,
|
||||
v.name -- CPUs must be named ! use anvil
|
||||
)
|
||||
local cpus = self.getCraftingCPUs() or { }
|
||||
for cpu,v in pairs(cpus) do
|
||||
if not v.busy then
|
||||
self.requestCrafting({
|
||||
id = item.name,
|
||||
dmg = item.damage,
|
||||
nbt_hash = item.nbtHash,
|
||||
},
|
||||
count or 1,
|
||||
v.name -- CPUs must be named ! use anvil
|
||||
)
|
||||
|
||||
os.sleep(0) -- needed ?
|
||||
cpus = self.getCraftingCPUs() or { }
|
||||
os.sleep(0) -- needed ?
|
||||
cpus = self.getCraftingCPUs() or { }
|
||||
|
||||
if cpus[cpu].busy then
|
||||
self.jobList[cpu] = {
|
||||
name = item.name,
|
||||
damage = item.damage,
|
||||
nbtHash = item.nbtHash,
|
||||
count = count,
|
||||
}
|
||||
return true
|
||||
end
|
||||
break -- only need to try the first available cpu
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
if cpus[cpu].busy then
|
||||
self.jobList[cpu] = {
|
||||
name = item.name,
|
||||
damage = item.damage,
|
||||
nbtHash = item.nbtHash,
|
||||
count = count,
|
||||
}
|
||||
return true
|
||||
end
|
||||
break -- only need to try the first available cpu
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
function MEAdapter:getJobList()
|
||||
local cpus = self.getCraftingCPUs() or { }
|
||||
for cpu,v in pairs(cpus) do
|
||||
if not v.busy then
|
||||
self.jobList[cpu] = nil
|
||||
end
|
||||
end
|
||||
local cpus = self.getCraftingCPUs() or { }
|
||||
for cpu,v in pairs(cpus) do
|
||||
if not v.busy then
|
||||
self.jobList[cpu] = nil
|
||||
end
|
||||
end
|
||||
|
||||
return self.jobList
|
||||
return self.jobList
|
||||
end
|
||||
|
||||
function MEAdapter:isCrafting(item)
|
||||
for _,v in pairs(self:getJobList()) do
|
||||
if v.name == item.name and
|
||||
v.damage == item.damage and
|
||||
v.nbtHash == item.nbtHash then
|
||||
return true
|
||||
end
|
||||
end
|
||||
for _,v in pairs(self:getJobList()) do
|
||||
if v.name == item.name and
|
||||
v.damage == item.damage and
|
||||
v.nbtHash == item.nbtHash then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function MEAdapter:craftItems(items)
|
||||
local cpus = self.getCraftingCPUs() or { }
|
||||
local count = 0
|
||||
local cpus = self.getCraftingCPUs() or { }
|
||||
local count = 0
|
||||
|
||||
for _,cpu in pairs(cpus) do
|
||||
if cpu.busy then
|
||||
return
|
||||
end
|
||||
end
|
||||
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 not self:isCrafting(item) then
|
||||
if self:craft(item, item.count) then
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
for _,item in pairs(items) do
|
||||
if count >= #cpus then
|
||||
break
|
||||
end
|
||||
if not self:isCrafting(item) then
|
||||
if self:craft(item, item.count) then
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function MEAdapter:provide(item, qty, slot, direction)
|
||||
return pcall(function()
|
||||
for _,stack in pairs(self.getAvailableItems('all')) do
|
||||
if stack.item.id == item.name and
|
||||
(not item.damage or stack.item.dmg == item.damage) and
|
||||
(not item.nbtHash or stack.item.nbt_hash == item.nbtHash) then
|
||||
local amount = math.min(qty, stack.item.qty)
|
||||
if amount > 0 then
|
||||
self.exportItem(stack.item, direction or self.direction, amount, slot)
|
||||
end
|
||||
qty = qty - amount
|
||||
if qty <= 0 then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
return pcall(function()
|
||||
for _,stack in pairs(self.getAvailableItems('all')) do
|
||||
if stack.item.id == item.name and
|
||||
(not item.damage or stack.item.dmg == item.damage) and
|
||||
(not item.nbtHash or stack.item.nbt_hash == item.nbtHash) then
|
||||
local amount = math.min(qty, stack.item.qty)
|
||||
if amount > 0 then
|
||||
self.exportItem(stack.item, direction or self.direction, amount, slot)
|
||||
end
|
||||
qty = qty - amount
|
||||
if qty <= 0 then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function MEAdapter:insert(slot, qty, toSlot)
|
||||
local s, m = pcall(self.pullItem, self.direction, slot, qty, toSlot)
|
||||
if not s and m then
|
||||
os.sleep(1)
|
||||
pcall(self.pullItem, self.direction, slot, qty, toSlot)
|
||||
end
|
||||
local s, m = pcall(self.pullItem, self.direction, slot, qty, toSlot)
|
||||
if not s and m then
|
||||
os.sleep(1)
|
||||
pcall(self.pullItem, self.direction, slot, qty, toSlot)
|
||||
end
|
||||
end
|
||||
|
||||
return MEAdapter
|
||||
|
||||
@@ -8,83 +8,83 @@ local MEAdapter = class(RSAdapter)
|
||||
local DEVICE_TYPE = 'appliedenergistics2:interface'
|
||||
|
||||
function MEAdapter:init(args)
|
||||
local defaults = {
|
||||
name = 'appliedEnergistics',
|
||||
jobList = { },
|
||||
}
|
||||
Util.merge(self, defaults)
|
||||
Util.merge(self, args)
|
||||
local defaults = {
|
||||
name = 'appliedEnergistics',
|
||||
jobList = { },
|
||||
}
|
||||
Util.merge(self, defaults)
|
||||
Util.merge(self, args)
|
||||
|
||||
local controller
|
||||
if not self.side then
|
||||
controller = Peripheral.getByType(DEVICE_TYPE)
|
||||
else
|
||||
controller = Peripheral.getBySide(self.side)
|
||||
end
|
||||
local controller
|
||||
if not self.side then
|
||||
controller = Peripheral.getByType(DEVICE_TYPE)
|
||||
else
|
||||
controller = Peripheral.getBySide(self.side)
|
||||
end
|
||||
|
||||
if controller then
|
||||
Util.merge(self, controller)
|
||||
end
|
||||
if controller then
|
||||
Util.merge(self, controller)
|
||||
end
|
||||
end
|
||||
|
||||
function MEAdapter:isValid()
|
||||
return self.type == DEVICE_TYPE and not not self.findItems
|
||||
return self.type == DEVICE_TYPE and not not self.findItems
|
||||
end
|
||||
|
||||
function MEAdapter:clearFinished()
|
||||
for _,key in pairs(Util.keys(self.jobList)) do
|
||||
local job = self.jobList[key]
|
||||
if job.info.status() == 'finished' then
|
||||
self.jobList[key] = nil
|
||||
end
|
||||
end
|
||||
for _,key in pairs(Util.keys(self.jobList)) do
|
||||
local job = self.jobList[key]
|
||||
if job.info.status() == 'finished' then
|
||||
self.jobList[key] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function MEAdapter:isCPUAvailable()
|
||||
local cpus = self.getCraftingCPUs() or { }
|
||||
local busy = 0
|
||||
local cpus = self.getCraftingCPUs() or { }
|
||||
local busy = 0
|
||||
|
||||
for _,cpu in pairs(cpus) do
|
||||
if cpu.busy then
|
||||
busy = busy + 1
|
||||
end
|
||||
end
|
||||
self:clearFinished()
|
||||
return busy == Util.size(self.jobList) and busy < #cpus
|
||||
for _,cpu in pairs(cpus) do
|
||||
if cpu.busy then
|
||||
busy = busy + 1
|
||||
end
|
||||
end
|
||||
self:clearFinished()
|
||||
return busy == Util.size(self.jobList) and busy < #cpus
|
||||
end
|
||||
|
||||
function MEAdapter:craft(item, count)
|
||||
if not self:isCPUAvailable() then
|
||||
return false
|
||||
end
|
||||
if not self:isCPUAvailable() then
|
||||
return false
|
||||
end
|
||||
|
||||
local detail = self.findItem(item)
|
||||
if detail and detail.craft then
|
||||
local info = detail.craft(count or 1)
|
||||
if info.status() == 'unknown' then
|
||||
self.jobList[info.getId()] = {
|
||||
name = item.name,
|
||||
damage = item.damage,
|
||||
nbtHash = item.nbtHash,
|
||||
info = info,
|
||||
}
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
local detail = self.findItem(item)
|
||||
if detail and detail.craft then
|
||||
local info = detail.craft(count or 1)
|
||||
if info.status() == 'unknown' then
|
||||
self.jobList[info.getId()] = {
|
||||
name = item.name,
|
||||
damage = item.damage,
|
||||
nbtHash = item.nbtHash,
|
||||
info = info,
|
||||
}
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
function MEAdapter:isCrafting(item)
|
||||
self:clearFinished()
|
||||
self:clearFinished()
|
||||
_G._p = self.jobList
|
||||
for _,job in pairs(self.jobList) do
|
||||
if job.name == item.name and
|
||||
job.damage == item.damage and
|
||||
job.nbtHash == item.nbtHash then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
for _,job in pairs(self.jobList) do
|
||||
if job.name == item.name and
|
||||
job.damage == item.damage and
|
||||
job.nbtHash == item.nbtHash then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
return MEAdapter
|
||||
|
||||
@@ -5,91 +5,91 @@ 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
|
||||
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()
|
||||
Message.enable()
|
||||
end
|
||||
|
||||
Event.on('device_attach', function(event, deviceName)
|
||||
if deviceName == 'wireless_modem' then
|
||||
Message.enable()
|
||||
end
|
||||
if deviceName == 'wireless_modem' then
|
||||
Message.enable()
|
||||
end
|
||||
end)
|
||||
|
||||
function Message.addHandler(type, f)
|
||||
table.insert(messageHandlers, {
|
||||
type = type,
|
||||
f = f,
|
||||
enabled = true
|
||||
})
|
||||
table.insert(messageHandlers, {
|
||||
type = type,
|
||||
f = f,
|
||||
enabled = true
|
||||
})
|
||||
end
|
||||
|
||||
function Message.removeHandler(h)
|
||||
for k,v in pairs(messageHandlers) do
|
||||
for k,v in pairs(messageHandlers) do
|
||||
if v == h then
|
||||
messageHandlers[k] = nil
|
||||
break
|
||||
end
|
||||
end
|
||||
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
|
||||
for k,h in pairs(messageHandlers) do
|
||||
if h.type == msg.type then
|
||||
function(event, side, sendChannel, replyChannel, msg, distance)
|
||||
if msg and msg.type then -- filter out messages from other systems
|
||||
local id = replyChannel
|
||||
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
|
||||
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 not device.wireless_modem then
|
||||
error('No modem attached', 2)
|
||||
end
|
||||
|
||||
if id then
|
||||
device.wireless_modem.transmit(id, os.getComputerID(), {
|
||||
type = msgType, contents = contents
|
||||
})
|
||||
else
|
||||
device.wireless_modem.transmit(60000, os.getComputerID(), {
|
||||
type = msgType, contents = contents
|
||||
})
|
||||
end
|
||||
if id then
|
||||
device.wireless_modem.transmit(id, os.getComputerID(), {
|
||||
type = msgType, contents = contents
|
||||
})
|
||||
else
|
||||
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
|
||||
if not device.wireless_modem then
|
||||
error('No modem attached', 2)
|
||||
end
|
||||
|
||||
Message.send(nil, t, contents)
|
||||
Message.send(nil, t, 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
|
||||
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
|
||||
|
||||
return Message
|
||||
@@ -10,50 +10,50 @@ local USER_DIR = '/usr/etc/names'
|
||||
local nameDB = TableDB()
|
||||
|
||||
function nameDB:loadDirectory(directory)
|
||||
if fs.exists(directory) then
|
||||
local files = fs.list(directory)
|
||||
table.sort(files)
|
||||
if fs.exists(directory) then
|
||||
local files = fs.list(directory)
|
||||
table.sort(files)
|
||||
|
||||
for _,file in ipairs(files) do
|
||||
local mod = file:match('(%S+).json')
|
||||
if mod then
|
||||
local blocks = JSON.decodeFromFile(fs.combine(directory, file))
|
||||
for _,file in ipairs(files) do
|
||||
local mod = file:match('(%S+).json')
|
||||
if mod then
|
||||
local blocks = JSON.decodeFromFile(fs.combine(directory, file))
|
||||
|
||||
if not blocks then
|
||||
error('Unable to read ' .. fs.combine(directory, file))
|
||||
end
|
||||
if not blocks then
|
||||
error('Unable to read ' .. fs.combine(directory, file))
|
||||
end
|
||||
|
||||
for strId, block in pairs(blocks) do
|
||||
strId = string.format('%s:%s', mod, strId)
|
||||
if type(block.name) == 'string' then
|
||||
self.data[strId .. ':0'] = block.name
|
||||
else
|
||||
for nid,name in pairs(block.name) do
|
||||
self.data[strId .. ':' .. (nid-1)] = name
|
||||
end
|
||||
end
|
||||
end
|
||||
for strId, block in pairs(blocks) do
|
||||
strId = string.format('%s:%s', mod, strId)
|
||||
if type(block.name) == 'string' then
|
||||
self.data[strId .. ':0'] = block.name
|
||||
else
|
||||
for nid,name in pairs(block.name) do
|
||||
self.data[strId .. ':' .. (nid-1)] = name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
elseif file:match('(%S+).db') then
|
||||
local names = Util.readTable(fs.combine(directory, file))
|
||||
if not names then
|
||||
error('Unable to read ' .. fs.combine(directory, file))
|
||||
end
|
||||
for key,name in pairs(names) do
|
||||
self.data[key] = name
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif file:match('(%S+).db') then
|
||||
local names = Util.readTable(fs.combine(directory, file))
|
||||
if not names then
|
||||
error('Unable to read ' .. fs.combine(directory, file))
|
||||
end
|
||||
for key,name in pairs(names) do
|
||||
self.data[key] = name
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function nameDB:load()
|
||||
self:loadDirectory(CORE_DIR)
|
||||
self:loadDirectory(USER_DIR)
|
||||
self:loadDirectory(CORE_DIR)
|
||||
self:loadDirectory(USER_DIR)
|
||||
end
|
||||
|
||||
function nameDB:getName(strId)
|
||||
return self.data[strId] or strId
|
||||
return self.data[strId] or strId
|
||||
end
|
||||
|
||||
nameDB:load()
|
||||
|
||||
32
core/apis/proxy.lua
Normal file
32
core/apis/proxy.lua
Normal file
@@ -0,0 +1,32 @@
|
||||
local Socket = require('socket')
|
||||
|
||||
local Proxy = { }
|
||||
|
||||
function Proxy.create(remoteId, uri)
|
||||
local socket, msg = Socket.connect(remoteId, 188)
|
||||
|
||||
if not socket then
|
||||
error(msg)
|
||||
end
|
||||
|
||||
socket.co = coroutine.running()
|
||||
|
||||
socket:write(uri)
|
||||
local methods = socket:read(2) or error('Timed out')
|
||||
|
||||
local hijack = { }
|
||||
for _,method in pairs(methods) do
|
||||
hijack[method] = function(...)
|
||||
socket:write({ method, ... })
|
||||
local resp = socket:read()
|
||||
if not resp then
|
||||
error('timed out: ' .. method)
|
||||
end
|
||||
return table.unpack(resp)
|
||||
end
|
||||
end
|
||||
|
||||
return hijack, socket
|
||||
end
|
||||
|
||||
return Proxy
|
||||
@@ -6,134 +6,134 @@ local Util = require('util')
|
||||
local RefinedAdapter = class()
|
||||
|
||||
function RefinedAdapter:init(args)
|
||||
local defaults = {
|
||||
name = 'refinedStorage',
|
||||
}
|
||||
Util.merge(self, defaults)
|
||||
Util.merge(self, args)
|
||||
local defaults = {
|
||||
name = 'refinedStorage',
|
||||
}
|
||||
Util.merge(self, defaults)
|
||||
Util.merge(self, args)
|
||||
|
||||
local controller
|
||||
if not self.side then
|
||||
controller = Peripheral.getByMethod('getCraftingTasks')
|
||||
else
|
||||
controller = Peripheral.getBySide(self.side)
|
||||
end
|
||||
local controller
|
||||
if not self.side then
|
||||
controller = Peripheral.getByMethod('getCraftingTasks')
|
||||
else
|
||||
controller = Peripheral.getBySide(self.side)
|
||||
end
|
||||
|
||||
if controller then
|
||||
Util.merge(self, controller)
|
||||
end
|
||||
if controller then
|
||||
Util.merge(self, controller)
|
||||
end
|
||||
end
|
||||
|
||||
function RefinedAdapter:isValid()
|
||||
return not not self.getCraftingTasks
|
||||
return not not self.getCraftingTasks
|
||||
end
|
||||
|
||||
function RefinedAdapter:getItemDetails(item)
|
||||
local detail = self.findItems(item)
|
||||
if detail and #detail > 0 then
|
||||
return detail[1].getMetadata()
|
||||
end
|
||||
local detail = self.findItems(item)
|
||||
if detail and #detail > 0 then
|
||||
return detail[1].getMetadata()
|
||||
end
|
||||
end
|
||||
|
||||
function RefinedAdapter:getCachedItemDetails(item)
|
||||
local cached = itemDB:get(item)
|
||||
if cached then
|
||||
return cached
|
||||
end
|
||||
local cached = itemDB:get(item)
|
||||
if cached then
|
||||
return cached
|
||||
end
|
||||
|
||||
local detail = self:getItemDetails(item)
|
||||
if detail then
|
||||
return itemDB:add(detail)
|
||||
end
|
||||
local detail = self:getItemDetails(item)
|
||||
if detail then
|
||||
return itemDB:add(detail)
|
||||
end
|
||||
end
|
||||
|
||||
function RefinedAdapter:refresh(throttle)
|
||||
return self:listItems(throttle)
|
||||
return self:listItems(throttle)
|
||||
end
|
||||
|
||||
function RefinedAdapter:listItems(throttle)
|
||||
local items = { }
|
||||
throttle = throttle or Util.throttle()
|
||||
local items = { }
|
||||
throttle = throttle or Util.throttle()
|
||||
|
||||
local s, m = pcall(function()
|
||||
for _,v in pairs(self.listAvailableItems()) do
|
||||
--if v.count > 0 then
|
||||
local item = self:getCachedItemDetails(v)
|
||||
if item then
|
||||
item = Util.shallowCopy(item)
|
||||
item.count = v.count
|
||||
table.insert(items, item)
|
||||
end
|
||||
--end
|
||||
throttle()
|
||||
end
|
||||
end)
|
||||
local s, m = pcall(function()
|
||||
for _,v in pairs(self.listAvailableItems()) do
|
||||
--if v.count > 0 then
|
||||
local item = self:getCachedItemDetails(v)
|
||||
if item then
|
||||
item = Util.shallowCopy(item)
|
||||
item.count = v.count
|
||||
table.insert(items, item)
|
||||
end
|
||||
--end
|
||||
throttle()
|
||||
end
|
||||
end)
|
||||
|
||||
if not s and m then
|
||||
_G._syslog(m)
|
||||
end
|
||||
if not s and m then
|
||||
_G._syslog(m)
|
||||
end
|
||||
|
||||
itemDB:flush()
|
||||
if not Util.empty(items) then
|
||||
return items
|
||||
end
|
||||
itemDB:flush()
|
||||
if not Util.empty(items) then
|
||||
return items
|
||||
end
|
||||
end
|
||||
|
||||
function RefinedAdapter:getItemInfo(item)
|
||||
return self:getItemDetails(item)
|
||||
return self:getItemDetails(item)
|
||||
end
|
||||
|
||||
function RefinedAdapter:isCPUAvailable()
|
||||
return true
|
||||
return true
|
||||
end
|
||||
|
||||
function RefinedAdapter:craft(item, qty)
|
||||
local detail = self.findItem(item)
|
||||
if detail then
|
||||
return detail.craft(qty)
|
||||
end
|
||||
local detail = self.findItem(item)
|
||||
if detail then
|
||||
return detail.craft(qty)
|
||||
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
|
||||
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:provide(item, qty, slot, direction)
|
||||
return pcall(function()
|
||||
for _,stack in pairs(self.listAvailableItems()) do
|
||||
if stack.name == item.name and
|
||||
(not item.damage or stack.damage == item.damage) and
|
||||
(not item.nbtHash or stack.nbtHash == item.nbtHash) then
|
||||
local amount = math.min(qty, stack.count)
|
||||
if amount > 0 then
|
||||
local detail = self.findItem(item)
|
||||
if detail then
|
||||
return detail.export(direction or self.direction, amount, slot)
|
||||
end
|
||||
end
|
||||
qty = qty - amount
|
||||
if qty <= 0 then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
return pcall(function()
|
||||
for _,stack in pairs(self.listAvailableItems()) do
|
||||
if stack.name == item.name and
|
||||
(not item.damage or stack.damage == item.damage) and
|
||||
(not item.nbtHash or stack.nbtHash == item.nbtHash) then
|
||||
local amount = math.min(qty, stack.count)
|
||||
if amount > 0 then
|
||||
local detail = self.findItem(item)
|
||||
if detail then
|
||||
return detail.export(direction or self.direction, amount, slot)
|
||||
end
|
||||
end
|
||||
qty = qty - amount
|
||||
if qty <= 0 then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function RefinedAdapter:extract(slot, qty, toSlot)
|
||||
self.pushItems(self.direction, slot, qty, toSlot)
|
||||
self.pushItems(self.direction, slot, qty, toSlot)
|
||||
end
|
||||
|
||||
function RefinedAdapter:insert(slot, qty, toSlot)
|
||||
self.pullItems(self.direction, slot, qty, toSlot)
|
||||
self.pullItems(self.direction, slot, qty, toSlot)
|
||||
end
|
||||
|
||||
return RefinedAdapter
|
||||
|
||||
@@ -1,66 +1,66 @@
|
||||
local class = require('class')
|
||||
local Event = require('event')
|
||||
local Map = require('map')
|
||||
local Proxy = require('proxy')
|
||||
local Proxy = require('core.proxy')
|
||||
|
||||
local Swarm = class()
|
||||
function Swarm:init(args)
|
||||
self.pool = { }
|
||||
Map.merge(self, args)
|
||||
self.pool = { }
|
||||
Map.merge(self, args)
|
||||
end
|
||||
|
||||
function Swarm:add(id, args)
|
||||
local member = Map.shallowCopy(args or { })
|
||||
member.id = id
|
||||
self.pool[id] = member
|
||||
local member = Map.shallowCopy(args or { })
|
||||
member.id = id
|
||||
self.pool[id] = member
|
||||
end
|
||||
|
||||
function Swarm:remove(id, s, m)
|
||||
local member = self.pool[id]
|
||||
if member then
|
||||
self.pool[id] = nil
|
||||
self:onRemove(member, s, m)
|
||||
if member.socket then
|
||||
member.socket:close()
|
||||
member.socket = nil
|
||||
end
|
||||
if member.handler then
|
||||
member.handler:terminate()
|
||||
member.handler = nil
|
||||
end
|
||||
end
|
||||
local member = self.pool[id]
|
||||
if member then
|
||||
self.pool[id] = nil
|
||||
self:onRemove(member, s, m)
|
||||
if member.socket then
|
||||
member.socket:close()
|
||||
member.socket = nil
|
||||
end
|
||||
if member.handler then
|
||||
member.handler:terminate()
|
||||
member.handler = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Swarm:run(fn)
|
||||
for id, member in pairs(self.pool) do
|
||||
if not member.socket then
|
||||
member.handler = Event.addRoutine(function()
|
||||
local s, m = pcall(function()
|
||||
member.turtle, member.socket = Proxy.create(id, 'turtle')
|
||||
for id, member in pairs(self.pool) do
|
||||
if not member.socket then
|
||||
member.handler = Event.addRoutine(function()
|
||||
local s, m = pcall(function()
|
||||
member.turtle, member.socket = Proxy.create(id, 'turtle')
|
||||
|
||||
fn(member)
|
||||
end)
|
||||
self:remove(id, s, m)
|
||||
end)
|
||||
end
|
||||
end
|
||||
fn(member)
|
||||
end)
|
||||
self:remove(id, s, m)
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Swarm:stop()
|
||||
for _, member in pairs(self.pool) do
|
||||
if member.socket then
|
||||
member.socket:close()
|
||||
member.socket = nil
|
||||
end
|
||||
end
|
||||
for _, member in pairs(self.pool) do
|
||||
if member.socket then
|
||||
member.socket:close()
|
||||
member.socket = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Override
|
||||
function Swarm:onRemove(member, success, msg)
|
||||
print('removed from pool: ' .. member.id)
|
||||
if not success then
|
||||
_G.printError(msg)
|
||||
end
|
||||
print('removed from pool: ' .. member.id)
|
||||
if not success then
|
||||
_G.printError(msg)
|
||||
end
|
||||
end
|
||||
|
||||
return Swarm
|
||||
|
||||
@@ -3,47 +3,47 @@ local Util = require('util')
|
||||
|
||||
local TableDB = class()
|
||||
function TableDB:init(args)
|
||||
local defaults = {
|
||||
fileName = '',
|
||||
dirty = false,
|
||||
data = { },
|
||||
}
|
||||
Util.merge(defaults, args)
|
||||
Util.merge(self, defaults)
|
||||
local defaults = {
|
||||
fileName = '',
|
||||
dirty = false,
|
||||
data = { },
|
||||
}
|
||||
Util.merge(defaults, args)
|
||||
Util.merge(self, defaults)
|
||||
end
|
||||
|
||||
function TableDB:load()
|
||||
local t = Util.readTable(self.fileName)
|
||||
if t then
|
||||
self.data = t.data or t
|
||||
end
|
||||
local t = Util.readTable(self.fileName)
|
||||
if t then
|
||||
self.data = t.data or t
|
||||
end
|
||||
end
|
||||
|
||||
function TableDB:add(key, entry)
|
||||
if type(key) == 'table' then
|
||||
key = table.concat(key, ':')
|
||||
end
|
||||
self.data[key] = entry
|
||||
self.dirty = true
|
||||
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]
|
||||
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
|
||||
self.data[key] = nil
|
||||
self.dirty = true
|
||||
end
|
||||
|
||||
function TableDB:flush()
|
||||
if self.dirty then
|
||||
Util.writeTable(self.fileName, self.data)
|
||||
self.dirty = false
|
||||
end
|
||||
if self.dirty then
|
||||
Util.writeTable(self.fileName, self.data)
|
||||
self.dirty = false
|
||||
end
|
||||
end
|
||||
|
||||
return TableDB
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,60 +6,60 @@ local turtle = _G.turtle
|
||||
local CRAFTING_TABLE = 'minecraft:crafting_table'
|
||||
|
||||
local function clearGrid(inventory)
|
||||
for i = 1, 16 do
|
||||
local count = turtle.getItemCount(i)
|
||||
if count > 0 then
|
||||
inventory:insert(i, count)
|
||||
if turtle.getItemCount(i) ~= 0 then
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
for i = 1, 16 do
|
||||
local count = turtle.getItemCount(i)
|
||||
if count > 0 then
|
||||
inventory:insert(i, count)
|
||||
if turtle.getItemCount(i) ~= 0 then
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function turtle.craftItem(item, count, inventoryInfo)
|
||||
local success, msg
|
||||
local success, msg
|
||||
|
||||
local inventory = Adapter.wrap(inventoryInfo)
|
||||
if not inventory then
|
||||
return false, 'Invalid inventory'
|
||||
end
|
||||
local inventory = Adapter.wrap(inventoryInfo)
|
||||
if not inventory then
|
||||
return false, 'Invalid inventory'
|
||||
end
|
||||
|
||||
local equipped, side
|
||||
if not turtle.isEquipped('workbench') then
|
||||
local modemSide = turtle.isEquipped('modem') or 'right'
|
||||
local osides = { left = 'right', right = 'left' }
|
||||
side = osides[modemSide]
|
||||
if not turtle.select(CRAFTING_TABLE) then
|
||||
clearGrid(inventory)
|
||||
if not turtle.selectOpenSlot() then
|
||||
return false, 'Inventory is full'
|
||||
end
|
||||
if not inventory:provide({ name = CRAFTING_TABLE, damage = 0 }, 1) then
|
||||
return false, 'Missing crafting table'
|
||||
end
|
||||
end
|
||||
local equipped, side
|
||||
if not turtle.isEquipped('workbench') then
|
||||
local modemSide = turtle.isEquipped('modem') or 'right'
|
||||
local osides = { left = 'right', right = 'left' }
|
||||
side = osides[modemSide]
|
||||
if not turtle.select(CRAFTING_TABLE) then
|
||||
clearGrid(inventory)
|
||||
if not turtle.selectOpenSlot() then
|
||||
return false, 'Inventory is full'
|
||||
end
|
||||
if not inventory:provide({ name = CRAFTING_TABLE, damage = 0 }, 1) then
|
||||
return false, 'Missing crafting table'
|
||||
end
|
||||
end
|
||||
|
||||
local slot = turtle.select(CRAFTING_TABLE)
|
||||
turtle.equip(side, CRAFTING_TABLE)
|
||||
equipped = turtle.getItemDetail(slot.index)
|
||||
end
|
||||
local slot = turtle.select(CRAFTING_TABLE)
|
||||
turtle.equip(side, CRAFTING_TABLE)
|
||||
equipped = turtle.getItemDetail(slot.index)
|
||||
end
|
||||
|
||||
clearGrid(inventory)
|
||||
success, msg = Craft.craftRecipe(item, count or 1, inventory)
|
||||
clearGrid(inventory)
|
||||
success, msg = Craft.craftRecipe(item, count or 1, inventory)
|
||||
|
||||
if equipped then
|
||||
turtle.selectOpenSlot()
|
||||
inventory:provide({ name = equipped.name, damage = equipped.damage }, 1)
|
||||
turtle.equip(side, equipped.name .. ':' .. equipped.damage)
|
||||
end
|
||||
if equipped then
|
||||
turtle.selectOpenSlot()
|
||||
inventory:provide({ name = equipped.name, damage = equipped.damage }, 1)
|
||||
turtle.equip(side, equipped.name .. ':' .. equipped.damage)
|
||||
end
|
||||
|
||||
return success, msg
|
||||
return success, msg
|
||||
end
|
||||
|
||||
function turtle.canCraft(item, count, items)
|
||||
return Craft.canCraft(item, count, items)
|
||||
return Craft.canCraft(item, count, items)
|
||||
end
|
||||
|
||||
return true
|
||||
|
||||
@@ -9,167 +9,167 @@ local box = { }
|
||||
local oldCallback
|
||||
|
||||
local function toKey(pt)
|
||||
return table.concat({ pt.x, pt.y, pt.z }, ':')
|
||||
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 }
|
||||
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 Point.inBox(testNode, box) then
|
||||
local key = toKey(testNode)
|
||||
if not checkedNodes[key] then
|
||||
nodes[key] = testNode
|
||||
end
|
||||
end
|
||||
end
|
||||
if Point.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',
|
||||
}
|
||||
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
|
||||
-- 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 }
|
||||
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 Point.inBox(node, box) then
|
||||
if Point.inBox(node, box) then
|
||||
|
||||
local key = toKey(node)
|
||||
checkedNodes[key] = true
|
||||
nodes[key] = nil
|
||||
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
|
||||
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'))
|
||||
dig(turtle.getAction('forward'))
|
||||
elseif action == 'down' then
|
||||
dig(turtle.getAction('down'))
|
||||
dig(turtle.getAction('forward'))
|
||||
elseif action == 'back' then
|
||||
dig(turtle.getAction('up'))
|
||||
dig(turtle.getAction('down'))
|
||||
end
|
||||
if action == 'turn' then
|
||||
dig(turtle.getAction('forward'))
|
||||
elseif action == 'up' then
|
||||
dig(turtle.getAction('up'))
|
||||
dig(turtle.getAction('forward'))
|
||||
elseif action == 'down' then
|
||||
dig(turtle.getAction('down'))
|
||||
dig(turtle.getAction('forward'))
|
||||
elseif action == 'back' then
|
||||
dig(turtle.getAction('up'))
|
||||
dig(turtle.getAction('down'))
|
||||
end
|
||||
|
||||
if oldCallback then
|
||||
oldCallback(action)
|
||||
end
|
||||
if oldCallback then
|
||||
oldCallback(action)
|
||||
end
|
||||
end
|
||||
|
||||
-- find the closest block
|
||||
-- * favor same plane
|
||||
-- * going backwards only if the dest is above or below
|
||||
local function closestPoint(reference, pts)
|
||||
local lpt, lm -- lowest
|
||||
for _,pt in pairs(pts) do
|
||||
local m = Point.turtleDistance(reference, pt)
|
||||
local h = Point.calculateHeading(reference, pt)
|
||||
local t = Point.calculateTurns(reference.heading, h)
|
||||
if pt.y ~= reference.y then -- try and stay on same plane
|
||||
m = m + .01
|
||||
end
|
||||
if t ~= 2 or pt.y == reference.y then
|
||||
m = m + t
|
||||
if t > 0 then
|
||||
m = m + .01
|
||||
end
|
||||
end
|
||||
if not lm or m < lm then
|
||||
lpt = pt
|
||||
lm = m
|
||||
end
|
||||
end
|
||||
return lpt
|
||||
local lpt, lm -- lowest
|
||||
for _,pt in pairs(pts) do
|
||||
local m = Point.turtleDistance(reference, pt)
|
||||
local h = Point.calculateHeading(reference, pt)
|
||||
local t = Point.calculateTurns(reference.heading, h)
|
||||
if pt.y ~= reference.y then -- try and stay on same plane
|
||||
m = m + .01
|
||||
end
|
||||
if t ~= 2 or pt.y == reference.y then
|
||||
m = m + t
|
||||
if t > 0 then
|
||||
m = m + .01
|
||||
end
|
||||
end
|
||||
if not lm or m < lm then
|
||||
lpt = pt
|
||||
lm = m
|
||||
end
|
||||
end
|
||||
return lpt
|
||||
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
|
||||
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 closestPoint(turtle.getPoint(), t)
|
||||
return closestPoint(turtle.getPoint(), t)
|
||||
end
|
||||
|
||||
function turtle.level(startPt, endPt, firstPt, verbose)
|
||||
checkedNodes = { }
|
||||
nodes = { }
|
||||
box = { }
|
||||
checkedNodes = { }
|
||||
nodes = { }
|
||||
box = { }
|
||||
|
||||
box.x = math.min(startPt.x, endPt.x)
|
||||
box.y = math.min(startPt.y, endPt.y)
|
||||
box.z = math.min(startPt.z, endPt.z)
|
||||
box.ex = math.max(startPt.x, endPt.x)
|
||||
box.ey = math.max(startPt.y, endPt.y)
|
||||
box.ez = math.max(startPt.z, endPt.z)
|
||||
box.x = math.min(startPt.x, endPt.x)
|
||||
box.y = math.min(startPt.y, endPt.y)
|
||||
box.z = math.min(startPt.z, endPt.z)
|
||||
box.ex = math.max(startPt.x, endPt.x)
|
||||
box.ey = math.max(startPt.y, endPt.y)
|
||||
box.ez = math.max(startPt.z, endPt.z)
|
||||
|
||||
if not Point.inBox(firstPt, box) then
|
||||
error('Starting point is not in leveling area')
|
||||
end
|
||||
if not Point.inBox(firstPt, box) then
|
||||
error('Starting point is not in leveling area')
|
||||
end
|
||||
|
||||
if not turtle.pathfind(firstPt) then
|
||||
error('failed to reach starting point')
|
||||
end
|
||||
if not turtle.pathfind(firstPt) then
|
||||
error('failed to reach starting point')
|
||||
end
|
||||
|
||||
turtle.set({
|
||||
digPolicy = dig,
|
||||
attackPolicy = 'attack',
|
||||
movePolicy = 'moveAssured',
|
||||
})
|
||||
turtle.set({
|
||||
digPolicy = dig,
|
||||
attackPolicy = 'attack',
|
||||
movePolicy = 'moveAssured',
|
||||
})
|
||||
|
||||
|
||||
oldCallback = turtle.getMoveCallback()
|
||||
turtle.setMoveCallback(move)
|
||||
oldCallback = turtle.getMoveCallback()
|
||||
turtle.setMoveCallback(move)
|
||||
|
||||
repeat
|
||||
local key = toKey(turtle.point)
|
||||
repeat
|
||||
local key = toKey(turtle.point)
|
||||
|
||||
checkedNodes[key] = true
|
||||
nodes[key] = nil
|
||||
checkedNodes[key] = true
|
||||
nodes[key] = nil
|
||||
|
||||
dig(turtle.getAction('down'))
|
||||
dig(turtle.getAction('up'))
|
||||
dig(turtle.getAction('forward'))
|
||||
dig(turtle.getAction('down'))
|
||||
dig(turtle.getAction('up'))
|
||||
dig(turtle.getAction('forward'))
|
||||
|
||||
if verbose then
|
||||
print(string.format('%d nodes remaining', Util.size(nodes)))
|
||||
end
|
||||
if verbose then
|
||||
print(string.format('%d nodes remaining', Util.size(nodes)))
|
||||
end
|
||||
|
||||
if not next(nodes) then
|
||||
break
|
||||
end
|
||||
if not next(nodes) then
|
||||
break
|
||||
end
|
||||
|
||||
local node = closestPoint(turtle.point, nodes)
|
||||
node = getAdjacentPoint(node)
|
||||
if not turtle.go(node) then
|
||||
break
|
||||
end
|
||||
until turtle.isAborted()
|
||||
local node = closestPoint(turtle.point, nodes)
|
||||
node = getAdjacentPoint(node)
|
||||
if not turtle.go(node) then
|
||||
break
|
||||
end
|
||||
until turtle.isAborted()
|
||||
|
||||
turtle.resetState()
|
||||
turtle.setMoveCallback(oldCallback)
|
||||
turtle.resetState()
|
||||
turtle.setMoveCallback(oldCallback)
|
||||
end
|
||||
|
||||
return true
|
||||
|
||||
@@ -18,12 +18,12 @@ local Runners = {
|
||||
}
|
||||
|
||||
Equipper.equipLeft('minecraft:diamond_sword')
|
||||
local scanner = Equipper.equipRight('plethora:module:2', 'plethora:scanner')
|
||||
local scanner = Equipper.equipRight('plethora:scanner')
|
||||
|
||||
local facing = scanner.getBlockMeta(0, 0, 0).state.facing
|
||||
turtle.point.heading = Point.facings[facing].heading
|
||||
|
||||
local sensor = Equipper.equipRight('plethora:module:3', 'plethora:sensor')
|
||||
local sensor = Equipper.equipRight('plethora:sensor')
|
||||
|
||||
turtle.setMovementStrategy('goto')
|
||||
turtle.set({ attackPolicy = 'attack' })
|
||||
@@ -32,9 +32,9 @@ local function findChests()
|
||||
if chest then
|
||||
return { chest }
|
||||
end
|
||||
Equipper.equipRight('plethora:module:2', 'plethora:scanner')
|
||||
Equipper.equipRight('plethora:scanner')
|
||||
local chests = scanner.scan()
|
||||
Equipper.equipRight('plethora:module:3', 'plethora:sensor')
|
||||
Equipper.equipRight('plethora:sensor')
|
||||
|
||||
Util.filterInplace(chests, function(b)
|
||||
if b.name == 'minecraft:chest' or
|
||||
|
||||
373
farms/farmer.lua
373
farms/farmer.lua
@@ -12,238 +12,241 @@ local STARTUP_FILE = 'usr/autorun/farmer.lua'
|
||||
local MIN_FUEL = 2500
|
||||
|
||||
local FUEL = Util.transpose {
|
||||
'minecraft:coal:0',
|
||||
'minecraft:coal:1',
|
||||
'minecraft:blaze_rod:0',
|
||||
'minecraft:coal:0',
|
||||
'minecraft:coal:1',
|
||||
'minecraft:blaze_rod:0',
|
||||
}
|
||||
|
||||
local scanner = Equipper.equipRight('plethora:module:2', 'plethora:scanner')
|
||||
local scanner = Equipper.equipRight('plethora:scanner')
|
||||
|
||||
local crops = Util.readTable(CONFIG_FILE) or {
|
||||
['minecraft:wheat'] =
|
||||
{ seed = 'minecraft:wheat_seeds', mature = 7, action = 'plant' },
|
||||
['minecraft:carrots'] =
|
||||
{ seed = 'minecraft:carrot', mature = 7, action = 'plant' },
|
||||
['minecraft:potatoes'] =
|
||||
{ seed = 'minecraft:potato', mature = 7, action = 'plant' },
|
||||
['minecraft:beetroots'] =
|
||||
{ seed = 'minecraft:beetroot_seeds', mature = 3, action = 'plant' },
|
||||
['minecraft:nether_wart'] =
|
||||
{ seed = 'minecraft:nether_wart', mature = 3, action = 'plant' },
|
||||
['minecraft:cocoa'] =
|
||||
{ seed = 'minecraft:dye:3', mature = 8, action = 'pick' },
|
||||
['minecraft:reeds'] = { action = 'bash' },
|
||||
['minecraft:chorus_flower'] = { action = 'bash' },
|
||||
['minecraft:chorus_plant'] =
|
||||
{ seed = 'minecraft:chorus_flower', mature = 0, action = 'bash-smash', },
|
||||
['minecraft:melon_block'] = { action = 'smash' },
|
||||
['minecraft:pumpkin'] = { action = 'smash' },
|
||||
['minecraft:chest'] = { action = 'drop' },
|
||||
['minecraft:cactus'] = { action = 'smash' },
|
||||
['minecraft:wheat'] =
|
||||
{ seed = 'minecraft:wheat_seeds', mature = 7, action = 'plant' },
|
||||
['minecraft:carrots'] =
|
||||
{ seed = 'minecraft:carrot', mature = 7, action = 'plant' },
|
||||
['minecraft:potatoes'] =
|
||||
{ seed = 'minecraft:potato', mature = 7, action = 'plant' },
|
||||
['minecraft:beetroots'] =
|
||||
{ seed = 'minecraft:beetroot_seeds', mature = 3, action = 'plant' },
|
||||
['minecraft:nether_wart'] =
|
||||
{ seed = 'minecraft:nether_wart', mature = 3, action = 'plant' },
|
||||
['minecraft:cocoa'] =
|
||||
{ seed = 'minecraft:dye:3', mature = 8, action = 'pick' },
|
||||
['minecraft:reeds'] = { action = 'bash' },
|
||||
['minecraft:chorus_flower'] = { action = 'bash' },
|
||||
['minecraft:chorus_plant'] =
|
||||
{ seed = 'minecraft:chorus_flower', mature = 0, action = 'bash-smash', },
|
||||
['minecraft:melon_block'] = { action = 'smash' },
|
||||
['minecraft:pumpkin'] = { action = 'smash' },
|
||||
['minecraft:chest'] = { action = 'drop' },
|
||||
['minecraft:cactus'] = { action = 'smash' },
|
||||
}
|
||||
|
||||
if not fs.exists(CONFIG_FILE) then
|
||||
Util.writeTable(CONFIG_FILE, crops)
|
||||
Util.writeTable(CONFIG_FILE, crops)
|
||||
end
|
||||
|
||||
if not fs.exists(STARTUP_FILE) then
|
||||
Util.writeFile(STARTUP_FILE,
|
||||
[[os.sleep(1)
|
||||
Util.writeFile(STARTUP_FILE,
|
||||
[[os.sleep(1)
|
||||
shell.openForegroundTab('farmer.lua')]])
|
||||
print('Autorun program created: ' .. STARTUP_FILE)
|
||||
print('Autorun program created: ' .. STARTUP_FILE)
|
||||
end
|
||||
|
||||
local retain = Util.transpose {
|
||||
"minecraft:diamond_pickaxe",
|
||||
"plethora:module:2",
|
||||
"plethora:module:3",
|
||||
"minecraft:diamond_pickaxe",
|
||||
"plethora:module:2",
|
||||
"plethora:module:3",
|
||||
}
|
||||
|
||||
for _, v in pairs(crops) do
|
||||
if v.seed then
|
||||
retain[v.seed] = true
|
||||
end
|
||||
if v.seed then
|
||||
retain[v.seed] = true
|
||||
end
|
||||
end
|
||||
|
||||
local function scan()
|
||||
local blocks = scanner.scan()
|
||||
local summed = turtle.getSummedInventory()
|
||||
local doDropOff
|
||||
local blocks = scanner.scan()
|
||||
local summed = turtle.getSummedInventory()
|
||||
local doDropOff
|
||||
|
||||
for _,v in pairs(summed) do
|
||||
if v.count > 32 then
|
||||
doDropOff = true
|
||||
break
|
||||
end
|
||||
end
|
||||
for _,v in pairs(summed) do
|
||||
if v.count > 32 then
|
||||
doDropOff = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
Util.filterInplace(blocks, function(b)
|
||||
b.action = crops[b.name] and crops[b.name].action
|
||||
Util.filterInplace(blocks, function(b)
|
||||
b.action = crops[b.name] and crops[b.name].action
|
||||
|
||||
if b.action == 'bash' then
|
||||
return b.y == 0
|
||||
end
|
||||
if b.action == 'drop' then
|
||||
return doDropOff and (b.y == -1 or b.y == 1)
|
||||
end
|
||||
if b.action == 'bash-smash' then
|
||||
if b.y == -1 then
|
||||
b.action = 'smash'
|
||||
end
|
||||
if b.y == 0 then
|
||||
b.action = 'bash'
|
||||
end
|
||||
return b.action ~= 'bash-smash'
|
||||
end
|
||||
if b.action == 'bash' then
|
||||
return b.y == 0
|
||||
end
|
||||
if b.action == 'drop' then
|
||||
return doDropOff and (b.y == -1 or b.y == 1)
|
||||
end
|
||||
if b.action == 'bash-smash' then
|
||||
if b.y == -1 then
|
||||
b.action = 'smash'
|
||||
end
|
||||
if b.y == 0 then
|
||||
b.action = 'bash'
|
||||
end
|
||||
return b.action ~= 'bash-smash'
|
||||
end
|
||||
|
||||
if b.action == 'smash' then
|
||||
return b.y == -1
|
||||
end
|
||||
if b.action == 'pick' then
|
||||
return b.y == 0 and b.state.age == 2
|
||||
end
|
||||
if b.action == 'bump' then
|
||||
return b.y == 0
|
||||
end
|
||||
return b.action == 'plant' and
|
||||
b.metadata == crops[b.name].mature and
|
||||
b.y == -1
|
||||
end)
|
||||
if b.action == 'smash' then
|
||||
return b.y == -1
|
||||
end
|
||||
if b.action == 'pick' then
|
||||
return b.y == 0 and b.state.age == 2
|
||||
end
|
||||
if b.action == 'bump' then
|
||||
return b.y == 0
|
||||
end
|
||||
if b.action == 'plant' and b.y == -1 then
|
||||
if not b.metadata then -- minecraft 1.10
|
||||
b = scanner.getBlockMeta(b.x, b.y, b.z)
|
||||
end
|
||||
return b.metadata == crops[b.name].mature
|
||||
end
|
||||
end)
|
||||
|
||||
local harvestCount = 0
|
||||
for _,b in pairs(blocks) do
|
||||
b.x = b.x + turtle.point.x
|
||||
b.y = b.y + turtle.point.y
|
||||
b.z = b.z + turtle.point.z
|
||||
if b.action ~= 'drop' then
|
||||
harvestCount = harvestCount + 1
|
||||
end
|
||||
end
|
||||
local harvestCount = 0
|
||||
for _,b in pairs(blocks) do
|
||||
b.x = b.x + turtle.point.x
|
||||
b.y = b.y + turtle.point.y
|
||||
b.z = b.z + turtle.point.z
|
||||
if b.action ~= 'drop' then
|
||||
harvestCount = harvestCount + 1
|
||||
end
|
||||
end
|
||||
|
||||
return blocks, harvestCount
|
||||
return blocks, harvestCount
|
||||
end
|
||||
|
||||
local function harvest(blocks)
|
||||
Equipper.equipRight('minecraft:diamond_pickaxe')
|
||||
Equipper.equipRight('minecraft:diamond_pickaxe')
|
||||
|
||||
local dropped
|
||||
local dropped
|
||||
|
||||
Point.eachClosest(turtle.point, blocks, function(b)
|
||||
turtle.select(1)
|
||||
Point.eachClosest(turtle.point, blocks, function(b)
|
||||
turtle.select(1)
|
||||
|
||||
if b.action == 'bash' then
|
||||
turtle.digForwardAt(b)
|
||||
if b.action == 'bash' then
|
||||
turtle.digForwardAt(b)
|
||||
|
||||
elseif b.action == 'drop' and not dropped then
|
||||
if turtle.go(b.y == 1 and Point.below(b) or Point.above(b)) then
|
||||
local dropFn = b.y == 1 and turtle.dropUp or turtle.dropDown
|
||||
local dropDir = b.y == 1 and 'down' or 'up'
|
||||
elseif b.action == 'drop' and not dropped then
|
||||
if turtle.go(b.y == 1 and Point.below(b) or Point.above(b)) then
|
||||
local dropFn = b.y == 1 and turtle.dropUp or turtle.dropDown
|
||||
local dropDir = b.y == 1 and 'down' or 'up'
|
||||
|
||||
turtle.eachFilledSlot(function(slot)
|
||||
if not retain[slot.name] and not retain[slot.key] then
|
||||
turtle.select(slot.index)
|
||||
dropFn()
|
||||
end
|
||||
end)
|
||||
local summed = turtle.getSummedInventory()
|
||||
for k,v in pairs(summed) do
|
||||
if v.count > 16 then
|
||||
dropFn(k, v.count - 16)
|
||||
end
|
||||
end
|
||||
turtle.eachFilledSlot(function(slot)
|
||||
if not retain[slot.name] and not retain[slot.key] then
|
||||
turtle.select(slot.index)
|
||||
dropFn()
|
||||
end
|
||||
end)
|
||||
local summed = turtle.getSummedInventory()
|
||||
for k,v in pairs(summed) do
|
||||
if v.count > 16 then
|
||||
dropFn(k, v.count - 16)
|
||||
end
|
||||
end
|
||||
|
||||
dropped = true
|
||||
turtle.condense()
|
||||
dropped = true
|
||||
turtle.condense()
|
||||
|
||||
if turtle.getFuelLevel() < MIN_FUEL then
|
||||
local inv = peripheral.wrap('bottom')
|
||||
if inv and inv.list then
|
||||
for k, v in pairs(inv.list()) do
|
||||
if FUEL[table.concat({ v.name, v.damage }, ':')] then
|
||||
local count = inv.pushItems(dropDir, k, v.count)
|
||||
if count > 0 then
|
||||
turtle.refuel(v.name, v.count)
|
||||
print('Fuel: ' .. turtle.getFuelLevel())
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if turtle.getFuelLevel() < MIN_FUEL then
|
||||
local inv = peripheral.wrap('bottom')
|
||||
if inv and inv.list then
|
||||
for k, v in pairs(inv.list()) do
|
||||
if FUEL[table.concat({ v.name, v.damage }, ':')] then
|
||||
local count = inv.pushItems(dropDir, k, v.count)
|
||||
if count > 0 then
|
||||
turtle.refuel(v.name, v.count)
|
||||
print('Fuel: ' .. turtle.getFuelLevel())
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
elseif b.action == 'smash' then
|
||||
if turtle.digDownAt(b) then
|
||||
if crops[b.name].seed then
|
||||
turtle.placeDown(crops[b.name].seed)
|
||||
end
|
||||
end
|
||||
elseif b.action == 'smash' then
|
||||
if turtle.digDownAt(b) then
|
||||
if crops[b.name].seed then
|
||||
turtle.placeDown(crops[b.name].seed)
|
||||
end
|
||||
end
|
||||
|
||||
elseif b.action == 'plant' then
|
||||
if turtle.digDownAt(b) then
|
||||
turtle.placeDown(crops[b.name].seed)
|
||||
end
|
||||
elseif b.action == 'plant' then
|
||||
if turtle.digDownAt(b) then
|
||||
turtle.placeDown(crops[b.name].seed)
|
||||
end
|
||||
|
||||
elseif b.action == 'bump' then
|
||||
if turtle.faceAgainst(b) then
|
||||
Equipper.equipRight('plethora:module:3', 'plethora:sensor')
|
||||
os.sleep(.5)
|
||||
-- search the ground for the dropped cactus
|
||||
local sensed = peripheral.call('right', 'sense')
|
||||
Equipper.equipRight('minecraft:diamond_pickaxe')
|
||||
Util.filterInplace(sensed, function(s)
|
||||
if s.displayName == 'item.tile.cactus' then
|
||||
s.x = Util.round(s.x) + turtle.point.x
|
||||
s.z = Util.round(s.z) + turtle.point.z
|
||||
s.y = -1
|
||||
if Point.distance(b, s) < 6 then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end)
|
||||
Point.eachClosest(turtle.point, sensed, function(s)
|
||||
turtle.suckDownAt(s)
|
||||
end)
|
||||
end
|
||||
elseif b.action == 'bump' then
|
||||
if turtle.faceAgainst(b) then
|
||||
Equipper.equipRight('plethora:sensor')
|
||||
os.sleep(.5)
|
||||
-- search the ground for the dropped cactus
|
||||
local sensed = peripheral.call('right', 'sense')
|
||||
Equipper.equipRight('minecraft:diamond_pickaxe')
|
||||
Util.filterInplace(sensed, function(s)
|
||||
if s.displayName == 'item.tile.cactus' then
|
||||
s.x = Util.round(s.x) + turtle.point.x
|
||||
s.z = Util.round(s.z) + turtle.point.z
|
||||
s.y = -1
|
||||
if Point.distance(b, s) < 6 then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end)
|
||||
Point.eachClosest(turtle.point, sensed, function(s)
|
||||
turtle.suckDownAt(s)
|
||||
end)
|
||||
end
|
||||
|
||||
elseif b.action == 'pick' then
|
||||
local h = Point.facings[b.state.facing].heading
|
||||
local hi = Point.headings[(h + 2) % 4] -- opposite heading
|
||||
elseif b.action == 'pick' then
|
||||
local h = Point.facings[b.state.facing].heading
|
||||
local hi = Point.headings[(h + 2) % 4] -- opposite heading
|
||||
|
||||
-- without pathfinding, will be unable to circle log
|
||||
if turtle.go({ x = b.x + hi.xd, z = b.z + hi.zd, heading = h }) then
|
||||
if turtle.dig() then
|
||||
turtle.place(crops[b.name].seed)
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
Equipper.equipRight('plethora:module:2', 'plethora:scanner')
|
||||
-- without pathfinding, will be unable to circle log
|
||||
if turtle.go({ x = b.x + hi.xd, z = b.z + hi.zd, heading = h }) then
|
||||
if turtle.dig() then
|
||||
turtle.place(crops[b.name].seed)
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
Equipper.equipRight('plethora:scanner')
|
||||
end
|
||||
|
||||
local s, m = turtle.run(function()
|
||||
local facing = scanner.getBlockMeta(0, 0, 0).state.facing
|
||||
turtle.point.heading = Point.facings[facing].heading
|
||||
local facing = scanner.getBlockMeta(0, 0, 0).state.facing
|
||||
turtle.point.heading = Point.facings[facing].heading
|
||||
|
||||
turtle.setStatus('farming')
|
||||
print('Fuel: ' .. turtle.getFuelLevel())
|
||||
turtle.setStatus('farming')
|
||||
print('Fuel: ' .. turtle.getFuelLevel())
|
||||
|
||||
turtle.setMovementStrategy('goto')
|
||||
repeat
|
||||
local blocks, harvestCount = scan()
|
||||
if harvestCount > 0 then
|
||||
turtle.setStatus('Harvesting')
|
||||
harvest(blocks)
|
||||
turtle.setStatus('Sleeping')
|
||||
end
|
||||
os.sleep(10)
|
||||
if turtle.getFuelLevel() < 10 then
|
||||
turtle.setStatus('Out of fuel')
|
||||
error('Out of fuel')
|
||||
end
|
||||
until turtle.isAborted()
|
||||
turtle.setMovementStrategy('goto')
|
||||
repeat
|
||||
local blocks, harvestCount = scan()
|
||||
if harvestCount > 0 then
|
||||
turtle.setStatus('Harvesting')
|
||||
harvest(blocks)
|
||||
turtle.setStatus('Sleeping')
|
||||
end
|
||||
os.sleep(10)
|
||||
if turtle.getFuelLevel() < 10 then
|
||||
turtle.setStatus('Out of fuel')
|
||||
error('Out of fuel')
|
||||
end
|
||||
until turtle.isAborted()
|
||||
end)
|
||||
|
||||
if not s and m then
|
||||
error(m)
|
||||
error(m)
|
||||
end
|
||||
|
||||
@@ -9,8 +9,9 @@ Requirements
|
||||
* Standard Modem
|
||||
* Block Scanner
|
||||
* Entity Sensor
|
||||
* Crafting Table
|
||||
* Furnace
|
||||
* Vanilla Chest
|
||||
* Sapling
|
||||
* GPS
|
||||
|
||||
Setup
|
||||
@@ -19,18 +20,20 @@ Setup
|
||||
> package install farms
|
||||
> reboot
|
||||
|
||||
The turtle will need some fuel initially.
|
||||
|
||||
The tree farm fits exactly in one chunk. It's best to have a mostly level ground around the center of the farming area as the turtle will only collect saplings that have fallen to the same level as the turtle.
|
||||
|
||||
To align the turtle perfectly in one chunk, position the turtle 8 blocks diagonally from the bottom left corner.
|
||||
|
||||
Place a sapling directly in front of the turtle and place all the required items into the inventory.
|
||||
Place all the required items into the inventory.
|
||||
|
||||
To start the program, run:
|
||||
|
||||
> superTreefarm
|
||||
A startup file is created automatically the first time the program is run (usr/autorun/superTreefarm.lua).
|
||||
|
||||
If the turtle does not get any saplings from the initial tree, place down another sapling in front of the turtle.
|
||||
If the turtle does not get any saplings from the initial tree, another sapling in the turtle.
|
||||
|
||||
Tips
|
||||
====
|
||||
|
||||
@@ -10,111 +10,117 @@ local turtle = _G.turtle
|
||||
local STARTUP_FILE = 'usr/autorun/rancher.lua'
|
||||
|
||||
local retain = Util.transpose {
|
||||
'minecraft:shears',
|
||||
'minecraft:wheat',
|
||||
'minecraft:diamond_sword',
|
||||
'plethora:module:3',
|
||||
'minecraft:shears',
|
||||
'minecraft:wheat',
|
||||
'minecraft:diamond_sword',
|
||||
'plethora:module:3',
|
||||
}
|
||||
local config = {
|
||||
animal = 'Cow',
|
||||
max_animals = 15,
|
||||
animal = 'Cow',
|
||||
max_animals = 15,
|
||||
}
|
||||
Config.load('rancher', config)
|
||||
|
||||
local ANIMALS = {
|
||||
Pig = { min = 0, food = 'minecraft:carrot' },
|
||||
Sheep = { min = .5, food = 'minecraft:wheat' },
|
||||
Cow = { min = .5, food = 'minecraft:wheat' },
|
||||
Pig = { min = 0, food = 'minecraft:carrot' },
|
||||
Sheep = { min = .5, food = 'minecraft:wheat' },
|
||||
Cow = { min = .5, food = 'minecraft:wheat' },
|
||||
}
|
||||
|
||||
local animal = ANIMALS[config.animal]
|
||||
|
||||
Equipper.equipLeft('minecraft:diamond_sword')
|
||||
local sensor = Equipper.equipRight('plethora:module:3', 'plethora:sensor')
|
||||
local sensor = Equipper.equipRight('plethora:sensor')
|
||||
|
||||
local chest = Adapter({ side = 'bottom', direction = 'up' }) or error('missing chest')
|
||||
|
||||
if not fs.exists(STARTUP_FILE) then
|
||||
Util.writeFile(STARTUP_FILE,
|
||||
[[os.sleep(1)
|
||||
Util.writeFile(STARTUP_FILE,
|
||||
[[os.sleep(1)
|
||||
shell.openForegroundTab('rancher.lua')]])
|
||||
print('Autorun program created: ' .. STARTUP_FILE)
|
||||
print('Autorun program created: ' .. STARTUP_FILE)
|
||||
end
|
||||
|
||||
local function getAnimalCount()
|
||||
local blocks = sensor.sense()
|
||||
local blocks = sensor.sense()
|
||||
|
||||
local grown = 0
|
||||
local babies = 0
|
||||
local grown = 0
|
||||
local babies = 0
|
||||
|
||||
Util.filterInplace(blocks, function(v)
|
||||
if v.name == config.animal then
|
||||
if v.y > -.5 then grown = grown + 1 end
|
||||
if v.y < -.5 then babies = babies + 1 end
|
||||
return v.y > -.5
|
||||
end
|
||||
end)
|
||||
Util.filterInplace(blocks, function(v)
|
||||
if v.name == config.animal then
|
||||
local entity = sensor.getMetaByID(v.id)
|
||||
if entity then
|
||||
if entity.isChild then
|
||||
babies = babies + 1
|
||||
else
|
||||
grown = grown + 1
|
||||
end
|
||||
return not entity.isChild
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
Util.print('%d grown, %d babies', grown, babies)
|
||||
Util.print('%d grown, %d babies', grown, babies)
|
||||
|
||||
return #blocks
|
||||
return #blocks
|
||||
end
|
||||
|
||||
local function butcher()
|
||||
Equipper.equipRight('minecraft:diamond_sword')
|
||||
turtle.select(1)
|
||||
Equipper.equipRight('minecraft:diamond_sword')
|
||||
turtle.select(1)
|
||||
|
||||
turtle.attack()
|
||||
for _ = 1, 3 do
|
||||
turtle.turnRight()
|
||||
turtle.attack()
|
||||
end
|
||||
Equipper.equipRight('plethora:module:3', 'plethora:sensor')
|
||||
turtle.attack()
|
||||
for _ = 1, 3 do
|
||||
turtle.turnRight()
|
||||
turtle.attack()
|
||||
end
|
||||
Equipper.equipRight('plethora:sensor')
|
||||
|
||||
turtle.eachFilledSlot(function(slot)
|
||||
if not retain[slot.name] then
|
||||
chest:insert(slot.index, 64)
|
||||
end
|
||||
end)
|
||||
turtle.eachFilledSlot(function(slot)
|
||||
if not retain[slot.name] then
|
||||
chest:insert(slot.index, 64)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
local function breed()
|
||||
turtle.select(1)
|
||||
turtle.select(1)
|
||||
|
||||
if config.animal == 'Sheep' then
|
||||
turtle.place('minecraft:shears')
|
||||
end
|
||||
turtle.place('minecraft:wheat')
|
||||
for _ = 1, 3 do
|
||||
turtle.turnRight()
|
||||
if config.animal == 'Sheep' then
|
||||
turtle.place('minecraft:shears')
|
||||
end
|
||||
turtle.place('minecraft:wheat')
|
||||
end
|
||||
if config.animal == 'Sheep' then
|
||||
turtle.place('minecraft:shears')
|
||||
end
|
||||
turtle.place('minecraft:wheat')
|
||||
for _ = 1, 3 do
|
||||
turtle.turnRight()
|
||||
if config.animal == 'Sheep' then
|
||||
turtle.place('minecraft:shears')
|
||||
end
|
||||
turtle.place('minecraft:wheat')
|
||||
end
|
||||
end
|
||||
|
||||
local s, m = turtle.run(function()
|
||||
print('Configured animal: ' .. config.animal)
|
||||
print('Configured animal: ' .. config.animal)
|
||||
|
||||
repeat
|
||||
local animalCount = getAnimalCount()
|
||||
if animalCount > config.max_animals then
|
||||
turtle.setStatus('Butchering')
|
||||
butcher()
|
||||
elseif turtle.getItemCount(animal.food) == 0 then
|
||||
if chest:provide({ name = animal.food, damage = 0 }, 64) == 0 then
|
||||
print('Out of ' .. animal.food)
|
||||
turtle.setStatus('Out of food')
|
||||
end
|
||||
else
|
||||
turtle.setStatus('Breeding')
|
||||
breed()
|
||||
end
|
||||
os.sleep(5)
|
||||
until turtle.isAborted()
|
||||
repeat
|
||||
local animalCount = getAnimalCount()
|
||||
if animalCount > config.max_animals then
|
||||
turtle.setStatus('Butchering')
|
||||
butcher()
|
||||
elseif turtle.getItemCount(animal.food) == 0 then
|
||||
if chest:provide({ name = animal.food, damage = 0 }, 64) == 0 then
|
||||
print('Out of ' .. animal.food)
|
||||
turtle.setStatus('Out of food')
|
||||
end
|
||||
else
|
||||
turtle.setStatus('Breeding')
|
||||
breed()
|
||||
end
|
||||
os.sleep(5)
|
||||
until turtle.isAborted()
|
||||
end)
|
||||
|
||||
if not s and m then
|
||||
error(m)
|
||||
error(m)
|
||||
end
|
||||
|
||||
@@ -11,7 +11,7 @@ local STARTUP_FILE = 'usr/autorun/spawner.lua'
|
||||
local mobTypes = { }
|
||||
|
||||
Equipper.equipLeft('minecraft:diamond_sword')
|
||||
local scanner = Equipper.equipRight('plethora:module:2', 'plethora:scanner')
|
||||
local scanner = Equipper.equipRight('plethora:scanner')
|
||||
|
||||
turtle.reset()
|
||||
local facing = scanner.getBlockMeta(0, 0, 0).state.facing
|
||||
@@ -28,13 +28,13 @@ Util.filterInplace(data, function(b)
|
||||
end)
|
||||
local chest = Point.closest(spawner, data) or error('missing drop off chest')
|
||||
|
||||
local sensor = Equipper.equipRight('plethora:module:3', 'plethora:sensor')
|
||||
local sensor = Equipper.equipRight('plethora:sensor')
|
||||
|
||||
if not fs.exists(STARTUP_FILE) then
|
||||
Util.writeFile(STARTUP_FILE,
|
||||
string.format([[os.sleep(1)
|
||||
Util.writeFile(STARTUP_FILE,
|
||||
string.format([[os.sleep(1)
|
||||
shell.openForegroundTab('spawner.lua %s')]], table.concat({ ... }, ' ')))
|
||||
print('Autorun program created: ' .. STARTUP_FILE)
|
||||
print('Autorun program created: ' .. STARTUP_FILE)
|
||||
end
|
||||
|
||||
turtle.setMovementStrategy('goto')
|
||||
@@ -70,8 +70,8 @@ local function normalize(b)
|
||||
b.z = Util.round(b.z) + turtle.point.z
|
||||
|
||||
return b.x >= spawner.x - 4 and b.x <= spawner.x + 4 and
|
||||
b.y >= spawner.y - 4 and b.y <= spawner.y + 4 and
|
||||
b.z >= spawner.z - 4 and b.z <= spawner.z + 4
|
||||
b.y >= spawner.y - 4 and b.y <= spawner.y + 4 and
|
||||
b.z >= spawner.z - 4 and b.z <= spawner.z + 4
|
||||
end
|
||||
|
||||
local function aboveAttack(b)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
1010
farms/treefarm.lua
1010
farms/treefarm.lua
File diff suppressed because it is too large
Load Diff
@@ -3,25 +3,25 @@ local Sound = require('sound')
|
||||
local os = _G.os
|
||||
|
||||
local tunes = {
|
||||
{ sound = 'record.11', length = '1:11' },
|
||||
{ sound = 'record.13', length = '2:58' },
|
||||
{ sound = 'record.blocks', length = '5:45' },
|
||||
{ sound = 'record.cat', length = '3:05' },
|
||||
{ sound = 'record.chirp', length = '3:05' },
|
||||
{ sound = 'record.far', length = '2:54' },
|
||||
{ sound = 'record.mall', length = '3:17' },
|
||||
{ sound = 'record.mellohi', length = '1:36' },
|
||||
{ sound = 'record.stal', length = '2:30' },
|
||||
{ sound = 'record.strad', length = '3:08' },
|
||||
{ sound = 'record.wait', length = '3:58' },
|
||||
{ sound = 'record.ward', length = '4:11' },
|
||||
{ sound = 'record.11', length = '1:11' },
|
||||
{ sound = 'record.13', length = '2:58' },
|
||||
{ sound = 'record.blocks', length = '5:45' },
|
||||
{ sound = 'record.cat', length = '3:05' },
|
||||
{ sound = 'record.chirp', length = '3:05' },
|
||||
{ sound = 'record.far', length = '2:54' },
|
||||
{ sound = 'record.mall', length = '3:17' },
|
||||
{ sound = 'record.mellohi', length = '1:36' },
|
||||
{ sound = 'record.stal', length = '2:30' },
|
||||
{ sound = 'record.strad', length = '3:08' },
|
||||
{ sound = 'record.wait', length = '3:58' },
|
||||
{ sound = 'record.ward', length = '4:11' },
|
||||
}
|
||||
|
||||
while true do
|
||||
local song = tunes[math.random(1, #tunes)]
|
||||
Sound.play(song.sound)
|
||||
local min, sec = song.length:match('(%d+):(%d+)')
|
||||
local length = tonumber(min)*60 + tonumber(sec)
|
||||
print(string.format('Playing %s (%s)', song.sound, song.length))
|
||||
os.sleep(length + 3)
|
||||
local song = tunes[math.random(1, #tunes)]
|
||||
Sound.play(song.sound)
|
||||
local min, sec = song.length:match('(%d+):(%d+)')
|
||||
local length = tonumber(min)*60 + tonumber(sec)
|
||||
print(string.format('Playing %s (%s)', song.sound, song.length))
|
||||
os.sleep(length + 3)
|
||||
end
|
||||
|
||||
@@ -3,6 +3,7 @@ local GPS = require('gps')
|
||||
local Util = require('util')
|
||||
|
||||
local args = { ... }
|
||||
local colors = _G.colors
|
||||
local fs = _G.fs
|
||||
local gps = _G.gps
|
||||
local os = _G.os
|
||||
|
||||
@@ -16,68 +16,68 @@ local turtle = _G.turtle
|
||||
multishell.setTitle(multishell.getCurrent(), 'Milo')
|
||||
|
||||
local function Syntax(msg)
|
||||
print([[
|
||||
print([[
|
||||
Turtle must be provided with:
|
||||
* Introspection module (never bound)
|
||||
* Workbench
|
||||
* Introspection module (never bound)
|
||||
* Workbench
|
||||
|
||||
Turtle must be connected to:
|
||||
* Wired modem (activated)
|
||||
* Wired modem (activated)
|
||||
]])
|
||||
|
||||
error(msg)
|
||||
error(msg)
|
||||
end
|
||||
|
||||
local modem
|
||||
for _,v in pairs(device) do
|
||||
if v.type == 'wired_modem' then
|
||||
if modem then
|
||||
Syntax('Only 1 wired modem can be connected')
|
||||
end
|
||||
modem = v
|
||||
end
|
||||
if v.type == 'wired_modem' then
|
||||
if modem then
|
||||
Syntax('Only 1 wired modem can be connected')
|
||||
end
|
||||
modem = v
|
||||
end
|
||||
end
|
||||
|
||||
if not modem or not modem.getNameLocal then
|
||||
Syntax('Wired modem missing')
|
||||
Syntax('Wired modem missing')
|
||||
end
|
||||
|
||||
if not modem.getNameLocal() then
|
||||
Syntax('Wired modem is not active')
|
||||
Syntax('Wired modem is not active')
|
||||
end
|
||||
|
||||
local introspection = device['plethora:introspection'] or
|
||||
turtle.equip('left', 'plethora:module:0') and device['plethora:introspection'] or
|
||||
Syntax('Introspection module missing')
|
||||
turtle.equip('left', 'plethora:module:0') and device['plethora:introspection'] or
|
||||
Syntax('Introspection module missing')
|
||||
|
||||
if not device.workbench then
|
||||
turtle.equip('right', 'minecraft:crafting_table:0')
|
||||
if not device.workbench then
|
||||
Syntax('Workbench missing')
|
||||
end
|
||||
turtle.equip('right', 'minecraft:crafting_table:0')
|
||||
if not device.workbench then
|
||||
Syntax('Workbench missing')
|
||||
end
|
||||
end
|
||||
|
||||
local localName = modem.getNameLocal()
|
||||
|
||||
local context = {
|
||||
resources = Util.readTable(Milo.RESOURCE_FILE) or { },
|
||||
resources = Util.readTable(Milo.RESOURCE_FILE) or { },
|
||||
|
||||
state = { },
|
||||
craftingQueue = { },
|
||||
tasks = { },
|
||||
queue = { },
|
||||
plugins = { },
|
||||
loggers = { },
|
||||
state = { },
|
||||
craftingQueue = { },
|
||||
tasks = { },
|
||||
queue = { },
|
||||
plugins = { },
|
||||
loggers = { },
|
||||
|
||||
taskTimer = 0,
|
||||
taskCounter = 0,
|
||||
taskTimer = 0,
|
||||
taskCounter = 0,
|
||||
|
||||
storage = Storage(),
|
||||
turtleInventory = {
|
||||
name = localName,
|
||||
mtype = 'hidden',
|
||||
adapter = introspection.getInventory(),
|
||||
}
|
||||
storage = Storage(),
|
||||
turtleInventory = {
|
||||
name = localName,
|
||||
mtype = 'hidden',
|
||||
adapter = introspection.getInventory(),
|
||||
}
|
||||
}
|
||||
|
||||
context.storage.nodes[localName] = context.turtleInventory
|
||||
@@ -88,23 +88,23 @@ context.storage:initStorage()
|
||||
context.storage.turtleInventory = context.turtleInventory
|
||||
|
||||
local function loadPlugin(file)
|
||||
local s, plugin = Util.run(_ENV, file, context)
|
||||
if not s and plugin then
|
||||
_G.printError('Error loading: ' .. file)
|
||||
error(plugin or 'Unknown error')
|
||||
end
|
||||
local s, plugin = Util.run(_ENV, file, context)
|
||||
if not s and plugin then
|
||||
_G.printError('Error loading: ' .. file)
|
||||
error(plugin or 'Unknown error')
|
||||
end
|
||||
|
||||
if plugin and type(plugin) == 'table' then
|
||||
Milo:registerPlugin(plugin)
|
||||
end
|
||||
if plugin and type(plugin) == 'table' then
|
||||
Milo:registerPlugin(plugin)
|
||||
end
|
||||
end
|
||||
|
||||
local function loadDirectory(dir)
|
||||
for _, file in pairs(fs.list(dir)) do
|
||||
if not fs.isDir(fs.combine(dir, file)) then
|
||||
loadPlugin(fs.combine(dir, file))
|
||||
end
|
||||
end
|
||||
for _, file in pairs(fs.list(dir)) do
|
||||
if not fs.isDir(fs.combine(dir, file)) then
|
||||
loadPlugin(fs.combine(dir, file))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local programDir = fs.getDir(shell.getRunningProgram())
|
||||
@@ -113,17 +113,17 @@ loadDirectory(fs.combine(programDir, 'plugins'))
|
||||
loadDirectory(fs.combine(programDir, 'plugins/item'))
|
||||
|
||||
for k in pairs(Milo:getState('plugins') or { }) do
|
||||
loadPlugin(k)
|
||||
loadPlugin(k)
|
||||
end
|
||||
|
||||
table.sort(context.tasks, function(a, b)
|
||||
return a.priority < b.priority
|
||||
return a.priority < b.priority
|
||||
end)
|
||||
|
||||
_G._syslog('Tasks\n-----')
|
||||
for _, task in ipairs(context.tasks) do
|
||||
task.execTime = 0
|
||||
_G._syslog('%d: %s', task.priority, task.name)
|
||||
task.execTime = 0
|
||||
_G._syslog('%d: %s', task.priority, task.name)
|
||||
end
|
||||
|
||||
Milo:clearGrid()
|
||||
@@ -132,92 +132,92 @@ UI:setPage(UI:getPage('listing'))
|
||||
Sound.play('ui.toast.challenge_complete')
|
||||
|
||||
Event.on({ 'milo_cycle', 'milo_queue' }, function(e)
|
||||
if context.storage:isOnline() then
|
||||
if #context.queue > 0 then
|
||||
local queue = context.queue
|
||||
context.queue = { }
|
||||
for _, entry in pairs(queue) do
|
||||
local s, m = pcall(entry.callback, entry.request)
|
||||
if not s and m then
|
||||
_G._syslog('callback crashed')
|
||||
_G._syslog(m)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if context.storage:isOnline() then
|
||||
if #context.queue > 0 then
|
||||
local queue = context.queue
|
||||
context.queue = { }
|
||||
for _, entry in pairs(queue) do
|
||||
local s, m = pcall(entry.callback, entry.request)
|
||||
if not s and m then
|
||||
_G._syslog('callback crashed')
|
||||
_G._syslog(m)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if e == 'milo_cycle' and not Milo:isCraftingPaused() then
|
||||
local taskTimer = Util.timer()
|
||||
Milo:resetCraftingStatus()
|
||||
if e == 'milo_cycle' and not Milo:isCraftingPaused() then
|
||||
local taskTimer = Util.timer()
|
||||
Milo:resetCraftingStatus()
|
||||
|
||||
for _, task in ipairs(context.tasks) do
|
||||
local timer = Util.timer()
|
||||
local s, m = pcall(function() task:cycle(context) end)
|
||||
if not s and m then
|
||||
_G._syslog(task.name .. ' crashed')
|
||||
_G._syslog(m)
|
||||
end
|
||||
task.execTime = task.execTime + timer()
|
||||
end
|
||||
context.taskTimer = context.taskTimer + taskTimer()
|
||||
context.taskCounter = context.taskCounter + 1
|
||||
for _, task in ipairs(context.tasks) do
|
||||
local timer = Util.timer()
|
||||
local s, m = pcall(function() task:cycle(context) end)
|
||||
if not s and m then
|
||||
_G._syslog(task.name .. ' crashed')
|
||||
_G._syslog(m)
|
||||
end
|
||||
task.execTime = task.execTime + timer()
|
||||
end
|
||||
context.taskTimer = context.taskTimer + taskTimer()
|
||||
context.taskCounter = context.taskCounter + 1
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
if context.storage:isOnline() and #context.queue > 0 then
|
||||
os.queueEvent('milo_cycle')
|
||||
end
|
||||
if context.storage:isOnline() and #context.queue > 0 then
|
||||
os.queueEvent('milo_cycle')
|
||||
end
|
||||
end)
|
||||
|
||||
Event.on('turtle_inventory', function()
|
||||
Milo:queueRequest({ }, function()
|
||||
if not Milo:isCraftingPaused() then
|
||||
Milo:clearGrid()
|
||||
end
|
||||
end)
|
||||
Milo:queueRequest({ }, function()
|
||||
if not Milo:isCraftingPaused() then
|
||||
Milo:clearGrid()
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
local cycleHandle
|
||||
cycleHandle = Event.onInterval(5, function()
|
||||
Event.trigger('milo_cycle')
|
||||
if context.taskCounter > 0 then
|
||||
--local average = context.taskTimer / context.taskCounter
|
||||
--_syslog('Interval: ' .. math.max(5, 2 + average * 3))
|
||||
--cycleHandle.updateInterval(math.max(5, 2 + average * 3))
|
||||
end
|
||||
Event.trigger('milo_cycle')
|
||||
if context.taskCounter > 0 then
|
||||
--local average = context.taskTimer / context.taskCounter
|
||||
--_syslog('Interval: ' .. math.max(5, 2 + average * 3))
|
||||
--cycleHandle.updateInterval(math.max(5, 2 + average * 3))
|
||||
end
|
||||
end)
|
||||
|
||||
Event.on({ 'storage_offline', 'storage_online' }, function()
|
||||
if context.storage:isOnline() then
|
||||
Milo:resumeCrafting({ key = 'storageOnline' })
|
||||
else
|
||||
Milo:pauseCrafting({ key = 'storageOnline', msg = 'Storage offline' })
|
||||
end
|
||||
if context.storage:isOnline() then
|
||||
Milo:resumeCrafting({ key = 'storageOnline' })
|
||||
else
|
||||
Milo:pauseCrafting({ key = 'storageOnline', msg = 'Storage offline' })
|
||||
end
|
||||
end)
|
||||
|
||||
Event.on('terminate', function()
|
||||
for _, node in pairs(context.storage.nodes) do
|
||||
if node.category == 'display' and node.adapter and node.adapter.clear then
|
||||
node.adapter.setBackgroundColor(colors.black)
|
||||
node.adapter.clear()
|
||||
end
|
||||
end
|
||||
for _, node in pairs(context.storage.nodes) do
|
||||
if node.category == 'display' and node.adapter and node.adapter.clear then
|
||||
node.adapter.setBackgroundColor(colors.black)
|
||||
node.adapter.clear()
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
os.queueEvent(
|
||||
context.storage:isOnline() and 'storage_online' or 'storage_offline',
|
||||
context.storage:isOnline())
|
||||
context.storage:isOnline() and 'storage_online' or 'storage_offline',
|
||||
context.storage:isOnline())
|
||||
|
||||
local oldDebug = _G._syslog
|
||||
_G._syslog = function(...)
|
||||
for _,v in pairs(context.loggers) do
|
||||
v(...)
|
||||
end
|
||||
oldDebug(...)
|
||||
for _,v in pairs(context.loggers) do
|
||||
v(...)
|
||||
end
|
||||
oldDebug(...)
|
||||
end
|
||||
|
||||
local s, m = pcall(function()
|
||||
UI:pullEvents()
|
||||
UI:pullEvents()
|
||||
end)
|
||||
|
||||
_G._syslog = oldDebug
|
||||
|
||||
@@ -13,512 +13,512 @@ local peripheral = _G.peripheral
|
||||
local shell = _ENV.shell
|
||||
|
||||
local context = {
|
||||
state = Config.load('miloRemote', { displayMode = 0, deposit = true }),
|
||||
responseHandlers = { },
|
||||
state = Config.load('miloRemote', { displayMode = 0, deposit = true }),
|
||||
responseHandlers = { },
|
||||
}
|
||||
|
||||
local depositMode = {
|
||||
[ true ] = { text = '\25', textColor = colors.black, help = 'Deposit enabled' },
|
||||
[ false ] = { text = '\215', textColor = colors.red, help = 'Deposit disabled' },
|
||||
[ true ] = { text = '\25', textColor = colors.black, help = 'Deposit enabled' },
|
||||
[ false ] = { text = '\215', textColor = colors.red, help = 'Deposit disabled' },
|
||||
}
|
||||
|
||||
local displayModes = {
|
||||
[0] = { text = 'A', help = 'Showing all items' },
|
||||
[1] = { text = 'I', help = 'Showing inventory items' },
|
||||
[0] = { text = 'A', help = 'Showing all items' },
|
||||
[1] = { text = 'I', help = 'Showing inventory items' },
|
||||
}
|
||||
|
||||
local page = UI.Page {
|
||||
menuBar = UI.MenuBar {
|
||||
buttons = {
|
||||
{
|
||||
text = 'Refresh',
|
||||
x = -12,
|
||||
event = 'refresh'
|
||||
},
|
||||
{
|
||||
name = 'config',
|
||||
text = '\187',
|
||||
x = -3,
|
||||
},
|
||||
},
|
||||
infoBar = UI.StatusBar {
|
||||
x = 1, ex = -16,
|
||||
backgroundColor = colors.lightGray,
|
||||
},
|
||||
},
|
||||
grid = UI.Grid {
|
||||
y = 2, ey = -2,
|
||||
columns = {
|
||||
{ heading = ' Qty', key = 'count' , width = 4, align = 'right' },
|
||||
{ heading = 'Name', key = 'displayName' },
|
||||
},
|
||||
values = { },
|
||||
sortColumn = context.state.sortColumn or 'count',
|
||||
inverseSort = context.state.inverseSort,
|
||||
help = '^(s)tack, ^(a)ll'
|
||||
},
|
||||
statusBar = UI.Window {
|
||||
y = -1,
|
||||
filter = UI.TextEntry {
|
||||
x = 1, ex = -12,
|
||||
limit = 50,
|
||||
shadowText = 'filter',
|
||||
backgroundColor = colors.cyan,
|
||||
backgroundFocusColor = colors.cyan,
|
||||
accelerators = {
|
||||
[ 'enter' ] = 'eject',
|
||||
[ 'up' ] = 'grid_up',
|
||||
[ 'down' ] = 'grid_down',
|
||||
[ 'control-a' ] = 'eject_all',
|
||||
},
|
||||
},
|
||||
amount = UI.TextEntry {
|
||||
x = -11, ex = -7,
|
||||
limit = 3,
|
||||
shadowText = '1',
|
||||
shadowTextColor = colors.gray,
|
||||
backgroundColor = colors.black,
|
||||
backgroundFocusColor = colors.black,
|
||||
accelerators = {
|
||||
[ 'enter' ] = 'eject_specified',
|
||||
[ 'control-a' ] = 'eject_all',
|
||||
},
|
||||
help = 'Request amount',
|
||||
},
|
||||
depositToggle = UI.Button {
|
||||
x = -6,
|
||||
event = 'toggle_deposit',
|
||||
text = '\215',
|
||||
},
|
||||
display = UI.Button {
|
||||
x = -3,
|
||||
event = 'toggle_display',
|
||||
text = displayModes[context.state.displayMode].text,
|
||||
help = displayModes[context.state.displayMode].help,
|
||||
},
|
||||
},
|
||||
notification = UI.Notification {
|
||||
anchor = 'top',
|
||||
},
|
||||
accelerators = {
|
||||
r = 'refresh',
|
||||
[ 'control-r' ] = 'refresh',
|
||||
[ 'control-e' ] = 'eject',
|
||||
[ 'control-s' ] = 'eject_stack',
|
||||
[ 'control-a' ] = 'eject_all',
|
||||
menuBar = UI.MenuBar {
|
||||
buttons = {
|
||||
{
|
||||
text = 'Refresh',
|
||||
x = -12,
|
||||
event = 'refresh'
|
||||
},
|
||||
{
|
||||
name = 'config',
|
||||
text = '\187',
|
||||
x = -3,
|
||||
},
|
||||
},
|
||||
infoBar = UI.StatusBar {
|
||||
x = 1, ex = -16,
|
||||
backgroundColor = colors.lightGray,
|
||||
},
|
||||
},
|
||||
grid = UI.Grid {
|
||||
y = 2, ey = -2,
|
||||
columns = {
|
||||
{ heading = ' Qty', key = 'count' , width = 4, align = 'right' },
|
||||
{ heading = 'Name', key = 'displayName' },
|
||||
},
|
||||
values = { },
|
||||
sortColumn = context.state.sortColumn or 'count',
|
||||
inverseSort = context.state.inverseSort,
|
||||
help = '^(s)tack, ^(a)ll'
|
||||
},
|
||||
statusBar = UI.Window {
|
||||
y = -1,
|
||||
filter = UI.TextEntry {
|
||||
x = 1, ex = -12,
|
||||
limit = 50,
|
||||
shadowText = 'filter',
|
||||
backgroundColor = colors.cyan,
|
||||
backgroundFocusColor = colors.cyan,
|
||||
accelerators = {
|
||||
[ 'enter' ] = 'eject',
|
||||
[ 'up' ] = 'grid_up',
|
||||
[ 'down' ] = 'grid_down',
|
||||
[ 'control-a' ] = 'eject_all',
|
||||
},
|
||||
},
|
||||
amount = UI.TextEntry {
|
||||
x = -11, ex = -7,
|
||||
limit = 3,
|
||||
shadowText = '1',
|
||||
shadowTextColor = colors.gray,
|
||||
backgroundColor = colors.black,
|
||||
backgroundFocusColor = colors.black,
|
||||
accelerators = {
|
||||
[ 'enter' ] = 'eject_specified',
|
||||
[ 'control-a' ] = 'eject_all',
|
||||
},
|
||||
help = 'Request amount',
|
||||
},
|
||||
depositToggle = UI.Button {
|
||||
x = -6,
|
||||
event = 'toggle_deposit',
|
||||
text = '\215',
|
||||
},
|
||||
display = UI.Button {
|
||||
x = -3,
|
||||
event = 'toggle_display',
|
||||
text = displayModes[context.state.displayMode].text,
|
||||
help = displayModes[context.state.displayMode].help,
|
||||
},
|
||||
},
|
||||
notification = UI.Notification {
|
||||
anchor = 'top',
|
||||
},
|
||||
accelerators = {
|
||||
r = 'refresh',
|
||||
[ 'control-r' ] = 'refresh',
|
||||
[ 'control-e' ] = 'eject',
|
||||
[ 'control-s' ] = 'eject_stack',
|
||||
[ 'control-a' ] = 'eject_all',
|
||||
|
||||
q = 'quit',
|
||||
},
|
||||
items = { },
|
||||
q = 'quit',
|
||||
},
|
||||
items = { },
|
||||
}
|
||||
|
||||
local function getPlayerName()
|
||||
local neural = peripheral.find('neuralInterface')
|
||||
local neural = peripheral.find('neuralInterface')
|
||||
|
||||
if neural and neural.getName then
|
||||
return neural.getName()
|
||||
end
|
||||
if neural and neural.getName then
|
||||
return neural.getName()
|
||||
end
|
||||
end
|
||||
|
||||
function page.grid:getRowTextColor(row, selected)
|
||||
if row.is_craftable then
|
||||
return colors.yellow
|
||||
end
|
||||
if row.has_recipe then
|
||||
return colors.cyan
|
||||
end
|
||||
return UI.Grid:getRowTextColor(row, selected)
|
||||
if row.is_craftable then
|
||||
return colors.yellow
|
||||
end
|
||||
if row.has_recipe then
|
||||
return colors.cyan
|
||||
end
|
||||
return UI.Grid:getRowTextColor(row, selected)
|
||||
end
|
||||
|
||||
function page.grid:getDisplayValues(row)
|
||||
row = Util.shallowCopy(row)
|
||||
row.count = row.count > 0 and Util.toBytes(row.count) or ''
|
||||
return row
|
||||
row = Util.shallowCopy(row)
|
||||
row.count = row.count > 0 and Util.toBytes(row.count) or ''
|
||||
return row
|
||||
end
|
||||
|
||||
function page.grid:sortCompare(a, b)
|
||||
if self.sortColumn ~= 'displayName' then
|
||||
if a[self.sortColumn] == b[self.sortColumn] then
|
||||
if self.inverseSort then
|
||||
return a.displayName > b.displayName
|
||||
end
|
||||
return a.displayName < b.displayName
|
||||
end
|
||||
if a[self.sortColumn] == 0 then
|
||||
return self.inverseSort
|
||||
end
|
||||
if b[self.sortColumn] == 0 then
|
||||
return not self.inverseSort
|
||||
end
|
||||
return a[self.sortColumn] < b[self.sortColumn]
|
||||
end
|
||||
return UI.Grid.sortCompare(self, a, b)
|
||||
if self.sortColumn ~= 'displayName' then
|
||||
if a[self.sortColumn] == b[self.sortColumn] then
|
||||
if self.inverseSort then
|
||||
return a.displayName > b.displayName
|
||||
end
|
||||
return a.displayName < b.displayName
|
||||
end
|
||||
if a[self.sortColumn] == 0 then
|
||||
return self.inverseSort
|
||||
end
|
||||
if b[self.sortColumn] == 0 then
|
||||
return not self.inverseSort
|
||||
end
|
||||
return a[self.sortColumn] < b[self.sortColumn]
|
||||
end
|
||||
return UI.Grid.sortCompare(self, a, b)
|
||||
end
|
||||
|
||||
function page.grid:eventHandler(event)
|
||||
if event.type == 'grid_sort' then
|
||||
context.state.sortColumn = event.sortColumn
|
||||
context.state.inverseSort = event.inverseSort
|
||||
Config.update('miloRemote', context.state)
|
||||
end
|
||||
return UI.Grid.eventHandler(self, event)
|
||||
if event.type == 'grid_sort' then
|
||||
context.state.sortColumn = event.sortColumn
|
||||
context.state.inverseSort = event.inverseSort
|
||||
Config.update('miloRemote', context.state)
|
||||
end
|
||||
return UI.Grid.eventHandler(self, event)
|
||||
end
|
||||
|
||||
function page:transfer(item, count, msg)
|
||||
context:sendRequest({ request = 'transfer', item = item, count = count }, msg)
|
||||
context:sendRequest({ request = 'transfer', item = item, count = count }, msg)
|
||||
end
|
||||
|
||||
function page:eventHandler(event)
|
||||
if event.type == 'quit' then
|
||||
UI:exitPullEvents()
|
||||
if event.type == 'quit' then
|
||||
UI:exitPullEvents()
|
||||
|
||||
elseif event.type == 'setup' then
|
||||
self.setup.form:setValues(context.state)
|
||||
self.setup:show()
|
||||
elseif event.type == 'setup' then
|
||||
self.setup.form:setValues(context.state)
|
||||
self.setup:show()
|
||||
|
||||
elseif event.type == 'toggle_deposit' then
|
||||
context.state.deposit = not context.state.deposit
|
||||
Util.merge(self.statusBar.depositToggle, depositMode[context.state.deposit])
|
||||
self.statusBar:draw()
|
||||
context:setStatus(depositMode[context.state.deposit].help)
|
||||
context:notifyInfo(depositMode[context.state.deposit].help)
|
||||
Config.update('miloRemote', context.state)
|
||||
elseif event.type == 'toggle_deposit' then
|
||||
context.state.deposit = not context.state.deposit
|
||||
Util.merge(self.statusBar.depositToggle, depositMode[context.state.deposit])
|
||||
self.statusBar:draw()
|
||||
context:setStatus(depositMode[context.state.deposit].help)
|
||||
context:notifyInfo(depositMode[context.state.deposit].help)
|
||||
Config.update('miloRemote', context.state)
|
||||
|
||||
elseif event.type == 'focus_change' then
|
||||
context:setStatus(event.focused.help)
|
||||
elseif event.type == 'focus_change' then
|
||||
context:setStatus(event.focused.help)
|
||||
|
||||
elseif event.type == 'eject' or event.type == 'grid_select' then
|
||||
local item = self.grid:getSelected()
|
||||
if item then
|
||||
self:transfer(item, 1, 'requesting 1 ...')
|
||||
end
|
||||
elseif event.type == 'eject' or event.type == 'grid_select' then
|
||||
local item = self.grid:getSelected()
|
||||
if item then
|
||||
self:transfer(item, 1, 'requesting 1 ...')
|
||||
end
|
||||
|
||||
elseif event.type == 'eject_stack' then
|
||||
local item = self.grid:getSelected()
|
||||
if item then
|
||||
self:transfer(item, 'stack', 'requesting stack ...')
|
||||
end
|
||||
elseif event.type == 'eject_stack' then
|
||||
local item = self.grid:getSelected()
|
||||
if item then
|
||||
self:transfer(item, 'stack', 'requesting stack ...')
|
||||
end
|
||||
|
||||
elseif event.type == 'eject_all' then
|
||||
local item = self.grid:getSelected()
|
||||
if item then
|
||||
self:transfer(item, 'all', 'requesting all ...')
|
||||
end
|
||||
elseif event.type == 'eject_all' then
|
||||
local item = self.grid:getSelected()
|
||||
if item then
|
||||
self:transfer(item, 'all', 'requesting all ...')
|
||||
end
|
||||
|
||||
elseif event.type == 'eject_specified' then
|
||||
local item = self.grid:getSelected()
|
||||
local count = tonumber(self.statusBar.amount.value)
|
||||
if item and count then
|
||||
self.statusBar.amount:reset()
|
||||
self:setFocus(self.statusBar.filter)
|
||||
self:transfer(item, count, 'requesting ' .. count .. ' ...')
|
||||
else
|
||||
Sound.play('entity.villager.no')
|
||||
context:notifyError('nope ...')
|
||||
end
|
||||
elseif event.type == 'eject_specified' then
|
||||
local item = self.grid:getSelected()
|
||||
local count = tonumber(self.statusBar.amount.value)
|
||||
if item and count then
|
||||
self.statusBar.amount:reset()
|
||||
self:setFocus(self.statusBar.filter)
|
||||
self:transfer(item, count, 'requesting ' .. count .. ' ...')
|
||||
else
|
||||
Sound.play('entity.villager.no')
|
||||
context:notifyError('nope ...')
|
||||
end
|
||||
|
||||
elseif event.type == 'plugin' then
|
||||
event.button.callback(context)
|
||||
elseif event.type == 'plugin' then
|
||||
event.button.callback(context)
|
||||
|
||||
elseif event.type == 'rescan' then
|
||||
self:setFocus(self.statusBar.filter)
|
||||
self:refresh('scan')
|
||||
self.grid:draw()
|
||||
elseif event.type == 'rescan' then
|
||||
self:setFocus(self.statusBar.filter)
|
||||
self:refresh('scan')
|
||||
self.grid:draw()
|
||||
|
||||
elseif event.type == 'grid_up' then
|
||||
self.grid:emit({ type = 'scroll_up' })
|
||||
elseif event.type == 'grid_up' then
|
||||
self.grid:emit({ type = 'scroll_up' })
|
||||
|
||||
elseif event.type == 'grid_down' then
|
||||
self.grid:emit({ type = 'scroll_down' })
|
||||
elseif event.type == 'grid_down' then
|
||||
self.grid:emit({ type = 'scroll_down' })
|
||||
|
||||
elseif event.type == 'refresh' then
|
||||
self:setFocus(self.statusBar.filter)
|
||||
self:refresh('list')
|
||||
self.grid:draw()
|
||||
elseif event.type == 'refresh' then
|
||||
self:setFocus(self.statusBar.filter)
|
||||
self:refresh('list')
|
||||
self.grid:draw()
|
||||
|
||||
elseif event.type == 'toggle_display' then
|
||||
context.state.displayMode = (context.state.displayMode + 1) % 2
|
||||
Util.merge(event.button, displayModes[context.state.displayMode])
|
||||
event.button:draw()
|
||||
self:applyFilter()
|
||||
context:setStatus(event.button.help)
|
||||
context:notifyInfo(event.button.help)
|
||||
self.grid:draw()
|
||||
Config.update('miloRemote', context.state)
|
||||
elseif event.type == 'toggle_display' then
|
||||
context.state.displayMode = (context.state.displayMode + 1) % 2
|
||||
Util.merge(event.button, displayModes[context.state.displayMode])
|
||||
event.button:draw()
|
||||
self:applyFilter()
|
||||
context:setStatus(event.button.help)
|
||||
context:notifyInfo(event.button.help)
|
||||
self.grid:draw()
|
||||
Config.update('miloRemote', context.state)
|
||||
|
||||
elseif event.type == 'text_change' and event.element == self.statusBar.filter then
|
||||
self.filter = event.text
|
||||
if #self.filter == 0 then
|
||||
self.filter = nil
|
||||
end
|
||||
self:applyFilter()
|
||||
self.grid:setIndex(1)
|
||||
self.grid:draw()
|
||||
elseif event.type == 'text_change' and event.element == self.statusBar.filter then
|
||||
self.filter = event.text
|
||||
if #self.filter == 0 then
|
||||
self.filter = nil
|
||||
end
|
||||
self:applyFilter()
|
||||
self.grid:setIndex(1)
|
||||
self.grid:draw()
|
||||
|
||||
else
|
||||
UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
else
|
||||
UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function page:enable()
|
||||
self:setFocus(self.statusBar.filter)
|
||||
Util.merge(self.statusBar.depositToggle, depositMode[context.state.deposit])
|
||||
UI.Page.enable(self)
|
||||
if not context.state.server then
|
||||
self.setup.form:setValues(context.state)
|
||||
self.setup:show()
|
||||
end
|
||||
Event.onTimeout(.1, function()
|
||||
self:refresh('list')
|
||||
self.grid:draw()
|
||||
self:sync()
|
||||
end)
|
||||
self:setFocus(self.statusBar.filter)
|
||||
Util.merge(self.statusBar.depositToggle, depositMode[context.state.deposit])
|
||||
UI.Page.enable(self)
|
||||
if not context.state.server then
|
||||
self.setup.form:setValues(context.state)
|
||||
self.setup:show()
|
||||
end
|
||||
Event.onTimeout(.1, function()
|
||||
self:refresh('list')
|
||||
self.grid:draw()
|
||||
self:sync()
|
||||
end)
|
||||
end
|
||||
|
||||
local function splitKey(key)
|
||||
local t = Util.split(key, '(.-):')
|
||||
local item = { }
|
||||
if #t[#t] > 8 then
|
||||
item.nbtHash = table.remove(t)
|
||||
end
|
||||
item.damage = tonumber(table.remove(t))
|
||||
item.name = table.concat(t, ':')
|
||||
return item
|
||||
local t = Util.split(key, '(.-):')
|
||||
local item = { }
|
||||
if #t[#t] > 8 then
|
||||
item.nbtHash = table.remove(t)
|
||||
end
|
||||
item.damage = tonumber(table.remove(t))
|
||||
item.name = table.concat(t, ':')
|
||||
return item
|
||||
end
|
||||
|
||||
function page:expandList(list)
|
||||
local t = { }
|
||||
for k,v in pairs(list) do
|
||||
local item = splitKey(k)
|
||||
item.has_recipe, item.count, item.displayName = v:match('(%d+):(%d+):(.+)')
|
||||
item.count = tonumber(item.count) or 0
|
||||
item.lname = item.displayName:lower()
|
||||
item.has_recipe = item.has_recipe == '1'
|
||||
t[k] = item
|
||||
end
|
||||
return t
|
||||
local t = { }
|
||||
for k,v in pairs(list) do
|
||||
local item = splitKey(k)
|
||||
item.has_recipe, item.count, item.displayName = v:match('(%d+):(%d+):(.+)')
|
||||
item.count = tonumber(item.count) or 0
|
||||
item.lname = item.displayName:lower()
|
||||
item.has_recipe = item.has_recipe == '1'
|
||||
t[k] = item
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
function page:refresh(requestType)
|
||||
context:sendRequest({ request = requestType }, 'refreshing...')
|
||||
context:sendRequest({ request = requestType }, 'refreshing...')
|
||||
end
|
||||
|
||||
function page:applyFilter()
|
||||
local function filterItems(t, filter, displayMode)
|
||||
self.grid.sortColumn = context.state.sortColumn or 'count'
|
||||
self.grid.inverseSort = context.state.inverseSort
|
||||
local function filterItems(t, filter, displayMode)
|
||||
self.grid.sortColumn = context.state.sortColumn or 'count'
|
||||
self.grid.inverseSort = context.state.inverseSort
|
||||
|
||||
if filter then
|
||||
local r = { }
|
||||
filter = filter:lower()
|
||||
self.grid.sortColumn = 'score'
|
||||
self.grid.inverseSort = true
|
||||
if filter then
|
||||
local r = { }
|
||||
filter = filter:lower()
|
||||
self.grid.sortColumn = 'score'
|
||||
self.grid.inverseSort = true
|
||||
|
||||
for _,v in pairs(t) do
|
||||
v.score = fuzzy(v.lname, filter)
|
||||
if v.score then
|
||||
if v.count > 0 then
|
||||
v.score = v.score + 1
|
||||
end
|
||||
table.insert(r, v)
|
||||
end
|
||||
end
|
||||
return r
|
||||
for _,v in pairs(t) do
|
||||
v.score = fuzzy(v.lname, filter)
|
||||
if v.score then
|
||||
if v.count > 0 then
|
||||
v.score = v.score + 1
|
||||
end
|
||||
table.insert(r, v)
|
||||
end
|
||||
end
|
||||
return r
|
||||
|
||||
elseif displayMode > 0 then
|
||||
local r = { }
|
||||
elseif displayMode > 0 then
|
||||
local r = { }
|
||||
|
||||
for _,v in pairs(t) do
|
||||
if v.count > 0 then
|
||||
table.insert(r, v)
|
||||
end
|
||||
end
|
||||
return r
|
||||
end
|
||||
for _,v in pairs(t) do
|
||||
if v.count > 0 then
|
||||
table.insert(r, v)
|
||||
end
|
||||
end
|
||||
return r
|
||||
end
|
||||
|
||||
return t
|
||||
end
|
||||
local t = filterItems(self.items, self.filter, context.state.displayMode)
|
||||
self.grid:setValues(t)
|
||||
return t
|
||||
end
|
||||
local t = filterItems(self.items, self.filter, context.state.displayMode)
|
||||
self.grid:setValues(t)
|
||||
end
|
||||
|
||||
context.page = page
|
||||
|
||||
function context:setStatus(status)
|
||||
page.menuBar.infoBar.values = status
|
||||
page.menuBar.infoBar:draw()
|
||||
page:sync()
|
||||
page.menuBar.infoBar.values = status
|
||||
page.menuBar.infoBar:draw()
|
||||
page:sync()
|
||||
end
|
||||
|
||||
function context:notifySuccess(status)
|
||||
page.notification:success(status)
|
||||
page:sync()
|
||||
page.notification:success(status)
|
||||
page:sync()
|
||||
end
|
||||
|
||||
function context:notifyInfo(status)
|
||||
page.notification:info(status)
|
||||
page:sync()
|
||||
page.notification:info(status)
|
||||
page:sync()
|
||||
end
|
||||
|
||||
function context:notifyError(status)
|
||||
page.notification:error(status)
|
||||
page:sync()
|
||||
page.notification:error(status)
|
||||
page:sync()
|
||||
end
|
||||
|
||||
local function processMessages(s)
|
||||
Event.addRoutine(function()
|
||||
s.co = coroutine.running()
|
||||
repeat
|
||||
local response = s:read()
|
||||
if not response then
|
||||
break
|
||||
end
|
||||
local h = context.responseHandlers[response.type]
|
||||
if h then
|
||||
h(response)
|
||||
end
|
||||
if response.msg then
|
||||
context:notifyInfo(response.msg)
|
||||
end
|
||||
until not s.connected
|
||||
Event.addRoutine(function()
|
||||
s.co = coroutine.running()
|
||||
repeat
|
||||
local response = s:read()
|
||||
if not response then
|
||||
break
|
||||
end
|
||||
local h = context.responseHandlers[response.type]
|
||||
if h then
|
||||
h(response)
|
||||
end
|
||||
if response.msg then
|
||||
context:notifyInfo(response.msg)
|
||||
end
|
||||
until not s.connected
|
||||
|
||||
s:close()
|
||||
s = nil
|
||||
context:notifyError('disconnected ...')
|
||||
Sound.play('entity.villager.no')
|
||||
end)
|
||||
s:close()
|
||||
s = nil
|
||||
context:notifyError('disconnected ...')
|
||||
Sound.play('entity.villager.no')
|
||||
end)
|
||||
end
|
||||
|
||||
function context:sendRequest(data, statusMsg)
|
||||
if not context.state.server then
|
||||
self:notifyError('Invalid configuration')
|
||||
return
|
||||
end
|
||||
if not context.state.server then
|
||||
self:notifyError('Invalid configuration')
|
||||
return
|
||||
end
|
||||
|
||||
local player = getPlayerName()
|
||||
if not player then
|
||||
self:notifyError('Missing neural or introspection')
|
||||
return
|
||||
end
|
||||
local player = getPlayerName()
|
||||
if not player then
|
||||
self:notifyError('Missing neural or introspection')
|
||||
return
|
||||
end
|
||||
|
||||
local success
|
||||
sync(page, function()
|
||||
local msg
|
||||
for _ = 1, 2 do
|
||||
if not context.socket or not context.socket.connected then
|
||||
self:notifyInfo('connecting ...')
|
||||
context.socket, msg = Socket.connect(context.state.server, 4242)
|
||||
if context.socket then
|
||||
context.socket:write(player)
|
||||
local r = context.socket:read(2)
|
||||
if r and not r.msg then
|
||||
self:notifySuccess('connected ...')
|
||||
processMessages(context.socket)
|
||||
else
|
||||
msg = r and r.msg or 'Timed out'
|
||||
context.socket:close()
|
||||
context.socket = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
if context.socket then
|
||||
if statusMsg then
|
||||
self:notifyInfo(statusMsg)
|
||||
end
|
||||
if context.socket:write(data) then
|
||||
success = true
|
||||
return
|
||||
end
|
||||
context.socket:close()
|
||||
context.socket = nil
|
||||
end
|
||||
end
|
||||
self:notifyError(msg or 'Failed to connect')
|
||||
end)
|
||||
local success
|
||||
sync(page, function()
|
||||
local msg
|
||||
for _ = 1, 2 do
|
||||
if not context.socket or not context.socket.connected then
|
||||
self:notifyInfo('connecting ...')
|
||||
context.socket, msg = Socket.connect(context.state.server, 4242)
|
||||
if context.socket then
|
||||
context.socket:write(player)
|
||||
local r = context.socket:read(2)
|
||||
if r and not r.msg then
|
||||
self:notifySuccess('connected ...')
|
||||
processMessages(context.socket)
|
||||
else
|
||||
msg = r and r.msg or 'Timed out'
|
||||
context.socket:close()
|
||||
context.socket = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
if context.socket then
|
||||
if statusMsg then
|
||||
self:notifyInfo(statusMsg)
|
||||
end
|
||||
if context.socket:write(data) then
|
||||
success = true
|
||||
return
|
||||
end
|
||||
context.socket:close()
|
||||
context.socket = nil
|
||||
end
|
||||
end
|
||||
self:notifyError(msg or 'Failed to connect')
|
||||
end)
|
||||
|
||||
return success
|
||||
return success
|
||||
end
|
||||
|
||||
function context:getState(key)
|
||||
return self.state[key]
|
||||
return self.state[key]
|
||||
end
|
||||
|
||||
function context:setState(key, value)
|
||||
self.state[key] = value
|
||||
Config.update('miloRemote', self.state)
|
||||
self.state[key] = value
|
||||
Config.update('miloRemote', self.state)
|
||||
end
|
||||
|
||||
context.responseHandlers['received'] = function(response)
|
||||
Sound.play('entity.item.pickup')
|
||||
local ritem = page.items[response.key]
|
||||
if ritem then
|
||||
ritem.count = response.count
|
||||
if page.enabled then
|
||||
page.grid:draw()
|
||||
page:sync()
|
||||
end
|
||||
end
|
||||
Sound.play('entity.item.pickup')
|
||||
local ritem = page.items[response.key]
|
||||
if ritem then
|
||||
ritem.count = response.count
|
||||
if page.enabled then
|
||||
page.grid:draw()
|
||||
page:sync()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context.responseHandlers['list'] = function(response)
|
||||
page.items = page:expandList(response.list)
|
||||
page:applyFilter()
|
||||
if page.enabled then
|
||||
page.grid:draw()
|
||||
page.grid:sync()
|
||||
end
|
||||
page.items = page:expandList(response.list)
|
||||
page:applyFilter()
|
||||
if page.enabled then
|
||||
page.grid:draw()
|
||||
page.grid:sync()
|
||||
end
|
||||
end
|
||||
|
||||
context.responseHandlers['transfer'] = function(response)
|
||||
if response.count > 0 then
|
||||
Sound.play('entity.item.pickup')
|
||||
local item = page.items[response.key]
|
||||
if item then
|
||||
item.count = response.current
|
||||
if page.enabled then
|
||||
page.grid:draw()
|
||||
page:sync()
|
||||
end
|
||||
end
|
||||
end
|
||||
if response.craft then
|
||||
if response.craft > 0 then
|
||||
context:notifyInfo(response.craft .. ' crafting ...')
|
||||
elseif response.craft + response.count < response.requested then
|
||||
if response.craft + response.count == 0 then
|
||||
Sound.play('entity.villager.no')
|
||||
end
|
||||
context:notifyInfo((response.craft + response.count) .. ' available ...')
|
||||
end
|
||||
end
|
||||
if response.count > 0 then
|
||||
Sound.play('entity.item.pickup')
|
||||
local item = page.items[response.key]
|
||||
if item then
|
||||
item.count = response.current
|
||||
if page.enabled then
|
||||
page.grid:draw()
|
||||
page:sync()
|
||||
end
|
||||
end
|
||||
end
|
||||
if response.craft then
|
||||
if response.craft > 0 then
|
||||
context:notifyInfo(response.craft .. ' crafting ...')
|
||||
elseif response.craft + response.count < response.requested then
|
||||
if response.craft + response.count == 0 then
|
||||
Sound.play('entity.villager.no')
|
||||
end
|
||||
context:notifyInfo((response.craft + response.count) .. ' available ...')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function loadDirectory(dir)
|
||||
local dropdown = {
|
||||
{ text = 'Setup', event = 'setup' },
|
||||
{ spacer = true },
|
||||
{
|
||||
text = 'Rescan storage',
|
||||
event = 'rescan',
|
||||
help = 'Rescan all inventories'
|
||||
},
|
||||
}
|
||||
local dropdown = {
|
||||
{ text = 'Setup', event = 'setup' },
|
||||
{ spacer = true },
|
||||
{
|
||||
text = 'Rescan storage',
|
||||
event = 'rescan',
|
||||
help = 'Rescan all inventories'
|
||||
},
|
||||
}
|
||||
|
||||
for _, file in pairs(fs.list(dir)) do
|
||||
local s, m = Util.run(_ENV, fs.combine(dir, file), context)
|
||||
if not s and m then
|
||||
_G.printError('Error loading: ' .. file)
|
||||
error(m or 'Unknown error')
|
||||
elseif s and m then
|
||||
table.insert(dropdown, {
|
||||
text = m.menuItem,
|
||||
event = 'plugin',
|
||||
callback = m.callback,
|
||||
})
|
||||
end
|
||||
end
|
||||
page.menuBar.config:add({ dropmenu = UI.DropMenu { buttons = dropdown } })
|
||||
for _, file in pairs(fs.list(dir)) do
|
||||
local s, m = Util.run(_ENV, fs.combine(dir, file), context)
|
||||
if not s and m then
|
||||
_G.printError('Error loading: ' .. file)
|
||||
error(m or 'Unknown error')
|
||||
elseif s and m then
|
||||
table.insert(dropdown, {
|
||||
text = m.menuItem,
|
||||
event = 'plugin',
|
||||
callback = m.callback,
|
||||
})
|
||||
end
|
||||
end
|
||||
page.menuBar.config:add({ dropmenu = UI.DropMenu { buttons = dropdown } })
|
||||
end
|
||||
|
||||
local programDir = fs.getDir(shell.getRunningProgram())
|
||||
@@ -528,5 +528,5 @@ UI:setPage(page)
|
||||
UI:pullEvents()
|
||||
|
||||
if context.socket then
|
||||
context.socket:close()
|
||||
context.socket:close()
|
||||
end
|
||||
|
||||
@@ -35,6 +35,39 @@ local function makeRecipeKey(item)
|
||||
return table.concat({ item.name, item.damage or 0, item.nbtHash }, ':')
|
||||
end
|
||||
|
||||
local function convert(ingredient)
|
||||
return type(ingredient) == 'table' and ingredient or {
|
||||
key = ingredient,
|
||||
count = 1,
|
||||
}
|
||||
end
|
||||
|
||||
local function getCraftingTool(storage, item)
|
||||
local items = storage:listItems()
|
||||
|
||||
for _,v in pairs(items) do
|
||||
if item.name == v.name and
|
||||
(not item.damage or item.damage == v.damage) and
|
||||
(not item.nbtHash or item.nbtHash == v.nbtHash) then
|
||||
return v
|
||||
end
|
||||
end
|
||||
|
||||
return item
|
||||
end
|
||||
|
||||
function Craft.ingedients(recipe)
|
||||
local i = 0
|
||||
local keys = Util.keys(recipe.ingredients)
|
||||
return function()
|
||||
i = i + 1
|
||||
local a = keys[i]
|
||||
if a then
|
||||
return a, convert(recipe.ingredients[a])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Craft.clearGrid(storage)
|
||||
local success = true
|
||||
local tasks = Tasks()
|
||||
@@ -74,8 +107,9 @@ end
|
||||
function Craft.sumIngredients(recipe)
|
||||
-- produces { ['minecraft:planks:0'] = 8 }
|
||||
local t = { }
|
||||
for _,item in pairs(recipe.ingredients) do
|
||||
t[item] = (t[item] or 0) + 1
|
||||
for _,entry in pairs(recipe.ingredients) do
|
||||
local item = convert(entry)
|
||||
t[item.key] = (t[item.key] or 0) + item.count
|
||||
end
|
||||
return t
|
||||
end
|
||||
@@ -108,12 +142,13 @@ local function machineCraft(recipe, storage, machineName, request, count, item)
|
||||
if count > 0 then
|
||||
local xferred = { }
|
||||
for k,v in pairs(recipe.ingredients) do
|
||||
local provided = storage:export(machine, k, count, splitKey(v))
|
||||
local entry = convert(v)
|
||||
local provided = storage:export(machine, k, count * entry.count, splitKey(entry.key))
|
||||
xferred[k] = {
|
||||
key = v,
|
||||
key = entry.key,
|
||||
count = provided,
|
||||
}
|
||||
if provided ~= count then
|
||||
if provided ~= count * entry.count then
|
||||
-- take back out whatever we put in
|
||||
for k2,v2 in pairs(xferred) do
|
||||
if v2.count > 0 then
|
||||
@@ -143,6 +178,9 @@ local function turtleCraft(recipe, storage, request, count)
|
||||
|
||||
for k,v in pairs(recipe.ingredients) do
|
||||
local item = splitKey(v)
|
||||
if recipe.craftingTools and recipe.craftingTools[v] then
|
||||
item = getCraftingTool(storage, item)
|
||||
end
|
||||
tasks:add(function()
|
||||
if storage:export(storage.turtleInventory, k, count, item) ~= count then
|
||||
request.status = 'rescan needed ?'
|
||||
@@ -185,7 +223,8 @@ local function turtleCraft(recipe, storage, request, count)
|
||||
end
|
||||
|
||||
function Craft.processPending(item, storage)
|
||||
for key, count in pairs(item.pending) do
|
||||
for _, key in pairs(Util.keys(item.pending)) do
|
||||
local count = item.pending[key]
|
||||
local imported = storage.activity[key]
|
||||
if imported then
|
||||
local amount = math.min(imported, count)
|
||||
@@ -238,7 +277,6 @@ end
|
||||
|
||||
function Craft.craftRecipeInternal(recipe, count, storage, origItem, path)
|
||||
local request = origItem.ingredients[recipe.result]
|
||||
|
||||
--[[
|
||||
if origItem.pending[recipe.result] then
|
||||
request.status = 'processing'
|
||||
@@ -425,23 +463,24 @@ function Craft.getCraftableAmount(inRecipe, inCount, items, missing)
|
||||
local canCraft = 0
|
||||
|
||||
for _ = 1, count do
|
||||
for _,item in pairs(recipe.ingredients) do
|
||||
local summedItem = summedItems[item] or Craft.getItemCount(items, item)
|
||||
for _,entry in pairs(recipe.ingredients) do
|
||||
local item = convert(entry)
|
||||
local summedItem = summedItems[item.key] or Craft.getItemCount(items, item.key)
|
||||
|
||||
local irecipe = findValidRecipe(item, path)
|
||||
local irecipe = findValidRecipe(item.key, path)
|
||||
if irecipe and summedItem <= 0 then
|
||||
local p = Util.shallowCopy(path)
|
||||
p[irecipe.result] = true
|
||||
summedItem = summedItem + sumItems(irecipe, summedItems, 1, p)
|
||||
summedItem = summedItem + sumItems(irecipe, summedItems, item.count, p)
|
||||
end
|
||||
if summedItem <= 0 then
|
||||
if missing and not irecipe then
|
||||
missing.name = item
|
||||
missing.name = item.key
|
||||
end
|
||||
return canCraft
|
||||
end
|
||||
if not recipe.craftingTools or not recipe.craftingTools[item] then
|
||||
summedItems[item] = summedItem - 1
|
||||
if not recipe.craftingTools or not recipe.craftingTools[item.key] then
|
||||
summedItems[item.key] = summedItem - item.count
|
||||
end
|
||||
end
|
||||
canCraft = canCraft + recipe.count
|
||||
|
||||
@@ -11,9 +11,9 @@ local _find = string.find
|
||||
local _max = math.max
|
||||
|
||||
return function(str, pattern)
|
||||
local start = _find(str, pattern, 1, true)
|
||||
if start then
|
||||
-- All letters before the current one are considered leading, so add them to our penalty
|
||||
return SCORE_WEIGHT + _max(LEADING_LETTER_PENALTY * (start - 1), LEADING_LETTER_PENALTY_MAX)
|
||||
end
|
||||
local start = _find(str, pattern, 1, true)
|
||||
if start then
|
||||
-- All letters before the current one are considered leading, so add them to our penalty
|
||||
return SCORE_WEIGHT + _max(LEADING_LETTER_PENALTY * (start - 1), LEADING_LETTER_PENALTY_MAX)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -224,8 +224,8 @@ function Milo:eject(item, count)
|
||||
total = total + amount
|
||||
count = count - amount
|
||||
|
||||
--Sound.play('ui.button.click')
|
||||
Sound.play('entity.illusion_illager.death', .3)
|
||||
Sound.play('ui.button.click')
|
||||
--Sound.play('entity.illusion_illager.death', .3)
|
||||
turtle.emptyInventory()
|
||||
end
|
||||
return total
|
||||
@@ -273,6 +273,7 @@ function Milo:learnRecipe()
|
||||
local tool = Util.shallowCopy(v2)
|
||||
if tool.maxDamage > 0 then
|
||||
tool.damage = '*'
|
||||
v2.damage = '*'
|
||||
end
|
||||
|
||||
--[[
|
||||
|
||||
@@ -7,46 +7,46 @@ local os = _G.os
|
||||
local Adapter = class(Mini)
|
||||
|
||||
function Adapter:init(args)
|
||||
Mini.init(self, args)
|
||||
Mini.init(self, args)
|
||||
|
||||
self._rawList = self.list
|
||||
self._rawList = self.list
|
||||
|
||||
function self.list()
|
||||
-- wait for up to 1 sec until any items that have been inserted
|
||||
-- into interface are added to the system
|
||||
for _ = 0, 20 do
|
||||
if #self._rawList() == 0 then
|
||||
break
|
||||
end
|
||||
os.sleep(0)
|
||||
end
|
||||
function self.list()
|
||||
-- wait for up to 1 sec until any items that have been inserted
|
||||
-- into interface are added to the system
|
||||
for _ = 0, 20 do
|
||||
if #self._rawList() == 0 then
|
||||
break
|
||||
end
|
||||
os.sleep(0)
|
||||
end
|
||||
|
||||
local list = { }
|
||||
for _, v in pairs(self.listAvailableItems()) do
|
||||
list[itemDB:makeKey(v)] = v
|
||||
end
|
||||
return list
|
||||
end
|
||||
local list = { }
|
||||
for _, v in pairs(self.listAvailableItems()) do
|
||||
list[itemDB:makeKey(v)] = v
|
||||
end
|
||||
return list
|
||||
end
|
||||
|
||||
function self.getItemMeta(key)
|
||||
local item = self.findItem(itemDB:splitKey(key))
|
||||
if item and item.getMetadata then
|
||||
return item.getMetadata()
|
||||
end
|
||||
end
|
||||
function self.getItemMeta(key)
|
||||
local item = self.findItem(itemDB:splitKey(key))
|
||||
if item and item.getMetadata then
|
||||
return item.getMetadata()
|
||||
end
|
||||
end
|
||||
|
||||
function self.pushItems(target, key, amount, slot)
|
||||
local item = self.findItem(itemDB:splitKey(key))
|
||||
if item and item.export then
|
||||
return item.export(target, amount, slot)
|
||||
end
|
||||
return 0
|
||||
end
|
||||
function self.pushItems(target, key, amount, slot)
|
||||
local item = self.findItem(itemDB:splitKey(key))
|
||||
if item and item.export then
|
||||
return item.export(target, amount, slot)
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
function self.pullItems(target, key, amount, slot)
|
||||
_G._syslog({target, key, amount, slot })
|
||||
return 0
|
||||
end
|
||||
function self.pullItems(target, key, amount, slot)
|
||||
_G._syslog({target, key, amount, slot })
|
||||
return 0
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
@@ -7,37 +7,37 @@ local device = _G.device
|
||||
local Adapter = class()
|
||||
|
||||
function Adapter:init(args)
|
||||
if args.side then
|
||||
local inventory = device[args.side]
|
||||
if inventory then
|
||||
Util.merge(self, inventory)
|
||||
end
|
||||
end
|
||||
if args.side then
|
||||
local inventory = device[args.side]
|
||||
if inventory then
|
||||
Util.merge(self, inventory)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Adapter:listItems(throttle)
|
||||
local cache = { }
|
||||
throttle = throttle or Util.throttle()
|
||||
local cache = { }
|
||||
throttle = throttle or Util.throttle()
|
||||
|
||||
for k,v in pairs(self.list()) do
|
||||
if v.count > 0 then
|
||||
local key = table.concat({ v.name, v.damage, v.nbtHash }, ':')
|
||||
for k,v in pairs(self.list()) do
|
||||
if v.count > 0 then
|
||||
local key = table.concat({ v.name, v.damage, v.nbtHash }, ':')
|
||||
|
||||
local entry = cache[key]
|
||||
if entry then
|
||||
entry.count = entry.count + v.count
|
||||
else
|
||||
cache[key] = itemDB:get(v, function() return self.getItemMeta(k) end)
|
||||
end
|
||||
throttle()
|
||||
end
|
||||
end
|
||||
local entry = cache[key]
|
||||
if entry then
|
||||
entry.count = entry.count + v.count
|
||||
else
|
||||
cache[key] = itemDB:get(v, function() return self.getItemMeta(k) end)
|
||||
end
|
||||
throttle()
|
||||
end
|
||||
end
|
||||
|
||||
-- TODO: cache number of slots, free slots, used slots
|
||||
-- useful for when inserting into chests
|
||||
-- ie. insert only if chest does not have item and has free slots
|
||||
-- TODO: cache number of slots, free slots, used slots
|
||||
-- useful for when inserting into chests
|
||||
-- ie. insert only if chest does not have item and has free slots
|
||||
|
||||
self.cache = cache
|
||||
self.cache = cache
|
||||
end
|
||||
|
||||
return Adapter
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,68 +7,68 @@ local free = { }
|
||||
|
||||
local function createTask(fn)
|
||||
local task = table.remove(free)
|
||||
if not task then
|
||||
task = {
|
||||
fn = fn,
|
||||
co = coroutine.create(function()
|
||||
local args = { }
|
||||
while true do
|
||||
pcall(task.fn, table.unpack(args))
|
||||
task.dead = true
|
||||
table.insert(free, task)
|
||||
args = { coroutine.yield() }
|
||||
end
|
||||
end)
|
||||
}
|
||||
else
|
||||
task.dead = nil
|
||||
task.fn = fn
|
||||
end
|
||||
if not task then
|
||||
task = {
|
||||
fn = fn,
|
||||
co = coroutine.create(function()
|
||||
local args = { }
|
||||
while true do
|
||||
pcall(task.fn, table.unpack(args))
|
||||
task.dead = true
|
||||
table.insert(free, task)
|
||||
args = { coroutine.yield() }
|
||||
end
|
||||
end)
|
||||
}
|
||||
else
|
||||
task.dead = nil
|
||||
task.fn = fn
|
||||
end
|
||||
return task
|
||||
end
|
||||
|
||||
function TaskRunner:init(args)
|
||||
self.tasks = { }
|
||||
self.errorMsg = 'Task failed: '
|
||||
self.tasks = { }
|
||||
self.errorMsg = 'Task failed: '
|
||||
|
||||
for k,v in pairs(args or { }) do
|
||||
self[k] = v
|
||||
end
|
||||
for k,v in pairs(args or { }) do
|
||||
self[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
function TaskRunner:add(fn)
|
||||
table.insert(self.tasks, createTask(fn))
|
||||
table.insert(self.tasks, createTask(fn))
|
||||
end
|
||||
|
||||
function TaskRunner:run()
|
||||
if #self.tasks > 0 then
|
||||
local event = { }
|
||||
if #self.tasks > 0 then
|
||||
local event = { }
|
||||
|
||||
while true do
|
||||
for n = #self.tasks, 1, -1 do
|
||||
local task = self.tasks[n]
|
||||
if task.filter == nil or task.filter == event[1] or event[1] == "terminate" then
|
||||
local ok, param = coroutine.resume(task.co, table.unpack(event))
|
||||
if not ok then
|
||||
self:onError(param)
|
||||
else
|
||||
task.filter = param
|
||||
end
|
||||
if task.dead then
|
||||
table.remove(self.tasks, n)
|
||||
end
|
||||
end
|
||||
end
|
||||
if #self.tasks == 0 then
|
||||
break
|
||||
end
|
||||
event = { os.pullEventRaw() }
|
||||
end
|
||||
end
|
||||
while true do
|
||||
for n = #self.tasks, 1, -1 do
|
||||
local task = self.tasks[n]
|
||||
if task.filter == nil or task.filter == event[1] or event[1] == "terminate" then
|
||||
local ok, param = coroutine.resume(task.co, table.unpack(event))
|
||||
if not ok then
|
||||
self:onError(param)
|
||||
else
|
||||
task.filter = param
|
||||
end
|
||||
if task.dead then
|
||||
table.remove(self.tasks, n)
|
||||
end
|
||||
end
|
||||
end
|
||||
if #self.tasks == 0 then
|
||||
break
|
||||
end
|
||||
event = { os.pullEventRaw() }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function TaskRunner:onError(msg)
|
||||
_G._syslog(msg.errorMsg .. msg)
|
||||
_G._syslog(msg.errorMsg .. msg)
|
||||
end
|
||||
|
||||
return TaskRunner
|
||||
|
||||
@@ -8,14 +8,14 @@ reboot
|
||||
|
||||
Use multiple brewing stands at once to brew potions.
|
||||
SETUP:
|
||||
Place an introspection module into the turtles inventory.
|
||||
Connect turtle to milo network with a wired modem.
|
||||
Connect turtle to a second wired modem that is connected to brewing stands ONLY.
|
||||
Add as many brewing stands as needed.
|
||||
Place an introspection module into the turtles inventory.
|
||||
Connect turtle to milo network with a wired modem.
|
||||
Connect turtle to a second wired modem that is connected to brewing stands ONLY.
|
||||
Add as many brewing stands as needed.
|
||||
CONFIGURATION:
|
||||
Set turtle as a "Generic Inventory"
|
||||
export blaze powder to slot 5
|
||||
import from slots 7-9
|
||||
Set turtle as a "Generic Inventory"
|
||||
export blaze powder to slot 5
|
||||
import from slots 7-9
|
||||
Use this turtle for machine crafting.
|
||||
--]]
|
||||
|
||||
@@ -31,23 +31,23 @@ local turtle = _G.turtle
|
||||
local STARTUP_FILE = 'usr/autorun/brewArray.lua'
|
||||
|
||||
local function equip(side, item, rawName)
|
||||
local equipped = peripheral.getType(side)
|
||||
local equipped = peripheral.getType(side)
|
||||
|
||||
if equipped == item then
|
||||
return true
|
||||
end
|
||||
if equipped == item then
|
||||
return true
|
||||
end
|
||||
|
||||
if not turtle.equip(side, rawName or item) then
|
||||
if not turtle.selectSlotWithQuantity(0) then
|
||||
error('No slots available')
|
||||
end
|
||||
turtle.equip(side)
|
||||
if not turtle.equip(side, item) then
|
||||
error('Unable to equip ' .. item)
|
||||
end
|
||||
end
|
||||
if not turtle.equip(side, rawName or item) then
|
||||
if not turtle.selectSlotWithQuantity(0) then
|
||||
error('No slots available')
|
||||
end
|
||||
turtle.equip(side)
|
||||
if not turtle.equip(side, item) then
|
||||
error('Unable to equip ' .. item)
|
||||
end
|
||||
end
|
||||
|
||||
turtle.select(1)
|
||||
turtle.select(1)
|
||||
end
|
||||
|
||||
equip('left', 'plethora:introspection', 'plethora:module:0')
|
||||
@@ -55,8 +55,8 @@ local intro = device['plethora:introspection']
|
||||
local inv = intro.getInventory()
|
||||
|
||||
if not fs.exists(STARTUP_FILE) then
|
||||
Util.writeFile(STARTUP_FILE,
|
||||
[[os.sleep(1)
|
||||
Util.writeFile(STARTUP_FILE,
|
||||
[[os.sleep(1)
|
||||
shell.openForegroundTab('packages/milo/apps/brewArray.lua')]])
|
||||
end
|
||||
|
||||
@@ -65,27 +65,27 @@ local localName
|
||||
|
||||
print('detecting wired modem connected to brewing stands...')
|
||||
for _, dev in pairs(device) do
|
||||
if dev.type == 'wired_modem' then
|
||||
local list = dev.getNamesRemote()
|
||||
brew = { }
|
||||
localName = dev.getNameLocal()
|
||||
for _, name in pairs(list) do
|
||||
if device[name].type ~= 'minecraft:brewing_stand' then
|
||||
brew = nil
|
||||
break
|
||||
end
|
||||
table.insert(brew, device[name])
|
||||
end
|
||||
end
|
||||
if brew then
|
||||
print('Using wired modem: ' .. dev.name)
|
||||
print('Brewing stands: ' .. #brew)
|
||||
break
|
||||
end
|
||||
if dev.type == 'wired_modem' then
|
||||
local list = dev.getNamesRemote()
|
||||
brew = { }
|
||||
localName = dev.getNameLocal()
|
||||
for _, name in pairs(list) do
|
||||
if device[name].type ~= 'minecraft:brewing_stand' then
|
||||
brew = nil
|
||||
break
|
||||
end
|
||||
table.insert(brew, device[name])
|
||||
end
|
||||
end
|
||||
if brew then
|
||||
print('Using wired modem: ' .. dev.name)
|
||||
print('Brewing stands: ' .. #brew)
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not brew then
|
||||
error('Turtle must be connected to a second wired_modem connected to brewing stands only')
|
||||
error('Turtle must be connected to a second wired_modem connected to brewing stands only')
|
||||
end
|
||||
|
||||
_G.printError([[Program must be restarted if new brewing stands are added.]])
|
||||
@@ -95,66 +95,66 @@ _G.printError([[Program must be restarted if new brewing stands are added.]])
|
||||
-- slot 5: blaze powder
|
||||
|
||||
local function process(list)
|
||||
local active = false
|
||||
local active = false
|
||||
|
||||
for _, brewing in ipairs(Util.shallowCopy(brew)) do
|
||||
local s, m = pcall(function()-- block updates can cause errors
|
||||
local bs = brewing.list()
|
||||
for _, brewing in ipairs(Util.shallowCopy(brew)) do
|
||||
local s, m = pcall(function()-- block updates can cause errors
|
||||
local bs = brewing.list()
|
||||
|
||||
local cooking = bs[1] and bs[2] and bs[3] and bs[4]
|
||||
if cooking then
|
||||
active = true
|
||||
end
|
||||
local cooking = bs[1] and bs[2] and bs[3] and bs[4]
|
||||
if cooking then
|
||||
active = true
|
||||
end
|
||||
|
||||
-- fuel
|
||||
local fuel = bs[5] or { count = 0 }
|
||||
if fuel.count < 1 then
|
||||
print('fueling ' ..brewing.name)
|
||||
brewing.pullItems(localName, 5, 1, 5)
|
||||
end
|
||||
-- fuel
|
||||
local fuel = bs[5] or { count = 0 }
|
||||
if fuel.count < 1 then
|
||||
print('fueling ' ..brewing.name)
|
||||
brewing.pullItems(localName, 5, 1, 5)
|
||||
end
|
||||
|
||||
if not cooking and (bs[1] or bs[2] or bs[3] or bs[4]) then
|
||||
print('pulling from : ' .. brewing.name)
|
||||
for i = 1, 4 do
|
||||
brewing.pushItems(localName, i, 1, 6 + i)
|
||||
end
|
||||
end
|
||||
if not cooking and (bs[1] or bs[2] or bs[3] or bs[4]) then
|
||||
print('pulling from : ' .. brewing.name)
|
||||
for i = 1, 4 do
|
||||
brewing.pushItems(localName, i, 1, 6 + i)
|
||||
end
|
||||
end
|
||||
|
||||
if not cooking and list[1] and list[2] and list[3] and list[4] then
|
||||
print('brewing : ' .. brewing.name)
|
||||
for i = 1, 4 do
|
||||
brewing.pullItems(localName, i, 1, i)
|
||||
list[i].count = list[i].count - 1
|
||||
if list[i].count == 0 then
|
||||
list[i] = nil
|
||||
end
|
||||
end
|
||||
if not cooking and list[1] and list[2] and list[3] and list[4] then
|
||||
print('brewing : ' .. brewing.name)
|
||||
for i = 1, 4 do
|
||||
brewing.pullItems(localName, i, 1, i)
|
||||
list[i].count = list[i].count - 1
|
||||
if list[i].count == 0 then
|
||||
list[i] = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- push brewing stand to end of list
|
||||
Util.removeByValue(brew, brewing)
|
||||
table.insert(brew, brewing)
|
||||
end
|
||||
end)
|
||||
if not s and m then
|
||||
_G.printError(m)
|
||||
end
|
||||
end
|
||||
-- push brewing stand to end of list
|
||||
Util.removeByValue(brew, brewing)
|
||||
table.insert(brew, brewing)
|
||||
end
|
||||
end)
|
||||
if not s and m then
|
||||
_G.printError(m)
|
||||
end
|
||||
end
|
||||
|
||||
return active
|
||||
return active
|
||||
end
|
||||
|
||||
Event.on('turtle_inventory', function()
|
||||
while true do
|
||||
if not process(inv.list()) then
|
||||
break
|
||||
end
|
||||
os.sleep(3)
|
||||
end
|
||||
while true do
|
||||
if not process(inv.list()) then
|
||||
break
|
||||
end
|
||||
os.sleep(3)
|
||||
end
|
||||
end)
|
||||
|
||||
Event.onInterval(5, function()
|
||||
-- for some reason, it keeps stalling ...
|
||||
os.queueEvent('turtle_inventory')
|
||||
-- for some reason, it keeps stalling ...
|
||||
os.queueEvent('turtle_inventory')
|
||||
end)
|
||||
|
||||
os.queueEvent('turtle_inventory')
|
||||
|
||||
@@ -7,22 +7,22 @@ local turtle = _G.turtle
|
||||
local STARTUP_FILE = 'usr/autorun/cobbleGen.lua'
|
||||
|
||||
if not fs.exists(STARTUP_FILE) then
|
||||
Util.writeFile(STARTUP_FILE,
|
||||
[[os.sleep(1)
|
||||
Util.writeFile(STARTUP_FILE,
|
||||
[[os.sleep(1)
|
||||
shell.openForegroundTab('packages/milo/apps/cobblegen')]])
|
||||
end
|
||||
|
||||
os.queueEvent('turtle_inventory')
|
||||
while true do
|
||||
print('waiting')
|
||||
os.pullEvent('turtle_inventory')
|
||||
print('waiting for cobble')
|
||||
for _ = 1, 20 do
|
||||
if turtle.inspectDown() then
|
||||
break
|
||||
end
|
||||
os.sleep(.1)
|
||||
end
|
||||
print('digging')
|
||||
turtle.digDown()
|
||||
print('waiting')
|
||||
os.pullEvent('turtle_inventory')
|
||||
print('waiting for cobble')
|
||||
for _ = 1, 20 do
|
||||
if turtle.inspectDown() then
|
||||
break
|
||||
end
|
||||
os.sleep(.1)
|
||||
end
|
||||
print('digging')
|
||||
turtle.digDown()
|
||||
end
|
||||
|
||||
@@ -13,48 +13,48 @@ local turtle = _G.turtle
|
||||
local STARTUP_FILE = 'usr/autorun/enderchest.lua'
|
||||
|
||||
local enderChest = device.manipulator and
|
||||
device.manipulator.getEnder or
|
||||
error('Must be connected to a manipulator with a bound introspection module')
|
||||
device.manipulator.getEnder or
|
||||
error('Must be connected to a manipulator with a bound introspection module')
|
||||
|
||||
if not fs.exists(STARTUP_FILE) then
|
||||
Util.writeFile(STARTUP_FILE,
|
||||
[[os.sleep(1)
|
||||
Util.writeFile(STARTUP_FILE,
|
||||
[[os.sleep(1)
|
||||
shell.openForegroundTab('packages/milo/apps/enderchest')]])
|
||||
end
|
||||
|
||||
local directions = Util.transpose {
|
||||
'north', 'south', 'east', 'west', 'up', 'down'
|
||||
'north', 'south', 'east', 'west', 'up', 'down'
|
||||
}
|
||||
|
||||
Event.on('turtle_inventory', function()
|
||||
local s, m = pcall(function()
|
||||
local direction
|
||||
local s, m = pcall(function()
|
||||
local direction
|
||||
|
||||
for _, d in pairs(enderChest().getTransferLocations()) do
|
||||
if directions[d] then
|
||||
direction = d
|
||||
break
|
||||
end
|
||||
end
|
||||
for _, d in pairs(enderChest().getTransferLocations()) do
|
||||
if directions[d] then
|
||||
direction = d
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not direction then
|
||||
error('Unable to determine transfer direction')
|
||||
end
|
||||
if not direction then
|
||||
error('Unable to determine transfer direction')
|
||||
end
|
||||
|
||||
turtle.eachFilledSlot(function(s)
|
||||
print('sending')
|
||||
enderChest().pullItems(direction, s.index)
|
||||
end)
|
||||
end)
|
||||
if not s and m then
|
||||
_G.printError(m)
|
||||
end
|
||||
print('idle')
|
||||
turtle.eachFilledSlot(function(s)
|
||||
print('sending')
|
||||
enderChest().pullItems(direction, s.index)
|
||||
end)
|
||||
end)
|
||||
if not s and m then
|
||||
_G.printError(m)
|
||||
end
|
||||
print('idle')
|
||||
end)
|
||||
|
||||
Event.onInterval(5, function()
|
||||
-- for some reason, it keeps stalling ...
|
||||
os.queueEvent('turtle_inventory')
|
||||
-- for some reason, it keeps stalling ...
|
||||
os.queueEvent('turtle_inventory')
|
||||
end)
|
||||
|
||||
os.queueEvent('turtle_inventory')
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
Use multiple furnaces at once to smelt items.
|
||||
|
||||
SETUP:
|
||||
Place an introspection module into the turtles inventory.
|
||||
Connect turtle to milo network with a wired modem.
|
||||
Connect turtle to a second wired modem that is connected to furnaces ONLY.
|
||||
Add as many furnaces as needed.
|
||||
Place an introspection module into the turtles inventory.
|
||||
Connect turtle to milo network with a wired modem.
|
||||
Connect turtle to a second wired modem that is connected to furnaces ONLY.
|
||||
Add as many furnaces as needed.
|
||||
|
||||
CONFIGURATION:
|
||||
Set turtle as a "Generic Inventory"
|
||||
export coal to slot 2
|
||||
import from slot 3
|
||||
Set turtle as a "Generic Inventory"
|
||||
export coal to slot 2
|
||||
import from slot 3
|
||||
|
||||
Use this turtle for machine crafting.
|
||||
--]]
|
||||
@@ -31,23 +31,23 @@ local FUEL_SLOT = 2
|
||||
local OUTPUT_SLOT = 3
|
||||
|
||||
local function equip(side, item, rawName)
|
||||
local equipped = peripheral.getType(side)
|
||||
local equipped = peripheral.getType(side)
|
||||
|
||||
if equipped == item then
|
||||
return true
|
||||
end
|
||||
if equipped == item then
|
||||
return true
|
||||
end
|
||||
|
||||
if not turtle.equip(side, rawName or item) then
|
||||
if not turtle.selectSlotWithQuantity(0) then
|
||||
error('No slots available')
|
||||
end
|
||||
turtle.equip(side)
|
||||
if not turtle.equip(side, item) then
|
||||
error('Unable to equip ' .. item)
|
||||
end
|
||||
end
|
||||
if not turtle.equip(side, rawName or item) then
|
||||
if not turtle.selectSlotWithQuantity(0) then
|
||||
error('No slots available')
|
||||
end
|
||||
turtle.equip(side)
|
||||
if not turtle.equip(side, item) then
|
||||
error('Unable to equip ' .. item)
|
||||
end
|
||||
end
|
||||
|
||||
turtle.select(1)
|
||||
turtle.select(1)
|
||||
end
|
||||
|
||||
equip('left', 'plethora:introspection', 'plethora:module:0')
|
||||
@@ -55,8 +55,8 @@ local intro = device['plethora:introspection']
|
||||
local inv = intro.getInventory()
|
||||
|
||||
if not fs.exists(STARTUP_FILE) then
|
||||
Util.writeFile(STARTUP_FILE,
|
||||
[[os.sleep(1)
|
||||
Util.writeFile(STARTUP_FILE,
|
||||
[[os.sleep(1)
|
||||
shell.openForegroundTab('packages/milo/apps/furni')]])
|
||||
end
|
||||
|
||||
@@ -65,121 +65,121 @@ local localName
|
||||
|
||||
print('detecting wired modem connected to furnaces...')
|
||||
for _, dev in pairs(device) do
|
||||
if dev.type == 'wired_modem' and dev.getNameLocal then
|
||||
local list = dev.getNamesRemote()
|
||||
furnaces = { }
|
||||
localName = dev.getNameLocal()
|
||||
for _, name in pairs(list) do
|
||||
if device[name].type ~= 'minecraft:furnace' then
|
||||
furnaces = nil
|
||||
break
|
||||
end
|
||||
table.insert(furnaces, {
|
||||
dev = device[name],
|
||||
list = device[name].list(),
|
||||
})
|
||||
end
|
||||
end
|
||||
if furnaces then
|
||||
print('Using wired modem: ' .. dev.name)
|
||||
print('Furnaces: ' .. #furnaces)
|
||||
break
|
||||
end
|
||||
if dev.type == 'wired_modem' and dev.getNameLocal then
|
||||
local list = dev.getNamesRemote()
|
||||
furnaces = { }
|
||||
localName = dev.getNameLocal()
|
||||
for _, name in pairs(list) do
|
||||
if device[name].type ~= 'minecraft:furnace' then
|
||||
furnaces = nil
|
||||
break
|
||||
end
|
||||
table.insert(furnaces, {
|
||||
dev = device[name],
|
||||
list = device[name].list(),
|
||||
})
|
||||
end
|
||||
end
|
||||
if furnaces then
|
||||
print('Using wired modem: ' .. dev.name)
|
||||
print('Furnaces: ' .. #furnaces)
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not furnaces then
|
||||
error('Turtle must be connected to a second wired_modem connected to furnaces only')
|
||||
error('Turtle must be connected to a second wired_modem connected to furnaces only')
|
||||
end
|
||||
|
||||
_G.printError([[Program must be restarted if new furnaces are added.]])
|
||||
|
||||
local function getSlot(furnace, slotNo)
|
||||
if not furnace.list[slotNo] then
|
||||
furnace.list[slotNo] = {
|
||||
count = 0
|
||||
}
|
||||
end
|
||||
return furnace.list[slotNo]
|
||||
if not furnace.list[slotNo] then
|
||||
furnace.list[slotNo] = {
|
||||
count = 0
|
||||
}
|
||||
end
|
||||
return furnace.list[slotNo]
|
||||
end
|
||||
|
||||
local function process(list)
|
||||
local inItem = list[INPUT_SLOT]
|
||||
local inFuel = list[FUEL_SLOT]
|
||||
local inReturn = list[OUTPUT_SLOT] or { count = 0 }
|
||||
local inItem = list[INPUT_SLOT]
|
||||
local inFuel = list[FUEL_SLOT]
|
||||
local inReturn = list[OUTPUT_SLOT] or { count = 0 }
|
||||
|
||||
for _, furnace in ipairs(Util.shallowCopy(furnaces)) do
|
||||
local s, m = pcall(function()
|
||||
if furnace.list[INPUT_SLOT] and furnace.list[INPUT_SLOT].count > 0 then
|
||||
furnace.list = furnace.dev.list()
|
||||
print('listing ' .. furnace.dev.name)
|
||||
end
|
||||
for _, furnace in ipairs(Util.shallowCopy(furnaces)) do
|
||||
local s, m = pcall(function()
|
||||
if furnace.list[INPUT_SLOT] and furnace.list[INPUT_SLOT].count > 0 then
|
||||
furnace.list = furnace.dev.list()
|
||||
print('listing ' .. furnace.dev.name)
|
||||
end
|
||||
|
||||
-- items to cook
|
||||
local cooking = getSlot(furnace, INPUT_SLOT)
|
||||
if cooking.count < 64 and inItem and inItem.count > 0 then
|
||||
if cooking.count == 0 or cooking.name == inItem.name then
|
||||
print('cooking : ' .. furnace.dev.name)
|
||||
local count = furnace.dev.pullItems(localName, INPUT_SLOT, SMELT_AMOUNT, INPUT_SLOT)
|
||||
-- items to cook
|
||||
local cooking = getSlot(furnace, INPUT_SLOT)
|
||||
if cooking.count < 64 and inItem and inItem.count > 0 then
|
||||
if cooking.count == 0 or cooking.name == inItem.name then
|
||||
print('cooking : ' .. furnace.dev.name)
|
||||
local count = furnace.dev.pullItems(localName, INPUT_SLOT, SMELT_AMOUNT, INPUT_SLOT)
|
||||
|
||||
if count > 0 then
|
||||
inItem.count = inItem.count - count
|
||||
if count > 0 then
|
||||
inItem.count = inItem.count - count
|
||||
|
||||
cooking.name = inItem.name
|
||||
cooking.count = cooking.count + count
|
||||
cooking.name = inItem.name
|
||||
cooking.count = cooking.count + count
|
||||
|
||||
-- push to end of queue
|
||||
Util.removeByValue(furnaces, furnace)
|
||||
table.insert(furnaces, furnace)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- push to end of queue
|
||||
Util.removeByValue(furnaces, furnace)
|
||||
table.insert(furnaces, furnace)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- fuel
|
||||
local fuel = getSlot(furnace, FUEL_SLOT)
|
||||
if fuel.count < 8 and inFuel and inFuel.count > 0 then
|
||||
if fuel.count == 0 or fuel.name == inFuel.name then
|
||||
print('fueling ' .. furnace.dev.name)
|
||||
local count = furnace.dev.pullItems(localName, FUEL_SLOT, 8 - fuel.count, FUEL_SLOT)
|
||||
if count > 0 then
|
||||
inFuel.count = inFuel.count - count
|
||||
-- fuel
|
||||
local fuel = getSlot(furnace, FUEL_SLOT)
|
||||
if fuel.count < 8 and inFuel and inFuel.count > 0 then
|
||||
if fuel.count == 0 or fuel.name == inFuel.name then
|
||||
print('fueling ' .. furnace.dev.name)
|
||||
local count = furnace.dev.pullItems(localName, FUEL_SLOT, 8 - fuel.count, FUEL_SLOT)
|
||||
if count > 0 then
|
||||
inFuel.count = inFuel.count - count
|
||||
|
||||
fuel.name = inFuel.name
|
||||
fuel.count = fuel.count + count
|
||||
end
|
||||
end
|
||||
end
|
||||
fuel.name = inFuel.name
|
||||
fuel.count = fuel.count + count
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local result = getSlot(furnace, OUTPUT_SLOT)
|
||||
if result.count > 0 then
|
||||
if inReturn.count == 0 or result.name == inReturn.name then
|
||||
print('pulling from : ' .. furnace.dev.name)
|
||||
local count = furnace.dev.pushItems(localName, OUTPUT_SLOT, result.count, OUTPUT_SLOT)
|
||||
local result = getSlot(furnace, OUTPUT_SLOT)
|
||||
if result.count > 0 then
|
||||
if inReturn.count == 0 or result.name == inReturn.name then
|
||||
print('pulling from : ' .. furnace.dev.name)
|
||||
local count = furnace.dev.pushItems(localName, OUTPUT_SLOT, result.count, OUTPUT_SLOT)
|
||||
|
||||
if count > 0 then
|
||||
result.count = result.count - count
|
||||
if result.count == 0 then
|
||||
furnace.list[OUTPUT_SLOT] = nil
|
||||
end
|
||||
if count > 0 then
|
||||
result.count = result.count - count
|
||||
if result.count == 0 then
|
||||
furnace.list[OUTPUT_SLOT] = nil
|
||||
end
|
||||
|
||||
inReturn.name = result.name
|
||||
inReturn.count = inReturn.count + count
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
if not s and m then
|
||||
_G.printError(m)
|
||||
end
|
||||
end
|
||||
inReturn.name = result.name
|
||||
inReturn.count = inReturn.count + count
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
if not s and m then
|
||||
_G.printError(m)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Event.on('turtle_inventory', function()
|
||||
process(inv.list())
|
||||
print('idle')
|
||||
process(inv.list())
|
||||
print('idle')
|
||||
end)
|
||||
|
||||
Event.onInterval(3, function()
|
||||
os.queueEvent('turtle_inventory')
|
||||
os.queueEvent('turtle_inventory')
|
||||
end)
|
||||
|
||||
os.queueEvent('turtle_inventory')
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
--[[
|
||||
For initially setting up large amounts of storage chests.
|
||||
For initially setting up large amounts of storage chests.
|
||||
]]
|
||||
|
||||
local Util = require('util')
|
||||
@@ -11,17 +11,17 @@ local st = args[1] or error('Specify a storage type (ie. minecraft:chest)')
|
||||
|
||||
local config = { }
|
||||
peripheral.find(st, function(n)
|
||||
config[n] = {
|
||||
name = n,
|
||||
category = 'storage',
|
||||
mtype = 'storage',
|
||||
}
|
||||
config[n] = {
|
||||
name = n,
|
||||
category = 'storage',
|
||||
mtype = 'storage',
|
||||
}
|
||||
end)
|
||||
|
||||
print('Found ' .. Util.size(config))
|
||||
|
||||
if Util.size(config) == 0 then
|
||||
error('Invalid peripheral type')
|
||||
error('Invalid peripheral type')
|
||||
end
|
||||
|
||||
Util.writeTable('usr/config/storageGen', config)
|
||||
|
||||
@@ -4,90 +4,90 @@ local UI = require('ui')
|
||||
local turtle = _G.turtle
|
||||
|
||||
local learnPage = UI.Page {
|
||||
titleBar = UI.TitleBar { title = 'Learn Recipe' },
|
||||
wizard = UI.Wizard {
|
||||
y = 2, ey = -2,
|
||||
pages = {
|
||||
general = UI.WizardPage {
|
||||
index = 1,
|
||||
grid = UI.ScrollingGrid {
|
||||
x = 2, ex = -2, y = 2, ey = -2,
|
||||
disableHeader = true,
|
||||
columns = {
|
||||
{ heading = 'Name', key = 'name'},
|
||||
},
|
||||
sortColumn = 'name',
|
||||
},
|
||||
accelerators = {
|
||||
grid_select = 'nextView',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
notification = UI.Notification { },
|
||||
titleBar = UI.TitleBar { title = 'Learn Recipe' },
|
||||
wizard = UI.Wizard {
|
||||
y = 2, ey = -2,
|
||||
pages = {
|
||||
general = UI.WizardPage {
|
||||
index = 1,
|
||||
grid = UI.ScrollingGrid {
|
||||
x = 2, ex = -2, y = 2, ey = -2,
|
||||
disableHeader = true,
|
||||
columns = {
|
||||
{ heading = 'Name', key = 'name'},
|
||||
},
|
||||
sortColumn = 'name',
|
||||
},
|
||||
accelerators = {
|
||||
grid_select = 'nextView',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
notification = UI.Notification { },
|
||||
}
|
||||
|
||||
local general = learnPage.wizard.pages.general
|
||||
|
||||
function general:validate()
|
||||
Milo:setState('learnType', self.grid:getSelected().value)
|
||||
return true
|
||||
Milo:setState('learnType', self.grid:getSelected().value)
|
||||
return true
|
||||
end
|
||||
|
||||
function learnPage:enable()
|
||||
local t = { }
|
||||
local t = { }
|
||||
|
||||
for _, page in pairs(self.wizard.pages) do
|
||||
if page.validFor then
|
||||
t[page.validFor] = {
|
||||
name = page.validFor,
|
||||
value = page.validFor,
|
||||
}
|
||||
end
|
||||
end
|
||||
general.grid:setValues(t)
|
||||
general.grid:setSelected('name', Milo:getState('learnType') or '')
|
||||
for _, page in pairs(self.wizard.pages) do
|
||||
if page.validFor then
|
||||
t[page.validFor] = {
|
||||
name = page.validFor,
|
||||
value = page.validFor,
|
||||
}
|
||||
end
|
||||
end
|
||||
general.grid:setValues(t)
|
||||
general.grid:setSelected('name', Milo:getState('learnType') or '')
|
||||
|
||||
Milo:pauseCrafting({ key = 'gridInUse', msg = 'Crafting paused' })
|
||||
Milo:pauseCrafting({ key = 'gridInUse', msg = 'Crafting paused' })
|
||||
|
||||
self:focusFirst()
|
||||
UI.Page.enable(self)
|
||||
self:focusFirst()
|
||||
UI.Page.enable(self)
|
||||
end
|
||||
|
||||
function learnPage:disable()
|
||||
Milo:resumeCrafting({ key = 'gridInUse' })
|
||||
return UI.Page.disable(self)
|
||||
Milo:resumeCrafting({ key = 'gridInUse' })
|
||||
return UI.Page.disable(self)
|
||||
end
|
||||
|
||||
function learnPage.wizard:getPage(index)
|
||||
local pages = { }
|
||||
table.insert(pages, general)
|
||||
local selected = general.grid:getSelected()
|
||||
for _, page in pairs(self.pages) do
|
||||
if page.validFor and (not selected or selected.value == page.validFor) then
|
||||
table.insert(pages, page)
|
||||
end
|
||||
end
|
||||
table.sort(pages, function(a, b)
|
||||
return a.index < b.index
|
||||
end)
|
||||
local pages = { }
|
||||
table.insert(pages, general)
|
||||
local selected = general.grid:getSelected()
|
||||
for _, page in pairs(self.pages) do
|
||||
if page.validFor and (not selected or selected.value == page.validFor) then
|
||||
table.insert(pages, page)
|
||||
end
|
||||
end
|
||||
table.sort(pages, function(a, b)
|
||||
return a.index < b.index
|
||||
end)
|
||||
|
||||
return pages[index]
|
||||
return pages[index]
|
||||
end
|
||||
|
||||
function learnPage:eventHandler(event)
|
||||
if event.type == 'cancel' then
|
||||
turtle.emptyInventory()
|
||||
UI:setPreviousPage()
|
||||
if event.type == 'cancel' then
|
||||
turtle.emptyInventory()
|
||||
UI:setPreviousPage()
|
||||
|
||||
elseif event.type == 'form_invalid' or event.type == 'general_error' then
|
||||
self.notification:error(event.message)
|
||||
self:setFocus(event.field)
|
||||
elseif event.type == 'form_invalid' or event.type == 'general_error' then
|
||||
self.notification:error(event.message)
|
||||
self:setFocus(event.field)
|
||||
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
UI:addPage('learnWizard', learnPage)
|
||||
|
||||
@@ -11,390 +11,390 @@ local context = Milo:getContext()
|
||||
local displayMode = Milo:getState('displayMode') or 0
|
||||
|
||||
local displayModes = {
|
||||
[0] = { text = 'A', help = 'Showing all items' },
|
||||
[1] = { text = 'I', help = 'Showing inventory items' },
|
||||
[0] = { text = 'A', help = 'Showing all items' },
|
||||
[1] = { text = 'I', help = 'Showing inventory items' },
|
||||
}
|
||||
|
||||
local page = UI.Page {
|
||||
menuBar = UI.MenuBar {
|
||||
buttons = {
|
||||
{ text = 'Learn', event = 'learn' },
|
||||
{ text = 'Craft', event = 'craft' },
|
||||
{ text = 'Edit', event = 'details' },
|
||||
{ text = 'Refresh', event = 'refresh', x = -12 },
|
||||
{
|
||||
text = '\187',
|
||||
x = -3,
|
||||
dropdown = {
|
||||
{ text = 'Setup', event = 'network' },
|
||||
{ spacer = true },
|
||||
{
|
||||
text = 'Rescan storage',
|
||||
event = 'rescan',
|
||||
help = 'Rescan all inventories'
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
grid = UI.Grid {
|
||||
y = 2, ey = -2,
|
||||
columns = {
|
||||
{ heading = ' Qty', key = 'count' , width = 4, align = 'right' },
|
||||
{ heading = 'Name', key = 'displayName' },
|
||||
{ heading = 'Min', key = 'low' , width = 4 },
|
||||
{ heading = 'Max', key = 'limit' , width = 4 },
|
||||
},
|
||||
sortColumn = Milo:getState('sortColumn') or 'count',
|
||||
inverseSort = Milo:getState('inverseSort'),
|
||||
},
|
||||
statusBar = UI.StatusBar {
|
||||
filter = UI.TextEntry {
|
||||
x = 1, ex = -17,
|
||||
limit = 50,
|
||||
shadowText = 'filter',
|
||||
shadowTextColor = colors.gray,
|
||||
backgroundColor = colors.cyan,
|
||||
backgroundFocusColor = colors.cyan,
|
||||
accelerators = {
|
||||
[ 'enter' ] = 'eject',
|
||||
[ 'up' ] = 'grid_up',
|
||||
[ 'down' ] = 'grid_down',
|
||||
[ 'control-a' ] = 'eject_all',
|
||||
},
|
||||
},
|
||||
storageStatus = UI.Text {
|
||||
x = -16, ex = -9,
|
||||
textColor = colors.lime,
|
||||
backgroundColor = colors.cyan,
|
||||
value = '',
|
||||
},
|
||||
amount = UI.TextEntry {
|
||||
x = -8, ex = -4,
|
||||
limit = 3,
|
||||
shadowText = '1',
|
||||
shadowTextColor = colors.gray,
|
||||
backgroundColor = colors.black,
|
||||
backgroundFocusColor = colors.black,
|
||||
accelerators = {
|
||||
[ 'enter' ] = 'eject_specified',
|
||||
[ 'control-a' ] = 'eject_all',
|
||||
},
|
||||
help = 'Specify an amount to send',
|
||||
},
|
||||
display = UI.Button {
|
||||
x = -3,
|
||||
event = 'toggle_display',
|
||||
value = 0,
|
||||
text = displayModes[displayMode].text,
|
||||
help = displayModes[displayMode].help,
|
||||
},
|
||||
},
|
||||
notification = UI.Notification {
|
||||
anchor = 'top',
|
||||
},
|
||||
throttle = UI.Throttle {
|
||||
textColor = colors.yellow,
|
||||
borderColor = colors.gray,
|
||||
},
|
||||
accelerators = {
|
||||
r = 'refresh',
|
||||
[ 'control-r' ] = 'refresh',
|
||||
menuBar = UI.MenuBar {
|
||||
buttons = {
|
||||
{ text = 'Learn', event = 'learn' },
|
||||
{ text = 'Craft', event = 'craft' },
|
||||
{ text = 'Edit', event = 'details' },
|
||||
{ text = 'Refresh', event = 'refresh', x = -12 },
|
||||
{
|
||||
text = '\187',
|
||||
x = -3,
|
||||
dropdown = {
|
||||
{ text = 'Setup', event = 'network' },
|
||||
{ spacer = true },
|
||||
{
|
||||
text = 'Rescan storage',
|
||||
event = 'rescan',
|
||||
help = 'Rescan all inventories'
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
grid = UI.Grid {
|
||||
y = 2, ey = -2,
|
||||
columns = {
|
||||
{ heading = ' Qty', key = 'count' , width = 4, align = 'right' },
|
||||
{ heading = 'Name', key = 'displayName' },
|
||||
{ heading = 'Min', key = 'low' , width = 4 },
|
||||
{ heading = 'Max', key = 'limit' , width = 4 },
|
||||
},
|
||||
sortColumn = Milo:getState('sortColumn') or 'count',
|
||||
inverseSort = Milo:getState('inverseSort'),
|
||||
},
|
||||
statusBar = UI.StatusBar {
|
||||
filter = UI.TextEntry {
|
||||
x = 1, ex = -17,
|
||||
limit = 50,
|
||||
shadowText = 'filter',
|
||||
shadowTextColor = colors.gray,
|
||||
backgroundColor = colors.cyan,
|
||||
backgroundFocusColor = colors.cyan,
|
||||
accelerators = {
|
||||
[ 'enter' ] = 'eject',
|
||||
[ 'up' ] = 'grid_up',
|
||||
[ 'down' ] = 'grid_down',
|
||||
[ 'control-a' ] = 'eject_all',
|
||||
},
|
||||
},
|
||||
storageStatus = UI.Text {
|
||||
x = -16, ex = -9,
|
||||
textColor = colors.lime,
|
||||
backgroundColor = colors.cyan,
|
||||
value = '',
|
||||
},
|
||||
amount = UI.TextEntry {
|
||||
x = -8, ex = -4,
|
||||
limit = 3,
|
||||
shadowText = '1',
|
||||
shadowTextColor = colors.gray,
|
||||
backgroundColor = colors.black,
|
||||
backgroundFocusColor = colors.black,
|
||||
accelerators = {
|
||||
[ 'enter' ] = 'eject_specified',
|
||||
[ 'control-a' ] = 'eject_all',
|
||||
},
|
||||
help = 'Specify an amount to send',
|
||||
},
|
||||
display = UI.Button {
|
||||
x = -3,
|
||||
event = 'toggle_display',
|
||||
value = 0,
|
||||
text = displayModes[displayMode].text,
|
||||
help = displayModes[displayMode].help,
|
||||
},
|
||||
},
|
||||
notification = UI.Notification {
|
||||
anchor = 'top',
|
||||
},
|
||||
throttle = UI.Throttle {
|
||||
textColor = colors.yellow,
|
||||
borderColor = colors.gray,
|
||||
},
|
||||
accelerators = {
|
||||
r = 'refresh',
|
||||
[ 'control-r' ] = 'refresh',
|
||||
|
||||
[ 'control-e' ] = 'eject',
|
||||
[ 'control-s' ] = 'eject_stack',
|
||||
[ 'control-a' ] = 'eject_all',
|
||||
[ 'control-e' ] = 'eject',
|
||||
[ 'control-s' ] = 'eject_stack',
|
||||
[ 'control-a' ] = 'eject_all',
|
||||
|
||||
[ 'control-m' ] = 'network',
|
||||
[ 'control-m' ] = 'network',
|
||||
|
||||
q = 'quit',
|
||||
},
|
||||
allItems = { }
|
||||
q = 'quit',
|
||||
},
|
||||
allItems = { }
|
||||
}
|
||||
|
||||
function page.statusBar:draw()
|
||||
return UI.Window.draw(self)
|
||||
return UI.Window.draw(self)
|
||||
end
|
||||
|
||||
function page.grid:getRowTextColor(row, selected)
|
||||
if row.is_craftable then
|
||||
return colors.yellow
|
||||
end
|
||||
if row.has_recipe then
|
||||
return colors.cyan
|
||||
end
|
||||
return UI.Grid:getRowTextColor(row, selected)
|
||||
if row.is_craftable then
|
||||
return colors.yellow
|
||||
end
|
||||
if row.has_recipe then
|
||||
return colors.cyan
|
||||
end
|
||||
return UI.Grid:getRowTextColor(row, selected)
|
||||
end
|
||||
|
||||
function page.grid:getDisplayValues(row)
|
||||
row = Util.shallowCopy(row)
|
||||
row.count = row.count > 0 and 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
|
||||
row = Util.shallowCopy(row)
|
||||
row.count = row.count > 0 and 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 page.grid:sortCompare(a, b)
|
||||
if self.sortColumn ~= 'displayName' then
|
||||
if a[self.sortColumn] == b[self.sortColumn] then
|
||||
if self.inverseSort then
|
||||
return a.displayName > b.displayName
|
||||
end
|
||||
return a.displayName < b.displayName
|
||||
end
|
||||
if a[self.sortColumn] == 0 then
|
||||
return self.inverseSort
|
||||
end
|
||||
if b[self.sortColumn] == 0 then
|
||||
return not self.inverseSort
|
||||
end
|
||||
return a[self.sortColumn] < b[self.sortColumn]
|
||||
end
|
||||
return UI.Grid.sortCompare(self, a, b)
|
||||
if self.sortColumn ~= 'displayName' then
|
||||
if a[self.sortColumn] == b[self.sortColumn] then
|
||||
if self.inverseSort then
|
||||
return a.displayName > b.displayName
|
||||
end
|
||||
return a.displayName < b.displayName
|
||||
end
|
||||
if a[self.sortColumn] == 0 then
|
||||
return self.inverseSort
|
||||
end
|
||||
if b[self.sortColumn] == 0 then
|
||||
return not self.inverseSort
|
||||
end
|
||||
return a[self.sortColumn] < b[self.sortColumn]
|
||||
end
|
||||
return UI.Grid.sortCompare(self, a, b)
|
||||
end
|
||||
|
||||
function page.grid:eventHandler(event)
|
||||
if event.type == 'grid_sort' then
|
||||
Milo:setState('sortColumn', event.sortColumn)
|
||||
Milo:setState('inverseSort', event.inverseSort)
|
||||
end
|
||||
return UI.Grid.eventHandler(self, event)
|
||||
if event.type == 'grid_sort' then
|
||||
Milo:setState('sortColumn', event.sortColumn)
|
||||
Milo:setState('inverseSort', event.inverseSort)
|
||||
end
|
||||
return UI.Grid.eventHandler(self, event)
|
||||
end
|
||||
|
||||
function page:eject(amount)
|
||||
local item = self.grid:getSelected()
|
||||
if item and amount then
|
||||
-- get most up-to-date item
|
||||
if item then
|
||||
if amount == 'stack' then
|
||||
amount = item.maxCount or 64
|
||||
elseif amount == 'all' then
|
||||
item = Milo:getItem(item)
|
||||
if item then
|
||||
amount = item.count
|
||||
end
|
||||
end
|
||||
local item = self.grid:getSelected()
|
||||
if item and amount then
|
||||
-- get most up-to-date item
|
||||
if item then
|
||||
if amount == 'stack' then
|
||||
amount = item.maxCount or 64
|
||||
elseif amount == 'all' then
|
||||
item = Milo:getItem(item)
|
||||
if item then
|
||||
amount = item.count
|
||||
end
|
||||
end
|
||||
|
||||
if item and amount > 0 then
|
||||
item = Util.shallowCopy(item)
|
||||
self.grid.values[self.grid.sorted[self.grid.index]] = item
|
||||
local request = Milo:craftAndEject(item, amount)
|
||||
item.count = request.current - request.count
|
||||
if item and amount > 0 then
|
||||
item = Util.shallowCopy(item)
|
||||
self.grid.values[self.grid.sorted[self.grid.index]] = item
|
||||
local request = Milo:craftAndEject(item, amount)
|
||||
item.count = request.current - request.count
|
||||
|
||||
if request.craft then
|
||||
if request.craft > 0 then
|
||||
self:notifyInfo(request.craft .. ' crafting ...')
|
||||
elseif request.craft + request.count < request.requested then
|
||||
if request.craft + request.count == 0 then
|
||||
Sound.play('entity.villager.no')
|
||||
end
|
||||
self:notifyInfo((request.craft + request.count) .. ' available ...')
|
||||
end
|
||||
end
|
||||
if request.craft then
|
||||
if request.craft > 0 then
|
||||
self:notifyInfo(request.craft .. ' crafting ...')
|
||||
elseif request.craft + request.count < request.requested then
|
||||
if request.craft + request.count == 0 then
|
||||
Sound.play('entity.villager.no')
|
||||
end
|
||||
self:notifyInfo((request.craft + request.count) .. ' available ...')
|
||||
end
|
||||
end
|
||||
|
||||
if request.count + request.craft > 0 then
|
||||
self.grid:draw()
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Sound.play('entity.villager.no')
|
||||
if request.count + request.craft > 0 then
|
||||
self.grid:draw()
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Sound.play('entity.villager.no')
|
||||
end
|
||||
|
||||
function page:eventHandler(event)
|
||||
if event.type == 'quit' then
|
||||
UI:exitPullEvents()
|
||||
if event.type == 'quit' then
|
||||
UI:exitPullEvents()
|
||||
|
||||
elseif event.type == 'eject' or event.type == 'grid_select' then
|
||||
self:eject(1)
|
||||
elseif event.type == 'eject' or event.type == 'grid_select' then
|
||||
self:eject(1)
|
||||
|
||||
elseif event.type == 'eject_stack' then
|
||||
self:eject('stack')
|
||||
elseif event.type == 'eject_stack' then
|
||||
self:eject('stack')
|
||||
|
||||
elseif event.type == 'eject_all' then
|
||||
self:eject('all')
|
||||
elseif event.type == 'eject_all' then
|
||||
self:eject('all')
|
||||
|
||||
elseif event.type == 'eject_specified' then
|
||||
if self:eject(tonumber(self.statusBar.amount.value)) then
|
||||
self.statusBar.amount:reset()
|
||||
self:setFocus(self.statusBar.filter)
|
||||
end
|
||||
elseif event.type == 'eject_specified' then
|
||||
if self:eject(tonumber(self.statusBar.amount.value)) then
|
||||
self.statusBar.amount:reset()
|
||||
self:setFocus(self.statusBar.filter)
|
||||
end
|
||||
|
||||
elseif event.type == 'network' then
|
||||
UI:setPage('network')
|
||||
elseif event.type == 'network' then
|
||||
UI:setPage('network')
|
||||
|
||||
elseif event.type == 'details' or event.type == 'grid_select_right' then
|
||||
local item = self.grid:getSelected()
|
||||
if item then
|
||||
UI:setPage('item', item)
|
||||
end
|
||||
elseif event.type == 'details' or event.type == 'grid_select_right' then
|
||||
local item = self.grid:getSelected()
|
||||
if item then
|
||||
UI:setPage('item', item)
|
||||
end
|
||||
|
||||
elseif event.type == 'grid_up' then
|
||||
self.grid:emit({ type = 'scroll_up' })
|
||||
elseif event.type == 'grid_up' then
|
||||
self.grid:emit({ type = 'scroll_up' })
|
||||
|
||||
elseif event.type == 'grid_down' then
|
||||
self.grid:emit({ type = 'scroll_down' })
|
||||
elseif event.type == 'grid_down' then
|
||||
self.grid:emit({ type = 'scroll_down' })
|
||||
|
||||
elseif event.type == 'refresh' then
|
||||
self:refresh()
|
||||
self.grid:draw()
|
||||
self:setFocus(self.statusBar.filter)
|
||||
elseif event.type == 'refresh' then
|
||||
self:refresh()
|
||||
self.grid:draw()
|
||||
self:setFocus(self.statusBar.filter)
|
||||
|
||||
elseif event.type == 'rescan' then
|
||||
self:refresh(true)
|
||||
self.grid:draw()
|
||||
self:setFocus(self.statusBar.filter)
|
||||
elseif event.type == 'rescan' then
|
||||
self:refresh(true)
|
||||
self.grid:draw()
|
||||
self:setFocus(self.statusBar.filter)
|
||||
|
||||
elseif event.type == 'toggle_display' then
|
||||
displayMode = (displayMode + 1) % 2
|
||||
Util.merge(event.button, displayModes[displayMode])
|
||||
event.button:draw()
|
||||
self:applyFilter()
|
||||
self.grid:draw()
|
||||
Milo:setState('displayMode', displayMode)
|
||||
elseif event.type == 'toggle_display' then
|
||||
displayMode = (displayMode + 1) % 2
|
||||
Util.merge(event.button, displayModes[displayMode])
|
||||
event.button:draw()
|
||||
self:applyFilter()
|
||||
self.grid:draw()
|
||||
Milo:setState('displayMode', displayMode)
|
||||
|
||||
elseif event.type == 'learn' then
|
||||
UI:setPage('learnWizard')
|
||||
elseif event.type == 'learn' then
|
||||
UI:setPage('learnWizard')
|
||||
|
||||
elseif event.type == 'craft' then
|
||||
local item = self.grid:getSelected()
|
||||
if item then
|
||||
if Craft.findRecipe(item) then -- or item.is_craftable then
|
||||
UI:setPage('craft', self.grid:getSelected())
|
||||
else
|
||||
self.notification:error('No recipe defined')
|
||||
end
|
||||
end
|
||||
elseif event.type == 'craft' then
|
||||
local item = self.grid:getSelected()
|
||||
if item then
|
||||
if Craft.findRecipe(item) then -- or item.is_craftable then
|
||||
UI:setPage('craft', self.grid:getSelected())
|
||||
else
|
||||
self.notification:error('No recipe defined')
|
||||
end
|
||||
end
|
||||
|
||||
elseif event.type == 'text_change' and event.element == self.statusBar.filter then
|
||||
self.filter = event.text
|
||||
if #self.filter == 0 then
|
||||
self.filter = nil
|
||||
end
|
||||
self:applyFilter()
|
||||
self.grid:setIndex(1)
|
||||
self.grid:draw()
|
||||
self.statusBar.filter:focus()
|
||||
elseif event.type == 'text_change' and event.element == self.statusBar.filter then
|
||||
self.filter = event.text
|
||||
if #self.filter == 0 then
|
||||
self.filter = nil
|
||||
end
|
||||
self:applyFilter()
|
||||
self.grid:setIndex(1)
|
||||
self.grid:draw()
|
||||
self.statusBar.filter:focus()
|
||||
|
||||
else
|
||||
UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
else
|
||||
UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function page:notifySuccess(status)
|
||||
self.notification:success(status)
|
||||
self.notification:success(status)
|
||||
end
|
||||
|
||||
function page:notifyInfo(status)
|
||||
self.notification:info(status)
|
||||
self.notification:info(status)
|
||||
end
|
||||
|
||||
function page:notifyError(status)
|
||||
self.notification:error(status)
|
||||
self.notification:error(status)
|
||||
end
|
||||
|
||||
function page:enable(args)
|
||||
local function updateStatus()
|
||||
self.statusBar.storageStatus.value =
|
||||
context.storage:isOnline() and '' or 'offline'
|
||||
self.statusBar.storageStatus.textColor =
|
||||
context.storage:isOnline() and colors.lime or colors.red
|
||||
end
|
||||
updateStatus()
|
||||
local function updateStatus()
|
||||
self.statusBar.storageStatus.value =
|
||||
context.storage:isOnline() and '' or 'offline'
|
||||
self.statusBar.storageStatus.textColor =
|
||||
context.storage:isOnline() and colors.lime or colors.red
|
||||
end
|
||||
updateStatus()
|
||||
|
||||
Event.onTimeout(0, function()
|
||||
self:refresh()
|
||||
self:draw()
|
||||
self:sync()
|
||||
Event.onTimeout(0, function()
|
||||
self:refresh()
|
||||
self:draw()
|
||||
self:sync()
|
||||
|
||||
self.timer = Event.onInterval(3, function()
|
||||
for _,v in pairs(self.grid.values) do
|
||||
local c = context.storage.cache[v.key]
|
||||
v.count = c and c.count or 0
|
||||
end
|
||||
self.grid:draw()
|
||||
self:sync()
|
||||
end)
|
||||
self.timer = Event.onInterval(3, function()
|
||||
for _,v in pairs(self.grid.values) do
|
||||
local c = context.storage.cache[v.key]
|
||||
v.count = c and c.count or 0
|
||||
end
|
||||
self.grid:draw()
|
||||
self:sync()
|
||||
end)
|
||||
|
||||
self.handler = Event.on({ 'storage_offline', 'storage_online' }, function()
|
||||
updateStatus()
|
||||
self.statusBar.storageStatus:draw()
|
||||
self:sync()
|
||||
end)
|
||||
end)
|
||||
self.handler = Event.on({ 'storage_offline', 'storage_online' }, function()
|
||||
updateStatus()
|
||||
self.statusBar.storageStatus:draw()
|
||||
self:sync()
|
||||
end)
|
||||
end)
|
||||
|
||||
if args and args.filter then
|
||||
self.filter = args.filter
|
||||
self.statusBar.filter.value = args.filter
|
||||
end
|
||||
if args and args.filter then
|
||||
self.filter = args.filter
|
||||
self.statusBar.filter.value = args.filter
|
||||
end
|
||||
|
||||
if args and args.message then
|
||||
self.notification:success(args.message)
|
||||
end
|
||||
if args and args.message then
|
||||
self.notification:success(args.message)
|
||||
end
|
||||
|
||||
self:setFocus(self.statusBar.filter)
|
||||
UI.Page.enable(self)
|
||||
self:setFocus(self.statusBar.filter)
|
||||
UI.Page.enable(self)
|
||||
end
|
||||
|
||||
function page:disable()
|
||||
Event.off(self.timer)
|
||||
Event.off(self.handler)
|
||||
UI.Page.disable(self)
|
||||
Event.off(self.timer)
|
||||
Event.off(self.handler)
|
||||
UI.Page.disable(self)
|
||||
end
|
||||
|
||||
function page:refresh(force)
|
||||
local throttle = function() self.throttle:update() end
|
||||
local throttle = function() self.throttle:update() end
|
||||
|
||||
self.throttle:enable()
|
||||
self.allItems = Milo:mergeResources(Milo:listItems(force, throttle))
|
||||
self:applyFilter()
|
||||
self.throttle:disable()
|
||||
self.throttle:enable()
|
||||
self.allItems = Milo:mergeResources(Milo:listItems(force, throttle))
|
||||
self:applyFilter()
|
||||
self.throttle:disable()
|
||||
end
|
||||
|
||||
function page:applyFilter()
|
||||
local function filterItems(t, filter)
|
||||
self.grid.sortColumn = Milo:getState('sortColumn') or 'count'
|
||||
self.grid.inverseSort = Milo:getState('inverseSort')
|
||||
local function filterItems(t, filter)
|
||||
self.grid.sortColumn = Milo:getState('sortColumn') or 'count'
|
||||
self.grid.inverseSort = Milo:getState('inverseSort')
|
||||
|
||||
if filter then
|
||||
local r = { }
|
||||
filter = filter:lower()
|
||||
self.grid.sortColumn = 'score'
|
||||
self.grid.inverseSort = true
|
||||
if filter then
|
||||
local r = { }
|
||||
filter = filter:lower()
|
||||
self.grid.sortColumn = 'score'
|
||||
self.grid.inverseSort = true
|
||||
|
||||
for _,v in pairs(t) do
|
||||
v.score = fuzzy(v.lname, filter)
|
||||
if v.score then
|
||||
if v.count > 0 then
|
||||
v.score = v.score + 1
|
||||
end
|
||||
table.insert(r, v)
|
||||
end
|
||||
end
|
||||
return r
|
||||
for _,v in pairs(t) do
|
||||
v.score = fuzzy(v.lname, filter)
|
||||
if v.score then
|
||||
if v.count > 0 then
|
||||
v.score = v.score + 1
|
||||
end
|
||||
table.insert(r, v)
|
||||
end
|
||||
end
|
||||
return r
|
||||
|
||||
elseif displayMode > 0 then
|
||||
local r = { }
|
||||
elseif displayMode > 0 then
|
||||
local r = { }
|
||||
|
||||
for _,v in pairs(t) do
|
||||
if v.count > 0 then
|
||||
table.insert(r, v)
|
||||
end
|
||||
end
|
||||
return r
|
||||
end
|
||||
for _,v in pairs(t) do
|
||||
if v.count > 0 then
|
||||
table.insert(r, v)
|
||||
end
|
||||
end
|
||||
return r
|
||||
end
|
||||
|
||||
return t
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
local t = filterItems(self.allItems, self.filter)
|
||||
self.grid:setValues(t)
|
||||
local t = filterItems(self.allItems, self.filter)
|
||||
self.grid:setValues(t)
|
||||
end
|
||||
|
||||
UI:addPage('listing', page)
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
{
|
||||
[ "9302912a2d9794a47241faefc475335b4e07a581" ] = {
|
||||
title = "Remote",
|
||||
category = "Apps",
|
||||
run = "MiloRemote",
|
||||
requires = "neuralInterface",
|
||||
iconExt = "\0304\031f\135\129\0314\128\128\031f\130\030f\128\
|
||||
[ "9302912a2d9794a47241faefc475335b4e07a581" ] = {
|
||||
title = "Remote",
|
||||
category = "Apps",
|
||||
run = "MiloRemote",
|
||||
requires = "neuralInterface",
|
||||
iconExt = "\0304\031f\135\129\0314\128\128\031f\130\030f\128\
|
||||
\031f\128\031c\159\149\0300\0317\143\0304\031c\149\030f\0314\133\
|
||||
\031f\128\030c\0310\142\030f\031c\149\030c\0310\139\030f\031c\149\031f\128",
|
||||
},
|
||||
[ "eea426f9baef72a8fcefd091e0cec5ab94a76698" ] = {
|
||||
title = "Milo",
|
||||
category = "Apps",
|
||||
run = "MiloLocal",
|
||||
requires = 'advancedTurtle',
|
||||
iconExt = "\0304\031f\135\129\0314\128\128\031f\130\030f\128\
|
||||
},
|
||||
[ "eea426f9baef72a8fcefd091e0cec5ab94a76698" ] = {
|
||||
title = "Milo",
|
||||
category = "Apps",
|
||||
run = "MiloLocal",
|
||||
requires = 'advancedTurtle',
|
||||
iconExt = "\0304\031f\135\129\0314\128\128\031f\130\030f\128\
|
||||
\031f\128\031c\159\149\0300\0317\143\0304\031c\149\030f\0314\133\
|
||||
\031f\128\030c\0310\142\030f\031c\149\030c\0310\139\030f\031c\149\031f\128",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -15,239 +15,239 @@ local template =
|
||||
Right-clicking on the activity monitor will reset the totals.]]
|
||||
|
||||
local wizardPage = UI.WizardPage {
|
||||
title = 'Activity Monitor',
|
||||
index = 2,
|
||||
backgroundColor = colors.cyan,
|
||||
[1] = UI.TextArea {
|
||||
x = 2, ex = -2, y = 2, ey = 6,
|
||||
marginRight = 0,
|
||||
value = string.format(template, Ansi.yellow, Ansi.reset),
|
||||
},
|
||||
form = UI.Form {
|
||||
x = 2, ex = -2, y = 7, ey = -2,
|
||||
manualControls = true,
|
||||
[1] = UI.Chooser {
|
||||
width = 9,
|
||||
formLabel = 'Font Size', formKey = 'textScale',
|
||||
nochoice = 'Small',
|
||||
choices = {
|
||||
{ name = 'Small', value = .5 },
|
||||
{ name = 'Large', value = 1 },
|
||||
},
|
||||
help = 'Adjust text scaling',
|
||||
},
|
||||
},
|
||||
title = 'Activity Monitor',
|
||||
index = 2,
|
||||
backgroundColor = colors.cyan,
|
||||
[1] = UI.TextArea {
|
||||
x = 2, ex = -2, y = 2, ey = 6,
|
||||
marginRight = 0,
|
||||
value = string.format(template, Ansi.yellow, Ansi.reset),
|
||||
},
|
||||
form = UI.Form {
|
||||
x = 2, ex = -2, y = 7, ey = -2,
|
||||
manualControls = true,
|
||||
[1] = UI.Chooser {
|
||||
width = 9,
|
||||
formLabel = 'Font Size', formKey = 'textScale',
|
||||
nochoice = 'Small',
|
||||
choices = {
|
||||
{ name = 'Small', value = .5 },
|
||||
{ name = 'Large', value = 1 },
|
||||
},
|
||||
help = 'Adjust text scaling',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
function wizardPage:setNode(node)
|
||||
self.form:setValues(node)
|
||||
self.form:setValues(node)
|
||||
end
|
||||
|
||||
function wizardPage:validate()
|
||||
return self.form:save()
|
||||
return self.form:save()
|
||||
end
|
||||
|
||||
function wizardPage:saveNode(node)
|
||||
os.queueEvent('monitor_resize', node.name)
|
||||
os.queueEvent('monitor_resize', node.name)
|
||||
end
|
||||
|
||||
function wizardPage:isValidType(node)
|
||||
local m = device[node.name]
|
||||
return m and m.type == 'monitor' and {
|
||||
name = 'Activity Monitor',
|
||||
value = 'activity',
|
||||
category = 'display',
|
||||
help = 'Display storage activity'
|
||||
}
|
||||
local m = device[node.name]
|
||||
return m and m.type == 'monitor' and {
|
||||
name = 'Activity Monitor',
|
||||
value = 'activity',
|
||||
category = 'display',
|
||||
help = 'Display storage activity'
|
||||
}
|
||||
end
|
||||
|
||||
function wizardPage:isValidFor(node)
|
||||
return node.mtype == 'activity'
|
||||
return node.mtype == 'activity'
|
||||
end
|
||||
|
||||
UI:getPage('nodeWizard').wizard:add({ activity = wizardPage })
|
||||
|
||||
--[[ Display ]]--
|
||||
local function createPage(node)
|
||||
local monitor = UI.Device {
|
||||
device = node.adapter,
|
||||
textScale = node.textScale or .5,
|
||||
}
|
||||
local monitor = UI.Device {
|
||||
device = node.adapter,
|
||||
textScale = node.textScale or .5,
|
||||
}
|
||||
|
||||
function monitor:resize()
|
||||
self.textScale = node.textScale or .5
|
||||
UI.Device.resize(self)
|
||||
end
|
||||
function monitor:resize()
|
||||
self.textScale = node.textScale or .5
|
||||
UI.Device.resize(self)
|
||||
end
|
||||
|
||||
local page = UI.Page {
|
||||
parent = monitor,
|
||||
backgroundColor = colors.black,
|
||||
grid = UI.Grid {
|
||||
ey = -3,
|
||||
columns = {
|
||||
{ heading = 'Qty', key = 'count', width = 6, align = 'right' },
|
||||
{ heading = '+/-', key = 'change', width = 6, align = 'right' },
|
||||
{ heading = 'Name', key = 'displayName' },
|
||||
{ heading = 'Rate', key = 'rate', width = 6, align = 'right' },
|
||||
},
|
||||
sortColumn = 'displayName',
|
||||
headerBackgroundColor = colors.black,
|
||||
headerTextColor = colors.cyan,
|
||||
headerHeight = 2,
|
||||
},
|
||||
buttons = UI.Window {
|
||||
y = -1,
|
||||
backgroundColor = colors.black,
|
||||
prevButton = UI.Button {
|
||||
x = 1, width = 5,
|
||||
event = 'previous',
|
||||
textColor = colors.cyan,
|
||||
backgroundColor = colors.black,
|
||||
text = ' < '
|
||||
},
|
||||
resetButton = UI.Button {
|
||||
x = 7, ex = -7,
|
||||
event = 'reset',
|
||||
textColor = colors.cyan,
|
||||
backgroundColor = colors.black,
|
||||
text = 'Reset'
|
||||
},
|
||||
nextButton = UI.Button {
|
||||
x = -5, width = 5,
|
||||
event = 'next',
|
||||
textColor = colors.cyan,
|
||||
backgroundColor = colors.black,
|
||||
text = ' > '
|
||||
},
|
||||
},
|
||||
timestamp = os.clock(),
|
||||
}
|
||||
local page = UI.Page {
|
||||
parent = monitor,
|
||||
backgroundColor = colors.black,
|
||||
grid = UI.Grid {
|
||||
ey = -3,
|
||||
columns = {
|
||||
{ heading = 'Qty', key = 'count', width = 6, align = 'right' },
|
||||
{ heading = '+/-', key = 'change', width = 6, align = 'right' },
|
||||
{ heading = 'Name', key = 'displayName' },
|
||||
{ heading = 'Rate', key = 'rate', width = 6, align = 'right' },
|
||||
},
|
||||
sortColumn = 'displayName',
|
||||
headerBackgroundColor = colors.black,
|
||||
headerTextColor = colors.cyan,
|
||||
headerHeight = 2,
|
||||
},
|
||||
buttons = UI.Window {
|
||||
y = -1,
|
||||
backgroundColor = colors.black,
|
||||
prevButton = UI.Button {
|
||||
x = 1, width = 5,
|
||||
event = 'previous',
|
||||
textColor = colors.cyan,
|
||||
backgroundColor = colors.black,
|
||||
text = ' < '
|
||||
},
|
||||
resetButton = UI.Button {
|
||||
x = 7, ex = -7,
|
||||
event = 'reset',
|
||||
textColor = colors.cyan,
|
||||
backgroundColor = colors.black,
|
||||
text = 'Reset'
|
||||
},
|
||||
nextButton = UI.Button {
|
||||
x = -5, width = 5,
|
||||
event = 'next',
|
||||
textColor = colors.cyan,
|
||||
backgroundColor = colors.black,
|
||||
text = ' > '
|
||||
},
|
||||
},
|
||||
timestamp = os.clock(),
|
||||
}
|
||||
|
||||
function page.grid:getRowTextColor(row, selected)
|
||||
if row.lastCount and row.lastCount ~= row.count then
|
||||
return row.count > row.lastCount and colors.yellow or colors.lightGray
|
||||
end
|
||||
return UI.Grid:getRowTextColor(row, selected)
|
||||
end
|
||||
function page.grid:getRowTextColor(row, selected)
|
||||
if row.lastCount and row.lastCount ~= row.count then
|
||||
return row.count > row.lastCount and colors.yellow or colors.lightGray
|
||||
end
|
||||
return UI.Grid:getRowTextColor(row, selected)
|
||||
end
|
||||
|
||||
function page.grid:getDisplayValues(row)
|
||||
row = Util.shallowCopy(row)
|
||||
function page.grid:getDisplayValues(row)
|
||||
row = Util.shallowCopy(row)
|
||||
|
||||
local ind = '+'
|
||||
if row.change < 0 then
|
||||
ind = ''
|
||||
end
|
||||
local ind = '+'
|
||||
if row.change < 0 then
|
||||
ind = ''
|
||||
end
|
||||
|
||||
row.change = ind .. Util.toBytes(row.change)
|
||||
row.count = Util.toBytes(row.count)
|
||||
row.rate = Util.toBytes(row.rate)
|
||||
row.change = ind .. Util.toBytes(row.change)
|
||||
row.count = Util.toBytes(row.count)
|
||||
row.rate = Util.toBytes(row.rate)
|
||||
|
||||
return row
|
||||
end
|
||||
return row
|
||||
end
|
||||
|
||||
function page:eventHandler(event)
|
||||
if event.type == 'reset' then
|
||||
self:reset()
|
||||
function page:eventHandler(event)
|
||||
if event.type == 'reset' then
|
||||
self:reset()
|
||||
|
||||
elseif event.type == 'next' then
|
||||
self.grid:nextPage()
|
||||
elseif event.type == 'next' then
|
||||
self.grid:nextPage()
|
||||
|
||||
elseif event.type == 'previous' then
|
||||
self.grid:previousPage()
|
||||
elseif event.type == 'previous' then
|
||||
self.grid:previousPage()
|
||||
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
|
||||
Event.onTimeout(.1, function()
|
||||
self:setFocus(self.grid)
|
||||
self:sync()
|
||||
end)
|
||||
return true
|
||||
end
|
||||
Event.onTimeout(.1, function()
|
||||
self:setFocus(self.grid)
|
||||
self:sync()
|
||||
end)
|
||||
return true
|
||||
end
|
||||
|
||||
function page:reset()
|
||||
self.lastItems = nil
|
||||
self.grid:setValues({ })
|
||||
self.grid:draw()
|
||||
end
|
||||
function page:reset()
|
||||
self.lastItems = nil
|
||||
self.grid:setValues({ })
|
||||
self.grid:draw()
|
||||
end
|
||||
|
||||
function page:refresh()
|
||||
local t = context.storage.cache
|
||||
function page:refresh()
|
||||
local t = context.storage.cache
|
||||
|
||||
if t and not self.lastItems then
|
||||
self.lastItems = { }
|
||||
for k,v in pairs(t) do
|
||||
self.lastItems[k] = {
|
||||
displayName = v.displayName,
|
||||
initialCount = v.count,
|
||||
}
|
||||
end
|
||||
self.timestamp = os.clock()
|
||||
self.grid:setValues({ })
|
||||
if t and not self.lastItems then
|
||||
self.lastItems = { }
|
||||
for k,v in pairs(t) do
|
||||
self.lastItems[k] = {
|
||||
displayName = v.displayName,
|
||||
initialCount = v.count,
|
||||
}
|
||||
end
|
||||
self.timestamp = os.clock()
|
||||
self.grid:setValues({ })
|
||||
|
||||
else
|
||||
for _,v in pairs(self.lastItems) do
|
||||
v.lastCount = v.count
|
||||
v.count = nil
|
||||
end
|
||||
else
|
||||
for _,v in pairs(self.lastItems) do
|
||||
v.lastCount = v.count
|
||||
v.count = nil
|
||||
end
|
||||
|
||||
self.elapsed = os.clock() - self.timestamp
|
||||
self.elapsed = os.clock() - self.timestamp
|
||||
|
||||
for k,v in pairs(t) do
|
||||
local v2 = self.lastItems[k]
|
||||
if v2 then
|
||||
v2.count = v.count
|
||||
else
|
||||
self.lastItems[k] = {
|
||||
displayName = v.displayName,
|
||||
count = v.count,
|
||||
initialCount = 0,
|
||||
}
|
||||
end
|
||||
end
|
||||
for k,v in pairs(t) do
|
||||
local v2 = self.lastItems[k]
|
||||
if v2 then
|
||||
v2.count = v.count
|
||||
else
|
||||
self.lastItems[k] = {
|
||||
displayName = v.displayName,
|
||||
count = v.count,
|
||||
initialCount = 0,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
local changedItems = { }
|
||||
for k,v in pairs(self.lastItems) do
|
||||
if not v.count then
|
||||
v.count = 0
|
||||
end
|
||||
if v.count ~= v.initialCount then
|
||||
v.change = v.count - v.initialCount
|
||||
v.rate = Util.round(60 / self.elapsed * v.change, 1)
|
||||
changedItems[k] = v
|
||||
end
|
||||
end
|
||||
local changedItems = { }
|
||||
for k,v in pairs(self.lastItems) do
|
||||
if not v.count then
|
||||
v.count = 0
|
||||
end
|
||||
if v.count ~= v.initialCount then
|
||||
v.change = v.count - v.initialCount
|
||||
v.rate = Util.round(60 / self.elapsed * v.change, 1)
|
||||
changedItems[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
self.grid:setValues(changedItems)
|
||||
end
|
||||
self.grid:draw()
|
||||
end
|
||||
self.grid:setValues(changedItems)
|
||||
end
|
||||
self.grid:draw()
|
||||
end
|
||||
|
||||
function page:update()
|
||||
page:refresh()
|
||||
page:sync()
|
||||
end
|
||||
function page:update()
|
||||
page:refresh()
|
||||
page:sync()
|
||||
end
|
||||
|
||||
UI:setPage(page)
|
||||
return page
|
||||
UI:setPage(page)
|
||||
return page
|
||||
end
|
||||
|
||||
local pages = { }
|
||||
|
||||
--[[ Task ]]--
|
||||
local ActivityTask = {
|
||||
name = 'activity',
|
||||
priority = 30,
|
||||
name = 'activity',
|
||||
priority = 30,
|
||||
}
|
||||
|
||||
function ActivityTask:cycle()
|
||||
for node in context.storage:filterActive('activity') do
|
||||
if not pages[node.name] then
|
||||
pages[node.name] = createPage(node)
|
||||
end
|
||||
pages[node.name]:update()
|
||||
end
|
||||
for node in context.storage:filterActive('activity') do
|
||||
if not pages[node.name] then
|
||||
pages[node.name] = createPage(node)
|
||||
end
|
||||
pages[node.name]:update()
|
||||
end
|
||||
end
|
||||
|
||||
Milo:registerTask(ActivityTask)
|
||||
|
||||
@@ -21,97 +21,97 @@ Backup configuration files each minecraft day.
|
||||
]]
|
||||
|
||||
local wizardPage = UI.WizardPage {
|
||||
title = 'Backup Drive',
|
||||
index = 2,
|
||||
backgroundColor = colors.cyan,
|
||||
[1] = UI.TextArea {
|
||||
x = 2, ex = -2, y = 2, ey = -2,
|
||||
value = string.format(template, Ansi.yellow, Ansi.reset),
|
||||
},
|
||||
title = 'Backup Drive',
|
||||
index = 2,
|
||||
backgroundColor = colors.cyan,
|
||||
[1] = UI.TextArea {
|
||||
x = 2, ex = -2, y = 2, ey = -2,
|
||||
value = string.format(template, Ansi.yellow, Ansi.reset),
|
||||
},
|
||||
}
|
||||
|
||||
function wizardPage:isValidType(node)
|
||||
local m = device[node.name]
|
||||
return m and m.type == 'drive' and {
|
||||
name = 'Backup Drive',
|
||||
value = 'backup',
|
||||
category = 'custom',
|
||||
help = 'Backup configuration files',
|
||||
}
|
||||
local m = device[node.name]
|
||||
return m and m.type == 'drive' and {
|
||||
name = 'Backup Drive',
|
||||
value = 'backup',
|
||||
category = 'custom',
|
||||
help = 'Backup configuration files',
|
||||
}
|
||||
end
|
||||
|
||||
function wizardPage:isValidFor(node)
|
||||
return node.mtype == 'backup'
|
||||
return node.mtype == 'backup'
|
||||
end
|
||||
|
||||
UI:getPage('nodeWizard').wizard:add({ backupDrive = wizardPage })
|
||||
|
||||
local function clearOld(dir, fname)
|
||||
local files = { }
|
||||
local files = { }
|
||||
|
||||
for _, file in pairs(fs.list(dir)) do
|
||||
if file:match(fname) then
|
||||
table.insert(files, file)
|
||||
end
|
||||
end
|
||||
if #files > 1 then
|
||||
table.sort(files, function(a, b)
|
||||
return tonumber(a:match('.(%d+)')) > tonumber(b:match('.(%d+)'))
|
||||
end)
|
||||
while #files > 1 do
|
||||
local old = table.remove(files, #files)
|
||||
fs.delete(fs.combine(dir, old))
|
||||
end
|
||||
end
|
||||
for _, file in pairs(fs.list(dir)) do
|
||||
if file:match(fname) then
|
||||
table.insert(files, file)
|
||||
end
|
||||
end
|
||||
if #files > 1 then
|
||||
table.sort(files, function(a, b)
|
||||
return tonumber(a:match('.(%d+)')) > tonumber(b:match('.(%d+)'))
|
||||
end)
|
||||
while #files > 1 do
|
||||
local old = table.remove(files, #files)
|
||||
fs.delete(fs.combine(dir, old))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function makeBackup(dir, fname)
|
||||
clearOld(dir, fname)
|
||||
local source = fs.combine('usr/config', fname)
|
||||
local dest = string.format('%s/%s.%d', dir, fname, os.day())
|
||||
fs.copy(source, dest)
|
||||
clearOld(dir, fname)
|
||||
local source = fs.combine('usr/config', fname)
|
||||
local dest = string.format('%s/%s.%d', dir, fname, os.day())
|
||||
fs.copy(source, dest)
|
||||
end
|
||||
|
||||
local function backupNode(node)
|
||||
local files = {
|
||||
'storage',
|
||||
'milo.state',
|
||||
'machine_crafting.db',
|
||||
'recipes.db',
|
||||
'resources.db',
|
||||
}
|
||||
local s, m = pcall(function()
|
||||
if not node.adapter.isDiskPresent() then
|
||||
_G._syslog('BACKUP error: No media present')
|
||||
else
|
||||
local dir = node.adapter.getMountPath()
|
||||
for _, v in pairs(files) do
|
||||
makeBackup(dir, v)
|
||||
end
|
||||
end
|
||||
end)
|
||||
if not s and m then
|
||||
_G._syslog('BACKUP error:' .. m)
|
||||
end
|
||||
local files = {
|
||||
'storage',
|
||||
'milo.state',
|
||||
'machine_crafting.db',
|
||||
'recipes.db',
|
||||
'resources.db',
|
||||
}
|
||||
local s, m = pcall(function()
|
||||
if not node.adapter.isDiskPresent() then
|
||||
_G._syslog('BACKUP error: No media present')
|
||||
else
|
||||
local dir = node.adapter.getMountPath()
|
||||
for _, v in pairs(files) do
|
||||
makeBackup(dir, v)
|
||||
end
|
||||
end
|
||||
end)
|
||||
if not s and m then
|
||||
_G._syslog('BACKUP error:' .. m)
|
||||
end
|
||||
end
|
||||
|
||||
--[[ Task ]]--
|
||||
local BackupTask = {
|
||||
name = 'backup',
|
||||
priority = 99,
|
||||
name = 'backup',
|
||||
priority = 99,
|
||||
}
|
||||
|
||||
function BackupTask:cycle()
|
||||
for node in context.storage:filterActive('backup') do
|
||||
if not drives[node.name] then
|
||||
drives[node.name] = Event.onInterval(DAY, function()
|
||||
_G._syslog('BACKUP: started')
|
||||
if node.adapter and node.adapter.online then
|
||||
backupNode(node)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
for node in context.storage:filterActive('backup') do
|
||||
if not drives[node.name] then
|
||||
drives[node.name] = Event.onInterval(DAY, function()
|
||||
_G._syslog('BACKUP: started')
|
||||
if node.adapter and node.adapter.online then
|
||||
backupNode(node)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Milo:registerTask(BackupTask)
|
||||
|
||||
@@ -6,67 +6,67 @@ local Util = require('util')
|
||||
local context = Milo:getContext()
|
||||
|
||||
local craftTask = {
|
||||
name = 'crafting',
|
||||
priority = 70,
|
||||
name = 'crafting',
|
||||
priority = 70,
|
||||
}
|
||||
|
||||
function craftTask:craft(recipe, item)
|
||||
if Milo:isCraftingPaused() then
|
||||
return
|
||||
end
|
||||
if Milo:isCraftingPaused() then
|
||||
return
|
||||
end
|
||||
|
||||
-- TODO: refactor into craft.lua
|
||||
Craft.processPending(item, context.storage)
|
||||
-- TODO: refactor into craft.lua
|
||||
Craft.processPending(item, context.storage)
|
||||
|
||||
-- create a mini-list of items that are required for this recipe
|
||||
item.ingredients = Craft.getResourceList(
|
||||
recipe, Milo:listItems(), item.requested - item.crafted, item.pending)
|
||||
-- create a mini-list of items that are required for this recipe
|
||||
item.ingredients = Craft.getResourceList(
|
||||
recipe, Milo:listItems(), item.requested - item.crafted, item.pending)
|
||||
|
||||
for k, v in pairs(item.ingredients) do
|
||||
v.crafted = v.used
|
||||
v.count = v.used
|
||||
v.key = k
|
||||
if v.need > 0 then
|
||||
v.status = 'No recipe'
|
||||
v.statusCode = Craft.STATUS_ERROR
|
||||
end
|
||||
end
|
||||
item.ingredients[recipe.result] = item
|
||||
item.ingredients[recipe.result].total = item.count
|
||||
item.ingredients[recipe.result].crafted = item.crafted
|
||||
for k, v in pairs(item.ingredients) do
|
||||
v.crafted = v.used
|
||||
v.count = v.used
|
||||
v.key = k
|
||||
if v.need > 0 then
|
||||
v.status = 'No recipe'
|
||||
v.statusCode = Craft.STATUS_ERROR
|
||||
end
|
||||
end
|
||||
item.ingredients[recipe.result] = item
|
||||
item.ingredients[recipe.result].total = item.count
|
||||
item.ingredients[recipe.result].crafted = item.crafted
|
||||
|
||||
Craft.craftRecipe(recipe, item.requested - item.crafted, context.storage, item)
|
||||
Craft.craftRecipe(recipe, item.requested - item.crafted, context.storage, item)
|
||||
end
|
||||
|
||||
function craftTask:cycle()
|
||||
for _,key in pairs(Util.keys(context.craftingQueue)) do
|
||||
local item = context.craftingQueue[key]
|
||||
if item.requested - item.crafted > 0 then
|
||||
local recipe = Craft.findRecipe(key)
|
||||
if recipe then
|
||||
for _,key in pairs(Util.keys(context.craftingQueue)) do
|
||||
local item = context.craftingQueue[key]
|
||||
if item.requested - item.crafted > 0 then
|
||||
local recipe = Craft.findRecipe(key)
|
||||
if recipe then
|
||||
|
||||
if not item.notified then
|
||||
Sound.play('block.end_portal_frame.fill')
|
||||
item.notified = true
|
||||
end
|
||||
if not item.notified then
|
||||
Sound.play('block.end_portal_frame.fill')
|
||||
item.notified = true
|
||||
end
|
||||
|
||||
self:craft(recipe, item)
|
||||
self:craft(recipe, item)
|
||||
|
||||
if item.crafted >= item.requested then
|
||||
item.status = 'crafted'
|
||||
item.statusCode = Craft.STATUS_SUCCESS
|
||||
if item.callback then
|
||||
item.callback(item) -- invoke callback
|
||||
end
|
||||
end
|
||||
if item.crafted >= item.requested then
|
||||
item.status = 'crafted'
|
||||
item.statusCode = Craft.STATUS_SUCCESS
|
||||
if item.callback then
|
||||
item.callback(item) -- invoke callback
|
||||
end
|
||||
end
|
||||
|
||||
else
|
||||
item.status = '(no recipe)'
|
||||
item.statusCode = Craft.STATUS_ERROR
|
||||
item.crafted = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
item.status = '(no recipe)'
|
||||
item.statusCode = Craft.STATUS_ERROR
|
||||
item.crafted = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Milo:registerTask(craftTask)
|
||||
@@ -7,126 +7,126 @@ local Util = require('util')
|
||||
local colors = _G.colors
|
||||
|
||||
local craftPage = UI.Page {
|
||||
titleBar = UI.TitleBar { },
|
||||
wizard = UI.Wizard {
|
||||
y = 2, ey = -2,
|
||||
pages = {
|
||||
quantity = UI.WizardPage {
|
||||
index = 1,
|
||||
text = UI.Text {
|
||||
x = 6, y = 3,
|
||||
value = 'Quantity',
|
||||
},
|
||||
count = UI.TextEntry {
|
||||
x = 15, y = 3, width = 10,
|
||||
limit = 6,
|
||||
value = 1,
|
||||
},
|
||||
ejectText = UI.Text {
|
||||
x = 6, y = 4,
|
||||
value = 'Eject',
|
||||
},
|
||||
eject = UI.Chooser {
|
||||
x = 15, y = 4, width = 7,
|
||||
value = true,
|
||||
nochoice = 'No',
|
||||
choices = {
|
||||
{ name = 'Yes', value = true },
|
||||
{ name = 'No', value = false },
|
||||
},
|
||||
},
|
||||
},
|
||||
resources = UI.WizardPage {
|
||||
index = 2,
|
||||
grid = UI.ScrollingGrid {
|
||||
y = 2, ey = -2,
|
||||
columns = {
|
||||
{ heading = 'Name', key = 'displayName' },
|
||||
{ heading = 'Total', key = 'total' , width = 5 },
|
||||
{ heading = 'Used', key = 'used' , width = 5 },
|
||||
{ heading = 'Need', key = 'need' , width = 5 },
|
||||
},
|
||||
sortColumn = 'displayName',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
titleBar = UI.TitleBar { },
|
||||
wizard = UI.Wizard {
|
||||
y = 2, ey = -2,
|
||||
pages = {
|
||||
quantity = UI.WizardPage {
|
||||
index = 1,
|
||||
text = UI.Text {
|
||||
x = 6, y = 3,
|
||||
value = 'Quantity',
|
||||
},
|
||||
count = UI.TextEntry {
|
||||
x = 15, y = 3, width = 10,
|
||||
limit = 6,
|
||||
value = 1,
|
||||
},
|
||||
ejectText = UI.Text {
|
||||
x = 6, y = 4,
|
||||
value = 'Eject',
|
||||
},
|
||||
eject = UI.Chooser {
|
||||
x = 15, y = 4, width = 7,
|
||||
value = true,
|
||||
nochoice = 'No',
|
||||
choices = {
|
||||
{ name = 'Yes', value = true },
|
||||
{ name = 'No', value = false },
|
||||
},
|
||||
},
|
||||
},
|
||||
resources = UI.WizardPage {
|
||||
index = 2,
|
||||
grid = UI.ScrollingGrid {
|
||||
y = 2, ey = -2,
|
||||
columns = {
|
||||
{ heading = 'Name', key = 'displayName' },
|
||||
{ heading = 'Total', key = 'total' , width = 5 },
|
||||
{ heading = 'Used', key = 'used' , width = 5 },
|
||||
{ heading = 'Need', key = 'need' , width = 5 },
|
||||
},
|
||||
sortColumn = 'displayName',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
function craftPage:enable(item)
|
||||
self.item = item
|
||||
self:focusFirst()
|
||||
self.titleBar.title = itemDB:getName(item)
|
||||
self.item = item
|
||||
self:focusFirst()
|
||||
self.titleBar.title = itemDB:getName(item)
|
||||
-- self.wizard.pages.quantity.eject.value = true
|
||||
UI.Page.enable(self)
|
||||
UI.Page.enable(self)
|
||||
end
|
||||
|
||||
function craftPage.wizard.pages.resources.grid:getDisplayValues(row)
|
||||
local function dv(v)
|
||||
return v == 0 and '' or Util.toBytes(v)
|
||||
end
|
||||
row = Util.shallowCopy(row)
|
||||
row.total = Util.toBytes(row.total)
|
||||
row.used = dv(row.used)
|
||||
row.need = dv(row.need)
|
||||
return row
|
||||
local function dv(v)
|
||||
return v == 0 and '' or Util.toBytes(v)
|
||||
end
|
||||
row = Util.shallowCopy(row)
|
||||
row.total = Util.toBytes(row.total)
|
||||
row.used = dv(row.used)
|
||||
row.need = dv(row.need)
|
||||
return row
|
||||
end
|
||||
|
||||
function craftPage.wizard.pages.resources.grid:getRowTextColor(row, selected)
|
||||
if row.need > 0 then
|
||||
return colors.orange
|
||||
end
|
||||
return UI.Grid:getRowTextColor(row, selected)
|
||||
if row.need > 0 then
|
||||
return colors.orange
|
||||
end
|
||||
return UI.Grid:getRowTextColor(row, selected)
|
||||
end
|
||||
|
||||
function craftPage.wizard:eventHandler(event)
|
||||
if event.type == 'nextView' then
|
||||
local count = tonumber(self.pages.quantity.count.value)
|
||||
if not count or count <= 0 then
|
||||
self.pages.quantity.count.backgroundColor = colors.red
|
||||
self.pages.quantity.count:draw()
|
||||
return false
|
||||
end
|
||||
self.pages.quantity.count.backgroundColor = colors.black
|
||||
end
|
||||
return UI.Wizard.eventHandler(self, event)
|
||||
if event.type == 'nextView' then
|
||||
local count = tonumber(self.pages.quantity.count.value)
|
||||
if not count or count <= 0 then
|
||||
self.pages.quantity.count.backgroundColor = colors.red
|
||||
self.pages.quantity.count:draw()
|
||||
return false
|
||||
end
|
||||
self.pages.quantity.count.backgroundColor = colors.black
|
||||
end
|
||||
return UI.Wizard.eventHandler(self, event)
|
||||
end
|
||||
|
||||
function craftPage.wizard.pages.resources:enable()
|
||||
local items = Milo:listItems()
|
||||
local count = tonumber(self.parent.quantity.count.value)
|
||||
local recipe = Craft.findRecipe(craftPage.item)
|
||||
if recipe then
|
||||
local ingredients = Craft.getResourceList4(recipe, items, count)
|
||||
for _,v in pairs(ingredients) do
|
||||
v.displayName = itemDB:getName(v)
|
||||
end
|
||||
self.grid:setValues(ingredients)
|
||||
else
|
||||
self.grid:setValues({ })
|
||||
end
|
||||
return UI.WizardPage.enable(self)
|
||||
local items = Milo:listItems()
|
||||
local count = tonumber(self.parent.quantity.count.value)
|
||||
local recipe = Craft.findRecipe(craftPage.item)
|
||||
if recipe then
|
||||
local ingredients = Craft.getResourceList4(recipe, items, count)
|
||||
for _,v in pairs(ingredients) do
|
||||
v.displayName = itemDB:getName(v)
|
||||
end
|
||||
self.grid:setValues(ingredients)
|
||||
else
|
||||
self.grid:setValues({ })
|
||||
end
|
||||
return UI.WizardPage.enable(self)
|
||||
end
|
||||
|
||||
function craftPage:eventHandler(event)
|
||||
if event.type == 'cancel' then
|
||||
UI:setPreviousPage()
|
||||
if event.type == 'cancel' then
|
||||
UI:setPreviousPage()
|
||||
|
||||
elseif event.type == 'accept' then
|
||||
local item = Util.shallowCopy(self.item)
|
||||
item.requested = tonumber(self.wizard.pages.quantity.count.value)
|
||||
item.forceCrafting = true
|
||||
if self.wizard.pages.quantity.eject.value then
|
||||
item.callback = function(request)
|
||||
Milo:eject(item, request.requested)
|
||||
end
|
||||
end
|
||||
Milo:requestCrafting(item)
|
||||
UI:setPreviousPage()
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
elseif event.type == 'accept' then
|
||||
local item = Util.shallowCopy(self.item)
|
||||
item.requested = tonumber(self.wizard.pages.quantity.count.value)
|
||||
item.forceCrafting = true
|
||||
if self.wizard.pages.quantity.eject.value then
|
||||
item.callback = function(request)
|
||||
Milo:eject(item, request.requested)
|
||||
end
|
||||
end
|
||||
Milo:requestCrafting(item)
|
||||
UI:setPreviousPage()
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
UI:addPage('craft', craftPage)
|
||||
|
||||
@@ -12,27 +12,27 @@ Any items placed in this chest will be imported into storage.
|
||||
]]
|
||||
|
||||
local inputChestWizardPage = UI.WizardPage {
|
||||
title = 'Input Chest',
|
||||
index = 2,
|
||||
backgroundColor = colors.cyan,
|
||||
[1] = UI.TextArea {
|
||||
x = 2, ex = -2, y = 2, ey = -2,
|
||||
value = string.format(template, Ansi.yellow, Ansi.reset),
|
||||
},
|
||||
title = 'Input Chest',
|
||||
index = 2,
|
||||
backgroundColor = colors.cyan,
|
||||
[1] = UI.TextArea {
|
||||
x = 2, ex = -2, y = 2, ey = -2,
|
||||
value = string.format(template, Ansi.yellow, Ansi.reset),
|
||||
},
|
||||
}
|
||||
|
||||
function inputChestWizardPage:isValidType(node)
|
||||
local m = device[node.name]
|
||||
return m and m.pullItems and {
|
||||
name = 'Input Chest',
|
||||
value = 'input',
|
||||
category = 'custom',
|
||||
help = 'Sends all items to storage',
|
||||
}
|
||||
local m = device[node.name]
|
||||
return m and m.pullItems and {
|
||||
name = 'Input Chest',
|
||||
value = 'input',
|
||||
category = 'custom',
|
||||
help = 'Sends all items to storage',
|
||||
}
|
||||
end
|
||||
|
||||
function inputChestWizardPage:isValidFor(node)
|
||||
return node.mtype == 'input'
|
||||
return node.mtype == 'input'
|
||||
end
|
||||
|
||||
UI:getPage('nodeWizard').wizard:add({ inputChest = inputChestWizardPage })
|
||||
|
||||
@@ -5,57 +5,57 @@ local Util = require('util')
|
||||
local context = Milo:getContext()
|
||||
|
||||
local page = UI.Page {
|
||||
titleBar = UI.TitleBar {
|
||||
title = 'Item settings',
|
||||
previousPage = true,
|
||||
},
|
||||
statusBar = UI.StatusBar { },
|
||||
notification = UI.Notification { },
|
||||
titleBar = UI.TitleBar {
|
||||
title = 'Item settings',
|
||||
previousPage = true,
|
||||
},
|
||||
statusBar = UI.StatusBar { },
|
||||
notification = UI.Notification { },
|
||||
}
|
||||
|
||||
function page:enable(item)
|
||||
if not self.tabs then
|
||||
table.sort(context.plugins.itemTab, function(a, b) return a.index < b.index end)
|
||||
local t = Util.shallowCopy(context.plugins.itemTab)
|
||||
t.y = 2
|
||||
t.ey = -2
|
||||
if not self.tabs then
|
||||
table.sort(context.plugins.itemTab, function(a, b) return a.index < b.index end)
|
||||
local t = Util.shallowCopy(context.plugins.itemTab)
|
||||
t.y = 2
|
||||
t.ey = -2
|
||||
|
||||
self:add({ tabs = UI.Tabs(t) })
|
||||
end
|
||||
self:add({ tabs = UI.Tabs(t) })
|
||||
end
|
||||
|
||||
for _, v in pairs(context.plugins.itemTab) do
|
||||
if v.UIElement then
|
||||
v:setItem(item)
|
||||
end
|
||||
end
|
||||
self.tabs:selectTab(context.plugins.itemTab[1])
|
||||
UI.Page.enable(self)
|
||||
for _, v in pairs(context.plugins.itemTab) do
|
||||
if v.UIElement then
|
||||
v:setItem(item)
|
||||
end
|
||||
end
|
||||
self.tabs:selectTab(context.plugins.itemTab[1])
|
||||
UI.Page.enable(self)
|
||||
end
|
||||
|
||||
function page:eventHandler(event)
|
||||
if event.type == 'tab_activate' then
|
||||
event.activated:focusFirst()
|
||||
if event.type == 'tab_activate' then
|
||||
event.activated:focusFirst()
|
||||
|
||||
elseif event.type == 'form_invalid' then
|
||||
self.notification:error(event.message)
|
||||
elseif event.type == 'form_invalid' then
|
||||
self.notification:error(event.message)
|
||||
|
||||
elseif event.type == 'focus_change' then
|
||||
self.statusBar:setStatus(event.focused.help)
|
||||
self.statusBar:draw()
|
||||
elseif event.type == 'focus_change' then
|
||||
self.statusBar:setStatus(event.focused.help)
|
||||
self.statusBar:draw()
|
||||
|
||||
elseif event.type == 'success_message' then
|
||||
self.notification:success(event.message)
|
||||
elseif event.type == 'success_message' then
|
||||
self.notification:success(event.message)
|
||||
|
||||
elseif event.type == 'info_message' then
|
||||
self.notification:info(event.message)
|
||||
elseif event.type == 'info_message' then
|
||||
self.notification:info(event.message)
|
||||
|
||||
elseif event.type == 'error_message' then
|
||||
self.notification:error(event.message)
|
||||
elseif event.type == 'error_message' then
|
||||
self.notification:error(event.message)
|
||||
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
UI:addPage('item', page)
|
||||
|
||||
@@ -7,62 +7,62 @@ local colors = _G.colors
|
||||
local context = Milo:getContext()
|
||||
|
||||
local machinesTab = UI.Tab {
|
||||
tabTitle = 'Machine',
|
||||
index = 3,
|
||||
backgroundColor = colors.cyan,
|
||||
grid = UI.ScrollingGrid {
|
||||
x = 2, ex = -2, y = 2, ey = -2,
|
||||
disableHeader = true,
|
||||
columns = {
|
||||
{ heading = 'Name', key = 'displayName'},
|
||||
},
|
||||
sortColumn = 'displayName',
|
||||
help = 'Double-click to set machine',
|
||||
},
|
||||
tabTitle = 'Machine',
|
||||
index = 3,
|
||||
backgroundColor = colors.cyan,
|
||||
grid = UI.ScrollingGrid {
|
||||
x = 2, ex = -2, y = 2, ey = -2,
|
||||
disableHeader = true,
|
||||
columns = {
|
||||
{ heading = 'Name', key = 'displayName'},
|
||||
},
|
||||
sortColumn = 'displayName',
|
||||
help = 'Double-click to set machine',
|
||||
},
|
||||
}
|
||||
|
||||
function machinesTab:setItem(item)
|
||||
self.item = item
|
||||
local machine = Craft.machineLookup[self.item.key]
|
||||
local t = Util.filter(context.storage.nodes, function(node)
|
||||
if node.category == 'machine' or node.category == 'custom' then -- TODO: - need a setting instead (ie. canCraft)
|
||||
return node.adapter and node.adapter.online and node.adapter.pushItems
|
||||
end
|
||||
end)
|
||||
self.grid:setValues(t)
|
||||
if machine then
|
||||
self.grid:setSelected('name', machine)
|
||||
end
|
||||
self.parent:setActive(self, item.has_recipe)
|
||||
self.item = item
|
||||
local machine = Craft.machineLookup[self.item.key]
|
||||
local t = Util.filter(context.storage.nodes, function(node)
|
||||
if node.category == 'machine' or node.category == 'custom' then -- TODO: - need a setting instead (ie. canCraft)
|
||||
return node.adapter and node.adapter.online and node.adapter.pushItems
|
||||
end
|
||||
end)
|
||||
self.grid:setValues(t)
|
||||
if machine then
|
||||
self.grid:setSelected('name', machine)
|
||||
end
|
||||
self.parent:setActive(self, item.has_recipe)
|
||||
end
|
||||
|
||||
function machinesTab.grid:getDisplayValues(row)
|
||||
row = Util.shallowCopy(row)
|
||||
row.displayName = row.displayName or row.name
|
||||
return row
|
||||
row = Util.shallowCopy(row)
|
||||
row.displayName = row.displayName or row.name
|
||||
return row
|
||||
end
|
||||
|
||||
function machinesTab.grid:getRowTextColor(row, selected)
|
||||
if row.name == Craft.machineLookup[self.parent.item.key] then
|
||||
return colors.yellow
|
||||
end
|
||||
return UI.Grid:getRowTextColor(row, selected)
|
||||
if row.name == Craft.machineLookup[self.parent.item.key] then
|
||||
return colors.yellow
|
||||
end
|
||||
return UI.Grid:getRowTextColor(row, selected)
|
||||
end
|
||||
|
||||
function machinesTab:eventHandler(event)
|
||||
if event.type == 'grid_select' then
|
||||
if event.selected.name == Craft.machineLookup[self.item.key] then
|
||||
Craft.machineLookup[self.item.key] = nil
|
||||
else
|
||||
Craft.machineLookup[self.item.key] = event.selected.name
|
||||
end
|
||||
Util.writeTable(Craft.MACHINE_LOOKUP, Craft.machineLookup)
|
||||
if event.type == 'grid_select' then
|
||||
if event.selected.name == Craft.machineLookup[self.item.key] then
|
||||
Craft.machineLookup[self.item.key] = nil
|
||||
else
|
||||
Craft.machineLookup[self.item.key] = event.selected.name
|
||||
end
|
||||
Util.writeTable(Craft.MACHINE_LOOKUP, Craft.machineLookup)
|
||||
|
||||
self.grid:draw()
|
||||
self:emit({ type = 'info_message', message = 'Saved' })
|
||||
self.grid:draw()
|
||||
self:emit({ type = 'info_message', message = 'Saved' })
|
||||
|
||||
return true
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return { itemTab = machinesTab }
|
||||
|
||||
@@ -7,91 +7,91 @@ local Util = require('util')
|
||||
local context = Milo:getContext()
|
||||
|
||||
local manageTab = UI.Tab {
|
||||
tabTitle = 'Manage',
|
||||
index = 1,
|
||||
form = UI.Form {
|
||||
x = 1, ex = -1, ey = -1,
|
||||
--manualControls = true,
|
||||
[1] = UI.TextEntry {
|
||||
formLabel = 'Name', formKey = 'displayName', help = 'Override display name',
|
||||
shadowText = 'Display name',
|
||||
required = true,
|
||||
limit = 120,
|
||||
},
|
||||
[2] = UI.TextEntry {
|
||||
width = 7,
|
||||
formLabel = 'Min', formKey = 'low', help = 'Craft if below min',
|
||||
validate = 'numeric',
|
||||
},
|
||||
[3] = UI.TextEntry {
|
||||
width = 7,
|
||||
formLabel = 'Max', formKey = 'limit', help = 'Send to trash if above max',
|
||||
validate = 'numeric',
|
||||
},
|
||||
[4] = UI.Checkbox {
|
||||
formLabel = 'Ignore Dmg', formKey = 'ignoreDamage',
|
||||
help = 'Ignore damage of item',
|
||||
},
|
||||
[5] = UI.Checkbox {
|
||||
formLabel = 'Ignore NBT', formKey = 'ignoreNbtHash',
|
||||
help = 'Ignore NBT of item',
|
||||
},
|
||||
},
|
||||
tabTitle = 'Manage',
|
||||
index = 1,
|
||||
form = UI.Form {
|
||||
x = 1, ex = -1, ey = -1,
|
||||
--manualControls = true,
|
||||
[1] = UI.TextEntry {
|
||||
formLabel = 'Name', formKey = 'displayName', help = 'Override display name',
|
||||
shadowText = 'Display name',
|
||||
required = true,
|
||||
limit = 120,
|
||||
},
|
||||
[2] = UI.TextEntry {
|
||||
width = 7,
|
||||
formLabel = 'Min', formKey = 'low', help = 'Craft if below min',
|
||||
validate = 'numeric',
|
||||
},
|
||||
[3] = UI.TextEntry {
|
||||
width = 7,
|
||||
formLabel = 'Max', formKey = 'limit', help = 'Send to trash if above max',
|
||||
validate = 'numeric',
|
||||
},
|
||||
[4] = UI.Checkbox {
|
||||
formLabel = 'Ignore Dmg', formKey = 'ignoreDamage',
|
||||
help = 'Ignore damage of item',
|
||||
},
|
||||
[5] = UI.Checkbox {
|
||||
formLabel = 'Ignore NBT', formKey = 'ignoreNbtHash',
|
||||
help = 'Ignore NBT of item',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
function manageTab:setItem(item)
|
||||
self.item = item
|
||||
self.res = Util.shallowCopy(context.resources[item.key] or { })
|
||||
self.res.displayName = self.item.displayName
|
||||
self.form:setValues(self.res)
|
||||
self.item = item
|
||||
self.res = Util.shallowCopy(context.resources[item.key] or { })
|
||||
self.res.displayName = self.item.displayName
|
||||
self.form:setValues(self.res)
|
||||
|
||||
-- TODO: ignore damage should not be active if there is not a maxDamage value
|
||||
-- TODO: ignore damage should not be active if there is not a maxDamage value
|
||||
end
|
||||
|
||||
function manageTab:eventHandler(event)
|
||||
if event.type == 'form_cancel' then
|
||||
UI:setPreviousPage()
|
||||
if event.type == 'form_cancel' then
|
||||
UI:setPreviousPage()
|
||||
|
||||
elseif event.type == 'form_complete' then
|
||||
if self.form:save() then
|
||||
if self.res.displayName ~= self.item.displayName then
|
||||
self.item.displayName = self.res.displayName
|
||||
itemDB:add(self.item)
|
||||
itemDB:flush()
|
||||
if context.storage.cache[self.item.key] then
|
||||
context.storage.cache[self.item.key].displayName = self.res.displayName
|
||||
end
|
||||
--context.storage:setDirty()
|
||||
end
|
||||
elseif event.type == 'form_complete' then
|
||||
if self.form:save() then
|
||||
if self.res.displayName ~= self.item.displayName then
|
||||
self.item.displayName = self.res.displayName
|
||||
itemDB:add(self.item)
|
||||
itemDB:flush()
|
||||
if context.storage.cache[self.item.key] then
|
||||
context.storage.cache[self.item.key].displayName = self.res.displayName
|
||||
end
|
||||
--context.storage:setDirty()
|
||||
end
|
||||
|
||||
self.res.displayName = nil
|
||||
Map.prune(self.res, function(v)
|
||||
if type(v) == 'boolean' then
|
||||
return v
|
||||
elseif type(v) == 'string' then
|
||||
return #v > 0
|
||||
end
|
||||
return true
|
||||
end)
|
||||
self.res.displayName = nil
|
||||
Map.prune(self.res, function(v)
|
||||
if type(v) == 'boolean' then
|
||||
return v
|
||||
elseif type(v) == 'string' then
|
||||
return #v > 0
|
||||
end
|
||||
return true
|
||||
end)
|
||||
|
||||
local newKey = {
|
||||
name = self.item.name,
|
||||
damage = self.res.ignoreDamage and 0 or self.item.damage,
|
||||
nbtHash = not self.res.ignoreNbtHash and self.item.nbtHash or nil,
|
||||
}
|
||||
local newKey = {
|
||||
name = self.item.name,
|
||||
damage = self.res.ignoreDamage and 0 or self.item.damage,
|
||||
nbtHash = not self.res.ignoreNbtHash and self.item.nbtHash or nil,
|
||||
}
|
||||
|
||||
context.resources[self.item.key] = nil
|
||||
if not Util.empty(self.res) then
|
||||
context.resources[itemDB:makeKey(newKey)] = self.res
|
||||
end
|
||||
context.resources[self.item.key] = nil
|
||||
if not Util.empty(self.res) then
|
||||
context.resources[itemDB:makeKey(newKey)] = self.res
|
||||
end
|
||||
|
||||
Milo:saveResources()
|
||||
UI:setPreviousPage()
|
||||
end
|
||||
else
|
||||
return
|
||||
end
|
||||
return true
|
||||
Milo:saveResources()
|
||||
UI:setPreviousPage()
|
||||
end
|
||||
else
|
||||
return
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
return { itemTab = manageTab }
|
||||
|
||||
@@ -6,85 +6,89 @@ local UI = require('ui')
|
||||
local colors = _G.colors
|
||||
|
||||
local recipeTab = UI.Tab {
|
||||
tabTitle = 'Recipe',
|
||||
index = 2,
|
||||
backgroundColor = colors.cyan,
|
||||
grid = UI.ScrollingGrid {
|
||||
x = 2, ex = -2, y = 2, ey = -4,
|
||||
disableHeader = true,
|
||||
columns = {
|
||||
{ heading = 'Slot', key = 'slot', width = 2 },
|
||||
{ heading = 'Key', key = 'key' },
|
||||
},
|
||||
sortColumn = 'slot',
|
||||
},
|
||||
ignoreResultNBT = UI.Button {
|
||||
x = 2, y = -2,
|
||||
text = 'Ignore Result NBT', event = 'ignore_result_nbt',
|
||||
},
|
||||
ignoreNBT = UI.Button {
|
||||
x = -13, y = -2,
|
||||
text = 'Ignore NBT', event = 'ignore_nbt',
|
||||
},
|
||||
tabTitle = 'Recipe',
|
||||
index = 2,
|
||||
backgroundColor = colors.cyan,
|
||||
grid = UI.ScrollingGrid {
|
||||
x = 2, ex = -2, y = 2, ey = -4,
|
||||
disableHeader = true,
|
||||
columns = {
|
||||
{ heading = 'Slot', key = 'slot', width = 2 },
|
||||
{ heading = 'Count', key = 'count', width = 2 },
|
||||
{ heading = 'Key', key = 'key' },
|
||||
},
|
||||
sortColumn = 'slot',
|
||||
},
|
||||
ignoreResultNBT = UI.Button {
|
||||
x = 2, y = -2,
|
||||
text = 'Ignore Result NBT', event = 'ignore_result_nbt',
|
||||
},
|
||||
ignoreNBT = UI.Button {
|
||||
x = -13, y = -2,
|
||||
text = 'Ignore NBT', event = 'ignore_nbt',
|
||||
},
|
||||
}
|
||||
|
||||
function recipeTab:setItem(item)
|
||||
self.item = item
|
||||
self.recipe = Craft.findRecipe(self.item)
|
||||
self.item = item
|
||||
self.recipe = Craft.findRecipe(self.item)
|
||||
|
||||
self.parent:setActive(self, self.recipe)
|
||||
self.parent:setActive(self, self.recipe)
|
||||
|
||||
local t = { }
|
||||
if self.recipe then
|
||||
for k, v in pairs(self.recipe.ingredients) do
|
||||
table.insert(t, {
|
||||
slot = k,
|
||||
key = v,
|
||||
})
|
||||
end
|
||||
local key = itemDB:splitKey(self.recipe.result)
|
||||
self.ignoreResultNBT.inactive = not key.nbtHash
|
||||
end
|
||||
self.grid:setValues(t)
|
||||
local t = { }
|
||||
if self.recipe then
|
||||
for k, v in Craft.ingedients(self.recipe) do
|
||||
_syslog(k)
|
||||
_syslog(v)
|
||||
table.insert(t, {
|
||||
slot = k,
|
||||
key = v.key,
|
||||
count = v.count,
|
||||
})
|
||||
end
|
||||
local key = itemDB:splitKey(self.recipe.result)
|
||||
self.ignoreResultNBT.inactive = not key.nbtHash
|
||||
end
|
||||
self.grid:setValues(t)
|
||||
end
|
||||
|
||||
function recipeTab:eventHandler(event)
|
||||
if event.type == 'ignore_result_nbt' then
|
||||
-- remove old entry
|
||||
Milo:updateRecipe(self.recipe.result)
|
||||
if event.type == 'ignore_result_nbt' then
|
||||
-- remove old entry
|
||||
Milo:updateRecipe(self.recipe.result)
|
||||
|
||||
local item = itemDB:splitKey(self.recipe.result)
|
||||
item.nbtHash = nil
|
||||
self.recipe.result = itemDB:makeKey(item)
|
||||
local item = itemDB:splitKey(self.recipe.result)
|
||||
item.nbtHash = nil
|
||||
self.recipe.result = itemDB:makeKey(item)
|
||||
|
||||
-- add updated entry
|
||||
Milo:updateRecipe(self.recipe.result, self.recipe)
|
||||
-- add updated entry
|
||||
Milo:updateRecipe(self.recipe.result, self.recipe)
|
||||
|
||||
self.ignoreResultNBT.inactive = true
|
||||
self:emit({ type = 'info_message', message = 'Recipe updated' })
|
||||
self.ignoreResultNBT.inactive = true
|
||||
self:emit({ type = 'info_message', message = 'Recipe updated' })
|
||||
|
||||
elseif event.type == 'grid_focus_row' then
|
||||
local key = itemDB:splitKey(event.selected.key)
|
||||
self.ignoreNBT.inactive = not key.nbtHash
|
||||
self.ignoreNBT:draw()
|
||||
elseif event.type == 'grid_focus_row' then
|
||||
local key = itemDB:splitKey(event.selected.key)
|
||||
self.ignoreNBT.inactive = not key.nbtHash
|
||||
self.ignoreNBT:draw()
|
||||
|
||||
elseif event.type == 'ignore_nbt' then
|
||||
local selected = self.grid:getSelected()
|
||||
local item = itemDB:splitKey(selected.key)
|
||||
item.nbtHash = nil
|
||||
selected.key = itemDB:makeKey(item)
|
||||
self.grid:draw()
|
||||
elseif event.type == 'ignore_nbt' then
|
||||
local selected = self.grid:getSelected()
|
||||
local item = itemDB:splitKey(selected.key)
|
||||
item.nbtHash = nil
|
||||
selected.key = itemDB:makeKey(item)
|
||||
self.grid:draw()
|
||||
|
||||
self.recipe.ingredients = { }
|
||||
for _, v in pairs(self.grid.values) do
|
||||
self.recipe.ingredients[v.slot] = v.key
|
||||
end
|
||||
self.recipe.ingredients = { }
|
||||
for _, v in pairs(self.grid.values) do
|
||||
self.recipe.ingredients[v.slot] = v.key
|
||||
end
|
||||
|
||||
Milo:updateRecipe(self.recipe.result, self.recipe)
|
||||
self:emit({ type = 'info_message', message = 'Recipe updated' })
|
||||
Milo:updateRecipe(self.recipe.result, self.recipe)
|
||||
self:emit({ type = 'info_message', message = 'Recipe updated' })
|
||||
|
||||
return true
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return { itemTab = recipeTab }
|
||||
|
||||
@@ -7,49 +7,49 @@ local colors = _G.colors
|
||||
local context = Milo:getContext()
|
||||
|
||||
local resetTab = UI.Tab {
|
||||
tabTitle = 'Reset',
|
||||
index = 5,
|
||||
backgroundColor = colors.cyan,
|
||||
textArea = UI.TextArea {
|
||||
y = 2, ey = 6,
|
||||
textColor = colors.yellow,
|
||||
value = [[ Warning!
|
||||
tabTitle = 'Reset',
|
||||
index = 5,
|
||||
backgroundColor = colors.cyan,
|
||||
textArea = UI.TextArea {
|
||||
y = 2, ey = 6,
|
||||
textColor = colors.yellow,
|
||||
value = [[ Warning!
|
||||
|
||||
This will clear all setting,
|
||||
recipe, and machine for this item.]]
|
||||
},
|
||||
resetButton = UI.Button {
|
||||
x = 17, y = 7,
|
||||
event = 'reset',
|
||||
text = 'Reset',
|
||||
help = 'Clear recipe and all settings',
|
||||
},
|
||||
This will clear all setting,
|
||||
recipe, and machine for this item.]]
|
||||
},
|
||||
resetButton = UI.Button {
|
||||
x = 17, y = 7,
|
||||
event = 'reset',
|
||||
text = 'Reset',
|
||||
help = 'Clear recipe and all settings',
|
||||
},
|
||||
}
|
||||
|
||||
function resetTab:setItem(item)
|
||||
self.item = item
|
||||
self.item = item
|
||||
end
|
||||
|
||||
function resetTab:eventHandler(event)
|
||||
if event.type == 'reset' then
|
||||
if context.userRecipes[self.item.key] then
|
||||
Milo:updateRecipe(self.item.key, nil)
|
||||
end
|
||||
if event.type == 'reset' then
|
||||
if context.userRecipes[self.item.key] then
|
||||
Milo:updateRecipe(self.item.key, nil)
|
||||
end
|
||||
|
||||
if context.resources[self.item.key] then
|
||||
context.resources[self.item.key] = nil
|
||||
Milo:saveResources()
|
||||
end
|
||||
if context.resources[self.item.key] then
|
||||
context.resources[self.item.key] = nil
|
||||
Milo:saveResources()
|
||||
end
|
||||
|
||||
if Craft.machineLookup[self.item.key] then
|
||||
Craft.machineLookup[self.item.key] = nil
|
||||
Util.writeTable(Craft.MACHINE_LOOKUP, Craft.machineLookup)
|
||||
end
|
||||
if Craft.machineLookup[self.item.key] then
|
||||
Craft.machineLookup[self.item.key] = nil
|
||||
Util.writeTable(Craft.MACHINE_LOOKUP, Craft.machineLookup)
|
||||
end
|
||||
|
||||
UI:setPreviousPage()
|
||||
UI:setPreviousPage()
|
||||
|
||||
return true
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return { itemTab = resetTab }
|
||||
|
||||
@@ -13,224 +13,224 @@ local os = _G.os
|
||||
|
||||
--[[ Configuration Screen ]]
|
||||
local wizardPage = UI.WizardPage {
|
||||
title = 'Crafting Monitor',
|
||||
index = 2,
|
||||
backgroundColor = colors.cyan,
|
||||
[1] = UI.TextArea {
|
||||
x = 2, ex = -2, y = 2, ey = 3,
|
||||
marginRight = 0,
|
||||
textColor = colors.yellow,
|
||||
value = 'Displays the crafting progress.'
|
||||
},
|
||||
form = UI.Form {
|
||||
x = 2, ex = -2, y = 4, ey = -2,
|
||||
manualControls = true,
|
||||
[1] = UI.Chooser {
|
||||
width = 9,
|
||||
formLabel = 'Font Size', formKey = 'textScale',
|
||||
nochoice = 'Small',
|
||||
choices = {
|
||||
{ name = 'Small', value = .5 },
|
||||
{ name = 'Large', value = 1 },
|
||||
},
|
||||
help = 'Adjust text scaling',
|
||||
},
|
||||
},
|
||||
title = 'Crafting Monitor',
|
||||
index = 2,
|
||||
backgroundColor = colors.cyan,
|
||||
[1] = UI.TextArea {
|
||||
x = 2, ex = -2, y = 2, ey = 3,
|
||||
marginRight = 0,
|
||||
textColor = colors.yellow,
|
||||
value = 'Displays the crafting progress.'
|
||||
},
|
||||
form = UI.Form {
|
||||
x = 2, ex = -2, y = 4, ey = -2,
|
||||
manualControls = true,
|
||||
[1] = UI.Chooser {
|
||||
width = 9,
|
||||
formLabel = 'Font Size', formKey = 'textScale',
|
||||
nochoice = 'Small',
|
||||
choices = {
|
||||
{ name = 'Small', value = .5 },
|
||||
{ name = 'Large', value = 1 },
|
||||
},
|
||||
help = 'Adjust text scaling',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
function wizardPage:setNode(node)
|
||||
self.form:setValues(node)
|
||||
self.form:setValues(node)
|
||||
end
|
||||
|
||||
function wizardPage:saveNode(node)
|
||||
os.queueEvent('monitor_resize', node.name)
|
||||
os.queueEvent('monitor_resize', node.name)
|
||||
end
|
||||
|
||||
function wizardPage:validate()
|
||||
return self.form:save()
|
||||
return self.form:save()
|
||||
end
|
||||
|
||||
function wizardPage:isValidType(node)
|
||||
local m = device[node.name]
|
||||
return m and m.type == 'monitor' and {
|
||||
name = 'Crafting Monitor',
|
||||
value = 'jobs',
|
||||
category = 'display',
|
||||
help = 'Display crafting progress / jobs'
|
||||
}
|
||||
local m = device[node.name]
|
||||
return m and m.type == 'monitor' and {
|
||||
name = 'Crafting Monitor',
|
||||
value = 'jobs',
|
||||
category = 'display',
|
||||
help = 'Display crafting progress / jobs'
|
||||
}
|
||||
end
|
||||
|
||||
function wizardPage:isValidFor(node)
|
||||
return node.mtype == 'jobs'
|
||||
return node.mtype == 'jobs'
|
||||
end
|
||||
|
||||
UI:getPage('nodeWizard').wizard:add({ jobs = wizardPage })
|
||||
|
||||
--[[ Display ]]
|
||||
local function createPage(node)
|
||||
local monitor = UI.Device {
|
||||
device = node.adapter,
|
||||
textScale = node.textScale or .5,
|
||||
}
|
||||
local monitor = UI.Device {
|
||||
device = node.adapter,
|
||||
textScale = node.textScale or .5,
|
||||
}
|
||||
|
||||
function monitor:resize()
|
||||
self.textScale = node.textScale or .5
|
||||
UI.Device.resize(self)
|
||||
end
|
||||
function monitor:resize()
|
||||
self.textScale = node.textScale or .5
|
||||
UI.Device.resize(self)
|
||||
end
|
||||
|
||||
local page = UI.Page {
|
||||
parent = monitor,
|
||||
grid = UI.Grid {
|
||||
--ey = -6,
|
||||
sortColumn = 'index',
|
||||
columns = {
|
||||
{ heading = 'Qty', key = 'remaining', width = 4 },
|
||||
{ heading = 'Crafting', key = 'displayName', },
|
||||
{ heading = 'Status', key = 'status', },
|
||||
{ heading = 'need', key = 'need', width = 4 },
|
||||
-- { heading = 'total', key = 'total', width = 4 },
|
||||
-- { heading = 'used', key = 'used', width = 4 },
|
||||
-- { heading = 'count', key = 'count', width = 4 },
|
||||
{ heading = 'crafted', key = 'crafted', width = 5 },
|
||||
-- { heading = 'Progress', key = 'progress', width = 8 },
|
||||
},
|
||||
headerBackgroundColor = colors.black,
|
||||
headerTextColor = colors.cyan,
|
||||
headerHeight = 2,
|
||||
},
|
||||
local page = UI.Page {
|
||||
parent = monitor,
|
||||
grid = UI.Grid {
|
||||
--ey = -6,
|
||||
sortColumn = 'index',
|
||||
columns = {
|
||||
{ heading = 'Qty', key = 'remaining', width = 4 },
|
||||
{ heading = 'Crafting', key = 'displayName', },
|
||||
{ heading = 'Status', key = 'status', },
|
||||
{ heading = 'need', key = 'need', width = 4 },
|
||||
-- { heading = 'total', key = 'total', width = 4 },
|
||||
-- { heading = 'used', key = 'used', width = 4 },
|
||||
-- { heading = 'count', key = 'count', width = 4 },
|
||||
{ heading = 'crafted', key = 'crafted', width = 5 },
|
||||
-- { heading = 'Progress', key = 'progress', width = 8 },
|
||||
},
|
||||
headerBackgroundColor = colors.black,
|
||||
headerTextColor = colors.cyan,
|
||||
headerHeight = 2,
|
||||
},
|
||||
--[[
|
||||
buttons = UI.Window {
|
||||
y = -5, height = 5,
|
||||
backgroundColor = colors.gray,
|
||||
prevButton = UI.Button {
|
||||
x = 2, y = 2, height = 3, width = 5,
|
||||
event = 'previous',
|
||||
backgroundColor = colors.lightGray,
|
||||
text = ' < '
|
||||
},
|
||||
cancelButton = UI.Button {
|
||||
x = 8, y = 2, height = 3, ex = -8,
|
||||
event = 'cancel_job',
|
||||
backgroundColor = colors.lightGray,
|
||||
text = 'Cancel Job'
|
||||
},
|
||||
nextButton = UI.Button {
|
||||
x = -6, y = 2, height = 3, width = 5,
|
||||
event = 'next',
|
||||
backgroundColor = colors.lightGray,
|
||||
text = ' > '
|
||||
},
|
||||
},
|
||||
buttons = UI.Window {
|
||||
y = -5, height = 5,
|
||||
backgroundColor = colors.gray,
|
||||
prevButton = UI.Button {
|
||||
x = 2, y = 2, height = 3, width = 5,
|
||||
event = 'previous',
|
||||
backgroundColor = colors.lightGray,
|
||||
text = ' < '
|
||||
},
|
||||
cancelButton = UI.Button {
|
||||
x = 8, y = 2, height = 3, ex = -8,
|
||||
event = 'cancel_job',
|
||||
backgroundColor = colors.lightGray,
|
||||
text = 'Cancel Job'
|
||||
},
|
||||
nextButton = UI.Button {
|
||||
x = -6, y = 2, height = 3, width = 5,
|
||||
event = 'next',
|
||||
backgroundColor = colors.lightGray,
|
||||
text = ' > '
|
||||
},
|
||||
},
|
||||
]]
|
||||
}
|
||||
}
|
||||
|
||||
function page:updateList(craftList)
|
||||
if not Milo:isCraftingPaused() then
|
||||
local t = { }
|
||||
for _,v in pairs(craftList) do
|
||||
table.insert(t, v)
|
||||
v.index = #t
|
||||
for k2,v2 in pairs(v.ingredients or { }) do
|
||||
if v2.key ~= v.key --[[and v2.statusCode ]] then
|
||||
table.insert(t, v2)
|
||||
if not v2.displayName then
|
||||
v2.displayName = itemDB:getName(k2)
|
||||
end
|
||||
v2.index = #t
|
||||
end
|
||||
end
|
||||
end
|
||||
self.grid:setValues(t)
|
||||
self.grid:update()
|
||||
self:draw()
|
||||
self:sync()
|
||||
end
|
||||
end
|
||||
function page:updateList(craftList)
|
||||
if not Milo:isCraftingPaused() then
|
||||
local t = { }
|
||||
for _,v in pairs(craftList) do
|
||||
table.insert(t, v)
|
||||
v.index = #t
|
||||
for k2,v2 in pairs(v.ingredients or { }) do
|
||||
if v2.key ~= v.key --[[and v2.statusCode ]] then
|
||||
table.insert(t, v2)
|
||||
if not v2.displayName then
|
||||
v2.displayName = itemDB:getName(k2)
|
||||
end
|
||||
v2.index = #t
|
||||
end
|
||||
end
|
||||
end
|
||||
self.grid:setValues(t)
|
||||
self.grid:update()
|
||||
self:draw()
|
||||
self:sync()
|
||||
end
|
||||
end
|
||||
|
||||
function page.grid:getDisplayValues(row)
|
||||
row = Util.shallowCopy(row)
|
||||
if not row.displayName then
|
||||
row.displayName = itemDB:getName(row)
|
||||
end
|
||||
if row.requested then
|
||||
row.remaining = math.max(0, row.requested - row.crafted)
|
||||
else
|
||||
row.displayName = ' ' .. row.displayName
|
||||
end
|
||||
--row.progress = string.format('%d/%d', row.crafted, row.count)
|
||||
return row
|
||||
end
|
||||
function page.grid:getDisplayValues(row)
|
||||
row = Util.shallowCopy(row)
|
||||
if not row.displayName then
|
||||
row.displayName = itemDB:getName(row)
|
||||
end
|
||||
if row.requested then
|
||||
row.remaining = math.max(0, row.requested - row.crafted)
|
||||
else
|
||||
row.displayName = ' ' .. row.displayName
|
||||
end
|
||||
--row.progress = string.format('%d/%d', row.crafted, row.count)
|
||||
return row
|
||||
end
|
||||
|
||||
function page.grid:getRowTextColor(row, selected)
|
||||
local statusColor = {
|
||||
[ Craft.STATUS_ERROR ] = colors.red,
|
||||
[ Craft.STATUS_WARNING ] = colors.orange,
|
||||
[ Craft.STATUS_INFO ] = colors.yellow,
|
||||
[ Craft.STATUS_SUCCESS ] = colors.green,
|
||||
}
|
||||
return row.statusCode and statusColor[row.statusCode] or
|
||||
UI.Grid:getRowTextColor(row, selected)
|
||||
end
|
||||
function page.grid:getRowTextColor(row, selected)
|
||||
local statusColor = {
|
||||
[ Craft.STATUS_ERROR ] = colors.red,
|
||||
[ Craft.STATUS_WARNING ] = colors.orange,
|
||||
[ Craft.STATUS_INFO ] = colors.yellow,
|
||||
[ Craft.STATUS_SUCCESS ] = colors.green,
|
||||
}
|
||||
return row.statusCode and statusColor[row.statusCode] or
|
||||
UI.Grid:getRowTextColor(row, selected)
|
||||
end
|
||||
|
||||
-- no sorting allowed
|
||||
function page:setInverseSort() end
|
||||
function page:setSortColumn() end
|
||||
-- no sorting allowed
|
||||
function page:setInverseSort() end
|
||||
function page:setSortColumn() end
|
||||
|
||||
function page:eventHandler(event)
|
||||
if event.type == 'cancel_job' then
|
||||
Sound.play('entity.villager.no', .5)
|
||||
function page:eventHandler(event)
|
||||
if event.type == 'cancel_job' then
|
||||
Sound.play('entity.villager.no', .5)
|
||||
|
||||
elseif event.type == 'next' then
|
||||
self.grid:nextPage()
|
||||
elseif event.type == 'next' then
|
||||
self.grid:nextPage()
|
||||
|
||||
elseif event.type == 'previous' then
|
||||
self.grid:previousPage()
|
||||
elseif event.type == 'previous' then
|
||||
self.grid:previousPage()
|
||||
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
|
||||
Event.onTimeout(.1, function()
|
||||
self:setFocus(self.grid)
|
||||
self:sync()
|
||||
end)
|
||||
return true
|
||||
end
|
||||
Event.onTimeout(.1, function()
|
||||
self:setFocus(self.grid)
|
||||
self:sync()
|
||||
end)
|
||||
return true
|
||||
end
|
||||
|
||||
UI:setPage(page)
|
||||
return page
|
||||
UI:setPage(page)
|
||||
return page
|
||||
end
|
||||
|
||||
local pages = { }
|
||||
|
||||
Event.on({ 'milo_resume', 'milo_pause' }, function(_, reason)
|
||||
for node in context.storage:filterActive('jobs') do
|
||||
local page = pages[node.name]
|
||||
if page then
|
||||
if reason then
|
||||
page.grid:clear()
|
||||
page.grid:centeredWrite(math.ceil(page.grid.height / 2), reason.msg)
|
||||
else
|
||||
page.grid:draw()
|
||||
end
|
||||
page:sync()
|
||||
end
|
||||
end
|
||||
for node in context.storage:filterActive('jobs') do
|
||||
local page = pages[node.name]
|
||||
if page then
|
||||
if reason then
|
||||
page.grid:clear()
|
||||
page.grid:centeredWrite(math.ceil(page.grid.height / 2), reason.msg)
|
||||
else
|
||||
page.grid:draw()
|
||||
end
|
||||
page:sync()
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
--[[ Task ]]
|
||||
local task = {
|
||||
name = 'job status',
|
||||
priority = 80,
|
||||
name = 'job status',
|
||||
priority = 80,
|
||||
}
|
||||
|
||||
function task:cycle()
|
||||
for node in context.storage:filterActive('jobs') do
|
||||
if not pages[node.name] then
|
||||
pages[node.name] = createPage(node)
|
||||
end
|
||||
pages[node.name]:updateList(context.craftingQueue)
|
||||
end
|
||||
for node in context.storage:filterActive('jobs') do
|
||||
if not pages[node.name] then
|
||||
pages[node.name] = createPage(node)
|
||||
end
|
||||
pages[node.name]:updateList(context.craftingQueue)
|
||||
end
|
||||
end
|
||||
|
||||
Milo:registerTask(task)
|
||||
|
||||
@@ -111,7 +111,14 @@ function pages.confirmation:validate()
|
||||
}
|
||||
|
||||
for k,v in pairs(inventory) do
|
||||
recipe.ingredients[k] = itemDB:makeKey(v)
|
||||
if v.count == 1 then
|
||||
recipe.ingredients[k] = itemDB:makeKey(v)
|
||||
else
|
||||
recipe.ingredients[k] = {
|
||||
key = itemDB:makeKey(v),
|
||||
count = v.count,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
Milo:saveMachineRecipe(recipe, result, machine.name)
|
||||
|
||||
@@ -8,100 +8,100 @@ local context = Milo:getContext()
|
||||
local device = _G.device
|
||||
|
||||
local page = UI.Page {
|
||||
titleBar = UI.TitleBar { title = 'Reassign Machine' },
|
||||
grid = UI.ScrollingGrid {
|
||||
y = 2, ey = -4,
|
||||
values = context.storage.nodes,
|
||||
columns = {
|
||||
{ key = 'suffix', width = 4, align = 'right' },
|
||||
{ heading = 'Name', key = 'displayName' },
|
||||
{ heading = 'Type', key = 'mtype', width = 4 },
|
||||
{ heading = 'Pri', key = 'priority', width = 3 },
|
||||
},
|
||||
sortColumn = 'displayName',
|
||||
help = 'Select Node',
|
||||
},
|
||||
accept = UI.Button {
|
||||
x = -9, y = -2,
|
||||
event = 'grid_select',
|
||||
text = 'Accept',
|
||||
},
|
||||
cancel = UI.Button {
|
||||
x = -18, y = -2,
|
||||
event = 'cancel',
|
||||
text = 'Cancel',
|
||||
},
|
||||
accelerators = {
|
||||
grid_select = 'nextView',
|
||||
},
|
||||
notification = UI.Notification { },
|
||||
titleBar = UI.TitleBar { title = 'Reassign Machine' },
|
||||
grid = UI.ScrollingGrid {
|
||||
y = 2, ey = -4,
|
||||
values = context.storage.nodes,
|
||||
columns = {
|
||||
{ key = 'suffix', width = 4, align = 'right' },
|
||||
{ heading = 'Name', key = 'displayName' },
|
||||
{ heading = 'Type', key = 'mtype', width = 4 },
|
||||
{ heading = 'Pri', key = 'priority', width = 3 },
|
||||
},
|
||||
sortColumn = 'displayName',
|
||||
help = 'Select Node',
|
||||
},
|
||||
accept = UI.Button {
|
||||
x = -9, y = -2,
|
||||
event = 'grid_select',
|
||||
text = 'Accept',
|
||||
},
|
||||
cancel = UI.Button {
|
||||
x = -18, y = -2,
|
||||
event = 'cancel',
|
||||
text = 'Cancel',
|
||||
},
|
||||
accelerators = {
|
||||
grid_select = 'nextView',
|
||||
},
|
||||
notification = UI.Notification { },
|
||||
}
|
||||
|
||||
function page.grid:getDisplayValues(row)
|
||||
row = Util.shallowCopy(row)
|
||||
local t = { row.name:match(':(.+)_(%d+)$') }
|
||||
if #t ~= 2 then
|
||||
t = { row.name:match('(.+)_(%d+)$') }
|
||||
end
|
||||
if t and #t == 2 then
|
||||
row.name, row.suffix = table.unpack(t)
|
||||
row.name = row.name .. '_' .. row.suffix
|
||||
end
|
||||
row.displayName = row.displayName or row.name
|
||||
return row
|
||||
row = Util.shallowCopy(row)
|
||||
local t = { row.name:match(':(.+)_(%d+)$') }
|
||||
if #t ~= 2 then
|
||||
t = { row.name:match('(.+)_(%d+)$') }
|
||||
end
|
||||
if t and #t == 2 then
|
||||
row.name, row.suffix = table.unpack(t)
|
||||
row.name = row.name .. '_' .. row.suffix
|
||||
end
|
||||
row.displayName = row.displayName or row.name
|
||||
return row
|
||||
end
|
||||
|
||||
function page.grid:getRowTextColor(row, selected)
|
||||
if row.mtype == 'ignore' then
|
||||
return colors.lightGray
|
||||
end
|
||||
return UI.Grid:getRowTextColor(row, selected)
|
||||
if row.mtype == 'ignore' then
|
||||
return colors.lightGray
|
||||
end
|
||||
return UI.Grid:getRowTextColor(row, selected)
|
||||
end
|
||||
|
||||
function page:applyFilter()
|
||||
local t = Util.filter(context.storage.nodes, function(v)
|
||||
return v.mtype == 'ignore' and device[v.name]
|
||||
end)
|
||||
local t = Util.filter(context.storage.nodes, function(v)
|
||||
return v.mtype == 'ignore' and device[v.name]
|
||||
end)
|
||||
|
||||
self.grid:setValues(t)
|
||||
self.grid:setValues(t)
|
||||
end
|
||||
|
||||
function page:enable(machine)
|
||||
self.machine = machine
|
||||
self:applyFilter()
|
||||
self.machine = machine
|
||||
self:applyFilter()
|
||||
|
||||
UI.Page.enable(self)
|
||||
UI.Page.enable(self)
|
||||
end
|
||||
|
||||
function page:eventHandler(event)
|
||||
if event.type == 'grid_select' then
|
||||
local target = self.grid:getSelected()
|
||||
if target then
|
||||
local adapter = target.adapter
|
||||
local name = target.name
|
||||
Util.merge(target, self.machine)
|
||||
target.adapter = adapter
|
||||
target.name = name
|
||||
if event.type == 'grid_select' then
|
||||
local target = self.grid:getSelected()
|
||||
if target then
|
||||
local adapter = target.adapter
|
||||
local name = target.name
|
||||
Util.merge(target, self.machine)
|
||||
target.adapter = adapter
|
||||
target.name = name
|
||||
|
||||
context.storage.nodes[self.machine.name] = nil
|
||||
context.storage:saveConfiguration()
|
||||
context.storage.nodes[self.machine.name] = nil
|
||||
context.storage:saveConfiguration()
|
||||
|
||||
for k,v in pairs(Craft.machineLookup) do
|
||||
if v == self.machine.name then
|
||||
Craft.machineLookup[k] = name
|
||||
end
|
||||
Util.writeTable(Craft.MACHINE_LOOKUP, Craft.machineLookup)
|
||||
end
|
||||
for k,v in pairs(Craft.machineLookup) do
|
||||
if v == self.machine.name then
|
||||
Craft.machineLookup[k] = name
|
||||
end
|
||||
Util.writeTable(Craft.MACHINE_LOOKUP, Craft.machineLookup)
|
||||
end
|
||||
|
||||
UI:setPreviousPage()
|
||||
end
|
||||
UI:setPreviousPage()
|
||||
end
|
||||
|
||||
elseif event.type == 'cancel' then
|
||||
UI:setPreviousPage()
|
||||
elseif event.type == 'cancel' then
|
||||
UI:setPreviousPage()
|
||||
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
end
|
||||
|
||||
UI:addPage('machineMover', page)
|
||||
|
||||
@@ -14,29 +14,29 @@ Add all speed upgrades possible.
|
||||
]]
|
||||
|
||||
local wizardPage = UI.WizardPage {
|
||||
title = 'Mass Storage',
|
||||
index = 2,
|
||||
backgroundColor = colors.cyan,
|
||||
[1] = UI.TextArea {
|
||||
x = 2, ex = -2, y = 2, ey = -2,
|
||||
value = string.format(template, Ansi.red, Ansi.reset),
|
||||
},
|
||||
title = 'Mass Storage',
|
||||
index = 2,
|
||||
backgroundColor = colors.cyan,
|
||||
[1] = UI.TextArea {
|
||||
x = 2, ex = -2, y = 2, ey = -2,
|
||||
value = string.format(template, Ansi.red, Ansi.reset),
|
||||
},
|
||||
}
|
||||
|
||||
function wizardPage:isValidFor(node)
|
||||
if node.mtype == 'storage' then
|
||||
local m = device[node.name]
|
||||
return m and m.listAvailableItems
|
||||
end
|
||||
if node.mtype == 'storage' then
|
||||
local m = device[node.name]
|
||||
return m and m.listAvailableItems
|
||||
end
|
||||
end
|
||||
|
||||
function wizardPage:setNode(node)
|
||||
self.node = node
|
||||
self.node = node
|
||||
end
|
||||
|
||||
function wizardPage:validate()
|
||||
self.node.adapterType = 'massAdapter'
|
||||
return true
|
||||
self.node.adapterType = 'massAdapter'
|
||||
return true
|
||||
end
|
||||
|
||||
-- disable until a way is found to transfer between 2 non-transferrable nodes
|
||||
|
||||
@@ -4,18 +4,18 @@ local args = { ... }
|
||||
local context = args[1]
|
||||
|
||||
local function learn()
|
||||
context:sendRequest({
|
||||
request = 'craft',
|
||||
slot = 15,
|
||||
})
|
||||
context:sendRequest({
|
||||
request = 'craft',
|
||||
slot = 15,
|
||||
})
|
||||
end
|
||||
|
||||
context.responseHandlers['craft'] = function(response)
|
||||
if response.success then
|
||||
Sound.play('entity.item.pickup')
|
||||
else
|
||||
Sound.play('entity.villager.no')
|
||||
end
|
||||
if response.success then
|
||||
Sound.play('entity.item.pickup')
|
||||
else
|
||||
Sound.play('entity.villager.no')
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
|
||||
@@ -9,36 +9,36 @@ local context = args[1]
|
||||
local SHIELD_SLOT = 2
|
||||
|
||||
Event.addRoutine(function()
|
||||
local lastTransfer
|
||||
while true do
|
||||
local sleepTime = 1.5
|
||||
if lastTransfer and os.clock() - lastTransfer < 2 then
|
||||
sleepTime = .1
|
||||
end
|
||||
local lastTransfer
|
||||
while true do
|
||||
local sleepTime = 1.5
|
||||
if lastTransfer and os.clock() - lastTransfer < 2 then
|
||||
sleepTime = .1
|
||||
end
|
||||
|
||||
os.sleep(context.socket and sleepTime or 5)
|
||||
if context.state.deposit and context.state.server and (context.state.useShield or context.state.slot) then
|
||||
local neural = device.neuralInterface
|
||||
local inv = context.state.useShield and 'getEquipment' or 'getInventory'
|
||||
if neural and neural[inv] then
|
||||
local s, m = pcall(function()
|
||||
local method = neural[inv]
|
||||
local item = method and method().list()[context.state.useShield and SHIELD_SLOT or context.state.slot]
|
||||
if item then
|
||||
if context:sendRequest({
|
||||
request = 'deposit',
|
||||
source = context.state.useShield and 'equipment' or 'inventory',
|
||||
slot = context.state.useShield and SHIELD_SLOT or context.state.slot,
|
||||
count = item.count,
|
||||
}) then
|
||||
lastTransfer = os.clock()
|
||||
end
|
||||
end
|
||||
end)
|
||||
if not s and m then
|
||||
_G._syslog(m)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
os.sleep(context.socket and sleepTime or 5)
|
||||
if context.state.deposit and context.state.server and (context.state.useShield or context.state.slot) then
|
||||
local neural = device.neuralInterface
|
||||
local inv = context.state.useShield and 'getEquipment' or 'getInventory'
|
||||
if neural and neural[inv] then
|
||||
local s, m = pcall(function()
|
||||
local method = neural[inv]
|
||||
local item = method and method().list()[context.state.useShield and SHIELD_SLOT or context.state.slot]
|
||||
if item then
|
||||
if context:sendRequest({
|
||||
request = 'deposit',
|
||||
source = context.state.useShield and 'equipment' or 'inventory',
|
||||
slot = context.state.useShield and SHIELD_SLOT or context.state.slot,
|
||||
count = item.count,
|
||||
}) then
|
||||
lastTransfer = os.clock()
|
||||
end
|
||||
end
|
||||
end)
|
||||
if not s and m then
|
||||
_G._syslog(m)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
@@ -10,70 +10,70 @@ local context = args[1]
|
||||
local ni = peripheral.find('neuralInterface')
|
||||
|
||||
if not context.state.depositAll then
|
||||
context.state.depositAll = { }
|
||||
context.state.depositAll = { }
|
||||
end
|
||||
if not context.state.depositAll.retain then
|
||||
context.state.depositAll.retain = { }
|
||||
context.state.depositAll.retain = { }
|
||||
end
|
||||
|
||||
local page = UI.Page {
|
||||
titleBar = UI.TitleBar {
|
||||
backgroundColor = colors.gray,
|
||||
title = 'Deposit full inventory',
|
||||
previousPage = true,
|
||||
},
|
||||
items = UI.ScrollingGrid {
|
||||
x = 2, ex = -2, y = 2, ey = -4,
|
||||
columns = {
|
||||
{ heading = 'Qty', key = 'count', width = 3 },
|
||||
{ heading = 'Name', key = 'displayName', },
|
||||
},
|
||||
sortColumn = 'count',
|
||||
inverseSort = true
|
||||
},
|
||||
form = UI.Form {
|
||||
x = 2, ex = -2, y = -2, ey = -2,
|
||||
margin = 1,
|
||||
[1] = UI.Checkbox {
|
||||
formLabel = 'Include hotbar', formKey = 'includeHotbar',
|
||||
help = 'Also send the contents of the hotbar to Milo (excluding the neural connector)'
|
||||
}
|
||||
},
|
||||
notification = UI.Notification(),
|
||||
titleBar = UI.TitleBar {
|
||||
backgroundColor = colors.gray,
|
||||
title = 'Deposit full inventory',
|
||||
previousPage = true,
|
||||
},
|
||||
items = UI.ScrollingGrid {
|
||||
x = 2, ex = -2, y = 2, ey = -4,
|
||||
columns = {
|
||||
{ heading = 'Qty', key = 'count', width = 3 },
|
||||
{ heading = 'Name', key = 'displayName', },
|
||||
},
|
||||
sortColumn = 'count',
|
||||
inverseSort = true
|
||||
},
|
||||
form = UI.Form {
|
||||
x = 2, ex = -2, y = -2, ey = -2,
|
||||
margin = 1,
|
||||
[1] = UI.Checkbox {
|
||||
formLabel = 'Include hotbar', formKey = 'includeHotbar',
|
||||
help = 'Also send the contents of the hotbar to Milo (excluding the neural connector)'
|
||||
}
|
||||
},
|
||||
notification = UI.Notification(),
|
||||
}
|
||||
|
||||
local function makeKey(item) -- group items regardless of damage
|
||||
local damage = item.maxDamage == 0 and item.damage
|
||||
return itemDB:makeKey({ name = item.name, damage = damage })
|
||||
local damage = item.maxDamage == 0 and item.damage
|
||||
return itemDB:makeKey({ name = item.name, damage = damage })
|
||||
end
|
||||
|
||||
function page:updateInventoryList()
|
||||
local inv = ni.getInventory().list()
|
||||
local list = { }
|
||||
local inv = ni.getInventory().list()
|
||||
local list = { }
|
||||
|
||||
for slot, item in pairs(inv) do
|
||||
if (context.state.depositAll.includeHotbar or slot > 9) and item.name ~= 'plethora:neuralconnector' then
|
||||
item = itemDB:get(item, function() return ni.getInventory().getItemMeta(slot) end)
|
||||
local key = makeKey(item)
|
||||
if not list[key] then
|
||||
item.displayName = item.displayName:match('(.+) %(damage:.+%)') or item.displayName
|
||||
list[key] = item
|
||||
else
|
||||
list[key].count = list[key].count + item.count
|
||||
end
|
||||
list[key].key = key
|
||||
end
|
||||
end
|
||||
for slot, item in pairs(inv) do
|
||||
if (context.state.depositAll.includeHotbar or slot > 9) and item.name ~= 'plethora:neuralconnector' then
|
||||
item = itemDB:get(item, function() return ni.getInventory().getItemMeta(slot) end)
|
||||
local key = makeKey(item)
|
||||
if not list[key] then
|
||||
item.displayName = item.displayName:match('(.+) %(damage:.+%)') or item.displayName
|
||||
list[key] = item
|
||||
else
|
||||
list[key].count = list[key].count + item.count
|
||||
end
|
||||
list[key].key = key
|
||||
end
|
||||
end
|
||||
|
||||
self.items:setValues(list)
|
||||
self.items:draw()
|
||||
itemDB:flush()
|
||||
self.items:setValues(list)
|
||||
self.items:draw()
|
||||
itemDB:flush()
|
||||
end
|
||||
|
||||
function page:enable()
|
||||
self.form:setValues(context.state.depositAll)
|
||||
self:updateInventoryList()
|
||||
UI.Page.enable(self)
|
||||
self.form:setValues(context.state.depositAll)
|
||||
self:updateInventoryList()
|
||||
UI.Page.enable(self)
|
||||
end
|
||||
|
||||
function page.items:getRowTextColor(row)
|
||||
@@ -84,57 +84,57 @@ function page.items:getRowTextColor(row)
|
||||
end
|
||||
|
||||
function page:depositAll()
|
||||
self.notification:info('Depositing all items...')
|
||||
self.notification:info('Depositing all items...')
|
||||
|
||||
local inv = ni.getInventory().list()
|
||||
local inv = ni.getInventory().list()
|
||||
|
||||
for slot, item in pairs(inv) do
|
||||
item = itemDB:get(item, function() return ni.getInventory().getItemMeta(slot) end)
|
||||
local key = makeKey(item)
|
||||
if not context.state.depositAll.retain[key] then
|
||||
if (context.state.depositAll.includeHotbar or slot > 9) and item.name ~= 'plethora:neuralconnector' then
|
||||
context:sendRequest({
|
||||
request = 'deposit',
|
||||
source = 'inventory',
|
||||
slot = slot,
|
||||
count = item.count,
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
for slot, item in pairs(inv) do
|
||||
item = itemDB:get(item, function() return ni.getInventory().getItemMeta(slot) end)
|
||||
local key = makeKey(item)
|
||||
if not context.state.depositAll.retain[key] then
|
||||
if (context.state.depositAll.includeHotbar or slot > 9) and item.name ~= 'plethora:neuralconnector' then
|
||||
context:sendRequest({
|
||||
request = 'deposit',
|
||||
source = 'inventory',
|
||||
slot = slot,
|
||||
count = item.count,
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function page:eventHandler(event)
|
||||
if event.type == 'checkbox_change' and event.element.formKey == 'includeHotbar' then
|
||||
context.state.depositAll.includeHotbar = event.checked
|
||||
page:updateInventoryList()
|
||||
if event.type == 'checkbox_change' and event.element.formKey == 'includeHotbar' then
|
||||
context.state.depositAll.includeHotbar = event.checked
|
||||
page:updateInventoryList()
|
||||
|
||||
elseif event.type == 'grid_select' then
|
||||
local key = event.selected.key
|
||||
if context.state.depositAll.retain[key] then
|
||||
context.state.depositAll.retain[key] = nil
|
||||
else
|
||||
context.state.depositAll.retain[key] = true
|
||||
end
|
||||
context:setState('depositAll', context.state.depositAll)
|
||||
self.items:draw()
|
||||
elseif event.type == 'grid_select' then
|
||||
local key = event.selected.key
|
||||
if context.state.depositAll.retain[key] then
|
||||
context.state.depositAll.retain[key] = nil
|
||||
else
|
||||
context.state.depositAll.retain[key] = true
|
||||
end
|
||||
context:setState('depositAll', context.state.depositAll)
|
||||
self.items:draw()
|
||||
|
||||
elseif event.type == 'form_complete' then
|
||||
Config.update('miloRemote', context.state)
|
||||
page:depositAll()
|
||||
UI:setPreviousPage()
|
||||
elseif event.type == 'form_complete' then
|
||||
Config.update('miloRemote', context.state)
|
||||
page:depositAll()
|
||||
UI:setPreviousPage()
|
||||
|
||||
elseif event.type == 'form_cancel' then
|
||||
UI:setPreviousPage()
|
||||
elseif event.type == 'form_cancel' then
|
||||
UI:setPreviousPage()
|
||||
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
menuItem = 'Deposit all',
|
||||
callback = function()
|
||||
UI:setPage(page)
|
||||
end,
|
||||
menuItem = 'Deposit all',
|
||||
callback = function()
|
||||
UI:setPage(page)
|
||||
end,
|
||||
}
|
||||
|
||||
@@ -16,13 +16,13 @@ local page = UI.Page {
|
||||
title = 'Auto-feeder',
|
||||
previousPage = true,
|
||||
},
|
||||
grid = UI.ScrollingGrid {
|
||||
y = 2, ey = -2,
|
||||
columns = {
|
||||
{ heading = 'Name', key = 'displayName' },
|
||||
},
|
||||
sortColumn = 'displayName',
|
||||
},
|
||||
grid = UI.ScrollingGrid {
|
||||
y = 2, ey = -2,
|
||||
columns = {
|
||||
{ heading = 'Name', key = 'displayName' },
|
||||
},
|
||||
sortColumn = 'displayName',
|
||||
},
|
||||
statusBar = UI.StatusBar {
|
||||
values = 'Double-click to toggle'
|
||||
},
|
||||
@@ -55,45 +55,45 @@ function page.grid:getRowTextColor(row)
|
||||
end
|
||||
|
||||
local function getFood(food)
|
||||
for slot,v in pairs(ni.getInventory().list()) do
|
||||
local key = itemDB:makeKey(v)
|
||||
if key == food then
|
||||
local item = ni.getInventory().getItem(slot)
|
||||
if item and item.consume then
|
||||
return item
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
for slot,v in pairs(ni.getInventory().list()) do
|
||||
local key = itemDB:makeKey(v)
|
||||
if key == food then
|
||||
local item = ni.getInventory().getItem(slot)
|
||||
if item and item.consume then
|
||||
return item
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function page:eventHandler(event)
|
||||
if event.type == 'grid_select' then
|
||||
if context.state.food == event.selected.key then
|
||||
context:setState('food')
|
||||
self.grid:draw()
|
||||
elseif getFood(event.selected.key) then
|
||||
context:setState('food', event.selected.key)
|
||||
self.grid:draw()
|
||||
else
|
||||
Sound.play('entity.villager.no')
|
||||
end
|
||||
if event.type == 'grid_select' then
|
||||
if context.state.food == event.selected.key then
|
||||
context:setState('food')
|
||||
self.grid:draw()
|
||||
elseif getFood(event.selected.key) then
|
||||
context:setState('food', event.selected.key)
|
||||
self.grid:draw()
|
||||
else
|
||||
Sound.play('entity.villager.no')
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
Event.onInterval(5, function()
|
||||
local s, m = pcall(function() -- prevent errors from some mod items
|
||||
if context.state.food and ni.getMetaOwner().food.hungry then
|
||||
local item = getFood(context.state.food)
|
||||
if item then
|
||||
item.consume()
|
||||
end
|
||||
end
|
||||
end)
|
||||
if not s and m then
|
||||
_G._syslog(m)
|
||||
end
|
||||
local s, m = pcall(function() -- prevent errors from some mod items
|
||||
if context.state.food and ni.getMetaOwner().food.hungry then
|
||||
local item = getFood(context.state.food)
|
||||
if item then
|
||||
item.consume()
|
||||
end
|
||||
end
|
||||
end)
|
||||
if not s and m then
|
||||
_G._syslog(m)
|
||||
end
|
||||
end)
|
||||
|
||||
return {
|
||||
|
||||
@@ -10,78 +10,78 @@ local STARTUP_FILE = 'usr/autorun/miloRemote.lua'
|
||||
local context = ({ ... })[1]
|
||||
|
||||
local setup = UI.SlideOut {
|
||||
backgroundColor = colors.cyan,
|
||||
titleBar = UI.TitleBar {
|
||||
title = 'Remote Setup',
|
||||
},
|
||||
form = UI.Form {
|
||||
x = 2, ex = -2, y = 2, ey = -1,
|
||||
[1] = UI.TextEntry {
|
||||
formLabel = 'Server', formKey = 'server',
|
||||
help = 'ID for the server',
|
||||
shadowText = 'Milo server ID',
|
||||
limit = 6,
|
||||
validate = 'numeric',
|
||||
required = true,
|
||||
},
|
||||
[2] = UI.TextEntry {
|
||||
formLabel = 'Return Slot', formKey = 'slot',
|
||||
help = 'Use a slot for sending to storage',
|
||||
shadowText = 'Inventory slot #',
|
||||
limit = 5,
|
||||
validate = 'numeric',
|
||||
required = false,
|
||||
},
|
||||
[3] = UI.Checkbox {
|
||||
formLabel = 'Shield Slot', formKey = 'useShield',
|
||||
help = 'Or, use the shield slot for sending'
|
||||
},
|
||||
[4] = UI.Checkbox {
|
||||
formLabel = 'Run on startup', formKey = 'runOnStartup',
|
||||
help = 'Run this program on startup'
|
||||
},
|
||||
info = UI.TextArea {
|
||||
x = 1, ex = -1, y = 6, ey = -4,
|
||||
textColor = colors.yellow,
|
||||
marginLeft = 0,
|
||||
marginRight = 0,
|
||||
value = [[The Milo turtle must connect to a manipulator with a ]] ..
|
||||
[[bound introspection module. The neural interface must ]] ..
|
||||
[[also have an introspection module.]],
|
||||
},
|
||||
},
|
||||
statusBar = UI.StatusBar {
|
||||
backgroundColor = colors.cyan,
|
||||
},
|
||||
backgroundColor = colors.cyan,
|
||||
titleBar = UI.TitleBar {
|
||||
title = 'Remote Setup',
|
||||
},
|
||||
form = UI.Form {
|
||||
x = 2, ex = -2, y = 2, ey = -1,
|
||||
[1] = UI.TextEntry {
|
||||
formLabel = 'Server', formKey = 'server',
|
||||
help = 'ID for the server',
|
||||
shadowText = 'Milo server ID',
|
||||
limit = 6,
|
||||
validate = 'numeric',
|
||||
required = true,
|
||||
},
|
||||
[2] = UI.TextEntry {
|
||||
formLabel = 'Return Slot', formKey = 'slot',
|
||||
help = 'Use a slot for sending to storage',
|
||||
shadowText = 'Inventory slot #',
|
||||
limit = 5,
|
||||
validate = 'numeric',
|
||||
required = false,
|
||||
},
|
||||
[3] = UI.Checkbox {
|
||||
formLabel = 'Shield Slot', formKey = 'useShield',
|
||||
help = 'Or, use the shield slot for sending'
|
||||
},
|
||||
[4] = UI.Checkbox {
|
||||
formLabel = 'Run on startup', formKey = 'runOnStartup',
|
||||
help = 'Run this program on startup'
|
||||
},
|
||||
info = UI.TextArea {
|
||||
x = 1, ex = -1, y = 6, ey = -4,
|
||||
textColor = colors.yellow,
|
||||
marginLeft = 0,
|
||||
marginRight = 0,
|
||||
value = [[The Milo turtle must connect to a manipulator with a ]] ..
|
||||
[[bound introspection module. The neural interface must ]] ..
|
||||
[[also have an introspection module.]],
|
||||
},
|
||||
},
|
||||
statusBar = UI.StatusBar {
|
||||
backgroundColor = colors.cyan,
|
||||
},
|
||||
}
|
||||
|
||||
function setup:eventHandler(event)
|
||||
if event.type == 'focus_change' then
|
||||
self.statusBar:setStatus(event.focused.help)
|
||||
if event.type == 'focus_change' then
|
||||
self.statusBar:setStatus(event.focused.help)
|
||||
|
||||
elseif event.type == 'form_complete' then
|
||||
Config.update('miloRemote', context.state)
|
||||
self:hide()
|
||||
context.page:refresh('list')
|
||||
context.page.grid:draw()
|
||||
context.page:setFocus(context.page.statusBar.filter)
|
||||
elseif event.type == 'form_complete' then
|
||||
Config.update('miloRemote', context.state)
|
||||
self:hide()
|
||||
context.page:refresh('list')
|
||||
context.page.grid:draw()
|
||||
context.page:setFocus(context.page.statusBar.filter)
|
||||
|
||||
if context.state.runOnStartup then
|
||||
if not fs.exists(STARTUP_FILE) then
|
||||
Util.writeFile(STARTUP_FILE,
|
||||
[[os.sleep(1)
|
||||
if context.state.runOnStartup then
|
||||
if not fs.exists(STARTUP_FILE) then
|
||||
Util.writeFile(STARTUP_FILE,
|
||||
[[os.sleep(1)
|
||||
shell.openForegroundTab('packages/milo/MiloRemote')]])
|
||||
end
|
||||
elseif fs.exists(STARTUP_FILE) then
|
||||
fs.delete(STARTUP_FILE)
|
||||
end
|
||||
end
|
||||
elseif fs.exists(STARTUP_FILE) then
|
||||
fs.delete(STARTUP_FILE)
|
||||
end
|
||||
|
||||
elseif event.type == 'form_cancel' then
|
||||
self:hide()
|
||||
context.page:setFocus(context.page.statusBar.filter)
|
||||
end
|
||||
elseif event.type == 'form_cancel' then
|
||||
self:hide()
|
||||
context.page:setFocus(context.page.statusBar.filter)
|
||||
end
|
||||
|
||||
return UI.SlideOut.eventHandler(self, event)
|
||||
return UI.SlideOut.eventHandler(self, event)
|
||||
end
|
||||
|
||||
context.page:add({ setup = setup })
|
||||
|
||||
@@ -5,42 +5,42 @@ local context = Milo:getContext()
|
||||
local device = _G.device
|
||||
|
||||
local function craftHandler(user, message, socket)
|
||||
local function craft()
|
||||
local slots = {
|
||||
[1] = 1, [2] = 2, [3] = 3,
|
||||
[5] = 10, [6] = 11, [7] = 12,
|
||||
[9] = 19, [10] = 20, [11] = 21,
|
||||
}
|
||||
local inventory = device[user .. ':inventory']
|
||||
if inventory then
|
||||
for k, v in pairs(slots) do
|
||||
inventory.pushItems(context.turtleInventory.name, v + message.slot - 1, 1, k)
|
||||
end
|
||||
local recipe, msg = Milo:learnRecipe()
|
||||
if recipe then
|
||||
socket:write({
|
||||
type = 'craft',
|
||||
msg = 'Learned: ' .. itemDB:getName(recipe),
|
||||
success = true,
|
||||
})
|
||||
for k,v in pairs(context.turtleInventory.adapter.list()) do
|
||||
inventory.pullItems(context.turtleInventory.name, k, v.count)
|
||||
end
|
||||
else
|
||||
socket:write({
|
||||
type = 'craft',
|
||||
msg = msg,
|
||||
})
|
||||
for k, v in pairs(slots) do
|
||||
inventory.pullItems(context.turtleInventory.name, k, 1, v + message.slot - 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
local function craft()
|
||||
local slots = {
|
||||
[1] = 1, [2] = 2, [3] = 3,
|
||||
[5] = 10, [6] = 11, [7] = 12,
|
||||
[9] = 19, [10] = 20, [11] = 21,
|
||||
}
|
||||
local inventory = device[user .. ':inventory']
|
||||
if inventory then
|
||||
for k, v in pairs(slots) do
|
||||
inventory.pushItems(context.turtleInventory.name, v + message.slot - 1, 1, k)
|
||||
end
|
||||
local recipe, msg = Milo:learnRecipe()
|
||||
if recipe then
|
||||
socket:write({
|
||||
type = 'craft',
|
||||
msg = 'Learned: ' .. itemDB:getName(recipe),
|
||||
success = true,
|
||||
})
|
||||
for k,v in pairs(context.turtleInventory.adapter.list()) do
|
||||
inventory.pullItems(context.turtleInventory.name, k, v.count)
|
||||
end
|
||||
else
|
||||
socket:write({
|
||||
type = 'craft',
|
||||
msg = msg,
|
||||
})
|
||||
for k, v in pairs(slots) do
|
||||
inventory.pullItems(context.turtleInventory.name, k, 1, v + message.slot - 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Milo:queueRequest({ }, craft)
|
||||
Milo:queueRequest({ }, craft)
|
||||
end
|
||||
|
||||
return {
|
||||
remoteHandler = { callback = craftHandler, messages = { craft = true } }
|
||||
remoteHandler = { callback = craftHandler, messages = { craft = true } }
|
||||
}
|
||||
|
||||
@@ -2,39 +2,39 @@ local itemDB = require('core.itemDB')
|
||||
local Milo = require('milo')
|
||||
|
||||
local ReplenishTask = {
|
||||
name = 'replenish',
|
||||
priority = 60,
|
||||
name = 'replenish',
|
||||
priority = 60,
|
||||
}
|
||||
|
||||
function ReplenishTask:cycle(context)
|
||||
for k,res in pairs(context.resources) do
|
||||
if res.low then
|
||||
local item = itemDB:splitKey(k)
|
||||
item.key = k
|
||||
for k,res in pairs(context.resources) do
|
||||
if res.low then
|
||||
local item = itemDB:splitKey(k)
|
||||
item.key = k
|
||||
|
||||
local _, count = Milo:getMatches(item, res)
|
||||
local _, count = Milo:getMatches(item, res)
|
||||
|
||||
if count < res.low then
|
||||
local nbtHash
|
||||
if not res.ignoreNbtHash then
|
||||
nbtHash = item.nbtHash
|
||||
end
|
||||
Milo:requestCrafting({
|
||||
name = item.name,
|
||||
damage = res.ignoreDamage and 0 or item.damage,
|
||||
nbtHash = nbtHash,
|
||||
requested = res.low - count,
|
||||
count = count,
|
||||
replenish = true,
|
||||
})
|
||||
else
|
||||
local request = context.craftingQueue[itemDB:makeKey(item)]
|
||||
if request and request.replenish then
|
||||
--request.count = request.crafted
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if count < res.low then
|
||||
local nbtHash
|
||||
if not res.ignoreNbtHash then
|
||||
nbtHash = item.nbtHash
|
||||
end
|
||||
Milo:requestCrafting({
|
||||
name = item.name,
|
||||
damage = res.ignoreDamage and 0 or item.damage,
|
||||
nbtHash = nbtHash,
|
||||
requested = res.low - count,
|
||||
count = count,
|
||||
replenish = true,
|
||||
})
|
||||
else
|
||||
local request = context.craftingQueue[itemDB:makeKey(item)]
|
||||
if request and request.replenish then
|
||||
--request.count = request.crafted
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Milo:registerTask(ReplenishTask)
|
||||
|
||||
@@ -8,66 +8,66 @@ local context = Milo:getContext()
|
||||
|
||||
local speakerNode = context.storage:getSingleNode('speaker')
|
||||
if speakerNode then
|
||||
Sound.setVolume(speakerNode.volume)
|
||||
Sound.setVolume(speakerNode.volume)
|
||||
end
|
||||
|
||||
local wizardPage = UI.WizardPage {
|
||||
title = 'Speaker',
|
||||
index = 2,
|
||||
backgroundColor = colors.cyan,
|
||||
[1] = UI.Text {
|
||||
x = 2, y = 2,
|
||||
textColor = colors.yellow,
|
||||
value = 'Set the volume for sound effects',
|
||||
},
|
||||
form = UI.Form {
|
||||
x = 2, ex = -2, y = 3, ey = -2,
|
||||
manualControls = true,
|
||||
volume = UI.TextEntry {
|
||||
formLabel = 'Volume', formKey = 'volume',
|
||||
width = 5, limit = 3,
|
||||
validate = 'numeric',
|
||||
help = 'A value from 0 (mute) to 1 (loud)',
|
||||
},
|
||||
testSound = UI.Button {
|
||||
x = 15, y = 2,
|
||||
text = 'Test', event = 'test_sound',
|
||||
help = 'Test sound volume',
|
||||
},
|
||||
},
|
||||
title = 'Speaker',
|
||||
index = 2,
|
||||
backgroundColor = colors.cyan,
|
||||
[1] = UI.Text {
|
||||
x = 2, y = 2,
|
||||
textColor = colors.yellow,
|
||||
value = 'Set the volume for sound effects',
|
||||
},
|
||||
form = UI.Form {
|
||||
x = 2, ex = -2, y = 3, ey = -2,
|
||||
manualControls = true,
|
||||
volume = UI.TextEntry {
|
||||
formLabel = 'Volume', formKey = 'volume',
|
||||
width = 5, limit = 3,
|
||||
validate = 'numeric',
|
||||
help = 'A value from 0 (mute) to 1 (loud)',
|
||||
},
|
||||
testSound = UI.Button {
|
||||
x = 15, y = 2,
|
||||
text = 'Test', event = 'test_sound',
|
||||
help = 'Test sound volume',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
function wizardPage:setNode(node)
|
||||
self.form:setValues(node)
|
||||
self.form:setValues(node)
|
||||
end
|
||||
|
||||
function wizardPage:saveNode(node)
|
||||
Sound.setVolume(node.volume)
|
||||
Sound.setVolume(node.volume)
|
||||
end
|
||||
|
||||
function wizardPage:validate()
|
||||
return self.form:save()
|
||||
return self.form:save()
|
||||
end
|
||||
|
||||
function wizardPage:isValidType(node)
|
||||
local m = device[node.name]
|
||||
return m and m.type == 'speaker' and {
|
||||
name = 'Speaker',
|
||||
value = 'speaker',
|
||||
category = 'custom',
|
||||
help = 'Sound effects',
|
||||
}
|
||||
local m = device[node.name]
|
||||
return m and m.type == 'speaker' and {
|
||||
name = 'Speaker',
|
||||
value = 'speaker',
|
||||
category = 'custom',
|
||||
help = 'Sound effects',
|
||||
}
|
||||
end
|
||||
|
||||
function wizardPage:isValidFor(node)
|
||||
return node.mtype == 'speaker'
|
||||
return node.mtype == 'speaker'
|
||||
end
|
||||
|
||||
function wizardPage:eventHandler(event)
|
||||
if event.type == 'test_sound' then
|
||||
local vol = tonumber(self.form.volume.value)
|
||||
Sound.play('entity.item.pickup', vol)
|
||||
end
|
||||
if event.type == 'test_sound' then
|
||||
local vol = tonumber(self.form.volume.value)
|
||||
Sound.play('entity.item.pickup', vol)
|
||||
end
|
||||
end
|
||||
|
||||
UI:getPage('nodeWizard').wizard:add({ speaker = wizardPage })
|
||||
|
||||
@@ -16,278 +16,278 @@ local template =
|
||||
Right-clicking on the activity monitor will reset the totals.]]
|
||||
|
||||
local wizardPage = UI.WizardPage {
|
||||
title = 'Status Monitor',
|
||||
index = 2,
|
||||
backgroundColor = colors.cyan,
|
||||
[1] = UI.TextArea {
|
||||
x = 2, ex = -2, y = 2, ey = 6,
|
||||
marginRight = 0,
|
||||
value = string.format(template, Ansi.yellow, Ansi.reset),
|
||||
},
|
||||
form = UI.Form {
|
||||
x = 2, ex = -2, y = 7, ey = -2,
|
||||
manualControls = true,
|
||||
[1] = UI.Chooser {
|
||||
width = 9,
|
||||
formLabel = 'Font Size', formKey = 'textScale',
|
||||
nochoice = 'Small',
|
||||
choices = {
|
||||
{ name = 'Small', value = .5 },
|
||||
{ name = 'Large', value = 1 },
|
||||
},
|
||||
help = 'Adjust text scaling',
|
||||
},
|
||||
},
|
||||
title = 'Status Monitor',
|
||||
index = 2,
|
||||
backgroundColor = colors.cyan,
|
||||
[1] = UI.TextArea {
|
||||
x = 2, ex = -2, y = 2, ey = 6,
|
||||
marginRight = 0,
|
||||
value = string.format(template, Ansi.yellow, Ansi.reset),
|
||||
},
|
||||
form = UI.Form {
|
||||
x = 2, ex = -2, y = 7, ey = -2,
|
||||
manualControls = true,
|
||||
[1] = UI.Chooser {
|
||||
width = 9,
|
||||
formLabel = 'Font Size', formKey = 'textScale',
|
||||
nochoice = 'Small',
|
||||
choices = {
|
||||
{ name = 'Small', value = .5 },
|
||||
{ name = 'Large', value = 1 },
|
||||
},
|
||||
help = 'Adjust text scaling',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
function wizardPage:setNode(node)
|
||||
self.form:setValues(node)
|
||||
self.form:setValues(node)
|
||||
end
|
||||
|
||||
function wizardPage:validate()
|
||||
return self.form:save()
|
||||
return self.form:save()
|
||||
end
|
||||
|
||||
function wizardPage:saveNode(node)
|
||||
os.queueEvent('monitor_resize', node.name)
|
||||
os.queueEvent('monitor_resize', node.name)
|
||||
end
|
||||
|
||||
function wizardPage:isValidType(node)
|
||||
local m = device[node.name]
|
||||
return m and m.type == 'monitor' and {
|
||||
name = 'Status Monitor',
|
||||
value = 'status',
|
||||
category = 'display',
|
||||
help = 'Display storage status'
|
||||
}
|
||||
local m = device[node.name]
|
||||
return m and m.type == 'monitor' and {
|
||||
name = 'Status Monitor',
|
||||
value = 'status',
|
||||
category = 'display',
|
||||
help = 'Display storage status'
|
||||
}
|
||||
end
|
||||
|
||||
function wizardPage:isValidFor(node)
|
||||
return node.mtype == 'status'
|
||||
return node.mtype == 'status'
|
||||
end
|
||||
|
||||
UI:getPage('nodeWizard').wizard:add({ statusMonitor = wizardPage })
|
||||
|
||||
--[[ Display ]]--
|
||||
local function createPage(node)
|
||||
local monitor = UI.Device {
|
||||
device = node.adapter,
|
||||
textScale = node.textScale or .5,
|
||||
}
|
||||
local monitor = UI.Device {
|
||||
device = node.adapter,
|
||||
textScale = node.textScale or .5,
|
||||
}
|
||||
|
||||
function monitor:resize()
|
||||
self.textScale = node.textScale or .5
|
||||
UI.Device.resize(self)
|
||||
end
|
||||
function monitor:resize()
|
||||
self.textScale = node.textScale or .5
|
||||
UI.Device.resize(self)
|
||||
end
|
||||
|
||||
local page = UI.Page {
|
||||
parent = monitor,
|
||||
tabs = UI.Tabs {
|
||||
[1] = UI.Tab {
|
||||
tabTitle = 'Overview',
|
||||
backgroundColor = colors.black,
|
||||
onlineLabel = UI.Text {
|
||||
x = 2, y = 2,
|
||||
value = 'Storage Status',
|
||||
},
|
||||
onlineText = UI.Text {
|
||||
x = 18, ex = -2, y = 2,
|
||||
},
|
||||
tpsLabel = UI.Text {
|
||||
x = 2, y = 3,
|
||||
value = 'Tasks/sec',
|
||||
},
|
||||
tpsText = UI.Text {
|
||||
x = 18, ex = -2, y = 3,
|
||||
},
|
||||
tasksLabel = UI.Text {
|
||||
x = -18, y = 3,
|
||||
value = 'Proc time',
|
||||
},
|
||||
tasksText = UI.Text {
|
||||
x = -6, ex = -2, y = 3,
|
||||
align = 'right',
|
||||
},
|
||||
storageLabel = UI.Text {
|
||||
x = 2, ex = -1, y = 6,
|
||||
},
|
||||
storage = UI.ProgressBar {
|
||||
x = 2, ex = -2, y = 7, height = 3,
|
||||
},
|
||||
unlockedLabel = UI.Text {
|
||||
x = 2, ex = -1, y = 12,
|
||||
},
|
||||
unlocked = UI.ProgressBar {
|
||||
x = 2, ex = -2, y = 13, height = 3,
|
||||
},
|
||||
craftingLabel = UI.Text {
|
||||
x = 2, ex = -1, y = 18,
|
||||
value = 'Crafting Status',
|
||||
},
|
||||
crafting = UI.ProgressBar {
|
||||
x = 2, ex = -2, y = 19, height = 3,
|
||||
value = 100,
|
||||
},
|
||||
},
|
||||
[2] = UI.Tab {
|
||||
tabTitle = 'Stats',
|
||||
textArea = UI.TextArea {
|
||||
y = 3,
|
||||
},
|
||||
},
|
||||
[3] = UI.Tab {
|
||||
tabTitle = 'Storage',
|
||||
grid = UI.ScrollingGrid {
|
||||
y = 2,
|
||||
columns = {
|
||||
{ heading = 'Name', key = 'name' },
|
||||
{ heading = 'Size', key = 'size', width = 5 },
|
||||
{ heading = 'Used', key = 'used', width = 5 },
|
||||
{ heading = 'Perc', key = 'perc', width = 5 },
|
||||
-- TODO: add % to each number
|
||||
},
|
||||
sortColumn = 'name',
|
||||
},
|
||||
},
|
||||
[4] = UI.Tab {
|
||||
tabTitle = 'Offline',
|
||||
grid = UI.ScrollingGrid {
|
||||
y = 2,
|
||||
columns = {
|
||||
{ heading = 'Name', key = 'name' },
|
||||
},
|
||||
sortColumn = 'name',
|
||||
},
|
||||
},
|
||||
[5] = UI.Tab {
|
||||
tabTitle = 'Activity',
|
||||
term = UI.Embedded {
|
||||
--visible = true,
|
||||
},
|
||||
},
|
||||
[6] = UI.Tab {
|
||||
tabTitle = 'Tasks',
|
||||
grid = UI.ScrollingGrid {
|
||||
y = 2,
|
||||
values = context.tasks,
|
||||
columns = {
|
||||
{ heading = 'Priority', key = 'priority', width = 5 },
|
||||
{ heading = 'Name', key = 'name' },
|
||||
{ heading = 'Avg', key = 'avg', width = 7, align = 'right' },
|
||||
{ heading = '%', key = 'perc', width = 7, align = 'right' },
|
||||
},
|
||||
sortColumn = 'priority',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
local page = UI.Page {
|
||||
parent = monitor,
|
||||
tabs = UI.Tabs {
|
||||
[1] = UI.Tab {
|
||||
tabTitle = 'Overview',
|
||||
backgroundColor = colors.black,
|
||||
onlineLabel = UI.Text {
|
||||
x = 2, y = 2,
|
||||
value = 'Storage Status',
|
||||
},
|
||||
onlineText = UI.Text {
|
||||
x = 18, ex = -2, y = 2,
|
||||
},
|
||||
tpsLabel = UI.Text {
|
||||
x = 2, y = 3,
|
||||
value = 'Tasks/sec',
|
||||
},
|
||||
tpsText = UI.Text {
|
||||
x = 18, ex = -2, y = 3,
|
||||
},
|
||||
tasksLabel = UI.Text {
|
||||
x = -18, y = 3,
|
||||
value = 'Proc time',
|
||||
},
|
||||
tasksText = UI.Text {
|
||||
x = -6, ex = -2, y = 3,
|
||||
align = 'right',
|
||||
},
|
||||
storageLabel = UI.Text {
|
||||
x = 2, ex = -1, y = 6,
|
||||
},
|
||||
storage = UI.ProgressBar {
|
||||
x = 2, ex = -2, y = 7, height = 3,
|
||||
},
|
||||
unlockedLabel = UI.Text {
|
||||
x = 2, ex = -1, y = 12,
|
||||
},
|
||||
unlocked = UI.ProgressBar {
|
||||
x = 2, ex = -2, y = 13, height = 3,
|
||||
},
|
||||
craftingLabel = UI.Text {
|
||||
x = 2, ex = -1, y = 18,
|
||||
value = 'Crafting Status',
|
||||
},
|
||||
crafting = UI.ProgressBar {
|
||||
x = 2, ex = -2, y = 19, height = 3,
|
||||
value = 100,
|
||||
},
|
||||
},
|
||||
[2] = UI.Tab {
|
||||
tabTitle = 'Stats',
|
||||
textArea = UI.TextArea {
|
||||
y = 3,
|
||||
},
|
||||
},
|
||||
[3] = UI.Tab {
|
||||
tabTitle = 'Storage',
|
||||
grid = UI.ScrollingGrid {
|
||||
y = 2,
|
||||
columns = {
|
||||
{ heading = 'Name', key = 'name' },
|
||||
{ heading = 'Size', key = 'size', width = 5 },
|
||||
{ heading = 'Used', key = 'used', width = 5 },
|
||||
{ heading = 'Perc', key = 'perc', width = 5 },
|
||||
-- TODO: add % to each number
|
||||
},
|
||||
sortColumn = 'name',
|
||||
},
|
||||
},
|
||||
[4] = UI.Tab {
|
||||
tabTitle = 'Offline',
|
||||
grid = UI.ScrollingGrid {
|
||||
y = 2,
|
||||
columns = {
|
||||
{ heading = 'Name', key = 'name' },
|
||||
},
|
||||
sortColumn = 'name',
|
||||
},
|
||||
},
|
||||
[5] = UI.Tab {
|
||||
tabTitle = 'Activity',
|
||||
term = UI.Embedded {
|
||||
--visible = true,
|
||||
},
|
||||
},
|
||||
[6] = UI.Tab {
|
||||
tabTitle = 'Tasks',
|
||||
grid = UI.ScrollingGrid {
|
||||
y = 2,
|
||||
values = context.tasks,
|
||||
columns = {
|
||||
{ heading = 'Priority', key = 'priority', width = 5 },
|
||||
{ heading = 'Name', key = 'name' },
|
||||
{ heading = 'Avg', key = 'avg', width = 7, align = 'right' },
|
||||
{ heading = '%', key = 'perc', width = 7, align = 'right' },
|
||||
},
|
||||
sortColumn = 'priority',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
local overviewTab = page.tabs[1]
|
||||
local statsTab = page.tabs[2]
|
||||
local usageTab = page.tabs[3]
|
||||
local stateTab = page.tabs[4]
|
||||
local activityTab = page.tabs[5]
|
||||
local taskTab = page.tabs[6]
|
||||
local overviewTab = page.tabs[1]
|
||||
local statsTab = page.tabs[2]
|
||||
local usageTab = page.tabs[3]
|
||||
local stateTab = page.tabs[4]
|
||||
local activityTab = page.tabs[5]
|
||||
local taskTab = page.tabs[6]
|
||||
|
||||
local function getStorageStats()
|
||||
local stats = { }
|
||||
local totals = {
|
||||
usedSlots = 0,
|
||||
totalSlots = 0,
|
||||
totalChests = 0,
|
||||
unlockedSlots = 0,
|
||||
usedUnlockedSlots = 0,
|
||||
}
|
||||
local function getStorageStats()
|
||||
local stats = { }
|
||||
local totals = {
|
||||
usedSlots = 0,
|
||||
totalSlots = 0,
|
||||
totalChests = 0,
|
||||
unlockedSlots = 0,
|
||||
usedUnlockedSlots = 0,
|
||||
}
|
||||
|
||||
for n in context.storage:filterActive('storage') do
|
||||
if n.adapter.size and n.adapter.list then
|
||||
pcall(function()
|
||||
local updated = n.adapter.__lastUpdate ~= n.adapter.lastUpdate
|
||||
if updated then
|
||||
n.adapter.__used = Util.size(n.adapter.list())
|
||||
n.adapter.__lastUpdate = n.adapter.lastUpdate
|
||||
end
|
||||
if not n.adapter.__used then
|
||||
n.adapter.__used = Util.size(n.adapter.list())
|
||||
end
|
||||
table.insert(stats, {
|
||||
name = n.displayName or n.name,
|
||||
size = n.adapter.__size,
|
||||
used = n.adapter.__used,
|
||||
perc = math.floor(n.adapter.__used / n.adapter.__size * 100),
|
||||
updated = updated,
|
||||
})
|
||||
totals.usedSlots = totals.usedSlots + n.adapter.__used
|
||||
totals.totalSlots = totals.totalSlots + n.adapter.__size
|
||||
totals.totalChests = totals.totalChests + 1
|
||||
if not n.lock then
|
||||
totals.unlockedSlots = totals.unlockedSlots + n.adapter.__size
|
||||
totals.usedUnlockedSlots = totals.usedUnlockedSlots + n.adapter.__used
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
for n in context.storage:filterActive('storage') do
|
||||
if n.adapter.size and n.adapter.list then
|
||||
pcall(function()
|
||||
local updated = n.adapter.__lastUpdate ~= n.adapter.lastUpdate
|
||||
if updated then
|
||||
n.adapter.__used = Util.size(n.adapter.list())
|
||||
n.adapter.__lastUpdate = n.adapter.lastUpdate
|
||||
end
|
||||
if not n.adapter.__used then
|
||||
n.adapter.__used = Util.size(n.adapter.list())
|
||||
end
|
||||
table.insert(stats, {
|
||||
name = n.displayName or n.name,
|
||||
size = n.adapter.__size,
|
||||
used = n.adapter.__used,
|
||||
perc = math.floor(n.adapter.__used / n.adapter.__size * 100),
|
||||
updated = updated,
|
||||
})
|
||||
totals.usedSlots = totals.usedSlots + n.adapter.__used
|
||||
totals.totalSlots = totals.totalSlots + n.adapter.__size
|
||||
totals.totalChests = totals.totalChests + 1
|
||||
if not n.lock then
|
||||
totals.unlockedSlots = totals.unlockedSlots + n.adapter.__size
|
||||
totals.usedUnlockedSlots = totals.usedUnlockedSlots + n.adapter.__used
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
return stats, totals
|
||||
end
|
||||
return stats, totals
|
||||
end
|
||||
|
||||
function stateTab:refresh()
|
||||
self.grid.values = { }
|
||||
for _, v in pairs(context.storage.nodes) do
|
||||
if v.mtype ~= 'hidden' then
|
||||
if not v.adapter or not v.adapter.online then
|
||||
table.insert(self.grid.values, {
|
||||
name = v.displayName or v.name
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
self.grid:update()
|
||||
end
|
||||
function stateTab:refresh()
|
||||
self.grid.values = { }
|
||||
for _, v in pairs(context.storage.nodes) do
|
||||
if v.mtype ~= 'hidden' then
|
||||
if not v.adapter or not v.adapter.online then
|
||||
table.insert(self.grid.values, {
|
||||
name = v.displayName or v.name
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
self.grid:update()
|
||||
end
|
||||
|
||||
function stateTab:enable()
|
||||
self:refresh()
|
||||
self.handle = Event.onInterval(5, function()
|
||||
self:refresh()
|
||||
self.grid:draw()
|
||||
self:sync()
|
||||
end)
|
||||
UI.Tab.enable(self)
|
||||
end
|
||||
function stateTab:enable()
|
||||
self:refresh()
|
||||
self.handle = Event.onInterval(5, function()
|
||||
self:refresh()
|
||||
self.grid:draw()
|
||||
self:sync()
|
||||
end)
|
||||
UI.Tab.enable(self)
|
||||
end
|
||||
|
||||
function stateTab:disable()
|
||||
Event.off(self.handle)
|
||||
UI.Tab.disable(self)
|
||||
end
|
||||
function stateTab:disable()
|
||||
Event.off(self.handle)
|
||||
UI.Tab.disable(self)
|
||||
end
|
||||
|
||||
function usageTab:refresh()
|
||||
self.grid:setValues(getStorageStats())
|
||||
end
|
||||
function usageTab:refresh()
|
||||
self.grid:setValues(getStorageStats())
|
||||
end
|
||||
|
||||
function usageTab:enable()
|
||||
self:refresh()
|
||||
self.handle = Event.onInterval(5, function()
|
||||
self:refresh()
|
||||
self.grid:draw()
|
||||
self:sync()
|
||||
end)
|
||||
UI.Tab.enable(self)
|
||||
end
|
||||
function usageTab:enable()
|
||||
self:refresh()
|
||||
self.handle = Event.onInterval(5, function()
|
||||
self:refresh()
|
||||
self.grid:draw()
|
||||
self:sync()
|
||||
end)
|
||||
UI.Tab.enable(self)
|
||||
end
|
||||
|
||||
function usageTab:disable()
|
||||
Event.off(self.handle)
|
||||
UI.Tab.disable(self)
|
||||
end
|
||||
function usageTab:disable()
|
||||
Event.off(self.handle)
|
||||
UI.Tab.disable(self)
|
||||
end
|
||||
|
||||
function usageTab.grid:getRowTextColor(row, selected)
|
||||
return row.updated and colors.yellow or
|
||||
UI.Grid:getRowTextColor(row, selected)
|
||||
end
|
||||
function usageTab.grid:getRowTextColor(row, selected)
|
||||
return row.updated and colors.yellow or
|
||||
UI.Grid:getRowTextColor(row, selected)
|
||||
end
|
||||
|
||||
function statsTab.textArea:draw()
|
||||
local _, stats = getStorageStats()
|
||||
local totalItems, nodeCount = 0, 0
|
||||
local formatString = [[
|
||||
function statsTab.textArea:draw()
|
||||
local _, stats = getStorageStats()
|
||||
local totalItems, nodeCount = 0, 0
|
||||
local formatString = [[
|
||||
Storage Usage : %d%%
|
||||
Slots : %d of %d used
|
||||
Unique Items : %d
|
||||
@@ -297,191 +297,191 @@ Nodes : %d
|
||||
Unlocked Slots : %d of %d (%d%%)
|
||||
]]
|
||||
|
||||
for _,v in pairs(context.storage.nodes) do
|
||||
if v.adapter and v.adapter.online then
|
||||
nodeCount = nodeCount + 1
|
||||
end
|
||||
end
|
||||
for _,v in pairs(context.storage.nodes) do
|
||||
if v.adapter and v.adapter.online then
|
||||
nodeCount = nodeCount + 1
|
||||
end
|
||||
end
|
||||
|
||||
for _,v in pairs(context.storage.cache) do
|
||||
totalItems = totalItems + v.count
|
||||
end
|
||||
for _,v in pairs(context.storage.cache) do
|
||||
totalItems = totalItems + v.count
|
||||
end
|
||||
|
||||
self.value = string.format(formatString,
|
||||
math.floor(stats.usedSlots / stats.totalSlots * 100),
|
||||
stats.usedSlots,
|
||||
stats.totalSlots,
|
||||
Util.size(context.storage.cache),
|
||||
totalItems,
|
||||
nodeCount,
|
||||
stats.usedUnlockedSlots,
|
||||
stats.unlockedSlots,
|
||||
math.floor(stats.usedUnlockedSlots / stats.unlockedSlots * 100))
|
||||
UI.TextArea.draw(self)
|
||||
end
|
||||
self.value = string.format(formatString,
|
||||
math.floor(stats.usedSlots / stats.totalSlots * 100),
|
||||
stats.usedSlots,
|
||||
stats.totalSlots,
|
||||
Util.size(context.storage.cache),
|
||||
totalItems,
|
||||
nodeCount,
|
||||
stats.usedUnlockedSlots,
|
||||
stats.unlockedSlots,
|
||||
math.floor(stats.usedUnlockedSlots / stats.unlockedSlots * 100))
|
||||
UI.TextArea.draw(self)
|
||||
end
|
||||
|
||||
function statsTab:enable()
|
||||
self.handle = Event.onInterval(5, function()
|
||||
self.textArea:draw()
|
||||
self:sync()
|
||||
end)
|
||||
UI.Tab.enable(self)
|
||||
end
|
||||
function statsTab:enable()
|
||||
self.handle = Event.onInterval(5, function()
|
||||
self.textArea:draw()
|
||||
self:sync()
|
||||
end)
|
||||
UI.Tab.enable(self)
|
||||
end
|
||||
|
||||
function statsTab:disable()
|
||||
Event.off(self.handle)
|
||||
UI.Tab.disable(self)
|
||||
end
|
||||
function statsTab:disable()
|
||||
Event.off(self.handle)
|
||||
UI.Tab.disable(self)
|
||||
end
|
||||
|
||||
function taskTab.grid:getDisplayValues(row)
|
||||
return {
|
||||
name = row.name,
|
||||
priority = row.priority,
|
||||
avg = Util.round(row.execTime / context.taskCounter * 1000) .. ' ms',
|
||||
perc = Util.round(row.execTime / context.taskTimer * 100) .. '%',
|
||||
}
|
||||
end
|
||||
function taskTab.grid:getDisplayValues(row)
|
||||
return {
|
||||
name = row.name,
|
||||
priority = row.priority,
|
||||
avg = Util.round(row.execTime / context.taskCounter * 1000) .. ' ms',
|
||||
perc = Util.round(row.execTime / context.taskTimer * 100) .. '%',
|
||||
}
|
||||
end
|
||||
|
||||
function taskTab:refresh()
|
||||
self.grid:update()
|
||||
end
|
||||
function taskTab:refresh()
|
||||
self.grid:update()
|
||||
end
|
||||
|
||||
function taskTab:enable()
|
||||
self:refresh()
|
||||
self.handle = Event.onInterval(5, function()
|
||||
self:refresh()
|
||||
self.grid:draw()
|
||||
self:sync()
|
||||
end)
|
||||
UI.Tab.enable(self)
|
||||
end
|
||||
function taskTab:enable()
|
||||
self:refresh()
|
||||
self.handle = Event.onInterval(5, function()
|
||||
self:refresh()
|
||||
self.grid:draw()
|
||||
self:sync()
|
||||
end)
|
||||
UI.Tab.enable(self)
|
||||
end
|
||||
|
||||
function taskTab:disable()
|
||||
Event.off(self.handle)
|
||||
UI.Tab.disable(self)
|
||||
end
|
||||
function taskTab:disable()
|
||||
Event.off(self.handle)
|
||||
UI.Tab.disable(self)
|
||||
end
|
||||
|
||||
function overviewTab:draw()
|
||||
local _, stats = getStorageStats()
|
||||
function overviewTab:draw()
|
||||
local _, stats = getStorageStats()
|
||||
|
||||
self.onlineText.textColor = context.storage:isOnline() and colors.green or colors.red
|
||||
self.onlineText.value = context.storage:isOnline() and 'Online' or 'Offline'
|
||||
self.onlineText.textColor = context.storage:isOnline() and colors.green or colors.red
|
||||
self.onlineText.value = context.storage:isOnline() and 'Online' or 'Offline'
|
||||
|
||||
self.tpsText.value = tostring(Util.round(self.tasks / (os.clock() - self.timer), 2))
|
||||
self.tasksText.value = tostring(Util.round(context.taskTimer / context.taskCounter, 2))
|
||||
self.tpsText.value = tostring(Util.round(self.tasks / (os.clock() - self.timer), 2))
|
||||
self.tasksText.value = tostring(Util.round(context.taskTimer / context.taskCounter, 2))
|
||||
|
||||
local total, crafted = 0, 0
|
||||
for _,v in pairs(context.craftingQueue) do
|
||||
total = total + v.requested
|
||||
crafted = crafted + v.crafted
|
||||
end
|
||||
if Milo:isCraftingPaused() then
|
||||
self.crafting.progressColor = colors.yellow
|
||||
self.crafting.value = 100
|
||||
else
|
||||
self.crafting.progressColor = colors.orange
|
||||
self.crafting.value = total > 0 and math.ceil(crafted / total * 100) or 0
|
||||
end
|
||||
local total, crafted = 0, 0
|
||||
for _,v in pairs(context.craftingQueue) do
|
||||
total = total + v.requested
|
||||
crafted = crafted + v.crafted
|
||||
end
|
||||
if Milo:isCraftingPaused() then
|
||||
self.crafting.progressColor = colors.yellow
|
||||
self.crafting.value = 100
|
||||
else
|
||||
self.crafting.progressColor = colors.orange
|
||||
self.crafting.value = total > 0 and math.ceil(crafted / total * 100) or 0
|
||||
end
|
||||
|
||||
local percent = math.floor(stats.usedSlots / stats.totalSlots * 100)
|
||||
local color = colors.green
|
||||
if percent > 90 then
|
||||
color = colors.red
|
||||
elseif percent > 75 then
|
||||
color = colors.yellow
|
||||
end
|
||||
self.storage.progressColor = color
|
||||
self.storage.value = percent
|
||||
local percent = math.floor(stats.usedSlots / stats.totalSlots * 100)
|
||||
local color = colors.green
|
||||
if percent > 90 then
|
||||
color = colors.red
|
||||
elseif percent > 75 then
|
||||
color = colors.yellow
|
||||
end
|
||||
self.storage.progressColor = color
|
||||
self.storage.value = percent
|
||||
|
||||
self.storageLabel.value = string.format('Total Usage: %s%% (%s of %s slots)',
|
||||
percent, stats.usedSlots, stats.totalSlots)
|
||||
self.storageLabel.value = string.format('Total Usage: %s%% (%s of %s slots)',
|
||||
percent, stats.usedSlots, stats.totalSlots)
|
||||
|
||||
percent = math.floor(stats.usedUnlockedSlots / stats.unlockedSlots * 100)
|
||||
color = colors.green
|
||||
if percent > 90 then
|
||||
color = colors.red
|
||||
elseif percent > 75 then
|
||||
color = colors.yellow
|
||||
end
|
||||
self.unlocked.progressColor = color
|
||||
self.unlocked.value = percent
|
||||
percent = math.floor(stats.usedUnlockedSlots / stats.unlockedSlots * 100)
|
||||
color = colors.green
|
||||
if percent > 90 then
|
||||
color = colors.red
|
||||
elseif percent > 75 then
|
||||
color = colors.yellow
|
||||
end
|
||||
self.unlocked.progressColor = color
|
||||
self.unlocked.value = percent
|
||||
|
||||
self.unlockedLabel.value = string.format('Unlocked Usage: %s%% (%s of %s slots)',
|
||||
percent, stats.usedUnlockedSlots, stats.unlockedSlots)
|
||||
self.unlockedLabel.value = string.format('Unlocked Usage: %s%% (%s of %s slots)',
|
||||
percent, stats.usedUnlockedSlots, stats.unlockedSlots)
|
||||
|
||||
UI.Tab.draw(self)
|
||||
end
|
||||
UI.Tab.draw(self)
|
||||
end
|
||||
|
||||
function overviewTab:enable()
|
||||
self.timer = os.clock()
|
||||
self.tasks = 0
|
||||
self.handle = Event.onInterval(5, function()
|
||||
self:draw()
|
||||
self:sync()
|
||||
end)
|
||||
self.handle2 = Event.on({ 'milo_resume', 'milo_pause', 'storage_offline', 'storage_online' }, function()
|
||||
self:draw()
|
||||
self:sync()
|
||||
end)
|
||||
self.handle3 = Event.on('plethora_task', function()
|
||||
self.tasks = self.tasks + 1
|
||||
end)
|
||||
UI.Tab.enable(self)
|
||||
end
|
||||
function overviewTab:enable()
|
||||
self.timer = os.clock()
|
||||
self.tasks = 0
|
||||
self.handle = Event.onInterval(5, function()
|
||||
self:draw()
|
||||
self:sync()
|
||||
end)
|
||||
self.handle2 = Event.on({ 'milo_resume', 'milo_pause', 'storage_offline', 'storage_online' }, function()
|
||||
self:draw()
|
||||
self:sync()
|
||||
end)
|
||||
self.handle3 = Event.on({ 'plethora_task', 'task_complete' }, function()
|
||||
self.tasks = self.tasks + 1
|
||||
end)
|
||||
UI.Tab.enable(self)
|
||||
end
|
||||
|
||||
function overviewTab:disable()
|
||||
Event.off(self.handle)
|
||||
Event.off(self.handle2)
|
||||
Event.off(self.handle3)
|
||||
UI.Tab.disable(self)
|
||||
end
|
||||
function overviewTab:disable()
|
||||
Event.off(self.handle)
|
||||
Event.off(self.handle2)
|
||||
Event.off(self.handle3)
|
||||
UI.Tab.disable(self)
|
||||
end
|
||||
|
||||
function page:eventHandler(event)
|
||||
if event.type == 'tab_activate' then
|
||||
local state = Milo:getState('statusState') or { }
|
||||
state[node.name] = event.activated.tabTitle
|
||||
Milo:setState('statusState', state)
|
||||
end
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
function page:eventHandler(event)
|
||||
if event.type == 'tab_activate' then
|
||||
local state = Milo:getState('statusState') or { }
|
||||
state[node.name] = event.activated.tabTitle
|
||||
Milo:setState('statusState', state)
|
||||
end
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
|
||||
table.insert(context.loggers, function(...)
|
||||
local oterm = term.redirect(activityTab.term.win)
|
||||
activityTab.term.win.scrollBottom()
|
||||
Util.print(...)
|
||||
term.redirect(oterm)
|
||||
if activityTab.enabled then
|
||||
activityTab:sync()
|
||||
end
|
||||
end)
|
||||
table.insert(context.loggers, function(...)
|
||||
local oterm = term.redirect(activityTab.term.win)
|
||||
activityTab.term.win.scrollBottom()
|
||||
Util.print(...)
|
||||
term.redirect(oterm)
|
||||
if activityTab.enabled then
|
||||
activityTab:sync()
|
||||
end
|
||||
end)
|
||||
|
||||
Event.onTimeout(0, function()
|
||||
UI:setPage(page)
|
||||
end)
|
||||
Event.onTimeout(0, function()
|
||||
UI:setPage(page)
|
||||
end)
|
||||
|
||||
-- restore active tab
|
||||
local tabState = Milo:getState('statusState') or { }
|
||||
if tabState[node.name] then
|
||||
page.tabs:selectTab(Util.find(page.tabs, 'tabTitle', tabState[node.name]))
|
||||
end
|
||||
-- restore active tab
|
||||
local tabState = Milo:getState('statusState') or { }
|
||||
if tabState[node.name] then
|
||||
page.tabs:selectTab(Util.find(page.tabs, 'tabTitle', tabState[node.name]))
|
||||
end
|
||||
|
||||
return page
|
||||
return page
|
||||
end
|
||||
|
||||
local pages = { }
|
||||
|
||||
--[[ Task ]]--
|
||||
local task = {
|
||||
name = 'status',
|
||||
priority = 99,
|
||||
name = 'status',
|
||||
priority = 99,
|
||||
}
|
||||
|
||||
function task:cycle()
|
||||
for node in context.storage:filterActive('status') do
|
||||
if not pages[node.name] then
|
||||
pages[node.name] = createPage(node)
|
||||
end
|
||||
end
|
||||
for node in context.storage:filterActive('status') do
|
||||
if not pages[node.name] then
|
||||
pages[node.name] = createPage(node)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Milo:registerTask(task)
|
||||
|
||||
@@ -7,89 +7,89 @@ local device = _G.device
|
||||
|
||||
--[[ Configuration Screen ]]
|
||||
local wizardPage = UI.WizardPage {
|
||||
title = 'Trashcan',
|
||||
index = 2,
|
||||
backgroundColor = colors.cyan,
|
||||
info = UI.TextArea {
|
||||
x = 1, ex = -1, y = 2, ey = 4,
|
||||
textColor = colors.yellow,
|
||||
marginLeft = 1,
|
||||
marginRight = 1,
|
||||
value = [[ Items can be automatically dropped from this storage.]],
|
||||
},
|
||||
form = UI.Form {
|
||||
x = 2, ex = -2, y = 4, ey = -2,
|
||||
manualControls = true,
|
||||
[1] = UI.Checkbox {
|
||||
formLabel = 'Drop', formKey = 'drop',
|
||||
help = 'Drop the items out of this inventory',
|
||||
},
|
||||
[2] = UI.Chooser {
|
||||
width = 9,
|
||||
formLabel = 'Direction', formKey = 'dropDirection',
|
||||
nochoice = 'Down',
|
||||
choices = {
|
||||
{ name = 'Down', value = 'down' },
|
||||
{ name = 'Up', value = 'up' },
|
||||
{ name = 'North', value = 'north' },
|
||||
{ name = 'South', value = 'south' },
|
||||
{ name = 'East', value = 'east' },
|
||||
{ name = 'West', value = 'west' },
|
||||
},
|
||||
help = 'Drop in a specified direction'
|
||||
},
|
||||
},
|
||||
title = 'Trashcan',
|
||||
index = 2,
|
||||
backgroundColor = colors.cyan,
|
||||
info = UI.TextArea {
|
||||
x = 1, ex = -1, y = 2, ey = 4,
|
||||
textColor = colors.yellow,
|
||||
marginLeft = 1,
|
||||
marginRight = 1,
|
||||
value = [[ Items can be automatically dropped from this storage.]],
|
||||
},
|
||||
form = UI.Form {
|
||||
x = 2, ex = -2, y = 4, ey = -2,
|
||||
manualControls = true,
|
||||
[1] = UI.Checkbox {
|
||||
formLabel = 'Drop', formKey = 'drop',
|
||||
help = 'Drop the items out of this inventory',
|
||||
},
|
||||
[2] = UI.Chooser {
|
||||
width = 9,
|
||||
formLabel = 'Direction', formKey = 'dropDirection',
|
||||
nochoice = 'Down',
|
||||
choices = {
|
||||
{ name = 'Down', value = 'down' },
|
||||
{ name = 'Up', value = 'up' },
|
||||
{ name = 'North', value = 'north' },
|
||||
{ name = 'South', value = 'south' },
|
||||
{ name = 'East', value = 'east' },
|
||||
{ name = 'West', value = 'west' },
|
||||
},
|
||||
help = 'Drop in a specified direction'
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
function wizardPage:validate()
|
||||
return self.form:save()
|
||||
return self.form:save()
|
||||
end
|
||||
|
||||
function wizardPage:setNode(node)
|
||||
self.form:setValues(node)
|
||||
self.form:setValues(node)
|
||||
end
|
||||
|
||||
function wizardPage:isValidType(node)
|
||||
local m = device[node.name]
|
||||
return m and m.pullItems and {
|
||||
name = 'Trashcan',
|
||||
value = 'trashcan',
|
||||
category = 'custom',
|
||||
help = 'An inventory to send unwanted items',
|
||||
}
|
||||
local m = device[node.name]
|
||||
return m and m.pullItems and {
|
||||
name = 'Trashcan',
|
||||
value = 'trashcan',
|
||||
category = 'custom',
|
||||
help = 'An inventory to send unwanted items',
|
||||
}
|
||||
end
|
||||
|
||||
function wizardPage:isValidFor(node)
|
||||
return node.mtype == 'trashcan'
|
||||
return node.mtype == 'trashcan'
|
||||
end
|
||||
|
||||
UI:getPage('nodeWizard').wizard:add({ trashcan = wizardPage })
|
||||
|
||||
--[[ TASK ]]--
|
||||
local task = {
|
||||
name = 'trashcan',
|
||||
priority = 90,
|
||||
name = 'trashcan',
|
||||
priority = 90,
|
||||
}
|
||||
|
||||
local function filter(a)
|
||||
return a.drop
|
||||
return a.drop
|
||||
end
|
||||
|
||||
function task:cycle(context)
|
||||
local tasks = Tasks()
|
||||
local tasks = Tasks()
|
||||
|
||||
for node in context.storage:filterActive('trashcan', filter) do
|
||||
pcall(function()
|
||||
for k in pairs(node.adapter.list()) do
|
||||
local direction = node.dropDirection or 'down'
|
||||
tasks:add(function()
|
||||
node.adapter.drop(k, 64, direction)
|
||||
end)
|
||||
end
|
||||
end)
|
||||
end
|
||||
for node in context.storage:filterActive('trashcan', filter) do
|
||||
pcall(function()
|
||||
for k in pairs(node.adapter.list()) do
|
||||
local direction = node.dropDirection or 'down'
|
||||
tasks:add(function()
|
||||
node.adapter.drop(k, 64, direction)
|
||||
end)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
tasks:run()
|
||||
tasks:run()
|
||||
end
|
||||
|
||||
Milo:registerTask(task)
|
||||
|
||||
@@ -20,160 +20,160 @@ local paused, abort
|
||||
local chunkIndex = 0
|
||||
local swarm = Swarm()
|
||||
local blocks = Util.transpose({
|
||||
'minecraft:chest',
|
||||
'minecraft:chest',
|
||||
-- 'minecraft:mob_spawner',
|
||||
'quark:crystal',
|
||||
'minecraft:mossy_cobblestone'
|
||||
'quark:crystal',
|
||||
'minecraft:mossy_cobblestone'
|
||||
})
|
||||
local locations = { }
|
||||
|
||||
gpt.x = gpt.x + 1
|
||||
|
||||
local function getLocations()
|
||||
local y = gpt.y - 8
|
||||
while y > 5 do
|
||||
table.insert(locations, y)
|
||||
y = y - 16
|
||||
end
|
||||
if y > 0 then
|
||||
table.insert(locations, 5)
|
||||
end
|
||||
local y = gpt.y - 8
|
||||
while y > 5 do
|
||||
table.insert(locations, y)
|
||||
y = y - 16
|
||||
end
|
||||
if y > 0 then
|
||||
table.insert(locations, 5)
|
||||
end
|
||||
end
|
||||
|
||||
for _, b in pairs(scanner.scan()) do
|
||||
if b.name == 'computercraft:turtle_advanced' or
|
||||
b.name == 'computercraft:turtle' then
|
||||
if b.name == 'computercraft:turtle_advanced' or
|
||||
b.name == 'computercraft:turtle' then
|
||||
|
||||
local v = scanner.getBlockMeta(b.x, b.y, b.z)
|
||||
if v and v.computer then
|
||||
if not v.computer.isOn then
|
||||
print('Powered off: ' .. v.computer.id)
|
||||
elseif v.turtle.fuel < 100 then
|
||||
print('not enough fuel: ' .. v.computer.id)
|
||||
else
|
||||
swarm:add(v.computer.id, {
|
||||
point = {
|
||||
x = gpt.x + b.x,
|
||||
y = gpt.y + b.y,
|
||||
z = gpt.z + b.z,
|
||||
heading = Point.facings[v.state.facing].heading,
|
||||
},
|
||||
index = Util.size(swarm.pool),
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
local v = scanner.getBlockMeta(b.x, b.y, b.z)
|
||||
if v and v.computer then
|
||||
if not v.computer.isOn then
|
||||
print('Powered off: ' .. v.computer.id)
|
||||
elseif v.turtle.fuel < 100 then
|
||||
print('not enough fuel: ' .. v.computer.id)
|
||||
else
|
||||
swarm:add(v.computer.id, {
|
||||
point = {
|
||||
x = gpt.x + b.x,
|
||||
y = gpt.y + b.y,
|
||||
z = gpt.z + b.z,
|
||||
heading = Point.facings[v.state.facing].heading,
|
||||
},
|
||||
index = Util.size(swarm.pool),
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function getNextPoint(member)
|
||||
local z = math.floor(chunkIndex / COLUMNS)
|
||||
local x = chunkIndex % COLUMNS
|
||||
local z = math.floor(chunkIndex / COLUMNS)
|
||||
local x = chunkIndex % COLUMNS
|
||||
|
||||
chunkIndex = chunkIndex + 1
|
||||
chunkIndex = chunkIndex + 1
|
||||
|
||||
while paused do
|
||||
if abort then
|
||||
return
|
||||
end
|
||||
os.sleep(3)
|
||||
end
|
||||
while paused do
|
||||
if abort then
|
||||
return
|
||||
end
|
||||
os.sleep(3)
|
||||
end
|
||||
|
||||
return {
|
||||
x = gpt.x + (x * 16),
|
||||
y = gpt.y + member.index,
|
||||
z = gpt.z + (z * 16)
|
||||
}
|
||||
return {
|
||||
x = gpt.x + (x * 16),
|
||||
y = gpt.y + member.index,
|
||||
z = gpt.z + (z * 16)
|
||||
}
|
||||
end
|
||||
|
||||
local function run(member)
|
||||
local turtle = member.turtle
|
||||
local turtle = member.turtle
|
||||
|
||||
if not turtle.has('plethora:module:2') then
|
||||
error('missing scanner')
|
||||
end
|
||||
turtle.reset()
|
||||
turtle.set({
|
||||
attackPolicy = 'attack',
|
||||
digPolicy = 'turtleSafe',
|
||||
movementStrategy = 'goto',
|
||||
point = member.point,
|
||||
})
|
||||
turtle.select(1)
|
||||
local swapSide = turtle.isEquipped('modem') == 'right' and 'left' or 'right'
|
||||
if not turtle.has('plethora:module:2') then
|
||||
error('missing scanner')
|
||||
end
|
||||
turtle.reset()
|
||||
turtle.set({
|
||||
attackPolicy = 'attack',
|
||||
digPolicy = 'turtleSafe',
|
||||
movementStrategy = 'goto',
|
||||
point = member.point,
|
||||
})
|
||||
turtle.select(1)
|
||||
local swapSide = turtle.isEquipped('modem') == 'right' and 'left' or 'right'
|
||||
|
||||
repeat
|
||||
local pt = getNextPoint(member)
|
||||
if pt then
|
||||
turtle.set({ status = 'Relocating' })
|
||||
turtle.go({ y = pt.y })
|
||||
local c = os.clock()
|
||||
while not turtle.go(pt) do
|
||||
if abort then
|
||||
break
|
||||
end
|
||||
os.sleep(.5)
|
||||
if os.clock() - c > 3 then
|
||||
Sound.play('entity.villager.no')
|
||||
print('stuck: ' .. member.id)
|
||||
turtle.set({ status = 'Stuck' })
|
||||
end
|
||||
end
|
||||
turtle.set({ status = 'Boring' })
|
||||
repeat
|
||||
local pt = getNextPoint(member)
|
||||
if pt then
|
||||
turtle.set({ status = 'Relocating' })
|
||||
turtle.go({ y = pt.y })
|
||||
local c = os.clock()
|
||||
while not turtle.go(pt) do
|
||||
if abort then
|
||||
break
|
||||
end
|
||||
os.sleep(.5)
|
||||
if os.clock() - c > 3 then
|
||||
Sound.play('entity.villager.no')
|
||||
print('stuck: ' .. member.id)
|
||||
turtle.set({ status = 'Stuck' })
|
||||
end
|
||||
end
|
||||
turtle.set({ status = 'Boring' })
|
||||
|
||||
for _, v in ipairs(locations) do
|
||||
if abort then
|
||||
break
|
||||
end
|
||||
turtle.go({ y = v })
|
||||
turtle.equip(swapSide, 'plethora:module:2')
|
||||
local found = turtle.scan(blocks)
|
||||
turtle.equip(swapSide, 'minecraft:diamond_pickaxe')
|
||||
if Util.size(found) > 0 then
|
||||
paused = true
|
||||
local _, b = next(found)
|
||||
print(string.format('%s:%s:%s %s', b.x, b.y, b.z, b.name))
|
||||
print('press r to continue')
|
||||
for _ = 1, 3 do
|
||||
Sound.play('block.note.pling')
|
||||
os.sleep(.3)
|
||||
end
|
||||
end
|
||||
end
|
||||
turtle.go({ y = pt.y })
|
||||
end
|
||||
until abort
|
||||
for _, v in ipairs(locations) do
|
||||
if abort then
|
||||
break
|
||||
end
|
||||
turtle.go({ y = v })
|
||||
turtle.equip(swapSide, 'plethora:module:2')
|
||||
local found = turtle.scan(blocks)
|
||||
turtle.equip(swapSide, 'minecraft:diamond_pickaxe')
|
||||
if Util.size(found) > 0 then
|
||||
paused = true
|
||||
local _, b = next(found)
|
||||
print(string.format('%s:%s:%s %s', b.x, b.y, b.z, b.name))
|
||||
print('press r to continue')
|
||||
for _ = 1, 3 do
|
||||
Sound.play('block.note.pling')
|
||||
os.sleep(.3)
|
||||
end
|
||||
end
|
||||
end
|
||||
turtle.go({ y = pt.y })
|
||||
end
|
||||
until abort
|
||||
|
||||
turtle.set({ status = 'Aborting' })
|
||||
turtle.go({ y = gpt.y + member.index })
|
||||
turtle.go({ x = gpt.x, y = gpt.y + member.index, z = gpt.z })
|
||||
turtle.set({ status = 'Aborting' })
|
||||
turtle.go({ y = gpt.y + member.index })
|
||||
turtle.go({ x = gpt.x, y = gpt.y + member.index, z = gpt.z })
|
||||
|
||||
repeat until turtle.go({ y = gpt.y })
|
||||
turtle.set({ status = 'idle' })
|
||||
repeat until turtle.go({ y = gpt.y })
|
||||
turtle.set({ status = 'idle' })
|
||||
end
|
||||
|
||||
function swarm:onRemove(member, success, message)
|
||||
if not success then
|
||||
Sound.play('entity.villager.no')
|
||||
print('Removed from swarm: ' .. member.id)
|
||||
_G.printError(message)
|
||||
end
|
||||
if not success then
|
||||
Sound.play('entity.villager.no')
|
||||
print('Removed from swarm: ' .. member.id)
|
||||
_G.printError(message)
|
||||
end
|
||||
|
||||
print('Turtles: ' .. Util.size(self.pool))
|
||||
if Util.size(self.pool) == 0 then
|
||||
Event.exitPullEvents()
|
||||
end
|
||||
print('Turtles: ' .. Util.size(self.pool))
|
||||
if Util.size(self.pool) == 0 then
|
||||
Event.exitPullEvents()
|
||||
end
|
||||
end
|
||||
|
||||
print('press a to abort, r to resume')
|
||||
Event.on('char', function(_, k)
|
||||
if k == 'r' then
|
||||
print('Resuming')
|
||||
paused = false
|
||||
elseif k == 'a' then
|
||||
gpt = GPS.getPoint()
|
||||
print('Aborting')
|
||||
abort = true
|
||||
end
|
||||
if k == 'r' then
|
||||
print('Resuming')
|
||||
paused = false
|
||||
elseif k == 'a' then
|
||||
gpt = GPS.getPoint()
|
||||
print('Aborting')
|
||||
abort = true
|
||||
end
|
||||
end)
|
||||
|
||||
getLocations()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,13 @@
|
||||
{
|
||||
[ "58ec8d6e36e346d9f42eb43935652e3e58e2c829" ] = {
|
||||
title = "Mwm",
|
||||
category = "Apps",
|
||||
icon = "\030f\031f \0304 \
|
||||
[ "58ec8d6e36e346d9f42eb43935652e3e58e2c829" ] = {
|
||||
title = "Mwm",
|
||||
category = "Apps",
|
||||
icon = "\030f\031f \0304 \
|
||||
\030f\031dshell]\0304\0314 \
|
||||
\0304\031f ",
|
||||
iconExt = "\030 \031f\0305\031f\155\030f\128\031d\152\140\030d\031f\151\030f\128\128\0304\0314\128\
|
||||
iconExt = "\030 \031f\0305\031f\155\030f\128\031d\152\140\030d\031f\151\030f\128\128\0304\0314\128\
|
||||
\030 \031f\030f\0315\152\129\030d\031f\141\030f\031d\153\030d\031f\149\030f\031d\131\148\0304\0314\128\
|
||||
\030 \031f\0304\031f\131\131\131\131\131\131\131\030e\0314\131",
|
||||
run = "mwm.lua usr/config/mwm",
|
||||
},
|
||||
run = "mwm.lua usr/config/mwm",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -8,76 +8,76 @@ local os = _G.os
|
||||
local remoteId
|
||||
local args = { ... }
|
||||
if #args == 1 then
|
||||
remoteId = tonumber(args[1])
|
||||
remoteId = tonumber(args[1])
|
||||
else
|
||||
print('Enter host ID')
|
||||
remoteId = tonumber(_G.read())
|
||||
print('Enter host ID')
|
||||
remoteId = tonumber(_G.read())
|
||||
end
|
||||
|
||||
if not remoteId then
|
||||
error('Syntax: mirrorClient <host ID>')
|
||||
error('Syntax: mirrorClient <host ID>')
|
||||
end
|
||||
|
||||
local function wrapTerm(socket)
|
||||
local methods = { 'blit', 'clear', 'clearLine', 'setCursorPos', 'write',
|
||||
'setTextColor', 'setTextColour', 'setBackgroundColor',
|
||||
'setBackgroundColour', 'scroll', 'setCursorBlink', }
|
||||
local methods = { 'blit', 'clear', 'clearLine', 'setCursorPos', 'write',
|
||||
'setTextColor', 'setTextColour', 'setBackgroundColor',
|
||||
'setBackgroundColour', 'scroll', 'setCursorBlink', }
|
||||
|
||||
socket.term = multishell.term
|
||||
socket.oldTerm = Util.shallowCopy(socket.term)
|
||||
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
|
||||
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
|
||||
print('connecting...')
|
||||
local socket
|
||||
|
||||
while true do
|
||||
socket = Socket.connect(remoteId, 5901)
|
||||
if socket then
|
||||
break
|
||||
end
|
||||
os.sleep(3)
|
||||
end
|
||||
while true do
|
||||
socket = Socket.connect(remoteId, 5901)
|
||||
if socket then
|
||||
break
|
||||
end
|
||||
os.sleep(3)
|
||||
end
|
||||
|
||||
print('connected')
|
||||
print('connected')
|
||||
|
||||
wrapTerm(socket)
|
||||
wrapTerm(socket)
|
||||
|
||||
os.queueEvent('term_resize')
|
||||
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
|
||||
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
|
||||
for k,v in pairs(socket.oldTerm) do
|
||||
socket.term[k] = v
|
||||
end
|
||||
|
||||
socket:close()
|
||||
socket:close()
|
||||
|
||||
end
|
||||
|
||||
@@ -7,41 +7,41 @@ local term = _G.term
|
||||
local mon = term.current()
|
||||
local args = { ... }
|
||||
if args[1] then
|
||||
mon = _G.device[args[1]]
|
||||
mon = _G.device[args[1]]
|
||||
end
|
||||
|
||||
if not mon then
|
||||
error('Invalid monitor')
|
||||
error('Invalid monitor')
|
||||
end
|
||||
|
||||
mon.setBackgroundColor(colors.black)
|
||||
mon.clear()
|
||||
|
||||
while true do
|
||||
local socket = Socket.server(5901)
|
||||
local socket = Socket.server(5901)
|
||||
|
||||
print('mirror: connection from ' .. socket.dhost)
|
||||
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)
|
||||
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)
|
||||
|
||||
while true do
|
||||
Event.pullEvent()
|
||||
if not socket.connected then
|
||||
break
|
||||
end
|
||||
end
|
||||
while true do
|
||||
Event.pullEvent()
|
||||
if not socket.connected then
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
print('connection lost')
|
||||
print('connection lost')
|
||||
|
||||
socket:close()
|
||||
socket:close()
|
||||
end
|
||||
|
||||
676
monitor/mwm.lua
676
monitor/mwm.lua
@@ -17,8 +17,8 @@ local term = _G.term
|
||||
local window = _G.window
|
||||
|
||||
local function syntax()
|
||||
printError('Syntax:')
|
||||
error('mwm sessionName [monitor]')
|
||||
printError('Syntax:')
|
||||
error('mwm sessionName [monitor]')
|
||||
end
|
||||
|
||||
local args = { ... }
|
||||
@@ -33,9 +33,9 @@ local parentMon
|
||||
local defaultEnv = Util.shallowCopy(_ENV)
|
||||
defaultEnv.multishell = multishell
|
||||
if args[2] then
|
||||
parentMon = peripheral.wrap(args[2]) or syntax()
|
||||
parentMon = peripheral.wrap(args[2]) or syntax()
|
||||
else
|
||||
parentMon = peripheral.find('monitor') or syntax()
|
||||
parentMon = peripheral.find('monitor') or syntax()
|
||||
end
|
||||
|
||||
parentMon.setTextScale(.5)
|
||||
@@ -54,8 +54,8 @@ monitor.setBackgroundColor(colors.gray)
|
||||
monitor.clear()
|
||||
|
||||
local function nextUID()
|
||||
UID = UID + 1
|
||||
return UID
|
||||
UID = UID + 1
|
||||
return UID
|
||||
end
|
||||
|
||||
local function xprun(env, path, ...)
|
||||
@@ -68,471 +68,471 @@ local function xprun(env, path, ...)
|
||||
end
|
||||
|
||||
local function write(win, x, y, text)
|
||||
win.setCursorPos(x, y)
|
||||
win.write(text)
|
||||
win.setCursorPos(x, y)
|
||||
win.write(text)
|
||||
end
|
||||
|
||||
local function redraw()
|
||||
--monitor.clear()
|
||||
monitor.canvas:dirty()
|
||||
--monitor.setBackgroundColor(colors.gray)
|
||||
monitor.canvas:clear(colors.gray)
|
||||
for k, process in ipairs(processes) do
|
||||
process.container.canvas:dirty()
|
||||
process:focus(k == #processes)
|
||||
end
|
||||
--monitor.clear()
|
||||
monitor.canvas:dirty()
|
||||
--monitor.setBackgroundColor(colors.gray)
|
||||
monitor.canvas:clear(colors.gray)
|
||||
for k, process in ipairs(processes) do
|
||||
process.container.canvas:dirty()
|
||||
process:focus(k == #processes)
|
||||
end
|
||||
end
|
||||
|
||||
local function getProcessAt(x, y)
|
||||
for k = #processes, 1, -1 do
|
||||
local process = processes[k]
|
||||
if x >= process.x and
|
||||
y >= process.y and
|
||||
x <= process.x + process.width - 1 and
|
||||
y <= process.y + process.height - 1 then
|
||||
return k, process
|
||||
end
|
||||
end
|
||||
for k = #processes, 1, -1 do
|
||||
local process = processes[k]
|
||||
if x >= process.x and
|
||||
y >= process.y and
|
||||
x <= process.x + process.width - 1 and
|
||||
y <= process.y + process.height - 1 then
|
||||
return k, process
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--[[ A runnable process ]]--
|
||||
local Process = { }
|
||||
|
||||
function Process:new(args)
|
||||
args.env = args.env or Util.shallowCopy(defaultEnv)
|
||||
args.width = args.width or termDim.width
|
||||
args.height = args.height or termDim.height
|
||||
args.env = args.env or Util.shallowCopy(defaultEnv)
|
||||
args.width = args.width or termDim.width
|
||||
args.height = args.height or termDim.height
|
||||
|
||||
-- TODO: randomize start position
|
||||
local self = setmetatable({
|
||||
uid = nextUID(),
|
||||
x = args.x or 1,
|
||||
y = args.y or 1,
|
||||
width = args.width + 2,
|
||||
height = args.height + 3,
|
||||
path = args.path,
|
||||
args = args.args or { },
|
||||
title = args.title or 'shell',
|
||||
}, { __index = Process })
|
||||
-- TODO: randomize start position
|
||||
local self = setmetatable({
|
||||
uid = nextUID(),
|
||||
x = args.x or 1,
|
||||
y = args.y or 1,
|
||||
width = args.width + 2,
|
||||
height = args.height + 3,
|
||||
path = args.path,
|
||||
args = args.args or { },
|
||||
title = args.title or 'shell',
|
||||
}, { __index = Process })
|
||||
|
||||
self:adjustDimensions()
|
||||
if not args.x then
|
||||
self.x = math.random(1, monDim.width - self.width + 1)
|
||||
self.y = math.random(1, monDim.height - self.height + 1)
|
||||
end
|
||||
self:adjustDimensions()
|
||||
if not args.x then
|
||||
self.x = math.random(1, monDim.width - self.width + 1)
|
||||
self.y = math.random(1, monDim.height - self.height + 1)
|
||||
end
|
||||
|
||||
self.container = Terminal.window(monitor, self.x, self.y, self.width, self.height, true)
|
||||
self.window = window.create(self.container, 2, 3, args.width, args.height, true)
|
||||
self.terminal = self.window
|
||||
self.container = Terminal.window(monitor, self.x, self.y, self.width, self.height, true)
|
||||
self.window = window.create(self.container, 2, 3, args.width, args.height, true)
|
||||
self.terminal = self.window
|
||||
|
||||
self.container.canvas.parent = monitor.canvas
|
||||
table.insert(monitor.canvas.layers, 1, self.container.canvas)
|
||||
self.container.canvas:setVisible(true)
|
||||
self.container.canvas.parent = monitor.canvas
|
||||
table.insert(monitor.canvas.layers, 1, self.container.canvas)
|
||||
self.container.canvas:setVisible(true)
|
||||
|
||||
--self.container.getSize = self.window.getSize
|
||||
--self.container.getSize = self.window.getSize
|
||||
|
||||
self.co = coroutine.create(function()
|
||||
local result, err
|
||||
self.co = coroutine.create(function()
|
||||
local result, err
|
||||
|
||||
if args.fn then
|
||||
result, err = Util.runFunction(args.env, args.fn, table.unpack(self.args))
|
||||
elseif args.path then
|
||||
result, err = xprun(args.env, args.path, table.unpack(self.args))
|
||||
end
|
||||
if args.fn then
|
||||
result, err = Util.runFunction(args.env, args.fn, table.unpack(self.args))
|
||||
elseif args.path then
|
||||
result, err = xprun(args.env, args.path, table.unpack(self.args))
|
||||
end
|
||||
|
||||
if not result and err and err ~= 'Terminated' then
|
||||
printError('\n' .. tostring(err))
|
||||
os.pullEventRaw('terminate')
|
||||
end
|
||||
multishell.removeProcess(self)
|
||||
end)
|
||||
if not result and err and err ~= 'Terminated' then
|
||||
printError('\n' .. tostring(err))
|
||||
os.pullEventRaw('terminate')
|
||||
end
|
||||
multishell.removeProcess(self)
|
||||
end)
|
||||
|
||||
self:focus(false)
|
||||
self:focus(false)
|
||||
|
||||
return self
|
||||
return self
|
||||
end
|
||||
|
||||
function Process:focus(focused)
|
||||
if focused then
|
||||
self.container.setBackgroundColor(colors.yellow)
|
||||
else
|
||||
self.container.setBackgroundColor(colors.gray)
|
||||
end
|
||||
self.container.setTextColor(colors.black)
|
||||
write(self.container, 2, 2, string.rep(' ', self.width - 2))
|
||||
write(self.container, 3, 2, self.title)
|
||||
write(self.container, self.width - 2, 2, '*')
|
||||
if focused then
|
||||
self.container.setBackgroundColor(colors.yellow)
|
||||
else
|
||||
self.container.setBackgroundColor(colors.gray)
|
||||
end
|
||||
self.container.setTextColor(colors.black)
|
||||
write(self.container, 2, 2, string.rep(' ', self.width - 2))
|
||||
write(self.container, 3, 2, self.title)
|
||||
write(self.container, self.width - 2, 2, '*')
|
||||
|
||||
if focused then
|
||||
self.window.restoreCursor()
|
||||
elseif self.showSizers then
|
||||
self:drawSizers(false)
|
||||
end
|
||||
if focused then
|
||||
self.window.restoreCursor()
|
||||
elseif self.showSizers then
|
||||
self:drawSizers(false)
|
||||
end
|
||||
end
|
||||
|
||||
function Process:drawSizers(showSizers)
|
||||
local sizeChars = {
|
||||
'\135', '\139', '\141', '\142'
|
||||
}
|
||||
local sizeChars = {
|
||||
'\135', '\139', '\141', '\142'
|
||||
}
|
||||
|
||||
if Util.getVersion() < 1.8 then
|
||||
sizeChars = {
|
||||
'#', '#', '#', '#'
|
||||
}
|
||||
end
|
||||
if Util.getVersion() < 1.8 then
|
||||
sizeChars = {
|
||||
'#', '#', '#', '#'
|
||||
}
|
||||
end
|
||||
|
||||
self.showSizers = showSizers
|
||||
self.showSizers = showSizers
|
||||
|
||||
self.container.setBackgroundColor(colors.black)
|
||||
self.container.setTextColor(colors.white)
|
||||
self.container.setBackgroundColor(colors.black)
|
||||
self.container.setTextColor(colors.white)
|
||||
|
||||
if self.showSizers then
|
||||
write(self.container, 1, 1, sizeChars[1])
|
||||
write(self.container, self.width, 1, sizeChars[2])
|
||||
write(self.container, 1, self.height, sizeChars[3])
|
||||
write(self.container, self.width, self.height, sizeChars[4])
|
||||
if self.showSizers then
|
||||
write(self.container, 1, 1, sizeChars[1])
|
||||
write(self.container, self.width, 1, sizeChars[2])
|
||||
write(self.container, 1, self.height, sizeChars[3])
|
||||
write(self.container, self.width, self.height, sizeChars[4])
|
||||
|
||||
self.container.setTextColor(colors.yellow)
|
||||
write(self.container, 1, 3, '+')
|
||||
write(self.container, 1, 5, '-')
|
||||
write(self.container, 3, 1, '+')
|
||||
write(self.container, 5, 1, '-')
|
||||
self.container.setTextColor(colors.yellow)
|
||||
write(self.container, 1, 3, '+')
|
||||
write(self.container, 1, 5, '-')
|
||||
write(self.container, 3, 1, '+')
|
||||
write(self.container, 5, 1, '-')
|
||||
|
||||
local str = string.format('%d x %d', self.width - 2, self.height - 3)
|
||||
write(self.container, (self.width - #str) / 2, 1, str)
|
||||
local str = string.format('%d x %d', self.width - 2, self.height - 3)
|
||||
write(self.container, (self.width - #str) / 2, 1, str)
|
||||
|
||||
else
|
||||
write(self.container, 1, 1, string.rep(' ', self.width))
|
||||
write(self.container, self.width, 1, ' ')
|
||||
write(self.container, 1, self.height, ' ')
|
||||
write(self.container, self.width, self.height, ' ')
|
||||
write(self.container, 1, 3, ' ')
|
||||
write(self.container, 1, 5, ' ')
|
||||
end
|
||||
else
|
||||
write(self.container, 1, 1, string.rep(' ', self.width))
|
||||
write(self.container, self.width, 1, ' ')
|
||||
write(self.container, 1, self.height, ' ')
|
||||
write(self.container, self.width, self.height, ' ')
|
||||
write(self.container, 1, 3, ' ')
|
||||
write(self.container, 1, 5, ' ')
|
||||
end
|
||||
end
|
||||
|
||||
function Process:adjustDimensions()
|
||||
self.width = math.min(self.width, monDim.width)
|
||||
self.height = math.min(self.height, monDim.height)
|
||||
self.width = math.min(self.width, monDim.width)
|
||||
self.height = math.min(self.height, monDim.height)
|
||||
|
||||
self.x = math.max(1, self.x)
|
||||
self.y = math.max(1, self.y)
|
||||
self.x = math.min(self.x, monDim.width - self.width + 1)
|
||||
self.y = math.min(self.y, monDim.height - self.height + 1)
|
||||
self.x = math.max(1, self.x)
|
||||
self.y = math.max(1, self.y)
|
||||
self.x = math.min(self.x, monDim.width - self.width + 1)
|
||||
self.y = math.min(self.y, monDim.height - self.height + 1)
|
||||
end
|
||||
|
||||
function Process:reposition()
|
||||
self:adjustDimensions()
|
||||
self.container.reposition(self.x, self.y, self.width, self.height)
|
||||
self.container.setBackgroundColor(colors.black)
|
||||
self.container.clear()
|
||||
self.window.reposition(2, 3, self.width - 2, self.height - 3)
|
||||
if self.window ~= self.terminal then
|
||||
self.terminal.reposition(1, 1, self.width - 2, self.height - 3)
|
||||
end
|
||||
redraw()
|
||||
self:adjustDimensions()
|
||||
self.container.reposition(self.x, self.y, self.width, self.height)
|
||||
self.container.setBackgroundColor(colors.black)
|
||||
self.container.clear()
|
||||
self.window.reposition(2, 3, self.width - 2, self.height - 3)
|
||||
if self.window ~= self.terminal then
|
||||
self.terminal.reposition(1, 1, self.width - 2, self.height - 3)
|
||||
end
|
||||
redraw()
|
||||
end
|
||||
|
||||
function Process:click(x, y)
|
||||
if y == 2 then -- title bar
|
||||
if x == self.width - 2 then
|
||||
self:resume('terminate')
|
||||
else
|
||||
self:drawSizers(not self.showSizers)
|
||||
end
|
||||
if y == 2 then -- title bar
|
||||
if x == self.width - 2 then
|
||||
self:resume('terminate')
|
||||
else
|
||||
self:drawSizers(not self.showSizers)
|
||||
end
|
||||
|
||||
elseif x == 1 or y == 1 then -- sizers
|
||||
self:resizeClick(x, y)
|
||||
elseif x == 1 or y == 1 then -- sizers
|
||||
self:resizeClick(x, y)
|
||||
|
||||
elseif x > 1 and x < self.width then
|
||||
if self.showSizers then
|
||||
self:drawSizers(false)
|
||||
end
|
||||
self:resume('mouse_click', 1, x - 1, y - 2)
|
||||
self:resume('mouse_up', 1, x - 1, y - 2)
|
||||
end
|
||||
elseif x > 1 and x < self.width then
|
||||
if self.showSizers then
|
||||
self:drawSizers(false)
|
||||
end
|
||||
self:resume('mouse_click', 1, x - 1, y - 2)
|
||||
self:resume('mouse_up', 1, x - 1, y - 2)
|
||||
end
|
||||
end
|
||||
|
||||
function Process:resizeClick(x, y)
|
||||
if x == 1 and y == 3 then
|
||||
self.height = self.height + 1
|
||||
elseif x == 1 and y == 5 then
|
||||
self.height = self.height - 1
|
||||
elseif x == 3 and y == 1 then
|
||||
self.width = self.width + 1
|
||||
elseif x == 5 and y == 1 then
|
||||
self.width = self.width - 1
|
||||
else
|
||||
return
|
||||
end
|
||||
self:reposition()
|
||||
self:resume('term_resize')
|
||||
self:drawSizers(true)
|
||||
multishell.saveSession(sessionFile)
|
||||
if x == 1 and y == 3 then
|
||||
self.height = self.height + 1
|
||||
elseif x == 1 and y == 5 then
|
||||
self.height = self.height - 1
|
||||
elseif x == 3 and y == 1 then
|
||||
self.width = self.width + 1
|
||||
elseif x == 5 and y == 1 then
|
||||
self.width = self.width - 1
|
||||
else
|
||||
return
|
||||
end
|
||||
self:reposition()
|
||||
self:resume('term_resize')
|
||||
self:drawSizers(true)
|
||||
multishell.saveSession(sessionFile)
|
||||
end
|
||||
|
||||
function Process:resume(event, ...)
|
||||
if coroutine.status(self.co) == 'dead' then
|
||||
return
|
||||
end
|
||||
if coroutine.status(self.co) == 'dead' then
|
||||
return
|
||||
end
|
||||
|
||||
if not self.filter or self.filter == event or event == "terminate" then
|
||||
--term.redirect(self.terminal)
|
||||
local previousTerm = term.redirect(self.terminal)
|
||||
if not self.filter or self.filter == event or event == "terminate" then
|
||||
--term.redirect(self.terminal)
|
||||
local previousTerm = term.redirect(self.terminal)
|
||||
|
||||
local previous = running
|
||||
running = self -- stupid shell set title
|
||||
local ok, result = coroutine.resume(self.co, event, ...)
|
||||
running = previous
|
||||
local previous = running
|
||||
running = self -- stupid shell set title
|
||||
local ok, result = coroutine.resume(self.co, event, ...)
|
||||
running = previous
|
||||
|
||||
self.terminal = term.current()
|
||||
term.redirect(previousTerm)
|
||||
self.terminal = term.current()
|
||||
term.redirect(previousTerm)
|
||||
|
||||
if ok then
|
||||
self.filter = result
|
||||
else
|
||||
printError(result)
|
||||
end
|
||||
return ok, result
|
||||
end
|
||||
if ok then
|
||||
self.filter = result
|
||||
else
|
||||
printError(result)
|
||||
end
|
||||
return ok, result
|
||||
end
|
||||
end
|
||||
|
||||
--[[ Install a multishell manager for the monitor ]]--
|
||||
function multishell.getFocus()
|
||||
return processes[#processes].uid
|
||||
return processes[#processes].uid
|
||||
end
|
||||
|
||||
function multishell.setFocus(uid)
|
||||
local process = Util.find(processes, 'uid', uid)
|
||||
local process = Util.find(processes, 'uid', uid)
|
||||
|
||||
if process then
|
||||
local lastFocused = processes[#processes]
|
||||
if lastFocused ~= process then
|
||||
if process then
|
||||
local lastFocused = processes[#processes]
|
||||
if lastFocused ~= process then
|
||||
|
||||
if lastFocused then
|
||||
lastFocused:focus(false)
|
||||
end
|
||||
if lastFocused then
|
||||
lastFocused:focus(false)
|
||||
end
|
||||
|
||||
Util.removeByValue(processes, process)
|
||||
table.insert(processes, process)
|
||||
Util.removeByValue(processes, process)
|
||||
table.insert(processes, process)
|
||||
|
||||
process.container.canvas:raise()
|
||||
process:focus(true)
|
||||
process.container.canvas:dirty()
|
||||
end
|
||||
return true
|
||||
end
|
||||
return false
|
||||
process.container.canvas:raise()
|
||||
process:focus(true)
|
||||
process.container.canvas:dirty()
|
||||
end
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function multishell.getTitle(uid)
|
||||
local process = Util.find(processes, 'uid', uid)
|
||||
if process then
|
||||
return process.title
|
||||
end
|
||||
local process = Util.find(processes, 'uid', uid)
|
||||
if process then
|
||||
return process.title
|
||||
end
|
||||
end
|
||||
|
||||
function multishell.setTitle(uid, title)
|
||||
local process = Util.find(processes, 'uid', uid)
|
||||
if process then
|
||||
process.title = title or ''
|
||||
process:focus(process == processes[#processes])
|
||||
end
|
||||
local process = Util.find(processes, 'uid', uid)
|
||||
if process then
|
||||
process.title = title or ''
|
||||
process:focus(process == processes[#processes])
|
||||
end
|
||||
end
|
||||
|
||||
function multishell.getCurrent()
|
||||
if running then
|
||||
return running.uid
|
||||
end
|
||||
if running then
|
||||
return running.uid
|
||||
end
|
||||
end
|
||||
|
||||
function multishell.getCount()
|
||||
return #processes
|
||||
return #processes
|
||||
end
|
||||
|
||||
function multishell.getTabs()
|
||||
return processes
|
||||
return processes
|
||||
end
|
||||
|
||||
function multishell.launch(env, file, ...)
|
||||
return multishell.openTab({
|
||||
path = file,
|
||||
env = env,
|
||||
title = 'shell',
|
||||
args = { ... },
|
||||
})
|
||||
return multishell.openTab({
|
||||
path = file,
|
||||
env = env,
|
||||
title = 'shell',
|
||||
args = { ... },
|
||||
})
|
||||
end
|
||||
|
||||
function multishell.openTab(tabInfo)
|
||||
local process = Process:new(tabInfo)
|
||||
local process = Process:new(tabInfo)
|
||||
|
||||
table.insert(processes, 1, process)
|
||||
table.insert(processes, 1, process)
|
||||
|
||||
--local previousTerm = term.current()
|
||||
process:resume()
|
||||
--term.redirect(previousTerm)
|
||||
--local previousTerm = term.current()
|
||||
process:resume()
|
||||
--term.redirect(previousTerm)
|
||||
|
||||
multishell.saveSession(sessionFile)
|
||||
multishell.saveSession(sessionFile)
|
||||
|
||||
return process.uid
|
||||
return process.uid
|
||||
end
|
||||
|
||||
function multishell.removeProcess(process)
|
||||
Util.removeByValue(processes, process)
|
||||
process.container.canvas:removeLayer()
|
||||
Util.removeByValue(processes, process)
|
||||
process.container.canvas:removeLayer()
|
||||
|
||||
multishell.saveSession(sessionFile)
|
||||
redraw()
|
||||
multishell.saveSession(sessionFile)
|
||||
redraw()
|
||||
end
|
||||
|
||||
function multishell.saveSession(filename)
|
||||
local t = { }
|
||||
for _,process in ipairs(processes) do
|
||||
if process.path and not process.isShell then
|
||||
table.insert(t, {
|
||||
x = process.x,
|
||||
y = process.y,
|
||||
width = process.width - 2,
|
||||
height = process.height - 3,
|
||||
path = process.path,
|
||||
args = process.args,
|
||||
})
|
||||
end
|
||||
end
|
||||
Util.writeTable(filename, t)
|
||||
local t = { }
|
||||
for _,process in ipairs(processes) do
|
||||
if process.path and not process.isShell then
|
||||
table.insert(t, {
|
||||
x = process.x,
|
||||
y = process.y,
|
||||
width = process.width - 2,
|
||||
height = process.height - 3,
|
||||
path = process.path,
|
||||
args = process.args,
|
||||
})
|
||||
end
|
||||
end
|
||||
Util.writeTable(filename, t)
|
||||
end
|
||||
|
||||
function multishell.loadSession(filename)
|
||||
local config = Util.readTable(filename)
|
||||
if config then
|
||||
for k = #config, 1, -1 do
|
||||
multishell.openTab(config[k])
|
||||
end
|
||||
end
|
||||
local config = Util.readTable(filename)
|
||||
if config then
|
||||
for k = #config, 1, -1 do
|
||||
multishell.openTab(config[k])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function multishell.stop()
|
||||
multishell._stop = true
|
||||
multishell._stop = true
|
||||
end
|
||||
|
||||
function multishell.start()
|
||||
while not multishell._stop do
|
||||
while not multishell._stop do
|
||||
|
||||
local event = { os.pullEventRaw() }
|
||||
local event = { os.pullEventRaw() }
|
||||
|
||||
if event[1] == 'terminate' then
|
||||
local focused = processes[#processes]
|
||||
if focused.isShell then
|
||||
focused:resume('terminate')
|
||||
else
|
||||
break
|
||||
end
|
||||
if event[1] == 'terminate' then
|
||||
local focused = processes[#processes]
|
||||
if focused.isShell then
|
||||
focused:resume('terminate')
|
||||
else
|
||||
break
|
||||
end
|
||||
|
||||
elseif event[1] == 'monitor_touch' then
|
||||
local x, y = event[3], event[4]
|
||||
elseif event[1] == 'monitor_touch' then
|
||||
local x, y = event[3], event[4]
|
||||
|
||||
local key, process = getProcessAt(x, y)
|
||||
if process then
|
||||
if key ~= #processes then
|
||||
multishell.setFocus(process.uid)
|
||||
multishell.saveSession(sessionFile)
|
||||
end
|
||||
process:click(x - process.x + 1, y - process.y + 1)
|
||||
local key, process = getProcessAt(x, y)
|
||||
if process then
|
||||
if key ~= #processes then
|
||||
multishell.setFocus(process.uid)
|
||||
multishell.saveSession(sessionFile)
|
||||
end
|
||||
process:click(x - process.x + 1, y - process.y + 1)
|
||||
|
||||
else
|
||||
process = processes[#processes]
|
||||
if process and process.showSizers then
|
||||
process.x = math.floor(x - (process.width) / 2)
|
||||
process.y = y
|
||||
process:reposition()
|
||||
process:drawSizers(true)
|
||||
multishell.saveSession(sessionFile)
|
||||
end
|
||||
end
|
||||
else
|
||||
process = processes[#processes]
|
||||
if process and process.showSizers then
|
||||
process.x = math.floor(x - (process.width) / 2)
|
||||
process.y = y
|
||||
process:reposition()
|
||||
process:drawSizers(true)
|
||||
multishell.saveSession(sessionFile)
|
||||
end
|
||||
end
|
||||
|
||||
elseif event[1] == 'mouse_click' or
|
||||
event[1] == 'mouse_up' then
|
||||
elseif event[1] == 'mouse_click' or
|
||||
event[1] == 'mouse_up' then
|
||||
|
||||
local focused = processes[#processes]
|
||||
if not focused.isShell then
|
||||
multishell.setFocus(1) -- shell is always 1
|
||||
else
|
||||
focused:resume(unpack(event))
|
||||
end
|
||||
local focused = processes[#processes]
|
||||
if not focused.isShell then
|
||||
multishell.setFocus(1) -- shell is always 1
|
||||
else
|
||||
focused:resume(unpack(event))
|
||||
end
|
||||
|
||||
elseif event[1] == 'char' or
|
||||
event[1] == 'key' or
|
||||
event[1] == 'key_up' or
|
||||
event[1] == 'paste' then
|
||||
elseif event[1] == 'char' or
|
||||
event[1] == 'key' or
|
||||
event[1] == 'key_up' or
|
||||
event[1] == 'paste' then
|
||||
|
||||
local focused = processes[#processes]
|
||||
if focused then
|
||||
focused:resume(unpack(event))
|
||||
end
|
||||
local focused = processes[#processes]
|
||||
if focused then
|
||||
focused:resume(unpack(event))
|
||||
end
|
||||
|
||||
else
|
||||
for _,process in pairs(Util.shallowCopy(processes)) do
|
||||
process:resume(unpack(event))
|
||||
end
|
||||
end
|
||||
else
|
||||
for _,process in pairs(Util.shallowCopy(processes)) do
|
||||
process:resume(unpack(event))
|
||||
end
|
||||
end
|
||||
|
||||
monitor.canvas:render(parentMon)
|
||||
monitor.canvas:render(parentMon)
|
||||
|
||||
local focused = processes[#processes]
|
||||
if focused then
|
||||
focused.window.restoreCursor()
|
||||
end
|
||||
end
|
||||
local focused = processes[#processes]
|
||||
if focused then
|
||||
focused.window.restoreCursor()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--[[ Special shell process for launching programs ]]--
|
||||
local function addShell()
|
||||
|
||||
local process = setmetatable({
|
||||
x = monDim.width,
|
||||
y = monDim.height,
|
||||
width = 1,
|
||||
height = 1,
|
||||
isShell = true,
|
||||
uid = nextUID(),
|
||||
title = 'Terminal',
|
||||
}, { __index = Process })
|
||||
local process = setmetatable({
|
||||
x = monDim.width,
|
||||
y = monDim.height,
|
||||
width = 1,
|
||||
height = 1,
|
||||
isShell = true,
|
||||
uid = nextUID(),
|
||||
title = 'Terminal',
|
||||
}, { __index = Process })
|
||||
|
||||
function process:focus(focused)
|
||||
self.window.setVisible(focused)
|
||||
if focused then
|
||||
self.window.restoreCursor()
|
||||
else
|
||||
parentTerm.clear()
|
||||
parentTerm.setCursorBlink(false)
|
||||
local str = 'Click screen for shell'
|
||||
write(parentTerm,
|
||||
math.floor((termDim.width - #str) / 2),
|
||||
math.floor(termDim.height / 2),
|
||||
str)
|
||||
end
|
||||
end
|
||||
function process:focus(focused)
|
||||
self.window.setVisible(focused)
|
||||
if focused then
|
||||
self.window.restoreCursor()
|
||||
else
|
||||
parentTerm.clear()
|
||||
parentTerm.setCursorBlink(false)
|
||||
local str = 'Click screen for shell'
|
||||
write(parentTerm,
|
||||
math.floor((termDim.width - #str) / 2),
|
||||
math.floor(termDim.height / 2),
|
||||
str)
|
||||
end
|
||||
end
|
||||
|
||||
function process:click()
|
||||
end
|
||||
function process:click()
|
||||
end
|
||||
|
||||
process.container = Terminal.window(monitor, process.x, process.y+1, process.width, process.height, true)
|
||||
process.window = window.create(parentTerm, 1, 1, termDim.width, termDim.height, true)
|
||||
process.terminal = process.window
|
||||
process.container = Terminal.window(monitor, process.x, process.y+1, process.width, process.height, true)
|
||||
process.window = window.create(parentTerm, 1, 1, termDim.width, termDim.height, true)
|
||||
process.terminal = process.window
|
||||
|
||||
process.co = coroutine.create(function()
|
||||
print('To run a program on the monitor, type "fg <program>"')
|
||||
print('To quit, type "exit"')
|
||||
os.run(Util.shallowCopy(defaultEnv), shell.resolveProgram('shell'))
|
||||
multishell.stop()
|
||||
end)
|
||||
process.co = coroutine.create(function()
|
||||
print('To run a program on the monitor, type "fg <program>"')
|
||||
print('To quit, type "exit"')
|
||||
os.run(Util.shallowCopy(defaultEnv), shell.resolveProgram('shell'))
|
||||
multishell.stop()
|
||||
end)
|
||||
|
||||
table.insert(processes, process)
|
||||
process:focus(true)
|
||||
table.insert(processes, process)
|
||||
process:focus(true)
|
||||
|
||||
local previousTerm = term.current()
|
||||
process:resume()
|
||||
term.redirect(previousTerm)
|
||||
local previousTerm = term.current()
|
||||
process:resume()
|
||||
term.redirect(previousTerm)
|
||||
end
|
||||
|
||||
addShell()
|
||||
|
||||
@@ -3,4 +3,7 @@
|
||||
repository = 'kepler155c/opus-apps/{{OPUS_BRANCH}}/neural',
|
||||
description = [[ Applications using various plethora modules ]],
|
||||
licence = 'MIT',
|
||||
required = {
|
||||
'core',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
local Event = require('event')
|
||||
local itemDB = require('core.itemDB')
|
||||
local UI = require('ui')
|
||||
local Util = require('util')
|
||||
local Event = require('event')
|
||||
local itemDB = require('core.itemDB')
|
||||
local UI = require('ui')
|
||||
local Util = require('util')
|
||||
|
||||
local device = _G.device
|
||||
local gps = _G.gps
|
||||
local device = _G.device
|
||||
local gps = _G.gps
|
||||
local multishell = _ENV.multishell
|
||||
|
||||
local glasses = device['plethora:glasses']
|
||||
local scanner = device['plethora:scanner'] or
|
||||
@@ -14,11 +15,13 @@ local projecting = { }
|
||||
|
||||
local function getPoint()
|
||||
local pt = { gps.locate() }
|
||||
return {
|
||||
x = pt[1],
|
||||
y = pt[2],
|
||||
z = pt[3],
|
||||
}
|
||||
if pt[1] then
|
||||
return {
|
||||
x = pt[1],
|
||||
y = pt[2],
|
||||
z = pt[3],
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
local offset = getPoint()
|
||||
@@ -60,7 +63,7 @@ local page = UI.Page {
|
||||
},
|
||||
sortColumn = 'name',
|
||||
accelerators = {
|
||||
grid_select = 'noop',
|
||||
grid_select = 'inspect',
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -75,21 +78,25 @@ function page:scan()
|
||||
local entry = itemDB:get(table.concat({ b.name, b.metadata }, ':'))
|
||||
if not entry then
|
||||
local meta = scanner.getBlockMeta(b.x, b.y, b.z)
|
||||
entry = itemDB:add({
|
||||
name = meta.name,
|
||||
displayName = meta.displayName,
|
||||
damage = meta.metadata,
|
||||
})
|
||||
if meta.name == b.name and meta.metadata == b.metadata then
|
||||
entry = itemDB:add({
|
||||
name = meta.name,
|
||||
displayName = meta.displayName,
|
||||
damage = meta.metadata,
|
||||
})
|
||||
end
|
||||
end
|
||||
b.key = entry.displayName
|
||||
if acc[b.key] then
|
||||
acc[b.key].count = acc[b.key].count + 1
|
||||
else
|
||||
entry = Util.shallowCopy(entry)
|
||||
entry.lname = entry.displayName:lower()
|
||||
entry.count = 1
|
||||
entry.key = b.key
|
||||
acc[b.key] = entry
|
||||
if entry then
|
||||
b.key = entry.displayName
|
||||
if acc[b.key] then
|
||||
acc[b.key].count = acc[b.key].count + 1
|
||||
else
|
||||
entry = Util.shallowCopy(entry)
|
||||
entry.lname = entry.displayName:lower()
|
||||
entry.count = 1
|
||||
entry.key = b.key
|
||||
acc[b.key] = entry
|
||||
end
|
||||
end
|
||||
throttle()
|
||||
return acc
|
||||
@@ -121,43 +128,45 @@ function page.detail:show(blocks, entry)
|
||||
local scanned = scanner.scan()
|
||||
local pos = getPoint()
|
||||
|
||||
blocks = Util.reduce(scanned, function(acc, b)
|
||||
if b.name == t.name and b.metadata == t.damage then
|
||||
-- track block's world position
|
||||
b.id = table.concat({
|
||||
math.floor(pos.x + b.x),
|
||||
math.floor(pos.y + b.y),
|
||||
math.floor(pos.z + b.z) }, ':')
|
||||
acc[b.id] = b
|
||||
end
|
||||
return acc
|
||||
end, { })
|
||||
|
||||
for _, b in pairs(blocks) do
|
||||
if not projecting[b.id] then
|
||||
projecting[b.id] = b
|
||||
pcall(function()
|
||||
b.box = canvas.addItem({
|
||||
pos.x - offset.x + b.x + -(pos.x % 1) + .5,
|
||||
pos.y - offset.y + b.y + -(pos.y % 1) + .5,
|
||||
pos.z - offset.z + b.z + -(pos.z % 1) + .5 },
|
||||
b.name, b.damage, .5)
|
||||
end)
|
||||
if not b.box then
|
||||
b.box = canvas.addBox(
|
||||
pos.x - offset.x + b.x + -(pos.x % 1) + .25,
|
||||
pos.y - offset.y + b.y + -(pos.y % 1) + .25,
|
||||
pos.z - offset.z + b.z + -(pos.z % 1) + .25,
|
||||
.5, .5, .5)
|
||||
if pos and offset then
|
||||
blocks = Util.reduce(scanned, function(acc, b)
|
||||
if b.name == t.name and b.metadata == t.damage then
|
||||
-- track block's world position
|
||||
b.id = table.concat({
|
||||
math.floor(pos.x + b.x),
|
||||
math.floor(pos.y + b.y),
|
||||
math.floor(pos.z + b.z) }, ':')
|
||||
acc[b.id] = b
|
||||
end
|
||||
b.box.setDepthTested(false)
|
||||
end
|
||||
end
|
||||
return acc
|
||||
end, { })
|
||||
|
||||
for _, b in pairs(projecting) do
|
||||
if not blocks[b.id] then
|
||||
b.box.remove()
|
||||
projecting[b.id] = nil
|
||||
for _, b in pairs(blocks) do
|
||||
if not projecting[b.id] then
|
||||
projecting[b.id] = b
|
||||
pcall(function()
|
||||
b.box = canvas.addItem({
|
||||
pos.x - offset.x + b.x + -(pos.x % 1) + .5,
|
||||
pos.y - offset.y + b.y + -(pos.y % 1) + .5,
|
||||
pos.z - offset.z + b.z + -(pos.z % 1) + .5 },
|
||||
b.name, b.damage, .5)
|
||||
end)
|
||||
if not b.box then
|
||||
b.box = canvas.addBox(
|
||||
pos.x - offset.x + b.x + -(pos.x % 1) + .25,
|
||||
pos.y - offset.y + b.y + -(pos.y % 1) + .25,
|
||||
pos.z - offset.z + b.z + -(pos.z % 1) + .25,
|
||||
.5, .5, .5)
|
||||
end
|
||||
b.box.setDepthTested(false)
|
||||
end
|
||||
end
|
||||
|
||||
for _, b in pairs(projecting) do
|
||||
if not blocks[b.id] then
|
||||
b.box.remove()
|
||||
projecting[b.id] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -178,7 +187,14 @@ function page:eventHandler(event)
|
||||
elseif event.type == 'scan' then
|
||||
self:scan()
|
||||
|
||||
elseif event.type == 'grid_select' and event.element == page.grid then
|
||||
elseif event.type == 'grid_select' and event.element == self.detail.grid then
|
||||
multishell.openTab({
|
||||
path = 'sys/apps/Lua.lua',
|
||||
args = { event.selected },
|
||||
focused = true,
|
||||
})
|
||||
|
||||
elseif event.type == 'grid_select' and event.element == self.grid then
|
||||
self.detail:show(self.blocks, self.grid:getSelected())
|
||||
|
||||
elseif event.type == 'cancel' then
|
||||
|
||||
@@ -18,22 +18,13 @@ local projecting = { }
|
||||
local offset
|
||||
local canvas = glasses and intro and glasses.canvas3d().create()
|
||||
|
||||
local config = Config.load('Sensor', {
|
||||
ignore = { }
|
||||
})
|
||||
local config = Config.load('Sensor')
|
||||
|
||||
local page = UI.Page {
|
||||
tabs = UI.Tabs {
|
||||
listing = UI.Tab {
|
||||
tabTitle = 'Listing',
|
||||
menuBar = UI.MenuBar {
|
||||
buttons = {
|
||||
{ text = 'Ignore', event = 'ignore' },
|
||||
{ text = 'Details', event = 'detail' },
|
||||
},
|
||||
},
|
||||
grid = UI.ScrollingGrid {
|
||||
y = 2,
|
||||
columns = {
|
||||
{ heading = 'Name', key = 'displayName' },
|
||||
{ heading = 'X', key = 'x', width = 3, align = 'right' },
|
||||
@@ -45,14 +36,7 @@ local page = UI.Page {
|
||||
},
|
||||
summary = UI.Tab {
|
||||
tabTitle = 'Summary',
|
||||
menuBar = UI.MenuBar {
|
||||
buttons = {
|
||||
{ text = 'Projector', event = 'project' },
|
||||
{ text = 'Ignore', event = 'ignore' },
|
||||
},
|
||||
},
|
||||
grid = UI.ScrollingGrid {
|
||||
y = 2,
|
||||
columns = {
|
||||
{ heading = 'Name', key = 'displayName' },
|
||||
{ heading = 'Count', key = 'count', width = 5, align = 'right' },
|
||||
@@ -134,13 +118,6 @@ local function project(entities)
|
||||
end
|
||||
end
|
||||
|
||||
local function ignoreEntity(entity)
|
||||
if entity then
|
||||
config.ignore[entity.name] = true
|
||||
Config.update('Sensor', config)
|
||||
end
|
||||
end
|
||||
|
||||
function detail:enable(entity)
|
||||
local function update()
|
||||
local t = { }
|
||||
@@ -194,8 +171,6 @@ end
|
||||
function listing:enable()
|
||||
self.handler = Event.onInterval(.5, function()
|
||||
local entities = sensor.sense()
|
||||
Util.filterInplace(entities, function(e) return not config.ignore[e.name] end)
|
||||
|
||||
self.grid:setValues(entities)
|
||||
self.grid:draw()
|
||||
self:sync()
|
||||
@@ -209,14 +184,11 @@ function listing:disable()
|
||||
end
|
||||
|
||||
function listing:eventHandler(event)
|
||||
if event.type == 'detail' or event.type == 'grid_select' then
|
||||
if event.type == 'grid_select' then
|
||||
local selected = self.grid:getSelected()
|
||||
if selected then
|
||||
UI:setPage(detail, selected)
|
||||
end
|
||||
|
||||
elseif event.type == 'ignore' then
|
||||
ignoreEntity(self.grid:getSelected())
|
||||
end
|
||||
|
||||
return UI.Tab.eventHandler(self, event)
|
||||
@@ -225,7 +197,6 @@ end
|
||||
function summary:enable()
|
||||
self.handler = Event.onInterval(.5, function()
|
||||
local entities = sensor.sense()
|
||||
Util.filterInplace(entities, function(e) return not config.ignore[e.name] end)
|
||||
|
||||
local t = { }
|
||||
local highlight = { }
|
||||
@@ -265,10 +236,7 @@ function summary.grid:getRowTextColor(row, selected)
|
||||
end
|
||||
|
||||
function summary:eventHandler(event)
|
||||
if event.type == 'ignore' then
|
||||
ignoreEntity(self.grid:getSelected())
|
||||
|
||||
elseif event.type == 'project' or event.type == 'grid_select' then
|
||||
if event.type == 'grid_select' then
|
||||
local selected = self.grid:getSelected()
|
||||
if selected then
|
||||
self.target = selected.name
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
local Angle = { }
|
||||
|
||||
function Angle.towards(x, y, z)
|
||||
return math.deg(math.atan2(-x, z)), math.deg(-math.atan2(y, math.sqrt(x * x + z * z)))
|
||||
return math.deg(math.atan2(-x, z)), math.deg(-math.atan2(y, math.sqrt(x * x + z * z)))
|
||||
end
|
||||
|
||||
function Angle.away(x, y, z)
|
||||
return math.deg(math.atan2(x, -z)), 0
|
||||
return math.deg(math.atan2(x, -z)), 0
|
||||
end
|
||||
|
||||
return Angle
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
local Mobs = { }
|
||||
|
||||
local hostiles = {
|
||||
BabySkeleton = true,
|
||||
BabyZombie = true,
|
||||
Bat = true,
|
||||
Blaze = true,
|
||||
CaveSpider = true,
|
||||
Creeper = true,
|
||||
Ghast = true,
|
||||
Husk = true,
|
||||
LavaSlime = true,
|
||||
PigZombie = true,
|
||||
Skeleton = true,
|
||||
Slime = true,
|
||||
Spider = true,
|
||||
Witch = true,
|
||||
WitherSkeleton = true,
|
||||
Zombie = true,
|
||||
ZombieVillager = true,
|
||||
BabySkeleton = true,
|
||||
BabyZombie = true,
|
||||
Bat = true,
|
||||
Blaze = true,
|
||||
CaveSpider = true,
|
||||
Creeper = true,
|
||||
Ghast = true,
|
||||
Husk = true,
|
||||
LavaSlime = true,
|
||||
PigZombie = true,
|
||||
Skeleton = true,
|
||||
Slime = true,
|
||||
Spider = true,
|
||||
Witch = true,
|
||||
WitherSkeleton = true,
|
||||
Zombie = true,
|
||||
ZombieVillager = true,
|
||||
}
|
||||
|
||||
function Mobs.getNames()
|
||||
return hostiles
|
||||
return hostiles
|
||||
end
|
||||
|
||||
return Mobs
|
||||
|
||||
@@ -7,149 +7,149 @@ local NONE = "none"
|
||||
local ASYNC = "async"
|
||||
|
||||
local function call_handler(handler, params)
|
||||
if handler then
|
||||
return handler(unpack(params))
|
||||
end
|
||||
if handler then
|
||||
return handler(unpack(params))
|
||||
end
|
||||
end
|
||||
|
||||
local function create_transition(name)
|
||||
local can, to, from, params
|
||||
local can, to, from, params
|
||||
|
||||
local function transition(self, ...)
|
||||
if self.asyncState == NONE then
|
||||
can, to = self:can(name)
|
||||
from = self.current
|
||||
params = { self, name, from, to, ...}
|
||||
local function transition(self, ...)
|
||||
if self.asyncState == NONE then
|
||||
can, to = self:can(name)
|
||||
from = self.current
|
||||
params = { self, name, from, to, ...}
|
||||
|
||||
if not can then return false end
|
||||
self.currentTransitioningEvent = name
|
||||
if not can then return false end
|
||||
self.currentTransitioningEvent = name
|
||||
|
||||
local beforeReturn = call_handler(self["onbefore" .. name], params)
|
||||
local leaveReturn = call_handler(self["onleave" .. from], params)
|
||||
local beforeReturn = call_handler(self["onbefore" .. name], params)
|
||||
local leaveReturn = call_handler(self["onleave" .. from], params)
|
||||
|
||||
if beforeReturn == false or leaveReturn == false then
|
||||
return false
|
||||
end
|
||||
if beforeReturn == false or leaveReturn == false then
|
||||
return false
|
||||
end
|
||||
|
||||
self.asyncState = name .. "WaitingOnLeave"
|
||||
self.asyncState = name .. "WaitingOnLeave"
|
||||
|
||||
if leaveReturn ~= ASYNC then
|
||||
transition(self, ...)
|
||||
end
|
||||
if leaveReturn ~= ASYNC then
|
||||
transition(self, ...)
|
||||
end
|
||||
|
||||
return true
|
||||
elseif self.asyncState == name .. "WaitingOnLeave" then
|
||||
self.current = to
|
||||
return true
|
||||
elseif self.asyncState == name .. "WaitingOnLeave" then
|
||||
self.current = to
|
||||
|
||||
local enterReturn = call_handler(self["onenter" .. to] or self["on" .. to], params)
|
||||
local enterReturn = call_handler(self["onenter" .. to] or self["on" .. to], params)
|
||||
|
||||
self.asyncState = name .. "WaitingOnEnter"
|
||||
self.asyncState = name .. "WaitingOnEnter"
|
||||
|
||||
if enterReturn ~= ASYNC then
|
||||
transition(self, ...)
|
||||
end
|
||||
if enterReturn ~= ASYNC then
|
||||
transition(self, ...)
|
||||
end
|
||||
|
||||
return true
|
||||
elseif self.asyncState == name .. "WaitingOnEnter" then
|
||||
call_handler(self["onafter" .. name] or self["on" .. name], params)
|
||||
call_handler(self["onstatechange"], params)
|
||||
self.asyncState = NONE
|
||||
self.currentTransitioningEvent = nil
|
||||
return true
|
||||
else
|
||||
if string.find(self.asyncState, "WaitingOnLeave") or string.find(self.asyncState, "WaitingOnEnter") then
|
||||
self.asyncState = NONE
|
||||
transition(self, ...)
|
||||
return true
|
||||
end
|
||||
end
|
||||
return true
|
||||
elseif self.asyncState == name .. "WaitingOnEnter" then
|
||||
call_handler(self["onafter" .. name] or self["on" .. name], params)
|
||||
call_handler(self["onstatechange"], params)
|
||||
self.asyncState = NONE
|
||||
self.currentTransitioningEvent = nil
|
||||
return true
|
||||
else
|
||||
if string.find(self.asyncState, "WaitingOnLeave") or string.find(self.asyncState, "WaitingOnEnter") then
|
||||
self.asyncState = NONE
|
||||
transition(self, ...)
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
self.currentTransitioningEvent = nil
|
||||
return false
|
||||
end
|
||||
self.currentTransitioningEvent = nil
|
||||
return false
|
||||
end
|
||||
|
||||
return transition
|
||||
return transition
|
||||
end
|
||||
|
||||
local function add_to_map(map, event)
|
||||
if type(event.from) == 'string' then
|
||||
map[event.from] = event.to
|
||||
else
|
||||
for _, from in ipairs(event.from) do
|
||||
map[from] = event.to
|
||||
end
|
||||
end
|
||||
if type(event.from) == 'string' then
|
||||
map[event.from] = event.to
|
||||
else
|
||||
for _, from in ipairs(event.from) do
|
||||
map[from] = event.to
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function machine.create(options)
|
||||
assert(options.events)
|
||||
assert(options.events)
|
||||
|
||||
local fsm = {}
|
||||
setmetatable(fsm, machine)
|
||||
local fsm = {}
|
||||
setmetatable(fsm, machine)
|
||||
|
||||
fsm.options = options
|
||||
fsm.current = options.initial or 'none'
|
||||
fsm.asyncState = NONE
|
||||
fsm.events = {}
|
||||
fsm.options = options
|
||||
fsm.current = options.initial or 'none'
|
||||
fsm.asyncState = NONE
|
||||
fsm.events = {}
|
||||
|
||||
for _, event in ipairs(options.events or {}) do
|
||||
local name = event.name
|
||||
fsm[name] = fsm[name] or create_transition(name)
|
||||
fsm.events[name] = fsm.events[name] or { map = {} }
|
||||
add_to_map(fsm.events[name].map, event)
|
||||
end
|
||||
for _, event in ipairs(options.events or {}) do
|
||||
local name = event.name
|
||||
fsm[name] = fsm[name] or create_transition(name)
|
||||
fsm.events[name] = fsm.events[name] or { map = {} }
|
||||
add_to_map(fsm.events[name].map, event)
|
||||
end
|
||||
|
||||
for name, callback in pairs(options.callbacks or {}) do
|
||||
fsm[name] = callback
|
||||
end
|
||||
for name, callback in pairs(options.callbacks or {}) do
|
||||
fsm[name] = callback
|
||||
end
|
||||
|
||||
return fsm
|
||||
return fsm
|
||||
end
|
||||
|
||||
function machine:is(state)
|
||||
return self.current == state
|
||||
return self.current == state
|
||||
end
|
||||
|
||||
function machine:can(e)
|
||||
local event = self.events[e]
|
||||
local to = event and event.map[self.current] or event.map['*']
|
||||
return to ~= nil, to
|
||||
local event = self.events[e]
|
||||
local to = event and event.map[self.current] or event.map['*']
|
||||
return to ~= nil, to
|
||||
end
|
||||
|
||||
function machine:cannot(e)
|
||||
return not self:can(e)
|
||||
return not self:can(e)
|
||||
end
|
||||
|
||||
function machine:todot(filename)
|
||||
local dotfile = io.open(filename,'w')
|
||||
dotfile:write('digraph {\n')
|
||||
local transition = function(event,from,to)
|
||||
dotfile:write(string.format('%s -> %s [label=%s];\n',from,to,event))
|
||||
end
|
||||
for _, event in pairs(self.options.events) do
|
||||
if type(event.from) == 'table' then
|
||||
for _, from in ipairs(event.from) do
|
||||
transition(event.name,from,event.to)
|
||||
end
|
||||
else
|
||||
transition(event.name,event.from,event.to)
|
||||
end
|
||||
end
|
||||
dotfile:write('}\n')
|
||||
dotfile:close()
|
||||
local dotfile = io.open(filename,'w')
|
||||
dotfile:write('digraph {\n')
|
||||
local transition = function(event,from,to)
|
||||
dotfile:write(string.format('%s -> %s [label=%s];\n',from,to,event))
|
||||
end
|
||||
for _, event in pairs(self.options.events) do
|
||||
if type(event.from) == 'table' then
|
||||
for _, from in ipairs(event.from) do
|
||||
transition(event.name,from,event.to)
|
||||
end
|
||||
else
|
||||
transition(event.name,event.from,event.to)
|
||||
end
|
||||
end
|
||||
dotfile:write('}\n')
|
||||
dotfile:close()
|
||||
end
|
||||
|
||||
function machine:transition(event)
|
||||
if self.currentTransitioningEvent == event then
|
||||
return self[self.currentTransitioningEvent](self)
|
||||
end
|
||||
if self.currentTransitioningEvent == event then
|
||||
return self[self.currentTransitioningEvent](self)
|
||||
end
|
||||
end
|
||||
|
||||
function machine:cancelTransition(event)
|
||||
if self.currentTransitioningEvent == event then
|
||||
self.asyncState = NONE
|
||||
self.currentTransitioningEvent = nil
|
||||
end
|
||||
if self.currentTransitioningEvent == event then
|
||||
self.asyncState = NONE
|
||||
self.currentTransitioningEvent = nil
|
||||
end
|
||||
end
|
||||
|
||||
machine.NONE = NONE
|
||||
|
||||
@@ -3,28 +3,28 @@ local GPS = require('gps')
|
||||
local device = _G.device
|
||||
|
||||
if device.neuralInterface and device.wireless_modem then
|
||||
local ni = require('neural.interface')
|
||||
device.neuralInterface.goTo = function(x, y, z)
|
||||
local pt = GPS.locate(2)
|
||||
if pt then
|
||||
return pcall(function()
|
||||
if device.neuralInterface.walk then
|
||||
local gpt = {
|
||||
x = x - pt.x,
|
||||
y = 0,
|
||||
z = z - pt.z,
|
||||
}
|
||||
gpt.x = math.min(math.max(gpt.x, -15), 15)
|
||||
gpt.z = math.min(math.max(gpt.z, -15), 15)
|
||||
return device.neuralInterface.walk(gpt.x, gpt.y, gpt.z, 2)
|
||||
elseif ni.launch then
|
||||
local y, p = ni.yap(pt, { x = x, y = y + 3, z = z })
|
||||
ni.look(y, 0)
|
||||
return ni.launch(y, p, 1.5)
|
||||
end
|
||||
end)
|
||||
end
|
||||
return false, 'No GPS'
|
||||
end
|
||||
local ni = require('neural.interface')
|
||||
device.neuralInterface.goTo = function(x, y, z)
|
||||
local pt = GPS.locate(2)
|
||||
if pt then
|
||||
return pcall(function()
|
||||
if device.neuralInterface.walk then
|
||||
local gpt = {
|
||||
x = x - pt.x,
|
||||
y = 0,
|
||||
z = z - pt.z,
|
||||
}
|
||||
gpt.x = math.min(math.max(gpt.x, -15), 15)
|
||||
gpt.z = math.min(math.max(gpt.z, -15), 15)
|
||||
return device.neuralInterface.walk(gpt.x, gpt.y, gpt.z, 2)
|
||||
elseif ni.launch then
|
||||
local y, p = ni.yap(pt, { x = x, y = y + 3, z = z })
|
||||
ni.look(y, 0)
|
||||
return ni.launch(y, p, 1.5)
|
||||
end
|
||||
end)
|
||||
end
|
||||
return false, 'No GPS'
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,70 +1,70 @@
|
||||
local opus = {
|
||||
'fffff00',
|
||||
'ffff07000',
|
||||
'ff00770b00 4444',
|
||||
'ff077777444444444',
|
||||
'f07777744444444444',
|
||||
'f0000777444444444',
|
||||
'070000111744444',
|
||||
'777770000',
|
||||
'7777000000',
|
||||
'70700000000',
|
||||
'077000000000',
|
||||
'fffff00',
|
||||
'ffff07000',
|
||||
'ff00770b00 4444',
|
||||
'ff077777444444444',
|
||||
'f07777744444444444',
|
||||
'f0000777444444444',
|
||||
'070000111744444',
|
||||
'777770000',
|
||||
'7777000000',
|
||||
'70700000000',
|
||||
'077000000000',
|
||||
}
|
||||
|
||||
local hex = {
|
||||
['0'] = 0xF0F0F04F,
|
||||
['1'] = 0xF2B2334F,
|
||||
['2'] = 0xE57FD84F,
|
||||
['3'] = 0x99B2F24F,
|
||||
['4'] = 0xDEDE6C4F,
|
||||
['5'] = 0x7FCC194F,
|
||||
['6'] = 0xF2B2CC4F,
|
||||
['7'] = 0x4C4C4C4F,
|
||||
['8'] = 0x9999994F,
|
||||
['9'] = 0x4C99B24F,
|
||||
['a'] = 0xB266E54F,
|
||||
['b'] = 0x3366CC4F,
|
||||
['c'] = 0x7F664C4F,
|
||||
['d'] = 0x57A64E4F,
|
||||
['e'] = 0xCC4C4C4F,
|
||||
['0'] = 0xF0F0F04F,
|
||||
['1'] = 0xF2B2334F,
|
||||
['2'] = 0xE57FD84F,
|
||||
['3'] = 0x99B2F24F,
|
||||
['4'] = 0xDEDE6C4F,
|
||||
['5'] = 0x7FCC194F,
|
||||
['6'] = 0xF2B2CC4F,
|
||||
['7'] = 0x4C4C4C4F,
|
||||
['8'] = 0x9999994F,
|
||||
['9'] = 0x4C99B24F,
|
||||
['a'] = 0xB266E54F,
|
||||
['b'] = 0x3366CC4F,
|
||||
['c'] = 0x7F664C4F,
|
||||
['d'] = 0x57A64E4F,
|
||||
['e'] = 0xCC4C4C4F,
|
||||
-- ['f'] = 0x191919FF, -- transparent
|
||||
}
|
||||
|
||||
local function update()
|
||||
local canvas = device['plethora:glasses'] and device['plethora:glasses'].canvas()
|
||||
if canvas then
|
||||
local Tween = require('ui.tween')
|
||||
local canvas = device['plethora:glasses'] and device['plethora:glasses'].canvas()
|
||||
if canvas then
|
||||
local Tween = require('ui.tween')
|
||||
|
||||
canvas.clear()
|
||||
local w, h = canvas.getSize()
|
||||
local pos = { x = w / 2, y = h / 2 - 30 }
|
||||
local group = canvas.addGroup(pos)
|
||||
local function drawLine(k, line)
|
||||
for i = 1, #line do
|
||||
local pix = hex[line:sub(i, i)]
|
||||
if pix then
|
||||
group.addRectangle(i*1.5, k*2.25, 1.5, 2.25, pix)
|
||||
end
|
||||
end
|
||||
end
|
||||
canvas.clear()
|
||||
local w, h = canvas.getSize()
|
||||
local pos = { x = w / 2, y = h / 2 - 30 }
|
||||
local group = canvas.addGroup(pos)
|
||||
local function drawLine(k, line)
|
||||
for i = 1, #line do
|
||||
local pix = hex[line:sub(i, i)]
|
||||
if pix then
|
||||
group.addRectangle(i*1.5, k*2.25, 1.5, 2.25, pix)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for k,line in ipairs(opus) do
|
||||
drawLine(k, line)
|
||||
end
|
||||
os.sleep(.5)
|
||||
local tween = Tween.new(40, pos, { x = w - 60, y = h - 30 }, 'outBounce')
|
||||
repeat
|
||||
local finished = tween:update(1)
|
||||
os.sleep(0)
|
||||
group.setPosition(pos.x, pos.y)
|
||||
until finished
|
||||
end
|
||||
for k,line in ipairs(opus) do
|
||||
drawLine(k, line)
|
||||
end
|
||||
os.sleep(.5)
|
||||
local tween = Tween.new(40, pos, { x = w - 60, y = h - 30 }, 'outBounce')
|
||||
repeat
|
||||
local finished = tween:update(1)
|
||||
os.sleep(0)
|
||||
group.setPosition(pos.x, pos.y)
|
||||
until finished
|
||||
end
|
||||
end
|
||||
|
||||
kernel.run({
|
||||
title = 'opus',
|
||||
env = _ENV,
|
||||
hidden = true,
|
||||
fn = update,
|
||||
title = 'opus',
|
||||
env = _ENV,
|
||||
hidden = true,
|
||||
fn = update,
|
||||
})
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user