DNA basics (#14724)

* DNA component

* Commit numba 2

* Added DNA into Station Records Computer

* commit numba 3

* commit numba 4

* Vomit also contain DNA component now

* fixed DNA field not clearing after scanning another item

* commit numba 10
Drinking leaves DNA on an object. Breaking glasses, bottles and beakers leave DNA and leave fingerprints/fibers with 40% chance on glass shards. + lotta fixes

* 11

* 12

* 14

* Added DNA guide entry

* FIX
This commit is contained in:
faint
2023-03-31 07:49:25 +03:00
committed by GitHub
parent dfcb7b3c97
commit 8b6996cbae
25 changed files with 173 additions and 9 deletions

View File

@@ -52,6 +52,12 @@ namespace Content.Client.Forensics
{ {
text.AppendLine(fiber); 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(); Diagnostics.Text = text.ToString();
} }
} }

View File

@@ -118,7 +118,11 @@ public sealed partial class GeneralStationRecordConsoleWindow : DefaultWindow
}, },
new Label() 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")))
} }
}; };

View File

@@ -2,6 +2,7 @@ using Content.Server.Body.Components;
using Content.Server.Chemistry.EntitySystems; using Content.Server.Chemistry.EntitySystems;
using Content.Server.Chemistry.ReactionEffects; using Content.Server.Chemistry.ReactionEffects;
using Content.Server.Fluids.EntitySystems; using Content.Server.Fluids.EntitySystems;
using Content.Server.Forensics;
using Content.Server.HealthExaminable; using Content.Server.HealthExaminable;
using Content.Server.Popups; using Content.Server.Popups;
using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Components;
@@ -292,7 +293,14 @@ public sealed class BloodstreamSystem : EntitySystem
// Pass some of the chemstream into the spilled blood. // Pass some of the chemstream into the spilled blood.
var temp = component.ChemicalSolution.SplitSolution(component.BloodTemporarySolution.Volume / 10); var temp = component.ChemicalSolution.SplitSolution(component.BloodTemporarySolution.Volume / 10);
component.BloodTemporarySolution.AddSolution(temp, _prototypeManager); 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<ForensicsComponent>(puddle.Owner); //TODO: Get rid of .Owner
if (TryComp<DnaComponent>(uid, out var dna))
comp.DNAs.Add(dna.DNA);
}
component.BloodTemporarySolution.RemoveAllSolution(); component.BloodTemporarySolution.RemoveAllSolution();
} }
@@ -331,6 +339,13 @@ public sealed class BloodstreamSystem : EntitySystem
component.BloodTemporarySolution.RemoveAllSolution(); component.BloodTemporarySolution.RemoveAllSolution();
tempSol.AddSolution(component.ChemicalSolution, _prototypeManager); tempSol.AddSolution(component.ChemicalSolution, _prototypeManager);
component.ChemicalSolution.RemoveAllSolution(); component.ChemicalSolution.RemoveAllSolution();
_spillableSystem.SpillAt(uid, tempSol, "PuddleBlood", true); var puddle = _spillableSystem.SpillAt(uid, tempSol, "PuddleBlood", true);
if (puddle != null)
{
var comp = EnsureComp<ForensicsComponent>(puddle.Owner); //TODO: Get rid of .Owner
if (TryComp<DnaComponent>(uid, out var dna))
comp.DNAs.Add(dna.DNA);
}
} }
} }

View File

@@ -1,7 +1,9 @@
using Content.Server.Forensics;
using Content.Server.Stack; using Content.Server.Stack;
using Content.Shared.Prototypes; using Content.Shared.Prototypes;
using Content.Shared.Stacks; using Content.Shared.Stacks;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
namespace Content.Server.Destructible.Thresholds.Behaviors namespace Content.Server.Destructible.Thresholds.Behaviors
@@ -19,6 +21,9 @@ namespace Content.Server.Destructible.Thresholds.Behaviors
[DataField("offset")] [DataField("offset")]
public float Offset { get; set; } = 0.5f; public float Offset { get; set; } = 0.5f;
[DataField("transferForensics")]
public bool DoTransferForensics = false;
public void Execute(EntityUid owner, DestructibleSystem system, EntityUid? cause = null) public void Execute(EntityUid owner, DestructibleSystem system, EntityUid? cause = null)
{ {
var position = system.EntityManager.GetComponent<TransformComponent>(owner).MapPosition; var position = system.EntityManager.GetComponent<TransformComponent>(owner).MapPosition;
@@ -37,15 +42,34 @@ namespace Content.Server.Destructible.Thresholds.Behaviors
{ {
var spawned = system.EntityManager.SpawnEntity(entityId, position.Offset(getRandomVector())); var spawned = system.EntityManager.SpawnEntity(entityId, position.Offset(getRandomVector()));
system.StackSystem.SetCount(spawned, count); system.StackSystem.SetCount(spawned, count);
TransferForensics(spawned, system, owner);
} }
else else
{ {
for (var i = 0; i < count; i++) 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<ForensicsComponent>(owner, out var forensicsComponent))
return;
var comp = system.EntityManager.EnsureComponent<ForensicsComponent>(spawned);
comp.DNAs = forensicsComponent.DNAs;
if (!system.Random.Prob(0.4f))
return;
comp.Fingerprints = forensicsComponent.Fingerprints;
comp.Fibers = forensicsComponent.Fibers;
}
} }
} }

View File

@@ -0,0 +1,11 @@
namespace Content.Server.Forensics;
/// <summary>
/// This component is for mobs that have DNA.
/// </summary>
[RegisterComponent]
public sealed class DnaComponent : Component
{
[DataField("dna"), ViewVariables(VVAccess.ReadWrite)]
public string DNA = String.Empty;
}

View File

@@ -20,6 +20,12 @@ namespace Content.Server.Forensics
[ViewVariables(VVAccess.ReadOnly)] [ViewVariables(VVAccess.ReadOnly)]
public List<string> Fibers = new(); public List<string> Fibers = new();
/// <summary>
/// DNA that the forensic scanner found from the <see cref="DNAComponent"/> on an entity.
/// </summary>
[ViewVariables(VVAccess.ReadOnly)]
public List<string> DNAs = new();
/// <summary> /// <summary>
/// What is the name of the entity that was scanned last? /// What is the name of the entity that was scanned last?
/// </summary> /// </summary>

View File

@@ -8,5 +8,8 @@ namespace Content.Server.Forensics
[DataField("fibers")] [DataField("fibers")]
public HashSet<string> Fibers = new(); public HashSet<string> Fibers = new();
[DataField("dnas")]
public HashSet<string> DNAs = new();
} }
} }

View File

@@ -47,6 +47,7 @@ namespace Content.Server.Forensics
var state = new ForensicScannerBoundUserInterfaceState( var state = new ForensicScannerBoundUserInterfaceState(
component.Fingerprints, component.Fingerprints,
component.Fibers, component.Fibers,
component.DNAs,
component.LastScannedName, component.LastScannedName,
component.PrintCooldown, component.PrintCooldown,
component.PrintReadyAt); component.PrintReadyAt);
@@ -69,12 +70,14 @@ namespace Content.Server.Forensics
{ {
scanner.Fingerprints = new(); scanner.Fingerprints = new();
scanner.Fibers = new(); scanner.Fibers = new();
scanner.DNAs = new();
} }
else else
{ {
scanner.Fingerprints = forensics.Fingerprints.ToList(); scanner.Fingerprints = forensics.Fingerprints.ToList();
scanner.Fibers = forensics.Fibers.ToList(); scanner.Fibers = forensics.Fibers.ToList();
scanner.DNAs = forensics.DNAs.ToList();
} }
scanner.LastScannedName = MetaData(args.Args.Target.Value).EntityName; scanner.LastScannedName = MetaData(args.Args.Target.Value).EntityName;
@@ -211,6 +214,12 @@ namespace Content.Server.Forensics
{ {
text.AppendLine(fiber); 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()); _paperSystem.SetContent(printed, text.ToString());
_audioSystem.PlayPvs(component.SoundPrint, uid, _audioSystem.PlayPvs(component.SoundPrint, uid,
@@ -230,6 +239,7 @@ namespace Content.Server.Forensics
component.Fingerprints = new(); component.Fingerprints = new();
component.Fibers = new(); component.Fibers = new();
component.DNAs = new();
component.LastScannedName = string.Empty; component.LastScannedName = string.Empty;
UpdateUserInterface(uid, component); UpdateUserInterface(uid, component);

View File

@@ -11,7 +11,8 @@ namespace Content.Server.Forensics
public override void Initialize() public override void Initialize()
{ {
SubscribeLocalEvent<FingerprintComponent, ContactInteractionEvent>(OnInteract); SubscribeLocalEvent<FingerprintComponent, ContactInteractionEvent>(OnInteract);
SubscribeLocalEvent<FingerprintComponent, ComponentInit>(OnInit); SubscribeLocalEvent<FingerprintComponent, ComponentInit>(OnFingeprintInit);
SubscribeLocalEvent<DnaComponent, ComponentInit>(OnDNAInit);
} }
private void OnInteract(EntityUid uid, FingerprintComponent component, ContactInteractionEvent args) private void OnInteract(EntityUid uid, FingerprintComponent component, ContactInteractionEvent args)
@@ -19,11 +20,16 @@ namespace Content.Server.Forensics
ApplyEvidence(uid, args.Other); 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(); component.Fingerprint = GenerateFingerprint();
} }
private void OnDNAInit(EntityUid uid, DnaComponent component, ComponentInit args)
{
component.DNA = GenerateDNA();
}
private string GenerateFingerprint() private string GenerateFingerprint()
{ {
byte[] fingerprint = new byte[16]; byte[] fingerprint = new byte[16];
@@ -31,6 +37,19 @@ namespace Content.Server.Forensics
return Convert.ToHexString(fingerprint); return Convert.ToHexString(fingerprint);
} }
private string GenerateDNA()
{
var letters = new List<string> { "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) private void ApplyEvidence(EntityUid user, EntityUid target)
{ {
var component = EnsureComp<ForensicsComponent>(target); var component = EnsureComp<ForensicsComponent>(target);

View File

@@ -2,6 +2,7 @@ using Content.Server.Body.Components;
using Content.Server.Body.Systems; using Content.Server.Body.Systems;
using Content.Server.Chemistry.EntitySystems; using Content.Server.Chemistry.EntitySystems;
using Content.Server.Fluids.Components; using Content.Server.Fluids.Components;
using Content.Server.Forensics;
using Content.Server.Nutrition.Components; using Content.Server.Nutrition.Components;
using Content.Server.Nutrition.EntitySystems; using Content.Server.Nutrition.EntitySystems;
using Content.Server.Popups; using Content.Server.Popups;
@@ -49,6 +50,10 @@ namespace Content.Server.Medical
var puddle = EntityManager.SpawnEntity("PuddleVomit", Transform(uid).Coordinates); var puddle = EntityManager.SpawnEntity("PuddleVomit", Transform(uid).Coordinates);
var forensics = EnsureComp<ForensicsComponent>(puddle);
if (TryComp<DnaComponent>(uid, out var dna))
forensics.DNAs.Add(dna.DNA);
var puddleComp = Comp<PuddleComponent>(puddle); var puddleComp = Comp<PuddleComponent>(puddle);
SoundSystem.Play("/Audio/Effects/Fluids/splat.ogg", Filter.Pvs(uid), uid, AudioHelpers.WithVariation(0.2f).WithVolume(-4f)); SoundSystem.Play("/Audio/Effects/Fluids/splat.ogg", Filter.Pvs(uid), uid, AudioHelpers.WithVariation(0.2f).WithVolume(-4f));

View File

@@ -4,6 +4,7 @@ using Content.Server.Chemistry.Components.SolutionManager;
using Content.Server.Chemistry.EntitySystems; using Content.Server.Chemistry.EntitySystems;
using Content.Server.DoAfter; using Content.Server.DoAfter;
using Content.Server.Fluids.EntitySystems; using Content.Server.Fluids.EntitySystems;
using Content.Server.Forensics;
using Content.Server.Nutrition.Components; using Content.Server.Nutrition.Components;
using Content.Server.Popups; using Content.Server.Popups;
using Content.Shared.Administration.Logs; using Content.Shared.Administration.Logs;
@@ -377,6 +378,10 @@ namespace Content.Server.Nutrition.EntitySystems
component.ForceDrink = false; component.ForceDrink = false;
args.Handled = true; args.Handled = true;
var comp = EnsureComp<ForensicsComponent>(uid);
if (TryComp<DnaComponent>(args.Args.Target, out var dna))
comp.DNAs.Add(dna.DNA);
} }
private void AddDrinkVerb(EntityUid uid, DrinkComponent component, GetVerbsEvent<AlternativeVerb> ev) private void AddDrinkVerb(EntityUid uid, DrinkComponent component, GetVerbsEvent<AlternativeVerb> ev)

View File

@@ -74,8 +74,9 @@ public sealed class StationRecordsSystem : EntitySystem
} }
TryComp<FingerprintComponent>(player, out var fingerprintComponent); TryComp<FingerprintComponent>(player, out var fingerprintComponent);
TryComp<DnaComponent>(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 /// this call will cause an exception. Ensure that a general record starts out with a job
/// that is currently a valid job prototype. /// that is currently a valid job prototype.
/// </param> /// </param>
/// <param name="mobFingerprint">Fingerprint of the character.</param>
/// <param name="dna">DNA of the character.</param>
///
/// <param name="profile"> /// <param name="profile">
/// Profile for the related player. This is so that other systems can get further information /// Profile for the related player. This is so that other systems can get further information
/// about the player character. /// about the player character.
/// Optional - other systems should anticipate this. /// Optional - other systems should anticipate this.
/// </param> /// </param>
/// <param name="records">Station records component.</param> /// <param name="records">Station records component.</param>
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) StationRecordsComponent? records = null)
{ {
if (!Resolve(station, ref records)) if (!Resolve(station, ref records))
@@ -126,7 +130,8 @@ public sealed class StationRecordsSystem : EntitySystem
Species = species, Species = species,
Gender = gender, Gender = gender,
DisplayPriority = jobPrototype.Weight, DisplayPriority = jobPrototype.Weight,
Fingerprint = mobFingerprint Fingerprint = mobFingerprint,
DNA = dna
}; };
var key = AddRecord(station, records); var key = AddRecord(station, records);

View File

@@ -7,6 +7,7 @@ namespace Content.Shared.Forensics
{ {
public readonly List<string> Fingerprints = new(); public readonly List<string> Fingerprints = new();
public readonly List<string> Fibers = new(); public readonly List<string> Fibers = new();
public readonly List<string> DNAs = new();
public readonly string LastScannedName = string.Empty; public readonly string LastScannedName = string.Empty;
public readonly TimeSpan PrintCooldown = TimeSpan.Zero; public readonly TimeSpan PrintCooldown = TimeSpan.Zero;
public readonly TimeSpan PrintReadyAt = TimeSpan.Zero; public readonly TimeSpan PrintReadyAt = TimeSpan.Zero;
@@ -14,12 +15,14 @@ namespace Content.Shared.Forensics
public ForensicScannerBoundUserInterfaceState( public ForensicScannerBoundUserInterfaceState(
List<string> fingerprints, List<string> fingerprints,
List<string> fibers, List<string> fibers,
List<string> dnas,
string lastScannedName, string lastScannedName,
TimeSpan printCooldown, TimeSpan printCooldown,
TimeSpan printReadyAt) TimeSpan printReadyAt)
{ {
Fingerprints = fingerprints; Fingerprints = fingerprints;
Fibers = fibers; Fibers = fibers;
DNAs = dnas;
LastScannedName = lastScannedName; LastScannedName = lastScannedName;
PrintCooldown = printCooldown; PrintCooldown = printCooldown;
PrintReadyAt = printReadyAt; PrintReadyAt = printReadyAt;

View File

@@ -62,4 +62,10 @@ public sealed class GeneralStationRecord
/// </summary> /// </summary>
[ViewVariables] [ViewVariables]
public string? Fingerprint; public string? Fingerprint;
/// <summary>
/// DNA of the person.
/// </summary>
[ViewVariables]
public string? DNA;
} }

View File

@@ -1,6 +1,7 @@
forensic-scanner-interface-title = Forensic scanner forensic-scanner-interface-title = Forensic scanner
forensic-scanner-interface-fingerprints = Fingerprints forensic-scanner-interface-fingerprints = Fingerprints
forensic-scanner-interface-fibers = Fibers forensic-scanner-interface-fibers = Fibers
forensic-scanner-interface-dnas = DNAs
forensic-scanner-interface-no-data = No scan data available forensic-scanner-interface-no-data = No scan data available
forensic-scanner-interface-print = Print forensic-scanner-interface-print = Print
forensic-scanner-interface-clear = Clear forensic-scanner-interface-clear = Clear

View File

@@ -21,3 +21,6 @@ guide-entry-xenoarchaeology = Xenoarchaeology
guide-entry-artifact-reports = Artifact Reports guide-entry-artifact-reports = Artifact Reports
guide-entry-traversal-distorter = Traversal Distorter guide-entry-traversal-distorter = Traversal Distorter
guide-entry-machine-upgrading = Machine Upgrading guide-entry-machine-upgrading = Machine Upgrading
guide-entry-security = Security
guide-entry-dna = DNA

View File

@@ -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-species = Species: {$species}
general-station-record-console-record-gender = Gender: {$gender} general-station-record-console-record-gender = Gender: {$gender}
general-station-record-console-record-fingerprint = Fingerprint: {$fingerprint} general-station-record-console-record-fingerprint = Fingerprint: {$fingerprint}
general-station-record-console-record-dna = DNA: {$dna}

View File

@@ -297,6 +297,7 @@
proper: true proper: true
- type: StandingState - type: StandingState
- type: Fingerprint - type: Fingerprint
- type: Dna
- type: MobPrice - type: MobPrice
price: 1500 # Kidnapping a living person and selling them for cred is a good move. 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. deathPenalty: 0.01 # However they really ought to be living and intact, otherwise they're worth 100x less.

View File

@@ -54,6 +54,7 @@
ShardGlass: ShardGlass:
min: 1 min: 1
max: 1 max: 1
transferForensics: true
- !type:DoActsBehavior - !type:DoActsBehavior
acts: [ "Destruction" ] acts: [ "Destruction" ]
- type: DamageOnLand - type: DamageOnLand

View File

@@ -56,6 +56,7 @@
BrokenBottle: BrokenBottle:
min: 1 min: 1
max: 1 max: 1
transferForensics: true
- !type:DoActsBehavior - !type:DoActsBehavior
acts: [ "Destruction" ] acts: [ "Destruction" ]
- type: Tag - type: Tag

View File

@@ -65,6 +65,7 @@
ShardGlass: ShardGlass:
min: 1 min: 1
max: 1 max: 1
transferForensics: true
- !type:DoActsBehavior - !type:DoActsBehavior
acts: [ "Destruction" ] acts: [ "Destruction" ]
- type: DamageOnLand - type: DamageOnLand

View File

@@ -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"

View File

@@ -6,6 +6,7 @@
- Botany - Botany
- Engineering - Engineering
- Science - Science
- Security
- type: guideEntry - type: guideEntry
id: Survival id: Survival

View File

@@ -0,0 +1,18 @@
<Document>
# DNA
## How to get someones 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.
<Box>
<GuideEntityEmbed Entity="ForensicScanner"/>
</Box>
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.
<Box>
<GuideEntityEmbed Entity="ComputerStationRecords"/>
</Box>
</Document>

View File

@@ -0,0 +1,3 @@
<Document>
</Document>