-
Notifications
You must be signed in to change notification settings - Fork 9
/
mine.lua
1916 lines (1705 loc) · 52.2 KB
/
mine.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
local helper = {}
--#region CONFIG----
local cfg = {}
cfg.localConfig = {
--if true, the program will attempt to download and use the config from remoteConfigPath
--useful if you have many turtles and you don't want to change the config of each one manually
useRemoteConfig = false,
remoteConfigPath = "http://localhost:33344/config.lua",
--this command will be used when the program is started as "mine def" (mineLoop overrides this command)
defaultCommand = "cube 3 3 8",
--false: build walls/floor/ceiling everywhere, true: only where there is fluid
plugFluidsOnly = true,
--maximum taxicab distance from enterance point when collecting ores, 0 = disable ore traversal
oreTraversalRadius = 0,
--layer mining order, use "z" for branch mining, "y" for anything else
--"y" - mine top to bottom layer by layer, "z" - mine forward in vertical slices
layerSeparationAxis="y",
--false: use regular chests, true: use entangled chests
--if true, the turtle will place a single entangled chest to drop off items and break it afterwards.
--tested with chests from https://www.curseforge.com/minecraft/mc-mods/kibe
useEntangledChests = false,
--false: refuel from inventory, true: refuel from (a different) entangled chest
--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.
useFuelEntangledChest = false,
--true: use two chuck loaders to mine indefinitely without moving into unloaded chunks.
--This doesn't work with chunk loaders from https://www.curseforge.com/minecraft/mc-mods/kibe, but might work with some other mod.
--After an area is mined, the turtle will shift by mineLoopOffset and execute mineLoopCommand
--mineLoopCommand is used in place of defaultCommand when launching as "mine def"
mineLoop = false,
mineLoopOffset = {x=0, y=0, z=8},
mineLoopCommand = "rcube 1 1 1"
}
cfg.getRemoteConfig = function(remotePath)
local handle = http.get(remotePath)
if not handle then
helper.printError("Server not responding, using local")
return nil
end
local data = handle.readAll()
handle.close()
local deser = textutils.unserialise(data)
if not deser then
helper.printError("Couldn't parse remote config, using local")
return nil
end
for key,_ in pairs(cfg.localConfig) do
if deser[key] == nil then
helper.printError("No key", key, "in remote config, using local")
return nil
end
end
return deser
end
cfg.processConfig = function()
local config = cfg.localConfig
if cfg.localConfig.useRemoteConfig then
helper.print("Downloading config..")
local remoteConfig = cfg.getRemoteConfig(config.remoteConfigPath)
config = remoteConfig or cfg.localConfig
end
return config
end
--#endregion
--#region CONSTANTS----
local SOUTH = 0
local WEST = 1
local NORTH = 2
local EAST = 3
local CHEST_SLOT = "chest_slot"
local BLOCK_SLOT = "block_slot"
local FUEL_SLOT = "fuel_slot"
local FUEL_CHEST_SLOT = "fuel_chest_slot"
local CHUNK_LOADER_SLOT = "chunk_loader_slot"
local MISC_SLOT = "misc_slot"
local ACTION = 0
local TASK = 1
local PATHFIND_INSIDE_AREA = 0
local PATHFIND_OUTSIDE_AREA = 1
local PATHFIND_INSIDE_NONPRESERVED_AREA = 2
local PATHFIND_ANYWHERE_NONPRESERVED = 3
local SUCCESS = 0
local FAILED_NONONE_COMPONENTCOUNT = 1
local FAILED_TURTLE_NOTINREGION = 2
local FAILED_REGION_EMPTY = 4
local REFUEL_THRESHOLD = 500
local RETRY_DELAY = 3
local FALLING_BLOCKS = {
["minecraft:gravel"] = true,
["minecraft:sand"] = true
}
--#endregion
--#region WRAPPER----
local wrapt = (function()
local self = {
selectedSlot = 1,
direction = SOUTH,
x = 0,
y = 0,
z = 0
}
local public = {}
--wrap everything in turtle
for key,value in pairs(turtle) do
public[key] = value
end
--init turtle selected slot
turtle.select(self.selectedSlot);
public.select = function(slot)
if self.selectedSlot ~= slot then
turtle.select(slot)
self.selectedSlot = slot
end
end
public.forward = function()
local success = turtle.forward()
if not success then
return success
end
if self.direction == EAST then
self.x = self.x + 1
elseif self.direction == WEST then
self.x = self.x - 1
elseif self.direction == SOUTH then
self.z = self.z + 1
elseif self.direction == NORTH then
self.z = self.z - 1
end
return success
end
public.up = function()
local success = turtle.up()
if not success then
return success
end
self.y = self.y + 1
return success
end
public.down = function()
local success = turtle.down()
if not success then
return success
end
self.y = self.y - 1
return success
end
public.turnRight = function()
local success = turtle.turnRight()
if not success then
return success
end
self.direction = self.direction + 1
if self.direction > 3 then
self.direction = 0
end
return success
end
public.turnLeft = function()
local success = turtle.turnLeft()
if not success then
return success
end
self.direction = self.direction - 1
if self.direction < 0 then
self.direction = 3
end
return success
end
public.getX = function() return self.x end
public.getY = function() return self.y end
public.getZ = function() return self.z end
public.getDirection = function() return self.direction end
public.getPosition = function() return {x = self.x, y = self.y, z = self.z, direction = self.direction} end
return public
end)()
--#endregion
--#region TYPE ALIASES---
-- use https://marketplace.visualstudio.com/items?itemName=sumneko.lua for intellisense
---@alias vec3 {x: number, y: number, z: number}
---@alias vec3dir {x: number, y: number, z: number, direction: integer}
--#endregion
--#region DEBUG FUNCTIONS----
local debug = {
dumpPoints = function(points, filename)
local file = fs.open(filename .. ".txt","w")
for _, value in pairs(points) do
local line =
tostring(value.x)..","..
tostring(value.y)..","..
tostring(value.z)..","
if value.adjacent then
line = line .. "0,128,0"
elseif value.inacc then
line = line .. "255,0,0"
elseif value.triple then
line = line .. "0,255,0"
elseif value.checkedInFirstPass then
line = line .. "0,0,0"
else
helper.printError("Invalid block type when dumping points")
end
if math.abs(value.z) < 100 then
file.writeLine(line)
end
end
file.close()
end,
---@param points {[string]:vec3}
---@param filename string
dumpPath = function(points, filename)
local file = fs.open(filename .. ".txt","w")
for key, value in pairs(points) do
if tonumber(key) then
local line =
tostring(value.x)..","..
tostring(value.y)..","..
tostring(value.z)
file.writeLine(line)
end
end
file.close()
end,
dumpLayers = function(layers, filename)
for index, layer in ipairs(layers) do
local file = fs.open(filename .. tostring(index) .. ".txt","w")
for _, value in ipairs(layer) do
local line =
tostring(value.x)..","..
tostring(value.y)..","..
tostring(value.z)..","
if value.adjacent then
line = line .. "0,128,0"
elseif value.inacc then
line = line .. "255,0,0"
elseif value.triple then
line = line .. "0,255,0"
elseif value.checkedInFirstPass then
line = line .. "0,0,0"
else
helper.printError("Invalid block type when dumping layers")
end
file.writeLine(line)
end
file.close()
end
end
}
--#endregion
--#region HELPER FUNCTIONS----
---@param dX number
---@param dZ number
helper.deltaToDirection = function(dX, dZ)
if dX > 0 then
return EAST
elseif dX < 0 then
return WEST
elseif dZ > 0 then
return SOUTH
elseif dZ < 0 then
return NORTH
end
error("Invalid delta", 2)
end
---@param T table
---@return integer
helper.tableLength = function(T)
local count = 0
for _ in pairs(T) do count = count + 1 end
return count
end
---@param x number
---@param y number
---@param z number
helper.getIndex = function(x, y, z)
return tostring(x) .. "," .. tostring(y) .. "," .. tostring(z)
end
---@param pos1 vec3
---@param pos2 vec3
helper.isPosEqual = function(pos1, pos2)
return pos1.x == pos2.x and pos1.y == pos2.y and pos1.z == pos2.z
end
---@param pos vec3
---@return {[string]: vec3}
helper.getSurroundings = function(pos)
return {
[helper.getIndex(pos.x + 1, pos.y, pos.z)] = {x = pos.x + 1, y = pos.y, z = pos.z},
[helper.getIndex(pos.x - 1, pos.y, pos.z)] = {x = pos.x - 1, y = pos.y, z = pos.z},
[helper.getIndex(pos.x, pos.y + 1, pos.z)] = {x = pos.x, y = pos.y + 1, z = pos.z},
[helper.getIndex(pos.x, pos.y - 1, pos.z)] = {x = pos.x, y = pos.y - 1, z = pos.z},
[helper.getIndex(pos.x, pos.y, pos.z + 1)] = {x = pos.x, y = pos.y, z = pos.z + 1},
[helper.getIndex(pos.x, pos.y, pos.z - 1)] = {x = pos.x, y = pos.y, z = pos.z - 1}
}
end
---@param currentPos vec3dir
---@return vec3
helper.getForwardPos = function(currentPos)
local newPos = {x = currentPos.x, y = currentPos.y, z = currentPos.z}
if currentPos.direction == EAST then
newPos.x = newPos.x + 1
elseif currentPos.direction == WEST then
newPos.x = newPos.x - 1
elseif currentPos.direction == SOUTH then
newPos.z = newPos.z + 1
elseif currentPos.direction == NORTH then
newPos.z = newPos.z - 1
end
return newPos
end
---@param from vec3
---@param to vec3
---@return number
helper.distance = function(from, to)
return math.abs(from.x - to.x) + math.abs(from.y - to.y) + math.abs(from.z - to.z)
end
---@param number number
---@return integer
helper.sign = function(number)
return number > 0 and 1 or (number == 0 and 0 or -1)
end
---@param number number
---@param a number
---@param b number
helper.inRange = function(number, a, b)
return number >= a and number <= b
end
helper.splitString = function(string, separator)
if not separator then separator = "%s" end
local split = {}
for str in string.gmatch(string, "([^"..separator.."]+)") do
table.insert(split, str)
end
return split
end
helper.stringEndsWith = function (str, ending)
return ending == "" or str:sub(-#ending) == ending
end
helper.tableContains = function(table, element, comparison)
for _, value in pairs(table) do
if comparison(value, element) then
return true
end
end
return false
end
helper.readOnlyTable = function(t)
local mt = {
__index = t,
__newindex = function(_, _, _)
error("Cannot write into a read-only table", 2)
end
}
local proxy = {}
setmetatable(proxy, mt)
return proxy
end
helper.osYield = function()
---@diagnostic disable-next-line: undefined-field
os.queueEvent("fakeEvent");
---@diagnostic disable-next-line: undefined-field
os.pullEvent();
end
helper.printError = function(...)
term.setTextColor(colors.red)
print(...)
end
helper.printWarning = function(...)
term.setTextColor(colors.yellow)
print(...)
end
helper.print = function(...)
term.setTextColor(colors.white)
print(...)
end
helper.write = function(...)
term.setTextColor(colors.white)
term.write(...)
end
helper.read = function()
term.setTextColor(colors.lightGray)
local data = read()
term.setTextColor(colors.white)
return data
end
--#endregion
--#region SHAPE LIBRARY----
local shapes = {}
shapes.custom = {
command = "custom",
shortDesc = "custom <filename>",
longDesc = "Executes a function named \"generate\" from the specified file and uses the data it returns as a shape",
args = {"str"},
generate = function(filename)
local env = {
table=table,
fs=fs,
http=http,
io=io,
math=math,
os=os,
parallel=parallel,
string=string,
vector=vector,
textutils=textutils
}
local chunk, err = loadfile(filename, "bt", env)
if err then
helper.printError("Couldn't load file:", err);
return {}
end
chunk();
if type(env.generate) ~= "function" then
helper.printError("File does not contain generate function")
return {}
end
local generated = env.generate()
if type(generated) ~= "table" then
helper.printError("Generate function didn't return a table")
return {}
end
local blocks = {}
for _, value in ipairs(generated) do
if type(value.x) ~= "number" or type(value.y) ~= "number" or type(value.z) ~= "number" then
helper.printError("Invalid coordinates entry:", textutils.serialize(value))
return {}
end
blocks[helper.getIndex(value.x, value.y, value.z)] = {x = value.x, y = value.y, z = value.z}
end
return blocks
end
}
shapes.sphere = {
command = "sphere",
shortDesc = "sphere <diameter>",
longDesc = "Mine a sphere of diameter <diameter>, starting from it's bottom center",
args = {"2.."},
generate = function(diameter)
local radius = math.ceil(diameter / 2.0)
local radiusSq = (diameter / 2.0) * (diameter / 2.0)
local blocks = {}
local first = nil
for j=-radius,radius do
for i=-radius,radius do
for k=-radius,radius do
if diameter % 2 == 0 then
if math.pow(i+0.5, 2) + math.pow(j+0.5, 2) + math.pow(k+0.5, 2) < radiusSq then
if not first then
first = j
end
blocks[helper.getIndex(i,j-first,k)] = {x = i, y = j-first, z = k}
end
else
if math.pow(i, 2) + math.pow(j, 2) + math.pow(k, 2) < radiusSq then
if not first then
first = j
end
blocks[helper.getIndex(i,j-first,k)] = {x = i, y = j-first, z = k}
end
end
end
end
helper.osYield()
end
return blocks
end
}
shapes.cuboid = {
command="cube",
shortDesc = "cube <left> <up> <forward>",
longDesc = "Mine a cuboid of a specified size. Use negative values to dig in an opposite direction",
args= {"..-2 2..", "..-2 2..", "..-2 2.."},
generate = function(x, y, z)
local blocks = {}
local sX = helper.sign(x)
local sY = helper.sign(y)
local sZ = helper.sign(z)
local tX = sX * (math.abs(x) - 1)
local tY = sY * (math.abs(y) - 1)
local tZ = sZ * (math.abs(z) - 1)
for i=0,tX,sX do
for j=0,tY,sY do
for k=0,tZ,sZ do
blocks[helper.getIndex(i,j,k)] = {x = i, y = j, z = k}
end
end
helper.osYield()
end
return blocks
end
}
shapes.centeredCuboid = {
command="rcube",
shortDesc = "rcube <leftR> <upR> <forwardR>",
longDesc = "Mine a cuboid centered on the turtle. Each dimension is a \"radius\", so typing \"rcube 1 1 1\" will yield a 3x3x3 cube",
args={"1..", "1..", "1.."},
generate = function(rX, rY, rZ)
local blocks = {}
for i=-rX,rX do
for j=-rY,rY do
for k=-rZ,rZ do
blocks[helper.getIndex(i,j,k)] = {x = i, y = j, z = k}
end
end
helper.osYield()
end
return blocks
end
}
shapes.branch = {
command="branch",
shortDesc = "branch <branchLen> <shaftLen>",
longDesc = "Branch-mining. <branchLen> is the length of each branch, <shaftLen> is the length of the main shaft",
args={"0..", "3.."},
generate = function(xRadius, zDepth)
local blocks = {}
--generate corridor
for x=-1,1 do
for y=0,2 do
for z=0,zDepth-1 do
blocks[helper.getIndex(x,y,z)] = {x = x, y = y, z = z}
end
end
end
--generate branches
for z=2,zDepth-1,2 do
local y = (z % 4 == 2) and 0 or 2
for x=0,xRadius-1 do
blocks[helper.getIndex(x+2,y,z)] = {x = x+2, y = y, z = z}
blocks[helper.getIndex(-x-2,y,z)] = {x = -x-2, y = y, z = z}
end
end
return blocks
end
}
--#endregion
--#region REGION PROCESSING----
local region = {}
region.createShape = function()
local blocks = {}
for i=-7,7 do
for j=0,7 do
for k=-7,7 do
if i*i + j*j + k*k < 2.5*2.5 then
blocks[helper.getIndex(i,j,k)] = {x = i, y = j, z = k}
end
end
end
end
return blocks
end
region.cloneBlocks = function(allBlocks)
local cloned = {}
for key,value in pairs(allBlocks) do
local blockClone = {
x = value.x,
y = value.y,
z = value.z
}
cloned[key] = blockClone
end
return cloned
end
--mark blocks that are next to walls
region.markAdjacentInPlace = function(allBlocks)
for key,value in pairs(allBlocks) do
local xMinus = allBlocks[helper.getIndex(value.x - 1, value.y, value.z)]
local xPlus = allBlocks[helper.getIndex(value.x + 1, value.y, value.z)]
local yMinus = allBlocks[helper.getIndex(value.x, value.y - 1, value.z)]
local yPlus = allBlocks[helper.getIndex(value.x, value.y + 1, value.z)]
local zMinus = allBlocks[helper.getIndex(value.x, value.y, value.z - 1)]
local zPlus = allBlocks[helper.getIndex(value.x, value.y, value.z + 1)]
if not xMinus or not xPlus or not yMinus or not yPlus or not zMinus or not zPlus then
value.adjacent = true
if yMinus then yMinus.checkedInFirstPass = true end
if yPlus then yPlus.checkedInFirstPass = true end
end
end
end
--mark positions where the turtle can check both the block above and the block below
region.markTripleInPlace = function(allBlocks)
local minY = 9999999;
for key,value in pairs(allBlocks) do
if not value.checkedInFirstPass and not value.adjacent then
minY = math.min(minY, value.y)
end
end
for key,value in pairs(allBlocks) do
if not value.checkedInFirstPass and not value.adjacent then
local offset = (value.y - minY) % 3;
if offset == 0 then
local blockAbove = allBlocks[helper.getIndex(value.x, value.y+1, value.z)]
if blockAbove ~= nil and blockAbove.checkedInFirstPass ~= true then
value.checkedInFirstPass = true
else
value.inacc = true
end
elseif offset == 1 then
value.triple = true
elseif offset == 2 and allBlocks[helper.getIndex(value.x, value.y-1, value.z)] ~= nil then
local blockBelow = allBlocks[helper.getIndex(value.x, value.y-1, value.z)]
if blockBelow ~= nil and blockBelow.checkedInFirstPass ~= true then
value.checkedInFirstPass = true
else
value.inacc = true
end
end
end
end
end
region.findConnectedComponents = function(allBlocks)
local visited = {}
local components = {}
local counter = 0
local lastTime = os.clock()
for key,value in pairs(allBlocks) do
if not visited[key] then
local component = {}
local toVisit = {[key] = value}
while true do
local newToVisit = {}
local didSomething = false
for currentKey,current in pairs(toVisit) do
didSomething = true
visited[currentKey] = true
component[currentKey] = current
local minusX = helper.getIndex(current.x-1, current.y, current.z)
local plusX = helper.getIndex(current.x+1, current.y, current.z)
local minusY = helper.getIndex(current.x, current.y-1, current.z)
local plusY = helper.getIndex(current.x, current.y+1, current.z)
local minusZ = helper.getIndex(current.x, current.y, current.z-1)
local plusZ = helper.getIndex(current.x, current.y, current.z+1)
if allBlocks[minusX] and not visited[minusX] then newToVisit[minusX] = allBlocks[minusX] end
if allBlocks[plusX] and not visited[plusX] then newToVisit[plusX] = allBlocks[plusX] end
if allBlocks[minusY] and not visited[minusY] then newToVisit[minusY] = allBlocks[minusY] end
if allBlocks[plusY] and not visited[plusY] then newToVisit[plusY] = allBlocks[plusY] end
if allBlocks[minusZ] and not visited[minusZ] then newToVisit[minusZ] = allBlocks[minusZ] end
if allBlocks[plusZ] and not visited[plusZ] then newToVisit[plusZ] = allBlocks[plusZ] end
counter = counter + 1
if counter % 50 == 0 then
local curTime = os.clock()
if curTime - lastTime > 1 then
lastTime = curTime
helper.osYield()
end
end
end
toVisit = newToVisit
if not didSomething then break end
end
table.insert(components, component)
end
end
return components
end
region.separateLayers = function(allBlocks, direction)
if direction ~= "y" and direction ~= "z" then
error("Invalid direction value", 2)
end
local layers = {}
local min = 999999
local max = -999999
for key,value in pairs(allBlocks) do
if not (not value.adjacent and value.checkedInFirstPass) then
local index = direction == "y" and value.y or value.z
if not layers[index] then
layers[index] = {}
end
layers[index][key] = value
min = math.min(min, index)
max = math.max(max, index)
end
end
if min == 999999 then
error("There should be at least one block in passed table", 2)
end
local reassLayers = {}
for key, value in pairs(layers) do
local index = direction == "y" and (max - min+1) - (key - min) or (key - min + 1)
reassLayers[index] = value
end
return reassLayers
end
region.sortFunction = function(a, b)
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))
end
region.findClosestPoint = function(location, points, usedPoints)
local surroundings = helper.getSurroundings(location)
local existingSurroundings = {}
local foundClose = false
for key,value in pairs(surroundings) do
if points[key] and not usedPoints[key] then
table.insert(existingSurroundings, value)
foundClose = true
end
end
if foundClose then
table.sort(existingSurroundings, region.sortFunction)
local closest = table.remove(existingSurroundings)
return points[helper.getIndex(closest.x, closest.y, closest.z)]
end
local minDist = 999999
local minValue = nil
for key,value in pairs(points) do
if not usedPoints[key] then
local dist = helper.distance(value,location)
if dist < minDist then
minDist = dist
minValue = value
end
end
end
if not minValue then
return nil
end
return minValue
end
--travelling salesman, nearest neighbour method
region.findOptimalBlockOrder = function(layers)
local newLayers = {}
local lastTime = os.clock()
for index, layer in ipairs(layers) do
local newLayer = {}
local usedPoints = {}
local current = region.findClosestPoint({x=0,y=0,z=0}, layer, usedPoints)
repeat
usedPoints[helper.getIndex(current.x, current.y, current.z)] = true
table.insert(newLayer, current)
current = region.findClosestPoint(current, layer, usedPoints)
local curTime = os.clock()
if curTime - lastTime > 1 then
lastTime = curTime
helper.osYield()
end
until not current
newLayers[index]=newLayer
end
return newLayers
end
region.createLayersFromArea = function(diggingArea, direction)
local blocksToProcess = region.cloneBlocks(diggingArea)
region.markAdjacentInPlace(blocksToProcess)
region.markTripleInPlace(blocksToProcess)
local layers = region.separateLayers(blocksToProcess, direction)
local orderedLayers = region.findOptimalBlockOrder(layers)
return orderedLayers
end
region.shiftRegion = function(shape, delta)
local newShape = {}
for key,value in pairs(shape) do
local newPos = {x = value.x + delta.x, y = value.y + delta.y, z = value.z + delta.z}
newShape[helper.getIndex(newPos.x, newPos.y, newPos.z)] = newPos
end
return newShape
end
region.reserveChests = function(blocks)
local blocksCopy = {}
local counter = 0
for _,value in pairs(blocks) do
counter = counter + 1
blocksCopy[counter] = value
end
table.sort(blocksCopy, function(a,b)
if a.y ~= b.y then return a.y > b.y
elseif a.z~=b.z then return a.z > b.z
else return a.x > b.x end
end)
return {reserved=blocksCopy}
end
region.validateRegion = function(blocks)
local result = SUCCESS
--there must be only one connected component
local components = region.findConnectedComponents(blocks)
if helper.tableLength(components) == 0 then
result = result + FAILED_REGION_EMPTY
end
if helper.tableLength(components) > 1 then
result = result + FAILED_NONONE_COMPONENTCOUNT
end
--the turtle must be inside of the region
if not blocks[helper.getIndex(0,0,0)] then
result = result + FAILED_TURTLE_NOTINREGION
end
return result
end
--#endregion
--#region PATHFINDING----
local path = {}
path.pathfindUpdateNeighbour = function(data, neighbour, destination, origin, neighbourIndex)
local originIndex = helper.getIndex(origin.x, origin.y, origin.z)
if not data[neighbourIndex] then
data[neighbourIndex] = {}
data[neighbourIndex].startDist = data[originIndex].startDist + 1
data[neighbourIndex].heuristicDist = helper.distance(neighbour, destination)
data[neighbourIndex].previous = origin
elseif data[originIndex].startDist + 1 < data[neighbourIndex].startDist then
data[neighbourIndex].startDist = data[originIndex].startDist + 1
data[neighbourIndex].previous = origin
end
end
path.traversable = function(area, index, pathfindingType)
if pathfindingType == PATHFIND_INSIDE_AREA then
return area[index] ~= nil
elseif pathfindingType == PATHFIND_INSIDE_NONPRESERVED_AREA then
return not not (area[index] and not area[index].preserve)
elseif pathfindingType == PATHFIND_OUTSIDE_AREA then
return area[index] == nil
elseif PATHFIND_ANYWHERE_NONPRESERVED then
return area[index] == nil or not area[index].preserve
end
error("Unknown pathfinding type", 3)
end
path.pathfind = function(from, to, allBlocks, pathfindingType)
if helper.isPosEqual(from,to) then
return {length = 0}
end
local data = {}
local openSet = {}
local closedSet = {}
local current = from
local curIndex = helper.getIndex(from.x, from.y, from.z)
openSet[curIndex] = current
data[curIndex] = {}
data[curIndex].startDist = 0
data[curIndex].heuristicDist = helper.distance(current, to)
while true do
local surroundings = helper.getSurroundings(current)
for key,value in pairs(surroundings) do
if path.traversable(allBlocks,key,pathfindingType) and not closedSet[key] then
path.pathfindUpdateNeighbour(data, value, to, current, key)
openSet[key] = value
end
end
closedSet[curIndex] = current
openSet[curIndex] = nil
local minN = 9999999
local minValue = nil
for key,value in pairs(openSet) do
local sum = data[key].startDist + data[key].heuristicDist
if sum < minN then
minN = sum
minValue = value
end
end
current = minValue;
if current == nil then
helper.printWarning("No path from", from.x, from.y, from.z, "to", to.x, to.y, to.z)
return false
end
curIndex = helper.getIndex(current.x, current.y, current.z)
if helper.isPosEqual(current,to) then
break
end
end
local almostFinalPath = {}
local counter = 1
while current ~= nil do
almostFinalPath[counter] = current
counter = counter + 1
current = data[helper.getIndex(current.x, current.y, current.z)].previous
end
local reversedPath = {}
local newCounter = 1
for i=counter-1,1,-1 do
reversedPath[newCounter] = almostFinalPath[i]
newCounter = newCounter + 1
end
reversedPath.length = newCounter-1;
return reversedPath
end
--#endregion
--#region SLOT MANAGER----
local slots = (function()
local slotAssignments = {}
local assigned = false
local public = {}
local slotDesc = nil
local generateDescription = function(config)
local desc = {
[CHEST_SLOT] = "Chests",
[BLOCK_SLOT] = "Cobblestone",
[FUEL_SLOT] = "Fuel (only coal supported)",
[FUEL_CHEST_SLOT] = "Fuel Entangled Chest",
[CHUNK_LOADER_SLOT] = "2 Chunk Loaders",
}
if config.useEntangledChests then
desc[CHEST_SLOT] = "Entangled Chest"
end
return desc
end
public.assignSlots = function(config)
if assigned then
error("Slots have already been assigned", 2)
end
assigned = true
local currentSlot = 1
slotAssignments[CHEST_SLOT] = currentSlot
currentSlot = currentSlot + 1
if config.useFuelEntangledChest then
slotAssignments[FUEL_CHEST_SLOT] = currentSlot
currentSlot = currentSlot + 1
else
slotAssignments[FUEL_SLOT] = currentSlot
currentSlot = currentSlot + 1
end
if config.mineLoop then
slotAssignments[CHUNK_LOADER_SLOT] = currentSlot
currentSlot = currentSlot + 1
end
slotAssignments[BLOCK_SLOT] = currentSlot