local component = require("component") local robot = require("robot") local sides = require("sides") local recipes = require('recipes') local crafting = component.crafting local inventory = component.inventory_controller RESULT_SLOT = 4 -- robots inventory is 4 wide and crafting uses the top left portion local slot_map = { 1, 2, 3, 5, 6, 7, 9, 10, 11 } ---"item" being pair string id and damage separated by ';' eg. "minecraft:wool;3" ---returns "minecraft:wool", 3 ---@param str string ---@param delimiter string ---@return string|nil ---@return number|nil local function parse_item(str, delimiter) local pos = string.find(str, delimiter) if not pos then return nil, nil end return string.sub(str, 1, pos - 1), tonumber(string.sub(str, pos + 1)) end ---add count to the selected key, or create it if it doesn't exist ---@param dict table ---@param key string ---@param count number local function add(dict, key, count) if dict[key] then dict[key] = dict[key] + count else dict[key] = count end end ---append the array b to the end of array a ---@param a table ---@param b table ---@return table local function append(a, b) for i = 1, #b do a[#a + 1] = b[i] end return a end ---read the facing inventory and return a table of item names and their count or nil if not facing an inventory ---@return table|nil local function read_chest_contents() local contents = {} local size = inventory.getInventorySize(sides.forward) if not size then return nil end for slot = 1, size do local stack = inventory.getStackInSlot(sides.forward, slot) if stack then local name = stack.name .. ";" .. stack.damage add(contents, name, stack.size) end end return contents end ---store the item in the selected slot into the facing inventory ---@return boolean local function store_item() -- check if that item is present in the inventory local item = inventory.getStackInInternalSlot() local size = inventory.getInventorySize(sides.forward) local empty_slot = -1 for slot = 1, size do local stack = inventory.getStackInSlot(sides.forward, slot) if stack then if stack.name == item.name then inventory.dropIntoSlot(sides.forward, slot) -- check if all items have been stored local remaining = inventory.getStackInInternalSlot() if not remaining then print('Stored successfully') return true end end else if empty_slot == -1 then empty_slot = slot end end end -- item not present in inventory if empty_slot ~= -1 then -- store in first empty slot local result, error = inventory.dropIntoSlot(sides.forward, empty_slot) if not result then print('Cannot store item: ' .. error .. "\n") return false else print('Stored successfully') return true end else print('Cannot store item - Inventory full') return false end end ---Search the facing chest for "item" and take "count" items and place them in "slot" ---"item" being pair string id and damage separated by ';' eg. "minecraft:wool;3" ---Returns number of items fetched ---@param item string ---@param output_slot number ---@return boolean local function fetch_item(item, output_slot) local name, damage = parse_item(item, ";") robot.select(output_slot) local size = inventory.getInventorySize(sides.forward) if not size then print('Not facing a chest') return false end for slot = 1, size do local stack = inventory.getStackInSlot(sides.forward, slot) if stack then if stack.name == name and stack.damage == damage then inventory.suckFromSlot(sides.forward, slot, 1) return true end end end print('Missing ' .. item) return false end ---accepts a recipe table containing result and ingredients ---@param recipe table ---@return boolean local function craft_single_recipe(recipe) if inventory.getStackInInternalSlot(RESULT_SLOT) then print('Cannot craft - output slot occupied') return false end for i, ingredient in pairs(recipe.shape) do print('Fetching ' .. ingredient) fetch_item(ingredient, slot_map[i]) end -- select slot for the result robot.select(RESULT_SLOT) -- craft the resulting item local crafted = crafting.craft(1) if not crafted then print('Crafting failed') return false end return store_item() end ---comment ---@param recipe table ---@param available_items table ---@return boolean success ---@return table total_required_items ---@return table recipe_crafting_order array of recipes local function craft_recipe_recursion(recipe, available_items) local total_required_items = {} local total_crafting_order = { recipe } for item, count in pairs(recipe.requires) do local available = available_items[item] if not available then available = 0 end if available >= count then print('I have ' .. count .. ' ' .. item) -- don't have to craft anything add(total_required_items, item, count) -- remove it from tracked available items so if other recipe requires it, it needs to craft it available_items[item] = available_items[item] - count else local required_count = count - available print('Missing ' .. required_count .. ' ' .. item) -- check if I can craft it local sub_recipe = recipes[item] if sub_recipe then print('Trying to craft ' .. item) local number_of_crafting_operations = math.ceil(required_count / sub_recipe.count) local excess = required_count % sub_recipe.count print(' Will make ' .. number_of_crafting_operations .. ' crafting operations') print(' That will produce ' .. excess .. ' items I can use later') local current_crafting_order = {} for _ = 1, number_of_crafting_operations do local success, required_items, crafting_order = craft_recipe_recursion(sub_recipe, available_items) if not success then return false, {}, {} end for k, v in pairs(required_items) do add(total_required_items, k, v) end -- append current_crafting_order = append(current_crafting_order, crafting_order) end -- prepend total_crafting_order = append(current_crafting_order, total_crafting_order) if excess > 0 then add(available_items, item, excess) end else print('Need ' .. required_count .. ' more ' .. item) return false, {}, {} end end end return true, total_required_items, total_crafting_order end ---accepts a recipe table ---@param item string ---@return boolean local function craft_recipe(item) print('Trying to craft ' .. item) local recipe = recipes[item] if not recipe then print('Cannot craft ' .. item) return false end local available_items = read_chest_contents() if not available_items then print('Not facing a chest') return false end local success, required_items, crafting_order = craft_recipe_recursion(recipe, available_items) if not success then print('Failed') return false end print('\nRequires these items:') for k, v in pairs(required_items) do print(k, v) end print('\nCrafting in order:') for i, rec in ipairs(crafting_order) do print(i, rec.result) end -- proceed to crafting the things for i, rec in ipairs(crafting_order) do print('Step #' .. i .. ' - ' .. rec.result) if not craft_single_recipe(rec) then print('Unexpected error') return false end end return true end -- local target = ... -- if not target then -- print("Usage: craft ") -- return -- end -- craft_recipe('minecraft:iron_pickaxe;0') craft_recipe('opencomputers:keyboard;0') -- store_item() -- local contents = read_chest_contents() -- if contents then -- for k,v in pairs(contents) do -- print(k,v) -- end -- else -- print('Not facing a chest') -- end