From 9e5487b10059f5d09323283400d6c1e7e23fcc1a Mon Sep 17 00:00:00 2001 From: kepler155c Date: Sun, 21 Oct 2018 18:43:04 -0400 Subject: [PATCH] Lora major refactor --- inventoryManager/apis/lora/lora.lua | 334 ++++ inventoryManager/apis/lora/tasks.lua | 12 + inventoryManager/apis/networkedAdapter18.lua | 34 +- inventoryManager/inventoryManager.lua | 1509 +----------------- inventoryManager/plugins/autocraftTask.lua | 24 + inventoryManager/plugins/craft.lua | 167 ++ inventoryManager/plugins/inputChestTask.lua | 37 + inventoryManager/plugins/item.lua | 246 +++ inventoryManager/plugins/jobList.lua | 65 + inventoryManager/plugins/learn.lua | 174 ++ inventoryManager/plugins/limitTask.lua | 36 + inventoryManager/plugins/listing.lua | 250 +++ inventoryManager/plugins/machines.lua | 152 ++ inventoryManager/plugins/replenishTask.lua | 47 + 14 files changed, 1605 insertions(+), 1482 deletions(-) create mode 100644 inventoryManager/apis/lora/lora.lua create mode 100644 inventoryManager/apis/lora/tasks.lua create mode 100644 inventoryManager/plugins/autocraftTask.lua create mode 100644 inventoryManager/plugins/craft.lua create mode 100644 inventoryManager/plugins/inputChestTask.lua create mode 100644 inventoryManager/plugins/item.lua create mode 100644 inventoryManager/plugins/jobList.lua create mode 100644 inventoryManager/plugins/learn.lua create mode 100644 inventoryManager/plugins/limitTask.lua create mode 100644 inventoryManager/plugins/listing.lua create mode 100644 inventoryManager/plugins/machines.lua create mode 100644 inventoryManager/plugins/replenishTask.lua diff --git a/inventoryManager/apis/lora/lora.lua b/inventoryManager/apis/lora/lora.lua new file mode 100644 index 0000000..a274aca --- /dev/null +++ b/inventoryManager/apis/lora/lora.lua @@ -0,0 +1,334 @@ +local Craft = require('turtle.craft') +local itemDB = require('itemDB') +local Util = require('util') + +local os = _G.os +local term = _G.term +local turtle = _G.turtle + +local Lora = { + RECIPES_FILE = 'usr/config/recipes.db', + RESOURCE_FILE = 'usr/config/resources.db', + + STATUS_INFO = 'info', + STATUS_WARNING = 'warning', + STATUS_ERROR = 'error', + + tasks = { }, + craftingStatus = { }, +} + +function Lora:init(context) + self.context = context +end + +function Lora:getContext() + return self.context +end + +function Lora:pauseCrafting() + self.craftingPaused = true +end + +function Lora:resumeCrafting() + self.craftingPaused = false +end + +function Lora:isCraftingPaused() + return self.craftingPaused +end + +function Lora:uniqueKey(item) + return table.concat({ item.name, item.damage, item.nbtHash }, ':') +end + +function Lora:getCraftingStatus() + return self.craftingStatus +end + +function Lora:resetCraftingStatus() + self.craftingStatus = { } +end + +function Lora:updateCraftingStatus(list) + for k,v in pairs(list) do + self.craftingStatus[k] = v + end + + self.context.jobList:updateList(self.craftingStatus) +end + +function Lora:registerTask(task) + table.insert(self.tasks, task) +end + +function Lora:showError(msg) + term.clear() + self.context.jobList:showError() + print(msg) + print('rebooting in 5 secs') + os.sleep(5) + os.reboot() +end + +function Lora: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 + +function Lora:getItemWithQty(res, ignoreDamage, ignoreNbtHash) + local items = self:listItems() + local item = self:getItem(items, res, ignoreDamage, ignoreNbtHash) + + if item and (ignoreDamage or ignoreNbtHash) then + local count = 0 + + for _,v in pairs(items) do + if item.name == v.name and + (ignoreDamage or item.damage == v.damage) and + (ignoreNbtHash or item.nbtHash == v.nbtHash) then + count = count + v.count + end + end + item.count = count + end + + return item +end + +function Lora:clearGrid() + local function clear() + turtle.eachFilledSlot(function(slot) + self.context.inventoryAdapter:insert(slot.index, slot.count, nil, slot) + end) + + for i = 1, 16 do + if turtle.getItemCount(i) ~= 0 then + return false + end + end + return true + end + return clear() or clear() +end + +function Lora:eject(item, qty) + local s, m = pcall(function() + self.context.inventoryAdapter:provide(item, qty) + turtle.emptyInventory() + end) + if not s and m then + debug(m) + end +end + +function Lora:saveResources() + local t = { } + + for k,v in pairs(self.context.resources) do + v = Util.shallowCopy(v) + local keys = Util.transpose({ 'auto', 'low', 'limit', + 'ignoreDamage', 'ignoreNbtHash', + 'rsControl', 'rsDevice', 'rsSide' }) + + for _,key in pairs(Util.keys(v)) do + if not keys[key] then + v[key] = nil + end + end + if not Util.empty(v) then + t[k] = v + end + end + + Util.writeTable(Lora.RESOURCE_FILE, t) +end + +-- Return a list of everything in the system +function Lora:listItems() + for _ = 1, 5 do + self.items = self.context.inventoryAdapter:listItems() + if self.items then + break + end +-- jobList:showError('Error - retrying in 3 seconds') + os.sleep(3) + end + if not self.items then + self:showError('Error - rebooting in 5 seconds') + end + + return self.items +end + +function Lora:addCraftingRequest(item, craftList, count) + local key = self:uniqueKey(item) + local request = craftList[key] + if not craftList[key] then + request = { name = item.name, damage = item.damage, nbtHash = item.nbtHash, count = 0 } + request.displayName = itemDB:getName(request) + craftList[key] = request + end + request.count = request.count + count + return request +end + +-- Craft +function Lora:craftItem(recipe, items, originalItem, craftList, count) + local missing = { } + local toCraft = Craft.getCraftableAmount(recipe, count, items, missing) + if missing.name then + originalItem.status = string.format('%s missing', itemDB:getName(missing.name)) + originalItem.statusCode = self.STATUS_WARNING + end + + local crafted = 0 + + if toCraft > 0 then + crafted = Craft.craftRecipe(recipe, toCraft, self.context.inventoryAdapter) + self:clearGrid() + items = self:listItems() + count = count - crafted + end + + if count > 0 and items then + local ingredients = Craft.getResourceList4(recipe, items, count) + for _,ingredient in pairs(ingredients) do + if ingredient.need > 0 then + local item = self:addCraftingRequest(ingredient, craftList, ingredient.need) + if Craft.findRecipe(item) then + item.status = string.format('%s missing', itemDB:getName(ingredient)) + item.statusCode = self.STATUS_WARNING + else + item.status = 'no recipe' + item.statusCode = self.STATUS_ERROR + end + end + end + end + return crafted +end + +-- Craft as much as possible regardless if all ingredients are available +function Lora:forceCraftItem(inRecipe, items, originalItem, craftList, inCount) + local summed = { } + local throttle = Util.throttle() + + local function sumItems(recipe, count) + count = math.ceil(count / recipe.count) + local craftable = count + + for key,iqty in pairs(Craft.sumIngredients(recipe)) do + throttle() + local item = itemDB:splitKey(key) + local summedItem = summed[key] + if not summedItem then + summedItem = Util.shallowCopy(item) + summedItem.recipe = Craft.findRecipe(item) + summedItem.count = Craft.getItemCount(items, key) + summedItem.need = 0 + summedItem.used = 0 + summedItem.craftable = 0 + summed[key] = summedItem + end + + local total = count * iqty -- 4 * 2 + local used = math.min(summedItem.count, total) -- 5 + local need = total - used -- 3 + + if recipe.craftingTools and recipe.craftingTools[key] then + if summedItem.count > 0 then + summedItem.used = 1 + summedItem.need = 0 + need = 0 + elseif not summedItem.recipe then + summedItem.need = 1 + need = 1 + else + need = 1 + end + else + summedItem.count = summedItem.count - used + summedItem.used = summedItem.used + used + end + + if need > 0 then + if not summedItem.recipe then + craftable = math.min(craftable, math.floor(used / iqty)) + summedItem.need = summedItem.need + need + else + local c = sumItems(summedItem.recipe, need) -- 4 + craftable = math.min(craftable, math.floor((used + c) / iqty)) + summedItem.craftable = summedItem.craftable + c + end + end + end + if craftable > 0 then + craftable = Craft.craftRecipe(recipe, craftable * recipe.count, + self.context.inventoryAdapter) / recipe.count + self:clearGrid() + end + + return craftable * recipe.count + end + + local count = sumItems(inRecipe, inCount) + + if count < inCount then + for _,ingredient in pairs(summed) do + if ingredient.need > 0 then + local item = self:addCraftingRequest(ingredient, craftList, ingredient.need) + if Craft.findRecipe(item) then + item.status = string.format('%s missing', itemDB:getName(ingredient)) + item.statusCode = self.STATUS_WARNING + else + item.status = '(no recipe)' + item.statusCode = self.STATUS_ERROR + end + end + end + end + return count +end + +function Lora:craft(recipe, items, item, craftList) + item.status = nil + item.statusCode = nil + item.crafted = 0 + + if self:isCraftingPaused() then + return + end + + if not self:clearGrid() then + item.status = 'Grid obstructed' + item.statusCode = self.STATUS_ERROR + return + end + + if item.forceCrafting then + item.crafted = self:forceCraftItem(recipe, items, item, craftList, item.count) + else + item.crafted = self:craftItem(recipe, items, item, craftList, item.count) + end +end + +function Lora:craftItems(craftList) + for _,key in pairs(Util.keys(craftList)) do + local item = craftList[key] + local recipe = Craft.recipes[key] + if recipe then + self:craft(recipe, self:listItems(), item, craftList) + elseif not self.context.controllerAdapter then + item.status = '(no recipe)' + item.statusCode = self.STATUS_ERROR + end + end +end + +return Lora diff --git a/inventoryManager/apis/lora/tasks.lua b/inventoryManager/apis/lora/tasks.lua new file mode 100644 index 0000000..ac3ca07 --- /dev/null +++ b/inventoryManager/apis/lora/tasks.lua @@ -0,0 +1,12 @@ +local Util = require('util') + +local fs = _G.fs +local shell = _ENV.shell + +local Tasks = { + list = { } +} + + + +return Tasks diff --git a/inventoryManager/apis/networkedAdapter18.lua b/inventoryManager/apis/networkedAdapter18.lua index 99e36aa..bd7a538 100644 --- a/inventoryManager/apis/networkedAdapter18.lua +++ b/inventoryManager/apis/networkedAdapter18.lua @@ -10,6 +10,7 @@ function NetworkedAdapter:init(args) name = 'Networked Adapter', remotes = { }, remoteDefaults = { }, + dirty = true, } Util.merge(self, defaults) Util.merge(self, args) @@ -53,17 +54,25 @@ function NetworkedAdapter:isValid() end function NetworkedAdapter:refresh(throttle) + self.dirty = true return self:listItems(throttle) end -- provide a consolidated list of items function NetworkedAdapter:listItems(throttle) + if not self.dirty then + return self.items + end + local cache = { } local items = { } throttle = throttle or Util.throttle() for _, remote in pairs(self.remotes) do - remote:listItems(throttle) + if not remote:listItems(throttle) then + debug('no List') + error('Listing failed: ', remote.name) + end local rcache = remote.cache or { } -- TODO: add a method in each adapter that only updates a passed cache @@ -84,10 +93,10 @@ function NetworkedAdapter:listItems(throttle) end end - if not Util.empty(items) then - self.cache = cache - return items - end + self.dirty = false + self.cache = cache + self.items = items + return items end function NetworkedAdapter:getItemInfo(item) @@ -99,19 +108,16 @@ function NetworkedAdapter:getItemInfo(item) return items[key] end -function NetworkedAdapter:getPercentUsed() - if self.cache and self.getDrawerCount then - return math.floor(Util.size(self.cache) / self.getDrawerCount() * 100) - end - return 0 -end - function NetworkedAdapter:provide(item, qty, slot, direction) local total = 0 for _, remote in ipairs(self.remotes) do debug('%s -> slot %d: %d %s', remote.side, slot or -1, qty, item.name) local amount = remote:provide(item, qty, slot, direction) + if amount > 0 then + self.dirty = true + remote.dirty = true + end qty = qty - amount total = total + amount if qty <= 0 then @@ -158,6 +164,10 @@ debug('attempting to insert ' .. item.name) local function insert(remote) debug('slot %d -> %s: %s', slot, remote.side, qty) local amount = remote:insert(slot, qty, toSlot) + if amount > 0 then + self.dirty = true + remote.dirty = true + end qty = qty - amount total = total + amount end diff --git a/inventoryManager/inventoryManager.lua b/inventoryManager/inventoryManager.lua index 9a5fa5c..d751fe8 100644 --- a/inventoryManager/inventoryManager.lua +++ b/inventoryManager/inventoryManager.lua @@ -19,7 +19,7 @@ Refined storage TODO: add required block - Turtle crafting (optional): + Turtle crafting: 1. The turtle must have a crafting table equipped. 2. Equip the turtle with an introspection module. @@ -42,48 +42,35 @@ Configuration: Configuration file is usr/config/inventoryManager - valid sides: - top, bottom, left, right, front, back - - valid directions: - up, down, north, south, east, west - - Required: - computerFacing : direction turtle is facing - inventory : side for the main inventory (can be the same as the controller) - - Optional: - craftingChest : side for the chest used for crafting - controller : side for AE cable/interface or RS controller - trashDirection : direction of trash block (trashcan/inventory/etc) in - relationship to the main inventory. This block does not - need to touch the turtle, only the main inventory block. monitor : valid options include: type/monitor - will use the first monitor found side/north - specify a direction (top/bottom/east/etc) name/monitor_1 - specify the exact name of the peripheral ]]-- +--[[ +limit +organize +replenish +autocraft +]] + _G.requireInjector() -local Ansi = require('ansi') local Config = require('config') -local Craft = require('turtle.craft') local Event = require('event') local itemDB = require('itemDB') +local Lora = require('lora/lora') local Peripheral = require('peripheral') -local Terminal = require('terminal') local UI = require('ui') local Util = require('util') local InventoryAdapter = require('inventoryAdapter') -local colors = _G.colors -local device = _G.device +local fs = _G.fs local multishell = _ENV.multishell -local os = _G.os -local term = _G.term -local turtle = _G.turtle +local shell = _ENV.shell + if multishell then multishell.setTitle(multishell.getCurrent(), 'Resource Manager') @@ -100,29 +87,20 @@ if not modem or not modem.getNameLocal then error('Wired modem is not connected') end -local feederAdapter -local controllerAdapter - local storage = { } for k,v in pairs(config.remoteDefaults) do if v.mtype == 'storage' then storage[k] = v - elseif v.mtype == 'input' then - feederAdapter = InventoryAdapter.wrap({ side = k, direction = modem.getNameLocal() }) elseif v.mtype == 'controller' then -- TODO: look for controller end end -debug(storage) local inventoryAdapter = InventoryAdapter.wrap({ remoteDefaults = storage }) if not inventoryAdapter then error('Invalid inventory configuration') end -local introspectionModule = device['plethora:introspection'] or - error('introspection module not found') - -- TODO: cleanup for _, v in pairs(modem.getNamesRemote()) do local remote = Peripheral.get({ name = v }) @@ -141,1462 +119,53 @@ for _, v in pairs(modem.getNamesRemote()) do end end -local STATUS_INFO = 'info' -local STATUS_WARNING = 'warning' -local STATUS_ERROR = 'error' - -local RESOURCE_FILE = 'usr/config/resources.db' -local RECIPES_FILE = 'usr/config/recipes.db' - -local craftingPaused = false -local canCraft = not not (turtle and turtle.craft) -local canLearn = not not (canCraft and introspectionModule) -local userRecipes = Util.readTable(RECIPES_FILE) or { } -local jobList -local resources -local demandCrafting = { } - -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 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(Craft.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 listItems() - local items - for _ = 1, 5 do - items = inventoryAdapter:listItems() - if items then - break - end - jobList:showError('Error - retrying in 3 seconds') - os.sleep(3) - end - if not items then --- error('could not check inventory') -term.clear() -jobList:showError('Error - rebooting in 5 seconds') -print('Communication failure') -print('rebooting in 5 secs') -os.sleep(5) -os.reboot() - end - - return items -end - -local function filterItems(t, filter, displayMode) - if filter or displayMode > 0 then - local r = { } - if filter then - filter = filter:lower() - end - for _,v in pairs(t) do - if not filter or string.find(v.lname, filter, 1, true) then - if not displayMode or - displayMode == 0 or - displayMode == 1 and v.count > 0 or - displayMode == 2 and v.has_recipe then - table.insert(r, v) - end - end - end - return r - end - return t -end - -local function clearGrid() - local function clear() - turtle.eachFilledSlot(function(slot) - inventoryAdapter:insert(slot.index, slot.count, nil, slot) - end) - - for i = 1, 16 do - if turtle.getItemCount(i) ~= 0 then - return false - end - end - return true - end - return clear() or clear() -end - -local function addCraftingRequest(item, craftList, count) - local key = uniqueKey(item) - local request = craftList[key] - if not craftList[key] then - request = { name = item.name, damage = item.damage, nbtHash = item.nbtHash, count = 0 } - request.displayName = itemDB:getName(request) - craftList[key] = request - end - request.count = request.count + count - return request -end - --- Craft -local function craftItem(recipe, items, originalItem, craftList, count) - local missing = { } - local toCraft = Craft.getCraftableAmount(recipe, count, items, missing) - if missing.name then - originalItem.status = string.format('%s missing', itemDB:getName(missing.name)) - originalItem.statusCode = STATUS_WARNING - end - - local crafted = 0 - - if toCraft > 0 then - crafted = Craft.craftRecipe(recipe, toCraft, inventoryAdapter) - clearGrid() - items = listItems() - count = count - crafted - end - - if count > 0 and items then - local ingredients = Craft.getResourceList4(recipe, items, count) - for _,ingredient in pairs(ingredients) do - if ingredient.need > 0 then - local item = addCraftingRequest(ingredient, craftList, ingredient.need) - if Craft.findRecipe(item) then - item.status = string.format('%s missing', itemDB:getName(ingredient)) - item.statusCode = STATUS_WARNING - else - item.status = 'no recipe' - item.statusCode = STATUS_ERROR - end - end - end - end - return crafted -end - --- Craft as much as possible regardless if all ingredients are available -local function forceCraftItem(inRecipe, items, originalItem, craftList, inCount) - local summed = { } - local throttle = Util.throttle() - - local function sumItems(recipe, count) - count = math.ceil(count / recipe.count) - local craftable = count - - for key,iqty in pairs(Craft.sumIngredients(recipe)) do - throttle() - local item = itemDB:splitKey(key) - local summedItem = summed[key] - if not summedItem then - summedItem = Util.shallowCopy(item) - summedItem.recipe = Craft.findRecipe(item) - summedItem.count = Craft.getItemCount(items, key) - summedItem.need = 0 - summedItem.used = 0 - summedItem.craftable = 0 - summed[key] = summedItem - end - - local total = count * iqty -- 4 * 2 - local used = math.min(summedItem.count, total) -- 5 - local need = total - used -- 3 - - if recipe.craftingTools and recipe.craftingTools[key] then - if summedItem.count > 0 then - summedItem.used = 1 - summedItem.need = 0 - need = 0 - elseif not summedItem.recipe then - summedItem.need = 1 - need = 1 - else - need = 1 - end - else - summedItem.count = summedItem.count - used - summedItem.used = summedItem.used + used - end - - if need > 0 then - if not summedItem.recipe then - craftable = math.min(craftable, math.floor(used / iqty)) - summedItem.need = summedItem.need + need - else - local c = sumItems(summedItem.recipe, need) -- 4 - craftable = math.min(craftable, math.floor((used + c) / iqty)) - summedItem.craftable = summedItem.craftable + c - end - end - end - if craftable > 0 then - craftable = Craft.craftRecipe(recipe, craftable * recipe.count, inventoryAdapter) / recipe.count - clearGrid() - end - - return craftable * recipe.count - end - - local count = sumItems(inRecipe, inCount) - - if count < inCount then - for _,ingredient in pairs(summed) do - if ingredient.need > 0 then - local item = addCraftingRequest(ingredient, craftList, ingredient.need) - if Craft.findRecipe(item) then - item.status = string.format('%s missing', itemDB:getName(ingredient)) - item.statusCode = STATUS_WARNING - else - item.status = '(no recipe)' - item.statusCode = STATUS_ERROR - end - end - end - end - return count -end - -local function craft(recipe, items, item, craftList) - item.status = nil - item.statusCode = nil - item.crafted = 0 - - if craftingPaused or not canCraft then - return - end - - if not clearGrid() then - item.status = 'Grid obstructed' - item.statusCode = STATUS_ERROR - return - end - - if item.forceCrafting then - item.crafted = forceCraftItem(recipe, items, item, craftList, item.count) - else - item.crafted = craftItem(recipe, items, item, craftList, item.count) - end -end - -local function craftItems(craftList, allItems) - -- turtle crafting - if canCraft then - for _,key in pairs(Util.keys(craftList)) do - local item = craftList[key] - local recipe = Craft.recipes[key] - if recipe then - craft(recipe, allItems, item, craftList) - allItems = listItems() -- refresh counts - if not allItems then - break - end - elseif not controllerAdapter then - item.status = '(no recipe)' - item.statusCode = STATUS_ERROR - end - end - end - - -- redstone control - for _,item in pairs(craftList) do - if item.rsControl then - item.status = '(activated)' - item.statusCode = STATUS_INFO - end - end - - -- controller - if controllerAdapter then - for key,item in pairs(craftList) do - if (not canCraft or not Craft.recipes[key]) and not item.rsControl then - if controllerAdapter:isCrafting(item) then - item.status = '(crafting)' - item.statusCode = STATUS_INFO - elseif not controllerAdapter:isCPUAvailable() then - item.status = '(waiting)' - item.statusCode = STATUS_WARNING - else - local count = item.count - item.crafted = 0 - while count >= 1 do -- try to request smaller quantities until successful - local s = pcall(function() - item.status = '(no recipe)' - item.statusCode = STATUS_ERROR - if not controllerAdapter:craft(item, count) then - item.status = '(missing ingredients)' - item.statusCode = STATUS_WARNING - error('failed') - end - item.status = '(crafting)' - item.statusCode = STATUS_INFO - item.crafted = count - end) - if s then - break -- successfully requested crafting - end - count = math.floor(count / 2) - end - end - end - end - end - - if not controllerAdapter and not canCraft then - for _,item in pairs(craftList) do - if not item.rsControl then - item.status = 'Invalid setup' - item.statusCode = STATUS_INFO - end - end - end -end - -local function queue(fn) - while craftingPaused do - os.sleep(1) - end - fn() -end - -local function eject(item, qty) - if _G.turtle then - local s, m = pcall(function() - inventoryAdapter:provide(item, qty) - _G.turtle.emptyInventory() - end) - if not s and m then - debug(m) - 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 - - jobList = UI.Page { - parent = mon, - grid = UI.Grid { - sortColumn = 'displayName', - backgroundFocusColor = colors.black, - columns = { - { heading = 'Qty', key = 'count', width = 6 }, - { heading = 'Crafting', key = 'displayName', width = mon.width / 2 - 10 }, - { heading = 'Status', key = 'status', width = mon.width - 10 }, - }, - }, - } - - function jobList:showError(msg) - self.grid:clear() - self.grid:centeredWrite(math.ceil(self.grid.height / 2), msg) - self:sync() - end - - function jobList:updateList(craftList) - self.grid:setValues(craftList) - self.grid:update() - self:draw() - self:sync() - end - - function jobList.grid: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_INFO then - return colors.lime - end - return UI.Grid:getRowTextColor(row, selected) - end - - jobList:enable() - jobList:draw() - jobList:sync() -end - -local function getAutocraftItems() - local craftList = { } - - for _,res in pairs(resources) do - if res.auto then - res = Util.shallowCopy(res) - res.count = 256 -- this could be higher to increase autocrafting speed - local key = uniqueKey(res) - craftList[key] = res - end - end - return craftList -end - -local function getItemWithQty(items, res, ignoreDamage, ignoreNbtHash) - local item = getItem(items, res, ignoreDamage, ignoreNbtHash) - - if item and (ignoreDamage or ignoreNbtHash) then - local count = 0 - - for _,v in pairs(items) do - if item.name == v.name and - (ignoreDamage or item.damage == v.damage) and - (ignoreNbtHash or item.nbtHash == v.nbtHash) then - count = count + v.count - end - end - item.count = count - end - - return item -end - -local function watchResources(items) - local craftList = { } - local outputs = { } - - for _,res in pairs(resources) do - local item = getItemWithQty(items, res, res.ignoreDamage, res.ignoreNbtHash) - if not item then - item = { - damage = res.damage, - nbtHash = res.nbtHash, - name = res.name, - displayName = itemDB:getName(res), - count = 0 - } - end - - if res.limit and item.count > res.limit then - inventoryAdapter:provide( - { name = item.name, damage = item.damage, nbtHash = item.nbtHash }, - item.count - res.limit, - nil, - config.trashDirection) - - elseif res.low and item.count < res.low then - if res.ignoreDamage then - item.damage = 0 - end - local key = uniqueKey(res) - - craftList[key] = { - damage = item.damage, - nbtHash = item.nbtHash, - count = res.low - item.count, - name = item.name, - displayName = item.displayName, - status = '', - rsControl = res.rsControl, - } - end - - if res.rsControl and res.rsDevice and res.rsSide then - local enable = item.count < res.low - if not outputs[res.rsDevice] then - outputs[res.rsDevice] = { } - end - outputs[res.rsDevice][res.rsSide] = outputs[res.rsDevice][res.rsSide] or enable - end - end - - for rsDevice, sides in pairs(outputs) do - for side, enable in pairs(sides) do - pcall(function() - device[rsDevice].setOutput(side, enable) - end) - end - end - - return craftList -end - -local function emptyFeederChest() -_G._p2 =feederAdapter - if feederAdapter then - local list = feederAdapter.list() -- raw list ! - for k,v in pairs(list) do - feederAdapter:extract(k, v.count, 1) - inventoryAdapter:insert(1, v.count, nil, v) - end - end -end - local function loadResources() - resources = Util.readTable(RESOURCE_FILE) or { } + local resources = Util.readTable(Lora.RESOURCE_FILE) or { } for k,v in pairs(resources) do Util.merge(v, itemDB:splitKey(k)) end + + return resources end -local function saveResources() - local t = { } - - for k,v in pairs(resources) do - v = Util.shallowCopy(v) - local keys = Util.transpose({ 'auto', 'low', 'limit', - 'ignoreDamage', 'ignoreNbtHash', - 'rsControl', 'rsDevice', 'rsSide' }) - - for _,key in pairs(Util.keys(v)) do - if not keys[key] then - v[key] = nil - end - end - if not Util.empty(v) then - t[k] = v - end - end - - Util.writeTable(RESOURCE_FILE, t) -end - -local machinesPage = UI.Page { - titleBar = UI.TitleBar { - previousPage = true, - title = 'Machines', - }, - grid = UI.ScrollingGrid { - y = 2, ey = -2, - values = config.remoteDefaults, - columns = { - { heading = 'Name', key = 'displayName' }, - { heading = 'Priority', key = 'priority', width = 5 }, - { heading = 'Type', key = 'mtype', width = 5 }, - }, - sortColumn = 'name', - }, - detail = UI.SlideOut { - backgroundColor = colors.cyan, - form = UI.Form { - x = 1, y = 2, ex = -1, ey = -2, - [7] = UI.Text { - x = 12, y = 1, - width = 20, - value = 'test', - }, - [1] = UI.TextEntry { - formLabel = 'Name', formKey = 'displayName', help = '...', - limit = 64, - }, - [2] = UI.Chooser { - width = 15, - formLabel = 'Type', formKey = 'mtype', - nochoice = 'Storage', - choices = { - { name = 'Storage', value = 'storage' }, - { name = 'Trashcan', value = 'trashcan' }, - { name = 'Input chest', value = 'input' }, - { name = 'Ignore', value = 'ignore' }, - }, - help = 'Check if machine is empty before crafting' - }, - [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 = 'Priority', formKey = 'priority', help = '...', - limit = 4, - }, - [5] = UI.TextEntry { - formLabel = 'Max Craft', formKey = 'maxCount', help = '...', - limit = 4, - }, - [6] = UI.TextEntry { - formLabel = 'Lock to', formKey = 'lockWith', help = '...', - limit = 64, - }, - }, - statusBar = UI.StatusBar(), - }, - statusBar = UI.StatusBar { - values = 'Select Machine', - }, - accelerators = { - h = 'toggle_hidden', - } +local context = { + config = config, + inventoryAdapter = inventoryAdapter, + resources = loadResources(), + userRecipes = Util.readTable(Lora.RECIPES_FILE) or { }, } -function machinesPage:enable() - self.grid:update() - UI.Page.enable(self) -end +Lora:init(context) -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 +local programDir = fs.getDir(shell.getRunningProgram()) +local pluginDir = fs.combine(programDir, 'plugins') -function machinesPage.grid:getDisplayValues(row) - row = Util.shallowCopy(row) - row.displayName = row.displayName or row.name - return row -end - -function machinesPage.grid:getRowTextColor(row, selected) - if row.mtype == 'ignore' then - return colors.lightGray - 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.form[7].value = event.selected.name -debug(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 or nil - self.detail.form.values.ignore = self.detail.form.values.ignore == true or nil - self.detail.form.values.priority = tonumber(self.detail.form.values.priority) - self.detail.form.values.maxCount = tonumber(self.detail.form.values.maxCount) - if #self.detail.form.values.displayName == 0 then - self.detail.form.values.displayName = nil - end - if #self.detail.form.values.lockWith == 0 then - self.detail.form.values.lockWith = nil - end - Config.update('inventoryManager', config) - self.detail:hide() - self.grid:update() - - elseif event.type == 'form_cancel' then - self.detail:hide() - - else - UI.Page.eventHandler(self, event) - end - return true -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.TextEntry { - width = 7, - formLabel = 'Max', formKey = 'limit', help = 'Eject if above max' - }, - [3] = UI.Chooser { - width = 7, - formLabel = 'Autocraft', formKey = 'auto', - nochoice = 'No', - choices = { - { name = 'Yes', value = true }, - { name = 'No', value = false }, - }, - help = 'Craft until out of ingredients' - }, - [4] = 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' - }, - [5] = UI.Chooser { - width = 7, - formLabel = 'Ignore NBT', formKey = 'ignoreNbtHash', - nochoice = 'No', - choices = { - { name = 'Yes', value = true }, - { name = 'No', value = false }, - }, - help = 'Ignore NBT of item' - }, ---[[ - [6] = UI.Button { - x = 2, y = -2, width = 10, - formLabel = 'Redstone', - event = 'show_rs', - text = 'Configure', - }, -]] - infoButton = UI.Button { - x = 2, y = -2, - event = 'show_info', - text = 'Info', - }, - }, - rsControl = UI.SlideOut { - backgroundColor = colors.cyan, - titleBar = UI.TitleBar { - title = "Redstone Control", - }, - form = UI.Form { - y = 2, - [1] = UI.Chooser { - width = 7, - formLabel = 'RS Control', formKey = 'rsControl', - nochoice = 'No', - choices = { - { name = 'Yes', value = true }, - { name = 'No', value = false }, - }, - help = 'Control via redstone' - }, - [2] = UI.Chooser { - width = 25, - formLabel = 'RS Device', formKey = 'rsDevice', - --choices = devices, - help = 'Redstone Device' - }, - [3] = UI.Chooser { - width = 10, - formLabel = 'RS Side', formKey = 'rsSide', - --nochoice = 'No', - choices = { - { name = 'up', value = 'up' }, - { name = 'down', value = 'down' }, - { name = 'east', value = 'east' }, - { name = 'north', value = 'north' }, - { name = 'west', value = 'west' }, - { name = 'south', value = 'south' }, - }, - help = 'Output side' - }, - }, - }, - info = UI.SlideOut { - titleBar = UI.TitleBar { - title = "Information", - }, - textArea = UI.TextArea { - x = 2, ex = -2, y = 3, ey = -4, - backgroundColor = colors.black, - }, - cancel = UI.Button { - ex = -2, y = -2, width = 6, - text = 'Okay', - event = 'hide_info', - }, - }, - statusBar = UI.StatusBar { } -} - -function itemPage:enable(item) - self.item = item - - self.form:setValues(item) - self.titleBar.title = item.displayName or item.name - - UI.Page.enable(self) - self:focusFirst() -end - -function itemPage.rsControl:enable() - local devices = self.form[1].choices - Util.clear(devices) - for _,dev in pairs(device) do - if dev.setOutput then - table.insert(devices, { name = dev.name, value = dev.name }) - end - end - - if Util.size(devices) == 0 then - table.insert(devices, { name = 'None found', values = '' }) - end - - UI.SlideOut.enable(self) -end - -function itemPage.rsControl:eventHandler(event) - if event.type == 'form_cancel' then - self:hide() - elseif event.type == 'form_complete' then - self:hide() - 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 == 'show_rs' then - self.rsControl:show() - - elseif event.type == 'show_info' then - local value = - string.format('%s%s%s\n%s\n', - Ansi.orange, self.item.displayName, Ansi.reset, - self.item.name) - - if self.item.nbtHash then - value = value .. self.item.nbtHash .. '\n' - end - - value = value .. string.format('\n%sDamage:%s %s', - Ansi.yellow, Ansi.reset, self.item.damage) - - if self.item.maxDamage and self.item.maxDamage > 0 then - value = value .. string.format(' (max: %s)', self.item.maxDamage) - end - - if self.item.maxCount then - value = value .. string.format('\n%sStack Size: %s%s', - Ansi.yellow, Ansi.reset, self.item.maxCount) - end - - self.info.textArea.value = value - self.info:show() - - elseif event.type == 'hide_info' then - self.info:hide() - - 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 originalKey = uniqueKey(self.item) - - local filtered = Util.shallowCopy(values) - filtered.low = tonumber(filtered.low) - filtered.limit = tonumber(filtered.limit) - - if filtered.auto ~= true then - filtered.auto = nil - end - - if filtered.rsControl ~= true then - filtered.rsControl = nil - filtered.rsSide = nil - filtered.rsDevice = nil - end - - if filtered.ignoreDamage == true then - filtered.damage = 0 - else - filtered.ignoreDamage = nil - end - - if filtered.ignoreNbtHash == true then - filtered.nbtHash = nil - else - filtered.ignoreNbtHash = nil - end - resources[originalKey] = nil - resources[uniqueKey(filtered)] = filtered - - filtered.count = nil - saveResources() - - UI:setPreviousPage() - - else - return UI.Page.eventHandler(self, event) - end - return true -end - -local listingPage = UI.Page { - menuBar = UI.MenuBar { - buttons = { - { text = 'Learn', event = 'learn' }, - { text = 'Forget', event = 'forget' }, - { text = 'Craft', event = 'craft' }, - { text = 'Refresh', event = 'refresh', x = -9 }, - }, - }, - grid = UI.Grid { - y = 2, ey = -2, - columns = { - { heading = 'Name', key = 'displayName' }, - { heading = 'Qty', key = 'count' , width = 4 }, - { heading = 'Min', key = 'low' , width = 4 }, - { heading = 'Max', key = 'limit' , width = 4 }, - }, - sortColumn = 'displayName', - }, - statusBar = UI.StatusBar { - filter = UI.TextEntry { - x = 1, ex = -4, - limit = 50, - shadowText = 'filter', - shadowTextColor = colors.gray, - backgroundColor = colors.cyan, - backgroundFocusColor = colors.cyan, - }, - display = UI.Button { - x = -3, - event = 'toggle_display', - value = 0, - text = 'A', - }, - }, - notification = UI.Notification(), - accelerators = { - r = 'refresh', - q = 'quit', - grid_select_right = 'craft', - [ 'control-e' ] = 'eject', - [ 'control-s' ] = 'eject_stack', - [ 'control-m' ] = 'machines', - }, - displayMode = 0, -} - -function listingPage.statusBar:draw() - return UI.Window.draw(self) -end - -function listingPage.grid:getRowTextColor(row, selected) - if row.is_craftable then - return colors.yellow - end - if canCraft and 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 - if row.limit then - row.limit = Util.toBytes(row.limit) - end - return row -end - -function listingPage:eventHandler(event) - if event.type == 'quit' then - UI:exitPullEvents() - - elseif event.type == 'eject' then - local item = self.grid:getSelected() - if item then - queue(function() eject(item, 1) end) - end - - elseif event.type == 'eject_stack' then - local item = self.grid:getSelected() - if item then - queue(function() eject(item, itemDB:getMaxCount(item)) end) - end - - elseif event.type == 'machines' then - UI:setPage('machines') - - 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 == 'toggle_display' then - local values = { - [0] = 'A', - [1] = 'I', - [2] = 'C', - } - - event.button.value = (event.button.value + 1) % 3 - self.displayMode = event.button.value - event.button.text = values[event.button.value] - event.button:draw() - self:applyFilter() - self.grid:draw() - - elseif event.type == 'learn' then - if canLearn then - UI:setPage('learn') - else - self.notification:error('Missing a crafting chest or workbench\nCheck configuration') - end - - elseif event.type == 'craft' or event.type == 'grid_select_right' then - local item = self.grid:getSelected() - if Craft.findRecipe(item) or true then -- or item.is_craftable then - UI:setPage('craft', self.grid:getSelected()) - else - self.notification:error('No recipe defined') - end - - elseif event.type == 'forget' then - local item = self.grid:getSelected() - if item then - local key = uniqueKey(item) - - if userRecipes[key] then - userRecipes[key] = nil - Util.writeTable(RECIPES_FILE, userRecipes) - Craft.loadRecipes() - end - - if resources[key] then - resources[key] = nil - saveResources() - end - - self.notification:info('Forgot: ' .. item.name) - 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 = listItems() - mergeResources(self.allItems) - self:applyFilter() -end - -function listingPage:applyFilter() - local t = filterItems(self.allItems, self.filter, self.displayMode) - self.grid:setValues(t) -end - -local function getTurtleInventory() - local list = { } - for i = 1,16 do - list[i] = introspectionModule.getInventory().getItemMeta(i) - end - return list -end - -local function learnRecipe(page) - local ingredients = getTurtleInventory() - if ingredients then - turtle.select(1) - if canLearn and turtle.craft() then - local results = getTurtleInventory() - if results and results[1] then - 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[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 = 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] = uniqueKey(ingredient) - end - - userRecipes[key] = newRecipe - - Util.writeTable(RECIPES_FILE, 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() - - eject(recipe, recipe.count) - return true - end - else - listingPage.notification:error('Failed to craft', 3) - end - else - listingPage.notification:error('No recipe defined', 3) +for _, file in pairs(fs.list(pluginDir)) do + local s, m = Util.run(_ENV, fs.combine(pluginDir, file)) + if not s and m then + error(m or 'Unknown error') 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' - } -} +table.sort(Lora.tasks, function(a, b) + return a.priority < b.priority +end) -function learnPage:enable() - craftingPaused = true - self:focusFirst() - UI.Dialog.enable(self) -end +Lora:clearGrid() -function learnPage:disable() - craftingPaused = false - 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 - -local craftPage = UI.Page { - titleBar = UI.TitleBar { }, - wizard = UI.Wizard { - y = 2, ey = -2, - pages = { - quantity = UI.Window { - 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.Window { - 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.wizard.pages.quantity.eject.value = true - UI.Page.enable(self) -end - -function craftPage.wizard.pages.resources.grid:getDisplayValues(row) - local function dv(v) - if v == 0 then - return '' - end - return 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) -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) -end - -function craftPage.wizard.pages.resources:enable() - local items = 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.Window.enable(self) -end - -function craftPage:eventHandler(event) - if event.type == 'cancel' then - UI:setPreviousPage() - - elseif event.type == 'accept' then - local key = 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 - demandCrafting[key].forceCrafting = true - demandCrafting[key].eject = self.wizard.pages.quantity.eject.value == true - UI:setPreviousPage() - else - return UI.Page.eventHandler(self, event) - end - return true -end - -loadResources() -if canCraft then - clearGrid() -end - -UI:setPages({ - listing = listingPage, - item = itemPage, - learn = learnPage, - craft = craftPage, - machines = machinesPage, -}) - -jobMonitor() -UI:setPage(listingPage) -listingPage:setFocus(listingPage.statusBar.filter) +local page = UI:getPage('listing') +UI:setPage(page) +page:setFocus(page.statusBar.filter) Event.onInterval(5, function() + if not Lora:isCraftingPaused() then + Lora:resetCraftingStatus() + context.inventoryAdapter:refresh() - emptyFeederChest() - - if not craftingPaused then - local items = listItems() - if not items or Util.size(items) == 0 then - jobList:showError('No items in system') - else - local demandCrafted - if Util.size(demandCrafting) > 0 then - items = listItems() - if items then - demandCrafted = Util.shallowCopy(demandCrafting) - craftItems(demandCrafted, items) - end - end - - items = listItems() - local craftList - if items then - craftList = watchResources(items) - craftItems(craftList, items) - end - - if demandCrafted and craftList then - for k,v in pairs(demandCrafted) do - craftList[k] = v - end - end - - for _,key in pairs(Util.keys(demandCrafting)) do - local item = demandCrafting[key] - if item.crafted then - item.count = math.max(0, item.count - item.crafted) - if item.count <= 0 then - demandCrafting[key] = nil - item.statusCode = 'success' - if item.eject then - eject(item, item.ocount) - end - end - end - end - - jobList:updateList(craftList) - - craftList = getAutocraftItems(items) -- autocrafted items don't show on job monitor - craftItems(craftList, items) + for _, task in ipairs(Lora.tasks) do + task:cycle(context) end end end) UI:pullEvents() -jobList.parent:reset() diff --git a/inventoryManager/plugins/autocraftTask.lua b/inventoryManager/plugins/autocraftTask.lua new file mode 100644 index 0000000..2d089d9 --- /dev/null +++ b/inventoryManager/plugins/autocraftTask.lua @@ -0,0 +1,24 @@ +local Lora = require('lora/lora') +local Util = require('util') + +local Autocraft = { + priority = 100, +} + +function Autocraft:cycle(context) + local list = { } + + for _,res in pairs(context.resources) do + if res.auto then + res = Util.shallowCopy(res) + res.count = 256 + list[Lora:uniqueKey(res)] = res + end + end + + if not Util.empty(list) then + Lora:craftItems(list) + end +end + +Lora:registerTask(Autocraft) diff --git a/inventoryManager/plugins/craft.lua b/inventoryManager/plugins/craft.lua new file mode 100644 index 0000000..e7b4f39 --- /dev/null +++ b/inventoryManager/plugins/craft.lua @@ -0,0 +1,167 @@ +local Craft = require('turtle.craft') +local itemDB = require('itemDB') +local Lora = require('lora/lora') +local UI = require('ui') +local Util = require('util') + +local colors = _G.colors + +local demandCrafting = { } + +local craftPage = UI.Page { + titleBar = UI.TitleBar { }, + wizard = UI.Wizard { + y = 2, ey = -2, + pages = { + quantity = UI.Window { + 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.Window { + 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.wizard.pages.quantity.eject.value = true + UI.Page.enable(self) +end + +function craftPage.wizard.pages.resources.grid:getDisplayValues(row) + local function dv(v) + if v == 0 then + return '' + end + return 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) +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) +end + +function craftPage.wizard.pages.resources:enable() + local items = Lora: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.Window.enable(self) +end + +function craftPage:eventHandler(event) + if event.type == 'cancel' then + UI:setPreviousPage() + + elseif event.type == 'accept' then + local key = Lora: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 + demandCrafting[key].forceCrafting = true + demandCrafting[key].eject = self.wizard.pages.quantity.eject.value == true + UI:setPreviousPage() + else + return UI.Page.eventHandler(self, event) + end + return true +end + +local demandCraftingTask = { + priority = 20, +} + +function demandCraftingTask:cycle() + if Util.size(demandCrafting) > 0 then + local demandCrafted = Util.shallowCopy(demandCrafting) + Lora:craftItems(demandCrafted) + + for _, item in pairs(demandCrafting) do + if item.crafted then + item.count = math.max(0, item.count - item.crafted) + if item.count <= 0 then + item.statusCode = 'success' + end + end + end + + Lora:updateCraftingStatus(demandCrafted) + + for _,key in pairs(Util.keys(demandCrafting)) do + local item = demandCrafting[key] + if item.crafted and item.count <= 0 then + demandCrafting[key] = nil + if item.eject then + Lora:eject(item, item.ocount) + end + end + end + end +end + +UI:addPage('craft', craftPage) +Lora:registerTask(demandCraftingTask) diff --git a/inventoryManager/plugins/inputChestTask.lua b/inventoryManager/plugins/inputChestTask.lua new file mode 100644 index 0000000..516ea81 --- /dev/null +++ b/inventoryManager/plugins/inputChestTask.lua @@ -0,0 +1,37 @@ +local InventoryAdapter = require('inventoryAdapter') +local Lora = require('lora/lora') + +local device = _G.device +local modem = device.wired_modem + +local InputChest = { + priority = 1, + adapters = { }, +} + +function InputChest:init(context) + for k,v in pairs(context.config.remoteDefaults) do + if v.mtype == 'input' then + local adapter = InventoryAdapter.wrap({ side = k, direction = modem.getNameLocal() }) + if adapter then + table.insert(self.adapters, adapter) + end + end + end +end + +-- TODO: clear grid + +function InputChest:cycle(context) + for _, adapter in pairs(self.adapters) do + local list = adapter.list() -- raw list ! + for k,v in pairs(list) do + adapter:extract(k, v.count, 1) + context.inventoryAdapter:insert(1, v.count, nil, v) + end + end +end + +InputChest:init(Lora:getContext()) + +Lora:registerTask(InputChest) diff --git a/inventoryManager/plugins/item.lua b/inventoryManager/plugins/item.lua new file mode 100644 index 0000000..e6c8b0c --- /dev/null +++ b/inventoryManager/plugins/item.lua @@ -0,0 +1,246 @@ +local Ansi = require('ansi') +local Lora = require('lora/lora') +local UI = require('ui') +local Util = require('util') + +local colors = _G.colors +local device = _G.device + +local context = Lora:getContext() + +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.TextEntry { + width = 7, + formLabel = 'Max', formKey = 'limit', help = 'Eject if above max' + }, + [3] = UI.Chooser { + width = 7, + formLabel = 'Autocraft', formKey = 'auto', + nochoice = 'No', + choices = { + { name = 'Yes', value = true }, + { name = 'No', value = false }, + }, + help = 'Craft until out of ingredients' + }, + [4] = 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' + }, + [5] = UI.Chooser { + width = 7, + formLabel = 'Ignore NBT', formKey = 'ignoreNbtHash', + nochoice = 'No', + choices = { + { name = 'Yes', value = true }, + { name = 'No', value = false }, + }, + help = 'Ignore NBT of item' + }, +--[[ + [6] = UI.Button { + x = 2, y = -2, width = 10, + formLabel = 'Redstone', + event = 'show_rs', + text = 'Configure', + }, +]] + infoButton = UI.Button { + x = 2, y = -2, + event = 'show_info', + text = 'Info', + }, + }, + rsControl = UI.SlideOut { + backgroundColor = colors.cyan, + titleBar = UI.TitleBar { + title = "Redstone Control", + }, + form = UI.Form { + y = 2, + [1] = UI.Chooser { + width = 7, + formLabel = 'RS Control', formKey = 'rsControl', + nochoice = 'No', + choices = { + { name = 'Yes', value = true }, + { name = 'No', value = false }, + }, + help = 'Control via redstone' + }, + [2] = UI.Chooser { + width = 25, + formLabel = 'RS Device', formKey = 'rsDevice', + --choices = devices, + help = 'Redstone Device' + }, + [3] = UI.Chooser { + width = 10, + formLabel = 'RS Side', formKey = 'rsSide', + --nochoice = 'No', + choices = { + { name = 'up', value = 'up' }, + { name = 'down', value = 'down' }, + { name = 'east', value = 'east' }, + { name = 'north', value = 'north' }, + { name = 'west', value = 'west' }, + { name = 'south', value = 'south' }, + }, + help = 'Output side' + }, + }, + }, + info = UI.SlideOut { + titleBar = UI.TitleBar { + title = "Information", + }, + textArea = UI.TextArea { + x = 2, ex = -2, y = 3, ey = -4, + backgroundColor = colors.black, + }, + cancel = UI.Button { + ex = -2, y = -2, width = 6, + text = 'Okay', + event = 'hide_info', + }, + }, + statusBar = UI.StatusBar { } +} + +function itemPage:enable(item) + self.item = item + + self.form:setValues(item) + self.titleBar.title = item.displayName or item.name + + UI.Page.enable(self) + self:focusFirst() +end + +function itemPage.rsControl:enable() + local devices = self.form[1].choices + Util.clear(devices) + for _,dev in pairs(device) do + if dev.setOutput then + table.insert(devices, { name = dev.name, value = dev.name }) + end + end + + if Util.size(devices) == 0 then + table.insert(devices, { name = 'None found', values = '' }) + end + + UI.SlideOut.enable(self) +end + +function itemPage.rsControl:eventHandler(event) + if event.type == 'form_cancel' then + self:hide() + elseif event.type == 'form_complete' then + self:hide() + 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 == 'show_rs' then + self.rsControl:show() + + elseif event.type == 'show_info' then + local value = + string.format('%s%s%s\n%s\n', + Ansi.orange, self.item.displayName, Ansi.reset, + self.item.name) + + if self.item.nbtHash then + value = value .. self.item.nbtHash .. '\n' + end + + value = value .. string.format('\n%sDamage:%s %s', + Ansi.yellow, Ansi.reset, self.item.damage) + + if self.item.maxDamage and self.item.maxDamage > 0 then + value = value .. string.format(' (max: %s)', self.item.maxDamage) + end + + if self.item.maxCount then + value = value .. string.format('\n%sStack Size: %s%s', + Ansi.yellow, Ansi.reset, self.item.maxCount) + end + + self.info.textArea.value = value + self.info:show() + + elseif event.type == 'hide_info' then + self.info:hide() + + 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 originalKey = Lora:uniqueKey(self.item) + + local filtered = Util.shallowCopy(values) + filtered.low = tonumber(filtered.low) + filtered.limit = tonumber(filtered.limit) + + if filtered.auto ~= true then + filtered.auto = nil + end + + if filtered.rsControl ~= true then + filtered.rsControl = nil + filtered.rsSide = nil + filtered.rsDevice = nil + end + + if filtered.ignoreDamage == true then + filtered.damage = 0 + else + filtered.ignoreDamage = nil + end + + if filtered.ignoreNbtHash == true then + filtered.nbtHash = nil + else + filtered.ignoreNbtHash = nil + end + context.resources[originalKey] = nil + context.resources[Lora:uniqueKey(filtered)] = filtered + + filtered.count = nil + Lora:saveResources() + + UI:setPreviousPage() + + else + return UI.Page.eventHandler(self, event) + end + return true +end + +UI:addPage('item', itemPage) diff --git a/inventoryManager/plugins/jobList.lua b/inventoryManager/plugins/jobList.lua new file mode 100644 index 0000000..3b4e7c5 --- /dev/null +++ b/inventoryManager/plugins/jobList.lua @@ -0,0 +1,65 @@ +local Lora = require('lora/lora') +local Peripheral = require('peripheral') +local UI = require('ui') + +local colors = _G.colors + +local context = Lora:getContext() +local mon = Peripheral.lookup(context.config.monitor) or + error('Monitor is not attached') +local display = UI.Device { + device = mon, + textScale = .5, +} + +local jobList = UI.Page { + parent = display, + grid = UI.Grid { + sortColumn = 'displayName', + backgroundFocusColor = colors.black, + columns = { + { heading = 'Qty', key = 'count', width = 6 }, + { heading = 'Crafting', key = 'displayName', width = display.width / 2 - 10 }, + { heading = 'Status', key = 'status', width = display.width - 10 }, + }, + }, +} + +function jobList:showError(msg) + self.grid:clear() + self.grid:centeredWrite(math.ceil(self.grid.height / 2), msg) + self:sync() +end + +function jobList:updateList(craftList) + self.grid:setValues(craftList) + self.grid:update() + self:draw() + self:sync() +end + +function jobList.grid:getRowTextColor(row, selected) + if row.statusCode == Lora.STATUS_ERROR then + return colors.red + elseif row.statusCode == Lora.STATUS_WARNING then + return colors.yellow + elseif row.statusCode == Lora.STATUS_INFO then + return colors.lime + end + return UI.Grid:getRowTextColor(row, selected) +end + +jobList:enable() +jobList:draw() +jobList:sync() + +local JobListTask = { + priority = 80, +} + +function JobListTask:cycle() + jobList:updateList(Lora:getCraftingStatus()) +end + +Lora:registerTask(JobListTask) +context.jobList = jobList diff --git a/inventoryManager/plugins/learn.lua b/inventoryManager/plugins/learn.lua new file mode 100644 index 0000000..139b55f --- /dev/null +++ b/inventoryManager/plugins/learn.lua @@ -0,0 +1,174 @@ +local Craft = require('turtle.craft') +local itemDB = require('itemDB') +local Lora = require('lora/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/plugins/limitTask.lua b/inventoryManager/plugins/limitTask.lua new file mode 100644 index 0000000..3fc86d7 --- /dev/null +++ b/inventoryManager/plugins/limitTask.lua @@ -0,0 +1,36 @@ +local Lora = require('lora/lora') + +local LimitTask = { + priority = 10, +} + +function LimitTask:init(context) + for k,v in pairs(context.config.remoteDefaults) do + if v.mtype == 'trashcan' then + self.trashcan = k + break + end + end +end + +function LimitTask:cycle(context) + if not self.trashcan then + return + end + + for _,res in pairs(context.resources) do + if res.limit then + local item = Lora: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 }, + item.count - res.limit, + nil, + self.trashcan) + end + end + end +end + +LimitTask:init(Lora:getContext()) +Lora:registerTask(LimitTask) diff --git a/inventoryManager/plugins/listing.lua b/inventoryManager/plugins/listing.lua new file mode 100644 index 0000000..3ac7031 --- /dev/null +++ b/inventoryManager/plugins/listing.lua @@ -0,0 +1,250 @@ +local Craft = require('turtle.craft') +local itemDB = require('itemDB') +local Lora = require('lora/lora') +local UI = require('ui') +local Util = require('util') + +local colors = _G.colors +local os = _G.os + +local context = Lora:getContext() + +local function queue(fn) + while Lora:isCraftingPaused() do + os.sleep(1) + end + fn() +end + +local function mergeResources(t) + for _,v in pairs(context.resources) do + local item = Lora: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(Craft.recipes) do + local v = itemDB:splitKey(k) + local item = Lora: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, displayMode) + if filter or displayMode > 0 then + local r = { } + if filter then + filter = filter:lower() + end + for _,v in pairs(t) do + if not filter or string.find(v.lname, filter, 1, true) then + if not displayMode or + displayMode == 0 or + displayMode == 1 and v.count > 0 or + displayMode == 2 and v.has_recipe then + table.insert(r, v) + end + end + end + return r + end + return t +end + +local listingPage = UI.Page { + menuBar = UI.MenuBar { + buttons = { + { text = 'Learn', event = 'learn' }, + { text = 'Forget', event = 'forget' }, + { text = 'Craft', event = 'craft' }, + { text = 'Refresh', event = 'refresh', x = -9 }, + }, + }, + grid = UI.Grid { + y = 2, ey = -2, + columns = { + { heading = 'Name', key = 'displayName' }, + { heading = 'Qty', key = 'count' , width = 4 }, + { heading = 'Min', key = 'low' , width = 4 }, + { heading = 'Max', key = 'limit' , width = 4 }, + }, + sortColumn = 'displayName', + }, + statusBar = UI.StatusBar { + filter = UI.TextEntry { + x = 1, ex = -4, + limit = 50, + shadowText = 'filter', + shadowTextColor = colors.gray, + backgroundColor = colors.cyan, + backgroundFocusColor = colors.cyan, + }, + display = UI.Button { + x = -3, + event = 'toggle_display', + value = 0, + text = 'A', + }, + }, + notification = UI.Notification(), + accelerators = { + r = 'refresh', + q = 'quit', + grid_select_right = 'craft', + [ 'control-e' ] = 'eject', + [ 'control-s' ] = 'eject_stack', + [ 'control-m' ] = 'machines', + }, + displayMode = 0, +} + +function listingPage.statusBar:draw() + return UI.Window.draw(self) +end + +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 + if row.limit then + row.limit = Util.toBytes(row.limit) + end + return row +end + +function listingPage:eventHandler(event) + if event.type == 'quit' then + UI:exitPullEvents() + + elseif event.type == 'eject' then + local item = self.grid:getSelected() + if item then + queue(function() Lora: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) + end + + elseif event.type == 'machines' then + UI:setPage('machines') + + 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 == 'toggle_display' then + local values = { + [0] = 'A', + [1] = 'I', + [2] = 'C', + } + + event.button.value = (event.button.value + 1) % 3 + self.displayMode = event.button.value + event.button.text = values[event.button.value] + event.button:draw() + self:applyFilter() + self.grid:draw() + + elseif event.type == 'learn' then + UI:setPage('learn') + + elseif event.type == 'craft' or event.type == 'grid_select_right' then + local item = self.grid:getSelected() + if Craft.findRecipe(item) or true then -- or item.is_craftable then + UI:setPage('craft', self.grid:getSelected()) + else + self.notification:error('No recipe defined') + end + + elseif event.type == 'forget' then + local item = self.grid:getSelected() + if item then + local key = Lora:uniqueKey(item) + + if context.userRecipes[key] then + context.userRecipes[key] = nil + Util.writeTable(Lora.RECIPES_FILE, context.userRecipes) + Craft.loadRecipes() + end + + if context.resources[key] then + context.resources[key] = nil + Lora:saveResources() + end + + self.notification:info('Forgot: ' .. item.name) + 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 = Lora:listItems() + mergeResources(self.allItems) + self:applyFilter() +end + +function listingPage:applyFilter() + local t = filterItems(self.allItems, self.filter, self.displayMode) + self.grid:setValues(t) +end + +UI:addPage('listing', listingPage) diff --git a/inventoryManager/plugins/machines.lua b/inventoryManager/plugins/machines.lua new file mode 100644 index 0000000..4ded5d8 --- /dev/null +++ b/inventoryManager/plugins/machines.lua @@ -0,0 +1,152 @@ +local Config = require('config') +local Lora = require('lora/lora') +local UI = require('ui') +local Util = require('util') + +local colors = _G.colors + +local context = Lora:getContext() + +local machinesPage = UI.Page { + titleBar = UI.TitleBar { + previousPage = true, + title = 'Machines', + }, + grid = UI.ScrollingGrid { + y = 2, ey = -2, + values = context.config.remoteDefaults, + columns = { + { heading = 'Name', key = 'displayName' }, + { heading = 'Priority', key = 'priority', width = 5 }, + { heading = 'Type', key = 'mtype', width = 5 }, + }, + sortColumn = 'name', + }, + detail = UI.SlideOut { + backgroundColor = colors.cyan, + form = UI.Form { + x = 1, y = 2, ex = -1, ey = -2, + [7] = UI.Text { + x = 12, y = 1, + width = 28, + }, + [1] = UI.TextEntry { + formLabel = 'Name', formKey = 'displayName', help = '...', + limit = 64, + }, + [2] = UI.Chooser { + width = 15, + formLabel = 'Type', formKey = 'mtype', + nochoice = 'Storage', + choices = { + { name = 'Storage', value = 'storage' }, + { name = 'Trashcan', value = 'trashcan' }, + { name = 'Input chest', value = 'input' }, + { name = 'Ignore', value = 'ignore' }, + }, + help = 'Check if machine is empty before crafting' + }, + [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 = 'Priority', formKey = 'priority', help = '...', + limit = 4, + }, + [5] = UI.TextEntry { + formLabel = 'Max Craft', formKey = 'maxCount', help = '...', + limit = 4, + }, + [6] = UI.TextEntry { + formLabel = 'Lock to', formKey = 'lockWith', help = '...', + width = 18, + limit = 64, + }, + [8] = UI.Button { + x = -9, ey = -4, + text = 'Detect', help = '...', + limit = 64, + }, + }, + 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:getDisplayValues(row) + row = Util.shallowCopy(row) + row.displayName = row.displayName or row.name + return row +end + +function machinesPage.grid:getRowTextColor(row, selected) + if row.mtype == 'ignore' then + return colors.lightGray + 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.form[7].value = event.selected.name +debug(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 or nil + self.detail.form.values.ignore = self.detail.form.values.ignore == true or nil + self.detail.form.values.priority = tonumber(self.detail.form.values.priority) + self.detail.form.values.maxCount = tonumber(self.detail.form.values.maxCount) + if #self.detail.form.values.displayName == 0 then + self.detail.form.values.displayName = nil + end + if #self.detail.form.values.lockWith == 0 then + self.detail.form.values.lockWith = nil + end + Config.update('inventoryManager', context.config) + self.detail:hide() + self.grid:update() + + elseif event.type == 'form_cancel' then + self.detail:hide() + + else + UI.Page.eventHandler(self, event) + end + return true +end + +UI:addPage('machines', machinesPage) diff --git a/inventoryManager/plugins/replenishTask.lua b/inventoryManager/plugins/replenishTask.lua new file mode 100644 index 0000000..f06ac0e --- /dev/null +++ b/inventoryManager/plugins/replenishTask.lua @@ -0,0 +1,47 @@ +local itemDB = require('itemDB') +local Lora = require('lora/lora') + +local ReplenishTask = { + priority = 30, +} + +function ReplenishTask:cycle(context) + local craftList = { } + + for _,res in pairs(context.resources) do + if res.low then + local item = Lora:getItemWithQty(res, res.ignoreDamage, res.ignoreNbtHash) + if not item then + item = { + damage = res.damage, + nbtHash = res.nbtHash, + name = res.name, + displayName = itemDB:getName(res), + count = 0 + } + end + + if item.count < res.low then + if res.ignoreDamage then + item.damage = 0 + end + local key = Lora:uniqueKey(res) + + craftList[key] = { + damage = item.damage, + nbtHash = item.nbtHash, + count = res.low - item.count, + name = item.name, + displayName = item.displayName, + status = '', + rsControl = res.rsControl, + } + end + end + end + + Lora:craftItems(craftList) + Lora:updateCraftingStatus(craftList) +end + +Lora:registerTask(ReplenishTask)