From a5c7fde53083c7918e81fbbf2b2942cc5543ab0c Mon Sep 17 00:00:00 2001 From: kepler155c Date: Mon, 16 Oct 2017 16:55:37 -0400 Subject: [PATCH] crafter --- .gitignore | 1 + apis/turtle/craft.lua | 3 +- apps/crafter.lua | 567 ++++++++++++++++++++++++++++++++++++++++++ apps/edit.lua | 12 +- 4 files changed, 575 insertions(+), 8 deletions(-) create mode 100644 apps/crafter.lua diff --git a/.gitignore b/.gitignore index a9004eb..a5e9d77 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /etc/fstab +/etc/recipes2.db diff --git a/apis/turtle/craft.lua b/apis/turtle/craft.lua index 2026f42..b0457d4 100644 --- a/apis/turtle/craft.lua +++ b/apis/turtle/craft.lua @@ -1,6 +1,7 @@ -local itemDB = require('itemDB') local Util = require('util') +local turtle = _G.turtle + local Craft = { } local function clearGrid(inventoryAdapter) diff --git a/apps/crafter.lua b/apps/crafter.lua new file mode 100644 index 0000000..c68a51a --- /dev/null +++ b/apps/crafter.lua @@ -0,0 +1,567 @@ +_G.requireInjector() + +local ChestAdapter = require('chestAdapter18') +local Event = require('event') +local itemDB = require('itemDB') +local Peripheral = require('peripheral') +local UI = require('ui') +local Util = require('util') + +local colors = _G.colors +local device = _G.device +local multishell = _ENV.multishell +local turtle = _G.turtle + +multishell.setTitle(multishell.getCurrent(), 'Crafter') + +repeat until not turtle.forward() +local inventoryAdapter = ChestAdapter({ wrapSide = 'front', direction = 'north' }) + +local RESOURCE_FILE = 'usr/config/resources.db' +local RECIPES_FILE = 'usr/etc/recipes2.db' + +local craftingPaused = false +local recipes = Util.readTable(RECIPES_FILE) or { } +local resources +local machines = { } + +local function getItem(items, inItem, ignoreDamage) + for _,item in pairs(items) do + if item.name == inItem.name then + if ignoreDamage then + return item + elseif item.damage == inItem.damage and item.nbtHash == inItem.nbtHash then + return item + end + end + end +end + +local function splitKey(key) + local t = Util.split(key, '(.-):') + local item = { } + if #t[#t] > 8 then + item.nbtHash = table.remove(t) + end + item.damage = tonumber(table.remove(t)) + item.name = table.concat(t, ':') + return item +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(recipes) do + local v = 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 gotoMachine(machine) + local _, k = Util.find(machines, 'name', machine) + + if not k then + error('Unable to locate machine: ' .. tostring(machine)) + end + + for _ = 1, k - 1 do + turtle.back() + end +end + +local function canCraft(recipe, items, count) + count = math.ceil(count / recipe.count) + + for _,key in pairs(recipe.ingredients) do + local item = getItem(items, splitKey(key)) + if not item then + return 0 + end + count = math.min(item.count, count) + end + + return count +end + +local function craftItem(recipe, items, count) + repeat until not turtle.forward() + count = canCraft(recipe, items, count) + if count == 0 then + return false + end + + for k,v in pairs(recipe.ingredients) do + local item = splitKey(v) + inventoryAdapter:provide(item, count, k) + if turtle.getItemCount(k) ~= count then + return false + end + end + gotoMachine(recipe.machine) + turtle.emptyInventory(turtle.dropDown) +end + +local function craftItems(craftList, items) + for key, item in pairs(craftList) do + local recipe = recipes[key] + if recipe then + craftItem(recipe, items, item.count) + repeat until not turtle.forward() + items = inventoryAdapter:listItems() -- refresh counts + end + end +end + +local function getItemWithQty(items, res, ignoreDamage) + local item = getItem(items, res, ignoreDamage) + + if item and ignoreDamage then + local count = 0 + + for _,v in pairs(items) do + if item.name == v.name and item.nbtHash == v.nbtHash then + if item.maxDamage > 0 or item.damage == v.damage then + count = count + v.count + end + end + end + item.count = count + end + + return item +end + +local function watchResources(items) + local craftList = { } + + for _,res in pairs(resources) do + local item = getItemWithQty(items, res, res.ignoreDamage) + if not item then + item = { + damage = res.damage, + nbtHash = res.nbtHash, + name = res.name, + displayName = itemDB:getName(res), + count = 0 + } + end + + if 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 + end + return craftList +end + +local function loadResources() + resources = Util.readTable(RESOURCE_FILE) or { } + for k,v in pairs(resources) do + Util.merge(v, 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 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 = 'RS Control', formKey = 'rsControl', + nochoice = 'No', + choices = { + { name = 'Yes', value = true }, + { name = 'No', value = false }, + }, + help = 'Control via redstone' + }, + [6] = UI.Chooser { + width = 25, + formLabel = 'RS Device', formKey = 'rsDevice', + --choices = devices, + help = 'Redstone Device' + }, + [7] = 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' + }, + }, + statusBar = UI.StatusBar { } +} + +function itemPage:enable(item) + self.item = item + + self.form:setValues(item) + self.titleBar.title = item.displayName or item.name + + local devices = self.form[6].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.Page.enable(self) + self:focusFirst() +end + +function itemPage:eventHandler(event) + if event.type == 'form_cancel' then + UI:setPreviousPage() + + 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', 'auto', 'low', 'limit', 'damage', + 'nbtHash', + 'rsControl', 'rsDevice', 'rsSide', } + + local filtered = { } + for _,key in pairs(keys) do + filtered[key] = values[key] + end + filtered.low = tonumber(filtered.low) + filtered.limit = tonumber(filtered.limit) + + --filtered.ignoreDamage = filtered.ignoreDamage == true + --filtered.auto = filtered.auto == true + --filtered.rsControl = filtered.rsControl == true + + 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 values.ignoreDamage == true then + filtered.damage = 0 + filtered.ignoreDamage = true + end + + resources[uniqueKey(filtered)] = filtered + 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, height = UI.term.height - 2, + columns = { + { heading = 'Name', key = 'displayName' , width = 22 }, + { heading = 'Qty', key = 'count' , width = 5 }, + { heading = 'Min', key = 'low' , width = 4 }, + { heading = 'Max', key = 'limit' , width = 4 }, + }, + sortColumn = 'displayName', + }, + statusBar = UI.StatusBar { + filterText = UI.Text { + x = 2, + value = 'Filter', + }, + filter = UI.TextEntry { + x = 9, ex = -2, + limit = 50, + 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 + if row.limit then + row.limit = Util.toBytes(row.limit) + end + return row +end + +function listingPage.statusBar:draw() + return UI.Window.draw(self) +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 == 'learn' then + UI:setPage('learn') + + 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 = inventoryAdapter:listItems() + mergeResources(self.allItems) + self:applyFilter() +end + +function listingPage:applyFilter() + local t = filterItems(self.allItems, self.filter) + self.grid:setValues(t) +end + +loadResources() + +UI:setPages({ + listing = listingPage, + item = itemPage, +}) + +UI:setPage(listingPage) +listingPage:setFocus(listingPage.statusBar.filter) + +repeat until not turtle.forward() +clearGrid() + +repeat + local machine = Peripheral.getBySide('bottom') + if machine then + debug(machine.name) + table.insert(machines, machine) + end +until not turtle.back() + +Event.onInterval(30, function() + if not craftingPaused then + repeat until not turtle.forward() + if turtle.getFuelLevel() < 100 then + turtle.select(1) + inventoryAdapter:provide({ name = 'minecraft:coal', damage = 1 }, 16, 1) + turtle.refuel() + end + local items = inventoryAdapter:listItems() + local craftList = watchResources(items) + craftItems(craftList, items) + end +end) + +UI:pullEvents() diff --git a/apps/edit.lua b/apps/edit.lua index 85c63b6..871e79b 100644 --- a/apps/edit.lua +++ b/apps/edit.lua @@ -26,7 +26,7 @@ if fs.exists(sPath) and fs.isDir(sPath) then end if multishell then - multishell.setTitle(multishell.getCurrent(), sPath) + multishell.setTitle(multishell.getCurrent(), fs.getName(sPath)) end local x, y = 1, 1 @@ -1093,12 +1093,6 @@ local __actions = { actions = __actions --- Actual program functionality begins -load(sPath) - -term.setCursorBlink(true) -redraw() - local modifiers = { [ keys.leftCtrl ] = true, [ keys.rightCtrl ] = true, @@ -1222,6 +1216,10 @@ function input:translate(event, code, p1, p2) end end +load(sPath) +term.setCursorBlink(true) +redraw() + while bRunning do local sEvent, param, param2, param3 = os.pullEventRaw() local action