-- https://git.l--n.de/justuswolff/cc_housebuild/raw/branch/main/src/main.lua if not _G["turtle"] then error("This program only runs on turtles!") end local turtle = _G["turtle"] print("House building system") print("JUFS Technologies (c) 2026 (Justus Wolff)") print("fuel: "..tostring(turtle.getFuelLevel()).."/"..tostring(turtle.getFuelLimit())) os.sleep(0.1) function term.setcol(fc, bc) term.setTextColor(fc) term.setBackgroundColor(bc) end local function reset() term.setcol(colors.white, colors.black) term.clear() term.setCursorPos(1,1) end local function incline() local _,y = term.getCursorPos() term.setCursorPos(1,y+1) end local function selopt(options, title) local selected = 1 while true do reset() term.setcol(colors.black, colors.white) term.write(title) for i,v in pairs(options) do incline() if i == selected then term.setcol(colors.black, colors.blue) else term.setcol(colors.white, colors.black) end term.write(v) end local event,key = os.pullEvent("key") if keys.getName(key) == "w" and selected ~= 1 then selected = selected - 1 end if keys.getName(key) == "s" and selected ~= #options then selected = selected + 1 end if keys.getName(key) == "enter" then return selected end end end local function integritycheck() if not fs.exists("designs") then fs.makeDir("designs") end end local function posasstring(...) local positions = {...} local out = "" for _,v in pairs(positions) do out = out .. tostring(v) .. ":" end return out end local function load(name) local file = fs.open("designs/"..name, "r") local content = file.readAll() file.close() content = textutils.unserialiseJSON(content) return content["buf"],content["dimensions"] end local function newdesign() local buf = {} local dimensions = { x = 8, z = 8, y = 1 } local currentfloor = 1 local camx,camy = 0,0 local currentblock = 2 local blockindex = 1 local blocks = { 2, -- no ceiling 3, -- doorway 4, -- glass } local function renderblock(index) local block = blocks[index] if block == 2 then -- no ceiling term.blit("C", colors.toBlit(colors.black), colors.toBlit(colors.red)) end if block == 3 then -- doorway term.blit("D", colors.toBlit(colors.black), colors.toBlit(colors.brown)) end if block == 4 then -- glass term.blit("W", colors.toBlit(colors.black), colors.toBlit(colors.white)) end end while true do -- render buf reset() for x=1+camx,dimensions["x"]+camx,1 do for z=1+camy,dimensions["z"]+camy,1 do local currentbuf = buf[posasstring(x-camx, currentfloor, z-camy)] term.setCursorPos(x, z) if currentbuf == 1 then -- wall term.blit(" ", colors.toBlit(colors.white), colors.toBlit(colors.white)) elseif currentbuf > 1 then -- blocks renderblock(currentbuf-1) --term.blit("C", colors.toBlit(colors.black), colors.toBlit(colors.red)) elseif currentbuf == 0 or currentbuf == nil then -- nothing term.blit("\127", colors.toBlit(colors.gray), colors.toBlit(colors.black)) end end end term.setcol(colors.yellow, colors.black) local _,y = term.getSize() term.setCursorPos(1,y-1) term.write("Press Ctrl for menu. ") term.setCursorPos(1,y) term.write("x: "..tostring(camx)) term.write(" z: "..tostring(camy)) term.write(" floor: "..tostring(currentfloor)) term.write(" Block: ") renderblock(blockindex) -- user input local event = table.pack(os.pullEvent()) if event[1] == "mouse_click" or event[1] == "mouse_drag" then event[3] = event[3]+camx event[4] = event[4]+camy if event[3] <= dimensions["x"] and event[4] <= dimensions["z"] then if event[2] == 1 then -- left button, set buf[posasstring(event[3], currentfloor, event[4])] = 1 end if event[2] == 3 then -- middle button, set special block buf[posasstring(event[3], currentfloor, event[4])] = currentblock end if event[2] == 2 then -- right button, erase buf[posasstring(event[3], currentfloor, event[4])] = 0 end end end if event[1] == "mouse_scroll" then if event[2] == 1 then -- down if blockindex == #blocks then blockindex = 1 else blockindex = blockindex + 1 end end if event[2] == -1 then -- up if blockindex == 1 then blockindex = #blocks else blockindex = blockindex - 1 end end currentblock = blocks[blockindex] end if event[1] == "key" then if keys.getName(event[2]) == "leftCtrl" then -- menu local action = selopt({ "Save", "Load", "Exit", "Change X size", "Change floor amount", "Change Z size" }, "menu") if action == 3 then return end -- exit if action == 1 then -- save reset() for _,v in pairs(fs.list("designs")) do term.write(v) incline() end write("Enter name: ") local name = read() local file = fs.open("designs/"..name, "w") file.write(textutils.serialiseJSON({ ["dimensions"] = dimensions, ["buf"] = buf })) file.close() end if action == 2 then -- load reset() for _,v in pairs(fs.list("designs")) do term.write(v) incline() end write("Enter name: ") local name = read() if not fs.exists("designs/"..name) or name == "" then printError("Design not found!") else buf,dimensions = load(name) camx,camy,currentfloor = 0,0,1 end end if action == 4 then -- change X size reset() write("Enter new X size: ") local xsize = tonumber(read()) dimensions["x"] = xsize end if action == 5 then -- change floor amount reset() write("Enter new floor amount: ") local ysize = tonumber(read()) dimensions["y"] = ysize end if action == 6 then -- change Z size reset() write("Enter new Z size: ") local zsize = tonumber(read()) dimensions["z"] = zsize end end if keys.getName(event[2]) == "q" and currentfloor < dimensions["y"] then -- go up currentfloor = currentfloor + 1 end if keys.getName(event[2]) == "e" and currentfloor > 1 then -- go down currentfloor = currentfloor - 1 end if keys.getName(event[2]) == "w" and camy < dimensions["z"] then -- pan up camy = camy + 1 end if keys.getName(event[2]) == "s" and camy > -dimensions["z"] then -- pan down camy = camy - 1 end if keys.getName(event[2]) == "a" and camx < dimensions["x"] then -- pan left camx = camx + 1 end if keys.getName(event[2]) == "d" and camx > -dimensions["z"] then -- pan right camx = camx - 1 end end end end local function move(direction) if direction == "left" then turtle.turnLeft() return end if direction == "right" then turtle.turnRight() return end while true do if turtle.getFuelLevel() == 0 then reset() print("Out of fuel! Please insert fuel into current slot.") while true do local suc = turtle.refuel(64) if suc then print("Refuelled. Press enter to continue.") read("") break end os.sleep(1) end end local suc,reason = turtle[direction]() if not suc then printError(reason) print("Resolve the error and press enter to continue.") read("") else break end end end local function place(direction) local func = turtle.place local cfunc = turtle.compare if direction == "down" then func = turtle.placeDown cfunc = turtle.compareDown end if direction == "up" then func = turtle.placeUp cfunc = turtle.compareUp end while true do local suc = func() if not suc then -- next slot if cfunc() and turtle.getItemDetail(turtle.getSelectedSlot()) ~= nil then return end local current = turtle.getSelectedSlot() if current == 16 then turtle.select(1) else turtle.select(current+1) end else break end os.sleep(0) -- yield end end local function placebuf(buf, x, y, z) if buf[posasstring(x,y,z)] == 1 then place("down") end end local function getnearestunplaced(buf, pbuf, cx,cy,cz,clayer, sx,sz) local distance = math.huge local selected = nil for x=1,sx,1 do for y=1,sz,1 do local needplace = buf[posasstring(x,cy,y)] if pbuf[posasstring(x,cy,y)] then needplace = 0 end -- already placed if clayer == 0 or clayer == 2 then needplace = needplace == 1 end if clayer == 1 then needplace = needplace == 1 or needplace == 4 end if clayer == 3 then needplace = needplace == 1 or needplace == 4 or needplace == 3 end if needplace then -- needs to be placed, calculate distance local cd = math.abs(y-cz)+math.abs(x-cx) -- raw distance (amount of blocks between) if cd < distance then distance = cd selected = {x,y} end end end end return selected end local function center(direction, wanted) if direction == 1 and wanted ~= 1 then move("left") return true end if direction == -1 and wanted ~= -1 then move("right") return true end if direction == 0 then return true end return false end local function moveto(x,y,cx,cz,direction) while x ~= cx do if cx < x then -- x center(direction) direction = 0 cx = cx + 1 move("forward") end if cx > x then center(direction) direction = 0 cx = cx - 1 move("back") end end while y ~= cz do if y > cz then -- z if center(direction, 1) then move("right") end cz = cz + 1 move("forward") direction = 1 end if y < cz then if center(direction, -1) then move("left") end cz = cz - 1 move("forward") direction = -1 end end return cx,cz,direction end local function tcontains(x, y) for _,v in pairs(x) do if v == y then return true end end return false end local function render(buf, pbuf, cx,cy,cz, tx,tz, sx,sz, shouldsetlist,invertslist) shouldsetlist = shouldsetlist or { 1, } reset() for x=1,sx,1 do for z=1,sz,1 do local currentbuf = pbuf[posasstring(x, cy, z)] term.setCursorPos(x, z) if currentbuf then -- wall term.blit(" ", colors.toBlit(colors.white), colors.toBlit(colors.white)) elseif (currentbuf == false or currentbuf == nil) and (invertslist and not tcontains(shouldsetlist, buf[posasstring(x, cy, z)]) or tcontains(shouldsetlist, buf[posasstring(x, cy, z)])) then -- nothing term.blit("\127", colors.toBlit(colors.gray), colors.toBlit(colors.black)) end end end term.setCursorPos(cx,cz) term.blit(" ", colors.toBlit(colors.red), colors.toBlit(colors.red)) term.setCursorPos(tx,tz) term.blit(" ", colors.toBlit(colors.lime), colors.toBlit(colors.lime)) end local function printdes(buf, dimensions) move("up") move("forward") local cx = 1 local cz = 1 local direction = 0 for cy=1,dimensions["y"],1 do for clayer=1,3,1 do -- build walls local pbuf = {} while true do local target = getnearestunplaced(buf, pbuf, cx,cy,cz,clayer, dimensions["x"],dimensions["z"]) if not target then break end render(buf, pbuf, cx,cy,cz, target[1],target[2], dimensions["x"],dimensions["z"], {1}) --read("") cx,cz,direction = moveto(target[1],target[2],cx,cz,direction) place("down") pbuf[posasstring(cx,cy,cz)] = true end move("up") end -- build ceiling/floor local pbuf = {} local cbuf = {} for _cz=1,dimensions["z"],1 do for _cx=1,dimensions["x"],1 do if buf[posasstring(_cx,cy,_cz)] ~= 2 then cbuf[posasstring(_cx,0,_cz)] = 1 else cbuf[posasstring(_cx,0,_cz)] = 0 end end end while true do local target = getnearestunplaced(cbuf, pbuf, cx,0,cz,0, dimensions["x"],dimensions["z"]) if not target then break end render(cbuf, pbuf, cx,0,cz, target[1],target[2], dimensions["x"],dimensions["z"], {1}) cx,cz,direction = moveto(target[1],target[2],cx,cz,direction) place("down") pbuf[posasstring(cx,0,cz)] = true end -- return to standard pos but +1 to y cx,cz,direction = moveto(1,1,cx,cz,direction) move("up") end center(direction) end while true do integritycheck() local action = selopt({ "New Design", "Print Design" }, "Select Action") if action == 1 then newdesign() end if action == 2 then reset() for _,v in pairs(fs.list("designs")) do term.write(v) incline() end write("Enter name: ") local name = read() if not fs.exists("designs/"..name) or name == "" then printError("Design not found!") else local buf,dimensions = load(name) write("Calculating fuel cost...") local cost = dimensions["x"]*dimensions["z"]*(dimensions["y"]*4-1) print(tostring(turtle.getFuelLevel()).."/"..tostring(cost)) if turtle.getFuelLevel() < cost then write("WARNING: Not enough fuel! Continue anyway? y/n: ") while true do local ans = read("") if ans == "y" then printdes(buf, dimensions) break elseif ans == "n" then break end end else printdes(buf, dimensions) end end end end