using Content.Server.NPC.HTN; using Content.Shared.Damage; using Content.Shared.FixedPoint; using Content.Shared.Mobs; using Robust.Shared.Player; namespace Content.Server.KillTracking; /// /// This handles and recording who is damaging and killing entities. /// public sealed class KillTrackingSystem : EntitySystem { /// public override void Initialize() { SubscribeLocalEvent(OnDamageChanged); SubscribeLocalEvent(OnMobStateChanged); } private void OnDamageChanged(EntityUid uid, KillTrackerComponent component, DamageChangedEvent args) { if (args.DamageDelta == null) return; if (!args.DamageIncreased) { foreach (var key in component.LifetimeDamage.Keys) { component.LifetimeDamage[key] -= args.DamageDelta.Total; } return; } var source = GetKillSource(args.Origin); var damage = component.LifetimeDamage.GetValueOrDefault(source); component.LifetimeDamage[source] = damage + args.DamageDelta.Total; } private void OnMobStateChanged(EntityUid uid, KillTrackerComponent component, MobStateChangedEvent args) { if (args.NewMobState != component.KillState || args.OldMobState >= args.NewMobState) return; // impulse is the entity that did the finishing blow. var killImpulse = GetKillSource(args.Origin); // source is the kill tracker source with the most damage dealt. var largestSource = GetLargestSource(component.LifetimeDamage); largestSource ??= killImpulse; KillSource? killSource; KillSource? assistSource = null; if (killImpulse is KillEnvironmentSource) { // if the kill was environmental, whatever did the most damage gets the kill. killSource = largestSource; } else if (killImpulse == largestSource) { // if the impulse and the source are the same, there's no assist killSource = killImpulse; } else { // the impulse gets the kill and the most damage gets the assist killSource = killImpulse; // no assist is given to environmental kills if (largestSource is not KillEnvironmentSource) { // you have to do at least 50% of largest source's damage to get the assist. if (component.LifetimeDamage[largestSource] >= component.LifetimeDamage[killSource] / 2) { assistSource = largestSource; } } } // it's a suicide if: // - you caused your own death // - the kill source was the entity that died // - the entity that died had an assist on themselves var suicide = args.Origin == uid || killSource is KillNpcSource npc && npc.NpcEnt == uid || killSource is KillPlayerSource player && player.PlayerId == CompOrNull(uid)?.PlayerSession.UserId || assistSource is KillNpcSource assistNpc && assistNpc.NpcEnt == uid || assistSource is KillPlayerSource assistPlayer && assistPlayer.PlayerId == CompOrNull(uid)?.PlayerSession.UserId; var ev = new KillReportedEvent(uid, killSource, assistSource, suicide); RaiseLocalEvent(uid, ref ev, true); } private KillSource GetKillSource(EntityUid? sourceEntity) { if (TryComp(sourceEntity, out var actor)) return new KillPlayerSource(actor.PlayerSession.UserId); if (HasComp(sourceEntity)) return new KillNpcSource(sourceEntity.Value); return new KillEnvironmentSource(); } private KillSource? GetLargestSource(Dictionary lifetimeDamages) { KillSource? maxSource = null; var maxDamage = FixedPoint2.Zero; foreach (var (source, damage) in lifetimeDamages) { if (damage < maxDamage) continue; maxSource = source; maxDamage = damage; } return maxSource; } } /// /// Event broadcasted and raised by-ref on an entity with when they are killed. /// /// The entity that was killed /// The primary source of the kill /// A secondary source of the kill. Can be null. /// True if the entity that was killed caused their own death. [ByRefEvent] public readonly record struct KillReportedEvent(EntityUid Entity, KillSource Primary, KillSource? Assist, bool Suicide);