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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,37 +7,37 @@ local device = _G.device
local Adapter = class()
function Adapter:init(args)
if args.side then
local inventory = device[args.side]
if inventory then
Util.merge(self, inventory)
end
end
if args.side then
local inventory = device[args.side]
if inventory then
Util.merge(self, inventory)
end
end
end
function Adapter:listItems(throttle)
local cache = { }
throttle = throttle or Util.throttle()
local cache = { }
throttle = throttle or Util.throttle()
for k,v in pairs(self.list()) do
if v.count > 0 then
local key = table.concat({ v.name, v.damage, v.nbtHash }, ':')
for k,v in pairs(self.list()) do
if v.count > 0 then
local key = table.concat({ v.name, v.damage, v.nbtHash }, ':')
local entry = cache[key]
if entry then
entry.count = entry.count + v.count
else
cache[key] = itemDB:get(v, function() return self.getItemMeta(k) end)
end
throttle()
end
end
local entry = cache[key]
if entry then
entry.count = entry.count + v.count
else
cache[key] = itemDB:get(v, function() return self.getItemMeta(k) end)
end
throttle()
end
end
-- TODO: cache number of slots, free slots, used slots
-- useful for when inserting into chests
-- ie. insert only if chest does not have item and has free slots
-- TODO: cache number of slots, free slots, used slots
-- useful for when inserting into chests
-- ie. insert only if chest does not have item and has free slots
self.cache = cache
self.cache = cache
end
return Adapter

File diff suppressed because it is too large Load Diff

View File

@@ -7,68 +7,68 @@ local free = { }
local function createTask(fn)
local task = table.remove(free)
if not task then
task = {
fn = fn,
co = coroutine.create(function()
local args = { }
while true do
pcall(task.fn, table.unpack(args))
task.dead = true
table.insert(free, task)
args = { coroutine.yield() }
end
end)
}
else
task.dead = nil
task.fn = fn
end
if not task then
task = {
fn = fn,
co = coroutine.create(function()
local args = { }
while true do
pcall(task.fn, table.unpack(args))
task.dead = true
table.insert(free, task)
args = { coroutine.yield() }
end
end)
}
else
task.dead = nil
task.fn = fn
end
return task
end
function TaskRunner:init(args)
self.tasks = { }
self.errorMsg = 'Task failed: '
self.tasks = { }
self.errorMsg = 'Task failed: '
for k,v in pairs(args or { }) do
self[k] = v
end
for k,v in pairs(args or { }) do
self[k] = v
end
end
function TaskRunner:add(fn)
table.insert(self.tasks, createTask(fn))
table.insert(self.tasks, createTask(fn))
end
function TaskRunner:run()
if #self.tasks > 0 then
local event = { }
if #self.tasks > 0 then
local event = { }
while true do
for n = #self.tasks, 1, -1 do
local task = self.tasks[n]
if task.filter == nil or task.filter == event[1] or event[1] == "terminate" then
local ok, param = coroutine.resume(task.co, table.unpack(event))
if not ok then
self:onError(param)
else
task.filter = param
end
if task.dead then
table.remove(self.tasks, n)
end
end
end
if #self.tasks == 0 then
break
end
event = { os.pullEventRaw() }
end
end
while true do
for n = #self.tasks, 1, -1 do
local task = self.tasks[n]
if task.filter == nil or task.filter == event[1] or event[1] == "terminate" then
local ok, param = coroutine.resume(task.co, table.unpack(event))
if not ok then
self:onError(param)
else
task.filter = param
end
if task.dead then
table.remove(self.tasks, n)
end
end
end
if #self.tasks == 0 then
break
end
event = { os.pullEventRaw() }
end
end
end
function TaskRunner:onError(msg)
_G._syslog(msg.errorMsg .. msg)
_G._syslog(msg.errorMsg .. msg)
end
return TaskRunner

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
--[[
For initially setting up large amounts of storage chests.
For initially setting up large amounts of storage chests.
]]
local Util = require('util')
@@ -11,17 +11,17 @@ local st = args[1] or error('Specify a storage type (ie. minecraft:chest)')
local config = { }
peripheral.find(st, function(n)
config[n] = {
name = n,
category = 'storage',
mtype = 'storage',
}
config[n] = {
name = n,
category = 'storage',
mtype = 'storage',
}
end)
print('Found ' .. Util.size(config))
if Util.size(config) == 0 then
error('Invalid peripheral type')
error('Invalid peripheral type')
end
Util.writeTable('usr/config/storageGen', config)

View File

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

View File

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

View File

@@ -1,20 +1,20 @@
{
[ "9302912a2d9794a47241faefc475335b4e07a581" ] = {
title = "Remote",
category = "Apps",
run = "MiloRemote",
requires = "neuralInterface",
iconExt = "\0304\031f\135\129\0314\128\128\031f\130\030f\128\
[ "9302912a2d9794a47241faefc475335b4e07a581" ] = {
title = "Remote",
category = "Apps",
run = "MiloRemote",
requires = "neuralInterface",
iconExt = "\0304\031f\135\129\0314\128\128\031f\130\030f\128\
\031f\128\031c\159\149\0300\0317\143\0304\031c\149\030f\0314\133\
\031f\128\030c\0310\142\030f\031c\149\030c\0310\139\030f\031c\149\031f\128",
},
[ "eea426f9baef72a8fcefd091e0cec5ab94a76698" ] = {
title = "Milo",
category = "Apps",
run = "MiloLocal",
requires = 'advancedTurtle',
iconExt = "\0304\031f\135\129\0314\128\128\031f\130\030f\128\
},
[ "eea426f9baef72a8fcefd091e0cec5ab94a76698" ] = {
title = "Milo",
category = "Apps",
run = "MiloLocal",
requires = 'advancedTurtle',
iconExt = "\0304\031f\135\129\0314\128\128\031f\130\030f\128\
\031f\128\031c\159\149\0300\0317\143\0304\031c\149\030f\0314\133\
\031f\128\030c\0310\142\030f\031c\149\030c\0310\139\030f\031c\149\031f\128",
},
},
}

View File

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

View File

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

View File

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

View File

@@ -7,126 +7,126 @@ local Util = require('util')
local colors = _G.colors
local craftPage = UI.Page {
titleBar = UI.TitleBar { },
wizard = UI.Wizard {
y = 2, ey = -2,
pages = {
quantity = UI.WizardPage {
index = 1,
text = UI.Text {
x = 6, y = 3,
value = 'Quantity',
},
count = UI.TextEntry {
x = 15, y = 3, width = 10,
limit = 6,
value = 1,
},
ejectText = UI.Text {
x = 6, y = 4,
value = 'Eject',
},
eject = UI.Chooser {
x = 15, y = 4, width = 7,
value = true,
nochoice = 'No',
choices = {
{ name = 'Yes', value = true },
{ name = 'No', value = false },
},
},
},
resources = UI.WizardPage {
index = 2,
grid = UI.ScrollingGrid {
y = 2, ey = -2,
columns = {
{ heading = 'Name', key = 'displayName' },
{ heading = 'Total', key = 'total' , width = 5 },
{ heading = 'Used', key = 'used' , width = 5 },
{ heading = 'Need', key = 'need' , width = 5 },
},
sortColumn = 'displayName',
},
},
},
},
titleBar = UI.TitleBar { },
wizard = UI.Wizard {
y = 2, ey = -2,
pages = {
quantity = UI.WizardPage {
index = 1,
text = UI.Text {
x = 6, y = 3,
value = 'Quantity',
},
count = UI.TextEntry {
x = 15, y = 3, width = 10,
limit = 6,
value = 1,
},
ejectText = UI.Text {
x = 6, y = 4,
value = 'Eject',
},
eject = UI.Chooser {
x = 15, y = 4, width = 7,
value = true,
nochoice = 'No',
choices = {
{ name = 'Yes', value = true },
{ name = 'No', value = false },
},
},
},
resources = UI.WizardPage {
index = 2,
grid = UI.ScrollingGrid {
y = 2, ey = -2,
columns = {
{ heading = 'Name', key = 'displayName' },
{ heading = 'Total', key = 'total' , width = 5 },
{ heading = 'Used', key = 'used' , width = 5 },
{ heading = 'Need', key = 'need' , width = 5 },
},
sortColumn = 'displayName',
},
},
},
},
}
function craftPage:enable(item)
self.item = item
self:focusFirst()
self.titleBar.title = itemDB:getName(item)
self.item = item
self:focusFirst()
self.titleBar.title = itemDB:getName(item)
-- self.wizard.pages.quantity.eject.value = true
UI.Page.enable(self)
UI.Page.enable(self)
end
function craftPage.wizard.pages.resources.grid:getDisplayValues(row)
local function dv(v)
return v == 0 and '' or Util.toBytes(v)
end
row = Util.shallowCopy(row)
row.total = Util.toBytes(row.total)
row.used = dv(row.used)
row.need = dv(row.need)
return row
local function dv(v)
return v == 0 and '' or Util.toBytes(v)
end
row = Util.shallowCopy(row)
row.total = Util.toBytes(row.total)
row.used = dv(row.used)
row.need = dv(row.need)
return row
end
function craftPage.wizard.pages.resources.grid:getRowTextColor(row, selected)
if row.need > 0 then
return colors.orange
end
return UI.Grid:getRowTextColor(row, selected)
if row.need > 0 then
return colors.orange
end
return UI.Grid:getRowTextColor(row, selected)
end
function craftPage.wizard:eventHandler(event)
if event.type == 'nextView' then
local count = tonumber(self.pages.quantity.count.value)
if not count or count <= 0 then
self.pages.quantity.count.backgroundColor = colors.red
self.pages.quantity.count:draw()
return false
end
self.pages.quantity.count.backgroundColor = colors.black
end
return UI.Wizard.eventHandler(self, event)
if event.type == 'nextView' then
local count = tonumber(self.pages.quantity.count.value)
if not count or count <= 0 then
self.pages.quantity.count.backgroundColor = colors.red
self.pages.quantity.count:draw()
return false
end
self.pages.quantity.count.backgroundColor = colors.black
end
return UI.Wizard.eventHandler(self, event)
end
function craftPage.wizard.pages.resources:enable()
local items = Milo:listItems()
local count = tonumber(self.parent.quantity.count.value)
local recipe = Craft.findRecipe(craftPage.item)
if recipe then
local ingredients = Craft.getResourceList4(recipe, items, count)
for _,v in pairs(ingredients) do
v.displayName = itemDB:getName(v)
end
self.grid:setValues(ingredients)
else
self.grid:setValues({ })
end
return UI.WizardPage.enable(self)
local items = Milo:listItems()
local count = tonumber(self.parent.quantity.count.value)
local recipe = Craft.findRecipe(craftPage.item)
if recipe then
local ingredients = Craft.getResourceList4(recipe, items, count)
for _,v in pairs(ingredients) do
v.displayName = itemDB:getName(v)
end
self.grid:setValues(ingredients)
else
self.grid:setValues({ })
end
return UI.WizardPage.enable(self)
end
function craftPage:eventHandler(event)
if event.type == 'cancel' then
UI:setPreviousPage()
if event.type == 'cancel' then
UI:setPreviousPage()
elseif event.type == 'accept' then
local item = Util.shallowCopy(self.item)
item.requested = tonumber(self.wizard.pages.quantity.count.value)
item.forceCrafting = true
if self.wizard.pages.quantity.eject.value then
item.callback = function(request)
Milo:eject(item, request.requested)
end
end
Milo:requestCrafting(item)
UI:setPreviousPage()
else
return UI.Page.eventHandler(self, event)
end
return true
elseif event.type == 'accept' then
local item = Util.shallowCopy(self.item)
item.requested = tonumber(self.wizard.pages.quantity.count.value)
item.forceCrafting = true
if self.wizard.pages.quantity.eject.value then
item.callback = function(request)
Milo:eject(item, request.requested)
end
end
Milo:requestCrafting(item)
UI:setPreviousPage()
else
return UI.Page.eventHandler(self, event)
end
return true
end
UI:addPage('craft', craftPage)

View File

@@ -12,27 +12,27 @@ Any items placed in this chest will be imported into storage.
]]
local inputChestWizardPage = UI.WizardPage {
title = 'Input Chest',
index = 2,
backgroundColor = colors.cyan,
[1] = UI.TextArea {
x = 2, ex = -2, y = 2, ey = -2,
value = string.format(template, Ansi.yellow, Ansi.reset),
},
title = 'Input Chest',
index = 2,
backgroundColor = colors.cyan,
[1] = UI.TextArea {
x = 2, ex = -2, y = 2, ey = -2,
value = string.format(template, Ansi.yellow, Ansi.reset),
},
}
function inputChestWizardPage:isValidType(node)
local m = device[node.name]
return m and m.pullItems and {
name = 'Input Chest',
value = 'input',
category = 'custom',
help = 'Sends all items to storage',
}
local m = device[node.name]
return m and m.pullItems and {
name = 'Input Chest',
value = 'input',
category = 'custom',
help = 'Sends all items to storage',
}
end
function inputChestWizardPage:isValidFor(node)
return node.mtype == 'input'
return node.mtype == 'input'
end
UI:getPage('nodeWizard').wizard:add({ inputChest = inputChestWizardPage })

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -111,7 +111,14 @@ function pages.confirmation:validate()
}
for k,v in pairs(inventory) do
recipe.ingredients[k] = itemDB:makeKey(v)
if v.count == 1 then
recipe.ingredients[k] = itemDB:makeKey(v)
else
recipe.ingredients[k] = {
key = itemDB:makeKey(v),
count = v.count,
}
end
end
Milo:saveMachineRecipe(recipe, result, machine.name)

View File

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

View File

@@ -14,29 +14,29 @@ Add all speed upgrades possible.
]]
local wizardPage = UI.WizardPage {
title = 'Mass Storage',
index = 2,
backgroundColor = colors.cyan,
[1] = UI.TextArea {
x = 2, ex = -2, y = 2, ey = -2,
value = string.format(template, Ansi.red, Ansi.reset),
},
title = 'Mass Storage',
index = 2,
backgroundColor = colors.cyan,
[1] = UI.TextArea {
x = 2, ex = -2, y = 2, ey = -2,
value = string.format(template, Ansi.red, Ansi.reset),
},
}
function wizardPage:isValidFor(node)
if node.mtype == 'storage' then
local m = device[node.name]
return m and m.listAvailableItems
end
if node.mtype == 'storage' then
local m = device[node.name]
return m and m.listAvailableItems
end
end
function wizardPage:setNode(node)
self.node = node
self.node = node
end
function wizardPage:validate()
self.node.adapterType = 'massAdapter'
return true
self.node.adapterType = 'massAdapter'
return true
end
-- disable until a way is found to transfer between 2 non-transferrable nodes

View File

@@ -4,18 +4,18 @@ local args = { ... }
local context = args[1]
local function learn()
context:sendRequest({
request = 'craft',
slot = 15,
})
context:sendRequest({
request = 'craft',
slot = 15,
})
end
context.responseHandlers['craft'] = function(response)
if response.success then
Sound.play('entity.item.pickup')
else
Sound.play('entity.villager.no')
end
if response.success then
Sound.play('entity.item.pickup')
else
Sound.play('entity.villager.no')
end
end
return {

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,42 +5,42 @@ local context = Milo:getContext()
local device = _G.device
local function craftHandler(user, message, socket)
local function craft()
local slots = {
[1] = 1, [2] = 2, [3] = 3,
[5] = 10, [6] = 11, [7] = 12,
[9] = 19, [10] = 20, [11] = 21,
}
local inventory = device[user .. ':inventory']
if inventory then
for k, v in pairs(slots) do
inventory.pushItems(context.turtleInventory.name, v + message.slot - 1, 1, k)
end
local recipe, msg = Milo:learnRecipe()
if recipe then
socket:write({
type = 'craft',
msg = 'Learned: ' .. itemDB:getName(recipe),
success = true,
})
for k,v in pairs(context.turtleInventory.adapter.list()) do
inventory.pullItems(context.turtleInventory.name, k, v.count)
end
else
socket:write({
type = 'craft',
msg = msg,
})
for k, v in pairs(slots) do
inventory.pullItems(context.turtleInventory.name, k, 1, v + message.slot - 1)
end
end
end
end
local function craft()
local slots = {
[1] = 1, [2] = 2, [3] = 3,
[5] = 10, [6] = 11, [7] = 12,
[9] = 19, [10] = 20, [11] = 21,
}
local inventory = device[user .. ':inventory']
if inventory then
for k, v in pairs(slots) do
inventory.pushItems(context.turtleInventory.name, v + message.slot - 1, 1, k)
end
local recipe, msg = Milo:learnRecipe()
if recipe then
socket:write({
type = 'craft',
msg = 'Learned: ' .. itemDB:getName(recipe),
success = true,
})
for k,v in pairs(context.turtleInventory.adapter.list()) do
inventory.pullItems(context.turtleInventory.name, k, v.count)
end
else
socket:write({
type = 'craft',
msg = msg,
})
for k, v in pairs(slots) do
inventory.pullItems(context.turtleInventory.name, k, 1, v + message.slot - 1)
end
end
end
end
Milo:queueRequest({ }, craft)
Milo:queueRequest({ }, craft)
end
return {
remoteHandler = { callback = craftHandler, messages = { craft = true } }
remoteHandler = { callback = craftHandler, messages = { craft = true } }
}

View File

@@ -2,39 +2,39 @@ local itemDB = require('core.itemDB')
local Milo = require('milo')
local ReplenishTask = {
name = 'replenish',
priority = 60,
name = 'replenish',
priority = 60,
}
function ReplenishTask:cycle(context)
for k,res in pairs(context.resources) do
if res.low then
local item = itemDB:splitKey(k)
item.key = k
for k,res in pairs(context.resources) do
if res.low then
local item = itemDB:splitKey(k)
item.key = k
local _, count = Milo:getMatches(item, res)
local _, count = Milo:getMatches(item, res)
if count < res.low then
local nbtHash
if not res.ignoreNbtHash then
nbtHash = item.nbtHash
end
Milo:requestCrafting({
name = item.name,
damage = res.ignoreDamage and 0 or item.damage,
nbtHash = nbtHash,
requested = res.low - count,
count = count,
replenish = true,
})
else
local request = context.craftingQueue[itemDB:makeKey(item)]
if request and request.replenish then
--request.count = request.crafted
end
end
end
end
if count < res.low then
local nbtHash
if not res.ignoreNbtHash then
nbtHash = item.nbtHash
end
Milo:requestCrafting({
name = item.name,
damage = res.ignoreDamage and 0 or item.damage,
nbtHash = nbtHash,
requested = res.low - count,
count = count,
replenish = true,
})
else
local request = context.craftingQueue[itemDB:makeKey(item)]
if request and request.replenish then
--request.count = request.crafted
end
end
end
end
end
Milo:registerTask(ReplenishTask)

View File

@@ -8,66 +8,66 @@ local context = Milo:getContext()
local speakerNode = context.storage:getSingleNode('speaker')
if speakerNode then
Sound.setVolume(speakerNode.volume)
Sound.setVolume(speakerNode.volume)
end
local wizardPage = UI.WizardPage {
title = 'Speaker',
index = 2,
backgroundColor = colors.cyan,
[1] = UI.Text {
x = 2, y = 2,
textColor = colors.yellow,
value = 'Set the volume for sound effects',
},
form = UI.Form {
x = 2, ex = -2, y = 3, ey = -2,
manualControls = true,
volume = UI.TextEntry {
formLabel = 'Volume', formKey = 'volume',
width = 5, limit = 3,
validate = 'numeric',
help = 'A value from 0 (mute) to 1 (loud)',
},
testSound = UI.Button {
x = 15, y = 2,
text = 'Test', event = 'test_sound',
help = 'Test sound volume',
},
},
title = 'Speaker',
index = 2,
backgroundColor = colors.cyan,
[1] = UI.Text {
x = 2, y = 2,
textColor = colors.yellow,
value = 'Set the volume for sound effects',
},
form = UI.Form {
x = 2, ex = -2, y = 3, ey = -2,
manualControls = true,
volume = UI.TextEntry {
formLabel = 'Volume', formKey = 'volume',
width = 5, limit = 3,
validate = 'numeric',
help = 'A value from 0 (mute) to 1 (loud)',
},
testSound = UI.Button {
x = 15, y = 2,
text = 'Test', event = 'test_sound',
help = 'Test sound volume',
},
},
}
function wizardPage:setNode(node)
self.form:setValues(node)
self.form:setValues(node)
end
function wizardPage:saveNode(node)
Sound.setVolume(node.volume)
Sound.setVolume(node.volume)
end
function wizardPage:validate()
return self.form:save()
return self.form:save()
end
function wizardPage:isValidType(node)
local m = device[node.name]
return m and m.type == 'speaker' and {
name = 'Speaker',
value = 'speaker',
category = 'custom',
help = 'Sound effects',
}
local m = device[node.name]
return m and m.type == 'speaker' and {
name = 'Speaker',
value = 'speaker',
category = 'custom',
help = 'Sound effects',
}
end
function wizardPage:isValidFor(node)
return node.mtype == 'speaker'
return node.mtype == 'speaker'
end
function wizardPage:eventHandler(event)
if event.type == 'test_sound' then
local vol = tonumber(self.form.volume.value)
Sound.play('entity.item.pickup', vol)
end
if event.type == 'test_sound' then
local vol = tonumber(self.form.volume.value)
Sound.play('entity.item.pickup', vol)
end
end
UI:getPage('nodeWizard').wizard:add({ speaker = wizardPage })

View File

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

View File

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