-- Server-Driven Turtle v5 (Pure Eval Protocol) -- All behavior is driven by the server via eval commands. -- This script only handles: eval execution, status broadcasting, -- GPS tracking, inventory/peripheral events. local CHANNEL_RECEIVE = 100 local CHANNEL_SEND = 101 local STATUS_CHANNEL = 102 -- State tracking (lightweight - server drives everything) local state = { position = nil, homePosition = nil, fuel = 0, inventory = {}, facing = 0, lastStatusUpdate = 0, } -- Configuration local config = { statusUpdateInterval = 5, } -- Check for modem local modem = peripheral.find("modem") if not modem then error("No wireless modem found!") end modem.open(CHANNEL_RECEIVE) print("Server-Driven Turtle v5 (Pure Eval Protocol)") print("ID: " .. os.getComputerID()) print("Modem opened on channel " .. CHANNEL_RECEIVE) -- Store facing in global for eval access _G._turtleFacing = 0 -- ========== GPS Functions ========== local function updatePosition() local x, y, z = gps.locate(2) if x then state.position = {x = math.floor(x), y = math.floor(y), z = math.floor(z)} return true end return false end -- ========== Fuel & Inventory ========== local function updateFuel() state.fuel = turtle.getFuelLevel() return state.fuel end local function updateInventory() state.inventory = {} for slot = 1, 16 do local item = turtle.getItemDetail(slot) if item then table.insert(state.inventory, { slot = slot, name = item.name, count = item.count }) end end end -- ========== Status Broadcasting ========== local function broadcastStatus() updateFuel() updateInventory() local surroundings = {} local hasBlock, data hasBlock, data = turtle.inspect() if hasBlock then surroundings.forward = {name = data.name, metadata = data.metadata or 0} end hasBlock, data = turtle.inspectUp() if hasBlock then surroundings.up = {name = data.name, metadata = data.metadata or 0} end hasBlock, data = turtle.inspectDown() if hasBlock then surroundings.down = {name = data.name, metadata = data.metadata or 0} end local statusPacket = { type = "status", turtleID = os.getComputerID(), position = state.position, homePosition = state.homePosition, fuel = state.fuel, inventoryCount = #state.inventory, inventory = state.inventory, facing = state.facing, surroundings = surroundings, evalSupported = true, label = os.getComputerLabel(), } modem.transmit(STATUS_CHANNEL, CHANNEL_RECEIVE, statusPacket) end -- ========== Eval/Response Protocol ========== local function executeEval(uuid, code) local fn, compileError = load(code, "eval", "t") if not fn then modem.transmit(CHANNEL_SEND, CHANNEL_RECEIVE, { type = "eval_response", uuid = uuid, result = nil, error = "Compile error: " .. tostring(compileError), turtleID = os.getComputerID() }) return end local results = table.pack(pcall(fn)) local ok = results[1] if ok then local returnVal if results.n <= 2 then returnVal = results[2] else returnVal = {} for i = 2, results.n do returnVal[i - 1] = results[i] end end modem.transmit(CHANNEL_SEND, CHANNEL_RECEIVE, { type = "eval_response", uuid = uuid, result = returnVal, error = nil, turtleID = os.getComputerID() }) else modem.transmit(CHANNEL_SEND, CHANNEL_RECEIVE, { type = "eval_response", uuid = uuid, result = nil, error = "Runtime error: " .. tostring(results[2]), turtleID = os.getComputerID() }) end end -- ========== Message Processing ========== local function processMessage(channel, message) if type(message) ~= "table" then return end local myID = os.getComputerID() if message.target and message.target ~= myID then return end if message.type == "eval" and message.uuid and message.code then print("Eval: " .. message.uuid:sub(1, 8) .. "...") executeEval(message.uuid, message.code) elseif message.type == "home_position" and message.turtleID == myID then if message.homePosition then state.homePosition = message.homePosition print("Home synced from server") end elseif message.type == "home_set_confirm" and message.turtleID == myID then if message.homePosition then state.homePosition = message.homePosition print("Home confirmed by server") end elseif message.type == "rename" and message.name then os.setComputerLabel(message.name) print("Renamed to: " .. message.name) broadcastStatus() end end -- ========== Sync Home ========== local function syncHomeWithServer() modem.transmit(CHANNEL_SEND, CHANNEL_RECEIVE, { type = "request_home", turtleID = os.getComputerID() }) return true end -- ========== Initialization ========== print("Initializing...") local x, y, z = gps.locate(5) if x then state.position = {x = math.floor(x), y = math.floor(y), z = math.floor(z)} print("GPS: OK - " .. state.position.x .. "," .. state.position.y .. "," .. state.position.z) else print("GPS: Not available") state.position = nil end syncHomeWithServer() updateFuel() -- ========== Movement Wrapping (heading + position tracking) ========== -- Heading: 0=south(+z), 1=west(-x), 2=north(-z), 3=east(+x) local MOVE_DELTA = { [0] = { x = 0, z = 1 }, -- south [1] = { x = -1, z = 0 }, -- west [2] = { x = 0, z = -1 }, -- north [3] = { x = 1, z = 0 }, -- east } local _rawForward = turtle.forward local _rawBack = turtle.back local _rawUp = turtle.up local _rawDown = turtle.down local _rawTurnLeft = turtle.turnLeft local _rawTurnRight = turtle.turnRight turtle.forward = function() local ok, reason = _rawForward() if ok and state.position then local d = MOVE_DELTA[state.facing] state.position.x = state.position.x + d.x state.position.z = state.position.z + d.z end return ok, reason end turtle.back = function() local ok, reason = _rawBack() if ok and state.position then local d = MOVE_DELTA[state.facing] state.position.x = state.position.x - d.x state.position.z = state.position.z - d.z end return ok, reason end turtle.up = function() local ok, reason = _rawUp() if ok and state.position then state.position.y = state.position.y + 1 end return ok, reason end turtle.down = function() local ok, reason = _rawDown() if ok and state.position then state.position.y = state.position.y - 1 end return ok, reason end turtle.turnLeft = function() local ok = _rawTurnLeft() if ok then state.facing = (state.facing + 3) % 4 _G._turtleFacing = state.facing end return ok end turtle.turnRight = function() local ok = _rawTurnRight() if ok then state.facing = (state.facing + 1) % 4 _G._turtleFacing = state.facing end return ok end -- Detect heading via GPS triangulation local function detectHeading() local x1, _, z1 = gps.locate(2) if not x1 then return false end if _rawForward() then local x2, _, z2 = gps.locate(2) _rawBack() if x2 then local dx = math.floor(x2) - math.floor(x1) local dz = math.floor(z2) - math.floor(z1) if dz > 0 then state.facing = 0 -- south elseif dz < 0 then state.facing = 2 -- north elseif dx > 0 then state.facing = 3 -- east elseif dx < 0 then state.facing = 1 -- west end _G._turtleFacing = state.facing return true end end return false end if state.position then if detectHeading() then print("Heading: " .. ({"south","west","north","east"})[state.facing + 1]) else print("Heading: unknown (blocked)") end end -- ========== Pathfinding Module ========== local pathfind = {} --- Face a target heading. function pathfind.face(targetH) targetH = targetH % 4 while state.facing ~= targetH do local diff = (targetH - state.facing) % 4 if diff == 1 then turtle.turnRight() elseif diff == 3 then turtle.turnLeft() else turtle.turnRight() turtle.turnRight() end end end --- Navigate to target coordinates with simple obstacle avoidance. -- @param tx, ty, tz target position -- @param options { dig = false, maxAttempts = 256 } -- @return true or false, error function pathfind.goto(tx, ty, tz, options) options = options or {} local maxAttempts = options.maxAttempts or 256 local dig = options.dig or false local attempts = 0 while attempts < maxAttempts do local pos = state.position if not pos then return false, "No GPS position" end -- Arrived? if pos.x == tx and pos.y == ty and pos.z == tz then return true end attempts = attempts + 1 local moved = false -- Priority: Y first (get to correct height), then X, then Z if not moved and pos.y ~= ty then if pos.y < ty then moved = turtle.up() if not moved and dig then turtle.digUp(); moved = turtle.up() end else moved = turtle.down() if not moved and dig then turtle.digDown(); moved = turtle.down() end end end if not moved and pos.x ~= tx then pathfind.face(tx > pos.x and 3 or 1) moved = turtle.forward() if not moved and dig then turtle.dig(); moved = turtle.forward() end end if not moved and pos.z ~= tz then pathfind.face(tz > pos.z and 0 or 2) moved = turtle.forward() if not moved and dig then turtle.dig(); moved = turtle.forward() end end -- Obstacle avoidance: try going around if not moved then if turtle.up() then moved = true elseif turtle.down() then moved = true else turtle.turnRight() if turtle.forward() then moved = true else turtle.turnLeft() turtle.turnLeft() if turtle.forward() then moved = true else turtle.turnRight() -- restore heading return false, "Stuck at " .. pos.x .. "," .. pos.y .. "," .. pos.z end end end end end return false, "Max attempts exceeded" end --- Go home (to saved home position). function pathfind.goHome(options) if not state.homePosition then return false, "No home position set" end return pathfind.goto(state.homePosition.x, state.homePosition.y, state.homePosition.z, options) end --- Get current heading name. function pathfind.headingName() return ({"south","west","north","east"})[state.facing + 1] end -- Expose globally for eval access _G._pathfind = pathfind print("Ready! Turtle " .. os.getComputerID() .. " online (v5 server-driven + pathfinding)") broadcastStatus() -- ========== Main Loop ========== parallel.waitForAny( function() -- GPS retry loop while true do if not state.position then sleep(10) if updatePosition() then print("GPS acquired!") broadcastStatus() end else sleep(30) end end end, function() -- Status broadcast loop while true do sleep(config.statusUpdateInterval) broadcastStatus() end end, function() -- Command processing (eval protocol) while true do local event, side, channel, replyChannel, message = os.pullEvent("modem_message") if channel == CHANNEL_RECEIVE then processMessage(channel, message) end end end, function() -- Inventory change events (real-time) while true do os.pullEvent("turtle_inventory") local inventory = {} for i = 1, 16 do local item = turtle.getItemDetail(i) if item then table.insert(inventory, { slot = i, name = item.name, count = item.count }) end end modem.transmit(CHANNEL_SEND, CHANNEL_RECEIVE, { type = "inventory_update", turtleID = os.getComputerID(), inventory = inventory, timestamp = os.epoch("utc") }) end end, function() -- Peripheral attached events (real-time) while true do os.pullEvent("peripheral") sleep(0.1) -- Small delay to let CC register local peripherals = {} local names = peripheral.getNames() for _, name in ipairs(names) do peripherals[name] = {types = {peripheral.getType(name)}} end modem.transmit(CHANNEL_SEND, CHANNEL_RECEIVE, { type = "peripheral_attached", turtleID = os.getComputerID(), peripherals = peripherals, timestamp = os.epoch("utc") }) end end, function() -- Peripheral detached events (real-time) while true do local _, side = os.pullEvent("peripheral_detach") if not peripheral.isPresent(side) then modem.transmit(CHANNEL_SEND, CHANNEL_RECEIVE, { type = "peripheral_detached", turtleID = os.getComputerID(), side = side, timestamp = os.epoch("utc") }) end end end )