diff --git a/inventoryManager/Crafter.lua b/inventoryManager/Crafter.lua deleted file mode 100644 index 0589d0e..0000000 --- a/inventoryManager/Crafter.lua +++ /dev/null @@ -1,1130 +0,0 @@ ---[[ - Turtle/machine crafting. - - Requirements: - Turtle must be restricted forward and back by some obstacle. - The turtle must have access to the main inventory at the most forward location. - Machines must be placed above or below the line along the turtle's backwards travel. - - Optional: - Monitors can be placed touching the turtle at the most forward position to - display crafting status. - - Sample setups: - M = machine, I = inventory, O = obstacle, T = turtle - - Turtle facing <--- - - MMMM - IT O - MMM - - IMMMM - O O - MMMMMM -]]-- - -_G.requireInjector() - -local InventoryAdapter = require('inventoryAdapter') -local Config = require('config') -local Event = require('event') -local itemDB = require('itemDB') -local Peripheral = require('peripheral') -local UI = require('ui') -local Terminal = require('terminal') -local Util = require('util') - -local colors = _G.colors -local os = _G.os -local term = _G.term -local turtle = _G.turtle - -local config = { - computerFacing = 'north', - inventorySide = 'front', - monitor = 'type/monitor', -} -Config.load('crafter', config) - -repeat until not turtle.forward() - -local inventoryAdapter = InventoryAdapter.wrap({ - side = config.inventorySide, - facing = config.computerFacing -}) -if not inventoryAdapter then - error('Invalid inventory configuration') -end - -local RESOURCE_FILE = 'usr/config/resources.db' -local RECIPES_FILE = 'usr/config/recipes2.db' -local MACHINES_FILE = 'usr/config/machines.db' - -local STATUS_ERROR = 'error' -local STATUS_INFO = 'info' -local STATUS_SUCCESS = 'success' -local STATUS_WARNING = 'warning' - -local recipes = Util.readTable(RECIPES_FILE) or { } -local resources -local machines = { } -local jobListGrid -local listing, docked = false, false - -local function getItem(items, inItem, ignoreDamage, ignoreNbtHash) - for _,item in pairs(items) do - if item.name == inItem.name and - (ignoreDamage or item.damage == inItem.damage) and - (ignoreNbtHash or item.nbtHash == inItem.nbtHash) then - return item - end - end -end - -local function uniqueKey(item) - return table.concat({ item.name, item.damage, item.nbtHash }, ':') -end - -local function getItemQuantity(items, res) - local count = 0 - for _,v in pairs(items) do - if res.name == v.name and - ((not res.damage and v.maxDamage > 0) or res.damage == v.damage) and - ((not res.nbtHash and v.nbtHash) or res.nbtHash == v.nbtHash) then - count = count + v.count - end - end - return count -end - -local function mergeResources(t) - for _,v in pairs(resources) do - local item = getItem(t, v) - if item then - Util.merge(item, v) - else - item = Util.shallowCopy(v) - item.count = 0 - table.insert(t, item) - end - end - - for k in pairs(recipes) do - local v = itemDB:splitKey(k) - local item = getItem(t, v) - if not item then - item = Util.shallowCopy(v) - item.count = 0 - table.insert(t, item) - end - item.has_recipe = true - end - - for _,v in pairs(t) do - if not v.displayName then - v.displayName = itemDB:getName(v) - end - v.lname = v.displayName:lower() - end -end - -local function filterItems(t, filter) - if filter then - local r = {} - filter = filter:lower() - for _,v in pairs(t) do - if string.find(v.lname, filter) then - table.insert(r, v) - end - end - return r - end - return t -end - -local function clearGrid() - for i = 1, 16 do - local count = turtle.getItemCount(i) - if count > 0 then - inventoryAdapter:insert(i, count) - if turtle.getItemCount(i) ~= 0 then - return false - end - end - end - return true -end - -local function undock() - while listing do - os.sleep(.5) - end - docked = false -end - -local function gotoMachine(machine) - undock() - for _ = 1, machine.index do - if not turtle.back() then - return - end - end - - return true -end - -local function dock() - if not docked then - repeat until not turtle.forward() - end - docked = true -end - -local function getItems() - while not docked do - os.sleep(.5) - end - - listing = true - - local items - for _ = 1, 5 do - items = inventoryAdapter:listItems() - if items then - break - end - end - if not items then - error('could not check inventory') - end - - listing = false - - return items -end - -local function isMachineEmpty(machine, item) - local list = { true } - - pcall(function() -- fails randomly in 1.7x - local side = turtle.getAction(machine.dir).side - local methods = Util.transpose(Peripheral.getMethods(side)) - - if methods.getAllStacks then -- 1.7x - list = Peripheral.call(side, 'getAllStacks', false) - elseif methods.list then - list = Peripheral.call(side, 'list') - elseif methods.getProgress then - if Peripheral.call(side, 'getProgress') == 0 then - return true - end - else - item.statusCode = STATUS_ERROR - item.status = 'Unable to check empty status' - return - end - - if tonumber(machine.ignoreSlot) then - list[tonumber(machine.ignoreSlot)] = nil - end - end) - - if Util.empty(list) then - return true - end - item.statusCode = STATUS_INFO - item.status = 'machine busy' -end - -local function craftItem(ikey, item, items, machineStatus) - dock() - - local resource = resources[ikey] - if not resource or not resource.machine then - item.statusCode = STATUS_ERROR - item.status = 'machine not defined' - return - end - - local machine = Util.find(machines, 'order', resource.machine) - if not machine then - item.statusCode = STATUS_ERROR - item.status = 'invalid machine' - return - end - - local ms = machineStatus[machine.order] - if not ms then - ms = { count = 0 } - machineStatus[machine.order] = ms - end - - local slot = 1 - local maxCount = math.ceil(item.need / item.recipe.count) - maxCount = math.min(machine.maxCount or 64, maxCount) - - if maxCount > 0 and maxCount - ms.count <= 0 then - item.statusCode = STATUS_INFO - item.status = 'machine busy' - return - end - - maxCount = maxCount - ms.count - - for key,qty in pairs(item.recipe.ingredients) do - local ingredient = itemDB:get(key) - local c = math.min(maxCount * qty, getItemQuantity(items, ingredient)) - c = math.min(c, ingredient.maxCount) - c = math.floor(c / qty) - if c < maxCount then - maxCount = c - end - if maxCount <= 0 then - item.status = 'Missing ' .. ingredient.displayName - item.statusCode = STATUS_WARNING - return - end - end - - if machine.maxCount then - ms.count = ms.count + maxCount - end - - turtle.setStatus('Craft: ' .. itemDB:getName(ikey)) - for key,qty in pairs(item.recipe.ingredients) do - local ingredient = itemDB:get(key) --- local c = item.craftable * qty --- while c > 0 do ---debug(key) - inventoryAdapter:provide(ingredient, maxCount * qty, slot) - if turtle.getItemCount(slot) ~= maxCount * qty then - item.status = 'Extract failed: ' .. (ingredient.displayName or itemDB:getName(ingredient)) - item.statusCode = STATUS_ERROR - return - end --- c = c - maxCount - slot = slot + 1 - --end - end - - if not gotoMachine(machine) then - item.status = 'failed to find machine' - item.statusCode = STATUS_ERROR - else - if machine.empty and not isMachineEmpty(machine, item) then - return - end - - if machine.dir == 'up' then - turtle.emptyInventory(turtle.dropUp) - else - turtle.emptyInventory(turtle.dropDown) - end - if #turtle.getFilledSlots() ~= 0 then - item.statusCode = STATUS_INFO - item.status = 'machine busy' - else - item.statusCode = STATUS_SUCCESS - item.status = 'crafting' - end - end -end - -local function expandList(list, items) - local summed = { } - - local function sumItems(key, count) - local item = itemDB:splitKey(key) - local summedItem = summed[key] - if not summedItem then - summedItem = Util.shallowCopy(item) - summedItem.recipe = recipes[key] - summedItem.count = getItemQuantity(items, item) - summedItem.displayName = itemDB:getName(item) - summedItem.total = 0 - summedItem.need = 0 - summedItem.used = 0 - summedItem.craftable = 0 - summed[key] = summedItem - end - local total = count - local used = math.min(summedItem.count, total) - local need = total - used - - summedItem.total = summedItem.total + total - summedItem.count = summedItem.count - used - summedItem.used = summedItem.used + used - summedItem.need = summedItem.need + need - - if need > 0 and summedItem.recipe then - need = math.ceil(need / summedItem.recipe.count) - for ikey,iqty in pairs(summedItem.recipe.ingredients) do - sumItems(ikey, math.ceil(need * iqty)) - end - end - end - - for key, item in pairs(list) do - sumItems(key, item.count) - end - - return Util.filter(summed, function(a) return a.need > 0 end) -end - -local function watchResources(items) - local craftList = { } - - for _,res in pairs(resources) do - if res.low then - local item = Util.shallowCopy(res) - item.nbtHash = res.nbtHash - item.damage = res.damage - if res.ignoreDamage then - item.damage = nil - end - item.count = getItemQuantity(items, item) - if item.count < res.low then - item.displayName = itemDB:getName(res) - item.count = res.low -- - item.count - craftList[uniqueKey(res)] = item - end - end - end - - return craftList -end - -local function craftItems() - local machineStatus = { } - local items = getItems() - local craftList = watchResources(items) - local list = expandList(craftList, items) - jobListGrid:setValues(list) - jobListGrid:update() - jobListGrid:draw() - jobListGrid:sync() - for key, item in pairs(list) do - if item.need > 0 and item.recipe then - craftItem(key, item, items, machineStatus) - dock() - items = getItems() - clearGrid() - elseif item.need > 0 then - item.status = 'no recipe' - item.statusCode = STATUS_ERROR - end - jobListGrid:update() - jobListGrid:draw() - jobListGrid:sync() - end - turtle.setStatus('idle') -end - -local function loadResources() - resources = Util.readTable(RESOURCE_FILE) or { } - for k,v in pairs(resources) do - Util.merge(v, itemDB:splitKey(k)) - end -end - -local function saveResources() - local t = { } - - for k,v in pairs(resources) do - v = Util.shallowCopy(v) - - v.name = nil - v.damage = nil - v.nbtHash = nil - t[k] = v - end - - Util.writeTable(RESOURCE_FILE, t) -end - -local function findMachines() - turtle.setStatus('Inspecting machines') - - dock() - - local function getName(dir) - local side = turtle.getAction(dir).side - if Peripheral.isPresent(side) then - local methods = Util.transpose(Peripheral.getMethods(side)) - if methods.getMetadata then - local name = Peripheral.call(side, 'getMetadata').displayName - if name and not string.find(name, '.', 1, true) then - return name - end - elseif methods.getInventoryName then -- 1.7x - return Peripheral.call(side, 'getInventoryName') - end - return Peripheral.getType(side) - end - local _, machine = turtle.getAction(dir).inspect() - if not machine or type(machine) ~= 'table' then - return 'Unknown' - end - return machine.name or 'Unknown' - end - - local index = 0 - - local function getMachine(dir) - local name = getName(dir) - table.insert(machines, { - name = name, - rawName = name, - index = index, - dir = dir, - order = #machines + 1 - }) - end - - repeat - getMachine('down') - getMachine('up') - index = index + 1 - undock() - until not turtle.back() - - local mf = Util.readTable(MACHINES_FILE) or { } - for _,m in pairs(machines) do - local m2 = Util.find(mf, 'order', m.order) - if m2 then - if not m2.rawName then - m2.rawName = m.rawName - end - if m.rawName == m2.rawName then - m.name = m2.name or m.name - end - m.empty = m2.empty - m.ignore = m2.ignore - m.ignoreSlot = m2.ignoreSlot - m.maxCount = m2.maxCount - end - end -end - -local function jobMonitor() - local mon = Peripheral.lookup(config.monitor) - - if mon then - mon = UI.Device({ - device = mon, - textScale = .5, - }) - else - mon = UI.Device({ - device = Terminal.getNullTerm(term.current()) - }) - end - - jobListGrid = UI.Grid({ - parent = mon, - sortColumn = 'displayName', - columns = { - { heading = 'Qty', key = 'need', width = 6 }, - { heading = 'Crafting', key = 'displayName', width = (mon.width - 18) / 2 }, - { heading = 'Status', key = 'status', }, - }, - }) - - function jobListGrid:getRowTextColor(row, selected) - if row.statusCode == STATUS_ERROR then - return colors.red - elseif row.statusCode == STATUS_WARNING then - return colors.yellow - elseif row.statusCode == STATUS_SUCCESS then - return colors.lime - end - - return UI.Grid:getRowTextColor(row, selected) - end - - jobListGrid:draw() - jobListGrid:sync() -end - -local itemPage = UI.Page { - titleBar = UI.TitleBar { - title = 'Limit Resource', - previousPage = true, - event = 'form_cancel', - }, - form = UI.Form { - x = 1, y = 2, height = 10, ex = -1, - [1] = UI.TextEntry { - width = 7, - formLabel = 'Min', formKey = 'low', help = 'Craft if below min' - }, - [2] = UI.Chooser { - width = 7, - formLabel = 'Ignore Dmg', formKey = 'ignoreDamage', - nochoice = 'No', - choices = { - { name = 'Yes', value = true }, - { name = 'No', value = false }, - }, - help = 'Ignore damage of item' - }, - [3] = UI.Button { - text = 'Select', event= 'selectMachine', - formLabel = 'Machine' - }, - info = UI.TextArea { - x = 2, ex = -2, y = 6, height = 3, - textColor = colors.gray, - }, - button = UI.Button { - x = 2, y = 9, - text = 'Recipe', event = 'learn', - }, - }, - machines = UI.SlideOut { - backgroundColor = colors.cyan, - titleBar = UI.TitleBar { - title = 'Select Machine', - previousPage = true, - }, - grid = UI.ScrollingGrid { - y = 2, ey = -4, - values = machines, - disableHeader = true, - columns = { - { heading = '', key = 'index', width = 2 }, - { heading = 'Name', key = 'name'}, - }, - sortColumn = 'order', - }, - button1 = UI.Button { - x = -14, y = -2, - text = 'Ok', event = 'setMachine', - }, - button2 = UI.Button { - x = -9, y = -2, - text = 'Cancel', event = 'cancelMachine', - }, - }, - statusBar = UI.StatusBar { } -} - -function itemPage:enable(item) - if item then - self.item = Util.shallowCopy(item) - self.form:setValues(item) - self.titleBar.title = item.displayName or item.name - end - UI.Page.enable(self) - self:focusFirst() -end - -function itemPage.form.info:draw() - local recipe = recipes[uniqueKey(itemPage.item)] - self.value = '' - if recipe and itemPage.item.machine then - self.value = string.format('Crafts %d using the %s machine', - recipe.count, - machines[itemPage.item.machine].name) - end - UI.TextArea.draw(self) -end - -function itemPage.machines.grid:getRowTextColor(row, selected) - if itemPage.item.machine == row.order then - return colors.yellow - end - return UI.Grid:getRowTextColor(row, selected) -end - ---[[ -function itemPage.machines:eventHandler(event) - if event.type == 'grid_focus_row' then - self.statusBar:setStatus(string.format('%d %s', event.selected.index, event.selected.dir)) - else - return UI.SlideOut.eventHandler(self, event) - end - return true -end -]] - -function itemPage:eventHandler(event) - if event.type == 'form_cancel' then - UI:setPreviousPage() - - elseif event.type == 'learn' then - UI:setPage('learn', self.item) - - elseif event.type == 'setMachine' then - self.item.machine = self.machines.grid:getSelected().order - self.machines:hide() - - elseif event.type == 'cancelMachine' then - self.machines:hide() - - elseif event.type == 'selectMachine' then - local machineCopy = Util.shallowCopy(machines) - Util.filterInplace(machineCopy, function(m) return not m.ignore end) - self.machines.grid:setValues(machineCopy) - if self.item.machine then - local _, index = Util.find(machineCopy, 'order', self.item.machine) - if index then - self.machines.grid:setIndex(index) - end - else - self.machines.grid:setIndex(1) - end - self.machines:show() - - elseif event.type == 'focus_change' then - self.statusBar:setStatus(event.focused.help) - self.statusBar:draw() - - elseif event.type == 'form_complete' then - local values = self.form.values - local keys = { 'name', 'low', 'damage', 'nbtHash', 'machine' } - - local filtered = { } - for _,key in pairs(keys) do - filtered[key] = values[key] - end - filtered.low = tonumber(filtered.low) - filtered.machine = self.item.machine - - if values.ignoreDamage == true then - filtered.damage = 0 - filtered.ignoreDamage = true - end - - local key = uniqueKey(filtered) - - resources[key] = filtered - saveResources() - - UI:setPreviousPage() - - else - return UI.Page.eventHandler(self, event) - end - return true -end - -local learnPage = UI.Page { - ingredients = UI.ScrollingGrid { - y = 2, height = 3, - disableHeader = true, - columns = { - { heading = 'Name', key = 'displayName', width = 31 }, - { heading = 'Qty', key = 'count' , width = 5 }, - }, - sortColumn = 'displayName', - }, - grid = UI.ScrollingGrid { - y = 6, height = 5, - disableHeader = true, - columns = { - { heading = 'Name', key = 'displayName', width = 31 }, - { heading = 'Qty', key = 'count' , width = 5 }, - }, - sortColumn = 'displayName', - }, - filter = UI.TextEntry { - x = 20, ex = -2, y = 5, - limit = 50, - shadowText = 'filter', - backgroundColor = colors.lightGray, - backgroundFocusColor = colors.lightGray, - }, - count = UI.TextEntry { - x = 11, y = -1, width = 5, - limit = 50, - }, - button1 = UI.Button { - x = -14, y = -1, - text = 'Ok', event = 'accept', - }, - button2 = UI.Button { - x = -9, y = -1, - text = 'Cancel', event = 'cancel', - }, -} - -function learnPage:enable(target) - self.target = target - self.allItems = getItems() - mergeResources(self.allItems) - - self.filter.value = '' - self.grid.values = self.allItems - self.grid:update() - self.ingredients.values = { } - self.count.value = 1 - - if target.has_recipe then - local recipe = recipes[uniqueKey(target)] - self.count.value = recipe.count - for k,v in pairs(recipe.ingredients) do - self.ingredients.values[k] = - { name = k, count = v, displayName = itemDB:getName(k) } - end - end - self.ingredients:update() - - self:setFocus(self.filter) - UI.Page.enable(self) -end - -function learnPage:draw() - UI.Window.draw(self) - self:write(2, 1, 'Ingredients', nil, colors.yellow) - self:write(2, 5, 'Inventory', nil, colors.yellow) - self:write(2, 12, 'Produces') -end - -function learnPage:eventHandler(event) - - if event.type == 'text_change' and event.element == self.filter then - local t = filterItems(learnPage.allItems, event.text) - self.grid:setValues(t) - self.grid:draw() - - elseif event.type == 'cancel' then - UI:setPreviousPage() - - elseif event.type == 'accept' then - - local recipe = { - count = tonumber(self.count.value) or 1, - ingredients = { }, - } - for key, item in pairs(self.ingredients.values) do - recipe.ingredients[key] = item.count - end - recipes[uniqueKey(self.target)] = recipe - Util.writeTable(RECIPES_FILE, recipes) - - UI:setPreviousPage() - - elseif event.type == 'grid_select' then - if event.element == self.grid then - local key = uniqueKey(event.selected) - if not self.ingredients.values[key] then - self.ingredients.values[key] = Util.shallowCopy(event.selected) - self.ingredients.values[key].count = 0 - end - self.ingredients.values[key].count = self.ingredients.values[key].count + 1 - self.ingredients:update() - self.ingredients:draw() - elseif event.element == self.ingredients then - event.selected.count = event.selected.count - 1 - if event.selected.count == 0 then - self.ingredients.values[uniqueKey(event.selected)] = nil - self.ingredients:update() - end - self.ingredients:draw() - end - - else - return UI.Page.eventHandler(self, event) - end - return true -end - -local machinesPage = UI.Page { - titleBar = UI.TitleBar { - previousPage = true, - title = 'Machines', - }, - grid = UI.ScrollingGrid { - y = 2, ey = -2, - values = machines, - columns = { - { heading = 'Name', key = 'name' }, - { heading = 'Side', key = 'dir', width = 5 }, - { heading = 'Index', key = 'index', width = 5 }, - }, - sortColumn = 'order', - }, - detail = UI.SlideOut { - backgroundColor = colors.cyan, - form = UI.Form { - x = 1, y = 2, ex = -1, ey = -2, - [1] = UI.TextEntry { - formLabel = 'Name', formKey = 'name', help = '...', - limit = 64, - }, - [2] = UI.Chooser { - width = 7, - formLabel = 'Hidden', formKey = 'ignore', - nochoice = 'No', - choices = { - { name = 'Yes', value = true }, - { name = 'No', value = false }, - }, - help = 'Do not show this machine' - }, - [3] = UI.Chooser { - width = 7, - formLabel = 'Empty', formKey = 'empty', - nochoice = 'No', - choices = { - { name = 'Yes', value = true }, - { name = 'No', value = false }, - }, - help = 'Check if machine is empty before crafting' - }, - [4] = UI.TextEntry { - formLabel = 'Ignore Slot', formKey = 'ignoreSlot', help = '...', - limit = 4, - }, - [5] = UI.TextEntry { - formLabel = 'Max Craft', formKey = 'maxCount', help = '...', - limit = 4, - }, - }, - statusBar = UI.StatusBar(), - }, - statusBar = UI.StatusBar { - values = 'Select Machine', - }, - accelerators = { - h = 'toggle_hidden', - } -} - -function machinesPage:enable() - self.grid:update() - UI.Page.enable(self) -end - -function machinesPage.detail:eventHandler(event) - if event.type == 'focus_change' then - self.statusBar:setStatus(event.focused.help) - end - return UI.SlideOut.eventHandler(self, event) -end - -function machinesPage.grid:getRowTextColor(row, selected) - if row.ignore then - return colors.yellow - end - return UI.Grid:getRowTextColor(row, selected) -end - -function machinesPage:eventHandler(event) - if event.type == 'grid_select' then - self.detail.form:setValues(event.selected) - self.detail:show() - - elseif event.type == 'toggle_hidden' then - local selected = self.grid:getSelected() - if selected then - selected.ignore = not selected.ignore - Util.writeTable(MACHINES_FILE, machines) - self:draw() - end - - elseif event.type == 'form_complete' then - self.detail.form.values.empty = self.detail.form.values.empty == true - self.detail.form.values.ignore = self.detail.form.values.ignore == true - self.detail.form.values.ignoreSlot = tonumber(self.detail.form.values.ignoreSlot) - self.detail.form.values.maxCount = tonumber(self.detail.form.values.maxCount) - Util.writeTable(MACHINES_FILE, machines) - self.detail:hide() - - elseif event.type == 'form_cancel' then - self.detail:hide() - - else - UI.Page.eventHandler(self, event) - end - return true -end - -local listingPage = UI.Page { - menuBar = UI.MenuBar { - buttons = { - { text = 'Forget', event = 'forget' }, - { text = 'Machines', event = 'machines' }, - { text = 'Refresh', event = 'refresh', x = -9 }, - }, - }, - grid = UI.Grid { - y = 2, height = UI.term.height - 2, - columns = { - { heading = 'Name', key = 'displayName' }, - { heading = 'Qty', key = 'count' , width = 5 }, - { heading = 'Min', key = 'low' , width = 4 }, - }, - sortColumn = 'displayName', - }, - statusBar = UI.Window { - y = -1, - filter = UI.TextEntry { - limit = 50, - shadowText = 'filter', - shadowTextColor = colors.lightGray, - backgroundColor = colors.gray, - backgroundFocusColor = colors.gray, - }, - }, - accelerators = { - r = 'refresh', - q = 'quit', - } -} - -function listingPage.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 listingPage.grid:getDisplayValues(row) - row = Util.shallowCopy(row) - row.count = Util.toBytes(row.count) - if row.low then - row.low = Util.toBytes(row.low) - end - return row -end - -function listingPage.statusBar.filter:eventHandler(event) - if event.type == 'mouse_rightclick' then - self.value = '' - self:draw() - local page = UI:getCurrentPage() - page.filter = nil - page:applyFilter() - page.grid:draw() - page:setFocus(self) - end - return UI.TextEntry.eventHandler(self, event) -end - -function listingPage:eventHandler(event) - if event.type == 'quit' then - UI:exitPullEvents() - - elseif event.type == 'grid_select' then - local selected = event.selected - UI:setPage('item', selected) - - elseif event.type == 'refresh' then - self:refresh() - self.grid:draw() - self.statusBar.filter:focus() - - elseif event.type == 'machines' then - UI:setPage('machines') - - elseif event.type == 'craft' then - UI:setPage('craft', self.grid:getSelected()) - - elseif event.type == 'forget' then - local item = self.grid:getSelected() - if item then - local key = uniqueKey(item) - - if recipes[key] then - recipes[key] = nil - Util.writeTable(RECIPES_FILE, recipes) - end - - if resources[key] then - resources[key] = nil - Util.writeTable(RESOURCE_FILE, resources) - end - - self.statusBar:timedStatus('Forgot: ' .. item.name, 3) - self:refresh() - self.grid:draw() - end - - elseif event.type == 'text_change' then - self.filter = event.text - if #self.filter == 0 then - self.filter = nil - end - self:applyFilter() - self.grid:draw() - self.statusBar.filter:focus() - - else - UI.Page.eventHandler(self, event) - end - return true -end - -function listingPage:enable() - self:refresh() - self:setFocus(self.statusBar.filter) - UI.Page.enable(self) -end - -function listingPage:refresh() - self.allItems = getItems() - mergeResources(self.allItems) - self:applyFilter() -end - -function listingPage:applyFilter() - local t = filterItems(self.allItems, self.filter) - self.grid:setValues(t) -end - --- randomly errors in 1.7x with "you are not attached to this computer" -print('Inspecting machines') -local retryCount = 0 -while true do - Util.clear(machines) - local s, m = pcall(findMachines) - if not s and m then - _G.printError(m) - else - break - end - retryCount = retryCount + 1 - if retryCount > 3 then - error(m) - end - print('retrying...') -end - -loadResources() -dock() -clearGrid() -jobMonitor() - -UI:setPages({ - listing = listingPage, - machines = machinesPage, - item = itemPage, - learn = learnPage, -}) - -UI:setPage(listingPage) -listingPage:setFocus(listingPage.statusBar.filter) - -Event.on('turtle_abort', function() - UI:exitPullEvents() -end) - -Event.onInterval(30, function() - dock() - if turtle.getFuelLevel() < 100 then - turtle.select(1) - inventoryAdapter:provide({ name = 'minecraft:coal', damage = 1 }, 16, 1) - turtle.refuel() - end - craftItems() -end) - -turtle.setStatus('idle') -UI:pullEvents() -jobListGrid.parent:reset() diff --git a/inventoryManager/plugins/exportView.lua b/inventoryManager/plugins/exportView.lua deleted file mode 100644 index fbdc130..0000000 --- a/inventoryManager/plugins/exportView.lua +++ /dev/null @@ -1,121 +0,0 @@ -local itemDB = require('itemDB') -local Lora = require('lora') -local UI = require('ui') -local Util = require('util') - -local colors = _G.colors -local device = _G.device - -local itemSlideout = UI.SlideOut { - backgroundColor = colors.cyan, - grid = UI.ScrollingGrid { - y = 3, ey = -2, - columns = { - { heading = 'Name', key = 'displayName', width = 31 }, - { heading = 'Qty', key = 'count' , width = 5 }, - }, - sortColumn = 'displayName', - }, - filter = UI.TextEntry { - x = 2, ex = -2, y = 2, - limit = 50, - shadowText = 'filter', - backgroundColor = colors.lightGray, - backgroundFocusColor = colors.lightGray, - }, - button1 = UI.Button { - x = -14, y = -1, - text = 'Ok', event = 'accept', - }, - button2 = UI.Button { - x = -9, y = -1, - text = 'Cancel', event = 'collapse', - }, -} - -function itemSlideout:filterItems(t, filter) - if filter then - local r = {} - filter = filter:lower() - for _,v in pairs(t) do - if string.find(v.lname, filter) then - table.insert(r, v) - end - end - return r - end - return t -end - -function itemSlideout.grid:enable() - if not self.allItems then - self.allItems = Lora:listItems() - Lora:mergeResources(self.allItems) - self:setValues(self.allItems) - end - UI.Grid.enable(self) -end - -function itemSlideout:eventHandler(event) - if event.type == 'text_change' and event.element == self.filter then - local t = self:filterItems(self.grid.allItems, event.text) - self.grid:setValues(t) - self.grid:draw() - end - return UI.SlideOut.eventHandler(self, event) -end - -local exportView = UI.Window { - mtype = 'machine', - title = 'Export item into machine', - index = 3, - grid = UI.ScrollingGrid { - x = 2, ex = -6, y = 2, ey = -2, - columns = { - { heading = 'Slot', key = 'slot', width = 4 }, - { heading = 'Item', key = 'displayName' }, - }, - sortColumn = 'slot', - }, - add = UI.Button { - x = -4, y = 4, - text = '+', event = 'add_export', help = '...', - }, - remove = UI.Button { - x = -4, y = 6, - text = '-', event = 'remove_export', help = '...', - }, -} - -function exportView:save(machine) - machine.exports = not Util.empty(self.grid.values) and self.grid.values or nil - return true -end - -function exportView:setMachine(machine) - local m = device[machine.name] - self.slotCount = m.size() - self.grid:setValues(machine.exports or { }) -end - -function exportView.grid:getDisplayValues(row) - row = Util.shallowCopy(row) - row.displayName = itemDB:getName(row.name) - return row -end - -function exportView:eventHandler(event) - if event.type == 'grid_select' or event.type == 'add_export' then - itemSlideout:show() - elseif event.type == 'remove_export' then - local row = self.grid:getSelected() - if row then - Util.removeByValue(self.grid.values, row) - self.grid:update() - self.grid:draw() - end - end -end - -UI:getPage('machineWizard'):add({ items = itemSlideout }) -UI:getPage('machineWizard').wizard:add({ export = exportView }) diff --git a/inventoryManager/plugins/learn.lua b/inventoryManager/plugins/learn.lua deleted file mode 100644 index a8ebf06..0000000 --- a/inventoryManager/plugins/learn.lua +++ /dev/null @@ -1,174 +0,0 @@ -local Craft = require('turtle.craft') -local itemDB = require('itemDB') -local Lora = require('lora') -local UI = require('ui') -local Util = require('util') - -local device = _G.device -local turtle = _G.turtle - -local context = Lora:getContext() - --- TODO: try networked module -local introspectionModule = device['plethora:introspection'] or - error('Introspection module not found') - -local function getTurtleInventory() - local list = { } - for i = 1,16 do - list[i] = introspectionModule.getInventory().getItemMeta(i) - end - return list -end - -local function learnRecipe() - local ingredients = getTurtleInventory() - local listingPage = UI:getPage('listing') - - if ingredients then - turtle.select(1) - if turtle.craft() then - local results = getTurtleInventory() - if results and results[1] then - Lora:clearGrid() - - local maxCount - local newRecipe = { - ingredients = ingredients, - } - - local numResults = 0 - for _,v in pairs(results) do - if v.count > 0 then - numResults = numResults + 1 - end - end - if numResults > 1 then - for _,v1 in pairs(results) do - for _,v2 in pairs(ingredients) do - if v1.name == v2.name and - v1.nbtHash == v2.nbtHash and - (v1.damage == v2.damage or - (v1.maxDamage > 0 and v2.maxDamage > 0 and - v1.damage ~= v2.damage)) then - if not newRecipe.crafingTools then - newRecipe.craftingTools = { } - end - local tool = Util.shallowCopy(v2) - if tool.maxDamage > 0 then - tool.damage = '*' - end - - --[[ - Turtles can only craft one item at a time using a tool :( - ]]-- - maxCount = 1 - - newRecipe.craftingTools[Lora:uniqueKey(tool)] = true - v1.craftingTool = true - break - end - end - end - end - - local recipe - for _,v in pairs(results) do - if not v.craftingTool then - recipe = v - if maxCount then - recipe.maxCount = maxCount - end - break - end - end - - if not recipe then - debug(results) - debug(newRecipe) - error('Failed - view system log') - end - - newRecipe.count = recipe.count - - local key = Lora:uniqueKey(recipe) - if recipe.maxCount ~= 64 then - newRecipe.maxCount = recipe.maxCount - end - for k,ingredient in pairs(Util.shallowCopy(ingredients)) do - if ingredient.maxDamage > 0 then - -- ingredient.damage = '*' -- I don't think this is right - end - ingredients[k] = Lora:uniqueKey(ingredient) - end - - context.userRecipes[key] = newRecipe - Util.writeTable(Lora.RECIPES_FILE, context.userRecipes) - Craft.loadRecipes() - - local displayName = itemDB:getName(recipe) - - listingPage.statusBar.filter:setValue(displayName) - listingPage.notification:success('Learned: ' .. displayName) - listingPage.filter = displayName - listingPage:refresh() - listingPage.grid:draw() - - Lora:eject(recipe, recipe.count) - return true - end - else - listingPage.notification:error('Failed to craft', 3) - end - else - listingPage.notification:error('No recipe defined', 3) - end -end - -local learnPage = UI.Dialog { - height = 7, width = UI.term.width - 6, - title = 'Learn Recipe', - idField = UI.Text { - x = 5, - y = 3, - width = UI.term.width - 10, - value = 'Place recipe in turtle' - }, - accept = UI.Button { - x = -14, y = -3, - text = 'Ok', event = 'accept', - }, - cancel = UI.Button { - x = -9, y = -3, - text = 'Cancel', event = 'cancel' - }, - statusBar = UI.StatusBar { - status = 'Crafting paused' - } -} - -function learnPage:enable() - Lora:pauseCrafting() - self:focusFirst() - UI.Dialog.enable(self) -end - -function learnPage:disable() - Lora:resumeCrafting() - UI.Dialog.disable(self) -end - -function learnPage:eventHandler(event) - if event.type == 'cancel' then - UI:setPreviousPage() - elseif event.type == 'accept' then - if learnRecipe(self) then - UI:setPreviousPage() - end - else - return UI.Dialog.eventHandler(self, event) - end - return true -end - -UI:addPage('learn', learnPage) diff --git a/inventoryManager/.package b/milo/.package similarity index 88% rename from inventoryManager/.package rename to milo/.package index f1522ad..737e1e0 100644 --- a/inventoryManager/.package +++ b/milo/.package @@ -1,6 +1,6 @@ { required = { - 'opus-inventory-manager', + 'opus-develop-1.8', }, title = 'Inventory manager for Opus OS', description = [[ diff --git a/inventoryManager/inventoryManager.lua b/milo/Milo.lua similarity index 87% rename from inventoryManager/inventoryManager.lua rename to milo/Milo.lua index 02d08bd..4ed142d 100644 --- a/inventoryManager/inventoryManager.lua +++ b/milo/Milo.lua @@ -40,7 +40,7 @@ via CC cables. Configuration: - Configuration file is usr/config/inventoryManager + Configuration file is usr/config/milo monitor : valid options include: type/monitor - will use the first monitor found @@ -66,7 +66,7 @@ _G.requireInjector() local Config = require('config') local Event = require('event') local itemDB = require('itemDB') -local Lora = require('lora') +local Milo = require('milo') local Peripheral = require('peripheral') local UI = require('ui') local Util = require('util') @@ -78,14 +78,14 @@ local multishell = _ENV.multishell local shell = _ENV.shell if multishell then - multishell.setTitle(multishell.getCurrent(), 'Resource Manager') + multishell.setTitle(multishell.getCurrent(), 'Milo') end local config = { monitor = 'type/monitor', remoteDefaults = { }, } -Config.load('inventoryManager', config) +Config.load('milo', config) local modem = Peripheral.get('wired_modem') if not modem or not modem.getNameLocal then @@ -125,7 +125,7 @@ for _, v in pairs(modem.getNamesRemote()) do end local function loadResources() - local resources = Util.readTable(Lora.RESOURCE_FILE) or { } + local resources = Util.readTable(Milo.RESOURCE_FILE) or { } for k,v in pairs(resources) do Util.merge(v, itemDB:splitKey(k)) end @@ -137,10 +137,12 @@ local context = { config = config, inventoryAdapter = inventoryAdapter, resources = loadResources(), - userRecipes = Util.readTable(Lora.RECIPES_FILE) or { }, + userRecipes = Util.readTable(Milo.RECIPES_FILE) or { }, + learnTypes = { }, + machineTypes = { }, } -Lora:init(context) +Milo:init(context) local function loadDirectory(dir) for _, file in pairs(fs.list(dir)) do @@ -155,22 +157,24 @@ local programDir = fs.getDir(shell.getRunningProgram()) loadDirectory(fs.combine(programDir, 'core')) loadDirectory(fs.combine(programDir, 'plugins')) -table.sort(Lora.tasks, function(a, b) +table.sort(Milo.tasks, function(a, b) return a.priority < b.priority end) -Lora:clearGrid() +Milo:clearGrid() local page = UI:getPage('listing') UI:setPage(page) page:setFocus(page.statusBar.filter) +-- TODO: Event.on('device_detach', function() end) + Event.onInterval(5, function() - if not Lora:isCraftingPaused() then - Lora:resetCraftingStatus() + if not Milo:isCraftingPaused() then + Milo:resetCraftingStatus() context.inventoryAdapter:refresh() - for _, task in ipairs(Lora.tasks) do + for _, task in ipairs(Milo.tasks) do local s, m = pcall(function() task:cycle(context) end) if not s and m then Util.print(task) diff --git a/inventoryManager/apis/inventoryAdapter.lua b/milo/apis/inventoryAdapter.lua similarity index 100% rename from inventoryManager/apis/inventoryAdapter.lua rename to milo/apis/inventoryAdapter.lua diff --git a/inventoryManager/apis/lora.lua b/milo/apis/milo.lua similarity index 84% rename from inventoryManager/apis/lora.lua rename to milo/apis/milo.lua index d866d48..e453a9b 100644 --- a/inventoryManager/apis/lora.lua +++ b/milo/apis/milo.lua @@ -1,3 +1,4 @@ +local Config = require('config') local Craft = require('turtle.craft') local itemDB = require('itemDB') local Util = require('util') @@ -6,7 +7,7 @@ local os = _G.os local term = _G.term local turtle = _G.turtle -local Lora = { +local Milo = { RECIPES_FILE = 'usr/config/recipes.db', RESOURCE_FILE = 'usr/config/resources.db', @@ -18,50 +19,67 @@ local Lora = { craftingStatus = { }, } -function Lora:init(context) +function Milo:init(context) self.context = context end -function Lora:getContext() +function Milo:getContext() return self.context end -function Lora:pauseCrafting() +function Milo:pauseCrafting() self.craftingPaused = true end -function Lora:resumeCrafting() +function Milo:resumeCrafting() self.craftingPaused = false end -function Lora:isCraftingPaused() +function Milo:isCraftingPaused() return self.craftingPaused end -function Lora:uniqueKey(item) +function Milo:getState(key) + if not self.state then + self.state = { } + Config.load('milo.state', self.state) + end + return self.state[key] +end + +function Milo:setState(key, value) + if not self.state then + self.state = { } + Config.load('milo.state', self.state) + end + self.state[key] = value + Config.update('milo.state', self.state) +end + +function Milo:uniqueKey(item) return table.concat({ item.name, item.damage, item.nbtHash }, ':') end -function Lora:getCraftingStatus() +function Milo:getCraftingStatus() return self.craftingStatus end -function Lora:resetCraftingStatus() +function Milo:resetCraftingStatus() self.craftingStatus = { } self.context.inventoryAdapter.activity = { } end -function Lora:updateCraftingStatus(list) +function Milo:updateCraftingStatus(list) for k,v in pairs(list) do self.craftingStatus[k] = v end end -function Lora:registerTask(task) +function Milo:registerTask(task) table.insert(self.tasks, task) end -function Lora:showError(msg) +function Milo:showError(msg) term.clear() self.context.jobList:showError() print(msg) @@ -70,7 +88,7 @@ function Lora:showError(msg) os.reboot() end -function Lora:getItem(items, inItem, ignoreDamage, ignoreNbtHash) +function Milo:getItem(items, inItem, ignoreDamage, ignoreNbtHash) for _,item in pairs(items) do if item.name == inItem.name and (ignoreDamage or item.damage == inItem.damage) and @@ -80,7 +98,7 @@ function Lora:getItem(items, inItem, ignoreDamage, ignoreNbtHash) end end -function Lora:getItemWithQty(res, ignoreDamage, ignoreNbtHash) +function Milo:getItemWithQty(res, ignoreDamage, ignoreNbtHash) local items = self:listItems() local item = self:getItem(items, res, ignoreDamage, ignoreNbtHash) @@ -100,7 +118,7 @@ function Lora:getItemWithQty(res, ignoreDamage, ignoreNbtHash) return item end -function Lora:clearGrid() +function Milo:clearGrid() local function clear() turtle.eachFilledSlot(function(slot) self.context.inventoryAdapter:insert(slot.index, slot.count, nil, slot) @@ -116,7 +134,7 @@ function Lora:clearGrid() return clear() or clear() end -function Lora:eject(item, qty) +function Milo:eject(item, qty) local s, m = pcall(function() self.context.inventoryAdapter:provide(item, qty) turtle.emptyInventory() @@ -126,9 +144,9 @@ function Lora:eject(item, qty) end end -function Lora:mergeResources(t) +function Milo:mergeResources(t) for _,v in pairs(self.context.resources) do - local item = Lora:getItem(t, v) + local item = self:getItem(t, v) if item then Util.merge(item, v) else @@ -140,7 +158,7 @@ function Lora:mergeResources(t) for k in pairs(Craft.recipes) do local v = itemDB:splitKey(k) - local item = Lora:getItem(t, v) + local item = self:getItem(t, v) if not item then item = Util.shallowCopy(v) item.count = 0 @@ -157,7 +175,7 @@ function Lora:mergeResources(t) end end -function Lora:saveResources() +function Milo:saveResources() local t = { } for k,v in pairs(self.context.resources) do @@ -176,11 +194,11 @@ function Lora:saveResources() end end - Util.writeTable(Lora.RESOURCE_FILE, t) + Util.writeTable(self.RESOURCE_FILE, t) end -- Return a list of everything in the system -function Lora:listItems() +function Milo:listItems() for _ = 1, 5 do self.items = self.context.inventoryAdapter:listItems() if self.items then @@ -196,7 +214,7 @@ function Lora:listItems() return self.items end -function Lora:addCraftingRequest(item, craftList, count) +function Milo:addCraftingRequest(item, craftList, count) local key = self:uniqueKey(item) local request = craftList[key] if not craftList[key] then @@ -209,7 +227,7 @@ function Lora:addCraftingRequest(item, craftList, count) end -- Craft -function Lora:craftItem(recipe, items, originalItem, craftList, count) +function Milo:craftItem(recipe, items, originalItem, craftList, count) local missing = { } local toCraft = Craft.getCraftableAmount(recipe, count, items, missing) if missing.name then @@ -245,7 +263,7 @@ function Lora:craftItem(recipe, items, originalItem, craftList, count) end -- Craft as much as possible regardless if all ingredients are available -function Lora:forceCraftItem(inRecipe, items, originalItem, craftList, inCount) +function Milo:forceCraftItem(inRecipe, items, originalItem, craftList, inCount) local summed = { } local throttle = Util.throttle() @@ -326,7 +344,7 @@ function Lora:forceCraftItem(inRecipe, items, originalItem, craftList, inCount) return count end -function Lora:craft(recipe, items, item, craftList) +function Milo:craft(recipe, items, item, craftList) item.status = nil item.statusCode = nil item.crafted = 0 @@ -348,7 +366,7 @@ function Lora:craft(recipe, items, item, craftList) end end -function Lora:craftItems(craftList) +function Milo:craftItems(craftList) for _,key in pairs(Util.keys(craftList)) do local item = craftList[key] if item.count > 0 then @@ -367,4 +385,4 @@ function Lora:craftItems(craftList) end end -return Lora +return Milo diff --git a/inventoryManager/apis/networkedAdapter18.lua b/milo/apis/networkedAdapter18.lua similarity index 100% rename from inventoryManager/apis/networkedAdapter18.lua rename to milo/apis/networkedAdapter18.lua diff --git a/inventoryManager/apis/turtle/craft.lua b/milo/apis/turtle/craft.lua similarity index 100% rename from inventoryManager/apis/turtle/craft.lua rename to milo/apis/turtle/craft.lua diff --git a/inventoryManager/core/machines.lua b/milo/core/machines.lua similarity index 80% rename from inventoryManager/core/machines.lua rename to milo/core/machines.lua index 675554b..ada5f32 100644 --- a/inventoryManager/core/machines.lua +++ b/milo/core/machines.lua @@ -1,11 +1,13 @@ local Config = require('config') -local Lora = require('lora') +local itemDB = require('itemDB') +local Milo = require('milo') local UI = require('ui') local Util = require('util') local colors = _G.colors +local device = _G.device -local context = Lora:getContext() +local context = Milo:getContext() local machinesPage = UI.Page { titleBar = UI.TitleBar { @@ -57,13 +59,13 @@ end local machineWizard = UI.Page { titleBar = UI.TitleBar { title = 'Configure' }, wizard = UI.Wizard { - y = 2, ey = -3, + y = 2, ey = -2, pages = { general = UI.Window { index = 1, backgroundColor = colors.cyan, form = UI.Form { - x = 1, y = 2, ex = -1, ey = -2, + x = 1, y = 1, ex = -1, ey = 3, manualControls = true, [1] = UI.TextEntry { formLabel = 'Name', formKey = 'displayName', @@ -84,11 +86,21 @@ local machineWizard = UI.Page { help = 'Select type', }, }, + grid = UI.ScrollingGrid { + y = 5, ey = -2, x = 2, ex = -2, + columns = { + { heading = 'Slot', key = 'slot', width = 4 }, + { heading = 'Name', key = 'displayName', }, + { heading = 'Qty', key = 'count' , width = 3 }, + }, + sortColumn = 'slot', + help = 'Contents of inventory', + }, }, confirmation = UI.Window { title = 'Confirm changes', index = 2, - grid = UI.TextArea { + notice = UI.TextArea { x = 2, ex = -2, y = 2, ey = -2, value = [[Press accept to save the changes. @@ -98,7 +110,9 @@ The settings will take effect immediately!]], }, }, }, - statusBar = UI.StatusBar(), + statusBar = UI.StatusBar { + backgroundColor = colors.cyan, + }, notification = UI.Notification { }, } @@ -107,6 +121,25 @@ function machineWizard.wizard.pages.general:enable() self:focusFirst() end +function machineWizard.wizard.pages.general:setMachine(machine) + local inventory + + if device[machine.name] and device[machine.name].list then + inventory = device[machine.name].list() + for k,v in pairs(inventory) do + v.slot = k + end + end + + self.grid:setValues(inventory or { }) +end + +function machineWizard.wizard.pages.general.grid:getDisplayValues(row) + row = Util.shallowCopy(row) + row.displayName = itemDB:getName(row) + return row +end + function machineWizard.wizard.pages.general:validate() return self.form:save() end @@ -168,7 +201,7 @@ function machineWizard:eventHandler(event) end end context.config.remoteDefaults[self.machine.name] = self.machine - Config.update('inventoryManagerX', context.config) + Config.update('milo', context.config) UI:setPreviousPage() diff --git a/inventoryManager/plugins/autocraftTask.lua b/milo/plugins/autocraftTask.lua similarity index 71% rename from inventoryManager/plugins/autocraftTask.lua rename to milo/plugins/autocraftTask.lua index 697e5ff..caf83a5 100644 --- a/inventoryManager/plugins/autocraftTask.lua +++ b/milo/plugins/autocraftTask.lua @@ -1,4 +1,4 @@ -local Lora = require('lora') +local Milo = require('milo') local Util = require('util') local Autocraft = { @@ -12,13 +12,13 @@ function Autocraft:cycle(context) if res.auto then res = Util.shallowCopy(res) res.count = 256 - list[Lora:uniqueKey(res)] = res + list[Milo:uniqueKey(res)] = res end end if not Util.empty(list) then - Lora:craftItems(list) + Milo:craftItems(list) end end -Lora:registerTask(Autocraft) +Milo:registerTask(Autocraft) diff --git a/inventoryManager/plugins/demandCraft.lua b/milo/plugins/demandCraft.lua similarity index 95% rename from inventoryManager/plugins/demandCraft.lua rename to milo/plugins/demandCraft.lua index 6a0c635..c30f2cf 100644 --- a/inventoryManager/plugins/demandCraft.lua +++ b/milo/plugins/demandCraft.lua @@ -1,6 +1,6 @@ local Craft = require('turtle.craft') local itemDB = require('itemDB') -local Lora = require('lora') +local Milo = require('milo') local UI = require('ui') local Util = require('util') @@ -98,7 +98,7 @@ function craftPage.wizard:eventHandler(event) end function craftPage.wizard.pages.resources:enable() - local items = Lora:listItems() + local items = Milo:listItems() local count = tonumber(self.parent.quantity.count.value) local recipe = Craft.findRecipe(craftPage.item) if recipe then @@ -118,7 +118,7 @@ function craftPage:eventHandler(event) UI:setPreviousPage() elseif event.type == 'accept' then - local key = Lora:uniqueKey(self.item) + local key = Milo:uniqueKey(self.item) demandCrafting[key] = Util.shallowCopy(self.item) demandCrafting[key].count = tonumber(self.wizard.pages.quantity.count.value) demandCrafting[key].ocount = demandCrafting[key].count @@ -155,7 +155,7 @@ function demandCraftingTask:cycle(context) end if Util.size(demandCrafted) > 0 then - Lora:craftItems(demandCrafted) + Milo:craftItems(demandCrafted) end for _,key in pairs(Util.keys(demandCrafting)) do @@ -166,7 +166,7 @@ function demandCraftingTask:cycle(context) item.statusCode = 'success' demandCrafting[key] = nil if item.eject then - Lora:eject(item, item.ocount) + Milo:eject(item, item.ocount) end end end @@ -174,4 +174,4 @@ function demandCraftingTask:cycle(context) end UI:addPage('craft', craftPage) -Lora:registerTask(demandCraftingTask) +Milo:registerTask(demandCraftingTask) diff --git a/inventoryManager/plugins/exportTask.lua b/milo/plugins/exportTask.lua similarity index 92% rename from inventoryManager/plugins/exportTask.lua rename to milo/plugins/exportTask.lua index 937610a..1e645f0 100644 --- a/inventoryManager/plugins/exportTask.lua +++ b/milo/plugins/exportTask.lua @@ -1,5 +1,5 @@ local itemDB = require('itemDB') -local Lora = require('lora') +local Milo = require('milo') local device = _G.device @@ -28,4 +28,4 @@ function ExportTask:cycle(context) end end -Lora:registerTask(ExportTask) +Milo:registerTask(ExportTask) diff --git a/milo/plugins/exportView.lua b/milo/plugins/exportView.lua new file mode 100644 index 0000000..3d01297 --- /dev/null +++ b/milo/plugins/exportView.lua @@ -0,0 +1,213 @@ +local itemDB = require('itemDB') +local Milo = require('milo') +local UI = require('ui') +local Util = require('util') + +local colors = _G.colors +local device = _G.device + +local itemSlideout = UI.SlideOut { + backgroundColor = colors.cyan, + menuBar = UI.MenuBar { + buttons = { + { text = 'Save', event = 'save' }, + { text = 'Cancel', event = 'cancel' }, + { text = 'Refresh', event = 'refresh', x = -9 }, + }, + }, + grid = UI.ScrollingGrid { + y = 2, ey = -6, + columns = { + { heading = 'Name', key = 'displayName', width = 31 }, + { heading = 'Qty', key = 'count' , width = 5 }, + }, + sortColumn = 'displayName', + help = 'Select item to export', + }, + filter = UI.TextEntry { + x = 2, ex = 18, y = -3, + limit = 50, + shadowText = 'filter', + backgroundColor = colors.lightGray, + backgroundFocusColor = colors.lightGray, + }, + form = UI.Form { + x = 21, y = -4, height = 3, + margin = 1, + manualControls = true, + [1] = UI.Chooser { + width = 7, + formLabel = 'Slot', formKey = 'slot', + nochoice = 1, + help = 'Export into this slot', + }, + [2] = UI.Chooser { + width = 7, + formLabel = 'Ignore Dmg', formKey = 'ignoreDamage', + pruneEmpty = true, + nochoice = 'No', + choices = { + { name = 'Yes', value = true }, + { name = 'No', value = false }, + }, + help = 'Ignore damage of item when exporting' + }, + [3] = UI.Chooser { + width = 7, + formLabel = 'Ignore NBT', formKey = 'ignoreNbtHash', + pruneEmpty = true, + nochoice = 'No', + choices = { + { name = 'Yes', value = true }, + { name = 'No', value = false }, + }, + help = 'Ignore NBT of item when exporting' + }, + }, + statusBar = UI.StatusBar { + backgroundColor = colors.cyan, + }, +} + +function itemSlideout:filterItems(t, filter) + if filter and #filter > 0 then + local r = {} + filter = filter:lower() + for _,v in pairs(t) do + if string.find(v.lname, filter) then + table.insert(r, v) + end + end + return r + end + return t +end + +function itemSlideout.grid:enable() + if not self.allItems then + self.allItems = Milo:listItems() + Milo:mergeResources(self.allItems) + self:setValues(self.allItems) + end + UI.Grid.enable(self) +end + +function itemSlideout:show(machine, entry) + self.machine = machine + self.entry = entry + + self.form.choices = { } + local m = device[machine.name] + for k = 1, m.size() do + table.insert(self.form[1].choices, { + name = k, + value = k, + }) + end + + if not entry.slot then + entry.slot = 1 + end + self.form:setValues(entry) + + UI.SlideOut.show(self) + self:setFocus(self.filter) + --self.filter:focus() +end + +function itemSlideout:eventHandler(event) + if event.type == 'text_change' then + local t = self:filterItems(self.grid.allItems, event.text) + self.grid:setValues(t) + self.grid:draw() + + elseif event.type == 'focus_change' then + self.statusBar:setStatus(event.focused.help) + + elseif event.type == 'save' then + local selected = self.grid:getSelected() + if not selected then + self.statusBar:setStatus('Select an item to export') + else + self.form:save() + self.form.values.name = itemDB:makeKey(selected) + table.insert(self.machine.exports, self.form.values) + self:hide() + end + + elseif event.type == 'cancel' then + self:hide() + + elseif event.type == 'refresh' then + self.allItems = Milo:listItems() + Milo:mergeResources(self.allItems) + local t = self:filterItems(self.allItems, self.filter.value) + self.grid:setValues(t) + self.grid:draw() + self.filter:focus() + else + return UI.SlideOut.eventHandler(self, event) + end + return true +end + +local exportView = UI.Window { + mtype = 'machine', + title = 'Export item into machine', + index = 3, + grid = UI.ScrollingGrid { + x = 2, ex = -6, y = 2, ey = -2, + columns = { + { heading = 'Slot', key = 'slot', width = 4 }, + { heading = 'Item', key = 'displayName' }, + }, + sortColumn = 'slot', + }, + add = UI.Button { + x = -4, y = 4, + text = '+', event = 'add_export', help = '...', + }, + remove = UI.Button { + x = -4, y = 6, + text = '-', event = 'remove_export', help = '...', + }, +} + +function exportView:save(machine) + machine.exports = not Util.empty(self.grid.values) and self.grid.values or nil + return true +end + +function exportView:setMachine(machine) + self.machine = machine + if not self.machine.exports then + self.machine.exports = { } + end + self.grid:setValues(machine.exports) +end + +function exportView.grid:getDisplayValues(row) + row = Util.shallowCopy(row) + row.displayName = itemDB:getName(row.name) + return row +end + +function exportView:eventHandler(event) + if event.type == 'grid_select' then + itemSlideout:show(self.machine, self.grid:getSelected()) + + elseif event.type == 'add_export' then + itemSlideout:show(self.machine, { }) + + elseif event.type == 'remove_export' then + local row = self.grid:getSelected() + if row then + Util.removeByValue(self.grid.values, row) + self.grid:update() + self.grid:draw() + end + end +end + +UI:getPage('machineWizard'):add({ items = itemSlideout }) +UI:getPage('machineWizard').wizard:add({ export = exportView }) diff --git a/inventoryManager/plugins/importTask.lua b/milo/plugins/importTask.lua similarity index 86% rename from inventoryManager/plugins/importTask.lua rename to milo/plugins/importTask.lua index b9fc3a3..9fd9bda 100644 --- a/inventoryManager/plugins/importTask.lua +++ b/milo/plugins/importTask.lua @@ -1,4 +1,4 @@ -local Lora = require('lora') +local Milo = require('milo') local device = _G.device @@ -8,7 +8,7 @@ local ImportTask = { function ImportTask:cycle(context) for source, v in pairs(context.config.remoteDefaults) do - if v.exports then + if v.imports then local machine = device[source] if machine and machine.getItemMeta then for slotNo in pairs(v.imports) do @@ -24,4 +24,4 @@ function ImportTask:cycle(context) end end -Lora:registerTask(ImportTask) +Milo:registerTask(ImportTask) diff --git a/inventoryManager/plugins/importView.lua b/milo/plugins/importView.lua similarity index 100% rename from inventoryManager/plugins/importView.lua rename to milo/plugins/importView.lua diff --git a/inventoryManager/plugins/inputChestTask.lua b/milo/plugins/inputChestTask.lua similarity index 88% rename from inventoryManager/plugins/inputChestTask.lua rename to milo/plugins/inputChestTask.lua index 29e5dcc..fb6072b 100644 --- a/inventoryManager/plugins/inputChestTask.lua +++ b/milo/plugins/inputChestTask.lua @@ -1,4 +1,4 @@ -local Lora = require('lora') +local Milo = require('milo') local device = _G.device @@ -21,4 +21,4 @@ function InputChest:cycle(context) end end -Lora:registerTask(InputChest) +Milo:registerTask(InputChest) diff --git a/inventoryManager/plugins/item.lua b/milo/plugins/item.lua similarity index 96% rename from inventoryManager/plugins/item.lua rename to milo/plugins/item.lua index bcbe553..c315296 100644 --- a/inventoryManager/plugins/item.lua +++ b/milo/plugins/item.lua @@ -1,12 +1,12 @@ local Ansi = require('ansi') -local Lora = require('lora') +local Milo = require('milo') local UI = require('ui') local Util = require('util') local colors = _G.colors local device = _G.device -local context = Lora:getContext() +local context = Milo:getContext() local itemPage = UI.Page { titleBar = UI.TitleBar { @@ -204,7 +204,7 @@ function itemPage:eventHandler(event) elseif event.type == 'form_complete' then local values = self.form.values - local originalKey = Lora:uniqueKey(self.item) + local originalKey = Milo:uniqueKey(self.item) local filtered = Util.shallowCopy(values) filtered.low = tonumber(filtered.low) @@ -232,10 +232,10 @@ function itemPage:eventHandler(event) filtered.ignoreNbtHash = nil end context.resources[originalKey] = nil - context.resources[Lora:uniqueKey(filtered)] = filtered + context.resources[Milo:uniqueKey(filtered)] = filtered filtered.count = nil - Lora:saveResources() + Milo:saveResources() UI:setPreviousPage() diff --git a/inventoryManager/plugins/jobList.lua b/milo/plugins/jobList.lua similarity index 81% rename from inventoryManager/plugins/jobList.lua rename to milo/plugins/jobList.lua index efba70f..a320c1d 100644 --- a/inventoryManager/plugins/jobList.lua +++ b/milo/plugins/jobList.lua @@ -1,10 +1,10 @@ -local Lora = require('lora') +local Milo = require('milo') local Peripheral = require('peripheral') local UI = require('ui') local colors = _G.colors -local context = Lora:getContext() +local context = Milo:getContext() local mon = Peripheral.lookup(context.config.monitor) or error('Monitor is not attached') local display = UI.Device { @@ -39,11 +39,11 @@ function jobList:updateList(craftList) end function jobList.grid:getRowTextColor(row, selected) - if row.statusCode == Lora.STATUS_ERROR then + if row.statusCode == Milo.STATUS_ERROR then return colors.red - elseif row.statusCode == Lora.STATUS_WARNING then + elseif row.statusCode == Milo.STATUS_WARNING then return colors.yellow - elseif row.statusCode == Lora.STATUS_INFO then + elseif row.statusCode == Milo.STATUS_INFO then return colors.lime end return UI.Grid:getRowTextColor(row, selected) @@ -58,8 +58,8 @@ local JobListTask = { } function JobListTask:cycle() - jobList:updateList(Lora:getCraftingStatus()) + jobList:updateList(Milo:getCraftingStatus()) end -Lora:registerTask(JobListTask) +Milo:registerTask(JobListTask) context.jobList = jobList diff --git a/milo/plugins/learn.lua b/milo/plugins/learn.lua new file mode 100644 index 0000000..fdbe940 --- /dev/null +++ b/milo/plugins/learn.lua @@ -0,0 +1,61 @@ +local Milo = require('milo') +local UI = require('ui') + +local context = Milo:getContext() + +local learnPage = UI.Dialog { + height = 6, width = UI.term.width - 6, + title = 'Learn Recipe', + chooser = UI.Chooser { + x = 8, y = 3, + width = 20, + }, + cancel = UI.Button { + x = 3, y = -2, + text = 'Cancel', event = 'cancel' + }, + accept = UI.Button { + ex = -3, y = -2, + width = 8, + text = 'Ok', event = 'accept', + }, +} + +function learnPage:enable() + self.chooser.choices = { } + + for k in pairs(context.learnTypes) do + table.insert(self.chooser.choices, { + name = k, + value = k, + }) + end + self.chooser.value = + Milo:getState('learnType') or + self.chooser.choices[1].value + + self:focusFirst() + UI.Dialog.enable(self) +end + +function learnPage:disable() + UI.Dialog.disable(self) +end + +function learnPage:eventHandler(event) + if event.type == 'cancel' then + UI:setPreviousPage() + + elseif event.type == 'accept' then + local choice = self.chooser.value + + Milo:setState('learnType', choice) + UI:setPage(context.learnTypes[choice]) + + else + return UI.Dialog.eventHandler(self, event) + end + return true +end + +UI:addPage('learn', learnPage) diff --git a/inventoryManager/plugins/limitTask.lua b/milo/plugins/limitTask.lua similarity index 82% rename from inventoryManager/plugins/limitTask.lua rename to milo/plugins/limitTask.lua index 82767d3..a9e123b 100644 --- a/inventoryManager/plugins/limitTask.lua +++ b/milo/plugins/limitTask.lua @@ -1,4 +1,4 @@ -local Lora = require('lora') +local Milo = require('milo') local LimitTask = { priority = 10, @@ -20,7 +20,7 @@ function LimitTask:cycle(context) for _,res in pairs(context.resources) do if res.limit then - local item = Lora:getItemWithQty(res, res.ignoreDamage, res.ignoreNbtHash) + local item = Milo:getItemWithQty(res, res.ignoreDamage, res.ignoreNbtHash) if item and item.count > res.limit then context.inventoryAdapter:provide( { name = item.name, damage = item.damage, nbtHash = item.nbtHash }, @@ -32,4 +32,4 @@ function LimitTask:cycle(context) end end -Lora:registerTask(LimitTask) +Milo:registerTask(LimitTask) diff --git a/inventoryManager/plugins/listing.lua b/milo/plugins/listing.lua similarity index 92% rename from inventoryManager/plugins/listing.lua rename to milo/plugins/listing.lua index 8a9aeb8..421f42d 100644 --- a/inventoryManager/plugins/listing.lua +++ b/milo/plugins/listing.lua @@ -1,16 +1,16 @@ local Craft = require('turtle.craft') local itemDB = require('itemDB') -local Lora = require('lora') +local Milo = require('milo') local UI = require('ui') local Util = require('util') local colors = _G.colors local os = _G.os -local context = Lora:getContext() +local context = Milo:getContext() local function queue(fn) - while Lora:isCraftingPaused() do + while Milo:isCraftingPaused() do os.sleep(1) end fn() @@ -120,13 +120,13 @@ debug(event) elseif event.type == 'eject' then local item = self.grid:getSelected() if item then - queue(function() Lora:eject(item, 1) end) + queue(function() Milo:eject(item, 1) end) end elseif event.type == 'eject_stack' then local item = self.grid:getSelected() if item then - queue(function() Lora:eject(item, itemDB:getMaxCount(item)) end) + queue(function() Milo:eject(item, itemDB:getMaxCount(item)) end) end elseif event.type == 'machines' then @@ -171,17 +171,17 @@ debug(event) elseif event.type == 'forget' then local item = self.grid:getSelected() if item then - local key = Lora:uniqueKey(item) + local key = Milo:uniqueKey(item) if context.userRecipes[key] then context.userRecipes[key] = nil - Util.writeTable(Lora.RECIPES_FILE, context.userRecipes) + Util.writeTable(Milo.RECIPES_FILE, context.userRecipes) Craft.loadRecipes() end if context.resources[key] then context.resources[key] = nil - Lora:saveResources() + Milo:saveResources() end self.notification:info('Forgot: ' .. item.name) @@ -211,8 +211,8 @@ function listingPage:enable() end function listingPage:refresh() - self.allItems = Lora:listItems() - Lora:mergeResources(self.allItems) + self.allItems = Milo:listItems() + Milo:mergeResources(self.allItems) self:applyFilter() end diff --git a/milo/plugins/machineLearn.lua b/milo/plugins/machineLearn.lua new file mode 100644 index 0000000..36e35df --- /dev/null +++ b/milo/plugins/machineLearn.lua @@ -0,0 +1,172 @@ +local Craft = require('turtle.craft') +local itemDB = require('itemDB') +local Milo = require('milo') +local UI = require('ui') +local Util = require('util') + +local colors = _G.colors +local device = _G.device +local turtle = _G.turtle + +local MACHINE_LOOKUP = 'usr/config/machine_crafting.db' + +local context = Milo:getContext() + +local function getTurtleInventory() + local introspectionModule = device['plethora:introspection'] or + error('Introspection module not found') + + local list = { } + for i = 1,16 do + list[i] = introspectionModule.getInventory().getItemMeta(i) + end + return list +end + +local machineLearnWizard = UI.Page { + titleBar = UI.TitleBar { title = 'Learn a crafting recipe' }, + wizard = UI.Wizard { + y = 2, ey = -2, + pages = { + machine = UI.Window { + index = 1, + grid = UI.ScrollingGrid { + y = 2, ey = -2, + values = context.config.remoteDefaults, + columns = { + { heading = 'Name', key = 'displayName' }, + }, + sortColumn = 'displayName', + }, + }, + confirmation = UI.Window { + index = 2, + notice = UI.TextArea { + x = 2, ex = -2, y = 2, ey = -2, + backgroundColor = colors.black, + value = +[[Place items in slots according to the machine's inventory. + +Place the result in the last slot of the turtle. + +Example: Slot 1 is the top slot in a furnace.]], + }, + }, + }, + }, + notification = UI.Notification { }, +} + +local pages = machineLearnWizard.wizard.pages +local machine + +function pages.machine.grid:getDisplayValues(row) + row = Util.shallowCopy(row) + row.displayName = row.displayName or row.name + return row +end + +function pages.machine:validate() + +-- TODO: validation should only be invoked when moving forward (i think) +-- TODO: index number validation in wizard + + local selected = self.grid:getSelected() + if not selected then + machineLearnWizard.notification:error('No machines configured') + return + end + + machine = device[selected.name] + if not machine then + machineLearnWizard.notification:error('Machine not found') + return + end + + if not machine.size then + machineLearnWizard.notification:error('Invalid machine') + return + end + + return true +end + +function pages.confirmation:validate() + local inventory = getTurtleInventory() + local result = inventory[16] + local slotCount = machine.size() + + inventory[16] = nil + + if not result then + machineLearnWizard.notification:error('Result must be placed in last slot') + return + end + + if Util.empty(inventory) then + machineLearnWizard.notification:error('Ingredients not present') + return + end + + for k in pairs(inventory) do + if k > slotCount then + machineLearnWizard.notification:error( + 'Slot ' .. k .. ' is not valid\nThe valid slots are 1 - ' .. machine.size()) + return + end + end + + local recipe = { + count = result.count, + ingredients = { }, + maxCount = result.maxCount ~= 64 and result.maxCount or nil, + } + + for k,v in pairs(inventory) do + recipe.ingredients[k] = Milo:uniqueKey(v) + end + + local key = Milo:uniqueKey(result) + + -- save the recipe + context.userRecipes[key] = recipe + Util.writeTable(Milo.RECIPES_FILE, context.userRecipes) + Craft.loadRecipes() + + -- save the machine association + Craft.machineLookup[key] = machine.name + Util.writeTable(MACHINE_LOOKUP, Craft.machineLookup) + + local listingPage = UI:getPage('listing') + local displayName = itemDB:getName(result) + + listingPage.statusBar.filter:setValue(displayName) + listingPage.notification:success('Learned: ' .. displayName) + listingPage.filter = displayName + listingPage:refresh() + listingPage.grid:draw() + + return true +end + +function machineLearnWizard:enable() + Milo:pauseCrafting() + UI.Page.enable(self) +end + +function machineLearnWizard:disable() + Milo:resumeCrafting() + UI.Page.disable(self) +end + +function machineLearnWizard:eventHandler(event) + if event.type == 'cancel' or event.type == 'accept' then + turtle.emptyInventory() + UI:setPage('listing') + else + return UI.Page.eventHandler(self, event) + end + return true +end + +context.learnTypes['Machine processing'] = machineLearnWizard diff --git a/milo/plugins/potionImportTask.lua b/milo/plugins/potionImportTask.lua new file mode 100644 index 0000000..68edbfb --- /dev/null +++ b/milo/plugins/potionImportTask.lua @@ -0,0 +1,24 @@ +local Milo = require('milo') + +local device = _G.device + +local PotionImportTask = { + priority = 3, +} + +function PotionImportTask:cycle(context) + for _, v in pairs(device) do + if v.type == 'minecraft:brewing_stand' and v.getBrewTime() == 0 then + local list = v.list() + if not list[4] and list[1] then + for i = 1, 3 do + if list[i] then + context.inventoryAdapter:insert(i, 1, nil, list[i], v.name) + end + end + end + end + end +end + +Milo:registerTask(PotionImportTask) diff --git a/inventoryManager/plugins/replenishTask.lua b/milo/plugins/replenishTask.lua similarity index 81% rename from inventoryManager/plugins/replenishTask.lua rename to milo/plugins/replenishTask.lua index 1568f5b..dc5616a 100644 --- a/inventoryManager/plugins/replenishTask.lua +++ b/milo/plugins/replenishTask.lua @@ -1,5 +1,5 @@ local itemDB = require('itemDB') -local Lora = require('lora') +local Milo = require('milo') local ReplenishTask = { priority = 30, @@ -10,7 +10,7 @@ function ReplenishTask:cycle(context) for _,res in pairs(context.resources) do if res.low then - local item = Lora:getItemWithQty(res, res.ignoreDamage, res.ignoreNbtHash) + local item = Milo:getItemWithQty(res, res.ignoreDamage, res.ignoreNbtHash) if not item then item = { damage = res.damage, @@ -25,7 +25,7 @@ function ReplenishTask:cycle(context) if res.ignoreDamage then item.damage = 0 end - local key = Lora:uniqueKey(res) + local key = Milo:uniqueKey(res) craftList[key] = { damage = item.damage, @@ -40,7 +40,7 @@ function ReplenishTask:cycle(context) end end - Lora:craftItems(craftList) + Milo:craftItems(craftList) end -Lora:registerTask(ReplenishTask) +Milo:registerTask(ReplenishTask) diff --git a/inventoryManager/plugins/storageView.lua b/milo/plugins/storageView.lua similarity index 100% rename from inventoryManager/plugins/storageView.lua rename to milo/plugins/storageView.lua diff --git a/milo/plugins/turtleLearn.lua b/milo/plugins/turtleLearn.lua new file mode 100644 index 0000000..6897035 --- /dev/null +++ b/milo/plugins/turtleLearn.lua @@ -0,0 +1,175 @@ +local Craft = require('turtle.craft') +local itemDB = require('itemDB') +local Milo = require('milo') +local UI = require('ui') +local Util = require('util') + +local device = _G.device +local turtle = _G.turtle + +local context = Milo:getContext() + +local function getTurtleInventory() + local introspectionModule = device['plethora:introspection'] or + error('Introspection module not found') + + local list = { } + for i = 1,16 do + list[i] = introspectionModule.getInventory().getItemMeta(i) + end + return list +end + +local function learnRecipe() + local ingredients = getTurtleInventory() + + if not ingredients then + return false, 'No recipe defined' + end + + turtle.select(1) + if not turtle.craft() then + return false, 'Failed to craft' + end + + local results = getTurtleInventory() + if not results or not results[1] then + return false, 'Failed to craft' + end + + local maxCount + local newRecipe = { + ingredients = ingredients, + } + + local numResults = 0 + for _,v in pairs(results) do + if v.count > 0 then + numResults = numResults + 1 + end + end + if numResults > 1 then + for _,v1 in pairs(results) do + for _,v2 in pairs(ingredients) do + if v1.name == v2.name and + v1.nbtHash == v2.nbtHash and + (v1.damage == v2.damage or + (v1.maxDamage > 0 and v2.maxDamage > 0 and + v1.damage ~= v2.damage)) then + if not newRecipe.crafingTools then + newRecipe.craftingTools = { } + end + local tool = Util.shallowCopy(v2) + if tool.maxDamage > 0 then + tool.damage = '*' + end + + --[[ + Turtles can only craft one item at a time using a tool :( + ]]-- + maxCount = 1 + + newRecipe.craftingTools[Milo:uniqueKey(tool)] = true + v1.craftingTool = true + break + end + end + end + end + + local recipe + for _,v in pairs(results) do + if not v.craftingTool then + recipe = v + if maxCount then + recipe.maxCount = maxCount + end + break + end + end + + if not recipe then + debug(results) + debug(newRecipe) + error('Failed - view system log') + end + + newRecipe.count = recipe.count + + local key = Milo:uniqueKey(recipe) + if recipe.maxCount ~= 64 then + newRecipe.maxCount = recipe.maxCount + end + for k,ingredient in pairs(Util.shallowCopy(ingredients)) do + if ingredient.maxDamage > 0 then + -- ingredient.damage = '*' -- I don't think this is right + end + ingredients[k] = Milo:uniqueKey(ingredient) + end + + context.userRecipes[key] = newRecipe + Util.writeTable(Milo.RECIPES_FILE, context.userRecipes) + Craft.loadRecipes() + + turtle.emptyInventory() + + return recipe +end + +local turtleLearnWizard = UI.Page { + titleBar = UI.TitleBar { title = 'Learn a crafting recipe' }, + wizard = UI.Wizard { + y = 2, ey = -3, + pages = { + confirmation = UI.Window { + index = 1, + notice = UI.TextArea { + x = 2, ex = -2, y = 2, ey = -2, + value = +[[Place recipe in turtle!]], + }, + }, + }, + }, + notification = UI.Notification { }, +} + +function turtleLearnWizard:enable() + Milo:pauseCrafting() + UI.Page.enable(self) +end + +function turtleLearnWizard:disable() + Milo:resumeCrafting() + UI.Page.disable(self) +end + +function turtleLearnWizard.wizard.pages.confirmation:validate() + local recipe, msg = learnRecipe(self) + + if recipe then + local listingPage = UI:getPage('listing') + local displayName = itemDB:getName(recipe) + + listingPage.statusBar.filter:setValue(displayName) + listingPage.notification:success('Learned: ' .. displayName) + listingPage.filter = displayName + listingPage:refresh() + listingPage.grid:draw() + + return true + else + turtleLearnWizard.notification:error(msg) + end +end + +function turtleLearnWizard:eventHandler(event) + if event.type == 'cancel' or event.type == 'accept' then + UI:setPage('listing') + else + return UI.Page.eventHandler(self, event) + end + return true +end + +context.learnTypes['Turtle crafting'] = turtleLearnWizard