local _VERSION = "1.0.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 MIN_FEEDING_ITEMS = 4 local MAX_SUCK_ATTEMPTS = 16 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 }, ["minecraft:coal_block"] = { tier = 1 }, ["mekanism:block_charcoal"] = { tier = 1 }, ["justdirethings:coalblock_t1"] = { tier = 1 }, ["justdirethings:coalblock_t2"] = { tier = 2 }, ["justdirethings:coalblock_t3"] = { tier = 3 }, ["justdirethings:coalblock_t4"] = { tier = 4 }, ["minecraft:gold_block"] = { tier = 2 }, ["minecraft:diamond_block"] = { tier = 3 }, ["minecraft:netherite_block"] = { tier = 4 }, } local FEEDING_ITEMS_BY_TIER = { [1] = { "minecraft:sugar", "minecraft:rotten_flesh" }, [2] = { "minecraft:nether_wart" }, [3] = { "minecraft:chorus_fruit" }, [4] = { "minecraft:sculk" }, } local DIR_NORTH = 0 local DIR_EAST = 1 local DIR_SOUTH = 2 local DIR_WEST = 3 local DIRECTION_DELTAS = { [DIR_NORTH] = { x = 0, z = -1 }, [DIR_EAST] = { x = 1, z = 0 }, [DIR_SOUTH] = { x = 0, z = 1 }, [DIR_WEST] = { x = -1, z = 0 }, } local HOME = { x = -2, y = 0, z = 0, facing = DIR_EAST } local GOO_CHECK = { x = -1, y = 0, z = 0, facing = DIR_EAST } local GOO_CHECKS = { { name = "west goo check", x = -1, y = 0, z = 0, facing = DIR_EAST, action = "forward" }, { name = "east goo check", x = 1, y = 0, z = 0, facing = DIR_WEST, action = "forward" }, { name = "north goo check", x = 0, y = 0, z = -1, facing = DIR_SOUTH, action = "forward" }, { name = "south goo check", x = 0, y = 0, z = 1, facing = DIR_NORTH, action = "forward" }, { name = "top goo check", x = 0, y = 1, z = 0, facing = DIR_EAST, action = "down" }, } local TARGETS = { { name = "west side", x = -2, y = 0, z = 0, facing = DIR_EAST, action = "forward" }, { name = "east side", x = 2, y = 0, z = 0, facing = DIR_WEST, action = "forward" }, { name = "north side", x = 0, y = 0, z = -2, facing = DIR_SOUTH, action = "forward" }, { name = "south side", x = 0, y = 0, z = 2, facing = DIR_NORTH, action = "forward" }, { name = "top", x = -1, y = 1, z = 0, facing = DIR_EAST, action = "forward" }, } local position = { x = HOME.x, y = HOME.y, z = HOME.z } local facing = HOME.facing local loggedBlockedItems = {} local placedTargets = {} local lastGooTier = nil 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 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 isFuelItem(itemName) for i = 1, #FUEL_ITEMS do if FUEL_ITEMS[i] == itemName then return true end end return false end local function isFeedingItemForTier(itemName, gooTier) local feedingItems = FEEDING_ITEMS_BY_TIER[gooTier] or {} for i = 1, #feedingItems do if feedingItems[i] == itemName then return true end end return false end local function countItem(itemName) local count = 0 for slot = 1, 16 do local item = turtle.getItemDetail(slot) if item and item.name == itemName then count = count + item.count end end return count end local function countFeedingItems(gooTier) local count = 0 local feedingItems = FEEDING_ITEMS_BY_TIER[gooTier] or {} for i = 1, #feedingItems do count = count + countItem(feedingItems[i]) end return count 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 hasFreeSlot() for slot = 1, 16 do if turtle.getItemCount(slot) == 0 then return true end end return false 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 turnRight() turtle.turnRight() facing = (facing + 1) % 4 end local function turnLeft() turtle.turnLeft() facing = (facing + 3) % 4 end local function turnTo(direction) while facing ~= direction do local rightTurns = (direction - facing) % 4 if rightTurns == 3 then turnLeft() else turnRight() end end end local function moveForward() if not turtle.forward() then return false end local delta = DIRECTION_DELTAS[facing] position.x = position.x + delta.x position.z = position.z + delta.z return true end local function moveUp() if not turtle.up() then return false end position.y = position.y + 1 return true end local function moveDown() if not turtle.down() then return false end position.y = position.y - 1 return true end local function moveAxis(target, positiveDirection, negativeDirection, axis) while position[axis] ~= target do if position[axis] < target then turnTo(positiveDirection) else turnTo(negativeDirection) end if not moveForward() then return false end end return true end local function goTo(target) while position.y < target.y do if not moveUp() then return false end end while position.y > target.y do if not moveDown() then return false end end if not moveAxis(target.x, DIR_EAST, DIR_WEST, "x") then return false end if not moveAxis(target.z, DIR_SOUTH, DIR_NORTH, "z") then return false end if target.facing then turnTo(target.facing) end return true end local function mustGoTo(target, description) while not goTo(target) do print("Cannot reach " .. description .. ". Clear the path and waiting...") os.sleep(WAIT_SECONDS) end end local function goHome() mustGoTo(HOME, "home/provider") end local function goToGooCheck() mustGoTo(GOO_CHECK, "goo check position") end local function inspectDirection(action) if action == "forward" then return turtle.inspect() elseif action == "up" then return turtle.inspectUp() elseif action == "down" then return turtle.inspectDown() end error("unknown inspect action " .. tostring(action)) end local function placeDirection(action) if action == "forward" then return turtle.place() elseif action == "up" then return turtle.placeUp() elseif action == "down" then return turtle.placeDown() end error("unknown place action " .. tostring(action)) 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 isProviderBelow() local ok = turtle.inspectDown() return ok end local function pullFromProvider() goHome() local pulled = false for _ = 1, MAX_SUCK_ATTEMPTS do if not hasFreeSlot() then break end if turtle.suckDown() then pulled = true else break end end return pulled end local function shouldKeepItem(item, gooTier) if isFuelItem(item.name) then return true end local processItem = PROCESS_ITEMS[item.name] if processItem and processItem.tier <= gooTier then return true end if isFeedingItemForTier(item.name, gooTier) then return countFeedingItems(gooTier) <= MIN_FEEDING_ITEMS end return false end local function depositOutputs(gooTier) goHome() local previousSlot = turtle.getSelectedSlot() for slot = 1, 16 do local item = turtle.getItemDetail(slot) if item and not shouldKeepItem(item, gooTier) then turtle.select(slot) if turtle.dropDown() then print("Deposited " .. item.name .. ".") else print("Could not deposit " .. item.name .. ". Provider may be full.") break end end end turtle.select(previousSlot) end local function waitForProvider(message) if message then print(message) end repeat os.sleep(WAIT_SECONDS) until pullFromProvider() 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 waitForProvider("Fuel is below " .. tostring(MIN_START_FUEL) .. ". Add fuel to the provider below home.") 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 waitForProvider("Fuel is below " .. tostring(MIN_START_FUEL) .. ". Add fuel to the provider below home.") 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 inspectGoo() for i = 1, #GOO_CHECKS do local check = GOO_CHECKS[i] if goTo(check) then local ok, inspected = inspectDirection(check.action) if ok then local tier = parseGooTier(inspected.name) if tier then lastGooTier = tier return tier, inspected, check end end end end if lastGooTier then print("Cannot reach the goo to inspect it. Using last known tier " .. tostring(lastGooTier) .. ".") return lastGooTier, { state = { alive = true } }, nil end error("expected a reachable Just Dire Things goo block around the turtle") end local function ensureGooAlive() while true do local gooTier, inspected, check = inspectGoo() if not check or inspected.state and inspected.state.alive == true then return gooTier end local slot, item = findFeedingSlot(gooTier) if not slot then waitForProvider("Goo tier " .. tostring(gooTier) .. " is not alive. Add feeding items to the provider.") else turtle.select(slot) print("Goo tier " .. tostring(gooTier) .. " is not alive. Feeding with " .. item.name .. "...") if not placeDirection(check.action) 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 = findEligibleProcessSlot(gooTier) if not slot then return nil end turtle.select(slot) return item end local function inspectTarget(target) if target.action == "forward" then return turtle.inspect() elseif target.action == "up" then return turtle.inspectUp() elseif target.action == "down" then return turtle.inspectDown() end error("unknown target action " .. tostring(target.action)) end local function digTarget(target) if target.action == "forward" then return turtle.dig() elseif target.action == "up" then return turtle.digUp() elseif target.action == "down" then return turtle.digDown() end error("unknown target action " .. tostring(target.action)) end local function placeTarget(target) if target.action == "forward" then return turtle.place() elseif target.action == "up" then return turtle.placeUp() elseif target.action == "down" then return turtle.placeDown() end error("unknown target action " .. tostring(target.action)) end local function ensureMiningSpace(gooTier) while not hasFreeSlot() do depositOutputs(gooTier) if not hasFreeSlot() then print("Inventory is full and provider may be full. Waiting...") os.sleep(WAIT_SECONDS) end end end local function isTargetQueued(target) for i = 1, #placedTargets do if placedTargets[i] == target then return true end end return false end local function removeQueuedTarget(index) table.remove(placedTargets, index) end local function goToTarget(target) mustGoTo(target, target.name) end local function mineQueuedTarget(gooTier) if #placedTargets == 0 then return false end local target = placedTargets[1] goToTarget(target) ensureGooAlive() goToTarget(target) local ok, inspected = inspectTarget(target) if not ok then print("Queued " .. target.name .. " is empty.") removeQueuedTarget(1) return true end if isProcessBlock(inspected.name) then return false end if isGooBlock(inspected.name) then removeQueuedTarget(1) return true end print("Mining processed " .. target.name .. " block: " .. tostring(inspected.name)) ensureMiningSpace(gooTier) goToTarget(target) if digTarget(target) then removeQueuedTarget(1) return true end print("Could not mine " .. target.name .. ".") return false end local function placeAvailableTarget(gooTier) for i = 1, #TARGETS do local target = TARGETS[i] if not isTargetQueued(target) then goToTarget(target) ensureGooAlive() goToTarget(target) local ok, inspected = inspectTarget(target) if ok then if not isProcessBlock(inspected.name) and not isGooBlock(inspected.name) then print("Mining existing " .. target.name .. " block: " .. tostring(inspected.name)) ensureMiningSpace(gooTier) goToTarget(target) if digTarget(target) then return true end end else local item = selectProcessItem(gooTier) if not item then return false end print("Placing " .. item.name .. " on goo " .. target.name) if placeTarget(target) then placedTargets[#placedTargets + 1] = target return true end print("Could not place " .. target.name .. ".") end end end return false end local function waitAtOldestTarget() if #placedTargets == 0 then goToGooCheck() else goToTarget(placedTargets[1]) end print("Waiting for goo processing...") os.sleep(WAIT_SECONDS) end local function startup() print("goo started. Layout: provider below turtle, goo two blocks ahead, turtle facing goo.") if not hasPickaxeEquipped() then error("goo requires a turtle with a pickaxe equipped") end if not isProviderBelow() then error("goo requires a provider/deposit barrel below the turtle") end pullFromProvider() ensureStartFuel() end startup() while true do ensureRuntimeFuel() local gooTier = ensureGooAlive() depositOutputs(gooTier) pullFromProvider() ensureRuntimeFuel() gooTier = ensureGooAlive() local changed = mineQueuedTarget(gooTier) if not changed then changed = placeAvailableTarget(gooTier) end if not changed then if findEligibleProcessSlot(gooTier) then waitAtOldestTarget() else waitForProvider("No eligible process block found. Add inputs to the provider below home.") end end end