2024-09-08 22:46:41 +02:00
-- IndustrialTest
-- Copyright (C) 2024 mrkubax10
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
-- You should have received a copy of the GNU General Public License
-- along with this program. If not, see <http://www.gnu.org/licenses/>.
local function addNodeToNetwork ( pos , networkMasterPos )
local meta = minetest.get_meta ( pos )
local networks = { }
if meta : contains ( " industrialtest.networks " ) then
networks = minetest.deserialize ( meta : get_string ( " industrialtest.networks " ) )
end
for _ , network in ipairs ( networks ) do
if network.x == networkMasterPos.x and network.y == networkMasterPos.y and network.z == networkMasterPos.z then
return
end
end
table.insert ( networks , networkMasterPos )
meta : set_string ( " industrialtest.networks " , minetest.serialize ( networks ) )
end
local function clampFlow ( pos , flow )
local def = minetest.registered_nodes [ minetest.get_node ( pos ) . name ]
local newFlow
if def.groups and def.groups . _industrialtest_cable then
newFlow = def._industrialtest_cableFlow
else
local meta = minetest.get_meta ( pos )
newFlow = meta : get_int ( " industrialtest.powerFlow " )
end
return math.min ( flow , newFlow )
end
-- \brief Transfers power from source node to it's network, if sides is set then power will be only transfered to network connected to that sides
-- \param pos Vector with position of source node
-- \param (optional) sides table with Vectors
-- \param (optional) flowOverride number
-- \returns two values: true if any neighbouring node has room for more power, false otherwise
-- true if any power was transferred, false otherwise
function industrialtest . api . powerFlow ( pos , sides , flowOverride )
local meta = minetest.get_meta ( pos )
-- if machine doesn't have network map then it's not capable of transferring power
local network = industrialtest.api . getNetwork ( meta )
if not network or # network == 0 then
return false , false
end
local endpointCount = 0
for _ , endpoint in ipairs ( network ) do
local endpointMeta = minetest.get_meta ( endpoint.position )
if not industrialtest.api . isFullyCharged ( endpointMeta ) and ( not sides or sides [ endpoint.sourceSide ] ) then
endpointCount = endpointCount + 1
end
end
if endpointCount == 0 then
return false , false
end
local powerDistribution = math.floor ( ( flowOverride and flowOverride or meta : get_int ( " industrialtest.powerFlow " ) ) / endpointCount )
local transferred = false
local roomAvailable = false
for _ , endpoint in ipairs ( network ) do
if not sides or sides [ endpoint.sourceSide ] then
local endpointMeta = minetest.get_meta ( endpoint.position )
if powerDistribution <= endpoint.flow then
local transferredPower = industrialtest.api . transferPower ( meta , endpointMeta , powerDistribution )
if transferredPower > 0 then
transferred = true
end
local def = minetest.registered_nodes [ minetest.get_node ( endpoint.position ) . name ]
2025-01-08 22:45:12 +01:00
if def and def._industrialtest_self then
def._industrialtest_self : updateFormspec ( endpoint.position )
if def._industrialtest_self . onPowerFlow and transferredPower > 0 then
def._industrialtest_self : onPowerFlow ( endpoint.position , industrialtest.api . getOppositeSide ( endpoint.side ) , transferredPower )
2024-09-08 22:46:41 +02:00
end
2025-01-08 22:45:12 +01:00
def._industrialtest_self : triggerIfNeeded ( endpoint.position )
else
-- Support for bare definitions that don't use industrialtest pseudo-OOP
minetest.get_node_timer ( endpoint.position ) : start ( industrialtest.updateDelay )
2024-09-08 22:46:41 +02:00
end
if not industrialtest.api . isFullyCharged ( endpointMeta ) then
roomAvailable = true
end
else
minetest.remove_node ( endpoint.position )
industrialtest.internal . explode ( endpoint.position , 2 )
end
end
end
return roomAvailable , transferred
end
-- \brief Creates network map starting from node at pos, optionally omitting node at omit
-- \param pos vector
-- \param (optional) addCables bool
-- \param (optional) omit Vector
-- \returns table with network map
function industrialtest . api . createNetworkMap ( pos , addCables , omit )
local workers = { }
local map = { }
local connections = industrialtest.api . getConnections ( pos , " i " )
if # connections == 0 then
return map
end
local sides = {
[ " -1,0,0 " ] = 1 ,
[ " 1,0,0 " ] = 2 ,
[ " 0,-1,0 " ] = 3 ,
[ " 0,1,0 " ] = 4 ,
[ " 0,0,-1 " ] = 5 ,
[ " 0,0,1 " ] = 6
}
local serializedSourcePos = pos.x .. " , " .. pos.y .. " , " .. pos.z
local visitedNodes = { [ serializedSourcePos ] = true }
for _ , conn in ipairs ( connections ) do
if not omit or conn.x ~= omit.x or conn.y ~= omit.y or conn.z ~= omit.z then
visitedNodes [ conn.x .. " , " .. conn.y .. " , " .. conn.z ] = true
addNodeToNetwork ( conn , pos )
local sideVector = vector.subtract ( conn , pos )
local serializedSideVector = sideVector.x .. " , " .. sideVector.y .. " , " .. sideVector.z
local def = minetest.registered_nodes [ minetest.get_node ( conn ) . name ]
if def.groups . _industrialtest_cable then
table.insert ( workers , {
position = conn ,
targetPosition = conn ,
distance = 1 ,
flow = def._industrialtest_cableFlow ,
targetFlow = 0 ,
sourceSide = industrialtest.api . normalizeSide ( pos , sides [ serializedSideVector ] )
} )
if addCables then
table.insert ( map , {
position = conn ,
distance = 0
} )
end
else
local meta = minetest.get_meta ( conn )
table.insert ( map , {
position = conn ,
distance = 0 ,
flow = meta : get_int ( " industrialtest.powerFlow " ) ,
side = sides [ serializedSideVector ] ,
sourceSide = industrialtest.api . normalizeSide ( pos , sides [ serializedSideVector ] )
} )
end
end
end
while # workers > 0 do
for i = 1 , # workers do
local worker = workers [ i ]
connections = industrialtest.api . getConnections ( worker.position , " i " )
if # connections == 0 then
table.remove ( workers , i )
break
else
local directionAssigned = false
local foundNewNode = false
for _ , conn in ipairs ( connections ) do
if not omit or conn.x ~= omit.x or conn.y ~= omit.y or conn.z ~= omit.z then
local serializedPos = conn.x .. " , " .. conn.y .. " , " .. conn.z
if not visitedNodes [ serializedPos ] then
local def = minetest.registered_nodes [ minetest.get_node ( conn ) . name ]
visitedNodes [ serializedPos ] = true
foundNewNode = true
addNodeToNetwork ( conn , pos )
if def.groups . _industrialtest_cable then
if directionAssigned then
table.insert ( workers , {
position = conn ,
targetPosition = conn ,
distance = worker.distance + 1 ,
flow = clampFlow ( conn , worker.flow ) ,
targetFlow = 0 ,
sourceSide = worker.sourceSide
} )
else
worker.targetPosition = conn
worker.distance = worker.distance + 1
worker.targetFlow = clampFlow ( conn , worker.flow )
directionAssigned = true
end
if addCables then
table.insert ( map , {
position = conn ,
distance = worker.distance + 1 ,
} )
end
else
local sideVector = vector.subtract ( conn , worker.position )
table.insert ( map , {
position = conn ,
distance = worker.distance ,
flow = clampFlow ( conn , worker.flow ) ,
side = sides [ sideVector.x .. " , " .. sideVector.y .. " , " .. sideVector.z ] ,
sourceSide = worker.sourceSide
} )
if # connections == 1 then
foundNewNode = false
break
end
end
end
end
end
if not foundNewNode then
table.remove ( workers , i )
break
end
worker.position = worker.targetPosition
worker.flow = worker.targetFlow
end
end
end
return map
end
function industrialtest . api . removeNodeFromNetwork ( pos , nodePos )
local meta = minetest.get_meta ( pos )
if not meta : contains ( " industrialtest.network " ) then
return
end
local network = minetest.deserialize ( meta : get_string ( " industrialtest.network " ) )
local removed = false
for key , node in ipairs ( network ) do
if node.position . x == nodePos.x and node.position . y == nodePos.y and node.position . z == nodePos.z then
table.remove ( network , key )
removed = true
break
end
end
if removed then
meta : set_string ( " industrialtest.network " , minetest.serialize ( network ) )
end
end
-- \brief Creates network map and writes it to node metadata at pos, optionally omitting node at omit
-- \param pos Vector
-- \param (optional) omit Vector
-- \returns nil
function industrialtest . api . createNetworkMapForNode ( pos , omit )
local meta = minetest.get_meta ( pos )
local network = industrialtest.api . createNetworkMap ( pos , false , omit )
meta : set_string ( " industrialtest.network " , minetest.serialize ( network ) )
end
-- \brief Returns true if meta contains network map, false otherwise
-- \param meta MetaDataRef
-- \returns bool
function industrialtest . api . isNetworkMaster ( meta )
return meta : contains ( " industrialtest.network " )
end
-- \brief Returns network table if node containing meta belongs to any networks, false otherwise
-- \param meta MetaDataRef
-- \returns bool or table
function industrialtest . api . isAttachedToNetwork ( meta )
if not meta : contains ( " industrialtest.networks " ) then
return false
end
local networks = minetest.deserialize ( meta : get_string ( " industrialtest.networks " ) )
if # networks == 0 then
return false
end
return networks
end
-- \brief Returns network master network from it's meta, if meta doesn't contain network map then function returns false
-- \param meta MetaDataRef
-- \returns table or bool
function industrialtest . api . getNetwork ( meta )
if not meta : contains ( " industrialtest.network " ) then
return false
end
return minetest.deserialize ( meta : get_string ( " industrialtest.network " ) )
end
-- \brief Returns connections of node with power storage. If direction is "i" only input connections will be returned, if direction is "o" only output connections
-- will be returned, if it's not provided all connections will be returned.
-- \param pos Vector
-- \param (optional) direction string
-- \returns table
function industrialtest . api . getConnections ( pos , direction )
local result = { }
local neighbourPositions = {
vector.offset ( pos , - 1 , 0 , 0 ) ,
vector.offset ( pos , 1 , 0 , 0 ) ,
vector.offset ( pos , 0 , - 1 , 0 ) ,
vector.offset ( pos , 0 , 1 , 0 ) ,
vector.offset ( pos , 0 , 0 , - 1 ) ,
vector.offset ( pos , 0 , 0 , 1 )
}
local sourceMeta = minetest.get_meta ( pos )
local sourceDef = minetest.registered_nodes [ minetest.get_node ( pos ) . name ]
local directionOutput = ( not direction or direction == " o " )
local directionInput = ( not direction or direction == " i " )
for key , conn in ipairs ( neighbourPositions ) do
local meta = minetest.get_meta ( conn )
local def = minetest.registered_nodes [ minetest.get_node ( conn ) . name ]
local normalizedKey = industrialtest.api . normalizeSide ( pos , key )
local powerOutput = ( sourceDef.groups . _industrialtest_cable or industrialtest.api . isPowerOutput ( sourceMeta , normalizedKey ) )
local powerInput = ( sourceDef.groups . _industrialtest_cable or industrialtest.api . isPowerInput ( sourceMeta , normalizedKey ) )
if def.groups . _industrialtest_cable or industrialtest.api . hasPowerStorage ( meta ) then
local side = industrialtest.api . normalizeSide ( conn , industrialtest.api . getOppositeSide ( normalizedKey ) )
if ( powerOutput and directionInput and ( def.groups . _industrialtest_cable or industrialtest.api . isPowerInput ( meta , side ) ) ) or ( ( def.groups . _industrialtest_cable or industrialtest.api . isPowerOutput ( meta , side ) ) and powerInput and directionOutput ) then
table.insert ( result , conn )
end
end
end
return result
end