From ea9bbe37802f0e843f8080bd27dc6d0c455de854 Mon Sep 17 00:00:00 2001 From: suethecake Date: Sat, 6 Sep 2014 23:29:41 -0600 Subject: [PATCH] Overhauls in-game forensics system to be more in-depth. --- code/_onclick/hud/hud.dm | 8 + code/game/atoms.dm | 4 +- code/game/machinery/washing_machine.dm | 3 + .../effects/decals/Cleanable/humans.dm | 2 + code/game/objects/items.dm | 17 +- code/game/objects/structures/watercloset.dm | 13 + code/modules/clothing/clothing.dm | 1 + code/modules/detectivework/evidence.dm | 3 +- code/modules/detectivework/forensics.dm | 666 ++++++++++++++++++ .../mob/living/carbon/human/examine.dm | 24 + code/modules/mob/living/carbon/human/life.dm | 5 +- .../mob/living/carbon/human/update_icons.dm | 27 + code/modules/mob/living/living_defines.dm | 1 + code/modules/projectiles/gun.dm | 9 + code/modules/reagents/Chemistry-Reagents.dm | 74 +- code/modules/reagents/Chemistry-Recipes.dm | 6 + icons/obj/forensics.dmi | Bin 0 -> 16704 bytes nano/templates/dnaforensics.tmpl | 42 ++ 18 files changed, 895 insertions(+), 10 deletions(-) create mode 100644 code/modules/detectivework/forensics.dm create mode 100644 icons/obj/forensics.dmi create mode 100644 nano/templates/dnaforensics.tmpl diff --git a/code/_onclick/hud/hud.dm b/code/_onclick/hud/hud.dm index 5e005c5f..7c80ad3a 100644 --- a/code/_onclick/hud/hud.dm +++ b/code/_onclick/hud/hud.dm @@ -14,6 +14,7 @@ var/datum/global_hud/global_hud = new() var/obj/screen/blurry var/list/vimpaired var/list/darkMask + var/obj/screen/lum var/obj/screen/nvg /datum/global_hud/New() @@ -38,6 +39,13 @@ var/datum/global_hud/global_hud = new() nvg.layer = 17 nvg.mouse_opacity = 0 + lum = new /obj/screen() + lum.screen_loc = "1,1" + lum.icon = 'icons/obj/nvg_hud_full.dmi' + lum.icon_state = "luminol_hud" + lum.layer = 17 + lum.mouse_opacity = 0 + var/obj/screen/O var/i //that nasty looking dither you get when you're short-sighted diff --git a/code/game/atoms.dm b/code/game/atoms.dm index d63ba30c..ca6760c7 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -6,6 +6,7 @@ var/list/fingerprintshidden var/fingerprintslast = null var/list/blood_DNA + var/list/stored_DNA = list() var/blood_color var/last_bumped = 0 var/pass_flags = 0 @@ -417,7 +418,6 @@ its easier to just keep the beam vertical. if(toxvomit) this.icon_state = "vomittox_[pick(1,4)]" - /atom/proc/clean_blood() src.germ_level = 0 if(istype(blood_DNA, /list)) @@ -442,4 +442,4 @@ its easier to just keep the beam vertical. return 0 /atom/proc/checkpass(passflag) - return pass_flags&passflag + return pass_flags&passflag \ No newline at end of file diff --git a/code/game/machinery/washing_machine.dm b/code/game/machinery/washing_machine.dm index 48ade625..bc52a6ad 100644 --- a/code/game/machinery/washing_machine.dm +++ b/code/game/machinery/washing_machine.dm @@ -43,6 +43,9 @@ for(var/atom/A in contents) A.clean_blood() + for(var/obj/item/clothing/C in contents) + C.gsr = 0 + for(var/obj/item/I in contents) I.decontaminate() diff --git a/code/game/objects/effects/decals/Cleanable/humans.dm b/code/game/objects/effects/decals/Cleanable/humans.dm index 9d1b0c94..574597e0 100644 --- a/code/game/objects/effects/decals/Cleanable/humans.dm +++ b/code/game/objects/effects/decals/Cleanable/humans.dm @@ -50,6 +50,8 @@ var/global/list/image/splatter_cache=list() return if(amount < 1) return + if(invisibility != 0) + return if(perp.shoes)//Adding blood to shoes perp.shoes.blood_color = basecolor diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index 808640a4..1b256543 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -4,6 +4,7 @@ var/image/blood_overlay = null //this saves our blood splatter overlay, which will be processed not to go over the edges of the sprite var/abstract = 0 var/item_state = null + var/wasbloody = 0 var/r_speed = 1.0 var/health = null var/burn_point = null @@ -103,7 +104,7 @@ size = "huge" else //if ((CLUMSY in usr.mutations) && prob(50)) t = "funny-looking" - usr << "This is a [src.blood_DNA ? "bloody " : ""]\icon[src][src.name]. It is a [size] item." + usr << "This is a [src.blood_DNA ? "bloody " : ""][src.wasbloody == 2 ? "luminol covered " : ""]\icon[src][src.name]. It is a [size] item." if(src.desc) usr << src.desc return @@ -552,9 +553,21 @@ . = ..() if(blood_overlay) overlays.Remove(blood_overlay) + blood_overlay = null if(istype(src, /obj/item/clothing/gloves)) var/obj/item/clothing/gloves/G = src G.transfer_blood = 0 + if(wasbloody == 2) + wasbloody = 1 + return + +/obj/item/proc/reveal_blood() + if(wasbloody == 1) + wasbloody = 2 + generate_blood_overlay() + blood_overlay.color = "#007fff" + /*blood_overlay.invisibility = INVISIBILITY_LUMINOL*/ + overlays += blood_overlay /obj/item/add_blood(mob/living/carbon/human/M as mob) @@ -578,6 +591,8 @@ 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 + stored_DNA[M.dna.unique_enzymes] = M.dna.b_type + wasbloody = 1 return 1 //we applied blood to the item /obj/item/proc/generate_blood_overlay() diff --git a/code/game/objects/structures/watercloset.dm b/code/game/objects/structures/watercloset.dm index ed5bf1d2..440c8556 100644 --- a/code/game/objects/structures/watercloset.dm +++ b/code/game/objects/structures/watercloset.dm @@ -344,6 +344,14 @@ M.clean_blood() if(ishuman(M)) M:update_inv_gloves() + if(ishuman(M)) + var/mob/living/carbon/human/B = M + if(B.gloves) + var/obj/item/clothing/gloves/C = B.gloves + C.gsr = 0 + else + B.gsr = 0 + for(var/mob/V in viewers(src, null)) V.show_message("\blue [M] washes their hands using \the [src].") @@ -392,6 +400,11 @@ if(user.get_active_hand() != I) return //Person has switched hands or the item in their hands O.clean_blood() + + if(istype(O, /obj/item/clothing)) + var/obj/item/clothing/G = O + G.gsr = 0 + user.visible_message( \ "\blue [user] washes \a [I] using \the [src].", \ "\blue You wash \a [I] using \the [src].") diff --git a/code/modules/clothing/clothing.dm b/code/modules/clothing/clothing.dm index 9dddf399..d96fc888 100644 --- a/code/modules/clothing/clothing.dm +++ b/code/modules/clothing/clothing.dm @@ -1,6 +1,7 @@ /obj/item/clothing name = "clothing" var/list/species_restricted = null //Only these species can wear this kit. + var/gsr = 0 //BS12: Species-restricted clothing check. /obj/item/clothing/mob_can_equip(M as mob, slot) diff --git a/code/modules/detectivework/evidence.dm b/code/modules/detectivework/evidence.dm index 7148a5af..c4120d5e 100644 --- a/code/modules/detectivework/evidence.dm +++ b/code/modules/detectivework/evidence.dm @@ -96,6 +96,7 @@ icon = 'icons/obj/card.dmi' icon_state = "fingerprint0" var/amount = 10.0 + var/complete_prints = list() item_state = "paper" throwforce = 1 w_class = 1.0 @@ -108,4 +109,4 @@ desc = "Apply finger print card." icon = 'icons/obj/items.dmi' icon_state = "fcardholder0" - item_state = "clipboard" + item_state = "clipboard" \ No newline at end of file diff --git a/code/modules/detectivework/forensics.dm b/code/modules/detectivework/forensics.dm new file mode 100644 index 00000000..5488587c --- /dev/null +++ b/code/modules/detectivework/forensics.dm @@ -0,0 +1,666 @@ +//swabs and code therein + +/obj/item/weapon/storage/box/swabs + name = "box of swab kits" + desc = "Sterilized equipment within. Do not contaminate." + icon = 'forensics.dmi' + icon_state = "dnakit" + storage_slots=14 + can_hold = list("/obj/item/weapon/forensics/swab") + max_combined_w_class = 42 + + New() + ..() + new /obj/item/weapon/forensics/swab( src ) + new /obj/item/weapon/forensics/swab( src ) + new /obj/item/weapon/forensics/swab( src ) + new /obj/item/weapon/forensics/swab( src ) + new /obj/item/weapon/forensics/swab( src ) + new /obj/item/weapon/forensics/swab( src ) + new /obj/item/weapon/forensics/swab( src ) + new /obj/item/weapon/forensics/swab( src ) + new /obj/item/weapon/forensics/swab( src ) + new /obj/item/weapon/forensics/swab( src ) + new /obj/item/weapon/forensics/swab( src ) + new /obj/item/weapon/forensics/swab( src ) + new /obj/item/weapon/forensics/swab( src ) + new /obj/item/weapon/forensics/swab( src ) + +/obj/item/weapon/forensics/swab + name = "swab kit" + desc = "A sterilized cotton swab and vial used to take forensic samples." + icon = 'forensics.dmi' + icon_state = "swab" + flags = FPRINT | TABLEPASS | CONDUCT | NOBLUDGEON + w_class = 1.0 + var/used = 0 + var/gsr = 0 + var/list/dna = list() + +/obj/item/weapon/forensics/swab/attackby(obj/item/weapon/W as obj, mob/user as mob) + if(istype(W, /obj/item/weapon/pen) || istype(W, /obj/item/device/flashlight/pen)) + var/tmp_label = sanitize(input(user, "Enter a label for [src.name]","Label")) + if(length(tmp_label) > 20) + user << "\red The label can be at most 20 characters long." + else + user << "\blue You set the label to \"[tmp_label]\"." + name = "[initial(name)] \"[tmp_label]\"." + +/obj/item/weapon/forensics/swab/attack(mob/living/carbon/human/M as mob, mob/user as mob) + if(used == 1) + user << "\blue This swab has already been used." + return + else if(user.zone_sel.selecting == "mouth") + user.visible_message("[user] swabs [M]'s mouth for a saliva sample.", "You swab [M]'s mouth for a saliva sample.") + dna = M.dna.unique_enzymes + used = 1 + name = "swab of [M]'s DNA" + desc = "[initial(desc)]
\blue The label on the vial reads 'Sample of [M]'s DNA'." + update_icon() + return + else if(user.zone_sel.selecting == "r_hand" || user.zone_sel.selecting == "l_hand") + user.visible_message("[user] swabs [M]'s palm for a sample.", "You swab [M]'s palm for a sample.") + gsr = M.gsr + used = 1 + name = "swab of [M] for GSR tests" + desc = "[initial(desc)]
\blue The label on the vial reads 'GSR Sample from [M]'." + update_icon() + return + else + return + +/obj/item/weapon/forensics/swab/afterattack(atom/A as obj, mob/user as mob, proximity) + if(istype(A, /obj/item/weapon/forensics/slide)) + return + if(used == 1) + return + if(!proximity) return + if(loc != user) + return + + add_fingerprint(user) + + if (istype(A, /obj/effect/decal/cleanable/blood) || istype(A, /obj/effect/rune) || istype(A, /obj/effect/decal/cleanable/blood/gibs)) + if(!isnull(A.blood_DNA)) + for(var/blood in A.blood_DNA) + dna = A.blood_DNA + user.visible_message("[user] swabs [A] for a sample.", "You swab [A] for a DNA sample.") + used = 1 + update_icon() + return + + else if(istype(A, /obj/item/clothing)) + switch(alert(user,"What would you like to swab for?",,"Blood","Gunshot Residue","Cancel")) + if("Blood") + dna = A.stored_DNA + user.visible_message("[user] swabs [A] for a sample.", "You swab [A] for a DNA sample.") + used = 1 + name = "swab of DNA from [A]" + desc = "[initial(desc)]
\blue The label on the vial reads 'Sample of DNA from [A].'." + update_icon() + return + if("Gunshot Residue") + var/obj/item/clothing/B = A + gsr = B.gsr + user.visible_message("[user] swabs [A] for a sample.", "You swab [A] for a GSR sample.") + used = 1 + name = "swab of [A] for GSR tests" + desc = "[initial(desc)]
\blue The label on the vial reads 'GSR Sample from [A].'" + update_icon() + return + if("Cancel") + return + + else if (istype(A, /obj/item/)) + if(!isnull(A.stored_DNA)) + dna = A.stored_DNA + user.visible_message("[user] swabs [A] for a sample.", "You swab [A] for a DNA sample.") + used = 1 + desc = "[initial(desc)]
\blue The label on the vial reads 'Sample of DNA from [A].'." + update_icon() + return + + + +/obj/item/weapon/forensics/swab/update_icon() + if(used == 0) + icon_state = "swab" + if(used == 1) + icon_state = "swab_used" + +//crime scene kit +/obj/item/weapon/storage/briefcase/crimekit + name = "Crime Scene Kit" + desc = "A stainless steel-plated carrycase for all your forensic needs. Feels heavy." + icon = 'forensics.dmi' + icon_state = "case" + item_state = "case" + storage_slots=14 + max_combined_w_class = 42 + +//microscope slide code and such +/obj/item/weapon/storage/box/slides + name = "microscope slide box" + icon_state = "solution_trays" + + New() + ..() + new /obj/item/weapon/forensics/slide( src ) + new /obj/item/weapon/forensics/slide( src ) + new /obj/item/weapon/forensics/slide( src ) + new /obj/item/weapon/forensics/slide( src ) + new /obj/item/weapon/forensics/slide( src ) + new /obj/item/weapon/forensics/slide( src ) + new /obj/item/weapon/forensics/slide( src ) + +/obj/item/weapon/forensics/slide + name = "microscope slide" + desc = "A pair of thin glass panes used in the examination of samples beneath a microscope." + icon = 'forensics.dmi' + icon_state = "slide" + flags = FPRINT | TABLEPASS | CONDUCT | NOBLUDGEON + w_class = 1.0 + var/slidesamp = 0 + var/gsr = 0 + var/list/fibers_complete = list() + +/obj/item/weapon/forensics/slide/attackby(obj/item/weapon/W as obj, mob/user as mob) + if(slidesamp == 1) + user << "\red There is already a sample in the slide." + return + if(istype (W, /obj/item/weapon/forensics/swab)) + var/obj/item/weapon/forensics/swab/D = W + user << "\blue You insert the sample into the slide." + slidesamp = 1 + user.drop_item() + D.loc = src + gsr = D.gsr + icon_state = "slideswab" + update_icon() + else if(istype (W, /obj/item/weapon/fiberbag)) + var/obj/item/weapon/fiberbag/E = W + user << "\blue You insert the sample into the slide." + slidesamp = 1 + user.drop_item() + E.loc = src + fibers_complete = E.fibers_complete + icon_state = "slidefiber" + update_icon() + else + user << "\red You don't think this will fit." + return + +/obj/item/weapon/forensics/slide/verb/verb_remove_from_slide() + set category = "Object" + set name = "Empty Slide" + set src in usr + + if(issilicon(usr)) + return + + var/obj/item/weapon/W = locate() in src + if(W) + usr << "You remove \the [W] from \the [src]." + src.slidesamp = 0 + src.gsr = 0 + src.fibers_complete = list() + src.update_icon() + W.loc = get_turf(src) + return + +/obj/item/weapon/forensics/slide/update_icon() + if(slidesamp == 0) + icon_state = "slide" + +//microscope code itself +/obj/machinery/microscope + name = "high powered electron microscope" + desc = "A highly advanced microscope capable of zooming up to 3000x." + icon = 'forensics.dmi' + icon_state = "microscope" + anchored = 1 + var/obj/item/weapon/forensics/sample = null + var/report_num = 0 + density = 1 + +/obj/machinery/microscope/attackby(obj/item/weapon/W as obj, mob/user as mob) + if(sample) + user << "\red There is already a slide in the microscope." + return + else if(istype(W, /obj/item/weapon/forensics/slide)) + user << "\blue You insert the slide into the microscope." + sample = W + user.drop_item() + W.loc = src + update_icon() + return + else if(istype(W, /obj/item/weapon/f_card)) + user << "\blue You insert the fingerprint card into the microscope." + sample = W + user.drop_item() + W.loc = src + update_icon() + return + +/obj/machinery/microscope/attack_hand(mob/user) + var/obj/item/weapon/W = locate() in src + if(sample) + switch(alert("What would you like to do?",,"Examine for GSR","Examine Fingerprints","Examine Fibers","Cancel")) + if("Examine for GSR") + if(istype(W, /obj/item/weapon/forensics/slide)) + user << "\blue The microscope whirrs as you examine the sample." + spawn(25) + var/obj/item/weapon/forensics/slide/C = W + if(C.gsr == 0) + user << "\red No traces of gunshot residue were found." + else + user << "\blue Printing findings now..." + var/obj/item/weapon/paper/P = new(src) + P.name = "GSR report #[++report_num]: [C.name]" + P.stamped = list(/obj/item/weapon/stamp) + P.overlays = list("paper_stamped") + + var/data = "No information available." + if(C.gsr != null) + data = "Molecular analysis on provided sample has determined the presence of chemicals composing gunpowder.

" + data += "GSR Analysis: Positive
Significant amount of residue detected.

" + else + data += "No residue found.
" + P.info = "GSR analysis report #[report_num]
" + P.info += "Scanned item:
[C.name]

" + data + P.loc = src.loc + P.update_icon() + return + else + user << "\red I don't think that is applicable here..." + return + + if("Examine Fingerprints") + if(istype(W, /obj/item/weapon/f_card)) + user << "\blue The microscope whirrs as you examine the sample." + spawn(25) + user << "\blue Printing findings now..." + var/obj/item/weapon/paper/Y = new(src) + Y.name = "Fingerprint report #[++report_num]: [W.name]" + Y.stamped = list(/obj/item/weapon/stamp) + Y.overlays = list("paper_stamped") + + var/data = "No information available." + var/obj/item/weapon/f_card/B = W + if(B.complete_prints != null) + data = "Surface analysis on provided card has determined the presence of uique fingerprint strings.

" + for(var/prints in B.complete_prints) + data += "\blue Fingerprint string: [B.complete_prints[prints]]

" + else + data += "No fingerprints found.
" + Y.info = "Fingerprint analysis report #[report_num]
" + Y.info += "Scanned item:
[B.name]

" + data + Y.loc = src.loc + Y.update_icon() + return + else + user << "\red I don't think that is applicable here..." + return + + if("Examine Fibers") + if(istype(W, /obj/item/weapon/forensics/slide)) + user << "\blue The microscope whirrs as you examine the sample." + spawn(25) + user << "\blue Printing findings now..." + var/obj/item/weapon/paper/Z = new(src) + Z.name = "Fiber report #[++report_num]: [W.name]" + Z.stamped = list(/obj/item/weapon/stamp) + Z.overlays = list("paper_stamped") + + var/data = "No information available." + var/obj/item/weapon/fiberbag/D = W + if(D.fibers_complete != null) + data = "Molecular analysis on provided sample has determined the presence of unique fiber strings.

" + for(var/j = 1, j <= D.fibers_complete.len, j++) + data += "\blue Most likely match for fibers: [D.fibers_complete[j]]

" + else + data += "No fibers found.
" + Z.info = "Fiber analysis report #[report_num]
" + Z.info += "Scanned item:
[D.name]

" + data + Z.loc = src.loc + Z.update_icon() + return + else + user << "\red I don't think that is applicable here..." + return + if("Cancel") + return + else + user << "\blue The microscope has no sample." + +/obj/machinery/microscope/verb/verb_remove_slide() + set category = "Object" + set name = "Remove Sample" + set src in oview(1) + + if(issilicon(usr)) + return + + var/obj/item/weapon/W = locate() in src + if(W) + usr << "You remove \the [W] from \the [src]." + src.sample = null + src.update_icon() + W.loc = get_turf(src) + return + else + usr << "This microscope does not have a sample in it." + +/obj/machinery/microscope/update_icon() + if(sample) + icon_state = "microscopeslide" + else + icon_state = "microscope" + +//fingerprint powder +/obj/item/weapon/forensics/powder + name = "fingerprint powder" + desc = "A jar containing aluminum powder and a specialized brush." + icon = 'forensics.dmi' + icon_state = "dust" + var/list/complete_prints = list() + var/stored = list() + var/data_enty = list() + w_class = 1.0 + +/obj/item/weapon/forensics/powder/afterattack(atom/A as obj|turf|area, mob/user as mob, proximity) + if(!proximity) return + if(loc != user) + return + + add_fingerprint(user) + + if ((!A.fingerprints || !A.fingerprints.len)) + user.visible_message("\blue [user] carefully begins to print [A] using their [src].","\blue Unable to locate any fingerprints on [A]!") + return 0 + + if(!A.fingerprints || !A.fingerprints.len) + if(A.fingerprints) + del(A.fingerprints) + else + user << "\blue You managed to isolate [A.fingerprints.len] fingerprints and transfer them to an adhesive sheet." + /* for(var/i in A.fingerprints) + var/print = A.fingerprints[i] + var/obj/item/weapon/f_card/F = new /obj/item/weapon/f_card( user.loc ) + F.complete_prints += print + F.name = "[initial(name)] from ([A])" + F.icon_state = "fingerprint1" */ + //We keep this, because it's the start of the string. + var/list/data_entry = stored + if(islist(data_entry)) //Yay, it was already stored! + //Merge the fingerprints. + complete_prints = data_entry//[1] Starting the main part of the hack: I'm removing the number references from the code, hopefully I can use a singular sum_list commabnd as a result. + for(var/print in A.fingerprints) + var/merged_print = complete_prints[print] + if(!merged_print) + complete_prints[print] = A.fingerprints[print] + else + complete_prints[print] = stringmerge(complete_prints[print],A.fingerprints[print]) + //We nix EVERYTHING that that has to do with non-print bullshite. So, yah, shorter code. + var/list/sum_list//[4] Pack it back up! || Except not 4 anymore, just one + sum_list/*[1]*/ = A.fingerprints ? A.fingerprints.Copy() : null + //nixed all of the sum_list commands that didn't do shit. + stored = sum_list + var/obj/item/weapon/f_card/F = new /obj/item/weapon/f_card( user.loc ) + F.complete_prints = stored + stored = list() + data_enty = list() + complete_prints = list() + F.name = "fingerprint card from ([A])" + F.icon_state = "fingerprint1" + return 0 + +//fiber collection +/obj/item/weapon/fiberbag + name = "fiber bag" + desc = "Used to hold fiber evidence for the detective." + icon = 'icons/obj/forensics.dmi' + icon_state = "fiberbag" + var/list/fibers_complete = list() + +/obj/item/weapon/forensics/fiberkit + name = "Fiber Collection Kit" + desc = "A magnifying glass and tweezers. Used to lift suit fibers." + icon = 'forensics.dmi' + icon_state = "m_glass" + var/list/fibers_complete = list() + var/stored = list() + var/data_enty = list() + +/obj/item/weapon/forensics/fiberkit/afterattack(atom/A as obj|turf|area, mob/user as mob, proximity) + if(!proximity) return + if(loc != user) + return + + add_fingerprint(user) + + if((!A.suit_fibers)) + user.visible_message("\blue [user] carefully begins to examine [A] using their [src].","\blue Unable to locate any suit fibers on [A]!") + return 0 + else + user << "\blue You managed to isolate [A.suit_fibers.len] samples of fiber evidence and transfer it to an evidence bag." + var/list/data_entry = stored + if(islist(data_entry)) //Yay, it was already stored! + //Do the stuff that does the stuff + fibers_complete = data_entry//[2] Again, not needed because single entry lists and not multiple entry cascades. + for(var/j = 1, j <= A.suit_fibers.len, j++) //Fibers~~~ + if(!fibers_complete.Find(A.suit_fibers[j])) //It isn't! Add! + fibers_complete += A.suit_fibers[j] + var/list/sum_list//[4] Pack it back up! || Except not 4 anymore, just one + sum_list/*[1]*/ = A.suit_fibers ? A.suit_fibers.Copy() : null + //nixed all of the sum_list commands that didn't do shit. + stored = sum_list + var/obj/item/weapon/fiberbag/F = new /obj/item/weapon/fiberbag( user.loc ) + F.fibers_complete = stored + stored = list() + data_enty = list() + fibers_complete = list() + F.name = "fiber bag from ([A])" + return 0 + + + +//Luminol and Goggles + +/obj/item/clothing/glasses/UV + name = "Ultraviolet Goggles" + desc = "A pair of specialized forensic glasses designed to detect luminol flouresence.." + icon_state = "thermal" + item_state = "glasses" + origin_tech = "magnets=3" + +/obj/item/weapon/reagent_containers/spray/luminol + name = "luminol bottle" + desc = "A bottle containing an ordorless, colorless liquid." + icon = 'icons/obj/forensics.dmi' + icon_state = "luminol" + item_state = "cleaner" + amount_per_transfer_from_this = 10 + possible_transfer_amounts = list(5,10) + volume = 250 + +/obj/item/weapon/reagent_containers/spray/luminol/New() + ..() + reagents.add_reagent("luminol", 250) + +//DNA machine + +/obj/machinery/dnaforensics + name = "DNA analyzer" + desc = "A high tech machine that is designed to read DNA samples properly." + icon = 'icons/obj/forensics.dmi' + icon_state = "dnaopen" + anchored = 1 + var/ui_title = "QuikScan DNA Analyzer" + var/range = 10 + density = 1 + + + //vars important for analyzing + var/obj/item/weapon/forensics/swab/bloodsamp = null + var/closed = 0 + var/scanning = 0 + var/scanner_progress = 0 + var/scanner_rate = 2.50 //scanning takes 40 seconds + var/last_process_worldtime = 0 + var/report_num = 0 + +/obj/machinery/dnaforensics/attackby(obj/item/weapon/forensics/swab/W as obj, mob/user as mob) + if(src.bloodsamp) + user << "\red There is already a sample in the machine." + return + if(src.closed == 1) + user << "\red Open the cover before inserting the sample." + return + if(W.used == 1) + src.bloodsamp = W + user.drop_item() + W.loc = src + user << "You insert [W] into the machine." + +/obj/machinery/dnaforensics/update_icon() + ..() + if(! (stat & (NOPOWER)) && (scanning) && (closed == 1)) + icon_state = "dnaworking" + else if(! (stat & (NOPOWER)) && (scanning != 1) && (closed == 1)) + icon_state = "dnaclosed" + else if(! (stat & (NOPOWER)) && (scanning != 1) && (closed == 0)) + icon_state = "dnaopen" + +/obj/machinery/dnaforensics/ui_interact(mob/user, ui_key = "main",var/datum/nanoui/ui = null) + if(stat & (NOPOWER)) return + if(user.stat || user.restrained()) return + + // this is the data which will be sent to the ui + var/data[0] + data["scan_progress"] = round(scanner_progress) + data["scanning"] = scanning + data["bloodsamp"] = (bloodsamp ? bloodsamp.name : "") + data["bloodsamp_desc"] = (bloodsamp ? (bloodsamp.desc ? bloodsamp.desc : "No information on record.") : "") + + ui = nanomanager.try_update_ui(user, src, ui_key, ui, data) + if (!ui) + // the ui does not exist, so we'll create a new() one + // for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm + ui = new(user, src, ui_key, "dnaforensics.tmpl", "QuikScan DNA Analyzer", 540, 326) + // when the ui is first opened this is the data it will use + ui.set_initial_data(data) + // open the new ui window + ui.open() + // auto update every Master Controller tick + ui.set_auto_update(1) + +/obj/machinery/dnaforensics/Topic(href, href_list) + if(stat & (NOPOWER)) + return 0 // don't update UIs attached to this object + + src.add_fingerprint(usr) + + if(href_list["scanItem"]) + if(scanning) + stop_scanning() + else + if(bloodsamp) + if(closed == 1) + scanner_progress = 0 + scanning = 1 + usr << "Scan initiated." + update_icon() + else + usr << "Please close sample lid before initiating scan." + else + usr << "Insert an item to scan." + + if(href_list["ejectItem"]) + if(bloodsamp) + bloodsamp.loc = src.loc + bloodsamp = null + + return 1 + +/obj/machinery/dnaforensics/process() + if(scanning) + if(!bloodsamp || bloodsamp.loc != src) + bloodsamp = null + stop_scanning() + else if(scanner_progress >= 100) + complete_scan() + return + else + //calculate time difference + var/deltaT = (world.time - last_process_worldtime) * 0.1 + scanner_progress = min(100, scanner_progress + scanner_rate * deltaT) + last_process_worldtime = world.time + +/obj/machinery/dnaforensics/proc/stop_scanning() + scanning = 0 + +/obj/machinery/dnaforensics/proc/complete_scan() + src.visible_message("\blue \icon[src] makes an insistent chime.", 2) + update_icon() + + if(bloodsamp) + var/obj/item/weapon/paper/P = new(src) + P.name = "[src] report #[++report_num]: [bloodsamp.name]" + P.stamped = list(/obj/item/weapon/stamp) + P.overlays = list("paper_stamped") + + //dna data itself + var/data = "No scan information available." + if(bloodsamp.dna != null) + data = "Spectometric analysis on provided sample has determined the presence of [bloodsamp.dna.len] strings of DNA.

" + for(var/blood in bloodsamp.dna) + data += "\blue Blood type: [bloodsamp.dna[blood]]
\nDNA: [blood]

" + else + data += "No DNA found.
" + P.info = "[src] analysis report #[report_num]
" + P.info += "Scanned item:
[bloodsamp.name]
[bloodsamp.desc]

" + data + P.loc = src.loc + P.update_icon() + stop_scanning() + update_icon() + return + +/obj/machinery/dnaforensics/attack_ai(mob/user as mob) + return src.attack_hand(user) + +/obj/machinery/dnaforensics/attack_paw(mob/user as mob) + return src.attack_hand(user) + +/obj/machinery/dnaforensics/attack_hand(mob/user as mob) + ui_interact(user) + +/obj/machinery/dnaforensics/verb/toggle_lid() + set category = "Object" + set name = "Toggle Lid" + set src in oview(1) + + if(issilicon(usr)) + return + + if(scanning) + usr << "You can't do that while [src] is scanning!" + return + + if(closed == 1) + icon_state = "dnaopen" + closed = 0 + src.update_icon() + else + icon_state = "dnaclosed" + closed = 1 + src.update_icon() + +/obj/machinery/printer + name = "printer" + desc = "Your standard printer." + icon = 'icons/obj/forensics.dmi' + icon_state = "printer" + var/printing = 0 + density = 1 + anchored = 1 \ No newline at end of file diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm index 69af9ce2..ffe888f0 100644 --- a/code/modules/mob/living/carbon/human/examine.dm +++ b/code/modules/mob/living/carbon/human/examine.dm @@ -72,6 +72,8 @@ if(w_uniform.blood_DNA) msg += "[t_He] [t_is] wearing \icon[w_uniform] [w_uniform.gender==PLURAL?"some":"a"] blood-stained [w_uniform.name][tie_msg]!\n" + else if(w_uniform.wasbloody == 2) + msg += "[t_He] [t_is] wearing \icon[w_uniform] [w_uniform.gender==PLURAL?"some":"a"] luminol-covered [w_uniform.name][tie_msg]!\n" else msg += "[t_He] [t_is] wearing \icon[w_uniform] \a [w_uniform][tie_msg].\n" @@ -79,6 +81,8 @@ if(head) if(head.blood_DNA) msg += "[t_He] [t_is] wearing \icon[head] [head.gender==PLURAL?"some":"a"] blood-stained [head.name] on [t_his] head!\n" + else if(head.wasbloody == 2) + msg += "[t_He] [t_is] wearing \icon[head] [head.gender==PLURAL?"some":"a"] luminol-covered [head.name] on [t_his] head!\n" else msg += "[t_He] [t_is] wearing \icon[head] \a [head] on [t_his] head.\n" @@ -86,6 +90,8 @@ if(wear_suit) if(wear_suit.blood_DNA) msg += "[t_He] [t_is] wearing \icon[wear_suit] [wear_suit.gender==PLURAL?"some":"a"] blood-stained [wear_suit.name]!\n" + else if(wear_suit.wasbloody == 2) + msg += "[t_He] [t_is] wearing \icon[wear_suit] [wear_suit.gender==PLURAL?"some":"a"] luminol-covered [wear_suit.name]!\n" else msg += "[t_He] [t_is] wearing \icon[wear_suit] \a [wear_suit].\n" @@ -93,6 +99,8 @@ if(s_store && !skipsuitstorage) if(s_store.blood_DNA) msg += "[t_He] [t_is] carrying \icon[s_store] [s_store.gender==PLURAL?"some":"a"] blood-stained [s_store.name] on [t_his] [wear_suit.name]!\n" + else if(s_store.wasbloody == 2) + msg += "[t_He] [t_is] carrying \icon[s_store] [s_store.gender==PLURAL?"some":"a"] luminol-covered [s_store.name] on [t_his] [wear_suit.name]!\n" else msg += "[t_He] [t_is] carrying \icon[s_store] \a [s_store] on [t_his] [wear_suit.name].\n" @@ -100,6 +108,8 @@ if(back) if(back.blood_DNA) msg += "[t_He] [t_has] \icon[back] [back.gender==PLURAL?"some":"a"] blood-stained [back] on [t_his] back.\n" + else if(back.wasbloody == 2) + msg += "[t_He] [t_has] \icon[back] [back.gender==PLURAL?"some":"a"] luminol-covered [back] on [t_his] back.\n" else msg += "[t_He] [t_has] \icon[back] \a [back] on [t_his] back.\n" @@ -107,6 +117,8 @@ if(l_hand) if(l_hand.blood_DNA) msg += "[t_He] [t_is] holding \icon[l_hand] [l_hand.gender==PLURAL?"some":"a"] blood-stained [l_hand.name] in [t_his] left hand!\n" + else if(l_hand.wasbloody == 2) + msg += "[t_He] [t_is] holding \icon[l_hand] [l_hand.gender==PLURAL?"some":"a"] luminol-covered [l_hand.name] in [t_his] left hand!\n" else msg += "[t_He] [t_is] holding \icon[l_hand] \a [l_hand] in [t_his] left hand.\n" @@ -114,6 +126,8 @@ if(r_hand) if(r_hand.blood_DNA) msg += "[t_He] [t_is] holding \icon[r_hand] [r_hand.gender==PLURAL?"some":"a"] blood-stained [r_hand.name] in [t_his] right hand!\n" + else if(r_hand.wasbloody == 2) + msg += "[t_He] [t_is] holding \icon[r_hand] [r_hand.gender==PLURAL?"some":"a"] luminol-covered [r_hand.name] in [t_his] right hand!\n" else msg += "[t_He] [t_is] holding \icon[r_hand] \a [r_hand] in [t_his] right hand.\n" @@ -121,6 +135,8 @@ if(gloves && !skipgloves) if(gloves.blood_DNA) msg += "[t_He] [t_has] \icon[gloves] [gloves.gender==PLURAL?"some":"a"] blood-stained [gloves.name] on [t_his] hands!\n" + else if(gloves.wasbloody == 2) + msg += "[t_He] [t_has] \icon[gloves] [gloves.gender==PLURAL?"some":"a"] luminol-covered [gloves.name] on [t_his] hands!\n" else msg += "[t_He] [t_has] \icon[gloves] \a [gloves] on [t_his] hands.\n" else if(blood_DNA) @@ -139,6 +155,8 @@ if(belt) if(belt.blood_DNA) msg += "[t_He] [t_has] \icon[belt] [belt.gender==PLURAL?"some":"a"] blood-stained [belt.name] about [t_his] waist!\n" + else if(belt.wasbloody == 2) + msg += "[t_He] [t_has] \icon[belt] [belt.gender==PLURAL?"some":"a"] luminol-covered [belt.name] about [t_his] waist!\n" else msg += "[t_He] [t_has] \icon[belt] \a [belt] about [t_his] waist.\n" @@ -146,6 +164,8 @@ if(shoes && !skipshoes) if(shoes.blood_DNA) msg += "[t_He] [t_is] wearing \icon[shoes] [shoes.gender==PLURAL?"some":"a"] blood-stained [shoes.name] on [t_his] feet!\n" + else if(shoes.wasbloody == 2) + msg += "[t_He] [t_is] wearing \icon[shoes] [shoes.gender==PLURAL?"some":"a"] luminol-covered [shoes.name] on [t_his] feet!\n" else msg += "[t_He] [t_is] wearing \icon[shoes] \a [shoes] on [t_his] feet.\n" else if(feet_blood_DNA) @@ -155,6 +175,8 @@ if(wear_mask && !skipmask) if(wear_mask.blood_DNA) msg += "[t_He] [t_has] \icon[wear_mask] [wear_mask.gender==PLURAL?"some":"a"] blood-stained [wear_mask.name] on [t_his] face!\n" + else if(wear_mask.wasbloody == 2) + msg += "[t_He] [t_has] \icon[wear_mask] [wear_mask.gender==PLURAL?"some":"a"] luminol-covered [wear_mask.name] on [t_his] face!\n" else msg += "[t_He] [t_has] \icon[wear_mask] \a [wear_mask] on [t_his] face.\n" @@ -162,6 +184,8 @@ if(glasses && !skipeyes) if(glasses.blood_DNA) msg += "[t_He] [t_has] \icon[glasses] [glasses.gender==PLURAL?"some":"a"] blood-stained [glasses] covering [t_his] eyes!\n" + else if(glasses.wasbloody == 2) + msg += "[t_He] [t_has] \icon[glasses] [glasses.gender==PLURAL?"some":"a"] luminol-covered [glasses] covering [t_his] eyes!\n" else msg += "[t_He] [t_has] \icon[glasses] \a [glasses] covering [t_his] eyes.\n" diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm index c1146fe5..55b36c61 100644 --- a/code/modules/mob/living/carbon/human/life.dm +++ b/code/modules/mob/living/carbon/human/life.dm @@ -1236,7 +1236,7 @@ if(copytext(hud.icon_state,1,4) == "hud") //ugly, but icon comparison is worse, I believe client.images.Remove(hud) - client.screen.Remove(global_hud.blurry, global_hud.druggy, global_hud.vimpaired, global_hud.darkMask, global_hud.nvg) + client.screen.Remove(global_hud.blurry, global_hud.druggy, global_hud.vimpaired, global_hud.darkMask, global_hud.nvg, global_hud.lum) update_action_buttons() @@ -1388,6 +1388,9 @@ if(istype(G,/obj/item/clothing/glasses/night)) see_invisible = SEE_INVISIBLE_MINIMUM client.screen += global_hud.nvg + if(istype(G,/obj/item/clothing/glasses/UV)) + see_invisible = SEE_INVISIBLE_LIVING + client.screen += global_hud.lum /* HUD shit goes here, as long as it doesn't modify sight flags */ // The purpose of this is to stop xray and w/e from preventing you from using huds -- Love, Doohl diff --git a/code/modules/mob/living/carbon/human/update_icons.dm b/code/modules/mob/living/carbon/human/update_icons.dm index 52576d03..d4d6eb26 100644 --- a/code/modules/mob/living/carbon/human/update_icons.dm +++ b/code/modules/mob/living/carbon/human/update_icons.dm @@ -578,6 +578,11 @@ proc/get_damage_icon_part(damage_state, body_part) bloodsies.color = w_uniform.blood_color standing.overlays += bloodsies + if(w_uniform.wasbloody == 2) + var/image/bloodsies = image("icon" = 'icons/effects/blood.dmi', "icon_state" = "uniformblood") + bloodsies.color = "#007fff" + standing.overlays += bloodsies + if(w_uniform:hastie) //WE CHECKED THE TYPE ABOVE. THIS REALLY SHOULD BE FINE. var/tie_color = w_uniform:hastie.item_color if(!tie_color) tie_color = w_uniform:hastie.icon_state @@ -624,6 +629,10 @@ proc/get_damage_icon_part(damage_state, body_part) var/image/bloodsies = image("icon" = 'icons/effects/blood.dmi', "icon_state" = "bloodyhands") bloodsies.color = gloves.blood_color standing.overlays += bloodsies + else if(gloves.wasbloody == 2) + var/image/bloodsies = image("icon" = 'icons/effects/blood.dmi', "icon_state" = "bloodyhands") + bloodsies.color = "#007fff" + standing.overlays += bloodsies gloves.screen_loc = ui_gloves overlays_standing[GLOVES_LAYER] = standing else @@ -664,6 +673,10 @@ proc/get_damage_icon_part(damage_state, body_part) var/image/bloodsies = image("icon" = 'icons/effects/blood.dmi', "icon_state" = "shoeblood") bloodsies.color = shoes.blood_color standing.overlays += bloodsies + else if(shoes.wasbloody == 2) + var/image/bloodsies = image("icon" = 'icons/effects/blood.dmi', "icon_state" = "shoeblood") + bloodsies.color = "#007fff" + standing.overlays += bloodsies overlays_standing[SHOES_LAYER] = standing else if(feet_blood_DNA) @@ -697,6 +710,10 @@ proc/get_damage_icon_part(damage_state, body_part) var/image/bloodsies = image("icon" = 'icons/effects/blood.dmi', "icon_state" = "helmetblood") bloodsies.color = head.blood_color standing.overlays += bloodsies + if(head.wasbloody == 2) + var/image/bloodsies = image("icon" = 'icons/effects/blood.dmi', "icon_state" = "helmetblood") + bloodsies.color = "#007fff" + standing.overlays += bloodsies overlays_standing[HEAD_LAYER] = standing else overlays_standing[HEAD_LAYER] = null @@ -729,6 +746,12 @@ proc/get_damage_icon_part(damage_state, body_part) bloodsies.color = wear_suit.blood_color standing.overlays += bloodsies + if(wear_suit.wasbloody == 2) + var/obj/item/clothing/suit/S = wear_suit + var/image/bloodsies = image("icon" = 'icons/effects/blood.dmi', "icon_state" = "[S.blood_overlay_type]blood") + bloodsies.color = "#007fff" + standing.overlays += bloodsies + overlays_standing[SUIT_LAYER] = standing update_tail_showing(0) @@ -756,6 +779,10 @@ proc/get_damage_icon_part(damage_state, body_part) var/image/bloodsies = image("icon" = 'icons/effects/blood.dmi', "icon_state" = "maskblood") bloodsies.color = wear_mask.blood_color standing.overlays += bloodsies + else if( !istype(wear_mask, /obj/item/clothing/mask/cigarette) && wear_mask.wasbloody == 2 ) + var/image/bloodsies = image("icon" = 'icons/effects/blood.dmi', "icon_state" = "maskblood") + bloodsies.color = wear_suit.blood_color + standing.overlays += bloodsies overlays_standing[FACEMASK_LAYER] = standing else overlays_standing[FACEMASK_LAYER] = null diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm index a599b678..8ffbdb21 100644 --- a/code/modules/mob/living/living_defines.dm +++ b/code/modules/mob/living/living_defines.dm @@ -30,6 +30,7 @@ var/t_oxygen = null var/t_sl_gas = null var/t_n2 = null + var/gsr = 0 //forensics variable for gunshot residue var/now_pushing = null diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm index 5fa82687..f62c25d9 100644 --- a/code/modules/projectiles/gun.dm +++ b/code/modules/projectiles/gun.dm @@ -198,6 +198,15 @@ else user.update_inv_r_hand() + if(istype(src, /obj/item/weapon/gun/projectile) && ishuman(user)) + var/mob/living/carbon/human/H = user + if(!H.gloves) + H.gsr = 1 + else + var/obj/item/clothing/G = H.gloves + G.gsr = 1 + + /obj/item/weapon/gun/proc/can_fire() return load_into_chamber() diff --git a/code/modules/reagents/Chemistry-Reagents.dm b/code/modules/reagents/Chemistry-Reagents.dm index dbebe839..7b53a3a4 100644 --- a/code/modules/reagents/Chemistry-Reagents.dm +++ b/code/modules/reagents/Chemistry-Reagents.dm @@ -958,6 +958,62 @@ datum ..() return + + + luminol + name = "Luminol" + id = "luminol" + description = "A compound that interacts with blood on the molecular level." + reagent_state = LIQUID + color = "#F2F3F4" + overdose = REAGENTS_OVERDOSE + + reaction_obj(var/obj/O, var/volume) + if(istype(O,/obj/effect/decal/cleanable/blood)) + var/obj/effect/decal/cleanable/blood/W + W.invisibility = 1 + W.color = "#007fff" + W.basecolor = "#007fff" + else if (istype(O, /obj/item/)) + var/obj/item/I = O + I.reveal_blood() + return + else + return + + reaction_turf(var/turf/T, var/volume) + if(volume >= 1) + if(istype(T, /turf/simulated)) + var/turf/simulated/S = T + S.dirt = 0 + T.clean_blood() + for(var/obj/effect/decal/cleanable/blood/C in T.contents) + C.invisibility = 1 + C.basecolor = "#007fff" + C.color = "#007fff" + if(istype(C, /obj/effect/decal/cleanable/blood/tracks/footprints)) + var/obj/effect/decal/cleanable/blood/tracks/footprints/D = C + for(var/datum/fluidtrack/E in D.stack) + E.basecolor = "#007fff" + D.update_icon() + return + + reaction_mob(var/mob/living/carbon/human/H, var/method=TOUCH, var/volume) + if(H.head) + if(H.head.reveal_blood()) + H.update_inv_head(0) + if(H.wear_suit) + if(H.wear_suit.reveal_blood()) + H.update_inv_wear_suit(0) + else if(H.w_uniform) + if(H.w_uniform.reveal_blood()) + H.update_inv_w_uniform(0) + if(H.shoes) + if(H.shoes.reveal_blood()) + H.update_inv_shoes(0) + else + return + space_cleaner name = "Space cleaner" id = "cleaner" @@ -967,11 +1023,14 @@ datum overdose = REAGENTS_OVERDOSE reaction_obj(var/obj/O, var/volume) - if(istype(O,/obj/effect/decal/cleanable)) + if(istype(O,/obj/effect/decal/cleanable/blood)) + O.invisibility = INVISIBILITY_MAXIMUM + return + else if(istype(O,/obj/effect/decal/cleanable/)) del(O) else - if(O) - O.clean_blood() + O.clean_blood() + return reaction_turf(var/turf/T, var/volume) if(volume >= 1) @@ -980,8 +1039,13 @@ datum S.dirt = 0 T.clean_blood() for(var/obj/effect/decal/cleanable/C in T.contents) - src.reaction_obj(C, volume) - del(C) + if(istype (C,/obj/effect/decal/cleanable/blood)) + src.reaction_obj(C, volume) + C.invisibility = INVISIBILITY_MAXIMUM + return + else + src.reaction_obj(C, volume) + del(C) for(var/mob/living/carbon/slime/M in T) M.adjustToxLoss(rand(5,10)) diff --git a/code/modules/reagents/Chemistry-Recipes.dm b/code/modules/reagents/Chemistry-Recipes.dm index 99d219f2..573147a2 100644 --- a/code/modules/reagents/Chemistry-Recipes.dm +++ b/code/modules/reagents/Chemistry-Recipes.dm @@ -713,6 +713,12 @@ datum required_reagents = list("ammonia" = 1, "water" = 1) result_amount = 2 + luminol + name = "Luminol" + id = "luminol" + result = "luminol" + required_reagents = list("hydrogen" = 3, "carbon" = 3, "ammonia" = 3) + plantbgone name = "Plant-B-Gone" id = "plantbgone" diff --git a/icons/obj/forensics.dmi b/icons/obj/forensics.dmi new file mode 100644 index 0000000000000000000000000000000000000000..63225c30125c65f376760705864a9194d5faeb3d GIT binary patch literal 16704 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 Status +
+
+ {{:~link(scanning ? 'Halt Scan' : 'Begin Scan', 'signal-diag', {'scanItem' : 1}, null)}} +
+
+ {{:~link('Eject item', 'eject', {'ejectItem' : 1}, (bloodsamp && !scanning) ? null : 'disabled')}} +
+
+

Scanner

+
+
Scan progress:
+
+ {{:~displayBar(scan_progress, 0, 100, 'good')}} + {{:scan_progress}} % +
+
+ {{if scan_progress >= 100}} + Scan completed successfully. + {{/if}} +
+
+
+
+
Item:
+
+ {{if bloodsamp}} + {{:bloodsamp}} + {{else}} + No item inserted + {{/if}} +
+
+
+
Heuristic analysis:
+
+ {{if bloodsamp_desc}} + {{:bloodsamp_desc}} + {{/if}} +
+
+
\ No newline at end of file