mirror of https://github.com/kepler155c/opus
709 lines
16 KiB
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)
|