diff --git a/_maps/map_files/stations/boxstation.dmm b/_maps/map_files/stations/boxstation.dmm
index dfbf64b2423..546c636bb09 100644
--- a/_maps/map_files/stations/boxstation.dmm
+++ b/_maps/map_files/stations/boxstation.dmm
@@ -6632,12 +6632,7 @@
/obj/structure/noticeboard{
pixel_y = -30
},
-/obj/item/paper_bin{
- pixel_x = -3;
- pixel_y = 7
- },
-/obj/item/pen/multi,
-/obj/item/reagent_containers/drinks/flask/detflask,
+/obj/machinery/dnaforensics,
/turf/simulated/floor/carpet/grimey,
/area/station/security/detective)
"ayB" = (
@@ -8861,6 +8856,12 @@
/obj/structure/table/wood,
/obj/item/book/manual/wiki/security_space_law,
/obj/item/clothing/glasses/sunglasses,
+/obj/item/camera/detective,
+/obj/item/camera_film,
+/obj/item/camera_film,
+/obj/item/storage/photo_album{
+ pixel_y = -10
+ },
/turf/simulated/floor/carpet,
/area/station/security/detective)
"aFC" = (
@@ -8883,16 +8884,16 @@
/area/station/science/hallway)
"aFD" = (
/obj/structure/table/wood,
-/obj/item/flashlight/lamp/green{
- on = 0;
- pixel_x = -3;
- pixel_y = 8
- },
/obj/item/storage/fancy/cigarettes/dromedaryco,
/obj/item/ashtray/bronze,
/obj/item/radio/intercom/department/security{
pixel_x = 28
},
+/obj/item/flashlight/lamp/green{
+ on = 0;
+ pixel_x = -3;
+ pixel_y = 8
+ },
/turf/simulated/floor/carpet,
/area/station/security/detective)
"aFF" = (
@@ -9871,6 +9872,12 @@
/obj/item/radio/intercom/department/security{
pixel_y = -28
},
+/obj/item/paper_bin{
+ pixel_x = -3;
+ pixel_y = 7
+ },
+/obj/item/pen/multi,
+/obj/item/reagent_containers/drinks/flask/detflask,
/turf/simulated/floor/carpet/grimey,
/area/station/security/detective)
"aJl" = (
@@ -63736,16 +63743,11 @@
/area/station/medical/virology)
"lvV" = (
/obj/structure/table/wood,
-/obj/item/storage/photo_album{
- pixel_y = -10
- },
-/obj/item/camera_film,
-/obj/item/camera_film,
-/obj/item/camera/detective,
/obj/machinery/requests_console/directional/south,
/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{
dir = 5
},
+/obj/machinery/microscope,
/turf/simulated/floor/carpet/grimey,
/area/station/security/detective)
"lwi" = (
diff --git a/_maps/map_files/stations/cerestation.dmm b/_maps/map_files/stations/cerestation.dmm
index dd894fc6a27..907eee65842 100644
--- a/_maps/map_files/stations/cerestation.dmm
+++ b/_maps/map_files/stations/cerestation.dmm
@@ -3139,6 +3139,7 @@
/obj/structure/cable/orange{
icon_state = "1-8"
},
+/obj/machinery/light,
/turf/simulated/floor/carpet/grimey,
/area/station/security/detective)
"awQ" = (
@@ -22294,7 +22295,6 @@
/turf/simulated/floor/engine,
/area/station/hallway/spacebridge/serveng)
"dkb" = (
-/obj/machinery/requests_console/directional/south,
/obj/structure/cable/orange{
icon_state = "4-8"
},
@@ -27494,9 +27494,6 @@
/obj/structure/cable/orange{
icon_state = "0-4"
},
-/obj/structure/shelf/security,
-/obj/item/book/manual/wiki/security_space_law,
-/obj/item/camera/detective,
/turf/simulated/floor/carpet/grimey,
/area/station/security/detective)
"emC" = (
@@ -28066,7 +28063,7 @@
/obj/structure/cable/orange{
icon_state = "4-8"
},
-/obj/machinery/light,
+/obj/machinery/requests_console/directional/south,
/turf/simulated/floor/carpet/grimey,
/area/station/security/detective)
"etJ" = (
@@ -42258,6 +42255,7 @@
pixel_x = -8;
pixel_y = 11
},
+/obj/item/camera/detective,
/turf/simulated/floor/carpet,
/area/station/security/detective)
"hAH" = (
@@ -55744,10 +55742,10 @@
},
/area/station/hallway/primary/fore/east)
"kms" = (
-/obj/effect/spawner/random/maintenance,
-/obj/effect/spawner/random/storage,
-/turf/simulated/floor/plating,
-/area/station/maintenance/security/aft_port)
+/obj/structure/table/wood,
+/obj/machinery/microscope,
+/turf/simulated/floor/carpet/grimey,
+/area/station/security/detective)
"kmw" = (
/obj/machinery/alarm/directional/north,
/turf/simulated/floor/plasteel{
@@ -79463,8 +79461,7 @@
},
/area/station/maintenance/starboard)
"pqf" = (
-/obj/structure/table,
-/obj/item/storage/box/evidence,
+/obj/effect/spawner/random/storage,
/turf/simulated/floor/plating,
/area/station/maintenance/security/aft_port)
"pqm" = (
@@ -107001,6 +106998,9 @@
/obj/machinery/computer/med_data{
dir = 4
},
+/obj/machinery/light{
+ dir = 8
+ },
/turf/simulated/floor/carpet/grimey,
/area/station/security/detective)
"vjb" = (
@@ -107817,10 +107817,10 @@
/turf/simulated/floor/plating,
/area/station/maintenance/security/starboard)
"vsk" = (
-/obj/structure/table,
-/obj/item/storage/fancy/donut_box,
-/turf/simulated/floor/plating,
-/area/station/maintenance/security/aft_port)
+/obj/structure/table/wood,
+/obj/machinery/dnaforensics,
+/turf/simulated/floor/carpet/grimey,
+/area/station/security/detective)
"vsn" = (
/turf/simulated/floor/plasteel{
icon_state = "dark"
@@ -136933,8 +136933,8 @@ dyJ
nHy
viT
emw
+vsk
hyY
-pqf
diC
nxA
xJa
@@ -137190,8 +137190,8 @@ pPN
fvV
jiW
dkb
+kms
hyY
-vsk
diC
guQ
xJa
@@ -137448,7 +137448,7 @@ hAA
dwb
etG
hyY
-kms
+hyY
diC
xyw
xJa
@@ -137964,7 +137964,7 @@ awL
hyY
xJa
qyT
-duh
+pqf
xJa
cRv
rNK
diff --git a/_maps/map_files/stations/deltastation.dmm b/_maps/map_files/stations/deltastation.dmm
index 8ad47a3358f..1a74e072321 100644
--- a/_maps/map_files/stations/deltastation.dmm
+++ b/_maps/map_files/stations/deltastation.dmm
@@ -23415,6 +23415,7 @@
/obj/item/camera_film,
/obj/item/camera_film,
/obj/item/camera/detective,
+/obj/item/storage/box/evidence,
/turf/simulated/floor/wood,
/area/station/security/detective)
"bNs" = (
@@ -61206,6 +61207,7 @@
},
/obj/item/pen/multi,
/obj/item/toy/crayon/white/chalk,
+/obj/item/hand_labeler,
/turf/simulated/floor/wood,
/area/station/security/detective)
"ips" = (
@@ -72028,7 +72030,8 @@
/turf/simulated/floor/plasteel/dark,
/area/station/medical/surgery/observation)
"nhR" = (
-/obj/structure/filingcabinet/chestdrawer,
+/obj/structure/table/wood,
+/obj/machinery/microscope,
/turf/simulated/floor/wood,
/area/station/security/detective)
"nhT" = (
@@ -92350,6 +92353,11 @@
icon_state = "whitegreen"
},
/area/station/medical/virology)
+"vFQ" = (
+/obj/structure/table/wood,
+/obj/machinery/dnaforensics,
+/turf/simulated/floor/wood,
+/area/station/security/detective)
"vGg" = (
/obj/machinery/economy/vending/coffee,
/obj/structure/sign/poster/official/random/directional/west,
@@ -94278,16 +94286,11 @@
/turf/simulated/floor/plasteel,
/area/station/engineering/atmos)
"wuO" = (
-/obj/structure/table/wood,
/obj/machinery/power/apc/important/directional/west,
/obj/structure/cable{
icon_state = "0-4"
},
-/obj/item/storage/box/evidence,
-/obj/item/hand_labeler,
-/obj/item/folder/red{
- pixel_y = 3
- },
+/obj/structure/filingcabinet/chestdrawer,
/turf/simulated/floor/wood,
/area/station/security/detective)
"wuT" = (
@@ -151126,7 +151129,7 @@ lZy
mft
bLn
bPx
-mEG
+vFQ
nhR
coP
bLn
diff --git a/_maps/map_files/stations/emeraldstation.dmm b/_maps/map_files/stations/emeraldstation.dmm
index c8060a4dcfc..38af231eb50 100644
--- a/_maps/map_files/stations/emeraldstation.dmm
+++ b/_maps/map_files/stations/emeraldstation.dmm
@@ -16018,6 +16018,11 @@
"djk" = (
/obj/structure/table/wood,
/obj/item/reagent_containers/drinks/flask/detflask,
+/obj/item/paper_bin{
+ pixel_x = -3;
+ pixel_y = 7
+ },
+/obj/item/hand_labeler,
/turf/simulated/floor/carpet/grimey,
/area/station/security/detective)
"djB" = (
@@ -44411,8 +44416,7 @@
/area/station/security/armory/secure)
"iNz" = (
/obj/structure/table/wood,
-/obj/item/storage/box/evidence,
-/obj/item/hand_labeler,
+/obj/machinery/microscope,
/turf/simulated/floor/carpet/grimey,
/area/station/security/detective)
"iNH" = (
@@ -68784,6 +68788,8 @@
pixel_y = -10
},
/obj/item/camera/detective,
+/obj/item/taperecorder,
+/obj/item/storage/box/evidence,
/turf/simulated/floor/carpet/grimey,
/area/station/security/detective)
"nJW" = (
@@ -102006,12 +102012,8 @@
/area/station/maintenance/dorms/fore)
"uji" = (
/obj/structure/table/wood,
-/obj/item/paper_bin{
- pixel_x = -3;
- pixel_y = 7
- },
-/obj/item/taperecorder,
/obj/machinery/light,
+/obj/machinery/dnaforensics,
/turf/simulated/floor/carpet/grimey,
/area/station/security/detective)
"ujC" = (
diff --git a/_maps/map_files/stations/metastation.dmm b/_maps/map_files/stations/metastation.dmm
index 56351b28e6b..92990c751f3 100644
--- a/_maps/map_files/stations/metastation.dmm
+++ b/_maps/map_files/stations/metastation.dmm
@@ -6837,7 +6837,6 @@
dir = 4;
pixel_x = -32
},
-/obj/structure/disposalpipe/segment,
/turf/simulated/floor/carpet,
/area/station/security/detective)
"aRO" = (
@@ -30507,9 +30506,10 @@
/turf/simulated/floor/plating,
/area/station/maintenance/aft)
"cUW" = (
-/obj/structure/closet/firecloset,
-/turf/simulated/floor/plasteel,
-/area/station/security/brig)
+/obj/structure/table/wood,
+/obj/machinery/dnaforensics,
+/turf/simulated/floor/carpet,
+/area/station/security/detective)
"cUX" = (
/turf/simulated/floor/plasteel{
icon_state = "dark"
@@ -33831,7 +33831,8 @@
},
/area/station/science/toxins/mixing)
"dJx" = (
-/obj/structure/filingcabinet,
+/obj/structure/disposalpipe/trunk,
+/obj/machinery/disposal,
/turf/simulated/floor/carpet/grimey,
/area/station/security/detective)
"dJB" = (
@@ -34711,9 +34712,10 @@
},
/area/station/science/xenobiology)
"dZZ" = (
-/obj/structure/closet/emcloset,
-/turf/simulated/floor/plasteel,
-/area/station/security/brig)
+/obj/structure/table/wood,
+/obj/machinery/microscope,
+/turf/simulated/floor/carpet,
+/area/station/security/detective)
"eac" = (
/obj/machinery/atmospherics/pipe/simple/visible{
dir = 5
@@ -42088,6 +42090,13 @@
icon_state = "darkgrey"
},
/area/station/maintenance/aft)
+"gEr" = (
+/obj/machinery/atmospherics/pipe/simple/visible/cyan,
+/turf/simulated/floor/plasteel{
+ dir = 8;
+ icon_state = "caution"
+ },
+/area/station/engineering/atmos)
"gEt" = (
/obj/structure/table/glass,
/obj/item/paper/crumpled,
@@ -49357,12 +49366,12 @@
},
/area/station/engineering/control)
"jnZ" = (
-/obj/item/kirbyplants/large/plant14,
/obj/machinery/atmospherics/unary/vent_scrubber/on,
/obj/machinery/camera{
c_tag = "Brig Detective's Office";
dir = 8
},
+/obj/structure/filingcabinet,
/turf/simulated/floor/carpet,
/area/station/security/detective)
"joD" = (
@@ -50364,7 +50373,6 @@
/area/station/engineering/transmission_laser)
"jIf" = (
/obj/machinery/hologram/holopad,
-/obj/structure/disposalpipe/segment,
/turf/simulated/floor/carpet,
/area/station/security/detective)
"jIj" = (
@@ -72095,42 +72103,7 @@
icon_state = "darkbluecorners"
},
/area/station/medical/storage/secondary)
-"roR" = (
-/obj/machinery/atmospherics/pipe/simple/visible/cyan,
-/turf/simulated/floor/plasteel{
- dir = 8;
- icon_state = "caution"
- },
-/area/station/engineering/atmos)
-"roV" = (
-/obj/structure/sign/directions/engineering{
- dir = 4;
- pixel_x = 32
- },
-/obj/structure/sign/directions/bridge{
- dir = 4;
- pixel_x = 32;
- pixel_y = -8
- },
-/obj/structure/sign/directions/security{
- dir = 4;
- pixel_x = 32;
- pixel_y = 8
- },
-/turf/simulated/floor/plasteel{
- icon_state = "neutralcorner"
- },
-/area/station/hallway/primary/fore/west)
"rpt" = (
-/obj/item/radio/intercom{
- name = "west bump";
- pixel_x = -28
- },
-/obj/item/radio/intercom/department/security{
- pixel_x = -28;
- pixel_y = -7
- },
-/obj/structure/disposalpipe/segment,
/turf/simulated/floor/carpet,
/area/station/security/detective)
"rpy" = (
@@ -82342,9 +82315,8 @@
/obj/machinery/atmospherics/pipe/simple/hidden/supply{
dir = 4
},
-/obj/structure/disposalpipe/sortjunction/reversed{
- dir = 4;
- sort_type_txt = "24"
+/obj/structure/disposalpipe/segment{
+ dir = 4
},
/turf/simulated/floor/carpet/grimey,
/area/station/security/detective)
@@ -82774,12 +82746,13 @@
/obj/structure/cable{
icon_state = "4-8"
},
-/obj/structure/disposalpipe/segment{
- dir = 4
- },
/obj/machinery/atmospherics/pipe/manifold/hidden/supply{
dir = 1
},
+/obj/structure/disposalpipe/sortjunction/reversed{
+ dir = 4;
+ sort_type_txt = "24"
+ },
/turf/simulated/floor/carpet/grimey,
/area/station/security/detective)
"vpo" = (
@@ -88496,8 +88469,6 @@
},
/area/station/hallway/supply/aft)
"xta" = (
-/obj/structure/disposalpipe/trunk,
-/obj/machinery/disposal,
/obj/machinery/light_switch{
name = "north bump";
pixel_y = 24
@@ -106558,7 +106529,7 @@ kOC
xqq
wrX
xqq
-roV
+xqq
uFw
rbK
iGn
@@ -119641,8 +119612,8 @@ iBR
iBR
iBR
iBR
-dZZ
-cUW
+qeK
+qeK
qeK
vqm
bdT
@@ -119898,8 +119869,8 @@ aiN
xSg
erZ
iBR
-qeK
-qeK
+cUW
+dZZ
qeK
dJx
voS
@@ -132283,7 +132254,7 @@ uJI
xBg
xoS
bYt
-roR
+gEr
kcr
jOr
kuW
diff --git a/code/datums/datacore.dm b/code/datums/datacore.dm
index 1f0a37d8df3..99c4cb0be13 100644
--- a/code/datums/datacore.dm
+++ b/code/datums/datacore.dm
@@ -169,6 +169,7 @@ GLOBAL_VAR_INIT(record_id_num, 1001)
G.fields["sex"] = capitalize(H.gender)
G.fields["species"] = H.dna.species.name
G.fields["ai_target"] = "None" // for malf hud
+ G.fields["b_dna"] = H.dna.unique_enzymes
// Do some ID card checking stuff here to save on resources
var/card_photo
diff --git a/code/game/jobs/job/security_jobs.dm b/code/game/jobs/job/security_jobs.dm
index 19567d21ce8..f597a80d152 100644
--- a/code/game/jobs/job/security_jobs.dm
+++ b/code/game/jobs/job/security_jobs.dm
@@ -180,7 +180,6 @@
pda = /obj/item/pda/detective
backpack_contents = list(
/obj/item/storage/box/evidence = 1,
- /obj/item/detective_scanner = 1,
/obj/item/melee/classic_baton/telescopic = 1
)
diff --git a/code/game/machinery/computer/medical_records.dm b/code/game/machinery/computer/medical_records.dm
index 811c5c84710..b9361cc63c5 100644
--- a/code/game/machinery/computer/medical_records.dm
+++ b/code/game/machinery/computer/medical_records.dm
@@ -106,6 +106,7 @@
"ref" = "\ref[R]",
"name" = R.fields["name"],
"id" = R.fields["id"],
+ "dna" = R.fields["b_dna"],
"rank" = R.fields["rank"],
"p_stat" = R.fields["p_stat"],
"m_stat" = R.fields["m_stat"])
diff --git a/code/game/machinery/machine_frame.dm b/code/game/machinery/machine_frame.dm
index ede3ae65ca4..b727d972329 100644
--- a/code/game/machinery/machine_frame.dm
+++ b/code/game/machinery/machine_frame.dm
@@ -1261,3 +1261,23 @@ to destroy them and players will be able to make replacements.
/obj/item/stock_parts/micro_laser = 1,
/obj/item/stock_parts/manipulator = 1,
)
+
+// Detective machines
+/obj/item/circuitboard/dnaforensics
+ name = "circuit board (DNA analyzer)"
+ build_path = /obj/machinery/dnaforensics
+ board_type = "machine"
+ origin_tech = "programming=2;combat=2"
+ req_components = list(
+ /obj/item/stock_parts/micro_laser = 2,
+ /obj/item/stock_parts/manipulator = 1)
+
+
+/obj/item/circuitboard/microscope
+ name = "circuit board (Microscope)"
+ build_path = /obj/machinery/microscope
+ board_type = "machine"
+ origin_tech = "programming=2;combat=2"
+ req_components = list(
+ /obj/item/stock_parts/micro_laser = 1,
+ /obj/item/stack/sheet/glass = 1)
diff --git a/code/game/machinery/vendors/wardrobe_vendors.dm b/code/game/machinery/vendors/wardrobe_vendors.dm
index 6594514bbe4..ae05d45ae25 100644
--- a/code/game/machinery/vendors/wardrobe_vendors.dm
+++ b/code/game/machinery/vendors/wardrobe_vendors.dm
@@ -127,7 +127,8 @@
/obj/item/reagent_containers/drinks/flask/detflask = 2,
/obj/item/storage/fancy/cigarettes/dromedaryco = 5,
/obj/item/clothing/head/helmet/space/plasmaman/white = 2,
- /obj/item/clothing/under/plasmaman/enviroslacks = 2)
+ /obj/item/clothing/under/plasmaman/enviroslacks = 2,
+ /obj/item/storage/box/swabs = 2)
prices = list(/obj/item/clothing/under/rank/security/detective = 50,
/obj/item/clothing/under/rank/security/detective/black = 75,
@@ -151,7 +152,8 @@
/obj/item/reagent_containers/drinks/flask/detflask = 50,
/obj/item/storage/fancy/cigarettes/dromedaryco = 5,
/obj/item/clothing/head/helmet/space/plasmaman/white = 60,
- /obj/item/clothing/under/plasmaman/enviroslacks = 60)
+ /obj/item/clothing/under/plasmaman/enviroslacks = 60,
+ /obj/item/storage/box/swabs = 20)
contraband = list(/obj/item/toy/figure/crew/detective = 1)
diff --git a/code/game/objects/effects/decals/cleanable.dm b/code/game/objects/effects/decals/cleanable.dm
index 26171686ba7..0f6b3bdad5e 100644
--- a/code/game/objects/effects/decals/cleanable.dm
+++ b/code/game/objects/effects/decals/cleanable.dm
@@ -53,8 +53,8 @@
bloodiness -= add_blood
S.bloody_shoes[blood_state] = min(MAX_SHOE_BLOODINESS, S.bloody_shoes[blood_state] + add_blood)
S.bloody_shoes[BLOOD_BASE_ALPHA] = BLOODY_FOOTPRINT_BASE_ALPHA * (alpha/255)
- if(blood_DNA && length(blood_DNA))
- S.add_blood(H.blood_DNA, basecolor)
+ if(length(blood_DNA))
+ S.add_blood(blood_DNA, basecolor)
S.blood_state = blood_state
S.blood_color = basecolor
update_icon()
diff --git a/code/game/objects/items/weapons/storage/belt.dm b/code/game/objects/items/weapons/storage/belt.dm
index 880e8813543..38bb7a8a6ec 100644
--- a/code/game/objects/items/weapons/storage/belt.dm
+++ b/code/game/objects/items/weapons/storage/belt.dm
@@ -324,6 +324,9 @@
/obj/item/restraints/legcuffs/bola,
/obj/item/clothing/mask/gas/sechailer,
/obj/item/detective_scanner,
+ /obj/item/sample/print,
+ /obj/item/forensics/swab,
+ /obj/item/forensics/sample_kit,
)
/obj/item/storage/belt/security/full/populate_contents()
diff --git a/code/game/objects/structures/crates_lockers/closets/secure/security_lockers.dm b/code/game/objects/structures/crates_lockers/closets/secure/security_lockers.dm
index 9ce976f4c2c..b06a9d2ddde 100644
--- a/code/game/objects/structures/crates_lockers/closets/secure/security_lockers.dm
+++ b/code/game/objects/structures/crates_lockers/closets/secure/security_lockers.dm
@@ -36,6 +36,7 @@
new /obj/item/reagent_containers/drinks/mug/hop(src)
new /obj/item/clothing/accessory/medal/service(src)
new /obj/item/storage/bag/garment/head_of_personnel(src)
+ new /obj/item/storage/box/fingerprints(src) // used for fingerprinting new arrivals
/obj/structure/closet/secure_closet/hos
name = "head of security's locker"
@@ -195,7 +196,6 @@
new /obj/item/ammo_box/magazine/detective/speedcharger(src)
new /obj/item/clipboard(src)
new /obj/item/clothing/gloves/color/latex(src)
- new /obj/item/detective_scanner(src)
new /obj/item/flash(src)
new /obj/item/flashlight/seclite(src)
new /obj/item/holosign_creator/detective(src)
@@ -205,8 +205,10 @@
new /obj/item/storage/bag/garment/detective(src)
new /obj/item/storage/belt/security(src)
new /obj/item/storage/box/evidence(src)
+ new /obj/item/taperecorder(src)
new /obj/item/storage/box/tapes(src)
new /obj/item/taperecorder(src)
+ new /obj/item/storage/briefcase/crimekit(src)
/obj/structure/closet/secure_closet/injection
name = "lethal injections locker"
diff --git a/code/modules/clothing/clothing.dm b/code/modules/clothing/clothing.dm
index 430b2b00677..f00c8b8c3c8 100644
--- a/code/modules/clothing/clothing.dm
+++ b/code/modules/clothing/clothing.dm
@@ -43,6 +43,8 @@
/// Detective Work, used for allowing a given atom to leave its fibers on stuff. Allowed by default
var/can_leave_fibers = TRUE
+ /// Detective Work, firing a ballistic weapon will add a signature to this var
+ var/gunshot_residue
/obj/item/clothing/update_icon_state()
if(!can_toggle)
@@ -1240,3 +1242,7 @@
icon = 'icons/obj/clothing/neck.dmi'
body_parts_covered = UPPER_TORSO
slot_flags = ITEM_SLOT_NECK
+
+/obj/item/clothing/clean_blood(radiation_clean = FALSE)
+ . = ..()
+ gunshot_residue = null
diff --git a/code/modules/detective_work/evidence.dm b/code/modules/detective_work/evidence.dm
index 79ad107bb01..c27ba9371b1 100644
--- a/code/modules/detective_work/evidence.dm
+++ b/code/modules/detective_work/evidence.dm
@@ -80,3 +80,179 @@
else
to_chat(user, "[src] is empty.")
icon_state = "evidenceobj"
+
+/obj/item/sample
+ name = "\improper Forensic sample"
+ icon = 'icons/obj/forensics/forensics.dmi'
+ w_class = WEIGHT_CLASS_TINY
+ var/list/evidence = list()
+ new_attack_chain = TRUE
+
+/obj/item/sample/Initialize(mapload, atom/supplied)
+ . = ..()
+ if(supplied)
+ transfer_evidence(supplied)
+ name = "[initial(name)] ([supplied])"
+
+/obj/item/sample/print/New(newloc, atom/supplied)
+ . = ..()
+ if(length(evidence))
+ icon_state = "fingerprint1"
+
+/obj/item/sample/proc/transfer_evidence(atom/supplied)
+ if(length(supplied.suit_fibers))
+ evidence = supplied.suit_fibers.Copy()
+ supplied.suit_fibers.Cut()
+
+/obj/item/sample/proc/merge_evidence(obj/item/sample/supplied, mob/user)
+ if(!length(supplied.evidence))
+ return FALSE
+ evidence |= supplied.evidence
+ name = "[initial(name)] (combined)"
+ to_chat(user, "You add [supplied] to [src].")
+ return TRUE
+
+/obj/item/sample/print/merge_evidence(obj/item/sample/supplied, mob/user)
+ if(!supplied || !length(supplied.evidence))
+ return FALSE
+ 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)"
+ to_chat(user, "You overlay [src] and [supplied], combining the print records.")
+ return TRUE
+
+/obj/item/sample/item_interaction(mob/living/user, obj/item/used, list/modifiers)
+ if(istype(used, src.type))
+ user.unequip(used)
+ if(merge_evidence(used, user))
+ qdel(used)
+ return ITEM_INTERACT_COMPLETE
+ return ..()
+
+/obj/item/sample/print
+ name = "fingerprint card"
+ desc = "Preserves fingerprints."
+ icon = 'icons/obj/card.dmi'
+ icon_state = "fingerprint0"
+ item_state = "paper"
+ /// boolean variable, indicates if print card is in the used state
+ var/used
+
+/obj/item/sample/print/update_icon_state()
+ . = ..()
+ icon_state = "fingerprint0"
+ if(used)
+ icon_state = "fingerprint1"
+
+
+/obj/item/sample/print/activate_self(mob/user)
+ if(..() || length(evidence) || !ishuman(user))
+ return FINISH_ATTACK
+
+ var/mob/living/carbon/human/H = user
+ if(H.gloves)
+ to_chat(user, "Take [H.gloves] off first.")
+ return FINISH_ATTACK
+
+ to_chat(user, "You press your fingertips firmly against the card.")
+ var/fullprint = H.get_full_print()
+ evidence[fullprint] = fullprint
+ name = "[initial(name)] ([H])"
+ used = TRUE
+ update_icon(UPDATE_ICON_STATE)
+
+/obj/item/sample/print/interact_with_atom(atom/target, mob/living/user, list/modifiers)
+ var/mob/living/carbon/human/H = target
+ if(user.zone_selected == "r_hand" || user.zone_selected == "l_hand")
+
+ if(!ishuman(target))
+ return ..()
+
+ if(length(evidence))
+ return ITEM_INTERACT_COMPLETE
+
+ if(H.gloves)
+ to_chat(user, "[H] is wearing gloves.")
+ return ITEM_INTERACT_COMPLETE
+
+ if(user != H && H.stat == CONSCIOUS && !H.restrained())
+ user.visible_message("[user] tried to fingerprint [H], but he resists.")
+ return ITEM_INTERACT_COMPLETE
+
+ var/has_hand = (H.has_organ("r_hand") || H.has_organ("l_hand"))
+ if(!has_hand)
+ to_chat(user, "[H] has no hands!")
+ return ITEM_INTERACT_COMPLETE
+ if(!do_after(user, 2 SECONDS, target = H))
+ return ITEM_INTERACT_COMPLETE
+
+ user.visible_message("[user] makes a copy of [H]'s fingerprints'.")
+ var/fullprint = H.get_full_print()
+ evidence[fullprint] = fullprint
+ transfer_evidence(src)
+ name = "[initial(name)] ([H])"
+ used = TRUE
+ update_appearance(UPDATE_ICON_STATE)
+ return ITEM_INTERACT_COMPLETE
+ else
+ to_chat(user, "You need to target [H]'s hands to obtain fingerprints!'.")
+ return ITEM_INTERACT_COMPLETE
+
+/obj/item/sample/print/transfer_evidence(atom/supplied)
+ if(length(supplied.fingerprints))
+ for(var/print in supplied.fingerprints)
+ evidence[print] = supplied.fingerprints[print]
+ supplied.fingerprints.Cut()
+
+/obj/item/sample/fibers
+ name = "fiber bag"
+ desc = "Used to store fiber evidence for forensic examianation."
+ icon_state = "fiberbag"
+
+/obj/item/forensics
+ icon = 'icons/obj/forensics/forensics.dmi'
+ w_class = WEIGHT_CLASS_TINY
+ new_attack_chain = TRUE
+
+/obj/item/forensics/sample_kit
+ name = "fiber collection kit"
+ desc = "A magnifying glass and pair of tweezers. Used to lift fabric fibers from crime scene objects. Use on harm intent to collect samples without disturbing the scene."
+ icon_state = "m_glass"
+ w_class = WEIGHT_CLASS_SMALL
+ /// naming for individual evidence items
+ var/evidence_type = "fibers"
+ var/evidence_path = /obj/item/sample/fibers
+/obj/item/forensics/sample_kit/proc/can_take_sample(mob/user, atom/supplied)
+ return length(supplied.suit_fibers)
+
+/obj/item/forensics/sample_kit/proc/take_sample(mob/user, atom/supplied)
+ var/obj/item/sample/S = new evidence_path(get_turf(user))
+ S.transfer_evidence(supplied)
+ to_chat(user, "You move [S.evidence.len] [S.evidence.len > 1 ? "[evidence_type]s" : "[evidence_type]"] into [S].")
+
+/obj/item/forensics/sample_kit/interact_with_atom(atom/target, mob/living/user, list/modifiers)
+ if(user.a_intent != INTENT_HARM)
+ return ..()
+
+ if(can_take_sample(user, target))
+ take_sample(user, target)
+ else
+ to_chat(user, "You cannot find [evidence_type] on [target].")
+ return ITEM_INTERACT_COMPLETE
+
+/obj/item/forensics/sample_kit/MouseDrop(atom/over)
+ if(ismob(src.loc))
+ interact_with_atom(over, loc)
+
+/obj/item/forensics/sample_kit/powder
+ name = "fingerprint powder"
+ desc = "A jar of aluminum powder and a specialized brush. Use on harm intent to collect samples without leaving additional prints."
+ icon_state = "dust"
+ evidence_type = "prints"
+ evidence_path = /obj/item/sample/print
+
+/obj/item/forensics/sample_kit/powder/can_take_sample(mob/user, atom/supplied)
+ return length(supplied.fingerprints)
diff --git a/code/modules/detective_work/forensic_kits.dm b/code/modules/detective_work/forensic_kits.dm
new file mode 100644
index 00000000000..88847cab09b
--- /dev/null
+++ b/code/modules/detective_work/forensic_kits.dm
@@ -0,0 +1,62 @@
+// Boxes
+/obj/item/storage/box/swabs
+ name = "box of forensic swabs"
+ desc = "A sterile box full of forensic swabs, used to acquire DNA samples and test for lingering weapon residue."
+ icon_state = "sec_box"
+
+/obj/item/storage/box/swabs/populate_contents()
+ ..()
+ new /obj/item/forensics/swab(src)
+ new /obj/item/forensics/swab(src)
+ new /obj/item/forensics/swab(src)
+ new /obj/item/forensics/swab(src)
+ new /obj/item/forensics/swab(src)
+ new /obj/item/forensics/swab(src)
+
+/obj/item/storage/box/fingerprints
+ name = "box of fingerprint cards"
+ desc = "A sealed box full of fingerprint cards, used to acquire print sets from humanoids."
+ icon_state = "id_prisoner_box"
+
+/obj/item/storage/box/fingerprints/populate_contents()
+ ..()
+ new /obj/item/sample/print(src)
+ new /obj/item/sample/print(src)
+ new /obj/item/sample/print(src)
+ new /obj/item/sample/print(src)
+ new /obj/item/sample/print(src)
+ new /obj/item/sample/print(src)
+
+// Crime scene kit
+/obj/item/storage/briefcase/crimekit
+ name = "crime scene kit"
+ desc = "A stainless-steel case containing everything a detective could need. It's quite heavy."
+ icon = 'icons/obj/forensics/forensics.dmi'
+ icon_state = "case"
+ lefthand_file = 'icons/obj/forensics/items_lefthand.dmi'
+ righthand_file = 'icons/obj/forensics/items_righthand.dmi'
+ item_state = "case"
+
+/obj/item/storage/briefcase/crimekit/populate_contents()
+ ..()
+ new /obj/item/storage/box/swabs(src)
+ new /obj/item/storage/box/fingerprints(src)
+ new /obj/item/forensics/sample_kit(src)
+ new /obj/item/forensics/sample_kit/powder(src)
+ new /obj/item/storage/csi_markers(src)
+
+/obj/item/storage/csi_markers
+ name = "crime scene markers box"
+ desc = "A cardboard box full of crime scene marker cards."
+ icon = 'icons/obj/boxes.dmi'
+ icon_state = "sec_box"
+ w_class = WEIGHT_CLASS_TINY
+
+/obj/item/storage/csi_markers/populate_contents()
+ new /obj/item/csi_marker/n1(src)
+ new /obj/item/csi_marker/n2(src)
+ new /obj/item/csi_marker/n3(src)
+ new /obj/item/csi_marker/n4(src)
+ new /obj/item/csi_marker/n5(src)
+ new /obj/item/csi_marker/n6(src)
+ new /obj/item/csi_marker/n7(src)
diff --git a/code/modules/detective_work/forensic_machines.dm b/code/modules/detective_work/forensic_machines.dm
new file mode 100644
index 00000000000..dbd0aa26d41
--- /dev/null
+++ b/code/modules/detective_work/forensic_machines.dm
@@ -0,0 +1,289 @@
+// DNA machine
+/obj/machinery/dnaforensics
+ name = "\improper DNA analyzer"
+ desc = "A high-tech machine that is designed to sequence DNA samples."
+ icon = 'icons/obj/forensics/forensics.dmi'
+ icon_state = "dna-open"
+ anchored = TRUE
+ density = TRUE
+
+ var/obj/item/forensics/swab = null
+ /// is currently scanning
+ var/scanning = FALSE
+ /// Global number of reports ran from that machine type
+ var/report_num = FALSE
+
+/obj/machinery/dnaforensics/Initialize(mapload)
+ . = ..()
+ component_parts = list()
+ component_parts += new /obj/item/circuitboard/dnaforensics(null)
+ component_parts += new /obj/item/stock_parts/micro_laser(null)
+ component_parts += new /obj/item/stock_parts/manipulator(null)
+ component_parts += new /obj/item/stock_parts/micro_laser(null)
+ RefreshParts()
+
+/obj/machinery/dnaforensics/examine(mob/user)
+ . = ..()
+ . += "Click while holding a sample to insert a sample."
+ . += "Alt-Click to eject the current sample."
+ . += "Click with an empty hand to analyze the current sample."
+
+/obj/machinery/dnaforensics/item_interaction(mob/living/user, obj/item/used, list/modifiers)
+ if(!istype(used, /obj/item/forensics))
+ return ..()
+
+ if(panel_open)
+ to_chat(user, "You must close the panel!")
+ return ITEM_INTERACT_COMPLETE
+
+ if(swab)
+ to_chat(user, "There is already a sample inside the scanner.")
+ return ITEM_INTERACT_COMPLETE
+
+ if(istype(used, /obj/item/forensics/swab))
+ to_chat(user, "You insert [used] into [src].")
+ user.unequip(used)
+ used.forceMove(src)
+ swab = used
+ update_icon()
+ else
+ to_chat(user, "This is not a compatible sample!")
+ return ITEM_INTERACT_COMPLETE
+
+/obj/machinery/dnaforensics/attack_hand(mob/user)
+
+ if(!swab)
+ to_chat(user, "The scanner is empty!")
+ return
+ scanning = TRUE
+ update_appearance(UPDATE_ICON)
+ to_chat(user, "The scanner begins to hum as you analyze [swab].")
+
+ if(!do_after(user, 2.5 SECONDS, src) || QDELETED(swab))
+ to_chat(user, "You have stopped analyzing [swab || "the swab"].")
+ scanning = FALSE
+ update_appearance(UPDATE_ICON)
+ return
+
+ to_chat(user, "Printing report...")
+ var/obj/item/paper/report = new(get_turf(src))
+ report.stamped = list(/obj/item/stamp)
+ report_num++
+
+ var/obj/item/forensics/swab/bloodswab = swab
+ report.name = ("DNA scanner report no. [++report_num]: [bloodswab.name]")
+ // dna data itself
+ var/data = "No analysis data available."
+ if(!isnull(bloodswab.dna))
+ data = "Spectrometric analysis on the provided sample determined the presence of DNA. DNA String(s) found: [length(bloodswab.dna)].
"
+ for(var/blood in bloodswab.dna)
+ data += "Blood type: [bloodswab.dna[blood]]
\nDNA: [blood]
"
+ else
+ data += "\nNo DNA found.
"
+ report.info = "Report number: [report_num]
"
+ report.info += "\nAnalyzed object:
[bloodswab.name]
[bloodswab.desc]
" + data
+ report.forceMove(get_turf(src))
+ report.update_icon()
+ scanning = FALSE
+ update_appearance(UPDATE_ICON)
+
+/obj/machinery/dnaforensics/proc/remove_sample(mob/living/remover)
+ if(!istype(remover) || HAS_TRAIT(remover, TRAIT_HANDS_BLOCKED) || !Adjacent(remover))
+ return
+ if(!swab)
+ to_chat(remover, "There is no sample inside the scanner!")
+ return
+ to_chat(remover, "You remove [swab] from the scanner.")
+ swab.forceMove(get_turf(src))
+ remover.put_in_hands(swab)
+ swab = null
+ update_appearance(UPDATE_ICON_STATE)
+
+/obj/machinery/dnaforensics/AltClick()
+ remove_sample(usr)
+
+/obj/machinery/dnaforensics/MouseDrop(atom/other)
+ if(usr == other)
+ remove_sample(usr)
+ return
+ return ..()
+
+/obj/machinery/dnaforensics/update_icon_state()
+ if(scanning)
+ icon_state = "dna-working"
+ else if(swab)
+ icon_state = "dna-closed"
+ else
+ icon_state = "dna-open"
+
+/obj/machinery/dnaforensics/screwdriver_act(mob/user, obj/item/I)
+ if(swab)
+ return
+ . = TRUE
+ default_deconstruction_screwdriver(user, "dna-open-unpowered", "dna-open", I)
+
+/obj/machinery/dnaforensics/wrench_act(mob/user, obj/item/I)
+ . = TRUE
+ default_unfasten_wrench(user, I)
+
+/obj/machinery/dnaforensics/crowbar_act(mob/user, obj/item/I)
+ if(swab)
+ return
+ . = TRUE
+ default_deconstruction_crowbar(user, I)
+
+// Microscope code itself
+/obj/machinery/microscope
+ name = "microscope"
+ desc = "A microscope capable of magnifying images up to 3000 times."
+ icon = 'icons/obj/forensics/forensics.dmi'
+ icon_state = "microscope"
+ anchored = TRUE
+ density = TRUE
+ var/obj/item/sample = null
+ var/report_num = 0
+ var/fingerprint_complete = 6
+
+/obj/machinery/microscope/Initialize(mapload)
+ . = ..()
+ component_parts = list()
+ component_parts += new /obj/item/circuitboard/microscope(null)
+ component_parts += new /obj/item/stock_parts/micro_laser(null)
+ component_parts += new /obj/item/stack/sheet/glass(null)
+ RefreshParts()
+
+/obj/machinery/microscope/examine(mob/user)
+ . = ..()
+ . += "Click while holding a sample to insert a sample."
+ . += "Alt-Click to eject the current sample."
+ . += "Click with an empty hand to study the current sample."
+
+/obj/machinery/microscope/item_interaction(mob/living/user, obj/item/used, list/modifiers)
+ if(!istype(used, /obj/item/forensics/swab) && !istype(used, /obj/item/sample))
+ return ..()
+
+ if(panel_open)
+ to_chat(user, "You must close the panel!")
+ return ITEM_INTERACT_COMPLETE
+
+ if(sample)
+ to_chat(user, "There is already a sample in the microscope!")
+ return ITEM_INTERACT_COMPLETE
+
+ if(istype(used, /obj/item/forensics/swab)|| istype(used, /obj/item/sample/fibers) || istype(used, /obj/item/sample/print))
+ add_fingerprint(user)
+ to_chat(user, "You insert [used] into the microscope.")
+ user.unequip(used)
+ used.forceMove(src)
+ sample = used
+ update_appearance(UPDATE_ICON_STATE)
+ return ITEM_INTERACT_COMPLETE
+
+/obj/machinery/microscope/attack_hand(mob/user)
+
+ if(!sample)
+ to_chat(user, "There is no sample in the microscope to study.")
+ return
+
+ add_fingerprint(user)
+ to_chat(user, "The microscope buzzes as you study [sample].")
+
+ if(!do_after(user, 2.5 SECONDS, src) || !sample)
+ to_chat(user, "You stop studying [sample].")
+ return
+
+ to_chat(user, "Printing Report...")
+ var/obj/item/paper/report = new(get_turf(src))
+ report.stamped = list(/obj/item/stamp)
+ report_num++
+
+ if(istype(sample, /obj/item/forensics/swab))
+ var/obj/item/forensics/swab/swab = sample
+
+ report.name = ("Forensic report no. [++report_num]: [swab.name]")
+ report.info = "Report number: [report_num]
"
+ report.info += "Analyzed object:
[swab.name]
"
+
+ if(swab.gsr)
+ report.info += "Gunpowder residue found. Caliber: [swab.gsr]."
+ else
+ report.info += "Powder residue from the bullet was not found."
+
+ else if(istype(sample, /obj/item/sample/fibers))
+ var/obj/item/sample/fibers/fibers = sample
+ report.name = ("Report on fiber sample no. [++report_num]: [fibers.name]")
+ report.info = "Report number: [report_num]
"
+ report.info += "Analyzed object:
[fibers.name]
"
+ if(fibers.evidence)
+ report.info += "Molecular analysis on the provided sample determined the presence of the following unique fiber strands:
"
+ for(var/fiber in fibers.evidence)
+ report.info += "Most Likely Match: [fiber]
"
+ else
+ report.info += "No fibers found."
+ else if(istype(sample, /obj/item/sample/print))
+ report.name = ("Fingerprint Analysis Report No. [report_num]: [sample.name]")
+ report.info = "Report number: [report_num]
"
+ report.info += "Fingerprint Analysis Report No. [report_num]: [sample.name]
"
+ var/obj/item/sample/print/card = sample
+ if(card.evidence && card.evidence.len)
+ report.info += "
Surface analysis identified the following unique fingerprints:
"
+ for(var/prints in card.evidence)
+ report.info += "Fingerprint: "
+ if(!is_complete_print(prints))
+ report.info += "INCOMPLETE PRINT"
+ else
+ report.info += "[prints]"
+ report.info += "
"
+ else
+ report.info += "No analysis information available."
+
+ if(report)
+ report.update_icon()
+ if(report.info)
+ to_chat(user, report.info)
+ return
+
+/obj/machinery/microscope/proc/remove_sample(mob/living/remover)
+ if(!istype(remover) || remover.incapacitated() || !Adjacent(remover))
+ return
+ if(!sample)
+ to_chat(remover, "There is no sample inside the microscope!")
+ return
+ to_chat(remover, "you removed [sample] from the microscope.")
+ sample.forceMove(get_turf(src))
+ remover.put_in_hands(sample)
+ sample = null
+ update_appearance(UPDATE_ICON_STATE)
+
+/obj/machinery/microscope/proc/is_complete_print(print)
+ return stringpercent(print) <= fingerprint_complete
+
+/obj/machinery/microscope/AltClick()
+ remove_sample(usr)
+
+/obj/machinery/microscope/MouseDrop(atom/other)
+ if(usr == other)
+ remove_sample(usr)
+ else
+ return ..()
+
+/obj/machinery/microscope/update_icon_state()
+ icon_state = "microscope"
+ if(sample)
+ icon_state += "slide"
+
+/obj/machinery/microscope/screwdriver_act(mob/user, obj/item/I)
+ if(sample)
+ return
+ . = TRUE
+ default_deconstruction_screwdriver(user, "microscope_off", "microscope", I)
+
+/obj/machinery/microscope/wrench_act(mob/user, obj/item/I)
+ . = TRUE
+ default_unfasten_wrench(user, I)
+
+/obj/machinery/microscope/crowbar_act(mob/user, obj/item/I)
+ if(sample)
+ return
+ . = TRUE
+ default_deconstruction_crowbar(user, I)
diff --git a/code/modules/detective_work/scene_cards.dm b/code/modules/detective_work/scene_cards.dm
new file mode 100644
index 00000000000..ae2310d22a9
--- /dev/null
+++ b/code/modules/detective_work/scene_cards.dm
@@ -0,0 +1,35 @@
+/obj/item/csi_marker
+ name = "crime scene marker"
+ desc = "Plastic cards used to mark points of interests on the scene. Just like in the holoshows!"
+ icon = 'icons/obj/forensics/forensics.dmi'
+ icon_state = "card1"
+ w_class = WEIGHT_CLASS_TINY
+ /// Used for the number on the marker
+ var/number
+
+/obj/item/csi_marker/n1
+ number = 1
+
+/obj/item/csi_marker/n2
+ number = 2
+ icon_state = "card2"
+
+/obj/item/csi_marker/n3
+ number = 3
+ icon_state = "card3"
+
+/obj/item/csi_marker/n4
+ number = 4
+ icon_state = "card4"
+
+/obj/item/csi_marker/n5
+ number = 5
+ icon_state = "card5"
+
+/obj/item/csi_marker/n6
+ number = 6
+ icon_state = "card6"
+
+/obj/item/csi_marker/n7
+ number = 7
+ icon_state = "card7"
diff --git a/code/modules/detective_work/swabs.dm b/code/modules/detective_work/swabs.dm
new file mode 100644
index 00000000000..6cb5db334fe
--- /dev/null
+++ b/code/modules/detective_work/swabs.dm
@@ -0,0 +1,102 @@
+/obj/item/forensics/swab
+ name = "sample collection kit"
+ desc = "A sterile cotton swab and test tube for collecting samples."
+ icon_state = "swab"
+ /// currently in machine
+ var/dispenser = FALSE
+ /// gunshot residue data
+ var/gsr = FALSE
+ /// DNA sample data
+ var/list/dna
+ /// boolean, used or not
+ var/used
+
+/obj/item/forensics/swab/proc/is_used()
+ return used
+
+/obj/item/forensics/swab/interact_with_atom(atom/target, mob/living/user, list/modifiers)
+ if(isstorage(target)) // Storage handling. Maybe add an intent check?
+ return ..()
+
+ if(is_used())
+ to_chat(user, "This sample kit is already used.")
+ return ITEM_INTERACT_COMPLETE
+
+ add_fingerprint(user)
+ in_use = TRUE
+ to_chat(user, "You start collecting evidence...")
+ if(do_after(user, 2 SECONDS, target = user))
+ var/list/choices = list()
+ var/list/found_blood = list()
+
+ // This is utterly hateful, yes, but so is blood_DNA. - Chuga
+ if(issimulatedturf(target))
+ for(var/obj/effect/decal/cleanable/C in target.contents)
+ if(istype(C, /obj/effect/decal/cleanable/blood) || istype(C, /obj/effect/decal/cleanable/trail_holder))
+ found_blood |= C.blood_DNA
+ else if(isliving(target))
+ var/mob/living/L = target
+ found_blood |= L.get_blood_dna_list()
+ else
+ if(target.blood_DNA)
+ found_blood |= target.blood_DNA
+
+ if(length(found_blood))
+ choices |= "Blood"
+ if(istype(target, /obj/item/clothing/gloves))
+ choices |= "Gunpowder particles"
+
+ var/choice
+ if(!length(choices))
+ to_chat(user, "There is no evidence on [target].")
+ in_use = FALSE
+ return ITEM_INTERACT_COMPLETE
+ else if(length(choices) == 1)
+ choice = choices[1]
+ else
+ choice = tgui_input_list(user, "What evidence are you looking for?", "Collection of evidence", choices)
+
+ if(!choice)
+ in_use = FALSE
+ return ITEM_INTERACT_COMPLETE
+
+ var/sample_type
+ var/target_dna
+ var/target_gsr
+ if(choice == "Blood")
+ target_dna = found_blood
+ sample_type = "blood"
+
+ else if(choice == "Gunpowder particles")
+ var/obj/item/clothing/B = target
+ if(!istype(B) || !B.gunshot_residue)
+ to_chat(user, "There is not a hint of gunpowder on [target].")
+ in_use = FALSE
+ return ITEM_INTERACT_COMPLETE
+ target_gsr = B.gunshot_residue
+ sample_type = "powder"
+
+ if(sample_type)
+ user.visible_message(
+ "[user] takes a swab from [target] for analysis.",
+ "You take a swab from [target] for analysis.")
+ if(!dispenser)
+ dna = target_dna
+ gsr = target_gsr
+ set_used(sample_type, target)
+ else
+ var/obj/item/forensics/swab/S = new(get_turf(user))
+ S.dna = target_dna
+ S.gsr = target_gsr
+ S.set_used(sample_type, target)
+ in_use = FALSE
+ return ITEM_INTERACT_COMPLETE
+
+/obj/item/forensics/swab/proc/set_used(sample_str, atom/source)
+ name = "[initial(name)] ([sample_str] - [source])"
+ desc = "[initial(desc)] The label on the bottle reads: 'Sample [sample_str] - [source].'."
+ icon_state = "swab_used"
+ used = TRUE
+
+/obj/item/forensics/swab/cyborg
+ dispenser = TRUE
diff --git a/code/modules/mob/mob_vars.dm b/code/modules/mob/mob_vars.dm
index fd8575ce1cb..69f4cbae906 100644
--- a/code/modules/mob/mob_vars.dm
+++ b/code/modules/mob/mob_vars.dm
@@ -254,7 +254,8 @@
/// This is *not* necessarily an instance of [/datum/click_intercept].
var/datum/click_interceptor
+ /// gunshot residue for det work. holds the caliber of any BALLISTIC weapon fired by this mob without gloves.
+ var/gunshot_residue
/// For storing what do_after's something has, key = string, value = amount of interactions of that type happening.
var/list/do_afters
-
new_attack_chain = TRUE
diff --git a/code/modules/projectiles/ammunition.dm b/code/modules/projectiles/ammunition.dm
index 29afa7ac37b..2578520c00f 100644
--- a/code/modules/projectiles/ammunition.dm
+++ b/code/modules/projectiles/ammunition.dm
@@ -276,3 +276,12 @@
for(var/obj/item/ammo in stored_ammo)
ammo.forceMove(turf_mag)
stored_ammo -= ammo
+
+/obj/item/ammo_casing/proc/leave_residue(mob/living/carbon/human/H)
+ if(!istype(H))
+ return
+ if(H.gloves)
+ var/obj/item/clothing/G = H.gloves
+ G.gunshot_residue = caliber
+ else
+ H.gunshot_residue = caliber
diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm
index d959b2da722..35d051414d7 100644
--- a/code/modules/projectiles/gun.dm
+++ b/code/modules/projectiles/gun.dm
@@ -313,6 +313,8 @@
shoot_live_shot(user, target, TRUE, message)
else
shoot_live_shot(user, target, FALSE, message)
+ chambered.leave_residue(user)
+
else
shoot_with_empty_chamber(user)
return
diff --git a/code/modules/research/designs/autolathe_designs.dm b/code/modules/research/designs/autolathe_designs.dm
index 12f41c3b863..4b434559c68 100644
--- a/code/modules/research/designs/autolathe_designs.dm
+++ b/code/modules/research/designs/autolathe_designs.dm
@@ -1086,3 +1086,11 @@
materials = list(MAT_METAL = 500)
build_path = /obj/item/melee/knuckleduster
category = list("hacked", "Security")
+
+/datum/design/sample_kit
+ name = "Forensic Swab"
+ id = "forensicswab"
+ build_type = AUTOLATHE
+ materials = list(MAT_METAL = 500, MAT_GLASS = 250)
+ build_path = /obj/item/forensics/swab
+ category = list("initial", "Security")
diff --git a/code/modules/research/designs/machine_designs.dm b/code/modules/research/designs/machine_designs.dm
index 0f4b4802a62..5051d9c5763 100644
--- a/code/modules/research/designs/machine_designs.dm
+++ b/code/modules/research/designs/machine_designs.dm
@@ -729,3 +729,24 @@
materials = list(MAT_GLASS = 1000)
build_path = /obj/item/circuitboard/organ_analyzer
category = list("Research Machinery")
+
+// Forensic Machines (DNA analyzer, microscope)
+/datum/design/dnaforensics
+ name = "Machine Design (DNA analyzer)"
+ desc = "DNA analyzer for forensic DNA analysis of objects."
+ id = "dnaforensics"
+ req_tech = list("programming" = 2, "combat" = 2, "magnets" = 2)
+ build_type = IMPRINTER
+ materials = list(MAT_GLASS = 1000)
+ build_path = /obj/item/circuitboard/dnaforensics
+ category = list("Misc. Machinery")
+
+/datum/design/microscope
+ name = "Machine Design (Forensic Microscope)"
+ desc = "Microscope capable of magnifying images 3000 times"
+ id = "microscope"
+ req_tech = list("programming" = 2, "combat" = 2, "magnets" = 2)
+ build_type = IMPRINTER
+ materials = list(MAT_GLASS = 1000)
+ build_path = /obj/item/circuitboard/microscope
+ category = list("Misc. Machinery")
diff --git a/code/modules/supply/supply_packs/pack_security.dm b/code/modules/supply/supply_packs/pack_security.dm
index 456fcd55c2d..2d81da2daa3 100644
--- a/code/modules/supply/supply_packs/pack_security.dm
+++ b/code/modules/supply/supply_packs/pack_security.dm
@@ -93,15 +93,11 @@
containername = "disabler crate"
/datum/supply_packs/security/forensics
- name = "Forensics Crate"
- contains = list(/obj/item/detective_scanner,
- /obj/item/storage/box/evidence,
- /obj/item/camera,
- /obj/item/taperecorder,
- /obj/item/toy/crayon/white,
- /obj/item/clothing/head/det_hat)
- cost = 100
- containername = "forensics crate"
+ name = "Forensics Resupply Crate"
+ contains = list(/obj/item/storage/briefcase/crimekit,
+ /obj/item/storage/briefcase/crimekit)
+ cost = 250
+ containername = "forensics resupply crate"
///// Armory stuff
diff --git a/icons/obj/forensics/forensics.dmi b/icons/obj/forensics/forensics.dmi
new file mode 100644
index 00000000000..3c1caa35d32
Binary files /dev/null and b/icons/obj/forensics/forensics.dmi differ
diff --git a/icons/obj/forensics/items_lefthand.dmi b/icons/obj/forensics/items_lefthand.dmi
new file mode 100644
index 00000000000..06111f9dbaa
Binary files /dev/null and b/icons/obj/forensics/items_lefthand.dmi differ
diff --git a/icons/obj/forensics/items_righthand.dmi b/icons/obj/forensics/items_righthand.dmi
new file mode 100644
index 00000000000..ce269b6c062
Binary files /dev/null and b/icons/obj/forensics/items_righthand.dmi differ
diff --git a/paradise.dme b/paradise.dme
index 80e0179cd27..cd472d0a44c 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -2039,7 +2039,11 @@
#include "code\modules\detective_work\detective_work.dm"
#include "code\modules\detective_work\evidence.dm"
#include "code\modules\detective_work\footprints_and_rag.dm"
+#include "code\modules\detective_work\forensic_kits.dm"
+#include "code\modules\detective_work\forensic_machines.dm"
#include "code\modules\detective_work\scanner.dm"
+#include "code\modules\detective_work\scene_cards.dm"
+#include "code\modules\detective_work\swabs.dm"
#include "code\modules\economy\Accounts_DB.dm"
#include "code\modules\economy\economy_admin.dm"
#include "code\modules\economy\economy_helpers.dm"
diff --git a/tgui/packages/tgui/interfaces/MedicalRecords.jsx b/tgui/packages/tgui/interfaces/MedicalRecords.jsx
index 884887b64a8..a4c3e0a58ad 100644
--- a/tgui/packages/tgui/interfaces/MedicalRecords.jsx
+++ b/tgui/packages/tgui/interfaces/MedicalRecords.jsx
@@ -133,6 +133,7 @@ const MedicalRecordsListBase = (_properties) => {
'+(n?e:_(e,!0))+"\n":""+(n?e:_(e,!0))+"\n"}},{key:"blockquote",value:function(e){return"\n".concat(e,"\n")}},{key:"html",value:function(e){return e}},{key:"heading",value:function(e,t,n,r){if(this.options.headerIds){var i=this.options.headerPrefix+r.slug(n);return"
".concat(e,"
\n")}},{key:"table",value:function(e,t){return t&&(t="".concat(t,"")),"".concat(e,"")}},{key:"br",value:function(){return this.options.xhtml?"