diff --git a/baystation12.dme b/baystation12.dme
index 3cc437dad63..0ec465a3de5 100644
--- a/baystation12.dme
+++ b/baystation12.dme
@@ -252,7 +252,6 @@
#define FILE_DIR "interface"
#define FILE_DIR "maps"
#define FILE_DIR "maps/RandomZLevels"
-#define FILE_DIR "music"
#define FILE_DIR "sound"
#define FILE_DIR "sound/AI"
#define FILE_DIR "sound/ambience"
@@ -267,6 +266,8 @@
#define FILE_DIR "sound/violin"
#define FILE_DIR "sound/voice"
#define FILE_DIR "sound/weapons"
+#define FILE_DIR "tools"
+#define FILE_DIR "tools/Redirector"
// END_FILE_DIR
// BEGIN_PREFERENCES
diff --git a/code/modules/DetectiveWork/detective_work.dm b/code/modules/DetectiveWork/detective_work.dm
new file mode 100644
index 00000000000..2dc409beb6f
--- /dev/null
+++ b/code/modules/DetectiveWork/detective_work.dm
@@ -0,0 +1,626 @@
+//CONTAINS: Suit fibers and Detective's Scanning Computer
+
+atom/var/list/suit_fibers
+
+atom/proc/add_fibers(mob/living/carbon/human/M)
+ if(M.gloves && istype(M.gloves,/obj/item/clothing/))
+ var/obj/item/clothing/gloves/G = M.gloves
+ if(G.transfer_blood) //bloodied gloves transfer blood to touched objects
+ if(add_blood(G.bloody_hands_mob)) //only reduces the bloodiness of our gloves if the item wasn't already bloody
+ G.transfer_blood--
+ else if(M.bloody_hands)
+ if(add_blood(M.bloody_hands_mob))
+ M.bloody_hands--
+ if(!suit_fibers) suit_fibers = list()
+ var/fibertext
+ var/item_multiplier = istype(src,/obj/item)?1.2:1
+ if(M.wear_suit)
+ fibertext = "Material from \a [M.wear_suit]."
+ if(prob(10*item_multiplier) && !(fibertext in suit_fibers))
+ //world.log << "Added fibertext: [fibertext]"
+ suit_fibers += fibertext
+ if(!(M.wear_suit.body_parts_covered & 32))
+ if(M.w_uniform)
+ fibertext = "Fibers from \a [M.w_uniform]."
+ if(prob(12*item_multiplier) && !(fibertext in suit_fibers)) //Wearing a suit means less of the uniform exposed.
+ //world.log << "Added fibertext: [fibertext]"
+ suit_fibers += fibertext
+ if(!(M.wear_suit.body_parts_covered & 64))
+ if(M.gloves)
+ fibertext = "Material from a pair of [M.gloves.name]."
+ if(prob(20*item_multiplier) && !(fibertext in suit_fibers))
+ //world.log << "Added fibertext: [fibertext]"
+ suit_fibers += fibertext
+ else if(M.w_uniform)
+ fibertext = "Fibers from \a [M.w_uniform]."
+ if(prob(15*item_multiplier) && !(fibertext in suit_fibers))
+ // "Added fibertext: [fibertext]"
+ suit_fibers += fibertext
+ if(M.gloves)
+ fibertext = "Material from a pair of [M.gloves.name]."
+ if(prob(20*item_multiplier) && !(fibertext in suit_fibers))
+ //world.log << "Added fibertext: [fibertext]"
+ suit_fibers += "Material from a pair of [M.gloves.name]."
+ else if(M.gloves)
+ fibertext = "Material from a pair of [M.gloves.name]."
+ if(prob(20*item_multiplier) && !(fibertext in suit_fibers))
+ //world.log << "Added fibertext: [fibertext]"
+ suit_fibers += "Material from a pair of [M.gloves.name]."
+ if(!suit_fibers.len) del suit_fibers
+
+var/const/FINGERPRINT_COMPLETE = 6 //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)
+
+obj/machinery/computer/forensic_scanning
+ name = "\improper High-Res Forensic Scanning Computer"
+ icon_state = "forensic"
+ var/obj/item/scanning
+ var/temp = ""
+ var/canclear = 1
+ var/authenticated = 0
+
+//Here's the structure for files: each entry is a list, and entry one in that list is the string of their
+//full and scrambled fingerprint. This acts as the method to arrange evidence. Each subsequent entry is list
+//in the form (from entries):
+// 1: Object
+// 2: All prints on the object
+// 3: All fibers on the object
+// 4: All blood on the object
+//This is then used to show what objects were used to "find" the full print, as well as the fibers on it.
+ var/list/files
+//This holds objects (1) without prints, and their fibers(2) and blood(3).
+ var/list/misc
+ var/obj/item/weapon/f_card/card
+
+ var/scan_data = ""
+ var/scan_name = ""
+ var/scan_process = 0
+
+ req_access = list(access_forensics_lockers)
+
+
+ New()
+ ..()
+ new /obj/item/weapon/book/manual/detective(get_turf(src))
+ return
+
+
+ attack_ai(mob/user)
+ return attack_hand(user)
+
+
+ attack_hand(mob/user)
+ if(..())
+ return
+ user.machine = src
+ var/dat = ""
+ var/isai = 0
+ if(istype(usr,/mob/living/silicon))
+ isai = 1
+ if(temp)
+ dat += "[temp]
"
+ if(canclear) dat += "{Clear Screen}"
+ else
+ if(!authenticated)
+ dat += "{Log In}"
+ else
+ dat += "{Log Out}
"
+ if(scanning)
+ if(scan_process)
+ dat += "Scan Object: {[scanning.name]}
"
+ dat += "{Cancel Scan} {Print}
"
+ else
+ if(isai) dat += "Scan Object: {[scanning.name]}
"
+ else dat += "Scan Object: {[scanning.name]}
"
+ dat += "{Scan} {Print}
"
+ else
+ if(isai) dat += "{No Object Inserted}
"
+ else dat += "{No Object Inserted}
"
+ dat += "{Scan} {Print}
"
+ dat += "{Access Database}
"
+ dat += "[scan_data]"
+ if(scan_data && !scan_process)
+ dat += "
{Erase Data}"
+ user << browse(dat,"window=scanner")
+ onclose(user,"scanner")
+
+
+ Topic(href,href_list)
+ switch(href_list["operation"])
+ if("login")
+ var/mob/M = usr
+ if(istype(M,/mob/living/silicon))
+ authenticated = 1
+ updateDialog()
+ return
+ if (allowed(M))
+ authenticated = 1
+ if("logout")
+ authenticated = 0
+ if("clear")
+ if(canclear)
+ temp = null
+ if("eject")
+ if(scanning)
+ scanning.loc = loc
+ scanning = null
+ else
+ temp = "Eject Failed: No Object"
+ if("insert")
+ 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 -= scanning
+ I.icon_state = "evidenceobj"
+ else
+ scanning = I
+ M.drop_item()
+ I.loc = src
+ else
+ usr << "Invalid Object Rejected."
+ if("card") //Processing a fingerprint card.
+ var/mob/M = usr
+ var/obj/item/I = M.get_active_hand()
+ if(!(I && istype(I,/obj/item/weapon/f_card)))
+ I = card
+ if(I && istype(I,/obj/item/weapon/f_card))
+ card = I
+ if(!card.fingerprints)
+ card.fingerprints = list()
+ if(card.amount > 1 || !card.fingerprints.len)
+ usr << "\red ERROR: No prints/too many cards."
+ if(card.loc == src)
+ card.loc = src.loc
+ card = null
+ return
+ M.drop_item()
+ I.loc = src
+ process_card()
+ else
+ usr << "\red Invalid Object Rejected."
+ if("database") //Viewing all records in each database
+ canclear = 1
+ if(href_list["delete_record"])
+ delete_dossier(href_list["delete_record"])
+ if(href_list["delete_aux"])
+ delete_record(href_list["delete_aux"])
+ if((!misc || !misc.len) && (!files || !files.len))
+ temp = "Database is empty."
+ else
+ if(files && files.len)
+ temp = "Criminal Evidence Database
"
+ temp += "Consolidated data points:
"
+ for(var/print in files)
+ var/list/file = files[print]
+ temp += "{[file[2]]}
"
+ temp += "
{Insert Finger Print Card (To complete a Dossier)}
"
+ else
+ temp = ""
+ if(misc && misc.len)
+ temp += "Auxiliary Evidence Database
"
+ temp += "This is where anything without fingerprints goes.
"
+ for(var/atom in misc)
+ var/list/data_entry = misc[atom]
+ temp += "{[data_entry[3]]}
"
+ if("record") //Viewing a record from the "files" database.
+ canclear = 0
+ if(files)
+ var/list/dossier = files[href_list["identifier"]]
+ if(href_list["ren"])
+ var/new_title = copytext(sanitize(input("Rename to what?", "Dossier Editing", "Dossier [files.Find(href_list["identifier"])]") as null|text),1,MAX_MESSAGE_LEN)
+ if(new_title)
+ dossier[2] = new_title
+ else
+ usr << "Illegal or blank name."
+ temp = "Criminal Evidence Database
"
+ temp += "Consolidated data points: [dossier[2]]
"
+ var/print_string = "Fingerprints: Print not complete!
"
+ if(stringpercent(dossier[1]) <= FINGERPRINT_COMPLETE)
+ print_string = "Fingerprints: (80% or higher completion reached)
[dossier[1]]
"
+ temp += print_string
+ for(var/object in dossier)
+ if(object == dossier[1] || object == dossier[2])
+ continue
+ temp += "
"
+ var/list/outputs = dossier[object]
+ var/list/prints = outputs[1]
+ temp += "Object: [outputs[4]]
"
+ temp += " Fingerprints:
"
+ temp += " [prints.len] Unique fingerprints found.
"
+ var/complete_prints = 0
+ for(var/print in prints)
+ if(stringpercent(prints[print]) <= FINGERPRINT_COMPLETE)
+ complete_prints++
+ temp += " [prints[print]]
"
+ if(complete_prints)
+ temp += " And [prints.len - complete_prints] unknown unique prints.
"
+ else
+ temp += " No prints of sufficient completeness.
"
+ var/list/fibers = outputs[2]
+ if(fibers && fibers.len)
+ temp += " Fibers:
"
+ for(var/j = 1, j <= fibers.len, j++)
+ temp += " [fibers[j]]
"
+ var/list/blood = outputs[3]
+ if(blood && blood.len)
+ temp += " Blood:
"
+ for(var/named in blood)
+ temp += " Type: [blood[named]], DNA: [named]
"
+ temp += "
{Rename this Dossier}"
+ temp += "
{Delete this Dossier}"
+ temp += "
{Print}"
+ else
+ temp = "ERROR. Database not found!
"
+ temp += "
{Return}"
+ if("databaseprint") //Printing from the "files" database.
+ if(files)
+ var/obj/item/weapon/paper/P = new(loc)
+ P.name = "\improper Database File (Dossier [files.Find(href_list["identifier"])])"
+ P.overlays += "paper_words"
+ P.info = "Criminal Evidence Database
"
+ var/list/dossier = files[href_list["identifier"]]
+ P.info += "Consolidated data points: [dossier[2]]
"
+ var/print_string = "Fingerprints: Print not complete!
"
+ if(stringpercent(dossier[1]) <= FINGERPRINT_COMPLETE)
+ print_string = "Fingerprints: (80% or higher completion reached)
[dossier[1]]
"
+ P.info += print_string
+ for(var/object in dossier)
+ if(object == dossier[1] || object == dossier[2])
+ continue
+ P.info += "
"
+ var/list/outputs = dossier[object]
+ var/list/prints = outputs[1]
+ P.info += "Object: [outputs[4]]
"
+ P.info += " Fingerprints:
"
+ P.info += " [prints.len] Unique fingerprints found.
"
+ var/complete_prints = 0
+ for(var/print in prints)
+ if(stringpercent(prints[print]) <= FINGERPRINT_COMPLETE)
+ complete_prints++
+ P.info += " [prints[print]]
"
+ if(complete_prints)
+ P.info += " And [prints.len - complete_prints] unknown unique prints.
"
+ else
+ P.info += " No prints of sufficient completeness.
"
+ var/list/fibers = outputs[2]
+ if(fibers && fibers.len)
+ P.info += " Fibers:
"
+ for(var/j = 1, j <= fibers.len, j++)
+ P.info += " [fibers[j]]
"
+ var/list/blood = outputs[3]
+ if(blood && blood.len)
+ P.info += " Blood:
"
+ for(var/named in blood)
+ P.info += " Type: [blood[named]], DNA: [named]
"
+ else
+ usr << "ERROR. Database not found!
"
+ if("auxiliary") //Viewing a record from the "misc" database.
+ canclear = 0
+ if(misc)
+ temp = "Auxiliary Evidence Database
"
+ var/list/outputs = misc[href_list["identifier"]]
+ temp += "Consolidated data points: [outputs[3]]
"
+ var/list/prints = outputs[4]
+ if(prints)
+ temp += " Fingerprints:
"
+ temp += " [prints.len] Unique fingerprints found.
"
+ var/complete_prints = 0
+ for(var/print in prints)
+ if(stringpercent(prints[print]) <= FINGERPRINT_COMPLETE)
+ complete_prints++
+ temp += " [prints[print]]
"
+ if(complete_prints)
+ temp += " And [prints.len - complete_prints] unknown unique prints.
"
+ else
+ temp += " No prints of sufficient completeness.
"
+ var/list/fibers = outputs[1]
+ if(fibers && fibers.len)
+ temp += " Fibers:
"
+ for(var/fiber in fibers)
+ temp += " [fiber]
"
+ var/list/blood = outputs[2]
+ if(blood && blood.len)
+ temp += " Blood:
"
+ for(var/named in blood)
+ temp += " Type: [blood[named]], DNA: [named]
"
+ temp += "
{Delete This Record}"
+ temp += "
{Print}"
+ else
+ temp = "ERROR. Database not found!
"
+ temp += "
{Return}"
+ if("auxiliaryprint") //Printing from the "misc" database.
+ if(misc)
+ var/obj/item/weapon/paper/P = new(loc)
+ var/list/outputs = misc[href_list["identifier"]]
+ P.name = "\improper Auxiliary Database File ([outputs[3]])"
+ P.overlays += "paper_words"
+ P.info = "Auxiliary Evidence Database
"
+ P.info += "Consolidated data points: [outputs[3]]
"
+ var/list/prints = outputs[4]
+ if(prints)
+ P.info += " Fingerprints:
"
+ P.info += " [prints.len] Unique fingerprints found.
"
+ var/complete_prints = 0
+ for(var/print in prints)
+ if(stringpercent(prints[print]) <= FINGERPRINT_COMPLETE)
+ complete_prints++
+ P.info += " [prints[print]]
"
+ if(complete_prints)
+ P.info += " And [prints.len - complete_prints] unknown unique prints.
"
+ else
+ P.info += " No prints of sufficient completeness.
"
+ var/list/fibers = outputs[1]
+ if(fibers && fibers.len)
+ P.info += " Fibers:
"
+ for(var/fiber in fibers)
+ P.info += " [fiber]
"
+ var/list/blood = outputs[2]
+ if(blood && blood.len)
+ P.info += " Blood:
"
+ for(var/named in blood)
+ P.info += " Type: [blood[named]], DNA: [named]
"
+ else
+ usr << "ERROR. Database not found!
"
+ if("scan")
+ if(istype(scanning,/obj/item/weapon/f_card))
+ card = scanning
+ scanning = initial(scanning)
+ process_card()
+ else if(scanning)
+ scan_process = 3
+ scan_data = "Scanning [scanning]: 25% complete"
+ updateDialog()
+ sleep(50)
+ if(!scan_process)
+ scan_data = null
+ updateDialog()
+ return
+ scan_data = "Scanning [scanning]: 50% complete"
+ updateDialog()
+ scan_process = 2
+ sleep(50)
+ if(!scan_process)
+ scan_data = null
+ updateDialog()
+ return
+ scan_data = "Scanning [scanning]: 75% complete"
+ updateDialog()
+ scan_process = 1
+ sleep(50)
+ if(!scan_process)
+ scan_data = null
+ updateDialog()
+ return
+ if(scanning)
+ scan_process = 0
+ scan_name = scanning.name
+ scan_data = "[scanning]
"
+ if (scanning.blood_DNA)
+ scan_data += "Blood Found:
"
+ for(var/blood in scanning.blood_DNA)
+ scan_data += "Blood type: [scanning.blood_DNA[blood]]\nDNA: [blood]
"
+ else
+ scan_data += "No Blood Found
"
+ if(!scanning.fingerprints)
+ scan_data += "No Fingerprints Found
"
+ else
+ scan_data += "Isolated [scanning.fingerprints.len] Fingerprints. Loaded into database.
"
+ add_data(scanning)
+
+ if(!scanning.suit_fibers)
+ scan_data += "No Fibers/Materials Located
"
+ else
+ scan_data += "Fibers/Materials Found:
"
+ for(var/data in scanning.suit_fibers)
+ scan_data += "- [data]
"
+ if(istype(scanning,/obj/item/device/detective_scanner) || (istype(scanning, /obj/item/device/pda) && scanning:cartridge && scanning:cartridge.access_security))
+ scan_data += "
Data transfered from \the [scanning] to Database.
"
+ add_data_scanner(scanning)
+ else if(!scanning.fingerprints)
+ scan_data += "
Add to Database?
"
+ else
+ temp = "Scan Failed: No Object"
+
+
+ if("print") //Printing scan data
+ if(scan_data)
+ temp = "Scan Data Printed."
+ var/obj/item/weapon/paper/P = new(loc)
+ P.name = "\improper Scan Data ([scan_name])"
+ P.info = "[scan_data]"
+ P.overlays += "paper_words"
+ else
+ temp = "Print Failed: No Data"
+ if("erase")
+ scan_data = ""
+ if("cancel")
+ scan_process = 0
+ if("add") //Adding an object (Manually) to the database.
+ if(scanning)
+ add_data(scanning)
+ else
+ temp = "Data Transfer Failed: No Object."
+ if("rename")
+ if(!files || !files[href_list["identifier"]])
+ temp = "ERROR: Record/Database not found!"
+ else
+ var/new_title = copytext(sanitize(input("Rename to what?", "Dossier Editing", "Dossier [files.Find(href_list["identifier"])]") as null|text),1,MAX_MESSAGE_LEN)
+ if(new_title)
+ var/list/file = files[href_list["identifier"]]
+ file[2] = new_title
+ updateUsrDialog()
+
+ ex_act()
+ return
+
+
+ proc/add_data_scanner(var/obj/item/device/W)
+ if(istype(W, /obj/item/device/detective_scanner))
+ if(W:stored)
+ for(var/atom in W:stored)
+ var/list/data = W:stored[atom]
+ add_data_master(atom,data[1],data[2],data[3],data[4])
+ W:stored = list()
+ else if(istype(W, /obj/item/device/pda) && W:cartridge && W:cartridge.access_security)
+ if(W:cartridge.stored_data)
+ for(var/atom in W:cartridge.stored_data)
+ var/list/data = W:cartridge.stored_data[atom]
+ add_data_master(atom,data[1],data[2],data[3],data[4])
+ W:cartridge.stored_data = list()
+ return
+
+ proc/add_data(var/atom/scanned_atom)
+ return add_data_master("\ref [scanned_atom]", scanned_atom.fingerprints,\
+ scanned_atom.suit_fibers, scanned_atom.blood_DNA, "[scanned_atom.name] (Direct Scan)")
+
+
+
+/********************************
+*****DO NOT DIRECTLY CALL ME*****
+********************************/
+ proc/add_data_master(var/atom_reference, var/list/atom_fingerprints, var/list/atom_suit_fibers, var/list/atom_blood_DNA, var/atom_name)
+//What follows is massive. It cross references all stored data in the scanner with the other stored data,
+//and what is already in the computer. Not sure how bad the lag may/may not be.
+
+ if(!misc)
+ misc = list()
+ var/list/data_entry = misc[atom_reference]
+ if(data_entry)
+ var/list/fibers = data_entry[1]
+ if(!fibers)
+ fibers = list()
+ if(atom_suit_fibers)
+ for(var/fiber in atom_suit_fibers) //Fibers~~~
+ if(!fibers.Find(fiber)) //It isn't! Add!
+ fibers += fiber
+ var/list/blood = data_entry[2]
+ if(!blood)
+ blood = list()
+ if(atom_blood_DNA)
+ for(var/main_blood in atom_blood_DNA)
+ if(!blood[main_blood])
+ blood[main_blood] = atom_blood_DNA[blood]
+ var/list/prints = data_entry[4]
+ if(!prints && atom_fingerprints)
+ prints = list()
+ if(atom_fingerprints)
+ for(var/print in atom_fingerprints)
+ if(!prints[print])
+ prints[print] = atom_fingerprints[print]
+ else
+ var/list/templist[4]
+ templist[1] = atom_suit_fibers
+ templist[2] = atom_blood_DNA
+ templist[3] = atom_name
+ templist[4] = atom_fingerprints
+ misc[atom_reference] = templist //Store it!
+ //Has prints.
+ if(atom_fingerprints)
+ if(!files)
+ files = list()
+ for(var/main_print in atom_fingerprints)
+ data_entry = files[main_print]
+ if(data_entry)//The print is already in here!
+ var/list/internal_atom = data_entry[atom_reference] //Lets see if we can find the current object
+ if(internal_atom)
+ //We must be on a roll! Just update what needs to be updated.
+ var/list/internal_prints = internal_atom[1]
+ for(var/print in atom_fingerprints) //Sorry for the double loop! D:
+ var/associated_print = internal_prints[print]
+ var/reference_print = atom_fingerprints[print]
+ if(associated_print && associated_print != reference_print) //It does not match
+ internal_prints[print] = stringmerge(associated_print, reference_print)
+ else if(!associated_print)
+ internal_prints[print] = reference_print
+ //If the main print was updated, lets update the master as well.
+ if(print == main_print && (!associated_print || (associated_print && associated_print != reference_print)))
+ update_fingerprints(main_print, internal_prints[print])
+ //Fibers.
+ var/list/fibers = internal_atom[2]
+ if(!fibers)
+ fibers = list()
+ if(atom_suit_fibers)
+ for(var/fiber in atom_suit_fibers) //Fibers~~~
+ if(!fibers.Find(fiber)) //It isn't! Add!
+ fibers += fiber
+ //Blood.
+ var/list/blood = internal_atom[3]
+ if(!blood)
+ blood = list()
+ if(atom_blood_DNA)
+ for(var/main_blood in atom_blood_DNA)
+ if(!blood[main_blood])
+ blood[main_blood] = atom_blood_DNA[blood]
+
+ continue
+ //It's not in there! We gotta add it.
+ update_fingerprints(main_print, atom_fingerprints[main_print])
+ var/list/data_point[4]
+ data_point[1] = atom_fingerprints
+ data_point[2] = atom_suit_fibers
+ data_point[3] = atom_blood_DNA
+ data_point[4] = atom_name
+ data_entry[atom_reference] = data_point
+ continue
+ //No print at all! New data entry, go!
+ var/list/data_point[4]
+ data_point[1] = atom_fingerprints
+ data_point[2] = atom_suit_fibers
+ data_point[3] = atom_blood_DNA
+ data_point[4] = atom_name
+ var/list/new_file[2]
+ new_file[1] = atom_fingerprints[main_print]
+ new_file[2] = "Dossier [files.len + 1]"
+ new_file[atom_reference] = data_point
+ files[main_print] = new_file
+ return 1
+/********************************
+***END DO NOT DIRECTLY CALL ME***
+********************************/
+
+ proc/update_fingerprints(var/ref_print, var/new_print)
+ var/list/master = files[ref_print]
+ if(master)
+ master[1] = stringmerge(master[1],new_print)
+ else
+ CRASH("Fucking hell. Something went wrong, and it tried to update a null print or something. Tell SkyMarshal (and give him this call stack)")
+ return
+
+ proc/process_card() //Same as above, but for fingerprint cards
+ if(card.fingerprints && !(card.amount > 1) && islist(card.fingerprints) && files && files.len)
+ usr << "You insert the card, and it is destroyed by the machinery in the process of comparing prints."
+ var/found = 0
+ for(var/master_print in card.fingerprints)
+ var/list/data_entry = files[master_print]
+ if(data_entry)
+ found = 1
+ data_entry[1] = master_print
+ if(found)
+ usr << "The machinery finds it can complete a match."
+ else
+ usr << "No match found."
+ del(card)
+ else
+ usr << "\red ERROR: No prints/too many cards."
+ if(card.loc == src)
+ card.loc = src.loc
+ card = null
+ return
+ return
+
+ proc/delete_record(var/atom_ref) //Deletes an entry in the misc database at the given location
+ if(misc && misc.len)
+ misc.Remove(atom_ref)
+ return
+
+ proc/delete_dossier(var/print) //Deletes a Dossier at a given location.
+ if(files && files.len)
+ files.Remove(print)
+ return
+
+ detective
+ icon_state = "old"
+ name = "PowerScan Mk.I"
\ No newline at end of file
diff --git a/code/modules/DetectiveWork/evidence.dm b/code/modules/DetectiveWork/evidence.dm
new file mode 100644
index 00000000000..a5aa48dd5c2
--- /dev/null
+++ b/code/modules/DetectiveWork/evidence.dm
@@ -0,0 +1,80 @@
+//CONTAINS: Evidence bags
+
+/obj/item/weapon/evidencebag
+ name = "evidence bag"
+ desc = "An empty evidence bag."
+ icon = 'icons/obj/storage.dmi'
+ icon_state = "evidenceobj"
+ w_class = 1
+
+/obj/item/weapon/evidencebag/afterattack(obj/item/O, mob/user as mob)
+ if(!in_range(O,user))
+ return
+
+ if(istype(O, /obj/item/weapon/storage))
+ return ..()
+
+ if(!istype(O) || O.anchored == 1)
+ user << "You can't put that inside \the [src]!"
+ return ..()
+
+ if(istype(O, /obj/item/weapon/evidencebag))
+ user << "You find putting an evidence bag in another evidence bag to be slightly absurd."
+ return
+
+ if(contents.len)
+ user << "\The [src] already has something inside it."
+ return ..()
+
+ if(!isturf(O.loc)) //If it isn't on the floor. Do some checks to see if it's in our hands or a box. Otherwise give up.
+ if(istype(O.loc,/obj/item/weapon/storage)) //in a container.
+ var/obj/item/weapon/storage/U = O.loc
+ user.client.screen -= O
+ U.contents.Remove(O)
+ else if(user.l_hand == O) //in a hand
+ user.drop_l_hand()
+ else if(user.r_hand == O) //in a hand
+ user.drop_r_hand()
+ else
+ return
+
+ user.visible_message("\The [user] puts \a [O] into \a [src]", "You put \the [O] inside \the [src].",\
+ "You hear a rustle as someone puts something into a plastic bag.")
+ icon_state = "evidence"
+ var/image/I = image("icon"=O, "layer"=FLOAT_LAYER) //take a snapshot. (necessary to stop the underlays appearing under our inventory-HUD slots ~Carn
+ underlays += I
+ desc = "An evidence bag containing \a [O]. [O.desc]"
+ O.loc = src
+ w_class = O.w_class
+ return
+
+
+/obj/item/weapon/evidencebag/attack_self(mob/user as mob)
+ if (contents.len)
+ var/obj/item/I = contents[1]
+ user.visible_message("\The [user] takes \a [I] out of \a [src]", "You take \the [I] out of \the [src].",\
+ "You hear someone rustle around in a plastic bag, and remove something.")
+ underlays = null //remove the underlays
+ user.put_in_hands(I)
+ w_class = 1
+ icon_state = "evidenceobj"
+ desc = "An empty evidence bag."
+
+ else
+ user << "\The [src] is empty."
+ icon_state = "evidenceobj"
+ return
+
+/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)
+ new /obj/item/weapon/f_card(src)
+ ..()
+ return
\ No newline at end of file
diff --git a/code/modules/DetectiveWork/footprints_and_rag.dm b/code/modules/DetectiveWork/footprints_and_rag.dm
new file mode 100644
index 00000000000..4aceed22ec0
--- /dev/null
+++ b/code/modules/DetectiveWork/footprints_and_rag.dm
@@ -0,0 +1,200 @@
+/*
+obj/item/clothing/shoes/var
+ track_blood = 0
+ mob/living/carbon/human/track_blood_mob
+ track_blood_type*/
+mob/var
+ bloody_hands = 0
+ mob/living/carbon/human/bloody_hands_mob
+ track_blood
+ mob/living/carbon/human/track_blood_mob
+ track_blood_type
+obj/item/clothing/gloves/var
+ transfer_blood = 0
+ mob/living/carbon/human/bloody_hands_mob
+
+/*
+obj/effect/decal/cleanable/var
+ track_amt = 3
+ mob/blood_owner
+
+turf/Exited(mob/living/carbon/human/M)
+ if(istype(M,/mob/living) && !istype(M,/mob/living/carbon/metroid))
+ if(!istype(src, /turf/space)) // Bloody tracks code starts here
+ var/dofoot = 1
+ if(istype(M,/mob/living/simple_animal))
+ if(!(istype(M,/mob/living/simple_animal/cat) || istype(M,/mob/living/simple_animal/corgi) || istype(M,/mob/living/simple_animal/constructwraith)))
+ dofoot = 0
+
+ if(dofoot)
+
+ if(!istype(src, /turf/space)) // Bloody tracks code starts here
+ if(M.track_blood > 0)
+ M.track_blood--
+ src.add_bloody_footprints(M.track_blood_mob,1,M.dir,get_tracks(M),M.track_blood_type)
+ else if(istype(M,/mob/living/carbon/human))
+ if(M.shoes && istype(M.shoes,/obj/item/clothing/shoes))
+ var/obj/item/clothing/shoes/S = M.shoes
+ if(S.track_blood > 0)
+ S.track_blood--
+ src.add_bloody_footprints(S.track_blood_mob,1,M.dir,S.name,S.track_blood_type) // And bloody tracks end here
+ . = ..()
+turf/Entered(mob/living/carbon/human/M)
+ if(istype(M,/mob/living) && !istype(M,/mob/living/carbon/metroid))
+ var/dofoot = 1
+ if(istype(M,/mob/living/simple_animal))
+ if(!(istype(M,/mob/living/simple_animal/cat) || istype(M,/mob/living/simple_animal/corgi) || istype(M,/mob/living/simple_animal/constructwraith)))
+ dofoot = 0
+
+ if(dofoot)
+
+ if(M.track_blood > 0)
+ M.track_blood--
+ src.add_bloody_footprints(M.track_blood_mob,0,M.dir,get_tracks(M),M.track_blood_type)
+ else if(istype(M,/mob/living/carbon/human))
+ if(M.shoes && istype(M.shoes,/obj/item/clothing/shoes) && !istype(src,/turf/space))
+ var/obj/item/clothing/shoes/S = M.shoes
+ if(S.track_blood > 0)
+ S.track_blood--
+ src.add_bloody_footprints(S.track_blood_mob,0,M.dir,S.name,S.track_blood_type)
+
+
+ for(var/obj/effect/decal/cleanable/B in src)
+ if(B:track_amt <= 0) continue
+ if(B.type != /obj/effect/decal/cleanable/blood/tracks)
+ if(istype(B, /obj/effect/decal/cleanable/xenoblood) || istype(B, /obj/effect/decal/cleanable/blood) || istype(B, /obj/effect/decal/cleanable/oil) || istype(B, /obj/effect/decal/cleanable/robot_debris))
+
+ var/track_type = "blood"
+ if(istype(B, /obj/effect/decal/cleanable/xenoblood))
+ track_type = "xeno"
+ else if(istype(B, /obj/effect/decal/cleanable/oil) || istype(B, /obj/effect/decal/cleanable/robot_debris))
+ track_type = "oil"
+
+ if(istype(M,/mob/living/carbon/human))
+ if(M.shoes && istype(M.shoes,/obj/item/clothing/shoes))
+ var/obj/item/clothing/shoes/S = M.shoes
+ S.add_blood(B.blood_owner)
+ S.track_blood_mob = B.blood_owner
+ S.track_blood = max(S.track_blood,8)
+ S.track_blood_type = track_type
+ else
+ M.add_blood(B.blood_owner)
+ M.track_blood_mob = B.blood_owner
+ M.track_blood = max(M.track_blood,rand(4,8))
+ M.track_blood_type = track_type
+ B.track_amt--
+ break
+ . = ..()
+
+turf/proc/add_bloody_footprints(mob/living/carbon/human/M,leaving,d,info,bloodcolor)
+ for(var/obj/effect/decal/cleanable/blood/tracks/T in src)
+ if(T.dir == d && findtext(T.icon_state, bloodcolor))
+ if((leaving && T.icon_state == "steps2") || (!leaving && T.icon_state == "steps1"))
+ T.desc = "These bloody footprints appear to have been made by [info]."
+ if(!T.blood_DNA)
+ T.blood_DNA = list()
+ if(istype(M,/mob/living/carbon/human))
+ T.blood_DNA[M.dna.unique_enzymes] = M.dna.b_type
+ else if(istype(M,/mob/living/carbon/alien))
+ T.blood_DNA["UNKNOWN DNA"] = "X*"
+ else if(istype(M,/mob/living/carbon/monkey))
+ T.blood_DNA["Non-human DNA"] = "A+"
+ return
+ var/obj/effect/decal/cleanable/blood/tracks/this = new(src)
+ this.icon = 'icons/effects/footprints.dmi'
+
+ var/preiconstate = ""
+
+ if(info == "animal paws")
+ preiconstate = "paw"
+ else if(info == "alien claws")
+ preiconstate = "claw"
+ else if(info == "small alien feet")
+ preiconstate = "paw"
+
+ if(leaving)
+ this.icon_state = "[bloodcolor][preiconstate]2"
+ else
+ this.icon_state = "[bloodcolor][preiconstate]1"
+ this.dir = d
+
+ if(bloodcolor == "blood")
+ this.desc = "These bloody footprints appear to have been made by [info]."
+ else if(bloodcolor == "xeno")
+ this.desc = "These acidic bloody footprints appear to have been made by [info]."
+ else if(bloodcolor == "oil")
+ this.name = "oil"
+ this.desc = "These oil footprints appear to have been made by [info]."
+
+ if(istype(M,/mob/living/carbon/human))
+ if(!this.blood_DNA)
+ this.blood_DNA = list()
+ this.blood_DNA[M.dna.unique_enzymes] = M.dna.b_type
+
+proc/get_tracks(mob/M)
+ if(istype(M,/mob/living))
+ if(istype(M,/mob/living/carbon/human))
+ . = "human feet"
+ else if(istype(M,/mob/living/carbon/monkey) || istype(M,/mob/living/simple_animal/cat) || istype(M,/mob/living/simple_animal/corgi) || istype(M,/mob/living/simple_animal/crab))
+ . = "animal paws"
+ else if(istype(M,/mob/living/silicon/robot))
+ . = "robot feet"
+ else if(istype(M,/mob/living/carbon/alien/humanoid))
+ . = "alien claws"
+ else if(istype(M,/mob/living/carbon/alien/larva))
+ . = "small alien feet"
+ else
+ . = "an unknown creature"*/
+
+
+proc/blood_incompatible(donor,receiver)
+ var
+ donor_antigen = copytext(donor,1,lentext(donor))
+ receiver_antigen = copytext(receiver,1,lentext(receiver))
+ donor_rh = findtext("+",donor)
+ receiver_rh = findtext("+",receiver)
+ if(donor_rh && !receiver_rh) return 1
+ switch(receiver_antigen)
+ if("A")
+ if(donor_antigen != "A" && donor_antigen != "O") return 1
+ if("B")
+ if(donor_antigen != "B" && donor_antigen != "O") return 1
+ if("O")
+ if(donor_antigen != "O") return 1
+ //AB is a universal receiver.
+ return 0
+
+/obj/item/weapon/reagent_containers/glass/rag
+ name = "damp rag"
+ desc = "For cleaning up messes, you suppose."
+ w_class = 1
+ icon = 'icons/obj/toy.dmi'
+ icon_state = "rag"
+ amount_per_transfer_from_this = 5
+ possible_transfer_amounts = list(5)
+ volume = 5
+ can_be_placed_into = null
+
+ attack(atom/target as obj|turf|area, mob/user as mob , flag)
+ if(ismob(target) && target.reagents && reagents.total_volume)
+ user.visible_message("\red \The [target] has been smothered with \the [src] by \the [user]!", "\red You smother \the [target] with \the [src]!", "You hear some struggling and muffled cries of surprise")
+ src.reagents.reaction(target, INGEST)
+ spawn(5) src.reagents.clear_reagents()
+ return
+ else
+ ..()
+
+ afterattack(atom/A as obj|turf|area, mob/user as mob)
+ if(istype(A) && src in user)
+ user.visible_message("[user] starts to wipe down [A] with [src]!")
+ if(do_after(user,30))
+ user.visible_message("[user] finishes wiping off the [A]!")
+ A.clean_blood()
+ return
+
+ examine()
+ if (!usr)
+ return
+ usr << "That's \a [src]."
+ usr << desc
+ return
\ No newline at end of file
diff --git a/code/modules/DetectiveWork/scanner.dm b/code/modules/DetectiveWork/scanner.dm
new file mode 100644
index 00000000000..09eb646a448
--- /dev/null
+++ b/code/modules/DetectiveWork/scanner.dm
@@ -0,0 +1,172 @@
+//CONTAINS: Detective's Scanner
+
+/obj/item/device/detective_scanner
+ name = "Scanner"
+ desc = "Used to scan objects for DNA and fingerprints."
+ icon_state = "forensic1"
+ var/amount = 20.0
+ var/list/stored = list()
+ w_class = 3.0
+ item_state = "electronic"
+ flags = FPRINT | TABLEPASS | CONDUCT | USEDELAY
+ slot_flags = SLOT_BELT
+
+ attackby(obj/item/weapon/f_card/W as obj, mob/user as mob)
+ ..()
+ if (istype(W, /obj/item/weapon/f_card))
+ if (W.fingerprints)
+ return
+ if (src.amount == 20)
+ return
+ if (W.amount + src.amount > 20)
+ src.amount = 20
+ W.amount = W.amount + src.amount - 20
+ else
+ src.amount += W.amount
+ //W = null
+ del(W)
+ add_fingerprint(user)
+ if (W)
+ W.add_fingerprint(user)
+ return
+
+ attack(mob/living/carbon/human/M as mob, mob/user as mob)
+ if (!ishuman(M))
+ user << "\red [M] is not human and cannot have the fingerprints."
+ return 0
+ if (( !( istype(M.dna, /datum/dna) ) || M.gloves) )
+ user << "\blue No fingerprints found on [M]"
+ return 0
+ else
+ if (src.amount < 1)
+ user << text("\blue Fingerprints scanned on [M]. Need more cards to print.")
+ else
+ src.amount--
+ 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 << "\blue Done printing."
+ user << "\blue [M]'s Fingerprints: [md5(M.dna.uni_identity)]"
+ if ( !M.blood_DNA || !M.blood_DNA.len )
+ user << "\blue No blood found on [M]"
+ if(M.blood_DNA)
+ del(M.blood_DNA)
+ else
+ user << "\blue Blood found on [M]. Analysing..."
+ spawn(15)
+ for(var/blood in M.blood_DNA)
+ user << "\blue Blood type: [M.blood_DNA[blood]]\nDNA: [blood]"
+ return
+
+ afterattack(atom/A as obj|turf|area, mob/user as mob)
+ if(!in_range(A,user))
+ return
+ if(loc != user)
+ return
+ if(istype(A,/obj/machinery/computer/forensic_scanning)) //breaks shit.
+ return
+ if(istype(A,/obj/item/weapon/f_card))
+ user << "The scanner displays on the screen: \"ERROR 43: Object on Excluded Object List.\""
+ return
+
+ add_fingerprint(user)
+
+
+ //Special case for blood splaters.
+ if (istype(A, /obj/effect/decal/cleanable/blood) || istype(A, /obj/effect/rune))
+ if(!isnull(A.blood_DNA))
+ for(var/blood in A.blood_DNA)
+ user << "\blue Blood type: [A.blood_DNA[blood]]\nDNA: [blood]"
+ return
+
+ //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." : "."]" ,\
+ "\blue Unable to locate any fingerprints, materials, fibers, or blood on [A]!",\
+ "You hear a faint hum of electrical equipment.")
+ return 0
+
+ if(add_data(A))
+ user << "\blue Object already in internal memory. Consolidating data..."
+ return
+
+
+ //PRINTS
+ if(!A.fingerprints || !A.fingerprints.len)
+ if(A.fingerprints)
+ del(A.fingerprints)
+ else
+ user << "\blue Isolated [A.fingerprints.len] fingerprints: 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 << "\blue No intact prints found"
+ else
+ user << "\blue Found [complete_prints.len] intact prints"
+ for(var/i in complete_prints)
+ user << "\blue [i]"
+
+ //FIBERS
+ if(A.suit_fibers)
+ user << "\blue Fibers/Materials Data Stored: Scan with Hi-Res Forensic Scanner to retrieve."
+
+ //Blood
+ if (A.blood_DNA)
+ user << "\blue Blood found on [A]. Analysing..."
+ spawn(15)
+ for(var/blood in A.blood_DNA)
+ user << "Blood type: \red [A.blood_DNA[blood]] \t \black DNA: \red [blood]"
+ if(prob(80) || !A.fingerprints)
+ 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.")
+ return 0
+ else
+ user.visible_message("\The [user] scans \the [A] with \a [src], the air around [user.gender == MALE ? "him" : "her"] humming[prob(70) ? " gently." : "."]\n[user.gender == MALE ? "He" : "She"] seems to perk up slightly at the readout." ,\
+ "The results of the scan pique your interest.",\
+ "You hear a faint hum of electrical equipment, and someone making a thoughtful noise.")
+ return 0
+ return
+
+ proc/add_data(atom/A as mob|obj|turf|area)
+ //I love hashtables.
+ var/list/data_entry = stored["\ref [A]"]
+ if(islist(data_entry)) //Yay, it was already stored!
+ //Merge the fingerprints.
+ var/list/data_prints = data_entry[1]
+ for(var/print in A.fingerprints)
+ var/merged_print = data_prints[print]
+ if(!merged_print)
+ data_prints[print] = A.fingerprints[print]
+ else
+ data_prints[print] = stringmerge(data_prints[print],A.fingerprints[print])
+
+ //Now the fibers
+ var/list/fibers = data_entry[2]
+ if(!fibers)
+ fibers = list()
+ if(A.suit_fibers && A.suit_fibers.len)
+ for(var/j = 1, j <= A.suit_fibers.len, j++) //Fibers~~~
+ if(!fibers.Find(A.suit_fibers[j])) //It isn't! Add!
+ fibers += A.suit_fibers[j]
+ var/list/blood = data_entry[3]
+ if(!blood)
+ blood = list()
+ if(A.blood_DNA && A.blood_DNA.len)
+ for(var/main_blood in A.blood_DNA)
+ if(!blood[main_blood])
+ blood[main_blood] = A.blood_DNA[blood]
+ return 1
+ var/list/sum_list[4] //Pack it back up!
+ sum_list[1] = A.fingerprints
+ sum_list[2] = A.suit_fibers
+ sum_list[3] = A.blood_DNA
+ sum_list[4] = "\The [A] in \the [get_area(A)]"
+ stored["\ref [A]"] = sum_list
+ return 0
\ No newline at end of file