[MIRROR] Forensics Datum (#11015)

Co-authored-by: Will <7099514+Willburd@users.noreply.github.com>
Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com>
This commit is contained in:
CHOMPStation2StaffMirrorBot
2025-06-06 07:18:13 -07:00
committed by GitHub
parent 887639ef5c
commit d7cd22d2d0
69 changed files with 614 additions and 413 deletions

View File

@@ -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

View File

@@ -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 // "

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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!"))

View File

@@ -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)

View File

@@ -93,13 +93,14 @@
var/whereLink = "<A href='byond://?_src_=holder;[HrefToken()];adminplayerobservecoodjump=1;X=[location.x];Y=[location.y];Z=[location.z]'>[where]</a>"
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 = "(<A href='byond://?_src_=holder;[HrefToken()];adminmoreinfo=\ref[M]'>?</a>)"
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.")

View File

@@ -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)

View File

@@ -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()

View File

@@ -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)

View File

@@ -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)

View File

@@ -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]")

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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()

View File

@@ -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)

View File

@@ -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))

View File

@@ -9,6 +9,6 @@
dat += "<table cellspacing=5><tr><th>Name</th><th>DNA</th><th>Blood Type</th></tr>"
for(var/mob/living/carbon/human/H in mob_list)
if(H.dna && H.ckey)
dat += "<tr><td>[H]</td><td>[H.dna.unique_enzymes]</td><td>[H.b_type]</td></tr>"
dat += "<tr><td>[H]</td><td>[H.dna.unique_enzymes]</td><td>[H.dna ? H.dna.b_type : DEFAULT_BLOOD_TYPE]</td></tr>"
dat += "</table>"
user << browse("<html>[dat]</html>", "window=DNA;size=440x410")

View File

@@ -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

View File

@@ -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]

View File

@@ -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"

View File

@@ -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()
. = ..()

View File

@@ -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)

View File

@@ -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))

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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()

View File

@@ -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]."),\

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -123,14 +123,14 @@
accessory_descs += "<a href='byond://?src=\ref[src];lookitem_desc_only=\ref[A]'>\a [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 <a href='byond://?src=\ref[src];lookitem_desc_only=\ref[w_uniform]'>[w_uniform.name]</a>![tie_msg]")
else
msg += "[T.He] [T.is] wearing [icon2html(w_uniform,user.client)] <a href='byond://?src=\ref[src];lookitem_desc_only=\ref[w_uniform]'>\a [w_uniform]</a>.[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 <a href='byond://?src=\ref[src];lookitem_desc_only=\ref[head]'>[head.name]</a> on [T.his] head!")
else
msg += "[T.He] [T.is] wearing [icon2html(head,user.client)] <a href='byond://?src=\ref[src];lookitem_desc_only=\ref[head]'>\a [head]</a> on [T.his] head."
@@ -147,35 +147,35 @@
accessory_descs += "<a href='byond://?src=\ref[src];lookitem_desc_only=\ref[accessory]'>\a [accessory]</a>"
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 <a href='byond://?src=\ref[src];lookitem_desc_only=\ref[wear_suit]'>[wear_suit.name]</a>![tie_msg]")
else
msg += "[T.He] [T.is] wearing [icon2html(wear_suit,user.client)] <a href='byond://?src=\ref[src];lookitem_desc_only=\ref[wear_suit]'>\a [wear_suit]</a>.[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 <a href='byond://?src=\ref[src];lookitem_desc_only=\ref[s_store]'>[s_store.name]</a> on [T.his] [wear_suit.name]!")
else
msg += "[T.He] [T.is] carrying [icon2html(s_store,user.client)] <a href='byond://?src=\ref[src];lookitem_desc_only=\ref[s_store]'>\a [s_store]</a> 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 <a href='byond://?src=\ref[src];lookitem_desc_only=\ref[back]'>[back]</a> on [T.his] back.")
else
msg += "[T.He] [T.has] [icon2html(back,user.client)] <a href='byond://?src=\ref[src];lookitem_desc_only=\ref[back]'>\a [back]</a> 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 <a href='byond://?src=\ref[src];lookitem_desc_only=\ref[l_hand]'>[l_hand.name]</a> in [T.his] left hand!")
else
msg += "[T.He] [T.is] holding [icon2html(l_hand,user.client)] <a href='byond://?src=\ref[src];lookitem_desc_only=\ref[l_hand]'>\a [l_hand]</a> 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 <a href='byond://?src=\ref[src];lookitem_desc_only=\ref[r_hand]'>[r_hand.name]</a> in [T.his] right hand!")
else
msg += "[T.He] [T.is] holding [icon2html(r_hand,user.client)] <a href='byond://?src=\ref[src];lookitem_desc_only=\ref[r_hand]'>\a [r_hand]</a> in [T.his] right hand."
@@ -192,12 +192,12 @@
accessory_descs += "<a href='byond://?src=\ref[src];lookitem_desc_only=\ref[A]'>\a [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 <a href='byond://?src=\ref[src];lookitem_desc_only=\ref[gloves]'>[gloves.name]</a> on [T.his] hands![gloves_acc_msg]")
else
msg += "[T.He] [T.has] [icon2html(gloves,user.client)] <a href='byond://?src=\ref[src];lookitem_desc_only=\ref[gloves]'>\a [gloves]</a> 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 <a href='byond://?src=\ref[src];lookitem_desc_only=\ref[belt]'>[belt.name]</a> about [T.his] waist!")
else
msg += "[T.He] [T.has] [icon2html(belt,user.client)] <a href='byond://?src=\ref[src];lookitem_desc_only=\ref[belt]'>\a [belt]</a> 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 <a href='byond://?src=\ref[src];lookitem_desc_only=\ref[shoes]'>[shoes.name]</a> on [T.his] feet!")
else
msg += "[T.He] [T.is] wearing [icon2html(shoes,user.client)] <a href='byond://?src=\ref[src];lookitem_desc_only=\ref[shoes]'>\a [shoes]</a> 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 <a href='byond://?src=\ref[src];lookitem_desc_only=\ref[wear_mask]'>[wear_mask.name]</a> [descriptor]!")
else
msg += "[T.He] [T.has] [icon2html(wear_mask,user.client)] <a href='byond://?src=\ref[src];lookitem_desc_only=\ref[wear_mask]'>\a [wear_mask]</a> [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 <a href='byond://?src=\ref[src];lookitem_desc_only=\ref[glasses]'>[glasses]</a> covering [T.his] eyes!")
else
msg += "[T.He] [T.has] [icon2html(glasses,user.client)] <a href='byond://?src=\ref[src];lookitem_desc_only=\ref[glasses]'>\a [glasses]</a> covering [T.his] eyes."

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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]. (<A href='byond://?_src_=holder;[HrefToken()];adminplayerobservecoodjump=1;X=[T.x];Y=[T.y];Z=[T.z]'>JMP</a>)", 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()]. (<A href='byond://?_src_=holder;[HrefToken()];adminplayerobservecoodjump=1;X=[T.x];Y=[T.y];Z=[T.z]'>JMP</a>)", 1)
// start the dephase
phase_in(T)
shadekin_adjust_energy(-20) // loss of energy for the interception

View File

@@ -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))

View File

@@ -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)

View File

@@ -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--

View File

@@ -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"])

View File

@@ -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)))

View File

@@ -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)

View File

@@ -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]")

View File

@@ -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]")

View File

@@ -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"

View File

@@ -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)

View File

@@ -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()

View File

@@ -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

View File

@@ -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].")

View File

@@ -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.

View File

@@ -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.

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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
..()

View File

@@ -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

View File

@@ -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]. (<A href='byond://?_src_=holder;[HrefToken()];adminplayerobservecoodjump=1;X=[x];Y=[y];Z=[z]'>JMP</a>)", 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()]. (<A href='byond://?_src_=holder;[HrefToken()];adminplayerobservecoodjump=1;X=[x];Y=[y];Z=[z]'>JMP</a>)", 1)
sleep(6 SECONDS) // GET OUT, GET OUT
stat = BROKEN

View File

@@ -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

View File

@@ -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)

View File

@@ -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))
..()

View File

@@ -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"