Add files

This commit is contained in:
Matt Blunt 2016-02-07 18:20:06 +00:00
parent d63007f101
commit c0673612b1
14 changed files with 2204 additions and 0 deletions

166
aStar Normal file
View File

@ -0,0 +1,166 @@
if not pQueue then
if not os.loadAPI("pQueue") then
error("could not load pQueue API")
end
end
-- a very basic map API used to store node information
local mapMethods = {
get = function(self, tVector)
if self.map[tVector.x] and self.map[tVector.x][tVector.y] then
return self.map[tVector.x][tVector.y][tVector.z]
end
end,
set = function(self, tVector, value)
if not self.map[tVector.x] then
self.map[tVector.x] = {}
end
if not self.map[tVector.x][tVector.y] then
self.map[tVector.x][tVector.y] = {}
end
self.map[tVector.x][tVector.y][tVector.z] = value
return self.map[tVector.x][tVector.y][tVector.z]
end,
getOrSet = function(self, tVector, value)
if self.map[tVector.x] and self.map[tVector.x][tVector.y] and self.map[tVector.x][tVector.y][tVector.z] ~= nil then
return self.map[tVector.x][tVector.y][tVector.z], false
else
return self:set(tVector, value), true
end
end,
}
local mapMetatable = {__index = mapMethods}
function newMap()
return setmetatable({map = {}}, mapMetatable)
end
local function makePath(nodes, start, startEnd, goalStart, goal)
local current, path = startEnd, {}
while not vectorEquals(current, start) do
table.insert(path, current)
current = nodes:get(current)[1]
end
current = goalStart
while not vectorEquals(current, goal) do
table.insert(path, 1, current)
current = nodes:get(current)[1]
end
table.insert(path, 1, goal)
return path
end
function vectorEquals(a, b) -- the comparison function used in pQueue
return a.x == b.x and a.y == b.y and a.z == b.z
end
local posZ = vector.new(0, 0, 1)
local negX = vector.new(-1, 0, 0)
local negZ = vector.new(0, 0, -1)
local posX = vector.new(1, 0, 0)
local posY = vector.new(0, 1, 0)
local negY = vector.new(0, -1, 0)
function adjacent(u)
return {
u + posZ,
u + negX,
u + negZ,
u + posX,
u + posY,
u + negY,
}
end
function distance(a, b) -- 1-norm/manhattan metric
return math.abs(a.x - b.x) + math.abs(a.y - b.y) + math.abs(a.z - b.z)
end
function compute(distanceFunction, start, goal)
if type(distanceFunction) ~= "function" then
error("aStar new: distanceFunction must be of type function", 2)
end
local distanceFunc = distanceFunction -- is this necessary?
-- node data structure is {parent node, true cost from startNode/goalNode, whether in closed list, search direction this node was found in, whether in open list}
local nodes = newMap()
nodes:set(start, {start + vector.new(0, 0, -1), 0, false, true, true})
nodes:set(goal, {goal + vector.new(0, 0, -1), 0, false, false, true})
local openStartSet = pQueue.new()
openStartSet:insert(start, distance(start, goal))
local openGoalSet = pQueue.new()
openGoalSet:insert(goal, distance(start, goal))
local yieldCount = 0
local activeOpenSet, pendingOpenSet = openStartSet, openGoalSet
local forwardSearch, lastNode, switch = true, false, false
local current, currNode, parent
local baseCost
local newCost
local nbrNode, newNode
local preHeuristic
while not openStartSet:isEmpty() and not openGoalSet:isEmpty() do
--yield every so often to avoid getting timed out
yieldCount = yieldCount + 1
if yieldCount > 200 then
os.pullEvent(os.queueEvent("yield"))
yieldCount = 0
end
if switch then --switch the search direction
activeOpenSet, pendingOpenSet = pendingOpenSet, activeOpenSet
forwardSearch = not forwardSearch
lastNode = false
end
current = activeOpenSet:pop()
currNode = nodes:get(current)
parent = current - currNode[1]
currNode[3], currNode[5], switch = true, false, true
for _, neighbour in ipairs(adjacent(current)) do
baseCost = distanceFunc(current, neighbour)
if baseCost < math.huge then -- if not graph:get(neighbour) then
newCost = currNode[2] + baseCost
nbrNode, newNode = nodes:getOrSet(neighbour, {current, newCost, false, forwardSearch, false})
if switch and ((not lastNode) or vectorEquals(lastNode, neighbour)) then
switch = false
end
if not newNode then
if forwardSearch ~= nbrNode[4] then -- nbrNode has been discovered in the opposite search direction
if nbrNode[3] then -- and is in the closed list so has been expanded already
return makePath(nodes, start, (forwardSearch and current) or neighbour, (forwardSearch and neighbour) or current, goal)
end
elseif newCost < nbrNode[2] then
if nbrNode[5] then
activeOpenSet:remove(neighbour, vectorEquals)
nbrNode[5] = false
end
nbrNode[3] = false
end
end
if (newNode or (forwardSearch ~= nbrNode[4] and not nbrNode[5] and not nbrNode[3])) and newCost < math.huge then
nbrNode[1] = current
nbrNode[2] = newCost
nbrNode[4] = currNode[4]
nbrNode[5] = true
preHeuristic = distance(neighbour, (forwardSearch and goal) or start)
activeOpenSet:insert(neighbour, newCost + preHeuristic + 0.0001*(preHeuristic + parent.length(parent:cross(neighbour - current))))
end
end
end
lastNode = current
end
return false
end

131
location Normal file
View File

@ -0,0 +1,131 @@
-- a slightly modified version of an API I found somewhere
-- the headings have been altered to use the Minecraft directions
local location = {
add = function(self, o)
return vector.new(
self.x + o.x,
self.y + o.y,
self.z + o.z
)
end,
sub = function(self, o)
return vector.new(
self.x - o.x,
self.y - o.y,
self.z - o.z
)
end,
mul = function(self, m)
return vector.new(
self.x * m,
self.y * m,
self.z * m
)
end,
div = function(self, d)
return vector.new(
self.x / d,
self.y / d,
self.z / d
)
end,
left = function(self)
if turtle.turnLeft() then
self.h = (self.h - 1) % 4
return true
end
return false
end,
right = function(self)
if turtle.turnRight() then
self.h = (self.h + 1) % 4
return true
end
return false
end,
forward = function(self)
if turtle.forward() then
self.x = self.x + (self.h - 2) * (self.h % 2)
self.z = self.z + (1 - self.h) * ((self.h + 1) % 2)
return true
end
return false
end,
back = function(self)
if turtle.back() then
self.x = self.x - (self.h - 2) * (self.h % 2)
self.z = self.z - (1 - self.h) * ((self.h + 1) % 2)
return true
end
return false
end,
up = function(self)
if turtle.up() then
self.y = self.y + 1
return true
end
return false
end,
down = function(self)
if turtle.down() then
self.y = self.y - 1
return true
end
return false
end,
moveDeltas = function(self)
return (self.h - 2) * (self.h % 2), (1 - self.h) * ((self.h + 1) % 2)
end,
setHeading = function(self, heading)
if not heading or heading < 0 or heading > 3 then return nil, "Heading Not in Range" end
while self.h ~= heading do
if (self.h + 1) % 4 == heading % 4 then
self:right()
else
self:left()
end
end
return true
end,
getHeading = function(self)
return self.h
end,
tovector = function(self)
if vector then
return vector.new(
self.x,
self.y,
self.z
)
else
return nil
end
end,
tostring = function(self)
return self.x..","..self.y..","..self.z..","..self.h
end,
value = function(self)
return self.x, self.y, self.z, self.h
end,
}
local lmetatable = {
__index = location,
__add = location.add,
__sub = location.sub,
__mul = location.mul,
__div = location.div,
__unm = function(l) return l:mul(-1) end,
__tostring = function(l) return l:tostring() end,
}
function new( x, y, z, h )
local l = {
x = x or 0,
y = y or 0,
z = z or 0,
h = h or 1
}
setmetatable( l, lmetatable )
return l
end

209
maps/compactMap Normal file
View File

@ -0,0 +1,209 @@
local BIT_MASKS = {
SET_COORD = bit.blshift(1, 7),
SET_X_COORD = bit.blshift(1, 6),
COORD = 15,
COORD_DATA = bit.blshift(7, 4),
}
local function isValidValue(value)
return value == nil or (type(value) == "number" and value % 1 == 0 and value >= 0 and value <= 7)
end
local function toGridCode(tVector)
return math.floor(tVector.x/16), math.floor(tVector.y/16), math.floor(tVector.z/16), tVector.x % 16, tVector.y % 16, tVector.z % 16
end
local function setGrid(tMap, x, y, z, grid)
if not tMap.map[x] then
tMap.map[x] = {}
end
if not tMap.map[x][y] then
tMap.map[x][y] = {}
end
tMap.map[x][y][z] = grid
return tMap.map[x][y][z]
end
local function getGrid(tMap, x, y, z)
if not tMap.map[x] or not tMap.map[x][y] or not tMap.map[x][y][z] then
return tMap:load(x, y, z)
else
return tMap.map[x][y][z]
end
end
local mapMethods = {
getGrid = function(self, tVector, y, z)
local gX, gY, gZ
if y and z then
gX, gY, gZ = tVector, y, z
else
gX, gY, gZ = toGridCode(tVector)
end
return getGrid(self, gX, gY, gZ)
end,
load = function(self, tVector, y, z)
local gX, gY, gZ
if y and z then
gX, gY, gZ = tVector, y, z
else
gX, gY, gZ = toGridCode(tVector)
end
local gridPath = fs.combine(self.mapDir, gX..","..gY..","..gZ)
if fs.exists(gridPath) then
local handle = fs.open(gridPath, "rb")
if handle then
local grid = {}
--load grid data
local currX, currY, currZ
local dataByte = handle.read()
while dataByte do
if bit.band(dataByte, BIT_MASKS.SET_COORD) == BIT_MASKS.SET_COORD then
--we are changing our currX or currY coord
if bit.band(dataByte, BIT_MASKS.SET_X_COORD) == BIT_MASKS.SET_X_COORD then
--we are changing our currX coord
currX = bit.band(dataByte, BIT_MASKS.COORD)
else
--we are changing our currY coord
currY = bit.band(dataByte, BIT_MASKS.COORD)
end
else
--we are setting the value for a proper coord
currZ = bit.band(dataByte, BIT_MASKS.COORD)
if currX and currY and currZ then
if not grid[currX] then
grid[currX] = {}
end
if not grid[currX][currY] then
grid[currX][currY] = {}
end
grid[currX][currY][currZ] = bit.brshift(bit.band(dataByte, BIT_MASKS.COORD_DATA), 4)
end
end
dataByte = handle.read()
end
handle.close()
return setGrid(self, gX, gY, gZ, grid)
end
end
return setGrid(self, gX, gY, gZ, {})
end,
loadAll = function(self)
if fs.exists(self.mapDir) and fs.isDir(self.mapDir) then
for _, gridFile in ipairs(fs.list(self.mapDir)) do
local _, _, gX, gY, gZ = string.find(gridFile, "(.+)%,(.+)%,(.+)")
if gX and gY and gX then
self:load(tonumber(gX), tonumber(gY), tonumber(gZ))
end
end
end
end,
save = function(self, tVector, y, z)
local gX, gY, gZ
if y and z then
gX, gY, gZ = tVector, y, z
else
gX, gY, gZ = toGridCode(tVector)
end
if self.map[gX] and self.map[gX][gY] and self.map[gX][gY][gZ] then
local grid = self.map[gX][gY][gZ]
if next(grid) then
local handle = fs.open(fs.combine(self.mapDir, gX..","..gY..","..gZ), "wb")
if handle then
for x, gridYZ in pairs(grid) do
handle.write(BIT_MASKS.SET_COORD + BIT_MASKS.SET_X_COORD + x)
for y, gridZ in pairs(gridYZ) do
handle.write(BIT_MASKS.SET_COORD + y)
for z, coordValue in pairs(gridZ) do
handle.write(bit.blshift(coordValue, 4) + z)
end
end
end
handle.close()
end
else
fs.delete(fs.combine(self.mapDir, gX..","..gY..","..gZ))
end
end
end,
saveAll = function(self)
for gX, YZmap in pairs(self.map) do
for gY, Zmap in pairs(YZmap) do
for gZ, grid in pairs(Zmap) do
self:save(gX, gY, gZ)
end
end
end
end,
get = function(self, tVector)
local gX, gY, gZ, pX, pY, pZ = toGridCode(tVector)
local grid = getGrid(self, gX, gY, gZ)
if grid[pX] and grid[pX][pY] then
return grid[pX][pY][pZ]
end
end,
set = function(self, tVector, value)
if not isValidValue(value) then
--should we throw an error or use a default value?
error("compactMap set: value is not valid", 2)
end
local gX, gY, gZ, pX, pY, pZ = toGridCode(tVector)
local grid = getGrid(self, gX, gY, gZ)
if not grid[pX] then
grid[pX] = {}
end
if not grid[pX][pY] then
grid[pX][pY] = {}
end
grid[pX][pY][pZ] = value
return grid[pX][pY][pZ]
end,
getOrSet = function(self, tVector, value)
local gX, gY, gZ, pX, pY, pZ = toGridCode(tVector)
local grid = getGrid(self, gX, gY, gZ)
if grid[pX] and grid[pX][pY] and grid[pX][pY][pZ] then
return grid[pX][pY][pZ], false
else
if not isValidValue(value) then
--should we throw an error or use a default value?
error("compactMap getOrSet: value is not valid", 2)
end
if not grid[pX] then
grid[pX] = {}
end
if not grid[pX][pY] then
grid[pX][pY] = {}
end
grid[pX][pY][pZ] = value
return grid[pX][pY][pZ], true
end
end,
}
local mapMetatable = {__index = mapMethods}
function new(mapDir)
local tMap = {}
if type(mapDir) == "string" then
if not fs.exists(mapDir) then
fs.makeDir(mapDir)
elseif not fs.isDir(mapDir) then
error("compactMap new: not a valid directory", 2)
end
tMap.mapDir = mapDir
else
error("compactMap new: directory must be string", 2)
end
tMap.map = {}
setmetatable(tMap, mapMetatable)
return tMap
end

142
maps/map Normal file
View File

@ -0,0 +1,142 @@
local maps = {}
local function toGridCode(tVector)
return math.floor(tVector.x/16), math.floor(tVector.y/16), math.floor(tVector.z/16), tVector.x % 16, tVector.y % 16, tVector.z % 16
end
local function setGrid(tMap, x, y, z, grid)
if not tMap.map[x] then
tMap.map[x] = {}
end
if not tMap.map[x][y] then
tMap.map[x][y] = {}
end
tMap.map[x][y][z] = grid
return tMap.map[x][y][z]
end
local function getGrid(tMap, x, y, z)
if not tMap.map[x] or not tMap.map[x][y] or not tMap.map[x][y][z] then
return tMap:load(x, y, z)
else
return tMap.map[x][y][z]
end
end
local methods = {
load = function(self, tVector, y, z)
local gX, gY, gZ
if y and z then
gX, gY, gZ = tVector, y, z
else
gX, gY, gZ = toGridCode(tVector)
end
if self.name then
if fs.exists(".maps/"..self.name.."/"..gX..","..gY..","..gZ) then
local handle = fs.open(".maps/"..self.name.."/"..gX..","..gY..","..gZ, "r")
if handle then
local grid = handle.readAll()
handle.close()
for i = 15, 0, -1 do
grid = string.gsub(grid, tostring(i).."=", "%["..tostring(i).."%]=")
end
grid = textutils.unserialize(grid)
if type(grid) == "table" then
return setGrid(self, gX, gY, gZ, grid)
end
end
end
end
return setGrid(self, gX, gY, gZ, {})
end,
loadAll = function(self)
if self.name and fs.exists(".maps/"..self.name) and fs.isDir(".maps/"..self.name) then
for _, gridFile in ipairs(fs.list(".maps/"..self.name)) do
local _, _, gX, gY, gZ = string.find(gridFile, "(.+)%,(.+)%,(.+)")
if gX and gY and gX then
self:load(tonumber(gX), tonumber(gY), tonumber(gZ))
end
end
end
end,
save = function(self)
if self.name then
local saveDir = ".maps/"..self.name
for x, YZmap in pairs(self.map) do
for y, Zmap in pairs(YZmap) do
for z, grid in pairs(Zmap) do
if next(grid) then
local handle = fs.open(fs.combine(saveDir, x..","..y..","..z), "w")
local data = textutils.serialize(grid)
data = string.gsub(data, " ", "")
for i = 0, 15 do
data, num = string.gsub(data, "%["..tostring(i).."%]=", tostring(i).."=")
end
handle.write(data)
handle.close()
end
end
end
end
end
end,
get = function(self, tVector)
local gX, gY, gZ, pX, pY, pZ = toGridCode(tVector)
local grid = getGrid(self, gX, gY, gZ)
if grid[pX] and grid[pX][pY] then
return grid[pX][pY][pZ]
end
end,
set = function(self, tVector, value)
local gX, gY, gZ, pX, pY, pZ = toGridCode(tVector)
local grid = getGrid(self, gX, gY, gZ)
if not grid[pX] then
grid[pX] = {}
end
if not grid[pX][pY] then
grid[pX][pY] = {}
end
grid[pX][pY][pZ] = value
return grid[pX][pY][pZ]
end,
getOrSet = function(self, tVector, value)
local gX, gY, gZ, pX, pY, pZ = toGridCode(tVector)
local grid = getGrid(self, gX, gY, gZ)
if grid[pX] and grid[pX][pY] and grid[pX][pY][pZ] then
return grid[pX][pY][pZ], false
else
if not grid[pX] then
grid[pX] = {}
end
if not grid[pX][pY] then
grid[pX][pY] = {}
end
grid[pX][pY][pZ] = value
return grid[pX][pY][pZ], true
end
end,
}
function new(name)
local tMap = {}
if name and type(name) == "string" then
if maps[name] then
return maps[name]
end
tMap.name = name
if not fs.exists(".maps/"..name) then
fs.makeDir(".maps/"..name)
end
maps[name] = tMap
end
tMap.map = {}
setmetatable(tMap, {__index = methods})
return tMap
end

185
maps/remoteMap_client Normal file
View File

@ -0,0 +1,185 @@
local REDNET_TIMEOUT = 1
local MESSAGE_TYPE = {
GET = 0,
SET = 1,
}
local function newMessage(messageType, grid, data)
return {
type = messageType,
ID = math.random(0, 2^30),
grid = grid,
data = data,
}
end
local function sendAndWaitForResponse(recipientID, message, protocol)
rednet.send(recipientID, message, protocol)
local attemptNumber = 1
while true do
local senderID, reply = rednet.receive(protocol, REDNET_TIMEOUT)
if senderID == recipientID and type(reply) == "table" and reply.type == message.type and reply.ID == message.ID then
return reply.data
elseif not senderID then
if attemptNumber < 3 then
rednet.send(recipientID, message, protocol)
attemptNumber = attemptNumber + 1
else
return false
end
end
end
end
local function isValidValue(value)
return value == nil or value == -1 or value == 1
end
local function toGridCode(tVector)
return math.floor(tVector.x/16), math.floor(tVector.y/16), math.floor(tVector.z/16), tVector.x % 16, tVector.y % 16, tVector.z % 16
end
local function getRemoteGrid(tMap, x, y, z)
if not tMap.remoteGrids[x] or not tMap.remoteGrids[x][y] or not tMap.remoteGrids[x][y][z] then
local message = newMessage(MESSAGE_TYPE.GET, {x, y, z}, nil)
local remoteGrid = sendAndWaitForResponse(tMap.serverID, message, tMap.protocol) or {}
tMap.remoteGridsAge[x..","..y..","..z] = os.clock()
if not tMap.remoteGrids[x] then
tMap.remoteGrids[x] = {}
end
if not tMap.remoteGrids[x][y] then
tMap.remoteGrids[x][y] = {}
end
tMap.remoteGrids[x][y][z] = remoteGrid
return remoteGrid
else
return tMap.remoteGrids[x][y][z]
end
end
local function getUpdateGrid(tMap, x, y, z)
if not tMap.updateGrids[x] or not tMap.updateGrids[x][y] or not tMap.updateGrids[x][y][z] then
local updateGrid = {}
if not tMap.updateGrids[x] then
tMap.updateGrids[x] = {}
end
if not tMap.updateGrids[x][y] then
tMap.updateGrids[x][y] = {}
end
tMap.updateGrids[x][y][z] = updateGrid
return updateGrid
else
return tMap.updateGrids[x][y][z]
end
end
local remoteMapMethods = {
get = function(self, coord)
local gX, gY, gZ, pX, pY, pZ = toGridCode(coord)
local grid = getRemoteGrid(self, gX, gY, gZ)
if grid[pX] and grid[pX][pY] then
return grid[pX][pY][pZ]
end
end,
set = function(self, coord, value)
if not isValidValue(value) then
--should we throw an error or use a default value?
error("remoteMap set: value is not valid", 2)
end
local gX, gY, gZ, pX, pY, pZ = toGridCode(coord)
local grid = getUpdateGrid(self, gX, gY, gZ)
if not grid[pX] then
grid[pX] = {}
end
if not grid[pX][pY] then
grid[pX][pY] = {}
end
grid[pX][pY][pZ] = value
end,
check = function(self)
local time = os.clock()
local newRemoteGridsAge = {}
for gridCode, gridAge in pairs(self.remoteGridsAge) do
if time - gridAge >= self.timeout then
local x, y, z = string.match(gridCode, "([-]?%d+),([-]?%d+),([-]?%d+)")
x, y, z = tonumber(x), tonumber(y), tonumber(z)
if x and y and z then
if self.remoteGrids[x] and self.remoteGrids[x][y] then
self.remoteGrids[x][y][z] = nil
end
end
else
newRemoteGridsAge[gridCode] = gridAge
end
end
local newUpdateGridsAge = {}
for gridCode, gridAge in pairs(self.updateGridsAge) do
if time - gridAge >= self.timeout then
-- remove grid from updateGrids ???
else
newUpdateGridsAge[gridCode] = gridAge
end
end
self.remoteGridsAge = newRemoteGridsAge
self.updateGridsAge = newUpdateGridsAge
end,
pushUpdates = function(self, ignoreTimeout)
local newUpdateGrids = {}
for gX, YZmap in pairs(self.updateGrids) do
newUpdateGrids[gX] = {}
for gY, Zmap in pairs(YZmap) do
newUpdateGrids[gX][gY] = {}
for gZ, grid in pairs(Zmap) do
local gridCode = gX..","..gY..","..gZ
if next(grid) then
if ignoreTimeout == true or (not self.updateGridsAge[gridCode]) or os.clock() - self.updateGridsAge[gridCode] >= self.timeout then
local message = newMessage(MESSAGE_TYPE.SET, {gX, gY, gZ}, grid)
local response = sendAndWaitForResponse(self.serverID, message, self.protocol)
if response == true then
self.updateGridsAge[gridCode] = os.clock()
else
newUpdateGrids[gX][gY][gZ] = grid
end
else
newUpdateGrids[gX][gY][gZ] = grid
end
end
end
end
end
self.updateGrids = newUpdateGrids
end,
}
local remoteMapMetatable = {__index = remoteMapMethods}
function new(mapName, timeout)
if type(mapName) ~= "string" then
error("mapName must be string")
end
if type(timeout) ~= "number" or timeout < 0 then
error("timeout must be positive number")
end
local protocol = "map_share:"..mapName
local serverID = rednet.lookup(protocol, "SERVER")
if not serverID then
error("could not find map share server")
end
local remoteMap = {
mapName = mapName,
protocol = protocol,
serverID = serverID,
timeout = timeout,
remoteGrids = {},
remoteGridsAge = {},
updateGrids = {},
updateGridsAge = {},
}
return setmetatable(remoteMap, remoteMapMetatable)
end

163
maps/remoteMap_server Normal file
View File

@ -0,0 +1,163 @@
local args = {...}
--===== OPEN REDNET =====--
for _, side in ipairs(redstone.getSides()) do
if peripheral.getType(side) == "modem" then
rednet.open(side)
end
end
if not rednet.isOpen() then
printError("could not open rednet")
return
end
--===== LOAD MAP =====--
if not compactMap then
if not os.loadAPI("compactMap") then
error("could not load API: compactMap")
end
end
local map = compactMap.new(args[1])
--===== SET REDNET PROTOCOL =====--
local MAP_SHARE_PROTOCOL
if args[2] and type(args[2]) == "string" then
MAP_SHARE_PROTOCOL = "map_share:"..args[2]
else
MAP_SHARE_PROTOCOL = "map_share:"..fs.getName(args[1])
end
--===== HOST AS SERVER =====--
do
local host = rednet.lookup(MAP_SHARE_PROTOCOL, "SERVER")
if host and host ~= os.computerID() then
printError("server already running for this map share")
return
end
end
rednet.host(MAP_SHARE_PROTOCOL, "SERVER")
--===== UTILS =====--
local MESSAGE_TYPE = {
GET = 0,
SET = 1,
}
local receivedMessages = {}
local receivedMessageTimeouts = {}
local function isValidCoord(coord)
return type(coord) == "number" and coord % 1 == 0 and coord >= 0 and coord <= 15
end
local function updateCoord(x, y, z, value)
local coord = vector.new(x, y, z)
local currValue = map:get(coord)
if value == 1 then
if currValue then
map:set(coord, math.min(7, currValue + 1))
else
map:set(coord, 0)
end
elseif value == -1 then
if currValue then
if currValue == 0 then
map:set(coord, nil)
else
map:set(coord, currValue - 1)
end
end
end
end
local function updateMap(newData, gX, gY, gZ)
if type(newData) == "table" then
local currX, currY
for x, gridYZ in pairs(newData) do
if isValidCoord(x) and type(gridYZ) == "table" then
currX = gX*16 + x
for y, gridZ in pairs(gridYZ) do
if isValidCoord(y) and type(gridZ) == "table" then
currY = gY*16 + y
for z, value in pairs(gridZ) do
if isValidCoord(z) then
updateCoord(currX, currY, gZ*16 + z, value)
end
end
end
end
end
end
map:save(gX, gY, gZ)
end
end
local function checkGridCoordFormat(gridCoord)
if type(gridCoord) == "table" and #gridCoord == 3 then
for i = 1, 3 do
local coord = gridCoord[i]
if type(coord) ~= "number" or coord % 1 ~= 0 then
return false
end
end
return true
end
return false
end
local function newMessage(messageType, messageID, grid, data)
return {
type = messageType,
ID = messageID,
grid = grid,
data = data,
}
end
--===== REPEATED MESSAGE HANDLING =====--
local function clearOldMessages()
while true do
local event, timer = os.pullEvent("timer")
local messageID = receivedMessageTimeouts[timer]
if messageID then
receivedMessageTimeouts[timer] = nil
receivedMessages[messageID] = nil
end
end
end
--===== MAIN =====--
local function main()
while true do
local senderID, message = rednet.receive(MAP_SHARE_PROTOCOL)
if type(message) == "table" and checkGridCoordFormat(message.grid) then
if message.type == MESSAGE_TYPE.GET then
local gridData = map:getGrid(unpack(message.grid))
local replyMessage = newMessage(MESSAGE_TYPE.GET, message.ID, message.grid, gridData)
rednet.send(senderID, replyMessage, MAP_SHARE_PROTOCOL)
elseif message.type == MESSAGE_TYPE.SET then
if not receivedMessages[message.ID] then
updateMap(message.data, unpack(message.grid))
receivedMessages[message.ID] = true
receivedMessageTimeouts[os.startTimer(15)] = message.ID
end
local replyMessage = newMessage(MESSAGE_TYPE.SET, message.ID, message.grid, true)
rednet.send(senderID, replyMessage, MAP_SHARE_PROTOCOL)
end
end
end
end
--===== USER INTERFACE =====--
local function control()
while true do
local event, key = os.pullEvent("key")
if key == keys.backspace then
break
end
end
end
parallel.waitForAny(main, clearOldMessages, control)
rednet.unhost(MAP_SHARE_PROTOCOL)

117
maps/remoteMap_viewer Normal file
View File

@ -0,0 +1,117 @@
local tArgs = {...}
local function printUsage()
print("Usage: viewRemoteMap <(string) mapName> <(int) x-coord> <(int) y-coord> <(int) z-coord>")
print("mapName must be the complete file path")
end
if not remoteMap then
if not os.loadAPI("remoteMap") then
error("Could not load remoteMap API")
end
end
for _, side in ipairs(redstone.getSides()) do
if peripheral.getType(side) == "modem" then
rednet.open(side)
end
end
if type(tArgs[1]) ~= "string" then
error("string expected for map name")
end
local map = remoteMap.new(tArgs[1], 5)
local startX, startY, startZ
if #tArgs == 4 then
for i = 2, 4 do
local num = tArgs[i]
if not tonumber(num) or num % 1 ~= 0 then
printUsage()
return
end
end
startX = tArgs[2]
startY = tArgs[3]
startZ = tArgs[4]
end
term.setCursorBlink(false)
term.setBackgroundColour(colours.black)
local cont = true
local width, height = term.getSize()
local currW, currH = 1, 1
term.setCursorBlink(true)
term.setTextColour(colours.red)
local redraw = true
while cont do
if redraw then
map:check()
term.setBackgroundColour(colours.black)
term.clear()
term.setCursorPos(1, height)
term.clearLine()
term.write(tostring(startX + currW - 1)..","..tostring(startY)..","..tostring(startZ + currH - 1))
term.write(" -- ")
term.write(tostring(math.floor( (startX + currW - 1)/16 )))
term.write(",")
term.write(tostring(math.floor( (startY)/16 )))
term.write(",")
term.write(tostring(math.floor( (startZ + currH - 1)/16 )))
for x = 1, width do
for z = 1, height - 1 do
local value = map:get(vector.new(startX + x - 1, startY, startZ + z - 1))
if value then
term.setBackgroundColour(colours.white)
term.setCursorPos(x, z)
term.write(string.sub(value, 1, 1))
end
end
end
term.setCursorPos(currW, currH)
redraw = false
end
local event = {os.pullEvent()}
if event[1] == "key" then
local key = event[2]
if key == keys.up then
startZ = startZ - 1
elseif key == keys.down then
startZ = startZ + 1
elseif key == keys.left then
startX = startX - 1
elseif key == keys.right then
startX = startX + 1
elseif key == keys.numPadAdd then
startY = startY + 1
elseif key == keys.numPadSubtract then
startY = startY - 1
elseif key == keys.backspace then
cont = false
end
redraw = true
elseif event[1] == "mouse_click" then
if event[4] < height then
currW, currH = event[3], event[4]
term.setBackgroundColour(colours.black)
term.setCursorPos(1, height)
term.clearLine()
term.write(tostring(startX + currW - 1)..","..tostring(startY)..","..tostring(startZ + currH - 1))
term.write(" -- ")
term.write(tostring(math.floor( (startX + currW - 1)/16 )))
term.write(",")
term.write(tostring(math.floor( (startY)/16 )))
term.write(",")
term.write(tostring(math.floor( (startZ + currH - 1)/16 )))
term.setCursorPos(currW, currH)
end
elseif event[1] == "term_resize" then
width, height = term.getSize()
redraw = true
end
end
term.setBackgroundColour(colours.black)
term.setCursorPos(1, 1)
term.clear()

222
maps/tinyMap Normal file
View File

@ -0,0 +1,222 @@
local BIT_MASKS = {
SET_COORD = bit.blshift(1, 5),
SET_X_COORD = bit.blshift(1, 4),
COORD = 15,
COORD_DATA = bit.blshift(1, 4),
}
local function base256_to_base64(input)
local output = {}
for i = 1, #input, 3 do
table.insert(output, bit.brshift(input[i] or 0, 2))
table.insert(output, bit.blshift(bit.band(input[i] or 0, 3), 4) + bit.brshift(input[i+1] or 0, 4))
table.insert(output, bit.blshift(bit.band(input[i+1] or 0, 15), 2) + bit.brshift(input[i+2] or 0, 6))
table.insert(output, bit.band(input[i+2] or 0, 63))
end
return output
end
local function base64_to_base256(input)
local output = {}
for i = 1, #input, 4 do
table.insert(output, bit.blshift(input[i] or 0, 2) + bit.brshift(input[i+1] or 0, 4))
table.insert(output, bit.blshift(bit.band(input[i+1] or 0, 15), 4) + bit.brshift(input[i+2] or 0, 2))
table.insert(output, bit.blshift(bit.band(input[i+2] or 0, 3), 6) + (input[i+3] or 0))
end
return output
end
local function isValidValue(value)
return value == nil or value == 0 or value == 1
end
local function toGridCode(tVector)
return math.floor(tVector.x/16), math.floor(tVector.y/16), math.floor(tVector.z/16), tVector.x % 16, tVector.y % 16, tVector.z % 16
end
local function setGrid(tMap, x, y, z, grid)
if not tMap.map[x] then
tMap.map[x] = {}
end
if not tMap.map[x][y] then
tMap.map[x][y] = {}
end
tMap.map[x][y][z] = grid
return tMap.map[x][y][z]
end
local function getGrid(tMap, x, y, z)
if not tMap.map[x] or not tMap.map[x][y] or not tMap.map[x][y][z] then
return tMap:load(x, y, z)
else
return tMap.map[x][y][z]
end
end
local mapMethods = {
load = function(self, tVector, y, z)
local gX, gY, gZ
if y and z then
gX, gY, gZ = tVector, y, z
else
gX, gY, gZ = toGridCode(tVector)
end
local gridPath = fs.combine(self.mapDir, gX..","..gY..","..gZ)
if fs.exists(gridPath) then
local handle = fs.open(gridPath, "rb")
if handle then
local grid = {}
local rawData = {}
local rawDataByte = handle.read()
while rawDataByte do
table.insert(rawData, rawDataByte)
rawDataByte = handle.read()
end
handle.close()
--local data = rawData
local data = base256_to_base64(rawData)
--load grid data
local currX, currY, currZ
local dataByte
for _, dataByte in ipairs(data) do
if bit.band(dataByte, BIT_MASKS.SET_COORD) == BIT_MASKS.SET_COORD then
--we are changing our currX or currY coord
if bit.band(dataByte, BIT_MASKS.SET_X_COORD) == BIT_MASKS.SET_X_COORD then
--we are changing our currX coord
currX = bit.band(dataByte, BIT_MASKS.COORD)
else
--we are changing our currY coord
currY = bit.band(dataByte, BIT_MASKS.COORD)
end
else
--we are setting the value for a proper coord
currZ = bit.band(dataByte, BIT_MASKS.COORD)
if currX and currY and currZ then
if not grid[currX] then
grid[currX] = {}
end
if not grid[currX][currY] then
grid[currX][currY] = {}
end
grid[currX][currY][currZ] = bit.brshift(bit.band(dataByte, BIT_MASKS.COORD_DATA), 4)
end
end
end
return setGrid(self, gX, gY, gZ, grid)
end
end
return setGrid(self, gX, gY, gZ, {})
end,
loadAll = function(self)
if fs.exists(self.mapDir) and fs.isDir(self.mapDir) then
for _, gridFile in ipairs(fs.list(self.mapDir)) do
local _, _, gX, gY, gZ = string.find(gridFile, "(.+)%,(.+)%,(.+)")
if gX and gY and gX then
self:load(tonumber(gX), tonumber(gY), tonumber(gZ))
end
end
end
end,
save = function(self)
for gX, YZmap in pairs(self.map) do
for gY, Zmap in pairs(YZmap) do
for gZ, grid in pairs(Zmap) do
if next(grid) then
local rawData = {}
for x, gridYZ in pairs(grid) do
table.insert(rawData, BIT_MASKS.SET_COORD + BIT_MASKS.SET_X_COORD + x)
for y, gridZ in pairs(gridYZ) do
table.insert(rawData, BIT_MASKS.SET_COORD + y)
for z, coordValue in pairs(gridZ) do
table.insert(rawData, bit.blshift(coordValue, 4) + z)
end
end
end
--local data = rawData
local data = base64_to_base256(rawData)
local handle = fs.open(fs.combine(self.mapDir, gX..","..gY..","..gZ), "wb")
if handle then
for _, dataByte in ipairs(data) do
handle.write(dataByte)
end
handle.close()
end
else
fs.delete(fs.combine(self.mapDir, gX..","..gY..","..gZ))
end
end
end
end
end,
get = function(self, tVector)
local gX, gY, gZ, pX, pY, pZ = toGridCode(tVector)
local grid = getGrid(self, gX, gY, gZ)
if grid[pX] and grid[pX][pY] then
return grid[pX][pY][pZ]
end
end,
set = function(self, tVector, value)
if not isValidValue(value) then
--should we throw an error or use a default value?
error("set: value is not valid", 2)
end
local gX, gY, gZ, pX, pY, pZ = toGridCode(tVector)
local grid = getGrid(self, gX, gY, gZ)
if not grid[pX] then
grid[pX] = {}
end
if not grid[pX][pY] then
grid[pX][pY] = {}
end
grid[pX][pY][pZ] = value
return grid[pX][pY][pZ]
end,
getOrSet = function(self, tVector, value)
local gX, gY, gZ, pX, pY, pZ = toGridCode(tVector)
local grid = getGrid(self, gX, gY, gZ)
if grid[pX] and grid[pX][pY] and grid[pX][pY][pZ] then
return grid[pX][pY][pZ], false
else
if not isValidValue(value) then
--should we throw an error or use a default value?
error("getOrSet: value is not valid", 2)
end
if not grid[pX] then
grid[pX] = {}
end
if not grid[pX][pY] then
grid[pX][pY] = {}
end
grid[pX][pY][pZ] = value
return grid[pX][pY][pZ], true
end
end,
}
local mapMetatable = {__index = mapMethods}
function new(mapDir)
local tMap = {}
if type(mapDir) == "string" then
if not fs.exists(mapDir) then
fs.makeDir(mapDir)
elseif not fs.isDir(mapDir) then
error("new: not a valid directory", 2)
end
tMap.mapDir = mapDir
else
error("new: directory must be string", 2)
end
tMap.map = {}
setmetatable(tMap, mapMetatable)
return tMap
end

128
maps/viewMap Normal file
View File

@ -0,0 +1,128 @@
local tArgs = {...}
local function printUsage()
print("Usages:")
print("viewMap <(string) mapAPIPath> <(string) mapName>")
print("viewMap <(string) mapAPIPath> <(string) mapName> <(int) x-coord> <(int) y-coord> <(int) z-coord>")
print("mapName must be the complete file path")
end
local mapAPIPath = tArgs[1]
if not mapAPIPath or type(mapAPIPath) ~= "string" or not fs.exists(mapAPIPath) or fs.isDir(mapAPIPath) then
error("invalid mapAPIPath: "..tostring(mapAPIPath))
end
local mapAPI = fs.getName(mapAPIPath)
if not _G[mapAPI] then
if not os.loadAPI(mapAPIPath) then
error("could not load mapAPI: "..tostring(mapAPIPath))
end
end
mapAPI = _G[mapAPI]
local map
local mapName = tArgs[2]
if mapName and type(mapName) == "string" and fs.exists(mapName) and fs.isDir(mapName) then
map = mapAPI.new(mapName)
if not map then
error("could not load map at "..mapName)
end
else
printUsage()
end
local startX, startY, startZ
if #tArgs == 5 then
for i = 3, 5 do
local num = tArgs[i]
if not tonumber(num) or num % 1 ~= 0 then
printUsage()
return
end
end
startX = tArgs[3]
startY = tArgs[4]
startZ = tArgs[5]
end
if not startX then
map:loadAll()
--find any point on map that isn't empty
end
term.setCursorBlink(false)
term.setBackgroundColour(colours.black)
local cont = true
local width, height = term.getSize()
local currW, currH = 1, 1
term.setCursorBlink(true)
term.setTextColour(colours.red)
local redraw = true
while cont do
if redraw then
term.setBackgroundColour(colours.black)
term.clear()
term.setCursorPos(1, height)
term.clearLine()
term.write(tostring(startX + currW - 1)..","..tostring(startY)..","..tostring(startZ + currH - 1))
term.write(" -- ")
term.write(tostring(math.floor( (startX + currW - 1)/16 )))
term.write(",")
term.write(tostring(math.floor( (startY)/16 )))
term.write(",")
term.write(tostring(math.floor( (startZ + currH - 1)/16 )))
for x = 1, width do
for z = 1, height - 1 do
local value = map:get(vector.new(startX + x - 1, startY, startZ + z - 1))
if value then
term.setBackgroundColour(colours.white)
term.setCursorPos(x, z)
term.write(string.sub(value, 1, 1))
end
end
end
term.setCursorPos(currW, currH)
redraw = false
end
local event = {os.pullEvent()}
if event[1] == "key" then
local key = event[2]
if key == keys.up then
startZ = startZ - 1
elseif key == keys.down then
startZ = startZ + 1
elseif key == keys.left then
startX = startX - 1
elseif key == keys.right then
startX = startX + 1
elseif key == keys.numPadAdd then
startY = startY + 1
elseif key == keys.numPadSubtract then
startY = startY - 1
elseif key == keys.backspace then
cont = false
end
redraw = true
elseif event[1] == "mouse_click" then
if event[4] < height then
currW, currH = event[3], event[4]
term.setCursorPos(1, height)
term.clearLine()
term.write(tostring(startX + currW - 1)..","..tostring(startY)..","..tostring(startZ + currH - 1))
term.write(" -- ")
term.write(tostring(math.floor( (startX + currW - 1)/16 )))
term.write(",")
term.write(tostring(math.floor( (startY)/16 )))
term.write(",")
term.write(tostring(math.floor( (startZ + currH - 1)/16 )))
term.setCursorPos(currW, currH)
end
elseif event[1] == "term_resize" then
width, height = term.getSize()
redraw = true
end
end
term.setBackgroundColour(colours.black)
term.setCursorPos(1, 1)
term.clear()

296
netNav Normal file
View File

@ -0,0 +1,296 @@
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")
if type(mapTimeout) ~= "number" or mapTimeout < 0 then
end
error("timeout must be positive number")
end
serverMap = remoteMap.new(mapName, mapTimeout)
end
function getMap()
return serverMap
end

27
netNav_goto Normal file
View File

@ -0,0 +1,27 @@
if not netNav then
if not os.loadAPI("netNav") then
error("could not load netNav API")
end
end
for _, side in ipairs(redstone.getSides()) do
if peripheral.getType(side) == "modem" then
rednet.open(side)
end
end
netNav.setMap("main", 30)
local tArgs = {...}
if #tArgs == 3 then
for i = 1, #tArgs do
if tonumber(tArgs[i]) then
tArgs[i] = tonumber(tArgs[i])
else
error("argument "..i.." must be a valid coordinate")
end
end
print(netNav.goto(tArgs[1], tArgs[2], tArgs[3]))
else
error("arguments must be 3 or 4 numbers")
end

108
pQueue Normal file
View File

@ -0,0 +1,108 @@
local function sift_up(queue, index)
local current, parent = index, (index - (index % 2))/2
while current > 1 and queue.cmp(queue[current][2], queue[parent][2]) do
queue[current], queue[parent] = queue[parent], queue[current]
current, parent = parent, (parent - (parent % 2))/2
end
return current
end
local function sift_down(queue, index)
local current, child, size = index, 2*index, #queue
while child <= size do
if child < size and queue.cmp(queue[child + 1][2], queue[child][2]) then
child = child + 1
end
if queue.cmp(queue[child][2], queue[current][2]) then
queue[current], queue[child] = queue[child], queue[current]
current, child = child, 2*child
else
break
end
end
return current
end
local methods = {
insert = function(self, element, value)
table.insert(self, {element, value})
return sift_up(self, #self)
end,
remove = function(self, element, compFunc)
local index = self:contains(element, compFunc)
if index then
local size = #self
self[index], self[size] = self[size], self[index]
local ret = table.remove(self)
if size > 1 and index < size then
sift_down(self, index)
if index > 1 then
sift_up(self, index)
end
end
return unpack(ret)
end
end,
pop = function(self)
if self[1] then
local size = #self
self[1], self[size] = self[size], self[1]
local ret = table.remove(self)
if size > 1 then
sift_down(self, 1)
end
return unpack(ret)
end
end,
peek = function(self)
if self[1] then
return self[1][1], self[1][2]
end
end,
contains = function(self, element, compFunc)
for index, entry in ipairs(self) do
if (compFunc and compFunc(entry[1], element)) or entry[1] == element then
return index
end
end
return false
end,
isEmpty = function(self)
return #self == 0
end,
size = function(self)
return #self
end,
getValue = function(self, element, compFunc)
local index = self:contains(element, compFunc)
return (index and self[index][2]) or false
end,
setValue = function(self, element, value, compFunc)
local index = self:contains(element, compFunc)
if index then
self[index][2] = value
sift_up(self, index)
sift_down(self, index)
return true
else
return false
end
end,
}
function new(compareFunc)
local queue = {}
queue.cmp = type(compareFunc) == "function" and compareFunc or function(a, b) return a < b end
setmetatable(queue, {__index = methods})
return queue
end

291
starNav Normal file
View File

@ -0,0 +1,291 @@
if not tinyMap then
if not os.loadAPI("tinyMap") then
error("could not load tinyMap 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 SESSION_COORD_DISTANCE = math.huge
local MAIN_COORD_CLEAR = nil
local MAIN_COORD_BLOCKED = 1
local MAIN_COORD_DISTANCE = 1024
local sessionMidPoint
local sessionMaxDistance
local sessionMap
local mainMap = tinyMap.new("starNavMaps")
local function sup_norm(a, b)
return math.max(math.abs(a.x - b.x), math.abs(a.y - b.y), math.abs(a.z - b.z))
end
local function distanceFunc(a, b)
local sessionMapA, sessionMapB = sessionMap:get(a), sessionMap:get(b)
if sup_norm(a, sessionMidPoint) > sessionMaxDistance then
return SESSION_COORD_DISTANCE -- first coord is outside the search region
elseif sup_norm(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 -- one of the coords has been found to be blocked this during this session
elseif sessionMapA == SESSION_COORD_CLEAR and sessionMapA == SESSION_COORD_CLEAR then
return aStar.distance(a, b) -- both coords has been found to be clear this during this session
elseif mainMap:get(a) or mainMap:get(b) then
return MAIN_COORD_DISTANCE
end
return aStar.distance(a, b) -- we are assuming both coords are clear
end
local function getHeading()
local i = 0
while turtle.detect() do
if i == 4 then
if turtle.up() then
i = 0
else
error("help I'm trapped in a ridiculous place")
end
else
turtle.turnRight()
i = i + 1
end
end
local p1 = {gps.locate()}
if #p1 == 3 then
p1 = vector.new(unpack(p1))
else
error("no gps signal - phase 1")
end
i = 0
while not turtle.forward() do
if i > 5 then error("couldn't move to determine direction") end
i = i + 1
sleep(1)
end
local p2 = {gps.locate()}
turtle.back()
if #p2 == 3 then
p2 = vector.new(unpack(p2))
else
error("no gps signal - phase 2")
end
local dir = p2 - p1
if dir.x == 1 then
return 3
elseif dir.x == -1 then
return 1
elseif dir.z == 1 then
return 0
elseif dir.z == -1 then
return 2
else
error("could not determine direction - phase 3")
end
end
local function detect(currPos, adjPos)
local dir = adjPos - currPos
if dir.y == 1 then
return turtle.detectUp()
elseif dir.y == -1 then
return turtle.detectDown()
elseif dir.x == 1 then
position:setHeading(3)
return turtle.detect()
elseif dir.x == -1 then
position:setHeading(1)
return turtle.detect()
elseif dir.z == 1 then
position:setHeading(0)
return turtle.detect()
elseif dir.z == -1 then
position:setHeading(2)
return turtle.detect()
else
return false
end
end
local function inspect(currPos, adjPos)
local dir = adjPos - currPos
if dir.y == 1 then
return turtle.inspectUp()
elseif dir.y == -1 then
return turtle.inspectDown()
elseif dir.x == 1 then
position:setHeading(3)
return turtle.inspect()
elseif dir.x == -1 then
position:setHeading(1)
return turtle.inspect()
elseif dir.z == 1 then
position:setHeading(0)
return turtle.inspect()
elseif dir.z == -1 then
position:setHeading(2)
return turtle.inspect()
else
return false
end
end
local function updateCoord(coord, isBlocked)
if isBlocked then
sessionMap:set(pos, SESSION_COORD_BLOCKED)
mainMap:set(pos, MAIN_COORD_BLOCKED)
else
sessionMap:set(pos, SESSION_COORD_CLEAR)
mainMap:set(pos, MAIN_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
mainMap:save()
end
local function move(currPos, adjPos)
if not detect(position, adjPos) then
local dir = adjPos - currPos
if dir.y == 1 then
return position:up()
elseif dir.y == -1 then
return position:down()
else
return position:forward()
end
else
return false
end
end
function goto(x, y, z, maxDistance)
if turtle.getFuelLevel() == 0 then
return false, "ran out of fuel"
end
if not position then
local heading = getHeading()
local currPos = {gps.locate()}
if #currPos == 3 then
local x, y, z = unpack(currPos)
position = location.new(x, y, z, heading)
else
return false, "couldn't determine location"
end
end
local goal = vector.new(tonumber(x), tonumber(y), tonumber(z))
sessionMap = aStar.newMap()
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*sup_norm(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 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
sessionMap:set(movePos, SESSION_COORD_BLOCKED)
recalculate, isTurtle = true, true
end
elseif blockPresent then
scan(position) -- update map info
recalculate = true
elseif turtle.getFuelLevel() == 0 then
return false, "ran out of fuel"
else
sleep(1)
end
if recalculate then
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
movePos = table.remove(path)
if isTurtle then
sessionMap:set(movePos, nil)
end
end
end
if mainMap:get(movePos) then
mainMap:set(movePos, MAIN_COORD_CLEAR)
end
end
return true
end
function getPosition()
if position then
return position:value()
end
end

19
starNav_goto Normal file
View File

@ -0,0 +1,19 @@
if not starNav then
if not os.loadAPI("starNav") then
error("could not load starNav API")
end
end
local tArgs = {...}
if #tArgs == 3 or #tArgs == 4 then
for i = 1, #tArgs do
if tonumber(tArgs[i]) then
tArgs[i] = tonumber(tArgs[i])
else
error("argument "..i.." must be a valid coordinate")
end
end
print(starNav.goto(tArgs[1], tArgs[2], tArgs[3], tArgs[4]))
else
error("arguments must be 3 or 4 numbers")
end