diff --git a/Content.Server/_ES/Masks/Objectives/ESKillTroupeObjectiveSystem.cs b/Content.Server/_ES/Masks/Objectives/ESKillTroupeObjectiveSystem.cs index ac08602440..5cb64e00db 100644 --- a/Content.Server/_ES/Masks/Objectives/ESKillTroupeObjectiveSystem.cs +++ b/Content.Server/_ES/Masks/Objectives/ESKillTroupeObjectiveSystem.cs @@ -1,4 +1,5 @@ using Content.Server._ES.Masks.Objectives.Components; +using Content.Server._ES.Masks.Objectives.Relays.Components; using Content.Shared._ES.KillTracking.Components; using Content.Shared._ES.Objectives; @@ -10,26 +11,25 @@ namespace Content.Server._ES.Masks.Objectives; /// public sealed class ESKillTroupeObjectiveSystem : ESBaseObjectiveSystem { + public override Type[] RelayComponents => [typeof(ESKilledRelayComponent)]; + /// public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnKillReported); + SubscribeLocalEvent(OnKill); } - private void OnKillReported(ref ESPlayerKilledEvent args) + private void OnKill(Entity ent, ref ESKilledPlayerEvent args) { - if (!args.ValidKill || !MindSys.TryGetMind(args.Killer.Value, out var mind)) + if (!args.ValidKill) return; - foreach (var objective in ObjectivesSys.GetObjectives(mind.Value.Owner)) - { - if (!MaskSys.TryGetTroupe(args.Killed, out var troupe)) - return; + if (!MaskSys.TryGetTroupe(args.Killed, out var troupe)) + return; - if ((troupe == objective.Comp.Troupe) ^ objective.Comp.Invert) - ObjectivesSys.AdjustObjectiveCounter(objective.Owner); - } + if ((troupe == ent.Comp.Troupe) ^ ent.Comp.Invert) + ObjectivesSys.AdjustObjectiveCounter(ent.Owner); } } diff --git a/Content.Server/_ES/Masks/Vigilante/Components/ESKillKillerObjectiveComponent.cs b/Content.Server/_ES/Masks/Vigilante/Components/ESKillKillerObjectiveComponent.cs new file mode 100644 index 0000000000..31105220db --- /dev/null +++ b/Content.Server/_ES/Masks/Vigilante/Components/ESKillKillerObjectiveComponent.cs @@ -0,0 +1,10 @@ +using Content.Shared._ES.Objectives.Components; + +namespace Content.Server._ES.Masks.Vigilante.Components; + +/// +/// Objective that works with to add progress each time a "killer" is killed. +/// +[RegisterComponent] +[Access(typeof(ESKillKillerObjectiveSystem))] +public sealed partial class ESKillKillerObjectiveComponent : Component; diff --git a/Content.Server/_ES/Masks/Vigilante/ESKillKillerObjectiveSystem.cs b/Content.Server/_ES/Masks/Vigilante/ESKillKillerObjectiveSystem.cs new file mode 100644 index 0000000000..093ec71f21 --- /dev/null +++ b/Content.Server/_ES/Masks/Vigilante/ESKillKillerObjectiveSystem.cs @@ -0,0 +1,37 @@ +using Content.Server._ES.Masks.Objectives.Relays.Components; +using Content.Server._ES.Masks.Vigilante.Components; +using Content.Shared._ES.KillTracking; +using Content.Shared._ES.KillTracking.Components; +using Content.Shared._ES.Objectives; + +namespace Content.Server._ES.Masks.Vigilante; + +public sealed class ESKillKillerObjectiveSystem : ESBaseObjectiveSystem +{ + [Dependency] private readonly ESKillTrackingSystem _killTracking = default!; + + public override Type[] RelayComponents => [typeof(ESKilledRelayComponent)]; + + /// + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnKill); + } + + private void OnKill(Entity ent, ref ESKilledPlayerEvent args) + { + // Suicides don't count and i'll enumerate why because it's funny: + // If you can suicide to get the kill, then the best strategy is to murder someone at complete random + // If you randomly get a killer. Great! If not, you can just suicide at any point during the round, + // and it will count as a valid completion. + // + // It's fun to think about, though. + if (args.Suicide) + return; + + if (_killTracking.GetPlayerKillCount(args.Killed) > 0) + ObjectivesSys.AdjustObjectiveCounter(ent.Owner); + } +} diff --git a/Content.Shared/_ES/KillTracking/Components/ESKillTrackerComponent.cs b/Content.Shared/_ES/KillTracking/Components/ESKillTrackerComponent.cs index c10d3bba7a..9b0374ed0f 100644 --- a/Content.Shared/_ES/KillTracking/Components/ESKillTrackerComponent.cs +++ b/Content.Shared/_ES/KillTracking/Components/ESKillTrackerComponent.cs @@ -56,7 +56,7 @@ public readonly struct ESPlayerKilledEvent(EntityUid killed, EntityUid? killer) } /// -/// Event raised on an entity when they kill and entity with . +/// Event raised on an entity when they kill an entity with . /// [ByRefEvent] public readonly struct ESKilledPlayerEvent(EntityUid killed, EntityUid killer) diff --git a/Content.Shared/_ES/KillTracking/Components/ESKillerTrackerComponent.cs b/Content.Shared/_ES/KillTracking/Components/ESKillerTrackerComponent.cs new file mode 100644 index 0000000000..a133f67f76 --- /dev/null +++ b/Content.Shared/_ES/KillTracking/Components/ESKillerTrackerComponent.cs @@ -0,0 +1,15 @@ +namespace Content.Shared._ES.KillTracking.Components; + +/// +/// Holds info about the entities with killed by this entity. +/// +[RegisterComponent] +[Access(typeof(ESKillTrackingSystem))] +public sealed partial class ESKillerTrackerComponent : Component +{ + /// + /// Number of players this entity has killed. + /// + [DataField] + public int KilledPlayerCount; +} diff --git a/Content.Shared/_ES/KillTracking/ESKillTrackingSystem.cs b/Content.Shared/_ES/KillTracking/ESKillTrackingSystem.cs index 0299061726..a50f5ee7b7 100644 --- a/Content.Shared/_ES/KillTracking/ESKillTrackingSystem.cs +++ b/Content.Shared/_ES/KillTracking/ESKillTrackingSystem.cs @@ -5,6 +5,7 @@ using Content.Shared.Damage.Systems; using Content.Shared.Destructible; using Content.Shared.FixedPoint; +using Content.Shared.Humanoid; using Content.Shared.Interaction.Events; using Content.Shared.Mobs; using Content.Shared.Mobs.Components; @@ -95,10 +96,17 @@ private void RaiseKillEvent(Entity ent) var ev = new ESPlayerKilledEvent(ent, killer); RaiseLocalEvent(ent, ref ev, true); - if (killer.HasValue) + if (!killer.HasValue) + return; + + var killerEv = new ESKilledPlayerEvent(ent, killer.Value); + RaiseLocalEvent(killer.Value, ref killerEv); + + // Only increment the player kill tracker if it was like a real player + if (HasComp(ent)) { - var killerEv = new ESKilledPlayerEvent(ent, killer.Value); - RaiseLocalEvent(killer.Value, ref killerEv); + var comp = EnsureComp(killer.Value); + ++comp.KilledPlayerCount; } } @@ -172,4 +180,12 @@ public List GetOrderedSources(Entity en .ThenByDescending(s => s.AccumulatedDamage) // Within those groups, go from most damage to least damage. .ToList(); } + + public int GetPlayerKillCount(Entity ent) + { + if (!Resolve(ent, ref ent.Comp, false)) + return 0; + + return ent.Comp.KilledPlayerCount; + } } diff --git a/Resources/Locale/en-US/_ES/masks/masks.ftl b/Resources/Locale/en-US/_ES/masks/masks.ftl index 3de1dc8734..d3b73d8568 100644 --- a/Resources/Locale/en-US/_ES/masks/masks.ftl +++ b/Resources/Locale/en-US/_ES/masks/masks.ftl @@ -44,6 +44,9 @@ es-mask-vandal-desc = As a Vandal, sabotage random machines around the station f es-mask-veteran-name = Veteran es-mask-veteran-desc = As a Veteran, help the station by using your trusty sidearm to take care of anybody who isn't aligned with the crew. +es-mask-vigilante-name = Vigilante +es-mask-vigilante-desc = As a Vigilante, seek out those who have killed others and bring them to justice by killing them yourself. + es-mask-vip-name = VIP es-mask-vip-desc = As a VIP, use your fancy VIP card to help confirm your innocence in times of peril. diff --git a/Resources/Prototypes/_ES/Catalog/Fills/Crates/caches.yml b/Resources/Prototypes/_ES/Catalog/Fills/Crates/caches.yml index a15fb0f22e..02e7f99837 100644 --- a/Resources/Prototypes/_ES/Catalog/Fills/Crates/caches.yml +++ b/Resources/Prototypes/_ES/Catalog/Fills/Crates/caches.yml @@ -133,6 +133,28 @@ weight: 2 - id: ESWeaponPistol9mmSilenced +# Vigilante +- type: entity + parent: ESCrateCache + id: ESCrateCacheCrewVigilante + suffix: ES, Vigilante Cache + components: + - type: EntityTableContainerFill + containers: + entity_storage: + tableId: ESCrewCacheVigilante + +- type: entityTable + id: ESCrewCacheVigilante + table: + group: + - all: + - id: ESWeaponRevolver357 + - group: + - id: ESWeaponPistol9mm + weight: 2 + - id: ESWeaponPistol9mmSilenced + # Survivalist - type: entity parent: ESCrateCache diff --git a/Resources/Prototypes/_ES/MaskSets/gunners.yml b/Resources/Prototypes/_ES/MaskSets/gunners.yml index 8bea1c826a..60dcda5148 100644 --- a/Resources/Prototypes/_ES/MaskSets/gunners.yml +++ b/Resources/Prototypes/_ES/MaskSets/gunners.yml @@ -4,3 +4,4 @@ ESArmsDealer: 0.3 ESMercenary: 1 ESVeteran: 1 + ESVigilante: 1 diff --git a/Resources/Prototypes/_ES/Masks/crew.yml b/Resources/Prototypes/_ES/Masks/crew.yml index c4d3b4e7e9..53c594076d 100644 --- a/Resources/Prototypes/_ES/Masks/crew.yml +++ b/Resources/Prototypes/_ES/Masks/crew.yml @@ -123,20 +123,6 @@ all: - id: ESObjectiveSurvive -- type: esMask - id: ESVeteran - name: es-mask-veteran-name - troupe: ESCrew - description: es-mask-veteran-desc - color: ruber - weight: 1 - objectives: - id: ESObjectiveKillNonCrew - mindComponents: - - type: ESMaskCacheSpawner - cacheProto: - id: ESCrateCacheCrewVeteran - - type: esMask parent: ESBaseCommandRestricted id: ESVandal @@ -157,6 +143,34 @@ weight: 2 amount: 1, 2 +- type: esMask + id: ESVeteran + name: es-mask-veteran-name + troupe: ESCrew + description: es-mask-veteran-desc + color: ruber + weight: 1 + objectives: + id: ESObjectiveKillNonCrew + mindComponents: + - type: ESMaskCacheSpawner + cacheProto: + id: ESCrateCacheCrewVeteran + +- type: esMask + id: ESVigilante + name: es-mask-vigilante-name + troupe: ESCrew + description: es-mask-vigilante-desc + color: orchid + weight: 1 + objectives: + id: ESObjectiveVigilanteKillKiller + mindComponents: + - type: ESMaskCacheSpawner + cacheProto: + id: ESCrateCacheCrewVigilante + - type: esMask id: ESVIP name: es-mask-vip-name diff --git a/Resources/Prototypes/_ES/Objectives/Crew/vigilante.yml b/Resources/Prototypes/_ES/Objectives/Crew/vigilante.yml new file mode 100644 index 0000000000..a131af8b21 --- /dev/null +++ b/Resources/Prototypes/_ES/Objectives/Crew/vigilante.yml @@ -0,0 +1,12 @@ +- type: entity + parent: ESBaseMaskObjective + id: ESObjectiveVigilanteKillKiller + name: Kill a killer + description: Find someone who has taken the life of another and exact justice. + components: + - type: ESObjective + icon: + sprite: _ES/Objects/Weapons/Guns/357.rsi + state: icon + - type: ESKillKillerObjective + - type: ESCounterObjective