2021-10-08 14:13:06 +02:00
/*************************************************************************/
/* multiplayer_spawner.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
# include "multiplayer_spawner.h"
# include "core/io/marshalls.h"
# include "core/multiplayer/multiplayer_api.h"
# include "scene/main/window.h"
# include "scene/scene_string_names.h"
2022-05-23 02:24:14 +02:00
# ifdef TOOLS_ENABLED
/* This is editor only */
bool MultiplayerSpawner : : _set ( const StringName & p_name , const Variant & p_value ) {
if ( p_name = = " _spawnable_scene_count " ) {
spawnable_scenes . resize ( p_value ) ;
notify_property_list_changed ( ) ;
return true ;
} else {
String ns = p_name ;
if ( ns . begins_with ( " scenes/ " ) ) {
uint32_t index = ns . get_slicec ( ' / ' , 1 ) . to_int ( ) ;
ERR_FAIL_UNSIGNED_INDEX_V ( index , spawnable_scenes . size ( ) , false ) ;
spawnable_scenes [ index ] . path = p_value ;
return true ;
}
}
return false ;
}
bool MultiplayerSpawner : : _get ( const StringName & p_name , Variant & r_ret ) const {
if ( p_name = = " _spawnable_scene_count " ) {
r_ret = spawnable_scenes . size ( ) ;
return true ;
} else {
String ns = p_name ;
if ( ns . begins_with ( " scenes/ " ) ) {
uint32_t index = ns . get_slicec ( ' / ' , 1 ) . to_int ( ) ;
ERR_FAIL_UNSIGNED_INDEX_V ( index , spawnable_scenes . size ( ) , false ) ;
r_ret = spawnable_scenes [ index ] . path ;
return true ;
}
}
return false ;
}
void MultiplayerSpawner : : _get_property_list ( List < PropertyInfo > * p_list ) const {
2022-07-09 20:45:30 +02:00
p_list - > push_back ( PropertyInfo ( Variant : : INT , " _spawnable_scene_count " , PROPERTY_HINT_NONE , " " , PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_ARRAY , " Auto Spawn List,scenes/ " ) ) ;
2022-05-23 02:24:14 +02:00
List < String > exts ;
ResourceLoader : : get_recognized_extensions_for_type ( " PackedScene " , & exts ) ;
String ext_hint ;
for ( const String & E : exts ) {
if ( ! ext_hint . is_empty ( ) ) {
ext_hint + = " , " ;
}
ext_hint + = " *. " + E ;
}
for ( uint32_t i = 0 ; i < spawnable_scenes . size ( ) ; i + + ) {
p_list - > push_back ( PropertyInfo ( Variant : : STRING , " scenes/ " + itos ( i ) , PROPERTY_HINT_FILE , ext_hint , PROPERTY_USAGE_EDITOR ) ) ;
}
}
# endif
void MultiplayerSpawner : : add_spawnable_scene ( const String & p_path ) {
SpawnableScene sc ;
sc . path = p_path ;
if ( Engine : : get_singleton ( ) - > is_editor_hint ( ) ) {
ERR_FAIL_COND ( ! FileAccess : : exists ( p_path ) ) ;
} else {
sc . cache = ResourceLoader : : load ( p_path ) ;
ERR_FAIL_COND_MSG ( sc . cache . is_null ( ) , " Invalid spawnable scene: " + p_path ) ;
}
spawnable_scenes . push_back ( sc ) ;
}
int MultiplayerSpawner : : get_spawnable_scene_count ( ) const {
return spawnable_scenes . size ( ) ;
}
String MultiplayerSpawner : : get_spawnable_scene ( int p_idx ) const {
return spawnable_scenes [ p_idx ] . path ;
}
void MultiplayerSpawner : : clear_spawnable_scenes ( ) {
spawnable_scenes . clear ( ) ;
}
Vector < String > MultiplayerSpawner : : _get_spawnable_scenes ( ) const {
Vector < String > ss ;
ss . resize ( spawnable_scenes . size ( ) ) ;
for ( int i = 0 ; i < ss . size ( ) ; i + + ) {
ss . write [ i ] = spawnable_scenes [ i ] . path ;
}
return ss ;
}
void MultiplayerSpawner : : _set_spawnable_scenes ( const Vector < String > & p_scenes ) {
clear_spawnable_scenes ( ) ;
for ( int i = 0 ; i < p_scenes . size ( ) ; i + + ) {
add_spawnable_scene ( p_scenes [ i ] ) ;
}
}
2021-10-08 14:13:06 +02:00
void MultiplayerSpawner : : _bind_methods ( ) {
2022-05-23 02:24:14 +02:00
ClassDB : : bind_method ( D_METHOD ( " add_spawnable_scene " , " path " ) , & MultiplayerSpawner : : add_spawnable_scene ) ;
ClassDB : : bind_method ( D_METHOD ( " get_spawnable_scene_count " ) , & MultiplayerSpawner : : get_spawnable_scene_count ) ;
ClassDB : : bind_method ( D_METHOD ( " get_spawnable_scene " , " path " ) , & MultiplayerSpawner : : get_spawnable_scene ) ;
ClassDB : : bind_method ( D_METHOD ( " clear_spawnable_scenes " ) , & MultiplayerSpawner : : clear_spawnable_scenes ) ;
ClassDB : : bind_method ( D_METHOD ( " _get_spawnable_scenes " ) , & MultiplayerSpawner : : _get_spawnable_scenes ) ;
ClassDB : : bind_method ( D_METHOD ( " _set_spawnable_scenes " , " scenes " ) , & MultiplayerSpawner : : _set_spawnable_scenes ) ;
2021-10-08 14:13:06 +02:00
2022-05-23 02:24:14 +02:00
ADD_PROPERTY ( PropertyInfo ( Variant : : PACKED_STRING_ARRAY , " _spawnable_scenes " , PROPERTY_HINT_NONE , " " , ( PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL ) ) , " _set_spawnable_scenes " , " _get_spawnable_scenes " ) ;
ClassDB : : bind_method ( D_METHOD ( " spawn " , " data " ) , & MultiplayerSpawner : : spawn , DEFVAL ( Variant ( ) ) ) ;
2021-10-08 14:13:06 +02:00
ClassDB : : bind_method ( D_METHOD ( " get_spawn_path " ) , & MultiplayerSpawner : : get_spawn_path ) ;
ClassDB : : bind_method ( D_METHOD ( " set_spawn_path " , " path " ) , & MultiplayerSpawner : : set_spawn_path ) ;
ADD_PROPERTY ( PropertyInfo ( Variant : : NODE_PATH , " spawn_path " , PROPERTY_HINT_NONE , " " ) , " set_spawn_path " , " get_spawn_path " ) ;
ClassDB : : bind_method ( D_METHOD ( " get_spawn_limit " ) , & MultiplayerSpawner : : get_spawn_limit ) ;
ClassDB : : bind_method ( D_METHOD ( " set_spawn_limit " , " limit " ) , & MultiplayerSpawner : : set_spawn_limit ) ;
ADD_PROPERTY ( PropertyInfo ( Variant : : INT , " spawn_limit " , PROPERTY_HINT_RANGE , " 0,1024,1,or_greater " ) , " set_spawn_limit " , " get_spawn_limit " ) ;
GDVIRTUAL_BIND ( _spawn_custom , " data " ) ;
ADD_SIGNAL ( MethodInfo ( " despawned " , PropertyInfo ( Variant : : INT , " scene_id " ) , PropertyInfo ( Variant : : OBJECT , " node " , PROPERTY_HINT_RESOURCE_TYPE , " Node " ) ) ) ;
ADD_SIGNAL ( MethodInfo ( " spawned " , PropertyInfo ( Variant : : INT , " scene_id " ) , PropertyInfo ( Variant : : OBJECT , " node " , PROPERTY_HINT_RESOURCE_TYPE , " Node " ) ) ) ;
}
void MultiplayerSpawner : : _update_spawn_node ( ) {
# ifdef TOOLS_ENABLED
if ( Engine : : get_singleton ( ) - > is_editor_hint ( ) ) {
return ;
}
# endif
if ( spawn_node . is_valid ( ) ) {
Node * node = Object : : cast_to < Node > ( ObjectDB : : get_instance ( spawn_node ) ) ;
if ( node & & node - > is_connected ( " child_entered_tree " , callable_mp ( this , & MultiplayerSpawner : : _node_added ) ) ) {
node - > disconnect ( " child_entered_tree " , callable_mp ( this , & MultiplayerSpawner : : _node_added ) ) ;
}
}
Node * node = spawn_path . is_empty ( ) & & is_inside_tree ( ) ? nullptr : get_node_or_null ( spawn_path ) ;
if ( node ) {
spawn_node = node - > get_instance_id ( ) ;
2022-07-09 20:45:30 +02:00
if ( get_spawnable_scene_count ( ) & & ! GDVIRTUAL_IS_OVERRIDDEN ( _spawn_custom ) ) {
2021-10-08 14:13:06 +02:00
node - > connect ( " child_entered_tree " , callable_mp ( this , & MultiplayerSpawner : : _node_added ) ) ;
}
} else {
spawn_node = ObjectID ( ) ;
}
}
void MultiplayerSpawner : : _notification ( int p_what ) {
2022-02-15 18:06:48 +01:00
switch ( p_what ) {
case NOTIFICATION_POST_ENTER_TREE : {
_update_spawn_node ( ) ;
} break ;
case NOTIFICATION_EXIT_TREE : {
_update_spawn_node ( ) ;
2022-05-08 10:09:19 +02:00
for ( const KeyValue < ObjectID , SpawnInfo > & E : tracked_nodes ) {
Node * node = Object : : cast_to < Node > ( ObjectDB : : get_instance ( E . key ) ) ;
2022-02-15 18:06:48 +01:00
ERR_CONTINUE ( ! node ) ;
node - > disconnect ( SceneStringNames : : get_singleton ( ) - > tree_exiting , callable_mp ( this , & MultiplayerSpawner : : _node_exit ) ) ;
// This is unlikely, but might still crash the engine.
if ( node - > is_connected ( SceneStringNames : : get_singleton ( ) - > ready , callable_mp ( this , & MultiplayerSpawner : : _node_ready ) ) ) {
node - > disconnect ( SceneStringNames : : get_singleton ( ) - > ready , callable_mp ( this , & MultiplayerSpawner : : _node_ready ) ) ;
}
get_multiplayer ( ) - > despawn ( node , this ) ;
2021-10-08 14:13:06 +02:00
}
2022-02-15 18:06:48 +01:00
tracked_nodes . clear ( ) ;
} break ;
2021-10-08 14:13:06 +02:00
}
}
void MultiplayerSpawner : : _node_added ( Node * p_node ) {
if ( ! get_multiplayer ( ) - > has_multiplayer_peer ( ) | | ! is_multiplayer_authority ( ) ) {
return ;
}
if ( tracked_nodes . has ( p_node - > get_instance_id ( ) ) ) {
return ;
}
const Node * parent = get_spawn_node ( ) ;
if ( ! parent | | p_node - > get_parent ( ) ! = parent ) {
return ;
}
2022-05-23 02:24:14 +02:00
int id = find_spawnable_scene_index_from_path ( p_node - > get_scene_file_path ( ) ) ;
2021-10-08 14:13:06 +02:00
if ( id = = INVALID_ID ) {
return ;
}
const String name = p_node - > get_name ( ) ;
ERR_FAIL_COND_MSG ( name . validate_node_name ( ) ! = name , vformat ( " Unable to auto-spawn node with reserved name: %s. Make sure to add your replicated scenes via 'add_child(node, true)' to produce valid names. " , name ) ) ;
_track ( p_node , Variant ( ) , id ) ;
}
NodePath MultiplayerSpawner : : get_spawn_path ( ) const {
return spawn_path ;
}
void MultiplayerSpawner : : set_spawn_path ( const NodePath & p_path ) {
spawn_path = p_path ;
_update_spawn_node ( ) ;
}
void MultiplayerSpawner : : _track ( Node * p_node , const Variant & p_argument , int p_scene_id ) {
ObjectID oid = p_node - > get_instance_id ( ) ;
if ( ! tracked_nodes . has ( oid ) ) {
tracked_nodes [ oid ] = SpawnInfo ( p_argument . duplicate ( true ) , p_scene_id ) ;
p_node - > connect ( SceneStringNames : : get_singleton ( ) - > tree_exiting , callable_mp ( this , & MultiplayerSpawner : : _node_exit ) , varray ( p_node - > get_instance_id ( ) ) , CONNECT_ONESHOT ) ;
p_node - > connect ( SceneStringNames : : get_singleton ( ) - > ready , callable_mp ( this , & MultiplayerSpawner : : _node_ready ) , varray ( p_node - > get_instance_id ( ) ) , CONNECT_ONESHOT ) ;
}
}
void MultiplayerSpawner : : _node_ready ( ObjectID p_id ) {
get_multiplayer ( ) - > spawn ( ObjectDB : : get_instance ( p_id ) , this ) ;
}
void MultiplayerSpawner : : _node_exit ( ObjectID p_id ) {
Node * node = Object : : cast_to < Node > ( ObjectDB : : get_instance ( p_id ) ) ;
ERR_FAIL_COND ( ! node ) ;
if ( tracked_nodes . has ( p_id ) ) {
tracked_nodes . erase ( p_id ) ;
get_multiplayer ( ) - > despawn ( node , this ) ;
}
}
2022-05-23 02:24:14 +02:00
int MultiplayerSpawner : : find_spawnable_scene_index_from_path ( const String & p_scene ) const {
for ( uint32_t i = 0 ; i < spawnable_scenes . size ( ) ; i + + ) {
if ( spawnable_scenes [ i ] . path = = p_scene ) {
2021-10-08 14:13:06 +02:00
return i ;
}
}
return INVALID_ID ;
}
2022-05-23 02:24:14 +02:00
int MultiplayerSpawner : : find_spawnable_scene_index_from_object ( const ObjectID & p_id ) const {
2021-10-08 14:13:06 +02:00
const SpawnInfo * info = tracked_nodes . getptr ( p_id ) ;
return info ? info - > id : INVALID_ID ;
}
const Variant MultiplayerSpawner : : get_spawn_argument ( const ObjectID & p_id ) const {
const SpawnInfo * info = tracked_nodes . getptr ( p_id ) ;
return info ? info - > args : Variant ( ) ;
}
Node * MultiplayerSpawner : : instantiate_scene ( int p_id ) {
ERR_FAIL_COND_V_MSG ( spawn_limit & & spawn_limit < = tracked_nodes . size ( ) , nullptr , " Spawn limit reached! " ) ;
2022-05-23 02:24:14 +02:00
ERR_FAIL_UNSIGNED_INDEX_V ( ( uint32_t ) p_id , spawnable_scenes . size ( ) , nullptr ) ;
Ref < PackedScene > scene = spawnable_scenes [ p_id ] . cache ;
2021-10-08 14:13:06 +02:00
ERR_FAIL_COND_V ( scene . is_null ( ) , nullptr ) ;
return scene - > instantiate ( ) ;
}
Node * MultiplayerSpawner : : instantiate_custom ( const Variant & p_data ) {
ERR_FAIL_COND_V_MSG ( spawn_limit & & spawn_limit < = tracked_nodes . size ( ) , nullptr , " Spawn limit reached! " ) ;
Object * obj = nullptr ;
Node * node = nullptr ;
if ( GDVIRTUAL_CALL ( _spawn_custom , p_data , obj ) ) {
node = Object : : cast_to < Node > ( obj ) ;
}
return node ;
}
Node * MultiplayerSpawner : : spawn ( const Variant & p_data ) {
ERR_FAIL_COND_V ( ! is_inside_tree ( ) | | ! get_multiplayer ( ) - > has_multiplayer_peer ( ) | | ! is_multiplayer_authority ( ) , nullptr ) ;
ERR_FAIL_COND_V_MSG ( spawn_limit & & spawn_limit < = tracked_nodes . size ( ) , nullptr , " Spawn limit reached! " ) ;
ERR_FAIL_COND_V_MSG ( ! GDVIRTUAL_IS_OVERRIDDEN ( _spawn_custom ) , nullptr , " Custom spawn requires the '_spawn_custom' virtual method to be implemented via script. " ) ;
Node * parent = get_spawn_node ( ) ;
ERR_FAIL_COND_V_MSG ( ! parent , nullptr , " Cannot find spawn node. " ) ;
Node * node = instantiate_custom ( p_data ) ;
ERR_FAIL_COND_V_MSG ( ! node , nullptr , " The '_spawn_custom' implementation must return a valid Node. " ) ;
_track ( node , p_data ) ;
parent - > add_child ( node , true ) ;
return node ;
}