740 lines
21 KiB
Lua
740 lines
21 KiB
Lua
-- Advanced Autonomous Mining Turtle v4 (Eval/Response Protocol)
|
|
-- Features: Server-driven state machine, UUID command correlation,
|
|
-- GPS tracking, local fallback intelligence, block scanning
|
|
|
|
local CHANNEL_RECEIVE = 100
|
|
local CHANNEL_SEND = 101
|
|
local STATUS_CHANNEL = 102
|
|
|
|
-- State tracking (lightweight - server drives behavior via eval)
|
|
local state = {
|
|
mode = "idle",
|
|
position = nil,
|
|
homePosition = nil,
|
|
fuel = 0,
|
|
inventory = {},
|
|
facing = 0,
|
|
lastStatusUpdate = 0,
|
|
evalSupported = true,
|
|
}
|
|
|
|
-- Configuration
|
|
local config = {
|
|
statusUpdateInterval = 5,
|
|
valuableBlocks = {
|
|
["minecraft:coal_ore"] = 1,
|
|
["minecraft:iron_ore"] = 2,
|
|
["minecraft:gold_ore"] = 3,
|
|
["minecraft:diamond_ore"] = 5,
|
|
["minecraft:emerald_ore"] = 5,
|
|
["minecraft:redstone_ore"] = 2,
|
|
["minecraft:lapis_ore"] = 2,
|
|
["minecraft:deepslate_coal_ore"] = 1,
|
|
["minecraft:deepslate_iron_ore"] = 2,
|
|
["minecraft:deepslate_gold_ore"] = 3,
|
|
["minecraft:deepslate_diamond_ore"] = 5,
|
|
["minecraft:deepslate_emerald_ore"] = 5,
|
|
["minecraft:deepslate_redstone_ore"] = 2,
|
|
["minecraft:deepslate_lapis_ore"] = 2,
|
|
}
|
|
}
|
|
|
|
-- Check for modem
|
|
local modem = peripheral.find("modem")
|
|
if not modem then
|
|
error("No wireless modem found!")
|
|
end
|
|
modem.open(CHANNEL_RECEIVE)
|
|
print("Advanced Mining Turtle v4 (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
|
|
|
|
-- ========== Movement Functions (with position tracking) ==========
|
|
|
|
local function updateFacingAfterTurn(right)
|
|
if right then
|
|
state.facing = (state.facing + 1) % 4
|
|
else
|
|
state.facing = (state.facing - 1) % 4
|
|
end
|
|
_G._turtleFacing = state.facing
|
|
end
|
|
|
|
local function smartForward()
|
|
local success = turtle.forward()
|
|
if success and state.position then
|
|
if state.facing == 0 then state.position.z = state.position.z - 1
|
|
elseif state.facing == 1 then state.position.x = state.position.x + 1
|
|
elseif state.facing == 2 then state.position.z = state.position.z + 1
|
|
elseif state.facing == 3 then state.position.x = state.position.x - 1
|
|
end
|
|
end
|
|
return success
|
|
end
|
|
|
|
local function smartBack()
|
|
local success = turtle.back()
|
|
if success and state.position then
|
|
if state.facing == 0 then state.position.z = state.position.z + 1
|
|
elseif state.facing == 1 then state.position.x = state.position.x - 1
|
|
elseif state.facing == 2 then state.position.z = state.position.z - 1
|
|
elseif state.facing == 3 then state.position.x = state.position.x + 1
|
|
end
|
|
end
|
|
return success
|
|
end
|
|
|
|
local function smartUp()
|
|
local success = turtle.up()
|
|
if success and state.position then
|
|
state.position.y = state.position.y + 1
|
|
end
|
|
return success
|
|
end
|
|
|
|
local function smartDown()
|
|
local success = turtle.down()
|
|
if success and state.position then
|
|
state.position.y = state.position.y - 1
|
|
end
|
|
return success
|
|
end
|
|
|
|
local function smartTurnRight()
|
|
local success = turtle.turnRight()
|
|
if success then updateFacingAfterTurn(true) end
|
|
return success
|
|
end
|
|
|
|
local function smartTurnLeft()
|
|
local success = turtle.turnLeft()
|
|
if success then updateFacingAfterTurn(false) end
|
|
return success
|
|
end
|
|
|
|
-- ========== Fuel & Inventory ==========
|
|
|
|
local function updateFuel()
|
|
state.fuel = turtle.getFuelLevel()
|
|
return state.fuel
|
|
end
|
|
|
|
local function tryRefuel()
|
|
for slot = 1, 16 do
|
|
turtle.select(slot)
|
|
if turtle.refuel(0) then
|
|
local count = turtle.getItemCount()
|
|
if count > 0 then
|
|
turtle.refuel(1)
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
return false
|
|
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
|
|
|
|
local function inventoryFull()
|
|
for slot = 1, 16 do
|
|
if turtle.getItemCount(slot) == 0 then
|
|
return false
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
-- ========== Block Scanning ==========
|
|
|
|
local function scanAllDirections()
|
|
if not state.position then return {} end
|
|
|
|
local scannedBlocks = {}
|
|
local myID = os.getComputerID()
|
|
|
|
local function addBlock(relX, relY, relZ, blockData)
|
|
if blockData and blockData.name then
|
|
table.insert(scannedBlocks, {
|
|
x = state.position.x + relX,
|
|
y = state.position.y + relY,
|
|
z = state.position.z + relZ,
|
|
name = blockData.name,
|
|
metadata = blockData.metadata or 0,
|
|
discoveredBy = myID
|
|
})
|
|
end
|
|
end
|
|
|
|
local hasBlock, data
|
|
hasBlock, data = turtle.inspectUp()
|
|
if hasBlock then addBlock(0, 1, 0, data) end
|
|
|
|
hasBlock, data = turtle.inspectDown()
|
|
if hasBlock then addBlock(0, -1, 0, data) end
|
|
|
|
local originalFacing = state.facing
|
|
for i = 0, 3 do
|
|
hasBlock, data = turtle.inspect()
|
|
if hasBlock then
|
|
local relX, relZ = 0, 0
|
|
if state.facing == 0 then relZ = -1
|
|
elseif state.facing == 1 then relX = 1
|
|
elseif state.facing == 2 then relZ = 1
|
|
elseif state.facing == 3 then relX = -1
|
|
end
|
|
addBlock(relX, 0, relZ, data)
|
|
end
|
|
if i < 3 then smartTurnRight() end
|
|
end
|
|
|
|
if state.facing ~= originalFacing then
|
|
smartTurnRight()
|
|
end
|
|
|
|
return scannedBlocks
|
|
end
|
|
|
|
local function reportDiscoveredBlocks(blocks)
|
|
if #blocks == 0 then return end
|
|
modem.transmit(CHANNEL_SEND, CHANNEL_RECEIVE, {
|
|
type = "blocks_discovered",
|
|
turtleID = os.getComputerID(),
|
|
blocks = blocks,
|
|
timestamp = os.epoch("utc")
|
|
})
|
|
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(),
|
|
mode = state.mode,
|
|
position = state.position,
|
|
homePosition = state.homePosition,
|
|
fuel = state.fuel,
|
|
inventoryCount = #state.inventory,
|
|
inventory = state.inventory,
|
|
facing = state.facing,
|
|
surroundings = surroundings,
|
|
evalSupported = true,
|
|
}
|
|
|
|
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 ok, result = pcall(fn)
|
|
|
|
if ok then
|
|
modem.transmit(CHANNEL_SEND, CHANNEL_RECEIVE, {
|
|
type = "eval_response",
|
|
uuid = uuid,
|
|
result = result,
|
|
error = nil,
|
|
turtleID = os.getComputerID()
|
|
})
|
|
else
|
|
modem.transmit(CHANNEL_SEND, CHANNEL_RECEIVE, {
|
|
type = "eval_response",
|
|
uuid = uuid,
|
|
result = nil,
|
|
error = "Runtime error: " .. tostring(result),
|
|
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.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 = {}
|
|
local fns = {}
|
|
for i = 1, 16 do
|
|
fns[i] = function()
|
|
local item = turtle.getItemDetail(i, true)
|
|
if item then
|
|
inventory[tostring(i)] = item
|
|
end
|
|
end
|
|
end
|
|
parallel.waitForAll(table.unpack(fns))
|
|
|
|
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
|
|
)
|