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