local utils = require('libs/utils') local turtleUtils = require('libs/turtle-utils') local config = require('config/harvesting') local VERSION = "0.6.0" local IDLE_TIME = 2 local WAIT_ITEM_IDLE_TIME = 5 local MIN_FUEL_NEEDED = (100 + config.firstCropZ + config.length) * 2 local MIN_FREE_SLOTS_BEFORE_COMPACT = 4 local INFERIUM_QUERY_PORT = 111 local INFERIUM_REPLY_PORT = 112 -- a table with the list of current crops local localPlan = 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 assertEnoughFuel() if turtle.getFuelLevel() < MIN_FUEL_NEEDED then error('Not enough fuel') end 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 function isSeed(item) local tags = item and item.tags or {} return tags['forge:seeds'] or tags['mysticalagriculture:seeds'] or false end -- Inventory utils -- the slot of the item is returned local function waitForItem(inventory, itemName, count, sleepTime) sleepTime = sleepTime or 5 -- TODO: DEFAULT_IDLE_TIME while true do for slot, item in pairs(inventory.list()) do if item.name == itemName and item.count >= count then return slot end end os.sleep(IDLE_TIME) end end -- Implementations local function retrieveHomePositionProcedure() 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() 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 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 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() end local function forward() if not turtle.forward() then error('turtle is blocked') end end local function harvestProcedure() 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() 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 -- TODO: fetch timeout ? local function fetchRemotePlan() local fallbackPlan = config.defaultRemotePlan or {} local modem = peripheral.find("modem") if not modem then return fallbackPlan end modem.open(INFERIUM_REPLY_PORT) local message = { type = 'getplan', payload = { computerId = os.getComputerID() } } modem.transmit(INFERIUM_QUERY_PORT, INFERIUM_REPLY_PORT, textutils.serialize(message)) local channel, replyRawMessage repeat _, _, channel, _, replyRawMessage = os.pullEvent("modem_message") until channel == INFERIUM_REPLY_PORT modem.close(INFERIUM_REPLY_PORT) if not replyRawMessage then return fallbackPlan end local replyMessage = textutils.unserialize(replyRawMessage) if replyMessage and replyMessage.payload then return replyMessage.payload end return fallbackPlan end local function removeSeeds(seeds) goToHarvestPoint() local stateSeeds = seeds 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) 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 = waitForItem(storageInventory, seedName, count, WAIT_ITEM_IDLE_TIME) local pushOk = storageInventory.pushItems(peripheral.getName(bufferInventory), slot, count) 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 end end local function replantSeeds() goToHarvestPoint() 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 retrieveSeeds(seedsToPlant) replantSeeds() end -- reset the local plan (it will be re-updated during the harvest procedure) localPlan = {} end -- Main procedure local function main() print("Starting Trap's inferium harvester v" .. VERSION) turtleUtils.refuelAllFromInventory() assertEnoughFuel() print("> retrieving home position") retrieveHomePositionProcedure() print("> start the harvesting process") while true do dropAllProcedure() refuelProcedure() replantProcedure() goToHarvestPoint() harvestProcedure() goBackToHome() end end main()