AdvancedChunkyTurtle.lua
470 lines · 15.7 KB
only follows and keeps chunks loaded
Copy & run
wget https://perlytiara.github.io/turtles.tips/raw/programs/perlytiara/tClear/AdvancedChunkyTurtle.lua
| 1 | --{program="AdvancedChunkyTurtle",version="2.0",date="2024-12-19"} |
| 2 | --------------------------------------- |
| 3 | -- Advanced Chunky Turtle by AI Assistant |
| 4 | -- 2024-12-19, v2.0 Enhanced chunky turtle with improved following |
| 5 | --------------------------------------- |
| 6 | |
| 7 | --------------------------------------- |
| 8 | ---- DESCRIPTION ---------------------- |
| 9 | --------------------------------------- |
| 10 | -- Enhanced chunky turtle with advanced wireless communication |
| 11 | -- Follows mining turtle to keep chunks loaded and prevent turtle breaking |
| 12 | -- Features improved movement, better error handling, and status reporting |
| 13 | -- Based on original tClearChunky.lua with wireless enhancements |
| 14 | |
| 15 | --------------------------------------- |
| 16 | ---- ASSUMPTIONS ---------------------- |
| 17 | --------------------------------------- |
| 18 | -- Requires a wireless turtle with wireless modem |
| 19 | -- Should be placed one block to the right of the mining turtle |
| 20 | -- No tools needed - only follows and keeps chunks loaded |
| 21 | |
| 22 | --------------------------------------- |
| 23 | ---- VARIABLES ------------------------ |
| 24 | --------------------------------------- |
| 25 | local cVersion = "v2.0" |
| 26 | local cPrgName = "AdvancedChunkyTurtle" |
| 27 | local blnDebugPrint = true |
| 28 | |
| 29 | -- Communication settings |
| 30 | local protocol = "advanced-mining" |
| 31 | local chunkyProtocol = "tclear-chunky" |
| 32 | local masterTurtleId = nil |
| 33 | local controllerId = nil |
| 34 | local isActive = false |
| 35 | local isPaired = false |
| 36 | |
| 37 | -- Position tracking |
| 38 | local position = {x = 0, y = 0, z = 1, facing = 0} -- relative to master (to the right) |
| 39 | local targetPosition = {x = 0, y = 0, z = 1, facing = 0} |
| 40 | local lastKnownMasterPosition = {x = 0, y = 0, z = 0, facing = 0} |
| 41 | |
| 42 | -- Operation settings |
| 43 | local chunkLoadingInterval = 2 -- seconds between chunk loading signals |
| 44 | local statusReportInterval = 10 -- seconds between status reports |
| 45 | local lastChunkLoad = 0 |
| 46 | local lastStatusReport = 0 |
| 47 | local lastBroadcast = 0 |
| 48 | local broadcastInterval = 5 -- seconds |
| 49 | |
| 50 | -- Movement settings |
| 51 | local maxRetries = 3 |
| 52 | local retryDelay = 0.5 -- seconds |
| 53 | |
| 54 | --------------------------------------- |
| 55 | ---- Utility Functions ---------------- |
| 56 | --------------------------------------- |
| 57 | local function debugPrint(str) |
| 58 | if blnDebugPrint then |
| 59 | print("[ChunkyTurtle] " .. str) |
| 60 | end |
| 61 | end |
| 62 | |
| 63 | local function findModem() |
| 64 | for _, p in pairs(rs.getSides()) do |
| 65 | if peripheral.isPresent(p) and peripheral.getType(p) == "modem" then |
| 66 | return p |
| 67 | end |
| 68 | end |
| 69 | error("No wireless modem attached to this turtle.") |
| 70 | end |
| 71 | |
| 72 | local function sendMessage(targetId, message, customProtocol) |
| 73 | local protocolToUse = customProtocol or protocol |
| 74 | rednet.send(targetId, message, protocolToUse) |
| 75 | debugPrint("Sent to " .. targetId .. ": " .. textutils.serialize(message)) |
| 76 | end |
| 77 | |
| 78 | local function sendStatus(status, data) |
| 79 | local targetId = controllerId or masterTurtleId |
| 80 | if targetId then |
| 81 | sendMessage(targetId, { |
| 82 | type = "status", |
| 83 | status = status, |
| 84 | id = os.getComputerID(), |
| 85 | position = position, |
| 86 | data = data or {}, |
| 87 | timestamp = os.time() |
| 88 | }) |
| 89 | end |
| 90 | end |
| 91 | |
| 92 | local function sendChunkLoad() |
| 93 | if masterTurtleId then |
| 94 | sendMessage(masterTurtleId, { |
| 95 | type = "chunk_load", |
| 96 | id = os.getComputerID(), |
| 97 | position = position, |
| 98 | timestamp = os.time() |
| 99 | }, chunkyProtocol) |
| 100 | end |
| 101 | end |
| 102 | |
| 103 | --------------------------------------- |
| 104 | ---- Movement Functions --------------- |
| 105 | --------------------------------------- |
| 106 | local function safeMove(moveFunction, direction) |
| 107 | local retries = 0 |
| 108 | while retries < maxRetries do |
| 109 | if moveFunction() then |
| 110 | return true |
| 111 | else |
| 112 | retries = retries + 1 |
| 113 | if retries < maxRetries then |
| 114 | debugPrint("Move failed, retrying " .. direction .. " (attempt " .. retries .. ")") |
| 115 | sleep(retryDelay) |
| 116 | end |
| 117 | end |
| 118 | end |
| 119 | debugPrint("Failed to move " .. direction .. " after " .. maxRetries .. " attempts") |
| 120 | return false |
| 121 | end |
| 122 | |
| 123 | local function safeDig(digFunction, direction) |
| 124 | local retries = 0 |
| 125 | while retries < maxRetries do |
| 126 | if digFunction() then |
| 127 | return true |
| 128 | else |
| 129 | retries = retries + 1 |
| 130 | if retries < maxRetries then |
| 131 | debugPrint("Dig failed, retrying " .. direction .. " (attempt " .. retries .. ")") |
| 132 | sleep(retryDelay) |
| 133 | end |
| 134 | end |
| 135 | end |
| 136 | debugPrint("Failed to dig " .. direction .. " after " .. maxRetries .. " attempts") |
| 137 | return false |
| 138 | end |
| 139 | |
| 140 | local function moveTo(targetX, targetY, targetZ, targetFacing) |
| 141 | -- Calculate relative movement needed from current position to target |
| 142 | local dx = targetX - position.x |
| 143 | local dy = targetY - position.y |
| 144 | local dz = targetZ - position.z |
| 145 | local dfacing = (targetFacing - position.facing) % 4 |
| 146 | |
| 147 | debugPrint("Moving from (" .. position.x .. "," .. position.y .. "," .. position.z .. ") to (" .. targetX .. "," .. targetY .. "," .. targetZ .. ")") |
| 148 | debugPrint("Delta: dx=" .. dx .. " dy=" .. dy .. " dz=" .. dz .. " dfacing=" .. dfacing) |
| 149 | |
| 150 | -- Turn to correct facing first |
| 151 | if dfacing == 1 then |
| 152 | turtle.turnRight() |
| 153 | position.facing = (position.facing + 1) % 4 |
| 154 | elseif dfacing == 2 then |
| 155 | turtle.turnRight() |
| 156 | turtle.turnRight() |
| 157 | position.facing = (position.facing + 2) % 4 |
| 158 | elseif dfacing == 3 then |
| 159 | turtle.turnLeft() |
| 160 | position.facing = (position.facing - 1) % 4 |
| 161 | end |
| 162 | |
| 163 | -- Move vertically first (up) |
| 164 | while dy > 0 do |
| 165 | if safeMove(turtle.up, "up") then |
| 166 | dy = dy - 1 |
| 167 | position.y = position.y + 1 |
| 168 | debugPrint("Moved up to y=" .. position.y) |
| 169 | else |
| 170 | debugPrint("Cannot move up, trying to dig") |
| 171 | if safeDig(turtle.digUp, "up") then |
| 172 | sleep(0.1) |
| 173 | if safeMove(turtle.up, "up") then |
| 174 | dy = dy - 1 |
| 175 | position.y = position.y + 1 |
| 176 | debugPrint("Moved up to y=" .. position.y) |
| 177 | else |
| 178 | debugPrint("Still blocked after digging up") |
| 179 | break |
| 180 | end |
| 181 | else |
| 182 | debugPrint("Cannot dig up, giving up") |
| 183 | break |
| 184 | end |
| 185 | end |
| 186 | end |
| 187 | |
| 188 | -- Move vertically (down) |
| 189 | while dy < 0 do |
| 190 | if safeMove(turtle.down, "down") then |
| 191 | dy = dy + 1 |
| 192 | position.y = position.y - 1 |
| 193 | debugPrint("Moved down to y=" .. position.y) |
| 194 | else |
| 195 | debugPrint("Cannot move down, trying to dig") |
| 196 | if safeDig(turtle.digDown, "down") then |
| 197 | sleep(0.1) |
| 198 | if safeMove(turtle.down, "down") then |
| 199 | dy = dy + 1 |
| 200 | position.y = position.y - 1 |
| 201 | debugPrint("Moved down to y=" .. position.y) |
| 202 | else |
| 203 | debugPrint("Still blocked after digging down") |
| 204 | break |
| 205 | end |
| 206 | else |
| 207 | debugPrint("Cannot dig down, giving up") |
| 208 | break |
| 209 | end |
| 210 | end |
| 211 | end |
| 212 | |
| 213 | -- Move horizontally - handle X movement (forward/backward relative to facing) |
| 214 | while dx > 0 do |
| 215 | if safeMove(turtle.forward, "forward") then |
| 216 | dx = dx - 1 |
| 217 | position.x = position.x + 1 |
| 218 | debugPrint("Moved forward to x=" .. position.x) |
| 219 | else |
| 220 | debugPrint("Blocked forward, trying to dig") |
| 221 | if safeDig(turtle.dig, "forward") then |
| 222 | sleep(0.1) |
| 223 | if safeMove(turtle.forward, "forward") then |
| 224 | dx = dx - 1 |
| 225 | position.x = position.x + 1 |
| 226 | debugPrint("Moved forward to x=" .. position.x) |
| 227 | else |
| 228 | debugPrint("Still blocked after digging forward") |
| 229 | break |
| 230 | end |
| 231 | else |
| 232 | debugPrint("Cannot dig forward, giving up") |
| 233 | break |
| 234 | end |
| 235 | end |
| 236 | end |
| 237 | |
| 238 | while dx < 0 do |
| 239 | -- Turn around to move backward |
| 240 | turtle.turnLeft() |
| 241 | turtle.turnLeft() |
| 242 | if safeMove(turtle.forward, "backward") then |
| 243 | dx = dx + 1 |
| 244 | position.x = position.x - 1 |
| 245 | debugPrint("Moved backward to x=" .. position.x) |
| 246 | else |
| 247 | debugPrint("Blocked backward, trying to dig") |
| 248 | if safeDig(turtle.dig, "backward") then |
| 249 | sleep(0.1) |
| 250 | if safeMove(turtle.forward, "forward") then |
| 251 | dx = dx + 1 |
| 252 | position.x = position.x - 1 |
| 253 | debugPrint("Moved backward to x=" .. position.x) |
| 254 | else |
| 255 | debugPrint("Still blocked after digging backward") |
| 256 | end |
| 257 | else |
| 258 | debugPrint("Cannot dig backward, giving up") |
| 259 | end |
| 260 | end |
| 261 | turtle.turnLeft() |
| 262 | turtle.turnLeft() |
| 263 | if dx < 0 then break end -- If still can't move, give up |
| 264 | end |
| 265 | |
| 266 | -- Move sideways - handle Z movement (left/right relative to facing) |
| 267 | while dz > 0 do |
| 268 | turtle.turnRight() |
| 269 | if safeMove(turtle.forward, "right") then |
| 270 | dz = dz - 1 |
| 271 | position.z = position.z + 1 |
| 272 | debugPrint("Moved right to z=" .. position.z) |
| 273 | else |
| 274 | debugPrint("Blocked right, trying to dig") |
| 275 | if safeDig(turtle.dig, "right") then |
| 276 | sleep(0.1) |
| 277 | if safeMove(turtle.forward, "right") then |
| 278 | dz = dz - 1 |
| 279 | position.z = position.z + 1 |
| 280 | debugPrint("Moved right to z=" .. position.z) |
| 281 | else |
| 282 | debugPrint("Still blocked after digging right") |
| 283 | end |
| 284 | else |
| 285 | debugPrint("Cannot dig right, giving up") |
| 286 | end |
| 287 | end |
| 288 | turtle.turnLeft() |
| 289 | if dz > 0 then break end -- If still can't move, give up |
| 290 | end |
| 291 | |
| 292 | while dz < 0 do |
| 293 | turtle.turnLeft() |
| 294 | if safeMove(turtle.forward, "left") then |
| 295 | dz = dz + 1 |
| 296 | position.z = position.z - 1 |
| 297 | debugPrint("Moved left to z=" .. position.z) |
| 298 | else |
| 299 | debugPrint("Blocked left, trying to dig") |
| 300 | if safeDig(turtle.dig, "left") then |
| 301 | sleep(0.1) |
| 302 | if safeMove(turtle.forward, "left") then |
| 303 | dz = dz + 1 |
| 304 | position.z = position.z - 1 |
| 305 | debugPrint("Moved left to z=" .. position.z) |
| 306 | else |
| 307 | debugPrint("Still blocked after digging left") |
| 308 | end |
| 309 | else |
| 310 | debugPrint("Cannot dig left, giving up") |
| 311 | end |
| 312 | end |
| 313 | turtle.turnRight() |
| 314 | if dz < 0 then break end -- If still can't move, give up |
| 315 | end |
| 316 | |
| 317 | -- Update final facing |
| 318 | position.facing = targetFacing |
| 319 | debugPrint("Final position: (" .. position.x .. "," .. position.y .. "," .. position.z .. ") facing=" .. position.facing) |
| 320 | |
| 321 | -- Update target position |
| 322 | targetPosition = {x = targetX, y = targetY, z = targetZ, facing = targetFacing} |
| 323 | end |
| 324 | |
| 325 | --------------------------------------- |
| 326 | ---- Message Processing --------------- |
| 327 | --------------------------------------- |
| 328 | local function processMessage(senderId, message, msgProtocol) |
| 329 | if message.type == "discover" then |
| 330 | -- Respond to discovery request |
| 331 | sendMessage(senderId, { |
| 332 | type = "chunky_turtle_available", |
| 333 | name = "Advanced Chunky Turtle", |
| 334 | fuel = turtle.getFuelLevel(), |
| 335 | timestamp = os.time() |
| 336 | }) |
| 337 | return true |
| 338 | |
| 339 | elseif message.type == "start_chunky" then |
| 340 | -- Start chunky mode |
| 341 | masterTurtleId = message.masterId |
| 342 | controllerId = senderId |
| 343 | isActive = true |
| 344 | isPaired = true |
| 345 | |
| 346 | print("SUCCESS: Paired with master turtle " .. masterTurtleId) |
| 347 | print("Chunky turtle is now active and ready to follow!") |
| 348 | |
| 349 | sendStatus("paired", {chunkyId = os.getComputerID()}) |
| 350 | |
| 351 | -- Send ready confirmation |
| 352 | sendMessage(controllerId, { |
| 353 | type = "chunky_ready", |
| 354 | chunkyId = os.getComputerID(), |
| 355 | timestamp = os.time() |
| 356 | }) |
| 357 | |
| 358 | return true |
| 359 | |
| 360 | elseif message.type == "move" then |
| 361 | if isActive and message.target then |
| 362 | debugPrint("Moving to " .. message.target.x .. "," .. message.target.y .. "," .. message.target.z) |
| 363 | moveTo(message.target.x, message.target.y, message.target.z, message.target.facing or 0) |
| 364 | sendStatus("moved", {position = position}) |
| 365 | return true |
| 366 | end |
| 367 | |
| 368 | elseif message.type == "stop" or message.type == "stop_operation" then |
| 369 | isActive = false |
| 370 | isPaired = false |
| 371 | debugPrint("Stopped by master turtle") |
| 372 | sendStatus("stopped", {}) |
| 373 | return true |
| 374 | |
| 375 | elseif message.type == "ping" then |
| 376 | sendStatus("alive", {position = position}) |
| 377 | return true |
| 378 | end |
| 379 | |
| 380 | return false |
| 381 | end |
| 382 | |
| 383 | --------------------------------------- |
| 384 | ---- Main Program ---------------------- |
| 385 | --------------------------------------- |
| 386 | local function main() |
| 387 | term.clear() |
| 388 | term.setCursorPos(1, 1) |
| 389 | |
| 390 | print("==========================================") |
| 391 | print(" Advanced Chunky Turtle " .. cVersion) |
| 392 | print("==========================================") |
| 393 | print() |
| 394 | |
| 395 | -- Initialize rednet |
| 396 | local modemSide = findModem() |
| 397 | rednet.open(modemSide) |
| 398 | print("Rednet initialized on " .. modemSide) |
| 399 | |
| 400 | local thisId = os.getComputerID() |
| 401 | print("Chunky turtle ID: " .. thisId) |
| 402 | print() |
| 403 | print("Waiting for pairing with master turtle...") |
| 404 | print("Press Ctrl+T to exit") |
| 405 | |
| 406 | -- Send initial broadcast |
| 407 | sendMessage(0, { |
| 408 | type = "chunky_turtle_available", |
| 409 | id = thisId, |
| 410 | timestamp = os.time() |
| 411 | }) |
| 412 | |
| 413 | print("Sent initial broadcast - waiting for master turtle...") |
| 414 | |
| 415 | -- Main loop |
| 416 | while true do |
| 417 | local timer = os.startTimer(0.1) -- Check for messages every 0.1 seconds |
| 418 | |
| 419 | -- Handle rednet messages |
| 420 | local senderId, message, msgProtocol = rednet.receive(0.1) |
| 421 | if senderId then |
| 422 | -- Accept messages on multiple protocols |
| 423 | if msgProtocol == protocol or msgProtocol == chunkyProtocol or msgProtocol == "tclear-run" or msgProtocol == "tclear" or msgProtocol == nil then |
| 424 | local handled = processMessage(senderId, message, msgProtocol) |
| 425 | if handled then |
| 426 | debugPrint("Processed message from " .. senderId) |
| 427 | end |
| 428 | end |
| 429 | end |
| 430 | |
| 431 | -- Send chunk loading signal periodically |
| 432 | local currentTime = os.time() |
| 433 | if isActive and (currentTime - lastChunkLoad) >= chunkLoadingInterval then |
| 434 | sendChunkLoad() |
| 435 | lastChunkLoad = currentTime |
| 436 | end |
| 437 | |
| 438 | -- Send status reports periodically |
| 439 | if isPaired and (currentTime - lastStatusReport) >= statusReportInterval then |
| 440 | sendStatus("alive", {position = position, fuel = turtle.getFuelLevel()}) |
| 441 | lastStatusReport = currentTime |
| 442 | end |
| 443 | |
| 444 | -- Send periodic broadcasts if not paired yet |
| 445 | if not isPaired and (currentTime - lastBroadcast) >= broadcastInterval then |
| 446 | sendMessage(0, { |
| 447 | type = "chunky_turtle_available", |
| 448 | id = thisId, |
| 449 | timestamp = currentTime |
| 450 | }) |
| 451 | print("Sent broadcast - still waiting for master turtle...") |
| 452 | lastBroadcast = currentTime |
| 453 | end |
| 454 | |
| 455 | -- Handle timer events (cleanup) |
| 456 | local event, timerId = os.pullEvent("timer") |
| 457 | if timerId == timer then |
| 458 | -- Timer expired, continue loop |
| 459 | end |
| 460 | end |
| 461 | end |
| 462 | |
| 463 | -- Run main program |
| 464 | local ok, err = pcall(main) |
| 465 | if not ok then |
| 466 | print("Error: " .. tostring(err)) |
| 467 | print("Press any key to exit...") |
| 468 | os.pullEvent("key") |
| 469 | end |
| 470 |