local _VERSION = "0.2.0" local args = table.pack(...) local command = args[1] local GOO_BLOCK_PREFIX = "justdirethings:gooblock_tier" local MIN_START_FUEL = 50 local REFUEL_THRESHOLD = 1000 local WAIT_SECONDS = 2 local FUEL_ITEMS = { "justdirethings:coal_t4", "justdirethings:coal_t3", "justdirethings:coal_t2", "justdirethings:coal_t1", "minecraft:coal", } local PROCESS_ITEMS = { ["minecraft:iron_block"] = { tier = 1, label = "iron" }, ["minecraft:coal_block"] = { tier = 1, label = "coal" }, ["mekanism:block_charcoal"] = { tier = 1, label = "charcoal" }, ["minecraft:gold_block"] = { tier = 2, label = "gold" }, ["minecraft:diamond_block"] = { tier = 3, label = "diamond" }, ["minecraft:netherite_block"] = { tier = 4, label = "netherite" }, } local FEEDING_ITEMS_BY_TIER = { [1] = { "minecraft:sugar", "minecraft:rotten_flesh" }, [2] = { "minecraft:nether_wart" }, [3] = { "minecraft:chorus_fruit" }, [4] = { "minecraft:sculk" }, } local loggedBlockedItems = {} local function isFlag(name) return function(arg) return arg == "-" .. name or arg == "--" .. name end end local isHelpFlag = isFlag("help") local isVersionFlag = isFlag("version") local function printUsage() print("goo usage:") print() print("\t\t\tgoo start") print("\t\t\tgoo version") print("\t\t\tgoo help") end if command == "version" or isVersionFlag(command) then print("goo v" .. _VERSION) return end if command == nil or command == "" or command == "help" or isHelpFlag(command) then printUsage() return end if command ~= "start" then printUsage() return end if not turtle then error("goo must be run on a turtle") end local function hasPickaxeEquipped() local left = turtle.getEquippedLeft() local right = turtle.getEquippedRight() return (left and string.match(left.name or "", "_pickaxe$") ~= nil) or (right and string.match(right.name or "", "_pickaxe$") ~= nil) end local function turnAround() turtle.turnRight() turtle.turnRight() end local function parseGooTier(blockName) local tier = string.match(blockName or "", "^" .. GOO_BLOCK_PREFIX .. "(%d+)$") return tonumber(tier) end local function isGooBlock(blockName) return parseGooTier(blockName) ~= nil end local function isProcessBlock(blockName) return PROCESS_ITEMS[blockName] ~= nil end local function inspectGoo() local ok, inspected = turtle.inspectUp() if not ok then error("expected a Just Dire Things goo block above the turtle") end local tier = parseGooTier(inspected.name) if not tier then error("expected a Just Dire Things goo block above the turtle, got " .. tostring(inspected.name)) end return tier, inspected end local function findItemSlot(itemName) for slot = 1, 16 do local item = turtle.getItemDetail(slot) if item and item.name == itemName then return slot, item end end return nil end local function waitForInventory(message) if message then print(message) end os.pullEvent("turtle_inventory") end local function hasFreeSlot() for slot = 1, 16 do if turtle.getItemCount(slot) == 0 then return true end end return false end local function ensureMiningSpace() while not hasFreeSlot() do waitForInventory("Inventory is full. Remove items to keep mining.") end end local function getFuelLevel() local fuelLevel = turtle.getFuelLevel() if fuelLevel == "unlimited" then return nil end return fuelLevel end local function getRefuelTarget() local fuelLimit = turtle.getFuelLimit() if fuelLimit == "unlimited" or fuelLimit >= REFUEL_THRESHOLD then return REFUEL_THRESHOLD end return fuelLimit end local function findFuelSlot() for i = 1, #FUEL_ITEMS do local slot, item = findItemSlot(FUEL_ITEMS[i]) if slot then return slot, item end end return nil end local function refuelFromInventory() local fuelLevel = getFuelLevel() if not fuelLevel or fuelLevel >= getRefuelTarget() then return false end local previousSlot = turtle.getSelectedSlot() local consumedFuel = false while fuelLevel < getRefuelTarget() do local slot, item = findFuelSlot() if not slot then break end turtle.select(slot) if turtle.refuel(1) then consumedFuel = true print("Refueled with " .. item.name .. ". Fuel: " .. tostring(turtle.getFuelLevel())) fuelLevel = getFuelLevel() else print("Could not refuel with " .. item.name .. ".") break end end turtle.select(previousSlot) return consumedFuel end local function ensureStartFuel() while true do local fuelLevel = getFuelLevel() if not fuelLevel or fuelLevel >= MIN_START_FUEL then return end refuelFromInventory() fuelLevel = getFuelLevel() if not fuelLevel or fuelLevel >= MIN_START_FUEL then return end waitForInventory("Fuel is below " .. tostring(MIN_START_FUEL) .. ". Insert fuel to start.") end end local function ensureRuntimeFuel() local fuelLevel = getFuelLevel() if not fuelLevel then return end if fuelLevel < getRefuelTarget() then refuelFromInventory() end fuelLevel = getFuelLevel() while fuelLevel and fuelLevel < MIN_START_FUEL do waitForInventory("Fuel is below " .. tostring(MIN_START_FUEL) .. ". Insert fuel to continue.") refuelFromInventory() fuelLevel = getFuelLevel() end end local function findFeedingSlot(gooTier) local feedingItems = FEEDING_ITEMS_BY_TIER[gooTier] or {} for i = 1, #feedingItems do local slot, item = findItemSlot(feedingItems[i]) if slot then return slot, item end end return nil end local function findEligibleProcessSlot(gooTier) for slot = 1, 16 do local item = turtle.getItemDetail(slot) local processItem = item and PROCESS_ITEMS[item.name] if processItem then if processItem.tier <= gooTier then return slot, item, processItem end local logKey = item.name .. ":" .. tostring(gooTier) if not loggedBlockedItems[logKey] then print( item.name .. " requires goo tier " .. tostring(processItem.tier) .. ", current tier is " .. tostring(gooTier) ) loggedBlockedItems[logKey] = true end end end return nil end local function ensureGooAlive() while true do local gooTier, inspected = inspectGoo() if inspected.state and inspected.state.alive == true then return gooTier end local slot, item = findFeedingSlot(gooTier) if not slot then waitForInventory("Goo tier " .. tostring(gooTier) .. " is not alive. Waiting for feeding item...") else turtle.select(slot) print("Goo tier " .. tostring(gooTier) .. " is not alive. Feeding with " .. item.name .. "...") if not turtle.placeUp() then print("Could not feed the goo. Waiting before retry...") os.sleep(WAIT_SECONDS) else os.sleep(1) end end end end local function selectProcessItem(gooTier) local slot, item, processItem = findEligibleProcessSlot(gooTier) if not slot then return nil end turtle.select(slot) return item, processItem end local function workTarget(inspectFn, digFn, placeFn, gooTier, targetName) local ok, inspected = inspectFn() if ok then if isGooBlock(inspected.name) then return false end if isProcessBlock(inspected.name) then return false end print("Mining processed " .. targetName .. " block: " .. tostring(inspected.name)) ensureMiningSpace() return digFn() end local item = selectProcessItem(gooTier) if not item then return false end print("Placing " .. item.name .. " on goo " .. targetName) return placeFn() end local function workHorizontalSide(gooTier, sideIndex) if not turtle.forward() then print("Cannot move to horizontal side " .. tostring(sideIndex) .. ". Waiting...") return false end local changed = workTarget(turtle.inspectUp, turtle.digUp, turtle.placeUp, gooTier, "side") if not turtle.back() then error("could not return below the goo") end return changed end local function workHorizontalSides(gooTier) local changed = false for sideIndex = 1, 4 do changed = workHorizontalSide(gooTier, sideIndex) or changed turtle.turnRight() end return changed end -- Returns `changed, reachedTop`. `reachedTop` is true only once we have actually -- climbed above the goo and inspected its top face, so the caller can stop trying -- the remaining sides; it stays false when this side is blocked or unclimbable. local function workTopFromCurrentSide(gooTier) if not turtle.forward() then return false, false end -- Only the presence of a block above matters here; ignore the inspect metadata. local sideOccupied = turtle.inspectUp() local changed = false local reachedTop = false if not sideOccupied then if turtle.up() then if turtle.up() then turnAround() changed = workTarget(turtle.inspect, turtle.dig, turtle.place, gooTier, "top") reachedTop = true turnAround() if not turtle.down() then error("could not descend from top placement position") end else print("Cannot climb high enough to work on the top side.") end if not turtle.down() then error("could not return to side floor position") end else print("Cannot climb to work on the top side.") end end if not turtle.back() then error("could not return below the goo after top side check") end return changed, reachedTop end local function workTop(gooTier) local changed = false local done = false -- Iterate all four sides so the four turnRight calls net to zero (facing is -- restored), but skip the climb once the single top face has been handled. for _ = 1, 4 do if not done then local sideChanged, reachedTop = workTopFromCurrentSide(gooTier) changed = sideChanged or changed done = sideChanged or reachedTop end turtle.turnRight() end return changed end local function turnLeft(times) for _ = 1, times do turtle.turnLeft() end end local function moveToSideFacingCenter() for turns = 0, 3 do if turtle.forward() then turnAround() return turns end turtle.turnRight() end return nil end local function returnFromBottomSide(turns) if not turtle.forward() then return false end turnAround() turnLeft(turns) return true end local function workBottom() local gooTier = ensureGooAlive() local turns = moveToSideFacingCenter() if not turns then print("Cannot move to a side position for bottom placement. Waiting...") return false end local changed = false -- The bottom face is worked as one atomic round-trip (place -> wait -> mine -> -- return) rather than fire-and-forget like the other faces, because the turtle -- must vacate and then reclaim its home cell within a single pass. while true do local ok, inspected = turtle.inspect() if not ok then if changed then if returnFromBottomSide(turns) then return changed end print("Center position is clear, but the turtle could not return. Waiting...") os.sleep(WAIT_SECONDS) else local item = selectProcessItem(gooTier) if item then print("Placing " .. item.name .. " on goo bottom") if turtle.place() then changed = true else print("Could not place bottom block.") end elseif returnFromBottomSide(turns) then return changed else print("Center position is blocked. Waiting...") os.sleep(WAIT_SECONDS) end end elseif isProcessBlock(inspected.name) then print("Bottom block is still processing. Waiting outside...") os.sleep(WAIT_SECONDS) elseif isGooBlock(inspected.name) then error("unexpected goo block in the turtle center position") else print("Mining processed bottom block: " .. tostring(inspected.name)) ensureMiningSpace() if turtle.dig() then changed = true else print("Could not mine bottom block. Waiting...") os.sleep(WAIT_SECONDS) end end end end print("goo started. Place the turtle directly below the goo block.") if not hasPickaxeEquipped() then error("goo requires a turtle with a pickaxe equipped") end ensureStartFuel() while true do ensureRuntimeFuel() local gooTier = ensureGooAlive() -- Also relied on for its side effect: emits the "requires goo tier X" warnings -- for blocks the current tier cannot process yet. local hasEligibleBlocks = findEligibleProcessSlot(gooTier) ~= nil local changed = false changed = workTop(gooTier) or changed changed = workHorizontalSides(gooTier) or changed changed = workBottom() or changed if not changed then if hasEligibleBlocks then print("No free goo side found. Waiting for processing...") os.sleep(WAIT_SECONDS) else waitForInventory("No eligible process block found. Waiting for inventory...") end end end