diff --git a/Content.Client/Forensics/ForensicScannerMenu.xaml.cs b/Content.Client/Forensics/ForensicScannerMenu.xaml.cs index 98561727f4..84ffd7969e 100644 --- a/Content.Client/Forensics/ForensicScannerMenu.xaml.cs +++ b/Content.Client/Forensics/ForensicScannerMenu.xaml.cs @@ -52,6 +52,12 @@ namespace Content.Client.Forensics { text.AppendLine(fiber); } + text.AppendLine(); + text.AppendLine(Loc.GetString("forensic-scanner-interface-dnas")); + foreach (var dna in msg.DNAs) + { + text.AppendLine(dna); + } Diagnostics.Text = text.ToString(); } } diff --git a/Content.Client/StationRecords/GeneralStationRecordConsoleWindow.xaml.cs b/Content.Client/StationRecords/GeneralStationRecordConsoleWindow.xaml.cs index 91b3c3d8b7..b5aa896aeb 100644 --- a/Content.Client/StationRecords/GeneralStationRecordConsoleWindow.xaml.cs +++ b/Content.Client/StationRecords/GeneralStationRecordConsoleWindow.xaml.cs @@ -118,7 +118,11 @@ public sealed partial class GeneralStationRecordConsoleWindow : DefaultWindow }, new Label() { - Text = Loc.GetString("general-station-record-console-record-fingerprint", ("fingerprint", record.Fingerprint is null ? Loc.GetString("generic-not-available-shorthand") : record.Fingerprint)) + Text = Loc.GetString("general-station-record-console-record-fingerprint", ("fingerprint", record.Fingerprint ?? Loc.GetString("generic-not-available-shorthand"))) + }, + new Label() + { + Text = Loc.GetString("general-station-record-console-record-dna", ("dna", record.DNA ?? Loc.GetString("generic-not-available-shorthand"))) } }; diff --git a/Content.Server/Body/Systems/BloodstreamSystem.cs b/Content.Server/Body/Systems/BloodstreamSystem.cs index 4d548dbb38..0f37aa9fce 100644 --- a/Content.Server/Body/Systems/BloodstreamSystem.cs +++ b/Content.Server/Body/Systems/BloodstreamSystem.cs @@ -2,6 +2,7 @@ using Content.Server.Body.Components; using Content.Server.Chemistry.EntitySystems; 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.Chemistry.Components; @@ -292,7 +293,14 @@ public sealed class BloodstreamSystem : EntitySystem // Pass some of the chemstream into the spilled blood. var temp = component.ChemicalSolution.SplitSolution(component.BloodTemporarySolution.Volume / 10); component.BloodTemporarySolution.AddSolution(temp, _prototypeManager); - _spillableSystem.SpillAt(uid, component.BloodTemporarySolution, "PuddleBlood", false); + var puddle = _spillableSystem.SpillAt(uid, component.BloodTemporarySolution, "PuddleBlood", false); + if (puddle != null) + { + var comp = EnsureComp(puddle.Owner); //TODO: Get rid of .Owner + if (TryComp(uid, out var dna)) + comp.DNAs.Add(dna.DNA); + } + component.BloodTemporarySolution.RemoveAllSolution(); } @@ -331,6 +339,13 @@ public sealed class BloodstreamSystem : EntitySystem component.BloodTemporarySolution.RemoveAllSolution(); tempSol.AddSolution(component.ChemicalSolution, _prototypeManager); component.ChemicalSolution.RemoveAllSolution(); - _spillableSystem.SpillAt(uid, tempSol, "PuddleBlood", true); + var puddle = _spillableSystem.SpillAt(uid, tempSol, "PuddleBlood", true); + + if (puddle != null) + { + var comp = EnsureComp(puddle.Owner); //TODO: Get rid of .Owner + if (TryComp(uid, out var dna)) + comp.DNAs.Add(dna.DNA); + } } } diff --git a/Content.Server/Destructible/Thresholds/Behaviors/SpawnEntitiesBehavior.cs b/Content.Server/Destructible/Thresholds/Behaviors/SpawnEntitiesBehavior.cs index a665fc6155..62e8cf6aab 100644 --- a/Content.Server/Destructible/Thresholds/Behaviors/SpawnEntitiesBehavior.cs +++ b/Content.Server/Destructible/Thresholds/Behaviors/SpawnEntitiesBehavior.cs @@ -1,7 +1,9 @@ +using Content.Server.Forensics; using Content.Server.Stack; using Content.Shared.Prototypes; using Content.Shared.Stacks; using Robust.Shared.Prototypes; +using Robust.Shared.Random; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary; namespace Content.Server.Destructible.Thresholds.Behaviors @@ -19,6 +21,9 @@ namespace Content.Server.Destructible.Thresholds.Behaviors [DataField("offset")] public float Offset { get; set; } = 0.5f; + [DataField("transferForensics")] + public bool DoTransferForensics = false; + public void Execute(EntityUid owner, DestructibleSystem system, EntityUid? cause = null) { var position = system.EntityManager.GetComponent(owner).MapPosition; @@ -37,15 +42,34 @@ namespace Content.Server.Destructible.Thresholds.Behaviors { var spawned = system.EntityManager.SpawnEntity(entityId, position.Offset(getRandomVector())); system.StackSystem.SetCount(spawned, count); + + TransferForensics(spawned, system, owner); } else { for (var i = 0; i < count; i++) { - system.EntityManager.SpawnEntity(entityId, position.Offset(getRandomVector())); + var spawned = system.EntityManager.SpawnEntity(entityId, position.Offset(getRandomVector())); + + TransferForensics(spawned, system, owner); } } } } + + public void TransferForensics(EntityUid spawned, DestructibleSystem system, EntityUid owner) + { + if (!DoTransferForensics || + !system.EntityManager.TryGetComponent(owner, out var forensicsComponent)) + return; + + var comp = system.EntityManager.EnsureComponent(spawned); + comp.DNAs = forensicsComponent.DNAs; + + if (!system.Random.Prob(0.4f)) + return; + comp.Fingerprints = forensicsComponent.Fingerprints; + comp.Fibers = forensicsComponent.Fibers; + } } } diff --git a/Content.Server/Forensics/Components/DnaComponent.cs b/Content.Server/Forensics/Components/DnaComponent.cs new file mode 100644 index 0000000000..101b9a0900 --- /dev/null +++ b/Content.Server/Forensics/Components/DnaComponent.cs @@ -0,0 +1,11 @@ +namespace Content.Server.Forensics; + +/// +/// This component is for mobs that have DNA. +/// +[RegisterComponent] +public sealed class DnaComponent : Component +{ + [DataField("dna"), ViewVariables(VVAccess.ReadWrite)] + public string DNA = String.Empty; +} diff --git a/Content.Server/Forensics/Components/ForensicScannerComponent.cs b/Content.Server/Forensics/Components/ForensicScannerComponent.cs index f82ffff9e9..7e64a5a05b 100644 --- a/Content.Server/Forensics/Components/ForensicScannerComponent.cs +++ b/Content.Server/Forensics/Components/ForensicScannerComponent.cs @@ -20,6 +20,12 @@ namespace Content.Server.Forensics [ViewVariables(VVAccess.ReadOnly)] public List Fibers = new(); + /// + /// DNA that the forensic scanner found from the on an entity. + /// + [ViewVariables(VVAccess.ReadOnly)] + public List DNAs = new(); + /// /// What is the name of the entity that was scanned last? /// diff --git a/Content.Server/Forensics/Components/ForensicsComponent.cs b/Content.Server/Forensics/Components/ForensicsComponent.cs index bc054d96e1..e814cd025f 100644 --- a/Content.Server/Forensics/Components/ForensicsComponent.cs +++ b/Content.Server/Forensics/Components/ForensicsComponent.cs @@ -8,5 +8,8 @@ namespace Content.Server.Forensics [DataField("fibers")] public HashSet Fibers = new(); + + [DataField("dnas")] + public HashSet DNAs = new(); } } diff --git a/Content.Server/Forensics/Systems/ForensicScannerSystem.cs b/Content.Server/Forensics/Systems/ForensicScannerSystem.cs index 1f7df3d82f..83dfc87223 100644 --- a/Content.Server/Forensics/Systems/ForensicScannerSystem.cs +++ b/Content.Server/Forensics/Systems/ForensicScannerSystem.cs @@ -47,6 +47,7 @@ namespace Content.Server.Forensics var state = new ForensicScannerBoundUserInterfaceState( component.Fingerprints, component.Fibers, + component.DNAs, component.LastScannedName, component.PrintCooldown, component.PrintReadyAt); @@ -69,12 +70,14 @@ namespace Content.Server.Forensics { scanner.Fingerprints = new(); scanner.Fibers = new(); + scanner.DNAs = new(); } else { scanner.Fingerprints = forensics.Fingerprints.ToList(); scanner.Fibers = forensics.Fibers.ToList(); + scanner.DNAs = forensics.DNAs.ToList(); } scanner.LastScannedName = MetaData(args.Args.Target.Value).EntityName; @@ -211,6 +214,12 @@ namespace Content.Server.Forensics { text.AppendLine(fiber); } + text.AppendLine(); + text.AppendLine(Loc.GetString("forensic-scanner-interface-dnas")); + foreach (var dna in component.DNAs) + { + text.AppendLine(dna); + } _paperSystem.SetContent(printed, text.ToString()); _audioSystem.PlayPvs(component.SoundPrint, uid, @@ -230,6 +239,7 @@ namespace Content.Server.Forensics component.Fingerprints = new(); component.Fibers = new(); + component.DNAs = new(); component.LastScannedName = string.Empty; UpdateUserInterface(uid, component); diff --git a/Content.Server/Forensics/Systems/ForensicsSystem.cs b/Content.Server/Forensics/Systems/ForensicsSystem.cs index e122ea4555..e59ec93614 100644 --- a/Content.Server/Forensics/Systems/ForensicsSystem.cs +++ b/Content.Server/Forensics/Systems/ForensicsSystem.cs @@ -11,7 +11,8 @@ namespace Content.Server.Forensics public override void Initialize() { SubscribeLocalEvent(OnInteract); - SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnFingeprintInit); + SubscribeLocalEvent(OnDNAInit); } private void OnInteract(EntityUid uid, FingerprintComponent component, ContactInteractionEvent args) @@ -19,11 +20,16 @@ namespace Content.Server.Forensics ApplyEvidence(uid, args.Other); } - private void OnInit(EntityUid uid, FingerprintComponent component, ComponentInit args) + private void OnFingeprintInit(EntityUid uid, FingerprintComponent component, ComponentInit args) { component.Fingerprint = GenerateFingerprint(); } + private void OnDNAInit(EntityUid uid, DnaComponent component, ComponentInit args) + { + component.DNA = GenerateDNA(); + } + private string GenerateFingerprint() { byte[] fingerprint = new byte[16]; @@ -31,6 +37,19 @@ namespace Content.Server.Forensics return Convert.ToHexString(fingerprint); } + private string GenerateDNA() + { + var letters = new List { "A", "C", "G", "T" }; + string DNA = String.Empty; + + for (int i = 0; i < 16; i++) + { + DNA += letters[_random.Next(letters.Count)]; + } + + return DNA; + } + private void ApplyEvidence(EntityUid user, EntityUid target) { var component = EnsureComp(target); diff --git a/Content.Server/Medical/VomitSystem.cs b/Content.Server/Medical/VomitSystem.cs index 8751bc6409..ed02bee666 100644 --- a/Content.Server/Medical/VomitSystem.cs +++ b/Content.Server/Medical/VomitSystem.cs @@ -2,6 +2,7 @@ using Content.Server.Body.Components; using Content.Server.Body.Systems; using Content.Server.Chemistry.EntitySystems; using Content.Server.Fluids.Components; +using Content.Server.Forensics; using Content.Server.Nutrition.Components; using Content.Server.Nutrition.EntitySystems; using Content.Server.Popups; @@ -49,6 +50,10 @@ namespace Content.Server.Medical var puddle = EntityManager.SpawnEntity("PuddleVomit", Transform(uid).Coordinates); + var forensics = EnsureComp(puddle); + if (TryComp(uid, out var dna)) + forensics.DNAs.Add(dna.DNA); + var puddleComp = Comp(puddle); SoundSystem.Play("/Audio/Effects/Fluids/splat.ogg", Filter.Pvs(uid), uid, AudioHelpers.WithVariation(0.2f).WithVolume(-4f)); diff --git a/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs b/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs index 678875d61e..933efc268a 100644 --- a/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs @@ -4,6 +4,7 @@ using Content.Server.Chemistry.Components.SolutionManager; using Content.Server.Chemistry.EntitySystems; using Content.Server.DoAfter; using Content.Server.Fluids.EntitySystems; +using Content.Server.Forensics; using Content.Server.Nutrition.Components; using Content.Server.Popups; using Content.Shared.Administration.Logs; @@ -377,6 +378,10 @@ namespace Content.Server.Nutrition.EntitySystems component.ForceDrink = false; args.Handled = true; + + var comp = EnsureComp(uid); + if (TryComp(args.Args.Target, out var dna)) + comp.DNAs.Add(dna.DNA); } private void AddDrinkVerb(EntityUid uid, DrinkComponent component, GetVerbsEvent ev) diff --git a/Content.Server/StationRecords/Systems/StationRecordsSystem.cs b/Content.Server/StationRecords/Systems/StationRecordsSystem.cs index 8ab2ca2129..4b90e64193 100644 --- a/Content.Server/StationRecords/Systems/StationRecordsSystem.cs +++ b/Content.Server/StationRecords/Systems/StationRecordsSystem.cs @@ -74,8 +74,9 @@ public sealed class StationRecordsSystem : EntitySystem } TryComp(player, out var fingerprintComponent); + TryComp(player, out var dnaComponent); - CreateGeneralRecord(station, idUid.Value, profile.Name, profile.Age, profile.Species, profile.Gender, jobId, fingerprintComponent?.Fingerprint, profile, records); + CreateGeneralRecord(station, idUid.Value, profile.Name, profile.Age, profile.Species, profile.Gender, jobId, fingerprintComponent?.Fingerprint, dnaComponent?.DNA, profile, records); } @@ -97,13 +98,16 @@ public sealed class StationRecordsSystem : EntitySystem /// this call will cause an exception. Ensure that a general record starts out with a job /// that is currently a valid job prototype. /// + /// Fingerprint of the character. + /// DNA of the character. + /// /// /// Profile for the related player. This is so that other systems can get further information /// about the player character. /// Optional - other systems should anticipate this. /// /// Station records component. - public void CreateGeneralRecord(EntityUid station, EntityUid? idUid, string name, int age, string species, Gender gender, string jobId, string? mobFingerprint, HumanoidCharacterProfile? profile = null, + public void CreateGeneralRecord(EntityUid station, EntityUid? idUid, string name, int age, string species, Gender gender, string jobId, string? mobFingerprint, string? dna, HumanoidCharacterProfile? profile = null, StationRecordsComponent? records = null) { if (!Resolve(station, ref records)) @@ -126,7 +130,8 @@ public sealed class StationRecordsSystem : EntitySystem Species = species, Gender = gender, DisplayPriority = jobPrototype.Weight, - Fingerprint = mobFingerprint + Fingerprint = mobFingerprint, + DNA = dna }; var key = AddRecord(station, records); diff --git a/Content.Shared/Forensics/ForensicScannerEvent.cs b/Content.Shared/Forensics/ForensicScannerEvent.cs index 6ea4179c90..f305125b99 100644 --- a/Content.Shared/Forensics/ForensicScannerEvent.cs +++ b/Content.Shared/Forensics/ForensicScannerEvent.cs @@ -7,6 +7,7 @@ namespace Content.Shared.Forensics { public readonly List Fingerprints = new(); public readonly List Fibers = new(); + public readonly List DNAs = new(); public readonly string LastScannedName = string.Empty; public readonly TimeSpan PrintCooldown = TimeSpan.Zero; public readonly TimeSpan PrintReadyAt = TimeSpan.Zero; @@ -14,12 +15,14 @@ namespace Content.Shared.Forensics public ForensicScannerBoundUserInterfaceState( List fingerprints, List fibers, + List dnas, string lastScannedName, TimeSpan printCooldown, TimeSpan printReadyAt) { Fingerprints = fingerprints; Fibers = fibers; + DNAs = dnas; LastScannedName = lastScannedName; PrintCooldown = printCooldown; PrintReadyAt = printReadyAt; diff --git a/Content.Shared/StationRecords/GeneralStationRecord.cs b/Content.Shared/StationRecords/GeneralStationRecord.cs index 74964178c6..de4cda8f25 100644 --- a/Content.Shared/StationRecords/GeneralStationRecord.cs +++ b/Content.Shared/StationRecords/GeneralStationRecord.cs @@ -62,4 +62,10 @@ public sealed class GeneralStationRecord /// [ViewVariables] public string? Fingerprint; + + /// + /// DNA of the person. + /// + [ViewVariables] + public string? DNA; } diff --git a/Resources/Locale/en-US/forensics/forensics.ftl b/Resources/Locale/en-US/forensics/forensics.ftl index f08ac76778..88494820ec 100644 --- a/Resources/Locale/en-US/forensics/forensics.ftl +++ b/Resources/Locale/en-US/forensics/forensics.ftl @@ -1,6 +1,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-no-data = No scan data available forensic-scanner-interface-print = Print forensic-scanner-interface-clear = Clear diff --git a/Resources/Locale/en-US/guidebook/guides.ftl b/Resources/Locale/en-US/guidebook/guides.ftl index deb439a88d..287e47f291 100644 --- a/Resources/Locale/en-US/guidebook/guides.ftl +++ b/Resources/Locale/en-US/guidebook/guides.ftl @@ -21,3 +21,6 @@ guide-entry-xenoarchaeology = Xenoarchaeology guide-entry-artifact-reports = Artifact Reports guide-entry-traversal-distorter = Traversal Distorter guide-entry-machine-upgrading = Machine Upgrading + +guide-entry-security = Security +guide-entry-dna = DNA diff --git a/Resources/Locale/en-US/station-records/general-station-records.ftl b/Resources/Locale/en-US/station-records/general-station-records.ftl index a041a8cbee..7bbde02f74 100644 --- a/Resources/Locale/en-US/station-records/general-station-records.ftl +++ b/Resources/Locale/en-US/station-records/general-station-records.ftl @@ -8,3 +8,4 @@ general-station-record-console-record-title = Job: {$job} general-station-record-console-record-species = Species: {$species} general-station-record-console-record-gender = Gender: {$gender} general-station-record-console-record-fingerprint = Fingerprint: {$fingerprint} +general-station-record-console-record-dna = DNA: {$dna} diff --git a/Resources/Prototypes/Entities/Mobs/Species/base.yml b/Resources/Prototypes/Entities/Mobs/Species/base.yml index 0e09e636b3..3c9b8eb85f 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/base.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/base.yml @@ -297,6 +297,7 @@ proper: true - type: StandingState - type: Fingerprint + - type: Dna - type: MobPrice price: 1500 # Kidnapping a living person and selling them for cred is a good move. deathPenalty: 0.01 # However they really ought to be living and intact, otherwise they're worth 100x less. diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks.yml b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks.yml index 2cc7ceb86f..43b9a64e7a 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks.yml @@ -54,6 +54,7 @@ ShardGlass: min: 1 max: 1 + transferForensics: true - !type:DoActsBehavior acts: [ "Destruction" ] - type: DamageOnLand diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/trash_drinks.yml b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/trash_drinks.yml index c41671830f..f879ee78cc 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/trash_drinks.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/trash_drinks.yml @@ -56,6 +56,7 @@ BrokenBottle: min: 1 max: 1 + transferForensics: true - !type:DoActsBehavior acts: [ "Destruction" ] - type: Tag diff --git a/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml b/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml index 3737054ffd..c27ceb7f8a 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml @@ -65,6 +65,7 @@ ShardGlass: min: 1 max: 1 + transferForensics: true - !type:DoActsBehavior acts: [ "Destruction" ] - type: DamageOnLand diff --git a/Resources/Prototypes/Guidebook/security.yml b/Resources/Prototypes/Guidebook/security.yml new file mode 100644 index 0000000000..903cf93690 --- /dev/null +++ b/Resources/Prototypes/Guidebook/security.yml @@ -0,0 +1,11 @@ +- type: guideEntry + id: Security + name: guide-entry-security + text: "/Server Info/Guidebook/Security/Security.xml" + children: + - DNA + +- type: guideEntry + id: DNA + name: guide-entry-dna + text: "/Server Info/Guidebook/Security/DNA.xml" diff --git a/Resources/Prototypes/Guidebook/shiftandcrew.yml b/Resources/Prototypes/Guidebook/shiftandcrew.yml index 80880ddb95..bfd98014ef 100644 --- a/Resources/Prototypes/Guidebook/shiftandcrew.yml +++ b/Resources/Prototypes/Guidebook/shiftandcrew.yml @@ -6,6 +6,7 @@ - Botany - Engineering - Science + - Security - type: guideEntry id: Survival diff --git a/Resources/Server Info/Guidebook/Security/DNA.xml b/Resources/Server Info/Guidebook/Security/DNA.xml new file mode 100644 index 0000000000..1a2820ad3d --- /dev/null +++ b/Resources/Server Info/Guidebook/Security/DNA.xml @@ -0,0 +1,18 @@ + + # DNA + + ## How to get someone’s DNA? + + You can scan blood puddles, vomit, glasses, bottles, cans and other liquid containers using the [color=#a4885c]forensic scanner[/color] to get DNA of any person. + + + + So be careful before fighting with someone. + + ## I got DNA. How do I recognize whose it is? + + You can print the forensic information of the object you scanned so you never miss it. Now with the paper containing DNA you can simply find a [color=#a4885c]Station Records Computer[/color] and look for a person whose DNA matches. + + + + diff --git a/Resources/Server Info/Guidebook/Security/Security.xml b/Resources/Server Info/Guidebook/Security/Security.xml new file mode 100644 index 0000000000..3faf9bc407 --- /dev/null +++ b/Resources/Server Info/Guidebook/Security/Security.xml @@ -0,0 +1,3 @@ + + +