From 390db61bb692559da9c1e017347015b50cacd82f Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 5 Dec 2024 19:39:07 +0100 Subject: [PATCH] Multiplayer: Finalized matchmaking system. --- project.godot | 11 +++- scenes/ui/elements/name_label.tscn | 10 ++++ scenes/ui/lobby.tscn | 20 +++++++ scripts/ui/lobby.gd | 16 +++++ scripts/utils/game.gd | 8 ++- scripts/utils/networking.gd | 95 +++++++++++++++++++++++++++--- start.tscn | 20 ++++++- 7 files changed, 168 insertions(+), 12 deletions(-) create mode 100644 scenes/ui/elements/name_label.tscn create mode 100644 scenes/ui/lobby.tscn create mode 100644 scripts/ui/lobby.gd diff --git a/project.godot b/project.godot index 1524ec6..72c2865 100644 --- a/project.godot +++ b/project.godot @@ -66,6 +66,13 @@ gdscript/warnings/property_used_as_function=2 gdscript/warnings/constant_used_as_function=2 gdscript/warnings/function_used_as_property=2 -[editor] +[display] -run/main_run_args="--client" +window/size/viewport_width=800 +window/size/viewport_height=600 +window/stretch/mode="canvas_items" +window/stretch/aspect="expand" + +[rendering] + +environment/defaults/default_clear_color=Color(0, 0, 0, 1) diff --git a/scenes/ui/elements/name_label.tscn b/scenes/ui/elements/name_label.tscn new file mode 100644 index 0000000..095c8ae --- /dev/null +++ b/scenes/ui/elements/name_label.tscn @@ -0,0 +1,10 @@ +[gd_scene format=3 uid="uid://ormc7s6yhyck"] + +[node name="NameLabel" type="Label"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +text = "SamplePlayer" +vertical_alignment = 1 diff --git a/scenes/ui/lobby.tscn b/scenes/ui/lobby.tscn new file mode 100644 index 0000000..fa09c10 --- /dev/null +++ b/scenes/ui/lobby.tscn @@ -0,0 +1,20 @@ +[gd_scene load_steps=2 format=3 uid="uid://dq4exc1bdyahe"] + +[ext_resource type="Script" path="res://scripts/ui/lobby.gd" id="1_gd585"] + +[node name="Lobby" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_gd585") + +[node name="NameContainer" type="VBoxContainer" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 diff --git a/scripts/ui/lobby.gd b/scripts/ui/lobby.gd new file mode 100644 index 0000000..a801d88 --- /dev/null +++ b/scripts/ui/lobby.gd @@ -0,0 +1,16 @@ +extends Control + +var label: PackedScene = preload("res://scenes/ui/elements/name_label.tscn") + +func _ready() -> void: + if Networking.playerlist_changed.connect(_on_playerlist_changed) != OK: + Log.error("FAILED to connect the playerlist_changed signal to _on_playerlist_changed!", "Internal Error: Failed to connect signal.") + _on_playerlist_changed() + +func _on_playerlist_changed() -> void: + for child: Label in $NameContainer.get_children(): + child.free() + for peer: String in Networking.peers: + var instance: Label = label.instantiate() + instance.text = peer + $NameContainer.add_child(instance) diff --git a/scripts/utils/game.gd b/scripts/utils/game.gd index 179841b..52b9e5c 100644 --- a/scripts/utils/game.gd +++ b/scripts/utils/game.gd @@ -6,7 +6,8 @@ var ip: String = "127.0.0.1" var port: int = 25262 var max_clients: int = 1024 -var room_name: String = "sample" +var roomname: String = "sample" +var username: String = "sample" func _ready() -> void: var args: PackedStringArray = OS.get_cmdline_args() @@ -18,6 +19,9 @@ func _ready() -> void: "--logmode": logmode = int(args[i+1]) skip = true + "--username": + username = args[i+1] + skip = true "--server": if Networking.start_server() != OK: Log.error("Failed to start server! Closing application.", "Failed to start server!") @@ -27,3 +31,5 @@ func _ready() -> void: pass # Catch in order to prevent a warning. _: Log.warning("Unknown argument: %s!" % args[i]) + else: + skip = false diff --git a/scripts/utils/networking.gd b/scripts/utils/networking.gd index a155916..d0265ea 100644 --- a/scripts/utils/networking.gd +++ b/scripts/utils/networking.gd @@ -1,5 +1,7 @@ extends Node +signal playerlist_changed() + ## Connection Code # Connect the signals. @@ -12,6 +14,11 @@ func _ready() -> void: Log.error("FAILED to connect the server_disconnected signal to _on_server_disconnected!", "Internal Error: Failed to connect signal.") if multiplayer.peer_disconnected.connect(_on_peer_disconnected) != OK: Log.error("FAILED to connect the peer_disconnected signal to _on_peer_disconnected!", "Internal Error: Failed to connect signal.") + if playerlist_changed.connect(_on_playerlist_changed) != OK: + Log.error("FAILED to connect the playerlist_changed signal to _on_playerlist_changed!", "Internal Error: Failed to connect signal.") + +func _on_playerlist_changed() -> void: + pass # Start the network listener. func start_server() -> Error: @@ -42,14 +49,17 @@ func close_network() -> void: var mutex: Mutex = Mutex.new() var rooms: Dictionary = {} +# peers has two different uses: On the server it is an index of all rooms with their peers, while on the client it is just a lookup table for peers and their usernames. var peers: Dictionary = {} +var isManager: bool = false +var managerID: int func join_room() -> void: if join_server() != OK: Log.warning("Failed to connect to sever.") func _on_connected_ok() -> void: - if rpc_id(1, "send_roomname", Game.room_name) != OK: + if rpc_id(1, "send_roomname", Game.roomname) != OK: Log.warning("Failed to send roomname!") func _on_peer_disconnected(id: int) -> void: @@ -91,19 +101,19 @@ func send_roomname(roomname: String) -> void: rooms[multiplayer.get_remote_sender_id()] = roomname peers[roomname] = [multiplayer.get_remote_sender_id()] Log.info("Room %s registered." % roomname) - if rpc_id(multiplayer.get_remote_sender_id(), "server_response", 0) != OK: + if rpc_id(multiplayer.get_remote_sender_id(), "server_response", 0, rooms[roomname]) != OK: Log.warning("Failed to send elevation reply to %d!") elif peers[roomname] is Array: # For some odd reason, Godot doesn't allow casting peers[roomname] to an Array, but this works? var peer_array: Array = peers[roomname] peer_array.append(multiplayer.get_remote_sender_id()) peers[roomname] = peer_array - if rpc_id(multiplayer.get_remote_sender_id(), "server_response", 1) != OK: + if rpc_id(multiplayer.get_remote_sender_id(), "server_response", 1, rooms[roomname]) != OK: Log.warning("Failed to send join reply to %d!") Log.debug("Room %s joined by peer %d." % [roomname, multiplayer.get_remote_sender_id()]) else: Log.warning("Peer %d tried to send this client a request meant for the authority!" % multiplayer.get_remote_sender_id()) - if rpc_id(multiplayer.get_remote_sender_id(), "server_response", 2) != OK: + if rpc_id(multiplayer.get_remote_sender_id(), "server_response", 2, 0) != OK: Log.warning("Failed to send fail reply to %d!") mutex.unlock() @@ -119,11 +129,80 @@ func peer_disconnected_message() -> void: # This is the initial server response, which tells the client what its role is. @rpc("authority", "call_remote", "reliable") -func server_response(status: int) -> void: +func server_response(status: int, manager: int) -> void: match status: 0: - pass # TODO: makes new room manager + # create new room host + isManager = true + if get_tree().change_scene_to_file("res://scenes/ui/lobby.tscn") != OK: + Log.error("Couldn't change to the lobby scene! Closing application.", "Couldn't change to the lobby scene!") + peers[Game.username] = manager + managerID = manager 1: - pass # TODO: makes new room member + # create new room member + managerID = manager + if rpc_id(managerID, "request_playerlist", Game.username) != OK: + Log.warning("Failed to send username to room host %d." % multiplayer.get_remote_sender_id()) 2: - pass # TODO: server error + Log.warning("Sent a join request to a regular peer, not the server!") + +# This informs the server of the recently joined player. +@rpc("any_peer", "call_remote", "reliable") +func inform_server(roomname: String) -> void: + mutex.lock() + if multiplayer.get_unique_id() == 1: + if multiplayer.get_remote_sender_id() == rooms[roomname]: + var peer_array: Array = peers[roomname] + peer_array.append(multiplayer.get_remote_sender_id()) + peers[roomname] = peer_array + else: + Log.warning("Peer %d attempted to trick the server into adding it to the room!" % multiplayer.get_remote_sender_id()) + else: + Log.warning("Peer %d tried to send this client a request meant for the authority!" % multiplayer.get_remote_sender_id()) + mutex.unlock() + +# Get the playerlist from the room host. +@rpc("any_peer", "call_remote", "reliable") +func request_playerlist(peername: String) -> void: + if isManager: + if !peers.has(peername): + peers[peername] = multiplayer.get_remote_sender_id() + if rpc_id(multiplayer.get_remote_sender_id(), "send_playerlist", 0, peers) != OK: + Log.warning("Failed to send join status to %d." % multiplayer.get_remote_sender_id()) + if rpc_id(1, "inform_server", Game.roomname) != OK: + Log.warning("Failed to send playerinfo to server.") + for peer: String in peers: + var remote_id: int = peers[peer] + if remote_id != multiplayer.get_unique_id(): + if rpc_id(remote_id, "send_playerlist", 1, peers) != OK: + Log.warning("Failed to send playerlist to %d." % multiplayer.get_remote_sender_id()) + if emit_signal("playerlist_changed") != OK: + Log.warning("Couldn't emit playerlist_changed signal.") + else: + if rpc_id(multiplayer.get_remote_sender_id(), "send_playerlist", 2, {}) != OK: + Log.warning("Couldn't send failure response to peer %d!" % multiplayer.get_remote_sender_id()) + Log.warning("Peer %d tried to register with name %s, which already exists!" % [multiplayer.get_remote_sender_id(), peername]) + else: + if rpc_id(multiplayer.get_remote_sender_id(), "send_playerlist", 3, {}) != OK: + Log.warning("Couldn't send failure response to peer %d!" % multiplayer.get_remote_sender_id()) + Log.warning("Peer %d tried to send this client a request meant for the room host!" % multiplayer.get_remote_sender_id()) + +# Recive the playerlist on the client. +@rpc("any_peer", "call_remote", "reliable") +func send_playerlist(status: int, newPeers: Dictionary) -> void: + if multiplayer.get_remote_sender_id() == managerID: + match status: + 0: + peers = newPeers + Log.info("Room %s successfully joined." % Game.roomname) + if get_tree().change_scene_to_file("res://scenes/ui/lobby.tscn") != OK: + Log.error("Couldn't change to the lobby scene! Closing application.", "Couldn't change to the lobby scene!") + 1: + peers = newPeers + Log.debug("Playerlist recived.") + if emit_signal("playerlist_changed") != OK: + Log.warning("Couldn't emit playerlist_changed signal.") + 2: + Log.warning("Name %s is already taken in the room!" % Game.username) + 3: + Log.warning("Sent a playerlist request to a regular peer, not the host!") diff --git a/start.tscn b/start.tscn index 911b56d..acbe8a3 100644 --- a/start.tscn +++ b/start.tscn @@ -1,3 +1,21 @@ [gd_scene format=3 uid="uid://dhi34ugxtgmlg"] -[node name="Start" type="Node2D"] +[node name="Start" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="LoadingText" type="Label" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_font_sizes/font_size = 64 +text = "Loading, Please Wait . . ." +horizontal_alignment = 1 +vertical_alignment = 1