]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Add more DNA interactions (#21989)
authorthemias <89101928+themias@users.noreply.github.com>
Fri, 15 Dec 2023 09:52:55 +0000 (04:52 -0500)
committerGitHub <noreply@github.com>
Fri, 15 Dec 2023 09:52:55 +0000 (02:52 -0700)
* Add more DNA interactions

* remove unused import

* update based on feedback

* Add event for chemistrysystem.injector

* move event to shared; transfer dna to implanter

* doafter and interaction event fixes

* add BreakOnHandChange

* doh

* use events instead of updating component directly

* Add DataFields to ForensicScannerComponent fields

* Convert most events to system api call

20 files changed:
Content.Client/Forensics/ForensicScannerMenu.xaml.cs
Content.Server/Body/Systems/BloodstreamSystem.cs
Content.Server/Chemistry/EntitySystems/ChemistrySystem.Injector.cs
Content.Server/Chemistry/EntitySystems/ChemistrySystemHypospray.cs
Content.Server/Forensics/Components/ForensicScannerComponent.cs
Content.Server/Forensics/Components/ForensicsComponent.cs
Content.Server/Forensics/Components/ResidueComponent.cs [new file with mode: 0644]
Content.Server/Forensics/Systems/ForensicScannerSystem.cs
Content.Server/Forensics/Systems/ForensicsSystem.cs
Content.Server/Medical/VomitSystem.cs
Content.Server/Nutrition/EntitySystems/DrinkSystem.cs
Content.Server/Nutrition/EntitySystems/SmokingSystem.cs
Content.Shared/Forensics/Events.cs
Content.Shared/Forensics/ForensicScannerEvent.cs
Content.Shared/Implants/SharedImplanterSystem.cs
Resources/Locale/en-US/forensics/forensics.ftl
Resources/Locale/en-US/forensics/residues.ftl [new file with mode: 0644]
Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml
Resources/Prototypes/Entities/Objects/Specific/Janitorial/soap.yml
Resources/Prototypes/tags.yml

index 84ffd7969e7446efb525a7f0ad5e72b995fc6dde..8b6152c86126be55ba02e96b907e1175224ae788 100644 (file)
@@ -58,6 +58,12 @@ namespace Content.Client.Forensics
             {
                 text.AppendLine(dna);
             }
+            text.AppendLine();
+            text.AppendLine(Loc.GetString("forensic-scanner-interface-residues"));
+            foreach (var residue in msg.Residues)
+            {
+                text.AppendLine(residue);
+            }
             Diagnostics.Text = text.ToString();
         }
     }
index f1ab5702e5bfdf733678dfb40576756fbc8b3976..cb83625615bce55cc1ce503e916099728777e422 100644 (file)
@@ -1,7 +1,6 @@
 using Content.Server.Body.Components;
 using Content.Server.Chemistry.ReactionEffects;
 using Content.Server.Fluids.EntitySystems;
-using Content.Server.Forensics;
 using Content.Server.HealthExaminable;
 using Content.Server.Popups;
 using Content.Shared.Alert;
@@ -22,6 +21,8 @@ using Robust.Shared.Prototypes;
 using Robust.Shared.Random;
 using Content.Shared.Speech.EntitySystems;
 using Robust.Server.Audio;
+using Robust.Shared.GameObjects;
+using Content.Server.Forensics;
 
 namespace Content.Server.Body.Systems;
 
@@ -38,6 +39,7 @@ public sealed class BloodstreamSystem : EntitySystem
     [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
     [Dependency] private readonly SharedStutteringSystem _stutteringSystem = default!;
     [Dependency] private readonly AlertsSystem _alertsSystem = default!;
+    [Dependency] private readonly ForensicsSystem _forensicsSystem = default!;
 
     public override void Initialize()
     {
@@ -322,11 +324,7 @@ public sealed class BloodstreamSystem : EntitySystem
             component.BloodTemporarySolution.AddSolution(temp, _prototypeManager);
             if (_puddleSystem.TrySpillAt(uid, component.BloodTemporarySolution, out var puddleUid, false))
             {
-                if (TryComp<DnaComponent>(uid, out var dna))
-                {
-                    var comp = EnsureComp<ForensicsComponent>(puddleUid);
-                    comp.DNAs.Add(dna.DNA);
-                }
+                _forensicsSystem.TransferDna(puddleUid, uid, false);
             }
 
             component.BloodTemporarySolution.RemoveAllSolution();
@@ -378,11 +376,7 @@ public sealed class BloodstreamSystem : EntitySystem
 
         if (_puddleSystem.TrySpillAt(uid, tempSol, out var puddleUid))
         {
-            if (TryComp<DnaComponent>(uid, out var dna))
-            {
-                var comp = EnsureComp<ForensicsComponent>(puddleUid);
-                comp.DNAs.Add(dna.DNA);
-            }
+            _forensicsSystem.TransferDna(puddleUid, uid, false);
         }
     }
 
index 6b085d133ed577ed343331022cd78764656efe71..c618094d1b0220fde8914b873725d03aecfcd3cc 100644 (file)
@@ -15,6 +15,7 @@ using Content.Shared.Mobs.Components;
 using Content.Shared.Verbs;
 using Content.Shared.Stacks;
 using Robust.Shared.Player;
+using Content.Shared.Forensics;
 
 namespace Content.Server.Chemistry.EntitySystems;
 
@@ -290,7 +291,7 @@ public sealed partial class ChemistrySystem
                 ("target", Identity.Entity(target, EntityManager))), injector, user);
 
         Dirty(component);
-        AfterInject(component, injector);
+        AfterInject(component, injector, target);
     }
 
     private void TryInject(InjectorComponent component, EntityUid injector, EntityUid targetEntity, Solution targetSolution, EntityUid user, bool asRefill)
@@ -328,10 +329,10 @@ public sealed partial class ChemistrySystem
                 ("target", Identity.Entity(targetEntity, EntityManager))), injector, user);
 
         Dirty(component);
-        AfterInject(component, injector);
+        AfterInject(component, injector, targetEntity);
     }
 
-    private void AfterInject(InjectorComponent component, EntityUid injector)
+    private void AfterInject(InjectorComponent component, EntityUid injector, EntityUid target)
     {
         // Automatically set syringe to draw after completely draining it.
         if (_solutions.TryGetSolution(injector, InjectorComponent.SolutionName, out var solution)
@@ -339,9 +340,13 @@ public sealed partial class ChemistrySystem
         {
             component.ToggleState = SharedInjectorComponent.InjectorToggleMode.Draw;
         }
+
+        // Leave some DNA from the injectee on it
+        var ev = new TransferDnaEvent { Donor = target, Recipient = injector };
+        RaiseLocalEvent(target, ref ev);
     }
 
-    private void AfterDraw(InjectorComponent component, EntityUid injector)
+    private void AfterDraw(InjectorComponent component, EntityUid injector, EntityUid target)
     {
         // Automatically set syringe to inject after completely filling it.
         if (_solutions.TryGetSolution(injector, InjectorComponent.SolutionName, out var solution)
@@ -349,6 +354,10 @@ public sealed partial class ChemistrySystem
         {
             component.ToggleState = SharedInjectorComponent.InjectorToggleMode.Inject;
         }
+
+        // Leave some DNA from the drawee on it
+        var ev = new TransferDnaEvent { Donor = target, Recipient = injector };
+        RaiseLocalEvent(target, ref ev);
     }
 
     private void TryDraw(InjectorComponent component, EntityUid injector, EntityUid targetEntity, Solution targetSolution, EntityUid user, BloodstreamComponent? stream = null)
@@ -389,7 +398,7 @@ public sealed partial class ChemistrySystem
                 ("target", Identity.Entity(targetEntity, EntityManager))), injector, user);
 
         Dirty(component);
-        AfterDraw(component, injector);
+        AfterDraw(component, injector, targetEntity);
     }
 
     private void DrawFromBlood(EntityUid user, EntityUid injector, EntityUid target, InjectorComponent component, Solution injectorSolution, BloodstreamComponent stream, FixedPoint2 transferAmount)
@@ -414,7 +423,7 @@ public sealed partial class ChemistrySystem
                 ("target", Identity.Entity(target, EntityManager))), injector, user);
 
         Dirty(component);
-        AfterDraw(component, injector);
+        AfterDraw(component, injector, target);
     }
 
 }
index fcc3c58fa7b38a6c5b6830972f4ad34920b0bced..cb4feb806401863e422c00675bd7d2779d5adb7f 100644 (file)
@@ -14,6 +14,7 @@ using Content.Shared.Mobs.Components;
 using Content.Shared.Weapons.Melee.Events;
 using Content.Shared.Timing;
 using Robust.Shared.GameStates;
+using Content.Shared.Forensics;
 
 namespace Content.Server.Chemistry.EntitySystems
 {
@@ -138,6 +139,9 @@ namespace Content.Server.Chemistry.EntitySystems
             _reactiveSystem.DoEntityReaction(target.Value, removedSolution, ReactionMethod.Injection);
             _solutions.TryAddSolution(target.Value, targetSolution, removedSolution);
 
+            var ev = new TransferDnaEvent { Donor = target.Value, Recipient = uid };
+            RaiseLocalEvent(target.Value, ref ev);
+
             // same LogType as syringes...
             _adminLogger.Add(LogType.ForceFeed, $"{_entMan.ToPrettyString(user):user} injected {_entMan.ToPrettyString(target.Value):target} with a solution {SolutionContainerSystem.ToPrettyString(removedSolution):removedSolution} using a {_entMan.ToPrettyString(uid):using}");
 
index 16eb852590ef9166a15f3eea996c2f56437f07c9..ad262138482fdd94adcc29f8bfcab9c16fbe43e4 100644 (file)
@@ -13,21 +13,27 @@ namespace Content.Server.Forensics
         /// <summary>
         /// A list of fingerprint GUIDs that the forensic scanner found from the <see cref="ForensicsComponent"/> on an entity.
         /// </summary>
-        [ViewVariables(VVAccess.ReadOnly)]
+        [ViewVariables(VVAccess.ReadOnly), DataField("fingerprints")]
         public List<string> Fingerprints = new();
 
         /// <summary>
         /// A list of glove fibers that the forensic scanner found from the <see cref="ForensicsComponent"/> on an entity.
         /// </summary>
-        [ViewVariables(VVAccess.ReadOnly)]
+        [ViewVariables(VVAccess.ReadOnly), DataField("fibers")]
         public List<string> Fibers = new();
 
         /// <summary>
         /// DNA that the forensic scanner found from the <see cref="DNAComponent"/> on an entity.
         /// </summary>
-        [ViewVariables(VVAccess.ReadOnly)]
+        [ViewVariables(VVAccess.ReadOnly), DataField("dnas")]
         public List<string> DNAs = new();
 
+        /// <summary>
+        /// Residue that the forensic scanner found from the <see cref="ForensicsComponent"/> on an entity.
+        /// </summary>
+        [ViewVariables(VVAccess.ReadOnly), DataField("residues")]
+        public List<string> Residues = new();
+
         /// <summary>
         /// What is the name of the entity that was scanned last?
         /// </summary>
index 5a73a6a3e38faf4a5d121649ce7bb95f8e131e3b..27eccf3334a540734e811796f7a3dc2593218075 100644 (file)
@@ -11,5 +11,27 @@ namespace Content.Server.Forensics
 
         [DataField("dnas")]
         public HashSet<string> DNAs = new();
+
+        [DataField("residues")]
+        public HashSet<string> Residues = new();
+
+        /// <summary>
+        /// How long it takes to wipe the prints/blood/etc. off of this entity
+        /// </summary>
+        [DataField("cleanDelay")]
+        public float CleanDelay = 12.0f;
+
+        /// <summary>
+        /// How close you must be to wipe the prints/blood/etc. off of this entity
+        /// </summary>
+        [DataField("cleanDistance")]
+        public float CleanDistance = 1.5f;
+
+        /// <summary>
+        /// Can the DNA be cleaned off of this entity?
+        /// e.g. you can clean the DNA off of a knife, but not a puddle
+        /// </summary>
+        [DataField("canDnaBeCleaned")]
+        public bool CanDnaBeCleaned = true;
     }
 }
diff --git a/Content.Server/Forensics/Components/ResidueComponent.cs b/Content.Server/Forensics/Components/ResidueComponent.cs
new file mode 100644 (file)
index 0000000..10895e7
--- /dev/null
@@ -0,0 +1,15 @@
+namespace Content.Server.Forensics;
+
+/// <summary>
+/// This controls residues left on items
+/// which the forensics system uses.
+/// </summary>
+[RegisterComponent]
+public sealed partial class ResidueComponent : Component
+{
+    [DataField]
+    public LocId ResidueAdjective = "residue-unknown";
+
+    [DataField]
+    public string? ResidueColor;
+}
index a073574e1d14173e967fb1f61cc0f2b5eecb4ba2..6710864c729a1630fbab0c0cc535b8016719991c 100644 (file)
@@ -51,6 +51,7 @@ namespace Content.Server.Forensics
                 component.Fingerprints,
                 component.Fibers,
                 component.DNAs,
+                component.Residues,
                 component.LastScannedName,
                 component.PrintCooldown,
                 component.PrintReadyAt);
@@ -74,6 +75,7 @@ namespace Content.Server.Forensics
                     scanner.Fingerprints = new();
                     scanner.Fibers = new();
                     scanner.DNAs = new();
+                    scanner.Residues = new();
                 }
 
                 else
@@ -81,6 +83,7 @@ namespace Content.Server.Forensics
                     scanner.Fingerprints = forensics.Fingerprints.ToList();
                     scanner.Fibers = forensics.Fibers.ToList();
                     scanner.DNAs = forensics.DNAs.ToList();
+                    scanner.Residues = forensics.Residues.ToList();
                 }
 
                 scanner.LastScannedName = MetaData(args.Args.Target.Value).EntityName;
@@ -222,6 +225,12 @@ namespace Content.Server.Forensics
             {
                 text.AppendLine(dna);
             }
+            text.AppendLine();
+            text.AppendLine(Loc.GetString("forensic-scanner-interface-residues"));
+            foreach (var residue in component.Residues)
+            {
+                text.AppendLine(residue);
+            }
 
             _paperSystem.SetContent(printed, text.ToString());
             _audioSystem.PlayPvs(component.SoundPrint, uid,
index d4281c8b8d5c483b14fd9bbab251ecb92a1bf024..3c6d1d30afd9b5d11b684f1ae61a06a687686dba 100644 (file)
@@ -1,5 +1,13 @@
+using Content.Server.Body.Components;
+using Content.Server.Chemistry.EntitySystems;
+using Content.Server.DoAfter;
+using Content.Shared.DoAfter;
+using Content.Shared.Forensics;
+using Content.Shared.Interaction;
 using Content.Shared.Interaction.Events;
 using Content.Shared.Inventory;
+using Content.Shared.Tag;
+using Content.Shared.Weapons.Melee.Events;
 using Robust.Shared.Random;
 
 namespace Content.Server.Forensics
@@ -8,11 +16,19 @@ namespace Content.Server.Forensics
     {
         [Dependency] private readonly IRobustRandom _random = default!;
         [Dependency] private readonly InventorySystem _inventory = default!;
+        [Dependency] private readonly TagSystem _tagSystem = default!;
+        [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
         public override void Initialize()
         {
             SubscribeLocalEvent<FingerprintComponent, ContactInteractionEvent>(OnInteract);
             SubscribeLocalEvent<FingerprintComponent, MapInitEvent>(OnFingerprintInit);
             SubscribeLocalEvent<DnaComponent, MapInitEvent>(OnDNAInit);
+
+            SubscribeLocalEvent<DnaComponent, BeingGibbedEvent>(OnBeingGibbed);
+            SubscribeLocalEvent<ForensicsComponent, MeleeHitEvent>(OnMeleeHit);
+            SubscribeLocalEvent<ForensicsComponent, AfterInteractEvent>(OnAfterInteract);
+            SubscribeLocalEvent<ForensicsComponent, CleanForensicsDoAfterEvent>(OnCleanForensicsDoAfter);
+            SubscribeLocalEvent<DnaComponent, TransferDnaEvent>(OnTransferDnaEvent);
         }
 
         private void OnInteract(EntityUid uid, FingerprintComponent component, ContactInteractionEvent args)
@@ -30,6 +46,79 @@ namespace Content.Server.Forensics
             component.DNA = GenerateDNA();
         }
 
+        private void OnBeingGibbed(EntityUid uid, DnaComponent component, BeingGibbedEvent args)
+        {
+            foreach(EntityUid part in args.GibbedParts)
+            {
+                var partComp = EnsureComp<ForensicsComponent>(part);
+                partComp.DNAs.Add(component.DNA);
+                partComp.CanDnaBeCleaned = false;
+            }
+        }
+
+        private void OnMeleeHit(EntityUid uid, ForensicsComponent component, MeleeHitEvent args)
+        {
+            if((args.BaseDamage.DamageDict.TryGetValue("Blunt", out var bluntDamage) && bluntDamage.Value > 0) ||
+                (args.BaseDamage.DamageDict.TryGetValue("Slash", out var slashDamage) && slashDamage.Value > 0) ||
+                (args.BaseDamage.DamageDict.TryGetValue("Piercing", out var pierceDamage) && pierceDamage.Value > 0))
+            {
+                foreach(EntityUid hitEntity in args.HitEntities)
+                {
+                    if(TryComp<DnaComponent>(hitEntity, out var hitEntityComp))
+                        component.DNAs.Add(hitEntityComp.DNA);
+                }
+            }
+        }
+
+        private void OnAfterInteract(EntityUid uid, ForensicsComponent component, AfterInteractEvent args)
+        {
+            if (args.Handled)
+                return;
+
+            if (!_tagSystem.HasTag(args.Used, "CleansForensics"))
+                return;
+
+            if((component.DNAs.Count > 0 && component.CanDnaBeCleaned) || (component.Fingerprints.Count + component.Fibers.Count > 0))
+            {
+                var doAfterArgs = new DoAfterArgs(EntityManager, args.User, component.CleanDelay, new CleanForensicsDoAfterEvent(), uid, target: args.Target, used: args.Used)
+                {
+                    BreakOnHandChange = true,
+                    NeedHand = true,
+                    BreakOnDamage = true,
+                    BreakOnTargetMove = true,
+                    MovementThreshold = 0.01f,
+                    DistanceThreshold = component.CleanDistance,
+                };
+
+
+                _doAfterSystem.TryStartDoAfter(doAfterArgs);
+
+                args.Handled = true;
+            }
+        }
+
+        private void OnCleanForensicsDoAfter(EntityUid uid, ForensicsComponent component, CleanForensicsDoAfterEvent args)
+        {
+            if (args.Handled || args.Cancelled || args.Args.Target == null)
+                return;
+
+            if (!TryComp<ForensicsComponent>(args.Target, out var targetComp))
+                return;
+
+            targetComp.Fibers = new();
+            targetComp.Fingerprints = new();
+
+            if (targetComp.CanDnaBeCleaned)
+                targetComp.DNAs = new();
+
+            // leave behind evidence it was cleaned
+            if (TryComp<FiberComponent>(args.Used, out var fiber))
+                targetComp.Fibers.Add(string.IsNullOrEmpty(fiber.FiberColor) ? Loc.GetString("forensic-fibers", ("material", fiber.FiberMaterial)) : Loc.GetString("forensic-fibers-colored", ("color", fiber.FiberColor), ("material", fiber.FiberMaterial)));
+
+            if (TryComp<ResidueComponent>(args.Used, out var residue))
+                targetComp.Residues.Add(string.IsNullOrEmpty(residue.ResidueColor) ? Loc.GetString("forensic-residue", ("adjective", residue.ResidueAdjective)) : Loc.GetString("forensic-residue-colored", ("color", residue.ResidueColor), ("adjective", residue.ResidueAdjective)));
+        }
+
         public string GenerateFingerprint()
         {
             var fingerprint = new byte[16];
@@ -64,5 +153,31 @@ namespace Content.Server.Forensics
             if (TryComp<FingerprintComponent>(user, out var fingerprint))
                 component.Fingerprints.Add(fingerprint.Fingerprint ?? "");
         }
+
+        private void OnTransferDnaEvent(EntityUid uid, DnaComponent component, ref TransferDnaEvent args)
+        {
+            var recipientComp = EnsureComp<ForensicsComponent>(args.Recipient);
+            recipientComp.DNAs.Add(component.DNA);
+            recipientComp.CanDnaBeCleaned = args.CanDnaBeCleaned;
+        }
+
+        #region Public API
+
+        /// <summary>
+        /// Transfer DNA from one entity onto the forensics of another
+        /// </summary>
+        /// <param name="recipient">The entity receiving the DNA</param>
+        /// <param name="donor">The entity applying its DNA</param>
+        /// <param name="canDnaBeCleaned">If this DNA be cleaned off of the recipient. e.g. cleaning a knife vs cleaning a puddle of blood</param>
+        public void TransferDna(EntityUid recipient, EntityUid donor, bool canDnaBeCleaned = true)
+        {
+            if (TryComp<DnaComponent>(donor, out var donorComp))
+            {
+                EnsureComp<ForensicsComponent>(recipient, out var recipientComp);
+                recipientComp.DNAs.Add(donorComp.DNA);
+            }
+        }
+
+        #endregion
     }
 }
index 974c1981b569678db43ffa54b14139916dd7be0c..fedb6d2357bb9b2223ad50bbb44e471867c54da5 100644 (file)
@@ -28,6 +28,7 @@ namespace Content.Server.Medical
         [Dependency] private readonly SolutionContainerSystem _solutionContainer = default!;
         [Dependency] private readonly StunSystem _stun = default!;
         [Dependency] private readonly ThirstSystem _thirst = default!;
+        [Dependency] private readonly ForensicsSystem _forensics = default!;
 
         /// <summary>
         /// Make an entity vomit, if they have a stomach.
@@ -85,9 +86,7 @@ namespace Content.Server.Medical
 
             if (_puddle.TrySpillAt(uid, solution, out var puddle, false))
             {
-                var forensics = EnsureComp<ForensicsComponent>(puddle);
-                if (TryComp<DnaComponent>(uid, out var dna))
-                    forensics.DNAs.Add(dna.DNA);
+                _forensics.TransferDna(puddle, uid, false);
             }
 
             // Force sound to play as spill doesn't work if solution is empty.
index 1829dc32c8f204ae411ae5c3b5b62408171fbe6a..e45c904591e37e8ec91c02303695e5d58af3ed75 100644 (file)
@@ -54,6 +54,7 @@ public sealed class DrinkSystem : EntitySystem
     [Dependency] private readonly SharedInteractionSystem _interaction = default!;
     [Dependency] private readonly SolutionContainerSystem _solutionContainer = default!;
     [Dependency] private readonly StomachSystem _stomach = default!;
+    [Dependency] private readonly ForensicsSystem _forensics = default!;
 
     public override void Initialize()
     {
@@ -399,9 +400,7 @@ public sealed class DrinkSystem : EntitySystem
         //TODO: Grab the stomach UIDs somehow without using Owner
         _stomach.TryTransferSolution(firstStomach.Value.Comp.Owner, drained, firstStomach.Value.Comp);
 
-        var comp = EnsureComp<ForensicsComponent>(uid);
-        if (TryComp<DnaComponent>(args.Target, out var dna))
-            comp.DNAs.Add(dna.DNA);
+        _forensics.TransferDna(uid, args.Target.Value);
 
         if (!forceDrink && solution.Volume > 0)
             args.Repeat = true;
index 276393b728b00195ee424e0790ea59e02d0c41f6..63a558238b061df1bf4c567c83cb41dbe58f7fab 100644 (file)
@@ -15,6 +15,8 @@ using Content.Shared.Temperature;
 using Robust.Server.GameObjects;
 using Robust.Shared.Containers;
 using System.Linq;
+using Content.Shared.Inventory.Events;
+using Content.Server.Forensics;
 
 namespace Content.Server.Nutrition.EntitySystems
 {
@@ -30,6 +32,7 @@ namespace Content.Server.Nutrition.EntitySystems
         [Dependency] private readonly SharedItemSystem _items = default!;
         [Dependency] private readonly SharedContainerSystem _container = default!;
         [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+        [Dependency] private readonly ForensicsSystem _forensics = default!;
 
         private const float UpdateTimer = 3f;
 
@@ -44,6 +47,7 @@ namespace Content.Server.Nutrition.EntitySystems
         {
             SubscribeLocalEvent<SmokableComponent, IsHotEvent>(OnSmokableIsHotEvent);
             SubscribeLocalEvent<SmokableComponent, ComponentShutdown>(OnSmokableShutdownEvent);
+            SubscribeLocalEvent<SmokableComponent, GotEquippedEvent>(OnSmokeableEquipEvent);
 
             InitializeCigars();
             InitializePipes();
@@ -85,6 +89,14 @@ namespace Content.Server.Nutrition.EntitySystems
             _active.Remove(uid);
         }
 
+        private void OnSmokeableEquipEvent(EntityUid uid, SmokableComponent component, GotEquippedEvent args)
+        {
+            if (args.Slot == "mask")
+            {
+                _forensics.TransferDna(uid, args.Equipee, false);
+            }
+        }
+
         public override void Update(float frameTime)
         {
             _timer += frameTime;
index d49bb24fbb7d912c3ed689fc603b4aba2bb51d11..7300b78d76ba3a05a41c02808e6c9c7f92cfd700 100644 (file)
@@ -24,3 +24,30 @@ public sealed partial class ForensicPadDoAfterEvent : DoAfterEvent
 
     public override DoAfterEvent Clone() => this;
 }
+
+[Serializable, NetSerializable]
+public sealed partial class CleanForensicsDoAfterEvent : SimpleDoAfterEvent
+{
+}
+
+/// <summary>
+/// An event to apply DNA evidence from a donor onto some recipient.
+/// </summary>
+[ByRefEvent]
+public record struct TransferDnaEvent()
+{
+    /// <summary>
+    /// The entity donating the DNA.
+    /// </summary>
+    public EntityUid Donor;
+
+    /// <summary>
+    /// The entity receiving the DNA.
+    /// </summary>
+    public EntityUid Recipient;
+
+    /// <summary>
+    /// Can the DNA be cleaned off?
+    /// </summary>
+    public bool CanDnaBeCleaned = true;
+}
index f305125b99ef6eb412b620c9ea8191336fb546b7..ce84b1f7b3611e098530578ced39f4029ff8a8eb 100644 (file)
@@ -8,6 +8,7 @@ namespace Content.Shared.Forensics
         public readonly List<string> Fingerprints = new();
         public readonly List<string> Fibers = new();
         public readonly List<string> DNAs = new();
+        public readonly List<string> Residues = new();
         public readonly string LastScannedName = string.Empty;
         public readonly TimeSpan PrintCooldown = TimeSpan.Zero;
         public readonly TimeSpan PrintReadyAt = TimeSpan.Zero;
@@ -16,6 +17,7 @@ namespace Content.Shared.Forensics
             List<string> fingerprints,
             List<string> fibers,
             List<string> dnas,
+            List<string> residues,
             string lastScannedName,
             TimeSpan printCooldown,
             TimeSpan printReadyAt)
@@ -23,6 +25,7 @@ namespace Content.Shared.Forensics
             Fingerprints = fingerprints;
             Fibers = fibers;
             DNAs = dnas;
+            Residues = residues;
             LastScannedName = lastScannedName;
             PrintCooldown = printCooldown;
             PrintReadyAt = printReadyAt;
index b3b2421f205b38220e53625e227ead5d907bd43b..6c8284b9bec7008e7db8984c3cf5950e1567181c 100644 (file)
@@ -3,6 +3,7 @@ using System.Linq;
 using Content.Shared.Containers.ItemSlots;
 using Content.Shared.DoAfter;
 using Content.Shared.Examine;
+using Content.Shared.Forensics;
 using Content.Shared.IdentityManagement;
 using Content.Shared.Implants.Components;
 using Content.Shared.Popups;
@@ -72,6 +73,9 @@ public abstract class SharedImplanterSystem : EntitySystem
         else
             ImplantMode(implanter, component);
 
+        var ev = new TransferDnaEvent { Donor = target, Recipient = implanter };
+        RaiseLocalEvent(target, ref ev);
+
         Dirty(component);
     }
 
@@ -140,6 +144,10 @@ public abstract class SharedImplanterSystem : EntitySystem
                 implantComp.ImplantedEntity = null;
                 implanterContainer.Insert(implant);
                 permanentFound = implantComp.Permanent;
+
+                var ev = new TransferDnaEvent { Donor = target, Recipient = implanter };
+                RaiseLocalEvent(target, ref ev);
+
                 //Break so only one implant is drawn
                 break;
             }
index 88494820ec0ec499e8ee63e1a825cbe6e025920f..2326aeb9c21ec2e59258c2b0a325fd216b0bd276 100644 (file)
@@ -2,6 +2,7 @@ forensic-scanner-interface-title = Forensic scanner
 forensic-scanner-interface-fingerprints = Fingerprints
 forensic-scanner-interface-fibers = Fibers
 forensic-scanner-interface-dnas = DNAs
+forensic-scanner-interface-residues = Residues
 forensic-scanner-interface-no-data = No scan data available
 forensic-scanner-interface-print = Print
 forensic-scanner-interface-clear = Clear
diff --git a/Resources/Locale/en-US/forensics/residues.ftl b/Resources/Locale/en-US/forensics/residues.ftl
new file mode 100644 (file)
index 0000000..3c5fe3c
--- /dev/null
@@ -0,0 +1,11 @@
+forensic-residue = {LOC($adjective)} residue
+forensic-residue-colored = {LOC($adjective)} {LOC($color)} residue
+
+residue-unknown = unknown
+residue-slippery = slippery
+
+residue-green = green
+residue-blue = blue
+residue-red = red
+residue-grey = grey
+residue-brown = brown
\ No newline at end of file
index c7deba07328394217b763a3af469d6801a80ae86..4119b12a46b843f15c33e6c43cbf8dfbe1f11aa2 100644 (file)
       tags:
         - DroneUsable
         - Mop
+        - CleansForensics
+    - type: Fiber
+      fiberColor: fibers-white
index ca267a308ca9b3e004f90f0da9ac21f0c7441aa1..5905c6b12880b97c7e89bc355b100071e13c55c6 100644 (file)
@@ -7,6 +7,7 @@
   - type: Tag
     tags:
     - Soap
+    - CleansForensics
   - type: Sprite
     sprite: Objects/Specific/Janitorial/soap.rsi
     layers:
@@ -68,6 +69,9 @@
   - type: Food
     solution: soap
   - type: BadFood
+  - type: Residue
+    residueAdjective: residue-slippery
+    residueColor: residue-green
 
 - type: entity
   name: soap
@@ -90,6 +94,9 @@
         reagents:
         - ReagentId: SoapReagent
           Quantity: 100
+  - type: Residue
+    residueAdjective: residue-slippery
+    residueColor: residue-grey
 
 - type: entity
   name: soap
     fillBaseName: deluxe-
   - type: Item
     heldPrefix: deluxe
+  - type: Residue
+    residueAdjective: residue-slippery
+    residueColor: residue-brown
 
 - type: entity
   name: soap
     flavors:
       - clean
       - punishment
+  - type: Residue
+    residueAdjective: residue-slippery
+    residueColor: residue-red
 
 - type: entity
   name: soaplet
     flavors:
       - clean
       - meaty
+  - type: Residue
+    residueAdjective: residue-slippery
+    residueColor: residue-red
 
 - type: entity
   name: omega soap
         reagents:
         - ReagentId: SoapReagent
           Quantity: 240
+  - type: Residue
+    residueAdjective: residue-slippery
+    residueColor: residue-blue
\ No newline at end of file
index 7793d47d37b6da981a5a145cea0eb24be96bd1f6..dfdaa8578904e5078b37a41af6bb34703f5fad32 100644 (file)
 
 - type: Tag
   id:  boots
+
+- type: Tag
+  id: CleansForensics
\ No newline at end of file