Add files
This commit is contained in:
parent
d63007f101
commit
c0673612b1
166
aStar
Normal file
166
aStar
Normal 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
131
location
Normal 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
209
maps/compactMap
Normal 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
142
maps/map
Normal 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
185
maps/remoteMap_client
Normal 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
163
maps/remoteMap_server
Normal 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
117
maps/remoteMap_viewer
Normal 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
222
maps/tinyMap
Normal 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
128
maps/viewMap
Normal 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
296
netNav
Normal 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
27
netNav_goto
Normal 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
108
pQueue
Normal 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
291
starNav
Normal 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
19
starNav_goto
Normal 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
|
||||
Loading…
x
Reference in New Issue
Block a user