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