diff --git a/default_env.tres b/assets/default_env.tres similarity index 100% rename from default_env.tres rename to assets/default_env.tres diff --git a/iosevkalyte.tres b/assets/fonts/iosevkalyte/iosevkalyte.tres similarity index 100% rename from iosevkalyte.tres rename to assets/fonts/iosevkalyte/iosevkalyte.tres diff --git a/assets/img/check.png b/assets/img/check.png new file mode 100644 index 0000000..e2199ae Binary files /dev/null and b/assets/img/check.png differ diff --git a/assets/img/check.png.import b/assets/img/check.png.import new file mode 100644 index 0000000..c8284f5 --- /dev/null +++ b/assets/img/check.png.import @@ -0,0 +1,35 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/check.png-77d9c6c18a6e8ab3e8f9b0823b5b1f3f.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/img/check.png" +dest_files=[ "res://.import/check.png-77d9c6c18a6e8ab3e8f9b0823b5b1f3f.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +process/normal_map_invert_y=false +stream=false +size_limit=0 +detect_3d=true +svg/scale=1.0 diff --git a/assets/img/cross.png b/assets/img/cross.png new file mode 100644 index 0000000..c026e68 Binary files /dev/null and b/assets/img/cross.png differ diff --git a/assets/img/cross.png.import b/assets/img/cross.png.import new file mode 100644 index 0000000..3096a08 --- /dev/null +++ b/assets/img/cross.png.import @@ -0,0 +1,35 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/cross.png-5021b7dd515961663820ff8784177d02.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/img/cross.png" +dest_files=[ "res://.import/cross.png-5021b7dd515961663820ff8784177d02.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +process/normal_map_invert_y=false +stream=false +size_limit=0 +detect_3d=true +svg/scale=1.0 diff --git a/icon.png b/assets/img/icon.png similarity index 100% rename from icon.png rename to assets/img/icon.png diff --git a/icon.png.import b/assets/img/icon.png.import similarity index 68% rename from icon.png.import rename to assets/img/icon.png.import index 96cbf46..2fd7d97 100644 --- a/icon.png.import +++ b/assets/img/icon.png.import @@ -2,15 +2,15 @@ importer="texture" type="StreamTexture" -path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" +path="res://.import/icon.png-859001d62588b6d9cb01a8eba79b6466.stex" metadata={ "vram_texture": false } [deps] -source_file="res://icon.png" -dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ] +source_file="res://assets/img/icon.png" +dest_files=[ "res://.import/icon.png-859001d62588b6d9cb01a8eba79b6466.stex" ] [params] @@ -28,6 +28,7 @@ process/fix_alpha_border=true process/premult_alpha=false process/HDR_as_SRGB=false process/invert_color=false +process/normal_map_invert_y=false stream=false size_limit=0 detect_3d=true diff --git a/theme.tres b/assets/theme.tres similarity index 50% rename from theme.tres rename to assets/theme.tres index 9fd7b85..17f63f3 100644 --- a/theme.tres +++ b/assets/theme.tres @@ -1,6 +1,6 @@ [gd_resource type="Theme" load_steps=2 format=2] -[ext_resource path="res://iosevkalyte.tres" type="DynamicFont" id=1] +[ext_resource path="res://assets/fonts/iosevkalyte/iosevkalyte.tres" type="DynamicFont" id=1] [resource] default_font = ExtResource( 1 ) diff --git a/project.godot b/project.godot index 5bbe40c..af45c66 100644 --- a/project.godot +++ b/project.godot @@ -11,12 +11,12 @@ config_version=4 [application] config/name="kdt" -run/main_scene="res://main.tscn" -config/icon="res://icon.png" +run/main_scene="res://screens/main.tscn" +config/icon="res://assets/img/icon.png" [autoload] -Global="*res://global.gd" +Global="*res://scripts/global.gd" [gdnative] @@ -36,4 +36,4 @@ quality/driver/driver_name="GLES2" vram_compression/import_etc=true vram_compression/import_etc2=false environment/default_clear_color=Color( 0, 0, 0, 1 ) -environment/default_environment="res://default_env.tres" +environment/default_environment="res://assets/default_env.tres" diff --git a/game.tscn b/screens/game.tscn similarity index 90% rename from game.tscn rename to screens/game.tscn index f74d35f..bde09c7 100644 --- a/game.tscn +++ b/screens/game.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=2 format=2] -[ext_resource path="res://game.gd" type="Script" id=1] +[ext_resource path="res://scripts/game.gd" type="Script" id=1] [node name="Node2D" type="Node2D"] script = ExtResource( 1 ) diff --git a/lobby.tscn b/screens/lobby.tscn similarity index 66% rename from lobby.tscn rename to screens/lobby.tscn index 1373cf9..059dc4b 100644 --- a/lobby.tscn +++ b/screens/lobby.tscn @@ -1,7 +1,7 @@ [gd_scene load_steps=3 format=2] -[ext_resource path="res://lobby.gd" type="Script" id=1] -[ext_resource path="res://theme.tres" type="Theme" id=2] +[ext_resource path="res://scripts/lobby.gd" type="Script" id=1] +[ext_resource path="res://assets/theme.tres" type="Theme" id=2] [node name="lobby" type="MarginContainer"] anchor_right = 1.0 @@ -34,14 +34,50 @@ __meta__ = { "_edit_use_anchors_": false } -[node name="lobby_info" type="Label" parent="v/head"] +[node name="lobby_info" type="LineEdit" parent="v/head"] margin_left = 154.0 +margin_top = 9.0 +margin_right = 356.0 +margin_bottom = 40.0 +size_flags_horizontal = 3 +size_flags_vertical = 4 +text = "Lobby Info" +placeholder_text = "Lobby Name" + +[node name="l1" type="Label" parent="v/head"] +margin_left = 360.0 +margin_top = 14.0 +margin_right = 464.0 +margin_bottom = 35.0 +text = " Max Players:" + +[node name="max_players" type="LineEdit" parent="v/head"] +margin_left = 468.0 +margin_top = 9.0 +margin_right = 574.0 +margin_bottom = 40.0 +size_flags_vertical = 4 +text = "20" + +[node name="lock" type="CheckButton" parent="v/head"] +margin_left = 578.0 +margin_right = 706.0 +margin_bottom = 50.0 +hint_tooltip = "Prevent players from joining to minimize \"Ready Up\" trolling" +text = "Locked" + +[node name="start" type="Button" parent="v/head"] +margin_left = 710.0 +margin_right = 860.0 +margin_bottom = 50.0 +rect_min_size = Vector2( 150, 0 ) +text = "Start Game" + +[node name="ready_up" type="CheckButton" parent="v/head"] +margin_left = 864.0 margin_right = 984.0 margin_bottom = 50.0 -size_flags_horizontal = 3 -size_flags_vertical = 3 -valign = 1 -autowrap = true +text = "Ready" __meta__ = { "_edit_use_anchors_": false } @@ -61,6 +97,7 @@ margin_bottom = 506.0 margin_right = 180.0 margin_bottom = 21.0 text = "Players: 1" +valign = 2 __meta__ = { "_edit_use_anchors_": false } @@ -73,6 +110,8 @@ rect_min_size = Vector2( 180, 0 ) size_flags_horizontal = 3 size_flags_vertical = 3 auto_height = true +icon_scale = 0.5 +fixed_icon_size = Vector2( 32, 32 ) [node name="v" type="VBoxContainer" parent="v/body"] margin_left = 184.0 @@ -83,28 +122,28 @@ size_flags_vertical = 3 [node name="chat_head" type="HBoxContainer" parent="v/body/v"] margin_right = 800.0 -margin_bottom = 29.0 +margin_bottom = 40.0 size_flags_horizontal = 3 [node name="label" type="Label" parent="v/body/v/chat_head"] -margin_right = 680.0 -margin_bottom = 29.0 +margin_right = 628.0 +margin_bottom = 40.0 size_flags_horizontal = 3 size_flags_vertical = 3 text = "Chat" -valign = 1 +valign = 2 -[node name="auto_scroll" type="CheckBox" parent="v/body/v/chat_head"] -margin_left = 684.0 +[node name="auto_scroll" type="CheckButton" parent="v/body/v/chat_head"] +margin_left = 632.0 margin_right = 800.0 -margin_bottom = 29.0 +margin_bottom = 40.0 size_flags_horizontal = 0 size_flags_vertical = 3 pressed = true text = "Auto Scroll" [node name="messages" type="TextEdit" parent="v/body/v"] -margin_top = 33.0 +margin_top = 44.0 margin_right = 800.0 margin_bottom = 471.0 size_flags_horizontal = 3 @@ -140,5 +179,7 @@ size_flags_vertical = 3 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="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/main.tscn b/screens/main.tscn similarity index 96% rename from main.tscn rename to screens/main.tscn index a3ea0df..c9163c1 100644 --- a/main.tscn +++ b/screens/main.tscn @@ -1,8 +1,8 @@ [gd_scene load_steps=5 format=2] -[ext_resource path="res://main.gd" type="Script" id=1] +[ext_resource path="res://scripts/main.gd" type="Script" id=1] [ext_resource path="res://assets/fonts/iosevkalyte/iosevkalyte-regular.ttf" type="DynamicFontData" id=2] -[ext_resource path="res://theme.tres" type="Theme" id=3] +[ext_resource path="res://assets/theme.tres" type="Theme" id=3] [sub_resource type="DynamicFont" id=1] size = 70 diff --git a/multiplayer.tscn b/screens/multiplayer.tscn similarity index 95% rename from multiplayer.tscn rename to screens/multiplayer.tscn index e169320..fcae2d5 100644 --- a/multiplayer.tscn +++ b/screens/multiplayer.tscn @@ -1,7 +1,7 @@ [gd_scene load_steps=3 format=2] -[ext_resource path="res://multiplayer.gd" type="Script" id=1] -[ext_resource path="res://theme.tres" type="Theme" id=2] +[ext_resource path="res://scripts/multiplayer.gd" type="Script" id=1] +[ext_resource path="res://assets/theme.tres" type="Theme" id=2] [node name="Control" type="MarginContainer"] anchor_right = 1.0 diff --git a/screens/test.tscn b/screens/test.tscn new file mode 100644 index 0000000..6099145 --- /dev/null +++ b/screens/test.tscn @@ -0,0 +1,18 @@ +[gd_scene format=2] + +[node name="Control" type="GridContainer"] +anchor_right = 1.0 +anchor_bottom = 1.0 +columns = 4 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="HBoxContainer" type="HBoxContainer" parent="."] +margin_bottom = 14.0 + +[node name="Label" type="Label" parent="."] +margin_left = 4.0 +margin_right = 69.0 +margin_bottom = 14.0 +text = "some text" diff --git a/game.gd b/scripts/game.gd similarity index 100% rename from game.gd rename to scripts/game.gd diff --git a/global.gd b/scripts/global.gd similarity index 100% rename from global.gd rename to scripts/global.gd diff --git a/lobby.gd b/scripts/lobby.gd similarity index 62% rename from lobby.gd rename to scripts/lobby.gd index c7deaaf..bdf94c7 100644 --- a/lobby.gd +++ b/scripts/lobby.gd @@ -1,23 +1,42 @@ extends MarginContainer onready var peers = $v/body/peers/peers -onready var lobby_info_label = $v/head/lobby_info +onready var lobby_name = $v/head/lobby_info onready var peers_list_label = $v/body/peers/label onready var cursors = {} onready var chat = $v/body/v/messages onready var chat_edit = $v/body/v/h/chat onready var auto_scroll_chk = $v/body/v/chat_head/auto_scroll +onready var max_players = $v/head/max_players +onready var ready_up = $v/head/ready_up +onready var lock = $v/head/lock +onready var start = $v/head/start + +onready var ready_tex: Texture = load("res://assets/img/check.png") +onready var not_ready_tex: Texture = load("res://assets/img/cross.png") +onready var can_start = true + +onready var peers_map = {} + func _ready(): Global.client.signaller.connect("peer_joined", self, "_peer_joined") Global.client.signaller.connect("peer_left", self, "_peer_left") # Global.client.connect("peer_disconnected", self, "_peer_left") Global.client.signaller.connect("lobby_left", self, "_lobby_left") - lobby_info_label.text = "%s%s" % [Global.client.signaller.lobby_name, _host_suffix(get_tree().get_network_unique_id())] + lobby_name.text = "%s" % Global.client.signaller.lobby_name Global.client.signaller.connect("websocket_disconnected", self, "_signaller_disconnected") Global.client.signaller.request_peer_list() call_deferred("add_chat", "# Connected to %s" % Global.client.signaller.lobby_name) + var is_host = Global.client.signaller.peer_id == 1 + if is_host: + ready_up.queue_free() + else: + max_players.editable = false + start.queue_free() + lock.queue_free() + func _host_suffix(id): if id == 1: return " (Host)" @@ -29,23 +48,27 @@ func _signaller_disconnected(): func _peer_joined(joined_peers): call_deferred("update_player_count") - print("Joined Peers: %s" % [joined_peers]) - for i in range(len(joined_peers)): - var id = joined_peers[i]["id"] - var exists = false - for j in range(peers.get_item_count()): - var md = peers.get_item_metadata(j) - if md and md.has("id") and md["id"] == id: - print("Already have this one") - exists = true - break - if not exists: - var peerId = joined_peers[i]["peerId"] - var name = joined_peers[i]["name"] - print("New Lobby Peer ID: %s, Name: %s, PeerID: %s" % [id, name, peerId]) - call_deferred("add_chat", "> %s joined the lobby" % name) - peers.add_item("%s%s" % [name, _host_suffix(peerId)]) - peers.set_item_metadata(peers.get_item_count() - 1, { "id": id, "peerId": peerId, "name": name }) + for peer_index in range(len(joined_peers)): + var peer = joined_peers[peer_index] + var id = peer["id"] + var peer_text = "%s%s" % [peer["name"], _host_suffix(peer["peerId"])] + if peers_map.has(id): + var i = peers_map[id] + var old_peer = peers.get_item_metadata(i) + peers.set_item_metadata(i, peer) + # announce changes? + else: + call_deferred("add_chat", "> %s joined the lobby" % peer["name"]) + var ready = false + var tex = not_ready_tex + if peerId == 1: + ready = true + tex = ready_tex + peers.add_item(peer_text, tex) + var i = peers.get_item_count() - 1 + peers.set_item_metadata(i, peer) + peers_map[id] = i + if !ready: can_start = false func _peer_left(ids): call_deferred("update_player_count") @@ -64,7 +87,12 @@ func update_player_count(): peers_list_label.text = "Players: %d" % peers.get_item_count() func _on_Button_pressed(): - send_chat_message() + send_chkkkkkkat_message() + +func _on_ready_up_toggled(button_pressed: bool): + +func _on_lobby_info_text_changed(new_text: String): + Global.client.signaller.set_lobby_name(new_text) remotesync func add_chat(message): if auto_scroll_chk.pressed: call_deferred("scroll_chat_to_bottom") diff --git a/main.gd b/scripts/main.gd similarity index 100% rename from main.gd rename to scripts/main.gd diff --git a/multiplayer.gd b/scripts/multiplayer.gd similarity index 70% rename from multiplayer.gd rename to scripts/multiplayer.gd index 0fadb3e..f8cae5a 100644 --- a/multiplayer.gd +++ b/scripts/multiplayer.gd @@ -9,6 +9,8 @@ onready var join_button = $v/head/join onready var lobbies_label = $v/subhead/label onready var display_name_edit = $v/subhead/display_name +onready var lobbies_map = {} + func _ready(): display_name_edit.text = Global.client.signaller.display_name join_button.disabled = true @@ -44,13 +46,21 @@ func _on_join_pressed(): Global.client.signaller.join_lobby(lobbies.get_item_metadata(items[0])["id"]) func _lobby_new(new_lobbies): - for i in range(len(new_lobbies)): - var id = new_lobbies[i]["id"] - var name = new_lobbies[i]["name"] - print("New Lobby ", id, name) - # TODO: could keep an index of IDs and indexes - lobbies.add_item(name) - lobbies.set_item_metadata(lobbies.get_item_count() - 1, { "id": id }) + # TODO: handle scrolling so that the user is never too jarred (like chat) + for lobby_index in range(len(new_lobbies)): + var lobby = new_lobbies[lobby_index] + var id = lobby["id"] + var lobby_text = "%s (%d/%d players)" % [lobby["name"], lobby["currentPlayers"], lobby["maxPlayers"]] + if lobbies_map.has(id): + print("Updated Lobby ", lobby) + var i = lobbies_map[id] + lobbies.set_item_metadata(i, lobby) + lobbies.set_item_text(i, lobby_text) + else: + print("New Lobby ", lobby) + lobbies.add_item(lobby_text) + lobbies_map[id] = lobbies.get_item_count() - 1 + lobbies.set_item_metadata(lobbies_map[id], lobby) if Global.join_first_available_lobby: Global.join_first_available_lobby = false @@ -58,10 +68,10 @@ func _lobby_new(new_lobbies): lobbies_label.text = "Active Lobbies: %d" % lobbies.get_item_count() func _lobby_delete(id): - for i in range(lobbies.get_item_count()): - if id == lobbies.get_item_metadata(i)["id"]: - lobbies.remove_item(i) - return + if lobbies_map.has(id): + var i = lobbies_map[id] + # TODO: this will break our current mappings due to re-order + lobbies.remove_item(i) lobbies_label.text = "Active Lobbies: %d" % lobbies.get_item_count() func _on_lobbies_item_activated(_index): diff --git a/multiplayer_client.gd b/scripts/multiplayer_client.gd similarity index 97% rename from multiplayer_client.gd rename to scripts/multiplayer_client.gd index 05fcd22..4dac680 100644 --- a/multiplayer_client.gd +++ b/scripts/multiplayer_client.gd @@ -4,8 +4,8 @@ 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 = "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"] }, diff --git a/signaller_client.gd b/scripts/signaller_client.gd similarity index 90% rename from signaller_client.gd rename to scripts/signaller_client.gd index b36c49d..d606af3 100644 --- a/signaller_client.gd +++ b/scripts/signaller_client.gd @@ -28,6 +28,8 @@ onready var peer_id = null onready var display_name = "UnnamedPlayer" onready var lobby_name = null +func is_host(): return peer_id == 1 + const DISPLAY_NAME_FILE = "user://display_name.txt" func _ready(): @@ -104,6 +106,18 @@ func set_display_name(new_display_name: String): func _send(s: String): return ws.get_peer(1).put_packet(s.to_utf8()) +func set_lobby_name(s: String): + if is_host(): + _send("json:%s" % JSON.print({"type": "update_lobby", "data": {"name": s}})) + +func lock_lobby(): + if is_host(): + _send("json:%s" % JSON.print({"type": "update_lobby", "data": {"locked": true}})) + +func set_lobby_max_players(n: int): + if is_host(): + _send("json:%s" % JSON.print({"type": "update_lobby", "data": {"maxPlayers": n}})) + func _parse_msg(): var msg: String = ws.get_peer(1).get_packet().get_string_from_utf8() if msg.begins_with("json:"): @@ -124,7 +138,7 @@ func handle_message(data: Dictionary): emit_signal("peer_id_set", peer_id) "lobby_list": emit_signal("lobby_new", data["data"]) - "lobby_new": emit_signal("lobby_new", [{"id": data["id"], "name": data["name"]}]) + "lobby_new": emit_signal("lobby_new", [data]) "lobby_delete": emit_signal("lobby_delete", data["id"]) "lobby_joined": @@ -139,10 +153,8 @@ func handle_message(data: Dictionary): "peer_joined": print(typeof(data), data) - if data.get("id") != null: - emit_signal("peer_joined", [{"id": data["id"], "name": data["name"], "peerId": data["peerId"]}]) - else: - emit_signal("peer_joined", 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"]) diff --git a/server.ts b/server.ts index 6f50384..2258dbc 100644 --- a/server.ts +++ b/server.ts @@ -1,6 +1,6 @@ // import { randomInt } from "./deps.ts"; -const SERVER_VERSION = "0.1.0"; +const SERVER_VERSION = "0.2.0"; // TODO: version comparison type ID = string; @@ -14,8 +14,11 @@ interface DataMessage { type: string; } -type ServerDataObject = Record; -type ServerData = ServerDataObject[] | ServerDataObject | string; +type ServerDataObject = Record< + string, + string | number | symbol | null | boolean +>; +type ServerData = ServerDataObject[] | ServerDataObject | string | boolean; type Message = string | DataMessage; const buildMessage = ( @@ -35,6 +38,7 @@ class Client { name: string; socket: WebSocket; lobby: Lobby | null; + ready: boolean; constructor(socket: WebSocket) { this.id = crypto.randomUUID(); @@ -42,6 +46,7 @@ class Client { this.peerId = null; this.name = "Client"; this.lobby = null; + this.ready = false; allClients.set(this.id, this); } @@ -50,6 +55,13 @@ 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()) { @@ -83,25 +95,54 @@ class Client { peerList() { if (!this.lobby) return; - const peers: { id: ID; name: string; peerId: number | null }[] = []; - this.lobby.clients.forEach(({ id, name, peerId }) => - peers.push({ id, name, peerId }) + 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.send(buildMessage("peer_joined", peers)); // TODO: chunk async? } lobbyList() { - const netLobbies: { id: ID; name: string }[] = []; - allLobbies.forEach(({ id, name }) => netLobbies.push({ id, name })); + const netLobbies: { + id: ID; + name: string; + maxPlayers: number; + 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)); } - lobbyNew({ id, name }: Lobby) { - // if the client is already in a lobby, we don't care about new lobbies + lobbyNew(lobby: Lobby) { + // if the client is already in a lobby, they don't care about new lobbies if (this.lobby) return; - this.send(buildMessage("lobby_new", { id, name })); + const { id, name, maxPlayers, locked } = lobby; + this.send( + buildMessage("lobby_new", { + id, + name, + maxPlayers, + locked, + currentPlayers: lobby.clients.size, + }), + ); } lobbyDelete({ id }: Lobby) { @@ -109,14 +150,14 @@ class Client { this.send(buildMessage("lobby_delete", { id })); } - lobbyCreate(name?: string) { + lobbyCreate(opts?: Partial) { if (this.lobby) { this.send( `[info] cannot create lobby (already in lobby ${this.lobby.id})`, ); return; } - new Lobby(this, name || `${this.name}'s lobby`); + new Lobby(this, opts?.name || `${this.name}'s lobby`, opts?.maxPlayers); } lobbyJoin(lobby: Lobby) { @@ -155,16 +196,54 @@ class Lobby { name: string; clients: Map; hostClientId: ID; + maxPlayers: number; + locked: boolean; - constructor(host: Client, name?: string) { + constructor(host: Client, name?: string, maxPlayers = 20) { this.id = crypto.randomUUID(); this.hostClientId = host.id; this.clients = new Map(); this.name = name || this.id; + this.maxPlayers = maxPlayers; + this.locked = false; allLobbies.set(this.id, this); host.peerId = 1; host.lobbyJoin(this); + this.notify(); + } + + update( + requestor: Client, + newValues: { name?: string; maxPlayers?: number; locked?: boolean }, + ) { + if (requestor.peerId === 1) { + for (const k in newValues) { + switch (k) { + case "name": + if (newValues?.name) this.name = newValues.name; + break; + case "maxPlayers": + if ( + newValues?.maxPlayers && typeof newValues.maxPlayers === "number" + ) { + this.maxPlayers = newValues.maxPlayers; + } + break; + case "locked": + if (newValues?.locked) this.locked = newValues.locked; + break; + } + } + this.notify(); + } else { + requestor.send( + "[info] you cannot update this lobby (you are not the host!)", + ); + } + } + + notify() { allClients.forEach((client) => client.lobbyNew(this)); } @@ -204,6 +283,7 @@ class Lobby { ), ); this.clients.set(client.id, client); + this.notify(); } removeClient({ id }: Client) { @@ -213,11 +293,18 @@ class Lobby { console.warn("Host left!"); this.remove(); } + this.notify(); } } interface ClientMessage { - type: "candidate" | "offer" | "answer" | "init" | "lobby_create"; + type: + | "candidate" + | "offer" + | "answer" + | "init" + | "lobby_create" + | "update_lobby"; data: ServerDataObject; } @@ -228,9 +315,18 @@ function onMessage(client: Client, ev: MessageEvent) { if (msg === "pong") return; console.log("Client Message Received", msg); if (msg === "init") { - client.send(buildMessage("init", { id: client.id, name: client.name })); + client.send( + buildMessage("init", { + id: client.id, + 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") { @@ -259,8 +355,14 @@ function onMessage(client: Client, ev: MessageEvent) { 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(data.data?.name?.toString()); + client.lobbyCreate({ name: data.data?.name?.toString() }); if (data.data.name) { client.name = data.data.name.toString(); }