From 22fdc857b01ae315130946dd32c243538fa21df7 Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Wed, 22 Jul 2026 11:43:24 -0500 Subject: [PATCH 1/2] Here we go... What I thought was going to be a quick 10-minute change is going to become a week-long escapade to fix a race condition. Player controllers are now a spawnable. Added "CollisionShape3D" and "RayCast3D" as spawnable nodes for the player controller. Changed player position sender to use spawnable native transform calls. Major refactor in spawnable_manger to help address race condition, and lots of comments for my sanity. Player database is now synced as well when joining a server. Player auto respawning. When a player node is destryoed, it is respawned after a short time. --- src/scenes/managers/app/network.gd | 6 +- src/scenes/managers/app/scene.gd | 27 ++++-- src/scenes/managers/scene/network.gd | 44 +++++++++- src/scenes/managers/scene/player.gd | 48 +++++------ .../managers/scene/spawnable_manager.gd | 85 ++++++++++++++++--- src/scenes/players/player.gd | 8 +- src/scripts/utils/node_schema_builder.gd | 4 + src/scripts/utils/schema.json | 24 ++++++ 8 files changed, 187 insertions(+), 59 deletions(-) diff --git a/src/scenes/managers/app/network.gd b/src/scenes/managers/app/network.gd index 0ae291a3..39888f19 100644 --- a/src/scenes/managers/app/network.gd +++ b/src/scenes/managers/app/network.gd @@ -98,10 +98,12 @@ func start_server(port: int = 0, root_scene: Enum.BaseLevel = Enum.BaseLevel.GRI scene_m.set_master_root_from_program(_scene, Enum.BaseLevel.GRID) scene_m.start_master_scene(_scene) + scene_m.set_active_session(_scene) # DEV: Force spawn the host. - scene_m.get_master_scene(_scene).get_node("PlayerManager").add_player(1) - scene_m.set_active_session(_scene) + # FIXME: This is probably bad design. + scene_m.get_master_scene(_scene).get_node("NetworkManager")._on_peer_connected(1) + Events.dash_session_changed.emit(_scene) return response_dict diff --git a/src/scenes/managers/app/scene.gd b/src/scenes/managers/app/scene.gd index 8125e749..3b11ee34 100644 --- a/src/scenes/managers/app/scene.gd +++ b/src/scenes/managers/app/scene.gd @@ -132,6 +132,8 @@ func stop_master_scene(id: String): func set_active_session(session_id: String): GlobalLogger.log("Setting session '%s' active." % session_id) + active_session = session_id + for _scene in network_m.get_connected_sessions(): # Each session gets disabled scene_container.get_node(_scene.id).visible = false @@ -141,7 +143,6 @@ func set_active_session(session_id: String): # session_id gets enabled. scene_container.get_node(session_id).process_mode = Node.PROCESS_MODE_INHERIT - active_session = session_id _set_camera_active_state(session_id, true) scene_container.get_node(session_id).visible = true _set_player_authority_state(session_id, true) @@ -176,9 +177,13 @@ func _set_camera_active_state(session_id, state: bool = false) -> void: var player_manager: Node = master_scene.get_node("PlayerManager") var player_database = player_manager.players var my_database_entry = player_database.get(my_id) - var camera = my_database_entry.get("node").get_node("Head/Camera3D") + if my_database_entry != null: + if my_database_entry.node == null: + # FIXME: This error should not be necessary, there is a bigger problem somewhere else. + return - camera.current = state + var camera = my_database_entry.node.get_node("Head/Camera3D") + camera.current = state return @@ -192,11 +197,17 @@ func _set_player_authority_state(session_id, is_active: bool = false) -> void: var player_manager: Node = master_scene.get_node("PlayerManager") var player_database = player_manager.players var my_database_entry = player_database.get(my_id) - var player = my_database_entry.get("node") - if is_active: - player.set_multiplayer_authority(int(my_id)) - return + if my_database_entry != null: + var player = my_database_entry.node + + if player == null: + # FIXME: This error should not be necessary, there is a bigger problem somewhere else. + return + + if is_active: + player.set_multiplayer_authority(int(my_id)) + return - player.set_multiplayer_authority(0) + player.set_multiplayer_authority(0) return diff --git a/src/scenes/managers/scene/network.gd b/src/scenes/managers/scene/network.gd index 0e685072..abcca0a3 100644 --- a/src/scenes/managers/scene/network.gd +++ b/src/scenes/managers/scene/network.gd @@ -71,12 +71,13 @@ func entity_position(entity_id: int, position): @rpc("any_peer", "reliable") func dev_request_spawnables_database() -> void: var caller_id = multiplayer.get_remote_sender_id() - var the_data = spawnable_m._database - spawnable_m.receive_database.rpc_id(caller_id, the_data, spawnable_m._database_id) - spawnable_m.sync_all() - return + # FIXME: We should not need to send the database_id, the host should tell the client what the id of a node is! + GlobalLogger.log("Sending spawnable database to peer %d: %d entries, DB_ID: %d" % [caller_id, spawnable_m._database.size(), spawnable_m._database_id]) + spawnable_m.receive_database.rpc_id(caller_id, spawnable_m._database, spawnable_m._database_id, player_m.players) + return +# FIXME Old dev function. Should be refactored! @rpc("any_peer", "reliable") func dev_request_sync() -> void: if is_multiplayer_authority() == false: @@ -90,7 +91,10 @@ func gizmo_selection(node_id: int, to_select: bool) -> void: func _on_connected_to_server(): + # When the client is connected to the server, request the database from the server. GlobalLogger.log("[%s] I am connected to a server." % _my_id) + + # Request the spawnable database from host rpc_id(1, "dev_request_spawnables_database") @@ -103,11 +107,25 @@ func _on_peer_connected(peer_id: int): if is_multiplayer_authority() == false: return + GlobalLogger.log("Peer '%s' is connected! Creating a player controller." % [peer_id]) + + # Add player to the database. player_m.add_player(peer_id) player_m.add_player.rpc(peer_id) + # Spawn the player controller. + var _entity = await spawnable_m.create("OM_PlayerController") + + # Set the player node in the player database. + player_m.set_player_node(peer_id, _entity) + + # The host adds a listener for the on_delete, then spawns the player back in. + _entity.connect("tree_exiting", _on_peer_player_node_destroyed.bind(peer_id)) + GlobalLogger.log("[%s] Peer '%s' connected to our server." % [_my_id, peer_id]) + rpc_id(peer_id, "set_root", Enum.BaseLevel.GRID) + # FIXME: There are now two functions that do this. Refactor one of them out. The newer one has better player node syncing. rpc_id(peer_id, "add_players", player_m.players) return @@ -121,3 +139,21 @@ func _on_peer_disconnected(peer_id: int) -> void: GlobalLogger.log("[%s] Peer '%s' disconnected to our server." % [_my_id, peer_id]) return + +func _on_peer_player_node_destroyed(peer_id: int) -> void: + GlobalLogger.log("Peer '%s' was destroyed! Queued a player controller respawn." % [peer_id]) + var _timer = get_tree().create_timer(1) + + _timer.timeout.connect(func () -> void: + if player_m.players.keys().has(peer_id) == false: + # Check to see if player still exists in the database, don't spawn if they are gone. + GlobalLogger.log("Peer '%s' was disconnected, not respawning a player controller." % [peer_id]) + return + + var _entity = await spawnable_m.create("OM_PlayerController") + spawnable_m.set_authority.rpc(int(_entity.name), peer_id) + player_m.set_player_node(peer_id, _entity) + _entity.connect("tree_exiting", _on_peer_player_node_destroyed.bind(peer_id)) + ) + + return diff --git a/src/scenes/managers/scene/player.gd b/src/scenes/managers/scene/player.gd index 3bc95dca..7a9aff17 100644 --- a/src/scenes/managers/scene/player.gd +++ b/src/scenes/managers/scene/player.gd @@ -8,9 +8,11 @@ # --- License extends Node +@onready var spawnable_m = get_node("../SpawnableManager") + const PLAYER_TEMPLATE = { "peer_id": 0, - "has_spawned": false, + "node_id": -1, "node": null, } @@ -26,9 +28,18 @@ func add_player(peer_id: int) -> void: GlobalLogger.log("[%s] Adding peer '%s' to the player list" % [caller_id, peer_id]) database_template.set("peer_id", peer_id) players.set(str(peer_id), database_template) + return - spawn_player(str(peer_id)) +func set_player_database(database: Dictionary) -> void: + players = database + # Fix the database references + for _player in players.keys(): + var _entry = players[_player] + var _db_entry = spawnable_m.get_by_id(int(_entry.node_id)) + _entry.node = _db_entry.node + + return @rpc("authority", "reliable") func remove_player(peer_id: int) -> void: @@ -38,34 +49,23 @@ func remove_player(peer_id: int) -> void: players[str(peer_id)].get("node").queue_free() players.erase(str(peer_id)) +@rpc("authority", "reliable") +func set_player_node(peer_id: int, node: Node3D) -> void: + var _target = players[str(peer_id)] -@rpc("authority", "unreliable") -func spawn_player(peer_id: String) -> void: - var caller_id = multiplayer.get_remote_sender_id() - - if players[peer_id].get("has_spawned") == true: - GlobalLogger.log("[%s] Did not spawn peer '%s', already exists!" % [caller_id, peer_id], Enum.LogLevel.WARNING) + # TODO: Error warnings + if _target == null: return - GlobalLogger.log("[%s] Spawning peer '%s'" % [caller_id, peer_id]) + _target.node = node + _target.node_id = node.name - if module_active == false: - GlobalLogger.log("[%s] Could not spawn peer '%s', module inactive." % [caller_id, peer_id]) - return - - var _player_scene: PackedScene = load("res://scenes/players/player.tscn") - var _new_player: Node3D = _player_scene.instantiate() - _new_player.name = str(peer_id) - _new_player.position = Vector3(0, 0, 0) - _new_player.set_multiplayer_authority(int(peer_id)) - get_node("../root").add_child(_new_player) - var player_node = get_node("../root").get_node(str(peer_id)) - GlobalLogger.log("[%s] Spawned peer '%s'." % [caller_id, peer_id]) - players[peer_id].set("node", player_node) - players[peer_id].set("has_spawned", true) + # If this is our own player node being set, ensure we have authority + if peer_id == multiplayer.get_unique_id(): + if node and node.has_method("set_multiplayer_authority"): + node.set_multiplayer_authority(peer_id) return - func kill_player() -> void: GlobalLogger.log("'%s' is not implemented." % get_stack()[0]["function"], Enum.LogLevel.WARNING) return diff --git a/src/scenes/managers/scene/spawnable_manager.gd b/src/scenes/managers/scene/spawnable_manager.gd index a87da0c7..51c090b5 100644 --- a/src/scenes/managers/scene/spawnable_manager.gd +++ b/src/scenes/managers/scene/spawnable_manager.gd @@ -19,7 +19,7 @@ const SPAWNABLE_TEMPLATE: Dictionary = { "type": -1, "spawner": -1, "physics_owner": 1, - "node": "", + "node": null, "id": -1, "pretty_name": "ERROR", } @@ -34,6 +34,7 @@ var _database := [] @onready var instance_root = get_parent().get_node("root") @onready var rpcawaiter = get_parent().get_node("RpcAwaiter") @onready var session_signalbus: Node = get_node("../SignalBus") +@onready var player_m = get_node("../PlayerManager") # TODO: Handle physics for our items, and @@ -66,11 +67,11 @@ func sync_all() -> void: set_transform.rpc(spawnable.id, spawnable.node.transform) return - @rpc("any_peer", "reliable") func create(node_type: String, node_parent: int = 0, model_path: String = "") -> Variant: var my_id: int = app_network_m._database.sessions_api[app_scene_m.active_session].get_unique_id() var caller_id: int = multiplayer.get_remote_sender_id() + GlobalLogger.log("[%s] Spawning '%s'." % [my_id, node_type]) if my_id == 1: var entity = spawn_spawnable(node_type, "", model_path, node_parent) @@ -78,14 +79,18 @@ func create(node_type: String, node_parent: int = 0, model_path: String = "") -> var database_index: int = _database.find_custom(func(entry): return entry.id == entity) if caller_id != 0 && caller_id != my_id: - # This is a client request to spawn + # This is a client request to spawn. + # We return the synced node name here so we can get the client-side node later. return int(_database[database_index].node.name) return _database[database_index].node else: - # TODO: Graceful error for when RPC target is not found? var entity = await rpcawaiter.send_rpc(1, create.bind(node_type, node_parent, model_path)) + + # Since we are given the node name, we will need to find the node in our database. var database_index: int = _database.find_custom(func(entry): return entry.id == entity) + + # Database index was found, get the node at that index. return _database[database_index].node @rpc("any_peer", "reliable") @@ -109,15 +114,15 @@ func destroy(node_id: int) -> Variant: return @rpc("any_peer", "reliable") -func set_transform(node_id: int, transform: Transform3D) -> void: +func set_transform(node_id: int, p_transform: Transform3D) -> void: var _my_id: int = app_network_m._database.sessions_api[app_scene_m.active_session].get_unique_id() var _caller_id: int = multiplayer.get_remote_sender_id() if _my_id == 1: # TODO: Compress for network? - transform_spawnable.rpc(node_id, transform) + transform_spawnable.rpc(node_id, p_transform) else: - await rpcawaiter.send_rpc(1, set_transform.bind(node_id, transform)) + await rpcawaiter.send_rpc(1, set_transform.bind(node_id, p_transform)) return return @@ -133,6 +138,18 @@ func set_property(node_id: int, property_name: String, property_value: Variant) return return +@rpc("any_peer", "reliable") +func set_authority(node_id: int, peer_id: int) -> void: + # TODO: Only allow the host to call this function. + var _my_id: int = app_network_m._database.sessions_api[app_scene_m.active_session].get_unique_id() + var _caller_id: int = multiplayer.get_remote_sender_id() + + if _my_id == 1: + set_authority_on_spawnable.rpc(node_id, peer_id) + else: + await rpcawaiter.send_rpc(1, set_authority.bind(node_id, peer_id)) + return + # TODO: How would large assets work? @rpc("authority", "reliable") func spawn_spawnable(p_type: String, p_name: String = "", p_path: String = "", parent_id: int = 0) -> int: @@ -145,7 +162,7 @@ func spawn_spawnable(p_type: String, p_name: String = "", p_path: String = "", p var database_index = _database.find_custom(func(entry): return entry.id == parent_id) parent_node = _database[database_index].node - _spawned_entity = _spawn_node(p_type, 1, parent_node, p_path) + _spawned_entity = _spawn_node(p_type, 1, parent_node, p_path, _spawnable_id) _spawned_entity.owner = parent_node session_signalbus.node_created.emit(_spawned_entity) @@ -171,6 +188,14 @@ func delete_spawnable(node_name: String) -> void: @rpc("call_local", "authority", "reliable") func transform_spawnable(node_id: int, transform: Transform3D) -> void: var _entity_db = get_by_id(node_id) + if _entity_db == null: + GlobalLogger.log("Could not locate node id '%s'" % node_id, Enum.LogLevel.WARNING) + return + + if _entity_db.node == null: + GlobalLogger.log("Could not locate node '%s'" % node_id, Enum.LogLevel.WARNING) + return + _entity_db.node.transform = transform return @@ -178,9 +203,24 @@ func transform_spawnable(node_id: int, transform: Transform3D) -> void: func set_property_on_spawnable(node_id: int, property_name: String, property_value: Variant): var _entity_db = get_by_id(node_id) + if _entity_db == {}: + return + _entity_db.node.set_indexed(property_name, property_value) return +@rpc("call_local", "authority", "reliable") +func set_authority_on_spawnable(node_id: int, peer_id: int) -> void: + # TODO: Only allow the host to call this function. + var _entity_db = get_by_id(node_id) + + # TODO: Error Check + + _entity_db.node.set_multiplayer_authority(peer_id) + + GlobalLogger.log("Giving peer '%s' authority for node '%s'." % [peer_id, node_id]) + return + func _get_deletion_queue(node_name: String) -> Array: var _entry_index = _database.find_custom(func(item): return item.id == int(node_name)) var _node = _database[_entry_index].node @@ -202,10 +242,27 @@ func _add_to_deletion_queue(node: Node, list: Array[Node] = []) -> Array[Node]: @rpc("authority", "reliable") -func receive_database(database: Array, id: int) -> void: +func receive_database(database: Array, _id: int, players: Dictionary) -> void: + var _my_id: int = app_network_m._database.sessions_api[app_scene_m.active_session].get_unique_id() + + # FIXME: I don't think this is required since we won't have this be called multiple times. + # Clear existing database to prevent ID conflicts. + for entry in _database: + entry.node.queue_free() + _database.clear() + + GlobalLogger.log("[%s] Receiving spawnable database with %d entries" % [_my_id, database.size()]) + for spawnable in database: + GlobalLogger.log("Spawning '%s' as '%s'." % [spawnable.id, spawnable.type]) spawn_spawnable(spawnable.type, str(spawnable.id)) - network_m.rpc_id(1, "dev_request_sync") + + GlobalLogger.log("[%s] Database sync complete." % _my_id) + + # Update the player database. + player_m.set_player_database(players) + + network_m.dev_request_sync.rpc_id(1) return func set_node_visible_to_inspector(node: Node) -> void: @@ -241,7 +298,7 @@ func get_by_id(spawnable_id: int) -> Dictionary: return _database[target_entry] -func _spawn_node(node_type: String, node_owner: int, parent: Node = instance_root, model_path = "") -> Node: +func _spawn_node(node_type: String, node_owner: int, parent: Node = instance_root, model_path = "", node_name: String = _database_id) -> Node: var _node: Node var _node_name = node_type var _node_schema = NSB.get_entry(_node_name) @@ -254,7 +311,7 @@ func _spawn_node(node_type: String, node_owner: int, parent: Node = instance_roo _node = NSB.build(_node_name, model_path) # Add to database - var _db_id = _add_to_database(_node, node_type, node_owner) + var _db_id = _add_to_database(_node, node_type, node_owner, int(node_name)) if model_path != "": _pretty_name = model_path.get_file() @@ -274,9 +331,9 @@ func _spawn_node(node_type: String, node_owner: int, parent: Node = instance_roo return _node -func _add_to_database(node: Node, type: String, node_owner: int) -> int: +func _add_to_database(node: Node, type: String, node_owner: int, node_id: int = 0) -> int: var _db_entry = SPAWNABLE_TEMPLATE.duplicate() - _db_entry.id = int(_database_id) + _db_entry.id = int(_database_id) if node_id == 0 else node_id _db_entry.node = node _db_entry.type = type _db_entry.spawner = node_owner diff --git a/src/scenes/players/player.gd b/src/scenes/players/player.gd index a740e2f7..35b613c6 100644 --- a/src/scenes/players/player.gd +++ b/src/scenes/players/player.gd @@ -168,13 +168,7 @@ func _send_player_position() -> void: if is_multiplayer_authority() == false: return - var compressed_position = NetworkCompression.c_16_pos(position) - var compressed_rotation = NetworkCompression.c_16_vec3(rotation) - - # HACK: We are just appending the rotation bits at the end here. It should probably be more efficient somewhere else. - compressed_position.append_array(compressed_rotation) - - _app_scene_m.get_master_scene(_app_scene_m.active_session).get_node("NetworkManager").entity_position.rpc(int(name), compressed_position) + _session_spawnable_m.set_transform(int(name), transform) return diff --git a/src/scripts/utils/node_schema_builder.gd b/src/scripts/utils/node_schema_builder.gd index 2e55ce02..9f3bc70a 100644 --- a/src/scripts/utils/node_schema_builder.gd +++ b/src/scripts/utils/node_schema_builder.gd @@ -76,6 +76,10 @@ static func build(node_name: String, model_path: String = "") -> Node: _add_node_metadata(_work_node) return _work_node + if node_name == "OM_PlayerController": + var _work_node = load("res://scenes/players/player.tscn").instantiate() + return _work_node + if node_name == "Capsule": var _work_node = MeshInstance3D.new() _work_node.mesh = CapsuleMesh.new() diff --git a/src/scripts/utils/schema.json b/src/scripts/utils/schema.json index 8f20a7ca..87a1aea6 100644 --- a/src/scripts/utils/schema.json +++ b/src/scripts/utils/schema.json @@ -7,6 +7,30 @@ "hidden": true, "deep_delete": false }, + "OM_PlayerController": { + "requires_setup": true, + "pretty_name": "Player", + "node": null, + "icon": "godot/CharacterController3D.svg", + "hidden": true, + "deep_delete": false + }, + "CollisionShape3D": { + "requires_setup": false, + "pretty_name": "CollisionShape3D", + "node": "CollisionShape3D", + "icon": "godot/CollisionShape3D.svg", + "hidden": false, + "deep_delete": false + }, + "RayCast3D": { + "requires_setup": false, + "pretty_name": "RayCast3D", + "node": "RayCast3D", + "icon": "godot/RayCast3D.svg", + "hidden": false, + "deep_delete": false + }, "Camera3D": { "requires_setup": true, "pretty_name": "Camera3D", From 6883a1d591ce8ec3c8900bd96d616c036678a8cf Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Thu, 23 Jul 2026 12:56:38 -0500 Subject: [PATCH 2/2] Fixed the problem. It was not a race condition, it was me unexpectedly modifying the database_id in the middle of syncing. --- src/scenes/managers/scene/network.gd | 7 ++-- .../managers/scene/spawnable_manager.gd | 32 +++++++++++++++---- src/scripts/managers/settings.gd | 1 - 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/scenes/managers/scene/network.gd b/src/scenes/managers/scene/network.gd index abcca0a3..1c5d42e6 100644 --- a/src/scenes/managers/scene/network.gd +++ b/src/scenes/managers/scene/network.gd @@ -120,7 +120,7 @@ func _on_peer_connected(peer_id: int): player_m.set_player_node(peer_id, _entity) # The host adds a listener for the on_delete, then spawns the player back in. - _entity.connect("tree_exiting", _on_peer_player_node_destroyed.bind(peer_id)) + _entity.connect("tree_exiting", _on_peer_player_node_destroyed.bind(peer_id, int(_entity.name))) GlobalLogger.log("[%s] Peer '%s' connected to our server." % [_my_id, peer_id]) @@ -140,8 +140,11 @@ func _on_peer_disconnected(peer_id: int) -> void: GlobalLogger.log("[%s] Peer '%s' disconnected to our server." % [_my_id, peer_id]) return -func _on_peer_player_node_destroyed(peer_id: int) -> void: +func _on_peer_player_node_destroyed(peer_id: int, node_id: int) -> void: GlobalLogger.log("Peer '%s' was destroyed! Queued a player controller respawn." % [peer_id]) + spawnable_m.destroy(node_id) + spawnable_m.destroy.rpc(node_id) + var _timer = get_tree().create_timer(1) _timer.timeout.connect(func () -> void: diff --git a/src/scenes/managers/scene/spawnable_manager.gd b/src/scenes/managers/scene/spawnable_manager.gd index 51c090b5..cfe317ce 100644 --- a/src/scenes/managers/scene/spawnable_manager.gd +++ b/src/scenes/managers/scene/spawnable_manager.gd @@ -64,6 +64,9 @@ func sync_all() -> void: if !is_multiplayer_authority(): return for spawnable in _database: + if spawnable.node == null: + GlobalLogger.log("Node does not exist.", Enum.LogLevel.WARNING) + return set_transform.rpc(spawnable.id, spawnable.node.transform) return @@ -74,8 +77,10 @@ func create(node_type: String, node_parent: int = 0, model_path: String = "") -> GlobalLogger.log("[%s] Spawning '%s'." % [my_id, node_type]) if my_id == 1: - var entity = spawn_spawnable(node_type, "", model_path, node_parent) - spawn_spawnable.rpc(node_type, "", model_path, node_parent) + var _target_id: String = str(_database_id) + + var entity = spawn_spawnable(node_type, _target_id, model_path, node_parent) + spawn_spawnable.rpc(node_type, _target_id, model_path, node_parent) var database_index: int = _database.find_custom(func(entry): return entry.id == entity) if caller_id != 0 && caller_id != my_id: @@ -153,7 +158,6 @@ func set_authority(node_id: int, peer_id: int) -> void: # TODO: How would large assets work? @rpc("authority", "reliable") func spawn_spawnable(p_type: String, p_name: String = "", p_path: String = "", parent_id: int = 0) -> int: - var _spawnable_id = p_name if p_name != "" else str(_database_id) var _spawned_entity var parent_node = get_parent().get_node("root") @@ -162,7 +166,7 @@ func spawn_spawnable(p_type: String, p_name: String = "", p_path: String = "", p var database_index = _database.find_custom(func(entry): return entry.id == parent_id) parent_node = _database[database_index].node - _spawned_entity = _spawn_node(p_type, 1, parent_node, p_path, _spawnable_id) + _spawned_entity = _spawn_node(p_type, 1, parent_node, p_path, p_name) _spawned_entity.owner = parent_node session_signalbus.node_created.emit(_spawned_entity) @@ -242,7 +246,7 @@ func _add_to_deletion_queue(node: Node, list: Array[Node] = []) -> Array[Node]: @rpc("authority", "reliable") -func receive_database(database: Array, _id: int, players: Dictionary) -> void: +func receive_database(database: Array, id: int, players: Dictionary) -> void: var _my_id: int = app_network_m._database.sessions_api[app_scene_m.active_session].get_unique_id() # FIXME: I don't think this is required since we won't have this be called multiple times. @@ -251,12 +255,17 @@ func receive_database(database: Array, _id: int, players: Dictionary) -> void: entry.node.queue_free() _database.clear() + GlobalLogger.log("[%s] Receiving spawnable database with %d entries" % [_my_id, database.size()]) for spawnable in database: GlobalLogger.log("Spawning '%s' as '%s'." % [spawnable.id, spawnable.type]) spawn_spawnable(spawnable.type, str(spawnable.id)) + # FIXME: We don't worry about the database index on the client, the server tells us the node id. + # Set the database id + _database_id = id + GlobalLogger.log("[%s] Database sync complete." % _my_id) # Update the player database. @@ -298,7 +307,7 @@ func get_by_id(spawnable_id: int) -> Dictionary: return _database[target_entry] -func _spawn_node(node_type: String, node_owner: int, parent: Node = instance_root, model_path = "", node_name: String = _database_id) -> Node: +func _spawn_node(node_type: String, node_owner: int, parent: Node = instance_root, model_path = "", node_name: String = str(_database_id)) -> Node: var _node: Node var _node_name = node_type var _node_schema = NSB.get_entry(_node_name) @@ -332,8 +341,17 @@ func _spawn_node(node_type: String, node_owner: int, parent: Node = instance_roo func _add_to_database(node: Node, type: String, node_owner: int, node_id: int = 0) -> int: + # If we are a client, we ignore _database_id entirely. var _db_entry = SPAWNABLE_TEMPLATE.duplicate() - _db_entry.id = int(_database_id) if node_id == 0 else node_id + + if node_id == 0: + GlobalLogger.log("No node_id supplied.", Enum.LogLevel.ERROR) + return 0 + + _db_entry.id = int(_database_id) + else: + _db_entry.id = node_id + _db_entry.node = node _db_entry.type = type _db_entry.spawner = node_owner diff --git a/src/scripts/managers/settings.gd b/src/scripts/managers/settings.gd index 37665eee..5b982956 100644 --- a/src/scripts/managers/settings.gd +++ b/src/scripts/managers/settings.gd @@ -85,7 +85,6 @@ func _load_settings() -> void: var _content = _file.get_as_text() var _parsed = JSON.parse_string(_content) _settings = _parsed - print(_content) GlobalLogger.log("Settings have been loaded.", Enum.LogLevel.INFO) return