local net = require('libs/net') local utils = require('libs/utils') local turtleUtils = require('libs/turtle-utils') local config = require('config/harvesting') local VERSION = "2.2.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 MIN_FREE_SLOTS_BEFORE_COMPACT = 4 -- 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() print('> refuel') local refuelOk, refuelErr = turtleUtils.refuelWithBuffer('bottom', 'front', MIN_FUEL_NEEDED, IDLE_TIME) if not refuelOk then error('Cannot refuel the turtle: "' .. refuelErr .. '"') 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 harvestDown(index) 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() print('> harvest') for i=1, config.length, 1 do harvestDown(i) 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 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' } while true do local replyMessage, _ = net.sendQuery(INFERIUM_SERVER, message, NETWORK_TIMEOUT) print('> fetched') if replyMessage and replyMessage.payload then previouslyFetchedRemotePlan = replyMessage.payload return replyMessage.payload elseif previouslyFetchedRemotePlan then print('> failed to fetch remote plan, use the previous one instead') return previouslyFetchedRemotePlan end os.sleep(IDLE_TIME) end end local function removeSeeds(seeds) goToHarvestPoint() print('> remove old seeds') 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 inventory') 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 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() if localPlan == nil then retrieveLocalPlan() end if localPlan == nil then error('cannot retrieve the local plan') end local remotePlan = fetchRemotePlan() if remotePlan == nil then error('cannot fetch the remote 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() goToHarvestPoint() harvestProcedure() goBackToHome() cycleNumber = cycleNumber + 1 end net.closeRednet() end main()