diff --git a/scenes/game.gd b/scenes/game.gd index d809432..2cb9d26 100644 --- a/scenes/game.gd +++ b/scenes/game.gd @@ -1,6 +1,80 @@ extends Node class_name Game +## The name to be given to the player controlled by the local peer. +var local_player_name: String -func _on_peernode_identified(peernode: PeerNode, player_name: String) -> void: - pass # Replace with function body. +## The color to be given to the player controlled by the local peer. +var local_player_color: Color + +## The [PeerNodeDirectory] instance child of this node. +@onready var peer_dir: PeerNodeDirectory = $"PeerNodeDirectory" + +## The [PlayerNodeDirectory] instance child of this node. +@onready var player_dir: PlayerNodeDirectory = $"PlayerNodeDirectory" + + +func init_signals() -> void: + multiplayer.connected_to_server.connect(_on_multiplayer_connected_to_server) + multiplayer.server_disconnected.connect(_on_multiplayer_disconnected_from_server) + multiplayer.connection_failed.connect(_on_multiplayer_connection_failed) + multiplayer.peer_connected.connect(_on_multiplayer_peer_connected) + multiplayer.peer_disconnected.connect(_on_multiplayer_peer_disconnected) + + +func _on_multiplayer_connected_to_server() -> void: + Log.peer(self, "Connected to server!") + +func _on_multiplayer_disconnected_from_server() -> void: + Log.peer(self, "Disconnected from server.") + +func _on_multiplayer_connection_failed() -> void: + Log.peer(self, "Connection failed...") + +func _on_multiplayer_peer_connected(peer_id: int) -> void: + Log.peer(self, "Peer connected: %d" % peer_id) + if multiplayer.is_server(): + for peernode in peer_dir.get_children(): + peer_dir.rpc_create_peernode.rpc_id(peer_id, peernode.get_multiplayer_authority()) + peer_dir.rpc_create_peernode.rpc(peer_id) + +func _on_multiplayer_peer_disconnected(peer_id: int) -> void: + Log.peer(self, "Peer disconnected: %d" % peer_id) + if multiplayer.is_server(): + peer_dir.rpc_destroy_peernode(peer_id) + + +func _on_peerdir_peernode_created(peernode: PeerNode) -> void: + Log.peer(self, "Peernode created: %d" % peernode.get_multiplayer_authority()) + if peernode.is_multiplayer_authority(): + peernode.rpc_identify.rpc(local_player_name) + +func _on_peerdir_peernode_destroyed(peernode: PeerNode) -> void: + Log.peer(self, "Peernode destroyed: %d" % peernode.get_multiplayer_authority()) + for playernode in player_dir.get_children(): + if playernode.get_multiplayer_authority() == peernode.get_multiplayer_authority(): + playernode.possess(1) + +func _on_peerdir_peernode_identified(player_name: String, peernode: PeerNode) -> void: + Log.peer(self, "Peernode identified: %d → %s" % [peernode.get_multiplayer_authority(), player_name]) + player_dir.rpc_possess_playernode.rpc(player_name, peernode.get_multiplayer_authority()) + + +func _on_playerdir_playernode_created(playernode: PlayerNode) -> void: + Log.peer(self, "Playernode `%s` created" % playernode.name) + +func _on_playerdir_playernode_destroyed(playernode: PlayerNode) -> void: + Log.peer(self, "Playernode `%s` destroyed" % playernode.name) + # Technically, this shouldn't ever happen + +func _on_playerdir_playernode_name_changed(old: String, new: String, playernode: PlayerNode) -> void: + Log.peer(self, "Playernode `%s` changed name: %s (was %s)" % [playernode.name, new, old]) + +func _on_playerdir_playernode_color_changed(old: Color, new: String, playernode: PlayerNode) -> void: + Log.peer(self, "Playernode `%s` changed color: %s (was %s)" % [playernode.name, new, old]) + +func _on_playerdir_playernode_possessed(old: int, new: int, playernode: PlayerNode) -> void: + Log.peer(self, "Playernode `%s` possessed: %d (was %d)" % [playernode.name, new, old]) + if playernode.is_multiplayer_authority(): + playernode.rpc_set_name.rpc(local_player_name) + playernode.rpc_set_color.rpc(local_player_color) diff --git a/scenes/game.tscn b/scenes/game.tscn index 5bb39c6..ce9b73d 100644 --- a/scenes/game.tscn +++ b/scenes/game.tscn @@ -1,12 +1,19 @@ -[gd_scene load_steps=3 format=3 uid="uid://b4w6ungwwjxsg"] +[gd_scene load_steps=4 format=3 uid="uid://dyxwp5rnxiff7"] [ext_resource type="Script" path="res://scenes/game.gd" id="1_cdtng"] -[ext_resource type="PackedScene" uid="uid://cwj0fyj6obj6r" path="res://scenes/multiple_players_tracker.tscn" id="2_f2gy2"] +[ext_resource type="PackedScene" uid="uid://7ux0cl08c2ho" path="res://scenes/peernode_directory.tscn" id="1_p0gju"] +[ext_resource type="PackedScene" uid="uid://d3jo4swi2c7ae" path="res://scenes/playernode_directory.tscn" id="2_pvlqw"] [node name="Game" type="Node"] script = ExtResource("1_cdtng") -[node name="MultiplePlayersTracker" parent="." instance=ExtResource("2_f2gy2")] +[node name="PeerNodeDirectory" parent="." instance=ExtResource("1_p0gju")] -[connection signal="created" from="MultiplePlayersTracker" to="." method="_on_single_player_tracker_created"] -[connection signal="trackers_changed" from="MultiplePlayersTracker" to="." method="_on_trackers_changed"] +[node name="PlayerNodeDirectory" parent="." instance=ExtResource("2_pvlqw")] + +[connection signal="child_entered_tree" from="PeerNodeDirectory" to="." method="_on_peerdir_peernode_created"] +[connection signal="child_exiting_tree" from="PeerNodeDirectory" to="." method="_on_peerdir_peernode_destroyed"] +[connection signal="peernode_identified" from="PeerNodeDirectory" to="." method="_on_peerdir_peernode_identified"] +[connection signal="playernode_color_changed" from="PlayerNodeDirectory" to="." method="_on_playerdir_playernode_color_changed"] +[connection signal="playernode_name_changed" from="PlayerNodeDirectory" to="." method="_on_playerdir_playernode_name_changed"] +[connection signal="playernode_possessed" from="PlayerNodeDirectory" to="." method="_on_playerdir_playernode_possessed"] diff --git a/scenes/main.gd b/scenes/main.gd index eb12adf..c9cb074 100644 --- a/scenes/main.gd +++ b/scenes/main.gd @@ -61,7 +61,8 @@ func init_client_game(player_name: String, player_color: Color, server_address: scene_tree.set_multiplayer(smp, ^"/root/Main/Client") client_game_instance.init_signals() - client_game_instance.init_identity(player_name, player_color) + client_game_instance.local_player_name = player_name + client_game_instance.local_player_color = player_color smp.set_multiplayer_peer(peer) func deinit_client_game(): @@ -74,8 +75,18 @@ func init_lobby_menu(): lobby_menu_instance.leave_confirmed.connect(_on_leave_confirmed) lobby_menu_instance.start_confirmed.connect(_on_start_confirmed) interface_instance.add_child(lobby_menu_instance) + client_game_instance.player_dir.child_entered_tree.connect(lobby_menu_instance.players_list._on_playernode_created) + client_game_instance.player_dir.child_exiting_tree.connect(lobby_menu_instance.players_list._on_playernode_destroyed) + client_game_instance.player_dir.playernode_name_changed.connect(lobby_menu_instance.players_list._on_playernode_name_changed) + client_game_instance.player_dir.playernode_color_changed.connect(lobby_menu_instance.players_list._on_playernode_color_changed) + client_game_instance.player_dir.playernode_possessed.connect(lobby_menu_instance.players_list._on_playernode_possessed) func deinit_lobby_menu(): + client_game_instance.player_dir.child_entered_tree.disconnect(lobby_menu_instance.players_list._on_playernode_created) + client_game_instance.player_dir.child_exiting_tree.disconnect(lobby_menu_instance.players_list._on_playernode_destroyed) + client_game_instance.player_dir.playernode_name_changed.disconnect(lobby_menu_instance.players_list._on_playernode_name_changed) + client_game_instance.player_dir.playernode_color_changed.disconnect(lobby_menu_instance.players_list._on_playernode_color_changed) + client_game_instance.player_dir.playernode_possessed.disconnect(lobby_menu_instance.players_list._on_playernode_possessed) lobby_menu_instance.queue_free() func _ready(): @@ -86,13 +97,11 @@ func _on_hosting_confirmed(player_name: String, player_color: Color, server_port init_server_game(server_port) init_client_game(player_name, player_color, "127.0.0.1", server_port) init_lobby_menu() - client_game_instance.trackers_changed.connect(lobby_menu_instance._on_trackers_changed) func _on_connecting_confirmed(player_name: String, player_color: Color, server_address: String, server_port: int): deinit_main_menu() init_client_game(player_name, player_color, server_address, server_port) init_lobby_menu() - client_game_instance.trackers_changed.connect(lobby_menu_instance._on_trackers_changed) func _on_leave_confirmed(): deinit_lobby_menu() diff --git a/scenes/peernode_directory.gd b/scenes/peernode_directory.gd index 5fffb47..c02f12b 100644 --- a/scenes/peernode_directory.gd +++ b/scenes/peernode_directory.gd @@ -29,7 +29,7 @@ func rpc_create_peernode(peer_id: int): peernodes_by_id[peer_id] = peernode peernode.set_multiplayer_authority(peer_id) peernode.identified.connect(_on_peernode_identified.bind(peernode)) - add_child(peernode) + add_child(peernode, true) ## Destroy the [PeerNode] for the given peer_id, or do nothing if it does not exist. @@ -44,8 +44,8 @@ func rpc_destroy_peernode(peer_id: int): ## Called on the server when a [PeerNode] calls [method rpc_identify] for itself. -func _on_peernode_identified(peernode: PeerNode, player_name: String): - peernode_identified.emit(peernode, player_name) +func _on_peernode_identified(player_name: String, peernode: PeerNode): + peernode_identified.emit(player_name, peernode) ## Emitted on the server when a [PeerNode] calls [method rpc_identify] for itself. -signal peernode_identified(peernode: PeerNode, player_name: String) +signal peernode_identified(player_name: String, peernode: PeerNode) diff --git a/scenes/player_label.gd b/scenes/player_label.gd index 264ed01..745c229 100644 --- a/scenes/player_label.gd +++ b/scenes/player_label.gd @@ -2,7 +2,14 @@ extends Label class_name PlayerLabel -func update_from_tracker(tracker: SinglePlayerTracker): - text = tracker.player_name - add_theme_color_override("font_color", tracker.player_color) - modulate.a = 1.0 if tracker.get_multiplayer_authority() != 1 else 0.3 +func set_player_name(value: String): + text = value + +func set_player_color(value: Color): + add_theme_color_override("font_color", value) + +func set_possessed(value: int): + if value == 1: + modulate.a = 0.3 + else: + modulate.a = 1.0 diff --git a/scenes/playernode.gd b/scenes/playernode.gd index 24e050d..6ab4209 100644 --- a/scenes/playernode.gd +++ b/scenes/playernode.gd @@ -2,4 +2,60 @@ extends Node class_name PlayerNode -# TODO \ No newline at end of file +## The name of the player represented by this node. +var player_name: String + +## The color of the player represented by this node. +var player_color: Color + +## Change the [field player_name] everywhere. +@rpc("authority", "call_local", "reliable") +func rpc_set_name(value: String): + if player_name != value: + var old_value: String = player_name + player_name = value + name_changed.emit(old_value, value) + +## Change the [field player_color] everywhere. +@rpc("authority", "call_local", "reliable") +func rpc_set_color(value: Color): + if player_color != value: + var old_value: Color = player_color + player_color = value + color_changed.emit(old_value, value) + +## Change the multiplayer authority on the local client. +## +## Used by [PlayerNodeDirectory], provided here for the purpose of signal emission. +func possess(value: int) -> void: + var old_value: int = get_multiplayer_authority() + if old_value != value: + set_multiplayer_authority(value) + possessed.emit(old_value, value) + + +## Prompt the multiplayer authority for this node to call [method rpc_set_name] again with the current value. +## +## Used to repeat [field player_name] to new peers. +@rpc("any_peer", "call_local", "reliable") +func rpc_query_name(): + if is_multiplayer_authority(): + rpc_set_name.rpc(player_name) + +## Prompt the multiplayer authority for this node to call [method rpc_set_color] again with the current value. +## +## Used to repeat [field player_color] to new peers. +@rpc("any_peer", "call_local", "reliable") +func rpc_query_color(): + if is_multiplayer_authority(): + rpc_set_color.rpc(player_color) + + +## Emitted when the name changes on the local scene because of [method rpc_set_name]. +signal name_changed(old: String, new: String) + +## Emitted when the color changes on the local scene because of [method rpc_set_color]. +signal color_changed(old: Color, new: Color) + +## Emitted when the multiplayer authority of this [Node] is changed via [method possess]. +signal possessed(old: int, new: int) diff --git a/scenes/playernode_directory.gd b/scenes/playernode_directory.gd index 6b830ad..047eac6 100644 --- a/scenes/playernode_directory.gd +++ b/scenes/playernode_directory.gd @@ -16,12 +16,6 @@ var playernodes_by_name: Dictionary = {} func get_playernode(player_name: String) -> PlayerNode: return playernodes_by_name.get(player_name) -## Called everywhere when a [PlayerNode] is renamed. -func _on_playernode_renamed(old_name: String, new_name: String) -> void: - var playernode = playernodes_by_name.get(old_name) - playernodes_by_name.erase(old_name) - playernodes_by_name[new_name] = playernode - ## Create a new [PlayerNode] for the given [param player_name], giving control of it to [param peer_id]. ## @@ -34,11 +28,32 @@ func rpc_possess_playernode(player_name: String, peer_id: int): # If the playernode does not exist, create it if playernode == null: playernode = playernode_scene.instantiate() - add_child(playernode) + playernode.name_changed.connect(_on_playernode_name_changed.bind(playernode)) + playernode.color_changed.connect(_on_playernode_color_changed.bind(playernode)) + playernode.possessed.connect(_on_playernode_possessed.bind(playernode)) + add_child(playernode, true) # If the multiplayer authority does not match the requested one, make it match - if playernode.get_multiplayer_authority() != peer_id: - playernode.set_multiplayer_authority(peer_id) - child_repossessed.emit(playernode) + playernode.possess(peer_id) + + + +func _on_playernode_name_changed(old: String, new: String, playernode: PlayerNode): + playernodes_by_name.erase(old) + playernodes_by_name[new] = playernode + playernode_name_changed.emit(old, new, playernode) + +func _on_playernode_color_changed(old: Color, new: Color, playernode: PlayerNode): + playernode_color_changed.emit(old, new, playernode) + +func _on_playernode_possessed(old: int, new: int, playernode: PlayerNode): + playernode_possessed.emit(old, new, playernode) + + +## Emitted when the name of one of the children [PlayerNode]s changes on the local scene. +signal playernode_name_changed(old: String, new: String, playernode: PlayerNode) + +## Emitted when the name of one of the children [PlayerNode]s changes on the local scene. +signal playernode_color_changed(old: Color, new: Color, playernode: PlayerNode) ## Emitted everywhere when one of the children [PlayerNode]s has changed multiplayer authority. -signal child_repossessed(playernode: PlayerNode) +signal playernode_possessed(old: int, new: int, playernode: PlayerNode) diff --git a/scenes/players_list.gd b/scenes/players_list.gd index 0619cab..f4df099 100644 --- a/scenes/players_list.gd +++ b/scenes/players_list.gd @@ -4,13 +4,26 @@ class_name PlayersList @onready var layout = $"Scrollable/Layout" -const player_label_scene = preload("res://scenes/player_label.tscn") +const label_scene: PackedScene = preload("res://scenes/player_label.tscn") + +var labels_by_playernode: Dictionary = {} -func _on_trackers_changed(trackers: Dictionary): - for child in layout.get_children(): - child.queue_free() - for tracker in trackers.values(): - var player_label_instance = player_label_scene.instantiate() - player_label_instance.update_from_tracker(tracker) - layout.add_child(player_label_instance) +func _on_playernode_created(playernode: PlayerNode) -> void: + var label_instance = label_scene.instantiate() + labels_by_playernode[playernode] = label_instance + layout.add_child(label_instance) + +func _on_playernode_destroyed(playernode: PlayerNode) -> void: + labels_by_playernode[playernode].queue_free() + labels_by_playernode.erase(playernode) + +func _on_playernode_name_changed(_old: String, new: String, playernode: PlayerNode) -> void: + labels_by_playernode[playernode].set_player_name(new) + +func _on_playernode_color_changed(_old: Color, new: Color, playernode: PlayerNode) -> void: + labels_by_playernode[playernode].set_player_color(new) + +func _on_playernode_possessed(_old: int, new: int, playernode: PlayerNode) -> void: + labels_by_playernode[playernode].set_possessed(new) +