GrooveypenguinX

mechac - createastral

Jan 31st, 2026 (edited)
12
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 15.69 KB | None | 0 0
  1. -- Define necessary functions for GUI
  2. function pos(...)
  3. return term.setCursorPos(...)
  4. end
  5. function cls(...)
  6. return term.clear()
  7. end
  8. function tCol(...)
  9. return term.setTextColor(...)
  10. end
  11. function bCol(...)
  12. return term.setBackgroundColor(...)
  13. end
  14. function box(...)
  15. return paintutils.drawFilledBox(...)
  16. end
  17. function line(...)
  18. return paintutils.drawLine(...)
  19. end
  20. function getInput(prompt)
  21. term.write(prompt)
  22. return read()
  23. end
  24.  
  25. -- Attempt to get the terminal size
  26. x, y = term.getSize()
  27.  
  28. -- If the terminal size is not valid (nil or incorrect), default to 80x25
  29. if not x or not y then
  30. x, y = 80, 25 -- Default values for terminal size
  31. end
  32.  
  33. -- Peripheral setup and recipe file
  34. local RECIPE_FILE = "recipes.txt" -- File to store recipes
  35.  
  36. -- Initialize ingredient chest (will be set after logAtBottom is defined)
  37. local ingredientChest = nil
  38.  
  39. -- New variables for pagination
  40. local recipesPerPage = 11
  41. local currentPage = 1
  42.  
  43. -- Initialize selectedRecipeIndex safely
  44. local selectedRecipeIndex = 1
  45. local recipeNames = {}
  46.  
  47. -- Initialize peripherals
  48. local externalMonitor = peripheral.find("monitor", function(name, wrapped)
  49. return name ~= "monitor"
  50. end)
  51.  
  52. -- Set smaller text scale for external monitor if present
  53. if externalMonitor then
  54. externalMonitor.setTextScale(0.5) -- 0.5 is half size, adjust as needed (common values: 0.5, 1 for normal)
  55. end
  56.  
  57. -- Function to log messages at the bottom of the screen and on an external monitor if present
  58. local function logAtBottom(message)
  59. -- Log to main monitor
  60. tCol(colors.white)
  61. bCol(colors.black)
  62. pos(2, y)
  63. write(string.sub(message, 1, x - 2)) -- Only write what fits on one line
  64.  
  65. -- Log to external monitor if present
  66. if externalMonitor then
  67. local extWidth, extHeight = externalMonitor.getSize()
  68. local extY = extHeight - 1 -- Write at the bottom of the external monitor
  69.  
  70. -- Scroll if at the bottom
  71. local cursorX, cursorY = externalMonitor.getCursorPos()
  72. if cursorY == extY then
  73. externalMonitor.scroll(1)
  74. end
  75.  
  76. externalMonitor.setCursorPos(1, extY)
  77. externalMonitor.setTextColor(colors.white)
  78. externalMonitor.setBackgroundColor(colors.black)
  79. externalMonitor.write(string.sub(message, 1, extWidth - 2)) -- Adjust message to fit external monitor
  80. end
  81. end
  82.  
  83. -- Find the ingredient chest (can be any chest, barrel, or storage)
  84. local function findIngredientChest()
  85. -- Look for any inventory peripheral
  86. local chestTypes = {"chest", "barrel", "ironchest", "sophisticated", "storage"}
  87.  
  88. for _, peripheralName in ipairs(peripheral.getNames()) do
  89. for _, chestType in ipairs(chestTypes) do
  90. if string.match(peripheralName, chestType) then
  91. print("Found ingredient storage: " .. peripheralName) -- Use print during initialization
  92. return peripheral.wrap(peripheralName)
  93. end
  94. end
  95. end
  96.  
  97. return nil
  98. end
  99.  
  100. -- Now we can safely initialize the ingredient chest
  101. ingredientChest = findIngredientChest()
  102.  
  103. -- Function to simulate pressing enter on external monitor, pushing logs up
  104. local function pressEnterOnExternalMonitor()
  105. if externalMonitor then
  106. local _, extHeight = externalMonitor.getSize()
  107.  
  108. -- Get the current cursor position
  109. local cursorX, cursorY = externalMonitor.getCursorPos()
  110.  
  111. -- If we're at the bottom, scroll up
  112. if cursorY == extHeight then
  113. externalMonitor.scroll(1)
  114. cursorY = extHeight - 1 -- Reset cursor to last line after scrolling
  115. end
  116.  
  117. -- Move cursor to next line to simulate pressing enter
  118. externalMonitor.setCursorPos(1, cursorY + 1)
  119. end
  120. end
  121.  
  122. -- Helper function to calculate page count
  123. local function getPageCount()
  124. return math.ceil(#recipeNames / recipesPerPage)
  125. end
  126.  
  127. -- Helper function to get recipes for the current page
  128. local function getPageRecipes()
  129. local start = (currentPage - 1) * recipesPerPage + 1
  130. local stop = math.min(start + recipesPerPage - 1, #recipeNames)
  131. return { table.unpack(recipeNames, start, stop) }
  132. end
  133.  
  134. -- Function to find and sort mechanical crafters with dynamic grid sizing
  135. local function findAndSortCrafters()
  136. local crafters = {}
  137. for _, peripheralName in ipairs(peripheral.getNames()) do
  138. if string.match(peripheralName, "mechanical_crafter") then
  139. -- Extract number from the name
  140. local number = tonumber(string.match(peripheralName, "%d+"))
  141. if number then
  142. table.insert(crafters, { name = peripheralName, number = number })
  143. end
  144. end
  145. end
  146.  
  147. -- Sort crafters by their number in ascending order
  148. table.sort(crafters, function(a, b)
  149. return a.number < b.number
  150. end)
  151.  
  152. -- Determine grid size dynamically
  153. local gridSize = math.min(math.ceil(math.sqrt(#crafters)), 9) -- Max 9x9
  154. local sortedCraftersGrid = {}
  155.  
  156. for i = 1, gridSize do
  157. sortedCraftersGrid[i] = {}
  158. for j = 1, gridSize do
  159. local index = (i - 1) * gridSize + j
  160. if crafters[index] then
  161. sortedCraftersGrid[i][j] = crafters[index].name
  162. else
  163. -- If there are fewer crafters than grid slots, fill with nil
  164. sortedCraftersGrid[i][j] = nil
  165. end
  166. end
  167. end
  168.  
  169. return sortedCraftersGrid
  170. end
  171.  
  172. -- Use this function to initialize crafterGrid
  173. local crafterGrid = findAndSortCrafters()
  174.  
  175. -- Load and save recipes
  176. local function loadRecipes()
  177. if not fs.exists(RECIPE_FILE) then
  178. return {}
  179. end
  180. local file = fs.open(RECIPE_FILE, "r")
  181. local data = file.readAll()
  182. file.close()
  183. return textutils.unserialize(data) or {}
  184. end
  185.  
  186. local function saveRecipes(recipes)
  187. local file = fs.open(RECIPE_FILE, "w")
  188. file.write(textutils.serialize(recipes))
  189. file.close()
  190. end
  191.  
  192. -- Function to find an item in the ingredient chest by name
  193. local function findItemInChest(itemName)
  194. if not ingredientChest then
  195. logAtBottom("ERROR: No ingredient chest found!")
  196. return nil
  197. end
  198.  
  199. local items = ingredientChest.list()
  200.  
  201. for slot, item in pairs(items) do
  202. -- Match the item name (handle both "minecraft:item" and "item" formats)
  203. if item.name == itemName or item.name:match(":(.+)$") == itemName then
  204. return slot, item.count
  205. end
  206. end
  207.  
  208. return nil, 0
  209. end
  210.  
  211. -- Function to push an item from chest to a peripheral (mechanical crafter)
  212. local function pushItemToPeripheral(itemName, targetPeripheral)
  213. if not ingredientChest then
  214. logAtBottom("ERROR: No ingredient chest connected!")
  215. return false
  216. end
  217.  
  218. local slot, count = findItemInChest(itemName)
  219.  
  220. if not slot then
  221. logAtBottom("ERROR: Item not found in chest: " .. itemName)
  222. return false
  223. end
  224.  
  225. if count < 1 then
  226. logAtBottom("ERROR: Not enough of item: " .. itemName)
  227. return false
  228. end
  229.  
  230. -- Push 1 item from the chest to the target peripheral
  231. local pushed = ingredientChest.pushItems(targetPeripheral, slot, 1)
  232.  
  233. if pushed > 0 then
  234. return true
  235. else
  236. logAtBottom("ERROR: Failed to push item to " .. targetPeripheral)
  237. return false
  238. end
  239. end
  240.  
  241. local craftRecipe
  242.  
  243. -- Check if we have enough of an item in the chest
  244. local function checkItemAvailability(itemName, requiredCount)
  245. local slot, count = findItemInChest(itemName)
  246.  
  247. if not slot then
  248. logAtBottom("ERROR: Missing ingredient: " .. itemName)
  249. return false
  250. end
  251.  
  252. if count < requiredCount then
  253. logAtBottom("ERROR: Not enough " .. itemName .. " (need " .. requiredCount .. ", have " .. count .. ")")
  254. return false
  255. end
  256.  
  257. return true
  258. end
  259.  
  260. -- Modify craftRecipe to use chest-based inventory
  261. craftRecipe = function(name)
  262. local recipes = loadRecipes()
  263. local recipe = recipes[name]
  264. if not recipe then
  265. logAtBottom("Error: Recipe '" .. name .. "' not found.")
  266. return false
  267. end
  268.  
  269. if not ingredientChest then
  270. logAtBottom("ERROR: No ingredient chest connected!")
  271. return false
  272. end
  273.  
  274. local missingIngredients = {}
  275.  
  276. -- Gather all ingredients and calculate the required amounts
  277. for _, itemName in pairs(recipe) do
  278. missingIngredients[itemName] = (missingIngredients[itemName] or 0) + 1
  279. end
  280.  
  281. -- Check if we have all required ingredients
  282. local canCraft = true
  283. for itemName, totalRequired in pairs(missingIngredients) do
  284. if not checkItemAvailability(itemName, totalRequired) then
  285. canCraft = false
  286. end
  287. end
  288.  
  289. if not canCraft then
  290. logAtBottom("Cannot craft: Missing ingredients")
  291. return false
  292. end
  293.  
  294. logAtBottom("All ingredients available, starting craft...")
  295.  
  296. -- Populate the crafting grid
  297. for position, itemName in pairs(recipe) do
  298. local row, col = position:match("row(%d+)_col(%d+)")
  299. row, col = tonumber(row), tonumber(col)
  300.  
  301. if row and col and crafterGrid[row] and crafterGrid[row][col] then
  302. local crafter = crafterGrid[row][col]
  303. if not pushItemToPeripheral(itemName, crafter) then
  304. logAtBottom("Failed to push " .. itemName .. " to " .. position)
  305. return false
  306. end
  307. else
  308. logAtBottom("Error: Invalid grid position '" .. position .. "'.")
  309. return false
  310. end
  311. end
  312.  
  313. -- Start the crafting process
  314. logAtBottom("Initiating crafting process...")
  315. redstone.setOutput("right", true)
  316. os.sleep(0.1) -- Very short delay for redstone to activate
  317. redstone.setOutput("right", false)
  318.  
  319. logAtBottom("Craft completed for: " .. name)
  320. return true
  321. end
  322.  
  323. local function removeRecipe()
  324. local recipes = loadRecipes()
  325. local removedRecipeName = recipeNames[selectedRecipeIndex]
  326. if removedRecipeName then
  327. -- Prompt for confirmation
  328. tCol(colors.red)
  329. pos(2, y)
  330. write("Confirm removal of '" .. removedRecipeName .. "'? (Y/N): ")
  331. local confirmation = read():lower()
  332.  
  333. if confirmation == "y" then
  334. recipes[removedRecipeName] = nil
  335. saveRecipes(recipes)
  336. logAtBottom("Recipe '" .. removedRecipeName .. "' removed.")
  337. else
  338. logAtBottom("Removal cancelled.")
  339. return
  340. end
  341. else
  342. logAtBottom("No recipe selected.")
  343. end
  344. recipeNames = {}
  345. for name in pairs(recipes) do
  346. table.insert(recipeNames, name)
  347. end
  348. selectedRecipeIndex = math.min(selectedRecipeIndex, #recipeNames)
  349. end
  350.  
  351. -- Create a new recipe with support for larger grids
  352. local function createRecipe()
  353. write("Enter recipe name: ")
  354. local name = read()
  355.  
  356. print("Select grid size:")
  357. print("1. 3x3")
  358. print("2. 5x5")
  359. print("3. 7x7")
  360. print("4. 9x9")
  361. write("Choose an option: ")
  362. local gridChoice = tonumber(read())
  363.  
  364. local gridSizes = { 3, 5, 7, 9 }
  365. local gridSize = gridSizes[gridChoice] or 3 -- Default to 3x3 if choice is invalid
  366.  
  367. local recipe = {}
  368.  
  369. for row = 1, gridSize do
  370. for col = 1, gridSize do
  371. write(string.format("Row %d, Col %d: ", row, col))
  372. local input = read()
  373. if input and input ~= "" then
  374. recipe[string.format("row%d_col%d", row, col)] = input
  375. end
  376. end
  377. end
  378.  
  379. local recipes = loadRecipes()
  380. recipes[name] = recipe
  381. saveRecipes(recipes)
  382. logAtBottom("Recipe created: " .. name)
  383. end
  384.  
  385. local function loadAndUpdateRecipes()
  386. local recipes = loadRecipes()
  387. recipeNames = {}
  388. for name in pairs(recipes) do
  389. table.insert(recipeNames, name)
  390. end
  391.  
  392. -- Ensure selectedRecipeIndex is within bounds
  393. if #recipeNames > 0 then
  394. -- Safeguard against out of bounds, ensure it's valid after loading
  395. selectedRecipeIndex = selectedRecipeIndex and math.min(selectedRecipeIndex, #recipeNames) or 1
  396. else
  397. selectedRecipeIndex = nil -- No recipes, set to nil
  398. end
  399. end
  400.  
  401. local listStartY = 3 -- Starting position for the recipe list
  402.  
  403. -- GUI and event loop
  404. local function drawMenu()
  405. cls()
  406. loadAndUpdateRecipes()
  407.  
  408. -- Safeguard: ensure selectedRecipeIndex is valid
  409. if #recipeNames > 0 and not selectedRecipeIndex then
  410. selectedRecipeIndex = 1
  411. elseif #recipeNames == 0 then
  412. selectedRecipeIndex = nil
  413. end
  414.  
  415. -- Draw background and layout
  416. box(1, 1, x, y, colors.lightBlue) -- Background
  417. box(1, 2, 40, y - 2, colors.gray) -- Left panel for recipes (widened)
  418. box(41, 2, 50, y - 2, colors.darkGray) -- Right panel for buttons (widened)
  419. box(1, y - 1, x, y, colors.black) -- Bottom log area
  420.  
  421. -- Header Lines
  422. line(40, 2, 40, y - 2, colors.lightGray)
  423. line(49, 2, 49, y - 2, colors.lightGray)
  424.  
  425. -- Header
  426. tCol(colors.purple)
  427. bCol(colors.lightBlue)
  428. pos(2, 1)
  429. write("Mechanical Crafter Automation Program")
  430.  
  431. tCol(colors.yellow)
  432. bCol(colors.gray)
  433. pos(2, 2)
  434. write("Recipes")
  435.  
  436. -- Draw Recipe List with pagination
  437. local pageRecipes = getPageRecipes()
  438. -- Helper function to extract the part after the colon
  439. local function extractName(fullName)
  440. return fullName:match(":(.+)$") or fullName -- Extract after colon or return fullName if no colon
  441. end
  442.  
  443. for i, name in ipairs(pageRecipes) do
  444. local displayName = extractName(name) -- Get the display name
  445.  
  446. if selectedRecipeIndex and i == (selectedRecipeIndex - (currentPage - 1) * recipesPerPage) then
  447. tCol(colors.black)
  448. bCol(colors.yellow)
  449. else
  450. tCol(colors.white)
  451. bCol(colors.black)
  452. end
  453. pos(2, listStartY + i - 1) -- Adjust Y position
  454. write(displayName or "") -- Display the extracted name
  455. end
  456.  
  457. -- Pagination indicators
  458. tCol(colors.white)
  459. bCol(colors.gray)
  460. pos(2, y - 2)
  461. write("Page " .. currentPage .. " of " .. getPageCount())
  462.  
  463. -- Draw "Next Page" and "Previous Page" buttons
  464. local buttonY = y - 3
  465. if currentPage > 1 then
  466. bCol(colors.blue)
  467. pos(15, buttonY)
  468. write("< Prev")
  469. end
  470. if currentPage < getPageCount() then
  471. bCol(colors.blue)
  472. pos(22, buttonY)
  473. write("Next >")
  474. end
  475.  
  476. -- Buttons on the right side
  477. tCol(colors.white)
  478. bCol(colors.green)
  479. pos(42, 7)
  480. write("Craft")
  481.  
  482. bCol(colors.blue)
  483. pos(42, 9)
  484. write("Add")
  485.  
  486. bCol(colors.red)
  487. pos(42, 11)
  488. write("Remove")
  489.  
  490. bCol(colors.red)
  491. pos(42, 13)
  492. write("Exit")
  493.  
  494. -- Bottom log display
  495. tCol(colors.white)
  496. bCol(colors.black)
  497. pos(2, y)
  498. write("Log: Awaiting actions...")
  499. end
  500.  
  501. -- GUI and event loop
  502. local function main()
  503. loadAndUpdateRecipes()
  504.  
  505. -- Check if we have the necessary peripherals
  506. if not ingredientChest then
  507. print("ERROR: No ingredient chest found!")
  508. print("Please connect a chest, barrel, or other storage.")
  509. return
  510. end
  511.  
  512. if #crafterGrid == 0 then
  513. print("ERROR: No mechanical crafters found!")
  514. print("Please connect mechanical crafters via wired modems.")
  515. return
  516. end
  517.  
  518. logAtBottom("System ready. Chest and crafters detected.")
  519.  
  520. -- Start GUI
  521. while true do
  522. drawMenu()
  523.  
  524. local event, button, mx, my = os.pullEvent("mouse_click")
  525. mx = tonumber(mx)
  526. my = tonumber(my)
  527.  
  528. -- Safeguard: ensure mx and my are valid numbers
  529. if mx and my then
  530. -- Check for pagination clicks
  531. local buttonY = y - 3
  532.  
  533. if my == buttonY then
  534. if mx >= 15 and mx <= 20 and currentPage > 1 then
  535. currentPage = currentPage - 1
  536. selectedRecipeIndex = math.max(1, (currentPage - 1) * recipesPerPage + 1)
  537. elseif mx >= 22 and mx <= 27 and currentPage < getPageCount() then
  538. currentPage = currentPage + 1
  539. selectedRecipeIndex = math.min(#recipeNames, currentPage * recipesPerPage)
  540. end
  541. end
  542.  
  543. -- Check the exit button
  544. if mx >= 42 and mx <= 50 and my == 13 then
  545. return -- Close the program
  546.  
  547. -- Craft button
  548. elseif mx >= 42 and mx <= 50 and my == 7 then
  549. if #recipeNames > 0 and selectedRecipeIndex then
  550. local name = recipeNames[selectedRecipeIndex]
  551. logAtBottom("Manual craft request: " .. name)
  552. craftRecipe(name)
  553. end
  554.  
  555. -- Add button
  556. elseif mx >= 42 and mx <= 50 and my == 9 then
  557. createRecipe()
  558. loadAndUpdateRecipes()
  559. logAtBottom("New recipe added. Menu updated.")
  560.  
  561. -- Remove button
  562. elseif mx >= 42 and mx <= 50 and my == 11 then
  563. if selectedRecipeIndex then
  564. removeRecipe() -- Remove the selected recipe
  565. loadAndUpdateRecipes()
  566. logAtBottom("Recipe removed. Menu updated.")
  567. end
  568.  
  569. -- Recipe List Click Event
  570. elseif mx >= 1 and mx <= 40 and my >= listStartY and my < (listStartY + recipesPerPage) then
  571. local clickedIndex = my - listStartY + 1
  572. local actualIndex = (currentPage - 1) * recipesPerPage + clickedIndex
  573. if actualIndex <= #recipeNames then
  574. selectedRecipeIndex = actualIndex
  575. end
  576. end
  577. end
  578. end
  579. end
  580.  
  581.  
  582. main()
  583.  
Add Comment
Please, Sign In to add comment