Initial files

develop
Jef Roosens 2020-12-28 14:45:50 +01:00
parent 1378189ba2
commit 6dc2343ee5
11 changed files with 758 additions and 0 deletions

9
.treefarm.conf 100644
View File

@ -0,0 +1,9 @@
{
width = 3,
depth = 3,
spacing = 3,
border_width = 2,
log_name = 'minecraft:birch_log',
sapling_name = 'minecraft:birch_sapling',
delay = 90,
}

12
dig.log 100644
View File

@ -0,0 +1,12 @@
[INFO] Refueled using non-preferred.
[WARN] Refuel failed. Missing: 8
[ERROR] Insufficient fuel.
[INFO] Refueled using non-preferred.
[INFO] Refueled using non-preferred.
[WARN] Refuel failed. Missing: 23
[ERROR] Insufficient fuel.
[INFO] Refueled using non-preferred.
[INFO] Height is not a multiple of 3.
[WARN] Refuel failed. Missing: 165
[ERROR] Insufficient fuel.
[INFO] Refueled using non-preferred.

85
dig.lua 100644
View File

@ -0,0 +1,85 @@
-- Created by Jef Roosens
--
-- This program lets you dig out a rectangle
-- TODO return to surface option
-- TODO arbitrary height instead of multiple of 3
-- TODO item storage solution
local logger = require("jlib.log").Logger:new{level = 3, filename = "dig.log"}
local input = require("jlib.input")
input.logger = logger
local move = require("jlib.move").move
move.logger = logger
local fuel = require("jlib.fuel")
fuel.logger = logger
local function layer(width, depth, offset)
for i = 1, width do
-- Mine slice
move('f', depth - 1, { turtle.digUp, turtle.digDown })
-- Move to next slice
if i < width then
dir = (i + offset) % 2
if dir == 0 then turtle.turnLeft() else turtle.turnRight() end
move('f', 1, { turtle.digUp, turtle.digDown })
if dir == 0 then turtle.turnLeft() else turtle.turnRight() end
end
end
end
local function dig(width, depth, height, dir)
-- Dig initial blocks
turtle.digUp()
turtle.digDown()
local layers = height / 3
for j = 1, layers do
local offset = 0
if j % 2 == 0 and width % 2 == 0 then offset = 1 end
layer(width, depth, offset)
-- Move down a layer
if j < layers then
move(dir, 3)
if dir == 'd' then turtle.digDown() else turtle.digUp() end
turtle.turnLeft()
turtle.turnLeft()
end
end
end
local function main(args)
-- Take inputs
local width = tonumber(args[1]) or input.read_int("Width: ")
local depth = tonumber(args[2]) or input.read_int("Depth: ")
local height = tonumber(args[3]) or input.read_int("Height: ")
-- TODO add check for dir
local dir = args[4]
-- Make sure height is valid
-- TODO make program work with any height
if height % 3 ~= 0 then
return logger:log(3, "Height is not a multiple of 3.")
end
-- Check fuel level
-- The + 2 accounts for the moving between layers
local fuel_needed = (depth * width + 3) * (height / 3)
if not fuel.check(fuel_needed) then
return logger:log(1, "Insufficient fuel.")
end
-- Do the dig
dig(width, depth, height, dir)
logger:close()
end
main({...})

115
jlib/fuel.lua 100644
View File

@ -0,0 +1,115 @@
-- Created by Jef Roosens
--
-- This module contains functions which ease fuel management in a turtle
-- local turtle = require('turtle')
local module = {}
module.logger = require("jlib.log").Logger:new{level = -1}
-- Refuel the turtle using the given slot; tries to optimize refuel speed by
-- calculating needed refuel count
--
-- @param slot slot to refuel from
-- @param goal refuel goal to reach
-- @return wether the refuel was enough
function module.refuel_from(slot, goal)
-- First, we get the current level
local start = turtle.getFuelLevel()
-- If we've already reached the goal, just return true
if start >= goal then return true end
-- Select the slot and refuel once
turtle.select(slot)
turtle.refuel(1)
-- Get the difference
local level = turtle.getFuelLevel()
local diff = level - start
-- If the diff is 0 the slot is empty or can't refuel, so return false
if diff == 0 then return false end
-- Calculate needed refuels
local needed = math.ceil((goal - level) / diff)
-- Do the refuel
turtle.refuel(needed)
-- Return wether or not we reached the goal
return turtle.getFuelLevel() >= goal
end
-- Check if the turtle has enough fuel; takes a table of preferred items
--
-- @param goal fuel goal to reach
-- @param preferred table containing names of preferred items
-- @return wether or not the fuel level was reached
function module.check(goal, preferred)
-- Convert table to set for faster lookup
local set = {}
if preferred then
for _, v in ipairs(preferred) do
set[v] = true
end
end
-- Turtle needs to be able to get down at all times
local i, done = 1, turtle.getFuelLevel() >= goal
-- Instantly quit if it's already good
-- This check is mostly useful voor the logging aspect
if done then
module.logger:info("No refuel needed.")
return true
end
-- Store all non-preferred options
local options = {}
-- Iterate over all 16 slots, or stop earlier if possible
while not done and i <= 16 do
local data = turtle.getItemDetail(i)
if data and set[data.name] then
done = module.refuel_from(i, goal)
else
table.insert(options, i)
end
i = i + 1
end
-- Exit if goal has been reached
if done then
module.logger:info("Refueled using preferred.")
return true
end
-- Process non-preferred options
for _, j in pairs(options) do
done = module.refuel_from(j, goal)
if done then
module.logger:info("Refueled using non-preferred.")
return true
end
end
module.logger:warning(
"Refuel failed. Missing: " .. goal - turtle.getFuelLevel())
return false
end
return module

28
jlib/input.lua 100644
View File

@ -0,0 +1,28 @@
-- Created by Jef Roosens
--
-- Useful functions for receiving input from the user
local module = {}
module.logger = require("jlib.log").Logger:new{level = -1}
-- Read an integer
--
-- @param prompt prompt string to show
-- @param negative wether or not the number can be negative; defaults to false
function module.read_int(prompt, negative)
while true do
write(prompt)
local num = tonumber(read())
if num and num >= 0 or not negative then
module.logger:info("Input: " .. num)
return num
end
print("Please enter a valid number.")
end
end
return module

90
jlib/log.lua 100644
View File

@ -0,0 +1,90 @@
-- Created by Jef Roosens
--
-- Module to add logging to a program. This module is integrated in all jlib
-- functions.
local module = {}
-- Define class with default values
local Logger = {
level = 2,
level_colors = {
colors.red, colors.orange, colors.yellow, colors.blue, colors.white},
prefixes = { "CRIT", "ERROR", "WARN", "INFO", "DEBUG" },
filename = nil
}
Logger.__index = Logger
-- Return a new logger 'object'.
--
-- @param level log level:
-- (0) critical, (1) errors, (2) warnings, (3) info,(4) debug
-- @param level_colors table containing color names for messages; order is
-- { critical, errors, warnings, info, debug }. Only colors for the needed log
-- level need to be provided: e.g. if the log level is (2) warnings, then only
-- two colors need to be provided. If not provided, defaults to
-- { red, orange, yellow, blue, white }.
-- @param prefixes prefix to show for each log level
-- @param filename log file to write to. If not supplied, log will only be
-- outputted to stdout
-- @return a logger 'object'
function Logger:new(o)
o = o or {}
setmetatable(o, self)
-- Fill in default values where needed
-- TODO find out if this is needed or not
-- for k, v in pairs(Logger) do
-- o[k] = o[k] or Logger[k]
-- end
-- Open file handler
if o.filename then
o._file = open(filename, "a")
end
return o
end
-- Log a message with the given level
--
-- @param level log level; levels higher than logger level get discarded
-- @param message message to log
-- @return wether or not the message was actually logged
function Logger:log(level, message)
-- Exit if the level is too high
if level > self.level then return false end
-- Log to screen
local cur_color = term.getTextColor()
term.setTextColor(self.colors[level + 1])
term.write("[" .. self.prefixes[level + 1] .. "] ")
term.setTextColor(cur_color)
print(message)
-- Log to file
if self._file then
local line = "[" .. self.prefixes[level + 1] .. "] " .. message .. '\n'
self._file.write(line)
self._file.flush()
end
end
-- Just some convenience functions; wrappers around Logger:log
function Logger:critical(message) self.log(0, message) end
function Logger:error(message) self.log(1, message) end
function Logger:warning(message) self.log(2, message) end
function Logger:info(message) self.log(3, message) end
function Logger:debug(message) self.log(4, message) end
-- Close the logger a.k.a. close the log file handler
function Logger:close()
if self._file then self._file.close() end
end
module.Logger = Logger
return module

32
jlib/mine.lua 100644
View File

@ -0,0 +1,32 @@
-- Created by Jef Roosens
local module = {}
module.logger = require("jlib.log").Logger:new{level = -1}
-- Safely dig in given direction (aka account for falling blocks)
--
-- @param dir direction to dig (f, u, d)
-- @return amount of blocks it broke
function module.dig(dir)
local funcs = {
f = { turtle.detect, turtle.dig },
u = { turtle.detectUp, turtle.digUp },
d = { turtle.detectDown, turtle.digDown }
}
local i = 0
while funcs[dir][1]() do
funcs[dir][2]()
i = i + 1
end
module.logger:info("Mined " .. i .. " blocks")
return i
end
return module

50
jlib/move.lua 100644
View File

@ -0,0 +1,50 @@
-- Created by Jef Roosens
--
-- Utilities to ease movement using a turtle
local dig = require("jlib.mine").dig
local module = {}
module.logger = require("jlib.log").Logger:new{level = -1}
-- Safely move n blocks. This means accounting for gravel, trees growing...
--
-- @param dir direction to move (u, d, f)
-- @param n number of blocks to move. If nil (or not supplied), 1 is taken as
-- @param side_effect a function/table of functions which gets called after the
-- turtle has moved 1 block. This can be used to extend the functionality of
-- the function without having to re-write it.
function module.move(dir, n, side_effect)
-- Default value
n = n or 1
local funcs = {
u = turtle.up,
d = turtle.down,
f = turtle.forward
}
for _ = 1, n do
dig(dir)
funcs[dir]()
-- Run the side-effect if given
if side_effect then
if type(side_effect) == "table" then
for _, f in pairs(side_effect) do
f()
end
else
side_effect()
end
end
end
module.logger:info("Moved " .. n .. " blocks")
end
return module

100
tree.lua 100644
View File

@ -0,0 +1,100 @@
-- Created by Jef Roosens
-- TODO: fix bug where if you start a 2x2 tree in the middle, the turtle will still end up on the bottom
local fuel = require('jlib.fuel')
local move = require('jlib.move').move
-- Chop a tree in the given direction
local function chop(tree_type, two_deep, go_down, return_to_start)
local i, data = 0
if go_down then _, data = turtle.inspectDown() else _, data = turtle.inspectUp() end
while data.name == tree_type do
-- Make sure we have enough fuel to get back up
fuel.check(i + 1)
if two_deep then turtle.dig() end
-- This also mines the block
if go_down then move('d') else move('u') end
i = i + 1
if go_down then _, data = turtle.inspectDown() else _, data = turtle.inspectUp() end
end
-- Dig one higher. This allows us to chop jungle trees, which often leave one block higher
if i > 0 and not go_down then
fuel.check(i + 1)
if two_deep then turtle.dig() end
move('u')
i = i + 1
end
-- Dig the final block
if two_deep then turtle.dig() end
-- Because we check the fuel level every time, we know for certain we can get back up/down
if return_to_start then
if go_down then move('u', i) else move('d', i) end
end
end
-- Determine tree type
local _, data = turtle.inspect()
local tree_type = data.name
if not fuel.check(1) then
print("I need 1 fuel to start.")
return
end
-- Get into position
turtle.dig()
turtle.forward()
-- Check if it's a two-deep tree
local _, data = turtle.inspect()
local two_deep, two_by_two, right = data.name == tree_type, false, false
-- Check if it's a 2x2 tree
if two_deep then
turtle.turnRight()
local _, data = turtle.inspect()
turtle.turnLeft()
two_by_two = data.name == tree_type
-- Check if we're on the right side instead
if not two_by_two then
turtle.turnLeft()
_, data = turtle.inspect()
turtle.turnRight()
two_by_two = data.name == tree_type
right = two_by_two
end
end
-- Chop the actual tree
-- Chop left half
chop(tree_type, two_deep, true, true)
-- Come back down if it's not a two_by_two, else don't
chop(tree_type, two_deep, false, not two_by_two)
-- Get into position if it's a two-by-two
if two_by_two then
if right then turtle.turnLeft() else turtle.turnRight() end
turtle.dig()
turtle.forward()
if right then turtle.turnRight() else turtle.turnLeft() end
-- Go back down, or dig second half
chop(tree_type, two_deep, true, false)
if right then turtle.turnRight() else turtle.turnLeft() end
turtle.forward()
if right then turtle.turnLeft() else turtle.turnRight() end
end

211
treefarm.lua 100644
View File

@ -0,0 +1,211 @@
-- Created by Jef Roosens
local logger = require('jlib.log').init_logger(3, nil, "treefarm.log")
local fuel = require('jlib.fuel')
fuel.logger = logger
local move = require('jlib.move').move
-- Chop a 1x1 tree
--
-- @param max_height height of tallest known tree
--
-- @return height of chopped tree if greater than known highest, otherwise
-- known highest
local function chop(max_height, log_name)
logger:log(3, "Chopping tree")
-- Make sure we have enough fuel for the largest known tree
fuel.check(2 * max_height + 1, { log_name })
-- Move one forward
move('f')
turtle.digDown()
-- Chop up until we no longer find logs
local _, data = turtle.inspectUp()
local height = 0
while data.name == log_name do
-- These checks most likely always succeed instantly thanks to the
-- tallest tree system
fuel.check(height, { log_name })
move('u', 1)
_, data = turtle.inspectUp()
height = height + 1
end
-- Move back down
move('d', height)
-- Return the new height if it's greater than the old
max_height = math.max(max_height, height)
logger:log(4, "Max height: " .. max_height)
return max_height
end
-- Select given item
--
-- @param item_name name of item to select
--
-- @return wether or not the item could be selected
local function select_item(item_name)
for i = 1, 16 do
local data = turtle.getItemDetail(i)
if data and data.name == item_name then
turtle.select(i)
return true
end
end
return false
end
-- Do one cycle through the farm
--
-- @param max_height height of tallest known tree
--
-- @return new height of tallest known tree
local function cycle(width, depth, max_height, sapling_name, log_name,
spacing)
logger:log(3, "Starting cycle")
for i = 1, width do
-- Check for fuel to go through entire row
fuel.check((spacing + 1) * (depth - 1) + 2, { log_name })
for j = 1, depth do
local _, data = turtle.inspect()
-- Chop down the tree, or move forward
if data.name == log_name then
-- Gotta re-do the check from the beginning of this row
fuel.check(
2 * max_height + 1 + (spacing + 1) * (depth - j) + 2,
{ log_name })
max_height = chop(max_height, log_name)
else
move('f')
end
-- Place down a sapling, if possible
if select_item(sapling_name) then
turtle.placeDown()
end
-- Pick up any dropped saplings
turtle.suckDown()
-- Move forward
if j < depth then
move('f', spacing)
else
move('f')
end
end
-- Turn
if i < width then
if i % 2 == 0 then turtle.turnLeft() else turtle.turnRight() end
fuel.check(spacing + 1, { log_name })
move('f', spacing + 1)
if i % 2 == 0 then turtle.turnLeft() else turtle.turnRight() end
end
end
return max_height
end
-- Do maintenance stuff: drop off items, and restock on saplings
local function maintenance(width, depth, sapling_name, log_name)
logger:log(3, "Doing maintenance")
fuel.check(6, { log_name })
-- Move towards sapling chest
move('f')
turtle.turnLeft()
move('f')
-- Deposite all saplings
while select_item(sapling_name) do turtle.drop(64) end
-- Get enough saplings
turtle.select(1)
turtle.suck(width * depth)
-- Move towards log chest
turtle.turnRight()
move('f')
turtle.turnLeft()
-- Deposite all logs
while select_item(log_name) do turtle.drop(64) end
-- Keep 10 logs for refueling
turtle.select(2)
turtle.suck(10)
-- Get back to start block
turtle.turnRight()
turtle.turnRight()
move('f')
turtle.turnRight()
move('f', 2)
logger:log(3, "Maintenance finished")
end
local function main()
-- Load the settings, or quit if it fails
if not settings.load(".treefarm.conf") then
return logger:log(1, "Couldn't load configuration file. Please make sure it exists and doesn't contain syntax errors.")
end
-- Get all variables (API calls are slow)
local width, depth, spacing, border_width, log_name, sapling_name, delay =
settings.get("width"), settings.get("depth"), settings.get("spacing"),
settings.get("border_width"), settings.get("log_name"),
settings.get("sapling_name"), settings.get("delay")
local max_height = 0
logger:log(3, "Settings loaded")
-- Start the main loop
logger:log(3, "Starting main loop")
while true do
-- Get into start position for cycle
fuel.check(2, { log_name })
move('f', 2)
-- Run through two cycles
max_height = cycle(width, depth, max_height, sapling_name, log_name,
spacing)
turtle.turnRight()
turtle.turnRight()
sleep(delay)
max_height = cycle(width, depth, max_height, sapling_name, log_name,
spacing)
-- Do maintenance and subtract maintenance time from delay
fuel.check(border_width, { log_name })
move('f', border_width)
local seconds = os.clock()
maintenance(width, depth, sapling_name, log_name)
sleep(math.max(0, delay - os.clock() + seconds))
end
end
main()

26
tunnel.lua 100644
View File

@ -0,0 +1,26 @@
-- Created by Jef Roosens
--
-- This script digs a 1x2 tunnel of a given length
local logger = require("jlib.log").init_logger(3)
local fuel = require("jlib.fuel")
fuel.logger = logger
local move = require("jlib.move").move
local input = require("jlib.input")
local function main(args)
-- Get tunnel length
local length = tonumber(args[1]) or input.read_int("Tunnel length: ")
-- Check if fuel is sufficient
if not fuel.check(length) then
return logger:log(1, "Insufficient fuel.")
end
-- Do the digging
move('f', length, { turtle.digDown, turtle.digUp })
end
main({...})