Files
Delta-v/Content.Server/Forensics/Systems/ForensicsSystem.cs
T
29e55b9464 Upstream Merge (June / July) attempt 2 electric boogaloo (#4607)
* Fix the sensor monitoring console forcing a GC every 3 seconds (#38146)

* Optimize sensor monitoring window graph drawing

* Add shared static Vector2 pool for all GraphView instances

* Address requested changes

* remove lock

* Update submodule to 264.0.0 (#38629)

* Toy/Plushie Inhands and Wearables (#38514)

* Fixed dependency injection and some other issues in a few places.

* More issue fixes

* patchfor broken NetworkConfiguratorLinkMenu (#38632)

fix

* Fix solutions flickering when transferring contents (#34838)

* Use Solution clones when applying SolutionComponent states

* Revert "Use Solution clones when applying SolutionComponent states"

This reverts commit 013fd111cf92b22562e00f98a7aaa49bc4b4ed62.

* Make Solution implement ICloneable and rename Clone method.

* Copy CanReact value when cloning a Solution

* Convert to IRobustCloneable

* Fix typos in guidebook: Buisness → Business (#38636)

* Fix typo in `MinorAntagonists.xml`

Buisness → Business

* Fix typo in `YourFirstCharacter.xml`

Buisness → Business

* Various Headphones Fixes and Tweaks (#38479)

* sprites, wearables

* neck

* icon-on sprite

* Cleanup prototype instantiation in `DamageTest` (#38639)

Cleanup prototype instantiation in DamageTest

* Cleanup prototype instantiation in `ExplosionSystem` (#38642)

Cleanup prototype instantiation in ExplosionSystem

* AddBodyPartCommand localization. (#38612)

commit

* fix ItemSlotsSystem debug assert (#38655)

* Allow the Command & Super door remotes to use the access of their user. (Re-creation of PR due to changes to game balance) (#35536)

* Added directional beacons (#38284)

* Added directional beacons

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Fixed names

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

---------

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Power stat and nuke codes commands get some LEC love. (#38585)

* commit

* requested changes.

* Dsay Dirty and Follow commands converted to LEC and localized. (#38666)

* commit

* whoopwhoopwhoop

* Retro laser sprite fix (#38676)

* Fixed everything except the icon

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* fixed icon

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

---------

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* fix water coolers (#38681)

* Monochromacy typo fix (#38686)

* fixes the typo

* Fixed cloning looking for the trait, not the component, RE https://github.com/space-wizards/space-station-14/pull/38686#issuecomment-3025093504

* Apply suggestions from code review

---------

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

* Validate `CloningSettingsPrototype`s (#38688)

* Validate CloningSettingsPrototypes

* Update Content.IntegrationTests/Tests/Cloning/CloningSettingsPrototypeTest.cs

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

* Check EventComponents too

---------

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

* Add test of objective-related console commands (#36400)

* Add test of objective add/list/remove commands

* Not sure why we're validating test prototypes, but sure

* We don't need a map

* Improvements and fixups for New Status Effect API (#38660)

* Predict healing and bloodstream (#38690)

* initial commit

* reapply 38126

* fix rootable

* someone missed an important minus sign here

* try this

* fix

* fix

* reenable crit hits

* cleanup

* fix status time dirtying

* fix

* camelCase

* Healing and bloodstream prediction fixes.

* Resolving Wizard casting recall on nuke disk making it impossible to disarm (#38661)

* Resolving Wizard Recall on Nuke disk making it impossible to disarm
- Adding a DisarmBomb case to nuke status update loop
- Changing a few methods and parameters to properly follow formatting standards
- Updating some names to follow camelCase

* Updating missed tag

* Reverting DataField change

Should prevent this preventative bugfix being a breaking change.

* Switch HSV to the default colorspace for character customization (#38434)

* Made HSV default for character editor

* Adds/fixes comments to HSV defaulting

* Added dropbox fix, potentially cursed

* Revert "Added dropbox fix, potentially cursed"

This reverts commit a709883366fbee813e839742125e70844672af29.

---------

Co-authored-by: TrixxedHeart <46364955+TrixxedBit@users.noreply.github.com>

* Fix: Don't deploy foldables when clicking on items inside containers (#38709)

* Fix

* Apply suggestions from code review

---------

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

* Pressure Relief Valve (#36708)

* initial system (this math is probably WRONG)

* General code cleanup and OnExamined support
(holy moly this code sucks)

* UICode and related events foundation
TODO:
- Actually write the XAML UI and the underlying system
- Un-shitcode the entire thing
- Actually test everything...

* Working UI code
TODO: Make predicted, as this certainly isn't predicted. Even though I said it was. It isn't.

* Remove one TODO for unshitcoding the examine code

* Add reminder
yea

* Make predicted (defenitely isn't)
(also defenitely isn't a copypaste from pressure pump code)

* It's predicted!
TODO:
- Give it snazzy predicted visuals!
- Have a different field for pressure entry, lest it gets bulldozed every UI update.

* Improve gas pressure relief valve UI
TODO: Reminder to reduce amount of dirties using deltafields

* Implement DirtyField prediction

* Entity<T> cleanup
A lot of Entity<T> conversions and lukewarm cleanup.

Also got caught copy pasting code in 4K UHD but it's not like you couldn't tell.

* More cleanup and comments

* Remove TODO comment on bulldozing window title

* """refactoring"""
- Move appearance out of shared and finally fix it. Pointless to predict appearance in this instance.
- More Entity<T> conversions because I like them.
- Move UI creation handling over entirely to the ActivatableUI system.
- Fix a hardcoded locale string (why????).

* Add visuals

* Revert debugging variable replacememt
yea

* Revert skissue

* Remove unused using directives and remove TODO

* Localize, cleanup, document

* Fix adminlogging discrepancy

* Add ability to construct, add guidebook entry

* Clear up comment

* Add guidebook tooltip to valve

* Convert GasPressureReliefValveBoundUserInterface declaration into primary constructor

* Adds more input handling and adds autofill on open

* Un-deepfry input validator shitcode
Genuinely what was I smoking

* improve visuals logic

* Refactor again
- Update math to the correct implementation
- Moved code that could be re-used in the future into a helper method under AtmosphereSystem.Gases.cs

* I'm sorry but I hate warnings

* Remove unused using directive in AtmosphereSystem.Gases.cs

* Review and cleanup

* Lukewarm UI glossup

* Maintainer for the upstream project btw

* Remove redundant state sets and messy logic

* Unduplicate valve updater code

* Redo UI (im sorry Slarti)

* run tests

* Test refactored UI messaging

* Second round of UI improvements
- God please find a way to improve this system. Feels bad.

* Update loop implementation

* Further predict UI

* Clear up SetToCurrentThreshold

* cleanup

* Update to master + pipe layers and bug fixes
want to run tests

* fixes

* Deploy rename pipebomb

* Documentation and requested changes

* Rename the method that wiggled away

* Undo rounding changes

* Fix comment

* Rename and cleanup

* Apply suggestions from code review

---------

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

* Fix TextLinkTag (#32203)

* Adjust uplink buy button to be under item icon (#38596)

* Adjusted uplink buy button to be under item icon

* Put the discount subtext under the icon

* Indent fixes, added margin

---------

Co-authored-by: TrixxedHeart <46364955+TrixxedBit@users.noreply.github.com>

* Scurrets - Audio Improvements (#38482)

* Scurret audio tuning

* Add new sfx

* Update sneezing sfx

* YAML support

* Rename a folder

* make telesci wreck easier (#37569)

rel

* Validate `ShaderPrototype` IDs (#38728)

* Convert all shader prototype string literals to protoids in overlays

* Convert more shader prototype literal strings to protoids

* Convert ValidatePrototypeId to ProtoId

* Later

* [BUGFIX] Fixed revenant malfunction ability not working properly only MediBots and Stasis bed (#38664)

* fixed

* clean up

* orks fix smart

* review fix 1

* more requested changes

* less cursed

* more descriptive description

* better wording

* Tiny Tiny Cleanup of the EyeClosingSystem. (#38734)

Update EyeClosingSystem.cs

* Fix human skin tone distribution (#38701)

* fix: Use PredictedQueueDel for gib spell (#38729)

* fix: don't default to uncharged sprite state for cells (#38730)

* Fix Hristov description - remove inaccurate technical specs (#38746)

- Removes inaccurate 'armor piercing 14.5mm shells' reference
- Replaces it with a more funny description, matching the style of the other snipers and guns
- Keeps ammunition type '.60 anti-materiel ammo' specification
- Fixes issue #38590

Co-authored-by: Arthur Fiorese de Andrade <aandrade@cmcxs.gov.br>

* feat: allow mopping evaporating puddles (#38743)

* Validate remaining `ProtoId` strings (#38747)

Validate remaining ProtoId strings

* Validate `ProtoId`s in tests (#38745)

* Convert string literals to protoids in Content.Tests

* Convert string literals to protoids or consts in Content.IntegrationTests

* Fix linter failures
Tricksy static using misled me

* Cleanup warning in StomachSystem (#38748)

you did not see this

* Vox scars (#38592)

* Added vox scars n'stuff, renamed vox_tattoos.ftl to just vox.ftl

* Revert "Added vox scars n'stuff, renamed vox_tattoos.ftl to just vox.ftl"

This reverts commit c73da55ba3b39ddf93b493aecd85604c54dd8a15.

* locale key fix

* Changed top surgery scar names to be more generalized

* Adjusted face scars

* Formatting fixes

---------

Co-authored-by: TrixxedHeart <46364955+TrixxedBit@users.noreply.github.com>

* UnlockNode command to LEC. (#38751)

* commit

* Update UnlockNodeCommand.cs

* commit

* move command locale to its own file.

* Update Content.Server/Xenoarchaeology/Artifact/UnlockNodeCommand.cs

---------

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

* Typofixes for figurine dialogue (#38737)

* Typofixes for figurine dialogue

* Forgot two

* Janitor Tool: Wire Brush (#38667)

* Wow! It's -brush-

* spacing.

* Update Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

* Update Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

* fixed changes

---------

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

* Minor escape menu UX improvements (#38650)

* fix: spellbooks can have infinite charges (#38376)

* fix: spellbooks can have infinite charges

* refactor: indicate infinite spellbook charges with null

Not sure if I like this much better...
2025-11-06 18:22:39 +00:00

411 lines
18 KiB
C#

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.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 class ForensicsSystem : EntitySystem
{
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
public override void Initialize()
{
SubscribeLocalEvent<HandsComponent, ContactInteractionEvent>(OnInteract);
SubscribeLocalEvent<FiberComponent, MapInitEvent>(OnFiberInit, after: [typeof(BloodstreamSystem)]); // DeltaV - (#1455) unique glove fibers
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, BeingGibbedEvent>(OnBeingGibbed);
SubscribeLocalEvent<ForensicsComponent, MeleeHitEvent>(OnMeleeHit);
SubscribeLocalEvent<ForensicsComponent, GotRehydratedEvent>(OnRehydrated);
SubscribeLocalEvent<CleansForensicsComponent, AfterInteractEvent>(OnAfterInteract, after: new[] { typeof(AbsorbentSystem) });
SubscribeLocalEvent<ForensicsComponent, CleanForensicsDoAfterEvent>(OnCleanForensicsDoAfter);
SubscribeLocalEvent<DnaComponent, TransferDnaEvent>(OnTransferDnaEvent);
SubscribeLocalEvent<DnaSubstanceTraceComponent, SolutionContainerChangedEvent>(OnSolutionChanged);
SubscribeLocalEvent<CleansForensicsComponent, GetVerbsEvent<UtilityVerb>>(OnUtilityVerb);
}
private void OnSolutionChanged(Entity<DnaSubstanceTraceComponent> ent, ref SolutionContainerChangedEvent ev)
{
var soln = GetSolutionsDNA(ev.Solution);
if (soln.Count > 0)
{
var comp = EnsureComp<ForensicsComponent>(ent.Owner);
foreach (string dna in soln)
{
comp.DNAs.Add(dna);
}
}
}
private void OnInteract(EntityUid uid, HandsComponent component, ContactInteractionEvent args)
{
ApplyEvidence(uid, args.Other);
}
// Begin DeltaV Additions
// #1455 - unique glove fibers
private void OnFiberInit(Entity<FiberComponent> ent, ref MapInitEvent args)
{
ent.Comp.Fiberprint = GenerateFingerprint(length: 7);
}
// End DeltaV Additions
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(EntityUid uid, ForensicsComponent component, BeingGibbedEvent args)
{
string dna = Loc.GetString("forensics-dna-unknown");
if (TryComp(uid, out DnaComponent? dnaComp) && dnaComp.DNA != null)
dna = dnaComp.DNA;
foreach (EntityUid part in args.GibbedParts)
{
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();
if (TryComp<SolutionContainerManagerComponent>(uid, out var comp))
{
foreach (var (_, soln) in _solutionContainerSystem.EnumerateSolutions((uid, comp)))
{
list.AddRange(GetSolutionsDNA(soln.Comp.Solution));
}
}
return list;
}
public List<string> GetSolutionsDNA(Solution soln)
{
List<string> list = new();
foreach (var reagent in soln.Contents)
{
foreach (var data in reagent.Reagent.EnsureReagentData())
{
if (data is DnaData)
{
list.Add(((DnaData) data).DNA);
}
}
}
return list;
}
private void OnAfterInteract(Entity<CleansForensicsComponent> cleanForensicsEntity, ref AfterInteractEvent args)
{
if (args.Handled || !args.CanReach || args.Target == null)
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(Loc.GetString("forensics-verb-text")),
Message = Loc.GetString(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", target)), 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", target)), user, user);
return true;
}
else
{
_popupSystem.PopupEntity(Loc.GetString("forensics-cleaning-cannot-clean", ("target", target)), 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(int length = 16) // DeltaV #1455 - allow changing the length of the fingerprint hash
{
var fingerprint = new byte[length]; // DeltaV #1455 - allow changing the length of the fingerprint hash
_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);
// Begin DeltaV Additions - IPCs and borgs leave fibers
if (TryComp<FiberComponent>(user, out var fiber) && CanAccessFingerprint(user, out _))
{
var fiberLocale = string.IsNullOrEmpty(fiber.FiberColor)
? Loc.GetString("forensic-fibers", ("material", fiber.FiberMaterial))
: Loc.GetString("forensic-fibers-colored", ("color", fiber.FiberColor), ("material", fiber.FiberMaterial));
component.Fibers.Add(fiberLocale + " ; " + fiber.Fiberprint);
}
// End DeltaV Additions
if (_inventory.TryGetSlotEntity(user, "gloves", out var gloves))
{
// DeltaV #1455 - unique glove fibers
if (TryComp<FiberComponent>(gloves, out fiber) && !string.IsNullOrEmpty(fiber.FiberMaterial))
{
var fiberLocale = string.IsNullOrEmpty(fiber.FiberColor)
? Loc.GetString("forensic-fibers", ("material", fiber.FiberMaterial))
: Loc.GetString("forensic-fibers-colored", ("color", fiber.FiberColor), ("material", fiber.FiberMaterial));
component.Fibers.Add(fiberLocale + " ; " + fiber.Fiberprint);
}
// End of DeltaV code
}
if (TryComp<FingerprintComponent>(user, out var fingerprint) && CanAccessFingerprint(user, out _))
component.Fingerprints.Add(fingerprint.Fingerprint ?? "");
}
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
/// <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 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);
}
/// <summary>
/// Give the entity a new, random fingerprint string.
/// Does nothing if it does not have the FingerprintComponent.
/// </summary>
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)
{
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
}
}