KillTrackingSystem.cs 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. using Content.Server.NPC.HTN;
  2. using Content.Shared.Damage;
  3. using Content.Shared.FixedPoint;
  4. using Content.Shared.Mobs;
  5. using Content.Shared.Mobs.Systems;
  6. using Robust.Shared.Player;
  7. namespace Content.Server.KillTracking;
  8. /// <summary>
  9. /// This handles <see cref="KillTrackerComponent"/> and recording who is damaging and killing entities.
  10. /// </summary>
  11. public sealed class KillTrackingSystem : EntitySystem
  12. {
  13. /// <inheritdoc/>
  14. public override void Initialize()
  15. {
  16. // Add damage to LifetimeDamage before MobStateChangedEvent gets raised
  17. SubscribeLocalEvent<KillTrackerComponent, DamageChangedEvent>(OnDamageChanged, before: [ typeof(MobThresholdSystem) ]);
  18. SubscribeLocalEvent<KillTrackerComponent, MobStateChangedEvent>(OnMobStateChanged);
  19. }
  20. private void OnDamageChanged(EntityUid uid, KillTrackerComponent component, DamageChangedEvent args)
  21. {
  22. if (args.DamageDelta == null)
  23. return;
  24. if (!args.DamageIncreased)
  25. {
  26. foreach (var key in component.LifetimeDamage.Keys)
  27. {
  28. component.LifetimeDamage[key] -= args.DamageDelta.GetTotal();
  29. }
  30. return;
  31. }
  32. var source = GetKillSource(args.Origin);
  33. var damage = component.LifetimeDamage.GetValueOrDefault(source);
  34. component.LifetimeDamage[source] = damage + args.DamageDelta.GetTotal();
  35. }
  36. private void OnMobStateChanged(EntityUid uid, KillTrackerComponent component, MobStateChangedEvent args)
  37. {
  38. if (args.NewMobState != component.KillState || args.OldMobState >= args.NewMobState)
  39. return;
  40. // impulse is the entity that did the finishing blow.
  41. var killImpulse = GetKillSource(args.Origin);
  42. // source is the kill tracker source with the most damage dealt.
  43. var largestSource = GetLargestSource(component.LifetimeDamage);
  44. largestSource ??= killImpulse;
  45. KillSource killSource;
  46. KillSource? assistSource = null;
  47. if (killImpulse is KillEnvironmentSource)
  48. {
  49. // if the kill was environmental, whatever did the most damage gets the kill.
  50. killSource = largestSource;
  51. }
  52. else if (killImpulse == largestSource)
  53. {
  54. // if the impulse and the source are the same, there's no assist
  55. killSource = killImpulse;
  56. }
  57. else
  58. {
  59. // the impulse gets the kill and the most damage gets the assist
  60. killSource = killImpulse;
  61. // no assist is given to environmental kills
  62. if (largestSource is not KillEnvironmentSource
  63. && component.LifetimeDamage.TryGetValue(largestSource, out var largestDamage))
  64. {
  65. var killDamage = component.LifetimeDamage.GetValueOrDefault(killSource);
  66. // you have to do at least twice as much damage as the killing source to get the assist.
  67. if (largestDamage >= killDamage / 2)
  68. assistSource = largestSource;
  69. }
  70. }
  71. // it's a suicide if:
  72. // - you caused your own death
  73. // - the kill source was the entity that died
  74. // - the entity that died had an assist on themselves
  75. var suicide = args.Origin == uid ||
  76. killSource is KillNpcSource npc && npc.NpcEnt == uid ||
  77. killSource is KillPlayerSource player && player.PlayerId == CompOrNull<ActorComponent>(uid)?.PlayerSession.UserId ||
  78. assistSource is KillNpcSource assistNpc && assistNpc.NpcEnt == uid ||
  79. assistSource is KillPlayerSource assistPlayer && assistPlayer.PlayerId == CompOrNull<ActorComponent>(uid)?.PlayerSession.UserId;
  80. var ev = new KillReportedEvent(uid, killSource, assistSource, suicide);
  81. RaiseLocalEvent(uid, ref ev, true);
  82. }
  83. private KillSource GetKillSource(EntityUid? sourceEntity)
  84. {
  85. if (TryComp<ActorComponent>(sourceEntity, out var actor))
  86. return new KillPlayerSource(actor.PlayerSession.UserId);
  87. if (HasComp<HTNComponent>(sourceEntity))
  88. return new KillNpcSource(sourceEntity.Value);
  89. return new KillEnvironmentSource();
  90. }
  91. private KillSource? GetLargestSource(Dictionary<KillSource, FixedPoint2> lifetimeDamages)
  92. {
  93. KillSource? maxSource = null;
  94. var maxDamage = FixedPoint2.Zero;
  95. foreach (var (source, damage) in lifetimeDamages)
  96. {
  97. if (damage < maxDamage)
  98. continue;
  99. maxSource = source;
  100. maxDamage = damage;
  101. }
  102. return maxSource;
  103. }
  104. }
  105. /// <summary>
  106. /// Event broadcasted and raised by-ref on an entity with <see cref="KillTrackerComponent"/> when they are killed.
  107. /// </summary>
  108. /// <param name="Entity">The entity that was killed</param>
  109. /// <param name="Primary">The primary source of the kill</param>
  110. /// <param name="Assist">A secondary source of the kill. Can be null.</param>
  111. /// <param name="Suicide">True if the entity that was killed caused their own death.</param>
  112. [ByRefEvent]
  113. public readonly record struct KillReportedEvent(EntityUid Entity, KillSource Primary, KillSource? Assist, bool Suicide);