diff --git a/code/modules/detectivework/scanner.dm b/code/modules/detectivework/scanner.dm
index 653a4990797..aedec750914 100644
--- a/code/modules/detectivework/scanner.dm
+++ b/code/modules/detectivework/scanner.dm
@@ -1,7 +1,5 @@
//CONTAINS: Detective's Scanner
-// TODO: Split everything into easy to manage procs.
-
/obj/item/detective_scanner
name = "forensic scanner"
desc = "Used to remotely scan objects and biomass for DNA and fingerprints. Can print a report of the findings."
@@ -17,30 +15,17 @@
slot_flags = ITEM_SLOT_BELT
/// if the scanner is currently busy processing
var/scanner_busy = FALSE
- var/list/log = list()
+ var/list/log_data = list()
var/range = 8
var/view_check = TRUE
var/forensicPrintCount = 0
- actions_types = list(/datum/action/item_action/display_detective_scan_results)
-/datum/action/item_action/display_detective_scan_results
- name = "Display Forensic Scanner Results"
-
-/datum/action/item_action/display_detective_scan_results/Trigger(trigger_flags)
- var/obj/item/detective_scanner/scanner = target
- if(istype(scanner))
- scanner.display_detective_scan_results(usr)
-
-/obj/item/detective_scanner/attack_self(mob/user)
- if(!LAZYLEN(log))
- balloon_alert(user, "no logs!")
- return
- if(scanner_busy)
- balloon_alert(user, "scanner busy!")
- return
- scanner_busy = TRUE
- balloon_alert(user, "printing report...")
- addtimer(CALLBACK(src, PROC_REF(safe_print_report)), 10 SECONDS)
+/obj/item/detective_scanner/interact(mob/user)
+ . = ..()
+ if(user.stat != CONSCIOUS || !user.can_read(src) || user.is_blind())
+ return ITEM_INTERACT_BLOCKING
+ ui_interact(user)
+ return ITEM_INTERACT_SUCCESS
/**
* safe_print_report - a wrapper proc for print_report
@@ -59,11 +44,51 @@
var/frNum = ++forensicPrintCount
report_paper.name = "FR-[frNum] 'Forensic Record'"
- var/report_text = "
Forensic Record - (FR-[frNum])
"
- report_text += jointext(log, "
")
- report_text += "
Notes:
"
+ var/list/report_text = list("Forensic Record - (FR-[frNum])
")
- report_paper.add_raw_text(report_text)
+ for(var/list/log in log_data)
+ report_text += "[capitalize(log["scan_target"])] scan at [log["scan_time"]]
"
+
+ if(!log[DETSCAN_CATEGORY_FIBER] && !log[DETSCAN_CATEGORY_BLOOD] && !log[DETSCAN_CATEGORY_FINGERS] && !log[DETSCAN_CATEGORY_DRINK] && !log[DETSCAN_CATEGORY_ACCESS])
+ report_text += "No forensic traces found.
"
+ continue
+
+ if(log[DETSCAN_CATEGORY_FIBER])
+ report_text += "- [DETSCAN_CATEGORY_FIBER]
- "
+ for(var/fibers in log[DETSCAN_CATEGORY_FIBER])
+ report_text += fibers + "
"
+ report_text += " "
+
+ if(log[DETSCAN_CATEGORY_BLOOD])
+ report_text += "- [DETSCAN_CATEGORY_BLOOD]
- "
+ for(var/blood in log[DETSCAN_CATEGORY_BLOOD])
+ report_text += "[blood], [log[DETSCAN_CATEGORY_BLOOD][blood]]
"
+ report_text += " "
+
+ if(log[DETSCAN_CATEGORY_FINGERS])
+ report_text += "- [DETSCAN_CATEGORY_FINGERS]
- "
+ for(var/fingers in log[DETSCAN_CATEGORY_FINGERS])
+ report_text += fingers + "
"
+ report_text += " "
+
+ if(log[DETSCAN_CATEGORY_DRINK])
+ report_text += "- [DETSCAN_CATEGORY_DRINK]
- "
+ for(var/reagent in log[DETSCAN_CATEGORY_DRINK])
+ report_text += "[reagent]: [log[DETSCAN_CATEGORY_DRINK][reagent]] u.
"
+ report_text += " "
+
+ if(log[DETSCAN_CATEGORY_ACCESS])
+ report_text += "- [DETSCAN_CATEGORY_ACCESS]
- "
+ for(var/region in log[DETSCAN_CATEGORY_ACCESS])
+ var/list/access_list = log[DETSCAN_CATEGORY_ACCESS][region]
+ report_text += "[region]: [access_list.Join(", ")]
"
+ report_text += " "
+
+ report_text += "
"
+
+ report_text += "Notes:
"
+
+ report_paper.add_raw_text(report_text.Join())
report_paper.update_appearance()
if(ismob(loc))
@@ -72,7 +97,7 @@
balloon_alert(printer, "logs cleared")
// Clear the logs
- log = list()
+ log_data = list()
/obj/item/detective_scanner/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
if(SHOULD_SKIP_INTERACTION(interacting_with, src, user))
@@ -92,6 +117,7 @@
/obj/item/detective_scanner/proc/safe_scan(mob/user, atom/atom_to_scan)
set waitfor = FALSE
if(scanner_busy)
+ balloon_alert(user, "scanner busy!")
return
if(!scan(user, atom_to_scan)) // this should only return FALSE if a runtime occurs during the scan proc, so ideally never
balloon_alert(user, "scanner error!") // but in case it does, we 'error' instead of just bricking the scanner
@@ -123,30 +149,31 @@
// GATHER INFORMATION
- //Make our assoc list array
- // The keys are the headers used for it, and the value is a list of each line printed
- var/list/det_data = list()
- var/list/blood = GET_ATOM_BLOOD_DNA(scanned_atom)
- det_data[DETSCAN_CATEGORY_FIBER] = GET_ATOM_FIBRES(scanned_atom)
-
- var/target_name = scanned_atom.name
+ var/list/log_entry_data = list()
// Start gathering
- if(ishuman(scanned_atom))
+ log_entry_data["scan_target"] = scanned_atom.name
+ log_entry_data["scan_time"] = station_time_timestamp()
+ log_entry_data[DETSCAN_CATEGORY_FIBER] = GET_ATOM_FIBRES(scanned_atom)
+
+ var/list/blood = GET_ATOM_BLOOD_DNA(scanned_atom)
+ if(length(blood))
+ LAZYADD(log_entry_data[DETSCAN_CATEGORY_BLOOD], blood)
+
+ if(ishuman(scanned_atom))
var/mob/living/carbon/human/scanned_human = scanned_atom
if(!scanned_human.gloves)
- LAZYADD(det_data[DETSCAN_CATEGORY_FINGERS], md5(scanned_human.dna?.unique_identity))
+ LAZYADD(log_entry_data[DETSCAN_CATEGORY_FINGERS], md5(scanned_human.dna?.unique_identity))
else if(!ismob(scanned_atom))
- det_data[DETSCAN_CATEGORY_FINGERS] = GET_ATOM_FINGERPRINTS(scanned_atom)
+ log_entry_data[DETSCAN_CATEGORY_FINGERS] = GET_ATOM_FINGERPRINTS(scanned_atom)
// Only get reagents from non-mobs.
for(var/datum/reagent/present_reagent as anything in scanned_atom.reagents?.reagent_list)
- LAZYADD(det_data[DETSCAN_CATEGORY_DRINK], \
- "Reagent: [present_reagent.name] Volume: [present_reagent.volume]")
+ LAZYADD(log_entry_data[DETSCAN_CATEGORY_DRINK], list(present_reagent.name = present_reagent.volume))
// Get blood data from the blood reagent.
if(!istype(present_reagent, /datum/reagent/blood))
@@ -165,92 +192,79 @@
var/access_in_region = SSid_access.accesses_by_region[region] & user_id.GetAccess()
if(!length(access_in_region))
continue
- LAZYADD(det_data[DETSCAN_CATEGORY_ACCESS], "[region]:")
var/list/access_names = list()
for(var/access_num in access_in_region)
access_names += SSid_access.get_access_desc(access_num)
- LAZYADD(det_data[DETSCAN_CATEGORY_ACCESS], english_list(access_names))
-
-
- for(var/bloodtype in blood)
- LAZYADD(det_data[DETSCAN_CATEGORY_BLOOD], \
- "Type: [blood[bloodtype]] DNA (UE): [bloodtype]")
+ LAZYADD(log_entry_data[DETSCAN_CATEGORY_ACCESS], region)
+ LAZYADD(log_entry_data[DETSCAN_CATEGORY_ACCESS][region], english_list(access_names))
// sends it off to be modified by the items
- SEND_SIGNAL(scanned_atom, COMSIG_DETECTIVE_SCANNED, user, det_data)
+ SEND_SIGNAL(scanned_atom, COMSIG_DETECTIVE_SCANNED, user, log_entry_data)
- // We gathered everything. Create a fork and slowly display the results to the holder of the scanner.
-
- var/found_something = FALSE
- add_log("[station_time_timestamp()][get_timestamp()] - [target_name]", 0)
-
- for(var/category in DETSCAN_DEFAULT_ORDER())
- if(!LAZYLEN(det_data[category]))
- continue // no data found, move to next category
- sleep(3 SECONDS)
- add_log(span_info("[category]:"))
- for(var/line in det_data[category])
- add_log(line)
- found_something = TRUE
-
- // Make sure the original user is still holding the scanner before sending them the results
- var/mob/holder = null
- if(src.loc == user)
- holder = user
- else if(ismob(src.loc))
- holder = src.loc
-
- if(!found_something)
- add_log("# No forensic traces found #", 0) // Don't display this to the holder user
- if(holder)
- to_chat(holder, span_warning("Unable to locate any fingerprints, materials, fibers, or blood on \the [target_name]!"))
- else
- if(holder)
- to_chat(holder, span_notice("You finish scanning \the [target_name]."))
-
- add_log("---------------------------------------------------------", 0)
+ stoplag(3 SECONDS)
+ log_data += list(log_entry_data)
return TRUE
-/obj/item/detective_scanner/proc/add_log(msg, broadcast = 1)
- if(scanner_busy)
- if(broadcast && ismob(loc))
- var/mob/logger = loc
- to_chat(logger, msg)
- log += " [msg]"
- else
- CRASH("[src] [REF(src)] is adding a log when it was never put in scanning mode!")
-
/proc/get_timestamp()
return time2text(world.time + 432000, ":ss")
/obj/item/detective_scanner/click_alt(mob/living/user)
- if(!LAZYLEN(log))
- balloon_alert(user, "no logs!")
- return CLICK_ACTION_BLOCKING
- if(scanner_busy)
- balloon_alert(user, "scanner busy!")
- return CLICK_ACTION_BLOCKING
- balloon_alert(user, "deleting logs...")
- if(!do_after(user, 3 SECONDS, target = src))
- return CLICK_ACTION_BLOCKING
- balloon_alert(user, "logs cleared")
- log = list()
- return CLICK_ACTION_SUCCESS
-
+ return clear_logs()
/obj/item/detective_scanner/examine(mob/user)
. = ..()
- if(LAZYLEN(log) && !scanner_busy)
+ if(LAZYLEN(log_data) && !scanner_busy)
. += span_notice("Alt-click to clear scanner logs.")
-/obj/item/detective_scanner/proc/display_detective_scan_results(mob/living/user)
- // No need for can-use checks since the action button should do proper checks
- if(!LAZYLEN(log))
- balloon_alert(user, "no logs!")
+
+/obj/item/detective_scanner/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "ForensicScanner", "Forensic Scanner")
+ ui.open()
+
+/obj/item/detective_scanner/ui_data(mob/user)
+ var/list/data = list()
+ data["log_data"] = log_data
+ return data
+
+/obj/item/detective_scanner/ui_act(action, params, datum/tgui/ui)
+ . = ..()
+ if(.)
return
+ switch(action)
+ if("clear")
+ clear_logs(ui.user)
+ ui.send_update()
+ if("delete")
+ var/index = params["index"] + 1
+ if(!log_data[index])
+ return
+ if(scanner_busy)
+ balloon_alert(ui.user, "scanner busy!")
+ return
+ log_data.Cut(index, index + 1)
+ balloon_alert(ui.user, "log deleted")
+ ui.send_update()
+ if("print")
+ if(!LAZYLEN(log_data))
+ balloon_alert(ui.user, "no logs!")
+ return
+ if(scanner_busy)
+ balloon_alert(ui.user, "scanner busy!")
+ return
+ scanner_busy = TRUE
+ playsound(src, 'sound/machines/printer.ogg', 50)
+ balloon_alert(ui.user, "printing report...")
+ addtimer(CALLBACK(src, PROC_REF(safe_print_report)), 3 SECONDS)
+
+/obj/item/detective_scanner/proc/clear_logs(mob/living/user)
+ if(!LAZYLEN(log_data))
+ balloon_alert(user, "no logs!")
+ return CLICK_ACTION_BLOCKING
if(scanner_busy)
balloon_alert(user, "scanner busy!")
- return
- to_chat(user, span_notice("Scanner Report"))
- for(var/iterLog in log)
- to_chat(user, iterLog)
+ return CLICK_ACTION_BLOCKING
+ balloon_alert(user, "logs cleared")
+ log_data = list()
+ return CLICK_ACTION_SUCCESS
diff --git a/tgui/packages/tgui/interfaces/ForensicScanner.tsx b/tgui/packages/tgui/interfaces/ForensicScanner.tsx
new file mode 100644
index 00000000000..51410a38d4d
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ForensicScanner.tsx
@@ -0,0 +1,145 @@
+import {
+ Box,
+ Button,
+ Icon,
+ LabeledList,
+ NoticeBox,
+ Section,
+} from 'tgui-core/components';
+import { capitalizeFirst } from 'tgui-core/string';
+
+import { useBackend } from '../backend';
+import { Window } from '../layouts';
+
+type ForensicScannerData = {
+ log_data: LogData[];
+};
+
+type LogData = {
+ scan_target: string;
+ scan_time: string;
+ Prints: Record;
+ Fibers: Record;
+ Blood: Record;
+ Reagents: Record;
+ 'ID Access': Record;
+};
+
+export const ForensicScanner = (props) => {
+ const { act, data } = useBackend();
+ const { log_data = [] } = data;
+ return (
+
+
+ {log_data.length === 0 ? (
+ Log empty.
+ ) : (
+
+ act('clear')}
+ >
+ Clear logs
+
+
+ >
+ }
+ >
+ {log_data
+ .map((log, index) => (
+
+ ))
+ .reverse()}
+
+ )}
+
+
+ );
+};
+
+const ForensicLog = ({ log, index }: { log: LogData; index: number }) => {
+ const { act } = useBackend();
+ return (
+ act('delete', { index })}
+ />
+ }
+ >
+ {!log.Prints && !log.Fibers && !log.Blood && !log.Reagents ? (
+ No forensic traces found.
+ ) : (
+
+ {log.Fibers && Object.keys(log.Fibers).length > 0 && (
+
+ {Object.keys(log.Fibers).map((fibers) => (
+
+
+ {fibers}
+
+ ))}
+
+ )}
+ {log.Prints && Object.values(log.Prints).length > 0 && (
+
+ {Object.values(log.Prints).map((print) => (
+
+
+ {print}
+
+ ))}
+
+ )}
+ {log.Blood && Object.keys(log.Blood).length > 0 && (
+
+ {Object.keys(log.Blood).map((dna) => (
+
+
+ {`${dna}, ${log.Blood[dna]}`}
+
+ ))}
+
+ )}
+ {log.Reagents && Object.keys(log.Reagents)?.length > 0 && (
+
+
+ {Object.keys(log.Reagents).map((reagent) => (
+
+ {`${log.Reagents[reagent]} u.`}
+
+ ))}
+
+
+ )}
+ {log['ID Access'] && Object.keys(log['ID Access'])?.length > 0 && (
+
+
+ {Object.keys(log['ID Access']).map((region) => (
+
+ {log['ID Access'][region]}
+
+ ))}
+
+
+ )}
+
+ )}
+
+ ) as any;
+};