GracewallRuleSystem.cs 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. using Content.Server.GameTicking.Rules.Components;
  2. using Content.Shared.NPC.Components;
  3. using Content.Shared.Physics;
  4. using Robust.Shared.Physics.Events;
  5. using Robust.Shared.Physics.Systems;
  6. using Robust.Shared.Timing;
  7. using Content.Server.Chat.Systems;
  8. using Robust.Shared.Physics;
  9. using Content.Shared.GameTicking.Components;
  10. using Robust.Shared.Physics.Components;
  11. using System.Collections.Generic;
  12. namespace Content.Server.GameTicking.Rules;
  13. public sealed class GracewallRuleSystem : GameRuleSystem<GracewallRuleComponent>
  14. {
  15. [Dependency] private readonly SharedPhysicsSystem _physics = default!;
  16. [Dependency] private readonly ChatSystem _chat = default!;
  17. [Dependency] private readonly IGameTiming _gameTiming = default!;
  18. private const int GraceWallCollisionGroup = (int)CollisionGroup.MidImpassable;
  19. // Cache for entity passability checks
  20. private Dictionary<EntityUid, bool> _passabilityCache = new();
  21. private TimeSpan _lastCacheClear;
  22. private const float CacheClearInterval = 5.0f; // Clear cache every 5 seconds
  23. public override void Initialize()
  24. {
  25. base.Initialize();
  26. SubscribeLocalEvent<GracewallAreaComponent, StartCollideEvent>(OnStartCollide);
  27. SubscribeLocalEvent<GracewallAreaComponent, PreventCollideEvent>(OnPreventCollide);
  28. }
  29. protected override void Started(EntityUid uid, GracewallRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
  30. {
  31. base.Started(uid, component, gameRule, args);
  32. component.Timer = (float)component.GracewallDuration.TotalSeconds;
  33. component.GracewallActive = true;
  34. _lastCacheClear = _gameTiming.CurTime;
  35. // Schedule the announcement for 15 seconds later
  36. var announcementMessage = $"The grace wall is up for {component.GracewallDuration.TotalMinutes} minutes!";
  37. Timer.Spawn(TimeSpan.FromSeconds(15), () =>
  38. {
  39. _chat.DispatchGlobalAnnouncement(announcementMessage, "Round", false, null, Color.Yellow);
  40. });
  41. Log.Info($"Grace wall active for {component.GracewallDuration.TotalMinutes} minutes.");
  42. // Activate all grace wall areas
  43. var query = EntityQueryEnumerator<GracewallAreaComponent, TransformComponent, FixturesComponent>();
  44. while (query.MoveNext(out var wallUid, out var area, out var xform, out var fixtures))
  45. {
  46. area.GracewallActive = true;
  47. UpdateGracewallPhysics(wallUid, area, fixtures, true);
  48. }
  49. }
  50. protected override void Ended(EntityUid uid, GracewallRuleComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args)
  51. {
  52. base.Ended(uid, component, gameRule, args);
  53. // Ensure walls are deactivated if the rule ends unexpectedly
  54. DeactivateAllGraceWalls(component);
  55. _chat.DispatchGlobalAnnouncement("The grace wall is now down!", "Round", false, null, Color.Yellow);
  56. }
  57. public override void Update(float frameTime)
  58. {
  59. base.Update(frameTime);
  60. // Clear cache periodically to avoid memory leaks
  61. var currentTime = _gameTiming.CurTime;
  62. if (currentTime - _lastCacheClear > TimeSpan.FromSeconds(CacheClearInterval))
  63. {
  64. _passabilityCache.Clear();
  65. _lastCacheClear = currentTime;
  66. }
  67. var query = EntityQueryEnumerator<GracewallRuleComponent, GameRuleComponent>();
  68. while (query.MoveNext(out var ruleUid, out var gracewall, out var gameRule))
  69. {
  70. if (!GameTicker.IsGameRuleActive(ruleUid, gameRule) || !gracewall.GracewallActive)
  71. continue;
  72. gracewall.Timer -= frameTime;
  73. if (gracewall.Timer <= 0)
  74. {
  75. Log.Info("Grace wall duration ended.");
  76. DeactivateAllGraceWalls(gracewall);
  77. _chat.DispatchGlobalAnnouncement("The grace wall is now down!", "Round", false, null, Color.Yellow);
  78. }
  79. }
  80. }
  81. private void DeactivateAllGraceWalls(GracewallRuleComponent component)
  82. {
  83. component.GracewallActive = false;
  84. // Deactivate all grace wall areas
  85. var query = EntityQueryEnumerator<GracewallAreaComponent, FixturesComponent>();
  86. while (query.MoveNext(out var wallUid, out var area, out var fixtures))
  87. {
  88. if (area.Permanent == false)
  89. {
  90. area.GracewallActive = false;
  91. UpdateGracewallPhysics(wallUid, area, fixtures, false);
  92. }
  93. }
  94. }
  95. private void UpdateGracewallPhysics(EntityUid uid, GracewallAreaComponent component, FixturesComponent fixtures, bool active)
  96. {
  97. // Check if the specific fixture we defined in the prototype exists
  98. if (!fixtures.Fixtures.TryGetValue("gracewall", out var fixture))
  99. {
  100. Log.Warning($"Gracewall entity {ToPrettyString(uid)} is missing the 'gracewall' fixture!");
  101. return;
  102. }
  103. // Modify the fixture's collision properties
  104. _physics.SetCollisionLayer(uid, "gracewall", fixture, active ? GraceWallCollisionGroup : (int)CollisionGroup.None);
  105. // Ensure the change takes effect immediately
  106. if (TryComp<PhysicsComponent>(uid, out var physics))
  107. _physics.WakeBody(uid, body: physics);
  108. }
  109. private void OnStartCollide(EntityUid uid, GracewallAreaComponent component, ref StartCollideEvent args)
  110. {
  111. // This event is kept minimal to reduce lag
  112. if (!component.GracewallActive)
  113. return;
  114. }
  115. private bool CheckPassable(EntityUid entityTryingToPass, GracewallAreaComponent component)
  116. {
  117. // Check cache first
  118. if (_passabilityCache.TryGetValue(entityTryingToPass, out var result))
  119. return result;
  120. // Original logic
  121. if (TryComp<NpcFactionMemberComponent>(entityTryingToPass, out var factions))
  122. {
  123. foreach (var faction in component.BlockingFactions)
  124. {
  125. if (faction == "All")
  126. {
  127. _passabilityCache[entityTryingToPass] = true;
  128. return true;
  129. }
  130. foreach (var member in factions.Factions)
  131. {
  132. if (member.ToString() == faction)
  133. {
  134. _passabilityCache[entityTryingToPass] = true;
  135. return true;
  136. }
  137. }
  138. }
  139. }
  140. _passabilityCache[entityTryingToPass] = false;
  141. return false;
  142. }
  143. private void OnPreventCollide(EntityUid uid, GracewallAreaComponent component, ref PreventCollideEvent args)
  144. {
  145. // Only handle collisions when the wall is active
  146. if (!component.GracewallActive)
  147. return;
  148. // Get the entity trying to pass through
  149. var otherEntity = args.OtherEntity;
  150. // Skip processing for entities we've already determined can pass
  151. if (_passabilityCache.TryGetValue(otherEntity, out var canPass) && canPass)
  152. return;
  153. // Check if the entity trying to pass through should be blocked
  154. if (!CheckPassable(otherEntity, component))
  155. {
  156. args.Cancelled = true;
  157. }
  158. }
  159. }