diff --git a/Scenes/Root.tscn b/Scenes/Root.tscn index 717caca..aa1196e 100644 --- a/Scenes/Root.tscn +++ b/Scenes/Root.tscn @@ -1,11 +1,21 @@ -[gd_scene load_steps=6 format=3 uid="uid://bjhmjrldq4lkt"] +[gd_scene load_steps=9 format=3 uid="uid://bjhmjrldq4lkt"] [ext_resource type="PackedScene" uid="uid://b3gx0bl43lku3" path="res://Scenes/Player.tscn" id="1_vby0g"] [ext_resource type="PackedScene" uid="uid://bvll23f5ibd4v" path="res://Scenes/UI/LaunchScreen.tscn" id="2_7o53i"] [ext_resource type="Script" path="res://Scripts/Launcher.cs" id="2_u5cms"] +[ext_resource type="Script" path="res://Scripts/Logic/Scoreboard.cs" id="3_as2wg"] [ext_resource type="Script" path="res://Scripts/Logic/World.cs" id="3_xwguj"] +[ext_resource type="PackedScene" uid="uid://bb704b0kpwwkr" path="res://Scenes/UI/PlayerDiedScreen.tscn" id="5_pimes"] [ext_resource type="PackedScene" uid="uid://c7w5sgq0bshk0" path="res://Scenes/UI/HUD.tscn" id="5_qvun1"] +[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_gyspy"] +properties/0/path = NodePath(".:RoundProgress") +properties/0/spawn = true +properties/0/replication_mode = 1 +properties/1/path = NodePath(".:RoundTimeLeft") +properties/1/spawn = true +properties/1/replication_mode = 1 + [node name="Node" type="Node"] [node name="LaunchScreen" parent="." node_paths=PackedStringArray("Launcher") instance=ExtResource("2_7o53i")] @@ -16,14 +26,26 @@ script = ExtResource("2_u5cms") World = NodePath("../World") Overlay = NodePath("../OverlayLayer") +[node name="ScoreboardNode" type="Node" parent="."] +script = ExtResource("3_as2wg") + [node name="OverlayLayer" type="CanvasLayer" parent="."] [node name="Hud" parent="OverlayLayer" node_paths=PackedStringArray("World") instance=ExtResource("5_qvun1")] World = NodePath("../../World") -[node name="World" type="Node2D" parent="."] +[node name="PlayerDiedScreen" parent="OverlayLayer" instance=ExtResource("5_pimes")] +visible = false + +[node name="World" type="Node2D" parent="." node_paths=PackedStringArray("Scoreboard")] script = ExtResource("3_xwguj") +Scoreboard = NodePath("../ScoreboardNode") PlayerScene = ExtResource("1_vby0g") +RoundDuration = 10.0 + +[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="."] +root_path = NodePath("../World") +replication_config = SubResource("SceneReplicationConfig_gyspy") [node name="PlayerSpawner" type="MultiplayerSpawner" parent="."] _spawnable_scenes = PackedStringArray("res://Scenes/Player.tscn") diff --git a/Scenes/UI/HUD.tscn b/Scenes/UI/HUD.tscn index f899fab..5734eb2 100644 --- a/Scenes/UI/HUD.tscn +++ b/Scenes/UI/HUD.tscn @@ -1,13 +1,7 @@ -[gd_scene load_steps=4 format=3 uid="uid://c7w5sgq0bshk0"] +[gd_scene load_steps=2 format=3 uid="uid://c7w5sgq0bshk0"] [ext_resource type="Script" path="res://Scripts/UI/HUD.cs" id="1_2iqqk"] -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_bl2bd"] -bg_color = Color(0.243137, 0.243137, 0.243137, 1) - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_it2a4"] -bg_color = Color(0.545098, 0.545098, 0.545098, 1) - [node name="Hud" type="Control"] layout_mode = 3 anchors_preset = 15 @@ -83,17 +77,37 @@ show_percentage = false [node name="BottomBox" type="VBoxContainer" parent="."] layout_mode = 0 -offset_top = 608.0 -offset_right = 1152.0 -offset_bottom = 648.0 +offset_left = 16.0 +offset_top = 592.0 +offset_right = 1136.0 +offset_bottom = 632.0 alignment = 2 -[node name="ProgressBar" type="ProgressBar" parent="BottomBox"] -custom_minimum_size = Vector2(2.08165e-12, 4) +[node name="HBox" type="HBoxContainer" parent="BottomBox"] +layout_mode = 2 + +[node name="PositionLabel" type="Label" parent="BottomBox/HBox"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "(0, 0)" + +[node name="RoundLabel" type="Label" parent="BottomBox/HBox"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Round 1" +horizontal_alignment = 1 + +[node name="RoundSecondLabel" type="Label" parent="BottomBox/HBox"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "60.00" +horizontal_alignment = 2 + +[node name="ProgressBar" type="ProgressBar" parent="BottomBox"] +custom_minimum_size = Vector2(2.08165e-12, 8) layout_mode = 2 -theme_override_styles/background = SubResource("StyleBoxFlat_bl2bd") -theme_override_styles/fill = SubResource("StyleBoxFlat_it2a4") value = 50.0 +rounded = true allow_greater = true allow_lesser = true show_percentage = false diff --git a/Scenes/UI/PlayerDiedScreen.tscn b/Scenes/UI/PlayerDiedScreen.tscn new file mode 100644 index 0000000..3a7cf6e --- /dev/null +++ b/Scenes/UI/PlayerDiedScreen.tscn @@ -0,0 +1,28 @@ +[gd_scene format=3 uid="uid://bb704b0kpwwkr"] + +[node name="PlayerDiedScreen" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="CenterContainer" type="CenterContainer" parent="."] +layout_mode = 0 +offset_right = 1152.0 +offset_bottom = 648.0 + +[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer"] +layout_mode = 2 + +[node name="Title" type="Label" parent="CenterContainer/VBoxContainer"] +layout_mode = 2 +theme_override_font_sizes/font_size = 28 +text = "You died" +horizontal_alignment = 1 + +[node name="Caption" type="Label" parent="CenterContainer/VBoxContainer"] +layout_mode = 2 +text = "Respawn in Next Round" +horizontal_alignment = 1 diff --git a/Scripts/Bullet.cs b/Scripts/Bullet.cs index 8f8ad0e..d97f69d 100644 --- a/Scripts/Bullet.cs +++ b/Scripts/Bullet.cs @@ -20,7 +20,7 @@ public partial class Bullet : Area2D if (body is Player player) { if (player.PlayerId == PlayerId) return; - player.TakeDamage(Damage); + player.TakeDamage(Damage, PlayerId); } if (body is Brick brick) diff --git a/Scripts/Logic/Scoreboard.cs b/Scripts/Logic/Scoreboard.cs new file mode 100644 index 0000000..0ce6079 --- /dev/null +++ b/Scripts/Logic/Scoreboard.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using Godot; + +namespace AceField.Scripts.Logic; + +public record PlayerData(string Name, int Score = 0) +{ + public string Name { get; set; } = Name; + public int Score { get; set; } = Score; +}; + +public partial class Scoreboard : Node +{ + public Dictionary Players = new(); + + public void AddPlayer(int networkId, string playerName = null) + { + Players[networkId] = new PlayerData(playerName ?? $"Player#{networkId}"); + } + + public void RemovePlayer(int networkId) + { + Players.Remove(networkId); + } + + public void AddScore(int networkId, int amount = 1) + { + if (Players[networkId]?.Score == null) return; + Players[networkId]!.Score += amount; + } +} diff --git a/Scripts/Logic/World.cs b/Scripts/Logic/World.cs index 52e18a0..9e141e0 100644 --- a/Scripts/Logic/World.cs +++ b/Scripts/Logic/World.cs @@ -4,86 +4,135 @@ namespace AceField.Scripts.Logic; public partial class World : Node2D { - [Export] public PackedScene PlayerScene; + [Export] public Scoreboard Scoreboard; + [Export] public PackedScene PlayerScene; - public void StartGame(string currentPlayerName = null) - { - if (!Multiplayer.IsServer()) - return; + [Export] public int RoundCount = 1; + [Export] public double RoundDuration = 60; + [Export] public double RoundProgress; + [Export] public double RoundTimeLeft; - // Handling player connect / disconnect after this client connected - Multiplayer.PeerDisconnected += RemovePlayer_Adaptor; - Multiplayer.PeerConnected += AddPlayer_Adaptor; + private Timer _roundTimer; - // Handling player connected before this client - foreach (var id in Multiplayer.GetPeers()) - AddPlayer(id); + public void StartGame(string currentPlayerName = null) + { + if (!Multiplayer.IsServer()) + return; - // Add this client as a player if client isn't a dedicated server - if (!OS.HasFeature("dedicated_server")) - AddPlayer(1, currentPlayerName); - } + _roundTimer = new Timer(); + _roundTimer.WaitTime = RoundDuration; + _roundTimer.Autostart = true; + _roundTimer.Timeout += NewRound; + _roundTimer.OneShot = true; + AddChild(_roundTimer); + _roundTimer.Start(); - public override void _ExitTree() - { - if (!Multiplayer.IsServer()) - return; + // Handling player connect / disconnect after this client connected + Multiplayer.PeerDisconnected += RemovePlayer_Adaptor; + Multiplayer.PeerConnected += AddPlayer_Adaptor; - Multiplayer.PeerDisconnected -= RemovePlayer_Adaptor; - Multiplayer.PeerConnected -= AddPlayer_Adaptor; - } + // Handling player connected before this client + foreach (var id in Multiplayer.GetPeers()) + Scoreboard.AddPlayer(id); - private static string BuildPlayerName(int id) - => $"Player@{id}"; + // Add this client as a player if client isn't a dedicated server + if (!OS.HasFeature("dedicated_server")) + Scoreboard.AddPlayer(1, currentPlayerName); - private void AddPlayer_Adaptor(long id) - => AddPlayer((int)id); + // Add players into the game + PutPlayers(currentPlayerName); + } - private void RemovePlayer_Adaptor(long id) - => RemovePlayer((int)id); + public override void _ExitTree() + { + if (!Multiplayer.IsServer()) + return; - private void AddPlayer(int id, string name = null) - { - var player = PlayerScene.Instantiate(); - player.PlayerId = id; - var position = Vector2.FromAngle(GD.Randf() * 2 * Mathf.Pi); - player.Position = new Vector2(position.X * 5f * GD.Randf(), position.Y * 5f * GD.Randf()); - player.Name = BuildPlayerName(id); - player.PlayerName = name; + Multiplayer.PeerDisconnected -= RemovePlayer_Adaptor; + Multiplayer.PeerConnected -= AddPlayer_Adaptor; + } - AddChild(player, true); - } + public override void _Process(double delta) + { + if (Multiplayer.IsServer()) + { + RoundProgress = _roundTimer.TimeLeft / _roundTimer.WaitTime; + RoundTimeLeft = _roundTimer.TimeLeft; + } + } - private void RemovePlayer(int id) - { - var name = BuildPlayerName(id); - if (!HasNode(name)) - return; + private static string BuildPlayerName(int id) + => $"Player@{id}"; - GetNode(name).QueueFree(); - } + private void AddPlayer_Adaptor(long id) + => Scoreboard.AddPlayer((int)id); - public Player GetCurrentPlayer() - { - foreach(var child in GetChildren()) - { - if (child is Player { IsCurrentPlayer: true } player) - { - return player; - } - } + private void RemovePlayer_Adaptor(long id) + => Scoreboard.RemovePlayer((int)id); - return null; - } - - public T GetTileByPosition(Vector2 position) where T : Node2D - { - foreach (var item in GetChildren()) - { - if (item is T tile && tile.Position == position) - return tile; - } + private void SpawnPlayer(int id, string name = null) + { + var player = PlayerScene.Instantiate(); + player.PlayerId = id; + var position = Vector2.FromAngle(GD.Randf() * 2 * Mathf.Pi); + player.Position = new Vector2(position.X * 5f * GD.Randf(), position.Y * 5f * GD.Randf()); + player.Name = BuildPlayerName(id); + player.PlayerName = name; + player.PlayerDied += (killerId) => + { + if (killerId == 0) return; + Scoreboard.AddScore(killerId); + }; - return null; - } -} \ No newline at end of file + AddChild(player, true); + } + + private void PutPlayers(string currentPlayerName = null) + { + // Spawn clients + foreach (var id in Multiplayer.GetPeers()) + SpawnPlayer(id, Scoreboard.Players[id]?.Name); + + // Spawn host + if (!OS.HasFeature("dedicated_server")) + SpawnPlayer(1, currentPlayerName ?? Scoreboard.Players[1]?.Name); + } + + private void NewRound() + { + RoundCount++; + foreach (var child in GetChildren()) + { + if (child is not Timer) + child.QueueFree(); + } + + PutPlayers(); + + _roundTimer.Start(); + } + + public Player GetCurrentPlayer() + { + foreach (var child in GetChildren()) + { + if (child is Player { IsCurrentPlayer: true } player) + { + return player; + } + } + + return null; + } + + public T GetTileByPosition(Vector2 position) where T : Node2D + { + foreach (var item in GetChildren()) + { + if (item is T tile && tile.Position == position) + return tile; + } + + return null; + } +} diff --git a/Scripts/Player.cs b/Scripts/Player.cs index abd28cf..aa3208e 100644 --- a/Scripts/Player.cs +++ b/Scripts/Player.cs @@ -45,6 +45,9 @@ public partial class Player : CharacterBody2D } } + [Signal] + public delegate void PlayerDiedEventHandler(int killerId); + public bool IsCurrentPlayer => _currentPlayerId == Multiplayer.GetUniqueId(); public bool IsReloading @@ -71,6 +74,9 @@ public partial class Player : CharacterBody2D AmmoAmount = MaxAmmoAmount; PlayerInput.IsReloading = false; }; + + GetParent().GetParent().GetNode("OverlayLayer/Hud").Show(); + GetParent().GetParent().GetNode("OverlayLayer/PlayerDiedScreen").Hide(); } public override void _Process(double delta) @@ -157,9 +163,9 @@ public partial class Player : CharacterBody2D MoveAndSlide(); } - public void TakeDamage(double damage) + public void TakeDamage(double damage, int attackerId = 0) { - Rpc(nameof(GotDamage), damage); + Rpc(nameof(GotDamage), damage, attackerId); } private void Shoot(string name) @@ -189,7 +195,7 @@ public partial class Player : CharacterBody2D } [Rpc(MultiplayerApi.RpcMode.AnyPeer, CallLocal = true)] - private void GotDamage(double damage) + private void GotDamage(double damage, int attackerId) { var protection = GetNode("ProtectionCountdown"); @@ -206,5 +212,18 @@ public partial class Player : CharacterBody2D var shakableCamera = GetNode("Camera2D"); shakableCamera.AddTrauma(0.5f); + + if (!(Health <= 0)) return; + Rpc(nameof(Die), attackerId); + if (!IsCurrentPlayer) return; + GetParent().GetParent().GetNode("OverlayLayer/Hud").Hide(); + GetParent().GetParent().GetNode("OverlayLayer/PlayerDiedScreen").Show(); + } + + [Rpc(MultiplayerApi.RpcMode.AnyPeer, CallLocal = true)] + private void Die(int killerId = 0) + { + EmitSignal(SignalName.PlayerDied, killerId); + QueueFree(); } } diff --git a/Scripts/UI/HUD.cs b/Scripts/UI/HUD.cs index c7b698f..74bc88b 100644 --- a/Scripts/UI/HUD.cs +++ b/Scripts/UI/HUD.cs @@ -18,6 +18,14 @@ public partial class HUD : Control public override void _Process(double delta) { + var roundBar = GetNode("BottomBox/ProgressBar"); + roundBar.Value = World.RoundProgress * 100; + + var roundLabel = GetNode