cc-libs/programs/goo.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