From d2abbb69f2eb562078cad89e99285e6b7a236063 Mon Sep 17 00:00:00 2001 From: Daniel Flanagan Date: Wed, 8 Dec 2021 23:39:56 -0600 Subject: [PATCH] WIP --- .../iosevkalyte-regular.ttf.import | 29 -- assets/theme.tres | 4 +- project.godot | 2 +- screens/lobby_browser.tscn | 14 +- screens/main_menu.tscn | 2 +- screens/multiplayer_lobby.tscn | 1 + scripts/global/global.gd | 72 +++-- scripts/global/multiplayer_client.gd | 117 -------- scripts/global/signaller_client.gd | 244 ++++++++-------- scripts/global/webrtc_negotiator.gd | 107 +++++++ scripts/screens/game.gd | 2 + scripts/screens/lobby_browser.gd | 50 ++-- scripts/screens/main_menu.gd | 7 +- scripts/screens/multiplayer_lobby.gd | 13 +- server.ts | 265 +++++++++--------- 15 files changed, 464 insertions(+), 465 deletions(-) delete mode 100644 assets/fonts/iosevkalyte/iosevkalyte-regular.ttf.import delete mode 100644 scripts/global/multiplayer_client.gd create mode 100644 scripts/global/webrtc_negotiator.gd diff --git a/assets/fonts/iosevkalyte/iosevkalyte-regular.ttf.import b/assets/fonts/iosevkalyte/iosevkalyte-regular.ttf.import deleted file mode 100644 index 0b7e41f..0000000 --- a/assets/fonts/iosevkalyte/iosevkalyte-regular.ttf.import +++ /dev/null @@ -1,29 +0,0 @@ -[remap] - -importer="font_data_dynamic" -type="FontData" -uid="uid://cklw0gsblqjmg" -path="res://.godot/imported/iosevkalyte-regular.ttf-a2a88ad4f5b4e12dda9997dfa2756a6e.fontdata" - -[deps] - -source_file="res://assets/fonts/iosevkalyte/iosevkalyte-regular.ttf" -dest_files=["res://.godot/imported/iosevkalyte-regular.ttf-a2a88ad4f5b4e12dda9997dfa2756a6e.fontdata"] - -[params] - -antialiased=true -multichannel_signed_distance_field=false -msdf_pixel_range=8 -msdf_size=48 -force_autohinter=false -hinting=1 -oversampling=0.0 -compress=true -preload/char_ranges=PackedStringArray() -preload/glyph_ranges=PackedStringArray() -preload/configurations=PackedStringArray() -support_overrides/language_enabled=PackedStringArray() -support_overrides/language_disabled=PackedStringArray() -support_overrides/script_enabled=PackedStringArray() -support_overrides/script_disabled=PackedStringArray() diff --git a/assets/theme.tres b/assets/theme.tres index 42ebfb8..c64ba58 100644 --- a/assets/theme.tres +++ b/assets/theme.tres @@ -3,7 +3,7 @@ [ext_resource path="res://assets/fonts/iosevkalyte/iosevkalyte.tres" type="DynamicFont" id=1] [ext_resource path="res://assets/img/panel.png" type="Texture" id=2] -[sub_resource type="Image" id=1] +[sub_resource type="Image" id=5] data = { "data": PoolByteArray( 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42, 37, 37, 42 ), "format": "RGB8", @@ -15,7 +15,7 @@ data = { [sub_resource type="ImageTexture" id=2] flags = 4 flags = 4 -image = SubResource( 1 ) +image = SubResource( 5 ) size = Vector2( 8, 8 ) [sub_resource type="StyleBoxTexture" id=3] diff --git a/project.godot b/project.godot index 835db02..3c3b8dd 100644 --- a/project.godot +++ b/project.godot @@ -36,7 +36,7 @@ Global="*res://scripts/global/global.gd" [gdnative] -singletons=[ "res://webrtc/webrtc.tres" ] +singletons=[ "res://webrtc/webrtc_debug.tres" ] [physics] diff --git a/screens/lobby_browser.tscn b/screens/lobby_browser.tscn index d5e8bc5..27103c7 100644 --- a/screens/lobby_browser.tscn +++ b/screens/lobby_browser.tscn @@ -62,10 +62,10 @@ __meta__ = { } [node name="subhead" type="HBoxContainer" parent="v"] -visible = false margin_top = 54.0 margin_right = 984.0 margin_bottom = 85.0 +size_flags_horizontal = 3 [node name="label" type="Label" parent="v/subhead"] margin_right = 830.0 @@ -83,7 +83,7 @@ rect_min_size = Vector2( 150, 0 ) placeholder_text = "Your Name Here" [node name="body" type="ScrollContainer" parent="v"] -margin_top = 54.0 +margin_top = 89.0 margin_right = 984.0 margin_bottom = 560.0 size_flags_horizontal = 3 @@ -91,7 +91,7 @@ size_flags_vertical = 3 [node name="p" type="PanelContainer" parent="v/body"] margin_right = 984.0 -margin_bottom = 506.0 +margin_bottom = 471.0 size_flags_horizontal = 3 size_flags_vertical = 3 @@ -99,21 +99,21 @@ size_flags_vertical = 3 margin_left = 7.0 margin_top = 7.0 margin_right = 977.0 -margin_bottom = 499.0 +margin_bottom = 464.0 size_flags_horizontal = 3 size_flags_vertical = 3 [node name="zero_state" type="CenterContainer" parent="v/body/p/lobbies"] margin_right = 970.0 -margin_bottom = 492.0 +margin_bottom = 457.0 size_flags_horizontal = 3 size_flags_vertical = 3 [node name="Label" type="Label" parent="v/body/p/lobbies/zero_state"] margin_left = 177.0 -margin_top = 235.0 +margin_top = 218.0 margin_right = 793.0 -margin_bottom = 256.0 +margin_bottom = 239.0 size_flags_horizontal = 3 size_flags_vertical = 3 text = "Looks like there are no active lobbies at the moment! Why don't you make one?" diff --git a/screens/main_menu.tscn b/screens/main_menu.tscn index afc917c..4c1d6a1 100644 --- a/screens/main_menu.tscn +++ b/screens/main_menu.tscn @@ -107,6 +107,6 @@ hint_tooltip = "Open https://lyte.dev" text = "lytedev" [connection signal="pressed" from="VBoxContainer/HBoxContainer/quit" to="." method="_on_JoinLobbyButton_pressed"] -[connection signal="pressed" from="VBoxContainer/HBoxContainer/multiplayer" to="." method="_on_CreateLobbyButton_pressed"] +[connection signal="pressed" from="VBoxContainer/HBoxContainer/multiplayer" to="." method="_on_multiplayer_pressed"] [connection signal="pressed" from="VBoxContainer/HBoxContainer/Singleplayer" to="." method="_on_Singleplayer_pressed"] [connection signal="pressed" from="VBoxContainer/Container/VBoxContainer/HBoxContainer/LinkButton" to="." method="_on_LinkButton_pressed"] diff --git a/screens/multiplayer_lobby.tscn b/screens/multiplayer_lobby.tscn index 8890722..85153bb 100644 --- a/screens/multiplayer_lobby.tscn +++ b/screens/multiplayer_lobby.tscn @@ -185,6 +185,7 @@ text = "Send Message" [connection signal="pressed" from="v/head/leave_button" to="." method="_on_leave_button_pressed"] [connection signal="text_changed" from="v/head/lobby_info" to="." method="_on_lobby_info_text_changed"] +[connection signal="pressed" from="v/head/start" to="." method="_on_start_pressed"] [connection signal="toggled" from="v/head/ready_up" to="." method="_on_ready_up_toggled"] [connection signal="text_entered" from="v/body/v/h/chat" to="." method="_on_TextEdit_text_entered"] [connection signal="pressed" from="v/body/v/h/Button" to="." method="_on_Button_pressed"] diff --git a/scripts/global/global.gd b/scripts/global/global.gd index e55f101..e22ad9d 100644 --- a/scripts/global/global.gd +++ b/scripts/global/global.gd @@ -1,52 +1,74 @@ extends Node -const MultiplayerClient = preload("multiplayer_client.gd") +onready var signaller_url = "ws://localhost:8888" +# onready var signaller_url = "wss://webrtc-signaller.deno.dev:443" -onready var client = MultiplayerClient.new() +onready var ice_servers = [ + { "urls": ["stun:[::1]:3478", "stun:stun.l.google.com:19302"] }, + # { "urls": ["stun:stun.l.google.com:19302"] }, # just google + # { "urls": ["stun:[::1]:3478"] }, # just localhost +] -# for command line flags -onready var goto_multiplayer = false -onready var create_lobby = false -onready var join_first_available_lobby = false +const SignallerClient = preload("signaller_client.gd") +const WebRTCNegotiator = preload("webrtc_negotiator.gd") + +onready var signaller_client = SignallerClient.new(signaller_url) +onready var negotiator = WebRTCNegotiator.new(ice_servers, signaller_client) + +onready var onetime_cmd_flags = { + "--multiplayer": false, + "--create-lobby": false, + "--join-first-available-lobby": false, +} + +func check_onetime_flag(flag): + var result = onetime_cmd_flags["--%s" % flag] + onetime_cmd_flags["--%s" % flag] = false + return result func _ready(): - # client.signaller.connect("websocket_connected", self, "signaller_disconnected") - add_child(client) - client.signaller.connect("websocket_connected", self, "_signaller_connected") + negotiator.signaller_client = signaller_client + negotiator.ice_servers = ice_servers + add_child(negotiator) - for arg in OS.get_cmdline_args(): - match arg: - "--multiplayer": goto_multiplayer = true - "--create-lobby": create_lobby = true - "--join-first-available-lobby": join_first_available_lobby = true - var a: print("Unknown command line arg: %s" % a) - - if goto_multiplayer: - goto_multiplayer = false - lobby_browser() + for flag in onetime_cmd_flags.keys(): + if flag in OS.get_cmdline_args(): + onetime_cmd_flags[flag] = true func goto_scene(scene_resource_name): var _result = get_tree().change_scene("res://screens/%s.tscn" % scene_resource_name) func main_menu(): - client.close() + negotiator.close() goto_scene("main_menu") +func fake_singleplayer(): + print("Faking network peer for singleplayer...") + negotiator.multiplayer.initialize(1, true) + get_tree().network_peer = negotiator.multiplayer + var peer: WebRTCPeerConnection = WebRTCPeerConnection.new() + peer.initialize() + negotiator.multiplayer.add_peer(peer, 1) + func start_singleplayer_game(): - client.fake_singleplayer() - client.close() + fake_singleplayer() + negotiator.close() + goto_game() + +func goto_game(): goto_scene("game") func _signaller_connected(): goto_scene("lobby_browser") func lobby_browser(): - client.close() - Global.client.connect_to_signaller() + negotiator.close() + signaller_client.connect("connected", self, "_signaller_connected") + negotiator.connect_to_signaller() func lobby(): goto_scene("multiplayer_lobby") func quit(): - client.close() + negotiator.close() get_tree().quit() diff --git a/scripts/global/multiplayer_client.gd b/scripts/global/multiplayer_client.gd deleted file mode 100644 index 8e4c5ac..0000000 --- a/scripts/global/multiplayer_client.gd +++ /dev/null @@ -1,117 +0,0 @@ -extends Node - -""" -This module sets up WebRTC peer connections. -""" - -# var multiplayer_url = "wss://webrtc-signaller.deno.dev:443" -var multiplayer_url = "ws://localhost:8888" -# var multiplayer_url = "ws://echo.websocket.org" -var webrtc_ice_servers = [ - { "urls": ["stun:stun.l.google.com:19302"] }, - # { "urls": ["stun:localhost:3478"] } -] - -const SignallerClient = preload("signaller_client.gd") - -onready var mp = WebRTCMultiplayer.new() -onready var signaller = SignallerClient.new() - -func _ready(): - signaller.connect("offer_received", self, "offer_received") - signaller.connect("answer_received", self, "answer_received") - signaller.connect("candidate_received", self, "candidate_received") - # signaller.connect("websocket_connected", self, "connected") - # signaller.connect("websocket_disconnected", self, "disconnected") - - signaller.connect("peer_id_set", self, "_peer_id_set") - signaller.connect("peer_joined", self, "_peer_joined") - # mp.connect("peer_disconnected", self, "peer_disconnected") - # connect("lobby_sealed", self, "lobby_sealed") - add_child(signaller) - -func fake_singleplayer(): - print("Faking network peer for singleplayer...") - mp.initialize(1, true) - get_tree().network_peer = mp - var peer: WebRTCPeerConnection = WebRTCPeerConnection.new() - peer.initialize() - mp.add_peer(peer, 1) - -func close(): - mp.close() - signaller.close() - -func connect_to_signaller(): - close() - signaller.connect_to_websocket_signaller(multiplayer_url) - -func _create_peer(peer_id): - # if peer_id == signaller.peer_id: return - var peer: WebRTCPeerConnection = WebRTCPeerConnection.new() - print("New Net Peer with peer_id %d" % [peer_id]) - var _peer_init_results = peer.initialize({"iceServers": webrtc_ice_servers}) - var _result = peer.connect("session_description_created", self, "_offer_created", [peer_id]) - _result = peer.connect("ice_candidate_created", self, "_new_ice_candidate", [peer_id]) - mp.add_peer(peer, peer_id) - var uid = mp.get_unique_id() - if peer_id > uid: - var offer_result = peer.create_offer() - print("Create Offer Result: %s" % offer_result) - else: - print("Did not create offer: %d <= %d" % [peer_id, uid]) - return peer - -func _new_ice_candidate(mid_name, index_name, sdp_name, peer_id): - print("New ICE Candidate - MID: %s, Index: %s, SDP: %s, PeerID: %s" % [mid_name, index_name, sdp_name, peer_id]) - signaller.send_candidate(peer_id, mid_name, index_name, sdp_name) - -func _offer_created(type, data, peer_id): - print("Offer created - Type: %s, PeerID: %s, Data: %s" % [type, peer_id, JSON.print(data)]) - if not mp.has_peer(peer_id): - return - mp.get_peer(peer_id).connection.set_local_description(type, data) - if type == "offer": - signaller.send_offer(peer_id, data) - else: - signaller.send_answer(peer_id, data) - -func _peer_id_set(peer_id): - print("Peer ID received %s - setting up to peer..." % [peer_id]) - mp.initialize(peer_id, true) - get_tree().network_peer = mp - -func _peer_joined(peers): - for i in range(len(peers)): - var peer = peers[i] - if peer.has("peerId"): - var peer_id = peer["peerId"] - if not mp.has_peer(peer_id): - _create_peer(peer_id) - -func peer_disconnected(id): - print("Peer %s disconnected" % id) - if mp.has_peer(id): - mp.remove_peer(id) - -func offer_received(data): - var id = data["peerId"] - if mp.has_peer(id): - print("Setting offer remote description: %s" % JSON.print(data)) - mp.get_peer(id).connection.set_remote_description("offer", data["offer"]) - else: - print("Received an offer for a peer with ID %s that hasn't been added" % id) - -func answer_received(data): - var id = data["peerId"] - if mp.has_peer(id): - print("Setting answer remote description: %s" % JSON.print(data)) - mp.get_peer(id).connection.set_remote_description("answer", data["answer"]) - -func candidate_received(data): - var id = data["peerId"] - if mp.has_peer(id): - print("Adding ice candidate: %s" % JSON.print(data)) - mp.get_peer(id).connection.add_ice_candidate(data["mid"], data["index"], data["sdp"]) - else: - print("Received candidate for non-existant peer %s" % id) diff --git a/scripts/global/signaller_client.gd b/scripts/global/signaller_client.gd index 3220846..ef6d3eb 100644 --- a/scripts/global/signaller_client.gd +++ b/scripts/global/signaller_client.gd @@ -3,168 +3,154 @@ extends Node """ This module is responsible for making a WebSocket connection to the signaller in order to enable establishish WebRTC P2P connections. Another module is -expected to fully setup the peer connections. (./multiplayer_client.gd) +expected to fully setup the peer connections. """ -signal lobby_new(lobbiesList) -signal lobby_delete(id) -signal lobby_joined(id, peer_id) -signal lobby_left(id) +const DISPLAY_NAME_FILE = "user://display_name.txt" +const DEFAULT_DISPLAY_NAME = "UnnamedPlayer" -signal peer_joined(peers) +# lobby data +signal lobby_data_received(lobbies) +signal lobby_joined(lobby_and_peer) +signal lobby_left(id) +signal lobby_delete(id) + +# peer data +signal peer_data_received(peers) signal peer_left(id) -signal candidate_received(data) + +# WebRTC negotiations +signal candidate_received(cand) signal offer_received(data) signal answer_received(data) -signal client_id_set(client_id) -signal peer_id_set(peer_id) -signal websocket_connected -signal websocket_disconnected -onready var ws: WebSocketClient = WebSocketClient.new() +# underlying websocket +signal received_message +signal connected +signal connection_failure +signal disconnected +onready var websocket: WebSocketClient = WebSocketClient.new() +# TODO: setget these? onready var client_id = null -onready var lobby_id = null +onready var display_name = DEFAULT_DISPLAY_NAME setget set_display_name onready var peer_id = null -onready var display_name = "UnnamedPlayer" -onready var lobby_name = null -func is_host(): return peer_id == 1 +var websocket_url = null -const DISPLAY_NAME_FILE = "user://display_name.txt" +func _init(url): + websocket_url = url func _ready(): - var dnf = File.new() - if dnf.open(DISPLAY_NAME_FILE, File.READ) == OK: - display_name = dnf.get_as_text() - dnf.close() - var _result = ws.connect("data_received", self, "_parse_msg") - _result = ws.connect("connection_established", self, "_connected") - _result = ws.connect("connection_closed", self, "_closed") - _result = ws.connect("connection_error", self, "_closed", [false]) - _result = ws.connect("connection_failed", self, "_closed", [false]) - _result = ws.connect("connection_succeeded", self, "_succ") - _result = ws.connect("server_close_request", self, "_close_request") + display_name = _load_display_name() + websocket.connect("data_received", self, "_handle_data") + websocket.connect("connection_established", self, "_connected") + # websocket.connect("connection_succeeded", self, "_connection_succeeded") + websocket.connect("connection_closed", self, "_closed") + websocket.connect("connection_error", self, "_connection_error", [false]) + websocket.connect("connection_failed", self, "_connection_error", [false]) + websocket.connect("server_close_request", self, "_close_request") -func _succ(): - print("WebSocket Connection Succeeded") +func connect_websocket(): + close() + print("Attempting to connect to WebSocket signalling server at %s" % websocket_url) + var result = websocket.connect_to_url(websocket_url) + if result != OK: + print("Failed to connect to WebSocket signalling server at %s: %s" % [websocket_url, result]) func close(): - ws.disconnect_from_host() + websocket.disconnect_from_host() -func connect_to_websocket_signaller(url: String): - print("WebSocket: %s" % ws) - close() - print("Attempting to connect to WebSocket signalling server at %s" % url) - var result = ws.connect_to_url(url) - if result != OK: - print("FAILED TO CONNECT TO %s" % url) - print("WebSocket Connect Result: %s" % result) +func set_display_name(new_display_name: String): + display_name = new_display_name + _send("update_display_name:%s" % display_name) -func _closed(unknown): - print("WebSocket closed: %s: " % unknown) - emit_signal("websocket_disconnected") +func request_lobby_list(): + return _send("request_lobby_list") -func _close_request(code: int, reason: String): - print("Received WebSocket close request from signalling server - Code: %s, Reason: %s" % [code, reason]) - -func _connected(protocol = ""): - print("WebSocket signaller connected via protocol %s" % protocol) - ws.get_peer(1).set_write_mode(WebSocketPeer.WRITE_MODE_TEXT) - emit_signal("websocket_connected") - _send("json:%s" % JSON.print({ - "type": "init", - "data": { - "name": display_name - } - })) - -func _process(_delta: float): - var status: int = ws.get_connection_status() - if status == WebSocketClient.CONNECTION_CONNECTED or status == WebSocketClient.CONNECTION_CONNECTING: - ws.poll() func join_lobby(id: String): return _send("lobby_join:%s" % id) func create_lobby(): return _send("lobby_create") -func request_lobby_list(): - return _send("request_lobby_list") - -func request_peer_list(): - return _send("request_peer_list") - -func set_display_name(new_display_name: String): - display_name = new_display_name - _send("update_display_name:%s" % display_name) - var dnf = File.new() - if dnf.open(DISPLAY_NAME_FILE, File.WRITE) == OK: - dnf.store_string(display_name) - dnf.close() - -func _send(s: String): - return ws.get_peer(1).put_packet(s.to_utf8()) +func is_host(): return peer_id == 1 func set_lobby_name(s: String): - if is_host(): - _send("json:%s" % JSON.print({"type": "update_lobby", "data": {"name": s}})) + if is_host(): _send_json({"name": s}, "update_lobby") func lock_lobby(): - if is_host(): - _send("json:%s" % JSON.print({"type": "update_lobby", "data": {"locked": true}})) + if is_host(): _send_json({"locked": true}, "update_lobby") func set_lobby_max_players(n: int): - if is_host(): - _send("json:%s" % JSON.print({"type": "update_lobby", "data": {"maxPlayers": n}})) + if is_host(): _send_json({"maxPlayers": n}, "update_lobby") -func set_ready(n: bool): - if !is_host(): - _send("set_ready:%s" % JSON.print(n)) +func request_peer_list(): _send("request_peer_list") -func _parse_msg(): - var msg: String = ws.get_peer(1).get_packet().get_string_from_utf8() +func send_candidate(peerId, mid, index, sdp) -> int: + return _send_json({"peerId": peerId, "mid": mid, "index": index, "sdp": sdp}, "candidate") + +func send_offer(peerId, offer) -> int: + return _send_json({"peerId": peerId, "offer": offer }, "offer") + +func send_answer(peerId, answer) -> int: + return _send_json({"peerId": peerId, "answer": answer }, "answer") + +func _closed(code): + print("WebSocket closed: %s: " % code) + emit_signal("disconnected") + +func _close_request(code: int, reason: String): + print("Received WebSocket close request from signalling server - Code: %s, Reason: %s" % [code, reason]) + # TODO: does this fire _closed? + +func _connected(protocol = ""): + print("Signaller connected via WebSocket using protocol %s" % protocol) + websocket.get_peer(1).set_write_mode(WebSocketPeer.WRITE_MODE_TEXT) + emit_signal("connected") + _send_json({name = display_name}, "init") + +func _process(_delta: float): + var status: int = websocket.get_connection_status() + if status == WebSocketClient.CONNECTION_CONNECTED or status == WebSocketClient.CONNECTION_CONNECTING: + websocket.poll() + +func _handle_data(): + var msg: String = websocket.get_peer(1).get_packet().get_string_from_utf8() if msg.begins_with("json:"): var data = JSON.parse(msg.substr(5)) if data.error == OK: - handle_message(data.result) - var _result = ws.get_peer(1).put_packet("".to_utf8()) + _process_message(data.result) + var _result = websocket.get_peer(1).put_packet("".to_utf8()) else: print("Unhandled message: %s" % msg) -func handle_message(data: Dictionary): +func _process_message(data: Dictionary): match data["type"]: - "your_id": - client_id = data["data"]["id"] - emit_signal("client_id_set", client_id) + "init": + client_id = data.id + print("Signaller Received init response: %s" % data) "your_peer_id": - peer_id = int(data["data"]) + peer_id = int(data.peerId) emit_signal("peer_id_set", peer_id) - "lobby_list": emit_signal("lobby_new", data["data"]) - "lobby_new": emit_signal("lobby_new", [data]) - "lobby_delete": emit_signal("lobby_delete", data["id"]) + "lobby_data": emit_signal("lobby_data_received", data.data) + "lobby_delete": emit_signal("lobby_delete", data.id) "lobby_joined": - lobby_id = data["id"] - lobby_name = data["name"] - peer_id = data["peerId"] - emit_signal("lobby_joined", data["id"], data["peerId"]) + peer_id = data.peerId + emit_signal("lobby_joined", data) "lobby_left": - lobby_id = null - lobby_name = null + peer_id = null emit_signal("lobby_left", data["id"]) - "peer_joined": + "peer_data": print(typeof(data), data) if data.get("id") != null: emit_signal("peer_joined", [data]) else: emit_signal("peer_joined", data["data"]) "peer_left": print("Peer Left: %s" % data) - emit_signal("peer_left", data["data"]) - "ready_change": - print("Peer Ready Change") - emit_signal("peer_joined", [data]) + emit_signal("peer_left", data) "candidate": print("Candidate received - Data: %s" % JSON.print(data["data"])) @@ -176,29 +162,31 @@ func handle_message(data: Dictionary): print("Answer received - Data: %s" % JSON.print(data["data"])) emit_signal("answer_received", data["data"]) "ping": - # print("Signaller Ping") _send("pong") _: print("Unhandled Message - Data: %s" % JSON.print(data)) -func send_candidate(peerId, mid, index, sdp) -> int: - return _send("json:%s" % JSON.print({ - "type": "candidate", - "data": { - "peerId": peerId, - "mid": mid, - "index": index, - "sdp": sdp - } - })) +func _load_display_name(): + var _display_name = DEFAULT_DISPLAY_NAME + var display_name_file = File.new() + if display_name_file.open(DISPLAY_NAME_FILE, File.READ) == OK: + _display_name = display_name_file.get_as_text() + else: + print("Failed to open %s for reading display_name, creating default" % DISPLAY_NAME_FILE) + _store_display_name() + display_name_file.close() + return _display_name -func send_offer(peerId, offer) -> int: - return _send("json:%s" % JSON.print({ "type": "offer", "data": {"peerId": peerId, "offer": offer }})) +func _store_display_name(): + var display_name_file = File.new() + if display_name_file.open(DISPLAY_NAME_FILE, File.WRITE) == OK: + display_name_file.store_string(display_name) + else: + print("Failed to open %s for writing display_name" % DISPLAY_NAME_FILE) + display_name_file.close() -func send_answer(peerId, answer) -> int: - return _send("json:%s" % JSON.print({ "type": "answer", "data": {"peerId": peerId, "answer": answer }})) +func _send(s: String): + return websocket.get_peer(1).put_packet(s.to_utf8()) -""" - elif type.begins_with("S: "): - emit_signal("lobby_sealed") - return -""" +func _send_json(data: Dictionary, type=null): + if type != null: data["type"] = type + _send("json:%s" % JSON.print(data)) diff --git a/scripts/global/webrtc_negotiator.gd b/scripts/global/webrtc_negotiator.gd new file mode 100644 index 0000000..c7d5044 --- /dev/null +++ b/scripts/global/webrtc_negotiator.gd @@ -0,0 +1,107 @@ +extends Node + +""" +This module sets up WebRTC peer connections. +""" + +onready var webrtc = WebRTCMultiplayer.new() + +var signaller_client = null +var ice_servers = null + +func _init(_ice_servers, _signaller_client): + ice_servers = _ice_servers + signaller_client = _signaller_client + +func _ready(): + signaller_client.connect("offer_received", self, "_offer_received") + signaller_client.connect("answer_received", self, "_answer_received") + signaller_client.connect("candidate_received", self, "_candidate_received") + signaller_client.connect("connected", self, "_signaller_connected") + # signaller_client.connect("websocket_disconnected", self, "disconnected") + + signaller_client.connect("peer_id_set", self, "_peer_id_set") + signaller_client.connect("peer_joined", self, "_peer_joined") + # webrtc.connect("peer_disconnected", self, "peer_disconnected") + # connect("lobby_sealed", self, "lobby_sealed") + add_child(self.signaller_client) + +func _signaller_connected(): + print("Connected to signaller. Will be using the following ICE servers: %s", ice_servers) + +func close(): + webrtc.close() + signaller_client.close() + +func connect_to_signaller(): + signaller_client.connect_websocket() + +func _create_peer(peer_id): + # if peer_id == signaller_client.peer_id: return + var peer: WebRTCPeerConnection = WebRTCPeerConnection.new() + print("New Net Peer with peer_id %d" % [peer_id]) + var _peer_init_results = peer.initialize({"iceServers": ice_servers}) + var _result = peer.connect("session_description_created", self, "_offer_created", [peer_id]) + _result = peer.connect("ice_candidate_created", self, "_new_ice_candidate", [peer_id]) + webrtc.add_peer(peer, peer_id) + var uid = webrtc.get_unique_id() + if peer_id > uid: + var offer_result = peer.create_offer() + print("Create Offer Result: %s" % offer_result) + else: + print("Did not create offer: %d <= %d" % [peer_id, uid]) + return peer + +func _new_ice_candidate(mid_name, index_name, sdp_name, peer_id): + print("New ICE Candidate - MID: %s, Index: %s, SDP: %s, PeerID: %s" % [mid_name, index_name, sdp_name, peer_id]) + signaller_client.send_candidate(peer_id, mid_name, index_name, sdp_name) + +func _offer_created(type, data, peer_id): + print("Offer created - Type: %s, PeerID: %s, Data: %s" % [type, peer_id, JSON.print(data)]) + if not webrtc.has_peer(peer_id): + return + webrtc.get_peer(peer_id).connection.set_local_description(type, data) + if type == "offer": + signaller_client.send_offer(peer_id, data) + else: + signaller_client.send_answer(peer_id, data) + +func _peer_id_set(peer_id): + print("Peer ID received %s - setting up to peer..." % [peer_id]) + webrtc.initialize(peer_id, true) + get_tree().network_peer = webrtc + +func _peer_joined(peers): + for i in range(len(peers)): + var peer = peers[i] + if peer.has("peerId"): + var peer_id = peer["peerId"] + if not webrtc.has_peer(peer_id): + _create_peer(peer_id) + +func peer_disconnected(id): + print("Peer %s disconnected" % id) + if webrtc.has_peer(id): + webrtc.remove_peer(id) + +func offer_received(data): + var id = data["peerId"] + if webrtc.has_peer(id): + print("Setting offer remote description: %s" % JSON.print(data)) + webrtc.get_peer(id).connection.set_remote_description("offer", data["offer"]) + else: + print("Received an offer for a peer with ID %s that hasn't been added" % id) + +func answer_received(data): + var id = data["peerId"] + if webrtc.has_peer(id): + print("Setting answer remote description: %s" % JSON.print(data)) + webrtc.get_peer(id).connection.set_remote_description("answer", data["answer"]) + +func candidate_received(data): + var id = data["peerId"] + if webrtc.has_peer(id): + print("Adding ice candidate: %s" % JSON.print(data)) + webrtc.get_peer(id).connection.add_ice_candidate(data["mid"], data["index"], data["sdp"]) + else: + print("Received candidate for non-existant peer %s" % id) diff --git a/scripts/screens/game.gd b/scripts/screens/game.gd index b8f2afc..4a8d1fd 100644 --- a/scripts/screens/game.gd +++ b/scripts/screens/game.gd @@ -12,6 +12,8 @@ remotesync func add_player(peer_id): var new_player = player.instance() new_player.set_network_master(peer_id) add_child(new_player) + new_player.global_position = Vector2(100, 100) + print("Added player: %s" % new_player) func _on_Button_pressed(): Global.main_menu() diff --git a/scripts/screens/lobby_browser.gd b/scripts/screens/lobby_browser.gd index 7295e0e..1c3dd0d 100644 --- a/scripts/screens/lobby_browser.gd +++ b/scripts/screens/lobby_browser.gd @@ -14,17 +14,16 @@ onready var lobbies = {} onready var lobby = preload("res://objects/lobby.tscn") func _ready(): - display_name_edit.text = Global.client.signaller.display_name - Global.client.signaller.connect("lobby_new", self, "_lobby_new") - Global.client.signaller.connect("lobby_delete", self, "_lobby_delete") - Global.client.signaller.connect("lobby_joined", self, "_lobby_joined") - Global.client.signaller.connect("lobby_left", self, "_lobby_left") - Global.client.signaller.connect("websocket_disconnected", self, "_signaller_disconnected") - Global.client.signaller.request_lobby_list() + display_name_edit.text = Global.signaller_client.display_name + Global.signaller_client.connect("lobby_new", self, "_lobby_new") + Global.signaller_client.connect("lobby_delete", self, "_lobby_delete") + Global.signaller_client.connect("lobby_joined", self, "_lobby_joined") + Global.signaller_client.connect("lobby_left", self, "_lobby_left") + Global.signaller_client.connect("websocket_disconnected", self, "_signaller_disconnected") + Global.signaller_client.request_lobby_list() - if Global.create_lobby: - Global.create_lobby = false - Global.client.signaller.create_lobby() + if Global.check_onetime_flag("create-lobby"): + Global.signaller_client.create_lobby() """ var ls = [] @@ -35,13 +34,14 @@ func _ready(): # TODO: add search func _input(ev): - if ev is InputEventKey and ev.pressed: - if ev.scancode == KEY_T: - var ls = [] - for n in range(5): - var j = n + lobbies.size() - ls.push_back({id = j, name = j, currentPlayers = j, maxPlayers = j, locked = false}) - call_deferred("_lobby_new", ls) + pass +# if ev is InputEventKey and ev.pressed: +# if ev.scancode == KEY_T: +# var ls = [] +# for n in range(5): +# var j = n + lobbies.size() +# ls.push_back({id = j, name = j, currentPlayers = j, maxPlayers = j, locked = false}) +# call_deferred("_lobby_new", ls) func _signaller_disconnected(): Global.main_menu() @@ -56,12 +56,12 @@ func _on_back_pressed(): Global.main_menu() func _on_create_lobby_pressed(): - Global.client.signaller.create_lobby() + Global.signaller_client.create_lobby() func _on_join_pressed(): var items = lobbies.get_selected_items() if len(items) > 0: - Global.client.signaller.join_lobby(lobbies.get_item_metadata(items[0])["id"]) + Global.signaller_client.join_lobby(lobbies.get_item_metadata(items[0])["id"]) func _lobby_new(new_lobbies: Array): # TODO: handle scrolling so that the user is never too jarred (like chat) @@ -75,14 +75,14 @@ func _lobby_new(new_lobbies: Array): if Global.join_first_available_lobby: Global.join_first_available_lobby = false - Global.client.signaller.join_lobby(id) + Global.signaller_client.join_lobby(id) func _lobby_delete(id: String): print("Lobby Deleted: %s" % id) _delete_lobby(id) func _add_lobby(id, lobby_data): - call_deferred("update_lobbies_text") + call_deferred("_update_lobbies_text") print("New Lobby ", lobby_data) # if lobby_data.currentPlayers > 0: var new_lobby = lobby.instance() @@ -91,19 +91,19 @@ func _add_lobby(id, lobby_data): lobbies[id] = new_lobby func _update_lobby(id, lobby_data): - call_deferred("update_lobbies_text") + call_deferred("_update_lobbies_text") print("Updated Lobby ", lobby_data) lobbies[id].set_with_dict(lobby_data) func _delete_lobby(id): - call_deferred("update_lobbies_text") + call_deferred("_update_lobbies_text") if lobbies.has(id): print("Removing lobby...") lobbies_grid.remove_child(lobbies[id]) lobbies[id].queue_free() lobbies.erase(id) -func update_lobbies_text(): +func _update_lobbies_text(): var n = lobbies.size() lobbies_label.text = "Active Lobbies: %d" % n if n < 1: @@ -114,4 +114,4 @@ func update_lobbies_text(): lobbies_grid.remove_child(zero_state) func _on_display_name_text_changed(new_text): - Global.client.signaller.set_display_name(new_text) + Global.signaller_client.display_name = new_text diff --git a/scripts/screens/main_menu.gd b/scripts/screens/main_menu.gd index c8797e0..c07409c 100644 --- a/scripts/screens/main_menu.gd +++ b/scripts/screens/main_menu.gd @@ -1,14 +1,17 @@ extends Node +func _ready(): + if Global.check_onetime_flag("multiplayer"): + _on_multiplayer_pressed() + func _on_Singleplayer_pressed(): Global.start_singleplayer_game() -func _on_CreateLobbyButton_pressed(): +func _on_multiplayer_pressed(): Global.lobby_browser() func _on_JoinLobbyButton_pressed(): Global.quit() - func _on_LinkButton_pressed(): OS.shell_open("https://lyte.dev") diff --git a/scripts/screens/multiplayer_lobby.gd b/scripts/screens/multiplayer_lobby.gd index 955dbf8..883fe85 100644 --- a/scripts/screens/multiplayer_lobby.gd +++ b/scripts/screens/multiplayer_lobby.gd @@ -29,7 +29,7 @@ func _ready(): call_deferred("add_chat", "# Connected to %s" % Global.client.signaller.lobby_name) # hide/show controls depending on whether or not we're the host - var is_host = Global.client.signaller.peer_id == 1 + is_host = Global.client.signaller.peer_id == 1 if is_host: ready_up.queue_free() else: @@ -37,6 +37,7 @@ func _ready(): max_players.editable = false start.queue_free() lock.queue_free() + print("Setting up lobby... %s %s" % [Global.client.signaller.peer_id, is_host]) func _lobby_update(l): for u in l: @@ -48,7 +49,7 @@ func _lobby_update(l): func _peer_joined(joined_peers): call_deferred("update_player_count") - if Global.client.signaller.is_host(): call_deferred("update_can_start") + if is_host: call_deferred("update_can_start") for peer_index in range(len(joined_peers)): var peer_data = joined_peers[peer_index] var id = peer_data["id"] @@ -112,6 +113,14 @@ func update_can_start(): if can_start: add_chat("! All players ready - game may now be started") start.disabled = !can_start +func _on_start_pressed(): + print("Start %s" % is_host) + if is_host: rpc("start_game") + +remotesync func start_game(): + print("Starting game!") + Global.goto_game() + func _lobby_left(_id): Global.lobby_browser() func _on_leave_button_pressed(): Global.lobby_browser() func _on_TextEdit_text_entered(_new_text): send_chat_message() diff --git a/server.ts b/server.ts index 0e6aff2..667beae 100644 --- a/server.ts +++ b/server.ts @@ -15,7 +15,7 @@ interface DataMessage { } type ServerDataObject = Record< - string, + "peerId" | string, string | number | symbol | null | boolean >; type ServerData = ServerDataObject[] | ServerDataObject | string | boolean; @@ -24,13 +24,11 @@ type Message = string | DataMessage; const buildMessage = ( type: string, data: ServerData | ServerData[], -) => - Object.assign( - { type }, - Array.isArray(data) - ? { data } - : (typeof data === "object" ? data : { data }), - ); +) => { + if (Array.isArray(data)) return { type, data }; + else if (typeof data === "object") return { type, ...data }; + return `${type}:${data}`; +}; class Client { id: ID; @@ -38,7 +36,6 @@ class Client { name: string; socket: WebSocket; lobby: Lobby | null; - ready: boolean; constructor(socket: WebSocket) { this.id = crypto.randomUUID(); @@ -46,7 +43,6 @@ class Client { this.peerId = null; this.name = "Client"; this.lobby = null; - this.ready = false; allClients.set(this.id, this); } @@ -55,13 +51,6 @@ class Client { return this.socket.readyState == WebSocket.OPEN; } - setReady(ready: boolean) { - this.ready = ready; - this.lobby?.broadcast( - buildMessage("ready_change", { id: this.id, ready: this.ready }), - ); - } - remove() { this.lobbyLeave(); if (this.isConnected()) { @@ -94,17 +83,24 @@ class Client { } peerList() { - if (!this.lobby) return; + if (!this.lobby) { + this.send( + buildMessage( + "info", + "you cannot request a list of peers unless you are in a lobby", + ), + ); + return; + } const peers: { id: ID; name: string; peerId: number | null; - ready: boolean; }[] = []; - this.lobby.clients.forEach(({ id, name, peerId, ready }) => - peers.push({ id, name, peerId, ready }) + this.lobby.clients.forEach(({ id, name, peerId }) => + peers.push({ id, name, peerId }) ); - this.send(buildMessage("peer_joined", peers)); + this.send(buildMessage("peer_data", peers)); // TODO: chunk async? } @@ -116,32 +112,15 @@ class Client { locked: boolean; currentPlayers: number; }[] = []; - allLobbies.forEach((lobby) => { - const { id, name, maxPlayers, locked } = lobby; - netLobbies.push({ - id, - name, - maxPlayers, - locked, - currentPlayers: lobby.clients.size, - }); - }); - // TODO: chunk async? - this.send(buildMessage("lobby_list", netLobbies)); + allLobbies.forEach((lobby) => netLobbies.push(lobby.toData())); + this.send(buildMessage("lobby_data", netLobbies)); } lobbyNew(lobby: Lobby) { - // if the client is already in a lobby, only send messages about their lobby - if (this.lobby && this.lobby != lobby) return; - const { id, name, maxPlayers, locked } = lobby; + // if the client is already in a lobby, do not send them lobby updates + if (this.lobby) return; this.send( - buildMessage("lobby_new", { - id, - name, - maxPlayers, - locked, - currentPlayers: lobby.clients.size, - }), + buildMessage("lobby_data", [lobby.toData()]), ); } @@ -151,10 +130,12 @@ class Client { } lobbyCreate(opts?: Partial) { - this.ready = true; if (this.lobby) { this.send( - `[info] cannot create lobby (already in lobby ${this.lobby.id})`, + buildMessage( + "info", + `cannot create lobby: already in lobby ${this.lobby.id}`, + ), ); return; } @@ -163,22 +144,25 @@ class Client { lobbyJoin(lobby: Lobby) { if (this.lobby) { - this.send(`[info] cannot join lobby (already in lobby ${this.lobby.id})`); + this.send( + buildMessage( + `info`, + `cannot join lobby: already in lobby ${this.lobby.id}`, + ), + ); return; } this.lobby = lobby; lobby.addClient(this); this.send( buildMessage("lobby_joined", { - id: lobby.id, - name: lobby.name, + ...this.lobby.toData(), peerId: this.peerId, }), ); } lobbyLeave() { - this.ready = false; const leavingLobby = this.lobby; if (!leavingLobby) { this.send(`[info] cannot leave lobby (not in a lobby)`); @@ -219,6 +203,16 @@ class Lobby { this.notify(); } + toData() { + return { + id: this.id, + name: this.name, + maxPlayers: this.maxPlayers, + locked: this.locked, + currentPlayers: this.clients.size, + }; + } + update( requestor: Client, newValues: { name?: string; maxPlayers?: number; locked?: boolean }, @@ -273,18 +267,17 @@ class Lobby { crypto.getRandomValues(arr); client.peerId = Math.abs(arr[0]); } - client.send(buildMessage("your_peer_id", client.peerId.toString())); + client.send(buildMessage("init_peer", client.peerId.toString())); this.broadcast( - buildMessage("peer_joined", [{ + buildMessage("peer_data", [{ id: client.id, name: client.name, peerId: client.peerId, }]), ); - console.log("Sending peer_joined..."); client.send( buildMessage( - "peer_joined", + "peer_data", Array.from(this.clients.values()).map( ({ id, name, peerId }) => ({ id, name, peerId }), ), @@ -303,6 +296,13 @@ class Lobby { } this.notify(); } + + getPeer(peerId: number): Client | null { + for (const [_id, client] of this.clients) { + if (client.peerId == peerId) return client; + } + return null; + } } interface ClientMessage { @@ -316,86 +316,102 @@ interface ClientMessage { data: ServerDataObject; } +function parseMessage(message: string): { type: string; data?: ServerData } { + const trimmedMessage = message.trim(); + if (trimmedMessage.startsWith("json:")) { + const { type, ...data } = JSON.parse(trimmedMessage.substr(5)); + return { type, data }; + } else { + const splitAt = trimmedMessage.indexOf(":"); + if (splitAt > 0) { + return { + type: trimmedMessage.substr(0, splitAt), + data: trimmedMessage.substr(splitAt + 1), + }; + } else { + return { type: trimmedMessage }; + } + } +} + // events function onMessage(client: Client, ev: MessageEvent) { // TODO: log who from? - const msg = ev.data.trim(); - if (msg === "pong") return; + const msg = parseMessage(ev.data); + if (msg.type === "pong") return; console.log("Client Message Received", msg); - if (msg === "init") { - client.send( - buildMessage("init", { + switch (msg.type) { + case "init": + if (msg.data && (msg.data as { name: string })["name"]) { + client.name = (msg.data as { name: string }).name.toString(); + } + client.send(buildMessage("init", { id: client.id, + peerId: client.peerId, name: client.name, serverVersion: SERVER_VERSION, - }), - ); - } - if (msg === "lobby_create") client.lobbyCreate(); - if (msg.startsWith("set_ready:")) { - client.setReady(JSON.parse(msg.split(":", 2)[1])); - } - if (msg === "lobby_leave") client.lobbyLeave(); - if (msg === "request_lobby_list") client.lobbyList(); - if (msg === "request_peer_list") { - if (client.lobby == null) { - client.send(`[info] not in a lobby`); - } else { + })); + break; + case "lobby_create": + client.lobbyCreate(msg.data as Partial); + break; + case "lobby_leave": + client.lobbyLeave(); + break; + case "request_lobby_list": + client.lobbyList(); + break; + case "request_peer_list": client.peerList(); - } - } - if (msg.startsWith("update_display_name:")) { - const displayName = msg.substr("update_display_name:".indexOf(":") + 1); - client.name = displayName; - } - if (msg.startsWith("lobby_join:")) { - const id = msg.substr(11); - const lobby = allLobbies.get(id); - if (lobby) { - client.lobbyJoin(lobby); - // client.peerList(); - } else client.send(`[info] could not find lobby ${id}`); - } - if (msg.startsWith("json:")) { - const data: ClientMessage = JSON.parse(msg.substr(5)); - if (data.type === "init") { - if (data.data.name) { - client.name = data.data.name.toString(); - } - client.send(buildMessage("init", { id: client.id, name: client.name })); - } else if (data.type === "update_lobby") { - if (client.lobby) { - client.lobby.update(client, data["data"]); - } else { - client.send("[info] cannot update lobby (you're not even in one!)"); - } - } else if (data.type === "lobby_create") { - client.lobbyCreate({ name: data.data?.name?.toString() }); - if (data.data.name) { - client.name = data.data.name.toString(); - } - client.send(buildMessage("init", { id: client.id, name: client.name })); - } else if (["candidate", "answer", "offer"].includes(data.type)) { - console.log("Received WebRTC Negotiation Message..."); - if (typeof data.data === "object") { - const subdata = data.data; - if (typeof subdata["peerId"] === "number") { - const destPeerId: number = subdata["peerId"]; - for (const iClient of client.lobby?.clients.values() || []) { - if (iClient.peerId == destPeerId) { - const payload = Object.assign({}, data); - const srcPeerId = client.peerId; - payload.data.peerId = srcPeerId; - console.log( - `Forwarding WebRTC Negotiation Message from peer ${srcPeerId} to peer ${destPeerId}...`, - ); - iClient.send(payload); - break; - } - } + break; + case "update_display_name": + if (msg.data) client.name = msg.data.toString(); + break; + case "lobby_join": + if (msg.data) { + const lobby = allLobbies.get(msg.data.toString()); + if (lobby) { + client.lobbyJoin(lobby); + } else { + client.send(buildMessage("info", `count not find lobby ${msg.data}`)); } } + break; + case "update_lobby": + if (client.lobby) { + if (typeof (msg.data) === "object") { + client.lobby.update(client, msg.data as Partial); + } + } else { + client.send( + buildMessage( + "info", + "failed to update lobby info: you are not in a lobby", + ), + ); + } + break; + case "candidate": + /* falls through */ + case "answer": + /* falls through */ + case "offer": { + if (!client.lobby) return; + console.log(`Received WebRTC Negotiation Message (type: ${msg.type})...`); + if (typeof msg.data !== "object") return; + const webrtcMessage = (msg.data as { peerId?: number }); + if (!webrtcMessage.peerId || typeof webrtcMessage.peerId !== "number") { + return; + } + const destClient = client.lobby.getPeer(webrtcMessage.peerId); + if (!destClient || !destClient.peerId) return; + webrtcMessage.peerId = destClient.peerId; + destClient.send(buildMessage(msg.type, webrtcMessage)); + break; } + default: + console.debug("Unknown message: ", msg); + break; } } @@ -428,13 +444,10 @@ const PORT = parseInt(Deno.env.get("PORT") || "80"); console.log("Listening on port", PORT); const listener = Deno.listen({ port: PORT }); for await (const conn of listener) { - // console.debug("Connection received:", conn); (async () => { const server = Deno.serveHttp(conn); for await (const { respondWith, request } of server) { - // console.debug("HTTP Request Received", request); try { - // console.warn(JSON.stringify([allClients, allLobbies])); const { socket, response } = Deno.upgradeWebSocket(request); const client = new Client(socket); socket.onmessage = (ev) => onMessage(client, ev);