From 9922f8a48e9817d1973c587c8ac9c733b965fcf5 Mon Sep 17 00:00:00 2001 From: denizk0461 Date: Sat, 28 Jun 2025 15:19:04 +0200 Subject: [PATCH] Initial push --- .editorconfig | 4 + .gitattributes | 2 + .gitignore | 3 + client_interface.gd | 2 + client_interface.gd.uid | 1 + export_presets.cfg | 41 +++++++++ game.tscn | 79 ++++++++++++++++ icon.svg | 1 + icon.svg.import | 37 ++++++++ project.godot | 30 +++++++ rpc_handler.tscn | 14 +++ screens/client.tscn | 60 +++++++++++++ screens/server.gd | 25 ++++++ screens/server.gd.uid | 1 + screens/server.tscn | 25 ++++++ screens/test.gd | 10 +++ screens/test.gd.uid | 1 + screens/test.tscn | 60 +++++++++++++ server.py | 194 ++++++++++++++++++++++++++++++++++++++++ web_client.gd | 169 ++++++++++++++++++++++++++++++++++ web_client.gd.uid | 1 + 21 files changed, 760 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 client_interface.gd create mode 100644 client_interface.gd.uid create mode 100644 export_presets.cfg create mode 100644 game.tscn create mode 100644 icon.svg create mode 100644 icon.svg.import create mode 100644 project.godot create mode 100644 rpc_handler.tscn create mode 100644 screens/client.tscn create mode 100644 screens/server.gd create mode 100644 screens/server.gd.uid create mode 100644 screens/server.tscn create mode 100644 screens/test.gd create mode 100644 screens/test.gd.uid create mode 100644 screens/test.tscn create mode 100644 server.py create mode 100644 web_client.gd create mode 100644 web_client.gd.uid diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..f28239b --- /dev/null +++ b/.editorconfig @@ -0,0 +1,4 @@ +root = true + +[*] +charset = utf-8 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..8ad74f7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Normalize EOL for all files that Git considers text files. +* text=auto eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0af181c --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +# Godot 4+ specific ignores +.godot/ +/android/ diff --git a/client_interface.gd b/client_interface.gd new file mode 100644 index 0000000..4771f29 --- /dev/null +++ b/client_interface.gd @@ -0,0 +1,2 @@ +class_name ClientInterface +extends Control diff --git a/client_interface.gd.uid b/client_interface.gd.uid new file mode 100644 index 0000000..85b70cf --- /dev/null +++ b/client_interface.gd.uid @@ -0,0 +1 @@ +uid://c0cqkatyo4uj1 diff --git a/export_presets.cfg b/export_presets.cfg new file mode 100644 index 0000000..11173a0 --- /dev/null +++ b/export_presets.cfg @@ -0,0 +1,41 @@ +[preset.0] + +name="Linux" +platform="Linux" +runnable=true +advanced_options=false +dedicated_server=false +custom_features="" +export_filter="all_resources" +include_filter="" +exclude_filter="" +export_path="../00 Exports/magician/magician_server.x86_64" +patches=PackedStringArray() +encryption_include_filters="" +encryption_exclude_filters="" +seed=0 +encrypt_pck=false +encrypt_directory=false +script_export_mode=2 + +[preset.0.options] + +custom_template/debug="" +custom_template/release="" +debug/export_console_wrapper=1 +binary_format/embed_pck=false +texture_format/s3tc_bptc=true +texture_format/etc2_astc=false +binary_format/architecture="x86_64" +ssh_remote_deploy/enabled=false +ssh_remote_deploy/host="fish" +ssh_remote_deploy/port="22" +ssh_remote_deploy/extra_args_ssh="" +ssh_remote_deploy/extra_args_scp="" +ssh_remote_deploy/run_script="#!/usr/bin/env bash +export DISPLAY=:0 +unzip -o -q \"{temp_dir}/{archive_name}\" -d \"{temp_dir}\" +\"{temp_dir}/{exe_name}\" {cmd_args}" +ssh_remote_deploy/cleanup_script="#!/usr/bin/env bash +kill $(pgrep -x -f \"{temp_dir}/{exe_name} {cmd_args}\") +rm -rf \"{temp_dir}\"" diff --git a/game.tscn b/game.tscn new file mode 100644 index 0000000..32002b7 --- /dev/null +++ b/game.tscn @@ -0,0 +1,79 @@ +[gd_scene load_steps=4 format=3 uid="uid://gqrdpjslr4np"] + +[ext_resource type="Script" uid="uid://bfaxxdr7v0emr" path="res://web_client.gd" id="1_80nbo"] +[ext_resource type="Script" uid="uid://c0cqkatyo4uj1" path="res://client_interface.gd" id="2_e2o6t"] + +[sub_resource type="SystemFont" id="SystemFont_80nbo"] +font_names = PackedStringArray("IBM Plex Mono") +font_weight = 500 + +[node name="Game" type="Node"] + +[node name="WebClient" type="Node" parent="." node_paths=PackedStringArray("name_input", "session_code_input", "output", "create_lobby_button", "join_lobby_button", "disconnect_lobby_button")] +script = ExtResource("1_80nbo") +name_input = NodePath("../ClientInterface/HBoxContainer/VBoxContainer/NameInput") +session_code_input = NodePath("../ClientInterface/HBoxContainer/VBoxContainer/SessionCodeInput") +output = NodePath("../ClientInterface/HBoxContainer/Output") +create_lobby_button = NodePath("../ClientInterface/HBoxContainer/VBoxContainer/CreateLobbyButton") +join_lobby_button = NodePath("../ClientInterface/HBoxContainer/VBoxContainer/JoinLobbyButton") +disconnect_lobby_button = NodePath("../ClientInterface/HBoxContainer/VBoxContainer/DisconnectLobbyButton") + +[node name="ClientInterface" type="Control" parent="."] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("2_e2o6t") + +[node name="ColorRect" type="ColorRect" parent="ClientInterface"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +color = Color(0.34, 0.33116, 0.2992, 1) + +[node name="HBoxContainer" type="HBoxContainer" parent="ClientInterface"] +layout_mode = 0 +offset_right = 800.0 +offset_bottom = 1080.0 + +[node name="VBoxContainer" type="VBoxContainer" parent="ClientInterface/HBoxContainer"] +custom_minimum_size = Vector2(300, 0) +layout_mode = 2 + +[node name="NameInput" type="LineEdit" parent="ClientInterface/HBoxContainer/VBoxContainer"] +layout_mode = 2 +placeholder_text = "Your Name" + +[node name="CreateLobbyButton" type="Button" parent="ClientInterface/HBoxContainer/VBoxContainer"] +layout_mode = 2 +text = "Create Lobby" + +[node name="SessionCodeInput" type="LineEdit" parent="ClientInterface/HBoxContainer/VBoxContainer"] +layout_mode = 2 +placeholder_text = "Lobby Code" + +[node name="JoinLobbyButton" type="Button" parent="ClientInterface/HBoxContainer/VBoxContainer"] +layout_mode = 2 +text = "Join Lobby" + +[node name="DisconnectLobbyButton" type="Button" parent="ClientInterface/HBoxContainer/VBoxContainer"] +layout_mode = 2 +disabled = true +text = "Disconnect" + +[node name="Output" type="Label" parent="ClientInterface/HBoxContainer"] +custom_minimum_size = Vector2(64, 1) +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 1 +theme_override_fonts/font = SubResource("SystemFont_80nbo") +autowrap_mode = 2 + +[connection signal="pressed" from="ClientInterface/HBoxContainer/VBoxContainer/CreateLobbyButton" to="WebClient" method="_on_create_lobby_button_pressed"] +[connection signal="pressed" from="ClientInterface/HBoxContainer/VBoxContainer/JoinLobbyButton" to="WebClient" method="_on_join_lobby_button_pressed"] +[connection signal="pressed" from="ClientInterface/HBoxContainer/VBoxContainer/DisconnectLobbyButton" to="WebClient" method="_on_disconnect_lobby_button_pressed"] diff --git a/icon.svg b/icon.svg new file mode 100644 index 0000000..9d8b7fa --- /dev/null +++ b/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icon.svg.import b/icon.svg.import new file mode 100644 index 0000000..ca23bce --- /dev/null +++ b/icon.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dtu0pfws0gha4" +path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icon.svg" +dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/project.godot b/project.godot new file mode 100644 index 0000000..dd1a48c --- /dev/null +++ b/project.godot @@ -0,0 +1,30 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=5 + +[application] + +config/name="magician" +run/main_scene="uid://gqrdpjslr4np" +config/features=PackedStringArray("4.4", "GL Compatibility") +config/icon="res://icon.svg" + +[autoload] + +RPCHandler="*res://rpc_handler.tscn" + +[display] + +window/size/viewport_width=1920 +window/size/viewport_height=1080 + +[rendering] + +renderer/rendering_method="gl_compatibility" +renderer/rendering_method.mobile="gl_compatibility" diff --git a/rpc_handler.tscn b/rpc_handler.tscn new file mode 100644 index 0000000..d9b12d5 --- /dev/null +++ b/rpc_handler.tscn @@ -0,0 +1,14 @@ +[gd_scene load_steps=2 format=3 uid="uid://dd3obeotog37w"] + +[sub_resource type="GDScript" id="GDScript_flq8k"] +script/source = "extends Node + +@rpc(\"any_peer\", \"call_local\", \"reliable\") +func ring_bell(id: int) -> void: + $BellLabel.text = str(id) + \" has rung the bell!\" + await get_tree().create_timer(1.0).timeout + $BellLabel.text = \"\" +" + +[node name="RPCHandler" type="Node"] +script = SubResource("GDScript_flq8k") diff --git a/screens/client.tscn b/screens/client.tscn new file mode 100644 index 0000000..d188370 --- /dev/null +++ b/screens/client.tscn @@ -0,0 +1,60 @@ +[gd_scene load_steps=2 format=3 uid="uid://cgffw8lmsypov"] + +[ext_resource type="Script" uid="uid://bfaxxdr7v0emr" path="res://web_client.gd" id="1_eergy"] + +[node name="Client" type="Control" node_paths=PackedStringArray("name_input", "session_code_input")] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_eergy") +name_input = NodePath("VBoxContainer/NameInput") +session_code_input = NodePath("VBoxContainer/SessionCodeInput") + +[node name="Label" type="Label" parent="."] +layout_mode = 0 +offset_left = 466.0 +offset_top = 312.0 +offset_right = 609.0 +offset_bottom = 335.0 +text = "I WILL CLIENT YOU" + +[node name="BellLabel" type="Label" parent="."] +layout_mode = 0 +offset_left = 620.0 +offset_top = 658.0 +offset_right = 1285.0 +offset_bottom = 736.0 + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -250.0 +offset_top = -80.0 +offset_right = 250.0 +offset_bottom = 80.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="NameInput" type="LineEdit" parent="VBoxContainer"] +layout_mode = 2 + +[node name="CreateSessionButton" type="Button" parent="VBoxContainer"] +layout_mode = 2 +text = "Create" + +[node name="SessionCodeInput" type="LineEdit" parent="VBoxContainer"] +layout_mode = 2 + +[node name="ConnectButton" type="Button" parent="VBoxContainer"] +layout_mode = 2 +text = "Connect" + +[connection signal="pressed" from="VBoxContainer/CreateSessionButton" to="." method="_on_create_session_button_pressed"] +[connection signal="pressed" from="VBoxContainer/ConnectButton" to="." method="_on_connect_button_pressed"] diff --git a/screens/server.gd b/screens/server.gd new file mode 100644 index 0000000..e0372ad --- /dev/null +++ b/screens/server.gd @@ -0,0 +1,25 @@ +class_name Server +extends Control + +var _peer: ENetMultiplayerPeer = ENetMultiplayerPeer.new() +var _port: int = 9974 + +@export var text_output: Label + +func _ready() -> void: + multiplayer.peer_connected.connect(_on_peer_connected) + + _write_line("me server, you jane.") + _open_server() + +func _open_server() -> void: + _peer.create_server(_port) + get_tree().get_multiplayer().multiplayer_peer = _peer + _write_line("created server at port: {port}".format({"port": _port})) + +func _write_line(str: String) -> void: + #text_output.text += str + "\n" + print(str) + +func _on_peer_connected(id: int) -> void: + _write_line("peer connected: {peer_id}".format({"peer_id": id})) diff --git a/screens/server.gd.uid b/screens/server.gd.uid new file mode 100644 index 0000000..4545c58 --- /dev/null +++ b/screens/server.gd.uid @@ -0,0 +1 @@ +uid://bbrx5vsfnm8c8 diff --git a/screens/server.tscn b/screens/server.tscn new file mode 100644 index 0000000..522a3d7 --- /dev/null +++ b/screens/server.tscn @@ -0,0 +1,25 @@ +[gd_scene load_steps=3 format=3 uid="uid://derhsnqg6p1lb"] + +[ext_resource type="Script" uid="uid://bbrx5vsfnm8c8" path="res://screens/server.gd" id="1_mesmk"] + +[sub_resource type="SystemFont" id="SystemFont_mesmk"] +font_names = PackedStringArray("IBM Plex Mono") +font_weight = 500 + +[node name="Server" type="Control" node_paths=PackedStringArray("text_output")] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_mesmk") +text_output = NodePath("Label") + +[node name="Label" type="Label" parent="."] +layout_mode = 0 +offset_left = 20.0 +offset_top = 20.0 +offset_right = 1900.0 +offset_bottom = 1060.0 +theme_override_fonts/font = SubResource("SystemFont_mesmk") diff --git a/screens/test.gd b/screens/test.gd new file mode 100644 index 0000000..9f514e0 --- /dev/null +++ b/screens/test.gd @@ -0,0 +1,10 @@ +extends Control + +#func _ready() -> void: + #_on_server_button_pressed() + +func _on_client_button_pressed() -> void: + get_tree().change_scene_to_file("uid://cgffw8lmsypov") + +func _on_server_button_pressed() -> void: + get_tree().change_scene_to_file("uid://derhsnqg6p1lb") diff --git a/screens/test.gd.uid b/screens/test.gd.uid new file mode 100644 index 0000000..8a6db3e --- /dev/null +++ b/screens/test.gd.uid @@ -0,0 +1 @@ +uid://ck2vokm0mc23h diff --git a/screens/test.tscn b/screens/test.tscn new file mode 100644 index 0000000..f9c2ccb --- /dev/null +++ b/screens/test.tscn @@ -0,0 +1,60 @@ +[gd_scene load_steps=2 format=3 uid="uid://ulnxbtq8mny1"] + +[ext_resource type="Script" uid="uid://ck2vokm0mc23h" path="res://screens/test.gd" id="1_gb08i"] + +[node name="Test" 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_gb08i") + +[node name="Label" type="Label" parent="."] +layout_mode = 0 +offset_left = 610.0 +offset_top = 190.0 +offset_right = 650.0 +offset_bottom = 213.0 +text = "Test" + +[node name="Label2" type="Label" parent="."] +layout_mode = 1 +anchors_preset = -1 +anchor_right = 0.132 +anchor_bottom = 0.28 +offset_left = 475.0 +offset_top = 460.0 +offset_right = 261.56 +offset_bottom = 180.6 +text = "feedback" + +[node name="HBoxContainer" type="HBoxContainer" parent="."] +layout_mode = 0 +offset_left = 139.0 +offset_top = 207.0 +offset_right = 440.0 +offset_bottom = 271.0 + +[node name="ClientButton" type="Button" parent="HBoxContainer"] +layout_mode = 2 +text = "be client" + +[node name="ServerButton" type="Button" parent="HBoxContainer"] +layout_mode = 2 +text = "make server" + +[node name="Label3" type="Label" parent="."] +layout_mode = 1 +anchors_preset = -1 +anchor_right = 0.132 +anchor_bottom = 0.28 +offset_left = 456.0 +offset_top = 510.0 +offset_right = 273.56 +offset_bottom = 230.6 +text = "feedback" + +[connection signal="pressed" from="HBoxContainer/ClientButton" to="." method="_on_client_button_pressed"] +[connection signal="pressed" from="HBoxContainer/ServerButton" to="." method="_on_server_button_pressed"] diff --git a/server.py b/server.py new file mode 100644 index 0000000..471c0bf --- /dev/null +++ b/server.py @@ -0,0 +1,194 @@ +from twisted.internet.protocol import DatagramProtocol +from twisted.internet import reactor +from time import sleep + +import sys + +def address_to_string(address): + ip, port = address + return ':'.join([ip, str(port)]) + +class ServerProtocol(DatagramProtocol): + + def __init__(self): + self.active_sessions: dict[Session] = {} + self.registered_clients: dict[Client] = {} + + # Checks whether a client with the given client_id is already registered. + def is_client_registered(self, client_id): + return client_id in self.registered_clients + + # Checks if a session has already been registered with the given session_id. + def session_exists(self, session_id) -> bool: + return session_id in self.active_sessions + + # Creates a new session for players to join. + def create_session(self, session_id, host_client_id) -> int: + if self.session_exists(session_id): + # Tried to create existing session + return 1 + + self.active_sessions[session_id] = Session(session_id, host_client_id, self) + return 0 + + # Closes an existing session. Must disconnect all clients manually before calling this! + def close_session(self, s_id): + try: + del self.active_sessions[s_id] + except KeyError: + print("Tried to terminate non-existing session") + + # Registers a client and connects them to a given session. + def register_client(self, session_id, client_id, client_name, client_ip, client_port) -> int: + if self.is_client_registered(client_id): + print("Client %s is already registered." % [client_id]) + return 1 + + if not self.session_exists(session_id): + print("Client registered for non-existing session") + return 2 + + if len(self.active_sessions[session_id].registered_clients) >= 2: + print("Trying to join full session") + return 3 + + new_client = Client(session_id, client_id, client_name, client_ip, client_port) + self.registered_clients[client_id] = new_client + self.active_sessions[session_id].register_client(new_client) + + return 0 + + # Disconnects a client and removes them from the registered_clients. + def disconnect_client(self, session_id, client_id): + try: + del self.registered_clients[client_id] + self.active_sessions[session_id].remove_client(client_id) + except KeyError: + print("Tried to disconnect unregistered client %s." % [client_id]) + + # Checks whether the given client owns any session. Returns the session ID if true. + # Returns an empty string otherwise. + def owns_session(self, client_id) -> str: + for session_id in self.active_sessions: + if self.active_sessions[session_id].host_client_id == client_id: + return session_id + return "" + + # Sends a message to a client specified by their address. + def send_message(self, msg, address): + self.transport.write(bytes(msg, "utf-8"), address) + + # Executed when data is received from a client. + def datagramReceived(self, datagram, address): + """Handle incoming datagram messages.""" + print(datagram) + data_string = datagram.decode("utf-8") + msg_type = data_string[:2] + + print(address) + + client_ip, client_port = address + split_data = data_string.split(":") + + if msg_type == "cr": + # create lobby + error = self.create_session(split_data[1], split_data[2]) + + if error == 1: + # tried to create existing session + print("Could not create session; already exists") + self.transport.write(bytes('c1', "utf-8"), address) + return + + # TODO no error handling for register_client here yet + print(self.register_client(split_data[1], split_data[2], split_data[3], client_ip, client_port)) + print(client_port) + print("CLIENT PORT") + print("successfully created session and registered client") + self.transport.write(bytes('c0', "utf-8"), address) + + + elif msg_type == "jn": + # join lobby + error = self.register_client(split_data[1], split_data[2], split_data[3], client_ip, client_port) + print(client_port) + print("CLIENT PORT") + + if error == 1: + # client already registered + self.send_message('j1', address) + return + elif error == 2: + # session does not exist + self.send_message('j2', address) + return + elif error == 3: + # session full + self.send_message('j3', address) + return + + self.send_message('j0', address) + print("joined successfully") + + elif msg_type == "lv": + session_id = split_data[1] + leaving_client_id = split_data[2] + owned_session = self.owns_session(leaving_client_id) + if owned_session == "": + # client is only connected to a session + self.disconnect_client(session_id, leaving_client_id) + print("disconnected client from session") + else: + # client owns a session + # disconnect all clients + clients_to_delete = self.active_sessions[session_id].registered_clients.copy() + for client in clients_to_delete: + print(clients_to_delete) + print(client) + client_address = (client.client_ip, client.client_port) + print(client_address) + self.send_message('ex', client_address) + self.disconnect_client(client.session_id, client.client_id) + + self.close_session(session_id) + print("host gone; disconnected clients and closed session") + +class Session: + + def __init__(self, session_id, host_client_id, server): + self.id = session_id + self.host_client_id = host_client_id + self.server = server + self.registered_clients: list[Client] = [] + + # Registers a client in the session. + def register_client(self, client): + if client in self.registered_clients: + # Client is already registered + return + + self.registered_clients.append(client) + + def remove_client(self, client_id): + for client in self.registered_clients: + if client.client_id == client_id: + self.registered_clients.remove(client) + +class Client: + + def __init__(self, session_id, client_id, client_name, client_ip, client_port): + self.session_id = session_id + self.client_id = client_id + self.client_name = client_name + self.client_ip = client_ip + self.client_port = client_port + +if __name__ == '__main__': + if len(sys.argv) < 2: + print("Usage: ./server.py PORT") + sys.exit(1) + + port = int(sys.argv[1]) + reactor.listenUDP(port, ServerProtocol()) + print('Listening on *:%d' % (port)) + reactor.run() diff --git a/web_client.gd b/web_client.gd new file mode 100644 index 0000000..9a83ee6 --- /dev/null +++ b/web_client.gd @@ -0,0 +1,169 @@ +class_name WebClient +extends Node + +@export var name_input: LineEdit +@export var session_code_input: LineEdit +@export var output: Label +@export var create_lobby_button: Button +@export var join_lobby_button: Button +@export var disconnect_lobby_button: Button + +var _peer: ENetMultiplayerPeer = ENetMultiplayerPeer.new() +var _address: String = "127.0.0.1" +var _port: int = 9974 + +var _udp: PacketPeerUDP = PacketPeerUDP.new() +var _tcp = PacketPeerDTLS + +var _session_id: String +var _client_id: String +var _client_name: String + +var _is_in_lobby: bool = false + +const MSG_CREATE_LOBBY: String = "cr" +const MSG_JOIN_LOBBY: String = "jn" +const MSG_LEAVE_LOBBY: String = "lv" + +const REMOTE_CREATE_SESSION_SUCCESS: String = "c0" +const REMOTE_ERR_CREATE_SESSION: String = "c1" +const REMOTE_JOIN_SESSION_SUCCESS: String = "j0" +const REMOTE_JOIN_SESSION_DUPLICATE: String = "j1" +const REMOTE_JOIN_SESSION_MISSING: String = "j2" +const REMOTE_JOIN_SESSION_FULL: String = "j3" +const REMOTE_EXIT_SESSION: String = "ex" + +func _ready() -> void: + _write_output("me client, you jane.") + +func _process(delta: float) -> void: + if _udp.get_available_packet_count() > 0: + var packet_str = _udp.get_packet().get_string_from_ascii() + print(packet_str) + match packet_str.substr(0, 2): + # Session creation + REMOTE_CREATE_SESSION_SUCCESS: + _write_output("Created and joined lobby successfully.") + _write_output("Lobby code: {session_id}".format({ + "session_id": _session_id + })) + _write_output("Your player code: {client_id}".format({ + "client_id": _client_id + })) + _on_lobby_connected() + + REMOTE_ERR_CREATE_SESSION: + _write_output("Could not create lobby.") + _write_output("A lobby with the code {session_id} already exists.".format({ + "session_id": _session_id + })) + + REMOTE_JOIN_SESSION_SUCCESS: + _write_output("Joined lobby successfully.") + _write_output("Your player code: {client_id}".format({"client_id": _client_id})) + _on_lobby_connected() + + # Session joining + REMOTE_JOIN_SESSION_DUPLICATE: + _write_output("Could not join lobby {session_id}.".format({ + "session_id": _session_id + })) + _write_output("A player with the code {client_id} is already registered in the lobby.".format({ + "client_id": _client_id, + })) + + REMOTE_JOIN_SESSION_MISSING: + _write_output("No lobby exists with the code {session_id}.".format({ + "session_id": _session_id + })) + + REMOTE_JOIN_SESSION_FULL: + _write_output("Could not join lobby {session_id}.".format({ + "session_id": _session_id + })) + _write_output("The lobby has reached the maximum player count.") + + # Session exit + REMOTE_EXIT_SESSION: + _on_lobby_disconnected() + +func _exit_tree() -> void: + _disconnect_from_lobby() + +func _generate_code() -> String: + # use a local randomized RNG to keep the global RNG reproducible + var rng = RandomNumberGenerator.new() + rng.randomize() + var length = 6 + var result = '' + for _n in range(length): + var ascii = rng.randi_range(0, 25) + 65 + result += '%c' % ascii + return result + +func _on_peer_connected(id: int) -> void: + $Label.text += "connected to id: " + str(id) + +func _get_client_string(remote_code: String, session_id: String, client_id: String, client_name: String) -> PackedByteArray: + return "{remote_code}:{session_id}:{client_id}:{client_name}".format({ + "remote_code": remote_code, + "session_id": session_id, + "client_id": client_id, + "client_name": client_name, + }).to_utf8_buffer() + +func _disconnect_from_lobby() -> void: + if _is_in_lobby: + _udp.put_packet(_get_client_string(MSG_LEAVE_LOBBY, _session_id, _client_id, _client_name)) + _udp.close() + +func _write_output(text: String) -> void: + output.text += text + "\n" + +func _on_lobby_connected() -> void: + _is_in_lobby = true + create_lobby_button.disabled = true + join_lobby_button.disabled = true + disconnect_lobby_button.disabled = false + +func _on_lobby_disconnected() -> void: + _write_output("Connection with lobby {session_id} ended.".format({ + "session_id": _session_id + })) + _udp.close() + _is_in_lobby = false + name_input.editable = true + session_code_input.editable = true + session_code_input.text = "" + _session_id = "" + create_lobby_button.disabled = false + join_lobby_button.disabled = false + disconnect_lobby_button.disabled = true + +# Create new session +func _on_create_lobby_button_pressed() -> void: + _udp.set_dest_address(_address, _port) + _session_id = _generate_code() + session_code_input.text = _session_id + name_input.editable = false + session_code_input.editable = false + _client_id = _generate_code() + _client_name = name_input.text.strip_edges() + + _udp.put_packet(_get_client_string(MSG_CREATE_LOBBY, _session_id, _client_id, _client_name)) + +# Connect to existing session +func _on_join_lobby_button_pressed() -> void: + _udp.set_dest_address(_address, _port) + name_input.editable = false + session_code_input.editable = false + + _session_id = session_code_input.text.strip_edges() + _client_id = _generate_code() + _client_name = name_input.text.strip_edges() + + _udp.put_packet(_get_client_string(MSG_JOIN_LOBBY, _session_id, _client_id, _client_name)) + +func _on_disconnect_lobby_button_pressed() -> void: + _disconnect_from_lobby() + _on_lobby_disconnected() diff --git a/web_client.gd.uid b/web_client.gd.uid new file mode 100644 index 0000000..a1dc6e0 --- /dev/null +++ b/web_client.gd.uid @@ -0,0 +1 @@ +uid://bfaxxdr7v0emr