Add files
This commit is contained in:
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()
|
||||
Reference in New Issue
Block a user