mirror of
https://github.com/theFox6/microexpansion.git
synced 2024-11-26 17:13:53 +01:00
403 lines
11 KiB
Lua
403 lines
11 KiB
Lua
--- Microexpansion network
|
|
-- @type network
|
|
-- @field #table controller_pos the position of the controller
|
|
-- @field #table access a table of players and their respective access levels
|
|
-- @field #number default_access_level the access level of unlisted players
|
|
-- @field #number power_load the power currently provided to the network
|
|
-- @field #number power_storage the power that can be stored for the next tick
|
|
local network = {
|
|
default_access_level = microexpansion.constants.security.access_levels.view,
|
|
power_load = 0,
|
|
power_storage = 0
|
|
}
|
|
local me = microexpansion
|
|
me.network = network
|
|
|
|
local access_level = microexpansion.constants.security.access_levels
|
|
|
|
--- construct a new network
|
|
-- @function [parent=#network] new
|
|
-- @param #table o the object to become a network or nil
|
|
-- @return #table the new network object
|
|
function network.new(o)
|
|
return setmetatable(o or {}, {__index = network})
|
|
end
|
|
|
|
--- check if a node can be connected
|
|
-- @function [parent=#network] can_connect
|
|
-- @param #table np the position of the node to be checked
|
|
-- the node itself or the name of the node
|
|
-- @return #boolean whether this node has the group me_connect
|
|
function network.can_connect(np)
|
|
local nn
|
|
if type(np)=="string" then
|
|
nn = np
|
|
else
|
|
if np.name then
|
|
nn = np.name
|
|
else
|
|
local node = me.get_node(np)
|
|
nn = node.name
|
|
end
|
|
end
|
|
return minetest.get_item_group(nn, "me_connect") > 0
|
|
end
|
|
|
|
--- get all adjacent connected nodes
|
|
-- @function [parent=#network] adjacent_connected_nodes
|
|
-- @param #table pos the position of the base node
|
|
-- @param #boolean include_ctrl whether to check for the controller
|
|
-- @return #table all nodes that have the group me_connect
|
|
function network.adjacent_connected_nodes(pos, include_ctrl)
|
|
local adjacent = {
|
|
{x=pos.x+1, y=pos.y, z=pos.z},
|
|
{x=pos.x-1, y=pos.y, z=pos.z},
|
|
{x=pos.x, y=pos.y+1, z=pos.z},
|
|
{x=pos.x, y=pos.y-1, z=pos.z},
|
|
{x=pos.x, y=pos.y, z=pos.z+1},
|
|
{x=pos.x, y=pos.y, z=pos.z-1},
|
|
}
|
|
|
|
local nodes = {}
|
|
|
|
for _,apos in pairs(adjacent) do
|
|
local napos = me.get_node(apos)
|
|
local nn = napos.name
|
|
if network.can_connect(nn) then
|
|
if include_ctrl == false then
|
|
if nn ~= "microexpansion:ctrl" then
|
|
table.insert(nodes,{pos = apos, name = nn})
|
|
end
|
|
else
|
|
table.insert(nodes,{pos = apos, name = nn})
|
|
end
|
|
end
|
|
end
|
|
|
|
return nodes
|
|
end
|
|
|
|
function network:get_access_level(player)
|
|
local name
|
|
if not player then
|
|
return self.default_access_level
|
|
elseif type(player) == "string" then
|
|
name = player
|
|
else
|
|
name = player:get_player_name()
|
|
end
|
|
if not self.access then
|
|
return self.default_access_level
|
|
end
|
|
return self.access[name] or self.default_access_level
|
|
end
|
|
|
|
function network:set_access_level(player, level)
|
|
local name
|
|
if not player then
|
|
self.default_access_level = level
|
|
elseif type(player) == "string" then
|
|
name = player
|
|
else
|
|
name = player:get_player_name()
|
|
end
|
|
if not self.access then
|
|
self.access = {}
|
|
end
|
|
self.access[name] = level
|
|
self:fallback_access()
|
|
-- autosave network data
|
|
me.autosave()
|
|
end
|
|
|
|
function network:fallback_access()
|
|
local full_access = access_level.full
|
|
if not self.access then
|
|
--something must have gone badly wrong
|
|
me.log("no network access table in fallback method","error")
|
|
self.access = {}
|
|
end
|
|
for _,l in pairs(self.access) do
|
|
if l == full_access then
|
|
return
|
|
end
|
|
end
|
|
local meta = minetest.get_meta(self.controller_pos)
|
|
local owner = meta:get_string("owner")
|
|
if owner == "" then
|
|
me.log("ME Network Controller without owner at: " .. vector.to_string(self.controller_pos), "warning")
|
|
else
|
|
self.access[owner] = full_access
|
|
end
|
|
end
|
|
|
|
function network:list_access()
|
|
if not self.access then
|
|
self.access = {}
|
|
end
|
|
return self.access
|
|
end
|
|
|
|
--- provide power to the network
|
|
-- @function [parent=#network] provide
|
|
-- @param #number power the amount of power provided
|
|
function network:provide(power)
|
|
self.power_load = self.power_load + power
|
|
end
|
|
|
|
--- demand power from the network
|
|
-- @function [parent=#network] demand
|
|
-- @param #number power the amount of power demanded
|
|
-- @return #boolean whether the power was provided
|
|
function network:demand(power)
|
|
if self.power_load - power < 0 then
|
|
return false
|
|
end
|
|
self.power_load = self.power_load - power
|
|
return true
|
|
end
|
|
|
|
--- add power capacity to the network
|
|
-- @function [parent=#network] add_power_capacity
|
|
-- @param #number power the amount of power that can be stored
|
|
function network:add_power_capacity(power)
|
|
self.power_storage = self.power_storage + power
|
|
end
|
|
|
|
--- add power capacity to the network
|
|
-- @function [parent=#network] add_power_capacity
|
|
-- @param #number power the amount of power that can't be stored anymore
|
|
function network:remove_power_capacity(power)
|
|
self.power_storage = self.power_storage - power
|
|
if self.power_storage < 0 then
|
|
microexpansion.log("power storage of network "..self.." dropped below zero","warning")
|
|
end
|
|
end
|
|
|
|
--- remove overload
|
|
-- to be called by the controller every turn
|
|
-- @function [parent=#network] remove_overload
|
|
function network:remove_overload()
|
|
self.power_load = math.min(self.power_load, self.power_storage)
|
|
end
|
|
|
|
--- get a drives item capacity
|
|
-- @function get_drive_capacity
|
|
-- @param #table pos the position of the drive
|
|
-- @return #number the number of items that can be stored in the drive
|
|
local function get_drive_capacity(pos)
|
|
local cap = 0
|
|
local meta = minetest.get_meta(pos)
|
|
local inv = meta:get_inventory()
|
|
for i = 1, inv:get_size("main") do
|
|
cap = cap + me.get_cell_size(inv:get_stack("main", i):get_name())
|
|
end
|
|
return cap
|
|
end
|
|
|
|
--- get the item capacity of a network
|
|
-- @function [parent=#network] get_item_capacity
|
|
-- @return #number the total number of items that can be stored in the network
|
|
function network:get_item_capacity()
|
|
local cap = 0
|
|
for npos in me.connected_nodes(self.controller_pos) do
|
|
if me.get_node(npos).name == "microexpansion:drive" then
|
|
cap = cap + get_drive_capacity(npos)
|
|
end
|
|
end
|
|
self.capacity_cache = cap
|
|
return cap
|
|
end
|
|
|
|
local function remove_slots(inv,ln,target,csize)
|
|
for i = target, csize do
|
|
local s = inv:get_stack(ln,i)
|
|
if not s:is_empty() then
|
|
inv:set_stack(ln, i, "")
|
|
me.insert_item(s, inv, ln)
|
|
end
|
|
end
|
|
--perhaps allow list removal
|
|
if target < 0 then
|
|
target = 1
|
|
end
|
|
inv:set_size(ln, target)
|
|
end
|
|
|
|
function network:set_storage_space(count,listname)
|
|
local c = count or 1
|
|
local ln = listname or "main"
|
|
local inv = self:get_inventory()
|
|
local csize = inv:get_size(ln)
|
|
local space = 0
|
|
local inside = 0
|
|
local contents = inv:get_list(ln) or {}
|
|
for _,s in pairs(contents) do
|
|
if s:is_empty() then
|
|
space = space + 1
|
|
else
|
|
inside = inside + s:get_count()
|
|
end
|
|
end
|
|
local cap = self:get_item_capacity()
|
|
--the capacity is allocated outside of the condition because it updates the cached capacity
|
|
if count == true then
|
|
if inside < cap then
|
|
c = 1
|
|
else
|
|
c = 0
|
|
end
|
|
end
|
|
local needed = c - space
|
|
if needed > 0 then
|
|
needed = needed + csize
|
|
inv:set_size(ln, needed)
|
|
elseif needed < 0 then
|
|
needed = needed + csize
|
|
remove_slots(inv,ln,needed,csize)
|
|
end
|
|
-- autosave network data
|
|
me.autosave()
|
|
end
|
|
|
|
function network:update()
|
|
self:set_storage_space(true)
|
|
end
|
|
|
|
function network:get_inventory_name()
|
|
local cp = self.controller_pos
|
|
assert(cp, "trying to get inventory name of a network without controller")
|
|
return "microexpansion_storage_"..cp.x.."_"..cp.y.."_"..cp.z
|
|
end
|
|
|
|
function network:get_inventory_space(inv, list)
|
|
local inv = inv or self:get_inventory()
|
|
local listname = list or "main"
|
|
local max_slots = inv:get_size(listname)
|
|
local max_items = self.capacity_cache
|
|
|
|
local slots, items = 0, 0
|
|
-- Get amount of items in drive
|
|
for i = 1, max_slots do
|
|
local dstack = inv:get_stack(listname, i)
|
|
if dstack:get_name() ~= "" then
|
|
slots = slots + 1
|
|
local num = dstack:get_count()
|
|
if num == 0 then num = 1 end
|
|
items = items + num
|
|
end
|
|
end
|
|
return math.max(max_items-items,0)
|
|
end
|
|
|
|
local function create_inventory(net)
|
|
local invname = net:get_inventory_name()
|
|
net.inv = minetest.create_detached_inventory(invname, {
|
|
allow_put = function(inv, listname, index, stack, player)
|
|
if net:get_access_level(player) < access_level.interact then
|
|
return 0
|
|
end
|
|
local inside_stack = inv:get_stack(listname, index)
|
|
local stack_name = stack:get_name()
|
|
if minetest.get_item_group(stack_name, "microexpansion_cell") > 0 then
|
|
return 0
|
|
end
|
|
-- improve performance by skipping unnessecary calls
|
|
if inside_stack:get_name() ~= stack_name or inside_stack:get_count() >= inside_stack:get_stack_max() then
|
|
if inv:get_stack(listname, index+1):get_name() ~= "" then
|
|
return stack:get_count()
|
|
end
|
|
end
|
|
local max_slots = inv:get_size(listname)
|
|
local max_items = net.capacity_cache
|
|
|
|
local slots, items = 0, 0
|
|
-- Get amount of items in drive
|
|
for i = 1, max_slots do
|
|
local dstack = inv:get_stack(listname, i)
|
|
if dstack:get_name() ~= "" then
|
|
slots = slots + 1
|
|
local num = dstack:get_count()
|
|
if num == 0 then num = 1 end
|
|
items = items + num
|
|
end
|
|
end
|
|
return math.max(math.min(stack:get_count(),max_items-items),0)
|
|
end,
|
|
on_put = function(inv, listname, _, stack)
|
|
inv:remove_item(listname, stack)
|
|
me.insert_item(stack, inv, listname)
|
|
net:set_storage_space(true)
|
|
end,
|
|
allow_take = function(_, _, _, stack, player)
|
|
if net:get_access_level(player) < access_level.interact then
|
|
return 0
|
|
end
|
|
return math.min(stack:get_count(),stack:get_stack_max())
|
|
end,
|
|
on_take = function()
|
|
--update the inventory size in the next step as it is not allowed in on_take
|
|
minetest.after(0, function() net:set_storage_space(true) end)
|
|
end
|
|
})
|
|
end
|
|
|
|
function network:get_inventory()
|
|
if not self.inv then
|
|
create_inventory(self)
|
|
assert(self.inv,"no inventory created")
|
|
end
|
|
return self.inv
|
|
end
|
|
|
|
function network:load_inventory(lists)
|
|
local inv = self:get_inventory()
|
|
for listname,c in pairs(lists) do
|
|
inv:set_size(listname, #c)
|
|
for i,s in pairs(c) do
|
|
inv:set_stack(listname,i,s)
|
|
end
|
|
end
|
|
end
|
|
|
|
function network:save_inventory()
|
|
local contents = {}
|
|
local lists = self.inv:get_lists()
|
|
for listname,c in pairs(lists or {}) do
|
|
local ct = {}
|
|
contents[listname] = ct
|
|
for i,stack in pairs(c) do
|
|
ct[i] = stack:to_string()
|
|
end
|
|
end
|
|
return contents
|
|
end
|
|
|
|
function network:load()
|
|
if self.strinv then
|
|
self:load_inventory(self.strinv)
|
|
end
|
|
end
|
|
|
|
function network:serialize()
|
|
local sert = {}
|
|
for i,v in pairs(self) do
|
|
if i == "inv" then
|
|
sert.strinv = self:save_inventory()
|
|
elseif i == "strinv" then
|
|
if not sert.strinv then
|
|
sert[i] = v
|
|
end
|
|
else
|
|
sert[i] = v
|
|
end
|
|
end
|
|
return sert
|
|
end
|
|
|
|
function network:destruct()
|
|
minetest.remove_detached_inventory(self:get_inventory_name())
|
|
self.controller_pos = nil
|
|
self.inv = nil
|
|
end
|