]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Better DNA forensics & ReagentData (#26699)
authorSlamBamActionman <83650252+SlamBamActionman@users.noreply.github.com>
Thu, 8 Aug 2024 23:27:27 +0000 (01:27 +0200)
committerGitHub <noreply@github.com>
Thu, 8 Aug 2024 23:27:27 +0000 (09:27 +1000)
* Added the ability for blood to track DNA using ReagentData; Forensic Scanner now accounts for solution DNA, non-DNA holders have "Unknown DNA"

* Removes touch DNA for puddles, adds DNA to vomit

* DNA now leaves traces in containers and those marked without don't show DNA on scan (except for puddles), gibbed parts have DNA

* Fix stupid metamorphic glass bug grrr

* Removed SpillableComponent since DnaSubstanceTraceComponent is used instead

* Removes data field from maps, adds DNA tracking for some missed items

* Give default value, fix missing values.

* Fixes recipe bug

* Review changes

* Make the Data list into a nullable type

* Revert map changes

* Move gibbed unknown DNA to forensicssystem

45 files changed:
Content.Client/Forensics/ForensicScannerMenu.xaml.cs
Content.Server/Body/Systems/BloodstreamSystem.cs
Content.Server/Chemistry/EntitySystems/VaporSystem.cs
Content.Server/Chemistry/TileReactions/CleanDecalsReaction.cs
Content.Server/Chemistry/TileReactions/CleanTileReaction.cs
Content.Server/Chemistry/TileReactions/CreateEntityTileReaction.cs
Content.Server/Chemistry/TileReactions/ExtinguishTileReaction.cs
Content.Server/Chemistry/TileReactions/FlammableTileReaction.cs
Content.Server/Chemistry/TileReactions/PryTileReaction.cs
Content.Server/Chemistry/TileReactions/SpillIfPuddlePresentTileReaction.cs
Content.Server/Chemistry/TileReactions/SpillTileReaction.cs
Content.Server/Fluids/EntitySystems/PuddleSystem.cs
Content.Server/Fluids/EntitySystems/SmokeSystem.cs
Content.Server/Forensics/Components/DnaSubstanceTraceComponent.cs [new file with mode: 0644]
Content.Server/Forensics/Components/ForensicScannerComponent.cs
Content.Server/Forensics/Systems/ForensicScannerSystem.cs
Content.Server/Forensics/Systems/ForensicsSystem.cs
Content.Server/Implants/SubdermalImplantSystem.cs
Content.Server/Medical/VomitSystem.cs
Content.Shared/Chemistry/Components/Solution.cs
Content.Shared/Chemistry/EntitySystems/SharedSolutionContainerSystem.cs
Content.Shared/Chemistry/Reaction/ITileReaction.cs
Content.Shared/Chemistry/Reagent/DNAData.cs [new file with mode: 0644]
Content.Shared/Chemistry/Reagent/ReagentId.cs
Content.Shared/Chemistry/Reagent/ReagentPrototype.cs
Content.Shared/Chemistry/Reagent/ReagentQuantity.cs
Content.Shared/Forensics/Events.cs
Content.Shared/Forensics/ForensicScannerEvent.cs
Resources/Locale/en-US/forensics/forensics.ftl
Resources/Prototypes/Entities/Effects/puddle.yml
Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks-cartons.yml
Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks.yml
Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_bottles.yml
Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_cans.yml
Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_cups.yml
Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_special.yml
Resources/Prototypes/Entities/Objects/Consumable/Drinks/trash_drinks.yml
Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml
Resources/Prototypes/Entities/Objects/Specific/chemical-containers.yml
Resources/Prototypes/Entities/Objects/Specific/chemistry-bottles.yml
Resources/Prototypes/Entities/Objects/Specific/chemistry-vials.yml
Resources/Prototypes/Entities/Objects/Specific/chemistry.yml
Resources/Prototypes/Entities/Objects/Tools/bucket.yml
Resources/Prototypes/Entities/Structures/Specific/Janitor/janicart.yml
Resources/Prototypes/tags.yml

index 5f7f8e0056c652c7cc8732d3b96238016e72694d..dd013ed2357c5e61d187d9885eea28411251e881 100644 (file)
@@ -54,10 +54,16 @@ namespace Content.Client.Forensics
             }
             text.AppendLine();
             text.AppendLine(Loc.GetString("forensic-scanner-interface-dnas"));
-            foreach (var dna in msg.DNAs)
+            foreach (var dna in msg.TouchDNAs)
             {
                 text.AppendLine(dna);
             }
+            foreach (var dna in msg.SolutionDNAs)
+            {
+                if (msg.TouchDNAs.Contains(dna))
+                    continue;
+                text.AppendLine(dna);
+            }
             text.AppendLine();
             text.AppendLine(Loc.GetString("forensic-scanner-interface-residues"));
             foreach (var residue in msg.Residues)
index a8a86028d40b66c7411d3f3d8db444c7d7665668..9647634a9ebc396840106b543bf125ca9d2da622 100644 (file)
@@ -8,10 +8,12 @@ using Content.Shared.Alert;
 using Content.Shared.Chemistry.Components;
 using Content.Shared.Chemistry.EntitySystems;
 using Content.Shared.Chemistry.Reaction;
+using Content.Shared.Chemistry.Reagent;
 using Content.Shared.Damage;
 using Content.Shared.Damage.Prototypes;
 using Content.Shared.Drunk;
 using Content.Shared.FixedPoint;
+using Content.Shared.Forensics;
 using Content.Shared.HealthExaminable;
 using Content.Shared.Mobs.Systems;
 using Content.Shared.Popups;
@@ -54,6 +56,7 @@ public sealed class BloodstreamSystem : EntitySystem
         SubscribeLocalEvent<BloodstreamComponent, ReactionAttemptEvent>(OnReactionAttempt);
         SubscribeLocalEvent<BloodstreamComponent, SolutionRelayEvent<ReactionAttemptEvent>>(OnReactionAttempt);
         SubscribeLocalEvent<BloodstreamComponent, RejuvenateEvent>(OnRejuvenate);
+        SubscribeLocalEvent<BloodstreamComponent, GenerateDnaEvent>(OnDnaGenerated);
     }
 
     private void OnMapInit(Entity<BloodstreamComponent> ent, ref MapInitEvent args)
@@ -183,8 +186,18 @@ public sealed class BloodstreamSystem : EntitySystem
         bloodSolution.MaxVolume = entity.Comp.BloodMaxVolume;
         tempSolution.MaxVolume = entity.Comp.BleedPuddleThreshold * 4; // give some leeway, for chemstream as well
 
+        // Ensure blood that should have DNA has it; must be run here, in case DnaComponent has not yet been initialized
+
+        if (TryComp<DnaComponent>(entity.Owner, out var donorComp) && donorComp.DNA == String.Empty)
+        {
+            donorComp.DNA = _forensicsSystem.GenerateDNA();
+
+            var ev = new GenerateDnaEvent { Owner = entity.Owner, DNA = donorComp.DNA };
+            RaiseLocalEvent(entity.Owner, ref ev);
+        }
+
         // Fill blood solution with BLOOD
-        bloodSolution.AddReagent(entity.Comp.BloodReagent, entity.Comp.BloodMaxVolume - bloodSolution.Volume);
+        bloodSolution.AddReagent(new ReagentId(entity.Comp.BloodReagent, GetEntityBloodData(entity.Owner)), entity.Comp.BloodMaxVolume - bloodSolution.Volume);
     }
 
     private void OnDamageChanged(Entity<BloodstreamComponent> ent, ref DamageChangedEvent args)
@@ -349,7 +362,7 @@ public sealed class BloodstreamSystem : EntitySystem
         }
 
         if (amount >= 0)
-            return _solutionContainerSystem.TryAddReagent(component.BloodSolution.Value, component.BloodReagent, amount, out _);
+            return _solutionContainerSystem.TryAddReagent(component.BloodSolution.Value, component.BloodReagent, amount, null, GetEntityBloodData(uid));
 
         // Removal is more involved,
         // since we also wanna handle moving it to the temporary solution
@@ -370,10 +383,7 @@ public sealed class BloodstreamSystem : EntitySystem
                 tempSolution.AddSolution(temp, _prototypeManager);
             }
 
-            if (_puddleSystem.TrySpillAt(uid, tempSolution, out var puddleUid, sound: false))
-            {
-                _forensicsSystem.TransferDna(puddleUid, uid, canDnaBeCleaned: false);
-            }
+            _puddleSystem.TrySpillAt(uid, tempSolution, out var puddleUid, sound: false);
 
             tempSolution.RemoveAllSolution();
         }
@@ -436,10 +446,7 @@ public sealed class BloodstreamSystem : EntitySystem
             _solutionContainerSystem.RemoveAllSolution(component.TemporarySolution.Value);
         }
 
-        if (_puddleSystem.TrySpillAt(uid, tempSol, out var puddleUid))
-        {
-            _forensicsSystem.TransferDna(puddleUid, uid, canDnaBeCleaned: false);
-        }
+        _puddleSystem.TrySpillAt(uid, tempSol, out var puddleUid);
     }
 
     /// <summary>
@@ -464,6 +471,40 @@ public sealed class BloodstreamSystem : EntitySystem
         component.BloodReagent = reagent;
 
         if (currentVolume > 0)
-            _solutionContainerSystem.TryAddReagent(component.BloodSolution.Value, component.BloodReagent, currentVolume, out _);
+            _solutionContainerSystem.TryAddReagent(component.BloodSolution.Value, component.BloodReagent, currentVolume, null, GetEntityBloodData(uid));
+    }
+
+    private void OnDnaGenerated(Entity<BloodstreamComponent> entity, ref GenerateDnaEvent args)
+    {
+        if (_solutionContainerSystem.ResolveSolution(entity.Owner, entity.Comp.BloodSolutionName, ref entity.Comp.BloodSolution, out var bloodSolution))
+        {
+            foreach (var reagent in bloodSolution.Contents)
+            {
+                List<ReagentData> reagentData = reagent.Reagent.EnsureReagentData();
+                reagentData.RemoveAll(x => x is DnaData);
+                reagentData.AddRange(GetEntityBloodData(entity.Owner));
+            }
+        }
+    }
+
+    /// <summary>
+    /// Get the reagent data for blood that a specific entity should have.
+    /// </summary>
+    public List<ReagentData> GetEntityBloodData(EntityUid uid)
+    {
+        var bloodData = new List<ReagentData>();
+        var dnaData = new DnaData();
+
+        if (TryComp<DnaComponent>(uid, out var donorComp))
+        {
+            dnaData.DNA = donorComp.DNA;
+        } else
+        {
+            dnaData.DNA = Loc.GetString("forensics-dna-unknown");
+        }
+
+        bloodData.Add(dnaData);
+
+        return bloodData;
     }
 }
index dbb8572299f251e91026a9064af25b9bc2d4d365..5093b59409f4bcc67b8a80d094807ca462240a46 100644 (file)
@@ -123,7 +123,7 @@ namespace Content.Server.Chemistry.EntitySystems
                     var reagent = _protoManager.Index<ReagentPrototype>(reagentQuantity.Reagent.Prototype);
 
                     var reaction =
-                        reagent.ReactionTile(tile, (reagentQuantity.Quantity / vapor.TransferAmount) * 0.25f, EntityManager);
+                        reagent.ReactionTile(tile, (reagentQuantity.Quantity / vapor.TransferAmount) * 0.25f, EntityManager, reagentQuantity.Reagent.Data);
 
                     if (reaction > reagentQuantity.Quantity)
                     {
index 6958dabb81b15690f28e709afbc19e03362576d8..5047a2605211d86e4ade830476d2116a3c1c8d16 100644 (file)
@@ -21,10 +21,12 @@ public sealed partial class CleanDecalsReaction : ITileReaction
     [DataField]
     public FixedPoint2 CleanCost { get; private set; } = FixedPoint2.New(0.25f);
 
+
     public FixedPoint2 TileReact(TileRef tile,
         ReagentPrototype reagent,
         FixedPoint2 reactVolume,
-        IEntityManager entityManager)
+        IEntityManager entityManager,
+        List<ReagentData>? data)
     {
         if (reactVolume <= CleanCost ||
             !entityManager.TryGetComponent<MapGridComponent>(tile.GridUid, out var grid) ||
index 08f77de72dd3bb7c0448f1a9308649252e2d2582..ad02cb8ff903d4d7d735af8582d657e8b7d33a22 100644 (file)
@@ -34,7 +34,8 @@ public sealed partial class CleanTileReaction : ITileReaction
     FixedPoint2 ITileReaction.TileReact(TileRef tile,
         ReagentPrototype reagent,
         FixedPoint2 reactVolume,
-        IEntityManager entityManager)
+        IEntityManager entityManager
+        , List<ReagentData>? data)
     {
         var entities = entityManager.System<EntityLookupSystem>().GetLocalEntitiesIntersecting(tile, 0f).ToArray();
         var puddleQuery = entityManager.GetEntityQuery<PuddleComponent>();
index 6b106b1fc0a345be2ca0083bea9bf4c5acf62b34..0249b6255a2d98e597727535228c2e237b20f376 100644 (file)
@@ -38,7 +38,8 @@ public sealed partial class CreateEntityTileReaction : ITileReaction
     public FixedPoint2 TileReact(TileRef tile,
         ReagentPrototype reagent,
         FixedPoint2 reactVolume,
-        IEntityManager entityManager)
+        IEntityManager entityManager,
+        List<ReagentData>? data)
     {
         if (reactVolume >= Usage)
         {
index 198f650ac1a01182bb6f6a5c9ce566e43ba009fa..2b9475235f88fef33f925a2488776b37f771c255 100644 (file)
@@ -17,7 +17,8 @@ namespace Content.Server.Chemistry.TileReactions
         public FixedPoint2 TileReact(TileRef tile,
             ReagentPrototype reagent,
             FixedPoint2 reactVolume,
-            IEntityManager entityManager)
+            IEntityManager entityManager,
+            List<ReagentData>? data)
         {
             if (reactVolume <= FixedPoint2.Zero || tile.Tile.IsEmpty)
                 return FixedPoint2.Zero;
index b13b70d3d5c27f38aae53adb74582168d5740484..dd0b4960efcb58360e35f0c05121c0f574b0d657 100644 (file)
@@ -16,7 +16,8 @@ namespace Content.Server.Chemistry.TileReactions
         public FixedPoint2 TileReact(TileRef tile,
             ReagentPrototype reagent,
             FixedPoint2 reactVolume,
-            IEntityManager entityManager)
+            IEntityManager entityManager,
+            List<ReagentData>? data)
         {
             if (reactVolume <= FixedPoint2.Zero || tile.Tile.IsEmpty)
                 return FixedPoint2.Zero;
index c10b031720fefc864111aa3a0f055a73dc32e979..49971475c12b813c52bea4f77c9bf877d38cd0ac 100644 (file)
@@ -1,4 +1,4 @@
-using Content.Server.Maps;
+using Content.Server.Maps;
 using Content.Shared.Chemistry.Reaction;
 using Content.Shared.Chemistry.Reagent;
 using Content.Shared.FixedPoint;
@@ -15,7 +15,8 @@ public sealed partial class PryTileReaction : ITileReaction
     public FixedPoint2 TileReact(TileRef tile,
         ReagentPrototype reagent,
         FixedPoint2 reactVolume,
-        IEntityManager entityManager)
+        IEntityManager entityManager,
+        List<ReagentData>? data)
     {
         var sys = entityManager.System<TileSystem>();
         sys.PryTile(tile);
index 6b46b8949594c436c2bd96086ef7d9ab852778c6..8c8b371da36c79e6d46575eebddc047be7197451 100644 (file)
@@ -15,13 +15,14 @@ namespace Content.Server.Chemistry.TileReactions
         public FixedPoint2 TileReact(TileRef tile,
             ReagentPrototype reagent,
             FixedPoint2 reactVolume,
-            IEntityManager entityManager)
+            IEntityManager entityManager,
+            List<ReagentData>? data)
         {
             var spillSystem = entityManager.System<PuddleSystem>();
             if (reactVolume < 5 || !spillSystem.TryGetPuddle(tile, out _))
                 return FixedPoint2.Zero;
 
-            return spillSystem.TrySpillAt(tile, new Solution(reagent.ID, reactVolume), out _, sound: false, tileReact: false)
+            return spillSystem.TrySpillAt(tile, new Solution(reagent.ID, reactVolume, data), out _, sound: false, tileReact: false)
                 ? reactVolume
                 : FixedPoint2.Zero;
         }
index fadc7147c9c66c577805e1567180f600ac95e2d9..f9fb2b90d0be752f5b7306002d988c53687b75cd 100644 (file)
@@ -29,13 +29,14 @@ namespace Content.Server.Chemistry.TileReactions
         public FixedPoint2 TileReact(TileRef tile,
             ReagentPrototype reagent,
             FixedPoint2 reactVolume,
-            IEntityManager entityManager)
+            IEntityManager entityManager,
+            List<ReagentData>? data)
         {
             if (reactVolume < 5)
                 return FixedPoint2.Zero;
 
             if (entityManager.EntitySysManager.GetEntitySystem<PuddleSystem>()
-                .TrySpillAt(tile, new Solution(reagent.ID, reactVolume), out var puddleUid, false, false))
+                .TrySpillAt(tile, new Solution(reagent.ID, reactVolume, data), out var puddleUid, false, false))
             {
                 var slippery = entityManager.EnsureComponent<SlipperyComponent>(puddleUid);
                 slippery.LaunchForwardsMultiplier = _launchForwardsMultiplier;
index a232ed8c0eb433291ed534c15277376996ae38a1..67ba2a76bb11b99101b6dfb3e16639de85612e62 100644 (file)
@@ -712,7 +712,7 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
         {
             var (reagent, quantity) = solution.Contents[i];
             var proto = _prototypeManager.Index<ReagentPrototype>(reagent.Prototype);
-            var removed = proto.ReactionTile(tileRef, quantity, EntityManager);
+            var removed = proto.ReactionTile(tileRef, quantity, EntityManager, reagent.Data);
             if (removed <= FixedPoint2.Zero)
                 continue;
 
index 78693bfdbcc7ad54b6e4b6050cbb0d48ace03022..72450562f1e98ebac9852d696e9607313cba9aff 100644 (file)
@@ -315,7 +315,7 @@ public sealed class SmokeSystem : EntitySystem
                 continue;
 
             var reagent = _prototype.Index<ReagentPrototype>(reagentQuantity.Reagent.Prototype);
-            reagent.ReactionTile(tile, reagentQuantity.Quantity, EntityManager);
+            reagent.ReactionTile(tile, reagentQuantity.Quantity, EntityManager, reagentQuantity.Reagent.Data);
         }
     }
 
diff --git a/Content.Server/Forensics/Components/DnaSubstanceTraceComponent.cs b/Content.Server/Forensics/Components/DnaSubstanceTraceComponent.cs
new file mode 100644 (file)
index 0000000..7b3cb97
--- /dev/null
@@ -0,0 +1,9 @@
+namespace Content.Server.Forensics;
+
+/// <summary>
+/// This component stops the entity from leaving finger prints,
+/// usually so fibres can be left instead.
+/// </summary>
+[RegisterComponent]
+public sealed partial class DnaSubstanceTraceComponent : Component
+{ }
index ad262138482fdd94adcc29f8bfcab9c16fbe43e4..033ea913c3f5e37ec801d927a2197020bfa393c0 100644 (file)
@@ -26,7 +26,13 @@ namespace Content.Server.Forensics
         /// DNA that the forensic scanner found from the <see cref="DNAComponent"/> on an entity.
         /// </summary>
         [ViewVariables(VVAccess.ReadOnly), DataField("dnas")]
-        public List<string> DNAs = new();
+        public List<string> TouchDNAs = new();
+
+        /// <summary>
+        /// DNA that the forensic scanner found from the solution containers in an entity.
+        /// </summary>
+        [ViewVariables(VVAccess.ReadOnly), DataField]
+        public List<string> SolutionDNAs = new();
 
         /// <summary>
         /// Residue that the forensic scanner found from the <see cref="ForensicsComponent"/> on an entity.
index 80062569b85466fd9326466f90858cde598fe084..b6dc068f7893b5824ba75a4db4d01551a9953379 100644 (file)
@@ -3,16 +3,19 @@ using System.Text;
 using Content.Server.Popups;
 using Content.Shared.UserInterface;
 using Content.Shared.DoAfter;
+using Content.Shared.Fluids.Components;
 using Content.Shared.Forensics;
 using Content.Shared.Hands.EntitySystems;
 using Content.Shared.Interaction;
 using Content.Shared.Paper;
 using Content.Shared.Verbs;
+using Content.Shared.Tag;
 using Robust.Shared.Audio.Systems;
 using Robust.Server.GameObjects;
 using Robust.Shared.Audio;
 using Robust.Shared.Player;
 using Robust.Shared.Timing;
+using Content.Server.Chemistry.Containers.EntitySystems;
 // todo: remove this stinky LINQy
 
 namespace Content.Server.Forensics
@@ -27,6 +30,9 @@ namespace Content.Server.Forensics
         [Dependency] private readonly SharedHandsSystem _handsSystem = default!;
         [Dependency] private readonly SharedAudioSystem _audioSystem = default!;
         [Dependency] private readonly MetaDataSystem _metaData = default!;
+        [Dependency] private readonly ForensicsSystem _forensicsSystem = default!;
+        [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
+        [Dependency] private readonly TagSystem _tag = default!;
 
         public override void Initialize()
         {
@@ -46,7 +52,8 @@ namespace Content.Server.Forensics
             var state = new ForensicScannerBoundUserInterfaceState(
                 component.Fingerprints,
                 component.Fibers,
-                component.DNAs,
+                component.TouchDNAs,
+                component.SolutionDNAs,
                 component.Residues,
                 component.LastScannedName,
                 component.PrintCooldown,
@@ -69,18 +76,25 @@ namespace Content.Server.Forensics
                 {
                     scanner.Fingerprints = new();
                     scanner.Fibers = new();
-                    scanner.DNAs = new();
+                    scanner.TouchDNAs = new();
                     scanner.Residues = new();
                 }
-
                 else
                 {
                     scanner.Fingerprints = forensics.Fingerprints.ToList();
                     scanner.Fibers = forensics.Fibers.ToList();
-                    scanner.DNAs = forensics.DNAs.ToList();
+                    scanner.TouchDNAs = forensics.DNAs.ToList();
                     scanner.Residues = forensics.Residues.ToList();
                 }
 
+                if (_tag.HasTag(args.Args.Target.Value, "DNASolutionScannable"))
+                {
+                    scanner.SolutionDNAs = _forensicsSystem.GetSolutionsDNA(args.Args.Target.Value);
+                } else
+                {
+                    scanner.SolutionDNAs = new();
+                }
+
                 scanner.LastScannedName = MetaData(args.Args.Target.Value).EntityName;
             }
 
@@ -206,8 +220,15 @@ namespace Content.Server.Forensics
             }
             text.AppendLine();
             text.AppendLine(Loc.GetString("forensic-scanner-interface-dnas"));
-            foreach (var dna in component.DNAs)
+            foreach (var dna in component.TouchDNAs)
+            {
+                text.AppendLine(dna);
+            }
+            foreach (var dna in component.SolutionDNAs)
             {
+                Log.Debug(dna);
+                if (component.TouchDNAs.Contains(dna))
+                    continue;
                 text.AppendLine(dna);
             }
             text.AppendLine();
@@ -232,7 +253,8 @@ namespace Content.Server.Forensics
         {
             component.Fingerprints = new();
             component.Fibers = new();
-            component.DNAs = new();
+            component.TouchDNAs = new();
+            component.SolutionDNAs = new();
             component.LastScannedName = string.Empty;
 
             UpdateUserInterface(uid, component);
index c0d990aa597b6ba509cb4ff59bdac187e9b25543..ec311d136a07f3f92d086ccf7d947ee2e47d843a 100644 (file)
@@ -1,11 +1,16 @@
 using Content.Server.Body.Components;
+using Content.Server.Chemistry.Containers.EntitySystems;
 using Content.Server.DoAfter;
 using Content.Server.Fluids.EntitySystems;
 using Content.Server.Forensics.Components;
 using Content.Server.Popups;
+using Content.Shared.Chemistry.EntitySystems;
 using Content.Shared.Popups;
 using Content.Shared.Chemistry.Components;
+using Content.Shared.Chemistry.Reagent;
+using Content.Shared.Chemistry.Components.SolutionManager;
 using Content.Shared.DoAfter;
+using Content.Shared.Fluids.Components;
 using Content.Shared.Forensics;
 using Content.Shared.Interaction;
 using Content.Shared.Interaction.Events;
@@ -23,20 +28,35 @@ namespace Content.Server.Forensics
         [Dependency] private readonly InventorySystem _inventory = default!;
         [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
         [Dependency] private readonly PopupSystem _popupSystem = default!;
+        [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
+
         public override void Initialize()
         {
             SubscribeLocalEvent<FingerprintComponent, ContactInteractionEvent>(OnInteract);
             SubscribeLocalEvent<FingerprintComponent, MapInitEvent>(OnFingerprintInit);
             SubscribeLocalEvent<DnaComponent, MapInitEvent>(OnDNAInit);
 
-            SubscribeLocalEvent<DnaComponent, BeingGibbedEvent>(OnBeingGibbed);
+            SubscribeLocalEvent<ForensicsComponent, BeingGibbedEvent>(OnBeingGibbed);
             SubscribeLocalEvent<ForensicsComponent, MeleeHitEvent>(OnMeleeHit);
             SubscribeLocalEvent<ForensicsComponent, GotRehydratedEvent>(OnRehydrated);
             SubscribeLocalEvent<CleansForensicsComponent, AfterInteractEvent>(OnAfterInteract, after: new[] { typeof(AbsorbentSystem) });
             SubscribeLocalEvent<ForensicsComponent, CleanForensicsDoAfterEvent>(OnCleanForensicsDoAfter);
             SubscribeLocalEvent<DnaComponent, TransferDnaEvent>(OnTransferDnaEvent);
+            SubscribeLocalEvent<DnaSubstanceTraceComponent, SolutionContainerChangedEvent>(OnSolutionChanged);
             SubscribeLocalEvent<CleansForensicsComponent, GetVerbsEvent<UtilityVerb>>(OnUtilityVerb);
+        }
 
+        private void OnSolutionChanged(Entity<DnaSubstanceTraceComponent> ent, ref SolutionContainerChangedEvent ev)
+        {
+            var soln = GetSolutionsDNA(ev.Solution);
+            if (soln.Count > 0)
+            {
+                var comp = EnsureComp<ForensicsComponent>(ent.Owner);
+                foreach (string dna in soln)
+                {
+                    comp.DNAs.Add(dna);
+                }
+            }
         }
 
         private void OnInteract(EntityUid uid, FingerprintComponent component, ContactInteractionEvent args)
@@ -51,15 +71,26 @@ namespace Content.Server.Forensics
 
         private void OnDNAInit(EntityUid uid, DnaComponent component, MapInitEvent args)
         {
-            component.DNA = GenerateDNA();
+            if (component.DNA == String.Empty)
+            {
+                component.DNA = GenerateDNA();
+
+                var ev = new GenerateDnaEvent { Owner = uid, DNA = component.DNA };
+                RaiseLocalEvent(uid, ref ev);
+            }
         }
 
-        private void OnBeingGibbed(EntityUid uid, DnaComponent component, BeingGibbedEvent args)
+        private void OnBeingGibbed(EntityUid uid, ForensicsComponent component, BeingGibbedEvent args)
         {
+            string dna = Loc.GetString("forensics-dna-unknown");
+
+            if (TryComp(uid, out DnaComponent? dnaComp))
+                dna = dnaComp.DNA;
+
             foreach (EntityUid part in args.GibbedParts)
             {
                 var partComp = EnsureComp<ForensicsComponent>(part);
-                partComp.DNAs.Add(component.DNA);
+                partComp.DNAs.Add(dna);
                 partComp.CanDnaBeCleaned = false;
             }
         }
@@ -106,6 +137,34 @@ namespace Content.Server.Forensics
             }
         }
 
+        public List<string> GetSolutionsDNA(EntityUid uid)
+        {
+            List<string> list = new();
+            if (TryComp<SolutionContainerManagerComponent>(uid, out var comp))
+            {
+                foreach (var (_, soln) in _solutionContainerSystem.EnumerateSolutions((uid, comp)))
+                {
+                    list.AddRange(GetSolutionsDNA(soln.Comp.Solution));
+                }
+            }
+            return list;
+        }
+
+        public List<string> GetSolutionsDNA(Solution soln)
+        {
+            List<string> list = new();
+            foreach (var reagent in soln.Contents)
+            {
+                foreach (var data in reagent.Reagent.EnsureReagentData())
+                {
+                    if (data is DnaData)
+                    {
+                        list.Add(((DnaData) data).DNA);
+                    }
+                }
+            }
+            return list;
+        }
         private void OnAfterInteract(Entity<CleansForensicsComponent> cleanForensicsEntity, ref AfterInteractEvent args)
         {
             if (args.Handled || !args.CanReach || args.Target == null)
index 88c5fb9459253d80c7958fd115e409274511cbfc..7c69ec8ea5448d75a9e911639c3058662651e11e 100644 (file)
@@ -1,10 +1,11 @@
-using Content.Server.Cuffs;
+using Content.Server.Cuffs;
 using Content.Server.Forensics;
 using Content.Server.Humanoid;
 using Content.Server.Implants.Components;
 using Content.Server.Store.Components;
 using Content.Server.Store.Systems;
 using Content.Shared.Cuffs.Components;
+using Content.Shared.Forensics;
 using Content.Shared.Humanoid;
 using Content.Shared.Implants;
 using Content.Shared.Implants.Components;
@@ -212,6 +213,9 @@ public sealed class SubdermalImplantSystem : SharedSubdermalImplantSystem
             if (TryComp<DnaComponent>(ent, out var dna))
             {
                 dna.DNA = _forensicsSystem.GenerateDNA();
+
+                var ev = new GenerateDnaEvent { Owner = ent, DNA = dna.DNA };
+                RaiseLocalEvent(ent, ref ev);
             }
             if (TryComp<FingerprintComponent>(ent, out var fingerprint))
             {
index ec04a27db63e7c042813f7d32f7e192657ba4eed..4cc4e538cee8962fae2283fca13b14a4a0f2ed80 100644 (file)
@@ -6,6 +6,7 @@ using Content.Server.Forensics;
 using Content.Server.Popups;
 using Content.Server.Stunnable;
 using Content.Shared.Chemistry.Components;
+using Content.Shared.Chemistry.Reagent;
 using Content.Shared.IdentityManagement;
 using Content.Shared.Nutrition.Components;
 using Content.Shared.Nutrition.EntitySystems;
@@ -28,6 +29,7 @@ namespace Content.Server.Medical
         [Dependency] private readonly StunSystem _stun = default!;
         [Dependency] private readonly ThirstSystem _thirst = default!;
         [Dependency] private readonly ForensicsSystem _forensics = default!;
+        [Dependency] private readonly BloodstreamSystem _bloodstream = default!;
 
         /// <summary>
         /// Make an entity vomit, if they have a stomach.
@@ -83,7 +85,7 @@ namespace Content.Server.Medical
                 }
 
                 // Makes a vomit solution the size of 90% of the chemicals removed from the chemstream
-                solution.AddReagent("Vomit", vomitAmount); // TODO: Dehardcode vomit prototype
+                solution.AddReagent(new ReagentId("Vomit", _bloodstream.GetEntityBloodData(uid)), vomitAmount); // TODO: Dehardcode vomit prototype
             }
 
             if (_puddle.TrySpillAt(uid, solution, out var puddle, false))
index 4de3c369f7cce5e09ba51941dc97dc06372849fc..725a685a8be9249dead715321519f0463af58f48 100644 (file)
@@ -147,7 +147,7 @@ namespace Content.Shared.Chemistry.Components
         /// </summary>
         /// <param name="prototype">The prototype ID of the reagent to add.</param>
         /// <param name="quantity">The quantity in milli-units.</param>
-        public Solution(string prototype, FixedPoint2 quantity, ReagentData? data = null) : this()
+        public Solution(string prototype, FixedPoint2 quantity, List<ReagentData>? data = null) : this()
         {
             AddReagent(new ReagentId(prototype, data), quantity);
         }
@@ -243,7 +243,7 @@ namespace Content.Shared.Chemistry.Components
             return false;
         }
 
-        public bool ContainsReagent(string reagentId, ReagentData? data)
+        public bool ContainsReagent(string reagentId, List<ReagentData>? data)
             => ContainsReagent(new(reagentId, data));
 
         public bool TryGetReagent(ReagentId id, out ReagentQuantity quantity)
@@ -404,7 +404,7 @@ namespace Content.Shared.Chemistry.Components
         /// </summary>
         /// <param name="proto">The prototype of the reagent to add.</param>
         /// <param name="quantity">The quantity in milli-units.</param>
-        public void AddReagent(ReagentPrototype proto, FixedPoint2 quantity, float temperature, IPrototypeManager? protoMan, ReagentData? data = null)
+        public void AddReagent(ReagentPrototype proto, FixedPoint2 quantity, float temperature, IPrototypeManager? protoMan, List<ReagentData>? data = null)
         {
             if (_heatCapacityDirty)
                 UpdateHeatCapacity(protoMan);
@@ -489,7 +489,7 @@ namespace Content.Shared.Chemistry.Components
             {
                 var (reagent, curQuantity) = Contents[i];
 
-                if(reagent != toRemove.Reagent)
+                if(reagent.Prototype != toRemove.Reagent.Prototype)
                     continue;
 
                 var newQuantity = curQuantity - toRemove.Quantity;
@@ -523,7 +523,7 @@ namespace Content.Shared.Chemistry.Components
         /// <param name="prototype">The prototype of the reagent to be removed.</param>
         /// <param name="quantity">The amount of reagent to remove.</param>
         /// <returns>How much reagent was actually removed. Zero if the reagent is not present on the solution.</returns>
-        public FixedPoint2 RemoveReagent(string prototype, FixedPoint2 quantity, ReagentData? data = null)
+        public FixedPoint2 RemoveReagent(string prototype, FixedPoint2 quantity, List<ReagentData>? data = null)
         {
             return RemoveReagent(new ReagentQuantity(prototype, quantity, data));
         }
index 7e00157b6ee3b9d63d6d87025c971971e0cfc436..ad9cd01fbf2be12775b18065416dfcd9fc8e0092 100644 (file)
@@ -452,7 +452,7 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem
     /// <param name="quantity">The amount of reagent to add.</param>
     /// <returns>If all the reagent could be added.</returns>
     [PublicAPI]
-    public bool TryAddReagent(Entity<SolutionComponent> soln, string prototype, FixedPoint2 quantity, float? temperature = null, ReagentData? data = null)
+    public bool TryAddReagent(Entity<SolutionComponent> soln, string prototype, FixedPoint2 quantity, float? temperature = null, List<ReagentData>? data = null)
         => TryAddReagent(soln, new ReagentQuantity(prototype, quantity, data), out _, temperature);
 
     /// <summary>
@@ -464,7 +464,7 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem
     /// <param name="quantity">The amount of reagent to add.</param>
     /// <param name="acceptedQuantity">The amount of reagent successfully added.</param>
     /// <returns>If all the reagent could be added.</returns>
-    public bool TryAddReagent(Entity<SolutionComponent> soln, string prototype, FixedPoint2 quantity, out FixedPoint2 acceptedQuantity, float? temperature = null, ReagentData? data = null)
+    public bool TryAddReagent(Entity<SolutionComponent> soln, string prototype, FixedPoint2 quantity, out FixedPoint2 acceptedQuantity, float? temperature = null, List<ReagentData>? data = null)
     {
         var reagent = new ReagentQuantity(prototype, quantity, data);
         return TryAddReagent(soln, reagent, out acceptedQuantity, temperature);
@@ -513,7 +513,7 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem
     /// <param name="prototype">The Id of the reagent to remove.</param>
     /// <param name="quantity">The amount of reagent to remove.</param>
     /// <returns>If the reagent to remove was found in the container.</returns>
-    public bool RemoveReagent(Entity<SolutionComponent> soln, string prototype, FixedPoint2 quantity, ReagentData? data = null)
+    public bool RemoveReagent(Entity<SolutionComponent> soln, string prototype, FixedPoint2 quantity, List<ReagentData>? data = null)
     {
         return RemoveReagent(soln, new ReagentQuantity(prototype, quantity, data));
     }
index af6cc34f48a2b452e1d7ea3b977035f9b333091d..31b7c9129c19eee3ff8debfeee80476d7c5f7ec6 100644 (file)
@@ -1,4 +1,4 @@
-using Content.Shared.Chemistry.Reagent;
+using Content.Shared.Chemistry.Reagent;
 using Content.Shared.FixedPoint;
 using Robust.Shared.Map;
 
@@ -9,6 +9,7 @@ namespace Content.Shared.Chemistry.Reaction
         FixedPoint2 TileReact(TileRef tile,
             ReagentPrototype reagent,
             FixedPoint2 reactVolume,
-            IEntityManager entityManager);
+            IEntityManager entityManager,
+            List<ReagentData>? data = null);
     }
 }
diff --git a/Content.Shared/Chemistry/Reagent/DNAData.cs b/Content.Shared/Chemistry/Reagent/DNAData.cs
new file mode 100644 (file)
index 0000000..e75e994
--- /dev/null
@@ -0,0 +1,28 @@
+using Content.Shared.FixedPoint;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Chemistry.Reagent;
+
+[ImplicitDataDefinitionForInheritors, Serializable, NetSerializable]
+public sealed partial class DnaData : ReagentData
+{
+    [DataField]
+    public string DNA = String.Empty;
+
+    public override ReagentData Clone() => this;
+
+    public override bool Equals(ReagentData? other)
+    {
+        if (other == null)
+        {
+            return false;
+        }
+
+        return ((DnaData) other).DNA == DNA;
+    }
+
+    public override int GetHashCode()
+    {
+        return DNA.GetHashCode();
+    }
+}
index 07a420021971e2703ccdb27f7e3baf8ba8aefb4f..798dd28db490aeab83ad55dc8308f04804795b35 100644 (file)
@@ -1,6 +1,7 @@
-using Content.Shared.FixedPoint;
+using Content.Shared.FixedPoint;
 using Robust.Shared.Serialization;
 using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
+using System.Linq;
 
 namespace Content.Shared.Chemistry.Reagent;
 
@@ -20,17 +21,23 @@ public partial struct ReagentId : IEquatable<ReagentId>
     /// Any additional data that is unique to this reagent type. E.g., for blood this could be DNA data.
     /// </summary>
     [DataField("data")]
-    public ReagentData? Data { get; private set; }
+    public List<ReagentData>? Data { get; private set; } = new();
 
-    public ReagentId(string prototype, ReagentData? data)
+    public ReagentId(string prototype, List<ReagentData>? data)
     {
         Prototype = prototype;
-        Data = data;
+        Data = data ?? new();
     }
 
     public ReagentId()
     {
         Prototype = default!;
+        Data = new();
+    }
+
+    public List<ReagentData> EnsureReagentData()
+    {
+        return (Data != null) ? Data : new List<ReagentData>();
     }
 
     public bool Equals(ReagentId other)
@@ -44,13 +51,13 @@ public partial struct ReagentId : IEquatable<ReagentId>
         if (other.Data == null)
             return false;
 
-        if (Data.GetType() != other.Data.GetType())
+        if (Data.Except(other.Data).Any() || other.Data.Except(Data).Any() || Data.Count != other.Data.Count)
             return false;
 
-        return Data.Equals(other.Data);
+        return true;
     }
 
-    public bool Equals(string prototype, ReagentData? otherData = null)
+    public bool Equals(string prototype, List<ReagentData>? otherData = null)
     {
         if (Prototype != prototype)
             return false;
@@ -73,12 +80,12 @@ public partial struct ReagentId : IEquatable<ReagentId>
 
     public string ToString(FixedPoint2 quantity)
     {
-        return Data?.ToString(Prototype, quantity) ?? $"{Prototype}:{quantity}";
+        return $"{Prototype}:{quantity}";
     }
 
     public override string ToString()
     {
-        return Data?.ToString(Prototype) ?? Prototype;
+        return $"{Prototype}";
     }
 
     public static bool operator ==(ReagentId left, ReagentId right)
index fe937b9de4301fe276b6e3c3cf88e33b9d60a606..dba2ba03a3f060fea1a8ece0f3c140ff927c390e 100644 (file)
@@ -143,7 +143,7 @@ namespace Content.Shared.Chemistry.Reagent
         [DataField]
         public SoundSpecifier FootstepSound = new SoundCollectionSpecifier("FootstepWater", AudioParams.Default.WithVolume(6));
 
-        public FixedPoint2 ReactionTile(TileRef tile, FixedPoint2 reactVolume, IEntityManager entityManager)
+        public FixedPoint2 ReactionTile(TileRef tile, FixedPoint2 reactVolume, IEntityManager entityManager, List<ReagentData>? data)
         {
             var removed = FixedPoint2.Zero;
 
@@ -152,7 +152,7 @@ namespace Content.Shared.Chemistry.Reagent
 
             foreach (var reaction in TileReactions)
             {
-                removed += reaction.TileReact(tile, this, reactVolume - removed, entityManager);
+                removed += reaction.TileReact(tile, this, reactVolume - removed, entityManager, data);
 
                 if (removed > reactVolume)
                     throw new Exception("Removed more than we have!");
index 9644f919f748a74999968f14fe1e749218c18a6c..4cc87f5bd78d31b7f89ed32df206a18beb2e3b7f 100644 (file)
@@ -17,7 +17,7 @@ public partial struct ReagentQuantity : IEquatable<ReagentQuantity>
     [ViewVariables]
     public ReagentId Reagent { get; private set; }
 
-    public ReagentQuantity(string reagentId, FixedPoint2 quantity, ReagentData? data)
+    public ReagentQuantity(string reagentId, FixedPoint2 quantity, List<ReagentData>? data = null)
         : this(new ReagentId(reagentId, data), quantity)
     {
     }
@@ -37,7 +37,7 @@ public partial struct ReagentQuantity : IEquatable<ReagentQuantity>
         return Reagent.ToString(Quantity);
     }
 
-    public void Deconstruct(out string prototype, out FixedPoint2 quantity, out ReagentData? data)
+    public void Deconstruct(out string prototype, out FixedPoint2 quantity, out List<ReagentData>? data)
     {
         prototype = Reagent.Prototype;
         quantity = Quantity;
index 7300b78d76ba3a05a41c02808e6c9c7f92cfd700..f7b9475cb577c8facefb98a7774650043332b57f 100644 (file)
@@ -51,3 +51,20 @@ public record struct TransferDnaEvent()
     /// </summary>
     public bool CanDnaBeCleaned = true;
 }
+
+/// <summary>
+/// An event to generate and act upon new DNA for an entity.
+/// </summary>
+[ByRefEvent]
+public record struct GenerateDnaEvent()
+{
+    /// <summary>
+    /// The entity getting new DNA.
+    /// </summary>
+    public EntityUid Owner;
+
+    /// <summary>
+    /// The generated DNA.
+    /// </summary>
+    public string DNA;
+}
index ce84b1f7b3611e098530578ced39f4029ff8a8eb..f275680b0575145641bbcf1f998fa25e6c2c789a 100644 (file)
@@ -7,7 +7,8 @@ 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> TouchDNAs = new();
+        public readonly List<string> SolutionDNAs = new();
         public readonly List<string> Residues = new();
         public readonly string LastScannedName = string.Empty;
         public readonly TimeSpan PrintCooldown = TimeSpan.Zero;
@@ -16,7 +17,8 @@ namespace Content.Shared.Forensics
         public ForensicScannerBoundUserInterfaceState(
             List<string> fingerprints,
             List<string> fibers,
-            List<string> dnas,
+            List<string> touchDnas,
+            List<string> solutionDnas,
             List<string> residues,
             string lastScannedName,
             TimeSpan printCooldown,
@@ -24,7 +26,8 @@ namespace Content.Shared.Forensics
         {
             Fingerprints = fingerprints;
             Fibers = fibers;
-            DNAs = dnas;
+            TouchDNAs = touchDnas;
+            SolutionDNAs = solutionDnas;
             Residues = residues;
             LastScannedName = lastScannedName;
             PrintCooldown = printCooldown;
index 29e2db8f78872ad9b177185b29dead35b60b328f..712e8511bb061fd7f9f2f2fa75bb5aa0747eb415 100644 (file)
@@ -23,8 +23,10 @@ forensic-scanner-verb-message = Perform a forensic scan
 forensic-pad-fingerprint-name = {$entity}'s fingerprints
 forensic-pad-gloves-name = fibers from {$entity}
 
+forensics-dna-unknown = unknown DNA
+
 forensics-verb-text = Remove evidence
 forensics-verb-message = Remove fingerprints and DNA residues from the object!
 forensics-cleaning = You begin cleaning the evidence off of {THE($target)}...
 
-forensics-cleaning-cannot-clean = There is nothing cleanable on {THE($target)}!
\ No newline at end of file
+forensics-cleaning-cannot-clean = There is nothing cleanable on {THE($target)}!
index 62bb923a61b309fb72b8a477b794f0a1be44fb23..36f0faa1df1a8918d5abd07989a66707f9ca812f 100644 (file)
     solution: puddle
   - type: BadDrink
   - type: IgnoresFingerprints
+  - type: Tag
+    tags:
+    - DNASolutionScannable
index 0c069f615ee94fd19acbf39dadc808f9fef3cfb6..9dd3cffbe6929d168e3d5abae1384fbb99ddd854 100644 (file)
@@ -42,6 +42,7 @@
         #In future maybe add generic plastic scrap trash/debris
   - type: TrashOnSolutionEmpty
     solution: drink
+  - type: DnaSubstanceTrace
 
 - type: entity
   parent: DrinkCartonBaseFull
index c435e6c98fe9dccb79a914f1b9e52a4947ff33c6..4a9d5c04635ddf7aff8e49eeb76762f8df173c6f 100644 (file)
@@ -35,6 +35,7 @@
     interfaces:
       enum.TransferAmountUiKey.Key:
         type: TransferAmountBoundUserInterface
+  - type: DnaSubstanceTrace
 
 - type: entity
   parent: DrinkBase
index a5f72b85460bc787d5d168f19045ec285947cb7e..86309124516f11806d642495a85d3044b61db964 100644 (file)
@@ -39,6 +39,7 @@
   - type: PhysicalComposition
     materialComposition:
       Plastic: 100
+  - type: DnaSubstanceTrace
   - type: PressurizedSolution
     solution: drink
   - type: Shakeable
index b6488dbe8bf0285767b8431624c3eae0c0535569..f5b733d37b1ece8814558196706566f63333ffb7 100644 (file)
@@ -66,6 +66,7 @@
   - type: Tag
     tags:
     - DrinkCan
+  - type: DnaSubstanceTrace
 
 - type: entity
   parent: DrinkCanBaseFull
index d13c9ad2ca962e4eebc3b3afd7965d72de428170..f0103cc2197b3d9feeb206b38872d179e7773989 100644 (file)
@@ -38,6 +38,7 @@
     damage:
       types:
         Blunt: 0
+  - type: DnaSubstanceTrace
 
 - type: entity
   parent: DrinkBaseCup
index dfb6ac31c0383ab04c992c12df35b1f0eaf74b87..930cf8175762af5a451c598a43db82becbaf6d53 100644 (file)
@@ -34,6 +34,7 @@
   - type: PhysicalComposition
     materialComposition:
       Steel: 50
+  - type: DnaSubstanceTrace
   - type: ReactionMixer
     mixOnInteract: false
     reactionTypes:
index 9f94be9576bdec4225f8e113850ec8bab51620b8..eadeeabd7479426b58b6b42c9622a4dfd5af87a6 100644 (file)
@@ -76,6 +76,7 @@
     materialComposition:
       Glass: 100
   - type: SpaceGarbage
+  - type: DnaSubstanceTrace
 
 - type: entity
   name: base empty bottle
index 2a6f314904a0e976ab98ff4d2df3adf4a36ac7b9..ccb79008105a930c11d137aedfbf25a056c9ea6b 100644 (file)
@@ -50,6 +50,7 @@
   - type: GuideHelp
     guides:
     - Janitorial
+  - type: DnaSubstanceTrace
 
 - type: entity
   parent: BaseItem
       tags:
         - Mop
         - MopAdv
+    - type: DnaSubstanceTrace
 
 - type: entity
   name: wet floor sign
     - type: CleansForensics
     - type: Fiber
       fiberColor: fibers-white
+    - type: DnaSubstanceTrace
index fd931a05c7af560b8bf705b65c73348a6d307df5..d1020ff6094b8ee4251249ebd8fe892d55d1760f 100644 (file)
@@ -82,6 +82,7 @@
     - type: Tag
       tags:
       - ChemDispensable
+    - type: DnaSubstanceTrace
 
 - type: entity
   parent: Jug
index 6fdb77fe5fcae266cfcad1c062185b416e9de05b..f8f55cf8fc5bee06b3f7d1772ab9fb6ab78baf93 100644 (file)
@@ -93,6 +93,7 @@
         transferForensics: true
       - !type:DoActsBehavior
         acts: [ "Destruction" ]
+  - type: DnaSubstanceTrace
 
 - type: entity
   parent: BaseChemistryEmptyBottle
index 39a35dba8c6aedd49b08d6af0334cf3e298737c1..eca666ca4f4ac46437cca0dc76afa3ac4c7f5cfe 100644 (file)
         acts: [ "Destruction" ]
   - type: Spillable
     solution: beaker
+  - type: DnaSubstanceTrace
 
 - type: entity
   id: VestineChemistryVial
index c888559bc30aaee1f110fb10f151c8ec2e2f8636..72f612436bac08e8844978b2a0fa043e80d7e201 100644 (file)
@@ -92,6 +92,7 @@
         Blunt: 5
   - type: StaticPrice
     price: 30
+  - type: DnaSubstanceTrace
 
 - type: entity
   parent: BaseItem
     damageModifierSet: Glass
   - type: StaticPrice
     price: 30
+  - type: DnaSubstanceTrace
 
 - type: entity
   name: beaker
     inHandsFillBaseName: -fill-
   - type: StaticPrice
     price: 40
+  - type: DnaSubstanceTrace
   - type: Tag
     tags:
     - Dropper
index 58c8dae2b05aa942e45cb20df53dda3d9c5d9eb3..b64aa3a0412b0e7a0af63484b6df8e2e4ff22a7c 100644 (file)
@@ -67,3 +67,4 @@
   - type: PhysicalComposition
     materialComposition:
       Plastic: 50
+  - type: DnaSubstanceTrace
index 9fd05beaeea08436c93ee15ec7a6064d31e8b8c9..6ed06addcd4aacbded189f29873f644467c42e91 100644 (file)
@@ -84,6 +84,7 @@
       behaviors:
       - !type:DoActsBehavior
         acts: ["Destruction"]
+  - type: DnaSubstanceTrace
   - type: Fixtures
     fixtures:
       fix1:
     - type: GuideHelp
       guides:
       - Janitorial
+    - type: DnaSubstanceTrace
index 197966b4917936fc692961071d1599983d5f4c95..adf25cc71dabc3b2e4bfaf52d3637ecc69da9d83 100644 (file)
 - type: Tag
   id: DiscreteHealthAnalyzer #So construction recipes don't eat medical PDAs
 
+- type: Tag
+  id: DNASolutionScannable
+
 - type: Tag
   id: DockArrivals