ForensicsSystem.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. using Content.Server.Body.Components;
  2. using Content.Server.Body.Systems;
  3. using Content.Server.DoAfter;
  4. using Content.Server.Fluids.EntitySystems;
  5. using Content.Server.Forensics.Components;
  6. using Content.Server.Popups;
  7. using Content.Shared.Chemistry.EntitySystems;
  8. using Content.Shared.Popups;
  9. using Content.Shared.Chemistry.Components;
  10. using Content.Shared.Chemistry.Reagent;
  11. using Content.Shared.Chemistry.Components.SolutionManager;
  12. using Content.Shared.DoAfter;
  13. using Content.Shared.Forensics;
  14. using Content.Shared.Forensics.Components;
  15. using Content.Shared.Interaction;
  16. using Content.Shared.Interaction.Events;
  17. using Content.Shared.Inventory;
  18. using Content.Shared.Weapons.Melee.Events;
  19. using Robust.Shared.Random;
  20. using Content.Shared.Verbs;
  21. using Robust.Shared.Utility;
  22. namespace Content.Server.Forensics
  23. {
  24. public sealed class ForensicsSystem : EntitySystem
  25. {
  26. [Dependency] private readonly IRobustRandom _random = default!;
  27. [Dependency] private readonly InventorySystem _inventory = default!;
  28. [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
  29. [Dependency] private readonly PopupSystem _popupSystem = default!;
  30. [Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
  31. public override void Initialize()
  32. {
  33. SubscribeLocalEvent<FingerprintComponent, ContactInteractionEvent>(OnInteract);
  34. SubscribeLocalEvent<FingerprintComponent, MapInitEvent>(OnFingerprintInit, after: new[] { typeof(BloodstreamSystem) });
  35. // 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
  36. SubscribeLocalEvent<DnaComponent, MapInitEvent>(OnDNAInit, after: new[] { typeof(BloodstreamSystem) });
  37. SubscribeLocalEvent<ForensicsComponent, BeingGibbedEvent>(OnBeingGibbed);
  38. SubscribeLocalEvent<ForensicsComponent, MeleeHitEvent>(OnMeleeHit);
  39. SubscribeLocalEvent<ForensicsComponent, GotRehydratedEvent>(OnRehydrated);
  40. SubscribeLocalEvent<CleansForensicsComponent, AfterInteractEvent>(OnAfterInteract, after: new[] { typeof(AbsorbentSystem) });
  41. SubscribeLocalEvent<ForensicsComponent, CleanForensicsDoAfterEvent>(OnCleanForensicsDoAfter);
  42. SubscribeLocalEvent<DnaComponent, TransferDnaEvent>(OnTransferDnaEvent);
  43. SubscribeLocalEvent<DnaSubstanceTraceComponent, SolutionContainerChangedEvent>(OnSolutionChanged);
  44. SubscribeLocalEvent<CleansForensicsComponent, GetVerbsEvent<UtilityVerb>>(OnUtilityVerb);
  45. }
  46. private void OnSolutionChanged(Entity<DnaSubstanceTraceComponent> ent, ref SolutionContainerChangedEvent ev)
  47. {
  48. var soln = GetSolutionsDNA(ev.Solution);
  49. if (soln.Count > 0)
  50. {
  51. var comp = EnsureComp<ForensicsComponent>(ent.Owner);
  52. foreach (string dna in soln)
  53. {
  54. comp.DNAs.Add(dna);
  55. }
  56. }
  57. }
  58. private void OnInteract(EntityUid uid, FingerprintComponent component, ContactInteractionEvent args)
  59. {
  60. ApplyEvidence(uid, args.Other);
  61. }
  62. private void OnFingerprintInit(Entity<FingerprintComponent> ent, ref MapInitEvent args)
  63. {
  64. if (ent.Comp.Fingerprint == null)
  65. RandomizeFingerprint((ent.Owner, ent.Comp));
  66. }
  67. private void OnDNAInit(Entity<DnaComponent> ent, ref MapInitEvent args)
  68. {
  69. if (ent.Comp.DNA == null)
  70. RandomizeDNA((ent.Owner, ent.Comp));
  71. else
  72. {
  73. // If set manually (for example by cloning) we also need to inform the bloodstream of the correct DNA string so it can be updated
  74. var ev = new GenerateDnaEvent { Owner = ent.Owner, DNA = ent.Comp.DNA };
  75. RaiseLocalEvent(ent.Owner, ref ev);
  76. }
  77. }
  78. private void OnBeingGibbed(EntityUid uid, ForensicsComponent component, BeingGibbedEvent args)
  79. {
  80. string dna = Loc.GetString("forensics-dna-unknown");
  81. if (TryComp(uid, out DnaComponent? dnaComp) && dnaComp.DNA != null)
  82. dna = dnaComp.DNA;
  83. foreach (EntityUid part in args.GibbedParts)
  84. {
  85. var partComp = EnsureComp<ForensicsComponent>(part);
  86. partComp.DNAs.Add(dna);
  87. partComp.CanDnaBeCleaned = false;
  88. }
  89. }
  90. private void OnMeleeHit(EntityUid uid, ForensicsComponent component, MeleeHitEvent args)
  91. {
  92. if ((args.BaseDamage.DamageDict.TryGetValue("Blunt", out var bluntDamage) && bluntDamage.Value > 0) ||
  93. (args.BaseDamage.DamageDict.TryGetValue("Slash", out var slashDamage) && slashDamage.Value > 0) ||
  94. (args.BaseDamage.DamageDict.TryGetValue("Piercing", out var pierceDamage) && pierceDamage.Value > 0))
  95. {
  96. foreach (EntityUid hitEntity in args.HitEntities)
  97. {
  98. if (TryComp<DnaComponent>(hitEntity, out var hitEntityComp) && hitEntityComp.DNA != null)
  99. component.DNAs.Add(hitEntityComp.DNA);
  100. }
  101. }
  102. }
  103. private void OnRehydrated(Entity<ForensicsComponent> ent, ref GotRehydratedEvent args)
  104. {
  105. CopyForensicsFrom(ent.Comp, args.Target);
  106. }
  107. /// <summary>
  108. /// Copy forensic information from a source entity to a destination.
  109. /// Existing forensic information on the target is still kept.
  110. /// </summary>
  111. public void CopyForensicsFrom(ForensicsComponent src, EntityUid target)
  112. {
  113. var dest = EnsureComp<ForensicsComponent>(target);
  114. foreach (var dna in src.DNAs)
  115. {
  116. dest.DNAs.Add(dna);
  117. }
  118. foreach (var fiber in src.Fibers)
  119. {
  120. dest.Fibers.Add(fiber);
  121. }
  122. foreach (var print in src.Fingerprints)
  123. {
  124. dest.Fingerprints.Add(print);
  125. }
  126. }
  127. public List<string> GetSolutionsDNA(EntityUid uid)
  128. {
  129. List<string> list = new();
  130. if (TryComp<SolutionContainerManagerComponent>(uid, out var comp))
  131. {
  132. foreach (var (_, soln) in _solutionContainerSystem.EnumerateSolutions((uid, comp)))
  133. {
  134. list.AddRange(GetSolutionsDNA(soln.Comp.Solution));
  135. }
  136. }
  137. return list;
  138. }
  139. public List<string> GetSolutionsDNA(Solution soln)
  140. {
  141. List<string> list = new();
  142. foreach (var reagent in soln.Contents)
  143. {
  144. foreach (var data in reagent.Reagent.EnsureReagentData())
  145. {
  146. if (data is DnaData)
  147. {
  148. list.Add(((DnaData) data).DNA);
  149. }
  150. }
  151. }
  152. return list;
  153. }
  154. private void OnAfterInteract(Entity<CleansForensicsComponent> cleanForensicsEntity, ref AfterInteractEvent args)
  155. {
  156. if (args.Handled || !args.CanReach || args.Target == null)
  157. return;
  158. args.Handled = TryStartCleaning(cleanForensicsEntity, args.User, args.Target.Value);
  159. }
  160. private void OnUtilityVerb(Entity<CleansForensicsComponent> entity, ref GetVerbsEvent<UtilityVerb> args)
  161. {
  162. if (!args.CanInteract || !args.CanAccess)
  163. return;
  164. // These need to be set outside for the anonymous method!
  165. var user = args.User;
  166. var target = args.Target;
  167. var verb = new UtilityVerb()
  168. {
  169. Act = () => TryStartCleaning(entity, user, target),
  170. Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/bubbles.svg.192dpi.png")),
  171. Text = Loc.GetString(Loc.GetString("forensics-verb-text")),
  172. Message = Loc.GetString(Loc.GetString("forensics-verb-message")),
  173. // This is important because if its true using the cleaning device will count as touching the object.
  174. DoContactInteraction = false
  175. };
  176. args.Verbs.Add(verb);
  177. }
  178. /// <summary>
  179. /// Attempts to clean the given item with the given CleansForensics entity.
  180. /// </summary>
  181. /// <param name="cleanForensicsEntity">The entity that is being used to clean the target.</param>
  182. /// <param name="user">The user that is using the cleanForensicsEntity.</param>
  183. /// <param name="target">The target of the forensics clean.</param>
  184. /// <returns>True if the target can be cleaned and has some sort of DNA or fingerprints / fibers and false otherwise.</returns>
  185. public bool TryStartCleaning(Entity<CleansForensicsComponent> cleanForensicsEntity, EntityUid user, EntityUid target)
  186. {
  187. if (!TryComp<ForensicsComponent>(target, out var forensicsComp))
  188. {
  189. _popupSystem.PopupEntity(Loc.GetString("forensics-cleaning-cannot-clean", ("target", target)), user, user, PopupType.MediumCaution);
  190. return false;
  191. }
  192. var totalPrintsAndFibers = forensicsComp.Fingerprints.Count + forensicsComp.Fibers.Count;
  193. var hasRemovableDNA = forensicsComp.DNAs.Count > 0 && forensicsComp.CanDnaBeCleaned;
  194. if (hasRemovableDNA || totalPrintsAndFibers > 0)
  195. {
  196. var cleanDelay = cleanForensicsEntity.Comp.CleanDelay;
  197. var doAfterArgs = new DoAfterArgs(EntityManager, user, cleanDelay, new CleanForensicsDoAfterEvent(), cleanForensicsEntity, target: target, used: cleanForensicsEntity)
  198. {
  199. NeedHand = true,
  200. BreakOnDamage = true,
  201. BreakOnMove = true,
  202. MovementThreshold = 0.01f,
  203. DistanceThreshold = forensicsComp.CleanDistance,
  204. };
  205. _doAfterSystem.TryStartDoAfter(doAfterArgs);
  206. _popupSystem.PopupEntity(Loc.GetString("forensics-cleaning", ("target", target)), user, user);
  207. return true;
  208. }
  209. else
  210. {
  211. _popupSystem.PopupEntity(Loc.GetString("forensics-cleaning-cannot-clean", ("target", target)), user, user, PopupType.MediumCaution);
  212. return false;
  213. }
  214. }
  215. private void OnCleanForensicsDoAfter(EntityUid uid, ForensicsComponent component, CleanForensicsDoAfterEvent args)
  216. {
  217. if (args.Handled || args.Cancelled || args.Args.Target == null)
  218. return;
  219. if (!TryComp<ForensicsComponent>(args.Target, out var targetComp))
  220. return;
  221. targetComp.Fibers = new();
  222. targetComp.Fingerprints = new();
  223. if (targetComp.CanDnaBeCleaned)
  224. targetComp.DNAs = new();
  225. // leave behind evidence it was cleaned
  226. if (TryComp<FiberComponent>(args.Used, out var fiber))
  227. 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)));
  228. if (TryComp<ResidueComponent>(args.Used, out var residue))
  229. 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)));
  230. }
  231. public string GenerateFingerprint()
  232. {
  233. var fingerprint = new byte[16];
  234. _random.NextBytes(fingerprint);
  235. return Convert.ToHexString(fingerprint);
  236. }
  237. public string GenerateDNA()
  238. {
  239. var letters = new[] { "A", "C", "G", "T" };
  240. var DNA = string.Empty;
  241. for (var i = 0; i < 16; i++)
  242. {
  243. DNA += letters[_random.Next(letters.Length)];
  244. }
  245. return DNA;
  246. }
  247. private void ApplyEvidence(EntityUid user, EntityUid target)
  248. {
  249. if (HasComp<IgnoresFingerprintsComponent>(target))
  250. return;
  251. var component = EnsureComp<ForensicsComponent>(target);
  252. if (_inventory.TryGetSlotEntity(user, "gloves", out var gloves))
  253. {
  254. if (TryComp<FiberComponent>(gloves, out var fiber) && !string.IsNullOrEmpty(fiber.FiberMaterial))
  255. 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)));
  256. if (HasComp<FingerprintMaskComponent>(gloves))
  257. return;
  258. }
  259. if (TryComp<FingerprintComponent>(user, out var fingerprint))
  260. component.Fingerprints.Add(fingerprint.Fingerprint ?? "");
  261. }
  262. private void OnTransferDnaEvent(EntityUid uid, DnaComponent component, ref TransferDnaEvent args)
  263. {
  264. if (component.DNA == null)
  265. return;
  266. var recipientComp = EnsureComp<ForensicsComponent>(args.Recipient);
  267. recipientComp.DNAs.Add(component.DNA);
  268. recipientComp.CanDnaBeCleaned = args.CanDnaBeCleaned;
  269. }
  270. #region Public API
  271. /// <summary>
  272. /// Give the entity a new, random DNA string and call an event to notify other systems like the bloodstream that it has been changed.
  273. /// Does nothing if it does not have the DnaComponent.
  274. /// </summary>
  275. public void RandomizeDNA(Entity<DnaComponent?> ent)
  276. {
  277. if (!Resolve(ent, ref ent.Comp, false))
  278. return;
  279. ent.Comp.DNA = GenerateDNA();
  280. Dirty(ent);
  281. var ev = new GenerateDnaEvent { Owner = ent.Owner, DNA = ent.Comp.DNA };
  282. RaiseLocalEvent(ent.Owner, ref ev);
  283. }
  284. /// <summary>
  285. /// Give the entity a new, random fingerprint string.
  286. /// Does nothing if it does not have the FingerprintComponent.
  287. /// </summary>
  288. public void RandomizeFingerprint(Entity<FingerprintComponent?> ent)
  289. {
  290. if (!Resolve(ent, ref ent.Comp, false))
  291. return;
  292. ent.Comp.Fingerprint = GenerateFingerprint();
  293. Dirty(ent);
  294. }
  295. /// <summary>
  296. /// Transfer DNA from one entity onto the forensics of another
  297. /// </summary>
  298. /// <param name="recipient">The entity receiving the DNA</param>
  299. /// <param name="donor">The entity applying its DNA</param>
  300. /// <param name="canDnaBeCleaned">If this DNA be cleaned off of the recipient. e.g. cleaning a knife vs cleaning a puddle of blood</param>
  301. public void TransferDna(EntityUid recipient, EntityUid donor, bool canDnaBeCleaned = true)
  302. {
  303. if (TryComp<DnaComponent>(donor, out var donorComp) && donorComp.DNA != null)
  304. {
  305. EnsureComp<ForensicsComponent>(recipient, out var recipientComp);
  306. recipientComp.DNAs.Add(donorComp.DNA);
  307. recipientComp.CanDnaBeCleaned = canDnaBeCleaned;
  308. }
  309. }
  310. #endregion
  311. }
  312. }