mirror of
https://github.com/Relintai/pandemonium_demo_projects.git
synced 2025-01-17 14:57:20 +01:00
Make sure everything works in the networking folder. Also removed webrtc examples.
This commit is contained in:
parent
3a9b6ac729
commit
e292916886
@ -61,7 +61,7 @@ func _connected_fail():
|
|||||||
|
|
||||||
# Lobby management functions.
|
# Lobby management functions.
|
||||||
|
|
||||||
remote func register_player(new_player_name):
|
func register_player(new_player_name):
|
||||||
var id = get_tree().get_rpc_sender_id()
|
var id = get_tree().get_rpc_sender_id()
|
||||||
print(id)
|
print(id)
|
||||||
players[id] = new_player_name
|
players[id] = new_player_name
|
||||||
@ -73,7 +73,7 @@ func unregister_player(id):
|
|||||||
emit_signal("player_list_changed")
|
emit_signal("player_list_changed")
|
||||||
|
|
||||||
|
|
||||||
remote func pre_start_game(spawn_points):
|
func pre_start_game(spawn_points):
|
||||||
# Change scene.
|
# Change scene.
|
||||||
var world = load("res://world.tscn").instance()
|
var world = load("res://world.tscn").instance()
|
||||||
get_tree().get_root().add_child(world)
|
get_tree().get_root().add_child(world)
|
||||||
@ -111,11 +111,11 @@ remote func pre_start_game(spawn_points):
|
|||||||
post_start_game()
|
post_start_game()
|
||||||
|
|
||||||
|
|
||||||
remote func post_start_game():
|
func post_start_game():
|
||||||
get_tree().set_pause(false) # Unpause and unleash the 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())
|
assert(get_tree().is_network_server())
|
||||||
|
|
||||||
if not id in players_ready:
|
if not id in players_ready:
|
||||||
@ -181,3 +181,8 @@ func _ready():
|
|||||||
get_tree().connect("connected_to_server", self, "_connected_ok")
|
get_tree().connect("connected_to_server", self, "_connected_ok")
|
||||||
get_tree().connect("connection_failed", self, "_connected_fail")
|
get_tree().connect("connection_failed", self, "_connected_fail")
|
||||||
get_tree().connect("server_disconnected", self, "_server_disconnected")
|
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)
|
||||||
|
@ -2,13 +2,13 @@ extends KinematicBody2D
|
|||||||
|
|
||||||
const MOTION_SPEED = 90.0
|
const MOTION_SPEED = 90.0
|
||||||
|
|
||||||
puppet var puppet_pos = Vector2()
|
var puppet_pos = Vector2()
|
||||||
puppet var puppet_motion = Vector2()
|
var puppet_motion = Vector2()
|
||||||
|
|
||||||
export var stunned = false
|
export var stunned = false
|
||||||
|
|
||||||
# Use sync because it will be called everywhere
|
# 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()
|
var bomb = preload("res://bomb.tscn").instance()
|
||||||
bomb.set_name(bomb_name) # Ensure unique name for the bomb
|
bomb.set_name(bomb_name) # Ensure unique name for the bomb
|
||||||
bomb.position = pos
|
bomb.position = pos
|
||||||
@ -47,8 +47,8 @@ func _physics_process(_delta):
|
|||||||
|
|
||||||
prev_bombing = bombing
|
prev_bombing = bombing
|
||||||
|
|
||||||
rset("puppet_motion", motion)
|
rpc("set_puppet_motion", motion)
|
||||||
rset("puppet_pos", position)
|
rpc("set_puppet_pos", position)
|
||||||
else:
|
else:
|
||||||
position = puppet_pos
|
position = puppet_pos
|
||||||
motion = puppet_motion
|
motion = puppet_motion
|
||||||
@ -76,11 +76,11 @@ func _physics_process(_delta):
|
|||||||
puppet_pos = position # To avoid jitter
|
puppet_pos = position # To avoid jitter
|
||||||
|
|
||||||
|
|
||||||
puppet func stun():
|
func stun():
|
||||||
stunned = true
|
stunned = true
|
||||||
|
|
||||||
|
|
||||||
master func exploded(_by_who):
|
func exploded(_by_who):
|
||||||
if stunned:
|
if stunned:
|
||||||
return
|
return
|
||||||
rpc("stun") # Stun puppets
|
rpc("stun") # Stun puppets
|
||||||
@ -91,6 +91,18 @@ func set_player_name(new_name):
|
|||||||
get_node("label").set_text(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():
|
func _ready():
|
||||||
stunned = false
|
stunned = false
|
||||||
puppet_pos = position
|
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)
|
||||||
|
@ -21,10 +21,6 @@ config/icon="res://icon.png"
|
|||||||
|
|
||||||
gamestate="*res://gamestate.gd"
|
gamestate="*res://gamestate.gd"
|
||||||
|
|
||||||
[debug]
|
|
||||||
|
|
||||||
gdscript/warnings/return_value_discarded=false
|
|
||||||
|
|
||||||
[display]
|
[display]
|
||||||
|
|
||||||
window/dpi/allow_hidpi=true
|
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)
|
, 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"
|
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
extends KinematicBody2D
|
extends KinematicBody2D
|
||||||
|
|
||||||
# Sent to everyone else
|
# Sent to everyone else
|
||||||
puppet func do_explosion():
|
func do_explosion():
|
||||||
$"AnimationPlayer".play("explode")
|
$"AnimationPlayer".play("explode")
|
||||||
|
|
||||||
|
|
||||||
# Received by owner of the rock
|
# Received by owner of the rock
|
||||||
master func exploded(by_who):
|
func exploded(by_who):
|
||||||
rpc("do_explosion") # Re-sent to puppet rocks
|
rpc("do_explosion") # Re-sent to puppet rocks
|
||||||
$"../../Score".rpc("increase_score", by_who)
|
$"../../Score".rpc("increase_score", by_who)
|
||||||
do_explosion()
|
do_explosion()
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
rpc_config("do_explosion", MultiplayerAPI.RPC_MODE_PUPPET)
|
||||||
|
rpc_config("exploded", MultiplayerAPI.RPC_MODE_MASTER)
|
||||||
|
@ -16,7 +16,7 @@ func _process(_delta):
|
|||||||
$"../Winner".show()
|
$"../Winner".show()
|
||||||
|
|
||||||
|
|
||||||
remotesync func increase_score(for_who):
|
func increase_score(for_who):
|
||||||
assert(for_who in player_labels)
|
assert(for_who in player_labels)
|
||||||
var pl = player_labels[for_who]
|
var pl = player_labels[for_who]
|
||||||
pl.score += 1
|
pl.score += 1
|
||||||
@ -31,16 +31,18 @@ func add_player(id, new_player_name):
|
|||||||
var font = DynamicFont.new()
|
var font = DynamicFont.new()
|
||||||
font.set_size(18)
|
font.set_size(18)
|
||||||
font.set_font_data(preload("res://montserrat.otf"))
|
font.set_font_data(preload("res://montserrat.otf"))
|
||||||
l.add_font_override("font", font)
|
l.add_theme_font_override("font", font)
|
||||||
add_child(l)
|
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():
|
func _ready():
|
||||||
$"../Winner".hide()
|
$"../Winner".hide()
|
||||||
set_process(true)
|
set_process(true)
|
||||||
|
|
||||||
|
rpc_config("increase_score", MultiplayerAPI.RPC_MODE_REMOTESYNC)
|
||||||
|
|
||||||
|
|
||||||
func _on_exit_game_pressed():
|
func _on_exit_game_pressed():
|
||||||
gamestate.end_game()
|
gamestate.end_game()
|
||||||
|
@ -41,7 +41,7 @@ func _process(delta):
|
|||||||
rpc("_reset_ball", true)
|
rpc("_reset_ball", true)
|
||||||
|
|
||||||
|
|
||||||
remotesync func bounce(left, random):
|
func bounce(left, random):
|
||||||
# Using sync because both players can make it bounce.
|
# Using sync because both players can make it bounce.
|
||||||
if left:
|
if left:
|
||||||
direction.x = abs(direction.x)
|
direction.x = abs(direction.x)
|
||||||
@ -53,14 +53,20 @@ remotesync func bounce(left, random):
|
|||||||
direction = direction.normalized()
|
direction = direction.normalized()
|
||||||
|
|
||||||
|
|
||||||
remotesync func stop():
|
func stop():
|
||||||
stopped = true
|
stopped = true
|
||||||
|
|
||||||
|
|
||||||
remotesync func _reset_ball(for_left):
|
func _reset_ball(for_left):
|
||||||
position = _screen_size / 2
|
position = _screen_size / 2
|
||||||
if for_left:
|
if for_left:
|
||||||
direction = Vector2.LEFT
|
direction = Vector2.LEFT
|
||||||
else:
|
else:
|
||||||
direction = Vector2.RIGHT
|
direction = Vector2.RIGHT
|
||||||
_speed = DEFAULT_SPEED
|
_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)
|
||||||
|
@ -33,7 +33,7 @@ func _process(delta):
|
|||||||
|
|
||||||
|
|
||||||
# Synchronize position and speed to the other peers.
|
# 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
|
position = pos
|
||||||
_motion = motion
|
_motion = motion
|
||||||
|
|
||||||
@ -47,3 +47,7 @@ func _on_paddle_area_enter(area):
|
|||||||
if is_network_master():
|
if is_network_master():
|
||||||
# Random for new direction generated on each peer.
|
# Random for new direction generated on each peer.
|
||||||
area.rpc("bounce", left, randf())
|
area.rpc("bounce", left, randf())
|
||||||
|
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
rpc_config("set_pos_and_motion", MultiplayerAPI.RPC_MODE_PUPPET)
|
||||||
|
@ -26,8 +26,10 @@ func _ready():
|
|||||||
|
|
||||||
print("Unique id: ", 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:
|
if add_to_left:
|
||||||
score_left += 1
|
score_left += 1
|
||||||
score_left_node.set_text(str(score_left))
|
score_left_node.set_text(str(score_left))
|
||||||
|
@ -17,10 +17,6 @@ other should select the address and press 'join'."
|
|||||||
run/main_scene="res://lobby.tscn"
|
run/main_scene="res://lobby.tscn"
|
||||||
config/icon="res://icon.png"
|
config/icon="res://icon.png"
|
||||||
|
|
||||||
[debug]
|
|
||||||
|
|
||||||
gdscript/warnings/return_value_discarded=false
|
|
||||||
|
|
||||||
[display]
|
[display]
|
||||||
|
|
||||||
window/size/width=640
|
window/size/width=640
|
||||||
@ -57,7 +53,6 @@ move_up={
|
|||||||
|
|
||||||
[rendering]
|
[rendering]
|
||||||
|
|
||||||
quality/driver/driver_name="GLES2"
|
|
||||||
2d/snapping/use_gpu_pixel_snap=true
|
2d/snapping/use_gpu_pixel_snap=true
|
||||||
vram_compression/import_etc=true
|
vram_compression/import_etc=true
|
||||||
vram_compression/import_etc2=false
|
vram_compression/import_etc2=false
|
||||||
|
1
networking/webrtc_minimal/.gitignore
vendored
1
networking/webrtc_minimal/.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
webrtc
|
|
@ -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
|
|
@ -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)
|
|
@ -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())
|
|
@ -1,4 +0,0 @@
|
|||||||
extends LinkButton
|
|
||||||
|
|
||||||
func _on_LinkButton_pressed():
|
|
||||||
OS.shell_open("https://github.com/godotengine/webrtc-native/releases")
|
|
@ -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()))
|
|
@ -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"]
|
|
@ -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())
|
|
@ -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 )
|
|
@ -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
|
|
1
networking/webrtc_signaling/.gitignore
vendored
1
networking/webrtc_signaling/.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
webrtc
|
|
@ -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)
|
|
@ -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)
|
|
@ -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()
|
|
@ -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()
|
|
@ -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"]
|
|
@ -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")
|
|
@ -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"]
|
|
@ -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 |
@ -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
|
|
@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
node_modules/
|
|
@ -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"
|
|
||||||
}
|
|
@ -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);
|
|
@ -25,6 +25,5 @@ singletons=[ ]
|
|||||||
|
|
||||||
[rendering]
|
[rendering]
|
||||||
|
|
||||||
quality/driver/driver_name="GLES2"
|
|
||||||
vram_compression/import_etc=true
|
vram_compression/import_etc=true
|
||||||
vram_compression/import_etc2=false
|
vram_compression/import_etc2=false
|
||||||
|
@ -16,6 +16,5 @@ run/main_scene="res://Main.tscn"
|
|||||||
|
|
||||||
[rendering]
|
[rendering]
|
||||||
|
|
||||||
quality/driver/driver_name="GLES2"
|
|
||||||
vram_compression/import_etc=true
|
vram_compression/import_etc=true
|
||||||
vram_compression/import_etc2=false
|
vram_compression/import_etc2=false
|
||||||
|
@ -17,6 +17,5 @@ config/icon="res://icon.png"
|
|||||||
|
|
||||||
[rendering]
|
[rendering]
|
||||||
|
|
||||||
quality/driver/driver_name="GLES2"
|
|
||||||
vram_compression/import_etc=true
|
vram_compression/import_etc=true
|
||||||
vram_compression/import_etc2=false
|
vram_compression/import_etc2=false
|
||||||
|
@ -8,18 +8,18 @@ onready var _action = $HBoxContainer/VBoxContainer/Action
|
|||||||
var _players = []
|
var _players = []
|
||||||
var _turn = -1
|
var _turn = -1
|
||||||
|
|
||||||
master func set_player_name(name):
|
func set_player_name(name):
|
||||||
var sender = get_tree().get_rpc_sender_id()
|
var sender = get_tree().get_rpc_sender_id()
|
||||||
rpc("update_player_name", sender, name)
|
rpc("update_player_name", sender, name)
|
||||||
|
|
||||||
|
|
||||||
remotesync func update_player_name(player, name):
|
func update_player_name(player, name):
|
||||||
var pos = _players.find(player)
|
var pos = _players.find(player)
|
||||||
if pos != -1:
|
if pos != -1:
|
||||||
_list.set_item_text(pos, name)
|
_list.set_item_text(pos, name)
|
||||||
|
|
||||||
|
|
||||||
master func request_action(action):
|
func request_action(action):
|
||||||
var sender = get_tree().get_rpc_sender_id()
|
var sender = get_tree().get_rpc_sender_id()
|
||||||
if _players[_turn] != 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))
|
rpc("_log", "Someone is trying to cheat! %s" % str(sender))
|
||||||
@ -28,13 +28,13 @@ master func request_action(action):
|
|||||||
next_turn()
|
next_turn()
|
||||||
|
|
||||||
|
|
||||||
remotesync func do_action(action):
|
func do_action(action):
|
||||||
var name = _list.get_item_text(_turn)
|
var name = _list.get_item_text(_turn)
|
||||||
var val = randi() % 100
|
var val = randi() % 100
|
||||||
rpc("_log", "%s: %ss %d" % [name, action, val])
|
rpc("_log", "%s: %ss %d" % [name, action, val])
|
||||||
|
|
||||||
|
|
||||||
remotesync func set_turn(turn):
|
func set_turn(turn):
|
||||||
_turn = turn
|
_turn = turn
|
||||||
if turn >= _players.size():
|
if turn >= _players.size():
|
||||||
return
|
return
|
||||||
@ -46,7 +46,7 @@ remotesync func set_turn(turn):
|
|||||||
_action.disabled = _players[turn] != get_tree().get_network_unique_id()
|
_action.disabled = _players[turn] != get_tree().get_network_unique_id()
|
||||||
|
|
||||||
|
|
||||||
remotesync func del_player(id):
|
func del_player(id):
|
||||||
var pos = _players.find(id)
|
var pos = _players.find(id)
|
||||||
if pos == -1:
|
if pos == -1:
|
||||||
return
|
return
|
||||||
@ -58,7 +58,7 @@ remotesync func del_player(id):
|
|||||||
rpc("set_turn", _turn)
|
rpc("set_turn", _turn)
|
||||||
|
|
||||||
|
|
||||||
remotesync func add_player(id, name=""):
|
func add_player(id, name=""):
|
||||||
_players.append(id)
|
_players.append(id)
|
||||||
if name == "":
|
if name == "":
|
||||||
_list.add_item("... connecting ...", null, false)
|
_list.add_item("... connecting ...", null, false)
|
||||||
@ -106,7 +106,7 @@ func on_peer_del(id):
|
|||||||
rpc("del_player", id)
|
rpc("del_player", id)
|
||||||
|
|
||||||
|
|
||||||
remotesync func _log(what):
|
func _log(what):
|
||||||
$HBoxContainer/RichTextLabel.add_text(what + "\n")
|
$HBoxContainer/RichTextLabel.add_text(what + "\n")
|
||||||
|
|
||||||
|
|
||||||
@ -116,3 +116,15 @@ func _on_Action_pressed():
|
|||||||
next_turn()
|
next_turn()
|
||||||
else:
|
else:
|
||||||
rpc_id(1, "request_action", "roll")
|
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)
|
||||||
|
Loading…
Reference in New Issue
Block a user