GracewallRuleSystem.cs 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  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. namespace Content.Server.GameTicking.Rules;
  12. public sealed class GracewallRuleSystem : GameRuleSystem<GracewallRuleComponent>
  13. {
  14. [Dependency] private readonly SharedPhysicsSystem _physics = default!;
  15. // Define a specific collision group for the grace wall.
  16. // Make sure this group is defined in CollisionGroups.yml and NPCs are set to not collide with it.
  17. [Dependency] private readonly ChatSystem _chat = default!;
  18. private const int GraceWallCollisionGroup = (int)CollisionGroup.MidImpassable; // Example: Use MidImpassable or define a custom one
  19. private FixturesComponent? _fixtures;
  20. public override void Initialize()
  21. {
  22. base.Initialize();
  23. SubscribeLocalEvent<GracewallAreaComponent, StartCollideEvent>(OnStartCollide);
  24. // Potentially subscribe to PreventCollideEvent if more fine-grained control is needed
  25. }
  26. protected override void Started(EntityUid uid, GracewallRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
  27. {
  28. base.Started(uid, component, gameRule, args);
  29. component.Timer = (float)component.GracewallDuration.TotalSeconds;
  30. component.GracewallActive = true;
  31. // Schedule the announcement for 15 seconds later
  32. var announcementMessage = $"The grace wall is up for {component.GracewallDuration.TotalMinutes} minutes!";
  33. Timer.Spawn(TimeSpan.FromSeconds(15), () =>
  34. {
  35. _chat.DispatchGlobalAnnouncement(announcementMessage, "Round", false, null, Color.Yellow);
  36. });
  37. Log.Info($"Grace wall active for {component.GracewallDuration.TotalMinutes} minutes.");
  38. // Activate all grace wall areas
  39. var query = EntityQueryEnumerator<GracewallAreaComponent, TransformComponent, FixturesComponent>();
  40. while (query.MoveNext(out var wallUid, out var area, out var xform, out var fixtures))
  41. {
  42. area.GracewallActive = true;
  43. UpdateGracewallPhysics(wallUid, area, fixtures, true);
  44. }
  45. }
  46. protected override void Ended(EntityUid uid, GracewallRuleComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args)
  47. {
  48. base.Ended(uid, component, gameRule, args);
  49. // Ensure walls are deactivated if the rule ends unexpectedly
  50. DeactivateAllGraceWalls(component);
  51. _chat.DispatchGlobalAnnouncement("The grace wall is now down!", "Round", false, null, Color.Yellow);
  52. }
  53. public override void Update(float frameTime)
  54. {
  55. base.Update(frameTime);
  56. var query = EntityQueryEnumerator<GracewallRuleComponent, GameRuleComponent>();
  57. while (query.MoveNext(out var uid, out var gracewall, out var gameRule))
  58. {
  59. if (!GameTicker.IsGameRuleActive(uid, gameRule) || !gracewall.GracewallActive)
  60. continue;
  61. gracewall.Timer -= frameTime;
  62. if (gracewall.Timer <= 0)
  63. {
  64. Log.Info("Grace wall duration ended.");
  65. DeactivateAllGraceWalls(gracewall);
  66. _chat.DispatchGlobalAnnouncement("The grace wall is now down!", "Round", false, null, Color.Yellow);
  67. }
  68. }
  69. }
  70. private void DeactivateAllGraceWalls(GracewallRuleComponent component)
  71. {
  72. component.GracewallActive = false;
  73. // Deactivate all grace wall areas
  74. var query = EntityQueryEnumerator<GracewallAreaComponent, FixturesComponent>();
  75. while (query.MoveNext(out var wallUid, out var area, out var fixtures))
  76. {
  77. area.GracewallActive = false;
  78. UpdateGracewallPhysics(wallUid, area, fixtures, false);
  79. }
  80. }
  81. private void UpdateGracewallPhysics(EntityUid uid, GracewallAreaComponent component, FixturesComponent fixtures, bool active)
  82. {
  83. // Check if the specific fixture we defined in the prototype exists
  84. if (!fixtures.Fixtures.TryGetValue("gracewall", out var fixture))
  85. {
  86. Log.Warning($"Gracewall entity {ToPrettyString(uid)} is missing the 'gracewall' fixture!");
  87. // Attempt to create it dynamically if missing? Or just rely on the prototype.
  88. // For now, we'll just log a warning. If it's consistently missing, the prototype needs fixing.
  89. return;
  90. }
  91. // Modify the fixture's collision properties
  92. // This assumes NPCs have a mask that *doesn't* include GraceWallCollisionGroup
  93. _physics.SetCollisionLayer(uid, "gracewall", fixture, active ? GraceWallCollisionGroup : (int)CollisionGroup.None);
  94. // Setting the layer might require refreshing contacts or waking the body if it's asleep
  95. // to ensure the change takes effect immediately.
  96. if (TryComp<PhysicsComponent>(uid, out var physics))
  97. _physics.WakeBody(uid, body: physics);
  98. // Ensure the radius is correct (it might change dynamically later)
  99. // This requires getting the specific fixture and modifying its shape.
  100. // For simplicity, we assume the prototype radius is sufficient for now.
  101. // If dynamic radius is needed, we'd need more complex logic here.
  102. }
  103. private void OnStartCollide(EntityUid uid, GracewallAreaComponent component, ref StartCollideEvent args)
  104. {
  105. // This event triggers when something *starts* colliding with the grace wall fixture.
  106. // We only care when the wall is active.
  107. if (!component.GracewallActive)
  108. return;
  109. // Check if the other entity is an NPC
  110. var otherUid = args.OtherEntity;
  111. if (HasComp<NpcFactionMemberComponent>(otherUid))
  112. {
  113. // The collision system *should* prevent entry based on layers/masks.
  114. // However, if an NPC somehow starts colliding (e.g., spawned inside, teleported),
  115. // we could potentially push them out here.
  116. // For now, we rely on the collision group preventing entry.
  117. // Log.Debug($"NPC {ToPrettyString(otherUid)} collided with active grace wall {ToPrettyString(uid)}.");
  118. }
  119. }
  120. }