550 lines
12 KiB
Lua
550 lines
12 KiB
Lua
local net = require('libs/net')
|
|
local utils = require('libs/utils')
|
|
local turtleUtils = require('libs/turtle-utils')
|
|
local config = require('config/harvesting')
|
|
|
|
local VERSION = "2.6.0"
|
|
local INFERIUM_SERVER = 'inferium.com'
|
|
local IDLE_TIME = 2
|
|
local WAIT_ITEM_IDLE_TIME = 5
|
|
local NETWORK_TIMEOUT = 4
|
|
local MIN_FUEL_NEEDED = (100 + config.firstCropZ + config.length) * 2
|
|
local MAX_FERTILIZED_ESSENCE = 32
|
|
local MIN_FREE_SLOTS_BEFORE_COMPACT = 4
|
|
|
|
local FERTILIZED_ESSENCE = 'mysticalagriculture:fertilized_essence'
|
|
|
|
-- a table with the list of current crops
|
|
local localPlan = nil
|
|
|
|
local previouslyFetchedRemotePlan = 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 hasEnoughFuel()
|
|
return turtle.getFuelLevel() >= MIN_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
|
|
|
|
local function isSeed(item)
|
|
local tags = item and item.tags or {}
|
|
return tags['forge:seeds'] or tags['mysticalagriculture:seeds'] or false
|
|
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
|
|
|
|
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 not turtleUtils.dropSlot(i, turtle.dropDown) do
|
|
os.sleep(IDLE_TIME)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function refuelProcedure()
|
|
local fuelLevel = turtle.getFuelLevel()
|
|
print('> fuel: ' .. tostring(fuelLevel))
|
|
|
|
if fuelLevel < MIN_FUEL_NEEDED then
|
|
print('> refuel needed (minimum is ' .. MIN_FUEL_NEEDED .. ')')
|
|
|
|
local refuelOk, refuelErr = turtleUtils.refuelWithBuffer('bottom', 'front', MIN_FUEL_NEEDED, IDLE_TIME)
|
|
|
|
if not refuelOk then
|
|
error('Cannot refuel the turtle: "' .. refuelErr .. '"')
|
|
end
|
|
|
|
print('> fuel: ' .. tostring(fuelLevel))
|
|
end
|
|
end
|
|
|
|
local function goToHarvestPoint()
|
|
turtle.turnLeft()
|
|
|
|
for i=1, config.firstCropZ, 1 do
|
|
turtle.forward()
|
|
end
|
|
end
|
|
|
|
local function goBackToHome()
|
|
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(FERTILIZED_ESSENCE) then
|
|
while turtle.placeDown() do end
|
|
end
|
|
end
|
|
|
|
local function harvestDown(index)
|
|
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()
|
|
if not turtle.forward() then
|
|
error('turtle is blocked')
|
|
end
|
|
end
|
|
|
|
local function harvestProcedure()
|
|
goToHarvestPoint()
|
|
|
|
term.write('> harvest on ' .. tostring(config.length) .. ' blocks ')
|
|
local nbHarvested = 0
|
|
for i=1, config.length, 1 do
|
|
if harvestDown(i) 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()
|
|
end
|
|
|
|
local function retrieveLocalPlan()
|
|
goToHarvestPoint()
|
|
|
|
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()
|
|
end
|
|
|
|
local function fetchRemotePlan()
|
|
print('> fetch remote plan')
|
|
local message = { type = 'getplan' }
|
|
|
|
local lastErrMessage = nil
|
|
|
|
while true do
|
|
local replyMessage, errMessage = net.sendQuery(INFERIUM_SERVER, message, NETWORK_TIMEOUT)
|
|
if replyMessage and replyMessage.payload then
|
|
previouslyFetchedRemotePlan = replyMessage.payload
|
|
print('> plan fetched')
|
|
return replyMessage.payload
|
|
elseif previouslyFetchedRemotePlan then
|
|
print('> failed to fetch: ' .. tostring(errMessage))
|
|
print('> use the previous recorded remote plan instead')
|
|
return previouslyFetchedRemotePlan
|
|
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)
|
|
goToHarvestPoint()
|
|
|
|
print('> remove ' .. utils.sizeof(seeds) .. ' seed(s)')
|
|
local stateSeeds = seeds -- warning: do not mutate the data (only the ref)
|
|
|
|
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)
|
|
end
|
|
|
|
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()
|
|
dropAllProcedure()
|
|
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
|
|
|
|
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, FERTILIZED_ESSENCE)
|
|
|
|
if slot then
|
|
local inventoryCount = getCountItem(storageInventory, 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()
|
|
goToHarvestPoint()
|
|
|
|
print('> replant seeds')
|
|
|
|
for i=1, config.length, 1 do
|
|
local ok, block = turtle.inspectDown()
|
|
|
|
if not ok then
|
|
turtleUtils.selectItemBy(function(slot)
|
|
local item = turtle.getItemDetail(slot, true)
|
|
return isSeed(item)
|
|
end)
|
|
|
|
turtle.placeDown()
|
|
end
|
|
|
|
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()
|
|
end
|
|
|
|
local function replantProcedure()
|
|
local remotePlan = fetchRemotePlan()
|
|
|
|
if remotePlan == nil then
|
|
error('cannot fetch the remote plan')
|
|
end
|
|
|
|
if localPlan == nil then
|
|
retrieveLocalPlan()
|
|
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)
|
|
end
|
|
|
|
if utils.sizeof(seedsToPlant) > 0 then
|
|
if retrieveSeeds(seedsToPlant) then
|
|
replantSeeds()
|
|
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 hasEnoughFuel() then
|
|
turtleUtils.refuelAllFromInventory()
|
|
end
|
|
|
|
if not hasEnoughFuel() then
|
|
error('Not enough fuel', 0)
|
|
end
|
|
|
|
retrieveHomePositionProcedure()
|
|
|
|
local cycleNumber = 1
|
|
|
|
while true do
|
|
resetTerminal()
|
|
print('> Starting cycle ' .. tostring(cycleNumber))
|
|
print()
|
|
|
|
dropAllProcedure()
|
|
refuelProcedure()
|
|
|
|
replantProcedure()
|
|
|
|
if config.fertilizedBoost then
|
|
print('> fertilized boost enabled')
|
|
retrieveFertilizedEssences()
|
|
end
|
|
|
|
harvestProcedure()
|
|
|
|
cycleNumber = cycleNumber + 1
|
|
end
|
|
|
|
net.closeRednet()
|
|
end
|
|
|
|
main() |