spaces->tab, equipper improvements, supertreefarm rewrite, follow improvements, sensor cleanup, milo multiple items allowed in recipes, remote canvas access

This commit is contained in:
kepler155c@gmail.com
2019-06-18 15:23:20 -04:00
parent 3b9b509429
commit 045b32884f
162 changed files with 20448 additions and 20286 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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")

View File

@@ -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()

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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
View 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

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
====

View File

@@ -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

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
--[[

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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')

View File

@@ -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

View File

@@ -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')

View File

@@ -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')

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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",
},
},
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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 })

View File

@@ -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)

View File

@@ -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 }

View File

@@ -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 }

View File

@@ -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 }

View File

@@ -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 }

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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,
}

View File

@@ -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 {

View File

@@ -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 })

View File

@@ -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 } }
}

View File

@@ -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)

View File

@@ -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 })

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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",
},
}

View File

@@ -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

View File

@@ -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

View File

@@ -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()

View File

@@ -3,4 +3,7 @@
repository = 'kepler155c/opus-apps/{{OPUS_BRANCH}}/neural',
description = [[ Applications using various plethora modules ]],
licence = 'MIT',
required = {
'core',
},
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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