Files
wwdpublic/Content.Server/Forensics/Systems/ForensicsSystem.cs
Angelo Fallaria 8768df7912 Unique Glove Fibers (#642)
# Description

Cherry-picked from Delta-V, originally by @WarMechanic
(https://github.com/DeltaV-Station/Delta-v/pull/1455).

Original Description:

> Every pair of gloves now has its own fingerprint, so items can be
traced back to gloves which can then be traced back to people.
> 
> ## Why / Balance
> 
> Evidence is very important to court cases running smoothly, so
detectives now have more evidence that can certify whether or not John
Syndicate's behaviour is valid. Traitors are now encouraged to either
clean evidence off syndicate gear regardless of glove status, or use a
disposable pair of gloves specifically for handling syndicate gear to be
disposed of later.
> 
> Aside from being required to obfuscate evidence you leave behind,
there is now a value proposition to searching glove prints of
departments. Wearing gloves that does not correspond your department can
punish an unknowing detective into searching the wrong people.
> 
> ## Technical details
> 
> `FiberComponent.cs` now stores a Fiberprint variable like
`FingerprintComponent.cs`. The code for assigning a fiberprint is the
same as the fingerprint. When evidence is placed on an object, the
fiberprint is concatenated to its localised fiber type.

<details><summary><h1>Original Media</h1></summary>
<p>

> hm ok we have these specific gloves on an akms
> <br>
> 
> <img
src="https://private-user-images.githubusercontent.com/69510347/345294367-55819ed3-1e89-401f-b7fb-0d8569bd7aa2.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjI1NjU2ODYsIm5iZiI6MTcyMjU2NTM4NiwicGF0aCI6Ii82OTUxMDM0Ny8zNDUyOTQzNjctNTU4MTllZDMtMWU4OS00MDFmLWI3ZmItMGQ4NTY5YmQ3YWEyLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDA4MDIlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwODAyVDAyMjMwNlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTYxZWUxMjZjY2FiMTU1ODdlOGFiNDliN2ZjZjg4MmYxZmY3ZWY0MGMzN2UxZWM1MjA2MjBlYjY1ZDQ2YTJjMGYmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.t5sASM_0is4bqd1YIizQ0lldAZ5RAStbNzXNuzHmdfU">
> 
> hm well we found the gloves and they have fingerprints
> <br>
> 
> <img
src="https://private-user-images.githubusercontent.com/69510347/345294443-a9b21171-bee5-4c5f-8ec6-cf5576572d45.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjI1NjU2ODYsIm5iZiI6MTcyMjU2NTM4NiwicGF0aCI6Ii82OTUxMDM0Ny8zNDUyOTQ0NDMtYTliMjExNzEtYmVlNS00YzVmLThlYzYtY2Y1NTc2NTcyZDQ1LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDA4MDIlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwODAyVDAyMjMwNlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTM3ZTY3NjRhZmEzMjRhYTkwNjUyOWI3MzgzOGM0ZWRjY2Q3MTg0YzViN2NjMDQyZTgzMDBiMmZlZDAyYWJjYjUmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.uSn27Drf1K8rcfQtYRo_fGKudbO03fp40GNHUSd4Uw0">
> 
> gotem
> <br>
> 
> <img
src="https://private-user-images.githubusercontent.com/69510347/345294064-d6254f48-2d81-4702-ac03-c1c796712ef4.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjI1NjU2ODYsIm5iZiI6MTcyMjU2NTM4NiwicGF0aCI6Ii82OTUxMDM0Ny8zNDUyOTQwNjQtZDYyNTRmNDgtMmQ4MS00NzAyLWFjMDMtYzFjNzk2NzEyZWY0LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDA4MDIlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwODAyVDAyMjMwNlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTgzNWYzODBmOWVhYzQzMjMxNDdlMzk5Zjk2ODY4N2I1ZWNmYmNkNGI2NGFlMjZiMDM1NGVjNDJlOGFjNjViN2YmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.TjsSLQpLoGsXOHS-wl_LSASdLxgeVTKxCGugkX7-v90">

</details>
</p>

# Changelog

🆑 WarMechanic
- add: Gloves now have unique fingerprints. Items can be traced back to
gloves, which can then be traced back to people.

---------

Signed-off-by: Angelo Fallaria <ba.fallaria@gmail.com>
Co-authored-by: WarMechanic <69510347+WarMechanic@users.noreply.github.com>
Co-authored-by: deltanedas <39013340+deltanedas@users.noreply.github.com>
Co-authored-by: Danger Revolution! <142105406+DangerRevolution@users.noreply.github.com>
Co-authored-by: VMSolidus <evilexecutive@gmail.com>
2024-08-06 18:03:35 -04:00

230 lines
9.6 KiB
C#

using Content.Server.Body.Components;
using Content.Server.DoAfter;
using Content.Server.Fluids.EntitySystems;
using Content.Server.Forensics.Components;
using Content.Server.Popups;
using Content.Shared.Chemistry.Components;
using Content.Shared.DoAfter;
using Content.Shared.Forensics;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Inventory;
using Content.Shared.Weapons.Melee.Events;
using Robust.Shared.Random;
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!;
public override void Initialize()
{
SubscribeLocalEvent<FingerprintComponent, ContactInteractionEvent>(OnInteract);
SubscribeLocalEvent<FiberComponent, MapInitEvent>(OnFiberInit);
SubscribeLocalEvent<FingerprintComponent, MapInitEvent>(OnFingerprintInit);
SubscribeLocalEvent<DnaComponent, MapInitEvent>(OnDNAInit);
SubscribeLocalEvent<DnaComponent, 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);
}
private void OnInteract(EntityUid uid, FingerprintComponent component, ContactInteractionEvent args)
{
ApplyEvidence(uid, args.Other);
}
private void OnFiberInit(EntityUid uid, FiberComponent component, MapInitEvent args)
{
component.Fiberprint = GenerateFingerprint(length: 7);
}
private void OnFingerprintInit(EntityUid uid, FingerprintComponent component, MapInitEvent args)
{
component.Fingerprint = GenerateFingerprint();
}
private void OnDNAInit(EntityUid uid, DnaComponent component, MapInitEvent args)
{
component.DNA = GenerateDNA();
}
private void OnBeingGibbed(EntityUid uid, DnaComponent component, BeingGibbedEvent args)
{
foreach(EntityUid part in args.GibbedParts)
{
var partComp = EnsureComp<ForensicsComponent>(part);
partComp.DNAs.Add(component.DNA);
partComp.CanDnaBeCleaned = false;
}
}
private void OnMeleeHit(EntityUid uid, ForensicsComponent component, MeleeHitEvent args)
{
if((args.BaseDamage.DamageDict.TryGetValue("Blunt", out var bluntDamage) && bluntDamage.Value > 0) ||
(args.BaseDamage.DamageDict.TryGetValue("Slash", out var slashDamage) && slashDamage.Value > 0) ||
(args.BaseDamage.DamageDict.TryGetValue("Piercing", out var pierceDamage) && pierceDamage.Value > 0))
{
foreach(EntityUid hitEntity in args.HitEntities)
{
if(TryComp<DnaComponent>(hitEntity, out var hitEntityComp))
component.DNAs.Add(hitEntityComp.DNA);
}
}
}
private void 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);
}
}
private void OnAfterInteract(EntityUid uid, CleansForensicsComponent component, AfterInteractEvent args)
{
if (args.Handled)
return;
if (!TryComp<ForensicsComponent>(args.Target, out var forensicsComp))
return;
if((forensicsComp.DNAs.Count > 0 && forensicsComp.CanDnaBeCleaned) || (forensicsComp.Fingerprints.Count + forensicsComp.Fibers.Count > 0))
{
var doAfterArgs = new DoAfterArgs(EntityManager, args.User, component.CleanDelay, new CleanForensicsDoAfterEvent(), uid, target: args.Target, used: args.Used)
{
BreakOnHandChange = true,
NeedHand = true,
BreakOnDamage = true,
BreakOnTargetMove = true,
MovementThreshold = 0.01f,
DistanceThreshold = forensicsComp.CleanDistance,
};
_doAfterSystem.TryStartDoAfter(doAfterArgs);
_popupSystem.PopupEntity(Loc.GetString("forensics-cleaning", ("target", args.Target)), args.User, args.User);
args.Handled = true;
}
}
private void OnCleanForensicsDoAfter(EntityUid uid, ForensicsComponent component, CleanForensicsDoAfterEvent args)
{
if (args.Handled || args.Cancelled || args.Args.Target == null)
return;
if (!TryComp<ForensicsComponent>(args.Target, out var targetComp))
return;
targetComp.Fibers = new();
targetComp.Fingerprints = new();
if (targetComp.CanDnaBeCleaned)
targetComp.DNAs = new();
// leave behind evidence it was cleaned
if (TryComp<FiberComponent>(args.Used, out var fiber))
targetComp.Fibers.Add(string.IsNullOrEmpty(fiber.FiberColor) ? Loc.GetString("forensic-fibers", ("material", fiber.FiberMaterial)) : Loc.GetString("forensic-fibers-colored", ("color", fiber.FiberColor), ("material", fiber.FiberMaterial)));
if (TryComp<ResidueComponent>(args.Used, out var residue))
targetComp.Residues.Add(string.IsNullOrEmpty(residue.ResidueColor) ? Loc.GetString("forensic-residue", ("adjective", residue.ResidueAdjective)) : Loc.GetString("forensic-residue-colored", ("color", residue.ResidueColor), ("adjective", residue.ResidueAdjective)));
}
public string GenerateFingerprint(int length = 16)
{
var fingerprint = new byte[Math.Clamp(length, 0, 255)];
_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))
{
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);
}
if (HasComp<FingerprintMaskComponent>(gloves))
return;
}
if (TryComp<FingerprintComponent>(user, out var fingerprint))
component.Fingerprints.Add(fingerprint.Fingerprint ?? "");
}
private void OnTransferDnaEvent(EntityUid uid, DnaComponent component, ref TransferDnaEvent args)
{
var recipientComp = EnsureComp<ForensicsComponent>(args.Recipient);
recipientComp.DNAs.Add(component.DNA);
recipientComp.CanDnaBeCleaned = args.CanDnaBeCleaned;
}
#region Public API
/// <summary>
/// Transfer DNA from one entity onto the forensics of another
/// </summary>
/// <param name="recipient">The entity receiving the DNA</param>
/// <param name="donor">The entity applying its DNA</param>
/// <param name="canDnaBeCleaned">If this DNA be cleaned off of the recipient. e.g. cleaning a knife vs cleaning a puddle of blood</param>
public void TransferDna(EntityUid recipient, EntityUid donor, bool canDnaBeCleaned = true)
{
if (TryComp<DnaComponent>(donor, out var donorComp))
{
EnsureComp<ForensicsComponent>(recipient, out var recipientComp);
recipientComp.DNAs.Add(donorComp.DNA);
recipientComp.CanDnaBeCleaned = canDnaBeCleaned;
}
}
#endregion
}
}