TeamDeathMatchRuleSystem.cs 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. using System.Linq;
  2. using Content.Server.Administration.Commands;
  3. using Content.Server.GameTicking.Rules.Components;
  4. using Content.Server.KillTracking;
  5. using Content.Server.Mind;
  6. using Content.Server.Points;
  7. using Content.Server.RoundEnd;
  8. using Content.Server.Station.Systems;
  9. using Content.Server.NPC.Systems;
  10. using Content.Shared.GameTicking;
  11. using Content.Shared.GameTicking.Components;
  12. using Content.Shared.NPC.Components;
  13. using Content.Shared.Points;
  14. using Content.Shared.Storage;
  15. using Robust.Server.GameObjects;
  16. using Robust.Server.Player;
  17. using Robust.Shared.Utility;
  18. using Content.Shared.NPC.Systems;
  19. using Robust.Shared.Player;
  20. namespace Content.Server.GameTicking.Rules;
  21. /// <summary>
  22. /// Manages <see cref="TeamDeathMatchRuleComponent"/>
  23. /// </summary>
  24. public sealed class TeamDeathMatchRuleSystem : GameRuleSystem<TeamDeathMatchRuleComponent>
  25. {
  26. [Dependency] private readonly IPlayerManager _player = default!;
  27. [Dependency] private readonly MindSystem _mind = default!;
  28. [Dependency] private readonly PointSystem _point = default!;
  29. [Dependency] private readonly RespawnRuleSystem _respawn = default!;
  30. [Dependency] private readonly RoundEndSystem _roundEnd = default!;
  31. [Dependency] private readonly StationSpawningSystem _stationSpawning = default!;
  32. [Dependency] private readonly NpcFactionSystem _factionSystem = default!; // Added dependency
  33. [Dependency] private readonly TransformSystem _transform = default!;
  34. [Dependency] private readonly IEntityManager _entities = default!;
  35. public override void Initialize()
  36. {
  37. base.Initialize();
  38. SubscribeLocalEvent<PlayerBeforeSpawnEvent>(OnBeforeSpawn);
  39. SubscribeLocalEvent<PlayerSpawnCompleteEvent>(OnSpawnComplete);
  40. SubscribeLocalEvent<KillReportedEvent>(OnKillReported);
  41. }
  42. private void OnBeforeSpawn(PlayerBeforeSpawnEvent ev)
  43. {
  44. var query = EntityQueryEnumerator<TeamDeathMatchRuleComponent, RespawnTrackerComponent, GameRuleComponent>();
  45. while (query.MoveNext(out var uid, out var dm, out var tracker, out var rule))
  46. {
  47. if (!GameTicker.IsGameRuleActive(uid, rule))
  48. continue;
  49. var newMind = _mind.CreateMind(ev.Player.UserId, ev.Profile.Name);
  50. _mind.SetUserId(newMind, ev.Player.UserId);
  51. var mobMaybe = _stationSpawning.SpawnPlayerCharacterOnStation(ev.Station, null, ev.Profile);
  52. DebugTools.AssertNotNull(mobMaybe);
  53. var mob = mobMaybe!.Value;
  54. _mind.TransferTo(newMind, mob);
  55. EnsureComp<KillTrackerComponent>(mob);
  56. ev.Handled = true;
  57. break;
  58. }
  59. }
  60. private void OnSpawnComplete(PlayerSpawnCompleteEvent ev)
  61. {
  62. EnsureComp<KillTrackerComponent>(ev.Mob);
  63. var query = EntityQueryEnumerator<TeamDeathMatchRuleComponent, GameRuleComponent>();
  64. while (query.MoveNext(out var uid, out var component, out var rule))
  65. {
  66. if (!GameTicker.IsGameRuleActive(uid, rule))
  67. continue;
  68. if (component.Team1 == "")
  69. {
  70. if (TryComp<NpcFactionMemberComponent>(ev.Mob, out var npc))
  71. {
  72. if (npc.Factions.Count > 0 && npc.Factions.First() != component.Team2)
  73. {
  74. component.Team1 = npc.Factions.First();
  75. }
  76. }
  77. }
  78. if (component.Team2 == "")
  79. {
  80. if (TryComp<NpcFactionMemberComponent>(ev.Mob, out var npc))
  81. {
  82. if (npc.Factions.Count > 0 && npc.Factions.First() != component.Team1)
  83. {
  84. component.Team2 = npc.Factions.First();
  85. }
  86. }
  87. }
  88. }
  89. }
  90. private void OnKillReported(ref KillReportedEvent ev)
  91. {
  92. var query = EntityQueryEnumerator<TeamDeathMatchRuleComponent, GameRuleComponent>();
  93. while (query.MoveNext(out var uid, out var dm, out var rule))
  94. {
  95. if (!GameTicker.IsGameRuleActive(uid, rule))
  96. continue;
  97. // Check if the killed entity is part of either team using FactionSystem
  98. if (HasComp<NpcFactionMemberComponent>(ev.Entity))
  99. {
  100. string killedTeam = "";
  101. if (_factionSystem.IsMember(ev.Entity, dm.Team1))
  102. {
  103. dm.Team1Deaths += 1;
  104. dm.Team2Kills += 1;
  105. killedTeam = dm.Team1;
  106. }
  107. else if (_factionSystem.IsMember(ev.Entity, dm.Team2))
  108. {
  109. dm.Team2Deaths += 1;
  110. dm.Team1Kills += 1;
  111. killedTeam = dm.Team2;
  112. }
  113. // Track individual player stats
  114. if (ev.Primary is KillPlayerSource playerSource)
  115. {
  116. var playerIdStr = playerSource.PlayerId.ToString();
  117. if (!dm.KDRatio.ContainsKey(playerIdStr))
  118. {
  119. // Find player name from sessions
  120. string playerName = "Unknown";
  121. foreach (var session in _player.Sessions)
  122. {
  123. if (session.UserId == playerSource.PlayerId)
  124. {
  125. playerName = session.Name;
  126. break;
  127. }
  128. }
  129. string playerTeam = killedTeam == dm.Team1 ? dm.Team2 : dm.Team1;
  130. dm.KDRatio[playerIdStr] = new PlayerKDStats
  131. {
  132. Name = playerName,
  133. Team = playerTeam
  134. };
  135. }
  136. dm.KDRatio[playerIdStr].Kills++;
  137. }
  138. // Track deaths for the killed player
  139. if (_entities.TryGetComponent(ev.Entity, out ActorComponent? actorComponent))
  140. {
  141. var playerIdStrKilled = actorComponent.PlayerSession.UserId.ToString();
  142. if (!dm.KDRatio.ContainsKey(playerIdStrKilled))
  143. {
  144. dm.KDRatio[playerIdStrKilled] = new PlayerKDStats
  145. {
  146. Name = actorComponent.PlayerSession.Name,
  147. Team = killedTeam
  148. };
  149. }
  150. dm.KDRatio[playerIdStrKilled].Deaths++;
  151. }
  152. }
  153. }
  154. }
  155. protected override void AppendRoundEndText(EntityUid uid, TeamDeathMatchRuleComponent component, GameRuleComponent gameRule, ref RoundEndTextAppendEvent args)
  156. {
  157. // If we are using points, use them to display winner
  158. if (component.Team1Points > 0 && component.Team2Points > 0)
  159. {
  160. if (component.Team1Points > component.Team2Points)
  161. {
  162. args.AddLine($"[color=lime]{component.Team1}[/color] has won!");
  163. }
  164. else if (component.Team1Points < component.Team2Points)
  165. {
  166. args.AddLine($"[color=lime]{component.Team2}[/color] has won!");
  167. }
  168. else
  169. {
  170. args.AddLine("The round ended in a [color=yellow]draw[/color]!");
  171. }
  172. }
  173. args.AddLine("");
  174. args.AddLine($"[color=cyan]{component.Team1}[/color]: {component.Team1Kills} Kills, {component.Team1Deaths} Deaths");
  175. args.AddLine("");
  176. args.AddLine($"[color=cyan]{component.Team2}[/color]: {component.Team2Kills} Kills, {component.Team2Deaths} Deaths");
  177. // Display K/D ratio per player, sorted by K/D ratio
  178. args.AddLine("");
  179. args.AddLine("[color=yellow]Player Statistics:[/color]");
  180. // Sort players by K/D ratio (highest first)
  181. var sortedPlayers = component.KDRatio
  182. .OrderByDescending(p => p.Value.KDRatio)
  183. .ThenByDescending(p => p.Value.Kills)
  184. .ToList();
  185. // Display team 1 players
  186. args.AddLine($"[color=cyan]{component.Team1}[/color] Players:");
  187. foreach (var player in sortedPlayers.Where(p => p.Value.Team == component.Team1))
  188. {
  189. var kdRatio = player.Value.Deaths == 0 ? player.Value.Kills.ToString() : (player.Value.Kills / (float)player.Value.Deaths).ToString("F2");
  190. args.AddLine($" [color=white]{player.Value.Name}[/color]: {player.Value.Kills} Kills, {player.Value.Deaths} Deaths, K/D: {kdRatio}");
  191. }
  192. // Display team 2 players
  193. args.AddLine("");
  194. args.AddLine($"[color=cyan]{component.Team2}[/color] Players:");
  195. foreach (var player in sortedPlayers.Where(p => p.Value.Team == component.Team2))
  196. {
  197. var kdRatio = player.Value.Deaths == 0 ? player.Value.Kills.ToString() : (player.Value.Kills / (float)player.Value.Deaths).ToString("F2");
  198. args.AddLine($" [color=white]{player.Value.Name}[/color]: {player.Value.Kills} Kills, {player.Value.Deaths} Deaths, K/D: {kdRatio}");
  199. }
  200. }
  201. }