diff --git a/baystation12.dme b/baystation12.dme index 4851c962cb..cc513575bf 100644 --- a/baystation12.dme +++ b/baystation12.dme @@ -804,10 +804,11 @@ #include "code\modules\customitems\item_defines.dm" #include "code\modules\customitems\item_spawning.dm" #include "code\modules\destilery\main.dm" -#include "code\modules\detectivework\detective_work.dm" #include "code\modules\detectivework\evidence.dm" #include "code\modules\detectivework\footprints_and_rag.dm" +#include "code\modules\detectivework\forensics.dm" #include "code\modules\detectivework\scanner.dm" +#include "code\modules\detectivework\scanning_console.dm" #include "code\modules\economy\Accounts.dm" #include "code\modules\economy\Accounts_DB.dm" #include "code\modules\economy\ATM.dm" diff --git a/code/modules/detectivework/detective_work.dm b/code/modules/detectivework/detective_work.dm deleted file mode 100644 index 6650046512..0000000000 --- a/code/modules/detectivework/detective_work.dm +++ /dev/null @@ -1,629 +0,0 @@ -//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 & ~FULL_BODY && (M.wear_suit.body_parts_covered & ~UPPER_TORSO || M.wear_suit.body_parts_covered & ~LOWER_TORSO || M.wear_suit.body_parts_covered & ~ARMS || M.wear_suit.body_parts_covered & ~LEGS)) - 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 & ~HANDS) - 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.set_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]}
- {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}

" - [scan_data]"} - if(scan_data && !scan_process) - dat += "
{Erase Data}" - user << browse(dat,"window=scanner") - onclose(user,"scanner") - - - Topic(href,href_list) - if ((usr.contents.Find(src) || (in_range(src, usr) && istype(src.loc, /turf))) || (istype(usr, /mob/living/silicon))) - usr.set_machine(src) - 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

- 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

- 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) - var/list/dossier = files[href_list["identifier"]] - P.name = "\improper Database File ([dossier[2]])" - P.icon_state = "paper_words" - P.info = "Criminal Evidence Database

" - 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.icon_state = "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.icon_state = "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)) - var/obj/item/device/detective_scanner/D = W - if(D.stored) - for(var/atom in D.stored) - var/list/data = D.stored[atom] - add_data_master(atom,data[1],data[2],data[3],data[4]) - D.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 ? atom_suit_fibers.Copy() : null - templist[2] = atom_blood_DNA ? atom_blood_DNA.Copy() : null - templist[3] = atom_name - templist[4] = atom_fingerprints ? atom_fingerprints.Copy() : null - 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 ? atom_fingerprints.Copy() : null - data_point[2] = atom_suit_fibers ? atom_suit_fibers.Copy() : null - data_point[3] = atom_blood_DNA ? atom_blood_DNA.Copy() : null - 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 ? atom_fingerprints.Copy() : null - data_point[2] = atom_suit_fibers ? atom_suit_fibers.Copy() : null - data_point[3] = atom_blood_DNA ? atom_blood_DNA.Copy() : null - 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/forensics.dm b/code/modules/detectivework/forensics.dm new file mode 100644 index 0000000000..d04fe83f46 --- /dev/null +++ b/code/modules/detectivework/forensics.dm @@ -0,0 +1,73 @@ +//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 +proc/is_complete_print(var/print) + return stringpercent(print) <= FINGERPRINT_COMPLETE + +atom/var/list/suit_fibers + +atom/proc/add_fibers(mob/living/carbon/human/M) + if(M.gloves && istype(M.gloves,/obj/item/clothing/gloves)) + var/obj/item/clothing/gloves/G = M.gloves + if(G.transfer_blood) //bloodied gloves transfer blood to touched objects + if(add_blood(G.bloody_hands_mob)) //only reduces the bloodiness of our gloves if the item wasn't already bloody + 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 + var/suit_coverage = 0 + if(M.wear_suit) + fibertext = "Material from \a [M.wear_suit]." + if(prob(10*item_multiplier) && !(fibertext in suit_fibers)) + suit_fibers += fibertext + suit_coverage = M.wear_suit.body_parts_covered + + if(M.w_uniform && (M.w_uniform.body_parts_covered & ~suit_coverage)) + fibertext = "Fibers from \a [M.w_uniform]." + if(prob(15*item_multiplier) && !(fibertext in suit_fibers)) + suit_fibers += fibertext + + if(M.gloves && (M.w_uniform.body_parts_covered & ~suit_coverage)) + fibertext = "Material from a pair of [M.gloves.name]." + if(prob(20*item_multiplier) && !(fibertext in suit_fibers)) + suit_fibers += "Material from a pair of [M.gloves.name]." + +/datum/data/record/forensic + name = "forensic data" + var/uid + +/datum/data/record/forensic/New(var/atom/A) + uid = "\ref [A]" + fields["name"] = sanitize(A.name) + fields["area"] = sanitize("[get_area(A)]") + fields["fprints"] = A.fingerprints ? A.fingerprints.Copy() : list() + fields["fibers"] = A.suit_fibers ? A.suit_fibers.Copy() : list() + fields["blood"] = A.blood_DNA ? A.blood_DNA.Copy() : list() + fields["time"] = world.time + +/datum/data/record/forensic/proc/merge(var/datum/data/record/other) + var/list/prints = fields["fprints"] + var/list/o_prints = other.fields["fprints"] + for(var/print in o_prints) + if(!prints[print]) + prints[print] = o_prints[print] + else + prints[print] = stringmerge(prints[print], o_prints[print]) + fields["fprints"] = prints + + var/list/fibers = fields["fibers"] + var/list/o_fibers = other.fields["fibers"] + fibers |= o_fibers + fields["fibers"] = fibers + + var/list/blood = other.fields["blood"] + var/list/o_blood = other.fields["blood"] + blood |= o_blood + fields["blood"] = blood + + fields["area"] = other.fields["area"] + fields["time"] = other.fields["time"] \ No newline at end of file diff --git a/code/modules/detectivework/scanner.dm b/code/modules/detectivework/scanner.dm index 8ab4da0414..3916c799c8 100644 --- a/code/modules/detectivework/scanner.dm +++ b/code/modules/detectivework/scanner.dm @@ -1,181 +1,107 @@ -//CONTAINS: Detective's Scanner - - /obj/item/device/detective_scanner - name = "Scanner" + name = "forensic 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 | NOBLUDGEON 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." - flick("forensic0",src) - return 0 - if (( !( istype(M.dna, /datum/dna) ) || M.gloves) ) - user << "\blue No fingerprints found on [M]" - flick("forensic0",src) - 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, proximity) - if(!proximity) 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.\"" - flick("forensic0",src) - return - - add_fingerprint(user) - - //Special case for blood splatters, runes and gibs. - if (istype(A, /obj/effect/decal/cleanable/blood) || istype(A, /obj/effect/rune) || istype(A, /obj/effect/decal/cleanable/blood/gibs)) - if(!isnull(A.blood_DNA)) - for(var/blood in A.blood_DNA) - user << "\blue Blood type: [A.blood_DNA[blood]]\nDNA: [blood]" - flick("forensic2",src) - 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.") - flick("forensic0",src) - return 0 - - if(add_data(A)) - user << "\blue Object already in internal memory. Consolidating data..." - flick("forensic2",src) - 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." - flick("forensic2",src) - - //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.") - flick("forensic2",src) - 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.") - flick("forensic2",src) - return 0 - return - - proc/add_data(atom/A as mob|obj|turf|area) - //I love associative lists. - 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 ? A.fingerprints.Copy() : null - sum_list[2] = A.suit_fibers ? A.suit_fibers.Copy() : null - sum_list[3] = A.blood_DNA ? A.blood_DNA.Copy() : null - sum_list[4] = "\The [A] in \the [get_area(A)]" - stored["\ref [A]"] = sum_list +/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 \ No newline at end of file diff --git a/code/modules/detectivework/scanning_console.dm b/code/modules/detectivework/scanning_console.dm new file mode 100644 index 0000000000..9e32c58390 --- /dev/null +++ b/code/modules/detectivework/scanning_console.dm @@ -0,0 +1,305 @@ +/obj/machinery/computer/forensic_scanning + name = "high-res forensic scanning computer" + icon_state = "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 + +/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] + var/list/prints = rec.fields["fprints"] + for(var/master_print in card.fingerprints) + if(prints[master_print]) + prints[master_print] = master_print + found = 1 + 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) + 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 = stripped_input(usr,"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 = stripped_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() + del(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() \ No newline at end of file