feat(goo): rewrite automation flow

This commit is contained in:
Guillaume ARM 2026-06-01 20:50:05 +02:00
parent 7f6bd477d4
commit 9dd4455203

View File

@ -1,4 +1,4 @@
local _VERSION = "0.2.1"
local _VERSION = "1.0.0"
local args = table.pack(...)
local command = args[1]
@ -7,6 +7,8 @@ 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",
@ -36,7 +38,42 @@ local FEEDING_ITEMS_BY_TIER = {
[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)
@ -82,11 +119,6 @@ local function hasPickaxeEquipped()
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+)$")
@ -101,20 +133,51 @@ 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")
local function isFuelItem(itemName)
for i = 1, #FUEL_ITEMS do
if FUEL_ITEMS[i] == itemName then
return true
end
end
local tier = parseGooTier(inspected.name)
return false
end
if not tier then
error("expected a Just Dire Things goo block above the turtle, got " .. tostring(inspected.name))
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 tier, inspected
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)
@ -129,14 +192,6 @@ local function findItemSlot(itemName)
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
@ -147,12 +202,6 @@ local function hasFreeSlot()
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()
@ -185,6 +234,143 @@ local function findFuelSlot()
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()
@ -219,6 +405,83 @@ local function refuelFromInventory()
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()
@ -234,7 +497,7 @@ local function ensureStartFuel()
return
end
waitForInventory("Fuel is below " .. tostring(MIN_START_FUEL) .. ". Insert fuel to start.")
waitForProvider("Fuel is below " .. tostring(MIN_START_FUEL) .. ". Add fuel to the provider below home.")
end
end
@ -252,7 +515,7 @@ local function ensureRuntimeFuel()
fuelLevel = getFuelLevel()
while fuelLevel and fuelLevel < MIN_START_FUEL do
waitForInventory("Fuel is below " .. tostring(MIN_START_FUEL) .. ". Insert fuel to continue.")
waitForProvider("Fuel is below " .. tostring(MIN_START_FUEL) .. ". Add fuel to the provider below home.")
refuelFromInventory()
fuelLevel = getFuelLevel()
end
@ -300,23 +563,51 @@ local function findEligibleProcessSlot(gooTier)
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 = inspectGoo()
local gooTier, inspected, check = inspectGoo()
if inspected.state and inspected.state.alive == true then
if not check or 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...")
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 turtle.placeUp() then
if not placeDirection(check.action) then
print("Could not feed the goo. Waiting before retry...")
os.sleep(WAIT_SECONDS)
else
@ -327,7 +618,7 @@ local function ensureGooAlive()
end
local function selectProcessItem(gooTier)
local slot, item, processItem = findEligibleProcessSlot(gooTier)
local slot, item = findEligibleProcessSlot(gooTier)
if not slot then
return nil
@ -335,245 +626,207 @@ local function selectProcessItem(gooTier)
turtle.select(slot)
return item, processItem
return item
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()
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
local item = selectProcessItem(gooTier)
error("unknown target action " .. tostring(target.action))
end
if not item then
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
print("Placing " .. item.name .. " on goo " .. targetName)
return placeFn()
end
local target = placedTargets[1]
local function workHorizontalSide(gooTier, sideIndex)
if not turtle.forward() then
print("Cannot move to horizontal side " .. tostring(sideIndex) .. ". Waiting...")
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
local changed = workTarget(turtle.inspectUp, turtle.digUp, turtle.placeUp, gooTier, "side")
if not turtle.back() then
error("could not return below the goo")
if isGooBlock(inspected.name) then
removeQueuedTarget(1)
return true
end
return changed
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 workHorizontalSides(gooTier)
local changed = false
local function placeAvailableTarget(gooTier)
for i = 1, #TARGETS do
local target = TARGETS[i]
for sideIndex = 1, 4 do
changed = workHorizontalSide(gooTier, sideIndex) or changed
turtle.turnRight()
end
if not isTargetQueued(target) then
goToTarget(target)
ensureGooAlive()
goToTarget(target)
return changed
end
local ok, inspected = inspectTarget(target)
-- 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
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)
-- 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")
if digTarget(target) then
return true
end
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)
if not item then
return false
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)
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
print("goo started. Place the turtle directly below the goo block.")
local function waitAtOldestTarget()
if #placedTargets == 0 then
goToGooCheck()
else
goToTarget(placedTargets[1])
end
if not hasPickaxeEquipped() then
error("goo requires a turtle with a pickaxe equipped")
print("Waiting for goo processing...")
os.sleep(WAIT_SECONDS)
end
ensureStartFuel()
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()
-- 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
depositOutputs(gooTier)
pullFromProvider()
ensureRuntimeFuel()
local changed = false
changed = workTop(gooTier) or changed
changed = workHorizontalSides(gooTier) or changed
changed = workBottom() or changed
gooTier = ensureGooAlive()
local changed = mineQueuedTarget(gooTier)
if not changed then
if hasEligibleBlocks then
print("No free goo side found. Waiting for processing...")
os.sleep(WAIT_SECONDS)
changed = placeAvailableTarget(gooTier)
end
if not changed then
if findEligibleProcessSlot(gooTier) then
waitAtOldestTarget()
else
waitForInventory("No eligible process block found. Waiting for inventory...")
waitForProvider("No eligible process block found. Add inputs to the provider below home.")
end
end
end