From b2d6b2e9ba1ffc78103519aaff8460311da05a6b Mon Sep 17 00:00:00 2001 From: EmoGarbage404 Date: Mon, 16 Mar 2026 15:30:05 -0400 Subject: [PATCH 1/7] Move veteran prototype into alphabetic order --- Resources/Prototypes/_ES/Masks/crew.yml | 28 ++++++++++++------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Resources/Prototypes/_ES/Masks/crew.yml b/Resources/Prototypes/_ES/Masks/crew.yml index c4d3b4e7e9..b3b66bf6a1 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,20 @@ 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: ESVIP name: es-mask-vip-name From 0b15f9fe59700489a6422e037003dc323d5e65cc Mon Sep 17 00:00:00 2001 From: EmoGarbage404 Date: Mon, 16 Mar 2026 15:40:36 -0400 Subject: [PATCH 2/7] Add vigilante prototype --- Resources/Locale/en-US/_ES/masks/masks.ftl | 3 +++ Resources/Prototypes/_ES/Masks/crew.yml | 14 ++++++++++++++ 2 files changed, 17 insertions(+) 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/Masks/crew.yml b/Resources/Prototypes/_ES/Masks/crew.yml index b3b66bf6a1..08ebf6f0f6 100644 --- a/Resources/Prototypes/_ES/Masks/crew.yml +++ b/Resources/Prototypes/_ES/Masks/crew.yml @@ -157,6 +157,20 @@ 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: ESObjectiveKillNonCrew + mindComponents: + - type: ESMaskCacheSpawner + cacheProto: + id: ESCrateCacheCrewVeteran + - type: esMask id: ESVIP name: es-mask-vip-name From 44ee5ae06320b935e5a5849ff6ec344eeb2dd430 Mon Sep 17 00:00:00 2001 From: EmoGarbage404 Date: Mon, 16 Mar 2026 15:51:58 -0400 Subject: [PATCH 3/7] Add vigilante objective prototype --- Resources/Prototypes/_ES/Masks/crew.yml | 2 +- .../Prototypes/_ES/Objectives/Crew/vigilante.yml | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 Resources/Prototypes/_ES/Objectives/Crew/vigilante.yml diff --git a/Resources/Prototypes/_ES/Masks/crew.yml b/Resources/Prototypes/_ES/Masks/crew.yml index 08ebf6f0f6..1fadacabe3 100644 --- a/Resources/Prototypes/_ES/Masks/crew.yml +++ b/Resources/Prototypes/_ES/Masks/crew.yml @@ -165,7 +165,7 @@ color: orchid weight: 1 objectives: - id: ESObjectiveKillNonCrew + id: ESObjectiveVigilanteKillKiller mindComponents: - type: ESMaskCacheSpawner cacheProto: diff --git a/Resources/Prototypes/_ES/Objectives/Crew/vigilante.yml b/Resources/Prototypes/_ES/Objectives/Crew/vigilante.yml new file mode 100644 index 0000000000..bfa71ccd02 --- /dev/null +++ b/Resources/Prototypes/_ES/Objectives/Crew/vigilante.yml @@ -0,0 +1,11 @@ +- 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: ESCounterObjective From aa2c6d0c3d70b668ac7d5acaf500cfd1117ba8c6 Mon Sep 17 00:00:00 2001 From: EmoGarbage404 Date: Mon, 16 Mar 2026 16:22:40 -0400 Subject: [PATCH 4/7] Add functionality for "kill a killer" objective --- .../ESKillKillerObjectiveComponent.cs | 10 +++++ .../Vigilante/ESKillKillerObjectiveSystem.cs | 37 +++++++++++++++++++ .../Components/ESKillTrackerComponent.cs | 2 +- .../Components/ESKillerTrackerComponent.cs | 15 ++++++++ .../_ES/KillTracking/ESKillTrackingSystem.cs | 22 +++++++++-- .../_ES/Objectives/Crew/vigilante.yml | 1 + 6 files changed, 83 insertions(+), 4 deletions(-) create mode 100644 Content.Server/_ES/Masks/Vigilante/Components/ESKillKillerObjectiveComponent.cs create mode 100644 Content.Server/_ES/Masks/Vigilante/ESKillKillerObjectiveSystem.cs create mode 100644 Content.Shared/_ES/KillTracking/Components/ESKillerTrackerComponent.cs 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/Prototypes/_ES/Objectives/Crew/vigilante.yml b/Resources/Prototypes/_ES/Objectives/Crew/vigilante.yml index bfa71ccd02..a131af8b21 100644 --- a/Resources/Prototypes/_ES/Objectives/Crew/vigilante.yml +++ b/Resources/Prototypes/_ES/Objectives/Crew/vigilante.yml @@ -8,4 +8,5 @@ icon: sprite: _ES/Objects/Weapons/Guns/357.rsi state: icon + - type: ESKillKillerObjective - type: ESCounterObjective From 4245686e2772c31ec977079ba79ee2587fff7fc5 Mon Sep 17 00:00:00 2001 From: EmoGarbage404 Date: Mon, 16 Mar 2026 16:30:27 -0400 Subject: [PATCH 5/7] Update KillTroupeObjective to use new event --- .../Objectives/ESKillTroupeObjectiveSystem.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) 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); } } From f980fb38102c156488608a8289a59aec673ae039 Mon Sep 17 00:00:00 2001 From: EmoGarbage404 Date: Mon, 16 Mar 2026 16:34:11 -0400 Subject: [PATCH 6/7] Give vigilante a unique cache --- .../_ES/Catalog/Fills/Crates/caches.yml | 22 +++++++++++++++++++ Resources/Prototypes/_ES/Masks/crew.yml | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) 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/Masks/crew.yml b/Resources/Prototypes/_ES/Masks/crew.yml index 1fadacabe3..53c594076d 100644 --- a/Resources/Prototypes/_ES/Masks/crew.yml +++ b/Resources/Prototypes/_ES/Masks/crew.yml @@ -169,7 +169,7 @@ mindComponents: - type: ESMaskCacheSpawner cacheProto: - id: ESCrateCacheCrewVeteran + id: ESCrateCacheCrewVigilante - type: esMask id: ESVIP From 807cbd4bd857a3ec362992f524a0a5faab29e993 Mon Sep 17 00:00:00 2001 From: EmoGarbage404 Date: Mon, 16 Mar 2026 16:45:48 -0400 Subject: [PATCH 7/7] Add vigilante to gunners --- Resources/Prototypes/_ES/MaskSets/gunners.yml | 1 + 1 file changed, 1 insertion(+) 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