Traitor/mods/vents/network.lua

289 lines
6.9 KiB
Lua

--[[
Hyperloop Mod
=============
Copyright (C) 2017-2019 Joachim Stolberg
LGPLv2.1+
See LICENSE.txt for more information
Station and elevator network management
]]--
-- for lazy programmers
local S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local P = minetest.string_to_pos
local M = minetest.get_meta
-- Convert to list and add pos based on key string
local function table_to_list(table)
local lRes = {}
for key,item in pairs(table) do
item.pos = P(key)
lRes[#lRes+1] = item
end
return lRes
end
local function distance(pos1, pos2)
return math.floor(math.abs(pos1.x - pos2.x) +
math.abs(pos1.y - pos2.y) + math.abs(pos1.z - pos2.z))
end
-- Add the distance to pos to each list item
local function add_distance_to_list(lStations, pos)
for _,item in ipairs(lStations) do
item.distance = distance(item.pos, pos)
end
return lStations
end
-- Add the index to each list item
local function add_index_to_list(lStations)
-- walk through the list of floors for the next connection
local get_next = function(key, idx)
for _,floor in ipairs(lStations) do
if floor.conn[6] == key then -- upward match?
floor.idx = idx
return S(floor.pos) -- return floor key
end
end
end
local key = nil
for idx = 1,#lStations do
key = get_next(key, idx)
end
return lStations
end
-- Return a table with all stations, the given station (as 'sKey') is connected with
-- tRes is used for the resulting table (recursive call)
local function get_stations(tStations, sKey, tRes)
if not tStations[sKey] or not tStations[sKey].conn then
return {}
end
for dir,dest in pairs(tStations[sKey].conn) do
-- Not already visited?
if not tRes[dest] then
-- Known station?
if tStations[dest] then
tStations[dest].name = tStations[dest].name or ""
tRes[dest] = tStations[dest]
get_stations(tStations, dest, tRes)
end
end
end
return tRes
end
-- Return a list with sorted elevators, beginning with the top car
-- with no shaft upwards
local function sort_based_on_level(tStations)
local lStations = table_to_list(table.copy(tStations))
-- to be able to sort the list, an index has to be added
lStations = add_index_to_list(lStations)
table.sort(lStations, function(a,b) return (a.idx or 9999) < (b.idx or 9999) end)
return lStations
end
-- Return a list with sorted stations
local function sort_based_on_distance(tStations, pos)
local lStations = table_to_list(table.copy(tStations))
-- to be able to sort the list, the distance to pos has to be added
lStations = add_distance_to_list(lStations, pos)
table.sort(lStations, function(a,b) return a.distance < b.distance end)
return lStations
end
-- Return a list with sorted stations
local function sort_based_on_name(tStations, pos)
local lStations = table_to_list(table.copy(tStations))
-- Add distance
lStations = add_distance_to_list(lStations, pos)
table.sort(lStations, function(a,b) return a.name < b.name end)
return lStations
end
--
-- Class Network
--
--[[
tStations["(x,y,z)"] = {
["conn"] = {
dir = "(200,0,20)",
},
}
change_counter = n,
]]--
local Network = {}
vents.Network = Network
function Network:new()
local o = {
tStations = {},
change_counter = 0,
}
setmetatable(o, self)
self.__index = self
return o
end
-- Set an elevator or station entry.
-- tAttr is a table with additional attributes to be stored.
function Network:set(pos, name, tAttr)
if pos then
local sKey = S(pos)
if not self.tStations[sKey] then
self.tStations[sKey] = {
conn = {},
}
end
self.tStations[sKey].name = name or ""
for k,v in pairs(tAttr) do
self.tStations[sKey][k] = v
end
self.change_counter = self.change_counter + 1
end
end
-- Update an elevator or station entry.
-- tAttr is a table with additional attributes to be stored.
function Network:update(pos, tAttr)
if pos then
local sKey = S(pos)
if self.tStations[sKey] then
for k,v in pairs(tAttr) do
if v == "nil" then
self.tStations[sKey][k] = nil
else
self.tStations[sKey][k] = v
end
end
self.change_counter = self.change_counter + 1
end
end
end
function Network:get(pos)
return pos and self.tStations[S(pos)]
end
-- Delete an elevator or station entry.
function Network:delete(pos)
if pos then
self.tStations[S(pos)] = nil
self.change_counter = self.change_counter + 1
end
end
function Network:changed(counter)
return self.change_counter > counter, self.change_counter
end
-- Update the connection data base. The output dir information is needed
-- to be able to delete a connection, if necessary.
-- Returns true, if data base is changed.
function Network:update_connections(pos, out_dir, conn_pos)
local sKey = S(pos)
local res = false
if not self.tStations[sKey] then
self.tStations[sKey] = {}
res = true
end
if not self.tStations[sKey].conn then
self.tStations[sKey].conn = {}
res = true
end
conn_pos = S(conn_pos)
if self.tStations[sKey].conn[out_dir] ~= conn_pos then
self.tStations[sKey].conn[out_dir] = conn_pos
res = true
end
if res then
self.change_counter = self.change_counter + 1
end
return res
end
-- Return the nearest station position
function Network:get_next_station(pos)
local min_dist = 999999
local min_key = nil
local dist
for key,item in pairs(self.tStations) do
if not item.junction then
dist = distance(pos, P(key))
if dist < min_dist then
min_dist = dist
min_key = key
end
end
end
return P(min_key)
end
-- Return a sorted list of stations
-- Param pos: player pos
-- Param station_pos: next station pos or nil.
-- Used to generate list with connected stations only
-- Param sorted: either "dist" or "level"
function Network:station_list(pos, station_pos, sorted)
local tStations, lStations
if station_pos then
local tRes = {}
tStations = get_stations(self.tStations, S(station_pos), tRes) -- reduced
else
tStations = self.tStations -- all stations
end
if sorted == "dist" then
lStations = sort_based_on_distance(tStations, pos)
elseif sorted == "level" then
lStations = sort_based_on_level(tStations)
else
-- delete own station from list
tStations[S(station_pos)] = nil
lStations = sort_based_on_name(tStations, pos)
end
return lStations
end
-- Check the complete table by means of the provided callback bool = func(pos)
function Network:filter(callback)
local lKeys = {}
for key,_ in pairs(self.tStations) do
lKeys[#lKeys+1] = key
end
for _,key in ipairs(lKeys) do
if not callback(P(key)) then
self.tStations[key] = nil
end
end
end
function Network:deserialize(data)
if data ~= "" then
data = minetest.deserialize(data)
self.tStations = data.tStations
self.change_counter = data.change_counter
end
end
function Network:serialize()
return minetest.serialize(self)
end
-- Return a pos/item table with all network nodes, the node at pos is connected with
function Network:get_node_table(pos)
local tRes = {}
local key = S(pos)
get_stations(self.tStations, key, tRes)
tRes[key] = nil
return tRes
end