305 lines
8.4 KiB
Lua
305 lines
8.4 KiB
Lua
-- 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()
|
|
print("Ready! Turtle " .. os.getComputerID() .. " online (v5 server-driven)")
|
|
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
|
|
)
|