dome_tunnels.lua

633 lines · 18.4 KB

Open raw

Dome Tunnels by Silvamord (based on epicmining_turtle.lua style) Place the turtle at the bottom-left corner of the tunnel cross-section, facing forward (along the tunnel direction). The script will carve a dome profile slice-by-slice and advance. Parameters: - Width (min 2): number of blocks across (left to right) - Side height: height of the extreme left and right columns - Center height: height of the inner columns (2..width-1) - Length: tunnel length in blocks (number of slices) - Corner radius: rounds the top corners by reducing height near the sides (radius 0 keeps sharp edges; radius 1+ trims the uppermost layers on sides) Example matching the request: - Width = 5 - Side height = 3 - Center height = 4 - Length = your choice - Corner radius = 0..2 (try 1 for a slight dome rounding) ]]-- local version = "1.1" -- UI helpers (minimal) local function ask_number_default(prompt, default_value, min_val, max_val) while true do term.clear() term.setCursorPos(1,1) write("Dome Tunnels v"..version.."\n\n")

Copy & run

wget https://perlytiara.github.io/turtles.tips/raw/programs/perlytiara/dome_tunnels/dome_tunnels.lua
1--[[
2 Dome Tunnels by Silvamord (based on epicmining_turtle.lua style)
3 Place the turtle at the bottom-left corner of the tunnel cross-section,
4 facing forward (along the tunnel direction). The script will carve a dome
5 profile slice-by-slice and advance.
6
7 Parameters:
8 - Width (min 2): number of blocks across (left to right)
9 - Side height: height of the extreme left and right columns
10 - Center height: height of the inner columns (2..width-1)
11 - Length: tunnel length in blocks (number of slices)
12 - Corner radius: rounds the top corners by reducing height near the sides
13 (radius 0 keeps sharp edges; radius 1+ trims the uppermost layers on sides)
14
15 Example matching the request:
16 - Width = 5
17 - Side height = 3
18 - Center height = 4
19 - Length = your choice
20 - Corner radius = 0..2 (try 1 for a slight dome rounding)
21]]--
22
23local version = "1.1"
24
25-- UI helpers (minimal)
26local function ask_number_default(prompt, default_value, min_val, max_val)
27 while true do
28 term.clear()
29 term.setCursorPos(1,1)
30 write("Dome Tunnels v"..version.."\n\n")
31 write(string.format("%s [default: %s] ", prompt, tostring(default_value)))
32 local s = read()
33 if s == nil or s == "" then
34 return default_value
35 end
36 local n = tonumber(s)
37 if n and (not min_val or n >= min_val) and (not max_val or n <= max_val) then
38 return n
39 end
40 write("\nInvalid input. Press Enter to try again.")
41 read()
42 end
43end
44
45local function ask_yes_no(prompt, default_yes)
46 while true do
47 term.clear()
48 term.setCursorPos(1,1)
49 write("Dome Tunnels v"..version.."\n\n")
50 local def = default_yes and "Y" or "N"
51 write(string.format("%s (y/n) [default: %s] ", prompt, def))
52 local s = read()
53 s = s and string.lower(s) or ""
54 if s == "" then return default_yes end
55 if s == "y" or s == "yes" then return true end
56 if s == "n" or s == "no" then return false end
57 write("\nInvalid input. Press Enter to try again.")
58 read()
59 end
60end
61
62local function ask_choice(prompt, default_value, choices)
63 while true do
64 term.clear()
65 term.setCursorPos(1,1)
66 write("Dome Tunnels v"..version.."\n\n")
67 write(string.format("%s %s [default: %s] ", prompt, table.concat(choices, "/"), tostring(default_value)))
68 local s = read()
69 if s == nil or s == "" then return default_value end
70 s = string.lower(s)
71 for _, c in ipairs(choices) do
72 if s == c then return s end
73 end
74 write("\nInvalid input. Press Enter to try again.")
75 read()
76 end
77end
78
79-- Movement helpers
80local function ensure_fuel(threshold)
81 if turtle.getFuelLevel() == "unlimited" then return end
82 if turtle.getFuelLevel() >= threshold then return end
83 for i = 1, 16 do
84 turtle.select(i)
85 if turtle.refuel(0) then
86 while turtle.getFuelLevel() < threshold and turtle.refuel(1) do end
87 if turtle.getFuelLevel() >= threshold then
88 return
89 end
90 end
91 end
92 term.clear()
93 term.setCursorPos(1,1)
94 print("Out of fuel. Put fuel in inventory and press Enter.")
95 read()
96 return ensure_fuel(threshold)
97end
98
99local function dig_forward()
100 while turtle.detect() do
101 if not turtle.dig() then
102 -- try to attack entities if any
103 turtle.attack()
104 sleep(0.2)
105 end
106 end
107end
108
109local function dig_upwards()
110 while turtle.detectUp() do
111 if not turtle.digUp() then
112 turtle.attackUp()
113 sleep(0.2)
114 end
115 end
116end
117
118local function dig_downwards()
119 while turtle.detectDown() do
120 if not turtle.digDown() then
121 turtle.attackDown()
122 sleep(0.2)
123 end
124 end
125end
126
127local function safe_forward()
128 ensure_fuel(100)
129 while not turtle.forward() do
130 dig_forward()
131 -- if still blocked, wait a bit (gravel/sand falling)
132 sleep(0.05)
133 end
134end
135
136local function safe_up()
137 ensure_fuel(100)
138 while not turtle.up() do
139 dig_upwards()
140 sleep(0.05)
141 end
142end
143
144local function safe_down()
145 ensure_fuel(100)
146 while not turtle.down() do
147 dig_downwards()
148 sleep(0.05)
149 end
150end
151
152local function turn_left()
153 turtle.turnLeft()
154end
155
156local function turn_right()
157 turtle.turnRight()
158end
159
160-- Lateral move: step right relative to forward
161local function step_right()
162 turn_right()
163 dig_forward()
164 safe_forward()
165 turn_left()
166end
167
168-- Lateral move: step left relative to forward
169local function step_left()
170 turn_left()
171 dig_forward()
172 safe_forward()
173 turn_right()
174end
175
176-- Compute the effective height for a given column x (0-based from left)
177local function column_height(x, width, side_h, center_h, radius)
178 -- If radius == 0, use stepped profile (edges at side_h, inner at center_h)
179 if (radius or 0) <= 0 then
180 if x == 0 or x == width - 1 then return side_h else return center_h end
181 end
182 -- Rounded dome via cosine interpolation from edge (u=0) to center (u=1)
183 local u
184 if width <= 1 then
185 u = 1
186 else
187 u = 1 - (math.abs(2*x - (width - 1)) / (width - 1))
188 end
189 -- Curvature control: larger radius -> rounder corners (faster rise near edges)
190 local exponent = 1 / (1 + radius)
191 local u2 = u ^ exponent
192 local f = (1 - math.cos(math.pi * u2)) / 2 -- 0 at edges, 1 at center
193 local h = math.floor(0.5 + (side_h + (center_h - side_h) * f))
194 if h < side_h then h = side_h end
195 if h > center_h then h = center_h end
196 return h
197end
198
199local function compute_heights(width, side_h, center_h, radius)
200 local heights = {}
201 for x = 0, width - 1 do
202 heights[x+1] = column_height(x, width, side_h, center_h, radius)
203 end
204 return heights
205end
206
207-- Carve the slice in front of the turtle for a single forward step
208-- starting at bottom edge; if start_at_left is true, begin at leftmost,
209-- otherwise begin at rightmost. Uses serpentine per-row to reduce moves.
210-- Returns boolean: are we at left edge after finishing the slice?
211local function carve_slice(width, side_h, center_h, radius, start_at_left, current_level)
212 local heights = compute_heights(width, side_h, center_h, radius)
213 local need_upper = false
214 for i = 1, #heights do if (heights[i] or 0) >= 4 then need_upper = true break end end
215
216 local function move_to_level(target)
217 while current_level < target do safe_up(); current_level = current_level + 1 end
218 while current_level > target do safe_down(); current_level = current_level - 1 end
219 end
220
221 -- Enter slice once at start, then operate mostly at level 2
222 dig_forward(); safe_forward()
223 if (heights[start_at_left and 1 or width] or 0) >= 2 then
224 move_to_level(2)
225 else
226 current_level = 1
227 end
228
229 local end_at_left
230 if start_at_left then
231 for x = 1, width do
232 local h = heights[x] or 0
233 if current_level < 2 and h >= 2 then move_to_level(2) end
234 if current_level > 2 and h < 3 then move_to_level(2) end
235 if h >= 1 then turtle.digDown() end
236 if h >= 3 then dig_upwards() end
237 if x < width then step_right() end
238 end
239 end_at_left = false
240 else
241 for x = width, 1, -1 do
242 local h = heights[x] or 0
243 if current_level < 2 and h >= 2 then move_to_level(2) end
244 if current_level > 2 and h < 3 then move_to_level(2) end
245 if h >= 1 then turtle.digDown() end
246 if h >= 3 then dig_upwards() end
247 if x > 1 then step_left() end
248 end
249 end_at_left = true
250 end
251
252 -- Upper pass only if needed (h==4)
253 if need_upper then
254 move_to_level(3)
255 if end_at_left then
256 for x = 1, width do
257 if (heights[x] or 0) >= 4 then dig_upwards() end
258 if x < width then step_right() end
259 end
260 end_at_left = false
261 else
262 for x = width, 1, -1 do
263 if (heights[x] or 0) >= 4 then dig_upwards() end
264 if x > 1 then step_left() end
265 end
266 end_at_left = true
267 end
268 -- settle to level 2 to start next slice efficiently
269 move_to_level(2)
270 end
271
272 return end_at_left, current_level
273end
274
275-- Estimate forward/up/down moves required to carve a slice and advance by one
276local function estimate_moves_per_slice(width, side_h, center_h, radius)
277 local heights = compute_heights(width, side_h, center_h, radius)
278 local need_upper = false
279 for i = 1, #heights do if (heights[i] or 0) >= 4 then need_upper = true break end end
280 local lateral = (width - 1)
281 local vertical = 1 -- adjust to level 2
282 if need_upper then
283 vertical = vertical + 2 -- up to level 3 and back to 2
284 lateral = lateral + (width - 1)
285 end
286 local advance = 1 -- entering slice
287 return vertical + lateral + advance
288end
289
290local function turn_around()
291 turn_right(); turn_right()
292end
293
294local function move_forward_n(n)
295 for i = 1, n do
296 -- path should be clear; still be safe
297 safe_forward()
298 end
299end
300
301local function ensure_fuel_or_prompt(threshold)
302 ensure_fuel(threshold)
303 if turtle.getFuelLevel() == "unlimited" then return end
304 if turtle.getFuelLevel() >= threshold then return end
305 term.clear()
306 term.setCursorPos(1,1)
307 print("Fuel still low. Put fuel in inventory and press Enter.")
308 read()
309 ensure_fuel(threshold)
310end
311
312local function attempt_return_for_refuel(depth, reserve_needed)
313 -- Ensure we can get back to start and forth again
314 local minimal = depth * 2 + 4
315 if turtle.getFuelLevel() ~= "unlimited" and turtle.getFuelLevel() < minimal then
316 ensure_fuel_or_prompt(minimal)
317 end
318 turn_around()
319 move_forward_n(depth)
320 term.clear(); term.setCursorPos(1,1)
321 print("At start. Add fuel now, then press Enter to resume.")
322 read()
323 ensure_fuel_or_prompt(reserve_needed)
324 turn_around()
325 move_forward_n(depth)
326end
327
328-- Favorites storage helpers
329local function favorite_path()
330 local dir = ".dome_tunnels"
331 if not fs.exists(dir) then fs.makeDir(dir) end
332 return fs.combine(dir, "dome_tunnel_favorite")
333end
334
335local function load_favorite()
336 local path = favorite_path()
337 -- Backward compatibility: migrate old root-level file if present
338 if not fs.exists(path) and fs.exists("dome_favorite") then
339 local hOld = fs.open("dome_favorite", "r")
340 if hOld then
341 local d = hOld.readAll(); hOld.close()
342 local okOld, tblOld = pcall(textutils.unserialize, d)
343 if okOld and type(tblOld) == "table" then
344 local hNew = fs.open(path, "w")
345 if hNew then hNew.write(textutils.serialize(tblOld)); hNew.close() end
346 -- Optionally remove old file
347 pcall(fs.delete, "dome_favorite")
348 end
349 end
350 end
351 if not fs.exists(path) then return nil end
352 local h = fs.open(path, "r")
353 if not h then return nil end
354 local data = h.readAll()
355 h.close()
356 local ok, tbl = pcall(textutils.unserialize, data)
357 if ok and type(tbl) == "table" then return tbl end
358 return nil
359end
360
361local function save_favorite(fav)
362 local path = favorite_path()
363 local h = fs.open(path, "w")
364 if not h then return end
365 h.write(textutils.serialize(fav))
366 h.close()
367end
368
369-- Inventory helpers
370local function count_empty_slots()
371 local empty = 0
372 for i = 1, 16 do
373 if turtle.getItemCount(i) == 0 then empty = empty + 1 end
374 end
375 return empty
376end
377
378local function any_item_matches_slot(slot_idx)
379 if turtle.getItemCount(slot_idx) == 0 then return false end
380 for i = 1, 16 do
381 if i ~= slot_idx and turtle.getItemCount(i) > 0 then
382 turtle.select(i)
383 if turtle.compareTo(slot_idx) then return true end
384 end
385 end
386 return false
387end
388
389local function drop_matching_front(slot_idx)
390 for i = 1, 16 do
391 if i ~= slot_idx and turtle.getItemCount(i) > 0 then
392 turtle.select(i)
393 if turtle.compareTo(slot_idx) then
394 turtle.drop()
395 end
396 end
397 end
398end
399
400local function turn_to_side(side)
401 if side == "left" then turn_left() else turn_right() end
402end
403
404local function place_chest_in_wall(chest_slot, side)
405 if turtle.getItemCount(chest_slot) == 0 then return false end
406 turn_to_side(side)
407 dig_forward()
408 turtle.select(chest_slot)
409 local ok = turtle.place()
410 if not ok then
411 -- try clearing again and placing
412 dig_forward()
413 ok = turtle.place()
414 end
415 if not ok then
416 -- fallback: place below
417 ok = turtle.placeDown()
418 end
419 if not ok then
420 -- failed to place; face forward again
421 turn_to_side(side == "left" and "right" or "left")
422 return false
423 end
424 -- face back forward
425 turn_to_side(side == "left" and "right" or "left")
426 return true
427end
428
429local function deposit_into_front(chest_slot, torch_slot, throw_slot)
430 for i = 1, 16 do
431 if i ~= chest_slot and i ~= torch_slot and i ~= throw_slot and turtle.getItemCount(i) > 0 then
432 turtle.select(i)
433 if turtle.refuel(0) then
434 -- skip fuel
435 else
436 turtle.drop()
437 end
438 end
439 end
440 -- handle throw slot: keep one
441 if throw_slot and throw_slot >= 1 and throw_slot <= 16 then
442 local cnt = turtle.getItemCount(throw_slot)
443 if cnt > 1 then
444 turtle.select(throw_slot)
445 turtle.drop(cnt - 1)
446 end
447 end
448end
449
450local function ensure_inventory_capacity(cfg, at_left_edge)
451 local empty = count_empty_slots()
452 if empty > 0 then return end
453 if cfg.use_throw and turtle.getItemCount(cfg.throw_slot) > 0 and any_item_matches_slot(cfg.throw_slot) then
454 drop_matching_front(cfg.throw_slot)
455 if count_empty_slots() > 0 then return end
456 end
457 if cfg.use_chests then
458 local side = at_left_edge and "left" or "right"
459 if place_chest_in_wall(cfg.chest_slot, side) then
460 deposit_into_front(cfg.chest_slot, cfg.torch_slot, cfg.throw_slot)
461 end
462 end
463end
464
465local function place_torch_if_needed(step_idx, cfg, width, side_h, center_h, radius, at_left_edge)
466 if not cfg.use_torches then return end
467 if cfg.torch_spacing <= 0 then return end
468 if step_idx % cfg.torch_spacing ~= 0 then return end
469 if turtle.getItemCount(cfg.torch_slot) == 0 then return end
470 local function place_on_side(side)
471 local heights = compute_heights(width, side_h, center_h, radius)
472 local h_edge = heights[(side == "left") and 1 or width] or 1
473 local climbed = 0
474 if h_edge >= 2 then
475 safe_up()
476 climbed = climbed + 1
477 end
478 turn_to_side(side)
479 turtle.select(cfg.torch_slot)
480 local ok = turtle.place()
481 if not ok then
482 -- fallback to floor torch
483 turn_to_side(side == "left" and "right" or "left")
484 for i = 1, climbed do safe_down() end
485 turtle.select(cfg.torch_slot)
486 ok = turtle.placeDown()
487 return
488 end
489 turn_to_side(side == "left" and "right" or "left")
490 for i = 1, climbed do safe_down() end
491 end
492
493 if cfg.torch_side == "both" then
494 -- Place on both sides, starting with the current edge for efficiency
495 if at_left_edge then
496 place_on_side("left")
497 place_on_side("right")
498 else
499 place_on_side("right")
500 place_on_side("left")
501 end
502 else
503 -- Single side: honor user's side; if set to left/right, use that side
504 place_on_side(cfg.torch_side)
505 end
506end
507
508-- Main
509local function main()
510 term.clear()
511 term.setCursorPos(1,1)
512 print("Dome Tunnels v"..version)
513 print("Place turtle at bottom-left corner, facing forward.")
514
515 local fav = load_favorite()
516 local use_fav = false
517 if fav then
518 use_fav = ask_yes_no("Use favorite saved config?", true)
519 end
520
521 local width, side_h, center_h, length, radius, auto_return
522 local use_torches, torch_spacing, torch_side, torch_slot
523 local use_chests, chest_slot
524 local use_throw, throw_slot
525
526 if use_fav then
527 width = fav.width; side_h = fav.side_h; center_h = fav.center_h; length = fav.length; radius = fav.radius
528 auto_return = fav.auto_return
529 use_torches = fav.use_torches; torch_spacing = fav.torch_spacing or 9; torch_side = fav.torch_side or "right"; torch_slot = fav.torch_slot or 1
530 use_chests = fav.use_chests; chest_slot = fav.chest_slot or 2
531 use_throw = fav.use_throw; throw_slot = fav.throw_slot or 4
532 else
533 -- Defaults tailored to the requested shape: 5-wide, sides 3 high, center 4 high
534 width = ask_number_default("Width (min 2):", 5, 2, 64)
535 side_h = ask_number_default("Side height (>=1):", 3, 1, 64)
536 center_h = ask_number_default("Center height (>=1):", 4, 1, 64)
537 length = ask_number_default("Length (>=1):", 32, 1, 100000)
538 radius = ask_number_default("Corner radius (0 = stepped, higher = rounder):", 0, 0, 32)
539 auto_return = ask_yes_no("Auto-return to start to refuel when needed?", true)
540 use_torches = ask_yes_no("Place torches?", true)
541 if use_torches then
542 torch_spacing = ask_number_default("Torch spacing (blocks):", 9, 1, 64)
543 torch_side = ask_choice("Torch side:", "both", {"left", "right", "both"})
544 torch_slot = ask_number_default("Torch slot (1-16):", 1, 1, 16)
545 else
546 torch_spacing = 0; torch_side = "right"; torch_slot = 1
547 end
548 use_chests = ask_yes_no("Place chests to dump items when full?", true)
549 if use_chests then
550 chest_slot = ask_number_default("Chest slot (1-16):", 2, 1, 16)
551 else
552 chest_slot = 2
553 end
554 use_throw = ask_yes_no("Throw items matching a sample when full?", false)
555 if use_throw then
556 throw_slot = ask_number_default("Sample slot to throw (1-16):", 4, 1, 16)
557 else
558 throw_slot = 4
559 end
560 local savefav = ask_yes_no("Save these settings as favorite?", true)
561 if savefav then
562 local tosave = {
563 width = width, side_h = side_h, center_h = center_h, length = length, radius = radius,
564 auto_return = auto_return,
565 use_torches = use_torches, torch_spacing = torch_spacing, torch_side = torch_side, torch_slot = torch_slot,
566 use_chests = use_chests, chest_slot = chest_slot,
567 use_throw = use_throw, throw_slot = throw_slot
568 }
569 save_favorite(tosave)
570 end
571 end
572
573 local cfg = {
574 use_torches = use_torches, torch_spacing = torch_spacing, torch_side = torch_side, torch_slot = torch_slot,
575 use_chests = use_chests, chest_slot = chest_slot,
576 use_throw = use_throw, throw_slot = throw_slot,
577 auto_return = auto_return
578 }
579
580 term.clear()
581 term.setCursorPos(1,1)
582 print("Dome Tunnels v"..version)
583 print("Width:\t\t"..width)
584 print("Side height:\t"..side_h)
585 print("Center height:\t"..center_h)
586 print("Length:\t\t"..length)
587 print("Corner radius:\t"..radius)
588 print("Auto-return for fuel:\t"..(auto_return and "Yes" or "No"))
589 print("Torches:\t\t"..(cfg.use_torches and ("Yes (every "..cfg.torch_spacing.." blocks)") or "No"))
590 if cfg.use_torches then
591 print("Torch side:\t"..cfg.torch_side.." (top corners; no extra wall digging)")
592 print("Torch slot:\t"..cfg.torch_slot)
593 end
594 print("Chests:\t\t"..(cfg.use_chests and ("Yes (slot "..cfg.chest_slot..")") or "No"))
595 print("Throw items:\t"..(cfg.use_throw and ("Yes (sample slot "..cfg.throw_slot..")") or "No"))
596 print("")
597 print("Put torches in slot "..cfg.torch_slot..", chests in slot "..cfg.chest_slot..", sample item in slot "..cfg.throw_slot.." if throwing is enabled.")
598 print("")
599 print("Press Enter to start...")
600 read()
601
602 local per_slice_moves = estimate_moves_per_slice(width, side_h, center_h, radius)
603 local depth = 0
604 local at_left_edge = true -- start at bottom-left by convention
605 local current_level = 1
606
607 -- Carve tunnel
608 for step = 1, length do
609 ensure_inventory_capacity(cfg, at_left_edge)
610 -- Fuel check: we want enough for this slice plus the trip back to start
611 local reserve_needed = per_slice_moves + depth + 10
612 if turtle.getFuelLevel() ~= "unlimited" and turtle.getFuelLevel() < reserve_needed then
613 ensure_fuel_or_prompt(reserve_needed)
614 if turtle.getFuelLevel() < reserve_needed and cfg.auto_return then
615 attempt_return_for_refuel(depth, reserve_needed)
616 end
617 end
618
619 at_left_edge, current_level = carve_slice(width, side_h, center_h, radius, at_left_edge, current_level)
620 place_torch_if_needed(step, cfg, width, side_h, center_h, radius, at_left_edge)
621 ensure_inventory_capacity(cfg, at_left_edge)
622 depth = depth + 1
623 end
624
625 term.clear()
626 term.setCursorPos(1,1)
627 print("Done. Dome tunnel completed.")
628end
629
630main()
631
632
633