-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathdig.lua
More file actions
1522 lines (1278 loc) · 43.9 KB
/
dig.lua
File metadata and controls
1522 lines (1278 loc) · 43.9 KB
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
--- SimplifyDig 2.0: ComputerCraft Digging Utility.
---
--- This program intends to provide an easy-to-use interface for digging
--- different shapes with a ComputerCraft turtle.
--- It supports digging cuboids, tunnels, staircases, and bridges.
---
--- See the README.md for more information and usage instructions.
---
--- Copyright 2026 Matthew Wilbern (Fatboychummy)
--- Permission is hereby granted, free of charge, to any person obtaining a copy
--- of this software and associated documentation files (the “Software”), to
--- deal in the Software without restriction, including without limitation the
--- rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
--- sell copies of the Software, and to permit persons to whom the Software is
--- furnished to do so, subject to the following conditions:
---
--- The above copyright notice and this permission notice shall be included in
--- all copies or substantial portions of the Software.
---
--- THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
--- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
--- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
--- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
--- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
--- FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
--- IN THE SOFTWARE.
package.path = package.path .. ";./lib/?.lua;./lib/?/init.lua"
-- Set up logging.
local minilogger = require "minilogger"
local log = minilogger.new("main")
local root = require "filesystem"
local pp = root:programPath()
local DTR = require "deterministic_turtle_recovery"
local MAX_HEIGHT = 320 -- Minecraft maximum world height.
-- Argument parsing.
local argparse = require "simple_argparse"
local parser = argparse.new_parser(
"Simplify Digging 2.0",
"A utility to simplify digging operations with a turtle."
)
parser.add_option(
"shape",
"The shape to dig. Valid options are: cuboid, staircase, bridge.",
true
)
parser.add_flag(
"l",
"left",
"If set, the turtle will dig to the left instead of the right."
)
parser.add_flag(
"r",
"right",
"If set, the turtle will dig to the right instead of the left."
)
parser.add_flag(
"u",
"up",
"If set, the turtle will dig upwards instead of downwards."
)
parser.add_flag(
"d",
"down",
"If set, the turtle will dig downwards instead of upwards."
)
parser.add_flag(
"f",
"fuel",
"If set, allows the turtle to consume items like coal for fuel during digging."
)
parser.add_flag(
"n",
"noinv",
"Disables automatic inventory management, dumping every item in the inventory."
)
parser.add_option(
"broadcast",
"Specifies a path to a file that will handle broadcasting status updates from the turtle. Specify an empty string to disable.",
""
)
parser.add_option(
"loglevel",
"Sets the logging level for the program. Valid levels are: debug, info, warning, error.",
"info"
)
parser.add_option(
"file",
"Specifies a path to an existing state file to resume from."
)
parser.add_option(
"save",
"Specifies a path to save the turtle's state to."
)
-- Get the shape before building the rest of the parser.
local initial_parsed = parser.parse({...})
local shape = initial_parsed.options.shape
if initial_parsed.options.loglevel then
local level = initial_parsed.options.loglevel:upper()
if level ~= "DEBUG" and level ~= "INFO" and level ~= "WARNING" and level ~= "ERROR" then
log.error("Invalid log level specified: '%s'. Valid levels are: debug, info, warning, error.", level)
return
end
---@cast level "debug"|"info"|"warning"|"error"
minilogger.set_log_level(
minilogger.LOG_LEVELS[level]
)
end
local alias_map = {
cuboid = "cuboid",
cube = "cuboid",
box = "cuboid",
tunnel = "cuboid",
staircase = "staircase",
stair = "staircase",
stairs = "staircase",
bridge = "bridge",
}
local shape_type
if shape then
shape_type = alias_map[shape]
if not shape_type then
log.error("Invalid shape specified: '%s'. Valid options are: cuboid, tunnel, staircase, bridge.", shape)
return
end
end
-- Add shape-specific arguments.
if shape_type == "cuboid" then
parser.add_option(
"forwardlength",
"The length to dig forward.",
16
)
parser.add_option(
"width",
"The width to dig.",
16
)
parser.add_option(
"height",
"The height to dig. If not specified, will dig until bedrock.",
16
)
parser.add_flag(
"q",
"quarry",
"If set, the turtle will dig downwards until bedrock, ignoring the 'height' parameter."
)
elseif shape_type == "staircase" then
parser.add_option(
"forwardlength",
"The number of steps to dig.",
16
)
parser.add_option(
"height",
"The height of the passage for each step. Default is 3.",
3
)
parser.add_flag(
"s",
"stairs",
"If set, the turtle will place stairs in the staircase (requires stairs in inventory)."
)
parser.add_flag(
"t",
"torches",
"If set, the turtle will place torches in the staircase for lighting (requires torches in inventory)."
)
parser.add_option(
"torchinterval",
"The interval (in steps) at which to place torches if enabled.",
10
)
elseif shape_type == "bridge" then
parser.add_option(
"forwardlength",
"The length to dig the bridge.",
16
)
parser.add_flag(
"s",
"safe",
"If set, the turtle will place blocks beside itself as well to create a safe walkway."
)
parser.add_flag(
"r",
"roof",
"If set, the turtle will place blocks 2 blocks above itself to create a roof."
)
parser.add_flag(
"t",
"torches",
"If set, the turtle will place torches on the bridge for lighting (requires torches in inventory)."
)
parser.add_option(
"torchinterval",
"The interval (in blocks) at which to place torches if enabled.",
16
)
end
-- Final parse.
local parsed = parser.parse({...})
--- Set up automatic rebooting (save a `.lua` file to `/startup/` that re-runs this program with the same arguments).
---@return DTR dtr The initialized DTR instance.
local function setup_reboot()
if parsed.options.save then
log.info("Setting up automatic reboot")
local data_dir = pp:at("data")
local startup_dir = root:at("startup")
if not data_dir:exists() then
data_dir:mkdir()
end
if not startup_dir:exists() then
startup_dir:mkdir()
end
local reboot_file = startup_dir:file("9999_simplifydig_reboot.lua")
local running_program = shell.getRunningProgram()
local flags = ""
for flag, value in pairs(parsed.flags) do
if value then
flags = flags .. ("--%s "):format(flag)
end
end
local options = ""
for option, value in pairs(parsed.options) do
log.infof("Option %s=%s", option, tostring(value))
if option == "save" then option = "load" end -- The reboot file should mark the file to load from.
options = options .. ("--%s=%s "):format(option, tostring(value))
end
log.infof("Reboot file will have:\n Command: %s\n Flags: %s\n Options: %s", running_program, flags, options)
reboot_file:write(("shell.run('%s %s %s')"):format(running_program, flags, options))
return DTR.new(parsed.options.save)
end
if parsed.options.load then
log.infof("Loading state from file '%s' for recovery.", parsed.options.load)
local dtr = DTR.new(parsed.options.load)
dtr:load_state()
return dtr
end
log.info("No recovery file specified. Creating temporary file but not automatically rebooting.")
return DTR.new(tostring(pp:at("data"):file("temp_state.lua")))
end
--- Clean up the automatic reboot, deleting the auto save file if it exists.
local function cleanup_reboot()
log.info("Cleaning up reboot files.")
local startup_dir = root:at("startup")
local reboot_file = startup_dir:file("9999_simplifydig_reboot.lua")
if reboot_file:exists() then
reboot_file:delete()
end
if parsed.options.save or parsed.options.load then
local save_file = root:file(parsed.options.save or parsed.options.load)
if save_file:exists() then
save_file:delete()
end
else
-- It's under the temp file.
local temp_file = pp:at("data"):file("temp_state.lua")
if temp_file:exists() then
temp_file:delete()
end
end
end
--- Wraps a dtr instance's turtle functions such that they can be passed to the shape digging functions without modification.
---@param dtr DTR The DTR instance to wrap.
---@return WrappedDTR wrapped The wrapped DTR instance.
local function wrap_dtr(dtr)
---@class WrappedDTR
local wrapped = {
hit_bedrock = false
}
---@return boolean success
---@return string? reason
function wrapped.forward()
local ok, err = dtr:forward()
if err == "bedrock" then
wrapped.hit_bedrock = true
end
while not ok do
dtr:dig()
ok, err = dtr:forward()
end
return ok, err
end
---@return boolean success
---@return string? reason
function wrapped.back()
local ok, err = dtr:back()
if err == "bedrock" then
wrapped.hit_bedrock = true
end
while not ok do
dtr:turn_left()
dtr:turn_left()
dtr:dig()
dtr:turn_left()
dtr:turn_left()
ok, err = dtr:back()
end
return ok, err
end
---@return boolean success
---@return string? reason
function wrapped.up()
local ok, err = dtr:up()
if err == "bedrock" then
wrapped.hit_bedrock = true
end
while not ok do
dtr:dig_up()
ok, err = dtr:up()
end
return ok, err
end
---@return boolean success
---@return string? reason
function wrapped.down()
local ok, err = dtr:down()
if err == "bedrock" then
wrapped.hit_bedrock = true
end
while not ok do
dtr:dig_down()
ok, err = dtr:down()
end
return ok, err
end
---@return boolean success
---@return string? reason
function wrapped.turn_left()
return dtr:turn_left()
end
---@return boolean success
---@return string? reason
function wrapped.turn_right()
return dtr:turn_right()
end
---@return boolean success
---@return string? reason
function wrapped.dig()
return dtr:dig()
end
---@return boolean success
---@return string? reason
function wrapped.dig_up()
return dtr:dig_up()
end
---@return boolean success
---@return string? reason
function wrapped.dig_down()
return dtr:dig_down()
end
---@return boolean success
---@return string? reason
function wrapped.place()
return dtr:place()
end
---@return boolean success
---@return string? reason
function wrapped.place_up()
return dtr:place_up()
end
---@return boolean success
---@return string? reason
function wrapped.place_down()
return dtr:place_down()
end
return wrapped
end
--- Count the number of slots in the turtle's inventory that are occupied.
---@return integer n The number of occupied slots.
local function count_slots()
local n = 0
for i = 1, 16 do
if turtle.getItemCount(i) > 0 then
n = n + 1
end
end
return n
end
--- Drop the turtle's inventory.
---@param dtr DTR The DTR instance to notify when refueling.
---@param fuel boolean If true, will attempt to refuel with the items before dropping them.
---@param no_inv boolean Does the same as `fuel` in this function.
local function drop(dtr, fuel, no_inv)
for i = 1, 16 do
if turtle.getItemCount(i) > 0 then
turtle.select(i)
if fuel or no_inv then
-- Attempt to refuel before dropping it.
-- We also refuel if no_inv is enabled, since we'd just be throwing
-- away the item anyways.
if turtle.refuel(64) then
dtr:refueled()
end
end
turtle.drop()
end
end
end
--- Convert a file path to a require path (remove .lua and replace / with .).
---@param path string The file path to convert.
---@return string require_path The converted require path.
local function to_require_path(path)
return (path:gsub("%.lua$", ""):gsub("/", "."))
end
--- Verify that a broadcaster has the necessary functions.
---@param broadcaster SimplifyDig.Broadcaster The broadcaster to verify.
local function verify_broadcaster(broadcaster)
local function broadcaster_field_error(field, got)
error(("Broadcaster is missing required field '%s'. Got '%s'."):format(field, got))
end
local function broadcaster_field_check(field, _type)
if type(broadcaster[field]) ~= _type then
broadcaster_field_error(field, type(broadcaster[field]))
end
end
if type(broadcaster) ~= "table" then
error("Broadcaster must be a table.")
end
broadcaster_field_check("ready", "boolean")
broadcaster_field_check("setup", "function")
broadcaster_field_check("raw", "function")
broadcaster_field_check("keepalive", "function")
broadcaster_field_check("status", "function")
broadcaster_field_check("complete", "function")
broadcaster_field_check("panic", "function")
broadcaster_field_check("error", "function")
local ok, err = broadcaster.setup(parsed)
if not ok then
error(("Broadcaster setup failed: %s"):format(err or "unknown error"))
end
end
--- The function ran at the surface.
---@param dtr DTR The DTR instance to use for status updates and refueling.
---@param dispatch SimplifyDig.Broadcaster.Dispatcher The dispatcher to use for status updates.
---@param fuel boolean Whether to attempt to refuel with items in the inventory when at the surface. This should be `fuel or no_inv`.
local function gen_surface_func(dtr, dispatch, fuel)
---@param returning boolean If we're returning back to the mine when done.
return function(returning)
dispatch.status(dtr.state.position, dtr.state.facing, dtr.state.last_fuel)
while not peripheral.hasType("front", "inventory") do
log.warn("No inventory in front...")
dispatch.state "stuck"
dispatch.panic(
"No inventory in front to dump items into.",
dtr.state.position,
dtr.state.facing,
dtr.state.last_fuel
)
sleep(10)
end
dispatch.state "idle"
while true do
drop(dtr, fuel, false) -- Ignore `no_inv` here.
if count_slots() == 0 then
break
else
log.warn("Inventory still not empty after dumping...")
dispatch.state "stuck"
dispatch.panic(
"Inventory still not empty after dumping.",
dtr.state.position,
dtr.state.facing,
dtr.state.last_fuel
)
sleep(10)
end
end
dispatch.state "idle"
if returning then
dispatch.state "return-mine"
end
end
end
--- Run the moves.
---@param dtr DTR The DTR instance to use for status updates and refueling.
---@param wrapped_dtr WrappedDTR The wrapped DTR instance to use for move execution.
---@param dispatch SimplifyDig.Broadcaster.Dispatcher The dispatcher to use for status updates.
---@param get_next_move fun():function A function that returns the next move to execute. This allows the move generation to be dynamic and respond to events like hitting bedrock or needing to return to the surface.
---@param moves table A table of moves to execute, used for calculating completion percentage.
local function run_moves(dtr, wrapped_dtr, dispatch, get_next_move, moves)
local n_moves = #moves
dtr:refueled() -- Force dtr to update fuel level after initialization.
log.infof("Pre-calculated move list with %d moves.", #moves)
local move = 0
local function do_the_moves()
while #moves > 0 do
dispatch.state "digging"
move = move + 1
if not dtr.simulating and move == 418 then
dispatch.state "teapot"
end
local func = get_next_move()
--#region debug logging
---@type string?
local func_name
if func == wrapped_dtr.forward then
func_name = "forward"
elseif func == wrapped_dtr.turn_left then
func_name = "turn_left"
elseif func == wrapped_dtr.turn_right then
func_name = "turn_right"
elseif func == wrapped_dtr.up then
func_name = "up"
elseif func == wrapped_dtr.down then
func_name = "down"
elseif func == wrapped_dtr.dig_up then
func_name = nil
elseif func == wrapped_dtr.dig_down then
func_name = nil
elseif func == wrapped_dtr.dig then
func_name = nil
else
func_name = "unknown"
end
if func_name then
log.debugf("%d (%d): %s", dtr.state.recorded_moves, move, func_name)
end
--#endregion debug logging
local success, reason = func()
if not success and reason == "bedrock" then
log.warn("Hit bedrock during move. Marking bedrock reached and returning to surface.")
wrapped_dtr.hit_bedrock = true
end
if dtr.simulating and dtr.state.recorded_moves % 100 == 0 then
os.queueEvent("quick_yield")
os.pullEvent("quick_yield")
end
end
end
-- Returns a value between 0.25 and 1.25
local function random_offset_time()
return math.random() + 0.25
end
local function status_update_loop()
while true do
sleep((dispatch.TIMEOUTS.status / 1000) + random_offset_time())
dispatch.status(dtr.state.position, dtr.state.facing, dtr.state.last_fuel)
end
end
local function keepalive_loop()
while true do
sleep((dispatch.TIMEOUTS.keepalive / 1000) + random_offset_time())
dispatch.keepalive()
end
end
local function completion_loop()
if parsed.flags.quarry then
-- Do not send completion updates for quarry mode, as it is uncertain how deep the turtle needs to dig.
while true do
sleep(999)
end
end
while true do
sleep((dispatch.TIMEOUTS.completion / 1000) + random_offset_time())
dispatch.completion(1 - #moves / n_moves)
end
end
parallel.waitForAny(
do_the_moves,
status_update_loop,
completion_loop,
keepalive_loop
)
end
--#region Cuboid
--- Cuboid digging impl
---@param dispatch SimplifyDig.Broadcaster.Dispatcher The broadcast dispatcher to use for status updates.
---@param dtr DTR The DTR instance to use for status updates and refueling.
local function dig_cuboid_impl(dispatch, dtr)
local wrapped_dtr = wrap_dtr(dtr)
if dtr:should_simulate() then
dtr:start_simulating()
end
-- Initialization:
-- 1. Determine which way we want to turn based off arguments.
-- 2. Determine if we're going up or down based off arguments.
local turn = parsed.flags.left and wrapped_dtr.turn_left or wrapped_dtr.turn_right
local vertical_move = parsed.flags.up and wrapped_dtr.up or wrapped_dtr.down
local duo_vertical_dig = parsed.flags.up and wrapped_dtr.dig_up or wrapped_dtr.dig_down
local n_duo_vertical_dig = parsed.flags.up and wrapped_dtr.dig_down or wrapped_dtr.dig_up
local surface_func = gen_surface_func(dtr, dispatch, parsed.flags.fuel or parsed.flags.noinv)
-- Pull the values from arguments
local forward_length = tonumber(parsed.options.forwardlength)
local width = tonumber(parsed.options.width)
local height = parsed.flags.quarry and MAX_HEIGHT or tonumber(parsed.options.height) or math.huge
local no_inv = parsed.flags.noinv
local fuel = parsed.flags.fuel
if parsed.options.loglevel ~= "info" then
minilogger.set_log_level(minilogger.LOG_LEVELS[parsed.options.loglevel:upper()])
end
local function return_to_surface()
dispatch.state "return-home"
dtr:return_to_surface(true, 2, surface_func)
end
local function home()
dispatch.state "return-home"
dtr:return_to_surface(true, 2, surface_func, true)
end
---@type function[]
local moves = {}
local n_moves = 0 -- Only used for populating the table
local function m_insert(f)
n_moves = n_moves + 1
moves[n_moves] = f
end
local function get_next_move()
-- If we're recovering and we have recorded a return to surface, simulate that return.
if dtr:should_return_to_surface() then
log.debug("Return to surface caused by DTR recovery.")
return return_to_surface
end
if not dtr.simulating then
-- If we've hit bedrock.
if wrapped_dtr.hit_bedrock then
log.debug("Return to surface caused by hitting bedrock.")
return home
end
-- If the inventory is full, either dump it or return and dump it.
if count_slots() == 16 then
if no_inv then
drop(dtr, fuel, no_inv)
else
log.debug("Return to surface caused by full inventory.")
return return_to_surface
end
end
-- If we are running low on fuel, return to the surface.
if dtr:should_refuel() then
log.debug("Return to surface caused by low fuel.")
return return_to_surface
end
end
return table.remove(moves, 1)
end
local function reverse()
m_insert(turn)
m_insert(wrapped_dtr.forward)
m_insert(turn)
turn = turn == wrapped_dtr.turn_left and wrapped_dtr.turn_right or wrapped_dtr.turn_left
end
-- Move forward one block to be in the right position.
m_insert(wrapped_dtr.forward)
local height_remaining = height
-- If we only are digging one vertical layer, we just dig a plane.
-- If we are digging two layers, we dig a plane but enable digging down or up.
while height_remaining > 0 do
if height_remaining > 2 then
-- Digging in rows of three.
-- Need to move down (or up) by one to compensate.
m_insert(vertical_move)
end
for w = 1, width do
for _ = 1, forward_length - 1 do -- Subtract one here since we start 'in' the first block.
if height_remaining >= 2 then
m_insert(duo_vertical_dig)
end
if height_remaining >= 3 then
m_insert(n_duo_vertical_dig)
end
m_insert(wrapped_dtr.forward)
end
if w < width then
if height_remaining >= 2 then
m_insert(duo_vertical_dig)
end
if height_remaining >= 3 then
m_insert(n_duo_vertical_dig)
end
reverse()
end
end
if height_remaining >= 3 then
m_insert(n_duo_vertical_dig)
end
if height_remaining >= 2 then
m_insert(duo_vertical_dig)
end
if height_remaining > 3 then
for _ = 1, 2 do
m_insert(vertical_move)
end
m_insert(turn)
m_insert(turn)
else
log.debug("Return home caused by ITS THE END WOOOOOOOOOOOOOOOOOO")
m_insert(home)
end
height_remaining = height_remaining - 3
end
run_moves(dtr, wrapped_dtr, dispatch, get_next_move, moves)
end
--- Cuboid digging function
local function dig_cuboid()
log.infof("Starting cuboid dig with parameters:\n forwardlength=%d\n width=%d\n height=%s\n quarry=%s\n left_right=%s\n up_down=%s\n fuel=%s\n noinv=%s\n broadcast_file=%s\n log_level=%s",
parsed.options.forwardlength or -1,
parsed.options.width or -1,
parsed.options.height or "infinite",
parsed.flags.quarry and "true" or "false",
parsed.flags.left and "left" or "right",
parsed.flags.up and "up" or "down",
parsed.flags.fuel and "true" or "false",
parsed.flags.noinv and "true" or "false",
parsed.options.broadcast or "None",
parsed.options.loglevel or "info"
)
if not parsed.options.broadcast then
parsed.options.broadcast = tostring(pp:at("lib/broadcast"):file("empty.lua"))
end
local broadcaster = require(to_require_path(parsed.options.broadcast)) --[[@as SimplifyDig.Broadcaster]]
verify_broadcaster(broadcaster)
local dispatch = require "broadcast.dispatch"
dispatch.set_broadcaster(broadcaster)
dispatch.state "init"
dispatch.init(parsed)
local dtr = setup_reboot()
local ok, err = xpcall(dig_cuboid_impl, debug.traceback, dispatch, dtr)
if not ok then
pcall(log.errorf, "Cuboid dig failed: %s", err or "unknown error")
pcall(dispatch.error, err or "unknown error", dtr.state.position, dtr.state.facing, dtr.state.last_fuel)
pcall(dispatch.state, "error")
-- Elevate the error
error(err, 0)
end
log.info("Cuboid dig completed successfully.")
dispatch.complete()
cleanup_reboot()
end
--#endregion Cuboid
--#region Staircase
--- Staircase digging impl
---@param dispatch SimplifyDig.Broadcaster.Dispatcher The broadcaster to use for status updates.
local function dig_staircase_impl(dispatch)
local dtr = setup_reboot()
local wrapped_dtr = wrap_dtr(dtr)
if dtr:should_simulate() then
dtr:start_simulating()
end
-- Pull the values from arguments
local forward_length = tonumber(parsed.options.forwardlength)
local height = tonumber(parsed.options.height) or 3
if height < 3 then
error("Height must be at least 3 for staircase digging.", 0)
end
local place_stairs = parsed.flags.stairs
local place_torches = parsed.flags.torches
local torch_interval = tonumber(parsed.options.torchinterval) or 10
local no_inv = parsed.flags.noinv
local fuel = parsed.flags.fuel
local down = not parsed.flags.up
if parsed.options.loglevel ~= "info" then
minilogger.set_log_level(minilogger.LOG_LEVELS[parsed.options.loglevel:upper()])
end
local surface_func = gen_surface_func(dtr, dispatch, parsed.flags.fuel or parsed.flags.noinv)
local function return_to_surface()
dispatch.state "return-home"
error("Cannot return right now because we are nerds who haven't implemented stuff yet lmao", 0)
---@TODO We need to do a custom return to surface here, because we need
--- to move in a stair pattern instead of a straight line.
--dtr:return_to_surface(true, 2, surface_func)
end
local function home()
dispatch.state "return-home"
error("Cannot return right now because we are nerds who haven't implemented stuff yet lmao", 0)
--dtr:return_to_surface(true, 2, surface_func, true)
end
---@param item_name string
---@param match boolean? If true, will match the item name rather than direct comparison.
---@return integer? slot The slot containing the item, or nil if not found.
local function find(item_name, match)
for i = 1, 16 do
local detail = turtle.getItemDetail(i)
if detail and (match and string.find(detail.name, item_name) or detail.name == item_name) then
return i
end
end
return nil
end
---@type function[]
local moves = {}
local n_moves = 0 -- Only used for populating the table
---@param f function
local function m_insert(f)
n_moves = n_moves + 1
moves[n_moves] = f
end
local no_torches = false
local no_stairs = false
local function get_torch()
local torch_slot = find("minecraft:torch")
if not torch_slot then
no_torches = true
log.warn("No torches found in inventory.")
return
end
turtle.select(torch_slot)
end
local function get_stair()
local stair_slot = find("stairs", true)
if not stair_slot then
no_stairs = true
log.warn("No stairs found in inventory.")
return
end
turtle.select(stair_slot)
end
local function get_next_move()
-- If we're recovering and we have recorded a return to surface, simulate that return.
if dtr:should_return_to_surface() then
log.debug("Return to surface caused by DTR recovery.")
return return_to_surface
end
if not dtr.simulating then
-- If we've hit bedrock.
if wrapped_dtr.hit_bedrock then
log.debug("Return to surface caused by hitting bedrock.")
return home
end
-- If the inventory is full, either dump it or return and dump it.
if count_slots() == 16 then
if no_inv then
drop(dtr, fuel, no_inv)
else
log.debug("Return to surface caused by full inventory.")
return return_to_surface
end
end
-- If there's no torches, return for more.
if place_torches and no_torches then
log.debug("Return to surface caused by no torches.")
return return_to_surface