Advertisement
JackMacWindows

ComputerCraft Automatic Turtle Farm

May 3rd, 2022 (edited)
5,866
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 17.08 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 name = peripheral.find("modem", function(_, v) return not v.isWireless() end).getNameLocal()
  317.     for _, chest in ipairs{peripheral.find("minecraft:chest")} do
  318.         local items = chest.list()
  319.         for i = 1, chest.size() do
  320.             if items[i] then
  321.                 local item = items[i].name
  322.                 if inventory[item] then
  323.                     for slot, count in pairs(inventory[item]) do
  324.                         local d = chest.pullItems(name, slot, count, i)
  325.                         if d == 0 then break end
  326.                         if count - d <= 0 then inventory[item][slot] = nil
  327.                         else inventory[item][slot] = count - d end
  328.                     end
  329.                     if not next(inventory[item]) then inventory[item] = nil end
  330.                 elseif fuel and fuel.count < fuel.limit and item == fuel.name then
  331.                     local d = chest.pushItems(name, i, fuel.limit - fuel.count, fuel.slot)
  332.                     fuel.count = fuel.count + d
  333.                 elseif seed and seed.count < seed.limit and item == seed.name then
  334.                     local d = chest.pushItems(name, i, seed.limit - seed.count, seed.slot)
  335.                     seed.count = seed.count + d
  336.                 end
  337.             end
  338.             if not next(inventory) then break end
  339.         end
  340.         if not next(inventory) then break end
  341.     end
  342.     if next(inventory) then
  343.         for _, chest in ipairs{peripheral.find("minecraft:chest")} do
  344.             local items = chest.list()
  345.             for i = 1, chest.size() do
  346.                 if not items[i] then
  347.                     local item, list = next(inventory)
  348.                     for slot, count in pairs(list) do
  349.                         local d = chest.pullItems(name, slot, count, i)
  350.                         if d == 0 then break end
  351.                         if count - d <= 0 then list[slot] = nil
  352.                         else list[slot] = count - d end
  353.                     end
  354.                     if not next(list) then inventory[item] = nil end
  355.                 end
  356.                 if not next(inventory) then break end
  357.             end
  358.             if not next(inventory) then break end
  359.         end
  360.     end
  361.     if not fuel or not seed then
  362.         for _, chest in ipairs{peripheral.find("minecraft:chest")} do
  363.             local items = chest.list()
  364.             for i = 1, chest.size() do
  365.                 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
  366.                     local d = chest.pushItems(name, i, fuel and fuel.count - fuel.limit, 16)
  367.                     if fuel then fuel.count = fuel.count + d
  368.                     else fuel = {name = items[i].name, count = d, limit = turtle.getItemSpace(16)} end
  369.                 end
  370.                 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
  371.                     local d = chest.pushItems(name, i, seed and seed.count - seed.limit, 1)
  372.                     if seed then seed.count = seed.count + d
  373.                     else seed = {name = items[i].name, count = d, limit = turtle.getItemSpace(1)} end
  374.                 end
  375.                 if (fuel and fuel.count >= fuel.limit) and (seed and seed.count >= seed.limit) then break end
  376.             end
  377.             if (fuel and fuel.count >= fuel.limit) and (seed and seed.count >= seed.limit) then break end
  378.         end
  379.     end
  380. end
  381.  
  382. if fs.exists("jackmacwindows.farm-state.txt") then
  383.     local file = fs.open("jackmacwindows.farm-state.txt", "r")
  384.     x, y, z, direction = tonumber(file.readLine()), tonumber(file.readLine()), tonumber(file.readLine()), tonumber(file.readLine())
  385.     invertDirection = file.readLine() == "true"
  386.     file.close()
  387.     -- check if we were on a boundary block first
  388.     local found, block, ok, err, boundary
  389.     local lastY = y
  390.     repeat
  391.         found, block = turtle.inspectDown()
  392.         if not found then check(down()) end
  393.     until found
  394.     if groundBlocks[block.name] then
  395.         check(up())
  396.         turtle.digDown()
  397.     elseif not cropBlocks[block.name] then
  398.         if y == lastY then lastY = lastY + 1 end
  399.         while y < lastY do check(up()) end
  400.         while not back() do check(up()) end
  401.         boundary = true
  402.     end
  403.     if direction == 1 or direction == 3 then
  404.         -- we were in the middle of a rotation, finish that before continuing
  405.         local mv = (direction == 0) == invertDirection and left or right
  406.         if boundary then
  407.             check(mv())
  408.             check(mv())
  409.             check(tryForward())
  410.             invertDirection = not invertDirection
  411.             mv = mv == left and right or left
  412.             writePos()
  413.         end
  414.         check(mv())
  415.         handleCrop()
  416.         if x == 0 and z == 0 then
  417.             while y > 0 do check(down()) end
  418.             while y < 0 do check(up()) end
  419.             exchangeItems()
  420.         end
  421.     end
  422. elseif not peripheral.find("minecraft:chest") then
  423.     print[[
  424. Please move the turtle to the starting position next to a modem with a chest.
  425. The expected setup is the turtle next to a wired modem block, with a chest next to that modem block.
  426. This program cannot run until placed correctly.
  427. ]]
  428.     return
  429. else exchangeItems() end
  430.  
  431. local ok, err
  432. while true do
  433.     ok, err = tryForward()
  434.     if not ok then
  435.         if err == "Out of bounds" then
  436.             local mv = (direction == 0) == invertDirection and left or right
  437.             check(mv())
  438.             ok, err = tryForward()
  439.             if not ok then
  440.                 if err == "Out of bounds" then
  441.                     check(mv())
  442.                     check(mv())
  443.                     check(tryForward())
  444.                     invertDirection = not invertDirection
  445.                     mv = mv == left and right or left
  446.                     writePos()
  447.                 else panic(err) end
  448.             end
  449.             check(mv())
  450.         else panic(err) end
  451.     end
  452.     handleCrop()
  453.     if x == 0 and z == 0 then
  454.         while y > 0 do check(down()) end
  455.         while y < 0 do check(up()) end
  456.         exchangeItems()
  457.     end
  458.     if turtle.getFuelLevel() < 100 then refuel() end
  459. end
  460.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement