diff --git a/code/__DEFINES/blood.dm b/code/__DEFINES/blood.dm
index b3b8c0d1f3d..320c3ded51a 100644
--- a/code/__DEFINES/blood.dm
+++ b/code/__DEFINES/blood.dm
@@ -1,6 +1,3 @@
-/// Checks if an object is covered in blood
-#define HAS_BLOOD_DNA(thing) (length(thing.GetComponent(/datum/component/forensics)?.blood_DNA))
-
//Bloody shoes/footprints
/// Minimum alpha of footprints
#define BLOODY_FOOTPRINT_BASE_ALPHA 20
@@ -20,5 +17,5 @@
#define BLOOD_STATE_XENO "xeno"
/// Black robot oil
#define BLOOD_STATE_OIL "oil"
-/// No blood is present
+/// No blood is present
#define BLOOD_STATE_NOT_BLOODY "no blood whatsoever"
diff --git a/code/__HELPERS/forensics.dm b/code/__HELPERS/forensics.dm
new file mode 100644
index 00000000000..2d43a1ad61b
--- /dev/null
+++ b/code/__HELPERS/forensics.dm
@@ -0,0 +1,10 @@
+/// Returns the fingerprints on this atom
+#define GET_ATOM_FINGERPRINTS(atom) atom.forensics?.fingerprints
+/// Returns the hidden prints on this atom
+#define GET_ATOM_HIDDENPRINTS(atom) atom.forensics?.hiddenprints
+/// Returns the blood dna on this atom
+#define GET_ATOM_BLOOD_DNA(atom) atom.forensics?.blood_DNA
+/// Returns the fibers on this atom
+#define GET_ATOM_FIBRES(atom) atom.forensics?.fibers
+/// Returns the number of unique blood dna sources on this atom
+#define GET_ATOM_BLOOD_DNA_LENGTH(atom) (isnull(atom.forensics) ? 0 : length(atom.forensics.blood_DNA))
diff --git a/code/datums/brain_damage/phobia.dm b/code/datums/brain_damage/phobia.dm
index ac7e9f363ae..7e8f0d1ab99 100644
--- a/code/datums/brain_damage/phobia.dm
+++ b/code/datums/brain_damage/phobia.dm
@@ -48,13 +48,13 @@
var/list/seen_atoms = view(7, owner)
if(LAZYLEN(trigger_objs))
for(var/obj/O in seen_atoms)
- if(is_type_in_typecache(O, trigger_objs) || (phobia_type == "blood" && HAS_BLOOD_DNA(O)))
+ if(is_type_in_typecache(O, trigger_objs) || (phobia_type == "blood" && GET_ATOM_BLOOD_DNA_LENGTH(O)))
freak_out(O)
return
for(var/mob/living/carbon/human/HU in seen_atoms) //check equipment for trigger items
for(var/X in HU.get_all_worn_items() | HU.held_items)
var/obj/I = X
- if(!QDELETED(I) && (is_type_in_typecache(I, trigger_objs) || (phobia_type == "blood" && HAS_BLOOD_DNA(I))))
+ if(!QDELETED(I) && (is_type_in_typecache(I, trigger_objs) || (phobia_type == "blood" && GET_ATOM_BLOOD_DNA_LENGTH(I))))
freak_out(I)
return
diff --git a/code/datums/components/bloodysoles.dm b/code/datums/components/bloodysoles.dm
index 1c8969cfec1..9268385775f 100644
--- a/code/datums/components/bloodysoles.dm
+++ b/code/datums/components/bloodysoles.dm
@@ -71,7 +71,7 @@
if(HAS_TRAIT(parent_atom, TRAIT_LIGHT_STEP)) //the character is agile enough to don't mess their clothing and hands just from one blood splatter at floor
return TRUE
- parent_atom.add_blood_DNA(pool.return_blood_DNA())
+ parent_atom.add_blood_DNA(GET_ATOM_BLOOD_DNA(pool))
update_icon()
/**
@@ -156,7 +156,7 @@
oldLocFP.exited_dirs |= wielder.dir
add_parent_to_footprint(oldLocFP)
oldLocFP.bloodiness = half_our_blood
- oldLocFP.add_blood_DNA(parent_atom.return_blood_DNA())
+ oldLocFP.add_blood_DNA(GET_ATOM_BLOOD_DNA(parent_atom))
oldLocFP.update_appearance()
half_our_blood = bloody_shoes[last_blood_state] / 2
@@ -176,7 +176,7 @@
FP.entered_dirs |= wielder.dir
add_parent_to_footprint(FP)
FP.bloodiness = half_our_blood
- FP.add_blood_DNA(parent_atom.return_blood_DNA())
+ FP.add_blood_DNA(GET_ATOM_BLOOD_DNA(parent_atom))
FP.update_appearance()
diff --git a/code/datums/components/forensics.dm b/code/datums/components/forensics.dm
deleted file mode 100644
index 26b3d9f5c14..00000000000
--- a/code/datums/components/forensics.dm
+++ /dev/null
@@ -1,188 +0,0 @@
-/datum/component/forensics
- dupe_mode = COMPONENT_DUPE_UNIQUE
- can_transfer = TRUE
- var/list/fingerprints //assoc print = print
- var/list/hiddenprints //assoc ckey = realname/gloves/ckey
- var/list/blood_DNA //assoc dna = bloodtype
- var/list/fibers //assoc print = print
-
-/datum/component/forensics/InheritComponent(datum/component/forensics/F, original) //Use of | and |= being different here is INTENTIONAL.
- fingerprints = LAZY_LISTS_OR(fingerprints, F.fingerprints)
- hiddenprints = LAZY_LISTS_OR(hiddenprints, F.hiddenprints)
- blood_DNA = LAZY_LISTS_OR(blood_DNA, F.blood_DNA)
- fibers = LAZY_LISTS_OR(fibers, F.fibers)
- check_blood()
- return ..()
-
-/datum/component/forensics/Initialize(new_fingerprints, new_hiddenprints, new_blood_DNA, new_fibers)
- if(!isatom(parent))
- return COMPONENT_INCOMPATIBLE
- fingerprints = new_fingerprints
- hiddenprints = new_hiddenprints
- blood_DNA = new_blood_DNA
- fibers = new_fibers
- check_blood()
-
-/datum/component/forensics/RegisterWithParent()
- check_blood()
- RegisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean_act)
-
-/datum/component/forensics/UnregisterFromParent()
- UnregisterSignal(parent, list(COMSIG_COMPONENT_CLEAN_ACT))
-
-/datum/component/forensics/PostTransfer()
- if(!isatom(parent))
- return COMPONENT_INCOMPATIBLE
-
-/datum/component/forensics/proc/wipe_fingerprints()
- fingerprints = null
- return TRUE
-
-/datum/component/forensics/proc/wipe_hiddenprints()
- return //no.
-
-/datum/component/forensics/proc/wipe_blood_DNA()
- blood_DNA = null
- return TRUE
-
-/datum/component/forensics/proc/wipe_fibers()
- fibers = null
- return TRUE
-
-/datum/component/forensics/proc/clean_act(datum/source, clean_types)
- SIGNAL_HANDLER
-
- . = NONE
- if(clean_types & CLEAN_TYPE_FINGERPRINTS)
- wipe_fingerprints()
- . = COMPONENT_CLEANED
- if(clean_types & CLEAN_TYPE_BLOOD)
- wipe_blood_DNA()
- . = COMPONENT_CLEANED
- if(clean_types & CLEAN_TYPE_FIBERS)
- wipe_fibers()
- . = COMPONENT_CLEANED
-
-/datum/component/forensics/proc/add_fingerprint_list(list/_fingerprints) //list(text)
- if(!length(_fingerprints))
- return
- LAZYINITLIST(fingerprints)
- for(var/i in _fingerprints) //We use an associative list, make sure we don't just merge a non-associative list into ours.
- fingerprints[i] = i
- return TRUE
-
-/datum/component/forensics/proc/add_fingerprint(mob/living/M, ignoregloves = FALSE)
- if(!isliving(M))
- if(!iscameramob(M))
- return
- if(isaicamera(M))
- var/mob/camera/ai_eye/ai_camera = M
- if(!ai_camera.ai)
- return
- M = ai_camera.ai
- add_hiddenprint(M)
- if(ishuman(M))
- var/mob/living/carbon/human/H = M
- add_fibers(H)
- var/obj/item/gloves = H.gloves
- if(gloves) //Check if the gloves (if any) hide fingerprints
- if(!(gloves.body_parts_covered & HANDS) || HAS_TRAIT(gloves, TRAIT_FINGERPRINT_PASSTHROUGH) || HAS_TRAIT(H, TRAIT_FINGERPRINT_PASSTHROUGH))
- ignoregloves = TRUE
- if(!ignoregloves)
- H.gloves.add_fingerprint(H, TRUE) //ignoregloves = 1 to avoid infinite loop.
- return
- var/full_print = md5(H.dna.unique_identity)
- LAZYSET(fingerprints, full_print, full_print)
- return TRUE
-
-/datum/component/forensics/proc/add_fiber_list(list/_fibertext) //list(text)
- if(!length(_fibertext))
- return
- LAZYINITLIST(fibers)
- for(var/i in _fibertext) //We use an associative list, make sure we don't just merge a non-associative list into ours.
- fibers[i] = i
- return TRUE
-
-/datum/component/forensics/proc/add_fibers(mob/living/carbon/human/M)
- var/fibertext
- var/item_multiplier = isitem(src)?1.2:1
- if(M.wear_suit)
- fibertext = "Material from \a [M.wear_suit]."
- if(prob(10*item_multiplier) && !LAZYACCESS(fibers, fibertext))
- LAZYSET(fibers, fibertext, fibertext)
- if(!(M.wear_suit.body_parts_covered & CHEST))
- if(M.w_uniform)
- fibertext = "Fibers from \a [M.w_uniform]."
- if(prob(12*item_multiplier) && !LAZYACCESS(fibers, fibertext)) //Wearing a suit means less of the uniform exposed.
- LAZYSET(fibers, fibertext, fibertext)
- if(!(M.wear_suit.body_parts_covered & HANDS))
- if(M.gloves)
- fibertext = "Material from a pair of [M.gloves.name]."
- if(prob(20*item_multiplier) && !LAZYACCESS(fibers, fibertext))
- LAZYSET(fibers, fibertext, fibertext)
- else if(M.w_uniform)
- fibertext = "Fibers from \a [M.w_uniform]."
- if(prob(15*item_multiplier) && !LAZYACCESS(fibers, fibertext))
- // "Added fibertext: [fibertext]"
- LAZYSET(fibers, fibertext, fibertext)
- if(M.gloves)
- fibertext = "Material from a pair of [M.gloves.name]."
- if(prob(20*item_multiplier) && !LAZYACCESS(fibers, fibertext))
- LAZYSET(fibers, fibertext, fibertext)
- else if(M.gloves)
- fibertext = "Material from a pair of [M.gloves.name]."
- if(prob(20*item_multiplier) && !LAZYACCESS(fibers, fibertext))
- LAZYSET(fibers, fibertext, fibertext)
- return TRUE
-
-/datum/component/forensics/proc/add_hiddenprint_list(list/_hiddenprints) //list(ckey = text)
- if(!length(_hiddenprints))
- return
- LAZYINITLIST(hiddenprints)
- for(var/i in _hiddenprints) //We use an associative list, make sure we don't just merge a non-associative list into ours.
- hiddenprints[i] = _hiddenprints[i]
- return TRUE
-
-/datum/component/forensics/proc/add_hiddenprint(mob/M)
- if(!isliving(M))
- if(!iscameramob(M))
- return
- if(isaicamera(M))
- var/mob/camera/ai_eye/ai_camera = M
- if(!ai_camera.ai)
- return
- M = ai_camera.ai
- if(!M.key)
- return
- var/hasgloves = ""
- if(ishuman(M))
- var/mob/living/carbon/human/H = M
- if(H.gloves)
- hasgloves = "(gloves)"
- var/current_time = time_stamp()
- if(!LAZYACCESS(hiddenprints, M.key))
- LAZYSET(hiddenprints, M.key, "First: \[[current_time]\] \"[M.real_name]\"[hasgloves]. Ckey: [M.ckey]")
- else
- var/laststamppos = findtext(LAZYACCESS(hiddenprints, M.key), "\nLast: ")
- if(laststamppos)
- LAZYSET(hiddenprints, M.key, copytext(hiddenprints[M.key], 1, laststamppos))
- hiddenprints[M.key] += "\nLast: \[[current_time]\] \"[M.real_name]\"[hasgloves]. Ckey: [M.ckey]" //made sure to be existing by if(!LAZYACCESS);else
- var/atom/A = parent
- A.fingerprintslast = M.ckey
- return TRUE
-
-/datum/component/forensics/proc/add_blood_DNA(list/dna) //list(dna_enzymes = type)
- if(!length(dna))
- return
- LAZYINITLIST(blood_DNA)
- for(var/i in dna)
- blood_DNA[i] = dna[i]
- check_blood()
- return TRUE
-
-/datum/component/forensics/proc/check_blood()
- if(!isitem(parent))
- return
- if(!length(blood_DNA))
- return
- parent.AddElement(/datum/element/decal/blood)
diff --git a/code/datums/mutations/actions.dm b/code/datums/mutations/actions.dm
index d1119a4fa44..57b40b12476 100644
--- a/code/datums/mutations/actions.dm
+++ b/code/datums/mutations/actions.dm
@@ -53,7 +53,7 @@
if(sniffed)
var/old_target = tracking_target
possible = list()
- var/list/prints = sniffed.return_fingerprints()
+ var/list/prints = GET_ATOM_FINGERPRINTS(sniffed)
if(prints)
for(var/mob/living/carbon/C in GLOB.carbon_list)
if(prints[md5(C.dna.unique_identity)])
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 8627ccc53c5..b4ddb1d4709 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -159,6 +159,8 @@
var/damage_deflection = 0
var/resistance_flags = NONE // INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ON_FIRE | UNACIDABLE | ACID_PROOF
+ /// forensics datum, contains fingerprints, fibres, blood_dna and hiddenprints on this atom
+ var/datum/forensics/forensics
/**
* Called when an atom is created in byond (built in engine proc)
@@ -309,6 +311,9 @@
if(reagents)
QDEL_NULL(reagents)
+ if(forensics)
+ QDEL_NULL(forensics)
+
orbiters = null // The component is attached to us normaly and will be deleted elsewhere
LAZYCLEARLIST(overlays)
@@ -900,9 +905,9 @@
var/new_blood_dna = injected_mob.get_blood_dna_list()
if(!new_blood_dna)
return FALSE
- var/old_length = blood_DNA_length()
+ var/old_length = GET_ATOM_BLOOD_DNA_LENGTH(src)
add_blood_DNA(new_blood_dna)
- if(blood_DNA_length() == old_length)
+ if(GET_ATOM_BLOOD_DNA_LENGTH(src) == old_length)
return FALSE
return TRUE
diff --git a/code/game/objects/effects/decals/cleanable/humans.dm b/code/game/objects/effects/decals/cleanable/humans.dm
index a35d8cc642d..c0701ae6b68 100644
--- a/code/game/objects/effects/decals/cleanable/humans.dm
+++ b/code/game/objects/effects/decals/cleanable/humans.dm
@@ -52,7 +52,7 @@
return TRUE
/obj/effect/decal/cleanable/blood/replace_decal(obj/effect/decal/cleanable/blood/C)
- C.add_blood_DNA(return_blood_DNA())
+ C.add_blood_DNA(GET_ATOM_BLOOD_DNA(src))
if (bloodiness)
C.bloodiness = min((C.bloodiness + bloodiness), BLOOD_AMOUNT_PER_DECAL)
return ..()
diff --git a/code/game/objects/items/stacks/stack.dm b/code/game/objects/items/stacks/stack.dm
index 743f290782b..c98ab48efcf 100644
--- a/code/game/objects/items/stacks/stack.dm
+++ b/code/game/objects/items/stacks/stack.dm
@@ -687,9 +687,9 @@
. = ..()
/obj/item/stack/proc/copy_evidences(obj/item/stack/from)
- add_blood_DNA(from.return_blood_DNA())
- add_fingerprint_list(from.return_fingerprints())
- add_hiddenprint_list(from.return_hiddenprints())
+ add_blood_DNA(GET_ATOM_BLOOD_DNA(from))
+ add_fingerprint_list(GET_ATOM_FINGERPRINTS(from))
+ add_hiddenprint_list(GET_ATOM_HIDDENPRINTS(from))
fingerprintslast = from.fingerprintslast
//TODO bloody overlay
diff --git a/code/modules/admin/verbs/hiddenprints.dm b/code/modules/admin/verbs/hiddenprints.dm
index 2d87e404498..500792cc42a 100644
--- a/code/modules/admin/verbs/hiddenprints.dm
+++ b/code/modules/admin/verbs/hiddenprints.dm
@@ -3,7 +3,7 @@
return
var/interface = "A log of every player who has touched [victim], sorted by last touch.
"
- var/victim_hiddenprints = victim.return_hiddenprints()
+ var/victim_hiddenprints = GET_ATOM_HIDDENPRINTS(victim)
if(!islist(victim_hiddenprints))
victim_hiddenprints = list()
diff --git a/code/modules/antagonists/heretic/heretic_knowledge.dm b/code/modules/antagonists/heretic/heretic_knowledge.dm
index c3e9f086aa7..8f0ded9411a 100644
--- a/code/modules/antagonists/heretic/heretic_knowledge.dm
+++ b/code/modules/antagonists/heretic/heretic_knowledge.dm
@@ -373,7 +373,7 @@
/datum/heretic_knowledge/curse/recipe_snowflake_check(mob/living/user, list/atoms, list/selected_atoms, turf/loc)
fingerprints = list()
for(var/atom/requirements as anything in atoms)
- fingerprints[requirements.return_fingerprints()] = 1
+ fingerprints[GET_ATOM_FINGERPRINTS(requirements)] = 1
list_clear_nulls(fingerprints)
// No fingerprints? No ritual
diff --git a/code/modules/clothing/gloves/_gloves.dm b/code/modules/clothing/gloves/_gloves.dm
index ab008846a5c..f1111075c2b 100644
--- a/code/modules/clothing/gloves/_gloves.dm
+++ b/code/modules/clothing/gloves/_gloves.dm
@@ -32,7 +32,7 @@
if(damaged_clothes)
. += mutable_appearance('icons/effects/item_damage.dmi', "damagedgloves")
- if(HAS_BLOOD_DNA(src))
+ if(GET_ATOM_BLOOD_DNA_LENGTH(src))
. += mutable_appearance('icons/effects/blood.dmi', "bloodyhands")
/obj/item/clothing/gloves/update_clothes_damaged_state(damaged_state = CLOTHING_DAMAGED)
diff --git a/code/modules/clothing/head/_head.dm b/code/modules/clothing/head/_head.dm
index 83585452208..b6f8596f05d 100644
--- a/code/modules/clothing/head/_head.dm
+++ b/code/modules/clothing/head/_head.dm
@@ -65,7 +65,7 @@
if(damaged_clothes)
. += mutable_appearance('icons/effects/item_damage.dmi', "damagedhelmet")
- if(HAS_BLOOD_DNA(src))
+ if(GET_ATOM_BLOOD_DNA_LENGTH(src))
if(clothing_flags & LARGE_WORN_ICON)
. += mutable_appearance('icons/effects/64x64.dmi', "helmetblood_large")
else
diff --git a/code/modules/clothing/masks/_masks.dm b/code/modules/clothing/masks/_masks.dm
index d9c7dc8bae6..a0007239abc 100644
--- a/code/modules/clothing/masks/_masks.dm
+++ b/code/modules/clothing/masks/_masks.dm
@@ -50,7 +50,7 @@
if(body_parts_covered & HEAD)
if(damaged_clothes)
. += mutable_appearance('icons/effects/item_damage.dmi', "damagedmask")
- if(HAS_BLOOD_DNA(src))
+ if(GET_ATOM_BLOOD_DNA_LENGTH(src))
. += mutable_appearance('icons/effects/blood.dmi', "maskblood")
/obj/item/clothing/mask/update_clothes_damaged_state(damaged_state = CLOTHING_DAMAGED)
diff --git a/code/modules/clothing/neck/_neck.dm b/code/modules/clothing/neck/_neck.dm
index 8833b0cdeb8..4a6f898d43b 100644
--- a/code/modules/clothing/neck/_neck.dm
+++ b/code/modules/clothing/neck/_neck.dm
@@ -14,7 +14,7 @@
if(body_parts_covered & HEAD)
if(damaged_clothes)
. += mutable_appearance('icons/effects/item_damage.dmi', "damagedmask")
- if(HAS_BLOOD_DNA(src))
+ if(GET_ATOM_BLOOD_DNA_LENGTH(src))
. += mutable_appearance('icons/effects/blood.dmi', "maskblood")
/obj/item/clothing/neck/tie
diff --git a/code/modules/clothing/shoes/_shoes.dm b/code/modules/clothing/shoes/_shoes.dm
index c0a618089f8..4dd532f3227 100644
--- a/code/modules/clothing/shoes/_shoes.dm
+++ b/code/modules/clothing/shoes/_shoes.dm
@@ -48,7 +48,7 @@
if(damaged_clothes)
. += mutable_appearance('icons/effects/item_damage.dmi', "damagedshoe")
- if(HAS_BLOOD_DNA(src))
+ if(GET_ATOM_BLOOD_DNA_LENGTH(src))
if(clothing_flags & LARGE_WORN_ICON)
. += mutable_appearance('icons/effects/64x64.dmi', "shoeblood_large")
else
diff --git a/code/modules/clothing/suits/_suits.dm b/code/modules/clothing/suits/_suits.dm
index 40dadad345b..ce676017ea1 100644
--- a/code/modules/clothing/suits/_suits.dm
+++ b/code/modules/clothing/suits/_suits.dm
@@ -26,7 +26,7 @@
var/damagefile2use = (mutant_styles & STYLE_TAUR_ALL) ? 'modular_skyrat/master_files/icons/mob/64x32_item_damage.dmi' : 'icons/effects/item_damage.dmi'
. += mutable_appearance(damagefile2use, "damaged[blood_overlay_type]")
//SKYRAT EDIT CHANGE END
- if(HAS_BLOOD_DNA(src))
+ if(GET_ATOM_BLOOD_DNA_LENGTH(src))
//SKYRAT EDIT CHANGE BEGIN
//. += mutable_appearance('icons/effects/blood.dmi', "[blood_overlay_type]blood") //ORIGINAL
var/bloodfile2use = (mutant_styles & STYLE_TAUR_ALL) ? 'modular_skyrat/master_files/icons/mob/64x32_blood.dmi' : 'icons/effects/blood.dmi'
diff --git a/code/modules/clothing/under/_under.dm b/code/modules/clothing/under/_under.dm
index 0a93e179213..547dc74e696 100644
--- a/code/modules/clothing/under/_under.dm
+++ b/code/modules/clothing/under/_under.dm
@@ -34,7 +34,7 @@
if(damaged_clothes)
. += mutable_appearance('icons/effects/item_damage.dmi', "damageduniform")
- if(HAS_BLOOD_DNA(src))
+ if(GET_ATOM_BLOOD_DNA_LENGTH(src))
. += mutable_appearance('icons/effects/blood.dmi', "uniformblood")
if(accessory_overlay)
. += accessory_overlay
diff --git a/code/modules/detectivework/scanner.dm b/code/modules/detectivework/scanner.dm
index f3af29917d6..83513f44fb1 100644
--- a/code/modules/detectivework/scanner.dm
+++ b/code/modules/detectivework/scanner.dm
@@ -86,8 +86,8 @@
//Make our lists
var/list/fingerprints = list()
- var/list/blood = A.return_blood_DNA()
- var/list/fibers = A.return_fibers()
+ var/list/blood = GET_ATOM_BLOOD_DNA(A)
+ var/list/fibers = GET_ATOM_FIBRES(A)
var/list/reagents = list()
var/target_name = A.name
@@ -102,7 +102,7 @@
else if(!ismob(A))
- fingerprints = A.return_fingerprints()
+ fingerprints = GET_ATOM_FINGERPRINTS(A)
// Only get reagents from non-mobs.
if(A.reagents && A.reagents.reagent_list.len)
diff --git a/code/modules/forensics/_forensics.dm b/code/modules/forensics/_forensics.dm
new file mode 100644
index 00000000000..70d7699b3bb
--- /dev/null
+++ b/code/modules/forensics/_forensics.dm
@@ -0,0 +1,237 @@
+/**
+ * Forensics datum
+ *
+ * Placed onto atoms, and contains:
+ * * List of fingerprints on the atom
+ * * List of hidden prints (used for admins)
+ * * List of blood on the atom
+ * * List of clothing fibers on the atom
+ */
+/datum/forensics
+ /// Weakref to the parent owning this datum
+ var/datum/weakref/parent
+ /**
+ * List of fingerprints on this atom
+ *
+ * Formatting:
+ * * print = print
+ */
+ var/list/fingerprints
+ /**
+ * List of hiddenprints on this atom
+ *
+ * Formatting:
+ * * ckey = realname/gloves/ckey
+ */
+ var/list/hiddenprints
+ /**
+ * List of blood dna on this atom
+ *
+ * Formatting:
+ * * dna = bloodtype
+ */
+ var/list/blood_DNA
+ /**
+ * List of clothing fibers on this atom
+ *
+ * Formatting:
+ * * fiber = fiber
+ */
+ var/list/fibers
+
+/datum/forensics/New(atom/parent, fingerprints, hiddenprints, blood_DNA, fibers)
+ if(!isatom(parent))
+ stack_trace("We tried adding a forensics datum to something that isnt an atom. What the hell are you doing?")
+ qdel(src)
+ return
+
+ RegisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean_act)
+
+ src.parent = WEAKREF(parent)
+ src.fingerprints = fingerprints
+ src.hiddenprints = hiddenprints
+ src.blood_DNA = blood_DNA
+ src.fibers = fibers
+ check_blood()
+
+/// Merges the given lists into the preexisting values
+/datum/forensics/proc/inherit_new(list/fingerprints, list/hiddenprints, list/blood_DNA, list/fibers) //Use of | and |= being different here is INTENTIONAL.
+ if (fingerprints)
+ src.fingerprints = LAZY_LISTS_OR(src.fingerprints, fingerprints)
+ if (hiddenprints)
+ src.hiddenprints = LAZY_LISTS_OR(src.hiddenprints, hiddenprints)
+ if (blood_DNA)
+ src.blood_DNA = LAZY_LISTS_OR(src.blood_DNA, blood_DNA)
+ if (fibers)
+ src.fibers = LAZY_LISTS_OR(src.fibers, fibers)
+ check_blood()
+
+/datum/forensics/Destroy(force, ...)
+ var/atom/parent_atom = parent.resolve()
+ if (!isnull(parent_atom))
+ UnregisterSignal(parent_atom, list(COMSIG_COMPONENT_CLEAN_ACT))
+ return ..()
+
+/// Empties the fingerprints list
+/datum/forensics/proc/wipe_fingerprints()
+ fingerprints = null
+ return TRUE
+
+/// Empties the blood_DNA list
+/datum/forensics/proc/wipe_blood_DNA()
+ blood_DNA = null
+ return TRUE
+
+/// Empties the fibers list
+/datum/forensics/proc/wipe_fibers()
+ fibers = null
+ return TRUE
+
+/// Handles cleaning up the various forensic types
+/datum/forensics/proc/clean_act(datum/source, clean_types)
+ SIGNAL_HANDLER
+
+ if(clean_types & CLEAN_TYPE_FINGERPRINTS)
+ wipe_fingerprints()
+ if(clean_types & CLEAN_TYPE_BLOOD)
+ wipe_blood_DNA()
+ if(clean_types & CLEAN_TYPE_FIBERS)
+ wipe_fibers()
+
+/// Adds the given list into fingerprints
+/datum/forensics/proc/add_fingerprint_list(list/fingerprints)
+ if(!length(fingerprints))
+ return
+ LAZYINITLIST(src.fingerprints)
+ for(var/fingerprint in fingerprints) //We use an associative list, make sure we don't just merge a non-associative list into ours.
+ src.fingerprints[fingerprint] = fingerprint
+ return TRUE
+
+/// Adds a single fingerprint
+/datum/forensics/proc/add_fingerprint(mob/living/suspect, ignoregloves = FALSE)
+ if(!isliving(suspect))
+ if(!iscameramob(suspect))
+ return
+ if(isaicamera(suspect))
+ var/mob/camera/ai_eye/ai_camera = suspect
+ if(!ai_camera.ai)
+ return
+ suspect = ai_camera.ai
+ add_hiddenprint(suspect)
+ if(ishuman(suspect))
+ var/mob/living/carbon/human/human_suspect = suspect
+ add_fibers(human_suspect)
+ var/obj/item/gloves = human_suspect.gloves
+ if(gloves) //Check if the gloves (if any) hide fingerprints
+ if(!(gloves.body_parts_covered & HANDS) || HAS_TRAIT(gloves, TRAIT_FINGERPRINT_PASSTHROUGH) || HAS_TRAIT(human_suspect, TRAIT_FINGERPRINT_PASSTHROUGH))
+ ignoregloves = TRUE
+ if(!ignoregloves)
+ human_suspect.gloves.add_fingerprint(human_suspect, ignoregloves = TRUE) //ignoregloves = TRUE to avoid infinite loop.
+ return
+ var/full_print = md5(human_suspect.dna.unique_identity)
+ LAZYSET(fingerprints, full_print, full_print)
+ return TRUE
+
+/// Adds the given list into fibers
+/datum/forensics/proc/add_fiber_list(list/fibers)
+ if(!length(fibers))
+ return
+ LAZYINITLIST(src.fibers)
+ for(var/fiber in fibers) //We use an associative list, make sure we don't just merge a non-associative list into ours.
+ src.fibers[fiber] = fiber
+ return TRUE
+
+#define ITEM_FIBER_MULTIPLIER 1.2
+#define NON_ITEM_FIBER_MULTIPLIER 1
+
+/// Adds a single fiber
+/datum/forensics/proc/add_fibers(mob/living/carbon/human/suspect)
+ var/fibertext
+ var/item_multiplier = isitem(src) ? ITEM_FIBER_MULTIPLIER : NON_ITEM_FIBER_MULTIPLIER
+ if(suspect.wear_suit)
+ fibertext = "Material from \a [suspect.wear_suit]."
+ if(prob(10 * item_multiplier) && !LAZYACCESS(fibers, fibertext))
+ LAZYSET(fibers, fibertext, fibertext)
+ if(!(suspect.wear_suit.body_parts_covered & CHEST))
+ if(suspect.w_uniform)
+ fibertext = "Fibers from \a [suspect.w_uniform]."
+ if(prob(12 * item_multiplier) && !LAZYACCESS(fibers, fibertext)) //Wearing a suit means less of the uniform exposed.
+ LAZYSET(fibers, fibertext, fibertext)
+ if(!(suspect.wear_suit.body_parts_covered & HANDS))
+ if(suspect.gloves)
+ fibertext = "Material from a pair of [suspect.gloves.name]."
+ if(prob(20 * item_multiplier) && !LAZYACCESS(fibers, fibertext))
+ LAZYSET(fibers, fibertext, fibertext)
+ else if(suspect.w_uniform)
+ fibertext = "Fibers from \a [suspect.w_uniform]."
+ if(prob(15 * item_multiplier) && !LAZYACCESS(fibers, fibertext))
+ LAZYSET(fibers, fibertext, fibertext)
+ if(suspect.gloves)
+ fibertext = "Material from a pair of [suspect.gloves.name]."
+ if(prob(20 * item_multiplier) && !LAZYACCESS(fibers, fibertext))
+ LAZYSET(fibers, fibertext, fibertext)
+ else if(suspect.gloves)
+ fibertext = "Material from a pair of [suspect.gloves.name]."
+ if(prob(20 * item_multiplier) && !LAZYACCESS(fibers, fibertext))
+ LAZYSET(fibers, fibertext, fibertext)
+ return TRUE
+
+#undef ITEM_FIBER_MULTIPLIER
+#undef NON_ITEM_FIBER_MULTIPLIER
+
+/// Adds the given list into hiddenprints
+/datum/forensics/proc/add_hiddenprint_list(list/hiddenprints) //list(ckey = text)
+ if(!length(hiddenprints))
+ return
+ LAZYINITLIST(src.hiddenprints)
+ for(var/hidden_print in hiddenprints) //We use an associative list, make sure we don't just merge a non-associative list into ours.
+ src.hiddenprints[hidden_print] = hiddenprints[hidden_print]
+ return TRUE
+
+/// Adds a single hiddenprint
+/datum/forensics/proc/add_hiddenprint(mob/suspect)
+ if(!isliving(suspect))
+ if(!iscameramob(suspect))
+ return
+ if(isaicamera(suspect))
+ var/mob/camera/ai_eye/ai_camera = suspect
+ if(!ai_camera.ai)
+ return
+ suspect = ai_camera.ai
+ if(!suspect.key)
+ return
+ var/has_gloves = ""
+ if(ishuman(suspect))
+ var/mob/living/carbon/human/human_suspect = suspect
+ if(human_suspect.gloves)
+ has_gloves = "(gloves)"
+ var/current_time = time_stamp()
+ if(!LAZYACCESS(hiddenprints, suspect.key))
+ LAZYSET(hiddenprints, suspect.key, "First: \[[current_time]\] \"[suspect.real_name]\"[has_gloves]. Ckey: [suspect.ckey]")
+ else
+ var/last_stamp_pos = findtext(LAZYACCESS(hiddenprints, suspect.key), "\nLast: ")
+ if(last_stamp_pos)
+ LAZYSET(hiddenprints, suspect.key, copytext(hiddenprints[suspect.key], 1, last_stamp_pos))
+ hiddenprints[suspect.key] += "\nLast: \[[current_time]\] \"[suspect.real_name]\"[has_gloves]. Ckey: [suspect.ckey]" //made sure to be existing by if(!LAZYACCESS);else
+ var/atom/parent_atom = parent.resolve()
+ parent_atom.fingerprintslast = suspect.ckey
+ return TRUE
+
+/// Adds the given list into blood_DNA
+/datum/forensics/proc/add_blood_DNA(list/blood_DNA)
+ if(!length(blood_DNA))
+ return
+ LAZYINITLIST(src.blood_DNA)
+ for(var/gene in blood_DNA)
+ src.blood_DNA[gene] = blood_DNA[gene]
+ check_blood()
+ return TRUE
+
+/// Updates the blood displayed on parent
+/datum/forensics/proc/check_blood()
+ if(!isitem(parent.resolve()))
+ return
+ if(!length(blood_DNA))
+ return
+ var/atom/parent_atom = parent.resolve()
+ parent_atom.AddElement(/datum/element/decal/blood)
diff --git a/code/modules/forensics/forensics_helpers.dm b/code/modules/forensics/forensics_helpers.dm
new file mode 100644
index 00000000000..155c6ab6788
--- /dev/null
+++ b/code/modules/forensics/forensics_helpers.dm
@@ -0,0 +1,122 @@
+/// Adds a list of fingerprints to the atom
+/atom/proc/add_fingerprint_list(list/fingerprints_to_add) //ASSOC LIST FINGERPRINT = FINGERPRINT
+ if (isnull(fingerprints_to_add))
+ return
+ if (forensics)
+ forensics.inherit_new(fingerprints = fingerprints_to_add)
+ else
+ forensics = new(src, fingerprints = fingerprints_to_add)
+ return TRUE
+
+/// Adds a single fingerprint to the atom
+/atom/proc/add_fingerprint(mob/suspect, ignoregloves = FALSE) //Set ignoregloves to add prints irrespective of the mob having gloves on.
+ if (QDELING(src))
+ return
+ if (isnull(forensics))
+ forensics = new(src)
+ forensics.add_fingerprint(suspect, ignoregloves)
+ return TRUE
+
+/// Add a list of fibers to the atom
+/atom/proc/add_fiber_list(list/fibers_to_add) //ASSOC LIST FIBERTEXT = FIBERTEXT
+ if (isnull(fibers_to_add))
+ return
+ if (forensics)
+ forensics.inherit_new(fibers = fibers_to_add)
+ else
+ forensics = new(src, fibers = fibers_to_add)
+ return TRUE
+
+/// Adds a single fiber to the atom
+/atom/proc/add_fibers(mob/living/carbon/human/suspect)
+ var/old = 0
+ if(suspect.gloves && istype(suspect.gloves, /obj/item/clothing))
+ var/obj/item/clothing/gloves/suspect_gloves = suspect.gloves
+ old = length(GET_ATOM_BLOOD_DNA(suspect_gloves))
+ if(suspect_gloves.transfer_blood > 1) //bloodied gloves transfer blood to touched objects
+ if(add_blood_DNA(GET_ATOM_BLOOD_DNA(suspect_gloves)) && GET_ATOM_BLOOD_DNA_LENGTH(suspect_gloves) > old) //only reduces the bloodiness of our gloves if the item wasn't already bloody
+ suspect_gloves.transfer_blood -= 1
+ else if(suspect.blood_in_hands > 1)
+ old = length(GET_ATOM_BLOOD_DNA(suspect))
+ if(add_blood_DNA(GET_ATOM_BLOOD_DNA(suspect)) && GET_ATOM_BLOOD_DNA_LENGTH(suspect) > old)
+ suspect.blood_in_hands -= 1
+ if (isnull(forensics))
+ forensics = new(src)
+ forensics.add_fibers(suspect)
+ return TRUE
+
+/// Adds a list of hiddenprints to the atom
+/atom/proc/add_hiddenprint_list(list/hiddenprints_to_add) //NOTE: THIS IS FOR ADMINISTRATION FINGERPRINTS, YOU MUST CUSTOM SET THIS TO INCLUDE CKEY/REAL NAMES! CHECK FORENSICS.DM
+ if (isnull(hiddenprints_to_add))
+ return
+ if (forensics)
+ forensics.inherit_new(hiddenprints = hiddenprints_to_add)
+ else
+ forensics = new(src, hiddenprints = hiddenprints_to_add)
+ return TRUE
+
+/// Adds a single hiddenprint to the atom
+/atom/proc/add_hiddenprint(mob/suspect)
+ if (isnull(forensics))
+ forensics = new(src)
+ forensics.add_hiddenprint(suspect)
+ return TRUE
+
+/// Adds blood dna to the atom
+/atom/proc/add_blood_DNA(list/blood_DNA_to_add) //ASSOC LIST DNA = BLOODTYPE
+ return FALSE
+
+/obj/add_blood_DNA(list/blood_DNA_to_add)
+ . = ..()
+ if (isnull(blood_DNA_to_add))
+ return .
+ if (forensics)
+ forensics.inherit_new(blood_DNA = blood_DNA_to_add)
+ else
+ forensics = new(src, blood_DNA = blood_DNA_to_add)
+ return TRUE
+
+/obj/item/clothing/gloves/add_blood_DNA(list/blood_dna, list/datum/disease/diseases)
+ transfer_blood = rand(2, 4)
+ return ..()
+
+/turf/add_blood_DNA(list/blood_dna, list/datum/disease/diseases)
+ var/obj/effect/decal/cleanable/blood/splatter/blood_splatter = locate() in src
+ if(!blood_splatter)
+ blood_splatter = new /obj/effect/decal/cleanable/blood/splatter(src, diseases)
+ if(!QDELETED(blood_splatter))
+ blood_splatter.add_blood_DNA(blood_dna) //give blood info to the blood decal.
+ return TRUE //we bloodied the floor
+ return FALSE
+
+/mob/living/carbon/human/add_blood_DNA(list/blood_DNA_to_add, list/datum/disease/diseases)
+ if(wear_suit)
+ wear_suit.add_blood_DNA(blood_DNA_to_add)
+ update_inv_wear_suit()
+ else if(w_uniform)
+ w_uniform.add_blood_DNA(blood_DNA_to_add)
+ update_inv_w_uniform()
+ if(gloves)
+ var/obj/item/clothing/gloves/mob_gloves = gloves
+ mob_gloves.add_blood_DNA(blood_DNA_to_add)
+ else if(length(blood_DNA_to_add))
+ if (isnull(forensics))
+ forensics = new(src)
+ forensics.inherit_new(blood_DNA = blood_DNA_to_add)
+ blood_in_hands = rand(2, 4)
+ update_inv_gloves()
+ return TRUE
+
+/*
+ * Transfer all the fingerprints and hidden prints from [src] to [transfer_to].
+ */
+/atom/proc/transfer_fingerprints_to(atom/transfer_to)
+ transfer_to.add_fingerprint_list(GET_ATOM_FINGERPRINTS(src))
+ transfer_to.add_hiddenprint_list(GET_ATOM_HIDDENPRINTS(src))
+ transfer_to.fingerprintslast = fingerprintslast
+
+/*
+ * Transfer all the fibers from [src] to [transfer_to].
+ */
+/atom/proc/transfer_fibers_to(atom/transfer_to)
+ transfer_to.add_fiber_list(GET_ATOM_FIBRES(src))
diff --git a/code/modules/mob/living/blood.dm b/code/modules/mob/living/blood.dm
index 8077ec397ce..3d989815694 100644
--- a/code/modules/mob/living/blood.dm
+++ b/code/modules/mob/living/blood.dm
@@ -327,7 +327,7 @@
drop.transfer_mob_blood_dna(src)
return
else
- temp_blood_DNA = drop.return_blood_DNA() //we transfer the dna from the drip to the splatter
+ temp_blood_DNA = GET_ATOM_BLOOD_DNA(drop) //we transfer the dna from the drip to the splatter
qdel(drop)//the drip is replaced by a bigger splatter
else
T.PolluteTurf(/datum/pollutant/metallic_scent, 5) //SKYRAT EDIT ADDITION
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index c6f32850b8f..ad43a969390 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -1419,7 +1419,7 @@
if(!isturf(loc))
return
var/obj/effect/decal/cleanable/blood/hitsplatter/our_splatter = new(loc)
- our_splatter.add_blood_DNA(return_blood_DNA())
+ our_splatter.add_blood_DNA(GET_ATOM_BLOOD_DNA(src))
our_splatter.blood_dna_info = get_blood_dna_list()
var/turf/targ = get_ranged_target_turf(src, splatter_direction, splatter_strength)
our_splatter.fly_towards(targ, splatter_strength)
diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm
index 3725559a56c..eba07172fc4 100644
--- a/code/modules/mob/living/carbon/human/examine.dm
+++ b/code/modules/mob/living/carbon/human/examine.dm
@@ -82,11 +82,10 @@
if(!(I.item_flags & ABSTRACT) && !(I.item_flags & EXAMINE_SKIP))
. += "[t_He] [t_is] holding [I.get_examine_string(user)] in [t_his] [get_held_index_name(get_held_index_of_item(I))]."
- var/datum/component/forensics/FR = GetComponent(/datum/component/forensics)
//gloves
if(gloves && !(obscured & ITEM_SLOT_GLOVES) && !(gloves.item_flags & EXAMINE_SKIP))
. += "[t_He] [t_has] [gloves.get_examine_string(user)] on [t_his] hands."
- else if(FR && length(FR.blood_DNA))
+ else if(GET_ATOM_BLOOD_DNA_LENGTH(src))
if(num_hands)
. += span_warning("[t_He] [t_has] [num_hands > 1 ? "" : "a"] blood-stained hand[num_hands > 1 ? "s" : ""]!")
diff --git a/code/modules/mob/living/simple_animal/bot/hygienebot.dm b/code/modules/mob/living/simple_animal/bot/hygienebot.dm
index a1d9ab81793..f4527a55902 100644
--- a/code/modules/mob/living/simple_animal/bot/hygienebot.dm
+++ b/code/modules/mob/living/simple_animal/bot/hygienebot.dm
@@ -211,7 +211,7 @@
for(var/X in list(ITEM_SLOT_HEAD, ITEM_SLOT_MASK, ITEM_SLOT_ICLOTHING, ITEM_SLOT_OCLOTHING, ITEM_SLOT_FEET))
var/obj/item/I = L.get_item_by_slot(X)
- if(I && HAS_BLOOD_DNA(I))
+ if(I && GET_ATOM_BLOOD_DNA_LENGTH(I))
return FALSE
return TRUE
diff --git a/code/modules/mob/living/simple_animal/bot/mulebot.dm b/code/modules/mob/living/simple_animal/bot/mulebot.dm
index 2dd22bd7571..3447985e3be 100644
--- a/code/modules/mob/living/simple_animal/bot/mulebot.dm
+++ b/code/modules/mob/living/simple_animal/bot/mulebot.dm
@@ -487,7 +487,7 @@
if(!last_move || isspaceturf(oldLoc)) //if we didn't sucessfully move, or if our old location was a spaceturf.
return
var/obj/effect/decal/cleanable/blood/tracks/B = new(oldLoc)
- B.add_blood_DNA(return_blood_DNA())
+ B.add_blood_DNA(GET_ATOM_BLOOD_DNA(src))
B.setDir(direct)
bloodiness--
diff --git a/modular_skyrat/modules/digi_bloodsole/code/_shoes.dm b/modular_skyrat/modules/digi_bloodsole/code/_shoes.dm
index a25aae12cd3..9076ce503fd 100644
--- a/modular_skyrat/modules/digi_bloodsole/code/_shoes.dm
+++ b/modular_skyrat/modules/digi_bloodsole/code/_shoes.dm
@@ -4,7 +4,7 @@
return
if(damaged_clothes)
. += mutable_appearance('icons/effects/item_damage.dmi', "damagedshoe")
- if(HAS_BLOOD_DNA(src))
+ if(GET_ATOM_BLOOD_DNA(src))
if (mutant_styles & CLOTHING_DIGITIGRADE_VARIATION)
if(clothing_flags & LARGE_WORN_ICON)
. += mutable_appearance('modular_skyrat/modules/digi_bloodsole/icons/64x64.dmi', "shoeblood_large_digi")
diff --git a/tgstation.dme b/tgstation.dme
index adcb7c22e1a..915a56ce9d4 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -349,6 +349,7 @@
#include "code\__HELPERS\dna.dm"
#include "code\__HELPERS\files.dm"
#include "code\__HELPERS\filters.dm"
+#include "code\__HELPERS\forensics.dm"
#include "code\__HELPERS\game.dm"
#include "code\__HELPERS\global_lists.dm"
#include "code\__HELPERS\guid.dm"
@@ -786,7 +787,6 @@
#include "code\datums\components\explodable.dm"
#include "code\datums\components\faction_granter.dm"
#include "code\datums\components\force_move.dm"
-#include "code\datums\components\forensics.dm"
#include "code\datums\components\fov_handler.dm"
#include "code\datums\components\fullauto.dm"
#include "code\datums\components\gas_leaker.dm"
@@ -2757,7 +2757,6 @@
#include "code\modules\clothing\under\jobs\Plasmaman\engineering.dm"
#include "code\modules\clothing\under\jobs\Plasmaman\medsci.dm"
#include "code\modules\clothing\under\jobs\Plasmaman\security.dm"
-#include "code\modules\detectivework\detective_work.dm"
#include "code\modules\detectivework\evidence.dm"
#include "code\modules\detectivework\footprints_and_rag.dm"
#include "code\modules\detectivework\scanner.dm"
@@ -2925,6 +2924,8 @@
#include "code\modules\food_and_drinks\restaurant\custom_order.dm"
#include "code\modules\food_and_drinks\restaurant\generic_venues.dm"
#include "code\modules\food_and_drinks\restaurant\customers\_customer.dm"
+#include "code\modules\forensics\_forensics.dm"
+#include "code\modules\forensics\forensics_helpers.dm"
#include "code\modules\holiday\easter.dm"
#include "code\modules\holiday\foreign_calendar.dm"
#include "code\modules\holiday\holidays.dm"