Phylla

bilinual Builder

Nov 20th, 2025 (edited)
61
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 73.14 KB | Gaming | 0 0
  1. -- builder_pro_failsafe.lua (2025 PRO + SMART FAILSAFE + MULTILINGUAL)
  2. -- Shapes: Torus | Sphere | Cylinder | Dome (hollow)
  3. -- Modes: Safe + Fast (Overhead optional) | Random Slot Placement | Auto-Rounding
  4. -- SMART ERROR RECOVERY: Turtle kehrt bei Fehler zum letzten Save zurück!
  5. -- Multilingual: DE | EN (easily extendable)
  6. -- Start: Turtle an SW-Unterkante des Volumens, Blick nach Osten (+X)
  7. -- Chests: Material (0,-1,0) | Fuel (+1,-1,+1)
  8.  
  9. -- UI SCALING (reduces menu size)
  10. pcall(function() term.setTextScale(0.5) end)
  11. ------------------------------------------------------------
  12. -- LANGUAGE SYSTEM ------------------------------------------
  13. local LANG = "de"  -- "de" | "en"
  14. local LANG_FILE = "builder_lang.txt"
  15.  
  16. -- Translations
  17. local TRANSLATIONS = {
  18.   de = {
  19.     menu_title = "Builder Menu",
  20.     menu_mode = "mode",
  21.     menu_1 = "1) Plan ansehen",
  22.     menu_2 = "2) Build starten",
  23.     menu_3 = "3) Resume",
  24.     menu_4 = "4) Reset Save",
  25.     menu_5 = "5) Toggle Mode (aktuell: %s)",
  26.     menu_r = "R) Return to saved position (auto-resume)",
  27.     menu_c = "C) Configure Shape/Size",
  28.     menu_a = "A) Advanced (Overhead/Dig)",
  29.     menu_l = "L) Language / Sprache",
  30.     menu_q = "Q) Quit",
  31.    
  32.     -- Menu explanations
  33.     menu_explain_1 = "Zeigt geplante Struktur und Material-Bedarf",
  34.     menu_explain_2 = "Startet neuen Build (Chests werden platziert)",
  35.     menu_explain_3 = "Setzt unterbrochenen Build fort",
  36.     menu_explain_4 = "Loescht gespeicherten Fortschritt",
  37.     menu_explain_5_fast = "FAST: Schnell mit Overhead-Travel (fuer freien Luftraum)",
  38.     menu_explain_5_safe = "SAFE: Sicher, grabt durch Hindernisse",
  39.     menu_explain_r = "Turtle zu letzter Position (nach Chunk-Unload/Abbau)",
  40.     menu_explain_c = "Waehle Form und Parameter",
  41.     menu_explain_a = "Overhead-Travel & Dig-Optionen",
  42.     menu_explain_l = "Sprache wechseln (DE/EN)",
  43.    
  44.     shape_1 = "1) Torus",
  45.     shape_2 = "2) Sphere (Kugel/Ellipsoid)",
  46.     shape_3 = "3) Cylinder (Hohl)",
  47.     shape_4 = "4) Dome (Kuppel)",
  48.    
  49.     shape_explain_torus = "Donut-Form (Ring um Ring)",
  50.     shape_explain_sphere = "Hohle Kugel (mit XYZ-Skalierung fuer Ellipsoide)",
  51.     shape_explain_cylinder = "Hohler Zylinder (Turm oder Tunnel)",
  52.     shape_explain_dome = "Kuppel (ganz, oben oder unten)",
  53.    
  54.     dome_full = "Volle Kuppel",
  55.     dome_upper = "Obere Haelfte",
  56.     dome_lower = "Untere Haelfte",
  57.    
  58.     prompt_major_radius = "Grosser Radius R",
  59.     prompt_major_radius_help = "Abstand vom Zentrum zur Mitte des Rohrs",
  60.     prompt_tube_radius = "Rohr-Radius r",
  61.     prompt_tube_radius_help = "Dicke des Torus-Rohrs",
  62.     prompt_radius = "Radius r",
  63.     prompt_radius_help = "Radius der Struktur (Abstand vom Zentrum)",
  64.     prompt_thickness = "Wanddicke",
  65.     prompt_thickness_help = "Dicke der Wand in Bloecken (1=duenn, 2+=dick)",
  66.     prompt_height = "Hoehe (Y)",
  67.     prompt_height_help = "Hoehe des Zylinders in Bloecken",
  68.     prompt_floors = "Zwischenboeden (0=keine)",
  69.     prompt_floors_help = "Alle X Bloecke einen Boden (0=keine Boeden)",
  70.     prompt_caps = "Deckel/Boden voll schliessen?",
  71.     prompt_caps_help = "y=Zylinder zu, n=Zylinder offen",
  72.     prompt_xscale = "X-Skalierung (1.0=rund)",
  73.     prompt_xscale_help = "Streckung in X-Richtung (0.5=flach, 2.0=lang)",
  74.     prompt_yscale = "Y-Skalierung (1.0=rund)",
  75.     prompt_yscale_help = "Streckung in Y-Richtung (Hoehe)",
  76.     prompt_zscale = "Z-Skalierung (1.0=rund)",
  77.     prompt_zscale_help = "Streckung in Z-Richtung",
  78.     prompt_dome_type = "Dome-Typ (1=voll, 2=oben, 3=unten)",
  79.     prompt_dome_type_help = "1=Ganze Kugel, 2=Nur oben, 3=Nur unten",
  80.     prompt_cyl_orientation = "Zylinder-Orientierung (1=horizontal, 2=vertikal)",
  81.     prompt_cyl_orientation_help = "1=Tunnel/Roehre, 2=Turm/Saeule",
  82.    
  83.     -- Advanced menu
  84.     adv_explain_overhead = "Fliegt ueber Struktur zwischen Runs (schneller)",
  85.     adv_explain_dig = "Graebt durch Bloecke beim Bewegen (nur FAST)",
  86.    
  87.     -- Build start
  88.     build_setup_title = "Build-Setup",
  89.     build_setup_position = "Platziere Turtle an SW-Ecke (Suedwest)",
  90.     build_setup_facing = "Turtle muss nach OSTEN (+X) schauen",
  91.     build_setup_chests = "2 Chests bereithalten (Material + Fuel)",
  92.     build_setup_material = "Material-Chest: unter Turtle (0,-1,0)",
  93.     build_setup_fuel = "Fuel-Chest: rechts-vorne-unten (+1,-1,+1)",
  94.     build_setup_ready = "Druecke P zum Pausieren, Q zum Speichern+Beenden, S fuer Status",
  95.    
  96.     -- Plan display
  97.     plan_dimensions = "Abmessungen",
  98.     plan_volume = "Bauvolumen: %dx%d Grundflaeche, %d Bloecke hoch",
  99.     plan_size_xyz = "  X (Laenge): %d | Y (Hoehe): %d | Z (Breite): %d",
  100.     plan_bounds = "  Von (0,0,0) bis (%d,%d,%d)",
  101.     plan_blocks_total = "Geplante Bloecke: %d",
  102.     plan_center = "Mittelpunkt der Struktur",
  103.     plan_center_coords = "  Center: (%d, %d, %d) relativ zur Turtle",
  104.     plan_center_world = "  Bei Turtle-Start (0,0,0) → Center ist bei (%d, %d, %d)",
  105.     plan_center_help = "Bewege Turtle VOR dem Build um Center zu verschieben!",
  106.     plan_material_needed = "Benoetigtes Material",
  107.     plan_blocks = "Bloecke: %d",
  108.     plan_stacks = "Stacks (64er): ~%d",
  109.     plan_shulker = "Shulker Boxen: ~%d",
  110.     plan_fuel_approx = "Fuel (ca.): %d Bewegungen",
  111.     plan_coal = "Coal (ca.): ~%d",
  112.     plan_time_warning = "WARNUNG: %d Bloecke - das dauert lange!",
  113.     plan_time_estimate = "Geschaetzte Zeit: %.1f Stunden (bei ~20 Bloecke/Min)",
  114.    
  115.     fuel_low = "Fuel niedrig (%d/%d) - versuche aus Slot 1...",
  116.     fuel_refilled = "Fuel aufgefuellt! Level: %s",
  117.     material_fetched = "Material geholt! %d Bloecke verfuegbar",
  118.    
  119.     build_complete = "Build complete!",
  120.     save_deleted = "Save geloescht.",
  121.     mode_changed = "Mode -> %s",
  122.    
  123.     recovery_success = "RECOVERY ERFOLGREICH! Position: [%d,%d,%d]",
  124.     return_and_resume = "Zurueck an Bauposition: [%d,%d,%d] - Bauen geht weiter!",
  125.    
  126.     -- Keys
  127.     key_p = "P = Pause/Weiter",
  128.     key_q = "Q = Speichern & Beenden",
  129.     key_s = "S = Status anzeigen",
  130.   },
  131.  
  132.   en = {
  133.     menu_title = "Builder Menu",
  134.     menu_mode = "mode",
  135.     menu_1 = "1) View Plan",
  136.     menu_2 = "2) Start Build",
  137.     menu_3 = "3) Resume",
  138.     menu_4 = "4) Reset Save",
  139.     menu_5 = "5) Toggle Mode (current: %s)",
  140.     menu_r = "R) Return to saved position (auto-resume)",
  141.     menu_c = "C) Configure Shape/Size",
  142.     menu_a = "A) Advanced (Overhead/Dig)",
  143.     menu_l = "L) Language / Sprache",
  144.     menu_q = "Q) Quit",
  145.    
  146.     -- Menu explanations
  147.     menu_explain_1 = "Shows planned structure and material requirements",
  148.     menu_explain_2 = "Starts new build (chests will be placed)",
  149.     menu_explain_3 = "Continues interrupted build",
  150.     menu_explain_4 = "Deletes saved progress",
  151.     menu_explain_5_fast = "FAST: Quick with overhead travel (for open airspace)",
  152.     menu_explain_5_safe = "SAFE: Safe, digs through obstacles",
  153.     menu_explain_r = "Turtle to last position (after chunk unload/pickup)",
  154.     menu_explain_c = "Choose shape and parameters",
  155.     menu_explain_a = "Overhead-Travel & Dig options",
  156.     menu_explain_l = "Change language (DE/EN)",
  157.    
  158.     shape_1 = "1) Torus",
  159.     shape_2 = "2) Sphere (Ellipsoid)",
  160.     shape_3 = "3) Cylinder (Hollow)",
  161.     shape_4 = "4) Dome",
  162.    
  163.     shape_explain_torus = "Donut shape (ring around ring)",
  164.     shape_explain_sphere = "Hollow sphere (with XYZ scaling for ellipsoids)",
  165.     shape_explain_cylinder = "Hollow cylinder (tower or tunnel)",
  166.     shape_explain_dome = "Dome (full, upper or lower half)",
  167.    
  168.     dome_full = "Full Dome",
  169.     dome_upper = "Upper Half",
  170.     dome_lower = "Lower Half",
  171.    
  172.     prompt_major_radius = "Major Radius R",
  173.     prompt_major_radius_help = "Distance from center to middle of tube",
  174.     prompt_tube_radius = "Tube Radius r",
  175.     prompt_tube_radius_help = "Thickness of torus tube",
  176.     prompt_radius = "Radius r",
  177.     prompt_radius_help = "Radius of structure (distance from center)",
  178.     prompt_thickness = "Wall Thickness",
  179.     prompt_thickness_help = "Thickness of wall in blocks (1=thin, 2+=thick)",
  180.     prompt_height = "Height (Y)",
  181.     prompt_height_help = "Height of cylinder in blocks",
  182.     prompt_floors = "Intermediate Floors (0=none)",
  183.     prompt_floors_help = "Floor every X blocks (0=no floors)",
  184.     prompt_caps = "Close top/bottom caps?",
  185.     prompt_caps_help = "y=closed cylinder, n=open cylinder",
  186.     prompt_xscale = "X-Scaling (1.0=circular)",
  187.     prompt_xscale_help = "Stretch in X direction (0.5=flat, 2.0=long)",
  188.     prompt_yscale = "Y-Scaling (1.0=circular)",
  189.     prompt_yscale_help = "Stretch in Y direction (height)",
  190.     prompt_zscale = "Z-Scaling (1.0=circular)",
  191.     prompt_zscale_help = "Stretch in Z direction",
  192.     prompt_dome_type = "Dome Type (1=full, 2=upper, 3=lower)",
  193.     prompt_dome_type_help = "1=Full sphere, 2=Upper only, 3=Lower only",
  194.     prompt_cyl_orientation = "Cylinder Orientation (1=horizontal, 2=vertical)",
  195.     prompt_cyl_orientation_help = "1=Tunnel/Pipe, 2=Tower/Column",
  196.    
  197.     -- Advanced menu
  198.     adv_explain_overhead = "Flies over structure between runs (faster)",
  199.     adv_explain_dig = "Digs through blocks when moving (FAST only)",
  200.    
  201.     -- Build start
  202.     build_setup_title = "Build Setup",
  203.     build_setup_position = "Place turtle at SW corner (Southwest)",
  204.     build_setup_facing = "Turtle must face EAST (+X)",
  205.     build_setup_chests = "Have 2 chests ready (Material + Fuel)",
  206.     build_setup_material = "Material chest: under turtle (0,-1,0)",
  207.     build_setup_fuel = "Fuel chest: right-front-down (+1,-1,+1)",
  208.     build_setup_ready = "Press P to pause, Q to save+quit, S for status",
  209.    
  210.     -- Plan display
  211.     plan_dimensions = "Dimensions",
  212.     plan_volume = "Build volume: %dx%d footprint, %d blocks tall",
  213.     plan_size_xyz = "  X (Length): %d | Y (Height): %d | Z (Width): %d",
  214.     plan_bounds = "  From (0,0,0) to (%d,%d,%d)",
  215.     plan_blocks_total = "Planned blocks: %d",
  216.     plan_center = "Structure Center",
  217.     plan_center_coords = "  Center: (%d, %d, %d) relative to turtle",
  218.     plan_center_world = "  If turtle starts at (0,0,0) → Center is at (%d, %d, %d)",
  219.     plan_center_help = "Move turtle BEFORE building to shift center!",
  220.     plan_material_needed = "Required Material",
  221.     plan_blocks = "Blocks: %d",
  222.     plan_stacks = "Stacks (64): ~%d",
  223.     plan_shulker = "Shulker Boxes: ~%d",
  224.     plan_fuel_approx = "Fuel (approx): %d movements",
  225.     plan_coal = "Coal (approx): ~%d",
  226.     plan_time_warning = "WARNING: %d blocks - this will take a while!",
  227.     plan_time_estimate = "Estimated time: %.1f hours (at ~20 blocks/min)",
  228.    
  229.     fuel_low = "Fuel low (%d/%d) - trying from slot 1...",
  230.     fuel_refilled = "Fuel refilled! Level: %s",
  231.     material_fetched = "Material fetched! %d blocks available",
  232.    
  233.     build_complete = "Build complete!",
  234.     save_deleted = "Save deleted.",
  235.     mode_changed = "Mode -> %s",
  236.    
  237.     recovery_success = "RECOVERY SUCCESSFUL! Position: [%d,%d,%d]",
  238.     return_and_resume = "Back at build position: [%d,%d,%d] - Building continues!",
  239.    
  240.     -- Keys
  241.     key_p = "P = Pause/Continue",
  242.     key_q = "Q = Save & Quit",
  243.     key_s = "S = Show Status",
  244.   }
  245. }
  246.  
  247. local function t(key, ...)
  248.   local text = TRANSLATIONS[LANG] and TRANSLATIONS[LANG][key]
  249.   if not text then text = TRANSLATIONS["en"] and TRANSLATIONS["en"][key] end
  250.   if not text then return "[MISSING: "..key.."]" end
  251.   if select("#", ...) > 0 then return string.format(text, ...) end
  252.   return text
  253. end
  254.  
  255. local function loadLanguage()
  256.   if fs.exists(LANG_FILE) then
  257.     local f = fs.open(LANG_FILE, "r")
  258.     local lang = f.readAll()
  259.     f.close()
  260.     if TRANSLATIONS[lang] then LANG = lang end
  261.   end
  262. end
  263.  
  264. local function saveLanguage()
  265.   local f = fs.open(LANG_FILE, "w")
  266.   f.write(LANG)
  267.   f.close()
  268. end
  269.  
  270. loadLanguage()
  271.  
  272. ------------------------------------------------------------
  273. -- CONFIG ---------------------------------------------------
  274. local BUILD_MODE = "fast"
  275. local FAST_ALLOW_DIG = false
  276. local FAST_OVERHEAD_TRAVEL = true
  277. local OVERHEAD_MARGIN = 2
  278.  
  279. local SLOT_FUEL = 1
  280. local SLOT_MIN, SLOT_MAX = 2, 16
  281. local FUEL_BUFFER_MIN, FUEL_TARGET = 400, 2000
  282.  
  283. local STATE_FILE = "builder_state.txt"
  284. local SAVE_INTERVAL = 64
  285. local FUEL_CHECK_INTERVAL = 64
  286.  
  287. local STATE_VERSION = "2.1"
  288. local SUCK_AMOUNT = 64
  289. local FUEL_FETCH_RETRIES = 8
  290. local CHEST_WAIT_RETRIES = 20
  291. local MAX_STRUCTURE_SIZE = 200
  292. local MAX_MOVE_ATTEMPTS = 100
  293. local RECOVERY_MOVE_TIMEOUT = 500
  294. local STATUS_UPDATE_INTERVAL = 500  -- NEU: ms between status updates
  295.  
  296. -- NEU: Fuel Whitelist
  297. local FUEL_WHITELIST = {
  298.   ["minecraft:coal"]       = true,
  299.   ["minecraft:charcoal"]   = true,
  300.   ["minecraft:coal_block"] = true,
  301. }
  302.  
  303. ------------------------------------------------------------
  304. -- UI / HELPERS --------------------------------------------
  305. local hasColors = term and term.setTextColor and colors
  306. local function cprint(s,c) if hasColors then term.setTextColor(c) end print(s) if hasColors then term.setTextColor(colors.white) end end
  307. local function ok(s)   cprint("[OK] "..s, colors.lime) end
  308. local function warn(s) cprint("[WARN] "..s, colors.orange) end
  309. local function errc(s) cprint("[ERROR] "..s, colors.red) end
  310. local function info(s) cprint("[INFO] "..s, colors.lightBlue) end
  311. local function title(s) cprint("\n== "..s.." ==", colors.cyan) end
  312. local function statusLine(str) local w,h=term.getSize() term.setCursorPos(1,h) term.clearLine() term.write(str or "") end
  313. local function pressEnter(msg) write(msg or "Enter ... "); read() end
  314. local function round(n) return math.floor((tonumber(n) or 0) + 0.5) end
  315. local function fmtFuel(x) return (x=="unlimited") and "unlimited" or tostring(x) end
  316.  
  317. local function prompt(msg, def)
  318.   local hint = (def ~= nil) and (" ["..tostring(def).."]") or ""
  319.   write(msg..hint..": ")
  320.   local s = read()
  321.   if s=="" and def~=nil then return tostring(def) end
  322.   return s
  323. end
  324.  
  325. local function promptNumber(msg, def, min)
  326.   while true do
  327.     local s = prompt(msg, def)
  328.     local v = tonumber(s)
  329.     if v then v = round(v); if not min or v>=min then return v end end
  330.     print("Bitte gueltige Zahl eingeben.")
  331.   end
  332. end
  333.  
  334. local function promptBool(msg, def)
  335.   local defS = def and "y" or "n"
  336.   while true do
  337.     local s = prompt(msg.." (y/n)", defS):lower()
  338.     if s=="" then return def end
  339.     if s=="y" or s=="yes" or s=="1" or s=="ja" then return true end
  340.     if s=="n" or s=="no" or s=="0" or s=="nein" then return false end
  341.     print("Bitte y/n eingeben.")
  342.   end
  343. end
  344.  
  345. ------------------------------------------------------------
  346. -- ARGS / PARAMS -------------------------------------------
  347. local tArgs = {...}
  348. local function isShape(s) s=(s or ""):lower(); return (s=="torus" or s=="sphere" or s=="cylinder" or s=="dome") end
  349. local SHAPE, ACTION = "torus", ""
  350. local R, r, THICK = 20, 7, 1
  351. local XSCALE, YSCALE, ZSCALE = 1.0, 1.0, 1.0
  352. local CYL_H, CYL_FLOOR_STEP, CYL_CAPS = 12, 0, true
  353. local CYL_ORIENTATION = "horizontal"  -- NEU: "horizontal" (default) | "vertical"
  354. local DOME_HALF = "full"
  355.  
  356. local function toNum(s,d) local v=tonumber(s); return v and v or d end
  357. local function toBool(s,def)
  358.   if s==nil then return def end
  359.   local v=tostring(s):lower()
  360.   if v=="1" or v=="true" or v=="yes" or v=="ja" then return true end
  361.   if v=="0" or v=="false" or v=="no" or v=="nein" then return false end
  362.   return def
  363. end
  364.  
  365. if #tArgs>0 and isShape(tArgs[1]) then
  366.   SHAPE = tArgs[1]:lower()
  367.   local i=2
  368.   if SHAPE=="torus" then
  369.     R      = toNum(tArgs[i],20); i=i+1
  370.     r      = toNum(tArgs[i],7);  i=i+1
  371.     THICK  = toNum(tArgs[i],1);  i=i+1
  372.     YSCALE = toNum(tArgs[i],1.0);i=i+1
  373.   elseif SHAPE=="sphere" or SHAPE=="dome" then
  374.     r      = toNum(tArgs[i],8);  i=i+1
  375.     THICK  = toNum(tArgs[i],1);  i=i+1
  376.     XSCALE = toNum(tArgs[i],1.0);i=i+1
  377.     YSCALE = toNum(tArgs[i],1.0);i=i+1
  378.     ZSCALE = toNum(tArgs[i],1.0);i=i+1
  379.     if SHAPE=="dome" then
  380.       local half = (tArgs[i] or "upper"):lower()
  381.       DOME_HALF = (half=="upper" or half=="lower" or half=="full") and half or "upper"
  382.       i=i+1
  383.     end
  384.   elseif SHAPE=="cylinder" then
  385.     r      = toNum(tArgs[i],8);       i=i+1
  386.     CYL_H  = toNum(tArgs[i],12);      i=i+1
  387.     THICK  = toNum(tArgs[i],1);       i=i+1
  388.     CYL_FLOOR_STEP = toNum(tArgs[i],0) or 0; i=i+1
  389.     CYL_CAPS = toBool(tArgs[i], true); i=i+1
  390.     -- NEU: Orientation als Parameter
  391.     local orient = (tArgs[i] or "horizontal"):lower()
  392.     CYL_ORIENTATION = (orient=="vertical" or orient=="v") and "vertical" or "horizontal"
  393.     i=i+1
  394.   end
  395.   ACTION = (tArgs[i] or ""):lower()
  396. end
  397.  
  398. R, r, THICK, CYL_H, CYL_FLOOR_STEP = round(R), round(r), round(THICK), round(CYL_H), math.max(0, round(CYL_FLOOR_STEP))
  399.  
  400. if XSCALE <= 0 or YSCALE <= 0 or ZSCALE <= 0 then
  401.   errc("All scales must be > 0!"); return
  402. end
  403.  
  404. if THICK > 2*r then
  405.   warn(string.format("THICK=%d too large for r=%d! Setting to max: %d", THICK, r, 2*r))
  406.   THICK = 2*r
  407. end
  408.  
  409. if (SHAPE=="torus" and (R<=0 or r<=0 or THICK<=0))
  410. or ((SHAPE=="sphere" or SHAPE=="dome") and (r<=0 or THICK<=0))
  411. or (SHAPE=="cylinder" and (r<=0 or CYL_H<=0 or THICK<=0)) then
  412.   errc("Invalid parameters."); return
  413. end
  414.  
  415. ------------------------------------------------------------
  416. -- DERIVED GEOMETRY ----------------------------------------
  417. local XZ, YH, CX, CY, CZ
  418. local function recomputeDerived()
  419.   if SHAPE=="torus" then
  420.     XZ = 2*(R + r) + 1
  421.     YH = math.floor(2*r*YSCALE + 1)
  422.   elseif SHAPE=="sphere" then
  423.     local maxXZ = math.max(2*r*XSCALE, 2*r*ZSCALE)
  424.     XZ = math.floor(maxXZ + 1)
  425.     YH = math.floor(2*r*YSCALE + 1)
  426.   elseif SHAPE=="dome" then
  427.     local maxXZ = math.max(2*r*XSCALE, 2*r*ZSCALE)
  428.     XZ = math.floor(maxXZ + 1)
  429.     if DOME_HALF == "full" then
  430.       YH = math.floor(2*r*YSCALE + 1)
  431.     else
  432.       YH = math.floor(r*YSCALE + 1)
  433.     end
  434.   else  -- cylinder
  435.     if CYL_ORIENTATION == "vertical" then
  436.       -- Vertikaler Zylinder: Höhe in Y, Radius in XZ
  437.       XZ = 2*r + 1
  438.       YH = CYL_H
  439.     else
  440.       -- Horizontaler Zylinder: Höhe in X, Radius in YZ
  441.       XZ = CYL_H
  442.       YH = 2*r + 1
  443.     end
  444.   end
  445.   CX, CY, CZ = (XZ-1)/2, (YH-1)/2, (XZ-1)/2
  446. end
  447. recomputeDerived()
  448.  
  449. if XZ > MAX_STRUCTURE_SIZE or YH > MAX_STRUCTURE_SIZE then
  450.   errc(string.format("Structure too large! XZ=%d, YH=%d (Max: %d)", XZ, YH, MAX_STRUCTURE_SIZE))
  451.   return
  452. end
  453.  
  454. ------------------------------------------------------------
  455. -- STATE / POS ---------------------------------------------
  456. local pos={x=0,y=0,z=0}
  457. local heading=0
  458. local stepCounter, blocksPlaced = 0, 0
  459. local paused, shouldQuit = false, false
  460. local curLayer, curZ, curX = 0,0,0
  461. local plannedConfig = nil
  462. local lastSavedPos = {x=0,y=0,z=0}
  463. local lastSavedHeading = 0
  464. local recoveryMode = false
  465.  
  466. ------------------------------------------------------------
  467. -- SAVE/LOAD + FAILSAFE ------------------------------------
  468. local function saveState(st) local f=fs.open(STATE_FILE,"w"); f.write(textutils.serialize(st)); f.close() end
  469. local function loadState()
  470.   if not fs.exists(STATE_FILE) then return nil end
  471.   local f=fs.open(STATE_FILE,"r"); local txt=f.readAll(); f.close()
  472.   local ok2,data=pcall(textutils.unserialize,txt); if ok2 then return data end; return nil
  473. end
  474. local function clearState() if fs.exists(STATE_FILE) then fs.delete(STATE_FILE) end end
  475.  
  476. local function snapshotState()
  477.   return {
  478.     version=STATE_VERSION,
  479.     shape=SHAPE, R=R, r=r, THICK=THICK,
  480.     XSCALE=XSCALE, YSCALE=YSCALE, ZSCALE=ZSCALE,
  481.     CYL_H=CYL_H, CYL_FLOOR_STEP=CYL_FLOOR_STEP, CYL_CAPS=CYL_CAPS,
  482.     CYL_ORIENTATION=CYL_ORIENTATION,
  483.     DOME_HALF=DOME_HALF,
  484.     layer=curLayer, curZ=curZ, curX=curX,
  485.     pos={x=pos.x, y=pos.y, z=pos.z},
  486.     heading=heading,
  487.     BUILD_MODE=BUILD_MODE,
  488.     blocksPlaced=blocksPlaced
  489.   }
  490. end
  491.  
  492. local function sameConfig(a,b)
  493.   return a and b and a.shape==b.shape and a.R==b.R and a.r==b.r and a.THICK==b.THICK
  494.      and a.XSCALE==b.XSCALE and a.YSCALE==b.YSCALE and a.ZSCALE==b.ZSCALE
  495.      and a.CYL_H==b.CYL_H and a.CYL_FLOOR_STEP==b.CYL_FLOOR_STEP and a.CYL_CAPS==b.CYL_CAPS
  496.      and a.CYL_ORIENTATION==b.CYL_ORIENTATION
  497.      and a.DOME_HALF==b.DOME_HALF and a.BUILD_MODE==b.BUILD_MODE
  498. end
  499.  
  500. ------------------------------------------------------------
  501. -- MOVE -----------------------------------------------------
  502. local function turnLeft() turtle.turnLeft(); heading=(heading+3)%4 end
  503. local function turnRight() turtle.turnRight(); heading=(heading+1)%4 end
  504. local function face(dir) local d=(dir-heading)%4; if d==1 then turnRight() elseif d==2 then turnRight();turnRight() elseif d==3 then turnLeft() end end
  505. local function fwd()
  506.   if turtle.forward() then
  507.     if heading==0 then pos.x=pos.x+1 elseif heading==1 then pos.z=pos.z+1 elseif heading==2 then pos.x=pos.x-1 else pos.z=pos.z-1 end
  508.     stepCounter=stepCounter+1; return true
  509.   end
  510.   return false
  511. end
  512. local function up() if turtle.up() then pos.y=pos.y+1; stepCounter=stepCounter+1; return true end return false end
  513. local function down() if turtle.down() then pos.y=pos.y-1; stepCounter=stepCounter+1; return true end return false end
  514.  
  515. ------------------------------------------------------------
  516. -- SMART ERROR HANDLER -------------------------------------
  517. local function returnToSavedPosition()
  518.   recoveryMode = true
  519.   warn("RECOVERY: Returning to last saved position...")
  520.   info(string.format("Target: [%d,%d,%d] Heading:%d", lastSavedPos.x, lastSavedPos.y, lastSavedPos.z, lastSavedHeading))
  521.  
  522.   local moveCount = 0
  523.   local function safeMove(moveFunc, axis)
  524.     moveCount = moveCount + 1
  525.     if moveCount > RECOVERY_MOVE_TIMEOUT then
  526.       errc("RECOVERY FAILED: Too many move attempts!")
  527.       return false
  528.     end
  529.    
  530.     for attempt=1,3 do
  531.       if moveFunc() then return true end
  532.       if axis == "up" and turtle.detectUp() then turtle.digUp()
  533.       elseif axis == "down" and turtle.detectDown() then turtle.digDown()
  534.       elseif turtle.detect() then turtle.dig() end
  535.       sleep(0.2)
  536.     end
  537.     return false
  538.   end
  539.  
  540.   while pos.y < lastSavedPos.y do
  541.     if not safeMove(up, "up") then return false end
  542.   end
  543.  
  544.   while pos.x ~= lastSavedPos.x or pos.z ~= lastSavedPos.z do
  545.     if pos.x < lastSavedPos.x then
  546.       face(0)
  547.       if not safeMove(fwd, "forward") then return false end
  548.     elseif pos.x > lastSavedPos.x then
  549.       face(2)
  550.       if not safeMove(fwd, "forward") then return false end
  551.     elseif pos.z < lastSavedPos.z then
  552.       face(1)
  553.       if not safeMove(fwd, "forward") then return false end
  554.     elseif pos.z > lastSavedPos.z then
  555.       face(3)
  556.       if not safeMove(fwd, "forward") then return false end
  557.     end
  558.   end
  559.  
  560.   while pos.y > lastSavedPos.y do
  561.     if not safeMove(down, "down") then return false end
  562.   end
  563.   while pos.y < lastSavedPos.y do
  564.     if not safeMove(up, "up") then return false end
  565.   end
  566.  
  567.   face(lastSavedHeading)
  568.  
  569.   ok(t("recovery_success", pos.x, pos.y, pos.z))
  570.   recoveryMode = false
  571.   return true
  572. end
  573.  
  574. local function smartFailError(msg)
  575.   errc("ERROR: "..msg)
  576.  
  577.   local errorState = snapshotState()
  578.   saveState(errorState)
  579.   warn("State saved: "..STATE_FILE)
  580.  
  581.   if not recoveryMode then
  582.     if returnToSavedPosition() then
  583.       title("ERROR FIXED - READY FOR RESUME")
  584.       cprint("\nTurtle returned to last known position.", colors.yellow)
  585.       cprint("Problem: "..msg, colors.orange)
  586.       cprint("\nPlease fix the problem:", colors.lime)
  587.       cprint("  - Out of fuel: Fill Fuel Chest", colors.white)
  588.       cprint("  - Out of material: Fill Material Chest", colors.white)
  589.       cprint("  - Blocked: Remove obstacles", colors.white)
  590.       cprint("\nThen resume with:", colors.cyan)
  591.       cprint("  builder_pro_failsafe resume", colors.lightBlue)
  592.       cprint("\nOr press '3' in menu for Resume", colors.lightBlue)
  593.     else
  594.       errc("RECOVERY FAILED!")
  595.       errc(string.format("Turtle Position: [%d,%d,%d]", pos.x, pos.y, pos.z))
  596.       errc(string.format("Saved Position: [%d,%d,%d]", lastSavedPos.x, lastSavedPos.y, lastSavedPos.z))
  597.       warn("Please move turtle MANUALLY to saved position!")
  598.     end
  599.   else
  600.     errc("Recovery itself failed - turtle is stuck!")
  601.     errc(string.format("Position: [%d,%d,%d]", pos.x, pos.y, pos.z))
  602.   end
  603.  
  604.   shouldQuit = true
  605.   return false
  606. end
  607.  
  608. local function moveTo(tx, tz, ty, allowDig)
  609.   local blocked = 0
  610.  
  611.   local function tryUp()
  612.     if up() then blocked = 0; return true end
  613.     if turtle.detectUp() then turtle.digUp() end
  614.     blocked = blocked + 1
  615.     if blocked > MAX_MOVE_ATTEMPTS then return false end
  616.     sleep(0.05)
  617.     return false
  618.   end
  619.  
  620.   local function tryDown()
  621.     if down() then blocked = 0; return true end
  622.     if turtle.detectDown() then turtle.digDown() end
  623.     blocked = blocked + 1
  624.     if blocked > MAX_MOVE_ATTEMPTS then return false end
  625.     sleep(0.05)
  626.     return false
  627.   end
  628.  
  629.   while pos.y < ty do
  630.     if not tryUp() then
  631.       return smartFailError(string.format("Blocked upward at Y=%d (Target: %d)", pos.y, ty))
  632.     end
  633.   end
  634.  
  635.   while pos.y > ty do
  636.     if not tryDown() then
  637.       return smartFailError(string.format("Blocked downward at Y=%d (Target: %d)", pos.y, ty))
  638.     end
  639.   end
  640.  
  641.   local function stepDir(dir)
  642.     face(dir)
  643.     if fwd() then blocked = 0; return true end
  644.     if allowDig and turtle.detect() then turtle.dig() end
  645.     if fwd() then blocked = 0; return true end
  646.     blocked = blocked + 1
  647.     if blocked > MAX_MOVE_ATTEMPTS then return false end
  648.     sleep(0.05)
  649.     return false
  650.   end
  651.  
  652.   while pos.x ~= tx or pos.z ~= tz do
  653.     if pos.x < tx then
  654.       if not stepDir(0) then
  655.         return smartFailError(string.format("Move +X blocked @ [%d,%d,%d]", pos.x, pos.y, pos.z))
  656.       end
  657.     elseif pos.x > tx then
  658.       if not stepDir(2) then
  659.         return smartFailError(string.format("Move -X blocked @ [%d,%d,%d]", pos.x, pos.y, pos.z))
  660.       end
  661.     elseif pos.z < tz then
  662.       if not stepDir(1) then
  663.         return smartFailError(string.format("Move +Z blocked @ [%d,%d,%d]", pos.x, pos.y, pos.z))
  664.       end
  665.     elseif pos.z > tz then
  666.       if not stepDir(3) then
  667.         return smartFailError(string.format("Move -Z blocked @ [%d,%d,%d]", pos.x, pos.y, pos.z))
  668.       end
  669.     end
  670.   end
  671.  
  672.   return true
  673. end
  674.  
  675. ------------------------------------------------------------
  676. -- INSPECT / CHESTS / FUEL / INVENTORY ---------------------
  677. local function safeInspect(fn) local ok,d=fn(); if ok then return d.name or "block" end return nil end
  678. local function isInventoryName(n) if not n then return false end n=n:lower(); return n:find("chest",1,true) or n:find("barrel",1,true) or n:find("shulker",1,true) end
  679.  
  680. local function pauseGate()
  681.   while paused and not shouldQuit do statusLine("PAUSE (P=continue, Q=save+quit, S=status)"); sleep(0.1) end
  682.   return not shouldQuit
  683. end
  684.  
  685. local function isFuelLike(d)
  686.   if not d or not d.name then return false end
  687.   return FUEL_WHITELIST[d.name:lower()] == true
  688. end
  689.  
  690. local function findChestSlot()
  691.   for s=1,16 do local d=turtle.getItemDetail(s); if d and d.name and isInventoryName(d.name) then return s end end
  692.   return nil
  693. end
  694.  
  695. local function goToFuelChestLocation()
  696.   if not moveTo(0,0,0,false) then return false end
  697.   face(1); if not fwd() then return false end
  698.   face(0); if not fwd() then return false end
  699.   local dn=safeInspect(turtle.inspectDown); if dn and not isInventoryName(dn) then turtle.digDown() end
  700.   if not down() then return false end
  701.   face(1)
  702.   local fr=safeInspect(turtle.inspect); if fr and not isInventoryName(fr) then turtle.dig() end
  703.   return true
  704. end
  705.  
  706. -- NEU: Smart Overhead Routes
  707. local function goToBaseSmart()
  708.   if FAST_OVERHEAD_TRAVEL then
  709.     local travelY = YH + OVERHEAD_MARGIN
  710.     return moveTo(pos.x, pos.z, travelY, false)
  711.        and moveTo(0, 0, travelY, false)
  712.        and moveTo(0, 0, 0, false)
  713.   else
  714.     return moveTo(0, 0, 0, false)
  715.   end
  716. end
  717.  
  718. local function returnToWorkSmart(bx, bz, by, h)
  719.   if FAST_OVERHEAD_TRAVEL then
  720.     local travelY = YH + OVERHEAD_MARGIN
  721.     local ok = moveTo(pos.x, pos.z, travelY, false)
  722.            and moveTo(bx, bz, travelY, false)
  723.            and moveTo(bx, bz, by, false)
  724.     if ok then face(h) end
  725.     return ok
  726.   else
  727.     local ok = moveTo(bx, bz, by, false)
  728.     if ok then face(h) end
  729.     return ok
  730.   end
  731. end
  732.  
  733. local function placeChestsIfNeeded()
  734.   title("Auto-Chest Setup")
  735.  
  736.   -- Material Chest unter Turtle
  737.   local below=safeInspect(turtle.inspectDown)
  738.   if isInventoryName(below) then
  739.     ok("Material chest detected under turtle.")
  740.   else
  741.     local cs=findChestSlot()
  742.     if not cs then return smartFailError("No Chest/Barrel in inventory for material.") end
  743.     turtle.select(cs)
  744.     local n=safeInspect(turtle.inspectDown)
  745.     if n and not isInventoryName(n) then turtle.digDown() end
  746.     if not turtle.placeDown() then return smartFailError("Material chest could not be placed.") end
  747.     ok("Material chest placed.")
  748.   end
  749.  
  750.   -- Fuel Chest Position: (+1,-1,+1) relativ zu Start
  751.   -- NEU: Prüfe erst ob wir schon da sind
  752.   local needFuelChestCheck = true
  753.  
  754.   -- Wenn wir bei (0,0,0) sind, schaue ob Fuel-Chest schon existiert ohne hinzufahren
  755.   if pos.x == 0 and pos.y == 0 and pos.z == 0 then
  756.     -- Kleine Prüfung: Schaue nach vorne-rechts ob Chest erkennbar ist
  757.     -- (ohne komplett hinzufahren)
  758.     needFuelChestCheck = true  -- Wir müssen trotzdem hin um sicher zu sein
  759.   end
  760.  
  761.   local bx,bz,by,h = pos.x,pos.z,pos.y,heading
  762.  
  763.   if not goToFuelChestLocation() then
  764.     return smartFailError("Fuel spot not reachable.")
  765.   end
  766.  
  767.   local front=safeInspect(turtle.inspect)
  768.   local haveFuel=isInventoryName(front)
  769.  
  770.   if haveFuel then
  771.     ok("Fuel chest present.")
  772.   else
  773.     local cs2=findChestSlot()
  774.     if not cs2 then return smartFailError("No chest available for fuel.") end
  775.     turtle.select(cs2)
  776.     if not turtle.place() then return smartFailError("Fuel chest could not be placed.") end
  777.     ok("Fuel chest placed.")
  778.   end
  779.  
  780.   -- Zurück zur Startposition - aber OPTIMIERT
  781.   face(3)
  782.   if not up() then return smartFailError("Return from fuel spot: up failed") end
  783.   face(2)
  784.   if not fwd() then return smartFailError("Return from fuel spot: -X failed") end
  785.   face(3)
  786.   if not fwd() then return smartFailError("Return from fuel spot: -Z failed") end
  787.  
  788.   -- NEU: Wenn wir zur Basis zurück sollen (0,0,0), gehe direkt ohne Overhead
  789.   if bx == 0 and bz == 0 and by == 0 then
  790.     -- Bereits nah an Basis - kein Overhead nötig
  791.     if pos.x ~= 0 or pos.z ~= 0 or pos.y ~= 0 then
  792.       if not moveTo(0, 0, 0, false) then
  793.         return smartFailError("Return to start position failed.")
  794.       end
  795.     end
  796.     face(h)
  797.   else
  798.     -- Zur ursprünglichen Position zurück
  799.     if not returnToWorkSmart(bx, bz, by, h) then
  800.       return smartFailError("Return to start position failed.")
  801.     end
  802.   end
  803.  
  804.   info("Chests ready.")
  805.   return true
  806. end
  807.  
  808. -- NEU: Robust Fuel Fetch with Whitelist
  809. local function fetchFuelFromRightChest(target)
  810.   local bx,bz,by,h = pos.x,pos.z,pos.y,heading
  811.   info("Going to base for fuel refill...")
  812.  
  813.   if not goToBaseSmart() then
  814.     return smartFailError("Base not reachable.")
  815.   end
  816.  
  817.   face(0)
  818.   if not goToFuelChestLocation() then
  819.     return smartFailError("Fuel spot not reachable.")
  820.   end
  821.  
  822.   turtle.select(SLOT_FUEL)
  823.   local tries = 0
  824.  
  825.   while true do
  826.     tries = tries + 1
  827.  
  828.     local d = turtle.getItemDetail(SLOT_FUEL)
  829.     if d and (not isFuelLike(d)) then
  830.       warn("Slot 1 contains non-fuel: "..(d.name or "unknown").." -> back to chest")
  831.       turtle.drop()
  832.     end
  833.  
  834.     if turtle.getItemCount(SLOT_FUEL) == 0 then
  835.       turtle.suck(SUCK_AMOUNT)
  836.     end
  837.  
  838.     d = turtle.getItemDetail(SLOT_FUEL)
  839.     if d and isFuelLike(d) then
  840.       ok("Fetched: "..(d.name or "unknown"))
  841.       break
  842.     end
  843.  
  844.     if tries % 5 == 1 then
  845.       warn("============================================")
  846.       warn("FUEL CHEST CONTAINS NO VALID FUEL!")
  847.       warn("Please add Coal/Charcoal/Coal Block")
  848.       warn("Position: (+1,-1,+1) relative to base")
  849.       warn("Waiting... (Attempt "..tries..")")
  850.       warn("============================================")
  851.     end
  852.    
  853.     sleep(2)
  854.    
  855.     if shouldQuit then
  856.       face(3); up(); face(2); fwd(); face(3); fwd()
  857.       returnToWorkSmart(bx, bz, by, h)
  858.       return smartFailError("User aborted fuel refill (Q)")
  859.     end
  860.    
  861.     if tries > 50 then
  862.       return smartFailError("Fuel refill: No valid fuel items found (50 attempts).")
  863.     end
  864.   end
  865.  
  866.   info("Refueling from slot 1...")
  867.   while turtle.getItemCount(SLOT_FUEL) > 0
  868.     and turtle.getFuelLevel() ~= "unlimited"
  869.     and turtle.getFuelLevel() < target do
  870.     if not turtle.refuel(1) then break end
  871.   end
  872.   turtle.select(SLOT_MIN)
  873.  
  874.   face(3)
  875.   if not up() then
  876.     return smartFailError("Return from fuel spot: up failed")
  877.   end
  878.   face(2)
  879.   if not fwd() then
  880.     return smartFailError("Return from fuel spot: -X failed")
  881.   end
  882.   face(3)
  883.   if not fwd() then
  884.     return smartFailError("Return from fuel spot: -Z failed")
  885.   end
  886.  
  887.   if not returnToWorkSmart(bx, bz, by, h) then
  888.     return smartFailError("Return to build level failed.")
  889.   end
  890.  
  891.   local level=turtle.getFuelLevel()
  892.   ok(string.format("Fuel level after refill: %s", fmtFuel(level)))
  893.  
  894.   if level == "unlimited" then
  895.     return true
  896.   end
  897.  
  898.   if type(level) == "number" and level >= target then
  899.     return true
  900.   end
  901.  
  902.   warn(string.format("WARNING: Fuel level (%s) below target (%d)!", fmtFuel(level), target))
  903.   return false
  904. end
  905.  
  906. local function ensureFuelChestPresentOrWait()
  907.   while true do
  908.     local bx,bz,by,h=pos.x,pos.z,pos.y,heading
  909.     if not goToFuelChestLocation() then
  910.       warn("Fuel spot currently unreachable. Ensure +X/+Z is free.")
  911.     else
  912.       local present = isInventoryName(safeInspect(turtle.inspect))
  913.       face(3); up(); face(2); fwd(); face(3); fwd()
  914.       returnToWorkSmart(bx, bz, by, h)
  915.       if present then return true end
  916.       local cs=findChestSlot()
  917.       if cs then return placeChestsIfNeeded() end
  918.     end
  919.     statusLine("Waiting for fuel chest (or give turtle a chest)... Q=Quit")
  920.     sleep(2)
  921.     if shouldQuit then return false end
  922.   end
  923. end
  924.  
  925. -- NEU: Stricter Fuel Function with Slot 1 + Fetch
  926. local function refuelStrictSlot1OrFetch(target)
  927.   turtle.select(SLOT_FUEL)
  928.   local d = turtle.getItemDetail(SLOT_FUEL)
  929.   if d and isFuelLike(d) then
  930.     while turtle.getItemCount(SLOT_FUEL) > 0
  931.       and turtle.getFuelLevel() ~= "unlimited"
  932.       and turtle.getFuelLevel() < target do
  933.       if not turtle.refuel(1) then break end
  934.     end
  935.   end
  936.  
  937.   local slot1Count = turtle.getItemCount(SLOT_FUEL)
  938.   turtle.select(SLOT_MIN)
  939.  
  940.   local fl = turtle.getFuelLevel()
  941.   local needsFetch = (slot1Count == 0)
  942.                   or (fl ~= "unlimited" and type(fl)=="number" and fl < FUEL_BUFFER_MIN)
  943.  
  944.   if needsFetch then
  945.     lastSavedPos = { x = pos.x, y = pos.y, z = pos.z }
  946.     lastSavedHeading = heading
  947.    
  948.     local currentState = snapshotState()
  949.     saveState(currentState)
  950.     info("State saved before fuel refill")
  951.  
  952.     if not ensureFuelChestPresentOrWait() then
  953.       return smartFailError("No fuel chest available.")
  954.     end
  955.    
  956.     local success = fetchFuelFromRightChest(target)
  957.    
  958.     if not success then
  959.       return smartFailError("Fuel refill from chest failed.")
  960.     end
  961.    
  962.     if shouldQuit then
  963.       warn("shouldQuit was set - resetting after successful refill")
  964.       shouldQuit = false
  965.     end
  966.    
  967.     ok(t("return_and_resume", pos.x, pos.y, pos.z))
  968.   end
  969.  
  970.   return true
  971. end
  972.  
  973. -- NEU: Improved ensureFuelBuffered
  974. local function ensureFuelBuffered()
  975.   local fl = turtle.getFuelLevel()
  976.   if fl == "unlimited" then return true end
  977.   if type(fl)=="number" and fl >= FUEL_BUFFER_MIN then return true end
  978.  
  979.   if type(fl)=="number" and fl < 1 then
  980.     turtle.select(SLOT_FUEL)
  981.     local d = turtle.getItemDetail(SLOT_FUEL)
  982.     if d and isFuelLike(d) then
  983.       while turtle.getItemCount(SLOT_FUEL) > 0 and turtle.getFuelLevel() < FUEL_TARGET do
  984.         if not turtle.refuel(1) then break end
  985.       end
  986.       turtle.select(SLOT_MIN)
  987.       fl = turtle.getFuelLevel()
  988.       if fl ~= "unlimited" and type(fl)=="number" and fl >= FUEL_BUFFER_MIN then
  989.         return true
  990.       end
  991.     end
  992.     turtle.select(SLOT_MIN)
  993.     return smartFailError("Fuel = 0 and no valid fuel in slot 1. Add Coal/Charcoal to slot 1 and run 'resume'.")
  994.   end
  995.  
  996.   return refuelStrictSlot1OrFetch(FUEL_TARGET)
  997. end
  998.  
  999. local function compactBuildSlots()
  1000.   local mats={}
  1001.   for s=SLOT_MIN,SLOT_MAX do local d=turtle.getItemDetail(s); if d and d.name then mats[d.name]=mats[d.name] or {}; table.insert(mats[d.name],s) end end
  1002.   for _,slots in pairs(mats) do
  1003.     for i=1,#slots do
  1004.       local dst=slots[i]; local di=turtle.getItemDetail(dst)
  1005.       if di and di.count<64 then
  1006.         for j=i+1,#slots do local src=slots[j]
  1007.           if turtle.getItemCount(src)>0 then turtle.select(src); turtle.transferTo(dst); if turtle.getItemCount(src)==0 then break end end
  1008.         end
  1009.       end
  1010.     end
  1011.   end
  1012.   turtle.select(SLOT_MIN)
  1013. end
  1014.  
  1015. local function countPlaceables() local n=0 for s=SLOT_MIN,SLOT_MAX do n=n+turtle.getItemCount(s) end return n end
  1016.  
  1017. -- NEU: Material Refill without "under me" check - only base
  1018. local function ensureSomeBlocks()
  1019.   compactBuildSlots()
  1020.   if countPlaceables()>0 then return true end
  1021.  
  1022.   info("No material found - flying to base for refill...")
  1023.   local bx,bz,by,h = pos.x,pos.z,pos.y,heading
  1024.  
  1025.   lastSavedPos = { x = pos.x, y = pos.y, z = pos.z }
  1026.   lastSavedHeading = heading
  1027.   local currentState = snapshotState()
  1028.   saveState(currentState)
  1029.   info("State saved before material refill")
  1030.  
  1031.   if not goToBaseSmart() then
  1032.     return smartFailError("Return to base for refill failed")
  1033.   end
  1034.  
  1035.   local tries=0
  1036.   while not isInventoryName(safeInspect(turtle.inspectDown)) do
  1037.     local n=safeInspect(turtle.inspectDown)
  1038.     if n and n~="minecraft:air" then
  1039.       info("Removing blockage: "..n)
  1040.       turtle.digDown()
  1041.     else
  1042.       if tries > 0 and tries % 5 == 0 then
  1043.         warn("Waiting for material chest under turtle... ("..tries.."/"..CHEST_WAIT_RETRIES..")")
  1044.       end
  1045.       sleep(0.5)
  1046.     end
  1047.    
  1048.     tries=tries+1
  1049.     if tries > CHEST_WAIT_RETRIES then
  1050.       errc("Material chest missing after "..tries.." seconds!")
  1051.       errc("Place a Chest/Barrel DIRECTLY UNDER the turtle (0,-1,0)!")
  1052.       return smartFailError("Material chest under base missing")
  1053.     end
  1054.   end
  1055.   ok("Material chest found!")
  1056.  
  1057.   local refillAttempts = 0
  1058.   while countPlaceables() == 0 do
  1059.     for s=SLOT_MIN,SLOT_MAX do
  1060.       turtle.select(s)
  1061.       local need=64-turtle.getItemCount(s)
  1062.       if need>0 then turtle.suckDown(need) end
  1063.     end
  1064.     compactBuildSlots()
  1065.    
  1066.     if countPlaceables() > 0 then
  1067.       break
  1068.     end
  1069.    
  1070.     refillAttempts = refillAttempts + 1
  1071.     if refillAttempts % 3 == 1 then
  1072.       warn("============================================")
  1073.       warn("MATERIAL CHEST IS EMPTY!")
  1074.       warn("Please add building blocks to material chest:")
  1075.       warn("Position: (0,-1,0) - DIRECTLY UNDER turtle")
  1076.       warn("Waiting... (Attempt "..refillAttempts..") - Q to abort")
  1077.       warn("============================================")
  1078.     end
  1079.    
  1080.     sleep(2)
  1081.    
  1082.     if shouldQuit then
  1083.       returnToWorkSmart(bx, bz, by, h)
  1084.       return smartFailError("User aborted material refill (Q)")
  1085.     end
  1086.   end
  1087.  
  1088.   ok(t("material_fetched", countPlaceables()))
  1089.  
  1090.   if not returnToWorkSmart(bx, bz, by, h) then
  1091.     return smartFailError("Return to build level after refill failed")
  1092.   end
  1093.  
  1094.   if shouldQuit then
  1095.     warn("shouldQuit was set - resetting after successful material refill")
  1096.     shouldQuit = false
  1097.   end
  1098.  
  1099.   turtle.select(SLOT_MIN)
  1100.   ok(t("return_and_resume", pos.x, pos.y, pos.z))
  1101.   return true
  1102. end
  1103.  
  1104. ------------------------------------------------------------
  1105. -- RANDOM SLOT SELECTION & PLACE WITH RETRY ----------------
  1106. local function selectPlaceSlot()
  1107.   local available={}
  1108.   for s=SLOT_MIN,SLOT_MAX do if turtle.getItemCount(s)>0 then table.insert(available,s) end end
  1109.   if #available==0 then return false end
  1110.   local pick=available[math.random(1,#available)]
  1111.   turtle.select(pick)
  1112.   return true
  1113. end
  1114.  
  1115. local function placeDownOne()
  1116.   if turtle.detectDown() then return true end
  1117.   if not selectPlaceSlot() then return smartFailError("No build blocks available!") end
  1118.   if turtle.placeDown() then blocksPlaced=blocksPlaced+1; return true end
  1119.   if turtle.detectDown() then turtle.digDown(); sleep(0.05); if turtle.placeDown() then blocksPlaced=blocksPlaced+1; return true end end
  1120.   return smartFailError("Could not place block!")
  1121. end
  1122.  
  1123. ------------------------------------------------------------
  1124. -- SHAPE MASKS ---------------------------------------------
  1125. local function inCircle(x,z,rad) return (x*x + z*z) <= (rad*rad + 1e-6) end
  1126. local function ringShell(x,z,rad,th) local outer=rad+0.5; local inner=math.max(0, rad-(th-0.5)); local d=math.sqrt(x*x+z*z); return (d<=outer) and (d>=inner) end
  1127.  
  1128. local function onSphereShell(ix,iy,iz)
  1129.   local x = (ix - CX) / (XSCALE ~= 0 and XSCALE or 1)
  1130.   local y = (iy - CY) / (YSCALE ~= 0 and YSCALE or 1)
  1131.   local z = (iz - CZ) / (ZSCALE ~= 0 and ZSCALE or 1)
  1132.  
  1133.   local rr = math.sqrt(x*x + y*y + z*z)
  1134.   local bandOuter = r + 0.5
  1135.   local bandInner = math.max(0, r - (THICK - 0.5))
  1136.  
  1137.   return (rr <= bandOuter) and (rr >= bandInner)
  1138. end
  1139.  
  1140. local function onDomeShell(ix, iy, iz)
  1141.   local x = (ix - CX) / (XSCALE ~= 0 and XSCALE or 1)
  1142.   local y_real = iy
  1143.   local y_center = CY
  1144.  
  1145.   if DOME_HALF == "upper" then
  1146.     y_center = 0
  1147.     local y_scaled = (y_real - y_center) / (YSCALE ~= 0 and YSCALE or 1)
  1148.    
  1149.     if y_real < 0 then return false end
  1150.    
  1151.     local z = (iz - CZ) / (ZSCALE ~= 0 and ZSCALE or 1)
  1152.     local rr = math.sqrt(x*x + y_scaled*y_scaled + z*z)
  1153.     local bandOuter = r + 0.5
  1154.     local bandInner = math.max(0, r - (THICK - 0.5))
  1155.    
  1156.     return (rr <= bandOuter) and (rr >= bandInner)
  1157.    
  1158.   elseif DOME_HALF == "lower" then
  1159.     y_center = YH - 1
  1160.     local y_scaled = (y_real - y_center) / (YSCALE ~= 0 and YSCALE or 1)
  1161.    
  1162.     if y_real > y_center then return false end
  1163.    
  1164.     local z = (iz - CZ) / (ZSCALE ~= 0 and ZSCALE or 1)
  1165.     local rr = math.sqrt(x*x + y_scaled*y_scaled + z*z)
  1166.     local bandOuter = r + 0.5
  1167.     local bandInner = math.max(0, r - (THICK - 0.5))
  1168.    
  1169.     return (rr <= bandOuter) and (rr >= bandInner)
  1170.   else
  1171.     return onSphereShell(ix, iy, iz)
  1172.   end
  1173. end
  1174.  
  1175. local function onTorusShell(ix,iy,iz)
  1176.   local x=ix-CX; local y=(iy-CY)/YSCALE; local z=iz-CZ
  1177.   local rho=math.sqrt(x*x+z*z); local tube=math.sqrt((rho-R)*(rho-R)+y*y)
  1178.   local bandOuter=r+0.5; local bandInner=math.max(0, r-(THICK-0.5))
  1179.   return (tube<=bandOuter) and (tube>=bandInner)
  1180. end
  1181.  
  1182. local function onCylinderShell(ix,iy,iz)
  1183.   if CYL_ORIENTATION == "vertical" then
  1184.     -- Vertikaler Zylinder: Ring in XZ, entlang Y
  1185.     local x=ix-CX; local z=iz-CZ
  1186.     if ringShell(x,z,r,THICK) then return true end
  1187.     if CYL_CAPS and (iy==0 or iy==YH-1) then return inCircle(x,z,r+0.5) end
  1188.     if CYL_FLOOR_STEP>0 and iy>0 and iy<YH-1 and (iy % CYL_FLOOR_STEP == 0) then return inCircle(x,z,r+0.5) end
  1189.   else
  1190.     -- Horizontaler Zylinder: Ring in YZ, entlang X
  1191.     local y=iy-CY; local z=iz-CZ
  1192.     if ringShell(y,z,r,THICK) then return true end
  1193.     if CYL_CAPS and (ix==0 or ix==XZ-1) then return inCircle(y,z,r+0.5) end
  1194.     if CYL_FLOOR_STEP>0 and ix>0 and ix<XZ-1 and (ix % CYL_FLOOR_STEP == 0) then return inCircle(y,z,r+0.5) end
  1195.   end
  1196.   return false
  1197. end
  1198.  
  1199. local function onShell(ix,iy,iz)
  1200.   if SHAPE=="sphere" then return onSphereShell(ix,iy,iz)
  1201.   elseif SHAPE=="dome" then return onDomeShell(ix,iy,iz)
  1202.   elseif SHAPE=="torus" then return onTorusShell(ix,iy,iz)
  1203.   else return onCylinderShell(ix,iy,iz) end
  1204. end
  1205.  
  1206. local function totalBlocksPlanned() local n=0 for y=0,YH-1 do for z=0,XZ-1 do for x=0,XZ-1 do if onShell(x,y,z) then n=n+1 end end end end return n end
  1207.  
  1208. ------------------------------------------------------------
  1209. -- KEYS (P/Q/S) --------------------------------------------
  1210. local function keyListener()
  1211.   while true do
  1212.     local _, key = os.pullEvent("key")
  1213.     if key == keys.p then
  1214.       paused = not paused
  1215.       if paused then info("PAUSED (P = continue)") else info("RESUMED") end
  1216.     elseif key == keys.q then
  1217.       shouldQuit = true
  1218.       warn("Save & Quit...")
  1219.       saveState(snapshotState())
  1220.       return
  1221.     elseif key == keys.s then
  1222.       info(string.format("Status: Layer %d/%d | Pos [%d,%d,%d] | Fuel:%s | Inv:%d",
  1223.         curLayer, YH-1, pos.x,pos.y,pos.z, tostring(turtle.getFuelLevel()), countPlaceables()))
  1224.     end
  1225.   end
  1226. end
  1227. local function runWithKeys(fn) parallel.waitForAny(keyListener, function() fn() end) end
  1228.  
  1229. ------------------------------------------------------------
  1230. -- SHARED BUILD HELPERS ------------------------------------
  1231. -- NEU: Shared helper for contiguous X-runs (used by both FAST and SAFE)
  1232. local function makeRunsForRow(row, forward)
  1233.   if not row or #row == 0 then return {} end
  1234.   local runs = {}
  1235.   local step = forward and 1 or -1
  1236.   local s, p = row[1], row[1]
  1237.   for i = 2, #row do
  1238.     local x = row[i]
  1239.     if x == p + step then
  1240.       p = x
  1241.     else
  1242.       table.insert(runs, {s, p})
  1243.       s, p = x, x
  1244.     end
  1245.   end
  1246.   table.insert(runs, {s, p})
  1247.   return runs
  1248. end
  1249.  
  1250. local function planLayer(y)
  1251.   local plan, Zs = {}, {}
  1252.   for z=0,XZ-1 do
  1253.     local row={}
  1254.     local forward=(z%2==0)
  1255.     if forward then for x=0,XZ-1 do if onShell(x,y,z) then table.insert(row,x) end end
  1256.     else for x=XZ-1,0,-1 do if onShell(x,y,z) then table.insert(row,x) end end end
  1257.     if #row>0 then plan[z]=row; table.insert(Zs,z) end
  1258.   end
  1259.   return plan, Zs
  1260. end
  1261.  
  1262. ------------------------------------------------------------
  1263. -- FAST BUILD (RUN-OPTIMIZED) ------------------------------
  1264. local function hopToTarget(x, z, yTarget)
  1265.   if FAST_OVERHEAD_TRAVEL then
  1266.     -- NEU: Skip Overhead wenn schon nahe am Ziel (innerhalb 3 Blöcke)
  1267.     local distXZ = math.abs(pos.x - x) + math.abs(pos.z - z)
  1268.     local distY = math.abs(pos.y - yTarget)
  1269.    
  1270.     if distXZ <= 3 and distY <= 3 then
  1271.       -- Nah genug → Direkt hinfahren ohne Overhead
  1272.       return moveTo(x, z, yTarget, FAST_ALLOW_DIG)
  1273.     end
  1274.    
  1275.     -- Weit weg → Overhead-Travel nutzen
  1276.     local travelY = YH + OVERHEAD_MARGIN
  1277.     if not moveTo(pos.x,pos.z,travelY,false) then return false end
  1278.     if not moveTo(x, z, travelY, false) then return false end
  1279.     if not moveTo(x, z, yTarget, false) then return false end
  1280.     return true
  1281.   else
  1282.     return moveTo(x, z, yTarget, FAST_ALLOW_DIG)
  1283.   end
  1284. end
  1285.  
  1286. -- NEU: FAST Mode mit Run-Optimization!
  1287. local function buildFromFast(startY, startZ, startX)
  1288.   local totalPlan=totalBlocksPlanned()
  1289.   local t0=os.epoch("utc")
  1290.   local lastStatus = 0
  1291.  
  1292.   local function maybeStatus()
  1293.     local t = os.epoch("utc")
  1294.     if t - lastStatus > STATUS_UPDATE_INTERVAL then
  1295.       local rem = math.max(0, totalPlan - blocksPlaced)
  1296.       local elapsed = math.max(0.001, (t - t0) / 1000)
  1297.       local rate = (blocksPlaced > 0) and (blocksPlaced / elapsed) or 0
  1298.       if rate > 0 then
  1299.         statusLine(("FAST-RUN | Layer %d/%d | Z:%d | %d/%d | ETA: %.0fs")
  1300.           :format(curLayer, YH-1, curZ, blocksPlaced, totalPlan, rem / rate))
  1301.       else
  1302.         statusLine(("FAST-RUN | Layer %d/%d | Z:%d | %d/%d | ETA: --")
  1303.           :format(curLayer, YH-1, curZ, blocksPlaced, totalPlan))
  1304.       end
  1305.       lastStatus = t
  1306.     end
  1307.   end
  1308.  
  1309.   for y=startY, YH-1 do
  1310.     if shouldQuit then saveState(snapshotState()); return end
  1311.     curLayer=y
  1312.    
  1313.     lastSavedPos = {x=pos.x, y=pos.y, z=pos.z}
  1314.     lastSavedHeading = heading
  1315.    
  1316.     if not ensureFuelBuffered() then return end
  1317.     if shouldQuit then return end
  1318.     if not ensureSomeBlocks() then return end
  1319.     if shouldQuit then return end
  1320.    
  1321.     -- Plan layer with runs
  1322.     local layerPlan, sortedZ = planLayer(y)
  1323.    
  1324.     -- Start Z-index for resume
  1325.     local zStartIdx = 1
  1326.     if y == startY then
  1327.       for i, zz in ipairs(sortedZ) do
  1328.         if zz >= startZ then zStartIdx = i; break end
  1329.       end
  1330.     end
  1331.    
  1332.     for zi=zStartIdx, #sortedZ do
  1333.       if shouldQuit then saveState(snapshotState()); return end
  1334.       if not pauseGate() then saveState(snapshotState()); return end
  1335.      
  1336.       local z = sortedZ[zi]
  1337.       curZ = z
  1338.       local row = layerPlan[z]
  1339.       local forward = (z % 2 == 0)
  1340.       local step = forward and 1 or -1
  1341.      
  1342.       -- Create runs for this row
  1343.       local runs = makeRunsForRow(row, forward)
  1344.      
  1345.       -- Find start run for resume
  1346.       local runStartIdx, xResume = 1, nil
  1347.       if y == startY and z == startZ then
  1348.         for i, r in ipairs(runs) do
  1349.           local x1, x2 = r[1], r[2]
  1350.           local lo, hi = math.min(x1, x2), math.max(x1, x2)
  1351.           if startX >= lo and startX <= hi then
  1352.             runStartIdx = i
  1353.             xResume = startX
  1354.             break
  1355.           end
  1356.         end
  1357.       end
  1358.      
  1359.       for rIdx = runStartIdx, #runs do
  1360.         if shouldQuit then saveState(snapshotState()); return end
  1361.        
  1362.         local x1, x2 = runs[rIdx][1], runs[rIdx][2]
  1363.         local xStart = (rIdx == runStartIdx and xResume) and xResume or x1
  1364.        
  1365.         -- Hop to run start with overhead travel
  1366.         if not hopToTarget(xStart, z, y+1) then return end
  1367.         if shouldQuit then return end
  1368.        
  1369.         face(forward and 0 or 2)
  1370.         local x = xStart
  1371.        
  1372.         -- Build the run without overhead (stay at same height)
  1373.         while true do
  1374.           if shouldQuit then saveState(snapshotState()); return end
  1375.           curX = x
  1376.          
  1377.           -- Fuel check
  1378.           local fl = turtle.getFuelLevel()
  1379.           if fl ~= "unlimited" and type(fl)=="number" and fl < FUEL_BUFFER_MIN then
  1380.             if not ensureFuelBuffered() then return end
  1381.             if shouldQuit then return end
  1382.             -- Return to position in run
  1383.             if not hopToTarget(x, z, y+1) then return end
  1384.             if shouldQuit then return end
  1385.             face(forward and 0 or 2)
  1386.           end
  1387.          
  1388.           -- Material check
  1389.           if countPlaceables() == 0 then
  1390.             if not ensureSomeBlocks() then return end
  1391.             if shouldQuit then return end
  1392.             -- Return to position in run
  1393.             if not hopToTarget(x, z, y+1) then return end
  1394.             if shouldQuit then return end
  1395.             face(forward and 0 or 2)
  1396.           end
  1397.          
  1398.           -- Place block (all positions in run are valid)
  1399.           if not placeDownOne() then return end
  1400.           if shouldQuit then return end
  1401.          
  1402.           -- Save state
  1403.           if (blocksPlaced % SAVE_INTERVAL) == 0 then
  1404.             lastSavedPos = {x=pos.x, y=pos.y, z=pos.z}
  1405.             lastSavedHeading = heading
  1406.             saveState(snapshotState())
  1407.           end
  1408.           maybeStatus()
  1409.          
  1410.           -- End of run?
  1411.           if x == x2 then break end
  1412.          
  1413.           -- Move forward in run (no overhead!)
  1414.           if not fwd() then
  1415.             if FAST_ALLOW_DIG and turtle.detect() then turtle.dig() end
  1416.             if not fwd() then
  1417.               return smartFailError("Blocked in run @ Z="..z)
  1418.             end
  1419.           end
  1420.           if shouldQuit then return end
  1421.           x = x + step
  1422.         end
  1423.       end
  1424.     end
  1425.   end
  1426.  
  1427.   ok(t("build_complete"))
  1428.   moveTo(0,0,0,false); face(0); clearState()
  1429. end
  1430.  
  1431. ------------------------------------------------------------
  1432. -- SAFE BUILD (RUN-OPTIMIZED) ------------------------------
  1433. local function buildFromSafe(startY, startZ, startX)
  1434.   local totalPlan = totalBlocksPlanned()
  1435.   local t0 = os.epoch("utc")
  1436.   local lastStatus = 0
  1437.  
  1438.   local function maybeStatus()
  1439.     local t = os.epoch("utc")
  1440.     if t - lastStatus > STATUS_UPDATE_INTERVAL then
  1441.       local rem = math.max(0, totalPlan - blocksPlaced)
  1442.       local elapsed = math.max(0.001, (t - t0) / 1000)
  1443.       local rate = (blocksPlaced > 0) and (blocksPlaced / elapsed) or 0
  1444.       if rate > 0 then
  1445.         statusLine(("SAFE-RUN | Layer %d/%d | Z:%d | %d/%d | ETA: %.0fs")
  1446.           :format(curLayer, YH-1, curZ, blocksPlaced, totalPlan, rem / rate))
  1447.       else
  1448.         statusLine(("SAFE-RUN | Layer %d/%d | Z:%d | %d/%d | ETA: --")
  1449.           :format(curLayer, YH-1, curZ, blocksPlaced, totalPlan))
  1450.       end
  1451.       lastStatus = t
  1452.     end
  1453.   end
  1454.  
  1455.   for y = startY, YH-1 do
  1456.     if shouldQuit then saveState(snapshotState()); return end
  1457.     curLayer = y
  1458.    
  1459.     lastSavedPos = {x=pos.x, y=pos.y, z=pos.z}
  1460.     lastSavedHeading = heading
  1461.    
  1462.     if not ensureFuelBuffered() then return end
  1463.     if shouldQuit then return end
  1464.     if not ensureSomeBlocks() then return end
  1465.     if shouldQuit then return end
  1466.    
  1467.     -- Plan only for this layer
  1468.     local layerPlan, sortedZ = planLayer(y)
  1469.    
  1470.     -- Determine start Z-index (for resume)
  1471.     local zStartIdx = 1
  1472.     if y == startY then
  1473.       for i, zz in ipairs(sortedZ) do
  1474.         if zz >= startZ then zStartIdx = i; break end
  1475.       end
  1476.     end
  1477.    
  1478.     for zi = zStartIdx, #sortedZ do
  1479.       if shouldQuit then saveState(snapshotState()); return end
  1480.       if not pauseGate() then saveState(snapshotState()); return end
  1481.      
  1482.       local z = sortedZ[zi]
  1483.       curZ = z
  1484.       local row = layerPlan[z]
  1485.       local forward = (z % 2 == 0)
  1486.       local step = forward and 1 or -1
  1487.      
  1488.       -- Runs (contiguous X-ranges)
  1489.       local runs = makeRunsForRow(row, forward)
  1490.      
  1491.       -- Find start run for resume
  1492.       local runStartIdx, xResume = 1, nil
  1493.       if y == startY and z == startZ then
  1494.         for i, r in ipairs(runs) do
  1495.           local x1, x2 = r[1], r[2]
  1496.           local lo, hi = math.min(x1, x2), math.max(x1, x2)
  1497.           if startX >= lo and startX <= hi then
  1498.             runStartIdx = i
  1499.             xResume = startX
  1500.             break
  1501.           end
  1502.         end
  1503.       end
  1504.      
  1505.       for rIdx = runStartIdx, #runs do
  1506.         if shouldQuit then saveState(snapshotState()); return end
  1507.        
  1508.         local x1, x2 = runs[rIdx][1], runs[rIdx][2]
  1509.         local xStart = (rIdx == runStartIdx and xResume) and xResume or x1
  1510.        
  1511.         -- Move to run start once
  1512.         if not moveTo(xStart, z, y+1, true) then return end
  1513.         if shouldQuit then return end
  1514.        
  1515.         face(forward and 0 or 2)
  1516.         local x = xStart
  1517.        
  1518.         while true do
  1519.           if shouldQuit then saveState(snapshotState()); return end
  1520.           curX = x
  1521.          
  1522.           -- Fuel check (only at low level)
  1523.           local fl = turtle.getFuelLevel()
  1524.           if fl ~= "unlimited" and type(fl)=="number" and fl < FUEL_BUFFER_MIN then
  1525.             if not ensureFuelBuffered() then return end
  1526.             if shouldQuit then return end
  1527.             if not moveTo(x, z, y+1, true) then return end
  1528.             if shouldQuit then return end
  1529.             face(forward and 0 or 2)
  1530.           end
  1531.          
  1532.           if countPlaceables() == 0 then
  1533.             if not ensureSomeBlocks() then return end
  1534.             if shouldQuit then return end
  1535.             if not moveTo(x, z, y+1, true) then return end
  1536.             if shouldQuit then return end
  1537.             face(forward and 0 or 2)
  1538.           end
  1539.          
  1540.           -- In run all positions are valid -> place directly
  1541.           if not placeDownOne() then return end
  1542.           if shouldQuit then return end
  1543.          
  1544.           -- Save/Status
  1545.           if (blocksPlaced % SAVE_INTERVAL) == 0 then
  1546.             lastSavedPos = {x=pos.x, y=pos.y, z=pos.z}
  1547.             lastSavedHeading = heading
  1548.             saveState(snapshotState())
  1549.           end
  1550.           maybeStatus()
  1551.          
  1552.           if x == x2 then break end
  1553.           if not fwd() then
  1554.             if turtle.detect() then turtle.dig() end
  1555.             if not fwd() then return smartFailError("Blocked in run @ Z="..z) end
  1556.           end
  1557.           if shouldQuit then return end
  1558.           x = x + step
  1559.         end
  1560.       end
  1561.     end
  1562.   end
  1563.  
  1564.   ok(t("build_complete"))
  1565.   moveTo(0,0,0,true); face(0); clearState()
  1566. end
  1567.  
  1568. ------------------------------------------------------------
  1569. -- CONFIG / PLAN / MENU ------------------------------------
  1570. local function pickShape(cur)
  1571.   term.clear(); term.setCursorPos(1,1)
  1572.   title("Select Shape")
  1573.   print("")
  1574.   print(t("shape_1"))
  1575.   cprint("  "..t("shape_explain_torus"), colors.gray)
  1576.   print(t("shape_2"))
  1577.   cprint("  "..t("shape_explain_sphere"), colors.gray)
  1578.   print(t("shape_3"))
  1579.   cprint("  "..t("shape_explain_cylinder"), colors.gray)
  1580.   print(t("shape_4"))
  1581.   cprint("  "..t("shape_explain_dome"), colors.gray)
  1582.   print("")
  1583.   print("Enter = keep ("..cur..")")
  1584.   write("> ")
  1585.   local s = read()
  1586.   if s=="" then return cur end
  1587.   if s=="1" then return "torus"
  1588.   elseif s=="2" then return "sphere"
  1589.   elseif s=="3" then return "cylinder"
  1590.   elseif s=="4" then return "dome" end
  1591.   warn("Invalid - keeping "..cur); return cur
  1592. end
  1593.  
  1594. local function promptNumberWithHelp(msg, help, def, min)
  1595.   while true do
  1596.     print("")
  1597.     cprint(help, colors.gray)
  1598.     local s = prompt(msg, def)
  1599.     local v = tonumber(s)
  1600.     if v then v = round(v); if not min or v>=min then return v end end
  1601.     print("Bitte gueltige Zahl eingeben.")
  1602.   end
  1603. end
  1604.  
  1605. local function promptBoolWithHelp(msg, help, def)
  1606.   local defS = def and "y" or "n"
  1607.   while true do
  1608.     print("")
  1609.     cprint(help, colors.gray)
  1610.     local s = prompt(msg.." (y/n)", defS):lower()
  1611.     if s=="" then return def end
  1612.     if s=="y" or s=="yes" or s=="1" or s=="ja" then return true end
  1613.     if s=="n" or s=="no" or s=="0" or s=="nein" then return false end
  1614.     print("Bitte y/n eingeben.")
  1615.   end
  1616. end
  1617.  
  1618. local function configureShapeAndSize()
  1619.   SHAPE = pickShape(SHAPE)
  1620.   if SHAPE=="torus" then
  1621.     R      = promptNumberWithHelp(t("prompt_major_radius"), t("prompt_major_radius_help"), R, 1)
  1622.     r      = promptNumberWithHelp(t("prompt_tube_radius"), t("prompt_tube_radius_help"), r, 1)
  1623.     THICK  = promptNumberWithHelp(t("prompt_thickness"), t("prompt_thickness_help"), THICK, 1)
  1624.     YSCALE = promptNumberWithHelp(t("prompt_yscale"), t("prompt_yscale_help"), YSCALE, 0.1)
  1625.   elseif SHAPE=="sphere" then
  1626.     r      = promptNumberWithHelp(t("prompt_radius"), t("prompt_radius_help"), r, 1)
  1627.     THICK  = promptNumberWithHelp(t("prompt_thickness"), t("prompt_thickness_help"), THICK, 1)
  1628.     XSCALE = promptNumberWithHelp(t("prompt_xscale"), t("prompt_xscale_help"), XSCALE, 0.1)
  1629.     YSCALE = promptNumberWithHelp(t("prompt_yscale"), t("prompt_yscale_help"), YSCALE, 0.1)
  1630.     ZSCALE = promptNumberWithHelp(t("prompt_zscale"), t("prompt_zscale_help"), ZSCALE, 0.1)
  1631.   elseif SHAPE=="dome" then
  1632.     r      = promptNumberWithHelp(t("prompt_radius"), t("prompt_radius_help"), r, 1)
  1633.     THICK  = promptNumberWithHelp(t("prompt_thickness"), t("prompt_thickness_help"), THICK, 1)
  1634.     XSCALE = promptNumberWithHelp(t("prompt_xscale"), t("prompt_xscale_help"), XSCALE, 0.1)
  1635.     YSCALE = promptNumberWithHelp(t("prompt_yscale"), t("prompt_yscale_help"), YSCALE, 0.1)
  1636.     ZSCALE = promptNumberWithHelp(t("prompt_zscale"), t("prompt_zscale_help"), ZSCALE, 0.1)
  1637.     print("")
  1638.     cprint(t("prompt_dome_type_help"), colors.gray)
  1639.     print(t("prompt_dome_type"))
  1640.     print("1) "..t("dome_full"))
  1641.     print("2) "..t("dome_upper"))
  1642.     print("3) "..t("dome_lower"))
  1643.     write("> ")
  1644.     local dt = read()
  1645.     if dt=="1" then DOME_HALF="full"
  1646.     elseif dt=="2" then DOME_HALF="upper"
  1647.     elseif dt=="3" then DOME_HALF="lower"
  1648.     else DOME_HALF="upper" end
  1649.   else
  1650.     r      = promptNumberWithHelp(t("prompt_radius"), t("prompt_radius_help"), r, 1)
  1651.     CYL_H  = promptNumberWithHelp(t("prompt_height"), t("prompt_height_help"), CYL_H, 1)
  1652.     THICK  = promptNumberWithHelp(t("prompt_thickness"), t("prompt_thickness_help"), THICK, 1)
  1653.     CYL_FLOOR_STEP = promptNumberWithHelp(t("prompt_floors"), t("prompt_floors_help"), CYL_FLOOR_STEP or 0, 0)
  1654.     CYL_CAPS = promptBoolWithHelp(t("prompt_caps"), t("prompt_caps_help"), CYL_CAPS)
  1655.    
  1656.     -- NEU: Orientation auswählen
  1657.     print("")
  1658.     cprint(t("prompt_cyl_orientation_help"), colors.gray)
  1659.     print(t("prompt_cyl_orientation"))
  1660.     print("1) Horizontal (Default)")
  1661.     print("2) Vertical")
  1662.     write("> ")
  1663.     local orient = read()
  1664.     if orient == "2" then
  1665.       CYL_ORIENTATION = "vertical"
  1666.     else
  1667.       CYL_ORIENTATION = "horizontal"
  1668.     end
  1669.   end
  1670.  
  1671.   R, r, THICK, CYL_H, CYL_FLOOR_STEP = round(R), round(r), round(THICK), round(CYL_H), math.max(0, round(CYL_FLOOR_STEP))
  1672.   if THICK>2*r then
  1673.     warn(string.format("THICK=%d too large for r=%d! Setting to max: %d", THICK, r, 2*r))
  1674.     THICK=2*r
  1675.   end
  1676.   recomputeDerived()
  1677.  
  1678.   if XZ > MAX_STRUCTURE_SIZE or YH > MAX_STRUCTURE_SIZE then
  1679.     errc(string.format("Structure too large! XZ=%d, YH=%d (Max: %d)", XZ, YH, MAX_STRUCTURE_SIZE))
  1680.     pressEnter(); return
  1681.   end
  1682.  
  1683.   local n=totalBlocksPlanned()
  1684.   title("Plan (frozen)")
  1685.   print(string.format("Shape: %s", SHAPE))
  1686.   if SHAPE=="torus" then print(string.format("R=%d, r=%d, THICK=%d, YSCALE=%.2f", R,r,THICK,YSCALE))
  1687.   elseif SHAPE=="sphere" then print(string.format("r=%d, THICK=%d, X/Y/Z=%.2f/%.2f/%.2f", r,THICK,XSCALE,YSCALE,ZSCALE))
  1688.   elseif SHAPE=="dome" then print(string.format("r=%d, THICK=%d, X/Y/Z=%.2f/%.2f/%.2f, Half=%s", r,THICK,XSCALE,YSCALE,ZSCALE,DOME_HALF))
  1689.   else print(string.format("r=%d, height=%d, THICK=%d, floors=%d, caps=%s, orient=%s", r,CYL_H,THICK,CYL_FLOOR_STEP,tostring(CYL_CAPS),CYL_ORIENTATION)) end
  1690.   print("")
  1691.   cprint(t("plan_dimensions"), colors.yellow)
  1692.   print(t("plan_size_xyz", XZ, YH, XZ))
  1693.   print(t("plan_bounds", XZ-1, YH-1, XZ-1))
  1694.   print(t("plan_blocks_total", n))
  1695.   print("")
  1696.   cprint(t("plan_center"), colors.yellow)
  1697.   print(t("plan_center_coords", math.floor(CX), math.floor(CY), math.floor(CZ)))
  1698.   print(t("plan_center_world", math.floor(CX), math.floor(CY), math.floor(CZ)))
  1699.   cprint(t("plan_center_help"), colors.gray)
  1700.   print("")
  1701.   cprint(t("plan_material_needed"), colors.yellow)
  1702.   print(t("plan_blocks", n))
  1703.   print(t("plan_stacks", math.ceil(n/64)))
  1704.   print(t("plan_shulker", math.ceil(n/(27*64))))
  1705.   local approxMoves = n * 2 * (BUILD_MODE == "fast" and 3 or 1.5)
  1706.   print(t("plan_fuel_approx", math.ceil(approxMoves)))
  1707.   print(t("plan_coal", math.ceil(approxMoves/80)))
  1708.  
  1709.   if n > 50000 then
  1710.     print("")
  1711.     warn(t("plan_time_warning", n))
  1712.     local estimatedMinutes = math.ceil(n / 20)
  1713.     warn(t("plan_time_estimate", estimatedMinutes/60))
  1714.   end
  1715.  
  1716.   plannedConfig = {shape=SHAPE,R=R,r=r,THICK=THICK,XSCALE=XSCALE,YSCALE=YSCALE,ZSCALE=ZSCALE,CYL_H=CYL_H,CYL_FLOOR_STEP=CYL_FLOOR_STEP,CYL_CAPS=CYL_CAPS,CYL_ORIENTATION=CYL_ORIENTATION,DOME_HALF=DOME_HALF,BUILD_MODE=BUILD_MODE}
  1717.   pressEnter()
  1718. end
  1719.  
  1720. local function showPlan()
  1721.   recomputeDerived()
  1722.   local n=totalBlocksPlanned()
  1723.   title("Plan")
  1724.   print("Build Mode: "..BUILD_MODE)
  1725.   print(string.format("Shape: %s", SHAPE))
  1726.   if SHAPE=="torus" then print(string.format("R=%d, r=%d, THICK=%d, YSCALE=%.2f", R,r,THICK,YSCALE))
  1727.   elseif SHAPE=="sphere" then print(string.format("r=%d, THICK=%d, X/Y/Z=%.2f/%.2f/%.2f", r,THICK,XSCALE,YSCALE,ZSCALE))
  1728.   elseif SHAPE=="dome" then print(string.format("r=%d, THICK=%d, X/Y/Z=%.2f/%.2f/%.2f, Half=%s", r,THICK,XSCALE,YSCALE,ZSCALE,DOME_HALF))
  1729.   else print(string.format("r=%d, height=%d, THICK=%d, floors=%d, caps=%s, orient=%s", r,CYL_H,THICK,CYL_FLOOR_STEP,tostring(CYL_CAPS),CYL_ORIENTATION)) end
  1730.   print("")
  1731.   cprint(t("plan_dimensions"), colors.yellow)
  1732.   print(t("plan_size_xyz", XZ, YH, XZ))
  1733.   print(t("plan_bounds", XZ-1, YH-1, XZ-1))
  1734.   print(t("plan_blocks_total", n))
  1735.   print("")
  1736.   cprint(t("plan_center"), colors.yellow)
  1737.   print(t("plan_center_coords", math.floor(CX), math.floor(CY), math.floor(CZ)))
  1738.   print(t("plan_center_world", math.floor(CX), math.floor(CY), math.floor(CZ)))
  1739.   cprint(t("plan_center_help"), colors.gray)
  1740.   print("")
  1741.   cprint(t("plan_material_needed"), colors.yellow)
  1742.   print(t("plan_blocks", n))
  1743.   print(t("plan_stacks", math.ceil(n/64)))
  1744.   print(t("plan_shulker", math.ceil(n/(27*64))))
  1745.   local approxMoves = n * 2 * (BUILD_MODE == "fast" and 3 or 1.5)
  1746.   print(t("plan_fuel_approx", math.ceil(approxMoves)))
  1747.   print(t("plan_coal", math.ceil(approxMoves/80)))
  1748. end
  1749.  
  1750. local function buildStructure()
  1751.   local current = {shape=SHAPE,R=R,r=r,THICK=THICK,XSCALE=XSCALE,YSCALE=YSCALE,ZSCALE=ZSCALE,CYL_H=CYL_H,CYL_FLOOR_STEP=CYL_FLOOR_STEP,CYL_CAPS=CYL_CAPS,CYL_ORIENTATION=CYL_ORIENTATION,DOME_HALF=DOME_HALF,BUILD_MODE=BUILD_MODE}
  1752.   if plannedConfig and not sameConfig(plannedConfig,current) then
  1753.     warn("Configuration differs from last planned layout.")
  1754.     write("Continue with current configuration anyway? (y/n): ")
  1755.     if (read():lower()~="y") then info("Aborted."); return end
  1756.   end
  1757.  
  1758.   -- NEU: Setup-Hinweise anzeigen
  1759.   term.clear(); term.setCursorPos(1,1)
  1760.   title(t("build_setup_title"))
  1761.   print("")
  1762.   cprint(t("build_setup_position"), colors.yellow)
  1763.   cprint(t("build_setup_facing"), colors.yellow)
  1764.   print("")
  1765.   cprint(t("build_setup_chests"), colors.lime)
  1766.   cprint("  "..t("build_setup_material"), colors.white)
  1767.   cprint("  "..t("build_setup_fuel"), colors.white)
  1768.   print("")
  1769.   cprint(t("build_setup_ready"), colors.cyan)
  1770.   pressEnter("\n"..t("prompt_enter"))
  1771.  
  1772.   if not ensureFuelBuffered() then return end
  1773.   if shouldQuit then return end
  1774.   if not placeChestsIfNeeded() then return end
  1775.   if shouldQuit then return end
  1776.   if not ensureSomeBlocks() then return end
  1777.   if shouldQuit then return end
  1778.  
  1779.   clearState()
  1780.   curLayer,curZ,curX=0,0,0
  1781.   blocksPlaced=0
  1782.   lastSavedPos = {x=0,y=0,z=0}
  1783.   lastSavedHeading = 0
  1784.  
  1785.   if BUILD_MODE=="fast" then
  1786.     info("Starting build in FAST mode (Overhead="..tostring(FAST_OVERHEAD_TRAVEL)..")"); pressEnter()
  1787.     buildFromFast(0,0,0)
  1788.   else
  1789.     info("Starting build in SAFE mode"); pressEnter()
  1790.     buildFromSafe(0,0,0)
  1791.   end
  1792. end
  1793.  
  1794. local function resumeBuild()
  1795.   local st=loadState()
  1796.   if not st then errc("No save found."); return end
  1797.  
  1798.   if st.version ~= STATE_VERSION then
  1799.     warn(string.format("Save version: %s, Script version: %s",
  1800.          tostring(st.version or "1.0"), STATE_VERSION))
  1801.     if not promptBool("Load anyway? (might be incompatible)", false) then
  1802.       return
  1803.     end
  1804.   end
  1805.  
  1806.   SHAPE=st.shape or SHAPE; R=st.R or R; r=st.r or r; THICK=st.THICK or THICK
  1807.   XSCALE=st.XSCALE or XSCALE; YSCALE=st.YSCALE or YSCALE; ZSCALE=st.ZSCALE or ZSCALE
  1808.   CYL_H=st.CYL_H or CYL_H; CYL_FLOOR_STEP=(st.CYL_FLOOR_STEP~=nil) and st.CYL_FLOOR_STEP or CYL_FLOOR_STEP
  1809.   CYL_CAPS=(st.CYL_CAPS~=nil) and st.CYL_CAPS or CYL_CAPS
  1810.   CYL_ORIENTATION=st.CYL_ORIENTATION or CYL_ORIENTATION
  1811.   DOME_HALF=st.DOME_HALF or DOME_HALF
  1812.   BUILD_MODE=st.BUILD_MODE or BUILD_MODE
  1813.   recomputeDerived()
  1814.  
  1815.   curLayer=st.layer or 0; curZ=st.curZ or 0; curX=st.curX or 0
  1816.   pos=st.pos or {x=0,y=0,z=0}
  1817.   heading=st.heading or 0
  1818.   blocksPlaced=st.blocksPlaced or 0
  1819.  
  1820.   lastSavedPos = {x=pos.x, y=pos.y, z=pos.z}
  1821.   lastSavedHeading = heading
  1822.  
  1823.   title("RESUME BUILD")
  1824.   info(("Resume: Layer %d/%d, Cell (%d,%d) | Mode: %s"):format(curLayer,YH-1,curX,curZ,BUILD_MODE))
  1825.   info(string.format("Position: [%d,%d,%d] Heading:%d", pos.x,pos.y,pos.z,heading))
  1826.   info(string.format("Already placed: %d blocks", blocksPlaced))
  1827.   cprint("\n[IMPORTANT]", colors.yellow)
  1828.   cprint("The turtle should still be at the saved position!", colors.orange)
  1829.   cprint("If not: Move it manually there or the build will fail.", colors.orange)
  1830.   pressEnter("Ready to continue? Press Enter...")
  1831.  
  1832.   if BUILD_MODE=="fast" then buildFromFast(curLayer,curZ,curX) else buildFromSafe(curLayer,curZ,curX) end
  1833. end
  1834.  
  1835. -- NEU: Return to Saved Position with Auto-Resume
  1836. local function returnToSavedPositionManual()
  1837.   title("Return to Saved Position")
  1838.  
  1839.   local st = loadState()
  1840.   if not st then
  1841.     errc("File 'builder_state.txt' not found!")
  1842.     pressEnter()
  1843.     return
  1844.   end
  1845.  
  1846.   if not st.pos then
  1847.     errc("Invalid file content or pos missing.")
  1848.     pressEnter()
  1849.     return
  1850.   end
  1851.  
  1852.   local target = {
  1853.     x = st.pos.x,
  1854.     y = st.pos.y,
  1855.     z = st.pos.z,
  1856.     heading = st.heading or 0
  1857.   }
  1858.  
  1859.   info(string.format("Target position: [%d, %d, %d] Heading: %d", target.x, target.y, target.z, target.heading))
  1860.   cprint("\n[IMPORTANT]", colors.yellow)
  1861.   cprint("Make sure the turtle is at (0,0,0)!", colors.orange)
  1862.   cprint("Facing: East (+X)", colors.orange)
  1863.   pressEnter("\nReady? Press Enter...")
  1864.  
  1865.   pos = { x = 0, y = 0, z = 0 }
  1866.   heading = 0
  1867.  
  1868.   local function navForward()
  1869.     if turtle.forward() then
  1870.       if heading == 0 then pos.x = pos.x + 1
  1871.       elseif heading == 1 then pos.z = pos.z + 1
  1872.       elseif heading == 2 then pos.x = pos.x - 1
  1873.       elseif heading == 3 then pos.z = pos.z - 1
  1874.       end
  1875.       return true
  1876.     else
  1877.       warn(string.format("Blocked at: [%d, %d, %d]", pos.x, pos.y, pos.z))
  1878.       return false
  1879.     end
  1880.   end
  1881.  
  1882.   local function navUpTo(y)
  1883.     while pos.y < y do
  1884.       if turtle.up() then
  1885.         pos.y = pos.y + 1
  1886.       else
  1887.         errc("Blocked going up")
  1888.         return false
  1889.       end
  1890.     end
  1891.     while pos.y > y do
  1892.       if turtle.down() then
  1893.         pos.y = pos.y - 1
  1894.       else
  1895.         errc("Blocked going down")
  1896.         return false
  1897.       end
  1898.     end
  1899.     return true
  1900.   end
  1901.  
  1902.   local function navToZ(z)
  1903.     if pos.z < z then
  1904.       face(1)
  1905.       while pos.z < z do
  1906.         if not navForward() then return false end
  1907.       end
  1908.     elseif pos.z > z then
  1909.       face(3)
  1910.       while pos.z > z do
  1911.         if not navForward() then return false end
  1912.       end
  1913.     end
  1914.     return true
  1915.   end
  1916.  
  1917.   local function navToX(x)
  1918.     if pos.x < x then
  1919.       face(0)
  1920.       while pos.x < x do
  1921.         if not navForward() then return false end
  1922.       end
  1923.     elseif pos.x > x then
  1924.       face(2)
  1925.       while pos.x > x do
  1926.         if not navForward() then return false end
  1927.       end
  1928.     end
  1929.     return true
  1930.   end
  1931.  
  1932.   info("Starting navigation...")
  1933.  
  1934.   if not navUpTo(target.y) then
  1935.     errc("Navigation failed!")
  1936.     pressEnter()
  1937.     return
  1938.   end
  1939.  
  1940.   if not navToZ(target.z) then
  1941.     errc("Navigation failed!")
  1942.     pressEnter()
  1943.     return
  1944.   end
  1945.  
  1946.   if not navToX(target.x) then
  1947.     errc("Navigation failed!")
  1948.     pressEnter()
  1949.     return
  1950.   end
  1951.  
  1952.   face(target.heading)
  1953.  
  1954.   ok(string.format("Target reached! Position: [%d, %d, %d]", pos.x, pos.y, pos.z))
  1955.  
  1956.   SHAPE=st.shape or SHAPE
  1957.   R=st.R or R
  1958.   r=st.r or r
  1959.   THICK=st.THICK or THICK
  1960.   XSCALE=st.XSCALE or XSCALE
  1961.   YSCALE=st.YSCALE or YSCALE
  1962.   ZSCALE=st.ZSCALE or ZSCALE
  1963.   CYL_H=st.CYL_H or CYL_H
  1964.   CYL_FLOOR_STEP=(st.CYL_FLOOR_STEP~=nil) and st.CYL_FLOOR_STEP or CYL_FLOOR_STEP
  1965.   CYL_CAPS=(st.CYL_CAPS~=nil) and st.CYL_CAPS or CYL_CAPS
  1966.   CYL_ORIENTATION=st.CYL_ORIENTATION or CYL_ORIENTATION
  1967.   DOME_HALF=st.DOME_HALF or DOME_HALF
  1968.   BUILD_MODE=st.BUILD_MODE or BUILD_MODE
  1969.   recomputeDerived()
  1970.  
  1971.   curLayer=st.layer or 0
  1972.   curZ=st.curZ or 0
  1973.   curX=st.curX or 0
  1974.   blocksPlaced=st.blocksPlaced or 0
  1975.  
  1976.   lastSavedPos = {x=pos.x, y=pos.y, z=pos.z}
  1977.   lastSavedHeading = heading
  1978.  
  1979.   info(string.format("State restored: Layer %d/%d, Cell (%d,%d)", curLayer, YH-1, curX, curZ))
  1980.   info("Building will continue automatically...")
  1981.   sleep(2)
  1982.  
  1983.   if BUILD_MODE=="fast" then
  1984.     buildFromFast(curLayer,curZ,curX)
  1985.   else
  1986.     buildFromSafe(curLayer,curZ,curX)
  1987.   end
  1988. end
  1989.  
  1990. local function toggleAdvanced()
  1991.   title("Advanced Options")
  1992.   print("")
  1993.   print("1) Toggle Overhead-Travel (FAST): "..tostring(FAST_OVERHEAD_TRAVEL))
  1994.   cprint("   "..t("adv_explain_overhead"), colors.gray)
  1995.   print("2) Toggle FAST_ALLOW_DIG: "..tostring(FAST_ALLOW_DIG))
  1996.   cprint("   "..t("adv_explain_dig"), colors.gray)
  1997.   print("")
  1998.   print("Enter = back")
  1999.   write("> ")
  2000.   local s=read()
  2001.   if s=="1" then FAST_OVERHEAD_TRAVEL = not FAST_OVERHEAD_TRAVEL; ok("Overhead-Travel -> "..tostring(FAST_OVERHEAD_TRAVEL)); sleep(0.5)
  2002.   elseif s=="2" then FAST_ALLOW_DIG = not FAST_ALLOW_DIG; ok("FAST_ALLOW_DIG -> "..tostring(FAST_ALLOW_DIG)); sleep(0.5) end
  2003. end
  2004.  
  2005. local function changeLanguage()
  2006.   term.clear(); term.setCursorPos(1,1)
  2007.   title("Language / Sprache")
  2008.   print("Current / Aktuell: "..LANG)
  2009.   print("")
  2010.   print("Deutsch: de")
  2011.   print("English: en")
  2012.   print("")
  2013.   write("Select / Waehle (de/en): ")
  2014.   local newLang = read():lower()
  2015.  
  2016.   if TRANSLATIONS[newLang] then
  2017.     LANG = newLang
  2018.     saveLanguage()
  2019.     if newLang == "de" then
  2020.       ok("Sprache geaendert zu: "..LANG)
  2021.     else
  2022.       ok("Language changed to: "..LANG)
  2023.     end
  2024.     sleep(1)
  2025.   else
  2026.     warn("Invalid language / Ungueltige Sprache")
  2027.     sleep(1)
  2028.   end
  2029. end
  2030.  
  2031. local function showMenu()
  2032.   while true do
  2033.     term.clear(); term.setCursorPos(1,1)
  2034.     title(t("menu_title").." ("..BUILD_MODE.." "..t("menu_mode")..")")
  2035.     print(t("menu_1"))
  2036.     print(t("menu_2"))
  2037.     print(t("menu_3"))
  2038.     print("4) Reset Save")
  2039.     print(t("menu_5", BUILD_MODE))
  2040.     print(t("menu_c"))
  2041.     print(t("menu_a"))
  2042.     print(t("menu_r"))
  2043.     print(t("menu_l"))
  2044.     print(t("menu_q"))
  2045.     print("")
  2046.     cprint(t("key_p").." | "..t("key_q").." | "..t("key_s"), colors.yellow)
  2047.     write("> ")
  2048.     local c=(read() or ""):lower()
  2049.     if c=="1" then showPlan(); pressEnter()
  2050.     elseif c=="2" then runWithKeys(buildStructure); break
  2051.     elseif c=="3" then runWithKeys(resumeBuild); break
  2052.     elseif c=="4" then clearState(); ok(t("save_deleted")); pressEnter()
  2053.     elseif c=="5" then BUILD_MODE=(BUILD_MODE=="fast") and "safe" or "fast"; ok(t("mode_changed", BUILD_MODE)); sleep(1)
  2054.     elseif c=="c" then configureShapeAndSize()
  2055.     elseif c=="a" then toggleAdvanced()
  2056.     elseif c=="r" then runWithKeys(returnToSavedPositionManual); break
  2057.     elseif c=="l" then changeLanguage()
  2058.     elseif c=="q" then break end
  2059.   end
  2060. end
  2061.  
  2062. ------------------------------------------------------------
  2063. -- MAIN -----------------------------------------------------
  2064. math.randomseed(os.epoch("utc"))
  2065. if ACTION=="plan" then showPlan(); pressEnter()
  2066. elseif ACTION=="build" or ACTION=="place" then runWithKeys(buildStructure)
  2067. elseif ACTION=="resume" then runWithKeys(resumeBuild)
  2068. else showMenu()
  2069. end
Add Comment
Please, Sign In to add comment