opus/sys/apps/treefarm.lua

709 lines
16 KiB
Lua

requireInjector(getfenv(1))
--[[
Requirements:
Place turtle against an oak tree or oak sapling
Area around turtle must be flat and can only be dirt or grass
(9 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 cobble line. If the program is stopped before that time,
place the turtle in the original position before restarting the program.
]]--
local ChestProvider = require('chestProvider18')
local Craft = require('turtle.craft')
local Level = require('turtle.level')
local Point = require('point')
local Util = require('util')
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 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('sys/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 chestProvider = ChestProvider({
wrapSide = 'top',
direction = 'down',
})
if not chestProvider:isValid() then
print('invalid chestProvider')
read()
end
-- turtle.emptyInventory(turtle.dropUp)
Util.print('Crafting %d %s', (qty or 1), item)
success = Craft.craftRecipe(recipes[item], qty or 1, chestProvider)
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(slots)
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
local 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 i = 1, 3 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 false
end
if state.perimeter and
turtle.getFuelLevel() > FUEL_BASE + 100 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(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 Craft.canCraft(TORCH, 4, turtle.getSummedInventory()) and
turtle.getFuelLevel() > 100 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
sapling = ALL_SAPLINGS[math.random(1, #ALL_SAPLINGS)]
end
return sapling
end
local function fellTree(pt)
local function desparateRefuel(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() desparateRefuel(FUEL_DIRE) end)
desparateRefuel(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
desparateRefuel(FUEL_BASE + 100)
turtle.clearMoveCallback()
turtle.setPolicy("attack")
return true
end
local function fell()
local pts = Util.shallowCopy(state.trees)
local pt = table.remove(pts, math.random(1, #pts))
if not turtle.faceAgainst(pt) or
not string.match(inspect(turtle.inspect), 'minecraft:log') then
return true
end
print('Chopping')
local fuel = turtle.getFuelLevel()
table.insert(pts, 1, pt)
Point.eachClosest(turtle.point, pts, function(pt)
if turtle.faceAgainst(pt) and
string.match(inspect(turtle.inspect), 'minecraft:log') then
turtle.dig()
fellTree(pt)
end
turtle.placeAt(pt, randomSapling())
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(SAPLING) < 9 then
return true
end
print('Adding more trees')
local singleTree = state.trees[1]
state.trees = { }
for x = -2, 2, 2 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
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
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
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 b == FURNACE 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 < -16 then
error('lost')
end
end
while inspect(turtle.inspectDown) == COBBLESTONE do
pt.z = pt.z - 1
turtle.pathfind(pt)
if pt.z < -16 then
error('lost')
end
end
turtle.setPoint({
x = -(GRID_LENGTH),
y = 0,
z = -GRID_WIDTH,
heading = turtle.point.heading
})
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 tasks = {
{ desc = 'Finding ground', fn = findGround },
{ desc = 'Determine facing', fn = saveTurtleFacing },
{ desc = 'Finding home', fn = findHome },
{ desc = 'Adding trees', fn = moreTrees },
{ desc = 'Chopping', fn = fell },
{ desc = 'Snacking', fn = eatSaplings },
{ desc = 'Creating chest', fn = createChests },
{ desc = 'Creating furnace', fn = createFurnace },
{ desc = 'Emptying furnace', fn = emptyFurnace },
{ 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 },
}
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)