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