CC-navigation/netNav/netNav.lua
2016-08-13 08:57:08 +01:00

303 lines
7.4 KiB
Lua

if not remoteMap then
if not os.loadAPI("remoteMap") then
error("could not load remoteMap API")
end
end
if not aStar then
if not os.loadAPI("aStar") then
error("could not load aStar API")
end
end
if not location then
if not os.loadAPI("location") then
error("could not load location API")
end
end
local position
local SESSION_COORD_CLEAR = 1
local SESSION_COORD_BLOCKED = 2
local UPDATE_COORD_CLEAR = -1
local UPDATE_COORD_BLOCKED = 1
local sessionMap
local serverMap
local function distanceFunc(a, b)
local sessionMapA, sessionMapB = sessionMap:get(a), sessionMap:get(b)
if sessionMapA == SESSION_COORD_BLOCKED or sessionMapB == SESSION_COORD_BLOCKED then
return math.huge -- 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 2^(serverMapA + 1) or 1
serverMapB = serverMapB and 2^(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(peripheral.getNames()) 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
else
detectAll(currPos)
end
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
function goto(x, y, z)
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
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
local path = aStar.compute(distanceFunc, position, goal)
if not path then
return false, "no known path to goal"
end
while not 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)
serverMap:check()
serverMap:pushUpdates()
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 true
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