ProjectileSystem.cs 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. using Content.Server.Administration.Logs;
  2. using Content.Server.Destructible;
  3. using Content.Server.Effects;
  4. using Content.Server.Weapons.Ranged.Systems;
  5. using Content.Shared.Camera;
  6. using Content.Shared.Damage;
  7. using Content.Shared.Database;
  8. using Content.Shared.FixedPoint;
  9. using Content.Shared.Projectiles;
  10. using Robust.Shared.Physics.Events;
  11. using Robust.Shared.Player;
  12. namespace Content.Server.Projectiles;
  13. public sealed class ProjectileSystem : SharedProjectileSystem
  14. {
  15. [Dependency] private readonly IAdminLogManager _adminLogger = default!;
  16. [Dependency] private readonly ColorFlashEffectSystem _color = default!;
  17. [Dependency] private readonly DamageableSystem _damageableSystem = default!;
  18. [Dependency] private readonly DestructibleSystem _destructibleSystem = default!;
  19. [Dependency] private readonly GunSystem _guns = default!;
  20. [Dependency] private readonly SharedCameraRecoilSystem _sharedCameraRecoil = default!;
  21. public override void Initialize()
  22. {
  23. base.Initialize();
  24. SubscribeLocalEvent<ProjectileComponent, StartCollideEvent>(OnStartCollide);
  25. }
  26. private void OnStartCollide(EntityUid uid, ProjectileComponent component, ref StartCollideEvent args)
  27. {
  28. // This is so entities that shouldn't get a collision are ignored.
  29. if (args.OurFixtureId != ProjectileFixture || !args.OtherFixture.Hard
  30. || component.ProjectileSpent || component is { Weapon: null, OnlyCollideWhenShot: true })
  31. return;
  32. var target = args.OtherEntity;
  33. // it's here so this check is only done once before possible hit
  34. var attemptEv = new ProjectileReflectAttemptEvent(uid, component, false);
  35. RaiseLocalEvent(target, ref attemptEv);
  36. if (attemptEv.Cancelled)
  37. {
  38. SetShooter(uid, component, target);
  39. return;
  40. }
  41. var ev = new ProjectileHitEvent(component.Damage * _damageableSystem.UniversalProjectileDamageModifier, target, component.Shooter);
  42. RaiseLocalEvent(uid, ref ev);
  43. var otherName = ToPrettyString(target);
  44. var damageRequired = _destructibleSystem.DestroyedAt(target);
  45. if (TryComp<DamageableComponent>(target, out var damageableComponent))
  46. {
  47. damageRequired -= damageableComponent.TotalDamage;
  48. damageRequired = FixedPoint2.Max(damageRequired, FixedPoint2.Zero);
  49. }
  50. var modifiedDamage = _damageableSystem.TryChangeDamage(target, ev.Damage, component.IgnoreResistances, damageable: damageableComponent, origin: component.Shooter);
  51. var deleted = Deleted(target);
  52. if (modifiedDamage is not null && EntityManager.EntityExists(component.Shooter))
  53. {
  54. if (modifiedDamage.AnyPositive() && !deleted)
  55. {
  56. _color.RaiseEffect(Color.Red, new List<EntityUid> { target }, Filter.Pvs(target, entityManager: EntityManager));
  57. }
  58. _adminLogger.Add(LogType.BulletHit,
  59. HasComp<ActorComponent>(target) ? LogImpact.Extreme : LogImpact.High,
  60. $"Projectile {ToPrettyString(uid):projectile} shot by {ToPrettyString(component.Shooter!.Value):user} hit {otherName:target} and dealt {modifiedDamage.GetTotal():damage} damage");
  61. }
  62. // If penetration is to be considered, we need to do some checks to see if the projectile should stop.
  63. if (modifiedDamage is not null && component.PenetrationThreshold != 0)
  64. {
  65. // If a damage type is required, stop the bullet if the hit entity doesn't have that type.
  66. if (component.PenetrationDamageTypeRequirement != null)
  67. {
  68. var stopPenetration = false;
  69. foreach (var requiredDamageType in component.PenetrationDamageTypeRequirement)
  70. {
  71. if (!modifiedDamage.DamageDict.Keys.Contains(requiredDamageType))
  72. {
  73. stopPenetration = true;
  74. break;
  75. }
  76. }
  77. if (stopPenetration)
  78. component.ProjectileSpent = true;
  79. }
  80. // If the object won't be destroyed, it "tanks" the penetration hit.
  81. if (modifiedDamage.GetTotal() < damageRequired)
  82. {
  83. component.ProjectileSpent = true;
  84. }
  85. if (!component.ProjectileSpent)
  86. {
  87. component.PenetrationAmount += damageRequired;
  88. // The projectile has dealt enough damage to be spent.
  89. if (component.PenetrationAmount >= component.PenetrationThreshold)
  90. {
  91. component.ProjectileSpent = true;
  92. }
  93. }
  94. }
  95. else
  96. {
  97. component.ProjectileSpent = true;
  98. }
  99. if (!deleted)
  100. {
  101. _guns.PlayImpactSound(target, modifiedDamage, component.SoundHit, component.ForceSound);
  102. if (!args.OurBody.LinearVelocity.IsLengthZero())
  103. _sharedCameraRecoil.KickCamera(target, args.OurBody.LinearVelocity.Normalized());
  104. }
  105. if (component.DeleteOnCollide && component.ProjectileSpent)
  106. QueueDel(uid);
  107. if (component.ImpactEffect != null && TryComp(uid, out TransformComponent? xform))
  108. {
  109. RaiseNetworkEvent(new ImpactEffectEvent(component.ImpactEffect, GetNetCoordinates(xform.Coordinates)), Filter.Pvs(xform.Coordinates, entityMan: EntityManager));
  110. }
  111. }
  112. }