Files
opus-apps/farms/superTreefarm.lua
kepler155c@gmail.com 3d0dc8528d bug due to api rework
2019-02-09 05:29:45 -05:00

784 lines
18 KiB
Lua

local GPS = require('gps')
local Point = require('point')
local Util = require('util')
local fs = _G.fs
local os = _G.os
local peripheral = _G.peripheral
local turtle = _G.turtle
local STARTUP_FILE = 'usr/autorun/superTreefarm.lua'
local FUEL_BASE = 0
local FUEL_DIRE = FUEL_BASE + 10
local FUEL_GOOD = FUEL_BASE + 2000
local MIN_CHARCOAL = 24
local MIN_SAPLINGS = 32
local MAX_SAPLINGS = 48
local GRID = {
TL = { x = 8, y = 0, z = -7 },
TR = { x = 8, y = 0, z = 8 },
BL = { x = -7, y = 0, z = -7 },
BR = { x = -7, 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 APPLE = 'minecraft:apple:0'
local CHARCOAL = 'minecraft:coal:1'
local CHEST = 'minecraft:chest:0'
local COBBLESTONE = 'minecraft:cobblestone:0'
local CRAFTING_TABLE = 'minecraft:crafting_table:0'
local PICKAXE = 'minecraft:diamond_pickaxe'
local DIRT = 'minecraft:dirt:0'
local FURNACE = 'minecraft:furnace:0'
local MODEM = 'computercraft:peripheral:1'
local LOG = 'minecraft:log'
local LOG2 = 'minecraft:log2'
local OAK_LOG = 'minecraft:log:0'
local OAK_PLANK = 'minecraft:planks:0'
local OAK_SAPLING = 'minecraft:sapling:0'
local SAPLING = 'minecraft:sapling'
local SCANNER = 'plethora:module:2'
local SENSOR = 'plethora:module:3'
local STICK = 'minecraft:stick:0'
local STONE = 'minecraft:stone:0'
local TORCH = 'minecraft:torch:0'
local ALL_SAPLINGS = { }
local state = Util.readTable('usr/config/superTreefarm') or {
trees = {
{ x = 1, y = 0, z = 0 }
}
}
local clock = os.clock()
local function equip(side, item, rawName)
-- is it already equipped on the correct side?
local equipped = peripheral.getType(side)
if equipped == item then
return true
end
-- is it equipped on the opposite side?
-- will not work for non-peripheral items :(
local osides = { left = 'right', right = 'left' }
if peripheral.getType(osides[side]) == item then
if not turtle.selectSlotWithQuantity(0) then
error('No slots available')
end
turtle.equip(osides[side])
elseif not turtle.has(rawName or item) then
-- don't have the item - unequip that side to see if it's the correct item
if not turtle.selectSlotWithQuantity(0) then
error('No slots available')
end
turtle.equip(side)
end
-- TODO: if the non-peripheral item was equipped on the other side, then this will not work
if not turtle.has(rawName or item) then
error('Missing ' .. (rawName or item))
end
if not turtle.equip(side, rawName or item) then
error('Unable to equip ' .. (rawName or item))
end
turtle.select(1)
end
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/superTreefarm', 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, msg
if safePlaceBlock(CHEST) then
os.sleep(.2) -- needed for minecraft 1.12
Util.print('Crafting %d %s', (qty or 1), item)
success, msg = turtle.craftItem(item, qty or 1, {
side = 'top',
direction = 'down',
})
repeat until not turtle.suckUp()
if not success then
print(msg)
end
turtle.digUp()
end
return success
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 cook(item, count, result, fuel, fuelCount)
emptyFurnace()
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))
local lastSuck = os.clock()
repeat
os.sleep(1)
if turtle.suckUp() then
lastSuck = os.clock()
end
if os.clock() - lastSuck > 10 then
-- sponge bug
Util.print('Timed out waiting for furnace')
return
end
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 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._goto(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')
if not turtle.has(FURNACE) then
getCobblestone(8)
end
if turtle.has(FURNACE) or craftItem(FURNACE) then
turtle.drop(COBBLESTONE)
local furnacePt = { x = GRID.BL.x + 1, y = 1, z = GRID.BL.z + 1 }
turtle.placeAt(furnacePt, FURNACE)
setState('furnace', furnacePt)
end
end
turtle.addWorldBlock(state.furnace)
end
local function createChests()
if state.chest then
return
end
if turtle.getFuelLevel() > FUEL_GOOD and
turtle.canCraft(CHEST, 4, turtle.getSummedInventory()) then
print('Adding storage')
if turtle.has(CHEST, 2) or craftItem(CHEST, 2) then
local pt = Point.copy(GRID.BL)
pt.x = pt.x + 1
pt.y = pt.y - 1
pt.z = pt.z + 1
turtle.digDownAt(pt)
turtle.placeDown(CHEST)
pt.z = pt.z + 1
turtle.digDownAt(pt)
turtle.placeDown(CHEST)
setState('chest', Util.shallowCopy(pt))
turtle.drop(DIRT)
turtle.refuel(OAK_PLANK)
end
end
return true
end
local function dropOffItems()
if state.chest then
local slots = turtle.getSummedInventory()
if state.chest and
slots[CHARCOAL] and
slots[CHARCOAL].count >= MIN_CHARCOAL and
(turtle.getItemCount(LOG) > 16 or
turtle.getItemCount(LOG2) > 16) then
print('Storing logs')
turtle.pathfind(Point.above(state.chest))
turtle.dropDown(LOG)
turtle.dropDown(LOG2)
for _, sapling in pairs(ALL_SAPLINGS) do
if sapling.count > MAX_SAPLINGS then
turtle.dropDown(sapling.key, sapling.count - MAX_SAPLINGS)
end
end
turtle.dropDown(APPLE)
end
end
return true
end
local function eatSaplings()
Util.each(ALL_SAPLINGS, function(sapling)
if sapling.count > MAX_SAPLINGS then
turtle.refuel(sapling.key, sapling.count - MAX_SAPLINGS)
end
end)
return true
end
local function placeTorches()
if state.torches then
return
end
local slots = turtle.getSummedInventory()
if turtle.getFuelLevel() > 100 and
slots[CHARCOAL] and
slots[CHARCOAL].count >= MIN_CHARCOAL and
turtle.canCraft(TORCH, 4, slots) then
print('Placing torches')
if turtle.has(TORCH, 4) or 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
turtle.addWorldBlocks(pts)
Point.eachClosest(turtle.point, pts, function(pt)
turtle.placeDownAt(pt, TORCH)
end)
turtle.refuel(STICK)
turtle.refuel(OAK_PLANK)
setState('torches', pts)
end
end
return true
end
local function countSaplings()
local slots = turtle.getSummedInventory()
ALL_SAPLINGS = { }
for _, slot in pairs(slots) do
if slot.name == SAPLING then
table.insert(ALL_SAPLINGS, slot)
end
end
if #ALL_SAPLINGS == 0 then
table.insert(ALL_SAPLINGS, { name = OAK_SAPLING, count = 0 })
end
return true
end
local function randomSapling()
local sapling = ALL_SAPLINGS[math.random(1, #ALL_SAPLINGS)]
if sapling.count > 0 then
sapling.count = sapling.count - 1
return sapling.key
end
end
local function makeKey(b)
return table.concat({ b.x, b.y, b.z }, ':')
end
local function findDroppedSaplings()
equip('left', 'plethora:sensor', SENSOR)
local raw = peripheral.call('left', 'sense')
local sensed = Util.reduce(raw, function(acc, b)
Point.rotate(b, state.home.heading)
b.x = Util.round(b.x) + turtle.point.x
b.y = math.ceil(b.y) + turtle.point.y
b.z = Util.round(b.z) + turtle.point.z
if b.y == 0 and string.find(b.displayName, 'sapling', 1, true) then
b.sapling = true
acc[makeKey(b)] = b
end
end, { })
return sensed
end
local function scan(pt, filter, blocks)
turtle.pathfind(pt)
equip('left', 'plethora:scanner', SCANNER)
local raw = peripheral.call('left', 'scan')
return Util.reduce(raw, function(acc, b)
if b.y >= 0 then
Point.rotate(b, state.home.heading)
b.x = b.x + turtle.point.x
b.y = b.y + turtle.point.y
b.z = b.z + turtle.point.z
if filter(b) then
acc[makeKey(b)] = b
end
end
end, blocks or { })
end
local function getPlantLocations(blocks)
countSaplings()
Util.each(state.trees, function(sapling)
local key = makeKey(sapling)
local b = blocks[key]
if b then
if b.name == SAPLING then
blocks[key] = nil
else
b.plant = randomSapling()
end
return
end
b = Util.shallowCopy(sapling)
b.plant = randomSapling()
if b.plant then
blocks[key] = b
end
end)
end
local function fellTrees(blocks)
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.point.y == 0 then
if #state.trees == 1 and turtle.getFuelLevel() == 0 then
turtle.dig()
end
turtle.up()
end
for pt in Point.iterateClosest(turtle.point, blocks) do
if pt.y == 0 then
if pt.sapling then
repeat until not turtle.suckDownAt(pt)
else
turtle.digDownAt(pt)
if pt.plant then
turtle.placeDown(pt.plant)
turtle.select(1)
end
end
else
turtle.digAt(pt)
end
end
desperateRefuel(FUEL_BASE + 100)
turtle.clearMoveCallback()
return true
end
local function fell()
local function filter(b)
return b.y >= 0 and (b.name == LOG or b.name == LOG2 or b.name == SAPLING)
end
local fuel = turtle.getFuelLevel()
local sensed = { }
-- determine if we need saplings
if not Util.every(ALL_SAPLINGS, function(sapling)
return sapling.count >= MIN_SAPLINGS
end) then
sensed = findDroppedSaplings()
end
-- low scan
local blocks = scan(HOME_PT, filter)
local pt = Util.shallowCopy(HOME_PT)
while Util.any(blocks, function(b) return b.y > pt.y + 6 end) do
-- tree might be above low scan range, do a scan higher up
equip('left', PICKAXE)
pt.y = pt.y + 8
blocks = scan(pt, filter, blocks)
end
Util.merge(blocks, sensed)
-- add any locations that need saplings
getPlantLocations(blocks)
equip('left', PICKAXE)
if not Util.empty(blocks) then
print('Chopping')
fellTrees(blocks)
print('Used ' .. (fuel - turtle.getFuelLevel()) .. ' fuel')
end
return true
end
local function moreTrees()
if #state.trees > 1 then
return
end
if not state.chest or turtle.getItemCount(OAK_SAPLING) < 2 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, 1 do
if x ~= 0 or z ~= 0 then
local tree = { x = x, y = 0, z = z }
table.insert(state.trees, tree)
turtle.addWorldBlock(tree)
end
end
end
turtle.digAt(singleTree)
setState('trees', state.trees)
countSaplings()
Point.eachClosest(turtle.point, state.trees, function(pt)
local sapling = randomSapling()
if sapling then
turtle.placeDownAt(pt, sapling)
end
end)
end
local function findHome()
local pt = GPS.getPoint(2) or error('GPS not found')
equip('left', SCANNER)
local facing = peripheral.call('left', 'getBlockMeta', 0, 0, 0).state.facing
pt.heading = Point.facings[facing].heading
equip('left', PICKAXE)
if not state.home then
setState('home', pt)
end
-- convert to relative coordinates
turtle.setPoint({
x = pt.x - state.home.x,
y = pt.y - state.home.y,
z = pt.z - state.home.z,
heading = pt.heading,
})
Point.rotate(turtle.point, state.home.heading)
turtle.setHeading(state.home.heading)
turtle.point.heading = 0
turtle.setPathingBox({
x = GRID.TL.x,
y = GRID.TL.y,
z = GRID.TL.z,
ex = GRID.BR.x,
ey = 32,
ez = GRID.BR.z,
})
turtle.setPersistent(true)
turtle.addWorldBlocks(state.trees)
if state.torches and type(state.torches) == 'table' then
turtle.addWorldBlocks(state.trees)
end
end
local function returnHome()
turtle.pathfind(HOME_PT)
return true
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()
equip('right', 'modem', MODEM)
equip('left', PICKAXE)
local slots = turtle.getSummedInventory()
if not slots[CHEST] or not slots[CRAFTING_TABLE] or not slots[SCANNER] or not slots[SENSOR] then
error([[
Required:
* chest
* crafting table
* block scanner
* entity sensor]])
end
if not fs.exists(STARTUP_FILE) then
Util.writeFile(STARTUP_FILE,
[[os.sleep(1)
shell.openForegroundTab('superTreefarm.lua')]])
print('Autorun program created: ' .. STARTUP_FILE)
end
end
local tasks = {
{ desc = 'Startup check', fn = startupCheck },
{ desc = 'Finding home', fn = findHome },
{ desc = 'Creating furnace', fn = createFurnace },
{ desc = 'Creating chest', fn = createChests },
{ desc = 'Counting saplings', fn = countSaplings },
{ desc = 'Adding trees', fn = moreTrees },
{ desc = 'Emptying furnace', fn = emptyFurnace },
{ desc = 'Chopping', fn = fell },
{ desc = 'Snacking', fn = eatSaplings },
{ desc = 'Making charcoal', fn = makeSingleCharcoal },
{ desc = 'Making charcoal', fn = makeCharcoal },
--{ desc = 'Placing torches', fn = placeTorches },
{ desc = 'Refueling', fn = refuel },
{ desc = 'Dropping off items', fn = dropOffItems },
{ desc = 'Condensing', fn = turtle.condense },
{ desc = 'Returning home', fn = returnHome },
{ desc = 'Sleeping', fn = updateClock },
}
local s, m = turtle.run(function()
turtle.reset()
require('core.turtle.crafting')
--turtle.addFeatures('core.crafting')
turtle.set({
attackPolicy = 'attack',
digPolicy = 'dig',
})
while not turtle.isAborted() do
print('fuel: ' .. turtle.getFuelLevel())
for _,task in ipairs(Util.shallowCopy(tasks)) do
--print(task.desc)
turtle.setStatus(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