1
0

ScatteringGrenadeSystem.cs 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. using Content.Shared.Explosion.Components;
  2. using Content.Shared.Throwing;
  3. using Robust.Server.GameObjects;
  4. using Robust.Shared.Containers;
  5. using Robust.Shared.Map;
  6. using Robust.Shared.Random;
  7. using System.Numerics;
  8. using Content.Shared.Explosion.EntitySystems;
  9. namespace Content.Server.Explosion.EntitySystems;
  10. public sealed class ScatteringGrenadeSystem : SharedScatteringGrenadeSystem
  11. {
  12. [Dependency] private readonly SharedContainerSystem _container = default!;
  13. [Dependency] private readonly IRobustRandom _random = default!;
  14. [Dependency] private readonly ThrowingSystem _throwingSystem = default!;
  15. [Dependency] private readonly TransformSystem _transformSystem = default!;
  16. public override void Initialize()
  17. {
  18. base.Initialize();
  19. SubscribeLocalEvent<ScatteringGrenadeComponent, TriggerEvent>(OnScatteringTrigger);
  20. }
  21. /// <summary>
  22. /// Can be triggered either by damage or the use in hand timer, either way
  23. /// will store the event happening in IsTriggered for the next frame update rather than
  24. /// handling it here to prevent crashing the game
  25. /// </summary>
  26. private void OnScatteringTrigger(Entity<ScatteringGrenadeComponent> entity, ref TriggerEvent args)
  27. {
  28. entity.Comp.IsTriggered = true;
  29. args.Handled = true;
  30. }
  31. /// <summary>
  32. /// Every frame update we look for scattering grenades that were triggered (by damage or timer)
  33. /// Then we spawn the contents, throw them, optionally trigger them, then delete the original scatter grenade entity
  34. /// </summary>
  35. public override void Update(float frametime)
  36. {
  37. base.Update(frametime);
  38. var query = EntityQueryEnumerator<ScatteringGrenadeComponent>();
  39. while (query.MoveNext(out var uid, out var component))
  40. {
  41. var totalCount = component.Container.ContainedEntities.Count + component.UnspawnedCount;
  42. // if triggered while empty, (if it's blown up while empty) it'll just delete itself
  43. if (component.IsTriggered && totalCount > 0)
  44. {
  45. var grenadeCoord = _transformSystem.GetMapCoordinates(uid);
  46. var thrownCount = 0;
  47. var segmentAngle = 360 / totalCount;
  48. var additionalIntervalDelay = 0f;
  49. while (TrySpawnContents(grenadeCoord, component, out var contentUid))
  50. {
  51. Angle angle;
  52. if (component.RandomAngle)
  53. angle = _random.NextAngle();
  54. else
  55. {
  56. var angleMin = segmentAngle * thrownCount;
  57. var angleMax = segmentAngle * (thrownCount + 1);
  58. angle = Angle.FromDegrees(_random.Next(angleMin, angleMax));
  59. thrownCount++;
  60. }
  61. Vector2 direction = angle.ToVec().Normalized();
  62. if (component.RandomDistance)
  63. direction *= _random.NextFloat(component.RandomThrowDistanceMin, component.RandomThrowDistanceMax);
  64. else
  65. direction *= component.Distance;
  66. _throwingSystem.TryThrow(contentUid, direction, component.Velocity);
  67. if (component.TriggerContents)
  68. {
  69. additionalIntervalDelay += _random.NextFloat(component.IntervalBetweenTriggersMin, component.IntervalBetweenTriggersMax);
  70. var contentTimer = EnsureComp<ActiveTimerTriggerComponent>(contentUid);
  71. contentTimer.TimeRemaining = component.DelayBeforeTriggerContents + additionalIntervalDelay;
  72. var ev = new ActiveTimerTriggerEvent(contentUid, uid);
  73. RaiseLocalEvent(contentUid, ref ev);
  74. }
  75. }
  76. // Normally we'd use DeleteOnTrigger but because we need to wait for the frame update
  77. // we have to delete it here instead
  78. Del(uid);
  79. }
  80. }
  81. }
  82. /// <summary>
  83. /// Spawns one instance of the fill prototype or contained entity at the coordinate indicated
  84. /// </summary>
  85. private bool TrySpawnContents(MapCoordinates spawnCoordinates, ScatteringGrenadeComponent component, out EntityUid contentUid)
  86. {
  87. contentUid = default;
  88. if (component.UnspawnedCount > 0)
  89. {
  90. component.UnspawnedCount--;
  91. contentUid = Spawn(component.FillPrototype, spawnCoordinates);
  92. return true;
  93. }
  94. if (component.Container.ContainedEntities.Count > 0)
  95. {
  96. contentUid = component.Container.ContainedEntities[0];
  97. if (!_container.Remove(contentUid, component.Container))
  98. return false;
  99. return true;
  100. }
  101. return false;
  102. }
  103. }