Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- Define necessary functions for GUI
- function pos(...)
- return term.setCursorPos(...)
- end
- function cls(...)
- return term.clear()
- end
- function tCol(...)
- return term.setTextColor(...)
- end
- function bCol(...)
- return term.setBackgroundColor(...)
- end
- function box(...)
- return paintutils.drawFilledBox(...)
- end
- function line(...)
- return paintutils.drawLine(...)
- end
- function getInput(prompt)
- term.write(prompt)
- return read()
- end
- -- Attempt to get the terminal size
- x, y = term.getSize()
- -- If the terminal size is not valid (nil or incorrect), default to 80x25
- if not x or not y then
- x, y = 80, 25 -- Default values for terminal size
- end
- -- Peripheral setup and recipe file
- local RECIPE_FILE = "recipes.txt" -- File to store recipes
- -- Initialize ingredient chest (will be set after logAtBottom is defined)
- local ingredientChest = nil
- -- New variables for pagination
- local recipesPerPage = 11
- local currentPage = 1
- -- Initialize selectedRecipeIndex safely
- local selectedRecipeIndex = 1
- local recipeNames = {}
- -- Initialize peripherals
- local externalMonitor = peripheral.find("monitor", function(name, wrapped)
- return name ~= "monitor"
- end)
- -- Set smaller text scale for external monitor if present
- if externalMonitor then
- externalMonitor.setTextScale(0.5) -- 0.5 is half size, adjust as needed (common values: 0.5, 1 for normal)
- end
- -- Function to log messages at the bottom of the screen and on an external monitor if present
- local function logAtBottom(message)
- -- Log to main monitor
- tCol(colors.white)
- bCol(colors.black)
- pos(2, y)
- write(string.sub(message, 1, x - 2)) -- Only write what fits on one line
- -- Log to external monitor if present
- if externalMonitor then
- local extWidth, extHeight = externalMonitor.getSize()
- local extY = extHeight - 1 -- Write at the bottom of the external monitor
- -- Scroll if at the bottom
- local cursorX, cursorY = externalMonitor.getCursorPos()
- if cursorY == extY then
- externalMonitor.scroll(1)
- end
- externalMonitor.setCursorPos(1, extY)
- externalMonitor.setTextColor(colors.white)
- externalMonitor.setBackgroundColor(colors.black)
- externalMonitor.write(string.sub(message, 1, extWidth - 2)) -- Adjust message to fit external monitor
- end
- end
- -- Find the ingredient chest (can be any chest, barrel, or storage)
- local function findIngredientChest()
- -- Look for any inventory peripheral
- local chestTypes = {"chest", "barrel", "ironchest", "sophisticated", "storage"}
- for _, peripheralName in ipairs(peripheral.getNames()) do
- for _, chestType in ipairs(chestTypes) do
- if string.match(peripheralName, chestType) then
- print("Found ingredient storage: " .. peripheralName) -- Use print during initialization
- return peripheral.wrap(peripheralName)
- end
- end
- end
- return nil
- end
- -- Now we can safely initialize the ingredient chest
- ingredientChest = findIngredientChest()
- -- Function to simulate pressing enter on external monitor, pushing logs up
- local function pressEnterOnExternalMonitor()
- if externalMonitor then
- local _, extHeight = externalMonitor.getSize()
- -- Get the current cursor position
- local cursorX, cursorY = externalMonitor.getCursorPos()
- -- If we're at the bottom, scroll up
- if cursorY == extHeight then
- externalMonitor.scroll(1)
- cursorY = extHeight - 1 -- Reset cursor to last line after scrolling
- end
- -- Move cursor to next line to simulate pressing enter
- externalMonitor.setCursorPos(1, cursorY + 1)
- end
- end
- -- Helper function to calculate page count
- local function getPageCount()
- return math.ceil(#recipeNames / recipesPerPage)
- end
- -- Helper function to get recipes for the current page
- local function getPageRecipes()
- local start = (currentPage - 1) * recipesPerPage + 1
- local stop = math.min(start + recipesPerPage - 1, #recipeNames)
- return { table.unpack(recipeNames, start, stop) }
- end
- -- Function to find and sort mechanical crafters with dynamic grid sizing
- local function findAndSortCrafters()
- local crafters = {}
- for _, peripheralName in ipairs(peripheral.getNames()) do
- if string.match(peripheralName, "mechanical_crafter") then
- -- Extract number from the name
- local number = tonumber(string.match(peripheralName, "%d+"))
- if number then
- table.insert(crafters, { name = peripheralName, number = number })
- end
- end
- end
- -- Sort crafters by their number in ascending order
- table.sort(crafters, function(a, b)
- return a.number < b.number
- end)
- -- Determine grid size dynamically
- local gridSize = math.min(math.ceil(math.sqrt(#crafters)), 9) -- Max 9x9
- local sortedCraftersGrid = {}
- for i = 1, gridSize do
- sortedCraftersGrid[i] = {}
- for j = 1, gridSize do
- local index = (i - 1) * gridSize + j
- if crafters[index] then
- sortedCraftersGrid[i][j] = crafters[index].name
- else
- -- If there are fewer crafters than grid slots, fill with nil
- sortedCraftersGrid[i][j] = nil
- end
- end
- end
- return sortedCraftersGrid
- end
- -- Use this function to initialize crafterGrid
- local crafterGrid = findAndSortCrafters()
- -- Load and save recipes
- local function loadRecipes()
- if not fs.exists(RECIPE_FILE) then
- return {}
- end
- local file = fs.open(RECIPE_FILE, "r")
- local data = file.readAll()
- file.close()
- return textutils.unserialize(data) or {}
- end
- local function saveRecipes(recipes)
- local file = fs.open(RECIPE_FILE, "w")
- file.write(textutils.serialize(recipes))
- file.close()
- end
- -- Function to find an item in the ingredient chest by name
- local function findItemInChest(itemName)
- if not ingredientChest then
- logAtBottom("ERROR: No ingredient chest found!")
- return nil
- end
- local items = ingredientChest.list()
- for slot, item in pairs(items) do
- -- Match the item name (handle both "minecraft:item" and "item" formats)
- if item.name == itemName or item.name:match(":(.+)$") == itemName then
- return slot, item.count
- end
- end
- return nil, 0
- end
- -- Function to push an item from chest to a peripheral (mechanical crafter)
- local function pushItemToPeripheral(itemName, targetPeripheral)
- if not ingredientChest then
- logAtBottom("ERROR: No ingredient chest connected!")
- return false
- end
- local slot, count = findItemInChest(itemName)
- if not slot then
- logAtBottom("ERROR: Item not found in chest: " .. itemName)
- return false
- end
- if count < 1 then
- logAtBottom("ERROR: Not enough of item: " .. itemName)
- return false
- end
- -- Push 1 item from the chest to the target peripheral
- local pushed = ingredientChest.pushItems(targetPeripheral, slot, 1)
- if pushed > 0 then
- return true
- else
- logAtBottom("ERROR: Failed to push item to " .. targetPeripheral)
- return false
- end
- end
- local craftRecipe
- -- Check if we have enough of an item in the chest
- local function checkItemAvailability(itemName, requiredCount)
- local slot, count = findItemInChest(itemName)
- if not slot then
- logAtBottom("ERROR: Missing ingredient: " .. itemName)
- return false
- end
- if count < requiredCount then
- logAtBottom("ERROR: Not enough " .. itemName .. " (need " .. requiredCount .. ", have " .. count .. ")")
- return false
- end
- return true
- end
- -- Modify craftRecipe to use chest-based inventory
- craftRecipe = function(name)
- local recipes = loadRecipes()
- local recipe = recipes[name]
- if not recipe then
- logAtBottom("Error: Recipe '" .. name .. "' not found.")
- return false
- end
- if not ingredientChest then
- logAtBottom("ERROR: No ingredient chest connected!")
- return false
- end
- local missingIngredients = {}
- -- Gather all ingredients and calculate the required amounts
- for _, itemName in pairs(recipe) do
- missingIngredients[itemName] = (missingIngredients[itemName] or 0) + 1
- end
- -- Check if we have all required ingredients
- local canCraft = true
- for itemName, totalRequired in pairs(missingIngredients) do
- if not checkItemAvailability(itemName, totalRequired) then
- canCraft = false
- end
- end
- if not canCraft then
- logAtBottom("Cannot craft: Missing ingredients")
- return false
- end
- logAtBottom("All ingredients available, starting craft...")
- -- Populate the crafting grid
- for position, itemName in pairs(recipe) do
- local row, col = position:match("row(%d+)_col(%d+)")
- row, col = tonumber(row), tonumber(col)
- if row and col and crafterGrid[row] and crafterGrid[row][col] then
- local crafter = crafterGrid[row][col]
- if not pushItemToPeripheral(itemName, crafter) then
- logAtBottom("Failed to push " .. itemName .. " to " .. position)
- return false
- end
- else
- logAtBottom("Error: Invalid grid position '" .. position .. "'.")
- return false
- end
- end
- -- Start the crafting process
- logAtBottom("Initiating crafting process...")
- redstone.setOutput("right", true)
- os.sleep(0.1) -- Very short delay for redstone to activate
- redstone.setOutput("right", false)
- logAtBottom("Craft completed for: " .. name)
- return true
- end
- local function removeRecipe()
- local recipes = loadRecipes()
- local removedRecipeName = recipeNames[selectedRecipeIndex]
- if removedRecipeName then
- -- Prompt for confirmation
- tCol(colors.red)
- pos(2, y)
- write("Confirm removal of '" .. removedRecipeName .. "'? (Y/N): ")
- local confirmation = read():lower()
- if confirmation == "y" then
- recipes[removedRecipeName] = nil
- saveRecipes(recipes)
- logAtBottom("Recipe '" .. removedRecipeName .. "' removed.")
- else
- logAtBottom("Removal cancelled.")
- return
- end
- else
- logAtBottom("No recipe selected.")
- end
- recipeNames = {}
- for name in pairs(recipes) do
- table.insert(recipeNames, name)
- end
- selectedRecipeIndex = math.min(selectedRecipeIndex, #recipeNames)
- end
- -- Create a new recipe with support for larger grids
- local function createRecipe()
- write("Enter recipe name: ")
- local name = read()
- print("Select grid size:")
- print("1. 3x3")
- print("2. 5x5")
- print("3. 7x7")
- print("4. 9x9")
- write("Choose an option: ")
- local gridChoice = tonumber(read())
- local gridSizes = { 3, 5, 7, 9 }
- local gridSize = gridSizes[gridChoice] or 3 -- Default to 3x3 if choice is invalid
- local recipe = {}
- for row = 1, gridSize do
- for col = 1, gridSize do
- write(string.format("Row %d, Col %d: ", row, col))
- local input = read()
- if input and input ~= "" then
- recipe[string.format("row%d_col%d", row, col)] = input
- end
- end
- end
- local recipes = loadRecipes()
- recipes[name] = recipe
- saveRecipes(recipes)
- logAtBottom("Recipe created: " .. name)
- end
- local function loadAndUpdateRecipes()
- local recipes = loadRecipes()
- recipeNames = {}
- for name in pairs(recipes) do
- table.insert(recipeNames, name)
- end
- -- Ensure selectedRecipeIndex is within bounds
- if #recipeNames > 0 then
- -- Safeguard against out of bounds, ensure it's valid after loading
- selectedRecipeIndex = selectedRecipeIndex and math.min(selectedRecipeIndex, #recipeNames) or 1
- else
- selectedRecipeIndex = nil -- No recipes, set to nil
- end
- end
- local listStartY = 3 -- Starting position for the recipe list
- -- GUI and event loop
- local function drawMenu()
- cls()
- loadAndUpdateRecipes()
- -- Safeguard: ensure selectedRecipeIndex is valid
- if #recipeNames > 0 and not selectedRecipeIndex then
- selectedRecipeIndex = 1
- elseif #recipeNames == 0 then
- selectedRecipeIndex = nil
- end
- -- Draw background and layout
- box(1, 1, x, y, colors.lightBlue) -- Background
- box(1, 2, 40, y - 2, colors.gray) -- Left panel for recipes (widened)
- box(41, 2, 50, y - 2, colors.darkGray) -- Right panel for buttons (widened)
- box(1, y - 1, x, y, colors.black) -- Bottom log area
- -- Header Lines
- line(40, 2, 40, y - 2, colors.lightGray)
- line(49, 2, 49, y - 2, colors.lightGray)
- -- Header
- tCol(colors.purple)
- bCol(colors.lightBlue)
- pos(2, 1)
- write("Mechanical Crafter Automation Program")
- tCol(colors.yellow)
- bCol(colors.gray)
- pos(2, 2)
- write("Recipes")
- -- Draw Recipe List with pagination
- local pageRecipes = getPageRecipes()
- -- Helper function to extract the part after the colon
- local function extractName(fullName)
- return fullName:match(":(.+)$") or fullName -- Extract after colon or return fullName if no colon
- end
- for i, name in ipairs(pageRecipes) do
- local displayName = extractName(name) -- Get the display name
- if selectedRecipeIndex and i == (selectedRecipeIndex - (currentPage - 1) * recipesPerPage) then
- tCol(colors.black)
- bCol(colors.yellow)
- else
- tCol(colors.white)
- bCol(colors.black)
- end
- pos(2, listStartY + i - 1) -- Adjust Y position
- write(displayName or "") -- Display the extracted name
- end
- -- Pagination indicators
- tCol(colors.white)
- bCol(colors.gray)
- pos(2, y - 2)
- write("Page " .. currentPage .. " of " .. getPageCount())
- -- Draw "Next Page" and "Previous Page" buttons
- local buttonY = y - 3
- if currentPage > 1 then
- bCol(colors.blue)
- pos(15, buttonY)
- write("< Prev")
- end
- if currentPage < getPageCount() then
- bCol(colors.blue)
- pos(22, buttonY)
- write("Next >")
- end
- -- Buttons on the right side
- tCol(colors.white)
- bCol(colors.green)
- pos(42, 7)
- write("Craft")
- bCol(colors.blue)
- pos(42, 9)
- write("Add")
- bCol(colors.red)
- pos(42, 11)
- write("Remove")
- bCol(colors.red)
- pos(42, 13)
- write("Exit")
- -- Bottom log display
- tCol(colors.white)
- bCol(colors.black)
- pos(2, y)
- write("Log: Awaiting actions...")
- end
- -- GUI and event loop
- local function main()
- loadAndUpdateRecipes()
- -- Check if we have the necessary peripherals
- if not ingredientChest then
- print("ERROR: No ingredient chest found!")
- print("Please connect a chest, barrel, or other storage.")
- return
- end
- if #crafterGrid == 0 then
- print("ERROR: No mechanical crafters found!")
- print("Please connect mechanical crafters via wired modems.")
- return
- end
- logAtBottom("System ready. Chest and crafters detected.")
- -- Start GUI
- while true do
- drawMenu()
- local event, button, mx, my = os.pullEvent("mouse_click")
- mx = tonumber(mx)
- my = tonumber(my)
- -- Safeguard: ensure mx and my are valid numbers
- if mx and my then
- -- Check for pagination clicks
- local buttonY = y - 3
- if my == buttonY then
- if mx >= 15 and mx <= 20 and currentPage > 1 then
- currentPage = currentPage - 1
- selectedRecipeIndex = math.max(1, (currentPage - 1) * recipesPerPage + 1)
- elseif mx >= 22 and mx <= 27 and currentPage < getPageCount() then
- currentPage = currentPage + 1
- selectedRecipeIndex = math.min(#recipeNames, currentPage * recipesPerPage)
- end
- end
- -- Check the exit button
- if mx >= 42 and mx <= 50 and my == 13 then
- return -- Close the program
- -- Craft button
- elseif mx >= 42 and mx <= 50 and my == 7 then
- if #recipeNames > 0 and selectedRecipeIndex then
- local name = recipeNames[selectedRecipeIndex]
- logAtBottom("Manual craft request: " .. name)
- craftRecipe(name)
- end
- -- Add button
- elseif mx >= 42 and mx <= 50 and my == 9 then
- createRecipe()
- loadAndUpdateRecipes()
- logAtBottom("New recipe added. Menu updated.")
- -- Remove button
- elseif mx >= 42 and mx <= 50 and my == 11 then
- if selectedRecipeIndex then
- removeRecipe() -- Remove the selected recipe
- loadAndUpdateRecipes()
- logAtBottom("Recipe removed. Menu updated.")
- end
- -- Recipe List Click Event
- elseif mx >= 1 and mx <= 40 and my >= listStartY and my < (listStartY + recipesPerPage) then
- local clickedIndex = my - listStartY + 1
- local actualIndex = (currentPage - 1) * recipesPerPage + clickedIndex
- if actualIndex <= #recipeNames then
- selectedRecipeIndex = actualIndex
- end
- end
- end
- end
- end
- main()
Add Comment
Please, Sign In to add comment