Add more DNA interactions (#21989)

* 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
This commit is contained in:
themias
2023-12-15 04:52:55 -05:00
committed by GitHub
parent 2455980090
commit 9cc4a50692
20 changed files with 291 additions and 26 deletions

View File

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

View File

@@ -1,7 +1,6 @@
using Content.Server.Body.Components; using Content.Server.Body.Components;
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.Alert; using Content.Shared.Alert;
@@ -22,6 +21,8 @@ using Robust.Shared.Prototypes;
using Robust.Shared.Random; using Robust.Shared.Random;
using Content.Shared.Speech.EntitySystems; using Content.Shared.Speech.EntitySystems;
using Robust.Server.Audio; using Robust.Server.Audio;
using Robust.Shared.GameObjects;
using Content.Server.Forensics;
namespace Content.Server.Body.Systems; namespace Content.Server.Body.Systems;
@@ -38,6 +39,7 @@ public sealed class BloodstreamSystem : EntitySystem
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!; [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
[Dependency] private readonly SharedStutteringSystem _stutteringSystem = default!; [Dependency] private readonly SharedStutteringSystem _stutteringSystem = default!;
[Dependency] private readonly AlertsSystem _alertsSystem = default!; [Dependency] private readonly AlertsSystem _alertsSystem = default!;
[Dependency] private readonly ForensicsSystem _forensicsSystem = default!;
public override void Initialize() public override void Initialize()
{ {
@@ -322,11 +324,7 @@ public sealed class BloodstreamSystem : EntitySystem
component.BloodTemporarySolution.AddSolution(temp, _prototypeManager); component.BloodTemporarySolution.AddSolution(temp, _prototypeManager);
if (_puddleSystem.TrySpillAt(uid, component.BloodTemporarySolution, out var puddleUid, false)) if (_puddleSystem.TrySpillAt(uid, component.BloodTemporarySolution, out var puddleUid, false))
{ {
if (TryComp<DnaComponent>(uid, out var dna)) _forensicsSystem.TransferDna(puddleUid, uid, false);
{
var comp = EnsureComp<ForensicsComponent>(puddleUid);
comp.DNAs.Add(dna.DNA);
}
} }
component.BloodTemporarySolution.RemoveAllSolution(); component.BloodTemporarySolution.RemoveAllSolution();
@@ -378,11 +376,7 @@ public sealed class BloodstreamSystem : EntitySystem
if (_puddleSystem.TrySpillAt(uid, tempSol, out var puddleUid)) if (_puddleSystem.TrySpillAt(uid, tempSol, out var puddleUid))
{ {
if (TryComp<DnaComponent>(uid, out var dna)) _forensicsSystem.TransferDna(puddleUid, uid, false);
{
var comp = EnsureComp<ForensicsComponent>(puddleUid);
comp.DNAs.Add(dna.DNA);
}
} }
} }

View File

@@ -15,6 +15,7 @@ using Content.Shared.Mobs.Components;
using Content.Shared.Verbs; using Content.Shared.Verbs;
using Content.Shared.Stacks; using Content.Shared.Stacks;
using Robust.Shared.Player; using Robust.Shared.Player;
using Content.Shared.Forensics;
namespace Content.Server.Chemistry.EntitySystems; namespace Content.Server.Chemistry.EntitySystems;
@@ -290,7 +291,7 @@ public sealed partial class ChemistrySystem
("target", Identity.Entity(target, EntityManager))), injector, user); ("target", Identity.Entity(target, EntityManager))), injector, user);
Dirty(component); Dirty(component);
AfterInject(component, injector); AfterInject(component, injector, target);
} }
private void TryInject(InjectorComponent component, EntityUid injector, EntityUid targetEntity, Solution targetSolution, EntityUid user, bool asRefill) 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); ("target", Identity.Entity(targetEntity, EntityManager))), injector, user);
Dirty(component); 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. // Automatically set syringe to draw after completely draining it.
if (_solutions.TryGetSolution(injector, InjectorComponent.SolutionName, out var solution) if (_solutions.TryGetSolution(injector, InjectorComponent.SolutionName, out var solution)
@@ -339,9 +340,13 @@ public sealed partial class ChemistrySystem
{ {
component.ToggleState = SharedInjectorComponent.InjectorToggleMode.Draw; 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. // Automatically set syringe to inject after completely filling it.
if (_solutions.TryGetSolution(injector, InjectorComponent.SolutionName, out var solution) if (_solutions.TryGetSolution(injector, InjectorComponent.SolutionName, out var solution)
@@ -349,6 +354,10 @@ public sealed partial class ChemistrySystem
{ {
component.ToggleState = SharedInjectorComponent.InjectorToggleMode.Inject; 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) 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); ("target", Identity.Entity(targetEntity, EntityManager))), injector, user);
Dirty(component); 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) 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); ("target", Identity.Entity(target, EntityManager))), injector, user);
Dirty(component); Dirty(component);
AfterDraw(component, injector); AfterDraw(component, injector, target);
} }
} }

View File

@@ -14,6 +14,7 @@ using Content.Shared.Mobs.Components;
using Content.Shared.Weapons.Melee.Events; using Content.Shared.Weapons.Melee.Events;
using Content.Shared.Timing; using Content.Shared.Timing;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
using Content.Shared.Forensics;
namespace Content.Server.Chemistry.EntitySystems namespace Content.Server.Chemistry.EntitySystems
{ {
@@ -138,6 +139,9 @@ namespace Content.Server.Chemistry.EntitySystems
_reactiveSystem.DoEntityReaction(target.Value, removedSolution, ReactionMethod.Injection); _reactiveSystem.DoEntityReaction(target.Value, removedSolution, ReactionMethod.Injection);
_solutions.TryAddSolution(target.Value, targetSolution, removedSolution); _solutions.TryAddSolution(target.Value, targetSolution, removedSolution);
var ev = new TransferDnaEvent { Donor = target.Value, Recipient = uid };
RaiseLocalEvent(target.Value, ref ev);
// same LogType as syringes... // 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}"); _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}");

View File

@@ -13,21 +13,27 @@ namespace Content.Server.Forensics
/// <summary> /// <summary>
/// A list of fingerprint GUIDs that the forensic scanner found from the <see cref="ForensicsComponent"/> on an entity. /// A list of fingerprint GUIDs that the forensic scanner found from the <see cref="ForensicsComponent"/> on an entity.
/// </summary> /// </summary>
[ViewVariables(VVAccess.ReadOnly)] [ViewVariables(VVAccess.ReadOnly), DataField("fingerprints")]
public List<string> Fingerprints = new(); public List<string> Fingerprints = new();
/// <summary> /// <summary>
/// A list of glove fibers that the forensic scanner found from the <see cref="ForensicsComponent"/> on an entity. /// A list of glove fibers that the forensic scanner found from the <see cref="ForensicsComponent"/> on an entity.
/// </summary> /// </summary>
[ViewVariables(VVAccess.ReadOnly)] [ViewVariables(VVAccess.ReadOnly), DataField("fibers")]
public List<string> Fibers = new(); public List<string> Fibers = new();
/// <summary> /// <summary>
/// DNA that the forensic scanner found from the <see cref="DNAComponent"/> on an entity. /// DNA that the forensic scanner found from the <see cref="DNAComponent"/> on an entity.
/// </summary> /// </summary>
[ViewVariables(VVAccess.ReadOnly)] [ViewVariables(VVAccess.ReadOnly), DataField("dnas")]
public List<string> DNAs = new(); 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> /// <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

@@ -11,5 +11,27 @@ namespace Content.Server.Forensics
[DataField("dnas")] [DataField("dnas")]
public HashSet<string> DNAs = new(); 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;
} }
} }

View File

@@ -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;
}

View File

@@ -51,6 +51,7 @@ namespace Content.Server.Forensics
component.Fingerprints, component.Fingerprints,
component.Fibers, component.Fibers,
component.DNAs, component.DNAs,
component.Residues,
component.LastScannedName, component.LastScannedName,
component.PrintCooldown, component.PrintCooldown,
component.PrintReadyAt); component.PrintReadyAt);
@@ -74,6 +75,7 @@ namespace Content.Server.Forensics
scanner.Fingerprints = new(); scanner.Fingerprints = new();
scanner.Fibers = new(); scanner.Fibers = new();
scanner.DNAs = new(); scanner.DNAs = new();
scanner.Residues = new();
} }
else else
@@ -81,6 +83,7 @@ namespace Content.Server.Forensics
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.DNAs = forensics.DNAs.ToList();
scanner.Residues = forensics.Residues.ToList();
} }
scanner.LastScannedName = MetaData(args.Args.Target.Value).EntityName; scanner.LastScannedName = MetaData(args.Args.Target.Value).EntityName;
@@ -222,6 +225,12 @@ namespace Content.Server.Forensics
{ {
text.AppendLine(dna); 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()); _paperSystem.SetContent(printed, text.ToString());
_audioSystem.PlayPvs(component.SoundPrint, uid, _audioSystem.PlayPvs(component.SoundPrint, uid,

View 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.Interaction.Events;
using Content.Shared.Inventory; using Content.Shared.Inventory;
using Content.Shared.Tag;
using Content.Shared.Weapons.Melee.Events;
using Robust.Shared.Random; using Robust.Shared.Random;
namespace Content.Server.Forensics namespace Content.Server.Forensics
@@ -8,11 +16,19 @@ namespace Content.Server.Forensics
{ {
[Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly InventorySystem _inventory = default!; [Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] private readonly TagSystem _tagSystem = default!;
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
public override void Initialize() public override void Initialize()
{ {
SubscribeLocalEvent<FingerprintComponent, ContactInteractionEvent>(OnInteract); SubscribeLocalEvent<FingerprintComponent, ContactInteractionEvent>(OnInteract);
SubscribeLocalEvent<FingerprintComponent, MapInitEvent>(OnFingerprintInit); SubscribeLocalEvent<FingerprintComponent, MapInitEvent>(OnFingerprintInit);
SubscribeLocalEvent<DnaComponent, MapInitEvent>(OnDNAInit); 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) private void OnInteract(EntityUid uid, FingerprintComponent component, ContactInteractionEvent args)
@@ -30,6 +46,79 @@ namespace Content.Server.Forensics
component.DNA = GenerateDNA(); 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() public string GenerateFingerprint()
{ {
var fingerprint = new byte[16]; var fingerprint = new byte[16];
@@ -64,5 +153,31 @@ namespace Content.Server.Forensics
if (TryComp<FingerprintComponent>(user, out var fingerprint)) if (TryComp<FingerprintComponent>(user, out var fingerprint))
component.Fingerprints.Add(fingerprint.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
} }
} }

View File

@@ -28,6 +28,7 @@ namespace Content.Server.Medical
[Dependency] private readonly SolutionContainerSystem _solutionContainer = default!; [Dependency] private readonly SolutionContainerSystem _solutionContainer = default!;
[Dependency] private readonly StunSystem _stun = default!; [Dependency] private readonly StunSystem _stun = default!;
[Dependency] private readonly ThirstSystem _thirst = default!; [Dependency] private readonly ThirstSystem _thirst = default!;
[Dependency] private readonly ForensicsSystem _forensics = default!;
/// <summary> /// <summary>
/// Make an entity vomit, if they have a stomach. /// 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)) if (_puddle.TrySpillAt(uid, solution, out var puddle, false))
{ {
var forensics = EnsureComp<ForensicsComponent>(puddle); _forensics.TransferDna(puddle, uid, false);
if (TryComp<DnaComponent>(uid, out var dna))
forensics.DNAs.Add(dna.DNA);
} }
// Force sound to play as spill doesn't work if solution is empty. // Force sound to play as spill doesn't work if solution is empty.

View File

@@ -54,6 +54,7 @@ public sealed class DrinkSystem : EntitySystem
[Dependency] private readonly SharedInteractionSystem _interaction = default!; [Dependency] private readonly SharedInteractionSystem _interaction = default!;
[Dependency] private readonly SolutionContainerSystem _solutionContainer = default!; [Dependency] private readonly SolutionContainerSystem _solutionContainer = default!;
[Dependency] private readonly StomachSystem _stomach = default!; [Dependency] private readonly StomachSystem _stomach = default!;
[Dependency] private readonly ForensicsSystem _forensics = default!;
public override void Initialize() public override void Initialize()
{ {
@@ -399,9 +400,7 @@ public sealed class DrinkSystem : EntitySystem
//TODO: Grab the stomach UIDs somehow without using Owner //TODO: Grab the stomach UIDs somehow without using Owner
_stomach.TryTransferSolution(firstStomach.Value.Comp.Owner, drained, firstStomach.Value.Comp); _stomach.TryTransferSolution(firstStomach.Value.Comp.Owner, drained, firstStomach.Value.Comp);
var comp = EnsureComp<ForensicsComponent>(uid); _forensics.TransferDna(uid, args.Target.Value);
if (TryComp<DnaComponent>(args.Target, out var dna))
comp.DNAs.Add(dna.DNA);
if (!forceDrink && solution.Volume > 0) if (!forceDrink && solution.Volume > 0)
args.Repeat = true; args.Repeat = true;

View File

@@ -15,6 +15,8 @@ using Content.Shared.Temperature;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.Containers; using Robust.Shared.Containers;
using System.Linq; using System.Linq;
using Content.Shared.Inventory.Events;
using Content.Server.Forensics;
namespace Content.Server.Nutrition.EntitySystems namespace Content.Server.Nutrition.EntitySystems
{ {
@@ -30,6 +32,7 @@ namespace Content.Server.Nutrition.EntitySystems
[Dependency] private readonly SharedItemSystem _items = default!; [Dependency] private readonly SharedItemSystem _items = default!;
[Dependency] private readonly SharedContainerSystem _container = default!; [Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly ForensicsSystem _forensics = default!;
private const float UpdateTimer = 3f; private const float UpdateTimer = 3f;
@@ -44,6 +47,7 @@ namespace Content.Server.Nutrition.EntitySystems
{ {
SubscribeLocalEvent<SmokableComponent, IsHotEvent>(OnSmokableIsHotEvent); SubscribeLocalEvent<SmokableComponent, IsHotEvent>(OnSmokableIsHotEvent);
SubscribeLocalEvent<SmokableComponent, ComponentShutdown>(OnSmokableShutdownEvent); SubscribeLocalEvent<SmokableComponent, ComponentShutdown>(OnSmokableShutdownEvent);
SubscribeLocalEvent<SmokableComponent, GotEquippedEvent>(OnSmokeableEquipEvent);
InitializeCigars(); InitializeCigars();
InitializePipes(); InitializePipes();
@@ -85,6 +89,14 @@ namespace Content.Server.Nutrition.EntitySystems
_active.Remove(uid); _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) public override void Update(float frameTime)
{ {
_timer += frameTime; _timer += frameTime;

View File

@@ -24,3 +24,30 @@ public sealed partial class ForensicPadDoAfterEvent : DoAfterEvent
public override DoAfterEvent Clone() => this; 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;
}

View File

@@ -8,6 +8,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 List<string> DNAs = new();
public readonly List<string> Residues = 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;
@@ -16,6 +17,7 @@ namespace Content.Shared.Forensics
List<string> fingerprints, List<string> fingerprints,
List<string> fibers, List<string> fibers,
List<string> dnas, List<string> dnas,
List<string> residues,
string lastScannedName, string lastScannedName,
TimeSpan printCooldown, TimeSpan printCooldown,
TimeSpan printReadyAt) TimeSpan printReadyAt)
@@ -23,6 +25,7 @@ namespace Content.Shared.Forensics
Fingerprints = fingerprints; Fingerprints = fingerprints;
Fibers = fibers; Fibers = fibers;
DNAs = dnas; DNAs = dnas;
Residues = residues;
LastScannedName = lastScannedName; LastScannedName = lastScannedName;
PrintCooldown = printCooldown; PrintCooldown = printCooldown;
PrintReadyAt = printReadyAt; PrintReadyAt = printReadyAt;

View File

@@ -3,6 +3,7 @@ using System.Linq;
using Content.Shared.Containers.ItemSlots; using Content.Shared.Containers.ItemSlots;
using Content.Shared.DoAfter; using Content.Shared.DoAfter;
using Content.Shared.Examine; using Content.Shared.Examine;
using Content.Shared.Forensics;
using Content.Shared.IdentityManagement; using Content.Shared.IdentityManagement;
using Content.Shared.Implants.Components; using Content.Shared.Implants.Components;
using Content.Shared.Popups; using Content.Shared.Popups;
@@ -72,6 +73,9 @@ public abstract class SharedImplanterSystem : EntitySystem
else else
ImplantMode(implanter, component); ImplantMode(implanter, component);
var ev = new TransferDnaEvent { Donor = target, Recipient = implanter };
RaiseLocalEvent(target, ref ev);
Dirty(component); Dirty(component);
} }
@@ -140,6 +144,10 @@ public abstract class SharedImplanterSystem : EntitySystem
implantComp.ImplantedEntity = null; implantComp.ImplantedEntity = null;
implanterContainer.Insert(implant); implanterContainer.Insert(implant);
permanentFound = implantComp.Permanent; permanentFound = implantComp.Permanent;
var ev = new TransferDnaEvent { Donor = target, Recipient = implanter };
RaiseLocalEvent(target, ref ev);
//Break so only one implant is drawn //Break so only one implant is drawn
break; break;
} }

View File

@@ -2,6 +2,7 @@ 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-dnas = DNAs
forensic-scanner-interface-residues = Residues
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

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

View File

@@ -611,3 +611,6 @@
tags: tags:
- DroneUsable - DroneUsable
- Mop - Mop
- CleansForensics
- type: Fiber
fiberColor: fibers-white

View File

@@ -7,6 +7,7 @@
- type: Tag - type: Tag
tags: tags:
- Soap - Soap
- CleansForensics
- type: Sprite - type: Sprite
sprite: Objects/Specific/Janitorial/soap.rsi sprite: Objects/Specific/Janitorial/soap.rsi
layers: layers:
@@ -68,6 +69,9 @@
- type: Food - type: Food
solution: soap solution: soap
- type: BadFood - type: BadFood
- type: Residue
residueAdjective: residue-slippery
residueColor: residue-green
- type: entity - type: entity
name: soap name: soap
@@ -90,6 +94,9 @@
reagents: reagents:
- ReagentId: SoapReagent - ReagentId: SoapReagent
Quantity: 100 Quantity: 100
- type: Residue
residueAdjective: residue-slippery
residueColor: residue-grey
- type: entity - type: entity
name: soap name: soap
@@ -105,6 +112,9 @@
fillBaseName: deluxe- fillBaseName: deluxe-
- type: Item - type: Item
heldPrefix: deluxe heldPrefix: deluxe
- type: Residue
residueAdjective: residue-slippery
residueColor: residue-brown
- type: entity - type: entity
name: soap name: soap
@@ -127,6 +137,9 @@
flavors: flavors:
- clean - clean
- punishment - punishment
- type: Residue
residueAdjective: residue-slippery
residueColor: residue-red
- type: entity - type: entity
name: soaplet name: soaplet
@@ -189,6 +202,9 @@
flavors: flavors:
- clean - clean
- meaty - meaty
- type: Residue
residueAdjective: residue-slippery
residueColor: residue-red
- type: entity - type: entity
name: omega soap name: omega soap
@@ -214,3 +230,6 @@
reagents: reagents:
- ReagentId: SoapReagent - ReagentId: SoapReagent
Quantity: 240 Quantity: 240
- type: Residue
residueAdjective: residue-slippery
residueColor: residue-blue

View File

@@ -1124,3 +1124,6 @@
- type: Tag - type: Tag
id: boots id: boots
- type: Tag
id: CleansForensics