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()