diff --git a/src/Application/Teams/Flags/Flag.cs b/src/Application/Teams/Flags/Flag.cs
index 2e60d4f7..5c16f709 100644
--- a/src/Application/Teams/Flags/Flag.cs
+++ b/src/Application/Teams/Flags/Flag.cs
@@ -20,6 +20,14 @@ public class Flag
///
public bool IsCaptured() => Carrier is not null;
+ ///
+ /// Gets the name of the player who captured the flag.
+ ///
+ ///
+ /// If the flag is not captured, returns None.
+ ///
+ public string CarrierName => IsCaptured() ? Carrier.Name : "None";
+
///
/// Sets the player who holds the flag.
///
diff --git a/src/Application/Teams/Systems/TeamStatsSystem.cs b/src/Application/Teams/Systems/TeamStatsSystem.cs
new file mode 100644
index 00000000..a28527df
--- /dev/null
+++ b/src/Application/Teams/Systems/TeamStatsSystem.cs
@@ -0,0 +1,50 @@
+namespace CTF.Application.Teams.Systems;
+
+public class TeamStatsSystem(
+ IDialogService dialogService,
+ TeamTextDrawRenderer teamTextDrawRenderer) : ISystem
+{
+ [Event]
+ public void OnPlayerSpawn(Player player)
+ {
+ teamTextDrawRenderer.Show(player);
+ }
+
+ [Event]
+ public void OnPlayerDeath(Player deadPlayer, Player killer, Weapon reason)
+ {
+ PlayerInfo deadPlayerInfo = deadPlayer.GetInfo();
+ deadPlayerInfo.Team.StatsPerRound.AddDeaths();
+
+ if (killer.IsInvalidPlayer())
+ return;
+
+ PlayerInfo killerInfo = killer.GetInfo();
+ killerInfo.Team.StatsPerRound.AddKills();
+ }
+
+ [PlayerCommand("tstats")]
+ public void ShowStats(Player player)
+ {
+ Team alphaTeam = Team.Alpha;
+ Team betaTeam = Team.Beta;
+ var content =
+ $"""
+ {alphaTeam.ColorHex}>>> Alpha Team:
+ Members: {alphaTeam.Members.Count}
+ Score: {alphaTeam.StatsPerRound.Score}
+ Kills: {alphaTeam.StatsPerRound.Kills}
+ Deaths: {alphaTeam.StatsPerRound.Deaths}
+ Flag captured by: {alphaTeam.Flag.CarrierName}
+
+ {betaTeam.ColorHex}>>> Beta Team:
+ Members: {betaTeam.Members.Count}
+ Score: {betaTeam.StatsPerRound.Score}
+ Kills: {betaTeam.StatsPerRound.Kills}
+ Deaths: {betaTeam.StatsPerRound.Deaths}
+ Flag captured by: {betaTeam.Flag.CarrierName}
+ """;
+ var dialog = new MessageDialog("Team Stats", content, "Close");
+ dialogService.ShowAsync(player, dialog);
+ }
+}
diff --git a/src/Application/Teams/Systems/TeamSystem.cs b/src/Application/Teams/Systems/TeamSystem.cs
deleted file mode 100644
index 77ab115f..00000000
--- a/src/Application/Teams/Systems/TeamSystem.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace CTF.Application.Teams.Systems;
-
-public class TeamSystem(TeamTextDrawRenderer teamTextDrawRenderer) : ISystem
-{
- [Event]
- public void OnPlayerSpawn(Player player)
- {
- teamTextDrawRenderer.Show(player);
- }
-}
diff --git a/tests/Application.Tests/Teams/Flags/FlagTests.cs b/tests/Application.Tests/Teams/Flags/FlagTests.cs
index be5db459..cfccc6f6 100644
--- a/tests/Application.Tests/Teams/Flags/FlagTests.cs
+++ b/tests/Application.Tests/Teams/Flags/FlagTests.cs
@@ -42,6 +42,48 @@ public void IsCaptured_WhenFlagIsNotCapturedByPlayer_ShouldReturnsFalse()
actual.Should().BeFalse();
}
+ [Test]
+ public void CarrierName_WhenFlagIsCaptured_ShouldReturnsCarrierName()
+ {
+ // Arrange
+ var flag = new Flag
+ {
+ Model = FlagModel.Blue,
+ Icon = FlagIcon.Blue,
+ Name = "Blue",
+ ColorHex = Color.Blue
+ };
+ var expectedCarrierName = "Bob";
+ var fakeCarrier = new FakePlayer(id: 1, expectedCarrierName);
+ flag.SetCarrier(fakeCarrier);
+
+ // Act
+ string actual = flag.CarrierName;
+
+ // Assert
+ actual.Should().Be(expectedCarrierName);
+ }
+
+ [Test]
+ public void CarrierName_WhenFlagIsNotCaptured_ShouldReturnsNone()
+ {
+ // Arrange
+ var flag = new Flag
+ {
+ Model = FlagModel.Blue,
+ Icon = FlagIcon.Blue,
+ Name = "Blue",
+ ColorHex = Color.Blue
+ };
+ var expectedCarrierName = "None";
+
+ // Act
+ string actual = flag.CarrierName;
+
+ // Assert
+ actual.Should().Be(expectedCarrierName);
+ }
+
[Test]
public void SetCarrier_WhenArgumentIsNull_ShouldThrowArgumentNullException()
{