Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- ---
- --- "veinm" version 1.0 by Qendolin
- --- 22.09.2023 for CC: Restiched 1.100.8
- --- License: MPL 2.0
- --- Contact: 'Qendolin' on https://forums.computercraft.cc/
- ---
- --- Usage: veinm <pattern>... [options...]
- ---
- --- Options:
- --- -help
- --- -junk <junk_pattern>...
- --- -safe <width> [height] [depth]
- --- -debug <filters>...
- ---
- --- Specify at least one <pattern> to match against the block ids which should get mined.
- --- The turtle will find and mine all connected blocks of these types.
- --- Example: `veinm granite$ andesite$` will mine all blocks ending with "granite" and "andesite".
- ---
- --- Specify zero or more <junk_pattern> to match against item ids in the turtle's inventory
- --- which will get tossed out when the turtle is full.
- --- Example: `-junk stone$` will toss out any items ending with "stone" such as cobblestone.
- ---
- --- Specify a safe zone which the turtle will not enter.
- --- By default the safe zone extends infinitely behind the turtle starting position.
- --- Any dimension that is not specified is assumed to be infinite.
- --- Example: `-safe inf 11 10` will exclude an area behind the turtle that
- --- is `infinitely` wide, 5 blocks up, 5 blocks down and 10 blocks deep
- ---
- --- All patterns use the lua pattern syntax: https://www.lua.org/pil/20.2.html
- ---
- --- When the turtle cannot find any more blocks to mine it will return to its starting position.
- --- It will also return to its starting position when it runs low on fuel.
- ---
- --- Setup:
- --- 1. Place the turtle facing away from a chest. In this chest the turtle will deposit the collected items.
- --- 2. Make sure a block that you wish to mine is directly in front of the turtle.
- --- 3. Place enough coal in the first slot of the turtle to fill it 50%. I recommend Blocks of Coal.
- --- 4. Run `veinm`
- --- 5. Make sure to keep the turtle loaded!!
- ---
- FuelItem = "coal"
- ---@class Direction
- ---@field dx number
- ---@field dy number
- ---@field dz number
- ---@field index number
- ---@field name string
- Direction = {}
- ---
- ---@param dx number
- ---@param dy number
- ---@param dz number
- ---@param index number
- ---@return Direction
- function Direction:new(dx, dy, dz, index, name)
- local o = {}
- setmetatable(o, self)
- self.__index = self
- o.dx = dx
- o.dy = dy
- o.dz = dz
- o.index = index
- o.name = name
- return o
- end
- Directions = {
- None = Direction:new(0, 0, 0, -1, "none"),
- Forward = Direction:new(1, 0, 0, 0, "+F"),
- Right = Direction:new(0, 0, 1, 1, "+R"),
- Backward = Direction:new(-1, 0, 0, 2, "-F"),
- Left = Direction:new(0, 0, -1, 3, "-R"),
- Up = Direction:new(0, 1, 0, -1, "+U"),
- Down = Direction:new(0, -1, 0, -1, "-U"),
- }
- Directions[0] = Directions.Forward
- Directions[1] = Directions.Right
- Directions[2] = Directions.Backward
- Directions[3] = Directions.Left
- ---
- ---@param rotation Rotation
- ---@return Direction
- function Direction:add(rotation)
- if self.index == -1 then
- return self
- end
- local index = self.index + rotation.index
- index = math.fmod(math.fmod(index, 4) + 4, 4)
- return Directions[index]
- end
- ---
- ---@param other Direction
- ---@return Rotation
- function Direction:sub(other)
- if self.index == -1 or other.index == -1 then
- return Rotations.None
- end
- local index = other.index - self.index
- index = math.fmod(math.fmod(index, 4) + 4, 4)
- return Rotations[index]
- end
- ---
- ---@return string
- function Direction:toString()
- return string.format("%d,%d,%d", self.dx, self.dy, self.dz)
- end
- ---@class Rotation
- ---@field a number
- ---@field b number
- ---@field c number
- ---@field d number
- ---@field index number
- Rotation = {}
- ---
- ---@param a number
- ---@param b number
- ---@param c number
- ---@param d number
- ---@param index number
- ---@return Rotation
- function Rotation:new(a, b, c, d, index)
- local o = {}
- setmetatable(o, self)
- self.__index = self
- o.a = a
- o.b = b
- o.c = c
- o.d = d
- o.index = index
- return o
- end
- Rotations = {
- None = Rotation:new(1, 0, 0, 1, 0),
- Right = Rotation:new(0, -1, 1, 0, 1),
- Reverse = Rotation:new(-1, 0, 0, -1, 2),
- Left = Rotation:new(0, 1, -1, 0, 3),
- }
- Rotations[0] = Rotations.None
- Rotations[1] = Rotations.Right
- Rotations[2] = Rotations.Reverse
- Rotations[3] = Rotations.Left
- ---@param other Rotation
- function Rotation:add(other)
- local index = self.index + other.index
- index = math.fmod(math.fmod(index, 4) + 4, 4)
- return Rotations[index]
- end
- ---@class Vector
- ---@field x number
- ---@field y number
- ---@field z number
- Vector = {
- maxx = 1024,
- maxy = 512,
- maxz = 1024,
- minx = -1023,
- miny = -511,
- minz = -1023,
- }
- ---
- ---@param x number?
- ---@param y number?
- ---@param z number?
- ---@return Vector
- function Vector:new(x, y, z)
- local o = {}
- setmetatable(o, self)
- self.__index = self
- o.x = x or 0
- o.y = y or 0
- o.z = z or 0
- return o
- end
- Zero = Vector:new()
- ---
- ---@param other Vector
- ---@return boolean
- function Vector:equals(other)
- if self == nil and other == nil then
- return true
- end
- if self == nil or other == nil then
- return false
- end
- return self.x == other.x and self.y == other.y and self.z == other.z
- end
- ---
- ---@param other Vector | Direction
- ---@return Vector
- function Vector:add(other)
- if getmetatable(other) == Direction then
- return Vector:new(self.x + other.dx, self.y + other.dy, self.z + other.dz)
- end
- return Vector:new(self.x + other.x, self.y + other.y, self.z + other.z)
- end
- ---
- ---@param other Vector | Direction
- ---@return Vector
- function Vector:sub(other)
- if getmetatable(other) == Direction then
- return Vector:new(self.x - other.dx, self.y - other.dy, self.z - other.dz)
- end
- return Vector:new(self.x - other.x, self.y - other.y, self.z - other.z)
- end
- ---
- ---@param rotation Rotation
- ---@return Vector
- function Vector:rotate(rotation)
- local x = self.x * rotation.a + self.z * rotation.b
- local z = self.x * rotation.c + self.z * rotation.d
- return Vector:new(x, self.y, z)
- end
- ---
- ---@param direction Direction
- ---@return Vector
- function Vector:relative(direction)
- return self:add(direction)
- end
- ---
- ---@param other Vector
- ---@return number
- function Vector:distance(other)
- local dx = self.x - other.x
- local dy = self.y - other.y
- local dz = self.z - other.z
- return math.sqrt(dx * dx + dy * dy + dz * dz)
- end
- ---
- ---@param other Vector
- ---@return number
- function Vector:distanceSquare(other)
- local dx = self.x - other.x
- local dy = self.y - other.y
- local dz = self.z - other.z
- return dx * dx + dy * dy + dz * dz
- end
- ---
- ---@param other Vector
- ---@return number
- function Vector:distanceManhattan(other)
- local dx = self.x - other.x
- local dy = self.y - other.y
- local dz = self.z - other.z
- return math.abs(dx) + math.abs(dy) + math.abs(dz)
- end
- ---packs the vector into a 32bit int
- ---the allowed x and z range is -1023 to 1024
- ---the allowed y range is -511 to 512
- ---@alias VectorKey number
- ---@return VectorKey
- function Vector:toKey()
- local k = 0
- k = bit.bor(k, bit32.lshift(bit32.band(self.x, 0x7FF), 21))
- k = bit.bor(k, bit32.lshift(bit32.band(self.z, 0x7FF), 10))
- k = bit.bor(k, bit.band(self.y, 0x3FF), 16)
- return k
- end
- ---
- ---@return string
- function Vector:toString()
- return string.format("%d,%d,%d", self.x, self.y, self.z)
- end
- ---@alias QueueState "unknown" | "queued" | "visited" | "irrelevant" | "avoid"
- ---@class TargetMap
- ---@field states table<VectorKey, QueueState>
- ---@field queue table<VectorKey, Vector>
- ---@field queuedCount number
- ---@field visitedCount number
- ---@field mindedCount number
- ---@field recent VectorKey[]
- ---@field recentIndex number
- TargetMap = {
- states = {},
- queue = {},
- queuedCount = 0,
- visitedCount = 0,
- minedCount = 0,
- recent = {},
- recentIndex = 0
- }
- --
- ---@return TargetMap
- function TargetMap:new()
- local o = {}
- setmetatable(o, self)
- self.__index = self
- o.states = {}
- o.queue = {}
- o.queuedCount = 0
- o.visitedCount = 0
- o.minedCount = 0
- o.discoveredCount = 0
- o.recent = {}
- o.recentIndex = 0
- return o
- end
- ---
- ---@param pos Vector
- ---@return QueueState
- function TargetMap:get(pos)
- local state = self.states[pos:toKey()]
- if state == nil then
- return "unknown"
- end
- return state
- end
- ---
- ---@param pos Vector
- ---@param state QueueState
- function TargetMap:set(pos, state)
- local key = pos:toKey()
- local prev = self.states[key]
- self.states[key] = state
- if state == "queued" and prev ~= "queued" then
- self.queue[key] = pos
- self.recent[self.recentIndex] = key
- self.recentIndex = math.fmod(self.recentIndex + 1, 4)
- self.queuedCount = self.queuedCount + 1
- end
- if state ~= "queued" and prev == "queued" then
- self.queue[key] = nil
- self.queuedCount = self.queuedCount - 1
- end
- if state == "visited" and prev ~= "visited" then
- self.visitedCount = self.visitedCount + 1
- end
- if state ~= nil and prev == nil then
- self.discoveredCount = self.discoveredCount + 1
- end
- if state == "visited" and prev == "queued" then
- self.minedCount = self.minedCount + 1
- end
- end
- ---
- ---@param from Vector
- ---@param facing Direction
- ---@return Vector | nil
- function TargetMap:findNextTarget(from, facing)
- -- look in recent additions first, because there is a high
- -- chance that it's right next to us
- local bestPos = nil
- local bestValue = math.huge
- for index = 4, 1, -1 do
- local pos = self.queue[self.recent[index]]
- if pos ~= nil then
- local value = self:evaluateTarget(from, pos, facing)
- if value < bestValue then
- bestValue = value
- bestPos = pos
- end
- end
- end
- if bestValue <= 1 then
- return bestPos
- end
- for _, pos in pairs(self.queue) do
- if pos ~= false then
- local value = self:evaluateTarget(from, pos, facing)
- if value <= 1 then
- return pos
- end
- if value < bestValue then
- bestValue = value
- bestPos = pos
- end
- end
- end
- return bestPos
- end
- ---
- ---@param from Vector
- ---@param pos Vector
- ---@param facing Direction
- ---@return number
- function TargetMap:evaluateTarget(from, pos, facing)
- local value = from:distanceManhattan(pos) + Zero:distance(pos) * 0.5
- local discovery = self:evaluateDiscovery(pos, facing)
- value = value - discovery * 0.35
- if discovery <= 1 and pos.x == from.x + facing.dx and pos.z == from.z + facing.dz and pos.y == from.y then
- -- straigt ahead bonus
- value = value - 1
- end
- return value
- end
- ---
- ---@param pos Vector
- ---@param facing Direction
- ---@return number
- function TargetMap:evaluateDiscovery(pos, facing)
- local value = 0
- local p = Vector:new(pos.x, pos.y + 1, pos.z)
- if self.states[p:toKey()] == nil then
- value = value + 1
- end
- p.y = pos.y - 1
- if self.states[p:toKey()] == nil then
- value = value + 1
- end
- p.y = pos.y
- p.x = pos.x + facing.dx
- p.z = pos.z + facing.dz
- if self.states[p:toKey()] == nil then
- value = value + 1
- end
- return value
- end
- ---@class WriteHandle
- ---@field write fun(text: string)
- ---@field writeLine fun(text: string)
- ---@field flush fun()
- ---@field close fun()
- ---@class LogOp
- ---@field symbol string
- ---@field defaultArg any
- ---@field combine fun(args: any, arg: any): any
- ---@type table<string, LogOp>
- LogOps = {
- MoveForward = {
- symbol = "h",
- defaultArg = 0,
- ---@param args number
- ---@param arg number
- ---@return number
- combine = function(args, arg)
- return args + arg
- end
- },
- MoveVertical = {
- symbol = "v",
- defaultArg = 0,
- ---@param args number
- ---@param arg number
- ---@return number
- combine = function(args, arg)
- return args + arg
- end
- },
- Turn = {
- symbol = "t",
- defaultArg = 0,
- ---@param args number
- ---@param arg Rotation
- ---@return number
- combine = function(args, arg)
- return math.fmod(math.fmod(args + arg.index, 4) + 4, 4)
- end
- }
- }
- ---@class LogWriter
- ---@field op LogOp?
- ---@field args any
- ---@field file WriteHandle?
- LogWriter = {
- op = nil,
- args = nil,
- file = nil,
- }
- ---
- ---@param filename string
- ---@return LogWriter
- function LogWriter:new(filename)
- local o = {}
- setmetatable(o, self)
- self.__index = self
- o.op = ""
- o.args = ""
- fs.delete(filename)
- o.file = fs.open(filename, "a")
- return o
- end
- ---
- ---@param op LogOp
- ---@param arg any
- function LogWriter:append(op, arg)
- if self.op ~= op then
- self:flush()
- self.op = op
- self.args = self.op.combine(op.defaultArg, arg)
- else
- self.args = self.op.combine(self.args, arg)
- end
- end
- function LogWriter:flush()
- if self.op == nil then
- return
- end
- if self.op.defaultArg == self.args then
- return
- end
- self.file.write(self.op.symbol)
- self.file.writeLine(tostring(self.args))
- self.file.flush()
- end
- ---@alias BlockName string
- ---@class Program
- ---@field pos Vector
- ---@field targetPos Vector
- ---@field direction Direction
- ---@field map TargetMap
- ---@field targetPatterns string[]
- ---@field junkPatterns string[]
- ---@field tui TUI?
- ---@field stop boolean
- ---@field log LogWriter?
- ---@field safeZone AABB
- Program = {}
- ---
- ---@return Program
- function Program:new()
- local o = {}
- setmetatable(o, self)
- self.__index = self
- o.pos = Vector:new()
- o.targetPos = Vector:new()
- o.direction = Directions.Forward
- o.map = TargetMap:new()
- o.targetPatterns = {}
- o.junkPatterns = {}
- o.tui = TUI:new(o)
- o.log = LogWriter:new("veinm.log")
- o.safeZone = AABB:new(
- Vector:new(-Vector.minx, -Vector.miny, -Vector.minz),
- Vector:new(-1, Vector.maxy, Vector.maxz)
- )
- return o
- end
- ---
- ---@return Vector
- function Program:posInFront()
- return self.pos:add(self.direction)
- end
- ---
- ---@param rotation Rotation
- ---@return Vector
- function Program:posRelative(rotation)
- return self.pos:add(self.direction:add(rotation))
- end
- ---
- ---@return Vector
- function Program:posAbove()
- return self.pos:add(Directions.Up)
- end
- ---
- ---@return Vector
- function Program:posBelow()
- return self.pos:add(Directions.Down)
- end
- function Program:turnLeft()
- turtle.turnLeft()
- self.direction = self.direction:add(Rotations.Left)
- self.tui:draw()
- self.log:append(LogOps.Turn, Rotations.Left)
- end
- function Program:turnRight()
- turtle.turnRight()
- self.direction = self.direction:add(Rotations.Right)
- self.tui:draw()
- self.log:append(LogOps.Turn, Rotations.Right)
- end
- function Program:move()
- turtle.dig()
- while not turtle.forward() do
- sleep(0.25)
- turtle.dig()
- end
- self:finishMove(self.direction)
- self.log:append(LogOps.MoveForward, 1)
- end
- function Program:moveUp()
- turtle.digUp()
- while not turtle.up() do
- sleep(0.25)
- turtle.digUp()
- end
- self:finishMove(Directions.Up)
- self.log:append(LogOps.MoveVertical, 1)
- end
- function Program:moveDown()
- turtle.digDown()
- while not turtle.down() do
- sleep(0.25)
- turtle.digDown()
- end
- self:finishMove(Directions.Down)
- self.log:append(LogOps.MoveVertical, -1)
- end
- ---
- ---@param dir Direction
- function Program:finishMove(dir)
- self.pos = self.pos:add(dir)
- self:markPosVisited()
- self:processBlocks(dir)
- self.tui:draw()
- end
- function Program:markPosVisited()
- self.map:set(self.pos, "visited")
- end
- ---
- ---@return BlockName
- function Program:blockInFront()
- local succsess, data = turtle.inspect()
- if succsess then
- return data.name
- else
- return ""
- end
- end
- ---
- ---@return BlockName
- function Program:blockAbove()
- local succsess, data = turtle.inspectUp()
- if succsess then
- return data.name
- else
- return ""
- end
- end
- ---
- ---@return BlockName
- function Program:blockBelow()
- local succsess, data = turtle.inspectDown()
- if succsess then
- return data.name
- else
- return ""
- end
- end
- ---
- ---@param pos Vector
- ---@return boolean
- function Program:mayVisit(pos)
- return not self.safeZone:inside(pos)
- end
- ---
- ---@param name BlockName
- ---@return boolean
- function Program:isTarget(name)
- for i, pattern in ipairs(self.targetPatterns) do
- if string.find(name, pattern) then
- return true
- end
- end
- return false
- end
- ---
- ---@param pos Vector
- ---@param name BlockName
- function Program:processBlock(pos, name)
- if not self:mayVisit(pos) then
- self.map:set(pos, "avoid")
- elseif self:isTarget(name) then
- self.map:set(pos, "queued")
- else
- self.map:set(pos, "irrelevant")
- end
- end
- function Program:processBlockInFront()
- self:processBlock(self:posInFront(), self:blockInFront())
- end
- ---
- ---@param pos Vector
- ---@return boolean
- function Program:hasProcessed(pos)
- return self.map:get(pos) ~= "unknown"
- end
- ---
- ---@param lastMove Direction
- function Program:processBlocks(lastMove)
- -- up
- if not self:hasProcessed(self:posAbove()) then
- self:processBlock(self:posAbove(), self:blockAbove())
- end
- -- down
- if not self:hasProcessed(self:posBelow()) then
- self:processBlock(self:posBelow(), self:blockBelow())
- end
- local lastMoveVertical = lastMove.dy ~= 0
- local checkBack = lastMoveVertical and not self:hasProcessed(self:posRelative(Rotations.Reverse))
- -- front
- if not self:hasProcessed(self:posInFront()) then
- self:processBlockInFront()
- end
- local forward = self.direction
- local right = forward:add(Rotations.Right)
- local left = forward:add(Rotations.Left)
- local back = forward:add(Rotations.Reverse)
- -- right
- if not self:hasProcessed(self.pos:add(right)) then
- self:turnTo(right)
- self:processBlockInFront()
- if checkBack then
- self:turnTo(back)
- self:processBlockInFront()
- checkBack = false
- end
- end
- -- left
- if not self:hasProcessed(self.pos:add(left)) then
- self:turnTo(left)
- self:processBlockInFront()
- if checkBack then
- self:turnTo(back)
- self:processBlockInFront()
- checkBack = false
- end
- end
- self.tui:draw()
- end
- ---
- ---@param direction Direction
- function Program:turnTo(direction)
- local rotation = self.direction:sub(direction)
- if rotation == Rotations.None then
- return
- elseif rotation == Rotations.Right then
- self:turnRight()
- elseif rotation == Rotations.Reverse then
- self:turnLeft()
- self:turnLeft()
- elseif rotation == Rotations.Left then
- self:turnLeft()
- end
- end
- ---
- ---@param pos Vector
- function Program:moveTo(pos)
- self.targetPos = pos
- local delta = pos:sub(self.pos)
- if not self:mayVisit(pos) then
- error(string.format("Impossible move to %s, inside safe zone", pos:toString()))
- end
- for i = 1, delta.y, 1 do
- if self.map:get(self:posAbove()) == "avoid" then
- self:moveTo(self.safeZone:closestAvoidancePoint(self.pos))
- end
- self:moveUp()
- end
- for i = 1, -delta.y, 1 do
- if self.map:get(self:posBelow()) == "avoid" then
- self:moveTo(self.safeZone:closestAvoidancePoint(self.pos))
- end
- self:moveDown()
- end
- local dir = Directions.None
- if delta.x > 0 then
- dir = Directions.Forward
- elseif delta.x < 0 then
- dir = Directions.Backward
- end
- for i = 1, math.abs(delta.x), 1 do
- self:turnTo(dir)
- if not self:mayVisit(self:posInFront()) then
- self:moveTo(self.safeZone:closestAvoidancePoint(self.pos))
- self:turnTo(dir)
- end
- self:move()
- end
- dir = Directions.None
- if delta.z > 0 then
- dir = Directions.Right
- elseif delta.z < 0 then
- dir = Directions.Left
- end
- for i = 1, math.abs(delta.z), 1 do
- self:turnTo(dir)
- if not self:mayVisit(self:posInFront()) then
- self:moveTo(self.safeZone:closestAvoidancePoint(self.pos))
- self:turnTo(dir)
- end
- self:move()
- end
- end
- function Program:isInventoryFull()
- local emptySlots = 0
- for i = 1, 16, 1 do
- local count = turtle.getItemCount(i)
- if count == 0 then
- emptySlots = emptySlots + 1
- end
- end
- return emptySlots <= 1
- end
- function Program:moveToStart()
- if not self.pos:equals(Zero) then
- self:moveTo(Zero)
- end
- self:turnTo(Directions.Forward)
- end
- function Program:dropJunk()
- self.tui:setStatus("Dropping junk")
- for i = 1, 16, 1 do
- turtle.select(i)
- local type = turtle.getItemDetail(i)
- if type then
- local keep = true
- for _, pattern in ipairs(self.junkPatterns) do
- if string.find(type.name, pattern) then
- keep = false
- break
- end
- end
- if not keep then
- turtle.dropDown()
- end
- end
- end
- turtle.select(1)
- self.tui:setStatus("")
- end
- function Program:unload()
- self:turnTo(Directions.Backward)
- self.tui:setStatus("Unloading inventory")
- for i = 1, 16, 1 do
- turtle.select(i)
- local type = turtle.getItemDetail(i)
- local isFuel = false
- if type and string.find(type.name, FuelItem) then
- isFuel = true
- turtle.transferTo(1)
- end
- if not (isFuel and i == 1) then
- turtle.drop()
- end
- end
- turtle.select(1)
- self:turnTo(Directions.Forward)
- self.tui:setStatus("")
- end
- function Program:consumeFuel()
- if turtle.getFuelLevel() >= turtle.getFuelLimit() - 1000 then
- return
- end
- self.tui:setStatus("Refueling")
- for slot = 1, 16, 1 do
- turtle.select(slot)
- while turtle.getFuelLevel() < turtle.getFuelLimit() - 1000 do
- if not turtle.refuel(1) then
- break
- end
- end
- end
- turtle.select(1)
- self.tui:setStatus("")
- end
- function Program:unloadAndRefuel()
- self:unload()
- local minFuel = turtle.getFuelLimit() / 2
- while turtle.getFuelLevel() < minFuel do
- self.tui:setStatus(string.format("Insert %d fuel to continue", minFuel - turtle.getFuelLevel()))
- sleep(1)
- self.tui:draw()
- self:consumeFuel()
- end
- end
- ---@class TUI
- ---@field statusMessage string
- ---@field program Program?
- ---@field tick number
- TUI = {}
- ---
- ---@param program Program
- ---@return TUI
- function TUI:new(program)
- local o = {}
- setmetatable(o, self)
- self.__index = self
- o.statusMessage = ""
- o.program = program
- o.tick = 0
- o.lastTick = os.clock()
- return o
- end
- ---
- ---@param message string
- function TUI:setStatus(message)
- self.statusMessage = message
- self:draw()
- end
- function TUI:draw()
- local w, h = term.getSize()
- term.setCursorPos(1, 1)
- term.clearLine()
- term.write(TUI.ellipseString(self.statusMessage, w))
- term.setCursorPos(1, 3)
- TUI:table(w, { 2, 1 }, 2,
- { { "Position", self.program.pos:toString() }, { "Direction", self.program.direction.name } })
- local map = self.program.map
- local efficiency = math.floor(100 * (map.minedCount + 1) / map.visitedCount)
- term.setCursorPos(1, 6)
- TUI:table(w, { 1, 1, 1 }, 2,
- {
- { "Discovered", map.discoveredCount },
- { "Visited", map.visitedCount },
- { "Mined", map.minedCount }
- })
- term.setCursorPos(1, 9)
- TUI:table(w, { 1, 1, 1 }, 2,
- {
- { "Queued", map.queuedCount },
- { "Efficiency", efficiency .. "%" },
- { "Fuel", turtle.getFuelLevel() }
- })
- term.setCursorPos(1, h)
- term.clearLine()
- if self.lastTick ~= math.floor(os.clock()) then
- self.tick = self.tick + 1
- self.lastTick = math.floor(os.clock())
- end
- term.write(string.rep(".", math.fmod(self.tick, 4) + 1))
- end
- ---
- ---@param s string
- ---@param len number
- ---@return string
- function TUI.ellipseString(s, len)
- if string.len(s) > len then
- return string.sub(s, 1, len - 3) .. "..."
- end
- return s
- end
- ---
- ---@param width number
- ---@param columns number[]
- ---@param rows number
- ---@param data any[][]
- function TUI:table(width, columns, rows, data, options)
- local fractions = 0
- for _, fract in ipairs(columns) do
- fractions = fractions + fract
- end
- local availWidth = width
- local fractTotal = 0
- local colFmt = ""
- for col, fract in ipairs(columns) do
- local colStart = availWidth * (fractTotal / fractions)
- fractTotal = fractTotal + fract
- local colEnd = availWidth * (fractTotal / fractions)
- local size = math.floor(colEnd - colStart + 0.5)
- colFmt = colFmt .. "%-" .. tostring(size - 2) .. "s"
- if col < #columns then
- colFmt = colFmt .. "| "
- end
- end
- local cx, cy = term.getCursorPos()
- for row = 1, rows, 1 do
- local rowData = {}
- for col = 1, #data, 1 do
- local value = data[col][row] or ""
- table.insert(rowData, value)
- end
- term.clearLine()
- term.write(string.format(colFmt, table.unpack(rowData)))
- if row < rows then
- term.setCursorPos(cx, cy + row)
- end
- end
- end
- function Program:run()
- term.clear()
- self.tui:setStatus("Starting...")
- self:unloadAndRefuel()
- self:processBlocks(Directions.Forward)
- self:markPosVisited()
- while not self.stop do
- self:consumeFuel()
- if turtle.getFuelLevel() < self.pos:distanceManhattan(Zero) * 1.5 then
- self.tui:setStatus("Fuel low, returning to start")
- self:moveToStart()
- self:unloadAndRefuel()
- end
- if self:isInventoryFull() then
- self:dropJunk()
- end
- if self:isInventoryFull() then
- self.tui:setStatus("Going to unload inventory")
- self:moveToStart()
- self:unload()
- end
- if self.stop then
- break
- end
- local target = self.map:findNextTarget(self.pos, self.direction)
- if target ~= nil then
- self.tui:setStatus("Target: " .. target:toString())
- else
- self.tui:setStatus("No target, returning to start")
- self:moveToStart()
- self:unload()
- self.tui:setStatus("Finished")
- break
- end
- self:moveTo(target)
- end
- self.log:flush()
- while true do
- sleep(1)
- self.tui:draw()
- end
- end
- Debug = {
- enabled = false,
- filters = {},
- skip = 0,
- history = {}
- }
- ---
- ---@param id string
- ---@param msg? string
- function DebugStep(id, msg)
- if not Debug.enabled then
- return
- end
- if #Debug.filters > 0 then
- local match = false
- for _, pattern in ipairs(Debug.filters) do
- if string.find(id, pattern) then
- match = true
- break
- end
- end
- if not match then
- return
- end
- end
- if Debug.skip > 0 then
- Debug.skip = Debug.skip - 1
- return
- end
- local w, h = term.getSize()
- for i = 0, 4, 1 do
- term.setCursorPos(1, h - i)
- term.clearLine()
- end
- if msg ~= nil then
- write(msg)
- end
- while true do
- term.setCursorPos(1, h)
- term.clearLine()
- term.write(string.format("@ %s. [space] [ctrl]", id))
- ---@diagnostic disable-next-line: undefined-field
- local ev = { os.pullEvent("key") }
- if ev[2] == 32 then
- -- space
- break
- elseif ev[2] == 341 then
- -- left control
- term.setCursorPos(1, h)
- term.clearLine()
- local cmd = read(nil, Debug.history)
- ---@diagnostic disable-next-line: deprecated
- local fn, err = loadstring(cmd)
- if fn then
- RunDebugCmd(fn)
- else
- write("Error: ", err)
- end
- table.insert(Debug.history, cmd)
- end
- end
- for i = 0, 4, 1 do
- term.setCursorPos(1, h - i)
- term.clearLine()
- end
- end
- function RunDebugCmd(__func)
- _G.echo = function(...)
- local w, h = term.getSize()
- for i = 0, 4, 1 do
- term.setCursorPos(1, h - i)
- term.clearLine()
- end
- local args = { n = select("#", ...), ... }
- for i = 1, args.n do
- io.stdout:write(tostring(args[i]))
- end
- end
- ---@diagnostic disable-next-line: deprecated
- setfenv(__func, getfenv())
- local status, err = pcall(__func)
- if not status then
- echo(err)
- end
- _G.echo = nil
- end
- ---@class AABB
- ---@field min Vector
- ---@field max Vector
- AABB = {}
- ---
- ---@param min Vector
- ---@param max Vector
- ---@return AABB
- function AABB:new(min, max)
- local o = {}
- setmetatable(o, self)
- self.__index = self
- o.min = min
- o.max = max
- return o
- end
- ---
- ---@param pos Vector
- ---@return boolean
- function AABB:inside(pos)
- local min = self.min
- local max = self.max
- return pos.x >= min.x and pos.x <= max.x and
- pos.y >= min.y and pos.y <= max.y and
- pos.z >= min.z and pos.z <= max.z
- end
- ---
- ---NOTE: I haven't fully tested this algorith
- ---@param pos Vector
- ---@return Vector
- function AABB:closestAvoidancePoint(pos)
- local min = self.min
- local max = self.max
- local options = {
- { coord = 'x', value = min.x - 1, dist = math.abs(min.x - 1 - pos.x) },
- { coord = 'y', value = min.y - 1, dist = math.abs(min.y - 1 - pos.y) },
- { coord = 'z', value = min.z - 1, dist = math.abs(min.z - 1 - pos.z) },
- { coord = 'x', value = max.x + 1, dist = math.abs(max.x + 1 - pos.x) },
- { coord = 'y', value = max.y + 1, dist = math.abs(max.y + 1 - pos.y) },
- { coord = 'z', value = max.z + 1, dist = math.abs(max.z + 1 - pos.z) },
- }
- table.sort(options, function(a, b)
- return a.dist < b.dist
- end)
- local result = Vector:new(pos.x, pos.y, pos.z)
- result[options[2].coord] = options[2].value
- return result
- end
- ---
- ---@param width (number | string)?
- ---@param height (number | string)?
- ---@param depth (number | string)?
- function Program:parseSafeZone(width, height, depth)
- self.safeZone = AABB:new(Vector:new(), Vector:new())
- width = width or "inf"
- height = height or "inf"
- depth = depth or "inf"
- width = tonumber(width) or Vector.maxx * 2;
- height = tonumber(height) or Vector.maxy * 2
- depth = tonumber(depth) or Vector.maxz * 2
- self.safeZone.min.x = -math.ceil(depth)
- self.safeZone.max.x = -1
- self.safeZone.min.y = -math.ceil(height / 2 - 0.5)
- self.safeZone.max.y = math.ceil(height / 2 - 0.5)
- self.safeZone.min.z = -math.ceil(width / 2 - 0.5)
- self.safeZone.max.z = math.ceil(width / 2 - 0.5)
- end
- function Start(args)
- local junkPatterns = {}
- local targetPatterns = {}
- local safeZoneArgs = {}
- local activeList = targetPatterns
- local help = false
- for _, v in ipairs(args) do
- if string.find(v, "^-") then
- if v == "-junk" then
- activeList = junkPatterns
- elseif v == "-safe" then
- activeList = safeZoneArgs
- elseif v == "-help" then
- help = true
- break
- elseif v == "-debug" then
- Debug.enabled = true
- activeList = Debug.filters
- else
- help = true
- return
- end
- else
- table.insert(activeList, v)
- end
- end
- if #targetPatterns == 0 or help or #safeZoneArgs > 3 then
- print("Usage: veinm <pattern>... [options...]")
- print("Options:")
- print(" -help")
- print(" -junk <junk_pattern>...")
- print(" -safe <width> <height> <depth>")
- print(" -debug <filters>...")
- return
- end
- local app = Program:new()
- app.targetPatterns = targetPatterns
- app.junkPatterns = junkPatterns
- app:parseSafeZone(safeZoneArgs[1], safeZoneArgs[2], safeZoneArgs[3])
- app:run()
- end
- Start({ ... })
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement