This commit is contained in:
Daniel Flanagan 2021-12-08 23:39:56 -06:00
parent 25b93a9f2b
commit d2abbb69f2
15 changed files with 464 additions and 465 deletions

View file

@ -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()

View file

@ -3,7 +3,7 @@
[ext_resource path="res://assets/fonts/iosevkalyte/iosevkalyte.tres" type="DynamicFont" id=1] [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] [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 = {
"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 ), "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", "format": "RGB8",
@ -15,7 +15,7 @@ data = {
[sub_resource type="ImageTexture" id=2] [sub_resource type="ImageTexture" id=2]
flags = 4 flags = 4
flags = 4 flags = 4
image = SubResource( 1 ) image = SubResource( 5 )
size = Vector2( 8, 8 ) size = Vector2( 8, 8 )
[sub_resource type="StyleBoxTexture" id=3] [sub_resource type="StyleBoxTexture" id=3]

View file

@ -36,7 +36,7 @@ Global="*res://scripts/global/global.gd"
[gdnative] [gdnative]
singletons=[ "res://webrtc/webrtc.tres" ] singletons=[ "res://webrtc/webrtc_debug.tres" ]
[physics] [physics]

View file

@ -62,10 +62,10 @@ __meta__ = {
} }
[node name="subhead" type="HBoxContainer" parent="v"] [node name="subhead" type="HBoxContainer" parent="v"]
visible = false
margin_top = 54.0 margin_top = 54.0
margin_right = 984.0 margin_right = 984.0
margin_bottom = 85.0 margin_bottom = 85.0
size_flags_horizontal = 3
[node name="label" type="Label" parent="v/subhead"] [node name="label" type="Label" parent="v/subhead"]
margin_right = 830.0 margin_right = 830.0
@ -83,7 +83,7 @@ rect_min_size = Vector2( 150, 0 )
placeholder_text = "Your Name Here" placeholder_text = "Your Name Here"
[node name="body" type="ScrollContainer" parent="v"] [node name="body" type="ScrollContainer" parent="v"]
margin_top = 54.0 margin_top = 89.0
margin_right = 984.0 margin_right = 984.0
margin_bottom = 560.0 margin_bottom = 560.0
size_flags_horizontal = 3 size_flags_horizontal = 3
@ -91,7 +91,7 @@ size_flags_vertical = 3
[node name="p" type="PanelContainer" parent="v/body"] [node name="p" type="PanelContainer" parent="v/body"]
margin_right = 984.0 margin_right = 984.0
margin_bottom = 506.0 margin_bottom = 471.0
size_flags_horizontal = 3 size_flags_horizontal = 3
size_flags_vertical = 3 size_flags_vertical = 3
@ -99,21 +99,21 @@ size_flags_vertical = 3
margin_left = 7.0 margin_left = 7.0
margin_top = 7.0 margin_top = 7.0
margin_right = 977.0 margin_right = 977.0
margin_bottom = 499.0 margin_bottom = 464.0
size_flags_horizontal = 3 size_flags_horizontal = 3
size_flags_vertical = 3 size_flags_vertical = 3
[node name="zero_state" type="CenterContainer" parent="v/body/p/lobbies"] [node name="zero_state" type="CenterContainer" parent="v/body/p/lobbies"]
margin_right = 970.0 margin_right = 970.0
margin_bottom = 492.0 margin_bottom = 457.0
size_flags_horizontal = 3 size_flags_horizontal = 3
size_flags_vertical = 3 size_flags_vertical = 3
[node name="Label" type="Label" parent="v/body/p/lobbies/zero_state"] [node name="Label" type="Label" parent="v/body/p/lobbies/zero_state"]
margin_left = 177.0 margin_left = 177.0
margin_top = 235.0 margin_top = 218.0
margin_right = 793.0 margin_right = 793.0
margin_bottom = 256.0 margin_bottom = 239.0
size_flags_horizontal = 3 size_flags_horizontal = 3
size_flags_vertical = 3 size_flags_vertical = 3
text = "Looks like there are no active lobbies at the moment! Why don't you make one?" text = "Looks like there are no active lobbies at the moment! Why don't you make one?"

View file

@ -107,6 +107,6 @@ hint_tooltip = "Open https://lyte.dev"
text = "lytedev" text = "lytedev"
[connection signal="pressed" from="VBoxContainer/HBoxContainer/quit" to="." method="_on_JoinLobbyButton_pressed"] [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/HBoxContainer/Singleplayer" to="." method="_on_Singleplayer_pressed"]
[connection signal="pressed" from="VBoxContainer/Container/VBoxContainer/HBoxContainer/LinkButton" to="." method="_on_LinkButton_pressed"] [connection signal="pressed" from="VBoxContainer/Container/VBoxContainer/HBoxContainer/LinkButton" to="." method="_on_LinkButton_pressed"]

View file

@ -185,6 +185,7 @@ text = "Send Message"
[connection signal="pressed" from="v/head/leave_button" to="." method="_on_leave_button_pressed"] [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="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="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="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"] [connection signal="pressed" from="v/body/v/h/Button" to="." method="_on_Button_pressed"]

View file

@ -1,52 +1,74 @@
extends Node 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 const SignallerClient = preload("signaller_client.gd")
onready var goto_multiplayer = false const WebRTCNegotiator = preload("webrtc_negotiator.gd")
onready var create_lobby = false
onready var join_first_available_lobby = false 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(): func _ready():
# client.signaller.connect("websocket_connected", self, "signaller_disconnected") negotiator.signaller_client = signaller_client
add_child(client) negotiator.ice_servers = ice_servers
client.signaller.connect("websocket_connected", self, "_signaller_connected") add_child(negotiator)
for arg in OS.get_cmdline_args(): for flag in onetime_cmd_flags.keys():
match arg: if flag in OS.get_cmdline_args():
"--multiplayer": goto_multiplayer = true onetime_cmd_flags[flag] = 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()
func goto_scene(scene_resource_name): func goto_scene(scene_resource_name):
var _result = get_tree().change_scene("res://screens/%s.tscn" % scene_resource_name) var _result = get_tree().change_scene("res://screens/%s.tscn" % scene_resource_name)
func main_menu(): func main_menu():
client.close() negotiator.close()
goto_scene("main_menu") 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(): func start_singleplayer_game():
client.fake_singleplayer() fake_singleplayer()
client.close() negotiator.close()
goto_game()
func goto_game():
goto_scene("game") goto_scene("game")
func _signaller_connected(): func _signaller_connected():
goto_scene("lobby_browser") goto_scene("lobby_browser")
func lobby_browser(): func lobby_browser():
client.close() negotiator.close()
Global.client.connect_to_signaller() signaller_client.connect("connected", self, "_signaller_connected")
negotiator.connect_to_signaller()
func lobby(): func lobby():
goto_scene("multiplayer_lobby") goto_scene("multiplayer_lobby")
func quit(): func quit():
client.close() negotiator.close()
get_tree().quit() get_tree().quit()

View file

@ -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)

View file

@ -3,168 +3,154 @@ extends Node
""" """
This module is responsible for making a WebSocket connection to the signaller This module is responsible for making a WebSocket connection to the signaller
in order to enable establishish WebRTC P2P connections. Another module is 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) const DISPLAY_NAME_FILE = "user://display_name.txt"
signal lobby_delete(id) const DEFAULT_DISPLAY_NAME = "UnnamedPlayer"
signal lobby_joined(id, peer_id)
signal lobby_left(id)
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 peer_left(id)
signal candidate_received(data)
# WebRTC negotiations
signal candidate_received(cand)
signal offer_received(data) signal offer_received(data)
signal answer_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 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 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(): func _ready():
var dnf = File.new() display_name = _load_display_name()
if dnf.open(DISPLAY_NAME_FILE, File.READ) == OK: websocket.connect("data_received", self, "_handle_data")
display_name = dnf.get_as_text() websocket.connect("connection_established", self, "_connected")
dnf.close() # websocket.connect("connection_succeeded", self, "_connection_succeeded")
var _result = ws.connect("data_received", self, "_parse_msg") websocket.connect("connection_closed", self, "_closed")
_result = ws.connect("connection_established", self, "_connected") websocket.connect("connection_error", self, "_connection_error", [false])
_result = ws.connect("connection_closed", self, "_closed") websocket.connect("connection_failed", self, "_connection_error", [false])
_result = ws.connect("connection_error", self, "_closed", [false]) websocket.connect("server_close_request", self, "_close_request")
_result = ws.connect("connection_failed", self, "_closed", [false])
_result = ws.connect("connection_succeeded", self, "_succ")
_result = ws.connect("server_close_request", self, "_close_request")
func _succ(): func connect_websocket():
print("WebSocket Connection Succeeded") 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(): func close():
ws.disconnect_from_host() websocket.disconnect_from_host()
func connect_to_websocket_signaller(url: String): func set_display_name(new_display_name: String):
print("WebSocket: %s" % ws) display_name = new_display_name
close() _send("update_display_name:%s" % display_name)
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 _closed(unknown): func request_lobby_list():
print("WebSocket closed: %s: " % unknown) return _send("request_lobby_list")
emit_signal("websocket_disconnected")
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): func join_lobby(id: String):
return _send("lobby_join:%s" % id) return _send("lobby_join:%s" % id)
func create_lobby(): func create_lobby():
return _send("lobby_create") return _send("lobby_create")
func request_lobby_list(): func is_host(): return peer_id == 1
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 set_lobby_name(s: String): func set_lobby_name(s: String):
if is_host(): if is_host(): _send_json({"name": s}, "update_lobby")
_send("json:%s" % JSON.print({"type": "update_lobby", "data": {"name": s}}))
func lock_lobby(): func lock_lobby():
if is_host(): if is_host(): _send_json({"locked": true}, "update_lobby")
_send("json:%s" % JSON.print({"type": "update_lobby", "data": {"locked": true}}))
func set_lobby_max_players(n: int): func set_lobby_max_players(n: int):
if is_host(): if is_host(): _send_json({"maxPlayers": n}, "update_lobby")
_send("json:%s" % JSON.print({"type": "update_lobby", "data": {"maxPlayers": n}}))
func set_ready(n: bool): func request_peer_list(): _send("request_peer_list")
if !is_host():
_send("set_ready:%s" % JSON.print(n))
func _parse_msg(): func send_candidate(peerId, mid, index, sdp) -> int:
var msg: String = ws.get_peer(1).get_packet().get_string_from_utf8() 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:"): if msg.begins_with("json:"):
var data = JSON.parse(msg.substr(5)) var data = JSON.parse(msg.substr(5))
if data.error == OK: if data.error == OK:
handle_message(data.result) _process_message(data.result)
var _result = ws.get_peer(1).put_packet("".to_utf8()) var _result = websocket.get_peer(1).put_packet("".to_utf8())
else: else:
print("Unhandled message: %s" % msg) print("Unhandled message: %s" % msg)
func handle_message(data: Dictionary): func _process_message(data: Dictionary):
match data["type"]: match data["type"]:
"your_id": "init":
client_id = data["data"]["id"] client_id = data.id
emit_signal("client_id_set", client_id) print("Signaller Received init response: %s" % data)
"your_peer_id": "your_peer_id":
peer_id = int(data["data"]) peer_id = int(data.peerId)
emit_signal("peer_id_set", peer_id) emit_signal("peer_id_set", peer_id)
"lobby_list": emit_signal("lobby_new", data["data"]) "lobby_data": emit_signal("lobby_data_received", data.data)
"lobby_new": emit_signal("lobby_new", [data]) "lobby_delete": emit_signal("lobby_delete", data.id)
"lobby_delete": emit_signal("lobby_delete", data["id"])
"lobby_joined": "lobby_joined":
lobby_id = data["id"] peer_id = data.peerId
lobby_name = data["name"] emit_signal("lobby_joined", data)
peer_id = data["peerId"]
emit_signal("lobby_joined", data["id"], data["peerId"])
"lobby_left": "lobby_left":
lobby_id = null peer_id = null
lobby_name = null
emit_signal("lobby_left", data["id"]) emit_signal("lobby_left", data["id"])
"peer_joined": "peer_data":
print(typeof(data), data) print(typeof(data), data)
if data.get("id") != null: emit_signal("peer_joined", [data]) if data.get("id") != null: emit_signal("peer_joined", [data])
else: emit_signal("peer_joined", data["data"]) else: emit_signal("peer_joined", data["data"])
"peer_left": "peer_left":
print("Peer Left: %s" % data) print("Peer Left: %s" % data)
emit_signal("peer_left", data["data"]) emit_signal("peer_left", data)
"ready_change":
print("Peer Ready Change")
emit_signal("peer_joined", [data])
"candidate": "candidate":
print("Candidate received - Data: %s" % JSON.print(data["data"])) 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"])) print("Answer received - Data: %s" % JSON.print(data["data"]))
emit_signal("answer_received", data["data"]) emit_signal("answer_received", data["data"])
"ping": "ping":
# print("Signaller Ping")
_send("pong") _send("pong")
_: print("Unhandled Message - Data: %s" % JSON.print(data)) _: print("Unhandled Message - Data: %s" % JSON.print(data))
func send_candidate(peerId, mid, index, sdp) -> int: func _load_display_name():
return _send("json:%s" % JSON.print({ var _display_name = DEFAULT_DISPLAY_NAME
"type": "candidate", var display_name_file = File.new()
"data": { if display_name_file.open(DISPLAY_NAME_FILE, File.READ) == OK:
"peerId": peerId, _display_name = display_name_file.get_as_text()
"mid": mid, else:
"index": index, print("Failed to open %s for reading display_name, creating default" % DISPLAY_NAME_FILE)
"sdp": sdp _store_display_name()
} display_name_file.close()
})) return _display_name
func send_offer(peerId, offer) -> int: func _store_display_name():
return _send("json:%s" % JSON.print({ "type": "offer", "data": {"peerId": peerId, "offer": offer }})) 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: func _send(s: String):
return _send("json:%s" % JSON.print({ "type": "answer", "data": {"peerId": peerId, "answer": answer }})) return websocket.get_peer(1).put_packet(s.to_utf8())
""" func _send_json(data: Dictionary, type=null):
elif type.begins_with("S: "): if type != null: data["type"] = type
emit_signal("lobby_sealed") _send("json:%s" % JSON.print(data))
return
"""

View file

@ -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)

View file

@ -12,6 +12,8 @@ remotesync func add_player(peer_id):
var new_player = player.instance() var new_player = player.instance()
new_player.set_network_master(peer_id) new_player.set_network_master(peer_id)
add_child(new_player) add_child(new_player)
new_player.global_position = Vector2(100, 100)
print("Added player: %s" % new_player)
func _on_Button_pressed(): func _on_Button_pressed():
Global.main_menu() Global.main_menu()

View file

@ -14,17 +14,16 @@ onready var lobbies = {}
onready var lobby = preload("res://objects/lobby.tscn") onready var lobby = preload("res://objects/lobby.tscn")
func _ready(): func _ready():
display_name_edit.text = Global.client.signaller.display_name display_name_edit.text = Global.signaller_client.display_name
Global.client.signaller.connect("lobby_new", self, "_lobby_new") Global.signaller_client.connect("lobby_new", self, "_lobby_new")
Global.client.signaller.connect("lobby_delete", self, "_lobby_delete") Global.signaller_client.connect("lobby_delete", self, "_lobby_delete")
Global.client.signaller.connect("lobby_joined", self, "_lobby_joined") Global.signaller_client.connect("lobby_joined", self, "_lobby_joined")
Global.client.signaller.connect("lobby_left", self, "_lobby_left") Global.signaller_client.connect("lobby_left", self, "_lobby_left")
Global.client.signaller.connect("websocket_disconnected", self, "_signaller_disconnected") Global.signaller_client.connect("websocket_disconnected", self, "_signaller_disconnected")
Global.client.signaller.request_lobby_list() Global.signaller_client.request_lobby_list()
if Global.create_lobby: if Global.check_onetime_flag("create-lobby"):
Global.create_lobby = false Global.signaller_client.create_lobby()
Global.client.signaller.create_lobby()
""" """
var ls = [] var ls = []
@ -35,13 +34,14 @@ func _ready():
# TODO: add search # TODO: add search
func _input(ev): func _input(ev):
if ev is InputEventKey and ev.pressed: pass
if ev.scancode == KEY_T: # if ev is InputEventKey and ev.pressed:
var ls = [] # if ev.scancode == KEY_T:
for n in range(5): # var ls = []
var j = n + lobbies.size() # for n in range(5):
ls.push_back({id = j, name = j, currentPlayers = j, maxPlayers = j, locked = false}) # var j = n + lobbies.size()
call_deferred("_lobby_new", ls) # ls.push_back({id = j, name = j, currentPlayers = j, maxPlayers = j, locked = false})
# call_deferred("_lobby_new", ls)
func _signaller_disconnected(): func _signaller_disconnected():
Global.main_menu() Global.main_menu()
@ -56,12 +56,12 @@ func _on_back_pressed():
Global.main_menu() Global.main_menu()
func _on_create_lobby_pressed(): func _on_create_lobby_pressed():
Global.client.signaller.create_lobby() Global.signaller_client.create_lobby()
func _on_join_pressed(): func _on_join_pressed():
var items = lobbies.get_selected_items() var items = lobbies.get_selected_items()
if len(items) > 0: 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): func _lobby_new(new_lobbies: Array):
# TODO: handle scrolling so that the user is never too jarred (like chat) # 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: if Global.join_first_available_lobby:
Global.join_first_available_lobby = false Global.join_first_available_lobby = false
Global.client.signaller.join_lobby(id) Global.signaller_client.join_lobby(id)
func _lobby_delete(id: String): func _lobby_delete(id: String):
print("Lobby Deleted: %s" % id) print("Lobby Deleted: %s" % id)
_delete_lobby(id) _delete_lobby(id)
func _add_lobby(id, lobby_data): func _add_lobby(id, lobby_data):
call_deferred("update_lobbies_text") call_deferred("_update_lobbies_text")
print("New Lobby ", lobby_data) print("New Lobby ", lobby_data)
# if lobby_data.currentPlayers > 0: # if lobby_data.currentPlayers > 0:
var new_lobby = lobby.instance() var new_lobby = lobby.instance()
@ -91,19 +91,19 @@ func _add_lobby(id, lobby_data):
lobbies[id] = new_lobby lobbies[id] = new_lobby
func _update_lobby(id, lobby_data): func _update_lobby(id, lobby_data):
call_deferred("update_lobbies_text") call_deferred("_update_lobbies_text")
print("Updated Lobby ", lobby_data) print("Updated Lobby ", lobby_data)
lobbies[id].set_with_dict(lobby_data) lobbies[id].set_with_dict(lobby_data)
func _delete_lobby(id): func _delete_lobby(id):
call_deferred("update_lobbies_text") call_deferred("_update_lobbies_text")
if lobbies.has(id): if lobbies.has(id):
print("Removing lobby...") print("Removing lobby...")
lobbies_grid.remove_child(lobbies[id]) lobbies_grid.remove_child(lobbies[id])
lobbies[id].queue_free() lobbies[id].queue_free()
lobbies.erase(id) lobbies.erase(id)
func update_lobbies_text(): func _update_lobbies_text():
var n = lobbies.size() var n = lobbies.size()
lobbies_label.text = "Active Lobbies: %d" % n lobbies_label.text = "Active Lobbies: %d" % n
if n < 1: if n < 1:
@ -114,4 +114,4 @@ func update_lobbies_text():
lobbies_grid.remove_child(zero_state) lobbies_grid.remove_child(zero_state)
func _on_display_name_text_changed(new_text): func _on_display_name_text_changed(new_text):
Global.client.signaller.set_display_name(new_text) Global.signaller_client.display_name = new_text

View file

@ -1,14 +1,17 @@
extends Node extends Node
func _ready():
if Global.check_onetime_flag("multiplayer"):
_on_multiplayer_pressed()
func _on_Singleplayer_pressed(): func _on_Singleplayer_pressed():
Global.start_singleplayer_game() Global.start_singleplayer_game()
func _on_CreateLobbyButton_pressed(): func _on_multiplayer_pressed():
Global.lobby_browser() Global.lobby_browser()
func _on_JoinLobbyButton_pressed(): func _on_JoinLobbyButton_pressed():
Global.quit() Global.quit()
func _on_LinkButton_pressed(): func _on_LinkButton_pressed():
OS.shell_open("https://lyte.dev") OS.shell_open("https://lyte.dev")

View file

@ -29,7 +29,7 @@ func _ready():
call_deferred("add_chat", "# Connected to %s" % Global.client.signaller.lobby_name) call_deferred("add_chat", "# Connected to %s" % Global.client.signaller.lobby_name)
# hide/show controls depending on whether or not we're the host # 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: if is_host:
ready_up.queue_free() ready_up.queue_free()
else: else:
@ -37,6 +37,7 @@ func _ready():
max_players.editable = false max_players.editable = false
start.queue_free() start.queue_free()
lock.queue_free() lock.queue_free()
print("Setting up lobby... %s %s" % [Global.client.signaller.peer_id, is_host])
func _lobby_update(l): func _lobby_update(l):
for u in l: for u in l:
@ -48,7 +49,7 @@ func _lobby_update(l):
func _peer_joined(joined_peers): func _peer_joined(joined_peers):
call_deferred("update_player_count") 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)): for peer_index in range(len(joined_peers)):
var peer_data = joined_peers[peer_index] var peer_data = joined_peers[peer_index]
var id = peer_data["id"] 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") if can_start: add_chat("! All players ready - game may now be started")
start.disabled = !can_start 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 _lobby_left(_id): Global.lobby_browser()
func _on_leave_button_pressed(): Global.lobby_browser() func _on_leave_button_pressed(): Global.lobby_browser()
func _on_TextEdit_text_entered(_new_text): send_chat_message() func _on_TextEdit_text_entered(_new_text): send_chat_message()

265
server.ts
View file

@ -15,7 +15,7 @@ interface DataMessage {
} }
type ServerDataObject = Record< type ServerDataObject = Record<
string, "peerId" | string,
string | number | symbol | null | boolean string | number | symbol | null | boolean
>; >;
type ServerData = ServerDataObject[] | ServerDataObject | string | boolean; type ServerData = ServerDataObject[] | ServerDataObject | string | boolean;
@ -24,13 +24,11 @@ type Message = string | DataMessage;
const buildMessage = ( const buildMessage = (
type: string, type: string,
data: ServerData | ServerData[], data: ServerData | ServerData[],
) => ) => {
Object.assign( if (Array.isArray(data)) return { type, data };
{ type }, else if (typeof data === "object") return { type, ...data };
Array.isArray(data) return `${type}:${data}`;
? { data } };
: (typeof data === "object" ? data : { data }),
);
class Client { class Client {
id: ID; id: ID;
@ -38,7 +36,6 @@ class Client {
name: string; name: string;
socket: WebSocket; socket: WebSocket;
lobby: Lobby | null; lobby: Lobby | null;
ready: boolean;
constructor(socket: WebSocket) { constructor(socket: WebSocket) {
this.id = crypto.randomUUID(); this.id = crypto.randomUUID();
@ -46,7 +43,6 @@ class Client {
this.peerId = null; this.peerId = null;
this.name = "Client"; this.name = "Client";
this.lobby = null; this.lobby = null;
this.ready = false;
allClients.set(this.id, this); allClients.set(this.id, this);
} }
@ -55,13 +51,6 @@ class Client {
return this.socket.readyState == WebSocket.OPEN; 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() { remove() {
this.lobbyLeave(); this.lobbyLeave();
if (this.isConnected()) { if (this.isConnected()) {
@ -94,17 +83,24 @@ class Client {
} }
peerList() { 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: { const peers: {
id: ID; id: ID;
name: string; name: string;
peerId: number | null; peerId: number | null;
ready: boolean;
}[] = []; }[] = [];
this.lobby.clients.forEach(({ id, name, peerId, ready }) => this.lobby.clients.forEach(({ id, name, peerId }) =>
peers.push({ id, name, peerId, ready }) peers.push({ id, name, peerId })
); );
this.send(buildMessage("peer_joined", peers)); this.send(buildMessage("peer_data", peers));
// TODO: chunk async? // TODO: chunk async?
} }
@ -116,32 +112,15 @@ class Client {
locked: boolean; locked: boolean;
currentPlayers: number; currentPlayers: number;
}[] = []; }[] = [];
allLobbies.forEach((lobby) => { allLobbies.forEach((lobby) => netLobbies.push(lobby.toData()));
const { id, name, maxPlayers, locked } = lobby; this.send(buildMessage("lobby_data", netLobbies));
netLobbies.push({
id,
name,
maxPlayers,
locked,
currentPlayers: lobby.clients.size,
});
});
// TODO: chunk async?
this.send(buildMessage("lobby_list", netLobbies));
} }
lobbyNew(lobby: Lobby) { lobbyNew(lobby: Lobby) {
// if the client is already in a lobby, only send messages about their lobby // if the client is already in a lobby, do not send them lobby updates
if (this.lobby && this.lobby != lobby) return; if (this.lobby) return;
const { id, name, maxPlayers, locked } = lobby;
this.send( this.send(
buildMessage("lobby_new", { buildMessage("lobby_data", [lobby.toData()]),
id,
name,
maxPlayers,
locked,
currentPlayers: lobby.clients.size,
}),
); );
} }
@ -151,10 +130,12 @@ class Client {
} }
lobbyCreate(opts?: Partial<Lobby>) { lobbyCreate(opts?: Partial<Lobby>) {
this.ready = true;
if (this.lobby) { if (this.lobby) {
this.send( this.send(
`[info] cannot create lobby (already in lobby ${this.lobby.id})`, buildMessage(
"info",
`cannot create lobby: already in lobby ${this.lobby.id}`,
),
); );
return; return;
} }
@ -163,22 +144,25 @@ class Client {
lobbyJoin(lobby: Lobby) { lobbyJoin(lobby: Lobby) {
if (this.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; return;
} }
this.lobby = lobby; this.lobby = lobby;
lobby.addClient(this); lobby.addClient(this);
this.send( this.send(
buildMessage("lobby_joined", { buildMessage("lobby_joined", {
id: lobby.id, ...this.lobby.toData(),
name: lobby.name,
peerId: this.peerId, peerId: this.peerId,
}), }),
); );
} }
lobbyLeave() { lobbyLeave() {
this.ready = false;
const leavingLobby = this.lobby; const leavingLobby = this.lobby;
if (!leavingLobby) { if (!leavingLobby) {
this.send(`[info] cannot leave lobby (not in a lobby)`); this.send(`[info] cannot leave lobby (not in a lobby)`);
@ -219,6 +203,16 @@ class Lobby {
this.notify(); this.notify();
} }
toData() {
return {
id: this.id,
name: this.name,
maxPlayers: this.maxPlayers,
locked: this.locked,
currentPlayers: this.clients.size,
};
}
update( update(
requestor: Client, requestor: Client,
newValues: { name?: string; maxPlayers?: number; locked?: boolean }, newValues: { name?: string; maxPlayers?: number; locked?: boolean },
@ -273,18 +267,17 @@ class Lobby {
crypto.getRandomValues(arr); crypto.getRandomValues(arr);
client.peerId = Math.abs(arr[0]); 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( this.broadcast(
buildMessage("peer_joined", [{ buildMessage("peer_data", [{
id: client.id, id: client.id,
name: client.name, name: client.name,
peerId: client.peerId, peerId: client.peerId,
}]), }]),
); );
console.log("Sending peer_joined...");
client.send( client.send(
buildMessage( buildMessage(
"peer_joined", "peer_data",
Array.from(this.clients.values()).map( Array.from(this.clients.values()).map(
({ id, name, peerId }) => ({ id, name, peerId }), ({ id, name, peerId }) => ({ id, name, peerId }),
), ),
@ -303,6 +296,13 @@ class Lobby {
} }
this.notify(); this.notify();
} }
getPeer(peerId: number): Client | null {
for (const [_id, client] of this.clients) {
if (client.peerId == peerId) return client;
}
return null;
}
} }
interface ClientMessage { interface ClientMessage {
@ -316,86 +316,102 @@ interface ClientMessage {
data: ServerDataObject; 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 // events
function onMessage(client: Client, ev: MessageEvent) { function onMessage(client: Client, ev: MessageEvent) {
// TODO: log who from? // TODO: log who from?
const msg = ev.data.trim(); const msg = parseMessage(ev.data);
if (msg === "pong") return; if (msg.type === "pong") return;
console.log("Client Message Received", msg); console.log("Client Message Received", msg);
if (msg === "init") { switch (msg.type) {
client.send( case "init":
buildMessage("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, id: client.id,
peerId: client.peerId,
name: client.name, name: client.name,
serverVersion: SERVER_VERSION, serverVersion: SERVER_VERSION,
}), }));
); break;
} case "lobby_create":
if (msg === "lobby_create") client.lobbyCreate(); client.lobbyCreate(msg.data as Partial<Lobby>);
if (msg.startsWith("set_ready:")) { break;
client.setReady(JSON.parse(msg.split(":", 2)[1])); case "lobby_leave":
} client.lobbyLeave();
if (msg === "lobby_leave") client.lobbyLeave(); break;
if (msg === "request_lobby_list") client.lobbyList(); case "request_lobby_list":
if (msg === "request_peer_list") { client.lobbyList();
if (client.lobby == null) { break;
client.send(`[info] not in a lobby`); case "request_peer_list":
} else {
client.peerList(); client.peerList();
} break;
} case "update_display_name":
if (msg.startsWith("update_display_name:")) { if (msg.data) client.name = msg.data.toString();
const displayName = msg.substr("update_display_name:".indexOf(":") + 1); break;
client.name = displayName; case "lobby_join":
} if (msg.data) {
if (msg.startsWith("lobby_join:")) { const lobby = allLobbies.get(msg.data.toString());
const id = msg.substr(11); if (lobby) {
const lobby = allLobbies.get(id); client.lobbyJoin(lobby);
if (lobby) { } else {
client.lobbyJoin(lobby); client.send(buildMessage("info", `count not find lobby ${msg.data}`));
// 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_lobby":
if (client.lobby) {
if (typeof (msg.data) === "object") {
client.lobby.update(client, msg.data as Partial<Lobby>);
}
} 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); console.log("Listening on port", PORT);
const listener = Deno.listen({ port: PORT }); const listener = Deno.listen({ port: PORT });
for await (const conn of listener) { for await (const conn of listener) {
// console.debug("Connection received:", conn);
(async () => { (async () => {
const server = Deno.serveHttp(conn); const server = Deno.serveHttp(conn);
for await (const { respondWith, request } of server) { for await (const { respondWith, request } of server) {
// console.debug("HTTP Request Received", request);
try { try {
// console.warn(JSON.stringify([allClients, allLobbies]));
const { socket, response } = Deno.upgradeWebSocket(request); const { socket, response } = Deno.upgradeWebSocket(request);
const client = new Client(socket); const client = new Client(socket);
socket.onmessage = (ev) => onMessage(client, ev); socket.onmessage = (ev) => onMessage(client, ev);