Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- builder_pro_failsafe.lua (2025 PRO + SMART FAILSAFE - UPDATED)
- -- Shapes: Torus | Sphere | Cylinder (hollow)
- -- Modes: Safe + Fast (Overhead optional) | Random Slot Placement | Auto-Rounding
- -- SMART ERROR RECOVERY: Turtle kehrt bei Fehler zum letzten Save zurück!
- -- Start: Turtle an SW-Unterkante des Volumens, Blick nach Osten (+X)
- -- Chests: Material (0,-1,0) | Fuel (+1,-1,+1)
- ------------------------------------------------------------
- -- CONFIG ---------------------------------------------------
- local BUILD_MODE = "safe" -- "fast" | "safe"
- local FAST_ALLOW_DIG = false -- FAST: Dig beim Bewegen?
- local FAST_OVERHEAD_TRAVEL = true -- FAST: immer über Volumen hoppen
- local OVERHEAD_MARGIN = 2 -- Überhöhe für Overhead-Hop (YH + margin)
- local SLOT_FUEL = 1
- local SLOT_MIN, SLOT_MAX = 2, 16
- local FUEL_BUFFER_MIN, FUEL_TARGET = 400, 2000
- local STATE_FILE = "builder_state.txt"
- local SAVE_INTERVAL = 1
- local FUEL_CHECK_INTERVAL = 64
- -- NEU: Zusätzliche Sicherheits-Konstanten
- local STATE_VERSION = "2.0"
- local SUCK_AMOUNT = 64
- local FUEL_FETCH_RETRIES = 16
- local CHEST_WAIT_RETRIES = 32
- local MAX_STRUCTURE_SIZE = 256
- local MAX_MOVE_ATTEMPTS = 256
- local RECOVERY_MOVE_TIMEOUT = 500 -- Max Bewegungen bei Recovery
- -- NEU: Fuel Whitelist (nur diese Items als Fuel erlaubt)
- local FUEL_WHITELIST = {
- ["minecraft:coal"] = true,
- ["minecraft:charcoal"] = true,
- ["minecraft:coal_block"] = true,
- -- optional:
- -- ["minecraft:blaze_rod"] = true,
- -- ["minecraft:lava_bucket"] = true,
- }
- ------------------------------------------------------------
- -- UI / HELPERS --------------------------------------------
- local hasColors = term and term.setTextColor and colors
- local function cprint(s,c) if hasColors then term.setTextColor(c) end print(s) if hasColors then term.setTextColor(colors.white) end end
- local function ok(s) cprint("[OK] "..s, colors.lime) end
- local function warn(s) cprint("[WARN] "..s, colors.orange) end
- local function errc(s) cprint("[ERROR] "..s, colors.red) end
- local function info(s) cprint("[INFO] "..s, colors.lightBlue) end
- local function title(s) cprint("\n== "..s.." ==", colors.cyan) end
- local function statusLine(str) local w,h=term.getSize() term.setCursorPos(1,h) term.clearLine() term.write(str or "") end
- local function pressEnter(msg) write(msg or "Enter ... "); read() end
- local function round(n) return math.floor((tonumber(n) or 0) + 0.5) end
- local function fmtFuel(x) return (x=="unlimited") and "unlimited" or tostring(x) end
- local function prompt(msg, def)
- local hint = (def ~= nil) and (" ["..tostring(def).."]") or ""
- write(msg..hint..": ")
- local s = read()
- if s=="" and def~=nil then return tostring(def) end
- return s
- end
- local function promptNumber(msg, def, min)
- while true do
- local s = prompt(msg, def)
- local v = tonumber(s)
- if v then v = round(v); if not min or v>=min then return v end end
- print("Please enter a valid number.")
- end
- end
- local function promptBool(msg, def)
- local defS = def and "y" or "n"
- while true do
- local s = prompt(msg.." (y/n)", defS):lower()
- if s=="" then return def end
- if s=="y" or s=="yes" or s=="1" or s=="ja" then return true end
- if s=="n" or s=="no" or s=="0" or s=="nein" then return false end
- print("Please enter y/n.")
- end
- end
- ------------------------------------------------------------
- -- ARGS / PARAMS -------------------------------------------
- local tArgs = {...}
- local function isShape(s) s=(s or ""):lower(); return (s=="torus" or s=="sphere" or s=="cylinder") end
- local SHAPE, ACTION = "torus", ""
- local R, r, THICK, YSCALE = 20, 7, 1, 1.0
- local CYL_H, CYL_FLOOR_STEP, CYL_CAPS = 12, 0, true
- local function toNum(s,d) local v=tonumber(s); return v and v or d end
- local function toBool(s,def)
- if s==nil then return def end
- local v=tostring(s):lower()
- if v=="1" or v=="true" or v=="yes" or v=="ja" then return true end
- if v=="0" or v=="false" or v=="no" or v=="nein" then return false end
- return def
- end
- if #tArgs>0 and isShape(tArgs[1]) then
- SHAPE = tArgs[1]:lower()
- local i=2
- if SHAPE=="torus" then
- R = toNum(tArgs[i],20); i=i+1
- r = toNum(tArgs[i],7); i=i+1
- THICK = toNum(tArgs[i],1); i=i+1
- YSCALE = toNum(tArgs[i],1.0);i=i+1
- elseif SHAPE=="sphere" then
- r = toNum(tArgs[i],8); i=i+1
- THICK = toNum(tArgs[i],1); i=i+1
- YSCALE = toNum(tArgs[i],1.0);i=i+1
- elseif SHAPE=="cylinder" then
- r = toNum(tArgs[i],8); i=i+1
- CYL_H = toNum(tArgs[i],12); i=i+1
- THICK = toNum(tArgs[i],1); i=i+1
- CYL_FLOOR_STEP = toNum(tArgs[i],0) or 0; i=i+1
- CYL_CAPS = toBool(tArgs[i], true); i=i+1
- end
- ACTION = (tArgs[i] or ""):lower()
- elseif #tArgs>0 then
- print("Usage:")
- print(" builder_pro_failsafe torus R r [thickness] [yScale] [plan|build|resume]")
- print(" builder_pro_failsafe sphere r [thickness] [yScale] [plan|build|resume]")
- print(" builder_pro_failsafe cylinder r h [thickness] [floor] [caps] [plan|build|resume]")
- return
- end
- -- Auto-Round & validate
- R, r, THICK, CYL_H, CYL_FLOOR_STEP = round(R), round(r), round(THICK), round(CYL_H), math.max(0, round(CYL_FLOOR_STEP))
- -- Validierung YSCALE
- if YSCALE <= 0 then
- errc("YSCALE muss > 0 sein!"); return
- end
- if YSCALE > 10 then
- warn("YSCALE > 10 can lead to very large structures!")
- end
- if THICK > 2*r then
- warn(string.format("THICK=%d ist zu groß für r=%d! Setze auf Maximum: %d", THICK, r, 2*r))
- THICK = 2*r
- end
- if (SHAPE=="torus" and (R<=0 or r<=0 or THICK<=0))
- or (SHAPE=="sphere" and (r<=0 or THICK<=0))
- or (SHAPE=="cylinder" and (r<=0 or CYL_H<=0 or THICK<=0)) then
- errc("Invalid parameters."); return
- end
- ------------------------------------------------------------
- -- DERIVED GEOMETRY ----------------------------------------
- local XZ, YH, CX, CY, CZ
- local function recomputeDerived()
- if SHAPE=="torus" then
- XZ = 2*(R + r) + 1
- YH = math.floor(2*r*YSCALE + 1)
- elseif SHAPE=="sphere" then
- XZ = 2*r + 1
- YH = math.floor(2*r*YSCALE + 1)
- else
- XZ = 2*r + 1
- YH = CYL_H
- end
- CX, CY, CZ = (XZ-1)/2, (YH-1)/2, (XZ-1)/2
- end
- recomputeDerived()
- -- Größen-Check
- if XZ > MAX_STRUCTURE_SIZE or YH > MAX_STRUCTURE_SIZE then
- errc(string.format("Struktur zu groß! XZ=%d, YH=%d (Max: %d)", XZ, YH, MAX_STRUCTURE_SIZE))
- errc("Reduziere R/r/CYL_H oder erhöhe MAX_STRUCTURE_SIZE!")
- return
- end
- ------------------------------------------------------------
- -- STATE / POS ---------------------------------------------
- local pos={x=0,y=0,z=0}
- local heading=0 -- 0=+X,1=+Z,2=-X,3=-Z
- local stepCounter, blocksPlaced = 0, 0
- local paused, shouldQuit = false, false
- local curLayer, curZ, curX = 0,0,0
- local plannedConfig = nil
- -- NEU: Für Recovery
- local lastSavedPos = {x=0,y=0,z=0}
- local lastSavedHeading = 0
- local recoveryMode = false
- ------------------------------------------------------------
- -- SAVE/LOAD + FAILSAFE ------------------------------------
- local function saveState(st) local f=fs.open(STATE_FILE,"w"); f.write(textutils.serialize(st)); f.close() end
- local function loadState()
- if not fs.exists(STATE_FILE) then return nil end
- local f=fs.open(STATE_FILE,"r"); local txt=f.readAll(); f.close()
- local ok2,data=pcall(textutils.unserialize,txt); if ok2 then return data end; return nil
- end
- local function clearState() if fs.exists(STATE_FILE) then fs.delete(STATE_FILE) end end
- local function snapshotState()
- return {
- version=STATE_VERSION,
- shape=SHAPE, R=R, r=r, THICK=THICK, YSCALE=YSCALE,
- CYL_H=CYL_H, CYL_FLOOR_STEP=CYL_FLOOR_STEP, CYL_CAPS=CYL_CAPS,
- layer=curLayer, curZ=curZ, curX=curX,
- pos={x=pos.x, y=pos.y, z=pos.z},
- heading=heading,
- BUILD_MODE=BUILD_MODE,
- blocksPlaced=blocksPlaced
- }
- end
- local function sameConfig(a,b)
- 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
- 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
- end
- ------------------------------------------------------------
- -- MOVE -----------------------------------------------------
- local function turnLeft() turtle.turnLeft(); heading=(heading+3)%4 end
- local function turnRight() turtle.turnRight(); heading=(heading+1)%4 end
- 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
- local function fwd()
- if turtle.forward() then
- 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
- stepCounter=stepCounter+1; return true
- end
- return false
- end
- local function up() if turtle.up() then pos.y=pos.y+1; stepCounter=stepCounter+1; return true end return false end
- local function down() if turtle.down() then pos.y=pos.y-1; stepCounter=stepCounter+1; return true end return false end
- local function stepWithOptionalDig(dir)
- face(dir)
- if fwd() then return true end
- if FAST_ALLOW_DIG and turtle.detect() then turtle.dig(); return fwd() end
- return false
- end
- ------------------------------------------------------------
- -- SMART ERROR HANDLER -------------------------------------
- local function returnToSavedPosition()
- recoveryMode = true
- warn("RECOVERY: Kehre zu letzter gespeicherter Position zurück...")
- info(string.format("Ziel: [%d,%d,%d] Richtung:%d", lastSavedPos.x, lastSavedPos.y, lastSavedPos.z, lastSavedHeading))
- local moveCount = 0
- local function safeMove(moveFunc, axis)
- moveCount = moveCount + 1
- if moveCount > RECOVERY_MOVE_TIMEOUT then
- errc("RECOVERY FEHLGESCHLAGEN: Zu viele Bewegungsversuche!")
- return false
- end
- for attempt=1,3 do
- if moveFunc() then return true end
- -- Blockierung? Versuche zu diggen
- if axis == "up" and turtle.detectUp() then turtle.digUp()
- elseif axis == "down" and turtle.detectDown() then turtle.digDown()
- elseif turtle.detect() then turtle.dig() end
- sleep(0.2)
- end
- return false
- end
- -- Y zuerst (nach oben für Freiraum)
- while pos.y < lastSavedPos.y do
- if not safeMove(up, "up") then return false end
- end
- -- X und Z
- while pos.x ~= lastSavedPos.x or pos.z ~= lastSavedPos.z do
- if pos.x < lastSavedPos.x then
- face(0)
- if not safeMove(fwd, "forward") then return false end
- elseif pos.x > lastSavedPos.x then
- face(2)
- if not safeMove(fwd, "forward") then return false end
- elseif pos.z < lastSavedPos.z then
- face(1)
- if not safeMove(fwd, "forward") then return false end
- elseif pos.z > lastSavedPos.z then
- face(3)
- if not safeMove(fwd, "forward") then return false end
- end
- end
- -- Y anpassen (falls noch nötig)
- while pos.y > lastSavedPos.y do
- if not safeMove(down, "down") then return false end
- end
- while pos.y < lastSavedPos.y do
- if not safeMove(up, "up") then return false end
- end
- -- Richtung korrigieren
- face(lastSavedHeading)
- ok(string.format("RECOVERY ERFOLGREICH! Position: [%d,%d,%d]", pos.x, pos.y, pos.z))
- recoveryMode = false
- return true
- end
- local function smartFailError(msg)
- errc("FEHLER: "..msg)
- -- Save current state
- local errorState = snapshotState()
- saveState(errorState)
- warn("State gespeichert: "..STATE_FILE)
- -- Versuche zur letzten bekannten Position zurückzukehren
- if not recoveryMode then -- Verhindere Recovery-Loop
- if returnToSavedPosition() then
- title("FEHLER BEHOBEN - BEREIT FÜR RESUME")
- cprint("\nDie Turtle ist zur letzten bekannten Position zurückgekehrt.", colors.yellow)
- cprint("Problem: "..msg, colors.orange)
- cprint("\nBitte behebe das Problem:", colors.lime)
- cprint(" - Bei Fuel-Mangel: Fülle Fuel-Chest", colors.white)
- cprint(" - Bei Material-Mangel: Fülle Material-Chest", colors.white)
- cprint(" - Bei Blockierung: Entferne Hindernisse", colors.white)
- cprint("\nDann starte Resume mit:", colors.cyan)
- cprint(" builder_pro_failsafe resume", colors.lightBlue)
- cprint("\nOder drücke im Menü '3' für Resume", colors.lightBlue)
- else
- errc("RECOVERY FEHLGESCHLAGEN!")
- errc(string.format("Turtle Position: [%d,%d,%d]", pos.x, pos.y, pos.z))
- errc(string.format("Gespeicherte Position: [%d,%d,%d]", lastSavedPos.x, lastSavedPos.y, lastSavedPos.z))
- warn("Bitte bewege Turtle MANUELL zur gespeicherten Position!")
- end
- else
- errc("Recovery selbst fehlgeschlagen - Turtle steckt fest!")
- errc(string.format("Position: [%d,%d,%d]", pos.x, pos.y, pos.z))
- end
- shouldQuit = true
- return false
- end
- local function moveTo(tx, tz, ty, allowDig)
- local blocked = 0
- local function tryUp()
- if up() then blocked = 0; return true end
- if turtle.detectUp() then turtle.digUp() end
- blocked = blocked + 1
- if blocked > MAX_MOVE_ATTEMPTS then return false end
- sleep(0.05)
- return false
- end
- local function tryDown()
- if down() then blocked = 0; return true end
- if turtle.detectDown() then turtle.digDown() end
- blocked = blocked + 1
- if blocked > MAX_MOVE_ATTEMPTS then return false end
- sleep(0.05)
- return false
- end
- -- Y-Bewegung zuerst
- while pos.y < ty do
- if not tryUp() then
- return smartFailError(string.format("Aufwärts blockiert bei Y=%d (Ziel: %d)", pos.y, ty))
- end
- end
- while pos.y > ty do
- if not tryDown() then
- return smartFailError(string.format("Abwärts blockiert bei Y=%d (Ziel: %d)", pos.y, ty))
- end
- end
- -- XZ-Bewegung
- local function stepDir(dir)
- face(dir)
- if fwd() then blocked = 0; return true end
- if allowDig and turtle.detect() then turtle.dig() end
- if fwd() then blocked = 0; return true end
- blocked = blocked + 1
- if blocked > MAX_MOVE_ATTEMPTS then return false end
- sleep(0.05)
- return false
- end
- while pos.x ~= tx or pos.z ~= tz do
- if pos.x < tx then
- if not stepDir(0) then
- return smartFailError(string.format("Move +X blockiert @ [%d,%d,%d]", pos.x, pos.y, pos.z))
- end
- elseif pos.x > tx then
- if not stepDir(2) then
- return smartFailError(string.format("Move -X blockiert @ [%d,%d,%d]", pos.x, pos.y, pos.z))
- end
- elseif pos.z < tz then
- if not stepDir(1) then
- return smartFailError(string.format("Move +Z blockiert @ [%d,%d,%d]", pos.x, pos.y, pos.z))
- end
- elseif pos.z > tz then
- if not stepDir(3) then
- return smartFailError(string.format("Move -Z blockiert @ [%d,%d,%d]", pos.x, pos.y, pos.z))
- end
- end
- end
- return true
- end
- ------------------------------------------------------------
- -- INSPECT / CHESTS / FUEL / INVENTORY ---------------------
- local function safeInspect(fn) local ok,d=fn(); if ok then return d.name or "block" end return nil end
- 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
- local function pauseGate()
- while paused and not shouldQuit do statusLine("PAUSE (P=weiter, Q=save+quit, S=status)"); sleep(0.1) end
- return not shouldQuit
- end
- -- NEU: Fuel Whitelist Check
- local function isFuelLike(d)
- if not d or not d.name then return false end
- return FUEL_WHITELIST[d.name:lower()] == true
- end
- local function refuelFromSlot1(target)
- turtle.select(SLOT_FUEL)
- while turtle.getFuelLevel()~="unlimited" and turtle.getFuelLevel()<target do
- local d=turtle.getItemDetail(SLOT_FUEL); if not isFuelLike(d) then break end
- if not turtle.refuel(1) then break end
- end
- end
- local function dumpNonFuelToFront()
- local d=turtle.getItemDetail(SLOT_FUEL)
- if d and not isFuelLike(d) then
- turtle.select(SLOT_FUEL)
- turtle.drop()
- end
- end
- local function findChestSlot()
- for s=1,16 do local d=turtle.getItemDetail(s); if d and d.name and isInventoryName(d.name) then return s end end
- return nil
- end
- local function goToFuelChestLocation()
- if not moveTo(0,0,0,false) then return false end
- face(1); if not fwd() then return false end
- face(0); if not fwd() then return false end
- local dn=safeInspect(turtle.inspectDown); if dn and not isInventoryName(dn) then turtle.digDown() end
- if not down() then return false end
- face(1)
- local fr=safeInspect(turtle.inspect); if fr and not isInventoryName(fr) then turtle.dig() end
- return true
- end
- -- NEU: Smart Overhead-Routen
- local function goToBaseSmart()
- if FAST_OVERHEAD_TRAVEL then
- local travelY = YH + OVERHEAD_MARGIN
- return moveTo(pos.x, pos.z, travelY, false)
- and moveTo(0, 0, travelY, false)
- and moveTo(0, 0, 0, false)
- else
- return moveTo(0, 0, 0, false)
- end
- end
- local function returnToWorkSmart(bx, bz, by, h)
- if FAST_OVERHEAD_TRAVEL then
- local travelY = YH + OVERHEAD_MARGIN
- local ok = moveTo(pos.x, pos.z, travelY, false)
- and moveTo(bx, bz, travelY, false)
- and moveTo(bx, bz, by, false)
- if ok then face(h) end
- return ok
- else
- local ok = moveTo(bx, bz, by, false)
- if ok then face(h) end
- return ok
- end
- end
- local function placeChestsIfNeeded()
- title("Auto-Chest Setup")
- local below=safeInspect(turtle.inspectDown)
- if isInventoryName(below) then ok("Material chest erkannt unter Turtle.")
- else
- local cs=findChestSlot(); if not cs then return smartFailError("Keine Chest/Barrel im Inventar für Material.") end
- turtle.select(cs); local n=safeInspect(turtle.inspectDown); if n and not isInventoryName(n) then turtle.digDown() end
- if not turtle.placeDown() then return smartFailError("Material chest konnte nicht platziert werden.") end
- ok("Material chest platziert.")
- end
- local bx,bz,by,h = pos.x,pos.z,pos.y,heading
- if not goToFuelChestLocation() then return smartFailError("Fuel-Spot nicht erreichbar.") end
- local front=safeInspect(turtle.inspect)
- local haveFuel=isInventoryName(front)
- if haveFuel then ok("Fuel chest vorhanden.")
- else
- local cs2=findChestSlot(); if not cs2 then return smartFailError("Keine Chest für Fuel verfügbar.") end
- turtle.select(cs2)
- if not turtle.place() then return smartFailError("Fuel chest konnte nicht platziert werden.") end
- ok("Fuel chest platziert.")
- end
- -- Zurück über Overhead-Route
- face(3); if not up() then return smartFailError("Rückweg vom Fuel-Spot: up fehlgeschlagen") end
- face(2); if not fwd() then return smartFailError("Rückweg vom Fuel-Spot: -X fehlgeschlagen") end
- face(3); if not fwd() then return smartFailError("Rückweg vom Fuel-Spot: -Z fehlgeschlagen") end
- if not returnToWorkSmart(bx, bz, by, h) then
- return smartFailError("Rückkehr zur Startposition fehlgeschlagen.")
- end
- info("Chests ready.")
- return true
- end
- -- NEU: Robuste Fuel-Fetch mit Whitelist
- local function fetchFuelFromRightChest(target)
- local bx,bz,by,h = pos.x,pos.z,pos.y,heading
- info("Gehe zur Basis für Fuel-Refill ...")
- if not goToBaseSmart() then
- return smartFailError("Basis nicht erreichbar.")
- end
- face(0)
- if not goToFuelChestLocation() then
- return smartFailError("Fuel-Spot nicht erreichbar.")
- end
- turtle.select(SLOT_FUEL)
- local tries = 0
- while true do
- tries = tries + 1
- -- Dumpe Non-Fuel Items aus Slot 1
- local d = turtle.getItemDetail(SLOT_FUEL)
- if d and (not isFuelLike(d)) then
- warn("Slot 1 enthält Non-Fuel: "..(d.name or "unknown").." -> in Fuel-Chest zurück")
- turtle.drop()
- end
- -- Hole neues Item wenn Slot leer
- if turtle.getItemCount(SLOT_FUEL) == 0 then
- turtle.suck(SUCK_AMOUNT)
- end
- -- Prüfe ob es Fuel ist
- d = turtle.getItemDetail(SLOT_FUEL)
- if d and isFuelLike(d) then
- ok("Geholt: "..(d.name or "unknown"))
- break
- end
- -- Warte auf zulässiges Fuel
- if tries % 5 == 1 then
- warn("============================================")
- warn("FUEL-CHEST ENTHÄLT KEIN ZULÄSSIGES FUEL!")
- warn("Bitte fülle Coal/Charcoal/Coal Block ein")
- warn("Position: (+1,-1,+1) relativ zur Basis")
- warn("Warte... (Versuch "..tries..")")
- warn("============================================")
- end
- sleep(2)
- if shouldQuit then
- face(3); up(); face(2); fwd(); face(3); fwd()
- returnToWorkSmart(bx, bz, by, h)
- return smartFailError("User hat Fuel-Refill abgebrochen (Q)")
- end
- if tries > 50 then
- return smartFailError("Fuel-Refill: Keine zulässigen Fuel-Items gefunden (50 Versuche).")
- end
- end
- -- Tanke aus Slot 1
- info("Tanke aus Slot 1...")
- while turtle.getItemCount(SLOT_FUEL) > 0
- and turtle.getFuelLevel() ~= "unlimited"
- and turtle.getFuelLevel() < target do
- if not turtle.refuel(1) then break end
- end
- turtle.select(SLOT_MIN)
- -- Zurück über Overhead-Route
- face(3)
- if not up() then
- return smartFailError("Rückweg vom Fuel-Spot: up fehlgeschlagen")
- end
- face(2)
- if not fwd() then
- return smartFailError("Rückweg vom Fuel-Spot: -X fehlgeschlagen")
- end
- face(3)
- if not fwd() then
- return smartFailError("Rückweg vom Fuel-Spot: -Z fehlgeschlagen")
- end
- if not returnToWorkSmart(bx, bz, by, h) then
- return smartFailError("Rückkehr zur Bauebene fehlgeschlagen.")
- end
- local level=turtle.getFuelLevel()
- ok(string.format("Fuel-Level nach Refill: %s", fmtFuel(level)))
- -- WICHTIG: Prüfe ob erfolgreich aufgetankt
- if level == "unlimited" then
- return true
- end
- if type(level) == "number" and level >= target then
- return true
- end
- -- Immer noch nicht genug Fuel!
- warn(string.format("WARNUNG: Fuel-Level (%s) unter Target (%d)!", fmtFuel(level), target))
- return false
- end
- local function ensureFuelChestPresentOrWait()
- while true do
- local bx,bz,by,h=pos.x,pos.z,pos.y,heading
- if not goToFuelChestLocation() then
- warn("Fuel-Spot aktuell nicht erreichbar. Stelle sicher, dass +X/+Z frei ist.")
- else
- local present = isInventoryName(safeInspect(turtle.inspect))
- face(3); up(); face(2); fwd(); face(3); fwd()
- returnToWorkSmart(bx, bz, by, h)
- if present then return true end
- local cs=findChestSlot()
- if cs then return placeChestsIfNeeded() end
- end
- statusLine("Warte auf Fuel chest (oder gib Turtle eine Chest)… Q=Quit")
- sleep(2)
- if shouldQuit then return false end
- end
- end
- -- NEU: Striktere Fuel-Funktion mit Slot 1 + Fetch
- local function refuelStrictSlot1OrFetch(target)
- turtle.select(SLOT_FUEL)
- local d = turtle.getItemDetail(SLOT_FUEL)
- if d and isFuelLike(d) then
- while turtle.getItemCount(SLOT_FUEL) > 0
- and turtle.getFuelLevel() ~= "unlimited"
- and turtle.getFuelLevel() < target do
- if not turtle.refuel(1) then break end
- end
- end
- local slot1Count = turtle.getItemCount(SLOT_FUEL)
- turtle.select(SLOT_MIN)
- local fl = turtle.getFuelLevel()
- local needsFetch = (slot1Count == 0)
- or (fl ~= "unlimited" and type(fl)=="number" and fl < FUEL_BUFFER_MIN)
- if needsFetch then
- -- Anker sichern BEVOR wir wegfliegen
- lastSavedPos = { x = pos.x, y = pos.y, z = pos.z }
- lastSavedHeading = heading
- -- WICHTIG: State speichern bevor wir zur Chest fliegen
- local currentState = snapshotState()
- saveState(currentState)
- info("State gesichert vor Fuel-Refill")
- if not ensureFuelChestPresentOrWait() then
- return smartFailError("Keine Fuel-Chest verfügbar.")
- end
- local success = fetchFuelFromRightChest(target)
- if not success then
- return smartFailError("Fuel-Refill von der Fuel-Chest fehlgeschlagen.")
- end
- -- WICHTIG: Nach erfolgreichem Refill shouldQuit zurücksetzen!
- -- (könnte durch vorherige Fehler gesetzt worden sein)
- if shouldQuit then
- warn("shouldQuit war gesetzt - setze zurück nach erfolgreichem Refill")
- shouldQuit = false
- end
- ok(string.format("✅ Zurück an Bauposition: [%d,%d,%d] - Bauen geht weiter!", pos.x, pos.y, pos.z))
- end
- return true
- end
- -- NEU: Verbesserte ensureFuelBuffered
- local function ensureFuelBuffered()
- local fl = turtle.getFuelLevel()
- if fl == "unlimited" then return true end
- if type(fl)=="number" and fl >= FUEL_BUFFER_MIN then return true end
- -- 0 Fuel: NICHT bewegen. Nur Slot 1 nutzen (kein Material-Fuel!).
- if type(fl)=="number" and fl < 1 then
- turtle.select(SLOT_FUEL)
- local d = turtle.getItemDetail(SLOT_FUEL)
- if d and isFuelLike(d) then
- while turtle.getItemCount(SLOT_FUEL) > 0 and turtle.getFuelLevel() < FUEL_TARGET do
- if not turtle.refuel(1) then break end
- end
- turtle.select(SLOT_MIN)
- fl = turtle.getFuelLevel()
- if fl ~= "unlimited" and type(fl)=="number" and fl >= FUEL_BUFFER_MIN then
- return true
- end
- end
- turtle.select(SLOT_MIN)
- return smartFailError("Fuel = 0 und kein zulässiges Fuel in Slot 1. Lege Coal/Charcoal in Slot 1 und starte 'resume'.")
- end
- -- Unter Minimum -> strikt Slot 1 oder Fuel-Chest holen
- return refuelStrictSlot1OrFetch(FUEL_TARGET)
- end
- local function compactBuildSlots()
- local mats={}
- 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
- for _,slots in pairs(mats) do
- for i=1,#slots do
- local dst=slots[i]; local di=turtle.getItemDetail(dst)
- if di and di.count<64 then
- for j=i+1,#slots do local src=slots[j]
- if turtle.getItemCount(src)>0 then turtle.select(src); turtle.transferTo(dst); if turtle.getItemCount(src)==0 then break end end
- end
- end
- end
- end
- turtle.select(SLOT_MIN)
- end
- local function countPlaceables() local n=0 for s=SLOT_MIN,SLOT_MAX do n=n+turtle.getItemCount(s) end return n end
- -- NEU: Material-Refill ohne "unter mir" Check - nur Basis
- local function ensureSomeBlocks()
- compactBuildSlots()
- if countPlaceables()>0 then return true end
- info("Kein Material gefunden – fliege zur Basis für Nachschub ...")
- local bx,bz,by,h = pos.x,pos.z,pos.y,heading
- -- WICHTIG: State speichern bevor wir zur Basis fliegen
- lastSavedPos = { x = pos.x, y = pos.y, z = pos.z }
- lastSavedHeading = heading
- local currentState = snapshotState()
- saveState(currentState)
- info("State gesichert vor Material-Refill")
- if not goToBaseSmart() then
- return smartFailError("Rückkehr zur Basis für Nachschub fehlgeschlagen")
- end
- local tries=0
- while not isInventoryName(safeInspect(turtle.inspectDown)) do
- local n=safeInspect(turtle.inspectDown)
- if n and n~="minecraft:air" then
- info("Entferne Blockierung: "..n)
- turtle.digDown()
- else
- if tries > 0 and tries % 5 == 0 then
- warn("Warte auf Material-Chest unter Turtle... ("..tries.."/"..CHEST_WAIT_RETRIES..")")
- end
- sleep(0.5)
- end
- tries=tries+1
- if tries > CHEST_WAIT_RETRIES then
- errc("Material chest fehlt nach "..tries.." Sekunden!")
- errc("Platziere eine Chest/Barrel DIREKT UNTER der Turtle (0,-1,0)!")
- return smartFailError("Material chest unter Basis fehlt")
- end
- end
- ok("Material chest gefunden!")
- -- Versuche Material zu holen - WARTE wenn leer!
- local refillAttempts = 0
- while countPlaceables() == 0 do
- for s=SLOT_MIN,SLOT_MAX do
- turtle.select(s)
- local need=64-turtle.getItemCount(s)
- if need>0 then turtle.suckDown(need) end
- end
- compactBuildSlots()
- if countPlaceables() > 0 then
- break
- end
- refillAttempts = refillAttempts + 1
- if refillAttempts % 3 == 1 then
- warn("============================================")
- warn("MATERIAL-CHEST IST LEER!")
- warn("Bitte fülle Baumaterial in die Material-Chest:")
- warn("Position: (0,-1,0) - DIREKT UNTER der Turtle")
- warn("Warte... (Versuch "..refillAttempts..") - Q zum Abbrechen")
- warn("============================================")
- end
- sleep(2)
- if shouldQuit then
- returnToWorkSmart(bx, bz, by, h)
- return smartFailError("User hat Material-Refill abgebrochen (Q)")
- end
- end
- ok(string.format("Material geholt! %d Blöcke verfügbar", countPlaceables()))
- if not returnToWorkSmart(bx, bz, by, h) then
- return smartFailError("Rückkehr zur Bauebene nach Nachschub fehlgeschlagen")
- end
- -- WICHTIG: Nach erfolgreichem Refill shouldQuit zurücksetzen!
- if shouldQuit then
- warn("shouldQuit war gesetzt - setze zurück nach erfolgreichem Material-Refill")
- shouldQuit = false
- end
- turtle.select(SLOT_MIN)
- ok(string.format("✅ Zurück an Bauposition: [%d,%d,%d] - Bauen geht weiter!", pos.x, pos.y, pos.z))
- return true
- end
- ------------------------------------------------------------
- -- RANDOM SLOT SELECTION & PLACE WITH RETRY ----------------
- local function selectPlaceSlot()
- local available={}
- for s=SLOT_MIN,SLOT_MAX do if turtle.getItemCount(s)>0 then table.insert(available,s) end end
- if #available==0 then return false end
- local pick=available[math.random(1,#available)]
- turtle.select(pick)
- return true
- end
- local function placeDownOne()
- if turtle.detectDown() then return true end
- if not selectPlaceSlot() then return smartFailError("Keine Baublöcke mehr verfügbar!") end
- if turtle.placeDown() then blocksPlaced=blocksPlaced+1; return true end
- if turtle.detectDown() then turtle.digDown(); sleep(0.05); if turtle.placeDown() then blocksPlaced=blocksPlaced+1; return true end end
- return smartFailError("Konnte Block nicht platzieren!")
- end
- ------------------------------------------------------------
- -- SHAPE MASKS ---------------------------------------------
- local function inCircle(x,z,rad) return (x*x + z*z) <= (rad*rad + 1e-6) end
- 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
- local function onSphereShell(ix,iy,iz)
- local x=ix-CX; local y=(iy-CY)/YSCALE; local z=iz-CZ
- local rr=math.sqrt(x*x+y*y+z*z); local bandOuter=r+0.5; local bandInner=math.max(0, r-(THICK-0.5))
- return (rr<=bandOuter) and (rr>=bandInner)
- end
- local function onTorusShell(ix,iy,iz)
- local x=ix-CX; local y=(iy-CY)/YSCALE; local z=iz-CZ
- local rho=math.sqrt(x*x+z*z); local tube=math.sqrt((rho-R)*(rho-R)+y*y)
- local bandOuter=r+0.5; local bandInner=math.max(0, r-(THICK-0.5))
- return (tube<=bandOuter) and (tube>=bandInner)
- end
- local function onCylinderShell(ix,iy,iz)
- local x=ix-CX; local z=iz-CZ
- if ringShell(x,z,r,THICK) then return true end
- if CYL_CAPS and (iy==0 or iy==YH-1) then return inCircle(x,z,r+0.5) end
- 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
- return false
- end
- local function onShell(ix,iy,iz)
- if SHAPE=="sphere" then return onSphereShell(ix,iy,iz)
- elseif SHAPE=="torus" then return onTorusShell(ix,iy,iz)
- else return onCylinderShell(ix,iy,iz) end
- end
- 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
- ------------------------------------------------------------
- -- KEYS (P/Q/S) --------------------------------------------
- local function keyListener()
- while true do
- local _, key = os.pullEvent("key")
- if key == keys.p then
- paused = not paused
- if paused then info("PAUSED (P = weiter)") else info("RESUMED") end
- elseif key == keys.q then
- shouldQuit = true
- warn("Save & Quit…")
- saveState(snapshotState())
- return
- elseif key == keys.s then
- info(string.format("Status: Layer %d/%d | Pos [%d,%d,%d] | Fuel:%s | Inv:%d",
- curLayer, YH-1, pos.x,pos.y,pos.z, tostring(turtle.getFuelLevel()), countPlaceables()))
- end
- end
- end
- local function runWithKeys(fn) parallel.waitForAny(keyListener, function() fn() end) end
- ------------------------------------------------------------
- -- FAST BUILD ----------------------------------------------
- local function hopToTarget(x, z, yTarget)
- if FAST_OVERHEAD_TRAVEL then
- local travelY = YH + OVERHEAD_MARGIN
- if not moveTo(pos.x,pos.z,travelY,false) then return false end
- if not moveTo(x, z, travelY, false) then return false end
- if not moveTo(x, z, yTarget, false) then return false end
- return true
- else
- return moveTo(x, z, yTarget, FAST_ALLOW_DIG)
- end
- end
- local function planLayer(y)
- local plan, Zs = {}, {}
- for z=0,XZ-1 do
- local row={}
- local forward=(z%2==0)
- if forward then for x=0,XZ-1 do if onShell(x,y,z) then table.insert(row,x) end end
- else for x=XZ-1,0,-1 do if onShell(x,y,z) then table.insert(row,x) end end end
- if #row>0 then plan[z]=row; table.insert(Zs,z) end
- end
- return plan, Zs
- end
- local function buildFromFast(startY, startZ, startX)
- local totalPlan=totalBlocksPlanned()
- local t0=os.epoch("utc")
- for y=startY, YH-1 do
- if shouldQuit then saveState(snapshotState()); return end
- curLayer=y
- -- Update last saved position BEFORE layer
- lastSavedPos = {x=pos.x, y=pos.y, z=pos.z}
- lastSavedHeading = heading
- if not ensureFuelBuffered() then return end
- if shouldQuit then return end
- if not ensureSomeBlocks() then return end
- if shouldQuit then return end
- local layerPlan, sortedZ = planLayer(y)
- local z0idx=1; if y==startY then for i,zz in ipairs(sortedZ) do if zz>=startZ then z0idx=i; break end end end
- for zi=z0idx, #sortedZ do
- if shouldQuit then saveState(snapshotState()); return end
- local z=sortedZ[zi]; curZ=z; local row=layerPlan[z]
- local x0idx=1; if y==startY and z==startZ then
- local forward=(z%2==0)
- for j,x in ipairs(row) do if (forward and x>=startX) or ((not forward) and x<=startX) then x0idx=j; break end end
- end
- for j=x0idx, #row do
- if shouldQuit then saveState(snapshotState()); return end
- if not pauseGate() then saveState(snapshotState()); return end
- local x=row[j]; curX=x
- -- Fuel check basierend auf BLOCKS statt steps
- local currentFuel = turtle.getFuelLevel()
- if currentFuel ~= "unlimited" and currentFuel < FUEL_BUFFER_MIN then
- if not ensureFuelBuffered() then return end
- if shouldQuit then return end
- end
- if not hopToTarget(x,z,y+1) then return end
- if shouldQuit then return end
- if countPlaceables()==0 then
- if not ensureSomeBlocks() then return end
- if shouldQuit then return end
- if not hopToTarget(x,z,y+1) then return end
- if shouldQuit then return end
- end
- if not placeDownOne() then return end
- if shouldQuit then return end
- if (blocksPlaced % SAVE_INTERVAL)==0 then
- lastSavedPos = {x=pos.x, y=pos.y, z=pos.z}
- lastSavedHeading = heading
- saveState(snapshotState())
- end
- local elapsed=math.max(0.001,(os.epoch("utc")-t0)/1000)
- local rem=math.max(0,totalPlan-blocksPlaced)
- local rate=(blocksPlaced>0) and (blocksPlaced/elapsed) or 0
- 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))
- else statusLine(("FAST | Layer %d/%d | (%d,%d) | %d/%d | ETA: --"):format(y,YH-1,x,z,blocksPlaced,totalPlan)) end
- end
- end
- end
- ok("Build complete!")
- moveTo(0,0,0,false); face(0); clearState()
- end
- ------------------------------------------------------------
- -- SAFE BUILD ----------------------------------------------
- local function buildFromSafe(startY, startZ, startX)
- local totalPlan=totalBlocksPlanned()
- local t0=os.epoch("utc")
- for y=startY, YH-1 do
- if shouldQuit then saveState(snapshotState()); return end
- curLayer=y
- -- Update last saved position
- lastSavedPos = {x=pos.x, y=pos.y, z=pos.z}
- lastSavedHeading = heading
- if not ensureFuelBuffered() then return end
- if shouldQuit then return end
- if not ensureSomeBlocks() then return end
- if shouldQuit then return end
- local z0=(y==startY) and startZ or 0
- for z=z0, XZ-1 do
- if shouldQuit then saveState(snapshotState()); return end
- curZ=z
- if not pauseGate() then saveState(snapshotState()); return end
- local forward=(z%2==0)
- local xs,xe,step = (forward and 0 or XZ-1), (forward and XZ-1 or 0), (forward and 1 or -1)
- local x0 = (y==startY and z==z0) and startX or xs
- if not moveTo(x0, z, y+1, true) then return end
- if shouldQuit then return end
- face(forward and 0 or 2)
- local x=x0
- while true do
- if shouldQuit then saveState(snapshotState()); return end
- curX=x
- if onShell(x,y,z) then
- -- Fuel check
- local currentFuel = turtle.getFuelLevel()
- if currentFuel ~= "unlimited" and currentFuel < FUEL_BUFFER_MIN then
- if not ensureFuelBuffered() then return end
- if shouldQuit then return end
- end
- if countPlaceables()==0 then
- if not ensureSomeBlocks() then return end
- if shouldQuit then return end
- if not moveTo(x,z,y+1,true) then return end
- if shouldQuit then return end
- face(forward and 0 or 2)
- end
- if not placeDownOne() then return end
- if shouldQuit then return end
- if (blocksPlaced % SAVE_INTERVAL)==0 then
- lastSavedPos = {x=pos.x, y=pos.y, z=pos.z}
- lastSavedHeading = heading
- saveState(snapshotState())
- end
- end
- local elapsed=math.max(0.001,(os.epoch("utc")-t0)/1000)
- local rem=math.max(0,totalPlan-blocksPlaced)
- local rate=(blocksPlaced>0) and (blocksPlaced/elapsed) or 0
- if rate>0 then statusLine(("SAFE | Layer %d/%d | Z:%d | %d/%d | ETA: %.0fs"):format(y,YH-1,z,blocksPlaced,totalPlan,rem/rate))
- else statusLine(("SAFE | Layer %d/%d | Z:%d | %d/%d | ETA: --"):format(y,YH-1,z,blocksPlaced,totalPlan)) end
- if x==xe then break end
- if not fwd() then
- if turtle.detect() then turtle.dig() end
- if not fwd() then return smartFailError("Blocked on row @ Z="..z) end
- end
- if shouldQuit then return end
- x = x + step
- end
- end
- end
- ok("Build complete!")
- moveTo(0,0,0,true); face(0); clearState()
- end
- ------------------------------------------------------------
- -- CONFIG / PLAN / MENU ------------------------------------
- local function pickShape(cur)
- term.clear(); term.setCursorPos(1,1)
- title("Shape auswählen")
- print("1) Torus")
- print("2) Sphere (Kugel/Ellipsoid)")
- print("3) Cylinder (Hohl)")
- print("Enter = behalten ("..cur..")")
- write("> ")
- local s = read()
- if s=="" then return cur end
- if s=="1" then return "torus" elseif s=="2" then return "sphere" elseif s=="3" then return "cylinder" end
- warn("Ungültig – behalte "..cur); return cur
- end
- local function configureShapeAndSize()
- SHAPE = pickShape(SHAPE)
- if SHAPE=="torus" then
- R = promptNumber("Großer Radius R", R, 1)
- r = promptNumber("Rohr-Radius r", r, 1)
- THICK = promptNumber("Wanddicke", THICK, 1)
- while true do
- local input = prompt("Y-Skalierung (0.1-10.0, 1.0=kreisrund)", tostring(YSCALE))
- YSCALE = tonumber(input) or YSCALE
- if YSCALE > 0 and YSCALE <= 10 then break end
- warn("YSCALE muss zwischen 0.1 und 10.0 liegen!")
- end
- elseif SHAPE=="sphere" then
- r = promptNumber("Radius r", r, 1)
- THICK = promptNumber("Wanddicke", THICK, 1)
- while true do
- local input = prompt("Y-Skalierung (0.1-10.0, 1.0=Kugel)", tostring(YSCALE))
- YSCALE = tonumber(input) or YSCALE
- if YSCALE > 0 and YSCALE <= 10 then break end
- warn("YSCALE muss zwischen 0.1 und 10.0 liegen!")
- end
- else
- r = promptNumber("Radius r", r, 1)
- CYL_H = promptNumber("Höhe (Y)", CYL_H, 1)
- THICK = promptNumber("Wanddicke", THICK, 1)
- CYL_FLOOR_STEP = promptNumber("Zwischenböden (0=keine)", CYL_FLOOR_STEP or 0, 0)
- CYL_CAPS = promptBool("Deckel/Boden voll schließen?", CYL_CAPS)
- end
- -- clamp
- R, r, THICK, CYL_H, CYL_FLOOR_STEP = round(R), round(r), round(THICK), round(CYL_H), math.max(0, round(CYL_FLOOR_STEP))
- if THICK>2*r then
- warn(string.format("THICK=%d ist zu groß für r=%d! Setze auf Maximum: %d", THICK, r, 2*r))
- THICK=2*r
- end
- recomputeDerived()
- -- Größen-Check
- if XZ > MAX_STRUCTURE_SIZE or YH > MAX_STRUCTURE_SIZE then
- errc(string.format("Struktur zu groß! XZ=%d, YH=%d (Max: %d)", XZ, YH, MAX_STRUCTURE_SIZE))
- pressEnter(); return
- end
- -- freeze plan
- local n=totalBlocksPlanned()
- title("Plan (eingefroren)")
- print(string.format("Shape: %s", SHAPE))
- if SHAPE=="torus" then print(string.format("R=%d, r=%d, THICK=%d, YSCALE=%.2f", R,r,THICK,YSCALE))
- elseif SHAPE=="sphere" then print(string.format("r=%d, THICK=%d, YSCALE=%.2f", r,THICK,YSCALE))
- 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
- print(string.format("Bounds: XZ=%d x Y=%d", XZ, YH))
- print(string.format("Planned blocks: %d", n))
- -- Material-Schätzung
- cprint("\n[Material benötigt]", colors.yellow)
- print(string.format(" Blöcke: %d", n))
- print(string.format(" Stacks: ~%d", math.ceil(n/64)))
- print(string.format(" Shulker: ~%d", math.ceil(n/(27*64))))
- local approxMoves = n * 2 * (BUILD_MODE == "fast" and 3 or 1.5)
- print(string.format(" Fuel (ca.): %d", math.ceil(approxMoves)))
- print(string.format(" Coal: ~%d", math.ceil(approxMoves/80)))
- if n > 50000 then
- warn(string.format("WARNUNG: %d Blöcke! Das kann STUNDEN dauern!", n))
- local estimatedMinutes = math.ceil(n / 20)
- warn(string.format("Geschätzte Zeit: %.1f Stunden", estimatedMinutes/60))
- end
- 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}
- pressEnter()
- end
- local function showPlan()
- recomputeDerived()
- local n=totalBlocksPlanned()
- title("Plan")
- print("Build Mode: "..BUILD_MODE)
- print(string.format("Shape: %s", SHAPE))
- if SHAPE=="torus" then print(string.format("R=%d, r=%d, THICK=%d, YSCALE=%.2f", R,r,THICK,YSCALE))
- elseif SHAPE=="sphere" then print(string.format("r=%d, THICK=%d, YSCALE=%.2f", r,THICK,YSCALE))
- 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
- print(string.format("Bounds: XZ=%d x Y=%d", XZ, YH))
- print(string.format("Planned blocks: %d", n))
- -- Material-Schätzung
- cprint("\n[Material benötigt]", colors.yellow)
- print(string.format(" Blöcke: %d", n))
- print(string.format(" Stacks: ~%d", math.ceil(n/64)))
- print(string.format(" Shulker: ~%d", math.ceil(n/(27*64))))
- local approxMoves = n * 2 * (BUILD_MODE == "fast" and 3 or 1.5)
- print(string.format(" Fuel (ca.): %d", math.ceil(approxMoves)))
- print(string.format(" Coal: ~%d", math.ceil(approxMoves/80)))
- end
- local function buildStructure()
- 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}
- if plannedConfig and not sameConfig(plannedConfig,current) then
- warn("Konfiguration weicht vom zuletzt geplanten Layout ab.")
- write("Trotzdem mit aktueller Konfiguration bauen? (y/n): ")
- if (read():lower()~="y") then info("Abgebrochen."); return end
- end
- -- NEU: Erst Fuel, dann Chests
- if not ensureFuelBuffered() then return end
- if shouldQuit then return end
- if not placeChestsIfNeeded() then return end
- if shouldQuit then return end
- if not ensureSomeBlocks() then return end
- if shouldQuit then return end
- clearState()
- curLayer,curZ,curX=0,0,0
- blocksPlaced=0
- lastSavedPos = {x=0,y=0,z=0}
- lastSavedHeading = 0
- if BUILD_MODE=="fast" then
- info("Starting build in FAST mode (Overhead="..tostring(FAST_OVERHEAD_TRAVEL)..")"); pressEnter()
- buildFromFast(0,0,0)
- else
- info("Starting build in SAFE mode"); pressEnter()
- buildFromSafe(0,0,0)
- end
- end
- local function resumeBuild()
- local st=loadState()
- if not st then errc("Kein Save gefunden."); return end
- -- Version Check
- if st.version ~= STATE_VERSION then
- warn(string.format("Save-Version: %s, Script-Version: %s",
- tostring(st.version or "1.0"), STATE_VERSION))
- if not promptBool("Trotzdem laden? (könnte inkompatibel sein)", false) then
- return
- end
- end
- 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
- 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
- CYL_CAPS=(st.CYL_CAPS~=nil) and st.CYL_CAPS or CYL_CAPS
- BUILD_MODE=st.BUILD_MODE or BUILD_MODE
- recomputeDerived()
- curLayer=st.layer or 0; curZ=st.curZ or 0; curX=st.curX or 0
- pos=st.pos or {x=0,y=0,z=0}
- heading=st.heading or 0
- blocksPlaced=st.blocksPlaced or 0
- lastSavedPos = {x=pos.x, y=pos.y, z=pos.z}
- lastSavedHeading = heading
- title("RESUME BUILD")
- info(("Resume: Layer %d/%d, Cell (%d,%d) | Mode: %s"):format(curLayer,YH-1,curX,curZ,BUILD_MODE))
- info(string.format("Position: [%d,%d,%d] Richtung:%d", pos.x,pos.y,pos.z,heading))
- info(string.format("Bereits platziert: %d Blöcke", blocksPlaced))
- cprint("\n[WICHTIG]", colors.yellow)
- cprint("Die Turtle sollte sich noch an der gespeicherten Position befinden!", colors.orange)
- cprint("Falls nicht: Bewege sie manuell dorthin oder der Build wird fehlschlagen.", colors.orange)
- pressEnter("Bereit zum Fortsetzen? Enter...")
- if BUILD_MODE=="fast" then buildFromFast(curLayer,curZ,curX) else buildFromSafe(curLayer,curZ,curX) end
- end
- local function toggleAdvanced()
- title("Advanced Optionen")
- print("1) Toggle Overhead-Travel (FAST): "..tostring(FAST_OVERHEAD_TRAVEL))
- print("2) Toggle FAST_ALLOW_DIG: "..tostring(FAST_ALLOW_DIG))
- print("Enter = zurück")
- write("> ")
- local s=read()
- if s=="1" then FAST_OVERHEAD_TRAVEL = not FAST_OVERHEAD_TRAVEL; ok("Overhead-Travel -> "..tostring(FAST_OVERHEAD_TRAVEL)); sleep(0.5)
- elseif s=="2" then FAST_ALLOW_DIG = not FAST_ALLOW_DIG; ok("FAST_ALLOW_DIG -> "..tostring(FAST_ALLOW_DIG)); sleep(0.5) end
- end
- -- NEU: Return to Saved Position (für Chunk-Unload/Reset)
- local function returnToSavedPositionManual()
- title("Return to Saved Position")
- -- State-File laden
- local st = loadState()
- if not st then
- errc("❌ Datei 'builder_state.txt' nicht gefunden!")
- pressEnter()
- return
- end
- if not st.pos then
- errc("❌ Ungültiger Dateiinhalt oder pos fehlt.")
- pressEnter()
- return
- end
- -- Zielposition extrahieren
- local target = {
- x = st.pos.x,
- y = st.pos.y,
- z = st.pos.z,
- heading = st.heading or 0
- }
- info(string.format("📍 Zielposition: [%d, %d, %d] Richtung: %d", target.x, target.y, target.z, target.heading))
- cprint("\n[WICHTIG]", colors.yellow)
- cprint("Stelle sicher, dass die Turtle bei (0,0,0) steht!", colors.orange)
- cprint("Richtung: Osten (+X)", colors.orange)
- pressEnter("\nBereit? Enter...")
- -- Position & Richtung zurücksetzen
- pos = { x = 0, y = 0, z = 0 }
- heading = 0
- -- Helper-Funktionen für Navigation
- local function navForward()
- if turtle.forward() then
- 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
- elseif heading == 3 then pos.z = pos.z - 1
- end
- return true
- else
- warn(string.format("⚠️ Blockiert bei: [%d, %d, %d]", pos.x, pos.y, pos.z))
- return false
- end
- end
- local function navUpTo(y)
- while pos.y < y do
- if turtle.up() then
- pos.y = pos.y + 1
- else
- errc("⚠️ Blockiert beim Hochgehen")
- return false
- end
- end
- while pos.y > y do
- if turtle.down() then
- pos.y = pos.y - 1
- else
- errc("⚠️ Blockiert beim Runtergehen")
- return false
- end
- end
- return true
- end
- local function navToZ(z)
- if pos.z < z then
- face(1) -- +Z
- while pos.z < z do
- if not navForward() then return false end
- end
- elseif pos.z > z then
- face(3) -- -Z
- while pos.z > z do
- if not navForward() then return false end
- end
- end
- return true
- end
- local function navToX(x)
- if pos.x < x then
- face(0) -- +X
- while pos.x < x do
- if not navForward() then return false end
- end
- elseif pos.x > x then
- face(2) -- -X
- while pos.x > x do
- if not navForward() then return false end
- end
- end
- return true
- end
- -- Navigation ausführen: Y -> Z -> X -> Richtung
- info("Starte Navigation...")
- if not navUpTo(target.y) then
- errc("Navigation fehlgeschlagen!")
- pressEnter()
- return
- end
- if not navToZ(target.z) then
- errc("Navigation fehlgeschlagen!")
- pressEnter()
- return
- end
- if not navToX(target.x) then
- errc("Navigation fehlgeschlagen!")
- pressEnter()
- return
- end
- face(target.heading)
- ok(string.format("Psition reached! Position: [%d, %d, %d]", pos.x, pos.y, pos.z))
- -- NEU: State vollständig wiederherstellen
- 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
- 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
- CYL_CAPS=(st.CYL_CAPS~=nil) and st.CYL_CAPS or CYL_CAPS
- BUILD_MODE=st.BUILD_MODE or BUILD_MODE
- recomputeDerived()
- curLayer=st.layer or 0
- curZ=st.curZ or 0
- curX=st.curX or 0
- blocksPlaced=st.blocksPlaced or 0
- lastSavedPos = {x=pos.x, y=pos.y, z=pos.z}
- lastSavedHeading = heading
- info(string.format("State wiederhergestellt: Layer %d/%d, Cell (%d,%d)", curLayer, YH-1, curX, curZ))
- info("Bauen wird automatisch fortgesetzt...")
- sleep(2)
- -- NEU: Automatisch weiterbauen!
- if BUILD_MODE=="fast" then
- buildFromFast(curLayer,curZ,curX)
- else
- buildFromSafe(curLayer,curZ,curX)
- end
- end
- local function showMenu()
- while true do
- term.clear(); term.setCursorPos(1,1)
- title("Builder Menu ("..BUILD_MODE.." mode)")
- print("1) Show plan")
- print("2) Start build")
- print("3) Resume")
- print("4) Reset Save")
- print("5) Toggle Mode (aktuell: "..BUILD_MODE..")")
- print("C) Configure Shape/Size")
- print("A) Advanced (Overhead/Dig)")
- print("R) Return to saved position (auto-resume)")
- print("Q) Quit")
- write("> ")
- local c=(read() or ""):lower()
- if c=="1" then showPlan(); pressEnter()
- elseif c=="2" then runWithKeys(buildStructure); break
- elseif c=="3" then runWithKeys(resumeBuild); break
- elseif c=="4" then clearState(); ok("Save gelöscht."); pressEnter()
- elseif c=="5" then BUILD_MODE=(BUILD_MODE=="fast") and "safe" or "fast"; ok("Mode -> "..BUILD_MODE); sleep(1)
- elseif c=="c" then configureShapeAndSize()
- elseif c=="a" then toggleAdvanced()
- elseif c=="r" then runWithKeys(returnToSavedPositionManual); break
- elseif c=="q" then break end
- end
- end
- ------------------------------------------------------------
- -- MAIN -----------------------------------------------------
- math.randomseed(os.epoch("utc"))
- if ACTION=="plan" then showPlan(); pressEnter()
- elseif ACTION=="build" or ACTION=="place" then runWithKeys(buildStructure)
- elseif ACTION=="resume" then runWithKeys(resumeBuild)
- else showMenu()
- end
Add Comment
Please, Sign In to add comment