diff --git a/Cargo.toml b/Cargo.toml index a7892f8..fcfeae7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ resolver = "2" members = [ "dodge-the-creeps/rust", "hot-reload/rust", + "net-pong/rust", ] # Note about Jetbrains IDEs: "IDE Sync" (Refresh Cargo projects) may cause static analysis errors such as diff --git a/net-pong/LICENSE b/net-pong/LICENSE new file mode 100644 index 0000000..ee9dd26 --- /dev/null +++ b/net-pong/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Asdrome + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/net-pong/README.md b/net-pong/README.md new file mode 100644 index 0000000..ce5acfd --- /dev/null +++ b/net-pong/README.md @@ -0,0 +1,24 @@ +# Godot Rust Multiplayer Pong +based on https://godotengine.org/asset-library/asset/2798 + +## License + +This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for more details. + +The godot-rust Ferris icon was obtained from [their repository](https://github.com/godot-rust/assets) and its licence's details are explained [here](https://github.com/godot-rust/assets/blob/master/asset-licenses.md). + +Shield: [![CC BY 4.0][cc-by-shield]][cc-by] + +This work is licensed under a +[Creative Commons Attribution 4.0 International License][cc-by]. + +[![CC BY 4.0][cc-by-image]][cc-by] + +[cc-by]: http://creativecommons.org/licenses/by/4.0/ +[cc-by-image]: https://i.creativecommons.org/l/by/4.0/88x31.png +[cc-by-shield]: https://img.shields.io/badge/License-CC%20BY%204.0-lightgrey.svg + +## Acknowledgments + +- [Godot Engine](https://godotengine.org/) +- [Godot Rust](https://github.com/godot-rust/gdext) for their fantastic work on integrating Rust with Godot. diff --git a/net-pong/godot/.gitignore b/net-pong/godot/.gitignore new file mode 100644 index 0000000..0af181c --- /dev/null +++ b/net-pong/godot/.gitignore @@ -0,0 +1,3 @@ +# Godot 4+ specific ignores +.godot/ +/android/ diff --git a/net-pong/godot/README.md b/net-pong/godot/README.md new file mode 100644 index 0000000..b3731eb --- /dev/null +++ b/net-pong/godot/README.md @@ -0,0 +1,17 @@ +# Pong Multiplayer + +A multiplayer implementation of the classic pong game. +One of the players should press **Host**, while the other +should type in the host's IP address and press **Join**. + +Language: GDScript + +Renderer: Compatibility + +Note: The non-multiplayer version is available [here](https://github.com/godotengine/godot-demo-projects/tree/master/2d/pong). + +Check out this demo on the asset library: https://godotengine.org/asset-library/asset/2798 + +## Screenshots + +![Screenshot](screenshots/pong_multiplayer.png) diff --git a/net-pong/godot/ball.png b/net-pong/godot/ball.png new file mode 100644 index 0000000..465d352 Binary files /dev/null and b/net-pong/godot/ball.png differ diff --git a/net-pong/godot/ball.tscn b/net-pong/godot/ball.tscn new file mode 100644 index 0000000..5c41cd4 --- /dev/null +++ b/net-pong/godot/ball.tscn @@ -0,0 +1,14 @@ +[gd_scene load_steps=3 format=3 uid="uid://bjmldn1x3lpa"] + +[ext_resource type="Texture2D" uid="uid://i1imfdcn7ui" path="res://ball.png" id="2"] + +[sub_resource type="CircleShape2D" id="1"] +radius = 5.11969 + +[node name="Ball" type="Ball"] + +[node name="Sprite2D" type="Sprite2D" parent="."] +texture = ExtResource("2") + +[node name="Shape3D" type="CollisionShape2D" parent="."] +shape = SubResource("1") diff --git a/net-pong/godot/godot-rust.png b/net-pong/godot/godot-rust.png new file mode 100644 index 0000000..d1f5c14 Binary files /dev/null and b/net-pong/godot/godot-rust.png differ diff --git a/net-pong/godot/icon.webp b/net-pong/godot/icon.webp new file mode 100644 index 0000000..00cb813 Binary files /dev/null and b/net-pong/godot/icon.webp differ diff --git a/net-pong/godot/lobby.tscn b/net-pong/godot/lobby.tscn new file mode 100644 index 0000000..e962147 --- /dev/null +++ b/net-pong/godot/lobby.tscn @@ -0,0 +1,159 @@ +[gd_scene format=3 uid="uid://f85s2avde6r4"] + +[node name="Lobby" type="Control"] +layout_mode = 3 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -320.0 +offset_top = -200.0 +offset_right = 320.0 +offset_bottom = 200.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 2 +size_flags_vertical = 2 + +[node name="Title" type="Label" parent="."] +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -110.0 +offset_top = -156.0 +offset_right = 110.0 +offset_bottom = -116.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 2 +size_flags_vertical = 0 +theme_override_font_sizes/font_size = 32 +text = "Multiplayer Pong" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="LobbyPanel" type="Lobby" parent="." node_paths=PackedStringArray("address", "host_button", "join_button", "status_ok", "status_fail", "port_forward_label", "find_public_ip_button")] +address = NodePath("Address") +host_button = NodePath("HostButton") +join_button = NodePath("JoinButton") +status_ok = NodePath("StatusOk") +status_fail = NodePath("StatusFail") +port_forward_label = NodePath("PortForward") +find_public_ip_button = NodePath("FindPublicIP") +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -110.0 +offset_top = -73.0 +offset_right = 110.0 +offset_bottom = 73.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 2 +size_flags_vertical = 2 + +[node name="AddressLabel" type="Label" parent="LobbyPanel"] +layout_mode = 0 +offset_left = 10.0 +offset_top = 6.0 +offset_right = 77.0 +offset_bottom = 29.0 +size_flags_horizontal = 2 +size_flags_vertical = 0 +text = "Address:" + +[node name="Address" type="LineEdit" parent="LobbyPanel"] +layout_mode = 0 +offset_left = 10.0 +offset_top = 37.0 +offset_right = 210.0 +offset_bottom = 68.0 +size_flags_horizontal = 2 +size_flags_vertical = 2 +text = "127.0.0.1" + +[node name="HostButton" type="Button" parent="LobbyPanel"] +layout_mode = 0 +offset_left = 10.0 +offset_top = 76.0 +offset_right = 90.0 +offset_bottom = 107.0 +size_flags_horizontal = 2 +size_flags_vertical = 2 +text = "Host" + +[node name="JoinButton" type="Button" parent="LobbyPanel"] +layout_mode = 0 +offset_left = 130.0 +offset_top = 76.0 +offset_right = 210.0 +offset_bottom = 107.0 +size_flags_horizontal = 2 +size_flags_vertical = 2 +text = "Join" + +[node name="StatusOk" type="Label" parent="LobbyPanel"] +layout_mode = 0 +offset_left = 10.0 +offset_top = 114.0 +offset_right = 210.0 +offset_bottom = 137.0 +size_flags_horizontal = 2 +size_flags_vertical = 0 +horizontal_alignment = 1 + +[node name="StatusFail" type="Label" parent="LobbyPanel"] +modulate = Color(1, 0.427451, 0.345098, 1) +layout_mode = 0 +offset_left = 10.0 +offset_top = 114.0 +offset_right = 210.0 +offset_bottom = 137.0 +size_flags_horizontal = 2 +size_flags_vertical = 0 +horizontal_alignment = 1 + +[node name="PortForward" type="Label" parent="LobbyPanel"] +visible = false +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -278.0 +offset_top = 91.0 +offset_right = 25.0 +offset_bottom = 166.0 +grow_horizontal = 2 +grow_vertical = 2 +text = "If you want non-LAN clients to connect, +make sure the port 8910 in UDP +is forwarded on your router." + +[node name="FindPublicIP" type="LinkButton" parent="LobbyPanel"] +visible = false +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = 61.0 +offset_top = 118.0 +offset_right = 269.0 +offset_bottom = 141.0 +grow_horizontal = 2 +grow_vertical = 2 +text = "Find your public IP address" + +[connection signal="pressed" from="LobbyPanel/HostButton" to="LobbyPanel" method="_on_host_pressed"] +[connection signal="pressed" from="LobbyPanel/JoinButton" to="LobbyPanel" method="_on_join_pressed"] +[connection signal="pressed" from="LobbyPanel/FindPublicIP" to="LobbyPanel" method="_on_find_public_ip_pressed"] diff --git a/net-pong/godot/paddle.png b/net-pong/godot/paddle.png new file mode 100644 index 0000000..1860b07 Binary files /dev/null and b/net-pong/godot/paddle.png differ diff --git a/net-pong/godot/paddle.tscn b/net-pong/godot/paddle.tscn new file mode 100644 index 0000000..2896f2f --- /dev/null +++ b/net-pong/godot/paddle.tscn @@ -0,0 +1,27 @@ +[gd_scene load_steps=3 format=3 uid="uid://cpw46256eirwq"] + +[ext_resource type="Texture2D" uid="uid://bjw2yb853klh2" path="res://paddle.png" id="2"] + +[sub_resource type="CapsuleShape2D" id="1"] +radius = 4.78568 +height = 23.6064 + +[node name="Paddle" type="Paddle" node_paths=PackedStringArray("you_label")] +you_label = NodePath("You") + +[node name="Sprite2D" type="Sprite2D" parent="."] +texture = ExtResource("2") + +[node name="Shape3D" type="CollisionShape2D" parent="."] +shape = SubResource("1") + +[node name="You" type="Label" parent="."] +offset_left = -26.0 +offset_top = -33.0 +offset_right = 27.0 +offset_bottom = -19.0 +size_flags_horizontal = 2 +size_flags_vertical = 0 +text = "You" + +[connection signal="area_entered" from="." to="." method="_on_paddle_area_enter"] diff --git a/net-pong/godot/pong.tscn b/net-pong/godot/pong.tscn new file mode 100644 index 0000000..a519c7e --- /dev/null +++ b/net-pong/godot/pong.tscn @@ -0,0 +1,92 @@ +[gd_scene load_steps=4 format=3 uid="uid://bafoh1ief0147"] + +[ext_resource type="Texture2D" uid="uid://b10swafhe08oj" path="res://separator.png" id="2"] +[ext_resource type="PackedScene" uid="uid://cpw46256eirwq" path="res://paddle.tscn" id="3"] +[ext_resource type="PackedScene" uid="uid://bjmldn1x3lpa" path="res://ball.tscn" id="4"] + +[node name="Pong" type="Pong" node_paths=PackedStringArray("player1", "player2", "score_left_node", "score_right_node", "winner_left", "winner_right", "exit_game", "ball")] +player1 = NodePath("Player1") +player2 = NodePath("Player2") +score_left_node = NodePath("ScoreLeft") +score_right_node = NodePath("ScoreRight") +winner_left = NodePath("WinnerLeft") +winner_right = NodePath("WinnerRight") +exit_game = NodePath("ExitGame") +ball = NodePath("Ball") + +[node name="ColorRect" type="ColorRect" parent="."] +offset_right = 640.0 +offset_bottom = 400.0 +grow_horizontal = 2 +grow_vertical = 2 +color = Color(0.141176, 0.152941, 0.164706, 1) + +[node name="Separator" type="Sprite2D" parent="."] +position = Vector2(320, 200) +texture = ExtResource("2") + +[node name="Player1" parent="." instance=ExtResource("3")] +left = true +modulate = Color(0, 1, 1, 1) +position = Vector2(32.49, 188.622) + +[node name="Player2" parent="." instance=ExtResource("3")] +modulate = Color(1, 0, 1, 1) +position = Vector2(608.88, 188.622) + +[node name="Ball" parent="." instance=ExtResource("4")] +position = Vector2(320.387, 189.525) + +[node name="ScoreLeft" type="Label" parent="."] +offset_left = 240.0 +offset_top = 10.0 +offset_right = 280.0 +offset_bottom = 30.0 +size_flags_horizontal = 2 +size_flags_vertical = 0 +text = "0" + +[node name="ScoreRight" type="Label" parent="."] +offset_left = 360.0 +offset_top = 10.0 +offset_right = 400.0 +offset_bottom = 30.0 +size_flags_horizontal = 2 +size_flags_vertical = 0 +text = "0" + +[node name="WinnerLeft" type="Label" parent="."] +visible = false +offset_left = 190.0 +offset_top = 170.0 +offset_right = 267.0 +offset_bottom = 184.0 +size_flags_horizontal = 2 +size_flags_vertical = 0 +text = "The Winner!" + +[node name="WinnerRight" type="Label" parent="."] +visible = false +offset_left = 380.0 +offset_top = 170.0 +offset_right = 457.0 +offset_bottom = 184.0 +size_flags_horizontal = 2 +size_flags_vertical = 0 +text = "The Winner!" + +[node name="ExitGame" type="Button" parent="."] +visible = false +offset_left = 280.0 +offset_top = 340.0 +offset_right = 360.0 +offset_bottom = 360.0 +size_flags_horizontal = 2 +size_flags_vertical = 2 +text = "Exit Game" + +[node name="Camera2D" type="Camera2D" parent="."] +offset = Vector2(320, 200) + +[editable path="Player1"] +[editable path="Player2"] diff --git a/net-pong/godot/project.godot b/net-pong/godot/project.godot new file mode 100644 index 0000000..eb05f14 --- /dev/null +++ b/net-pong/godot/project.godot @@ -0,0 +1,42 @@ +; 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=5 + +[application] + +config/name="Rust Template" +run/main_scene="uid://f85s2avde6r4" +config/features=PackedStringArray("4.4", "GL Compatibility") +config/icon="uid://dcvpakna8i04d" + +[display] + +window/size/viewport_width=640 +window/size/viewport_height=400 +window/stretch/mode="canvas_items" + +[input] + +move_up={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null) +, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194320,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} +move_down={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null) +, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194322,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} + +[rendering] + +renderer/rendering_method="gl_compatibility" +renderer/rendering_method.mobile="gl_compatibility" diff --git a/net-pong/godot/rust.gdextension b/net-pong/godot/rust.gdextension new file mode 100644 index 0000000..1d985ae --- /dev/null +++ b/net-pong/godot/rust.gdextension @@ -0,0 +1,14 @@ +[configuration] +entry_symbol = "gdext_rust_init" +compatibility_minimum = 4.4 +reloadable = true + +[libraries] +linux.debug.x86_64 = "res://../rust/target/debug/librust.so" +linux.release.x86_64 = "res://../rust/target/release/librust.so" +windows.debug.x86_64 = "res://../rust/target/debug/rust.dll" +windows.release.x86_64 = "res://../rust/target/release/rust.dll" +macos.debug = "res://../rust/target/debug/librust.dylib" +macos.release = "res://../rust/target/release/librust.dylib" +macos.debug.arm64 = "res://../rust/target/debug/librust.dylib" +macos.release.arm64 = "res://../rust/target/release/librust.dylib" diff --git a/net-pong/godot/rust.gdextension.uid b/net-pong/godot/rust.gdextension.uid new file mode 100644 index 0000000..83211fc --- /dev/null +++ b/net-pong/godot/rust.gdextension.uid @@ -0,0 +1 @@ +uid://dsxaqyvttsnfo diff --git a/net-pong/godot/screenshots/.gdignore b/net-pong/godot/screenshots/.gdignore new file mode 100644 index 0000000..e69de29 diff --git a/net-pong/godot/screenshots/pong_multiplayer.png b/net-pong/godot/screenshots/pong_multiplayer.png new file mode 100644 index 0000000..67dcf9c Binary files /dev/null and b/net-pong/godot/screenshots/pong_multiplayer.png differ diff --git a/net-pong/godot/separator.png b/net-pong/godot/separator.png new file mode 100644 index 0000000..30c44fc Binary files /dev/null and b/net-pong/godot/separator.png differ diff --git a/net-pong/rust/.gitignore b/net-pong/rust/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/net-pong/rust/.gitignore @@ -0,0 +1 @@ +/target diff --git a/net-pong/rust/Cargo.toml b/net-pong/rust/Cargo.toml new file mode 100644 index 0000000..d182331 --- /dev/null +++ b/net-pong/rust/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "rust" +version = "1.0.0" +edition = "2024" +rust-version = "1.88" +license = "MPL-2.0" +publish = false + +[dependencies] +godot = {git = "https://github.com/godot-rust/gdext.git", rev = "18d8a2200cb0bb85b587336ba853db3bb54c9ffa"} + +[lib] +crate-type = ["cdylib"] # Compile this crate to a dynamic C library. diff --git a/net-pong/rust/src/ball.rs b/net-pong/rust/src/ball.rs new file mode 100644 index 0000000..087307c --- /dev/null +++ b/net-pong/rust/src/ball.rs @@ -0,0 +1,113 @@ +use godot::classes::Area2D; +use godot::prelude::*; + +const DEFAULT_SPEED: f64 = 100.0; + +#[derive(GodotClass)] +#[class(base=Area2D)] +pub struct Ball { + direction: Vector2, + stopped: bool, + _speed: f64, + base: Base, +} + +use godot::classes::IArea2D; + +use crate::pong::Pong; + +#[godot_api] +impl IArea2D for Ball { + fn init(base: Base) -> Self { + godot_print!("Hello, world!"); // Prints to the Godot console + + Self { + direction: Vector2::LEFT, + stopped: false, + _speed: DEFAULT_SPEED, + base, + } + } + + fn ready(&mut self) {} + + fn process(&mut self, delta: f64) { + let screen_size = self.base().get_viewport_rect().size; + self._speed += delta; + + if !self.stopped { + // Ball will move normally for both players, + // even if it's sightly out of sync between them, + // so each player sees the motion as smooth and not jerky. + let direction = self.direction; + let translation = direction * (self._speed * delta) as f32; + self.base_mut().translate(translation); + } + + // Check screen bounds to make ball bounce. + let ball_pos = self.base().get_position(); + if (ball_pos.y < 0.0 && self.direction.y < 0.0) + || (ball_pos.y > screen_size.y && self.direction.y > 0.0) + { + self.direction.y = -self.direction.y; + } + + let mut parent = self.base().get_parent().unwrap().cast::(); + if self.base().is_multiplayer_authority() { + // Only the master will decide when the ball is out in + // the left side (its own side). This makes the game + // playable even if latency is high and ball is going + // fast. Otherwise, the ball might be out in the other + // player's screen but not this one. + if ball_pos.x < 0.0 { + let args = vslice![false]; + parent.rpc("update_score", args); + self.base_mut().rpc("reset_ball", args); + } + } else { + // Only the puppet will decide when the ball is out in + // the right side, which is its own side. This makes + // the game playable even if latency is high and ball + // is going fast. Otherwise, the ball might be out in the + // other player's screen but not this one. + if ball_pos.x > screen_size.x { + let args = vslice![true]; + parent.rpc("update_score", args); + self.base_mut().rpc("reset_ball", args); + } + } + } +} + +#[godot_api] +impl Ball { + #[rpc(any_peer, call_local)] + fn bounce(&mut self, is_left: bool, random: f32) { + // Using sync because both players can make it bounce. + if is_left { + self.direction.x = self.direction.x.abs(); + } else { + self.direction.x = -self.direction.x.abs(); + } + self._speed *= 1.1; + self.direction.y = random * 2.0 - 1.0; + self.direction = self.direction.normalized(); + } + + #[rpc(any_peer, call_local)] + fn stop(&mut self) { + self.stopped = true; + } + + #[rpc(any_peer, call_local)] + fn reset_ball(&mut self, for_left: bool) { + let screen_center = self.base().get_viewport_rect().size / 2.0; + self.base_mut().set_position(screen_center); + if for_left { + self.direction = Vector2::LEFT; + } else { + self.direction = Vector2::RIGHT; + } + self._speed = DEFAULT_SPEED; + } +} diff --git a/net-pong/rust/src/lib.rs b/net-pong/rust/src/lib.rs new file mode 100644 index 0000000..d0ee06f --- /dev/null +++ b/net-pong/rust/src/lib.rs @@ -0,0 +1,11 @@ +use godot::prelude::*; + +struct RustExtension; + +#[gdextension] +unsafe impl ExtensionLibrary for RustExtension {} + +mod ball; +mod lobby; +mod paddle; +mod pong; diff --git a/net-pong/rust/src/lobby.rs b/net-pong/rust/src/lobby.rs new file mode 100644 index 0000000..484a13b --- /dev/null +++ b/net-pong/rust/src/lobby.rs @@ -0,0 +1,254 @@ +use godot::classes::Button; +use godot::classes::ENetMultiplayerPeer; +use godot::classes::Label; +use godot::classes::LineEdit; +use godot::classes::LinkButton; +use godot::classes::Os; +use godot::classes::Panel; +use godot::classes::ProjectSettings; +use godot::classes::enet_connection::CompressionMode; +use godot::classes::object::ConnectFlags; +use godot::global::Error; +use godot::prelude::*; + +const DEFAULT_PORT: i32 = 8910; + +#[derive(GodotClass)] +#[class(base=Panel)] +pub struct Lobby { + #[export] + address: Option>, + #[export] + host_button: Option>, + #[export] + join_button: Option>, + #[export] + status_ok: Option>, + #[export] + status_fail: Option>, + #[export] + port_forward_label: Option>, + #[export] + find_public_ip_button: Option>, + peer: Option>, + base: Base, +} + +use godot::classes::IPanel; + +use crate::pong::Pong; + +#[godot_api] +impl IPanel for Lobby { + fn init(base: Base) -> Self { + Self { + address: None, + host_button: None, + join_button: None, + status_ok: None, + status_fail: None, + port_forward_label: None, + find_public_ip_button: None, + peer: None, + base, + } + } + + fn ready(&mut self) { + /* + # Connect all the callbacks related to networking. + multiplayer.peer_connected.connect(_player_connected) + multiplayer.peer_disconnected.connect(_player_disconnected) + multiplayer.connected_to_server.connect(_connected_ok) + multiplayer.connection_failed.connect(_connected_fail) + multiplayer.server_disconnected.connect(_server_disconnected) + */ + let multiplayer = self.base().get_multiplayer().unwrap(); + let gd_ref = self.to_gd(); + multiplayer + .signals() + .peer_connected() + .builder() + .connect_other_gd(&gd_ref, |mut this: Gd, _id: i64| { + godot_print!("Someone connected, start the game!"); + let pong: Gd = load::("res://pong.tscn") + .instantiate() + .unwrap() + .cast(); + // Connect deferred so we can safely erase it from the callback. + pong.signals() + .game_finished() + .builder() + .flags(ConnectFlags::DEFERRED) + .connect_other_gd(&this, |mut this: Gd| { + this.bind_mut() + ._end_game("Client disconnected.".to_string()); + }); + + this.bind_mut() + .base_mut() + .get_tree() + .unwrap() + .get_root() + .unwrap() + .add_child(&pong); + this.bind_mut().base_mut().hide(); + }); + multiplayer + .signals() + .peer_disconnected() + .builder() + .connect_other_mut(&self.to_gd(), |this: &mut Self, _id: i64| { + if this.base().get_multiplayer().unwrap().is_server() { + this._end_game("Client disconnected.".to_string()); + } else { + this._end_game("Server disconnected.".to_string()); + } + }); + multiplayer + .signals() + .connected_to_server() + .builder() + .connect_other_mut(&self.to_gd(), |_this: &mut Self| { + // This function is not needed for this project. + }); + multiplayer + .signals() + .connection_failed() + .builder() + .connect_other_mut(&self.to_gd(), |this: &mut Self| { + this._set_status("Couldn't connect.".to_string(), false); + let mut multiplayer = this.base().get_multiplayer().unwrap(); + multiplayer.set_multiplayer_peer(Gd::null_arg()); // Remove peer. + this.host_button.as_mut().unwrap().set_disabled(false); + this.join_button.as_mut().unwrap().set_disabled(false); + }); + multiplayer + .signals() + .server_disconnected() + .builder() + .connect_other_mut(&self.to_gd(), |this: &mut Self| { + this._end_game("Server disconnected.".to_string()); + }); + + let gd_ref = self.to_gd(); + + // Clone the Gd