CaptureAreaSystem.cs 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. using Content.Server.GameTicking.Rules.Components;
  2. using Content.Shared.NPC.Components;
  3. using Content.Shared.Physics;
  4. using Robust.Shared.Timing;
  5. using Content.Server.Chat.Systems;
  6. using Content.Server.RoundEnd;
  7. using Content.Shared.Mobs.Components;
  8. using Content.Shared.Mobs;
  9. namespace Content.Server.GameTicking.Rules;
  10. public sealed class CaptureAreaSystem : GameRuleSystem<CaptureAreaRuleComponent>
  11. {
  12. [Dependency] private readonly SharedTransformSystem _transform = default!;
  13. [Dependency] private readonly IEntityManager _entityManager = default!;
  14. [Dependency] private readonly EntityLookupSystem _lookup = default!;
  15. [Dependency] private readonly IGameTiming _timing = default!;
  16. [Dependency] private readonly ChatSystem _chat = default!;
  17. [Dependency] private readonly RoundEndSystem _roundEndSystem = default!;
  18. [Dependency] private readonly GameTicker _gameTicker = default!;
  19. public override void Initialize()
  20. {
  21. base.Initialize();
  22. }
  23. public override void Update(float frameTime)
  24. {
  25. base.Update(frameTime);
  26. var query = EntityQueryEnumerator<CaptureAreaComponent>();
  27. while (query.MoveNext(out var uid, out var area))
  28. {
  29. ProcessArea(uid, area, frameTime);
  30. }
  31. }
  32. /// <summary>
  33. /// Processes a capture area, determining faction control based on the presence of alive faction members, updating control status, managing capture timers, and dispatching global announcements for control changes, timed warnings, and victory.
  34. /// </summary>
  35. /// <param name="uid">The entity identifier of the capture area.</param>
  36. /// <param name="area">The capture area component to process.</param>
  37. /// <param name="frameTime">The elapsed time since the last update, in seconds.</param>
  38. private void ProcessArea(EntityUid uid, CaptureAreaComponent area, float frameTime)
  39. {
  40. var areaXform = _transform.GetMapCoordinates(uid);
  41. var factionCounts = new Dictionary<string, int>();
  42. // Initialize counts for all capturable factions to 0
  43. foreach (var faction in area.CapturableFactions)
  44. {
  45. factionCounts[faction] = 0;
  46. }
  47. // Find entities in range and count factions
  48. var entitiesInRange = _lookup.GetEntitiesInRange(areaXform, area.CaptureRadius, LookupFlags.Dynamic | LookupFlags.Sundries); // Include dynamic entities and items/mobs etc.
  49. foreach (var entity in entitiesInRange)
  50. {
  51. if (EntityManager.TryGetComponent<MobStateComponent>(entity, out var mobState))
  52. {
  53. //do not count dead and crit mobs
  54. if (mobState.CurrentState == MobState.Alive)
  55. // Check if the entity has a faction and if it's one we care about
  56. if (_entityManager.TryGetComponent<NpcFactionMemberComponent>(entity, out var factionMember))
  57. {
  58. foreach (var faction in factionMember.Factions)
  59. {
  60. if (area.CapturableFactions.Contains(faction))
  61. factionCounts[faction]++;
  62. }
  63. }
  64. }
  65. }
  66. // Determine the controlling faction
  67. var currentController = "";
  68. var maxCount = 0;
  69. foreach (var (faction, count) in factionCounts)
  70. {
  71. if (count > maxCount)
  72. {
  73. maxCount = count;
  74. currentController = faction;
  75. }
  76. else if (maxCount != 0 && count == maxCount)
  77. {
  78. currentController = ""; // Contested
  79. }
  80. }
  81. // Update component state
  82. if (maxCount > 0 && currentController != "")
  83. {
  84. area.Occupied = true;
  85. }
  86. if (currentController != area.Controller)
  87. {
  88. // Controller changed (or became contested/empty)
  89. area.Controller = currentController;
  90. area.CaptureTimer = 0f; // Reset timer on change
  91. area.CaptureTimerAnnouncement1 = false;
  92. area.CaptureTimerAnnouncement2 = false;
  93. if (currentController == "")
  94. {
  95. _chat.DispatchGlobalAnnouncement($"{area.PreviousController} has lost control of {area.Name}!", "Objective", false, null, Color.Red);
  96. }
  97. else
  98. {
  99. _chat.DispatchGlobalAnnouncement($"{currentController} has gained control of {area.Name}!", "Objective", false, null, Color.DodgerBlue);
  100. }
  101. }
  102. else if (!string.IsNullOrEmpty(currentController))
  103. {
  104. // Controller remains the same, increment timer
  105. area.CaptureTimer += frameTime;
  106. //announce when theres 2 and 1 minutes left.
  107. var timeleft = area.CaptureDuration - area.CaptureTimer;
  108. if (timeleft <= 120 && area.CaptureTimerAnnouncement2 == false)
  109. {
  110. _chat.DispatchGlobalAnnouncement($"Two minutes until {currentController} captures {area.Name}!", "Round", false, null, Color.Blue);
  111. area.CaptureTimerAnnouncement2 = true;
  112. }
  113. else if (timeleft < 60 && area.CaptureTimerAnnouncement1 == false)
  114. {
  115. _chat.DispatchGlobalAnnouncement($"One minute until {currentController} captures {area.Name}!", "Round", false, null, Color.Blue);
  116. area.CaptureTimerAnnouncement1 = true;
  117. }
  118. //Check for capture completion
  119. if (area.CaptureTimer >= area.CaptureDuration)
  120. {
  121. if (_gameTicker.RunLevel == GameRunLevel.InRound)
  122. {
  123. _chat.DispatchGlobalAnnouncement($"{currentController} has captured {area.Name} and is victorious!", "Round", false, null, Color.Green);
  124. _roundEndSystem.EndRound();
  125. }
  126. }
  127. }
  128. else
  129. {
  130. // Area is empty or contested, and wasn't previously controlled by a single faction
  131. area.CaptureTimer = 0f; // Ensure timer is reset/stays reset
  132. area.CaptureTimerAnnouncement1 = false;
  133. area.CaptureTimerAnnouncement2 = false;
  134. }
  135. area.PreviousController = currentController;
  136. }
  137. }