Files
remoteturtle/turtle.lua

1061 lines
30 KiB
Lua

-- Server-Driven Turtle v5 (Pure Eval Protocol)-- Advanced Autonomous Mining Turtle v4 (Eval/Response Protocol)
-- All behavior is driven by the server via eval commands.-- Features: Server-driven state machine, UUID command correlation,
-- This script only handles: eval execution, status broadcasting,-- GPS tracking, local fallback intelligence, block scanning
-- GPS tracking, inventory/peripheral events.
local CHANNEL_RECEIVE = 100
local CHANNEL_RECEIVE = 100local CHANNEL_SEND = 101
local CHANNEL_SEND = 101local STATUS_CHANNEL = 102
local STATUS_CHANNEL = 102
-- State tracking (lightweight - server drives behavior via eval)
-- State tracking (lightweight - server drives everything)local state = {
local state = { mode = "idle",
position = nil, position = nil,
homePosition = nil, homePosition = nil,
fuel = 0, fuel = 0,
inventory = {}, inventory = {},
facing = 0, facing = 0,
lastStatusUpdate = 0, lastStatusUpdate = 0,
} evalSupported = true,
}
-- Configuration
local config = {-- Configuration
statusUpdateInterval = 5,local config = {
} statusUpdateInterval = 5,
valuableBlocks = {
-- Check for modem ["minecraft:coal_ore"] = 1,
local modem = peripheral.find("modem") ["minecraft:iron_ore"] = 2,
if not modem then ["minecraft:gold_ore"] = 3,
error("No wireless modem found!") ["minecraft:diamond_ore"] = 5,
end ["minecraft:emerald_ore"] = 5,
modem.open(CHANNEL_RECEIVE) ["minecraft:redstone_ore"] = 2,
print("Server-Driven Turtle v5 (Pure Eval Protocol)") ["minecraft:lapis_ore"] = 2,
print("ID: " .. os.getComputerID()) ["minecraft:deepslate_coal_ore"] = 1,
print("Modem opened on channel " .. CHANNEL_RECEIVE) ["minecraft:deepslate_iron_ore"] = 2,
["minecraft:deepslate_gold_ore"] = 3,
-- Store facing in global for eval access ["minecraft:deepslate_diamond_ore"] = 5,
_G._turtleFacing = 0 ["minecraft:deepslate_emerald_ore"] = 5,
["minecraft:deepslate_redstone_ore"] = 2,
-- ========== GPS Functions ========== ["minecraft:deepslate_lapis_ore"] = 2,
}
local function updatePosition()}
local x, y, z = gps.locate(2)
if x then-- Check for modem
state.position = {x = math.floor(x), y = math.floor(y), z = math.floor(z)}local modem = peripheral.find("modem")
return trueif not modem then
end error("No wireless modem found!")
return falseend
endmodem.open(CHANNEL_RECEIVE)
print("Advanced Mining Turtle v4 (Eval Protocol)")
-- ========== Fuel & Inventory ==========print("ID: " .. os.getComputerID())
print("Modem opened on channel " .. CHANNEL_RECEIVE)
local function updateFuel()
state.fuel = turtle.getFuelLevel()-- Store facing in global for eval access
return state.fuel_G._turtleFacing = 0
end
-- ========== GPS Functions ==========
local function updateInventory()
state.inventory = {}local function updatePosition()
for slot = 1, 16 do local x, y, z = gps.locate(2)
local item = turtle.getItemDetail(slot) if x then
if item then state.position = {x = math.floor(x), y = math.floor(y), z = math.floor(z)}
table.insert(state.inventory, { return true
slot = slot, end
name = item.name, return false
count = item.countend
})
end-- ========== Movement Functions (with position tracking) ==========
end
endlocal function updateFacingAfterTurn(right)
if right then
-- ========== Status Broadcasting ========== state.facing = (state.facing + 1) % 4
else
local function broadcastStatus() state.facing = (state.facing - 1) % 4
updateFuel() end
updateInventory() _G._turtleFacing = state.facing
end
local surroundings = {}
local hasBlock, datalocal function smartForward()
local success = turtle.forward()
hasBlock, data = turtle.inspect() if success and state.position then
if hasBlock then surroundings.forward = {name = data.name, metadata = data.metadata or 0} end if state.facing == 0 then state.position.z = state.position.z - 1
elseif state.facing == 1 then state.position.x = state.position.x + 1
hasBlock, data = turtle.inspectUp() elseif state.facing == 2 then state.position.z = state.position.z + 1
if hasBlock then surroundings.up = {name = data.name, metadata = data.metadata or 0} end elseif state.facing == 3 then state.position.x = state.position.x - 1
end
hasBlock, data = turtle.inspectDown() end
if hasBlock then surroundings.down = {name = data.name, metadata = data.metadata or 0} end return success
end
local statusPacket = {
type = "status",local function smartBack()
turtleID = os.getComputerID(), local success = turtle.back()
position = state.position, if success and state.position then
homePosition = state.homePosition, if state.facing == 0 then state.position.z = state.position.z + 1
fuel = state.fuel, elseif state.facing == 1 then state.position.x = state.position.x - 1
inventoryCount = #state.inventory, elseif state.facing == 2 then state.position.z = state.position.z - 1
inventory = state.inventory, elseif state.facing == 3 then state.position.x = state.position.x + 1
facing = state.facing, end
surroundings = surroundings, end
evalSupported = true, return success
label = os.getComputerLabel(),end
}
local function smartUp()
modem.transmit(STATUS_CHANNEL, CHANNEL_RECEIVE, statusPacket) local success = turtle.up()
end if success and state.position then
state.position.y = state.position.y + 1
-- ========== Eval/Response Protocol ========== end
return success
local function executeEval(uuid, code)end
local fn, compileError = load(code, "eval", "t")
if not fn thenlocal function smartDown()
modem.transmit(CHANNEL_SEND, CHANNEL_RECEIVE, { local success = turtle.down()
type = "eval_response", if success and state.position then
uuid = uuid, state.position.y = state.position.y - 1
result = nil, end
error = "Compile error: " .. tostring(compileError), return success
turtleID = os.getComputerID()end
})
returnlocal function smartTurnRight()
end local success = turtle.turnRight()
if success then updateFacingAfterTurn(true) end
local results = table.pack(pcall(fn)) return success
local ok = results[1]end
if ok thenlocal function smartTurnLeft()
local returnVal local success = turtle.turnLeft()
if results.n <= 2 then if success then updateFacingAfterTurn(false) end
returnVal = results[2] return success
elseend
returnVal = {}
for i = 2, results.n do-- ========== Fuel & Inventory ==========
returnVal[i - 1] = results[i]
endlocal function updateFuel()
end state.fuel = turtle.getFuelLevel()
modem.transmit(CHANNEL_SEND, CHANNEL_RECEIVE, { return state.fuel
type = "eval_response",end
uuid = uuid,
result = returnVal,local function tryRefuel()
error = nil, for slot = 1, 16 do
turtleID = os.getComputerID() turtle.select(slot)
}) if turtle.refuel(0) then
else local count = turtle.getItemCount()
modem.transmit(CHANNEL_SEND, CHANNEL_RECEIVE, { if count > 0 then
type = "eval_response", turtle.refuel(1)
uuid = uuid, return true
result = nil, end
error = "Runtime error: " .. tostring(results[2]), end
turtleID = os.getComputerID() end
}) return false
endend
end
local function updateInventory()
-- ========== Message Processing ========== state.inventory = {}
for slot = 1, 16 do
local function processMessage(channel, message) local item = turtle.getItemDetail(slot)
if type(message) ~= "table" then return end if item then
local myID = os.getComputerID() table.insert(state.inventory, {
if message.target and message.target ~= myID then return end slot = slot,
name = item.name,
if message.type == "eval" and message.uuid and message.code then count = item.count
print("Eval: " .. message.uuid:sub(1, 8) .. "...") })
executeEval(message.uuid, message.code) end
end
elseif message.type == "home_position" and message.turtleID == myID thenend
if message.homePosition then
state.homePosition = message.homePositionlocal function inventoryFull()
print("Home synced from server") for slot = 1, 16 do
end if turtle.getItemCount(slot) == 0 then
return false
elseif message.type == "home_set_confirm" and message.turtleID == myID then end
if message.homePosition then end
state.homePosition = message.homePosition return true
print("Home confirmed by server")end
end
-- ========== Block Scanning ==========
elseif message.type == "rename" and message.name then
os.setComputerLabel(message.name)local function scanAllDirections()
print("Renamed to: " .. message.name) if not state.position then return {} end
broadcastStatus()
end local scannedBlocks = {}
end local myID = os.getComputerID()
-- ========== Sync Home ========== local function addBlock(relX, relY, relZ, blockData)
if blockData and blockData.name then
local function syncHomeWithServer() table.insert(scannedBlocks, {
modem.transmit(CHANNEL_SEND, CHANNEL_RECEIVE, { x = state.position.x + relX,
type = "request_home", y = state.position.y + relY,
turtleID = os.getComputerID() z = state.position.z + relZ,
}) name = blockData.name,
return true metadata = blockData.metadata or 0,
end discoveredBy = myID
})
-- ========== Initialization ========== end
end
print("Initializing...")
local x, y, z = gps.locate(5) local hasBlock, data
if x then hasBlock, data = turtle.inspectUp()
state.position = {x = math.floor(x), y = math.floor(y), z = math.floor(z)} if hasBlock then addBlock(0, 1, 0, data) end
print("GPS: OK - " .. state.position.x .. "," .. state.position.y .. "," .. state.position.z)
else hasBlock, data = turtle.inspectDown()
print("GPS: Not available") if hasBlock then addBlock(0, -1, 0, data) end
state.position = nil
end local originalFacing = state.facing
for i = 0, 3 do
syncHomeWithServer() hasBlock, data = turtle.inspect()
updateFuel() if hasBlock then
print("Ready! Turtle " .. os.getComputerID() .. " online (v5 server-driven)") local relX, relZ = 0, 0
broadcastStatus() if state.facing == 0 then relZ = -1
elseif state.facing == 1 then relX = 1
-- ========== Main Loop ========== elseif state.facing == 2 then relZ = 1
elseif state.facing == 3 then relX = -1
parallel.waitForAny( end
function() addBlock(relX, 0, relZ, data)
-- GPS retry loop end
while true do if i < 3 then smartTurnRight() end
if not state.position then end
sleep(10)
if updatePosition() then if state.facing ~= originalFacing then
print("GPS acquired!") smartTurnRight()
broadcastStatus() end
end
else return scannedBlocks
sleep(30)end
end
endlocal function reportDiscoveredBlocks(blocks)
end, if #blocks == 0 then return end
modem.transmit(CHANNEL_SEND, CHANNEL_RECEIVE, {
function() type = "blocks_discovered",
-- Status broadcast loop turtleID = os.getComputerID(),
while true do blocks = blocks,
sleep(config.statusUpdateInterval) timestamp = os.epoch("utc")
broadcastStatus() })
endend
end,
-- ========== Status Broadcasting ==========
function()
-- Command processing (eval protocol)local function broadcastStatus()
while true do updateFuel()
local event, side, channel, replyChannel, message = os.pullEvent("modem_message") updateInventory()
if channel == CHANNEL_RECEIVE then
processMessage(channel, message) local surroundings = {}
end local hasBlock, data
end
end, hasBlock, data = turtle.inspect()
if hasBlock then surroundings.forward = {name = data.name, metadata = data.metadata or 0} end
function()
-- Inventory change events (real-time) hasBlock, data = turtle.inspectUp()
while true do if hasBlock then surroundings.up = {name = data.name, metadata = data.metadata or 0} end
os.pullEvent("turtle_inventory")
local inventory = {} hasBlock, data = turtle.inspectDown()
for i = 1, 16 do if hasBlock then surroundings.down = {name = data.name, metadata = data.metadata or 0} end
local item = turtle.getItemDetail(i)
if item then local statusPacket = {
table.insert(inventory, { type = "status",
slot = i, turtleID = os.getComputerID(),
name = item.name, mode = state.mode,
count = item.count position = state.position,
}) homePosition = state.homePosition,
end fuel = state.fuel,
end inventoryCount = #state.inventory,
inventory = state.inventory,
modem.transmit(CHANNEL_SEND, CHANNEL_RECEIVE, { facing = state.facing,
type = "inventory_update", surroundings = surroundings,
turtleID = os.getComputerID(), evalSupported = true,
inventory = inventory, label = os.getComputerLabel(),
timestamp = os.epoch("utc") }
})
end modem.transmit(STATUS_CHANNEL, CHANNEL_RECEIVE, statusPacket)
end,end
function()-- ========== Eval/Response Protocol ==========
-- Peripheral attached events (real-time)
while true dolocal function executeEval(uuid, code)
os.pullEvent("peripheral") local fn, compileError = load(code, "eval", "t")
sleep(0.1) if not fn then
local peripherals = {} modem.transmit(CHANNEL_SEND, CHANNEL_RECEIVE, {
local names = peripheral.getNames() type = "eval_response",
for _, name in ipairs(names) do uuid = uuid,
peripherals[name] = {types = {peripheral.getType(name)}} result = nil,
end error = "Compile error: " .. tostring(compileError),
turtleID = os.getComputerID()
modem.transmit(CHANNEL_SEND, CHANNEL_RECEIVE, { })
type = "peripheral_attached", return
turtleID = os.getComputerID(), end
peripherals = peripherals,
timestamp = os.epoch("utc") local results = table.pack(pcall(fn))
}) local ok = results[1]
end
end, if ok then
local returnVal
function() if results.n <= 2 then
-- Peripheral detached events (real-time) -- Single return value
while true do returnVal = results[2]
local _, side = os.pullEvent("peripheral_detach") else
if not peripheral.isPresent(side) then -- Multiple return values - pack into array
modem.transmit(CHANNEL_SEND, CHANNEL_RECEIVE, { returnVal = {}
type = "peripheral_detached", for i = 2, results.n do
turtleID = os.getComputerID(), returnVal[i - 1] = results[i]
side = side, end
timestamp = os.epoch("utc") end
}) modem.transmit(CHANNEL_SEND, CHANNEL_RECEIVE, {
end type = "eval_response",
end uuid = uuid,
end 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
-- ========== Legacy Command Handling ==========
local legacyCommands = {
forward = function() return smartForward(), "Moved forward" end,
back = function() return smartBack(), "Moved back" end,
up = function() return smartUp(), "Moved up" end,
down = function() return smartDown(), "Moved down" end,
turnLeft = function() return smartTurnLeft(), "Turned left" end,
turnRight = function() return smartTurnRight(), "Turned right" end,
dig = function()
local hasBlock, data = turtle.inspect()
if hasBlock then
turtle.dig()
return true, "Dug: " .. (data.name or "block")
end
return false, "Nothing to dig"
end,
digUp = function()
local hasBlock, data = turtle.inspectUp()
if hasBlock then
turtle.digUp()
return true, "Dug up: " .. (data.name or "block")
end
return false, "Nothing above"
end,
digDown = function()
local hasBlock, data = turtle.inspectDown()
if hasBlock then
turtle.digDown()
return true, "Dug down: " .. (data.name or "block")
end
return false, "Nothing below"
end,
place = function() return turtle.place(), "Placed" end,
select = function(slot) return turtle.select(slot), "Selected slot " .. slot end,
refuel = function()
local success = tryRefuel()
updateFuel()
broadcastStatus()
return success, success and ("Refueled! Now: " .. state.fuel) or "No fuel"
end,
status = function()
broadcastStatus()
return true, "Status sent"
end,
setHome = function()
if not state.position then
if not updatePosition() then
return false, "No GPS"
end
end
state.homePosition = {
x = state.position.x,
y = state.position.y,
z = state.position.z
}
modem.transmit(CHANNEL_SEND, CHANNEL_RECEIVE, {
type = "set_home",
turtleID = os.getComputerID(),
position = state.homePosition
})
return true, "Home set"
end,
explore = function()
if not state.homePosition then
if state.position then
state.homePosition = {
x = state.position.x,
y = state.position.y,
z = state.position.z
}
modem.transmit(CHANNEL_SEND, CHANNEL_RECEIVE, {
type = "set_home",
turtleID = os.getComputerID(),
position = state.homePosition
})
else
return false, "No home or GPS"
end
end
state.mode = "exploring"
broadcastStatus()
return true, "Exploring"
end,
mine = function()
if not state.homePosition then
if state.position then
state.homePosition = {
x = state.position.x,
y = state.position.y,
z = state.position.z
}
modem.transmit(CHANNEL_SEND, CHANNEL_RECEIVE, {
type = "set_home",
turtleID = os.getComputerID(),
position = state.homePosition
})
else
return false, "No home or GPS"
end
end
state.mode = "mining"
broadcastStatus()
return true, "Mining"
end,
returnHome = function()
state.mode = "returning"
broadcastStatus()
return true, "Returning home"
end,
stop = function()
state.mode = "idle"
broadcastStatus()
return true, "Stopped"
end,
manual = function()
state.mode = "manual"
broadcastStatus()
return true, "Manual control"
end,
}
local function processLegacyCommand(message)
if type(message) ~= "table" then return end
local myID = os.getComputerID()
if message.target and message.target ~= myID then return end
if message.command then
local handler = legacyCommands[message.command]
if handler then
local success, result = handler(message.param)
modem.transmit(CHANNEL_SEND, CHANNEL_RECEIVE, {
status = success and "ok" or "error",
message = result or "",
turtleID = myID
})
broadcastStatus()
end
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)
if message.stateUpdate then
state.mode = message.stateUpdate
end
elseif message.type == "state_change" and message.state then
state.mode = message.state
broadcastStatus()
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()
elseif message.command then
processLegacyCommand(message)
end
end
-- ========== Local Autonomous Behavior (fallback) ==========
local localStuckCounter = 0
local function getDistance(pos1, pos2)
return math.abs(pos1.x - pos2.x) + math.abs(pos1.y - pos2.y) + math.abs(pos1.z - pos2.z)
end
local function localExploreStep()
if state.position then
local blocks = scanAllDirections()
if #blocks > 0 then
reportDiscoveredBlocks(blocks)
end
end
-- Mine valuable ores
for _, direction in ipairs({"forward", "up", "down"}) do
local inspectFn = direction == "forward" and turtle.inspect
or direction == "up" and turtle.inspectUp
or turtle.inspectDown
local digFn = direction == "forward" and turtle.dig
or direction == "up" and turtle.digUp
or turtle.digDown
local hasBlock, data = inspectFn()
if hasBlock and config.valuableBlocks[data.name] then
digFn()
end
end
local r = math.random(1, 100)
if r < 75 then
local hasBlock = turtle.inspect()
if hasBlock then turtle.dig() end
if not smartForward() then
smartTurnRight()
localStuckCounter = localStuckCounter + 1
else
localStuckCounter = 0
end
elseif r < 90 and state.position and state.position.y > 15 then
local hasBlock = turtle.inspectDown()
if hasBlock then turtle.digDown() end
smartDown()
else
local hasBlock = turtle.inspectUp()
if hasBlock then turtle.digUp() end
smartUp()
end
if localStuckCounter > 5 then
turtle.digUp()
smartUp()
localStuckCounter = 0
end
end
local function localReturnHomeStep()
if not state.homePosition or not state.position then
state.mode = "idle"
return true
end
if getDistance(state.position, state.homePosition) <= 1 then
state.mode = "idle"
broadcastStatus()
return true
end
local dx = state.homePosition.x - state.position.x
local dy = state.homePosition.y - state.position.y
local dz = state.homePosition.z - state.position.z
if math.abs(dy) > 3 then
if dy > 0 then
turtle.digUp(); smartUp()
else
turtle.digDown(); smartDown()
end
else
local targetFacing
if math.abs(dx) > math.abs(dz) then
targetFacing = dx > 0 and 1 or 3
else
targetFacing = dz > 0 and 2 or 0
end
while state.facing ~= targetFacing do
smartTurnRight()
end
turtle.dig()
if not smartForward() then
smartTurnRight()
end
end
return false
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 (v4 eval protocol)")
broadcastStatus()
-- ========== Main Loop ==========
parallel.waitForAny(
function()
-- GPS retry
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
while true do
sleep(config.statusUpdateInterval)
broadcastStatus()
end
end,
function()
-- Command processing
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,
function()
-- Local autonomous fallback
while true do
if state.mode == "exploring" or state.mode == "mining" then
updateFuel()
if state.position and state.homePosition then
local dist = getDistance(state.position, state.homePosition)
if dist > 200 then
state.mode = "returning"
broadcastStatus()
elseif state.fuel ~= "unlimited" and state.fuel < 500 then
if not tryRefuel() then
state.mode = "returning"
broadcastStatus()
end
elseif inventoryFull() then
state.mode = "returning"
broadcastStatus()
else
localExploreStep()
sleep(0.2)
end
else
localExploreStep()
sleep(0.2)
end
elseif state.mode == "returning" then
local done = localReturnHomeStep()
if not done then sleep(0.1) end
else
sleep(0.5)
end
end
end
)