-- ============================================================
-- Six-tree automatic tree farm
-- For a ComputerCraft / CC:Tweaked Felling Turtle
--
-- Starting position:
-- X = 286
-- Z = 108
-- Y = 63
--
-- IMPORTANT:
-- Turtle must start facing +Z (toward Z=109,110,...)
--
-- Chest must be directly UNDER the turtle's starting position.
-- ============================================================
-- ------------------------------------------------------------
-- CONFIGURATION
-- ------------------------------------------------------------
local HOME_X = 286
local HOME_Z = 108
-- Tree coordinates, expressed as X/Z because all are at Y=63.
local trees = {
{ x = 284, z = 110 },
{ x = 288, z = 110 },
{ x = 284, z = 114 },
{ x = 288, z = 114 },
{ x = 284, z = 118 },
{ x = 288, z = 118 }
}
-- How long to wait between complete farm checks.
local CHECK_INTERVAL = 30
-- Maximum bone-meal attempts before giving up on a sapling.
local MAX_BONEMEAL_ATTEMPTS = 32
-- Refuel if below this amount.
local MIN_FUEL = 250
-- ------------------------------------------------------------
-- POSITION TRACKING
-- ------------------------------------------------------------
--
-- Direction:
-- 0 = +Z
-- 1 = -X
-- 2 = -Z
-- 3 = +X
--
-- We start facing +Z.
-- ------------------------------------------------------------
local posX = HOME_X
local posZ = HOME_Z
local direction = 0
local function turnLeft()
turtle.turnLeft()
direction = (direction + 1) % 4
end
local function turnRight()
turtle.turnRight()
direction = (direction + 3) % 4
end
local function face(target)
while direction ~= target do
turnRight()
end
end
local function forward()
local attempts = 0
while not turtle.forward() do
attempts = attempts + 1
if attempts > 20 then
error("Unable to move forward.")
end
-- Don't automatically destroy random obstacles.
sleep(0.25)
end
if direction == 0 then
posZ = posZ + 1
elseif direction == 1 then
posX = posX - 1
elseif direction == 2 then
posZ = posZ - 1
elseif direction == 3 then
posX = posX + 1
end
end
local function back()
local attempts = 0
while not turtle.back() do
attempts = attempts + 1
if attempts > 20 then
error("Unable to move backward.")
end
sleep(0.25)
end
if direction == 0 then
posZ = posZ - 1
elseif direction == 1 then
posX = posX + 1
elseif direction == 2 then
posZ = posZ + 1
elseif direction == 3 then
posX = posX - 1
end
end
-- ------------------------------------------------------------
-- NAVIGATION
-- ------------------------------------------------------------
local function moveTo(x, z)
-- X movement
if posX < x then
face(3) -- +X
while posX < x do
forward()
end
elseif posX > x then
face(1) -- -X
while posX > x do
forward()
end
end
-- Z movement
if posZ < z then
face(0) -- +Z
while posZ < z do
forward()
end
elseif posZ > z then
face(2) -- -Z
while posZ > z do
forward()
end
end
end
local function goHome()
moveTo(HOME_X, HOME_Z)
face(0)
end
-- ------------------------------------------------------------
-- ITEM IDENTIFICATION
-- ------------------------------------------------------------
local function endsWith(text, suffix)
return text:sub(-#suffix) == suffix
end
local function isSaplingName(name)
if not name then
return false
end
return endsWith(name, "_sapling") or
name == "minecraft:mangrove_propagule"
end
local function isLogName(name)
if not name then
return false
end
return endsWith(name, "_log") or
endsWith(name, "_wood") or
endsWith(name, "_stem") or
endsWith(name, "_hyphae")
end
local function isFuelName(name)
return name == "minecraft:coal" or
name == "minecraft:charcoal" or
name == "minecraft:coal_block"
end
local function isBoneMealName(name)
return name == "minecraft:bone_meal"
end
-- ------------------------------------------------------------
-- INVENTORY SEARCH
-- ------------------------------------------------------------
local function findItem(testFunction)
for slot = 1, 16 do
local item = turtle.getItemDetail(slot)
if item and testFunction(item.name) then
return slot, item
end
end
return nil
end
local function findBoneMeal()
return findItem(isBoneMealName)
end
local function findAnySapling()
return findItem(isSaplingName)
end
local function findExactItem(name)
for slot = 1, 16 do
local item = turtle.getItemDetail(slot)
if item and item.name == name then
return slot
end
end
return nil
end
-- ------------------------------------------------------------
-- FUEL
-- ------------------------------------------------------------
local function refuel()
local fuel = turtle.getFuelLevel()
if fuel == "unlimited" then
return true
end
if fuel >= MIN_FUEL then
return true
end
print("Fuel low: " .. fuel)
for slot = 1, 16 do
local item = turtle.getItemDetail(slot)
if item and isFuelName(item.name) then
turtle.select(slot)
while turtle.getItemCount(slot) > 0 and
turtle.getFuelLevel() < MIN_FUEL do
turtle.refuel(1)
end
if turtle.getFuelLevel() >= MIN_FUEL then
print("Refueled: " .. turtle.getFuelLevel())
return true
end
end
end
print("WARNING: No fuel available!")
return false
end
-- ------------------------------------------------------------
-- BLOCK INSPECTION
-- ------------------------------------------------------------
local function blockInFront()
local success, block = turtle.inspect()
if success then
return block
end
return nil
end
local function logInFront()
local block = blockInFront()
return block and isLogName(block.name)
end
local function logAbove()
local success, block = turtle.inspectUp()
return success and isLogName(block.name)
end
local function logBelow()
local success, block = turtle.inspectDown()
return success and isLogName(block.name)
end
-- ------------------------------------------------------------
-- RECURSIVE TREE HARVEST
--
-- Turtle should be standing INSIDE the location of a log which
-- it has just removed.
--
-- Every recursive call returns the turtle to exactly the same
-- location and orientation from which that call started.
-- ------------------------------------------------------------
local function harvestConnectedLogs()
-- ----- LOG ABOVE -----
if logAbove() then
turtle.digUp()
if turtle.up() then
harvestConnectedLogs()
turtle.down()
end
end
-- ----- LOG BELOW -----
--
-- Normally the parent log has already been removed, so this
-- will not cause us to walk back through the tree.
--
-- It also catches unusual downward-connected branches.
--
if logBelow() then
turtle.digDown()
if turtle.down() then
harvestConnectedLogs()
turtle.up()
end
end
-- ----- FOUR HORIZONTAL DIRECTIONS -----
for side = 1, 4 do
if logInFront() then
turtle.dig()
if turtle.forward() then
harvestConnectedLogs()
turtle.back()
end
end
turtle.turnRight()
end
end
-- ------------------------------------------------------------
-- DETERMINE REPLACEMENT SAPLING FROM LOG TYPE
-- ------------------------------------------------------------
local function saplingFromLog(logName)
if not logName then
return nil
end
-- minecraft:oak_log -> minecraft:oak_sapling
if endsWith(logName, "_log") then
local prefix = logName:sub(1, #logName - 4)
return prefix .. "_sapling"
end
-- Wood variants
if endsWith(logName, "_wood") then
local prefix = logName:sub(1, #logName - 5)
return prefix .. "_sapling"
end
return nil
end
-- ------------------------------------------------------------
-- GROW SAPLING
-- ------------------------------------------------------------
local function growSapling()
for attempt = 1, MAX_BONEMEAL_ATTEMPTS do
local block = blockInFront()
-- It grew.
if block and isLogName(block.name) then
print("Tree grew after " .. (attempt - 1) ..
" bone meal attempts.")
return true, block.name
end
-- No longer a sapling and not a log.
if not block or not isSaplingName(block.name) then
return false, nil
end
local slot = findBoneMeal()
if not slot then
print("Out of bone meal.")
return false, nil
end
turtle.select(slot)
turtle.place()
sleep(0.15)
end
local block = blockInFront()
if block and isLogName(block.name) then
return true, block.name
end
print("Sapling did not grow.")
return false, nil
end
-- ------------------------------------------------------------
-- PLANT SAPLING
-- ------------------------------------------------------------
local function plantSapling(preferredName)
local slot = nil
-- Try to use the same species.
if preferredName then
slot = findExactItem(preferredName)
end
-- Otherwise use whatever sapling is available.
if not slot then
slot = findAnySapling()
end
if not slot then
print("WARNING: No saplings available!")
return false
end
turtle.select(slot)
if turtle.place() then
print("Sapling planted.")
return true
end
print("WARNING: Unable to plant sapling.")
return false
end
-- ------------------------------------------------------------
-- HARVEST A SINGLE TREE
--
-- Turtle is standing one block NORTH (-Z) of the planting spot
-- and facing SOUTH (+Z).
-- ------------------------------------------------------------
local function processTree()
local block = blockInFront()
if not block then
print("Planting spot is empty.")
plantSapling(nil)
return
end
local preferredSapling = nil
-- --------------------------------------------------------
-- SAPLING
-- --------------------------------------------------------
if isSaplingName(block.name) then
print("Sapling detected: " .. block.name)
preferredSapling = block.name
local grown, logName = growSapling()
if not grown then
print("Leaving sapling for next pass.")
return
end
block = blockInFront()
if not block then
return
end
end
-- --------------------------------------------------------
-- TREE
-- --------------------------------------------------------
if isLogName(block.name) then
print("Tree detected: " .. block.name)
if not preferredSapling then
preferredSapling = saplingFromLog(block.name)
end
-- Remove base trunk.
turtle.dig()
-- Move into the space previously occupied by the trunk.
if not turtle.forward() then
print("ERROR: Could not enter trunk location.")
return
end
-- Cut every connected log.
harvestConnectedLogs()
-- Return to our approach square.
turtle.back()
-- Plant replacement.
plantSapling(preferredSapling)
print("Tree harvested.")
return
end
print("Not a tree or sapling: " .. block.name)
end
-- ------------------------------------------------------------
-- DROP HARVEST INTO CHEST
-- ------------------------------------------------------------
local function dumpHarvest()
goHome()
print("Dropping harvest into chest...")
for slot = 1, 16 do
local item = turtle.getItemDetail(slot)
if item then
local keep =
isBoneMealName(item.name) or
isSaplingName(item.name) or
isFuelName(item.name)
if not keep then
turtle.select(slot)
if not turtle.dropDown() then
print("Chest may be full!")
end
end
end
end
end
-- ------------------------------------------------------------
-- VISIT ONE TREE
-- ------------------------------------------------------------
local function visitTree(tree)
refuel()
-- Stand one block north of the tree.
local approachX = tree.x
local approachZ = tree.z - 1
moveTo(approachX, approachZ)
-- Face south, toward the tree.
face(0)
print("")
print("---------------------------")
print("Tree " .. tree.x .. ", " .. tree.z)
print("---------------------------")
processTree()
-- Deposit after EACH tree.
dumpHarvest()
end
-- ------------------------------------------------------------
-- MAIN PROGRAM
-- ------------------------------------------------------------
print("")
print("================================")
print(" Six Tree Automatic Tree Farm")
print("================================")
print("")
print("Home: " .. HOME_X .. ", " .. HOME_Z)
print("Trees: " .. #trees)
print("")
refuel()
while true do
print("")
print("Beginning farm sweep...")
for i, tree in ipairs(trees) do
print("")
print("Tree " .. i .. " of " .. #trees)
visitTree(tree)
end
goHome()
dumpHarvest()
print("")
print("Sweep complete.")
print("Sleeping " .. CHECK_INTERVAL .. " seconds.")
sleep(CHECK_INTERVAL)
end