Make sure everything works in the networking folder. Also removed webrtc examples.

This commit is contained in:
Relintai 2023-04-06 23:01:47 +02:00
parent 3a9b6ac729
commit e292916886
39 changed files with 76 additions and 1677 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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,16 +31,18 @@ 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():
gamestate.end_game()

View File

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

View File

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

View File

@ -26,8 +26,10 @@ func _ready():
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))

View File

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

View File

@ -1 +0,0 @@
webrtc

View File

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

View File

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

View File

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

View File

@ -1,4 +0,0 @@
extends LinkButton
func _on_LinkButton_pressed():
OS.shell_open("https://github.com/godotengine/webrtc-native/releases")

View File

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

View File

@ -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"]

View File

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

View File

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

View File

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

View File

@ -1 +0,0 @@
webrtc

View File

@ -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: <ROOM>`), 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: <ID>`, sent by the server to identify the client when it joins a room.
- `N: <ID>`, sent by the server to notify new peers in the same lobby.
- `D: <ID>`, 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: <ID>`, used to send an offer.
- `A: <ID>`, used to send an answer.
- `C: <ID>`, used to send a candidate.
When sending the parameter, a client will set `<ID>` 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)

View File

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

View File

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

View File

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

View File

@ -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"]

View File

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

View File

@ -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"]

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

View File

@ -1,210 +0,0 @@
extends Node
const TIMEOUT = 1000 # Unresponsive clients times out after 1 sec
const SEAL_TIME = 10000 # A sealed room will be closed after this time
const ALFNUM = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
var _alfnum = ALFNUM.to_ascii()
var rand: RandomNumberGenerator = RandomNumberGenerator.new()
var lobbies: Dictionary = {}
var server: WebSocketServer = WebSocketServer.new()
var peers: Dictionary = {}
class Peer extends Reference:
var id = -1
var lobby = ""
var time = OS.get_ticks_msec()
func _init(peer_id):
id = peer_id
class Lobby extends Reference:
var peers: Array = []
var host: int = -1
var sealed: bool = false
var time = 0
func _init(host_id: int):
host = host_id
func join(peer_id, server) -> 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

View File

@ -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"
]
}
}

View File

@ -1 +0,0 @@
node_modules/

View File

@ -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"
}

View File

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

View File

@ -25,6 +25,5 @@ singletons=[ ]
[rendering]
quality/driver/driver_name="GLES2"
vram_compression/import_etc=true
vram_compression/import_etc2=false

View File

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

View File

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

View File

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