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 alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
local function sixBitToBase64(input) local function sixBitToBase64(input)
return _sub(alphabet, input+1, input+1) return _sub(alphabet, input+1, input+1)
end end
local function base64ToSixBit(input) local function base64ToSixBit(input)
for i=1, 64 do for i=1, 64 do
if input == _sub(alphabet, i, i) then if input == _sub(alphabet, i, i) then
return i-1 return i-1
end end
end end
end end
local function octetToBase64(o1, o2, o3) local function octetToBase64(o1, o2, o3)
local i1 = sixBitToBase64(_brshift(_band(o1, 0xFC), 2)) local i1 = sixBitToBase64(_brshift(_band(o1, 0xFC), 2))
local i2 local i2
local i3 = "=" local i3 = "="
local i4 = "=" local i4 = "="
if o2 then if o2 then
i2 = sixBitToBase64(_bor( _blshift(_band(o1, 3), 4), _brshift(_band(o2, 0xF0), 4) )) i2 = sixBitToBase64(_bor( _blshift(_band(o1, 3), 4), _brshift(_band(o2, 0xF0), 4) ))
if not o3 then if not o3 then
i3 = sixBitToBase64(_blshift(_band(o2, 0x0F), 2)) i3 = sixBitToBase64(_blshift(_band(o2, 0x0F), 2))
else else
i3 = sixBitToBase64(_bor( _blshift(_band(o2, 0x0F), 2), _brshift(_band(o3, 0xC0), 6) )) i3 = sixBitToBase64(_bor( _blshift(_band(o2, 0x0F), 2), _brshift(_band(o3, 0xC0), 6) ))
end end
else else
i2 = sixBitToBase64(_blshift(_band(o1, 3), 4)) i2 = sixBitToBase64(_blshift(_band(o1, 3), 4))
end end
if o3 then if o3 then
i4 = sixBitToBase64(_band(o3, 0x3F)) i4 = sixBitToBase64(_band(o3, 0x3F))
end end
return i1..i2..i3..i4 return i1..i2..i3..i4
end end
-- octet 1 needs characters 1/2 -- octet 1 needs characters 1/2
@@ -53,61 +53,61 @@ end
-- octet 3 needs characters 3/4 -- octet 3 needs characters 3/4
local function base64ToThreeOctet(s1) local function base64ToThreeOctet(s1)
local c1 = base64ToSixBit(_sub(s1, 1, 1)) local c1 = base64ToSixBit(_sub(s1, 1, 1))
local c2 = base64ToSixBit(_sub(s1, 2, 2)) local c2 = base64ToSixBit(_sub(s1, 2, 2))
local c3 local c3
local c4 local c4
local o1 local o1
local o2 local o2
local o3 local o3
if _sub(s1, 3, 3) == "=" then if _sub(s1, 3, 3) == "=" then
c3 = nil c3 = nil
c4 = nil c4 = nil
elseif _sub(s1, 4, 4) == "=" then elseif _sub(s1, 4, 4) == "=" then
c3 = base64ToSixBit(_sub(s1, 3, 3)) c3 = base64ToSixBit(_sub(s1, 3, 3))
c4 = nil c4 = nil
else else
c3 = base64ToSixBit(_sub(s1, 3, 3)) c3 = base64ToSixBit(_sub(s1, 3, 3))
c4 = base64ToSixBit(_sub(s1, 4, 4)) c4 = base64ToSixBit(_sub(s1, 4, 4))
end end
o1 = _bor( _blshift(c1, 2), _brshift(_band( c2, 0x30 ), 4) ) o1 = _bor( _blshift(c1, 2), _brshift(_band( c2, 0x30 ), 4) )
if c3 then if c3 then
o2 = _bor( _blshift(_band(c2, 0x0F), 4), _brshift(_band( c3, 0x3C ), 2) ) o2 = _bor( _blshift(_band(c2, 0x0F), 4), _brshift(_band( c3, 0x3C ), 2) )
else else
o2 = nil o2 = nil
end end
if c4 then if c4 then
o3 = _bor( _blshift(_band(c3, 3), 6), c4 ) o3 = _bor( _blshift(_band(c3, 3), 6), c4 )
else else
o3 = nil o3 = nil
end end
return o1, o2, o3 return o1, o2, o3
end end
local function splitIntoBlocks(bytes) local function splitIntoBlocks(bytes)
local blockNum = 1 local blockNum = 1
local blocks = {} local blocks = {}
for i=1, #bytes, 3 do for i=1, #bytes, 3 do
blocks[blockNum] = {bytes[i], bytes[i+1], bytes[i+2]} blocks[blockNum] = {bytes[i], bytes[i+1], bytes[i+2]}
--[[ --[[
if #blocks[blockNum] < 3 then if #blocks[blockNum] < 3 then
for j=#blocks[blockNum]+1, 3 do for j=#blocks[blockNum]+1, 3 do
table.insert(blocks[blockNum], 0) table.insert(blocks[blockNum], 0)
end end
end end
]] ]]
blockNum = blockNum+1 blockNum = blockNum+1
end end
return blocks return blocks
end end
function Base64.encode(bytes) function Base64.encode(bytes)
local blocks = splitIntoBlocks(bytes) local blocks = splitIntoBlocks(bytes)
local output = "" local output = ""
for i=1, #blocks do for i=1, #blocks do
output = output..octetToBase64( unpack(blocks[i]) ) output = output..octetToBase64( unpack(blocks[i]) )
end end
return output return output
end end
local function Throttle() local function Throttle()
@@ -123,32 +123,32 @@ local function Throttle()
end end
function Base64.decode(str) function Base64.decode(str)
local bytes = {} local bytes = {}
local blocks = {} local blocks = {}
local blockNum = 1 local blockNum = 1
local throttle = Throttle() local throttle = Throttle()
for i=1, #str, 4 do for i=1, #str, 4 do
blocks[blockNum] = _sub(str, i, i+3) blocks[blockNum] = _sub(str, i, i+3)
blockNum = blockNum+1 blockNum = blockNum+1
end end
for i=1, #blocks do for i=1, #blocks do
local o1, o2, o3 = base64ToThreeOctet(blocks[i]) local o1, o2, o3 = base64ToThreeOctet(blocks[i])
table.insert(bytes, o1) table.insert(bytes, o1)
table.insert(bytes, o2) table.insert(bytes, o2)
table.insert(bytes, o3) table.insert(bytes, o3)
throttle() throttle()
end end
-- Remove padding: -- Remove padding:
--[[ --[[
for i=#bytes, 1, -1 do for i=#bytes, 1, -1 do
if bytes[i] ~= 0 then if bytes[i] ~= 0 then
break break
else else
bytes[i] = nil bytes[i] = nil
end end
end end
]] ]]
return bytes return bytes
end end
return Base64 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() local Builder = class()
Util.merge(Builder, { Util.merge(Builder, {
isCommandComputer = not turtle, isCommandComputer = not turtle,
loc = { }, loc = { },
index = 1, index = 1,
mode = 'build', mode = 'build',
}) })
local BUILDER_DIR = 'usr/builder' local BUILDER_DIR = 'usr/builder'
@@ -20,82 +20,82 @@ local BUILDER_DIR = 'usr/builder'
local blockInfo = Blocks() local blockInfo = Blocks()
function Builder:getBlockCounts() function Builder:getBlockCounts()
local blocks = { } local blocks = { }
for k = self.index, #self.schematic.blocks do for k = self.index, #self.schematic.blocks do
local b = self.schematic.blocks[k] local b = self.schematic.blocks[k]
local key = tostring(b.id) .. ':' .. b.dmg local key = tostring(b.id) .. ':' .. b.dmg
local block = blocks[key] local block = blocks[key]
if not block then if not block then
block = Util.shallowCopy(b) block = Util.shallowCopy(b)
block.qty = 0 block.qty = 0
block.need = 0 block.need = 0
blocks[key] = block blocks[key] = block
end end
block.need = block.need + 1 block.need = block.need + 1
end end
return blocks return blocks
end end
function Builder:substituteBlocks(throttle) 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 -- replace schematic block type with substitution
local pb = blockInfo:getPlaceableBlock(b.id, b.dmg) 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 }) local sub = self.subDB:get({ b.id, b.dmg })
if sub then if sub then
b.id, b.dmg = self.subDB:extract(sub) b.id, b.dmg = self.subDB:extract(sub)
end end
throttle() throttle()
end end
end end
function Builder:reloadSchematic(throttle) function Builder:reloadSchematic(throttle)
self.schematic:reload(throttle) self.schematic:reload(throttle)
self:substituteBlocks(throttle) self:substituteBlocks(throttle)
end end
function Builder:log(...) function Builder:log(...)
Util.print(...) Util.print(...)
end end
function Builder:dumpInventory() function Builder:dumpInventory()
end end
function Builder:logBlock(index, b) function Builder:logBlock(index, b)
local bdir = b.direction or '' local bdir = b.direction or ''
local logText = string.format('%d %s:%d (x:%d,z:%d:y:%d) %s', 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) index, b.id, b.dmg, b.x, b.z, b.y, bdir)
self:log(logText) self:log(logText)
-- self:log(b.index) -- unique identifier of block -- self:log(b.index) -- unique identifier of block
if device.wireless_modem then if device.wireless_modem then
Message.broadcast('builder', { x = b.x, y = b.y, z = b.z, heading = b.heading }) Message.broadcast('builder', { x = b.x, y = b.y, z = b.z, heading = b.heading })
end end
end end
function Builder:saveProgress(index) function Builder:saveProgress(index)
Util.writeTable( Util.writeTable(
fs.combine(BUILDER_DIR, self.schematic.filename .. '.progress'), fs.combine(BUILDER_DIR, self.schematic.filename .. '.progress'),
{ index = index, loc = self.loc } { index = index, loc = self.loc }
) )
end end
function Builder:loadProgress(filename) function Builder:loadProgress(filename)
local progress = Util.readTable(fs.combine(BUILDER_DIR, filename)) local progress = Util.readTable(fs.combine(BUILDER_DIR, filename))
if progress then if progress then
self.index = progress.index self.index = progress.index
if self.index > #self.schematic.blocks then if self.index > #self.schematic.blocks then
self.index = 1 self.index = 1
end end
self.loc = progress.loc or { } self.loc = progress.loc or { }
end end
end end
return Builder return Builder

View File

@@ -8,77 +8,77 @@ local os = _G.os
local read = _G.read local read = _G.read
function Builder:begin() function Builder:begin()
local direction = 1 local direction = 1
local last = #self.schematic.blocks local last = #self.schematic.blocks
local throttle = Util.throttle() local throttle = Util.throttle()
local cx, cy, cz = commands.getBlockPosition() local cx, cy, cz = commands.getBlockPosition()
if self.loc.x then if self.loc.x then
cx, cy, cz = self.loc.rx, self.loc.ry, self.loc.rz cx, cy, cz = self.loc.rx, self.loc.ry, self.loc.rz
end end
if self.mode == 'destroy' then if self.mode == 'destroy' then
direction = -1 direction = -1
last = 1 last = 1
end end
for i = self.index, last, direction do for i = self.index, last, direction do
self.index = i 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 local id = b.id
if self.mode == 'destroy' then if self.mode == 'destroy' then
id = 'minecraft:air' id = 'minecraft:air'
end end
local function placeBlock(bid, dmg, x, y, z) local function placeBlock(bid, dmg, x, y, z)
local command = table.concat({ local command = table.concat({
"setblock", "setblock",
cx + x + 1, cx + x + 1,
cy + y, cy + y,
cz + z + 1, cz + z + 1,
bid, bid,
dmg, dmg,
}, ' ') }, ' ')
commands.execAsync(command) commands.execAsync(command)
local result = { os.pullEvent("task_complete") } local result = { os.pullEvent("task_complete") }
if not result[4] then if not result[4] then
Util.print(result[5]) Util.print(result[5])
if self.mode ~= 'destroy' then if self.mode ~= 'destroy' then
read() read()
end end
end 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 if b.twoHigh then
local _, topBlock = self.schematic:findIndexAt(b.x, b.z, b.y + 1, true) local _, topBlock = self.schematic:findIndexAt(b.x, b.z, b.y + 1, true)
if topBlock then if topBlock then
placeBlock(id, topBlock.odmg, b.x, b.y + 1, b.z) placeBlock(id, topBlock.odmg, b.x, b.y + 1, b.z)
end end
end end
if self.mode == 'destroy' then if self.mode == 'destroy' then
self:saveProgress(math.max(self.index, 1)) self:saveProgress(math.max(self.index, 1))
else else
self:saveProgress(self.index + 1) self:saveProgress(self.index + 1)
end end
else else
throttle() -- sleep in case there are a large # of skipped blocks throttle() -- sleep in case there are a large # of skipped blocks
end end
end end
fs.delete(self.schematic.filename .. '.progress') fs.delete(self.schematic.filename .. '.progress')
print('Finished') print('Finished')
Event.exitPullEvents() Event.exitPullEvents()
end end
return Builder 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 local turtle = _G.turtle
--[[ --[[
A supplier turtle for the builder turtle. For larger builds, use A supplier turtle for the builder turtle. For larger builds, use
ender modems. ender modems.
Setup: Setup:
1. chest or ME interface at level 0 (bottom of build area) 1. chest or ME interface at level 0 (bottom of build area)
2. builder turtle on top facing the build area 2. builder turtle on top facing the build area
3. If facing the build turtle, the supplier turtle is to the right 3. If facing the build turtle, the supplier turtle is to the right
pointing at the chest/interface pointing at the chest/interface
]]-- ]]--
local ChestProvider = require('core.chestProvider') local ChestProvider = require('core.chestProvider')
if Util.getVersion() == 1.8 then if Util.getVersion() == 1.8 then
ChestProvider = require('core.chestProvider18') ChestProvider = require('core.chestProvider18')
end end
if not device.wireless_modem then if not device.wireless_modem then
error('No wireless modem detected') error('No wireless modem detected')
end end
local __BUILDER_ID = 6 local __BUILDER_ID = 6
local itemInfoDB local itemInfoDB
local Builder = { local Builder = {
version = '1.70', version = '1.70',
ccVersion = nil, ccVersion = nil,
slots = { }, slots = { },
index = 1, index = 1,
fuelItem = { id = 'minecraft:coal', dmg = 0 }, fuelItem = { id = 'minecraft:coal', dmg = 0 },
resupplying = true, resupplying = true,
ready = true, ready = true,
} }
--[[-- maxStackDB --]]-- --[[-- maxStackDB --]]--
local maxStackDB = TableDB({ local maxStackDB = TableDB({
fileName = 'maxstack.db', fileName = 'maxstack.db',
tabledef = { tabledef = {
autokeys = false, autokeys = false,
type = 'simple', type = 'simple',
columns = { columns = {
{ label = 'Key', type = 'key', length = 8 }, { label = 'Key', type = 'key', length = 8 },
{ label = 'Quantity', type = 'number', length = 2 } { label = 'Quantity', type = 'number', length = 2 }
} }
} }
}) })
function maxStackDB:get(id, dmg) function maxStackDB:get(id, dmg)
return self.data[id .. ':' .. dmg] or 64 return self.data[id .. ':' .. dmg] or 64
end end
function Builder:dumpInventory() function Builder:dumpInventory()
local success = true local success = true
for i = 1, 16 do for i = 1, 16 do
local qty = turtle.getItemCount(i) local qty = turtle.getItemCount(i)
if qty > 0 then if qty > 0 then
self.itemProvider:insert(i, qty) self.itemProvider:insert(i, qty)
end end
if turtle.getItemCount(i) ~= 0 then if turtle.getItemCount(i) ~= 0 then
success = false success = false
end end
end end
turtle.select(1) turtle.select(1)
return success return success
end end
function Builder:dumpInventoryWithCheck() function Builder:dumpInventoryWithCheck()
while not self:dumpInventory() do while not self:dumpInventory() do
Builder:log('Unable to dump inventory') Builder:log('Unable to dump inventory')
print('Provider is full or missing - make space or replace') print('Provider is full or missing - make space or replace')
print('Press enter to continue') print('Press enter to continue')
--turtle.setHeading(0) --turtle.setHeading(0)
self.ready = false self.ready = false
_G.read() _G.read()
end end
self.ready = true self.ready = true
end end
function Builder:autocraft(supplies) function Builder:autocraft(supplies)
local t = { } local t = { }
for _,s in pairs(supplies) do for _,s in pairs(supplies) do
local key = s.id .. ':' .. s.dmg local key = s.id .. ':' .. s.dmg
local item = t[key] local item = t[key]
if not item then if not item then
item = { item = {
id = s.id, id = s.id,
dmg = s.dmg, dmg = s.dmg,
qty = 0, qty = 0,
} }
t[key] = item t[key] = item
end end
item.qty = item.qty + (s.need-s.qty) item.qty = item.qty + (s.need-s.qty)
end end
Builder.itemProvider:craftItems(t) Builder.itemProvider:craftItems(t)
end end
function Builder:refuel() function Builder:refuel()
while turtle.getFuelLevel() < 4000 and self.fuelItem do while turtle.getFuelLevel() < 4000 and self.fuelItem do
Builder:log('Refueling') Builder:log('Refueling')
turtle.select(1) turtle.select(1)
self.itemProvider:provide(self.fuelItem, 64, 1) self.itemProvider:provide(self.fuelItem, 64, 1)
if turtle.getItemCount(1) == 0 then if turtle.getItemCount(1) == 0 then
Builder:log('Out of fuel, add coal to chest/ME system') Builder:log('Out of fuel, add coal to chest/ME system')
--turtle.setHeading(0) --turtle.setHeading(0)
os.sleep(5) os.sleep(5)
else else
turtle.refuel(64) turtle.refuel(64)
end end
end end
end end
function Builder:log(...) function Builder:log(...)
Util.print(...) Util.print(...)
end end
function Builder:getSupplies() function Builder:getSupplies()
Builder.itemProvider:refresh() Builder.itemProvider:refresh()
local t = { } local t = { }
for _,s in ipairs(self.slots) do for _,s in ipairs(self.slots) do
if s.need > 0 then if s.need > 0 then
local item = Builder.itemProvider:getItemInfo(s) local item = Builder.itemProvider:getItemInfo(s)
if item then if item then
if item.name then if item.name then
s.name = item.name s.name = item.name
end 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 if qty + s.qty > item.max_size then
maxStackDB:add({ s.id, s.dmg }, item.max_size) maxStackDB:add({ s.id, s.dmg }, item.max_size)
maxStackDB.dirty = true maxStackDB.dirty = true
maxStackDB:flush() maxStackDB:flush()
qty = item.max_size qty = item.max_size
s.need = qty s.need = qty
end end
if qty > 0 then if qty > 0 then
self.itemProvider:provide(item, qty, s.index) self.itemProvider:provide(item, qty, s.index)
s.qty = turtle.getItemCount(s.index) s.qty = turtle.getItemCount(s.index)
end end
end end
end end
if s.qty < s.need then if s.qty < s.need then
table.insert(t, s) table.insert(t, s)
local name = s.name or s.id .. ':' .. s.dmg local name = s.name or s.id .. ':' .. s.dmg
local item = itemInfoDB:get({ s.id, s.dmg }) local item = itemInfoDB:get({ s.id, s.dmg })
if item then if item then
name = item.displayName name = item.displayName
end end
Builder:log('Need %d %s', s.need - s.qty, name) Builder:log('Need %d %s', s.need - s.qty, name)
end end
end end
return t return t
end end
local function moveTowardsX(dx) local function moveTowardsX(dx)
local direction = dx - turtle.point.x local direction = dx - turtle.point.x
local move local move
if direction == 0 then if direction == 0 then
return false return false
end end
if direction > 0 and turtle.point.heading == 0 or if direction > 0 and turtle.point.heading == 0 or
direction < 0 and turtle.point.heading == 2 then direction < 0 and turtle.point.heading == 2 then
move = turtle.forward move = turtle.forward
else else
move = turtle.back move = turtle.back
end end
return move() return move()
end end
local function moveTowardsZ(dz) local function moveTowardsZ(dz)
local direction = dz - turtle.point.z local direction = dz - turtle.point.z
local move local move
if direction == 0 then if direction == 0 then
return false return false
end end
if direction > 0 and turtle.point.heading == 1 or if direction > 0 and turtle.point.heading == 1 or
direction < 0 and turtle.point.heading == 3 then direction < 0 and turtle.point.heading == 3 then
move = turtle.forward move = turtle.forward
else else
move = turtle.back move = turtle.back
end end
return move() return move()
end end
function Builder:finish() function Builder:finish()
Builder.resupplying = true Builder.resupplying = true
Builder.ready = false Builder.ready = false
if turtle.gotoLocation('supplies') then if turtle.gotoLocation('supplies') then
turtle.setHeading(1) turtle.setHeading(1)
os.sleep(.1) -- random 'Computer is not connected' error... os.sleep(.1) -- random 'Computer is not connected' error...
Builder:dumpInventory() Builder:dumpInventory()
Event.exitPullEvents() Event.exitPullEvents()
print('Finished') print('Finished')
end end
end end
function Builder:gotoBuilder() function Builder:gotoBuilder()
if Builder.lastPoint then if Builder.lastPoint then
turtle.setStatus('tracking') turtle.setStatus('tracking')
while true do while true do
local pt = Point.copy(Builder.lastPoint) local pt = Point.copy(Builder.lastPoint)
pt.y = pt.y + 3 pt.y = pt.y + 3
if turtle.point.y ~= pt.y then if turtle.point.y ~= pt.y then
turtle.gotoY(pt.y) turtle.gotoY(pt.y)
else else
local distance = Point.turtleDistance(turtle.point, pt) local distance = Point.turtleDistance(turtle.point, pt)
if distance <= 3 then if distance <= 3 then
Builder:log('Synchronized') Builder:log('Synchronized')
break break
end end
if turtle.point.heading % 2 == 0 then if turtle.point.heading % 2 == 0 then
if turtle.point.x == pt.x then if turtle.point.x == pt.x then
turtle.headTowardsZ(pt.z) turtle.headTowardsZ(pt.z)
moveTowardsZ(pt.z) moveTowardsZ(pt.z)
else else
moveTowardsX(pt.x) moveTowardsX(pt.x)
end end
elseif turtle.point.z ~= pt.z then elseif turtle.point.z ~= pt.z then
moveTowardsZ(pt.z) moveTowardsZ(pt.z)
else else
turtle.headTowardsX(pt.x) turtle.headTowardsX(pt.x)
moveTowardsX(pt.x) moveTowardsX(pt.x)
end end
end end
end end
end end
end end
Message.addHandler('builder', Message.addHandler('builder',
function(_, id, msg) function(_, id, msg)
if not id or id ~= __BUILDER_ID then if not id or id ~= __BUILDER_ID then
return return
end end
if not Builder.resupplying then if not Builder.resupplying then
local pt = msg.contents local pt = msg.contents
pt.y = pt.y + 3 pt.y = pt.y + 3
turtle.setStatus('supervising') turtle.setStatus('supervising')
turtle.gotoYfirst(pt) turtle.gotoYfirst(pt)
end end
end) end)
Message.addHandler('supplyList', Message.addHandler('supplyList',
function(_, id, msg) function(_, id, msg)
if not id or id ~= __BUILDER_ID then if not id or id ~= __BUILDER_ID then
return return
end end
turtle.setStatus('resupplying') turtle.setStatus('resupplying')
Builder.resupplying = true Builder.resupplying = true
Builder.slots = msg.contents.slots Builder.slots = msg.contents.slots
Builder.slotUid = msg.contents.uid Builder.slotUid = msg.contents.uid
Builder:log('Received supply list ' .. Builder.slotUid) Builder:log('Received supply list ' .. Builder.slotUid)
os.sleep(0) os.sleep(0)
if not turtle.gotoLocation('supplies') then if not turtle.gotoLocation('supplies') then
Builder:log('Failed to go to supply location') Builder:log('Failed to go to supply location')
Builder.ready = false Builder.ready = false
Event.exitPullEvents() Event.exitPullEvents()
end end
turtle.setHeading(1) turtle.setHeading(1)
os.sleep(.2) -- random 'Computer is not connected' error... os.sleep(.2) -- random 'Computer is not connected' error...
Builder:dumpInventoryWithCheck() Builder:dumpInventoryWithCheck()
Builder:refuel() Builder:refuel()
while true do while true do
local supplies = Builder:getSupplies() local supplies = Builder:getSupplies()
if #supplies == 0 then if #supplies == 0 then
break break
end end
Builder:autocraft(supplies) Builder:autocraft(supplies)
turtle.setStatus('waiting') turtle.setStatus('waiting')
os.sleep(5) os.sleep(5)
end end
Builder:log('Got all supplies') Builder:log('Got all supplies')
os.sleep(0) os.sleep(0)
Builder:gotoBuilder() Builder:gotoBuilder()
Builder.resupplying = false Builder.resupplying = false
end) end)
Message.addHandler('needSupplies', Message.addHandler('needSupplies',
function(_, id, msg) function(_, id, msg)
if not id or id ~= __BUILDER_ID then if not id or id ~= __BUILDER_ID then
return return
end end
if Builder.resupplying or msg.contents.uid ~= Builder.slotUid then if Builder.resupplying or msg.contents.uid ~= Builder.slotUid then
Builder:log('No supplies ready') Builder:log('No supplies ready')
Message.send(__BUILDER_ID, 'gotSupplies') Message.send(__BUILDER_ID, 'gotSupplies')
else else
turtle.setStatus('supplying') turtle.setStatus('supplying')
Builder:log('Supplying') Builder:log('Supplying')
os.sleep(0) os.sleep(0)
local pt = msg.contents.point local pt = msg.contents.point
pt.y = turtle.getPoint().y pt.y = turtle.getPoint().y
pt.heading = nil pt.heading = nil
if not turtle.gotoYfirst(pt) then -- location of builder if not turtle.gotoYfirst(pt) then -- location of builder
Builder.resupplying = true Builder.resupplying = true
Message.send(__BUILDER_ID, 'gotSupplies') Message.send(__BUILDER_ID, 'gotSupplies')
os.sleep(0) os.sleep(0)
if not turtle.gotoLocation('supplies') then if not turtle.gotoLocation('supplies') then
Builder:log('failed to go to supply location') Builder:log('failed to go to supply location')
Event.exitPullEvents() Event.exitPullEvents()
end end
turtle.setHeading(1) turtle.setHeading(1)
return return
end end
pt.y = pt.y - 2 -- location where builder should go for the chest to be above pt.y = pt.y - 2 -- location where builder should go for the chest to be above
turtle.select(15) turtle.select(15)
turtle.placeDown() turtle.placeDown()
os.sleep(.1) -- random computer not connected error os.sleep(.1) -- random computer not connected error
local p = ChestProvider({ direction = 'up', wrapSide = 'bottom' }) local p = ChestProvider({ direction = 'up', wrapSide = 'bottom' })
for i = 1, 16 do for i = 1, 16 do
p:insert(i, 64) p:insert(i, 64)
end 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) Message.waitForMessage('thanks', 5, __BUILDER_ID)
--os.sleep(0) --os.sleep(0)
--p.condenseItems() --p.condenseItems()
for i = 1, 16 do for i = 1, 16 do
p:extract(i, 64) p:extract(i, 64)
end end
turtle.digDown() turtle.digDown()
turtle.setStatus('waiting') turtle.setStatus('waiting')
end end
end) end)
Message.addHandler('finished', Message.addHandler('finished',
function(_, id) function(_, id)
if not id or id ~= __BUILDER_ID then if not id or id ~= __BUILDER_ID then
return return
end end
Builder:finish() Builder:finish()
end) end)
Event.on('turtle_abort', Event.on('turtle_abort',
function() function()
turtle.abort(false) turtle.abort(false)
turtle.setStatus('aborting') turtle.setStatus('aborting')
Builder:finish() Builder:finish()
end) end)
local function onTheWay() -- parallel routine local function onTheWay() -- parallel routine
while true do while true do
local _, _, _, id, msg, _ = os.pullEvent('modem_message') local _, _, _, id, msg, _ = os.pullEvent('modem_message')
if Builder.ready then if Builder.ready then
if id == __BUILDER_ID and msg and msg.type then if id == __BUILDER_ID and msg and msg.type then
if msg.type == 'needSupplies' then if msg.type == 'needSupplies' then
Message.send(__BUILDER_ID, 'gotSupplies', { supplies = true }) Message.send(__BUILDER_ID, 'gotSupplies', { supplies = true })
elseif msg.type == 'builder' then elseif msg.type == 'builder' then
Builder.lastPoint = msg.contents Builder.lastPoint = msg.contents
end end
end end
end end
end end
end end
local args = {...} local args = {...}
if #args < 2 then if #args < 2 then
error('syntax: <builder id> <facing>') error('syntax: <builder id> <facing>')
end end
__BUILDER_ID = tonumber(args[1]) __BUILDER_ID = tonumber(args[1])
@@ -401,30 +401,30 @@ __BUILDER_ID = tonumber(args[1])
maxStackDB:load() maxStackDB:load()
itemInfoDB = TableDB({ itemInfoDB = TableDB({
fileName = 'items.db' fileName = 'items.db'
}) })
itemInfoDB:load() itemInfoDB:load()
Builder.itemProvider = MEProvider({ direction = args[2] }) Builder.itemProvider = MEProvider({ direction = args[2] })
if not Builder.itemProvider:isValid() then if not Builder.itemProvider:isValid() then
local sides = { local sides = {
east = 'west', east = 'west',
west = 'east', west = 'east',
north = 'south', north = 'south',
south = 'north', south = 'north',
} }
Builder.itemProvider = ChestProvider({ direction = sides[args[2]], wrapSide = 'front' }) Builder.itemProvider = ChestProvider({ direction = sides[args[2]], wrapSide = 'front' })
if not Builder.itemProvider:isValid() then if not Builder.itemProvider:isValid() then
error('A chest or ME interface must be in front of turtle') error('A chest or ME interface must be in front of turtle')
end end
end end
turtle.run(function() 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) end)

View File

@@ -13,15 +13,15 @@ local function Syntax(msg)
* Entity sensor * Entity sensor
* Introspection module * Introspection module
]]) ]])
error(msg) error(msg)
end end
local neural = device['neuralInterface'] or Syntax('Must be run on a neural interface') local neural = device['neuralInterface'] or Syntax('Must be run on a neural interface')
local function assertModule(module, name) local function assertModule(module, name)
if not neural.hasModule(module) then if not neural.hasModule(module) then
Syntax('Missing: ' .. name) Syntax('Missing: ' .. name)
end end
end end
assertModule('plethora:glasses', 'Overlay glasses') assertModule('plethora:glasses', 'Overlay glasses')
assertModule('plethora:sensor', 'Entity sensor') assertModule('plethora:sensor', 'Entity sensor')
@@ -31,102 +31,102 @@ local BUILDER_DIR = 'usr/builder'
--[[-- SubDB --]]-- --[[-- SubDB --]]--
local subDB = TableDB({ local subDB = TableDB({
fileName = fs.combine(BUILDER_DIR, 'sub.db'), fileName = fs.combine(BUILDER_DIR, 'sub.db'),
}) })
function subDB:load() function subDB:load()
if fs.exists(self.fileName) then if fs.exists(self.fileName) then
TableDB.load(self) TableDB.load(self)
elseif not Builder.isCommandComputer then elseif not Builder.isCommandComputer then
self:seedDB() self:seedDB()
end end
end end
function subDB:seedDB() function subDB:seedDB()
self.data = { self.data = {
[ "minecraft:redstone_wire:0" ] = "minecraft:redstone:0", [ "minecraft:redstone_wire:0" ] = "minecraft:redstone:0",
[ "minecraft:wall_sign:0" ] = "minecraft:sign:0", [ "minecraft:wall_sign:0" ] = "minecraft:sign:0",
[ "minecraft:standing_sign:0" ] = "minecraft:sign:0", [ "minecraft:standing_sign:0" ] = "minecraft:sign:0",
[ "minecraft:potatoes:0" ] = "minecraft:potato:0", [ "minecraft:potatoes:0" ] = "minecraft:potato:0",
[ "minecraft:unlit_redstone_torch:0" ] = "minecraft:redstone_torch:0", [ "minecraft:unlit_redstone_torch:0" ] = "minecraft:redstone_torch:0",
[ "minecraft:powered_repeater:0" ] = "minecraft:repeater:0", [ "minecraft:powered_repeater:0" ] = "minecraft:repeater:0",
[ "minecraft:unpowered_repeater:0" ] = "minecraft:repeater:0", [ "minecraft:unpowered_repeater:0" ] = "minecraft:repeater:0",
[ "minecraft:carrots:0" ] = "minecraft:carrot:0", [ "minecraft:carrots:0" ] = "minecraft:carrot:0",
[ "minecraft:cocoa:0" ] = "minecraft:dye:3", [ "minecraft:cocoa:0" ] = "minecraft:dye:3",
[ "minecraft:unpowered_comparator:0" ] = "minecraft:comparator:0", [ "minecraft:unpowered_comparator:0" ] = "minecraft:comparator:0",
[ "minecraft:powered_comparator:0" ] = "minecraft:comparator:0", [ "minecraft:powered_comparator:0" ] = "minecraft:comparator:0",
[ "minecraft:piston_head:0" ] = "minecraft:air:0", [ "minecraft:piston_head:0" ] = "minecraft:air:0",
[ "minecraft:piston_extension:0" ] = "minecraft:air:0", [ "minecraft:piston_extension:0" ] = "minecraft:air:0",
[ "minecraft:portal:0" ] = "minecraft:air:0", [ "minecraft:portal:0" ] = "minecraft:air:0",
[ "minecraft:double_wooden_slab:0" ] = "minecraft:planks:0", [ "minecraft:double_wooden_slab:0" ] = "minecraft:planks:0",
[ "minecraft:double_wooden_slab:1" ] = "minecraft:planks:1", [ "minecraft:double_wooden_slab:1" ] = "minecraft:planks:1",
[ "minecraft:double_wooden_slab:2" ] = "minecraft:planks:2", [ "minecraft:double_wooden_slab:2" ] = "minecraft:planks:2",
[ "minecraft:double_wooden_slab:3" ] = "minecraft:planks:3", [ "minecraft:double_wooden_slab:3" ] = "minecraft:planks:3",
[ "minecraft:double_wooden_slab:4" ] = "minecraft:planks:4", [ "minecraft:double_wooden_slab:4" ] = "minecraft:planks:4",
[ "minecraft:double_wooden_slab:5" ] = "minecraft:planks:5", [ "minecraft:double_wooden_slab:5" ] = "minecraft:planks:5",
[ "minecraft:lit_redstone_lamp:0" ] = "minecraft:redstone_lamp:0", [ "minecraft:lit_redstone_lamp:0" ] = "minecraft:redstone_lamp:0",
[ "minecraft:double_stone_slab:1" ] = "minecraft:sandstone:0", [ "minecraft:double_stone_slab:1" ] = "minecraft:sandstone:0",
[ "minecraft:double_stone_slab:2" ] = "minecraft:planks:0", [ "minecraft:double_stone_slab:2" ] = "minecraft:planks:0",
[ "minecraft:double_stone_slab:3" ] = "minecraft:cobblestone:0", [ "minecraft:double_stone_slab:3" ] = "minecraft:cobblestone:0",
[ "minecraft:double_stone_slab:4" ] = "minecraft:brick_block:0", [ "minecraft:double_stone_slab:4" ] = "minecraft:brick_block:0",
[ "minecraft:double_stone_slab:5" ] = "minecraft:stonebrick:0", [ "minecraft:double_stone_slab:5" ] = "minecraft:stonebrick:0",
[ "minecraft:double_stone_slab:6" ] = "minecraft:nether_brick:0", [ "minecraft:double_stone_slab:6" ] = "minecraft:nether_brick:0",
[ "minecraft:double_stone_slab:7" ] = "minecraft:quartz_block:0", [ "minecraft:double_stone_slab:7" ] = "minecraft:quartz_block:0",
[ "minecraft:double_stone_slab:9" ] = "minecraft:sandstone:2", [ "minecraft:double_stone_slab:9" ] = "minecraft:sandstone:2",
[ "minecraft:double_stone_slab2:0" ] = "minecraft:sandstone:0", [ "minecraft:double_stone_slab2:0" ] = "minecraft:sandstone:0",
[ "minecraft:stone_slab:2" ] = "minecraft:wooden_slab:0", [ "minecraft:stone_slab:2" ] = "minecraft:wooden_slab:0",
[ "minecraft:wheat:0" ] = "minecraft:wheat_seeds:0", [ "minecraft:wheat:0" ] = "minecraft:wheat_seeds:0",
[ "minecraft:flowing_water:0" ] = "minecraft:air:0", [ "minecraft:flowing_water:0" ] = "minecraft:air:0",
[ "minecraft:lit_furnace:0" ] = "minecraft:furnace:0", [ "minecraft:lit_furnace:0" ] = "minecraft:furnace:0",
[ "minecraft:wall_banner:0" ] = "minecraft:banner:0", [ "minecraft:wall_banner:0" ] = "minecraft:banner:0",
[ "minecraft:standing_banner:0" ] = "minecraft:banner:0", [ "minecraft:standing_banner:0" ] = "minecraft:banner:0",
[ "minecraft:tripwire:0" ] = "minecraft:string:0", [ "minecraft:tripwire:0" ] = "minecraft:string:0",
[ "minecraft:pumpkin_stem:0" ] = "minecraft:pumpkin_seeds:0", [ "minecraft:pumpkin_stem:0" ] = "minecraft:pumpkin_seeds:0",
} }
self.dirty = true self.dirty = true
self:flush() self:flush()
end end
function subDB:add(s) function subDB:add(s)
TableDB.add(self, { s.id, s.dmg }, table.concat({ s.sid, s.sdmg }, ':')) TableDB.add(self, { s.id, s.dmg }, table.concat({ s.sid, s.sdmg }, ':'))
self:flush() self:flush()
end end
function subDB:remove(s) function subDB:remove(s)
-- TODO: tableDB.remove should take table key -- TODO: tableDB.remove should take table key
TableDB.remove(self, s.id .. ':' .. s.dmg) TableDB.remove(self, s.id .. ':' .. s.dmg)
self:flush() self:flush()
end end
function subDB:extract(s) function subDB:extract(s)
local id, dmg = s:match('(.+):(%d+)') local id, dmg = s:match('(.+):(%d+)')
return id, tonumber(dmg) return id, tonumber(dmg)
end end
function subDB:getSubstitutedItem(id, dmg) function subDB:getSubstitutedItem(id, dmg)
local sub = TableDB.get(self, { id, dmg }) local sub = TableDB.get(self, { id, dmg })
if sub then if sub then
id, dmg = self:extract(sub) id, dmg = self:extract(sub)
end end
return { id = id, dmg = dmg } return { id = id, dmg = dmg }
end end
function subDB:lookupBlocksForSub(sid, sdmg) function subDB:lookupBlocksForSub(sid, sdmg)
local t = { } local t = { }
for k,v in pairs(self.data) do for k,v in pairs(self.data) do
local id, dmg = self:extract(v) local id, dmg = self:extract(v)
if id == sid and dmg == sdmg then if id == sid and dmg == sdmg then
id, dmg = self:extract(k) id, dmg = self:extract(k)
t[k] = { id = id, dmg = dmg, sid = sid, sdmg = sdmg } t[k] = { id = id, dmg = dmg, sid = sid, sdmg = sdmg }
end end
end end
return t return t
end end
--[[-- startup logic --]]-- --[[-- startup logic --]]--
local args = {...} local args = {...}
if #args < 1 then if #args < 1 then
error('supply file name') error('supply file name')
end end
subDB:load() subDB:load()
@@ -142,18 +142,18 @@ Builder:substituteBlocks(Util.throttle())
local cn = neural.canvas3d().create() local cn = neural.canvas3d().create()
local pos = neural.getMetaOwner().withinBlock 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 for i = 1, #Builder.schematic.blocks do
local b = Builder.schematic:getComputedBlock(i) local b = Builder.schematic:getComputedBlock(i)
if b.id ~= "minecraft:air" and b.id ~= 'minecraft:water' then if b.id ~= "minecraft:air" and b.id ~= 'minecraft:water' then
local s, m = pcall(function() local s, m = pcall(function()
cn.addItem({ b.x, b.y, b.z }, b.id, b.dmg) cn.addItem({ b.x, b.y, b.z }, b.id, b.dmg)
end) end)
if not s and m then if not s and m then
_G.printError(m) _G.printError(m)
end end
end end
end end
pcall(_G.read) pcall(_G.read)

View File

@@ -3,15 +3,15 @@ local fs = _G.fs
local peripheral = _G.peripheral local peripheral = _G.peripheral
if ccemux then if ccemux then
-- add a System setup tab -- add a System setup tab
fs.mount('sys/apps/system/ccemux.lua', 'linkfs', 'packages/ccemux/system/ccemux.lua') 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 for k,v in pairs(Config.load('ccemux')) do
if not peripheral.getType(k) then if not peripheral.getType(k) then
ccemux.attach(k, v.type, v.args) ccemux.attach(k, v.type, v.args)
end end
end end
end end

View File

@@ -15,18 +15,18 @@ local REGISTRY_DIR = 'usr/.registry'
-- FIX SOMEDAY -- FIX SOMEDAY
local function registerApp(app, key) local function registerApp(app, key)
app.key = SHA1.sha1(key) app.key = SHA1.sha1(key)
Util.writeTable(fs.combine(REGISTRY_DIR, app.key), app) Util.writeTable(fs.combine(REGISTRY_DIR, app.key), app)
os.queueEvent('os_register_app') os.queueEvent('os_register_app')
end end
local function unregisterApp(key) local function unregisterApp(key)
local filename = fs.combine(REGISTRY_DIR, SHA1.sha1(key)) local filename = fs.combine(REGISTRY_DIR, SHA1.sha1(key))
if fs.exists(filename) then if fs.exists(filename) then
fs.delete(filename) fs.delete(filename)
os.queueEvent('os_register_app') os.queueEvent('os_register_app')
end end
end end
@@ -40,366 +40,366 @@ local APP_DIR = 'usr/apps'
local sources = { local sources = {
{ text = "STD Default", { text = "STD Default",
event = 'source', event = 'source',
url = "http://pastebin.com/raw/zVws7eLq" }, --stock url = "http://pastebin.com/raw/zVws7eLq" }, --stock
--[[ --[[
{ text = "Discover", { text = "Discover",
event = 'source', event = 'source',
generateName = true, generateName = true,
url = "http://pastebin.com/raw/9bXfCz6M" }, --owned by dannysmc95 url = "http://pastebin.com/raw/9bXfCz6M" }, --owned by dannysmc95
{ text = "Opus", { text = "Opus",
event = 'source', event = 'source',
url = "http://pastebin.com/raw/ajQ91Rmn" }, url = "http://pastebin.com/raw/ajQ91Rmn" },
]] ]]
} }
shell.setDir(APP_DIR) shell.setDir(APP_DIR)
local function downloadApp(app) local function downloadApp(app)
local h local h
if type(app.url) == "table" then if type(app.url) == "table" then
h = contextualGet(app.url[1]) h = contextualGet(app.url[1])
else else
h = http.get(app.url) h = http.get(app.url)
end end
if h then if h then
local contents = h.readAll() local contents = h.readAll()
h:close() h:close()
return contents return contents
end end
end end
local function runApp(app, checkExists, ...) local function runApp(app, checkExists, ...)
local path, fn local path, fn
local args = { ... } local args = { ... }
if checkExists and fs.exists(fs.combine(APP_DIR, app.name)) then if checkExists and fs.exists(fs.combine(APP_DIR, app.name)) then
path = fs.combine(APP_DIR, app.name) path = fs.combine(APP_DIR, app.name)
else else
local program = downloadApp(app) local program = downloadApp(app)
fn = function() fn = function()
if not program then if not program then
error('Failed to download') error('Failed to download')
end end
local fn = loadstring(program, app.name) local fn = loadstring(program, app.name)
if not fn then if not fn then
error('Failed to download') error('Failed to download')
end end
setfenv(fn, sandboxEnv) setfenv(fn, sandboxEnv)
fn(unpack(args)) fn(unpack(args))
end end
end end
multishell.openTab({ multishell.openTab({
title = app.name, title = app.name,
env = sandboxEnv, env = sandboxEnv,
path = path, path = path,
fn = fn, fn = fn,
focused = true, focused = true,
}) })
return true, 'Running program' return true, 'Running program'
end end
local installApp = function(app) local installApp = function(app)
local program = downloadApp(app) local program = downloadApp(app)
if not program then if not program then
return false, "Failed to download" return false, "Failed to download"
end end
local fullPath = fs.combine(APP_DIR, app.name) local fullPath = fs.combine(APP_DIR, app.name)
Util.writeFile(fullPath, program) Util.writeFile(fullPath, program)
return true, 'Installed as ' .. fullPath return true, 'Installed as ' .. fullPath
end end
local viewApp = function(app) local viewApp = function(app)
local program = downloadApp(app) local program = downloadApp(app)
if not program then if not program then
return false, "Failed to download" return false, "Failed to download"
end end
Util.writeFile('/.source', program) Util.writeFile('/.source', program)
shell.openForegroundTab('edit /.source') shell.openForegroundTab('edit /.source')
fs.delete('/.source') fs.delete('/.source')
return true return true
end end
local getSourceListing = function(source) local getSourceListing = function(source)
local contents = http.get(source.url) local contents = http.get(source.url)
if contents then if contents then
local fn = loadstring(contents.readAll(), source.text) local fn = loadstring(contents.readAll(), source.text)
contents.close() contents.close()
local env = { std = { } } local env = { std = { } }
setmetatable(env, { __index = _G }) setmetatable(env, { __index = _G })
setfenv(fn, env) setfenv(fn, env)
fn() fn()
if env.contextualGet then if env.contextualGet then
contextualGet = env.contextualGet contextualGet = env.contextualGet
end end
source.storeURLs = env.std.storeURLs source.storeURLs = env.std.storeURLs
source.storeCatagoryNames = env.std.storeCatagoryNames source.storeCatagoryNames = env.std.storeCatagoryNames
if source.storeURLs and source.storeCatagoryNames then if source.storeURLs and source.storeCatagoryNames then
for k,v in pairs(source.storeURLs) do for k,v in pairs(source.storeURLs) do
if source.generateName then if source.generateName then
v.name = v.title:match('(%w+)') v.name = v.title:match('(%w+)')
if not v.name or #v.name == 0 then if not v.name or #v.name == 0 then
v.name = tostring(k) v.name = tostring(k)
else else
v.name = v.name:lower() v.name = v.name:lower()
end end
else else
v.name = k v.name = k
end end
v.categoryName = source.storeCatagoryNames[v.catagory] v.categoryName = source.storeCatagoryNames[v.catagory]
v.ltitle = v.title:lower() v.ltitle = v.title:lower()
end end
end end
end end
end end
local appPage = UI.Page { local appPage = UI.Page {
menuBar = UI.MenuBar { menuBar = UI.MenuBar {
-- showBackButton = not pocket, -- showBackButton = not pocket,
buttons = { buttons = {
{ text = '\027', event = 'back' }, { text = '\027', event = 'back' },
{ text = 'Install', event = 'install' }, { text = 'Install', event = 'install' },
{ text = 'Run', event = 'run' }, { text = 'Run', event = 'run' },
{ text = 'View', event = 'view' }, { text = 'View', event = 'view' },
{ text = 'Remove', event = 'uninstall', name = 'removeButton' }, { text = 'Remove', event = 'uninstall', name = 'removeButton' },
}, },
}, },
container = UI.Window { container = UI.Window {
x = 2, y = 3, ex = -2, ey = -3, x = 2, y = 3, ex = -2, ey = -3,
viewport = UI.Viewport(), viewport = UI.Viewport(),
}, },
notification = UI.Notification(), notification = UI.Notification(),
accelerators = { accelerators = {
q = 'back', q = 'back',
backspace = 'back', backspace = 'back',
}, },
} }
function appPage.container.viewport:draw() function appPage.container.viewport:draw()
local app = self.parent.parent.app local app = self.parent.parent.app
local str = string.format( local str = string.format(
'%s \nBy: %s \nCategory: %s\nFile name: %s\n\n%s', '%s \nBy: %s \nCategory: %s\nFile name: %s\n\n%s',
Ansi.yellow .. app.title .. Ansi.reset, Ansi.yellow .. app.title .. Ansi.reset,
app.creator, app.creator,
app.categoryName, app.name, app.categoryName, app.name,
Ansi.yellow .. app.description .. Ansi.reset) Ansi.yellow .. app.description .. Ansi.reset)
self:clear() self:clear()
self:setCursorPos(1, 1) self:setCursorPos(1, 1)
self:print(str) self:print(str)
self.ymax = self.cursorY self.ymax = self.cursorY
if appPage.notification.enabled then if appPage.notification.enabled then
appPage.notification:draw() appPage.notification:draw()
end end
end end
function appPage:enable(source, app) function appPage:enable(source, app)
self.source = source self.source = source
self.app = app self.app = app
UI.Page.enable(self) UI.Page.enable(self)
self.container.viewport:setScrollPosition(0) self.container.viewport:setScrollPosition(0)
if fs.exists(fs.combine(APP_DIR, app.name)) then if fs.exists(fs.combine(APP_DIR, app.name)) then
self.menuBar.removeButton:enable('Remove') self.menuBar.removeButton:enable('Remove')
else else
self.menuBar.removeButton:disable('Remove') self.menuBar.removeButton:disable('Remove')
end end
end end
function appPage:eventHandler(event) function appPage:eventHandler(event)
if event.type == 'back' then if event.type == 'back' then
UI:setPreviousPage() UI:setPreviousPage()
elseif event.type == 'run' then elseif event.type == 'run' then
self.notification:info('Running program', 3) self.notification:info('Running program', 3)
self:sync() self:sync()
runApp(self.app, true) runApp(self.app, true)
elseif event.type == 'view' then elseif event.type == 'view' then
self.notification:info('Downloading program', 3) self.notification:info('Downloading program', 3)
self:sync() self:sync()
viewApp(self.app) viewApp(self.app)
elseif event.type == 'uninstall' then elseif event.type == 'uninstall' then
if self.app.runOnly then if self.app.runOnly then
runApp(self.app, false, 'uninstall') runApp(self.app, false, 'uninstall')
else else
fs.delete(fs.combine(APP_DIR, self.app.name)) fs.delete(fs.combine(APP_DIR, self.app.name))
self.notification:success("Uninstalled " .. self.app.name, 3) self.notification:success("Uninstalled " .. self.app.name, 3)
self:focusFirst(self) self:focusFirst(self)
self.menuBar.removeButton:disable('Remove') self.menuBar.removeButton:disable('Remove')
self.menuBar:draw() self.menuBar:draw()
unregisterApp(self.app.creator .. '.' .. self.app.name) unregisterApp(self.app.creator .. '.' .. self.app.name)
end end
elseif event.type == 'install' then elseif event.type == 'install' then
self.notification:info("Installing", 3) self.notification:info("Installing", 3)
self:sync() self:sync()
local s, m local s, m
if self.app.runOnly then if self.app.runOnly then
s,m = runApp(self.app, false) s,m = runApp(self.app, false)
else else
s,m = installApp(self.app) s,m = installApp(self.app)
end end
if s then if s then
self.notification:success(m, 3) self.notification:success(m, 3)
if not self.app.runOnly then if not self.app.runOnly then
self.menuBar.removeButton:enable('Remove') self.menuBar.removeButton:enable('Remove')
self.menuBar:draw() self.menuBar:draw()
local category = 'Apps' local category = 'Apps'
if self.app.catagoryName == 'Game' then if self.app.catagoryName == 'Game' then
category = 'Games' category = 'Games'
end end
registerApp({ registerApp({
run = fs.combine(APP_DIR, self.app.name), run = fs.combine(APP_DIR, self.app.name),
title = self.app.title, title = self.app.title,
category = category, category = category,
icon = self.app.icon, icon = self.app.icon,
}, self.app.creator .. '.' .. self.app.name) }, self.app.creator .. '.' .. self.app.name)
end end
else else
self.notification:error(m, 3) self.notification:error(m, 3)
end end
else else
return UI.Page.eventHandler(self, event) return UI.Page.eventHandler(self, event)
end end
return true return true
end end
local categoryPage = UI.Page { local categoryPage = UI.Page {
menuBar = UI.MenuBar { menuBar = UI.MenuBar {
buttons = { buttons = {
{ text = 'Catalog', dropdown = sources }, { text = 'Catalog', dropdown = sources },
{ text = 'Category', name = 'categoryButton', dropdown = { } }, { text = 'Category', name = 'categoryButton', dropdown = { } },
}, },
}, },
grid = UI.ScrollingGrid { grid = UI.ScrollingGrid {
y = 2, ey = -2, y = 2, ey = -2,
columns = { columns = {
{ heading = 'Title', key = 'title' }, { heading = 'Title', key = 'title' },
}, },
sortColumn = 'title', sortColumn = 'title',
}, },
statusBar = UI.StatusBar(), statusBar = UI.StatusBar(),
accelerators = { accelerators = {
l = 'lua', l = 'lua',
q = 'quit', q = 'quit',
}, },
} }
function categoryPage:setCategory(source, name, index) function categoryPage:setCategory(source, name, index)
self.grid.values = { } self.grid.values = { }
for _,v in pairs(source.storeURLs) do for _,v in pairs(source.storeURLs) do
if index == 0 or index == v.catagory then if index == 0 or index == v.catagory then
table.insert(self.grid.values, v) table.insert(self.grid.values, v)
end end
end end
self.statusBar:setStatus(string.format('%s: %s', source.text, name)) self.statusBar:setStatus(string.format('%s: %s', source.text, name))
self.grid:update() self.grid:update()
self.grid:setIndex(1) self.grid:setIndex(1)
end end
function categoryPage:setSource(source) function categoryPage:setSource(source)
if not source.categoryMenu then if not source.categoryMenu then
self.statusBar:setStatus('Loading...') self.statusBar:setStatus('Loading...')
self.statusBar:draw() self.statusBar:draw()
self:sync() self:sync()
getSourceListing(source) getSourceListing(source)
if not source.storeURLs then if not source.storeURLs then
error('Unable to download application list') error('Unable to download application list')
end end
local buttons = { } local buttons = { }
for k,v in Util.spairs(source.storeCatagoryNames, for k,v in Util.spairs(source.storeCatagoryNames,
function(a, b) return a:lower() < b:lower() end) do function(a, b) return a:lower() < b:lower() end) do
if v ~= 'Operating System' then if v ~= 'Operating System' then
table.insert(buttons, { table.insert(buttons, {
text = v, text = v,
event = 'category', event = 'category',
index = k, index = k,
}) })
end end
end end
source.categoryMenu = UI.DropMenu({ source.categoryMenu = UI.DropMenu({
buttons = buttons, buttons = buttons,
}) })
source.index, source.name = Util.first(source.storeCatagoryNames) source.index, source.name = Util.first(source.storeCatagoryNames)
categoryPage.menuBar.categoryButton:add({ categoryPage.menuBar.categoryButton:add({
categoryMenu = source.categoryMenu categoryMenu = source.categoryMenu
}) })
end end
self.source = source self.source = source
self.menuBar.categoryButton.dropmenu = source.categoryMenu self.menuBar.categoryButton.dropmenu = source.categoryMenu
categoryPage:setCategory(source, source.name, source.index) categoryPage:setCategory(source, source.name, source.index)
end end
function categoryPage.grid:sortCompare(a, b) function categoryPage.grid:sortCompare(a, b)
return a.ltitle < b.ltitle return a.ltitle < b.ltitle
end end
function categoryPage.grid:getRowTextColor(row, selected) function categoryPage.grid:getRowTextColor(row, selected)
if fs.exists(fs.combine(APP_DIR, row.name)) then if fs.exists(fs.combine(APP_DIR, row.name)) then
return colors.orange return colors.orange
end end
return UI.Grid:getRowTextColor(row, selected) return UI.Grid:getRowTextColor(row, selected)
end end
function categoryPage:eventHandler(event) function categoryPage:eventHandler(event)
if event.type == 'grid_select' or event.type == 'select' then if event.type == 'grid_select' or event.type == 'select' then
UI:setPage(appPage, self.source, self.grid:getSelected()) UI:setPage(appPage, self.source, self.grid:getSelected())
elseif event.type == 'category' then elseif event.type == 'category' then
self:setCategory(self.source, event.button.text, event.button.index) self:setCategory(self.source, event.button.text, event.button.index)
self:setFocus(self.grid) self:setFocus(self.grid)
self:draw() self:draw()
elseif event.type == 'source' then elseif event.type == 'source' then
self:setFocus(self.grid) self:setFocus(self.grid)
self:setSource(event.button) self:setSource(event.button)
self:draw() self:draw()
elseif event.type == 'quit' then elseif event.type == 'quit' then
UI:exitPullEvents() UI:exitPullEvents()
else else
return UI.Page.eventHandler(self, event) return UI.Page.eventHandler(self, event)
end end
return true return true
end end
print("Retrieving catalog list") print("Retrieving catalog list")

View File

@@ -10,196 +10,196 @@ local peripheral = _G.peripheral
--[[ -- PeripheralsPage -- ]] -- --[[ -- PeripheralsPage -- ]] --
local peripheralsPage = UI.Page { local peripheralsPage = UI.Page {
grid = UI.ScrollingGrid { grid = UI.ScrollingGrid {
ey = -2, ey = -2,
columns = { columns = {
{ heading = 'Type', key = 'type' }, { heading = 'Type', key = 'type' },
{ heading = 'Side', key = 'side' }, { heading = 'Side', key = 'side' },
}, },
sortColumn = 'type', sortColumn = 'type',
autospace = true, autospace = true,
}, },
statusBar = UI.StatusBar { statusBar = UI.StatusBar {
values = 'Select peripheral', values = 'Select peripheral',
}, },
accelerators = { accelerators = {
q = 'quit', q = 'quit',
}, },
} }
function peripheralsPage.grid:draw() function peripheralsPage.grid:draw()
local sides = peripheral.getNames() local sides = peripheral.getNames()
Util.clear(self.values) Util.clear(self.values)
for _,side in pairs(sides) do for _,side in pairs(sides) do
table.insert(self.values, { table.insert(self.values, {
type = peripheral.getType(side), type = peripheral.getType(side),
side = side side = side
}) })
end end
self:update() self:update()
self:adjustWidth() self:adjustWidth()
UI.Grid.draw(self) UI.Grid.draw(self)
end end
function peripheralsPage:updatePeripherals() function peripheralsPage:updatePeripherals()
if UI:getCurrentPage() == self then if UI:getCurrentPage() == self then
self.grid:draw() self.grid:draw()
self:sync() self:sync()
end end
end end
function peripheralsPage:eventHandler(event) function peripheralsPage:eventHandler(event)
if event.type == 'quit' then if event.type == 'quit' then
Event.exitPullEvents() Event.exitPullEvents()
elseif event.type == 'grid_select' then elseif event.type == 'grid_select' then
UI:setPage('methods', event.selected) UI:setPage('methods', event.selected)
end end
return UI.Page.eventHandler(self, event) return UI.Page.eventHandler(self, event)
end end
--[[ -- MethodsPage -- ]] -- --[[ -- MethodsPage -- ]] --
local methodsPage = UI.Page { local methodsPage = UI.Page {
backgroundColor = colors.black, backgroundColor = colors.black,
doc = UI.TextArea { doc = UI.TextArea {
backgroundColor = colors.black, backgroundColor = colors.black,
x = 2, y = 2, ex = -1, ey = -7, x = 2, y = 2, ex = -1, ey = -7,
}, },
grid = UI.ScrollingGrid { grid = UI.ScrollingGrid {
y = -6, ey = -2, y = -6, ey = -2,
columns = { columns = {
{ heading = 'Name', key = 'name', width = UI.term.width } { heading = 'Name', key = 'name', width = UI.term.width }
}, },
sortColumn = 'name', sortColumn = 'name',
}, },
statusBar = UI.StatusBar { statusBar = UI.StatusBar {
status = 'q to return', status = 'q to return',
}, },
accelerators = { accelerators = {
q = 'back', q = 'back',
backspace = 'back', backspace = 'back',
}, },
} }
function methodsPage:enable(p) function methodsPage:enable(p)
self.peripheral = p or self.peripheral self.peripheral = p or self.peripheral
p = peripheral.wrap(self.peripheral.side) p = peripheral.wrap(self.peripheral.side)
if p.getDocs then if p.getDocs then
-- plethora -- plethora
self.grid.values = { } self.grid.values = { }
for k,v in pairs(p.getDocs()) do for k,v in pairs(p.getDocs()) do
table.insert(self.grid.values, { table.insert(self.grid.values, {
name = k, name = k,
doc = v, doc = v,
}) })
end end
elseif not p.getAdvancedMethodsData then elseif not p.getAdvancedMethodsData then
-- computercraft -- computercraft
self.grid.values = { } self.grid.values = { }
for name in pairs(p) do for name in pairs(p) do
table.insert(self.grid.values, { table.insert(self.grid.values, {
name = name, name = name,
noext = true, noext = true,
}) })
end end
else else
-- open peripherals -- open peripherals
self.grid.values = p.getAdvancedMethodsData() self.grid.values = p.getAdvancedMethodsData()
for name,f in pairs(self.grid.values) do for name,f in pairs(self.grid.values) do
f.name = name f.name = name
end end
end end
self.grid:update() self.grid:update()
self.grid:setIndex(1) self.grid:setIndex(1)
self.doc:setText(self:getDocumentation()) self.doc:setText(self:getDocumentation())
self.statusBar:setStatus(self.peripheral.type) self.statusBar:setStatus(self.peripheral.type)
UI.Page.enable(self) UI.Page.enable(self)
self:setFocus(self.grid) self:setFocus(self.grid)
end end
function methodsPage:eventHandler(event) function methodsPage:eventHandler(event)
if event.type == 'back' then if event.type == 'back' then
UI:setPage(peripheralsPage) UI:setPage(peripheralsPage)
return true return true
elseif event.type == 'grid_focus_row' then elseif event.type == 'grid_focus_row' then
self.doc:setText(self:getDocumentation()) self.doc:setText(self:getDocumentation())
end end
return UI.Page.eventHandler(self, event) return UI.Page.eventHandler(self, event)
end end
function methodsPage:getDocumentation() function methodsPage:getDocumentation()
local method = self.grid:getSelected() local method = self.grid:getSelected()
if method.noext then -- computercraft docs if method.noext then -- computercraft docs
return 'No documentation' return 'No documentation'
end end
if method.doc then -- plethora docs if method.doc then -- plethora docs
return Ansi.yellow .. method.doc return Ansi.yellow .. method.doc
end end
-- open peripherals docs -- open peripherals docs
local sb = { } local sb = { }
if method.description then if method.description then
table.insert(sb, method.description .. '\n\n') table.insert(sb, method.description .. '\n\n')
end end
if method.returnTypes ~= '()' then if method.returnTypes ~= '()' then
table.insert(sb, Ansi.yellow .. method.returnTypes .. ' ') table.insert(sb, Ansi.yellow .. method.returnTypes .. ' ')
end end
table.insert(sb, Ansi.blue .. method.name .. Ansi.reset .. '(') table.insert(sb, Ansi.blue .. method.name .. Ansi.reset .. '(')
for k,arg in ipairs(method.args) do for k,arg in ipairs(method.args) do
if arg.optional then if arg.optional then
table.insert(sb, Ansi.orange .. string.format('[%s]', arg.name)) table.insert(sb, Ansi.orange .. string.format('[%s]', arg.name))
else else
table.insert(sb, Ansi.green .. arg.name) table.insert(sb, Ansi.green .. arg.name)
end end
if k < #method.args then if k < #method.args then
table.insert(sb, ',') table.insert(sb, ',')
end end
end end
table.insert(sb, Ansi.reset .. ')') table.insert(sb, Ansi.reset .. ')')
Util.filterInplace(method.args, function(a) return #a.description > 0 end) Util.filterInplace(method.args, function(a) return #a.description > 0 end)
if #method.args > 0 then if #method.args > 0 then
table.insert(sb, '\n\n') table.insert(sb, '\n\n')
for k,arg in ipairs(method.args) do for k,arg in ipairs(method.args) do
if arg.optional then if arg.optional then
table.insert(sb, Ansi.orange) table.insert(sb, Ansi.orange)
else else
table.insert(sb, Ansi.green) table.insert(sb, Ansi.green)
end end
table.insert(sb, arg.name .. Ansi.reset .. ': ' .. arg.description) table.insert(sb, arg.name .. Ansi.reset .. ': ' .. arg.description)
if k ~= #method.args then if k ~= #method.args then
table.insert(sb, '\n\n') table.insert(sb, '\n\n')
end end
end end
end end
return table.concat(sb) return table.concat(sb)
end end
Event.on('peripheral', function() Event.on('peripheral', function()
peripheralsPage:updatePeripherals() peripheralsPage:updatePeripherals()
end) end)
Event.on('peripheral_detach', function() Event.on('peripheral_detach', function()
peripheralsPage:updatePeripherals() peripheralsPage:updatePeripherals()
end) end)
UI:setPage(peripheralsPage) UI:setPage(peripheralsPage)
UI:setPages({ UI:setPages({
methods = methodsPage, methods = methodsPage,
}) })
UI:pullEvents() UI:pullEvents()

View File

@@ -10,230 +10,230 @@ local peripheral = _G.peripheral
local drives = { } local drives = { }
peripheral.find('drive', function(n, v) peripheral.find('drive', function(n, v)
if not drives.left then if not drives.left then
drives.left = Util.shallowCopy(v) drives.left = Util.shallowCopy(v)
drives.left.name = n drives.left.name = n
else else
drives.right = Util.shallowCopy(v) drives.right = Util.shallowCopy(v)
drives.right.name = n drives.right.name = n
end end
end) end)
if not (drives.left and drives.right) then if not (drives.left and drives.right) then
error('Two drives are required') error('Two drives are required')
end end
local COPY_LEFT = 1 local COPY_LEFT = 1
local COPY_RIGHT = 2 local COPY_RIGHT = 2
local directions = { local directions = {
[ COPY_LEFT ] = { text = '-->>' }, [ COPY_LEFT ] = { text = '-->>' },
[ COPY_RIGHT ] = { text = '<<--' }, [ COPY_RIGHT ] = { text = '<<--' },
} }
local config = Config.load('DiskCopy', { local config = Config.load('DiskCopy', {
eject = true, eject = true,
automatic = false, automatic = false,
copyDir = COPY_LEFT copyDir = COPY_LEFT
}) })
local page = UI.Page { local page = UI.Page {
linfo = UI.Window { linfo = UI.Window {
x = 2, y = 2, ey = 5, width = 18, x = 2, y = 2, ey = 5, width = 18,
}, },
rinfo = UI.Window { rinfo = UI.Window {
x = -19, y = 2, ey = 5, width = 18, x = -19, y = 2, ey = 5, width = 18,
}, },
dir = UI.Button { dir = UI.Button {
x = 17, y = 7, width = 6, x = 17, y = 7, width = 6,
event = 'change_dir', event = 'change_dir',
}, },
progress = UI.ProgressBar { progress = UI.ProgressBar {
x = 2, ex = -2, y = -4, x = 2, ex = -2, y = -4,
backgroundColor = colors.black, backgroundColor = colors.black,
}, },
ejectText = UI.Text { ejectText = UI.Text {
x = 2, y = -2, x = 2, y = -2,
value = 'Eject' value = 'Eject'
}, },
eject = UI.Checkbox { eject = UI.Checkbox {
x = 8, y = -2, x = 8, y = -2,
}, },
automaticText = UI.Text { automaticText = UI.Text {
x = 12, y = -2, x = 12, y = -2,
value = 'Copy automatically' value = 'Copy automatically'
}, },
automatic = UI.Checkbox { automatic = UI.Checkbox {
x = 31, y = -2, x = 31, y = -2,
}, },
copyButton = UI.Button { copyButton = UI.Button {
x = -7, y = -2, x = -7, y = -2,
text = 'Copy', text = 'Copy',
event = 'copy', event = 'copy',
inactive = true, inactive = true,
}, },
warning = UI.Text { warning = UI.Text {
x = 2, ex = -2, y = -5, x = 2, ex = -2, y = -5,
align = 'center', align = 'center',
textColor = colors.orange, textColor = colors.orange,
}, },
notification = UI.Notification { }, notification = UI.Notification { },
} }
function page:enable() function page:enable()
Util.merge(self.dir, directions[config.copyDir]) Util.merge(self.dir, directions[config.copyDir])
self.eject.value = config.eject self.eject.value = config.eject
self.automatic.value = config.automatic 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 end
local function isValid(drive) local function isValid(drive)
return drive.isDiskPresent() and drive.getMountPath() return drive.isDiskPresent() and drive.getMountPath()
end end
local function needsLabel(drive) 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 end
function page:drawInfo(drive, textArea) function page:drawInfo(drive, textArea)
local function getLabel() local function getLabel()
return not drive.isDiskPresent() and 'empty' or return not drive.isDiskPresent() and 'empty' or
not drive.getMountPath() and 'invalid' or not drive.getMountPath() and 'invalid' or
drive.getDiskLabel() or 'unlabeled' drive.getDiskLabel() or 'unlabeled'
end end
local function getUsed() local function getUsed()
return isValid(drive) and fs.getSize(drive.getMountPath(), true) or 0 return isValid(drive) and fs.getSize(drive.getMountPath(), true) or 0
end end
local function getFree() local function getFree()
return isValid(drive) and fs.getFreeSpace(drive.getMountPath()) or 0 return isValid(drive) and fs.getFreeSpace(drive.getMountPath()) or 0
end end
textArea:setCursorPos(1, 1) textArea:setCursorPos(1, 1)
textArea:print(string.format('Drive: %s%s%s\nLabel: %s%s%s\nUsed: %s%s%s\nFree: %s%s%s', 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, Ansi.yellow, drive.name, Ansi.reset,
isValid(drive) and Ansi.yellow or Ansi.orange, getLabel():sub(1, 10), 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(getUsed()), Ansi.reset,
Ansi.yellow, Util.toBytes(getFree()), Ansi.reset)) Ansi.yellow, Util.toBytes(getFree()), Ansi.reset))
end end
function page:scan() function page:scan()
local showWarning = needsLabel(drives.left) or needsLabel(drives.right) local showWarning = needsLabel(drives.left) or needsLabel(drives.right)
local valid = isValid(drives.left) and isValid(drives.right) local valid = isValid(drives.left) and isValid(drives.right)
self.warning.value = showWarning and 'Computers must be labeled' self.warning.value = showWarning and 'Computers must be labeled'
self.copyButton.inactive = not valid self.copyButton.inactive = not valid
self:draw() self:draw()
self.progress:centeredWrite(1, 'Analyzing Disks..') self.progress:centeredWrite(1, 'Analyzing Disks..')
self.progress:sync() self.progress:sync()
self:drawInfo(drives.left, self.linfo) self:drawInfo(drives.left, self.linfo)
self:drawInfo(drives.right, self.rinfo) self:drawInfo(drives.right, self.rinfo)
self.progress:clear() self.progress:clear()
end end
function page:copy() function page:copy()
local sdrive = config.copyDir == COPY_LEFT and drives.left or drives.right 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 tdrive = config.copyDir == COPY_LEFT and drives.right or drives.left
local throttle = Util.throttle() local throttle = Util.throttle()
local sourceFiles, targetFiles = { }, { } local sourceFiles, targetFiles = { }, { }
local function getListing(mountPath, path, files) local function getListing(mountPath, path, files)
for _,f in pairs(fs.list(path)) do for _,f in pairs(fs.list(path)) do
local file = fs.combine(path, f) local file = fs.combine(path, f)
if not fs.isReadOnly(file) then if not fs.isReadOnly(file) then
files[string.sub(file, #mountPath + 1)] = true files[string.sub(file, #mountPath + 1)] = true
if fs.isDir(file) then if fs.isDir(file) then
getListing(mountPath, file, files) getListing(mountPath, file, files)
end end
end end
end end
throttle() throttle()
end end
self.progress:centeredWrite(1, 'Computing..') self.progress:centeredWrite(1, 'Computing..')
self.progress:sync() self.progress:sync()
getListing(sdrive.getMountPath(), sdrive.getMountPath(), sourceFiles) getListing(sdrive.getMountPath(), sdrive.getMountPath(), sourceFiles)
getListing(tdrive.getMountPath(), tdrive.getMountPath(), targetFiles) getListing(tdrive.getMountPath(), tdrive.getMountPath(), targetFiles)
local copied = 0 local copied = 0
local totalFiles = Util.size(sourceFiles) local totalFiles = Util.size(sourceFiles)
local function rawCopy(source, target) local function rawCopy(source, target)
if fs.isDir(source) then if fs.isDir(source) then
copied = copied + 1 copied = copied + 1
if not fs.exists(target) then if not fs.exists(target) then
fs.makeDir(target) fs.makeDir(target)
end end
for _,f in pairs(fs.list(source)) do for _,f in pairs(fs.list(source)) do
rawCopy(fs.combine(source, f), fs.combine(target, f)) rawCopy(fs.combine(source, f), fs.combine(target, f))
end end
else else
if fs.exists(target) then if fs.exists(target) then
fs.delete(target) fs.delete(target)
end end
fs.copy(source, target) fs.copy(source, target)
copied = copied + 1 copied = copied + 1
self.progress.value = copied * 100 / totalFiles self.progress.value = copied * 100 / totalFiles
self.progress:draw() self.progress:draw()
self.progress:sync() self.progress:sync()
end end
throttle() throttle()
end end
local function cleanup() local function cleanup()
for k in pairs(targetFiles) do for k in pairs(targetFiles) do
if not sourceFiles[k] then if not sourceFiles[k] then
fs.delete(fs.combine(tdrive.getMountPath(), k)) fs.delete(fs.combine(tdrive.getMountPath(), k))
end end
end end
end end
self.progress:clear() self.progress:clear()
rawCopy(sdrive.getMountPath(), tdrive.getMountPath()) rawCopy(sdrive.getMountPath(), tdrive.getMountPath())
cleanup() cleanup()
self.progress:centeredWrite(1, 'Copy Complete', colors.lime, colors.black) self.progress:centeredWrite(1, 'Copy Complete', colors.lime, colors.black)
self.progress:sync() self.progress:sync()
self.progress.value = 0 self.progress.value = 0
self.progress:clear() self.progress:clear()
self:scan() self:scan()
if config.eject then if config.eject then
tdrive.ejectDisk() tdrive.ejectDisk()
end end
end end
function page:eventHandler(event) function page:eventHandler(event)
if event.type == 'change_dir' then if event.type == 'change_dir' then
config.copyDir = (config.copyDir) % 2 + 1 config.copyDir = (config.copyDir) % 2 + 1
Util.merge(self.dir, directions[config.copyDir]) Util.merge(self.dir, directions[config.copyDir])
Config.update('DiskCopy', config) Config.update('DiskCopy', config)
self.dir:draw() self.dir:draw()
elseif event.type == 'copy' then elseif event.type == 'copy' then
self:copy() self:copy()
elseif event.type == 'checkbox_change' then elseif event.type == 'checkbox_change' then
if event.element == self.eject then if event.element == self.eject then
config.eject = not not event.checked config.eject = not not event.checked
elseif event.element == self.automatic then elseif event.element == self.automatic then
config.automatic = not not event.checked config.automatic = not not event.checked
end end
Config.update('DiskCopy', config) Config.update('DiskCopy', config)
event.element:draw() event.element:draw()
else else
return UI.Page.eventHandler(self, event) return UI.Page.eventHandler(self, event)
@@ -242,22 +242,22 @@ function page:eventHandler(event)
end end
Event.on("disk", function() Event.on("disk", function()
page:scan() page:scan()
page:sync() page:sync()
if config.automatic and not page.copyButton.inactive then if config.automatic and not page.copyButton.inactive then
page:copy() page:copy()
end end
end) end)
Event.on("disk_eject", function() Event.on("disk_eject", function()
page:scan() page:scan()
page:sync() page:sync()
end) end)
Event.onTimeout(.2, function() Event.onTimeout(.2, function()
page:scan() page:scan()
page:sync() page:sync()
end) end)
UI:setPage(page) UI:setPage(page)

View File

@@ -8,131 +8,131 @@ local os = _G.os
UI:configure('Events', ...) UI:configure('Events', ...)
local page = UI.Page { local page = UI.Page {
menuBar = UI.MenuBar { menuBar = UI.MenuBar {
buttons = { buttons = {
{ text = 'Filter', event = 'filter' }, { text = 'Filter', event = 'filter' },
{ text = 'Reset', event = 'reset' }, { text = 'Reset', event = 'reset' },
{ text = 'Pause ', event = 'toggle', name = 'pauseButton' }, { text = 'Pause ', event = 'toggle', name = 'pauseButton' },
}, },
}, },
grid = UI.Grid { grid = UI.Grid {
y = 2, y = 2,
columns = { columns = {
{ key = 'event' }, { key = 'event' },
{ key = 'p1' }, { key = 'p1' },
{ key = 'p2' }, { key = 'p2' },
{ key = 'p3' }, { key = 'p3' },
{ key = 'p4' }, { key = 'p4' },
{ key = 'p5' }, { key = 'p5' },
}, },
autospace = true, autospace = true,
disableHeader = true, disableHeader = true,
}, },
accelerators = { accelerators = {
f = 'filter', f = 'filter',
p = 'toggle', p = 'toggle',
r = 'reset', r = 'reset',
c = 'clear', c = 'clear',
q = 'quit', q = 'quit',
}, },
filtered = { }, filtered = { },
} }
function page:eventHandler(event) function page:eventHandler(event)
if event.type == 'filter' then if event.type == 'filter' then
local entry = self.grid:getSelected() local entry = self.grid:getSelected()
self.filtered[entry.event] = true self.filtered[entry.event] = true
elseif event.type == 'toggle' then elseif event.type == 'toggle' then
self.paused = not self.paused self.paused = not self.paused
if self.paused then if self.paused then
self.menuBar.pauseButton.text = 'Resume' self.menuBar.pauseButton.text = 'Resume'
else else
self.menuBar.pauseButton.text = 'Pause ' self.menuBar.pauseButton.text = 'Pause '
end end
self.menuBar:draw() self.menuBar:draw()
elseif event.type == 'grid_select' then elseif event.type == 'grid_select' then
multishell.openTab({ multishell.openTab({
path = 'sys/apps/Lua.lua', path = 'sys/apps/Lua.lua',
args = { event.selected }, args = { event.selected },
focused = true, focused = true,
}) })
elseif event.type == 'reset' then elseif event.type == 'reset' then
self.filtered = { } self.filtered = { }
self.grid:setValues({ }) self.grid:setValues({ })
self.grid:draw() self.grid:draw()
if self.paused then if self.paused then
self:emit({ type = 'toggle' }) self:emit({ type = 'toggle' })
end end
elseif event.type == 'clear' then elseif event.type == 'clear' then
self.grid:setValues({ }) self.grid:setValues({ })
self.grid:draw() self.grid:draw()
elseif event.type == 'quit' then elseif event.type == 'quit' then
UI:exitPullEvents() UI:exitPullEvents()
--[[ --[[
elseif event.type == 'focus_change' then elseif event.type == 'focus_change' then
if event.focused == self.grid then if event.focused == self.grid then
if not self.paused then if not self.paused then
self:emit({ type = 'toggle' }) self:emit({ type = 'toggle' })
end end
end end
--]] --]]
else else
return UI.Page.eventHandler(self, event) return UI.Page.eventHandler(self, event)
end end
return true return true
end end
function page.grid:getDisplayValues(row) function page.grid:getDisplayValues(row)
row = Util.shallowCopy(row) row = Util.shallowCopy(row)
local function tovalue(s) local function tovalue(s)
if type(s) == 'table' then if type(s) == 'table' then
return 'table' return 'table'
end end
return s return s
end end
for k,v in pairs(row) do for k,v in pairs(row) do
row[k] = tovalue(v) row[k] = tovalue(v)
end end
return row return row
end end
function page.grid:draw() function page.grid:draw()
self:adjustWidth() self:adjustWidth()
UI.Grid.draw(self) UI.Grid.draw(self)
end end
Event.addRoutine(function() Event.addRoutine(function()
while true do while true do
local e = { os.pullEvent() } local e = { os.pullEvent() }
if not page.paused and not page.filtered[e[1]] then if not page.paused and not page.filtered[e[1]] then
table.insert(page.grid.values, 1, { table.insert(page.grid.values, 1, {
event = e[1], event = e[1],
p1 = e[2], p1 = e[2],
p2 = e[3], p2 = e[3],
p3 = e[4], p3 = e[4],
p4 = e[5], p4 = e[5],
p5 = e[6], p5 = e[6],
}) })
if #page.grid.values > page.grid.height then if #page.grid.values > page.grid.height then
table.remove(page.grid.values, #page.grid.values) table.remove(page.grid.values, #page.grid.values)
end end
page.grid:update() page.grid:update()
page.grid:draw() page.grid:draw()
page:sync() page:sync()
end end
end end
end) end)
UI:setPage(page) UI:setPage(page)

View File

@@ -15,32 +15,64 @@ local gpt = GPS.getPoint() or error('GPS not found')
local pts, blocks local pts, blocks
local page = UI.Page { local page = UI.Page {
mode = UI.Chooser { menuBar = UI.MenuBar {
x = 13, y = -1, buttons = {
choices = { { text = 'Range', event = 'range' },
{ name = 'No breaking', value = 'digNone' }, { text = 'Stop', event = 'stop' },
{ name = 'Destructive', value = 'turtleSafe' }, },
}, mode = UI.Chooser {
value = 'digNone', x = -16,
}, choices = {
grid = UI.ScrollingGrid { { name = 'No breaking', value = 'digNone' },
y = 2, ey = -2, { name = 'Destructive', value = 'turtleSafe' },
columns = { },
{ heading = 'Label', key = 'label' }, value = 'digNone',
{ heading = 'Dist', key = 'distance' }, },
{ heading = 'Status', key = 'status' }, },
{ heading = 'Fuel', key = 'fuel' }, grid = UI.ScrollingGrid {
}, y = 2, ey = -2,
sortColumn = 'distance', columns = {
autospace = true, { 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) function page.grid:getRowTextColor(row, selected)
if swarm.pool[row.id] then if swarm.pool[row.id] then
return colors.yellow return colors.yellow
end end
return UI.ScrollingGrid.getRowTextColor(self, row, selected) return UI.ScrollingGrid.getRowTextColor(self, row, selected)
end end
function page.grid:getDisplayValues(row) function page.grid:getDisplayValues(row)
@@ -55,130 +87,155 @@ function page.grid:getDisplayValues(row)
end end
function page:enable() function page:enable()
local function update() local function update()
local t = { } local t = { }
for _,v in pairs(network) do for _,v in pairs(network) do
if v.fuel and v.active and v.fuel > 0 and v.distance then if v.fuel and v.active and v.fuel > 0 and v.distance then
table.insert(t, v) table.insert(t, v)
end end
end end
self.grid:setValues(t) self.grid:setValues(t)
end end
Event.onInterval(3, function() Event.onInterval(3, function()
update() update()
self.grid:draw() self.grid:draw()
self:sync() self:sync()
end) end)
update() update()
UI.Page.enable(self) UI.Page.enable(self)
end end
local function follow(member) local function follow(member)
local turtle = member.turtle local turtle = member.turtle
turtle.reset() turtle.reset()
turtle.set({ turtle.set({
digPolicy = page.mode.value, digPolicy = page.menuBar.mode.value,
status = 'Following', status = 'Following',
}) })
if not turtle.enableGPS(nil, true) then if not turtle.enableGPS(nil, true) then
error('turtle: No GPS found') error('turtle: No GPS found')
end end
member.snmp = Socket.connect(member.id, 161) member.snmp = Socket.connect(member.id, 161)
member.snmp.co = coroutine.running() member.snmp.co = coroutine.running()
local pt local pt
while true do while true do
while pt and Point.same(gpt, pt) do while pt and Point.same(gpt, pt) do
os.sleep(.5) os.sleep(.5)
end end
pt = Point.copy(gpt) pt = Point.copy(gpt)
local cpt = Point.closest(turtle.getPoint(), pts) local cpt = Point.closest(turtle.getPoint(), pts)
turtle.abort(false) turtle.abort(false)
if turtle.pathfind(cpt, { blocks = blocks }) then if turtle.pathfind(cpt, { blocks = blocks }) then
turtle.headTowards(pt) turtle.headTowards(pt)
end end
end end
end end
function swarm:onRemove(member, status, message) function swarm:onRemove(member, status, message)
if member.socket then if member.socket then
pcall(function() pcall(function()
member.turtle.set({ status = 'idle' }) member.turtle.set({ status = 'idle' })
member.turtle.abort(true) member.turtle.abort(true)
end) end)
end end
if member.snmp then if member.snmp then
member.snmp:close() member.snmp:close()
member.snmp = nil member.snmp = nil
end end
if not status then if not status then
_G._syslog(message) _G._syslog(message)
end end
end end
function page:eventHandler(event) function page:eventHandler(event)
if event.type == 'grid_select' then if event.type == 'grid_select' then
if not swarm.pool[event.selected.id] then if not swarm.pool[event.selected.id] then
swarm:add(event.selected.id) swarm:add(event.selected.id)
swarm:run(follow) swarm:run(follow)
else else
swarm:remove(event.selected.id) swarm:remove(event.selected.id)
end end
self.grid:draw() self.grid:draw()
elseif event.type == 'choice_change' then elseif event.type == 'choice_change' then
local script = string.format('turtle.set({ digPolicy = "%s"})', event.value) local script = string.format('turtle.set({ digPolicy = "%s"})', event.value)
for _, member in pairs(swarm.pool) do for _, member in pairs(swarm.pool) do
member.snmp:write({ type = 'scriptEx', args = script }) member.snmp:write({ type = 'scriptEx', args = script })
end end
else elseif event.type == 'stop' then
return UI.Page.eventHandler(self, event) for id in pairs(swarm.pool) do
end swarm:remove(id)
return true 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 end
Event.addRoutine(function() Event.addRoutine(function()
while true do while true do
local pt = GPS.getPoint() local pt = GPS.getPoint()
if not pts or (pt and not Point.same(pt, gpt)) then if not pts or (pt and not Point.same(pt, gpt)) then
gpt = pt gpt = pt
pts = { pts = {
{ x = pt.x + 2, z = pt.z, y = pt.y }, { x = pt.x + 2, z = pt.z, y = pt.y },
{ 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 },
{ x = pt.x, z = pt.z - 2, y = pt.y }, { x = pt.x, z = pt.z - 2, y = pt.y },
} }
blocks = { } blocks = { }
local function addBlocks(tpt) local function addBlocks(tpt)
table.insert(blocks, tpt) table.insert(blocks, tpt)
local apts = Point.adjacentPoints(tpt) local apts = Point.adjacentPoints(tpt)
for _,apt in pairs(apts) do for _,apt in pairs(apts) do
table.insert(blocks, apt) table.insert(blocks, apt)
end end
end end
-- don't run into player -- don't run into player
addBlocks(pt) addBlocks(pt)
addBlocks(Point.above(pt)) addBlocks(Point.above(pt))
for _, member in pairs(swarm.pool) do for _, member in pairs(swarm.pool) do
if member.snmp then if member.snmp then
member.snmp:write({ type = 'scriptEx', args = 'turtle.abort(true)' }) member.snmp:write({ type = 'scriptEx', args = 'turtle.abort(true)' })
end end
end end
end end
os.sleep(1) os.sleep(1)
end end
end) end)
UI:setPage(page) UI:setPage(page)

View File

@@ -21,12 +21,12 @@ local config = { }
Config.load('Turtles', config) Config.load('Turtles', config)
local options = { local options = {
turtle = { arg = 'i', type = 'number', value = config.id or -1, turtle = { arg = 'i', type = 'number', value = config.id or -1,
desc = 'Turtle ID' }, desc = 'Turtle ID' },
tab = { arg = 's', type = 'string', value = config.tab or 'Sel', tab = { arg = 's', type = 'string', value = config.tab or 'Sel',
desc = 'Selected tab to display' }, desc = 'Selected tab to display' },
help = { arg = 'h', type = 'flag', value = false, help = { arg = 'h', type = 'flag', value = false,
desc = 'Displays the options' }, desc = 'Displays the options' },
} }
local SCRIPTS_PATH = 'packages/common/etc/scripts' local SCRIPTS_PATH = 'packages/common/etc/scripts'
@@ -35,343 +35,343 @@ local nullTerm = Terminal.getNullTerm(term.current())
local socket local socket
local page = UI.Page { local page = UI.Page {
coords = UI.Window { coords = UI.Window {
backgroundColor = colors.black, backgroundColor = colors.black,
height = 3, height = 3,
}, },
tabs = UI.Tabs { tabs = UI.Tabs {
x = 1, y = 4, ey = -2, x = 1, y = 4, ey = -2,
scripts = UI.ScrollingGrid { scripts = UI.ScrollingGrid {
tabTitle = 'Run', tabTitle = 'Run',
backgroundColor = colors.cyan, backgroundColor = colors.cyan,
columns = { columns = {
{ heading = '', key = 'label' }, { heading = '', key = 'label' },
}, },
disableHeader = true, disableHeader = true,
sortColumn = 'label', sortColumn = 'label',
autospace = true, autospace = true,
}, },
turtles = UI.ScrollingGrid { turtles = UI.ScrollingGrid {
tabTitle = 'Select', tabTitle = 'Select',
backgroundColor = colors.cyan, backgroundColor = colors.cyan,
columns = { columns = {
{ heading = 'label', key = 'label' }, { heading = 'label', key = 'label' },
{ heading = 'Dist', key = 'distance' }, { heading = 'Dist', key = 'distance' },
{ heading = 'Status', key = 'status' }, { heading = 'Status', key = 'status' },
{ heading = 'Fuel', key = 'fuel' }, { heading = 'Fuel', key = 'fuel' },
}, },
disableHeader = true, disableHeader = true,
sortColumn = 'label', sortColumn = 'label',
autospace = true, autospace = true,
}, },
inventory = UI.ScrollingGrid { inventory = UI.ScrollingGrid {
backgroundColor = colors.cyan, backgroundColor = colors.cyan,
tabTitle = 'Inv', tabTitle = 'Inv',
columns = { columns = {
{ heading = '', key = 'index', width = 2 }, { heading = '', key = 'index', width = 2 },
{ heading = '', key = 'qty', width = 2 }, { heading = '', key = 'qty', width = 2 },
{ heading = 'Inventory', key = 'id', width = UI.term.width - 7 }, { heading = 'Inventory', key = 'id', width = UI.term.width - 7 },
}, },
disableHeader = true, disableHeader = true,
sortColumn = 'index', sortColumn = 'index',
}, },
--[[ --[[
policy = UI.ScrollingGrid { policy = UI.ScrollingGrid {
tabTitle = 'Mod', tabTitle = 'Mod',
backgroundColor = UI.TabBar.defaults.selectedBackgroundColor, backgroundColor = UI.TabBar.defaults.selectedBackgroundColor,
columns = { columns = {
{ heading = 'label', key = 'label' }, { heading = 'label', key = 'label' },
}, },
values = policies, values = policies,
disableHeader = true, disableHeader = true,
sortColumn = 'label', sortColumn = 'label',
autospace = true, autospace = true,
}, },
]] ]]
action = UI.Window { action = UI.Window {
tabTitle = 'Action', tabTitle = 'Action',
backgroundColor = colors.cyan, backgroundColor = colors.cyan,
moveUp = UI.Button { moveUp = UI.Button {
x = 5, y = 2, x = 5, y = 2,
text = 'up', text = 'up',
fn = 'turtle.up', fn = 'turtle.up',
}, },
moveDown = UI.Button { moveDown = UI.Button {
x = 5, y = 4, x = 5, y = 4,
text = 'dn', text = 'dn',
fn = 'turtle.down', fn = 'turtle.down',
}, },
moveForward = UI.Button { moveForward = UI.Button {
x = 9, y = 3, x = 9, y = 3,
text = 'f', text = 'f',
fn = 'turtle.forward', fn = 'turtle.forward',
}, },
moveBack = UI.Button { moveBack = UI.Button {
x = 2, y = 3, x = 2, y = 3,
text = 'b', text = 'b',
fn = 'turtle.back', fn = 'turtle.back',
}, },
turnLeft = UI.Button { turnLeft = UI.Button {
x = 2, y = 6, x = 2, y = 6,
text = 'lt', text = 'lt',
fn = 'turtle.turnLeft', fn = 'turtle.turnLeft',
}, },
turnRight = UI.Button { turnRight = UI.Button {
x = 8, y = 6, x = 8, y = 6,
text = 'rt', text = 'rt',
fn = 'turtle.turnRight', fn = 'turtle.turnRight',
}, },
info = UI.TextArea { info = UI.TextArea {
x = 15, y = 2, x = 15, y = 2,
inactive = true, inactive = true,
} }
}, },
}, },
statusBar = UI.StatusBar { statusBar = UI.StatusBar {
values = { }, values = { },
columns = { columns = {
{ key = 'status' }, { key = 'status' },
{ key = 'distance', width = 6 }, { key = 'distance', width = 6 },
{ key = 'fuel', width = 6 }, { key = 'fuel', width = 6 },
}, },
}, },
notification = UI.Notification(), notification = UI.Notification(),
accelerators = { accelerators = {
q = 'quit', q = 'quit',
}, },
} }
function page:enable(turtle) function page:enable(turtle)
self.turtle = turtle self.turtle = turtle
UI.Page.enable(self) UI.Page.enable(self)
end end
function page:runFunction(script, nowrap) function page:runFunction(script, nowrap)
for _ = 1, 2 do for _ = 1, 2 do
if not socket then if not socket then
socket = Socket.connect(self.turtle.id, 161) socket = Socket.connect(self.turtle.id, 161)
end end
if socket then if socket then
if not nowrap then if not nowrap then
script = 'turtle.run(' .. script .. ')' script = 'turtle.run(' .. script .. ')'
end end
if socket:write({ type = 'scriptEx', args = script }) then if socket:write({ type = 'scriptEx', args = script }) then
local t = socket:read(3) local t = socket:read(3)
if t then if t then
return table.unpack(t) return table.unpack(t)
end end
return false, 'Socket timeout' return false, 'Socket timeout'
end end
end end
socket = nil socket = nil
end end
self.notification:error('Unable to connect') self.notification:error('Unable to connect')
end end
function page:runScript(scriptName) function page:runScript(scriptName)
if self.turtle then if self.turtle then
self.notification:info('Connecting') self.notification:info('Connecting')
self:sync() self:sync()
local cmd = string.format('Script %d %s', self.turtle.id, scriptName) local cmd = string.format('Script %d %s', self.turtle.id, scriptName)
local ot = term.redirect(nullTerm) local ot = term.redirect(nullTerm)
pcall(function() shell.run(cmd) end) pcall(function() shell.run(cmd) end)
term.redirect(ot) term.redirect(ot)
self.notification:success('Sent') self.notification:success('Sent')
end end
end end
function page.coords:draw() function page.coords:draw()
local t = self.parent.turtle local t = self.parent.turtle
self:clear() self:clear()
if t then if t then
self:setCursorPos(2, 2) self:setCursorPos(2, 2)
local ind = 'GPS' local ind = 'GPS'
if not t.point.gps then if not t.point.gps then
ind = 'REL' ind = 'REL'
end end
self:print(string.format('%s : %d,%d,%d', self:print(string.format('%s : %d,%d,%d',
ind, t.point.x, t.point.y, t.point.z)) ind, t.point.x, t.point.y, t.point.z))
end end
end end
--[[ Inventory Tab ]]-- --[[ Inventory Tab ]]--
function page.tabs.inventory:getRowTextColor(row, selected) function page.tabs.inventory:getRowTextColor(row, selected)
if page.turtle and row.selected then if page.turtle and row.selected then
return colors.yellow return colors.yellow
end end
return UI.ScrollingGrid.getRowTextColor(self, row, selected) return UI.ScrollingGrid.getRowTextColor(self, row, selected)
end end
function page.tabs.inventory:draw() function page.tabs.inventory:draw()
local t = page.turtle local t = page.turtle
Util.clear(self.values) Util.clear(self.values)
if t then if t then
for _,v in ipairs(t.inventory) do for _,v in ipairs(t.inventory) do
if v.qty > 0 then if v.qty > 0 then
table.insert(self.values, v) table.insert(self.values, v)
if v.index == t.slotIndex then if v.index == t.slotIndex then
v.selected = true v.selected = true
end end
if v.id then if v.id then
v.id = itemDB:getName(v) v.id = itemDB:getName(v)
end end
end end
end end
end end
self:adjustWidth() self:adjustWidth()
self:update() self:update()
UI.ScrollingGrid.draw(self) UI.ScrollingGrid.draw(self)
end end
function page.tabs.inventory:eventHandler(event) function page.tabs.inventory:eventHandler(event)
if event.type == 'grid_select' then if event.type == 'grid_select' then
local fn = string.format('turtle.select(%d)', event.selected.index) local fn = string.format('turtle.select(%d)', event.selected.index)
page:runFunction(fn) page:runFunction(fn)
else else
return UI.ScrollingGrid.eventHandler(self, event) return UI.ScrollingGrid.eventHandler(self, event)
end end
return true return true
end end
function page.tabs.scripts:draw() function page.tabs.scripts:draw()
Util.clear(self.values) Util.clear(self.values)
local files = fs.list(SCRIPTS_PATH) local files = fs.list(SCRIPTS_PATH)
for _,path in pairs(files) do for _,path in pairs(files) do
table.insert(self.values, { label = path, path = fs.combine(SCRIPTS_PATH, path) }) table.insert(self.values, { label = path, path = fs.combine(SCRIPTS_PATH, path) })
end end
self:update() self:update()
UI.ScrollingGrid.draw(self) UI.ScrollingGrid.draw(self)
end end
function page.tabs.scripts:eventHandler(event) function page.tabs.scripts:eventHandler(event)
if event.type == 'grid_select' then if event.type == 'grid_select' then
page:runScript(event.selected.label) page:runScript(event.selected.label)
else else
return UI.ScrollingGrid.eventHandler(self, event) return UI.ScrollingGrid.eventHandler(self, event)
end end
return true return true
end end
function page.tabs.turtles:getDisplayValues(row) function page.tabs.turtles:getDisplayValues(row)
row = Util.shallowCopy(row) row = Util.shallowCopy(row)
if row.fuel then if row.fuel then
row.fuel = Util.toBytes(row.fuel) row.fuel = Util.toBytes(row.fuel)
end end
if row.distance then if row.distance then
row.distance = Util.round(row.distance, 1) row.distance = Util.round(row.distance, 1)
end end
return row return row
end end
function page.tabs.turtles:draw() function page.tabs.turtles:draw()
Util.clear(self.values) Util.clear(self.values)
for _,v in pairs(network) do for _,v in pairs(network) do
if v.fuel then if v.fuel then
table.insert(self.values, v) table.insert(self.values, v)
end end
end end
self:update() self:update()
UI.ScrollingGrid.draw(self) UI.ScrollingGrid.draw(self)
end end
function page.tabs.turtles:eventHandler(event) function page.tabs.turtles:eventHandler(event)
if event.type == 'grid_select' then if event.type == 'grid_select' then
page.turtle = event.selected page.turtle = event.selected
config.id = event.selected.id config.id = event.selected.id
Config.update('Turtles', config) Config.update('Turtles', config)
multishell.setTitle(multishell.getCurrent(), page.turtle.label) multishell.setTitle(multishell.getCurrent(), page.turtle.label)
if socket then if socket then
socket:close() socket:close()
socket = nil socket = nil
end end
else else
return UI.ScrollingGrid.eventHandler(self, event) return UI.ScrollingGrid.eventHandler(self, event)
end end
return true return true
end end
function page.statusBar:draw() function page.statusBar:draw()
local t = self.parent.turtle local t = self.parent.turtle
if t then if t then
self.values.status = t.status self.values.status = t.status
self.values.distance = Util.round(t.distance, 2) self.values.distance = Util.round(t.distance, 2)
self.values.fuel = Util.toBytes(t.fuel) self.values.fuel = Util.toBytes(t.fuel)
end end
UI.StatusBar.draw(self) UI.StatusBar.draw(self)
end end
function page:showBlocks() function page:showBlocks()
local script = [[ local script = [[
local function inspect(direction) local function inspect(direction)
local s,b = turtle['inspect' .. (direction or '')]() local s,b = turtle['inspect' .. (direction or '')]()
if not s then if not s then
return 'minecraft:air:0' return 'minecraft:air:0'
end end
return string.format('%s:%d', b.name, b.metadata) return string.format('%s:%d', b.name, b.metadata)
end end
local bu, bf, bd = inspect('Up'), inspect(), inspect('Down') local bu, bf, bd = inspect('Up'), inspect(), inspect('Down')
return string.format('%s\n%s\n%s', bu, bf, bd) return string.format('%s\n%s\n%s', bu, bf, bd)
]] ]]
local s, m = self:runFunction(script, true) local s, m = self:runFunction(script, true)
self.tabs.action.info:setText(s or m) self.tabs.action.info:setText(s or m)
end end
function page:eventHandler(event) function page:eventHandler(event)
if event.type == 'quit' then if event.type == 'quit' then
UI:exitPullEvents() UI:exitPullEvents()
elseif event.type == 'tab_select' then elseif event.type == 'tab_select' then
config.tab = event.button.text config.tab = event.button.text
Config.update('Turtles', config) Config.update('Turtles', config)
elseif event.type == 'button_press' then elseif event.type == 'button_press' then
if event.button.fn then if event.button.fn then
self:runFunction(event.button.fn, event.button.nowrap) self:runFunction(event.button.fn, event.button.nowrap)
self:showBlocks() self:showBlocks()
elseif event.button.script then elseif event.button.script then
self:runScript(event.button.script) self:runScript(event.button.script)
end end
else else
return UI.Page.eventHandler(self, event) return UI.Page.eventHandler(self, event)
end end
return true return true
end end
function page:enable() function page:enable()
UI.Page.enable(self) UI.Page.enable(self)
-- self.tabs:activateTab(page.tabs.turtles) -- self.tabs:activateTab(page.tabs.turtles)
end end
if not Util.getOptions(options, { ... }, true) then if not Util.getOptions(options, { ... }, true) then
return return
end end
if options.turtle.value >= 0 then if options.turtle.value >= 0 then
for _ = 1, 10 do for _ = 1, 10 do
page.turtle = _G.network[options.turtle.value] page.turtle = _G.network[options.turtle.value]
if page.turtle then if page.turtle then
break break
end end
os.sleep(1) os.sleep(1)
end end
end end
Event.onInterval(1, function() Event.onInterval(1, function()
if page.turtle then if page.turtle then
local t = _G.network[page.turtle.id] local t = _G.network[page.turtle.id]
page.turtle = t page.turtle = t
page:draw() page:draw()
page:sync() page:sync()
end end
end) end)
if config.tab then if config.tab then
page.tabs.tabBar:selectTab(config.tab) page.tabs.tabBar:selectTab(config.tab)
end end
UI:setPage(page) UI:setPage(page)

View File

@@ -1,7 +1,7 @@
local c = function(shell, nIndex, sText) local c = function(shell, nIndex, sText)
if nIndex == 1 then if nIndex == 1 then
return _G.fs.complete(sText, shell.dir(), true, false) return _G.fs.complete(sText, shell.dir(), true, false)
end end
end end
_ENV.shell.setCompletionFunction("packages/common/edit.lua", c) _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 -- hud
local canvas = glasses and glasses.canvas() local canvas = glasses and glasses.canvas()
if canvas then if canvas then
local lh local lh
local function addText(x, y, text, color) local function addText(x, y, text, color)
local th = canvas.group.addText({ x, y }, text, color or 0xa0a0a0FF) local th = canvas.group.addText({ x, y }, text, color or 0xa0a0a0FF)
lh = lh or th.getLineHeight() lh = lh or th.getLineHeight()
th.setShadow(true) th.setShadow(true)
th.setScale(.75) th.setScale(.75)
return th return th
end end
canvas.group = canvas.addGroup({ 4, 90 }) canvas.group = canvas.addGroup({ 4, 90 })
canvas.group.bg = canvas.group.addRectangle(0, 0, 80, 10, 0x40404080) canvas.group.bg = canvas.group.addRectangle(0, 0, 80, 10, 0x40404080)
canvas.group.addLines( canvas.group.addLines(
{ 0, 0 }, { 0, 0 },
{ 80, 0 }, { 80, 0 },
{ 80, 10 }, { 80, 10 },
{ 0, 10 }, { 0, 10 },
{ 0, 0 }, { 0, 0 },
0x202020FF, 0x202020FF,
2) 2)
addText(20, 2, 'Swarm Miner', 0xc0c0c0FF) addText(20, 2, 'Swarm Miner', 0xc0c0c0FF)
local y = 15 local y = 15
addText(3, y, 'Turtles') addText(3, y, 'Turtles')
canvas.turtles = addText(60, y, '') canvas.turtles = addText(60, y, '')
canvas.group.addLine({ 0, y + lh - 2 }, { 80, y + lh - 2 }, 0x404040FF, 4) canvas.group.addLine({ 0, y + lh - 2 }, { 80, y + lh - 2 }, 0x404040FF, 4)
y = y + lh + 5 y = y + lh + 5
addText(3, y, 'Queue') addText(3, y, 'Queue')
canvas.queue = addText(60, y, '') canvas.queue = addText(60, y, '')
canvas.group.addLine({ 0, y + lh - 2 }, { 80, y + lh - 2 }, 0x404040FF, 4) canvas.group.addLine({ 0, y + lh - 2 }, { 80, y + lh - 2 }, 0x404040FF, 4)
end end
-- container -- container
@@ -62,19 +62,19 @@ local box, offset
local paused = false local paused = false
local function inBox(pt) local function inBox(pt)
if not box or not box.ex then if not box or not box.ex then
return true return true
end end
return Point.inBox(pt, box) return Point.inBox(pt, box)
end end
local function locate() local function locate()
for _ = 1, 3 do for _ = 1, 3 do
local pt = GPS.getPoint() local pt = GPS.getPoint()
if pt then if pt then
return pt return pt
end end
end end
end end
local spt = GPS.getPoint() or error('GPS failure') local spt = GPS.getPoint() or error('GPS failure')
@@ -88,7 +88,7 @@ local abort
local function hijackTurtle(remoteId) local function hijackTurtle(remoteId)
local socket, msg = Socket.connect(remoteId, 188) local socket, msg = Socket.connect(remoteId, 188)
if not socket then if not socket then
error(msg) error(msg)
end end
@@ -111,240 +111,240 @@ local function hijackTurtle(remoteId)
end end
local function getNextPoint(turtle) local function getNextPoint(turtle)
if not paused then if not paused then
local pt = Point.closest(turtle.getPoint(), queue) local pt = Point.closest(turtle.getPoint(), queue)
if pt then if pt then
turtle.pt = pt turtle.pt = pt
queue[pt.pkey] = nil queue[pt.pkey] = nil
return pt return pt
end end
end end
end end
local function run(member, point) local function run(member, point)
Event.addRoutine(function() Event.addRoutine(function()
local turtle, socket local turtle, socket
local _, m = pcall(function() local _, m = pcall(function()
member.active = true member.active = true
turtle, socket = hijackTurtle(member.id) turtle, socket = hijackTurtle(member.id)
local function emptySlots(retain, pt) local function emptySlots(retain, pt)
local slots = turtle.getFilledSlots() local slots = turtle.getFilledSlots()
for _,slot in pairs(slots) do for _,slot in pairs(slots) do
if not retain[slot.key] then if not retain[slot.key] then
turtle.select(slot.index) turtle.select(slot.index)
if pt then if pt then
turtle.dropAt(pt, 64) turtle.dropAt(pt, 64)
else else
turtle.dropUp(64) turtle.dropUp(64)
end end
end end
end end
end end
local function dropOff() local function dropOff()
-- go to 2 above chest -- go to 2 above chest
local topPoint = Point.copy(chestPoint) local topPoint = Point.copy(chestPoint)
topPoint.y = topPoint.y + 2 topPoint.y = topPoint.y + 2
turtle.gotoY(topPoint.y) turtle.gotoY(topPoint.y)
while not turtle.go(topPoint) do while not turtle.go(topPoint) do
os.sleep(.5) os.sleep(.5)
end end
-- path to chest -- path to chest
local box = Point.makeBox( local box = Point.makeBox(
{ x = chestPoint.x - 3, y = chestPoint.y + 3, z = chestPoint.z - 3 }, { x = chestPoint.x - 3, y = chestPoint.y + 3, z = chestPoint.z - 3 },
{ x = chestPoint.x + 3, y = chestPoint.y, z = chestPoint.z + 3 } { x = chestPoint.x + 3, y = chestPoint.y, z = chestPoint.z + 3 }
) )
turtle.set({ turtle.set({
movementStrategy = 'pathing', movementStrategy = 'pathing',
pathingBox = Point.normalizeBox(box), pathingBox = Point.normalizeBox(box),
digPolicy = 'digNone', digPolicy = 'digNone',
}) })
while not turtle.moveAgainst(chestPoint) do while not turtle.moveAgainst(chestPoint) do
os.sleep(.5) os.sleep(.5)
end end
emptySlots({ }, chestPoint) emptySlots({ }, chestPoint)
-- path to 3 above chest -- path to 3 above chest
turtle.pathfind(Point.above(topPoint)) turtle.pathfind(Point.above(topPoint))
turtle.set({ turtle.set({
movementStrategy = 'goto', movementStrategy = 'goto',
digPolicy = 'blacklist', digPolicy = 'blacklist',
}) })
end end
if turtle then if turtle then
turtles[member.id] = turtle turtles[member.id] = turtle
turtle.reset() turtle.reset()
turtle.set({ turtle.set({
attackPolicy = 'attack', attackPolicy = 'attack',
digPolicy = 'blacklist', digPolicy = 'blacklist',
blacklist = { blacklist = {
'turtle', 'turtle',
'chest', 'chest',
'shulker', 'shulker',
}, },
movementStrategy = 'goto', movementStrategy = 'goto',
point = point, point = point,
}) })
turtle.select(1) turtle.select(1)
repeat repeat
local pt = getNextPoint(turtle) local pt = getNextPoint(turtle)
if pt then if pt then
member.status = 'digging' member.status = 'digging'
if blockTypes[pt.key] == true then if blockTypes[pt.key] == true then
if turtle.moveAgainst(pt) then if turtle.moveAgainst(pt) then
local index = turtle.selectOpenSlot() local index = turtle.selectOpenSlot()
if turtle.digAt(pt, pt.name) then if turtle.digAt(pt, pt.name) then
local slot = turtle.getSlot(index) local slot = turtle.getSlot(index)
if slot.count > 0 then if slot.count > 0 then
blockTypes[pt.key] = slot.key blockTypes[pt.key] = slot.key
if slot.key ~= pt.key then if slot.key ~= pt.key then
blockTypes[slot.key] = true blockTypes[slot.key] = true
end end
end end
end end
end end
turtle.select(1) turtle.select(1)
else else
turtle.digAt(pt, pt.name) turtle.digAt(pt, pt.name)
end end
if turtle.getItemCount(15) > 0 then if turtle.getItemCount(15) > 0 then
member.status = 'ejecting trash' member.status = 'ejecting trash'
emptySlots(blockTypes) emptySlots(blockTypes)
turtle.condense() turtle.condense()
if turtle.getItemCount(15) > 0 then if turtle.getItemCount(15) > 0 then
member.status = 'dropping off' member.status = 'dropping off'
if not chestPoint then if not chestPoint then
member.abort = true member.abort = true
member.status = 'full' member.status = 'full'
else else
dropOff() dropOff()
end end
end end
turtle.select(1) turtle.select(1)
end end
else else
member.status = 'waiting' member.status = 'waiting'
os.sleep(1) os.sleep(1)
end end
if member.fuel < 100 then if member.fuel < 100 then
member.status = 'out of fuel' member.status = 'out of fuel'
break break
end end
until member.abort until member.abort
end end
emptySlots(blockTypes) emptySlots(blockTypes)
if chestPoint then if chestPoint then
dropOff() dropOff()
while not turtle.go(Point.above(spt)) do while not turtle.go(Point.above(spt)) do
os.sleep(.5) os.sleep(.5)
end end
--if turtle.selectSlotWithQuantity(0) then --if turtle.selectSlotWithQuantity(0) then
--turtle.set({ digPolicy = 'dig' }) --turtle.set({ digPolicy = 'dig' })
--end --end
while not turtle.go(spt) do while not turtle.go(spt) do
os.sleep(.5) os.sleep(.5)
end end
else else
turtle.gotoY(spt.y) turtle.gotoY(spt.y)
while not turtle.go(spt) do while not turtle.go(spt) do
os.sleep(.5) os.sleep(.5)
end end
end end
end) end)
turtles[member.id] = nil turtles[member.id] = nil
member.status = m member.status = m
member.active = false member.active = false
if socket then if socket then
socket:close() socket:close()
end end
end) end)
end end
local function drawContainer(pos) local function drawContainer(pos)
if canvas3d then if canvas3d then
canvas3d.clear() canvas3d.clear()
local function addBox(b) local function addBox(b)
canvas3d.addBox( canvas3d.addBox(
b.x - offset.x + .25, b.x - offset.x + .25,
b.y - offset.y + .25 , b.y - offset.y + .25 ,
b.z - offset.z + .25 , b.z - offset.z + .25 ,
.5, .5, .5).setDepthTested(false) .5, .5, .5).setDepthTested(false)
end end
if box and box.ex then 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.z })
addBox({ x = box.x, y = box.y, z = box.ez }) 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.z })
addBox({ x = box.ex, y = box.y, z = box.ez }) 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.z })
addBox({ x = box.x, y = box.ey, z = box.ez }) 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.z })
addBox({ x = box.ex, y = box.ey, z = box.ez }) addBox({ x = box.ex, y = box.ey, z = box.ez })
elseif box then elseif box then
canvas3d.recenter({ -(pos.x % 1), -(pos.y % 1), -(pos.z % 1) }) canvas3d.recenter({ -(pos.x % 1), -(pos.y % 1), -(pos.z % 1) })
addBox(box) addBox(box)
end end
end end
end end
local pauseResume = { local pauseResume = {
{ text = 'Pause', event = 'pause' }, { text = 'Pause', event = 'pause' },
{ text = 'Resume', event = 'resume' }, { text = 'Resume', event = 'resume' },
} }
local containerText = { local containerText = {
[[Set a corner to contain mining area]], [[Set a corner to contain mining area]],
[[Set ending corner]], [[Set ending corner]],
[[Set again to clear]], [[Set again to clear]],
} }
local containTab = UI.Tab { local containTab = UI.Tab {
tabTitle = 'Contain', tabTitle = 'Contain',
button = UI.Button { button = UI.Button {
x = 2, y = 2, x = 2, y = 2,
text = 'Set corner', text = 'Set corner',
event = 'contain' event = 'contain'
}, },
textArea = UI.TextArea { textArea = UI.TextArea {
x = 2, y = 4, x = 2, y = 4,
value = containerText[1], value = containerText[1],
}, },
} }
local blocksTab = UI.Tab { local blocksTab = UI.Tab {
tabTitle = 'Blocks', tabTitle = 'Blocks',
grid = UI.ScrollingGrid { grid = UI.ScrollingGrid {
y = 1, y = 1,
columns = { columns = {
{ heading = 'Count', key = 'count', width = 6, align = 'right' }, { heading = 'Count', key = 'count', width = 6, align = 'right' },
{ heading = 'Name', key = 'displayName' }, { heading = 'Name', key = 'displayName' },
}, },
sortColumn = 'displayName', sortColumn = 'displayName',
}, },
} }
local turtlesTab = UI.Tab { local turtlesTab = UI.Tab {
tabTitle = 'Turtles', tabTitle = 'Turtles',
grid = UI.ScrollingGrid { grid = UI.ScrollingGrid {
y = 1, y = 1,
values = pool, values = pool,
columns = { columns = {
{ heading = 'ID', key = 'id', width = 5, }, { heading = 'ID', key = 'id', width = 5, },
{ heading = ' Fuel', key = 'fuel', width = 5, align = 'right' }, { heading = ' Fuel', key = 'fuel', width = 5, align = 'right' },
{ heading = ' Dist', key = 'distance', width = 5, align = 'right' }, { heading = ' Dist', key = 'distance', width = 5, align = 'right' },
{ heading = 'Status', key = 'status' }, { heading = 'Status', key = 'status' },
}, },
sortColumn = 'label', sortColumn = 'label',
}, },
} }
local page = UI.Page { local page = UI.Page {
@@ -354,264 +354,264 @@ local page = UI.Page {
{ text = 'Abort', event = 'abort' }, { text = 'Abort', event = 'abort' },
pauseResume[1], pauseResume[1],
}, },
}, },
tabs = UI.Tabs { tabs = UI.Tabs {
y = 2, ey = -2, y = 2, ey = -2,
[1] = blocksTab, [1] = blocksTab,
[2] = turtlesTab, [2] = turtlesTab,
[3] = containTab, [3] = containTab,
}, },
info = UI.Window { info = UI.Window {
y = -1, y = -1,
backgroundColor = colors.blue, backgroundColor = colors.blue,
} }
} }
function page.info:draw() function page.info:draw()
self:clear() self:clear()
self:write(2, 1, 'Turtles: ' .. Util.size(turtles)) self:write(2, 1, 'Turtles: ' .. Util.size(turtles))
if not chestPoint then if not chestPoint then
self:write(16, 1, 'No chest') self:write(16, 1, 'No chest')
end end
self:write(28, 1, 'Queue: ' .. Util.size(queue)) self:write(28, 1, 'Queue: ' .. Util.size(queue))
end end
function turtlesTab.grid:getDisplayValues(row) function turtlesTab.grid:getDisplayValues(row)
row = Util.shallowCopy(row) row = Util.shallowCopy(row)
row.distance = row.distance and Util.round(row.distance, 1) row.distance = row.distance and Util.round(row.distance, 1)
row.fuel = row.fuel and row.fuel > 0 and Util.toBytes(row.fuel) or '' row.fuel = row.fuel and row.fuel > 0 and Util.toBytes(row.fuel) or ''
return row return row
end end
function page:scan() function page:scan()
local gpt = GPS.getPoint() local gpt = GPS.getPoint()
if not gpt then if not gpt then
return return
end end
local rawBlocks = scanner:scan() local rawBlocks = scanner:scan()
local candidates = { } local candidates = { }
self.totals = Util.reduce(rawBlocks, self.totals = Util.reduce(rawBlocks,
function(acc, b) function(acc, b)
b.key = table.concat({ b.name, b.metadata }, ':') b.key = table.concat({ b.name, b.metadata }, ':')
local entry = acc[b.key] local entry = acc[b.key]
if not entry then if not entry then
b.displayName = itemDB:getName(b.key) b.displayName = itemDB:getName(b.key)
b.count = 1 b.count = 1
acc[b.key] = b acc[b.key] = b
else else
entry.count = entry.count + 1 entry.count = entry.count + 1
end end
if b.name == 'computercraft:turtle_advanced' or if b.name == 'computercraft:turtle_advanced' or
b.name == 'computercraft:turtle_expanded' or b.name == 'computercraft:turtle_expanded' or
b.name == 'computercraft:turtle' then b.name == 'computercraft:turtle' then
table.insert(candidates, b) table.insert(candidates, b)
end end
if b.name == 'minecraft:chest' or b.name:find('shulker') then if b.name == 'minecraft:chest' or b.name:find('shulker') then
chestPoint = b chestPoint = b
end end
-- add relevant blocks to queue -- add relevant blocks to queue
b.x = gpt.x + b.x b.x = gpt.x + b.x
b.y = gpt.y + b.y b.y = gpt.y + b.y
b.z = gpt.z + b.z b.z = gpt.z + b.z
b.pkey = table.concat({ b.x, b.y, b.z }, ':') b.pkey = table.concat({ b.x, b.y, b.z }, ':')
if blockTypes[b.key] and inBox(b) then if blockTypes[b.key] and inBox(b) then
if not Util.any(turtles, function(t) if not Util.any(turtles, function(t)
return t.pt and t.pt.pkey == b.pkey return t.pt and t.pt.pkey == b.pkey
end) then end) then
queue[b.pkey] = b queue[b.pkey] = b
end end
else else
queue[b.pkey] = nil queue[b.pkey] = nil
end end
return acc return acc
end, end,
{ }) { })
for _, b in pairs(candidates) do for _, b in pairs(candidates) do
local v = scanner.getBlockMeta(b.x - gpt.x, b.y - gpt.y, b.z - gpt.z) local v = scanner.getBlockMeta(b.x - gpt.x, b.y - gpt.y, b.z - gpt.z)
if v and v.computer then if v and v.computer then
local member = pool[v.computer.id] local member = pool[v.computer.id]
if not member then if not member then
member = { member = {
id = v.computer.id, id = v.computer.id,
label = v.computer.label, label = v.computer.label,
} }
pool[v.computer.id] = member pool[v.computer.id] = member
end end
member.fuel = v.turtle.fuel member.fuel = v.turtle.fuel
member.distance = 0 member.distance = 0
if not v.computer.isOn then if not v.computer.isOn then
member.status = 'Powered off' member.status = 'Powered off'
elseif v.turtle.fuel < 100 and not member.active then elseif v.turtle.fuel < 100 and not member.active then
member.status = 'Not enough fuel' member.status = 'Not enough fuel'
elseif not member.active and not member.abort then elseif not member.active and not member.abort then
local pt = Point.copy(b) local pt = Point.copy(b)
pt.heading = Point.facings[v.state.facing].heading pt.heading = Point.facings[v.state.facing].heading
run(member, pt) run(member, pt)
end end
end end
end end
end end
function blocksTab.grid:getDisplayValues(row) function blocksTab.grid:getDisplayValues(row)
row = Util.shallowCopy(row) row = Util.shallowCopy(row)
row.count = Util.toBytes(row.count) .. ' ' row.count = Util.toBytes(row.count) .. ' '
return row return row
end end
function blocksTab.grid:getRowTextColor(row, selected) function blocksTab.grid:getRowTextColor(row, selected)
return blockTypes[row.key] and return blockTypes[row.key] and
colors.yellow or colors.yellow or
UI.Grid.getRowTextColor(self, row, selected) UI.Grid.getRowTextColor(self, row, selected)
end end
function blocksTab:eventHandler(event) function blocksTab:eventHandler(event)
if event.type == 'grid_select' then if event.type == 'grid_select' then
local key = event.selected.key local key = event.selected.key
if blockTypes[key] then if blockTypes[key] then
for k,v in pairs(queue) do for k,v in pairs(queue) do
if v.key == key then if v.key == key then
queue[k] = nil queue[k] = nil
end end
end end
blockTypes[key] = nil blockTypes[key] = nil
else else
blockTypes[key] = true blockTypes[key] = true
end end
self.grid:draw() self.grid:draw()
end end
end end
function page:eventHandler(event) function page:eventHandler(event)
if event.type == 'scan' then if event.type == 'scan' then
blocksTab.grid:setValues(self.totals) blocksTab.grid:setValues(self.totals)
blocksTab.grid:draw() blocksTab.grid:draw()
self.tabs:selectTab(blocksTab) self.tabs:selectTab(blocksTab)
elseif event.type == 'pause' then elseif event.type == 'pause' then
paused = true paused = true
Util.merge(event.button, pauseResume[2]) Util.merge(event.button, pauseResume[2])
event.button:draw() event.button:draw()
elseif event.type == 'resume' then elseif event.type == 'resume' then
paused = false paused = false
Util.merge(event.button, pauseResume[1]) Util.merge(event.button, pauseResume[1])
event.button:draw() event.button:draw()
elseif event.type == 'contain' then elseif event.type == 'contain' then
local pt = { gps.locate() } local pt = { gps.locate() }
local pos = { local pos = {
x = pt[1], x = pt[1],
y = pt[2], y = pt[2],
z = pt[3], z = pt[3],
} }
if not box then if not box then
offset = { offset = {
x = math.floor(pos.x), x = math.floor(pos.x),
y = math.floor(pos.y), y = math.floor(pos.y),
z = math.floor(pos.z), z = math.floor(pos.z),
} }
box = { box = {
x = math.floor(pos.x), x = math.floor(pos.x),
y = math.floor(pos.y) - 1, y = math.floor(pos.y) - 1,
z = math.floor(pos.z), z = math.floor(pos.z),
} }
containTab.textArea.value = containerText[2] containTab.textArea.value = containerText[2]
elseif not box.ex then elseif not box.ex then
box.ex = math.floor(pos.x) box.ex = math.floor(pos.x)
box.ey = math.floor(pos.y) - 1 box.ey = math.floor(pos.y) - 1
box.ez = math.floor(pos.z) box.ez = math.floor(pos.z)
box = Point.normalizeBox(box) box = Point.normalizeBox(box)
containTab.textArea.value = containerText[3] containTab.textArea.value = containerText[3]
else else
box = nil box = nil
containTab.textArea.value = containerText[1] containTab.textArea.value = containerText[1]
end end
containTab.textArea:draw() containTab.textArea:draw()
drawContainer(pos) drawContainer(pos)
elseif event.type == 'abort' then elseif event.type == 'abort' then
for _, v in pairs(pool) do for _, v in pairs(pool) do
v.abort = true v.abort = true
v.status = 'aborting' v.status = 'aborting'
end end
spt = Point.above(locate()) spt = Point.above(locate())
abort = true abort = true
end end
UI.Page.eventHandler(self, event) UI.Page.eventHandler(self, event)
end end
Event.onInterval(5, function() Event.onInterval(5, function()
if not abort and not paused then if not abort and not paused then
--local meta = scanner.getMetaOwner() --local meta = scanner.getMetaOwner()
--if meta.isSneaking then --if meta.isSneaking then
page:scan() page:scan()
-- Sound.play('entity.bobber.throw', .6) -- Sound.play('entity.bobber.throw', .6)
--end --end
end end
end) end)
Event.onInterval(1, function() Event.onInterval(1, function()
for id,v in pairs(network) do for id,v in pairs(network) do
if v.fuel then if v.fuel then
if pool[id] then if pool[id] then
pool[id].fuel = v.fuel pool[id].fuel = v.fuel
pool[id].distance = v.distance pool[id].distance = v.distance
end end
end end
end end
if abort and Util.size(turtles) == 0 then if abort and Util.size(turtles) == 0 then
Event.exitPullEvents() Event.exitPullEvents()
end end
if turtlesTab.enabled then if turtlesTab.enabled then
turtlesTab.grid:update() turtlesTab.grid:update()
turtlesTab.grid:draw() turtlesTab.grid:draw()
end end
page.info:draw() page.info:draw()
page.info:sync() page.info:sync()
if canvas then if canvas then
canvas.turtles.setText(tostring(Util.size(turtles))) canvas.turtles.setText(tostring(Util.size(turtles)))
canvas.queue.setText(tostring(Util.size(queue))) canvas.queue.setText(tostring(Util.size(queue)))
end end
end) end)
Event.onTimeout(.1, function() Event.onTimeout(.1, function()
page:scan() page:scan()
blocksTab.grid:setValues(page.totals) blocksTab.grid:setValues(page.totals)
blocksTab.grid:draw() blocksTab.grid:draw()
page:sync() page:sync()
end) end)
UI:setPage(page) UI:setPage(page)
--[[ --[[
Event.onTerminate(function() Event.onTerminate(function()
spt = Point.above(locate()) spt = Point.above(locate())
for _, v in pairs(pool) do for _, v in pairs(pool) do
v.status = 'aborting' v.status = 'aborting'
v.abort = true v.abort = true
end end
abort = true abort = true
end) end)
]] ]]
Event.pullEvents() Event.pullEvents()
if canvas then if canvas then
canvas3d.clear() canvas3d.clear()
canvas.group.remove() canvas.group.remove()
end end

View File

@@ -8,154 +8,154 @@ local os = _G.os
local ChestAdapter = class() local ChestAdapter = class()
local convertNames = { local convertNames = {
name = 'id', name = 'id',
damage = 'dmg', damage = 'dmg',
maxCount = 'max_size', maxCount = 'max_size',
count = 'qty', count = 'qty',
displayName = 'display_name', displayName = 'display_name',
maxDamage = 'max_dmg', maxDamage = 'max_dmg',
nbtHash = 'nbt_hash', nbtHash = 'nbt_hash',
} }
-- Strip off color prefix -- Strip off color prefix
local function safeString(text) 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 newText = {}
for i = 4, #text do for i = 4, #text do
val = text:byte(i) val = text:byte(i)
newText[i - 3] = (val > 31 and val < 127) and val or 63 newText[i - 3] = (val > 31 and val < 127) and val or 63
end end
return string.char(unpack(newText)) return string.char(unpack(newText))
end end
return text return text
end end
local function convertItem(item) local function convertItem(item)
for k,v in pairs(convertNames) do for k,v in pairs(convertNames) do
item[k] = item[v] item[k] = item[v]
item[v] = nil item[v] = nil
end end
item.displayName = safeString(item.displayName) item.displayName = safeString(item.displayName)
end end
function ChestAdapter:init(args) function ChestAdapter:init(args)
local defaults = { local defaults = {
name = 'chest', name = 'chest',
} }
Util.merge(self, defaults) Util.merge(self, defaults)
Util.merge(self, args) Util.merge(self, args)
local chest local chest
if not self.side then if not self.side then
chest = Peripheral.getByMethod('getAllStacks') chest = Peripheral.getByMethod('getAllStacks')
else else
chest = Peripheral.getBySide(self.side) chest = Peripheral.getBySide(self.side)
if chest and not chest.getAllStacks then if chest and not chest.getAllStacks then
chest = nil chest = nil
end end
end end
if chest then if chest then
Util.merge(self, chest) Util.merge(self, chest)
if chest.listAvailableItems then if chest.listAvailableItems then
self.list = chest.listAvailableItems self.list = chest.listAvailableItems
end end
end end
end end
function ChestAdapter:isValid() function ChestAdapter:isValid()
return not not self.getAllStacks return not not self.getAllStacks
end end
function ChestAdapter:refresh(throttle) function ChestAdapter:refresh(throttle)
return self:listItems(throttle) return self:listItems(throttle)
end end
-- provide a consolidated list of items -- provide a consolidated list of items
function ChestAdapter:listItems(throttle) function ChestAdapter:listItems(throttle)
local cache = { } local cache = { }
local items = { } local items = { }
throttle = throttle or Util.throttle() throttle = throttle or Util.throttle()
-- getAllStacks sometimes fails -- getAllStacks sometimes fails
local s, m = pcall(function() local s, m = pcall(function()
for _,v in pairs(self.getAllStacks(false)) do for _,v in pairs(self.getAllStacks(false)) do
if v.qty > 0 then if v.qty > 0 then
convertItem(v) convertItem(v)
local key = table.concat({ v.name, v.damage, v.nbtHash }, ':') local key = table.concat({ v.name, v.damage, v.nbtHash }, ':')
local entry = cache[key] local entry = cache[key]
if not entry then if not entry then
entry = itemDB:get(v) or itemDB:add(v) entry = itemDB:get(v) or itemDB:add(v)
entry = Util.shallowCopy(entry) entry = Util.shallowCopy(entry)
entry.count = 0 entry.count = 0
cache[key] = entry cache[key] = entry
table.insert(items, entry) table.insert(items, entry)
end end
entry.count = entry.count + v.count entry.count = entry.count + v.count
throttle() throttle()
end end
itemDB:flush() itemDB:flush()
end end
end) end)
if s then if s then
if not Util.empty(items) then if not Util.empty(items) then
self.cache = cache self.cache = cache
return items return items
end end
else else
_G._syslog(m) _G._syslog(m)
end end
end end
function ChestAdapter:getItemInfo(item) function ChestAdapter:getItemInfo(item)
if not self.cache then if not self.cache then
self:listItems() self:listItems()
end end
local key = table.concat({ item.name, item.damage, item.nbtHash }, ':') local key = table.concat({ item.name, item.damage, item.nbtHash }, ':')
return self.cache[key] return self.cache[key]
end end
function ChestAdapter:provide(item, qty, slot, direction) function ChestAdapter:provide(item, qty, slot, direction)
pcall(function() pcall(function()
for key,stack in Util.rpairs(self.getAllStacks(false)) do for key,stack in Util.rpairs(self.getAllStacks(false)) do
if stack.id == item.name and if stack.id == item.name and
(not item.damage or stack.dmg == item.damage) and (not item.damage or stack.dmg == item.damage) and
(not item.nbtHash or stack.nbt_hash == item.nbtHash) then (not item.nbtHash or stack.nbt_hash == item.nbtHash) then
local amount = math.min(qty, stack.qty) local amount = math.min(qty, stack.qty)
if amount > 0 then if amount > 0 then
self.pushItemIntoSlot(direction or self.direction, key, amount, slot) self.pushItemIntoSlot(direction or self.direction, key, amount, slot)
end end
qty = qty - amount qty = qty - amount
if qty <= 0 then if qty <= 0 then
break break
end end
end end
end end
end) end)
end end
function ChestAdapter:extract(slot, qty, toSlot) function ChestAdapter:extract(slot, qty, toSlot)
if toSlot then if toSlot then
self.pushItemIntoSlot(self.direction, slot, qty, toSlot) self.pushItemIntoSlot(self.direction, slot, qty, toSlot)
else else
self.pushItem(self.direction, slot, qty) self.pushItem(self.direction, slot, qty)
end end
end end
function ChestAdapter:insert(slot, qty, toSlot) function ChestAdapter:insert(slot, qty, toSlot)
-- toSlot not tested ... -- toSlot not tested ...
local s, m = pcall(self.pullItem, self.direction, slot, qty, toSlot) local s, m = pcall(self.pullItem, self.direction, slot, qty, toSlot)
if not s and m then if not s and m then
os.sleep(1) os.sleep(1)
pcall(self.pullItem, self.direction, slot, qty, toSlot) pcall(self.pullItem, self.direction, slot, qty, toSlot)
end end
end end
return ChestAdapter return ChestAdapter

View File

@@ -6,159 +6,159 @@ local Peripheral = require('peripheral')
local ChestAdapter = class() local ChestAdapter = class()
function ChestAdapter:init(args) function ChestAdapter:init(args)
local defaults = { local defaults = {
name = 'chest', name = 'chest',
adapter = 'ChestAdapter18' adapter = 'ChestAdapter18'
} }
Util.merge(self, defaults) Util.merge(self, defaults)
Util.merge(self, args) Util.merge(self, args)
local chest local chest
if not self.side then if not self.side then
chest = Peripheral.getByMethod('list') or chest = Peripheral.getByMethod('list') or
Peripheral.getByMethod('listAvailableItems') Peripheral.getByMethod('listAvailableItems')
else else
chest = Peripheral.getBySide(self.side) chest = Peripheral.getBySide(self.side)
if chest and not chest.list and not chest.listAvailableItems then if chest and not chest.list and not chest.listAvailableItems then
chest = nil chest = nil
end end
end end
if chest then if chest then
Util.merge(self, chest) Util.merge(self, chest)
if chest.listAvailableItems then if chest.listAvailableItems then
self.list = chest.listAvailableItems self.list = chest.listAvailableItems
end end
end end
end end
function ChestAdapter:isValid() function ChestAdapter:isValid()
return not not self.list return not not self.list
end end
-- handle both AE/RS and generic inventory -- handle both AE/RS and generic inventory
function ChestAdapter:getItemDetails(index, item) function ChestAdapter:getItemDetails(index, item)
if self.getItemMeta then if self.getItemMeta then
local s, detail = pcall(self.getItemMeta, index) local s, detail = pcall(self.getItemMeta, index)
if not s or not detail or detail.name ~= item.name then if not s or not detail or detail.name ~= item.name then
return return
end end
return detail return detail
else else
local detail = self.findItems(item) local detail = self.findItems(item)
if detail and #detail > 0 then if detail and #detail > 0 then
return detail[1].getMetadata() return detail[1].getMetadata()
end end
end end
end end
function ChestAdapter:getCachedItemDetails(item, k) function ChestAdapter:getCachedItemDetails(item, k)
local cached = itemDB:get(item) local cached = itemDB:get(item)
if cached then if cached then
return cached return cached
end end
local detail = self:getItemDetails(k, item) local detail = self:getItemDetails(k, item)
if detail then if detail then
return itemDB:add(detail) return itemDB:add(detail)
end end
end end
function ChestAdapter:refresh(throttle) function ChestAdapter:refresh(throttle)
return self:listItems(throttle) return self:listItems(throttle)
end end
-- provide a consolidated list of items -- provide a consolidated list of items
function ChestAdapter:listItems(throttle) function ChestAdapter:listItems(throttle)
for _ = 1, 5 do for _ = 1, 5 do
local list = self:listItemsInternal(throttle) local list = self:listItemsInternal(throttle)
if list then if list then
return list return list
end end
end end
error('Error accessing inventory: ' .. self.direction) error('Error accessing inventory: ' .. self.direction)
end end
function ChestAdapter:listItemsInternal(throttle) function ChestAdapter:listItemsInternal(throttle)
local cache = { } local cache = { }
local items = { } local items = { }
throttle = throttle or Util.throttle() throttle = throttle or Util.throttle()
for k,v in pairs(self.list()) do for k,v in pairs(self.list()) do
if v.count > 0 then if v.count > 0 then
local key = table.concat({ v.name, v.damage, v.nbtHash }, ':') local key = table.concat({ v.name, v.damage, v.nbtHash }, ':')
local entry = cache[key] local entry = cache[key]
if not entry then if not entry then
entry = self:getCachedItemDetails(v, k) entry = self:getCachedItemDetails(v, k)
if not entry then if not entry then
return -- Inventory has changed return -- Inventory has changed
end end
entry = Util.shallowCopy(entry) entry = Util.shallowCopy(entry)
entry.count = 0 entry.count = 0
cache[key] = entry cache[key] = entry
table.insert(items, entry) table.insert(items, entry)
end end
if entry then if entry then
entry.count = entry.count + v.count entry.count = entry.count + v.count
end end
throttle() throttle()
end end
end end
itemDB:flush() itemDB:flush()
self.cache = cache self.cache = cache
return items return items
end end
function ChestAdapter:getItemInfo(item) function ChestAdapter:getItemInfo(item)
if not self.cache then if not self.cache then
self:listItems() self:listItems()
end end
local key = table.concat({ item.name, item.damage, item.nbtHash }, ':') local key = table.concat({ item.name, item.damage, item.nbtHash }, ':')
local items = self.cache or { } local items = self.cache or { }
return items[key] return items[key]
end end
function ChestAdapter:getPercentUsed() function ChestAdapter:getPercentUsed()
if self.cache and self.getDrawerCount then if self.cache and self.getDrawerCount then
return math.floor(Util.size(self.cache) / self.getDrawerCount() * 100) return math.floor(Util.size(self.cache) / self.getDrawerCount() * 100)
end end
return 0 return 0
end end
function ChestAdapter:provide(item, qty, slot, direction) function ChestAdapter:provide(item, qty, slot, direction)
local total = 0 local total = 0
local _, m = pcall(function() local _, m = pcall(function()
local stacks = self.list() local stacks = self.list()
for key,stack in Util.rpairs(stacks) do for key,stack in Util.rpairs(stacks) do
if stack.name == item.name and if stack.name == item.name and
stack.damage == item.damage and stack.damage == item.damage and
stack.nbtHash == item.nbtHash then stack.nbtHash == item.nbtHash then
local amount = math.min(qty, stack.count) local amount = math.min(qty, stack.count)
if amount > 0 then if amount > 0 then
amount = self.pushItems(direction or self.direction, key, amount, slot) amount = self.pushItems(direction or self.direction, key, amount, slot)
end end
qty = qty - amount qty = qty - amount
total = total + amount total = total + amount
if qty <= 0 then if qty <= 0 then
break break
end end
end end
end end
end) end)
return total, m return total, m
end end
function ChestAdapter:extract(slot, qty, toSlot, direction) 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 end
function ChestAdapter:insert(slot, qty, toSlot, direction) 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 end
return ChestAdapter return ChestAdapter

View File

@@ -6,277 +6,277 @@ local Util = require('util')
local itemDB = TableDB({ fileName = 'usr/config/items.db' }) local itemDB = TableDB({ fileName = 'usr/config/items.db' })
local function safeString(text) 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 newText = { }
local skip = 0 local skip = 0
for i = 1, #text do for i = 1, #text do
val = text:byte(i) val = text:byte(i)
if val == 167 then if val == 167 then
skip = 2 skip = 2
end end
if skip > 0 then if skip > 0 then
skip = skip - 1 skip = skip - 1
else else
if val >= 32 and val <= 128 then if val >= 32 and val <= 128 then
newText[#newText + 1] = val newText[#newText + 1] = val
end end
end end
end end
return string.char(unpack(newText)) return string.char(unpack(newText))
end end
return text return text
end end
function itemDB:makeKey(item) function itemDB:makeKey(item)
if not item then error('itemDB:makeKey: item is required', 2) end if not item then error('itemDB:makeKey: item is required', 2) end
return table.concat({ item.name, item.damage or '*', item.nbtHash }, ':') return table.concat({ item.name, item.damage or '*', item.nbtHash }, ':')
end end
function itemDB:splitKey(key, item) function itemDB:splitKey(key, item)
item = item or { } item = item or { }
local t = Util.split(key, '(.-):') local t = Util.split(key, '(.-):')
if #t[#t] > 8 then if #t[#t] > 8 then
item.nbtHash = table.remove(t) item.nbtHash = table.remove(t)
end end
local damage = table.remove(t) local damage = table.remove(t)
if damage ~= '*' then if damage ~= '*' then
item.damage = tonumber(damage) item.damage = tonumber(damage)
end end
item.name = table.concat(t, ':') item.name = table.concat(t, ':')
return item return item
end end
function itemDB:get(key, populateFn) function itemDB:get(key, populateFn)
if not key then error('itemDB:get: key is required', 2) end if not key then error('itemDB:get: key is required', 2) end
if type(key) == 'string' then if type(key) == 'string' then
key = self:splitKey(key) key = self:splitKey(key)
else else
key = Util.shallowCopy(key) key = Util.shallowCopy(key)
end end
local item = self:_get(key) local item = self:_get(key)
if not item and populateFn then if not item and populateFn then
item = populateFn() item = populateFn()
if item then if item then
item = self:add(item) item = self:add(item)
end end
end end
return item and Util.merge(key, item) return item and Util.merge(key, item)
end end
function itemDB:_get(key) function itemDB:_get(key)
if not key then error('itemDB:get: key is required', 2) end if not key then error('itemDB:get: key is required', 2) end
if type(key) == 'string' then if type(key) == 'string' then
key = self:splitKey(key) key = self:splitKey(key)
end end
local item = TableDB.get(self, self:makeKey(key)) local item = TableDB.get(self, self:makeKey(key))
if item then if item then
return item return item
end end
-- try finding an item that has damage values -- try finding an item that has damage values
if type(key.damage) == 'number' then if type(key.damage) == 'number' then
item = TableDB.get(self, self:makeKey({ name = key.name, nbtHash = key.nbtHash })) item = TableDB.get(self, self:makeKey({ name = key.name, nbtHash = key.nbtHash }))
if item and item.maxDamage then if item and item.maxDamage then
item = Util.shallowCopy(item) item = Util.shallowCopy(item)
item.damage = key.damage item.damage = key.damage
if item.maxDamage > 0 and type(item.damage) == 'number' and item.damage > 0 then 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) item.displayName = string.format('%s (damage: %s)', item.displayName, item.damage)
end end
return item return item
end end
else else
for k,item in pairs(self.data) do for k,item in pairs(self.data) do
if key.name == item.name and if key.name == item.name and
key.nbtHash == key.nbtHash and key.nbtHash == key.nbtHash and
item.maxDamage > 0 then item.maxDamage > 0 then
item = Util.shallowCopy(item) item = Util.shallowCopy(item)
item.nbtHash = key.nbtHash item.nbtHash = key.nbtHash
return item return item
end end
end end
end end
if key.nbtHash then if key.nbtHash then
item = self:get({ name = key.name, damage = key.damage }) item = self:get({ name = key.name, damage = key.damage })
if item and item.ignoreNBT then if item and item.ignoreNBT then
item = Util.shallowCopy(item) item = Util.shallowCopy(item)
item.nbtHash = key.nbtHash item.nbtHash = key.nbtHash
item.damage = key.damage item.damage = key.damage
return item return item
end end
end end
end end
local function formatTime(t) local function formatTime(t)
local m = math.floor(t/60) local m = math.floor(t/60)
local s = t % 60 local s = t % 60
if s < 10 then if s < 10 then
s = '0' .. s s = '0' .. s
end end
return m .. ':' .. s return m .. ':' .. s
end end
--[[ --[[
If the base item contains an NBT hash, then the NBT hash uniquely If the base item contains an NBT hash, then the NBT hash uniquely
identifies this item. identifies this item.
]]-- ]]--
function itemDB:add(baseItem) function itemDB:add(baseItem)
local nItem = { local nItem = {
name = baseItem.name, name = baseItem.name,
damage = baseItem.damage, damage = baseItem.damage,
nbtHash = baseItem.nbtHash, nbtHash = baseItem.nbtHash,
} }
-- if detail.maxDamage > 0 then -- if detail.maxDamage > 0 then
-- nItem.damage = '*' -- nItem.damage = '*'
-- end -- end
nItem.displayName = safeString(baseItem.displayName) nItem.displayName = safeString(baseItem.displayName)
nItem.maxCount = baseItem.maxCount nItem.maxCount = baseItem.maxCount
nItem.maxDamage = baseItem.maxDamage nItem.maxDamage = baseItem.maxDamage
-- enchanted items -- enchanted items
if baseItem.enchantments then if baseItem.enchantments then
if nItem.name == 'minecraft:enchanted_book' then if nItem.name == 'minecraft:enchanted_book' then
nItem.displayName = 'Book: ' nItem.displayName = 'Book: '
else else
nItem.displayName = nItem.displayName .. ': ' nItem.displayName = nItem.displayName .. ': '
end end
for k, v in ipairs(baseItem.enchantments) do for k, v in ipairs(baseItem.enchantments) do
if k > 1 then if k > 1 then
nItem.displayName = nItem.displayName .. ', ' nItem.displayName = nItem.displayName .. ', '
end end
nItem.displayName = nItem.displayName .. v.fullName nItem.displayName = nItem.displayName .. v.fullName
end end
-- turtles / computers / etc -- turtles / computers / etc
elseif baseItem.computer then elseif baseItem.computer then
-- a turtle's NBT is updated constantly -- a turtle's NBT is updated constantly
-- update the cache with the new NBT -- update the cache with the new NBT
if baseItem.computer.id then if baseItem.computer.id then
Map.removeMatches(self.data, { name = nItem.name, displayName = nItem.displayName }) Map.removeMatches(self.data, { name = nItem.name, displayName = nItem.displayName })
end end
nItem.displayName = baseItem.computer.label or baseItem.displayName nItem.displayName = baseItem.computer.label or baseItem.displayName
-- disks -- disks
elseif baseItem.media then elseif baseItem.media then
-- don't ignore nbt... as disks can be labeled -- don't ignore nbt... as disks can be labeled
if baseItem.media.recordTitle then if baseItem.media.recordTitle then
nItem.displayName = nItem.displayName .. ': ' .. baseItem.media.recordTitle nItem.displayName = nItem.displayName .. ': ' .. baseItem.media.recordTitle
end end
-- potions -- potions
elseif nItem.name == 'minecraft:potion' or nItem.name == 'minecraft:lingering_potion' then elseif nItem.name == 'minecraft:potion' or nItem.name == 'minecraft:lingering_potion' then
if baseItem.effects then if baseItem.effects then
local effect = baseItem.effects[1] local effect = baseItem.effects[1]
if effect.amplifier == 1 then if effect.amplifier == 1 then
nItem.displayName = nItem.displayName .. ' II' nItem.displayName = nItem.displayName .. ' II'
end end
if effect.duration and effect.duration > 0 then if effect.duration and effect.duration > 0 then
nItem.displayName = string.format('%s (%s)', nItem.displayName, formatTime(effect.duration)) nItem.displayName = string.format('%s (%s)', nItem.displayName, formatTime(effect.duration))
end end
end end
else else
for k,item in pairs(self.data) do for k,item in pairs(self.data) do
if nItem.name == item.name and if nItem.name == item.name and
nItem.displayName == item.displayName then nItem.displayName == item.displayName then
if nItem.nbtHash ~= item.nbtHash and nItem.damage ~= item.damage then if nItem.nbtHash ~= item.nbtHash and nItem.damage ~= item.damage then
nItem.damage = '*' nItem.damage = '*'
nItem.nbtHash = nil nItem.nbtHash = nil
nItem.ignoreNBT = true nItem.ignoreNBT = true
self.data[k] = nil self.data[k] = nil
break break
elseif nItem.damage ~= item.damage then elseif nItem.damage ~= item.damage then
nItem.damage = '*' nItem.damage = '*'
self.data[k] = nil self.data[k] = nil
break break
elseif nItem.nbtHash ~= item.nbtHash then elseif nItem.nbtHash ~= item.nbtHash then
nItem.nbtHash = nil nItem.nbtHash = nil
nItem.ignoreNBT = true nItem.ignoreNBT = true
self.data[k] = nil self.data[k] = nil
break break
end end
end end
end end
end end
TableDB.add(self, self:makeKey(nItem), nItem) TableDB.add(self, self:makeKey(nItem), nItem)
nItem = Util.shallowCopy(nItem) nItem = Util.shallowCopy(nItem)
nItem.damage = baseItem.damage nItem.damage = baseItem.damage
nItem.nbtHash = baseItem.nbtHash nItem.nbtHash = baseItem.nbtHash
return nItem return nItem
end end
-- Accepts: "minecraft:stick:0" or { name = 'minecraft:stick', damage = 0 } -- Accepts: "minecraft:stick:0" or { name = 'minecraft:stick', damage = 0 }
function itemDB:getName(item) function itemDB:getName(item)
if type(item) == 'string' then if type(item) == 'string' then
item = self:splitKey(item) item = self:splitKey(item)
end end
local detail = self:get(item) local detail = self:get(item)
if detail then if detail then
return detail.displayName return detail.displayName
end end
-- fallback to nameDB -- fallback to nameDB
local strId = self:makeKey(item) local strId = self:makeKey(item)
local name = nameDB.data[strId] local name = nameDB.data[strId]
if not name and not item.damage then if not name and not item.damage then
name = nameDB.data[self:makeKey({ name = item.name, damage = 0, nbtHash = item.nbtHash })] name = nameDB.data[self:makeKey({ name = item.name, damage = 0, nbtHash = item.nbtHash })]
end end
return name or strId return name or strId
end end
function itemDB:getMaxCount(item) function itemDB:getMaxCount(item)
local detail = self:get(item) local detail = self:get(item)
return detail and detail.maxCount or 64 return detail and detail.maxCount or 64
end end
function itemDB:load() function itemDB:load()
TableDB.load(self) TableDB.load(self)
for key,item in pairs(self.data) do for key,item in pairs(self.data) do
self:splitKey(key, item) self:splitKey(key, item)
item.maxDamage = item.maxDamage or 0 item.maxDamage = item.maxDamage or 0
item.maxCount = item.maxCount or 64 item.maxCount = item.maxCount or 64
end end
end end
function itemDB:flush() function itemDB:flush()
if self.dirty then if self.dirty then
local t = { } local t = { }
for k,v in pairs(self.data) do for k,v in pairs(self.data) do
v = Util.shallowCopy(v) v = Util.shallowCopy(v)
v.name = nil v.name = nil
v.damage = nil v.damage = nil
v.nbtHash = nil v.nbtHash = nil
v.count = nil -- wipe out previously saved counts - temporary v.count = nil -- wipe out previously saved counts - temporary
if v.maxDamage == 0 then if v.maxDamage == 0 then
v.maxDamage = nil v.maxDamage = nil
end end
if v.maxCount == 64 then if v.maxCount == 64 then
v.maxCount = nil v.maxCount = nil
end end
t[k] = v t[k] = v
end end
Util.writeTable(self.fileName, t) Util.writeTable(self.fileName, t)
self.dirty = false self.dirty = false
end end
end end
itemDB:load() itemDB:load()

View File

@@ -6,249 +6,249 @@ local Util = require('util')
local os = _G.os local os = _G.os
local convertNames = { local convertNames = {
name = 'id', name = 'id',
damage = 'dmg', damage = 'dmg',
maxCount = 'max_size', maxCount = 'max_size',
count = 'qty', count = 'qty',
displayName = 'display_name', displayName = 'display_name',
maxDamage = 'max_dmg', maxDamage = 'max_dmg',
nbtHash = 'nbt_hash', nbtHash = 'nbt_hash',
} }
-- Strip off color prefix -- Strip off color prefix
local function safeString(text) 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 newText = {}
for i = 4, #text do for i = 4, #text do
val = text:byte(i) val = text:byte(i)
newText[i - 3] = (val > 31 and val < 127) and val or 63 newText[i - 3] = (val > 31 and val < 127) and val or 63
end end
return string.char(unpack(newText)) return string.char(unpack(newText))
end end
return text return text
end end
local function convertItem(item) local function convertItem(item)
for k,v in pairs(convertNames) do for k,v in pairs(convertNames) do
item[k] = item[v] item[k] = item[v]
item[v] = nil item[v] = nil
end end
item.displayName = safeString(item.displayName) item.displayName = safeString(item.displayName)
end end
local MEAdapter = class() local MEAdapter = class()
function MEAdapter:init(args) function MEAdapter:init(args)
local defaults = { local defaults = {
items = { }, items = { },
name = 'ME', name = 'ME',
jobList = { }, jobList = { },
} }
Util.merge(self, defaults) Util.merge(self, defaults)
Util.merge(self, args) Util.merge(self, args)
local chest local chest
if not self.side then if not self.side then
chest = Peripheral.getByMethod('getAvailableItems') chest = Peripheral.getByMethod('getAvailableItems')
else else
chest = Peripheral.getBySide(self.side) chest = Peripheral.getBySide(self.side)
if chest and not chest.getAvailableItems then if chest and not chest.getAvailableItems then
chest = nil chest = nil
end end
end end
if chest then if chest then
Util.merge(self, chest) Util.merge(self, chest)
end end
end end
function MEAdapter:isValid() function MEAdapter:isValid()
return self.getAvailableItems and self.getAvailableItems() return self.getAvailableItems and self.getAvailableItems()
end end
function MEAdapter:refresh() function MEAdapter:refresh()
self.items = nil self.items = nil
local hasItems, failed local hasItems, failed
local s, m = pcall(function() local s, m = pcall(function()
self.items = self.getAvailableItems('all') self.items = self.getAvailableItems('all')
for _,v in pairs(self.items) do for _,v in pairs(self.items) do
Util.merge(v, v.item) Util.merge(v, v.item)
convertItem(v) convertItem(v)
-- if power has been interrupted, the list will still be returned -- if power has been interrupted, the list will still be returned
-- but all items will have a 0 quantity -- but all items will have a 0 quantity
-- ensure that the list is valid -- ensure that the list is valid
if not hasItems then if not hasItems then
hasItems = v.count > 0 hasItems = v.count > 0
end end
if not v.fingerprint then if not v.fingerprint then
failed = true failed = true
break break
end end
if not itemDB:get(v) then if not itemDB:get(v) then
itemDB:add(v, v) itemDB:add(v, v)
end end
end end
end) end)
itemDB:flush() itemDB:flush()
if not s and m then if not s and m then
_G._syslog(m) _G._syslog(m)
end end
if s and not failed and hasItems and self.items and not Util.empty(self.items) then if s and not failed and hasItems and self.items and not Util.empty(self.items) then
return self.items return self.items
end end
self.items = nil self.items = nil
end end
function MEAdapter:listItems() function MEAdapter:listItems()
self:refresh() self:refresh()
return self.items return self.items
end end
function MEAdapter:getItemInfo(item) function MEAdapter:getItemInfo(item)
for _,i in pairs(self.items) do for _,i in pairs(self.items) do
if item.name == i.name and if item.name == i.name and
item.damage == i.damage and item.damage == i.damage and
item.nbtHash == i.nbtHash then item.nbtHash == i.nbtHash then
return i return i
end end
end end
end end
function MEAdapter:isCPUAvailable() function MEAdapter:isCPUAvailable()
local cpus = self.getCraftingCPUs() or { } local cpus = self.getCraftingCPUs() or { }
local available = false local available = false
for cpu,v in pairs(cpus) do for cpu,v in pairs(cpus) do
if not v.busy then if not v.busy then
available = true available = true
elseif not self.jobList[cpu] then -- something else is crafting something (don't know what) 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 return false -- return false since we are in an unknown state
end end
end end
return available return available
end end
function MEAdapter:craft(item, count) function MEAdapter:craft(item, count)
if not self:isCPUAvailable() then if not self:isCPUAvailable() then
return false return false
end end
self:refresh() self:refresh()
item = self:getItemInfo(item) item = self:getItemInfo(item)
if item and item.is_craftable then if item and item.is_craftable then
local cpus = self.getCraftingCPUs() or { } local cpus = self.getCraftingCPUs() or { }
for cpu,v in pairs(cpus) do for cpu,v in pairs(cpus) do
if not v.busy then if not v.busy then
self.requestCrafting({ self.requestCrafting({
id = item.name, id = item.name,
dmg = item.damage, dmg = item.damage,
nbt_hash = item.nbtHash, nbt_hash = item.nbtHash,
}, },
count or 1, count or 1,
v.name -- CPUs must be named ! use anvil v.name -- CPUs must be named ! use anvil
) )
os.sleep(0) -- needed ? os.sleep(0) -- needed ?
cpus = self.getCraftingCPUs() or { } cpus = self.getCraftingCPUs() or { }
if cpus[cpu].busy then if cpus[cpu].busy then
self.jobList[cpu] = { self.jobList[cpu] = {
name = item.name, name = item.name,
damage = item.damage, damage = item.damage,
nbtHash = item.nbtHash, nbtHash = item.nbtHash,
count = count, count = count,
} }
return true return true
end end
break -- only need to try the first available cpu break -- only need to try the first available cpu
end end
end end
return false return false
end end
end end
function MEAdapter:getJobList() function MEAdapter:getJobList()
local cpus = self.getCraftingCPUs() or { } local cpus = self.getCraftingCPUs() or { }
for cpu,v in pairs(cpus) do for cpu,v in pairs(cpus) do
if not v.busy then if not v.busy then
self.jobList[cpu] = nil self.jobList[cpu] = nil
end end
end end
return self.jobList return self.jobList
end end
function MEAdapter:isCrafting(item) function MEAdapter:isCrafting(item)
for _,v in pairs(self:getJobList()) do for _,v in pairs(self:getJobList()) do
if v.name == item.name and if v.name == item.name and
v.damage == item.damage and v.damage == item.damage and
v.nbtHash == item.nbtHash then v.nbtHash == item.nbtHash then
return true return true
end end
end end
end end
function MEAdapter:craftItems(items) function MEAdapter:craftItems(items)
local cpus = self.getCraftingCPUs() or { } local cpus = self.getCraftingCPUs() or { }
local count = 0 local count = 0
for _,cpu in pairs(cpus) do for _,cpu in pairs(cpus) do
if cpu.busy then if cpu.busy then
return return
end end
end end
for _,item in pairs(items) do for _,item in pairs(items) do
if count >= #cpus then if count >= #cpus then
break break
end end
if not self:isCrafting(item) then if not self:isCrafting(item) then
if self:craft(item, item.count) then if self:craft(item, item.count) then
count = count + 1 count = count + 1
end end
end end
end end
end end
function MEAdapter:provide(item, qty, slot, direction) function MEAdapter:provide(item, qty, slot, direction)
return pcall(function() return pcall(function()
for _,stack in pairs(self.getAvailableItems('all')) do for _,stack in pairs(self.getAvailableItems('all')) do
if stack.item.id == item.name and if stack.item.id == item.name and
(not item.damage or stack.item.dmg == item.damage) and (not item.damage or stack.item.dmg == item.damage) and
(not item.nbtHash or stack.item.nbt_hash == item.nbtHash) then (not item.nbtHash or stack.item.nbt_hash == item.nbtHash) then
local amount = math.min(qty, stack.item.qty) local amount = math.min(qty, stack.item.qty)
if amount > 0 then if amount > 0 then
self.exportItem(stack.item, direction or self.direction, amount, slot) self.exportItem(stack.item, direction or self.direction, amount, slot)
end end
qty = qty - amount qty = qty - amount
if qty <= 0 then if qty <= 0 then
break break
end end
end end
end end
end) end)
end end
function MEAdapter:insert(slot, qty, toSlot) function MEAdapter:insert(slot, qty, toSlot)
local s, m = pcall(self.pullItem, self.direction, slot, qty, toSlot) local s, m = pcall(self.pullItem, self.direction, slot, qty, toSlot)
if not s and m then if not s and m then
os.sleep(1) os.sleep(1)
pcall(self.pullItem, self.direction, slot, qty, toSlot) pcall(self.pullItem, self.direction, slot, qty, toSlot)
end end
end end
return MEAdapter return MEAdapter

View File

@@ -8,83 +8,83 @@ local MEAdapter = class(RSAdapter)
local DEVICE_TYPE = 'appliedenergistics2:interface' local DEVICE_TYPE = 'appliedenergistics2:interface'
function MEAdapter:init(args) function MEAdapter:init(args)
local defaults = { local defaults = {
name = 'appliedEnergistics', name = 'appliedEnergistics',
jobList = { }, jobList = { },
} }
Util.merge(self, defaults) Util.merge(self, defaults)
Util.merge(self, args) Util.merge(self, args)
local controller local controller
if not self.side then if not self.side then
controller = Peripheral.getByType(DEVICE_TYPE) controller = Peripheral.getByType(DEVICE_TYPE)
else else
controller = Peripheral.getBySide(self.side) controller = Peripheral.getBySide(self.side)
end end
if controller then if controller then
Util.merge(self, controller) Util.merge(self, controller)
end end
end end
function MEAdapter:isValid() function MEAdapter:isValid()
return self.type == DEVICE_TYPE and not not self.findItems return self.type == DEVICE_TYPE and not not self.findItems
end end
function MEAdapter:clearFinished() function MEAdapter:clearFinished()
for _,key in pairs(Util.keys(self.jobList)) do for _,key in pairs(Util.keys(self.jobList)) do
local job = self.jobList[key] local job = self.jobList[key]
if job.info.status() == 'finished' then if job.info.status() == 'finished' then
self.jobList[key] = nil self.jobList[key] = nil
end end
end end
end end
function MEAdapter:isCPUAvailable() function MEAdapter:isCPUAvailable()
local cpus = self.getCraftingCPUs() or { } local cpus = self.getCraftingCPUs() or { }
local busy = 0 local busy = 0
for _,cpu in pairs(cpus) do for _,cpu in pairs(cpus) do
if cpu.busy then if cpu.busy then
busy = busy + 1 busy = busy + 1
end end
end end
self:clearFinished() self:clearFinished()
return busy == Util.size(self.jobList) and busy < #cpus return busy == Util.size(self.jobList) and busy < #cpus
end end
function MEAdapter:craft(item, count) function MEAdapter:craft(item, count)
if not self:isCPUAvailable() then if not self:isCPUAvailable() then
return false return false
end end
local detail = self.findItem(item) local detail = self.findItem(item)
if detail and detail.craft then if detail and detail.craft then
local info = detail.craft(count or 1) local info = detail.craft(count or 1)
if info.status() == 'unknown' then if info.status() == 'unknown' then
self.jobList[info.getId()] = { self.jobList[info.getId()] = {
name = item.name, name = item.name,
damage = item.damage, damage = item.damage,
nbtHash = item.nbtHash, nbtHash = item.nbtHash,
info = info, info = info,
} }
return true return true
end end
return false return false
end end
end end
function MEAdapter:isCrafting(item) function MEAdapter:isCrafting(item)
self:clearFinished() self:clearFinished()
_G._p = self.jobList _G._p = self.jobList
for _,job in pairs(self.jobList) do for _,job in pairs(self.jobList) do
if job.name == item.name and if job.name == item.name and
job.damage == item.damage and job.damage == item.damage and
job.nbtHash == item.nbtHash then job.nbtHash == item.nbtHash then
return true return true
end end
end end
return false return false
end end
return MEAdapter return MEAdapter

View File

@@ -5,91 +5,91 @@ local Message = { }
local messageHandlers = {} local messageHandlers = {}
function Message.enable() function Message.enable()
if not device.wireless_modem.isOpen(os.getComputerID()) then if not device.wireless_modem.isOpen(os.getComputerID()) then
device.wireless_modem.open(os.getComputerID()) device.wireless_modem.open(os.getComputerID())
end end
if not device.wireless_modem.isOpen(60000) then if not device.wireless_modem.isOpen(60000) then
device.wireless_modem.open(60000) device.wireless_modem.open(60000)
end end
end end
if device and device.wireless_modem then if device and device.wireless_modem then
Message.enable() Message.enable()
end end
Event.on('device_attach', function(event, deviceName) Event.on('device_attach', function(event, deviceName)
if deviceName == 'wireless_modem' then if deviceName == 'wireless_modem' then
Message.enable() Message.enable()
end end
end) end)
function Message.addHandler(type, f) function Message.addHandler(type, f)
table.insert(messageHandlers, { table.insert(messageHandlers, {
type = type, type = type,
f = f, f = f,
enabled = true enabled = true
}) })
end end
function Message.removeHandler(h) function Message.removeHandler(h)
for k,v in pairs(messageHandlers) do for k,v in pairs(messageHandlers) do
if v == h then if v == h then
messageHandlers[k] = nil messageHandlers[k] = nil
break break
end end
end end
end end
Event.on('modem_message', Event.on('modem_message',
function(event, side, sendChannel, replyChannel, msg, distance) function(event, side, sendChannel, replyChannel, msg, distance)
if msg and msg.type then -- filter out messages from other systems if msg and msg.type then -- filter out messages from other systems
local id = replyChannel local id = replyChannel
for k,h in pairs(messageHandlers) do for k,h in pairs(messageHandlers) do
if h.type == msg.type then if h.type == msg.type then
-- should provide msg.contents instead of message - type is already known -- should provide msg.contents instead of message - type is already known
h.f(h, id, msg, distance) h.f(h, id, msg, distance)
end end
end end
end end
end end
) )
function Message.send(id, msgType, contents) function Message.send(id, msgType, contents)
if not device.wireless_modem then if not device.wireless_modem then
error('No modem attached', 2) error('No modem attached', 2)
end end
if id then if id then
device.wireless_modem.transmit(id, os.getComputerID(), { device.wireless_modem.transmit(id, os.getComputerID(), {
type = msgType, contents = contents type = msgType, contents = contents
}) })
else else
device.wireless_modem.transmit(60000, os.getComputerID(), { device.wireless_modem.transmit(60000, os.getComputerID(), {
type = msgType, contents = contents type = msgType, contents = contents
}) })
end end
end end
function Message.broadcast(t, contents) function Message.broadcast(t, contents)
if not device.wireless_modem then if not device.wireless_modem then
error('No modem attached', 2) error('No modem attached', 2)
end end
Message.send(nil, t, contents) Message.send(nil, t, contents)
end end
function Message.waitForMessage(msgType, timeout, fromId) function Message.waitForMessage(msgType, timeout, fromId)
local timerId = os.startTimer(timeout) local timerId = os.startTimer(timeout)
repeat repeat
local e, side, _id, id, msg, distance = os.pullEvent() local e, side, _id, id, msg, distance = os.pullEvent()
if e == 'modem_message' then if e == 'modem_message' then
if msg and msg.type and msg.type == msgType then if msg and msg.type and msg.type == msgType then
if not fromId or id == fromId then if not fromId or id == fromId then
return e, id, msg, distance return e, id, msg, distance
end end
end end
end end
until e == 'timer' and side == timerId until e == 'timer' and side == timerId
end end
return Message return Message

View File

@@ -10,50 +10,50 @@ local USER_DIR = '/usr/etc/names'
local nameDB = TableDB() local nameDB = TableDB()
function nameDB:loadDirectory(directory) function nameDB:loadDirectory(directory)
if fs.exists(directory) then if fs.exists(directory) then
local files = fs.list(directory) local files = fs.list(directory)
table.sort(files) table.sort(files)
for _,file in ipairs(files) do for _,file in ipairs(files) do
local mod = file:match('(%S+).json') local mod = file:match('(%S+).json')
if mod then if mod then
local blocks = JSON.decodeFromFile(fs.combine(directory, file)) local blocks = JSON.decodeFromFile(fs.combine(directory, file))
if not blocks then if not blocks then
error('Unable to read ' .. fs.combine(directory, file)) error('Unable to read ' .. fs.combine(directory, file))
end end
for strId, block in pairs(blocks) do for strId, block in pairs(blocks) do
strId = string.format('%s:%s', mod, strId) strId = string.format('%s:%s', mod, strId)
if type(block.name) == 'string' then if type(block.name) == 'string' then
self.data[strId .. ':0'] = block.name self.data[strId .. ':0'] = block.name
else else
for nid,name in pairs(block.name) do for nid,name in pairs(block.name) do
self.data[strId .. ':' .. (nid-1)] = name self.data[strId .. ':' .. (nid-1)] = name
end end
end end
end end
elseif file:match('(%S+).db') then elseif file:match('(%S+).db') then
local names = Util.readTable(fs.combine(directory, file)) local names = Util.readTable(fs.combine(directory, file))
if not names then if not names then
error('Unable to read ' .. fs.combine(directory, file)) error('Unable to read ' .. fs.combine(directory, file))
end end
for key,name in pairs(names) do for key,name in pairs(names) do
self.data[key] = name self.data[key] = name
end end
end end
end end
end end
end end
function nameDB:load() function nameDB:load()
self:loadDirectory(CORE_DIR) self:loadDirectory(CORE_DIR)
self:loadDirectory(USER_DIR) self:loadDirectory(USER_DIR)
end end
function nameDB:getName(strId) function nameDB:getName(strId)
return self.data[strId] or strId return self.data[strId] or strId
end end
nameDB:load() 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() local RefinedAdapter = class()
function RefinedAdapter:init(args) function RefinedAdapter:init(args)
local defaults = { local defaults = {
name = 'refinedStorage', name = 'refinedStorage',
} }
Util.merge(self, defaults) Util.merge(self, defaults)
Util.merge(self, args) Util.merge(self, args)
local controller local controller
if not self.side then if not self.side then
controller = Peripheral.getByMethod('getCraftingTasks') controller = Peripheral.getByMethod('getCraftingTasks')
else else
controller = Peripheral.getBySide(self.side) controller = Peripheral.getBySide(self.side)
end end
if controller then if controller then
Util.merge(self, controller) Util.merge(self, controller)
end end
end end
function RefinedAdapter:isValid() function RefinedAdapter:isValid()
return not not self.getCraftingTasks return not not self.getCraftingTasks
end end
function RefinedAdapter:getItemDetails(item) function RefinedAdapter:getItemDetails(item)
local detail = self.findItems(item) local detail = self.findItems(item)
if detail and #detail > 0 then if detail and #detail > 0 then
return detail[1].getMetadata() return detail[1].getMetadata()
end end
end end
function RefinedAdapter:getCachedItemDetails(item) function RefinedAdapter:getCachedItemDetails(item)
local cached = itemDB:get(item) local cached = itemDB:get(item)
if cached then if cached then
return cached return cached
end end
local detail = self:getItemDetails(item) local detail = self:getItemDetails(item)
if detail then if detail then
return itemDB:add(detail) return itemDB:add(detail)
end end
end end
function RefinedAdapter:refresh(throttle) function RefinedAdapter:refresh(throttle)
return self:listItems(throttle) return self:listItems(throttle)
end end
function RefinedAdapter:listItems(throttle) function RefinedAdapter:listItems(throttle)
local items = { } local items = { }
throttle = throttle or Util.throttle() throttle = throttle or Util.throttle()
local s, m = pcall(function() local s, m = pcall(function()
for _,v in pairs(self.listAvailableItems()) do for _,v in pairs(self.listAvailableItems()) do
--if v.count > 0 then --if v.count > 0 then
local item = self:getCachedItemDetails(v) local item = self:getCachedItemDetails(v)
if item then if item then
item = Util.shallowCopy(item) item = Util.shallowCopy(item)
item.count = v.count item.count = v.count
table.insert(items, item) table.insert(items, item)
end end
--end --end
throttle() throttle()
end end
end) end)
if not s and m then if not s and m then
_G._syslog(m) _G._syslog(m)
end end
itemDB:flush() itemDB:flush()
if not Util.empty(items) then if not Util.empty(items) then
return items return items
end end
end end
function RefinedAdapter:getItemInfo(item) function RefinedAdapter:getItemInfo(item)
return self:getItemDetails(item) return self:getItemDetails(item)
end end
function RefinedAdapter:isCPUAvailable() function RefinedAdapter:isCPUAvailable()
return true return true
end end
function RefinedAdapter:craft(item, qty) function RefinedAdapter:craft(item, qty)
local detail = self.findItem(item) local detail = self.findItem(item)
if detail then if detail then
return detail.craft(qty) return detail.craft(qty)
end end
end end
function RefinedAdapter:isCrafting(item) function RefinedAdapter:isCrafting(item)
for _,task in pairs(self.getCraftingTasks()) do for _,task in pairs(self.getCraftingTasks()) do
local output = task.getPattern().outputs[1] local output = task.getPattern().outputs[1]
if output.name == item.name and if output.name == item.name and
output.damage == item.damage and output.damage == item.damage and
output.nbtHash == item.nbtHash then output.nbtHash == item.nbtHash then
return true return true
end end
end end
return false return false
end end
function RefinedAdapter:provide(item, qty, slot, direction) function RefinedAdapter:provide(item, qty, slot, direction)
return pcall(function() return pcall(function()
for _,stack in pairs(self.listAvailableItems()) do for _,stack in pairs(self.listAvailableItems()) do
if stack.name == item.name and if stack.name == item.name and
(not item.damage or stack.damage == item.damage) and (not item.damage or stack.damage == item.damage) and
(not item.nbtHash or stack.nbtHash == item.nbtHash) then (not item.nbtHash or stack.nbtHash == item.nbtHash) then
local amount = math.min(qty, stack.count) local amount = math.min(qty, stack.count)
if amount > 0 then if amount > 0 then
local detail = self.findItem(item) local detail = self.findItem(item)
if detail then if detail then
return detail.export(direction or self.direction, amount, slot) return detail.export(direction or self.direction, amount, slot)
end end
end end
qty = qty - amount qty = qty - amount
if qty <= 0 then if qty <= 0 then
break break
end end
end end
end end
end) end)
end end
function RefinedAdapter:extract(slot, qty, toSlot) function RefinedAdapter:extract(slot, qty, toSlot)
self.pushItems(self.direction, slot, qty, toSlot) self.pushItems(self.direction, slot, qty, toSlot)
end end
function RefinedAdapter:insert(slot, qty, toSlot) function RefinedAdapter:insert(slot, qty, toSlot)
self.pullItems(self.direction, slot, qty, toSlot) self.pullItems(self.direction, slot, qty, toSlot)
end end
return RefinedAdapter return RefinedAdapter

View File

@@ -1,66 +1,66 @@
local class = require('class') local class = require('class')
local Event = require('event') local Event = require('event')
local Map = require('map') local Map = require('map')
local Proxy = require('proxy') local Proxy = require('core.proxy')
local Swarm = class() local Swarm = class()
function Swarm:init(args) function Swarm:init(args)
self.pool = { } self.pool = { }
Map.merge(self, args) Map.merge(self, args)
end end
function Swarm:add(id, args) function Swarm:add(id, args)
local member = Map.shallowCopy(args or { }) local member = Map.shallowCopy(args or { })
member.id = id member.id = id
self.pool[id] = member self.pool[id] = member
end end
function Swarm:remove(id, s, m) function Swarm:remove(id, s, m)
local member = self.pool[id] local member = self.pool[id]
if member then if member then
self.pool[id] = nil self.pool[id] = nil
self:onRemove(member, s, m) self:onRemove(member, s, m)
if member.socket then if member.socket then
member.socket:close() member.socket:close()
member.socket = nil member.socket = nil
end end
if member.handler then if member.handler then
member.handler:terminate() member.handler:terminate()
member.handler = nil member.handler = nil
end end
end end
end end
function Swarm:run(fn) function Swarm:run(fn)
for id, member in pairs(self.pool) do for id, member in pairs(self.pool) do
if not member.socket then if not member.socket then
member.handler = Event.addRoutine(function() member.handler = Event.addRoutine(function()
local s, m = pcall(function() local s, m = pcall(function()
member.turtle, member.socket = Proxy.create(id, 'turtle') member.turtle, member.socket = Proxy.create(id, 'turtle')
fn(member) fn(member)
end) end)
self:remove(id, s, m) self:remove(id, s, m)
end) end)
end end
end end
end end
function Swarm:stop() function Swarm:stop()
for _, member in pairs(self.pool) do for _, member in pairs(self.pool) do
if member.socket then if member.socket then
member.socket:close() member.socket:close()
member.socket = nil member.socket = nil
end end
end end
end end
-- Override -- Override
function Swarm:onRemove(member, success, msg) function Swarm:onRemove(member, success, msg)
print('removed from pool: ' .. member.id) print('removed from pool: ' .. member.id)
if not success then if not success then
_G.printError(msg) _G.printError(msg)
end end
end end
return Swarm return Swarm

View File

@@ -3,47 +3,47 @@ local Util = require('util')
local TableDB = class() local TableDB = class()
function TableDB:init(args) function TableDB:init(args)
local defaults = { local defaults = {
fileName = '', fileName = '',
dirty = false, dirty = false,
data = { }, data = { },
} }
Util.merge(defaults, args) Util.merge(defaults, args)
Util.merge(self, defaults) Util.merge(self, defaults)
end end
function TableDB:load() function TableDB:load()
local t = Util.readTable(self.fileName) local t = Util.readTable(self.fileName)
if t then if t then
self.data = t.data or t self.data = t.data or t
end end
end end
function TableDB:add(key, entry) function TableDB:add(key, entry)
if type(key) == 'table' then if type(key) == 'table' then
key = table.concat(key, ':') key = table.concat(key, ':')
end end
self.data[key] = entry self.data[key] = entry
self.dirty = true self.dirty = true
end end
function TableDB:get(key) function TableDB:get(key)
if type(key) == 'table' then if type(key) == 'table' then
key = table.concat(key, ':') key = table.concat(key, ':')
end end
return self.data[key] return self.data[key]
end end
function TableDB:remove(key) function TableDB:remove(key)
self.data[key] = nil self.data[key] = nil
self.dirty = true self.dirty = true
end end
function TableDB:flush() function TableDB:flush()
if self.dirty then if self.dirty then
Util.writeTable(self.fileName, self.data) Util.writeTable(self.fileName, self.data)
self.dirty = false self.dirty = false
end end
end end
return TableDB 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 CRAFTING_TABLE = 'minecraft:crafting_table'
local function clearGrid(inventory) local function clearGrid(inventory)
for i = 1, 16 do for i = 1, 16 do
local count = turtle.getItemCount(i) local count = turtle.getItemCount(i)
if count > 0 then if count > 0 then
inventory:insert(i, count) inventory:insert(i, count)
if turtle.getItemCount(i) ~= 0 then if turtle.getItemCount(i) ~= 0 then
return false return false
end end
end end
end end
return true return true
end end
function turtle.craftItem(item, count, inventoryInfo) function turtle.craftItem(item, count, inventoryInfo)
local success, msg local success, msg
local inventory = Adapter.wrap(inventoryInfo) local inventory = Adapter.wrap(inventoryInfo)
if not inventory then if not inventory then
return false, 'Invalid inventory' return false, 'Invalid inventory'
end end
local equipped, side local equipped, side
if not turtle.isEquipped('workbench') then if not turtle.isEquipped('workbench') then
local modemSide = turtle.isEquipped('modem') or 'right' local modemSide = turtle.isEquipped('modem') or 'right'
local osides = { left = 'right', right = 'left' } local osides = { left = 'right', right = 'left' }
side = osides[modemSide] side = osides[modemSide]
if not turtle.select(CRAFTING_TABLE) then if not turtle.select(CRAFTING_TABLE) then
clearGrid(inventory) clearGrid(inventory)
if not turtle.selectOpenSlot() then if not turtle.selectOpenSlot() then
return false, 'Inventory is full' return false, 'Inventory is full'
end end
if not inventory:provide({ name = CRAFTING_TABLE, damage = 0 }, 1) then if not inventory:provide({ name = CRAFTING_TABLE, damage = 0 }, 1) then
return false, 'Missing crafting table' return false, 'Missing crafting table'
end end
end end
local slot = turtle.select(CRAFTING_TABLE) local slot = turtle.select(CRAFTING_TABLE)
turtle.equip(side, CRAFTING_TABLE) turtle.equip(side, CRAFTING_TABLE)
equipped = turtle.getItemDetail(slot.index) equipped = turtle.getItemDetail(slot.index)
end end
clearGrid(inventory) clearGrid(inventory)
success, msg = Craft.craftRecipe(item, count or 1, inventory) success, msg = Craft.craftRecipe(item, count or 1, inventory)
if equipped then if equipped then
turtle.selectOpenSlot() turtle.selectOpenSlot()
inventory:provide({ name = equipped.name, damage = equipped.damage }, 1) inventory:provide({ name = equipped.name, damage = equipped.damage }, 1)
turtle.equip(side, equipped.name .. ':' .. equipped.damage) turtle.equip(side, equipped.name .. ':' .. equipped.damage)
end end
return success, msg return success, msg
end end
function turtle.canCraft(item, count, items) function turtle.canCraft(item, count, items)
return Craft.canCraft(item, count, items) return Craft.canCraft(item, count, items)
end end
return true return true

View File

@@ -9,167 +9,167 @@ local box = { }
local oldCallback local oldCallback
local function toKey(pt) local function toKey(pt)
return table.concat({ pt.x, pt.y, pt.z }, ':') return table.concat({ pt.x, pt.y, pt.z }, ':')
end end
local function addNode(node) local function addNode(node)
for i = 0, 5 do for i = 0, 5 do
local hi = turtle.getHeadingInfo(i) local hi = turtle.getHeadingInfo(i)
local testNode = { x = node.x + hi.xd, y = node.y + hi.yd, z = node.z + hi.zd } local testNode = { x = node.x + hi.xd, y = node.y + hi.yd, z = node.z + hi.zd }
if Point.inBox(testNode, box) then if Point.inBox(testNode, box) then
local key = toKey(testNode) local key = toKey(testNode)
if not checkedNodes[key] then if not checkedNodes[key] then
nodes[key] = testNode nodes[key] = testNode
end end
end end
end end
end end
local function dig(action) local function dig(action)
local directions = { local directions = {
top = 'up', top = 'up',
bottom = 'down', bottom = 'down',
} }
-- convert to up, down, north, south, east, west -- convert to up, down, north, south, east, west
local direction = directions[action.side] or local direction = directions[action.side] or
turtle.getHeadingInfo(turtle.point.heading).direction turtle.getHeadingInfo(turtle.point.heading).direction
local hi = turtle.getHeadingInfo(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 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) local key = toKey(node)
checkedNodes[key] = true checkedNodes[key] = true
nodes[key] = nil nodes[key] = nil
if action.dig() then if action.dig() then
addNode(node) addNode(node)
repeat until not action.dig() -- sand, etc repeat until not action.dig() -- sand, etc
return true return true
end end
end end
end end
local function move(action) local function move(action)
if action == 'turn' then if action == 'turn' then
dig(turtle.getAction('forward')) dig(turtle.getAction('forward'))
elseif action == 'up' then elseif action == 'up' then
dig(turtle.getAction('up')) dig(turtle.getAction('up'))
dig(turtle.getAction('forward')) dig(turtle.getAction('forward'))
elseif action == 'down' then elseif action == 'down' then
dig(turtle.getAction('down')) dig(turtle.getAction('down'))
dig(turtle.getAction('forward')) dig(turtle.getAction('forward'))
elseif action == 'back' then elseif action == 'back' then
dig(turtle.getAction('up')) dig(turtle.getAction('up'))
dig(turtle.getAction('down')) dig(turtle.getAction('down'))
end end
if oldCallback then if oldCallback then
oldCallback(action) oldCallback(action)
end end
end end
-- find the closest block -- find the closest block
-- * favor same plane -- * favor same plane
-- * going backwards only if the dest is above or below -- * going backwards only if the dest is above or below
local function closestPoint(reference, pts) local function closestPoint(reference, pts)
local lpt, lm -- lowest local lpt, lm -- lowest
for _,pt in pairs(pts) do for _,pt in pairs(pts) do
local m = Point.turtleDistance(reference, pt) local m = Point.turtleDistance(reference, pt)
local h = Point.calculateHeading(reference, pt) local h = Point.calculateHeading(reference, pt)
local t = Point.calculateTurns(reference.heading, h) local t = Point.calculateTurns(reference.heading, h)
if pt.y ~= reference.y then -- try and stay on same plane if pt.y ~= reference.y then -- try and stay on same plane
m = m + .01 m = m + .01
end end
if t ~= 2 or pt.y == reference.y then if t ~= 2 or pt.y == reference.y then
m = m + t m = m + t
if t > 0 then if t > 0 then
m = m + .01 m = m + .01
end end
end end
if not lm or m < lm then if not lm or m < lm then
lpt = pt lpt = pt
lm = m lm = m
end end
end end
return lpt return lpt
end end
local function getAdjacentPoint(pt) local function getAdjacentPoint(pt)
local t = { } local t = { }
table.insert(t, pt) table.insert(t, pt)
for i = 0, 5 do for i = 0, 5 do
local hi = turtle.getHeadingInfo(i) local hi = turtle.getHeadingInfo(i)
local heading local heading
if i < 4 then if i < 4 then
heading = (hi.heading + 2) % 4 heading = (hi.heading + 2) % 4
end end
table.insert(t, { x = pt.x + hi.xd, z = pt.z + hi.zd, y = pt.y + hi.yd, heading = heading }) table.insert(t, { x = pt.x + hi.xd, z = pt.z + hi.zd, y = pt.y + hi.yd, heading = heading })
end end
return closestPoint(turtle.getPoint(), t) return closestPoint(turtle.getPoint(), t)
end end
function turtle.level(startPt, endPt, firstPt, verbose) function turtle.level(startPt, endPt, firstPt, verbose)
checkedNodes = { } checkedNodes = { }
nodes = { } nodes = { }
box = { } box = { }
box.x = math.min(startPt.x, endPt.x) box.x = math.min(startPt.x, endPt.x)
box.y = math.min(startPt.y, endPt.y) box.y = math.min(startPt.y, endPt.y)
box.z = math.min(startPt.z, endPt.z) box.z = math.min(startPt.z, endPt.z)
box.ex = math.max(startPt.x, endPt.x) box.ex = math.max(startPt.x, endPt.x)
box.ey = math.max(startPt.y, endPt.y) box.ey = math.max(startPt.y, endPt.y)
box.ez = math.max(startPt.z, endPt.z) box.ez = math.max(startPt.z, endPt.z)
if not Point.inBox(firstPt, box) then if not Point.inBox(firstPt, box) then
error('Starting point is not in leveling area') error('Starting point is not in leveling area')
end end
if not turtle.pathfind(firstPt) then if not turtle.pathfind(firstPt) then
error('failed to reach starting point') error('failed to reach starting point')
end end
turtle.set({ turtle.set({
digPolicy = dig, digPolicy = dig,
attackPolicy = 'attack', attackPolicy = 'attack',
movePolicy = 'moveAssured', movePolicy = 'moveAssured',
}) })
oldCallback = turtle.getMoveCallback() oldCallback = turtle.getMoveCallback()
turtle.setMoveCallback(move) turtle.setMoveCallback(move)
repeat repeat
local key = toKey(turtle.point) local key = toKey(turtle.point)
checkedNodes[key] = true checkedNodes[key] = true
nodes[key] = nil nodes[key] = nil
dig(turtle.getAction('down')) dig(turtle.getAction('down'))
dig(turtle.getAction('up')) dig(turtle.getAction('up'))
dig(turtle.getAction('forward')) dig(turtle.getAction('forward'))
if verbose then if verbose then
print(string.format('%d nodes remaining', Util.size(nodes))) print(string.format('%d nodes remaining', Util.size(nodes)))
end end
if not next(nodes) then if not next(nodes) then
break break
end end
local node = closestPoint(turtle.point, nodes) local node = closestPoint(turtle.point, nodes)
node = getAdjacentPoint(node) node = getAdjacentPoint(node)
if not turtle.go(node) then if not turtle.go(node) then
break break
end end
until turtle.isAborted() until turtle.isAborted()
turtle.resetState() turtle.resetState()
turtle.setMoveCallback(oldCallback) turtle.setMoveCallback(oldCallback)
end end
return true return true

View File

@@ -18,12 +18,12 @@ local Runners = {
} }
Equipper.equipLeft('minecraft:diamond_sword') 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 local facing = scanner.getBlockMeta(0, 0, 0).state.facing
turtle.point.heading = Point.facings[facing].heading 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.setMovementStrategy('goto')
turtle.set({ attackPolicy = 'attack' }) turtle.set({ attackPolicy = 'attack' })
@@ -32,9 +32,9 @@ local function findChests()
if chest then if chest then
return { chest } return { chest }
end end
Equipper.equipRight('plethora:module:2', 'plethora:scanner') Equipper.equipRight('plethora:scanner')
local chests = scanner.scan() local chests = scanner.scan()
Equipper.equipRight('plethora:module:3', 'plethora:sensor') Equipper.equipRight('plethora:sensor')
Util.filterInplace(chests, function(b) Util.filterInplace(chests, function(b)
if b.name == 'minecraft:chest' or if b.name == 'minecraft:chest' or

View File

@@ -12,238 +12,241 @@ local STARTUP_FILE = 'usr/autorun/farmer.lua'
local MIN_FUEL = 2500 local MIN_FUEL = 2500
local FUEL = Util.transpose { local FUEL = Util.transpose {
'minecraft:coal:0', 'minecraft:coal:0',
'minecraft:coal:1', 'minecraft:coal:1',
'minecraft:blaze_rod:0', '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 { local crops = Util.readTable(CONFIG_FILE) or {
['minecraft:wheat'] = ['minecraft:wheat'] =
{ seed = 'minecraft:wheat_seeds', mature = 7, action = 'plant' }, { seed = 'minecraft:wheat_seeds', mature = 7, action = 'plant' },
['minecraft:carrots'] = ['minecraft:carrots'] =
{ seed = 'minecraft:carrot', mature = 7, action = 'plant' }, { seed = 'minecraft:carrot', mature = 7, action = 'plant' },
['minecraft:potatoes'] = ['minecraft:potatoes'] =
{ seed = 'minecraft:potato', mature = 7, action = 'plant' }, { seed = 'minecraft:potato', mature = 7, action = 'plant' },
['minecraft:beetroots'] = ['minecraft:beetroots'] =
{ seed = 'minecraft:beetroot_seeds', mature = 3, action = 'plant' }, { seed = 'minecraft:beetroot_seeds', mature = 3, action = 'plant' },
['minecraft:nether_wart'] = ['minecraft:nether_wart'] =
{ seed = 'minecraft:nether_wart', mature = 3, action = 'plant' }, { seed = 'minecraft:nether_wart', mature = 3, action = 'plant' },
['minecraft:cocoa'] = ['minecraft:cocoa'] =
{ seed = 'minecraft:dye:3', mature = 8, action = 'pick' }, { seed = 'minecraft:dye:3', mature = 8, action = 'pick' },
['minecraft:reeds'] = { action = 'bash' }, ['minecraft:reeds'] = { action = 'bash' },
['minecraft:chorus_flower'] = { action = 'bash' }, ['minecraft:chorus_flower'] = { action = 'bash' },
['minecraft:chorus_plant'] = ['minecraft:chorus_plant'] =
{ seed = 'minecraft:chorus_flower', mature = 0, action = 'bash-smash', }, { seed = 'minecraft:chorus_flower', mature = 0, action = 'bash-smash', },
['minecraft:melon_block'] = { action = 'smash' }, ['minecraft:melon_block'] = { action = 'smash' },
['minecraft:pumpkin'] = { action = 'smash' }, ['minecraft:pumpkin'] = { action = 'smash' },
['minecraft:chest'] = { action = 'drop' }, ['minecraft:chest'] = { action = 'drop' },
['minecraft:cactus'] = { action = 'smash' }, ['minecraft:cactus'] = { action = 'smash' },
} }
if not fs.exists(CONFIG_FILE) then if not fs.exists(CONFIG_FILE) then
Util.writeTable(CONFIG_FILE, crops) Util.writeTable(CONFIG_FILE, crops)
end end
if not fs.exists(STARTUP_FILE) then if not fs.exists(STARTUP_FILE) then
Util.writeFile(STARTUP_FILE, Util.writeFile(STARTUP_FILE,
[[os.sleep(1) [[os.sleep(1)
shell.openForegroundTab('farmer.lua')]]) shell.openForegroundTab('farmer.lua')]])
print('Autorun program created: ' .. STARTUP_FILE) print('Autorun program created: ' .. STARTUP_FILE)
end end
local retain = Util.transpose { local retain = Util.transpose {
"minecraft:diamond_pickaxe", "minecraft:diamond_pickaxe",
"plethora:module:2", "plethora:module:2",
"plethora:module:3", "plethora:module:3",
} }
for _, v in pairs(crops) do for _, v in pairs(crops) do
if v.seed then if v.seed then
retain[v.seed] = true retain[v.seed] = true
end end
end end
local function scan() local function scan()
local blocks = scanner.scan() local blocks = scanner.scan()
local summed = turtle.getSummedInventory() local summed = turtle.getSummedInventory()
local doDropOff local doDropOff
for _,v in pairs(summed) do for _,v in pairs(summed) do
if v.count > 32 then if v.count > 32 then
doDropOff = true doDropOff = true
break break
end end
end end
Util.filterInplace(blocks, function(b) Util.filterInplace(blocks, function(b)
b.action = crops[b.name] and crops[b.name].action b.action = crops[b.name] and crops[b.name].action
if b.action == 'bash' then if b.action == 'bash' then
return b.y == 0 return b.y == 0
end end
if b.action == 'drop' then if b.action == 'drop' then
return doDropOff and (b.y == -1 or b.y == 1) return doDropOff and (b.y == -1 or b.y == 1)
end end
if b.action == 'bash-smash' then if b.action == 'bash-smash' then
if b.y == -1 then if b.y == -1 then
b.action = 'smash' b.action = 'smash'
end end
if b.y == 0 then if b.y == 0 then
b.action = 'bash' b.action = 'bash'
end end
return b.action ~= 'bash-smash' return b.action ~= 'bash-smash'
end end
if b.action == 'smash' then if b.action == 'smash' then
return b.y == -1 return b.y == -1
end end
if b.action == 'pick' then if b.action == 'pick' then
return b.y == 0 and b.state.age == 2 return b.y == 0 and b.state.age == 2
end end
if b.action == 'bump' then if b.action == 'bump' then
return b.y == 0 return b.y == 0
end end
return b.action == 'plant' and if b.action == 'plant' and b.y == -1 then
b.metadata == crops[b.name].mature and if not b.metadata then -- minecraft 1.10
b.y == -1 b = scanner.getBlockMeta(b.x, b.y, b.z)
end) end
return b.metadata == crops[b.name].mature
end
end)
local harvestCount = 0 local harvestCount = 0
for _,b in pairs(blocks) do for _,b in pairs(blocks) do
b.x = b.x + turtle.point.x b.x = b.x + turtle.point.x
b.y = b.y + turtle.point.y b.y = b.y + turtle.point.y
b.z = b.z + turtle.point.z b.z = b.z + turtle.point.z
if b.action ~= 'drop' then if b.action ~= 'drop' then
harvestCount = harvestCount + 1 harvestCount = harvestCount + 1
end end
end end
return blocks, harvestCount return blocks, harvestCount
end end
local function harvest(blocks) 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) Point.eachClosest(turtle.point, blocks, function(b)
turtle.select(1) turtle.select(1)
if b.action == 'bash' then if b.action == 'bash' then
turtle.digForwardAt(b) turtle.digForwardAt(b)
elseif b.action == 'drop' and not dropped then elseif b.action == 'drop' and not dropped then
if turtle.go(b.y == 1 and Point.below(b) or Point.above(b)) 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 dropFn = b.y == 1 and turtle.dropUp or turtle.dropDown
local dropDir = b.y == 1 and 'down' or 'up' local dropDir = b.y == 1 and 'down' or 'up'
turtle.eachFilledSlot(function(slot) turtle.eachFilledSlot(function(slot)
if not retain[slot.name] and not retain[slot.key] then if not retain[slot.name] and not retain[slot.key] then
turtle.select(slot.index) turtle.select(slot.index)
dropFn() dropFn()
end end
end) end)
local summed = turtle.getSummedInventory() local summed = turtle.getSummedInventory()
for k,v in pairs(summed) do for k,v in pairs(summed) do
if v.count > 16 then if v.count > 16 then
dropFn(k, v.count - 16) dropFn(k, v.count - 16)
end end
end end
dropped = true dropped = true
turtle.condense() turtle.condense()
if turtle.getFuelLevel() < MIN_FUEL then if turtle.getFuelLevel() < MIN_FUEL then
local inv = peripheral.wrap('bottom') local inv = peripheral.wrap('bottom')
if inv and inv.list then if inv and inv.list then
for k, v in pairs(inv.list()) do for k, v in pairs(inv.list()) do
if FUEL[table.concat({ v.name, v.damage }, ':')] then if FUEL[table.concat({ v.name, v.damage }, ':')] then
local count = inv.pushItems(dropDir, k, v.count) local count = inv.pushItems(dropDir, k, v.count)
if count > 0 then if count > 0 then
turtle.refuel(v.name, v.count) turtle.refuel(v.name, v.count)
print('Fuel: ' .. turtle.getFuelLevel()) print('Fuel: ' .. turtle.getFuelLevel())
break break
end end
end end
end end
end end
end end
end end
elseif b.action == 'smash' then elseif b.action == 'smash' then
if turtle.digDownAt(b) then if turtle.digDownAt(b) then
if crops[b.name].seed then if crops[b.name].seed then
turtle.placeDown(crops[b.name].seed) turtle.placeDown(crops[b.name].seed)
end end
end end
elseif b.action == 'plant' then elseif b.action == 'plant' then
if turtle.digDownAt(b) then if turtle.digDownAt(b) then
turtle.placeDown(crops[b.name].seed) turtle.placeDown(crops[b.name].seed)
end end
elseif b.action == 'bump' then elseif b.action == 'bump' then
if turtle.faceAgainst(b) then if turtle.faceAgainst(b) then
Equipper.equipRight('plethora:module:3', 'plethora:sensor') Equipper.equipRight('plethora:sensor')
os.sleep(.5) os.sleep(.5)
-- search the ground for the dropped cactus -- search the ground for the dropped cactus
local sensed = peripheral.call('right', 'sense') local sensed = peripheral.call('right', 'sense')
Equipper.equipRight('minecraft:diamond_pickaxe') Equipper.equipRight('minecraft:diamond_pickaxe')
Util.filterInplace(sensed, function(s) Util.filterInplace(sensed, function(s)
if s.displayName == 'item.tile.cactus' then if s.displayName == 'item.tile.cactus' then
s.x = Util.round(s.x) + turtle.point.x s.x = Util.round(s.x) + turtle.point.x
s.z = Util.round(s.z) + turtle.point.z s.z = Util.round(s.z) + turtle.point.z
s.y = -1 s.y = -1
if Point.distance(b, s) < 6 then if Point.distance(b, s) < 6 then
return true return true
end end
end end
end) end)
Point.eachClosest(turtle.point, sensed, function(s) Point.eachClosest(turtle.point, sensed, function(s)
turtle.suckDownAt(s) turtle.suckDownAt(s)
end) end)
end end
elseif b.action == 'pick' then elseif b.action == 'pick' then
local h = Point.facings[b.state.facing].heading local h = Point.facings[b.state.facing].heading
local hi = Point.headings[(h + 2) % 4] -- opposite heading local hi = Point.headings[(h + 2) % 4] -- opposite heading
-- without pathfinding, will be unable to circle log -- 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.go({ x = b.x + hi.xd, z = b.z + hi.zd, heading = h }) then
if turtle.dig() then if turtle.dig() then
turtle.place(crops[b.name].seed) turtle.place(crops[b.name].seed)
end end
end end
end end
end) end)
Equipper.equipRight('plethora:module:2', 'plethora:scanner') Equipper.equipRight('plethora:scanner')
end end
local s, m = turtle.run(function() local s, m = turtle.run(function()
local facing = scanner.getBlockMeta(0, 0, 0).state.facing local facing = scanner.getBlockMeta(0, 0, 0).state.facing
turtle.point.heading = Point.facings[facing].heading turtle.point.heading = Point.facings[facing].heading
turtle.setStatus('farming') turtle.setStatus('farming')
print('Fuel: ' .. turtle.getFuelLevel()) print('Fuel: ' .. turtle.getFuelLevel())
turtle.setMovementStrategy('goto') turtle.setMovementStrategy('goto')
repeat repeat
local blocks, harvestCount = scan() local blocks, harvestCount = scan()
if harvestCount > 0 then if harvestCount > 0 then
turtle.setStatus('Harvesting') turtle.setStatus('Harvesting')
harvest(blocks) harvest(blocks)
turtle.setStatus('Sleeping') turtle.setStatus('Sleeping')
end end
os.sleep(10) os.sleep(10)
if turtle.getFuelLevel() < 10 then if turtle.getFuelLevel() < 10 then
turtle.setStatus('Out of fuel') turtle.setStatus('Out of fuel')
error('Out of fuel') error('Out of fuel')
end end
until turtle.isAborted() until turtle.isAborted()
end) end)
if not s and m then if not s and m then
error(m) error(m)
end end

View File

@@ -9,8 +9,9 @@ Requirements
* Standard Modem * Standard Modem
* Block Scanner * Block Scanner
* Entity Sensor * Entity Sensor
* Crafting Table * Furnace
* Vanilla Chest * Vanilla Chest
* Sapling
* GPS * GPS
Setup Setup
@@ -19,18 +20,20 @@ Setup
> package install farms > package install farms
> reboot > 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. 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. 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: To start the program, run:
> superTreefarm > superTreefarm
A startup file is created automatically the first time the program is run (usr/autorun/superTreefarm.lua). 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 Tips
==== ====

View File

@@ -10,111 +10,117 @@ local turtle = _G.turtle
local STARTUP_FILE = 'usr/autorun/rancher.lua' local STARTUP_FILE = 'usr/autorun/rancher.lua'
local retain = Util.transpose { local retain = Util.transpose {
'minecraft:shears', 'minecraft:shears',
'minecraft:wheat', 'minecraft:wheat',
'minecraft:diamond_sword', 'minecraft:diamond_sword',
'plethora:module:3', 'plethora:module:3',
} }
local config = { local config = {
animal = 'Cow', animal = 'Cow',
max_animals = 15, max_animals = 15,
} }
Config.load('rancher', config) Config.load('rancher', config)
local ANIMALS = { local ANIMALS = {
Pig = { min = 0, food = 'minecraft:carrot' }, Pig = { min = 0, food = 'minecraft:carrot' },
Sheep = { min = .5, food = 'minecraft:wheat' }, Sheep = { min = .5, food = 'minecraft:wheat' },
Cow = { min = .5, food = 'minecraft:wheat' }, Cow = { min = .5, food = 'minecraft:wheat' },
} }
local animal = ANIMALS[config.animal] local animal = ANIMALS[config.animal]
Equipper.equipLeft('minecraft:diamond_sword') 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') local chest = Adapter({ side = 'bottom', direction = 'up' }) or error('missing chest')
if not fs.exists(STARTUP_FILE) then if not fs.exists(STARTUP_FILE) then
Util.writeFile(STARTUP_FILE, Util.writeFile(STARTUP_FILE,
[[os.sleep(1) [[os.sleep(1)
shell.openForegroundTab('rancher.lua')]]) shell.openForegroundTab('rancher.lua')]])
print('Autorun program created: ' .. STARTUP_FILE) print('Autorun program created: ' .. STARTUP_FILE)
end end
local function getAnimalCount() local function getAnimalCount()
local blocks = sensor.sense() local blocks = sensor.sense()
local grown = 0 local grown = 0
local babies = 0 local babies = 0
Util.filterInplace(blocks, function(v) Util.filterInplace(blocks, function(v)
if v.name == config.animal then if v.name == config.animal then
if v.y > -.5 then grown = grown + 1 end local entity = sensor.getMetaByID(v.id)
if v.y < -.5 then babies = babies + 1 end if entity then
return v.y > -.5 if entity.isChild then
end babies = babies + 1
end) 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 end
local function butcher() local function butcher()
Equipper.equipRight('minecraft:diamond_sword') Equipper.equipRight('minecraft:diamond_sword')
turtle.select(1) turtle.select(1)
turtle.attack() turtle.attack()
for _ = 1, 3 do for _ = 1, 3 do
turtle.turnRight() turtle.turnRight()
turtle.attack() turtle.attack()
end end
Equipper.equipRight('plethora:module:3', 'plethora:sensor') Equipper.equipRight('plethora:sensor')
turtle.eachFilledSlot(function(slot) turtle.eachFilledSlot(function(slot)
if not retain[slot.name] then if not retain[slot.name] then
chest:insert(slot.index, 64) chest:insert(slot.index, 64)
end end
end) end)
end end
local function breed() local function breed()
turtle.select(1) turtle.select(1)
if config.animal == 'Sheep' then if config.animal == 'Sheep' then
turtle.place('minecraft:shears') turtle.place('minecraft:shears')
end end
turtle.place('minecraft:wheat') turtle.place('minecraft:wheat')
for _ = 1, 3 do for _ = 1, 3 do
turtle.turnRight() turtle.turnRight()
if config.animal == 'Sheep' then if config.animal == 'Sheep' then
turtle.place('minecraft:shears') turtle.place('minecraft:shears')
end end
turtle.place('minecraft:wheat') turtle.place('minecraft:wheat')
end end
end end
local s, m = turtle.run(function() local s, m = turtle.run(function()
print('Configured animal: ' .. config.animal) print('Configured animal: ' .. config.animal)
repeat repeat
local animalCount = getAnimalCount() local animalCount = getAnimalCount()
if animalCount > config.max_animals then if animalCount > config.max_animals then
turtle.setStatus('Butchering') turtle.setStatus('Butchering')
butcher() butcher()
elseif turtle.getItemCount(animal.food) == 0 then elseif turtle.getItemCount(animal.food) == 0 then
if chest:provide({ name = animal.food, damage = 0 }, 64) == 0 then if chest:provide({ name = animal.food, damage = 0 }, 64) == 0 then
print('Out of ' .. animal.food) print('Out of ' .. animal.food)
turtle.setStatus('Out of food') turtle.setStatus('Out of food')
end end
else else
turtle.setStatus('Breeding') turtle.setStatus('Breeding')
breed() breed()
end end
os.sleep(5) os.sleep(5)
until turtle.isAborted() until turtle.isAborted()
end) end)
if not s and m then if not s and m then
error(m) error(m)
end end

View File

@@ -11,7 +11,7 @@ local STARTUP_FILE = 'usr/autorun/spawner.lua'
local mobTypes = { } local mobTypes = { }
Equipper.equipLeft('minecraft:diamond_sword') Equipper.equipLeft('minecraft:diamond_sword')
local scanner = Equipper.equipRight('plethora:module:2', 'plethora:scanner') local scanner = Equipper.equipRight('plethora:scanner')
turtle.reset() turtle.reset()
local facing = scanner.getBlockMeta(0, 0, 0).state.facing local facing = scanner.getBlockMeta(0, 0, 0).state.facing
@@ -28,13 +28,13 @@ Util.filterInplace(data, function(b)
end) end)
local chest = Point.closest(spawner, data) or error('missing drop off chest') 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 if not fs.exists(STARTUP_FILE) then
Util.writeFile(STARTUP_FILE, Util.writeFile(STARTUP_FILE,
string.format([[os.sleep(1) string.format([[os.sleep(1)
shell.openForegroundTab('spawner.lua %s')]], table.concat({ ... }, ' '))) shell.openForegroundTab('spawner.lua %s')]], table.concat({ ... }, ' ')))
print('Autorun program created: ' .. STARTUP_FILE) print('Autorun program created: ' .. STARTUP_FILE)
end end
turtle.setMovementStrategy('goto') turtle.setMovementStrategy('goto')
@@ -70,8 +70,8 @@ local function normalize(b)
b.z = Util.round(b.z) + turtle.point.z b.z = Util.round(b.z) + turtle.point.z
return b.x >= spawner.x - 4 and b.x <= spawner.x + 4 and 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.y >= spawner.y - 4 and b.y <= spawner.y + 4 and
b.z >= spawner.z - 4 and b.z <= spawner.z + 4 b.z >= spawner.z - 4 and b.z <= spawner.z + 4
end end
local function aboveAttack(b) 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 os = _G.os
local tunes = { local tunes = {
{ sound = 'record.11', length = '1:11' }, { sound = 'record.11', length = '1:11' },
{ sound = 'record.13', length = '2:58' }, { sound = 'record.13', length = '2:58' },
{ sound = 'record.blocks', length = '5:45' }, { sound = 'record.blocks', length = '5:45' },
{ sound = 'record.cat', length = '3:05' }, { sound = 'record.cat', length = '3:05' },
{ sound = 'record.chirp', length = '3:05' }, { sound = 'record.chirp', length = '3:05' },
{ sound = 'record.far', length = '2:54' }, { sound = 'record.far', length = '2:54' },
{ sound = 'record.mall', length = '3:17' }, { sound = 'record.mall', length = '3:17' },
{ sound = 'record.mellohi', length = '1:36' }, { sound = 'record.mellohi', length = '1:36' },
{ sound = 'record.stal', length = '2:30' }, { sound = 'record.stal', length = '2:30' },
{ sound = 'record.strad', length = '3:08' }, { sound = 'record.strad', length = '3:08' },
{ sound = 'record.wait', length = '3:58' }, { sound = 'record.wait', length = '3:58' },
{ sound = 'record.ward', length = '4:11' }, { sound = 'record.ward', length = '4:11' },
} }
while true do while true do
local song = tunes[math.random(1, #tunes)] local song = tunes[math.random(1, #tunes)]
Sound.play(song.sound) Sound.play(song.sound)
local min, sec = song.length:match('(%d+):(%d+)') local min, sec = song.length:match('(%d+):(%d+)')
local length = tonumber(min)*60 + tonumber(sec) local length = tonumber(min)*60 + tonumber(sec)
print(string.format('Playing %s (%s)', song.sound, song.length)) print(string.format('Playing %s (%s)', song.sound, song.length))
os.sleep(length + 3) os.sleep(length + 3)
end end

View File

@@ -3,6 +3,7 @@ local GPS = require('gps')
local Util = require('util') local Util = require('util')
local args = { ... } local args = { ... }
local colors = _G.colors
local fs = _G.fs local fs = _G.fs
local gps = _G.gps local gps = _G.gps
local os = _G.os local os = _G.os

View File

@@ -16,68 +16,68 @@ local turtle = _G.turtle
multishell.setTitle(multishell.getCurrent(), 'Milo') multishell.setTitle(multishell.getCurrent(), 'Milo')
local function Syntax(msg) local function Syntax(msg)
print([[ print([[
Turtle must be provided with: Turtle must be provided with:
* Introspection module (never bound) * Introspection module (never bound)
* Workbench * Workbench
Turtle must be connected to: Turtle must be connected to:
* Wired modem (activated) * Wired modem (activated)
]]) ]])
error(msg) error(msg)
end end
local modem local modem
for _,v in pairs(device) do for _,v in pairs(device) do
if v.type == 'wired_modem' then if v.type == 'wired_modem' then
if modem then if modem then
Syntax('Only 1 wired modem can be connected') Syntax('Only 1 wired modem can be connected')
end end
modem = v modem = v
end end
end end
if not modem or not modem.getNameLocal then if not modem or not modem.getNameLocal then
Syntax('Wired modem missing') Syntax('Wired modem missing')
end end
if not modem.getNameLocal() then if not modem.getNameLocal() then
Syntax('Wired modem is not active') Syntax('Wired modem is not active')
end end
local introspection = device['plethora:introspection'] or local introspection = device['plethora:introspection'] or
turtle.equip('left', 'plethora:module:0') and device['plethora:introspection'] or turtle.equip('left', 'plethora:module:0') and device['plethora:introspection'] or
Syntax('Introspection module missing') Syntax('Introspection module missing')
if not device.workbench then if not device.workbench then
turtle.equip('right', 'minecraft:crafting_table:0') turtle.equip('right', 'minecraft:crafting_table:0')
if not device.workbench then if not device.workbench then
Syntax('Workbench missing') Syntax('Workbench missing')
end end
end end
local localName = modem.getNameLocal() local localName = modem.getNameLocal()
local context = { local context = {
resources = Util.readTable(Milo.RESOURCE_FILE) or { }, resources = Util.readTable(Milo.RESOURCE_FILE) or { },
state = { }, state = { },
craftingQueue = { }, craftingQueue = { },
tasks = { }, tasks = { },
queue = { }, queue = { },
plugins = { }, plugins = { },
loggers = { }, loggers = { },
taskTimer = 0, taskTimer = 0,
taskCounter = 0, taskCounter = 0,
storage = Storage(), storage = Storage(),
turtleInventory = { turtleInventory = {
name = localName, name = localName,
mtype = 'hidden', mtype = 'hidden',
adapter = introspection.getInventory(), adapter = introspection.getInventory(),
} }
} }
context.storage.nodes[localName] = context.turtleInventory context.storage.nodes[localName] = context.turtleInventory
@@ -88,23 +88,23 @@ context.storage:initStorage()
context.storage.turtleInventory = context.turtleInventory context.storage.turtleInventory = context.turtleInventory
local function loadPlugin(file) local function loadPlugin(file)
local s, plugin = Util.run(_ENV, file, context) local s, plugin = Util.run(_ENV, file, context)
if not s and plugin then if not s and plugin then
_G.printError('Error loading: ' .. file) _G.printError('Error loading: ' .. file)
error(plugin or 'Unknown error') error(plugin or 'Unknown error')
end end
if plugin and type(plugin) == 'table' then if plugin and type(plugin) == 'table' then
Milo:registerPlugin(plugin) Milo:registerPlugin(plugin)
end end
end end
local function loadDirectory(dir) local function loadDirectory(dir)
for _, file in pairs(fs.list(dir)) do for _, file in pairs(fs.list(dir)) do
if not fs.isDir(fs.combine(dir, file)) then if not fs.isDir(fs.combine(dir, file)) then
loadPlugin(fs.combine(dir, file)) loadPlugin(fs.combine(dir, file))
end end
end end
end end
local programDir = fs.getDir(shell.getRunningProgram()) local programDir = fs.getDir(shell.getRunningProgram())
@@ -113,17 +113,17 @@ loadDirectory(fs.combine(programDir, 'plugins'))
loadDirectory(fs.combine(programDir, 'plugins/item')) loadDirectory(fs.combine(programDir, 'plugins/item'))
for k in pairs(Milo:getState('plugins') or { }) do for k in pairs(Milo:getState('plugins') or { }) do
loadPlugin(k) loadPlugin(k)
end end
table.sort(context.tasks, function(a, b) table.sort(context.tasks, function(a, b)
return a.priority < b.priority return a.priority < b.priority
end) end)
_G._syslog('Tasks\n-----') _G._syslog('Tasks\n-----')
for _, task in ipairs(context.tasks) do for _, task in ipairs(context.tasks) do
task.execTime = 0 task.execTime = 0
_G._syslog('%d: %s', task.priority, task.name) _G._syslog('%d: %s', task.priority, task.name)
end end
Milo:clearGrid() Milo:clearGrid()
@@ -132,92 +132,92 @@ UI:setPage(UI:getPage('listing'))
Sound.play('ui.toast.challenge_complete') Sound.play('ui.toast.challenge_complete')
Event.on({ 'milo_cycle', 'milo_queue' }, function(e) Event.on({ 'milo_cycle', 'milo_queue' }, function(e)
if context.storage:isOnline() then if context.storage:isOnline() then
if #context.queue > 0 then if #context.queue > 0 then
local queue = context.queue local queue = context.queue
context.queue = { } context.queue = { }
for _, entry in pairs(queue) do for _, entry in pairs(queue) do
local s, m = pcall(entry.callback, entry.request) local s, m = pcall(entry.callback, entry.request)
if not s and m then if not s and m then
_G._syslog('callback crashed') _G._syslog('callback crashed')
_G._syslog(m) _G._syslog(m)
end end
end end
end end
end end
if e == 'milo_cycle' and not Milo:isCraftingPaused() then if e == 'milo_cycle' and not Milo:isCraftingPaused() then
local taskTimer = Util.timer() local taskTimer = Util.timer()
Milo:resetCraftingStatus() Milo:resetCraftingStatus()
for _, task in ipairs(context.tasks) do for _, task in ipairs(context.tasks) do
local timer = Util.timer() local timer = Util.timer()
local s, m = pcall(function() task:cycle(context) end) local s, m = pcall(function() task:cycle(context) end)
if not s and m then if not s and m then
_G._syslog(task.name .. ' crashed') _G._syslog(task.name .. ' crashed')
_G._syslog(m) _G._syslog(m)
end end
task.execTime = task.execTime + timer() task.execTime = task.execTime + timer()
end end
context.taskTimer = context.taskTimer + taskTimer() context.taskTimer = context.taskTimer + taskTimer()
context.taskCounter = context.taskCounter + 1 context.taskCounter = context.taskCounter + 1
end end
if context.storage:isOnline() and #context.queue > 0 then if context.storage:isOnline() and #context.queue > 0 then
os.queueEvent('milo_cycle') os.queueEvent('milo_cycle')
end end
end) end)
Event.on('turtle_inventory', function() Event.on('turtle_inventory', function()
Milo:queueRequest({ }, function() Milo:queueRequest({ }, function()
if not Milo:isCraftingPaused() then if not Milo:isCraftingPaused() then
Milo:clearGrid() Milo:clearGrid()
end end
end) end)
end) end)
local cycleHandle local cycleHandle
cycleHandle = Event.onInterval(5, function() cycleHandle = Event.onInterval(5, function()
Event.trigger('milo_cycle') Event.trigger('milo_cycle')
if context.taskCounter > 0 then if context.taskCounter > 0 then
--local average = context.taskTimer / context.taskCounter --local average = context.taskTimer / context.taskCounter
--_syslog('Interval: ' .. math.max(5, 2 + average * 3)) --_syslog('Interval: ' .. math.max(5, 2 + average * 3))
--cycleHandle.updateInterval(math.max(5, 2 + average * 3)) --cycleHandle.updateInterval(math.max(5, 2 + average * 3))
end end
end) end)
Event.on({ 'storage_offline', 'storage_online' }, function() Event.on({ 'storage_offline', 'storage_online' }, function()
if context.storage:isOnline() then if context.storage:isOnline() then
Milo:resumeCrafting({ key = 'storageOnline' }) Milo:resumeCrafting({ key = 'storageOnline' })
else else
Milo:pauseCrafting({ key = 'storageOnline', msg = 'Storage offline' }) Milo:pauseCrafting({ key = 'storageOnline', msg = 'Storage offline' })
end end
end) end)
Event.on('terminate', function() Event.on('terminate', function()
for _, node in pairs(context.storage.nodes) do for _, node in pairs(context.storage.nodes) do
if node.category == 'display' and node.adapter and node.adapter.clear then if node.category == 'display' and node.adapter and node.adapter.clear then
node.adapter.setBackgroundColor(colors.black) node.adapter.setBackgroundColor(colors.black)
node.adapter.clear() node.adapter.clear()
end end
end end
end) end)
os.queueEvent( os.queueEvent(
context.storage:isOnline() and 'storage_online' or 'storage_offline', context.storage:isOnline() and 'storage_online' or 'storage_offline',
context.storage:isOnline()) context.storage:isOnline())
local oldDebug = _G._syslog local oldDebug = _G._syslog
_G._syslog = function(...) _G._syslog = function(...)
for _,v in pairs(context.loggers) do for _,v in pairs(context.loggers) do
v(...) v(...)
end end
oldDebug(...) oldDebug(...)
end end
local s, m = pcall(function() local s, m = pcall(function()
UI:pullEvents() UI:pullEvents()
end) end)
_G._syslog = oldDebug _G._syslog = oldDebug

View File

@@ -13,512 +13,512 @@ local peripheral = _G.peripheral
local shell = _ENV.shell local shell = _ENV.shell
local context = { local context = {
state = Config.load('miloRemote', { displayMode = 0, deposit = true }), state = Config.load('miloRemote', { displayMode = 0, deposit = true }),
responseHandlers = { }, responseHandlers = { },
} }
local depositMode = { local depositMode = {
[ true ] = { text = '\25', textColor = colors.black, help = 'Deposit enabled' }, [ true ] = { text = '\25', textColor = colors.black, help = 'Deposit enabled' },
[ false ] = { text = '\215', textColor = colors.red, help = 'Deposit disabled' }, [ false ] = { text = '\215', textColor = colors.red, help = 'Deposit disabled' },
} }
local displayModes = { local displayModes = {
[0] = { text = 'A', help = 'Showing all items' }, [0] = { text = 'A', help = 'Showing all items' },
[1] = { text = 'I', help = 'Showing inventory items' }, [1] = { text = 'I', help = 'Showing inventory items' },
} }
local page = UI.Page { local page = UI.Page {
menuBar = UI.MenuBar { menuBar = UI.MenuBar {
buttons = { buttons = {
{ {
text = 'Refresh', text = 'Refresh',
x = -12, x = -12,
event = 'refresh' event = 'refresh'
}, },
{ {
name = 'config', name = 'config',
text = '\187', text = '\187',
x = -3, x = -3,
}, },
}, },
infoBar = UI.StatusBar { infoBar = UI.StatusBar {
x = 1, ex = -16, x = 1, ex = -16,
backgroundColor = colors.lightGray, backgroundColor = colors.lightGray,
}, },
}, },
grid = UI.Grid { grid = UI.Grid {
y = 2, ey = -2, y = 2, ey = -2,
columns = { columns = {
{ heading = ' Qty', key = 'count' , width = 4, align = 'right' }, { heading = ' Qty', key = 'count' , width = 4, align = 'right' },
{ heading = 'Name', key = 'displayName' }, { heading = 'Name', key = 'displayName' },
}, },
values = { }, values = { },
sortColumn = context.state.sortColumn or 'count', sortColumn = context.state.sortColumn or 'count',
inverseSort = context.state.inverseSort, inverseSort = context.state.inverseSort,
help = '^(s)tack, ^(a)ll' help = '^(s)tack, ^(a)ll'
}, },
statusBar = UI.Window { statusBar = UI.Window {
y = -1, y = -1,
filter = UI.TextEntry { filter = UI.TextEntry {
x = 1, ex = -12, x = 1, ex = -12,
limit = 50, limit = 50,
shadowText = 'filter', shadowText = 'filter',
backgroundColor = colors.cyan, backgroundColor = colors.cyan,
backgroundFocusColor = colors.cyan, backgroundFocusColor = colors.cyan,
accelerators = { accelerators = {
[ 'enter' ] = 'eject', [ 'enter' ] = 'eject',
[ 'up' ] = 'grid_up', [ 'up' ] = 'grid_up',
[ 'down' ] = 'grid_down', [ 'down' ] = 'grid_down',
[ 'control-a' ] = 'eject_all', [ 'control-a' ] = 'eject_all',
}, },
}, },
amount = UI.TextEntry { amount = UI.TextEntry {
x = -11, ex = -7, x = -11, ex = -7,
limit = 3, limit = 3,
shadowText = '1', shadowText = '1',
shadowTextColor = colors.gray, shadowTextColor = colors.gray,
backgroundColor = colors.black, backgroundColor = colors.black,
backgroundFocusColor = colors.black, backgroundFocusColor = colors.black,
accelerators = { accelerators = {
[ 'enter' ] = 'eject_specified', [ 'enter' ] = 'eject_specified',
[ 'control-a' ] = 'eject_all', [ 'control-a' ] = 'eject_all',
}, },
help = 'Request amount', help = 'Request amount',
}, },
depositToggle = UI.Button { depositToggle = UI.Button {
x = -6, x = -6,
event = 'toggle_deposit', event = 'toggle_deposit',
text = '\215', text = '\215',
}, },
display = UI.Button { display = UI.Button {
x = -3, x = -3,
event = 'toggle_display', event = 'toggle_display',
text = displayModes[context.state.displayMode].text, text = displayModes[context.state.displayMode].text,
help = displayModes[context.state.displayMode].help, help = displayModes[context.state.displayMode].help,
}, },
}, },
notification = UI.Notification { notification = UI.Notification {
anchor = 'top', anchor = 'top',
}, },
accelerators = { accelerators = {
r = 'refresh', r = 'refresh',
[ 'control-r' ] = 'refresh', [ 'control-r' ] = 'refresh',
[ 'control-e' ] = 'eject', [ 'control-e' ] = 'eject',
[ 'control-s' ] = 'eject_stack', [ 'control-s' ] = 'eject_stack',
[ 'control-a' ] = 'eject_all', [ 'control-a' ] = 'eject_all',
q = 'quit', q = 'quit',
}, },
items = { }, items = { },
} }
local function getPlayerName() local function getPlayerName()
local neural = peripheral.find('neuralInterface') local neural = peripheral.find('neuralInterface')
if neural and neural.getName then if neural and neural.getName then
return neural.getName() return neural.getName()
end end
end end
function page.grid:getRowTextColor(row, selected) function page.grid:getRowTextColor(row, selected)
if row.is_craftable then if row.is_craftable then
return colors.yellow return colors.yellow
end end
if row.has_recipe then if row.has_recipe then
return colors.cyan return colors.cyan
end end
return UI.Grid:getRowTextColor(row, selected) return UI.Grid:getRowTextColor(row, selected)
end end
function page.grid:getDisplayValues(row) function page.grid:getDisplayValues(row)
row = Util.shallowCopy(row) row = Util.shallowCopy(row)
row.count = row.count > 0 and Util.toBytes(row.count) or '' row.count = row.count > 0 and Util.toBytes(row.count) or ''
return row return row
end end
function page.grid:sortCompare(a, b) function page.grid:sortCompare(a, b)
if self.sortColumn ~= 'displayName' then if self.sortColumn ~= 'displayName' then
if a[self.sortColumn] == b[self.sortColumn] then if a[self.sortColumn] == b[self.sortColumn] then
if self.inverseSort then if self.inverseSort then
return a.displayName > b.displayName return a.displayName > b.displayName
end end
return a.displayName < b.displayName return a.displayName < b.displayName
end end
if a[self.sortColumn] == 0 then if a[self.sortColumn] == 0 then
return self.inverseSort return self.inverseSort
end end
if b[self.sortColumn] == 0 then if b[self.sortColumn] == 0 then
return not self.inverseSort return not self.inverseSort
end end
return a[self.sortColumn] < b[self.sortColumn] return a[self.sortColumn] < b[self.sortColumn]
end end
return UI.Grid.sortCompare(self, a, b) return UI.Grid.sortCompare(self, a, b)
end end
function page.grid:eventHandler(event) function page.grid:eventHandler(event)
if event.type == 'grid_sort' then if event.type == 'grid_sort' then
context.state.sortColumn = event.sortColumn context.state.sortColumn = event.sortColumn
context.state.inverseSort = event.inverseSort context.state.inverseSort = event.inverseSort
Config.update('miloRemote', context.state) Config.update('miloRemote', context.state)
end end
return UI.Grid.eventHandler(self, event) return UI.Grid.eventHandler(self, event)
end end
function page:transfer(item, count, msg) 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 end
function page:eventHandler(event) function page:eventHandler(event)
if event.type == 'quit' then if event.type == 'quit' then
UI:exitPullEvents() UI:exitPullEvents()
elseif event.type == 'setup' then elseif event.type == 'setup' then
self.setup.form:setValues(context.state) self.setup.form:setValues(context.state)
self.setup:show() self.setup:show()
elseif event.type == 'toggle_deposit' then elseif event.type == 'toggle_deposit' then
context.state.deposit = not context.state.deposit context.state.deposit = not context.state.deposit
Util.merge(self.statusBar.depositToggle, depositMode[context.state.deposit]) Util.merge(self.statusBar.depositToggle, depositMode[context.state.deposit])
self.statusBar:draw() self.statusBar:draw()
context:setStatus(depositMode[context.state.deposit].help) context:setStatus(depositMode[context.state.deposit].help)
context:notifyInfo(depositMode[context.state.deposit].help) context:notifyInfo(depositMode[context.state.deposit].help)
Config.update('miloRemote', context.state) Config.update('miloRemote', context.state)
elseif event.type == 'focus_change' then elseif event.type == 'focus_change' then
context:setStatus(event.focused.help) context:setStatus(event.focused.help)
elseif event.type == 'eject' or event.type == 'grid_select' then elseif event.type == 'eject' or event.type == 'grid_select' then
local item = self.grid:getSelected() local item = self.grid:getSelected()
if item then if item then
self:transfer(item, 1, 'requesting 1 ...') self:transfer(item, 1, 'requesting 1 ...')
end end
elseif event.type == 'eject_stack' then elseif event.type == 'eject_stack' then
local item = self.grid:getSelected() local item = self.grid:getSelected()
if item then if item then
self:transfer(item, 'stack', 'requesting stack ...') self:transfer(item, 'stack', 'requesting stack ...')
end end
elseif event.type == 'eject_all' then elseif event.type == 'eject_all' then
local item = self.grid:getSelected() local item = self.grid:getSelected()
if item then if item then
self:transfer(item, 'all', 'requesting all ...') self:transfer(item, 'all', 'requesting all ...')
end end
elseif event.type == 'eject_specified' then elseif event.type == 'eject_specified' then
local item = self.grid:getSelected() local item = self.grid:getSelected()
local count = tonumber(self.statusBar.amount.value) local count = tonumber(self.statusBar.amount.value)
if item and count then if item and count then
self.statusBar.amount:reset() self.statusBar.amount:reset()
self:setFocus(self.statusBar.filter) self:setFocus(self.statusBar.filter)
self:transfer(item, count, 'requesting ' .. count .. ' ...') self:transfer(item, count, 'requesting ' .. count .. ' ...')
else else
Sound.play('entity.villager.no') Sound.play('entity.villager.no')
context:notifyError('nope ...') context:notifyError('nope ...')
end end
elseif event.type == 'plugin' then elseif event.type == 'plugin' then
event.button.callback(context) event.button.callback(context)
elseif event.type == 'rescan' then elseif event.type == 'rescan' then
self:setFocus(self.statusBar.filter) self:setFocus(self.statusBar.filter)
self:refresh('scan') self:refresh('scan')
self.grid:draw() self.grid:draw()
elseif event.type == 'grid_up' then elseif event.type == 'grid_up' then
self.grid:emit({ type = 'scroll_up' }) self.grid:emit({ type = 'scroll_up' })
elseif event.type == 'grid_down' then elseif event.type == 'grid_down' then
self.grid:emit({ type = 'scroll_down' }) self.grid:emit({ type = 'scroll_down' })
elseif event.type == 'refresh' then elseif event.type == 'refresh' then
self:setFocus(self.statusBar.filter) self:setFocus(self.statusBar.filter)
self:refresh('list') self:refresh('list')
self.grid:draw() self.grid:draw()
elseif event.type == 'toggle_display' then elseif event.type == 'toggle_display' then
context.state.displayMode = (context.state.displayMode + 1) % 2 context.state.displayMode = (context.state.displayMode + 1) % 2
Util.merge(event.button, displayModes[context.state.displayMode]) Util.merge(event.button, displayModes[context.state.displayMode])
event.button:draw() event.button:draw()
self:applyFilter() self:applyFilter()
context:setStatus(event.button.help) context:setStatus(event.button.help)
context:notifyInfo(event.button.help) context:notifyInfo(event.button.help)
self.grid:draw() self.grid:draw()
Config.update('miloRemote', context.state) Config.update('miloRemote', context.state)
elseif event.type == 'text_change' and event.element == self.statusBar.filter then elseif event.type == 'text_change' and event.element == self.statusBar.filter then
self.filter = event.text self.filter = event.text
if #self.filter == 0 then if #self.filter == 0 then
self.filter = nil self.filter = nil
end end
self:applyFilter() self:applyFilter()
self.grid:setIndex(1) self.grid:setIndex(1)
self.grid:draw() self.grid:draw()
else else
UI.Page.eventHandler(self, event) UI.Page.eventHandler(self, event)
end end
return true return true
end end
function page:enable() function page:enable()
self:setFocus(self.statusBar.filter) self:setFocus(self.statusBar.filter)
Util.merge(self.statusBar.depositToggle, depositMode[context.state.deposit]) Util.merge(self.statusBar.depositToggle, depositMode[context.state.deposit])
UI.Page.enable(self) UI.Page.enable(self)
if not context.state.server then if not context.state.server then
self.setup.form:setValues(context.state) self.setup.form:setValues(context.state)
self.setup:show() self.setup:show()
end end
Event.onTimeout(.1, function() Event.onTimeout(.1, function()
self:refresh('list') self:refresh('list')
self.grid:draw() self.grid:draw()
self:sync() self:sync()
end) end)
end end
local function splitKey(key) local function splitKey(key)
local t = Util.split(key, '(.-):') local t = Util.split(key, '(.-):')
local item = { } local item = { }
if #t[#t] > 8 then if #t[#t] > 8 then
item.nbtHash = table.remove(t) item.nbtHash = table.remove(t)
end end
item.damage = tonumber(table.remove(t)) item.damage = tonumber(table.remove(t))
item.name = table.concat(t, ':') item.name = table.concat(t, ':')
return item return item
end end
function page:expandList(list) function page:expandList(list)
local t = { } local t = { }
for k,v in pairs(list) do for k,v in pairs(list) do
local item = splitKey(k) local item = splitKey(k)
item.has_recipe, item.count, item.displayName = v:match('(%d+):(%d+):(.+)') item.has_recipe, item.count, item.displayName = v:match('(%d+):(%d+):(.+)')
item.count = tonumber(item.count) or 0 item.count = tonumber(item.count) or 0
item.lname = item.displayName:lower() item.lname = item.displayName:lower()
item.has_recipe = item.has_recipe == '1' item.has_recipe = item.has_recipe == '1'
t[k] = item t[k] = item
end end
return t return t
end end
function page:refresh(requestType) function page:refresh(requestType)
context:sendRequest({ request = requestType }, 'refreshing...') context:sendRequest({ request = requestType }, 'refreshing...')
end end
function page:applyFilter() function page:applyFilter()
local function filterItems(t, filter, displayMode) local function filterItems(t, filter, displayMode)
self.grid.sortColumn = context.state.sortColumn or 'count' self.grid.sortColumn = context.state.sortColumn or 'count'
self.grid.inverseSort = context.state.inverseSort self.grid.inverseSort = context.state.inverseSort
if filter then if filter then
local r = { } local r = { }
filter = filter:lower() filter = filter:lower()
self.grid.sortColumn = 'score' self.grid.sortColumn = 'score'
self.grid.inverseSort = true self.grid.inverseSort = true
for _,v in pairs(t) do for _,v in pairs(t) do
v.score = fuzzy(v.lname, filter) v.score = fuzzy(v.lname, filter)
if v.score then if v.score then
if v.count > 0 then if v.count > 0 then
v.score = v.score + 1 v.score = v.score + 1
end end
table.insert(r, v) table.insert(r, v)
end end
end end
return r return r
elseif displayMode > 0 then elseif displayMode > 0 then
local r = { } local r = { }
for _,v in pairs(t) do for _,v in pairs(t) do
if v.count > 0 then if v.count > 0 then
table.insert(r, v) table.insert(r, v)
end end
end end
return r return r
end end
return t return t
end end
local t = filterItems(self.items, self.filter, context.state.displayMode) local t = filterItems(self.items, self.filter, context.state.displayMode)
self.grid:setValues(t) self.grid:setValues(t)
end end
context.page = page context.page = page
function context:setStatus(status) function context:setStatus(status)
page.menuBar.infoBar.values = status page.menuBar.infoBar.values = status
page.menuBar.infoBar:draw() page.menuBar.infoBar:draw()
page:sync() page:sync()
end end
function context:notifySuccess(status) function context:notifySuccess(status)
page.notification:success(status) page.notification:success(status)
page:sync() page:sync()
end end
function context:notifyInfo(status) function context:notifyInfo(status)
page.notification:info(status) page.notification:info(status)
page:sync() page:sync()
end end
function context:notifyError(status) function context:notifyError(status)
page.notification:error(status) page.notification:error(status)
page:sync() page:sync()
end end
local function processMessages(s) local function processMessages(s)
Event.addRoutine(function() Event.addRoutine(function()
s.co = coroutine.running() s.co = coroutine.running()
repeat repeat
local response = s:read() local response = s:read()
if not response then if not response then
break break
end end
local h = context.responseHandlers[response.type] local h = context.responseHandlers[response.type]
if h then if h then
h(response) h(response)
end end
if response.msg then if response.msg then
context:notifyInfo(response.msg) context:notifyInfo(response.msg)
end end
until not s.connected until not s.connected
s:close() s:close()
s = nil s = nil
context:notifyError('disconnected ...') context:notifyError('disconnected ...')
Sound.play('entity.villager.no') Sound.play('entity.villager.no')
end) end)
end end
function context:sendRequest(data, statusMsg) function context:sendRequest(data, statusMsg)
if not context.state.server then if not context.state.server then
self:notifyError('Invalid configuration') self:notifyError('Invalid configuration')
return return
end end
local player = getPlayerName() local player = getPlayerName()
if not player then if not player then
self:notifyError('Missing neural or introspection') self:notifyError('Missing neural or introspection')
return return
end end
local success local success
sync(page, function() sync(page, function()
local msg local msg
for _ = 1, 2 do for _ = 1, 2 do
if not context.socket or not context.socket.connected then if not context.socket or not context.socket.connected then
self:notifyInfo('connecting ...') self:notifyInfo('connecting ...')
context.socket, msg = Socket.connect(context.state.server, 4242) context.socket, msg = Socket.connect(context.state.server, 4242)
if context.socket then if context.socket then
context.socket:write(player) context.socket:write(player)
local r = context.socket:read(2) local r = context.socket:read(2)
if r and not r.msg then if r and not r.msg then
self:notifySuccess('connected ...') self:notifySuccess('connected ...')
processMessages(context.socket) processMessages(context.socket)
else else
msg = r and r.msg or 'Timed out' msg = r and r.msg or 'Timed out'
context.socket:close() context.socket:close()
context.socket = nil context.socket = nil
end end
end end
end end
if context.socket then if context.socket then
if statusMsg then if statusMsg then
self:notifyInfo(statusMsg) self:notifyInfo(statusMsg)
end end
if context.socket:write(data) then if context.socket:write(data) then
success = true success = true
return return
end end
context.socket:close() context.socket:close()
context.socket = nil context.socket = nil
end end
end end
self:notifyError(msg or 'Failed to connect') self:notifyError(msg or 'Failed to connect')
end) end)
return success return success
end end
function context:getState(key) function context:getState(key)
return self.state[key] return self.state[key]
end end
function context:setState(key, value) function context:setState(key, value)
self.state[key] = value self.state[key] = value
Config.update('miloRemote', self.state) Config.update('miloRemote', self.state)
end end
context.responseHandlers['received'] = function(response) context.responseHandlers['received'] = function(response)
Sound.play('entity.item.pickup') Sound.play('entity.item.pickup')
local ritem = page.items[response.key] local ritem = page.items[response.key]
if ritem then if ritem then
ritem.count = response.count ritem.count = response.count
if page.enabled then if page.enabled then
page.grid:draw() page.grid:draw()
page:sync() page:sync()
end end
end end
end end
context.responseHandlers['list'] = function(response) context.responseHandlers['list'] = function(response)
page.items = page:expandList(response.list) page.items = page:expandList(response.list)
page:applyFilter() page:applyFilter()
if page.enabled then if page.enabled then
page.grid:draw() page.grid:draw()
page.grid:sync() page.grid:sync()
end end
end end
context.responseHandlers['transfer'] = function(response) context.responseHandlers['transfer'] = function(response)
if response.count > 0 then if response.count > 0 then
Sound.play('entity.item.pickup') Sound.play('entity.item.pickup')
local item = page.items[response.key] local item = page.items[response.key]
if item then if item then
item.count = response.current item.count = response.current
if page.enabled then if page.enabled then
page.grid:draw() page.grid:draw()
page:sync() page:sync()
end end
end end
end end
if response.craft then if response.craft then
if response.craft > 0 then if response.craft > 0 then
context:notifyInfo(response.craft .. ' crafting ...') context:notifyInfo(response.craft .. ' crafting ...')
elseif response.craft + response.count < response.requested then elseif response.craft + response.count < response.requested then
if response.craft + response.count == 0 then if response.craft + response.count == 0 then
Sound.play('entity.villager.no') Sound.play('entity.villager.no')
end end
context:notifyInfo((response.craft + response.count) .. ' available ...') context:notifyInfo((response.craft + response.count) .. ' available ...')
end end
end end
end end
local function loadDirectory(dir) local function loadDirectory(dir)
local dropdown = { local dropdown = {
{ text = 'Setup', event = 'setup' }, { text = 'Setup', event = 'setup' },
{ spacer = true }, { spacer = true },
{ {
text = 'Rescan storage', text = 'Rescan storage',
event = 'rescan', event = 'rescan',
help = 'Rescan all inventories' help = 'Rescan all inventories'
}, },
} }
for _, file in pairs(fs.list(dir)) do for _, file in pairs(fs.list(dir)) do
local s, m = Util.run(_ENV, fs.combine(dir, file), context) local s, m = Util.run(_ENV, fs.combine(dir, file), context)
if not s and m then if not s and m then
_G.printError('Error loading: ' .. file) _G.printError('Error loading: ' .. file)
error(m or 'Unknown error') error(m or 'Unknown error')
elseif s and m then elseif s and m then
table.insert(dropdown, { table.insert(dropdown, {
text = m.menuItem, text = m.menuItem,
event = 'plugin', event = 'plugin',
callback = m.callback, callback = m.callback,
}) })
end end
end end
page.menuBar.config:add({ dropmenu = UI.DropMenu { buttons = dropdown } }) page.menuBar.config:add({ dropmenu = UI.DropMenu { buttons = dropdown } })
end end
local programDir = fs.getDir(shell.getRunningProgram()) local programDir = fs.getDir(shell.getRunningProgram())
@@ -528,5 +528,5 @@ UI:setPage(page)
UI:pullEvents() UI:pullEvents()
if context.socket then if context.socket then
context.socket:close() context.socket:close()
end end

View File

@@ -35,6 +35,39 @@ local function makeRecipeKey(item)
return table.concat({ item.name, item.damage or 0, item.nbtHash }, ':') return table.concat({ item.name, item.damage or 0, item.nbtHash }, ':')
end 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) function Craft.clearGrid(storage)
local success = true local success = true
local tasks = Tasks() local tasks = Tasks()
@@ -74,8 +107,9 @@ end
function Craft.sumIngredients(recipe) function Craft.sumIngredients(recipe)
-- produces { ['minecraft:planks:0'] = 8 } -- produces { ['minecraft:planks:0'] = 8 }
local t = { } local t = { }
for _,item in pairs(recipe.ingredients) do for _,entry in pairs(recipe.ingredients) do
t[item] = (t[item] or 0) + 1 local item = convert(entry)
t[item.key] = (t[item.key] or 0) + item.count
end end
return t return t
end end
@@ -108,12 +142,13 @@ local function machineCraft(recipe, storage, machineName, request, count, item)
if count > 0 then if count > 0 then
local xferred = { } local xferred = { }
for k,v in pairs(recipe.ingredients) do 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] = { xferred[k] = {
key = v, key = entry.key,
count = provided, count = provided,
} }
if provided ~= count then if provided ~= count * entry.count then
-- take back out whatever we put in -- take back out whatever we put in
for k2,v2 in pairs(xferred) do for k2,v2 in pairs(xferred) do
if v2.count > 0 then if v2.count > 0 then
@@ -143,6 +178,9 @@ local function turtleCraft(recipe, storage, request, count)
for k,v in pairs(recipe.ingredients) do for k,v in pairs(recipe.ingredients) do
local item = splitKey(v) local item = splitKey(v)
if recipe.craftingTools and recipe.craftingTools[v] then
item = getCraftingTool(storage, item)
end
tasks:add(function() tasks:add(function()
if storage:export(storage.turtleInventory, k, count, item) ~= count then if storage:export(storage.turtleInventory, k, count, item) ~= count then
request.status = 'rescan needed ?' request.status = 'rescan needed ?'
@@ -185,7 +223,8 @@ local function turtleCraft(recipe, storage, request, count)
end end
function Craft.processPending(item, storage) 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] local imported = storage.activity[key]
if imported then if imported then
local amount = math.min(imported, count) local amount = math.min(imported, count)
@@ -238,7 +277,6 @@ end
function Craft.craftRecipeInternal(recipe, count, storage, origItem, path) function Craft.craftRecipeInternal(recipe, count, storage, origItem, path)
local request = origItem.ingredients[recipe.result] local request = origItem.ingredients[recipe.result]
--[[ --[[
if origItem.pending[recipe.result] then if origItem.pending[recipe.result] then
request.status = 'processing' request.status = 'processing'
@@ -425,23 +463,24 @@ function Craft.getCraftableAmount(inRecipe, inCount, items, missing)
local canCraft = 0 local canCraft = 0
for _ = 1, count do for _ = 1, count do
for _,item in pairs(recipe.ingredients) do for _,entry in pairs(recipe.ingredients) do
local summedItem = summedItems[item] or Craft.getItemCount(items, item) 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 if irecipe and summedItem <= 0 then
local p = Util.shallowCopy(path) local p = Util.shallowCopy(path)
p[irecipe.result] = true p[irecipe.result] = true
summedItem = summedItem + sumItems(irecipe, summedItems, 1, p) summedItem = summedItem + sumItems(irecipe, summedItems, item.count, p)
end end
if summedItem <= 0 then if summedItem <= 0 then
if missing and not irecipe then if missing and not irecipe then
missing.name = item missing.name = item.key
end end
return canCraft return canCraft
end end
if not recipe.craftingTools or not recipe.craftingTools[item] then if not recipe.craftingTools or not recipe.craftingTools[item.key] then
summedItems[item] = summedItem - 1 summedItems[item.key] = summedItem - item.count
end end
end end
canCraft = canCraft + recipe.count canCraft = canCraft + recipe.count

View File

@@ -11,9 +11,9 @@ local _find = string.find
local _max = math.max local _max = math.max
return function(str, pattern) return function(str, pattern)
local start = _find(str, pattern, 1, true) local start = _find(str, pattern, 1, true)
if start then if start then
-- All letters before the current one are considered leading, so add them to our penalty -- 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) return SCORE_WEIGHT + _max(LEADING_LETTER_PENALTY * (start - 1), LEADING_LETTER_PENALTY_MAX)
end end
end end

View File

@@ -224,8 +224,8 @@ function Milo:eject(item, count)
total = total + amount total = total + amount
count = count - amount count = count - amount
--Sound.play('ui.button.click') Sound.play('ui.button.click')
Sound.play('entity.illusion_illager.death', .3) --Sound.play('entity.illusion_illager.death', .3)
turtle.emptyInventory() turtle.emptyInventory()
end end
return total return total
@@ -273,6 +273,7 @@ function Milo:learnRecipe()
local tool = Util.shallowCopy(v2) local tool = Util.shallowCopy(v2)
if tool.maxDamage > 0 then if tool.maxDamage > 0 then
tool.damage = '*' tool.damage = '*'
v2.damage = '*'
end end
--[[ --[[

View File

@@ -7,46 +7,46 @@ local os = _G.os
local Adapter = class(Mini) local Adapter = class(Mini)
function Adapter:init(args) function Adapter:init(args)
Mini.init(self, args) Mini.init(self, args)
self._rawList = self.list self._rawList = self.list
function self.list() function self.list()
-- wait for up to 1 sec until any items that have been inserted -- wait for up to 1 sec until any items that have been inserted
-- into interface are added to the system -- into interface are added to the system
for _ = 0, 20 do for _ = 0, 20 do
if #self._rawList() == 0 then if #self._rawList() == 0 then
break break
end end
os.sleep(0) os.sleep(0)
end end
local list = { } local list = { }
for _, v in pairs(self.listAvailableItems()) do for _, v in pairs(self.listAvailableItems()) do
list[itemDB:makeKey(v)] = v list[itemDB:makeKey(v)] = v
end end
return list return list
end end
function self.getItemMeta(key) function self.getItemMeta(key)
local item = self.findItem(itemDB:splitKey(key)) local item = self.findItem(itemDB:splitKey(key))
if item and item.getMetadata then if item and item.getMetadata then
return item.getMetadata() return item.getMetadata()
end end
end end
function self.pushItems(target, key, amount, slot) function self.pushItems(target, key, amount, slot)
local item = self.findItem(itemDB:splitKey(key)) local item = self.findItem(itemDB:splitKey(key))
if item and item.export then if item and item.export then
return item.export(target, amount, slot) return item.export(target, amount, slot)
end end
return 0 return 0
end end
function self.pullItems(target, key, amount, slot) function self.pullItems(target, key, amount, slot)
_G._syslog({target, key, amount, slot }) _G._syslog({target, key, amount, slot })
return 0 return 0
end end
end end

View File

@@ -7,37 +7,37 @@ local device = _G.device
local Adapter = class() local Adapter = class()
function Adapter:init(args) function Adapter:init(args)
if args.side then if args.side then
local inventory = device[args.side] local inventory = device[args.side]
if inventory then if inventory then
Util.merge(self, inventory) Util.merge(self, inventory)
end end
end end
end end
function Adapter:listItems(throttle) function Adapter:listItems(throttle)
local cache = { } local cache = { }
throttle = throttle or Util.throttle() throttle = throttle or Util.throttle()
for k,v in pairs(self.list()) do for k,v in pairs(self.list()) do
if v.count > 0 then if v.count > 0 then
local key = table.concat({ v.name, v.damage, v.nbtHash }, ':') local key = table.concat({ v.name, v.damage, v.nbtHash }, ':')
local entry = cache[key] local entry = cache[key]
if entry then if entry then
entry.count = entry.count + v.count entry.count = entry.count + v.count
else else
cache[key] = itemDB:get(v, function() return self.getItemMeta(k) end) cache[key] = itemDB:get(v, function() return self.getItemMeta(k) end)
end end
throttle() throttle()
end end
end end
-- TODO: cache number of slots, free slots, used slots -- TODO: cache number of slots, free slots, used slots
-- useful for when inserting into chests -- useful for when inserting into chests
-- ie. insert only if chest does not have item and has free slots -- ie. insert only if chest does not have item and has free slots
self.cache = cache self.cache = cache
end end
return Adapter 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 function createTask(fn)
local task = table.remove(free) local task = table.remove(free)
if not task then if not task then
task = { task = {
fn = fn, fn = fn,
co = coroutine.create(function() co = coroutine.create(function()
local args = { } local args = { }
while true do while true do
pcall(task.fn, table.unpack(args)) pcall(task.fn, table.unpack(args))
task.dead = true task.dead = true
table.insert(free, task) table.insert(free, task)
args = { coroutine.yield() } args = { coroutine.yield() }
end end
end) end)
} }
else else
task.dead = nil task.dead = nil
task.fn = fn task.fn = fn
end end
return task return task
end end
function TaskRunner:init(args) function TaskRunner:init(args)
self.tasks = { } self.tasks = { }
self.errorMsg = 'Task failed: ' self.errorMsg = 'Task failed: '
for k,v in pairs(args or { }) do for k,v in pairs(args or { }) do
self[k] = v self[k] = v
end end
end end
function TaskRunner:add(fn) function TaskRunner:add(fn)
table.insert(self.tasks, createTask(fn)) table.insert(self.tasks, createTask(fn))
end end
function TaskRunner:run() function TaskRunner:run()
if #self.tasks > 0 then if #self.tasks > 0 then
local event = { } local event = { }
while true do while true do
for n = #self.tasks, 1, -1 do for n = #self.tasks, 1, -1 do
local task = self.tasks[n] local task = self.tasks[n]
if task.filter == nil or task.filter == event[1] or event[1] == "terminate" then if task.filter == nil or task.filter == event[1] or event[1] == "terminate" then
local ok, param = coroutine.resume(task.co, table.unpack(event)) local ok, param = coroutine.resume(task.co, table.unpack(event))
if not ok then if not ok then
self:onError(param) self:onError(param)
else else
task.filter = param task.filter = param
end end
if task.dead then if task.dead then
table.remove(self.tasks, n) table.remove(self.tasks, n)
end end
end end
end end
if #self.tasks == 0 then if #self.tasks == 0 then
break break
end end
event = { os.pullEventRaw() } event = { os.pullEventRaw() }
end end
end end
end end
function TaskRunner:onError(msg) function TaskRunner:onError(msg)
_G._syslog(msg.errorMsg .. msg) _G._syslog(msg.errorMsg .. msg)
end end
return TaskRunner return TaskRunner

View File

@@ -8,14 +8,14 @@ reboot
Use multiple brewing stands at once to brew potions. Use multiple brewing stands at once to brew potions.
SETUP: SETUP:
Place an introspection module into the turtles inventory. Place an introspection module into the turtles inventory.
Connect turtle to milo network with a wired modem. Connect turtle to milo network with a wired modem.
Connect turtle to a second wired modem that is connected to brewing stands ONLY. Connect turtle to a second wired modem that is connected to brewing stands ONLY.
Add as many brewing stands as needed. Add as many brewing stands as needed.
CONFIGURATION: CONFIGURATION:
Set turtle as a "Generic Inventory" Set turtle as a "Generic Inventory"
export blaze powder to slot 5 export blaze powder to slot 5
import from slots 7-9 import from slots 7-9
Use this turtle for machine crafting. Use this turtle for machine crafting.
--]] --]]
@@ -31,23 +31,23 @@ local turtle = _G.turtle
local STARTUP_FILE = 'usr/autorun/brewArray.lua' local STARTUP_FILE = 'usr/autorun/brewArray.lua'
local function equip(side, item, rawName) local function equip(side, item, rawName)
local equipped = peripheral.getType(side) local equipped = peripheral.getType(side)
if equipped == item then if equipped == item then
return true return true
end end
if not turtle.equip(side, rawName or item) then if not turtle.equip(side, rawName or item) then
if not turtle.selectSlotWithQuantity(0) then if not turtle.selectSlotWithQuantity(0) then
error('No slots available') error('No slots available')
end end
turtle.equip(side) turtle.equip(side)
if not turtle.equip(side, item) then if not turtle.equip(side, item) then
error('Unable to equip ' .. item) error('Unable to equip ' .. item)
end end
end end
turtle.select(1) turtle.select(1)
end end
equip('left', 'plethora:introspection', 'plethora:module:0') equip('left', 'plethora:introspection', 'plethora:module:0')
@@ -55,8 +55,8 @@ local intro = device['plethora:introspection']
local inv = intro.getInventory() local inv = intro.getInventory()
if not fs.exists(STARTUP_FILE) then if not fs.exists(STARTUP_FILE) then
Util.writeFile(STARTUP_FILE, Util.writeFile(STARTUP_FILE,
[[os.sleep(1) [[os.sleep(1)
shell.openForegroundTab('packages/milo/apps/brewArray.lua')]]) shell.openForegroundTab('packages/milo/apps/brewArray.lua')]])
end end
@@ -65,27 +65,27 @@ local localName
print('detecting wired modem connected to brewing stands...') print('detecting wired modem connected to brewing stands...')
for _, dev in pairs(device) do for _, dev in pairs(device) do
if dev.type == 'wired_modem' then if dev.type == 'wired_modem' then
local list = dev.getNamesRemote() local list = dev.getNamesRemote()
brew = { } brew = { }
localName = dev.getNameLocal() localName = dev.getNameLocal()
for _, name in pairs(list) do for _, name in pairs(list) do
if device[name].type ~= 'minecraft:brewing_stand' then if device[name].type ~= 'minecraft:brewing_stand' then
brew = nil brew = nil
break break
end end
table.insert(brew, device[name]) table.insert(brew, device[name])
end end
end end
if brew then if brew then
print('Using wired modem: ' .. dev.name) print('Using wired modem: ' .. dev.name)
print('Brewing stands: ' .. #brew) print('Brewing stands: ' .. #brew)
break break
end end
end end
if not brew then 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 end
_G.printError([[Program must be restarted if new brewing stands are added.]]) _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 -- slot 5: blaze powder
local function process(list) local function process(list)
local active = false local active = false
for _, brewing in ipairs(Util.shallowCopy(brew)) do for _, brewing in ipairs(Util.shallowCopy(brew)) do
local s, m = pcall(function()-- block updates can cause errors local s, m = pcall(function()-- block updates can cause errors
local bs = brewing.list() local bs = brewing.list()
local cooking = bs[1] and bs[2] and bs[3] and bs[4] local cooking = bs[1] and bs[2] and bs[3] and bs[4]
if cooking then if cooking then
active = true active = true
end end
-- fuel -- fuel
local fuel = bs[5] or { count = 0 } local fuel = bs[5] or { count = 0 }
if fuel.count < 1 then if fuel.count < 1 then
print('fueling ' ..brewing.name) print('fueling ' ..brewing.name)
brewing.pullItems(localName, 5, 1, 5) brewing.pullItems(localName, 5, 1, 5)
end end
if not cooking and (bs[1] or bs[2] or bs[3] or bs[4]) then if not cooking and (bs[1] or bs[2] or bs[3] or bs[4]) then
print('pulling from : ' .. brewing.name) print('pulling from : ' .. brewing.name)
for i = 1, 4 do for i = 1, 4 do
brewing.pushItems(localName, i, 1, 6 + i) brewing.pushItems(localName, i, 1, 6 + i)
end end
end end
if not cooking and list[1] and list[2] and list[3] and list[4] then if not cooking and list[1] and list[2] and list[3] and list[4] then
print('brewing : ' .. brewing.name) print('brewing : ' .. brewing.name)
for i = 1, 4 do for i = 1, 4 do
brewing.pullItems(localName, i, 1, i) brewing.pullItems(localName, i, 1, i)
list[i].count = list[i].count - 1 list[i].count = list[i].count - 1
if list[i].count == 0 then if list[i].count == 0 then
list[i] = nil list[i] = nil
end end
end end
-- push brewing stand to end of list -- push brewing stand to end of list
Util.removeByValue(brew, brewing) Util.removeByValue(brew, brewing)
table.insert(brew, brewing) table.insert(brew, brewing)
end end
end) end)
if not s and m then if not s and m then
_G.printError(m) _G.printError(m)
end end
end end
return active return active
end end
Event.on('turtle_inventory', function() Event.on('turtle_inventory', function()
while true do while true do
if not process(inv.list()) then if not process(inv.list()) then
break break
end end
os.sleep(3) os.sleep(3)
end end
end) end)
Event.onInterval(5, function() Event.onInterval(5, function()
-- for some reason, it keeps stalling ... -- for some reason, it keeps stalling ...
os.queueEvent('turtle_inventory') os.queueEvent('turtle_inventory')
end) end)
os.queueEvent('turtle_inventory') os.queueEvent('turtle_inventory')

View File

@@ -7,22 +7,22 @@ local turtle = _G.turtle
local STARTUP_FILE = 'usr/autorun/cobbleGen.lua' local STARTUP_FILE = 'usr/autorun/cobbleGen.lua'
if not fs.exists(STARTUP_FILE) then if not fs.exists(STARTUP_FILE) then
Util.writeFile(STARTUP_FILE, Util.writeFile(STARTUP_FILE,
[[os.sleep(1) [[os.sleep(1)
shell.openForegroundTab('packages/milo/apps/cobblegen')]]) shell.openForegroundTab('packages/milo/apps/cobblegen')]])
end end
os.queueEvent('turtle_inventory') os.queueEvent('turtle_inventory')
while true do while true do
print('waiting') print('waiting')
os.pullEvent('turtle_inventory') os.pullEvent('turtle_inventory')
print('waiting for cobble') print('waiting for cobble')
for _ = 1, 20 do for _ = 1, 20 do
if turtle.inspectDown() then if turtle.inspectDown() then
break break
end end
os.sleep(.1) os.sleep(.1)
end end
print('digging') print('digging')
turtle.digDown() turtle.digDown()
end end

View File

@@ -13,48 +13,48 @@ local turtle = _G.turtle
local STARTUP_FILE = 'usr/autorun/enderchest.lua' local STARTUP_FILE = 'usr/autorun/enderchest.lua'
local enderChest = device.manipulator and local enderChest = device.manipulator and
device.manipulator.getEnder or device.manipulator.getEnder or
error('Must be connected to a manipulator with a bound introspection module') error('Must be connected to a manipulator with a bound introspection module')
if not fs.exists(STARTUP_FILE) then if not fs.exists(STARTUP_FILE) then
Util.writeFile(STARTUP_FILE, Util.writeFile(STARTUP_FILE,
[[os.sleep(1) [[os.sleep(1)
shell.openForegroundTab('packages/milo/apps/enderchest')]]) shell.openForegroundTab('packages/milo/apps/enderchest')]])
end end
local directions = Util.transpose { local directions = Util.transpose {
'north', 'south', 'east', 'west', 'up', 'down' 'north', 'south', 'east', 'west', 'up', 'down'
} }
Event.on('turtle_inventory', function() Event.on('turtle_inventory', function()
local s, m = pcall(function() local s, m = pcall(function()
local direction local direction
for _, d in pairs(enderChest().getTransferLocations()) do for _, d in pairs(enderChest().getTransferLocations()) do
if directions[d] then if directions[d] then
direction = d direction = d
break break
end end
end end
if not direction then if not direction then
error('Unable to determine transfer direction') error('Unable to determine transfer direction')
end end
turtle.eachFilledSlot(function(s) turtle.eachFilledSlot(function(s)
print('sending') print('sending')
enderChest().pullItems(direction, s.index) enderChest().pullItems(direction, s.index)
end) end)
end) end)
if not s and m then if not s and m then
_G.printError(m) _G.printError(m)
end end
print('idle') print('idle')
end) end)
Event.onInterval(5, function() Event.onInterval(5, function()
-- for some reason, it keeps stalling ... -- for some reason, it keeps stalling ...
os.queueEvent('turtle_inventory') os.queueEvent('turtle_inventory')
end) end)
os.queueEvent('turtle_inventory') os.queueEvent('turtle_inventory')

View File

@@ -2,15 +2,15 @@
Use multiple furnaces at once to smelt items. Use multiple furnaces at once to smelt items.
SETUP: SETUP:
Place an introspection module into the turtles inventory. Place an introspection module into the turtles inventory.
Connect turtle to milo network with a wired modem. Connect turtle to milo network with a wired modem.
Connect turtle to a second wired modem that is connected to furnaces ONLY. Connect turtle to a second wired modem that is connected to furnaces ONLY.
Add as many furnaces as needed. Add as many furnaces as needed.
CONFIGURATION: CONFIGURATION:
Set turtle as a "Generic Inventory" Set turtle as a "Generic Inventory"
export coal to slot 2 export coal to slot 2
import from slot 3 import from slot 3
Use this turtle for machine crafting. Use this turtle for machine crafting.
--]] --]]
@@ -31,23 +31,23 @@ local FUEL_SLOT = 2
local OUTPUT_SLOT = 3 local OUTPUT_SLOT = 3
local function equip(side, item, rawName) local function equip(side, item, rawName)
local equipped = peripheral.getType(side) local equipped = peripheral.getType(side)
if equipped == item then if equipped == item then
return true return true
end end
if not turtle.equip(side, rawName or item) then if not turtle.equip(side, rawName or item) then
if not turtle.selectSlotWithQuantity(0) then if not turtle.selectSlotWithQuantity(0) then
error('No slots available') error('No slots available')
end end
turtle.equip(side) turtle.equip(side)
if not turtle.equip(side, item) then if not turtle.equip(side, item) then
error('Unable to equip ' .. item) error('Unable to equip ' .. item)
end end
end end
turtle.select(1) turtle.select(1)
end end
equip('left', 'plethora:introspection', 'plethora:module:0') equip('left', 'plethora:introspection', 'plethora:module:0')
@@ -55,8 +55,8 @@ local intro = device['plethora:introspection']
local inv = intro.getInventory() local inv = intro.getInventory()
if not fs.exists(STARTUP_FILE) then if not fs.exists(STARTUP_FILE) then
Util.writeFile(STARTUP_FILE, Util.writeFile(STARTUP_FILE,
[[os.sleep(1) [[os.sleep(1)
shell.openForegroundTab('packages/milo/apps/furni')]]) shell.openForegroundTab('packages/milo/apps/furni')]])
end end
@@ -65,121 +65,121 @@ local localName
print('detecting wired modem connected to furnaces...') print('detecting wired modem connected to furnaces...')
for _, dev in pairs(device) do for _, dev in pairs(device) do
if dev.type == 'wired_modem' and dev.getNameLocal then if dev.type == 'wired_modem' and dev.getNameLocal then
local list = dev.getNamesRemote() local list = dev.getNamesRemote()
furnaces = { } furnaces = { }
localName = dev.getNameLocal() localName = dev.getNameLocal()
for _, name in pairs(list) do for _, name in pairs(list) do
if device[name].type ~= 'minecraft:furnace' then if device[name].type ~= 'minecraft:furnace' then
furnaces = nil furnaces = nil
break break
end end
table.insert(furnaces, { table.insert(furnaces, {
dev = device[name], dev = device[name],
list = device[name].list(), list = device[name].list(),
}) })
end end
end end
if furnaces then if furnaces then
print('Using wired modem: ' .. dev.name) print('Using wired modem: ' .. dev.name)
print('Furnaces: ' .. #furnaces) print('Furnaces: ' .. #furnaces)
break break
end end
end end
if not furnaces then 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 end
_G.printError([[Program must be restarted if new furnaces are added.]]) _G.printError([[Program must be restarted if new furnaces are added.]])
local function getSlot(furnace, slotNo) local function getSlot(furnace, slotNo)
if not furnace.list[slotNo] then if not furnace.list[slotNo] then
furnace.list[slotNo] = { furnace.list[slotNo] = {
count = 0 count = 0
} }
end end
return furnace.list[slotNo] return furnace.list[slotNo]
end end
local function process(list) local function process(list)
local inItem = list[INPUT_SLOT] local inItem = list[INPUT_SLOT]
local inFuel = list[FUEL_SLOT] local inFuel = list[FUEL_SLOT]
local inReturn = list[OUTPUT_SLOT] or { count = 0 } local inReturn = list[OUTPUT_SLOT] or { count = 0 }
for _, furnace in ipairs(Util.shallowCopy(furnaces)) do for _, furnace in ipairs(Util.shallowCopy(furnaces)) do
local s, m = pcall(function() local s, m = pcall(function()
if furnace.list[INPUT_SLOT] and furnace.list[INPUT_SLOT].count > 0 then if furnace.list[INPUT_SLOT] and furnace.list[INPUT_SLOT].count > 0 then
furnace.list = furnace.dev.list() furnace.list = furnace.dev.list()
print('listing ' .. furnace.dev.name) print('listing ' .. furnace.dev.name)
end end
-- items to cook -- items to cook
local cooking = getSlot(furnace, INPUT_SLOT) local cooking = getSlot(furnace, INPUT_SLOT)
if cooking.count < 64 and inItem and inItem.count > 0 then if cooking.count < 64 and inItem and inItem.count > 0 then
if cooking.count == 0 or cooking.name == inItem.name then if cooking.count == 0 or cooking.name == inItem.name then
print('cooking : ' .. furnace.dev.name) print('cooking : ' .. furnace.dev.name)
local count = furnace.dev.pullItems(localName, INPUT_SLOT, SMELT_AMOUNT, INPUT_SLOT) local count = furnace.dev.pullItems(localName, INPUT_SLOT, SMELT_AMOUNT, INPUT_SLOT)
if count > 0 then if count > 0 then
inItem.count = inItem.count - count inItem.count = inItem.count - count
cooking.name = inItem.name cooking.name = inItem.name
cooking.count = cooking.count + count cooking.count = cooking.count + count
-- push to end of queue -- push to end of queue
Util.removeByValue(furnaces, furnace) Util.removeByValue(furnaces, furnace)
table.insert(furnaces, furnace) table.insert(furnaces, furnace)
end end
end end
end end
-- fuel -- fuel
local fuel = getSlot(furnace, FUEL_SLOT) local fuel = getSlot(furnace, FUEL_SLOT)
if fuel.count < 8 and inFuel and inFuel.count > 0 then if fuel.count < 8 and inFuel and inFuel.count > 0 then
if fuel.count == 0 or fuel.name == inFuel.name then if fuel.count == 0 or fuel.name == inFuel.name then
print('fueling ' .. furnace.dev.name) print('fueling ' .. furnace.dev.name)
local count = furnace.dev.pullItems(localName, FUEL_SLOT, 8 - fuel.count, FUEL_SLOT) local count = furnace.dev.pullItems(localName, FUEL_SLOT, 8 - fuel.count, FUEL_SLOT)
if count > 0 then if count > 0 then
inFuel.count = inFuel.count - count inFuel.count = inFuel.count - count
fuel.name = inFuel.name fuel.name = inFuel.name
fuel.count = fuel.count + count fuel.count = fuel.count + count
end end
end end
end end
local result = getSlot(furnace, OUTPUT_SLOT) local result = getSlot(furnace, OUTPUT_SLOT)
if result.count > 0 then if result.count > 0 then
if inReturn.count == 0 or result.name == inReturn.name then if inReturn.count == 0 or result.name == inReturn.name then
print('pulling from : ' .. furnace.dev.name) print('pulling from : ' .. furnace.dev.name)
local count = furnace.dev.pushItems(localName, OUTPUT_SLOT, result.count, OUTPUT_SLOT) local count = furnace.dev.pushItems(localName, OUTPUT_SLOT, result.count, OUTPUT_SLOT)
if count > 0 then if count > 0 then
result.count = result.count - count result.count = result.count - count
if result.count == 0 then if result.count == 0 then
furnace.list[OUTPUT_SLOT] = nil furnace.list[OUTPUT_SLOT] = nil
end end
inReturn.name = result.name inReturn.name = result.name
inReturn.count = inReturn.count + count inReturn.count = inReturn.count + count
end end
end end
end end
end) end)
if not s and m then if not s and m then
_G.printError(m) _G.printError(m)
end end
end end
end end
Event.on('turtle_inventory', function() Event.on('turtle_inventory', function()
process(inv.list()) process(inv.list())
print('idle') print('idle')
end) end)
Event.onInterval(3, function() Event.onInterval(3, function()
os.queueEvent('turtle_inventory') os.queueEvent('turtle_inventory')
end) end)
os.queueEvent('turtle_inventory') 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') local Util = require('util')
@@ -11,17 +11,17 @@ local st = args[1] or error('Specify a storage type (ie. minecraft:chest)')
local config = { } local config = { }
peripheral.find(st, function(n) peripheral.find(st, function(n)
config[n] = { config[n] = {
name = n, name = n,
category = 'storage', category = 'storage',
mtype = 'storage', mtype = 'storage',
} }
end) end)
print('Found ' .. Util.size(config)) print('Found ' .. Util.size(config))
if Util.size(config) == 0 then if Util.size(config) == 0 then
error('Invalid peripheral type') error('Invalid peripheral type')
end end
Util.writeTable('usr/config/storageGen', config) Util.writeTable('usr/config/storageGen', config)

View File

@@ -4,90 +4,90 @@ local UI = require('ui')
local turtle = _G.turtle local turtle = _G.turtle
local learnPage = UI.Page { local learnPage = UI.Page {
titleBar = UI.TitleBar { title = 'Learn Recipe' }, titleBar = UI.TitleBar { title = 'Learn Recipe' },
wizard = UI.Wizard { wizard = UI.Wizard {
y = 2, ey = -2, y = 2, ey = -2,
pages = { pages = {
general = UI.WizardPage { general = UI.WizardPage {
index = 1, index = 1,
grid = UI.ScrollingGrid { grid = UI.ScrollingGrid {
x = 2, ex = -2, y = 2, ey = -2, x = 2, ex = -2, y = 2, ey = -2,
disableHeader = true, disableHeader = true,
columns = { columns = {
{ heading = 'Name', key = 'name'}, { heading = 'Name', key = 'name'},
}, },
sortColumn = 'name', sortColumn = 'name',
}, },
accelerators = { accelerators = {
grid_select = 'nextView', grid_select = 'nextView',
}, },
}, },
}, },
}, },
notification = UI.Notification { }, notification = UI.Notification { },
} }
local general = learnPage.wizard.pages.general local general = learnPage.wizard.pages.general
function general:validate() function general:validate()
Milo:setState('learnType', self.grid:getSelected().value) Milo:setState('learnType', self.grid:getSelected().value)
return true return true
end end
function learnPage:enable() function learnPage:enable()
local t = { } local t = { }
for _, page in pairs(self.wizard.pages) do for _, page in pairs(self.wizard.pages) do
if page.validFor then if page.validFor then
t[page.validFor] = { t[page.validFor] = {
name = page.validFor, name = page.validFor,
value = page.validFor, value = page.validFor,
} }
end end
end end
general.grid:setValues(t) general.grid:setValues(t)
general.grid:setSelected('name', Milo:getState('learnType') or '') general.grid:setSelected('name', Milo:getState('learnType') or '')
Milo:pauseCrafting({ key = 'gridInUse', msg = 'Crafting paused' }) Milo:pauseCrafting({ key = 'gridInUse', msg = 'Crafting paused' })
self:focusFirst() self:focusFirst()
UI.Page.enable(self) UI.Page.enable(self)
end end
function learnPage:disable() function learnPage:disable()
Milo:resumeCrafting({ key = 'gridInUse' }) Milo:resumeCrafting({ key = 'gridInUse' })
return UI.Page.disable(self) return UI.Page.disable(self)
end end
function learnPage.wizard:getPage(index) function learnPage.wizard:getPage(index)
local pages = { } local pages = { }
table.insert(pages, general) table.insert(pages, general)
local selected = general.grid:getSelected() local selected = general.grid:getSelected()
for _, page in pairs(self.pages) do for _, page in pairs(self.pages) do
if page.validFor and (not selected or selected.value == page.validFor) then if page.validFor and (not selected or selected.value == page.validFor) then
table.insert(pages, page) table.insert(pages, page)
end end
end end
table.sort(pages, function(a, b) table.sort(pages, function(a, b)
return a.index < b.index return a.index < b.index
end) end)
return pages[index] return pages[index]
end end
function learnPage:eventHandler(event) function learnPage:eventHandler(event)
if event.type == 'cancel' then if event.type == 'cancel' then
turtle.emptyInventory() turtle.emptyInventory()
UI:setPreviousPage() UI:setPreviousPage()
elseif event.type == 'form_invalid' or event.type == 'general_error' then elseif event.type == 'form_invalid' or event.type == 'general_error' then
self.notification:error(event.message) self.notification:error(event.message)
self:setFocus(event.field) self:setFocus(event.field)
else else
return UI.Page.eventHandler(self, event) return UI.Page.eventHandler(self, event)
end end
return true return true
end end
UI:addPage('learnWizard', learnPage) UI:addPage('learnWizard', learnPage)

View File

@@ -11,390 +11,390 @@ local context = Milo:getContext()
local displayMode = Milo:getState('displayMode') or 0 local displayMode = Milo:getState('displayMode') or 0
local displayModes = { local displayModes = {
[0] = { text = 'A', help = 'Showing all items' }, [0] = { text = 'A', help = 'Showing all items' },
[1] = { text = 'I', help = 'Showing inventory items' }, [1] = { text = 'I', help = 'Showing inventory items' },
} }
local page = UI.Page { local page = UI.Page {
menuBar = UI.MenuBar { menuBar = UI.MenuBar {
buttons = { buttons = {
{ text = 'Learn', event = 'learn' }, { text = 'Learn', event = 'learn' },
{ text = 'Craft', event = 'craft' }, { text = 'Craft', event = 'craft' },
{ text = 'Edit', event = 'details' }, { text = 'Edit', event = 'details' },
{ text = 'Refresh', event = 'refresh', x = -12 }, { text = 'Refresh', event = 'refresh', x = -12 },
{ {
text = '\187', text = '\187',
x = -3, x = -3,
dropdown = { dropdown = {
{ text = 'Setup', event = 'network' }, { text = 'Setup', event = 'network' },
{ spacer = true }, { spacer = true },
{ {
text = 'Rescan storage', text = 'Rescan storage',
event = 'rescan', event = 'rescan',
help = 'Rescan all inventories' help = 'Rescan all inventories'
}, },
}, },
}, },
}, },
}, },
grid = UI.Grid { grid = UI.Grid {
y = 2, ey = -2, y = 2, ey = -2,
columns = { columns = {
{ heading = ' Qty', key = 'count' , width = 4, align = 'right' }, { heading = ' Qty', key = 'count' , width = 4, align = 'right' },
{ heading = 'Name', key = 'displayName' }, { heading = 'Name', key = 'displayName' },
{ heading = 'Min', key = 'low' , width = 4 }, { heading = 'Min', key = 'low' , width = 4 },
{ heading = 'Max', key = 'limit' , width = 4 }, { heading = 'Max', key = 'limit' , width = 4 },
}, },
sortColumn = Milo:getState('sortColumn') or 'count', sortColumn = Milo:getState('sortColumn') or 'count',
inverseSort = Milo:getState('inverseSort'), inverseSort = Milo:getState('inverseSort'),
}, },
statusBar = UI.StatusBar { statusBar = UI.StatusBar {
filter = UI.TextEntry { filter = UI.TextEntry {
x = 1, ex = -17, x = 1, ex = -17,
limit = 50, limit = 50,
shadowText = 'filter', shadowText = 'filter',
shadowTextColor = colors.gray, shadowTextColor = colors.gray,
backgroundColor = colors.cyan, backgroundColor = colors.cyan,
backgroundFocusColor = colors.cyan, backgroundFocusColor = colors.cyan,
accelerators = { accelerators = {
[ 'enter' ] = 'eject', [ 'enter' ] = 'eject',
[ 'up' ] = 'grid_up', [ 'up' ] = 'grid_up',
[ 'down' ] = 'grid_down', [ 'down' ] = 'grid_down',
[ 'control-a' ] = 'eject_all', [ 'control-a' ] = 'eject_all',
}, },
}, },
storageStatus = UI.Text { storageStatus = UI.Text {
x = -16, ex = -9, x = -16, ex = -9,
textColor = colors.lime, textColor = colors.lime,
backgroundColor = colors.cyan, backgroundColor = colors.cyan,
value = '', value = '',
}, },
amount = UI.TextEntry { amount = UI.TextEntry {
x = -8, ex = -4, x = -8, ex = -4,
limit = 3, limit = 3,
shadowText = '1', shadowText = '1',
shadowTextColor = colors.gray, shadowTextColor = colors.gray,
backgroundColor = colors.black, backgroundColor = colors.black,
backgroundFocusColor = colors.black, backgroundFocusColor = colors.black,
accelerators = { accelerators = {
[ 'enter' ] = 'eject_specified', [ 'enter' ] = 'eject_specified',
[ 'control-a' ] = 'eject_all', [ 'control-a' ] = 'eject_all',
}, },
help = 'Specify an amount to send', help = 'Specify an amount to send',
}, },
display = UI.Button { display = UI.Button {
x = -3, x = -3,
event = 'toggle_display', event = 'toggle_display',
value = 0, value = 0,
text = displayModes[displayMode].text, text = displayModes[displayMode].text,
help = displayModes[displayMode].help, help = displayModes[displayMode].help,
}, },
}, },
notification = UI.Notification { notification = UI.Notification {
anchor = 'top', anchor = 'top',
}, },
throttle = UI.Throttle { throttle = UI.Throttle {
textColor = colors.yellow, textColor = colors.yellow,
borderColor = colors.gray, borderColor = colors.gray,
}, },
accelerators = { accelerators = {
r = 'refresh', r = 'refresh',
[ 'control-r' ] = 'refresh', [ 'control-r' ] = 'refresh',
[ 'control-e' ] = 'eject', [ 'control-e' ] = 'eject',
[ 'control-s' ] = 'eject_stack', [ 'control-s' ] = 'eject_stack',
[ 'control-a' ] = 'eject_all', [ 'control-a' ] = 'eject_all',
[ 'control-m' ] = 'network', [ 'control-m' ] = 'network',
q = 'quit', q = 'quit',
}, },
allItems = { } allItems = { }
} }
function page.statusBar:draw() function page.statusBar:draw()
return UI.Window.draw(self) return UI.Window.draw(self)
end end
function page.grid:getRowTextColor(row, selected) function page.grid:getRowTextColor(row, selected)
if row.is_craftable then if row.is_craftable then
return colors.yellow return colors.yellow
end end
if row.has_recipe then if row.has_recipe then
return colors.cyan return colors.cyan
end end
return UI.Grid:getRowTextColor(row, selected) return UI.Grid:getRowTextColor(row, selected)
end end
function page.grid:getDisplayValues(row) function page.grid:getDisplayValues(row)
row = Util.shallowCopy(row) row = Util.shallowCopy(row)
row.count = row.count > 0 and Util.toBytes(row.count) row.count = row.count > 0 and Util.toBytes(row.count)
if row.low then if row.low then
row.low = Util.toBytes(row.low) row.low = Util.toBytes(row.low)
end end
if row.limit then if row.limit then
row.limit = Util.toBytes(row.limit) row.limit = Util.toBytes(row.limit)
end end
return row return row
end end
function page.grid:sortCompare(a, b) function page.grid:sortCompare(a, b)
if self.sortColumn ~= 'displayName' then if self.sortColumn ~= 'displayName' then
if a[self.sortColumn] == b[self.sortColumn] then if a[self.sortColumn] == b[self.sortColumn] then
if self.inverseSort then if self.inverseSort then
return a.displayName > b.displayName return a.displayName > b.displayName
end end
return a.displayName < b.displayName return a.displayName < b.displayName
end end
if a[self.sortColumn] == 0 then if a[self.sortColumn] == 0 then
return self.inverseSort return self.inverseSort
end end
if b[self.sortColumn] == 0 then if b[self.sortColumn] == 0 then
return not self.inverseSort return not self.inverseSort
end end
return a[self.sortColumn] < b[self.sortColumn] return a[self.sortColumn] < b[self.sortColumn]
end end
return UI.Grid.sortCompare(self, a, b) return UI.Grid.sortCompare(self, a, b)
end end
function page.grid:eventHandler(event) function page.grid:eventHandler(event)
if event.type == 'grid_sort' then if event.type == 'grid_sort' then
Milo:setState('sortColumn', event.sortColumn) Milo:setState('sortColumn', event.sortColumn)
Milo:setState('inverseSort', event.inverseSort) Milo:setState('inverseSort', event.inverseSort)
end end
return UI.Grid.eventHandler(self, event) return UI.Grid.eventHandler(self, event)
end end
function page:eject(amount) function page:eject(amount)
local item = self.grid:getSelected() local item = self.grid:getSelected()
if item and amount then if item and amount then
-- get most up-to-date item -- get most up-to-date item
if item then if item then
if amount == 'stack' then if amount == 'stack' then
amount = item.maxCount or 64 amount = item.maxCount or 64
elseif amount == 'all' then elseif amount == 'all' then
item = Milo:getItem(item) item = Milo:getItem(item)
if item then if item then
amount = item.count amount = item.count
end end
end end
if item and amount > 0 then if item and amount > 0 then
item = Util.shallowCopy(item) item = Util.shallowCopy(item)
self.grid.values[self.grid.sorted[self.grid.index]] = item self.grid.values[self.grid.sorted[self.grid.index]] = item
local request = Milo:craftAndEject(item, amount) local request = Milo:craftAndEject(item, amount)
item.count = request.current - request.count item.count = request.current - request.count
if request.craft then if request.craft then
if request.craft > 0 then if request.craft > 0 then
self:notifyInfo(request.craft .. ' crafting ...') self:notifyInfo(request.craft .. ' crafting ...')
elseif request.craft + request.count < request.requested then elseif request.craft + request.count < request.requested then
if request.craft + request.count == 0 then if request.craft + request.count == 0 then
Sound.play('entity.villager.no') Sound.play('entity.villager.no')
end end
self:notifyInfo((request.craft + request.count) .. ' available ...') self:notifyInfo((request.craft + request.count) .. ' available ...')
end end
end end
if request.count + request.craft > 0 then if request.count + request.craft > 0 then
self.grid:draw() self.grid:draw()
return true return true
end end
end end
end end
end end
Sound.play('entity.villager.no') Sound.play('entity.villager.no')
end end
function page:eventHandler(event) function page:eventHandler(event)
if event.type == 'quit' then if event.type == 'quit' then
UI:exitPullEvents() UI:exitPullEvents()
elseif event.type == 'eject' or event.type == 'grid_select' then elseif event.type == 'eject' or event.type == 'grid_select' then
self:eject(1) self:eject(1)
elseif event.type == 'eject_stack' then elseif event.type == 'eject_stack' then
self:eject('stack') self:eject('stack')
elseif event.type == 'eject_all' then elseif event.type == 'eject_all' then
self:eject('all') self:eject('all')
elseif event.type == 'eject_specified' then elseif event.type == 'eject_specified' then
if self:eject(tonumber(self.statusBar.amount.value)) then if self:eject(tonumber(self.statusBar.amount.value)) then
self.statusBar.amount:reset() self.statusBar.amount:reset()
self:setFocus(self.statusBar.filter) self:setFocus(self.statusBar.filter)
end end
elseif event.type == 'network' then elseif event.type == 'network' then
UI:setPage('network') UI:setPage('network')
elseif event.type == 'details' or event.type == 'grid_select_right' then elseif event.type == 'details' or event.type == 'grid_select_right' then
local item = self.grid:getSelected() local item = self.grid:getSelected()
if item then if item then
UI:setPage('item', item) UI:setPage('item', item)
end end
elseif event.type == 'grid_up' then elseif event.type == 'grid_up' then
self.grid:emit({ type = 'scroll_up' }) self.grid:emit({ type = 'scroll_up' })
elseif event.type == 'grid_down' then elseif event.type == 'grid_down' then
self.grid:emit({ type = 'scroll_down' }) self.grid:emit({ type = 'scroll_down' })
elseif event.type == 'refresh' then elseif event.type == 'refresh' then
self:refresh() self:refresh()
self.grid:draw() self.grid:draw()
self:setFocus(self.statusBar.filter) self:setFocus(self.statusBar.filter)
elseif event.type == 'rescan' then elseif event.type == 'rescan' then
self:refresh(true) self:refresh(true)
self.grid:draw() self.grid:draw()
self:setFocus(self.statusBar.filter) self:setFocus(self.statusBar.filter)
elseif event.type == 'toggle_display' then elseif event.type == 'toggle_display' then
displayMode = (displayMode + 1) % 2 displayMode = (displayMode + 1) % 2
Util.merge(event.button, displayModes[displayMode]) Util.merge(event.button, displayModes[displayMode])
event.button:draw() event.button:draw()
self:applyFilter() self:applyFilter()
self.grid:draw() self.grid:draw()
Milo:setState('displayMode', displayMode) Milo:setState('displayMode', displayMode)
elseif event.type == 'learn' then elseif event.type == 'learn' then
UI:setPage('learnWizard') UI:setPage('learnWizard')
elseif event.type == 'craft' then elseif event.type == 'craft' then
local item = self.grid:getSelected() local item = self.grid:getSelected()
if item then if item then
if Craft.findRecipe(item) then -- or item.is_craftable then if Craft.findRecipe(item) then -- or item.is_craftable then
UI:setPage('craft', self.grid:getSelected()) UI:setPage('craft', self.grid:getSelected())
else else
self.notification:error('No recipe defined') self.notification:error('No recipe defined')
end end
end end
elseif event.type == 'text_change' and event.element == self.statusBar.filter then elseif event.type == 'text_change' and event.element == self.statusBar.filter then
self.filter = event.text self.filter = event.text
if #self.filter == 0 then if #self.filter == 0 then
self.filter = nil self.filter = nil
end end
self:applyFilter() self:applyFilter()
self.grid:setIndex(1) self.grid:setIndex(1)
self.grid:draw() self.grid:draw()
self.statusBar.filter:focus() self.statusBar.filter:focus()
else else
UI.Page.eventHandler(self, event) UI.Page.eventHandler(self, event)
end end
return true return true
end end
function page:notifySuccess(status) function page:notifySuccess(status)
self.notification:success(status) self.notification:success(status)
end end
function page:notifyInfo(status) function page:notifyInfo(status)
self.notification:info(status) self.notification:info(status)
end end
function page:notifyError(status) function page:notifyError(status)
self.notification:error(status) self.notification:error(status)
end end
function page:enable(args) function page:enable(args)
local function updateStatus() local function updateStatus()
self.statusBar.storageStatus.value = self.statusBar.storageStatus.value =
context.storage:isOnline() and '' or 'offline' context.storage:isOnline() and '' or 'offline'
self.statusBar.storageStatus.textColor = self.statusBar.storageStatus.textColor =
context.storage:isOnline() and colors.lime or colors.red context.storage:isOnline() and colors.lime or colors.red
end end
updateStatus() updateStatus()
Event.onTimeout(0, function() Event.onTimeout(0, function()
self:refresh() self:refresh()
self:draw() self:draw()
self:sync() self:sync()
self.timer = Event.onInterval(3, function() self.timer = Event.onInterval(3, function()
for _,v in pairs(self.grid.values) do for _,v in pairs(self.grid.values) do
local c = context.storage.cache[v.key] local c = context.storage.cache[v.key]
v.count = c and c.count or 0 v.count = c and c.count or 0
end end
self.grid:draw() self.grid:draw()
self:sync() self:sync()
end) end)
self.handler = Event.on({ 'storage_offline', 'storage_online' }, function() self.handler = Event.on({ 'storage_offline', 'storage_online' }, function()
updateStatus() updateStatus()
self.statusBar.storageStatus:draw() self.statusBar.storageStatus:draw()
self:sync() self:sync()
end) end)
end) end)
if args and args.filter then if args and args.filter then
self.filter = args.filter self.filter = args.filter
self.statusBar.filter.value = args.filter self.statusBar.filter.value = args.filter
end end
if args and args.message then if args and args.message then
self.notification:success(args.message) self.notification:success(args.message)
end end
self:setFocus(self.statusBar.filter) self:setFocus(self.statusBar.filter)
UI.Page.enable(self) UI.Page.enable(self)
end end
function page:disable() function page:disable()
Event.off(self.timer) Event.off(self.timer)
Event.off(self.handler) Event.off(self.handler)
UI.Page.disable(self) UI.Page.disable(self)
end end
function page:refresh(force) function page:refresh(force)
local throttle = function() self.throttle:update() end local throttle = function() self.throttle:update() end
self.throttle:enable() self.throttle:enable()
self.allItems = Milo:mergeResources(Milo:listItems(force, throttle)) self.allItems = Milo:mergeResources(Milo:listItems(force, throttle))
self:applyFilter() self:applyFilter()
self.throttle:disable() self.throttle:disable()
end end
function page:applyFilter() function page:applyFilter()
local function filterItems(t, filter) local function filterItems(t, filter)
self.grid.sortColumn = Milo:getState('sortColumn') or 'count' self.grid.sortColumn = Milo:getState('sortColumn') or 'count'
self.grid.inverseSort = Milo:getState('inverseSort') self.grid.inverseSort = Milo:getState('inverseSort')
if filter then if filter then
local r = { } local r = { }
filter = filter:lower() filter = filter:lower()
self.grid.sortColumn = 'score' self.grid.sortColumn = 'score'
self.grid.inverseSort = true self.grid.inverseSort = true
for _,v in pairs(t) do for _,v in pairs(t) do
v.score = fuzzy(v.lname, filter) v.score = fuzzy(v.lname, filter)
if v.score then if v.score then
if v.count > 0 then if v.count > 0 then
v.score = v.score + 1 v.score = v.score + 1
end end
table.insert(r, v) table.insert(r, v)
end end
end end
return r return r
elseif displayMode > 0 then elseif displayMode > 0 then
local r = { } local r = { }
for _,v in pairs(t) do for _,v in pairs(t) do
if v.count > 0 then if v.count > 0 then
table.insert(r, v) table.insert(r, v)
end end
end end
return r return r
end end
return t return t
end end
local t = filterItems(self.allItems, self.filter) local t = filterItems(self.allItems, self.filter)
self.grid:setValues(t) self.grid:setValues(t)
end end
UI:addPage('listing', page) UI:addPage('listing', page)

View File

@@ -1,20 +1,20 @@
{ {
[ "9302912a2d9794a47241faefc475335b4e07a581" ] = { [ "9302912a2d9794a47241faefc475335b4e07a581" ] = {
title = "Remote", title = "Remote",
category = "Apps", category = "Apps",
run = "MiloRemote", run = "MiloRemote",
requires = "neuralInterface", requires = "neuralInterface",
iconExt = "\0304\031f\135\129\0314\128\128\031f\130\030f\128\ 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\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", \031f\128\030c\0310\142\030f\031c\149\030c\0310\139\030f\031c\149\031f\128",
}, },
[ "eea426f9baef72a8fcefd091e0cec5ab94a76698" ] = { [ "eea426f9baef72a8fcefd091e0cec5ab94a76698" ] = {
title = "Milo", title = "Milo",
category = "Apps", category = "Apps",
run = "MiloLocal", run = "MiloLocal",
requires = 'advancedTurtle', requires = 'advancedTurtle',
iconExt = "\0304\031f\135\129\0314\128\128\031f\130\030f\128\ 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\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", \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.]] Right-clicking on the activity monitor will reset the totals.]]
local wizardPage = UI.WizardPage { local wizardPage = UI.WizardPage {
title = 'Activity Monitor', title = 'Activity Monitor',
index = 2, index = 2,
backgroundColor = colors.cyan, backgroundColor = colors.cyan,
[1] = UI.TextArea { [1] = UI.TextArea {
x = 2, ex = -2, y = 2, ey = 6, x = 2, ex = -2, y = 2, ey = 6,
marginRight = 0, marginRight = 0,
value = string.format(template, Ansi.yellow, Ansi.reset), value = string.format(template, Ansi.yellow, Ansi.reset),
}, },
form = UI.Form { form = UI.Form {
x = 2, ex = -2, y = 7, ey = -2, x = 2, ex = -2, y = 7, ey = -2,
manualControls = true, manualControls = true,
[1] = UI.Chooser { [1] = UI.Chooser {
width = 9, width = 9,
formLabel = 'Font Size', formKey = 'textScale', formLabel = 'Font Size', formKey = 'textScale',
nochoice = 'Small', nochoice = 'Small',
choices = { choices = {
{ name = 'Small', value = .5 }, { name = 'Small', value = .5 },
{ name = 'Large', value = 1 }, { name = 'Large', value = 1 },
}, },
help = 'Adjust text scaling', help = 'Adjust text scaling',
}, },
}, },
} }
function wizardPage:setNode(node) function wizardPage:setNode(node)
self.form:setValues(node) self.form:setValues(node)
end end
function wizardPage:validate() function wizardPage:validate()
return self.form:save() return self.form:save()
end end
function wizardPage:saveNode(node) function wizardPage:saveNode(node)
os.queueEvent('monitor_resize', node.name) os.queueEvent('monitor_resize', node.name)
end end
function wizardPage:isValidType(node) function wizardPage:isValidType(node)
local m = device[node.name] local m = device[node.name]
return m and m.type == 'monitor' and { return m and m.type == 'monitor' and {
name = 'Activity Monitor', name = 'Activity Monitor',
value = 'activity', value = 'activity',
category = 'display', category = 'display',
help = 'Display storage activity' help = 'Display storage activity'
} }
end end
function wizardPage:isValidFor(node) function wizardPage:isValidFor(node)
return node.mtype == 'activity' return node.mtype == 'activity'
end end
UI:getPage('nodeWizard').wizard:add({ activity = wizardPage }) UI:getPage('nodeWizard').wizard:add({ activity = wizardPage })
--[[ Display ]]-- --[[ Display ]]--
local function createPage(node) local function createPage(node)
local monitor = UI.Device { local monitor = UI.Device {
device = node.adapter, device = node.adapter,
textScale = node.textScale or .5, textScale = node.textScale or .5,
} }
function monitor:resize() function monitor:resize()
self.textScale = node.textScale or .5 self.textScale = node.textScale or .5
UI.Device.resize(self) UI.Device.resize(self)
end end
local page = UI.Page { local page = UI.Page {
parent = monitor, parent = monitor,
backgroundColor = colors.black, backgroundColor = colors.black,
grid = UI.Grid { grid = UI.Grid {
ey = -3, ey = -3,
columns = { columns = {
{ heading = 'Qty', key = 'count', width = 6, align = 'right' }, { heading = 'Qty', key = 'count', width = 6, align = 'right' },
{ heading = '+/-', key = 'change', width = 6, align = 'right' }, { heading = '+/-', key = 'change', width = 6, align = 'right' },
{ heading = 'Name', key = 'displayName' }, { heading = 'Name', key = 'displayName' },
{ heading = 'Rate', key = 'rate', width = 6, align = 'right' }, { heading = 'Rate', key = 'rate', width = 6, align = 'right' },
}, },
sortColumn = 'displayName', sortColumn = 'displayName',
headerBackgroundColor = colors.black, headerBackgroundColor = colors.black,
headerTextColor = colors.cyan, headerTextColor = colors.cyan,
headerHeight = 2, headerHeight = 2,
}, },
buttons = UI.Window { buttons = UI.Window {
y = -1, y = -1,
backgroundColor = colors.black, backgroundColor = colors.black,
prevButton = UI.Button { prevButton = UI.Button {
x = 1, width = 5, x = 1, width = 5,
event = 'previous', event = 'previous',
textColor = colors.cyan, textColor = colors.cyan,
backgroundColor = colors.black, backgroundColor = colors.black,
text = ' < ' text = ' < '
}, },
resetButton = UI.Button { resetButton = UI.Button {
x = 7, ex = -7, x = 7, ex = -7,
event = 'reset', event = 'reset',
textColor = colors.cyan, textColor = colors.cyan,
backgroundColor = colors.black, backgroundColor = colors.black,
text = 'Reset' text = 'Reset'
}, },
nextButton = UI.Button { nextButton = UI.Button {
x = -5, width = 5, x = -5, width = 5,
event = 'next', event = 'next',
textColor = colors.cyan, textColor = colors.cyan,
backgroundColor = colors.black, backgroundColor = colors.black,
text = ' > ' text = ' > '
}, },
}, },
timestamp = os.clock(), timestamp = os.clock(),
} }
function page.grid:getRowTextColor(row, selected) function page.grid:getRowTextColor(row, selected)
if row.lastCount and row.lastCount ~= row.count then if row.lastCount and row.lastCount ~= row.count then
return row.count > row.lastCount and colors.yellow or colors.lightGray return row.count > row.lastCount and colors.yellow or colors.lightGray
end end
return UI.Grid:getRowTextColor(row, selected) return UI.Grid:getRowTextColor(row, selected)
end end
function page.grid:getDisplayValues(row) function page.grid:getDisplayValues(row)
row = Util.shallowCopy(row) row = Util.shallowCopy(row)
local ind = '+' local ind = '+'
if row.change < 0 then if row.change < 0 then
ind = '' ind = ''
end end
row.change = ind .. Util.toBytes(row.change) row.change = ind .. Util.toBytes(row.change)
row.count = Util.toBytes(row.count) row.count = Util.toBytes(row.count)
row.rate = Util.toBytes(row.rate) row.rate = Util.toBytes(row.rate)
return row return row
end end
function page:eventHandler(event) function page:eventHandler(event)
if event.type == 'reset' then if event.type == 'reset' then
self:reset() self:reset()
elseif event.type == 'next' then elseif event.type == 'next' then
self.grid:nextPage() self.grid:nextPage()
elseif event.type == 'previous' then elseif event.type == 'previous' then
self.grid:previousPage() self.grid:previousPage()
else else
return UI.Page.eventHandler(self, event) return UI.Page.eventHandler(self, event)
end end
Event.onTimeout(.1, function() Event.onTimeout(.1, function()
self:setFocus(self.grid) self:setFocus(self.grid)
self:sync() self:sync()
end) end)
return true return true
end end
function page:reset() function page:reset()
self.lastItems = nil self.lastItems = nil
self.grid:setValues({ }) self.grid:setValues({ })
self.grid:draw() self.grid:draw()
end end
function page:refresh() function page:refresh()
local t = context.storage.cache local t = context.storage.cache
if t and not self.lastItems then if t and not self.lastItems then
self.lastItems = { } self.lastItems = { }
for k,v in pairs(t) do for k,v in pairs(t) do
self.lastItems[k] = { self.lastItems[k] = {
displayName = v.displayName, displayName = v.displayName,
initialCount = v.count, initialCount = v.count,
} }
end end
self.timestamp = os.clock() self.timestamp = os.clock()
self.grid:setValues({ }) self.grid:setValues({ })
else else
for _,v in pairs(self.lastItems) do for _,v in pairs(self.lastItems) do
v.lastCount = v.count v.lastCount = v.count
v.count = nil v.count = nil
end end
self.elapsed = os.clock() - self.timestamp self.elapsed = os.clock() - self.timestamp
for k,v in pairs(t) do for k,v in pairs(t) do
local v2 = self.lastItems[k] local v2 = self.lastItems[k]
if v2 then if v2 then
v2.count = v.count v2.count = v.count
else else
self.lastItems[k] = { self.lastItems[k] = {
displayName = v.displayName, displayName = v.displayName,
count = v.count, count = v.count,
initialCount = 0, initialCount = 0,
} }
end end
end end
local changedItems = { } local changedItems = { }
for k,v in pairs(self.lastItems) do for k,v in pairs(self.lastItems) do
if not v.count then if not v.count then
v.count = 0 v.count = 0
end end
if v.count ~= v.initialCount then if v.count ~= v.initialCount then
v.change = v.count - v.initialCount v.change = v.count - v.initialCount
v.rate = Util.round(60 / self.elapsed * v.change, 1) v.rate = Util.round(60 / self.elapsed * v.change, 1)
changedItems[k] = v changedItems[k] = v
end end
end end
self.grid:setValues(changedItems) self.grid:setValues(changedItems)
end end
self.grid:draw() self.grid:draw()
end end
function page:update() function page:update()
page:refresh() page:refresh()
page:sync() page:sync()
end end
UI:setPage(page) UI:setPage(page)
return page return page
end end
local pages = { } local pages = { }
--[[ Task ]]-- --[[ Task ]]--
local ActivityTask = { local ActivityTask = {
name = 'activity', name = 'activity',
priority = 30, priority = 30,
} }
function ActivityTask:cycle() function ActivityTask:cycle()
for node in context.storage:filterActive('activity') do for node in context.storage:filterActive('activity') do
if not pages[node.name] then if not pages[node.name] then
pages[node.name] = createPage(node) pages[node.name] = createPage(node)
end end
pages[node.name]:update() pages[node.name]:update()
end end
end end
Milo:registerTask(ActivityTask) Milo:registerTask(ActivityTask)

View File

@@ -21,97 +21,97 @@ Backup configuration files each minecraft day.
]] ]]
local wizardPage = UI.WizardPage { local wizardPage = UI.WizardPage {
title = 'Backup Drive', title = 'Backup Drive',
index = 2, index = 2,
backgroundColor = colors.cyan, backgroundColor = colors.cyan,
[1] = UI.TextArea { [1] = UI.TextArea {
x = 2, ex = -2, y = 2, ey = -2, x = 2, ex = -2, y = 2, ey = -2,
value = string.format(template, Ansi.yellow, Ansi.reset), value = string.format(template, Ansi.yellow, Ansi.reset),
}, },
} }
function wizardPage:isValidType(node) function wizardPage:isValidType(node)
local m = device[node.name] local m = device[node.name]
return m and m.type == 'drive' and { return m and m.type == 'drive' and {
name = 'Backup Drive', name = 'Backup Drive',
value = 'backup', value = 'backup',
category = 'custom', category = 'custom',
help = 'Backup configuration files', help = 'Backup configuration files',
} }
end end
function wizardPage:isValidFor(node) function wizardPage:isValidFor(node)
return node.mtype == 'backup' return node.mtype == 'backup'
end end
UI:getPage('nodeWizard').wizard:add({ backupDrive = wizardPage }) UI:getPage('nodeWizard').wizard:add({ backupDrive = wizardPage })
local function clearOld(dir, fname) local function clearOld(dir, fname)
local files = { } local files = { }
for _, file in pairs(fs.list(dir)) do for _, file in pairs(fs.list(dir)) do
if file:match(fname) then if file:match(fname) then
table.insert(files, file) table.insert(files, file)
end end
end end
if #files > 1 then if #files > 1 then
table.sort(files, function(a, b) table.sort(files, function(a, b)
return tonumber(a:match('.(%d+)')) > tonumber(b:match('.(%d+)')) return tonumber(a:match('.(%d+)')) > tonumber(b:match('.(%d+)'))
end) end)
while #files > 1 do while #files > 1 do
local old = table.remove(files, #files) local old = table.remove(files, #files)
fs.delete(fs.combine(dir, old)) fs.delete(fs.combine(dir, old))
end end
end end
end end
local function makeBackup(dir, fname) local function makeBackup(dir, fname)
clearOld(dir, fname) clearOld(dir, fname)
local source = fs.combine('usr/config', fname) local source = fs.combine('usr/config', fname)
local dest = string.format('%s/%s.%d', dir, fname, os.day()) local dest = string.format('%s/%s.%d', dir, fname, os.day())
fs.copy(source, dest) fs.copy(source, dest)
end end
local function backupNode(node) local function backupNode(node)
local files = { local files = {
'storage', 'storage',
'milo.state', 'milo.state',
'machine_crafting.db', 'machine_crafting.db',
'recipes.db', 'recipes.db',
'resources.db', 'resources.db',
} }
local s, m = pcall(function() local s, m = pcall(function()
if not node.adapter.isDiskPresent() then if not node.adapter.isDiskPresent() then
_G._syslog('BACKUP error: No media present') _G._syslog('BACKUP error: No media present')
else else
local dir = node.adapter.getMountPath() local dir = node.adapter.getMountPath()
for _, v in pairs(files) do for _, v in pairs(files) do
makeBackup(dir, v) makeBackup(dir, v)
end end
end end
end) end)
if not s and m then if not s and m then
_G._syslog('BACKUP error:' .. m) _G._syslog('BACKUP error:' .. m)
end end
end end
--[[ Task ]]-- --[[ Task ]]--
local BackupTask = { local BackupTask = {
name = 'backup', name = 'backup',
priority = 99, priority = 99,
} }
function BackupTask:cycle() function BackupTask:cycle()
for node in context.storage:filterActive('backup') do for node in context.storage:filterActive('backup') do
if not drives[node.name] then if not drives[node.name] then
drives[node.name] = Event.onInterval(DAY, function() drives[node.name] = Event.onInterval(DAY, function()
_G._syslog('BACKUP: started') _G._syslog('BACKUP: started')
if node.adapter and node.adapter.online then if node.adapter and node.adapter.online then
backupNode(node) backupNode(node)
end end
end) end)
end end
end end
end end
Milo:registerTask(BackupTask) Milo:registerTask(BackupTask)

View File

@@ -6,67 +6,67 @@ local Util = require('util')
local context = Milo:getContext() local context = Milo:getContext()
local craftTask = { local craftTask = {
name = 'crafting', name = 'crafting',
priority = 70, priority = 70,
} }
function craftTask:craft(recipe, item) function craftTask:craft(recipe, item)
if Milo:isCraftingPaused() then if Milo:isCraftingPaused() then
return return
end end
-- TODO: refactor into craft.lua -- TODO: refactor into craft.lua
Craft.processPending(item, context.storage) Craft.processPending(item, context.storage)
-- create a mini-list of items that are required for this recipe -- create a mini-list of items that are required for this recipe
item.ingredients = Craft.getResourceList( item.ingredients = Craft.getResourceList(
recipe, Milo:listItems(), item.requested - item.crafted, item.pending) recipe, Milo:listItems(), item.requested - item.crafted, item.pending)
for k, v in pairs(item.ingredients) do for k, v in pairs(item.ingredients) do
v.crafted = v.used v.crafted = v.used
v.count = v.used v.count = v.used
v.key = k v.key = k
if v.need > 0 then if v.need > 0 then
v.status = 'No recipe' v.status = 'No recipe'
v.statusCode = Craft.STATUS_ERROR v.statusCode = Craft.STATUS_ERROR
end end
end end
item.ingredients[recipe.result] = item item.ingredients[recipe.result] = item
item.ingredients[recipe.result].total = item.count item.ingredients[recipe.result].total = item.count
item.ingredients[recipe.result].crafted = item.crafted 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 end
function craftTask:cycle() function craftTask:cycle()
for _,key in pairs(Util.keys(context.craftingQueue)) do for _,key in pairs(Util.keys(context.craftingQueue)) do
local item = context.craftingQueue[key] local item = context.craftingQueue[key]
if item.requested - item.crafted > 0 then if item.requested - item.crafted > 0 then
local recipe = Craft.findRecipe(key) local recipe = Craft.findRecipe(key)
if recipe then if recipe then
if not item.notified then if not item.notified then
Sound.play('block.end_portal_frame.fill') Sound.play('block.end_portal_frame.fill')
item.notified = true item.notified = true
end end
self:craft(recipe, item) self:craft(recipe, item)
if item.crafted >= item.requested then if item.crafted >= item.requested then
item.status = 'crafted' item.status = 'crafted'
item.statusCode = Craft.STATUS_SUCCESS item.statusCode = Craft.STATUS_SUCCESS
if item.callback then if item.callback then
item.callback(item) -- invoke callback item.callback(item) -- invoke callback
end end
end end
else else
item.status = '(no recipe)' item.status = '(no recipe)'
item.statusCode = Craft.STATUS_ERROR item.statusCode = Craft.STATUS_ERROR
item.crafted = 0 item.crafted = 0
end end
end end
end end
end end
Milo:registerTask(craftTask) Milo:registerTask(craftTask)

View File

@@ -7,126 +7,126 @@ local Util = require('util')
local colors = _G.colors local colors = _G.colors
local craftPage = UI.Page { local craftPage = UI.Page {
titleBar = UI.TitleBar { }, titleBar = UI.TitleBar { },
wizard = UI.Wizard { wizard = UI.Wizard {
y = 2, ey = -2, y = 2, ey = -2,
pages = { pages = {
quantity = UI.WizardPage { quantity = UI.WizardPage {
index = 1, index = 1,
text = UI.Text { text = UI.Text {
x = 6, y = 3, x = 6, y = 3,
value = 'Quantity', value = 'Quantity',
}, },
count = UI.TextEntry { count = UI.TextEntry {
x = 15, y = 3, width = 10, x = 15, y = 3, width = 10,
limit = 6, limit = 6,
value = 1, value = 1,
}, },
ejectText = UI.Text { ejectText = UI.Text {
x = 6, y = 4, x = 6, y = 4,
value = 'Eject', value = 'Eject',
}, },
eject = UI.Chooser { eject = UI.Chooser {
x = 15, y = 4, width = 7, x = 15, y = 4, width = 7,
value = true, value = true,
nochoice = 'No', nochoice = 'No',
choices = { choices = {
{ name = 'Yes', value = true }, { name = 'Yes', value = true },
{ name = 'No', value = false }, { name = 'No', value = false },
}, },
}, },
}, },
resources = UI.WizardPage { resources = UI.WizardPage {
index = 2, index = 2,
grid = UI.ScrollingGrid { grid = UI.ScrollingGrid {
y = 2, ey = -2, y = 2, ey = -2,
columns = { columns = {
{ heading = 'Name', key = 'displayName' }, { heading = 'Name', key = 'displayName' },
{ heading = 'Total', key = 'total' , width = 5 }, { heading = 'Total', key = 'total' , width = 5 },
{ heading = 'Used', key = 'used' , width = 5 }, { heading = 'Used', key = 'used' , width = 5 },
{ heading = 'Need', key = 'need' , width = 5 }, { heading = 'Need', key = 'need' , width = 5 },
}, },
sortColumn = 'displayName', sortColumn = 'displayName',
}, },
}, },
}, },
}, },
} }
function craftPage:enable(item) function craftPage:enable(item)
self.item = item self.item = item
self:focusFirst() self:focusFirst()
self.titleBar.title = itemDB:getName(item) self.titleBar.title = itemDB:getName(item)
-- self.wizard.pages.quantity.eject.value = true -- self.wizard.pages.quantity.eject.value = true
UI.Page.enable(self) UI.Page.enable(self)
end end
function craftPage.wizard.pages.resources.grid:getDisplayValues(row) function craftPage.wizard.pages.resources.grid:getDisplayValues(row)
local function dv(v) local function dv(v)
return v == 0 and '' or Util.toBytes(v) return v == 0 and '' or Util.toBytes(v)
end end
row = Util.shallowCopy(row) row = Util.shallowCopy(row)
row.total = Util.toBytes(row.total) row.total = Util.toBytes(row.total)
row.used = dv(row.used) row.used = dv(row.used)
row.need = dv(row.need) row.need = dv(row.need)
return row return row
end end
function craftPage.wizard.pages.resources.grid:getRowTextColor(row, selected) function craftPage.wizard.pages.resources.grid:getRowTextColor(row, selected)
if row.need > 0 then if row.need > 0 then
return colors.orange return colors.orange
end end
return UI.Grid:getRowTextColor(row, selected) return UI.Grid:getRowTextColor(row, selected)
end end
function craftPage.wizard:eventHandler(event) function craftPage.wizard:eventHandler(event)
if event.type == 'nextView' then if event.type == 'nextView' then
local count = tonumber(self.pages.quantity.count.value) local count = tonumber(self.pages.quantity.count.value)
if not count or count <= 0 then if not count or count <= 0 then
self.pages.quantity.count.backgroundColor = colors.red self.pages.quantity.count.backgroundColor = colors.red
self.pages.quantity.count:draw() self.pages.quantity.count:draw()
return false return false
end end
self.pages.quantity.count.backgroundColor = colors.black self.pages.quantity.count.backgroundColor = colors.black
end end
return UI.Wizard.eventHandler(self, event) return UI.Wizard.eventHandler(self, event)
end end
function craftPage.wizard.pages.resources:enable() function craftPage.wizard.pages.resources:enable()
local items = Milo:listItems() local items = Milo:listItems()
local count = tonumber(self.parent.quantity.count.value) local count = tonumber(self.parent.quantity.count.value)
local recipe = Craft.findRecipe(craftPage.item) local recipe = Craft.findRecipe(craftPage.item)
if recipe then if recipe then
local ingredients = Craft.getResourceList4(recipe, items, count) local ingredients = Craft.getResourceList4(recipe, items, count)
for _,v in pairs(ingredients) do for _,v in pairs(ingredients) do
v.displayName = itemDB:getName(v) v.displayName = itemDB:getName(v)
end end
self.grid:setValues(ingredients) self.grid:setValues(ingredients)
else else
self.grid:setValues({ }) self.grid:setValues({ })
end end
return UI.WizardPage.enable(self) return UI.WizardPage.enable(self)
end end
function craftPage:eventHandler(event) function craftPage:eventHandler(event)
if event.type == 'cancel' then if event.type == 'cancel' then
UI:setPreviousPage() UI:setPreviousPage()
elseif event.type == 'accept' then elseif event.type == 'accept' then
local item = Util.shallowCopy(self.item) local item = Util.shallowCopy(self.item)
item.requested = tonumber(self.wizard.pages.quantity.count.value) item.requested = tonumber(self.wizard.pages.quantity.count.value)
item.forceCrafting = true item.forceCrafting = true
if self.wizard.pages.quantity.eject.value then if self.wizard.pages.quantity.eject.value then
item.callback = function(request) item.callback = function(request)
Milo:eject(item, request.requested) Milo:eject(item, request.requested)
end end
end end
Milo:requestCrafting(item) Milo:requestCrafting(item)
UI:setPreviousPage() UI:setPreviousPage()
else else
return UI.Page.eventHandler(self, event) return UI.Page.eventHandler(self, event)
end end
return true return true
end end
UI:addPage('craft', craftPage) 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 { local inputChestWizardPage = UI.WizardPage {
title = 'Input Chest', title = 'Input Chest',
index = 2, index = 2,
backgroundColor = colors.cyan, backgroundColor = colors.cyan,
[1] = UI.TextArea { [1] = UI.TextArea {
x = 2, ex = -2, y = 2, ey = -2, x = 2, ex = -2, y = 2, ey = -2,
value = string.format(template, Ansi.yellow, Ansi.reset), value = string.format(template, Ansi.yellow, Ansi.reset),
}, },
} }
function inputChestWizardPage:isValidType(node) function inputChestWizardPage:isValidType(node)
local m = device[node.name] local m = device[node.name]
return m and m.pullItems and { return m and m.pullItems and {
name = 'Input Chest', name = 'Input Chest',
value = 'input', value = 'input',
category = 'custom', category = 'custom',
help = 'Sends all items to storage', help = 'Sends all items to storage',
} }
end end
function inputChestWizardPage:isValidFor(node) function inputChestWizardPage:isValidFor(node)
return node.mtype == 'input' return node.mtype == 'input'
end end
UI:getPage('nodeWizard').wizard:add({ inputChest = inputChestWizardPage }) UI:getPage('nodeWizard').wizard:add({ inputChest = inputChestWizardPage })

View File

@@ -5,57 +5,57 @@ local Util = require('util')
local context = Milo:getContext() local context = Milo:getContext()
local page = UI.Page { local page = UI.Page {
titleBar = UI.TitleBar { titleBar = UI.TitleBar {
title = 'Item settings', title = 'Item settings',
previousPage = true, previousPage = true,
}, },
statusBar = UI.StatusBar { }, statusBar = UI.StatusBar { },
notification = UI.Notification { }, notification = UI.Notification { },
} }
function page:enable(item) function page:enable(item)
if not self.tabs then if not self.tabs then
table.sort(context.plugins.itemTab, function(a, b) return a.index < b.index end) table.sort(context.plugins.itemTab, function(a, b) return a.index < b.index end)
local t = Util.shallowCopy(context.plugins.itemTab) local t = Util.shallowCopy(context.plugins.itemTab)
t.y = 2 t.y = 2
t.ey = -2 t.ey = -2
self:add({ tabs = UI.Tabs(t) }) self:add({ tabs = UI.Tabs(t) })
end end
for _, v in pairs(context.plugins.itemTab) do for _, v in pairs(context.plugins.itemTab) do
if v.UIElement then if v.UIElement then
v:setItem(item) v:setItem(item)
end end
end end
self.tabs:selectTab(context.plugins.itemTab[1]) self.tabs:selectTab(context.plugins.itemTab[1])
UI.Page.enable(self) UI.Page.enable(self)
end end
function page:eventHandler(event) function page:eventHandler(event)
if event.type == 'tab_activate' then if event.type == 'tab_activate' then
event.activated:focusFirst() event.activated:focusFirst()
elseif event.type == 'form_invalid' then elseif event.type == 'form_invalid' then
self.notification:error(event.message) self.notification:error(event.message)
elseif event.type == 'focus_change' then elseif event.type == 'focus_change' then
self.statusBar:setStatus(event.focused.help) self.statusBar:setStatus(event.focused.help)
self.statusBar:draw() self.statusBar:draw()
elseif event.type == 'success_message' then elseif event.type == 'success_message' then
self.notification:success(event.message) self.notification:success(event.message)
elseif event.type == 'info_message' then elseif event.type == 'info_message' then
self.notification:info(event.message) self.notification:info(event.message)
elseif event.type == 'error_message' then elseif event.type == 'error_message' then
self.notification:error(event.message) self.notification:error(event.message)
else else
return UI.Page.eventHandler(self, event) return UI.Page.eventHandler(self, event)
end end
return true return true
end end
UI:addPage('item', page) UI:addPage('item', page)

View File

@@ -7,62 +7,62 @@ local colors = _G.colors
local context = Milo:getContext() local context = Milo:getContext()
local machinesTab = UI.Tab { local machinesTab = UI.Tab {
tabTitle = 'Machine', tabTitle = 'Machine',
index = 3, index = 3,
backgroundColor = colors.cyan, backgroundColor = colors.cyan,
grid = UI.ScrollingGrid { grid = UI.ScrollingGrid {
x = 2, ex = -2, y = 2, ey = -2, x = 2, ex = -2, y = 2, ey = -2,
disableHeader = true, disableHeader = true,
columns = { columns = {
{ heading = 'Name', key = 'displayName'}, { heading = 'Name', key = 'displayName'},
}, },
sortColumn = 'displayName', sortColumn = 'displayName',
help = 'Double-click to set machine', help = 'Double-click to set machine',
}, },
} }
function machinesTab:setItem(item) function machinesTab:setItem(item)
self.item = item self.item = item
local machine = Craft.machineLookup[self.item.key] local machine = Craft.machineLookup[self.item.key]
local t = Util.filter(context.storage.nodes, function(node) 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) 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 return node.adapter and node.adapter.online and node.adapter.pushItems
end end
end) end)
self.grid:setValues(t) self.grid:setValues(t)
if machine then if machine then
self.grid:setSelected('name', machine) self.grid:setSelected('name', machine)
end end
self.parent:setActive(self, item.has_recipe) self.parent:setActive(self, item.has_recipe)
end end
function machinesTab.grid:getDisplayValues(row) function machinesTab.grid:getDisplayValues(row)
row = Util.shallowCopy(row) row = Util.shallowCopy(row)
row.displayName = row.displayName or row.name row.displayName = row.displayName or row.name
return row return row
end end
function machinesTab.grid:getRowTextColor(row, selected) function machinesTab.grid:getRowTextColor(row, selected)
if row.name == Craft.machineLookup[self.parent.item.key] then if row.name == Craft.machineLookup[self.parent.item.key] then
return colors.yellow return colors.yellow
end end
return UI.Grid:getRowTextColor(row, selected) return UI.Grid:getRowTextColor(row, selected)
end end
function machinesTab:eventHandler(event) function machinesTab:eventHandler(event)
if event.type == 'grid_select' then if event.type == 'grid_select' then
if event.selected.name == Craft.machineLookup[self.item.key] then if event.selected.name == Craft.machineLookup[self.item.key] then
Craft.machineLookup[self.item.key] = nil Craft.machineLookup[self.item.key] = nil
else else
Craft.machineLookup[self.item.key] = event.selected.name Craft.machineLookup[self.item.key] = event.selected.name
end end
Util.writeTable(Craft.MACHINE_LOOKUP, Craft.machineLookup) Util.writeTable(Craft.MACHINE_LOOKUP, Craft.machineLookup)
self.grid:draw() self.grid:draw()
self:emit({ type = 'info_message', message = 'Saved' }) self:emit({ type = 'info_message', message = 'Saved' })
return true return true
end end
end end
return { itemTab = machinesTab } return { itemTab = machinesTab }

View File

@@ -7,91 +7,91 @@ local Util = require('util')
local context = Milo:getContext() local context = Milo:getContext()
local manageTab = UI.Tab { local manageTab = UI.Tab {
tabTitle = 'Manage', tabTitle = 'Manage',
index = 1, index = 1,
form = UI.Form { form = UI.Form {
x = 1, ex = -1, ey = -1, x = 1, ex = -1, ey = -1,
--manualControls = true, --manualControls = true,
[1] = UI.TextEntry { [1] = UI.TextEntry {
formLabel = 'Name', formKey = 'displayName', help = 'Override display name', formLabel = 'Name', formKey = 'displayName', help = 'Override display name',
shadowText = 'Display name', shadowText = 'Display name',
required = true, required = true,
limit = 120, limit = 120,
}, },
[2] = UI.TextEntry { [2] = UI.TextEntry {
width = 7, width = 7,
formLabel = 'Min', formKey = 'low', help = 'Craft if below min', formLabel = 'Min', formKey = 'low', help = 'Craft if below min',
validate = 'numeric', validate = 'numeric',
}, },
[3] = UI.TextEntry { [3] = UI.TextEntry {
width = 7, width = 7,
formLabel = 'Max', formKey = 'limit', help = 'Send to trash if above max', formLabel = 'Max', formKey = 'limit', help = 'Send to trash if above max',
validate = 'numeric', validate = 'numeric',
}, },
[4] = UI.Checkbox { [4] = UI.Checkbox {
formLabel = 'Ignore Dmg', formKey = 'ignoreDamage', formLabel = 'Ignore Dmg', formKey = 'ignoreDamage',
help = 'Ignore damage of item', help = 'Ignore damage of item',
}, },
[5] = UI.Checkbox { [5] = UI.Checkbox {
formLabel = 'Ignore NBT', formKey = 'ignoreNbtHash', formLabel = 'Ignore NBT', formKey = 'ignoreNbtHash',
help = 'Ignore NBT of item', help = 'Ignore NBT of item',
}, },
}, },
} }
function manageTab:setItem(item) function manageTab:setItem(item)
self.item = item self.item = item
self.res = Util.shallowCopy(context.resources[item.key] or { }) self.res = Util.shallowCopy(context.resources[item.key] or { })
self.res.displayName = self.item.displayName self.res.displayName = self.item.displayName
self.form:setValues(self.res) 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 end
function manageTab:eventHandler(event) function manageTab:eventHandler(event)
if event.type == 'form_cancel' then if event.type == 'form_cancel' then
UI:setPreviousPage() UI:setPreviousPage()
elseif event.type == 'form_complete' then elseif event.type == 'form_complete' then
if self.form:save() then if self.form:save() then
if self.res.displayName ~= self.item.displayName then if self.res.displayName ~= self.item.displayName then
self.item.displayName = self.res.displayName self.item.displayName = self.res.displayName
itemDB:add(self.item) itemDB:add(self.item)
itemDB:flush() itemDB:flush()
if context.storage.cache[self.item.key] then if context.storage.cache[self.item.key] then
context.storage.cache[self.item.key].displayName = self.res.displayName context.storage.cache[self.item.key].displayName = self.res.displayName
end end
--context.storage:setDirty() --context.storage:setDirty()
end end
self.res.displayName = nil self.res.displayName = nil
Map.prune(self.res, function(v) Map.prune(self.res, function(v)
if type(v) == 'boolean' then if type(v) == 'boolean' then
return v return v
elseif type(v) == 'string' then elseif type(v) == 'string' then
return #v > 0 return #v > 0
end end
return true return true
end) end)
local newKey = { local newKey = {
name = self.item.name, name = self.item.name,
damage = self.res.ignoreDamage and 0 or self.item.damage, damage = self.res.ignoreDamage and 0 or self.item.damage,
nbtHash = not self.res.ignoreNbtHash and self.item.nbtHash or nil, nbtHash = not self.res.ignoreNbtHash and self.item.nbtHash or nil,
} }
context.resources[self.item.key] = nil context.resources[self.item.key] = nil
if not Util.empty(self.res) then if not Util.empty(self.res) then
context.resources[itemDB:makeKey(newKey)] = self.res context.resources[itemDB:makeKey(newKey)] = self.res
end end
Milo:saveResources() Milo:saveResources()
UI:setPreviousPage() UI:setPreviousPage()
end end
else else
return return
end end
return true return true
end end
return { itemTab = manageTab } return { itemTab = manageTab }

View File

@@ -6,85 +6,89 @@ local UI = require('ui')
local colors = _G.colors local colors = _G.colors
local recipeTab = UI.Tab { local recipeTab = UI.Tab {
tabTitle = 'Recipe', tabTitle = 'Recipe',
index = 2, index = 2,
backgroundColor = colors.cyan, backgroundColor = colors.cyan,
grid = UI.ScrollingGrid { grid = UI.ScrollingGrid {
x = 2, ex = -2, y = 2, ey = -4, x = 2, ex = -2, y = 2, ey = -4,
disableHeader = true, disableHeader = true,
columns = { columns = {
{ heading = 'Slot', key = 'slot', width = 2 }, { heading = 'Slot', key = 'slot', width = 2 },
{ heading = 'Key', key = 'key' }, { heading = 'Count', key = 'count', width = 2 },
}, { heading = 'Key', key = 'key' },
sortColumn = 'slot', },
}, sortColumn = 'slot',
ignoreResultNBT = UI.Button { },
x = 2, y = -2, ignoreResultNBT = UI.Button {
text = 'Ignore Result NBT', event = 'ignore_result_nbt', x = 2, y = -2,
}, text = 'Ignore Result NBT', event = 'ignore_result_nbt',
ignoreNBT = UI.Button { },
x = -13, y = -2, ignoreNBT = UI.Button {
text = 'Ignore NBT', event = 'ignore_nbt', x = -13, y = -2,
}, text = 'Ignore NBT', event = 'ignore_nbt',
},
} }
function recipeTab:setItem(item) function recipeTab:setItem(item)
self.item = item self.item = item
self.recipe = Craft.findRecipe(self.item) self.recipe = Craft.findRecipe(self.item)
self.parent:setActive(self, self.recipe) self.parent:setActive(self, self.recipe)
local t = { } local t = { }
if self.recipe then if self.recipe then
for k, v in pairs(self.recipe.ingredients) do for k, v in Craft.ingedients(self.recipe) do
table.insert(t, { _syslog(k)
slot = k, _syslog(v)
key = v, table.insert(t, {
}) slot = k,
end key = v.key,
local key = itemDB:splitKey(self.recipe.result) count = v.count,
self.ignoreResultNBT.inactive = not key.nbtHash })
end end
self.grid:setValues(t) local key = itemDB:splitKey(self.recipe.result)
self.ignoreResultNBT.inactive = not key.nbtHash
end
self.grid:setValues(t)
end end
function recipeTab:eventHandler(event) function recipeTab:eventHandler(event)
if event.type == 'ignore_result_nbt' then if event.type == 'ignore_result_nbt' then
-- remove old entry -- remove old entry
Milo:updateRecipe(self.recipe.result) Milo:updateRecipe(self.recipe.result)
local item = itemDB:splitKey(self.recipe.result) local item = itemDB:splitKey(self.recipe.result)
item.nbtHash = nil item.nbtHash = nil
self.recipe.result = itemDB:makeKey(item) self.recipe.result = itemDB:makeKey(item)
-- add updated entry -- add updated entry
Milo:updateRecipe(self.recipe.result, self.recipe) Milo:updateRecipe(self.recipe.result, self.recipe)
self.ignoreResultNBT.inactive = true self.ignoreResultNBT.inactive = true
self:emit({ type = 'info_message', message = 'Recipe updated' }) self:emit({ type = 'info_message', message = 'Recipe updated' })
elseif event.type == 'grid_focus_row' then elseif event.type == 'grid_focus_row' then
local key = itemDB:splitKey(event.selected.key) local key = itemDB:splitKey(event.selected.key)
self.ignoreNBT.inactive = not key.nbtHash self.ignoreNBT.inactive = not key.nbtHash
self.ignoreNBT:draw() self.ignoreNBT:draw()
elseif event.type == 'ignore_nbt' then elseif event.type == 'ignore_nbt' then
local selected = self.grid:getSelected() local selected = self.grid:getSelected()
local item = itemDB:splitKey(selected.key) local item = itemDB:splitKey(selected.key)
item.nbtHash = nil item.nbtHash = nil
selected.key = itemDB:makeKey(item) selected.key = itemDB:makeKey(item)
self.grid:draw() self.grid:draw()
self.recipe.ingredients = { } self.recipe.ingredients = { }
for _, v in pairs(self.grid.values) do for _, v in pairs(self.grid.values) do
self.recipe.ingredients[v.slot] = v.key self.recipe.ingredients[v.slot] = v.key
end end
Milo:updateRecipe(self.recipe.result, self.recipe) Milo:updateRecipe(self.recipe.result, self.recipe)
self:emit({ type = 'info_message', message = 'Recipe updated' }) self:emit({ type = 'info_message', message = 'Recipe updated' })
return true return true
end end
end end
return { itemTab = recipeTab } return { itemTab = recipeTab }

View File

@@ -7,49 +7,49 @@ local colors = _G.colors
local context = Milo:getContext() local context = Milo:getContext()
local resetTab = UI.Tab { local resetTab = UI.Tab {
tabTitle = 'Reset', tabTitle = 'Reset',
index = 5, index = 5,
backgroundColor = colors.cyan, backgroundColor = colors.cyan,
textArea = UI.TextArea { textArea = UI.TextArea {
y = 2, ey = 6, y = 2, ey = 6,
textColor = colors.yellow, textColor = colors.yellow,
value = [[ Warning! value = [[ Warning!
This will clear all setting, This will clear all setting,
recipe, and machine for this item.]] recipe, and machine for this item.]]
}, },
resetButton = UI.Button { resetButton = UI.Button {
x = 17, y = 7, x = 17, y = 7,
event = 'reset', event = 'reset',
text = 'Reset', text = 'Reset',
help = 'Clear recipe and all settings', help = 'Clear recipe and all settings',
}, },
} }
function resetTab:setItem(item) function resetTab:setItem(item)
self.item = item self.item = item
end end
function resetTab:eventHandler(event) function resetTab:eventHandler(event)
if event.type == 'reset' then if event.type == 'reset' then
if context.userRecipes[self.item.key] then if context.userRecipes[self.item.key] then
Milo:updateRecipe(self.item.key, nil) Milo:updateRecipe(self.item.key, nil)
end end
if context.resources[self.item.key] then if context.resources[self.item.key] then
context.resources[self.item.key] = nil context.resources[self.item.key] = nil
Milo:saveResources() Milo:saveResources()
end end
if Craft.machineLookup[self.item.key] then if Craft.machineLookup[self.item.key] then
Craft.machineLookup[self.item.key] = nil Craft.machineLookup[self.item.key] = nil
Util.writeTable(Craft.MACHINE_LOOKUP, Craft.machineLookup) Util.writeTable(Craft.MACHINE_LOOKUP, Craft.machineLookup)
end end
UI:setPreviousPage() UI:setPreviousPage()
return true return true
end end
end end
return { itemTab = resetTab } return { itemTab = resetTab }

View File

@@ -13,224 +13,224 @@ local os = _G.os
--[[ Configuration Screen ]] --[[ Configuration Screen ]]
local wizardPage = UI.WizardPage { local wizardPage = UI.WizardPage {
title = 'Crafting Monitor', title = 'Crafting Monitor',
index = 2, index = 2,
backgroundColor = colors.cyan, backgroundColor = colors.cyan,
[1] = UI.TextArea { [1] = UI.TextArea {
x = 2, ex = -2, y = 2, ey = 3, x = 2, ex = -2, y = 2, ey = 3,
marginRight = 0, marginRight = 0,
textColor = colors.yellow, textColor = colors.yellow,
value = 'Displays the crafting progress.' value = 'Displays the crafting progress.'
}, },
form = UI.Form { form = UI.Form {
x = 2, ex = -2, y = 4, ey = -2, x = 2, ex = -2, y = 4, ey = -2,
manualControls = true, manualControls = true,
[1] = UI.Chooser { [1] = UI.Chooser {
width = 9, width = 9,
formLabel = 'Font Size', formKey = 'textScale', formLabel = 'Font Size', formKey = 'textScale',
nochoice = 'Small', nochoice = 'Small',
choices = { choices = {
{ name = 'Small', value = .5 }, { name = 'Small', value = .5 },
{ name = 'Large', value = 1 }, { name = 'Large', value = 1 },
}, },
help = 'Adjust text scaling', help = 'Adjust text scaling',
}, },
}, },
} }
function wizardPage:setNode(node) function wizardPage:setNode(node)
self.form:setValues(node) self.form:setValues(node)
end end
function wizardPage:saveNode(node) function wizardPage:saveNode(node)
os.queueEvent('monitor_resize', node.name) os.queueEvent('monitor_resize', node.name)
end end
function wizardPage:validate() function wizardPage:validate()
return self.form:save() return self.form:save()
end end
function wizardPage:isValidType(node) function wizardPage:isValidType(node)
local m = device[node.name] local m = device[node.name]
return m and m.type == 'monitor' and { return m and m.type == 'monitor' and {
name = 'Crafting Monitor', name = 'Crafting Monitor',
value = 'jobs', value = 'jobs',
category = 'display', category = 'display',
help = 'Display crafting progress / jobs' help = 'Display crafting progress / jobs'
} }
end end
function wizardPage:isValidFor(node) function wizardPage:isValidFor(node)
return node.mtype == 'jobs' return node.mtype == 'jobs'
end end
UI:getPage('nodeWizard').wizard:add({ jobs = wizardPage }) UI:getPage('nodeWizard').wizard:add({ jobs = wizardPage })
--[[ Display ]] --[[ Display ]]
local function createPage(node) local function createPage(node)
local monitor = UI.Device { local monitor = UI.Device {
device = node.adapter, device = node.adapter,
textScale = node.textScale or .5, textScale = node.textScale or .5,
} }
function monitor:resize() function monitor:resize()
self.textScale = node.textScale or .5 self.textScale = node.textScale or .5
UI.Device.resize(self) UI.Device.resize(self)
end end
local page = UI.Page { local page = UI.Page {
parent = monitor, parent = monitor,
grid = UI.Grid { grid = UI.Grid {
--ey = -6, --ey = -6,
sortColumn = 'index', sortColumn = 'index',
columns = { columns = {
{ heading = 'Qty', key = 'remaining', width = 4 }, { heading = 'Qty', key = 'remaining', width = 4 },
{ heading = 'Crafting', key = 'displayName', }, { heading = 'Crafting', key = 'displayName', },
{ heading = 'Status', key = 'status', }, { heading = 'Status', key = 'status', },
{ heading = 'need', key = 'need', width = 4 }, { heading = 'need', key = 'need', width = 4 },
-- { heading = 'total', key = 'total', width = 4 }, -- { heading = 'total', key = 'total', width = 4 },
-- { heading = 'used', key = 'used', width = 4 }, -- { heading = 'used', key = 'used', width = 4 },
-- { heading = 'count', key = 'count', width = 4 }, -- { heading = 'count', key = 'count', width = 4 },
{ heading = 'crafted', key = 'crafted', width = 5 }, { heading = 'crafted', key = 'crafted', width = 5 },
-- { heading = 'Progress', key = 'progress', width = 8 }, -- { heading = 'Progress', key = 'progress', width = 8 },
}, },
headerBackgroundColor = colors.black, headerBackgroundColor = colors.black,
headerTextColor = colors.cyan, headerTextColor = colors.cyan,
headerHeight = 2, headerHeight = 2,
}, },
--[[ --[[
buttons = UI.Window { buttons = UI.Window {
y = -5, height = 5, y = -5, height = 5,
backgroundColor = colors.gray, backgroundColor = colors.gray,
prevButton = UI.Button { prevButton = UI.Button {
x = 2, y = 2, height = 3, width = 5, x = 2, y = 2, height = 3, width = 5,
event = 'previous', event = 'previous',
backgroundColor = colors.lightGray, backgroundColor = colors.lightGray,
text = ' < ' text = ' < '
}, },
cancelButton = UI.Button { cancelButton = UI.Button {
x = 8, y = 2, height = 3, ex = -8, x = 8, y = 2, height = 3, ex = -8,
event = 'cancel_job', event = 'cancel_job',
backgroundColor = colors.lightGray, backgroundColor = colors.lightGray,
text = 'Cancel Job' text = 'Cancel Job'
}, },
nextButton = UI.Button { nextButton = UI.Button {
x = -6, y = 2, height = 3, width = 5, x = -6, y = 2, height = 3, width = 5,
event = 'next', event = 'next',
backgroundColor = colors.lightGray, backgroundColor = colors.lightGray,
text = ' > ' text = ' > '
}, },
}, },
]] ]]
} }
function page:updateList(craftList) function page:updateList(craftList)
if not Milo:isCraftingPaused() then if not Milo:isCraftingPaused() then
local t = { } local t = { }
for _,v in pairs(craftList) do for _,v in pairs(craftList) do
table.insert(t, v) table.insert(t, v)
v.index = #t v.index = #t
for k2,v2 in pairs(v.ingredients or { }) do for k2,v2 in pairs(v.ingredients or { }) do
if v2.key ~= v.key --[[and v2.statusCode ]] then if v2.key ~= v.key --[[and v2.statusCode ]] then
table.insert(t, v2) table.insert(t, v2)
if not v2.displayName then if not v2.displayName then
v2.displayName = itemDB:getName(k2) v2.displayName = itemDB:getName(k2)
end end
v2.index = #t v2.index = #t
end end
end end
end end
self.grid:setValues(t) self.grid:setValues(t)
self.grid:update() self.grid:update()
self:draw() self:draw()
self:sync() self:sync()
end end
end end
function page.grid:getDisplayValues(row) function page.grid:getDisplayValues(row)
row = Util.shallowCopy(row) row = Util.shallowCopy(row)
if not row.displayName then if not row.displayName then
row.displayName = itemDB:getName(row) row.displayName = itemDB:getName(row)
end end
if row.requested then if row.requested then
row.remaining = math.max(0, row.requested - row.crafted) row.remaining = math.max(0, row.requested - row.crafted)
else else
row.displayName = ' ' .. row.displayName row.displayName = ' ' .. row.displayName
end end
--row.progress = string.format('%d/%d', row.crafted, row.count) --row.progress = string.format('%d/%d', row.crafted, row.count)
return row return row
end end
function page.grid:getRowTextColor(row, selected) function page.grid:getRowTextColor(row, selected)
local statusColor = { local statusColor = {
[ Craft.STATUS_ERROR ] = colors.red, [ Craft.STATUS_ERROR ] = colors.red,
[ Craft.STATUS_WARNING ] = colors.orange, [ Craft.STATUS_WARNING ] = colors.orange,
[ Craft.STATUS_INFO ] = colors.yellow, [ Craft.STATUS_INFO ] = colors.yellow,
[ Craft.STATUS_SUCCESS ] = colors.green, [ Craft.STATUS_SUCCESS ] = colors.green,
} }
return row.statusCode and statusColor[row.statusCode] or return row.statusCode and statusColor[row.statusCode] or
UI.Grid:getRowTextColor(row, selected) UI.Grid:getRowTextColor(row, selected)
end end
-- no sorting allowed -- no sorting allowed
function page:setInverseSort() end function page:setInverseSort() end
function page:setSortColumn() end function page:setSortColumn() end
function page:eventHandler(event) function page:eventHandler(event)
if event.type == 'cancel_job' then if event.type == 'cancel_job' then
Sound.play('entity.villager.no', .5) Sound.play('entity.villager.no', .5)
elseif event.type == 'next' then elseif event.type == 'next' then
self.grid:nextPage() self.grid:nextPage()
elseif event.type == 'previous' then elseif event.type == 'previous' then
self.grid:previousPage() self.grid:previousPage()
else else
return UI.Page.eventHandler(self, event) return UI.Page.eventHandler(self, event)
end end
Event.onTimeout(.1, function() Event.onTimeout(.1, function()
self:setFocus(self.grid) self:setFocus(self.grid)
self:sync() self:sync()
end) end)
return true return true
end end
UI:setPage(page) UI:setPage(page)
return page return page
end end
local pages = { } local pages = { }
Event.on({ 'milo_resume', 'milo_pause' }, function(_, reason) Event.on({ 'milo_resume', 'milo_pause' }, function(_, reason)
for node in context.storage:filterActive('jobs') do for node in context.storage:filterActive('jobs') do
local page = pages[node.name] local page = pages[node.name]
if page then if page then
if reason then if reason then
page.grid:clear() page.grid:clear()
page.grid:centeredWrite(math.ceil(page.grid.height / 2), reason.msg) page.grid:centeredWrite(math.ceil(page.grid.height / 2), reason.msg)
else else
page.grid:draw() page.grid:draw()
end end
page:sync() page:sync()
end end
end end
end) end)
--[[ Task ]] --[[ Task ]]
local task = { local task = {
name = 'job status', name = 'job status',
priority = 80, priority = 80,
} }
function task:cycle() function task:cycle()
for node in context.storage:filterActive('jobs') do for node in context.storage:filterActive('jobs') do
if not pages[node.name] then if not pages[node.name] then
pages[node.name] = createPage(node) pages[node.name] = createPage(node)
end end
pages[node.name]:updateList(context.craftingQueue) pages[node.name]:updateList(context.craftingQueue)
end end
end end
Milo:registerTask(task) Milo:registerTask(task)

View File

@@ -111,7 +111,14 @@ function pages.confirmation:validate()
} }
for k,v in pairs(inventory) do 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 end
Milo:saveMachineRecipe(recipe, result, machine.name) Milo:saveMachineRecipe(recipe, result, machine.name)

View File

@@ -8,100 +8,100 @@ local context = Milo:getContext()
local device = _G.device local device = _G.device
local page = UI.Page { local page = UI.Page {
titleBar = UI.TitleBar { title = 'Reassign Machine' }, titleBar = UI.TitleBar { title = 'Reassign Machine' },
grid = UI.ScrollingGrid { grid = UI.ScrollingGrid {
y = 2, ey = -4, y = 2, ey = -4,
values = context.storage.nodes, values = context.storage.nodes,
columns = { columns = {
{ key = 'suffix', width = 4, align = 'right' }, { key = 'suffix', width = 4, align = 'right' },
{ heading = 'Name', key = 'displayName' }, { heading = 'Name', key = 'displayName' },
{ heading = 'Type', key = 'mtype', width = 4 }, { heading = 'Type', key = 'mtype', width = 4 },
{ heading = 'Pri', key = 'priority', width = 3 }, { heading = 'Pri', key = 'priority', width = 3 },
}, },
sortColumn = 'displayName', sortColumn = 'displayName',
help = 'Select Node', help = 'Select Node',
}, },
accept = UI.Button { accept = UI.Button {
x = -9, y = -2, x = -9, y = -2,
event = 'grid_select', event = 'grid_select',
text = 'Accept', text = 'Accept',
}, },
cancel = UI.Button { cancel = UI.Button {
x = -18, y = -2, x = -18, y = -2,
event = 'cancel', event = 'cancel',
text = 'Cancel', text = 'Cancel',
}, },
accelerators = { accelerators = {
grid_select = 'nextView', grid_select = 'nextView',
}, },
notification = UI.Notification { }, notification = UI.Notification { },
} }
function page.grid:getDisplayValues(row) function page.grid:getDisplayValues(row)
row = Util.shallowCopy(row) row = Util.shallowCopy(row)
local t = { row.name:match(':(.+)_(%d+)$') } local t = { row.name:match(':(.+)_(%d+)$') }
if #t ~= 2 then if #t ~= 2 then
t = { row.name:match('(.+)_(%d+)$') } t = { row.name:match('(.+)_(%d+)$') }
end end
if t and #t == 2 then if t and #t == 2 then
row.name, row.suffix = table.unpack(t) row.name, row.suffix = table.unpack(t)
row.name = row.name .. '_' .. row.suffix row.name = row.name .. '_' .. row.suffix
end end
row.displayName = row.displayName or row.name row.displayName = row.displayName or row.name
return row return row
end end
function page.grid:getRowTextColor(row, selected) function page.grid:getRowTextColor(row, selected)
if row.mtype == 'ignore' then if row.mtype == 'ignore' then
return colors.lightGray return colors.lightGray
end end
return UI.Grid:getRowTextColor(row, selected) return UI.Grid:getRowTextColor(row, selected)
end end
function page:applyFilter() function page:applyFilter()
local t = Util.filter(context.storage.nodes, function(v) local t = Util.filter(context.storage.nodes, function(v)
return v.mtype == 'ignore' and device[v.name] return v.mtype == 'ignore' and device[v.name]
end) end)
self.grid:setValues(t) self.grid:setValues(t)
end end
function page:enable(machine) function page:enable(machine)
self.machine = machine self.machine = machine
self:applyFilter() self:applyFilter()
UI.Page.enable(self) UI.Page.enable(self)
end end
function page:eventHandler(event) function page:eventHandler(event)
if event.type == 'grid_select' then if event.type == 'grid_select' then
local target = self.grid:getSelected() local target = self.grid:getSelected()
if target then if target then
local adapter = target.adapter local adapter = target.adapter
local name = target.name local name = target.name
Util.merge(target, self.machine) Util.merge(target, self.machine)
target.adapter = adapter target.adapter = adapter
target.name = name target.name = name
context.storage.nodes[self.machine.name] = nil context.storage.nodes[self.machine.name] = nil
context.storage:saveConfiguration() context.storage:saveConfiguration()
for k,v in pairs(Craft.machineLookup) do for k,v in pairs(Craft.machineLookup) do
if v == self.machine.name then if v == self.machine.name then
Craft.machineLookup[k] = name Craft.machineLookup[k] = name
end end
Util.writeTable(Craft.MACHINE_LOOKUP, Craft.machineLookup) Util.writeTable(Craft.MACHINE_LOOKUP, Craft.machineLookup)
end end
UI:setPreviousPage() UI:setPreviousPage()
end end
elseif event.type == 'cancel' then elseif event.type == 'cancel' then
UI:setPreviousPage() UI:setPreviousPage()
else else
return UI.Page.eventHandler(self, event) return UI.Page.eventHandler(self, event)
end end
end end
UI:addPage('machineMover', page) UI:addPage('machineMover', page)

View File

@@ -14,29 +14,29 @@ Add all speed upgrades possible.
]] ]]
local wizardPage = UI.WizardPage { local wizardPage = UI.WizardPage {
title = 'Mass Storage', title = 'Mass Storage',
index = 2, index = 2,
backgroundColor = colors.cyan, backgroundColor = colors.cyan,
[1] = UI.TextArea { [1] = UI.TextArea {
x = 2, ex = -2, y = 2, ey = -2, x = 2, ex = -2, y = 2, ey = -2,
value = string.format(template, Ansi.red, Ansi.reset), value = string.format(template, Ansi.red, Ansi.reset),
}, },
} }
function wizardPage:isValidFor(node) function wizardPage:isValidFor(node)
if node.mtype == 'storage' then if node.mtype == 'storage' then
local m = device[node.name] local m = device[node.name]
return m and m.listAvailableItems return m and m.listAvailableItems
end end
end end
function wizardPage:setNode(node) function wizardPage:setNode(node)
self.node = node self.node = node
end end
function wizardPage:validate() function wizardPage:validate()
self.node.adapterType = 'massAdapter' self.node.adapterType = 'massAdapter'
return true return true
end end
-- disable until a way is found to transfer between 2 non-transferrable nodes -- 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 context = args[1]
local function learn() local function learn()
context:sendRequest({ context:sendRequest({
request = 'craft', request = 'craft',
slot = 15, slot = 15,
}) })
end end
context.responseHandlers['craft'] = function(response) context.responseHandlers['craft'] = function(response)
if response.success then if response.success then
Sound.play('entity.item.pickup') Sound.play('entity.item.pickup')
else else
Sound.play('entity.villager.no') Sound.play('entity.villager.no')
end end
end end
return { return {

View File

@@ -9,36 +9,36 @@ local context = args[1]
local SHIELD_SLOT = 2 local SHIELD_SLOT = 2
Event.addRoutine(function() Event.addRoutine(function()
local lastTransfer local lastTransfer
while true do while true do
local sleepTime = 1.5 local sleepTime = 1.5
if lastTransfer and os.clock() - lastTransfer < 2 then if lastTransfer and os.clock() - lastTransfer < 2 then
sleepTime = .1 sleepTime = .1
end end
os.sleep(context.socket and sleepTime or 5) 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 if context.state.deposit and context.state.server and (context.state.useShield or context.state.slot) then
local neural = device.neuralInterface local neural = device.neuralInterface
local inv = context.state.useShield and 'getEquipment' or 'getInventory' local inv = context.state.useShield and 'getEquipment' or 'getInventory'
if neural and neural[inv] then if neural and neural[inv] then
local s, m = pcall(function() local s, m = pcall(function()
local method = neural[inv] local method = neural[inv]
local item = method and method().list()[context.state.useShield and SHIELD_SLOT or context.state.slot] local item = method and method().list()[context.state.useShield and SHIELD_SLOT or context.state.slot]
if item then if item then
if context:sendRequest({ if context:sendRequest({
request = 'deposit', request = 'deposit',
source = context.state.useShield and 'equipment' or 'inventory', source = context.state.useShield and 'equipment' or 'inventory',
slot = context.state.useShield and SHIELD_SLOT or context.state.slot, slot = context.state.useShield and SHIELD_SLOT or context.state.slot,
count = item.count, count = item.count,
}) then }) then
lastTransfer = os.clock() lastTransfer = os.clock()
end end
end end
end) end)
if not s and m then if not s and m then
_G._syslog(m) _G._syslog(m)
end end
end end
end end
end end
end) end)

View File

@@ -10,70 +10,70 @@ local context = args[1]
local ni = peripheral.find('neuralInterface') local ni = peripheral.find('neuralInterface')
if not context.state.depositAll then if not context.state.depositAll then
context.state.depositAll = { } context.state.depositAll = { }
end end
if not context.state.depositAll.retain then if not context.state.depositAll.retain then
context.state.depositAll.retain = { } context.state.depositAll.retain = { }
end end
local page = UI.Page { local page = UI.Page {
titleBar = UI.TitleBar { titleBar = UI.TitleBar {
backgroundColor = colors.gray, backgroundColor = colors.gray,
title = 'Deposit full inventory', title = 'Deposit full inventory',
previousPage = true, previousPage = true,
}, },
items = UI.ScrollingGrid { items = UI.ScrollingGrid {
x = 2, ex = -2, y = 2, ey = -4, x = 2, ex = -2, y = 2, ey = -4,
columns = { columns = {
{ heading = 'Qty', key = 'count', width = 3 }, { heading = 'Qty', key = 'count', width = 3 },
{ heading = 'Name', key = 'displayName', }, { heading = 'Name', key = 'displayName', },
}, },
sortColumn = 'count', sortColumn = 'count',
inverseSort = true inverseSort = true
}, },
form = UI.Form { form = UI.Form {
x = 2, ex = -2, y = -2, ey = -2, x = 2, ex = -2, y = -2, ey = -2,
margin = 1, margin = 1,
[1] = UI.Checkbox { [1] = UI.Checkbox {
formLabel = 'Include hotbar', formKey = 'includeHotbar', formLabel = 'Include hotbar', formKey = 'includeHotbar',
help = 'Also send the contents of the hotbar to Milo (excluding the neural connector)' help = 'Also send the contents of the hotbar to Milo (excluding the neural connector)'
} }
}, },
notification = UI.Notification(), notification = UI.Notification(),
} }
local function makeKey(item) -- group items regardless of damage local function makeKey(item) -- group items regardless of damage
local damage = item.maxDamage == 0 and item.damage local damage = item.maxDamage == 0 and item.damage
return itemDB:makeKey({ name = item.name, damage = damage }) return itemDB:makeKey({ name = item.name, damage = damage })
end end
function page:updateInventoryList() function page:updateInventoryList()
local inv = ni.getInventory().list() local inv = ni.getInventory().list()
local list = { } local list = { }
for slot, item in pairs(inv) do for slot, item in pairs(inv) do
if (context.state.depositAll.includeHotbar or slot > 9) and item.name ~= 'plethora:neuralconnector' then 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) item = itemDB:get(item, function() return ni.getInventory().getItemMeta(slot) end)
local key = makeKey(item) local key = makeKey(item)
if not list[key] then if not list[key] then
item.displayName = item.displayName:match('(.+) %(damage:.+%)') or item.displayName item.displayName = item.displayName:match('(.+) %(damage:.+%)') or item.displayName
list[key] = item list[key] = item
else else
list[key].count = list[key].count + item.count list[key].count = list[key].count + item.count
end end
list[key].key = key list[key].key = key
end end
end end
self.items:setValues(list) self.items:setValues(list)
self.items:draw() self.items:draw()
itemDB:flush() itemDB:flush()
end end
function page:enable() function page:enable()
self.form:setValues(context.state.depositAll) self.form:setValues(context.state.depositAll)
self:updateInventoryList() self:updateInventoryList()
UI.Page.enable(self) UI.Page.enable(self)
end end
function page.items:getRowTextColor(row) function page.items:getRowTextColor(row)
@@ -84,57 +84,57 @@ function page.items:getRowTextColor(row)
end end
function page:depositAll() 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 for slot, item in pairs(inv) do
item = itemDB:get(item, function() return ni.getInventory().getItemMeta(slot) end) item = itemDB:get(item, function() return ni.getInventory().getItemMeta(slot) end)
local key = makeKey(item) local key = makeKey(item)
if not context.state.depositAll.retain[key] then if not context.state.depositAll.retain[key] then
if (context.state.depositAll.includeHotbar or slot > 9) and item.name ~= 'plethora:neuralconnector' then if (context.state.depositAll.includeHotbar or slot > 9) and item.name ~= 'plethora:neuralconnector' then
context:sendRequest({ context:sendRequest({
request = 'deposit', request = 'deposit',
source = 'inventory', source = 'inventory',
slot = slot, slot = slot,
count = item.count, count = item.count,
}) })
end end
end end
end end
end end
function page:eventHandler(event) function page:eventHandler(event)
if event.type == 'checkbox_change' and event.element.formKey == 'includeHotbar' then if event.type == 'checkbox_change' and event.element.formKey == 'includeHotbar' then
context.state.depositAll.includeHotbar = event.checked context.state.depositAll.includeHotbar = event.checked
page:updateInventoryList() page:updateInventoryList()
elseif event.type == 'grid_select' then elseif event.type == 'grid_select' then
local key = event.selected.key local key = event.selected.key
if context.state.depositAll.retain[key] then if context.state.depositAll.retain[key] then
context.state.depositAll.retain[key] = nil context.state.depositAll.retain[key] = nil
else else
context.state.depositAll.retain[key] = true context.state.depositAll.retain[key] = true
end end
context:setState('depositAll', context.state.depositAll) context:setState('depositAll', context.state.depositAll)
self.items:draw() self.items:draw()
elseif event.type == 'form_complete' then elseif event.type == 'form_complete' then
Config.update('miloRemote', context.state) Config.update('miloRemote', context.state)
page:depositAll() page:depositAll()
UI:setPreviousPage() UI:setPreviousPage()
elseif event.type == 'form_cancel' then elseif event.type == 'form_cancel' then
UI:setPreviousPage() UI:setPreviousPage()
else else
return UI.Page.eventHandler(self, event) return UI.Page.eventHandler(self, event)
end end
end end
return { return {
menuItem = 'Deposit all', menuItem = 'Deposit all',
callback = function() callback = function()
UI:setPage(page) UI:setPage(page)
end, end,
} }

View File

@@ -16,13 +16,13 @@ local page = UI.Page {
title = 'Auto-feeder', title = 'Auto-feeder',
previousPage = true, previousPage = true,
}, },
grid = UI.ScrollingGrid { grid = UI.ScrollingGrid {
y = 2, ey = -2, y = 2, ey = -2,
columns = { columns = {
{ heading = 'Name', key = 'displayName' }, { heading = 'Name', key = 'displayName' },
}, },
sortColumn = 'displayName', sortColumn = 'displayName',
}, },
statusBar = UI.StatusBar { statusBar = UI.StatusBar {
values = 'Double-click to toggle' values = 'Double-click to toggle'
}, },
@@ -55,45 +55,45 @@ function page.grid:getRowTextColor(row)
end end
local function getFood(food) local function getFood(food)
for slot,v in pairs(ni.getInventory().list()) do for slot,v in pairs(ni.getInventory().list()) do
local key = itemDB:makeKey(v) local key = itemDB:makeKey(v)
if key == food then if key == food then
local item = ni.getInventory().getItem(slot) local item = ni.getInventory().getItem(slot)
if item and item.consume then if item and item.consume then
return item return item
end end
break break
end end
end end
end end
function page:eventHandler(event) function page:eventHandler(event)
if event.type == 'grid_select' then if event.type == 'grid_select' then
if context.state.food == event.selected.key then if context.state.food == event.selected.key then
context:setState('food') context:setState('food')
self.grid:draw() self.grid:draw()
elseif getFood(event.selected.key) then elseif getFood(event.selected.key) then
context:setState('food', event.selected.key) context:setState('food', event.selected.key)
self.grid:draw() self.grid:draw()
else else
Sound.play('entity.villager.no') Sound.play('entity.villager.no')
end end
return true return true
end end
end end
Event.onInterval(5, function() Event.onInterval(5, function()
local s, m = pcall(function() -- prevent errors from some mod items local s, m = pcall(function() -- prevent errors from some mod items
if context.state.food and ni.getMetaOwner().food.hungry then if context.state.food and ni.getMetaOwner().food.hungry then
local item = getFood(context.state.food) local item = getFood(context.state.food)
if item then if item then
item.consume() item.consume()
end end
end end
end) end)
if not s and m then if not s and m then
_G._syslog(m) _G._syslog(m)
end end
end) end)
return { return {

View File

@@ -10,78 +10,78 @@ local STARTUP_FILE = 'usr/autorun/miloRemote.lua'
local context = ({ ... })[1] local context = ({ ... })[1]
local setup = UI.SlideOut { local setup = UI.SlideOut {
backgroundColor = colors.cyan, backgroundColor = colors.cyan,
titleBar = UI.TitleBar { titleBar = UI.TitleBar {
title = 'Remote Setup', title = 'Remote Setup',
}, },
form = UI.Form { form = UI.Form {
x = 2, ex = -2, y = 2, ey = -1, x = 2, ex = -2, y = 2, ey = -1,
[1] = UI.TextEntry { [1] = UI.TextEntry {
formLabel = 'Server', formKey = 'server', formLabel = 'Server', formKey = 'server',
help = 'ID for the server', help = 'ID for the server',
shadowText = 'Milo server ID', shadowText = 'Milo server ID',
limit = 6, limit = 6,
validate = 'numeric', validate = 'numeric',
required = true, required = true,
}, },
[2] = UI.TextEntry { [2] = UI.TextEntry {
formLabel = 'Return Slot', formKey = 'slot', formLabel = 'Return Slot', formKey = 'slot',
help = 'Use a slot for sending to storage', help = 'Use a slot for sending to storage',
shadowText = 'Inventory slot #', shadowText = 'Inventory slot #',
limit = 5, limit = 5,
validate = 'numeric', validate = 'numeric',
required = false, required = false,
}, },
[3] = UI.Checkbox { [3] = UI.Checkbox {
formLabel = 'Shield Slot', formKey = 'useShield', formLabel = 'Shield Slot', formKey = 'useShield',
help = 'Or, use the shield slot for sending' help = 'Or, use the shield slot for sending'
}, },
[4] = UI.Checkbox { [4] = UI.Checkbox {
formLabel = 'Run on startup', formKey = 'runOnStartup', formLabel = 'Run on startup', formKey = 'runOnStartup',
help = 'Run this program on startup' help = 'Run this program on startup'
}, },
info = UI.TextArea { info = UI.TextArea {
x = 1, ex = -1, y = 6, ey = -4, x = 1, ex = -1, y = 6, ey = -4,
textColor = colors.yellow, textColor = colors.yellow,
marginLeft = 0, marginLeft = 0,
marginRight = 0, marginRight = 0,
value = [[The Milo turtle must connect to a manipulator with a ]] .. value = [[The Milo turtle must connect to a manipulator with a ]] ..
[[bound introspection module. The neural interface must ]] .. [[bound introspection module. The neural interface must ]] ..
[[also have an introspection module.]], [[also have an introspection module.]],
}, },
}, },
statusBar = UI.StatusBar { statusBar = UI.StatusBar {
backgroundColor = colors.cyan, backgroundColor = colors.cyan,
}, },
} }
function setup:eventHandler(event) function setup:eventHandler(event)
if event.type == 'focus_change' then if event.type == 'focus_change' then
self.statusBar:setStatus(event.focused.help) self.statusBar:setStatus(event.focused.help)
elseif event.type == 'form_complete' then elseif event.type == 'form_complete' then
Config.update('miloRemote', context.state) Config.update('miloRemote', context.state)
self:hide() self:hide()
context.page:refresh('list') context.page:refresh('list')
context.page.grid:draw() context.page.grid:draw()
context.page:setFocus(context.page.statusBar.filter) context.page:setFocus(context.page.statusBar.filter)
if context.state.runOnStartup then if context.state.runOnStartup then
if not fs.exists(STARTUP_FILE) then if not fs.exists(STARTUP_FILE) then
Util.writeFile(STARTUP_FILE, Util.writeFile(STARTUP_FILE,
[[os.sleep(1) [[os.sleep(1)
shell.openForegroundTab('packages/milo/MiloRemote')]]) shell.openForegroundTab('packages/milo/MiloRemote')]])
end end
elseif fs.exists(STARTUP_FILE) then elseif fs.exists(STARTUP_FILE) then
fs.delete(STARTUP_FILE) fs.delete(STARTUP_FILE)
end end
elseif event.type == 'form_cancel' then elseif event.type == 'form_cancel' then
self:hide() self:hide()
context.page:setFocus(context.page.statusBar.filter) context.page:setFocus(context.page.statusBar.filter)
end end
return UI.SlideOut.eventHandler(self, event) return UI.SlideOut.eventHandler(self, event)
end end
context.page:add({ setup = setup }) context.page:add({ setup = setup })

View File

@@ -5,42 +5,42 @@ local context = Milo:getContext()
local device = _G.device local device = _G.device
local function craftHandler(user, message, socket) local function craftHandler(user, message, socket)
local function craft() local function craft()
local slots = { local slots = {
[1] = 1, [2] = 2, [3] = 3, [1] = 1, [2] = 2, [3] = 3,
[5] = 10, [6] = 11, [7] = 12, [5] = 10, [6] = 11, [7] = 12,
[9] = 19, [10] = 20, [11] = 21, [9] = 19, [10] = 20, [11] = 21,
} }
local inventory = device[user .. ':inventory'] local inventory = device[user .. ':inventory']
if inventory then if inventory then
for k, v in pairs(slots) do for k, v in pairs(slots) do
inventory.pushItems(context.turtleInventory.name, v + message.slot - 1, 1, k) inventory.pushItems(context.turtleInventory.name, v + message.slot - 1, 1, k)
end end
local recipe, msg = Milo:learnRecipe() local recipe, msg = Milo:learnRecipe()
if recipe then if recipe then
socket:write({ socket:write({
type = 'craft', type = 'craft',
msg = 'Learned: ' .. itemDB:getName(recipe), msg = 'Learned: ' .. itemDB:getName(recipe),
success = true, success = true,
}) })
for k,v in pairs(context.turtleInventory.adapter.list()) do for k,v in pairs(context.turtleInventory.adapter.list()) do
inventory.pullItems(context.turtleInventory.name, k, v.count) inventory.pullItems(context.turtleInventory.name, k, v.count)
end end
else else
socket:write({ socket:write({
type = 'craft', type = 'craft',
msg = msg, msg = msg,
}) })
for k, v in pairs(slots) do for k, v in pairs(slots) do
inventory.pullItems(context.turtleInventory.name, k, 1, v + message.slot - 1) inventory.pullItems(context.turtleInventory.name, k, 1, v + message.slot - 1)
end end
end end
end end
end end
Milo:queueRequest({ }, craft) Milo:queueRequest({ }, craft)
end end
return { 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 Milo = require('milo')
local ReplenishTask = { local ReplenishTask = {
name = 'replenish', name = 'replenish',
priority = 60, priority = 60,
} }
function ReplenishTask:cycle(context) function ReplenishTask:cycle(context)
for k,res in pairs(context.resources) do for k,res in pairs(context.resources) do
if res.low then if res.low then
local item = itemDB:splitKey(k) local item = itemDB:splitKey(k)
item.key = k item.key = k
local _, count = Milo:getMatches(item, res) local _, count = Milo:getMatches(item, res)
if count < res.low then if count < res.low then
local nbtHash local nbtHash
if not res.ignoreNbtHash then if not res.ignoreNbtHash then
nbtHash = item.nbtHash nbtHash = item.nbtHash
end end
Milo:requestCrafting({ Milo:requestCrafting({
name = item.name, name = item.name,
damage = res.ignoreDamage and 0 or item.damage, damage = res.ignoreDamage and 0 or item.damage,
nbtHash = nbtHash, nbtHash = nbtHash,
requested = res.low - count, requested = res.low - count,
count = count, count = count,
replenish = true, replenish = true,
}) })
else else
local request = context.craftingQueue[itemDB:makeKey(item)] local request = context.craftingQueue[itemDB:makeKey(item)]
if request and request.replenish then if request and request.replenish then
--request.count = request.crafted --request.count = request.crafted
end end
end end
end end
end end
end end
Milo:registerTask(ReplenishTask) Milo:registerTask(ReplenishTask)

View File

@@ -8,66 +8,66 @@ local context = Milo:getContext()
local speakerNode = context.storage:getSingleNode('speaker') local speakerNode = context.storage:getSingleNode('speaker')
if speakerNode then if speakerNode then
Sound.setVolume(speakerNode.volume) Sound.setVolume(speakerNode.volume)
end end
local wizardPage = UI.WizardPage { local wizardPage = UI.WizardPage {
title = 'Speaker', title = 'Speaker',
index = 2, index = 2,
backgroundColor = colors.cyan, backgroundColor = colors.cyan,
[1] = UI.Text { [1] = UI.Text {
x = 2, y = 2, x = 2, y = 2,
textColor = colors.yellow, textColor = colors.yellow,
value = 'Set the volume for sound effects', value = 'Set the volume for sound effects',
}, },
form = UI.Form { form = UI.Form {
x = 2, ex = -2, y = 3, ey = -2, x = 2, ex = -2, y = 3, ey = -2,
manualControls = true, manualControls = true,
volume = UI.TextEntry { volume = UI.TextEntry {
formLabel = 'Volume', formKey = 'volume', formLabel = 'Volume', formKey = 'volume',
width = 5, limit = 3, width = 5, limit = 3,
validate = 'numeric', validate = 'numeric',
help = 'A value from 0 (mute) to 1 (loud)', help = 'A value from 0 (mute) to 1 (loud)',
}, },
testSound = UI.Button { testSound = UI.Button {
x = 15, y = 2, x = 15, y = 2,
text = 'Test', event = 'test_sound', text = 'Test', event = 'test_sound',
help = 'Test sound volume', help = 'Test sound volume',
}, },
}, },
} }
function wizardPage:setNode(node) function wizardPage:setNode(node)
self.form:setValues(node) self.form:setValues(node)
end end
function wizardPage:saveNode(node) function wizardPage:saveNode(node)
Sound.setVolume(node.volume) Sound.setVolume(node.volume)
end end
function wizardPage:validate() function wizardPage:validate()
return self.form:save() return self.form:save()
end end
function wizardPage:isValidType(node) function wizardPage:isValidType(node)
local m = device[node.name] local m = device[node.name]
return m and m.type == 'speaker' and { return m and m.type == 'speaker' and {
name = 'Speaker', name = 'Speaker',
value = 'speaker', value = 'speaker',
category = 'custom', category = 'custom',
help = 'Sound effects', help = 'Sound effects',
} }
end end
function wizardPage:isValidFor(node) function wizardPage:isValidFor(node)
return node.mtype == 'speaker' return node.mtype == 'speaker'
end end
function wizardPage:eventHandler(event) function wizardPage:eventHandler(event)
if event.type == 'test_sound' then if event.type == 'test_sound' then
local vol = tonumber(self.form.volume.value) local vol = tonumber(self.form.volume.value)
Sound.play('entity.item.pickup', vol) Sound.play('entity.item.pickup', vol)
end end
end end
UI:getPage('nodeWizard').wizard:add({ speaker = wizardPage }) 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.]] Right-clicking on the activity monitor will reset the totals.]]
local wizardPage = UI.WizardPage { local wizardPage = UI.WizardPage {
title = 'Status Monitor', title = 'Status Monitor',
index = 2, index = 2,
backgroundColor = colors.cyan, backgroundColor = colors.cyan,
[1] = UI.TextArea { [1] = UI.TextArea {
x = 2, ex = -2, y = 2, ey = 6, x = 2, ex = -2, y = 2, ey = 6,
marginRight = 0, marginRight = 0,
value = string.format(template, Ansi.yellow, Ansi.reset), value = string.format(template, Ansi.yellow, Ansi.reset),
}, },
form = UI.Form { form = UI.Form {
x = 2, ex = -2, y = 7, ey = -2, x = 2, ex = -2, y = 7, ey = -2,
manualControls = true, manualControls = true,
[1] = UI.Chooser { [1] = UI.Chooser {
width = 9, width = 9,
formLabel = 'Font Size', formKey = 'textScale', formLabel = 'Font Size', formKey = 'textScale',
nochoice = 'Small', nochoice = 'Small',
choices = { choices = {
{ name = 'Small', value = .5 }, { name = 'Small', value = .5 },
{ name = 'Large', value = 1 }, { name = 'Large', value = 1 },
}, },
help = 'Adjust text scaling', help = 'Adjust text scaling',
}, },
}, },
} }
function wizardPage:setNode(node) function wizardPage:setNode(node)
self.form:setValues(node) self.form:setValues(node)
end end
function wizardPage:validate() function wizardPage:validate()
return self.form:save() return self.form:save()
end end
function wizardPage:saveNode(node) function wizardPage:saveNode(node)
os.queueEvent('monitor_resize', node.name) os.queueEvent('monitor_resize', node.name)
end end
function wizardPage:isValidType(node) function wizardPage:isValidType(node)
local m = device[node.name] local m = device[node.name]
return m and m.type == 'monitor' and { return m and m.type == 'monitor' and {
name = 'Status Monitor', name = 'Status Monitor',
value = 'status', value = 'status',
category = 'display', category = 'display',
help = 'Display storage status' help = 'Display storage status'
} }
end end
function wizardPage:isValidFor(node) function wizardPage:isValidFor(node)
return node.mtype == 'status' return node.mtype == 'status'
end end
UI:getPage('nodeWizard').wizard:add({ statusMonitor = wizardPage }) UI:getPage('nodeWizard').wizard:add({ statusMonitor = wizardPage })
--[[ Display ]]-- --[[ Display ]]--
local function createPage(node) local function createPage(node)
local monitor = UI.Device { local monitor = UI.Device {
device = node.adapter, device = node.adapter,
textScale = node.textScale or .5, textScale = node.textScale or .5,
} }
function monitor:resize() function monitor:resize()
self.textScale = node.textScale or .5 self.textScale = node.textScale or .5
UI.Device.resize(self) UI.Device.resize(self)
end end
local page = UI.Page { local page = UI.Page {
parent = monitor, parent = monitor,
tabs = UI.Tabs { tabs = UI.Tabs {
[1] = UI.Tab { [1] = UI.Tab {
tabTitle = 'Overview', tabTitle = 'Overview',
backgroundColor = colors.black, backgroundColor = colors.black,
onlineLabel = UI.Text { onlineLabel = UI.Text {
x = 2, y = 2, x = 2, y = 2,
value = 'Storage Status', value = 'Storage Status',
}, },
onlineText = UI.Text { onlineText = UI.Text {
x = 18, ex = -2, y = 2, x = 18, ex = -2, y = 2,
}, },
tpsLabel = UI.Text { tpsLabel = UI.Text {
x = 2, y = 3, x = 2, y = 3,
value = 'Tasks/sec', value = 'Tasks/sec',
}, },
tpsText = UI.Text { tpsText = UI.Text {
x = 18, ex = -2, y = 3, x = 18, ex = -2, y = 3,
}, },
tasksLabel = UI.Text { tasksLabel = UI.Text {
x = -18, y = 3, x = -18, y = 3,
value = 'Proc time', value = 'Proc time',
}, },
tasksText = UI.Text { tasksText = UI.Text {
x = -6, ex = -2, y = 3, x = -6, ex = -2, y = 3,
align = 'right', align = 'right',
}, },
storageLabel = UI.Text { storageLabel = UI.Text {
x = 2, ex = -1, y = 6, x = 2, ex = -1, y = 6,
}, },
storage = UI.ProgressBar { storage = UI.ProgressBar {
x = 2, ex = -2, y = 7, height = 3, x = 2, ex = -2, y = 7, height = 3,
}, },
unlockedLabel = UI.Text { unlockedLabel = UI.Text {
x = 2, ex = -1, y = 12, x = 2, ex = -1, y = 12,
}, },
unlocked = UI.ProgressBar { unlocked = UI.ProgressBar {
x = 2, ex = -2, y = 13, height = 3, x = 2, ex = -2, y = 13, height = 3,
}, },
craftingLabel = UI.Text { craftingLabel = UI.Text {
x = 2, ex = -1, y = 18, x = 2, ex = -1, y = 18,
value = 'Crafting Status', value = 'Crafting Status',
}, },
crafting = UI.ProgressBar { crafting = UI.ProgressBar {
x = 2, ex = -2, y = 19, height = 3, x = 2, ex = -2, y = 19, height = 3,
value = 100, value = 100,
}, },
}, },
[2] = UI.Tab { [2] = UI.Tab {
tabTitle = 'Stats', tabTitle = 'Stats',
textArea = UI.TextArea { textArea = UI.TextArea {
y = 3, y = 3,
}, },
}, },
[3] = UI.Tab { [3] = UI.Tab {
tabTitle = 'Storage', tabTitle = 'Storage',
grid = UI.ScrollingGrid { grid = UI.ScrollingGrid {
y = 2, y = 2,
columns = { columns = {
{ heading = 'Name', key = 'name' }, { heading = 'Name', key = 'name' },
{ heading = 'Size', key = 'size', width = 5 }, { heading = 'Size', key = 'size', width = 5 },
{ heading = 'Used', key = 'used', width = 5 }, { heading = 'Used', key = 'used', width = 5 },
{ heading = 'Perc', key = 'perc', width = 5 }, { heading = 'Perc', key = 'perc', width = 5 },
-- TODO: add % to each number -- TODO: add % to each number
}, },
sortColumn = 'name', sortColumn = 'name',
}, },
}, },
[4] = UI.Tab { [4] = UI.Tab {
tabTitle = 'Offline', tabTitle = 'Offline',
grid = UI.ScrollingGrid { grid = UI.ScrollingGrid {
y = 2, y = 2,
columns = { columns = {
{ heading = 'Name', key = 'name' }, { heading = 'Name', key = 'name' },
}, },
sortColumn = 'name', sortColumn = 'name',
}, },
}, },
[5] = UI.Tab { [5] = UI.Tab {
tabTitle = 'Activity', tabTitle = 'Activity',
term = UI.Embedded { term = UI.Embedded {
--visible = true, --visible = true,
}, },
}, },
[6] = UI.Tab { [6] = UI.Tab {
tabTitle = 'Tasks', tabTitle = 'Tasks',
grid = UI.ScrollingGrid { grid = UI.ScrollingGrid {
y = 2, y = 2,
values = context.tasks, values = context.tasks,
columns = { columns = {
{ heading = 'Priority', key = 'priority', width = 5 }, { heading = 'Priority', key = 'priority', width = 5 },
{ heading = 'Name', key = 'name' }, { heading = 'Name', key = 'name' },
{ heading = 'Avg', key = 'avg', width = 7, align = 'right' }, { heading = 'Avg', key = 'avg', width = 7, align = 'right' },
{ heading = '%', key = 'perc', width = 7, align = 'right' }, { heading = '%', key = 'perc', width = 7, align = 'right' },
}, },
sortColumn = 'priority', sortColumn = 'priority',
}, },
}, },
}, },
} }
local overviewTab = page.tabs[1] local overviewTab = page.tabs[1]
local statsTab = page.tabs[2] local statsTab = page.tabs[2]
local usageTab = page.tabs[3] local usageTab = page.tabs[3]
local stateTab = page.tabs[4] local stateTab = page.tabs[4]
local activityTab = page.tabs[5] local activityTab = page.tabs[5]
local taskTab = page.tabs[6] local taskTab = page.tabs[6]
local function getStorageStats() local function getStorageStats()
local stats = { } local stats = { }
local totals = { local totals = {
usedSlots = 0, usedSlots = 0,
totalSlots = 0, totalSlots = 0,
totalChests = 0, totalChests = 0,
unlockedSlots = 0, unlockedSlots = 0,
usedUnlockedSlots = 0, usedUnlockedSlots = 0,
} }
for n in context.storage:filterActive('storage') do for n in context.storage:filterActive('storage') do
if n.adapter.size and n.adapter.list then if n.adapter.size and n.adapter.list then
pcall(function() pcall(function()
local updated = n.adapter.__lastUpdate ~= n.adapter.lastUpdate local updated = n.adapter.__lastUpdate ~= n.adapter.lastUpdate
if updated then if updated then
n.adapter.__used = Util.size(n.adapter.list()) n.adapter.__used = Util.size(n.adapter.list())
n.adapter.__lastUpdate = n.adapter.lastUpdate n.adapter.__lastUpdate = n.adapter.lastUpdate
end end
if not n.adapter.__used then if not n.adapter.__used then
n.adapter.__used = Util.size(n.adapter.list()) n.adapter.__used = Util.size(n.adapter.list())
end end
table.insert(stats, { table.insert(stats, {
name = n.displayName or n.name, name = n.displayName or n.name,
size = n.adapter.__size, size = n.adapter.__size,
used = n.adapter.__used, used = n.adapter.__used,
perc = math.floor(n.adapter.__used / n.adapter.__size * 100), perc = math.floor(n.adapter.__used / n.adapter.__size * 100),
updated = updated, updated = updated,
}) })
totals.usedSlots = totals.usedSlots + n.adapter.__used totals.usedSlots = totals.usedSlots + n.adapter.__used
totals.totalSlots = totals.totalSlots + n.adapter.__size totals.totalSlots = totals.totalSlots + n.adapter.__size
totals.totalChests = totals.totalChests + 1 totals.totalChests = totals.totalChests + 1
if not n.lock then if not n.lock then
totals.unlockedSlots = totals.unlockedSlots + n.adapter.__size totals.unlockedSlots = totals.unlockedSlots + n.adapter.__size
totals.usedUnlockedSlots = totals.usedUnlockedSlots + n.adapter.__used totals.usedUnlockedSlots = totals.usedUnlockedSlots + n.adapter.__used
end end
end) end)
end end
end end
return stats, totals return stats, totals
end end
function stateTab:refresh() function stateTab:refresh()
self.grid.values = { } self.grid.values = { }
for _, v in pairs(context.storage.nodes) do for _, v in pairs(context.storage.nodes) do
if v.mtype ~= 'hidden' then if v.mtype ~= 'hidden' then
if not v.adapter or not v.adapter.online then if not v.adapter or not v.adapter.online then
table.insert(self.grid.values, { table.insert(self.grid.values, {
name = v.displayName or v.name name = v.displayName or v.name
}) })
end end
end end
end end
self.grid:update() self.grid:update()
end end
function stateTab:enable() function stateTab:enable()
self:refresh() self:refresh()
self.handle = Event.onInterval(5, function() self.handle = Event.onInterval(5, function()
self:refresh() self:refresh()
self.grid:draw() self.grid:draw()
self:sync() self:sync()
end) end)
UI.Tab.enable(self) UI.Tab.enable(self)
end end
function stateTab:disable() function stateTab:disable()
Event.off(self.handle) Event.off(self.handle)
UI.Tab.disable(self) UI.Tab.disable(self)
end end
function usageTab:refresh() function usageTab:refresh()
self.grid:setValues(getStorageStats()) self.grid:setValues(getStorageStats())
end end
function usageTab:enable() function usageTab:enable()
self:refresh() self:refresh()
self.handle = Event.onInterval(5, function() self.handle = Event.onInterval(5, function()
self:refresh() self:refresh()
self.grid:draw() self.grid:draw()
self:sync() self:sync()
end) end)
UI.Tab.enable(self) UI.Tab.enable(self)
end end
function usageTab:disable() function usageTab:disable()
Event.off(self.handle) Event.off(self.handle)
UI.Tab.disable(self) UI.Tab.disable(self)
end end
function usageTab.grid:getRowTextColor(row, selected) function usageTab.grid:getRowTextColor(row, selected)
return row.updated and colors.yellow or return row.updated and colors.yellow or
UI.Grid:getRowTextColor(row, selected) UI.Grid:getRowTextColor(row, selected)
end end
function statsTab.textArea:draw() function statsTab.textArea:draw()
local _, stats = getStorageStats() local _, stats = getStorageStats()
local totalItems, nodeCount = 0, 0 local totalItems, nodeCount = 0, 0
local formatString = [[ local formatString = [[
Storage Usage : %d%% Storage Usage : %d%%
Slots : %d of %d used Slots : %d of %d used
Unique Items : %d Unique Items : %d
@@ -297,191 +297,191 @@ Nodes : %d
Unlocked Slots : %d of %d (%d%%) Unlocked Slots : %d of %d (%d%%)
]] ]]
for _,v in pairs(context.storage.nodes) do for _,v in pairs(context.storage.nodes) do
if v.adapter and v.adapter.online then if v.adapter and v.adapter.online then
nodeCount = nodeCount + 1 nodeCount = nodeCount + 1
end end
end end
for _,v in pairs(context.storage.cache) do for _,v in pairs(context.storage.cache) do
totalItems = totalItems + v.count totalItems = totalItems + v.count
end end
self.value = string.format(formatString, self.value = string.format(formatString,
math.floor(stats.usedSlots / stats.totalSlots * 100), math.floor(stats.usedSlots / stats.totalSlots * 100),
stats.usedSlots, stats.usedSlots,
stats.totalSlots, stats.totalSlots,
Util.size(context.storage.cache), Util.size(context.storage.cache),
totalItems, totalItems,
nodeCount, nodeCount,
stats.usedUnlockedSlots, stats.usedUnlockedSlots,
stats.unlockedSlots, stats.unlockedSlots,
math.floor(stats.usedUnlockedSlots / stats.unlockedSlots * 100)) math.floor(stats.usedUnlockedSlots / stats.unlockedSlots * 100))
UI.TextArea.draw(self) UI.TextArea.draw(self)
end end
function statsTab:enable() function statsTab:enable()
self.handle = Event.onInterval(5, function() self.handle = Event.onInterval(5, function()
self.textArea:draw() self.textArea:draw()
self:sync() self:sync()
end) end)
UI.Tab.enable(self) UI.Tab.enable(self)
end end
function statsTab:disable() function statsTab:disable()
Event.off(self.handle) Event.off(self.handle)
UI.Tab.disable(self) UI.Tab.disable(self)
end end
function taskTab.grid:getDisplayValues(row) function taskTab.grid:getDisplayValues(row)
return { return {
name = row.name, name = row.name,
priority = row.priority, priority = row.priority,
avg = Util.round(row.execTime / context.taskCounter * 1000) .. ' ms', avg = Util.round(row.execTime / context.taskCounter * 1000) .. ' ms',
perc = Util.round(row.execTime / context.taskTimer * 100) .. '%', perc = Util.round(row.execTime / context.taskTimer * 100) .. '%',
} }
end end
function taskTab:refresh() function taskTab:refresh()
self.grid:update() self.grid:update()
end end
function taskTab:enable() function taskTab:enable()
self:refresh() self:refresh()
self.handle = Event.onInterval(5, function() self.handle = Event.onInterval(5, function()
self:refresh() self:refresh()
self.grid:draw() self.grid:draw()
self:sync() self:sync()
end) end)
UI.Tab.enable(self) UI.Tab.enable(self)
end end
function taskTab:disable() function taskTab:disable()
Event.off(self.handle) Event.off(self.handle)
UI.Tab.disable(self) UI.Tab.disable(self)
end end
function overviewTab:draw() function overviewTab:draw()
local _, stats = getStorageStats() local _, stats = getStorageStats()
self.onlineText.textColor = context.storage:isOnline() and colors.green or colors.red self.onlineText.textColor = context.storage:isOnline() and colors.green or colors.red
self.onlineText.value = context.storage:isOnline() and 'Online' or 'Offline' self.onlineText.value = context.storage:isOnline() and 'Online' or 'Offline'
self.tpsText.value = tostring(Util.round(self.tasks / (os.clock() - self.timer), 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)) self.tasksText.value = tostring(Util.round(context.taskTimer / context.taskCounter, 2))
local total, crafted = 0, 0 local total, crafted = 0, 0
for _,v in pairs(context.craftingQueue) do for _,v in pairs(context.craftingQueue) do
total = total + v.requested total = total + v.requested
crafted = crafted + v.crafted crafted = crafted + v.crafted
end end
if Milo:isCraftingPaused() then if Milo:isCraftingPaused() then
self.crafting.progressColor = colors.yellow self.crafting.progressColor = colors.yellow
self.crafting.value = 100 self.crafting.value = 100
else else
self.crafting.progressColor = colors.orange self.crafting.progressColor = colors.orange
self.crafting.value = total > 0 and math.ceil(crafted / total * 100) or 0 self.crafting.value = total > 0 and math.ceil(crafted / total * 100) or 0
end end
local percent = math.floor(stats.usedSlots / stats.totalSlots * 100) local percent = math.floor(stats.usedSlots / stats.totalSlots * 100)
local color = colors.green local color = colors.green
if percent > 90 then if percent > 90 then
color = colors.red color = colors.red
elseif percent > 75 then elseif percent > 75 then
color = colors.yellow color = colors.yellow
end end
self.storage.progressColor = color self.storage.progressColor = color
self.storage.value = percent self.storage.value = percent
self.storageLabel.value = string.format('Total Usage: %s%% (%s of %s slots)', self.storageLabel.value = string.format('Total Usage: %s%% (%s of %s slots)',
percent, stats.usedSlots, stats.totalSlots) percent, stats.usedSlots, stats.totalSlots)
percent = math.floor(stats.usedUnlockedSlots / stats.unlockedSlots * 100) percent = math.floor(stats.usedUnlockedSlots / stats.unlockedSlots * 100)
color = colors.green color = colors.green
if percent > 90 then if percent > 90 then
color = colors.red color = colors.red
elseif percent > 75 then elseif percent > 75 then
color = colors.yellow color = colors.yellow
end end
self.unlocked.progressColor = color self.unlocked.progressColor = color
self.unlocked.value = percent self.unlocked.value = percent
self.unlockedLabel.value = string.format('Unlocked Usage: %s%% (%s of %s slots)', self.unlockedLabel.value = string.format('Unlocked Usage: %s%% (%s of %s slots)',
percent, stats.usedUnlockedSlots, stats.unlockedSlots) percent, stats.usedUnlockedSlots, stats.unlockedSlots)
UI.Tab.draw(self) UI.Tab.draw(self)
end end
function overviewTab:enable() function overviewTab:enable()
self.timer = os.clock() self.timer = os.clock()
self.tasks = 0 self.tasks = 0
self.handle = Event.onInterval(5, function() self.handle = Event.onInterval(5, function()
self:draw() self:draw()
self:sync() self:sync()
end) end)
self.handle2 = Event.on({ 'milo_resume', 'milo_pause', 'storage_offline', 'storage_online' }, function() self.handle2 = Event.on({ 'milo_resume', 'milo_pause', 'storage_offline', 'storage_online' }, function()
self:draw() self:draw()
self:sync() self:sync()
end) end)
self.handle3 = Event.on('plethora_task', function() self.handle3 = Event.on({ 'plethora_task', 'task_complete' }, function()
self.tasks = self.tasks + 1 self.tasks = self.tasks + 1
end) end)
UI.Tab.enable(self) UI.Tab.enable(self)
end end
function overviewTab:disable() function overviewTab:disable()
Event.off(self.handle) Event.off(self.handle)
Event.off(self.handle2) Event.off(self.handle2)
Event.off(self.handle3) Event.off(self.handle3)
UI.Tab.disable(self) UI.Tab.disable(self)
end end
function page:eventHandler(event) function page:eventHandler(event)
if event.type == 'tab_activate' then if event.type == 'tab_activate' then
local state = Milo:getState('statusState') or { } local state = Milo:getState('statusState') or { }
state[node.name] = event.activated.tabTitle state[node.name] = event.activated.tabTitle
Milo:setState('statusState', state) Milo:setState('statusState', state)
end end
return UI.Page.eventHandler(self, event) return UI.Page.eventHandler(self, event)
end end
table.insert(context.loggers, function(...) table.insert(context.loggers, function(...)
local oterm = term.redirect(activityTab.term.win) local oterm = term.redirect(activityTab.term.win)
activityTab.term.win.scrollBottom() activityTab.term.win.scrollBottom()
Util.print(...) Util.print(...)
term.redirect(oterm) term.redirect(oterm)
if activityTab.enabled then if activityTab.enabled then
activityTab:sync() activityTab:sync()
end end
end) end)
Event.onTimeout(0, function() Event.onTimeout(0, function()
UI:setPage(page) UI:setPage(page)
end) end)
-- restore active tab -- restore active tab
local tabState = Milo:getState('statusState') or { } local tabState = Milo:getState('statusState') or { }
if tabState[node.name] then if tabState[node.name] then
page.tabs:selectTab(Util.find(page.tabs, 'tabTitle', tabState[node.name])) page.tabs:selectTab(Util.find(page.tabs, 'tabTitle', tabState[node.name]))
end end
return page return page
end end
local pages = { } local pages = { }
--[[ Task ]]-- --[[ Task ]]--
local task = { local task = {
name = 'status', name = 'status',
priority = 99, priority = 99,
} }
function task:cycle() function task:cycle()
for node in context.storage:filterActive('status') do for node in context.storage:filterActive('status') do
if not pages[node.name] then if not pages[node.name] then
pages[node.name] = createPage(node) pages[node.name] = createPage(node)
end end
end end
end end
Milo:registerTask(task) Milo:registerTask(task)

View File

@@ -7,89 +7,89 @@ local device = _G.device
--[[ Configuration Screen ]] --[[ Configuration Screen ]]
local wizardPage = UI.WizardPage { local wizardPage = UI.WizardPage {
title = 'Trashcan', title = 'Trashcan',
index = 2, index = 2,
backgroundColor = colors.cyan, backgroundColor = colors.cyan,
info = UI.TextArea { info = UI.TextArea {
x = 1, ex = -1, y = 2, ey = 4, x = 1, ex = -1, y = 2, ey = 4,
textColor = colors.yellow, textColor = colors.yellow,
marginLeft = 1, marginLeft = 1,
marginRight = 1, marginRight = 1,
value = [[ Items can be automatically dropped from this storage.]], value = [[ Items can be automatically dropped from this storage.]],
}, },
form = UI.Form { form = UI.Form {
x = 2, ex = -2, y = 4, ey = -2, x = 2, ex = -2, y = 4, ey = -2,
manualControls = true, manualControls = true,
[1] = UI.Checkbox { [1] = UI.Checkbox {
formLabel = 'Drop', formKey = 'drop', formLabel = 'Drop', formKey = 'drop',
help = 'Drop the items out of this inventory', help = 'Drop the items out of this inventory',
}, },
[2] = UI.Chooser { [2] = UI.Chooser {
width = 9, width = 9,
formLabel = 'Direction', formKey = 'dropDirection', formLabel = 'Direction', formKey = 'dropDirection',
nochoice = 'Down', nochoice = 'Down',
choices = { choices = {
{ name = 'Down', value = 'down' }, { name = 'Down', value = 'down' },
{ name = 'Up', value = 'up' }, { name = 'Up', value = 'up' },
{ name = 'North', value = 'north' }, { name = 'North', value = 'north' },
{ name = 'South', value = 'south' }, { name = 'South', value = 'south' },
{ name = 'East', value = 'east' }, { name = 'East', value = 'east' },
{ name = 'West', value = 'west' }, { name = 'West', value = 'west' },
}, },
help = 'Drop in a specified direction' help = 'Drop in a specified direction'
}, },
}, },
} }
function wizardPage:validate() function wizardPage:validate()
return self.form:save() return self.form:save()
end end
function wizardPage:setNode(node) function wizardPage:setNode(node)
self.form:setValues(node) self.form:setValues(node)
end end
function wizardPage:isValidType(node) function wizardPage:isValidType(node)
local m = device[node.name] local m = device[node.name]
return m and m.pullItems and { return m and m.pullItems and {
name = 'Trashcan', name = 'Trashcan',
value = 'trashcan', value = 'trashcan',
category = 'custom', category = 'custom',
help = 'An inventory to send unwanted items', help = 'An inventory to send unwanted items',
} }
end end
function wizardPage:isValidFor(node) function wizardPage:isValidFor(node)
return node.mtype == 'trashcan' return node.mtype == 'trashcan'
end end
UI:getPage('nodeWizard').wizard:add({ trashcan = wizardPage }) UI:getPage('nodeWizard').wizard:add({ trashcan = wizardPage })
--[[ TASK ]]-- --[[ TASK ]]--
local task = { local task = {
name = 'trashcan', name = 'trashcan',
priority = 90, priority = 90,
} }
local function filter(a) local function filter(a)
return a.drop return a.drop
end end
function task:cycle(context) function task:cycle(context)
local tasks = Tasks() local tasks = Tasks()
for node in context.storage:filterActive('trashcan', filter) do for node in context.storage:filterActive('trashcan', filter) do
pcall(function() pcall(function()
for k in pairs(node.adapter.list()) do for k in pairs(node.adapter.list()) do
local direction = node.dropDirection or 'down' local direction = node.dropDirection or 'down'
tasks:add(function() tasks:add(function()
node.adapter.drop(k, 64, direction) node.adapter.drop(k, 64, direction)
end) end)
end end
end) end)
end end
tasks:run() tasks:run()
end end
Milo:registerTask(task) Milo:registerTask(task)

View File

@@ -20,160 +20,160 @@ local paused, abort
local chunkIndex = 0 local chunkIndex = 0
local swarm = Swarm() local swarm = Swarm()
local blocks = Util.transpose({ local blocks = Util.transpose({
'minecraft:chest', 'minecraft:chest',
-- 'minecraft:mob_spawner', -- 'minecraft:mob_spawner',
'quark:crystal', 'quark:crystal',
'minecraft:mossy_cobblestone' 'minecraft:mossy_cobblestone'
}) })
local locations = { } local locations = { }
gpt.x = gpt.x + 1 gpt.x = gpt.x + 1
local function getLocations() local function getLocations()
local y = gpt.y - 8 local y = gpt.y - 8
while y > 5 do while y > 5 do
table.insert(locations, y) table.insert(locations, y)
y = y - 16 y = y - 16
end end
if y > 0 then if y > 0 then
table.insert(locations, 5) table.insert(locations, 5)
end end
end end
for _, b in pairs(scanner.scan()) do for _, b in pairs(scanner.scan()) do
if b.name == 'computercraft:turtle_advanced' or if b.name == 'computercraft:turtle_advanced' or
b.name == 'computercraft:turtle' then b.name == 'computercraft:turtle' then
local v = scanner.getBlockMeta(b.x, b.y, b.z) local v = scanner.getBlockMeta(b.x, b.y, b.z)
if v and v.computer then if v and v.computer then
if not v.computer.isOn then if not v.computer.isOn then
print('Powered off: ' .. v.computer.id) print('Powered off: ' .. v.computer.id)
elseif v.turtle.fuel < 100 then elseif v.turtle.fuel < 100 then
print('not enough fuel: ' .. v.computer.id) print('not enough fuel: ' .. v.computer.id)
else else
swarm:add(v.computer.id, { swarm:add(v.computer.id, {
point = { point = {
x = gpt.x + b.x, x = gpt.x + b.x,
y = gpt.y + b.y, y = gpt.y + b.y,
z = gpt.z + b.z, z = gpt.z + b.z,
heading = Point.facings[v.state.facing].heading, heading = Point.facings[v.state.facing].heading,
}, },
index = Util.size(swarm.pool), index = Util.size(swarm.pool),
}) })
end end
end end
end end
end end
local function getNextPoint(member) local function getNextPoint(member)
local z = math.floor(chunkIndex / COLUMNS) local z = math.floor(chunkIndex / COLUMNS)
local x = chunkIndex % COLUMNS local x = chunkIndex % COLUMNS
chunkIndex = chunkIndex + 1 chunkIndex = chunkIndex + 1
while paused do while paused do
if abort then if abort then
return return
end end
os.sleep(3) os.sleep(3)
end end
return { return {
x = gpt.x + (x * 16), x = gpt.x + (x * 16),
y = gpt.y + member.index, y = gpt.y + member.index,
z = gpt.z + (z * 16) z = gpt.z + (z * 16)
} }
end end
local function run(member) local function run(member)
local turtle = member.turtle local turtle = member.turtle
if not turtle.has('plethora:module:2') then if not turtle.has('plethora:module:2') then
error('missing scanner') error('missing scanner')
end end
turtle.reset() turtle.reset()
turtle.set({ turtle.set({
attackPolicy = 'attack', attackPolicy = 'attack',
digPolicy = 'turtleSafe', digPolicy = 'turtleSafe',
movementStrategy = 'goto', movementStrategy = 'goto',
point = member.point, point = member.point,
}) })
turtle.select(1) turtle.select(1)
local swapSide = turtle.isEquipped('modem') == 'right' and 'left' or 'right' local swapSide = turtle.isEquipped('modem') == 'right' and 'left' or 'right'
repeat repeat
local pt = getNextPoint(member) local pt = getNextPoint(member)
if pt then if pt then
turtle.set({ status = 'Relocating' }) turtle.set({ status = 'Relocating' })
turtle.go({ y = pt.y }) turtle.go({ y = pt.y })
local c = os.clock() local c = os.clock()
while not turtle.go(pt) do while not turtle.go(pt) do
if abort then if abort then
break break
end end
os.sleep(.5) os.sleep(.5)
if os.clock() - c > 3 then if os.clock() - c > 3 then
Sound.play('entity.villager.no') Sound.play('entity.villager.no')
print('stuck: ' .. member.id) print('stuck: ' .. member.id)
turtle.set({ status = 'Stuck' }) turtle.set({ status = 'Stuck' })
end end
end end
turtle.set({ status = 'Boring' }) turtle.set({ status = 'Boring' })
for _, v in ipairs(locations) do for _, v in ipairs(locations) do
if abort then if abort then
break break
end end
turtle.go({ y = v }) turtle.go({ y = v })
turtle.equip(swapSide, 'plethora:module:2') turtle.equip(swapSide, 'plethora:module:2')
local found = turtle.scan(blocks) local found = turtle.scan(blocks)
turtle.equip(swapSide, 'minecraft:diamond_pickaxe') turtle.equip(swapSide, 'minecraft:diamond_pickaxe')
if Util.size(found) > 0 then if Util.size(found) > 0 then
paused = true paused = true
local _, b = next(found) local _, b = next(found)
print(string.format('%s:%s:%s %s', b.x, b.y, b.z, b.name)) print(string.format('%s:%s:%s %s', b.x, b.y, b.z, b.name))
print('press r to continue') print('press r to continue')
for _ = 1, 3 do for _ = 1, 3 do
Sound.play('block.note.pling') Sound.play('block.note.pling')
os.sleep(.3) os.sleep(.3)
end end
end end
end end
turtle.go({ y = pt.y }) turtle.go({ y = pt.y })
end end
until abort until abort
turtle.set({ status = 'Aborting' }) turtle.set({ status = 'Aborting' })
turtle.go({ y = gpt.y + member.index }) turtle.go({ y = gpt.y + member.index })
turtle.go({ x = gpt.x, y = gpt.y + member.index, z = gpt.z }) turtle.go({ x = gpt.x, y = gpt.y + member.index, z = gpt.z })
repeat until turtle.go({ y = gpt.y }) repeat until turtle.go({ y = gpt.y })
turtle.set({ status = 'idle' }) turtle.set({ status = 'idle' })
end end
function swarm:onRemove(member, success, message) function swarm:onRemove(member, success, message)
if not success then if not success then
Sound.play('entity.villager.no') Sound.play('entity.villager.no')
print('Removed from swarm: ' .. member.id) print('Removed from swarm: ' .. member.id)
_G.printError(message) _G.printError(message)
end end
print('Turtles: ' .. Util.size(self.pool)) print('Turtles: ' .. Util.size(self.pool))
if Util.size(self.pool) == 0 then if Util.size(self.pool) == 0 then
Event.exitPullEvents() Event.exitPullEvents()
end end
end end
print('press a to abort, r to resume') print('press a to abort, r to resume')
Event.on('char', function(_, k) Event.on('char', function(_, k)
if k == 'r' then if k == 'r' then
print('Resuming') print('Resuming')
paused = false paused = false
elseif k == 'a' then elseif k == 'a' then
gpt = GPS.getPoint() gpt = GPS.getPoint()
print('Aborting') print('Aborting')
abort = true abort = true
end end
end) end)
getLocations() 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" ] = { [ "58ec8d6e36e346d9f42eb43935652e3e58e2c829" ] = {
title = "Mwm", title = "Mwm",
category = "Apps", category = "Apps",
icon = "\030f\031f \0304 \ icon = "\030f\031f \0304 \
\030f\031dshell]\0304\0314 \ \030f\031dshell]\0304\0314 \
\0304\031f ", \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\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", \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 remoteId
local args = { ... } local args = { ... }
if #args == 1 then if #args == 1 then
remoteId = tonumber(args[1]) remoteId = tonumber(args[1])
else else
print('Enter host ID') print('Enter host ID')
remoteId = tonumber(_G.read()) remoteId = tonumber(_G.read())
end end
if not remoteId then if not remoteId then
error('Syntax: mirrorClient <host ID>') error('Syntax: mirrorClient <host ID>')
end end
local function wrapTerm(socket) local function wrapTerm(socket)
local methods = { 'blit', 'clear', 'clearLine', 'setCursorPos', 'write', local methods = { 'blit', 'clear', 'clearLine', 'setCursorPos', 'write',
'setTextColor', 'setTextColour', 'setBackgroundColor', 'setTextColor', 'setTextColour', 'setBackgroundColor',
'setBackgroundColour', 'scroll', 'setCursorBlink', } 'setBackgroundColour', 'scroll', 'setCursorBlink', }
socket.term = multishell.term socket.term = multishell.term
socket.oldTerm = Util.shallowCopy(socket.term) socket.oldTerm = Util.shallowCopy(socket.term)
for _,k in pairs(methods) do for _,k in pairs(methods) do
socket.term[k] = function(...) socket.term[k] = function(...)
if not socket.queue then if not socket.queue then
socket.queue = { } socket.queue = { }
Event.onTimeout(0, function() Event.onTimeout(0, function()
if socket.queue then if socket.queue then
socket:write(socket.queue) socket:write(socket.queue)
socket.queue = nil socket.queue = nil
end end
end) end)
end end
table.insert(socket.queue, { table.insert(socket.queue, {
f = k, f = k,
args = { ... }, args = { ... },
}) })
socket.oldTerm[k](...) socket.oldTerm[k](...)
end end
end end
end end
while true do while true do
print('connecting...') print('connecting...')
local socket local socket
while true do while true do
socket = Socket.connect(remoteId, 5901) socket = Socket.connect(remoteId, 5901)
if socket then if socket then
break break
end end
os.sleep(3) os.sleep(3)
end end
print('connected') print('connected')
wrapTerm(socket) wrapTerm(socket)
os.queueEvent('term_resize') os.queueEvent('term_resize')
while true do while true do
local e = Event.pullEvent() local e = Event.pullEvent()
if e[1] == 'terminate' then if e[1] == 'terminate' then
break break
end end
if not socket.connected then if not socket.connected then
break break
end end
end end
for k,v in pairs(socket.oldTerm) do for k,v in pairs(socket.oldTerm) do
socket.term[k] = v socket.term[k] = v
end end
socket:close() socket:close()
end end

View File

@@ -7,41 +7,41 @@ local term = _G.term
local mon = term.current() local mon = term.current()
local args = { ... } local args = { ... }
if args[1] then if args[1] then
mon = _G.device[args[1]] mon = _G.device[args[1]]
end end
if not mon then if not mon then
error('Invalid monitor') error('Invalid monitor')
end end
mon.setBackgroundColor(colors.black) mon.setBackgroundColor(colors.black)
mon.clear() mon.clear()
while true do 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() Event.addRoutine(function()
while true do while true do
local data = socket:read() local data = socket:read()
if not data then if not data then
break break
end end
for _,v in ipairs(data) do for _,v in ipairs(data) do
mon[v.f](unpack(v.args)) mon[v.f](unpack(v.args))
end end
end end
end) end)
while true do while true do
Event.pullEvent() Event.pullEvent()
if not socket.connected then if not socket.connected then
break break
end end
end end
print('connection lost') print('connection lost')
socket:close() socket:close()
end end

View File

@@ -17,8 +17,8 @@ local term = _G.term
local window = _G.window local window = _G.window
local function syntax() local function syntax()
printError('Syntax:') printError('Syntax:')
error('mwm sessionName [monitor]') error('mwm sessionName [monitor]')
end end
local args = { ... } local args = { ... }
@@ -33,9 +33,9 @@ local parentMon
local defaultEnv = Util.shallowCopy(_ENV) local defaultEnv = Util.shallowCopy(_ENV)
defaultEnv.multishell = multishell defaultEnv.multishell = multishell
if args[2] then if args[2] then
parentMon = peripheral.wrap(args[2]) or syntax() parentMon = peripheral.wrap(args[2]) or syntax()
else else
parentMon = peripheral.find('monitor') or syntax() parentMon = peripheral.find('monitor') or syntax()
end end
parentMon.setTextScale(.5) parentMon.setTextScale(.5)
@@ -54,8 +54,8 @@ monitor.setBackgroundColor(colors.gray)
monitor.clear() monitor.clear()
local function nextUID() local function nextUID()
UID = UID + 1 UID = UID + 1
return UID return UID
end end
local function xprun(env, path, ...) local function xprun(env, path, ...)
@@ -68,471 +68,471 @@ local function xprun(env, path, ...)
end end
local function write(win, x, y, text) local function write(win, x, y, text)
win.setCursorPos(x, y) win.setCursorPos(x, y)
win.write(text) win.write(text)
end end
local function redraw() local function redraw()
--monitor.clear() --monitor.clear()
monitor.canvas:dirty() monitor.canvas:dirty()
--monitor.setBackgroundColor(colors.gray) --monitor.setBackgroundColor(colors.gray)
monitor.canvas:clear(colors.gray) monitor.canvas:clear(colors.gray)
for k, process in ipairs(processes) do for k, process in ipairs(processes) do
process.container.canvas:dirty() process.container.canvas:dirty()
process:focus(k == #processes) process:focus(k == #processes)
end end
end end
local function getProcessAt(x, y) local function getProcessAt(x, y)
for k = #processes, 1, -1 do for k = #processes, 1, -1 do
local process = processes[k] local process = processes[k]
if x >= process.x and if x >= process.x and
y >= process.y and y >= process.y and
x <= process.x + process.width - 1 and x <= process.x + process.width - 1 and
y <= process.y + process.height - 1 then y <= process.y + process.height - 1 then
return k, process return k, process
end end
end end
end end
--[[ A runnable process ]]-- --[[ A runnable process ]]--
local Process = { } local Process = { }
function Process:new(args) function Process:new(args)
args.env = args.env or Util.shallowCopy(defaultEnv) args.env = args.env or Util.shallowCopy(defaultEnv)
args.width = args.width or termDim.width args.width = args.width or termDim.width
args.height = args.height or termDim.height args.height = args.height or termDim.height
-- TODO: randomize start position -- TODO: randomize start position
local self = setmetatable({ local self = setmetatable({
uid = nextUID(), uid = nextUID(),
x = args.x or 1, x = args.x or 1,
y = args.y or 1, y = args.y or 1,
width = args.width + 2, width = args.width + 2,
height = args.height + 3, height = args.height + 3,
path = args.path, path = args.path,
args = args.args or { }, args = args.args or { },
title = args.title or 'shell', title = args.title or 'shell',
}, { __index = Process }) }, { __index = Process })
self:adjustDimensions() self:adjustDimensions()
if not args.x then if not args.x then
self.x = math.random(1, monDim.width - self.width + 1) self.x = math.random(1, monDim.width - self.width + 1)
self.y = math.random(1, monDim.height - self.height + 1) self.y = math.random(1, monDim.height - self.height + 1)
end end
self.container = Terminal.window(monitor, self.x, self.y, self.width, self.height, true) 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.window = window.create(self.container, 2, 3, args.width, args.height, true)
self.terminal = self.window self.terminal = self.window
self.container.canvas.parent = monitor.canvas self.container.canvas.parent = monitor.canvas
table.insert(monitor.canvas.layers, 1, self.container.canvas) table.insert(monitor.canvas.layers, 1, self.container.canvas)
self.container.canvas:setVisible(true) self.container.canvas:setVisible(true)
--self.container.getSize = self.window.getSize --self.container.getSize = self.window.getSize
self.co = coroutine.create(function() self.co = coroutine.create(function()
local result, err local result, err
if args.fn then if args.fn then
result, err = Util.runFunction(args.env, args.fn, table.unpack(self.args)) result, err = Util.runFunction(args.env, args.fn, table.unpack(self.args))
elseif args.path then elseif args.path then
result, err = xprun(args.env, args.path, table.unpack(self.args)) result, err = xprun(args.env, args.path, table.unpack(self.args))
end end
if not result and err and err ~= 'Terminated' then if not result and err and err ~= 'Terminated' then
printError('\n' .. tostring(err)) printError('\n' .. tostring(err))
os.pullEventRaw('terminate') os.pullEventRaw('terminate')
end end
multishell.removeProcess(self) multishell.removeProcess(self)
end) end)
self:focus(false) self:focus(false)
return self return self
end end
function Process:focus(focused) function Process:focus(focused)
if focused then if focused then
self.container.setBackgroundColor(colors.yellow) self.container.setBackgroundColor(colors.yellow)
else else
self.container.setBackgroundColor(colors.gray) self.container.setBackgroundColor(colors.gray)
end end
self.container.setTextColor(colors.black) self.container.setTextColor(colors.black)
write(self.container, 2, 2, string.rep(' ', self.width - 2)) write(self.container, 2, 2, string.rep(' ', self.width - 2))
write(self.container, 3, 2, self.title) write(self.container, 3, 2, self.title)
write(self.container, self.width - 2, 2, '*') write(self.container, self.width - 2, 2, '*')
if focused then if focused then
self.window.restoreCursor() self.window.restoreCursor()
elseif self.showSizers then elseif self.showSizers then
self:drawSizers(false) self:drawSizers(false)
end end
end end
function Process:drawSizers(showSizers) function Process:drawSizers(showSizers)
local sizeChars = { local sizeChars = {
'\135', '\139', '\141', '\142' '\135', '\139', '\141', '\142'
} }
if Util.getVersion() < 1.8 then if Util.getVersion() < 1.8 then
sizeChars = { sizeChars = {
'#', '#', '#', '#' '#', '#', '#', '#'
} }
end end
self.showSizers = showSizers self.showSizers = showSizers
self.container.setBackgroundColor(colors.black) self.container.setBackgroundColor(colors.black)
self.container.setTextColor(colors.white) self.container.setTextColor(colors.white)
if self.showSizers then if self.showSizers then
write(self.container, 1, 1, sizeChars[1]) write(self.container, 1, 1, sizeChars[1])
write(self.container, self.width, 1, sizeChars[2]) write(self.container, self.width, 1, sizeChars[2])
write(self.container, 1, self.height, sizeChars[3]) write(self.container, 1, self.height, sizeChars[3])
write(self.container, self.width, self.height, sizeChars[4]) write(self.container, self.width, self.height, sizeChars[4])
self.container.setTextColor(colors.yellow) self.container.setTextColor(colors.yellow)
write(self.container, 1, 3, '+') write(self.container, 1, 3, '+')
write(self.container, 1, 5, '-') write(self.container, 1, 5, '-')
write(self.container, 3, 1, '+') write(self.container, 3, 1, '+')
write(self.container, 5, 1, '-') write(self.container, 5, 1, '-')
local str = string.format('%d x %d', self.width - 2, self.height - 3) local str = string.format('%d x %d', self.width - 2, self.height - 3)
write(self.container, (self.width - #str) / 2, 1, str) write(self.container, (self.width - #str) / 2, 1, str)
else else
write(self.container, 1, 1, string.rep(' ', self.width)) write(self.container, 1, 1, string.rep(' ', self.width))
write(self.container, self.width, 1, ' ') write(self.container, self.width, 1, ' ')
write(self.container, 1, self.height, ' ') write(self.container, 1, self.height, ' ')
write(self.container, self.width, self.height, ' ') write(self.container, self.width, self.height, ' ')
write(self.container, 1, 3, ' ') write(self.container, 1, 3, ' ')
write(self.container, 1, 5, ' ') write(self.container, 1, 5, ' ')
end end
end end
function Process:adjustDimensions() function Process:adjustDimensions()
self.width = math.min(self.width, monDim.width) self.width = math.min(self.width, monDim.width)
self.height = math.min(self.height, monDim.height) self.height = math.min(self.height, monDim.height)
self.x = math.max(1, self.x) self.x = math.max(1, self.x)
self.y = math.max(1, self.y) self.y = math.max(1, self.y)
self.x = math.min(self.x, monDim.width - self.width + 1) self.x = math.min(self.x, monDim.width - self.width + 1)
self.y = math.min(self.y, monDim.height - self.height + 1) self.y = math.min(self.y, monDim.height - self.height + 1)
end end
function Process:reposition() function Process:reposition()
self:adjustDimensions() self:adjustDimensions()
self.container.reposition(self.x, self.y, self.width, self.height) self.container.reposition(self.x, self.y, self.width, self.height)
self.container.setBackgroundColor(colors.black) self.container.setBackgroundColor(colors.black)
self.container.clear() self.container.clear()
self.window.reposition(2, 3, self.width - 2, self.height - 3) self.window.reposition(2, 3, self.width - 2, self.height - 3)
if self.window ~= self.terminal then if self.window ~= self.terminal then
self.terminal.reposition(1, 1, self.width - 2, self.height - 3) self.terminal.reposition(1, 1, self.width - 2, self.height - 3)
end end
redraw() redraw()
end end
function Process:click(x, y) function Process:click(x, y)
if y == 2 then -- title bar if y == 2 then -- title bar
if x == self.width - 2 then if x == self.width - 2 then
self:resume('terminate') self:resume('terminate')
else else
self:drawSizers(not self.showSizers) self:drawSizers(not self.showSizers)
end end
elseif x == 1 or y == 1 then -- sizers elseif x == 1 or y == 1 then -- sizers
self:resizeClick(x, y) self:resizeClick(x, y)
elseif x > 1 and x < self.width then elseif x > 1 and x < self.width then
if self.showSizers then if self.showSizers then
self:drawSizers(false) self:drawSizers(false)
end end
self:resume('mouse_click', 1, x - 1, y - 2) self:resume('mouse_click', 1, x - 1, y - 2)
self:resume('mouse_up', 1, x - 1, y - 2) self:resume('mouse_up', 1, x - 1, y - 2)
end end
end end
function Process:resizeClick(x, y) function Process:resizeClick(x, y)
if x == 1 and y == 3 then if x == 1 and y == 3 then
self.height = self.height + 1 self.height = self.height + 1
elseif x == 1 and y == 5 then elseif x == 1 and y == 5 then
self.height = self.height - 1 self.height = self.height - 1
elseif x == 3 and y == 1 then elseif x == 3 and y == 1 then
self.width = self.width + 1 self.width = self.width + 1
elseif x == 5 and y == 1 then elseif x == 5 and y == 1 then
self.width = self.width - 1 self.width = self.width - 1
else else
return return
end end
self:reposition() self:reposition()
self:resume('term_resize') self:resume('term_resize')
self:drawSizers(true) self:drawSizers(true)
multishell.saveSession(sessionFile) multishell.saveSession(sessionFile)
end end
function Process:resume(event, ...) function Process:resume(event, ...)
if coroutine.status(self.co) == 'dead' then if coroutine.status(self.co) == 'dead' then
return return
end end
if not self.filter or self.filter == event or event == "terminate" then if not self.filter or self.filter == event or event == "terminate" then
--term.redirect(self.terminal) --term.redirect(self.terminal)
local previousTerm = term.redirect(self.terminal) local previousTerm = term.redirect(self.terminal)
local previous = running local previous = running
running = self -- stupid shell set title running = self -- stupid shell set title
local ok, result = coroutine.resume(self.co, event, ...) local ok, result = coroutine.resume(self.co, event, ...)
running = previous running = previous
self.terminal = term.current() self.terminal = term.current()
term.redirect(previousTerm) term.redirect(previousTerm)
if ok then if ok then
self.filter = result self.filter = result
else else
printError(result) printError(result)
end end
return ok, result return ok, result
end end
end end
--[[ Install a multishell manager for the monitor ]]-- --[[ Install a multishell manager for the monitor ]]--
function multishell.getFocus() function multishell.getFocus()
return processes[#processes].uid return processes[#processes].uid
end end
function multishell.setFocus(uid) function multishell.setFocus(uid)
local process = Util.find(processes, 'uid', uid) local process = Util.find(processes, 'uid', uid)
if process then if process then
local lastFocused = processes[#processes] local lastFocused = processes[#processes]
if lastFocused ~= process then if lastFocused ~= process then
if lastFocused then if lastFocused then
lastFocused:focus(false) lastFocused:focus(false)
end end
Util.removeByValue(processes, process) Util.removeByValue(processes, process)
table.insert(processes, process) table.insert(processes, process)
process.container.canvas:raise() process.container.canvas:raise()
process:focus(true) process:focus(true)
process.container.canvas:dirty() process.container.canvas:dirty()
end end
return true return true
end end
return false return false
end end
function multishell.getTitle(uid) function multishell.getTitle(uid)
local process = Util.find(processes, 'uid', uid) local process = Util.find(processes, 'uid', uid)
if process then if process then
return process.title return process.title
end end
end end
function multishell.setTitle(uid, title) function multishell.setTitle(uid, title)
local process = Util.find(processes, 'uid', uid) local process = Util.find(processes, 'uid', uid)
if process then if process then
process.title = title or '' process.title = title or ''
process:focus(process == processes[#processes]) process:focus(process == processes[#processes])
end end
end end
function multishell.getCurrent() function multishell.getCurrent()
if running then if running then
return running.uid return running.uid
end end
end end
function multishell.getCount() function multishell.getCount()
return #processes return #processes
end end
function multishell.getTabs() function multishell.getTabs()
return processes return processes
end end
function multishell.launch(env, file, ...) function multishell.launch(env, file, ...)
return multishell.openTab({ return multishell.openTab({
path = file, path = file,
env = env, env = env,
title = 'shell', title = 'shell',
args = { ... }, args = { ... },
}) })
end end
function multishell.openTab(tabInfo) 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() --local previousTerm = term.current()
process:resume() process:resume()
--term.redirect(previousTerm) --term.redirect(previousTerm)
multishell.saveSession(sessionFile) multishell.saveSession(sessionFile)
return process.uid return process.uid
end end
function multishell.removeProcess(process) function multishell.removeProcess(process)
Util.removeByValue(processes, process) Util.removeByValue(processes, process)
process.container.canvas:removeLayer() process.container.canvas:removeLayer()
multishell.saveSession(sessionFile) multishell.saveSession(sessionFile)
redraw() redraw()
end end
function multishell.saveSession(filename) function multishell.saveSession(filename)
local t = { } local t = { }
for _,process in ipairs(processes) do for _,process in ipairs(processes) do
if process.path and not process.isShell then if process.path and not process.isShell then
table.insert(t, { table.insert(t, {
x = process.x, x = process.x,
y = process.y, y = process.y,
width = process.width - 2, width = process.width - 2,
height = process.height - 3, height = process.height - 3,
path = process.path, path = process.path,
args = process.args, args = process.args,
}) })
end end
end end
Util.writeTable(filename, t) Util.writeTable(filename, t)
end end
function multishell.loadSession(filename) function multishell.loadSession(filename)
local config = Util.readTable(filename) local config = Util.readTable(filename)
if config then if config then
for k = #config, 1, -1 do for k = #config, 1, -1 do
multishell.openTab(config[k]) multishell.openTab(config[k])
end end
end end
end end
function multishell.stop() function multishell.stop()
multishell._stop = true multishell._stop = true
end end
function multishell.start() 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 if event[1] == 'terminate' then
local focused = processes[#processes] local focused = processes[#processes]
if focused.isShell then if focused.isShell then
focused:resume('terminate') focused:resume('terminate')
else else
break break
end end
elseif event[1] == 'monitor_touch' then elseif event[1] == 'monitor_touch' then
local x, y = event[3], event[4] local x, y = event[3], event[4]
local key, process = getProcessAt(x, y) local key, process = getProcessAt(x, y)
if process then if process then
if key ~= #processes then if key ~= #processes then
multishell.setFocus(process.uid) multishell.setFocus(process.uid)
multishell.saveSession(sessionFile) multishell.saveSession(sessionFile)
end end
process:click(x - process.x + 1, y - process.y + 1) process:click(x - process.x + 1, y - process.y + 1)
else else
process = processes[#processes] process = processes[#processes]
if process and process.showSizers then if process and process.showSizers then
process.x = math.floor(x - (process.width) / 2) process.x = math.floor(x - (process.width) / 2)
process.y = y process.y = y
process:reposition() process:reposition()
process:drawSizers(true) process:drawSizers(true)
multishell.saveSession(sessionFile) multishell.saveSession(sessionFile)
end end
end end
elseif event[1] == 'mouse_click' or elseif event[1] == 'mouse_click' or
event[1] == 'mouse_up' then event[1] == 'mouse_up' then
local focused = processes[#processes] local focused = processes[#processes]
if not focused.isShell then if not focused.isShell then
multishell.setFocus(1) -- shell is always 1 multishell.setFocus(1) -- shell is always 1
else else
focused:resume(unpack(event)) focused:resume(unpack(event))
end end
elseif event[1] == 'char' or elseif event[1] == 'char' or
event[1] == 'key' or event[1] == 'key' or
event[1] == 'key_up' or event[1] == 'key_up' or
event[1] == 'paste' then event[1] == 'paste' then
local focused = processes[#processes] local focused = processes[#processes]
if focused then if focused then
focused:resume(unpack(event)) focused:resume(unpack(event))
end end
else else
for _,process in pairs(Util.shallowCopy(processes)) do for _,process in pairs(Util.shallowCopy(processes)) do
process:resume(unpack(event)) process:resume(unpack(event))
end end
end end
monitor.canvas:render(parentMon) monitor.canvas:render(parentMon)
local focused = processes[#processes] local focused = processes[#processes]
if focused then if focused then
focused.window.restoreCursor() focused.window.restoreCursor()
end end
end end
end end
--[[ Special shell process for launching programs ]]-- --[[ Special shell process for launching programs ]]--
local function addShell() local function addShell()
local process = setmetatable({ local process = setmetatable({
x = monDim.width, x = monDim.width,
y = monDim.height, y = monDim.height,
width = 1, width = 1,
height = 1, height = 1,
isShell = true, isShell = true,
uid = nextUID(), uid = nextUID(),
title = 'Terminal', title = 'Terminal',
}, { __index = Process }) }, { __index = Process })
function process:focus(focused) function process:focus(focused)
self.window.setVisible(focused) self.window.setVisible(focused)
if focused then if focused then
self.window.restoreCursor() self.window.restoreCursor()
else else
parentTerm.clear() parentTerm.clear()
parentTerm.setCursorBlink(false) parentTerm.setCursorBlink(false)
local str = 'Click screen for shell' local str = 'Click screen for shell'
write(parentTerm, write(parentTerm,
math.floor((termDim.width - #str) / 2), math.floor((termDim.width - #str) / 2),
math.floor(termDim.height / 2), math.floor(termDim.height / 2),
str) str)
end end
end end
function process:click() function process:click()
end end
process.container = Terminal.window(monitor, process.x, process.y+1, process.width, process.height, true) 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.window = window.create(parentTerm, 1, 1, termDim.width, termDim.height, true)
process.terminal = process.window process.terminal = process.window
process.co = coroutine.create(function() process.co = coroutine.create(function()
print('To run a program on the monitor, type "fg <program>"') print('To run a program on the monitor, type "fg <program>"')
print('To quit, type "exit"') print('To quit, type "exit"')
os.run(Util.shallowCopy(defaultEnv), shell.resolveProgram('shell')) os.run(Util.shallowCopy(defaultEnv), shell.resolveProgram('shell'))
multishell.stop() multishell.stop()
end) end)
table.insert(processes, process) table.insert(processes, process)
process:focus(true) process:focus(true)
local previousTerm = term.current() local previousTerm = term.current()
process:resume() process:resume()
term.redirect(previousTerm) term.redirect(previousTerm)
end end
addShell() addShell()

View File

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

View File

@@ -1,10 +1,11 @@
local Event = require('event') local Event = require('event')
local itemDB = require('core.itemDB') local itemDB = require('core.itemDB')
local UI = require('ui') local UI = require('ui')
local Util = require('util') local Util = require('util')
local device = _G.device local device = _G.device
local gps = _G.gps local gps = _G.gps
local multishell = _ENV.multishell
local glasses = device['plethora:glasses'] local glasses = device['plethora:glasses']
local scanner = device['plethora:scanner'] or local scanner = device['plethora:scanner'] or
@@ -14,11 +15,13 @@ local projecting = { }
local function getPoint() local function getPoint()
local pt = { gps.locate() } local pt = { gps.locate() }
return { if pt[1] then
x = pt[1], return {
y = pt[2], x = pt[1],
z = pt[3], y = pt[2],
} z = pt[3],
}
end
end end
local offset = getPoint() local offset = getPoint()
@@ -60,7 +63,7 @@ local page = UI.Page {
}, },
sortColumn = 'name', sortColumn = 'name',
accelerators = { accelerators = {
grid_select = 'noop', grid_select = 'inspect',
}, },
}, },
}, },
@@ -75,21 +78,25 @@ function page:scan()
local entry = itemDB:get(table.concat({ b.name, b.metadata }, ':')) local entry = itemDB:get(table.concat({ b.name, b.metadata }, ':'))
if not entry then if not entry then
local meta = scanner.getBlockMeta(b.x, b.y, b.z) local meta = scanner.getBlockMeta(b.x, b.y, b.z)
entry = itemDB:add({ if meta.name == b.name and meta.metadata == b.metadata then
name = meta.name, entry = itemDB:add({
displayName = meta.displayName, name = meta.name,
damage = meta.metadata, displayName = meta.displayName,
}) damage = meta.metadata,
})
end
end end
b.key = entry.displayName if entry then
if acc[b.key] then b.key = entry.displayName
acc[b.key].count = acc[b.key].count + 1 if acc[b.key] then
else acc[b.key].count = acc[b.key].count + 1
entry = Util.shallowCopy(entry) else
entry.lname = entry.displayName:lower() entry = Util.shallowCopy(entry)
entry.count = 1 entry.lname = entry.displayName:lower()
entry.key = b.key entry.count = 1
acc[b.key] = entry entry.key = b.key
acc[b.key] = entry
end
end end
throttle() throttle()
return acc return acc
@@ -121,43 +128,45 @@ function page.detail:show(blocks, entry)
local scanned = scanner.scan() local scanned = scanner.scan()
local pos = getPoint() local pos = getPoint()
blocks = Util.reduce(scanned, function(acc, b) if pos and offset then
if b.name == t.name and b.metadata == t.damage then blocks = Util.reduce(scanned, function(acc, b)
-- track block's world position if b.name == t.name and b.metadata == t.damage then
b.id = table.concat({ -- track block's world position
math.floor(pos.x + b.x), b.id = table.concat({
math.floor(pos.y + b.y), math.floor(pos.x + b.x),
math.floor(pos.z + b.z) }, ':') math.floor(pos.y + b.y),
acc[b.id] = b math.floor(pos.z + b.z) }, ':')
end acc[b.id] = b
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)
end end
b.box.setDepthTested(false) return acc
end end, { })
end
for _, b in pairs(projecting) do for _, b in pairs(blocks) do
if not blocks[b.id] then if not projecting[b.id] then
b.box.remove() projecting[b.id] = b
projecting[b.id] = nil 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 end
end end
@@ -178,7 +187,14 @@ function page:eventHandler(event)
elseif event.type == 'scan' then elseif event.type == 'scan' then
self:scan() 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()) self.detail:show(self.blocks, self.grid:getSelected())
elseif event.type == 'cancel' then elseif event.type == 'cancel' then

View File

@@ -18,22 +18,13 @@ local projecting = { }
local offset local offset
local canvas = glasses and intro and glasses.canvas3d().create() local canvas = glasses and intro and glasses.canvas3d().create()
local config = Config.load('Sensor', { local config = Config.load('Sensor')
ignore = { }
})
local page = UI.Page { local page = UI.Page {
tabs = UI.Tabs { tabs = UI.Tabs {
listing = UI.Tab { listing = UI.Tab {
tabTitle = 'Listing', tabTitle = 'Listing',
menuBar = UI.MenuBar {
buttons = {
{ text = 'Ignore', event = 'ignore' },
{ text = 'Details', event = 'detail' },
},
},
grid = UI.ScrollingGrid { grid = UI.ScrollingGrid {
y = 2,
columns = { columns = {
{ heading = 'Name', key = 'displayName' }, { heading = 'Name', key = 'displayName' },
{ heading = 'X', key = 'x', width = 3, align = 'right' }, { heading = 'X', key = 'x', width = 3, align = 'right' },
@@ -45,14 +36,7 @@ local page = UI.Page {
}, },
summary = UI.Tab { summary = UI.Tab {
tabTitle = 'Summary', tabTitle = 'Summary',
menuBar = UI.MenuBar {
buttons = {
{ text = 'Projector', event = 'project' },
{ text = 'Ignore', event = 'ignore' },
},
},
grid = UI.ScrollingGrid { grid = UI.ScrollingGrid {
y = 2,
columns = { columns = {
{ heading = 'Name', key = 'displayName' }, { heading = 'Name', key = 'displayName' },
{ heading = 'Count', key = 'count', width = 5, align = 'right' }, { heading = 'Count', key = 'count', width = 5, align = 'right' },
@@ -134,13 +118,6 @@ local function project(entities)
end end
end end
local function ignoreEntity(entity)
if entity then
config.ignore[entity.name] = true
Config.update('Sensor', config)
end
end
function detail:enable(entity) function detail:enable(entity)
local function update() local function update()
local t = { } local t = { }
@@ -194,8 +171,6 @@ end
function listing:enable() function listing:enable()
self.handler = Event.onInterval(.5, function() self.handler = Event.onInterval(.5, function()
local entities = sensor.sense() local entities = sensor.sense()
Util.filterInplace(entities, function(e) return not config.ignore[e.name] end)
self.grid:setValues(entities) self.grid:setValues(entities)
self.grid:draw() self.grid:draw()
self:sync() self:sync()
@@ -209,14 +184,11 @@ function listing:disable()
end end
function listing:eventHandler(event) 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() local selected = self.grid:getSelected()
if selected then if selected then
UI:setPage(detail, selected) UI:setPage(detail, selected)
end end
elseif event.type == 'ignore' then
ignoreEntity(self.grid:getSelected())
end end
return UI.Tab.eventHandler(self, event) return UI.Tab.eventHandler(self, event)
@@ -225,7 +197,6 @@ end
function summary:enable() function summary:enable()
self.handler = Event.onInterval(.5, function() self.handler = Event.onInterval(.5, function()
local entities = sensor.sense() local entities = sensor.sense()
Util.filterInplace(entities, function(e) return not config.ignore[e.name] end)
local t = { } local t = { }
local highlight = { } local highlight = { }
@@ -265,10 +236,7 @@ function summary.grid:getRowTextColor(row, selected)
end end
function summary:eventHandler(event) function summary:eventHandler(event)
if event.type == 'ignore' then if event.type == 'grid_select' then
ignoreEntity(self.grid:getSelected())
elseif event.type == 'project' or event.type == 'grid_select' then
local selected = self.grid:getSelected() local selected = self.grid:getSelected()
if selected then if selected then
self.target = selected.name self.target = selected.name

View File

@@ -1,11 +1,11 @@
local Angle = { } local Angle = { }
function Angle.towards(x, y, z) 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 end
function Angle.away(x, y, z) function Angle.away(x, y, z)
return math.deg(math.atan2(x, -z)), 0 return math.deg(math.atan2(x, -z)), 0
end end
return Angle return Angle

View File

@@ -1,27 +1,27 @@
local Mobs = { } local Mobs = { }
local hostiles = { local hostiles = {
BabySkeleton = true, BabySkeleton = true,
BabyZombie = true, BabyZombie = true,
Bat = true, Bat = true,
Blaze = true, Blaze = true,
CaveSpider = true, CaveSpider = true,
Creeper = true, Creeper = true,
Ghast = true, Ghast = true,
Husk = true, Husk = true,
LavaSlime = true, LavaSlime = true,
PigZombie = true, PigZombie = true,
Skeleton = true, Skeleton = true,
Slime = true, Slime = true,
Spider = true, Spider = true,
Witch = true, Witch = true,
WitherSkeleton = true, WitherSkeleton = true,
Zombie = true, Zombie = true,
ZombieVillager = true, ZombieVillager = true,
} }
function Mobs.getNames() function Mobs.getNames()
return hostiles return hostiles
end end
return Mobs return Mobs

View File

@@ -7,149 +7,149 @@ local NONE = "none"
local ASYNC = "async" local ASYNC = "async"
local function call_handler(handler, params) local function call_handler(handler, params)
if handler then if handler then
return handler(unpack(params)) return handler(unpack(params))
end end
end end
local function create_transition(name) local function create_transition(name)
local can, to, from, params local can, to, from, params
local function transition(self, ...) local function transition(self, ...)
if self.asyncState == NONE then if self.asyncState == NONE then
can, to = self:can(name) can, to = self:can(name)
from = self.current from = self.current
params = { self, name, from, to, ...} params = { self, name, from, to, ...}
if not can then return false end if not can then return false end
self.currentTransitioningEvent = name self.currentTransitioningEvent = name
local beforeReturn = call_handler(self["onbefore" .. name], params) local beforeReturn = call_handler(self["onbefore" .. name], params)
local leaveReturn = call_handler(self["onleave" .. from], params) local leaveReturn = call_handler(self["onleave" .. from], params)
if beforeReturn == false or leaveReturn == false then if beforeReturn == false or leaveReturn == false then
return false return false
end end
self.asyncState = name .. "WaitingOnLeave" self.asyncState = name .. "WaitingOnLeave"
if leaveReturn ~= ASYNC then if leaveReturn ~= ASYNC then
transition(self, ...) transition(self, ...)
end end
return true return true
elseif self.asyncState == name .. "WaitingOnLeave" then elseif self.asyncState == name .. "WaitingOnLeave" then
self.current = to 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 if enterReturn ~= ASYNC then
transition(self, ...) transition(self, ...)
end end
return true return true
elseif self.asyncState == name .. "WaitingOnEnter" then elseif self.asyncState == name .. "WaitingOnEnter" then
call_handler(self["onafter" .. name] or self["on" .. name], params) call_handler(self["onafter" .. name] or self["on" .. name], params)
call_handler(self["onstatechange"], params) call_handler(self["onstatechange"], params)
self.asyncState = NONE self.asyncState = NONE
self.currentTransitioningEvent = nil self.currentTransitioningEvent = nil
return true return true
else else
if string.find(self.asyncState, "WaitingOnLeave") or string.find(self.asyncState, "WaitingOnEnter") then if string.find(self.asyncState, "WaitingOnLeave") or string.find(self.asyncState, "WaitingOnEnter") then
self.asyncState = NONE self.asyncState = NONE
transition(self, ...) transition(self, ...)
return true return true
end end
end end
self.currentTransitioningEvent = nil self.currentTransitioningEvent = nil
return false return false
end end
return transition return transition
end end
local function add_to_map(map, event) local function add_to_map(map, event)
if type(event.from) == 'string' then if type(event.from) == 'string' then
map[event.from] = event.to map[event.from] = event.to
else else
for _, from in ipairs(event.from) do for _, from in ipairs(event.from) do
map[from] = event.to map[from] = event.to
end end
end end
end end
function machine.create(options) function machine.create(options)
assert(options.events) assert(options.events)
local fsm = {} local fsm = {}
setmetatable(fsm, machine) setmetatable(fsm, machine)
fsm.options = options fsm.options = options
fsm.current = options.initial or 'none' fsm.current = options.initial or 'none'
fsm.asyncState = NONE fsm.asyncState = NONE
fsm.events = {} fsm.events = {}
for _, event in ipairs(options.events or {}) do for _, event in ipairs(options.events or {}) do
local name = event.name local name = event.name
fsm[name] = fsm[name] or create_transition(name) fsm[name] = fsm[name] or create_transition(name)
fsm.events[name] = fsm.events[name] or { map = {} } fsm.events[name] = fsm.events[name] or { map = {} }
add_to_map(fsm.events[name].map, event) add_to_map(fsm.events[name].map, event)
end end
for name, callback in pairs(options.callbacks or {}) do for name, callback in pairs(options.callbacks or {}) do
fsm[name] = callback fsm[name] = callback
end end
return fsm return fsm
end end
function machine:is(state) function machine:is(state)
return self.current == state return self.current == state
end end
function machine:can(e) function machine:can(e)
local event = self.events[e] local event = self.events[e]
local to = event and event.map[self.current] or event.map['*'] local to = event and event.map[self.current] or event.map['*']
return to ~= nil, to return to ~= nil, to
end end
function machine:cannot(e) function machine:cannot(e)
return not self:can(e) return not self:can(e)
end end
function machine:todot(filename) function machine:todot(filename)
local dotfile = io.open(filename,'w') local dotfile = io.open(filename,'w')
dotfile:write('digraph {\n') dotfile:write('digraph {\n')
local transition = function(event,from,to) local transition = function(event,from,to)
dotfile:write(string.format('%s -> %s [label=%s];\n',from,to,event)) dotfile:write(string.format('%s -> %s [label=%s];\n',from,to,event))
end end
for _, event in pairs(self.options.events) do for _, event in pairs(self.options.events) do
if type(event.from) == 'table' then if type(event.from) == 'table' then
for _, from in ipairs(event.from) do for _, from in ipairs(event.from) do
transition(event.name,from,event.to) transition(event.name,from,event.to)
end end
else else
transition(event.name,event.from,event.to) transition(event.name,event.from,event.to)
end end
end end
dotfile:write('}\n') dotfile:write('}\n')
dotfile:close() dotfile:close()
end end
function machine:transition(event) function machine:transition(event)
if self.currentTransitioningEvent == event then if self.currentTransitioningEvent == event then
return self[self.currentTransitioningEvent](self) return self[self.currentTransitioningEvent](self)
end end
end end
function machine:cancelTransition(event) function machine:cancelTransition(event)
if self.currentTransitioningEvent == event then if self.currentTransitioningEvent == event then
self.asyncState = NONE self.asyncState = NONE
self.currentTransitioningEvent = nil self.currentTransitioningEvent = nil
end end
end end
machine.NONE = NONE machine.NONE = NONE

View File

@@ -3,28 +3,28 @@ local GPS = require('gps')
local device = _G.device local device = _G.device
if device.neuralInterface and device.wireless_modem then if device.neuralInterface and device.wireless_modem then
local ni = require('neural.interface') local ni = require('neural.interface')
device.neuralInterface.goTo = function(x, y, z) device.neuralInterface.goTo = function(x, y, z)
local pt = GPS.locate(2) local pt = GPS.locate(2)
if pt then if pt then
return pcall(function() return pcall(function()
if device.neuralInterface.walk then if device.neuralInterface.walk then
local gpt = { local gpt = {
x = x - pt.x, x = x - pt.x,
y = 0, y = 0,
z = z - pt.z, z = z - pt.z,
} }
gpt.x = math.min(math.max(gpt.x, -15), 15) gpt.x = math.min(math.max(gpt.x, -15), 15)
gpt.z = math.min(math.max(gpt.z, -15), 15) gpt.z = math.min(math.max(gpt.z, -15), 15)
return device.neuralInterface.walk(gpt.x, gpt.y, gpt.z, 2) return device.neuralInterface.walk(gpt.x, gpt.y, gpt.z, 2)
elseif ni.launch then elseif ni.launch then
local y, p = ni.yap(pt, { x = x, y = y + 3, z = z }) local y, p = ni.yap(pt, { x = x, y = y + 3, z = z })
ni.look(y, 0) ni.look(y, 0)
return ni.launch(y, p, 1.5) return ni.launch(y, p, 1.5)
end end
end) end)
end end
return false, 'No GPS' return false, 'No GPS'
end end
end end

View File

@@ -1,70 +1,70 @@
local opus = { local opus = {
'fffff00', 'fffff00',
'ffff07000', 'ffff07000',
'ff00770b00 4444', 'ff00770b00 4444',
'ff077777444444444', 'ff077777444444444',
'f07777744444444444', 'f07777744444444444',
'f0000777444444444', 'f0000777444444444',
'070000111744444', '070000111744444',
'777770000', '777770000',
'7777000000', '7777000000',
'70700000000', '70700000000',
'077000000000', '077000000000',
} }
local hex = { local hex = {
['0'] = 0xF0F0F04F, ['0'] = 0xF0F0F04F,
['1'] = 0xF2B2334F, ['1'] = 0xF2B2334F,
['2'] = 0xE57FD84F, ['2'] = 0xE57FD84F,
['3'] = 0x99B2F24F, ['3'] = 0x99B2F24F,
['4'] = 0xDEDE6C4F, ['4'] = 0xDEDE6C4F,
['5'] = 0x7FCC194F, ['5'] = 0x7FCC194F,
['6'] = 0xF2B2CC4F, ['6'] = 0xF2B2CC4F,
['7'] = 0x4C4C4C4F, ['7'] = 0x4C4C4C4F,
['8'] = 0x9999994F, ['8'] = 0x9999994F,
['9'] = 0x4C99B24F, ['9'] = 0x4C99B24F,
['a'] = 0xB266E54F, ['a'] = 0xB266E54F,
['b'] = 0x3366CC4F, ['b'] = 0x3366CC4F,
['c'] = 0x7F664C4F, ['c'] = 0x7F664C4F,
['d'] = 0x57A64E4F, ['d'] = 0x57A64E4F,
['e'] = 0xCC4C4C4F, ['e'] = 0xCC4C4C4F,
-- ['f'] = 0x191919FF, -- transparent -- ['f'] = 0x191919FF, -- transparent
} }
local function update() local function update()
local canvas = device['plethora:glasses'] and device['plethora:glasses'].canvas() local canvas = device['plethora:glasses'] and device['plethora:glasses'].canvas()
if canvas then if canvas then
local Tween = require('ui.tween') local Tween = require('ui.tween')
canvas.clear() canvas.clear()
local w, h = canvas.getSize() local w, h = canvas.getSize()
local pos = { x = w / 2, y = h / 2 - 30 } local pos = { x = w / 2, y = h / 2 - 30 }
local group = canvas.addGroup(pos) local group = canvas.addGroup(pos)
local function drawLine(k, line) local function drawLine(k, line)
for i = 1, #line do for i = 1, #line do
local pix = hex[line:sub(i, i)] local pix = hex[line:sub(i, i)]
if pix then if pix then
group.addRectangle(i*1.5, k*2.25, 1.5, 2.25, pix) group.addRectangle(i*1.5, k*2.25, 1.5, 2.25, pix)
end end
end end
end end
for k,line in ipairs(opus) do for k,line in ipairs(opus) do
drawLine(k, line) drawLine(k, line)
end end
os.sleep(.5) os.sleep(.5)
local tween = Tween.new(40, pos, { x = w - 60, y = h - 30 }, 'outBounce') local tween = Tween.new(40, pos, { x = w - 60, y = h - 30 }, 'outBounce')
repeat repeat
local finished = tween:update(1) local finished = tween:update(1)
os.sleep(0) os.sleep(0)
group.setPosition(pos.x, pos.y) group.setPosition(pos.x, pos.y)
until finished until finished
end end
end end
kernel.run({ kernel.run({
title = 'opus', title = 'opus',
env = _ENV, env = _ENV,
hidden = true, hidden = true,
fn = update, fn = update,
}) })

Some files were not shown because too many files have changed in this diff Show More