From e292916886faae37c55122a0617a84f099964404 Mon Sep 17 00:00:00 2001 From: Relintai Date: Thu, 6 Apr 2023 23:01:47 +0200 Subject: [PATCH] Make sure everything works in the networking folder. Also removed webrtc examples. --- networking/multiplayer_bomber/gamestate.gd | 13 +- networking/multiplayer_bomber/player.gd | 26 +- .../multiplayer_bomber/project.pandemonium | 8 - networking/multiplayer_bomber/rock.gd | 8 +- networking/multiplayer_bomber/score.gd | 8 +- networking/multiplayer_pong/logic/ball.gd | 12 +- networking/multiplayer_pong/logic/paddle.gd | 6 +- networking/multiplayer_pong/logic/pong.gd | 4 +- .../multiplayer_pong/project.pandemonium | 5 - networking/webrtc_minimal/.gitignore | 1 - networking/webrtc_minimal/README.md | 9 - networking/webrtc_minimal/Signaling.gd | 31 -- networking/webrtc_minimal/chat.gd | 39 --- networking/webrtc_minimal/link_button.gd | 4 - networking/webrtc_minimal/main.gd | 17 - networking/webrtc_minimal/main.tscn | 30 -- networking/webrtc_minimal/minimal.gd | 41 --- networking/webrtc_minimal/minimal.tscn | 6 - networking/webrtc_minimal/project.pandemonium | 35 -- networking/webrtc_signaling/.gitignore | 1 - networking/webrtc_signaling/README.md | 44 --- .../client/multiplayer_client.gd | 100 ------ .../client/ws_webrtc_client.gd | 129 ------- networking/webrtc_signaling/demo/client_ui.gd | 79 ----- .../webrtc_signaling/demo/client_ui.tscn | 107 ------ networking/webrtc_signaling/demo/main.gd | 16 - networking/webrtc_signaling/demo/main.tscn | 100 ------ .../webrtc_signaling/project.pandemonium | 45 --- .../webrtc_signaling/screenshots/.gdignore | 0 .../screenshots/screenshot.png | Bin 20210 -> 0 bytes .../server/ws_webrtc_server.gd | 210 ------------ .../server_node/.eslintrc.json | 318 ------------------ .../webrtc_signaling/server_node/.gitignore | 1 - .../webrtc_signaling/server_node/package.json | 17 - .../webrtc_signaling/server_node/server.js | 252 -------------- networking/websocket_chat/project.pandemonium | 1 - .../websocket_minimal/project.pandemonium | 1 - .../websocket_multiplayer/project.pandemonium | 1 - .../websocket_multiplayer/script/game.gd | 28 +- 39 files changed, 76 insertions(+), 1677 deletions(-) delete mode 100644 networking/webrtc_minimal/.gitignore delete mode 100644 networking/webrtc_minimal/README.md delete mode 100644 networking/webrtc_minimal/Signaling.gd delete mode 100644 networking/webrtc_minimal/chat.gd delete mode 100644 networking/webrtc_minimal/link_button.gd delete mode 100644 networking/webrtc_minimal/main.gd delete mode 100644 networking/webrtc_minimal/main.tscn delete mode 100644 networking/webrtc_minimal/minimal.gd delete mode 100644 networking/webrtc_minimal/minimal.tscn delete mode 100644 networking/webrtc_minimal/project.pandemonium delete mode 100644 networking/webrtc_signaling/.gitignore delete mode 100644 networking/webrtc_signaling/README.md delete mode 100644 networking/webrtc_signaling/client/multiplayer_client.gd delete mode 100644 networking/webrtc_signaling/client/ws_webrtc_client.gd delete mode 100644 networking/webrtc_signaling/demo/client_ui.gd delete mode 100644 networking/webrtc_signaling/demo/client_ui.tscn delete mode 100644 networking/webrtc_signaling/demo/main.gd delete mode 100644 networking/webrtc_signaling/demo/main.tscn delete mode 100644 networking/webrtc_signaling/project.pandemonium delete mode 100644 networking/webrtc_signaling/screenshots/.gdignore delete mode 100644 networking/webrtc_signaling/screenshots/screenshot.png delete mode 100644 networking/webrtc_signaling/server/ws_webrtc_server.gd delete mode 100644 networking/webrtc_signaling/server_node/.eslintrc.json delete mode 100644 networking/webrtc_signaling/server_node/.gitignore delete mode 100644 networking/webrtc_signaling/server_node/package.json delete mode 100644 networking/webrtc_signaling/server_node/server.js diff --git a/networking/multiplayer_bomber/gamestate.gd b/networking/multiplayer_bomber/gamestate.gd index 0c19e39..e9a0d28 100644 --- a/networking/multiplayer_bomber/gamestate.gd +++ b/networking/multiplayer_bomber/gamestate.gd @@ -61,7 +61,7 @@ func _connected_fail(): # Lobby management functions. -remote func register_player(new_player_name): +func register_player(new_player_name): var id = get_tree().get_rpc_sender_id() print(id) players[id] = new_player_name @@ -73,7 +73,7 @@ func unregister_player(id): emit_signal("player_list_changed") -remote func pre_start_game(spawn_points): +func pre_start_game(spawn_points): # Change scene. var world = load("res://world.tscn").instance() get_tree().get_root().add_child(world) @@ -111,11 +111,11 @@ remote func pre_start_game(spawn_points): post_start_game() -remote func post_start_game(): +func post_start_game(): get_tree().set_pause(false) # Unpause and unleash the game! -remote func ready_to_start(id): +func ready_to_start(id): assert(get_tree().is_network_server()) if not id in players_ready: @@ -181,3 +181,8 @@ func _ready(): get_tree().connect("connected_to_server", self, "_connected_ok") get_tree().connect("connection_failed", self, "_connected_fail") get_tree().connect("server_disconnected", self, "_server_disconnected") + + rpc_config("register_player", MultiplayerAPI.RPC_MODE_REMOTE) + rpc_config("pre_start_game", MultiplayerAPI.RPC_MODE_REMOTE) + rpc_config("post_start_game", MultiplayerAPI.RPC_MODE_REMOTE) + rpc_config("ready_to_start", MultiplayerAPI.RPC_MODE_REMOTE) diff --git a/networking/multiplayer_bomber/player.gd b/networking/multiplayer_bomber/player.gd index ff34663..7efd13d 100644 --- a/networking/multiplayer_bomber/player.gd +++ b/networking/multiplayer_bomber/player.gd @@ -2,13 +2,13 @@ extends KinematicBody2D const MOTION_SPEED = 90.0 -puppet var puppet_pos = Vector2() -puppet var puppet_motion = Vector2() +var puppet_pos = Vector2() +var puppet_motion = Vector2() export var stunned = false # Use sync because it will be called everywhere -remotesync func setup_bomb(bomb_name, pos, by_who): +func setup_bomb(bomb_name, pos, by_who): var bomb = preload("res://bomb.tscn").instance() bomb.set_name(bomb_name) # Ensure unique name for the bomb bomb.position = pos @@ -47,8 +47,8 @@ func _physics_process(_delta): prev_bombing = bombing - rset("puppet_motion", motion) - rset("puppet_pos", position) + rpc("set_puppet_motion", motion) + rpc("set_puppet_pos", position) else: position = puppet_pos motion = puppet_motion @@ -76,11 +76,11 @@ func _physics_process(_delta): puppet_pos = position # To avoid jitter -puppet func stun(): +func stun(): stunned = true -master func exploded(_by_who): +func exploded(_by_who): if stunned: return rpc("stun") # Stun puppets @@ -91,6 +91,18 @@ func set_player_name(new_name): get_node("label").set_text(new_name) +func set_puppet_pos(pos): + puppet_pos = pos + +func set_puppet_motion(pos): + puppet_motion = pos + func _ready(): stunned = false puppet_pos = position + + rpc_config("set_puppet_pos", MultiplayerAPI.RPC_MODE_PUPPET) + rpc_config("set_puppet_motion", MultiplayerAPI.RPC_MODE_PUPPET) + rpc_config("setup_bomb", MultiplayerAPI.RPC_MODE_REMOTESYNC) + rpc_config("stun", MultiplayerAPI.RPC_MODE_PUPPET) + rpc_config("exploded", MultiplayerAPI.RPC_MODE_MASTER) diff --git a/networking/multiplayer_bomber/project.pandemonium b/networking/multiplayer_bomber/project.pandemonium index 945df72..829dfe9 100644 --- a/networking/multiplayer_bomber/project.pandemonium +++ b/networking/multiplayer_bomber/project.pandemonium @@ -21,10 +21,6 @@ config/icon="res://icon.png" gamestate="*res://gamestate.gd" -[debug] - -gdscript/warnings/return_value_discarded=false - [display] window/dpi/allow_hidpi=true @@ -83,7 +79,3 @@ set_bomb={ , Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"button_mask":0,"position":Vector2( 0, 0 ),"global_position":Vector2( 0, 0 ),"factor":1.0,"button_index":1,"pressed":false,"doubleclick":false,"script":null) ] } - -[rendering] - -quality/driver/driver_name="GLES2" diff --git a/networking/multiplayer_bomber/rock.gd b/networking/multiplayer_bomber/rock.gd index 9274859..1b5c620 100644 --- a/networking/multiplayer_bomber/rock.gd +++ b/networking/multiplayer_bomber/rock.gd @@ -1,12 +1,16 @@ extends KinematicBody2D # Sent to everyone else -puppet func do_explosion(): +func do_explosion(): $"AnimationPlayer".play("explode") # Received by owner of the rock -master func exploded(by_who): +func exploded(by_who): rpc("do_explosion") # Re-sent to puppet rocks $"../../Score".rpc("increase_score", by_who) do_explosion() + +func _ready(): + rpc_config("do_explosion", MultiplayerAPI.RPC_MODE_PUPPET) + rpc_config("exploded", MultiplayerAPI.RPC_MODE_MASTER) diff --git a/networking/multiplayer_bomber/score.gd b/networking/multiplayer_bomber/score.gd index 487a25d..74276bd 100644 --- a/networking/multiplayer_bomber/score.gd +++ b/networking/multiplayer_bomber/score.gd @@ -16,7 +16,7 @@ func _process(_delta): $"../Winner".show() -remotesync func increase_score(for_who): +func increase_score(for_who): assert(for_who in player_labels) var pl = player_labels[for_who] pl.score += 1 @@ -31,15 +31,17 @@ func add_player(id, new_player_name): var font = DynamicFont.new() font.set_size(18) font.set_font_data(preload("res://montserrat.otf")) - l.add_font_override("font", font) + l.add_theme_font_override("font", font) add_child(l) - player_labels[id] = { name = new_player_name, label = l, score = 0 } + player_labels[id] = { "name": new_player_name, "label": l, "score": 0 } func _ready(): $"../Winner".hide() set_process(true) + + rpc_config("increase_score", MultiplayerAPI.RPC_MODE_REMOTESYNC) func _on_exit_game_pressed(): diff --git a/networking/multiplayer_pong/logic/ball.gd b/networking/multiplayer_pong/logic/ball.gd index 10e972f..d593c4c 100644 --- a/networking/multiplayer_pong/logic/ball.gd +++ b/networking/multiplayer_pong/logic/ball.gd @@ -41,7 +41,7 @@ func _process(delta): rpc("_reset_ball", true) -remotesync func bounce(left, random): +func bounce(left, random): # Using sync because both players can make it bounce. if left: direction.x = abs(direction.x) @@ -53,14 +53,20 @@ remotesync func bounce(left, random): direction = direction.normalized() -remotesync func stop(): +func stop(): stopped = true -remotesync func _reset_ball(for_left): +func _reset_ball(for_left): position = _screen_size / 2 if for_left: direction = Vector2.LEFT else: direction = Vector2.RIGHT _speed = DEFAULT_SPEED + + +func _ready(): + rpc_config("bounce", MultiplayerAPI.RPC_MODE_REMOTESYNC) + rpc_config("stop", MultiplayerAPI.RPC_MODE_REMOTESYNC) + rpc_config("_reset_ball", MultiplayerAPI.RPC_MODE_REMOTESYNC) diff --git a/networking/multiplayer_pong/logic/paddle.gd b/networking/multiplayer_pong/logic/paddle.gd index 1c154b0..c0d4d95 100644 --- a/networking/multiplayer_pong/logic/paddle.gd +++ b/networking/multiplayer_pong/logic/paddle.gd @@ -33,7 +33,7 @@ func _process(delta): # Synchronize position and speed to the other peers. -puppet func set_pos_and_motion(pos, motion): +func set_pos_and_motion(pos, motion): position = pos _motion = motion @@ -47,3 +47,7 @@ func _on_paddle_area_enter(area): if is_network_master(): # Random for new direction generated on each peer. area.rpc("bounce", left, randf()) + + +func _ready(): + rpc_config("set_pos_and_motion", MultiplayerAPI.RPC_MODE_PUPPET) diff --git a/networking/multiplayer_pong/logic/pong.gd b/networking/multiplayer_pong/logic/pong.gd index b8d4021..3c5ed10 100644 --- a/networking/multiplayer_pong/logic/pong.gd +++ b/networking/multiplayer_pong/logic/pong.gd @@ -25,9 +25,11 @@ func _ready(): player2.set_network_master(get_tree().get_network_unique_id()) print("Unique id: ", get_tree().get_network_unique_id()) + + rpc_config("update_score", MultiplayerAPI.RPC_MODE_REMOTESYNC) -remotesync func update_score(add_to_left): +func update_score(add_to_left): if add_to_left: score_left += 1 score_left_node.set_text(str(score_left)) diff --git a/networking/multiplayer_pong/project.pandemonium b/networking/multiplayer_pong/project.pandemonium index aaf9e65..c6711b2 100644 --- a/networking/multiplayer_pong/project.pandemonium +++ b/networking/multiplayer_pong/project.pandemonium @@ -17,10 +17,6 @@ other should select the address and press 'join'." run/main_scene="res://lobby.tscn" config/icon="res://icon.png" -[debug] - -gdscript/warnings/return_value_discarded=false - [display] window/size/width=640 @@ -57,7 +53,6 @@ move_up={ [rendering] -quality/driver/driver_name="GLES2" 2d/snapping/use_gpu_pixel_snap=true vram_compression/import_etc=true vram_compression/import_etc2=false diff --git a/networking/webrtc_minimal/.gitignore b/networking/webrtc_minimal/.gitignore deleted file mode 100644 index d9bb4a8..0000000 --- a/networking/webrtc_minimal/.gitignore +++ /dev/null @@ -1 +0,0 @@ -webrtc diff --git a/networking/webrtc_minimal/README.md b/networking/webrtc_minimal/README.md deleted file mode 100644 index b80eeda..0000000 --- a/networking/webrtc_minimal/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# WebRTC Minimal - -This is a minimal sample of using WebRTC connections to connect two peers to each other. - -Language: GDScript - -Renderer: GLES 2 - -Check out this demo on the asset library: https://godotengine.org/asset-library/asset/536 diff --git a/networking/webrtc_minimal/Signaling.gd b/networking/webrtc_minimal/Signaling.gd deleted file mode 100644 index 8cfe7a7..0000000 --- a/networking/webrtc_minimal/Signaling.gd +++ /dev/null @@ -1,31 +0,0 @@ -# A local signaling server. Add this to autoloads with name "Signaling" (/root/Signaling) -extends Node - -# We will store the two peers here -var peers = [] - -func register(path): - assert(peers.size() < 2) - peers.append(path) - if peers.size() == 2: - get_node(peers[0]).peer.create_offer() - - -func _find_other(path): - # Find the other registered peer. - for p in peers: - if p != path: - return p - return "" - - -func send_session(path, type, sdp): - var other = _find_other(path) - assert(other != "") - get_node(other).peer.set_remote_description(type, sdp) - - -func send_candidate(path, mid, index, sdp): - var other = _find_other(path) - assert(other != "") - get_node(other).peer.add_ice_candidate(mid, index, sdp) diff --git a/networking/webrtc_minimal/chat.gd b/networking/webrtc_minimal/chat.gd deleted file mode 100644 index 26c6fa2..0000000 --- a/networking/webrtc_minimal/chat.gd +++ /dev/null @@ -1,39 +0,0 @@ -extends Node -# An example p2p chat client. - -var peer = WebRTCPeerConnection.new() - -# Create negotiated data channel. -var channel = peer.create_data_channel("chat", {"negotiated": true, "id": 1}) - -func _ready(): - # Connect all functions. - peer.connect("ice_candidate_created", self, "_on_ice_candidate") - peer.connect("session_description_created", self, "_on_session") - - # Register to the local signaling server (see below for the implementation). - Signaling.register(String(get_path())) - - -func _on_ice_candidate(mid, index, sdp): - # Send the ICE candidate to the other peer via signaling server. - Signaling.send_candidate(String(get_path()), mid, index, sdp) - - -func _on_session(type, sdp): - # Send the session to other peer via signaling server. - Signaling.send_session(String(get_path()), type, sdp) - # Set generated description as local. - peer.set_local_description(type, sdp) - - -func _process(delta): - # Always poll the connection frequently. - peer.poll() - if channel.get_ready_state() == WebRTCDataChannel.STATE_OPEN: - while channel.get_available_packet_count() > 0: - print(String(get_path()), " received: ", channel.get_packet().get_string_from_utf8()) - - -func send_message(message): - channel.put_packet(message.to_utf8()) diff --git a/networking/webrtc_minimal/link_button.gd b/networking/webrtc_minimal/link_button.gd deleted file mode 100644 index be300b9..0000000 --- a/networking/webrtc_minimal/link_button.gd +++ /dev/null @@ -1,4 +0,0 @@ -extends LinkButton - -func _on_LinkButton_pressed(): - OS.shell_open("https://github.com/godotengine/webrtc-native/releases") diff --git a/networking/webrtc_minimal/main.gd b/networking/webrtc_minimal/main.gd deleted file mode 100644 index cfb706d..0000000 --- a/networking/webrtc_minimal/main.gd +++ /dev/null @@ -1,17 +0,0 @@ -extends Node - -const Chat = preload("res://chat.gd") - -func _ready(): - var p1 = Chat.new() - var p2 = Chat.new() - add_child(p1) - add_child(p2) - - # Wait a second and send message from P1 - yield(get_tree().create_timer(1), "timeout") - p1.send_message("Hi from %s" % String(p1.get_path())) - - # Wait a second and send message from P2 - yield(get_tree().create_timer(1), "timeout") - p2.send_message("Hi from %s" % String(p2.get_path())) diff --git a/networking/webrtc_minimal/main.tscn b/networking/webrtc_minimal/main.tscn deleted file mode 100644 index 7cc8637..0000000 --- a/networking/webrtc_minimal/main.tscn +++ /dev/null @@ -1,30 +0,0 @@ -[gd_scene load_steps=4 format=2] - -[ext_resource path="res://minimal.tscn" type="PackedScene" id=1] -[ext_resource path="res://main.gd" type="Script" id=2] -[ext_resource path="res://link_button.gd" type="Script" id=3] - -[node name="Main" type="Node"] -script = ExtResource( 2 ) - -[node name="Minimal" parent="." instance=ExtResource( 1 )] - -[node name="CenterContainer" type="CenterContainer" parent="."] -anchor_right = 1.0 -anchor_bottom = 1.0 -__meta__ = { -"_edit_use_anchors_": true -} - -[node name="LinkButton" type="LinkButton" parent="CenterContainer"] -margin_left = 239.0 -margin_top = 293.0 -margin_right = 785.0 -margin_bottom = 307.0 -text = "Make sure to download the GDNative WebRTC Plugin and place it in the project folder" -script = ExtResource( 3 ) -__meta__ = { -"_edit_use_anchors_": false -} - -[connection signal="pressed" from="CenterContainer/LinkButton" to="CenterContainer/LinkButton" method="_on_LinkButton_pressed"] diff --git a/networking/webrtc_minimal/minimal.gd b/networking/webrtc_minimal/minimal.gd deleted file mode 100644 index 95bd036..0000000 --- a/networking/webrtc_minimal/minimal.gd +++ /dev/null @@ -1,41 +0,0 @@ -extends Node -# Main scene. - -# Create the two peers. -var p1 = WebRTCPeerConnection.new() -var p2 = WebRTCPeerConnection.new() -var ch1 = p1.create_data_channel("chat", {"id": 1, "negotiated": true}) -var ch2 = p2.create_data_channel("chat", {"id": 1, "negotiated": true}) - -func _ready(): - print(p1.create_data_channel("chat", {"id": 1, "negotiated": true})) - # Connect P1 session created to itself to set local description. - p1.connect("session_description_created", p1, "set_local_description") - # Connect P1 session and ICE created to p2 set remote description and candidates. - p1.connect("session_description_created", p2, "set_remote_description") - p1.connect("ice_candidate_created", p2, "add_ice_candidate") - - # Same for P2. - p2.connect("session_description_created", p2, "set_local_description") - p2.connect("session_description_created", p1, "set_remote_description") - p2.connect("ice_candidate_created", p1, "add_ice_candidate") - - # Let P1 create the offer. - p1.create_offer() - - # Wait a second and send message from P1. - yield(get_tree().create_timer(1), "timeout") - ch1.put_packet("Hi from P1".to_utf8()) - - # Wait a second and send message from P2. - yield(get_tree().create_timer(1), "timeout") - ch2.put_packet("Hi from P2".to_utf8()) - - -func _process(delta): - p1.poll() - p2.poll() - if ch1.get_ready_state() == ch1.STATE_OPEN and ch1.get_available_packet_count() > 0: - print("P1 received: ", ch1.get_packet().get_string_from_utf8()) - if ch2.get_ready_state() == ch2.STATE_OPEN and ch2.get_available_packet_count() > 0: - print("P2 received: ", ch2.get_packet().get_string_from_utf8()) diff --git a/networking/webrtc_minimal/minimal.tscn b/networking/webrtc_minimal/minimal.tscn deleted file mode 100644 index 2eb0dd7..0000000 --- a/networking/webrtc_minimal/minimal.tscn +++ /dev/null @@ -1,6 +0,0 @@ -[gd_scene load_steps=2 format=2] - -[ext_resource path="res://minimal.gd" type="Script" id=1] - -[node name="Minimal" type="Node"] -script = ExtResource( 1 ) diff --git a/networking/webrtc_minimal/project.pandemonium b/networking/webrtc_minimal/project.pandemonium deleted file mode 100644 index 0dfabc3..0000000 --- a/networking/webrtc_minimal/project.pandemonium +++ /dev/null @@ -1,35 +0,0 @@ -; Engine configuration file. -; It's best edited using the editor UI and not directly, -; since the parameters that go here are not all obvious. -; -; Format: -; [section] ; section goes between [] -; param=value ; assign values to parameters - -config_version=4 - -[application] - -config/name="WebRTC Minimal Connection" -config/description="This is a minimal sample of using WebRTC connections to connect two peers to each other." -run/main_scene="res://main.tscn" - -[autoload] - -Signaling="*res://Signaling.gd" - -[display] - -window/dpi/allow_hidpi=true -window/stretch/mode="2d" -window/stretch/aspect="expand" - -[gdnative] - -singletons=[ ] - -[rendering] - -quality/driver/driver_name="GLES2" -vram_compression/import_etc=true -vram_compression/import_etc2=false diff --git a/networking/webrtc_signaling/.gitignore b/networking/webrtc_signaling/.gitignore deleted file mode 100644 index d9bb4a8..0000000 --- a/networking/webrtc_signaling/.gitignore +++ /dev/null @@ -1 +0,0 @@ -webrtc diff --git a/networking/webrtc_signaling/README.md b/networking/webrtc_signaling/README.md deleted file mode 100644 index 95da639..0000000 --- a/networking/webrtc_signaling/README.md +++ /dev/null @@ -1,44 +0,0 @@ -# A WebSocket signaling server/client for WebRTC. - -This demo divided into 4 parts: - -- The `server` folder contains the signaling server implementation written in GDScript (so it can be run by a game server running Godot) -- The `server_node` folder contains the signaling server implementation written in Node.js (in case you dont want to run a godot game server). -- The `client` folder contains the client implementation in GDScript. - - It handles both the protocol and `WebRTCMultiplayer` separately. -- The `demo` contains a small app that uses it. - -**NOTE**: You must extract the [latest version](https://github.com/godotengine/webrtc-native/releases) of the WebRTC GDNative plugin in the project folder to run on a desktop. - -Language: GDScript - -Renderer: GLES 2 - -Check out this demo on the asset library: https://godotengine.org/asset-library/asset/537 - -## Protocol - -The protocol is text based, which is composed of a command and possibly multiple payload arguments, each separated by a new line. - -Messages without payload must still end with a newline and are the following: - -- `J: ` (or `J: `), must be sent by the client immediately after connecting to get a lobby assigned or join a known one. - These messages are from the server back to the client to notify the client of the assigned lobby, or simply of a successful join. -- `I: `, sent by the server to identify the client when it joins a room. -- `N: `, sent by the server to notify new peers in the same lobby. -- `D: `, sent by the server to notify when a peer in the same lobby disconnects. -- `S: `, sent by a client to seal the lobby (only the client that created it is allowed to seal a lobby). - -When a lobby is sealed, new clients will be unable to join, and the lobby will be destroyed (and clients disconnected) after 10 seconds. - -Messages with payload (used to transfer WebRTC parameters) are: - -- `O: `, used to send an offer. -- `A: `, used to send an answer. -- `C: `, used to send a candidate. - -When sending the parameter, a client will set `` as the destination peer, the server will replace it with the id of the sending peer, then relay it to the proper destination. - -## Screenshots - -![Screenshot](screenshots/screenshot.png) diff --git a/networking/webrtc_signaling/client/multiplayer_client.gd b/networking/webrtc_signaling/client/multiplayer_client.gd deleted file mode 100644 index 4f088a2..0000000 --- a/networking/webrtc_signaling/client/multiplayer_client.gd +++ /dev/null @@ -1,100 +0,0 @@ -extends "ws_webrtc_client.gd" - -var rtc_mp: WebRTCMultiplayer = WebRTCMultiplayer.new() -var sealed = false - -func _init(): - connect("connected", self, "connected") - connect("disconnected", self, "disconnected") - - connect("offer_received", self, "offer_received") - connect("answer_received", self, "answer_received") - connect("candidate_received", self, "candidate_received") - - connect("lobby_joined", self, "lobby_joined") - connect("lobby_sealed", self, "lobby_sealed") - connect("peer_connected", self, "peer_connected") - connect("peer_disconnected", self, "peer_disconnected") - - -func start(url, lobby = ""): - stop() - sealed = false - self.lobby = lobby - connect_to_url(url) - - -func stop(): - rtc_mp.close() - close() - - -func _create_peer(id): - var peer: WebRTCPeerConnection = WebRTCPeerConnection.new() - peer.initialize({ - "iceServers": [ { "urls": ["stun:stun.l.google.com:19302"] } ] - }) - peer.connect("session_description_created", self, "_offer_created", [id]) - peer.connect("ice_candidate_created", self, "_new_ice_candidate", [id]) - rtc_mp.add_peer(peer, id) - if id > rtc_mp.get_unique_id(): - peer.create_offer() - return peer - - -func _new_ice_candidate(mid_name, index_name, sdp_name, id): - send_candidate(id, mid_name, index_name, sdp_name) - - -func _offer_created(type, data, id): - if not rtc_mp.has_peer(id): - return - print("created", type) - rtc_mp.get_peer(id).connection.set_local_description(type, data) - if type == "offer": send_offer(id, data) - else: send_answer(id, data) - - -func connected(id): - print("Connected %d" % id) - rtc_mp.initialize(id, true) - - -func lobby_joined(lobby): - self.lobby = lobby - - -func lobby_sealed(): - sealed = true - - -func disconnected(): - print("Disconnected: %d: %s" % [code, reason]) - if not sealed: - stop() # Unexpected disconnect - - -func peer_connected(id): - print("Peer connected %d" % id) - _create_peer(id) - - -func peer_disconnected(id): - if rtc_mp.has_peer(id): rtc_mp.remove_peer(id) - - -func offer_received(id, offer): - print("Got offer: %d" % id) - if rtc_mp.has_peer(id): - rtc_mp.get_peer(id).connection.set_remote_description("offer", offer) - - -func answer_received(id, answer): - print("Got answer: %d" % id) - if rtc_mp.has_peer(id): - rtc_mp.get_peer(id).connection.set_remote_description("answer", answer) - - -func candidate_received(id, mid, index, sdp): - if rtc_mp.has_peer(id): - rtc_mp.get_peer(id).connection.add_ice_candidate(mid, index, sdp) diff --git a/networking/webrtc_signaling/client/ws_webrtc_client.gd b/networking/webrtc_signaling/client/ws_webrtc_client.gd deleted file mode 100644 index f95a7d7..0000000 --- a/networking/webrtc_signaling/client/ws_webrtc_client.gd +++ /dev/null @@ -1,129 +0,0 @@ -extends Node - -export var autojoin = true -export var lobby = "" # Will create a new lobby if empty. - -var client: WebSocketClient = WebSocketClient.new() -var code = 1000 -var reason = "Unknown" - -signal lobby_joined(lobby) -signal connected(id) -signal disconnected() -signal peer_connected(id) -signal peer_disconnected(id) -signal offer_received(id, offer) -signal answer_received(id, answer) -signal candidate_received(id, mid, index, sdp) -signal lobby_sealed() - -func _init(): - client.connect("data_received", self, "_parse_msg") - client.connect("connection_established", self, "_connected") - client.connect("connection_closed", self, "_closed") - client.connect("connection_error", self, "_closed") - client.connect("server_close_request", self, "_close_request") - - -func connect_to_url(url): - close() - code = 1000 - reason = "Unknown" - client.connect_to_url(url) - - -func close(): - client.disconnect_from_host() - - -func _closed(was_clean = false): - emit_signal("disconnected") - - -func _close_request(code, reason): - self.code = code - self.reason = reason - - -func _connected(protocol = ""): - client.get_peer(1).set_write_mode(WebSocketPeer.WRITE_MODE_TEXT) - if autojoin: - join_lobby(lobby) - - -func _parse_msg(): - var pkt_str: String = client.get_peer(1).get_packet().get_string_from_utf8() - - var req: PoolStringArray = pkt_str.split("\n", true, 1) - if req.size() != 2: # Invalid request size - return - - var type: String = req[0] - if type.length() < 3: # Invalid type size - return - - if type.begins_with("J: "): - emit_signal("lobby_joined", type.substr(3, type.length() - 3)) - return - elif type.begins_with("S: "): - emit_signal("lobby_sealed") - return - - var src_str: String = type.substr(3, type.length() - 3) - if not src_str.is_valid_integer(): # Source id is not an integer - return - - var src_id: int = int(src_str) - - if type.begins_with("I: "): - emit_signal("connected", src_id) - elif type.begins_with("N: "): - # Client connected - emit_signal("peer_connected", src_id) - elif type.begins_with("D: "): - # Client connected - emit_signal("peer_disconnected", src_id) - elif type.begins_with("O: "): - # Offer received - emit_signal("offer_received", src_id, req[1]) - elif type.begins_with("A: "): - # Answer received - emit_signal("answer_received", src_id, req[1]) - elif type.begins_with("C: "): - # Candidate received - var candidate: PoolStringArray = req[1].split("\n", false) - if candidate.size() != 3: - return - if not candidate[1].is_valid_integer(): - return - emit_signal("candidate_received", src_id, candidate[0], int(candidate[1]), candidate[2]) - - -func join_lobby(lobby): - return client.get_peer(1).put_packet(("J: %s\n" % lobby).to_utf8()) - - -func seal_lobby(): - return client.get_peer(1).put_packet("S: \n".to_utf8()) - - -func send_candidate(id, mid, index, sdp) -> int: - return _send_msg("C", id, "\n%s\n%d\n%s" % [mid, index, sdp]) - - -func send_offer(id, offer) -> int: - return _send_msg("O", id, offer) - - -func send_answer(id, answer) -> int: - return _send_msg("A", id, answer) - - -func _send_msg(type, id, data) -> int: - return client.get_peer(1).put_packet(("%s: %d\n%s" % [type, id, data]).to_utf8()) - - -func _process(delta): - var status: int = client.get_connection_status() - if status == WebSocketClient.CONNECTION_CONNECTING or status == WebSocketClient.CONNECTION_CONNECTED: - client.poll() diff --git a/networking/webrtc_signaling/demo/client_ui.gd b/networking/webrtc_signaling/demo/client_ui.gd deleted file mode 100644 index 3b862ee..0000000 --- a/networking/webrtc_signaling/demo/client_ui.gd +++ /dev/null @@ -1,79 +0,0 @@ -extends Control - -onready var client = $Client - -func _ready(): - client.connect("lobby_joined", self, "_lobby_joined") - client.connect("lobby_sealed", self, "_lobby_sealed") - client.connect("connected", self, "_connected") - client.connect("disconnected", self, "_disconnected") - client.rtc_mp.connect("peer_connected", self, "_mp_peer_connected") - client.rtc_mp.connect("peer_disconnected", self, "_mp_peer_disconnected") - client.rtc_mp.connect("server_disconnected", self, "_mp_server_disconnect") - client.rtc_mp.connect("connection_succeeded", self, "_mp_connected") - - -func _process(delta): - client.rtc_mp.poll() - while client.rtc_mp.get_available_packet_count() > 0: - _log(client.rtc_mp.get_packet().get_string_from_utf8()) - - -func _connected(id): - _log("Signaling server connected with ID: %d" % id) - - -func _disconnected(): - _log("Signaling server disconnected: %d - %s" % [client.code, client.reason]) - - -func _lobby_joined(lobby): - _log("Joined lobby %s" % lobby) - - -func _lobby_sealed(): - _log("Lobby has been sealed") - - -func _mp_connected(): - _log("Multiplayer is connected (I am %d)" % client.rtc_mp.get_unique_id()) - - -func _mp_server_disconnect(): - _log("Multiplayer is disconnected (I am %d)" % client.rtc_mp.get_unique_id()) - - -func _mp_peer_connected(id: int): - _log("Multiplayer peer %d connected" % id) - - -func _mp_peer_disconnected(id: int): - _log("Multiplayer peer %d disconnected" % id) - - -func _log(msg): - print(msg) - $VBoxContainer/TextEdit.text += str(msg) + "\n" - - -func ping(): - _log(client.rtc_mp.put_packet("ping".to_utf8())) - - -func _on_Peers_pressed(): - var d = client.rtc_mp.get_peers() - _log(d) - for k in d: - _log(client.rtc_mp.get_peer(k)) - - -func start(): - client.start($VBoxContainer/Connect/Host.text, $VBoxContainer/Connect/RoomSecret.text) - - -func _on_Seal_pressed(): - client.seal_lobby() - - -func stop(): - client.stop() diff --git a/networking/webrtc_signaling/demo/client_ui.tscn b/networking/webrtc_signaling/demo/client_ui.tscn deleted file mode 100644 index 4d5d909..0000000 --- a/networking/webrtc_signaling/demo/client_ui.tscn +++ /dev/null @@ -1,107 +0,0 @@ -[gd_scene load_steps=3 format=2] - -[ext_resource path="res://demo/client_ui.gd" type="Script" id=1] -[ext_resource path="res://client/multiplayer_client.gd" type="Script" id=2] - -[node name="ClientUI" type="Control"] -margin_right = 1024.0 -margin_bottom = 600.0 -size_flags_horizontal = 3 -size_flags_vertical = 3 -script = ExtResource( 1 ) -__meta__ = { -"_edit_use_anchors_": true -} - -[node name="Client" type="Node" parent="."] -script = ExtResource( 2 ) - -[node name="VBoxContainer" type="VBoxContainer" parent="."] -anchor_right = 1.0 -anchor_bottom = 1.0 -custom_constants/separation = 8 -__meta__ = { -"_edit_use_anchors_": false -} - -[node name="Connect" type="HBoxContainer" parent="VBoxContainer"] -margin_right = 1024.0 -margin_bottom = 24.0 - -[node name="Label" type="Label" parent="VBoxContainer/Connect"] -margin_top = 5.0 -margin_right = 73.0 -margin_bottom = 19.0 -text = "Connect to:" - -[node name="Host" type="LineEdit" parent="VBoxContainer/Connect"] -margin_left = 77.0 -margin_right = 921.0 -margin_bottom = 24.0 -size_flags_horizontal = 3 -text = "ws://localhost:9080" - -[node name="Room" type="Label" parent="VBoxContainer/Connect"] -margin_left = 925.0 -margin_right = 962.0 -margin_bottom = 24.0 -size_flags_vertical = 5 -text = "Room" -valign = 1 - -[node name="RoomSecret" type="LineEdit" parent="VBoxContainer/Connect"] -margin_left = 966.0 -margin_right = 1024.0 -margin_bottom = 24.0 -placeholder_text = "secret" - -[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"] -margin_top = 32.0 -margin_right = 1024.0 -margin_bottom = 52.0 -custom_constants/separation = 10 -__meta__ = { -"_edit_use_anchors_": false -} - -[node name="Start" type="Button" parent="VBoxContainer/HBoxContainer"] -margin_right = 41.0 -margin_bottom = 20.0 -text = "Start" - -[node name="Stop" type="Button" parent="VBoxContainer/HBoxContainer"] -margin_left = 51.0 -margin_right = 91.0 -margin_bottom = 20.0 -text = "Stop" - -[node name="Seal" type="Button" parent="VBoxContainer/HBoxContainer"] -margin_left = 101.0 -margin_right = 139.0 -margin_bottom = 20.0 -text = "Seal" - -[node name="Ping" type="Button" parent="VBoxContainer/HBoxContainer"] -margin_left = 149.0 -margin_right = 188.0 -margin_bottom = 20.0 -text = "Ping" - -[node name="Peers" type="Button" parent="VBoxContainer/HBoxContainer"] -margin_left = 198.0 -margin_right = 280.0 -margin_bottom = 20.0 -text = "Print peers" - -[node name="TextEdit" type="TextEdit" parent="VBoxContainer"] -margin_top = 60.0 -margin_right = 1024.0 -margin_bottom = 600.0 -size_flags_vertical = 3 -readonly = true - -[connection signal="pressed" from="VBoxContainer/HBoxContainer/Start" to="." method="start"] -[connection signal="pressed" from="VBoxContainer/HBoxContainer/Stop" to="." method="stop"] -[connection signal="pressed" from="VBoxContainer/HBoxContainer/Seal" to="." method="_on_Seal_pressed"] -[connection signal="pressed" from="VBoxContainer/HBoxContainer/Ping" to="." method="ping"] -[connection signal="pressed" from="VBoxContainer/HBoxContainer/Peers" to="." method="_on_Peers_pressed"] diff --git a/networking/webrtc_signaling/demo/main.gd b/networking/webrtc_signaling/demo/main.gd deleted file mode 100644 index b35c463..0000000 --- a/networking/webrtc_signaling/demo/main.gd +++ /dev/null @@ -1,16 +0,0 @@ -extends Control - -func _ready(): - if OS.get_name() == "HTML5": - $VBoxContainer/Signaling.hide() - - -func _on_listen_toggled(button_pressed): - if button_pressed: - $Server.listen(int($VBoxContainer/Signaling/Port.value)) - else: - $Server.stop() - - -func _on_LinkButton_pressed(): - OS.shell_open("https://github.com/godotengine/webrtc-native/releases") diff --git a/networking/webrtc_signaling/demo/main.tscn b/networking/webrtc_signaling/demo/main.tscn deleted file mode 100644 index 937c903..0000000 --- a/networking/webrtc_signaling/demo/main.tscn +++ /dev/null @@ -1,100 +0,0 @@ -[gd_scene load_steps=4 format=2] - -[ext_resource path="res://demo/main.gd" type="Script" id=1] -[ext_resource path="res://demo/client_ui.tscn" type="PackedScene" id=2] -[ext_resource path="res://server/ws_webrtc_server.gd" type="Script" id=3] - -[node name="Control" type="Control"] -anchor_left = 0.0136719 -anchor_top = 0.0166667 -anchor_right = 0.986328 -anchor_bottom = 0.983333 -margin_top = 4.32134e-07 -margin_bottom = -9.53674e-06 -script = ExtResource( 1 ) -__meta__ = { -"_edit_use_anchors_": true -} - -[node name="VBoxContainer" type="VBoxContainer" parent="."] -anchor_right = 1.0 -anchor_bottom = 1.0 -custom_constants/separation = 50 -__meta__ = { -"_edit_use_anchors_": true -} - -[node name="Signaling" type="HBoxContainer" parent="VBoxContainer"] -margin_right = 995.0 -margin_bottom = 24.0 - -[node name="Label" type="Label" parent="VBoxContainer/Signaling"] -margin_top = 5.0 -margin_right = 104.0 -margin_bottom = 19.0 -text = "Signaling server:" - -[node name="Port" type="SpinBox" parent="VBoxContainer/Signaling"] -margin_left = 108.0 -margin_right = 182.0 -margin_bottom = 24.0 -min_value = 1025.0 -max_value = 65535.0 -value = 9080.0 - -[node name="ListenButton" type="Button" parent="VBoxContainer/Signaling"] -margin_left = 186.0 -margin_right = 237.0 -margin_bottom = 24.0 -toggle_mode = true -text = "Listen" - -[node name="CenterContainer" type="CenterContainer" parent="VBoxContainer/Signaling"] -margin_left = 241.0 -margin_right = 995.0 -margin_bottom = 24.0 -size_flags_horizontal = 3 -size_flags_vertical = 3 - -[node name="LinkButton" type="LinkButton" parent="VBoxContainer/Signaling/CenterContainer"] -margin_left = 104.0 -margin_top = 5.0 -margin_right = 650.0 -margin_bottom = 19.0 -text = "Make sure to download the GDNative WebRTC Plugin and place it in the project folder" - -[node name="Clients" type="GridContainer" parent="VBoxContainer"] -margin_top = 74.0 -margin_right = 995.0 -margin_bottom = 579.0 -size_flags_horizontal = 3 -size_flags_vertical = 3 -custom_constants/vseparation = 15 -custom_constants/hseparation = 15 -columns = 2 - -[node name="ClientUI" parent="VBoxContainer/Clients" instance=ExtResource( 2 )] -margin_right = 490.0 -margin_bottom = 245.0 - -[node name="ClientUI2" parent="VBoxContainer/Clients" instance=ExtResource( 2 )] -margin_left = 505.0 -margin_right = 995.0 -margin_bottom = 245.0 - -[node name="ClientUI3" parent="VBoxContainer/Clients" instance=ExtResource( 2 )] -margin_top = 260.0 -margin_right = 490.0 -margin_bottom = 505.0 - -[node name="ClientUI4" parent="VBoxContainer/Clients" instance=ExtResource( 2 )] -margin_left = 505.0 -margin_top = 260.0 -margin_right = 995.0 -margin_bottom = 505.0 - -[node name="Server" type="Node" parent="."] -script = ExtResource( 3 ) - -[connection signal="toggled" from="VBoxContainer/Signaling/ListenButton" to="." method="_on_listen_toggled"] -[connection signal="pressed" from="VBoxContainer/Signaling/CenterContainer/LinkButton" to="." method="_on_LinkButton_pressed"] diff --git a/networking/webrtc_signaling/project.pandemonium b/networking/webrtc_signaling/project.pandemonium deleted file mode 100644 index 8b6a89c..0000000 --- a/networking/webrtc_signaling/project.pandemonium +++ /dev/null @@ -1,45 +0,0 @@ -; Engine configuration file. -; It's best edited using the editor UI and not directly, -; since the parameters that go here are not all obvious. -; -; Format: -; [section] ; section goes between [] -; param=value ; assign values to parameters - -config_version=4 - -[application] - -config/name="WebRTC Signaling Example" -config/description="A WebSocket signaling server/client for WebRTC. -This demo is devided in 4 parts. -The protocol is text based, and composed by a command and possibly -multiple payload arguments, each separated by a new line." -run/main_scene="res://demo/main.tscn" - -[debug] - -gdscript/warnings/shadowed_variable=false -gdscript/warnings/unused_argument=false -gdscript/warnings/return_value_discarded=false - -[display] - -window/dpi/allow_hidpi=true -window/stretch/mode="2d" -window/stretch/aspect="expand" - -[gdnative] - -singletons=[ ] -singletons_disabled=[ ] - -[network] - -modules/webrtc_gdnative_script="res://demo/webrtc/webrtc.gdns" - -[rendering] - -quality/driver/driver_name="GLES2" -vram_compression/import_etc=true -vram_compression/import_etc2=false diff --git a/networking/webrtc_signaling/screenshots/.gdignore b/networking/webrtc_signaling/screenshots/.gdignore deleted file mode 100644 index e69de29..0000000 diff --git a/networking/webrtc_signaling/screenshots/screenshot.png b/networking/webrtc_signaling/screenshots/screenshot.png deleted file mode 100644 index 5eb49356069fc9c9f6147617c9dd86e6046db4be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20210 zcmb^Y1yo&6vIh!p+}&M*6C_xWV8JCg1Pd;~3GN&mLU4D2y9Rf64J5d`yUROd=09`i z`|h2Yx7ItG-KW=KckimMs+Rgyg(%2Lpdb<=0sw#_B`K-|0MOtnq&hq#_{-i7$^ii6 z8l*&pRb1u{=EdT%6!85O)qI+a(#Z!JMYwr3@(RO)l6m;9%u(ZZCT*| z^QJWq4Q<+0s=sb_+OC;DUbsuJZnNL6>9nAik}sN!@0p-I&Pl>V3W&c(g8<92oo0SLs2WRS0-p4DZI^ z$z?RxcR5o;Ns&Cpd;Fw%w$NoscO;BavzjVFC5r2~FaCI!(h%|dnNTS@T_u+(;1vKs zfl&Gdf1fuCIA0CkR)2K9icaXy3HOp|wbG@-#{JnBwnsbmsF_=Ld(TbIUns4GC_(fx zERifT8@dOsM#neQY=v$7z}d+C?Z!ke*`pvSRH5{|F8CqvxWNi#PxPbNnyv@c*d1r@ zTOSGxN~+E&aYF*EfY+EorORn_i@j}?rPR~S^Z7cV;e74kubJ8w@b~OdWrmfz-~>U` zmn#!_xt87PdH!;np0IFUMP)x)Usv;o>&{ncTHfG6SMeSg#|4887PjO6I#WNA{nz=+s9 ztn_!T(P?Qd`dzSUVN`SExAQ;Ds=XfF zKa(FtA7g#(ze4idkdS1D3pTovhP^@PPjcvdM^LJJ(vb{Aah zc_vrYh9;K+M$`D-8uotsyq5UJa5SC&o&gicq%|e-jiZOmYzfb0GUs#L!-f+T7l%dv zZZ%(a2Yq%Gcd3T|KeJ0s5F|y)pr|y6W36I$U9>nO1yxJLn#Hdy>-Urst36hnX zyb{ftuRO;QQ<=hkmW>xJVxhPITmKkXAkbMDjn2oTb^LA@krI;wPV2p4>wOW|hx2$w z@?^AfsW;7tmHk4gPRC0mLn`!q(Bw}I*w4NUVSM_rR0%wgkyOC4=k=mL0v2&U>Ki&b zx|^Ns2uUjRRebXI^m(W~$IESkdhGypGL_f;Vco;#a7B&SdM##tPk{?qX}iMT7orJ1 z*n&l#-1=p`S6=Y(qIyh#&u&BVd~2iz$GlhN{B}Fz`J@Xi(IX2ElOD8!7f+ogL76D2 zD+yFp+w(dfFrWUI(hou-iMsKD3d;MCMt{2d)8ufWQfuw{XvvR3viUbtvJLkhsH zQ7{xbQzwSz!vWQ5&=tgN*j`PcN~>CDkdmi6T5Sk_<7JG?nDygWS`#P?ovqsTNl8gA zom&jHj8n3~f zK>g6`fNX`i6SV21BOtG^GMLYkdvqF%rN2H}^drTG)N?bX`pBfuXZkygP+&GjiemBD z*JEfgK!B3ewC{5NH|OTwbn&pO1W`XIeS&KaYBSq=-ubR`#w_h3!KTUSu!tVnLPsZE zCt^mAv6~eyjp1oh5A(%pX(TDoeN3pZf1E4$u`gS8M7kCjON0>`tJ3=NgjGS0<9}q# zA>(zR^Jp|5abgzpD^I$?i!H~hZs2kEl|JGy{jqJfezMwir>f92I?(U2a9lVa9?Fz`iSO#d8whX0dzFu-) zXsoqfBrL~i8H34dKJ6t+#X4w~UFLVc`phWF=D4Sl6abH#_P7$!UiA($$fY?FP!K7` zJ)~Ny6nRTZ9X{LSJobc18=V%%bgKHSUTu6{EBIJRoI%3p#H7)@Fh+(4A=EM^tMJ|! zLCCMMlp(%2>Eyd)hK`@5!F8D@b-F9N#ntd_jRnWiRI5ZeDU>CwKo^_1Zi8K^)jsnF z!Ww}uYg3cdjGXf|#lQI+cQo4Ms`;C)Nd%oH5kV@I=pZ9>ct}Ylg`qHXE*0+TqqA-v z1Q6c-uTrefxqVRiFrlHZ@)fhislv$wNppHy*trw2`tRPslWYC>jB!(?+vc@Lu!9b0 z67#!ptnpm(1u|N5pE?~X;s;~jE+N&eN{Ydn=Xsgi4%$KcehC>j#&pG=@5qsgp<{%M zlsq<)!Di9I87`bHn!_90<&03VFoIViC(6TQhpRT%>eWWH$(WhKULXp{Lm-iD%T0pS}7 zA6JECJrX)OsXXZtbbE^T^ON5=tW@8(#nw-FAy^%!I%{pu&uvHZ?m!Wgt4^yWb=kA^ z6Xp#LIR$XJUt6M3Cf+Fl5BdPTEJNJ%^rNsk4cS93jhEeR13Dx>4|!;C_?P8+pIp34 zTdN3ob+UmGD#Aw)iUwZmwh|BLyP#UXt{)*hPMXm~KSLJndMFPhTLT$hzJhe0#Z5Xt z-yVy|yMY#B=ey*ieY+ibS8mwEFnR6AF3V40I+`klBBwEouI&sT4uL3?-b3p12x%1z z>?!rM_VMTA3@1pd9tLk;&kdrN2_1M4qpv~(Q0QV=%bmf%!j$}1!6;%#P*{8+Wq0%< zQ4Hb@O$8DN=j6wN+>qWI*=WdCh)wRfad%`A63)z`KGfeBwxpWS zN}F;am6yGm`|8}uykp=}lth!--uq~vAjzNUU^~OxAImKPk1Doy z=KqN-Gh&vl!jc=JK(hgN%UrVO_8jkIwd1$g02v1y?Z?f<+6A3qUBG zKHNPTMo<^x1&S7Q;6em^CRL!}+rzk7?4>rzzEQNWTM@%h)d<7z{RyJ6KAr;EyLeIR*(@!wISz|Rh?q)|^?sI81f1d3Mir5;}P&W$X zb3V$;4G(?_Vo3>uk5B`0zD5C52%s6e;0okfEKKCOdcyVHQGOeTeOlib*nNy|xz^d- zZ>kYE!k{s&R#1p_x}hnUv}%V*xpsGVaY+Jy+l1~+^bnZA3$0<$waUwB9Ml{rKJ*w3 zh5HvH?0mrNXc9iJ*|=$Wb$2;VD`G|AT7S)1JNf zPYuHQY;r+-LL_FJaTqo@6rWe`sXRy@=_rHUd6iJkB(982?=gJiqR{E&YrpW?;hEp+kfo9mA!7g+hYtdBwwzzN*Rcr z!Mh^-Y70xw;Qm9I3m4M%?qZfcCSNAi!e_R6OUh&P;I{rzG8JdK%9Ss~@X2(V=T;^f znDQck%*@aik*JoCBt93qw)VVa{K4V9WT54W4pBhD4k3vNXuqe#X@U|k&Y9qAfrl2mq%Ilh4E}Lm3Nfy<|t=irZPj{ zl84cX(L}TGxN~xzEaAbdYOa1-xdK@Q(U(3UuhDey^f)qlOqU}Pplk8xOnARPuJ6tx zeU}EFE6M0(pavC@Ay=4CZV@w%4cfJB%q+mRvQzwGDc{rfqV`LiB|h>d7*GIjn8AzJ zlU(lX!&b>vXU%HeK3Yp${tqk=6-4_(@9S>co~4drR(yu9(5*8vk`ycRP*u(q_wzPw zDA6Wx8&q_t5EV@3E6BjL^WId*Ic!t*5=Ui^VSitx#FA$R$w+M$3jrCwTkP)*PU?!+ z9%(2;6u{ok=cecT^W|qX*QaK&=6AbXOM;T;_+o?GH@qPHtB%WjUz zzltVocPHZ>(j#IU-7X0uri*`V+kqq=o{kq<*^O+)yY85HopuCV^ms!wBf3LQK7$uK zmzTv(k1imfiU>TBv+A0d&@{a}RxoI+rt@igjcyrfmj%^qI|G!R27-9+&Dxbvk4(?M z^Poo?d>&_4(@}eyI}>Zk2;Js|f}L-Yh){ac-zfe>8sH-t#R6ei*poVTZw_!D6izlO z<64okUYWUe=R!+o(wJueE}TTFd~3HYwQT;3ka)r z4*&869uQQAjxb19U~hQ^#s2&e1MoIOAAc7C9V@G=1G#l4qlFYWCL-aevdMu+0|5l{ zQTA?)SN=*K(Pn^!2sRZC09z&&N6OE?Tjt~Ux-%*SAv0RQQZ`dx=LgIa8w&vsl1D>} z@t3RD$eCSe0ypg)M|z)^N^k_-AMAC)ff-yZc)*>Q4J-JhcwmAZw$4jOaAjNgQnD5r zq$)w0wyy=EIJy^0fL^8@I}Dcsu%mhZ=xR)$Ropo zTBpW%y*aYD%vcpfn-y$_yZ z&F_>?ix@pG5J1Y+sx>U&qYtAvVy4r6+MWr$Nrbr9-@Qdrc)}lX9x>C@JRhj_Xr!rEG-GF9L2Y5kRo4%ssn!xQtUh z5ZA@x*60*g0l#nG4y-)nJ zyOqk2i0y6O&s=0k1w+I&A|)ijYnWNQlrTcDem=E2UJo~w_F*fPFzt0$o`mysI52wF z5aU}%TY(n89UC1b@{j3&k0pS|s{3dLkIGBToTf z<-!KGQ){^Ddz(LnSj9@u##VrT&jj=kt##V&SC)SEMd*8Edvnpy`KW6A-|Uml&lP%Pc&=H{+6bsKFh z7X4a2UV1&|6e}zhD^VEx0Raiz4iU&(IZSch1Lm}U!*ET6uhHjZ;o@Pwm;CAA*aNfd z^6|Drb7*i;Ds*=k>|yY8c04InqEErZ1vkY8^j^EQ=%jCJnp%{5>g!X?>b*_0I;I`4 ztR{*^k-)rgzp*LS!M;y59ZW{I3yVFIy^mJwjd6MzSiw=Gc z0M5Lv{E<|l6jW7Fgj7{I0aev$Kt=W6|M1^E^)LPW_np5q`zZTVrT3W(L?AB_`1-BFEFHpjF^b^8ucAIZB(J-uzc@K~nux?8Cz`bg(j5 zMR4k&d-qV}H9r>UUqPF^>Kc9*Vo<^H-3?Szumo{aZHUs`03&?y{BB+i>|A^K(^3SN zi&8d9*R6TImU6Ab!$);X_D9S`kdQt_pdye;gb;aT%?YIKCj`~+`T0R91;>zQ&TfBF>!NqlcI@ZV`D==KyX$vUp-@xrG$eM zHk`?bL7+`h&6 z5=55t7DkqG?ov!N=i`eX-@Ls)X9|hA2Bel?VtiClC@m|~&otCG&?_!2#lnlBS<}#% z751UXMDg}k4uTHa+}w(Z734!NK!k;bwY0Px92~6J4*mM|sE*;C_ua4Fxu;{Ywr-51NH+t*j2?;#Ae*YelVvz4Uc-An>8-o+h0f z96TN$_UoJa7Q3NBTGEuetbL2x7elqQX2I0d{rJ%>+J_;mLD19LNtwuhV{E!9posIM z<^mDv>)hO2Rkht`v&4Qa9)ZY<3)uqbL_*4*9%_kRRaI4{cG~wd41rJ9KQ}izG7=SuDV3^#$DO#pzwhVgr`JWx zg5R!adz6!#`&^885&(UiTvkSUGEC3#x&aRl8{M(O*~!5+kR3@^)4k6&sKx$d;}fm| z=-6{Mjf58){N*tDQUop{tF3?V`G=LO24}JU2AyL~7{vmIpWDlY1upLNpa3B!u!phC z^Jm_NWsma@tM@R*(?zOo*J!Wxva3?9I@6I<+U!ccy5sSK*=)fPuLZxBCyrtiIE;SCZ!NZI#wDHn{m;nnjgZtfu zhNd|#YFrLlkffZfX_uELEQ@aK-q|MOVO8hhyz@y7@`2zt?$-r}g3?WRS$q!Vp>R2t zvB}Y5$_{i?g3e!e=pxAYaXr@qN1B`uH;*XpugSD)>`F~Nv0{J{Ap9A8(tRplLQ}Kf z8m741e7HS#5Zbl#5u>N4zYx`Jb~!CC0lJ~qz)t8_+dkpV6VuIX5*(J>jRMYP{7xRmoF$(KoZu<+}!T4ow2t~wVkap&ieiPH`Wpk3Ebq>;r!$H@zVxROL&kwP!2v0BO>~h z=DasC-5m1MXfWVJ)QLH(Vi;52U8-Txh(I_kl&fYsfILD6?NIk@LMw^&)hMfK>>p_? z^4MxMm9Tp2MD5(z*mm7Stmy;`)+1M8Em8Bw+N{h}Vl5a|uB)$w{r>oz9r0Aw_hUXyyqkr%7f;x;cZKUBR#J z6kclNpS^Cz6$OecewVSk==32)zS|TsHA*RaG7Xp4UvM=5KeI1 z|8?7Thw7`UriT0Bi5JXqKz}f|Z|B7iju=r}DD;mMX_^NmJZ1*5Lx}6W{;qW>qlmDV zFBi>A0Rabef6%^09!hy@gI%LoKf%nqpLRJji&lblStoXoV-8~qbA9n%KR}=bcCBEA zg+j$P5`Uzn=^8%Wp!*f&e&m`|^sndVXYck2PX;g=rpetlW7Sv@+9a6>>jR5jetK8% zX>%h)*{D#Cm<%Dhb_}tIk?|&cO-B-b(>M;N#&j$dE6sonMGt37{#AzRtAn3{DiN#Y zzB2FrCeu83;o;xU)6UQ(u{dmOZ0`+Vpk*_!{a7^kbcPa2sorK=Z0j)W**IwkuP#+v z>HUj1xdCps3%X5>;g<2<(es)5FWEbE9DbFM%syaua&1avBY5n%#8bt=uow2?bBwzi zg{nsezq?oO1rN5K6c)@jx~lS|3d9))j}kR6n-9nm>}_AjLu$es)qPE0hE5WYxAWF| zf$BX~BjU%XxTqi{byg-SHQR`_26W0Zl`r3K0{kcDl3^!KacVeNFj{Ji=_~anzL58b z(}sl02aDYNe)|R?1jpRMqL3~uB;?fNlGGinR@5{MJ`PrgtPKL6{Ii(v>`B#5EJM9N zI{^Q}8yAonx!*_Tz2Tl$5bC;CRb6dt&0WP)GqVOh*5}Sp;xlq^aIk(ZE-qeoW&Md2 zp%KcH80}kt5~ol2vqJ(#<_n>jnVDPQk$n2ku0>y@?(+)AR!DA86`C1aIpjMEvDurT z_VErV35CW=hXF)m)WLk)XD~+cLv^h`cwXh;SLA6LpL4Fs{?VelX0@uC=D<5-Xn9vd zhx&o!)R{=(SEpzJ-H1nu@9=zCf@u6cJ?OiAO5PdZuF+klC=qhRgJIsAqY#%RCE~HV zN!3#j^JcS1A_~X5>tD@Q332@e9+)_s#0{UL?7Py%@+F(+h!P=Td?V0vz3KH6%&l@w zjK6Ns33Ivg@{-@crXH#&Q(g|5D0EWihdiq<>K%1VWCB4uOEqX2R`72}GM8SiZg3%R zA_yrXFQ0Es_^tQy47_4&k7W#<&otQ6f<~)M$4HYq)P0O^&o;6BAFt+Zh7WoLT`>&R z)$xCd>QRVX^o9``be}@qo$FF?6D%$~4%oXr6;i02gaUpt+W+xd$q`nIg?0fbpj95=jLlS99cIQMm>M1TI<=Rwqu3o zI~p2@yg>3%A~Le$+r39UoJ5?0o1E)jQ5$$f8$R*JJG#9rKFLWKPrgUwTmQ;PiovO` zsEjwY#!CpT3xcyvP#=Qy>42^XyXYkvySD^}^Y?;toghdRo|`o&82^Vph)2)nBMChv zmP@4!|0x;IuQ2V;s}aC+kh7T*n&cxV=4&7iQjb9$$RUk%Ljn~7iHOV}_8KKzJr>0NXV)4dzzIVQPD0@g3Fx;JZNBt+V6?xQ8$^mJ8 z`Z*jQYD6_AAMj$SHGQqVvLhh9SHCQALnuyHmFEUlNVZ+WBNR*3utY3_k*S?U17{-f zSKxK)U-|viBUNe$+jx#8CQ)qAmSgUA7L8VF7mdCN-+-*8AM=mQ)SIr}qb_k%<6i|7 zuwiuU<7>Bm{>hHiR)jnrvauV^vfsN zwB}QB_O}&CmOp&zVU-+eN90b{79O1M=*jISh5D6ski(%3NAODTRn=sS`J=b}y+1IK z1zlt&b&~}18EW*Iz`XJ$}ZFQcBI$r5%}u_5W}c6+iHQj}_%<=o0pBn6>gs{`AC09-YR2Ib$}b3Z;=haeE>3V3U2Yp15Bx?LSmm$^fuk?}^wM?-DXe)xjO zM#)m;=XW21z@9SALU4o#<7zB^Iy=*ScgK6P(EQ~s2RE#+mL509TfXA{kxx09RI~DI zQ-fA8G56i(0Cm@pl}Cm;8nkd> z{*>|_{iQ#$96%`&)=F`?@ z>{pY|G4av!)6?wy{Mmv%(~%#Msk%b+V|5xl44s@%omLlch|L$wi)v9X(3qK+1U=6v z#|5#4W7HA&JkK7crZ^hcMi1|6LAIjUkMS8==GbH&T2hMBy1TVUNhv8mO?`AVbbc>R z`z?L8u#%V-eMe7mKL0x}Bt(h>Sn=mJl+Dfg#46aO@I{ec=JoIQBnj#`8}W_~4&MhQ zzcNaz(#ZIb|6N?^E6_raOC|v_Jd_**eM8I|x;BEaA=upt14B#`sG*3Aw)kt`TEYGp zQ`5R%uwGUpzq)Ubcd-W|@n$%?S*RrHnj~6P@H1ozF$@Y?x6wz@p@3$_SdV5VU zY|Jfr?C&6nAH>Ejr6*ysO$v4x9YZc_6#A&uUUg;X%ca}TWwZS}KYb``l!xy$&-^2v zShx4Ew7@Dw$Wl7jz@UtUnwnaxr^Vpt>nY!52}8M%);EE4gcereq&&F-V)$oYeT=x@ zeA@<}h>)JaL3X7~R+{WPQMkv4hwO2xGBQNJf&A2knD z1?>q?z!&>~_4GxR5C~EMe;C?_llKu3GVYIiOtoj0^NsgG*>7kVZl{Od&g@jz)&@rW z;OYs86SV&*r1K^cnU+gXFyc~lwX0*V;uo^L8xyJ+R~*inbv-~)t#F7~U^(D-!H!9b0joo*e1?!l_fp;YJ z8V2XO`ogzky2H@=7z$uTe=%YTKP-@fX>)?axZv!j%{ zy!s0FbP}~7E)a|2+9Sl4iciX0LRXIEWh}-o-ueqUTQih}r95!}kz;G$yiVka^`T`3^DUEo*&Dbn^Gc@+su6s+MPBZJL{%C34`rJx}+dLpp3ci z7gN(o_N!w=|LMO>-W(k)Dekp*&v-lLa_&^PZ!6jGNeQp74&f{ZF;1VJAS}}m5aGM9 z34#o^QRX}zCxaEK=Gq4am@!+Qus@3Y5nQd|y^gI+uzV2Md2>ALDYPC~_ollqkGeY- z1ucx2-wspwTzyrzt6{cbuP89M;^x~$bJ15f=;=ojc(TMBM^Qv`jSB(dFa%_zOm653 zmroz7M?S=Q`F>x^Qc6(QMH17Nwn9SegJo+TnbQvy(rGSQqaEDPL5Kb`E#^;Qb+fd< za?JRA$5}DI$~@_{8JP+8Q>{*5(G7x?2zAl1{^Gq1s3BLDlLg_+u(Ma63`rMP>d?$t zqUOI}-IxEuxgq9c%bJ>)AVJP`X85CPG`#^N5=LYMnbq9f>~VYqrEA0IyH)864&-t+ zGQyS@2@eY+oAHLmaBL&!gsY|RFa}4!WL>YWDBQS^UOcr(yit^@_?bWI(~v^<2rY6c zmdjkJ@;5G!AFpaKE@4)3sEmt-MA6lt{$hcR8^U(Z@ZHn?vDeF(E{G$G@beXqo0pXI z!p$Y5$1^?*U%Xq`8_qY94q;ND@vQ*;=sx$ksm_2>4OPO|(!ET&ebEVMqvKPZq+a|$Bdb-)Z|hY{tH}wy7E}z_7s)?fQ`}6);4xFUx)FQ==bB*T?g-H zVZ1-h@PlWy7H2f$b}I#`?^#e4E0MlF1*r>QyxC>UI7)Bf7QfArtS=c7|Tfd%PX-P1V`4VL8VKl{ejIzDeSnmmHBl>}a0I z{r`|*zlYNa*FH2fWW*UX)^ojR_v&)Xcy~6 zT$$2kZ0{SHqD5j)7&7}?Fh{WeQYISgq^nwt#f4z^2@GG|o)F9>VP9N?H zyIsRfj&MUwDpJWCyfvR3v6D9mV;d2VycJXU@SSvX1FSOr*OCmgPy4RQBJEG9%H5lt z`Z+p#xjK7qz&p}Ek=kFAorh`ZPq_O*B`5>IwzC@xL_vUpnlE`nSFO z^%?~*hV!30+=2M(`F~5{IMn~i-2bF76Yk&k@)zAgXhQ#HwEoc{i1go$)<5ay|1bCi z%g60M4FBIdy!bza_z$xFCguNf;9gkX`2T78f9Og-!hcebKNLn{{aS(raha06od8dDMu7b@cNLZvJYEgK12$1K^32t`n?w($}ru56nFcKV2 zEXta>olv->)RdXFfdkI2W(=!?oScEsBS+oS4Fa*2cG$VpL`VkMY zqIy%leBjpN)$IM)<9&at)m^-zDm1-JilJ%)cz9Jiqsqn8$Phhy|00~Pb+eqeoJtyP>~Ue&}n4F>5YG2^t4?D=Lrg3RNj zBaZRold26hHMKD~f__W&S%Ag;vgi^8C9J&VEL`SvITZ)>;5wB!A zmh9Zzp1yhAgYwn~Cs7v_Q&UrSt)>i{%o&NWRNJS8p3za}jfDoJS3wc%Ha0z9`G9M! z>$CGQK`03@+(k-C`g~bZVcSvlT|}JEgd zF2BwBcquqI*c*XbHbdaC3r%pL+BCv-x@~Nz)#);@O*c~K!Pz9>tql*EsYZ+NtiAntI2j+p+iwJ%ryC(Z z17O6q4olU)#NdGUa9hJk)rNhjt+(LBj%RB%m&a)V{qIqR;_`MUHWBvQDn*yc6l(%;>J zQv$qCd+;bIPXuV&sHtaLhOy8Y#Zc z71(2cJK7tOlJ%7~JfqR;FmO^LBDK9}wX1hDr3oI?FGCjPx+j$ zd`UG{+n>6qdvd{pJ~A|t@y;yp_Vrzuk@2Q2dL*HYhDO7XCnqN-%co|gT|Vc7k>P%& zyD%aGVW02PXsC!WNUfgxwc06t3u4$K^IDZ_G>pW&4wAd8tE;+Rx496p$WA@XD#ICG zce}Ue+dCYtitTiJi-9OaPtiD^DOP`fi;(XeX)y2Mtz^+^WME+9;$?{Em64GtS}ls( z?|T1g%>WA=Aer;+m?jsQj5qL#lIVH1RVUrcGBBzd8rHx5`Cp_HL;?t14JWZT^7HdQ z_Jrc?H{r$l`$M~q*GJWYQ5M_P&g5i5y?#qHBqT6^Mo+N_PF#RG!gP@P@BwBM+~#As zj^|1{1W)kM{e)T)Gn(|uE7rDy!NDSM22$~ox}z#*kMk0#l>5-<)JdS0&XTFTKq@Kg@GuaQf0>08PA0H1LQfN(5twZAG2t6FWg=5|FiO*mgO??v~j;60&!~kk_8W9gU^gY0$ zl_lcPySb@U_Q%QeH&Zr6Bjui~U`gSzMbdb&gYQdU1Kf}akiFmnluIsEK0P+976D>| zU61Ca!P+{e;z_cL>mA|Jpq9;r=~Q-Kb5EHViW7u0ywGkCsvCx+DsLHyZU*q!zc%VK z?`9Oh--Jd-@{D4Qf39&s^ad_L*r~G#87&u8yUi;msN{e<{$_3SCpcW_`M{=iDdICX z6VMxdcCmv?RC`>iUin2IcsyP2S^7}ni;O+{8Q<6djNENSO^P+QJmj20FJMio z^6s=*4lvBhuB5+8ymlN${GU_SIjWGPWU%$~R*H>>r)OOF`*tzXiw-}yI8<3>j`>-P zPx0U&RMlC8k(=9F9ut_yvjtNagSY;oaV)0C=}_McWTj>Ef)yvadQr3xnju5SVZ}Z< zSuQK9X_nzR&h%7Q8Qf~-W&@eNDVQdh1e^vBv+UahIWi6@ei!|SzP0%D$zxf|!AUsq z$+`_5VbZFan&R7W&CJHz5|qKoT>62viT>RT!7XlrNiJSl#LWMS0|&*8~RX@(<&c zFOt`X1J#EdrR(3`?U%#UD7uXE@(#st&hVg?u%ACO4UQ@>^_67m>XfU0*UjkQX1JmD zM?}TUE-WpicKM1C5j+=2k&ZkGPT)!>T-l+7feE<3BAyE6Jy>H_lJygU^8M}N+07jN zg*`^GD28?vka9PInFzocHl48Pe!O5KI|ij5Xy?Yr(m<44TD)aXSEW4Adg>?b(uGOD z|GQw5@wz?!NEK5}z1C8rTpa=e>+|D^Onl%+OWAjZ>`N}C5y3Ypj%w)jZ;$uC;r!3J zJ%|JZ20^3JdlrzGot7jtHIpY5>39ucv>~@g)Aqjz?Nu7~X@BDm1EbLS4(AMyXKrz) zX)!V2@IyJw3Ka}6ey5>aLJ5>WI z7L4F2|BcBKbaZqf@m+A%BA8J<=TXGu;q=`OTPrixT`+`~!#rmXtHyc&hVV+*)hb?u z_Pzz@MTR5}faJyoi5vWjMpCMscQywz>KNgT_f@>@r&g* zv#xDuKg2uss`!T0o6K^Z2J!erS9iu1%ppYCGof?EhhJ``Z|9%I)yOH3+1kAyMzx^8 zfuK}jR1duV&bQ6?*Cd1Ls9Qp@M$&UaqLESyMsv7nILP-(r#wC2`6qj>@n2V!ZExsW zhh3$!ypGS>EX!W^R$5vqZe;m#X^`U@;Os}42?EB7?%v*L^s6D3Hr8q_Y3Z+Q;4GOq zGs+6q_dlAOwF$mFfSw*0q48e*S>v}yr_jwQ*}=J#gveI0lw`u<;<)LgxY!661@zcL zvb_$NjjG}alv!gaeSK52EhlT#&ft6&pRYnXjTLP9(A9L220fyP+(9c`*BXD6=VX}` z3Ks1v)_|wfC|}2ytr9(tA{pAa3bvck)7zs>=EMs`wRU(oVm>zmz2{Z%riUAyj}!XK zlI!^vfq9Gdb5jK*K}LFzCwQ^Nz;1Q=rgY!={#6WS3|Ikinbm2gBN@I?3kd8wUmXcS z>C+*uyV%&->~pCG7A%n`9hxDwcm;vPKCks2AEWiI*tV`iU3jt1b>#!JF9__E= ziC|r3=}ak4h2HgWLmhX;TnIFO4{?jmdU94%0<>YG%X}fp_oTkM!L+NA?=s*g7tqr0 z;Q$|fa*FjnXQk&9eEjp!dl+y(RJrkxAY)CNIqNn)_4iezhSY{{4EbSthQA9>#VE;4 zF#^hV47sztblGxICB4kJ?D=t-i98&^Zc64McV-anyDp=JqEY5{GF6y{)bc7+^W~%Pr=HS)>!)hQ2tbv>3RygnF?dx6e6;e=41*30&7-|RKkVqg_gV(}CO zhs?nL!MY+*^CHbD{h#7D)Hu@Q4higq>$57YFV=W)*eEH)Q)|@brSr3cIU#K2FnXKS+q>_|`v}<5--4BdkHE=-~a?%~- zQT8ed@q#4dQux))SnsF)PTCzYm^S$DW&2%EGY!9;P7k)_t!o35W7H-!o9OD>k-|>` z_}IQ7)MEa2EcBp=(<6Om)oR>a$;3v+yzRh_-42@k9FRu$9{%sxTmpp$;Bs7Ab3YQ4 z^1L|NV|lxL$LafLpb$H-l?pc|BA^FMf2s*+w*GT??%4d%z)`nvOzzLh>}poP&}6-L z1=&@ZuY_G1!c$>HyqO;O)Ikvb5^9%}0IqoreysEQy2W!{;%9^{rD|Tv%RlOn##h$A z&TT5JMnpsuaJ!jzRtYEeEj)bF2v4gv6j}eXPiPYPYv{kDYYDO%CTO$)d=gS-+{%;XPzeWkuFP+5!injw|Q9>wUvj9e6U-% zdwPoS&0MtBYIbpPvFXPT|29{i+$@4QFpEx(m$LF>J1?Gb9H;?X1N_{N%*%;sX?~x$ zone9q>ynE~p&tEYWQOE3n&B_d#Kx)AI^{Cn3376VmDStdw=$6vV~DkFZpRb80FO#4 zmmeET-M6;3E;Lran4+WeW#-#C&3Q#m40-APhpQSh0|QJHfSY0zUzy$vZ+MJVMO$SM zbLbBk2Gd|nLh`AAEqd*%Qrv*jKfzFLh)7VK#;Q^{{D-sCDw$uu=OctEVcw{m*~$Fq z%IZWU4&=_Z(QaFuoh_~L(bcNvd?O&jE5`OWSO3gJ7cCrI&^5_zOkahz#wNX2| zp!-%9SA}$l38{6AF9qKlfhw|Hp|IX>_{~$*At1#SvKD&zV>RSWJ|~u%3}mJ z8;}#oA+O(#cdT~`dhEDZ$fW#2cFXB;cfXf4o}QcxeFU3MzNX@ufp>BH6mHt$JJICIPcSf;@Q|7B=Hf2iI01j`xf8B zWlBz(qV~W4{~?e19SDNW#nr1xJ5}Z7=C7xIA~)R6Qu-ho(K(y#5JJiTh#!{rjB{z9(I-4M25 z6W_$x9*>)FG9P!s@po!2K`CzYgeotH4W94S(71S5}0YEVll;(XqD}9h{s-0#i zBk{_MZbH}^@ct2p0DUS`4uRsN_Zg->nV+utREwQ!O>g>ObLjW?jtR>21g(YTw;&vm zgZ(~=-pS$N*AG>b<|PRsTq#|J3O9Gk+9)R4kNvJ^lWL3rLYRPb*PR2yS|*q&+qa|7oHB5lWGIg&*;J z*R=d%40RY?3S*-Te=s?ptK9reL_v0bv^w5%`XnU-q}UMh*TDab^LU<`GYh@CiDcC%?Ah`nF-qqDb zfc@C!ec!PBCyv3YyH!S9+-F){BTQag)7o7NE?)aIJapvw&FS+&!nSocE3zM1(W~Mi zCt~OFYwtuhRAkP&1k_F+vYR^U(jE96)wj`|v|JU#f&b4GTENS0Hosy$#H`SAVcXju z()(&>P1}9xdJ2pCk3+JOUz%_Frz-V3sRWifu5vwXtoTd;v;+y*B+wU-V=z4S`}c2= z`rEgXH*b8m{W|}xChk3uAiG4wT8ujl?Ow8W!shw-3!qSj$pI^93 zJyn%hvNK`Tqz^{C8alwGM#ZqDOAZwbz@b5&!wtE|TyLk%@|nBR&wXyDQ7#*}fwO}B zvVZ7Y4&z{}gB`opWpkfxIU=le_)77t`Yo(B#hGf`z+oT`TBUV}`N7+_yw!7px(wTz z;+t=??{5oe05@=svlLe+DNk**`8Dy^KK|lA^LA{0@LlE1LF4J^pRU)*M!ex;VHA0r zGGjxEo!Qi!|6lj8GAT%W`+H9FQBgk=Xq}w?SBZvTVA~WpnhqJu2DMy4qu`*km<}-9 zDR*^Ai(`<{H&#wsUcGmToWraAJPs~BhO1tc+c2!k>Jb!ttqPjdOJy+Hyz1GU^B);z z6o-YTt(y*-!JE#wV_kOrG3EpHN$%Clt8;_+6~6uj&Z{wk2HO+B76PpS+6J^8CVasj zY&WVPHnj`tu^WJ)*5Nl!%TU!e04Ep^5UF;-Tq4yvSi^jVY8l+aFyV$Wq)>nfq6H2_ z_$}eUIRN&?ElKpy#bNFi-~K+Y!+kQUj5t$a&l7&F{rN2D}t z{)2=Ya8hRrKT4{^tu{lR$S}TOM`Rc;s3q)Sa8jW`!XBuAykN~RGcvUJ9DBp#vzDv1 zjIVoxmJgg}a5*BecvT&P%=%TwBm_6cfeMg#wg>yPm5e?!J*WWA`B~oump}V~@yhUH vw;ibD0>%|$X~F>z2N+tYg bool: - if sealed: return false - if not server.has_peer(peer_id): return false - var new_peer: WebSocketPeer = server.get_peer(peer_id) - new_peer.put_packet(("I: %d\n" % (1 if peer_id == host else peer_id)).to_utf8()) - for p in peers: - if not server.has_peer(p): - continue - server.get_peer(p).put_packet(("N: %d\n" % peer_id).to_utf8()) - new_peer.put_packet(("N: %d\n" % (1 if p == host else p)).to_utf8()) - peers.push_back(peer_id) - return true - - - func leave(peer_id, server) -> bool: - if not peers.has(peer_id): return false - peers.erase(peer_id) - var close = false - if peer_id == host: - # The room host disconnected, will disconnect all peers. - close = true - if sealed: return close - # Notify other peers. - for p in peers: - if not server.has_peer(p): return close - if close: - # Disconnect peers. - server.disconnect_peer(p) - else: - # Notify disconnection. - server.get_peer(p).put_packet(("D: %d\n" % peer_id).to_utf8()) - return close - - - func seal(peer_id, server) -> bool: - # Only host can seal the room. - if host != peer_id: return false - sealed = true - for p in peers: - server.get_peer(p).put_packet("S: \n".to_utf8()) - time = OS.get_ticks_msec() - return true - - - -func _init(): - server.connect("data_received", self, "_on_data") - server.connect("client_connected", self, "_peer_connected") - server.connect("client_disconnected", self, "_peer_disconnected") - - -func _process(delta): - poll() - - -func listen(port): - stop() - rand.seed = OS.get_unix_time() - server.listen(port) - - -func stop(): - server.stop() - peers.clear() - - -func poll(): - if not server.is_listening(): - return - - server.poll() - - # Peers timeout. - for p in peers.values(): - if p.lobby == "" and OS.get_ticks_msec() - p.time > TIMEOUT: - server.disconnect_peer(p.id) - # Lobby seal. - for k in lobbies: - if not lobbies[k].sealed: - continue - if lobbies[k].time + SEAL_TIME < OS.get_ticks_msec(): - # Close lobby. - for p in lobbies[k].peers: - server.disconnect_peer(p) - - -func _peer_connected(id, protocol = ""): - peers[id] = Peer.new(id) - - -func _peer_disconnected(id, was_clean = false): - var lobby = peers[id].lobby - print("Peer %d disconnected from lobby: '%s'" % [id, lobby]) - if lobby and lobbies.has(lobby): - peers[id].lobby = "" - if lobbies[lobby].leave(id, server): - # If true, lobby host has disconnected, so delete it. - print("Deleted lobby %s" % lobby) - lobbies.erase(lobby) - peers.erase(id) - - -func _join_lobby(peer, lobby) -> bool: - if lobby == "": - for _i in range(0, 32): - lobby += char(_alfnum[rand.randi_range(0, ALFNUM.length()-1)]) - lobbies[lobby] = Lobby.new(peer.id) - elif not lobbies.has(lobby): - return false - lobbies[lobby].join(peer.id, server) - peer.lobby = lobby - # Notify peer of its lobby - server.get_peer(peer.id).put_packet(("J: %s\n" % lobby).to_utf8()) - print("Peer %d joined lobby: '%s'" % [peer.id, lobby]) - return true - - -func _on_data(id): - if not _parse_msg(id): - print("Parse message failed from peer %d" % id) - server.disconnect_peer(id) - - -func _parse_msg(id) -> bool: - var pkt_str: String = server.get_peer(id).get_packet().get_string_from_utf8() - - var req = pkt_str.split("\n", true, 1) - if req.size() != 2: # Invalid request size - return false - - var type = req[0] - if type.length() < 3: # Invalid type size - return false - - if type.begins_with("J: "): - if peers[id].lobby: # Peer must not have joined a lobby already! - return false - return _join_lobby(peers[id], type.substr(3, type.length() - 3)) - - if not peers[id].lobby: # Messages across peers are only allowed in same lobby - return false - - if not lobbies.has(peers[id].lobby): # Lobby not found? - return false - - var lobby = lobbies[peers[id].lobby] - - if type.begins_with("S: "): - # Client is sealing the room - return lobby.seal(id, server) - - var dest_str: String = type.substr(3, type.length() - 3) - if not dest_str.is_valid_integer(): # Destination id is not an integer - return false - - var dest_id: int = int(dest_str) - if dest_id == NetworkedMultiplayerPeer.TARGET_PEER_SERVER: - dest_id = lobby.host - - if not peers.has(dest_id): # Destination ID not connected - return false - - if peers[dest_id].lobby != peers[id].lobby: # Trying to contact someone not in same lobby - return false - - if id == lobby.host: - id = NetworkedMultiplayerPeer.TARGET_PEER_SERVER - - if type.begins_with("O: "): - # Client is making an offer - server.get_peer(dest_id).put_packet(("O: %d\n%s" % [id, req[1]]).to_utf8()) - elif type.begins_with("A: "): - # Client is making an answer - server.get_peer(dest_id).put_packet(("A: %d\n%s" % [id, req[1]]).to_utf8()) - elif type.begins_with("C: "): - # Client is making an answer - server.get_peer(dest_id).put_packet(("C: %d\n%s" % [id, req[1]]).to_utf8()) - return true diff --git a/networking/webrtc_signaling/server_node/.eslintrc.json b/networking/webrtc_signaling/server_node/.eslintrc.json deleted file mode 100644 index 1041982..0000000 --- a/networking/webrtc_signaling/server_node/.eslintrc.json +++ /dev/null @@ -1,318 +0,0 @@ -{ - "env": { - "browser": true, - "commonjs": true, - "es6": true - }, - "extends": "eslint:recommended", - "globals": { - "Atomics": "readonly", - "SharedArrayBuffer": "readonly" - }, - "parserOptions": { - "ecmaVersion": 2018 - }, - "rules": { - "accessor-pairs": "error", - "array-bracket-newline": "error", - "array-bracket-spacing": "error", - "array-callback-return": "error", - "array-element-newline": "error", - "arrow-body-style": "error", - "arrow-parens": "error", - "arrow-spacing": "error", - "block-scoped-var": "error", - "block-spacing": "error", - "brace-style": [ - "error", - "1tbs" - ], - "callback-return": "error", - "camelcase": "error", - "capitalized-comments": [ - "error", - "always" - ], - "class-methods-use-this": "error", - "comma-dangle": "error", - "comma-spacing": [ - "error", - { - "after": true, - "before": false - } - ], - "comma-style": "error", - "complexity": "error", - "computed-property-spacing": [ - "error", - "never" - ], - "consistent-return": "error", - "consistent-this": "error", - "curly": "off", - "default-case": "error", - "dot-location": "error", - "dot-notation": "error", - "eol-last": "error", - "eqeqeq": "error", - "func-call-spacing": "error", - "func-name-matching": "error", - "func-names": "error", - "func-style": [ - "error", - "declaration" - ], - "function-paren-newline": "error", - "generator-star-spacing": "error", - "global-require": "error", - "guard-for-in": "error", - "handle-callback-err": "error", - "id-blacklist": "error", - "id-length": "off", - "id-match": "error", - "implicit-arrow-linebreak": "error", - "indent": [ - "error", - "tab" - ], - "indent-legacy": "off", - "init-declarations": "error", - "jsx-quotes": "error", - "key-spacing": "error", - "keyword-spacing": [ - "error", - { - "after": true, - "before": true - } - ], - "line-comment-position": "off", - "linebreak-style": [ - "error", - "unix" - ], - "lines-around-comment": "error", - "lines-around-directive": "error", - "lines-between-class-members": [ - "error", - "never" - ], - "max-classes-per-file": "off", - "max-depth": "error", - "max-len": [ - "error", - { - "code": 80, - "tabWidth": 8 - } - ], - "max-lines": "error", - "max-lines-per-function": "error", - "max-nested-callbacks": "error", - "max-params": "error", - "max-statements": "off", - "max-statements-per-line": "error", - "multiline-comment-style": [ - "error", - "separate-lines" - ], - "new-cap": "error", - "new-parens": "error", - "newline-after-var": "off", - "newline-before-return": "off", - "newline-per-chained-call": "error", - "no-alert": "error", - "no-array-constructor": "error", - "no-async-promise-executor": "error", - "no-await-in-loop": "error", - "no-bitwise": "error", - "no-buffer-constructor": "error", - "no-caller": "error", - "no-catch-shadow": "error", - "no-confusing-arrow": "error", - "no-console": "off", - "no-continue": "error", - "no-div-regex": "error", - "no-duplicate-imports": "error", - "no-else-return": "error", - "no-empty-function": "error", - "no-eq-null": "error", - "no-eval": "error", - "no-extend-native": "error", - "no-extra-bind": "error", - "no-extra-label": "error", - "no-extra-parens": "error", - "no-floating-decimal": "error", - "no-implicit-coercion": "error", - "no-implicit-globals": "error", - "no-implied-eval": "error", - "no-inline-comments": "off", - "no-inner-declarations": [ - "error", - "functions" - ], - "no-invalid-this": "error", - "no-iterator": "error", - "no-label-var": "error", - "no-labels": "error", - "no-lone-blocks": "error", - "no-lonely-if": "error", - "no-loop-func": "error", - "no-magic-numbers": "off", - "no-misleading-character-class": "error", - "no-mixed-operators": "off", - "no-mixed-requires": "error", - "no-multi-assign": "error", - "no-multi-spaces": "error", - "no-multi-str": "error", - "no-multiple-empty-lines": "error", - "no-native-reassign": "error", - "no-negated-condition": "error", - "no-negated-in-lhs": "error", - "no-nested-ternary": "error", - "no-new": "error", - "no-new-func": "error", - "no-new-object": "error", - "no-new-require": "error", - "no-new-wrappers": "error", - "no-octal-escape": "error", - "no-param-reassign": "error", - "no-path-concat": "error", - "no-plusplus": "off", - "no-process-env": "error", - "no-process-exit": "error", - "no-proto": "error", - "no-prototype-builtins": "error", - "no-restricted-globals": "error", - "no-restricted-imports": "error", - "no-restricted-modules": "error", - "no-restricted-properties": "error", - "no-restricted-syntax": "error", - "no-return-assign": "error", - "no-return-await": "error", - "no-script-url": "error", - "no-self-compare": "error", - "no-sequences": "error", - "no-shadow": "error", - "no-shadow-restricted-names": "error", - "no-spaced-func": "error", - "no-sync": "error", - "no-tabs": [ - "error", - { - "allowIndentationTabs": true - } - ], - "no-template-curly-in-string": "error", - "no-ternary": "error", - "no-throw-literal": "error", - "no-trailing-spaces": "error", - "no-undef-init": "error", - "no-undefined": "error", - "no-underscore-dangle": "error", - "no-unmodified-loop-condition": "error", - "no-unneeded-ternary": "error", - "no-unused-expressions": "error", - "no-use-before-define": "error", - "no-useless-call": "error", - "no-useless-catch": "error", - "no-useless-computed-key": "error", - "no-useless-concat": "error", - "no-useless-constructor": "error", - "no-useless-rename": "error", - "no-useless-return": "error", - "no-var": "error", - "no-void": "error", - "no-warning-comments": "error", - "no-whitespace-before-property": "error", - "no-with": "error", - "nonblock-statement-body-position": "error", - "object-curly-newline": "error", - "object-curly-spacing": [ - "error", - "always" - ], - "object-property-newline": "error", - "object-shorthand": "error", - "one-var": "off", - "one-var-declaration-per-line": "error", - "operator-assignment": [ - "error", - "always" - ], - "operator-linebreak": "error", - "padded-blocks": "off", - "padding-line-between-statements": "error", - "prefer-arrow-callback": "off", - "prefer-const": "error", - "prefer-destructuring": "error", - "prefer-named-capture-group": "error", - "prefer-numeric-literals": "error", - "prefer-object-spread": "error", - "prefer-promise-reject-errors": "error", - "prefer-reflect": "off", - "prefer-rest-params": "error", - "prefer-spread": "error", - "prefer-template": "off", - "quote-props": "off", - "quotes": "error", - "radix": [ - "error", - "as-needed" - ], - "require-atomic-updates": "error", - "require-await": "error", - "require-jsdoc": "off", - "require-unicode-regexp": "error", - "rest-spread-spacing": "error", - "semi": "error", - "semi-spacing": [ - "error", - { - "after": true, - "before": false - } - ], - "semi-style": [ - "error", - "last" - ], - "sort-imports": "error", - "sort-keys": "error", - "sort-vars": "error", - "space-before-blocks": "error", - "space-before-function-paren": "error", - "space-in-parens": "error", - "space-infix-ops": "error", - "space-unary-ops": "error", - "spaced-comment": [ - "error", - "always" - ], - "strict": [ - "error", - "never" - ], - "switch-colon-spacing": "error", - "symbol-description": "error", - "template-curly-spacing": [ - "error", - "never" - ], - "template-tag-spacing": "error", - "unicode-bom": [ - "error", - "never" - ], - "valid-jsdoc": "error", - "vars-on-top": "error", - "wrap-iife": "error", - "wrap-regex": "error", - "yield-star-spacing": "error", - "yoda": [ - "error", - "never" - ] - } -} diff --git a/networking/webrtc_signaling/server_node/.gitignore b/networking/webrtc_signaling/server_node/.gitignore deleted file mode 100644 index c2658d7..0000000 --- a/networking/webrtc_signaling/server_node/.gitignore +++ /dev/null @@ -1 +0,0 @@ -node_modules/ diff --git a/networking/webrtc_signaling/server_node/package.json b/networking/webrtc_signaling/server_node/package.json deleted file mode 100644 index 7b5b7dc..0000000 --- a/networking/webrtc_signaling/server_node/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "signaling_server", - "version": "1.0.0", - "description": "", - "main": "server.js", - "dependencies": { - "ws": "^7.0.0" - }, - "devDependencies": { - "eslint": "^5.16.0" - }, - "scripts": { - "test": "eslint server.js && echo \"Lint OK\" && exit 0" - }, - "author": "Fabio Alessandrelli", - "license": "MIT" -} diff --git a/networking/webrtc_signaling/server_node/server.js b/networking/webrtc_signaling/server_node/server.js deleted file mode 100644 index 46edb97..0000000 --- a/networking/webrtc_signaling/server_node/server.js +++ /dev/null @@ -1,252 +0,0 @@ -const WebSocket = require("ws"); -const crypto = require("crypto"); - -const MAX_PEERS = 4096; -const MAX_LOBBIES = 1024; -const PORT = 9080; -const ALFNUM = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - -const NO_LOBBY_TIMEOUT = 1000; -const SEAL_CLOSE_TIMEOUT = 10000; -const PING_INTERVAL = 10000; - -const STR_NO_LOBBY = "Have not joined lobby yet"; -const STR_HOST_DISCONNECTED = "Room host has disconnected"; -const STR_ONLY_HOST_CAN_SEAL = "Only host can seal the lobby"; -const STR_SEAL_COMPLETE = "Seal complete"; -const STR_TOO_MANY_LOBBIES = "Too many lobbies open, disconnecting"; -const STR_ALREADY_IN_LOBBY = "Already in a lobby"; -const STR_LOBBY_DOES_NOT_EXISTS = "Lobby does not exists"; -const STR_LOBBY_IS_SEALED = "Lobby is sealed"; -const STR_INVALID_FORMAT = "Invalid message format"; -const STR_NEED_LOBBY = "Invalid message when not in a lobby"; -const STR_SERVER_ERROR = "Server error, lobby not found"; -const STR_INVALID_DEST = "Invalid destination"; -const STR_INVALID_CMD = "Invalid command"; -const STR_TOO_MANY_PEERS = "Too many peers connected"; -const STR_INVALID_TRANSFER_MODE = "Invalid transfer mode, must be text"; - -function randomInt (low, high) { - return Math.floor(Math.random() * (high - low + 1) + low); -} - -function randomId () { - return Math.abs(new Int32Array(crypto.randomBytes(4).buffer)[0]); -} - -function randomSecret () { - let out = ""; - for (let i = 0; i < 16; i++) { - out += ALFNUM[randomInt(0, ALFNUM.length - 1)]; - } - return out; -} - -const wss = new WebSocket.Server({ port: PORT }); - -class ProtoError extends Error { - constructor (code, message) { - super(message); - this.code = code; - } -} - -class Peer { - constructor (id, ws) { - this.id = id; - this.ws = ws; - this.lobby = ""; - // Close connection after 1 sec if client has not joined a lobby - this.timeout = setTimeout(() => { - if (!this.lobby) ws.close(4000, STR_NO_LOBBY); - }, NO_LOBBY_TIMEOUT); - } -} - -class Lobby { - constructor (name, host) { - this.name = name; - this.host = host; - this.peers = []; - this.sealed = false; - this.closeTimer = -1; - } - getPeerId (peer) { - if (this.host === peer.id) return 1; - return peer.id; - } - join (peer) { - const assigned = this.getPeerId(peer); - peer.ws.send(`I: ${assigned}\n`); - this.peers.forEach((p) => { - p.ws.send(`N: ${assigned}\n`); - peer.ws.send(`N: ${this.getPeerId(p)}\n`); - }); - this.peers.push(peer); - } - leave (peer) { - const idx = this.peers.findIndex((p) => peer === p); - if (idx === -1) return false; - const assigned = this.getPeerId(peer); - const close = assigned === 1; - this.peers.forEach((p) => { - // Room host disconnected, must close. - if (close) p.ws.close(4000, STR_HOST_DISCONNECTED); - // Notify peer disconnect. - else p.ws.send(`D: ${assigned}\n`); - }); - this.peers.splice(idx, 1); - if (close && this.closeTimer >= 0) { - // We are closing already. - clearTimeout(this.closeTimer); - this.closeTimer = -1; - } - return close; - } - seal (peer) { - // Only host can seal - if (peer.id !== this.host) { - throw new ProtoError(4000, STR_ONLY_HOST_CAN_SEAL); - } - this.sealed = true; - this.peers.forEach((p) => { - p.ws.send("S: \n"); - }); - console.log(`Peer ${peer.id} sealed lobby ${this.name} ` + - `with ${this.peers.length} peers`); - this.closeTimer = setTimeout(() => { - // Close peer connection to host (and thus the lobby) - this.peers.forEach((p) => { - p.ws.close(1000, STR_SEAL_COMPLETE); - }); - }, SEAL_CLOSE_TIMEOUT); - } -} - -const lobbies = new Map(); -let peersCount = 0; - -function joinLobby (peer, pLobby) { - let lobbyName = pLobby; - if (lobbyName === "") { - if (lobbies.size >= MAX_LOBBIES) { - throw new ProtoError(4000, STR_TOO_MANY_LOBBIES); - } - // Peer must not already be in a lobby - if (peer.lobby !== "") { - throw new ProtoError(4000, STR_ALREADY_IN_LOBBY); - } - lobbyName = randomSecret(); - lobbies.set(lobbyName, new Lobby(lobbyName, peer.id)); - console.log(`Peer ${peer.id} created lobby ${lobbyName}`); - console.log(`Open lobbies: ${lobbies.size}`); - } - const lobby = lobbies.get(lobbyName); - if (!lobby) throw new ProtoError(4000, STR_LOBBY_DOES_NOT_EXISTS); - if (lobby.sealed) throw new ProtoError(4000, STR_LOBBY_IS_SEALED); - peer.lobby = lobbyName; - console.log(`Peer ${peer.id} joining lobby ${lobbyName} ` + - `with ${lobby.peers.length} peers`); - lobby.join(peer); - peer.ws.send(`J: ${lobbyName}\n`); -} - -function parseMsg (peer, msg) { - const sep = msg.indexOf("\n"); - if (sep < 0) throw new ProtoError(4000, STR_INVALID_FORMAT); - - const cmd = msg.slice(0, sep); - if (cmd.length < 3) throw new ProtoError(4000, STR_INVALID_FORMAT); - - const data = msg.slice(sep); - - // Lobby joining. - if (cmd.startsWith("J: ")) { - joinLobby(peer, cmd.substr(3).trim()); - return; - } - - if (!peer.lobby) throw new ProtoError(4000, STR_NEED_LOBBY); - const lobby = lobbies.get(peer.lobby); - if (!lobby) throw new ProtoError(4000, STR_SERVER_ERROR); - - // Lobby sealing. - if (cmd.startsWith("S: ")) { - lobby.seal(peer); - return; - } - - // Message relaying format: - // - // [O|A|C]: DEST_ID\n - // PAYLOAD - // - // O: Client is sending an offer. - // A: Client is sending an answer. - // C: Client is sending a candidate. - let destId = parseInt(cmd.substr(3).trim()); - // Dest is not an ID. - if (!destId) throw new ProtoError(4000, STR_INVALID_DEST); - if (destId === 1) destId = lobby.host; - const dest = lobby.peers.find((e) => e.id === destId); - // Dest is not in this room. - if (!dest) throw new ProtoError(4000, STR_INVALID_DEST); - - function isCmd (what) { - return cmd.startsWith(`${what}: `); - } - if (isCmd("O") || isCmd("A") || isCmd("C")) { - dest.ws.send(cmd[0] + ": " + lobby.getPeerId(peer) + data); - return; - } - throw new ProtoError(4000, STR_INVALID_CMD); -} - -wss.on("connection", (ws) => { - if (peersCount >= MAX_PEERS) { - ws.close(4000, STR_TOO_MANY_PEERS); - return; - } - peersCount++; - const id = randomId(); - const peer = new Peer(id, ws); - ws.on("message", (message) => { - if (typeof message !== "string") { - ws.close(4000, STR_INVALID_TRANSFER_MODE); - return; - } - try { - parseMsg(peer, message); - } catch (e) { - const code = e.code || 4000; - console.log(`Error parsing message from ${id}:\n` + - message); - ws.close(code, e.message); - } - }); - ws.on("close", (code, reason) => { - peersCount--; - console.log(`Connection with peer ${peer.id} closed ` + - `with reason ${code}: ${reason}`); - if (peer.lobby && lobbies.has(peer.lobby) && - lobbies.get(peer.lobby).leave(peer)) { - lobbies.delete(peer.lobby); - console.log(`Deleted lobby ${peer.lobby}`); - console.log(`Open lobbies: ${lobbies.size}`); - peer.lobby = ""; - } - if (peer.timeout >= 0) { - clearTimeout(peer.timeout); - peer.timeout = -1; - } - }); - ws.on("error", (error) => { - console.error(error); - }); -}); - -const interval = setInterval(() => { // eslint-disable-line no-unused-vars - wss.clients.forEach((ws) => { - ws.ping(); - }); -}, PING_INTERVAL); diff --git a/networking/websocket_chat/project.pandemonium b/networking/websocket_chat/project.pandemonium index 2f9e7db..205a3a0 100644 --- a/networking/websocket_chat/project.pandemonium +++ b/networking/websocket_chat/project.pandemonium @@ -25,6 +25,5 @@ singletons=[ ] [rendering] -quality/driver/driver_name="GLES2" vram_compression/import_etc=true vram_compression/import_etc2=false diff --git a/networking/websocket_minimal/project.pandemonium b/networking/websocket_minimal/project.pandemonium index 12be0e2..4cf5b4a 100644 --- a/networking/websocket_minimal/project.pandemonium +++ b/networking/websocket_minimal/project.pandemonium @@ -16,6 +16,5 @@ run/main_scene="res://Main.tscn" [rendering] -quality/driver/driver_name="GLES2" vram_compression/import_etc=true vram_compression/import_etc2=false diff --git a/networking/websocket_multiplayer/project.pandemonium b/networking/websocket_multiplayer/project.pandemonium index 4612ad0..27b77b0 100644 --- a/networking/websocket_multiplayer/project.pandemonium +++ b/networking/websocket_multiplayer/project.pandemonium @@ -17,6 +17,5 @@ config/icon="res://icon.png" [rendering] -quality/driver/driver_name="GLES2" vram_compression/import_etc=true vram_compression/import_etc2=false diff --git a/networking/websocket_multiplayer/script/game.gd b/networking/websocket_multiplayer/script/game.gd index bd510ea..35e2899 100644 --- a/networking/websocket_multiplayer/script/game.gd +++ b/networking/websocket_multiplayer/script/game.gd @@ -8,18 +8,18 @@ onready var _action = $HBoxContainer/VBoxContainer/Action var _players = [] var _turn = -1 -master func set_player_name(name): +func set_player_name(name): var sender = get_tree().get_rpc_sender_id() rpc("update_player_name", sender, name) -remotesync func update_player_name(player, name): +func update_player_name(player, name): var pos = _players.find(player) if pos != -1: _list.set_item_text(pos, name) -master func request_action(action): +func request_action(action): var sender = get_tree().get_rpc_sender_id() if _players[_turn] != get_tree().get_rpc_sender_id(): rpc("_log", "Someone is trying to cheat! %s" % str(sender)) @@ -28,13 +28,13 @@ master func request_action(action): next_turn() -remotesync func do_action(action): +func do_action(action): var name = _list.get_item_text(_turn) var val = randi() % 100 rpc("_log", "%s: %ss %d" % [name, action, val]) -remotesync func set_turn(turn): +func set_turn(turn): _turn = turn if turn >= _players.size(): return @@ -46,7 +46,7 @@ remotesync func set_turn(turn): _action.disabled = _players[turn] != get_tree().get_network_unique_id() -remotesync func del_player(id): +func del_player(id): var pos = _players.find(id) if pos == -1: return @@ -58,7 +58,7 @@ remotesync func del_player(id): rpc("set_turn", _turn) -remotesync func add_player(id, name=""): +func add_player(id, name=""): _players.append(id) if name == "": _list.add_item("... connecting ...", null, false) @@ -106,7 +106,7 @@ func on_peer_del(id): rpc("del_player", id) -remotesync func _log(what): +func _log(what): $HBoxContainer/RichTextLabel.add_text(what + "\n") @@ -116,3 +116,15 @@ func _on_Action_pressed(): next_turn() else: rpc_id(1, "request_action", "roll") + + + +func _ready(): + rpc_config("set_player_name", MultiplayerAPI.RPC_MODE_MASTER) + rpc_config("update_player_name", MultiplayerAPI.RPC_MODE_REMOTESYNC) + rpc_config("request_action", MultiplayerAPI.RPC_MODE_MASTER) + rpc_config("do_action", MultiplayerAPI.RPC_MODE_REMOTESYNC) + rpc_config("set_turn", MultiplayerAPI.RPC_MODE_REMOTESYNC) + rpc_config("del_player", MultiplayerAPI.RPC_MODE_REMOTESYNC) + rpc_config("add_player", MultiplayerAPI.RPC_MODE_REMOTESYNC) + rpc_config("_log", MultiplayerAPI.RPC_MODE_REMOTESYNC)