Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- builder_pro_failsafe.lua (2025 PRO + SMART FAILSAFE + MULTILINGUAL)
- -- Shapes: Torus | Sphere | Cylinder | Dome (hollow)
- -- Modes: Safe + Fast (Overhead optional) | Random Slot Placement | Auto-Rounding
- -- SMART ERROR RECOVERY: Turtle kehrt bei Fehler zum letzten Save zurück!
- -- Multilingual: DE | EN (easily extendable)
- -- Start: Turtle an SW-Unterkante des Volumens, Blick nach Osten (+X)
- -- Chests: Material (0,-1,0) | Fuel (+1,-1,+1)
- -- UI SCALING (reduces menu size)
- pcall(function() term.setTextScale(0.5) end)
- ------------------------------------------------------------
- -- LANGUAGE SYSTEM ------------------------------------------
- local LANG = "de" -- "de" | "en"
- local LANG_FILE = "builder_lang.txt"
- -- Translations
- local TRANSLATIONS = {
- de = {
- menu_title = "Builder Menu",
- menu_mode = "mode",
- menu_1 = "1) Plan ansehen",
- menu_2 = "2) Build starten",
- menu_3 = "3) Resume",
- menu_4 = "4) Reset Save",
- menu_5 = "5) Toggle Mode (aktuell: %s)",
- menu_r = "R) Return to saved position (auto-resume)",
- menu_c = "C) Configure Shape/Size",
- menu_a = "A) Advanced (Overhead/Dig)",
- menu_l = "L) Language / Sprache",
- menu_q = "Q) Quit",
- -- Menu explanations
- menu_explain_1 = "Zeigt geplante Struktur und Material-Bedarf",
- menu_explain_2 = "Startet neuen Build (Chests werden platziert)",
- menu_explain_3 = "Setzt unterbrochenen Build fort",
- menu_explain_4 = "Loescht gespeicherten Fortschritt",
- menu_explain_5_fast = "FAST: Schnell mit Overhead-Travel (fuer freien Luftraum)",
- menu_explain_5_safe = "SAFE: Sicher, grabt durch Hindernisse",
- menu_explain_r = "Turtle zu letzter Position (nach Chunk-Unload/Abbau)",
- menu_explain_c = "Waehle Form und Parameter",
- menu_explain_a = "Overhead-Travel & Dig-Optionen",
- menu_explain_l = "Sprache wechseln (DE/EN)",
- shape_1 = "1) Torus",
- shape_2 = "2) Sphere (Kugel/Ellipsoid)",
- shape_3 = "3) Cylinder (Hohl)",
- shape_4 = "4) Dome (Kuppel)",
- shape_explain_torus = "Donut-Form (Ring um Ring)",
- shape_explain_sphere = "Hohle Kugel (mit XYZ-Skalierung fuer Ellipsoide)",
- shape_explain_cylinder = "Hohler Zylinder (Turm oder Tunnel)",
- shape_explain_dome = "Kuppel (ganz, oben oder unten)",
- dome_full = "Volle Kuppel",
- dome_upper = "Obere Haelfte",
- dome_lower = "Untere Haelfte",
- prompt_major_radius = "Grosser Radius R",
- prompt_major_radius_help = "Abstand vom Zentrum zur Mitte des Rohrs",
- prompt_tube_radius = "Rohr-Radius r",
- prompt_tube_radius_help = "Dicke des Torus-Rohrs",
- prompt_radius = "Radius r",
- prompt_radius_help = "Radius der Struktur (Abstand vom Zentrum)",
- prompt_thickness = "Wanddicke",
- prompt_thickness_help = "Dicke der Wand in Bloecken (1=duenn, 2+=dick)",
- prompt_height = "Hoehe (Y)",
- prompt_height_help = "Hoehe des Zylinders in Bloecken",
- prompt_floors = "Zwischenboeden (0=keine)",
- prompt_floors_help = "Alle X Bloecke einen Boden (0=keine Boeden)",
- prompt_caps = "Deckel/Boden voll schliessen?",
- prompt_caps_help = "y=Zylinder zu, n=Zylinder offen",
- prompt_xscale = "X-Skalierung (1.0=rund)",
- prompt_xscale_help = "Streckung in X-Richtung (0.5=flach, 2.0=lang)",
- prompt_yscale = "Y-Skalierung (1.0=rund)",
- prompt_yscale_help = "Streckung in Y-Richtung (Hoehe)",
- prompt_zscale = "Z-Skalierung (1.0=rund)",
- prompt_zscale_help = "Streckung in Z-Richtung",
- prompt_dome_type = "Dome-Typ (1=voll, 2=oben, 3=unten)",
- prompt_dome_type_help = "1=Ganze Kugel, 2=Nur oben, 3=Nur unten",
- prompt_cyl_orientation = "Zylinder-Orientierung (1=horizontal, 2=vertikal)",
- prompt_cyl_orientation_help = "1=Tunnel/Roehre, 2=Turm/Saeule",
- -- Advanced menu
- adv_explain_overhead = "Fliegt ueber Struktur zwischen Runs (schneller)",
- adv_explain_dig = "Graebt durch Bloecke beim Bewegen (nur FAST)",
- -- Build start
- build_setup_title = "Build-Setup",
- build_setup_position = "Platziere Turtle an SW-Ecke (Suedwest)",
- build_setup_facing = "Turtle muss nach OSTEN (+X) schauen",
- build_setup_chests = "2 Chests bereithalten (Material + Fuel)",
- build_setup_material = "Material-Chest: unter Turtle (0,-1,0)",
- build_setup_fuel = "Fuel-Chest: rechts-vorne-unten (+1,-1,+1)",
- build_setup_ready = "Druecke P zum Pausieren, Q zum Speichern+Beenden, S fuer Status",
- -- Plan display
- plan_dimensions = "Abmessungen",
- plan_volume = "Bauvolumen: %dx%d Grundflaeche, %d Bloecke hoch",
- plan_size_xyz = " X (Laenge): %d | Y (Hoehe): %d | Z (Breite): %d",
- plan_bounds = " Von (0,0,0) bis (%d,%d,%d)",
- plan_blocks_total = "Geplante Bloecke: %d",
- plan_center = "Mittelpunkt der Struktur",
- plan_center_coords = " Center: (%d, %d, %d) relativ zur Turtle",
- plan_center_world = " Bei Turtle-Start (0,0,0) → Center ist bei (%d, %d, %d)",
- plan_center_help = "Bewege Turtle VOR dem Build um Center zu verschieben!",
- plan_material_needed = "Benoetigtes Material",
- plan_blocks = "Bloecke: %d",
- plan_stacks = "Stacks (64er): ~%d",
- plan_shulker = "Shulker Boxen: ~%d",
- plan_fuel_approx = "Fuel (ca.): %d Bewegungen",
- plan_coal = "Coal (ca.): ~%d",
- plan_time_warning = "WARNUNG: %d Bloecke - das dauert lange!",
- plan_time_estimate = "Geschaetzte Zeit: %.1f Stunden (bei ~20 Bloecke/Min)",
- fuel_low = "Fuel niedrig (%d/%d) - versuche aus Slot 1...",
- fuel_refilled = "Fuel aufgefuellt! Level: %s",
- material_fetched = "Material geholt! %d Bloecke verfuegbar",
- build_complete = "Build complete!",
- save_deleted = "Save geloescht.",
- mode_changed = "Mode -> %s",
- recovery_success = "RECOVERY ERFOLGREICH! Position: [%d,%d,%d]",
- return_and_resume = "Zurueck an Bauposition: [%d,%d,%d] - Bauen geht weiter!",
- -- Keys
- key_p = "P = Pause/Weiter",
- key_q = "Q = Speichern & Beenden",
- key_s = "S = Status anzeigen",
- },
- en = {
- menu_title = "Builder Menu",
- menu_mode = "mode",
- menu_1 = "1) View Plan",
- menu_2 = "2) Start Build",
- menu_3 = "3) Resume",
- menu_4 = "4) Reset Save",
- menu_5 = "5) Toggle Mode (current: %s)",
- menu_r = "R) Return to saved position (auto-resume)",
- menu_c = "C) Configure Shape/Size",
- menu_a = "A) Advanced (Overhead/Dig)",
- menu_l = "L) Language / Sprache",
- menu_q = "Q) Quit",
- -- Menu explanations
- menu_explain_1 = "Shows planned structure and material requirements",
- menu_explain_2 = "Starts new build (chests will be placed)",
- menu_explain_3 = "Continues interrupted build",
- menu_explain_4 = "Deletes saved progress",
- menu_explain_5_fast = "FAST: Quick with overhead travel (for open airspace)",
- menu_explain_5_safe = "SAFE: Safe, digs through obstacles",
- menu_explain_r = "Turtle to last position (after chunk unload/pickup)",
- menu_explain_c = "Choose shape and parameters",
- menu_explain_a = "Overhead-Travel & Dig options",
- menu_explain_l = "Change language (DE/EN)",
- shape_1 = "1) Torus",
- shape_2 = "2) Sphere (Ellipsoid)",
- shape_3 = "3) Cylinder (Hollow)",
- shape_4 = "4) Dome",
- shape_explain_torus = "Donut shape (ring around ring)",
- shape_explain_sphere = "Hollow sphere (with XYZ scaling for ellipsoids)",
- shape_explain_cylinder = "Hollow cylinder (tower or tunnel)",
- shape_explain_dome = "Dome (full, upper or lower half)",
- dome_full = "Full Dome",
- dome_upper = "Upper Half",
- dome_lower = "Lower Half",
- prompt_major_radius = "Major Radius R",
- prompt_major_radius_help = "Distance from center to middle of tube",
- prompt_tube_radius = "Tube Radius r",
- prompt_tube_radius_help = "Thickness of torus tube",
- prompt_radius = "Radius r",
- prompt_radius_help = "Radius of structure (distance from center)",
- prompt_thickness = "Wall Thickness",
- prompt_thickness_help = "Thickness of wall in blocks (1=thin, 2+=thick)",
- prompt_height = "Height (Y)",
- prompt_height_help = "Height of cylinder in blocks",
- prompt_floors = "Intermediate Floors (0=none)",
- prompt_floors_help = "Floor every X blocks (0=no floors)",
- prompt_caps = "Close top/bottom caps?",
- prompt_caps_help = "y=closed cylinder, n=open cylinder",
- prompt_xscale = "X-Scaling (1.0=circular)",
- prompt_xscale_help = "Stretch in X direction (0.5=flat, 2.0=long)",
- prompt_yscale = "Y-Scaling (1.0=circular)",
- prompt_yscale_help = "Stretch in Y direction (height)",
- prompt_zscale = "Z-Scaling (1.0=circular)",
- prompt_zscale_help = "Stretch in Z direction",
- prompt_dome_type = "Dome Type (1=full, 2=upper, 3=lower)",
- prompt_dome_type_help = "1=Full sphere, 2=Upper only, 3=Lower only",
- prompt_cyl_orientation = "Cylinder Orientation (1=horizontal, 2=vertical)",
- prompt_cyl_orientation_help = "1=Tunnel/Pipe, 2=Tower/Column",
- -- Advanced menu
- adv_explain_overhead = "Flies over structure between runs (faster)",
- adv_explain_dig = "Digs through blocks when moving (FAST only)",
- -- Build start
- build_setup_title = "Build Setup",
- build_setup_position = "Place turtle at SW corner (Southwest)",
- build_setup_facing = "Turtle must face EAST (+X)",
- build_setup_chests = "Have 2 chests ready (Material + Fuel)",
- build_setup_material = "Material chest: under turtle (0,-1,0)",
- build_setup_fuel = "Fuel chest: right-front-down (+1,-1,+1)",
- build_setup_ready = "Press P to pause, Q to save+quit, S for status",
- -- Plan display
- plan_dimensions = "Dimensions",
- plan_volume = "Build volume: %dx%d footprint, %d blocks tall",
- plan_size_xyz = " X (Length): %d | Y (Height): %d | Z (Width): %d",
- plan_bounds = " From (0,0,0) to (%d,%d,%d)",
- plan_blocks_total = "Planned blocks: %d",
- plan_center = "Structure Center",
- plan_center_coords = " Center: (%d, %d, %d) relative to turtle",
- plan_center_world = " If turtle starts at (0,0,0) → Center is at (%d, %d, %d)",
- plan_center_help = "Move turtle BEFORE building to shift center!",
- plan_material_needed = "Required Material",
- plan_blocks = "Blocks: %d",
- plan_stacks = "Stacks (64): ~%d",
- plan_shulker = "Shulker Boxes: ~%d",
- plan_fuel_approx = "Fuel (approx): %d movements",
- plan_coal = "Coal (approx): ~%d",
- plan_time_warning = "WARNING: %d blocks - this will take a while!",
- plan_time_estimate = "Estimated time: %.1f hours (at ~20 blocks/min)",
- fuel_low = "Fuel low (%d/%d) - trying from slot 1...",
- fuel_refilled = "Fuel refilled! Level: %s",
- material_fetched = "Material fetched! %d blocks available",
- build_complete = "Build complete!",
- save_deleted = "Save deleted.",
- mode_changed = "Mode -> %s",
- recovery_success = "RECOVERY SUCCESSFUL! Position: [%d,%d,%d]",
- return_and_resume = "Back at build position: [%d,%d,%d] - Building continues!",
- -- Keys
- key_p = "P = Pause/Continue",
- key_q = "Q = Save & Quit",
- key_s = "S = Show Status",
- }
- }
- local function t(key, ...)
- local text = TRANSLATIONS[LANG] and TRANSLATIONS[LANG][key]
- if not text then text = TRANSLATIONS["en"] and TRANSLATIONS["en"][key] end
- if not text then return "[MISSING: "..key.."]" end
- if select("#", ...) > 0 then return string.format(text, ...) end
- return text
- end
- local function loadLanguage()
- if fs.exists(LANG_FILE) then
- local f = fs.open(LANG_FILE, "r")
- local lang = f.readAll()
- f.close()
- if TRANSLATIONS[lang] then LANG = lang end
- end
- end
- local function saveLanguage()
- local f = fs.open(LANG_FILE, "w")
- f.write(LANG)
- f.close()
- end
- loadLanguage()
- ------------------------------------------------------------
- -- CONFIG ---------------------------------------------------
- local BUILD_MODE = "fast"
- local FAST_ALLOW_DIG = false
- local FAST_OVERHEAD_TRAVEL = true
- local OVERHEAD_MARGIN = 2
- 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 = 64
- local FUEL_CHECK_INTERVAL = 64
- local STATE_VERSION = "2.1"
- local SUCK_AMOUNT = 64
- local FUEL_FETCH_RETRIES = 8
- local CHEST_WAIT_RETRIES = 20
- local MAX_STRUCTURE_SIZE = 200
- local MAX_MOVE_ATTEMPTS = 100
- local RECOVERY_MOVE_TIMEOUT = 500
- local STATUS_UPDATE_INTERVAL = 500 -- NEU: ms between status updates
- -- NEU: Fuel Whitelist
- local FUEL_WHITELIST = {
- ["minecraft:coal"] = true,
- ["minecraft:charcoal"] = true,
- ["minecraft:coal_block"] = 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("Bitte gueltige Zahl eingeben.")
- 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("Bitte y/n eingeben.")
- end
- end
- ------------------------------------------------------------
- -- ARGS / PARAMS -------------------------------------------
- local tArgs = {...}
- local function isShape(s) s=(s or ""):lower(); return (s=="torus" or s=="sphere" or s=="cylinder" or s=="dome") end
- local SHAPE, ACTION = "torus", ""
- local R, r, THICK = 20, 7, 1
- local XSCALE, YSCALE, ZSCALE = 1.0, 1.0, 1.0
- local CYL_H, CYL_FLOOR_STEP, CYL_CAPS = 12, 0, true
- local CYL_ORIENTATION = "horizontal" -- NEU: "horizontal" (default) | "vertical"
- local DOME_HALF = "full"
- 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" or SHAPE=="dome" then
- r = toNum(tArgs[i],8); i=i+1
- THICK = toNum(tArgs[i],1); i=i+1
- XSCALE = toNum(tArgs[i],1.0);i=i+1
- YSCALE = toNum(tArgs[i],1.0);i=i+1
- ZSCALE = toNum(tArgs[i],1.0);i=i+1
- if SHAPE=="dome" then
- local half = (tArgs[i] or "upper"):lower()
- DOME_HALF = (half=="upper" or half=="lower" or half=="full") and half or "upper"
- i=i+1
- end
- 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
- -- NEU: Orientation als Parameter
- local orient = (tArgs[i] or "horizontal"):lower()
- CYL_ORIENTATION = (orient=="vertical" or orient=="v") and "vertical" or "horizontal"
- i=i+1
- end
- ACTION = (tArgs[i] or ""):lower()
- end
- 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 XSCALE <= 0 or YSCALE <= 0 or ZSCALE <= 0 then
- errc("All scales must be > 0!"); return
- end
- if THICK > 2*r then
- warn(string.format("THICK=%d too large for r=%d! Setting to max: %d", THICK, r, 2*r))
- THICK = 2*r
- end
- if (SHAPE=="torus" and (R<=0 or r<=0 or THICK<=0))
- or ((SHAPE=="sphere" or SHAPE=="dome") 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
- local maxXZ = math.max(2*r*XSCALE, 2*r*ZSCALE)
- XZ = math.floor(maxXZ + 1)
- YH = math.floor(2*r*YSCALE + 1)
- elseif SHAPE=="dome" then
- local maxXZ = math.max(2*r*XSCALE, 2*r*ZSCALE)
- XZ = math.floor(maxXZ + 1)
- if DOME_HALF == "full" then
- YH = math.floor(2*r*YSCALE + 1)
- else
- YH = math.floor(r*YSCALE + 1)
- end
- else -- cylinder
- if CYL_ORIENTATION == "vertical" then
- -- Vertikaler Zylinder: Höhe in Y, Radius in XZ
- XZ = 2*r + 1
- YH = CYL_H
- else
- -- Horizontaler Zylinder: Höhe in X, Radius in YZ
- XZ = CYL_H
- YH = 2*r + 1
- end
- end
- CX, CY, CZ = (XZ-1)/2, (YH-1)/2, (XZ-1)/2
- end
- recomputeDerived()
- if XZ > MAX_STRUCTURE_SIZE or YH > MAX_STRUCTURE_SIZE then
- errc(string.format("Structure too large! XZ=%d, YH=%d (Max: %d)", XZ, YH, MAX_STRUCTURE_SIZE))
- return
- end
- ------------------------------------------------------------
- -- STATE / POS ---------------------------------------------
- local pos={x=0,y=0,z=0}
- local heading=0
- local stepCounter, blocksPlaced = 0, 0
- local paused, shouldQuit = false, false
- local curLayer, curZ, curX = 0,0,0
- local plannedConfig = nil
- 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,
- XSCALE=XSCALE, YSCALE=YSCALE, ZSCALE=ZSCALE,
- CYL_H=CYL_H, CYL_FLOOR_STEP=CYL_FLOOR_STEP, CYL_CAPS=CYL_CAPS,
- CYL_ORIENTATION=CYL_ORIENTATION,
- DOME_HALF=DOME_HALF,
- 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.XSCALE==b.XSCALE and a.YSCALE==b.YSCALE and a.ZSCALE==b.ZSCALE
- 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.CYL_ORIENTATION==b.CYL_ORIENTATION
- and a.DOME_HALF==b.DOME_HALF 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
- ------------------------------------------------------------
- -- SMART ERROR HANDLER -------------------------------------
- local function returnToSavedPosition()
- recoveryMode = true
- warn("RECOVERY: Returning to last saved position...")
- info(string.format("Target: [%d,%d,%d] Heading:%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 FAILED: Too many move attempts!")
- return false
- end
- for attempt=1,3 do
- if moveFunc() then return true end
- 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
- while pos.y < lastSavedPos.y do
- if not safeMove(up, "up") then return false end
- end
- 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
- 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
- face(lastSavedHeading)
- ok(t("recovery_success", pos.x, pos.y, pos.z))
- recoveryMode = false
- return true
- end
- local function smartFailError(msg)
- errc("ERROR: "..msg)
- local errorState = snapshotState()
- saveState(errorState)
- warn("State saved: "..STATE_FILE)
- if not recoveryMode then
- if returnToSavedPosition() then
- title("ERROR FIXED - READY FOR RESUME")
- cprint("\nTurtle returned to last known position.", colors.yellow)
- cprint("Problem: "..msg, colors.orange)
- cprint("\nPlease fix the problem:", colors.lime)
- cprint(" - Out of fuel: Fill Fuel Chest", colors.white)
- cprint(" - Out of material: Fill Material Chest", colors.white)
- cprint(" - Blocked: Remove obstacles", colors.white)
- cprint("\nThen resume with:", colors.cyan)
- cprint(" builder_pro_failsafe resume", colors.lightBlue)
- cprint("\nOr press '3' in menu for Resume", colors.lightBlue)
- else
- errc("RECOVERY FAILED!")
- errc(string.format("Turtle Position: [%d,%d,%d]", pos.x, pos.y, pos.z))
- errc(string.format("Saved Position: [%d,%d,%d]", lastSavedPos.x, lastSavedPos.y, lastSavedPos.z))
- warn("Please move turtle MANUALLY to saved position!")
- end
- else
- errc("Recovery itself failed - turtle is stuck!")
- 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
- while pos.y < ty do
- if not tryUp() then
- return smartFailError(string.format("Blocked upward at Y=%d (Target: %d)", pos.y, ty))
- end
- end
- while pos.y > ty do
- if not tryDown() then
- return smartFailError(string.format("Blocked downward at Y=%d (Target: %d)", pos.y, ty))
- end
- end
- 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 blocked @ [%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 blocked @ [%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 blocked @ [%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 blocked @ [%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=continue, Q=save+quit, S=status)"); sleep(0.1) end
- return not shouldQuit
- end
- 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 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 Routes
- 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")
- -- Material Chest unter Turtle
- local below=safeInspect(turtle.inspectDown)
- if isInventoryName(below) then
- ok("Material chest detected under turtle.")
- else
- local cs=findChestSlot()
- if not cs then return smartFailError("No Chest/Barrel in inventory for 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 could not be placed.") end
- ok("Material chest placed.")
- end
- -- Fuel Chest Position: (+1,-1,+1) relativ zu Start
- -- NEU: Prüfe erst ob wir schon da sind
- local needFuelChestCheck = true
- -- Wenn wir bei (0,0,0) sind, schaue ob Fuel-Chest schon existiert ohne hinzufahren
- if pos.x == 0 and pos.y == 0 and pos.z == 0 then
- -- Kleine Prüfung: Schaue nach vorne-rechts ob Chest erkennbar ist
- -- (ohne komplett hinzufahren)
- needFuelChestCheck = true -- Wir müssen trotzdem hin um sicher zu sein
- end
- local bx,bz,by,h = pos.x,pos.z,pos.y,heading
- if not goToFuelChestLocation() then
- return smartFailError("Fuel spot not reachable.")
- end
- local front=safeInspect(turtle.inspect)
- local haveFuel=isInventoryName(front)
- if haveFuel then
- ok("Fuel chest present.")
- else
- local cs2=findChestSlot()
- if not cs2 then return smartFailError("No chest available for fuel.") end
- turtle.select(cs2)
- if not turtle.place() then return smartFailError("Fuel chest could not be placed.") end
- ok("Fuel chest placed.")
- end
- -- Zurück zur Startposition - aber OPTIMIERT
- face(3)
- if not up() then return smartFailError("Return from fuel spot: up failed") end
- face(2)
- if not fwd() then return smartFailError("Return from fuel spot: -X failed") end
- face(3)
- if not fwd() then return smartFailError("Return from fuel spot: -Z failed") end
- -- NEU: Wenn wir zur Basis zurück sollen (0,0,0), gehe direkt ohne Overhead
- if bx == 0 and bz == 0 and by == 0 then
- -- Bereits nah an Basis - kein Overhead nötig
- if pos.x ~= 0 or pos.z ~= 0 or pos.y ~= 0 then
- if not moveTo(0, 0, 0, false) then
- return smartFailError("Return to start position failed.")
- end
- end
- face(h)
- else
- -- Zur ursprünglichen Position zurück
- if not returnToWorkSmart(bx, bz, by, h) then
- return smartFailError("Return to start position failed.")
- end
- end
- info("Chests ready.")
- return true
- end
- -- NEU: Robust Fuel Fetch with Whitelist
- local function fetchFuelFromRightChest(target)
- local bx,bz,by,h = pos.x,pos.z,pos.y,heading
- info("Going to base for fuel refill...")
- if not goToBaseSmart() then
- return smartFailError("Base not reachable.")
- end
- face(0)
- if not goToFuelChestLocation() then
- return smartFailError("Fuel spot not reachable.")
- end
- turtle.select(SLOT_FUEL)
- local tries = 0
- while true do
- tries = tries + 1
- local d = turtle.getItemDetail(SLOT_FUEL)
- if d and (not isFuelLike(d)) then
- warn("Slot 1 contains non-fuel: "..(d.name or "unknown").." -> back to chest")
- turtle.drop()
- end
- if turtle.getItemCount(SLOT_FUEL) == 0 then
- turtle.suck(SUCK_AMOUNT)
- end
- d = turtle.getItemDetail(SLOT_FUEL)
- if d and isFuelLike(d) then
- ok("Fetched: "..(d.name or "unknown"))
- break
- end
- if tries % 5 == 1 then
- warn("============================================")
- warn("FUEL CHEST CONTAINS NO VALID FUEL!")
- warn("Please add Coal/Charcoal/Coal Block")
- warn("Position: (+1,-1,+1) relative to base")
- warn("Waiting... (Attempt "..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 aborted fuel refill (Q)")
- end
- if tries > 50 then
- return smartFailError("Fuel refill: No valid fuel items found (50 attempts).")
- end
- end
- info("Refueling from 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)
- face(3)
- if not up() then
- return smartFailError("Return from fuel spot: up failed")
- end
- face(2)
- if not fwd() then
- return smartFailError("Return from fuel spot: -X failed")
- end
- face(3)
- if not fwd() then
- return smartFailError("Return from fuel spot: -Z failed")
- end
- if not returnToWorkSmart(bx, bz, by, h) then
- return smartFailError("Return to build level failed.")
- end
- local level=turtle.getFuelLevel()
- ok(string.format("Fuel level after refill: %s", fmtFuel(level)))
- if level == "unlimited" then
- return true
- end
- if type(level) == "number" and level >= target then
- return true
- end
- warn(string.format("WARNING: Fuel level (%s) below 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 currently unreachable. Ensure +X/+Z is free.")
- 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("Waiting for fuel chest (or give turtle a chest)... Q=Quit")
- sleep(2)
- if shouldQuit then return false end
- end
- end
- -- NEU: Stricter Fuel Function with 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
- lastSavedPos = { x = pos.x, y = pos.y, z = pos.z }
- lastSavedHeading = heading
- local currentState = snapshotState()
- saveState(currentState)
- info("State saved before fuel refill")
- if not ensureFuelChestPresentOrWait() then
- return smartFailError("No fuel chest available.")
- end
- local success = fetchFuelFromRightChest(target)
- if not success then
- return smartFailError("Fuel refill from chest failed.")
- end
- if shouldQuit then
- warn("shouldQuit was set - resetting after successful refill")
- shouldQuit = false
- end
- ok(t("return_and_resume", pos.x, pos.y, pos.z))
- end
- return true
- end
- -- NEU: Improved 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
- 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 and no valid fuel in slot 1. Add Coal/Charcoal to slot 1 and run 'resume'.")
- end
- 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 without "under me" check - only base
- local function ensureSomeBlocks()
- compactBuildSlots()
- if countPlaceables()>0 then return true end
- info("No material found - flying to base for refill...")
- local bx,bz,by,h = pos.x,pos.z,pos.y,heading
- lastSavedPos = { x = pos.x, y = pos.y, z = pos.z }
- lastSavedHeading = heading
- local currentState = snapshotState()
- saveState(currentState)
- info("State saved before material refill")
- if not goToBaseSmart() then
- return smartFailError("Return to base for refill failed")
- end
- local tries=0
- while not isInventoryName(safeInspect(turtle.inspectDown)) do
- local n=safeInspect(turtle.inspectDown)
- if n and n~="minecraft:air" then
- info("Removing blockage: "..n)
- turtle.digDown()
- else
- if tries > 0 and tries % 5 == 0 then
- warn("Waiting for material chest under turtle... ("..tries.."/"..CHEST_WAIT_RETRIES..")")
- end
- sleep(0.5)
- end
- tries=tries+1
- if tries > CHEST_WAIT_RETRIES then
- errc("Material chest missing after "..tries.." seconds!")
- errc("Place a Chest/Barrel DIRECTLY UNDER the turtle (0,-1,0)!")
- return smartFailError("Material chest under base missing")
- end
- end
- ok("Material chest found!")
- 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 IS EMPTY!")
- warn("Please add building blocks to material chest:")
- warn("Position: (0,-1,0) - DIRECTLY UNDER turtle")
- warn("Waiting... (Attempt "..refillAttempts..") - Q to abort")
- warn("============================================")
- end
- sleep(2)
- if shouldQuit then
- returnToWorkSmart(bx, bz, by, h)
- return smartFailError("User aborted material refill (Q)")
- end
- end
- ok(t("material_fetched", countPlaceables()))
- if not returnToWorkSmart(bx, bz, by, h) then
- return smartFailError("Return to build level after refill failed")
- end
- if shouldQuit then
- warn("shouldQuit was set - resetting after successful material refill")
- shouldQuit = false
- end
- turtle.select(SLOT_MIN)
- ok(t("return_and_resume", 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("No build blocks available!") 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("Could not place block!")
- 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) / (XSCALE ~= 0 and XSCALE or 1)
- local y = (iy - CY) / (YSCALE ~= 0 and YSCALE or 1)
- local z = (iz - CZ) / (ZSCALE ~= 0 and ZSCALE or 1)
- 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 onDomeShell(ix, iy, iz)
- local x = (ix - CX) / (XSCALE ~= 0 and XSCALE or 1)
- local y_real = iy
- local y_center = CY
- if DOME_HALF == "upper" then
- y_center = 0
- local y_scaled = (y_real - y_center) / (YSCALE ~= 0 and YSCALE or 1)
- if y_real < 0 then return false end
- local z = (iz - CZ) / (ZSCALE ~= 0 and ZSCALE or 1)
- local rr = math.sqrt(x*x + y_scaled*y_scaled + z*z)
- local bandOuter = r + 0.5
- local bandInner = math.max(0, r - (THICK - 0.5))
- return (rr <= bandOuter) and (rr >= bandInner)
- elseif DOME_HALF == "lower" then
- y_center = YH - 1
- local y_scaled = (y_real - y_center) / (YSCALE ~= 0 and YSCALE or 1)
- if y_real > y_center then return false end
- local z = (iz - CZ) / (ZSCALE ~= 0 and ZSCALE or 1)
- local rr = math.sqrt(x*x + y_scaled*y_scaled + z*z)
- local bandOuter = r + 0.5
- local bandInner = math.max(0, r - (THICK - 0.5))
- return (rr <= bandOuter) and (rr >= bandInner)
- else
- return onSphereShell(ix, iy, iz)
- end
- 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)
- if CYL_ORIENTATION == "vertical" then
- -- Vertikaler Zylinder: Ring in XZ, entlang Y
- 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
- else
- -- Horizontaler Zylinder: Ring in YZ, entlang X
- local y=iy-CY; local z=iz-CZ
- if ringShell(y,z,r,THICK) then return true end
- if CYL_CAPS and (ix==0 or ix==XZ-1) then return inCircle(y,z,r+0.5) end
- if CYL_FLOOR_STEP>0 and ix>0 and ix<XZ-1 and (ix % CYL_FLOOR_STEP == 0) then return inCircle(y,z,r+0.5) end
- end
- return false
- end
- local function onShell(ix,iy,iz)
- if SHAPE=="sphere" then return onSphereShell(ix,iy,iz)
- elseif SHAPE=="dome" then return onDomeShell(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 = continue)") 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
- ------------------------------------------------------------
- -- SHARED BUILD HELPERS ------------------------------------
- -- NEU: Shared helper for contiguous X-runs (used by both FAST and SAFE)
- local function makeRunsForRow(row, forward)
- if not row or #row == 0 then return {} end
- local runs = {}
- local step = forward and 1 or -1
- local s, p = row[1], row[1]
- for i = 2, #row do
- local x = row[i]
- if x == p + step then
- p = x
- else
- table.insert(runs, {s, p})
- s, p = x, x
- end
- end
- table.insert(runs, {s, p})
- return runs
- 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
- ------------------------------------------------------------
- -- FAST BUILD (RUN-OPTIMIZED) ------------------------------
- local function hopToTarget(x, z, yTarget)
- if FAST_OVERHEAD_TRAVEL then
- -- NEU: Skip Overhead wenn schon nahe am Ziel (innerhalb 3 Blöcke)
- local distXZ = math.abs(pos.x - x) + math.abs(pos.z - z)
- local distY = math.abs(pos.y - yTarget)
- if distXZ <= 3 and distY <= 3 then
- -- Nah genug → Direkt hinfahren ohne Overhead
- return moveTo(x, z, yTarget, FAST_ALLOW_DIG)
- end
- -- Weit weg → Overhead-Travel nutzen
- 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
- -- NEU: FAST Mode mit Run-Optimization!
- local function buildFromFast(startY, startZ, startX)
- local totalPlan=totalBlocksPlanned()
- local t0=os.epoch("utc")
- local lastStatus = 0
- local function maybeStatus()
- local t = os.epoch("utc")
- if t - lastStatus > STATUS_UPDATE_INTERVAL then
- local rem = math.max(0, totalPlan - blocksPlaced)
- local elapsed = math.max(0.001, (t - t0) / 1000)
- local rate = (blocksPlaced > 0) and (blocksPlaced / elapsed) or 0
- if rate > 0 then
- statusLine(("FAST-RUN | Layer %d/%d | Z:%d | %d/%d | ETA: %.0fs")
- :format(curLayer, YH-1, curZ, blocksPlaced, totalPlan, rem / rate))
- else
- statusLine(("FAST-RUN | Layer %d/%d | Z:%d | %d/%d | ETA: --")
- :format(curLayer, YH-1, curZ, blocksPlaced, totalPlan))
- end
- lastStatus = t
- end
- end
- for y=startY, YH-1 do
- if shouldQuit then saveState(snapshotState()); return end
- curLayer=y
- 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
- -- Plan layer with runs
- local layerPlan, sortedZ = planLayer(y)
- -- Start Z-index for resume
- local zStartIdx = 1
- if y == startY then
- for i, zz in ipairs(sortedZ) do
- if zz >= startZ then zStartIdx = i; break end
- end
- end
- for zi=zStartIdx, #sortedZ do
- if shouldQuit then saveState(snapshotState()); return end
- if not pauseGate() then saveState(snapshotState()); return end
- local z = sortedZ[zi]
- curZ = z
- local row = layerPlan[z]
- local forward = (z % 2 == 0)
- local step = forward and 1 or -1
- -- Create runs for this row
- local runs = makeRunsForRow(row, forward)
- -- Find start run for resume
- local runStartIdx, xResume = 1, nil
- if y == startY and z == startZ then
- for i, r in ipairs(runs) do
- local x1, x2 = r[1], r[2]
- local lo, hi = math.min(x1, x2), math.max(x1, x2)
- if startX >= lo and startX <= hi then
- runStartIdx = i
- xResume = startX
- break
- end
- end
- end
- for rIdx = runStartIdx, #runs do
- if shouldQuit then saveState(snapshotState()); return end
- local x1, x2 = runs[rIdx][1], runs[rIdx][2]
- local xStart = (rIdx == runStartIdx and xResume) and xResume or x1
- -- Hop to run start with overhead travel
- if not hopToTarget(xStart, z, y+1) then return end
- if shouldQuit then return end
- face(forward and 0 or 2)
- local x = xStart
- -- Build the run without overhead (stay at same height)
- while true do
- if shouldQuit then saveState(snapshotState()); return end
- curX = x
- -- Fuel check
- local fl = turtle.getFuelLevel()
- if fl ~= "unlimited" and type(fl)=="number" and fl < FUEL_BUFFER_MIN then
- if not ensureFuelBuffered() then return end
- if shouldQuit then return end
- -- Return to position in run
- if not hopToTarget(x, z, y+1) then return end
- if shouldQuit then return end
- face(forward and 0 or 2)
- end
- -- Material check
- if countPlaceables() == 0 then
- if not ensureSomeBlocks() then return end
- if shouldQuit then return end
- -- Return to position in run
- if not hopToTarget(x, z, y+1) then return end
- if shouldQuit then return end
- face(forward and 0 or 2)
- end
- -- Place block (all positions in run are valid)
- if not placeDownOne() then return end
- if shouldQuit then return end
- -- Save state
- if (blocksPlaced % SAVE_INTERVAL) == 0 then
- lastSavedPos = {x=pos.x, y=pos.y, z=pos.z}
- lastSavedHeading = heading
- saveState(snapshotState())
- end
- maybeStatus()
- -- End of run?
- if x == x2 then break end
- -- Move forward in run (no overhead!)
- if not fwd() then
- if FAST_ALLOW_DIG and turtle.detect() then turtle.dig() end
- if not fwd() then
- return smartFailError("Blocked in run @ Z="..z)
- end
- end
- if shouldQuit then return end
- x = x + step
- end
- end
- end
- end
- ok(t("build_complete"))
- moveTo(0,0,0,false); face(0); clearState()
- end
- ------------------------------------------------------------
- -- SAFE BUILD (RUN-OPTIMIZED) ------------------------------
- local function buildFromSafe(startY, startZ, startX)
- local totalPlan = totalBlocksPlanned()
- local t0 = os.epoch("utc")
- local lastStatus = 0
- local function maybeStatus()
- local t = os.epoch("utc")
- if t - lastStatus > STATUS_UPDATE_INTERVAL then
- local rem = math.max(0, totalPlan - blocksPlaced)
- local elapsed = math.max(0.001, (t - t0) / 1000)
- local rate = (blocksPlaced > 0) and (blocksPlaced / elapsed) or 0
- if rate > 0 then
- statusLine(("SAFE-RUN | Layer %d/%d | Z:%d | %d/%d | ETA: %.0fs")
- :format(curLayer, YH-1, curZ, blocksPlaced, totalPlan, rem / rate))
- else
- statusLine(("SAFE-RUN | Layer %d/%d | Z:%d | %d/%d | ETA: --")
- :format(curLayer, YH-1, curZ, blocksPlaced, totalPlan))
- end
- lastStatus = t
- end
- end
- for y = startY, YH-1 do
- if shouldQuit then saveState(snapshotState()); return end
- curLayer = y
- 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
- -- Plan only for this layer
- local layerPlan, sortedZ = planLayer(y)
- -- Determine start Z-index (for resume)
- local zStartIdx = 1
- if y == startY then
- for i, zz in ipairs(sortedZ) do
- if zz >= startZ then zStartIdx = i; break end
- end
- end
- for zi = zStartIdx, #sortedZ do
- if shouldQuit then saveState(snapshotState()); return end
- if not pauseGate() then saveState(snapshotState()); return end
- local z = sortedZ[zi]
- curZ = z
- local row = layerPlan[z]
- local forward = (z % 2 == 0)
- local step = forward and 1 or -1
- -- Runs (contiguous X-ranges)
- local runs = makeRunsForRow(row, forward)
- -- Find start run for resume
- local runStartIdx, xResume = 1, nil
- if y == startY and z == startZ then
- for i, r in ipairs(runs) do
- local x1, x2 = r[1], r[2]
- local lo, hi = math.min(x1, x2), math.max(x1, x2)
- if startX >= lo and startX <= hi then
- runStartIdx = i
- xResume = startX
- break
- end
- end
- end
- for rIdx = runStartIdx, #runs do
- if shouldQuit then saveState(snapshotState()); return end
- local x1, x2 = runs[rIdx][1], runs[rIdx][2]
- local xStart = (rIdx == runStartIdx and xResume) and xResume or x1
- -- Move to run start once
- if not moveTo(xStart, z, y+1, true) then return end
- if shouldQuit then return end
- face(forward and 0 or 2)
- local x = xStart
- while true do
- if shouldQuit then saveState(snapshotState()); return end
- curX = x
- -- Fuel check (only at low level)
- local fl = turtle.getFuelLevel()
- if fl ~= "unlimited" and type(fl)=="number" and fl < FUEL_BUFFER_MIN then
- if not ensureFuelBuffered() 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 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
- -- In run all positions are valid -> place directly
- if not placeDownOne() then return end
- if shouldQuit then return end
- -- Save/Status
- if (blocksPlaced % SAVE_INTERVAL) == 0 then
- lastSavedPos = {x=pos.x, y=pos.y, z=pos.z}
- lastSavedHeading = heading
- saveState(snapshotState())
- end
- maybeStatus()
- if x == x2 then break end
- if not fwd() then
- if turtle.detect() then turtle.dig() end
- if not fwd() then return smartFailError("Blocked in run @ Z="..z) end
- end
- if shouldQuit then return end
- x = x + step
- end
- end
- end
- end
- ok(t("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("Select Shape")
- print("")
- print(t("shape_1"))
- cprint(" "..t("shape_explain_torus"), colors.gray)
- print(t("shape_2"))
- cprint(" "..t("shape_explain_sphere"), colors.gray)
- print(t("shape_3"))
- cprint(" "..t("shape_explain_cylinder"), colors.gray)
- print(t("shape_4"))
- cprint(" "..t("shape_explain_dome"), colors.gray)
- print("")
- print("Enter = keep ("..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"
- elseif s=="4" then return "dome" end
- warn("Invalid - keeping "..cur); return cur
- end
- local function promptNumberWithHelp(msg, help, def, min)
- while true do
- print("")
- cprint(help, colors.gray)
- 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("Bitte gueltige Zahl eingeben.")
- end
- end
- local function promptBoolWithHelp(msg, help, def)
- local defS = def and "y" or "n"
- while true do
- print("")
- cprint(help, colors.gray)
- 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("Bitte y/n eingeben.")
- end
- end
- local function configureShapeAndSize()
- SHAPE = pickShape(SHAPE)
- if SHAPE=="torus" then
- R = promptNumberWithHelp(t("prompt_major_radius"), t("prompt_major_radius_help"), R, 1)
- r = promptNumberWithHelp(t("prompt_tube_radius"), t("prompt_tube_radius_help"), r, 1)
- THICK = promptNumberWithHelp(t("prompt_thickness"), t("prompt_thickness_help"), THICK, 1)
- YSCALE = promptNumberWithHelp(t("prompt_yscale"), t("prompt_yscale_help"), YSCALE, 0.1)
- elseif SHAPE=="sphere" then
- r = promptNumberWithHelp(t("prompt_radius"), t("prompt_radius_help"), r, 1)
- THICK = promptNumberWithHelp(t("prompt_thickness"), t("prompt_thickness_help"), THICK, 1)
- XSCALE = promptNumberWithHelp(t("prompt_xscale"), t("prompt_xscale_help"), XSCALE, 0.1)
- YSCALE = promptNumberWithHelp(t("prompt_yscale"), t("prompt_yscale_help"), YSCALE, 0.1)
- ZSCALE = promptNumberWithHelp(t("prompt_zscale"), t("prompt_zscale_help"), ZSCALE, 0.1)
- elseif SHAPE=="dome" then
- r = promptNumberWithHelp(t("prompt_radius"), t("prompt_radius_help"), r, 1)
- THICK = promptNumberWithHelp(t("prompt_thickness"), t("prompt_thickness_help"), THICK, 1)
- XSCALE = promptNumberWithHelp(t("prompt_xscale"), t("prompt_xscale_help"), XSCALE, 0.1)
- YSCALE = promptNumberWithHelp(t("prompt_yscale"), t("prompt_yscale_help"), YSCALE, 0.1)
- ZSCALE = promptNumberWithHelp(t("prompt_zscale"), t("prompt_zscale_help"), ZSCALE, 0.1)
- print("")
- cprint(t("prompt_dome_type_help"), colors.gray)
- print(t("prompt_dome_type"))
- print("1) "..t("dome_full"))
- print("2) "..t("dome_upper"))
- print("3) "..t("dome_lower"))
- write("> ")
- local dt = read()
- if dt=="1" then DOME_HALF="full"
- elseif dt=="2" then DOME_HALF="upper"
- elseif dt=="3" then DOME_HALF="lower"
- else DOME_HALF="upper" end
- else
- r = promptNumberWithHelp(t("prompt_radius"), t("prompt_radius_help"), r, 1)
- CYL_H = promptNumberWithHelp(t("prompt_height"), t("prompt_height_help"), CYL_H, 1)
- THICK = promptNumberWithHelp(t("prompt_thickness"), t("prompt_thickness_help"), THICK, 1)
- CYL_FLOOR_STEP = promptNumberWithHelp(t("prompt_floors"), t("prompt_floors_help"), CYL_FLOOR_STEP or 0, 0)
- CYL_CAPS = promptBoolWithHelp(t("prompt_caps"), t("prompt_caps_help"), CYL_CAPS)
- -- NEU: Orientation auswählen
- print("")
- cprint(t("prompt_cyl_orientation_help"), colors.gray)
- print(t("prompt_cyl_orientation"))
- print("1) Horizontal (Default)")
- print("2) Vertical")
- write("> ")
- local orient = read()
- if orient == "2" then
- CYL_ORIENTATION = "vertical"
- else
- CYL_ORIENTATION = "horizontal"
- end
- end
- 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 too large for r=%d! Setting to max: %d", THICK, r, 2*r))
- THICK=2*r
- end
- recomputeDerived()
- if XZ > MAX_STRUCTURE_SIZE or YH > MAX_STRUCTURE_SIZE then
- errc(string.format("Structure too large! XZ=%d, YH=%d (Max: %d)", XZ, YH, MAX_STRUCTURE_SIZE))
- pressEnter(); return
- end
- local n=totalBlocksPlanned()
- title("Plan (frozen)")
- 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, X/Y/Z=%.2f/%.2f/%.2f", r,THICK,XSCALE,YSCALE,ZSCALE))
- elseif SHAPE=="dome" then print(string.format("r=%d, THICK=%d, X/Y/Z=%.2f/%.2f/%.2f, Half=%s", r,THICK,XSCALE,YSCALE,ZSCALE,DOME_HALF))
- else print(string.format("r=%d, height=%d, THICK=%d, floors=%d, caps=%s, orient=%s", r,CYL_H,THICK,CYL_FLOOR_STEP,tostring(CYL_CAPS),CYL_ORIENTATION)) end
- print("")
- cprint(t("plan_dimensions"), colors.yellow)
- print(t("plan_size_xyz", XZ, YH, XZ))
- print(t("plan_bounds", XZ-1, YH-1, XZ-1))
- print(t("plan_blocks_total", n))
- print("")
- cprint(t("plan_center"), colors.yellow)
- print(t("plan_center_coords", math.floor(CX), math.floor(CY), math.floor(CZ)))
- print(t("plan_center_world", math.floor(CX), math.floor(CY), math.floor(CZ)))
- cprint(t("plan_center_help"), colors.gray)
- print("")
- cprint(t("plan_material_needed"), colors.yellow)
- print(t("plan_blocks", n))
- print(t("plan_stacks", math.ceil(n/64)))
- print(t("plan_shulker", math.ceil(n/(27*64))))
- local approxMoves = n * 2 * (BUILD_MODE == "fast" and 3 or 1.5)
- print(t("plan_fuel_approx", math.ceil(approxMoves)))
- print(t("plan_coal", math.ceil(approxMoves/80)))
- if n > 50000 then
- print("")
- warn(t("plan_time_warning", n))
- local estimatedMinutes = math.ceil(n / 20)
- warn(t("plan_time_estimate", estimatedMinutes/60))
- end
- plannedConfig = {shape=SHAPE,R=R,r=r,THICK=THICK,XSCALE=XSCALE,YSCALE=YSCALE,ZSCALE=ZSCALE,CYL_H=CYL_H,CYL_FLOOR_STEP=CYL_FLOOR_STEP,CYL_CAPS=CYL_CAPS,CYL_ORIENTATION=CYL_ORIENTATION,DOME_HALF=DOME_HALF,BUILD_MODE=BUILD_MODE}
- 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, X/Y/Z=%.2f/%.2f/%.2f", r,THICK,XSCALE,YSCALE,ZSCALE))
- elseif SHAPE=="dome" then print(string.format("r=%d, THICK=%d, X/Y/Z=%.2f/%.2f/%.2f, Half=%s", r,THICK,XSCALE,YSCALE,ZSCALE,DOME_HALF))
- else print(string.format("r=%d, height=%d, THICK=%d, floors=%d, caps=%s, orient=%s", r,CYL_H,THICK,CYL_FLOOR_STEP,tostring(CYL_CAPS),CYL_ORIENTATION)) end
- print("")
- cprint(t("plan_dimensions"), colors.yellow)
- print(t("plan_size_xyz", XZ, YH, XZ))
- print(t("plan_bounds", XZ-1, YH-1, XZ-1))
- print(t("plan_blocks_total", n))
- print("")
- cprint(t("plan_center"), colors.yellow)
- print(t("plan_center_coords", math.floor(CX), math.floor(CY), math.floor(CZ)))
- print(t("plan_center_world", math.floor(CX), math.floor(CY), math.floor(CZ)))
- cprint(t("plan_center_help"), colors.gray)
- print("")
- cprint(t("plan_material_needed"), colors.yellow)
- print(t("plan_blocks", n))
- print(t("plan_stacks", math.ceil(n/64)))
- print(t("plan_shulker", math.ceil(n/(27*64))))
- local approxMoves = n * 2 * (BUILD_MODE == "fast" and 3 or 1.5)
- print(t("plan_fuel_approx", math.ceil(approxMoves)))
- print(t("plan_coal", math.ceil(approxMoves/80)))
- end
- local function buildStructure()
- local current = {shape=SHAPE,R=R,r=r,THICK=THICK,XSCALE=XSCALE,YSCALE=YSCALE,ZSCALE=ZSCALE,CYL_H=CYL_H,CYL_FLOOR_STEP=CYL_FLOOR_STEP,CYL_CAPS=CYL_CAPS,CYL_ORIENTATION=CYL_ORIENTATION,DOME_HALF=DOME_HALF,BUILD_MODE=BUILD_MODE}
- if plannedConfig and not sameConfig(plannedConfig,current) then
- warn("Configuration differs from last planned layout.")
- write("Continue with current configuration anyway? (y/n): ")
- if (read():lower()~="y") then info("Aborted."); return end
- end
- -- NEU: Setup-Hinweise anzeigen
- term.clear(); term.setCursorPos(1,1)
- title(t("build_setup_title"))
- print("")
- cprint(t("build_setup_position"), colors.yellow)
- cprint(t("build_setup_facing"), colors.yellow)
- print("")
- cprint(t("build_setup_chests"), colors.lime)
- cprint(" "..t("build_setup_material"), colors.white)
- cprint(" "..t("build_setup_fuel"), colors.white)
- print("")
- cprint(t("build_setup_ready"), colors.cyan)
- pressEnter("\n"..t("prompt_enter"))
- 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("No save found."); return end
- 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("Load anyway? (might be incompatible)", 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
- XSCALE=st.XSCALE or XSCALE; YSCALE=st.YSCALE or YSCALE; ZSCALE=st.ZSCALE or ZSCALE
- 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
- CYL_ORIENTATION=st.CYL_ORIENTATION or CYL_ORIENTATION
- DOME_HALF=st.DOME_HALF or DOME_HALF
- 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] Heading:%d", pos.x,pos.y,pos.z,heading))
- info(string.format("Already placed: %d blocks", blocksPlaced))
- cprint("\n[IMPORTANT]", colors.yellow)
- cprint("The turtle should still be at the saved position!", colors.orange)
- cprint("If not: Move it manually there or the build will fail.", colors.orange)
- pressEnter("Ready to continue? Press Enter...")
- if BUILD_MODE=="fast" then buildFromFast(curLayer,curZ,curX) else buildFromSafe(curLayer,curZ,curX) end
- end
- -- NEU: Return to Saved Position with Auto-Resume
- local function returnToSavedPositionManual()
- title("Return to Saved Position")
- local st = loadState()
- if not st then
- errc("File 'builder_state.txt' not found!")
- pressEnter()
- return
- end
- if not st.pos then
- errc("Invalid file content or pos missing.")
- pressEnter()
- return
- end
- local target = {
- x = st.pos.x,
- y = st.pos.y,
- z = st.pos.z,
- heading = st.heading or 0
- }
- info(string.format("Target position: [%d, %d, %d] Heading: %d", target.x, target.y, target.z, target.heading))
- cprint("\n[IMPORTANT]", colors.yellow)
- cprint("Make sure the turtle is at (0,0,0)!", colors.orange)
- cprint("Facing: East (+X)", colors.orange)
- pressEnter("\nReady? Press Enter...")
- pos = { x = 0, y = 0, z = 0 }
- heading = 0
- 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("Blocked at: [%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("Blocked going up")
- return false
- end
- end
- while pos.y > y do
- if turtle.down() then
- pos.y = pos.y - 1
- else
- errc("Blocked going down")
- return false
- end
- end
- return true
- end
- local function navToZ(z)
- if pos.z < z then
- face(1)
- while pos.z < z do
- if not navForward() then return false end
- end
- elseif pos.z > z then
- face(3)
- 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)
- while pos.x < x do
- if not navForward() then return false end
- end
- elseif pos.x > x then
- face(2)
- while pos.x > x do
- if not navForward() then return false end
- end
- end
- return true
- end
- info("Starting navigation...")
- if not navUpTo(target.y) then
- errc("Navigation failed!")
- pressEnter()
- return
- end
- if not navToZ(target.z) then
- errc("Navigation failed!")
- pressEnter()
- return
- end
- if not navToX(target.x) then
- errc("Navigation failed!")
- pressEnter()
- return
- end
- face(target.heading)
- ok(string.format("Target reached! Position: [%d, %d, %d]", pos.x, pos.y, pos.z))
- SHAPE=st.shape or SHAPE
- R=st.R or R
- r=st.r or r
- THICK=st.THICK or THICK
- XSCALE=st.XSCALE or XSCALE
- YSCALE=st.YSCALE or YSCALE
- ZSCALE=st.ZSCALE or ZSCALE
- 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
- CYL_ORIENTATION=st.CYL_ORIENTATION or CYL_ORIENTATION
- DOME_HALF=st.DOME_HALF or DOME_HALF
- 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 restored: Layer %d/%d, Cell (%d,%d)", curLayer, YH-1, curX, curZ))
- info("Building will continue automatically...")
- sleep(2)
- if BUILD_MODE=="fast" then
- buildFromFast(curLayer,curZ,curX)
- else
- buildFromSafe(curLayer,curZ,curX)
- end
- end
- local function toggleAdvanced()
- title("Advanced Options")
- print("")
- print("1) Toggle Overhead-Travel (FAST): "..tostring(FAST_OVERHEAD_TRAVEL))
- cprint(" "..t("adv_explain_overhead"), colors.gray)
- print("2) Toggle FAST_ALLOW_DIG: "..tostring(FAST_ALLOW_DIG))
- cprint(" "..t("adv_explain_dig"), colors.gray)
- print("")
- print("Enter = back")
- 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
- local function changeLanguage()
- term.clear(); term.setCursorPos(1,1)
- title("Language / Sprache")
- print("Current / Aktuell: "..LANG)
- print("")
- print("Deutsch: de")
- print("English: en")
- print("")
- write("Select / Waehle (de/en): ")
- local newLang = read():lower()
- if TRANSLATIONS[newLang] then
- LANG = newLang
- saveLanguage()
- if newLang == "de" then
- ok("Sprache geaendert zu: "..LANG)
- else
- ok("Language changed to: "..LANG)
- end
- sleep(1)
- else
- warn("Invalid language / Ungueltige Sprache")
- sleep(1)
- end
- end
- local function showMenu()
- while true do
- term.clear(); term.setCursorPos(1,1)
- title(t("menu_title").." ("..BUILD_MODE.." "..t("menu_mode")..")")
- print(t("menu_1"))
- print(t("menu_2"))
- print(t("menu_3"))
- print("4) Reset Save")
- print(t("menu_5", BUILD_MODE))
- print(t("menu_c"))
- print(t("menu_a"))
- print(t("menu_r"))
- print(t("menu_l"))
- print(t("menu_q"))
- print("")
- cprint(t("key_p").." | "..t("key_q").." | "..t("key_s"), colors.yellow)
- 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(t("save_deleted")); pressEnter()
- elseif c=="5" then BUILD_MODE=(BUILD_MODE=="fast") and "safe" or "fast"; ok(t("mode_changed", BUILD_MODE)); sleep(1)
- elseif c=="c" then configureShapeAndSize()
- elseif c=="a" then toggleAdvanced()
- elseif c=="r" then runWithKeys(returnToSavedPositionManual); break
- elseif c=="l" then changeLanguage()
- 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