diff --git a/networking/websocket_multiplayer/.gitignore b/networking/websocket_multiplayer/.gitignore new file mode 100644 index 00000000..76c28d20 --- /dev/null +++ b/networking/websocket_multiplayer/.gitignore @@ -0,0 +1,3 @@ +.import/* +*.import +export_presets.cfg diff --git a/networking/websocket_multiplayer/default_env.tres b/networking/websocket_multiplayer/default_env.tres new file mode 100644 index 00000000..b9063186 --- /dev/null +++ b/networking/websocket_multiplayer/default_env.tres @@ -0,0 +1,20 @@ +[gd_resource type="Environment" load_steps=2 format=2] + +[sub_resource type="ProceduralSky" id=1] +sky_top_color = Color( 0.0470588, 0.454902, 0.976471, 1 ) +sky_horizon_color = Color( 0.556863, 0.823529, 0.909804, 1 ) +sky_curve = 0.25 +ground_bottom_color = Color( 0.101961, 0.145098, 0.188235, 1 ) +ground_horizon_color = Color( 0.482353, 0.788235, 0.952941, 1 ) +ground_curve = 0.01 +sun_energy = 16.0 +__meta__ = { + +} + +[resource] +background_mode = 2 +background_sky = SubResource( 1 ) +__meta__ = { + +} diff --git a/networking/websocket_multiplayer/icon.png b/networking/websocket_multiplayer/icon.png new file mode 100644 index 00000000..e1acbaaf Binary files /dev/null and b/networking/websocket_multiplayer/icon.png differ diff --git a/networking/websocket_multiplayer/img/crown.png b/networking/websocket_multiplayer/img/crown.png new file mode 100644 index 00000000..96ad44f2 Binary files /dev/null and b/networking/websocket_multiplayer/img/crown.png differ diff --git a/networking/websocket_multiplayer/project.godot b/networking/websocket_multiplayer/project.godot new file mode 100644 index 00000000..e3900027 --- /dev/null +++ b/networking/websocket_multiplayer/project.godot @@ -0,0 +1,24 @@ +; 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 + +_global_script_classes=[ ] +_global_script_class_icons={ + +} + +[application] + +config/name="Websocket Multiplayer Demo" +run/main_scene="res://scene/main.tscn" +config/icon="res://icon.png" + +[rendering] + +environment/default_environment="res://default_env.tres" diff --git a/networking/websocket_multiplayer/scene/game.tscn b/networking/websocket_multiplayer/scene/game.tscn new file mode 100644 index 00000000..7fddf3cc --- /dev/null +++ b/networking/websocket_multiplayer/scene/game.tscn @@ -0,0 +1,68 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://script/game.gd" type="Script" id=1] + +[node name="Game" type="Control"] +anchor_right = 1.0 +anchor_bottom = 1.0 +mouse_filter = 1 +size_flags_horizontal = 3 +size_flags_vertical = 3 +script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="HBoxContainer" type="HBoxContainer" parent="."] +anchor_right = 1.0 +anchor_bottom = 1.0 +__meta__ = { + +} + +[node name="RichTextLabel" type="RichTextLabel" parent="HBoxContainer"] +margin_right = 510.0 +margin_bottom = 600.0 +size_flags_horizontal = 3 +__meta__ = { + +} + +[node name="VBoxContainer" type="VBoxContainer" parent="HBoxContainer"] +margin_left = 514.0 +margin_right = 1024.0 +margin_bottom = 600.0 +size_flags_horizontal = 3 +__meta__ = { + +} + +[node name="Label" type="Label" parent="HBoxContainer/VBoxContainer"] +margin_right = 510.0 +margin_bottom = 14.0 +text = "Players:" +__meta__ = { + +} + +[node name="ItemList" type="ItemList" parent="HBoxContainer/VBoxContainer"] +margin_top = 18.0 +margin_right = 510.0 +margin_bottom = 576.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +same_column_width = true +__meta__ = { + +} + +[node name="Action" type="Button" parent="HBoxContainer/VBoxContainer"] +margin_top = 580.0 +margin_right = 510.0 +margin_bottom = 600.0 +disabled = true +text = "Do Action!" +__meta__ = { + +} +[connection signal="pressed" from="HBoxContainer/VBoxContainer/Action" to="." method="_on_Action_pressed"] diff --git a/networking/websocket_multiplayer/scene/main.tscn b/networking/websocket_multiplayer/scene/main.tscn new file mode 100644 index 00000000..c307df58 --- /dev/null +++ b/networking/websocket_multiplayer/scene/main.tscn @@ -0,0 +1,155 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://script/main.gd" type="Script" id=1] +[ext_resource path="res://scene/game.tscn" type="PackedScene" id=2] + +[node name="Control" type="Control"] +anchor_right = 1.0 +anchor_bottom = 1.0 +script = ExtResource( 1 ) +__meta__ = { + +} + +[node name="Panel" type="Panel" parent="."] +anchor_right = 1.0 +anchor_bottom = 1.0 +__meta__ = { + +} + +[node name="VBoxContainer" type="VBoxContainer" parent="Panel"] +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_left = 20.0 +margin_top = 20.0 +margin_right = -20.0 +margin_bottom = -20.0 +__meta__ = { + +} + +[node name="HBoxContainer" type="HBoxContainer" parent="Panel/VBoxContainer"] +margin_right = 984.0 +margin_bottom = 24.0 +__meta__ = { + +} + +[node name="Label" type="Label" parent="Panel/VBoxContainer/HBoxContainer"] +margin_top = 5.0 +margin_right = 326.0 +margin_bottom = 19.0 +size_flags_horizontal = 3 +text = "Name" +__meta__ = { + +} + +[node name="NameEdit" type="LineEdit" parent="Panel/VBoxContainer/HBoxContainer"] +margin_left = 330.0 +margin_right = 984.0 +margin_bottom = 24.0 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "A Godot User" +__meta__ = { + +} + +[node name="HBoxContainer2" type="HBoxContainer" parent="Panel/VBoxContainer"] +margin_top = 28.0 +margin_right = 984.0 +margin_bottom = 52.0 +__meta__ = { + +} + +[node name="HBoxContainer" type="HBoxContainer" parent="Panel/VBoxContainer/HBoxContainer2"] +margin_right = 326.0 +margin_bottom = 24.0 +size_flags_horizontal = 3 +__meta__ = { + +} + +[node name="Host" type="Button" parent="Panel/VBoxContainer/HBoxContainer2/HBoxContainer"] +margin_right = 42.0 +margin_bottom = 24.0 +text = "Host" +__meta__ = { + +} + +[node name="Control" type="Control" parent="Panel/VBoxContainer/HBoxContainer2/HBoxContainer"] +margin_left = 46.0 +margin_right = 241.0 +margin_bottom = 24.0 +size_flags_horizontal = 3 +__meta__ = { + +} + +[node name="Connect" type="Button" parent="Panel/VBoxContainer/HBoxContainer2/HBoxContainer"] +margin_left = 245.0 +margin_right = 326.0 +margin_bottom = 24.0 +text = "Connect to" +__meta__ = { + +} + +[node name="Disconnect" type="Button" parent="Panel/VBoxContainer/HBoxContainer2/HBoxContainer"] +visible = false +margin_left = 68.0 +margin_right = 152.0 +margin_bottom = 24.0 +text = "Disconnect" +__meta__ = { + +} + +[node name="Hostname" type="LineEdit" parent="Panel/VBoxContainer/HBoxContainer2"] +margin_left = 330.0 +margin_right = 984.0 +margin_bottom = 24.0 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "localhost" +placeholder_text = "localhost" +__meta__ = { + +} + +[node name="Control" type="Control" parent="Panel/VBoxContainer"] +margin_top = 56.0 +margin_right = 984.0 +margin_bottom = 76.0 +rect_min_size = Vector2( 0, 20 ) +__meta__ = { + +} + +[node name="Game" parent="Panel/VBoxContainer" instance=ExtResource( 2 )] +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_top = 80.0 +margin_right = 984.0 +margin_bottom = 560.0 + +[node name="AcceptDialog" type="AcceptDialog" parent="."] +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +margin_left = -200.0 +margin_top = -100.0 +margin_right = 200.0 +margin_bottom = 100.0 +dialog_text = "Connection closed" +__meta__ = { + +} +[connection signal="pressed" from="Panel/VBoxContainer/HBoxContainer2/HBoxContainer/Host" to="." method="_on_Host_pressed"] +[connection signal="pressed" from="Panel/VBoxContainer/HBoxContainer2/HBoxContainer/Connect" to="." method="_on_Connect_pressed"] +[connection signal="pressed" from="Panel/VBoxContainer/HBoxContainer2/HBoxContainer/Disconnect" to="." method="_on_Disconnect_pressed"] diff --git a/networking/websocket_multiplayer/script/game.gd b/networking/websocket_multiplayer/script/game.gd new file mode 100644 index 00000000..512efc03 --- /dev/null +++ b/networking/websocket_multiplayer/script/game.gd @@ -0,0 +1,104 @@ +extends Control + +const _crown = preload("res://img/crown.png") + +onready var _list = $HBoxContainer/VBoxContainer/ItemList +onready var _action = $HBoxContainer/VBoxContainer/Action + +var _players = [] +var _turn = -1 + +master func set_player_name(name): + var sender = get_tree().get_rpc_sender_id() + rpc("update_player_name", sender, name) + +sync 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): + 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)) + return + do_action(action) + next_turn() + +sync func do_action(action): + var name = _list.get_item_text(_turn) + var val = randi() % 100 + rpc("_log", "%s: %ss %d" % [name, action, val]) + +sync func set_turn(turn): + _turn = turn + if turn >= _players.size(): + return + for i in range(0, _players.size()): + if i == turn: + _list.set_item_icon(i, _crown) + else: + _list.set_item_icon(i, null) + _action.disabled = _players[turn] != get_tree().get_network_unique_id() + +sync func del_player(id): + var pos = _players.find(id) + if pos == -1: + return + _players.remove(pos) + _list.remove_item(pos) + if _turn > pos: + _turn -= 1 + if get_tree().is_network_server(): + rpc("set_turn", _turn) + +sync func add_player(id, name=""): + _players.append(id) + if name == "": + _list.add_item("... connecting ...", null, false) + else: + _list.add_item(name, null, false) + +func get_player_name(pos): + if pos < _list.get_item_count(): + return _list.get_item_text(pos) + else: + return "Error!" + +func next_turn(): + _turn += 1 + if _turn >= _players.size(): + _turn = 0 + rpc("set_turn", _turn) + +func start(): + set_turn(0) + +func stop(): + _players.clear() + _list.clear() + _turn = 0 + _action.disabled = true + +func on_peer_add(id): + if not get_tree().is_network_server(): + return + for i in range(0, _players.size()): + rpc_id(id, "add_player", _players[i], get_player_name(i)) + rpc("add_player", id) + rpc_id(id, "set_turn", _turn) + +func on_peer_del(id): + if not get_tree().is_network_server(): + return + rpc("del_player", id) + +sync func _log(what): + $HBoxContainer/RichTextLabel.add_text(what + "\n") + +func _on_Action_pressed(): + if get_tree().is_network_server(): + do_action("roll") + next_turn() + else: + rpc_id(1, "request_action", "roll") diff --git a/networking/websocket_multiplayer/script/main.gd b/networking/websocket_multiplayer/script/main.gd new file mode 100644 index 00000000..1dcb0423 --- /dev/null +++ b/networking/websocket_multiplayer/script/main.gd @@ -0,0 +1,73 @@ +extends Control + +const DEF_PORT = 8080 +const PROTO_NAME = "ludus" + +onready var _host_btn = $Panel/VBoxContainer/HBoxContainer2/HBoxContainer/Host +onready var _connect_btn = $Panel/VBoxContainer/HBoxContainer2/HBoxContainer/Connect +onready var _disconnect_btn = $Panel/VBoxContainer/HBoxContainer2/HBoxContainer/Disconnect +onready var _name_edit = $Panel/VBoxContainer/HBoxContainer/NameEdit +onready var _host_edit = $Panel/VBoxContainer/HBoxContainer2/Hostname +onready var _game = $Panel/VBoxContainer/Game + +func _ready(): + get_tree().connect("network_peer_disconnected", self, "_peer_disconnected") + get_tree().connect("network_peer_connected", self, "_peer_connected") + $AcceptDialog.get_label().align = Label.ALIGN_CENTER + $AcceptDialog.get_label().valign = Label.VALIGN_CENTER + +func start_game(): + _host_btn.disabled = true + _name_edit.editable = false + _host_edit.editable = false + _connect_btn.hide() + _disconnect_btn.show() + _game.start() + +func stop_game(): + _host_btn.disabled = false + _name_edit.editable = true + _host_edit.editable = true + _disconnect_btn.hide() + _connect_btn.show() + _game.stop() + +func _close_network(): + if get_tree().is_connected("server_disconnected", self, "_close_network"): + get_tree().disconnect("server_disconnected", self, "_close_network") + if get_tree().is_connected("connection_failed", self, "_close_network"): + get_tree().disconnect("connection_failed", self, "_close_network") + if get_tree().is_connected("connected_to_server", self, "_connected"): + get_tree().disconnect("connected_to_server", self, "_connected") + stop_game() + $AcceptDialog.show_modal() + $AcceptDialog.get_close_button().grab_focus() + get_tree().set_network_peer(null) + +func _connected(): + _game.rpc("set_player_name", _name_edit.text) + +func _peer_connected(id): + _game.on_peer_add(id) + +func _peer_disconnected(id): + _game.on_peer_del(id) + +func _on_Host_pressed(): + var host = WebSocketServer.new() + host.listen(DEF_PORT, PoolStringArray(["ludus"]), true) + get_tree().connect("server_disconnected", self, "_close_network") + get_tree().set_network_peer(host) + _game.add_player(1, _name_edit.text) + start_game() + +func _on_Disconnect_pressed(): + _close_network() + +func _on_Connect_pressed(): + var host = WebSocketClient.new() + host.connect_to_url("ws://" + _host_edit.text + ":" + str(DEF_PORT), PoolStringArray([PROTO_NAME]), true) + get_tree().connect("connection_failed", self, "_close_network") + get_tree().connect("connected_to_server", self, "_connected") + get_tree().set_network_peer(host) + start_game()