SHOW:
|
|
- or go back to the newest paste.
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(...) |