ChickenFellwo

ComputerCraft Automatic Turtle Farm

Oct 20th, 2023
894
1
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  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.     ["minecraft:glowstone"] = true,
  78.     -- add your own here:
  79.     --["<yourmod>:<block>"] = true,
  80. }
  81.  
  82. -- Blocks that are crops, with their maximum ages
  83. local cropBlocks = {
  84.     ["minecraft:wheat"] = 7,
  85.     ["minecraft:carrots"] = 7,
  86.     ["minecraft:potatoes"] = 7,
  87.     ["minecraft:beetroots"] = 3,
  88.     -- add your own here:
  89.     --["<yourmod>:<block>"] = <maximum age>,
  90. }
  91.  
  92. -- Mappings of crop blocks to seed items
  93. local seeds = {
  94.     ["minecraft:wheat"] = "minecraft:wheat_seeds",
  95.     ["minecraft:carrots"] = "minecraft:carrot",
  96.     ["minecraft:potatoes"] = "minecraft:potato",
  97.     ["minecraft:beetroots"] = "minecraft:beetroot_seeds",
  98.     -- add your own here:
  99.     --["<yourmod>:<block>"] = "<yourmod>:<seed>",
  100. }
  101.  
  102. -- Fuel types to pull from a chest if no fuel is in the inventory
  103. local fuels = {
  104.     ["minecraft:coal"] = true,
  105.     ["minecraft:charcoal"] = true,
  106.     ["minecraft:lava_bucket"] = true,
  107.     -- add your own here:
  108.     --["<yourmod>:<item>"] = true,
  109. }
  110.  
  111. local seedItems = {}
  112. for k, v in pairs(seeds) do seedItems[v] = k end
  113.  
  114. local function writePos()
  115.     local file = fs.open("jackmacwindows.farm-state.txt", "w")
  116.     file.writeLine(x)
  117.     file.writeLine(y)
  118.     file.writeLine(z)
  119.     file.writeLine(direction)
  120.     file.writeLine(invertDirection and "true" or "false")
  121.     file.close()
  122. end
  123.  
  124. local function refuel()
  125.     if turtle.getFuelLevel() == "unlimited" or turtle.getFuelLevel() == turtle.getFuelLimit() then return end
  126.     for i = 1, 16 do
  127.         if turtle.getItemCount(i) > 0 then
  128.             turtle.select(i)
  129.             turtle.refuel(turtle.getItemCount() - 1)
  130.             if turtle.getFuelLevel() == turtle.getFuelLimit() then return true end
  131.         end
  132.     end
  133.     if turtle.getFuelLevel() > 0 then return true
  134.     else return false, "Out of fuel" end
  135. end
  136.  
  137. local function forward()
  138.     local ok, err = turtle.forward()
  139.     if ok then
  140.         if direction == 0 then x = x + 1
  141.         elseif direction == 1 then z = z + 1
  142.         elseif direction == 2 then x = x - 1
  143.         else z = z - 1 end
  144.         writePos()
  145.         return true
  146.     elseif err:match "[Ff]uel" then
  147.         ok, err = refuel()
  148.         if ok then return forward()
  149.         else return ok, err end
  150.     else return false, err end
  151. end
  152.  
  153. local function back()
  154.     local ok, err = turtle.back()
  155.     if ok then
  156.         if direction == 0 then x = x - 1
  157.         elseif direction == 1 then z = z - 1
  158.         elseif direction == 2 then x = x + 1
  159.         else z = z + 1 end
  160.         writePos()
  161.         return true
  162.     elseif err:match "[Ff]uel" then
  163.         ok, err = refuel()
  164.         if ok then return forward()
  165.         else return ok, err end
  166.     else return false, err end
  167. end
  168.  
  169. local function up()
  170.     local ok, err = turtle.up()
  171.     if ok then
  172.         y = y + 1
  173.         writePos()
  174.         return true
  175.     elseif err:match "[Ff]uel" then
  176.         ok, err = refuel()
  177.         if ok then return forward()
  178.         else return ok, err end
  179.     else return false, err end
  180. end
  181.  
  182. local function down()
  183.     local ok, err = turtle.down()
  184.     if ok then
  185.         y = y - 1
  186.         writePos()
  187.         return true
  188.     elseif err:match "[Ff]uel" then
  189.         ok, err = refuel()
  190.         if ok then return forward()
  191.         else return ok, err end
  192.     else return false, err end
  193. end
  194.  
  195. local function left()
  196.     local ok, err = turtle.turnLeft()
  197.     if ok then
  198.         direction = (direction - 1) % 4
  199.         writePos()
  200.         return true
  201.     else return false, err end
  202. end
  203.  
  204. local function right()
  205.     local ok, err = turtle.turnRight()
  206.     if ok then
  207.         direction = (direction + 1) % 4
  208.         writePos()
  209.         return true
  210.     else return false, err end
  211. end
  212.  
  213. local function panic(msg)
  214.     term.clear()
  215.     term.setCursorPos(1, 1)
  216.     term.setTextColor(colors.red)
  217.     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.")
  218.     if peripheral.find("modem") then
  219.         peripheral.find("modem", rednet.open)
  220.         rednet.broadcast(msg, "jackmacwindows.farming-error")
  221.     end
  222.     local speaker = peripheral.find("speaker")
  223.     if speaker then
  224.         while true do
  225.             speaker.playNote("bit", 3, 12)
  226.             sleep(1)
  227.         end
  228.     else while true do os.pullEvent() end end
  229. end
  230.  
  231. local function check(ok, msg) if not ok then panic(msg) end end
  232.  
  233. local function tryForward()
  234.     local ok, err, found, block
  235.     repeat
  236.         found, block = turtle.inspect()
  237.         if found then
  238.             if groundBlocks[block.name] or cropBlocks[block.name] then
  239.                 ok, err = up()
  240.                 if not ok then return ok, err end
  241.             else return false, "Out of bounds" end
  242.         end
  243.     until not found
  244.     ok, err = forward()
  245.     if not ok then return ok, err end
  246.     local lastY = y
  247.     repeat
  248.         found, block = turtle.inspectDown()
  249.         if not found then
  250.             ok, err = down()
  251.             if not ok then return ok, err end
  252.         end
  253.     until found
  254.     if groundBlocks[block.name] then
  255.         ok, err = up()
  256.         if not ok then return ok, err end
  257.         turtle.digDown()
  258.     elseif not cropBlocks[block.name] then
  259.         while y < lastY do
  260.             ok, err = up()
  261.             if not ok then return ok, err end
  262.         end
  263.         ok, err = back()
  264.         if not ok then return ok, err end
  265.         return false, "Out of bounds"
  266.     end
  267.     return true
  268. end
  269.  
  270. local function selectItem(item)
  271.     local lut = {}
  272.     if type(item) == "table" then
  273.         if item[1] then for _, v in ipairs(item) do lut[v] = true end
  274.         else lut = item end
  275.     else lut[item] = true end
  276.     local lastEmpty
  277.     for i = 1, 16 do
  278.         local info = turtle.getItemDetail(i)
  279.         if info and lut[info.name] then
  280.             turtle.select(i)
  281.             return true, i
  282.         elseif not info and not lastEmpty then lastEmpty = i end
  283.     end
  284.     return false, lastEmpty
  285. end
  286.  
  287. local function handleCrop()
  288.     local found, block = turtle.inspectDown()
  289.     if not found then
  290.         if selectItem(seedItems) then turtle.placeDown() end
  291.     elseif block.state.age == cropBlocks[block.name] then
  292.         local seed = seeds[block.name]
  293.         turtle.select(1)
  294.         turtle.digDown()
  295.         turtle.suckDown()
  296.         if turtle.getItemDetail().name ~= seed and not selectItem(seed) then return end
  297.         turtle.placeDown()
  298.     end
  299. end
  300.  
  301. local function exchangeItems()
  302.     local inventory, fuel, seed = {}, nil, nil
  303.     for i = 1, 16 do
  304.         turtle.select(i)
  305.         local item = turtle.getItemDetail(i)
  306.         if item then
  307.             if not seed and seedItems[item.name] then
  308.                 seed = {slot = i, name = item.name, count = item.count, limit = turtle.getItemSpace(i)}
  309.             elseif not turtle.refuel(0) then
  310.                 inventory[item.name] = inventory[item.name] or {}
  311.                 inventory[item.name][i] = item.count
  312.             elseif not fuel then
  313.                 fuel = {slot = i, name = item.name, count = item.count, limit = turtle.getItemSpace(i)}
  314.             end
  315.         end
  316.     end
  317.     local name = peripheral.find("modem", function(_, v) return not v.isWireless() end).getNameLocal()
  318.     for _, chest in ipairs{peripheral.find("minecraft:chest")} do
  319.         local items = chest.list()
  320.         for i = 1, chest.size() do
  321.             if items[i] then
  322.                 local item = items[i].name
  323.                 if inventory[item] then
  324.                     for slot, count in pairs(inventory[item]) do
  325.                         local d = chest.pullItems(name, slot, count, i)
  326.                         if d == 0 then break end
  327.                         if count - d <= 0 then inventory[item][slot] = nil
  328.                         else inventory[item][slot] = count - d end
  329.                     end
  330.                     if not next(inventory[item]) then inventory[item] = nil end
  331.                 elseif fuel and fuel.count < fuel.limit and item == fuel.name then
  332.                     local d = chest.pushItems(name, i, fuel.limit - fuel.count, fuel.slot)
  333.                     fuel.count = fuel.count + d
  334.                 elseif seed and seed.count < seed.limit and item == seed.name then
  335.                     local d = chest.pushItems(name, i, seed.limit - seed.count, seed.slot)
  336.                     seed.count = seed.count + d
  337.                 end
  338.             end
  339.             if not next(inventory) then break end
  340.         end
  341.         if not next(inventory) then break end
  342.     end
  343.     if next(inventory) then
  344.         for _, chest in ipairs{peripheral.find("minecraft:chest")} do
  345.             local items = chest.list()
  346.             for i = 1, chest.size() do
  347.                 if not items[i] then
  348.                     local item, list = next(inventory)
  349.                     for slot, count in pairs(list) do
  350.                         local d = chest.pullItems(name, slot, count, i)
  351.                         if d == 0 then break end
  352.                         if count - d <= 0 then list[slot] = nil
  353.                         else list[slot] = count - d end
  354.                     end
  355.                     if not next(list) then inventory[item] = nil end
  356.                 end
  357.                 if not next(inventory) then break end
  358.             end
  359.             if not next(inventory) then break end
  360.         end
  361.     end
  362.     if not fuel or not seed then
  363.         for _, chest in ipairs{peripheral.find("minecraft:chest")} do
  364.             local items = chest.list()
  365.             for i = 1, chest.size() do
  366.                 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
  367.                     local d = chest.pushItems(name, i, fuel and fuel.count - fuel.limit, 16)
  368.                     if fuel then fuel.count = fuel.count + d
  369.                     else fuel = {name = items[i].name, count = d, limit = turtle.getItemSpace(16)} end
  370.                 end
  371.                 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
  372.                     local d = chest.pushItems(name, i, seed and seed.count - seed.limit, 1)
  373.                     if seed then seed.count = seed.count + d
  374.                     else seed = {name = items[i].name, count = d, limit = turtle.getItemSpace(1)} end
  375.                 end
  376.                 if (fuel and fuel.count >= fuel.limit) and (seed and seed.count >= seed.limit) then break end
  377.             end
  378.             if (fuel and fuel.count >= fuel.limit) and (seed and seed.count >= seed.limit) then break end
  379.         end
  380.     end
  381. end
  382.  
  383. if fs.exists("jackmacwindows.farm-state.txt") then
  384.     local file = fs.open("jackmacwindows.farm-state.txt", "r")
  385.     x, y, z, direction = tonumber(file.readLine()), tonumber(file.readLine()), tonumber(file.readLine()), tonumber(file.readLine())
  386.     invertDirection = file.readLine() == "true"
  387.     file.close()
  388.     -- check if we were on a boundary block first
  389.     local found, block, ok, err, boundary
  390.     local lastY = y
  391.     repeat
  392.         found, block = turtle.inspectDown()
  393.         if not found then check(down()) end
  394.     until found
  395.     if groundBlocks[block.name] then
  396.         check(up())
  397.         turtle.digDown()
  398.     elseif not cropBlocks[block.name] then
  399.         if y == lastY then lastY = lastY + 1 end
  400.         while y < lastY do check(up()) end
  401.         while not back() do check(up()) end
  402.         boundary = true
  403.     end
  404.     if direction == 1 or direction == 3 then
  405.         -- we were in the middle of a rotation, finish that before continuing
  406.         local mv = (direction == 0) == invertDirection and left or right
  407.         if boundary then
  408.             check(mv())
  409.             check(mv())
  410.             check(tryForward())
  411.             invertDirection = not invertDirection
  412.             mv = mv == left and right or left
  413.             writePos()
  414.         end
  415.         check(mv())
  416.         handleCrop()
  417.         if x == 0 and z == 0 then
  418.             while y > 0 do check(down()) end
  419.             while y < 0 do check(up()) end
  420.             exchangeItems()
  421.         end
  422.     end
  423. elseif not peripheral.find("minecraft:chest") then
  424.     print[[
  425. Please move the turtle to the starting position next to a modem with a chest.
  426. The expected setup is the turtle next to a wired modem block, with a chest next to that modem block.
  427. This program cannot run until placed correctly.
  428. ]]
  429.     return
  430. else exchangeItems() end
  431.  
  432. local ok, err
  433. while true do
  434.     ok, err = tryForward()
  435.     if not ok then
  436.         if err == "Out of bounds" then
  437.             local mv = (direction == 0) == invertDirection and left or right
  438.             check(mv())
  439.             ok, err = tryForward()
  440.             if not ok then
  441.                 if err == "Out of bounds" then
  442.                     check(mv())
  443.                     check(mv())
  444.                     check(tryForward())
  445.                     invertDirection = not invertDirection
  446.                     mv = mv == left and right or left
  447.                     writePos()
  448.                 else panic(err) end
  449.             end
  450.             check(mv())
  451.         else panic(err) end
  452.     end
  453.     handleCrop()
  454.     if x == 0 and z == 0 then
  455.         while y > 0 do check(down()) end
  456.         while y < 0 do check(up()) end
  457.         exchangeItems()
  458.     end
  459.     if turtle.getFuelLevel() < 100 then refuel() end
  460. end
  461.  
Advertisement
Add Comment
Please, Sign In to add comment