stairs.lua
304 lines · 7.3 KB
Fast stair builder
Usage: stairs [headroom] [up/down] [length] [place]
Copy & run
wget https://perlytiara.github.io/turtles.tips/raw/programs/stairs.lua
| 1 | -- stairs.lua - Fast stair builder |
| 2 | -- Usage: stairs [headroom] [up/down] [length] [place] |
| 3 | -- headroom = blocks above each step, length = total steps to build |
| 4 | |
| 5 | local args = {...} |
| 6 | |
| 7 | local function hasTurtle() |
| 8 | return pcall(function() return turtle.getFuelLevel() end) |
| 9 | end |
| 10 | |
| 11 | if not hasTurtle() then |
| 12 | print("Turtle required!") |
| 13 | return |
| 14 | end |
| 15 | |
| 16 | -- Movement helpers |
| 17 | local function df() while turtle.detect() do turtle.dig() end end |
| 18 | local function du() while turtle.detectUp() do turtle.digUp() end end |
| 19 | local function dd() while turtle.detectDown() do turtle.digDown() end end |
| 20 | local function gf() |
| 21 | while not turtle.forward() do |
| 22 | if turtle.detect() then turtle.dig() end |
| 23 | turtle.attack() |
| 24 | end |
| 25 | end |
| 26 | local function gu() |
| 27 | while not turtle.up() do |
| 28 | if turtle.detectUp() then turtle.digUp() end |
| 29 | turtle.attackUp() |
| 30 | end |
| 31 | end |
| 32 | local function gd() |
| 33 | while not turtle.down() do |
| 34 | if turtle.detectDown() then turtle.digDown() end |
| 35 | turtle.attackDown() |
| 36 | end |
| 37 | end |
| 38 | |
| 39 | -- Resource scanning and management |
| 40 | local function scanInventory() |
| 41 | local fuel = 0 |
| 42 | local blocks = 0 |
| 43 | local fuelSlots = {} |
| 44 | local blockSlots = {} |
| 45 | |
| 46 | for i = 1, 16 do |
| 47 | local count = turtle.getItemCount(i) |
| 48 | if count > 0 then |
| 49 | turtle.select(i) |
| 50 | local isFuel = turtle.refuel(0) |
| 51 | if isFuel then |
| 52 | fuel = fuel + count |
| 53 | table.insert(fuelSlots, i) |
| 54 | else |
| 55 | blocks = blocks + count |
| 56 | table.insert(blockSlots, i) |
| 57 | end |
| 58 | end |
| 59 | end |
| 60 | |
| 61 | return { |
| 62 | fuel = fuel, |
| 63 | blocks = blocks, |
| 64 | fuelSlots = fuelSlots, |
| 65 | blockSlots = blockSlots |
| 66 | } |
| 67 | end |
| 68 | |
| 69 | local function findBlockSlot() |
| 70 | for i = 1, 16 do |
| 71 | local count = turtle.getItemCount(i) |
| 72 | if count > 0 then |
| 73 | turtle.select(i) |
| 74 | local isFuel = turtle.refuel(0) |
| 75 | if not isFuel then return i end |
| 76 | end |
| 77 | end |
| 78 | return nil |
| 79 | end |
| 80 | |
| 81 | local function placeFloor() |
| 82 | if not turtle.detectDown() then |
| 83 | local slot = findBlockSlot() |
| 84 | if slot then |
| 85 | turtle.select(slot) |
| 86 | turtle.placeDown() |
| 87 | end |
| 88 | end |
| 89 | end |
| 90 | |
| 91 | -- Fuel management |
| 92 | local function refuel(target) |
| 93 | if turtle.getFuelLevel() == "unlimited" then return true end |
| 94 | |
| 95 | local current = turtle.getSelectedSlot() |
| 96 | for i = 1, 16 do |
| 97 | if turtle.getItemCount(i) > 0 then |
| 98 | turtle.select(i) |
| 99 | while turtle.getItemCount(i) > 0 and turtle.getFuelLevel() < target do |
| 100 | if not turtle.refuel(1) then break end |
| 101 | end |
| 102 | if turtle.getFuelLevel() >= target then break end |
| 103 | end |
| 104 | end |
| 105 | turtle.select(current) |
| 106 | |
| 107 | if turtle.getFuelLevel() < target then |
| 108 | print("Need fuel! Current: " .. turtle.getFuelLevel()) |
| 109 | print("Add coal/charcoal to any slot") |
| 110 | while turtle.getFuelLevel() < target do |
| 111 | sleep(1) |
| 112 | for i = 1, 16 do |
| 113 | if turtle.getItemCount(i) > 0 then |
| 114 | turtle.select(i) |
| 115 | if turtle.refuel(1) then break end |
| 116 | end |
| 117 | end |
| 118 | end |
| 119 | end |
| 120 | return true |
| 121 | end |
| 122 | |
| 123 | -- Clear headroom |
| 124 | local function clearUp(h) |
| 125 | du(); gu() |
| 126 | for i = 1, h - 1 do |
| 127 | du() |
| 128 | if i < h - 1 then gu() end |
| 129 | end |
| 130 | for i = 1, math.max(h - 2, 0) do gd() end |
| 131 | end |
| 132 | |
| 133 | local function clearDown(h) |
| 134 | for i = 1, h - 1 do gu() end |
| 135 | du() |
| 136 | for i = 1, h - 1 do gd() end |
| 137 | end |
| 138 | |
| 139 | -- Parse arguments |
| 140 | local headroom = 3 -- blocks above each step |
| 141 | local goUp = true |
| 142 | local length = nil -- number of steps to build |
| 143 | local autoPlace = false |
| 144 | local autoLength = false -- use surface detection for up, or blocks available |
| 145 | |
| 146 | if #args >= 1 then |
| 147 | headroom = math.max(1, tonumber(args[1]) or 3) |
| 148 | for i = 2, #args do |
| 149 | local arg = string.lower(args[i]) |
| 150 | local num = tonumber(args[i]) |
| 151 | if num then |
| 152 | length = math.max(1, num) |
| 153 | autoLength = false |
| 154 | elseif arg == "down" then |
| 155 | goUp = false |
| 156 | elseif arg == "up" then |
| 157 | goUp = true |
| 158 | elseif arg == "place" then |
| 159 | autoPlace = true |
| 160 | elseif arg == "auto" then |
| 161 | autoLength = true |
| 162 | end |
| 163 | end |
| 164 | else |
| 165 | -- Interactive prompts |
| 166 | term.clear() |
| 167 | term.setCursorPos(1, 1) |
| 168 | print("Stair Builder") |
| 169 | |
| 170 | -- Scan resources first |
| 171 | local resources = scanInventory() |
| 172 | print("Resources: " .. resources.fuel .. " fuel, " .. resources.blocks .. " blocks") |
| 173 | |
| 174 | write("Headroom (blocks above steps) [3]: ") |
| 175 | local h = read() |
| 176 | if h ~= "" then headroom = math.max(1, tonumber(h) or 3) end |
| 177 | |
| 178 | write("Direction (u/d) [u]: ") |
| 179 | local dir = string.lower(read()) |
| 180 | goUp = not (dir == "d" or dir == "down") |
| 181 | |
| 182 | -- Length options |
| 183 | local maxSteps = math.floor(resources.blocks / (autoPlace and 1 or 0.1)) -- rough estimate |
| 184 | if goUp then |
| 185 | write("Length - steps/surface/auto [surface]: ") |
| 186 | local lengthInput = string.lower(read()) |
| 187 | if lengthInput == "surface" or lengthInput == "" then |
| 188 | autoLength = true |
| 189 | length = nil |
| 190 | elseif lengthInput == "auto" then |
| 191 | autoLength = true |
| 192 | length = maxSteps |
| 193 | else |
| 194 | local num = tonumber(lengthInput) |
| 195 | if num then |
| 196 | length = math.max(1, num) |
| 197 | autoLength = false |
| 198 | else |
| 199 | autoLength = true |
| 200 | end |
| 201 | end |
| 202 | else |
| 203 | write("Depth (steps down) [32]: ") |
| 204 | local s = read() |
| 205 | length = math.max(1, tonumber(s) or 32) |
| 206 | autoLength = false |
| 207 | end |
| 208 | |
| 209 | write("Place floor blocks? (y/n) [n]: ") |
| 210 | local place = string.lower(read()) |
| 211 | autoPlace = (place == "y" or place == "yes") |
| 212 | end |
| 213 | |
| 214 | -- Set defaults if not specified |
| 215 | if not length and not autoLength then |
| 216 | if goUp then |
| 217 | autoLength = true -- surface detection for up |
| 218 | else |
| 219 | length = 32 -- default depth for down |
| 220 | end |
| 221 | end |
| 222 | |
| 223 | -- Resource check and planning |
| 224 | local resources = scanInventory() |
| 225 | print("Building " .. (goUp and "up" or "down") .. " stairs") |
| 226 | print("Headroom: " .. headroom .. " blocks above each step") |
| 227 | |
| 228 | if autoLength and goUp then |
| 229 | print("Mode: to surface (auto-detect)") |
| 230 | elseif autoLength then |
| 231 | print("Length: auto (max " .. math.floor(resources.blocks / (autoPlace and 1 or 0.1)) .. " steps)") |
| 232 | else |
| 233 | print("Length: " .. (length or "unknown") .. " steps") |
| 234 | end |
| 235 | |
| 236 | print("Resources: " .. resources.fuel .. " fuel, " .. resources.blocks .. " blocks") |
| 237 | |
| 238 | -- Estimate what we can build |
| 239 | local blocksPerStep = autoPlace and 1 or 0 |
| 240 | local maxPossibleSteps = blocksPerStep > 0 and math.floor(resources.blocks / blocksPerStep) or 999 |
| 241 | if autoPlace and maxPossibleSteps < (length or 32) then |
| 242 | print("Warning: Only enough blocks for " .. maxPossibleSteps .. " steps with floor placement") |
| 243 | end |
| 244 | |
| 245 | -- Initial fuel check |
| 246 | local fuelNeeded = (length or 64) * 3 |
| 247 | if resources.fuel * 80 < fuelNeeded then -- rough fuel value estimate |
| 248 | print("Warning: May need more fuel") |
| 249 | end |
| 250 | refuel(math.min(fuelNeeded, turtle.getFuelLevel() + 100)) |
| 251 | |
| 252 | local step = 0 |
| 253 | local openStreak = 0 |
| 254 | |
| 255 | while true do |
| 256 | step = step + 1 |
| 257 | |
| 258 | -- Refuel check every 8 steps |
| 259 | if step % 8 == 0 then |
| 260 | refuel(turtle.getFuelLevel() + 16) |
| 261 | end |
| 262 | |
| 263 | -- Build step |
| 264 | if goUp then |
| 265 | df(); gf() |
| 266 | if autoPlace then placeFloor() end |
| 267 | clearUp(headroom) |
| 268 | else |
| 269 | df(); gf(); dd(); gd() |
| 270 | if autoPlace then placeFloor() end |
| 271 | clearDown(headroom) |
| 272 | end |
| 273 | |
| 274 | -- Exit conditions |
| 275 | if not autoLength and step >= (length or 32) then |
| 276 | break |
| 277 | end |
| 278 | |
| 279 | -- Surface detection for up stairs |
| 280 | if autoLength and goUp then |
| 281 | if not turtle.detect() and not turtle.detectUp() then |
| 282 | openStreak = openStreak + 1 |
| 283 | else |
| 284 | openStreak = 0 |
| 285 | end |
| 286 | if openStreak >= 5 then break end |
| 287 | end |
| 288 | |
| 289 | -- Block limit check |
| 290 | if autoPlace then |
| 291 | local currentResources = scanInventory() |
| 292 | if currentResources.blocks <= 0 then |
| 293 | print("Out of blocks!") |
| 294 | break |
| 295 | end |
| 296 | end |
| 297 | |
| 298 | if step >= 1000 then |
| 299 | print("Safety stop at 1000 steps") |
| 300 | break |
| 301 | end |
| 302 | end |
| 303 | |
| 304 | print("Done! Built " .. step .. " steps") |