_G.requireInjector() --[[ Requirements: Place turtle against an oak tree or oak sapling Area around turtle must be flat and can only be dirt or grass (10 blocks in each direction from turtle) Turtle must have: crafting table, chest Turtle must have a pick equipped on the left side Optional: Add additional sapling types that can grow with a single sapling Notes: If the turtle does not get any saplings from the initial tree, place down another sapling in front of the turtle. The program will be able to survive server restarts as long as it has created the cobblestone line. If the program is stopped before that time, place the turtle in the original position before restarting the program. ]]-- local ChestAdapter = require('chestAdapter18') local Craft = require('turtle.craft') local Level = require('turtle.level') local Pathing = require('turtle.pathfind') local Point = require('point') local Util = require('util') local os = _G.os local read = _G.read local turtle = _G.turtle local FUEL_BASE = 0 local FUEL_DIRE = FUEL_BASE + 10 local FUEL_GOOD = FUEL_BASE + 2000 local MIN_CHARCOAL = 24 local MAX_SAPLINGS = 32 local GRID_WIDTH = 8 local GRID_LENGTH = 10 local GRID = { TL = { x = 8, y = 0, z = -8 }, TR = { x = 8, y = 0, z = 8 }, BL = { x = -10, y = 0, z = -8 }, BR = { x = -10, y = 0, z = 8 }, } local HOME_PT = { x = 0, y = 0, z = 0, heading = 0 } local DIG_BLACKLIST = { [ 'minecraft:furnace' ] = true, [ 'minecraft:lit_furnace' ] = true, [ 'minecraft:chest' ] = true, } local COBBLESTONE = 'minecraft:cobblestone:0' local CHARCOAL = 'minecraft:coal:1' local OAK_LOG = 'minecraft:log:0' local OAK_PLANK = 'minecraft:planks:0' local CHEST = 'minecraft:chest:0' local FURNACE = 'minecraft:furnace:0' local SAPLING = 'minecraft:sapling:0' local STONE = 'minecraft:stone:0' local TORCH = 'minecraft:torch:0' local DIRT = 'minecraft:dirt:0' local APPLE = 'minecraft:apple:0' local STICK = 'minecraft:stick:0' local CRAFTING_TABLE = 'minecraft:crafting_table:0' local ALL_SAPLINGS = { SAPLING } local state = Util.readTable('usr/config/treefarm') or { trees = { { x = 1, y = 0, z = 0 } } } local clock = os.clock() local recipes = Util.readTable('usr/etc/recipes.db') or { } Craft.setRecipes(recipes) local function inspect(fn) local s, item = fn() if s and item then return item.name .. ':' .. item.metadata end return 'minecraft:air:0' end local function setState(key, value) state[key] = value Util.writeTable('usr/config/treefarm', state) end local function refuel() if turtle.getFuelLevel() < FUEL_GOOD then local charcoal = turtle.getItemCount(CHARCOAL) if charcoal > 1 then turtle.refuel(CHARCOAL, math.min(charcoal - 1, MIN_CHARCOAL / 2)) print('fuel: ' .. turtle.getFuelLevel()) end end return true end local function safePlaceBlock(item) if turtle.placeUp(item) then return true end local s, m = turtle.inspectUp() if s and not DIG_BLACKLIST[m.name] then turtle.digUp() return turtle.placeUp(item) end turtle.forward() return turtle.placeUp(item) end local function craftItem(item, qty) local success if safePlaceBlock(CHEST) then if turtle.equip('left', 'minecraft:crafting_table') then local chestAdapter = ChestAdapter({ wrapSide = 'top', direction = 'down', }) if not chestAdapter:isValid() then print('invalid chestAdapter') read() end Util.print('Crafting %d %s', (qty or 1), item) success = Craft.craftRecipe(recipes[item], qty or 1, chestAdapter) repeat until not turtle.suckUp() end turtle.equip('left', 'minecraft:diamond_pickaxe') turtle.digUp() end return success end local function cook(item, count, result, fuel, fuelCount) setState('cooking', true) fuel = fuel or CHARCOAL fuelCount = fuelCount or math.ceil(count / 8) Util.print('Making %d %s', count, result) turtle.dropForwardAt(state.furnace, fuel, fuelCount) turtle.dropDownAt(state.furnace, item, count) count = count + turtle.getItemCount(result) turtle.select(1) turtle.pathfind(Point.below(state.furnace)) repeat os.sleep(1) turtle.suckUp() until turtle.getItemCount(result) >= count setState('cooking') end local function makeSingleCharcoal() local slots = turtle.getSummedInventory() if not state.furnace or slots[CHARCOAL] or not slots[OAK_LOG] or slots[OAK_LOG].count < 2 then return true end turtle.faceAgainst(state.furnace) if craftItem(OAK_PLANK) then cook(OAK_LOG, 1, CHARCOAL, OAK_PLANK, 1) turtle.refuel(OAK_PLANK) end return true end local function makeCharcoal() local slots = turtle.getSummedInventory() if not state.furnace or not slots[CHARCOAL] or slots[CHARCOAL].count >= MIN_CHARCOAL then return true end local function getLogSlot() local maxslot = { count = 0 } for k,slot in pairs(slots) do if string.match(k, 'minecraft:log') then if slot.count > maxslot.count then maxslot = slot end end end return maxslot end repeat slots = turtle.getSummedInventory() local charcoal = slots[CHARCOAL].count local slot = getLogSlot(slots) if slot.count < 8 then break end local toCook = math.min(charcoal, math.floor(slot.count / 8)) toCook = math.min(toCook, math.floor((MIN_CHARCOAL + 8 - charcoal) / 8)) toCook = toCook * 8 cook(slot.key, toCook, CHARCOAL) until charcoal + toCook >= MIN_CHARCOAL return true end local function emptyFurnace() if state.cooking then print('Emptying furnace') turtle.suckDownAt(state.furnace) turtle.suckForwardAt(state.furnace) turtle.suckUpAt(state.furnace) setState('cooking') end end local function getCobblestone(count) local slots = turtle.getSummedInventory() if not slots[COBBLESTONE] or slots[COBBLESTONE].count < count then print('Collecting cobblestone') slots[COBBLESTONE] = true slots[DIRT] = true local pt = Point.copy(GRID.BR) pt.x = GRID.BR.x + 2 pt.z = GRID.BR.z - 2 turtle.pathfind(pt) repeat turtle.select(1) turtle.digDown() turtle.down() for _ = 1, 4 do if inspect(turtle.inspect) == STONE then turtle.dig() end turtle.turnRight() end for item in pairs(turtle.getSummedInventory()) do if not slots[item] then turtle.drop(item) end end until turtle.getItemCount(COBBLESTONE) >= count turtle.gotoPoint(pt) turtle.placeDown(DIRT) turtle.drop(DIRT) end end local function createFurnace() if not state.furnace then if turtle.getFuelLevel() < FUEL_BASE + 100 then return true -- try again later end print('Adding a furnace') getCobblestone(8) if craftItem(FURNACE) then turtle.drop(COBBLESTONE) local furnacePt = { x = GRID.BL.x + 2, y = 1, z = GRID.BL.z + 2 } turtle.placeAt(furnacePt, FURNACE) setState('furnace', furnacePt) end end end local function createPerimeter() if not state.perimeter then if not state.furnace or turtle.getFuelLevel() < FUEL_BASE + 500 or turtle.getItemCount(OAK_LOG) == 0 or not craftItem(OAK_PLANK, 2) then return true end print('Creating a perimeter') getCobblestone(GRID_WIDTH * 2 + 1) cook(COBBLESTONE, 2, STONE, OAK_PLANK, 2) turtle.refuel(OAK_PLANK) turtle.pathfind(GRID.BL) turtle.digDown() turtle.placeDown(STONE) turtle.setMoveCallback(function() local target = COBBLESTONE if math.abs(turtle.point.x) == GRID_LENGTH and math.abs(turtle.point.z) == GRID_WIDTH then target = STONE end if inspect(turtle.inspectDown) ~= target then turtle.digDown() turtle.placeDown(target) end end) turtle.pathfind(GRID.BR) turtle.clearMoveCallback() turtle.drop(COBBLESTONE) turtle.drop(DIRT) setState('perimeter', true) end end local function createChests() if state.chest_1 then return end if state.perimeter and turtle.getFuelLevel() > FUEL_GOOD and Craft.canCraft(CHEST, 4, turtle.getSummedInventory()) then print('Adding storage') if craftItem(CHEST, 4) then local pt = Point.copy(GRID.BL) pt.x = pt.x + 1 pt.y = pt.y - 1 for i = 1, 2 do pt.z = pt.z + 1 turtle.digDownAt(pt) turtle.placeDown(CHEST) pt.z = pt.z + 1 turtle.digDownAt(pt) turtle.placeDown(CHEST) setState('chest_' .. i, Util.shallowCopy(pt)) pt.z = pt.z + 1 end turtle.drop(DIRT) turtle.refuel(OAK_PLANK) end end return true end local function dropOffItems() if state.chest_1 then local slots = turtle.getSummedInventory() if state.chest_1 and slots[CHARCOAL] and slots[CHARCOAL].count >= MIN_CHARCOAL and (turtle.getItemCount('minecraft:log') > 0 or turtle.getItemCount('minecraft:log2') > 0) then print('Storing logs') turtle.pathfind(Point.above(state.chest_1)) turtle.dropDown('minecraft:log') turtle.dropDown('minecraft:log2') end if slots[APPLE] then print('Storing apples') turtle.dropDownAt(state.chest_2, APPLE) end end return true end local function eatSaplings() local slots = turtle.getSummedInventory() for _, sapling in pairs(ALL_SAPLINGS) do if slots[sapling] and slots[sapling].count > MAX_SAPLINGS then turtle.refuel(sapling, slots[sapling].count - MAX_SAPLINGS) end end return true end local function placeTorches() if state.torches then return end if turtle.getFuelLevel() > 100 and Craft.canCraft(TORCH, 4, turtle.getSummedInventory()) then print('Placing torches') if craftItem(TORCH, 4) then local pts = { } for x = -4, 4, 8 do for z = -4, 4, 8 do table.insert(pts, { x = x, y = 0, z = z }) end end Point.eachClosest(turtle.point, pts, function(pt) turtle.placeAt(pt, TORCH) end) turtle.refuel(STICK) turtle.refuel(OAK_PLANK) setState('torches', true) end end return true end local function randomSapling() local sapling = SAPLING if #state.trees > 1 then ALL_SAPLINGS = { } local slots = turtle.getFilledSlots() for _, slot in pairs(slots) do if slot.name == 'minecraft:sapling' then table.insert(ALL_SAPLINGS, slot.key) end end if #ALL_SAPLINGS > 0 then sapling = ALL_SAPLINGS[math.random(1, #ALL_SAPLINGS)] end end return sapling end local function fellTree(pt) local function desperateRefuel(min) if turtle.getFuelLevel() < min then local logs = turtle.getItemCount(OAK_LOG) if logs > 0 then if craftItem(OAK_PLANK, math.min(8, logs * 4)) then turtle.refuel(OAK_PLANK) print('fuel: ' .. turtle.getFuelLevel()) end end end end turtle.setMoveCallback(function() desperateRefuel(FUEL_DIRE) end) desperateRefuel(FUEL_DIRE) if turtle.digUpAt(Point.above(pt)) then Level( { x = GRID_WIDTH-1, y = 1, z = GRID_WIDTH-1 }, { x = -(GRID_WIDTH-1), y = 50, z = -(GRID_WIDTH-1) }, Point.above(pt)) end desperateRefuel(FUEL_BASE + 100) turtle.clearMoveCallback() turtle.setPolicy("attack") return true end local function fell() local pts = Util.shallowCopy(state.trees) local rpt = table.remove(pts, math.random(1, #pts)) -- give the pathfinder hints about what to avoid (state.trees) if not turtle.faceAgainst(rpt, { blocks = Util.shallowCopy(state.trees) }) or not string.match(inspect(turtle.inspect), 'minecraft:log') then return true end print('Chopping') local fuel = turtle.getFuelLevel() -- push this point to the start of this list table.insert(pts, 1, rpt) Point.eachClosest(turtle.point, pts, function(pt) if turtle.faceAgainst(pt, { blocks = Util.shallowCopy(state.trees) }) and string.match(inspect(turtle.inspect), 'minecraft:log') then turtle.dig() fellTree(pt) end turtle.placeAt(pt, randomSapling()) turtle.select(1) end) print('Used ' .. (fuel - turtle.getFuelLevel()) .. ' fuel') return true end local function moreTrees() if #state.trees > 1 then return end if not state.chest_1 or turtle.getItemCount('minecraft:sapling') < 15 then return true end print('Adding more trees') local singleTree = state.trees[1] state.trees = { } for x = -2, 2, 1 do for z = -2, 2, 2 do table.insert(state.trees, { x = x, y = 0, z = z }) end end turtle.digAt(singleTree) fellTree(singleTree) setState('trees', state.trees) Point.eachClosest(turtle.point, state.trees, function(pt) turtle.placeDownAt(pt, randomSapling()) end) end local function getTurtleFacing(block) local directions = { [5] = 2, [3] = 3, [4] = 0, [2] = 1, } if not safePlaceBlock(block) then error('unable to place chest above') end local _, bi = turtle.inspectUp() turtle.digUp() return directions[bi.metadata] end local function saveTurtleFacing() if not state.facing then setState('facing', getTurtleFacing(CHEST)) end end local function findGround() print('Locating ground level') turtle.setPoint(HOME_PT) while true do local s, block = turtle.inspectDown() if not s then block = { name = 'minecraft:air', metadata = 0 } end local b = block.name .. ':' .. block.metadata if b == 'minecraft:dirt:0' or b == 'minecraft:grass:0' or block.name == 'minecraft:chest' then break end if b == COBBLESTONE or b == STONE then error('lost') end if b == TORCH or DIG_BLACKLIST[block.name] then turtle.forward() else turtle.digDown() turtle.down() end if turtle.point.y < -20 then error('lost') end end turtle.setPoint(HOME_PT) end local function findHome() if not state.perimeter then return end print('Determining location') turtle.point.heading = getTurtleFacing(CHEST) turtle.setHeading(state.facing) turtle.point.heading = 0 local pt = Point.copy(turtle.point) while inspect(turtle.inspectDown) ~= COBBLESTONE do pt.x = pt.x - 1 turtle.pathfind(pt) if pt.x < -20 then error('lost') end end while inspect(turtle.inspectDown) == COBBLESTONE do pt.z = pt.z - 1 turtle.pathfind(pt) if pt.z < -20 then error('lost') end end turtle.setPoint({ x = -(GRID_LENGTH), y = 0, z = -GRID_WIDTH, heading = turtle.point.heading }) -- when pathfinding - don't leave this box Pathing.setBox({ x = GRID.TL.x, y = GRID.TL.y, z = GRID.TL.z, ex = GRID.BR.x, ey = 5, ez = GRID.BR.z, }) end local function updateClock() local ONE_HOUR = 50 if os.clock() - clock > ONE_HOUR then clock = os.clock() else print('sleeping for ' .. math.floor(ONE_HOUR - (os.clock() - clock))) os.sleep(ONE_HOUR - (os.clock() - clock)) clock = os.clock() end return true end local function startupCheck() local slots = turtle.getSummedInventory() if not slots[CHEST] or not slots[CRAFTING_TABLE] then error('A chest and crafting table must be in inventory') end if state.facing and not state.perimeter then print('Perimeter has not been established.') print('Enter to continue if turtle is in the original starting position.') read() end end local tasks = { { desc = 'Startup check', fn = startupCheck }, { desc = 'Finding ground', fn = findGround }, { desc = 'Determine facing', fn = saveTurtleFacing }, { desc = 'Finding home', fn = findHome }, { desc = 'Emptying furnace', fn = emptyFurnace }, { desc = 'Adding trees', fn = moreTrees }, { desc = 'Chopping', fn = fell }, { desc = 'Snacking', fn = eatSaplings }, { desc = 'Creating chest', fn = createChests }, { desc = 'Creating furnace', fn = createFurnace }, { desc = 'Making charcoal', fn = makeSingleCharcoal }, { desc = 'Making charcoal', fn = makeCharcoal }, { desc = 'Creating perimeter', fn = createPerimeter }, { desc = 'Placing torches', fn = placeTorches }, { desc = 'Refueling', fn = refuel }, { desc = 'Dropping off items', fn = dropOffItems }, { desc = 'Condensing', fn = turtle.condense }, { desc = 'Sleeping', fn = updateClock }, } local s, m = turtle.run(function() turtle.setPolicy("attack") while not turtle.abort do print('fuel: ' .. turtle.getFuelLevel()) for _,task in ipairs(Util.shallowCopy(tasks)) do --print(task.desc) turtle.status = task.desc turtle.select(1) if not task.fn() then Util.filterInplace(tasks, function(v) return v.fn ~= task.fn end) end end end end) if not s then error(m or 'Failed') end