cc-libs/programs/goo.lua
2026-05-31 02:43:19 +02:00

537 lines
12 KiB
Lua

local _VERSION = '0.1.2';
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 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));
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
local function workTopFromCurrentSide(gooTier)
if not turtle.forward() then
return false;
end
local sideOccupied = turtle.inspectUp();
local changed = false;
if not sideOccupied then
if turtle.up() then
if turtle.up() then
turnAround();
changed = workTarget(turtle.inspect, turtle.dig, turtle.place, gooTier, 'top');
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;
end
local function workTop(gooTier)
local changed = false;
for _ = 1, 4 do
if not changed then
changed = workTopFromCurrentSide(gooTier);
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;
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));
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();
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