local url = "http://justus.l--n.de:3000/JUFS/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 bufistype(buf, x,y,z, targettype) if buf[posasstring(x,y,z)] == targettype then return true else return targettype == 0 and buf[posasstring(x,y,z)] == nil end end local function fill_getneighbors(x,y,z, sx,sz, seltype,buf) local out = {} if x ~= sx and bufistype(buf, x+1,y,z, seltype) then table.insert(out, {x+1,z}) end if x ~= 1 and bufistype(buf, x-1,y,z, seltype) then table.insert(out, {x-1,z}) end if z ~= sz and bufistype(buf, x,y,z+1, seltype) then table.insert(out, {x,z+1}) end if z ~= 1 and bufistype(buf, x,y,z-1, seltype) then table.insert(out, {x,z-1}) end return out end local function fill(ntype, x,y,z, buf, sx,sz) local inittype = buf[posasstring(x,y,z)] if inittype == ntype then return end -- no work needs to be done local neighbors = fill_getneighbors(x,y,z, sx,sz, inittype,buf) buf[posasstring(x,y,z)] = ntype local processed = 0 local totalneighbors = #neighbors term.setcol(colors.white, colors.black) while #neighbors > 0 do term.setCursorPos(1,1) term.write(tostring(processed).."/"..tostring(totalneighbors)) local cn = table.remove(neighbors, 1) local cx,cz = cn[1],cn[2] local newneigh = fill_getneighbors(cx,y,cz, sx,sz, inittype,buf) for _,v in pairs(newneigh) do table.insert(neighbors, v) buf[posasstring(v[1],y,v[2])] = ntype totalneighbors = totalneighbors + 1 end buf[posasstring(cx,y,cz)] = ntype processed = processed + 1 end 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 local shift = false 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 and 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 if buf[posasstring(x-camx, currentfloor-1, z-camy)] == 2 then term.blit("\127", colors.toBlit(colors.red), colors.toBlit(colors.black)) else term.blit("\127", colors.toBlit(colors.gray), colors.toBlit(colors.black)) end 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 if shift then fill(1, event[3],currentfloor,event[4], buf,dimensions["x"],dimensions["z"]) end buf[posasstring(event[3], currentfloor, event[4])] = 1 end if event[2] == 3 then -- middle button, set special block if shift then fill(currentblock, event[3],currentfloor,event[4], buf,dimensions["x"],dimensions["z"]) end buf[posasstring(event[3], currentfloor, event[4])] = currentblock end if event[2] == 2 then -- right button, erase if shift then fill(0, event[3],currentfloor,event[4], buf,dimensions["x"],dimensions["z"]) end 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]) == "leftShift" then shift = true end 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 if event[1] == "key_up" then if keys.getName(event[2]) == "leftShift" then shift = false end end end end local function move(direction, continousattempt) 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! Trying to refuel.") while true do local suc = turtle.refuel(64) if suc then break end local current = turtle.getSelectedSlot() if current == 16 then turtle.select(1) else turtle.select(current+1) end os.sleep(1) end end local suc,reason = turtle[direction]() if not suc and not continousattempt then printError(reason) print("Resolve the error and press enter to continue.") read("") elseif not suc and continousattempt then reset() printError("Failure moving "..direction.." Continous attempt true.") elseif suc then 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 directiondist(direction, wanted) if direction == 0 then return 1 end if direction == 1 and wanted == -1 then return 2 end if direction == -1 and wanted == 1 then return 2 end if direction ~= wanted then return 1 end -- failsafes if direction == wanted then return 0 end end local function needstobeplaced(placecode, clayer) if clayer == 0 or clayer == 2 then return placecode == 1 end if clayer == 1 then return placecode == 1 or placecode == 4 end if clayer == 3 then return placecode == 1 or placecode == 4 or placecode == 3 end end local function getnearestunplaced(buf, pbuf, cx,cy,cz,clayer, sx,sz, direction) local distance = math.huge local selected = nil for x=1,sx,1 do for y=1,sz,1 do local extracost = 0 if x ~= cx and direction ~= 0 then extracost = extracost + 1 end if y > cz and direction ~= 1 then extracost = extracost + directiondist(direction, 1) end if y < cz and direction ~= -1 then extracost = extracost + directiondist(direction, -1) end local needplace = buf[posasstring(x,cy,y)] if pbuf[posasstring(x,cy,y)] then needplace = 0 end -- already placed needplace = needstobeplaced(needplace, clayer) 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) cd = cd + extracost 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, } local tsx,tsy = term.getSize() local camx,camy = cx-(tsx/2),cz-(tsy/2) reset() for x=1,sx,1 do for z=1,sz,1 do local currentbuf = pbuf[posasstring(x, cy, z)] term.setCursorPos(x-camx, z-camy) 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-camx,cz-camy) term.blit(" ", colors.toBlit(colors.red), colors.toBlit(colors.red)) term.setCursorPos(tx-camx,tz-camy) 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 local setlists = { {1,4}, {1}, {1,3,4}, } 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"], direction) if not target then break end render(buf, pbuf, cx,cy,cz, target[1],target[2], dimensions["x"],dimensions["z"], setlists[clayer]) --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"], direction) 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 cx,cz,direction = moveto(0,1, cx,cz, direction) for _=1,dimensions["y"]*(4)+1,1 do -- go back to starting position move("down") end center(direction) end -- vertical stack printing local function VP_movetoy(y, target) while y > target do move("down", true) y = y - 1 end while y < target do move("up", true) y = y + 1 end return target end local function VP_moveto(x,z, cx,cz,direction) local y = 0 --[[ y lanes for directions: 1: +x 2: -x 3: +z 4: -z ]]-- while x ~= cx do if cx < x then -- x center(direction) direction = 0 y = VP_movetoy(y, 1) cx = cx + 1 move("forward", true) end if cx > x then center(direction) direction = 0 y = VP_movetoy(y, 2) cx = cx - 1 move("back", true) end end while z ~= cz do if z > cz then -- z if center(direction, 1) then move("right") end y = VP_movetoy(y, 3) cz = cz + 1 move("forward", true) direction = 1 end if z < cz then if center(direction, -1) then move("left") end y = VP_movetoy(y, 4) cz = cz - 1 move("forward", true) direction = -1 end end VP_movetoy(y, 0) return cx,cz,direction end local function VP_optimizestack(target) -- go from bottom to top and erase false entries until we encounter a true one. while #target > 0 do if target[1] then break else table.remove(target, 1) end end return target end local function VP_createstack(buf, dimensions) local fout = { ["height"] = dimensions["y"]*(4)+1, ["dimensions"] = dimensions, } local out = {} for x=1,dimensions["x"],1 do for z=1,dimensions["z"],1 do local tempbuf = {} for y=1,dimensions["y"],1 do -- for each y dimension local placecode = buf[posasstring(x,y,z)] for layer=1,3,1 do -- layers table.insert(tempbuf, needstobeplaced(placecode, layer)) end -- ceiling table.insert(tempbuf, buf[posasstring(x,y,z)] ~= 2) end VP_optimizestack(tempbuf) if #tempbuf > 0 then out[posasstring(x,z)] = tempbuf end end end fout["stacks"] = out return fout end local function VP_lengthofdict(target) local out = 0 for _,v in pairs(target) do out = out + 1 end return out end local function VP_splitstack(_stack, x) local stack = _stack["stacks"] local height = _stack["height"] local stacks = {} local spliteverx = math.floor(VP_lengthofdict(stack)/x) local ind = 0 local buf = {} for i,v in pairs(stack) do if ind == spliteverx then ind = 0 table.insert(stacks, buf) buf = {} end buf[i] = v ind = ind + 1 end table.insert(stacks, buf) local out = { height=height, stacks={} } for _,v in pairs(stacks) do table.insert(out["stacks"],v) end return out end --[[local function VP_inspectsplitted(stacks, dimensions) -- I will probably never finish this. local currentselected = 1 while true do reset() -- render local _,y = term.getSize() term.setCursorPos(1,y) term.setcol(colors.yellow, colors.black) term.write("Currentlayer: ") term.write(tostring(currentselected)) local event = table.pack(os.pullEvent()) if event[1] == "key" then end end end]] local function VP_printstack(buf, dimensions, cx,cy,cz,direction) reset() move("up") for _=1,buf["height"]-cy,1 do move("up") end local ox,oz = cx,cz cx,cz,direction = VP_moveto(cx+1,cz, cx,cz,direction) for x=1,dimensions["x"],1 do for z=1,dimensions["z"],1 do local tempbuf = buf["stacks"][posasstring(x,z)] if tempbuf then cx,cz,direction = VP_moveto(x,z, cx,cz,direction) for _=1,#tempbuf,1 do move("down", true) end for _,v in pairs(tempbuf) do if v then place("down") end move("up", true) end move("up", true) end end end VP_moveto(ox,oz, cx,cz,direction) for _=1,buf["height"]+1,1 do move("down") end center(direction, 0) return cx,cz,direction end local function VP_calccost(stacks, height,sx,sz) -- calculate needed blocks local cost = 0 for cx=1,sx,1 do for cz=1,sz,1 do local tempbuf = stacks[posasstring(cx,cz)] if tempbuf then for ind=1,height,1 do if tempbuf[ind] then cost = cost + 1 end end end end end return cost end local function VP_selectpos(design,dimensions,msg, cx,cz) cx,cz = cx or 0, cz or 0 local camx,camz = cx,cz while true do render(design,{}, camx,1,camz, camx,camz, dimensions["x"],dimensions["z"], {1,4}) term.setCursorPos(1,1) term.write(msg) local _,y = term.getSize() term.setCursorPos(1,y) term.write("x: ") term.write(tostring(camx)) term.write(" z: ") term.write(tostring(camz)) local _,key = os.pullEvent("key") key = keys.getName(key) if key == "w" then camz = camz - 1 end if key == "s" then camz = camz + 1 end if key == "a" then camx = camx - 1 end if key == "d" then camx = camx + 1 end if key == "enter" then break end end return camx,camz end local function expwarn() reset() term.write("This mode is experimental! Be cautious!") incline() term.write("Enter to continue.") read("") end local function randomstr(length) local possible = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890" local out = "" for _=1,length,1 do local ind = math.random(#possible) out = out .. string.sub(possible, ind, ind) end return out end while true do integritycheck() local action = selopt({ "New Design", "Print Design", "VPrint Design", "Join VPrint", "Update", }, "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 if action == 3 then expwarn() peripheral.find("modem", rednet.open) 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) local stacks = VP_createstack(buf, dimensions) local height = stacks["height"] print("Stacks created.") local code = randomstr(5) print("Pair code: '"..code.."'") write("Enter amount of partaking turtles: ") local expam = tonumber(read()) write("Press enter to send pair request.") read("") rednet.broadcast({ code=code, }, "HB_vprint_pair") print("Sent. awaiting pair accepts.") local accepted = {} while #accepted < expam do local id,msg = rednet.receive("HB_vprint_pairespond") if msg["code"] == code then table.insert(accepted, id) reset() term.write(tostring(#accepted).."/"..tostring(expam)) end end reset() print("Paired. Splitting stacks and sending out...") stacks = VP_splitstack(stacks, #accepted+1) -- plus 1 because we also build as the master turtle. if #stacks["stacks"] ~= #accepted+1 then printError("Internal error with VP_splitstack. It returned "..#stacks["stacks"].." stacks.") return end for i,v in pairs(accepted) do reset() print("ID "..tostring(v).." needs "..VP_calccost(stacks["stacks"][i], stacks["height"],dimensions["x"],dimensions["z"]).." blocks.") print("Please ensure that turtle has that many blocks and press enter to continue.") read("") rednet.send(v, { stack=stacks["stacks"][i], dimensions=dimensions, height=height }, "HB_vprint_pairacknowledge") end print("This turtle needs "..tostring(VP_calccost(stacks["stacks"][expam+1], stacks["height"],dimensions["x"],dimensions["z"])).." blocks.") print("Please ensure that turtle has that many blocks and press enter to continue.") read("") local x,y = 0,0 for _,v in pairs(accepted) do x,y = VP_selectpos(buf,dimensions,"Select position of turtle "..tostring(v), x,y) rednet.send(v, { cx=x, cz=y, }, "HB_vprint_pairpossend") end reset() write("Ready to begin, press enter to continue") read("") print("Sending begin signal.") for _,v in pairs(accepted) do rednet.send(v, {}, "HB_vprint_begin") end local ownstack = { stacks=stacks["stacks"][expam+1], dimensions=dimensions, height=height } VP_printstack(ownstack,dimensions, 0,0,1,0) end end if action == 4 then expwarn() peripheral.find("modem", rednet.open) reset() term.write("Enter the code: ") local code = read() local masterid = 0 local buf = {} local dimensions = {} local height = 0 local cx,cz = 0,0 while true do -- await pairing request and respond local id,request = rednet.receive("HB_vprint_pair") if request["code"] == code then masterid = id print("Detected master: "..tostring(masterid)) print("Responding...") rednet.send(masterid, { code=code, }, "HB_vprint_pairespond") print("Waiting for response... ID: "..tostring(os.getComputerID())) local id,msg = -1,{} repeat id,msg = rednet.receive("HB_vprint_pairacknowledge") until id == masterid buf = msg["stack"] dimensions = msg["dimensions"] height = msg["height"] break end end print("Received stacks and dimensions. ID: "..tostring(os.getComputerID())) local id,msg = -1,"" -- wait until we get our current position repeat id,msg = rednet.receive("HB_vprint_pairpossend") until id == masterid cx,cz = msg["cx"],msg["cz"] repeat -- wait until we can begin id = rednet.receive("HB_vprint_begin") until id == masterid local ownstack = { stacks=buf, dimensions=dimensions, height=height } VP_printstack(ownstack,dimensions, cx,0,cz,0) end if action == 5 then shell.run("rm", "main.lua") shell.run("wget", url) return end end