From fdf9c70338b4bbddb454f270c61eebdbb96edf64 Mon Sep 17 00:00:00 2001 From: "kepler155c@gmail.com" Date: Fri, 21 Dec 2018 19:48:40 -0500 Subject: [PATCH] superTreefarm --- farms/attack.lua | 12 +- farms/superTreefarm.lua | 697 ++++++++++++++++++++++++++++++++++++++++ milo/apis/craft2.lua | 4 +- neural/Scanner.lua | 2 +- 4 files changed, 701 insertions(+), 14 deletions(-) create mode 100644 farms/superTreefarm.lua diff --git a/farms/attack.lua b/farms/attack.lua index 4186adf..6a493ce 100644 --- a/farms/attack.lua +++ b/farms/attack.lua @@ -53,17 +53,6 @@ local sensor = device['plethora:sensor'] turtle.setMovementStrategy('goto') turtle.setPolicy(turtle.policies.attack) -function Point.iterateClosest(spt, ipts) - local pts = Util.shallowCopy(ipts) - return function() - local pt = Point.closest(spt, pts) - if pt then - Util.removeByValue(pts, pt) - return pt - end - end -end - local function findChests() if chest then return { chest } @@ -109,6 +98,7 @@ local function dropOff() end end end + turtle.select(1) end local function normalize(b) diff --git a/farms/superTreefarm.lua b/farms/superTreefarm.lua new file mode 100644 index 0000000..d6ada32 --- /dev/null +++ b/farms/superTreefarm.lua @@ -0,0 +1,697 @@ +--[[ + 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 GPS = require('gps') +local Peripheral = require('peripheral') +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 MAX_SAPLINGS = 32 + +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 HIGH_PT = { x = 0, y = 8, 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 LEAVES = 'minecraft:leaves' +local LOG = 'minecraft:log' +local LOG2 = 'minecraft:log2' +local OAK_LOG = 'minecraft:log:0' +local OAK_PLANK = 'minecraft:planks:0' +local SAPLING = 'minecraft:sapling:0' +local SCANNER = 'plethora:module:2' +local STICK = 'minecraft:stick:0' +local STONE = 'minecraft:stone:0' +local TORCH = 'minecraft:torch:0' + +local ALL_SAPLINGS = { + SAPLING +} + +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) + local equipped = Peripheral.lookup('side/' .. side) + + if equipped and equipped.type == item then + return true + end + + if not turtle.equip(side, rawName or item) then + if not turtle.selectSlotWithQuantity(0) then + error('No slots available') + end + turtle.equip(side) + if not turtle.equip(side, item) then + error('Unable to equip ' .. (rawName or item)) + end + 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') + getCobblestone(8) + + if turtle.has(FURNACE) or 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 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) > 0 or + turtle.getItemCount(LOG2) > 0) then + + print('Storing logs') + turtle.pathfind(Point.above(state.chest)) + turtle.dropDown(LOG) + turtle.dropDown(LOG2) + + for _, sapling in pairs(ALL_SAPLINGS) do + if slots[sapling] and slots[sapling].count > MAX_SAPLINGS then + turtle.dropDown(sapling, slots[sapling].count - MAX_SAPLINGS) + end + end + + turtle.dropDown(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 + turtle.canCraft(TORCH, 4, turtle.getSummedInventory()) 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 + 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 scan(pt, filter) + turtle.pathfind(pt) + + equip('left', 'plethora:scanner', SCANNER) + local raw = peripheral.call('left', 'scan') + equip('left', PICKAXE) + + local blocks = Util.reduce(raw, function(acc, b) + 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 + table.insert(acc, b) + end + end, { }) + + return blocks +end + +local function plantTrees() + local blocks = scan(HOME_PT, function(b) + return b.name == 'minecraft:sapling' + end) + + local plant = Util.reduce(state.trees, function(acc, b) + for _, sapling in pairs(blocks) do + if sapling.x == b.x and sapling.z == b.z then + return + end + end + table.insert(acc, b) + end, { }) + + Point.eachClosest(turtle.point, plant, function(pt) + turtle.digAt(pt) + turtle.placeAt(pt, randomSapling()) + turtle.select(1) + 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) + + turtle.setPolicy("digAttack") + + for pt in Point.iterateClosest(turtle.point, blocks) do + turtle.digAt(pt) + end + + desperateRefuel(FUEL_BASE + 100) + turtle.clearMoveCallback() + turtle.setPolicy("attackOnly") + + return true +end + +local function fell() + local blocks = scan(HOME_PT, function(b) + return b.y > 0 and (b.name == LEAVES or b.name == LOG or b.name == LOG2) + end) + + if #blocks > 0 then + print('Chopping') + + local fuel = turtle.getFuelLevel() + + fellTrees(blocks) + + blocks = scan(HIGH_PT, function(b) + return b.y > 0 and (b.name == LEAVES or b.name == LOG or b.name == LOG2) + end) + + fellTrees(blocks) + + print('Used ' .. (fuel - turtle.getFuelLevel()) .. ' fuel') + + plantTrees() + end + + return true +end + +local function moreTrees() + if #state.trees > 1 then + return + end + + if not state.chest 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 + if x ~= 0 or z ~= 0 then + table.insert(state.trees, { x = x, y = 0, z = z }) + end + end + end + + turtle.digAt(singleTree) + + setState('trees', state.trees) + + Point.eachClosest(turtle.point, state.trees, function(pt) + turtle.placeDownAt(pt, randomSapling()) + 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 = 16, + 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() + equip('right', MODEM, 'modem') + equip('left', PICKAXE) + + local slots = turtle.getSummedInventory() + + if not slots[CHEST] or not slots[CRAFTING_TABLE] or not slots[SCANNER] then + error('A chest and crafting table must be in inventory') + 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 = '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 = '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.reset() + turtle.addFeatures('level', 'crafting') + turtle.setPolicy("attack") + + 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 diff --git a/milo/apis/craft2.lua b/milo/apis/craft2.lua index daa76bd..4c74ce1 100644 --- a/milo/apis/craft2.lua +++ b/milo/apis/craft2.lua @@ -206,7 +206,7 @@ function Craft.craftRecipeInternal(recipe, count, storage, origItem) count = canCraft end -_G._debug({'eval', recipe.result, count }) +--_G._debug({'eval', recipe.result, count }) --local maxCount = math.floor((recipe.maxCount or 64) / recipe.count) local maxCount = recipe.maxCount or math.floor(64 / recipe.count) @@ -239,7 +239,7 @@ _G._debug({'eval', recipe.result, count }) while canCraft > 0 do local batch = math.min(canCraft, maxCount) local machine = Craft.machineLookup[recipe.result] -_G._debug({ 'crafting', recipe.result, batch }) +--_G._debug({ 'crafting', recipe.result, batch }) if machine then if not machineCraft(recipe, storage, machine, request, batch, origItem) then break diff --git a/neural/Scanner.lua b/neural/Scanner.lua index b0ed15f..16c8fd4 100644 --- a/neural/Scanner.lua +++ b/neural/Scanner.lua @@ -8,7 +8,7 @@ local device = _G.device local peripheral = _G.peripheral local scanner = device.neuralInterface or device['plethora:scanner'] or peripheral.find('manipulator') -if not scanner or not scanner.sense then +if not scanner or not scanner.scan then error('Plethora scanner must be equipped') end