Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- Bundled by luabundle {"version":"1.6.0"}
- local __bundle_require, __bundle_loaded, __bundle_register, __bundle_modules = (function(superRequire)
- local loadingPlaceholder = {[{}] = true}
- local register
- local modules = {}
- local require
- local loaded = {}
- register = function(name, body)
- if not modules[name] then
- modules[name] = body
- end
- end
- require = function(name)
- local loadedModule = loaded[name]
- if loadedModule then
- if loadedModule == loadingPlaceholder then
- return nil
- end
- else
- if not modules[name] then
- if not superRequire then
- local identifier = type(name) == 'string' and '\"' .. name .. '\"' or tostring(name)
- error('Tried to require ' .. identifier .. ', but no such module has been registered')
- else
- return superRequire(name)
- end
- end
- loaded[name] = loadingPlaceholder
- loadedModule = modules[name](require, loaded, register, modules)
- loaded[name] = loadedModule
- end
- return loadedModule
- end
- return require, loaded, register, modules
- end)(require)
- __bundle_register("__root", function(require, _LOADED, __bundle_register, __bundle_modules)
- package.path = package.path .. ";/lib/?.lua"
- package.path = package.path .. ";/app/turtle/?.lua"
- local Utils = require("utils")
- local World = require("geo.world")
- local navigate = require("squirtle.navigate")
- local face = require("squirtle.face")
- local nextPoint = require("dig.next-point")
- local boot = require("dig.boot")
- local drop = require("squirtle.drop")
- local SquirtleV2 = require("squirtle.squirtle-v2")
- ---@class DigAppState
- ---@field world World
- ---@field position Vector
- ---@field facing integer
- ---@field hasShulkers boolean
- ---@field ignore table<string>
- local function isGettingFull()
- return turtle.getItemCount(16) > 0
- end
- ---@param world World
- ---@param position Vector
- local function digUpDownIfInBounds(world, position)
- if World.isInBoundsY(world, position.y + 1) then
- SquirtleV2.tryDig("up")
- end
- if World.isInBoundsY(world, position.y - 1) then
- SquirtleV2.tryDig("down")
- end
- end
- ---@return string? direction
- local function placeAnywhere()
- if turtle.place() then
- return "front"
- end
- if turtle.placeUp() then
- return "up"
- end
- if turtle.placeDown() then
- return "down"
- end
- end
- ---@param direction string
- ---@return boolean unloadedAll
- local function loadIntoShulker(direction)
- local unloadedAll = true
- for slot = 1, 16 do
- local stack = turtle.getItemDetail(slot)
- if stack and not stack.name:match("shulker") then
- turtle.select(slot)
- if not drop(direction) then
- unloadedAll = false
- end
- end
- end
- return unloadedAll
- end
- ---@return boolean unloadedAll
- local function tryLoadShulkers()
- ---@type string?
- local placedSide = nil
- for slot = 1, 16 do
- local stack = turtle.getItemDetail(slot)
- if stack and stack.name:match("shulker") then
- turtle.select(slot)
- placedSide = placeAnywhere()
- if not placedSide then
- print("failed to place shulker, no space :(")
- -- [todo] bit of an issue returning false here - shulkers might have enough space for items,
- -- yet we effectively return "shulkers are full" just because we couldn't place it
- -- however, this should only be an issue when digging a 1-high layer
- return false
- else
- local unloadedAll = loadIntoShulker(placedSide)
- turtle.select(slot)
- SquirtleV2.dig(placedSide)
- if unloadedAll then
- return true
- end
- end
- end
- end
- return false
- end
- ---@param args table<string>
- ---@return boolean
- local function main(args)
- print("[dig v3.0.0] booting...")
- local state = boot(args)
- if not state then
- return false
- end
- print(string.format("[area] %dx%dx%d", state.world.depth, state.world.width, state.world.height))
- if #state.ignore > 0 then
- print("[ignore] " .. table.concat(state.ignore, ", "))
- end
- ---@type Vector|nil
- local point = state.position
- local start = state.position
- local world = state.world
- local facing = state.facing
- local shulkersFull = false
- turtle.select(1)
- ---@param block Block
- ---@return boolean
- local isBreakable = function(block)
- if #state.ignore == 0 then
- return true
- end
- return not Utils.find(state.ignore, function(item)
- return string.match(block.name, item)
- end)
- end
- local restoreBreakable = SquirtleV2.setBreakable(isBreakable)
- while point do
- if navigate(point, world, isBreakable) then
- digUpDownIfInBounds(world, point)
- if state.hasShulkers and not shulkersFull and isGettingFull() then
- shulkersFull = not tryLoadShulkers()
- turtle.select(1)
- end
- end
- point = nextPoint(point, world, start)
- end
- if state.hasShulkers and not shulkersFull then
- tryLoadShulkers()
- end
- restoreBreakable()
- print("[done] going home!")
- navigate(start, world, isBreakable)
- face(facing)
- return true
- end
- return main(arg)
- end)
- __bundle_register("squirtle.squirtle-v2", function(require, _LOADED, __bundle_register, __bundle_modules)
- local selectItem = require("squirtle.backpack.select-item")
- local turn = require("squirtle.turn")
- local place = require("squirtle.place")
- local Vector = require("elements.vector")
- local Cardinal = require("elements.cardinal")
- local Fuel = require("squirtle.fuel")
- local refuel = require("squirtle.refuel")
- local requireItems = require("squirtle.require-items")
- local Utils = require("utils")
- ---@class SquirtleV2SimulationResults
- ---@field steps integer
- ---@field placed table<string, integer>
- local natives = {
- move = {
- top = turtle.up,
- up = turtle.up,
- front = turtle.forward,
- forward = turtle.forward,
- bottom = turtle.down,
- down = turtle.down,
- back = turtle.back
- },
- dig = {
- top = turtle.digUp,
- up = turtle.digUp,
- front = turtle.dig,
- forward = turtle.dig,
- bottom = turtle.digDown,
- down = turtle.digDown
- },
- inspect = {
- top = turtle.inspectUp,
- up = turtle.inspectUp,
- front = turtle.inspect,
- forward = turtle.inspect,
- bottom = turtle.inspectDown,
- down = turtle.inspectDown
- }
- }
- ---@param block Block
- ---@return boolean
- local breakableSafeguard = function(block)
- return block.name ~= "minecraft:bedrock"
- end
- ---@class SquirtleV2
- ---@field results SquirtleV2SimulationResults
- ---@field breakable? fun(block: Block) : boolean
- local SquirtleV2 = {
- flipTurns = false,
- simulate = false,
- results = {placed = {}, steps = 0},
- position = Vector.create(0, 0, 0),
- facing = Cardinal.south
- }
- ---@param block Block
- ---@return boolean
- local function canBreak(block)
- return SquirtleV2.breakable ~= nil and breakableSafeguard(block) and SquirtleV2.breakable(block)
- end
- ---@param predicate? fun(block: Block) : boolean
- ---@return fun() : nil
- function SquirtleV2.setBreakable(predicate)
- local current = SquirtleV2.breakable
- local function restore()
- SquirtleV2.breakable = current
- end
- SquirtleV2.breakable = predicate
- return restore
- end
- ---@param side? string
- ---@param steps? integer
- ---@return boolean, integer, string?
- function SquirtleV2.tryMove(side, steps)
- side = side or "front"
- local native = natives.move[side]
- if not native then
- error(string.format("move() does not support side %s", side))
- end
- if SquirtleV2.simulate then
- if steps then
- SquirtleV2.results.steps = SquirtleV2.results.steps + 1
- else
- -- "tryMove()" doesn't simulate any steps because it is assumed that it is called only to move until an unbreakable block is hit,
- -- and since we're not simulating a world we can not really return a meaningful value of steps taken if none have been supplied.
- return false, 0, "simulation mode is active"
- end
- end
- steps = steps or 1
- if not Fuel.hasFuel(steps) then
- refuel(steps)
- end
- local delta = Cardinal.toVector(Cardinal.fromSide(side, SquirtleV2.facing))
- for step = 1, steps do
- repeat
- local success, error = native()
- if not success then
- local actionSide = side
- if side == "back" then
- actionSide = "front"
- SquirtleV2.around()
- end
- local block = SquirtleV2.inspect(actionSide)
- if not block then
- if side == "back" then
- SquirtleV2.around()
- end
- -- [todo] it is possible (albeit unlikely) that between handler() and inspect(), a previously
- -- existing block has been removed by someone else
- error(string.format("move(%s) failed, but there is no block in the way", side))
- end
- -- [todo] wanted to reuse newly introduced Squirtle.tryDig(), but it would be awkward to do so.
- -- maybe I find a non-awkward solution in the future?
- -- [todo] should tryDig really try to dig? I think I am going to use this only for "move until you hit something",
- -- so in that case, no, it shouldn't try to dig.
- if canBreak(block) then
- while SquirtleV2.dig(actionSide) do
- end
- if side == "back" then
- SquirtleV2.around()
- end
- else
- if side == "back" then
- SquirtleV2.around()
- end
- return false, step - 1, string.format("blocked by %s", block.name)
- end
- end
- until success
- SquirtleV2.position = Vector.plus(SquirtleV2.position, delta)
- end
- return true, steps
- end
- ---@param times? integer
- ---@return boolean, integer, string?
- function SquirtleV2.tryForward(times)
- return SquirtleV2.tryMove("forward", times)
- end
- ---@param times? integer
- ---@return boolean, integer, string?
- function SquirtleV2.tryUp(times)
- return SquirtleV2.tryMove("up", times)
- end
- ---@param times? integer
- ---@return boolean, integer, string?
- function SquirtleV2.tryDown(times)
- return SquirtleV2.tryMove("down", times)
- end
- ---@param times? integer
- ---@return boolean, integer, string?
- function SquirtleV2.tryBack(times)
- return SquirtleV2.tryMove("back", times)
- end
- ---@param side? string
- ---@param times? integer
- function SquirtleV2.move(side, times)
- if SquirtleV2.simulate then
- -- when simulating, only "move()" will simulate actual steps.
- times = times or 1
- SquirtleV2.results.steps = SquirtleV2.results.steps + 1
- return nil
- end
- local success, _, message = SquirtleV2.tryMove(side, times)
- if not success then
- error(string.format("move(%s) failed: %s", side, message))
- end
- end
- ---@param times? integer
- function SquirtleV2.forward(times)
- SquirtleV2.move("forward", times)
- end
- ---@param times? integer
- function SquirtleV2.up(times)
- SquirtleV2.move("up", times)
- end
- ---@param times? integer
- function SquirtleV2.down(times)
- SquirtleV2.move("down", times)
- end
- ---@param times? integer
- function SquirtleV2.back(times)
- SquirtleV2.move("back", times)
- end
- ---@param side? string
- function SquirtleV2.turn(side)
- if not SquirtleV2.simulate then
- if SquirtleV2.flipTurns then
- if side == "left" then
- side = "right"
- elseif side == "right" then
- side = "left"
- end
- end
- turn(side)
- end
- end
- function SquirtleV2.left()
- SquirtleV2.turn("left")
- end
- function SquirtleV2.right()
- SquirtleV2.turn("right")
- end
- function SquirtleV2.around()
- SquirtleV2.turn("back")
- end
- ---@param side? string
- ---@param toolSide? string
- ---@return boolean, string?
- function SquirtleV2.tryDig(side, toolSide)
- if SquirtleV2.simulate then
- return true
- end
- side = side or "front"
- local native = natives.dig[side]
- if not native then
- error(string.format("dig() does not support side %s", side))
- end
- local block = SquirtleV2.inspect(side)
- if not block then
- return false
- end
- if not canBreak(block) then
- return false, string.format("not allowed to dig block %s", block.name)
- end
- local success, message = native(toolSide)
- if not success and string.match(message, "tool") then
- if toolSide then
- error(string.format("dig(%s, %s) failed: %s", side, toolSide, message))
- else
- error(string.format("dig(%s) failed: %s", side, message))
- end
- end
- return success, message
- end
- ---@param side? string
- ---@param toolSide? string
- ---@return boolean, string?
- function SquirtleV2.dig(side, toolSide)
- local success, message = SquirtleV2.tryDig(side, toolSide)
- -- if there is no message, then there just wasn't anything to dig, meaning every other case is interpreted as an error
- if not success and message then
- error(message)
- end
- return success
- end
- ---@param block string
- ---@param side? string
- function SquirtleV2.place(block, side)
- if SquirtleV2.simulate then
- if not SquirtleV2.results.placed[block] then
- SquirtleV2.results.placed[block] = 0
- end
- SquirtleV2.results.placed[block] = SquirtleV2.results.placed[block] + 1
- else
- while not SquirtleV2.select(block, true) do
- requireItems({[block] = 1})
- end
- -- [todo] error handling
- place(side)
- end
- end
- ---@param block string
- function SquirtleV2.placeUp(block)
- SquirtleV2.place(block, "up")
- end
- ---@param block string
- function SquirtleV2.placeDown(block)
- SquirtleV2.place(block, "down")
- end
- ---@param name string
- ---@param exact? boolean
- ---@return false|integer
- function SquirtleV2.select(name, exact)
- return selectItem(name, exact)
- end
- ---@param side? string
- ---@param name? table|string
- ---@return Block? block
- function SquirtleV2.inspect(side, name)
- side = side or "front"
- local native = natives.inspect[side]
- if not native then
- error(string.format("inspect() does not support side %s", side))
- end
- local success, block = native()
- if success then
- if name then
- if type(name) == "string" and block.name == name then
- return block
- elseif type(name) == "table" and Utils.indexOf(name, block.name) > 0 then
- return block
- else
- return nil
- end
- end
- return block
- else
- return nil
- end
- end
- return SquirtleV2
- end)
- __bundle_register("utils", function(require, _LOADED, __bundle_register, __bundle_modules)
- local ccPretty = "cc.pretty"
- local Pretty = require(ccPretty)
- local copy = require("utils.copy")
- local indexOf = require("utils.index-of")
- local Utils = {copy = copy, indexOf = indexOf}
- ---@param list table
- ---@param values table
- ---@return table
- function Utils.push(list, values)
- for i = 1, #values do
- list[#list + 1] = values[i]
- end
- return list
- end
- ---@param list table
- ---@param value unknown
- ---@return boolean
- function Utils.contains(list, value)
- for i = 1, #list do
- if list[i] == value then
- return true
- end
- end
- return false
- end
- -- https://stackoverflow.com/a/26367080/1611592
- ---@generic T: table
- ---@param tbl T
- ---@return T
- function Utils.clone(tbl, seen)
- if seen and seen[tbl] then
- return seen[tbl]
- end
- local s = seen or {}
- local res = setmetatable({}, getmetatable(tbl))
- s[tbl] = res
- for k, v in pairs(tbl) do
- res[Utils.clone(k, s)] = Utils.clone(v, s)
- end
- return res
- end
- function Utils.isEmpty(t)
- for _, _ in pairs(t) do
- return false
- end
- return true
- end
- ---@generic T, U
- ---@param list T[]
- ---@param mapper fun(item: T, index: number): U
- ---@return U[]
- function Utils.map(list, mapper)
- local mapped = {}
- for i = 1, #list do
- table.insert(mapped, mapper(list[i], i))
- end
- return mapped
- end
- ---@generic T
- ---@param list T[]
- ---@param property string
- ---@return T[]
- function Utils.toMap(list, property)
- local map = {}
- for _, element in pairs(list) do
- local id = element[property]
- if type(id) ~= "string" then
- error("id must be of type string")
- end
- map[id] = element
- end
- return map
- end
- ---@generic T
- ---@param list T[]
- ---@param predicate fun(item: T, index: number): boolean
- ---@return T[]
- function Utils.filter(list, predicate)
- local filtered = {}
- for i = 1, #list do
- if predicate(list[i], i) then
- table.insert(filtered, list[i])
- end
- end
- return filtered
- end
- ---@generic T
- ---@param list T[]
- ---@param predicate fun(item: T, index: number): boolean
- ---@return T|nil, integer|nil
- function Utils.find(list, predicate)
- for i = 1, #list do
- if predicate(list[i], i) then
- return list[i], i
- end
- end
- end
- function Utils.reverse(list)
- for i = 1, #list / 2, 1 do
- list[i], list[#list - i + 1] = list[#list - i + 1], list[i]
- end
- return list
- end
- function Utils.prettyPrint(value)
- Pretty.print(Pretty.pretty(value))
- end
- function Utils.count(table)
- local size = 0
- for _ in pairs(table) do
- size = size + 1
- end
- return size
- end
- ---@generic T
- ---@param tbl T[]
- ---@return T?
- function Utils.first(tbl)
- for _, item in pairs(tbl) do
- return item
- end
- end
- function Utils.waitForUserToHitEnter()
- while true do
- local _, key = os.pullEvent("key")
- if (key == keys.enter) then
- break
- end
- end
- end
- function Utils.writeAutorunFile(args)
- local file = fs.open("startup/" .. args[1] .. ".autorun.lua", "w")
- file.write("shell.run(\"" .. table.concat(args, " ") .. "\")")
- file.close()
- end
- ---@param path string
- ---@return table?
- function Utils.readJson(path)
- local file = fs.open(path, "r")
- if not file then
- return
- end
- return textutils.unserializeJSON(file.readAll())
- end
- ---@param path string
- ---@param data table
- function Utils.writeJson(path, data)
- local file = fs.open(path, "w")
- file.write(textutils.serialiseJSON(data))
- file.close()
- end
- return Utils
- end)
- __bundle_register("utils.index-of", function(require, _LOADED, __bundle_register, __bundle_modules)
- ---@param tbl table
- ---@param item unknown
- ---@return integer
- return function(tbl, item)
- for i = 1, #tbl do
- if (tbl[i] == item) then
- return i
- end
- end
- return -1
- end
- end)
- __bundle_register("utils.copy", function(require, _LOADED, __bundle_register, __bundle_modules)
- ---@generic T: table
- ---@param tbl T
- ---@return T
- return function(tbl)
- local copy = {}
- for k, v in pairs(tbl) do
- copy[k] = v
- end
- return copy
- end
- end)
- __bundle_register("squirtle.require-items", function(require, _LOADED, __bundle_register, __bundle_modules)
- local Backpack = require("squirtle.backpack")
- local Utils = require("utils")
- ---@param items table<string, integer>
- local function getMissing(items)
- ---@type table<string, integer>
- local open = {}
- local stock = Backpack.getStock()
- for item, required in pairs(items) do
- local missing = required - (stock[item] or 0)
- if missing > 0 then
- open[item] = required - (stock[item] or 0)
- end
- end
- return open
- end
- ---@param items table<string, integer>
- return function(items)
- while true do
- ---@type table<string, integer>
- local open = getMissing(items)
- if Utils.count(open) == 0 then
- term.clear()
- term.setCursorPos(1, 1)
- return nil
- end
- term.clear()
- term.setCursorPos(1, 1)
- print("Required Items")
- local width = term.getSize()
- print(string.rep("-", width))
- for item, missing in pairs(open) do
- print(string.format("%dx %s", missing, item))
- end
- os.pullEvent("turtle_inventory")
- end
- end
- end)
- __bundle_register("squirtle.backpack", function(require, _LOADED, __bundle_register, __bundle_modules)
- local find = require("squirtle.backpack.find")
- local getSize = require("squirtle.backpack.get-size")
- local getStack = require("squirtle.backpack.get-stack")
- local getStacks = require("squirtle.backpack.get-stacks")
- local selectItem = require("squirtle.backpack.select-item")
- local selectSlot = require("squirtle.backpack.select-slot")
- local native = turtle
- local Backpack = {}
- -- [todo] move bit logic somewhere else?
- local bitSourceSlot = 15
- local bitSlot = 16
- local bitItemType = "minecraft:redstone_torch"
- local bitDisplayName = "bit"
- ---@return integer
- function Backpack.size()
- return getSize()
- end
- ---@param slot integer
- ---@param detailed? boolean
- ---@return ItemStack?
- function Backpack.getStack(slot, detailed)
- return getStack(slot, detailed)
- end
- ---@param slot integer
- ---@param count? integer
- function Backpack.transfer(slot, count)
- return native.transferTo(slot, count)
- end
- ---@return boolean
- function Backpack.isEmpty()
- for slot = 1, getSize() do
- if Backpack.numInSlot(slot) > 0 then
- return false
- end
- end
- return true
- end
- ---@return ItemStack[]
- function Backpack.getStacks()
- return getStacks()
- end
- ---@return table<string, integer>
- function Backpack.getStock()
- ---@type table<string, integer>
- local stock = {}
- for _, stack in pairs(Backpack.getStacks()) do
- stock[stack.name] = (stock[stack.name] or 0) + stack.count
- end
- return stock
- end
- ---@param predicate string|function<boolean, ItemStack>
- function Backpack.getItemStock(predicate)
- if type(predicate) == "string" then
- local name = predicate
- ---@param stack ItemStack
- predicate = function(stack)
- return stack.name == name
- end
- end
- local stock = 0
- for _, stack in pairs(Backpack.getStacks()) do
- if predicate(stack) then
- stock = stock + stack.count
- end
- end
- return stock
- end
- ---@param slot integer
- function Backpack.selectSlot(slot)
- return selectSlot(slot)
- end
- ---@param slot integer
- ---@return integer
- function Backpack.numInSlot(slot)
- return native.getItemCount(slot)
- end
- ---@return boolean
- function Backpack.selectSlotIfNotEmpty(slot)
- if Backpack.numInSlot(slot) > 0 then
- return Backpack.selectSlot(slot)
- else
- return false
- end
- end
- ---@param name string
- ---@param exact? boolean
- function Backpack.find(name, exact)
- return find(name, exact)
- end
- ---@param name string
- function Backpack.selectItem(name)
- return selectItem(name)
- end
- ---@param startAt? number
- function Backpack.firstEmptySlot(startAt)
- startAt = startAt or 1
- for slot = startAt, Backpack.size() do
- if Backpack.numInSlot(slot) == 0 then
- return slot
- end
- end
- return nil
- end
- ---@return boolean|integer
- function Backpack.selectFirstEmptySlot()
- local slot = Backpack.firstEmptySlot()
- if not slot then
- return false
- end
- Backpack.selectSlot(slot)
- return slot
- end
- function Backpack.readBits()
- local stack = getStack(bitSlot, true)
- -- [todo] should we error in case there is a stack and it is not
- -- the type of bit item we except?
- if stack and stack.name == bitItemType and stack.displayName:lower() == bitDisplayName then
- return stack.count
- end
- return 0
- end
- -- [todo] throw errors?
- ---@param bits integer
- function Backpack.setBits(bits)
- local current = Backpack.readBits()
- if current == bits then
- return true
- elseif current < bits then
- selectSlot(bitSourceSlot)
- return Backpack.transfer(bitSlot, bits - current)
- elseif current > bits then
- selectSlot(bitSlot)
- return Backpack.transfer(bitSourceSlot, current - bits)
- end
- end
- -- [todo] throw errors?
- ---@param bits integer
- function Backpack.orBits(bits)
- local current = Backpack.readBits()
- local next = bit.bor(bits, current)
- if next ~= current then
- return Backpack.setBits(next)
- end
- return true
- end
- -- [todo] throw errors?
- ---@param bits integer
- function Backpack.xorBits(bits)
- local current = Backpack.readBits()
- local next = bit.bxor(bits, current)
- if next ~= current then
- return Backpack.setBits(next)
- end
- return true
- end
- function Backpack.condense()
- for slot = getSize(), 1, -1 do
- local item = getStack(slot)
- if item then
- for targetSlot = 1, slot - 1 do
- local candidate = getStack(targetSlot, true)
- if candidate and candidate.name == item.name and candidate.count < candidate.maxCount then
- selectSlot(slot)
- Backpack.transfer(targetSlot)
- if Backpack.numInSlot(slot) == 0 then
- break
- end
- elseif not candidate then
- selectSlot(slot)
- Backpack.transfer(targetSlot)
- break
- end
- end
- end
- end
- end
- ---@return boolean
- function Backpack.isFull()
- for slot = 1, getSize() do
- if Backpack.numInSlot(slot) == 0 then
- return false
- end
- end
- return true
- end
- return Backpack;
- end)
- __bundle_register("squirtle.backpack.select-slot", function(require, _LOADED, __bundle_register, __bundle_modules)
- ---@param slot integer
- return function(slot)
- return turtle.select(slot)
- end
- end)
- __bundle_register("squirtle.backpack.select-item", function(require, _LOADED, __bundle_register, __bundle_modules)
- local find = require("squirtle.backpack.find")
- local selectSlot = require("squirtle.backpack.select-slot")
- ---@param name string
- ---@param exact? boolean
- ---@return false|integer
- return function(name, exact)
- local slot = find(name, exact)
- if not slot then
- return false
- end
- selectSlot(slot)
- return slot
- end
- end)
- __bundle_register("squirtle.backpack.find", function(require, _LOADED, __bundle_register, __bundle_modules)
- local getSize = require("squirtle.backpack.get-size")
- local getStack = require("squirtle.backpack.get-stack")
- ---@param name string
- ---@param exact? boolean
- return function(name, exact)
- for slot = 1, getSize() do
- local item = getStack(slot)
- if item and exact and item.name == name then
- return slot
- elseif item and not exact and string.find(item.name, name) then
- return slot
- end
- end
- end
- end)
- __bundle_register("squirtle.backpack.get-stack", function(require, _LOADED, __bundle_register, __bundle_modules)
- ---@param slot integer
- ---@param detailed? boolean
- ---@return ItemStack?
- return function(slot, detailed)
- return turtle.getItemDetail(slot, detailed)
- end
- end)
- __bundle_register("squirtle.backpack.get-size", function(require, _LOADED, __bundle_register, __bundle_modules)
- ---@return integer
- return function()
- return 16
- end
- end)
- __bundle_register("squirtle.backpack.get-stacks", function(require, _LOADED, __bundle_register, __bundle_modules)
- local getSize = require("squirtle.backpack.get-size")
- local getStack = require("squirtle.backpack.get-stack")
- -- [todo] idea: cache stacks until any event is pulled.
- -- that should - afaik - be completely safe, as any change in turtle inventory triggers a "turtle_inventory" event
- ---@return ItemStack[]
- return function()
- local stacks = {}
- for slot = 1, getSize() do
- local item = getStack(slot)
- if item then
- stacks[slot] = item
- end
- end
- return stacks
- end
- end)
- __bundle_register("squirtle.refuel", function(require, _LOADED, __bundle_register, __bundle_modules)
- local Fuel = require("squirtle.fuel")
- local refuelFromBackpack = require("squirtle.refuel.from-backpack")
- local refuelWithHelpFromPlayer = require("squirtle.refuel.with-help-from-player")
- ---@param fuel integer
- return function(fuel)
- if Fuel.hasFuel(fuel) then
- return true
- end
- refuelFromBackpack(fuel)
- if Fuel.getFuelLevel() < fuel then
- refuelWithHelpFromPlayer(fuel)
- end
- end
- end)
- __bundle_register("squirtle.refuel.with-help-from-player", function(require, _LOADED, __bundle_register, __bundle_modules)
- local Fuel = require("squirtle.fuel")
- local refuelFromBackpack = require("squirtle.refuel.from-backpack")
- ---@param fuel? integer
- return function(fuel)
- fuel = fuel or Fuel.getMissingFuel()
- if fuel > turtle.getFuelLimit() then
- error(string.format("required fuel is %d more than the tank can hold", fuel - turtle.getFuelLimit()))
- end
- local _, y = term.getCursorPos()
- while Fuel.getFuelLevel() < fuel do
- term.setCursorPos(1, y)
- term.clearLine()
- local openFuel = fuel - Fuel.getFuelLevel()
- term.write(string.format("[help] need %d more fuel please", openFuel))
- term.setCursorPos(1, y + 1)
- os.pullEvent("turtle_inventory")
- refuelFromBackpack(openFuel)
- end
- end
- end)
- __bundle_register("squirtle.refuel.from-backpack", function(require, _LOADED, __bundle_register, __bundle_modules)
- local Fuel = require("squirtle.fuel")
- local Backpack = require("squirtle.backpack")
- local bucket = "minecraft:bucket"
- ---@param fuel? integer
- return function(fuel)
- fuel = fuel or Fuel.getMissingFuel()
- local fuelStacks = Fuel.pickStacks(Backpack.getStacks(), fuel)
- local emptyBucketSlot = Backpack.find(bucket)
- for slot, stack in pairs(fuelStacks) do
- Backpack.selectSlot(slot)
- Fuel.refuel(stack.count)
- local remaining = Backpack.getStack(slot)
- if remaining and remaining.name == bucket then
- if (emptyBucketSlot == nil) or (not Backpack.transfer(emptyBucketSlot)) then
- emptyBucketSlot = slot
- end
- end
- end
- end
- end)
- __bundle_register("squirtle.fuel", function(require, _LOADED, __bundle_register, __bundle_modules)
- ---@class Fuel
- local Fuel = {}
- local native = turtle
- local items = {
- -- ["minecraft:lava_bucket"] = 1000,
- ["minecraft:coal"] = 80,
- ["minecraft:charcoal"] = 80,
- ["minecraft:coal_block"] = 800,
- -- ["minecraft:bamboo"] = 2
- }
- ---@param fuel integer
- function Fuel.hasFuel(fuel)
- local level = native.getFuelLevel()
- return level == "unlimited" or level >= fuel
- end
- ---@param count? integer
- function Fuel.refuel(count)
- return native.refuel(count)
- end
- ---@return integer
- function Fuel.getFuelLevel()
- return native.getFuelLevel()
- end
- ---@return integer
- function Fuel.getFuelLimit()
- return native.getFuelLimit()
- end
- ---@param limit? integer
- ---@return integer
- function Fuel.getMissingFuel(limit)
- local fuelLevel = Fuel.getFuelLevel()
- if fuelLevel == "unlimited" then
- return 0
- end
- if not limit then
- limit = Fuel.getFuelLimit()
- end
- return limit - Fuel.getFuelLevel()
- end
- ---@param item string
- function Fuel.isFuel(item)
- return items[item] ~= nil
- end
- ---@param item string
- function Fuel.getRefuelAmount(item)
- return items[item] or 0
- end
- --- @param stack table
- function Fuel.getStackRefuelAmount(stack)
- return Fuel.getRefuelAmount(stack.name) * stack.count
- end
- ---@param stacks ItemStack[]
- function Fuel.filterStacks(stacks)
- local fuelStacks = {}
- for slot, stack in pairs(stacks) do
- if Fuel.isFuel(stack.name) then
- fuelStacks[slot] = stack
- end
- end
- return fuelStacks
- end
- ---@param stacks ItemStack[]
- ---@param fuel number
- ---@param allowedOverFlow? number
- ---@return ItemStack[] fuelStacks, number openFuel
- function Fuel.pickStacks(stacks, fuel, allowedOverFlow)
- allowedOverFlow = math.max(allowedOverFlow or 1000, 0)
- local pickedStacks = {}
- local openFuel = fuel
- -- [todo] try to order stacks based on type of item
- -- for example, we may want to start with the smallest ones to minimize potential overflow
- for slot, stack in pairs(stacks) do
- if Fuel.isFuel(stack.name) then
- local stackRefuelAmount = Fuel.getStackRefuelAmount(stack)
- if stackRefuelAmount <= openFuel then
- pickedStacks[slot] = stack
- openFuel = openFuel - stackRefuelAmount
- else
- -- [todo] can be shortened
- -- actually, im not even sure we need the option to provide an allowed overflow
- local itemRefuelAmount = Fuel.getRefuelAmount(stack.name)
- local numRequiredItems = math.floor(openFuel / itemRefuelAmount)
- local numItemsToPick = numRequiredItems
- if allowedOverFlow > 0 and ((numItemsToPick + 1) * itemRefuelAmount) - openFuel <= allowedOverFlow then
- numItemsToPick = numItemsToPick + 1
- end
- -- local numRequiredItems = math.ceil(openFuel / itemRefuelAmount)
- -- if (numRequiredItems * itemRefuelAmount) - openFuel <= allowedOverFlow then
- if numItemsToPick > 0 then
- pickedStacks[slot] = {name = stack.name, count = numItemsToPick}
- openFuel = openFuel - stackRefuelAmount
- end
- end
- if openFuel <= 0 then
- break
- end
- end
- end
- -- if Utils.isEmpty(pickedStacks) then
- -- pickedStacks = nil
- -- end
- return pickedStacks, openFuel
- end
- ---@param stacks ItemStack[]
- function Fuel.sumFuel(stacks)
- local fuel = 0
- for _, stack in pairs(stacks) do
- if Fuel.isFuel(stack.name) then
- fuel = fuel + Fuel.getStackRefuelAmount(stack)
- end
- end
- return fuel
- end
- return Fuel
- end)
- __bundle_register("elements.cardinal", function(require, _LOADED, __bundle_register, __bundle_modules)
- local Vector = require("elements.vector")
- local Side = require("elements.side")
- ---@class Cardinal
- local Cardinal = {south = 0, west = 1, north = 2, east = 3, up = 4, down = 5}
- local names = {
- [Cardinal.south] = "south",
- [Cardinal.west] = "west",
- [Cardinal.north] = "north",
- [Cardinal.east] = "east",
- [Cardinal.up] = "up",
- [Cardinal.down] = "down"
- }
- ---@param vector Vector
- function Cardinal.fromVector(vector)
- if vector.x > 0 and vector.y == 0 and vector.z == 0 then
- return Cardinal.east
- elseif vector.x < 0 and vector.y == 0 and vector.z == 0 then
- return Cardinal.west
- elseif vector.x == 0 and vector.y == 0 and vector.z > 0 then
- return Cardinal.south
- elseif vector.x == 0 and vector.y == 0 and vector.z < 0 then
- return Cardinal.north
- elseif vector.x == 0 and vector.y > 0 and vector.z == 0 then
- return Cardinal.up
- elseif vector.x == 0 and vector.y < 0 and vector.z == 0 then
- return Cardinal.down
- end
- error(vector .. " is not a cardinal vector")
- end
- ---@param cardinal number
- function Cardinal.toVector(cardinal)
- if cardinal == Cardinal.south then
- return Vector.create(0, 0, 1)
- elseif cardinal == Cardinal.west then
- return Vector.create(-1, 0, 0)
- elseif cardinal == Cardinal.north then
- return Vector.create(0, 0, -1)
- elseif cardinal == Cardinal.east then
- return Vector.create(1, 0, 0)
- elseif cardinal == Cardinal.up then
- return Vector.create(0, 1, 0)
- elseif cardinal == Cardinal.down then
- return Vector.create(0, -1, 0)
- end
- end
- ---@param cardinal number
- function Cardinal.getName(cardinal)
- local name = names[cardinal]
- if name == nil then
- error("not a valid cardinal: " .. tostring(cardinal))
- end
- return names[cardinal] or tostring(cardinal);
- end
- -- [todo] why did i comment this out?
- -- function Cardinal.rotate(cardinal, rotation)
- -- return (cardinal + rotation) % 4
- -- end
- ---@param cardinal integer
- ---@param times? number
- ---@return integer
- function Cardinal.rotateLeft(cardinal, times)
- return (cardinal - (times or 1)) % 4
- end
- ---@param cardinal integer
- ---@param times? number
- ---@return integer
- function Cardinal.rotateRight(cardinal, times)
- return (cardinal + (times or 1)) % 4
- end
- ---@param cardinal integer
- ---@return integer
- function Cardinal.rotateAround(cardinal, times)
- return (cardinal + (2 * (times or 1))) % 4
- end
- ---@param cardinal integer
- ---@param side string|integer
- ---@param times? integer
- ---@return integer
- function Cardinal.rotate(cardinal, side, times)
- if side == Side.left or side == "left" then
- return Cardinal.rotateLeft(cardinal, times)
- elseif side == Side.right or side == "right" then
- return Cardinal.rotateRight(cardinal, times)
- elseif side == Side.back or side == "back" then
- return Cardinal.rotateAround(cardinal, times)
- elseif side == Side.front or side == "front" then
- return cardinal
- else
- error(string.format("rotate() doesn't support side %s", Side.getName(side)))
- end
- end
- -- [todo] did i give up?
- -- function Cardinal.rotateBy(cardinal, by)
- -- if (cardinal + 1) % 4 == by then
- -- else if (cardinal - 1) % 4 == by then
- -- else
- -- end
- -- end
- function Cardinal.isVertical(cardinal)
- return cardinal == Cardinal.up or cardinal == Cardinal.down
- end
- ---@param side string|integer
- ---@param facing integer
- ---@return integer
- function Cardinal.fromSide(side, facing)
- if side == Side.front or side == "front" or side == "forward" then
- return facing
- elseif side == Side.top or side == "top" or side == "up" then
- return Cardinal.up
- elseif side == Side.bottom or side == "bottom" or side == "down" then
- return Cardinal.down
- elseif side == Side.left or side == "left" then
- return Cardinal.rotateLeft(facing)
- elseif side == Side.right or side == "right" then
- return Cardinal.rotateRight(facing)
- elseif side == Side.back or side == "back" then
- return Cardinal.rotateAround(facing)
- end
- error(("invalid side: %s"):format(side))
- end
- ---@param value integer
- function Cardinal.isCardinal(value)
- return value >= 0 and value <= 5
- end
- return Cardinal
- end)
- __bundle_register("elements.side", function(require, _LOADED, __bundle_register, __bundle_modules)
- ---@class Side
- local Side = {front = 0, right = 1, back = 2, left = 3, top = 4, bottom = 5, up = 4, down = 5}
- local lookup = {
- front = 0,
- [0] = "front",
- right = 1,
- [1] = "right",
- back = 2,
- [2] = "back",
- left = 3,
- [3] = "left",
- top = 4,
- [4] = "top",
- bottom = 5,
- [5] = "bottom",
- forward = 0,
- up = 4,
- down = 5
- }
- if not turtle then
- Side.right = 3
- Side.left = 1
- end
- local names = {}
- for k, v in pairs(Side) do
- names[v] = k
- end
- ---@param side string|integer
- function Side.getName(side)
- if type(side) == "string" then
- return side
- else
- return names[side] or tostring(side);
- end
- end
- ---@param side string
- ---@return string
- function Side.rotateAround(side)
- return lookup[(lookup[side] + 2) % 4]
- end
- return Side
- end)
- __bundle_register("elements.vector", function(require, _LOADED, __bundle_register, __bundle_modules)
- ---@class Vector
- ---@field x number
- ---@field y number
- ---@field z number
- local Vector = {}
- ---@return Vector
- function Vector.create(x, y, z)
- if type(x) ~= "number" then
- error("expected x to be a number, was " .. type(x))
- elseif type(y) ~= "number" then
- error("expected y to be a number, was " .. type(y))
- elseif type(z) ~= "number" then
- error("expected z to be a number, was " .. type(z))
- end
- local instance = {x = x or 0, y = y or 0, z = z or 0}
- setmetatable(instance, {__tostring = Vector.toString, __concat = Vector.concat})
- return instance
- end
- function Vector.plus(a, b)
- if (type(a) == "number") then
- return Vector.create(b.x + a, b.y + a, b.z + a)
- elseif (type(b) == "number") then
- return Vector.create(a.x + b, a.y + b, a.z + b)
- else
- return Vector.create(a.x + b.x, a.y + b.y, a.z + b.z)
- end
- end
- ---@param other Vector
- function Vector.minus(self, other)
- return Vector.create(self.x - other.x, self.y - other.y, self.z - other.z)
- end
- ---@param self Vector
- ---@param other Vector
- ---@return boolean
- function Vector.equals(self, other)
- return self.x == other.x and self.y == other.y and self.z == other.z
- end
- ---@param self Vector
- ---@return number
- function Vector.length(self)
- return math.sqrt(self.x * self.x + self.y * self.y + self.z * self.z)
- end
- ---@param a Vector
- ---@param b Vector
- ---@return number
- function Vector.distance(a, b)
- return Vector.length(Vector.minus(a, b))
- end
- function Vector:negate()
- return Vector.create(-self.x, -self.y, -self.z)
- end
- function Vector:toString()
- return string.format("%d,%d,%d", self.x, self.y, self.z)
- end
- function Vector:concat(other)
- return tostring(self) .. tostring(other)
- end
- ---@param a Vector
- ---@param b Vector
- ---@return number
- function Vector.manhattan(a, b)
- return math.abs(b.x - a.x) + math.abs(b.y - a.y) + math.abs(b.z - a.z)
- end
- ---@param vector Vector
- ---@param times integer?
- ---@return Vector
- function Vector.rotateClockwise(vector, times)
- times = times or 1
- local rotated = Vector.create(vector.x, vector.y, vector.z)
- for _ = 1, times do
- local x = -rotated.z
- local z = rotated.x
- rotated.x = x
- rotated.z = z
- end
- return rotated
- end
- return Vector
- end)
- __bundle_register("squirtle.place", function(require, _LOADED, __bundle_register, __bundle_modules)
- local dig = require("squirtle.dig")
- local natives = {
- top = turtle.placeUp,
- up = turtle.placeUp,
- front = turtle.place,
- forward = turtle.place,
- bottom = turtle.placeDown,
- down = turtle.placeDown
- }
- ---@param side? string
- return function(side)
- side = side or "front"
- local handler = natives[side]
- if not handler then
- error(string.format("place() does not support side %s", side))
- end
- local success = handler()
- if not success then
- if dig(side) then
- success = handler()
- end
- end
- return success
- end
- end)
- __bundle_register("squirtle.dig", function(require, _LOADED, __bundle_register, __bundle_modules)
- local natives = {
- top = turtle.digUp,
- up = turtle.digUp,
- front = turtle.dig,
- forward = turtle.dig,
- bottom = turtle.digDown,
- down = turtle.digDown
- }
- ---@param side? string
- ---@param toolSide? string
- ---@return boolean
- return function(side, toolSide)
- side = side or "front"
- local handler = natives[side]
- if not handler then
- error(string.format("dig() does not support side %s", side))
- end
- local success = handler(toolSide)
- -- omitting message on purpose
- -- [todo] what is that purpose?
- return success
- end
- end)
- __bundle_register("squirtle.turn", function(require, _LOADED, __bundle_register, __bundle_modules)
- local Cardinal = require("elements.cardinal")
- local getState = require("squirtle.get-state")
- local changeState = require("squirtle.change-state")
- local native = turtle
- local function turnLeft()
- local state = getState()
- local success, message = native.turnLeft()
- if not success then
- return false, message
- end
- if state.facing then
- changeState({facing = Cardinal.rotateLeft(state.facing)})
- end
- return true
- end
- local function turnRight()
- local state = getState()
- local success, message = native.turnRight()
- if not success then
- return false, message
- end
- if state.facing then
- changeState({facing = Cardinal.rotateRight(state.facing)})
- end
- return true
- end
- local function turnBack()
- local turnFn = turnLeft
- if math.random() < .5 then
- turnFn = turnRight
- end
- local success, message = turnFn()
- if not success then
- return false, message
- end
- return turnFn()
- end
- ---@param side? string defaults to "left"
- return function(side)
- -- [todo] turning to the left by default is not a good idea,
- -- makes it harder to find bugs in case a bad side is given, e.g. "nil"
- side = side or "left"
- if side == "left" then
- return turnLeft()
- elseif side == "right" then
- return turnRight()
- elseif side == "back" then
- return turnBack()
- elseif side == "front" then
- return true
- else
- error(string.format("turn() does not support side %s", side))
- end
- end
- end)
- __bundle_register("squirtle.change-state", function(require, _LOADED, __bundle_register, __bundle_modules)
- local getState = require("squirtle.get-state")
- ---@param patch SquirtleState
- return function(patch)
- local state = getState()
- for key, value in pairs(patch) do
- state[key] = value
- end
- return state
- end
- end)
- __bundle_register("squirtle.get-state", function(require, _LOADED, __bundle_register, __bundle_modules)
- ---@class SquirtleState
- ---@field position Vector
- ---@field facing integer
- ---@type SquirtleState
- local state = {}
- ---@return SquirtleState
- return function()
- return state
- end
- end)
- __bundle_register("squirtle.drop", function(require, _LOADED, __bundle_register, __bundle_modules)
- local natives = {
- top = turtle.dropUp,
- up = turtle.dropUp,
- front = turtle.drop,
- bottom = turtle.dropDown,
- down = turtle.dropDown
- }
- ---@param side? string
- ---@param limit? integer
- ---@return boolean, string?
- return function(side, limit)
- side = side or "front"
- local handler = natives[side]
- if not handler then
- error(string.format("drop() does not support side %s", side))
- end
- return handler(limit)
- end
- end)
- __bundle_register("dig.boot", function(require, _LOADED, __bundle_register, __bundle_modules)
- local Fuel = require("squirtle.fuel")
- local Cardinal = require("elements.cardinal")
- local Vector = require("elements.vector")
- local World = require("geo.world")
- local changeState = require("squirtle.change-state")
- local refuel = require("squirtle.refuel")
- local function printUsage()
- print("Usage:")
- print("dig <depth> <width> <height>")
- print("(negative numbers possible)")
- end
- ---@param args table<string>
- ---@return DigAppState? state
- return function(args)
- local depth = tonumber(args[1])
- local width = tonumber(args[2])
- local height = tonumber(args[3])
- ---@type table<string>
- local ignore = {}
- for i = 4, #args do
- table.insert(ignore, args[i])
- end
- if not depth or not width or not height or depth == 0 or width == 0 or height == 0 then
- printUsage()
- return nil
- end
- depth = -depth
- local returnTripFuel = math.abs(depth) + math.abs(width) + math.abs(height)
- local numBlocks = math.abs(depth) * math.abs(width) * math.abs(height)
- print(numBlocks .. "x blocks, guessing " .. numBlocks / 32 .. " stacks")
- local requiredFuel = math.ceil((numBlocks + returnTripFuel) * 1.2)
- refuel(requiredFuel)
- local position = Vector.create(0, 0, 0)
- local facing = Cardinal.north
- changeState({facing = facing, position = position})
- local worldX = 0
- local worldY = 0
- local worldZ = 0
- if width < 0 then
- worldX = position.x + width + 1
- width = math.abs(width)
- end
- if height < 0 then
- worldY = position.y + height + 1
- height = math.abs(height)
- end
- if depth < 0 then
- worldZ = position.z + depth + 1
- depth = math.abs(depth)
- end
- local world = World.create(worldX, worldY, worldZ, width, height, depth)
- local hasShulkers = false
- for slot = 1, 16 do
- local stack = turtle.getItemDetail(slot)
- if stack and stack.name:match("shulker") then
- hasShulkers = true
- break
- end
- end
- return {position = position, facing = facing, world = world, hasShulkers = hasShulkers, ignore = ignore}
- end
- end)
- __bundle_register("geo.world", function(require, _LOADED, __bundle_register, __bundle_modules)
- local Vector = require("elements.vector")
- ---@class World
- ---@field width? integer
- ---@field height? integer
- ---@field depth? integer
- ---@field blocked table<string, unknown>
- ---@field x integer
- ---@field y integer
- ---@field z integer
- local World = {}
- local function swap(a, b)
- return b, a
- end
- ---@param value integer
- ---@param from integer
- ---@param length? integer
- ---@return boolean
- local function isInRange(value, from, length)
- if length == nil then
- return true
- elseif length == 0 then
- return false
- end
- local to = from + length
- if to < from then
- from, to = swap(from, to)
- end
- return value >= from and value < to
- end
- ---@param x integer
- ---@param y integer
- ---@param z integer
- ---@param width? integer
- ---@param height? integer
- ---@param depth? integer
- ---@return World
- local function create(x, y, z, width, height, depth)
- if (width ~= nil and width < 1) or (height ~= nil and height < 1) or (depth ~= nil and depth < 1) then
- error("can't create world with width/height/depth less than 1")
- end
- ---@type World
- local world = {x = x, y = y, z = z, width = width, height = height, depth = depth, blocked = {}}
- return world
- end
- ---@param world World
- ---@param x integer
- local function isInBoundsX(world, x)
- return isInRange(x, world.x, world.width)
- end
- ---@param world World
- ---@param y integer
- local function isInBoundsY(world, y)
- return isInRange(y, world.y, world.height)
- end
- ---@param world World
- ---@param z integer
- local function isInBoundsZ(world, z)
- return isInRange(z, world.z, world.depth)
- end
- ---@param world World
- ---@param point Vector
- local function isInBounds(world, point)
- return isInBoundsX(world, point.x) and isInBoundsY(world, point.y) and isInBoundsZ(world, point.z)
- end
- ---@param world World
- ---@param point Vector
- local function isInBottomPlane(world, point)
- return point.y == world.y
- end
- ---@param world World
- ---@param point Vector
- local function isInTopPlane(world, point)
- return point.y == world.y + world.height - 1
- end
- ---@param world World
- ---@param point Vector
- ---@return boolean
- local function isBlocked(world, point)
- if not isInBounds(world, point) then
- return false
- else
- return world.blocked[tostring(point)] ~= nil
- end
- end
- ---@param world World
- ---@param point Vector
- local function setBlock(world, point)
- world.blocked[tostring(point)] = true
- end
- ---@param world World
- ---@param point Vector
- local function clearBlock(world, point)
- world.blocked[tostring(point)] = nil
- end
- ---@param world World
- local function getCorners(world)
- return {
- Vector.create(world.x, world.y, world.z),
- Vector.create(world.x + world.width - 1, world.y, world.z),
- Vector.create(world.x, world.y + world.height - 1, world.z),
- Vector.create(world.x + world.width - 1, world.y + world.height - 1, world.z),
- --
- Vector.create(world.x, world.y, world.z + world.depth - 1),
- Vector.create(world.x + world.width - 1, world.y, world.z + world.depth - 1),
- Vector.create(world.x, world.y + world.height - 1, world.z + world.depth - 1),
- Vector.create(world.x + world.width - 1, world.y + world.height - 1, world.z + world.depth - 1)
- }
- end
- ---@param world World
- ---@param point Vector
- local function getClosestCorner(world, point)
- local corners = getCorners(world)
- ---@type Vector
- local best
- for i = 1, #corners do
- if best == nil or Vector.distance(best, point) > Vector.distance(corners[i], point) then
- best = corners[i]
- end
- end
- return best
- end
- return {
- create = create,
- isInBoundsX = isInBoundsX,
- isInBoundsY = isInBoundsY,
- isInBoundsZ = isInBoundsZ,
- isInBounds = isInBounds,
- isInBottomPlane = isInBottomPlane,
- isInTopPlane = isInTopPlane,
- isBlocked = isBlocked,
- setBlock = setBlock,
- clearBlock = clearBlock,
- getCorners = getCorners,
- getClosestCorner = getClosestCorner
- }
- end)
- __bundle_register("dig.next-point", function(require, _LOADED, __bundle_register, __bundle_modules)
- local Vector = require("elements.vector")
- local World = require("geo.world")
- ---@param world World
- ---@param start Vector
- local function isStartInBottomPlane(world, start)
- if World.isInBottomPlane(world, start) then
- return true
- elseif World.isInTopPlane(world, start) then
- return false
- else
- error("start must be in either world bottom or top plane")
- end
- end
- ---@param world World
- ---@param layerHeight integer
- ---@return integer
- function getNumLayers(world, layerHeight)
- return math.ceil(world.height / layerHeight)
- end
- ---@param point Vector
- ---@param world World
- ---@param start Vector
- ---@param layerHeight integer
- ---@return integer
- function getCurrentLayer(point, world, start, layerHeight)
- if layerHeight >= world.height then
- return 1
- end
- if isStartInBottomPlane(world, start) then
- return math.floor((point.y - world.y) / layerHeight) + 1
- else
- return math.floor(((world.y + world.height - 1) - point.y) / layerHeight) + 1
- end
- end
- ---@param layer integer
- ---@param world World
- ---@param start Vector
- ---@param layerHeight integer
- ---@return integer
- function getLayerTargetY(layer, world, start, layerHeight)
- if isStartInBottomPlane(world, start) then
- local targetY = math.floor(layerHeight / 2) + ((layer - 1) * layerHeight) + world.y
- if not World.isInBoundsY(world, targetY) then
- return world.y + world.height - 1
- else
- return targetY
- end
- else
- local targetY = (world.y + world.height - 1) - math.floor(layerHeight / 2) + ((layer - 1) * layerHeight)
- if not World.isInBoundsY(world, targetY) then
- return world.y
- else
- return targetY
- end
- end
- end
- ---@param point Vector
- ---@param world World
- ---@param start Vector
- ---@param layerHeight? integer
- ---@return Vector?
- return function(point, world, start, layerHeight)
- layerHeight = layerHeight or 3
- -- first we try to move to the correct y position, based on which layer we are in
- local currentLayer = getCurrentLayer(point, world, start, layerHeight)
- local targetY = getLayerTargetY(currentLayer, world, start, layerHeight)
- if point.y ~= targetY then
- if isStartInBottomPlane(world, start) then
- return Vector.plus(point, Vector.create(0, 1, 0))
- else
- return Vector.plus(point, Vector.create(0, -1, 0))
- end
- end
- -- then we snake through the plane for digging
- local delta = Vector.create(0, 0, 0)
- if start.x == world.x then
- delta.x = 1
- elseif start.x == world.x + world.width - 1 then
- delta.x = -1
- end
- if start.z == world.z then
- delta.z = 1
- elseif start.z == world.z + world.depth - 1 then
- delta.z = -1
- end
- if world.width > world.depth then
- local relative = Vector.minus(point, start)
- if relative.z % 2 == 1 then
- delta.x = delta.x * -1
- end
- if (currentLayer - 1) % 2 == 1 then
- delta.x = delta.x * -1
- delta.z = delta.z * -1
- end
- if World.isInBoundsX(world, point.x + delta.x) then
- return Vector.plus(point, Vector.create(delta.x, 0, 0))
- elseif World.isInBoundsZ(world, point.z + delta.z) then
- return Vector.plus(point, Vector.create(0, 0, delta.z))
- end
- else
- local relative = Vector.minus(point, start)
- if relative.x % 2 == 1 then
- delta.z = delta.z * -1
- end
- if (currentLayer - 1) % 2 == 1 then
- delta.x = delta.x * -1
- delta.z = delta.z * -1
- end
- if World.isInBoundsZ(world, point.z + delta.z) then
- return Vector.plus(point, Vector.create(0, 0, delta.z))
- elseif World.isInBoundsX(world, point.x + delta.x) then
- return Vector.plus(point, Vector.create(delta.x, 0, 0))
- end
- end
- -- after that, advance to the next layer, or stop if we are at the max layer
- if currentLayer == getNumLayers(world, layerHeight) then
- return nil
- elseif isStartInBottomPlane(world, start) then
- if World.isInBoundsY(world, point.y + 1) then
- return Vector.plus(point, Vector.create(0, 1, 0))
- end
- else
- if World.isInBoundsY(world, point.y - 1) then
- return Vector.plus(point, Vector.create(0, -1, 0))
- end
- end
- end
- end)
- __bundle_register("squirtle.face", function(require, _LOADED, __bundle_register, __bundle_modules)
- local getState = require("squirtle.get-state")
- local turn = require("squirtle.turn")
- ---@param target integer
- ---@param current? integer
- return function(target, current)
- local state = getState()
- current = current or state.facing
- if not current then
- error("facing not available")
- end
- if (current + 2) % 4 == target then
- turn("back")
- elseif (current + 1) % 4 == target then
- turn("right")
- elseif (current - 1) % 4 == target then
- turn("left")
- end
- return target
- end
- end)
- __bundle_register("squirtle.navigate", function(require, _LOADED, __bundle_register, __bundle_modules)
- local Cardinal = require("elements.cardinal")
- local Vector = require("elements.vector")
- local findPath = require("geo.find-path")
- local locate = require("squirtle.locate")
- local orientate = require("squirtle.orientate")
- local World = require("geo.world")
- local inspect = require("squirtle.inspect")
- local dig = require("squirtle.dig")
- local refuel = require("squirtle.refuel")
- local moveToPoint = require("squirtle.move.to-point")
- ---@param path Vector[]
- ---@return boolean, string?, integer?
- local function walkPath(path)
- for i, next in ipairs(path) do
- local success, failedSide = moveToPoint(next)
- if not success then
- return false, failedSide, i
- end
- end
- return true
- end
- ---@param to Vector
- ---@param world? World
- ---@param breakable? function
- return function(to, world, breakable)
- -- [todo] remove breakable() in favor of options;
- -- also we could pass them along to walkPath(),
- -- which then could progress further before failing
- -- because it could now dig stuff
- breakable = breakable or function(...)
- return false
- end
- if not world then
- local position = locate()
- world = World.create(position.x, position.y, position.z)
- end
- local from, facing = orientate()
- while true do
- local path, msg = findPath(from, to, facing, world)
- if not path then
- return false, msg
- end
- local distance = Vector.manhattan(from, to)
- refuel(distance)
- local success, failedSide = walkPath(path)
- if success then
- return true
- elseif failedSide then
- from, facing = orientate()
- local block = inspect(failedSide)
- local scannedLocation = Vector.plus(from, Cardinal.toVector(Cardinal.fromSide(failedSide, facing)))
- if block and breakable(block) then
- dig(failedSide)
- elseif block then
- World.setBlock(world, scannedLocation)
- else
- error("could not move, not sure why")
- end
- end
- end
- end
- end)
- __bundle_register("squirtle.move.to-point", function(require, _LOADED, __bundle_register, __bundle_modules)
- local Vector = require("elements.vector")
- local Cardinal = require("elements.cardinal")
- local locate = require("squirtle.locate")
- local move = require("squirtle.move")
- local face = require("squirtle.face")
- ---@param target Vector
- ---@return boolean, string?
- return function(target)
- local delta = Vector.minus(target, locate())
- if delta.y > 0 then
- if not move("top", delta.y) then
- return false, "top"
- end
- elseif delta.y < 0 then
- if not move("bottom", -delta.y) then
- return false, "bottom"
- end
- end
- if delta.x > 0 then
- face(Cardinal.east)
- if not move("front", delta.x) then
- return false, "front"
- end
- elseif delta.x < 0 then
- face(Cardinal.west)
- if not move("front", -delta.x) then
- return false, "front"
- end
- end
- if delta.z > 0 then
- face(Cardinal.south)
- if not move("front", delta.z) then
- return false, "front"
- end
- elseif delta.z < 0 then
- face(Cardinal.north)
- if not move("front", -delta.z) then
- return false, "front"
- end
- end
- return true
- end
- end)
- __bundle_register("squirtle.move", function(require, _LOADED, __bundle_register, __bundle_modules)
- local Vector = require("elements.vector")
- local Cardinal = require("elements.cardinal")
- local getState = require("squirtle.get-state")
- local changeState = require("squirtle.change-state")
- local Fuel = require("squirtle.fuel")
- local refuel = require("squirtle.refuel")
- local dig = require("squirtle.dig")
- local turn = require("squirtle.turn")
- local natives = {
- top = turtle.up,
- up = turtle.up,
- front = turtle.forward,
- forward = turtle.forward,
- bottom = turtle.down,
- down = turtle.down,
- back = turtle.back
- }
- -- [todo] move() should not call refuel like this. instead, a refueler interface should be provided.
- -- if no fuel could be acquired via the refueler, and there is not enough fuel, error out.
- ---@param side? string
- ---@param times? integer
- ---@param breakBlocks? boolean
- ---@return boolean, string?, integer?
- return function(side, times, breakBlocks)
- side = side or "front"
- local handler = natives[side]
- if not handler then
- error(string.format("move() does not support side %s", side))
- end
- times = times or 1
- if not Fuel.hasFuel(times) then
- refuel(times)
- end
- local state = getState()
- local delta
- if state.position and state.facing then
- position = state.position
- delta = Cardinal.toVector(Cardinal.fromSide(side, state.facing))
- end
- for step = 1, times do
- local success, message = handler()
- if not success then
- if breakBlocks then
- if side == "back" then
- turn("back")
- success = dig()
- turn("back")
- else
- success = dig(side)
- end
- if not success then
- return false, "failed to dig", step
- else
- -- todo: make sure moving again was actually successful
- handler()
- end
- else
- return false, message, step
- end
- end
- if delta then
- position = Vector.plus(position, delta)
- changeState({position = position})
- end
- end
- return true
- end
- end)
- __bundle_register("squirtle.locate", function(require, _LOADED, __bundle_register, __bundle_modules)
- local Vector = require("elements.vector")
- local getState = require("squirtle.get-state")
- local changeState = require("squirtle.change-state")
- ---@param refresh? boolean
- return function(refresh)
- local state = getState()
- local position = state.position
- if refresh or not position then
- local x, y, z = gps.locate()
- if not x then
- error("no gps available")
- end
- position = Vector.create(x, y, z)
- changeState({position = position})
- end
- return position
- end
- end)
- __bundle_register("squirtle.inspect", function(require, _LOADED, __bundle_register, __bundle_modules)
- local indexOf = require("utils.index-of")
- local natives = {
- top = turtle.inspectUp,
- up = turtle.inspectUp,
- front = turtle.inspect,
- forward = turtle.inspect,
- bottom = turtle.inspectDown,
- down = turtle.inspectDown
- }
- ---@param side? string
- ---@param name? table|string
- ---@return Block? block
- return function(side, name)
- side = side or "front"
- local handler = natives[side]
- if not handler then
- error(string.format("inspect() does not support side %s", side))
- end
- local success, block = handler()
- if success then
- if name then
- if type(name) == "string" and block.name == name then
- return block
- elseif type(name) == "table" and indexOf(name, block.name) > 0 then
- return block
- else
- return nil
- end
- end
- return block
- else
- return nil
- end
- end
- end)
- __bundle_register("squirtle.orientate", function(require, _LOADED, __bundle_register, __bundle_modules)
- local Vector = require("elements.vector")
- local Side = require("elements.side")
- local Cardinal = require("elements.cardinal")
- local getState = require("squirtle.get-state")
- local changeState = require("squirtle.change-state")
- local locate = require("squirtle.locate")
- local refuel = require("squirtle.refuel")
- local move = require("squirtle.move")
- local turn = require("squirtle.turn")
- ---@param side string
- ---@param position Vector
- local function stepOut(side, position)
- refuel(2)
- if not move(side) then
- return false
- end
- local now = locate(true)
- local cardinal = Cardinal.fromVector(Vector.minus(now, position))
- local facing = Cardinal.rotate(cardinal, side)
- changeState({facing = facing})
- while not move(Side.rotateAround(side)) do
- print("can't move back, something is blocking me. sleeping 1s...")
- os.sleep(1)
- end
- return true
- end
- ---@param position Vector
- local function orientateSameLayer(position)
- -- [todo] note to self: i purposely turn first cause it looks nicer. especially
- -- when it moves back, then forward again. looks like a little dance.
- -- what i wrote this note actually for: allow for different styles and randomization
- -- [todo#2] well, good i wrote this note. because i was surprised that the turtle
- -- doesn't first try simply moving forward, and i was wondering why.
- turn("right")
- local success = stepOut("back", position) or stepOut("front", position)
- turn("left")
- if not success then
- success = stepOut("back", position) or stepOut("front", position)
- end
- return success
- end
- ---@param refresh? boolean
- ---@return Vector position, integer facing
- return function(refresh)
- local state = getState()
- local position = locate(refresh)
- local facing = state.facing
- if refresh or not facing then
- if not orientateSameLayer(position) then
- error("failed to orientate. possibly blocked in.")
- end
- end
- return locate(), getState().facing
- end
- end)
- __bundle_register("geo.find-path", function(require, _LOADED, __bundle_register, __bundle_modules)
- local World = require("geo.world")
- local Cardinal = require("elements.cardinal")
- local Vector = require("elements.vector")
- ---@param hierarchy Vector[]
- ---@param start Vector
- ---@param goal Vector
- local function toPath(hierarchy, start, goal)
- local path = {}
- local next = goal
- while not Vector.equals(next, start) do
- table.insert(path, 1, next)
- next = hierarchy[Vector.toString(next)]
- end
- return path
- end
- ---@param open Vector[]
- ---@param naturals Vector[]
- ---@param forced Vector[]
- ---@param pruned Vector[]
- ---@param totalCost number[]
- local function findBest(open, naturals, forced, pruned, totalCost)
- local lowestF = nil
- local bestType = nil
- ---@type Vector
- local best = nil
- for key, value in pairs(open) do
- local type = 3
- if naturals[key] then
- type = 0
- elseif forced[key] then
- type = 1
- elseif pruned[key] then
- type = 2
- end
- if lowestF == nil or totalCost[key] < lowestF or (totalCost[key] <= lowestF and type < bestType) then
- best = value
- lowestF = totalCost[key]
- bestType = type
- end
- end
- return best
- end
- ---@param start Vector
- ---@param goal Vector
- ---@param orientation integer
- ---@param world? World
- return function(start, goal, orientation, world)
- if not world then
- world = World.create(start.x, start.y, start.z)
- end
- if World.isBlocked(world, goal) then
- return false, "goal is blocked"
- end
- ---@type Vector[]
- local open = {}
- local numOpen = 0
- local closed = {}
- ---@type Vector[]
- local reverseMap = {}
- local pastCost = {}
- local futureCost = {}
- local totalCost = {}
- local startKey = Vector.toString(start)
- local naturals = {}
- local forced = {}
- local pruned = {}
- open[startKey] = start
- pastCost[startKey] = 0
- futureCost[startKey] = Vector.manhattan(start, goal)
- totalCost[startKey] = pastCost[startKey] + futureCost[startKey]
- numOpen = numOpen + 1
- local cycles = 0
- local timeStarted = os.clock()
- local timeout = 3
- while (numOpen > 0) do
- cycles = cycles + 1
- if cycles % 100 == 0 then
- if os.clock() - timeStarted >= timeout then
- return false, timeout .. "s timeout reached"
- end
- -- [todo] make event name more unique to prevent collisions?
- os.queueEvent("a-star-progress")
- os.pullEvent("a-star-progress")
- end
- local current = findBest(open, naturals, forced, pruned, totalCost)
- if Vector.equals(current, goal) then
- return toPath(reverseMap, start, goal)
- end
- local currentKey = Vector.toString(current)
- open[currentKey] = nil
- closed[currentKey] = current
- numOpen = numOpen - 1
- if reverseMap[currentKey] then
- local delta = Vector.minus(current, reverseMap[currentKey])
- orientation = Cardinal.fromVector(delta)
- end
- local neighbours = {}
- for i = 0, 5 do
- local neighbour = Vector.plus(current, Cardinal.toVector(i))
- local neighbourKey = Vector.toString(neighbour)
- local requiresTurn = false
- if i ~= orientation and not Cardinal.isVertical(i) then
- requiresTurn = true
- end
- if closed[neighbourKey] == nil and World.isInBounds(world, neighbour) and
- not World.isBlocked(world, neighbour) then
- local tentativePastCost = pastCost[currentKey] + 1
- if (requiresTurn) then
- tentativePastCost = tentativePastCost + 1
- end
- if open[neighbourKey] == nil or tentativePastCost < pastCost[neighbourKey] then
- pastCost[neighbourKey] = tentativePastCost
- local neighbourFutureCost = Vector.manhattan(neighbour, goal)
- if (neighbour.x ~= goal.x) then
- neighbourFutureCost = neighbourFutureCost + 1
- end
- if (neighbour.z ~= goal.z) then
- neighbourFutureCost = neighbourFutureCost + 1
- end
- if (neighbour.y ~= goal.y) then
- neighbourFutureCost = neighbourFutureCost + 1
- end
- futureCost[neighbourKey] = neighbourFutureCost
- totalCost[neighbourKey] = pastCost[neighbourKey] + neighbourFutureCost
- reverseMap[neighbourKey] = current
- if (open[neighbourKey] == nil) then
- open[neighbourKey] = neighbour
- neighbours[i] = neighbour
- numOpen = numOpen + 1
- end
- end
- end
- end
- -- pruning
- if (reverseMap[currentKey] ~= nil) then
- for neighbourOrientation, neighbour in pairs(neighbours) do
- local neighbourKey = Vector.toString(neighbour)
- if (neighbourOrientation == orientation) then
- -- add natural neighbour
- naturals[neighbourKey] = neighbour
- else
- -- check blockade
- local check = Vector.plus(reverseMap[currentKey], Vector.minus(
- Cardinal.toVector(neighbourOrientation),
- Cardinal.toVector(orientation)))
- -- if (world[checkKey] == nil) then
- if not World.isBlocked(world, check) then
- -- add neighbour to prune
- pruned[neighbourKey] = neighbour
- else
- -- add neighbour to forced
- forced[neighbourKey] = neighbour
- end
- end
- end
- end
- end
- return false, "no path available"
- end
- end)
- return __bundle_require("__root")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement