jBlume

Advanced Mine Program

Jun 25th, 2023 (edited)
134
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. local helper = {}
  2. --#region CONFIG----
  3. local cfg = {}
  4. cfg.localConfig = {
  5.     --if true, the program will attempt to download and use the config from remoteConfigPath
  6.     --useful if you have many turtles and you don't want to change the config of each one manually
  7.     useRemoteConfig = false,
  8.     remoteConfigPath = "http://localhost:33344/config.lua",
  9.     --this command will be used when the program is started as "mine def" (mineLoop overrides this command)
  10.     defaultCommand = "cube 3 3 8",
  11.     --false: build walls/floor/ceiling everywhere, true: only where there is fluid
  12.     plugFluidsOnly = true,
  13.     --maximum taxicab distance from enterance point when collecting ores, 0 = disable ore traversal
  14.     oreTraversalRadius = 0,
  15.     --layer mining order, use "z" for branch mining, "y" for anything else
  16.     --"y" - mine top to bottom layer by layer, "z" - mine forward in vertical slices
  17.     layerSeparationAxis="y",
  18.     --false: use regular chests, true: use entangled chests
  19.     --if true, the turtle will place a single entangled chest to drop off items and break it afterwards.
  20.     --tested with chests from https://www.curseforge.com/minecraft/mc-mods/kibe
  21.     useEntangledChests = false,
  22.     --false: refuel from inventory, true: refuel from (a different) entangled chest
  23.     --if true, the turtle won't store any coal. Instead, when refueling, it will place the entangled chest, grab fuel from it, refuel, and then break the chest.
  24.     useFuelEntangledChest = false,
  25.     --true: use two chuck loaders to mine indefinitely without moving into unloaded chunks.
  26.     --This doesn't work with chunk loaders from https://www.curseforge.com/minecraft/mc-mods/kibe, but might work with some other mod.
  27.     --After an area is mined, the turtle will shift by mineLoopOffset and execute mineLoopCommand
  28.     --mineLoopCommand is used in place of defaultCommand when launching as "mine def"
  29.     mineLoop = false,
  30.     mineLoopOffset = {x=0, y=0, z=8},
  31.     mineLoopCommand = "rcube 1 1 1"
  32. }
  33.  
  34. cfg.getRemoteConfig = function(remotePath)
  35.     local handle = http.get(remotePath)
  36.     if not handle then
  37.         helper.printError("Server not responding, using local")
  38.         return nil
  39.     end
  40.     local data = handle.readAll()
  41.     handle.close()
  42.     local deser = textutils.unserialise(data)
  43.     if not deser then
  44.         helper.printError("Couldn't parse remote config, using local")
  45.         return nil
  46.     end
  47.     for key,_ in pairs(cfg.localConfig) do
  48.         if deser[key] == nil then
  49.             helper.printError("No key", key, "in remote config, using local")
  50.             return nil
  51.         end
  52.     end
  53.     return deser
  54. end
  55.  
  56. cfg.processConfig = function()
  57.     local config = cfg.localConfig
  58.     if cfg.localConfig.useRemoteConfig then
  59.         helper.print("Downloading config..")
  60.         local remoteConfig = cfg.getRemoteConfig(config.remoteConfigPath)
  61.         config = remoteConfig or cfg.localConfig
  62.     end
  63.     return config
  64. end
  65.  
  66. --#endregion
  67. --#region CONSTANTS----
  68.  
  69. local SOUTH = 0
  70. local WEST = 1
  71. local NORTH = 2
  72. local EAST = 3
  73.  
  74. local CHEST_SLOT = "chest_slot"
  75. local BLOCK_SLOT = "block_slot"
  76. local FUEL_SLOT = "fuel_slot"
  77. local FUEL_CHEST_SLOT = "fuel_chest_slot"
  78. local CHUNK_LOADER_SLOT = "chunk_loader_slot"
  79. local MISC_SLOT = "misc_slot"
  80.  
  81. local ACTION = 0
  82. local TASK = 1
  83.  
  84. local PATHFIND_INSIDE_AREA = 0
  85. local PATHFIND_OUTSIDE_AREA = 1
  86. local PATHFIND_INSIDE_NONPRESERVED_AREA = 2
  87. local PATHFIND_ANYWHERE_NONPRESERVED = 3
  88.  
  89. local SUCCESS = 0
  90. local FAILED_NONONE_COMPONENTCOUNT = 1
  91. local FAILED_TURTLE_NOTINREGION = 2
  92. local FAILED_REGION_EMPTY = 4
  93.  
  94. local REFUEL_THRESHOLD = 500
  95. local RETRY_DELAY = 3
  96.  
  97. local FALLING_BLOCKS = {
  98.     ["minecraft:gravel"] = true,
  99.     ["minecraft:sand"] = true
  100. }
  101.  
  102. --#endregion
  103. --#region WRAPPER----
  104.  
  105. local wrapt = (function()
  106.     local self = {
  107.         selectedSlot = 1,
  108.         direction = SOUTH,
  109.         x = 0,
  110.         y = 0,
  111.         z = 0
  112.     }
  113.  
  114.     local public = {}
  115.  
  116.     --wrap everything in turtle
  117.     for key,value in pairs(turtle) do
  118.         public[key] = value
  119.     end
  120.     --init turtle selected slot
  121.     turtle.select(self.selectedSlot);
  122.  
  123.     public.select = function(slot)
  124.         if self.selectedSlot ~= slot then
  125.             turtle.select(slot)
  126.             self.selectedSlot = slot
  127.         end
  128.     end
  129.  
  130.     public.forward = function()
  131.         local success = turtle.forward()
  132.         if not success then
  133.             return success
  134.         end
  135.         if self.direction == EAST then
  136.             self.x = self.x + 1
  137.         elseif self.direction == WEST then
  138.             self.x = self.x - 1
  139.         elseif self.direction == SOUTH then
  140.             self.z = self.z + 1
  141.         elseif self.direction == NORTH then
  142.             self.z = self.z - 1
  143.         end
  144.         return success
  145.     end
  146.  
  147.     public.up = function()
  148.         local success = turtle.up()
  149.         if not success then
  150.             return success
  151.         end
  152.         self.y = self.y + 1
  153.         return success
  154.     end
  155.  
  156.     public.down = function()
  157.         local success = turtle.down()
  158.         if not success then
  159.             return success
  160.         end
  161.         self.y = self.y - 1
  162.         return success
  163.     end
  164.  
  165.     public.turnRight = function()
  166.         local success = turtle.turnRight()
  167.         if not success then
  168.             return success
  169.         end
  170.         self.direction = self.direction + 1
  171.         if self.direction > 3 then
  172.             self.direction = 0
  173.         end
  174.         return success
  175.     end
  176.  
  177.     public.turnLeft = function()
  178.         local success = turtle.turnLeft()
  179.         if not success then
  180.             return success
  181.         end
  182.         self.direction = self.direction - 1
  183.         if self.direction < 0 then
  184.             self.direction = 3
  185.         end
  186.         return success
  187.     end
  188.  
  189.     public.getX = function() return self.x end
  190.     public.getY = function() return self.y end
  191.     public.getZ = function() return self.z end
  192.     public.getDirection = function() return self.direction end
  193.     public.getPosition = function() return {x = self.x, y = self.y, z = self.z, direction = self.direction} end
  194.  
  195.     return public
  196. end)()
  197.  
  198. --#endregion
  199. --#region DEBUG FUNCTIONS----
  200.  
  201. local debug = {
  202.     dumpPoints = function(points, filename)
  203.         local file = fs.open(filename .. ".txt","w")
  204.         for key, value in pairs(points) do
  205.             local line =
  206.                 tostring(value.x)..","..
  207.                 tostring(value.y)..","..
  208.                 tostring(value.z)..","
  209.             if value.adjacent then
  210.                 line = line .. "0,128,0"
  211.             elseif value.inacc then
  212.                 line = line .. "255,0,0"
  213.             elseif value.triple then
  214.                 line = line .. "0,255,0"
  215.             elseif value.checkedInFirstPass then
  216.                 line = line .. "0,0,0"
  217.             else
  218.                 helper.printError("Invalid block type when dumping points")
  219.             end
  220.  
  221.             if math.abs(value.z) < 100 then
  222.                 file.writeLine(line)
  223.             end
  224.         end
  225.         file.close()
  226.     end,
  227.  
  228.     dumpPath = function(points, filename)
  229.         local file = fs.open(filename .. ".txt","w")
  230.         for key, value in pairs(points) do
  231.             if tonumber(key) then
  232.                 local line =
  233.                     tostring(value.x)..","..
  234.                     tostring(value.y)..","..
  235.                     tostring(value.z)
  236.                 file.writeLine(line)
  237.             end
  238.         end
  239.         file.close()
  240.     end,
  241.  
  242.     dumpLayers = function(layers, filename)
  243.         for index, layer in ipairs(layers) do
  244.             local file = fs.open(filename .. tostring(index) .. ".txt","w")
  245.             for _, value in ipairs(layer) do
  246.                 local line =
  247.                     tostring(value.x)..","..
  248.                     tostring(value.y)..","..
  249.                     tostring(value.z)..","
  250.                 if value.adjacent then
  251.                     line = line .. "0,128,0"
  252.                 elseif value.inacc then
  253.                     line = line .. "255,0,0"
  254.                 elseif value.triple then
  255.                     line = line .. "0,255,0"
  256.                 elseif value.checkedInFirstPass then
  257.                     line = line .. "0,0,0"
  258.                 else
  259.                     helper.printError("Invalid block type when dumping layers")
  260.                 end
  261.                 file.writeLine(line)
  262.             end
  263.             file.close()
  264.         end
  265.     end
  266. }
  267.  
  268. --#endregion
  269. --#region HELPER FUNCTIONS----
  270.  
  271. helper.deltaToDirection = function(dX, dZ)
  272.     if dX > 0 then
  273.         return EAST
  274.     elseif dX < 0 then
  275.         return WEST
  276.     elseif dZ > 0 then
  277.         return SOUTH
  278.     elseif dZ < 0 then
  279.         return NORTH
  280.     end
  281.     error("Invalid delta", 2)
  282. end
  283.  
  284. helper.tableLength = function(T)
  285.     local count = 0
  286.     for _ in pairs(T) do count = count + 1 end
  287.     return count
  288. end
  289.  
  290. helper.getIndex = function(x, y, z)
  291.     return tostring(x) .. "," .. tostring(y) .. "," .. tostring(z)
  292. end
  293.  
  294. helper.isPosEqual = function(pos1, pos2)
  295.     return pos1.x == pos2.x and pos1.y == pos2.y and pos1.z == pos2.z
  296. end
  297.  
  298. helper.getSurroundings = function(pos)
  299.     return {
  300.         [helper.getIndex(pos.x + 1, pos.y, pos.z)] = {x = pos.x + 1, y = pos.y, z = pos.z},
  301.         [helper.getIndex(pos.x - 1, pos.y, pos.z)] = {x = pos.x - 1, y = pos.y, z = pos.z},
  302.         [helper.getIndex(pos.x, pos.y + 1, pos.z)] = {x = pos.x, y = pos.y + 1, z = pos.z},
  303.         [helper.getIndex(pos.x, pos.y - 1, pos.z)] = {x = pos.x, y = pos.y - 1, z = pos.z},
  304.         [helper.getIndex(pos.x, pos.y, pos.z + 1)] = {x = pos.x, y = pos.y, z = pos.z + 1},
  305.         [helper.getIndex(pos.x, pos.y, pos.z - 1)] = {x = pos.x, y = pos.y, z = pos.z - 1}
  306.     }
  307. end
  308.  
  309. helper.getForwardPos = function(currentPos)
  310.     local newPos = {x = currentPos.x, y = currentPos.y, z = currentPos.z}
  311.     if currentPos.direction == EAST then
  312.         newPos.x = newPos.x + 1
  313.     elseif currentPos.direction == WEST then
  314.         newPos.x = newPos.x - 1
  315.     elseif currentPos.direction == SOUTH then
  316.         newPos.z = newPos.z + 1
  317.     elseif currentPos.direction == NORTH then
  318.         newPos.z = newPos.z - 1
  319.     end
  320.     return newPos
  321. end
  322.  
  323. helper.distance = function(from, to)
  324.     return math.abs(from.x - to.x) + math.abs(from.y - to.y) + math.abs(from.z - to.z)
  325. end
  326.  
  327. helper.sign = function(number)
  328.     return number > 0 and 1 or (number == 0 and 0 or -1)
  329. end
  330.  
  331. helper.inRange = function(number, a, b)
  332.     return number >= a and number <= b
  333. end
  334.  
  335. helper.splitString = function(string, separator)
  336.     if not separator then separator = "%s" end
  337.     local split = {}
  338.     for str in string.gmatch(string, "([^"..separator.."]+)") do
  339.         table.insert(split, str)
  340.     end
  341.     return split
  342. end
  343.  
  344. helper.stringEndsWith = function (str, ending)
  345.     return ending == "" or str:sub(-#ending) == ending
  346. end
  347.  
  348. helper.tableContains = function(table, element, comparison)
  349.     for _, value in pairs(table) do
  350.         if comparison(value, element) then
  351.             return true
  352.         end
  353.     end
  354.     return false
  355. end
  356.  
  357. helper.readOnlyTable = function(t)
  358.     local mt = {
  359.         __index = t,
  360.         __newindex = function(t,k,v)
  361.             error("Cannot write into a read-only table", 2)
  362.         end
  363.     }
  364.     local proxy = {}
  365.     setmetatable(proxy, mt)
  366.     return proxy
  367. end
  368.  
  369. helper.osYield = function()
  370. ---@diagnostic disable-next-line: undefined-field
  371.     os.queueEvent("fakeEvent");
  372. ---@diagnostic disable-next-line: undefined-field
  373.     os.pullEvent();
  374. end
  375.  
  376. helper.printError = function(...)
  377.     term.setTextColor(colors.red)
  378.     print(...)
  379. end
  380.  
  381. helper.printWarning = function(...)
  382.     term.setTextColor(colors.yellow)
  383.     print(...)
  384. end
  385.  
  386. helper.print = function(...)
  387.     term.setTextColor(colors.white)
  388.     print(...)
  389. end
  390.  
  391. helper.write = function(...)
  392.     term.setTextColor(colors.white)
  393.     term.write(...)
  394. end
  395.  
  396. helper.read = function()
  397.     term.setTextColor(colors.lightGray)
  398.     local data = read()
  399.     term.setTextColor(colors.white)
  400.     return data
  401. end
  402.  
  403. --#endregion
  404. --#region SHAPE LIBRARY----
  405.  
  406. local shapes = {}
  407.  
  408. shapes.custom = {
  409.     command = "custom",
  410.     shortDesc = "custom <filename>",
  411.     longDesc = "Executes a function named \"generate\" from the specified file and uses the data it returns as a shape",
  412.     args = {"str"},
  413.     generate = function(filename)
  414.         local env = {
  415.             table=table,
  416.             fs=fs,
  417.             http=http,
  418.             io=io,
  419.             math=math,
  420.             os=os,
  421.             parallel=parallel,
  422.             string=string,
  423.             vector=vector,
  424.             textutils=textutils
  425.         }
  426.         local chunk, err = loadfile(filename, "bt", env)
  427.         if err then
  428.             helper.printError("Couldn't load file:", err);
  429.             return {}
  430.         end
  431.         chunk();
  432.         if type(env.generate) ~= "function" then
  433.             helper.printError("File does not contain generate function")
  434.             return {}
  435.         end
  436.         local generated = env.generate()
  437.         if type(generated) ~= "table" then
  438.             helper.printError("Generate function didn't return a table")
  439.             return {}
  440.         end
  441.         local blocks = {}
  442.         for _, value in ipairs(generated) do
  443.             if type(value.x) ~= "number" or type(value.y) ~= "number" or type(value.z) ~= "number" then
  444.                 helper.printError("Invalid coordinates entry:", textutils.serialize(value))
  445.                 return {}
  446.             end
  447.             blocks[helper.getIndex(value.x, value.y, value.z)] = {x = value.x, y = value.y, z = value.z}
  448.         end
  449.         return blocks
  450.     end
  451. }
  452.  
  453. shapes.sphere = {
  454.     command = "sphere",
  455.     shortDesc = "sphere <diameter>",
  456.     longDesc = "Mine a sphere of diameter <diameter>, starting from it's bottom center",
  457.     args = {"2.."},
  458.     generate = function(diameter)
  459.         local radius = math.ceil(diameter / 2.0)
  460.         local radiusSq = (diameter / 2.0) * (diameter / 2.0)
  461.         local blocks = {}
  462.         local first = nil
  463.         for j=-radius,radius do
  464.             for i=-radius,radius do
  465.                 for k=-radius,radius do
  466.                     if diameter % 2 == 0 then
  467.                         if math.pow(i+0.5, 2) + math.pow(j+0.5, 2) + math.pow(k+0.5, 2) < radiusSq then
  468.                             if not first then
  469.                                 first = j
  470.                             end
  471.                             blocks[helper.getIndex(i,j-first,k)] = {x = i, y = j-first, z = k}
  472.                         end
  473.                     else
  474.                         if math.pow(i, 2) + math.pow(j, 2) + math.pow(k, 2) < radiusSq then
  475.                             if not first then
  476.                                 first = j
  477.                             end
  478.                             blocks[helper.getIndex(i,j-first,k)] = {x = i, y = j-first, z = k}
  479.                         end
  480.                     end
  481.                 end
  482.             end
  483.             helper.osYield()
  484.         end
  485.         return blocks
  486.     end
  487. }
  488.  
  489. shapes.cuboid = {
  490.     command="cube",
  491.     shortDesc = "cube <left> <up> <forward>",
  492.     longDesc = "Mine a cuboid of a specified size. Use negative values to dig in an opposite direction",
  493.     args= {"..-2 2..", "..-2 2..", "..-2 2.."},
  494.     generate = function(x, y, z)
  495.         local blocks = {}
  496.         local sX = helper.sign(x)
  497.         local sY = helper.sign(y)
  498.         local sZ = helper.sign(z)
  499.         local tX = sX * (math.abs(x) - 1)
  500.         local tY = sY * (math.abs(y) - 1)
  501.         local tZ = sZ * (math.abs(z) - 1)
  502.         for i=0,tX,sX do
  503.             for j=0,tY,sY do
  504.                 for k=0,tZ,sZ do
  505.                     blocks[helper.getIndex(i,j,k)] = {x = i, y = j, z = k}
  506.                 end
  507.             end
  508.             helper.osYield()
  509.         end
  510.         return blocks
  511.     end
  512. }
  513.  
  514. shapes.centeredCuboid = {
  515.     command="rcube",
  516.     shortDesc = "rcube <leftR> <upR> <forwardR>",
  517.     longDesc = "Mine a cuboid centered on the turtle. Each dimension is a \"radius\", so typing \"rcube 1 1 1\" will yield a 3x3x3 cube",
  518.     args={"1..", "1..", "1.."},
  519.     generate = function(rX, rY, rZ)
  520.         local blocks = {}
  521.         for i=-rX,rX do
  522.             for j=-rY,rY do
  523.                 for k=-rZ,rZ do
  524.                     blocks[helper.getIndex(i,j,k)] = {x = i, y = j, z = k}
  525.                 end
  526.             end
  527.             helper.osYield()
  528.         end
  529.         return blocks
  530.     end
  531. }
  532.  
  533. shapes.branch = {
  534.     command="branch",
  535.     shortDesc = "branch <branchLen> <shaftLen>",
  536.     longDesc = "Branch-mining. <branchLen> is the length of each branch, <shaftLen> is the length of the main shaft",
  537.     args={"0..", "3.."},
  538.     generate = function(xRadius, zDepth)
  539.         local blocks = {}
  540.         --generate corridor
  541.         for x=-1,1 do
  542.             for y=0,2 do
  543.                 for z=0,zDepth-1 do
  544.                     blocks[helper.getIndex(x,y,z)] = {x = x, y = y, z = z}
  545.                 end
  546.             end
  547.         end
  548.         --generate branches
  549.         for z=2,zDepth-1,2 do
  550.             local y = (z % 4 == 2) and 0 or 2
  551.             for x=0,xRadius-1 do
  552.                 blocks[helper.getIndex(x+2,y,z)] = {x = x+2, y = y, z = z}
  553.                 blocks[helper.getIndex(-x-2,y,z)] = {x = -x-2, y = y, z = z}
  554.             end
  555.         end
  556.         return blocks
  557.     end
  558. }
  559.  
  560. --#endregion
  561. --#region REGION PROCESSING----
  562.  
  563. local region = {}
  564. region.createShape = function()
  565.     local blocks = {}
  566.     for i=-7,7 do
  567.         for j=0,7 do
  568.             for k=-7,7 do
  569.                 if i*i + j*j + k*k < 2.5*2.5 then
  570.                     blocks[helper.getIndex(i,j,k)] = {x = i, y = j, z = k}
  571.                 end
  572.             end
  573.         end
  574.     end
  575.     return blocks
  576. end
  577.  
  578. region.cloneBlocks = function(allBlocks)
  579.     local cloned = {}
  580.     for key,value in pairs(allBlocks) do
  581.         local blockClone = {
  582.             x = value.x,
  583.             y = value.y,
  584.             z = value.z
  585.         }
  586.         cloned[key] = blockClone
  587.     end
  588.     return cloned
  589. end
  590.  
  591. --mark blocks that are next to walls
  592. region.markAdjacentInPlace = function(allBlocks)
  593.     for key,value in pairs(allBlocks) do
  594.         local xMinus = allBlocks[helper.getIndex(value.x - 1, value.y, value.z)]
  595.         local xPlus = allBlocks[helper.getIndex(value.x + 1, value.y, value.z)]
  596.         local yMinus = allBlocks[helper.getIndex(value.x, value.y - 1, value.z)]
  597.         local yPlus = allBlocks[helper.getIndex(value.x, value.y + 1, value.z)]
  598.         local zMinus = allBlocks[helper.getIndex(value.x, value.y, value.z - 1)]
  599.         local zPlus = allBlocks[helper.getIndex(value.x, value.y, value.z + 1)]
  600.         if not xMinus or not xPlus or not yMinus or not yPlus or not zMinus or not zPlus then
  601.             value.adjacent = true
  602.             if yMinus then yMinus.checkedInFirstPass = true end
  603.             if yPlus then yPlus.checkedInFirstPass = true end
  604.         end
  605.     end
  606. end
  607.  
  608. --mark positions where the turtle can check both the block above and the block below
  609. region.markTripleInPlace = function(allBlocks)
  610.     local minY = 9999999;
  611.     for key,value in pairs(allBlocks) do
  612.         if not value.checkedInFirstPass and not value.adjacent then
  613.             minY = math.min(minY, value.y)
  614.         end
  615.     end
  616.     for key,value in pairs(allBlocks) do
  617.         if not value.checkedInFirstPass and not value.adjacent then
  618.             local offset = (value.y - minY) % 3;
  619.             if offset == 0 then
  620.                 local blockAbove = allBlocks[helper.getIndex(value.x, value.y+1, value.z)]
  621.                 if blockAbove ~= nil and blockAbove.checkedInFirstPass ~= true then
  622.                     value.checkedInFirstPass = true
  623.                 else
  624.                     value.inacc = true
  625.                 end
  626.             elseif offset == 1 then
  627.                 value.triple = true
  628.             elseif offset == 2 and allBlocks[helper.getIndex(value.x, value.y-1, value.z)] ~= nil then
  629.                 local blockBelow = allBlocks[helper.getIndex(value.x, value.y-1, value.z)]
  630.                 if blockBelow ~= nil and blockBelow.checkedInFirstPass ~= true then
  631.                     value.checkedInFirstPass = true
  632.                 else
  633.                     value.inacc = true
  634.                 end
  635.             end
  636.         end
  637.     end
  638. end
  639.  
  640. region.findConnectedComponents = function(allBlocks)
  641.     local visited = {}
  642.     local components = {}
  643.     local counter = 0
  644.     local lastTime = os.clock()
  645.     for key,value in pairs(allBlocks) do
  646.         if not visited[key] then
  647.             local component = {}
  648.             local toVisit = {[key] = value}
  649.             while true do
  650.                 local newToVisit = {}
  651.                 local didSomething = false
  652.                 for currentKey,current in pairs(toVisit) do
  653.                     didSomething = true
  654.                     visited[currentKey] = true
  655.                     component[currentKey] = current
  656.                     local minusX = helper.getIndex(current.x-1, current.y, current.z)
  657.                     local plusX = helper.getIndex(current.x+1, current.y, current.z)
  658.                     local minusY = helper.getIndex(current.x, current.y-1, current.z)
  659.                     local plusY = helper.getIndex(current.x, current.y+1, current.z)
  660.                     local minusZ = helper.getIndex(current.x, current.y, current.z-1)
  661.                     local plusZ = helper.getIndex(current.x, current.y, current.z+1)
  662.                     if allBlocks[minusX] and not visited[minusX] then newToVisit[minusX] = allBlocks[minusX] end
  663.                     if allBlocks[plusX] and not visited[plusX] then newToVisit[plusX] = allBlocks[plusX] end
  664.                     if allBlocks[minusY] and not visited[minusY] then newToVisit[minusY] = allBlocks[minusY] end
  665.                     if allBlocks[plusY] and not visited[plusY] then newToVisit[plusY] = allBlocks[plusY] end
  666.                     if allBlocks[minusZ] and not visited[minusZ] then newToVisit[minusZ] = allBlocks[minusZ] end
  667.                     if allBlocks[plusZ] and not visited[plusZ] then newToVisit[plusZ] = allBlocks[plusZ] end
  668.  
  669.                     counter = counter + 1
  670.                     if counter % 50 == 0 then
  671.                         local curTime = os.clock()
  672.                         if curTime - lastTime > 1 then
  673.                             lastTime = curTime
  674.                             helper.osYield()
  675.                         end
  676.                     end
  677.                 end
  678.                 toVisit = newToVisit
  679.                 if not didSomething then break end
  680.             end
  681.             table.insert(components, component)
  682.         end
  683.     end
  684.     return components
  685. end
  686.  
  687. region.separateLayers = function(allBlocks, direction)
  688.     if direction ~= "y" and direction ~= "z" then
  689.         error("Invalid direction value", 2)
  690.     end
  691.     local layers = {}
  692.     local min = 999999
  693.     local max = -999999
  694.     for key,value in pairs(allBlocks) do
  695.         if not (not value.adjacent and value.checkedInFirstPass) then
  696.             local index = direction == "y" and value.y or value.z
  697.             if not layers[index] then
  698.                 layers[index] = {}
  699.             end
  700.             layers[index][key] = value
  701.             min = math.min(min, index)
  702.             max = math.max(max, index)
  703.         end
  704.     end
  705.     if min == 999999 then
  706.         error("There should be at least one block in passed table", 2)
  707.     end
  708.    
  709.     local reassLayers = {}
  710.     for key, value in pairs(layers) do
  711.         local index = direction == "y" and (max - min+1) - (key - min) or (key - min + 1)
  712.         reassLayers[index] = value
  713.     end
  714.     return reassLayers
  715. end
  716.  
  717. region.sortFunction = function(a, b)
  718.     return (a.y ~= b.y and a.y < b.y or (a.x ~= b.x and a.x > b.x or a.z > b.z))
  719. end
  720.  
  721. region.findClosestPoint = function(location, points, usedPoints)
  722.     local surroundings = helper.getSurroundings(location)
  723.     local existingSurroundings = {}
  724.     local foundClose = false
  725.     for key,value in pairs(surroundings) do
  726.         if points[key] and not usedPoints[key] then
  727.             table.insert(existingSurroundings, value)
  728.             foundClose = true
  729.         end
  730.     end
  731.     if foundClose then
  732.         table.sort(existingSurroundings, region.sortFunction)
  733.         local closest = table.remove(existingSurroundings)
  734.         return points[helper.getIndex(closest.x, closest.y, closest.z)]
  735.     end
  736.  
  737.     local minDist = 999999
  738.     local minValue = nil
  739.     for key,value in pairs(points) do
  740.         if not usedPoints[key] then
  741.             local dist = helper.distance(value,location)
  742.             if dist < minDist then
  743.                 minDist = dist
  744.                 minValue = value
  745.             end
  746.         end
  747.     end
  748.     if not minValue then
  749.         return nil
  750.     end
  751.     return minValue
  752. end
  753.  
  754. --travelling salesman, nearest neighbour method
  755. region.findOptimalBlockOrder = function(layers)
  756.     local newLayers = {}
  757.     local lastTime = os.clock()
  758.     for index, layer in ipairs(layers) do
  759.         local newLayer = {}
  760.         local usedPoints = {}
  761.         local current = region.findClosestPoint({x=0,y=0,z=0}, layer, usedPoints)
  762.         repeat
  763.             usedPoints[helper.getIndex(current.x, current.y, current.z)] = true
  764.             table.insert(newLayer, current)
  765.             current = region.findClosestPoint(current, layer, usedPoints)
  766.             local curTime = os.clock()
  767.             if curTime - lastTime > 1 then
  768.                 lastTime = curTime
  769.                 helper.osYield()
  770.             end
  771.         until not current
  772.         newLayers[index]=newLayer
  773.     end
  774.     return newLayers
  775. end
  776.  
  777. region.createLayersFromArea = function(diggingArea, direction)
  778.     local blocksToProcess = region.cloneBlocks(diggingArea)
  779.     region.markAdjacentInPlace(blocksToProcess)
  780.     region.markTripleInPlace(blocksToProcess)
  781.     local layers = region.separateLayers(blocksToProcess, direction)
  782.     local orderedLayers = region.findOptimalBlockOrder(layers)
  783.     return orderedLayers
  784. end
  785.  
  786. region.shiftRegion = function(shape, delta)
  787.     local newShape = {}
  788.     for key,value in pairs(shape) do
  789.         local newPos = {x = value.x + delta.x, y = value.y + delta.y, z = value.z + delta.z}
  790.         newShape[helper.getIndex(newPos.x, newPos.y, newPos.z)] = newPos
  791.     end
  792.     return newShape
  793. end
  794.  
  795. region.reserveChests = function(blocks)
  796.     local blocksCopy = {}
  797.     local counter = 0
  798.     for _,value in pairs(blocks) do
  799.         counter = counter + 1
  800.         blocksCopy[counter] = value
  801.     end
  802.     table.sort(blocksCopy, function(a,b)
  803.         if a.y ~= b.y then return a.y > b.y
  804.         elseif a.z~=b.z then return a.z > b.z
  805.         else return a.x > b.x end
  806.     end)
  807.  
  808.     return {reserved=blocksCopy}
  809. end
  810.  
  811. region.validateRegion = function(blocks)
  812.     local result = SUCCESS
  813.     --there must be only one connected component
  814.     local components = region.findConnectedComponents(blocks)
  815.     if helper.tableLength(components) == 0 then
  816.         result = result + FAILED_REGION_EMPTY
  817.     end
  818.     if helper.tableLength(components) > 1 then
  819.         result = result + FAILED_NONONE_COMPONENTCOUNT
  820.     end
  821.     --the turtle must be inside of the region
  822.     if not blocks[helper.getIndex(0,0,0)] then
  823.         result = result + FAILED_TURTLE_NOTINREGION
  824.     end
  825.     return result
  826. end
  827.  
  828. --#endregion
  829. --#region PATHFINDING----
  830.  
  831. local path = {}
  832. path.pathfindUpdateNeighbour = function(data, neighbour, destination, origin, neighbourIndex)
  833.     local originIndex = helper.getIndex(origin.x, origin.y, origin.z)
  834.     if not data[neighbourIndex] then
  835.         data[neighbourIndex] = {}
  836.         data[neighbourIndex].startDist = data[originIndex].startDist + 1
  837.         data[neighbourIndex].heuristicDist = helper.distance(neighbour, destination)
  838.         data[neighbourIndex].previous = origin
  839.     elseif data[originIndex].startDist + 1 < data[neighbourIndex].startDist then
  840.         data[neighbourIndex].startDist = data[originIndex].startDist + 1
  841.         data[neighbourIndex].previous = origin
  842.     end
  843. end
  844.  
  845. path.traversable = function(area, index, pathfindingType)
  846.     if pathfindingType == PATHFIND_INSIDE_AREA then
  847.         return area[index] ~= nil
  848.     elseif pathfindingType == PATHFIND_INSIDE_NONPRESERVED_AREA then
  849.         return not not (area[index] and not area[index].preserve)
  850.     elseif pathfindingType == PATHFIND_OUTSIDE_AREA then
  851.         return area[index] == nil
  852.     elseif PATHFIND_ANYWHERE_NONPRESERVED then
  853.         return area[index] == nil or not area[index].preserve
  854.     end
  855.     error("Unknown pathfinding type", 3)
  856. end
  857.  
  858. path.pathfind = function(from, to, allBlocks, pathfindingType)
  859.     if helper.isPosEqual(from,to) then
  860.         return {length = 0}
  861.     end
  862.     local data = {}
  863.     local openSet = {}
  864.     local closedSet = {}
  865.  
  866.     local current = from
  867.     local curIndex = helper.getIndex(from.x, from.y, from.z)
  868.     openSet[curIndex] = current
  869.  
  870.     data[curIndex] = {}
  871.     data[curIndex].startDist = 0
  872.     data[curIndex].heuristicDist = helper.distance(current, to)
  873.  
  874.     while true do
  875.         local surroundings = helper.getSurroundings(current)
  876.         for key,value in pairs(surroundings) do
  877.             if path.traversable(allBlocks,key,pathfindingType) and not closedSet[key] then
  878.                 path.pathfindUpdateNeighbour(data, value, to, current, key)
  879.                 openSet[key] = value
  880.             end
  881.         end
  882.  
  883.         closedSet[curIndex] = current
  884.         openSet[curIndex] = nil
  885.  
  886.         local minN = 9999999
  887.         local minValue = nil
  888.         for key,value in pairs(openSet) do
  889.             local sum = data[key].startDist + data[key].heuristicDist
  890.             if sum < minN then
  891.                 minN = sum
  892.                 minValue = value
  893.             end
  894.         end
  895.         current = minValue;
  896.  
  897.         if current == nil then
  898.             helper.printWarning("No path from", from.x, from.y, from.z, "to", to.x, to.y, to.z)
  899.             return false
  900.         end
  901.  
  902.         curIndex = helper.getIndex(current.x, current.y, current.z)
  903.         if helper.isPosEqual(current,to) then
  904.             break
  905.         end
  906.     end
  907.  
  908.     local path = {}
  909.     local counter = 1
  910.     while current ~= nil do
  911.         path[counter] = current
  912.         counter = counter + 1
  913.         current = data[helper.getIndex(current.x, current.y, current.z)].previous
  914.     end
  915.  
  916.     local reversedPath = {}
  917.     local newCounter = 1
  918.     for i=counter-1,1,-1 do
  919.         reversedPath[newCounter] = path[i]
  920.         newCounter = newCounter + 1
  921.     end
  922.     reversedPath.length = newCounter-1;
  923.     return reversedPath
  924. end
  925.  
  926. --#endregion
  927. --#region SLOT MANAGER----
  928. local slots = (function()
  929.     local slotAssignments = {}
  930.     local assigned = false
  931.     local public = {}
  932.  
  933.     local slotDesc = nil
  934.  
  935.     local generateDescription = function(config)
  936.         local desc = {
  937.             [CHEST_SLOT] = "Chests",
  938.             [BLOCK_SLOT] = "Cobblestone",
  939.             [FUEL_SLOT] = "Fuel (only coal supported)",
  940.             [FUEL_CHEST_SLOT] = "Fuel Entangled Chest",
  941.             [CHUNK_LOADER_SLOT] = "2 Chunk Loaders",
  942.         }
  943.         if config.useEntangledChests then
  944.             desc[CHEST_SLOT] = "Entangled Chest"
  945.         end
  946.         return desc
  947.     end
  948.  
  949.     public.assignSlots = function(config)
  950.         if assigned then
  951.             error("Slots have already been assigned", 2)
  952.         end
  953.         assigned = true
  954.  
  955.         local currentSlot = 1
  956.         slotAssignments[CHEST_SLOT] = currentSlot
  957.         currentSlot = currentSlot + 1
  958.         if config.useFuelEntangledChest then
  959.             slotAssignments[FUEL_CHEST_SLOT] = currentSlot
  960.             currentSlot = currentSlot + 1
  961.         else
  962.             slotAssignments[FUEL_SLOT] = currentSlot
  963.             currentSlot = currentSlot + 1
  964.         end
  965.         if config.mineLoop then
  966.             slotAssignments[CHUNK_LOADER_SLOT] = currentSlot
  967.             currentSlot = currentSlot + 1
  968.         end
  969.         slotAssignments[BLOCK_SLOT] = currentSlot
  970.         currentSlot = currentSlot + 1
  971.         slotAssignments[MISC_SLOT] = currentSlot
  972.         currentSlot = currentSlot + 1
  973.  
  974.         slotDesc = generateDescription(config)
  975.     end
  976.  
  977.     public.get = function(slotId)
  978.         if slotAssignments[slotId] then
  979.             return slotAssignments[slotId]
  980.         else
  981.             error("Slot " .. tostring(slotId) .. " was not assigned", 2)
  982.         end
  983.     end
  984.  
  985.     public.printSlotInfo = function()
  986.         local inverse = {}
  987.         for key,value in pairs(slotAssignments) do
  988.             inverse[value] = key
  989.         end
  990.         for key,value in ipairs(inverse) do
  991.             if value ~= MISC_SLOT then
  992.                 helper.print("\tSlot", key, "-", slotDesc[value])
  993.             end
  994.         end
  995.     end
  996.  
  997.     return public
  998. end)()
  999.  
  1000. --#endregion
  1001. --#region UI----
  1002.  
  1003. local ui = {}
  1004.  
  1005. ui.printInfo = function()
  1006.     helper.print("Current fuel level is", wrapt.getFuelLevel())
  1007.     helper.print("Item slots, can change based on config:")
  1008.     slots.printSlotInfo()
  1009. end
  1010.  
  1011. ui.printProgramsInfo = function()
  1012.     helper.print("Select a program:")
  1013.     helper.print("\thelp <program>")
  1014.     for _,value in pairs(shapes) do
  1015.         helper.print("\t"..value.shortDesc)
  1016.     end
  1017. end
  1018.  
  1019. ui.tryShowHelp = function(input)
  1020.     if not input then
  1021.         return false
  1022.     end
  1023.     local split = helper.splitString(input)
  1024.     if split[1] ~= "help" then
  1025.         return false
  1026.     end
  1027.  
  1028.     if #split == 1 then
  1029.         ui.printProgramsInfo()
  1030.         return true
  1031.     end
  1032.  
  1033.     if #split ~= 2 then
  1034.         return false
  1035.     end
  1036.  
  1037.     local program = split[2]
  1038.     local shape = nil
  1039.     for key, value in pairs(shapes) do
  1040.         if value.command == program then
  1041.             shape = value
  1042.         end
  1043.     end
  1044.  
  1045.     if not shape then
  1046.         helper.printError("Unknown program")
  1047.         return true
  1048.     end
  1049.     helper.print("Usage:", shape.shortDesc)
  1050.     helper.print("\t"..shape.longDesc)
  1051.     return true
  1052. end
  1053.  
  1054. ui.testRange = function(range, value)
  1055.     if range == "str" then
  1056.         return "string"
  1057.     end
  1058.     if type(value) ~= "number" then
  1059.         return false
  1060.     end
  1061.  
  1062.     local subRanges = helper.splitString(range, " ")
  1063.     for _, range in ipairs(subRanges) do
  1064.         local borders = helper.splitString(range, "..")
  1065.         local tableLength = helper.tableLength(borders)
  1066.         if tableLength == 2 then
  1067.             local left = tonumber(borders[1])
  1068.             local right = tonumber(borders[2])
  1069.             if helper.inRange(value, left, right) then
  1070.                 return true
  1071.             end
  1072.         elseif tableLength == 1 then
  1073.             local isLeft = string.sub(range, 0, 1) ~= "."
  1074.             local border = tonumber(borders[1])
  1075.             local good = isLeft and (value >= border) or not isLeft and (value <= border)
  1076.             if good then
  1077.                 return true
  1078.             end
  1079.         end
  1080.     end
  1081.     return false
  1082. end
  1083.  
  1084. ui.parseArgs = function(argPattern, args)
  1085.     if helper.tableLength(argPattern) ~= helper.tableLength(args) then
  1086.         return nil
  1087.     end
  1088.    
  1089.     local parsed = {}
  1090.     for _,value in ipairs(args) do
  1091.         local number = tonumber(value)
  1092.         if not number then
  1093.             table.insert(parsed, value)
  1094.         end
  1095.         table.insert(parsed, number)
  1096.     end
  1097.  
  1098.     for index,value in ipairs(argPattern) do
  1099.         local result = ui.testRange(value, parsed[index])
  1100.         if result == "string" then
  1101.             parsed[index] = args[index]
  1102.         elseif not result then
  1103.             return nil
  1104.         end
  1105.     end
  1106.     return parsed
  1107. end
  1108.  
  1109. ui.parseProgram = function(string)
  1110.     if not string then
  1111.         return nil
  1112.     end
  1113.  
  1114.     local split = helper.splitString(string)
  1115.     if not split or helper.tableLength(split) == 0 then
  1116.         return nil
  1117.     end
  1118.    
  1119.     local program = split[1]
  1120.     local shape = nil
  1121.     for _, value in pairs(shapes) do
  1122.         if value.command == program then
  1123.             shape = value
  1124.         end
  1125.     end
  1126.     if not shape then
  1127.         return nil
  1128.     end
  1129.  
  1130.     local args = {table.unpack(split, 2, #split)}
  1131.     local parsed = ui.parseArgs(shape.args, args)
  1132.     if not parsed then
  1133.         return nil
  1134.     end
  1135.  
  1136.     return {shape = shape, args = parsed}
  1137. end
  1138.  
  1139. ui.promptForShape = function()
  1140.     local shape
  1141.     while true do
  1142.         helper.write("> ")
  1143.         local input = helper.read()
  1144.         shape = ui.parseProgram(input)
  1145.         if not shape then
  1146.             if not ui.tryShowHelp(input) then
  1147.                 helper.printError("Invalid program")
  1148.             end
  1149.         else
  1150.             break
  1151.         end
  1152.     end
  1153.     return shape
  1154. end
  1155.  
  1156. ui.showValidationError = function(validationResult)
  1157.     local error = "Invalid mining volume:";
  1158.     if bit32.band(validationResult, FAILED_REGION_EMPTY) ~= 0 then
  1159.         helper.printError("Invalid mining volume: \n\tVolume is empty")
  1160.         return
  1161.     end
  1162.     if bit32.band(validationResult, FAILED_NONONE_COMPONENTCOUNT) ~= 0 then
  1163.         error = error .. "\n\tVolume has multiple disconnected parts"
  1164.     end
  1165.     if bit32.band(validationResult, FAILED_TURTLE_NOTINREGION) ~= 0 then
  1166.         error = error .. "\n\tTurtle (pos(0,0,0)) not in volume"
  1167.     end
  1168.     helper.printError(error)
  1169. end
  1170.  
  1171. --#endregion
  1172.  
  1173. ----THE REST OF THE CODE----
  1174.  
  1175. local function execWithSlot(func, slot)
  1176.     wrapt.select(slots.get(slot))
  1177.     local data = func()
  1178.     wrapt.select(slots.get(MISC_SLOT))
  1179.     return data
  1180. end
  1181.  
  1182. local function isWaterSource(inspectFunction)
  1183.     local success, data = inspectFunction()
  1184.     if success and data.name == "minecraft:water" and data.state.level == 0 then
  1185.         return true
  1186.     end
  1187.     return false
  1188. end
  1189.  
  1190. local function isFluidSource(inspectFunction)
  1191.     local success, data = inspectFunction()
  1192.     if success and (data.name == "minecraft:lava" or data.name == "minecraft:water") and data.state.level == 0 then
  1193.         return true
  1194.     end
  1195.     return false
  1196. end
  1197.  
  1198. local function isFluid(inspectFunction)
  1199.     local success, data = inspectFunction()
  1200.     if success and (data.name == "minecraft:lava" or data.name == "minecraft:water") then
  1201.         return true
  1202.     end
  1203.     return false
  1204. end
  1205.  
  1206. local function isSand(inspectFunction)
  1207.     local success, data = inspectFunction()
  1208.     if success and FALLING_BLOCKS[data.name] then
  1209.         return true
  1210.     end
  1211.     return false
  1212. end
  1213.  
  1214. local function isOre(inspectFunction)
  1215.     local success, data = inspectFunction()
  1216.     if success and helper.stringEndsWith(data.name, "_ore") then
  1217.         return true
  1218.     end
  1219.     return false
  1220. end
  1221.  
  1222.  
  1223. local function dropInventory(dropFunction)
  1224.     local dropped = true
  1225.     for i=slots.get(MISC_SLOT),16 do
  1226.         if wrapt.getItemCount(i) > 0 then
  1227.             wrapt.select(i)
  1228.             dropped = dropFunction() and dropped
  1229.         end
  1230.     end
  1231.     wrapt.select(slots.get(MISC_SLOT))
  1232.     return dropped
  1233. end
  1234.  
  1235. local function forceMoveForward()
  1236.     if isWaterSource(wrapt.inspect) then
  1237.         execWithSlot(wrapt.place, BLOCK_SLOT)
  1238.     end
  1239.     repeat
  1240.         wrapt.dig()
  1241.     until wrapt.forward()
  1242. end
  1243.  
  1244. local function plugTop(onlyFluids)
  1245.     if isSand(wrapt.inspectUp) then
  1246.         wrapt.digUp()
  1247.     end
  1248.     if not wrapt.detectUp() and not onlyFluids or onlyFluids and isFluid(wrapt.inspectUp) then
  1249.         if wrapt.getItemCount(slots.get(BLOCK_SLOT)) > 0 then
  1250.             local tries = 0
  1251.             repeat tries = tries + 1 until execWithSlot(wrapt.placeUp, BLOCK_SLOT) or tries > 10
  1252.         end
  1253.     end
  1254. end
  1255.  
  1256. local function plugBottom(onlyFluids)
  1257.     if not onlyFluids or isFluidSource(wrapt.inspectDown) then
  1258.         execWithSlot(wrapt.placeDown, BLOCK_SLOT)
  1259.     end
  1260. end
  1261.  
  1262. local function digAbove()
  1263.     if isFluidSource(wrapt.inspectUp) then
  1264.         execWithSlot(wrapt.placeUp, BLOCK_SLOT)
  1265.     end
  1266.     wrapt.digUp()
  1267. end
  1268.  
  1269. local function digBelow()
  1270.     if isFluidSource(wrapt.inspectDown) then
  1271.         execWithSlot(wrapt.placeDown, BLOCK_SLOT)
  1272.     end
  1273.     wrapt.digDown()
  1274. end
  1275.  
  1276. local function digInFront()
  1277.     if isFluidSource(wrapt.inspect) then
  1278.         execWithSlot(wrapt.place, BLOCK_SLOT)
  1279.     end
  1280.     wrapt.dig()
  1281. end
  1282.  
  1283. local function turnTowardsDirection(targetDir)
  1284.     local delta = (targetDir - wrapt.getDirection()) % 4
  1285.     if delta == 1 then
  1286.         wrapt.turnRight()
  1287.     elseif delta == 2 then
  1288.         wrapt.turnRight()
  1289.         wrapt.turnRight()
  1290.     elseif delta == 3 then
  1291.         wrapt.turnLeft()
  1292.     end
  1293.     if targetDir ~= wrapt.getDirection() then
  1294.         error("Could not turn to requested direction")
  1295.     end
  1296. end
  1297.  
  1298.  
  1299.  
  1300. local function stepTo(tX, tY, tZ)
  1301.     local dX = tX - wrapt.getX()
  1302.     local dY = tY - wrapt.getY()
  1303.     local dZ = tZ - wrapt.getZ()
  1304.     if dY < 0 then
  1305.         repeat wrapt.digDown() until wrapt.down()
  1306.     elseif dY > 0 then
  1307.         repeat wrapt.digUp() until wrapt.up()
  1308.     else
  1309.         local dir = helper.deltaToDirection(dX, dZ)
  1310.         turnTowardsDirection(dir)
  1311.         forceMoveForward()
  1312.     end
  1313. end
  1314.  
  1315. local function goToCoords(curBlock, pathfindingArea, pathfindingType)
  1316.     local pos = wrapt.getPosition()
  1317.     local path = path.pathfind(pos, curBlock, pathfindingArea, pathfindingType)
  1318.     if not path then
  1319.         return false
  1320.     end
  1321.  
  1322.     for k=1,path.length do
  1323.         if not (path[k].x == pos.x and path[k].y == pos.y and path[k].z == pos.z) then
  1324.             stepTo(path[k].x, path[k].y, path[k].z)
  1325.         end
  1326.     end
  1327.     return true
  1328. end
  1329.  
  1330.  
  1331.  
  1332. local function findOre(oresTable, startPos, rangeLimit)
  1333.     local ores = {}
  1334.     local isForwardCloseEnough = function()
  1335.         return helper.distance(helper.getForwardPos(wrapt.getPosition()), startPos) <= rangeLimit
  1336.     end
  1337.     if isForwardCloseEnough() and isOre(wrapt.inspect) then
  1338.         table.insert(ores,helper.getForwardPos(wrapt.getPosition()))
  1339.     end
  1340.     local down = {x = wrapt.getX(), y = wrapt.getY() - 1, z = wrapt.getZ()}
  1341.     if helper.distance(down, startPos) <= rangeLimit and isOre(wrapt.inspectDown) then
  1342.         table.insert(ores, down)
  1343.     end
  1344.     local up = {x = wrapt.getX(), y = wrapt.getY() + 1, z = wrapt.getZ()}
  1345.     if helper.distance(up, startPos) <= rangeLimit and isOre(wrapt.inspectUp) then
  1346.         table.insert(ores, up)
  1347.     end
  1348.     wrapt.turnLeft()
  1349.     if isForwardCloseEnough() and isOre(wrapt.inspect) then
  1350.         table.insert(ores,helper.getForwardPos(wrapt.getPosition()))
  1351.     end
  1352.     wrapt.turnLeft()
  1353.     if isForwardCloseEnough() and isOre(wrapt.inspect) then
  1354.         table.insert(ores,helper.getForwardPos(wrapt.getPosition()))
  1355.     end
  1356.     wrapt.turnLeft()
  1357.     if isForwardCloseEnough() and isOre(wrapt.inspect) then
  1358.         table.insert(ores,helper.getForwardPos(wrapt.getPosition()))
  1359.     end
  1360.     for _,value in pairs(ores) do
  1361.         if not helper.tableContains(oresTable, value, helper.isPosEqual) then
  1362.             table.insert(oresTable, value)
  1363.         end
  1364.     end
  1365. end
  1366.  
  1367. --traverse an ore vein and return to original turtle position afterwards
  1368. local function traverseVein(blocks, rangeLimit)
  1369.     local startPos = wrapt.getPosition()
  1370.     local minePath = {}
  1371.     minePath[helper.getIndex(startPos.x, startPos.y, startPos.z)] = startPos
  1372.  
  1373.     local ores = {}
  1374.     local searchedPositions = {}
  1375.     while true do
  1376.         local currentIndex = helper.getIndex(wrapt.getX(),wrapt.getY(),wrapt.getZ())
  1377.         if not searchedPositions[currentIndex] then
  1378.             findOre(ores, startPos, rangeLimit)
  1379.             searchedPositions[currentIndex] = true
  1380.         end
  1381.         local targetOre = table.remove(ores)
  1382.         if not targetOre then
  1383.             goToCoords(startPos, minePath, PATHFIND_INSIDE_AREA)
  1384.             turnTowardsDirection(startPos.direction)
  1385.             break
  1386.         end
  1387.         local targetIndex = helper.getIndex(targetOre.x, targetOre.y, targetOre.z)
  1388.         if not blocks[targetIndex] or not blocks[targetIndex].preserve then
  1389.             minePath[helper.getIndex(targetOre.x, targetOre.y, targetOre.z)] = targetOre
  1390.             goToCoords(targetOre, minePath, PATHFIND_INSIDE_AREA)
  1391.         end
  1392.     end
  1393. end
  1394.  
  1395.  
  1396.  
  1397. --diggingOptions: plugFluidsOnly, oreTraversalRadius
  1398. local function processAdjacent(allBlocks, diggingOptions)
  1399.     local toPlace = {}
  1400.  
  1401.     local pos = wrapt.getPosition()
  1402.  
  1403.     local minusX = helper.getIndex(pos.x-1, pos.y, pos.z)
  1404.     local plusX = helper.getIndex(pos.x+1, pos.y, pos.z)
  1405.     local minusZ = helper.getIndex(pos.x, pos.y, pos.z-1)
  1406.     local plusZ = helper.getIndex(pos.x, pos.y, pos.z+1)
  1407.  
  1408.     if not allBlocks[minusX] then toPlace[minusX] = {x = pos.x - 1, y = pos.y, z = pos.z} end
  1409.     if not allBlocks[plusX] then toPlace[plusX] = {x = pos.x + 1, y = pos.y, z = pos.z} end
  1410.     if not allBlocks[minusZ] then toPlace[minusZ] = {x = pos.x, y = pos.y, z = pos.z - 1} end
  1411.     if not allBlocks[plusZ] then toPlace[plusZ] = {x = pos.x, y = pos.y, z = pos.z + 1} end
  1412.  
  1413.     for key,value in pairs(toPlace) do
  1414.         local dX = value.x - pos.x
  1415.         local dZ = value.z - pos.z
  1416.         local dir = helper.deltaToDirection(dX, dZ)
  1417.         turnTowardsDirection(dir)
  1418.         if diggingOptions.oreTraversalRadius > 0 and isOre(wrapt.inspect) then
  1419.             traverseVein(allBlocks, diggingOptions.oreTraversalRadius)
  1420.         end
  1421.         if not diggingOptions.plugFluidsOnly or isFluid(wrapt.inspect) then
  1422.             execWithSlot(wrapt.place, BLOCK_SLOT)
  1423.         end
  1424.     end
  1425.  
  1426.     local minusY = helper.getIndex(pos.x, pos.y-1, pos.z)
  1427.     local plusY = helper.getIndex(pos.x, pos.y+1, pos.z)
  1428.  
  1429.     if diggingOptions.oreTraversalRadius > 0 then
  1430.         if not allBlocks[minusY] and isOre(wrapt.inspectDown)
  1431.             or not allBlocks[plusY] and isOre(wrapt.inspectUp) then
  1432.             traverseVein(allBlocks, diggingOptions.oreTraversalRadius)
  1433.         end
  1434.     end
  1435.  
  1436.     if allBlocks[minusY] then
  1437.         if not allBlocks[minusY].preserve then digBelow() end
  1438.     else
  1439.         plugBottom(diggingOptions.plugFluidsOnly)
  1440.     end
  1441.  
  1442.     if allBlocks[plusY] then
  1443.         if not allBlocks[plusY].preserve then digAbove() end
  1444.     else
  1445.         plugTop(diggingOptions.plugFluidsOnly)
  1446.     end
  1447. end
  1448.  
  1449.  
  1450.  
  1451.  
  1452. local function processTriple(diggingArea)
  1453.     local pos = wrapt.getPosition()
  1454.     local minusY = helper.getIndex(pos.x, pos.y-1, pos.z)
  1455.     local plusY = helper.getIndex(pos.x, pos.y+1, pos.z)
  1456.     if not diggingArea[plusY].preserve then digAbove() end
  1457.     if not diggingArea[minusY].preserve then digBelow() end
  1458. end
  1459.  
  1460.  
  1461.  
  1462. local function sortInventory(sortFuel)
  1463.     --clear cobble slot
  1464.     local initCobbleData = wrapt.getItemDetail(slots.get(BLOCK_SLOT))
  1465.     if initCobbleData and initCobbleData.name ~= "minecraft:cobblestone" then
  1466.         wrapt.select(slots.get(BLOCK_SLOT))
  1467.         wrapt.drop()
  1468.     end
  1469.  
  1470.     --clear fuel slot
  1471.     if sortFuel then
  1472.         local initFuelData = wrapt.getItemDetail(slots.get(FUEL_SLOT))
  1473.         if initFuelData and initFuelData.name ~= "minecraft:coal" then
  1474.             wrapt.select(slots.get(FUEL_SLOT))
  1475.             wrapt.drop()
  1476.         end
  1477.     end
  1478.  
  1479.     --search inventory for cobble and fuel and put them in the right slots
  1480.     local fuelData = sortFuel and wrapt.getItemDetail(slots.get(FUEL_SLOT)) or {count=64}
  1481.     local cobbleData = wrapt.getItemDetail(slots.get(BLOCK_SLOT))
  1482.  
  1483.     if fuelData and cobbleData and fuelData.count > 32 and cobbleData.count > 32 then
  1484.         wrapt.select(slots.get(MISC_SLOT))
  1485.         return
  1486.     end
  1487.  
  1488.     for i=slots.get(MISC_SLOT),16 do
  1489.         local curData = wrapt.getItemDetail(i)
  1490.         if curData then
  1491.             if curData.name == "minecraft:cobblestone" then
  1492.                 wrapt.select(i)
  1493.                 wrapt.transferTo(slots.get(BLOCK_SLOT))
  1494.             elseif sortFuel and curData.name == "minecraft:coal" then
  1495.                 wrapt.select(i)
  1496.                 wrapt.transferTo(slots.get(FUEL_SLOT))
  1497.             end
  1498.         end
  1499.     end
  1500.  
  1501.     wrapt.select(slots.get(MISC_SLOT))
  1502. end
  1503.  
  1504.  
  1505.  
  1506. local function dropIntoEntangled(dropFunction)
  1507.     local result = false
  1508.     repeat
  1509.         result = dropInventory(dropFunction)
  1510.         if not result then
  1511.             helper.printWarning("Entangled chest is full, retrying..")
  1512. ---@diagnostic disable-next-line: undefined-field
  1513.             os.sleep(RETRY_DELAY)
  1514.         end
  1515.     until result
  1516. end
  1517.  
  1518. local function findSuitableEntangledChestPos(diggingArea)
  1519.     local surroundings = helper.getSurroundings(wrapt.getPosition())
  1520.     local options = {}
  1521.     for key,value in pairs(surroundings) do
  1522.         if diggingArea[key] and not diggingArea[key].preserve then
  1523.             table.insert(options, value)
  1524.         end
  1525.     end
  1526.     local selectedOption = table.remove(options)
  1527.     if not selectedOption then
  1528.         error("Did the turtle just surround itself with chests?")
  1529.         return nil
  1530.     end
  1531.     return selectedOption
  1532. end
  1533.  
  1534. local function dropOffEntangledChest(diggingArea)
  1535.     local selectedOption = findSuitableEntangledChestPos(diggingArea)
  1536.     if not selectedOption then
  1537.         return
  1538.     end
  1539.  
  1540.     local curPosition = wrapt.getPosition()
  1541.     local delta = {x = selectedOption.x-curPosition.x, y = selectedOption.y-curPosition.y, z = selectedOption.z-curPosition.z}
  1542.     if delta.y < 0 then
  1543.         repeat digBelow() until execWithSlot(wrapt.placeDown, CHEST_SLOT)
  1544.         dropIntoEntangled(wrapt.dropDown)
  1545.         wrapt.select(slots.get(CHEST_SLOT))
  1546.         digBelow()
  1547.         wrapt.select(slots.get(MISC_SLOT))
  1548.     elseif delta.y > 0 then
  1549.         repeat digAbove() until execWithSlot(wrapt.placeUp, CHEST_SLOT)
  1550.         dropIntoEntangled(wrapt.dropUp)
  1551.         wrapt.select(slots.get(CHEST_SLOT))
  1552.         digAbove()
  1553.         wrapt.select(slots.get(MISC_SLOT))
  1554.     elseif delta.x ~= 0 or delta.y ~= 0 or delta.z ~= 0 then
  1555.         local direction = helper.deltaToDirection(delta.x, delta.z)
  1556.         turnTowardsDirection(direction)
  1557.         repeat digInFront() until execWithSlot(wrapt.place, CHEST_SLOT)
  1558.         dropIntoEntangled(wrapt.drop)
  1559.         wrapt.select(slots.get(CHEST_SLOT))
  1560.         digInFront()
  1561.         wrapt.select(slots.get(MISC_SLOT))
  1562.     else
  1563.         error("Something went really wrong")
  1564.     end
  1565. end
  1566.  
  1567. local function makeNewChestInPlace(chestData, diggingArea)
  1568.     local newPos = table.remove(chestData.reserved)
  1569.     if not newPos then
  1570.         error("Out of reserved chest spots")
  1571.         return false
  1572.     end
  1573.     chestData.placed = newPos
  1574.    
  1575.     local chestPosIndex = helper.getIndex(chestData.placed.x, chestData.placed.y, chestData.placed.z)
  1576.     chestData.reserved[chestPosIndex] = nil
  1577.     local blockAbove = {x = chestData.placed.x, y = chestData.placed.y+1, z = chestData.placed.z}
  1578.     if not goToCoords(diggingArea[helper.getIndex(blockAbove.x, blockAbove.y, blockAbove.z)], diggingArea, PATHFIND_INSIDE_NONPRESERVED_AREA) then
  1579.         helper.printWarning("Could not pathfind to new chest location, trying again ignoring walls...")
  1580.         if not goToCoords(diggingArea[helper.getIndex(blockAbove.x, blockAbove.y, blockAbove.z)], diggingArea, PATHFIND_ANYWHERE_NONPRESERVED) then
  1581.             helper.printWarning("Fallback pathfinding failed")
  1582.             return false
  1583.         end
  1584.     end
  1585.     digBelow()
  1586.     while true do
  1587.         local success = execWithSlot(wrapt.placeDown, CHEST_SLOT)
  1588.         if success then
  1589.             break
  1590.         end
  1591.         local chestData = wrapt.getItemDetail(slots.get(CHEST_SLOT))
  1592.         if not chestData then
  1593.             helper.printWarning("Out of chests. Add chests to slot", slots.get(CHEST_SLOT))
  1594.         end
  1595. ---@diagnostic disable-next-line: undefined-field
  1596.         os.sleep(RETRY_DELAY)
  1597.     end
  1598.     diggingArea[chestPosIndex].preserve = true
  1599.     return true
  1600. end
  1601.  
  1602. local function dropOffNormally(chestData, diggingArea)
  1603.     if not chestData.placed then
  1604.         makeNewChestInPlace(chestData, diggingArea)
  1605.     end
  1606.  
  1607.     local blockAbove = {x = chestData.placed.x, y = chestData.placed.y+1, z = chestData.placed.z}
  1608.     if not goToCoords(diggingArea[helper.getIndex(blockAbove.x, blockAbove.y, blockAbove.z)], diggingArea, PATHFIND_INSIDE_NONPRESERVED_AREA) then
  1609.         helper.printWarning("Could not pathfind to chest location, trying again ignoring walls...")
  1610.         if not goToCoords(diggingArea[helper.getIndex(blockAbove.x, blockAbove.y, blockAbove.z)], diggingArea, PATHFIND_ANYWHERE_NONPRESERVED) then
  1611.             helper.printWarning("Fallback pathfinding failed too")
  1612.         end
  1613.     end
  1614.     repeat
  1615.         for i=slots.get(MISC_SLOT),16 do
  1616.             wrapt.select(i)
  1617.             if not wrapt.dropDown() and wrapt.getItemDetail(i) then
  1618.                 makeNewChestInPlace(chestData, diggingArea)
  1619.                 wrapt.dropDown()
  1620.             end
  1621.         end
  1622.     until not wrapt.getItemDetail(16)
  1623.     wrapt.select(slots.get(MISC_SLOT))
  1624. end
  1625.  
  1626. local function tryDropOffThings(chestData, diggingArea, entangledChest, force)
  1627.     if not wrapt.getItemDetail(16) and not force then
  1628.         return
  1629.     end
  1630.  
  1631.     if entangledChest then
  1632.         dropOffEntangledChest(diggingArea)
  1633.         return
  1634.     end
  1635.  
  1636.     dropOffNormally(chestData, diggingArea)
  1637. end
  1638.  
  1639.  
  1640.  
  1641. local function getFuelAndConsumeFromEntangled(suckFunction, dropFunction)
  1642.     local result = false
  1643.     wrapt.select(16)
  1644.     repeat
  1645.         repeat
  1646.             if not suckFunction(32) then
  1647.                 helper.printWarning("Refuel chest is empty, retrying...")
  1648.                 break
  1649.             end
  1650.             if not wrapt.refuel() then
  1651.                 helper.printWarning("Refuel chest contains garbage, retrying...")
  1652.                 while not dropFunction() do
  1653.                     helper.printWarning("Could not return garbage back to the chest, retrying...")
  1654. ---@diagnostic disable-next-line: undefined-field
  1655.                     os.sleep(RETRY_DELAY)
  1656.                 end
  1657.                 break
  1658.             end
  1659.             result = wrapt.getFuelLevel() > REFUEL_THRESHOLD
  1660.         until result
  1661.         if not result then
  1662. ---@diagnostic disable-next-line: undefined-field
  1663.             os.sleep(RETRY_DELAY)
  1664.         end
  1665.     until result
  1666. end
  1667.  
  1668. local function refuelEntangled(chestData, diggingArea, dropOffEntangled)
  1669.     tryDropOffThings(chestData, diggingArea, dropOffEntangled)
  1670.    
  1671.     local selectedOption = findSuitableEntangledChestPos(diggingArea)
  1672.     if not selectedOption then return end
  1673.  
  1674.     local curPosition = wrapt.getPosition()
  1675.     local delta = {x = selectedOption.x-curPosition.x, y = selectedOption.y-curPosition.y, z = selectedOption.z-curPosition.z}
  1676.     if delta.y < 0 then
  1677.         repeat digBelow() until execWithSlot(wrapt.placeDown, FUEL_CHEST_SLOT)
  1678.         getFuelAndConsumeFromEntangled(wrapt.suckDown, wrapt.dropDown)
  1679.         wrapt.select(slots.get(FUEL_CHEST_SLOT))
  1680.         digBelow()
  1681.         wrapt.select(slots.get(MISC_SLOT))
  1682.     elseif delta.y > 0 then
  1683.         repeat digAbove() until execWithSlot(wrapt.placeUp, FUEL_CHEST_SLOT)
  1684.         getFuelAndConsumeFromEntangled(wrapt.suckUp, wrapt.dropUp)
  1685.         wrapt.select(slots.get(FUEL_CHEST_SLOT))
  1686.         digAbove()
  1687.         wrapt.select(slots.get(MISC_SLOT))
  1688.     elseif delta.x ~= 0 or delta.y ~= 0 or delta.z ~= 0 then
  1689.         local direction = helper.deltaToDirection(delta.x, delta.z)
  1690.         turnTowardsDirection(direction)
  1691.         repeat digInFront() until execWithSlot(wrapt.place, FUEL_CHEST_SLOT)
  1692.         getFuelAndConsumeFromEntangled(wrapt.suck, wrapt.drop)
  1693.         wrapt.select(slots.get(FUEL_CHEST_SLOT))
  1694.         digInFront()
  1695.         wrapt.select(slots.get(MISC_SLOT))
  1696.     else
  1697.         error("Something went really wrong")
  1698.     end
  1699. end
  1700.  
  1701. local function refuelNormally()
  1702.     repeat
  1703.         local fuelData = wrapt.getItemDetail(slots.get(FUEL_SLOT))
  1704.         if not fuelData then
  1705.             sortInventory(true)
  1706.         end
  1707.         repeat
  1708.             local newFuelData = wrapt.getItemDetail(slots.get(FUEL_SLOT))
  1709.             if not newFuelData then
  1710.                 helper.printWarning("Out of fuel. Put fuel in slot", slots.get(FUEL_SLOT))
  1711.     ---@diagnostic disable-next-line: undefined-field
  1712.                 os.sleep(RETRY_DELAY)
  1713.                 helper.osYield()
  1714.             end
  1715.         until newFuelData
  1716.         execWithSlot(wrapt.refuel, FUEL_SLOT)
  1717.     until wrapt.getFuelLevel() > REFUEL_THRESHOLD
  1718. end
  1719.  
  1720. local function tryToRefuel(chestData, diggingArea, dropOffEntangledChest, refuelEntangledChest)
  1721.     if wrapt.getFuelLevel() < REFUEL_THRESHOLD then
  1722.         if refuelEntangledChest then
  1723.             refuelEntangled(chestData, diggingArea, dropOffEntangledChest)
  1724.             return
  1725.         else
  1726.             refuelNormally()
  1727.             return
  1728.         end
  1729.     end
  1730. end
  1731.  
  1732.  
  1733.  
  1734. local function executeDigging(layers, diggingArea, chestData, config)
  1735.     local counter = 0
  1736.     for layerIndex, layer in ipairs(layers) do
  1737.         for blockIndex, block in ipairs(layer) do
  1738.             if counter % 5 == 0 or not wrapt.getItemDetail(slots.get(BLOCK_SLOT)) then
  1739.                 sortInventory(not config.useFuelEntangledChest)
  1740.             end
  1741.             if counter % 5 == 0 then
  1742.                 tryToRefuel(chestData, diggingArea, config.useEntangledChests, config.useFuelEntangledChest)
  1743.             end
  1744.             tryDropOffThings(chestData, diggingArea, config.useEntangledChests)
  1745.             if not diggingArea[helper.getIndex(block.x, block.y, block.z)].preserve then
  1746.                 if not goToCoords(block, diggingArea, PATHFIND_INSIDE_NONPRESERVED_AREA) then
  1747.                     helper.printWarning("Couldn't find a path to next block, trying again ingoring walls...")
  1748.                     if not goToCoords(block, diggingArea, PATHFIND_ANYWHERE_NONPRESERVED) then
  1749.                         helper.printWarning("Fallback pathfinding failed, skipping the block")
  1750.                         break
  1751.                     end
  1752.                 end
  1753.                 if block.adjacent then
  1754.                     processAdjacent(diggingArea, config)
  1755.                 elseif block.triple then
  1756.                     processTriple(diggingArea)
  1757.                 end
  1758.             end
  1759.             counter = counter + 1
  1760.         end
  1761.     end
  1762.     tryDropOffThings(chestData, diggingArea, config.useEntangledChests, true)
  1763. end
  1764.  
  1765. local function getValidatedRegion(config, default)
  1766.     ui.printInfo()
  1767.     ui.printProgramsInfo()
  1768.     while true do
  1769.         local shape = nil
  1770.         if default then
  1771.             shape = ui.parseProgram(config.defaultCommand)
  1772.             if not shape then
  1773.                 helper.printError("defaultCommand is invalid")
  1774.                 default = false
  1775.             end
  1776.         end
  1777.  
  1778.         if not default then
  1779.             shape = ui.promptForShape()
  1780.         end
  1781.  
  1782.         local genRegion = shape.shape.generate(table.unpack(shape.args))
  1783.         local validationResult = region.validateRegion(genRegion)
  1784.         if validationResult == SUCCESS then
  1785.             return genRegion
  1786.         end
  1787.         ui.showValidationError(validationResult)
  1788.     end
  1789. end
  1790.  
  1791. local function launchDigging(config, default)
  1792.     local diggingArea = getValidatedRegion(config, default)
  1793.     local layers = region.createLayersFromArea(diggingArea, config.layerSeparationAxis)
  1794.     local chestData = region.reserveChests(diggingArea)
  1795.     executeDigging(layers, diggingArea, chestData, config)
  1796. end
  1797.  
  1798.  
  1799. local function executeMineLoop(config)
  1800.     local cumDelta = {x = 0, y = 0, z = 0}
  1801.     local prevPos = nil
  1802.     while true do
  1803.         --create a region to dig
  1804.         local shape = ui.parseProgram(config.mineLoopCommand)
  1805.         local diggingArea = shape.shape.generate(table.unpack(shape.args))
  1806.         diggingArea = region.shiftRegion(diggingArea, cumDelta)
  1807.         local layers = region.createLayersFromArea(diggingArea, config.layerSeparationAxis)
  1808.         local chestData = region.reserveChests(diggingArea)
  1809.  
  1810.         --place chunk loader
  1811.         local chunkLoaderPos = table.remove(chestData.reserved)
  1812.         local chunkLoaderIndex = helper.getIndex(chunkLoaderPos.x, chunkLoaderPos.y, chunkLoaderPos.z)
  1813.         local blockAbove = {x = chunkLoaderPos.x, y = chunkLoaderPos.y+1, z = chunkLoaderPos.z}
  1814.         if prevPos then
  1815.             prevPos.preserve = true
  1816.             diggingArea[helper.getIndex(prevPos.x, prevPos.y, prevPos.z)] = prevPos
  1817.         end
  1818.         if not goToCoords(blockAbove, diggingArea, PATHFIND_ANYWHERE_NONPRESERVED) then
  1819.             helper.printError("Could not navigate to new chunk loader position, aborting...")
  1820.             return
  1821.         end
  1822.         repeat digBelow() until execWithSlot(wrapt.placeDown, CHUNK_LOADER_SLOT)
  1823.         diggingArea[chunkLoaderIndex].preserve = true
  1824.  
  1825.         --remove old chunk loader
  1826.         if prevPos then
  1827.             local prevBlockAbove = {x = prevPos.x, y = prevPos.y+1, z = prevPos.z}
  1828.             if not goToCoords(prevBlockAbove, diggingArea, PATHFIND_ANYWHERE_NONPRESERVED) then
  1829.                 helper.printError("Could not navigate to previous chunkloader, aborting")
  1830.             end
  1831.             execWithSlot(digBelow, CHUNK_LOADER_SLOT)
  1832.             if not goToCoords(blockAbove, diggingArea, PATHFIND_ANYWHERE_NONPRESERVED) then
  1833.                 helper.printError("Could not navigate back from old loader, aborting...")
  1834.                 return
  1835.             end
  1836.         end
  1837.  
  1838.         --dig the region
  1839.         executeDigging(layers, diggingArea, chestData, config)
  1840.  
  1841.         prevPos = chunkLoaderPos
  1842.         cumDelta = {x = cumDelta.x + config.mineLoopOffset.x,y = cumDelta.y + config.mineLoopOffset.y,z = cumDelta.z + config.mineLoopOffset.z}
  1843.     end
  1844. end
  1845.  
  1846. local function launchMineLoop(config, autostart)
  1847.     helper.print("Verifying mineLoopCommand...")
  1848.     local shape = ui.parseProgram(config.mineLoopCommand)
  1849.     if not shape then
  1850.         helper.printError("mineLoopCommand is invalid")
  1851.         return
  1852.     end
  1853.     local areaToValidate = shape.shape.generate(table.unpack(shape.args))
  1854.     local validationResult = region.validateRegion(areaToValidate)
  1855.     if validationResult ~= SUCCESS then
  1856.         ui.showValidationError(validationResult)
  1857.         return
  1858.     end
  1859.  
  1860.     if not autostart then
  1861.         ui.printInfo()
  1862.         helper.print("Press Enter to start the loop")
  1863.         helper.read()
  1864.     end
  1865.     executeMineLoop(config)
  1866. end
  1867.  
  1868.  
  1869.  
  1870. local function main(...)
  1871.     local args = {...}
  1872.     local config = helper.readOnlyTable(cfg.processConfig())
  1873.     slots.assignSlots(config)
  1874.  
  1875.     local default = args[1] == "def"
  1876.  
  1877.     if config.mineLoop then
  1878.         launchMineLoop(config, default)
  1879.         return
  1880.     end
  1881.    
  1882.     launchDigging(config, default)
  1883. end
  1884.  
  1885. main(...)
Add Comment
Please, Sign In to add comment