minecraft-cc-tools/inferium-harvester.lua
2024-05-25 23:58:37 +02:00

580 lines
12 KiB
Lua

local net = require('libs/net')
local utils = require('libs/utils')
local inferium = require('libs/inferium')
local turtleUtils = require('libs/turtle-utils')
local VERSION = "4.1.0"
local INFERIUM_SERVER = 'inferium.com'
local IDLE_TIME = 2
local WAIT_ITEM_IDLE_TIME = 5
local NETWORK_TIMEOUT = 4
local MIN_INITIAL_FUEL_NEEDED = 16
local ADDITIONAL_FUEL_MARGIN = 100
local MAX_FERTILIZED_ESSENCE = 32
local MIN_FREE_SLOTS_BEFORE_COMPACT = 4
local TIME_TO_START = 3
-- a table with the list of current crops
local localPlan = nil
local previouslyFetchedRemoteConfig = nil
-- UTILS
local function removeFirst(t, x)
local res = {}
local removed = false
for k,v in pairs(t) do
if not removed and x == v then
removed = true
else
table.insert(res, v)
end
end
return res
end
local function getIndexedCount(t)
local res = {}
for k,v in pairs(t) do
local count = res[v] or 0
res[v] = count + 1
end
return res
end
local function getMinFuelNeeded(config)
return (ADDITIONAL_FUEL_MARGIN + config.firstCropZ + config.length) * 2
end
local function hasEnoughInitialFuel()
return turtle.getFuelLevel() >= MIN_INITIAL_FUEL_NEEDED
end
local function retrieveChestFuelOrientation()
local inventoryFound = false
for i=1, 4, 1 do
if turtleUtils.getInventory('front') then
inventoryFound = true
break
end
turtle.turnRight()
end
if not inventoryFound then
error('chest fuel not found')
end
end
local function getSeedNameFromCropName(cropName)
if not cropName then
return cropName
end
return string.gsub(cropName, 'crop', 'seeds')
end
-- Inventory utils
local function getItemSlot(inventory, itemName)
for slot, item in pairs(inventory.list()) do
if item and item.name == itemName then
return slot
end
end
end
local function getCountItem(inventory, itemName)
for _, item in pairs(inventory.list()) do
if item and item.name == itemName then
return item.count
end
end
end
-- Implementations
local function retrieveHomePositionProcedure()
print("> retrieving the home position")
if turtleUtils.getInventory('bottom') then
retrieveChestFuelOrientation()
return
end
turtle.turnRight()
if turtle.inspect() then
turtle.turnRight()
end
while true do
if turtleUtils.getInventory('bottom') then
turtle.turnLeft()
return
end
if turtle.inspect() then
break
else
turtle.forward()
end
end
turtle.turnLeft()
turtle.turnLeft()
while true do
if turtleUtils.getInventory('bottom') then
break
end
if turtle.inspect() then
error('Cannot retrieve home position')
else
turtle.forward()
end
end
turtle.turnLeft()
end
-- first parameter is config
local function dropAllProcedure(_)
print('> drop all')
for i=1, 16, 1 do
local count = turtle.getItemCount(i)
if count > 0 then
turtleUtils.waitForInventory('bottom', IDLE_TIME)
while turtle.getItemCount(i) and not turtleUtils.dropSlot(i, turtle.dropDown) do
os.sleep(IDLE_TIME)
end
end
end
end
local function refuelProcedure(config)
local fuelLevel = turtle.getFuelLevel()
print('> fuel: ' .. tostring(fuelLevel))
local minFuelNeeded = getMinFuelNeeded(config)
if fuelLevel < minFuelNeeded then
print('> refuel needed (minimum is ' .. minFuelNeeded .. ')')
local refuelOk, refuelErr = turtleUtils.refuelWithBuffer('bottom', 'front', minFuelNeeded, IDLE_TIME)
if not refuelOk then
error('Cannot refuel the turtle: "' .. refuelErr .. '"')
end
print('> fuel: ' .. tostring(fuelLevel))
end
end
local function goToHarvestPoint(config)
turtle.turnLeft()
for i=1, config.firstCropZ, 1 do
turtle.forward()
end
end
local function goBackToHome(config)
for i=1, config.firstCropZ, 1 do
turtle.forward()
end
turtle.turnLeft()
end
local function compactIfNeeded()
if turtleUtils.countFreeSlots() < MIN_FREE_SLOTS_BEFORE_COMPACT then
turtleUtils.compactInventory()
end
end
local function applyFertilizedEssenceIfAny()
if turtleUtils.selectItemByName(inferium.FERTILIZED_ESSENCE) then
while turtle.placeDown() do end
end
end
local function harvestDown(index, config)
if config.fertilizedBoost then
applyFertilizedEssenceIfAny()
end
local cropName = turtleUtils.waitForMatureCrop(turtle.inspectDown, IDLE_TIME)
local seedName = getSeedNameFromCropName(cropName)
localPlan[index] = seedName
if not seedName then
return false
end
if not turtle.digDown() then
error('turtle cannot harvest crop')
end
if not turtleUtils.selectItemByName(seedName) then
error('turtle cannot find any crop to place')
end
if not turtle.placeDown() then
error('turtle cannot place crop')
end
compactIfNeeded()
return true
end
local function forward()
local blocked = false
while not turtle.forward() do
if not blocked then
blocked = true
print('> turtle is blocked')
end
end
if blocked then
print('turtle is unblocked')
end
end
local function harvestProcedure(config)
goToHarvestPoint(config)
term.write('> harvest on ' .. tostring(config.length) .. ' blocks ')
local nbHarvested = 0
for i=1, config.length, 1 do
if harvestDown(i, config) then
nbHarvested = nbHarvested + 1
term.write('.')
else
term.write('!')
end
if i ~= config.length then
forward()
end
end
print()
print('> ' .. tostring(nbHarvested) .. ' harvested crops')
turtle.turnLeft()
turtle.turnLeft()
for i=1, config.length, 1 do
if i ~= config.length then
forward()
end
end
goBackToHome(config)
end
local function retrieveLocalPlan(config)
goToHarvestPoint(config)
print('> retrieve the local plan')
localPlan = {}
for i=1, config.length, 1 do
local ok, block = turtle.inspectDown()
local blockName = block and block.name
localPlan[i] = getSeedNameFromCropName(blockName)
if i ~= config.length then
forward()
end
end
turtle.turnLeft()
turtle.turnLeft()
for i=1, config.length, 1 do
if i ~= config.length then
forward()
end
end
goBackToHome(config)
end
local function fetchRemoteConfig(serverName)
print('> fetch remote config')
local message = { type = 'register-and-get-config' }
local lastErrMessage = nil
while true do
local replyMessage, errMessage = net.sendQuery(serverName, message, NETWORK_TIMEOUT)
if replyMessage and replyMessage.payload then
previouslyFetchedRemoteConfig = replyMessage.payload
print('> config fetched')
local payload = replyMessage.payload
if not payload then
error('cannot fetch the remote config')
end
if not payload.plan then
error('no "plan" property found in remote config')
end
return payload
elseif previouslyFetchedRemoteConfig then
print('> failed to fetch: ' .. tostring(errMessage))
print('> use the previous recorded remote config instead')
return previouslyFetchedRemoteConfig
elseif not replyMessage and errMessage then
if lastErrMessage ~= errMessage then
lastErrMessage = errMessage
print('> failed to fetch: ' .. tostring(errMessage))
end
else
error('unknown error during fetch')
end
os.sleep(IDLE_TIME)
end
end
local function removeSeeds(seeds, config)
goToHarvestPoint(config)
local nbOfSeedsToRemove = utils.sizeof(seeds)
print('> remove ' .. nbOfSeedsToRemove .. ' seed(s)')
local stateSeeds = seeds -- warning: do not mutate the data (only the ref)
local nbBlockTraveled = 0
for i=1, config.length, 1 do
local ok, block = turtle.inspectDown()
local blockName = block and block.name
local found = utils.find(stateSeeds, function(seedName) return seedName == getSeedNameFromCropName(blockName) end)
if found then
local digOk = turtle.digDown()
compactIfNeeded()
if not digOk then
error('cannot remove seed')
end
stateSeeds = removeFirst(stateSeeds, found)
nbOfSeedsToRemove = nbOfSeedsToRemove - 1
if nbOfSeedsToRemove < 1 then
break
end
end
if i ~= config.length then
forward()
nbBlockTraveled = nbBlockTraveled + 1
end
end
turtle.turnLeft()
turtle.turnLeft()
for i=1, nbBlockTraveled, 1 do
forward()
end
goBackToHome(config)
dropAllProcedure(config)
end
local function retrieveSeeds(seeds)
print('> retrieve seeds from storage')
local seedRetrieved = false
local seedsCount = getIndexedCount(seeds)
local storageInventory = turtleUtils.waitForInventory('bottom', WAIT_ITEM_IDLE_TIME)
local bufferInventory = turtleUtils.waitForInventory('front', WAIT_ITEM_IDLE_TIME)
for seedName, count in pairs(seedsCount) do
local slot = getItemSlot(storageInventory, seedName)
if slot and count > 0 then
local inventoryCount = getCountItem(storageInventory, seedName)
local realCount = math.min(count, inventoryCount)
local pushOk = storageInventory.pushItems(peripheral.getName(bufferInventory), slot, realCount)
if not pushOk then
error('retrieveSeeds error: cannot pushItems from storage to buffer')
end
local suckOk = turtle.suck()
if not suckOk then
error('retrieveSeeds error: cannot suck items from buffer')
end
seedRetrieved = true
end
end
return seedRetrieved
end
-- first parameter is config
local function retrieveFertilizedEssences(_)
local storageInventory = turtleUtils.waitForInventory('bottom', WAIT_ITEM_IDLE_TIME)
local bufferInventory = turtleUtils.waitForInventory('front', WAIT_ITEM_IDLE_TIME)
local slot = getItemSlot(storageInventory, inferium.FERTILIZED_ESSENCE)
if slot then
local inventoryCount = getCountItem(storageInventory, inferium.FERTILIZED_ESSENCE)
local realCount = math.min(MAX_FERTILIZED_ESSENCE, inventoryCount)
print('> retrieve ' .. tostring(realCount) .. ' fertilized essences from storage')
local pushOk = storageInventory.pushItems(peripheral.getName(bufferInventory), slot, realCount)
if not pushOk then
error('retrieveFertilizedEssences error: cannot pushItems from storage to buffer')
end
local suckOk = turtle.suck()
if not suckOk then
error('retrieveFertilizedEssences error: cannot suck items from buffer')
end
return true
end
return false
end
local function replantSeeds(config)
goToHarvestPoint(config)
print('> replant seeds')
local nbBlockTraveled = 0
for i=1, config.length, 1 do
local ok, block = turtle.inspectDown()
if not ok then
turtleUtils.selectItemBy(turtleUtils.isSeedInSlot)
turtle.placeDown()
if turtleUtils.isInventoryEmpty() then
break
end
end
if i ~= config.length then
forward()
nbBlockTraveled = nbBlockTraveled + 1
end
end
turtle.turnLeft()
turtle.turnLeft()
for i=1, nbBlockTraveled, 1 do
forward()
end
goBackToHome(config)
end
local function replantProcedure(config)
local remotePlan = config.plan
if localPlan == nil then
retrieveLocalPlan(config)
end
if localPlan == nil then
error('cannot retrieve the local plan')
end
local seedsToRemove = utils.shallowDiff(localPlan, remotePlan)
local seedsToPlant = utils.shallowDiff(remotePlan, localPlan)
if utils.sizeof(seedsToRemove) > 0 then
removeSeeds(seedsToRemove, config)
end
if utils.sizeof(seedsToPlant) > 0 then
if retrieveSeeds(seedsToPlant) then
replantSeeds(config)
end
end
-- reset the local plan (it will be re-updated during the harvest procedure)
localPlan = {}
end
local function resetTerminal()
term.clear()
term.setCursorPos(1, 1)
end
-- Main procedure
local function main()
net.openRednet()
print("Starting Trap's inferium harvester v" .. VERSION)
if not hasEnoughInitialFuel() then
turtleUtils.refuelAllFromInventory()
end
if not hasEnoughInitialFuel() then
error('Not enough fuel', 0)
end
print('Starting harvester in ' .. TIME_TO_START .. ' seconds...')
os.sleep(TIME_TO_START)
retrieveHomePositionProcedure()
local cycleNumber = 1
while true do
resetTerminal()
print('> Starting cycle ' .. tostring(cycleNumber))
print()
local remoteConfig = fetchRemoteConfig(inferium.SERVER)
dropAllProcedure(remoteConfig)
refuelProcedure(remoteConfig)
replantProcedure(remoteConfig)
if remoteConfig.fertilizedBoost then
print('> fertilized boost enabled')
retrieveFertilizedEssences(remoteConfig)
end
harvestProcedure(remoteConfig)
cycleNumber = cycleNumber + 1
end
net.closeRednet()
end
main()