CC-navigation/netNav/netNav.lua

359 lines
9.2 KiB
Lua

local apis = {
"remoteMap",
"aStar",
"location",
}
for _, api in ipairs(apis) do
if not _G[api] then
if not os.loadAPI(api) then
error("could not load API: "..api)
end
end
end
local position
local SESSION_MAX_DISTANCE_DEFAULT = 32
local SESSION_COORD_CLEAR = 1
local SESSION_COORD_BLOCKED = 2
local SESSION_COORD_DISTANCE = math.huge
local UPDATE_COORD_CLEAR = -1
local UPDATE_COORD_BLOCKED = 1
local UPDATE_COORD_DISTANCE = 2
local sessionMidPoint
local sessionMaxDistance
local sessionMap
local serverMap
local function distanceFunc(a, b)
local sessionMapA, sessionMapB = sessionMap:get(a), sessionMap:get(b)
if aStar.distance(a, sessionMidPoint) > sessionMaxDistance then
return SESSION_COORD_DISTANCE -- first coord is outside the search region
elseif aStar.distance(b, sessionMidPoint) > sessionMaxDistance then
return SESSION_COORD_DISTANCE -- second coord is outside the search region
elseif sessionMapA == SESSION_COORD_BLOCKED or sessionMapB == SESSION_COORD_BLOCKED then
return SESSION_COORD_DISTANCE -- we have found one of these coords to be blocked during this session
elseif sessionMapA == SESSION_COORD_CLEAR and sessionMapB == SESSION_COORD_CLEAR then
return aStar.distance(a, b) -- we have found both of these coords to be clear during this session
else
local serverMapA, serverMapB = serverMap:get(a), serverMap:get(b)
if serverMapA or serverMapB then
serverMapA = serverMapA and UPDATE_COORD_DISTANCE^(serverMapA + 1) or 1
serverMapB = serverMapB and UPDATE_COORD_DISTANCE^(serverMapB + 1) or 1
return math.max(serverMapA, serverMapB) -- the remote server map is indicating one of these coords may be blocked
end
end
return aStar.distance(a, b) -- we dont know anything useful so just calc the distance
end
local directions = {
[vector.new(0, 0, 1)] = 0,
[vector.new(-1, 0, 0)] = 1,
[vector.new(0, 0, -1)] = 2,
[vector.new(1, 0, 0)] = 3,
[vector.new(0, 1, 0)] = 4,
[vector.new(0, -1, 0)] = 5,
}
local function deltaToDirection(delta)
for vec, dir in pairs(directions) do
if aStar.vectorEquals(delta, vec) then
return dir
end
end
end
local function tryMove()
for i = 1, 4 do
if turtle.forward() then
return true
end
turtle.turnRight()
end
return false
end
local function findPosition()
local move = turtle.up
while not tryMove() do
if not move() then
if move == turtle.up then
move = turtle.down
move()
else
error("trapped in a ridiculous place")
end
end
end
local p1 = {gps.locate()}
if #p1 == 3 then
p1 = vector.new(unpack(p1))
else
error("no gps signal - phase 1")
end
if not turtle.back() then
error("couldn't move to determine direction")
end
local p2 = {gps.locate()}
if #p2 == 3 then
p2 = vector.new(unpack(p2))
else
error("no gps signal - phase 2")
end
local direction = deltaToDirection(p1 - p2)
if direction and direction < 4 then
return location.new(p2.x, p2.y, p2.z, direction)
else
return false
end
end
local function detect(currPos, adjPos)
local direction = deltaToDirection(adjPos - currPos)
if direction then
position:setHeading(direction)
if direction == 4 then
return turtle.detectUp()
elseif direction == 5 then
return turtle.detectDown()
else
return turtle.detect()
end
end
return false
end
local function inspect(currPos, adjPos)
local direction = deltaToDirection(adjPos - currPos)
if direction then
position:setHeading(direction)
if direction == 4 then
return turtle.inspectUp()
elseif direction == 5 then
return turtle.inspectDown()
else
return turtle.inspect()
end
end
return false
end
local function updateCoord(coord, isBlocked)
if isBlocked then
sessionMap:set(coord, SESSION_COORD_BLOCKED)
serverMap:set(coord, UPDATE_COORD_BLOCKED)
else
sessionMap:set(coord, SESSION_COORD_CLEAR)
serverMap:set(coord, UPDATE_COORD_CLEAR)
end
end
local function detectAll(currPos)
for _, pos in ipairs(aStar.adjacent(currPos)) do -- better order of checking directions
updateCoord(pos, detect(currPos, pos))
end
end
local function findSensor()
for _, side in ipairs({"left", "right"}) do
if peripheral.getType(side) == "turtlesensorenvironment" then
return side
end
end
return false
end
local function scan(currPos)
local sensorSide = findSensor()
if sensorSide then
local rawBlockInfo = peripheral.call(sensorSide, "sonicScan")
local sortedBlockInfo = aStar.newMap()
for _, blockInfo in ipairs(rawBlockInfo) do
sortedBlockInfo:set(currPos + vector.new(blockInfo.x, blockInfo.y, blockInfo.z), blockInfo)
end
local toCheckQueue = {}
for _, pos in ipairs(aStar.adjacent(currPos)) do
if sortedBlockInfo:get(pos) then
table.insert(toCheckQueue, pos)
end
end
while toCheckQueue[1] do
local pos = table.remove(toCheckQueue, 1)
local blockInfo = sortedBlockInfo:get(pos)
if blockInfo.type == "AIR" then
for _, pos2 in ipairs(aStar.adjacent(pos)) do
local blockInfo2 = sortedBlockInfo:get(pos2)
if blockInfo2 and not blockInfo2.checked then
table.insert(toCheckQueue, pos2)
end
end
updateCoord(pos, false)
else
updateCoord(pos, true)
end
blockInfo.checked = true
end
for _, blockInfo in ipairs(rawBlockInfo) do
local pos = currPos + vector.new(blockInfo.x, blockInfo.y, blockInfo.z)
local blockInfo = sortedBlockInfo:get(pos)
if not blockInfo.checked then
if blockInfo.type == "AIR" then
sessionMap:set(pos, SESSION_COORD_CLEAR)
else
sessionMap:set(pos, SESSION_COORD_BLOCKED)
end
end
end
else
detectAll(currPos)
end
serverMap:check()
serverMap:pushUpdates()
end
local function move(currPos, adjPos)
local direction = deltaToDirection(adjPos - currPos)
if direction then
position:setHeading(direction)
if direction == 4 then
return position:up()
elseif direction == 5 then
return position:down()
else
return position:forward()
end
end
return false
end
local exit = false
local function _goto(x, y, z, maxDistance)
exit = false
if not serverMap then
error("serverMap has not been specified")
end
if turtle.getFuelLevel() == 0 then
return false, "ran out of fuel"
end
if not position then
position = findPosition()
if not position then
return false, "couldn't determine location"
end
else
-- check if position has changed
local curPos = {gps.locate()}
if #curPos == 3 then
curPos = vector.new(unpack(curPos))
if not aStar.vectorEquals(curPos, position) then -- position has changed
position = findPosition()
if not position then
return false, "couldn't determine location"
end
end
end
end
local goal = vector.new(tonumber(x), tonumber(y), tonumber(z))
serverMap:check() -- remove timed out data we have received from server
sessionMap = aStar.newMap() -- reset the sessionMap
sessionMidPoint = vector.new(math.floor((goal.x + position.x)/2), math.floor((goal.y + position.y)/2), math.floor((goal.z + position.z)/2))
sessionMaxDistance = (type(maxDistance) == "number" and maxDistance) or math.max(2*aStar.distance(sessionMidPoint, goal), SESSION_MAX_DISTANCE_DEFAULT)
local path = aStar.compute(distanceFunc, position, goal)
if not path then
return false, "no known path to goal"
end
while not (exit or aStar.vectorEquals(position, goal)) do
local movePos = table.remove(path)
while not move(position, movePos) do
local blockPresent, blockData = inspect(position, movePos)
local recalculate, isTurtle = false, false
if blockPresent and (blockData.name == "ComputerCraft:CC-TurtleAdvanced" or blockData.name == "ComputerCraft:CC-Turtle") then -- there is a turtle in the way
sleep(math.random(0, 3))
local blockPresent2, blockData2 = inspect(position, movePos)
if blockPresent2 and (blockData2.name == "ComputerCraft:CC-TurtleAdvanced" or blockData2.name == "ComputerCraft:CC-Turtle") then -- the turtle is still there
recalculate, isTurtle = true, true
end
elseif blockPresent then
recalculate = true
elseif turtle.getFuelLevel() == 0 then
return false, "ran out of fuel"
else
sleep(1)
end
if recalculate then
scan(position)
if sessionMap:get(goal) == SESSION_COORD_BLOCKED then return false, "goal is blocked" end
path = aStar.compute(distanceFunc, position, goal)
if not path then
return false, "no known path to goal"
end
if isTurtle then
sessionMap:set(movePos, nil)
end
movePos = table.remove(path)
end
end
if serverMap:get(movePos) then
serverMap:set(movePos, UPDATE_COORD_CLEAR)
end
end
serverMap:check()
serverMap:pushUpdates(true)
return aStar.vectorEquals(position, goal)
end
local isRunning = false
function goto(...)
if isRunning then
return false, "already running"
end
isRunning = true
local passback = {pcall(_goto, ...)}
isRunning = false
if not passback[1] then
printError(passback[2])
return false
end
return unpack(passback, 2)
end
function stop()
if isRunning then
exit = true
end
end
function setMap(mapName, mapTimeout)
if type(mapName) ~= "string" then
error("mapName must be string")
end
if type(mapTimeout) ~= "number" or mapTimeout < 0 then
error("timeout must be positive number")
end
serverMap = remoteMap.new(mapName, mapTimeout)
end
function getMap()
return serverMap
end
function getPosition()
if position then
return position:value()
end
end