580 lines
12 KiB
Lua
580 lines
12 KiB
Lua
local _VERSION = "0.2.1"
|
|
|
|
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 },
|
|
["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 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
|