Phylla

ShapeBuilder V8

Nov 1st, 2025 (edited)
286
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 52.11 KB | Gaming | 0 0
  1. -- builder_pro_failsafe.lua (2025 PRO + SMART FAILSAFE - UPDATED)
  2. -- Shapes: Torus | Sphere | Cylinder (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. -- Start: Turtle an SW-Unterkante des Volumens, Blick nach Osten (+X)
  6. -- Chests: Material (0,-1,0) | Fuel (+1,-1,+1)
  7.  
  8. ------------------------------------------------------------
  9. -- CONFIG ---------------------------------------------------
  10. local BUILD_MODE = "safe"          -- "fast" | "safe"
  11. local FAST_ALLOW_DIG = false       -- FAST: Dig beim Bewegen?
  12. local FAST_OVERHEAD_TRAVEL = true  -- FAST: immer über Volumen hoppen
  13. local OVERHEAD_MARGIN = 2          -- Überhöhe für Overhead-Hop (YH + margin)
  14.  
  15. local SLOT_FUEL = 1
  16. local SLOT_MIN, SLOT_MAX = 2, 16
  17. local FUEL_BUFFER_MIN, FUEL_TARGET = 400, 2000
  18.  
  19. local STATE_FILE = "builder_state.txt"
  20. local SAVE_INTERVAL = 1
  21. local FUEL_CHECK_INTERVAL = 64
  22.  
  23. -- NEU: Zusätzliche Sicherheits-Konstanten
  24. local STATE_VERSION = "2.0"
  25. local SUCK_AMOUNT = 64
  26. local FUEL_FETCH_RETRIES = 16
  27. local CHEST_WAIT_RETRIES = 32
  28. local MAX_STRUCTURE_SIZE = 256
  29. local MAX_MOVE_ATTEMPTS = 256
  30. local RECOVERY_MOVE_TIMEOUT = 500  -- Max Bewegungen bei Recovery
  31.  
  32. -- NEU: Fuel Whitelist (nur diese Items als Fuel erlaubt)
  33. local FUEL_WHITELIST = {
  34.   ["minecraft:coal"]       = true,
  35.   ["minecraft:charcoal"]   = true,
  36.   ["minecraft:coal_block"] = true,
  37.   -- optional:
  38.   -- ["minecraft:blaze_rod"]   = true,
  39.   -- ["minecraft:lava_bucket"] = true,
  40. }
  41.  
  42. ------------------------------------------------------------
  43. -- UI / HELPERS --------------------------------------------
  44. local hasColors = term and term.setTextColor and colors
  45. local function cprint(s,c) if hasColors then term.setTextColor(c) end print(s) if hasColors then term.setTextColor(colors.white) end end
  46. local function ok(s)   cprint("[OK] "..s, colors.lime) end
  47. local function warn(s) cprint("[WARN] "..s, colors.orange) end
  48. local function errc(s) cprint("[ERROR] "..s, colors.red) end
  49. local function info(s) cprint("[INFO] "..s, colors.lightBlue) end
  50. local function title(s) cprint("\n== "..s.." ==", colors.cyan) end
  51. local function statusLine(str) local w,h=term.getSize() term.setCursorPos(1,h) term.clearLine() term.write(str or "") end
  52. local function pressEnter(msg) write(msg or "Enter ... "); read() end
  53. local function round(n) return math.floor((tonumber(n) or 0) + 0.5) end
  54. local function fmtFuel(x) return (x=="unlimited") and "unlimited" or tostring(x) end
  55.  
  56. local function prompt(msg, def)
  57.   local hint = (def ~= nil) and (" ["..tostring(def).."]") or ""
  58.   write(msg..hint..": ")
  59.   local s = read()
  60.   if s=="" and def~=nil then return tostring(def) end
  61.   return s
  62. end
  63. local function promptNumber(msg, def, min)
  64.   while true do
  65.     local s = prompt(msg, def)
  66.     local v = tonumber(s)
  67.     if v then v = round(v); if not min or v>=min then return v end end
  68.     print("Please enter a valid number.")
  69.   end
  70. end
  71. local function promptBool(msg, def)
  72.   local defS = def and "y" or "n"
  73.   while true do
  74.     local s = prompt(msg.." (y/n)", defS):lower()
  75.     if s=="" then return def end
  76.     if s=="y" or s=="yes" or s=="1" or s=="ja" then return true end
  77.     if s=="n" or s=="no" or s=="0" or s=="nein" then return false end
  78.     print("Please enter y/n.")
  79.   end
  80. end
  81.  
  82. ------------------------------------------------------------
  83. -- ARGS / PARAMS -------------------------------------------
  84. local tArgs = {...}
  85. local function isShape(s) s=(s or ""):lower(); return (s=="torus" or s=="sphere" or s=="cylinder") end
  86. local SHAPE, ACTION = "torus", ""
  87. local R, r, THICK, YSCALE = 20, 7, 1, 1.0
  88. local CYL_H, CYL_FLOOR_STEP, CYL_CAPS = 12, 0, true
  89.  
  90. local function toNum(s,d) local v=tonumber(s); return v and v or d end
  91. local function toBool(s,def)
  92.   if s==nil then return def end
  93.   local v=tostring(s):lower()
  94.   if v=="1" or v=="true" or v=="yes" or v=="ja" then return true end
  95.   if v=="0" or v=="false" or v=="no" or v=="nein" then return false end
  96.   return def
  97. end
  98.  
  99. if #tArgs>0 and isShape(tArgs[1]) then
  100.   SHAPE = tArgs[1]:lower()
  101.   local i=2
  102.   if SHAPE=="torus" then
  103.     R      = toNum(tArgs[i],20); i=i+1
  104.     r      = toNum(tArgs[i],7);  i=i+1
  105.     THICK  = toNum(tArgs[i],1);  i=i+1
  106.     YSCALE = toNum(tArgs[i],1.0);i=i+1
  107.   elseif SHAPE=="sphere" then
  108.     r      = toNum(tArgs[i],8);  i=i+1
  109.     THICK  = toNum(tArgs[i],1);  i=i+1
  110.     YSCALE = toNum(tArgs[i],1.0);i=i+1
  111.   elseif SHAPE=="cylinder" then
  112.     r      = toNum(tArgs[i],8);       i=i+1
  113.     CYL_H  = toNum(tArgs[i],12);      i=i+1
  114.     THICK  = toNum(tArgs[i],1);       i=i+1
  115.     CYL_FLOOR_STEP = toNum(tArgs[i],0) or 0; i=i+1
  116.     CYL_CAPS = toBool(tArgs[i], true); i=i+1
  117.   end
  118.   ACTION = (tArgs[i] or ""):lower()
  119. elseif #tArgs>0 then
  120.   print("Usage:")
  121.   print("  builder_pro_failsafe torus    R r [thickness] [yScale]   [plan|build|resume]")
  122.   print("  builder_pro_failsafe sphere   r [thickness] [yScale]     [plan|build|resume]")
  123.   print("  builder_pro_failsafe cylinder r h [thickness] [floor] [caps] [plan|build|resume]")
  124.   return
  125. end
  126.  
  127. -- Auto-Round & validate
  128. R, r, THICK, CYL_H, CYL_FLOOR_STEP = round(R), round(r), round(THICK), round(CYL_H), math.max(0, round(CYL_FLOOR_STEP))
  129.  
  130. -- Validierung YSCALE
  131. if YSCALE <= 0 then
  132.   errc("YSCALE muss > 0 sein!"); return
  133. end
  134. if YSCALE > 10 then
  135.   warn("YSCALE > 10 can lead to very large structures!")
  136. end
  137.  
  138. if THICK > 2*r then
  139.   warn(string.format("THICK=%d ist zu groß für r=%d! Setze auf Maximum: %d", THICK, r, 2*r))
  140.   THICK = 2*r
  141. end
  142.  
  143. if (SHAPE=="torus" and (R<=0 or r<=0 or THICK<=0))
  144. or (SHAPE=="sphere" and (r<=0 or THICK<=0))
  145. or (SHAPE=="cylinder" and (r<=0 or CYL_H<=0 or THICK<=0)) then
  146.   errc("Invalid parameters."); return
  147. end
  148.  
  149. ------------------------------------------------------------
  150. -- DERIVED GEOMETRY ----------------------------------------
  151. local XZ, YH, CX, CY, CZ
  152. local function recomputeDerived()
  153.   if SHAPE=="torus" then
  154.     XZ = 2*(R + r) + 1
  155.     YH = math.floor(2*r*YSCALE + 1)
  156.   elseif SHAPE=="sphere" then
  157.     XZ = 2*r + 1
  158.     YH = math.floor(2*r*YSCALE + 1)
  159.   else
  160.     XZ = 2*r + 1
  161.     YH = CYL_H
  162.   end
  163.   CX, CY, CZ = (XZ-1)/2, (YH-1)/2, (XZ-1)/2
  164. end
  165. recomputeDerived()
  166.  
  167. -- Größen-Check
  168. if XZ > MAX_STRUCTURE_SIZE or YH > MAX_STRUCTURE_SIZE then
  169.   errc(string.format("Struktur zu groß! XZ=%d, YH=%d (Max: %d)", XZ, YH, MAX_STRUCTURE_SIZE))
  170.   errc("Reduziere R/r/CYL_H oder erhöhe MAX_STRUCTURE_SIZE!")
  171.   return
  172. end
  173.  
  174. ------------------------------------------------------------
  175. -- STATE / POS ---------------------------------------------
  176. local pos={x=0,y=0,z=0}
  177. local heading=0  -- 0=+X,1=+Z,2=-X,3=-Z
  178. local stepCounter, blocksPlaced = 0, 0
  179. local paused, shouldQuit = false, false
  180. local curLayer, curZ, curX = 0,0,0
  181. local plannedConfig = nil
  182.  
  183. -- NEU: Für Recovery
  184. local lastSavedPos = {x=0,y=0,z=0}
  185. local lastSavedHeading = 0
  186. local recoveryMode = false
  187.  
  188. ------------------------------------------------------------
  189. -- SAVE/LOAD + FAILSAFE ------------------------------------
  190. local function saveState(st) local f=fs.open(STATE_FILE,"w"); f.write(textutils.serialize(st)); f.close() end
  191. local function loadState()
  192.   if not fs.exists(STATE_FILE) then return nil end
  193.   local f=fs.open(STATE_FILE,"r"); local txt=f.readAll(); f.close()
  194.   local ok2,data=pcall(textutils.unserialize,txt); if ok2 then return data end; return nil
  195. end
  196. local function clearState() if fs.exists(STATE_FILE) then fs.delete(STATE_FILE) end end
  197.  
  198. local function snapshotState()
  199.   return {
  200.     version=STATE_VERSION,
  201.     shape=SHAPE, R=R, r=r, THICK=THICK, YSCALE=YSCALE,
  202.     CYL_H=CYL_H, CYL_FLOOR_STEP=CYL_FLOOR_STEP, CYL_CAPS=CYL_CAPS,
  203.     layer=curLayer, curZ=curZ, curX=curX,
  204.     pos={x=pos.x, y=pos.y, z=pos.z},
  205.     heading=heading,
  206.     BUILD_MODE=BUILD_MODE,
  207.     blocksPlaced=blocksPlaced
  208.   }
  209. end
  210.  
  211. local function sameConfig(a,b)
  212.   return a and b and a.shape==b.shape and a.R==b.R and a.r==b.r and a.THICK==b.THICK and a.YSCALE==b.YSCALE
  213.      and a.CYL_H==b.CYL_H and a.CYL_FLOOR_STEP==b.CYL_FLOOR_STEP and a.CYL_CAPS==b.CYL_CAPS and a.BUILD_MODE==b.BUILD_MODE
  214. end
  215.  
  216. ------------------------------------------------------------
  217. -- MOVE -----------------------------------------------------
  218. local function turnLeft() turtle.turnLeft(); heading=(heading+3)%4 end
  219. local function turnRight() turtle.turnRight(); heading=(heading+1)%4 end
  220. 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
  221. local function fwd()
  222.   if turtle.forward() then
  223.     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
  224.     stepCounter=stepCounter+1; return true
  225.   end
  226.   return false
  227. end
  228. local function up() if turtle.up() then pos.y=pos.y+1; stepCounter=stepCounter+1; return true end return false end
  229. local function down() if turtle.down() then pos.y=pos.y-1; stepCounter=stepCounter+1; return true end return false end
  230.  
  231. local function stepWithOptionalDig(dir)
  232.   face(dir)
  233.   if fwd() then return true end
  234.   if FAST_ALLOW_DIG and turtle.detect() then turtle.dig(); return fwd() end
  235.   return false
  236. end
  237.  
  238. ------------------------------------------------------------
  239. -- SMART ERROR HANDLER -------------------------------------
  240. local function returnToSavedPosition()
  241.   recoveryMode = true
  242.   warn("RECOVERY: Kehre zu letzter gespeicherter Position zurück...")
  243.   info(string.format("Ziel: [%d,%d,%d] Richtung:%d", lastSavedPos.x, lastSavedPos.y, lastSavedPos.z, lastSavedHeading))
  244.  
  245.   local moveCount = 0
  246.   local function safeMove(moveFunc, axis)
  247.     moveCount = moveCount + 1
  248.     if moveCount > RECOVERY_MOVE_TIMEOUT then
  249.       errc("RECOVERY FEHLGESCHLAGEN: Zu viele Bewegungsversuche!")
  250.       return false
  251.     end
  252.    
  253.     for attempt=1,3 do
  254.       if moveFunc() then return true end
  255.       -- Blockierung? Versuche zu diggen
  256.       if axis == "up" and turtle.detectUp() then turtle.digUp()
  257.       elseif axis == "down" and turtle.detectDown() then turtle.digDown()
  258.       elseif turtle.detect() then turtle.dig() end
  259.       sleep(0.2)
  260.     end
  261.     return false
  262.   end
  263.  
  264.   -- Y zuerst (nach oben für Freiraum)
  265.   while pos.y < lastSavedPos.y do
  266.     if not safeMove(up, "up") then return false end
  267.   end
  268.  
  269.   -- X und Z
  270.   while pos.x ~= lastSavedPos.x or pos.z ~= lastSavedPos.z do
  271.     if pos.x < lastSavedPos.x then
  272.       face(0)
  273.       if not safeMove(fwd, "forward") then return false end
  274.     elseif pos.x > lastSavedPos.x then
  275.       face(2)
  276.       if not safeMove(fwd, "forward") then return false end
  277.     elseif pos.z < lastSavedPos.z then
  278.       face(1)
  279.       if not safeMove(fwd, "forward") then return false end
  280.     elseif pos.z > lastSavedPos.z then
  281.       face(3)
  282.       if not safeMove(fwd, "forward") then return false end
  283.     end
  284.   end
  285.  
  286.   -- Y anpassen (falls noch nötig)
  287.   while pos.y > lastSavedPos.y do
  288.     if not safeMove(down, "down") then return false end
  289.   end
  290.   while pos.y < lastSavedPos.y do
  291.     if not safeMove(up, "up") then return false end
  292.   end
  293.  
  294.   -- Richtung korrigieren
  295.   face(lastSavedHeading)
  296.  
  297.   ok(string.format("RECOVERY ERFOLGREICH! Position: [%d,%d,%d]", pos.x, pos.y, pos.z))
  298.   recoveryMode = false
  299.   return true
  300. end
  301.  
  302. local function smartFailError(msg)
  303.   errc("FEHLER: "..msg)
  304.  
  305.   -- Save current state
  306.   local errorState = snapshotState()
  307.   saveState(errorState)
  308.   warn("State gespeichert: "..STATE_FILE)
  309.  
  310.   -- Versuche zur letzten bekannten Position zurückzukehren
  311.   if not recoveryMode then  -- Verhindere Recovery-Loop
  312.     if returnToSavedPosition() then
  313.       title("FEHLER BEHOBEN - BEREIT FÜR RESUME")
  314.       cprint("\nDie Turtle ist zur letzten bekannten Position zurückgekehrt.", colors.yellow)
  315.       cprint("Problem: "..msg, colors.orange)
  316.       cprint("\nBitte behebe das Problem:", colors.lime)
  317.       cprint("  - Bei Fuel-Mangel: Fülle Fuel-Chest", colors.white)
  318.       cprint("  - Bei Material-Mangel: Fülle Material-Chest", colors.white)
  319.       cprint("  - Bei Blockierung: Entferne Hindernisse", colors.white)
  320.       cprint("\nDann starte Resume mit:", colors.cyan)
  321.       cprint("  builder_pro_failsafe resume", colors.lightBlue)
  322.       cprint("\nOder drücke im Menü '3' für Resume", colors.lightBlue)
  323.     else
  324.       errc("RECOVERY FEHLGESCHLAGEN!")
  325.       errc(string.format("Turtle Position: [%d,%d,%d]", pos.x, pos.y, pos.z))
  326.       errc(string.format("Gespeicherte Position: [%d,%d,%d]", lastSavedPos.x, lastSavedPos.y, lastSavedPos.z))
  327.       warn("Bitte bewege Turtle MANUELL zur gespeicherten Position!")
  328.     end
  329.   else
  330.     errc("Recovery selbst fehlgeschlagen - Turtle steckt fest!")
  331.     errc(string.format("Position: [%d,%d,%d]", pos.x, pos.y, pos.z))
  332.   end
  333.  
  334.   shouldQuit = true
  335.   return false
  336. end
  337.  
  338. local function moveTo(tx, tz, ty, allowDig)
  339.   local blocked = 0
  340.  
  341.   local function tryUp()
  342.     if up() then blocked = 0; return true end
  343.     if turtle.detectUp() then turtle.digUp() end
  344.     blocked = blocked + 1
  345.     if blocked > MAX_MOVE_ATTEMPTS then return false end
  346.     sleep(0.05)
  347.     return false
  348.   end
  349.  
  350.   local function tryDown()
  351.     if down() then blocked = 0; return true end
  352.     if turtle.detectDown() then turtle.digDown() end
  353.     blocked = blocked + 1
  354.     if blocked > MAX_MOVE_ATTEMPTS then return false end
  355.     sleep(0.05)
  356.     return false
  357.   end
  358.  
  359.   -- Y-Bewegung zuerst
  360.   while pos.y < ty do
  361.     if not tryUp() then
  362.       return smartFailError(string.format("Aufwärts blockiert bei Y=%d (Ziel: %d)", pos.y, ty))
  363.     end
  364.   end
  365.  
  366.   while pos.y > ty do
  367.     if not tryDown() then
  368.       return smartFailError(string.format("Abwärts blockiert bei Y=%d (Ziel: %d)", pos.y, ty))
  369.     end
  370.   end
  371.  
  372.   -- XZ-Bewegung
  373.   local function stepDir(dir)
  374.     face(dir)
  375.     if fwd() then blocked = 0; return true end
  376.     if allowDig and turtle.detect() then turtle.dig() end
  377.     if fwd() then blocked = 0; return true end
  378.     blocked = blocked + 1
  379.     if blocked > MAX_MOVE_ATTEMPTS then return false end
  380.     sleep(0.05)
  381.     return false
  382.   end
  383.  
  384.   while pos.x ~= tx or pos.z ~= tz do
  385.     if pos.x < tx then
  386.       if not stepDir(0) then
  387.         return smartFailError(string.format("Move +X blockiert @ [%d,%d,%d]", pos.x, pos.y, pos.z))
  388.       end
  389.     elseif pos.x > tx then
  390.       if not stepDir(2) then
  391.         return smartFailError(string.format("Move -X blockiert @ [%d,%d,%d]", pos.x, pos.y, pos.z))
  392.       end
  393.     elseif pos.z < tz then
  394.       if not stepDir(1) then
  395.         return smartFailError(string.format("Move +Z blockiert @ [%d,%d,%d]", pos.x, pos.y, pos.z))
  396.       end
  397.     elseif pos.z > tz then
  398.       if not stepDir(3) then
  399.         return smartFailError(string.format("Move -Z blockiert @ [%d,%d,%d]", pos.x, pos.y, pos.z))
  400.       end
  401.     end
  402.   end
  403.  
  404.   return true
  405. end
  406.  
  407. ------------------------------------------------------------
  408. -- INSPECT / CHESTS / FUEL / INVENTORY ---------------------
  409. local function safeInspect(fn) local ok,d=fn(); if ok then return d.name or "block" end return nil end
  410. 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
  411.  
  412. local function pauseGate()
  413.   while paused and not shouldQuit do statusLine("PAUSE (P=weiter, Q=save+quit, S=status)"); sleep(0.1) end
  414.   return not shouldQuit
  415. end
  416.  
  417. -- NEU: Fuel Whitelist Check
  418. local function isFuelLike(d)
  419.   if not d or not d.name then return false end
  420.   return FUEL_WHITELIST[d.name:lower()] == true
  421. end
  422.  
  423. local function refuelFromSlot1(target)
  424.   turtle.select(SLOT_FUEL)
  425.   while turtle.getFuelLevel()~="unlimited" and turtle.getFuelLevel()<target do
  426.     local d=turtle.getItemDetail(SLOT_FUEL); if not isFuelLike(d) then break end
  427.     if not turtle.refuel(1) then break end
  428.   end
  429. end
  430.  
  431. local function dumpNonFuelToFront()
  432.   local d=turtle.getItemDetail(SLOT_FUEL)
  433.   if d and not isFuelLike(d) then
  434.     turtle.select(SLOT_FUEL)
  435.     turtle.drop()
  436.   end
  437. end
  438.  
  439. local function findChestSlot()
  440.   for s=1,16 do local d=turtle.getItemDetail(s); if d and d.name and isInventoryName(d.name) then return s end end
  441.   return nil
  442. end
  443.  
  444. local function goToFuelChestLocation()
  445.   if not moveTo(0,0,0,false) then return false end
  446.   face(1); if not fwd() then return false end
  447.   face(0); if not fwd() then return false end
  448.   local dn=safeInspect(turtle.inspectDown); if dn and not isInventoryName(dn) then turtle.digDown() end
  449.   if not down() then return false end
  450.   face(1)
  451.   local fr=safeInspect(turtle.inspect); if fr and not isInventoryName(fr) then turtle.dig() end
  452.   return true
  453. end
  454.  
  455. -- NEU: Smart Overhead-Routen
  456. local function goToBaseSmart()
  457.   if FAST_OVERHEAD_TRAVEL then
  458.     local travelY = YH + OVERHEAD_MARGIN
  459.     return moveTo(pos.x, pos.z, travelY, false)
  460.        and moveTo(0, 0, travelY, false)
  461.        and moveTo(0, 0, 0, false)
  462.   else
  463.     return moveTo(0, 0, 0, false)
  464.   end
  465. end
  466.  
  467. local function returnToWorkSmart(bx, bz, by, h)
  468.   if FAST_OVERHEAD_TRAVEL then
  469.     local travelY = YH + OVERHEAD_MARGIN
  470.     local ok = moveTo(pos.x, pos.z, travelY, false)
  471.            and moveTo(bx, bz, travelY, false)
  472.            and moveTo(bx, bz, by, false)
  473.     if ok then face(h) end
  474.     return ok
  475.   else
  476.     local ok = moveTo(bx, bz, by, false)
  477.     if ok then face(h) end
  478.     return ok
  479.   end
  480. end
  481.  
  482. local function placeChestsIfNeeded()
  483.   title("Auto-Chest Setup")
  484.   local below=safeInspect(turtle.inspectDown)
  485.   if isInventoryName(below) then ok("Material chest erkannt unter Turtle.")
  486.   else
  487.     local cs=findChestSlot(); if not cs then return smartFailError("Keine Chest/Barrel im Inventar für Material.") end
  488.     turtle.select(cs); local n=safeInspect(turtle.inspectDown); if n and not isInventoryName(n) then turtle.digDown() end
  489.     if not turtle.placeDown() then return smartFailError("Material chest konnte nicht platziert werden.") end
  490.     ok("Material chest platziert.")
  491.   end
  492.  
  493.   local bx,bz,by,h = pos.x,pos.z,pos.y,heading
  494.   if not goToFuelChestLocation() then return smartFailError("Fuel-Spot nicht erreichbar.") end
  495.   local front=safeInspect(turtle.inspect)
  496.   local haveFuel=isInventoryName(front)
  497.   if haveFuel then ok("Fuel chest vorhanden.")
  498.   else
  499.     local cs2=findChestSlot(); if not cs2 then return smartFailError("Keine Chest für Fuel verfügbar.") end
  500.     turtle.select(cs2)
  501.     if not turtle.place() then return smartFailError("Fuel chest konnte nicht platziert werden.") end
  502.     ok("Fuel chest platziert.")
  503.   end
  504.  
  505.   -- Zurück über Overhead-Route
  506.   face(3); if not up() then return smartFailError("Rückweg vom Fuel-Spot: up fehlgeschlagen") end
  507.   face(2); if not fwd() then return smartFailError("Rückweg vom Fuel-Spot: -X fehlgeschlagen") end
  508.   face(3); if not fwd() then return smartFailError("Rückweg vom Fuel-Spot: -Z fehlgeschlagen") end
  509.  
  510.   if not returnToWorkSmart(bx, bz, by, h) then
  511.     return smartFailError("Rückkehr zur Startposition fehlgeschlagen.")
  512.   end
  513.  
  514.   info("Chests ready.")
  515.   return true
  516. end
  517.  
  518. -- NEU: Robuste Fuel-Fetch mit Whitelist
  519. local function fetchFuelFromRightChest(target)
  520.   local bx,bz,by,h = pos.x,pos.z,pos.y,heading
  521.   info("Gehe zur Basis für Fuel-Refill ...")
  522.  
  523.   if not goToBaseSmart() then
  524.     return smartFailError("Basis nicht erreichbar.")
  525.   end
  526.  
  527.   face(0)
  528.   if not goToFuelChestLocation() then
  529.     return smartFailError("Fuel-Spot nicht erreichbar.")
  530.   end
  531.  
  532.   turtle.select(SLOT_FUEL)
  533.   local tries = 0
  534.  
  535.   while true do
  536.     tries = tries + 1
  537.  
  538.     -- Dumpe Non-Fuel Items aus Slot 1
  539.     local d = turtle.getItemDetail(SLOT_FUEL)
  540.     if d and (not isFuelLike(d)) then
  541.       warn("Slot 1 enthält Non-Fuel: "..(d.name or "unknown").." -> in Fuel-Chest zurück")
  542.       turtle.drop()
  543.     end
  544.  
  545.     -- Hole neues Item wenn Slot leer
  546.     if turtle.getItemCount(SLOT_FUEL) == 0 then
  547.       turtle.suck(SUCK_AMOUNT)
  548.     end
  549.  
  550.     -- Prüfe ob es Fuel ist
  551.     d = turtle.getItemDetail(SLOT_FUEL)
  552.     if d and isFuelLike(d) then
  553.       ok("Geholt: "..(d.name or "unknown"))
  554.       break
  555.     end
  556.  
  557.     -- Warte auf zulässiges Fuel
  558.     if tries % 5 == 1 then
  559.       warn("============================================")
  560.       warn("FUEL-CHEST ENTHÄLT KEIN ZULÄSSIGES FUEL!")
  561.       warn("Bitte fülle Coal/Charcoal/Coal Block ein")
  562.       warn("Position: (+1,-1,+1) relativ zur Basis")
  563.       warn("Warte... (Versuch "..tries..")")
  564.       warn("============================================")
  565.     end
  566.    
  567.     sleep(2)
  568.    
  569.     if shouldQuit then
  570.       face(3); up(); face(2); fwd(); face(3); fwd()
  571.       returnToWorkSmart(bx, bz, by, h)
  572.       return smartFailError("User hat Fuel-Refill abgebrochen (Q)")
  573.     end
  574.    
  575.     if tries > 50 then
  576.       return smartFailError("Fuel-Refill: Keine zulässigen Fuel-Items gefunden (50 Versuche).")
  577.     end
  578.   end
  579.  
  580.   -- Tanke aus Slot 1
  581.   info("Tanke aus Slot 1...")
  582.   while turtle.getItemCount(SLOT_FUEL) > 0
  583.     and turtle.getFuelLevel() ~= "unlimited"
  584.     and turtle.getFuelLevel() < target do
  585.     if not turtle.refuel(1) then break end
  586.   end
  587.   turtle.select(SLOT_MIN)
  588.  
  589.   -- Zurück über Overhead-Route
  590.   face(3)
  591.   if not up() then
  592.     return smartFailError("Rückweg vom Fuel-Spot: up fehlgeschlagen")
  593.   end
  594.   face(2)
  595.   if not fwd() then
  596.     return smartFailError("Rückweg vom Fuel-Spot: -X fehlgeschlagen")
  597.   end
  598.   face(3)
  599.   if not fwd() then
  600.     return smartFailError("Rückweg vom Fuel-Spot: -Z fehlgeschlagen")
  601.   end
  602.  
  603.   if not returnToWorkSmart(bx, bz, by, h) then
  604.     return smartFailError("Rückkehr zur Bauebene fehlgeschlagen.")
  605.   end
  606.  
  607.   local level=turtle.getFuelLevel()
  608.   ok(string.format("Fuel-Level nach Refill: %s", fmtFuel(level)))
  609.  
  610.   -- WICHTIG: Prüfe ob erfolgreich aufgetankt
  611.   if level == "unlimited" then
  612.     return true
  613.   end
  614.  
  615.   if type(level) == "number" and level >= target then
  616.     return true
  617.   end
  618.  
  619.   -- Immer noch nicht genug Fuel!
  620.   warn(string.format("WARNUNG: Fuel-Level (%s) unter Target (%d)!", fmtFuel(level), target))
  621.   return false
  622. end
  623.  
  624. local function ensureFuelChestPresentOrWait()
  625.   while true do
  626.     local bx,bz,by,h=pos.x,pos.z,pos.y,heading
  627.     if not goToFuelChestLocation() then
  628.       warn("Fuel-Spot aktuell nicht erreichbar. Stelle sicher, dass +X/+Z frei ist.")
  629.     else
  630.       local present = isInventoryName(safeInspect(turtle.inspect))
  631.       face(3); up(); face(2); fwd(); face(3); fwd()
  632.       returnToWorkSmart(bx, bz, by, h)
  633.       if present then return true end
  634.       local cs=findChestSlot()
  635.       if cs then return placeChestsIfNeeded() end
  636.     end
  637.     statusLine("Warte auf Fuel chest (oder gib Turtle eine Chest)… Q=Quit")
  638.     sleep(2)
  639.     if shouldQuit then return false end
  640.   end
  641. end
  642.  
  643. -- NEU: Striktere Fuel-Funktion mit Slot 1 + Fetch
  644. local function refuelStrictSlot1OrFetch(target)
  645.   turtle.select(SLOT_FUEL)
  646.   local d = turtle.getItemDetail(SLOT_FUEL)
  647.   if d and isFuelLike(d) then
  648.     while turtle.getItemCount(SLOT_FUEL) > 0
  649.       and turtle.getFuelLevel() ~= "unlimited"
  650.       and turtle.getFuelLevel() < target do
  651.       if not turtle.refuel(1) then break end
  652.     end
  653.   end
  654.  
  655.   local slot1Count = turtle.getItemCount(SLOT_FUEL)
  656.   turtle.select(SLOT_MIN)
  657.  
  658.   local fl = turtle.getFuelLevel()
  659.   local needsFetch = (slot1Count == 0)
  660.                   or (fl ~= "unlimited" and type(fl)=="number" and fl < FUEL_BUFFER_MIN)
  661.  
  662.   if needsFetch then
  663.     -- Anker sichern BEVOR wir wegfliegen
  664.     lastSavedPos = { x = pos.x, y = pos.y, z = pos.z }
  665.     lastSavedHeading = heading
  666.    
  667.     -- WICHTIG: State speichern bevor wir zur Chest fliegen
  668.     local currentState = snapshotState()
  669.     saveState(currentState)
  670.     info("State gesichert vor Fuel-Refill")
  671.  
  672.     if not ensureFuelChestPresentOrWait() then
  673.       return smartFailError("Keine Fuel-Chest verfügbar.")
  674.     end
  675.    
  676.     local success = fetchFuelFromRightChest(target)
  677.    
  678.     if not success then
  679.       return smartFailError("Fuel-Refill von der Fuel-Chest fehlgeschlagen.")
  680.     end
  681.    
  682.     -- WICHTIG: Nach erfolgreichem Refill shouldQuit zurücksetzen!
  683.     -- (könnte durch vorherige Fehler gesetzt worden sein)
  684.     if shouldQuit then
  685.       warn("shouldQuit war gesetzt - setze zurück nach erfolgreichem Refill")
  686.       shouldQuit = false
  687.     end
  688.    
  689.     ok(string.format("✅ Zurück an Bauposition: [%d,%d,%d] - Bauen geht weiter!", pos.x, pos.y, pos.z))
  690.   end
  691.  
  692.   return true
  693. end
  694.  
  695. -- NEU: Verbesserte ensureFuelBuffered
  696. local function ensureFuelBuffered()
  697.   local fl = turtle.getFuelLevel()
  698.   if fl == "unlimited" then return true end
  699.   if type(fl)=="number" and fl >= FUEL_BUFFER_MIN then return true end
  700.  
  701.   -- 0 Fuel: NICHT bewegen. Nur Slot 1 nutzen (kein Material-Fuel!).
  702.   if type(fl)=="number" and fl < 1 then
  703.     turtle.select(SLOT_FUEL)
  704.     local d = turtle.getItemDetail(SLOT_FUEL)
  705.     if d and isFuelLike(d) then
  706.       while turtle.getItemCount(SLOT_FUEL) > 0 and turtle.getFuelLevel() < FUEL_TARGET do
  707.         if not turtle.refuel(1) then break end
  708.       end
  709.       turtle.select(SLOT_MIN)
  710.       fl = turtle.getFuelLevel()
  711.       if fl ~= "unlimited" and type(fl)=="number" and fl >= FUEL_BUFFER_MIN then
  712.         return true
  713.       end
  714.     end
  715.     turtle.select(SLOT_MIN)
  716.     return smartFailError("Fuel = 0 und kein zulässiges Fuel in Slot 1. Lege Coal/Charcoal in Slot 1 und starte 'resume'.")
  717.   end
  718.  
  719.   -- Unter Minimum -> strikt Slot 1 oder Fuel-Chest holen
  720.   return refuelStrictSlot1OrFetch(FUEL_TARGET)
  721. end
  722.  
  723. local function compactBuildSlots()
  724.   local mats={}
  725.   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
  726.   for _,slots in pairs(mats) do
  727.     for i=1,#slots do
  728.       local dst=slots[i]; local di=turtle.getItemDetail(dst)
  729.       if di and di.count<64 then
  730.         for j=i+1,#slots do local src=slots[j]
  731.           if turtle.getItemCount(src)>0 then turtle.select(src); turtle.transferTo(dst); if turtle.getItemCount(src)==0 then break end end
  732.         end
  733.       end
  734.     end
  735.   end
  736.   turtle.select(SLOT_MIN)
  737. end
  738.  
  739. local function countPlaceables() local n=0 for s=SLOT_MIN,SLOT_MAX do n=n+turtle.getItemCount(s) end return n end
  740.  
  741. -- NEU: Material-Refill ohne "unter mir" Check - nur Basis
  742. local function ensureSomeBlocks()
  743.   compactBuildSlots()
  744.   if countPlaceables()>0 then return true end
  745.  
  746.   info("Kein Material gefunden – fliege zur Basis für Nachschub ...")
  747.   local bx,bz,by,h = pos.x,pos.z,pos.y,heading
  748.  
  749.   -- WICHTIG: State speichern bevor wir zur Basis fliegen
  750.   lastSavedPos = { x = pos.x, y = pos.y, z = pos.z }
  751.   lastSavedHeading = heading
  752.   local currentState = snapshotState()
  753.   saveState(currentState)
  754.   info("State gesichert vor Material-Refill")
  755.  
  756.   if not goToBaseSmart() then
  757.     return smartFailError("Rückkehr zur Basis für Nachschub fehlgeschlagen")
  758.   end
  759.  
  760.   local tries=0
  761.   while not isInventoryName(safeInspect(turtle.inspectDown)) do
  762.     local n=safeInspect(turtle.inspectDown)
  763.     if n and n~="minecraft:air" then
  764.       info("Entferne Blockierung: "..n)
  765.       turtle.digDown()
  766.     else
  767.       if tries > 0 and tries % 5 == 0 then
  768.         warn("Warte auf Material-Chest unter Turtle... ("..tries.."/"..CHEST_WAIT_RETRIES..")")
  769.       end
  770.       sleep(0.5)
  771.     end
  772.    
  773.     tries=tries+1
  774.     if tries > CHEST_WAIT_RETRIES then
  775.       errc("Material chest fehlt nach "..tries.." Sekunden!")
  776.       errc("Platziere eine Chest/Barrel DIREKT UNTER der Turtle (0,-1,0)!")
  777.       return smartFailError("Material chest unter Basis fehlt")
  778.     end
  779.   end
  780.   ok("Material chest gefunden!")
  781.  
  782.   -- Versuche Material zu holen - WARTE wenn leer!
  783.   local refillAttempts = 0
  784.   while countPlaceables() == 0 do
  785.     for s=SLOT_MIN,SLOT_MAX do
  786.       turtle.select(s)
  787.       local need=64-turtle.getItemCount(s)
  788.       if need>0 then turtle.suckDown(need) end
  789.     end
  790.     compactBuildSlots()
  791.    
  792.     if countPlaceables() > 0 then
  793.       break
  794.     end
  795.    
  796.     refillAttempts = refillAttempts + 1
  797.     if refillAttempts % 3 == 1 then
  798.       warn("============================================")
  799.       warn("MATERIAL-CHEST IST LEER!")
  800.       warn("Bitte fülle Baumaterial in die Material-Chest:")
  801.       warn("Position: (0,-1,0) - DIREKT UNTER der Turtle")
  802.       warn("Warte... (Versuch "..refillAttempts..") - Q zum Abbrechen")
  803.       warn("============================================")
  804.     end
  805.    
  806.     sleep(2)
  807.    
  808.     if shouldQuit then
  809.       returnToWorkSmart(bx, bz, by, h)
  810.       return smartFailError("User hat Material-Refill abgebrochen (Q)")
  811.     end
  812.   end
  813.  
  814.   ok(string.format("Material geholt! %d Blöcke verfügbar", countPlaceables()))
  815.  
  816.   if not returnToWorkSmart(bx, bz, by, h) then
  817.     return smartFailError("Rückkehr zur Bauebene nach Nachschub fehlgeschlagen")
  818.   end
  819.  
  820.   -- WICHTIG: Nach erfolgreichem Refill shouldQuit zurücksetzen!
  821.   if shouldQuit then
  822.     warn("shouldQuit war gesetzt - setze zurück nach erfolgreichem Material-Refill")
  823.     shouldQuit = false
  824.   end
  825.  
  826.   turtle.select(SLOT_MIN)
  827.   ok(string.format("✅ Zurück an Bauposition: [%d,%d,%d] - Bauen geht weiter!", pos.x, pos.y, pos.z))
  828.   return true
  829. end
  830.  
  831. ------------------------------------------------------------
  832. -- RANDOM SLOT SELECTION & PLACE WITH RETRY ----------------
  833. local function selectPlaceSlot()
  834.   local available={}
  835.   for s=SLOT_MIN,SLOT_MAX do if turtle.getItemCount(s)>0 then table.insert(available,s) end end
  836.   if #available==0 then return false end
  837.   local pick=available[math.random(1,#available)]
  838.   turtle.select(pick)
  839.   return true
  840. end
  841.  
  842. local function placeDownOne()
  843.   if turtle.detectDown() then return true end
  844.   if not selectPlaceSlot() then return smartFailError("Keine Baublöcke mehr verfügbar!") end
  845.   if turtle.placeDown() then blocksPlaced=blocksPlaced+1; return true end
  846.   if turtle.detectDown() then turtle.digDown(); sleep(0.05); if turtle.placeDown() then blocksPlaced=blocksPlaced+1; return true end end
  847.   return smartFailError("Konnte Block nicht platzieren!")
  848. end
  849.  
  850. ------------------------------------------------------------
  851. -- SHAPE MASKS ---------------------------------------------
  852. local function inCircle(x,z,rad) return (x*x + z*z) <= (rad*rad + 1e-6) end
  853. 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
  854.  
  855. local function onSphereShell(ix,iy,iz)
  856.   local x=ix-CX; local y=(iy-CY)/YSCALE; local z=iz-CZ
  857.   local rr=math.sqrt(x*x+y*y+z*z); local bandOuter=r+0.5; local bandInner=math.max(0, r-(THICK-0.5))
  858.   return (rr<=bandOuter) and (rr>=bandInner)
  859. end
  860. local function onTorusShell(ix,iy,iz)
  861.   local x=ix-CX; local y=(iy-CY)/YSCALE; local z=iz-CZ
  862.   local rho=math.sqrt(x*x+z*z); local tube=math.sqrt((rho-R)*(rho-R)+y*y)
  863.   local bandOuter=r+0.5; local bandInner=math.max(0, r-(THICK-0.5))
  864.   return (tube<=bandOuter) and (tube>=bandInner)
  865. end
  866. local function onCylinderShell(ix,iy,iz)
  867.   local x=ix-CX; local z=iz-CZ
  868.   if ringShell(x,z,r,THICK) then return true end
  869.   if CYL_CAPS and (iy==0 or iy==YH-1) then return inCircle(x,z,r+0.5) end
  870.   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
  871.   return false
  872. end
  873. local function onShell(ix,iy,iz)
  874.   if SHAPE=="sphere" then return onSphereShell(ix,iy,iz)
  875.   elseif SHAPE=="torus" then return onTorusShell(ix,iy,iz)
  876.   else return onCylinderShell(ix,iy,iz) end
  877. end
  878. 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
  879.  
  880. ------------------------------------------------------------
  881. -- KEYS (P/Q/S) --------------------------------------------
  882. local function keyListener()
  883.   while true do
  884.     local _, key = os.pullEvent("key")
  885.     if key == keys.p then
  886.       paused = not paused
  887.       if paused then info("PAUSED (P = weiter)") else info("RESUMED") end
  888.     elseif key == keys.q then
  889.       shouldQuit = true
  890.       warn("Save & Quit…")
  891.       saveState(snapshotState())
  892.       return
  893.     elseif key == keys.s then
  894.       info(string.format("Status: Layer %d/%d | Pos [%d,%d,%d] | Fuel:%s | Inv:%d",
  895.         curLayer, YH-1, pos.x,pos.y,pos.z, tostring(turtle.getFuelLevel()), countPlaceables()))
  896.     end
  897.   end
  898. end
  899. local function runWithKeys(fn) parallel.waitForAny(keyListener, function() fn() end) end
  900.  
  901. ------------------------------------------------------------
  902. -- FAST BUILD ----------------------------------------------
  903. local function hopToTarget(x, z, yTarget)
  904.   if FAST_OVERHEAD_TRAVEL then
  905.     local travelY = YH + OVERHEAD_MARGIN
  906.     if not moveTo(pos.x,pos.z,travelY,false) then return false end
  907.     if not moveTo(x, z, travelY, false) then return false end
  908.     if not moveTo(x, z, yTarget, false) then return false end
  909.     return true
  910.   else
  911.     return moveTo(x, z, yTarget, FAST_ALLOW_DIG)
  912.   end
  913. end
  914.  
  915. local function planLayer(y)
  916.   local plan, Zs = {}, {}
  917.   for z=0,XZ-1 do
  918.     local row={}
  919.     local forward=(z%2==0)
  920.     if forward then for x=0,XZ-1 do if onShell(x,y,z) then table.insert(row,x) end end
  921.     else for x=XZ-1,0,-1 do if onShell(x,y,z) then table.insert(row,x) end end end
  922.     if #row>0 then plan[z]=row; table.insert(Zs,z) end
  923.   end
  924.   return plan, Zs
  925. end
  926.  
  927. local function buildFromFast(startY, startZ, startX)
  928.   local totalPlan=totalBlocksPlanned()
  929.   local t0=os.epoch("utc")
  930.  
  931.   for y=startY, YH-1 do
  932.     if shouldQuit then saveState(snapshotState()); return end
  933.     curLayer=y
  934.    
  935.     -- Update last saved position BEFORE layer
  936.     lastSavedPos = {x=pos.x, y=pos.y, z=pos.z}
  937.     lastSavedHeading = heading
  938.    
  939.     if not ensureFuelBuffered() then return end
  940.     if shouldQuit then return end
  941.     if not ensureSomeBlocks() then return end
  942.     if shouldQuit then return end
  943.    
  944.     local layerPlan, sortedZ = planLayer(y)
  945.     local z0idx=1; if y==startY then for i,zz in ipairs(sortedZ) do if zz>=startZ then z0idx=i; break end end end
  946.    
  947.     for zi=z0idx, #sortedZ do
  948.       if shouldQuit then saveState(snapshotState()); return end
  949.       local z=sortedZ[zi]; curZ=z; local row=layerPlan[z]
  950.      
  951.       local x0idx=1; if y==startY and z==startZ then
  952.         local forward=(z%2==0)
  953.         for j,x in ipairs(row) do if (forward and x>=startX) or ((not forward) and x<=startX) then x0idx=j; break end end
  954.       end
  955.      
  956.       for j=x0idx, #row do
  957.         if shouldQuit then saveState(snapshotState()); return end
  958.         if not pauseGate() then saveState(snapshotState()); return end
  959.        
  960.         local x=row[j]; curX=x
  961.        
  962.         -- Fuel check basierend auf BLOCKS statt steps
  963.         local currentFuel = turtle.getFuelLevel()
  964.         if currentFuel ~= "unlimited" and currentFuel < FUEL_BUFFER_MIN then
  965.           if not ensureFuelBuffered() then return end
  966.           if shouldQuit then return end
  967.         end
  968.        
  969.         if not hopToTarget(x,z,y+1) then return end
  970.         if shouldQuit then return end
  971.        
  972.         if countPlaceables()==0 then
  973.           if not ensureSomeBlocks() then return end
  974.           if shouldQuit then return end
  975.           if not hopToTarget(x,z,y+1) then return end
  976.           if shouldQuit then return end
  977.         end
  978.        
  979.         if not placeDownOne() then return end
  980.         if shouldQuit then return end
  981.  
  982.         if (blocksPlaced % SAVE_INTERVAL)==0 then
  983.           lastSavedPos = {x=pos.x, y=pos.y, z=pos.z}
  984.           lastSavedHeading = heading
  985.           saveState(snapshotState())
  986.         end
  987.  
  988.         local elapsed=math.max(0.001,(os.epoch("utc")-t0)/1000)
  989.         local rem=math.max(0,totalPlan-blocksPlaced)
  990.         local rate=(blocksPlaced>0) and (blocksPlaced/elapsed) or 0
  991.         if rate>0 then statusLine(("FAST | Layer %d/%d | (%d,%d) | %d/%d | ETA: %.0fs"):format(y,YH-1,x,z,blocksPlaced,totalPlan,rem/rate))
  992.         else statusLine(("FAST | Layer %d/%d | (%d,%d) | %d/%d | ETA: --"):format(y,YH-1,x,z,blocksPlaced,totalPlan)) end
  993.       end
  994.     end
  995.   end
  996.  
  997.   ok("Build complete!")
  998.   moveTo(0,0,0,false); face(0); clearState()
  999. end
  1000.  
  1001. ------------------------------------------------------------
  1002. -- SAFE BUILD ----------------------------------------------
  1003. local function buildFromSafe(startY, startZ, startX)
  1004.   local totalPlan=totalBlocksPlanned()
  1005.   local t0=os.epoch("utc")
  1006.  
  1007.   for y=startY, YH-1 do
  1008.     if shouldQuit then saveState(snapshotState()); return end
  1009.     curLayer=y
  1010.    
  1011.     -- Update last saved position
  1012.     lastSavedPos = {x=pos.x, y=pos.y, z=pos.z}
  1013.     lastSavedHeading = heading
  1014.    
  1015.     if not ensureFuelBuffered() then return end
  1016.     if shouldQuit then return end
  1017.     if not ensureSomeBlocks() then return end
  1018.     if shouldQuit then return end
  1019.    
  1020.     local z0=(y==startY) and startZ or 0
  1021.     for z=z0, XZ-1 do
  1022.       if shouldQuit then saveState(snapshotState()); return end
  1023.       curZ=z
  1024.       if not pauseGate() then saveState(snapshotState()); return end
  1025.      
  1026.       local forward=(z%2==0)
  1027.       local xs,xe,step = (forward and 0 or XZ-1), (forward and XZ-1 or 0), (forward and 1 or -1)
  1028.       local x0 = (y==startY and z==z0) and startX or xs
  1029.      
  1030.       if not moveTo(x0, z, y+1, true) then return end
  1031.       if shouldQuit then return end
  1032.      
  1033.       face(forward and 0 or 2)
  1034.       local x=x0
  1035.      
  1036.       while true do
  1037.         if shouldQuit then saveState(snapshotState()); return end
  1038.         curX=x
  1039.        
  1040.         if onShell(x,y,z) then
  1041.           -- Fuel check
  1042.           local currentFuel = turtle.getFuelLevel()
  1043.           if currentFuel ~= "unlimited" and currentFuel < FUEL_BUFFER_MIN then
  1044.             if not ensureFuelBuffered() then return end
  1045.             if shouldQuit then return end
  1046.           end
  1047.          
  1048.           if countPlaceables()==0 then
  1049.             if not ensureSomeBlocks() then return end
  1050.             if shouldQuit then return end
  1051.             if not moveTo(x,z,y+1,true) then return end
  1052.             if shouldQuit then return end
  1053.             face(forward and 0 or 2)
  1054.           end
  1055.          
  1056.           if not placeDownOne() then return end
  1057.           if shouldQuit then return end
  1058.          
  1059.           if (blocksPlaced % SAVE_INTERVAL)==0 then
  1060.             lastSavedPos = {x=pos.x, y=pos.y, z=pos.z}
  1061.             lastSavedHeading = heading
  1062.             saveState(snapshotState())
  1063.           end
  1064.         end
  1065.  
  1066.         local elapsed=math.max(0.001,(os.epoch("utc")-t0)/1000)
  1067.         local rem=math.max(0,totalPlan-blocksPlaced)
  1068.         local rate=(blocksPlaced>0) and (blocksPlaced/elapsed) or 0
  1069.         if rate>0 then statusLine(("SAFE | Layer %d/%d | Z:%d | %d/%d | ETA: %.0fs"):format(y,YH-1,z,blocksPlaced,totalPlan,rem/rate))
  1070.         else statusLine(("SAFE | Layer %d/%d | Z:%d | %d/%d | ETA: --"):format(y,YH-1,z,blocksPlaced,totalPlan)) end
  1071.  
  1072.         if x==xe then break end
  1073.         if not fwd() then
  1074.           if turtle.detect() then turtle.dig() end
  1075.           if not fwd() then return smartFailError("Blocked on row @ Z="..z) end
  1076.         end
  1077.         if shouldQuit then return end
  1078.         x = x + step
  1079.       end
  1080.     end
  1081.   end
  1082.  
  1083.   ok("Build complete!")
  1084.   moveTo(0,0,0,true); face(0); clearState()
  1085. end
  1086.  
  1087. ------------------------------------------------------------
  1088. -- CONFIG / PLAN / MENU ------------------------------------
  1089. local function pickShape(cur)
  1090.   term.clear(); term.setCursorPos(1,1)
  1091.   title("Shape auswählen")
  1092.   print("1) Torus")
  1093.   print("2) Sphere (Kugel/Ellipsoid)")
  1094.   print("3) Cylinder (Hohl)")
  1095.   print("Enter = behalten ("..cur..")")
  1096.   write("> ")
  1097.   local s = read()
  1098.   if s=="" then return cur end
  1099.   if s=="1" then return "torus" elseif s=="2" then return "sphere" elseif s=="3" then return "cylinder" end
  1100.   warn("Ungültig – behalte "..cur); return cur
  1101. end
  1102.  
  1103. local function configureShapeAndSize()
  1104.   SHAPE = pickShape(SHAPE)
  1105.   if SHAPE=="torus" then
  1106.     R      = promptNumber("Großer Radius R", R, 1)
  1107.     r      = promptNumber("Rohr-Radius r",   r, 1)
  1108.     THICK  = promptNumber("Wanddicke",       THICK, 1)
  1109.     while true do
  1110.       local input = prompt("Y-Skalierung (0.1-10.0, 1.0=kreisrund)", tostring(YSCALE))
  1111.       YSCALE = tonumber(input) or YSCALE
  1112.       if YSCALE > 0 and YSCALE <= 10 then break end
  1113.       warn("YSCALE muss zwischen 0.1 und 10.0 liegen!")
  1114.     end
  1115.   elseif SHAPE=="sphere" then
  1116.     r      = promptNumber("Radius r",        r, 1)
  1117.     THICK  = promptNumber("Wanddicke",       THICK, 1)
  1118.     while true do
  1119.       local input = prompt("Y-Skalierung (0.1-10.0, 1.0=Kugel)", tostring(YSCALE))
  1120.       YSCALE = tonumber(input) or YSCALE
  1121.       if YSCALE > 0 and YSCALE <= 10 then break end
  1122.       warn("YSCALE muss zwischen 0.1 und 10.0 liegen!")
  1123.     end
  1124.   else
  1125.     r      = promptNumber("Radius r",        r, 1)
  1126.     CYL_H  = promptNumber("Höhe (Y)",        CYL_H, 1)
  1127.     THICK  = promptNumber("Wanddicke",       THICK, 1)
  1128.     CYL_FLOOR_STEP = promptNumber("Zwischenböden (0=keine)", CYL_FLOOR_STEP or 0, 0)
  1129.     CYL_CAPS = promptBool("Deckel/Boden voll schließen?", CYL_CAPS)
  1130.   end
  1131.  
  1132.   -- clamp
  1133.   R, r, THICK, CYL_H, CYL_FLOOR_STEP = round(R), round(r), round(THICK), round(CYL_H), math.max(0, round(CYL_FLOOR_STEP))
  1134.   if THICK>2*r then
  1135.     warn(string.format("THICK=%d ist zu groß für r=%d! Setze auf Maximum: %d", THICK, r, 2*r))
  1136.     THICK=2*r
  1137.   end
  1138.   recomputeDerived()
  1139.  
  1140.   -- Größen-Check
  1141.   if XZ > MAX_STRUCTURE_SIZE or YH > MAX_STRUCTURE_SIZE then
  1142.     errc(string.format("Struktur zu groß! XZ=%d, YH=%d (Max: %d)", XZ, YH, MAX_STRUCTURE_SIZE))
  1143.     pressEnter(); return
  1144.   end
  1145.  
  1146.   -- freeze plan
  1147.   local n=totalBlocksPlanned()
  1148.   title("Plan (eingefroren)")
  1149.   print(string.format("Shape: %s", SHAPE))
  1150.   if SHAPE=="torus" then print(string.format("R=%d, r=%d, THICK=%d, YSCALE=%.2f", R,r,THICK,YSCALE))
  1151.   elseif SHAPE=="sphere" then print(string.format("r=%d, THICK=%d, YSCALE=%.2f", r,THICK,YSCALE))
  1152.   else print(string.format("r=%d, height=%d, THICK=%d, floors=%d, caps=%s", r,CYL_H,THICK,CYL_FLOOR_STEP,tostring(CYL_CAPS))) end
  1153.   print(string.format("Bounds: XZ=%d x Y=%d", XZ, YH))
  1154.   print(string.format("Planned blocks: %d", n))
  1155.  
  1156.   -- Material-Schätzung
  1157.   cprint("\n[Material benötigt]", colors.yellow)
  1158.   print(string.format("  Blöcke: %d", n))
  1159.   print(string.format("  Stacks: ~%d", math.ceil(n/64)))
  1160.   print(string.format("  Shulker: ~%d", math.ceil(n/(27*64))))
  1161.   local approxMoves = n * 2 * (BUILD_MODE == "fast" and 3 or 1.5)
  1162.   print(string.format("  Fuel (ca.): %d", math.ceil(approxMoves)))
  1163.   print(string.format("  Coal: ~%d", math.ceil(approxMoves/80)))
  1164.  
  1165.   if n > 50000 then
  1166.     warn(string.format("WARNUNG: %d Blöcke! Das kann STUNDEN dauern!", n))
  1167.     local estimatedMinutes = math.ceil(n / 20)
  1168.     warn(string.format("Geschätzte Zeit: %.1f Stunden", estimatedMinutes/60))
  1169.   end
  1170.  
  1171.   plannedConfig = {shape=SHAPE,R=R,r=r,THICK=THICK,YSCALE=YSCALE,CYL_H=CYL_H,CYL_FLOOR_STEP=CYL_FLOOR_STEP,CYL_CAPS=CYL_CAPS,BUILD_MODE=BUILD_MODE}
  1172.   pressEnter()
  1173. end
  1174.  
  1175. local function showPlan()
  1176.   recomputeDerived()
  1177.   local n=totalBlocksPlanned()
  1178.   title("Plan")
  1179.   print("Build Mode: "..BUILD_MODE)
  1180.   print(string.format("Shape: %s", SHAPE))
  1181.   if SHAPE=="torus" then print(string.format("R=%d, r=%d, THICK=%d, YSCALE=%.2f", R,r,THICK,YSCALE))
  1182.   elseif SHAPE=="sphere" then print(string.format("r=%d, THICK=%d, YSCALE=%.2f", r,THICK,YSCALE))
  1183.   else print(string.format("r=%d, height=%d, THICK=%d, floors=%d, caps=%s", r,CYL_H,THICK,CYL_FLOOR_STEP,tostring(CYL_CAPS))) end
  1184.   print(string.format("Bounds: XZ=%d x Y=%d", XZ, YH))
  1185.   print(string.format("Planned blocks: %d", n))
  1186.  
  1187.   -- Material-Schätzung
  1188.   cprint("\n[Material benötigt]", colors.yellow)
  1189.   print(string.format("  Blöcke: %d", n))
  1190.   print(string.format("  Stacks: ~%d", math.ceil(n/64)))
  1191.   print(string.format("  Shulker: ~%d", math.ceil(n/(27*64))))
  1192.   local approxMoves = n * 2 * (BUILD_MODE == "fast" and 3 or 1.5)
  1193.   print(string.format("  Fuel (ca.): %d", math.ceil(approxMoves)))
  1194.   print(string.format("  Coal: ~%d", math.ceil(approxMoves/80)))
  1195. end
  1196.  
  1197. local function buildStructure()
  1198.   local current = {shape=SHAPE,R=R,r=r,THICK=THICK,YSCALE=YSCALE,CYL_H=CYL_H,CYL_FLOOR_STEP=CYL_FLOOR_STEP,CYL_CAPS=CYL_CAPS,BUILD_MODE=BUILD_MODE}
  1199.   if plannedConfig and not sameConfig(plannedConfig,current) then
  1200.     warn("Konfiguration weicht vom zuletzt geplanten Layout ab.")
  1201.     write("Trotzdem mit aktueller Konfiguration bauen? (y/n): ")
  1202.     if (read():lower()~="y") then info("Abgebrochen."); return end
  1203.   end
  1204.  
  1205.   -- NEU: Erst Fuel, dann Chests
  1206.   if not ensureFuelBuffered() then return end
  1207.   if shouldQuit then return end
  1208.   if not placeChestsIfNeeded() then return end
  1209.   if shouldQuit then return end
  1210.   if not ensureSomeBlocks() then return end
  1211.   if shouldQuit then return end
  1212.  
  1213.   clearState()
  1214.   curLayer,curZ,curX=0,0,0
  1215.   blocksPlaced=0
  1216.   lastSavedPos = {x=0,y=0,z=0}
  1217.   lastSavedHeading = 0
  1218.  
  1219.   if BUILD_MODE=="fast" then
  1220.     info("Starting build in FAST mode (Overhead="..tostring(FAST_OVERHEAD_TRAVEL)..")"); pressEnter()
  1221.     buildFromFast(0,0,0)
  1222.   else
  1223.     info("Starting build in SAFE mode"); pressEnter()
  1224.     buildFromSafe(0,0,0)
  1225.   end
  1226. end
  1227.  
  1228. local function resumeBuild()
  1229.   local st=loadState()
  1230.   if not st then errc("Kein Save gefunden."); return end
  1231.  
  1232.   -- Version Check
  1233.   if st.version ~= STATE_VERSION then
  1234.     warn(string.format("Save-Version: %s, Script-Version: %s",
  1235.          tostring(st.version or "1.0"), STATE_VERSION))
  1236.     if not promptBool("Trotzdem laden? (könnte inkompatibel sein)", false) then
  1237.       return
  1238.     end
  1239.   end
  1240.  
  1241.   SHAPE=st.shape or SHAPE; R=st.R or R; r=st.r or r; THICK=st.THICK or THICK; YSCALE=st.YSCALE or YSCALE
  1242.   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
  1243.   CYL_CAPS=(st.CYL_CAPS~=nil) and st.CYL_CAPS or CYL_CAPS
  1244.   BUILD_MODE=st.BUILD_MODE or BUILD_MODE
  1245.   recomputeDerived()
  1246.  
  1247.   curLayer=st.layer or 0; curZ=st.curZ or 0; curX=st.curX or 0
  1248.   pos=st.pos or {x=0,y=0,z=0}
  1249.   heading=st.heading or 0
  1250.   blocksPlaced=st.blocksPlaced or 0
  1251.  
  1252.   lastSavedPos = {x=pos.x, y=pos.y, z=pos.z}
  1253.   lastSavedHeading = heading
  1254.  
  1255.   title("RESUME BUILD")
  1256.   info(("Resume: Layer %d/%d, Cell (%d,%d) | Mode: %s"):format(curLayer,YH-1,curX,curZ,BUILD_MODE))
  1257.   info(string.format("Position: [%d,%d,%d] Richtung:%d", pos.x,pos.y,pos.z,heading))
  1258.   info(string.format("Bereits platziert: %d Blöcke", blocksPlaced))
  1259.   cprint("\n[WICHTIG]", colors.yellow)
  1260.   cprint("Die Turtle sollte sich noch an der gespeicherten Position befinden!", colors.orange)
  1261.   cprint("Falls nicht: Bewege sie manuell dorthin oder der Build wird fehlschlagen.", colors.orange)
  1262.   pressEnter("Bereit zum Fortsetzen? Enter...")
  1263.  
  1264.   if BUILD_MODE=="fast" then buildFromFast(curLayer,curZ,curX) else buildFromSafe(curLayer,curZ,curX) end
  1265. end
  1266.  
  1267. local function toggleAdvanced()
  1268.   title("Advanced Optionen")
  1269.   print("1) Toggle Overhead-Travel (FAST): "..tostring(FAST_OVERHEAD_TRAVEL))
  1270.   print("2) Toggle FAST_ALLOW_DIG: "..tostring(FAST_ALLOW_DIG))
  1271.   print("Enter = zurück")
  1272.   write("> ")
  1273.   local s=read()
  1274.   if s=="1" then FAST_OVERHEAD_TRAVEL = not FAST_OVERHEAD_TRAVEL; ok("Overhead-Travel -> "..tostring(FAST_OVERHEAD_TRAVEL)); sleep(0.5)
  1275.   elseif s=="2" then FAST_ALLOW_DIG = not FAST_ALLOW_DIG; ok("FAST_ALLOW_DIG -> "..tostring(FAST_ALLOW_DIG)); sleep(0.5) end
  1276. end
  1277.  
  1278. -- NEU: Return to Saved Position (für Chunk-Unload/Reset)
  1279. local function returnToSavedPositionManual()
  1280.   title("Return to Saved Position")
  1281.  
  1282.   -- State-File laden
  1283.   local st = loadState()
  1284.   if not st then
  1285.     errc("❌ Datei 'builder_state.txt' nicht gefunden!")
  1286.     pressEnter()
  1287.     return
  1288.   end
  1289.  
  1290.   if not st.pos then
  1291.     errc("❌ Ungültiger Dateiinhalt oder pos fehlt.")
  1292.     pressEnter()
  1293.     return
  1294.   end
  1295.  
  1296.   -- Zielposition extrahieren
  1297.   local target = {
  1298.     x = st.pos.x,
  1299.     y = st.pos.y,
  1300.     z = st.pos.z,
  1301.     heading = st.heading or 0
  1302.   }
  1303.  
  1304.   info(string.format("📍 Zielposition: [%d, %d, %d] Richtung: %d", target.x, target.y, target.z, target.heading))
  1305.   cprint("\n[WICHTIG]", colors.yellow)
  1306.   cprint("Stelle sicher, dass die Turtle bei (0,0,0) steht!", colors.orange)
  1307.   cprint("Richtung: Osten (+X)", colors.orange)
  1308.   pressEnter("\nBereit? Enter...")
  1309.  
  1310.   -- Position & Richtung zurücksetzen
  1311.   pos = { x = 0, y = 0, z = 0 }
  1312.   heading = 0
  1313.  
  1314.   -- Helper-Funktionen für Navigation
  1315.   local function navForward()
  1316.     if turtle.forward() then
  1317.       if heading == 0 then pos.x = pos.x + 1
  1318.       elseif heading == 1 then pos.z = pos.z + 1
  1319.       elseif heading == 2 then pos.x = pos.x - 1
  1320.       elseif heading == 3 then pos.z = pos.z - 1
  1321.       end
  1322.       return true
  1323.     else
  1324.       warn(string.format("⚠️ Blockiert bei: [%d, %d, %d]", pos.x, pos.y, pos.z))
  1325.       return false
  1326.     end
  1327.   end
  1328.  
  1329.   local function navUpTo(y)
  1330.     while pos.y < y do
  1331.       if turtle.up() then
  1332.         pos.y = pos.y + 1
  1333.       else
  1334.         errc("⚠️ Blockiert beim Hochgehen")
  1335.         return false
  1336.       end
  1337.     end
  1338.     while pos.y > y do
  1339.       if turtle.down() then
  1340.         pos.y = pos.y - 1
  1341.       else
  1342.         errc("⚠️ Blockiert beim Runtergehen")
  1343.         return false
  1344.       end
  1345.     end
  1346.     return true
  1347.   end
  1348.  
  1349.   local function navToZ(z)
  1350.     if pos.z < z then
  1351.       face(1)  -- +Z
  1352.       while pos.z < z do
  1353.         if not navForward() then return false end
  1354.       end
  1355.     elseif pos.z > z then
  1356.       face(3)  -- -Z
  1357.       while pos.z > z do
  1358.         if not navForward() then return false end
  1359.       end
  1360.     end
  1361.     return true
  1362.   end
  1363.  
  1364.   local function navToX(x)
  1365.     if pos.x < x then
  1366.       face(0)  -- +X
  1367.       while pos.x < x do
  1368.         if not navForward() then return false end
  1369.       end
  1370.     elseif pos.x > x then
  1371.       face(2)  -- -X
  1372.       while pos.x > x do
  1373.         if not navForward() then return false end
  1374.       end
  1375.     end
  1376.     return true
  1377.   end
  1378.  
  1379.   -- Navigation ausführen: Y -> Z -> X -> Richtung
  1380.   info("Starte Navigation...")
  1381.  
  1382.   if not navUpTo(target.y) then
  1383.     errc("Navigation fehlgeschlagen!")
  1384.     pressEnter()
  1385.     return
  1386.   end
  1387.  
  1388.   if not navToZ(target.z) then
  1389.     errc("Navigation fehlgeschlagen!")
  1390.     pressEnter()
  1391.     return
  1392.   end
  1393.  
  1394.   if not navToX(target.x) then
  1395.     errc("Navigation fehlgeschlagen!")
  1396.     pressEnter()
  1397.     return
  1398.   end
  1399.  
  1400.   face(target.heading)
  1401.  
  1402.   ok(string.format("Psition reached! Position: [%d, %d, %d]", pos.x, pos.y, pos.z))
  1403.  
  1404.   -- NEU: State vollständig wiederherstellen
  1405.   SHAPE=st.shape or SHAPE
  1406.   R=st.R or R
  1407.   r=st.r or r
  1408.   THICK=st.THICK or THICK
  1409.   YSCALE=st.YSCALE or YSCALE
  1410.   CYL_H=st.CYL_H or CYL_H
  1411.   CYL_FLOOR_STEP=(st.CYL_FLOOR_STEP~=nil) and st.CYL_FLOOR_STEP or CYL_FLOOR_STEP
  1412.   CYL_CAPS=(st.CYL_CAPS~=nil) and st.CYL_CAPS or CYL_CAPS
  1413.   BUILD_MODE=st.BUILD_MODE or BUILD_MODE
  1414.   recomputeDerived()
  1415.  
  1416.   curLayer=st.layer or 0
  1417.   curZ=st.curZ or 0
  1418.   curX=st.curX or 0
  1419.   blocksPlaced=st.blocksPlaced or 0
  1420.  
  1421.   lastSavedPos = {x=pos.x, y=pos.y, z=pos.z}
  1422.   lastSavedHeading = heading
  1423.  
  1424.   info(string.format("State wiederhergestellt: Layer %d/%d, Cell (%d,%d)", curLayer, YH-1, curX, curZ))
  1425.   info("Bauen wird automatisch fortgesetzt...")
  1426.   sleep(2)
  1427.  
  1428.   -- NEU: Automatisch weiterbauen!
  1429.   if BUILD_MODE=="fast" then
  1430.     buildFromFast(curLayer,curZ,curX)
  1431.   else
  1432.     buildFromSafe(curLayer,curZ,curX)
  1433.   end
  1434. end
  1435.  
  1436. local function showMenu()
  1437.   while true do
  1438.     term.clear(); term.setCursorPos(1,1)
  1439.     title("Builder Menu ("..BUILD_MODE.." mode)")
  1440.     print("1) Show plan")
  1441.     print("2) Start build")
  1442.     print("3) Resume")
  1443.     print("4) Reset Save")
  1444.     print("5) Toggle Mode (aktuell: "..BUILD_MODE..")")
  1445.     print("C) Configure Shape/Size")
  1446.     print("A) Advanced (Overhead/Dig)")
  1447.     print("R) Return to saved position (auto-resume)")
  1448.     print("Q) Quit")
  1449.     write("> ")
  1450.     local c=(read() or ""):lower()
  1451.     if c=="1" then showPlan(); pressEnter()
  1452.     elseif c=="2" then runWithKeys(buildStructure); break
  1453.     elseif c=="3" then runWithKeys(resumeBuild); break
  1454.     elseif c=="4" then clearState(); ok("Save gelöscht."); pressEnter()
  1455.     elseif c=="5" then BUILD_MODE=(BUILD_MODE=="fast") and "safe" or "fast"; ok("Mode -> "..BUILD_MODE); sleep(1)
  1456.     elseif c=="c" then configureShapeAndSize()
  1457.     elseif c=="a" then toggleAdvanced()
  1458.     elseif c=="r" then runWithKeys(returnToSavedPositionManual); break
  1459.     elseif c=="q" then break end
  1460.   end
  1461. end
  1462.  
  1463. ------------------------------------------------------------
  1464. -- MAIN -----------------------------------------------------
  1465. math.randomseed(os.epoch("utc"))
  1466. if ACTION=="plan" then showPlan(); pressEnter()
  1467. elseif ACTION=="build" or ACTION=="place" then runWithKeys(buildStructure)
  1468. elseif ACTION=="resume" then runWithKeys(resumeBuild)
  1469. else showMenu()
  1470. end
Add Comment
Please, Sign In to add comment