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_MAX_DISTANCE_DEFAULT = 32 local SESSION_COORD_CLEAR = 1 local SESSION_COORD_BLOCKED = 2 local UPDATE_COORD_CLEAR = -1 local UPDATE_COORD_BLOCKED = 1 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 math.huge -- first coord is outside the search region elseif aStar.distance(b, sessionMidPoint) > sessionMaxDistance then return math.huge -- second coord is outside the search region elseif 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 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 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