From 9626ae360dc20667bd827cf4769cbeaa48407c8f Mon Sep 17 00:00:00 2001 From: Srayan Jana Date: Thu, 26 Jun 2025 15:40:12 -0700 Subject: [PATCH 1/5] add net pong --- net-pong/.gitattributes | 2 + net-pong/.gitignore | 15 + net-pong/LICENSE | 21 + net-pong/README.md | 24 + net-pong/godot/.editorconfig | 4 + net-pong/godot/.gitattributes | 2 + net-pong/godot/.gitignore | 3 + net-pong/godot/README.md | 17 + net-pong/godot/ball.png | Bin 0 -> 115 bytes net-pong/godot/ball.tscn | 14 + net-pong/godot/godot-rust.png | Bin 0 -> 13758 bytes net-pong/godot/icon.webp | Bin 0 -> 1826 bytes net-pong/godot/lobby.tscn | 159 +++++++ net-pong/godot/paddle.png | Bin 0 -> 84 bytes net-pong/godot/paddle.tscn | 27 ++ net-pong/godot/pong.tscn | 92 ++++ net-pong/godot/project.godot | 42 ++ net-pong/godot/rust.gdextension | 14 + net-pong/godot/rust.gdextension.uid | 1 + net-pong/godot/screenshots/.gdignore | 0 .../godot/screenshots/pong_multiplayer.png | Bin 0 -> 1333 bytes net-pong/godot/separator.png | Bin 0 -> 108 bytes net-pong/rust/.gitignore | 1 + net-pong/rust/Cargo.toml | 11 + net-pong/rust/src/ball.rs | 180 +++++++ net-pong/rust/src/lib.rs | 23 + net-pong/rust/src/lobby.rs | 446 ++++++++++++++++++ net-pong/rust/src/paddle.rs | 179 +++++++ net-pong/rust/src/pong.rs | 216 +++++++++ 29 files changed, 1493 insertions(+) create mode 100644 net-pong/.gitattributes create mode 100644 net-pong/.gitignore create mode 100644 net-pong/LICENSE create mode 100644 net-pong/README.md create mode 100644 net-pong/godot/.editorconfig create mode 100644 net-pong/godot/.gitattributes create mode 100644 net-pong/godot/.gitignore create mode 100644 net-pong/godot/README.md create mode 100644 net-pong/godot/ball.png create mode 100644 net-pong/godot/ball.tscn create mode 100644 net-pong/godot/godot-rust.png create mode 100644 net-pong/godot/icon.webp create mode 100644 net-pong/godot/lobby.tscn create mode 100644 net-pong/godot/paddle.png create mode 100644 net-pong/godot/paddle.tscn create mode 100644 net-pong/godot/pong.tscn create mode 100644 net-pong/godot/project.godot create mode 100644 net-pong/godot/rust.gdextension create mode 100644 net-pong/godot/rust.gdextension.uid create mode 100644 net-pong/godot/screenshots/.gdignore create mode 100644 net-pong/godot/screenshots/pong_multiplayer.png create mode 100644 net-pong/godot/separator.png create mode 100644 net-pong/rust/.gitignore create mode 100644 net-pong/rust/Cargo.toml create mode 100644 net-pong/rust/src/ball.rs create mode 100644 net-pong/rust/src/lib.rs create mode 100644 net-pong/rust/src/lobby.rs create mode 100644 net-pong/rust/src/paddle.rs create mode 100644 net-pong/rust/src/pong.rs diff --git a/net-pong/.gitattributes b/net-pong/.gitattributes new file mode 100644 index 0000000..51d481e --- /dev/null +++ b/net-pong/.gitattributes @@ -0,0 +1,2 @@ +# Normalize line endings for all files that Git considers text files. +* text=auto eol=lf \ No newline at end of file diff --git a/net-pong/.gitignore b/net-pong/.gitignore new file mode 100644 index 0000000..d9aac21 --- /dev/null +++ b/net-pong/.gitignore @@ -0,0 +1,15 @@ +# Godot 4+ specific ignores +.godot/ + +# Godot-specific ignores +.import/ +export.cfg +export_presets.cfg + +# Imported translations (automatically generated from CSV files) +*.translation + +# Mono-specific ignores +.mono/ +data_*/ +mono_crash.*.json 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/.editorconfig b/net-pong/godot/.editorconfig new file mode 100644 index 0000000..f28239b --- /dev/null +++ b/net-pong/godot/.editorconfig @@ -0,0 +1,4 @@ +root = true + +[*] +charset = utf-8 diff --git a/net-pong/godot/.gitattributes b/net-pong/godot/.gitattributes new file mode 100644 index 0000000..8ad74f7 --- /dev/null +++ b/net-pong/godot/.gitattributes @@ -0,0 +1,2 @@ +# Normalize EOL for all files that Git considers text files. +* text=auto eol=lf 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 0000000000000000000000000000000000000000..465d3521aa8d143ea46b540f2902a13a507b5b00 GIT binary patch literal 115 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 QY4=MjDDnT6UwoS*0)y|1ZU6uP literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..1860b0741531064d3bbc97137f9ad78e39c22cf0 GIT binary patch literal 84 zcmeAS@N?(olHy`uVBq!ia0vp^96+qV!2%>(e}0SxQqrC-jv*Ddk{KHR2QVZhbNz8= iKJ1nd(opb*nL+W%J%g4wiRTKwOivoN?T>t<7|M&0TKY#xG z{{8#cuU|iY{P_O;`?v4kKYaZ7{=C*=f zA09t(^61fHhYlavzyAQk|Nk2|ZJs@6?yT8!W&+WyIa8-ipFCyS#K}|o`X`oGR+m*& zm6cZ(7MJD~6y+5Z=HwM*=jLbSF!Z|}4gJ`E6YIk-JL?VGu=vG2c?k&H85O8shC@M@Ld zb`dqkAI;1GYh4+bI2N!hU@$qzB;dfH%D}pSQ>?*%`}hC57#`d`_2EVsLkUM&`tS9h zzvlYK#P-NDb3C~8_CHrcvGmDbN)3TgM_K%3Roy2u2ADFipPRIKAH#|*U7T`n80PV@ zI$UL#W9RcNohiXrts+r3avp!fD#i+fs!F>(lNnajn`>qim!y2vWAI|A660CGz!Cst z%}QWkRcK%eV7kDGPdH zuL7MlD}fQ>tP3pMKxd(QmCFI@)c}yM!CnQ%5Rz9xc7uWn5^6yGkWk}zkj;$ZYl!i9 zeT`;3KCb~|0_sv!ufZdQb%75sQotdZ3XBwi199R15`eD!wo3pMf#Ae8g_G?=H#5vP zaO<%72GzPhb54LG^S(0SNZ?4w)?tc}XkZZg>@0AAs}g8;LMkYMwaPc-9cFmpwgsP0 zz*zy+C!jQeC3&I6BSt_U_|A@;2H*jOmU_WS4vX<*rC!3JgUDPs;t}G!S8~Q@I~OiF Sd~Xf|5O})!xvX`F{I*FGD9P?56_G*8n#~jml#xkd7nH`zu^r~p25@A&t;uc GLK6VrgCLOr literal 0 HcmV?d00001 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..9e0827b --- /dev/null +++ b/net-pong/rust/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "rust" +version = "1.0.0" +edition = "2024" +license = "MIT" + +[dependencies] +godot = "0.3.1" + +[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..d45118f --- /dev/null +++ b/net-pong/rust/src/ball.rs @@ -0,0 +1,180 @@ +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; +use crate::variant_array_to_vec; + +#[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 { + //self.base().get_parent().update_score.rpc(false); + let args = varray![false]; + let args = variant_array_to_vec(args); + parent.rpc("update_score", args.as_slice()); + self.base_mut().rpc("reset_ball", args.as_slice()); + } + } 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 { + //self.base().get_parent().update_score.rpc(true); + let args = varray![true]; + let args = variant_array_to_vec(args); + parent.rpc("update_score", args.as_slice()); + self.base_mut().rpc("reset_ball", args.as_slice()); + } + } + + /* + _speed += delta + # 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. + if not stopped: + translate(_speed * delta * direction) + + # Check screen bounds to make ball bounce. + var ball_pos := position + if (ball_pos.y < 0 and direction.y < 0) or (ball_pos.y > _screen_size.y and direction.y > 0): + direction.y = -direction.y + + if 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: + get_parent().update_score.rpc(false) + _reset_ball.rpc(false) + 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: + get_parent().update_score.rpc(true) + _reset_ball.rpc(true) + */ + } +} + +#[godot_api] +impl Ball { + /* + @rpc("any_peer", "call_local") + func bounce(left: bool, random: float) -> void: + # Using sync because both players can make it bounce. + if left: + direction.x = abs(direction.x) + else: + direction.x = -abs(direction.x) + + _speed *= 1.1 + direction.y = random * 2.0 - 1 + direction = direction.normalized() + */ + #[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") + func stop() -> void: + stopped = true + */ + #[rpc(any_peer, call_local)] + fn stop(&mut self) { + self.stopped = true; + } + + /* + @rpc("any_peer", "call_local") + func _reset_ball(for_left: float) -> void: + position = _screen_size / 2 + if for_left: + direction = Vector2.LEFT + else: + direction = Vector2.RIGHT + _speed = DEFAULT_SPEED + */ + + #[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..7a9b522 --- /dev/null +++ b/net-pong/rust/src/lib.rs @@ -0,0 +1,23 @@ +use godot::prelude::*; + +struct RustExtension; + +#[gdextension] +unsafe impl ExtensionLibrary for RustExtension {} + +fn variant_array_to_vec(array: VariantArray) -> Vec { + let mut vec = Vec::new(); + for i in 0..array.len() { + vec.push( + array + .get(i) + .expect("Failed to get element from VariantArray"), + ); + } + vec +} + +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..5fa9b1e --- /dev/null +++ b/net-pong/rust/src/lobby.rs @@ -0,0 +1,446 @@ +use godot::classes::enet_connection::CompressionMode; +use godot::classes::object::ConnectFlags; +use godot::classes::project_settings; +/* +extends Panel + +# Default game server port. Can be any number between 1024 and 49151. +# Not present on the list of registered or common ports as of May 2024: +# https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers +const DEFAULT_PORT = 8910 + +@onready var address: LineEdit = $Address +@onready var host_button: Button = $HostButton +@onready var join_button: Button = $JoinButton +@onready var status_ok: Label = $StatusOk +@onready var status_fail: Label = $StatusFail +@onready var port_forward_label: Label = $PortForward +@onready var find_public_ip_button: LinkButton = $FindPublicIP + +var peer: ENetMultiplayerPeer + +func _ready() -> void: + # 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) + +#region Network callbacks from SceneTree +# Callback from SceneTree. +func _player_connected(_id: int) -> void: + # Someone connected, start the game! + var pong: Node2D = load("res://pong.tscn").instantiate() + # Connect deferred so we can safely erase it from the callback. + pong.game_finished.connect(_end_game, CONNECT_DEFERRED) + + get_tree().get_root().add_child(pong) + hide() + + +func _player_disconnected(_id: int) -> void: + if multiplayer.is_server(): + _end_game("Client disconnected.") + else: + _end_game("Server disconnected.") + + +# Callback from SceneTree, only for clients (not server). +func _connected_ok() -> void: + pass # This function is not needed for this project. + + +# Callback from SceneTree, only for clients (not server). +func _connected_fail() -> void: + _set_status("Couldn't connect.", false) + + multiplayer.set_multiplayer_peer(null) # Remove peer. + host_button.set_disabled(false) + join_button.set_disabled(false) + + +func _server_disconnected() -> void: + _end_game("Server disconnected.") +#endregion + +#region Game creation methods +func _end_game(with_error: String = "") -> void: + if has_node("/root/Pong"): + # Erase immediately, otherwise network might show + # errors (this is why we connected deferred above). + get_node(^"/root/Pong").free() + show() + + multiplayer.set_multiplayer_peer(null) # Remove peer. + host_button.set_disabled(false) + join_button.set_disabled(false) + + _set_status(with_error, false) + + +func _set_status(text: String, is_ok: bool) -> void: + # Simple way to show status. + if is_ok: + status_ok.set_text(text) + status_fail.set_text("") + else: + status_ok.set_text("") + status_fail.set_text(text) + + +func _on_host_pressed() -> void: + peer = ENetMultiplayerPeer.new() + # Set a maximum of 1 peer, since Pong is a 2-player game. + var err := peer.create_server(DEFAULT_PORT, 1) + if err != OK: + # Is another server running? + _set_status("Can't host, address in use.",false) + return + peer.get_host().compress(ENetConnection.COMPRESS_RANGE_CODER) + + multiplayer.set_multiplayer_peer(peer) + host_button.set_disabled(true) + join_button.set_disabled(true) + _set_status("Waiting for player...", true) + get_window().title = ProjectSettings.get_setting("application/config/name") + ": Server" + + # Only show hosting instructions when relevant. + port_forward_label.visible = true + find_public_ip_button.visible = true + + +func _on_join_pressed() -> void: + var ip := address.get_text() + if not ip.is_valid_ip_address(): + _set_status("IP address is invalid.", false) + return + + peer = ENetMultiplayerPeer.new() + peer.create_client(ip, DEFAULT_PORT) + peer.get_host().compress(ENetConnection.COMPRESS_RANGE_CODER) + multiplayer.set_multiplayer_peer(peer) + + _set_status("Connecting...", true) + get_window().title = ProjectSettings.get_setting("application/config/name") + ": Client" +#endregion + +func _on_find_public_ip_pressed() -> void: + OS.shell_open("https://icanhazip.com/") + + */ +use godot::classes::Button; +use godot::classes::ENetConnection; +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::global::Error; +use godot::prelude::*; + +const SCORE_TO_WIN: i32 = 10; +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