Advertisement
Guest User

Untitled

a guest
Aug 29th, 2023
538
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 74.66 KB | Gaming | 0 0
  1. -- Bundled by luabundle {"version":"1.6.0"}
  2. local __bundle_require, __bundle_loaded, __bundle_register, __bundle_modules = (function(superRequire)
  3.     local loadingPlaceholder = {[{}] = true}
  4.  
  5.     local register
  6.     local modules = {}
  7.  
  8.     local require
  9.     local loaded = {}
  10.  
  11.     register = function(name, body)
  12.         if not modules[name] then
  13.             modules[name] = body
  14.         end
  15.     end
  16.  
  17.     require = function(name)
  18.         local loadedModule = loaded[name]
  19.  
  20.         if loadedModule then
  21.             if loadedModule == loadingPlaceholder then
  22.                 return nil
  23.             end
  24.         else
  25.             if not modules[name] then
  26.                 if not superRequire then
  27.                     local identifier = type(name) == 'string' and '\"' .. name .. '\"' or tostring(name)
  28.                     error('Tried to require ' .. identifier .. ', but no such module has been registered')
  29.                 else
  30.                     return superRequire(name)
  31.                 end
  32.             end
  33.  
  34.             loaded[name] = loadingPlaceholder
  35.             loadedModule = modules[name](require, loaded, register, modules)
  36.             loaded[name] = loadedModule
  37.         end
  38.  
  39.         return loadedModule
  40.     end
  41.  
  42.     return require, loaded, register, modules
  43. end)(require)
  44. __bundle_register("__root", function(require, _LOADED, __bundle_register, __bundle_modules)
  45. package.path = package.path .. ";/lib/?.lua"
  46. package.path = package.path .. ";/app/turtle/?.lua"
  47.  
  48. local Utils = require("utils")
  49. local World = require("geo.world")
  50. local navigate = require("squirtle.navigate")
  51. local face = require("squirtle.face")
  52. local nextPoint = require("dig.next-point")
  53. local boot = require("dig.boot")
  54. local drop = require("squirtle.drop")
  55. local SquirtleV2 = require("squirtle.squirtle-v2")
  56.  
  57. ---@class DigAppState
  58. ---@field world World
  59. ---@field position Vector
  60. ---@field facing integer
  61. ---@field hasShulkers boolean
  62. ---@field ignore table<string>
  63.  
  64. local function isGettingFull()
  65.     return turtle.getItemCount(16) > 0
  66. end
  67.  
  68. ---@param world World
  69. ---@param position Vector
  70. local function digUpDownIfInBounds(world, position)
  71.     if World.isInBoundsY(world, position.y + 1) then
  72.         SquirtleV2.tryDig("up")
  73.     end
  74.  
  75.     if World.isInBoundsY(world, position.y - 1) then
  76.         SquirtleV2.tryDig("down")
  77.     end
  78. end
  79.  
  80. ---@return string? direction
  81. local function placeAnywhere()
  82.     if turtle.place() then
  83.         return "front"
  84.     end
  85.  
  86.     if turtle.placeUp() then
  87.         return "up"
  88.     end
  89.  
  90.     if turtle.placeDown() then
  91.         return "down"
  92.     end
  93. end
  94.  
  95. ---@param direction string
  96. ---@return boolean unloadedAll
  97. local function loadIntoShulker(direction)
  98.     local unloadedAll = true
  99.  
  100.     for slot = 1, 16 do
  101.         local stack = turtle.getItemDetail(slot)
  102.  
  103.         if stack and not stack.name:match("shulker") then
  104.             turtle.select(slot)
  105.             if not drop(direction) then
  106.                 unloadedAll = false
  107.             end
  108.         end
  109.     end
  110.  
  111.     return unloadedAll
  112. end
  113.  
  114. ---@return boolean unloadedAll
  115. local function tryLoadShulkers()
  116.     ---@type string?
  117.     local placedSide = nil
  118.  
  119.     for slot = 1, 16 do
  120.         local stack = turtle.getItemDetail(slot)
  121.  
  122.         if stack and stack.name:match("shulker") then
  123.             turtle.select(slot)
  124.             placedSide = placeAnywhere()
  125.  
  126.             if not placedSide then
  127.                 print("failed to place shulker, no space :(")
  128.                 -- [todo] bit of an issue returning false here - shulkers might have enough space for items,
  129.                 -- yet we effectively return "shulkers are full" just because we couldn't place it
  130.                 -- however, this should only be an issue when digging a 1-high layer
  131.                 return false
  132.             else
  133.                 local unloadedAll = loadIntoShulker(placedSide)
  134.                 turtle.select(slot)
  135.                 SquirtleV2.dig(placedSide)
  136.  
  137.                 if unloadedAll then
  138.                     return true
  139.                 end
  140.             end
  141.         end
  142.     end
  143.  
  144.     return false
  145. end
  146. ---@param args table<string>
  147. ---@return boolean
  148. local function main(args)
  149.     print("[dig v3.0.0] booting...")
  150.     local state = boot(args)
  151.  
  152.     if not state then
  153.         return false
  154.     end
  155.  
  156.     print(string.format("[area] %dx%dx%d", state.world.depth, state.world.width, state.world.height))
  157.  
  158.     if #state.ignore > 0 then
  159.         print("[ignore] " .. table.concat(state.ignore, ", "))
  160.     end
  161.  
  162.     ---@type Vector|nil
  163.     local point = state.position
  164.     local start = state.position
  165.     local world = state.world
  166.     local facing = state.facing
  167.     local shulkersFull = false
  168.     turtle.select(1)
  169.  
  170.     ---@param block Block
  171.     ---@return boolean
  172.     local isBreakable = function(block)
  173.         if #state.ignore == 0 then
  174.             return true
  175.         end
  176.  
  177.         return not Utils.find(state.ignore, function(item)
  178.             return string.match(block.name, item)
  179.         end)
  180.     end
  181.  
  182.     local restoreBreakable = SquirtleV2.setBreakable(isBreakable)
  183.  
  184.     while point do
  185.         if navigate(point, world, isBreakable) then
  186.             digUpDownIfInBounds(world, point)
  187.  
  188.             if state.hasShulkers and not shulkersFull and isGettingFull() then
  189.                 shulkersFull = not tryLoadShulkers()
  190.                 turtle.select(1)
  191.             end
  192.         end
  193.  
  194.         point = nextPoint(point, world, start)
  195.     end
  196.  
  197.     if state.hasShulkers and not shulkersFull then
  198.         tryLoadShulkers()
  199.     end
  200.  
  201.     restoreBreakable()
  202.     print("[done] going home!")
  203.     navigate(start, world, isBreakable)
  204.     face(facing)
  205.  
  206.     return true
  207. end
  208.  
  209. return main(arg)
  210.  
  211. end)
  212. __bundle_register("squirtle.squirtle-v2", function(require, _LOADED, __bundle_register, __bundle_modules)
  213. local selectItem = require("squirtle.backpack.select-item")
  214. local turn = require("squirtle.turn")
  215. local place = require("squirtle.place")
  216. local Vector = require("elements.vector")
  217. local Cardinal = require("elements.cardinal")
  218. local Fuel = require("squirtle.fuel")
  219. local refuel = require("squirtle.refuel")
  220. local requireItems = require("squirtle.require-items")
  221. local Utils = require("utils")
  222.  
  223. ---@class SquirtleV2SimulationResults
  224. ---@field steps integer
  225. ---@field placed table<string, integer>
  226.  
  227. local natives = {
  228.     move = {
  229.         top = turtle.up,
  230.         up = turtle.up,
  231.         front = turtle.forward,
  232.         forward = turtle.forward,
  233.         bottom = turtle.down,
  234.         down = turtle.down,
  235.         back = turtle.back
  236.     },
  237.     dig = {
  238.         top = turtle.digUp,
  239.         up = turtle.digUp,
  240.         front = turtle.dig,
  241.         forward = turtle.dig,
  242.         bottom = turtle.digDown,
  243.         down = turtle.digDown
  244.     },
  245.     inspect = {
  246.         top = turtle.inspectUp,
  247.         up = turtle.inspectUp,
  248.         front = turtle.inspect,
  249.         forward = turtle.inspect,
  250.         bottom = turtle.inspectDown,
  251.         down = turtle.inspectDown
  252.     }
  253. }
  254.  
  255. ---@param block Block
  256. ---@return boolean
  257. local breakableSafeguard = function(block)
  258.     return block.name ~= "minecraft:bedrock"
  259. end
  260.  
  261. ---@class SquirtleV2
  262. ---@field results SquirtleV2SimulationResults
  263. ---@field breakable? fun(block: Block) : boolean
  264. local SquirtleV2 = {
  265.     flipTurns = false,
  266.     simulate = false,
  267.     results = {placed = {}, steps = 0},
  268.     position = Vector.create(0, 0, 0),
  269.     facing = Cardinal.south
  270. }
  271.  
  272. ---@param block Block
  273. ---@return boolean
  274. local function canBreak(block)
  275.     return SquirtleV2.breakable ~= nil and breakableSafeguard(block) and SquirtleV2.breakable(block)
  276. end
  277.  
  278. ---@param predicate? fun(block: Block) : boolean
  279. ---@return fun() : nil
  280. function SquirtleV2.setBreakable(predicate)
  281.     local current = SquirtleV2.breakable
  282.  
  283.     local function restore()
  284.         SquirtleV2.breakable = current
  285.     end
  286.  
  287.     SquirtleV2.breakable = predicate
  288.  
  289.     return restore
  290. end
  291.  
  292. ---@param side? string
  293. ---@param steps? integer
  294. ---@return boolean, integer, string?
  295. function SquirtleV2.tryMove(side, steps)
  296.     side = side or "front"
  297.     local native = natives.move[side]
  298.  
  299.     if not native then
  300.         error(string.format("move() does not support side %s", side))
  301.     end
  302.  
  303.     if SquirtleV2.simulate then
  304.         if steps then
  305.             SquirtleV2.results.steps = SquirtleV2.results.steps + 1
  306.         else
  307.             -- "tryMove()" doesn't simulate any steps because it is assumed that it is called only to move until an unbreakable block is hit,
  308.             -- and since we're not simulating a world we can not really return a meaningful value of steps taken if none have been supplied.
  309.             return false, 0, "simulation mode is active"
  310.         end
  311.     end
  312.  
  313.     steps = steps or 1
  314.  
  315.     if not Fuel.hasFuel(steps) then
  316.         refuel(steps)
  317.     end
  318.  
  319.     local delta = Cardinal.toVector(Cardinal.fromSide(side, SquirtleV2.facing))
  320.  
  321.     for step = 1, steps do
  322.         repeat
  323.             local success, error = native()
  324.  
  325.             if not success then
  326.                 local actionSide = side
  327.  
  328.                 if side == "back" then
  329.                     actionSide = "front"
  330.                     SquirtleV2.around()
  331.                 end
  332.  
  333.                 local block = SquirtleV2.inspect(actionSide)
  334.  
  335.                 if not block then
  336.                     if side == "back" then
  337.                         SquirtleV2.around()
  338.                     end
  339.  
  340.                     -- [todo] it is possible (albeit unlikely) that between handler() and inspect(), a previously
  341.                     -- existing block has been removed by someone else
  342.                     error(string.format("move(%s) failed, but there is no block in the way", side))
  343.                 end
  344.  
  345.                 -- [todo] wanted to reuse newly introduced Squirtle.tryDig(), but it would be awkward to do so.
  346.                 -- maybe I find a non-awkward solution in the future?
  347.                 -- [todo] should tryDig really try to dig? I think I am going to use this only for "move until you hit something",
  348.                 -- so in that case, no, it shouldn't try to dig.
  349.                 if canBreak(block) then
  350.                     while SquirtleV2.dig(actionSide) do
  351.                     end
  352.  
  353.                     if side == "back" then
  354.                         SquirtleV2.around()
  355.                     end
  356.                 else
  357.                     if side == "back" then
  358.                         SquirtleV2.around()
  359.                     end
  360.  
  361.                     return false, step - 1, string.format("blocked by %s", block.name)
  362.                 end
  363.             end
  364.         until success
  365.  
  366.         SquirtleV2.position = Vector.plus(SquirtleV2.position, delta)
  367.     end
  368.  
  369.     return true, steps
  370. end
  371.  
  372. ---@param times? integer
  373. ---@return boolean, integer, string?
  374. function SquirtleV2.tryForward(times)
  375.     return SquirtleV2.tryMove("forward", times)
  376. end
  377.  
  378. ---@param times? integer
  379. ---@return boolean, integer, string?
  380. function SquirtleV2.tryUp(times)
  381.     return SquirtleV2.tryMove("up", times)
  382. end
  383.  
  384. ---@param times? integer
  385. ---@return boolean, integer, string?
  386. function SquirtleV2.tryDown(times)
  387.     return SquirtleV2.tryMove("down", times)
  388. end
  389.  
  390. ---@param times? integer
  391. ---@return boolean, integer, string?
  392. function SquirtleV2.tryBack(times)
  393.     return SquirtleV2.tryMove("back", times)
  394. end
  395.  
  396. ---@param side? string
  397. ---@param times? integer
  398. function SquirtleV2.move(side, times)
  399.     if SquirtleV2.simulate then
  400.         -- when simulating, only "move()" will simulate actual steps.
  401.         times = times or 1
  402.         SquirtleV2.results.steps = SquirtleV2.results.steps + 1
  403.  
  404.         return nil
  405.     end
  406.  
  407.     local success, _, message = SquirtleV2.tryMove(side, times)
  408.  
  409.     if not success then
  410.         error(string.format("move(%s) failed: %s", side, message))
  411.     end
  412. end
  413.  
  414. ---@param times? integer
  415. function SquirtleV2.forward(times)
  416.     SquirtleV2.move("forward", times)
  417. end
  418.  
  419. ---@param times? integer
  420. function SquirtleV2.up(times)
  421.     SquirtleV2.move("up", times)
  422. end
  423.  
  424. ---@param times? integer
  425. function SquirtleV2.down(times)
  426.     SquirtleV2.move("down", times)
  427. end
  428.  
  429. ---@param times? integer
  430. function SquirtleV2.back(times)
  431.     SquirtleV2.move("back", times)
  432. end
  433.  
  434. ---@param side? string
  435. function SquirtleV2.turn(side)
  436.     if not SquirtleV2.simulate then
  437.         if SquirtleV2.flipTurns then
  438.             if side == "left" then
  439.                 side = "right"
  440.             elseif side == "right" then
  441.                 side = "left"
  442.             end
  443.         end
  444.  
  445.         turn(side)
  446.     end
  447. end
  448.  
  449. function SquirtleV2.left()
  450.     SquirtleV2.turn("left")
  451. end
  452.  
  453. function SquirtleV2.right()
  454.     SquirtleV2.turn("right")
  455. end
  456.  
  457. function SquirtleV2.around()
  458.     SquirtleV2.turn("back")
  459. end
  460.  
  461. ---@param side? string
  462. ---@param toolSide? string
  463. ---@return boolean, string?
  464. function SquirtleV2.tryDig(side, toolSide)
  465.     if SquirtleV2.simulate then
  466.         return true
  467.     end
  468.  
  469.     side = side or "front"
  470.     local native = natives.dig[side]
  471.  
  472.     if not native then
  473.         error(string.format("dig() does not support side %s", side))
  474.     end
  475.  
  476.     local block = SquirtleV2.inspect(side)
  477.  
  478.     if not block then
  479.         return false
  480.     end
  481.  
  482.     if not canBreak(block) then
  483.         return false, string.format("not allowed to dig block %s", block.name)
  484.     end
  485.  
  486.     local success, message = native(toolSide)
  487.  
  488.     if not success and string.match(message, "tool") then
  489.         if toolSide then
  490.             error(string.format("dig(%s, %s) failed: %s", side, toolSide, message))
  491.         else
  492.             error(string.format("dig(%s) failed: %s", side, message))
  493.         end
  494.     end
  495.  
  496.     return success, message
  497. end
  498.  
  499. ---@param side? string
  500. ---@param toolSide? string
  501. ---@return boolean, string?
  502. function SquirtleV2.dig(side, toolSide)
  503.     local success, message = SquirtleV2.tryDig(side, toolSide)
  504.  
  505.     -- if there is no message, then there just wasn't anything to dig, meaning every other case is interpreted as an error
  506.     if not success and message then
  507.         error(message)
  508.     end
  509.  
  510.     return success
  511. end
  512.  
  513. ---@param block string
  514. ---@param side? string
  515. function SquirtleV2.place(block, side)
  516.     if SquirtleV2.simulate then
  517.         if not SquirtleV2.results.placed[block] then
  518.             SquirtleV2.results.placed[block] = 0
  519.         end
  520.  
  521.         SquirtleV2.results.placed[block] = SquirtleV2.results.placed[block] + 1
  522.     else
  523.         while not SquirtleV2.select(block, true) do
  524.             requireItems({[block] = 1})
  525.         end
  526.  
  527.         -- [todo] error handling
  528.         place(side)
  529.     end
  530. end
  531.  
  532. ---@param block string
  533. function SquirtleV2.placeUp(block)
  534.     SquirtleV2.place(block, "up")
  535. end
  536.  
  537. ---@param block string
  538. function SquirtleV2.placeDown(block)
  539.     SquirtleV2.place(block, "down")
  540. end
  541.  
  542. ---@param name string
  543. ---@param exact? boolean
  544. ---@return false|integer
  545. function SquirtleV2.select(name, exact)
  546.     return selectItem(name, exact)
  547. end
  548.  
  549. ---@param side? string
  550. ---@param name? table|string
  551. ---@return Block? block
  552. function SquirtleV2.inspect(side, name)
  553.     side = side or "front"
  554.     local native = natives.inspect[side]
  555.  
  556.     if not native then
  557.         error(string.format("inspect() does not support side %s", side))
  558.     end
  559.  
  560.     local success, block = native()
  561.  
  562.     if success then
  563.         if name then
  564.             if type(name) == "string" and block.name == name then
  565.                 return block
  566.             elseif type(name) == "table" and Utils.indexOf(name, block.name) > 0 then
  567.                 return block
  568.             else
  569.                 return nil
  570.             end
  571.         end
  572.  
  573.         return block
  574.     else
  575.         return nil
  576.     end
  577. end
  578.  
  579. return SquirtleV2
  580.  
  581. end)
  582. __bundle_register("utils", function(require, _LOADED, __bundle_register, __bundle_modules)
  583. local ccPretty = "cc.pretty"
  584. local Pretty = require(ccPretty)
  585. local copy = require("utils.copy")
  586. local indexOf = require("utils.index-of")
  587. local Utils = {copy = copy, indexOf = indexOf}
  588.  
  589. ---@param list table
  590. ---@param values table
  591. ---@return table
  592. function Utils.push(list, values)
  593.     for i = 1, #values do
  594.         list[#list + 1] = values[i]
  595.     end
  596.  
  597.     return list
  598. end
  599.  
  600. ---@param list table
  601. ---@param value unknown
  602. ---@return boolean
  603. function Utils.contains(list, value)
  604.     for i = 1, #list do
  605.         if list[i] == value then
  606.             return true
  607.         end
  608.     end
  609.  
  610.     return false
  611. end
  612.  
  613. -- https://stackoverflow.com/a/26367080/1611592
  614. ---@generic T: table
  615. ---@param tbl T
  616. ---@return T
  617. function Utils.clone(tbl, seen)
  618.     if seen and seen[tbl] then
  619.         return seen[tbl]
  620.     end
  621.  
  622.     local s = seen or {}
  623.     local res = setmetatable({}, getmetatable(tbl))
  624.     s[tbl] = res
  625.  
  626.     for k, v in pairs(tbl) do
  627.         res[Utils.clone(k, s)] = Utils.clone(v, s)
  628.     end
  629.  
  630.     return res
  631. end
  632.  
  633. function Utils.isEmpty(t)
  634.     for _, _ in pairs(t) do
  635.         return false
  636.     end
  637.  
  638.     return true
  639. end
  640.  
  641. ---@generic T, U
  642. ---@param list T[]
  643. ---@param mapper fun(item: T, index: number): U
  644. ---@return U[]
  645. function Utils.map(list, mapper)
  646.     local mapped = {}
  647.  
  648.     for i = 1, #list do
  649.         table.insert(mapped, mapper(list[i], i))
  650.     end
  651.  
  652.     return mapped
  653. end
  654.  
  655. ---@generic T
  656. ---@param list T[]
  657. ---@param property string
  658. ---@return T[]
  659. function Utils.toMap(list, property)
  660.     local map = {}
  661.  
  662.     for _, element in pairs(list) do
  663.         local id = element[property]
  664.  
  665.         if type(id) ~= "string" then
  666.             error("id must be of type string")
  667.         end
  668.  
  669.         map[id] = element
  670.     end
  671.  
  672.     return map
  673. end
  674.  
  675. ---@generic T
  676. ---@param list T[]
  677. ---@param predicate fun(item: T, index: number): boolean
  678. ---@return T[]
  679. function Utils.filter(list, predicate)
  680.     local filtered = {}
  681.  
  682.     for i = 1, #list do
  683.         if predicate(list[i], i) then
  684.             table.insert(filtered, list[i])
  685.         end
  686.     end
  687.  
  688.     return filtered
  689. end
  690.  
  691. ---@generic T
  692. ---@param list T[]
  693. ---@param predicate fun(item: T, index: number): boolean
  694. ---@return T|nil, integer|nil
  695. function Utils.find(list, predicate)
  696.     for i = 1, #list do
  697.         if predicate(list[i], i) then
  698.             return list[i], i
  699.         end
  700.     end
  701. end
  702.  
  703. function Utils.reverse(list)
  704.     for i = 1, #list / 2, 1 do
  705.         list[i], list[#list - i + 1] = list[#list - i + 1], list[i]
  706.     end
  707.  
  708.     return list
  709. end
  710.  
  711. function Utils.prettyPrint(value)
  712.     Pretty.print(Pretty.pretty(value))
  713. end
  714.  
  715. function Utils.count(table)
  716.     local size = 0
  717.  
  718.     for _ in pairs(table) do
  719.         size = size + 1
  720.     end
  721.  
  722.     return size
  723. end
  724.  
  725. ---@generic T
  726. ---@param tbl T[]
  727. ---@return T?
  728. function Utils.first(tbl)
  729.     for _, item in pairs(tbl) do
  730.         return item
  731.     end
  732. end
  733.  
  734. function Utils.waitForUserToHitEnter()
  735.     while true do
  736.         local _, key = os.pullEvent("key")
  737.         if (key == keys.enter) then
  738.             break
  739.         end
  740.     end
  741. end
  742.  
  743. function Utils.writeAutorunFile(args)
  744.     local file = fs.open("startup/" .. args[1] .. ".autorun.lua", "w")
  745.     file.write("shell.run(\"" .. table.concat(args, " ") .. "\")")
  746.     file.close()
  747. end
  748.  
  749. ---@param path string
  750. ---@return table?
  751. function Utils.readJson(path)
  752.     local file = fs.open(path, "r")
  753.  
  754.     if not file then
  755.         return
  756.     end
  757.  
  758.     return textutils.unserializeJSON(file.readAll())
  759. end
  760.  
  761. ---@param path string
  762. ---@param data table
  763. function Utils.writeJson(path, data)
  764.     local file = fs.open(path, "w")
  765.     file.write(textutils.serialiseJSON(data))
  766.     file.close()
  767. end
  768.  
  769. return Utils
  770.  
  771. end)
  772. __bundle_register("utils.index-of", function(require, _LOADED, __bundle_register, __bundle_modules)
  773. ---@param tbl table
  774. ---@param item unknown
  775. ---@return integer
  776. return function(tbl, item)
  777.     for i = 1, #tbl do
  778.         if (tbl[i] == item) then
  779.             return i
  780.         end
  781.     end
  782.  
  783.     return -1
  784. end
  785.  
  786. end)
  787. __bundle_register("utils.copy", function(require, _LOADED, __bundle_register, __bundle_modules)
  788. ---@generic T: table
  789. ---@param tbl T
  790. ---@return T
  791. return function(tbl)
  792.     local copy = {}
  793.  
  794.     for k, v in pairs(tbl) do
  795.         copy[k] = v
  796.     end
  797.  
  798.     return copy
  799. end
  800.  
  801. end)
  802. __bundle_register("squirtle.require-items", function(require, _LOADED, __bundle_register, __bundle_modules)
  803. local Backpack = require("squirtle.backpack")
  804. local Utils = require("utils")
  805.  
  806. ---@param items table<string, integer>
  807. local function getMissing(items)
  808.     ---@type table<string, integer>
  809.     local open = {}
  810.     local stock = Backpack.getStock()
  811.  
  812.     for item, required in pairs(items) do
  813.         local missing = required - (stock[item] or 0)
  814.  
  815.         if missing > 0 then
  816.             open[item] = required - (stock[item] or 0)
  817.         end
  818.     end
  819.  
  820.     return open
  821. end
  822.  
  823. ---@param items table<string, integer>
  824. return function(items)
  825.     while true do
  826.         ---@type table<string, integer>
  827.         local open = getMissing(items)
  828.  
  829.         if Utils.count(open) == 0 then
  830.             term.clear()
  831.             term.setCursorPos(1, 1)
  832.             return nil
  833.         end
  834.  
  835.         term.clear()
  836.         term.setCursorPos(1, 1)
  837.         print("Required Items")
  838.         local width = term.getSize()
  839.         print(string.rep("-", width))
  840.  
  841.         for item, missing in pairs(open) do
  842.             print(string.format("%dx %s", missing, item))
  843.         end
  844.  
  845.         os.pullEvent("turtle_inventory")
  846.     end
  847. end
  848.  
  849. end)
  850. __bundle_register("squirtle.backpack", function(require, _LOADED, __bundle_register, __bundle_modules)
  851. local find = require("squirtle.backpack.find")
  852. local getSize = require("squirtle.backpack.get-size")
  853. local getStack = require("squirtle.backpack.get-stack")
  854. local getStacks = require("squirtle.backpack.get-stacks")
  855. local selectItem = require("squirtle.backpack.select-item")
  856. local selectSlot = require("squirtle.backpack.select-slot")
  857.  
  858. local native = turtle
  859. local Backpack = {}
  860.  
  861. -- [todo] move bit logic somewhere else?
  862. local bitSourceSlot = 15
  863. local bitSlot = 16
  864. local bitItemType = "minecraft:redstone_torch"
  865. local bitDisplayName = "bit"
  866.  
  867. ---@return integer
  868. function Backpack.size()
  869.     return getSize()
  870. end
  871.  
  872. ---@param slot integer
  873. ---@param detailed? boolean
  874. ---@return ItemStack?
  875. function Backpack.getStack(slot, detailed)
  876.     return getStack(slot, detailed)
  877. end
  878.  
  879. ---@param slot integer
  880. ---@param count? integer
  881. function Backpack.transfer(slot, count)
  882.     return native.transferTo(slot, count)
  883. end
  884.  
  885. ---@return boolean
  886. function Backpack.isEmpty()
  887.     for slot = 1, getSize() do
  888.         if Backpack.numInSlot(slot) > 0 then
  889.             return false
  890.         end
  891.     end
  892.  
  893.     return true
  894. end
  895.  
  896. ---@return ItemStack[]
  897. function Backpack.getStacks()
  898.     return getStacks()
  899. end
  900.  
  901. ---@return table<string, integer>
  902. function Backpack.getStock()
  903.     ---@type table<string, integer>
  904.     local stock = {}
  905.  
  906.     for _, stack in pairs(Backpack.getStacks()) do
  907.         stock[stack.name] = (stock[stack.name] or 0) + stack.count
  908.     end
  909.  
  910.     return stock
  911. end
  912.  
  913. ---@param predicate string|function<boolean, ItemStack>
  914. function Backpack.getItemStock(predicate)
  915.     if type(predicate) == "string" then
  916.         local name = predicate
  917.  
  918.         ---@param stack ItemStack
  919.         predicate = function(stack)
  920.             return stack.name == name
  921.         end
  922.     end
  923.  
  924.     local stock = 0
  925.  
  926.     for _, stack in pairs(Backpack.getStacks()) do
  927.         if predicate(stack) then
  928.             stock = stock + stack.count
  929.         end
  930.     end
  931.  
  932.     return stock
  933. end
  934.  
  935. ---@param slot integer
  936. function Backpack.selectSlot(slot)
  937.     return selectSlot(slot)
  938. end
  939.  
  940. ---@param slot integer
  941. ---@return integer
  942. function Backpack.numInSlot(slot)
  943.     return native.getItemCount(slot)
  944. end
  945.  
  946. ---@return boolean
  947. function Backpack.selectSlotIfNotEmpty(slot)
  948.     if Backpack.numInSlot(slot) > 0 then
  949.         return Backpack.selectSlot(slot)
  950.     else
  951.         return false
  952.     end
  953. end
  954.  
  955. ---@param name string
  956. ---@param exact? boolean
  957. function Backpack.find(name, exact)
  958.     return find(name, exact)
  959. end
  960.  
  961. ---@param name string
  962. function Backpack.selectItem(name)
  963.     return selectItem(name)
  964. end
  965.  
  966. ---@param startAt? number
  967. function Backpack.firstEmptySlot(startAt)
  968.     startAt = startAt or 1
  969.  
  970.     for slot = startAt, Backpack.size() do
  971.         if Backpack.numInSlot(slot) == 0 then
  972.             return slot
  973.         end
  974.     end
  975.  
  976.     return nil
  977. end
  978.  
  979. ---@return boolean|integer
  980. function Backpack.selectFirstEmptySlot()
  981.     local slot = Backpack.firstEmptySlot()
  982.  
  983.     if not slot then
  984.         return false
  985.     end
  986.  
  987.     Backpack.selectSlot(slot)
  988.  
  989.     return slot
  990. end
  991.  
  992. function Backpack.readBits()
  993.     local stack = getStack(bitSlot, true)
  994.  
  995.     -- [todo] should we error in case there is a stack and it is not
  996.     -- the type of bit item we except?
  997.     if stack and stack.name == bitItemType and stack.displayName:lower() == bitDisplayName then
  998.         return stack.count
  999.     end
  1000.  
  1001.     return 0
  1002. end
  1003.  
  1004. -- [todo] throw errors?
  1005. ---@param bits integer
  1006. function Backpack.setBits(bits)
  1007.     local current = Backpack.readBits()
  1008.  
  1009.     if current == bits then
  1010.         return true
  1011.     elseif current < bits then
  1012.         selectSlot(bitSourceSlot)
  1013.         return Backpack.transfer(bitSlot, bits - current)
  1014.     elseif current > bits then
  1015.         selectSlot(bitSlot)
  1016.         return Backpack.transfer(bitSourceSlot, current - bits)
  1017.     end
  1018. end
  1019.  
  1020. -- [todo] throw errors?
  1021. ---@param bits integer
  1022. function Backpack.orBits(bits)
  1023.     local current = Backpack.readBits()
  1024.     local next = bit.bor(bits, current)
  1025.  
  1026.     if next ~= current then
  1027.         return Backpack.setBits(next)
  1028.     end
  1029.  
  1030.     return true
  1031. end
  1032.  
  1033. -- [todo] throw errors?
  1034. ---@param bits integer
  1035. function Backpack.xorBits(bits)
  1036.     local current = Backpack.readBits()
  1037.     local next = bit.bxor(bits, current)
  1038.  
  1039.     if next ~= current then
  1040.         return Backpack.setBits(next)
  1041.     end
  1042.  
  1043.     return true
  1044. end
  1045.  
  1046. function Backpack.condense()
  1047.     for slot = getSize(), 1, -1 do
  1048.         local item = getStack(slot)
  1049.  
  1050.         if item then
  1051.             for targetSlot = 1, slot - 1 do
  1052.                 local candidate = getStack(targetSlot, true)
  1053.  
  1054.                 if candidate and candidate.name == item.name and candidate.count < candidate.maxCount then
  1055.                     selectSlot(slot)
  1056.                     Backpack.transfer(targetSlot)
  1057.  
  1058.                     if Backpack.numInSlot(slot) == 0 then
  1059.                         break
  1060.                     end
  1061.                 elseif not candidate then
  1062.                     selectSlot(slot)
  1063.                     Backpack.transfer(targetSlot)
  1064.                     break
  1065.                 end
  1066.             end
  1067.         end
  1068.     end
  1069. end
  1070.  
  1071. ---@return boolean
  1072. function Backpack.isFull()
  1073.     for slot = 1, getSize() do
  1074.         if Backpack.numInSlot(slot) == 0 then
  1075.             return false
  1076.         end
  1077.     end
  1078.  
  1079.     return true
  1080. end
  1081.  
  1082. return Backpack;
  1083.  
  1084. end)
  1085. __bundle_register("squirtle.backpack.select-slot", function(require, _LOADED, __bundle_register, __bundle_modules)
  1086. ---@param slot integer
  1087. return function(slot)
  1088.     return turtle.select(slot)
  1089. end
  1090.  
  1091. end)
  1092. __bundle_register("squirtle.backpack.select-item", function(require, _LOADED, __bundle_register, __bundle_modules)
  1093. local find = require("squirtle.backpack.find")
  1094. local selectSlot = require("squirtle.backpack.select-slot")
  1095.  
  1096. ---@param name string
  1097. ---@param exact? boolean
  1098. ---@return false|integer
  1099. return function(name, exact)
  1100.     local slot = find(name, exact)
  1101.  
  1102.     if not slot then
  1103.         return false
  1104.     end
  1105.  
  1106.     selectSlot(slot)
  1107.  
  1108.     return slot
  1109. end
  1110.  
  1111. end)
  1112. __bundle_register("squirtle.backpack.find", function(require, _LOADED, __bundle_register, __bundle_modules)
  1113. local getSize = require("squirtle.backpack.get-size")
  1114. local getStack = require("squirtle.backpack.get-stack")
  1115.  
  1116. ---@param name string
  1117. ---@param exact? boolean
  1118. return function(name, exact)
  1119.     for slot = 1, getSize() do
  1120.         local item = getStack(slot)
  1121.  
  1122.         if item and exact and item.name == name then
  1123.             return slot
  1124.         elseif item and not exact and string.find(item.name, name) then
  1125.             return slot
  1126.         end
  1127.     end
  1128. end
  1129.  
  1130. end)
  1131. __bundle_register("squirtle.backpack.get-stack", function(require, _LOADED, __bundle_register, __bundle_modules)
  1132. ---@param slot integer
  1133. ---@param detailed? boolean
  1134. ---@return ItemStack?
  1135. return function(slot, detailed)
  1136.     return turtle.getItemDetail(slot, detailed)
  1137. end
  1138.  
  1139. end)
  1140. __bundle_register("squirtle.backpack.get-size", function(require, _LOADED, __bundle_register, __bundle_modules)
  1141. ---@return integer
  1142. return function()
  1143.     return 16
  1144. end
  1145.  
  1146. end)
  1147. __bundle_register("squirtle.backpack.get-stacks", function(require, _LOADED, __bundle_register, __bundle_modules)
  1148. local getSize = require("squirtle.backpack.get-size")
  1149. local getStack = require("squirtle.backpack.get-stack")
  1150.  
  1151. -- [todo] idea: cache stacks until any event is pulled.
  1152. -- that should - afaik - be completely safe, as any change in turtle inventory triggers a "turtle_inventory" event
  1153. ---@return ItemStack[]
  1154. return function()
  1155.     local stacks = {}
  1156.  
  1157.     for slot = 1, getSize() do
  1158.         local item = getStack(slot)
  1159.  
  1160.         if item then
  1161.             stacks[slot] = item
  1162.         end
  1163.     end
  1164.  
  1165.     return stacks
  1166. end
  1167.  
  1168. end)
  1169. __bundle_register("squirtle.refuel", function(require, _LOADED, __bundle_register, __bundle_modules)
  1170. local Fuel = require("squirtle.fuel")
  1171. local refuelFromBackpack = require("squirtle.refuel.from-backpack")
  1172. local refuelWithHelpFromPlayer = require("squirtle.refuel.with-help-from-player")
  1173.  
  1174. ---@param fuel integer
  1175. return function(fuel)
  1176.     if Fuel.hasFuel(fuel) then
  1177.         return true
  1178.     end
  1179.  
  1180.     refuelFromBackpack(fuel)
  1181.  
  1182.     if Fuel.getFuelLevel() < fuel then
  1183.         refuelWithHelpFromPlayer(fuel)
  1184.     end
  1185. end
  1186.  
  1187. end)
  1188. __bundle_register("squirtle.refuel.with-help-from-player", function(require, _LOADED, __bundle_register, __bundle_modules)
  1189. local Fuel = require("squirtle.fuel")
  1190. local refuelFromBackpack = require("squirtle.refuel.from-backpack")
  1191.  
  1192. ---@param fuel? integer
  1193. return function(fuel)
  1194.     fuel = fuel or Fuel.getMissingFuel()
  1195.  
  1196.     if fuel > turtle.getFuelLimit() then
  1197.         error(string.format("required fuel is %d more than the tank can hold", fuel - turtle.getFuelLimit()))
  1198.     end
  1199.  
  1200.     local _, y = term.getCursorPos()
  1201.  
  1202.     while Fuel.getFuelLevel() < fuel do
  1203.         term.setCursorPos(1, y)
  1204.         term.clearLine()
  1205.         local openFuel = fuel - Fuel.getFuelLevel()
  1206.         term.write(string.format("[help] need %d more fuel please", openFuel))
  1207.         term.setCursorPos(1, y + 1)
  1208.         os.pullEvent("turtle_inventory")
  1209.         refuelFromBackpack(openFuel)
  1210.     end
  1211. end
  1212.  
  1213. end)
  1214. __bundle_register("squirtle.refuel.from-backpack", function(require, _LOADED, __bundle_register, __bundle_modules)
  1215. local Fuel = require("squirtle.fuel")
  1216. local Backpack = require("squirtle.backpack")
  1217.  
  1218. local bucket = "minecraft:bucket"
  1219.  
  1220. ---@param fuel? integer
  1221. return function(fuel)
  1222.     fuel = fuel or Fuel.getMissingFuel()
  1223.     local fuelStacks = Fuel.pickStacks(Backpack.getStacks(), fuel)
  1224.     local emptyBucketSlot = Backpack.find(bucket)
  1225.  
  1226.     for slot, stack in pairs(fuelStacks) do
  1227.         Backpack.selectSlot(slot)
  1228.         Fuel.refuel(stack.count)
  1229.  
  1230.         local remaining = Backpack.getStack(slot)
  1231.  
  1232.         if remaining and remaining.name == bucket then
  1233.             if (emptyBucketSlot == nil) or (not Backpack.transfer(emptyBucketSlot)) then
  1234.                 emptyBucketSlot = slot
  1235.             end
  1236.         end
  1237.     end
  1238. end
  1239.  
  1240. end)
  1241. __bundle_register("squirtle.fuel", function(require, _LOADED, __bundle_register, __bundle_modules)
  1242. ---@class Fuel
  1243. local Fuel = {}
  1244. local native = turtle
  1245.  
  1246. local items = {
  1247.     -- ["minecraft:lava_bucket"] = 1000,
  1248.     ["minecraft:coal"] = 80,
  1249.     ["minecraft:charcoal"] = 80,
  1250.     ["minecraft:coal_block"] = 800,
  1251.     -- ["minecraft:bamboo"] = 2
  1252. }
  1253.  
  1254. ---@param fuel integer
  1255. function Fuel.hasFuel(fuel)
  1256.     local level = native.getFuelLevel()
  1257.  
  1258.     return level == "unlimited" or level >= fuel
  1259. end
  1260.  
  1261. ---@param count? integer
  1262. function Fuel.refuel(count)
  1263.     return native.refuel(count)
  1264. end
  1265.  
  1266. ---@return integer
  1267. function Fuel.getFuelLevel()
  1268.     return native.getFuelLevel()
  1269. end
  1270.  
  1271. ---@return integer
  1272. function Fuel.getFuelLimit()
  1273.     return native.getFuelLimit()
  1274. end
  1275.  
  1276. ---@param limit? integer
  1277. ---@return integer
  1278. function Fuel.getMissingFuel(limit)
  1279.     local fuelLevel = Fuel.getFuelLevel()
  1280.  
  1281.     if fuelLevel == "unlimited" then
  1282.         return 0
  1283.     end
  1284.  
  1285.     if not limit then
  1286.         limit = Fuel.getFuelLimit()
  1287.     end
  1288.  
  1289.     return limit - Fuel.getFuelLevel()
  1290. end
  1291.  
  1292. ---@param item string
  1293. function Fuel.isFuel(item)
  1294.     return items[item] ~= nil
  1295. end
  1296.  
  1297. ---@param item string
  1298. function Fuel.getRefuelAmount(item)
  1299.     return items[item] or 0
  1300. end
  1301.  
  1302. --- @param stack table
  1303. function Fuel.getStackRefuelAmount(stack)
  1304.     return Fuel.getRefuelAmount(stack.name) * stack.count
  1305. end
  1306.  
  1307. ---@param stacks ItemStack[]
  1308. function Fuel.filterStacks(stacks)
  1309.     local fuelStacks = {}
  1310.  
  1311.     for slot, stack in pairs(stacks) do
  1312.         if Fuel.isFuel(stack.name) then
  1313.             fuelStacks[slot] = stack
  1314.         end
  1315.     end
  1316.  
  1317.     return fuelStacks
  1318. end
  1319.  
  1320. ---@param stacks ItemStack[]
  1321. ---@param fuel number
  1322. ---@param allowedOverFlow? number
  1323. ---@return ItemStack[] fuelStacks, number openFuel
  1324. function Fuel.pickStacks(stacks, fuel, allowedOverFlow)
  1325.     allowedOverFlow = math.max(allowedOverFlow or 1000, 0)
  1326.     local pickedStacks = {}
  1327.     local openFuel = fuel
  1328.  
  1329.     -- [todo] try to order stacks based on type of item
  1330.     -- for example, we may want to start with the smallest ones to minimize potential overflow
  1331.     for slot, stack in pairs(stacks) do
  1332.         if Fuel.isFuel(stack.name) then
  1333.             local stackRefuelAmount = Fuel.getStackRefuelAmount(stack)
  1334.  
  1335.             if stackRefuelAmount <= openFuel then
  1336.                 pickedStacks[slot] = stack
  1337.                 openFuel = openFuel - stackRefuelAmount
  1338.             else
  1339.                 -- [todo] can be shortened
  1340.                 -- actually, im not even sure we need the option to provide an allowed overflow
  1341.                 local itemRefuelAmount = Fuel.getRefuelAmount(stack.name)
  1342.                 local numRequiredItems = math.floor(openFuel / itemRefuelAmount)
  1343.                 local numItemsToPick = numRequiredItems
  1344.  
  1345.                 if allowedOverFlow > 0 and ((numItemsToPick + 1) * itemRefuelAmount) - openFuel <= allowedOverFlow then
  1346.                     numItemsToPick = numItemsToPick + 1
  1347.                 end
  1348.                 -- local numRequiredItems = math.ceil(openFuel / itemRefuelAmount)
  1349.  
  1350.                 -- if (numRequiredItems * itemRefuelAmount) - openFuel <= allowedOverFlow then
  1351.                 if numItemsToPick > 0 then
  1352.                     pickedStacks[slot] = {name = stack.name, count = numItemsToPick}
  1353.                     openFuel = openFuel - stackRefuelAmount
  1354.                 end
  1355.             end
  1356.  
  1357.             if openFuel <= 0 then
  1358.                 break
  1359.             end
  1360.         end
  1361.     end
  1362.  
  1363.     -- if Utils.isEmpty(pickedStacks) then
  1364.     --     pickedStacks = nil
  1365.     -- end
  1366.  
  1367.     return pickedStacks, openFuel
  1368. end
  1369.  
  1370. ---@param stacks ItemStack[]
  1371. function Fuel.sumFuel(stacks)
  1372.     local fuel = 0
  1373.  
  1374.     for _, stack in pairs(stacks) do
  1375.         if Fuel.isFuel(stack.name) then
  1376.             fuel = fuel + Fuel.getStackRefuelAmount(stack)
  1377.         end
  1378.     end
  1379.  
  1380.     return fuel
  1381. end
  1382.  
  1383. return Fuel
  1384.  
  1385. end)
  1386. __bundle_register("elements.cardinal", function(require, _LOADED, __bundle_register, __bundle_modules)
  1387. local Vector = require("elements.vector")
  1388. local Side = require("elements.side")
  1389.  
  1390. ---@class Cardinal
  1391. local Cardinal = {south = 0, west = 1, north = 2, east = 3, up = 4, down = 5}
  1392.  
  1393. local names = {
  1394.     [Cardinal.south] = "south",
  1395.     [Cardinal.west] = "west",
  1396.     [Cardinal.north] = "north",
  1397.     [Cardinal.east] = "east",
  1398.     [Cardinal.up] = "up",
  1399.     [Cardinal.down] = "down"
  1400. }
  1401.  
  1402. ---@param vector Vector
  1403. function Cardinal.fromVector(vector)
  1404.     if vector.x > 0 and vector.y == 0 and vector.z == 0 then
  1405.         return Cardinal.east
  1406.     elseif vector.x < 0 and vector.y == 0 and vector.z == 0 then
  1407.         return Cardinal.west
  1408.     elseif vector.x == 0 and vector.y == 0 and vector.z > 0 then
  1409.         return Cardinal.south
  1410.     elseif vector.x == 0 and vector.y == 0 and vector.z < 0 then
  1411.         return Cardinal.north
  1412.     elseif vector.x == 0 and vector.y > 0 and vector.z == 0 then
  1413.         return Cardinal.up
  1414.     elseif vector.x == 0 and vector.y < 0 and vector.z == 0 then
  1415.         return Cardinal.down
  1416.     end
  1417.  
  1418.     error(vector .. " is not a cardinal vector")
  1419. end
  1420.  
  1421. ---@param cardinal number
  1422. function Cardinal.toVector(cardinal)
  1423.     if cardinal == Cardinal.south then
  1424.         return Vector.create(0, 0, 1)
  1425.     elseif cardinal == Cardinal.west then
  1426.         return Vector.create(-1, 0, 0)
  1427.     elseif cardinal == Cardinal.north then
  1428.         return Vector.create(0, 0, -1)
  1429.     elseif cardinal == Cardinal.east then
  1430.         return Vector.create(1, 0, 0)
  1431.     elseif cardinal == Cardinal.up then
  1432.         return Vector.create(0, 1, 0)
  1433.     elseif cardinal == Cardinal.down then
  1434.         return Vector.create(0, -1, 0)
  1435.     end
  1436. end
  1437.  
  1438. ---@param cardinal number
  1439. function Cardinal.getName(cardinal)
  1440.     local name = names[cardinal]
  1441.  
  1442.     if name == nil then
  1443.         error("not a valid cardinal: " .. tostring(cardinal))
  1444.     end
  1445.  
  1446.     return names[cardinal] or tostring(cardinal);
  1447. end
  1448.  
  1449. -- [todo] why did i comment this out?
  1450. -- function Cardinal.rotate(cardinal, rotation)
  1451. --     return (cardinal + rotation) % 4
  1452. -- end
  1453.  
  1454. ---@param cardinal integer
  1455. ---@param times? number
  1456. ---@return integer
  1457. function Cardinal.rotateLeft(cardinal, times)
  1458.     return (cardinal - (times or 1)) % 4
  1459. end
  1460.  
  1461. ---@param cardinal integer
  1462. ---@param times? number
  1463. ---@return integer
  1464. function Cardinal.rotateRight(cardinal, times)
  1465.     return (cardinal + (times or 1)) % 4
  1466. end
  1467.  
  1468. ---@param cardinal integer
  1469. ---@return integer
  1470. function Cardinal.rotateAround(cardinal, times)
  1471.     return (cardinal + (2 * (times or 1))) % 4
  1472. end
  1473.  
  1474. ---@param cardinal integer
  1475. ---@param side string|integer
  1476. ---@param times? integer
  1477. ---@return integer
  1478. function Cardinal.rotate(cardinal, side, times)
  1479.     if side == Side.left or side == "left" then
  1480.         return Cardinal.rotateLeft(cardinal, times)
  1481.     elseif side == Side.right or side == "right" then
  1482.         return Cardinal.rotateRight(cardinal, times)
  1483.     elseif side == Side.back or side == "back" then
  1484.         return Cardinal.rotateAround(cardinal, times)
  1485.     elseif side == Side.front or side == "front" then
  1486.         return cardinal
  1487.     else
  1488.         error(string.format("rotate() doesn't support side %s", Side.getName(side)))
  1489.     end
  1490. end
  1491.  
  1492. -- [todo] did i give up?
  1493. -- function Cardinal.rotateBy(cardinal, by)
  1494. --     if (cardinal + 1) % 4 == by then
  1495. --     else if (cardinal - 1) % 4 == by then
  1496. --     else
  1497. --     end
  1498. -- end
  1499.  
  1500. function Cardinal.isVertical(cardinal)
  1501.     return cardinal == Cardinal.up or cardinal == Cardinal.down
  1502. end
  1503.  
  1504. ---@param side string|integer
  1505. ---@param facing integer
  1506. ---@return integer
  1507. function Cardinal.fromSide(side, facing)
  1508.     if side == Side.front or side == "front" or side == "forward" then
  1509.         return facing
  1510.     elseif side == Side.top or side == "top" or side == "up" then
  1511.         return Cardinal.up
  1512.     elseif side == Side.bottom or side == "bottom" or side == "down" then
  1513.         return Cardinal.down
  1514.     elseif side == Side.left or side == "left" then
  1515.         return Cardinal.rotateLeft(facing)
  1516.     elseif side == Side.right or side == "right" then
  1517.         return Cardinal.rotateRight(facing)
  1518.     elseif side == Side.back or side == "back" then
  1519.         return Cardinal.rotateAround(facing)
  1520.     end
  1521.  
  1522.     error(("invalid side: %s"):format(side))
  1523. end
  1524.  
  1525. ---@param value integer
  1526. function Cardinal.isCardinal(value)
  1527.     return value >= 0 and value <= 5
  1528. end
  1529.  
  1530. return Cardinal
  1531.  
  1532. end)
  1533. __bundle_register("elements.side", function(require, _LOADED, __bundle_register, __bundle_modules)
  1534. ---@class Side
  1535. local Side = {front = 0, right = 1, back = 2, left = 3, top = 4, bottom = 5, up = 4, down = 5}
  1536.  
  1537. local lookup = {
  1538.     front = 0,
  1539.     [0] = "front",
  1540.     right = 1,
  1541.     [1] = "right",
  1542.     back = 2,
  1543.     [2] = "back",
  1544.     left = 3,
  1545.     [3] = "left",
  1546.     top = 4,
  1547.     [4] = "top",
  1548.     bottom = 5,
  1549.     [5] = "bottom",
  1550.     forward = 0,
  1551.     up = 4,
  1552.     down = 5
  1553. }
  1554.  
  1555. if not turtle then
  1556.     Side.right = 3
  1557.     Side.left = 1
  1558. end
  1559.  
  1560. local names = {}
  1561.  
  1562. for k, v in pairs(Side) do
  1563.     names[v] = k
  1564. end
  1565.  
  1566. ---@param side string|integer
  1567. function Side.getName(side)
  1568.     if type(side) == "string" then
  1569.         return side
  1570.     else
  1571.         return names[side] or tostring(side);
  1572.     end
  1573. end
  1574.  
  1575. ---@param side string
  1576. ---@return string
  1577. function Side.rotateAround(side)
  1578.     return lookup[(lookup[side] + 2) % 4]
  1579. end
  1580.  
  1581. return Side
  1582.  
  1583. end)
  1584. __bundle_register("elements.vector", function(require, _LOADED, __bundle_register, __bundle_modules)
  1585. ---@class Vector
  1586. ---@field x number
  1587. ---@field y number
  1588. ---@field z number
  1589. local Vector = {}
  1590.  
  1591. ---@return Vector
  1592. function Vector.create(x, y, z)
  1593.     if type(x) ~= "number" then
  1594.         error("expected x to be a number, was " .. type(x))
  1595.     elseif type(y) ~= "number" then
  1596.         error("expected y to be a number, was " .. type(y))
  1597.     elseif type(z) ~= "number" then
  1598.         error("expected z to be a number, was " .. type(z))
  1599.     end
  1600.  
  1601.     local instance = {x = x or 0, y = y or 0, z = z or 0}
  1602.  
  1603.     setmetatable(instance, {__tostring = Vector.toString, __concat = Vector.concat})
  1604.  
  1605.     return instance
  1606. end
  1607.  
  1608. function Vector.plus(a, b)
  1609.     if (type(a) == "number") then
  1610.         return Vector.create(b.x + a, b.y + a, b.z + a)
  1611.     elseif (type(b) == "number") then
  1612.         return Vector.create(a.x + b, a.y + b, a.z + b)
  1613.     else
  1614.         return Vector.create(a.x + b.x, a.y + b.y, a.z + b.z)
  1615.     end
  1616. end
  1617.  
  1618. ---@param other Vector
  1619. function Vector.minus(self, other)
  1620.     return Vector.create(self.x - other.x, self.y - other.y, self.z - other.z)
  1621. end
  1622.  
  1623. ---@param self Vector
  1624. ---@param other Vector
  1625. ---@return boolean
  1626. function Vector.equals(self, other)
  1627.     return self.x == other.x and self.y == other.y and self.z == other.z
  1628. end
  1629.  
  1630. ---@param self Vector
  1631. ---@return number
  1632. function Vector.length(self)
  1633.     return math.sqrt(self.x * self.x + self.y * self.y + self.z * self.z)
  1634. end
  1635.  
  1636. ---@param a Vector
  1637. ---@param b Vector
  1638. ---@return number
  1639. function Vector.distance(a, b)
  1640.     return Vector.length(Vector.minus(a, b))
  1641. end
  1642.  
  1643. function Vector:negate()
  1644.     return Vector.create(-self.x, -self.y, -self.z)
  1645. end
  1646.  
  1647. function Vector:toString()
  1648.     return string.format("%d,%d,%d", self.x, self.y, self.z)
  1649. end
  1650.  
  1651. function Vector:concat(other)
  1652.     return tostring(self) .. tostring(other)
  1653. end
  1654.  
  1655. ---@param a Vector
  1656. ---@param b Vector
  1657. ---@return number
  1658. function Vector.manhattan(a, b)
  1659.     return math.abs(b.x - a.x) + math.abs(b.y - a.y) + math.abs(b.z - a.z)
  1660. end
  1661.  
  1662. ---@param vector Vector
  1663. ---@param times integer?
  1664. ---@return Vector
  1665. function Vector.rotateClockwise(vector, times)
  1666.     times = times or 1
  1667.     local rotated = Vector.create(vector.x, vector.y, vector.z)
  1668.  
  1669.     for _ = 1, times do
  1670.         local x = -rotated.z
  1671.         local z = rotated.x
  1672.         rotated.x = x
  1673.         rotated.z = z
  1674.     end
  1675.  
  1676.     return rotated
  1677. end
  1678.  
  1679. return Vector
  1680.  
  1681. end)
  1682. __bundle_register("squirtle.place", function(require, _LOADED, __bundle_register, __bundle_modules)
  1683. local dig = require("squirtle.dig")
  1684.  
  1685. local natives = {
  1686.     top = turtle.placeUp,
  1687.     up = turtle.placeUp,
  1688.     front = turtle.place,
  1689.     forward = turtle.place,
  1690.     bottom = turtle.placeDown,
  1691.     down = turtle.placeDown
  1692. }
  1693.  
  1694. ---@param side? string
  1695. return function(side)
  1696.     side = side or "front"
  1697.     local handler = natives[side]
  1698.  
  1699.     if not handler then
  1700.         error(string.format("place() does not support side %s", side))
  1701.     end
  1702.  
  1703.     local success = handler()
  1704.  
  1705.     if not success then
  1706.         if dig(side) then
  1707.             success = handler()
  1708.         end
  1709.     end
  1710.  
  1711.     return success
  1712. end
  1713.  
  1714. end)
  1715. __bundle_register("squirtle.dig", function(require, _LOADED, __bundle_register, __bundle_modules)
  1716. local natives = {
  1717.     top = turtle.digUp,
  1718.     up = turtle.digUp,
  1719.     front = turtle.dig,
  1720.     forward = turtle.dig,
  1721.     bottom = turtle.digDown,
  1722.     down = turtle.digDown
  1723. }
  1724.  
  1725. ---@param side? string
  1726. ---@param toolSide? string
  1727. ---@return boolean
  1728. return function(side, toolSide)
  1729.     side = side or "front"
  1730.     local handler = natives[side]
  1731.  
  1732.     if not handler then
  1733.         error(string.format("dig() does not support side %s", side))
  1734.     end
  1735.  
  1736.     local success = handler(toolSide)
  1737.  
  1738.     -- omitting message on purpose
  1739.     -- [todo] what is that purpose?
  1740.     return success
  1741. end
  1742.  
  1743. end)
  1744. __bundle_register("squirtle.turn", function(require, _LOADED, __bundle_register, __bundle_modules)
  1745. local Cardinal = require("elements.cardinal")
  1746. local getState = require("squirtle.get-state")
  1747. local changeState = require("squirtle.change-state")
  1748. local native = turtle
  1749.  
  1750. local function turnLeft()
  1751.     local state = getState()
  1752.     local success, message = native.turnLeft()
  1753.  
  1754.     if not success then
  1755.         return false, message
  1756.     end
  1757.  
  1758.     if state.facing then
  1759.         changeState({facing = Cardinal.rotateLeft(state.facing)})
  1760.     end
  1761.  
  1762.     return true
  1763. end
  1764.  
  1765. local function turnRight()
  1766.     local state = getState()
  1767.     local success, message = native.turnRight()
  1768.  
  1769.     if not success then
  1770.         return false, message
  1771.     end
  1772.  
  1773.     if state.facing then
  1774.         changeState({facing = Cardinal.rotateRight(state.facing)})
  1775.     end
  1776.  
  1777.     return true
  1778. end
  1779.  
  1780. local function turnBack()
  1781.     local turnFn = turnLeft
  1782.  
  1783.     if math.random() < .5 then
  1784.         turnFn = turnRight
  1785.     end
  1786.  
  1787.     local success, message = turnFn()
  1788.  
  1789.     if not success then
  1790.         return false, message
  1791.     end
  1792.  
  1793.     return turnFn()
  1794. end
  1795.  
  1796. ---@param side? string defaults to "left"
  1797. return function(side)
  1798.     -- [todo] turning to the left by default is not a good idea,
  1799.     -- makes it harder to find bugs in case a bad side is given, e.g. "nil"
  1800.     side = side or "left"
  1801.  
  1802.     if side == "left" then
  1803.         return turnLeft()
  1804.     elseif side == "right" then
  1805.         return turnRight()
  1806.     elseif side == "back" then
  1807.         return turnBack()
  1808.     elseif side == "front" then
  1809.         return true
  1810.     else
  1811.         error(string.format("turn() does not support side %s", side))
  1812.     end
  1813. end
  1814.  
  1815. end)
  1816. __bundle_register("squirtle.change-state", function(require, _LOADED, __bundle_register, __bundle_modules)
  1817. local getState = require("squirtle.get-state")
  1818.  
  1819. ---@param patch SquirtleState
  1820. return function(patch)
  1821.     local state = getState()
  1822.  
  1823.     for key, value in pairs(patch) do
  1824.         state[key] = value
  1825.     end
  1826.  
  1827.     return state
  1828. end
  1829.  
  1830. end)
  1831. __bundle_register("squirtle.get-state", function(require, _LOADED, __bundle_register, __bundle_modules)
  1832. ---@class SquirtleState
  1833. ---@field position Vector
  1834. ---@field facing integer
  1835. ---@type SquirtleState
  1836. local state = {}
  1837.  
  1838. ---@return SquirtleState
  1839. return function()
  1840.     return state
  1841. end
  1842.  
  1843. end)
  1844. __bundle_register("squirtle.drop", function(require, _LOADED, __bundle_register, __bundle_modules)
  1845. local natives = {
  1846.     top = turtle.dropUp,
  1847.     up = turtle.dropUp,
  1848.     front = turtle.drop,
  1849.     bottom = turtle.dropDown,
  1850.     down = turtle.dropDown
  1851. }
  1852.  
  1853. ---@param side? string
  1854. ---@param limit? integer
  1855. ---@return boolean, string?
  1856. return function(side, limit)
  1857.     side = side or "front"
  1858.     local handler = natives[side]
  1859.  
  1860.     if not handler then
  1861.         error(string.format("drop() does not support side %s", side))
  1862.     end
  1863.  
  1864.     return handler(limit)
  1865. end
  1866.  
  1867. end)
  1868. __bundle_register("dig.boot", function(require, _LOADED, __bundle_register, __bundle_modules)
  1869. local Fuel = require("squirtle.fuel")
  1870. local Cardinal = require("elements.cardinal")
  1871. local Vector = require("elements.vector")
  1872. local World = require("geo.world")
  1873. local changeState = require("squirtle.change-state")
  1874. local refuel = require("squirtle.refuel")
  1875.  
  1876. local function printUsage()
  1877.     print("Usage:")
  1878.     print("dig <depth> <width> <height>")
  1879.     print("(negative numbers possible)")
  1880. end
  1881.  
  1882. ---@param args table<string>
  1883. ---@return DigAppState? state
  1884. return function(args)
  1885.     local depth = tonumber(args[1])
  1886.     local width = tonumber(args[2])
  1887.     local height = tonumber(args[3])
  1888.     ---@type table<string>
  1889.     local ignore = {}
  1890.  
  1891.     for i = 4, #args do
  1892.         table.insert(ignore, args[i])
  1893.     end
  1894.  
  1895.     if not depth or not width or not height or depth == 0 or width == 0 or height == 0 then
  1896.         printUsage()
  1897.         return nil
  1898.     end
  1899.  
  1900.     depth = -depth
  1901.  
  1902.     local returnTripFuel = math.abs(depth) + math.abs(width) + math.abs(height)
  1903.     local numBlocks = math.abs(depth) * math.abs(width) * math.abs(height)
  1904.     print(numBlocks .. "x blocks, guessing " .. numBlocks / 32 .. " stacks")
  1905.  
  1906.     local requiredFuel = math.ceil((numBlocks + returnTripFuel) * 1.2)
  1907.     refuel(requiredFuel)
  1908.  
  1909.     local position = Vector.create(0, 0, 0)
  1910.     local facing = Cardinal.north
  1911.     changeState({facing = facing, position = position})
  1912.  
  1913.     local worldX = 0
  1914.     local worldY = 0
  1915.     local worldZ = 0
  1916.  
  1917.     if width < 0 then
  1918.         worldX = position.x + width + 1
  1919.         width = math.abs(width)
  1920.     end
  1921.  
  1922.     if height < 0 then
  1923.         worldY = position.y + height + 1
  1924.         height = math.abs(height)
  1925.     end
  1926.  
  1927.     if depth < 0 then
  1928.         worldZ = position.z + depth + 1
  1929.         depth = math.abs(depth)
  1930.     end
  1931.  
  1932.     local world = World.create(worldX, worldY, worldZ, width, height, depth)
  1933.     local hasShulkers = false
  1934.  
  1935.     for slot = 1, 16 do
  1936.         local stack = turtle.getItemDetail(slot)
  1937.         if stack and stack.name:match("shulker") then
  1938.             hasShulkers = true
  1939.             break
  1940.         end
  1941.     end
  1942.  
  1943.     return {position = position, facing = facing, world = world, hasShulkers = hasShulkers, ignore = ignore}
  1944. end
  1945.  
  1946. end)
  1947. __bundle_register("geo.world", function(require, _LOADED, __bundle_register, __bundle_modules)
  1948. local Vector = require("elements.vector")
  1949.  
  1950. ---@class World
  1951. ---@field width? integer
  1952. ---@field height? integer
  1953. ---@field depth? integer
  1954. ---@field blocked table<string, unknown>
  1955. ---@field x integer
  1956. ---@field y integer
  1957. ---@field z integer
  1958. local World = {}
  1959.  
  1960. local function swap(a, b)
  1961.     return b, a
  1962. end
  1963.  
  1964. ---@param value integer
  1965. ---@param from integer
  1966. ---@param length? integer
  1967. ---@return boolean
  1968. local function isInRange(value, from, length)
  1969.     if length == nil then
  1970.         return true
  1971.     elseif length == 0 then
  1972.         return false
  1973.     end
  1974.  
  1975.     local to = from + length
  1976.  
  1977.     if to < from then
  1978.         from, to = swap(from, to)
  1979.     end
  1980.  
  1981.     return value >= from and value < to
  1982. end
  1983.  
  1984. ---@param x integer
  1985. ---@param y integer
  1986. ---@param z integer
  1987. ---@param width? integer
  1988. ---@param height? integer
  1989. ---@param depth? integer
  1990. ---@return World
  1991. local function create(x, y, z, width, height, depth)
  1992.     if (width ~= nil and width < 1) or (height ~= nil and height < 1) or (depth ~= nil and depth < 1) then
  1993.         error("can't create world with width/height/depth less than 1")
  1994.     end
  1995.  
  1996.     ---@type World
  1997.     local world = {x = x, y = y, z = z, width = width, height = height, depth = depth, blocked = {}}
  1998.  
  1999.     return world
  2000. end
  2001.  
  2002. ---@param world World
  2003. ---@param x integer
  2004. local function isInBoundsX(world, x)
  2005.     return isInRange(x, world.x, world.width)
  2006. end
  2007.  
  2008. ---@param world World
  2009. ---@param y integer
  2010. local function isInBoundsY(world, y)
  2011.     return isInRange(y, world.y, world.height)
  2012. end
  2013.  
  2014. ---@param world World
  2015. ---@param z integer
  2016. local function isInBoundsZ(world, z)
  2017.     return isInRange(z, world.z, world.depth)
  2018. end
  2019.  
  2020. ---@param world World
  2021. ---@param point Vector
  2022. local function isInBounds(world, point)
  2023.     return isInBoundsX(world, point.x) and isInBoundsY(world, point.y) and isInBoundsZ(world, point.z)
  2024. end
  2025.  
  2026. ---@param world World
  2027. ---@param point Vector
  2028. local function isInBottomPlane(world, point)
  2029.     return point.y == world.y
  2030. end
  2031.  
  2032. ---@param world World
  2033. ---@param point Vector
  2034. local function isInTopPlane(world, point)
  2035.     return point.y == world.y + world.height - 1
  2036. end
  2037.  
  2038. ---@param world World
  2039. ---@param point Vector
  2040. ---@return boolean
  2041. local function isBlocked(world, point)
  2042.     if not isInBounds(world, point) then
  2043.         return false
  2044.     else
  2045.         return world.blocked[tostring(point)] ~= nil
  2046.     end
  2047. end
  2048.  
  2049. ---@param world World
  2050. ---@param point Vector
  2051. local function setBlock(world, point)
  2052.     world.blocked[tostring(point)] = true
  2053. end
  2054.  
  2055. ---@param world World
  2056. ---@param point Vector
  2057. local function clearBlock(world, point)
  2058.     world.blocked[tostring(point)] = nil
  2059. end
  2060.  
  2061. ---@param world World
  2062. local function getCorners(world)
  2063.     return {
  2064.         Vector.create(world.x, world.y, world.z),
  2065.         Vector.create(world.x + world.width - 1, world.y, world.z),
  2066.         Vector.create(world.x, world.y + world.height - 1, world.z),
  2067.         Vector.create(world.x + world.width - 1, world.y + world.height - 1, world.z),
  2068.         --
  2069.         Vector.create(world.x, world.y, world.z + world.depth - 1),
  2070.         Vector.create(world.x + world.width - 1, world.y, world.z + world.depth - 1),
  2071.         Vector.create(world.x, world.y + world.height - 1, world.z + world.depth - 1),
  2072.         Vector.create(world.x + world.width - 1, world.y + world.height - 1, world.z + world.depth - 1)
  2073.     }
  2074. end
  2075.  
  2076. ---@param world World
  2077. ---@param point Vector
  2078. local function getClosestCorner(world, point)
  2079.     local corners = getCorners(world)
  2080.  
  2081.     ---@type Vector
  2082.     local best
  2083.  
  2084.     for i = 1, #corners do
  2085.         if best == nil or Vector.distance(best, point) > Vector.distance(corners[i], point) then
  2086.             best = corners[i]
  2087.         end
  2088.     end
  2089.  
  2090.     return best
  2091. end
  2092.  
  2093. return {
  2094.     create = create,
  2095.     isInBoundsX = isInBoundsX,
  2096.     isInBoundsY = isInBoundsY,
  2097.     isInBoundsZ = isInBoundsZ,
  2098.     isInBounds = isInBounds,
  2099.     isInBottomPlane = isInBottomPlane,
  2100.     isInTopPlane = isInTopPlane,
  2101.     isBlocked = isBlocked,
  2102.     setBlock = setBlock,
  2103.     clearBlock = clearBlock,
  2104.     getCorners = getCorners,
  2105.     getClosestCorner = getClosestCorner
  2106. }
  2107.  
  2108. end)
  2109. __bundle_register("dig.next-point", function(require, _LOADED, __bundle_register, __bundle_modules)
  2110. local Vector = require("elements.vector")
  2111. local World = require("geo.world")
  2112.  
  2113. ---@param world World
  2114. ---@param start Vector
  2115. local function isStartInBottomPlane(world, start)
  2116.     if World.isInBottomPlane(world, start) then
  2117.         return true
  2118.     elseif World.isInTopPlane(world, start) then
  2119.         return false
  2120.     else
  2121.         error("start must be in either world bottom or top plane")
  2122.     end
  2123. end
  2124.  
  2125. ---@param world World
  2126. ---@param layerHeight integer
  2127. ---@return integer
  2128. function getNumLayers(world, layerHeight)
  2129.     return math.ceil(world.height / layerHeight)
  2130. end
  2131.  
  2132. ---@param point Vector
  2133. ---@param world World
  2134. ---@param start Vector
  2135. ---@param layerHeight integer
  2136. ---@return integer
  2137. function getCurrentLayer(point, world, start, layerHeight)
  2138.     if layerHeight >= world.height then
  2139.         return 1
  2140.     end
  2141.  
  2142.     if isStartInBottomPlane(world, start) then
  2143.         return math.floor((point.y - world.y) / layerHeight) + 1
  2144.     else
  2145.         return math.floor(((world.y + world.height - 1) - point.y) / layerHeight) + 1
  2146.     end
  2147. end
  2148.  
  2149. ---@param layer integer
  2150. ---@param world World
  2151. ---@param start Vector
  2152. ---@param layerHeight integer
  2153. ---@return integer
  2154. function getLayerTargetY(layer, world, start, layerHeight)
  2155.     if isStartInBottomPlane(world, start) then
  2156.         local targetY = math.floor(layerHeight / 2) + ((layer - 1) * layerHeight) + world.y
  2157.  
  2158.         if not World.isInBoundsY(world, targetY) then
  2159.             return world.y + world.height - 1
  2160.         else
  2161.             return targetY
  2162.         end
  2163.     else
  2164.         local targetY = (world.y + world.height - 1) - math.floor(layerHeight / 2) + ((layer - 1) * layerHeight)
  2165.  
  2166.         if not World.isInBoundsY(world, targetY) then
  2167.             return world.y
  2168.         else
  2169.             return targetY
  2170.         end
  2171.     end
  2172. end
  2173.  
  2174. ---@param point Vector
  2175. ---@param world World
  2176. ---@param start Vector
  2177. ---@param layerHeight? integer
  2178. ---@return Vector?
  2179. return function(point, world, start, layerHeight)
  2180.     layerHeight = layerHeight or 3
  2181.  
  2182.     -- first we try to move to the correct y position, based on which layer we are in
  2183.     local currentLayer = getCurrentLayer(point, world, start, layerHeight)
  2184.     local targetY = getLayerTargetY(currentLayer, world, start, layerHeight)
  2185.  
  2186.     if point.y ~= targetY then
  2187.         if isStartInBottomPlane(world, start) then
  2188.             return Vector.plus(point, Vector.create(0, 1, 0))
  2189.         else
  2190.             return Vector.plus(point, Vector.create(0, -1, 0))
  2191.         end
  2192.     end
  2193.  
  2194.     -- then we snake through the plane for digging
  2195.     local delta = Vector.create(0, 0, 0)
  2196.  
  2197.     if start.x == world.x then
  2198.         delta.x = 1
  2199.     elseif start.x == world.x + world.width - 1 then
  2200.         delta.x = -1
  2201.     end
  2202.  
  2203.     if start.z == world.z then
  2204.         delta.z = 1
  2205.     elseif start.z == world.z + world.depth - 1 then
  2206.         delta.z = -1
  2207.     end
  2208.  
  2209.     if world.width > world.depth then
  2210.         local relative = Vector.minus(point, start)
  2211.  
  2212.         if relative.z % 2 == 1 then
  2213.             delta.x = delta.x * -1
  2214.         end
  2215.  
  2216.         if (currentLayer - 1) % 2 == 1 then
  2217.             delta.x = delta.x * -1
  2218.             delta.z = delta.z * -1
  2219.         end
  2220.  
  2221.         if World.isInBoundsX(world, point.x + delta.x) then
  2222.             return Vector.plus(point, Vector.create(delta.x, 0, 0))
  2223.         elseif World.isInBoundsZ(world, point.z + delta.z) then
  2224.             return Vector.plus(point, Vector.create(0, 0, delta.z))
  2225.         end
  2226.     else
  2227.         local relative = Vector.minus(point, start)
  2228.  
  2229.         if relative.x % 2 == 1 then
  2230.             delta.z = delta.z * -1
  2231.         end
  2232.  
  2233.         if (currentLayer - 1) % 2 == 1 then
  2234.             delta.x = delta.x * -1
  2235.             delta.z = delta.z * -1
  2236.         end
  2237.  
  2238.         if World.isInBoundsZ(world, point.z + delta.z) then
  2239.             return Vector.plus(point, Vector.create(0, 0, delta.z))
  2240.         elseif World.isInBoundsX(world, point.x + delta.x) then
  2241.             return Vector.plus(point, Vector.create(delta.x, 0, 0))
  2242.         end
  2243.     end
  2244.  
  2245.     -- after that, advance to the next layer, or stop if we are at the max layer
  2246.     if currentLayer == getNumLayers(world, layerHeight) then
  2247.         return nil
  2248.     elseif isStartInBottomPlane(world, start) then
  2249.         if World.isInBoundsY(world, point.y + 1) then
  2250.             return Vector.plus(point, Vector.create(0, 1, 0))
  2251.         end
  2252.     else
  2253.         if World.isInBoundsY(world, point.y - 1) then
  2254.             return Vector.plus(point, Vector.create(0, -1, 0))
  2255.         end
  2256.     end
  2257. end
  2258.  
  2259. end)
  2260. __bundle_register("squirtle.face", function(require, _LOADED, __bundle_register, __bundle_modules)
  2261. local getState = require("squirtle.get-state")
  2262. local turn = require("squirtle.turn")
  2263.  
  2264. ---@param target integer
  2265. ---@param current? integer
  2266. return function(target, current)
  2267.     local state = getState()
  2268.     current = current or state.facing
  2269.  
  2270.     if not current then
  2271.         error("facing not available")
  2272.     end
  2273.  
  2274.     if (current + 2) % 4 == target then
  2275.         turn("back")
  2276.     elseif (current + 1) % 4 == target then
  2277.         turn("right")
  2278.     elseif (current - 1) % 4 == target then
  2279.         turn("left")
  2280.     end
  2281.  
  2282.     return target
  2283. end
  2284.  
  2285. end)
  2286. __bundle_register("squirtle.navigate", function(require, _LOADED, __bundle_register, __bundle_modules)
  2287. local Cardinal = require("elements.cardinal")
  2288. local Vector = require("elements.vector")
  2289. local findPath = require("geo.find-path")
  2290. local locate = require("squirtle.locate")
  2291. local orientate = require("squirtle.orientate")
  2292. local World = require("geo.world")
  2293. local inspect = require("squirtle.inspect")
  2294. local dig = require("squirtle.dig")
  2295. local refuel = require("squirtle.refuel")
  2296. local moveToPoint = require("squirtle.move.to-point")
  2297.  
  2298. ---@param path Vector[]
  2299. ---@return boolean, string?, integer?
  2300. local function walkPath(path)
  2301.     for i, next in ipairs(path) do
  2302.         local success, failedSide = moveToPoint(next)
  2303.  
  2304.         if not success then
  2305.             return false, failedSide, i
  2306.         end
  2307.     end
  2308.  
  2309.     return true
  2310. end
  2311.  
  2312. ---@param to Vector
  2313. ---@param world? World
  2314. ---@param breakable? function
  2315. return function(to, world, breakable)
  2316.     -- [todo] remove breakable() in favor of options;
  2317.     -- also we could pass them along to walkPath(),
  2318.     -- which then could progress further before failing
  2319.     -- because it could now dig stuff
  2320.     breakable = breakable or function(...)
  2321.         return false
  2322.     end
  2323.  
  2324.     if not world then
  2325.         local position = locate()
  2326.         world = World.create(position.x, position.y, position.z)
  2327.     end
  2328.  
  2329.     local from, facing = orientate()
  2330.  
  2331.     while true do
  2332.         local path, msg = findPath(from, to, facing, world)
  2333.  
  2334.         if not path then
  2335.             return false, msg
  2336.         end
  2337.  
  2338.         local distance = Vector.manhattan(from, to)
  2339.         refuel(distance)
  2340.         local success, failedSide = walkPath(path)
  2341.  
  2342.         if success then
  2343.             return true
  2344.         elseif failedSide then
  2345.             from, facing = orientate()
  2346.             local block = inspect(failedSide)
  2347.             local scannedLocation = Vector.plus(from, Cardinal.toVector(Cardinal.fromSide(failedSide, facing)))
  2348.  
  2349.             if block and breakable(block) then
  2350.                 dig(failedSide)
  2351.             elseif block then
  2352.                 World.setBlock(world, scannedLocation)
  2353.             else
  2354.                 error("could not move, not sure why")
  2355.             end
  2356.         end
  2357.     end
  2358. end
  2359.  
  2360. end)
  2361. __bundle_register("squirtle.move.to-point", function(require, _LOADED, __bundle_register, __bundle_modules)
  2362. local Vector = require("elements.vector")
  2363. local Cardinal = require("elements.cardinal")
  2364. local locate = require("squirtle.locate")
  2365. local move = require("squirtle.move")
  2366. local face = require("squirtle.face")
  2367.  
  2368. ---@param target Vector
  2369. ---@return boolean, string?
  2370. return function(target)
  2371.     local delta = Vector.minus(target, locate())
  2372.  
  2373.     if delta.y > 0 then
  2374.         if not move("top", delta.y) then
  2375.             return false, "top"
  2376.         end
  2377.     elseif delta.y < 0 then
  2378.         if not move("bottom", -delta.y) then
  2379.             return false, "bottom"
  2380.         end
  2381.     end
  2382.  
  2383.     if delta.x > 0 then
  2384.         face(Cardinal.east)
  2385.         if not move("front", delta.x) then
  2386.             return false, "front"
  2387.         end
  2388.     elseif delta.x < 0 then
  2389.         face(Cardinal.west)
  2390.         if not move("front", -delta.x) then
  2391.             return false, "front"
  2392.         end
  2393.     end
  2394.  
  2395.     if delta.z > 0 then
  2396.         face(Cardinal.south)
  2397.         if not move("front", delta.z) then
  2398.             return false, "front"
  2399.         end
  2400.     elseif delta.z < 0 then
  2401.         face(Cardinal.north)
  2402.         if not move("front", -delta.z) then
  2403.             return false, "front"
  2404.         end
  2405.     end
  2406.  
  2407.     return true
  2408. end
  2409.  
  2410. end)
  2411. __bundle_register("squirtle.move", function(require, _LOADED, __bundle_register, __bundle_modules)
  2412. local Vector = require("elements.vector")
  2413. local Cardinal = require("elements.cardinal")
  2414. local getState = require("squirtle.get-state")
  2415. local changeState = require("squirtle.change-state")
  2416. local Fuel = require("squirtle.fuel")
  2417. local refuel = require("squirtle.refuel")
  2418. local dig = require("squirtle.dig")
  2419. local turn = require("squirtle.turn")
  2420.  
  2421. local natives = {
  2422.     top = turtle.up,
  2423.     up = turtle.up,
  2424.     front = turtle.forward,
  2425.     forward = turtle.forward,
  2426.     bottom = turtle.down,
  2427.     down = turtle.down,
  2428.     back = turtle.back
  2429. }
  2430.  
  2431. -- [todo] move() should not call refuel like this. instead, a refueler interface should be provided.
  2432. -- if no fuel could be acquired via the refueler, and there is not enough fuel, error out.
  2433. ---@param side? string
  2434. ---@param times? integer
  2435. ---@param breakBlocks? boolean
  2436. ---@return boolean, string?, integer?
  2437. return function(side, times, breakBlocks)
  2438.     side = side or "front"
  2439.     local handler = natives[side]
  2440.  
  2441.     if not handler then
  2442.         error(string.format("move() does not support side %s", side))
  2443.     end
  2444.  
  2445.     times = times or 1
  2446.  
  2447.     if not Fuel.hasFuel(times) then
  2448.         refuel(times)
  2449.     end
  2450.  
  2451.     local state = getState()
  2452.     local delta
  2453.  
  2454.     if state.position and state.facing then
  2455.         position = state.position
  2456.         delta = Cardinal.toVector(Cardinal.fromSide(side, state.facing))
  2457.     end
  2458.  
  2459.     for step = 1, times do
  2460.         local success, message = handler()
  2461.  
  2462.         if not success then
  2463.             if breakBlocks then
  2464.                 if side == "back" then
  2465.                     turn("back")
  2466.                     success = dig()
  2467.                     turn("back")
  2468.                 else
  2469.                     success = dig(side)
  2470.                 end
  2471.  
  2472.                 if not success then
  2473.                     return false, "failed to dig", step
  2474.                 else
  2475.                     -- todo: make sure moving again was actually successful
  2476.                     handler()
  2477.                 end
  2478.             else
  2479.                 return false, message, step
  2480.             end
  2481.         end
  2482.  
  2483.         if delta then
  2484.             position = Vector.plus(position, delta)
  2485.             changeState({position = position})
  2486.         end
  2487.     end
  2488.  
  2489.     return true
  2490. end
  2491.  
  2492. end)
  2493. __bundle_register("squirtle.locate", function(require, _LOADED, __bundle_register, __bundle_modules)
  2494. local Vector = require("elements.vector")
  2495. local getState = require("squirtle.get-state")
  2496. local changeState = require("squirtle.change-state")
  2497.  
  2498. ---@param refresh? boolean
  2499. return function(refresh)
  2500.     local state = getState()
  2501.     local position = state.position
  2502.  
  2503.     if refresh or not position then
  2504.         local x, y, z = gps.locate()
  2505.  
  2506.         if not x then
  2507.             error("no gps available")
  2508.         end
  2509.  
  2510.         position = Vector.create(x, y, z)
  2511.         changeState({position = position})
  2512.     end
  2513.  
  2514.     return position
  2515. end
  2516.  
  2517. end)
  2518. __bundle_register("squirtle.inspect", function(require, _LOADED, __bundle_register, __bundle_modules)
  2519. local indexOf = require("utils.index-of")
  2520.  
  2521. local natives = {
  2522.     top = turtle.inspectUp,
  2523.     up = turtle.inspectUp,
  2524.     front = turtle.inspect,
  2525.     forward = turtle.inspect,
  2526.     bottom = turtle.inspectDown,
  2527.     down = turtle.inspectDown
  2528. }
  2529.  
  2530. ---@param side? string
  2531. ---@param name? table|string
  2532. ---@return Block? block
  2533. return function(side, name)
  2534.     side = side or "front"
  2535.     local handler = natives[side]
  2536.  
  2537.     if not handler then
  2538.         error(string.format("inspect() does not support side %s", side))
  2539.     end
  2540.  
  2541.     local success, block = handler()
  2542.  
  2543.     if success then
  2544.         if name then
  2545.             if type(name) == "string" and block.name == name then
  2546.                 return block
  2547.             elseif type(name) == "table" and indexOf(name, block.name) > 0 then
  2548.                 return block
  2549.             else
  2550.                 return nil
  2551.             end
  2552.         end
  2553.  
  2554.         return block
  2555.     else
  2556.         return nil
  2557.     end
  2558. end
  2559.  
  2560. end)
  2561. __bundle_register("squirtle.orientate", function(require, _LOADED, __bundle_register, __bundle_modules)
  2562. local Vector = require("elements.vector")
  2563. local Side = require("elements.side")
  2564. local Cardinal = require("elements.cardinal")
  2565. local getState = require("squirtle.get-state")
  2566. local changeState = require("squirtle.change-state")
  2567. local locate = require("squirtle.locate")
  2568. local refuel = require("squirtle.refuel")
  2569. local move = require("squirtle.move")
  2570. local turn = require("squirtle.turn")
  2571.  
  2572. ---@param side string
  2573. ---@param position Vector
  2574. local function stepOut(side, position)
  2575.     refuel(2)
  2576.  
  2577.     if not move(side) then
  2578.         return false
  2579.     end
  2580.  
  2581.     local now = locate(true)
  2582.     local cardinal = Cardinal.fromVector(Vector.minus(now, position))
  2583.     local facing = Cardinal.rotate(cardinal, side)
  2584.  
  2585.     changeState({facing = facing})
  2586.  
  2587.     while not move(Side.rotateAround(side)) do
  2588.         print("can't move back, something is blocking me. sleeping 1s...")
  2589.         os.sleep(1)
  2590.     end
  2591.  
  2592.     return true
  2593. end
  2594.  
  2595. ---@param position Vector
  2596. local function orientateSameLayer(position)
  2597.     -- [todo] note to self: i purposely turn first cause it looks nicer. especially
  2598.     -- when it moves back, then forward again. looks like a little dance.
  2599.     -- what i wrote this note actually for: allow for different styles and randomization
  2600.     -- [todo#2] well, good i wrote this note. because i was surprised that the turtle
  2601.     -- doesn't first try simply moving forward, and i was wondering why.
  2602.     turn("right")
  2603.     local success = stepOut("back", position) or stepOut("front", position)
  2604.     turn("left")
  2605.  
  2606.     if not success then
  2607.         success = stepOut("back", position) or stepOut("front", position)
  2608.     end
  2609.  
  2610.     return success
  2611. end
  2612.  
  2613. ---@param refresh? boolean
  2614. ---@return Vector position, integer facing
  2615. return function(refresh)
  2616.     local state = getState()
  2617.     local position = locate(refresh)
  2618.     local facing = state.facing
  2619.  
  2620.     if refresh or not facing then
  2621.         if not orientateSameLayer(position) then
  2622.             error("failed to orientate. possibly blocked in.")
  2623.         end
  2624.     end
  2625.  
  2626.     return locate(), getState().facing
  2627. end
  2628.  
  2629. end)
  2630. __bundle_register("geo.find-path", function(require, _LOADED, __bundle_register, __bundle_modules)
  2631. local World = require("geo.world")
  2632. local Cardinal = require("elements.cardinal")
  2633. local Vector = require("elements.vector")
  2634.  
  2635. ---@param hierarchy Vector[]
  2636. ---@param start Vector
  2637. ---@param goal Vector
  2638. local function toPath(hierarchy, start, goal)
  2639.     local path = {}
  2640.     local next = goal
  2641.  
  2642.     while not Vector.equals(next, start) do
  2643.         table.insert(path, 1, next)
  2644.         next = hierarchy[Vector.toString(next)]
  2645.     end
  2646.  
  2647.     return path
  2648. end
  2649.  
  2650. ---@param open Vector[]
  2651. ---@param naturals Vector[]
  2652. ---@param forced Vector[]
  2653. ---@param pruned Vector[]
  2654. ---@param totalCost number[]
  2655. local function findBest(open, naturals, forced, pruned, totalCost)
  2656.     local lowestF = nil
  2657.     local bestType = nil
  2658.  
  2659.     ---@type Vector
  2660.     local best = nil
  2661.  
  2662.     for key, value in pairs(open) do
  2663.         local type = 3
  2664.  
  2665.         if naturals[key] then
  2666.             type = 0
  2667.         elseif forced[key] then
  2668.             type = 1
  2669.         elseif pruned[key] then
  2670.             type = 2
  2671.         end
  2672.  
  2673.         if lowestF == nil or totalCost[key] < lowestF or (totalCost[key] <= lowestF and type < bestType) then
  2674.             best = value
  2675.             lowestF = totalCost[key]
  2676.             bestType = type
  2677.         end
  2678.     end
  2679.  
  2680.     return best
  2681. end
  2682.  
  2683. ---@param start Vector
  2684. ---@param goal Vector
  2685. ---@param orientation integer
  2686. ---@param world? World
  2687. return function(start, goal, orientation, world)
  2688.     if not world then
  2689.         world = World.create(start.x, start.y, start.z)
  2690.     end
  2691.  
  2692.     if World.isBlocked(world, goal) then
  2693.         return false, "goal is blocked"
  2694.     end
  2695.  
  2696.     ---@type Vector[]
  2697.     local open = {}
  2698.     local numOpen = 0
  2699.     local closed = {}
  2700.  
  2701.     ---@type Vector[]
  2702.     local reverseMap = {}
  2703.     local pastCost = {}
  2704.     local futureCost = {}
  2705.     local totalCost = {}
  2706.     local startKey = Vector.toString(start)
  2707.     local naturals = {}
  2708.     local forced = {}
  2709.     local pruned = {}
  2710.  
  2711.     open[startKey] = start
  2712.     pastCost[startKey] = 0
  2713.     futureCost[startKey] = Vector.manhattan(start, goal)
  2714.     totalCost[startKey] = pastCost[startKey] + futureCost[startKey]
  2715.     numOpen = numOpen + 1
  2716.  
  2717.     local cycles = 0
  2718.     local timeStarted = os.clock()
  2719.     local timeout = 3
  2720.  
  2721.     while (numOpen > 0) do
  2722.         cycles = cycles + 1
  2723.  
  2724.         if cycles % 100 == 0 then
  2725.             if os.clock() - timeStarted >= timeout then
  2726.                 return false, timeout .. "s timeout reached"
  2727.             end
  2728.  
  2729.             -- [todo] make event name more unique to prevent collisions?
  2730.             os.queueEvent("a-star-progress")
  2731.             os.pullEvent("a-star-progress")
  2732.         end
  2733.  
  2734.         local current = findBest(open, naturals, forced, pruned, totalCost)
  2735.  
  2736.         if Vector.equals(current, goal) then
  2737.             return toPath(reverseMap, start, goal)
  2738.         end
  2739.  
  2740.         local currentKey = Vector.toString(current)
  2741.  
  2742.         open[currentKey] = nil
  2743.         closed[currentKey] = current
  2744.         numOpen = numOpen - 1
  2745.  
  2746.         if reverseMap[currentKey] then
  2747.             local delta = Vector.minus(current, reverseMap[currentKey])
  2748.             orientation = Cardinal.fromVector(delta)
  2749.         end
  2750.  
  2751.         local neighbours = {}
  2752.  
  2753.         for i = 0, 5 do
  2754.             local neighbour = Vector.plus(current, Cardinal.toVector(i))
  2755.             local neighbourKey = Vector.toString(neighbour)
  2756.             local requiresTurn = false
  2757.  
  2758.             if i ~= orientation and not Cardinal.isVertical(i) then
  2759.                 requiresTurn = true
  2760.             end
  2761.  
  2762.             if closed[neighbourKey] == nil and World.isInBounds(world, neighbour) and
  2763.                 not World.isBlocked(world, neighbour) then
  2764.                 local tentativePastCost = pastCost[currentKey] + 1
  2765.  
  2766.                 if (requiresTurn) then
  2767.                     tentativePastCost = tentativePastCost + 1
  2768.                 end
  2769.  
  2770.                 if open[neighbourKey] == nil or tentativePastCost < pastCost[neighbourKey] then
  2771.                     pastCost[neighbourKey] = tentativePastCost
  2772.  
  2773.                     local neighbourFutureCost = Vector.manhattan(neighbour, goal)
  2774.  
  2775.                     if (neighbour.x ~= goal.x) then
  2776.                         neighbourFutureCost = neighbourFutureCost + 1
  2777.                     end
  2778.                     if (neighbour.z ~= goal.z) then
  2779.                         neighbourFutureCost = neighbourFutureCost + 1
  2780.                     end
  2781.                     if (neighbour.y ~= goal.y) then
  2782.                         neighbourFutureCost = neighbourFutureCost + 1
  2783.                     end
  2784.  
  2785.                     futureCost[neighbourKey] = neighbourFutureCost
  2786.                     totalCost[neighbourKey] = pastCost[neighbourKey] + neighbourFutureCost
  2787.                     reverseMap[neighbourKey] = current
  2788.  
  2789.                     if (open[neighbourKey] == nil) then
  2790.                         open[neighbourKey] = neighbour
  2791.                         neighbours[i] = neighbour
  2792.                         numOpen = numOpen + 1
  2793.                     end
  2794.                 end
  2795.             end
  2796.         end
  2797.  
  2798.         -- pruning
  2799.         if (reverseMap[currentKey] ~= nil) then
  2800.             for neighbourOrientation, neighbour in pairs(neighbours) do
  2801.                 local neighbourKey = Vector.toString(neighbour)
  2802.  
  2803.                 if (neighbourOrientation == orientation) then
  2804.                     -- add natural neighbour
  2805.                     naturals[neighbourKey] = neighbour
  2806.                 else
  2807.                     -- check blockade
  2808.                     local check = Vector.plus(reverseMap[currentKey], Vector.minus(
  2809.                                                   Cardinal.toVector(neighbourOrientation),
  2810.                                                   Cardinal.toVector(orientation)))
  2811.  
  2812.                     -- if (world[checkKey] == nil) then
  2813.                     if not World.isBlocked(world, check) then
  2814.                         -- add neighbour to prune
  2815.                         pruned[neighbourKey] = neighbour
  2816.                     else
  2817.                         -- add neighbour to forced
  2818.                         forced[neighbourKey] = neighbour
  2819.                     end
  2820.                 end
  2821.             end
  2822.         end
  2823.     end
  2824.  
  2825.     return false, "no path available"
  2826. end
  2827.  
  2828. end)
  2829. return __bundle_require("__root")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement