-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathjam
2008 lines (1811 loc) · 70.2 KB
/
jam
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
--[[
IMPORTANT: don't use this program. Aside from the fact that it may be broken
according to some people, ComputerCraft has currently a bug that can deadlock
the game when a turtle moves next to a disk drive. And that happens a lot with
JAM. Turtles moving next to a disk drive, I mean. So the chances are actually
pretty high for this to happen. Trust me, it does happen.
]]
print("Unusable due to a CC bug as it stands (CC 1.56). Remove this code line to use it anyway at your own peril.") return
--[[
JAM - Just Another Miner - 2013 Sangar
This program is licensed under the MIT license.
http://opensource.org/licenses/mit-license.php
This program is intended to run on turtles working in a very specific
environment, to collaboratively (optional) dig up blocks of interest in a
designated area. Yes, it's a mining program ;)
Features:
- Fully resumable, thanks to LAMA and my state API. This resume is *very*
stable. It even survived multiple game crashes (out of memory).
- Interactively configurable (quarry size, torch placement).
- Supports turtles cooperatively working on the same quarry, up to the
total number of holes being dug - one turtle per hole, max. If there's
fewer turtles than holes, the holes will be worked on sequentially.
- Adding new turtles is super-easy, just place them into the dock, facing
the fuel chest (away from the disk drive) and the environment will be
installed on it and executed.
To get started, set up the docking station like this (top-down view).
CF Where: CD = Chest for dropping found blocks.
CD TU CT CF = Chest with fuel.
DD CT = Chest with torches (only needed when used).
DD = A disk drive with an empty floppy inside.
TU = A turtle, looking at the fuel chest.
Make sure the turtle in the middle is facing the fuel chest, then install
the environment like this:
> pastebin get h9KJ4DRt jam-install
> jam-install
The program will guide you through the next steps. Always make sure there's
enough fuel (and if used, torches) in the chests, and empty the dropbox
regularly!
To add more turtles to an existing quarry, simply place them into the
docking station like you did the first turtle, i.e. facing the fuel chest.
It will automatically be configured by the program written onto the floppy
disk when the quarry was set up and then run this program.
]]
assert(os.loadAPI("apis/bapil"))
assert(os.loadAPI("apis/logger"))
assert(os.loadAPI("apis/startup"))
assert(os.loadAPI("apis/state"))
assert(os.loadAPI("apis/lama"))
-------------------------------------------------------------------------------
-- Semi-config (you normally won't want to touch these) --
-------------------------------------------------------------------------------
-- The channel on which we'll send out status messages.
local sendChannel = 10113
-- Every how many levels torch placing turtles should place a torch.
local torchFrequency = 8
-- Whether to write our progress into a log file (each time the program changes
-- its state it would log that). This is primarily intended for debugging.
local writeLog = false
-- File in which to store current program state on the turtle, for resuming the
-- program after a reboot.
local stateFile = "/.jam-state"
-- The path to the file on the disk drive which we use to track how many jobs
-- we have already assigned to turtles. Note that this will be prefixed with
-- the disk drives mount path, so it should be a relative path.
local jobFile = ".jobs"
-- Given the turtle is in it's native orientation, this is the side the chest
-- we drop stuff we found into.
local dropSide = lama.side.left
-- Given the turtle is in it's native orientation, this is the side the chest
-- we can get fuel from.
local fuelSide = lama.side.front
-- Given the turtle is in it's native orientation, this is the side the chest
-- we can get new torches from.
local torchSide = lama.side.right
-- Given the turtle is in it's native orientation, this is the side the floppy
-- disk drive we use to track job progress on is at.
local diskSide = lama.side.back
-------------------------------------------------------------------------------
-- Internal variables / constants. DO NOT CHANGE THESE. --
-------------------------------------------------------------------------------
-- The slot number in which we keep our torches.
local torchSlot = 16
-- The relative level at which to move away from the dock, towards the holes.
local awayLevel = 1
-- The relative level at which to move back from the holes, towards the dock.
local backLevel = -1
-- Relative level at which to start digging.
local jobLevel = -2
-- Same as diskSide but using redstone.getSides() constants (for disk API).
local rsDiskSide = ({[lama.side.forward] = "front",
[lama.side.right] = "right",
[lama.side.back] = "back",
[lama.side.left] = "left"})[diskSide]
-- The log we use. Print to screen if we're not supposed to log stuff.
local log = writeLog and logger.new("jam") or
{info = function(_, fmt, ...)
print("[INFO] " .. string.format(fmt, ...))
end,
warn = function(_, fmt, ...)
print("[WARNING] " .. string.format(fmt, ...))
end}
-- Forward declaration of namespaces.
local private
-------------------------------------------------------------------------------
-- Program states --
-------------------------------------------------------------------------------
-- Start assembling our state machine.
local program = state.new(stateFile)
--[[
This state is purely used for interactively setting up a dig site. It asks
the user a couple of questions, prepares the disk drive and then reboots
into the actual worker loop.
]]
:add("setup", function()
-- Make sure there's a disk drive behind us.
if not disk.isPresent(rsDiskSide) or not disk.getMountPath(rsDiskSide) then
lama.turn((diskSide + 2) % 4)
end
if not private.waitForDiskDrive(0.5) then
return private.showBlueprint()
end
-- Configure the turtle to use our local coordinate system.
lama.set(0, 0, 0, lama.side.north)
-- If there's no job file on the disk drive we're starting from scratch.
local diskPath = disk.getMountPath(rsDiskSide)
local jobFileDisk = fs.combine(diskPath, jobFile)
assert(not fs.exists(jobFileDisk) or not fs.isDir(jobFileDisk),
"Bad disk, folder at job file location.")
if not fs.exists(jobFileDisk) then
assert(shell, "Setup must be run from the shell.")
local jobCount, placeTorches,
placeIgnored, haveIgnoreChest = private.setupDigSite()
if jobCount == nil then
-- nil is the abort signal, restart the state.
return
end
-- Check for chests.
print("Checking for chests...")
lama.turn(dropSide)
assert(turtle.detect(),
"Bad setup detected, no drop chest found.")
lama.turn(fuelSide)
assert(turtle.detect(),
"Bad setup detected, no fuel chest found.")
if placeTorches or haveIgnoreChest then
lama.turn(torchSide)
assert(turtle.detect(),
"Bad setup detected, no torch/ignored blocks chest found.")
end
-- Ask for ignored blocks in ignore chest before getting started.
if haveIgnoreChest then
private.showIgnoreChestInfo(placeIgnored)
end
-- If all went well, write the job file to the disk so that all turtles
-- placed into the dock from now on will be initialized automatically.
term.clear()
term.setCursorPos(1, 1)
print("Writing settings to disk... ")
-- Fetch up-to-date path (may have changed after turning).
lama.turn((diskSide + 2) % 4)
private.waitForDiskDrive(0.5)
local diskPath = disk.getMountPath(rsDiskSide)
local jobFileDisk = fs.combine(diskPath, jobFile)
local file = assert(fs.open(jobFileDisk, "w"),
"Could not open job file for writing.")
file.write(textutils.serialize({totalJobs = jobCount,
placeTorches = placeTorches,
placeIgnored = placeIgnored,
haveIgnoreChest = haveIgnoreChest}))
file.close()
end
-- Next up: add this turtle as a worker to the quarry.
switchTo("add_turtle")
-- Finish initialization via disk startup. For example, this will install
-- this program as an autorun script.
os.reboot()
end)
--[[
Given an existing dig site adds a new turtle to work on it.
]]
:add("add_turtle", function()
-- Align back to face away from the disk drive.
lama.turn((diskSide + 2) % 4)
private.waitForDiskDrive(0.5)
assert(disk.isPresent(rsDiskSide) and disk.getMountPath(rsDiskSide),
"Bad setup detected, no disk in disk drive behind me!")
-- Copy global quarry settings.
local jobData = private.readJobData()
placeIgnored = placeIgnored or jobData.placeIgnored
placeTorches = placeTorches or jobData.placeTorches
haveIgnoreChest = haveIgnoreChest or jobData.haveIgnoreChest
-- Ask for the blocks to ignore to be placed in our inventory. Remember how
-- many block types we're supposed to ignore.
if haveIgnoreChest then
ignoreCount = private.setupIgnoredBlocksFromIgnoreChest()
else
ignoreCount = private.setupIgnoredBlocks(placeTorches, placeIgnored)
-- If we got nothing (nil) we're supposed to try again.
if not ignoreCount then
return
end
end
-- Drop any duplicates we have of the stuff we're supposed to ignore.
-- Switching to goto_drop is equivalent to drop, but more uniform flow.
switchTo("goto_drop")
end)
--[[
Go back to the docking station to drop stuff we picked up.
]]
:add("goto_drop", function()
log:info("Going home to deliver the goods.")
private.sendMessage("state", "goto_drop")
private.select(1)
local x, y, z = lama.get()
local path = private.generatePath(x, y, z, 0, 0, 0, "home")
private.forceMove("goto_drop", function()
return lama.navigate(path, math.huge, true)
end)
switchTo("drop")
end)
--[[
Actually drop anything in our inventory that's surplus.
]]
:add("drop", function()
log:info("Dropping it!")
private.sendMessage("state", "drop")
if not placeIgnored or not job then
for slot = 1, ignoreCount do
if turtle.getItemCount(slot) > 1 then
private.drop(slot, 1)
end
end
end
local function isIgnoredBlock(slot)
if not ignoredLookup then
return false
end
for i = 1, #ignoredLookup do
if ignoredLookup[i] == slot then
return true
end
end
return false
end
for slot = ignoreCount + 1, 16 do
if not placeTorches or slot ~= torchSlot then
if not isIgnoredBlock(slot) then
private.drop(slot)
end
end
end
-- Continue with our current job, or get a new one if we don't have one.
if job then
switchTo("refuel_job")
else
switchTo("get_job")
-- Turn back to original orientation.
lama.turn((diskSide + 2) % 4)
-- We reboot before each job because this way the Lua VM gets a proper
-- cleaning (not sure if that's what caused it, but running multiple
-- turtles for extended periods made my worlds crash), and because I
-- had a weird bug where the turtle would otherwise not see the disk
-- inside the disk drive until either rebooted forcefully, or I opened
-- the menu, shortly. Yes. I know. Totally insane, but 99% reproducible
-- in that one world.
os.reboot()
end
end)
--[[
Tries to get a job, on success refuels and executes it, otherwise refuels
to move to retirement position (out of the way of other turtles).
]]
:add("get_job", function()
log:info("Looking for a new job.")
private.sendMessage("state", "get_job")
-- Make sure the disk drive is behind us.
assert(private.waitForDiskDrive(5),
"Bad setup detected, no disk in disk drive behind me!")
-- Returns a timestamp based on the ingame time (in ticks).
local function timestamp()
return (os.day() * 24 + os.time()) * 1000
end
-- Get the current job information.
local jobData = private.readJobData()
-- If we finished a job, clear it from the list of active jobs.
if finishedJob and jobData.activeJobs and
jobData.activeJobs[finishedJob]
then
-- Remember how long the last couple of jobs took to finish. This is
-- useful for predicting when an active job has taken an unexpectedly
-- long time and may require the player's investigation (via some
-- stationary computer attached to the disk drive for example).
local started = jobData.activeJobs[finishedJob]
local finished = timestamp()
local duration = finished - started
jobData.jobDurations = jobData.jobDurations or {}
table.insert(jobData.jobDurations, 1, duration)
if #jobData.jobDurations > 10 then
table.remove(jobData.jobDurations)
end
jobData.activeJobs[finishedJob] = nil
finishedJob = nil
end
-- Try to get a new job.
local nextJob = jobData.nextJob or 1
if nextJob <= jobData.totalJobs then
job = nextJob
jobData.activeJobs = jobData.activeJobs or {}
jobData.activeJobs[job] = timestamp()
jobData.nextJob = job + 1
if job == 1 then
jobData.startTime = timestamp()
end
switchTo("refuel_job")
log:info("Got one! Our new job ticket is #%d.", job)
private.sendMessage("job", job)
else
endSlot = jobData.finished or 0
jobData.finished = endSlot + 1
jobData.stopTime = timestamp()
switchTo("refuel_end")
end
-- Write new state back to disk.
local diskPath = disk.getMountPath(rsDiskSide)
local jobFileDisk = fs.combine(diskPath, jobFile)
local file = assert(fs.open(jobFileDisk, "w"),
"Bad disk, failed to open job file for writing.")
file.write(textutils.serialize(jobData))
file.close()
end)
--[[
Gets enough fuel to get to and back from our job.
]]
:add("refuel_job", function()
log:info("Getting enough fuel to make it to our job and back.")
private.sendMessage("state", "refuel_job")
private.refuel(private.computeExpeditionCost(job), ignoreCount)
switchTo("restock_torches")
end)
--[[
Refills our torch stack.
]]
:add("restock_torches", function()
if placeTorches then
log:info("Picking up some torches to bring light into the dark.")
private.sendMessage("state", "restock_torches")
private.restockTorches(ignoreCount)
end
switchTo("goto_job")
end)
--[[
Moves the turtle to the top of the hole it should dig.
]]
:add("goto_job", function()
local tx, tz = private.computeCoordinates(job)
log:info("On my way to my workplace @ (%d, %d)!", tx, tz)
private.sendMessage("state", "goto_job")
private.select(1)
local x, y, z = lama.get()
local path = private.generatePath(x, y, z, tx, jobLevel, tz, "job")
private.forceMove("goto_job", function()
return lama.navigate(path, math.huge, true, true)
end)
if resumeDigUp then
switchTo("dig_up")
else
switchTo("dig_down")
end
end)
--[[
Digs a hole down until we hit bedrock, returns to drop stuff if full and
returns to where we left off.
]]
:add("dig_down", function()
log:info("Looking for bedrock, can offer dancing skills.")
private.sendMessage("state", "dig_down")
if resumeDigDown then
log:info("Resuming from where I took a break.")
private.select(1)
while lama.getY() > resumeDigDown do
lama.down(math.huge, true)
end
resumeDigDown = nil
end
repeat
local newSamples = private.digLevel(ignoreCount, ignoreSamples, true)
if newSamples then
ignoreSamples = newSamples
save()
end
if private.isInventoryFull() then
-- We may sample the blocks on the level we're on twice (again when
-- coming back), but that's an acceptable simplification.
resumeDigDown = lama.getY()
return switchTo("goto_drop")
end
private.select(1)
local brokeStuff, pickedSomethingUp =
private.suckAndDig(turtle.suckDown, turtle.digDown)
if (placeIgnored or placeTorches) and
pickedSomethingUp and not groundLevel
then
groundLevel = lama.getY()
save()
end
-- Only try to move down if we don't break anything while doing so. If
-- suckAndDig() may return false even though there is something if our
-- inventory is full and we should go empty it, so check if there's no
-- block before we move.
local couldMove = false
if brokeStuff or not turtle.detectDown() then
couldMove = lama.down(math.huge, true)
end
until not brokeStuff and not couldMove
switchTo("rebuild_ignore_lookup")
end)
--[[
Sorts the blocks we're supposed to ignore, if any, so that the most
frequently encountered ones are at the lower inventory positions (for
faster early exists when comparing).
]]
:add("rebuild_ignore_lookup", function()
if placeIgnored and groundLevel then
log:info("Checking for overflow of ignorance.")
private.sendMessage("state", "rebuild_ignore_lookup")
-- Swaps two slots in the inventory.
local function swap(slotA, slotB)
if slotA == slotB then
return
end
-- Find an empty slot to work with as a temporary buffer.
local slotTmp
for slot = ignoreCount + 1, 16 do
if turtle.getItemCount(slot) == 0 then
slotTmp = slot
break
end
end
if not slotTmp then
return false
end
private.select(slotA)
turtle.transferTo(slotTmp)
private.select(slotB)
turtle.transferTo(slotA)
private.select(slotTmp)
turtle.transferTo(slotB)
return true
end
-- Build a look-up table of slots that also contain ignored items in
-- case we dug up so many we had an overflow. This is used for to avoid
-- having to check the complete inventory when placing ignored blocks
-- while moving up (which would be horribly slow).
local count, nextSlot = 0, 16
local lookup = {}
for slot = 16, ignoreCount + 1, -1 do
if turtle.getItemCount(slot) > 0 and
not placeTorches or slot ~= torchSlot
then
-- Got a candidate, compare it to all known ignored blocks.
private.select(slot)
for ignoreSlot = 1, ignoreCount do
if turtle.compareTo(ignoreSlot) then
-- This is an ignored block. Try to push it to the back
-- of the inventory, to avoid ripping holes into the
-- inventory list when we deplete the stack, which can
-- lead to getting two stacks of the same item type of
-- dug up blocks (e.g.: coal at 5, additional cobble at
-- 4, cobble depletes, new coal found, goes to 4 ->
-- both 4 and 5 contain coal, where if cobble were
-- behind the coal they'd be merged (assuming what coal
-- we had wasn't a full stack already, of course).
count = count + turtle.getItemCount(slot)
if swap(slot, nextSlot) then
table.insert(lookup, 1, nextSlot)
nextSlot = nextSlot - 1
else
table.insert(lookup, 1, slot)
end
break
end
end
end
end
if #lookup > 0 then
ignoredLookup = lookup
end
-- Add the counts for the excess items in the stacks of the reference
-- slots (we only need to keep one of these).
for slot = 1, ignoreCount do
count = count + (turtle.getItemCount(slot) - 1)
end
-- Remember the level at which we can start placing ignored blocks so
-- that we have enough up to the surface.
placeIgnoredLevel = groundLevel - count
end
switchTo("dig_up")
end)
--[[
Digs back up, picking up whatever isn't ignored. Returns home to drop
stuff if inventory is full to resume where it we off.
]]
:add("dig_up", function()
log:info("Moving up in the world.")
private.sendMessage("state", "dig_up")
-- Get back to where we stopped if we're resuming our job.
if resumeDigUp then
log:info("Resuming from where I took a break.")
private.select(1)
while lama.getY() > resumeDigUp do
lama.down(math.huge, true)
end
resumeDigUp = nil
end
-- Break after block placement to guarantee we always place as necessary.
while true do
-- Try placing before moving, so that we can be sure we placed our
-- thing if we restarted after finishing a move, but before placing.
if groundLevel and lama.getY() <= groundLevel then
-- Note that placeIgnored and placeTorches are mutually exclusive.
if placeIgnored and not placeIgnoredLevel or
lama.getY() > placeIgnoredLevel
then
-- Once we're above the level we can start placing our ignored
-- blocks from we unset the variable to reduce state file size.
if placeIgnoredLevel then
placeIgnoredLevel = nil
save()
end
-- Figure out which slot to place from, starting with overflow
-- slots (stored in ignoreLookup, determined in sort state).
local slot
if ignoredLookup then
slot = ignoredLookup[#ignoredLookup]
else
-- No more lookup-blocks. Draw from our reference stacks.
for i = 1, ignoreCount do
if turtle.getItemCount(i) > 1 then
slot = i
break
end
end
end
-- If no-one stole items from our inventory and we didn't
-- miscalculate we should always have a slot by now.
if slot then
private.select(slot)
turtle.placeDown()
if ignoredLookup then
if turtle.getItemCount(slot) == 0 then
table.remove(ignoredLookup)
end
if #ignoredLookup == 0 then
ignoredLookup = nil
end
save()
end
else
groundLevel = nil
end
elseif placeTorches and lama.getY() % torchFrequency == 0 then
private.select(torchSlot)
turtle.placeDown()
end
else
-- Reset after coming above ground level, to make the check fail
-- at the first part and to reduce state file size.
groundLevel = nil
end
-- Dig out the level.
local newSamples = private.digLevel(ignoreCount, ignoreSamples, false)
if newSamples then
ignoreSamples = newSamples
save()
end
-- Move up until we're back at our starting location.
if lama.getY() >= jobLevel then
break
end
if private.isInventoryFull() then
-- We may sample the blocks on the level we're on twice (again when
-- coming back), but that's an acceptable simplification.
resumeDigUp = lama.getY()
return switchTo("goto_drop")
end
private.select(1)
private.forceMove("dig_up", function()
return lama.up(math.huge, true)
end)
end
finishedJob = job
groundLevel = nil
placeIgnoredLevel = nil
ignoredLookup = nil
job = nil
switchTo("goto_drop")
end)
--[[
Gets enough fuel to move to the graveyard.
]]
:add("refuel_end", function()
log:info("Getting my last rites. Fuel! I meant fuel.")
private.sendMessage("state", "refuel_end")
-- Generate the path to our final resting place: two up, n forward and one
-- to the side (so future finishing turtles can pass us by).
local x, y, z = lama.get()
local path = {
{x = x, y = y, z = z},
{x = 0, y = 2, z = 0},
{x = 0, y = 2, z = endSlot},
{x = 1, y = 2, z = endSlot}
}
private.refuel(private.computeFuelNeeded(path), ignoreCount)
switchTo("clear_inventory")
end)
--[[
Clear our inventory when we're done.
]]
:add("clear_inventory", function()
log:info("Clearing out my inventory since I'm done with the world.")
private.sendMessage("state", "clear_inventory")
for slot = 1, 16 do
private.drop(slot)
end
private.select(1)
switchTo("goto_end")
end)
--[[
Moves to *in front of* the slot where we'll shut down.
]]
:add("goto_end", function()
log:info("Moving out of the way, I'm just useless junk anyway.")
private.sendMessage("state", "goto_end")
local x, y, z = lama.get()
local path = private.generatePath(x, y, z, 0, 2, endSlot, "end")
private.forceMove("goto_end", function()
return lama.navigate(path, math.huge, true)
end)
switchTo("end")
end)
--[[
Moves into the actual slot where we'll shut down and then die quietly.
]]
:add("end", function()
log:info("That's it, I'm going to end it right here. It was a nice life.")
private.sendMessage("state", "end")
private.forceMove("end", function()
return lama.moveto(1, 2, endSlot, lama.side.north, math.huge, true)
end)
startup.disable("jam")
switchTo(nil)
end)
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
-- Utility functions --
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
-- Private namespace.
private = {}
-------------------------------------------------------------------------------
-- Quarry and turtle setup --
-------------------------------------------------------------------------------
--[[
Show instructions on how to set up a JAM quarry.
]]
function private.showBlueprint()
term.clear()
term.setCursorPos(1, 1)
write("To use JAM you need to build a small \n" ..
"docking station. Build it like this: \n" ..
" \n" ..
" CF Where \n" ..
" CD TU CT CD: Chest for dropping \n" ..
" DD found blocks/items. \n" ..
" CF: Chest with turtle fuel.\n" ..
" CT: Chest with torches or blocks to \n" ..
" ignore (depends on config). \n" ..
" DD: Disk drive with an empty disk. \n" ..
" TU: This turtle, back to the drive. \n" ..
" \n" ..
"When done, press [Enter] to continue.")
private.prompt({keys.enter}, {})
end
--[[
Show information about how to configure ignored blocks.
]]
function private.showIgnoreChestInfo(placeIgnored)
term.clear()
term.setCursorPos(1, 1)
print("Please put the block types that you \n" ..
"want to avoid into the torch chest or \n" ..
"my inventory. Insert as many each, as \n" ..
"you wish to employ turtles (normally \n" ..
"one stack each should be plenty). \n\n" ..
"When done, press [Enter] to confirm.\n\n" ..
"Recommended: Stone, Dirt and possibly \n" ..
"Gravel or Sand (depends on the biome).\n" ..
(placeIgnored and
"You may also want to add Cobblestone, \n" ..
"to allow placing it while moving up."
or "" ))
private.prompt({keys.enter}, {})
end
--[[
Asks the user for the size of the dig site and configures the floppy disk.
This function never returns, it reboots the turtle after it has run.
]]
function private.setupDigSite()
-- Label the disk if it hasn't been labeled yet.
if not disk.getLabel(rsDiskSide) or disk.getLabel(rsDiskSide) == "" then
disk.setLabel(rsDiskSide, "JAM Job State")
end
local diskPath = disk.getMountPath(rsDiskSide)
-- Ask the user how big of a dig site he'd like to create.
term.clear()
term.setCursorPos(1, 1)
print(" Welcome to JAM: Just another Miner!\n\n" ..
"Please tell me how large an area you \n" ..
"want to mine. The area will always be \n" ..
"squared. Use the arrow keys to adjust \n" ..
"the size, then press [Enter]. \n")
local _, y = term.getCursorPos()
local format = {" Size: %d (%d chunk, %d hole)\n",
" Size: %d (%d chunks, %d holes)\n"}
print("\n")
write("[left/right: one, up/down: five steps]\n\n" ..
"(Note: chunks refers to the number of \n" ..
"affected ones, not the worked on area)")
local size, radius, chunks, jobCount = 1
repeat
local rx, ry = term.getCursorPos()
radius = size * 3
chunks = math.ceil(radius / 16)
chunks = chunks * chunks
jobCount = private.computeJobCount(radius)
term.setCursorPos(1, y)
term.clearLine()
write(string.format(size == 1 and format[1] or format[2],
radius, chunks, jobCount))
-- For termination not f*cking up the shell.
term.setCursorPos(rx, ry)
local _, code = os.pullEvent("key")
if code == keys.right then
-- Let's bound the maximum dig site size to the area of loaded
-- chunks around a single player...
size = math.min(55, size + 2)
elseif code == keys.left then
size = math.max(1, size - 2)
elseif code == keys.up then
size = math.min(55, size + 10)
elseif code == keys.down then
size = math.max(1, size - 10)
end
until code == keys.enter
local upOption = 0
repeat
term.clear()
term.setCursorPos(1, 1)
print("While making their way up out of the\n" ..
"holes they dug, do you want your \n" ..
"turtles to: \n")
if upOption == 0 then
print(" > Fill the hole with ignored blocks!\n\n" ..
" Place torches every now and then?\n\n" ..
" Just dig as fast as they can? \n")
elseif upOption == 1 then
print(" Fill the hole with ignored blocks?\n\n" ..
" > Place torches every now and then!\n\n" ..
" Just dig as fast as they can? \n")
elseif upOption == 2 then
print(" Fill the hole with ignored blocks?\n\n" ..
" Place torches every now and then?\n\n" ..
" > Just dig as fast as they can! \n")
end
print("Please select one with the arrow keys \n" ..
"and press [Enter] to confirm.")
local _, code = os.pullEvent("key")
if code == keys.up then
upOption = (upOption - 1) % 3
elseif code == keys.down then
upOption = (upOption + 1) % 3
end
until code == keys.enter
local placeIgnored = upOption == 0
local placeTorches = upOption == 1
local haveIgnoreChest = false
if not placeTorches then
term.clear()
term.setCursorPos(1, 1)
print("Since the turtles won't need one of \n" ..
"the chests for torches, you can \n" ..
"instead place blocks you want your \n" ..
"turtles to ignore in that chest. \n" ..
"This way you won't have to add them \n" ..
"to each one individually. You'll \n" ..
"have to place exacly one stack of at\n" ..
"least a size equal to the number of \n" ..
"turtles you plan to use into the \n" ..
"torch chest.\n")
write("Do you want to do this? [Y/n] > ")
haveIgnoreChest = private.prompt()
end
term.clear()
term.setCursorPos(1, 1)
print("Please confirm your settings\n\n" ..
" Radius: " .. radius .. "\n" ..
" Affected chunks: " .. chunks .. "\n" ..
" Holes/Jobs: " .. jobCount .. "\n" ..
" Place blocks: " .. tostring(placeIgnored) .. "\n" ..
" Place torches: " .. tostring(placeTorches) .. "\n" ..
" Blocks to ignore in chest: " .. tostring(haveIgnoreChest))
write("\nIs this correct? [Y/n] > ")
if not private.prompt() then
-- nil causes restart.
return nil
end
-- Set up the disk so that it installs the script to any
-- turtle that starts up while in front of it.
term.clear()
term.setCursorPos(1, 1)
print("Writing initialization data to disk...")
local install = {
{from = bapil.resolveAPI("apis/bapil"),
to = fs.combine(diskPath, "apis/bapil")},
{from = bapil.resolveAPI("apis/lama"),
to = fs.combine(diskPath, "apis/lama")},
{from = bapil.resolveAPI("apis/logger"),
to = fs.combine(diskPath, "apis/logger")},
{from = bapil.resolveAPI("apis/stacktrace"),
to = fs.combine(diskPath, "apis/stacktrace")},
{from = bapil.resolveAPI("apis/startup"),
to = fs.combine(diskPath, "apis/startup")},
{from = bapil.resolveAPI("apis/state"),
to = fs.combine(diskPath, "apis/state")},
{from = shell.getRunningProgram(),
to = fs.combine(diskPath, "programs/jam")},
{from = "programs/jam-status",
to = fs.combine(diskPath, "programs/jam-status")},
-- We write a startup file to the disk drive that will install our
-- environment to any new turtles started up in front of it. This is
-- pretty much the same we do here, in reverse.
{content = string.format(
[=[-- Ignore anything that isn't a turtle.
if not turtle then
return fs.exists("/startup") and dofile("/startup")
end
-- Ignore if we're on the wrong side. Otherwise get the mount path of the disk.
local diskSide = %q
local diskPath = disk.getMountPath(diskSide)
if not disk.isPresent(diskSide) then
return fs.exists("/startup") and dofile("/startup")
end
-- Update the environment, copy APIs, create startup file, that kind of stuff.
print("Updating JAM installation...")
-- Set label if necessary.
if os.getComputerLabel() == nil or os.getComputerLabel() == "" then
os.setComputerLabel("JAM-" .. os.getComputerID())
end
-- List of files we put on our turtles.
local install = {
{from = fs.combine(diskPath, "apis/bapil"),
to = "/apis/bapil"},
{from = fs.combine(diskPath, "apis/lama"),
to = "/apis/lama"},
{from = fs.combine(diskPath, "apis/logger"),
to = "/apis/logger"},
{from = fs.combine(diskPath, "apis/stacktrace"),
to = "/apis/stacktrace"},
{from = fs.combine(diskPath, "apis/startup"),
to = "/apis/startup"},
{from = fs.combine(diskPath, "apis/state"),
to = "/apis/state"},
{from = fs.combine(diskPath, "programs/jam"),
to = "/programs/jam"},
{content =
[[if startup then return false end
os.loadAPI("apis/bapil")
bapil.hijackOSAPI()
shell.setPath(shell.path() .. ":/programs")