mirror of
https://github.com/space-syndicate/space-station-14.git
synced 2026-10-09 10:21:18 +03:00
Predict Forensics V2.0 (Ready for Review) (#40693)
* What sitting on code does to a branch + no mo owner v2 * Replacing ViewVariables with AutoNetworkedField + Missed One V2 * Removing Virtual Methods & Renaming ForensicsSystem to SharedForensicsSystem (Also made it abstract) V2 * IProduceWidgets Review Changes & some of my own V2 * Lil Cleanup V2 * Part 1 of Review Changes (Only thing left is the BUI stuff and the constructor) V2 * It now WORKS!!! (Still needs the BUI and constuctor changes, ill figure it out later) * Test (Removal of ServerSide VomitSystem.cs) * Ok... * Moved System BloodstreamSystem.cs to SharedBloodstreamSystem.cs & deleted client BloodStreamSystem.cs to make SharedBloodstreamSystem.cs sealed. (No Unregistered errors) * Prediction * Fix? * No mo log error? * Revert "No mo log error?" This reverts commit 50c15a3deab6af9dffd7baed45820bae5558e41f. * Constructor going up * No ForensicScannerBoundUserInterface.cs (It doesnt wanna scan anymore but it doesnt crash) * For Conflicts * Guh * Yea * Organization & no ErrorHell (maybe) * Dirtying * Dirtying take 2 * Revert "Dirtying take 2" This reverts commit 5735012e21f43808db3ae010fce268eac24d0583. * the dirties dont do much here * For Conflict * Readded * Another Conflict Commit Fix * Conflict Fix Thrice * Empty Space * Keep it simple * Add it back in * Move BloodstreamSystem stuff thats causing the error back to system. This'll prob fix the conflict * Conflict fix? * Trust the fix * Looked at other prediction PR's, they had better code. * To not mix up the system in client * Removing Conflict * For conflict * For the conflic fix, will be removed later * Scuffed Fix * Removal & Adding the Decouple * Took some HEAVY pointers from SirWarocks work. The Roadblock has been CLEARED, DOAFTERS WORK NOW!! * Cleanup & Optimizing, mostly comps. * More Optimization, most of this is just imported Warock Code, his QOL included. Did not want to remove SolutionDNA's tho, mostly due to a Log.Debug in OnPrint. * Forgot that the Print Refreshes, more of Warocks work. * Removing Loc.GetString * All of BloodStream to Shared, hopefully no errors. * Sketchy Dirty Removed * Removed Timer.Spawn, added FrameUpdate cause anything else only updated when the window opened, & a bit of cleanup. * No mo Mispredict * GenerateDnaEvent Predicted * Forgot the user * I knew it was real, bloodstutter is REAL and now GONE. * Cant dirty NewEntityBloodData due to potential stutter, so its safer to put it all back into server. again... * Clearer * Thank you leahcat, no more identity leak. * Routine Maintenance * sealing the class * it checks for this now?! * While im here, FileScope Namespaces & removing unused namespaces * Update Attribute Type (SirWarock Import), it works and is much easier to read. * Forever Fixing Conflicts * Readonly Genocide & Partial Maxing * LGTM, BloodstreamSystem moved to SharedBloodstreamSystem! * Sohn * If OG Github back push this commit * One Billionth Commit Fix * Removed client/server stuff * Made implicit * NewLine & comment on why AutoGenCompState is true * Updatin the rest of the Popups to use Entity * I think that's what they meant? It looks cleaner at least. * Mass Attribute for LocalEvents that are safe to change. * Putting it back as abstract * maybe? * Guhh, rolling back empty client/server of BloodStream * Removed Client/Server BloodstreamSystems, changed access modifier from protected to private, & renamed SharedBloodstreamSystem to BloodstreamSystem * From Protected to Private * Removed comment & made it explicit on what changed to true * Guh, hopefully thats all of it... * Forgot * Review * Test Push * Line 316 in SharedForensicsSystem was causing the errors, better to play it safe * Review Changes * Update Content.Shared/Forensics/Events.cs Co-authored-by: Whatstone <166147148+whatston3@users.noreply.github.com> * The only thing SolutionDNAs does is search and clears itself. I see no reason to keep it * The print.disabled didnt properly disable when cleared. So ill just use the old if check * Figured out the rest of the SubscribeLocalEvents and networking BloodData cause its fully in Shared --------- Co-authored-by: Whatstone <166147148+whatston3@users.noreply.github.com>
This commit is contained in:
parent
8ad82dc665
commit
502af6955d
52 changed files with 1178 additions and 1300 deletions
|
|
@ -1,5 +0,0 @@
|
|||
using Content.Shared.Body.Systems;
|
||||
|
||||
namespace Content.Client.Body.Systems;
|
||||
|
||||
public sealed class BloodstreamSystem : SharedBloodstreamSystem;
|
||||
|
|
@ -1,75 +1,45 @@
|
|||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Timing;
|
||||
using Content.Shared.Forensics;
|
||||
using Content.Shared.Forensics.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client.Forensics
|
||||
namespace Content.Client.Forensics;
|
||||
|
||||
public sealed partial class ForensicScannerBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
|
||||
{
|
||||
public sealed partial class ForensicScannerBoundUserInterface : BoundUserInterface
|
||||
[ViewVariables]
|
||||
private ForensicScannerMenu? _window;
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
[Dependency] private IGameTiming _gameTiming = default!;
|
||||
base.Open();
|
||||
_window = this.CreateWindow<ForensicScannerMenu>();
|
||||
_window.Print.OnPressed += _ => Print();
|
||||
_window.Clear.OnPressed += _ => Clear();
|
||||
|
||||
[ViewVariables]
|
||||
private ForensicScannerMenu? _window;
|
||||
Update();
|
||||
}
|
||||
|
||||
[ViewVariables]
|
||||
private TimeSpan _printCooldown;
|
||||
private void Print()
|
||||
{
|
||||
SendPredictedMessage(new ForensicScannerPrintMessage());
|
||||
}
|
||||
|
||||
public ForensicScannerBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
}
|
||||
private void Clear()
|
||||
{
|
||||
SendPredictedMessage(new ForensicScannerClearMessage());
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
_window = this.CreateWindow<ForensicScannerMenu>();
|
||||
_window.Print.OnPressed += _ => Print();
|
||||
_window.Clear.OnPressed += _ => Clear();
|
||||
}
|
||||
public override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
private void Print()
|
||||
{
|
||||
SendMessage(new ForensicScannerPrintMessage());
|
||||
if (_window == null)
|
||||
return;
|
||||
|
||||
if (_window != null)
|
||||
_window.UpdatePrinterState(true);
|
||||
if (!EntMan.TryGetComponent(Owner, out ForensicScannerComponent? scanner))
|
||||
return;
|
||||
|
||||
// This UI does not require pinpoint accuracy as to when the Print
|
||||
// button is available again, so spawning client-side timers is
|
||||
// fine. The server will make sure the cooldown is honored.
|
||||
Timer.Spawn(_printCooldown, () =>
|
||||
{
|
||||
if (_window != null)
|
||||
_window.UpdatePrinterState(false);
|
||||
});
|
||||
}
|
||||
|
||||
private void Clear()
|
||||
{
|
||||
SendMessage(new ForensicScannerClearMessage());
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
|
||||
if (_window == null)
|
||||
return;
|
||||
|
||||
if (state is not ForensicScannerBoundUserInterfaceState cast)
|
||||
return;
|
||||
|
||||
_printCooldown = cast.PrintCooldown;
|
||||
|
||||
// TODO: Fix this
|
||||
if (cast.PrintReadyAt > _gameTiming.CurTime)
|
||||
Timer.Spawn(cast.PrintReadyAt - _gameTiming.CurTime, () =>
|
||||
{
|
||||
if (_window != null)
|
||||
_window.UpdatePrinterState(false);
|
||||
});
|
||||
|
||||
_window.UpdateState(cast);
|
||||
}
|
||||
_window.Update(scanner);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,74 +3,75 @@ using Robust.Client.AutoGenerated;
|
|||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Timing;
|
||||
using Content.Shared.Forensics;
|
||||
using Content.Shared.Forensics.Components;
|
||||
|
||||
namespace Content.Client.Forensics
|
||||
namespace Content.Client.Forensics;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class ForensicScannerMenu : DefaultWindow
|
||||
{
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class ForensicScannerMenu : DefaultWindow
|
||||
[Dependency] private IGameTiming _gameTiming = default!;
|
||||
|
||||
private TimeSpan? _printReady;
|
||||
|
||||
public ForensicScannerMenu()
|
||||
{
|
||||
[Dependency] private IGameTiming _gameTiming = default!;
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
}
|
||||
|
||||
public ForensicScannerMenu()
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
if (string.IsNullOrEmpty(NameLabel.Text))
|
||||
return;
|
||||
|
||||
Print.Disabled = _printReady > _gameTiming.CurTime;
|
||||
}
|
||||
|
||||
public void Update(ForensicScannerComponent scanner)
|
||||
{
|
||||
if (string.IsNullOrEmpty(scanner.LastScannedName))
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
Print.Disabled = true;
|
||||
Clear.Disabled = true;
|
||||
NameLabel.Text = string.Empty;
|
||||
Diagnostics.Text = string.Empty;
|
||||
return;
|
||||
}
|
||||
|
||||
public void UpdatePrinterState(bool disabled)
|
||||
_printReady = scanner.PrintReadyAt;
|
||||
Clear.Disabled = false;
|
||||
|
||||
NameLabel.Text = scanner.LastScannedName;
|
||||
|
||||
var text = new StringBuilder();
|
||||
|
||||
text.AppendLine(Loc.GetString("forensic-scanner-interface-fingerprints"));
|
||||
foreach (var fingerprint in scanner.Fingerprints)
|
||||
{
|
||||
Print.Disabled = disabled;
|
||||
text.AppendLine(fingerprint);
|
||||
}
|
||||
text.AppendLine();
|
||||
text.AppendLine(Loc.GetString("forensic-scanner-interface-fibers"));
|
||||
foreach (var fiber in scanner.Fibers)
|
||||
{
|
||||
text.AppendLine(fiber);
|
||||
}
|
||||
text.AppendLine();
|
||||
text.AppendLine(Loc.GetString("forensic-scanner-interface-dnas"));
|
||||
foreach (var dna in scanner.DNAs)
|
||||
{
|
||||
text.AppendLine(dna);
|
||||
}
|
||||
text.AppendLine();
|
||||
text.AppendLine(Loc.GetString("forensic-scanner-interface-residues"));
|
||||
foreach (var residue in scanner.Residues)
|
||||
{
|
||||
text.AppendLine(residue);
|
||||
}
|
||||
|
||||
public void UpdateState(ForensicScannerBoundUserInterfaceState msg)
|
||||
{
|
||||
if (string.IsNullOrEmpty(msg.LastScannedName))
|
||||
{
|
||||
Print.Disabled = true;
|
||||
Clear.Disabled = true;
|
||||
NameLabel.Text = string.Empty;
|
||||
Diagnostics.Text = string.Empty;
|
||||
return;
|
||||
}
|
||||
|
||||
Print.Disabled = (msg.PrintReadyAt > _gameTiming.CurTime);
|
||||
Clear.Disabled = false;
|
||||
|
||||
NameLabel.Text = msg.LastScannedName;
|
||||
|
||||
var text = new StringBuilder();
|
||||
|
||||
text.AppendLine(Loc.GetString("forensic-scanner-interface-fingerprints"));
|
||||
foreach (var fingerprint in msg.Fingerprints)
|
||||
{
|
||||
text.AppendLine(fingerprint);
|
||||
}
|
||||
text.AppendLine();
|
||||
text.AppendLine(Loc.GetString("forensic-scanner-interface-fibers"));
|
||||
foreach (var fiber in msg.Fibers)
|
||||
{
|
||||
text.AppendLine(fiber);
|
||||
}
|
||||
text.AppendLine();
|
||||
text.AppendLine(Loc.GetString("forensic-scanner-interface-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)
|
||||
{
|
||||
text.AppendLine(residue);
|
||||
}
|
||||
Diagnostics.Text = text.ToString();
|
||||
}
|
||||
Diagnostics.Text = text.ToString();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
using Content.Shared.Forensics.Systems;
|
||||
|
||||
namespace Content.Client.Forensics.Systems;
|
||||
|
||||
public sealed class ForensicsSystem : SharedForensicsSystem;
|
||||
|
|
@ -23,6 +23,7 @@ using Content.Shared.Atmos;
|
|||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Body;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Systems;
|
||||
using Content.Shared.Clothing.Components;
|
||||
using Content.Shared.Clumsy;
|
||||
using Content.Shared.Cluwne;
|
||||
|
|
|
|||
|
|
@ -1,35 +0,0 @@
|
|||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Systems;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.Forensics;
|
||||
|
||||
namespace Content.Server.Body.Systems;
|
||||
|
||||
public sealed class BloodstreamSystem : SharedBloodstreamSystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<BloodstreamComponent, GenerateDnaEvent>(OnDnaGenerated);
|
||||
}
|
||||
|
||||
// forensics is not predicted yet
|
||||
private void OnDnaGenerated(Entity<BloodstreamComponent> entity, ref GenerateDnaEvent args)
|
||||
{
|
||||
if (SolutionContainer.ResolveSolution(entity.Owner, entity.Comp.BloodSolutionName, ref entity.Comp.BloodSolution, out var bloodSolution))
|
||||
{
|
||||
var data = NewEntityBloodData(entity);
|
||||
entity.Comp.BloodReferenceSolution.SetReagentData(data);
|
||||
|
||||
foreach (var reagent in bloodSolution.Contents)
|
||||
{
|
||||
List<ReagentData> reagentData = reagent.Reagent.EnsureReagentData();
|
||||
reagentData.RemoveAll(x => x is DnaData);
|
||||
reagentData.AddRange(data);
|
||||
}
|
||||
}
|
||||
else
|
||||
Log.Error("Unable to set bloodstream DNA, solution entity could not be resolved");
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ using Content.Server.Body.Systems;
|
|||
using Content.Server.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Systems;
|
||||
using Content.Shared.Chemistry.Events;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Popups;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
using Content.Server.Forensics;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Systems;
|
||||
using Content.Server.Speech.EntitySystems;
|
||||
using Content.Shared.Cloning.Events;
|
||||
using Content.Shared.Clothing.Components;
|
||||
using Content.Shared.Clothing.EntitySystems;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Forensics.Components;
|
||||
using Content.Shared.Forensics.Systems;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Labels.Components;
|
||||
using Content.Shared.Labels.EntitySystems;
|
||||
|
|
@ -34,13 +36,13 @@ public sealed partial class CloningSystem
|
|||
{
|
||||
[Dependency] private SharedStackSystem _stack = default!;
|
||||
[Dependency] private LabelSystem _label = default!;
|
||||
[Dependency] private ForensicsSystem _forensics = default!;
|
||||
[Dependency] private PaperSystem _paper = default!;
|
||||
[Dependency] private VocalSystem _vocal = default!;
|
||||
[Dependency] private MovementSpeedModifierSystem _movementSpeedModifier = default!;
|
||||
[Dependency] private SharedChameleonClothingSystem _chameleonClothing = default!;
|
||||
[Dependency] private PullingSystem _pulling = default!;
|
||||
[Dependency] private SharedBloodstreamSystem _bloodstream = default!;
|
||||
[Dependency] private BloodstreamSystem _bloodstream = default!;
|
||||
[Dependency] private SharedForensicsSystem _forensics = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
|
|
@ -94,7 +96,7 @@ public sealed partial class CloningSystem
|
|||
private void OnCloneItemForensics(Entity<ForensicsComponent> ent, ref CloningItemEvent args)
|
||||
{
|
||||
// copy any forensics to the cloned item
|
||||
_forensics.CopyForensicsFrom(ent.Comp, args.CloneUid);
|
||||
_forensics.CopyForensicsFrom(ent.AsNullable(), args.CloneUid);
|
||||
}
|
||||
|
||||
private void OnCloneItemStore(Entity<StoreComponent> ent, ref CloningItemEvent args)
|
||||
|
|
|
|||
|
|
@ -24,6 +24,9 @@
|
|||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="EntityEffects\" />
|
||||
</ItemGroup>
|
||||
<Import Project="..\RobustToolbox\MSBuild\Robust.Properties.targets" />
|
||||
<Import Project="..\RobustToolbox\MSBuild\Robust.EntitySystemSubscriptionsGenerator.targets" />
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using System.Numerics;
|
||||
using Content.Server.Forensics;
|
||||
using Content.Server.Stack;
|
||||
using Content.Shared.Destructible.Thresholds;
|
||||
using Content.Shared.Forensics.Components;
|
||||
using Content.Shared.Prototypes;
|
||||
using Content.Shared.Stacks;
|
||||
using Robust.Server.GameObjects;
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ using Robust.Shared.Physics.Systems;
|
|||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
using System.Linq;
|
||||
using Content.Shared.Body.Systems;
|
||||
using Content.Shared.EntityEffects.Effects.Solution;
|
||||
using TimedDespawnComponent = Robust.Shared.Spawners.TimedDespawnComponent;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
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
|
||||
{ }
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
namespace Content.Server.Forensics
|
||||
{
|
||||
/// <summary>
|
||||
/// This controls fibers left by gloves on items,
|
||||
/// which the forensics system uses.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class FiberComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public LocId FiberMaterial = "fibers-synthetic";
|
||||
|
||||
[DataField]
|
||||
public string? FiberColor;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
namespace Content.Server.Forensics
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to take a sample of someone's fingerprints.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class ForensicPadComponent : Component
|
||||
{
|
||||
[DataField("scanDelay")]
|
||||
public float ScanDelay = 3.0f;
|
||||
|
||||
public bool Used = false;
|
||||
public String Sample = string.Empty;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,97 +0,0 @@
|
|||
using System.Threading;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.Forensics
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed partial class ForensicScannerComponent : Component
|
||||
{
|
||||
public CancellationTokenSource? CancelToken;
|
||||
|
||||
/// <summary>
|
||||
/// A list of fingerprint GUIDs that the forensic scanner found from the <see cref="ForensicsComponent"/> on an entity.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadOnly), DataField("fingerprints")]
|
||||
public List<string> Fingerprints = new();
|
||||
|
||||
/// <summary>
|
||||
/// A list of glove fibers that the forensic scanner found from the <see cref="ForensicsComponent"/> on an entity.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadOnly), DataField("fibers")]
|
||||
public List<string> Fibers = new();
|
||||
|
||||
/// <summary>
|
||||
/// DNA that the forensic scanner found from the <see cref="DNAComponent"/> on an entity.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadOnly), DataField("dnas")]
|
||||
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.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadOnly), DataField("residues")]
|
||||
public List<string> Residues = new();
|
||||
|
||||
/// <summary>
|
||||
/// What is the name of the entity that was scanned last?
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This will be used for the title of the printout and displayed to players.
|
||||
/// </remarks>
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public string LastScannedName = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// When will the scanner be ready to print again?
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public TimeSpan PrintReadyAt = TimeSpan.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// The time (in seconds) that it takes to scan an entity.
|
||||
/// </summary>
|
||||
[DataField("scanDelay")]
|
||||
public float ScanDelay = 3.0f;
|
||||
|
||||
/// <summary>
|
||||
/// How often can the scanner print out reports?
|
||||
/// </summary>
|
||||
[DataField("printCooldown")]
|
||||
public TimeSpan PrintCooldown = TimeSpan.FromSeconds(5);
|
||||
|
||||
/// <summary>
|
||||
/// The sound that's played when there's a match between a scan and an
|
||||
/// inserted forensic pad.
|
||||
/// </summary>
|
||||
[DataField("soundMatch")]
|
||||
public SoundSpecifier SoundMatch = new SoundPathSpecifier("/Audio/Machines/Nuke/angry_beep.ogg");
|
||||
|
||||
/// <summary>
|
||||
/// The sound that's played when there's no match between a scan and an
|
||||
/// inserted forensic pad.
|
||||
/// </summary>
|
||||
[DataField("soundNoMatch")]
|
||||
public SoundSpecifier SoundNoMatch = new SoundPathSpecifier("/Audio/Machines/airlock_deny.ogg");
|
||||
|
||||
/// <summary>
|
||||
/// The sound that's played when the scanner prints off a report.
|
||||
/// </summary>
|
||||
[DataField("soundPrint")]
|
||||
public SoundSpecifier SoundPrint = new SoundPathSpecifier("/Audio/Machines/short_print_and_rip.ogg");
|
||||
|
||||
/// <summary>
|
||||
/// What the machine will print
|
||||
/// </summary>
|
||||
[DataField("machineOutput", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string MachineOutput = "ForensicReportPaper";
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
namespace Content.Server.Forensics
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed partial class ForensicsComponent : Component
|
||||
{
|
||||
[DataField("fingerprints")]
|
||||
public HashSet<string> Fingerprints = new();
|
||||
|
||||
[DataField("fibers")]
|
||||
public HashSet<string> Fibers = new();
|
||||
|
||||
[DataField("dnas")]
|
||||
public HashSet<string> DNAs = new();
|
||||
|
||||
[DataField("residues")]
|
||||
public HashSet<string> Residues = new();
|
||||
|
||||
/// <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 wipe the DNA off of a knife, but not a cigarette
|
||||
/// </summary>
|
||||
[DataField("canDnaBeCleaned")]
|
||||
public bool CanDnaBeCleaned = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
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;
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
using Content.Shared.Forensics;
|
||||
using Content.Shared.Forensics.Components;
|
||||
using Content.Shared.Inventory;
|
||||
|
||||
namespace Content.Server.Forensics;
|
||||
|
||||
public sealed class FingerprintMaskSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<FingerprintMaskComponent, InventoryRelayedEvent<TryAccessFingerprintEvent>>(OnTryAccessFingerprint);
|
||||
}
|
||||
|
||||
private void OnTryAccessFingerprint(EntityUid uid, FingerprintMaskComponent comp, ref InventoryRelayedEvent<TryAccessFingerprintEvent> args)
|
||||
{
|
||||
if (args.Args.Cancelled)
|
||||
return;
|
||||
|
||||
args.Args.Blocker = uid;
|
||||
args.Args.Cancel();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,119 +0,0 @@
|
|||
using Content.Server.Popups;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Forensics;
|
||||
using Content.Shared.Forensics.Components;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Labels.EntitySystems;
|
||||
|
||||
namespace Content.Server.Forensics
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to transfer fingerprints from entities to forensic pads.
|
||||
/// </summary>
|
||||
public sealed partial class ForensicPadSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private SharedDoAfterSystem _doAfterSystem = default!;
|
||||
[Dependency] private PopupSystem _popupSystem = default!;
|
||||
[Dependency] private ForensicsSystem _forensics = default!;
|
||||
[Dependency] private LabelSystem _label = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<ForensicPadComponent, ExaminedEvent>(OnExamined);
|
||||
SubscribeLocalEvent<ForensicPadComponent, AfterInteractEvent>(OnAfterInteract);
|
||||
SubscribeLocalEvent<ForensicPadComponent, ForensicPadDoAfterEvent>(OnDoAfter);
|
||||
}
|
||||
|
||||
private void OnExamined(EntityUid uid, ForensicPadComponent component, ExaminedEvent args)
|
||||
{
|
||||
if (!args.IsInDetailsRange)
|
||||
return;
|
||||
|
||||
if (!component.Used)
|
||||
{
|
||||
args.PushMarkup(Loc.GetString("forensic-pad-unused"));
|
||||
return;
|
||||
}
|
||||
|
||||
args.PushMarkup(Loc.GetString("forensic-pad-sample", ("sample", component.Sample)));
|
||||
}
|
||||
|
||||
private void OnAfterInteract(EntityUid uid, ForensicPadComponent component, AfterInteractEvent args)
|
||||
{
|
||||
if (!args.CanReach || args.Target == null)
|
||||
return;
|
||||
|
||||
if (HasComp<ForensicScannerComponent>(args.Target))
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
|
||||
if (component.Used)
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("forensic-pad-already-used"), args.Target.Value, args.User);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_forensics.CanAccessFingerprint(args.Target.Value, out var blocker))
|
||||
{
|
||||
|
||||
if (blocker is { } item)
|
||||
_popupSystem.PopupEntity(Loc.GetString("forensic-pad-no-access-due", ("entity", Identity.Entity(item, EntityManager))), args.Target.Value, args.User);
|
||||
else
|
||||
_popupSystem.PopupEntity(Loc.GetString("forensic-pad-no-access"), args.Target.Value, args.User);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (TryComp<FingerprintComponent>(args.Target, out var fingerprint) && fingerprint.Fingerprint != null)
|
||||
{
|
||||
if (args.User != args.Target)
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("forensic-pad-start-scan-user", ("target", Identity.Entity(args.Target.Value, EntityManager))), args.Target.Value, args.User);
|
||||
_popupSystem.PopupEntity(Loc.GetString("forensic-pad-start-scan-target", ("user", Identity.Entity(args.User, EntityManager))), args.Target.Value, args.Target.Value);
|
||||
}
|
||||
StartScan(uid, args.User, args.Target.Value, component, fingerprint.Fingerprint);
|
||||
return;
|
||||
}
|
||||
|
||||
if (TryComp<FiberComponent>(args.Target, out var fiber))
|
||||
StartScan(uid, args.User, args.Target.Value, component, string.IsNullOrEmpty(fiber.FiberColor) ? Loc.GetString("forensic-fibers", ("material", fiber.FiberMaterial)) : Loc.GetString("forensic-fibers-colored", ("color", fiber.FiberColor), ("material", fiber.FiberMaterial)));
|
||||
}
|
||||
|
||||
private void StartScan(EntityUid used, EntityUid user, EntityUid target, ForensicPadComponent pad, string sample)
|
||||
{
|
||||
var ev = new ForensicPadDoAfterEvent(sample);
|
||||
|
||||
var doAfterEventArgs = new DoAfterArgs(EntityManager, user, pad.ScanDelay, ev, used, target: target, used: used)
|
||||
{
|
||||
NeedHand = true,
|
||||
BreakOnMove = true,
|
||||
};
|
||||
|
||||
_doAfterSystem.TryStartDoAfter(doAfterEventArgs);
|
||||
}
|
||||
|
||||
private void OnDoAfter(EntityUid uid, ForensicPadComponent padComponent, ForensicPadDoAfterEvent args)
|
||||
{
|
||||
if (args.Handled || args.Cancelled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Args.Target != null)
|
||||
{
|
||||
string label = Identity.Name(args.Args.Target.Value, EntityManager);
|
||||
_label.Label(uid, label);
|
||||
}
|
||||
|
||||
padComponent.Sample = args.Sample;
|
||||
padComponent.Used = true;
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,262 +0,0 @@
|
|||
using System.Linq;
|
||||
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.Timing;
|
||||
using Robust.Shared.Prototypes;
|
||||
// todo: remove this stinky LINQy
|
||||
|
||||
namespace Content.Server.Forensics
|
||||
{
|
||||
public sealed partial class ForensicScannerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private IGameTiming _gameTiming = default!;
|
||||
[Dependency] private SharedDoAfterSystem _doAfterSystem = default!;
|
||||
[Dependency] private UserInterfaceSystem _uiSystem = default!;
|
||||
[Dependency] private PopupSystem _popupSystem = default!;
|
||||
[Dependency] private PaperSystem _paperSystem = default!;
|
||||
[Dependency] private SharedHandsSystem _handsSystem = default!;
|
||||
[Dependency] private SharedAudioSystem _audioSystem = default!;
|
||||
[Dependency] private MetaDataSystem _metaData = default!;
|
||||
[Dependency] private ForensicsSystem _forensicsSystem = default!;
|
||||
[Dependency] private TagSystem _tag = default!;
|
||||
|
||||
private static readonly ProtoId<TagPrototype> DNASolutionScannableTag = "DNASolutionScannable";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ForensicScannerComponent, AfterInteractEvent>(OnAfterInteract);
|
||||
SubscribeLocalEvent<ForensicScannerComponent, AfterInteractUsingEvent>(OnAfterInteractUsing);
|
||||
SubscribeLocalEvent<ForensicScannerComponent, BeforeActivatableUIOpenEvent>(OnBeforeActivatableUIOpen);
|
||||
SubscribeLocalEvent<ForensicScannerComponent, GetVerbsEvent<UtilityVerb>>(OnUtilityVerb);
|
||||
SubscribeLocalEvent<ForensicScannerComponent, ForensicScannerPrintMessage>(OnPrint);
|
||||
SubscribeLocalEvent<ForensicScannerComponent, ForensicScannerClearMessage>(OnClear);
|
||||
SubscribeLocalEvent<ForensicScannerComponent, ForensicScannerDoAfterEvent>(OnDoAfter);
|
||||
}
|
||||
|
||||
private void UpdateUserInterface(EntityUid uid, ForensicScannerComponent component)
|
||||
{
|
||||
var state = new ForensicScannerBoundUserInterfaceState(
|
||||
component.Fingerprints,
|
||||
component.Fibers,
|
||||
component.TouchDNAs,
|
||||
component.SolutionDNAs,
|
||||
component.Residues,
|
||||
component.LastScannedName,
|
||||
component.PrintCooldown,
|
||||
component.PrintReadyAt);
|
||||
|
||||
_uiSystem.SetUiState(uid, ForensicScannerUiKey.Key, state);
|
||||
}
|
||||
|
||||
private void OnDoAfter(EntityUid uid, ForensicScannerComponent component, DoAfterEvent args)
|
||||
{
|
||||
if (args.Handled || args.Cancelled)
|
||||
return;
|
||||
|
||||
if (!TryComp(uid, out ForensicScannerComponent? scanner))
|
||||
return;
|
||||
|
||||
if (args.Args.Target != null)
|
||||
{
|
||||
if (!TryComp<ForensicsComponent>(args.Args.Target, out var forensics))
|
||||
{
|
||||
scanner.Fingerprints = new();
|
||||
scanner.Fibers = new();
|
||||
scanner.TouchDNAs = new();
|
||||
scanner.Residues = new();
|
||||
}
|
||||
else
|
||||
{
|
||||
scanner.Fingerprints = forensics.Fingerprints.ToList();
|
||||
scanner.Fibers = forensics.Fibers.ToList();
|
||||
scanner.TouchDNAs = forensics.DNAs.ToList();
|
||||
scanner.Residues = forensics.Residues.ToList();
|
||||
}
|
||||
|
||||
if (_tag.HasTag(args.Args.Target.Value, DNASolutionScannableTag))
|
||||
{
|
||||
scanner.SolutionDNAs = _forensicsSystem.GetSolutionsDNA(args.Args.Target.Value);
|
||||
} else
|
||||
{
|
||||
scanner.SolutionDNAs = new();
|
||||
}
|
||||
|
||||
scanner.LastScannedName = MetaData(args.Args.Target.Value).EntityName;
|
||||
}
|
||||
|
||||
OpenUserInterface(args.Args.User, (uid, scanner));
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// Hosts logic common between OnUtilityVerb and OnAfterInteract.
|
||||
/// </remarks>
|
||||
private void StartScan(EntityUid uid, ForensicScannerComponent component, EntityUid user, EntityUid target)
|
||||
{
|
||||
_doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, user, component.ScanDelay, new ForensicScannerDoAfterEvent(), uid, target: target, used: uid)
|
||||
{
|
||||
BreakOnMove = true,
|
||||
NeedHand = true
|
||||
});
|
||||
}
|
||||
|
||||
private void OnUtilityVerb(EntityUid uid, ForensicScannerComponent component, GetVerbsEvent<UtilityVerb> args)
|
||||
{
|
||||
if (!args.CanInteract || !args.CanAccess || component.CancelToken != null)
|
||||
return;
|
||||
|
||||
var verb = new UtilityVerb()
|
||||
{
|
||||
Act = () => StartScan(uid, component, args.User, args.Target),
|
||||
IconEntity = GetNetEntity(uid),
|
||||
Text = Loc.GetString("forensic-scanner-verb-text"),
|
||||
Message = Loc.GetString("forensic-scanner-verb-message"),
|
||||
// This is important because if its true using the scanner will count as touching the object.
|
||||
DoContactInteraction = false
|
||||
};
|
||||
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
private void OnAfterInteract(EntityUid uid, ForensicScannerComponent component, AfterInteractEvent args)
|
||||
{
|
||||
if (component.CancelToken != null || args.Target == null || !args.CanReach)
|
||||
return;
|
||||
|
||||
StartScan(uid, component, args.User, args.Target.Value);
|
||||
}
|
||||
|
||||
private void OnAfterInteractUsing(EntityUid uid, ForensicScannerComponent component, AfterInteractUsingEvent args)
|
||||
{
|
||||
if (args.Handled || !args.CanReach)
|
||||
return;
|
||||
|
||||
if (!TryComp<ForensicPadComponent>(args.Used, out var pad))
|
||||
return;
|
||||
|
||||
foreach (var fiber in component.Fibers)
|
||||
{
|
||||
if (fiber == pad.Sample)
|
||||
{
|
||||
_audioSystem.PlayPvs(component.SoundMatch, uid);
|
||||
_popupSystem.PopupEntity(Loc.GetString("forensic-scanner-match-fiber"), uid, args.User);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var fingerprint in component.Fingerprints)
|
||||
{
|
||||
if (fingerprint == pad.Sample)
|
||||
{
|
||||
_audioSystem.PlayPvs(component.SoundMatch, uid);
|
||||
_popupSystem.PopupEntity(Loc.GetString("forensic-scanner-match-fingerprint"), uid, args.User);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_audioSystem.PlayPvs(component.SoundNoMatch, uid);
|
||||
_popupSystem.PopupEntity(Loc.GetString("forensic-scanner-match-none"), uid, args.User);
|
||||
}
|
||||
|
||||
private void OnBeforeActivatableUIOpen(EntityUid uid, ForensicScannerComponent component, BeforeActivatableUIOpenEvent args)
|
||||
{
|
||||
UpdateUserInterface(uid, component);
|
||||
}
|
||||
|
||||
private void OpenUserInterface(EntityUid user, Entity<ForensicScannerComponent> scanner)
|
||||
{
|
||||
UpdateUserInterface(scanner, scanner.Comp);
|
||||
|
||||
_uiSystem.OpenUi(scanner.Owner, ForensicScannerUiKey.Key, user);
|
||||
}
|
||||
|
||||
private void OnPrint(EntityUid uid, ForensicScannerComponent component, ForensicScannerPrintMessage args)
|
||||
{
|
||||
var user = args.Actor;
|
||||
|
||||
if (_gameTiming.CurTime < component.PrintReadyAt)
|
||||
{
|
||||
// This shouldn't occur due to the UI guarding against it, but
|
||||
// if it does, tell the user why nothing happened.
|
||||
_popupSystem.PopupEntity(Loc.GetString("forensic-scanner-printer-not-ready"), uid, user);
|
||||
return;
|
||||
}
|
||||
|
||||
// Spawn a piece of paper.
|
||||
var printed = Spawn(component.MachineOutput, Transform(uid).Coordinates);
|
||||
_handsSystem.PickupOrDrop(args.Actor, printed, checkActionBlocker: false);
|
||||
|
||||
if (!TryComp<PaperComponent>(printed, out var paperComp))
|
||||
{
|
||||
Log.Error("Printed paper did not have PaperComponent.");
|
||||
return;
|
||||
}
|
||||
|
||||
_metaData.SetEntityName(printed, Loc.GetString("forensic-scanner-report-title", ("entity", component.LastScannedName)));
|
||||
|
||||
var text = new StringBuilder();
|
||||
|
||||
text.AppendLine(Loc.GetString("forensic-scanner-interface-fingerprints"));
|
||||
foreach (var fingerprint in component.Fingerprints)
|
||||
{
|
||||
text.AppendLine(fingerprint);
|
||||
}
|
||||
text.AppendLine();
|
||||
text.AppendLine(Loc.GetString("forensic-scanner-interface-fibers"));
|
||||
foreach (var fiber in component.Fibers)
|
||||
{
|
||||
text.AppendLine(fiber);
|
||||
}
|
||||
text.AppendLine();
|
||||
text.AppendLine(Loc.GetString("forensic-scanner-interface-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();
|
||||
text.AppendLine(Loc.GetString("forensic-scanner-interface-residues"));
|
||||
foreach (var residue in component.Residues)
|
||||
{
|
||||
text.AppendLine(residue);
|
||||
}
|
||||
|
||||
_paperSystem.SetContent((printed, paperComp), text.ToString());
|
||||
var audioParams = component.SoundPrint?.Params ?? AudioParams.Default;
|
||||
audioParams = audioParams.WithVariation(0.25f).AddVolume(3f).WithRolloffFactor(2.8f).WithMaxDistance(4.5f);
|
||||
_audioSystem.PlayPvs(component.SoundPrint, uid, audioParams);
|
||||
|
||||
component.PrintReadyAt = _gameTiming.CurTime + component.PrintCooldown;
|
||||
}
|
||||
|
||||
private void OnClear(EntityUid uid, ForensicScannerComponent component, ForensicScannerClearMessage args)
|
||||
{
|
||||
component.Fingerprints = new();
|
||||
component.Fibers = new();
|
||||
component.TouchDNAs = new();
|
||||
component.SolutionDNAs = new();
|
||||
component.LastScannedName = string.Empty;
|
||||
|
||||
UpdateUserInterface(uid, component);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,372 +0,0 @@
|
|||
using Content.Server.Body.Systems;
|
||||
using Content.Server.DoAfter;
|
||||
using Content.Server.Fluids.EntitySystems;
|
||||
using Content.Server.Forensics.Components;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Body.Events;
|
||||
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.Forensics;
|
||||
using Content.Shared.Forensics.Components;
|
||||
using Content.Shared.Forensics.Systems;
|
||||
using Content.Shared.Gibbing;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Weapons.Melee.Events;
|
||||
using Robust.Shared.Random;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Utility;
|
||||
using Content.Shared.Hands.Components;
|
||||
|
||||
namespace Content.Server.Forensics
|
||||
{
|
||||
public sealed partial class ForensicsSystem : SharedForensicsSystem
|
||||
{
|
||||
[Dependency] private IRobustRandom _random = default!;
|
||||
[Dependency] private InventorySystem _inventory = default!;
|
||||
[Dependency] private DoAfterSystem _doAfterSystem = default!;
|
||||
[Dependency] private PopupSystem _popupSystem = default!;
|
||||
[Dependency] private SharedSolutionContainerSystem _solutionContainerSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<HandsComponent, ContactInteractionEvent>(OnInteract);
|
||||
SubscribeLocalEvent<FingerprintComponent, MapInitEvent>(OnFingerprintInit, after: new[] { typeof(BloodstreamSystem) });
|
||||
// The solution entities are spawned on MapInit as well, so we have to wait for that to be able to set the DNA in the bloodstream correctly without ResolveSolution failing
|
||||
SubscribeLocalEvent<DnaComponent, MapInitEvent>(OnDNAInit, after: new[] { typeof(BloodstreamSystem) });
|
||||
|
||||
SubscribeLocalEvent<ForensicsComponent, GibbedBeforeDeletionEvent>(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, SolutionChangedEvent>(OnSolutionChanged);
|
||||
SubscribeLocalEvent<CleansForensicsComponent, GetVerbsEvent<UtilityVerb>>(OnUtilityVerb);
|
||||
}
|
||||
|
||||
private void OnSolutionChanged(Entity<DnaSubstanceTraceComponent> ent, ref SolutionChangedEvent 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, HandsComponent component, ContactInteractionEvent args)
|
||||
{
|
||||
ApplyEvidence(uid, args.Other);
|
||||
}
|
||||
|
||||
private void OnFingerprintInit(Entity<FingerprintComponent> ent, ref MapInitEvent args)
|
||||
{
|
||||
if (ent.Comp.Fingerprint == null)
|
||||
RandomizeFingerprint((ent.Owner, ent.Comp));
|
||||
}
|
||||
|
||||
private void OnDNAInit(Entity<DnaComponent> ent, ref MapInitEvent args)
|
||||
{
|
||||
if (ent.Comp.DNA == null)
|
||||
RandomizeDNA((ent.Owner, ent.Comp));
|
||||
else
|
||||
{
|
||||
// If set manually (for example by cloning) we also need to inform the bloodstream of the correct DNA string so it can be updated
|
||||
var ev = new GenerateDnaEvent { Owner = ent.Owner, DNA = ent.Comp.DNA };
|
||||
RaiseLocalEvent(ent.Owner, ref ev);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnBeingGibbed(Entity<ForensicsComponent> ent, ref GibbedBeforeDeletionEvent args)
|
||||
{
|
||||
string dna = Loc.GetString("forensics-dna-unknown");
|
||||
|
||||
if (TryComp(ent, out DnaComponent? dnaComp) && dnaComp.DNA != null)
|
||||
dna = dnaComp.DNA;
|
||||
|
||||
foreach (var part in args.Giblets)
|
||||
{
|
||||
var partComp = EnsureComp<ForensicsComponent>(part);
|
||||
partComp.DNAs.Add(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) && hitEntityComp.DNA != null)
|
||||
component.DNAs.Add(hitEntityComp.DNA);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRehydrated(Entity<ForensicsComponent> ent, ref GotRehydratedEvent args)
|
||||
{
|
||||
CopyForensicsFrom(ent.Comp, args.Target);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copy forensic information from a source entity to a destination.
|
||||
/// Existing forensic information on the target is still kept.
|
||||
/// </summary>
|
||||
public void CopyForensicsFrom(ForensicsComponent src, EntityUid target)
|
||||
{
|
||||
var dest = EnsureComp<ForensicsComponent>(target);
|
||||
foreach (var dna in src.DNAs)
|
||||
{
|
||||
dest.DNAs.Add(dna);
|
||||
}
|
||||
|
||||
foreach (var fiber in src.Fibers)
|
||||
{
|
||||
dest.Fibers.Add(fiber);
|
||||
}
|
||||
|
||||
foreach (var print in src.Fingerprints)
|
||||
{
|
||||
dest.Fingerprints.Add(print);
|
||||
}
|
||||
|
||||
foreach (var residue in src.Residues)
|
||||
{
|
||||
dest.Residues.Add(residue);
|
||||
}
|
||||
}
|
||||
|
||||
public List<string> GetSolutionsDNA(EntityUid uid)
|
||||
{
|
||||
List<string> list = new();
|
||||
foreach (var (_, soln) in _solutionContainerSystem.EnumerateSolutions(uid))
|
||||
{
|
||||
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)
|
||||
return;
|
||||
|
||||
args.Handled = TryStartCleaning(cleanForensicsEntity, args.User, args.Target.Value);
|
||||
}
|
||||
|
||||
private void OnUtilityVerb(Entity<CleansForensicsComponent> entity, ref GetVerbsEvent<UtilityVerb> args)
|
||||
{
|
||||
if (!args.CanInteract || !args.CanAccess)
|
||||
return;
|
||||
|
||||
// These need to be set outside for the anonymous method!
|
||||
var user = args.User;
|
||||
var target = args.Target;
|
||||
|
||||
var verb = new UtilityVerb()
|
||||
{
|
||||
Act = () => TryStartCleaning(entity, user, target),
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/bubbles.svg.192dpi.png")),
|
||||
Text = Loc.GetString("forensics-verb-text"),
|
||||
Message = Loc.GetString("forensics-verb-message"),
|
||||
// This is important because if its true using the cleaning device will count as touching the object.
|
||||
DoContactInteraction = false
|
||||
};
|
||||
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to clean the given item with the given CleansForensics entity.
|
||||
/// </summary>
|
||||
/// <param name="cleanForensicsEntity">The entity that is being used to clean the target.</param>
|
||||
/// <param name="user">The user that is using the cleanForensicsEntity.</param>
|
||||
/// <param name="target">The target of the forensics clean.</param>
|
||||
/// <returns>True if the target can be cleaned and has some sort of DNA or fingerprints / fibers and false otherwise.</returns>
|
||||
public bool TryStartCleaning(Entity<CleansForensicsComponent> cleanForensicsEntity, EntityUid user, EntityUid target)
|
||||
{
|
||||
if (!TryComp<ForensicsComponent>(target, out var forensicsComp))
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("forensics-cleaning-cannot-clean", ("target", Identity.Entity(target, EntityManager))), user, user, PopupType.MediumCaution);
|
||||
return false;
|
||||
}
|
||||
|
||||
var totalPrintsAndFibers = forensicsComp.Fingerprints.Count + forensicsComp.Fibers.Count;
|
||||
var hasRemovableDNA = forensicsComp.DNAs.Count > 0 && forensicsComp.CanDnaBeCleaned;
|
||||
|
||||
if (hasRemovableDNA || totalPrintsAndFibers > 0)
|
||||
{
|
||||
var cleanDelay = cleanForensicsEntity.Comp.CleanDelay;
|
||||
var doAfterArgs = new DoAfterArgs(EntityManager, user, cleanDelay, new CleanForensicsDoAfterEvent(), cleanForensicsEntity, target: target, used: cleanForensicsEntity)
|
||||
{
|
||||
NeedHand = true,
|
||||
BreakOnDamage = true,
|
||||
BreakOnMove = true,
|
||||
MovementThreshold = 0.01f,
|
||||
DistanceThreshold = forensicsComp.CleanDistance,
|
||||
};
|
||||
|
||||
_doAfterSystem.TryStartDoAfter(doAfterArgs);
|
||||
|
||||
_popupSystem.PopupEntity(Loc.GetString("forensics-cleaning", ("target", Identity.Entity(target, EntityManager))), user, user);
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("forensics-cleaning-cannot-clean", ("target", Identity.Entity(target, EntityManager))), user, user, PopupType.MediumCaution);
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void OnCleanForensicsDoAfter(EntityUid uid, ForensicsComponent component, CleanForensicsDoAfterEvent args)
|
||||
{
|
||||
if (args.Handled || args.Cancelled || args.Args.Target == null)
|
||||
return;
|
||||
|
||||
if (!TryComp<ForensicsComponent>(args.Target, out var targetComp))
|
||||
return;
|
||||
|
||||
targetComp.Fibers = new();
|
||||
targetComp.Fingerprints = new();
|
||||
|
||||
if (targetComp.CanDnaBeCleaned)
|
||||
targetComp.DNAs = new();
|
||||
|
||||
// leave behind evidence it was cleaned
|
||||
if (TryComp<FiberComponent>(args.Used, out var fiber))
|
||||
targetComp.Fibers.Add(string.IsNullOrEmpty(fiber.FiberColor) ? Loc.GetString("forensic-fibers", ("material", fiber.FiberMaterial)) : Loc.GetString("forensic-fibers-colored", ("color", fiber.FiberColor), ("material", fiber.FiberMaterial)));
|
||||
|
||||
if (TryComp<ResidueComponent>(args.Used, out var residue))
|
||||
targetComp.Residues.Add(string.IsNullOrEmpty(residue.ResidueColor) ? Loc.GetString("forensic-residue", ("adjective", residue.ResidueAdjective)) : Loc.GetString("forensic-residue-colored", ("color", residue.ResidueColor), ("adjective", residue.ResidueAdjective)));
|
||||
}
|
||||
|
||||
public string GenerateFingerprint()
|
||||
{
|
||||
var fingerprint = new byte[16];
|
||||
_random.NextBytes(fingerprint);
|
||||
return Convert.ToHexString(fingerprint);
|
||||
}
|
||||
|
||||
public string GenerateDNA()
|
||||
{
|
||||
var letters = new[] { "A", "C", "G", "T" };
|
||||
var DNA = string.Empty;
|
||||
|
||||
for (var i = 0; i < 16; i++)
|
||||
{
|
||||
DNA += letters[_random.Next(letters.Length)];
|
||||
}
|
||||
|
||||
return DNA;
|
||||
}
|
||||
|
||||
private void ApplyEvidence(EntityUid user, EntityUid target)
|
||||
{
|
||||
if (HasComp<IgnoresFingerprintsComponent>(target))
|
||||
return;
|
||||
|
||||
var component = EnsureComp<ForensicsComponent>(target);
|
||||
if (_inventory.TryGetSlotEntity(user, "gloves", out var gloves))
|
||||
{
|
||||
if (TryComp<FiberComponent>(gloves, out var fiber) && !string.IsNullOrEmpty(fiber.FiberMaterial))
|
||||
component.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<FingerprintComponent>(user, out var fingerprint) && CanAccessFingerprint(user, out _))
|
||||
component.Fingerprints.Add(fingerprint.Fingerprint ?? "");
|
||||
}
|
||||
|
||||
// TODO: Delete this. A lot of systems are manually raising this method event instead of calling the identical <see cref="TransferDna"/> method.
|
||||
// According to our code conventions we should not use method events.
|
||||
private void OnTransferDnaEvent(EntityUid uid, DnaComponent component, ref TransferDnaEvent args)
|
||||
{
|
||||
if (component.DNA == null)
|
||||
return;
|
||||
|
||||
var recipientComp = EnsureComp<ForensicsComponent>(args.Recipient);
|
||||
recipientComp.DNAs.Add(component.DNA);
|
||||
recipientComp.CanDnaBeCleaned = args.CanDnaBeCleaned;
|
||||
}
|
||||
|
||||
#region Public API
|
||||
public override void RandomizeDNA(Entity<DnaComponent?> ent)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
return;
|
||||
|
||||
ent.Comp.DNA = GenerateDNA();
|
||||
Dirty(ent);
|
||||
|
||||
var ev = new GenerateDnaEvent { Owner = ent.Owner, DNA = ent.Comp.DNA };
|
||||
RaiseLocalEvent(ent.Owner, ref ev);
|
||||
}
|
||||
|
||||
public override void RandomizeFingerprint(Entity<FingerprintComponent?> ent)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
return;
|
||||
|
||||
ent.Comp.Fingerprint = GenerateFingerprint();
|
||||
Dirty(ent);
|
||||
}
|
||||
|
||||
public override void TransferDna(EntityUid recipient, EntityUid donor, bool canDnaBeCleaned = true)
|
||||
{
|
||||
if (TryComp<DnaComponent>(donor, out var donorComp) && donorComp.DNA != null)
|
||||
{
|
||||
EnsureComp<ForensicsComponent>(recipient, out var recipientComp);
|
||||
recipientComp.DNAs.Add(donorComp.DNA);
|
||||
recipientComp.CanDnaBeCleaned = canDnaBeCleaned;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if there's a way to access the fingerprint of the target entity.
|
||||
/// </summary>
|
||||
/// <param name="target">The entity with the fingerprint</param>
|
||||
/// <param name="blocker">The entity that blocked accessing the fingerprint</param>
|
||||
public bool CanAccessFingerprint(EntityUid target, out EntityUid? blocker)
|
||||
{
|
||||
var ev = new TryAccessFingerprintEvent();
|
||||
|
||||
RaiseLocalEvent(target, ev);
|
||||
if (!ev.Cancelled && TryComp<InventoryComponent>(target, out var inv))
|
||||
_inventory.RelayEvent((target, inv), ev);
|
||||
|
||||
blocker = ev.Blocker;
|
||||
return !ev.Cancelled;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ using Robust.Shared.Audio.Systems;
|
|||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Timing;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Shared.Body.Systems;
|
||||
|
||||
namespace Content.Server.Medical;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
using Content.Shared.Atmos;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Server.Forensics;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Systems;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Clothing.Components;
|
||||
using Content.Shared.Clothing.EntitySystems;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Forensics.Systems;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Inventory.Events;
|
||||
using Content.Shared.Item;
|
||||
|
|
@ -14,9 +17,7 @@ using Content.Shared.Nutrition.Components;
|
|||
using Content.Shared.Smoking;
|
||||
using Content.Shared.Temperature;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Containers;
|
||||
using Content.Shared.Atmos;
|
||||
|
||||
namespace Content.Server.Nutrition.EntitySystems
|
||||
{
|
||||
|
|
@ -33,7 +34,7 @@ namespace Content.Server.Nutrition.EntitySystems
|
|||
[Dependency] private SharedItemSystem _items = default!;
|
||||
[Dependency] private SharedContainerSystem _container = default!;
|
||||
[Dependency] private SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private ForensicsSystem _forensics = default!;
|
||||
[Dependency] private SharedForensicsSystem _forensics = default!;
|
||||
|
||||
private const float UpdateTimer = 3f;
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using Content.Shared.Speech.EntitySystems;
|
|||
using Content.Shared.Anomaly.Components;
|
||||
using Content.Shared.Armor;
|
||||
using Content.Shared.Bed.Sleep;
|
||||
using Content.Shared.Body.Systems;
|
||||
using Content.Shared.Cloning.Events;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Damage.Systems;
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ namespace Content.Shared.Body.Components;
|
|||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent,]
|
||||
[AutoGenerateComponentState(fieldDeltas: true), AutoGenerateComponentPause]
|
||||
[Access(typeof(SharedBloodstreamSystem))]
|
||||
[Access(typeof(BloodstreamSystem))]
|
||||
public sealed partial class BloodstreamComponent : Component
|
||||
{
|
||||
public const string DefaultBloodSolutionName = "bloodstream";
|
||||
|
|
@ -157,7 +157,7 @@ public sealed partial class BloodstreamComponent : Component
|
|||
/// Caches the blood data of an entity.
|
||||
/// This is modified by DNA on init so it's not savable.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
[ViewVariables(VVAccess.ReadOnly), AutoNetworkedField]
|
||||
public List<ReagentData>? BloodData;
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ using Content.Shared.Damage.Systems;
|
|||
using Content.Shared.EntityEffects.Effects.Solution;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Fluids;
|
||||
using Content.Shared.Forensics;
|
||||
using Content.Shared.Forensics.Components;
|
||||
using Content.Shared.Gibbing;
|
||||
using Content.Shared.HealthExaminable;
|
||||
|
|
@ -22,16 +23,15 @@ using Content.Shared.StatusEffectNew;
|
|||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared.Body.Systems;
|
||||
|
||||
public abstract partial class SharedBloodstreamSystem : EntitySystem
|
||||
public sealed partial class BloodstreamSystem : EntitySystem
|
||||
{
|
||||
public static readonly EntProtoId Bloodloss = "StatusEffectBloodloss";
|
||||
|
||||
[Dependency] protected SharedSolutionContainerSystem SolutionContainer = default!;
|
||||
[Dependency] private SharedSolutionContainerSystem _solutionContainer = default!;
|
||||
[Dependency] private IGameTiming _timing = default!;
|
||||
[Dependency] private SharedAudioSystem _audio = default!;
|
||||
[Dependency] private SharedPopupSystem _popup = default!;
|
||||
|
|
@ -42,22 +42,6 @@ public abstract partial class SharedBloodstreamSystem : EntitySystem
|
|||
[Dependency] private DamageableSystem _damageableSystem = default!;
|
||||
[Dependency] private MetabolizerSystem _metabolizer = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<BloodstreamComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<BloodstreamComponent, EntRemovedFromContainerMessage>(OnEntRemoved);
|
||||
SubscribeLocalEvent<BloodstreamComponent, ReactionAttemptEvent>(OnReactionAttempt);
|
||||
SubscribeLocalEvent<BloodstreamComponent, SolutionRelayEvent<ReactionAttemptEvent>>(OnReactionAttempt);
|
||||
SubscribeLocalEvent<BloodstreamComponent, DamageChangedEvent>(OnDamageChanged);
|
||||
SubscribeLocalEvent<BloodstreamComponent, HealthBeingExaminedEvent>(OnHealthBeingExamined);
|
||||
SubscribeLocalEvent<BloodstreamComponent, GibbedBeforeDeletionEvent>(OnBeingGibbed);
|
||||
SubscribeLocalEvent<BloodstreamComponent, ApplyMetabolicMultiplierEvent>(OnApplyMetabolicMultiplier);
|
||||
SubscribeLocalEvent<BloodstreamComponent, RejuvenateEvent>(OnRejuvenate);
|
||||
SubscribeLocalEvent<BloodstreamComponent, MetabolismExclusionEvent>(OnMetabolismExclusion);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
|
@ -72,7 +56,7 @@ public abstract partial class SharedBloodstreamSystem : EntitySystem
|
|||
bloodstream.NextUpdate += bloodstream.AdjustedUpdateInterval;
|
||||
DirtyField(uid, bloodstream, nameof(BloodstreamComponent.NextUpdate)); // needs to be dirtied on the client so it can be rerolled during prediction
|
||||
|
||||
if (!SolutionContainer.ResolveSolution(uid, bloodstream.BloodSolutionName, ref bloodstream.BloodSolution))
|
||||
if (!_solutionContainer.ResolveSolution(uid, bloodstream.BloodSolutionName, ref bloodstream.BloodSolution))
|
||||
continue;
|
||||
|
||||
// Blood level regulation. Must be alive.
|
||||
|
|
@ -111,14 +95,15 @@ public abstract partial class SharedBloodstreamSystem : EntitySystem
|
|||
}
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnMapInit(Entity<BloodstreamComponent> entity, ref MapInitEvent args)
|
||||
{
|
||||
entity.Comp.NextUpdate = _timing.CurTime + entity.Comp.AdjustedUpdateInterval;
|
||||
DirtyField(entity, entity.Comp, nameof(BloodstreamComponent.NextUpdate));
|
||||
|
||||
SolutionContainer.EnsureSolution(entity.Owner, entity.Comp.BloodSolutionName, out var bloodSolution);
|
||||
SolutionContainer.EnsureSolution(entity.Owner, entity.Comp.BloodTemporarySolutionName, out var tempSolution);
|
||||
SolutionContainer.EnsureSolution(entity.Owner, entity.Comp.MetabolitesSolutionName, out var metabolitesSolution);
|
||||
_solutionContainer.EnsureSolution(entity.Owner, entity.Comp.BloodSolutionName, out var bloodSolution);
|
||||
_solutionContainer.EnsureSolution(entity.Owner, entity.Comp.BloodTemporarySolutionName, out var tempSolution);
|
||||
_solutionContainer.EnsureSolution(entity.Owner, entity.Comp.MetabolitesSolutionName, out var metabolitesSolution);
|
||||
|
||||
bloodSolution.Comp.Solution.MaxVolume = entity.Comp.BloodReferenceSolution.Volume * entity.Comp.MaxVolumeModifier;
|
||||
metabolitesSolution.Comp.Solution.MaxVolume = bloodSolution.Comp.Solution.MaxVolume;
|
||||
|
|
@ -134,6 +119,7 @@ public abstract partial class SharedBloodstreamSystem : EntitySystem
|
|||
|
||||
// prevent the infamous UdderSystem debug assert, see https://github.com/space-wizards/space-station-14/pull/35314
|
||||
// TODO: find a better solution than copy pasting this into every shared system that caches solution entities
|
||||
[SubscribeLocalEvent]
|
||||
private void OnEntRemoved(Entity<BloodstreamComponent> entity, ref EntRemovedFromContainerMessage args)
|
||||
{
|
||||
// Make sure the removed entity was our contained solution and set it to null
|
||||
|
|
@ -144,6 +130,26 @@ public abstract partial class SharedBloodstreamSystem : EntitySystem
|
|||
entity.Comp.TemporarySolution = null;
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnDnaGenerated(Entity<BloodstreamComponent> entity, ref GenerateDnaEvent args)
|
||||
{
|
||||
if (_solutionContainer.ResolveSolution(entity.Owner, entity.Comp.BloodSolutionName, ref entity.Comp.BloodSolution, out var bloodSolution))
|
||||
{
|
||||
var data = NewEntityBloodData(entity);
|
||||
entity.Comp.BloodReferenceSolution.SetReagentData(data);
|
||||
|
||||
foreach (var reagent in bloodSolution.Contents)
|
||||
{
|
||||
List<ReagentData> reagentData = reagent.Reagent.EnsureReagentData();
|
||||
reagentData.RemoveAll(x => x is DnaData);
|
||||
reagentData.AddRange(data);
|
||||
}
|
||||
}
|
||||
else
|
||||
Log.Error("Unable to set bloodstream DNA, solution entity could not be resolved");
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnReactionAttempt(Entity<BloodstreamComponent> ent, ref ReactionAttemptEvent args)
|
||||
{
|
||||
if (args.Cancelled)
|
||||
|
|
@ -171,6 +177,7 @@ public abstract partial class SharedBloodstreamSystem : EntitySystem
|
|||
// Having cheese-clots form in your veins can't be good for you.
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnReactionAttempt(Entity<BloodstreamComponent> ent, ref SolutionRelayEvent<ReactionAttemptEvent> args)
|
||||
{
|
||||
if (args.Solution.Comp.Id != ent.Comp.BloodSolutionName
|
||||
|
|
@ -182,6 +189,7 @@ public abstract partial class SharedBloodstreamSystem : EntitySystem
|
|||
OnReactionAttempt(ent, ref args.Event);
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnDamageChanged(Entity<BloodstreamComponent> ent, ref DamageChangedEvent args)
|
||||
{
|
||||
// The incoming state from the server raises a DamageChangedEvent as well.
|
||||
|
|
@ -241,6 +249,7 @@ public abstract partial class SharedBloodstreamSystem : EntitySystem
|
|||
/// <summary>
|
||||
/// Shows text on health examine, based on bleed rate and blood level.
|
||||
/// </summary>
|
||||
[SubscribeLocalEvent]
|
||||
private void OnHealthBeingExamined(Entity<BloodstreamComponent> ent, ref HealthBeingExaminedEvent args)
|
||||
{
|
||||
// Shows massively bleeding at 0.75x the max bleed rate.
|
||||
|
|
@ -276,29 +285,33 @@ public abstract partial class SharedBloodstreamSystem : EntitySystem
|
|||
}
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnBeingGibbed(Entity<BloodstreamComponent> ent, ref GibbedBeforeDeletionEvent args)
|
||||
{
|
||||
SpillAllSolutions(ent.AsNullable());
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnApplyMetabolicMultiplier(Entity<BloodstreamComponent> ent, ref ApplyMetabolicMultiplierEvent args)
|
||||
{
|
||||
ent.Comp.UpdateIntervalMultiplier = args.Multiplier;
|
||||
DirtyField(ent, ent.Comp, nameof(BloodstreamComponent.UpdateIntervalMultiplier));
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnRejuvenate(Entity<BloodstreamComponent> ent, ref RejuvenateEvent args)
|
||||
{
|
||||
TryModifyBleedAmount(ent.AsNullable(), -ent.Comp.BleedAmount);
|
||||
|
||||
if (SolutionContainer.ResolveSolution(ent.Owner, ent.Comp.BloodSolutionName, ref ent.Comp.BloodSolution))
|
||||
if (_solutionContainer.ResolveSolution(ent.Owner, ent.Comp.BloodSolutionName, ref ent.Comp.BloodSolution))
|
||||
{
|
||||
SolutionContainer.RemoveAllSolution(ent.Comp.BloodSolution.Value);
|
||||
_solutionContainer.RemoveAllSolution(ent.Comp.BloodSolution.Value);
|
||||
// TODO: Use Solutions API for this when it exists
|
||||
TryRegulateBloodLevel(ent.AsNullable(), ent.Comp.BloodReferenceSolution.Volume);
|
||||
}
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnMetabolismExclusion(Entity<BloodstreamComponent> ent, ref MetabolismExclusionEvent args)
|
||||
{
|
||||
// Adding all blood reagents for filtering blood in metabolizer
|
||||
|
|
@ -317,7 +330,7 @@ public abstract partial class SharedBloodstreamSystem : EntitySystem
|
|||
public float GetBloodLevel(Entity<BloodstreamComponent?> entity)
|
||||
{
|
||||
if (!Resolve(entity, ref entity.Comp)
|
||||
|| !SolutionContainer.ResolveSolution(entity.Owner, entity.Comp.BloodSolutionName, ref entity.Comp.BloodSolution, out var bloodSolution)
|
||||
|| !_solutionContainer.ResolveSolution(entity.Owner, entity.Comp.BloodSolutionName, ref entity.Comp.BloodSolution, out var bloodSolution)
|
||||
|| entity.Comp.BloodReferenceSolution.Volume == 0)
|
||||
{
|
||||
return 0.0f;
|
||||
|
|
@ -352,10 +365,10 @@ public abstract partial class SharedBloodstreamSystem : EntitySystem
|
|||
public bool TryAddToBloodstream(Entity<BloodstreamComponent?> ent, Solution solution)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp, logMissing: false)
|
||||
|| !SolutionContainer.ResolveSolution(ent.Owner, ent.Comp.BloodSolutionName, ref ent.Comp.BloodSolution))
|
||||
|| !_solutionContainer.ResolveSolution(ent.Owner, ent.Comp.BloodSolutionName, ref ent.Comp.BloodSolution))
|
||||
return false;
|
||||
|
||||
if (SolutionContainer.TryAddSolution(ent.Comp.BloodSolution.Value, solution))
|
||||
if (_solutionContainer.TryAddSolution(ent.Comp.BloodSolution.Value, solution))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
|
|
@ -370,7 +383,7 @@ public abstract partial class SharedBloodstreamSystem : EntitySystem
|
|||
public Solution? FlushChemicals(Entity<BloodstreamComponent?> ent, FixedPoint2 quantity, ProtoId<ReagentPrototype>? excludedReagent = null )
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp, logMissing: false)
|
||||
|| !SolutionContainer.ResolveSolution(ent.Owner, ent.Comp.BloodSolutionName, ref ent.Comp.BloodSolution, out var bloodSolution))
|
||||
|| !_solutionContainer.ResolveSolution(ent.Owner, ent.Comp.BloodSolutionName, ref ent.Comp.BloodSolution, out var bloodSolution))
|
||||
return null;
|
||||
|
||||
var flushedSolution = new Solution();
|
||||
|
|
@ -381,7 +394,7 @@ public abstract partial class SharedBloodstreamSystem : EntitySystem
|
|||
if (ent.Comp.BloodReferenceSolution.ContainsPrototype(reagentId.Prototype) || reagentId.Prototype == excludedReagent)
|
||||
continue;
|
||||
|
||||
var reagentFlushAmount = SolutionContainer.RemoveReagent(ent.Comp.BloodSolution.Value, reagentId, quantity);
|
||||
var reagentFlushAmount = _solutionContainer.RemoveReagent(ent.Comp.BloodSolution.Value, reagentId, quantity);
|
||||
flushedSolution.AddReagent(reagentId, reagentFlushAmount);
|
||||
}
|
||||
|
||||
|
|
@ -416,7 +429,7 @@ public abstract partial class SharedBloodstreamSystem : EntitySystem
|
|||
public bool TryRegulateBloodLevel(Entity<BloodstreamComponent?> ent, FixedPoint2 amount, float referenceFactor = 1f)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp, logMissing: false)
|
||||
|| !SolutionContainer.ResolveSolution(ent.Owner, ent.Comp.BloodSolutionName, ref ent.Comp.BloodSolution, out var bloodSolution)
|
||||
|| !_solutionContainer.ResolveSolution(ent.Owner, ent.Comp.BloodSolutionName, ref ent.Comp.BloodSolution, out var bloodSolution)
|
||||
|| amount == 0)
|
||||
return false;
|
||||
|
||||
|
|
@ -469,15 +482,15 @@ public abstract partial class SharedBloodstreamSystem : EntitySystem
|
|||
public bool TryBleedOut(Entity<BloodstreamComponent?> ent, FixedPoint2 amount)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp, logMissing: false)
|
||||
|| !SolutionContainer.ResolveSolution(ent.Owner, ent.Comp.BloodSolutionName, ref ent.Comp.BloodSolution)
|
||||
|| !_solutionContainer.ResolveSolution(ent.Owner, ent.Comp.BloodSolutionName, ref ent.Comp.BloodSolution)
|
||||
|| amount <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var leakedBlood = SolutionContainer.SplitSolution(ent.Comp.BloodSolution.Value, amount);
|
||||
var leakedBlood = _solutionContainer.SplitSolution(ent.Comp.BloodSolution.Value, amount);
|
||||
|
||||
if (!SolutionContainer.ResolveSolution(ent.Owner, ent.Comp.BloodTemporarySolutionName, ref ent.Comp.TemporarySolution, out var tempSolution))
|
||||
if (!_solutionContainer.ResolveSolution(ent.Owner, ent.Comp.BloodTemporarySolutionName, ref ent.Comp.TemporarySolution, out var tempSolution))
|
||||
return true;
|
||||
|
||||
tempSolution.AddSolution(leakedBlood, ProtoMan);
|
||||
|
|
@ -489,7 +502,7 @@ public abstract partial class SharedBloodstreamSystem : EntitySystem
|
|||
tempSolution.RemoveAllSolution();
|
||||
}
|
||||
|
||||
SolutionContainer.UpdateChemicals(ent.Comp.TemporarySolution.Value);
|
||||
_solutionContainer.UpdateChemicals(ent.Comp.TemporarySolution.Value);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -529,18 +542,18 @@ public abstract partial class SharedBloodstreamSystem : EntitySystem
|
|||
|
||||
var tempSol = new Solution();
|
||||
|
||||
if (SolutionContainer.ResolveSolution(ent.Owner, ent.Comp.BloodSolutionName, ref ent.Comp.BloodSolution, out var bloodSolution))
|
||||
if (_solutionContainer.ResolveSolution(ent.Owner, ent.Comp.BloodSolutionName, ref ent.Comp.BloodSolution, out var bloodSolution))
|
||||
{
|
||||
tempSol.MaxVolume += bloodSolution.MaxVolume;
|
||||
tempSol.AddSolution(bloodSolution, ProtoMan);
|
||||
SolutionContainer.RemoveAllSolution(ent.Comp.BloodSolution.Value);
|
||||
_solutionContainer.RemoveAllSolution(ent.Comp.BloodSolution.Value);
|
||||
}
|
||||
|
||||
if (SolutionContainer.ResolveSolution(ent.Owner, ent.Comp.BloodTemporarySolutionName, ref ent.Comp.TemporarySolution, out var tempSolution))
|
||||
if (_solutionContainer.ResolveSolution(ent.Owner, ent.Comp.BloodTemporarySolutionName, ref ent.Comp.TemporarySolution, out var tempSolution))
|
||||
{
|
||||
tempSol.MaxVolume += tempSolution.MaxVolume;
|
||||
tempSol.AddSolution(tempSolution, ProtoMan);
|
||||
SolutionContainer.RemoveAllSolution(ent.Comp.TemporarySolution.Value);
|
||||
_solutionContainer.RemoveAllSolution(ent.Comp.TemporarySolution.Value);
|
||||
}
|
||||
|
||||
_puddle.TrySpillAt(ent, tempSol, out _);
|
||||
|
|
@ -565,7 +578,7 @@ public abstract partial class SharedBloodstreamSystem : EntitySystem
|
|||
return;
|
||||
}
|
||||
|
||||
if (!SolutionContainer.ResolveSolution(ent.Owner, ent.Comp.BloodSolutionName, ref ent.Comp.BloodSolution, out var bloodSolution))
|
||||
if (!_solutionContainer.ResolveSolution(ent.Owner, ent.Comp.BloodSolutionName, ref ent.Comp.BloodSolution, out var bloodSolution))
|
||||
{
|
||||
ent.Comp.BloodReferenceSolution = reagents.Clone();
|
||||
DirtyField(ent, ent.Comp, nameof(BloodstreamComponent.BloodReferenceSolution));
|
||||
|
|
@ -586,7 +599,7 @@ public abstract partial class SharedBloodstreamSystem : EntitySystem
|
|||
|
||||
var solution = ent.Comp.BloodReferenceSolution.Clone();
|
||||
solution.ScaleSolution(currentVolume / solution.Volume);
|
||||
SolutionContainer.AddSolution(ent.Comp.BloodSolution.Value, solution);
|
||||
_solutionContainer.AddSolution(ent.Comp.BloodSolution.Value, solution);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -603,7 +616,7 @@ public abstract partial class SharedBloodstreamSystem : EntitySystem
|
|||
/// <summary>
|
||||
/// Gets new blood data for this entity and caches it in <see cref="BloodstreamComponent.BloodData"/>
|
||||
/// </summary>
|
||||
protected List<ReagentData> NewEntityBloodData(EntityUid uid)
|
||||
private List<ReagentData> NewEntityBloodData(EntityUid uid)
|
||||
{
|
||||
var bloodData = new List<ReagentData>();
|
||||
var dnaData = new DnaData();
|
||||
|
|
@ -19,7 +19,7 @@ public sealed partial class DevourSystem : EntitySystem
|
|||
[Dependency] private EntityWhitelistSystem _whitelistSystem = default!;
|
||||
[Dependency] private SharedActionsSystem _actionsSystem = default!;
|
||||
[Dependency] private SharedAudioSystem _audioSystem = default!;
|
||||
[Dependency] private SharedBloodstreamSystem _bloodstreamSystem = default!;
|
||||
[Dependency] private BloodstreamSystem _bloodstreamSystem = default!;
|
||||
[Dependency] private SharedContainerSystem _containerSystem = default!;
|
||||
[Dependency] private SharedDoAfterSystem _doAfterSystem = default!;
|
||||
[Dependency] private SharedPopupSystem _popupSystem = default!;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ namespace Content.Shared.EntityEffects.Effects.Body;
|
|||
/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
|
||||
public sealed partial class AddReagentToBloodstreamEntityEffectSystem : EntityEffectSystem<BloodstreamComponent, AddReagentToBloodstream>
|
||||
{
|
||||
[Dependency] private SharedBloodstreamSystem _bloodstream = default!;
|
||||
[Dependency] private BloodstreamSystem _bloodstream = default!;
|
||||
[Dependency] private ReactiveSystem _reactive = default!;
|
||||
|
||||
protected override void Effect(Entity<BloodstreamComponent> entity, ref EntityEffectEvent<AddReagentToBloodstream> args)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ namespace Content.Shared.EntityEffects.Effects.Body;
|
|||
/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
|
||||
public sealed partial class CleanBloodstreamEntityEffectSystem : EntityEffectSystem<BloodstreamComponent, CleanBloodstream>
|
||||
{
|
||||
[Dependency] private SharedBloodstreamSystem _bloodstream = default!;
|
||||
[Dependency] private BloodstreamSystem _bloodstream = default!;
|
||||
|
||||
protected override void Effect(Entity<BloodstreamComponent> entity, ref EntityEffectEvent<CleanBloodstream> args)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ namespace Content.Shared.EntityEffects.Effects.Body;
|
|||
/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
|
||||
public sealed partial class ModifyBleedEntityEffectSystem : EntityEffectSystem<BloodstreamComponent, ModifyBleed>
|
||||
{
|
||||
[Dependency] private SharedBloodstreamSystem _bloodstream = default!;
|
||||
[Dependency] private BloodstreamSystem _bloodstream = default!;
|
||||
|
||||
protected override void Effect(Entity<BloodstreamComponent> entity, ref EntityEffectEvent<ModifyBleed> args)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ namespace Content.Shared.EntityEffects.Effects.Body;
|
|||
/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
|
||||
public sealed partial class ModifyBloodLevelEntityEffectSystem : EntityEffectSystem<BloodstreamComponent, ModifyBloodLevel>
|
||||
{
|
||||
[Dependency] private SharedBloodstreamSystem _bloodstream = default!;
|
||||
[Dependency] private BloodstreamSystem _bloodstream = default!;
|
||||
|
||||
protected override void Effect(Entity<BloodstreamComponent> entity, ref EntityEffectEvent<ModifyBloodLevel> args)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
namespace Content.Server.Forensics;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Forensics.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This component is for items that can clean up forensic evidence
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class CleansForensicsComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// How long it takes to wipe prints/blood/etc. off of things using this entity
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float CleanDelay = 12.0f;
|
||||
public TimeSpan CleanDelay = TimeSpan.FromSeconds(12.0f);
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Forensics.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This component stops the entity from leaving finger prints,
|
||||
/// usually so fibers can be left instead.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class DnaSubstanceTraceComponent : Component;
|
||||
17
Content.Shared/Forensics/Components/FiberComponent.cs
Normal file
17
Content.Shared/Forensics/Components/FiberComponent.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Forensics.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This controls fibers left by gloves on items,
|
||||
/// which the forensics system uses.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class FiberComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public LocId FiberMaterial = "fibers-synthetic";
|
||||
|
||||
[DataField]
|
||||
public LocId? FiberColor;
|
||||
}
|
||||
25
Content.Shared/Forensics/Components/ForensicPadComponent.cs
Normal file
25
Content.Shared/Forensics/Components/ForensicPadComponent.cs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Forensics.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Used to take a sample of someone's fingerprints.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class ForensicPadComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The amount of time for the pad to be used to take a sample.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan ScanDelay = TimeSpan.FromSeconds(3.0f);
|
||||
|
||||
[AutoNetworkedField]
|
||||
public bool Used;
|
||||
|
||||
/// <summary>
|
||||
/// The information acquired from using the forensic pad.
|
||||
/// </summary>
|
||||
[AutoNetworkedField]
|
||||
public String Sample = string.Empty;
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Shared.Forensics.Components;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[AutoGenerateComponentState(raiseAfterAutoHandleState: true, fieldDeltas: true), AutoGenerateComponentPause]
|
||||
public sealed partial class ForensicScannerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// A list of fingerprint GUIDs that the forensic scanner found from the <see cref="ForensicsComponent"/> on an entity.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public List<string> Fingerprints = [];
|
||||
|
||||
/// <summary>
|
||||
/// A list of glove fibers that the forensic scanner found from the <see cref="ForensicsComponent"/> on an entity.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public List<string> Fibers = [];
|
||||
|
||||
/// <summary>
|
||||
/// DNA that the forensic scanner found from the <see cref="DnaComponent"/> on an entity.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public List<string> DNAs = [];
|
||||
|
||||
/// <summary>
|
||||
/// Residue that the forensic scanner found from the <see cref="ForensicsComponent"/> on an entity.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public List<string> Residues = [];
|
||||
|
||||
/// <summary>
|
||||
/// What is the name of the entity that was scanned last?
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This will be used for the title of the printout and displayed to players.
|
||||
/// </remarks>
|
||||
[DataField, AutoNetworkedField]
|
||||
public string LastScannedName = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// When will the scanner be ready to print again?
|
||||
/// </summary>
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField, AutoNetworkedField]
|
||||
public TimeSpan PrintReadyAt = TimeSpan.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// The time (in seconds) that it takes to scan an entity.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float ScanDelay = 3.0f;
|
||||
|
||||
/// <summary>
|
||||
/// How often can the scanner print out reports?
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan PrintCooldown = TimeSpan.FromSeconds(5);
|
||||
|
||||
/// <summary>
|
||||
/// The sound that's played when there's a match between a scan and an
|
||||
/// inserted forensic pad.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier SoundMatch = new SoundPathSpecifier("/Audio/Machines/Nuke/angry_beep.ogg");
|
||||
|
||||
/// <summary>
|
||||
/// The sound that's played when there's no match between a scan and an
|
||||
/// inserted forensic pad.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier SoundNoMatch = new SoundPathSpecifier("/Audio/Machines/airlock_deny.ogg");
|
||||
|
||||
/// <summary>
|
||||
/// The sound that's played when the scanner prints off a report.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier SoundPrint = new SoundPathSpecifier("/Audio/Machines/short_print_and_rip.ogg");
|
||||
|
||||
/// <summary>
|
||||
/// What type of paper the device will print.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntProtoId PaperPrototypeID = "ForensicReportPaper";
|
||||
}
|
||||
47
Content.Shared/Forensics/Components/ForensicsComponent.cs
Normal file
47
Content.Shared/Forensics/Components/ForensicsComponent.cs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Forensics.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Leaves evidence on entities for <see cref="ForensicScannerComponent"/> to find.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class ForensicsComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Leaves evidence of FingerPrints left by uncovered hands.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public HashSet<string> Fingerprints = [];
|
||||
|
||||
/// <summary>
|
||||
/// Leaves evidence of Fibers left by gloves.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public HashSet<string> Fibers = [];
|
||||
|
||||
/// <summary>
|
||||
/// Leaves evidence of DNA left by bodily fluids.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public HashSet<string> DNAs = [];
|
||||
|
||||
/// <summary>
|
||||
/// Leaves evidence of Residues left by cleaning products.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public HashSet<string> Residues = [];
|
||||
|
||||
/// <summary>
|
||||
/// How close you must be to wipe the prints/blood/etc. off of this entity
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float CleanDistance = 1.5f;
|
||||
|
||||
/// <summary>
|
||||
/// Can the DNA be cleaned off of this entity?
|
||||
/// e.g. you can wipe the DNA off of a knife, but not a cigarette
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool CanDnaBeCleaned = true;
|
||||
}
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
namespace Content.Server.Forensics.Components;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Forensics.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This component is for entities we do not wish to track fingerprints/fibers, like puddles
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class IgnoresFingerprintsComponent : Component { }
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class IgnoresFingerprintsComponent : Component;
|
||||
23
Content.Shared/Forensics/Components/ResidueComponent.cs
Normal file
23
Content.Shared/Forensics/Components/ResidueComponent.cs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Forensics.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This controls residues left on items
|
||||
/// which the forensics system uses.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class ResidueComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies residue, for now only color.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public LocId ResidueAdjective = "residue-unknown";
|
||||
|
||||
/// <summary>
|
||||
/// Leaves color of cleaning product for ResidueAdjective to use.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string? ResidueColor;
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Forensics.Components;
|
||||
using Content.Shared.Inventory;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
|
|
@ -12,7 +13,8 @@ public sealed partial class ForensicScannerDoAfterEvent : SimpleDoAfterEvent
|
|||
[Serializable, NetSerializable]
|
||||
public sealed partial class ForensicPadDoAfterEvent : DoAfterEvent
|
||||
{
|
||||
[DataField("sample", required: true)] public string Sample = default!;
|
||||
[DataField(required: true)]
|
||||
public string Sample = default!;
|
||||
|
||||
private ForensicPadDoAfterEvent()
|
||||
{
|
||||
|
|
@ -27,47 +29,23 @@ public sealed partial class ForensicPadDoAfterEvent : DoAfterEvent
|
|||
}
|
||||
|
||||
[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;
|
||||
}
|
||||
public sealed partial class CleanForensicsDoAfterEvent : SimpleDoAfterEvent;
|
||||
|
||||
/// <summary>
|
||||
/// Raised on an entity when its DNA has been changed.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct GenerateDnaEvent()
|
||||
public record struct GenerateDnaEvent(EntityUid Owner, string DNA)
|
||||
{
|
||||
/// <summary>
|
||||
/// The entity getting new DNA.
|
||||
/// </summary>
|
||||
public EntityUid Owner;
|
||||
public EntityUid Owner = Owner;
|
||||
|
||||
/// <summary>
|
||||
/// The generated DNA.
|
||||
/// </summary>
|
||||
public required string DNA;
|
||||
public string DNA = DNA;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -1,53 +1,15 @@
|
|||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Forensics
|
||||
namespace Content.Shared.Forensics;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum ForensicScannerUiKey : byte
|
||||
{
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class ForensicScannerBoundUserInterfaceState : BoundUserInterfaceState
|
||||
{
|
||||
public readonly List<string> Fingerprints = new();
|
||||
public readonly List<string> Fibers = 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;
|
||||
public readonly TimeSpan PrintReadyAt = TimeSpan.Zero;
|
||||
|
||||
public ForensicScannerBoundUserInterfaceState(
|
||||
List<string> fingerprints,
|
||||
List<string> fibers,
|
||||
List<string> touchDnas,
|
||||
List<string> solutionDnas,
|
||||
List<string> residues,
|
||||
string lastScannedName,
|
||||
TimeSpan printCooldown,
|
||||
TimeSpan printReadyAt)
|
||||
{
|
||||
Fingerprints = fingerprints;
|
||||
Fibers = fibers;
|
||||
TouchDNAs = touchDnas;
|
||||
SolutionDNAs = solutionDnas;
|
||||
Residues = residues;
|
||||
LastScannedName = lastScannedName;
|
||||
PrintCooldown = printCooldown;
|
||||
PrintReadyAt = printReadyAt;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum ForensicScannerUiKey : byte
|
||||
{
|
||||
Key
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class ForensicScannerPrintMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class ForensicScannerClearMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
}
|
||||
Key
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class ForensicScannerPrintMessage : BoundUserInterfaceMessage;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class ForensicScannerClearMessage : BoundUserInterfaceMessage;
|
||||
|
|
|
|||
17
Content.Shared/Forensics/Systems/FingerprintMaskSystem.cs
Normal file
17
Content.Shared/Forensics/Systems/FingerprintMaskSystem.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
using Content.Shared.Forensics.Components;
|
||||
using Content.Shared.Inventory;
|
||||
|
||||
namespace Content.Shared.Forensics.Systems;
|
||||
|
||||
public sealed partial class FingerprintMaskSystem : EntitySystem
|
||||
{
|
||||
[SubscribeLocalEvent]
|
||||
private void OnTryAccessFingerprint(Entity<FingerprintMaskComponent> gloves, ref InventoryRelayedEvent<TryAccessFingerprintEvent> args)
|
||||
{
|
||||
if (args.Args.Cancelled)
|
||||
return;
|
||||
|
||||
args.Args.Blocker = gloves.Owner;
|
||||
args.Args.Cancel();
|
||||
}
|
||||
}
|
||||
118
Content.Shared/Forensics/Systems/ForensicPadSystem.cs
Normal file
118
Content.Shared/Forensics/Systems/ForensicPadSystem.cs
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Forensics.Components;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Labels.EntitySystems;
|
||||
using Content.Shared.Popups;
|
||||
|
||||
namespace Content.Shared.Forensics.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// Used to transfer fingerprints from entities to forensic pads.
|
||||
/// </summary>
|
||||
public sealed partial class ForensicPadSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private SharedDoAfterSystem _doAfterSystem = default!;
|
||||
[Dependency] private SharedPopupSystem _popupSystem = default!;
|
||||
[Dependency] private SharedForensicsSystem _forensics = default!;
|
||||
[Dependency] private LabelSystem _label = default!;
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnExamined(Entity<ForensicPadComponent> pad, ref ExaminedEvent args)
|
||||
{
|
||||
if (!args.IsInDetailsRange)
|
||||
return;
|
||||
|
||||
if (!pad.Comp.Used)
|
||||
{
|
||||
args.PushMarkup(Loc.GetString("forensic-pad-unused"));
|
||||
return;
|
||||
}
|
||||
|
||||
args.PushMarkup(Loc.GetString("forensic-pad-sample", ("sample", pad.Comp.Sample)));
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnAfterInteract(Entity<ForensicPadComponent> pad, ref AfterInteractEvent args)
|
||||
{
|
||||
if (!args.CanReach || args.Target == null || HasComp<ForensicScannerComponent>(args.Target))
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
|
||||
if (pad.Comp.Used)
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("forensic-pad-already-used"), args.Target.Value, args.User);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_forensics.CanAccessFingerprint(args.Target.Value, out var blocker))
|
||||
{
|
||||
var message = blocker is { } item
|
||||
? Loc.GetString("forensic-pad-no-access-due", ("entity", Identity.Entity(item, EntityManager)))
|
||||
: Loc.GetString("forensic-pad-no-access");
|
||||
|
||||
_popupSystem.PopupEntity(message, args.Target.Value, args.User);
|
||||
return;
|
||||
}
|
||||
|
||||
if (TryComp<FingerprintComponent>(args.Target, out var fingerprint) && fingerprint.Fingerprint != null)
|
||||
{
|
||||
var skipDelay = true;
|
||||
if (args.User != args.Target)
|
||||
{
|
||||
var userMessage = Loc.GetString("forensic-pad-start-scan-user", ("target", Identity.Entity(args.Target.Value, EntityManager)));
|
||||
var targetMessage = Loc.GetString("forensic-pad-start-scan-target", ("user", Identity.Entity(args.User, EntityManager)));
|
||||
|
||||
skipDelay = false;
|
||||
_popupSystem.PopupEntity(userMessage, args.Target.Value, args.User);
|
||||
_popupSystem.PopupEntity(targetMessage, args.Target.Value, args.Target.Value);
|
||||
}
|
||||
StartScan(pad, args.User, args.Target.Value, pad.Comp, fingerprint.Fingerprint, skipDelay);
|
||||
return;
|
||||
}
|
||||
|
||||
if (TryComp<FiberComponent>(args.Target, out var fiber))
|
||||
{
|
||||
var fiberString = string.IsNullOrEmpty(fiber.FiberColor)
|
||||
? Loc.GetString("forensic-fibers", ("material", fiber.FiberMaterial))
|
||||
: Loc.GetString("forensic-fibers-colored", ("color", fiber.FiberColor), ("material", fiber.FiberMaterial));
|
||||
|
||||
StartScan(pad, args.User, args.Target.Value, pad.Comp, fiberString, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void StartScan(EntityUid used, EntityUid user, EntityUid target, ForensicPadComponent pad, string sample, bool skipDelay = false)
|
||||
{
|
||||
var ev = new ForensicPadDoAfterEvent(sample);
|
||||
var delay = skipDelay ? TimeSpan.Zero : pad.ScanDelay;
|
||||
|
||||
var doAfterEventArgs = new DoAfterArgs(EntityManager, user, delay, ev, used, target: target, used: used)
|
||||
{
|
||||
NeedHand = true,
|
||||
BreakOnMove = true,
|
||||
};
|
||||
|
||||
_doAfterSystem.TryStartDoAfter(doAfterEventArgs);
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnDoAfter(Entity<ForensicPadComponent> pad, ref ForensicPadDoAfterEvent args)
|
||||
{
|
||||
if (args.Handled || args.Cancelled)
|
||||
return;
|
||||
|
||||
if (args.Args.Target != null)
|
||||
{
|
||||
var label = Identity.Name(args.Args.Target.Value, EntityManager);
|
||||
_label.Label(pad, label);
|
||||
}
|
||||
|
||||
pad.Comp.Sample = args.Sample;
|
||||
pad.Comp.Used = true;
|
||||
|
||||
args.Handled = true;
|
||||
Dirty(pad);
|
||||
}
|
||||
}
|
||||
260
Content.Shared/Forensics/Systems/SharedForensicScannerSystem.cs
Normal file
260
Content.Shared/Forensics/Systems/SharedForensicScannerSystem.cs
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
using System.Linq;
|
||||
using System.Text;
|
||||
using Content.Shared.UserInterface;
|
||||
using Content.Shared.DoAfter;
|
||||
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.Shared.Audio;
|
||||
using Robust.Shared.Timing;
|
||||
using Content.Shared.Forensics.Components;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Popups;
|
||||
using Robust.Shared.Prototypes;
|
||||
// todo: remove this stinky LINQy
|
||||
|
||||
namespace Content.Shared.Forensics.Systems;
|
||||
|
||||
public sealed partial class SharedForensicScannerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private IGameTiming _gameTiming = default!;
|
||||
[Dependency] private SharedDoAfterSystem _doAfterSystem = default!;
|
||||
[Dependency] private SharedUserInterfaceSystem _uiSystem = default!;
|
||||
[Dependency] private SharedPopupSystem _popupSystem = default!;
|
||||
[Dependency] private PaperSystem _paperSystem = default!;
|
||||
[Dependency] private SharedHandsSystem _handsSystem = default!;
|
||||
[Dependency] private SharedAudioSystem _audioSystem = default!;
|
||||
[Dependency] private MetaDataSystem _metaData = default!;
|
||||
[Dependency] private SharedForensicsSystem _forensicsSystem = default!;
|
||||
[Dependency] private TagSystem _tag = default!;
|
||||
|
||||
private static readonly ProtoId<TagPrototype> DNASolutionScannableTag = "DNASolutionScannable";
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnScannerUpdate(Entity<ForensicScannerComponent> scanner, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
UpdateUi(scanner);
|
||||
}
|
||||
|
||||
private void UpdateUi(Entity<ForensicScannerComponent> scanner)
|
||||
{
|
||||
if (_uiSystem.TryGetOpenUi(scanner.Owner, ForensicScannerUiKey.Key, out var bui))
|
||||
{
|
||||
bui.Update();
|
||||
}
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnDoAfter(Entity<ForensicScannerComponent> scanner, ref ForensicScannerDoAfterEvent args)
|
||||
{
|
||||
if (args.Handled || args.Cancelled)
|
||||
return;
|
||||
|
||||
if (args.Args.Target != null)
|
||||
{
|
||||
if (TryComp<ForensicsComponent>(args.Args.Target, out var forensics))
|
||||
{
|
||||
scanner.Comp.Fingerprints = forensics.Fingerprints.ToList();
|
||||
scanner.Comp.Fibers = forensics.Fibers.ToList();
|
||||
scanner.Comp.DNAs = forensics.DNAs.ToList();
|
||||
scanner.Comp.Residues = forensics.Residues.ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
scanner.Comp.Fingerprints = [];
|
||||
scanner.Comp.Fibers = [];
|
||||
scanner.Comp.DNAs = [];
|
||||
scanner.Comp.Residues = [];
|
||||
}
|
||||
|
||||
if (_tag.HasTag(args.Args.Target.Value, DNASolutionScannableTag))
|
||||
{
|
||||
scanner.Comp.DNAs.AddRange(_forensicsSystem.GetSolutionsDNA(args.Args.Target.Value));
|
||||
}
|
||||
|
||||
scanner.Comp.LastScannedName = Identity.Name(args.Args.Target.Value, EntityManager, args.Args.User);
|
||||
|
||||
DirtyFields(scanner.AsNullable(),
|
||||
null,
|
||||
nameof(ForensicScannerComponent.Fingerprints),
|
||||
nameof(ForensicScannerComponent.Fibers),
|
||||
nameof(ForensicScannerComponent.DNAs),
|
||||
nameof(ForensicScannerComponent.Residues),
|
||||
nameof(ForensicScannerComponent.LastScannedName));
|
||||
}
|
||||
|
||||
OpenUserInterface(args.Args.User, scanner);
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// Hosts logic common between OnUtilityVerb and OnAfterInteract.
|
||||
/// </remarks>
|
||||
private void StartScan(Entity<ForensicScannerComponent> scanner, EntityUid user, EntityUid target)
|
||||
{
|
||||
_doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, user, scanner.Comp.ScanDelay, new ForensicScannerDoAfterEvent(), scanner, target: target, used: scanner)
|
||||
{
|
||||
BreakOnMove = true,
|
||||
NeedHand = true,
|
||||
});
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnUtilityVerb(Entity<ForensicScannerComponent> scanner, ref GetVerbsEvent<UtilityVerb> args)
|
||||
{
|
||||
if (!args.CanInteract || !args.CanAccess)
|
||||
return;
|
||||
|
||||
var evArgs = args;
|
||||
|
||||
var verb = new UtilityVerb()
|
||||
{
|
||||
Act = () => StartScan(scanner, evArgs.User, evArgs.Target),
|
||||
IconEntity = GetNetEntity(scanner),
|
||||
Text = Loc.GetString("forensic-scanner-verb-text"),
|
||||
Message = Loc.GetString("forensic-scanner-verb-message"),
|
||||
// This is important because if its true using the scanner will count as touching the object.
|
||||
DoContactInteraction = false,
|
||||
};
|
||||
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnAfterInteract(Entity<ForensicScannerComponent> scanner, ref AfterInteractEvent args)
|
||||
{
|
||||
if (args.Target == null || !args.CanReach)
|
||||
return;
|
||||
|
||||
StartScan(scanner, args.User, args.Target.Value);
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnAfterInteractUsing(Entity<ForensicScannerComponent> scanner, ref AfterInteractUsingEvent args)
|
||||
{
|
||||
if (args.Handled || !args.CanReach)
|
||||
return;
|
||||
|
||||
if (!TryComp<ForensicPadComponent>(args.Used, out var pad))
|
||||
return;
|
||||
|
||||
foreach (var fiber in scanner.Comp.Fibers)
|
||||
{
|
||||
if (fiber != pad.Sample)
|
||||
continue;
|
||||
|
||||
_audioSystem.PlayPredicted(scanner.Comp.SoundMatch, scanner.Owner, args.User);
|
||||
_popupSystem.PopupEntity(Loc.GetString("forensic-scanner-match-fiber"), scanner, args.User);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var fingerprint in scanner.Comp.Fingerprints)
|
||||
{
|
||||
if (fingerprint != pad.Sample)
|
||||
continue;
|
||||
|
||||
_audioSystem.PlayPredicted(scanner.Comp.SoundMatch, scanner.Owner, args.User);
|
||||
_popupSystem.PopupEntity(Loc.GetString("forensic-scanner-match-fingerprint"), scanner, args.User);
|
||||
return;
|
||||
}
|
||||
|
||||
_audioSystem.PlayPredicted(scanner.Comp.SoundNoMatch, scanner.Owner, args.User);
|
||||
_popupSystem.PopupEntity(Loc.GetString("forensic-scanner-match-none"), scanner, args.User);
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnBeforeActivatableUIOpen(Entity<ForensicScannerComponent> scanner, ref BeforeActivatableUIOpenEvent args)
|
||||
{
|
||||
UpdateUi(scanner);
|
||||
}
|
||||
|
||||
private void OpenUserInterface(EntityUid user, Entity<ForensicScannerComponent> scanner)
|
||||
{
|
||||
_uiSystem.OpenUi(scanner.Owner, ForensicScannerUiKey.Key, user, true);
|
||||
|
||||
UpdateUi(scanner);
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnPrint(Entity<ForensicScannerComponent> scanner, ref ForensicScannerPrintMessage args)
|
||||
{
|
||||
var user = args.Actor;
|
||||
|
||||
if (_gameTiming.CurTime < scanner.Comp.PrintReadyAt)
|
||||
{
|
||||
// This shouldn't occur due to the UI guarding against it, but
|
||||
// if it does, tell the user why nothing happened.
|
||||
_popupSystem.PopupEntity(Loc.GetString("forensic-scanner-printer-not-ready"), scanner, user);
|
||||
return;
|
||||
}
|
||||
|
||||
// Spawn a piece of paper.
|
||||
var printed = PredictedSpawnAtPosition(scanner.Comp.PaperPrototypeID, Transform(scanner).Coordinates);
|
||||
_handsSystem.PickupOrDrop(user, printed, checkActionBlocker: false);
|
||||
|
||||
if (!TryComp<PaperComponent>(printed, out var paperComp))
|
||||
{
|
||||
Log.Error("Printed paper did not have PaperComponent.");
|
||||
return;
|
||||
}
|
||||
|
||||
_metaData.SetEntityName(printed, Loc.GetString("forensic-scanner-report-title", ("entity", scanner.Comp.LastScannedName)));
|
||||
|
||||
var text = new StringBuilder();
|
||||
|
||||
text.AppendLine(Loc.GetString("forensic-scanner-interface-fingerprints"));
|
||||
foreach (var fingerprint in scanner.Comp.Fingerprints)
|
||||
{
|
||||
text.AppendLine(fingerprint);
|
||||
}
|
||||
text.AppendLine();
|
||||
text.AppendLine(Loc.GetString("forensic-scanner-interface-fibers"));
|
||||
foreach (var fiber in scanner.Comp.Fibers)
|
||||
{
|
||||
text.AppendLine(fiber);
|
||||
}
|
||||
text.AppendLine();
|
||||
text.AppendLine(Loc.GetString("forensic-scanner-interface-dnas"));
|
||||
foreach (var dna in scanner.Comp.DNAs)
|
||||
{
|
||||
text.AppendLine(dna);
|
||||
}
|
||||
text.AppendLine();
|
||||
text.AppendLine(Loc.GetString("forensic-scanner-interface-residues"));
|
||||
foreach (var residue in scanner.Comp.Residues)
|
||||
{
|
||||
text.AppendLine(residue);
|
||||
}
|
||||
|
||||
_paperSystem.SetContent((printed, paperComp), text.ToString());
|
||||
var audioParams = scanner.Comp.SoundPrint?.Params ?? AudioParams.Default;
|
||||
audioParams = audioParams.WithVariation(0.25f).AddVolume(3f).WithRolloffFactor(2.8f).WithMaxDistance(4.5f);
|
||||
_audioSystem.PlayPredicted(scanner.Comp.SoundPrint, scanner, user, audioParams);
|
||||
|
||||
scanner.Comp.PrintReadyAt = _gameTiming.CurTime + scanner.Comp.PrintCooldown;
|
||||
|
||||
DirtyField(scanner.AsNullable(), nameof(ForensicScannerComponent.PrintReadyAt));
|
||||
|
||||
UpdateUi(scanner);
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnClear(Entity<ForensicScannerComponent> scanner, ref ForensicScannerClearMessage args)
|
||||
{
|
||||
scanner.Comp.Fingerprints = [];
|
||||
scanner.Comp.Fibers = [];
|
||||
scanner.Comp.DNAs = [];
|
||||
scanner.Comp.LastScannedName = string.Empty;
|
||||
|
||||
DirtyFields(scanner.AsNullable(),
|
||||
null,
|
||||
nameof(ForensicScannerComponent.Fingerprints),
|
||||
nameof(ForensicScannerComponent.Fibers),
|
||||
nameof(ForensicScannerComponent.DNAs),
|
||||
nameof(ForensicScannerComponent.LastScannedName));
|
||||
|
||||
UpdateUi(scanner);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +1,371 @@
|
|||
using Content.Shared.Body.Systems;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Fluids;
|
||||
using Content.Shared.Forensics.Components;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Verbs;
|
||||
using Content.Shared.Gibbing;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Content.Shared.Weapons.Melee.Events;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Forensics.Systems;
|
||||
|
||||
public abstract class SharedForensicsSystem : EntitySystem
|
||||
public sealed partial class SharedForensicsSystem : EntitySystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Give the entity a new, random DNA string and call an event to notify other systems like the bloodstream that it has been changed.
|
||||
/// Does nothing if it does not have the DnaComponent.
|
||||
/// </summary>
|
||||
public virtual void RandomizeDNA(Entity<DnaComponent?> ent) { }
|
||||
[Dependency] private IRobustRandom _random = default!;
|
||||
[Dependency] private InventorySystem _inventory = default!;
|
||||
[Dependency] private SharedDoAfterSystem _doAfterSystem = default!;
|
||||
[Dependency] private SharedPopupSystem _popupSystem = default!;
|
||||
[Dependency] private SharedSolutionContainerSystem _solutionContainerSystem = default!;
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnSolutionChanged(Entity<DnaSubstanceTraceComponent> ent, ref SolutionChangedEvent ev)
|
||||
{
|
||||
var soln = GetSolutionsDNA(ev.Solution);
|
||||
|
||||
if (soln.Count <= 0)
|
||||
return;
|
||||
|
||||
var comp = EnsureComp<ForensicsComponent>(ent.Owner);
|
||||
foreach (var dna in soln)
|
||||
{
|
||||
comp.DNAs.Add(dna);
|
||||
}
|
||||
Dirty(ent);
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnInteract(Entity<HandsComponent> ent, ref ContactInteractionEvent args)
|
||||
{
|
||||
ApplyEvidence(ent.Owner, args.Other);
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent(after: [typeof(BloodstreamSystem)])]
|
||||
private void OnFingerprintInit(Entity<FingerprintComponent> ent, ref MapInitEvent args)
|
||||
{
|
||||
if (ent.Comp.Fingerprint == null)
|
||||
RandomizeFingerprint((ent.Owner, ent.Comp));
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent(after: [typeof(BloodstreamSystem)])]
|
||||
private void OnDNAInit(Entity<DnaComponent> ent, ref MapInitEvent args)
|
||||
{
|
||||
if (ent.Comp.DNA == null)
|
||||
RandomizeDNA(ent.AsNullable());
|
||||
else
|
||||
{
|
||||
// If set manually (for example by cloning) we also need to inform the bloodstream of the correct DNA string so it can be updated
|
||||
var ev = new GenerateDnaEvent { Owner = ent.Owner, DNA = ent.Comp.DNA };
|
||||
RaiseLocalEvent(ent.Owner, ref ev);
|
||||
Dirty(ent);
|
||||
}
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnBeingGibbed(Entity<ForensicsComponent> ent, ref GibbedBeforeDeletionEvent args)
|
||||
{
|
||||
var dna = Loc.GetString("forensics-dna-unknown");
|
||||
|
||||
if (TryComp(ent, out DnaComponent? dnaComp) && dnaComp.DNA != null)
|
||||
dna = dnaComp.DNA;
|
||||
|
||||
foreach (var part in args.Giblets)
|
||||
{
|
||||
var partComp = EnsureComp<ForensicsComponent>(part);
|
||||
partComp.DNAs.Add(dna);
|
||||
partComp.CanDnaBeCleaned = false;
|
||||
Dirty(part, partComp);
|
||||
}
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnMeleeHit(Entity<ForensicsComponent> weapon, ref 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))
|
||||
return;
|
||||
|
||||
foreach (var hitEntity in args.HitEntities)
|
||||
{
|
||||
if (TryComp<DnaComponent>(hitEntity, out var hitEntityComp) && hitEntityComp.DNA != null)
|
||||
weapon.Comp.DNAs.Add(hitEntityComp.DNA);
|
||||
}
|
||||
Dirty(weapon);
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnRehydrated(Entity<ForensicsComponent> ent, ref GotRehydratedEvent args)
|
||||
{
|
||||
CopyForensicsFrom(ent.Owner, args.Target);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Give the entity a new, random fingerprint string.
|
||||
/// Does nothing if it does not have the FingerprintComponent.
|
||||
/// Copy forensic information from a source entity to a destination.
|
||||
/// Existing forensic information on the target is still kept.
|
||||
/// </summary>
|
||||
public virtual void RandomizeFingerprint(Entity<FingerprintComponent?> ent) { }
|
||||
public void CopyForensicsFrom(Entity<ForensicsComponent?> src, EntityUid target)
|
||||
{
|
||||
if (!Resolve(target, ref src.Comp, false))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var targetComp = EnsureComp<ForensicsComponent>(target);
|
||||
foreach (var dna in src.Comp.DNAs)
|
||||
{
|
||||
targetComp.DNAs.Add(dna);
|
||||
}
|
||||
|
||||
foreach (var fiber in src.Comp.Fibers)
|
||||
{
|
||||
targetComp.Fibers.Add(fiber);
|
||||
}
|
||||
|
||||
foreach (var print in src.Comp.Fingerprints)
|
||||
{
|
||||
targetComp.Fingerprints.Add(print);
|
||||
}
|
||||
|
||||
foreach (var residue in src.Comp.Residues)
|
||||
{
|
||||
targetComp.Residues.Add(residue);
|
||||
}
|
||||
|
||||
Dirty(target, targetComp);
|
||||
}
|
||||
|
||||
public List<string> GetSolutionsDNA(EntityUid uid)
|
||||
{
|
||||
List<string> list = new();
|
||||
foreach (var (_, soln) in _solutionContainerSystem.EnumerateSolutions(uid))
|
||||
{
|
||||
list.AddRange(GetSolutionsDNA(soln.Comp.Solution));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public List<string> GetSolutionsDNA(Solution soln)
|
||||
{
|
||||
List<string> list = [];
|
||||
foreach (var reagent in soln.Contents)
|
||||
{
|
||||
foreach (var data in reagent.Reagent.EnsureReagentData())
|
||||
{
|
||||
if (data is DnaData)
|
||||
{
|
||||
list.Add(((DnaData) data).DNA);
|
||||
}
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
// IngestionSystem is for stopping the player from feeding soap when trying to clean someone.
|
||||
[SubscribeLocalEvent(before: [typeof(IngestionSystem)], after: [typeof(SharedAbsorbentSystem)])]
|
||||
private void OnAfterInteract(Entity<CleansForensicsComponent> cleanForensicsEntity, ref AfterInteractEvent args)
|
||||
{
|
||||
if (args.Handled || !args.CanReach || args.Target == null)
|
||||
return;
|
||||
|
||||
args.Handled = TryStartCleaning(cleanForensicsEntity, args.User, args.Target.Value);
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnUtilityVerb(Entity<CleansForensicsComponent> entity, ref GetVerbsEvent<UtilityVerb> args)
|
||||
{
|
||||
if (!args.CanInteract || !args.CanAccess)
|
||||
return;
|
||||
|
||||
// These need to be set outside for the anonymous method!
|
||||
var user = args.User;
|
||||
var target = args.Target;
|
||||
|
||||
var verb = new UtilityVerb
|
||||
{
|
||||
Act = () => TryStartCleaning(entity, user, target),
|
||||
Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/bubbles.svg.192dpi.png")),
|
||||
Text = Loc.GetString("forensics-verb-text"),
|
||||
Message = Loc.GetString("forensics-verb-message"),
|
||||
// This is important because if its true using the cleaning device will count as touching the object.
|
||||
DoContactInteraction = false,
|
||||
};
|
||||
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transfer DNA from one entity onto the forensics of another.
|
||||
/// Attempts to clean the given item with the given CleansForensics entity.
|
||||
/// </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 virtual void TransferDna(EntityUid recipient, EntityUid donor, bool canDnaBeCleaned = true) { }
|
||||
/// <param name="cleanForensicsEntity">The entity that is being used to clean the target.</param>
|
||||
/// <param name="user">The user that is using the cleanForensicsEntity.</param>
|
||||
/// <param name="target">The target of the forensics clean.</param>
|
||||
/// <returns>True if the target can be cleaned and has some sort of DNA or fingerprints / fibers and false otherwise.</returns>
|
||||
public bool TryStartCleaning(Entity<CleansForensicsComponent> cleanForensicsEntity, EntityUid user, EntityUid target)
|
||||
{
|
||||
if (!TryComp<ForensicsComponent>(target, out var forensicsComp))
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("forensics-cleaning-cannot-clean", ("target", Identity.Entity(target, EntityManager))), user, user, PopupType.MediumCaution);
|
||||
return false;
|
||||
}
|
||||
|
||||
var totalPrintsAndFibers = forensicsComp.Fingerprints.Count + forensicsComp.Fibers.Count;
|
||||
var hasRemovableDNA = forensicsComp.DNAs.Count > 0 && forensicsComp.CanDnaBeCleaned;
|
||||
|
||||
if (hasRemovableDNA || totalPrintsAndFibers > 0)
|
||||
{
|
||||
var cleanDelay = cleanForensicsEntity.Comp.CleanDelay;
|
||||
var doAfterArgs = new DoAfterArgs(EntityManager, user, cleanDelay, new CleanForensicsDoAfterEvent(), cleanForensicsEntity, target: target, used: cleanForensicsEntity)
|
||||
{
|
||||
NeedHand = true,
|
||||
BreakOnDamage = true,
|
||||
BreakOnMove = true,
|
||||
MovementThreshold = 0.01f,
|
||||
DistanceThreshold = forensicsComp.CleanDistance,
|
||||
};
|
||||
|
||||
_doAfterSystem.TryStartDoAfter(doAfterArgs);
|
||||
|
||||
var userPopupText = Loc.GetString("forensics-cleaning-user", ("target", Identity.Entity(target, EntityManager)));
|
||||
var othersPopupText = Loc.GetString("forensics-cleaning-others", ("user", Identity.Entity(user, EntityManager)), ("target", Identity.Entity(target, EntityManager)));
|
||||
_popupSystem.PopupEntity(userPopupText, othersPopupText, user, user);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_popupSystem.PopupEntity(Loc.GetString("forensics-cleaning-cannot-clean", ("target", Identity.Entity(target, EntityManager))), user, user, PopupType.MediumCaution);
|
||||
return false;
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnCleanForensicsDoAfter(Entity<ForensicsComponent> component, ref CleanForensicsDoAfterEvent args)
|
||||
{
|
||||
if (args.Handled || args.Cancelled || args.Args.Target == null)
|
||||
return;
|
||||
|
||||
if (!TryComp<ForensicsComponent>(args.Target, out var targetComp))
|
||||
return;
|
||||
|
||||
targetComp.Fibers = [];
|
||||
targetComp.Fingerprints = [];
|
||||
|
||||
if (targetComp.CanDnaBeCleaned)
|
||||
targetComp.DNAs = [];
|
||||
|
||||
// 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)));
|
||||
|
||||
Dirty(args.Target.Value, targetComp);
|
||||
}
|
||||
|
||||
public string GenerateFingerprint()
|
||||
{
|
||||
var fingerprint = new byte[16];
|
||||
_random.NextBytes(fingerprint);
|
||||
return Convert.ToHexString(fingerprint);
|
||||
}
|
||||
|
||||
public string GenerateDNA()
|
||||
{
|
||||
var letters = new[] { "A", "C", "G", "T" };
|
||||
var DNA = string.Empty;
|
||||
|
||||
for (var i = 0; i < 16; i++)
|
||||
{
|
||||
DNA += letters[_random.Next(letters.Length)];
|
||||
}
|
||||
|
||||
return DNA;
|
||||
}
|
||||
|
||||
private void ApplyEvidence(EntityUid user, EntityUid target)
|
||||
{
|
||||
if (HasComp<IgnoresFingerprintsComponent>(target))
|
||||
return;
|
||||
|
||||
var component = EnsureComp<ForensicsComponent>(target);
|
||||
if (_inventory.TryGetSlotEntity(user, "gloves", out var gloves))
|
||||
{
|
||||
if (TryComp<FiberComponent>(gloves, out var fiber) && !string.IsNullOrEmpty(fiber.FiberMaterial))
|
||||
component.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<FingerprintComponent>(user, out var fingerprint) && CanAccessFingerprint(user, out _))
|
||||
component.Fingerprints.Add(fingerprint.Fingerprint ?? "");
|
||||
|
||||
Dirty(target, component);
|
||||
}
|
||||
|
||||
#region PublicAPI
|
||||
|
||||
public void RandomizeDNA(Entity<DnaComponent?> ent)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
return;
|
||||
|
||||
ent.Comp.DNA = GenerateDNA();
|
||||
|
||||
var ev = new GenerateDnaEvent { Owner = ent.Owner, DNA = ent.Comp.DNA };
|
||||
RaiseLocalEvent(ent.Owner, ref ev);
|
||||
Dirty(ent);
|
||||
}
|
||||
|
||||
public void RandomizeFingerprint(Entity<FingerprintComponent?> ent)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
return;
|
||||
|
||||
ent.Comp.Fingerprint = GenerateFingerprint();
|
||||
Dirty(ent);
|
||||
}
|
||||
|
||||
/// <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) || donorComp.DNA == null)
|
||||
return;
|
||||
|
||||
EnsureComp<ForensicsComponent>(recipient, out var recipientComp);
|
||||
recipientComp.DNAs.Add(donorComp.DNA);
|
||||
recipientComp.CanDnaBeCleaned = canDnaBeCleaned;
|
||||
|
||||
Dirty(recipient, recipientComp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if there's a way to access the fingerprint of the target entity.
|
||||
/// </summary>
|
||||
/// <param name="target">The entity with the fingerprint</param>
|
||||
/// <param name="blocker">The entity that blocked accessing the fingerprint</param>
|
||||
public bool CanAccessFingerprint(EntityUid target, out EntityUid? blocker)
|
||||
{
|
||||
var ev = new TryAccessFingerprintEvent();
|
||||
|
||||
RaiseLocalEvent(target, ev);
|
||||
if (!ev.Cancelled && TryComp<InventoryComponent>(target, out var inv))
|
||||
_inventory.RelayEvent((target, inv), ev);
|
||||
|
||||
blocker = ev.Blocker;
|
||||
return !ev.Cancelled;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using Content.Shared.Damage.Systems;
|
|||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Forensics;
|
||||
using Content.Shared.Forensics.Systems;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Implants.Components;
|
||||
using Content.Shared.Interaction;
|
||||
|
|
@ -33,6 +34,7 @@ public abstract partial class SharedImplanterSystem : EntitySystem
|
|||
[Dependency] private SharedContainerSystem _container = default!;
|
||||
[Dependency] private SharedDoAfterSystem _doAfter = default!;
|
||||
[Dependency] private SharedPopupSystem _popup = default!;
|
||||
[Dependency] private SharedForensicsSystem _forensics = default!;
|
||||
[Dependency] private SharedUserInterfaceSystem _ui = default!;
|
||||
|
||||
[Dependency] private EntityQuery<SubdermalImplantComponent> _implantCompQuery;
|
||||
|
|
@ -286,8 +288,7 @@ public abstract partial class SharedImplanterSystem : EntitySystem
|
|||
else
|
||||
ImplantMode(ent);
|
||||
|
||||
var ev = new TransferDnaEvent { Donor = target, Recipient = ent.Owner };
|
||||
RaiseLocalEvent(target, ref ev);
|
||||
_forensics.TransferDna(ent, target);
|
||||
|
||||
Dirty(ent);
|
||||
}
|
||||
|
|
@ -435,8 +436,7 @@ public abstract partial class SharedImplanterSystem : EntitySystem
|
|||
_container.Remove(implant, implantContainer);
|
||||
_container.Insert(implant, implanterContainer);
|
||||
|
||||
var ev = new TransferDnaEvent { Donor = target, Recipient = implanter };
|
||||
RaiseLocalEvent(target, ref ev);
|
||||
_forensics.TransferDna(implanter, target);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ public abstract partial class SharedCryoPodSystem : EntitySystem
|
|||
[Dependency] private MobStateSystem _mobState = default!;
|
||||
[Dependency] private ReactiveSystem _reactive = default!;
|
||||
[Dependency] protected SharedAppearanceSystem Appearance = default!;
|
||||
[Dependency] private SharedBloodstreamSystem _bloodstream = default!;
|
||||
[Dependency] private BloodstreamSystem _bloodstream = default!;
|
||||
[Dependency] private SharedContainerSystem _container = default!;
|
||||
[Dependency] private SharedDoAfterSystem _doAfter = default!;
|
||||
[Dependency] private SharedHandsSystem _hands = default!;
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ public sealed partial class HealingSystem : EntitySystem
|
|||
[Dependency] private SharedAudioSystem _audio = default!;
|
||||
[Dependency] private ISharedAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private DamageableSystem _damageable = default!;
|
||||
[Dependency] private SharedBloodstreamSystem _bloodstreamSystem = default!;
|
||||
[Dependency] private BloodstreamSystem _bloodstreamSystem = default!;
|
||||
[Dependency] private SharedDoAfterSystem _doAfter = default!;
|
||||
[Dependency] private SharedStackSystem _stacks = default!;
|
||||
[Dependency] private SharedInteractionSystem _interactionSystem = default!;
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ public sealed partial class VomitSystem : EntitySystem
|
|||
[Dependency] private MobStateSystem _mobState = default!;
|
||||
[Dependency] private MovementModStatusSystem _movementMod = default!;
|
||||
[Dependency] private SharedAudioSystem _audio = default!;
|
||||
[Dependency] private SharedBloodstreamSystem _bloodstream = default!;
|
||||
[Dependency] private BloodstreamSystem _bloodstream = default!;
|
||||
[Dependency] private SharedForensicsSystem _forensics = default!;
|
||||
[Dependency] private SharedPopupSystem _popup = default!;
|
||||
[Dependency] private SharedPuddleSystem _puddle = default!;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ using Content.Shared.Database;
|
|||
using Content.Shared.Destructible;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Forensics;
|
||||
using Content.Shared.Forensics.Systems;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Interaction;
|
||||
|
|
@ -56,6 +56,7 @@ public sealed partial class IngestionSystem : EntitySystem
|
|||
[Dependency] private SharedPopupSystem _popup = default!;
|
||||
[Dependency] private SharedSolutionContainerSystem _solutionContainer = default!;
|
||||
[Dependency] private SharedTransformSystem _transform = default!;
|
||||
[Dependency] private SharedForensicsSystem _forensics = default!;
|
||||
|
||||
// Body Component Dependencies
|
||||
[Dependency] private BodySystem _body = default!;
|
||||
|
|
@ -497,13 +498,7 @@ public sealed partial class IngestionSystem : EntitySystem
|
|||
if (!IsEmpty(entity))
|
||||
{
|
||||
// Leave some of the consumer's DNA on the consumed item...
|
||||
var ev = new TransferDnaEvent
|
||||
{
|
||||
Donor = args.Target,
|
||||
Recipient = entity,
|
||||
CanDnaBeCleaned = false,
|
||||
};
|
||||
RaiseLocalEvent(args.Target, ref ev);
|
||||
_forensics.TransferDna(entity, args.Target, false);
|
||||
|
||||
args.Repeat = !args.ForceFed;
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ public sealed partial class RootableSystem : EntitySystem
|
|||
[Dependency] private ReactiveSystem _reactive = default!;
|
||||
[Dependency] private SharedActionsSystem _actions = default!;
|
||||
[Dependency] private SharedAudioSystem _audio = default!;
|
||||
[Dependency] private SharedBloodstreamSystem _blood = default!;
|
||||
[Dependency] private BloodstreamSystem _blood = default!;
|
||||
[Dependency] private SharedGravitySystem _gravity = default!;
|
||||
[Dependency] private SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private SharedSolutionContainerSystem _solutionContainer = default!;
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ 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-user = You begin cleaning the evidence off of {THE($target)}...
|
||||
forensics-cleaning-others = {CAPITALIZE($user)} begins cleaning the evidence off of {THE($target)}.
|
||||
|
||||
forensics-cleaning-cannot-clean = There is nothing cleanable on {THE($target)}!
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue