]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
ninja criminal records hacking (#24982)
authordeltanedas <39013340+deltanedas@users.noreply.github.com>
Thu, 9 May 2024 06:35:11 +0000 (06:35 +0000)
committerGitHub <noreply@github.com>
Thu, 9 May 2024 06:35:11 +0000 (23:35 -0700)
* more humour

* spotted a troll

* add TryFindObjective to MindSystem

* replace copypaste bool conditions with CodeCondition

* use CodeConditionSystem in ninja + add handling for criminal hack

* add criminal records hacking

* update objectives

* :trollface:

---------

Co-authored-by: deltanedas <@deltanedas:kde.org>
20 files changed:
Content.Client/CriminalRecords/Systems/CriminalRecordsHackerSystem.cs [new file with mode: 0644]
Content.Server/CriminalRecords/Systems/CriminalRecordsHackerSystem.cs [new file with mode: 0644]
Content.Server/Ninja/Systems/NinjaGlovesSystem.cs
Content.Server/Ninja/Systems/SpaceNinjaSystem.cs
Content.Server/Ninja/Systems/SpiderChargeSystem.cs
Content.Server/Objectives/Components/CodeConditionSystem.cs [new file with mode: 0644]
Content.Server/Objectives/Components/SpiderChargeConditionComponent.cs
Content.Server/Objectives/Components/TerrorConditionComponent.cs [deleted file]
Content.Server/Objectives/Systems/CodeConditionSystem.cs [new file with mode: 0644]
Content.Server/Objectives/Systems/NinjaConditionsSystem.cs
Content.Shared/CriminalRecords/Components/CriminalRecordsHackerComponent.cs [new file with mode: 0644]
Content.Shared/CriminalRecords/Systems/SharedCriminalRecordsHackerSystem.cs [new file with mode: 0644]
Content.Shared/Mind/SharedMindSystem.cs
Content.Shared/Ninja/Components/SpaceNinjaComponent.cs
Content.Shared/Ninja/Systems/SharedNinjaGlovesSystem.cs
Resources/Locale/en-US/ninja/ninja-actions.ftl
Resources/Prototypes/Datasets/criminal_records.yml
Resources/Prototypes/GameRules/midround.yml
Resources/Prototypes/Objectives/base_objectives.yml
Resources/Prototypes/Objectives/ninja.yml

diff --git a/Content.Client/CriminalRecords/Systems/CriminalRecordsHackerSystem.cs b/Content.Client/CriminalRecords/Systems/CriminalRecordsHackerSystem.cs
new file mode 100644 (file)
index 0000000..21fccc8
--- /dev/null
@@ -0,0 +1,7 @@
+using Content.Shared.CriminalRecords.Systems;
+
+namespace Content.Client.CriminalRecords.Systems;
+
+public sealed class CriminalRecordsHackerSystem : SharedCriminalRecordsHackerSystem
+{
+}
diff --git a/Content.Server/CriminalRecords/Systems/CriminalRecordsHackerSystem.cs b/Content.Server/CriminalRecords/Systems/CriminalRecordsHackerSystem.cs
new file mode 100644 (file)
index 0000000..91285a1
--- /dev/null
@@ -0,0 +1,62 @@
+using Content.Server.Chat.Systems;
+using Content.Server.Station.Systems;
+using Content.Server.StationRecords.Systems;
+using Content.Shared.CriminalRecords;
+using Content.Shared.CriminalRecords.Components;
+using Content.Shared.CriminalRecords.Systems;
+using Content.Shared.Dataset;
+using Content.Shared.Security;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
+
+namespace Content.Server.CriminalRecords.Systems;
+
+public sealed class CriminalRecordsHackerSystem : SharedCriminalRecordsHackerSystem
+{
+    [Dependency] private readonly ChatSystem _chat = default!;
+    [Dependency] private readonly IPrototypeManager _proto = default!;
+    [Dependency] private readonly IRobustRandom _random = default!;
+    [Dependency] private readonly StationSystem _station = default!;
+    [Dependency] private readonly StationRecordsSystem _records = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<CriminalRecordsHackerComponent, CriminalRecordsHackDoAfterEvent>(OnDoAfter);
+    }
+
+    private void OnDoAfter(Entity<CriminalRecordsHackerComponent> ent, ref CriminalRecordsHackDoAfterEvent args)
+    {
+        if (args.Cancelled || args.Handled || args.Target == null)
+            return;
+
+        if (_station.GetOwningStation(ent) is not {} station)
+            return;
+
+        var reasons = _proto.Index<DatasetPrototype>(ent.Comp.Reasons);
+        foreach (var (key, record) in _records.GetRecordsOfType<CriminalRecord>(station))
+        {
+            var reason = _random.Pick(reasons.Values);
+            record.Status = SecurityStatus.Wanted;
+            record.Reason = reason;
+            // no radio message since spam
+            // no history since lazy and its easy to remove anyway
+            // main damage with this is existing arrest warrants are lost and to anger beepsky
+        }
+
+        _chat.DispatchGlobalAnnouncement(Loc.GetString(ent.Comp.Announcement), playSound: true, colorOverride: Color.Red);
+
+        // once is enough
+        RemComp<CriminalRecordsHackerComponent>(ent);
+
+        var ev = new CriminalRecordsHackedEvent(ent, args.Target.Value);
+        RaiseLocalEvent(args.User, ref ev);
+    }
+}
+
+/// <summary>
+/// Raised on the user after hacking a criminal records console.
+/// </summary>
+[ByRefEvent]
+public record struct CriminalRecordsHackedEvent(EntityUid User, EntityUid Target);
index d84a72877517373fa2f5fb0fe2ff099049c984ab..2c0f6c63e3e589150dbb0c816a8f6b7730554fac 100644 (file)
@@ -1,8 +1,9 @@
 using Content.Server.Communications;
 using Content.Server.Mind;
 using Content.Server.Ninja.Events;
-using Content.Server.Objectives.Components;
+using Content.Server.Objectives.Systems;
 using Content.Shared.Communications;
+using Content.Shared.CriminalRecords.Components;
 using Content.Shared.Ninja.Components;
 using Content.Shared.Ninja.Systems;
 using Content.Shared.Research.Components;
@@ -16,6 +17,7 @@ namespace Content.Server.Ninja.Systems;
 public sealed class NinjaGlovesSystem : SharedNinjaGlovesSystem
 {
     [Dependency] private readonly EmagProviderSystem _emagProvider = default!;
+    [Dependency] private readonly CodeConditionSystem _codeCondition = default!;
     [Dependency] private readonly CommsHackerSystem _commsHacker = default!;
     [Dependency] private readonly MindSystem _mind = default!;
     [Dependency] private readonly SharedStunProviderSystem _stunProvider = default!;
@@ -88,12 +90,16 @@ public sealed class NinjaGlovesSystem : SharedNinjaGlovesSystem
 
         EnsureComp<ResearchStealerComponent>(user);
         // prevent calling in multiple threats by toggling gloves after
-        if (_mind.TryGetObjectiveComp<TerrorConditionComponent>(user, out var obj) && !obj.CalledInThreat)
+        if (!_codeCondition.IsCompleted(user, ninja.TerrorObjective))
         {
             var hacker = EnsureComp<CommsHackerComponent>(user);
             var rule = _ninja.NinjaRule(user);
             if (rule != null)
                 _commsHacker.SetThreats(user, rule.Threats, hacker);
         }
+        if (!_codeCondition.IsCompleted(user, ninja.MassArrestObjective))
+        {
+            EnsureComp<CriminalRecordsHackerComponent>(user);
+        }
     }
 }
index 835ac7ad6cdac1e357565f7bbc23734df5503b2d..1dfaf4f3393b91bf620ad4dd7b7aa9480652231d 100644 (file)
@@ -1,12 +1,15 @@
 using Content.Server.Communications;
 using Content.Server.Chat.Managers;
+using Content.Server.CriminalRecords.Systems;
 using Content.Server.GameTicking.Rules.Components;
+using Content.Server.GenericAntag;
+using Content.Server.Objectives.Components;
+using Content.Server.Objectives.Systems;
 using Content.Server.Power.Components;
 using Content.Server.Power.EntitySystems;
 using Content.Server.PowerCell;
 using Content.Server.Research.Systems;
 using Content.Server.Roles;
-using Content.Server.GenericAntag;
 using Content.Shared.Alert;
 using Content.Shared.Clothing.EntitySystems;
 using Content.Shared.Doors.Components;
@@ -19,7 +22,6 @@ using Content.Shared.Rounding;
 using Robust.Shared.Audio;
 using Robust.Shared.Player;
 using System.Diagnostics.CodeAnalysis;
-using Content.Server.Objectives.Components;
 using Robust.Shared.Audio.Systems;
 
 namespace Content.Server.Ninja.Systems;
@@ -28,7 +30,6 @@ namespace Content.Server.Ninja.Systems;
 // engi -> saboteur
 // medi -> idk reskin it
 // other -> assault
-// TODO: when criminal records is merged, hack it to set everyone to arrest
 
 /// <summary>
 /// Main ninja system that handles ninja setup, provides helper methods for the rest of the code to use.
@@ -37,6 +38,7 @@ public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem
 {
     [Dependency] private readonly AlertsSystem _alerts = default!;
     [Dependency] private readonly BatterySystem _battery = default!;
+    [Dependency] private readonly CodeConditionSystem _codeCondition = default!;
     [Dependency] private readonly IChatManager _chatMan = default!;
     [Dependency] private readonly PowerCellSystem _powerCell = default!;
     [Dependency] private readonly RoleSystem _role = default!;
@@ -52,6 +54,7 @@ public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem
         SubscribeLocalEvent<SpaceNinjaComponent, EmaggedSomethingEvent>(OnDoorjack);
         SubscribeLocalEvent<SpaceNinjaComponent, ResearchStolenEvent>(OnResearchStolen);
         SubscribeLocalEvent<SpaceNinjaComponent, ThreatCalledInEvent>(OnThreatCalledIn);
+        SubscribeLocalEvent<SpaceNinjaComponent, CriminalRecordsHackedEvent>(OnCriminalRecordsHacked);
     }
 
     public override void Update(float frameTime)
@@ -216,11 +219,21 @@ public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem
         Popup.PopupEntity(str, uid, uid, PopupType.Medium);
     }
 
-    private void OnThreatCalledIn(EntityUid uid, SpaceNinjaComponent comp, ref ThreatCalledInEvent args)
+    private void OnThreatCalledIn(Entity<SpaceNinjaComponent> ent, ref ThreatCalledInEvent args)
     {
-        if (_mind.TryGetObjectiveComp<TerrorConditionComponent>(uid, out var obj))
-        {
-            obj.CalledInThreat = true;
-        }
+        _codeCondition.SetCompleted(ent.Owner, ent.Comp.TerrorObjective);
+    }
+
+    private void OnCriminalRecordsHacked(Entity<SpaceNinjaComponent> ent, ref CriminalRecordsHackedEvent args)
+    {
+        _codeCondition.SetCompleted(ent.Owner, ent.Comp.MassArrestObjective);
+    }
+
+    /// <summary>
+    /// Called by <see cref="SpiderChargeSystem"/> when it detonates.
+    /// </summary>
+    public void DetonatedSpiderCharge(Entity<SpaceNinjaComponent> ent)
+    {
+        _codeCondition.SetCompleted(ent.Owner, ent.Comp.SpiderChargeObjective);
     }
 }
index 948d715f0a7bd4fc5822443a91e6d9f41b3e943f..64c958d6f1aba24d11e738c7bb7ae69cffd1ee87 100644 (file)
@@ -19,6 +19,7 @@ public sealed class SpiderChargeSystem : EntitySystem
     [Dependency] private readonly MindSystem _mind = default!;
     [Dependency] private readonly PopupSystem _popup = default!;
     [Dependency] private readonly SharedTransformSystem _transform = default!;
+    [Dependency] private readonly SpaceNinjaSystem _ninja = default!;
 
     public override void Initialize()
     {
@@ -76,10 +77,10 @@ public sealed class SpiderChargeSystem : EntitySystem
     /// </summary>
     private void OnExplode(EntityUid uid, SpiderChargeComponent comp, TriggerEvent args)
     {
-        if (comp.Planter == null || !_mind.TryGetObjectiveComp<SpiderChargeConditionComponent>(comp.Planter.Value, out var obj))
+        if (!TryComp<SpaceNinjaComponent>(comp.Planter, out var ninja))
             return;
 
         // assumes the target was destroyed, that the charge wasn't moved somehow
-        obj.Detonated = true;
+        _ninja.DetonatedSpiderCharge((comp.Planter.Value, ninja));
     }
 }
diff --git a/Content.Server/Objectives/Components/CodeConditionSystem.cs b/Content.Server/Objectives/Components/CodeConditionSystem.cs
new file mode 100644 (file)
index 0000000..581098c
--- /dev/null
@@ -0,0 +1,17 @@
+using Content.Server.Objectives.Systems;
+
+namespace Content.Server.Objectives.Components;
+
+/// <summary>
+/// An objective that is set to complete by code in another system.
+/// Use <see cref="CodeConditionSystem"/> to check and set this.
+/// </summary>
+[RegisterComponent, Access(typeof(CodeConditionSystem))]
+public sealed partial class CodeConditionComponent : Component
+{
+    /// <summary>
+    /// Whether the objective is complete or not.
+    /// </summary>
+    [DataField]
+    public bool Completed;
+}
index 368c9f27ed4a599035e7f0d5c035a2717f1c33d4..9983b35969f6acb04ad219cc8af639be8cd4f8df 100644 (file)
@@ -9,9 +9,6 @@ namespace Content.Server.Objectives.Components;
 [RegisterComponent, Access(typeof(NinjaConditionsSystem), typeof(SpiderChargeSystem), typeof(SpaceNinjaSystem))]
 public sealed partial class SpiderChargeConditionComponent : Component
 {
-    [DataField, ViewVariables(VVAccess.ReadWrite)]
-    public bool Detonated;
-
     /// <summary>
     /// Warp point that the spider charge has to target
     /// </summary>
diff --git a/Content.Server/Objectives/Components/TerrorConditionComponent.cs b/Content.Server/Objectives/Components/TerrorConditionComponent.cs
deleted file mode 100644 (file)
index acd3218..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-using Content.Server.Objectives.Systems;
-using Content.Shared.Ninja.Systems;
-
-namespace Content.Server.Objectives.Components;
-
-/// <summary>
-/// Requires that the player is a ninja and has called in a threat.
-/// </summary>
-[RegisterComponent, Access(typeof(NinjaConditionsSystem), typeof(SharedSpaceNinjaSystem))]
-public sealed partial class TerrorConditionComponent : Component
-{
-    /// <summary>
-    /// Whether the comms console has been hacked
-    /// </summary>
-    [DataField("calledInThreat"), ViewVariables(VVAccess.ReadWrite)]
-    public bool CalledInThreat;
-}
diff --git a/Content.Server/Objectives/Systems/CodeConditionSystem.cs b/Content.Server/Objectives/Systems/CodeConditionSystem.cs
new file mode 100644 (file)
index 0000000..7ba312f
--- /dev/null
@@ -0,0 +1,76 @@
+using Content.Server.Objectives.Components;
+using Content.Shared.Objectives.Components;
+using Content.Shared.Mind;
+using Content.Shared.Mind.Components;
+
+namespace Content.Server.Objectives.Systems;
+
+/// <summary>
+/// Handles <see cref="CodeConditionComponent"/> progress and provides API for systems to use.
+/// </summary>
+public sealed class CodeConditionSystem : EntitySystem
+{
+    [Dependency] private readonly SharedMindSystem _mind = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<CodeConditionComponent, ObjectiveGetProgressEvent>(OnGetProgress);
+    }
+
+    private void OnGetProgress(Entity<CodeConditionComponent> ent, ref ObjectiveGetProgressEvent args)
+    {
+        args.Progress = ent.Comp.Completed ? 1f : 0f;
+    }
+
+    /// <summary>
+    /// Returns whether an objective is completed.
+    /// </summary>
+    public bool IsCompleted(Entity<CodeConditionComponent?> ent)
+    {
+        if (!Resolve(ent, ref ent.Comp))
+            return false;
+
+        return ent.Comp.Completed;
+    }
+
+    /// <summary>
+    /// Returns true if a mob's objective with a certain prototype is completed.
+    /// </summary>
+    public bool IsCompleted(Entity<MindContainerComponent?> mob, string prototype)
+    {
+        if (_mind.GetMind(mob, mob.Comp) is not {} mindId)
+            return false;
+
+        if (!_mind.TryFindObjective(mindId, prototype, out var obj))
+            return false;
+
+        return IsCompleted(obj.Value);
+    }
+
+    /// <summary>
+    /// Sets an objective's completed field.
+    /// </summary>
+    public void SetCompleted(Entity<CodeConditionComponent?> ent, bool completed = true)
+    {
+        if (!Resolve(ent, ref ent.Comp))
+            return;
+
+        ent.Comp.Completed = completed;
+    }
+
+    /// <summary>
+    /// Sets a mob's objective to complete.
+    /// </summary>
+    public void SetCompleted(Entity<MindContainerComponent?> mob, string prototype, bool completed = true)
+    {
+        if (_mind.GetMind(mob, mob.Comp) is not {} mindId)
+            return;
+
+        if (!_mind.TryFindObjective(mindId, prototype, out var obj))
+            return;
+
+        SetCompleted(obj.Value, completed);
+    }
+}
index 888a365a5ddb8ece2825f9ca2bd4c949d8d46285..47c54b937a070d321d7d3fd466ecf51e4ea3b8c4 100644 (file)
@@ -23,11 +23,8 @@ public sealed class NinjaConditionsSystem : EntitySystem
 
         SubscribeLocalEvent<SpiderChargeConditionComponent, RequirementCheckEvent>(OnSpiderChargeRequirementCheck);
         SubscribeLocalEvent<SpiderChargeConditionComponent, ObjectiveAfterAssignEvent>(OnSpiderChargeAfterAssign);
-        SubscribeLocalEvent<SpiderChargeConditionComponent, ObjectiveGetProgressEvent>(OnSpiderChargeGetProgress);
 
         SubscribeLocalEvent<StealResearchConditionComponent, ObjectiveGetProgressEvent>(OnStealResearchGetProgress);
-
-        SubscribeLocalEvent<TerrorConditionComponent, ObjectiveGetProgressEvent>(OnTerrorGetProgress);
     }
 
     // doorjack
@@ -88,11 +85,6 @@ public sealed class NinjaConditionsSystem : EntitySystem
         _metaData.SetEntityName(uid, title, args.Meta);
     }
 
-    private void OnSpiderChargeGetProgress(EntityUid uid, SpiderChargeConditionComponent comp, ref ObjectiveGetProgressEvent args)
-    {
-        args.Progress = comp.Detonated ? 1f : 0f;
-    }
-
     // steal research
 
     private void OnStealResearchGetProgress(EntityUid uid, StealResearchConditionComponent comp, ref ObjectiveGetProgressEvent args)
@@ -108,9 +100,4 @@ public sealed class NinjaConditionsSystem : EntitySystem
 
         return MathF.Min(comp.DownloadedNodes.Count / (float) target, 1f);
     }
-
-    private void OnTerrorGetProgress(EntityUid uid, TerrorConditionComponent comp, ref ObjectiveGetProgressEvent args)
-    {
-        args.Progress = comp.CalledInThreat ? 1f : 0f;
-    }
 }
diff --git a/Content.Shared/CriminalRecords/Components/CriminalRecordsHackerComponent.cs b/Content.Shared/CriminalRecords/Components/CriminalRecordsHackerComponent.cs
new file mode 100644 (file)
index 0000000..189a387
--- /dev/null
@@ -0,0 +1,31 @@
+using Content.Shared.CriminalRecords.Systems;
+using Content.Shared.Dataset;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.CriminalRecords.Components;
+
+/// <summary>
+/// Lets the user hack a criminal records console, once.
+/// Everyone is set to wanted with a randomly picked reason.
+/// </summary>
+[RegisterComponent, NetworkedComponent, Access(typeof(SharedCriminalRecordsHackerSystem))]
+public sealed partial class CriminalRecordsHackerComponent : Component
+{
+    /// <summary>
+    /// How long the doafter is for hacking it.
+    /// </summary>
+    public TimeSpan Delay = TimeSpan.FromSeconds(20);
+
+    /// <summary>
+    /// Dataset of random reasons to use.
+    /// </summary>
+    [DataField]
+    public ProtoId<DatasetPrototype> Reasons = "CriminalRecordsWantedReasonPlaceholders";
+
+    /// <summary>
+    /// Announcement made after the console is hacked.
+    /// </summary>
+    [DataField]
+    public LocId Announcement = "ninja-criminal-records-hack-announcement";
+}
diff --git a/Content.Shared/CriminalRecords/Systems/SharedCriminalRecordsHackerSystem.cs b/Content.Shared/CriminalRecords/Systems/SharedCriminalRecordsHackerSystem.cs
new file mode 100644 (file)
index 0000000..e8e8e52
--- /dev/null
@@ -0,0 +1,48 @@
+using Content.Shared.CriminalRecords.Components;
+using Content.Shared.DoAfter;
+using Content.Shared.Interaction;
+using Content.Shared.Ninja.Systems;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.CriminalRecords.Systems;
+
+public abstract class SharedCriminalRecordsHackerSystem : EntitySystem
+{
+    [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
+    [Dependency] private readonly SharedNinjaGlovesSystem _gloves = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<CriminalRecordsHackerComponent, BeforeInteractHandEvent>(OnBeforeInteractHand);
+    }
+
+    private void OnBeforeInteractHand(Entity<CriminalRecordsHackerComponent> ent, ref BeforeInteractHandEvent args)
+    {
+        // TODO: generic event
+        if (args.Handled || !_gloves.AbilityCheck(ent, args, out var target))
+            return;
+
+        if (!HasComp<CriminalRecordsConsoleComponent>(target))
+            return;
+
+        var doAfterArgs = new DoAfterArgs(EntityManager, ent, ent.Comp.Delay, new CriminalRecordsHackDoAfterEvent(), target: target, used: ent, eventTarget: ent)
+        {
+            BreakOnDamage = true,
+            BreakOnUserMove = true,
+            MovementThreshold = 0.5f
+        };
+
+        _doAfter.TryStartDoAfter(doAfterArgs);
+        args.Handled = true;
+    }
+}
+
+/// <summary>
+/// Raised on the user when the doafter completes.
+/// </summary>
+[Serializable, NetSerializable]
+public sealed partial class CriminalRecordsHackDoAfterEvent : SimpleDoAfterEvent
+{
+}
index 1898126d803b6b7d340a90030061daa6eb88bb92..7887b8f9b221a6123084498d58b65b702ea9aaf8 100644 (file)
@@ -383,6 +383,30 @@ public abstract class SharedMindSystem : EntitySystem
         return false;
     }
 
+    /// <summary>
+    /// Tries to find an objective that has the same prototype as the argument.
+    /// </summary>
+    /// <remarks>
+    /// Will not work for objectives that have no prototype, or duplicate objectives with the same prototype.
+    /// <//remarks>
+    public bool TryFindObjective(Entity<MindComponent?> mind, string prototype, [NotNullWhen(true)] out EntityUid? objective)
+    {
+        objective = null;
+        if (!Resolve(mind, ref mind.Comp))
+            return false;
+
+        foreach (var uid in mind.Comp.Objectives)
+        {
+            if (MetaData(uid).EntityPrototype?.ID == prototype)
+            {
+                objective = uid;
+                return true;
+            }
+        }
+
+        return false;
+    }
+
     public bool TryGetSession(EntityUid? mindId, [NotNullWhen(true)] out ICommonSession? session)
     {
         session = null;
index dff4b56aa4a1b74dcca4cecc657430bf2c3aab82..0f3bff265cbacce88200c4ae9a931f3e219ac5ce 100644 (file)
@@ -1,6 +1,6 @@
 using Content.Shared.Ninja.Systems;
 using Robust.Shared.GameStates;
-using Robust.Shared.Serialization;
+using Robust.Shared.Prototypes;
 
 namespace Content.Shared.Ninja.Components;
 
@@ -35,4 +35,22 @@ public sealed partial class SpaceNinjaComponent : Component
     /// </summary>
     [DataField("katana"), AutoNetworkedField]
     public EntityUid? Katana;
+
+    /// <summary>
+    /// Objective to complete after calling in a threat.
+    /// </summary>
+    [DataField]
+    public EntProtoId TerrorObjective = "TerrorObjective";
+
+    /// <summary>
+    /// Objective to complete after setting everyone to arrest.
+    /// </summary>
+    [DataField]
+    public EntProtoId MassArrestObjective = "MassArrestObjective";
+
+    /// <summary>
+    /// Objective to complete after the spider charge detonates.
+    /// </summary>
+    [DataField]
+    public EntProtoId SpiderChargeObjective = "SpiderChargeObjective";
 }
index 815464bf7a33dd473e6dbc102afef56b84f7d56a..f61d0c6a9084cf0a750aa471113dc9bba562ff5c 100644 (file)
@@ -1,6 +1,7 @@
 using Content.Shared.Actions;
 using Content.Shared.CombatMode;
 using Content.Shared.Communications;
+using Content.Shared.CriminalRecords.Components;
 using Content.Shared.Examine;
 using Content.Shared.Hands.Components;
 using Content.Shared.Interaction;
@@ -62,6 +63,7 @@ public abstract class SharedNinjaGlovesSystem : EntitySystem
         RemComp<StunProviderComponent>(user);
         RemComp<ResearchStealerComponent>(user);
         RemComp<CommsHackerComponent>(user);
+        RemComp<CriminalRecordsHackerComponent>(user);
     }
 
     /// <summary>
index b42da33a29766b9977aa33fabb337bd29fdcdf35..f01f02a60e57fa69dca41c01cd86017f207f7b82 100644 (file)
@@ -4,3 +4,5 @@ ninja-suit-cooldown = The suit needs time to recuperate from the last attack.
 
 ninja-research-steal-fail = No new research nodes were stolen...
 ninja-research-steal-success = Stole {$count} new nodes from {THE($server)}.
+
+ninja-criminal-records-hack-announcement = ERROR: Criminal records has detected a [REDACTED] error #*;"
index ee283091843678f9cd09f9ce6984c92a9bcab75d..fe21757cd206c648413233fce7876a3c728b0893 100644 (file)
@@ -2,17 +2,23 @@
 - type: dataset
   id: CriminalRecordsWantedReasonPlaceholders
   values:
+  - Ate a delicious valid salad
   - Ate their own shoes
   - Being a clown
   - Being a mime
   - Breathed the wrong way
   - Broke into evac
   - Did literally nothing
+  - Did their job
   - Didn't say hello to me
   - Drank one too many
+  - Had two toolboxes, that's too many
   - Lied on common radio
   - Looked at me funny
+  - Lubed up the entire way to evac
+  - Set AME up on time
   - Slipped the HoS
   - Stole the clown's mask
   - Told an unfunny joke
   - Wore a gasmask
+  - Wore boxing gloves
index c417132992184b1cccff939cc897bb2974932daa..5a4cde31018bd32f37015bfcf5d0e4910bfee601 100644 (file)
@@ -12,6 +12,7 @@
     - DoorjackObjective
     - SpiderChargeObjective
     - TerrorObjective
+    - MassArrestObjective
     - NinjaSurviveObjective
   - type: NinjaRule
     threats: NinjaThreats
index e24b26e6e86a4c6f2fbd56a37baa25faf5b52e52..2ab5149213a6a6005f626a97572d1f1cb283e25f 100644 (file)
   id: BaseSurviveObjective
   components:
   - type: SurviveCondition
+
+# objective progress is controlled by a system and not the objective itself
+- type: entity
+  abstract: true
+  parent: BaseObjective
+  id: BaseCodeObjective
+  components:
+  - type: CodeCondition
index f2ac97be58bb3674e762e6ddfcdff7b61ee54824..0203059c2c451d7a6b09bd6a01b2c74a90321bae 100644 (file)
@@ -46,7 +46,7 @@
 
 - type: entity
   noSpawn: true
-  parent: BaseNinjaObjective
+  parent: [BaseNinjaObjective, BaseCodeObjective]
   id: SpiderChargeObjective
   description: This bomb can be detonated in a specific location. Note that the bomb will not work anywhere else!
   components:
@@ -54,7 +54,6 @@
     icon:
       sprite: Objects/Weapons/Bombs/spidercharge.rsi
       state: icon
-  - type: SpiderChargeCondition
 
 - type: entity
   noSpawn: true
@@ -70,7 +69,7 @@
 
 - type: entity
   noSpawn: true
-  parent: BaseNinjaObjective
+  parent: [BaseNinjaObjective, BaseCodeObjective]
   id: TerrorObjective
   name: Call in a threat
   description: Use your gloves on a communication console in order to bring another threat to the station.
     icon:
       sprite: Objects/Fun/Instruments/otherinstruments.rsi
       state: red_phone
-  - type: TerrorCondition
+
+- type: entity
+  noSpawn: true
+  parent: [BaseNinjaObjective, BaseCodeObjective]
+  id: MassArrestObjective
+  name: Set everyone to wanted
+  description: Use your gloves to hack a criminal records console, setting the entire station to be wanted!
+  components:
+  - type: Objective
+    icon:
+      sprite: Objects/Weapons/Melee/stunbaton.rsi
+      state: stunbaton_on