mirror of
https://github.com/Aurorastation/Aurora.3.git
synced 2026-08-26 14:31:59 +01:00
The Case Dossier program (#22703)
This adds the case dossier program. A program intended for investigators to use for their cases, with the ability to record evidence, photos, documents, people and their notes on a case. The rest of security is able to view cases in the program, but not edit them. Additionally, the forensic skill was added to evidence bags and photos, allowing a person to add the time, place and the person using them, mainly intended to be used with the case dossier program. Fibers and prints can now be gathered from objects inside evidence bags, to avoid contamination. Also fixed some bugs with taking photos of certain objects, such as airlocks. https://github.com/user-attachments/assets/92a880e9-9032-46d4-88c0-43d45b64f979
This commit is contained in:
@@ -7,8 +7,16 @@
|
||||
icon_state = "evidenceobj"
|
||||
item_state = ""
|
||||
w_class = WEIGHT_CLASS_SMALL
|
||||
var/obj/item/stored_item = null
|
||||
/// The item stored in the evidence bag
|
||||
var/obj/item/stored_item
|
||||
/// The label on the evidence bag
|
||||
var/label_text = ""
|
||||
/// Who collected the item
|
||||
var/collected_by = ""
|
||||
/// Where the item was collected
|
||||
var/collected_location = ""
|
||||
/// WHen the item was collected
|
||||
var/collected_time = ""
|
||||
|
||||
/obj/item/evidencebag/mechanics_hints(mob/user, distance, is_adjacent)
|
||||
. += ..()
|
||||
@@ -65,9 +73,9 @@
|
||||
|
||||
human_user.visible_message("<b>[human_user]</b> puts \the [I] into \the [src].", SPAN_NOTICE("You put \the [I] inside \the [src]."),\
|
||||
"You hear a rustle as someone puts something into a plastic bag.")
|
||||
store_item(I)
|
||||
store_item(I, user)
|
||||
|
||||
/obj/item/evidencebag/proc/store_item(obj/item/I)
|
||||
/obj/item/evidencebag/proc/store_item(obj/item/I, mob/user)
|
||||
icon_state = "evidence"
|
||||
var/mutable_appearance/MA = new(I)
|
||||
MA.pixel_x = 0
|
||||
@@ -75,6 +83,17 @@
|
||||
MA.layer = FLOAT_LAYER
|
||||
AddOverlays(list(MA, "evidence"))
|
||||
|
||||
var/forensic = GET_SKILL_LEVEL(user, FORENSICS_SKILL_COMPONENT)
|
||||
forensic = forensic ? forensic : SKILL_LEVEL_TRAINED
|
||||
|
||||
// Trained people would know to add this data
|
||||
if(forensic >= SKILL_LEVEL_FAMILIAR)
|
||||
collected_location = get_area_display_name(get_area(I))
|
||||
collected_by = user.name
|
||||
collected_time = worldtime2text()
|
||||
// And untrained people risk contaminating it
|
||||
else if (prob(25))
|
||||
I.add_fingerprint(user)
|
||||
desc = "A plastic bag containing [I]."
|
||||
I.forceMove(src)
|
||||
stored_item = I
|
||||
@@ -99,21 +118,60 @@
|
||||
icon_state = "evidenceobj"
|
||||
return
|
||||
|
||||
/obj/item/evidencebag/feedback_hints(mob/user, distance, is_adjacent)
|
||||
. = ..()
|
||||
if(label_text)
|
||||
. += SPAN_NOTICE("It is labelled: \"[label_text]\".")
|
||||
|
||||
if(collected_by)
|
||||
. += SPAN_NOTICE("Collected by: [collected_by].")
|
||||
|
||||
if(collected_location)
|
||||
. += SPAN_NOTICE("Collected from: [collected_location].")
|
||||
|
||||
if(collected_time)
|
||||
. += SPAN_NOTICE("Collected at: [collected_time].")
|
||||
|
||||
/obj/item/evidencebag/examine(mob/user, distance, is_adjacent, infix, suffix, show_extended)
|
||||
. = ..()
|
||||
if (stored_item)
|
||||
if(stored_item)
|
||||
examinate(user, stored_item, show_extended)
|
||||
|
||||
/obj/item/evidencebag/attackby(obj/item/attacking_item, mob/user)
|
||||
if(attacking_item.tool_behaviour == TOOL_PEN || istype(attacking_item, /obj/item/flashlight/pen))
|
||||
var/tmp_label = sanitizeSafe( tgui_input_text(user, "Enter a label for [name]", "Label", label_text, MAX_NAME_LEN), MAX_NAME_LEN )
|
||||
if(length(tmp_label) > MAX_NAME_LEN)
|
||||
to_chat(user, SPAN_NOTICE("The label can be at most [MAX_NAME_LEN] characters long."))
|
||||
var/static/list/evidence_label_fields = list(
|
||||
"Label text",
|
||||
"Collected by",
|
||||
"Collected location",
|
||||
"Collected time"
|
||||
)
|
||||
|
||||
var/field = tgui_input_list(user, "Which evidence label field do you want to edit?", "Evidence Label", evidence_label_fields)
|
||||
if(!field)
|
||||
return
|
||||
|
||||
var/current_value = get_evidence_label_value(field)
|
||||
|
||||
var/new_value = tgui_input_text(
|
||||
user,
|
||||
"Enter [field] for [name]",
|
||||
"Evidence Label",
|
||||
current_value,
|
||||
MAX_NAME_LEN
|
||||
)
|
||||
|
||||
if(isnull(new_value))
|
||||
return
|
||||
|
||||
set_evidence_label_value(field, new_value)
|
||||
|
||||
if(new_value == "")
|
||||
to_chat(user, SPAN_NOTICE("You clear [field]."))
|
||||
else
|
||||
to_chat(user, SPAN_NOTICE("You set the label to \"[tmp_label]\"."))
|
||||
label_text = tmp_label
|
||||
update_name_label()
|
||||
to_chat(user, SPAN_NOTICE("You set [field] to \"[new_value]\"."))
|
||||
|
||||
return
|
||||
|
||||
. = ..()
|
||||
|
||||
/obj/item/evidencebag/proc/update_name_label(var/base_name = initial(name))
|
||||
@@ -122,3 +180,34 @@
|
||||
name = base_name
|
||||
else
|
||||
name = "[base_name] ([label_text])"
|
||||
|
||||
/obj/item/evidencebag/proc/get_evidence_label_value(var/field)
|
||||
switch(field)
|
||||
if("Label text")
|
||||
return label_text
|
||||
if("Collected by")
|
||||
return collected_by
|
||||
if("Collected location")
|
||||
return collected_location
|
||||
if("Collected time")
|
||||
return collected_time
|
||||
|
||||
return ""
|
||||
|
||||
/obj/item/evidencebag/proc/set_evidence_label_value(var/field, var/value)
|
||||
switch(field)
|
||||
if("Label text")
|
||||
label_text = value
|
||||
update_name_label()
|
||||
return TRUE
|
||||
if("Collected by")
|
||||
collected_by = value
|
||||
return TRUE
|
||||
if("Collected location")
|
||||
collected_location = value
|
||||
return TRUE
|
||||
if("Collected time")
|
||||
collected_time = value
|
||||
return TRUE
|
||||
|
||||
return FALSE
|
||||
|
||||
@@ -175,6 +175,7 @@
|
||||
/obj/item/forensics/sample_kit/mechanics_hints(mob/user, distance, is_adjacent)
|
||||
. += ..()
|
||||
. += "Click drag it onto an object to collect fiber evidence. Alternatively, click on an object with non-Help intent."
|
||||
. += "Click an evidence bag to collect evidence from the object within it. Alternatively, using harm intent checks the evidence bag."
|
||||
|
||||
/obj/item/forensics/sample_kit/proc/can_take_sample(var/mob/user, var/atom/supplied)
|
||||
return (supplied.suit_fibers && supplied.suit_fibers.len)
|
||||
@@ -187,6 +188,13 @@
|
||||
if(!proximity)
|
||||
return
|
||||
add_fingerprint(user)
|
||||
|
||||
// We can sample items from within an evidence bag
|
||||
if(user.a_intent != I_HURT && istype(A, /obj/item/evidencebag))
|
||||
var/obj/item/evidencebag/bag = A
|
||||
if(bag.stored_item)
|
||||
A = bag.stored_item
|
||||
|
||||
if(can_take_sample(user, A))
|
||||
take_sample(user,A)
|
||||
return 1
|
||||
|
||||
@@ -78,5 +78,6 @@ ABSTRACT_TYPE(/datum/modular_computer_app_presets)
|
||||
/datum/computer_file/program/camera_monitor,\
|
||||
/datum/computer_file/program/digitalwarrant,\
|
||||
/datum/computer_file/program/records/security,\
|
||||
/datum/computer_file/program/case_dossier,\
|
||||
/datum/computer_file/program/guntracker,\
|
||||
)
|
||||
|
||||
@@ -0,0 +1,299 @@
|
||||
#define STATUS_OPEN "Open"
|
||||
#define STATUS_SUBMITTED "Submitted"
|
||||
#define STATUS_ARCHIVED "Archived"
|
||||
/datum/computer_file/program/case_dossier
|
||||
filename = "case_dossier"
|
||||
filedesc = "Case Dossier"
|
||||
extended_desc = "Official NTsec program for the handling of investigation cases."
|
||||
program_icon_state = "security_record"
|
||||
program_key_icon_state = "yellow_key"
|
||||
color = LIGHT_COLOR_RED
|
||||
size = 6
|
||||
requires_ntnet = TRUE
|
||||
available_on_ntnet = TRUE
|
||||
// Security officers and wardens are allowed to download the program, but not edit it
|
||||
required_access_download = ACCESS_SECURITY
|
||||
required_access_run = ACCESS_SECURITY
|
||||
usage_flags = PROGRAM_ALL_REGULAR | PROGRAM_STATIONBOUND
|
||||
tgui_id = "Casedossier"
|
||||
|
||||
var/list/stored_cases
|
||||
var/datum/investigation_case/open_case
|
||||
|
||||
/datum/computer_file/program/case_dossier/New(obj/item/modular_computer/comp)
|
||||
. = ..()
|
||||
stored_cases = GLOB.case_dossier_cases
|
||||
|
||||
/datum/computer_file/program/case_dossier/Destroy()
|
||||
stored_cases.Cut()
|
||||
open_case = null
|
||||
return ..()
|
||||
|
||||
/// Who is permitted to edit the investigation files. Defaults to forensic staff
|
||||
/datum/computer_file/program/case_dossier/proc/can_edit(mob/user)
|
||||
if(!user)
|
||||
return FALSE
|
||||
|
||||
var/obj/item/card/id/I = user.GetIdCard()
|
||||
if(!I)
|
||||
return FALSE
|
||||
|
||||
return (ACCESS_FORENSICS_LOCKERS in I.access)
|
||||
|
||||
/datum/computer_file/program/case_dossier/ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
|
||||
data["available_statuses"] = list(STATUS_OPEN, STATUS_SUBMITTED, STATUS_ARCHIVED)
|
||||
data["can_edit"] = can_edit(user)
|
||||
data["open_case"] = null
|
||||
data["cases"] = list()
|
||||
|
||||
if(open_case)
|
||||
open_case.send_photo_resources(user)
|
||||
data["open_case"] = open_case.tgui_data()
|
||||
|
||||
if(stored_cases)
|
||||
for(var/datum/investigation_case/C in stored_cases)
|
||||
data["cases"] += list(C.tgui_data(TRUE))
|
||||
|
||||
return data
|
||||
|
||||
/datum/investigation_case/proc/tgui_data(var/include_contents = TRUE)
|
||||
var/list/data = list()
|
||||
|
||||
data["id"] = "[case_id]"
|
||||
data["title"] = title
|
||||
data["status"] = status
|
||||
data["investigator"] = investigator
|
||||
data["created_at"] = created_at
|
||||
data["updated_at"] = updated_at
|
||||
data["tags"] = tags
|
||||
|
||||
data["victim_count"] = length(victims)
|
||||
data["suspect_count"] = length(suspects)
|
||||
data["witness_count"] = length(witnesses)
|
||||
data["evidence_count"] = length(evidence_refs)
|
||||
data["photo_count"] = length(photo_refs)
|
||||
data["report_count"] = length(report_refs)
|
||||
|
||||
if(include_contents)
|
||||
data["summary"] = summary
|
||||
data["timeline"] = timeline
|
||||
data["findings"] = findings
|
||||
|
||||
data["victims"] = person_list_tgui_data(victims)
|
||||
data["suspects"] = person_list_tgui_data(suspects)
|
||||
data["witnesses"] = person_list_tgui_data(witnesses)
|
||||
|
||||
data["evidence"] = evidence_list_tgui_data(evidence_refs)
|
||||
data["photos"] = evidence_list_tgui_data(photo_refs)
|
||||
data["reports"] = report_list_tgui_data(report_refs)
|
||||
|
||||
return data
|
||||
|
||||
/datum/computer_file/program/case_dossier/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
|
||||
. = ..()
|
||||
if(.)
|
||||
return
|
||||
|
||||
switch(action)
|
||||
if("new_case")
|
||||
create_new_case(usr)
|
||||
return TRUE
|
||||
|
||||
if("open_case")
|
||||
open_case_by_id(params["id"])
|
||||
return TRUE
|
||||
|
||||
if("save_case")
|
||||
save_open_case(usr)
|
||||
return TRUE
|
||||
|
||||
if("edit_case_field")
|
||||
if(open_case)
|
||||
open_case.set_field(params["field"], params["value"])
|
||||
else
|
||||
to_chat(usr, SPAN_NOTICE("No open case selected."))
|
||||
return TRUE
|
||||
|
||||
if("edit_case_tags")
|
||||
if(open_case)
|
||||
open_case.set_tags_from_text(params["value"])
|
||||
else
|
||||
to_chat(usr, SPAN_NOTICE("No open case selected."))
|
||||
return TRUE
|
||||
|
||||
if("add_person")
|
||||
if(open_case)
|
||||
open_case.add_person(params["list"])
|
||||
else
|
||||
to_chat(usr, SPAN_NOTICE("No open case selected."))
|
||||
return TRUE
|
||||
|
||||
if("edit_person")
|
||||
if(open_case)
|
||||
open_case.edit_person(params["list"], params["id"], params["field"], params["value"])
|
||||
else
|
||||
to_chat(usr, SPAN_NOTICE("No open case selected."))
|
||||
return TRUE
|
||||
|
||||
if("remove_person")
|
||||
if(open_case)
|
||||
open_case.remove_person(params["list"], params["id"])
|
||||
else
|
||||
to_chat(usr, SPAN_NOTICE("No open case selected."))
|
||||
return TRUE
|
||||
|
||||
if("scan_evidence")
|
||||
if(open_case)
|
||||
open_case.scan_held_evidence(usr, usr.get_type_in_hands(/obj/item))
|
||||
else
|
||||
to_chat(usr, SPAN_NOTICE("No open case selected."))
|
||||
return TRUE
|
||||
|
||||
if("scan_photo")
|
||||
if(open_case)
|
||||
open_case.scan_held_photo(usr, usr.get_type_in_hands(/obj/item))
|
||||
else
|
||||
to_chat(usr, SPAN_NOTICE("No open case selected."))
|
||||
return TRUE
|
||||
|
||||
if("scan_report")
|
||||
if(open_case)
|
||||
open_case.scan_held_report(usr, usr.get_type_in_hands(/obj/item))
|
||||
else
|
||||
to_chat(usr, SPAN_NOTICE("No open case selected."))
|
||||
return TRUE
|
||||
|
||||
if("print_summary")
|
||||
if(open_case)
|
||||
open_case.print_summary(usr)
|
||||
else
|
||||
to_chat(usr, SPAN_NOTICE("No open case selected."))
|
||||
return TRUE
|
||||
|
||||
if("print_evidence_log")
|
||||
if(open_case)
|
||||
open_case.print_evidence_log(usr)
|
||||
else
|
||||
to_chat(usr, SPAN_NOTICE("No open case selected."))
|
||||
return TRUE
|
||||
|
||||
if("set_case_status")
|
||||
var/datum/investigation_case/C = find_case_by_id(params["id"])
|
||||
if(C)
|
||||
C.set_field("status", params["status"])
|
||||
return TRUE
|
||||
|
||||
if("duplicate_case")
|
||||
duplicate_case_by_id(params["id"], usr)
|
||||
return TRUE
|
||||
|
||||
if("delete_case")
|
||||
delete_case_by_id(params["id"])
|
||||
return TRUE
|
||||
|
||||
if("add_manual_evidence")
|
||||
if(open_case)
|
||||
open_case.add_manual_evidence(usr)
|
||||
return TRUE
|
||||
|
||||
if("edit_evidence")
|
||||
if(open_case)
|
||||
open_case.edit_evidence(params["id"], params["field"], params["value"])
|
||||
return TRUE
|
||||
|
||||
if("remove_evidence")
|
||||
if(open_case)
|
||||
open_case.remove_evidence(params["id"])
|
||||
return TRUE
|
||||
|
||||
if("edit_photo")
|
||||
if(open_case)
|
||||
open_case.edit_photo(params["id"], params["field"], params["value"])
|
||||
return TRUE
|
||||
|
||||
if("remove_photo")
|
||||
if(open_case)
|
||||
open_case.remove_photo(params["id"])
|
||||
return TRUE
|
||||
|
||||
if("remove_report")
|
||||
if(open_case)
|
||||
open_case.remove_report(params["id"])
|
||||
return TRUE
|
||||
|
||||
/// Starts a new case
|
||||
/datum/computer_file/program/case_dossier/proc/create_new_case(var/mob/user)
|
||||
var/datum/investigation_case/C = new /datum/investigation_case(creator = user)
|
||||
|
||||
stored_cases += C
|
||||
open_case = C
|
||||
|
||||
/// Finds a new case by its id
|
||||
/datum/computer_file/program/case_dossier/proc/find_case_by_id(var/id)
|
||||
if(!id)
|
||||
return null
|
||||
|
||||
for(var/datum/investigation_case/C in stored_cases)
|
||||
if("[C.case_id]" == "[id]")
|
||||
return C
|
||||
|
||||
return null
|
||||
|
||||
/// Opens a case by its id
|
||||
/datum/computer_file/program/case_dossier/proc/open_case_by_id(var/id)
|
||||
var/datum/investigation_case/C = find_case_by_id(id)
|
||||
if(!C)
|
||||
return FALSE
|
||||
|
||||
open_case = C
|
||||
return TRUE
|
||||
|
||||
/// Saves the open case
|
||||
/// Right now it doesn't do much, as the program auto-saves.
|
||||
/datum/computer_file/program/case_dossier/proc/save_open_case(var/mob/user)
|
||||
if(!open_case)
|
||||
return FALSE
|
||||
|
||||
open_case.touch()
|
||||
|
||||
if(user)
|
||||
to_chat(user, SPAN_NOTICE("You save [open_case.title]."))
|
||||
|
||||
return TRUE
|
||||
|
||||
/datum/computer_file/program/case_dossier/proc/delete_case_by_id(var/id)
|
||||
var/datum/investigation_case/C = find_case_by_id(id)
|
||||
if(!C)
|
||||
return FALSE
|
||||
|
||||
stored_cases -= C
|
||||
|
||||
if(open_case == C)
|
||||
open_case = null
|
||||
|
||||
qdel(C)
|
||||
return TRUE
|
||||
|
||||
/// Duplicates the case based on the id
|
||||
/datum/computer_file/program/case_dossier/proc/duplicate_case_by_id(var/id, var/mob/user)
|
||||
var/datum/investigation_case/C = find_case_by_id(id)
|
||||
if(!C)
|
||||
return FALSE
|
||||
|
||||
var/datum/investigation_case/N = C.copy_case()
|
||||
|
||||
N.title = "Copy of [C.title]"
|
||||
N.created_by = user?.name
|
||||
N.created_at = worldtime2text()
|
||||
N.updated_at = N.created_at
|
||||
|
||||
stored_cases += N
|
||||
open_case = N
|
||||
|
||||
return TRUE
|
||||
|
||||
#undef STATUS_OPEN
|
||||
#undef STATUS_SUBMITTED
|
||||
#undef STATUS_ARCHIVED
|
||||
@@ -26,7 +26,7 @@
|
||||
/********
|
||||
* photo *
|
||||
********/
|
||||
GLOBAL_VAR_INIT(photo_count, 0)
|
||||
GLOBAL_VAR_INIT(photo_count, 1)
|
||||
|
||||
/obj/item/photo
|
||||
name = "photo"
|
||||
@@ -35,12 +35,26 @@ GLOBAL_VAR_INIT(photo_count, 0)
|
||||
icon_state = "photo"
|
||||
item_state = "paper"
|
||||
w_class = WEIGHT_CLASS_TINY
|
||||
var/picture_desc // Who and/or what's in the picture.
|
||||
/// Who and/or what's in the picture.
|
||||
var/picture_desc
|
||||
/// The photo's id
|
||||
var/id
|
||||
var/icon/img //Big photo image
|
||||
var/scribble //Scribble on the back.
|
||||
/// Big photo image
|
||||
var/icon/img
|
||||
/// Scribble on the back.
|
||||
var/scribble
|
||||
/// The small size of the photo used for the icon
|
||||
var/icon/tiny
|
||||
/// The size of the photo
|
||||
var/photo_size = 3
|
||||
/// The time the photo was taken
|
||||
var/time_taken
|
||||
/// The place the photo was taken
|
||||
var/location_taken
|
||||
/// Who took the photo
|
||||
var/taken_by
|
||||
/// The photo's name/caption
|
||||
var/caption
|
||||
|
||||
drop_sound = 'sound/items/drop/paper.ogg'
|
||||
pickup_sound = 'sound/items/pickup/paper.ogg'
|
||||
@@ -95,8 +109,10 @@ GLOBAL_VAR_INIT(photo_count, 0)
|
||||
if(surface_atom == usr || surface_atom.Adjacent(usr))
|
||||
if(n_name)
|
||||
name = "[initial(name)] ([n_name])"
|
||||
caption = n_name
|
||||
else
|
||||
name = initial(name)
|
||||
caption = null
|
||||
add_fingerprint(usr)
|
||||
|
||||
|
||||
@@ -238,6 +254,12 @@ GLOBAL_VAR_INIT(photo_count, 0)
|
||||
desc = "A one use - polaroid camera."
|
||||
pictures_left = 30
|
||||
|
||||
// Detective cameras have build in data recording, so no need for a skill check
|
||||
/obj/item/camera/detective/record_data(mob/living/user, obj/item/photo/pic)
|
||||
pic.time_taken = worldtime2text()
|
||||
pic.location_taken = get_area_display_name(get_area(user))
|
||||
pic.taken_by = user.name
|
||||
|
||||
//Proc for capturing check
|
||||
/mob/living/proc/can_capture_turf(turf/T)
|
||||
return TRUE // DVIEW will do sanity checks, we've got no special checks.
|
||||
@@ -288,9 +310,21 @@ GLOBAL_VAR_INIT(photo_count, 0)
|
||||
p.pixel_x = rand(-10, 10)
|
||||
p.pixel_y = rand(-10, 10)
|
||||
p.photo_size = size
|
||||
record_data(user, p)
|
||||
|
||||
return p
|
||||
|
||||
/// Data recorded on photos.
|
||||
/obj/item/camera/proc/record_data(mob/living/user, obj/item/photo/pic)
|
||||
var/forensic = GET_SKILL_LEVEL(user, FORENSICS_SKILL_COMPONENT)
|
||||
forensic = forensic ? forensic : SKILL_LEVEL_TRAINED
|
||||
|
||||
// Trained people would know to add this data
|
||||
if(forensic >= SKILL_LEVEL_FAMILIAR)
|
||||
pic.time_taken = worldtime2text()
|
||||
pic.location_taken = get_area_display_name(get_area(user))
|
||||
pic.taken_by = user.name
|
||||
|
||||
/obj/item/camera/proc/printpicture(mob/user, obj/item/photo/p)
|
||||
p.forceMove(user.loc)
|
||||
if(!user.get_inactive_hand())
|
||||
|
||||
Reference in New Issue
Block a user