diff --git a/milo/Milo.lua b/milo/Milo.lua index bb469bc..9bb37d9 100644 --- a/milo/Milo.lua +++ b/milo/Milo.lua @@ -1,9 +1,5 @@ --[[ Provides: autocrafting, resource limits, on-demand crafting. - - Turtle crafting: - 1. The turtle must have a crafting table equipped. - 2. Equip the turtle with an introspection module. ]]-- _G.requireInjector(_ENV) @@ -28,12 +24,12 @@ end local function Syntax(msg) print([[ -Turtle must be equipped with: - * Introspection module (unbound) +Turtle must be provided with: + * Introspection module (never bound) * Workbench Turtle must be connected to: - * Wired modem + * Wired modem (activated) ]]) error(msg) @@ -99,10 +95,12 @@ context.storage.turtleInventory = context.turtleInventory local function loadDirectory(dir) for _, file in pairs(fs.list(dir)) do - local s, m = Util.run(_ENV, fs.combine(dir, file)) - if not s and m then - _G.printError('Error loading: ' .. file) - error(m or 'Unknown error') + if not fs.isDir(fs.combine(dir, file)) then + local s, m = Util.run(_ENV, fs.combine(dir, file)) + if not s and m then + _G.printError('Error loading: ' .. file) + error(m or 'Unknown error') + end end end end diff --git a/milo/MiloRemote.lua b/milo/MiloRemote.lua index ee9853f..11d0105 100644 --- a/milo/MiloRemote.lua +++ b/milo/MiloRemote.lua @@ -1,5 +1,3 @@ -_G.requireInjector(_ENV) - local Config = require('config') local Event = require('event') local Sound = require('sound') @@ -12,14 +10,16 @@ local colors = _G.colors local device = _G.device local fs = _G.fs local os = _G.os +local shell = _ENV.shell local string = _G.string local SHIELD_SLOT = 2 local STARTUP_FILE = 'usr/autorun/miloRemote.lua' -local config = Config.load('miloRemote', { displayMode = 0 }) +local context = { + state = Config.load('miloRemote', { displayMode = 0 }), +} -local socket local depositMode = { [ true ] = { text = '\25', textColor = colors.black, help = 'Deposit enabled' }, [ false ] = { text = '\215', textColor = colors.red, help = 'Deposit disabled' }, @@ -40,17 +40,10 @@ local page = UI.Page { event = 'refresh' }, { + name = 'config', text = '\206', x = -3, - dropdown = { - { text = 'Setup', event = 'setup' }, - UI.MenuBar.spacer, - { - text = 'Rescan storage', - event = 'rescan', - help = 'Rescan all inventories' - }, - }, + }, }, infoBar = UI.StatusBar { @@ -65,8 +58,8 @@ local page = UI.Page { { heading = 'Name', key = 'displayName' }, }, values = { }, - sortColumn = config.sortColumn or 'count', - inverseSort = config.inverseSort, + sortColumn = context.state.sortColumn or 'count', + inverseSort = context.state.inverseSort, help = '^(s)tack, ^(a)ll' }, statusBar = UI.Window { @@ -101,8 +94,8 @@ local page = UI.Page { display = UI.Button { x = -3, event = 'toggle_display', - text = displayModes[config.displayMode].text, - help = displayModes[config.displayMode].help, + text = displayModes[context.state.displayMode].text, + help = displayModes[context.state.displayMode].help, }, }, accelerators = { @@ -121,7 +114,6 @@ local page = UI.Page { }, form = UI.Form { x = 2, ex = -2, y = 2, ey = -1, - values = config, [1] = UI.TextEntry { formLabel = 'Server', formKey = 'server', help = 'ID for the server', @@ -188,15 +180,19 @@ function page:processMessages(s) local ritem = self.items[response.key] if ritem then ritem.count = response.count - self.grid:draw() - self:sync() + if self.enabled then + self.grid:draw() + self:sync() + end end elseif response.type == 'list' then self.items = self:expandList(response.list) self:applyFilter() - self.grid:draw() - self.grid:sync() + if self.enabled then + self.grid:draw() + self.grid:sync() + end elseif response.type == 'transfer' then if response.count > 0 then @@ -204,8 +200,10 @@ function page:processMessages(s) local item = self.items[response.key] if item then item.count = response.current - self.grid:draw() - self:sync() + if self.enabled then + self.grid:draw() + self:sync() + end end end if response.craft then @@ -232,7 +230,7 @@ function page:processMessages(s) end function page:sendRequest(data, statusMsg) - if not config.server then + if not context.state.server then self:setStatus('Invalid configuration') return end @@ -247,35 +245,35 @@ function page:sendRequest(data, statusMsg) sync(self, function() local msg for _ = 1, 2 do - if not socket or not socket.connected then + if not context.socket or not context.socket.connected then self:setStatus('connecting ...') - socket, msg = Socket.connect(config.server, 4242) - if socket then - socket:write(player) - local r = socket:read(2) + 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:setStatus('connected ...') - self:processMessages(socket) + self:processMessages(context.socket) else msg = r and r.msg or 'Timed out' - socket:close() - socket = nil + context.socket:close() + context.socket = nil end end end - if socket then + if context.socket then if statusMsg then self:setStatus(statusMsg) Event.onTimeout(2, function() self:setStatus('') end) end - if socket:write(data) then + if context.socket:write(data) then success = true return end - socket:close() - socket = nil + context.socket:close() + context.socket = nil end end self:setStatus(msg or 'Failed to connect') @@ -321,9 +319,9 @@ end function page.grid:eventHandler(event) if event.type == 'grid_sort' then - config.sortColumn = event.sortColumn - config.inverseSort = event.inverseSort - Config.update('miloRemote', config) + context.state.sortColumn = event.sortColumn + context.state.inverseSort = event.inverseSort + Config.update('miloRemote', context.state) end return UI.Grid.eventHandler(self, event) end @@ -344,24 +342,24 @@ function page:eventHandler(event) UI:exitPullEvents() elseif event.type == 'setup' then - self.setup.form:setValues(config) + self.setup.form:setValues(context.state) self.setup:show() elseif event.type == 'toggle_deposit' then - config.deposit = not config.deposit - Util.merge(self.statusBar.depositToggle, depositMode[config.deposit]) + context.state.deposit = not context.state.deposit + Util.merge(self.statusBar.depositToggle, depositMode[context.state.deposit]) self.statusBar:draw() - self:setStatus(depositMode[config.deposit].help) - Config.update('miloRemote', config) + self:setStatus(depositMode[context.state.deposit].help) + Config.update('miloRemote', context.state) elseif event.type == 'form_complete' then - Config.update('miloRemote', config) + Config.update('miloRemote', context.state) self.setup:hide() self:refresh('list') self.grid:draw() self:setFocus(self.statusBar.filter) - if config.runOnStartup then + if context.state.runOnStartup then if not fs.exists(STARTUP_FILE) then Util.writeFile(STARTUP_FILE, [[os.sleep(1) @@ -408,6 +406,9 @@ shell.openForegroundTab('packages/milo/MiloRemote')]]) self:setStatus('nope ...') end + elseif event.type == 'plugin' then + event.button.callback(context) + elseif event.type == 'rescan' then self:setFocus(self.statusBar.filter) self:refresh('scan') @@ -419,13 +420,13 @@ shell.openForegroundTab('packages/milo/MiloRemote')]]) self.grid:draw() elseif event.type == 'toggle_display' then - config.displayMode = (config.displayMode + 1) % 2 - Util.merge(event.button, displayModes[config.displayMode]) + context.state.displayMode = (context.state.displayMode + 1) % 2 + Util.merge(event.button, displayModes[context.state.displayMode]) event.button:draw() self:applyFilter() self:setStatus(event.button.help) self.grid:draw() - Config.update('miloRemote', config) + Config.update('miloRemote', context.state) elseif event.type == 'text_change' and event.element == self.statusBar.filter then self.filter = event.text @@ -443,9 +444,9 @@ end function page:enable() self:setFocus(self.statusBar.filter) - Util.merge(self.statusBar.depositToggle, depositMode[config.deposit]) + Util.merge(self.statusBar.depositToggle, depositMode[context.state.deposit]) UI.Page.enable(self) - if not config.server then + if not context.state.server then self.setup:show() end Event.onTimeout(.1, function() @@ -502,7 +503,7 @@ function page:applyFilter() end return t end - local t = filterItems(self.items, self.filter, config.displayMode) + local t = filterItems(self.items, self.filter, context.state.displayMode) self.grid:setValues(t) end @@ -514,20 +515,20 @@ Event.addRoutine(function() sleepTime = .25 end - os.sleep(socket and sleepTime or 5) - if config.deposit then + os.sleep(context.socket and sleepTime or 5) + if context.state.deposit then local neural = device.neuralInterface - local inv = config.useShield and 'getEquipment' or 'getInventory' + local inv = context.state.useShield and 'getEquipment' or 'getInventory' if not neural or not neural[inv] then _G._debug('missing Introspection module') - elseif config.server and (config.useShield or config.slot) then + elseif context.state.server and (context.state.useShield or context.state.slot) then local s, m = pcall(function() local method = neural[inv] - local item = method and method().list()[config.useShield and SHIELD_SLOT or config.slot] + local item = method and method().list()[context.state.useShield and SHIELD_SLOT or context.state.slot] if item then if page:sendRequest({ request = 'deposit', - slot = config.useShield and 'shield' or config.slot, + slot = context.state.useShield and 'shield' or context.state.slot, count = item.count, }) then lastTransfer = os.clock() @@ -542,9 +543,50 @@ Event.addRoutine(function() end end) +context.page = page + +function context:getState(key) + return self.state[key] +end + +function context:setState(key, value) + self.state[key] = value + Config.update('miloRemote', self.state) +end + +local function loadDirectory(dir) + local dropdown = { + { text = 'Setup', event = 'setup' }, + UI.MenuBar.spacer, + { + 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 } }) +end + +local programDir = fs.getDir(shell.getRunningProgram()) +loadDirectory(fs.combine(programDir, 'plugins/remote')) + UI:setPage(page) UI:pullEvents() -if socket then - socket:close() +if context.socket then + context.socket:close() end diff --git a/milo/plugins/remote/autostore.lua b/milo/plugins/remote/autostore.lua new file mode 100644 index 0000000..3a628b0 --- /dev/null +++ b/milo/plugins/remote/autostore.lua @@ -0,0 +1,145 @@ +local itemDB = require('itemDB') +local Event = require('event') +local UI = require('ui') +local Util = require('util') + +local args = { ... } +local colors = _G.colors +local device = _G.device +local ni = device.neuralInterface + +local context = args[1] + +local page = UI.Page { + titleBar = UI.TitleBar { + backgroundColor = colors.gray, + title = 'Auto send items to storage', + previousPage = true, + }, + tabs = UI.Tabs { + y = 2, + inventory = UI.Window { + tabTitle = 'Inventory', + grid = UI.Grid { + y = 2, ey = -2, + columns = { + { heading = 'Name', key = 'displayName' }, + }, + sortColumn = 'displayName', + }, + }, + autostore = UI.Window { + tabTitle = 'Sending', + grid = UI.Grid { + y = 2, ey = -2, + columns = { + { heading = 'Name', key = 'displayName' }, + }, + sortColumn = 'displayName', + }, + }, + }, +} + +function page.tabs.inventory:enable() + local inv = ni.getInventory().list() + local list = { } + + for k, item in pairs(inv) do + local key = itemDB:makeKey(item) + if not list[key] then + local cItem = itemDB:get(item) + if not cItem then + cItem = itemDB:add(ni.getInventory().getItemMeta(k)) + end + if cItem then + cItem = Util.shallowCopy(cItem) + cItem.key = key + list[key] = cItem + end + end + end + self.grid:setValues(list) + itemDB:flush() + + return UI.Window.enable(self) +end + +function page.tabs.inventory.grid:getRowTextColor(row) + if context.state.autostore[row.key] then + return colors.yellow + end + return UI.Grid.getRowTextColor(self, row) +end + +function page.tabs.autostore:enable() + local list = { } + + for key in pairs(context.state.autostore or { }) do + local cItem = itemDB:get(key) + if cItem then + table.insert(list, cItem) + end + end + self.grid:setValues(list) + + return UI.Window.enable(self) +end + +function page.tabs.inventory:eventHandler(event) + if event.type == 'grid_select' then + local autostore = context.state.autostore or { } + if autostore[event.selected.key] then + autostore[event.selected.key] = nil + else + autostore[event.selected.key] = true + end + context:setState('autostore', autostore) + self.grid:draw() + return true + end +end + +function page.tabs.autostore:eventHandler(event) + if event.type == 'grid_select' then + local key = itemDB:makeKey(event.selected) + context.state.autostore[key] = nil + context:setState('autostore', context.state.autostore) + Util.removeByValue(self.grid.values, event.selected) + self.grid:update() + self.grid:draw() + return true + end +end + +Event.onInterval(5, function() + if context.state.deposit and (context.state.useShield or context.state.slot) then + local inv = ni.getInventory().list() + local slot = context.state.slot + local target = 'inventory' + local empty = not inv[slot] + + if context.state.useShield then + slot = 2 + target = 'equipment' + empty = not ni.getEquipment().list()[slot] + end + + if empty then + for k,v in pairs(inv) do + local key = itemDB:makeKey(v) + if context.state.autostore[key] then + ni.getInventory().pushItems(target, k, v.count, slot) + break + end + end + end + end +end) + +return { + menuItem = 'Autostore', + callback = function() + UI:setPage(page) + end, +}