JackMacWindows

ComputerCraft Automatic Turtle Farm

May 3rd, 2022 (edited)
14,315
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 17.49 KB | None | 0 0
  1. -- Turtle farm by JackMacWindows
  2. --
  3. -- This is a simple farming script for ComputerCraft turtles. To use, simply
  4. -- place a tilling turtle on top of a farming region, place a wired modem
  5. -- connected to a chest next to the turtle, and run this script.
  6. --
  7. -- Features:
  8. -- * Fully automatic field tending
  9. -- * Automatic tilling and planting to reduce setup time
  10. -- * Zero configuration to start a basic farm
  11. -- * Boundaries are automatically detected, so no need to calculate size
  12. -- * Non-rectangular and non-flat fields supported
  13. -- * Recovery after being unloaded
  14. -- * Automatic unloading and refueling from one or more chests
  15. --
  16. -- To create a farm, create a complete boundary around the dirt or grass area
  17. -- that you want the farm to be inside. Then add water to ensure the field stays
  18. -- fully watered. The field may be any height - the turtle will automatically
  19. -- move up or down to continue farming. The field may also be non-rectangular,
  20. -- but it will not detect single holes in a straight line going across the field.
  21. -- (e.g. if a boundary is at (100, 0) to (100, 100), the boundary may not have a
  22. -- hole taken out at (100, 25) to (100, 50).)
  23. --
  24. -- The turtle dispenses items when it reaches the origin point, which is the
  25. -- place where the turtle was when the farm was started. This point must have a
  26. -- modem next to it, with one or more chests placed next to that modem. The
  27. -- program will prompt you to set this up if not present. (Make sure to right-
  28. -- click the modem to turn it red and enable it.) Whenever the turtle returns to
  29. -- this point, it will dispense all items except one stack of seeds and one stack
  30. -- of fuel. If either of these stacks are not present, it will pick them up from
  31. -- the chests.
  32. --
  33. -- Farms may have multiple different types of crops, and the turtle will attempt
  34. -- to replace them with the same type of seed. However, these will have to be
  35. -- planted beforehand - when planting the first crops, it will use whatever
  36. -- seeds are found in the chest or turtle first.
  37. --
  38. -- If you'd like to add custom modded crops, scroll down to the "add your own
  39. -- here" sections, and fill out the templates for the blocks and items you want.
  40. --
  41. -- If you need any help, you may ask on the ComputerCraft Discord server at
  42. -- https://discord.computercraft.cc.
  43.  
  44. -- MIT License
  45. --
  46. -- Copyright (c) 2022 JackMacWindows
  47. --
  48. -- Permission is hereby granted, free of charge, to any person obtaining a copy
  49. -- of this software and associated documentation files (the "Software"), to deal
  50. -- in the Software without restriction, including without limitation the rights
  51. -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  52. -- copies of the Software, and to permit persons to whom the Software is
  53. -- furnished to do so, subject to the following conditions:
  54. --
  55. -- The above copyright notice and this permission notice shall be included in all
  56. -- copies or substantial portions of the Software.
  57. --
  58. -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  59. -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  60. -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  61. -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  62. -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  63. -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  64. -- SOFTWARE.
  65.  
  66. local x, y, z = 0, 0, 0
  67. local direction = 0
  68. local invertDirection = false
  69.  
  70. -- Ground blocks that are part of the farm
  71. local groundBlocks = {
  72.     ["minecraft:dirt"] = true,
  73.     ["minecraft:grass_block"] = true,
  74.     ["minecraft:farmland"] = true,
  75.     ["minecraft:water"] = true,
  76.     ["minecraft:flowing_water"] = true,
  77.     -- add your own here:
  78.     --["<yourmod>:<block>"] = true,
  79. }
  80.  
  81. -- Blocks that are crops, with their maximum ages
  82. local cropBlocks = {
  83.     ["minecraft:wheat"] = 7,
  84.     ["minecraft:carrots"] = 7,
  85.     ["minecraft:potatoes"] = 7,
  86.     ["minecraft:beetroots"] = 3,
  87.     -- add your own here:
  88.     --["<yourmod>:<block>"] = <maximum age>,
  89. }
  90.  
  91. -- Mappings of crop blocks to seed items
  92. local seeds = {
  93.     ["minecraft:wheat"] = "minecraft:wheat_seeds",
  94.     ["minecraft:carrots"] = "minecraft:carrot",
  95.     ["minecraft:potatoes"] = "minecraft:potato",
  96.     ["minecraft:beetroots"] = "minecraft:beetroot_seeds",
  97.     -- add your own here:
  98.     --["<yourmod>:<block>"] = "<yourmod>:<seed>",
  99. }
  100.  
  101. -- Fuel types to pull from a chest if no fuel is in the inventory
  102. local fuels = {
  103.     ["minecraft:coal"] = true,
  104.     ["minecraft:charcoal"] = true,
  105.     ["minecraft:lava_bucket"] = true,
  106.     -- add your own here:
  107.     --["<yourmod>:<item>"] = true,
  108. }
  109.  
  110. local seedItems = {}
  111. for k, v in pairs(seeds) do seedItems[v] = k end
  112.  
  113. local function writePos()
  114.     local file = fs.open("jackmacwindows.farm-state.txt", "w")
  115.     file.writeLine(x)
  116.     file.writeLine(y)
  117.     file.writeLine(z)
  118.     file.writeLine(direction)
  119.     file.writeLine(invertDirection and "true" or "false")
  120.     file.close()
  121. end
  122.  
  123. local function refuel()
  124.     if turtle.getFuelLevel() == "unlimited" or turtle.getFuelLevel() == turtle.getFuelLimit() then return end
  125.     for i = 1, 16 do
  126.         if turtle.getItemCount(i) > 0 then
  127.             turtle.select(i)
  128.             turtle.refuel(turtle.getItemCount() - 1)
  129.             if turtle.getFuelLevel() == turtle.getFuelLimit() then return true end
  130.         end
  131.     end
  132.     if turtle.getFuelLevel() > 0 then return true
  133.     else return false, "Out of fuel" end
  134. end
  135.  
  136. local function forward()
  137.     local ok, err = turtle.forward()
  138.     if ok then
  139.         if direction == 0 then x = x + 1
  140.         elseif direction == 1 then z = z + 1
  141.         elseif direction == 2 then x = x - 1
  142.         else z = z - 1 end
  143.         writePos()
  144.         return true
  145.     elseif err:match "[Ff]uel" then
  146.         ok, err = refuel()
  147.         if ok then return forward()
  148.         else return ok, err end
  149.     else return false, err end
  150. end
  151.  
  152. local function back()
  153.     local ok, err = turtle.back()
  154.     if ok then
  155.         if direction == 0 then x = x - 1
  156.         elseif direction == 1 then z = z - 1
  157.         elseif direction == 2 then x = x + 1
  158.         else z = z + 1 end
  159.         writePos()
  160.         return true
  161.     elseif err:match "[Ff]uel" then
  162.         ok, err = refuel()
  163.         if ok then return forward()
  164.         else return ok, err end
  165.     else return false, err end
  166. end
  167.  
  168. local function up()
  169.     local ok, err = turtle.up()
  170.     if ok then
  171.         y = y + 1
  172.         writePos()
  173.         return true
  174.     elseif err:match "[Ff]uel" then
  175.         ok, err = refuel()
  176.         if ok then return forward()
  177.         else return ok, err end
  178.     else return false, err end
  179. end
  180.  
  181. local function down()
  182.     local ok, err = turtle.down()
  183.     if ok then
  184.         y = y - 1
  185.         writePos()
  186.         return true
  187.     elseif err:match "[Ff]uel" then
  188.         ok, err = refuel()
  189.         if ok then return forward()
  190.         else return ok, err end
  191.     else return false, err end
  192. end
  193.  
  194. local function left()
  195.     local ok, err = turtle.turnLeft()
  196.     if ok then
  197.         direction = (direction - 1) % 4
  198.         writePos()
  199.         return true
  200.     else return false, err end
  201. end
  202.  
  203. local function right()
  204.     local ok, err = turtle.turnRight()
  205.     if ok then
  206.         direction = (direction + 1) % 4
  207.         writePos()
  208.         return true
  209.     else return false, err end
  210. end
  211.  
  212. local function panic(msg)
  213.     term.clear()
  214.     term.setCursorPos(1, 1)
  215.     term.setTextColor(colors.red)
  216.     print("An unrecoverable error occured while farming:", msg, "\nPlease hold Ctrl+T to stop the program, then solve the issue described above, run 'rm jackmacwindows.farm-state.txt', and return the turtle to the start position. Don't forget to label the turtle before breaking it.")
  217.     if peripheral.find("modem") then
  218.         peripheral.find("modem", rednet.open)
  219.         rednet.broadcast(msg, "jackmacwindows.farming-error")
  220.     end
  221.     local speaker = peripheral.find("speaker")
  222.     if speaker then
  223.         while true do
  224.             speaker.playNote("bit", 3, 12)
  225.             sleep(1)
  226.         end
  227.     else while true do os.pullEvent() end end
  228. end
  229.  
  230. local function check(ok, msg) if not ok then panic(msg) end end
  231.  
  232. local function tryForward()
  233.     local ok, err, found, block
  234.     repeat
  235.         found, block = turtle.inspect()
  236.         if found then
  237.             if groundBlocks[block.name] or cropBlocks[block.name] then
  238.                 ok, err = up()
  239.                 if not ok then return ok, err end
  240.             else return false, "Out of bounds" end
  241.         end
  242.     until not found
  243.     ok, err = forward()
  244.     if not ok then return ok, err end
  245.     local lastY = y
  246.     repeat
  247.         found, block = turtle.inspectDown()
  248.         if not found then
  249.             ok, err = down()
  250.             if not ok then return ok, err end
  251.         end
  252.     until found
  253.     if groundBlocks[block.name] then
  254.         ok, err = up()
  255.         if not ok then return ok, err end
  256.         turtle.digDown()
  257.     elseif not cropBlocks[block.name] then
  258.         while y < lastY do
  259.             ok, err = up()
  260.             if not ok then return ok, err end
  261.         end
  262.         ok, err = back()
  263.         if not ok then return ok, err end
  264.         return false, "Out of bounds"
  265.     end
  266.     return true
  267. end
  268.  
  269. local function selectItem(item)
  270.     local lut = {}
  271.     if type(item) == "table" then
  272.         if item[1] then for _, v in ipairs(item) do lut[v] = true end
  273.         else lut = item end
  274.     else lut[item] = true end
  275.     local lastEmpty
  276.     for i = 1, 16 do
  277.         local info = turtle.getItemDetail(i)
  278.         if info and lut[info.name] then
  279.             turtle.select(i)
  280.             return true, i
  281.         elseif not info and not lastEmpty then lastEmpty = i end
  282.     end
  283.     return false, lastEmpty
  284. end
  285.  
  286. local function handleCrop()
  287.     local found, block = turtle.inspectDown()
  288.     if not found then
  289.         if selectItem(seedItems) then turtle.placeDown() end
  290.     elseif block.state.age == cropBlocks[block.name] then
  291.         local seed = seeds[block.name]
  292.         turtle.select(1)
  293.         turtle.digDown()
  294.         turtle.suckDown()
  295.         if turtle.getItemDetail().name ~= seed and not selectItem(seed) then return end
  296.         turtle.placeDown()
  297.     end
  298. end
  299.  
  300. local function exchangeItems()
  301.     local inventory, fuel, seed = {}, nil, nil
  302.     for i = 1, 16 do
  303.         turtle.select(i)
  304.         local item = turtle.getItemDetail(i)
  305.         if item then
  306.             if not seed and seedItems[item.name] then
  307.                 seed = {slot = i, name = item.name, count = item.count, limit = turtle.getItemSpace(i)}
  308.             elseif not turtle.refuel(0) then
  309.                 inventory[item.name] = inventory[item.name] or {}
  310.                 inventory[item.name][i] = item.count
  311.             elseif not fuel then
  312.                 fuel = {slot = i, name = item.name, count = item.count, limit = turtle.getItemSpace(i)}
  313.             end
  314.         end
  315.     end
  316.     local modem = peripheral.find("modem", function(_, v) return not v.isWireless() end)
  317.     local tries = 0
  318.     while not modem and tries < 4 do
  319.         tries = tries + 1
  320.         check(left())
  321.         modem = peripheral.find("modem", function(_, v) return not v.isWireless() end)
  322.     end
  323.     if not modem then panic("Could not find modem!") end
  324.     local name = modem.getNameLocal()
  325.     for _, chest in ipairs{peripheral.find("minecraft:chest")} do
  326.         local items = chest.list()
  327.         for i = 1, chest.size() do
  328.             if items[i] then
  329.                 local item = items[i].name
  330.                 if inventory[item] then
  331.                     for slot, count in pairs(inventory[item]) do
  332.                         local d = chest.pullItems(name, slot, count, i)
  333.                         if d == 0 then break end
  334.                         if count - d <= 0 then inventory[item][slot] = nil
  335.                         else inventory[item][slot] = count - d end
  336.                     end
  337.                     if not next(inventory[item]) then inventory[item] = nil end
  338.                 elseif fuel and fuel.count < fuel.limit and item == fuel.name then
  339.                     local d = chest.pushItems(name, i, fuel.limit - fuel.count, fuel.slot)
  340.                     fuel.count = fuel.count + d
  341.                 elseif seed and seed.count < seed.limit and item == seed.name then
  342.                     local d = chest.pushItems(name, i, seed.limit - seed.count, seed.slot)
  343.                     seed.count = seed.count + d
  344.                 end
  345.             end
  346.             if not next(inventory) then break end
  347.         end
  348.         if not next(inventory) then break end
  349.     end
  350.     if next(inventory) then
  351.         for _, chest in ipairs{peripheral.find("minecraft:chest")} do
  352.             local items = chest.list()
  353.             for i = 1, chest.size() do
  354.                 if not items[i] then
  355.                     local item, list = next(inventory)
  356.                     for slot, count in pairs(list) do
  357.                         local d = chest.pullItems(name, slot, count, i)
  358.                         if d == 0 then break end
  359.                         if count - d <= 0 then list[slot] = nil
  360.                         else list[slot] = count - d end
  361.                     end
  362.                     if not next(list) then inventory[item] = nil end
  363.                 end
  364.                 if not next(inventory) then break end
  365.             end
  366.             if not next(inventory) then break end
  367.         end
  368.     end
  369.     if not fuel or not seed then
  370.         for _, chest in ipairs{peripheral.find("minecraft:chest")} do
  371.             local items = chest.list()
  372.             for i = 1, chest.size() do
  373.                 if items[i] and ((fuel and items[i].name == fuel.name and fuel.count < fuel.limit) or (not fuel and fuels[items[i].name])) then
  374.                     local d = chest.pushItems(name, i, fuel and fuel.count - fuel.limit, 16)
  375.                     if fuel then fuel.count = fuel.count + d
  376.                     else fuel = {name = items[i].name, count = d, limit = turtle.getItemSpace(16)} end
  377.                 end
  378.                 if items[i] and ((seed and items[i].name == seed.name and seed.count < seed.limit) or (not seed and seedItems[items[i].name])) then
  379.                     local d = chest.pushItems(name, i, seed and seed.count - seed.limit, 1)
  380.                     if seed then seed.count = seed.count + d
  381.                     else seed = {name = items[i].name, count = d, limit = turtle.getItemSpace(1)} end
  382.                 end
  383.                 if (fuel and fuel.count >= fuel.limit) and (seed and seed.count >= seed.limit) then break end
  384.             end
  385.             if (fuel and fuel.count >= fuel.limit) and (seed and seed.count >= seed.limit) then break end
  386.         end
  387.     end
  388. end
  389.  
  390. if fs.exists("jackmacwindows.farm-state.txt") then
  391.     local file = fs.open("jackmacwindows.farm-state.txt", "r")
  392.     x, y, z, direction = tonumber(file.readLine()), tonumber(file.readLine()), tonumber(file.readLine()), tonumber(file.readLine())
  393.     invertDirection = file.readLine() == "true"
  394.     file.close()
  395.     -- check if we were on a boundary block first
  396.     local found, block, ok, err, boundary
  397.     local lastY = y
  398.     repeat
  399.         found, block = turtle.inspectDown()
  400.         if not found then check(down()) end
  401.     until found
  402.     if groundBlocks[block.name] then
  403.         check(up())
  404.         turtle.digDown()
  405.     elseif not cropBlocks[block.name] then
  406.         if y == lastY then lastY = lastY + 1 end
  407.         while y < lastY do check(up()) end
  408.         while not back() do check(up()) end
  409.         boundary = true
  410.     end
  411.     if direction == 1 or direction == 3 then
  412.         -- we were in the middle of a rotation, finish that before continuing
  413.         local mv = (direction == 0) == invertDirection and left or right
  414.         if boundary then
  415.             check(mv())
  416.             check(mv())
  417.             check(tryForward())
  418.             invertDirection = not invertDirection
  419.             mv = mv == left and right or left
  420.             writePos()
  421.         end
  422.         check(mv())
  423.         handleCrop()
  424.         if x == 0 and z == 0 then
  425.             while y > 0 do check(down()) end
  426.             while y < 0 do check(up()) end
  427.             exchangeItems()
  428.         end
  429.     end
  430. elseif not peripheral.find("minecraft:chest") or not peripheral.find("modem", function(_, m) return not m.isWireless() end) then
  431.     print[[
  432. Please move the turtle to the starting position next to a modem with a chest.
  433. The expected setup is the turtle next to a wired modem block, with a chest next to that modem block.
  434. This program cannot run until placed correctly.
  435. ]]
  436.     return
  437. else exchangeItems() end
  438.  
  439. local ok, err
  440. while true do
  441.     ok, err = tryForward()
  442.     if not ok then
  443.         if err == "Out of bounds" then
  444.             local mv = (direction == 0) == invertDirection and left or right
  445.             check(mv())
  446.             ok, err = tryForward()
  447.             if not ok then
  448.                 if err == "Out of bounds" then
  449.                     check(mv())
  450.                     check(mv())
  451.                     check(tryForward())
  452.                     invertDirection = not invertDirection
  453.                     mv = mv == left and right or left
  454.                     writePos()
  455.                 else panic(err) end
  456.             end
  457.             check(mv())
  458.         else panic(err) end
  459.     end
  460.     handleCrop()
  461.     if x == 0 and z == 0 then
  462.         while y > 0 do check(down()) end
  463.         while y < 0 do check(up()) end
  464.         exchangeItems()
  465.     elseif peripheral.find("modem") then
  466.         x, y, z = 0, 0, 0
  467.         exchangeItems()
  468.     end
  469.     if turtle.getFuelLevel() < 100 then refuel() end
  470. end
  471.  
Advertisement
Add Comment
Please, Sign In to add comment