View difference between Paste ID: cFxq9e8W and H2ArwWM1
SHOW: | | - or go back to the newest paste.
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-
        local item = turtle.getItemDetail()
295+
296-
        if (not item or item.name ~= seed) and not selectItem(seed) then return end
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 modem = peripheral.find("modem", function(_, v) return not v.isWireless() end)
317+
    local name = peripheral.find("modem", function(_, v) return not v.isWireless() end).getNameLocal()
318-
	local tries = 0
318+
319-
	while not modem and tries < 4 do
319+
320-
		tries = tries + 1
320+
321-
		check(left())
321+
322-
		modem = peripheral.find("modem", function(_, v) return not v.isWireless() end)
322+
323-
	end
323+
324-
	if not modem then panic("Could not find modem!") end
324+
325-
    local name = modem.getNameLocal()
325+
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-
elseif not peripheral.find("minecraft:chest") or not peripheral.find("modem", function(_, m) return not m.isWireless() end) then
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