Files
cc_housebuild/src/main.lua
2026-02-18 20:15:57 +01:00

805 lines
25 KiB
Lua

-- 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! 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 and not continousattempt then
printError(reason)
print("Resolve the error and press enter to continue.")
read("")
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 top to bottom and erase false entries until we encounter a true one.
local index = #target
while #target > 0 do
if target[index] then break else
table.remove(target, index)
index = index - 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(_cx,cy,_cz)] ~= 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_splitstack(_stack, x)
local stack = _stack["stacks"]
local dimensions = _stack["dimensions"]
local sx = dimensions["sx"]
local sz = dimensions["sz"]
local height = _stack["height"]
local stacks = {}
local unevenstack = {}
local stackmod = math.fmod(#stack, x)
local stackdiv = math.floor(#stack/x)
if stackmod ~= 0 then
for tempstackindex=0,stackmod,1 do
table.insert(unevenstack, stack[posasstring(math.fmod(tempstackindex, sx),math.floor(tempstackindex/sx))])
end
end
if stackdiv ~= 0 then
local eind = 0
local x2 = x
if stackmod ~= 0 then x2 = x - 1 end
for _=0,x2,1 do
local buf = {}
for stackind=eind,stackdiv+eind,1 do
table.insert(buf, stack[posasstring(math.fmod(stackind, sx),math.floor(stackind/sx))])
end
table.insert(stacks, buf)
eind = eind + stackdiv
end
end
local out = {}
if #unevenstack > 0 then table.insert(out,unevenstack) end
for _,v in pairs(stacks) do
table.insert(out,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,cz,direction)
reset()
move("up")
for _=1,buf["height"],1 do
move("up")
end
cx,cz,direction = VP_moveto(cx+1,cz, cx,cz,direction)
local ox,oz = cx,cz
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,buf["height"],1 do
move("down")
end
for _,v in pairs(tempbuf) do
if v then
place("down")
end
move("up")
end
move("up")
end
end
end
for _=1,buf["height"],1 do
move("down")
end
VP_moveto(ox,oz, cx,cz,direction)
return cx,cz,direction
end
while true do
integritycheck()
local action = selopt({
"New Design",
"Print Design",
"VPrint 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
if action == 3 then
reset()
term.write("This mode is experimental! Be cautious!")
incline()
term.write("Enter to continue.")
read("")
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 cx,cz,direction = 1,1,0
VP_printstack(stacks, dimensions, cx,cz,direction)
end
end
end