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