From f2b47889035670095f0db881ed3cfff565064b1a Mon Sep 17 00:00:00 2001 From: Stefano Pigozzi Date: Mon, 18 Mar 2024 06:08:31 +0100 Subject: [PATCH] Sync more stuff --- scenes/game.gd | 80 ++++++++-------- scenes/game.tscn | 13 +-- scenes/game_hud.gd | 22 ++--- scenes/golf_ball.gd | 16 ++-- scenes/golf_level.gd | 125 +++++++++++++++--------- scenes/golf_tee.gd | 35 +++++-- scenes/level_manager.gd | 169 +++++++++++++++++++++++---------- scenes/main.gd | 18 ++-- scenes/peernode_directory.gd | 8 ++ scenes/peernode_directory.tscn | 2 + scenes/playernode.gd | 5 + scenes/playernode_directory.gd | 12 +++ 12 files changed, 321 insertions(+), 184 deletions(-) diff --git a/scenes/game.gd b/scenes/game.gd index 51224eb..1c18106 100644 --- a/scenes/game.gd +++ b/scenes/game.gd @@ -1,12 +1,9 @@ extends Node class_name Game -## The name to be given to the player controlled by the local peer. -var local_player_name: String -## The color to be given to the player controlled by the local peer. -var local_player_color: Color +@export_category("References") ## The [PeerNodeDirectory] instance child of this node. @export var peer_dir: PeerNodeDirectory = null @@ -21,8 +18,21 @@ var local_player_color: Color @export var level_manager: LevelManager = null + ## Emitted when the player has requested to exit from the game. -signal leave_confirmed +signal lost_connection + +## Emitted when the golf ball for the local player has been spawned. +## +## Used for [GameHUD] events. +signal local_player_spawned(ball: GolfBall) + + +## The name to be given to the player controlled by the local peer. +var local_player_name: String + +## The color to be given to the player controlled by the local peer. +var local_player_color: Color ## Initialize some signals needed by this node to function properly. @@ -42,11 +52,11 @@ func _on_multiplayer_disconnected_from_server() -> void: func _on_multiplayer_connection_failed() -> void: Log.peer(self, "Connection failed...") - leave_confirmed.emit() + lost_connection.emit() func _on_multiplayer_peer_connected(peer_id: int) -> void: Log.peer(self, "Peer connected: %d" % peer_id) - if multiplayer.is_server(): + if is_multiplayer_authority(): for peernode in peer_dir.get_children(): peer_dir.rpc_create_peernode.rpc_id(peer_id, peernode.get_multiplayer_authority()) for playernode in player_dir.get_children(): @@ -55,60 +65,44 @@ func _on_multiplayer_peer_connected(peer_id: int) -> void: playernode.rpc_query_color.rpc_id(playernode.get_multiplayer_authority()) playernode.rpc_query_scores.rpc_id(playernode.get_multiplayer_authority()) peer_dir.rpc_create_peernode.rpc(peer_id) - phase_tracker.rpc_set_phase.rpc_id(peer_id, phase_tracker.phase) func _on_multiplayer_peer_disconnected(peer_id: int) -> void: Log.peer(self, "Peer disconnected: %d" % peer_id) - if multiplayer.is_server(): + if is_multiplayer_authority(): for playernode in player_dir.get_children(): if playernode.get_multiplayer_authority() == peer_id: player_dir.rpc_possess_playernode.rpc(playernode.player_name, 1) peer_dir.rpc_destroy_peernode.rpc(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()) +func _on_peerdir_local_peernode_created(peernode: PeerNode) -> void: + Log.peer(self, "Local peernode created: %s" % peernode) + peernode.rpc_identify.rpc(local_player_name) 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()) + Log.peer(self, "Peernode identified: %s → %s" % [peernode, player_name]) + if is_multiplayer_authority(): + var peer_id = peernode.get_multiplayer_authority() + phase_tracker.rpc_set_phase.rpc_id(peer_id, phase_tracker.phase) + level_manager.sync_level(peer_id) + player_dir.rpc_possess_playernode.rpc(player_name, peer_id) func _on_playerdir_playernode_created(playernode: PlayerNode) -> void: - Log.peer(self, "Playernode `%s` created" % playernode.player_name) - -func _on_playerdir_playernode_destroyed(playernode: PlayerNode) -> void: - Log.peer(self, "Playernode `%s` destroyed" % playernode.player_name) - -func _on_playerdir_playernode_name_changed(old: String, new: String, playernode: PlayerNode) -> void: - Log.peer(self, "Playernode `%s` changed name: %s (was %s)" % [playernode.player_name, new, old]) - -func _on_playerdir_playernode_color_changed(old: Color, new: Color, playernode: PlayerNode) -> void: - Log.peer(self, "Playernode `%s` changed color: %s (was %s)" % [playernode.player_name, new, old]) - -func _on_playerdir_playernode_score_reported(strokes: int, playernode: PlayerNode) -> void: - Log.peer(self, "Playernode `%s` reported score: %d" % [playernode.player_name, strokes]) - -func _on_playerdir_playernode_scores_changed(old: Array, new: Array, playernode: PlayerNode) -> void: - Log.peer(self, "Playernode `%s` changed scores: %s (was %s)" % [playernode.player_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.player_name, new, old]) - if playernode.is_multiplayer_authority() and not multiplayer.is_server(): - playernode.rpc_set_name.rpc(local_player_name) - playernode.rpc_set_color.rpc(local_player_color) + Log.peer(self, "Playernode created: %s" % playernode) + if is_multiplayer_authority(): + level_manager.add_player(playernode) +func _on_playerdir_local_playernode_possessed(old: int, new: int, playernode: PlayerNode) -> void: + Log.peer(self, "Local playernode possessed: %d → %d" % [old, new]) + playernode.rpc_set_name.rpc(local_player_name) + playernode.rpc_set_color.rpc(local_player_color) func _on_main_start_confirmed() -> void: - if multiplayer.is_server(): + if is_multiplayer_authority(): phase_tracker.rpc_set_phase.rpc(PhaseTracker.Phase.PLAYING) - level_manager.rpc_next_level.rpc() + level_manager.next_level() func _on_level_manager_playlist_complete(_playlist: LevelPlaylist) -> void: - if multiplayer.is_server(): + if is_multiplayer_authority(): phase_tracker.rpc_set_phase.rpc(PhaseTracker.Phase.ENDED) diff --git a/scenes/game.tscn b/scenes/game.tscn index 6f1c5f7..973d2ee 100644 --- a/scenes/game.tscn +++ b/scenes/game.tscn @@ -24,21 +24,16 @@ level_manager = NodePath("LevelManager") [node name="PhaseTracker" parent="." instance=ExtResource("4_kab4c")] [node name="LevelManager" parent="." node_paths=PackedStringArray("playlist", "player_dir") instance=ExtResource("5_tdqk1")] -playlist = NodePath("LevelPlaylist") +playlist = NodePath("../LevelPlaylist") player_dir = NodePath("../PlayerNodeDirectory") -[node name="LevelPlaylist" parent="LevelManager" instance=ExtResource("6_bk2n3")] +[node name="LevelPlaylist" parent="." instance=ExtResource("6_bk2n3")] script = ExtResource("7_akb5s") levels = Array[PackedScene]([ExtResource("8_h8dsk"), ExtResource("9_2kliu")]) -[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="local_peernode_created" from="PeerNodeDirectory" to="." method="_on_peerdir_local_peernode_created"] [connection signal="peernode_identified" from="PeerNodeDirectory" to="." method="_on_peerdir_peernode_identified"] [connection signal="child_entered_tree" from="PlayerNodeDirectory" to="." method="_on_playerdir_playernode_created"] -[connection signal="child_exiting_tree" from="PlayerNodeDirectory" to="." method="_on_playerdir_playernode_destroyed"] -[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="local_playernode_possessed" from="PlayerNodeDirectory" to="." method="_on_playerdir_local_playernode_possessed"] [connection signal="playernode_possessed" from="PlayerNodeDirectory" to="." method="_on_playerdir_playernode_possessed"] -[connection signal="playernode_score_reported" from="PlayerNodeDirectory" to="." method="_on_playerdir_playernode_score_reported"] -[connection signal="playernode_scores_changed" from="PlayerNodeDirectory" to="." method="_on_playerdir_playernode_scores_changed"] [connection signal="playlist_complete" from="LevelManager" to="." method="_on_level_manager_playlist_complete"] diff --git a/scenes/game_hud.gd b/scenes/game_hud.gd index 6ea283f..b4489bd 100644 --- a/scenes/game_hud.gd +++ b/scenes/game_hud.gd @@ -19,37 +19,31 @@ func _on_level_completed(_level: GolfLevel) -> void: scores_panel.hide() func _on_playernode_name_changed(old: String, new: String, playernode: PlayerNode) -> void: - var instance = get_node_or_null("PlayerScoreLabel__%s" % old) + var instance = get_node_or_null(old) if instance != null: instance.from_score(playernode) - instance.name = "PlayerScoreLabel__%s" % new + instance.name = new func _on_playernode_color_changed(_old: Color, _new: Color, playernode: PlayerNode) -> void: - var instance = get_node_or_null("PlayerScoreLabel__%s" % playernode.player_name) + var instance = get_node_or_null(playernode.player_name) if instance != null: instance.from_score(playernode) func _on_playernode_possessed(_old: int, _new: int, playernode: PlayerNode) -> void: - var instance = get_node_or_null("PlayerScoreLabel__%s" % playernode.player_name) + var instance = get_node_or_null(playernode.player_name) if instance != null: instance.from_score(playernode) func _on_playernode_score_reported(strokes: int, playernode: PlayerNode) -> void: var score_instance = score_scene.instantiate() score_instance.from_score(strokes, playernode) - score_instance.name = "PlayerScoreLabel__%s" % playernode.player_name + score_instance.name = playernode.player_name scores_container.add_child(score_instance) scores_panel.show() func _on_playernode_scores_changed(_old: Array, _new: Array, _playernode: PlayerNode) -> void: pass -func _on_local_player_spawned(ball: GolfBall, _level: GolfLevel) -> void: - ball.putt_controller.putt.connect(_on_putt.bind(ball)) - -func _on_putt(_putt_vector: Vector2, ball: GolfBall) -> void: - strokes_label.text = "%d" % ball.strokes - - -func _on_strokes_changed(strokes: int) -> void: - strokes_label.text = "%s" % strokes +func _on_playernode_putt_performed(ball: GolfBall, playernode: PlayerNode) -> void: + if playernode.is_multiplayer_authority(): + strokes_label.text = "%s" % ball.strokes diff --git a/scenes/golf_ball.gd b/scenes/golf_ball.gd index 59e415f..57a3bdf 100644 --- a/scenes/golf_ball.gd +++ b/scenes/golf_ball.gd @@ -131,20 +131,22 @@ func check_has_entered_hole() -> bool: @rpc("authority", "call_local", "reliable") -func rpc_sync_enter_hole(): +func rpc_do_enter_hole(): + enter_hole() + + +func enter_hole(): in_hole = true - visible = false + hide() putt_controller.can_putt = false if not multiplayer.is_server(): hole_sound.play() entered_hole.emit(strokes) - -# FIXME: What happens on the server? ## Push this object's [field global_position] to all other clients. @rpc("authority", "call_remote", "unreliable_ordered") -func rpc_sync_global_position(nposition: Vector2): +func rpc_set_global_position(nposition: Vector2): global_position = nposition @@ -156,9 +158,9 @@ func _physics_process(delta) -> void: if not multiplayer.is_server() and is_multiplayer_authority(): if not in_hole: do_movement(delta) - rpc_sync_global_position.rpc(global_position) + rpc_set_global_position.rpc(global_position) apply_friction(delta) if check_has_entered_hole(): - rpc_sync_enter_hole.rpc() + rpc_do_enter_hole.rpc() if check_has_stopped(): putt_controller.can_putt = true diff --git a/scenes/golf_level.gd b/scenes/golf_level.gd index 6ebbaa3..7cd181d 100644 --- a/scenes/golf_level.gd +++ b/scenes/golf_level.gd @@ -2,12 +2,6 @@ extends Node2D class_name GolfLevel -## Emitted when it's time to change to the next level. -signal level_completed - -## Emitted when the [GolfBall] for the local player has been spawned. -signal local_player_spawned(ball: GolfBall) - @export_category("Level Data") @@ -30,10 +24,11 @@ signal local_player_spawned(ball: GolfBall) ## The [TileMap] of this level. @export var map: TileMap = null + + ## If active, the [Timer] between emissions of [signal everyone_entered_hole] and [signal level_completed]. var complete_timer: Timer = null - ## If on server, this variable indicates the [GolfLevel] to replicate. ## ## The [GolfLevel] in question should not be added to the scene tree, or it will cause problems on the client acting as server. @@ -65,20 +60,24 @@ const complete_timer_scene: PackedScene = preload("res://scenes/complete_timer.t ## Replicate the [field target] from the server to the clients via RPC. -func build() -> void: - build_tilemap() - build_tilemap_cells() - build_tee() - build_balls() - build_hole() - build_camera() +func build(peer_id: int = 0) -> void: + build_tilemap(peer_id) + build_tilemap_cells(peer_id) + build_tee(peer_id) + build_existing_balls(peer_id) + build_new_balls(peer_id) + build_hole(peer_id) + build_camera(peer_id) ## Replicate the [field map] of the [field target] to the remote [field map]. -func build_tilemap() -> void: +func build_tilemap(peer_id: int = 0) -> void: Log.peer(self, "Replicating map...") var tmap: TileMap = target.map - rpc_build_tilemap.rpc(tmap.global_position, tmap.global_rotation, tmap.global_scale) + if peer_id > 0: + rpc_build_tilemap.rpc_id(peer_id, tmap.global_position, tmap.global_rotation, tmap.global_scale) + else: + rpc_build_tilemap.rpc(tmap.global_position, tmap.global_rotation, tmap.global_scale) ## Create the [field map]. @rpc("authority", "call_local", "reliable") @@ -90,12 +89,13 @@ func rpc_build_tilemap(tposition: Vector2, trotation: float, tscale: Vector2): map.global_scale = tscale add_child(map) + ## Replicate the cells of [field target]'s [field map] to the remote [field map]. ## ## The [field map] must be already created. ## ## Only takes layer 0 into consideration. -func build_tilemap_cells() -> void: +func build_tilemap_cells(peer_id: int = 0) -> void: Log.peer(self, "Replicating map cells...") var tmap: TileMap = target.map const layer = 0 @@ -103,8 +103,11 @@ func build_tilemap_cells() -> void: var source_id: int = tmap.get_cell_source_id(0, coords) var atlas_coords: Vector2i = tmap.get_cell_atlas_coords(0, coords) var alternative_tile: int = tmap.get_cell_alternative_tile(0, coords) - rpc_build_tilemap_cell.rpc(layer, coords, source_id, atlas_coords, alternative_tile) - + if peer_id > 0: + rpc_build_tilemap_cell.rpc_id(peer_id, layer, coords, source_id, atlas_coords, alternative_tile) + else: + rpc_build_tilemap_cell.rpc(layer, coords, source_id, atlas_coords, alternative_tile) + ## Create a cell of [field map]. @rpc("authority", "call_local", "reliable") func rpc_build_tilemap_cell( @@ -119,10 +122,13 @@ func rpc_build_tilemap_cell( ## Replicate the [field tee] of the [field target] to the remote [field tee]. -func build_tee() -> void: +func build_tee(peer_id: int = 0) -> void: Log.peer(self, "Replicating tee...") var ttee: GolfTee = target.tee - rpc_build_tee.rpc(ttee.global_position) + if peer_id > 0: + rpc_build_tee.rpc_id(peer_id, ttee.global_position) + else: + rpc_build_tee.rpc(ttee.global_position) ## Create the [GolfTee] object. @rpc("authority", "call_local", "reliable") @@ -135,25 +141,50 @@ func rpc_build_tee(tposition: Vector2): ## Replicate the currently connected players' [GolfBall]s to the remote [field tee]. -func build_balls() -> void: - for playernode in player_dir.get_children(): - rpc_build_ball.rpc(playernode.player_name) +func build_existing_balls(peer_id: int = 0) -> void: + Log.peer(self, "Replicating existing golf balls...") + for tball in tee.get_children(): + if peer_id > 0: + rpc_build_ball.rpc_id(peer_id, tball.name, tball.position, tball.strokes, tball.in_hole) + else: + rpc_build_ball.rpc(tball.name, tball.position, tball.strokes, tball.in_hole) +func build_new_balls(peer_id: int = 0) -> void: + Log.peer(self, "Replicating new golf balls...") + for playernode in player_dir.get_children(): + if peer_id > 0: + rpc_build_ball.rpc_id(peer_id, playernode.player_name, Vector2.ZERO, 0, false) + else: + rpc_build_ball.rpc(playernode.player_name, Vector2.ZERO, 0, false) + +## Create the specified player's [GolfBall] at the remote [field tee]. +func build_new_ball(player_name: String, peer_id: int = 0) -> void: + Log.peer(self, "Replicating new golf ball for: %s" % player_name) + if peer_id > 0: + rpc_build_ball.rpc_id(peer_id, player_name, Vector2.ZERO, 0, false) + else: + rpc_build_ball.rpc(player_name, Vector2.ZERO, 0, false) ## Create and initialize a [GolfBall] for a single [PlayerNode] with the given name. @rpc("authority", "call_local", "reliable") -func rpc_build_ball(player_name: String): - Log.peer(self, "Building tee ball for: %s" % player_name) +func rpc_build_ball(player_name: String, tposition: Vector2, tstrokes: int, thole: bool): var playernode: PlayerNode = player_dir.get_playernode(player_name) - var ball = tee.spawn(playernode) - if playernode.is_multiplayer_authority(): - local_player_spawned.emit(ball) + var ball: GolfBall = tee.get_golfball(playernode) + if ball == null: + Log.peer(self, "Building golf ball for: %s" % player_name) + ball = tee.spawn(playernode, tposition, tstrokes, thole) + else: + Log.peer(self, "Not building duplicate golf ball for: %s" % player_name) + ## Replicate the [field hole] of the [field target] to the remote [field hole]. -func build_hole() -> void: +func build_hole(peer_id: int = 0) -> void: Log.peer(self, "Replicating hole...") var thole: GolfHole = target.hole - rpc_build_hole.rpc(thole.global_position, thole.global_scale) + if peer_id > 0: + rpc_build_hole.rpc_id(peer_id, thole.global_position, thole.global_scale) + else: + rpc_build_hole.rpc(thole.global_position, thole.global_scale) ## Create the [GolfHole] object. @rpc("authority", "call_local", "reliable") @@ -166,10 +197,13 @@ func rpc_build_hole(tposition: Vector2, tscale: Vector2): ## Replicate the [field camera] of the [field target] to the remote [field camera]. -func build_camera() -> void: +func build_camera(peer_id: int = 0) -> void: Log.peer(self, "Replicating camera...") var tcamera: FollowCamera = target.camera - rpc_build_camera.rpc(tcamera.global_position, target.camera_follows_player) + if peer_id > 0: + rpc_build_camera.rpc_id(peer_id, tcamera.global_position, target.camera_follows_player) + else: + rpc_build_camera.rpc(tcamera.global_position, target.camera_follows_player) ## Create the [Camera2D] object. @rpc("authority", "call_local", "reliable") @@ -185,37 +219,40 @@ func rpc_build_camera(tposition: Vector2, tfocus: bool): for child in player_dir.get_children(): var playernode = child as PlayerNode if playernode.is_multiplayer_authority(): - var ctarget = tee.get_node("GolfBall__%s" % child.player_name) + var ctarget = tee.get_node(child.player_name) camera.target = ctarget add_child(camera) func _ready() -> void: - player_dir.child_entered_tree.connect(_on_playernode_created) + player_dir.local_playernode_possessed.connect(_on_local_playernode_possessed) if multiplayer.is_server(): set_physics_process(false) hide() -func _on_playernode_created(node: Node) -> void: - Log.peer(self, "Spawning new player...") - var playernode: PlayerNode = node as PlayerNode - rpc_build_ball.rpc(playernode.player_name) - - func _on_everyone_entered_hole(): + Log.peer(self, "Everyone entered hole, starting end timer...") complete_timer = complete_timer_scene.instantiate() complete_timer.timeout.connect(_on_complete_timer_timeout) add_child(complete_timer) - func _on_complete_timer_timeout(): - complete_timer.queue_free() + Log.peer(self, "End timer has timed out, emitting level_completed signal...") level_completed.emit() + complete_timer.queue_free() + +func _on_local_playernode_possessed(_old: int, new: int, playernode: PlayerNode): + if camera != null: + var ctarget = tee.get_node(playernode.player_name) + camera.target = ctarget + + +## Emitted when it's time to change to the next level. +signal level_completed func _on_tree_exiting() -> void: - player_dir.child_entered_tree.disconnect(_on_playernode_created) if target: target.queue_free() target = null diff --git a/scenes/golf_tee.gd b/scenes/golf_tee.gd index d405186..699cd6b 100644 --- a/scenes/golf_tee.gd +++ b/scenes/golf_tee.gd @@ -10,17 +10,25 @@ signal everyone_entered_hole const ball_scene: PackedScene = preload("res://scenes/golf_ball.tscn") +func get_golfball(playernode: PlayerNode) -> GolfBall: + var nname = playernode.player_name + return get_node_or_null(nname) + + ## Add a new [GolfBall] from [field ball_scene], initialize it with details from the given [PlayerNode], and return it. -func spawn(playernode: PlayerNode) -> GolfBall: +func spawn(playernode: PlayerNode, tposition: Vector2 = Vector2.ZERO, tstrokes: int = 0, thole: bool = false) -> GolfBall: # Create the [GolfBall] var obj: GolfBall = ball_scene.instantiate() # Setup the initial values - obj.position = Vector2.ZERO - obj.name = "GolfBall__%s" % playernode.player_name + obj.position = tposition + obj.name = playernode.player_name + obj.strokes = tstrokes obj.player_name = playernode.player_name obj.player_color = playernode.player_color obj.set_multiplayer_authority(playernode.get_multiplayer_authority()) obj.putt_controller.can_putt = not multiplayer.is_server() and playernode.is_multiplayer_authority() + if thole: + obj.enter_hole() # Create callables to be able to cleanup signals on destruction var on_name_changed: Callable = _on_name_changed.bind(obj) var on_color_changed: Callable = _on_color_changed.bind(obj) @@ -31,9 +39,10 @@ func spawn(playernode: PlayerNode) -> GolfBall: playernode.color_changed.connect(on_color_changed) playernode.possessed.connect(on_possessed) obj.tree_exiting.connect(on_cleanup) + obj.putt_controller.putt.connect(_on_putt_performed.bind(playernode, obj)) obj.entered_hole.connect(_on_entered_hole.bind(playernode)) # Add the golf ball as a child of the tee - add_child(obj) + add_child(obj, true) # Return the created [GolfBall] return obj @@ -51,16 +60,26 @@ func is_everyone_in_hole() -> bool: return true -func _on_name_changed(_old: String, new: String, obj: GolfBall) -> void: - obj.name = "GolfBall__%s" % new +func _on_name_changed(old: String, new: String, obj: GolfBall) -> void: + Log.peer(self, "PlayerNode changed name, updating it on GolfBall: %s → %s" % [old, new]) + obj.name = new obj.player_name = new -func _on_color_changed(_old: Color, new: Color, obj: GolfBall) -> void: +func _on_color_changed(old: Color, new: Color, obj: GolfBall) -> void: + Log.peer(self, "PlayerNode changed color, updating it on GolfBall: %s → %s" % [old, new]) obj.player_color = new -func _on_possessed(_old: int, new: int, obj: GolfBall) -> void: +func _on_possessed(old: int, new: int, obj: GolfBall) -> void: + Log.peer(self, "PlayerNode changed owner, updating it on GolfBall: %d → %d" % [old, new]) obj.set_multiplayer_authority(new) obj.putt_controller.can_putt = is_multiplayer_authority() + if new == 1: + obj.hide() + else: + obj.show() + +func _on_putt_performed(_dir: Vector2, playernode: PlayerNode, obj: GolfBall) -> void: + playernode.putt_performed.emit(obj) func _on_cleanup(playernode: PlayerNode, on_name_changed: Callable, on_color_changed: Callable, on_possessed: Callable) -> void: playernode.name_changed.disconnect(on_name_changed) diff --git a/scenes/level_manager.gd b/scenes/level_manager.gd index 465d651..f7eef15 100644 --- a/scenes/level_manager.gd +++ b/scenes/level_manager.gd @@ -12,23 +12,10 @@ class_name LevelManager @export var player_dir: PlayerNodeDirectory = null -## Emitted when the [GolfBall] for the local player was spawned in a level. -signal local_player_spawned(ball: GolfBall, level: GolfLevel) - -## Emitted when the current level is about to be destroyed. -signal level_destroying(level: GolfLevel) - -## Emitted when the next level has been determined by calling [LevelPlaylist.advance]. -signal level_determined(scene: PackedScene) - -## Emitted when the new level has been added to the tree, but not built. -signal level_created(level: GolfLevel) ## Emitted when a level has been completed. signal level_completed(level: GolfLevel) -## Emitted when all levels in the [field playlist] have been exausted. -signal playlist_complete(playlist: LevelPlaylist) ## The blank [GolfLevel] to initialize on clients. @@ -37,57 +24,139 @@ const base_scene: PackedScene = preload("res://scenes/golf_level.tscn") ## The currently instantiated [GolfLevel]. var level: GolfLevel = null +## The next scene to use as [field target]. +## +## Should be null on everything that's not the server. +var next: PackedScene = null -## Create the empty [GolfLevel] object everywhere. -@rpc("authority", "call_local", "reliable") -func rpc_next_level(): - Log.peer(self, "Advancinng to the next level!") - # Destroy the current level - if level != null: - Log.peer(self, "Destroying the current level: %s" % level) - level_destroying.emit(level) - level.queue_free() - level = null - # Determine the next level +## The currently instantiated target [GolfLevel]. +## +## Should be null on everything that's not the server. +var target: GolfLevel = null + + +## Destroy the current [field level]. +func destroy_level() -> void: + Log.peer(self, "Emitting level_destroying signal...") + level_destroying.emit(level) + + Log.peer(self, "Queueing the free of the level and removing references...") + remove_child(level) + level.queue_free() + level = null + + Log.peer(self, "Emitting level_destroyed signal...") + level_destroyed.emit() + +## Emitted when the current level is about to be destroyed. +signal level_destroying(level: GolfLevel) + +## Emitted when the current level has been destroyed. +signal level_destroyed + + +## Advance the [field playlist] and return the next level to play. +func determine_next_level() -> void: Log.peer(self, "Determining the next level...") - var next = playlist.advance() + next = playlist.advance() + Log.peer(self, "Determined the next level: %s" % next) level_determined.emit(next) - ## Make sure the playlist hasn't been exausted + if next == null: - Log.peer(self, "Playlist is complete, not doing anything.") + Log.peer(self, "Playlist is complete.") playlist_complete.emit(playlist) - return - # Create the new level - Log.peer(self, "Instantiating empty level template...") + +## Emitted when the next level has been determined by calling [LevelPlaylist.advance]. +signal level_determined(scene: PackedScene) + +## Emitted when all levels in the [field playlist] have been exausted. +signal playlist_complete(playlist: LevelPlaylist) + + +## Instantiate and setup a copy of the [field base_scene]. +func initialize_level() -> void: + Log.peer(self, "Instantiating level template...") level = base_scene.instantiate() - Log.peer(self, "Instantiated empty level template: %s" % level) - # Configure the new level + Log.peer(self, "Configuring level variables...") level.player_dir = player_dir - level.local_player_spawned.connect(_on_local_player_spawned) + + Log.peer(self, "Connecting level signals...") level.level_completed.connect(_on_level_completed) - if multiplayer.is_server(): - Log.peer(self, "Instantiating the target level scene...") - level.target = next.instantiate() - Log.peer(self, "Instantiated the target level scene: %s" % level.target) - # Add the level to the tree - Log.peer(self, "Adding level to the tree...") + + Log.peer(self, "Adding level to the scene tree...") add_child(level, true) - level_created.emit(level) - # Start the replication - if multiplayer.is_server(): - Log.peer(self, "Building level...") - level.build() + + Log.peer(self, "Emitting level_initialized signal...") + level_initialized.emit(level) + +## Emitted when a copy of [field base_scene] has been initialized by [method initialize_level]. +signal level_initialized(level: GolfLevel) + + +## Instantiate a copy of the given target [GolfLevel]. +func init_target() -> void: + Log.peer(self, "Instantiating level targets...") + level.target = next.instantiate() + + Log.peer(self, "Emitting target_initialized signal...") + target_initialized.emit(level.target) + +## Emitted when a copy of the target level has been initialized by [method init_target]. +signal target_initialized(level: GolfLevel) + + +## Replicate the [method target] onto the [method level] of all currently connected clients if no peer_id is specified, or to only that client if a peer_id is specified. +func build_level(peer_id: int = 0) -> void: + Log.peer(self, "Building level...") + level.build(peer_id) + + Log.peer(self, "Emitting level_built signal...") + level_built.emit(level) + +## Emitted on the server when it has finished sending out RPCs for the [method GolfLevel.build] of the [field target] level. +signal level_built(level: GolfLevel) + + +## Send the current level to a given client. +func sync_level(peer_id: int = 0) -> void: + Log.peer(self, "Repeating the current level to: %d" % peer_id) + if level: + if peer_id > 0: + rpc_wipe_level.rpc_id(peer_id) + else: + rpc_wipe_level.rpc() + build_level(peer_id) + + +## Add a new player to the current level. +func add_player(playernode: PlayerNode): + Log.peer(self, "Adding a new player to the level in progress...") + if level != null: + level.build_new_ball(playernode.player_name) + + +## Advance to the next level. +func next_level() -> void: + Log.peer(self, "Advancing to the next level...") + determine_next_level() + if next != null: + rpc_wipe_level.rpc() + init_target() + build_level() + + +## Clear the current level on all clients and prepare to build a new one. +@rpc("authority", "call_local", "reliable") +func rpc_wipe_level(): + if level != null: + destroy_level() + initialize_level() func _on_level_completed() -> void: Log.peer(self, "Level completed!") level_completed.emit(level) if is_multiplayer_authority(): - rpc_next_level.rpc() - - -func _on_local_player_spawned(ball: GolfBall) -> void: - Log.peer(self, "Local player spawned: %s" % ball) - local_player_spawned.emit(ball, level) \ No newline at end of file + next_level() diff --git a/scenes/main.gd b/scenes/main.gd index 74f9d1e..d188af1 100644 --- a/scenes/main.gd +++ b/scenes/main.gd @@ -44,7 +44,7 @@ func init_server_game(server_port: int) -> void: scene_tree.set_multiplayer(smp, ^"/root/Main/Server") server_game_instance.init_signals() - server_game_instance.leave_confirmed.connect(_on_leave_confirmed) + server_game_instance.lost_connection.connect(_on_lost_connection) smp.set_multiplayer_peer(peer) func deinit_server_game() -> void: @@ -71,7 +71,7 @@ func init_client_game(player_name: String, player_color: Color, server_address: client_game_instance.local_player_name = player_name client_game_instance.local_player_color = player_color client_game_instance.phase_tracker.phase_changed.connect(_on_phase_changed) - client_game_instance.leave_confirmed.connect(_on_leave_confirmed) + client_game_instance.lost_connection.connect(_on_lost_connection) smp.set_multiplayer_peer(peer) func deinit_client_game() -> void: @@ -83,11 +83,11 @@ func deinit_client_game() -> void: func init_lobby_menu() -> void: lobby_menu_instance = lobby_menu_scene.instantiate() lobby_menu_instance.init_refs() - lobby_menu_instance.leave_confirmed.connect(_on_leave_confirmed) + lobby_menu_instance.leave_confirmed.connect(_on_lost_connection) if server_game_instance: lobby_menu_instance.start_button.disabled = false lobby_menu_instance.start_confirmed.connect(server_game_instance._on_main_start_confirmed) - client_game_instance.multiplayer.server_disconnected.connect(_on_leave_confirmed) + client_game_instance.multiplayer.server_disconnected.connect(_on_lost_connection) 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) @@ -96,10 +96,10 @@ func init_lobby_menu() -> void: interface_instance.add_child(lobby_menu_instance) func deinit_lobby_menu() -> void: - lobby_menu_instance.leave_confirmed.disconnect(_on_leave_confirmed) + lobby_menu_instance.leave_confirmed.disconnect(_on_lost_connection) if server_game_instance: lobby_menu_instance.start_confirmed.disconnect(server_game_instance._on_main_start_confirmed) - client_game_instance.multiplayer.server_disconnected.disconnect(_on_leave_confirmed) + client_game_instance.multiplayer.server_disconnected.disconnect(_on_lost_connection) 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) @@ -111,22 +111,22 @@ func deinit_lobby_menu() -> void: func init_game_hud() -> void: game_hud_instance = game_hud_scene.instantiate() client_game_instance.level_manager.level_completed.connect(game_hud_instance._on_level_completed) - client_game_instance.level_manager.local_player_spawned.connect(game_hud_instance._on_local_player_spawned) client_game_instance.player_dir.playernode_name_changed.connect(game_hud_instance._on_playernode_name_changed) client_game_instance.player_dir.playernode_color_changed.connect(game_hud_instance._on_playernode_color_changed) client_game_instance.player_dir.playernode_possessed.connect(game_hud_instance._on_playernode_possessed) client_game_instance.player_dir.playernode_score_reported.connect(game_hud_instance._on_playernode_score_reported) client_game_instance.player_dir.playernode_scores_changed.connect(game_hud_instance._on_playernode_scores_changed) + client_game_instance.player_dir.playernode_putt_performed.connect(game_hud_instance._on_playernode_putt_performed) interface_instance.add_child(game_hud_instance) func deinit_game_hud() -> void: client_game_instance.level_manager.level_completed.disconnect(game_hud_instance._on_level_completed) - client_game_instance.level_manager.local_player_spawned.disconnect(game_hud_instance._on_local_player_spawned) client_game_instance.player_dir.playernode_name_changed.disconnect(game_hud_instance._on_playernode_name_changed) client_game_instance.player_dir.playernode_color_changed.disconnect(game_hud_instance._on_playernode_color_changed) client_game_instance.player_dir.playernode_possessed.disconnect(game_hud_instance._on_playernode_possessed) client_game_instance.player_dir.playernode_score_reported.disconnect(game_hud_instance._on_playernode_score_reported) client_game_instance.player_dir.playernode_scores_changed.disconnect(game_hud_instance._on_playernode_scores_changed) + client_game_instance.player_dir.playernode_putt_performed.disconnect(game_hud_instance._on_playernode_putt_performed) game_hud_instance.queue_free() game_hud_instance = null @@ -144,7 +144,7 @@ func _on_connecting_confirmed(player_name: String, player_color: Color, server_a init_client_game(player_name, player_color, server_address, server_port) init_lobby_menu() -func _on_leave_confirmed() -> void: +func _on_lost_connection() -> void: if lobby_menu_instance != null: deinit_lobby_menu() if game_hud_instance != null: diff --git a/scenes/peernode_directory.gd b/scenes/peernode_directory.gd index 9f4b2b6..f112208 100644 --- a/scenes/peernode_directory.gd +++ b/scenes/peernode_directory.gd @@ -37,6 +37,14 @@ func rpc_destroy_peernode(peer_id: int): peernode.queue_free() +func _on_peernode_created(peernode: PeerNode) -> void: + if peernode.is_multiplayer_authority(): + local_peernode_created.emit(peernode) + +## Emitted on a client when the [PeerNode] for itself has been created. +signal local_peernode_created(peernode: PeerNode) + + ## Called on the server when a [PeerNode] calls [method rpc_identify] for itself. func _on_peernode_identified(player_name: String, peernode: PeerNode): peernode_identified.emit(player_name, peernode) diff --git a/scenes/peernode_directory.tscn b/scenes/peernode_directory.tscn index 37d8376..34883fa 100644 --- a/scenes/peernode_directory.tscn +++ b/scenes/peernode_directory.tscn @@ -4,3 +4,5 @@ [node name="PeerNodeDirectory" type="Node"] script = ExtResource("1_mc47a") + +[connection signal="child_entered_tree" from="." to="." method="_on_peernode_created"] diff --git a/scenes/playernode.gd b/scenes/playernode.gd index 234beef..8716ea9 100644 --- a/scenes/playernode.gd +++ b/scenes/playernode.gd @@ -106,3 +106,8 @@ signal score_reported(strokes: int) ## Emitted when the hole scores have changed because of [method rpc_set_scores]. signal scores_changed(old: Array, new: Array) + +## Emitted when a stroke has been performed on this player's [GolfBall]. +## +## Currently tracked only for the local ball. +signal putt_performed(ball: GolfBall) diff --git a/scenes/playernode_directory.gd b/scenes/playernode_directory.gd index ef66c3c..b80f178 100644 --- a/scenes/playernode_directory.gd +++ b/scenes/playernode_directory.gd @@ -28,6 +28,7 @@ func rpc_possess_playernode(player_name: String, peer_id: int): playernode.possessed.connect(_on_playernode_possessed.bind(playernode)) playernode.score_reported.connect(_on_playernode_score_reported.bind(playernode)) playernode.scores_changed.connect(_on_playernode_scores_changed.bind(playernode)) + playernode.putt_performed.connect(_on_playernode_putt_performed.bind(playernode)) var sanitized_player_name = PlayerNode.sanitize_player_name(player_name) playernode.player_name = sanitized_player_name playernode.name = sanitized_player_name @@ -45,6 +46,8 @@ func _on_playernode_color_changed(old: Color, new: Color, playernode: PlayerNode func _on_playernode_possessed(old: int, new: int, playernode: PlayerNode) -> void: playernode_possessed.emit(old, new, playernode) + if playernode.is_multiplayer_authority() and not multiplayer.is_server(): + local_playernode_possessed.emit(old, new, playernode) func _on_playernode_score_reported(strokes: int, playernode: PlayerNode) -> void: playernode_score_reported.emit(strokes, playernode) @@ -52,6 +55,9 @@ func _on_playernode_score_reported(strokes: int, playernode: PlayerNode) -> void func _on_playernode_scores_changed(old: Array, new: Array, playernode: PlayerNode) -> void: playernode_scores_changed.emit(old, new, playernode) +func _on_playernode_putt_performed(ball: GolfBall, playernode: PlayerNode) -> void: + playernode_putt_performed.emit(ball, 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) @@ -62,8 +68,14 @@ signal playernode_color_changed(old: Color, new: Color, playernode: PlayerNode) ## Emitted everywhere when one of the children [PlayerNode]s has changed multiplayer authority. signal playernode_possessed(old: int, new: int, playernode: PlayerNode) +## Emitted on a client when it becomes authority of a [PlayerNode]. +signal local_playernode_possessed(old: int, new: int, playernode: PlayerNode) + ## Emitted when a [PlayerNode] reports a score. signal playernode_score_reported(strokes: int, playernode: PlayerNode) ## Emitted when the scores of one of the children [PlayerNode]s change on the local scene. signal playernode_scores_changed(old: Array, new: Array, playernode: PlayerNode) + +## Emitted when a [PlayerNode] performs a putt on its controlled [GolfBall]. +signal playernode_putt_performed(ball: GolfBall, playernode: PlayerNode)