You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
447 lines
13 KiB
447 lines
13 KiB
local component = require("component")
|
|
local robot = require("robot")
|
|
local sides = require("sides")
|
|
local io = require("io")
|
|
|
|
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
|
|
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
|
|
|
|
---duplicate the table t
|
|
---@param t table
|
|
---@return table
|
|
local function clone(t)
|
|
local t2 = {}
|
|
for k, v in pairs(t) do
|
|
t2[k] = v
|
|
end
|
|
return t2
|
|
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
|
|
|
|
---Ask the user if they want to proceed
|
|
---@return boolean
|
|
local function confirm()
|
|
while true do
|
|
print("Proceed? [Y/n]")
|
|
local ch = io.read()
|
|
if not ch then
|
|
return false -- interrupted
|
|
end
|
|
if ch == 'y' or ch == 'Y' or ch == '' then
|
|
return true
|
|
elseif ch == 'n' then
|
|
return false
|
|
end
|
|
end
|
|
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 iter = inventory.getAllStacks(sides.forward)
|
|
if not iter then
|
|
return nil -- not facing an inventory
|
|
end
|
|
|
|
local stack = iter()
|
|
while stack do
|
|
if stack.name then -- check if the slot has an item in it
|
|
local name = stack.name .. ";" .. stack.damage
|
|
add(contents, name, stack.size)
|
|
end
|
|
stack = iter()
|
|
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 iter = inventory.getAllStacks(sides.forward)
|
|
local slot = 1
|
|
local empty_slot = -1
|
|
local stack = iter()
|
|
while stack do
|
|
if stack.name 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
|
|
stack = iter()
|
|
slot = slot + 1
|
|
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
|
|
---@param count number how many items to take
|
|
---@return boolean
|
|
local function fetch_item(item, output_slot, count)
|
|
if count == nil then
|
|
count = 1
|
|
end
|
|
|
|
local name, damage = parse_item(item, ";")
|
|
robot.select(output_slot)
|
|
local iter = inventory.getAllStacks(sides.forward)
|
|
if not iter then
|
|
print('Not facing a chest')
|
|
return false
|
|
end
|
|
|
|
local stack = iter()
|
|
local slot = 1
|
|
while stack do
|
|
if stack.name then
|
|
if stack.name == name and stack.damage == damage then
|
|
inventory.suckFromSlot(sides.forward, slot, count)
|
|
return true
|
|
end
|
|
end
|
|
stack = iter()
|
|
slot = slot + 1
|
|
end
|
|
|
|
print('Missing ' .. item)
|
|
return false
|
|
end
|
|
|
|
---clear the 3x3 grid of any leftover items
|
|
local function clear_crafting_space()
|
|
for _, slot in ipairs(SLOT_MAP) do
|
|
if inventory.getStackInInternalSlot(slot) then
|
|
robot.select(slot)
|
|
store_item()
|
|
end
|
|
end
|
|
if inventory.getStackInInternalSlot(RESULT_SLOT) then
|
|
robot.select(RESULT_SLOT)
|
|
store_item()
|
|
end
|
|
end
|
|
|
|
---accepts a recipe table containing result and ingredients
|
|
---@param recipe table
|
|
---@param count number how many items to craft
|
|
---@return boolean
|
|
local function craft_single_recipe(recipe, count)
|
|
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)
|
|
if not fetch_item(ingredient, SLOT_MAP[i], count) then
|
|
print('Failed to fetch ' .. ingredient)
|
|
confirm()
|
|
end
|
|
end
|
|
|
|
-- select slot for the result
|
|
robot.select(RESULT_SLOT)
|
|
|
|
-- craft the resulting item
|
|
local crafted = crafting.craft()
|
|
if not crafted then
|
|
print('Crafting failed')
|
|
return false
|
|
end
|
|
|
|
return store_item()
|
|
end
|
|
|
|
---comment
|
|
---@param recipe table
|
|
---@param available_items table available initially
|
|
---@param currently_available table available from previous crafring
|
|
---@return table total_required_items
|
|
---@return table recipe_crafting_order array of recipes
|
|
local function craft_recipe_recursion(recipe, available_items, currently_available)
|
|
local total_required_items = {}
|
|
local total_crafting_order = {}
|
|
|
|
-- print('Crafting ' .. recipe.result)
|
|
|
|
-- go through all the required ingredients
|
|
for item, count in pairs(recipe.requires) do
|
|
-- check if some crafting result provided this item
|
|
local available = currently_available[item]
|
|
if not available then
|
|
available = 0
|
|
end
|
|
-- print('Requires ' .. count .. 'x ' .. item )
|
|
-- print('I have ' .. available .. ' ' .. item .. ' from previous crafting')
|
|
|
|
if available >= count then
|
|
-- remove it from tracked available items so if other recipe requires it, it needs to craft it
|
|
currently_available[item] = currently_available[item] - count
|
|
else
|
|
-- remove the items it has from tracked available items so if other recipe requires it, it needs to craft it
|
|
currently_available[item] = 0
|
|
|
|
local required_count = count - available
|
|
|
|
-- check if there is enough in the main available storage
|
|
available = available_items[item]
|
|
if not available then
|
|
available = 0
|
|
end
|
|
if available >= required_count then
|
|
-- print('I have ' .. available .. ' ' .. item .. ' available')
|
|
-- add the required amount to required items
|
|
add(total_required_items, item, required_count)
|
|
-- remove it from tracked available items so if other recipe requires it, it needs to craft it
|
|
available_items[item] = available_items[item] - required_count
|
|
else
|
|
if available > 0 then
|
|
-- add what it has to required items
|
|
add(total_required_items, item, available)
|
|
-- remove it from tracked available items so if other recipe requires it, it needs to craft it
|
|
available_items[item] = 0
|
|
end
|
|
|
|
-- update count
|
|
required_count = required_count - available
|
|
|
|
-- print('Have ' .. available .. ' and is 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 = number_of_crafting_operations * sub_recipe.count - required_count
|
|
-- print(' Will make ' .. number_of_crafting_operations .. ' crafting operations')
|
|
-- print(' That will produce ' .. excess .. ' items I can use later')
|
|
|
|
-- do the crafting
|
|
local current_crafting_order = {}
|
|
for _ = 1, number_of_crafting_operations do
|
|
local required_items, crafting_order = craft_recipe_recursion(sub_recipe, available_items, currently_available)
|
|
for k, v in pairs(required_items) do
|
|
add(total_required_items, k, v)
|
|
end
|
|
current_crafting_order = append(current_crafting_order, crafting_order)
|
|
end
|
|
|
|
total_crafting_order = append(total_crafting_order, current_crafting_order)
|
|
if excess > 0 then
|
|
add(currently_available, item, excess)
|
|
end
|
|
else
|
|
-- print('Need ' .. required_count .. ' more ' .. item)
|
|
add(total_required_items, item, required_count)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- add the recipe it self to the end
|
|
total_crafting_order[#total_crafting_order+1] = recipe
|
|
|
|
return total_required_items, total_crafting_order
|
|
end
|
|
|
|
---accepts a recipe table
|
|
---@param search string search for an craftable item
|
|
---@param amount number how many items to craft
|
|
---@return boolean
|
|
local function craft_item(search, amount)
|
|
search = string.lower(search) -- convert to unified lowercase
|
|
|
|
local matches = {}
|
|
for key, value in pairs(recipes) do
|
|
if string.find(value.result, search) then
|
|
print(#matches+1 .. ") " .. value.result)
|
|
matches[#matches+1] = key
|
|
elseif value.name ~= nil then
|
|
if string.find(value.name, search) then
|
|
print(#matches+1 .. ") " .. value.result)
|
|
matches[#matches+1] = key
|
|
end
|
|
end
|
|
end
|
|
|
|
local selected
|
|
if #matches == 0 then
|
|
print(search .. ' not found')
|
|
return false
|
|
elseif #matches == 1 then
|
|
selected = matches[1]
|
|
else
|
|
local input = io.read()
|
|
if not input then
|
|
print('Interrupted')
|
|
return false
|
|
end
|
|
|
|
local index = tonumber(input)
|
|
if not index then
|
|
print('Invalid index')
|
|
return false
|
|
end
|
|
|
|
if index < 1 or index > #matches then
|
|
print('Invalid index')
|
|
return false
|
|
end
|
|
|
|
selected = matches[index]
|
|
end
|
|
|
|
clear_crafting_space()
|
|
|
|
local recipe = recipes[selected]
|
|
|
|
print('Trying to craft ' .. amount .. 'x ' .. recipe.result)
|
|
|
|
local repeating = false
|
|
|
|
local count = math.ceil(amount / recipe.count)
|
|
|
|
for _ = 1, count do
|
|
local available_items = read_chest_contents()
|
|
if not available_items then
|
|
print('Not facing a chest')
|
|
return false
|
|
end
|
|
|
|
local required_items, crafting_order = craft_recipe_recursion(recipe, clone(available_items), {})
|
|
|
|
print('\nRequires these items:')
|
|
local failed = false
|
|
for required_item, required_count in pairs(required_items) do
|
|
local available = available_items[required_item]
|
|
if not available then
|
|
available = 0
|
|
end
|
|
if available < required_count then
|
|
print(available .. "/" .. required_count .. "\t" .. required_item .. "\t- Missing " .. required_count - available)
|
|
failed = true
|
|
else
|
|
print(available .. "/" .. required_count .. "\t" .. required_item)
|
|
end
|
|
end
|
|
if failed then
|
|
return false
|
|
end
|
|
|
|
if not repeating then
|
|
if not confirm() then
|
|
print('Operation cancelled')
|
|
return false
|
|
end
|
|
end
|
|
|
|
-- proceed to crafting the things
|
|
local steps = #crafting_order
|
|
for i, rec in ipairs(crafting_order) do
|
|
print('Step ' .. i .. '/' .. steps .. ' - ' .. rec.result)
|
|
if not craft_single_recipe(rec, 1) then
|
|
print('Unexpected error')
|
|
return false
|
|
end
|
|
end
|
|
|
|
repeating = true
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
local target, amount = ...
|
|
|
|
if not target then
|
|
print("Usage: craft <item> [count]")
|
|
return
|
|
end
|
|
|
|
if not amount then
|
|
amount = 1
|
|
end
|
|
|
|
craft_item(target, amount)
|
|
|