From 1244c4a5fd0e0bda839823157b5f3ab887299a12 Mon Sep 17 00:00:00 2001 From: Zuhayr Date: Sat, 5 Sep 2015 23:08:14 +0930 Subject: [PATCH] Porting forensics from Aurora. --- baystation12.dme | 16 +- code/__defines/misc.dm | 36 +- code/datums/supplypacks.dm | 14 + code/game/atoms.dm | 10 +- code/game/jobs/job/security.dm | 3 +- code/game/machinery/buttons.dm | 2 - code/game/machinery/door_control.dm | 16 - code/game/machinery/doors/airlock.dm | 2 +- code/game/machinery/doors/door.dm | 2 - code/game/machinery/igniter.dm | 2 - .../effects/decals/Cleanable/humans.dm | 13 + .../effects/decals/Cleanable/tracks.dm | 7 + code/game/objects/effects/decals/cleanable.dm | 3 + code/game/objects/items.dm | 14 +- .../crates_lockers/closets/secure/security.dm | 1 - code/game/turfs/simulated.dm | 5 + code/game/turfs/turf.dm | 3 + code/modules/clothing/clothing.dm | 6 + code/modules/clothing/suits/jobs.dm | 4 +- code/modules/detectivework/footprints.dm | 0 code/modules/detectivework/forensics.dm | 4 + .../detectivework/microscope/dnascanner.dm | 153 + .../detectivework/microscope/microscope.dm | 108 + .../detectivework/microscope/slides.dm | 42 + code/modules/detectivework/scanner.dm | 115 - .../modules/detectivework/scanning_console.dm | 313 - code/modules/detectivework/tools/crimekit.dm | 17 + .../{evidence.dm => tools/evidencebag.dm} | 46 - code/modules/detectivework/tools/luminol.dm | 13 + .../{footprints_and_rag.dm => tools/rag.dm} | 26 +- .../detectivework/tools/sample_kits.dm | 158 + code/modules/detectivework/tools/storage.dm | 44 + code/modules/detectivework/tools/swabs.dm | 119 + code/modules/detectivework/tools/uvlight.dm | 69 + code/modules/mob/living/carbon/carbon.dm | 2 - code/modules/mob/living/carbon/human/human.dm | 6 + .../mob/living/carbon/human/human_defines.dm | 1 + code/modules/organs/blood.dm | 2 + code/modules/projectiles/ammunition.dm | 2 + code/modules/projectiles/guns/projectile.dm | 10 + code/modules/reagents/Chemistry-Reagents.dm | 7 - .../Chemistry-Reagents-Medicine.dm | 10 +- .../Chemistry-Reagents-Other.dm | 25 +- code/modules/reagents/Chemistry-Recipes.dm | 33 +- .../reagents/reagent_containers/spray.dm | 16 +- icons/obj/device.dmi | Bin 24472 -> 24779 bytes icons/obj/forensics.dmi | Bin 0 -> 16704 bytes maps/exodus-1.dmm | 11418 ++++++++-------- nano/templates/dnaforensics.tmpl | 45 + 49 files changed, 6666 insertions(+), 6297 deletions(-) create mode 100644 code/modules/detectivework/footprints.dm create mode 100644 code/modules/detectivework/microscope/dnascanner.dm create mode 100644 code/modules/detectivework/microscope/microscope.dm create mode 100644 code/modules/detectivework/microscope/slides.dm delete mode 100644 code/modules/detectivework/scanner.dm delete mode 100644 code/modules/detectivework/scanning_console.dm create mode 100644 code/modules/detectivework/tools/crimekit.dm rename code/modules/detectivework/{evidence.dm => tools/evidencebag.dm} (69%) create mode 100644 code/modules/detectivework/tools/luminol.dm rename code/modules/detectivework/{footprints_and_rag.dm => tools/rag.dm} (99%) create mode 100644 code/modules/detectivework/tools/sample_kits.dm create mode 100644 code/modules/detectivework/tools/storage.dm create mode 100644 code/modules/detectivework/tools/swabs.dm create mode 100644 code/modules/detectivework/tools/uvlight.dm create mode 100644 icons/obj/forensics.dmi create mode 100644 nano/templates/dnaforensics.tmpl diff --git a/baystation12.dme b/baystation12.dme index aaad35c05b6..47a1aedca6f 100644 --- a/baystation12.dme +++ b/baystation12.dme @@ -1048,11 +1048,19 @@ #include "code\modules\clothing\under\jobs\security.dm" #include "code\modules\clothing\under\xenos\resomi.dm" #include "code\modules\customitems\item_spawning.dm" -#include "code\modules\detectivework\evidence.dm" -#include "code\modules\detectivework\footprints_and_rag.dm" +#include "code\modules\detectivework\footprints.dm" #include "code\modules\detectivework\forensics.dm" -#include "code\modules\detectivework\scanner.dm" -#include "code\modules\detectivework\scanning_console.dm" +#include "code\modules\detectivework\microscope\dnascanner.dm" +#include "code\modules\detectivework\microscope\microscope.dm" +#include "code\modules\detectivework\microscope\slides.dm" +#include "code\modules\detectivework\tools\crimekit.dm" +#include "code\modules\detectivework\tools\evidencebag.dm" +#include "code\modules\detectivework\tools\luminol.dm" +#include "code\modules\detectivework\tools\rag.dm" +#include "code\modules\detectivework\tools\sample_kits.dm" +#include "code\modules\detectivework\tools\storage.dm" +#include "code\modules\detectivework\tools\swabs.dm" +#include "code\modules\detectivework\tools\uvlight.dm" #include "code\modules\economy\Accounts.dm" #include "code\modules\economy\Accounts_DB.dm" #include "code\modules\economy\ATM.dm" diff --git a/code/__defines/misc.dm b/code/__defines/misc.dm index 96e3d26a989..340ce4d32f5 100644 --- a/code/__defines/misc.dm +++ b/code/__defines/misc.dm @@ -63,24 +63,24 @@ #define LIFE_HUD 10 // STATUS_HUD that only reports dead or alive //some colors -#define COLOR_WHITE "#FFFFFF" -#define COLOR_SILVER "#C0C0C0" -#define COLOR_GRAY "#808080" -#define COLOR_BLACK "#000000" -#define COLOR_RED "#FF0000" -#define COLOR_MAROON "#800000" -#define COLOR_YELLOW "#FFFF00" -#define COLOR_OLIVE "#808000" -#define COLOR_LIME "#00FF00" -#define COLOR_GREEN "#008000" -#define COLOR_CYAN "#00FFFF" -#define COLOR_TEAL "#008080" -#define COLOR_BLUE "#0000FF" -#define COLOR_NAVY "#000080" -#define COLOR_PINK "#FF00FF" -#define COLOR_PURPLE "#800080" -#define COLOR_ORANGE "#FF9900" - +#define COLOR_WHITE "#FFFFFF" +#define COLOR_SILVER "#C0C0C0" +#define COLOR_GRAY "#808080" +#define COLOR_BLACK "#000000" +#define COLOR_RED "#FF0000" +#define COLOR_MAROON "#800000" +#define COLOR_YELLOW "#FFFF00" +#define COLOR_OLIVE "#808000" +#define COLOR_LIME "#00FF00" +#define COLOR_GREEN "#008000" +#define COLOR_CYAN "#00FFFF" +#define COLOR_TEAL "#008080" +#define COLOR_BLUE "#0000FF" +#define COLOR_NAVY "#000080" +#define COLOR_PINK "#FF00FF" +#define COLOR_PURPLE "#800080" +#define COLOR_ORANGE "#FF9900" +#define COLOR_LUMINOL "#66FFFF" // Shuttles. // These define the time taken for the shuttle to get to the space station, and the time before it leaves again. diff --git a/code/datums/supplypacks.dm b/code/datums/supplypacks.dm index 2e84cec43ca..14f032ab9ad 100644 --- a/code/datums/supplypacks.dm +++ b/code/datums/supplypacks.dm @@ -43,6 +43,20 @@ var/list/all_supply_groups = list("Operations","Security","Hospitality","Enginee group = "Security" hidden = 1 +/datum/supply_packs/forensics + name = "Auxiliary forensic tools" + contains = list(/obj/item/weapon/forensics/sample_kit, + /obj/item/weapon/forensics/sample_kit/powder, + /obj/item/weapon/storage/box/swabs, + /obj/item/weapon/storage/box/swabs, + /obj/item/weapon/storage/box/swabs, + /obj/item/weapon/storage/box/slides, + /obj/item/weapon/reagent_containers/spray/luminol) + cost = 30 + containertype = /obj/structure/closet/crate + containername = "Auxiliary forensic tools" + group = "Security" + /datum/supply_packs/food name = "Kitchen supply crate" contains = list(/obj/item/weapon/reagent_containers/food/condiment/flour, diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 77279ab1171..953967764e1 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -6,12 +6,14 @@ var/list/fingerprintshidden var/fingerprintslast = null var/list/blood_DNA + var/was_bloodied var/blood_color var/last_bumped = 0 var/pass_flags = 0 var/throwpass = 0 var/germ_level = GERM_LEVEL_AMBIENT // The higher the germ level, the more germ on the atom. var/simulated = 1 //filter for actions - used by lighting overlays + var/fluorescent // Shows up under a UV light. ///Chemistry. var/datum/reagents/reagents = null @@ -23,6 +25,9 @@ //Detective Work, used for the duplicate data points kept in the scanners var/list/original_atom +/atom/proc/reveal_blood() + return + /atom/proc/assume_air(datum/gas_mixture/giver) return null @@ -304,7 +309,7 @@ its easier to just keep the beam vertical. fingerprints = list() //Hash this shit. - var/full_print = md5(H.dna.uni_identity) + var/full_print = H.get_full_print() // Add the fingerprints // @@ -387,6 +392,7 @@ its easier to just keep the beam vertical. 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 = 1 blood_color = "#A10808" if(istype(M)) if (!istype(M.dna, /datum/dna)) @@ -406,10 +412,10 @@ its easier to just keep the beam vertical. if(toxvomit) this.icon_state = "vomittox_[pick(1,4)]" - /atom/proc/clean_blood() if(!simulated) return + fluorescent = 0 src.germ_level = 0 if(istype(blood_DNA, /list)) blood_DNA = null diff --git a/code/game/jobs/job/security.dm b/code/game/jobs/job/security.dm index e6d5daf46ce..d2574c50c8c 100644 --- a/code/game/jobs/job/security.dm +++ b/code/game/jobs/job/security.dm @@ -111,12 +111,11 @@ H.equip_to_slot_or_del(new /obj/item/weapon/flame/lighter/zippo(H), slot_l_store) if(H.backbag == 1)//Why cant some of these things spawn in his office? H.equip_to_slot_or_del(new /obj/item/weapon/storage/box/evidence(H), slot_l_hand) - H.equip_to_slot_or_del(new /obj/item/device/detective_scanner(H), slot_r_store) else H.equip_to_slot_or_del(new /obj/item/weapon/storage/box/evidence(H), slot_in_backpack) - H.equip_to_slot_or_del(new /obj/item/device/detective_scanner(H), slot_in_backpack) if(H.mind.role_alt_title && H.mind.role_alt_title == "Forensic Technician") H.equip_to_slot_or_del(new /obj/item/clothing/suit/storage/forensics/blue(H), slot_wear_suit) + H.equip_to_slot_or_del(new /obj/item/weapon/storage/briefcase/crimekit, slot_r_hand) else H.equip_to_slot_or_del(new /obj/item/clothing/suit/storage/det_trench(H), slot_wear_suit) H.equip_to_slot_or_del(new /obj/item/clothing/head/det(H), slot_head) diff --git a/code/game/machinery/buttons.dm b/code/game/machinery/buttons.dm index ec844b6004d..70557a2e18b 100644 --- a/code/game/machinery/buttons.dm +++ b/code/game/machinery/buttons.dm @@ -15,6 +15,4 @@ return src.attack_hand(user) /obj/machinery/button/attackby(obj/item/weapon/W, mob/user as mob) - if(istype(W, /obj/item/device/detective_scanner)) - return return src.attack_hand(user) \ No newline at end of file diff --git a/code/game/machinery/door_control.dm b/code/game/machinery/door_control.dm index 6662b20b671..b51d72ccd8e 100644 --- a/code/game/machinery/door_control.dm +++ b/code/game/machinery/door_control.dm @@ -24,22 +24,6 @@ user << "Error, no route to host." /obj/machinery/button/remote/attackby(obj/item/weapon/W, mob/user as mob) - /* For later implementation - if (istype(W, /obj/item/weapon/screwdriver)) - { - if(wiresexposed) - icon_state = "doorctrl0" - wiresexposed = 0 - - else - icon_state = "doorctrl-open" - wiresexposed = 1 - - return - } - */ - if(istype(W, /obj/item/device/detective_scanner)) - return return src.attack_hand(user) /obj/machinery/button/remote/emag_act(var/remaining_charges, var/mob/user) diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm index cce60eddcfe..dabe11853f1 100644 --- a/code/game/machinery/doors/airlock.dm +++ b/code/game/machinery/doors/airlock.dm @@ -738,7 +738,7 @@ About the new airlock wires panel: if(src.isElectrified()) if(src.shock(user, 75)) return - if(istype(C, /obj/item/device/detective_scanner) || istype(C, /obj/item/taperoll)) + if(istype(C, /obj/item/taperoll)) return src.add_fingerprint(user) diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm index 2d1680aa7d4..f583b620a42 100644 --- a/code/game/machinery/doors/door.dm +++ b/code/game/machinery/doors/door.dm @@ -202,8 +202,6 @@ ..() /obj/machinery/door/attackby(obj/item/I as obj, mob/user as mob) - if(istype(I, /obj/item/device/detective_scanner)) - return src.add_fingerprint(user) if(istype(I, /obj/item/stack/material) && I.get_material_name() == src.get_material_name()) diff --git a/code/game/machinery/igniter.dm b/code/game/machinery/igniter.dm index fa385a01155..5fd791843d9 100755 --- a/code/game/machinery/igniter.dm +++ b/code/game/machinery/igniter.dm @@ -72,8 +72,6 @@ // src.sd_SetLuminosity(0) /obj/machinery/sparker/attackby(obj/item/weapon/W as obj, mob/user as mob) - if(istype(W, /obj/item/device/detective_scanner)) - return if (istype(W, /obj/item/weapon/screwdriver)) add_fingerprint(user) src.disable = !src.disable diff --git a/code/game/objects/effects/decals/Cleanable/humans.dm b/code/game/objects/effects/decals/Cleanable/humans.dm index 6d3d7cd1dfa..9f3e6772704 100644 --- a/code/game/objects/effects/decals/Cleanable/humans.dm +++ b/code/game/objects/effects/decals/Cleanable/humans.dm @@ -22,6 +22,19 @@ var/global/list/image/splatter_cache=list() var/amount = 5 var/drytime +/obj/effect/decal/cleanable/blood/reveal_blood() + if(!fluorescent) + fluorescent = 1 + basecolor = COLOR_LUMINOL + update_icon() + +/obj/effect/decal/cleanable/blood/clean_blood() + fluorescent = 0 + if(invisibility != 100) + invisibility = 100 + amount = 0 + processing_objects -= src + /obj/effect/decal/cleanable/blood/Destroy() for(var/datum/disease/D in viruses) D.cure(0) diff --git a/code/game/objects/effects/decals/Cleanable/tracks.dm b/code/game/objects/effects/decals/Cleanable/tracks.dm index ae05e0e4cc3..0df1a5e12a1 100644 --- a/code/game/objects/effects/decals/Cleanable/tracks.dm +++ b/code/game/objects/effects/decals/Cleanable/tracks.dm @@ -29,6 +29,13 @@ var/global/list/image/fluidtrack_cache=list() src.basecolor=_color src.wet=_wet +/obj/effect/decal/cleanable/blood/tracks/reveal_blood() + if(!fluorescent) + if(stack && stack.len) + for(var/datum/fluidtrack/track in stack) + track.basecolor = COLOR_LUMINOL + ..() + // Footprints, tire trails... /obj/effect/decal/cleanable/blood/tracks amount = 0 diff --git a/code/game/objects/effects/decals/cleanable.dm b/code/game/objects/effects/decals/cleanable.dm index dc5e6c57e88..93d6a4bb52e 100644 --- a/code/game/objects/effects/decals/cleanable.dm +++ b/code/game/objects/effects/decals/cleanable.dm @@ -1,6 +1,9 @@ /obj/effect/decal/cleanable var/list/random_icon_states = list() +/obj/effect/decal/cleanable/clean_blood() + qdel(src) + /obj/effect/decal/cleanable/New() if (random_icon_states && length(src.random_icon_states) > 0) src.icon_state = pick(src.random_icon_states) diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index 9fa9ce9ff28..afabd9ae8a6 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -29,9 +29,9 @@ //It should be used purely for appearance. For gameplay effects caused by items covering body parts, use body_parts_covered. var/flags_inv = 0 var/body_parts_covered = 0 //see setup.dm for appropriate bit flags - + var/item_flags = 0 //Miscellaneous flags pertaining to equippable objects. - + //var/heat_transfer_coefficient = 1 //0 prevents all transfers, 1 is invisible var/gas_transfer_coefficient = 1 // for leaking gas from turf to mask and vice-versa (for masks right now, but at some point, i'd like to include space helmets) var/permeability_coefficient = 1 // for chemicals/diseases @@ -494,6 +494,12 @@ var/list/global/slot_flags_enumeration = list( var/obj/item/clothing/gloves/G = src G.transfer_blood = 0 +/obj/item/reveal_blood() + if(was_bloodied && !fluorescent) + fluorescent = 1 + blood_color = COLOR_LUMINOL + blood_overlay.color = COLOR_LUMINOL + update_icon() /obj/item/add_blood(mob/living/carbon/human/M as mob) if (!..()) @@ -608,10 +614,10 @@ modules/mob/living/carbon/human/life.dm if you die, you will be zoomed out. usr.visible_message("[zoomdevicename ? "[usr] looks up from the [src.name]" : "[usr] lowers the [src.name]"].") return - + /obj/item/proc/pwr_drain() return 0 // Process Kill /obj/item/proc/resolve_attackby(atom/A, mob/source) return A.attackby(src,source) - + diff --git a/code/game/objects/structures/crates_lockers/closets/secure/security.dm b/code/game/objects/structures/crates_lockers/closets/secure/security.dm index 9ac7bf648b7..2348fc434b1 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/security.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/security.dm @@ -258,7 +258,6 @@ new /obj/item/clothing/shoes/laceup(src) new /obj/item/weapon/storage/box/evidence(src) new /obj/item/device/radio/headset/headset_sec(src) - new /obj/item/device/detective_scanner(src) new /obj/item/clothing/suit/storage/vest/detective(src) new /obj/item/ammo_magazine/c45m/rubber(src) new /obj/item/ammo_magazine/c45m/rubber(src) diff --git a/code/game/turfs/simulated.dm b/code/game/turfs/simulated.dm index e1e65e6b2dc..337c56f7db6 100644 --- a/code/game/turfs/simulated.dm +++ b/code/game/turfs/simulated.dm @@ -14,6 +14,11 @@ var/max_fire_temperature_sustained = 0 //The max temperature of the fire which it was subjected to var/dirt = 0 +/turf/simulated/clean_blood() + for(var/obj/effect/decal/cleanable/blood/B in contents) + B.fluorescent = 0 + B.invisibility = 100 + /turf/simulated/New() ..() if(istype(loc, /area/chapel)) diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index ef83dd53646..595c239ee49 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -216,3 +216,6 @@ var/const/enterloopsanity = 100 if(A.density && !(A.flags & ON_BORDER)) return 1 return 0 + +/turf/proc/update_blood_overlays() + return diff --git a/code/modules/clothing/clothing.dm b/code/modules/clothing/clothing.dm index 894d2cb0a7c..593908f4cd2 100644 --- a/code/modules/clothing/clothing.dm +++ b/code/modules/clothing/clothing.dm @@ -4,6 +4,7 @@ var/flash_protection = FLASH_PROTECTION_NONE // Sets the item's level of flash protection. var/tint = TINT_NONE // Sets the item's level of visual impairment tint. var/list/species_restricted = null //Only these species can wear this kit. + var/gunshot_residue //Used by forensics. /* Sprites used when the clothing item is refit. This is done by setting icon_override. @@ -17,6 +18,11 @@ /obj/item/clothing/proc/update_clothing_icon() return +// Aurora forensics port. +/obj/item/clothing/clean_blood() + ..() + gunshot_residue = null + //BS12: Species-restricted clothing check. /obj/item/clothing/mob_can_equip(M as mob, slot) diff --git a/code/modules/clothing/suits/jobs.dm b/code/modules/clothing/suits/jobs.dm index e5044da7bee..85a8362e8e5 100644 --- a/code/modules/clothing/suits/jobs.dm +++ b/code/modules/clothing/suits/jobs.dm @@ -96,7 +96,7 @@ item_state = "det_suit" blood_overlay_type = "coat" body_parts_covered = UPPER_TORSO|ARMS - allowed = list(/obj/item/weapon/tank/emergency_oxygen, /obj/item/device/flashlight,/obj/item/weapon/gun/energy,/obj/item/weapon/gun/projectile,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/weapon/melee/baton,/obj/item/weapon/handcuffs,/obj/item/weapon/storage/fancy/cigarettes,/obj/item/weapon/flame/lighter,/obj/item/device/detective_scanner,/obj/item/device/taperecorder) + allowed = list(/obj/item/weapon/tank/emergency_oxygen, /obj/item/device/flashlight,/obj/item/weapon/gun/energy,/obj/item/weapon/gun/projectile,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/weapon/melee/baton,/obj/item/weapon/handcuffs,/obj/item/weapon/storage/fancy/cigarettes,/obj/item/weapon/flame/lighter,/obj/item/device/taperecorder) armor = list(melee = 50, bullet = 10, laser = 25, energy = 10, bomb = 0, bio = 0, rad = 0) /obj/item/clothing/suit/storage/det_trench/grey @@ -109,7 +109,7 @@ desc = "A forensics technician jacket." item_state = "det_suit" body_parts_covered = UPPER_TORSO|ARMS - allowed = list(/obj/item/weapon/tank/emergency_oxygen, /obj/item/device/flashlight,/obj/item/weapon/gun/energy,/obj/item/weapon/gun/projectile,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/weapon/melee/baton,/obj/item/weapon/handcuffs,/obj/item/device/detective_scanner,/obj/item/device/taperecorder) + allowed = list(/obj/item/weapon/tank/emergency_oxygen, /obj/item/device/flashlight,/obj/item/weapon/gun/energy,/obj/item/weapon/gun/projectile,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/weapon/melee/baton,/obj/item/weapon/handcuffs,/obj/item/device/taperecorder) armor = list(melee = 10, bullet = 10, laser = 15, energy = 10, bomb = 0, bio = 0, rad = 0) /obj/item/clothing/suit/storage/forensics/red diff --git a/code/modules/detectivework/footprints.dm b/code/modules/detectivework/footprints.dm new file mode 100644 index 00000000000..e69de29bb2d diff --git a/code/modules/detectivework/forensics.dm b/code/modules/detectivework/forensics.dm index bca3090c3ff..27e2231d243 100644 --- a/code/modules/detectivework/forensics.dm +++ b/code/modules/detectivework/forensics.dm @@ -1,3 +1,7 @@ +/obj/item/weapon/forensics + icon = 'icons/obj/forensics.dmi' + w_class = 1 + //This is the output of the stringpercent(print) proc, and means about 80% of //the print must be there for it to be complete. (Prints are 32 digits) var/const/FINGERPRINT_COMPLETE = 6 diff --git a/code/modules/detectivework/microscope/dnascanner.dm b/code/modules/detectivework/microscope/dnascanner.dm new file mode 100644 index 00000000000..4b3cafea50b --- /dev/null +++ b/code/modules/detectivework/microscope/dnascanner.dm @@ -0,0 +1,153 @@ +//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 + density = 1 + + var/obj/item/weapon/forensics/swab/bloodsamp = null + var/closed = 0 + var/scanning = 0 + var/scanner_progress = 0 + var/scanner_rate = 2.50 + var/last_process_worldtime = 0 + var/report_num = 0 + +/obj/machinery/dnaforensics/attackby(var/obj/item/W, mob/user as mob) + + if(bloodsamp) + user << "There is already a sample in the machine." + return + + if(closed) + user << "Open the cover before inserting the sample." + return + + var/obj/item/weapon/forensics/swab/swab = W + if(istype(swab) && swab.is_used()) + user.unEquip(W) + src.bloodsamp = swab + swab.loc = src + user << "You insert \the [W] into \the [src]." + else + user << "\The [src] only accepts used swabs." + return + +/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 + var/list/data = list() + 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.") : "") + data["lidstate"] = closed + + ui = nanomanager.try_update_ui(user, src, ui_key, ui, data) + if (!ui) + ui = new(user, src, ui_key, "dnaforensics.tmpl", "QuikScan DNA Analyzer", 540, 326) + ui.set_initial_data(data) + ui.open() + ui.set_auto_update(1) + +/obj/machinery/dnaforensics/Topic(href, href_list) + + if(..()) return 1 + + if(stat & (NOPOWER)) + return 0 // don't update UIs attached to this object + + if(href_list["scanItem"]) + if(scanning) + scanning = 0 + 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.forceMove(src.loc) + bloodsamp = null + + if(href_list["toggleLid"]) + toggle_lid() + + return 1 + +/obj/machinery/dnaforensics/process() + if(scanning) + if(!bloodsamp || bloodsamp.loc != src) + bloodsamp = null + scanning = 0 + 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/complete_scan() + src.visible_message("\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.forceMove(src.loc) + P.update_icon() + scanning = 0 + update_icon() + return + +/obj/machinery/dnaforensics/attack_ai(mob/user as mob) + ui_interact(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(usr.stat || !isliving(usr)) + return + + if(scanning) + usr << "You can't do that while [src] is scanning!" + return + + closed = !closed + src.update_icon() + +/obj/machinery/dnaforensics/update_icon() + ..() + if(!(stat & NOPOWER) && scanning) + icon_state = "dnaworking" + else if(closed) + icon_state = "dnaclosed" + else + icon_state = "dnaopen" diff --git a/code/modules/detectivework/microscope/microscope.dm b/code/modules/detectivework/microscope/microscope.dm new file mode 100644 index 00000000000..f420e2ee399 --- /dev/null +++ b/code/modules/detectivework/microscope/microscope.dm @@ -0,0 +1,108 @@ +//microscope code itself +/obj/machinery/microscope + name = "high powered electron microscope" + desc = "A highly advanced microscope capable of zooming up to 3000x." + icon = 'icons/obj/forensics.dmi' + icon_state = "microscope" + anchored = 1 + density = 1 + + var/obj/item/weapon/sample = null + var/report_num = 0 + +/obj/machinery/microscope/attackby(obj/item/weapon/W as obj, mob/user as mob) + + if(sample) + user << "There is already a slide in the microscope." + return + + if(istype(W, /obj/item/weapon/forensics/slide) || istype(W, /obj/item/weapon/sample/print)) + user << "You insert \the [W] into the microscope." + user.unEquip(W) + W.forceMove(src) + sample = W + update_icon() + return + +/obj/machinery/microscope/attack_hand(mob/user) + + if(!sample) + user << "The microscope has no sample to examine." + return + + user << "The microscope whirrs as you examine \the [sample]." + + if(!do_after(user, 25) || !sample) + return + + user << "Printing findings now..." + var/obj/item/weapon/paper/report = new(get_turf(src)) + report.stamped = list(/obj/item/weapon/stamp) + report.overlays = list("paper_stamped") + report_num++ + + if(istype(sample, /obj/item/weapon/forensics/slide)) + var/obj/item/weapon/forensics/slide/slide = sample + if(slide.has_swab) + var/obj/item/weapon/forensics/swab/swab = slide.has_swab + + report.name = "GSR report #[++report_num]: [swab.name]" + report.info = "Scanned item:
[swab.name]

" + + if(swab.gsr) + report.info += "Residue from a [swab.gsr] bullet detected." + else + report.info += "No gunpowder residue found." + + else if(slide.has_sample) + var/obj/item/weapon/sample/fibers/fibers = slide.has_sample + report.name = "Fiber report #[++report_num]: [fibers.name]" + report.info = "Scanned item:
[fibers.name]

" + if(fibers.evidence) + report.info = "Molecular analysis on provided sample has determined the presence of unique fiber strings.

" + for(var/fiber in fibers.evidence) + report.info += "Most likely match for fibers: [fiber]

" + else + report.info += "No fibers found." + else + report.name = "Empty slide report #[report_num]" + report.info = "Evidence suggests that there's nothing in this slide." + else if(istype(sample, /obj/item/weapon/sample/print)) + report.name = "Fingerprint report #[report_num]: [sample.name]" + report.info = "Fingerprint analysis report #[report_num]: [sample.name]
" + var/obj/item/weapon/sample/print/card = sample + if(card.evidence && card.evidence.len) + report.info += "Surface analysis has determined unique fingerprint strings:

" + for(var/prints in card.evidence) + report.info += "Fingerprint string: " + if(!is_complete_print(prints)) + report.info += "INCOMPLETE PRINT" + else + report.info += "[prints]" + report.info += "
" + else + report.info += "No information available." + + if(report) + report.update_icon() + if(report.info) + user << report.info + return + +/obj/machinery/microscope/AltClick() + if(usr.lying || !Adjacent(usr) || !ishuman(usr)) + return ..() + if(!sample) + usr << "\The [src] does not have a sample in it." + return + usr << "You remove \the [sample] from \the [src]." + sample.forceMove(get_turf(src)) + usr.put_in_hands(sample) + sample = null + update_icon() + return + +/obj/machinery/microscope/update_icon() + icon_state = "microscope" + if(sample) + icon_state += "slide" diff --git a/code/modules/detectivework/microscope/slides.dm b/code/modules/detectivework/microscope/slides.dm new file mode 100644 index 00000000000..8226e6a51e5 --- /dev/null +++ b/code/modules/detectivework/microscope/slides.dm @@ -0,0 +1,42 @@ +/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_state = "slide" + var/obj/item/weapon/forensics/swab/has_swab + var/obj/item/weapon/sample/fibers/has_sample + +/obj/item/weapon/forensics/slide/attackby(var/obj/item/weapon/W, var/mob/user) + if(has_swab || has_sample) + user << "There is already a sample in the slide." + return + if(istype (W, /obj/item/weapon/forensics/swab)) + has_swab = W + else if(istype(W, /obj/item/weapon/sample/fibers)) + has_sample = W + else + user << "You don't think this will fit." + return + user << "You insert the sample into the slide." + user.unEquip(W) + W.forceMove(src) + update_icon() + +/obj/item/weapon/forensics/slide/attack_self(var/mob/user) + if(has_swab || has_sample) + user << "You remove \the sample from \the [src]." + if(has_swab) + has_swab.loc = get_turf(src) + has_swab = null + if(has_sample) + has_sample.forceMove(get_turf(src)) + has_sample = null + update_icon() + return + +/obj/item/weapon/forensics/slide/update_icon() + if(!has_swab && !has_sample) + icon_state = "slide" + else if(has_swab) + icon_state = "slideswab" + else if(has_sample) + icon_state = "slidefiber" diff --git a/code/modules/detectivework/scanner.dm b/code/modules/detectivework/scanner.dm deleted file mode 100644 index dc372e23566..00000000000 --- a/code/modules/detectivework/scanner.dm +++ /dev/null @@ -1,115 +0,0 @@ -/obj/item/device/detective_scanner - name = "forensic scanner" - desc = "Used to scan objects for DNA and fingerprints." - icon_state = "forensic1" - var/list/stored = list() - w_class = 3.0 - item_state = "electronic" - flags = CONDUCT | NOBLUDGEON - slot_flags = SLOT_BELT - -/obj/item/device/detective_scanner/attack(mob/living/carbon/human/M as mob, mob/user as mob) - if (!ishuman(M)) - user << "[M] is not human and cannot have the fingerprints." - flick("forensic0",src) - return 0 - if (( !( istype(M.dna, /datum/dna) ) || M.gloves) ) - user << "No fingerprints found on [M]" - flick("forensic0",src) - return 0 - else - var/obj/item/weapon/f_card/F = new /obj/item/weapon/f_card( user.loc ) - F.amount = 1 - F.add_fingerprint(M) - F.icon_state = "fingerprint1" - F.name = text("FPrintC- '[M.name]'") - user << "Done printing." - user << "[M]'s Fingerprints: [md5(M.dna.uni_identity)]" - if ( M.blood_DNA && M.blood_DNA.len ) - user << "Blood found on [M]. Analysing..." - spawn(15) - for(var/blood in M.blood_DNA) - user << "Blood type: [M.blood_DNA[blood]]\nDNA: [blood]" - return - -/obj/item/device/detective_scanner/afterattack(atom/A as obj|turf, mob/user, proximity) - if(!proximity) return - if(ismob(A)) - return - if(istype(A,/obj/machinery/computer/forensic_scanning)) - user.visible_message("[user] takes a cord out of [src] and hooks its end into [A]" ,\ - "You download data from [src] to [A]") - var/obj/machinery/computer/forensic_scanning/F = A - F.sync_data(stored) - return - - if(istype(A,/obj/item/weapon/f_card)) - user << "The scanner displays on the screen: \"ERROR 43: Object on Excluded Object List.\"" - flick("forensic0",src) - return - - add_fingerprint(user) - - //General - if ((!A.fingerprints || !A.fingerprints.len) && !A.suit_fibers && !A.blood_DNA) - user.visible_message("\The [user] scans \the [A] with \a [src], the air around [user.gender == MALE ? "him" : "her"] humming[prob(70) ? " gently." : "."]" ,\ - "Unable to locate any fingerprints, materials, fibers, or blood on [A]!",\ - "You hear a faint hum of electrical equipment.") - flick("forensic0",src) - return 0 - - if(add_data(A)) - user << "Object already in internal memory. Consolidating data..." - flick("forensic2",src) - return - - //PRINTS - if(A.fingerprints && A.fingerprints.len) - user << "Isolated [A.fingerprints.len] fingerprints:" - user << "Data Stored: Scan with Hi-Res Forensic Scanner to retrieve." - var/list/complete_prints = list() - for(var/i in A.fingerprints) - var/print = A.fingerprints[i] - if(stringpercent(print) <= FINGERPRINT_COMPLETE) - complete_prints += print - if(complete_prints.len < 1) - user << "No intact prints found" - else - user << "Found [complete_prints.len] intact prints" - for(var/i in complete_prints) - user << "    [i]" - - //FIBERS - if(A.suit_fibers && A.suit_fibers.len) - user << "Fibers/Materials Data Stored: Scan with Hi-Res Forensic Scanner to retrieve." - flick("forensic2",src) - - //Blood - if (A.blood_DNA && A.blood_DNA.len) - user << "Blood detected. Analysing..." - spawn(15) - for(var/blood in A.blood_DNA) - user << "Blood type: \red [A.blood_DNA[blood]] \t \black DNA: \red [blood]" - - user.visible_message("\The [user] scans \the [A] with \a [src], the air around [user.gender == MALE ? "him" : "her"] humming[prob(70) ? " gently." : "."]" ,\ - "You finish scanning \the [A].",\ - "You hear a faint hum of electrical equipment.") - flick("forensic2",src) - return 0 - -/obj/item/device/detective_scanner/proc/add_data(atom/A as mob|obj|turf|area) - var/datum/data/record/forensic/old = stored["\ref [A]"] - var/datum/data/record/forensic/fresh = new(A) - - if(old) - fresh.merge(old) - . = 1 - stored["\ref [A]"] = fresh - -/obj/item/device/detective_scanner/verb/wipe() - set name = "Wipe Forensic Data" - set category = "Object" - set src in view(1) - if (alert("Are you sure you want to wipe all data from [src]?",,"Yes","No") == "Yes") - stored = list() - usr << "Forensic data erase complete." diff --git a/code/modules/detectivework/scanning_console.dm b/code/modules/detectivework/scanning_console.dm deleted file mode 100644 index 25658a06f0b..00000000000 --- a/code/modules/detectivework/scanning_console.dm +++ /dev/null @@ -1,313 +0,0 @@ -/obj/machinery/computer/forensic_scanning - name = "high-res forensic scanning computer" - icon_keyboard = "security_key" - icon_screen = "forensic" - - var/screen = "database" - var/authenticated = 0 - req_access = list(access_forensics_lockers) - var/scan_progress = -1 - var/obj/item/scanning - var/datum/data/record/forensic/current - - var/list/filters = list() - var/list/current_list = list() - - var/list/files = list() - -/obj/machinery/computer/forensic_scanning/proc/get_printable_data(var/datum/data/record/forensic/fresh) - . += "

[fresh.fields["name"]]

" - . += "Scanned in [fresh.fields["area"]] at [worldtime2text(fresh.fields["time"])]
" - var/list/prints = fresh.fields["fprints"] - if(prints.len) - . += "

Fingerprints:

" - var/incomplete = 0 - for(var/fullprint in prints) - var/print = prints[fullprint] - if(is_complete_print(print)) - . += "[print]
" - else - incomplete++ - if(incomplete) - . += "[incomplete] incomplete fingerprints." - else - . += "
No fingerprints recorded.
" - - var/list/fibers = fresh.fields["fibers"] - if(fibers.len) - . += "

Fibers:

" - . += "" - else - . += "
No fibers recorded." - - var/list/bloods = fresh.fields["blood"] - if(bloods.len) - . += "

Blood:

" - . += "" - else - . += "
No blood recorded." - -/obj/machinery/computer/forensic_scanning/proc/add_record(var/datum/data/record/forensic/fresh) - var/datum/data/record/forensic/old = files[fresh.uid] - if(old) - fresh.merge(old) - fresh.fields["label"] = old.fields["label"] - files[fresh.uid] = fresh - - //updating partial prints on other things - var/list/fprints = fresh.fields["fprints"] - if(fprints.len) - for(var/id in files) - var/datum/data/record/forensic/rec = files[id] - if(rec.update_prints(fprints)) - files[id] = rec - -/obj/machinery/computer/forensic_scanning/proc/process_card(var/obj/item/weapon/f_card/card) - if(card.fingerprints) - usr << "\The [src] sucks in \the [card] and whirrs, scanning it." - var/found = 0 - for(var/id in files) - var/datum/data/record/forensic/rec = files[id] - found = rec.update_prints(card.fingerprints) - if (found) - files[id] = rec - if(found) - usr << "Complete match found." - else - usr << "No match found." - return 1 - else - usr << "No fingerprints detected on [card]." - return 0 - -//Takes a list of forensic records, with key being reference to object, and updates internal database. -/obj/machinery/computer/forensic_scanning/proc/sync_data(var/list/newdata) - for(var/id in newdata) - var/datum/data/record/forensic/fresh = newdata[id] - add_record(fresh) - -/obj/machinery/computer/forensic_scanning/proc/get_filtered_set() - .= list() - for(var/id in files) - var/datum/data/record/forensic/cur = files[id] - var/add = 1 - if(filters["name"]) - add = 0 - for(var/filter in filters["name"]) - if(findtext(cur.fields["name"], filter)) - add = 1 - break - - if(filters["area"]) - add = 0 - for(var/filter in filters["area"]) - if(findtext(cur.fields["area"], filter)) - add = 1 - break - - if(filters["fprints"]) - add = 0 - var/list/prints = cur.fields["fprints"] - for(var/pid in prints) - var/print = prints[pid] - if (is_complete_print(print)) - for(var/filter in filters["fprints"]) - if(findtext(print, filter)) - add = 1 - break - - if(filters["fibers"]) - add = 0 - for(var/fiber in cur.fields["fibers"]) - for(var/filter in filters["fibers"]) - if(findtext(fiber, filter)) - add = 1 - break - - if(filters["blood"]) - add = 0 - for(var/DNA in cur.fields["blood"]) - for(var/filter in filters["blood"]) - if(findtext(DNA, filter)) - add = 1 - break - - if(filters["label"]) - add = 0 - for(var/filter in filters["label"]) - if(cur.fields["label"] && findtext(cur.fields["label"], filter)) - add = 1 - break - if (add) - .+=cur - -/obj/machinery/computer/forensic_scanning/attack_hand(mob/user) - if(..()) - return - user.set_machine(src) - - var/dat - if(!authenticated) - dat += "{Log In}" - else - dat += "{Log Out}" - dat += " | " - dat += "Database" - dat += " | " - dat += "Record details" - dat += " | " - dat += "Scanning" - dat +="


" - switch(screen) - if("database") //Database screen - dat += "Search filters:
" - var/list/filternames = list("Object"="name", "Area"="area", "Fingerprints"="fprints", "Fibers"="fibers", "DNA"="blood", "Label"="label") - for(var/filter in filternames) - var/fname = filternames[filter] - dat += "
[filter]: [filters[fname] ? list2text(filters[fname], ",") : "All"]" - - current_list = get_filtered_set() - dat+= "


" - if(current_list.len < 1) - dat += "No data matching your request found." - else - dat += "" - dat += "" - for(var/datum/data/record/forensic/record in current_list) - dat += "" - dat += "" - for(var/criteria in list("fprints", "fibers", "blood")) - var/list/data = record.fields[criteria] - dat += "" - dat += "" - dat += "" - dat += "" - dat += "
ObjectAreaFingerprintsFibersBloodLabel
[record.fields["name"]][record.fields["area"]][data.len ? data.len : "None"][record.fields["label"] ? record.fields["label"] : ""]Delete
" - dat += "Print all listed
" - - if("details") //Details screen - if(!current) - dat += "
NO RECORD SELECTED" - else - dat += get_printable_data(current) - dat += "
Labels: " - dat += "[current.fields["label"] ? current.fields["label"] : "None"]
" - dat += "Print record
" - - if("scan") //Scanning screen - dat += "Object: [scanning ? scanning.name : "-----"]
" - if (scanning) - if (scan_progress > 0) - dat += "Scan in progress." - dat += " Cancel
" - else - dat += "Scan
" - dat += "Insert fingerprint card here: -----" - - user << browse(dat,"window=fscanner") - onclose(user,"fscanner") - -/obj/machinery/computer/forensic_scanning/Topic(href,href_list) - if(..()) return 1 - switch(href_list["operation"]) - if("login") - var/mob/M = usr - if(istype(M,/mob/living/silicon) || allowed(M)) - authenticated = 1 - if("logout") - authenticated = 0 - if("filter") - var/filterstr = sanitize(input("Input the search criteria. Multiple values can be input, separated by a comma.", "Filter setting") as text|null) - if(filterstr) - filters[href_list["filter"]] = text2list(filterstr,",") - else - filters[href_list["filter"]] = null - if("screen") - screen = href_list["screen"] - if("details") - if(href_list["identifier"]) - screen = "details" - current = files[href_list["identifier"]] - else - usr << "No record found." - if("delete") - if(href_list["identifier"]) - if(alert("Are you sure you want to delete this record?","Record deletion", "Yes", "No") == "Yes") - files.Remove(href_list["identifier"]) - if(current && current.uid == href_list["identifier"]) - current = null - if("label") - if(current) - var/label = sanitize(input(usr,"Input the label for this record. Multiple values can be input, separated by a comma.", "Labeling record", current.fields["label"]) as text|null) - current.fields["label"] = label - if("object") - if(scanning) - scanning.loc = get_turf(src) - scan_progress = -1 - scanning = null - else - var/mob/M = usr - var/obj/item/I = M.get_active_hand() - if(I && istype(I)) - if(istype(I, /obj/item/weapon/evidencebag)) - scanning = I.contents[1] - scanning.loc = src - I.overlays.Cut() - I.w_class = 1 - I.icon_state = "evidenceobj" - else - scanning = I - M.drop_item() - I.loc = src - else - usr << "Invalid object, rejected." - if("scan") - if(scanning) - scan_progress = 10 - if("cancel") - scan_progress = -1 - if("card") - var/mob/M = usr - var/obj/item/I = M.get_active_hand() - if(istype(I, /obj/item/weapon/f_card)) - if(process_card(I)) - M.drop_item() - qdel(I) - else - usr << "Invalid fingerprint card, rejected." - if("print") - if(current) - var/obj/item/weapon/paper/P = new(loc) - P.name = "\improper Forensics Data ([current.fields["name"]])" - P.icon_state = "paper_words" - P.info = "Forensics Database - [worldtime2text(world.time)]

" - P.info += get_printable_data(current) - if("printall") - var/obj/item/weapon/paper/P = new(loc) - P.name = "\improper Forensics Data" - P.icon_state = "paper_words" - P.info = "Forensics Database - [worldtime2text(world.time)]

" - for(var/datum/data/record/forensic/cur in current_list) - P.info += get_printable_data(cur) - - updateUsrDialog() - -/obj/machinery/computer/forensic_scanning/process() - if (!..()) - return - if(scanning) - if(scan_progress > 0) - scan_progress-- - updateUsrDialog() - if(scan_progress == 0) - scan_progress = -1 - ping("Scan complete.") - var/datum/data/record/forensic/fresh = new(scanning) - add_record(fresh) - updateUsrDialog() diff --git a/code/modules/detectivework/tools/crimekit.dm b/code/modules/detectivework/tools/crimekit.dm new file mode 100644 index 00000000000..8b12203b217 --- /dev/null +++ b/code/modules/detectivework/tools/crimekit.dm @@ -0,0 +1,17 @@ +//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 = 'icons/obj/forensics.dmi' + icon_state = "case" + item_state = "case" + storage_slots = 14 + +/obj/item/weapon/storage/briefcase/crimekit/New() + ..() + new /obj/item/weapon/storage/box/swabs(src) + new /obj/item/weapon/storage/box/fingerprints(src) + new /obj/item/weapon/reagent_containers/spray/luminol(src) + new /obj/item/device/uv_light(src) + new /obj/item/weapon/forensics/sample_kit(src) + new /obj/item/weapon/forensics/sample_kit/powder(src) \ No newline at end of file diff --git a/code/modules/detectivework/evidence.dm b/code/modules/detectivework/tools/evidencebag.dm similarity index 69% rename from code/modules/detectivework/evidence.dm rename to code/modules/detectivework/tools/evidencebag.dm index 898dbbaf1d3..31c5b6a3588 100644 --- a/code/modules/detectivework/evidence.dm +++ b/code/modules/detectivework/tools/evidencebag.dm @@ -96,49 +96,3 @@ /obj/item/weapon/evidencebag/examine(mob/user) ..(user) if (stored_item) user.examinate(stored_item) - -/obj/item/weapon/storage/box/evidence - name = "evidence bag box" - desc = "A box claiming to contain evidence bags." - New() - new /obj/item/weapon/evidencebag(src) - new /obj/item/weapon/evidencebag(src) - new /obj/item/weapon/evidencebag(src) - new /obj/item/weapon/evidencebag(src) - new /obj/item/weapon/evidencebag(src) - new /obj/item/weapon/evidencebag(src) - ..() - return - -/obj/item/weapon/f_card - name = "finger print card" - desc = "Used to take fingerprints." - icon = 'icons/obj/card.dmi' - icon_state = "fingerprint0" - var/amount = 10.0 - item_state = "paper" - throwforce = 1 - w_class = 1.0 - slot_flags = SLOT_EARS - throw_speed = 3 - throw_range = 5 - -/obj/item/weapon/f_card/add_fingerprint(mob/living/M as mob, ignoregloves = 0) - if(..()) - var/mob/living/carbon/human/H = M - var/full_print = md5(H.dna.uni_identity) - fingerprints[full_print] = full_print - -/obj/item/weapon/f_card/examine(mob/user) - ..() - if(fingerprints.len) - user << "Fingerprints on this card:" - for(var/print in fingerprints) - user << "\t[fingerprints[print]]" - -/obj/item/weapon/fcardholder - name = "fingerprint card case" - desc = "Apply finger print card." - icon = 'icons/obj/items.dmi' - icon_state = "fcardholder0" - item_state = "clipboard" diff --git a/code/modules/detectivework/tools/luminol.dm b/code/modules/detectivework/tools/luminol.dm new file mode 100644 index 00000000000..ddf00a628a8 --- /dev/null +++ b/code/modules/detectivework/tools/luminol.dm @@ -0,0 +1,13 @@ +/obj/item/weapon/reagent_containers/spray/luminol + name = "luminol bottle" + desc = "A bottle containing an odourless, 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) \ No newline at end of file diff --git a/code/modules/detectivework/footprints_and_rag.dm b/code/modules/detectivework/tools/rag.dm similarity index 99% rename from code/modules/detectivework/footprints_and_rag.dm rename to code/modules/detectivework/tools/rag.dm index e94611bf10d..816a228a9c5 100644 --- a/code/modules/detectivework/footprints_and_rag.dm +++ b/code/modules/detectivework/tools/rag.dm @@ -71,7 +71,7 @@ icon_state = "raglit" else icon_state = "rag" - + var/obj/item/weapon/reagent_containers/food/drinks/bottle/B = loc if(istype(B)) B.update_icon() @@ -83,7 +83,7 @@ if(reagents.total_volume) var/target_text = trans_dest? "\the [trans_dest]" : "\the [user.loc]" user.visible_message("\The [user] begins to wring out [src] over [target_text].", "You begin to wring out [src] over [target_text].") - + if(do_after(user, reagents.total_volume*5)) //50 for a fully soaked rag if(trans_dest) reagents.trans_to(trans_dest, reagents.total_volume) @@ -114,29 +114,29 @@ if(user.zone_sel.selecting == "mouth") user.do_attack_animation(src) user.visible_message( - "\The [user] smothers [target] with [src]!", - "You smother [target] with [src]!", + "\The [user] smothers [target] with [src]!", + "You smother [target] with [src]!", "You hear some struggling and muffled cries of surprise" ) - + //it's inhaled, so... maybe CHEM_BLOOD doesn't make a whole lot of sense but it's the best we can do for now reagents.trans_to_mob(target, amount_per_transfer_from_this, CHEM_BLOOD) update_name() else wipe_down(target, user) return - + return ..() - + /obj/item/weapon/reagent_containers/glass/rag/afterattack(atom/A as obj|turf|area, mob/user as mob, proximity) - if(!proximity) + if(!proximity) return if(istype(A, /obj/structure/reagent_dispensers)) if(!reagents.get_free_space()) user << "\The [src] is already soaked." return - + if(A.reagents && A.reagents.trans_to_obj(src, reagents.maximum_volume)) user.visible_message("\The [user] soaks [src] using [A].", "You soak [src] using [A].") update_name() @@ -167,7 +167,7 @@ return if(!can_ignite()) return - + //also copied from matches if(reagents.get_reagent_amount("phoron")) // the phoron explodes when exposed to fire visible_message("\The [src] conflagrates violently!") @@ -176,7 +176,7 @@ e.start() qdel(src) return - + processing_objects += src set_light(2, null, "#E38F46") on_fire = 1 @@ -187,7 +187,7 @@ processing_objects -= src set_light(0) on_fire = 0 - + //rags sitting around with 1 second of burn time left is dumb. //ensures players always have a few seconds of burn time left when they light their rag if(burn_time <= 5) @@ -209,7 +209,7 @@ var/turf/location = get_turf(src) if(location) location.hotspot_expose(700, 5) - + if(burn_time <= 0) processing_objects -= src new /obj/effect/decal/cleanable/ash(location) diff --git a/code/modules/detectivework/tools/sample_kits.dm b/code/modules/detectivework/tools/sample_kits.dm new file mode 100644 index 00000000000..fa5c7312a8f --- /dev/null +++ b/code/modules/detectivework/tools/sample_kits.dm @@ -0,0 +1,158 @@ +/obj/item/weapon/sample + name = "forensic sample" + icon = 'icons/obj/forensics.dmi' + w_class = 1 + var/list/evidence = list() + +/obj/item/weapon/sample/New(var/newloc, var/atom/supplied) + ..(newloc) + if(supplied) + copy_evidence(supplied) + name = "[initial(name)] (\the [supplied])" + +/obj/item/weapon/sample/print/New(var/newloc, var/atom/supplied) + ..(newloc, supplied) + if(evidence && evidence.len) + icon_state = "fingerprint1" + +/obj/item/weapon/sample/proc/copy_evidence(var/atom/supplied) + if(supplied.suit_fibers && supplied.suit_fibers.len) + evidence = supplied.suit_fibers.Copy() + supplied.suit_fibers.Cut() + +/obj/item/weapon/sample/proc/merge_evidence(var/obj/item/weapon/sample/supplied, var/mob/user) + if(!supplied.evidence || !supplied.evidence.len) + return 0 + evidence |= supplied.evidence + name = "[initial(name)] (combined)" + user << "You transfer the contents of \the [supplied] into \the [src]." + return 1 + +/obj/item/weapon/sample/print/merge_evidence(var/obj/item/weapon/sample/supplied, var/mob/user) + if(!supplied.evidence || !supplied.evidence.len) + return 0 + for(var/print in supplied.evidence) + if(evidence[print]) + evidence[print] = stringmerge(evidence[print],supplied.evidence[print]) + else + evidence[print] = supplied.evidence[print] + name = "[initial(name)] (combined)" + user << "You overlay \the [src] and \the [supplied], combining the print records." + return 1 + +/obj/item/weapon/sample/attackby(var/obj/O, var/mob/user) + if(O.type == src.type) + user.unEquip(O) + if(merge_evidence(O, user)) + qdel(O) + return 1 + return ..() + +/obj/item/weapon/sample/fibers + name = "fiber bag" + desc = "Used to hold fiber evidence for the detective." + icon_state = "fiberbag" + +/obj/item/weapon/sample/print + name = "fingerprint card" + desc = "Records a set of fingerprints." + icon = 'icons/obj/card.dmi' + icon_state = "fingerprint0" + item_state = "paper" + +/obj/item/weapon/sample/print/attack_self(var/mob/user) + if(evidence && evidence.len) + return + if(!ishuman(user)) + return + var/mob/living/carbon/human/H = user + if(H.gloves) + user << "Take \the [H.gloves] off first." + return + + user << "You firmly press your fingertips onto the card." + var/fullprint = H.get_full_print() + evidence[fullprint] = fullprint + name = "[initial(name)] (\the [H])" + icon_state = "fingerprint1" + +/obj/item/weapon/sample/print/attack(var/mob/living/M, var/mob/user) + + if(!ishuman(M)) + return ..() + + if(evidence && evidence.len) + return 0 + + var/mob/living/carbon/human/H = M + + if(H.gloves) + user << "\The [H] is wearing gloves." + return 1 + + if(user != H && H.a_intent != "help" && !H.lying) + user.visible_message("\The [user] tries to take prints from \the [H], but they move away.") + return 1 + + if(user.zone_sel.selecting == "r_hand" || user.zone_sel.selecting == "l_hand") + var/has_hand + var/obj/item/organ/external/O = H.organs_by_name["r_hand"] + if(istype(O) && !O.is_stump()) + has_hand = 1 + else + O = H.organs_by_name["l_hand"] + if(istype(O) && !O.is_stump()) + has_hand = 1 + if(!has_hand) + user << "They don't have any hands." + return 1 + user.visible_message("[user] takes a copy of \the [H]'s fingerprints.") + var/fullprint = H.get_full_print() + evidence[fullprint] = fullprint + copy_evidence(src) + name = "[initial(name)] (\the [H])" + icon_state = "fingerprint1" + return 1 + return 0 + +/obj/item/weapon/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() + +/obj/item/weapon/forensics/sample_kit + name = "fiber collection kit" + desc = "A magnifying glass and tweezers. Used to lift suit fibers." + icon_state = "m_glass" + w_class = 2 + var/evidence_type = "fiber" + var/evidence_path = /obj/item/weapon/sample/fibers + +/obj/item/weapon/forensics/sample_kit/proc/can_take_sample(var/mob/user, var/atom/supplied) + return (supplied.suit_fibers && supplied.suit_fibers.len) + +/obj/item/weapon/forensics/sample_kit/proc/take_sample(var/mob/user, var/atom/supplied) + var/obj/item/weapon/sample/S = new evidence_path(get_turf(user), supplied) + user << "You transfer [S.evidence.len] [S.evidence.len > 1 ? "[evidence_type]s" : "[evidence_type]"] to \the [S]." + +/obj/item/weapon/forensics/sample_kit/afterattack(var/atom/A, var/mob/user, var/proximity) + if(!proximity) + return + add_fingerprint(user) + if(can_take_sample(user, A)) + take_sample(user,A) + return 1 + else + user << "You are unable to locate any [evidence_type]s on \the [A]." + return ..() + +/obj/item/weapon/forensics/sample_kit/powder + name = "fingerprint powder" + desc = "A jar containing aluminum powder and a specialized brush." + icon_state = "dust" + evidence_type = "fingerprint" + evidence_path = /obj/item/weapon/sample/print + +/obj/item/weapon/forensics/sample_kit/powder/can_take_sample(var/mob/user, var/atom/supplied) + return (supplied.fingerprints && supplied.fingerprints.len) diff --git a/code/modules/detectivework/tools/storage.dm b/code/modules/detectivework/tools/storage.dm new file mode 100644 index 00000000000..1555124cc9b --- /dev/null +++ b/code/modules/detectivework/tools/storage.dm @@ -0,0 +1,44 @@ +/obj/item/weapon/storage/box/swabs + name = "box of swab kits" + desc = "Sterilized equipment within. Do not contaminate." + icon = 'icons/obj/forensics.dmi' + icon_state = "dnakit" + can_hold = list(/obj/item/weapon/forensics/swab) + storage_slots = 14 + +/obj/item/weapon/storage/box/swabs/New() + ..() + for(var/i=0;i\The [H] is wearing a mask.
" + return 1 + + if(!H.dna || !H.dna.unique_enzymes) + user << "They don't seem to have DNA!" + return 1 + + if(user != H && H.a_intent != "help" && !H.lying) + user.visible_message("\The [user] tries to take a swab sample from \the [H], but they move away.") + return 1 + + if(user.zone_sel.selecting == "mouth") + if(!H.organs_by_name["head"]) + user << "They don't have a head." + return 1 + if(!H.check_has_mouth()) + user << "They don't have a mouth." + return 1 + user.visible_message("[user] swabs \the [H]'s mouth for a saliva sample.") + dna = list(H.dna.unique_enzymes) + sample_type = "DNA" + + else if(user.zone_sel.selecting == "r_hand" || user.zone_sel.selecting == "l_hand") + var/has_hand + var/obj/item/organ/external/O = H.organs_by_name["r_hand"] + if(istype(O) && !O.is_stump()) + has_hand = 1 + else + O = H.organs_by_name["l_hand"] + if(istype(O) && !O.is_stump()) + has_hand = 1 + if(!has_hand) + user << "They don't have any hands." + return 1 + user.visible_message("[user] swabs [H]'s palm for a sample.") + sample_type = "GSR" + gsr = H.gunshot_residue + else + return 0 + + if(sample_type) + used = 1 + name = "[initial(name)] ([sample_type] - [H])" + desc = "[initial(desc)] The label on the vial reads 'Sample of [sample_type] from [H].'." + icon_state = "swab_used" + return 1 + return 0 + +/obj/item/weapon/forensics/swab/afterattack(var/atom/A, var/mob/user, var/proximity) + + if(!proximity || istype(A, /obj/item/weapon/forensics/slide)) + return + + if(is_used()) + user << "This swab has already been used." + return + + add_fingerprint(user) + + var/list/choices = list() + if(A.blood_DNA) + choices |= "Blood" + if(istype(A, /obj/item/clothing)) + choices |= "Gunshot Residue" + + var/choice + if(!choices.len) + user << "There is no evidence on \the [A]." + return + else if(choices.len == 1) + choice = choices[1] + else + choice = input("What kind of evidence are you looking for?","Evidence Collection") as null|anything in choices + + if(!choice) + return + + var/sample_type + if(choice == "Blood") + if(!A.blood_DNA || !A.blood_DNA.len) return + dna = A.blood_DNA.Copy() + sample_type = "blood" + else if(choice == "Gunshot Residue") + var/obj/item/clothing/B = A + if(!istype(B) || !B.gunshot_residue) + user << "There is no residue on \the [A]." + return + gsr = B.gunshot_residue + sample_type = "residue" + + if(sample_type) + user.visible_message("\The [user] swabs \the [A] for a sample.", "You swab \the [A] for a sample.") + name = "[initial(name)] ([sample_type] - [A])" + desc = "[initial(desc)] The label on the vial reads 'Sample of [sample_type] from [A].'." + icon_state = "swab_used" + diff --git a/code/modules/detectivework/tools/uvlight.dm b/code/modules/detectivework/tools/uvlight.dm new file mode 100644 index 00000000000..b4e77c0ff29 --- /dev/null +++ b/code/modules/detectivework/tools/uvlight.dm @@ -0,0 +1,69 @@ +/obj/item/device/uv_light + name = "\improper UV light" + desc = "A small handheld black light." + icon_state = "uv_off" + slot_flags = SLOT_BELT + w_class = 2 + item_state = "electronic" + matter = list(DEFAULT_WALL_MATERIAL = 150) + origin_tech = list(TECH_MAGNET = 1, TECH_ENGINEERING = 1) + + var/list/scanned = list() + var/list/stored_alpha = list() + var/list/reset_objects = list() + + var/range = 3 + var/on = 0 + var/step_alpha = 50 + +/obj/item/device/uv_light/attack_self(var/mob/user) + on = !on + if(on) + set_light(range, 2, "#007fff") + processing_objects |= src + icon_state = "uv_on" + else + set_light(0) + clear_last_scan() + processing_objects -= src + icon_state = "uv_off" + +/obj/item/device/uv_light/proc/clear_last_scan() + if(scanned.len) + for(var/atom/O in scanned) + O.invisibility = scanned[O] + if(O.fluorescent == 2) O.fluorescent = 1 + scanned.Cut() + if(stored_alpha.len) + for(var/atom/O in stored_alpha) + O.alpha = stored_alpha[O] + if(O.fluorescent == 2) O.fluorescent = 1 + stored_alpha.Cut() + if(reset_objects.len) + for(var/obj/item/I in reset_objects) + I.overlays -= I.blood_overlay + if(I.fluorescent == 2) I.fluorescent = 1 + reset_objects.Cut() + +/obj/item/device/uv_light/process() + clear_last_scan() + if(on) + step_alpha = round(255/range) + var/turf/origin = get_turf(src) + if(!origin) + return + for(var/turf/T in range(range, origin)) + var/use_alpha = 255 - (step_alpha * get_dist(origin, T)) + for(var/atom/A in T.contents) + if(A.fluorescent == 1) + A.fluorescent = 2 //To prevent light crosstalk. + if(A.invisibility) + scanned[A] = A.invisibility + A.invisibility = 0 + stored_alpha[A] = A.alpha + A.alpha = use_alpha + if(istype(A, /obj/item)) + var/obj/item/O = A + if(O.was_bloodied && !(O.blood_overlay in O.overlays)) + O.overlays |= O.blood_overlay + reset_objects |= O \ No newline at end of file diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 812d27d125f..a3c16a4a1de 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -318,9 +318,7 @@ H.germ_level = 0 update_icons() //apply the now updated overlays to the mob - //Throwing stuff - /mob/proc/throw_item(atom/target) return diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 11ae1407778..6135d2ce029 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -997,8 +997,14 @@ verbs += /mob/living/carbon/human/proc/bloody_doodle return 1 //we applied blood to the item +/mob/living/carbon/human/proc/get_full_print() + if(!dna ||!dna.uni_identity) + return + return md5(dna.uni_identity) + /mob/living/carbon/human/clean_blood(var/clean_feet) .=..() + gunshot_residue = null if(clean_feet && !shoes && istype(feet_blood_DNA, /list) && feet_blood_DNA.len) feet_blood_color = null qdel(feet_blood_DNA) diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm index a9abd26ec01..9d1e397341c 100644 --- a/code/modules/mob/living/carbon/human/human_defines.dm +++ b/code/modules/mob/living/carbon/human/human_defines.dm @@ -80,6 +80,7 @@ var/hand_blood_color var/list/flavor_texts = list() + var/gunshot_residue mob_bump_flag = HUMAN mob_push_flags = ~HEAVY diff --git a/code/modules/organs/blood.dm b/code/modules/organs/blood.dm index 4c756ace0ce..ebc490601d3 100644 --- a/code/modules/organs/blood.dm +++ b/code/modules/organs/blood.dm @@ -316,4 +316,6 @@ proc/blood_splatter(var/target,var/datum/reagent/blood/source,var/large) if(source.data["virus2"]) B.virus2 = virus_copylist(source.data["virus2"]) + B.fluorescent = 0 + B.invisibility = 0 return B diff --git a/code/modules/projectiles/ammunition.dm b/code/modules/projectiles/ammunition.dm index bb23b9c3ee7..219ebff1410 100644 --- a/code/modules/projectiles/ammunition.dm +++ b/code/modules/projectiles/ammunition.dm @@ -7,6 +7,8 @@ slot_flags = SLOT_BELT | SLOT_EARS throwforce = 1 w_class = 1 + + var/leaves_residue = 1 var/caliber = "" //Which kind of guns it can be loaded into var/projectile_type //The bullet type to create when New() is called var/obj/item/projectile/BB = null //The loaded bullet - make it so that the projectiles are created only when needed? diff --git a/code/modules/projectiles/guns/projectile.dm b/code/modules/projectiles/guns/projectile.dm index 2f48fcaf138..d032cb38483 100644 --- a/code/modules/projectiles/guns/projectile.dm +++ b/code/modules/projectiles/guns/projectile.dm @@ -68,6 +68,16 @@ /obj/item/weapon/gun/projectile/proc/process_chambered() if (!chambered) return + // Aurora forensics port, gunpowder residue. + if(chambered.leaves_residue) + var/mob/living/carbon/human/H = loc + if(istype(H)) + if(!H.gloves) + H.gunshot_residue = chambered.caliber + else + var/obj/item/clothing/G = H.gloves + G.gunshot_residue = chambered.caliber + switch(handle_casings) if(EJECT_CASINGS) //eject casing onto ground. chambered.loc = get_turf(src) diff --git a/code/modules/reagents/Chemistry-Reagents.dm b/code/modules/reagents/Chemistry-Reagents.dm index 0df72664fdf..9330c8cdf5c 100644 --- a/code/modules/reagents/Chemistry-Reagents.dm +++ b/code/modules/reagents/Chemistry-Reagents.dm @@ -115,10 +115,3 @@ /datum/reagent/proc/reaction_mob(var/mob/target) touch_mob(target) - -/datum/reagent/woodpulp - name = "Wood Pulp" - id = "woodpulp" - description = "A mass of wood fibers." - reagent_state = LIQUID - color = "#B97A57" diff --git a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Medicine.dm b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Medicine.dm index b2cd248b257..18e31ddc02d 100644 --- a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Medicine.dm +++ b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Medicine.dm @@ -375,19 +375,27 @@ /datum/reagent/sterilizine name = "Sterilizine" id = "sterilizine" - description = "Sterilizes wounds in preparation for surgery." + description = "Sterilizes wounds in preparation for surgery and thoroughly removes blood." reagent_state = LIQUID color = "#C8A5DC" touch_met = 5 /datum/reagent/sterilizine/affect_touch(var/mob/living/carbon/M, var/alien, var/removed) M.germ_level -= min(removed*20, M.germ_level) + for(var/obj/item/I in M.contents) + I.was_bloodied = null + M.was_bloodied = null /datum/reagent/sterilizine/touch_obj(var/obj/O) O.germ_level -= min(volume*20, O.germ_level) + O.was_bloodied = null /datum/reagent/sterilizine/touch_turf(var/turf/T) T.germ_level -= min(volume*20, T.germ_level) + for(var/obj/item/I in T.contents) + I.was_bloodied = null + for(var/obj/effect/decal/cleanable/blood/B in T) + qdel(B) /datum/reagent/leporazine name = "Leporazine" diff --git a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Other.dm b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Other.dm index 09790d82c2a..fc7dc7606bc 100644 --- a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Other.dm +++ b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Other.dm @@ -271,10 +271,7 @@ touch_met = 50 /datum/reagent/space_cleaner/touch_obj(var/obj/O) - if(istype(O, /obj/effect/decal/cleanable)) - qdel(O) - else - O.clean_blood() + O.clean_blood() /datum/reagent/space_cleaner/touch_turf(var/turf/T) if(volume >= 1) @@ -375,3 +372,23 @@ id = "glue" description = "An extremely powerful bonding agent." color = "#FFFFCC" + +/datum/reagent/woodpulp + name = "Wood Pulp" + id = "woodpulp" + description = "A mass of wood fibers." + reagent_state = LIQUID + color = "#B97A57" + +/datum/reagent/luminol + name = "Luminol" + id = "luminol" + description = "A compound that interacts with blood on the molecular level." + reagent_state = LIQUID + color = "#F2F3F4" + +/datum/reagent/luminol/touch_obj(var/obj/O) + O.reveal_blood() + +/datum/reagent/luminol/touch_mob(var/mob/living/L) + L.reveal_blood() diff --git a/code/modules/reagents/Chemistry-Recipes.dm b/code/modules/reagents/Chemistry-Recipes.dm index 538bb67d868..fe93adee41c 100644 --- a/code/modules/reagents/Chemistry-Recipes.dm +++ b/code/modules/reagents/Chemistry-Recipes.dm @@ -2037,30 +2037,9 @@ required_reagents = list("space_up" = 1, "bluecuracao" = 1, "melonliquor" = 1) result_amount = 3 -/* Removed xenoarcheology stuff -datum - chemical_reaction - lithiumsodiumtungstate //LiNa2WO4, not the easiest chem to mix - name = "Lithium Sodium Tungstate" - id = "lithiumsodiumtungstate" - result = "lithiumsodiumtungstate" - required_reagents = list("lithium" = 1, "sodium" = 2, "tungsten" = 1, "acetone" = 4) - result_amount = 8 - - density_separated_liquid - name = "Density separated sample" - id = "density_separated_sample" - result = "density_separated_sample" - secondary_results = list("chemical_waste" = 1) - required_reagents = list("ground_rock" = 1, "lithiumsodiumtungstate" = 2) - result_amount = 2 - - analysis_liquid - name = "Analysis sample" - id = "analysis_sample" - result = "analysis_sample" - secondary_results = list("chemical_waste" = 1) - required_reagents = list("density_separated_sample" = 5) - result_amount = 4 - requires_heating = 1 -*/ +/datum/chemical_reaction/luminol + name = "Luminol" + id = "luminol" + result = "luminol" + required_reagents = list("hydrogen" = 2, "carbon" = 2, "ammonia" = 2) + result_amount = 6 \ No newline at end of file diff --git a/code/modules/reagents/reagent_containers/spray.dm b/code/modules/reagents/reagent_containers/spray.dm index 15059ee327f..efc8d9c2b83 100644 --- a/code/modules/reagents/reagent_containers/spray.dm +++ b/code/modules/reagents/reagent_containers/spray.dm @@ -55,7 +55,7 @@ /obj/item/weapon/reagent_containers/spray/proc/Spray_at(atom/A as mob|obj, mob/user as mob, proximity) if (A.density && proximity) - A.visible_message("[usr] sprays [A] with [src].") + A.visible_message("[usr] sprays [A] with [src].") reagents.splash(A, amount_per_transfer_from_this) else spawn(0) @@ -107,6 +107,14 @@ ..() reagents.add_reagent("cleaner", volume) +/obj/item/weapon/reagent_containers/spray/sterilizine + name = "sterilizine" + desc = "Great for hiding incriminating bloodstains and sterilizing scalpels." + +/obj/item/weapon/reagent_containers/spray/sterilizine/New() + ..() + reagents.add_reagent("sterilizine", volume) + /obj/item/weapon/reagent_containers/spray/pepper name = "pepperspray" desc = "Manufactured by UhangInc, used to blind and down an opponent quickly." @@ -161,12 +169,12 @@ volume = 600 origin_tech = list(TECH_COMBAT = 3, TECH_MATERIAL = 3, TECH_ENGINEERING = 3) -/obj/item/weapon/reagent_containers/spray/chemsprayer/Spray_at(atom/A as mob|obj) +/obj/item/weapon/reagent_containers/spray/chemsprayer/Spray_at(atom/A as mob|obj) var/direction = get_dir(src, A) var/turf/T = get_turf(A) var/turf/T1 = get_step(T,turn(direction, 90)) - var/turf/T2 = get_step(T,turn(direction, -90)) - var/list/the_targets = list(T, T1, T2) + var/turf/T2 = get_step(T,turn(direction, -90)) + var/list/the_targets = list(T, T1, T2) for(var/a = 1 to 3) spawn(0) diff --git a/icons/obj/device.dmi b/icons/obj/device.dmi index 4297bbb99279621f41f0b628648c31c06fb767d6..281ec1e62ba3827875dd1416e1bac56bc2a2cda0 100644 GIT binary patch delta 3328 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 KMNUMnLSTYijb>y3 delta 3019 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 z z835DGnow-4s!ucX3x|Qee``SPFE*m@8W0Yf8DTK#cp@SyNI4IK*sx*4ipP{n8=xBQ z0KieT+4tE-9O!69-)G|hmJ&P64l<)X=#S=Ge+T_@N`SMkM?x;X%e;OeB0R(~W;A?!> z44gUu082P^0Py>59bpCl?KrDJiHHi>igSUgcD()|5bV$5!e-@PsZ@hHvNg7-I z>eU`-#{mt%%a^6@!!m;p^*at!@7*vN`*8a+PiH^U`R8u3^+193KQ^GoS?lIba69 zhBdz>A|lE`+A7e8(~t1CzTAgHJe+<60NAzjy_~NR3WZ$T5NBs+mp$k6`Eo8NQ)0-> zjE4aIh^}e6e~|hSUDJX>0>(c52s1z;qC%Ip6U=IY9{^Oo{r0NPSHZ8pzaQW1O2lgY z4==38!^6YYzT}{o&+1PA5W0}qX>ROH+G%@U=%w57hlr?Dq|5-nJblmg`IXuMS5sZ; zW_WlQ)n|VX0QhFtTiEyT4SsjN7PFXj?%Vhn`AF6W+nHt%G}{8} z>+6@b>+K7LB&yx_JR^!Nj?kYeqQeortJ7xduQ6KO!P3 zb)F7`AR;0vR1APbL{yd-0EvjGEHMBQ5m8xU03;%!vcv#LL_}rj|5;H84vPP~J^%m! N2>?k&PDHLkV1i%+!(jjb 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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