forked from OpenPrograms/Sangar-Programs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
miner.lua
820 lines (720 loc) · 23.4 KB
/
miner.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
--[[
Branch mining program for OpenComputers robots.
This program is designed to dig out branches, in a fashion that allows
players to easily navigate the dug out tunnels. The primary concern was
not the performance of the mining, only a good detection rate, and nice
tunnels. Suggested upgrades for this are the geolyzer and inventory
controller upgrade, and depending on your world gen (ravines?) a hover
upgrade might be necessary. The rest is up to you (chunkloading, more
inventory, battery upgrades).
By Sangar, 2015
This program is licensed under the MIT license.
http://opensource.org/licenses/mit-license.php
]]
local component = require("component")
local computer = require("computer")
local robot = require("robot")
local shell = require("shell")
local sides = require("sides")
local event = require("event")
local args, options = shell.parse(...)
--[[ Config ]]-----------------------------------------------------------------
-- Every how many blocks to dig a side shaft. The default makes for a
-- two wide wall between tunnels, making sure we don't miss anything.
local shaftInterval = 3
-- Max recursion level for mining ore veins. We abort early because we
-- assume we'll encounter the same vein again from an adjacent tunnel.
local maxVeinRecursion = 8
-- Every how many blocks to place a torch when placing torches.
local torchInverval = 11
--[[ Constants ]]--------------------------------------------------------------
-- Quick look-up table for inverting directions.
local oppositeSides = {
[sides.north] = sides.south,
[sides.south] = sides.north,
[sides.east] = sides.west,
[sides.west] = sides.east,
[sides.up] = sides.down,
[sides.down] = sides.up
}
-- For pushTurn() readability.
local left = false
local right = not left
--[[ State ]]------------------------------------------------------------------
-- Slots we don't want to drop. Filled in during initialization, based
-- on items already in the inventory. Useful for stuff like /dev/null.
local keepSlot = {}
-- Slots that we keep torches in, updated when stocking up on torches.
local torchSlots = {}
--[[ "Passive" logic ]]--------------------------------------------------------
-- Keep track of moves we're away from our origin, and average energy used per
-- move. This is used to compute the threshold at which we have to return to
-- maintenance to recharge.
local preMoveEnergy, averageMoveCost, distanceToOrigin = 0, 15, 0
-- The actual callback called in postMove().
local onMove
-- Called whenever we're about to move, used to compute move cost.
local function preMove()
preMoveEnergy = computer.energy()
end
-- Called whenever we're done moving, used for automatic torch placement an digging.
local function postMove()
local moveCost = preMoveEnergy - computer.energy()
if moveCost > 0 then
averageMoveCost = (averageMoveCost + moveCost) / 2
end
if onMove then
onMove()
end
end
--[[ Utility ]]----------------------------------------------------------------
local function prompt(message)
io.write(message .. " [Y/n] ")
local result = io.read()
return result and (result == "" or result:lower() == "y")
end
-- Check if a block with the specified info should be mined.
local function shouldMine(info)
return info and info.name and (info.name:match(".*ore.*") or info.name:match(".*Ore.*"))
end
-- Number of stacks of torches to keep; default is 1 per inventory upgrade.
local function torchStacks()
return math.max(1, math.ceil(robot.inventorySize() / 16))
end
-- Look for the first empty slot in our inventory.
local function findEmptySlot()
for slot = 1, robot.inventorySize() do
if robot.count(slot) == 0 then
return slot
end
end
end
-- Find the first torch slot that still contains torches.
local function findTorchSlot()
for _, slot in ipairs(torchSlots) do
if robot.count(slot) > 0 then
return slot
end
end
end
-- Since robot.select() is an indirect call, we can speed things up a bit.
local selectedSlot
local function cachedSelect(slot)
if slot ~= selectedSlot then
robot.select(slot)
selectedSlot = slot
end
end
-- Place a single torch above the robot, if there are any torches left.
local function placeTorch()
local slot = findTorchSlot()
local result = false
if slot then
cachedSelect(slot)
result = robot.placeUp()
cachedSelect(1)
end
return result
end
-- Dig out a block on the specified side, without tool if possible.
local function dig(side, callback)
repeat
-- Check for maintenance first, to make sure we make the return trip when
-- our batteries are running low.
local emptySlot = findEmptySlot()
if callback then
callback(not emptySlot) -- Parameter: is inventory full.
emptySlot = findEmptySlot()
end
cachedSelect(1)
local something, what = component.robot.detect(side)
if not something or what == "replaceable" or what == "liquid" then
return true -- We can just move into whatever is there.
end
local brokeSomething
local info = component.isAvailable("geolyzer") and
component.geolyzer.analyze(side)
if info and info.name == "OpenComputers:robot" then
brokeSomething = true -- Wait for other robot to go away.
os.sleep(0.5)
elseif component.isAvailable("inventory_controller") and emptySlot then
cachedSelect(emptySlot)
component.inventory_controller.equip() -- Save some tool durability.
cachedSelect(1)
brokeSomething = component.robot.swing(side)
cachedSelect(emptySlot)
component.inventory_controller.equip()
cachedSelect(1)
end
if not brokeSomething then
brokeSomething = component.robot.swing(side)
end
until not brokeSomething
end
-- Force a move towards in the specified direction.
local function forceMove(side, delta)
preMove()
local result = component.robot.move(side)
if result then
distanceToOrigin = distanceToOrigin + delta
postMove()
else
-- Obstructed, try to clear the way.
if side == sides.back then
-- Moving backwards, turn around.
component.robot.turn(left)
component.robot.turn(left)
repeat
dig(sides.forward)
preMove()
until robot.forward()
distanceToOrigin = distanceToOrigin + delta
component.robot.turn(left)
component.robot.turn(left)
postMove() -- Slightly falsifies move cost, but must ensure we're rotated
-- correctly in case postMove() triggers going to maintenance.
else
repeat
dig(side)
preMove()
until component.robot.move(side)
distanceToOrigin = distanceToOrigin + delta
postMove()
end
end
return true
end
--[[ Navigation ]]-------------------------------------------------------------
-- Keeps track of our moves to allow "undoing" them for returning to the
-- docking station. Format is a list of moves, represented as tables
-- containing the type of move and distance to move, e.g.
-- {move=sides.back, count=10},
-- {turn=true, count=2}
-- means we first moved back 10 blocks, then turned around.
local moves = {}
-- Undo a *single* move, i.e. reduce the count of the latest move type.
local function undoMove(move)
if move.move then
local side = oppositeSides[move.move]
forceMove(side, -1)
else
local direction = not move.turn
component.robot.turn(direction)
end
move.count = move.count - 1
end
-- Make a turn in the specified direction.
local function pushTurn(direction)
component.robot.turn(direction)
if moves[#moves] and moves[#moves].turn == direction then
moves[#moves].count = moves[#moves].count + 1
else
moves[#moves + 1] = {turn=direction, count=1}
end
return true -- Allows for `return pushMove() and pushTurn() and pushMove()`.
end
-- Try to make a move towards the specified side.
local function pushMove(side, force)
preMove()
local result, reason = (force and forceMove or component.robot.move)(side, 1)
if result then
if moves[#moves] and moves[#moves].move == side then
moves[#moves].count = moves[#moves].count + 1
else
moves[#moves + 1] = {move=side, count=1}
end
if not force then
distanceToOrigin = distanceToOrigin + 1
end
postMove()
end
return result, reason
end
-- Undo the most recent move *type*. I.e. will undo all moves of the most
-- recent type (say we moved forwards twice, this will go back twice).
local function popMove()
-- Deep copy the move for returning it.
local move = moves[#moves] and {move=moves[#moves].move,
turn=moves[#moves].turn,
count=moves[#moves].count}
while moves[#moves] and moves[#moves].count > 0 do
undoMove(moves[#moves])
end
moves[#moves] = nil
return move
end
-- Get the current top and count values, to be used as a position snapshot
-- that can be restored later on by calling setTop().
local function getTop()
if moves[#moves] then
return #moves, moves[#moves].count
else
return 0, 0
end
end
-- Undo some moves based on a stored top and count received from getTop().
local function setTop(top, count, unsafe)
assert(top >= 0)
assert(top <= #moves)
assert(count >= 0)
assert(top < #moves or count <= moves[#moves].count)
while #moves > top do
if unsafe then
if moves[#moves].move then
distanceToOrigin = distanceToOrigin - moves[#moves].count
end
moves[#moves] = nil
else
popMove()
end
end
local move = moves[#moves]
if move then
while move.count > count do
if unsafe then
move.count = move.count - 1
distanceToOrigin = distanceToOrigin - 1
else
undoMove(move)
end
end
if move.count < 1 then
moves[#moves] = nil
end
end
end
-- Undo *all* moves made since program start, return the list of moves.
local function popMoves()
local result = {}
local move = popMove()
while move do
table.insert(result, 1, move)
move = popMove()
end
return result
end
-- Repeat the specified set of moves.
local function pushMoves(moves)
for _, move in ipairs(moves) do
if move.move then
for _ = 1, move.count do
pushMove(move.move, true)
end
else
for _ = 1, move.count do
pushTurn(move.turn)
end
end
end
end
--[[ Maintenance ]]------------------------------------------------------------
-- Energy required to return to docking bay.
local function costToReturn()
-- Overestimate a bit, to account for obstacles such as gravel or mobs.
return 5000 + averageMoveCost * distanceToOrigin * 1.25
end
-- Checks whether we need maintenance.
local function needsMaintenance()
return not robot.durability() or -- Tool broken?
computer.energy() < costToReturn() or -- Out of juice?
not findTorchSlot() -- No more torches?
end
-- Drops all inventory contents that are not marked for keeping.
local function dropMinedBlocks()
if component.isAvailable("inventory_controller") then
if not component.inventory_controller.getInventorySize(sides.down) then
io.write("There doesn't seem to be an inventory below me! Waiting to avoid spilling stuffs into the world.\n")
end
repeat os.sleep(5) until component.inventory_controller.getInventorySize(sides.down)
end
io.write("Dropping what I found.\n")
for slot = 1, robot.inventorySize() do
while not keepSlot[slot] and robot.count(slot) > 0 do
cachedSelect(slot)
robot.dropDown()
end
end
cachedSelect(1)
end
-- Ensures we have a tool with durability.
local function checkTool()
if not robot.durability() then
io.write("Tool is broken, getting a new one.\n")
if component.isAvailable("inventory_controller") then
cachedSelect(findEmptySlot()) -- Select an empty slot for working.
repeat
component.inventory_controller.equip() -- Drop whatever's in the tool slot.
while robot.count() > 0 do
robot.dropDown()
end
robot.suckUp(1) -- Pull something from above and equip it.
component.inventory_controller.equip()
until robot.durability()
cachedSelect(1)
else
-- Can't re-equip autonomously, wait for player to give us a tool.
io.write("HALP! I need a new tool.\n")
repeat
event.pull(10, "inventory_changed")
until robot.durability()
end
end
end
-- Ensures we have some torches.
local function checkTorches()
-- First, clean up our list and look for empty slots.
io.write("Getting my fill of torches.\n")
local oldTorchSlots = torchSlots
torchSlots = {}
for _, slot in ipairs(oldTorchSlots) do
keepSlot[slot] = nil
if robot.count(slot) > 0 then
torchSlots[#torchSlots + 1] = slot
end
end
while #torchSlots < torchStacks() do
local slot = findEmptySlot()
if not slot then
break -- This should never happen...
end
torchSlots[#torchSlots + 1] = slot
end
-- Then fill the slots with torches.
robot.turnLeft()
for _, slot in ipairs(torchSlots) do
keepSlot[slot] = true
if robot.space(slot) > 0 then
cachedSelect(slot)
repeat
local before = robot.space()
robot.suck(robot.space())
if robot.space() == before then
os.sleep(5) -- Don't busy idle.
end
until robot.space() < 1
cachedSelect(1)
end
end
robot.turnRight()
end
-- Recharge our batteries.
local function recharge()
io.write("Waiting until my batteries are full.\n")
while computer.maxEnergy() - computer.energy() > 100 do
os.sleep(1)
end
end
-- Go back to the docking bay for general maintenance if necessary.
local function gotoMaintenance(force)
if not force and not needsMaintenance() then
return -- No need yet.
end
-- Save some values for later, temporarily remove onMove callback.
local returnCost = costToReturn()
local moveCallback = onMove
onMove = nil
local top, count = getTop()
io.write("Going back for maintenance!\n")
local moves = popMoves()
assert(distanceToOrigin == 0)
dropMinedBlocks()
checkTool()
checkTorches()
recharge() -- Last so we can charge some during the other operations.
if moves and #moves > 0 then
if returnCost * 2 > computer.maxEnergy() and
not options.f and
not prompt("Going back will cost me half my energy. There's a good chance I will not return. Do you want to send me to my doom anyway?")
then
os.exit()
end
io.write("Returning to where I left off.\n")
pushMoves(moves)
end
local newTop, newCount = getTop()
assert(top == newTop)
assert(count == newCount)
onMove = moveCallback
end
--[[ Mining ]]-----------------------------------------------------------------
-- Move towards the specified direction, digging out blocks as necessary.
-- This is a "soft" version of forceMove in that it will try to clear its path,
-- but fail if it can't.
local function move(side)
local result, reason, retry
repeat
retry = false
if side ~= sides.back then
retry = dig(side, gotoMaintenance)
else
gotoMaintenance()
end
result, reason = pushMove(side)
until result or not retry
return result, reason
end
-- Turn to face the specified, relative orientation.
local function turnTowards(side)
if side == sides.left then
pushTurn(left)
elseif side == sides.right then
pushTurn(right)
elseif side == sides.back then
pushTurn(left)
pushTurn(left)
end
end
--[[ On move callbacks ]]------------------------------------------------------
-- Start automatically placing torches in the configured interval.
local function beginPlacingTorches()
local counter = 2
onMove = function()
if counter < 1 then
if placeTorch() then
counter = torchInverval
end
else
counter = counter - 1
end
end
end
-- Start digging out the block below us after each move.
local function beginDigginTrench()
onMove = function()
dig(sides.down, gotoMaintenance)
end
end
-- Stop automatically placing torches.
local function clearMoveCallback()
onMove = nil
end
--[[ Moving ]]-----------------------------------------------------------------
-- Dig out any interesting ores adjacent to the current position, recursively.
-- POST: back to the starting position and facing.
local function digVein(maxDepth)
if maxDepth < 1 then return end
for _, side in ipairs(sides) do
local sideIdx = sides[side]
-- skip unknown side
if oppositeSides[side] and shouldMine(component.geolyzer.analyze(sideIdx)) then
local top, count = getTop()
turnTowards(sideIdx)
if sideIdx == sides.up or sideIdx == sides.down then
move(sideIdx)
else
move(sides.forward)
end
digVein(maxDepth - 1)
setTop(top, count)
end
end
end
-- Dig out any interesting ores adjacent to the current position, recursively.
-- Also checks blocks adjacent to above block in exhaustive mode.
-- POST: back at the starting position and facing.
local function digVeins(exhaustive)
if component.isAvailable("geolyzer") then
digVein(maxVeinRecursion)
if exhaustive and move(sides.up) then
digVein(maxVeinRecursion)
popMove()
end
end
end
-- Dig a 1x2 tunnel of the specified length. Checks for ores.
-- Also checks upper row for ores in exhaustive mode.
-- PRE: bottom front of tunnel to dig.
-- POST: at the end of the tunnel.
local function dig1x2(length, exhaustive)
while length > 0 and move(sides.forward) do
dig(sides.up, gotoMaintenance)
digVeins(exhaustive)
length = length - 1
end
return length < 1
end
-- Dig a 1x3 tunnel of the specified length.
-- PRE: center front of tunnel to dig.
-- POST: at the end of the tunnel.
local function dig1x3(length)
while length > 0 and move(sides.forward) do
dig(sides.up, gotoMaintenance)
dig(sides.down, gotoMaintenance)
length = length - 1
end
return length < 1
end
-- Dig out a main shaft.
-- PRE: bottom front of main shaft.
-- POST: bottom front of main shaft.
local function digMainShaft(length)
io.write("Digging main shaft.\n")
if not move(sides.up) then
return false
end
local top, count = getTop()
if not (dig1x3(length) and
pushTurn(left) and
dig1x3(1) and
pushTurn(left) and
dig1x3(length - 1) and
(placeTorch() or true) and -- Just keep going...
pushTurn(left) and
dig1x3(1))
then
return false
end
-- Create snapshot for shortcut below.
local midTop, midCount = getTop()
if not (dig1x3(1) and
pushTurn(left) and
dig1x3(length - 1))
then
return false
end
placeTorch()
-- Shortcut: manually move back to start, do an unsafe setTop.
-- Otherwise we'd have to retrace all three rows.
setTop(midTop, midCount)
if pushTurn(left) and move(sides.back) then
setTop(top, count, true)
return true
end
return false
end
-- Dig all shafts in one cardinal direction (the one we're facing).
-- PRE: bottom front of a main shaft.
-- POST: bottom front of a main shaft.
local function digShafts(length)
local top, count = getTop() -- Remember start of main shaft.
local ok = digMainShaft(length)
setTop(top, count)
if not ok then
io.write("Failed digging main shaft, skipping.\n")
return
end
io.write("Beginning work on side shafts.\n")
for i = shaftInterval, length, shaftInterval do
io.write("Working on shafts #" .. (i / shaftInterval) .. ".\n")
if not dig1x2(shaftInterval) then -- Move to height of shaft.
break
end
local sideTop, sideCount = getTop() -- Remember position.
pushTurn(left) -- Dig left shaft.
dig1x2(i + 2, true)
beginPlacingTorches()
setTop(sideTop, sideCount)
clearMoveCallback()
pushTurn(right) -- Dig right shaft.
dig1x2(i + 2, true)
beginPlacingTorches()
setTop(sideTop, sideCount)
clearMoveCallback()
end
-- Go back to start of main shaft. Dig out the center of the main shaft
-- while we're at it, so we break through the ceiling between levels.
beginDigginTrench()
setTop(top, count)
clearMoveCallback()
end
-- Moves to the next main shaft, clockwise.
-- PRE: bottom front of nth main shaft.
-- POST: bottom front of (n+1)th main shaft.
local function gotoNextMainShaft()
return pushTurn(right) and
dig1x2(2) and
pushTurn(right) and
dig1x2(2) and
pushTurn(left)
end
--[[ Main ]]-------------------------------------------------------------------
local function main(radius, levels, full)
-- We dig tunnels every three blocks, to have a spacing of
-- two blocks between them (so we don't really have to follow
-- veins but can just break the blocks in the wall that are
-- interesting to us). So adjust the length accordingly.
radius = radius - radius % shaftInterval
-- Flag slots that contain something as do-not-drop and check
-- that we have some free inventory space at all.
local freeSlots = robot.inventorySize()
for slot = 1, robot.inventorySize() do
if robot.count(slot) > 0 then
keepSlot[slot] = true
freeSlots = freeSlots - 1
end
end
if freeSlots < 2 + torchStacks() then -- Place for mined blocks + torches.
io.write("Sorry, but I need more empty inventory space to work.\n")
os.exit()
end
gotoMaintenance(true)
if not move(sides.forward) then
io.write("Exit from docking bay obstructed, aborting.\n")
os.exit()
end
for level = 1, levels do
if level > 1 then
for _ = 1, 4 do
if not move(sides.down) then
io.write("Access to level " .. level .. " obstructed, aborting.\n")
popMoves()
gotoMaintenance(true)
os.exit()
end
end
end
local top, count = getTop()
for shaft = 1, full and 4 or 1 do
if shaft > 1 and not gotoNextMainShaft() then
break
end
digShafts(radius)
end
if full then
gotoNextMainShaft() -- Finish the circle.
end
setTop(top, count)
end
io.write("All done! Going home to clean up.\n")
popMoves()
gotoMaintenance(true)
end
if options.h or options.help then
io.write("Usage: miner [-hsf] [radius [levels [full]]]\n")
io.write(" -h: this help listing.\n")
io.write(" -s: start without prompting.\n")
io.write(" -f: force mining to continue even if max\n")
io.write(" fuel may be insufficient to return.\n")
io.write(" radius: the radius in blocks of the area to\n")
io.write(" mine. Adjusted to be a multiple of\n")
io.write(" three. Default: 9.\n")
io.write(" levels: the number of vertical levels to mine.\n")
io.write(" Default: 1.\n")
io.write(" full: whether to mine a full level (all four\n")
io.write(" cardinal directions). Default: false.\n")
os.exit()
end
local radius = tonumber(args[1]) or 9
local levels = tonumber(args[2]) or 1
local full = args[3] and args[3] == "true" or args[3] == "yes"
io.write("Will mine " .. levels .. " levels in a radius of " .. radius .. ".\n")
if full then
io.write("Will mine all four cardinal directions.\n")
end
if not component.isAvailable("geolyzer") then
io.write("Installing a geolyzer upgrade is strongly recommended.\n")
end
if not component.isAvailable("inventory_controller") then
io.write("Installing an inventory controller upgrade is strongly recommended.\n")
end
io.write("I'll drop mined out stuff below me.\n")
io.write("I'll be looking for torches on my left.\n")
if component.isAvailable("inventory_controller") then
io.write("I'll try to get new tools from above me.\n")
else
io.write("You'll need to manually provide me with new tools if they break.\n")
end
io.write("Run with -h or --help for parameter info.\n")
if options.s or prompt("Shall we begin?") then
main(radius, levels, full)
end