ProjectileGrenadeSystem.cs 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. using Content.Server.Explosion.Components;
  2. using Content.Server.Weapons.Ranged.Systems;
  3. using Robust.Server.GameObjects;
  4. using Robust.Shared.Containers;
  5. using Robust.Shared.Map;
  6. using Robust.Shared.Random;
  7. namespace Content.Server.Explosion.EntitySystems;
  8. public sealed class ProjectileGrenadeSystem : EntitySystem
  9. {
  10. [Dependency] private readonly GunSystem _gun = default!;
  11. [Dependency] private readonly IRobustRandom _random = default!;
  12. [Dependency] private readonly SharedContainerSystem _container = default!;
  13. [Dependency] private readonly TransformSystem _transformSystem = default!;
  14. public override void Initialize()
  15. {
  16. base.Initialize();
  17. SubscribeLocalEvent<ProjectileGrenadeComponent, ComponentInit>(OnFragInit);
  18. SubscribeLocalEvent<ProjectileGrenadeComponent, ComponentStartup>(OnFragStartup);
  19. SubscribeLocalEvent<ProjectileGrenadeComponent, TriggerEvent>(OnFragTrigger);
  20. }
  21. private void OnFragInit(Entity<ProjectileGrenadeComponent> entity, ref ComponentInit args)
  22. {
  23. entity.Comp.Container = _container.EnsureContainer<Container>(entity.Owner, "cluster-payload");
  24. }
  25. /// <summary>
  26. /// Setting the unspawned count based on capacity so we know how many new entities to spawn
  27. /// </summary>
  28. private void OnFragStartup(Entity<ProjectileGrenadeComponent> entity, ref ComponentStartup args)
  29. {
  30. if (entity.Comp.FillPrototype == null)
  31. return;
  32. entity.Comp.UnspawnedCount = Math.Max(0, entity.Comp.Capacity - entity.Comp.Container.ContainedEntities.Count);
  33. }
  34. /// <summary>
  35. /// Can be triggered either by damage or the use in hand timer
  36. /// </summary>
  37. private void OnFragTrigger(Entity<ProjectileGrenadeComponent> entity, ref TriggerEvent args)
  38. {
  39. FragmentIntoProjectiles(entity.Owner, entity.Comp);
  40. args.Handled = true;
  41. }
  42. /// <summary>
  43. /// Spawns projectiles at the coordinates of the grenade upon triggering
  44. /// Can customize the angle and velocity the projectiles come out at
  45. /// </summary>
  46. private void FragmentIntoProjectiles(EntityUid uid, ProjectileGrenadeComponent component)
  47. {
  48. var grenadeCoord = _transformSystem.GetMapCoordinates(uid);
  49. var shootCount = 0;
  50. var totalCount = component.Container.ContainedEntities.Count + component.UnspawnedCount;
  51. // Check for division by zero
  52. if (totalCount <= 0)
  53. {
  54. Logger.Warning($"ProjectileGrenade {ToPrettyString(uid)} has no projectiles to fragment into");
  55. return;
  56. }
  57. var segmentAngle = 360 / totalCount;
  58. while (TrySpawnContents(grenadeCoord, component, out var contentUid))
  59. {
  60. Angle angle;
  61. if (component.RandomAngle)
  62. angle = _random.NextAngle();
  63. else
  64. {
  65. var angleMin = segmentAngle * shootCount;
  66. var angleMax = segmentAngle * (shootCount + 1);
  67. angle = Angle.FromDegrees(_random.Next(angleMin, angleMax));
  68. shootCount++;
  69. }
  70. // velocity is randomized to make the projectiles look
  71. // slightly uneven, doesn't really change much, but it looks better
  72. var direction = angle.ToVec().Normalized();
  73. var velocity = _random.NextVector2(component.MinVelocity, component.MaxVelocity);
  74. _gun.ShootProjectile(contentUid, direction, velocity, uid, null);
  75. }
  76. }
  77. /// <summary>
  78. /// Spawns one instance of the fill prototype or contained entity at the coordinate indicated
  79. /// </summary>
  80. private bool TrySpawnContents(MapCoordinates spawnCoordinates, ProjectileGrenadeComponent component, out EntityUid contentUid)
  81. {
  82. contentUid = default;
  83. if (component.UnspawnedCount > 0)
  84. {
  85. component.UnspawnedCount--;
  86. contentUid = Spawn(component.FillPrototype, spawnCoordinates);
  87. return true;
  88. }
  89. if (component.Container.ContainedEntities.Count > 0)
  90. {
  91. contentUid = component.Container.ContainedEntities[0];
  92. if (!_container.Remove(contentUid, component.Container))
  93. return false;
  94. return true;
  95. }
  96. return false;
  97. }
  98. }