WIP
This commit is contained in:
parent
25b93a9f2b
commit
d2abbb69f2
|
@ -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()
|
|
@ -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]
|
||||
|
|
|
@ -36,7 +36,7 @@ Global="*res://scripts/global/global.gd"
|
|||
|
||||
[gdnative]
|
||||
|
||||
singletons=[ "res://webrtc/webrtc.tres" ]
|
||||
singletons=[ "res://webrtc/webrtc_debug.tres" ]
|
||||
|
||||
[physics]
|
||||
|
||||
|
|
|
@ -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?"
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
|
@ -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))
|
||||
|
|
107
scripts/global/webrtc_negotiator.gd
Normal file
107
scripts/global/webrtc_negotiator.gd
Normal 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)
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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()
|
||||
|
|
255
server.ts
255
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<Lobby>) {
|
||||
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<Lobby>);
|
||||
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);
|
||||
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);
|
||||
// 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!)");
|
||||
client.send(buildMessage("info", `count not find lobby ${msg.data}`));
|
||||
}
|
||||
} 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}...`,
|
||||
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",
|
||||
),
|
||||
);
|
||||
iClient.send(payload);
|
||||
}
|
||||
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);
|
||||
|
|
Loading…
Reference in a new issue