1

Merge netherite/excavation controllers, overhaul movement logic with verticality support

This commit is contained in:
2025-10-06 22:29:33 +02:00
parent 13b506923b
commit b5cb13732e
6 changed files with 350 additions and 292 deletions

View File

@ -0,0 +1,324 @@
local Direction = require("lib.direction")
local TurtleController = require("controller.turtle_controller")
---@alias ExcavationControllerConfig {slice_length: number, slice_height: number, center_slice_width: number, slice_width: number, slice_padding: number, slices_left: number, slices_right: number}
---@class ExcavationController
---@field controller TurtleController
---@field config ExcavationControllerConfig
local ExcavationController = {}
ExcavationController.__index = ExcavationController
---@return ExcavationController
function ExcavationController:Create()
local t = {}
setmetatable(t, ExcavationController)
-----------------------------------------------------------------------------------------------
-- Fields
-----------------------------------------------------------------------------------------------
t.controller = TurtleController:Create()
t.config = {
slice_length = 0,
slice_height = 0,
center_slice_width = 0,
slice_width = 0,
slice_padding = 0,
slices_left = 0,
slices_right = 0,
}
return t
end
-----------------------------------------------------------------------------------------------
-- Behavior Methods
-----------------------------------------------------------------------------------------------
---Excavates a single 1x1/1x2 tunnel of configured length.
---Will leave the turtle wherever it ends up.
---Only unstocks/refuels if required.
---@param mine_above boolean
---@param mine_below boolean
function ExcavationController:Excavate_1x1or2or3xL(mine_above, mine_below)
self.controller:EnableMiningForward()
if mine_above then
self.controller:EnableMiningAbove()
end
if mine_below then
self.controller:EnableMiningBelow()
end
self.controller:MoveForward(self.config.slice_length)
self.controller:DisableMiningForward()
self.controller:DisableMiningAbove()
self.controller:DisableMiningBelow()
end
---Excavates a single 1xH partial slice of configured length.
---Will leave the turtle in its starting position.
---Only unstocks/refuels if required.
function ExcavationController:Excavate_1xHxL()
self.controller:StorePosition()
-- We can mine 2 blocks heigh in a single go
local number_of_1x3xL = math.floor(self.config.slice_height / 3)
-- Prepare for mining the blocks below the turtle
if number_of_1x3xL > 0 then
self.controller:EnableMiningAbove()
self.controller:MoveVertical(1)
self.controller:DisableMiningAbove()
end
-- Mine 1x3xL
for i = 1,number_of_1x3xL do
self:Excavate_1x1or2or3xL(true, true)
self.controller:TurnRelative(2)
-- Don't move upwards in the last iteration so we can decide
-- how much to move depending on how much we still have to excavate
if i == number_of_1x3xL then
break
end
self.controller:EnableMiningAbove()
self.controller:MoveVertical(4)
self.controller:MoveVertical(-1)
self.controller:DisableMiningAbove()
end
-- Mine remaining 1x2xL or 1x1xL
if self.config.slice_height % 3 == 2 then
self.controller:EnableMiningAbove()
self.controller:MoveVertical(3)
self.controller:MoveVertical(-1)
self.controller:DisableMiningAbove()
self:Excavate_1x1or2or3xL(true, false)
elseif self.config.slice_height % 3 == 1 then
self.controller:EnableMiningAbove()
self.controller:MoveVertical(3)
self.controller:MoveVertical(-1)
self.controller:DisableMiningAbove()
self:Excavate_1x1or2or3xL(false, false)
end
self.controller:EnableMiningBelow()
self.controller:MoveBack()
self.controller:DisableMiningBelow()
end
---Excavates a single WxH slice of configured length (EAST to WEST).
---Will leave the turtle in its starting position.
---Only unstocks/refuels if required.
---@param center_slice boolean | nil
function ExcavationController:Excavate_WxHxL(center_slice)
self.controller:StorePosition()
center_slice = center_slice or false
local width = center_slice and self.config.center_slice_width or self.config.slice_width
for i = 1,width do
self:Excavate_1xHxL()
if i == width then
break
end
self.controller:TurnToDirection(Direction.WEST)
self.controller:EnableMiningForward()
self.controller:EnableMiningAbove()
self.controller:MoveForward(1)
self.controller:DisableMiningForward()
self.controller:DisableMiningAbove()
self.controller:TurnToDirection(Direction.NORTH)
end
self.controller:MoveBack()
end
---Excavates all slices.
---Will leave the turtle refueled and unstocked in its 0x0x0 position.
function ExcavationController:Excavate()
-- Enter excavation area
self.controller:EnableMiningForward()
self.controller:EnableMiningAbove()
self.controller:MoveForward(1)
self.controller:DisableMiningForward()
self.controller:DisableMiningAbove()
-- Excavate center slice (start from right/back/bottom)
-- Uneven center widths will be centered, even center widths will have a block more on the right
print("Excavating center slice...")
local center_width_right = math.floor(self.config.center_slice_width / 2)
self.controller:StorePosition()
self.controller:TurnToDirection(Direction.EAST)
self.controller:EnableMiningForward()
self.controller:EnableMiningAbove()
self.controller:MoveForward(center_width_right)
self.controller:DisableMiningForward()
self.controller:DisableMiningAbove()
self.controller:TurnToDirection(Direction.NORTH)
self:Excavate_WxHxL(true)
self.controller:MoveBack() -- (0, 0, 1)
-- Move to right slices starting location
local padded_width_right = self.config.slices_right * (self.config.slice_width + self.config.slice_padding)
self.controller:StorePosition()
self.controller:TurnToDirection(Direction.EAST)
self.controller:EnableMiningForward()
self.controller:EnableMiningAbove()
self.controller:MoveForward(center_width_right + padded_width_right)
self.controller:DisableMiningForward()
self.controller:DisableMiningAbove()
self.controller:TurnToDirection(Direction.NORTH)
-- Excavate right slices
print("Excavating right slices")
for i = 1,self.config.slices_right do
self:Excavate_WxHxL()
if i == self.config.slices_right then
break
end
-- Move to the next slice
self.controller:TurnToDirection(Direction.WEST)
self.controller:EnableMiningForward()
self.controller:EnableMiningAbove()
self.controller:MoveForward(self.config.slice_width + self.config.slice_padding)
self.controller:DisableMiningForward()
self.controller:DisableMiningAbove()
self.controller:TurnToDirection(Direction.NORTH)
end
self.controller:MoveBack() -- (0, 0, 1)
-- Move to left slices starting location
local center_width_left = self.config.center_slice_width - center_width_right
self.controller:StorePosition()
self.controller:TurnToDirection(Direction.WEST)
self.controller:EnableMiningForward()
self.controller:EnableMiningAbove()
self.controller:MoveForward(center_width_left)
self.controller:DisableMiningForward()
self.controller:DisableMiningAbove()
self.controller:TurnToDirection(Direction.NORTH)
-- Excavate left slices
print("Excavate left slices")
for i = 1,self.config.slices_left do
-- Move to the next slice
self.controller:TurnToDirection(Direction.WEST)
self.controller:EnableMiningForward()
self.controller:EnableMiningAbove()
self.controller:MoveForward(self.config.slice_padding)
self.controller:DisableMiningForward()
self.controller:DisableMiningAbove()
self.controller:TurnToDirection(Direction.NORTH)
self:Excavate_WxHxL()
if i == self.config.slices_left then
break
end
self.controller:TurnToDirection(Direction.WEST)
self.controller:EnableMiningForward()
self.controller:EnableMiningAbove()
self.controller:MoveForward(self.config.slice_width)
self.controller:DisableMiningForward()
self.controller:DisableMiningAbove()
self.controller:TurnToDirection(Direction.NORTH)
end
self.controller:MoveBack() -- (0, 0, 1)
-- Finish up
self.controller:MoveToPosition(0, 0, 0, self.controller.config.storage_direction)
self.controller:DropInventory()
self.controller:TurnToDirection(Direction.NORTH)
end
-----------------------------------------------------------------------------------------------
-- Management Methods
-----------------------------------------------------------------------------------------------
function ExcavationController:Configure()
local config_complete = false
while not config_complete do
print("How long should each slice be?")
self.config.slice_length = tonumber(io.read()) or 3
print("How high should each slice be?")
self.config.slice_height = tonumber(io.read()) or 3
print("How wide should the center slice be?")
self.config.center_slice_width = tonumber(io.read()) or 1
print("How wide should the other slices be?")
self.config.slice_width = tonumber(io.read()) or 1
print("How many blocks should remain between each slice?")
self.config.slice_padding = tonumber(io.read()) or 0
print("How many slices should the turtle mine to its right?")
self.config.slices_right = tonumber(io.read()) or 1
print("How many slices should the turtle mine to its left?")
self.config.slices_left = tonumber(io.read()) or 1
local padded_width_left = self.config.slices_left * (self.config.slice_width + self.config.slice_padding)
local padded_width_right = self.config.slices_right * (self.config.slice_width + self.config.slice_padding)
local padded_width = padded_width_left + padded_width_right + self.config.center_slice_width
local mined_width_left = self.config.slices_left * self.config.slice_width
local mined_width_right = self.config.slices_right * self.config.slice_width
local mined_width = mined_width_left + mined_width_right + self.config.center_slice_width
print("Configuration complete!")
print(("Mining area spans %d x %d x %d (width x height x forward), totalling %d blocks."):format(
padded_width, self.config.slice_height, self.config.slice_length, padded_width * self.config.slice_height * self.config.slice_length
))
print(("Turtle will mine %d block wide slices (%d wide in the center) with %d blocks of padding, totalling %d mined blocks."):format(
self.config.slice_width, self.config.center_slice_width, self.config.slice_padding, mined_width * self.config.slice_height * self.config.slice_length
))
print("Do you want to accept the configuration (enter 1, otherwise 0)?")
config_complete = tonumber(io.read()) == 1
end
end
-----------------------------------------------------------------------------------------------
-- Main Method
-----------------------------------------------------------------------------------------------
function ExcavationController:Run()
self.controller:Configure()
self:Configure()
-- Consume our starting fuel and refuel to the full amount
turtle.select(1)
turtle.refuel()
self.controller:RefuelIfEmpty()
self:Excavate()
end
return ExcavationController

View File

@ -1,148 +0,0 @@
local Direction = require("lib.direction")
local TurtleController = require("controller.turtle_controller")
---@alias NetheriteControllerConfig {mine_forward: number, slices_left: number, slices_right: number, slices_padding: number}
---@class NetheriteController
---@field controller TurtleController
---@field config NetheriteControllerConfig
local NetheriteController = {}
NetheriteController.__index = NetheriteController
-- TODO: This controller can't return to unstock/refuel if stuck in the middle of a slice
---@return NetheriteController
function NetheriteController:Create()
local t = {}
setmetatable(t, NetheriteController)
-----------------------------------------------------------------------------------------------
-- Fields
-----------------------------------------------------------------------------------------------
t.controller = TurtleController:Create()
t.config = {
mine_forward = 0,
slices_left = 0,
slices_right = 0,
slices_padding = 0,
}
return t
end
-----------------------------------------------------------------------------------------------
-- Behavior Methods
-----------------------------------------------------------------------------------------------
function NetheriteController:Excavate()
-- Enter the excavation area
self.controller:EnableMining()
self.controller:MoveRelative(1)
local right_padded_width = self.config.slices_right * (self.config.slices_padding + 1)
local left_padded_width = self.config.slices_left * (self.config.slices_padding + 1)
-- Move to the bottom right corner of the layer
self.controller:TurnToDirection(Direction.EAST)
self.controller:MoveRelative(right_padded_width)
self.controller:TurnToDirection(Direction.NORTH)
self.controller:MoveRelative(self.config.mine_forward)
self.controller:TurnToDirection(Direction.WEST)
self.controller:MoveRelative(left_padded_width + right_padded_width)
self.controller:TurnToDirection(Direction.SOUTH)
self.controller:MoveRelative(self.config.mine_forward)
self.controller:TurnToDirection(Direction.EAST)
self.controller:MoveRelative(left_padded_width + right_padded_width)
self.controller:TurnToDirection(Direction.NORTH)
-- Zig zag mine the full width from back to front
local turn_dir = -1
for i = 1,self.config.slices_right + self.config.slices_left do
self.controller:MoveRelative(self.config.mine_forward)
-- Skip the last turn, as we won't mine that slice
if i == self.config.slices_right + self.config.slices_left then
break
end
self.controller:TurnRelative(turn_dir)
self.controller:MoveRelative(self.config.slices_padding + 1)
self.controller:TurnRelative(turn_dir)
turn_dir = -1 * turn_dir
end
if self.controller.position.z > 1 then
self.controller:TurnToDirection(Direction.SOUTH)
self.controller:MoveRelative(self.controller.position.z - 1)
self.controller:MoveToPosition(0, 0, 0, Direction.NORTH)
end
-- Unload before doing the next layer
self.controller:TurnToDirection(self.controller.config.storage_direction)
self.controller:DropInventory()
self.controller:TurnToDirection(Direction.NORTH)
end
-----------------------------------------------------------------------------------------------
-- Management Methods
-----------------------------------------------------------------------------------------------
function NetheriteController:Configure()
local config_complete = false
while not config_complete do
print("How many blocks should the turtle mine forward?")
self.config.mine_forward = tonumber(io.read()) or 3
print("How many slices should the turtle to its right?")
self.config.slices_right = tonumber(io.read()) or 3
print("How many blocks should the turtle mine to its left?")
self.config.slices_left = tonumber(io.read()) or 3
print("How many blocks should the turtle leave between the slices?")
self.config.slices_padding = tonumber(io.read()) or 3
local width = self.config.slices_left + self.config.slices_right + 1
local height = 3
print("Configuration complete!")
print(("Turtle will mine an area of %d x %d x %d (forward x width x height, excluding padding) totalling %d blocks."):format(
self.config.mine_forward, width, height, self.config.mine_forward * width * height
))
print("Do you want to accept the configuration (enter 1, otherwise 0)?")
config_complete = tonumber(io.read()) == 1
end
end
-----------------------------------------------------------------------------------------------
-- Main Method
-----------------------------------------------------------------------------------------------
function NetheriteController:Run()
self.controller:Configure()
self:Configure()
-- Consume our starting fuel and refuel to the full amount
turtle.select(1)
turtle.refuel()
self.controller:RefuelIfEmpty()
self:Excavate()
end
return NetheriteController

View File

@ -157,6 +157,8 @@ end
function TestingController:Run()
self.controller:Configure()
self.controller:DisableMiningForward()
self.controller:DisableMiningAbove()
self.controller:DisableMiningBelow()
print("There are multiple tests available:")
print("1: Relative movement with relative rotation")

View File

@ -107,9 +107,13 @@ function TurtleController:MoveForward(number_of_blocks, skip_unstocking, skip_re
self:UnstockIfFull()
end
-- Mine
-- Mine/Move
if self.mine_forward then
turtle.dig()
while not turtle.forward() do
turtle.dig()
end
elseif not turtle.forward() then
error("Turtle failed to move forward!")
end
if self.mine_above then
turtle.digUp()
@ -118,11 +122,6 @@ function TurtleController:MoveForward(number_of_blocks, skip_unstocking, skip_re
turtle.digDown()
end
-- Move
if not turtle.forward() then
error("Turtle failed to move forward!")
end
-- Update current position
if self.position.dir == Direction.NORTH then
self.position.z = self.position.z + 1
@ -181,6 +180,12 @@ function TurtleController:MoveVertical(number_of_blocks, skip_unstocking, skip_r
end
---Stores the current position on the stack so we can return using TurtleController:MoveBack()
function TurtleController:StorePosition()
self.last_positions:Push(Position:Copy(self.position))
end
---Move to an absolute position. Stores the current position on the stack so we can return using TurtleController:MoveBack()
---@param x number The EAST/WEST axis (grows from WEST to EAST)
---@param y number The UP/DOWN axis (grows from DOWN to UP)

View File

@ -1,131 +0,0 @@
local Direction = require("lib.direction")
local TurtleController = require("controller.turtle_controller")
---@alias VolumeExcavationControllerConfig {mine_forward: number, mine_left: number, mine_right: number}
---@class VolumeExcavationController
---@field controller TurtleController
---@field config VolumeExcavationControllerConfig
local VolumeExcavationController = {}
VolumeExcavationController.__index = VolumeExcavationController
---@return VolumeExcavationController
function VolumeExcavationController:Create()
local t = {}
setmetatable(t, VolumeExcavationController)
-----------------------------------------------------------------------------------------------
-- Fields
-----------------------------------------------------------------------------------------------
t.controller = TurtleController:Create()
t.config = {
mine_forward = 0,
mine_left = 0,
mine_right = 0,
}
return t
end
-----------------------------------------------------------------------------------------------
-- Behavior Methods
-----------------------------------------------------------------------------------------------
function VolumeExcavationController:Excavate()
-- Enter the excavation area
self.controller:EnableMining()
self.controller:MoveRelative(1)
-- Move to the bottom right corner of the layer
self.controller:TurnToDirection(Direction.EAST)
self.controller:MoveRelative(self.config.mine_right)
self.controller:TurnToDirection(Direction.WEST)
-- Zig zag mine the full width from back to front
-- The direction is important so we can travel to start without colliding with unminded walls
local turn_dir = 1
for i = 1,self.config.mine_forward do
self.controller:MoveRelative(self.config.mine_left + self.config.mine_right)
-- Skip the last turn, as we won't mine that slice
-- We want to stay in the mined area so we can move back freely
if i == self.config.mine_forward then
break
end
-- Skip the turning if we're mining in a straight line
if self.config.mine_left + self.config.mine_right > 0 then
self.controller:TurnRelative(turn_dir)
self.controller:MoveRelative(1)
self.controller:TurnRelative(turn_dir)
end
turn_dir = -1 * turn_dir
end
self.controller:MoveToPosition(0, 0, 0, Direction.NORTH)
-- Unload before doing the next layer
self.controller:TurnToDirection(self.controller.config.storage_direction)
self.controller:DropInventory()
self.controller:TurnToDirection(Direction.NORTH)
end
-----------------------------------------------------------------------------------------------
-- Management Methods
-----------------------------------------------------------------------------------------------
function VolumeExcavationController:Configure()
local config_complete = false
while not config_complete do
print("How many blocks should the turtle mine forward?")
self.config.mine_forward = tonumber(io.read()) or 3
print("How many blocks should the turtle to its right?")
self.config.mine_right = tonumber(io.read()) or 3
print("How many blocks should the turtle mine to its left?")
self.config.mine_left = tonumber(io.read()) or 3
local width = self.config.mine_left + self.config.mine_right + 1
local height = 3
print("Configuration complete!")
print(("Turtle will mine an area of %d x %d x %d (forward x width x height) totalling %d blocks."):format(
self.config.mine_forward, width, height, self.config.mine_forward * width * height
))
print("Do you want to accept the configuration (enter 1, otherwise 0)?")
config_complete = tonumber(io.read()) == 1
end
end
-----------------------------------------------------------------------------------------------
-- Main Method
-----------------------------------------------------------------------------------------------
function VolumeExcavationController:Run()
self.controller:Configure()
self:Configure()
-- Consume our starting fuel and refuel to the full amount
turtle.select(1)
turtle.refuel()
self.controller:RefuelIfEmpty()
self:Excavate()
end
return VolumeExcavationController

View File

@ -1,18 +1,15 @@
local TestingController = require("controller.testing_controller")
local VolumeExcavationController = require("controller.volume_excavation_controller")
local NetheriteController = require("controller.netherite_controller")
local ExcavationController = require("controller.excavation_controller")
local controllers = {
TestingController:Create(),
VolumeExcavationController:Create(),
NetheriteController:Create(),
ExcavationController:Create(),
}
print("Multiple controllers are available:")
print("1: Testing Mode")
print("2: Volume Excavation")
print("3: Netherite Excavation")
local choice = 0
while choice < 1 or choice > #controllers do
@ -20,4 +17,13 @@ while choice < 1 or choice > #controllers do
choice = tonumber(io.read()) or 0
end
controllers[choice]:Run()
controllers[choice]:Run()
-- TODO: StackOverflow once the inventory is full, unstocking doesn't work...
-- Is the InventoryFull check wrong?
-- TODO: Test if there's a chest when dropping/sucking/mining (don't mine chests!)
-- TODO: Add controller to build rooms with walls of a specified material
-- TODO: Support specifying the mining height
-- TODO: Add configurable trash_list with items that won't be picked up
-- TODO: Add a speaker to the turtle (listen to music while mining)