From d7cd22d2d0647eddc77e1f31fb546a781034ee21 Mon Sep 17 00:00:00 2001
From: CHOMPStation2StaffMirrorBot
<94713762+CHOMPStation2StaffMirrorBot@users.noreply.github.com>
Date: Fri, 6 Jun 2025 07:18:13 -0700
Subject: [PATCH] [MIRROR] Forensics Datum (#11015)
Co-authored-by: Will <7099514+Willburd@users.noreply.github.com>
Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com>
---
code/_macros.dm | 1 +
code/datums/datacore.dm | 4 +-
code/datums/forensics_crime.dm | 322 ++++++++++++++++++
code/game/atoms.dm | 165 +--------
code/game/dna/dna2.dm | 2 +-
.../gamemodes/changeling/powers/transform.dm | 1 -
code/game/gamemodes/cult/ritual.dm | 6 +-
code/game/gamemodes/cult/runes.dm | 4 +-
code/game/machinery/wall_frames.dm | 8 +-
code/game/objects/effects/chem/chemsmoke.dm | 9 +-
.../effects/decals/Cleanable/humans.dm | 15 +-
.../effects/decals/Cleanable/tracks.dm | 3 +-
code/game/objects/effects/gibs.dm | 6 +-
code/game/objects/items.dm | 7 +-
.../items/devices/communicator/cartridge.dm | 2 +-
.../objects/items/devices/transfer_valve.dm | 4 +-
code/game/objects/items/stacks/stack.dm | 9 +-
code/game/objects/items/stacks/stack_vr.dm | 3 +-
.../objects/items/weapons/swords_axes_etc.dm | 2 +-
.../game/objects/items/weapons/tanks/tanks.dm | 4 +-
code/game/objects/structures/watercloset.dm | 3 +-
code/game/turfs/simulated.dm | 15 +-
.../admin/secrets/admin_secrets/list_dna.dm | 2 +-
.../preference_setup/general/03_body.dm | 2 +-
code/modules/client/preferences.dm | 4 +-
code/modules/clothing/accessories/torch.dm | 2 +-
code/modules/clothing/clothing.dm | 9 +-
code/modules/clothing/clothing_icons.dm | 4 +-
code/modules/clothing/masks/gasmask.dm | 7 +-
code/modules/clothing/masks/hailer.dm | 7 +-
code/modules/detectivework/forensics.dm | 136 ++++++--
code/modules/detectivework/tools/rag.dm | 2 -
.../detectivework/tools/sample_kits.dm | 22 +-
code/modules/detectivework/tools/scanner.dm | 29 +-
code/modules/detectivework/tools/swabs.dm | 12 +-
.../subtypes/manipulation.dm | 2 +-
code/modules/mob/_modifiers/traits_phobias.dm | 4 +-
.../mob/living/carbon/human/examine.dm | 26 +-
code/modules/mob/living/carbon/human/human.dm | 5 +-
.../mob/living/carbon/human/human_defense.dm | 2 -
.../mob/living/carbon/human/human_defines.dm | 2 -
.../living/carbon/human/human_helpers_vr.dm | 2 +-
.../species/shadekin/shadekin_abilities.dm | 4 +-
.../human/species/station/prometheans.dm | 3 +-
.../mob/living/carbon/human/update_icons.dm | 5 +-
.../living/simple_mob/subtypes/animal/vox.dm | 2 +-
code/modules/organs/blood.dm | 7 +-
code/modules/organs/data.dm | 1 +
code/modules/organs/organ.dm | 15 +-
code/modules/pda/cart.dm | 2 +-
code/modules/pda/cart_apps.dm | 2 +-
code/modules/pda/utilities.dm | 9 +-
code/modules/power/cell.dm | 4 +-
code/modules/power/lighting.dm | 8 +-
code/modules/power/singularity/act.dm | 8 +-
.../projectiles/guns/launcher/crossbow.dm | 2 +-
code/modules/projectiles/guns/projectile.dm | 4 +-
.../modules/projectiles/guns/projectile_ch.dm | 4 +-
code/modules/reagents/reagents/core.dm | 4 +-
.../vore/fluffstuff/custom_items_vr.dm | 10 +-
code/modules/vore/persist/persist_vr.dm | 2 +-
.../xenoarcheaology/effects/vampire.dm | 3 +-
code/modules/xenoarcheaology/finds/special.dm | 9 +-
code/modules/xenobio/items/extracts_vr.dm | 26 +-
.../code/game/machinery/food_replicator.dm | 4 +-
.../code/modules/clothing/clothing_icons.dm | 2 +-
.../code/modules/clothing/masks/hailer.dm | 6 +-
.../simple_mob/subtypes/slimess/xenobio.dm | 4 +-
vorestation.dme | 1 +
69 files changed, 614 insertions(+), 413 deletions(-)
create mode 100644 code/datums/forensics_crime.dm
diff --git a/code/_macros.dm b/code/_macros.dm
index f47d75f49d..aa1859d79a 100644
--- a/code/_macros.dm
+++ b/code/_macros.dm
@@ -7,6 +7,7 @@
#define get_z(A) (get_step(A, 0)?.z || 0)
#define RANDOM_BLOOD_TYPE pick(4;"O-", 36;"O+", 3;"A-", 28;"A+", 1;"B-", 20;"B+", 1;"AB-", 5;"AB+")
+#define DEFAULT_BLOOD_TYPE "A+"
// #define to_chat(target, message) target << message Not anymore!
//#define to_chat to_chat_filename=__FILE__;to_chat_line=__LINE__;to_chat_src=src;__to_chat
diff --git a/code/datums/datacore.dm b/code/datums/datacore.dm
index b29ef2096d..57f9d7e303 100644
--- a/code/datums/datacore.dm
+++ b/code/datums/datacore.dm
@@ -375,7 +375,7 @@ GLOBAL_LIST_EMPTY(PDA_Manifest)
M.fields["species"] = "[H.custom_species ? "[H.custom_species]" : H.species.name]"
else
M.fields["species"] = "[H.custom_species ? "[H.custom_species] ([H.species.name])" : H.species.name]"
- M.fields["b_type"] = H.b_type
+ M.fields["b_type"] = H.dna ? H.dna.b_type : DEFAULT_BLOOD_TYPE
M.fields["blood_reagent"] = H.dna.blood_reagents
M.fields["blood_color"] = H.dna.blood_color
M.fields["b_dna"] = H.dna.unique_enzymes
@@ -414,7 +414,7 @@ GLOBAL_LIST_EMPTY(PDA_Manifest)
L.fields["brain_type"] = H.get_FBP_type()
else
L.fields["brain_type"] = "Organic"
- L.fields["b_type"] = H.b_type
+ L.fields["b_type"] = H.dna ? H.dna.b_type : DEFAULT_BLOOD_TYPE
L.fields["b_dna"] = H.dna.unique_enzymes
L.fields["enzymes"] = H.dna.SE // Used in respawning
L.fields["identity"] = H.dna.UI // "
diff --git a/code/datums/forensics_crime.dm b/code/datums/forensics_crime.dm
new file mode 100644
index 0000000000..4c89d8fa48
--- /dev/null
+++ b/code/datums/forensics_crime.dm
@@ -0,0 +1,322 @@
+/datum/forensics_crime
+ VAR_PRIVATE/list/fingerprints = null
+ VAR_PRIVATE/list/fingerprintshidden = null
+ VAR_PRIVATE/list/suit_fibres = null
+ VAR_PRIVATE/list/blood_DNA = null
+ VAR_PRIVATE/fingerprintslast = null
+ VAR_PRIVATE/gunshot_residue = null
+
+
+//////////////////////////////////////////////////////////////////////////////////////
+// Fingerprints
+//////////////////////////////////////////////////////////////////////////////////////
+/// Adds fingerprints to the object, prints can be smudged, glove handling is done on the atom side of this proc.
+/datum/forensics_crime/proc/add_prints(var/mob/living/carbon/human/H)
+ //Now, lets get to the dirty work.
+ if(!fingerprints)
+ fingerprints = list()
+ if(!fingerprintshidden)
+ fingerprintshidden = list()
+
+ //More adminstuffz
+ if(fingerprintslast != H.key)
+ fingerprintshidden += "[time_stamp()]: [key_name(H)]"
+ fingerprintslast = H.key
+
+ //Hash this shit.
+ var/full_print = H.get_full_print()
+
+ // Add the fingerprints
+ if(fingerprints[full_print])
+ switch(stringpercent(fingerprints[full_print])) //tells us how many stars are in the current prints.
+
+ if(28 to 32)
+ if(prob(1))
+ fingerprints[full_print] = full_print // You rolled a one buddy.
+ else
+ fingerprints[full_print] = stars(full_print, rand(0,40)) // 24 to 32
+
+ if(24 to 27)
+ if(prob(3))
+ fingerprints[full_print] = full_print //Sucks to be you.
+ else
+ fingerprints[full_print] = stars(full_print, rand(15, 55)) // 20 to 29
+
+ if(20 to 23)
+ if(prob(5))
+ fingerprints[full_print] = full_print //Had a good run didn't ya.
+ else
+ fingerprints[full_print] = stars(full_print, rand(30, 70)) // 15 to 25
+
+ if(16 to 19)
+ if(prob(5))
+ fingerprints[full_print] = full_print //Welp.
+ else
+ fingerprints[full_print] = stars(full_print, rand(40, 100)) // 0 to 21
+
+ if(0 to 15)
+ if(prob(5))
+ fingerprints[full_print] = stars(full_print, rand(0,50)) // small chance you can smudge.
+ else
+ fingerprints[full_print] = full_print
+
+ else
+ fingerprints[full_print] = stars(full_print, rand(0, 20)) //Initial touch, not leaving much evidence the first time.
+
+ return TRUE
+
+/// Returns a list of fingerprint strings that have touched an object. Always returns a list.
+/datum/forensics_crime/proc/get_prints()
+ RETURN_TYPE(/list)
+ if(!fingerprints)
+ return list()
+ return fingerprints
+
+/// Returns true if any fingerprints are present on this object.
+/datum/forensics_crime/proc/has_prints()
+ if(!fingerprints || !fingerprints.len)
+ return FALSE
+ return TRUE
+
+/// Merges data from another forensics crime datum into this one. Entries with the same key will be merged. Does nothing if the origin datum's list is empty.
+/datum/forensics_crime/proc/merge_prints(var/datum/forensics_crime/origin)
+ if(!islist(origin?.fingerprints))
+ return
+ if(fingerprints)
+ fingerprints |= origin.fingerprints
+ else
+ fingerprints = origin.fingerprints.Copy()
+
+/// Clears data to default state, wiping all evidence
+/datum/forensics_crime/proc/clear_prints()
+ LAZYCLEARLIST(fingerprints)
+ fingerprintslast = null
+
+/// Sets the key of the last mob to touch this object
+/datum/forensics_crime/proc/set_lastprint(var/val)
+ fingerprintslast = val
+
+/// Gets the key of the last mob to touch this object
+/datum/forensics_crime/proc/get_lastprint()
+ return fingerprintslast
+
+
+//////////////////////////////////////////////////////////////////////////////////////
+// Hidden prints
+//////////////////////////////////////////////////////////////////////////////////////
+/// Adds hidden admin trackable fingerprints, visible even if normal fingerprints are smudged.
+/datum/forensics_crime/proc/add_hiddenprints(mob/living/M as mob)
+ if(!ishuman(M))
+ if(fingerprintslast != M.key)
+ fingerprintshidden += text("\[[time_stamp()]\] (Non-human mob). Real name: [], Key: []",M.real_name, M.key)
+ fingerprintslast = M.key
+ return FALSE
+ var/mob/living/carbon/human/H = M
+ if(H.gloves)
+ if(fingerprintslast != H.key)
+ fingerprintshidden += text("\[[time_stamp()]\] (Wearing gloves). Real name: [], Key: []",H.real_name, H.key)
+ fingerprintslast = H.key
+ return FALSE
+ if (mFingerprints in M.mutations)
+ if(fingerprintslast != H.key)
+ fingerprintshidden += text("\[[time_stamp()]\] (Noprint mutation). Real name: [], Key: []",H.real_name, H.key)
+ fingerprintslast = H.key
+ return FALSE
+ if(fingerprints)
+ return FALSE
+ if(fingerprintslast != H.key)
+ fingerprintshidden += text("\[[time_stamp()]\] Real name: [], Key: []",H.real_name, H.key)
+ fingerprintslast = H.key
+ return TRUE
+
+/// Returns a list of hidden admin fingerprint strings that describe how a mob interacted with an object, for debugging forensics, or investigating player behavior. Always returns a list.
+/datum/forensics_crime/proc/get_hiddenprints()
+ RETURN_TYPE(/list)
+ if(!fingerprintshidden)
+ return list()
+ return fingerprintshidden
+
+/// Returns true if and hidden admin fingerprints are on this object
+/datum/forensics_crime/proc/has_hiddenprints()
+ if(!fingerprintshidden || !fingerprintshidden.len)
+ return FALSE
+ return TRUE
+
+/// Merges data from another forensics crime datum into this one. Entries with the same key will be merged. Does nothing if the origin datum's list is empty.
+/datum/forensics_crime/proc/merge_hiddenprints(var/datum/forensics_crime/origin)
+ if(!islist(origin?.fingerprintshidden))
+ return
+ if(fingerprintshidden)
+ fingerprintshidden |= origin.fingerprintshidden
+ else
+ fingerprintshidden = origin.fingerprintshidden.Copy()
+
+
+//////////////////////////////////////////////////////////////////////////////////////
+// Fibres
+//////////////////////////////////////////////////////////////////////////////////////
+/// Adds stray fibres from clothing worn by a mob while handling something
+/datum/forensics_crime/proc/add_fibres(var/mob/living/carbon/human/M)
+ var/fibertext = null
+ var/item_multiplier = istype(src,/obj/item)?1.2:1
+ var/suit_coverage = 0
+ if(M.wear_suit)
+ if(prob(10*item_multiplier))
+ fibertext = "Material from \a [M.wear_suit]."
+ suit_coverage = M.wear_suit.body_parts_covered
+
+ if(M.w_uniform && (M.w_uniform.body_parts_covered & ~suit_coverage))
+ if(prob(15*item_multiplier))
+ fibertext = "Fibers from \a [M.w_uniform]."
+
+ if(M.gloves && (M.gloves.body_parts_covered & ~suit_coverage))
+ if(prob(20*item_multiplier))
+ fibertext = "Material from a pair of [M.gloves.name]."
+
+ if(!fibertext)
+ return
+
+ // Add to dataset
+ if(suit_fibres)
+ if(!(fibertext in suit_fibres))
+ suit_fibres.Add(fibertext)
+ return
+ suit_fibres = list()
+ suit_fibres.Add(fibertext)
+
+/// Gets a list of fibres contaminating this object
+/datum/forensics_crime/proc/get_fibres()
+ RETURN_TYPE(/list)
+ if(!suit_fibres)
+ return list()
+ return suit_fibres
+
+/// Returns true if any stray clothing fibres are on this object
+/datum/forensics_crime/proc/has_fibres()
+ if(!suit_fibres || !suit_fibres.len)
+ return FALSE
+ return TRUE
+
+/// Merges data from another forensics crime datum into this one. Entries with the same key will be merged. Does nothing if the origin datum's list is empty.
+/datum/forensics_crime/proc/merge_fibres(var/datum/forensics_crime/origin)
+ if(!islist(origin?.suit_fibres))
+ return
+ if(suit_fibres)
+ suit_fibres |= origin.suit_fibres
+ else
+ suit_fibres = origin.suit_fibres.Copy()
+
+/// Clears data to default state, wiping all evidence
+/datum/forensics_crime/proc/clear_fibres()
+ LAZYCLEARLIST(suit_fibres)
+
+
+//////////////////////////////////////////////////////////////////////////////////////
+// Blood dna
+//////////////////////////////////////////////////////////////////////////////////////
+#define XENO_DNA "UNKNOWN DNA STRUCTURE"
+#define NOT_HUMAN_DNA "Non-human DNA"
+/// Adds the mob's bloodtype to a UE keyed list, returns true if the key was not present in the list before.
+/datum/forensics_crime/proc/add_blooddna(var/datum/dna/dna_data,var/mob/M)
+ if(!blood_DNA)
+ blood_DNA = list()
+ // Special alien handling
+ if(istype(M, /mob/living/carbon/alien))
+ var/fresh = isnull(blood_DNA[XENO_DNA])
+ blood_DNA[XENO_DNA] = "X*"
+ return fresh
+ // Simple mob gibbing
+ if(!dna_data)
+ var/fresh = isnull(blood_DNA[NOT_HUMAN_DNA])
+ blood_DNA[NOT_HUMAN_DNA] = DEFAULT_BLOOD_TYPE
+ return fresh
+ // Standard blood
+ var/fresh = isnull(blood_DNA[dna_data.unique_enzymes])
+ blood_DNA[dna_data.unique_enzymes] = dna_data.b_type
+ return fresh
+#undef XENO_DNA
+#undef NOT_HUMAN_DNA
+
+/// Adds the mob's bloodtype to a UE keyed list, returns true if the key was not present in the list before. Uses an organ's dna_data datum instead of a mob's dna datum.
+/datum/forensics_crime/proc/add_blooddna_organ(var/datum/organ_data/dna_data)
+ if(!blood_DNA)
+ blood_DNA = list()
+ var/fresh = isnull(blood_DNA[dna_data.unique_enzymes])
+ blood_DNA[dna_data.unique_enzymes] = dna_data.b_type
+ return fresh
+
+/// Returns a list of UE keys with bloodtype values that have contaminated this object. Always returns a list.
+/datum/forensics_crime/proc/get_blooddna()
+ RETURN_TYPE(/list)
+ if(!blood_DNA)
+ return list()
+ return blood_DNA
+
+/// Returns true if any blood contaminated this object
+/datum/forensics_crime/proc/has_blooddna()
+ if(!blood_DNA || !blood_DNA.len)
+ return FALSE
+ return TRUE
+
+/// Merges data from another forensics crime datum into this one. Entries with the same key will be merged. Does nothing if the origin datum's list is empty. Supports merging from a list directly as well.
+/datum/forensics_crime/proc/merge_blooddna(var/datum/forensics_crime/origin, var/list/raw_list = null)
+ // Copying from a list, blood on a mob's feet is stored as a list outside of forensics data
+ if(raw_list)
+ if(blood_DNA)
+ blood_DNA |= raw_list
+ else
+ blood_DNA = raw_list.Copy()
+ return
+ // Copying from another datums
+ if(!islist(origin?.blood_DNA))
+ return
+ if(blood_DNA)
+ blood_DNA |= origin.blood_DNA
+ else
+ blood_DNA = origin.blood_DNA.Copy()
+
+/// Clears data to default state, wiping all evidence
+/datum/forensics_crime/proc/clear_blooddna()
+ LAZYCLEARLIST(blood_DNA)
+
+
+//////////////////////////////////////////////////////////////////////////////////////
+// Gunshot residue
+//////////////////////////////////////////////////////////////////////////////////////
+/// Sets a string name of the last fired bullet's caliber from a projectile based gun.
+/datum/forensics_crime/proc/add_gunshotresidue(var/gsr)
+ gunshot_residue = gsr
+
+/// Gets a string name of the last bullet caliber fired from a projectile based gun.
+/datum/forensics_crime/proc/get_gunshotresidue()
+ return gunshot_residue
+
+/// Clears data to default state, wiping all evidence
+/datum/forensics_crime/proc/clear_gunshotresidue()
+ gunshot_residue = null
+
+
+//////////////////////////////////////////////////////////////////////////////////////
+// Misc procs
+//////////////////////////////////////////////////////////////////////////////////////
+/// Cleans off forensic information, different cleaning types remove different things.
+/datum/forensics_crime/proc/wash(var/clean_types)
+ // These require specific cleaning flags
+ if(clean_types & CLEAN_TYPE_BLOOD)
+ clear_blooddna()
+ if(clean_types & CLEAN_TYPE_FINGERPRINTS)
+ clear_prints()
+ if(clean_types & CLEAN_TYPE_FIBERS)
+ clear_fibres()
+ // anything will wash away gunshot residue
+ if(clean_types > 0)
+ clear_gunshotresidue()
+
+/// Merges both visible and admin hidden investigation fingerprints, as well as setting the last fingerprint from the origin datum's if one is not already set.
+/datum/forensics_crime/proc/merge_allprints(var/datum/forensics_crime/origin)
+ if(!origin)
+ return
+ merge_prints(origin)
+ merge_hiddenprints(origin)
+ if(origin.fingerprintslast)
+ fingerprintslast = origin.fingerprintslast
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 696a1f2be2..8f0cce8817 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -2,10 +2,6 @@
layer = TURF_LAYER //This was here when I got here. Why though?
var/level = 2
var/flags = 0
- var/list/fingerprints
- var/list/fingerprintshidden
- var/fingerprintslast = null
- var/list/blood_DNA
var/was_bloodied
var/blood_color
var/pass_flags = 0
@@ -14,6 +10,7 @@
var/simulated = TRUE //filter for actions - used by lighting overlays
var/atom_say_verb = "says"
var/bubble_icon = "normal" ///what icon the atom uses for speechbubbles
+ var/datum/forensics_crime/forensic_data
var/fluorescent // Shows up under a UV light.
var/last_bumped = 0
@@ -65,6 +62,8 @@
QDEL_NULL(reagents)
if(light)
QDEL_NULL(light)
+ if(forensic_data)
+ QDEL_NULL(forensic_data)
return ..()
/atom/proc/reveal_blood()
@@ -187,7 +186,7 @@
SHOULD_CALL_PARENT(TRUE)
//This reformat names to get a/an properly working on item descriptions when they are bloody
var/f_name = "\a [src][infix]."
- if(src.blood_DNA && !istype(src, /obj/effect/decal))
+ if(forensic_data?.has_blooddna() && !istype(src, /obj/effect/decal))
if(gender == PLURAL)
f_name = "some "
else
@@ -270,163 +269,12 @@
AM.throwing = 0
return
-/atom/proc/add_hiddenprint(mob/living/M as mob)
- if(isnull(M)) return
- if(isnull(M.key)) return
- if (ishuman(M))
- var/mob/living/carbon/human/H = M
- if (!istype(H.dna, /datum/dna))
- return 0
- if (H.gloves)
- if(src.fingerprintslast != H.key)
- src.fingerprintshidden += text("\[[time_stamp()]\] (Wearing gloves). Real name: [], Key: []",H.real_name, H.key)
- src.fingerprintslast = H.key
- return 0
- if (!( src.fingerprints ))
- if(src.fingerprintslast != H.key)
- src.fingerprintshidden += text("\[[time_stamp()]\] Real name: [], Key: []",H.real_name, H.key)
- src.fingerprintslast = H.key
- return 1
- else
- if(src.fingerprintslast != M.key)
- src.fingerprintshidden += text("\[[time_stamp()]\] Real name: [], Key: []",M.real_name, M.key)
- src.fingerprintslast = M.key
- return
-
-/atom/proc/add_fingerprint(mob/living/M as mob, ignoregloves = 0)
- if(isnull(M)) return
- if(isAI(M)) return
- if(isnull(M.key)) return
- if (ishuman(M))
- //Add the list if it does not exist.
- if(!fingerprintshidden)
- fingerprintshidden = list()
-
- //Fibers~
- add_fibers(M)
-
- //He has no prints!
- if (mFingerprints in M.mutations)
- if(fingerprintslast != M.key)
- fingerprintshidden += "[time_stamp()]: [key_name(M)] (No fingerprints mutation)"
- fingerprintslast = M.key
- return 0 //Now, lets get to the dirty work.
- //First, make sure their DNA makes sense.
- var/mob/living/carbon/human/H = M
- if (!istype(H.dna, /datum/dna) || !H.dna.uni_identity || (length(H.dna.uni_identity) != 32))
- if(!istype(H.dna, /datum/dna))
- H.dna = new /datum/dna(null)
- H.dna.real_name = H.real_name
- H.check_dna()
-
- //Now, deal with gloves.
- if (H.gloves && H.gloves != src)
- if(fingerprintslast != H.key)
- fingerprintshidden += "[time_stamp()]: [key_name(H)] (Wearing [H.gloves])"
- fingerprintslast = H.key
- H.gloves.add_fingerprint(M)
-
- //Deal with gloves the pass finger/palm prints.
- if(!ignoregloves)
- if(H.gloves && H.gloves != src)
- if(istype(H.gloves, /obj/item/clothing/gloves))
- var/obj/item/clothing/gloves/G = H.gloves
- if(!prob(G.fingerprint_chance))
- return 0
-
- //More adminstuffz
- if(fingerprintslast != H.key)
- fingerprintshidden += "[time_stamp()]: [key_name(H)]"
- fingerprintslast = H.key
-
- //Make the list if it does not exist.
- if(!fingerprints)
- fingerprints = list()
-
- //Hash this shit.
- var/full_print = H.get_full_print()
-
- // Add the fingerprints
- //
- if(fingerprints[full_print])
- switch(stringpercent(fingerprints[full_print])) //tells us how many stars are in the current prints.
-
- if(28 to 32)
- if(prob(1))
- fingerprints[full_print] = full_print // You rolled a one buddy.
- else
- fingerprints[full_print] = stars(full_print, rand(0,40)) // 24 to 32
-
- if(24 to 27)
- if(prob(3))
- fingerprints[full_print] = full_print //Sucks to be you.
- else
- fingerprints[full_print] = stars(full_print, rand(15, 55)) // 20 to 29
-
- if(20 to 23)
- if(prob(5))
- fingerprints[full_print] = full_print //Had a good run didn't ya.
- else
- fingerprints[full_print] = stars(full_print, rand(30, 70)) // 15 to 25
-
- if(16 to 19)
- if(prob(5))
- fingerprints[full_print] = full_print //Welp.
- else
- fingerprints[full_print] = stars(full_print, rand(40, 100)) // 0 to 21
-
- if(0 to 15)
- if(prob(5))
- fingerprints[full_print] = stars(full_print, rand(0,50)) // small chance you can smudge.
- else
- fingerprints[full_print] = full_print
-
- else
- fingerprints[full_print] = stars(full_print, rand(0, 20)) //Initial touch, not leaving much evidence the first time.
-
-
- return 1
- else
- //Smudge up dem prints some
- if(fingerprintslast != M.key)
- fingerprintshidden += "[time_stamp()]: [key_name(M)]"
- fingerprintslast = M.key
-
- //Cleaning up shit.
- if(fingerprints && !fingerprints.len)
- qdel(fingerprints)
- return
-
-
-/atom/proc/transfer_fingerprints_to(var/atom/A)
-
- if(!istype(A.fingerprints,/list))
- A.fingerprints = list()
-
- if(!istype(A.fingerprintshidden,/list))
- A.fingerprintshidden = list()
-
- if(!istype(fingerprintshidden, /list))
- fingerprintshidden = list()
-
- //skytodo
- //A.fingerprints |= fingerprints //detective
- //A.fingerprintshidden |= fingerprintshidden //admin
- if(A.fingerprints && fingerprints)
- A.fingerprints |= fingerprints.Copy() //detective
- if(A.fingerprintshidden && fingerprintshidden)
- A.fingerprintshidden |= fingerprintshidden.Copy() //admin A.fingerprintslast = fingerprintslast
-
-
//returns 1 if made bloody, returns 0 otherwise
/atom/proc/add_blood(mob/living/carbon/human/M as mob)
if(flags & NOBLOODY)
return 0
- if(!blood_DNA || !istype(blood_DNA, /list)) //if our list of DNA doesn't exist yet (or isn't a list) initialise it.
- blood_DNA = list()
-
was_bloodied = TRUE
if(!blood_color)
blood_color = SYNTH_BLOOD_COLOUR
@@ -816,10 +664,7 @@ GLOBAL_LIST_EMPTY(icon_dimensions)
remove_atom_colour(WASHABLE_COLOUR_PRIORITY)
return TRUE
- if(istype(blood_DNA, /list))
- blood_DNA = null
- return TRUE
-
+ forensic_data?.wash(clean_types)
blood_color = null
germ_level = 0
fluorescent = 0
diff --git a/code/game/dna/dna2.dm b/code/game/dna/dna2.dm
index 90c9237e06..4a80c02f0d 100644
--- a/code/game/dna/dna2.dm
+++ b/code/game/dna/dna2.dm
@@ -46,7 +46,7 @@ GLOBAL_LIST_EMPTY_TYPED(dna_genes_bad, /datum/gene/trait)
var/list/UI[DNA_UI_LENGTH]
// From old dna.
- var/b_type = "A+" // Should probably change to an integer => string map but I'm lazy.
+ var/b_type = DEFAULT_BLOOD_TYPE // Should probably change to an integer => string map but I'm lazy.
var/real_name // Stores the real name of the person who originally got this dna datum. Used primarily for changelings,
// VOREStation
diff --git a/code/game/gamemodes/changeling/powers/transform.dm b/code/game/gamemodes/changeling/powers/transform.dm
index ee60199480..606beda59e 100644
--- a/code/game/gamemodes/changeling/powers/transform.dm
+++ b/code/game/gamemodes/changeling/powers/transform.dm
@@ -41,7 +41,6 @@
src.dna.b_type = "AB+" //This is needed to avoid blood rejection bugs. The fact that the blood type might not match up w/ records could be a *FEATURE* too.
if(ishuman(src))
var/mob/living/carbon/human/H = src
- H.b_type = "AB+" //For some reason we have two blood types on the mob.
H.identifying_gender = chosen_dna.identifying_gender
H.flavor_texts = chosen_dna.flavour_texts ? chosen_dna.flavour_texts.Copy() : null
src.real_name = chosen_dna.name
diff --git a/code/game/gamemodes/cult/ritual.dm b/code/game/gamemodes/cult/ritual.dm
index 767d388480..af93dd4d20 100644
--- a/code/game/gamemodes/cult/ritual.dm
+++ b/code/game/gamemodes/cult/ritual.dm
@@ -417,8 +417,7 @@ GLOBAL_LIST_INIT(rnwords, list("ire","ego","nahlizet","certum","veri","jatkaa","
R.word2 = english[required[2]]
R.word3 = english[required[3]]
R.check_icon()
- R.blood_DNA = list()
- R.blood_DNA[H.dna.unique_enzymes] = H.dna.b_type
+ R.add_blooddna(H.dna,H)
return
else
to_chat(user, "The book seems full of illegible scribbles. Is this a joke?")
@@ -451,8 +450,7 @@ GLOBAL_LIST_INIT(rnwords, list("ire","ego","nahlizet","certum","veri","jatkaa","
var/obj/effect/rune/R = new /obj/effect/rune
if(ishuman(user))
var/mob/living/carbon/human/H = user
- R.blood_DNA = list()
- R.blood_DNA[H.dna.unique_enzymes] = H.dna.b_type
+ R.add_blooddna(H.dna,H)
var/area/A = get_area(user)
log_and_message_admins("created \an [r] rune at \the [A.name] - [user.loc.x]-[user.loc.y]-[user.loc.z].")
switch(r)
diff --git a/code/game/gamemodes/cult/runes.dm b/code/game/gamemodes/cult/runes.dm
index e0c239797f..d74c19ab02 100644
--- a/code/game/gamemodes/cult/runes.dm
+++ b/code/game/gamemodes/cult/runes.dm
@@ -1038,7 +1038,7 @@ var/list/sacrificed = list()
culcount++
if(culcount >= 5)
for(var/obj/effect/rune/R in rune_list)
- if(R.blood_DNA == src.blood_DNA)
+ if(R.forensic_data?.get_blooddna() == src.forensic_data?.get_blooddna())
for(var/mob/living/M in orange(2,R))
M.take_overall_damage(0,15)
if (R.invisibility>M.see_invisible)
@@ -1048,7 +1048,7 @@ var/list/sacrificed = list()
var/turf/T = get_turf(R)
T.hotspot_expose(700,125)
for(var/obj/effect/decal/cleanable/blood/B in world)
- if(B.blood_DNA == src.blood_DNA)
+ if(B.forensic_data?.get_blooddna() == src.forensic_data?.get_blooddna())
for(var/mob/living/M in orange(1,B))
M.take_overall_damage(0,5)
to_chat(M, span_danger("Blood suddenly ignites, burning you!"))
diff --git a/code/game/machinery/wall_frames.dm b/code/game/machinery/wall_frames.dm
index 8a88ee9293..1c17b2d05a 100644
--- a/code/game/machinery/wall_frames.dm
+++ b/code/game/machinery/wall_frames.dm
@@ -45,9 +45,7 @@
return
var/obj/machinery/M = new build_machine_type(get_turf(src.loc), ndir, 1, frame_type)
- M.fingerprints = fingerprints
- M.fingerprintshidden = fingerprintshidden
- M.fingerprintslast = fingerprintslast
+ M.init_forensic_data().merge_allprints(forensic_data)
if(istype(src.loc, /obj/item/gripper)) //Typical gripper shenanigans
user.drop_item()
qdel(src)
@@ -94,9 +92,7 @@
new /obj/item/stack/material/steel(user.loc, (5 - frame_type.frame_size))
var/obj/machinery/M = new build_machine_type(loc, ndir, 1, frame_type)
- M.fingerprints = fingerprints
- M.fingerprintshidden = fingerprintshidden
- M.fingerprintslast = fingerprintslast
+ M.init_forensic_data().merge_allprints(forensic_data)
if(istype(src.loc, /obj/item/gripper)) //Typical gripper shenanigans
user.drop_item()
qdel(src)
diff --git a/code/game/objects/effects/chem/chemsmoke.dm b/code/game/objects/effects/chem/chemsmoke.dm
index 37df00c062..47920bacb3 100644
--- a/code/game/objects/effects/chem/chemsmoke.dm
+++ b/code/game/objects/effects/chem/chemsmoke.dm
@@ -93,13 +93,14 @@
var/whereLink = "[where]"
if(show_log)
- if(carry.my_atom.fingerprintslast)
- var/mob/M = get_mob_by_key(carry.my_atom.fingerprintslast)
+ var/print_name = carry.my_atom.forensic_data?.get_lastprint()
+ if(print_name)
+ var/mob/M = get_mob_by_key(print_name)
var/more = ""
if(M)
more = "(?)"
- message_admins("A chemical smoke reaction has taken place in ([whereLink])[contained]. Last associated key is [carry.my_atom.fingerprintslast][more].", 0, 1)
- log_game("A chemical smoke reaction has taken place in ([where])[contained]. Last associated key is [carry.my_atom.fingerprintslast].")
+ message_admins("A chemical smoke reaction has taken place in ([whereLink])[contained]. Last associated key is [print_name][more].", 0, 1)
+ log_game("A chemical smoke reaction has taken place in ([where])[contained]. Last associated key is [print_name].")
else
message_admins("A chemical smoke reaction has taken place in ([whereLink]). No associated key.", 0, 1)
log_game("A chemical smoke reaction has taken place in ([where])[contained]. No associated key.")
diff --git a/code/game/objects/effects/decals/Cleanable/humans.dm b/code/game/objects/effects/decals/Cleanable/humans.dm
index 7d75dccd06..7c6408febf 100644
--- a/code/game/objects/effects/decals/Cleanable/humans.dm
+++ b/code/game/objects/effects/decals/Cleanable/humans.dm
@@ -16,7 +16,6 @@ var/global/list/image/splatter_cache=list()
icon_state = "mfloor1"
random_icon_states = list("mfloor1", "mfloor2", "mfloor3", "mfloor4", "mfloor5", "mfloor6", "mfloor7")
var/base_icon = 'icons/effects/blood.dmi'
- blood_DNA = list()
var/basecolor="#A10808" // Color when wet.
var/synthblood = 0
var/list/datum/disease/viruses = list()
@@ -49,8 +48,7 @@ var/global/list/image/splatter_cache=list()
if(src.loc && isturf(src.loc))
for(var/obj/effect/decal/cleanable/blood/B in src.loc)
if(B != src)
- if (B.blood_DNA)
- blood_DNA |= B.blood_DNA.Copy()
+ init_forensic_data().merge_blooddna(B.forensic_data)
if(!(B.flags & ATOM_INITIALIZED))
B.delete_me = TRUE
else
@@ -102,21 +100,20 @@ var/global/list/image/splatter_cache=list()
S.update_icon() // Cut previous overlays
if(!S.blood_overlay)
S.generate_blood_overlay()
- if(!S.blood_DNA)
- S.blood_DNA = list()
+ if(!forensic_data?.has_blooddna())
S.blood_overlay.color = basecolor
S.add_overlay(S.blood_overlay)
if(S.blood_overlay && S.blood_overlay.color != basecolor)
S.blood_overlay.color = basecolor
S.add_overlay(S.blood_overlay)
- S.blood_DNA |= blood_DNA.Copy()
+ transfer_blooddna_to(S)
perp.update_inv_shoes()
else if (hasfeet)//Or feet
perp.feet_blood_color = basecolor
perp.track_blood = max(amount,perp.track_blood)
LAZYINITLIST(perp.feet_blood_DNA)
- perp.feet_blood_DNA |= blood_DNA.Copy()
+ perp.feet_blood_DNA |= init_forensic_data().get_blooddna().Copy()
perp.update_bloodied()
else if (perp.buckled && istype(perp.buckled, /obj/structure/bed/chair/wheelchair))
var/obj/structure/bed/chair/wheelchair/W = perp.buckled
@@ -145,9 +142,7 @@ var/global/list/image/splatter_cache=list()
var/taken = rand(1,amount)
amount -= taken
to_chat(user, span_notice("You get some of \the [src] on your hands."))
- if (!user.blood_DNA)
- user.blood_DNA = list()
- user.blood_DNA |= blood_DNA.Copy()
+ transfer_blooddna_to(user)
user.bloody_hands += taken
user.hand_blood_color = basecolor
user.update_inv_gloves(1)
diff --git a/code/game/objects/effects/decals/Cleanable/tracks.dm b/code/game/objects/effects/decals/Cleanable/tracks.dm
index 400a6124a4..4655a9e4bf 100644
--- a/code/game/objects/effects/decals/Cleanable/tracks.dm
+++ b/code/game/objects/effects/decals/Cleanable/tracks.dm
@@ -113,8 +113,7 @@ var/global/list/image/fluidtrack_cache=list()
updated=1
dirs |= comingdir|realgoing
- if(islist(blood_DNA))
- blood_DNA |= DNA.Copy()
+ init_forensic_data().merge_blooddna(null,DNA)
if(updated)
update_icon()
diff --git a/code/game/objects/effects/gibs.dm b/code/game/objects/effects/gibs.dm
index b48fe45d46..5e2f58fdc1 100644
--- a/code/game/objects/effects/gibs.dm
+++ b/code/game/objects/effects/gibs.dm
@@ -46,11 +46,11 @@
gib.update_icon()
- gib.blood_DNA = list()
+ gib.init_forensic_data()
if(MobDNA)
- gib.blood_DNA[MobDNA.unique_enzymes] = MobDNA.b_type
+ gib.add_blooddna(MobDNA,null)
else if(istype(src, /obj/effect/gibspawner/human)) // Probably a monkey
- gib.blood_DNA["Non-human DNA"] = "A+"
+ gib.add_blooddna(null,null)
if(istype(location,/turf/))
var/list/directions = gibdirections[i]
if(directions.len)
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index 6707470f27..0d42558f58 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -736,12 +736,9 @@ var/list/global/slot_flags_enumeration = list(
//Make the blood_overlay have the proper color then apply it.
blood_overlay.color = blood_color
add_overlay(blood_overlay)
-
- //if this blood isn't already in the list, add it
if(istype(M))
- if(blood_DNA[M.dna.unique_enzymes])
- return 0 //already bloodied with this blood. Cannot add more.
- blood_DNA[M.dna.unique_enzymes] = M.dna.b_type
+ add_blooddna(M.dna,M)
+
return 1 //we applied blood to the item
GLOBAL_LIST_EMPTY(blood_overlays_by_type)
diff --git a/code/game/objects/items/devices/communicator/cartridge.dm b/code/game/objects/items/devices/communicator/cartridge.dm
index e4e767ca1a..5c41dc19df 100644
--- a/code/game/objects/items/devices/communicator/cartridge.dm
+++ b/code/game/objects/items/devices/communicator/cartridge.dm
@@ -340,7 +340,7 @@
internal_data["stat_display_active2"] = data2
if(loc)
var/obj/item/PDA = loc
- var/mob/user = PDA.fingerprintslast
+ var/mob/user = PDA.forensic_data?.get_lastprint()
log_admin("STATUS: [user] set status screen with [src]. Message: [data1] [data2]")
message_admins("STATUS: [user] set status screen with [src]. Message: [data1] [data2]")
diff --git a/code/game/objects/items/devices/transfer_valve.dm b/code/game/objects/items/devices/transfer_valve.dm
index 1d7e3a3810..4dcbbb1deb 100644
--- a/code/game/objects/items/devices/transfer_valve.dm
+++ b/code/game/objects/items/devices/transfer_valve.dm
@@ -200,12 +200,12 @@
if(attacher)
log_str += ADMIN_QUE(attacher)
- var/mob/mob = get_mob_by_key(src.fingerprintslast)
+ var/mob/mob = get_mob_by_key(forensic_data?.get_lastprint())
var/last_touch_info = ""
if(mob)
last_touch_info = ADMIN_QUE(mob)
- log_str += " Last touched by: [src.fingerprintslast][last_touch_info]"
+ log_str += " Last touched by: [forensic_data?.get_lastprint()][last_touch_info]"
GLOB.bombers += log_str
message_admins(log_str, 0, 1)
log_game(log_str)
diff --git a/code/game/objects/items/stacks/stack.dm b/code/game/objects/items/stacks/stack.dm
index b32e61863d..d324718d7f 100644
--- a/code/game/objects/items/stacks/stack.dm
+++ b/code/game/objects/items/stacks/stack.dm
@@ -334,11 +334,7 @@
S.add(transfer)
if (prob(transfer/orig_amount * 100))
transfer_fingerprints_to(S)
- if(blood_DNA)
- if(S.blood_DNA)
- S.blood_DNA |= blood_DNA
- else
- S.blood_DNA = blood_DNA.Copy()
+ transfer_blooddna_to(S)
return transfer
return 0
@@ -361,8 +357,7 @@
newstack.color = color
if (prob(transfer/orig_amount * 100))
transfer_fingerprints_to(newstack)
- if(blood_DNA)
- newstack.blood_DNA |= blood_DNA
+ transfer_blooddna_to(newstack)
return newstack
return null
diff --git a/code/game/objects/items/stacks/stack_vr.dm b/code/game/objects/items/stacks/stack_vr.dm
index 59c59ab452..4539464819 100644
--- a/code/game/objects/items/stacks/stack_vr.dm
+++ b/code/game/objects/items/stacks/stack_vr.dm
@@ -6,8 +6,7 @@
if(pulledby)
pulledby.start_pulling(S)
transfer_fingerprints_to(S)
- if(blood_DNA)
- S.blood_DNA |= blood_DNA
+ S.init_forensic_data().merge_blooddna(forensic_data)
use(transfer)
S.add(transfer)
diff --git a/code/game/objects/items/weapons/swords_axes_etc.dm b/code/game/objects/items/weapons/swords_axes_etc.dm
index c4e1a164b0..3457c08209 100644
--- a/code/game/objects/items/weapons/swords_axes_etc.dm
+++ b/code/game/objects/items/weapons/swords_axes_etc.dm
@@ -84,7 +84,7 @@
playsound(src, 'sound/weapons/empty.ogg', 50, 1)
add_fingerprint(user)
- if(blood_overlay && blood_DNA && (blood_DNA.len >= 1)) //updates blood overlay, if any
+ if(blood_overlay && forensic_data?.has_blooddna()) //updates blood overlay, if any
cut_overlays()
var/icon/I = new /icon(src.icon, src.icon_state)
diff --git a/code/game/objects/items/weapons/tanks/tanks.dm b/code/game/objects/items/weapons/tanks/tanks.dm
index 54fd14817d..887b4d2301 100644
--- a/code/game/objects/items/weapons/tanks/tanks.dm
+++ b/code/game/objects/items/weapons/tanks/tanks.dm
@@ -395,8 +395,8 @@ var/list/global/tank_gauge_cache = list()
if(pressure > TANK_FRAGMENT_PRESSURE)
if(integrity <= 7)
if(!istype(src.loc,/obj/item/transfer_valve))
- message_admins("Explosive tank rupture! last key to touch the tank was [src.fingerprintslast].")
- log_game("Explosive tank rupture! last key to touch the tank was [src.fingerprintslast].")
+ message_admins("Explosive tank rupture! last key to touch the tank was [forensic_data?.get_lastprint()].")
+ log_game("Explosive tank rupture! last key to touch the tank was [forensic_data?.get_lastprint()].")
//Give the gas a chance to build up more pressure through reacting
air_contents.react()
diff --git a/code/game/objects/structures/watercloset.dm b/code/game/objects/structures/watercloset.dm
index 0522801e8b..52043ffbe3 100644
--- a/code/game/objects/structures/watercloset.dm
+++ b/code/game/objects/structures/watercloset.dm
@@ -546,7 +546,6 @@
if(ishuman(user))
var/mob/living/carbon/human/H = user
- H.gunshot_residue = null
if(H.gloves)
H.gloves.wash(CLEAN_SCRUB)
H.update_inv_gloves()
@@ -559,7 +558,7 @@
H.bloody_hands = 0
H.germ_level = 0
H.hand_blood_color = null
- LAZYCLEARLIST(H.blood_DNA)
+ H.forensic_data?.wash(CLEAN_SCRUB)
H.update_bloodied()
else
user.wash(CLEAN_SCRUB)
diff --git a/code/game/turfs/simulated.dm b/code/game/turfs/simulated.dm
index 2c62c4b24c..e3c94d8ec5 100644
--- a/code/game/turfs/simulated.dm
+++ b/code/game/turfs/simulated.dm
@@ -118,8 +118,8 @@
var/obj/item/clothing/shoes/S = H.shoes
if(istype(S))
S.handle_movement(src,(H.m_intent == I_RUN ? 1 : 0), H) // handle_movement now needs to know who is moving, for inshoe steppies
- if(S.track_blood && S.blood_DNA)
- bloodDNA = S.blood_DNA
+ if(S.track_blood && S.forensic_data?.has_blooddna())
+ bloodDNA = S.forensic_data.get_blooddna()
bloodcolor=S.blood_color
S.track_blood--
else
@@ -193,12 +193,9 @@
if(istype(M))
for(var/obj/effect/decal/cleanable/blood/B in contents)
- if(!B.blood_DNA)
- B.blood_DNA = list()
- if(!B.blood_DNA[M.dna.unique_enzymes])
- B.blood_DNA[M.dna.unique_enzymes] = M.dna.b_type
- if(M.IsInfected())
- B.viruses = M.GetViruses()
+ var/fresh = B.init_forensic_data().add_blooddna(M.dna,M)
+ if(fresh && M.IsInfected())
+ B.viruses = M.GetViruses()
return 1 //we bloodied the floor
blood_splatter(src,M.get_blood(M.vessel),1)
return 1 //we bloodied the floor
@@ -208,7 +205,7 @@
/turf/simulated/proc/add_blood_floor(mob/living/carbon/M as mob)
if( istype(M, /mob/living/carbon/alien ))
var/obj/effect/decal/cleanable/blood/xeno/this = new /obj/effect/decal/cleanable/blood/xeno(src)
- this.blood_DNA["UNKNOWN BLOOD"] = "X*"
+ this.init_forensic_data().add_blooddna(M.dna,M)
else if( istype(M, /mob/living/silicon/robot ))
new /obj/effect/decal/cleanable/blood/oil(src)
else if(ishuman(M))
diff --git a/code/modules/admin/secrets/admin_secrets/list_dna.dm b/code/modules/admin/secrets/admin_secrets/list_dna.dm
index d0a483ab29..a4caf27b1d 100644
--- a/code/modules/admin/secrets/admin_secrets/list_dna.dm
+++ b/code/modules/admin/secrets/admin_secrets/list_dna.dm
@@ -9,6 +9,6 @@
dat += "
| Name | DNA | Blood Type |
"
for(var/mob/living/carbon/human/H in mob_list)
if(H.dna && H.ckey)
- dat += "| [H] | [H.dna.unique_enzymes] | [H.b_type] |
"
+ dat += "| [H] | [H.dna.unique_enzymes] | [H.dna ? H.dna.b_type : DEFAULT_BLOOD_TYPE] |
"
dat += "
"
user << browse("[dat]", "window=DNA;size=440x410")
diff --git a/code/modules/client/preference_setup/general/03_body.dm b/code/modules/client/preference_setup/general/03_body.dm
index 526062c94f..6be053ea02 100644
--- a/code/modules/client/preference_setup/general/03_body.dm
+++ b/code/modules/client/preference_setup/general/03_body.dm
@@ -175,7 +175,7 @@ var/const/preview_icons = 'icons/mob/human_races/preview.dmi'
character.h_style = pref.h_style
character.f_style = pref.f_style
character.grad_style= pref.grad_style
- character.b_type = pref.b_type
+ character.dna.b_type= pref.b_type
character.synth_color = pref.synth_color
character.synth_markings = pref.synth_markings
character.digitigrade = pref.digitigrade
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index 84c2cf0f0b..147e216d85 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -22,7 +22,7 @@ var/list/preferences_datums = list()
//character preferences
var/real_name //our character's name
var/nickname //our character's nickname
- var/b_type = "A+" //blood type (not-chooseable)
+ var/b_type = DEFAULT_BLOOD_TYPE //blood type (not-chooseable)
var/blood_reagents = "default" //blood restoration reagents
var/headset = 1 //headset type
var/backbag = 2 //backpack type
@@ -456,7 +456,7 @@ var/list/preferences_datums = list()
character.grad_style= grad_style
character.f_style = f_style
character.grad_style= grad_style
- character.b_type = b_type
+ character.dna.b_type= b_type
character.synth_color = synth_color
var/datum/preference/color/synth_color_color = GLOB.preference_entries[/datum/preference/color/human/synth_color]
diff --git a/code/modules/clothing/accessories/torch.dm b/code/modules/clothing/accessories/torch.dm
index 5caf8bce93..845410859f 100644
--- a/code/modules/clothing/accessories/torch.dm
+++ b/code/modules/clothing/accessories/torch.dm
@@ -224,7 +224,7 @@ badges
if(!istype(H))
return
var/religion = "Unset"
- desc = "[initial(desc)]\nName: [H.real_name] ([H.get_species()])\nReligion: [religion]\nBlood type: [H.b_type]"
+ desc = "[initial(desc)]\nName: [H.real_name] ([H.get_species()])\nReligion: [religion]\nBlood type: [H.dna ? H.dna.b_type : DEFAULT_BLOOD_TYPE]"
/obj/item/clothing/accessory/badge/solgov/representative
name = "representative's badge"
diff --git a/code/modules/clothing/clothing.dm b/code/modules/clothing/clothing.dm
index 1edad0ce44..9776d4b246 100644
--- a/code/modules/clothing/clothing.dm
+++ b/code/modules/clothing/clothing.dm
@@ -4,7 +4,6 @@
drop_sound = 'sound/items/drop/clothing.ogg'
pickup_sound = 'sound/items/pickup/clothing.ogg'
var/list/species_restricted = null //Only these species can wear this kit.
- var/gunshot_residue //Used by forensics.
var/list/accessories
var/list/valid_accessory_slots
@@ -32,12 +31,6 @@
/obj/item/clothing/proc/update_clothing_icon()
return
-// Aurora forensics port.
-/obj/item/clothing/wash()
- . = ..()
- gunshot_residue = null
-
-
/obj/item/clothing/Initialize(mapload)
. = ..()
if(starting_accessories)
@@ -53,7 +46,7 @@
/obj/item/clothing/update_icon()
cut_overlays() //This removes all the overlays on the sprite and then goes down a checklist adding them as required.
- if(blood_DNA)
+ if(forensic_data?.has_blooddna())
add_blood()
. = ..()
diff --git a/code/modules/clothing/clothing_icons.dm b/code/modules/clothing/clothing_icons.dm
index 3137128e95..95c9c257d1 100644
--- a/code/modules/clothing/clothing_icons.dm
+++ b/code/modules/clothing/clothing_icons.dm
@@ -4,7 +4,7 @@
standing.add_overlay(A.get_mob_overlay())
/obj/item/clothing/apply_blood(var/image/standing)
- if(blood_DNA && blood_sprite_state && ishuman(loc))
+ if(forensic_data?.has_blooddna() && blood_sprite_state && ishuman(loc))
var/mob/living/carbon/human/H = loc
var/image/bloodsies = image(icon = H.species.get_blood_mask(H), icon_state = blood_sprite_state)
bloodsies.color = blood_color
@@ -21,7 +21,7 @@
//SUIT: Blood state is slightly different
/obj/item/clothing/suit/apply_blood(var/image/standing)
- if(blood_DNA && blood_sprite_state && ishuman(loc))
+ if(forensic_data?.has_blooddna() && blood_sprite_state && ishuman(loc))
var/mob/living/carbon/human/H = loc
blood_sprite_state = "[blood_overlay_type]blood"
var/image/bloodsies = image(icon = H.species.get_blood_mask(H), icon_state = blood_sprite_state)
diff --git a/code/modules/clothing/masks/gasmask.dm b/code/modules/clothing/masks/gasmask.dm
index 18a5c62d6b..a93e7abafe 100644
--- a/code/modules/clothing/masks/gasmask.dm
+++ b/code/modules/clothing/masks/gasmask.dm
@@ -49,10 +49,9 @@
playsound(src, 'sound/items/Screwdriver.ogg', 50, 1)
user.drop_item(src)
var/obj/item/clothing/mask/gas/sechailer/N = new /obj/item/clothing/mask/gas/sechailer(src.loc)
- N.fingerprints = src.fingerprints
- N.fingerprintshidden = src.fingerprintshidden
- N.fingerprintslast = src.fingerprintslast
- N.suit_fibers = src.suit_fibers
+ transfer_blooddna_to(N)
+ transfer_fingerprints_to(N)
+ transfer_fibres_to(N)
N.hailer = I
I.loc = N
if(!isturf(N.loc))
diff --git a/code/modules/clothing/masks/hailer.dm b/code/modules/clothing/masks/hailer.dm
index b3d265b86c..389b53a062 100644
--- a/code/modules/clothing/masks/hailer.dm
+++ b/code/modules/clothing/masks/hailer.dm
@@ -132,10 +132,9 @@
else
var/obj/N = new /obj/item/clothing/mask/gas/half(src.loc)
playsound(src, 'sound/items/Screwdriver.ogg', 50, 1)
- N.fingerprints = src.fingerprints
- N.fingerprintshidden = src.fingerprintshidden
- N.fingerprintslast = src.fingerprintslast
- N.suit_fibers = src.suit_fibers
+ transfer_blooddna_to(N)
+ transfer_fingerprints_to(N)
+ transfer_fibres_to(N)
if(!isturf(N.loc))
user.put_in_hands(hailer)
user.put_in_hands(N)
diff --git a/code/modules/detectivework/forensics.dm b/code/modules/detectivework/forensics.dm
index 9e7e0d2c88..17219b8a77 100644
--- a/code/modules/detectivework/forensics.dm
+++ b/code/modules/detectivework/forensics.dm
@@ -8,37 +8,117 @@ var/const/FINGERPRINT_COMPLETE = 6
/proc/is_complete_print(var/print)
return stringpercent(print) <= FINGERPRINT_COMPLETE
-/atom/var/list/suit_fibers
+/// Forensics: Returns the object's forensic information datum. If none exists, it makes it.
+/atom/proc/init_forensic_data()
+ RETURN_TYPE(/datum/forensics_crime)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ if(!forensic_data)
+ forensic_data = new()
+ return forensic_data
-/atom/proc/add_fibers(mob/living/carbon/human/M)
- if(M.gloves && istype(M.gloves,/obj/item/clothing/gloves))
- var/obj/item/clothing/gloves/G = M.gloves
- if(G.transfer_blood) //bloodied gloves transfer blood to touched objects
- if(add_blood(G.bloody_hands_mob)) //only reduces the bloodiness of our gloves if the item wasn't already bloody
+/// Forensics: Handles most forensic investigation actions while touching an object. Including fingerprints, stray fibers from clothing, and bloody hands smearing objects. Returns true if a fingerprint was made.
+/atom/proc/add_fingerprint(mob/living/M as mob, ignoregloves = FALSE)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ if(isnull(M) || isAI(M) || isnull(M.key))
+ return FALSE
+
+ //Fibers from worn clothing get transfered along with fingerprints~
+ var/datum/forensics_crime/C = init_forensic_data()
+ C.add_fibres(M)
+
+ // bloodied gloves and hands transfer blood to touched objects. Blood does not transfer if we are already bloody.
+ if(!forensic_data?.has_blooddna())
+ var/mob/living/carbon/human/H = M
+ if(ishuman(M) && H.gloves && istype(H.gloves,/obj/item/clothing/gloves))
+ var/obj/item/clothing/gloves/G = H.gloves
+ if(G.transfer_blood)
+ forensic_data.merge_blooddna(G.forensic_data)
G.transfer_blood--
- else if(M.bloody_hands)
- if(add_blood(M.bloody_hands_mob))
+ else if(M.bloody_hands)
+ forensic_data.merge_blooddna(M.forensic_data)
M.bloody_hands--
- if(!suit_fibers) suit_fibers = list()
- var/fibertext
- var/item_multiplier = istype(src,/obj/item)?1.2:1
- var/suit_coverage = 0
- if(M.wear_suit)
- fibertext = "Material from \a [M.wear_suit]."
- if(prob(10*item_multiplier) && !(fibertext in suit_fibers))
- suit_fibers += fibertext
- suit_coverage = M.wear_suit.body_parts_covered
+ //He has no prints!
+ if(mFingerprints in M.mutations)
+ if(C.get_lastprint() != M.key)
+ C.add_hiddenprints(M)
+ C.set_lastprint(M.key)
+ return FALSE
- if(M.w_uniform && (M.w_uniform.body_parts_covered & ~suit_coverage))
- fibertext = "Fibers from \a [M.w_uniform]."
- if(prob(15*item_multiplier) && !(fibertext in suit_fibers))
- suit_fibers += fibertext
+ //Smudge up dem prints some if it's just a mob
+ if(!ishuman(M))
+ if(C.get_lastprint() != M.key)
+ C.add_hiddenprints(M)
+ C.set_lastprint(M.key)
+ return TRUE
+
+ var/mob/living/carbon/human/H = M
+
+ //Now, deal with gloves.
+ if (H.gloves && H.gloves != src)
+ C.add_hiddenprints(M)
+ H.gloves.add_fingerprint(M,ignoregloves)
+
+ //Deal with gloves the pass finger/palm prints.
+ if(!ignoregloves)
+ if(H.gloves && H.gloves != src)
+ if(istype(H.gloves, /obj/item/clothing/gloves))
+ var/obj/item/clothing/gloves/G = H.gloves
+ if(!prob(G.fingerprint_chance))
+ return 0
+
+ return C.add_prints(H)
+
+/// Forensics: Adds an admin investigation fingerprint, even if no actual fingerprints are made. Used even if the action is done with a weapon as a way of logging actions for admins.
+/atom/proc/add_hiddenprint(mob/living/M as mob)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ if(isnull(M))
+ return
+ if(isnull(M.key))
+ return
+ init_forensic_data().add_hiddenprints(M)
+
+/// Forensics: Adds blood dna to an object, this also usually gives the object a bloody overlay, but that is handled by the object itself. Returns true if this is the first time this dna is being added to this object.
+/atom/proc/add_blooddna(var/datum/dna/dna_data,var/mob/M)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ return init_forensic_data().add_blooddna(dna_data,M)
+
+/// Forensics: Adds blood dna to an object, this version uses an organ's more restricted dna datum, but it still has all the information needed. Returns true if this is the first time this dna is being added to this object.
+/atom/proc/add_blooddna_organ(var/datum/organ_data/dna_data)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ return init_forensic_data().add_blooddna_organ(dna_data)
+
+/// Forensics: Adds fibres from suits or gloves
+/atom/proc/add_fibres(mob/living/carbon/human/M)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ init_forensic_data().add_fibres(M)
+
+/// Forensics: Transfers both our normal and hidden fingerprints to the specified object, handles the forensics datum creation itself.
+/atom/proc/transfer_fingerprints_to(var/atom/transfer_to)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ if(!forensic_data)
+ return
+ var/datum/forensics_crime/C = transfer_to.init_forensic_data()
+ C.merge_prints(forensic_data)
+ C.merge_hiddenprints(forensic_data)
+
+/// Forensics: Transfers our blood dna to the specified object, handles the forensics datum creation itself.
+/atom/proc/transfer_blooddna_to(var/atom/transfer_to)
+ if(!forensic_data)
+ return
+ transfer_to.init_forensic_data().merge_blooddna(forensic_data)
+
+/// Forensics: Transfers our stray fibers to the specified object, handles the forensics datum creation itself.
+/atom/proc/transfer_fibres_to(var/atom/transfer_to)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ if(!forensic_data)
+ return
+ transfer_to.init_forensic_data().merge_fibres(forensic_data)
+
+/// Forensics: Adds gunshot residue from firing boolets
+/atom/proc/add_gunshotresidue(var/obj/item/ammo_casing/shell)
+ init_forensic_data().add_gunshotresidue(shell.caliber)
- if(M.gloves && (M.gloves.body_parts_covered & ~suit_coverage))
- fibertext = "Material from a pair of [M.gloves.name]."
- if(prob(20*item_multiplier) && !(fibertext in suit_fibers))
- suit_fibers += "Material from a pair of [M.gloves.name]."
/datum/data/record/forensic
name = "forensic data"
@@ -48,9 +128,9 @@ var/const/FINGERPRINT_COMPLETE = 6
uid = "\ref [A]"
fields["name"] = sanitize(A.name)
fields["area"] = sanitize("[get_area(A)]")
- fields["fprints"] = A.fingerprints ? A.fingerprints.Copy() : list()
- fields["fibers"] = A.suit_fibers ? A.suit_fibers.Copy() : list()
- fields["blood"] = A.blood_DNA ? A.blood_DNA.Copy() : list()
+ fields["fprints"] = A.forensic_data?.get_prints().Copy()
+ fields["fibers"] = A.forensic_data?.get_fibres().Copy()
+ fields["blood"] = A.forensic_data?.get_blooddna().Copy()
fields["time"] = world.time
/datum/data/record/forensic/proc/merge(var/datum/data/record/other)
diff --git a/code/modules/detectivework/tools/rag.dm b/code/modules/detectivework/tools/rag.dm
index 667652e769..b565fcc4fe 100644
--- a/code/modules/detectivework/tools/rag.dm
+++ b/code/modules/detectivework/tools/rag.dm
@@ -1,6 +1,5 @@
/mob
var/bloody_hands = 0
- var/mob/living/carbon/human/bloody_hands_mob
var/track_blood = 0
var/list/feet_blood_DNA
var/track_blood_type
@@ -8,7 +7,6 @@
/obj/item/clothing/gloves
var/transfer_blood = 0
- var/mob/living/carbon/human/bloody_hands_mob
/obj/item/clothing/shoes/
var/track_blood = 0
diff --git a/code/modules/detectivework/tools/sample_kits.dm b/code/modules/detectivework/tools/sample_kits.dm
index d39cfa5cad..7d961f5317 100644
--- a/code/modules/detectivework/tools/sample_kits.dm
+++ b/code/modules/detectivework/tools/sample_kits.dm
@@ -6,7 +6,7 @@
/obj/item/sample/Initialize(mapload, var/atom/supplied)
. = ..()
- if(supplied)
+ if(supplied && supplied.forensic_data)
copy_evidence(supplied)
name = "[initial(name)] (\the [supplied])"
@@ -16,9 +16,10 @@
icon_state = "fingerprint1"
/obj/item/sample/proc/copy_evidence(var/atom/supplied)
- if(supplied.suit_fibers && supplied.suit_fibers.len)
- evidence = supplied.suit_fibers.Copy()
- supplied.suit_fibers.Cut()
+ var/list/fibre_data = supplied.forensic_data.get_fibres()
+ if(fibre_data && fibre_data.len)
+ evidence = fibre_data.Copy()
+ supplied.forensic_data.clear_fibres()
/obj/item/sample/proc/merge_evidence(var/obj/item/sample/supplied, var/mob/user)
if(!supplied.evidence || !supplied.evidence.len)
@@ -116,10 +117,11 @@
return 0
/obj/item/sample/print/copy_evidence(var/atom/supplied)
- if(supplied.fingerprints && supplied.fingerprints.len)
- for(var/print in supplied.fingerprints)
- evidence[print] = supplied.fingerprints[print]
- supplied.fingerprints.Cut()
+ var/list/print_data = supplied.forensic_data.get_prints()
+ if(print_data && print_data.len)
+ for(var/print in print_data)
+ evidence[print] = print_data[print]
+ supplied.forensic_data.clear_prints()
/obj/item/forensics/sample_kit
name = "fiber collection kit"
@@ -130,7 +132,7 @@
var/evidence_path = /obj/item/sample/fibers
/obj/item/forensics/sample_kit/proc/can_take_sample(var/mob/user, var/atom/supplied)
- return (supplied.suit_fibers && supplied.suit_fibers.len)
+ return supplied.forensic_data?.has_fibres()
/obj/item/forensics/sample_kit/proc/take_sample(var/mob/user, var/atom/supplied)
var/obj/item/sample/S = new evidence_path(get_turf(user), supplied)
@@ -155,4 +157,4 @@
evidence_path = /obj/item/sample/print
/obj/item/forensics/sample_kit/powder/can_take_sample(var/mob/user, var/atom/supplied)
- return (supplied.fingerprints && supplied.fingerprints.len)
+ return supplied.forensic_data?.has_prints()
diff --git a/code/modules/detectivework/tools/scanner.dm b/code/modules/detectivework/tools/scanner.dm
index a4a263938d..57b525c074 100644
--- a/code/modules/detectivework/tools/scanner.dm
+++ b/code/modules/detectivework/tools/scanner.dm
@@ -34,11 +34,12 @@
to_chat(user, span_notice("Done printing."))
// to_chat(user, span_notice("[M]'s Fingerprints: [md5(M.dna.uni_identity)]"))
- if(reveal_blood && M.blood_DNA && M.blood_DNA.len)
+ if(reveal_blood && M.forensic_data?.has_blooddna())
to_chat(user, span_notice("Blood found on [M]. Analysing..."))
spawn(15)
- for(var/blood in M.blood_DNA)
- to_chat(user, span_notice("Blood type: [M.blood_DNA[blood]]\nDNA: [blood]"))
+ var/list/blooddna = M.forensic_data.get_blooddna()
+ for(var/blood in blooddna)
+ to_chat(user, span_notice("Blood type: [blooddna[blood]]\nDNA: [blood]"))
return
/obj/item/detective_scanner/afterattack(atom/A as obj|turf, mob/user, proximity)
@@ -67,7 +68,7 @@
return 0
//General
- if ((!A.fingerprints || !A.fingerprints.len) && !A.suit_fibers && !A.blood_DNA)
+ if (!A.forensic_data?.has_prints() && !A.forensic_data?.has_fibres() && !A.forensic_data.has_blooddna())
user.visible_message("\The [user] scans \the [A] with \a [src], the air around [user.gender == MALE ? "him" : "her"] humming[prob(70) ? " gently." : "."]" ,\
span_warning("Unable to locate any fingerprints, materials, fibers, or blood on [A]!"),\
"You hear a faint hum of electrical equipment.")
@@ -80,14 +81,15 @@
return
//PRINTS
- if(A.fingerprints && A.fingerprints.len)
- to_chat(user, span_notice("Isolated [A.fingerprints.len] fingerprints:"))
+ if(A.forensic_data?.has_prints())
+ to_chat(user, span_notice("Isolated [A.forensic_data?.get_prints().len] fingerprints:"))
if(!reveal_incompletes)
to_chat(user, span_warning("Rapid Analysis Imperfect: Scan samples with H.R.F.S. equipment to determine nature of incomplete prints."))
var/list/complete_prints = list()
var/list/incomplete_prints = list()
- for(var/i in A.fingerprints)
- var/print = A.fingerprints[i]
+ var/list/print_data = A.forensic_data?.get_prints()
+ for(var/i in print_data)
+ var/print = print_data[i]
if(stringpercent(print) <= FINGERPRINT_COMPLETE)
complete_prints += print
else
@@ -107,21 +109,22 @@
//FIBERS
- if(A.suit_fibers && A.suit_fibers.len)
+ if(A.forensic_data?.has_fibres())
to_chat(user,span_notice("Fibers/Materials detected.[reveal_fibers ? " Analysing..." : " Acquisition of fibers for H.R.F.S. analysis advised."]"))
flick("[icon_state]1",src)
if(reveal_fibers && do_after(user, 5 SECONDS))
to_chat(user, span_notice("Apparel samples scanned:"))
- for(var/sample in A.suit_fibers)
+ for(var/sample in A.forensic_data.get_fibres())
to_chat(user, " - " + span_notice("[sample]"))
//Blood
- if (A.blood_DNA && A.blood_DNA.len)
+ if (A.forensic_data?.has_blooddna())
to_chat(user, span_notice("Blood detected.[reveal_blood ? " Analysing..." : " Acquisition of swab for H.R.F.S. analysis advised."]"))
if(reveal_blood && do_after(user, 5 SECONDS))
flick("[icon_state]1",src)
- for(var/blood in A.blood_DNA)
- to_chat(user, "Blood type: " + span_warning("[A.blood_DNA[blood]]") + " DNA: " + span_warning("[blood]"))
+ var/list/blood_data = A.forensic_data.get_blooddna()
+ for(var/blood in blood_data)
+ to_chat(user, "Blood type: " + span_warning("[blood_data[blood]]") + " DNA: " + span_warning("[blood]"))
user.visible_message("\The [user] scans \the [A] with \a [src], the air around [user.gender == MALE ? "him" : "her"] humming[prob(70) ? " gently." : "."]" ,\
span_notice("You finish scanning \the [A]."),\
diff --git a/code/modules/detectivework/tools/swabs.dm b/code/modules/detectivework/tools/swabs.dm
index 969954882d..215ecec751 100644
--- a/code/modules/detectivework/tools/swabs.dm
+++ b/code/modules/detectivework/tools/swabs.dm
@@ -59,7 +59,7 @@
return
user.visible_message("[user] swabs [H]'s palm for a sample.")
sample_type = "GSR"
- gsr = H.gunshot_residue
+ gsr = H.forensic_data?.get_gunshotresidue()
else
return
@@ -80,7 +80,7 @@
add_fingerprint(user)
var/list/choices = list()
- if(A.blood_DNA)
+ if(A.forensic_data?.has_blooddna())
choices |= "Blood"
if(istype(A, /obj/item/clothing))
choices |= "Gunshot Residue"
@@ -99,16 +99,16 @@
var/sample_type
if(choice == "Blood")
- if(!A.blood_DNA || !A.blood_DNA.len) return
- dna = A.blood_DNA.Copy()
+ if(!A.forensic_data?.has_blooddna()) return
+ dna = A.forensic_data?.get_blooddna().Copy()
sample_type = "blood"
else if(choice == "Gunshot Residue")
var/obj/item/clothing/B = A
- if(!istype(B) || !B.gunshot_residue)
+ if(!istype(B) || !B.forensic_data?.get_gunshotresidue())
to_chat(user, span_warning("There is no residue on \the [A]."))
return
- gsr = B.gunshot_residue
+ gsr = B.forensic_data?.get_gunshotresidue()
sample_type = "residue"
if(sample_type)
diff --git a/code/modules/integrated_electronics/subtypes/manipulation.dm b/code/modules/integrated_electronics/subtypes/manipulation.dm
index 0ceff2871e..d4620f8ef4 100644
--- a/code/modules/integrated_electronics/subtypes/manipulation.dm
+++ b/code/modules/integrated_electronics/subtypes/manipulation.dm
@@ -183,7 +183,7 @@
attached_grenade.det_time = between(1, detonation_time.data, 12) SECONDS
attached_grenade.activate()
var/atom/holder = loc
- log_and_message_admins("activated a grenade assembly. Last touches: Assembly: [holder.fingerprintslast] Circuit: [fingerprintslast] Grenade: [attached_grenade.fingerprintslast]")
+ log_and_message_admins("activated a grenade assembly. Last touches: Assembly: [holder.forensic_data?.get_lastprint()] Circuit: [forensic_data?.get_lastprint()] Grenade: [attached_grenade.forensic_data?.get_lastprint()]")
// These procs do not relocate the grenade, that's the callers responsibility
/obj/item/integrated_circuit/manipulation/grenade/proc/attach_grenade(var/obj/item/grenade/G)
diff --git a/code/modules/mob/_modifiers/traits_phobias.dm b/code/modules/mob/_modifiers/traits_phobias.dm
index f080fc4992..71aee1602c 100644
--- a/code/modules/mob/_modifiers/traits_phobias.dm
+++ b/code/modules/mob/_modifiers/traits_phobias.dm
@@ -140,7 +140,7 @@
clothing_slots += list(H.l_store, H.r_store)
for(var/obj/item/clothing/C in clothing_slots)
- if(C.blood_DNA && C.blood_color && C.blood_color != SYNTH_BLOOD_COLOUR)
+ if(C.forensic_data?.has_blooddna() && C.blood_color && C.blood_color != SYNTH_BLOOD_COLOUR)
human_blood_fear_amount += 1
// This is divided, since humans can wear so many items at once.
@@ -150,7 +150,7 @@
// Bloody objects are also bad.
if(istype(thing, /obj))
var/obj/O = thing
- if(O.blood_DNA && O.blood_color && O.blood_color != SYNTH_BLOOD_COLOUR)
+ if(O.forensic_data?.has_blooddna() && O.blood_color && O.blood_color != SYNTH_BLOOD_COLOUR)
fear_amount++
return fear_amount
diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm
index b43a5da4fe..8943f860a5 100644
--- a/code/modules/mob/living/carbon/human/examine.dm
+++ b/code/modules/mob/living/carbon/human/examine.dm
@@ -123,14 +123,14 @@
accessory_descs += "\a [A]"
tie_msg += " [lowertext(english_list(accessory_descs))]."
- if(w_uniform.blood_DNA)
+ if(w_uniform.forensic_data?.has_blooddna())
msg += span_warning("[T.He] [T.is] wearing [icon2html(w_uniform,user.client)] [w_uniform.gender==PLURAL?"some":"a"] [(w_uniform.blood_color != "#030303") ? "blood" : "oil"]-stained [w_uniform.name]![tie_msg]")
else
msg += "[T.He] [T.is] wearing [icon2html(w_uniform,user.client)] \a [w_uniform].[tie_msg]"
//head
if(head && !(skip_gear & EXAMINE_SKIPHELMET) && head.show_examine)
- if(head.blood_DNA)
+ if(head.forensic_data?.has_blooddna())
msg += span_warning("[T.He] [T.is] wearing [icon2html(head,user.client)] [head.gender==PLURAL?"some":"a"] [(head.blood_color != "#030303") ? "blood" : "oil"]-stained [head.name] on [T.his] head!")
else
msg += "[T.He] [T.is] wearing [icon2html(head,user.client)] \a [head] on [T.his] head."
@@ -147,35 +147,35 @@
accessory_descs += "\a [accessory]"
tie_msg += " [lowertext(english_list(accessory_descs))]."
- if(wear_suit.blood_DNA)
+ if(wear_suit.forensic_data?.has_blooddna())
msg += span_warning("[T.He] [T.is] wearing [icon2html(wear_suit,user.client)] [wear_suit.gender==PLURAL?"some":"a"] [(wear_suit.blood_color != "#030303") ? "blood" : "oil"]-stained [wear_suit.name]![tie_msg]")
else
msg += "[T.He] [T.is] wearing [icon2html(wear_suit,user.client)] \a [wear_suit].[tie_msg]"
//suit/armour storage
if(s_store && !(skip_gear & EXAMINE_SKIPSUITSTORAGE) && s_store.show_examine)
- if(s_store.blood_DNA)
+ if(s_store.forensic_data?.has_blooddna())
msg += span_warning("[T.He] [T.is] carrying [icon2html(s_store,user.client)] [s_store.gender==PLURAL?"some":"a"] [(s_store.blood_color != "#030303") ? "blood" : "oil"]-stained [s_store.name] on [T.his] [wear_suit.name]!")
else
msg += "[T.He] [T.is] carrying [icon2html(s_store,user.client)] \a [s_store] on [T.his] [wear_suit.name]."
//back
if(back && !(skip_gear & EXAMINE_SKIPBACKPACK) && back.show_examine)
- if(back.blood_DNA)
+ if(back.forensic_data?.has_blooddna())
msg += span_warning("[T.He] [T.has] [icon2html(back,user.client)] [back.gender==PLURAL?"some":"a"] [(back.blood_color != "#030303") ? "blood" : "oil"]-stained [back] on [T.his] back.")
else
msg += "[T.He] [T.has] [icon2html(back,user.client)] \a [back] on [T.his] back."
//left hand
if(l_hand && l_hand.show_examine)
- if(l_hand.blood_DNA)
+ if(l_hand.forensic_data?.has_blooddna())
msg += span_warning("[T.He] [T.is] holding [icon2html(l_hand,user.client)] [l_hand.gender==PLURAL?"some":"a"] [(l_hand.blood_color != "#030303") ? "blood" : "oil"]-stained [l_hand.name] in [T.his] left hand!")
else
msg += "[T.He] [T.is] holding [icon2html(l_hand,user.client)] \a [l_hand] in [T.his] left hand."
//right hand
if(r_hand && r_hand.show_examine)
- if(r_hand.blood_DNA)
+ if(r_hand.forensic_data?.has_blooddna())
msg += span_warning("[T.He] [T.is] holding [icon2html(r_hand,user.client)] [r_hand.gender==PLURAL?"some":"a"] [(r_hand.blood_color != "#030303") ? "blood" : "oil"]-stained [r_hand.name] in [T.his] right hand!")
else
msg += "[T.He] [T.is] holding [icon2html(r_hand,user.client)] \a [r_hand] in [T.his] right hand."
@@ -192,12 +192,12 @@
accessory_descs += "\a [A]"
gloves_acc_msg += " [lowertext(english_list(accessory_descs))]."
- if(gloves.blood_DNA)
+ if(gloves.forensic_data?.has_blooddna())
msg += span_warning("[T.He] [T.has] [icon2html(gloves,user.client)] [gloves.gender==PLURAL?"some":"a"] [(gloves.blood_color != "#030303") ? "blood" : "oil"]-stained [gloves.name] on [T.his] hands![gloves_acc_msg]")
else
msg += "[T.He] [T.has] [icon2html(gloves,user.client)] \a [gloves] on [T.his] hands.[gloves_acc_msg]"
- else if(blood_DNA && !(skip_body & EXAMINE_SKIPHANDS))
+ else if(forensic_data?.has_blooddna() && !(skip_body & EXAMINE_SKIPHANDS))
msg += span_warning("[T.He] [T.has] [(hand_blood_color != SYNTH_BLOOD_COLOUR) ? "blood" : "oil"]-stained hands!")
//handcuffed?
@@ -213,14 +213,14 @@
//belt
if(belt && !(skip_gear & EXAMINE_SKIPBELT) && belt.show_examine)
- if(belt.blood_DNA)
+ if(belt.forensic_data?.has_blooddna())
msg += span_warning("[T.He] [T.has] [icon2html(belt,user.client)] [belt.gender==PLURAL?"some":"a"] [(belt.blood_color != "#030303") ? "blood" : "oil"]-stained [belt.name] about [T.his] waist!")
else
msg += "[T.He] [T.has] [icon2html(belt,user.client)] \a [belt] about [T.his] waist."
//shoes
if(shoes && !(skip_gear & EXAMINE_SKIPSHOES) && shoes.show_examine)
- if(shoes.blood_DNA)
+ if(shoes.forensic_data?.has_blooddna())
msg += span_warning("[T.He] [T.is] wearing [icon2html(shoes,user.client)] [shoes.gender==PLURAL?"some":"a"] [(shoes.blood_color != "#030303") ? "blood" : "oil"]-stained [shoes.name] on [T.his] feet!")
else
msg += "[T.He] [T.is] wearing [icon2html(shoes,user.client)] \a [shoes] on [T.his] feet."
@@ -233,14 +233,14 @@
if(istype(wear_mask, /obj/item/grenade) && check_has_mouth())
descriptor = "in [T.his] mouth"
- if(wear_mask.blood_DNA)
+ if(wear_mask.forensic_data?.has_blooddna())
msg += span_warning("[T.He] [T.has] [icon2html(wear_mask,user.client)] [wear_mask.gender==PLURAL?"some":"a"] [(wear_mask.blood_color != "#030303") ? "blood" : "oil"]-stained [wear_mask.name] [descriptor]!")
else
msg += "[T.He] [T.has] [icon2html(wear_mask,user.client)] \a [wear_mask] [descriptor]."
//eyes
if(glasses && !(skip_gear & EXAMINE_SKIPEYEWEAR) && glasses.show_examine)
- if(glasses.blood_DNA)
+ if(glasses.forensic_data?.has_blooddna())
msg += span_warning("[T.He] [T.has] [icon2html(glasses,user.client)] [glasses.gender==PLURAL?"some":"a"] [(glasses.blood_color != "#030303") ? "blood" : "oil"]-stained [glasses] covering [T.his] eyes!")
else
msg += "[T.He] [T.has] [icon2html(glasses,user.client)] \a [glasses] covering [T.his] eyes."
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index a1291ceb72..1ccc48350e 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -1137,8 +1137,7 @@
return 0
//if this blood isn't already in the list, add it
if(istype(M))
- if(!blood_DNA[M.dna.unique_enzymes])
- blood_DNA[M.dna.unique_enzymes] = M.dna.b_type
+ add_blooddna(M.dna,M)
hand_blood_color = blood_color
update_bloodied()
add_verb(src, /mob/living/carbon/human/proc/bloody_doodle)
@@ -1152,8 +1151,6 @@
/mob/living/carbon/human/wash(clean_types)
. = ..()
- gunshot_residue = null
-
//Always do hands (or whatever's on our hands)
if(gloves)
gloves.wash(clean_types)
diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm
index 73553d27c3..08dff27628 100644
--- a/code/modules/mob/living/carbon/human/human_defense.dm
+++ b/code/modules/mob/living/carbon/human/human_defense.dm
@@ -577,11 +577,9 @@ emp_act
var/obj/item/clothing/gloves/gl = gloves
gl.add_blood(source)
gl.transfer_blood = amount
- gl.bloody_hands_mob = source
else
add_blood(source)
bloody_hands = amount
- bloody_hands_mob = source
update_inv_gloves() //updates on-mob overlays for bloody hands and/or bloody gloves
/mob/living/carbon/human/proc/bloody_body(var/mob/living/source)
diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm
index 700bbeb54a..87525e688f 100644
--- a/code/modules/mob/living/carbon/human/human_defines.dm
+++ b/code/modules/mob/living/carbon/human/human_defines.dm
@@ -46,7 +46,6 @@
var/age = 30 //Player's age (pure fluff)
- var/b_type = "A+" //Player's bloodtype
var/datum/robolimb/synthetic //If they are a synthetic (aka synthetic torso). Also holds the datum for the type of robolimb.
var/list/all_underwear = list()
@@ -92,7 +91,6 @@
var/hand_blood_color
var/list/flavor_texts = list()
- var/gunshot_residue
var/pulling_punches // Are you trying not to hurt your opponent?
var/robolimb_count = 0 // Total number of external robot parts.
var/robobody_count = 0 // Counts torso, groin, and head, if they're robotic
diff --git a/code/modules/mob/living/carbon/human/human_helpers_vr.dm b/code/modules/mob/living/carbon/human/human_helpers_vr.dm
index b3a42de43b..1776176137 100644
--- a/code/modules/mob/living/carbon/human/human_helpers_vr.dm
+++ b/code/modules/mob/living/carbon/human/human_helpers_vr.dm
@@ -161,7 +161,7 @@ var/static/icon/ingame_hud_med_vr = icon('icons/mob/hud_med_vr.dmi')
grad_style = character.grad_style
f_style = character.f_style
grad_style = character.grad_style
- b_type = character.b_type
+ dna?.b_type = character.dna ? character.dna.b_type : DEFAULT_BLOOD_TYPE
synth_color = character.synth_color
r_synth = character.r_synth
g_synth = character.g_synth
diff --git a/code/modules/mob/living/carbon/human/species/shadekin/shadekin_abilities.dm b/code/modules/mob/living/carbon/human/species/shadekin/shadekin_abilities.dm
index ead6bf5bfe..076fbdf98d 100644
--- a/code/modules/mob/living/carbon/human/species/shadekin/shadekin_abilities.dm
+++ b/code/modules/mob/living/carbon/human/species/shadekin/shadekin_abilities.dm
@@ -464,8 +464,8 @@
return FALSE
- log_admin("[key_name_admin(src)] was stunned out of phase at [T.x],[T.y],[T.z] by [dephaser.name], last touched by [dephaser.fingerprintslast].")
- message_admins("[key_name_admin(src)] was stunned out of phase at [T.x],[T.y],[T.z] by [dephaser.name], last touched by [dephaser.fingerprintslast]. (JMP)", 1)
+ log_admin("[key_name_admin(src)] was stunned out of phase at [T.x],[T.y],[T.z] by [dephaser.name], last touched by [dephaser.forensic_data?.get_lastprint()].")
+ message_admins("[key_name_admin(src)] was stunned out of phase at [T.x],[T.y],[T.z] by [dephaser.name], last touched by [dephaser.forensic_data?.get_lastprint()]. (JMP)", 1)
// start the dephase
phase_in(T)
shadekin_adjust_energy(-20) // loss of energy for the interception
diff --git a/code/modules/mob/living/carbon/human/species/station/prometheans.dm b/code/modules/mob/living/carbon/human/species/station/prometheans.dm
index 55ce9aed00..9aa6085f5d 100644
--- a/code/modules/mob/living/carbon/human/species/station/prometheans.dm
+++ b/code/modules/mob/living/carbon/human/species/station/prometheans.dm
@@ -238,8 +238,7 @@ var/datum/species/shapeshifter/promethean/prometheans
H.feet_blood_color = null
H.adjust_nutrition(rand(3, 10))
if(H.bloody_hands)
- LAZYCLEARLIST(H.blood_DNA)
- H.blood_DNA = null
+ H.forensic_data?.clear_blooddna()
H.hand_blood_color = null
H.bloody_hands = 0
H.adjust_nutrition(rand(3, 10))
diff --git a/code/modules/mob/living/carbon/human/update_icons.dm b/code/modules/mob/living/carbon/human/update_icons.dm
index 8c0263ac2b..d1c6bcbb52 100644
--- a/code/modules/mob/living/carbon/human/update_icons.dm
+++ b/code/modules/mob/living/carbon/human/update_icons.dm
@@ -394,13 +394,14 @@ GLOBAL_LIST_EMPTY(damage_icon_parts) //see UpdateDamageIcon()
return
remove_layer(BLOOD_LAYER)
- if(!blood_DNA && !feet_blood_DNA)
+ var/bloody_mess = forensic_data?.get_blooddna()
+ if(!bloody_mess && !feet_blood_DNA)
return
var/image/both = image(icon = 'icons/effects/effects.dmi', icon_state = "nothing", layer = BODY_LAYER+BLOOD_LAYER)
//Bloody hands
- if(blood_DNA)
+ if(bloody_mess)
var/image/bloodsies = image(icon = species.get_blood_mask(src), icon_state = "bloodyhands", layer = BODY_LAYER+BLOOD_LAYER)
bloodsies.color = hand_blood_color
both.add_overlay(bloodsies)
diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/vox.dm b/code/modules/mob/living/simple_mob/subtypes/animal/vox.dm
index 4f2546dcc9..2a034f191d 100644
--- a/code/modules/mob/living/simple_mob/subtypes/animal/vox.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/animal/vox.dm
@@ -83,7 +83,7 @@
to_chat(O, span_warning("[src] launches a razor-sharp quill at [target]!"))
var/obj/item/arrow/quill/Q = new(loc)
- Q.fingerprintslast = src.ckey
+ Q.add_fingerprint(ckey)
Q.throw_at(target,10,30)
quills--
diff --git a/code/modules/organs/blood.dm b/code/modules/organs/blood.dm
index e93f90f9b4..60967c1fda 100644
--- a/code/modules/organs/blood.dm
+++ b/code/modules/organs/blood.dm
@@ -468,11 +468,12 @@ var/const/CE_STABLE_THRESHOLD = 0.5
// Update blood information.
if(source.data["blood_DNA"])
- B.blood_DNA = list()
+ var/list/new_data = list()
if(source.data["blood_type"])
- B.blood_DNA[source.data["blood_DNA"]] = source.data["blood_type"]
+ new_data[source.data["blood_DNA"]] = source.data["blood_type"]
else
- B.blood_DNA[source.data["blood_DNA"]] = "O+"
+ new_data[source.data["blood_DNA"]] = "O+"
+ B.init_forensic_data().merge_blooddna(null,new_data)
// Update virus information.
if(source.data["viruses"])
diff --git a/code/modules/organs/data.dm b/code/modules/organs/data.dm
index 7b617ab5bd..05c7c6b9fa 100644
--- a/code/modules/organs/data.dm
+++ b/code/modules/organs/data.dm
@@ -27,6 +27,7 @@
self_clear = TRUE
// Setup cached dna data, as storing the entire DNA cloned is horrifically laggy
+ b_type = dna.b_type
unique_enzymes = dna.unique_enzymes
body_gender = dna.GetUIState(DNA_UI_GENDER)
if(!isnull(dna.GetUIValue(DNA_UI_SKIN_TONE)))
diff --git a/code/modules/organs/organ.dm b/code/modules/organs/organ.dm
index d4fcc7570c..4780fba9ac 100644
--- a/code/modules/organs/organ.dm
+++ b/code/modules/organs/organ.dm
@@ -105,9 +105,7 @@ var/list/organ_cache = list()
E.internal_organs = list()
E.internal_organs |= src
if(data)
- if(!blood_DNA)
- blood_DNA = list()
- blood_DNA[data.unique_enzymes] = data.b_type
+ add_blooddna_organ(data)
else
data.setup_from_species(GLOB.all_species["Human"])
@@ -131,9 +129,8 @@ var/list/organ_cache = list()
/obj/item/organ/proc/set_dna(var/datum/dna/new_dna)
if(new_dna)
data.setup_from_dna(new_dna)
- if(blood_DNA)
- blood_DNA.Cut()
- blood_DNA[data.unique_enzymes] = data.b_type
+ forensic_data?.clear_blooddna()
+ add_blooddna_organ(data)
/obj/item/organ/proc/die()
if(robotic < ORGAN_ROBOT)
@@ -460,10 +457,8 @@ var/list/organ_cache = list()
// Pass over the blood.
reagents.trans_to(O, reagents.total_volume)
-
- if(fingerprints) O.fingerprints = fingerprints.Copy()
- if(fingerprintshidden) O.fingerprintshidden = fingerprintshidden.Copy()
- if(fingerprintslast) O.fingerprintslast = fingerprintslast
+ transfer_fingerprints_to(O)
+ transfer_blooddna_to(O)
user.put_in_active_hand(O)
qdel(src)
diff --git a/code/modules/pda/cart.dm b/code/modules/pda/cart.dm
index 716f34468f..4054717d1f 100644
--- a/code/modules/pda/cart.dm
+++ b/code/modules/pda/cart.dm
@@ -293,7 +293,7 @@ var/list/civilian_cartridges = list(
status_signal.data["msg2"] = data2
if(loc)
var/obj/item/PDA = loc
- var/mob/user = PDA.fingerprintslast
+ var/mob/user = PDA.forensic_data?.get_lastprint()
log_admin("STATUS: [user] set status screen with [PDA]. Message: [data1] [data2]")
message_admins("STATUS: [user] set status screen with [PDA]. Message: [data1] [data2]")
diff --git a/code/modules/pda/cart_apps.dm b/code/modules/pda/cart_apps.dm
index 37e4784814..5e70bbf8ac 100644
--- a/code/modules/pda/cart_apps.dm
+++ b/code/modules/pda/cart_apps.dm
@@ -44,7 +44,7 @@
if("message")
status_signal.data["msg1"] = data1
status_signal.data["msg2"] = data2
- var/mob/user = pda.fingerprintslast
+ var/mob/user = pda.forensic_data?.get_lastprint()
if(isliving(pda.loc))
user = pda.loc
log_admin("STATUS: [user] set status screen with [pda]. Message: [data1] [data2]")
diff --git a/code/modules/pda/utilities.dm b/code/modules/pda/utilities.dm
index 64a0ca4c82..da901afa87 100644
--- a/code/modules/pda/utilities.dm
+++ b/code/modules/pda/utilities.dm
@@ -85,15 +85,14 @@
scan_blood(A, user)
/datum/data/pda/utility/scanmode/dna/proc/scan_blood(atom/A, mob/user)
- if(!A.blood_DNA)
+ var/list/blood_dna = A.forensic_data?.get_blooddna()
+ if(!blood_dna)
to_chat(user, span_notice("No blood found on [A]"))
- if(A.blood_DNA)
- qdel(A.blood_DNA)
else
to_chat(user, span_notice("Blood found on [A]. Analysing..."))
spawn(15)
- for(var/blood in A.blood_DNA)
- to_chat(user, span_notice("Blood type: [A.blood_DNA[blood]]\nDNA: [blood]"))
+ for(var/blood in blood_dna)
+ to_chat(user, span_notice("Blood type: [blood_dna[blood]]\nDNA: [blood]"))
/datum/data/pda/utility/scanmode/halogen
base_name = "Halogen Counter"
diff --git a/code/modules/power/cell.dm b/code/modules/power/cell.dm
index 2c15c1a754..4996de5848 100644
--- a/code/modules/power/cell.dm
+++ b/code/modules/power/cell.dm
@@ -189,8 +189,8 @@
return
//explosion(T, 0, 1, 2, 2)
- log_admin("LOG: Rigged power cell explosion, last touched by [fingerprintslast]")
- message_admins("LOG: Rigged power cell explosion, last touched by [fingerprintslast]")
+ log_admin("LOG: Rigged power cell explosion, last touched by [forensic_data?.get_lastprint()]")
+ message_admins("LOG: Rigged power cell explosion, last touched by [forensic_data?.get_lastprint()]")
explosion(T, devastation_range, heavy_impact_range, light_impact_range, flash_range)
diff --git a/code/modules/power/lighting.dm b/code/modules/power/lighting.dm
index 75afd51c55..72d6985d5c 100644
--- a/code/modules/power/lighting.dm
+++ b/code/modules/power/lighting.dm
@@ -485,8 +485,8 @@ var/global/list/light_type_cache = list()
if(rigged)
if(status == LIGHT_OK && trigger)
- log_admin("LOG: Rigged light explosion, last touched by [fingerprintslast]")
- message_admins("LOG: Rigged light explosion, last touched by [fingerprintslast]")
+ log_admin("LOG: Rigged light explosion, last touched by [forensic_data?.get_lastprint()]")
+ message_admins("LOG: Rigged light explosion, last touched by [forensic_data?.get_lastprint()]")
explode()
else if( prob( min(60, switchcount*switchcount*0.01) ) )
@@ -600,8 +600,8 @@ var/global/list/light_type_cache = list()
if(on && rigged)
- log_admin("LOG: Rigged light explosion, last touched by [fingerprintslast]")
- message_admins("LOG: Rigged light explosion, last touched by [fingerprintslast]")
+ log_admin("LOG: Rigged light explosion, last touched by [forensic_data?.get_lastprint()]")
+ message_admins("LOG: Rigged light explosion, last touched by [forensic_data?.get_lastprint()]")
explode()
diff --git a/code/modules/power/singularity/act.dm b/code/modules/power/singularity/act.dm
index ebb9ca1968..f8a5061677 100644
--- a/code/modules/power/singularity/act.dm
+++ b/code/modules/power/singularity/act.dm
@@ -74,12 +74,12 @@
return
var/prints = ""
- if(src.fingerprintshidden)
- prints = ", all touchers : " + src.fingerprintshidden
+ if(forensic_data?.get_hiddenprints())
+ prints = ", all touchers : " + forensic_data?.get_hiddenprints()
SetUniversalState(/datum/universal_state/supermatter_cascade)
- log_admin("New super singularity made by eating a SM crystal [prints]. Last touched by [src.fingerprintslast].")
- message_admins("New super singularity made by eating a SM crystal [prints]. Last touched by [src.fingerprintslast].")
+ log_admin("New super singularity made by eating a SM crystal [prints]. Last touched by [forensic_data?.get_lastprint()].")
+ message_admins("New super singularity made by eating a SM crystal [prints]. Last touched by [forensic_data?.get_lastprint()].")
qdel(src)
return 50000
diff --git a/code/modules/projectiles/guns/launcher/crossbow.dm b/code/modules/projectiles/guns/launcher/crossbow.dm
index 2638ed0273..ae7566a81e 100644
--- a/code/modules/projectiles/guns/launcher/crossbow.dm
+++ b/code/modules/projectiles/guns/launcher/crossbow.dm
@@ -150,7 +150,7 @@
var/obj/item/stack/rods/R = W
if (R.use(1))
bolt = new /obj/item/arrow/rod(src)
- bolt.fingerprintslast = src.fingerprintslast
+ bolt.add_fingerprint(user)
bolt.loc = src
update_icon()
user.visible_message("[user] jams [bolt] into [src].","You jam [bolt] into [src].")
diff --git a/code/modules/projectiles/guns/projectile.dm b/code/modules/projectiles/guns/projectile.dm
index 3f4e4efd50..a2b6fabf14 100644
--- a/code/modules/projectiles/guns/projectile.dm
+++ b/code/modules/projectiles/guns/projectile.dm
@@ -87,10 +87,10 @@
var/mob/living/carbon/human/H = loc
if(istype(H))
if(!istype(H.gloves, /obj/item/clothing))
- H.gunshot_residue = chambered.caliber
+ H.add_gunshotresidue(chambered)
else
var/obj/item/clothing/G = H.gloves
- G.gunshot_residue = chambered.caliber
+ G.add_gunshotresidue(chambered)
switch(handle_casings)
if(EJECT_CASINGS) //eject casing onto ground.
diff --git a/code/modules/projectiles/guns/projectile_ch.dm b/code/modules/projectiles/guns/projectile_ch.dm
index c6c421ec4f..5637035eec 100644
--- a/code/modules/projectiles/guns/projectile_ch.dm
+++ b/code/modules/projectiles/guns/projectile_ch.dm
@@ -203,10 +203,10 @@
var/mob/living/carbon/human/H = loc
if(istype(H))
if(!istype(H.gloves, /obj/item/clothing))
- H.gunshot_residue = chambered.caliber
+ H.add_gunshotresidue(chambered)
else
var/obj/item/clothing/G = H.gloves
- G.gunshot_residue = chambered.caliber
+ G.add_gunshotresidue(chambered)
switch(handle_casings)
if(EJECT_CASINGS) //eject casing onto ground.
diff --git a/code/modules/reagents/reagents/core.dm b/code/modules/reagents/reagents/core.dm
index 8ebf93504b..e2d87f78c1 100644
--- a/code/modules/reagents/reagents/core.dm
+++ b/code/modules/reagents/reagents/core.dm
@@ -38,9 +38,9 @@
if(!data["donor"] || ishuman(data["donor"]))
blood_splatter(T, src, 1)
else if(istype(data["donor"], /mob/living/carbon/alien))
+ var/mob/living/carbon/alien/A = data["donor"]
var/obj/effect/decal/cleanable/blood/B = blood_splatter(T, src, 1)
- if(B)
- B.blood_DNA["UNKNOWN DNA STRUCTURE"] = "X*"
+ B.add_blooddna(A.dna,A)
/datum/reagent/blood/affect_ingest(var/mob/living/carbon/M, var/alien, var/removed)
diff --git a/code/modules/vore/fluffstuff/custom_items_vr.dm b/code/modules/vore/fluffstuff/custom_items_vr.dm
index c9d568a839..b0d418f796 100644
--- a/code/modules/vore/fluffstuff/custom_items_vr.dm
+++ b/code/modules/vore/fluffstuff/custom_items_vr.dm
@@ -64,7 +64,7 @@
if(LAZYLEN(SS.breaches))
to_chat(user, span_warning("You should probably repair that before you start tinkering with it."))
return
- if(O.blood_DNA || O.contaminated) //check if we're bloody or gooey or whatever, so modkits can't be used to hide crimes easily.
+ if(O.forensic_data?.has_blooddna() || O.contaminated) //check if we're bloody or gooey or whatever, so modkits can't be used to hide crimes easily.
to_chat(user, span_warning("You should probably clean that up before you start tinkering with it."))
return
//we have to check that it's not the original type first, because otherwise it might convert wrong based on pathing; the subtype can still count as the basetype
@@ -107,11 +107,9 @@
var/obj/N = new to_type(O.loc)
user.visible_message(span_notice("[user] opens \the [src] and modifies \the [O] into \the [N]."),span_notice("You open \the [src] and modify \the [O] into \the [N]."))
- //crude, but transfer prints and fibers to avoid forensics abuse, same as the bloody/gooey check above
- N.fingerprints = O.fingerprints
- N.fingerprintshidden = O.fingerprintshidden
- N.fingerprintslast = O.fingerprintslast
- N.suit_fibers = O.suit_fibers
+ // Transfer forensics to, lets avoid CRIME exploits
+ O.transfer_fingerprints_to(N)
+ O.transfer_fibres_to(N)
//transfer logic could technically be made more thorough and handle stuff like helmet/boots/tank vars for suits, but in those cases you should be removing the items first anyway
if(skip_content_check && transfer_contents)
diff --git a/code/modules/vore/persist/persist_vr.dm b/code/modules/vore/persist/persist_vr.dm
index 670b47081f..548bd68bcf 100644
--- a/code/modules/vore/persist/persist_vr.dm
+++ b/code/modules/vore/persist/persist_vr.dm
@@ -129,7 +129,7 @@
prefs.s_tone = character.s_tone
prefs.h_style = character.h_style
prefs.f_style = character.f_style
- prefs.b_type = character.b_type
+ prefs.b_type = character.dna ? character.dna.b_type : DEFAULT_BLOOD_TYPE
// Saves mob's current custom species, ears, tail, wings and digitigrade legs state to prefs
// This basically needs to be the reverse of /datum/category_item/player_setup_item/vore/ears/copy_to_mob() ~Leshana
diff --git a/code/modules/xenoarcheaology/effects/vampire.dm b/code/modules/xenoarcheaology/effects/vampire.dm
index 81de444776..4391976909 100644
--- a/code/modules/xenoarcheaology/effects/vampire.dm
+++ b/code/modules/xenoarcheaology/effects/vampire.dm
@@ -32,8 +32,7 @@
B.basecolor = M.species.get_blood_colour(M)
B.color = M.species.get_blood_colour(M)
B.target_turf = pick(RANGE_TURFS(1, holder))
- B.blood_DNA = list()
- B.blood_DNA[M.dna.unique_enzymes] = M.dna.b_type
+ B.add_blooddna(M.dna,M)
var/blood_to_remove = (rand(10,30))
M.remove_blood(blood_to_remove)
if(harvested)
diff --git a/code/modules/xenoarcheaology/finds/special.dm b/code/modules/xenoarcheaology/finds/special.dm
index 7de9ebe3df..d65217d3ca 100644
--- a/code/modules/xenoarcheaology/finds/special.dm
+++ b/code/modules/xenoarcheaology/finds/special.dm
@@ -137,8 +137,7 @@
to_chat(M, span_red("The skin on your [parse_zone(target)] feels like it's ripping apart, and a stream of blood flies out."))
var/obj/effect/decal/cleanable/blood/splatter/animated/B = new(M.loc)
B.target_turf = pick(range(1, src))
- B.blood_DNA = list()
- B.blood_DNA[M.dna.unique_enzymes] = M.dna.b_type
+ B.add_blooddna(M.dna,M)
M.remove_blood(rand(25,50))
//animated blood 2 SPOOKY
@@ -161,13 +160,13 @@
//leave some drips behind
if(prob(50))
var/obj/effect/decal/cleanable/blood/drip/D = new(src.loc)
- D.blood_DNA = src.blood_DNA.Copy()
+ D.add_blooddna(forensic_data)
if(prob(50))
D = new(src.loc)
- D.blood_DNA = src.blood_DNA.Copy()
+ D.add_blooddna(forensic_data)
if(prob(50))
D = new(src.loc)
- D.blood_DNA = src.blood_DNA.Copy()
+ D.add_blooddna(forensic_data)
else
..()
diff --git a/code/modules/xenobio/items/extracts_vr.dm b/code/modules/xenobio/items/extracts_vr.dm
index 7be5802a46..488135ce99 100644
--- a/code/modules/xenobio/items/extracts_vr.dm
+++ b/code/modules/xenobio/items/extracts_vr.dm
@@ -332,7 +332,7 @@
required = /obj/item/slime_extract/orange
/decl/chemical_reaction/instant/slime/orange_fire/on_reaction(var/datum/reagents/holder)
- log_and_message_admins("Orange extract reaction (fire) has been activated in [get_area(holder.my_atom)]. Last fingerprints: [holder.my_atom.fingerprintslast]")
+ log_and_message_admins("Orange extract reaction (fire) has been activated in [get_area(holder.my_atom)]. Last fingerprints: [holder.my_atom.forensic_data?.get_lastprint()]")
holder.my_atom.visible_message(span_danger("\The [src] begins to vibrate violently!"))
playsound(holder.my_atom, 'sound/effects/phasein.ogg', 75, 1)
spawn(5 SECONDS)
@@ -370,7 +370,7 @@
if(!Z) // Paranoid.
return
- log_and_message_admins("Orange extract reaction (heat wave) has been activated in [get_area(holder.my_atom)]. Last fingerprints: [holder.my_atom.fingerprintslast]")
+ log_and_message_admins("Orange extract reaction (heat wave) has been activated in [get_area(holder.my_atom)]. Last fingerprints: [holder.my_atom.forensic_data?.get_lastprint()]")
var/list/nearby_things = view(T)
@@ -453,7 +453,7 @@
required = /obj/item/slime_extract/yellow
/decl/chemical_reaction/instant/slime/yellow_lightning/on_reaction(var/datum/reagents/holder)
- log_and_message_admins("Yellow extract reaction (lightning) has been activated in [get_area(holder.my_atom)]. Last fingerprints: [holder.my_atom.fingerprintslast]")
+ log_and_message_admins("Yellow extract reaction (lightning) has been activated in [get_area(holder.my_atom)]. Last fingerprints: [holder.my_atom.forensic_data?.get_lastprint()]")
holder.my_atom.visible_message(span_danger("\The [src] begins to vibrate violently!"))
playsound(holder.my_atom, 'sound/effects/phasein.ogg', 75, 1)
spawn(5 SECONDS)
@@ -484,7 +484,7 @@
required = /obj/item/slime_extract/yellow
/decl/chemical_reaction/instant/slime/yellow_emp/on_reaction(var/datum/reagents/holder)
- log_and_message_admins("Yellow extract reaction (emp) has been activated in [get_area(holder.my_atom)]. Last fingerprints: [holder.my_atom.fingerprintslast]")
+ log_and_message_admins("Yellow extract reaction (emp) has been activated in [get_area(holder.my_atom)]. Last fingerprints: [holder.my_atom.forensic_data?.get_lastprint()]")
holder.my_atom.visible_message(span_danger("\The [src] begins to vibrate violently!"))
playsound(holder.my_atom, 'sound/effects/phasein.ogg', 75, 1)
spawn(5 SECONDS)
@@ -525,7 +525,7 @@
required = /obj/item/slime_extract/gold
/decl/chemical_reaction/instant/slime/gold_random_mobs/on_reaction(var/datum/reagents/holder)
- log_and_message_admins("Gold extract reaction (random mobs) has been activated in [get_area(holder.my_atom)]. Last fingerprints: [holder.my_atom.fingerprintslast]")
+ log_and_message_admins("Gold extract reaction (random mobs) has been activated in [get_area(holder.my_atom)]. Last fingerprints: [holder.my_atom.forensic_data?.get_lastprint()]")
var/type_to_spawn
var/list/all_spawnable_types = list()
all_spawnable_types += xenobio_gold_mobs_safe
@@ -551,7 +551,7 @@
required = /obj/item/slime_extract/gold
/decl/chemical_reaction/instant/slime/gold_hostile_mob/on_reaction(var/datum/reagents/holder)
- log_and_message_admins("Gold extract reaction (dangerous mob) has been activated in [get_area(holder.my_atom)]. Last fingerprints: [holder.my_atom.fingerprintslast]")
+ log_and_message_admins("Gold extract reaction (dangerous mob) has been activated in [get_area(holder.my_atom)]. Last fingerprints: [holder.my_atom.forensic_data?.get_lastprint()]")
var/type_to_spawn = pickweight(xenobio_gold_mobs_hostile)
var/mob/living/C = new type_to_spawn(get_turf(holder.my_atom))
for(var/l = 1, l <= rand(1, 3), l++)
@@ -724,7 +724,7 @@
if(!Z) // Paranoid.
return
- log_and_message_admins("Dark Blue extract reaction (cold snap) has been activated in [get_area(holder.my_atom)]. Last fingerprints: [holder.my_atom.fingerprintslast]")
+ log_and_message_admins("Dark Blue extract reaction (cold snap) has been activated in [get_area(holder.my_atom)]. Last fingerprints: [holder.my_atom.forensic_data?.get_lastprint()]")
var/list/nearby_things = view(T)
@@ -874,7 +874,7 @@
H.add_modifier(/datum/modifier/berserk, 30 SECONDS)
to_chat(H, span_warning("An intense wave of rage is felt from inside, but you remain in control of yourself."))
- log_and_message_admins("Red extract reaction (enrage) has been activated in [get_area(holder.my_atom)]. Last fingerprints: [holder.my_atom.fingerprintslast]")
+ log_and_message_admins("Red extract reaction (enrage) has been activated in [get_area(holder.my_atom)]. Last fingerprints: [holder.my_atom.forensic_data?.get_lastprint()]")
playsound(holder.my_atom, 'sound/effects/phasein.ogg', 75, 1)
..()
@@ -920,7 +920,7 @@
required = /obj/item/slime_extract/green
/decl/chemical_reaction/instant/slime/green_radpulse/on_reaction(var/datum/reagents/holder)
- log_and_message_admins("Green extract reaction (radiation pulse) has been activated in [get_area(holder.my_atom)]. Last fingerprints: [holder.my_atom.fingerprintslast]")
+ log_and_message_admins("Green extract reaction (radiation pulse) has been activated in [get_area(holder.my_atom)]. Last fingerprints: [holder.my_atom.forensic_data?.get_lastprint()]")
playsound(holder.my_atom, 'sound/effects/phasein.ogg', 75, 1)
holder.my_atom.visible_message(span_danger("\The [holder.my_atom] begins to vibrate violently!"))
spawn(5 SECONDS)
@@ -936,7 +936,7 @@
required = /obj/item/slime_extract/green
/decl/chemical_reaction/instant/slime/green_emitter/on_reaction(var/datum/reagents/holder)
- log_and_message_admins("Green extract reaction (radiation emitter) has been activated in [get_area(holder.my_atom)]. Last fingerprints: [holder.my_atom.fingerprintslast]")
+ log_and_message_admins("Green extract reaction (radiation emitter) has been activated in [get_area(holder.my_atom)]. Last fingerprints: [holder.my_atom.forensic_data?.get_lastprint()]")
new /obj/item/slime_irradiator(get_turf(holder.my_atom))
..()
@@ -1079,7 +1079,7 @@
playsound(holder.my_atom, 'sound/effects/phasein.ogg', 75, 1)
holder.my_atom.visible_message(span_danger("\The [holder.my_atom] begins to vibrate violently!"))
- log_and_message_admins("Oil extract reaction (explosion) has been activated in [get_area(holder.my_atom)]. Last fingerprints: [holder.my_atom.fingerprintslast]")
+ log_and_message_admins("Oil extract reaction (explosion) has been activated in [get_area(holder.my_atom)]. Last fingerprints: [holder.my_atom.forensic_data?.get_lastprint()]")
spawn(5 SECONDS)
if(holder && holder.my_atom)
@@ -1133,7 +1133,7 @@
required = /obj/item/slime_extract/bluespace
/decl/chemical_reaction/instant/slime/bluespace_chaotic_tele/on_reaction(var/datum/reagents/holder)
- log_and_message_admins("Bluespace extract reaction (chaos teleport) has been activated in [get_area(holder.my_atom)]. Last fingerprints: [holder.my_atom.fingerprintslast]")
+ log_and_message_admins("Bluespace extract reaction (chaos teleport) has been activated in [get_area(holder.my_atom)]. Last fingerprints: [holder.my_atom.forensic_data?.get_lastprint()]")
for(var/mob/living/M in range(2,get_turf(holder.my_atom)))
if(M.buckled)
M.buckled.unbuckle_mob()
@@ -1520,7 +1520,7 @@
required = /obj/item/slime_extract/emerald
/decl/chemical_reaction/instant/slime/emerald_hell/on_reaction(var/datum/reagents/holder)
- log_and_message_admins("Emerald extract reaction (slip hell) has been activated in [get_area(holder.my_atom)]. Last fingerprints: [holder.my_atom.fingerprintslast]")
+ log_and_message_admins("Emerald extract reaction (slip hell) has been activated in [get_area(holder.my_atom)]. Last fingerprints: [holder.my_atom.forensic_data?.get_lastprint()]")
for(var/turf/simulated/T in trange(5, get_turf(holder.my_atom)))
if(!istype(T))
continue
diff --git a/modular_chomp/code/game/machinery/food_replicator.dm b/modular_chomp/code/game/machinery/food_replicator.dm
index 71a6a0b6c8..3c79bcddc2 100644
--- a/modular_chomp/code/game/machinery/food_replicator.dm
+++ b/modular_chomp/code/game/machinery/food_replicator.dm
@@ -223,8 +223,8 @@
visible_message(span_warning("Whirrs and spouts, starting to heat up!"))
playsound(src, pick('sound/effects/Glassbr1.ogg', 'sound/effects/Glassbr2.ogg', 'sound/effects/Glassbr3.ogg'), 50, 1)
- message_admins("[src] attempted to create an EX donk pocket at [x], [y], [z], last touched by [fingerprintslast]")
- log_game("[src] attempted to create an EX donk pocket at [x], [y], [z], last touched by [fingerprintslast]. (JMP)", 1)
+ message_admins("[src] attempted to create an EX donk pocket at [x], [y], [z], last touched by [forensic_data?.get_lastprint()]")
+ log_game("[src] attempted to create an EX donk pocket at [x], [y], [z], last touched by [forensic_data?.get_lastprint()]. (JMP)", 1)
sleep(6 SECONDS) // GET OUT, GET OUT
stat = BROKEN
diff --git a/modular_chomp/code/modules/clothing/clothing_icons.dm b/modular_chomp/code/modules/clothing/clothing_icons.dm
index af7ae51243..0fe79f6601 100644
--- a/modular_chomp/code/modules/clothing/clothing_icons.dm
+++ b/modular_chomp/code/modules/clothing/clothing_icons.dm
@@ -1,5 +1,5 @@
/obj/item/clothing/shoes/apply_blood(var/image/standing)
- if(blood_DNA && blood_sprite_state && ishuman(loc))
+ if(forensic_data?.has_blooddna() && blood_sprite_state && ishuman(loc))
var/mob/living/carbon/human/H = loc
var/image/bloodsies = image(icon = H.digitigrade ? 'icons/mob/human_races/masks/blood_digitigrade.dmi' : H.species.get_blood_mask(H), icon_state = blood_sprite_state)
bloodsies.color = blood_color
diff --git a/modular_chomp/code/modules/clothing/masks/hailer.dm b/modular_chomp/code/modules/clothing/masks/hailer.dm
index d66a4caa31..6649c584af 100644
--- a/modular_chomp/code/modules/clothing/masks/hailer.dm
+++ b/modular_chomp/code/modules/clothing/masks/hailer.dm
@@ -136,10 +136,8 @@
else
var/obj/N = new /obj/item/clothing/mask/gas/half(src.loc)
playsound(src, 'sound/items/Screwdriver.ogg', 50, 1)
- N.fingerprints = src.fingerprints
- N.fingerprintshidden = src.fingerprintshidden
- N.fingerprintslast = src.fingerprintslast
- N.suit_fibers = src.suit_fibers
+ transfer_fingerprints_to(N)
+ transfer_fibres_to(N)
if(!isturf(N.loc))
user.put_in_hands(hailer)
user.put_in_hands(N)
diff --git a/modular_chomp/code/modules/mob/living/simple_mob/subtypes/slimess/xenobio.dm b/modular_chomp/code/modules/mob/living/simple_mob/subtypes/slimess/xenobio.dm
index f4d4d5a084..1761c86885 100644
--- a/modular_chomp/code/modules/mob/living/simple_mob/subtypes/slimess/xenobio.dm
+++ b/modular_chomp/code/modules/mob/living/simple_mob/subtypes/slimess/xenobio.dm
@@ -433,7 +433,7 @@
required = /obj/item/slime_extract/nuclear
/decl/chemical_reaction/instant/slime/nuclear_radpulse/on_reaction(var/datum/reagents/holder)
- log_and_message_admins("Green extract reaction (radiation pulse) has been activated in [get_area(holder.my_atom)]. Last fingerprints: [holder.my_atom.fingerprintslast]")
+ log_and_message_admins("Green extract reaction (radiation pulse) has been activated in [get_area(holder.my_atom)]. Last fingerprints: [holder.my_atom.forensic_data?.get_lastprint()]")
playsound(holder.my_atom, 'sound/effects/phasein.ogg', 75, 1)
holder.my_atom.visible_message(span_danger("\The [holder.my_atom] begins to vibrate violently!"))
spawn(5 SECONDS)
@@ -449,7 +449,7 @@
required = /obj/item/slime_extract/green
/decl/chemical_reaction/instant/slime/green_emitter/on_reaction(var/datum/reagents/holder)
- log_and_message_admins("Green extract reaction (radiation emitter) has been activated in [get_area(holder.my_atom)]. Last fingerprints: [holder.my_atom.fingerprintslast]")
+ log_and_message_admins("Green extract reaction (radiation emitter) has been activated in [get_area(holder.my_atom)]. Last fingerprints: [holder.my_atom.forensic_data?.get_lastprint()]")
new /obj/item/slime_irradiator(get_turf(holder.my_atom))
..()
diff --git a/vorestation.dme b/vorestation.dme
index 02b49ad78f..2d5e0bd397 100644
--- a/vorestation.dme
+++ b/vorestation.dme
@@ -490,6 +490,7 @@
#include "code\datums\datum.dm"
#include "code\datums\datumvars.dm"
#include "code\datums\EPv2.dm"
+#include "code\datums\forensics_crime.dm"
#include "code\datums\ghost_query.dm"
#include "code\datums\ghost_query_vr.dm"
#include "code\datums\hierarchy.dm"