Files
Casper3667 7a99813dd0 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
2026-06-19 04:36:07 +00:00

224 lines
7.5 KiB
Plaintext

/obj/item/sample
name = "forensic sample"
icon = 'icons/obj/forensics.dmi'
w_class = WEIGHT_CLASS_TINY
var/list/evidence
var/list/source
var/label_text = ""
/obj/item/sample/Initialize(var/newloc, var/atom/supplied)
. = ..(newloc)
if(supplied)
copy_evidence(supplied)
name = "[initial(name)] ([supplied.get_swab_name()])"
LAZYADD(source, supplied.get_swab_name())
/obj/item/sample/print/Initialize(var/newloc, var/atom/supplied)
. = ..(newloc, supplied)
if(LAZYLEN(evidence))
icon_state = "fingerprint1"
/obj/item/sample/proc/copy_evidence(var/atom/supplied)
if(supplied.suit_fibers && supplied.suit_fibers.len)
LAZYADD(evidence, supplied.suit_fibers.Copy())
supplied.suit_fibers.Cut()
/obj/item/sample/proc/merge_evidence(var/obj/item/sample/supplied, var/mob/user)
if(!supplied.evidence || !supplied.evidence.len)
return 0
LAZYDISTINCTADD(evidence, supplied.evidence)
LAZYDISTINCTADD(source, supplied.source)
name = "[initial(name)] (combined)"
to_chat(user, SPAN_NOTICE("You transfer the contents of \the [initial(supplied.name)] into \the [src]."))
return 1
/obj/item/sample/print/merge_evidence(var/obj/item/sample/supplied, var/mob/user)
if(!supplied.evidence || !supplied.evidence.len)
return 0
for(var/print in supplied.evidence)
if(LAZYISIN(evidence, print))
LAZYSET(evidence, print, stringmerge(evidence[print],supplied.evidence[print]))
else
LAZYSET(evidence, print, supplied.evidence[print])
LAZYDISTINCTADD(source, supplied.source)
name = "[initial(name)] (combined)"
to_chat(user, SPAN_NOTICE("You overlay \the [src] and \the [initial(supplied.name)], combining the print records."))
return 1
/obj/item/sample/attackby(obj/item/attacking_item, mob/user, params)
if(attacking_item.type == src.type)
user.unEquip(attacking_item)
if(merge_evidence(attacking_item, user))
qdel(attacking_item)
return TRUE
else if (attacking_item.tool_behaviour == TOOL_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_WARNING("The label can be at most [MAX_NAME_LEN] characters long."))
else
to_chat(user, SPAN_NOTICE("You set the label to \"[tmp_label]\"."))
label_text = tmp_label
update_name_label()
return TRUE
return ..()
/obj/item/sample/proc/update_name_label()
if(label_text == null)
name = initial(name)
else
name = "[initial(name)] ([label_text])"
/atom/proc/get_swab_name()
return "\the [initial(name)]"
/obj/structure/machinery/door/get_swab_name()
if(name != initial(name))
return "\the [initial(name)]: [name]"
return ..()
/obj/item/sample/fibers
name = "fiber bag"
desc = "Used to hold fiber evidence for the detective."
icon_state = "fiberbag"
/obj/item/sample/fibers/mechanics_hints(mob/user, distance, is_adjacent)
. += ..()
. += "Holds various fibre evidence. Place it in a slide and the slide into a microscope to check them."
/obj/item/sample/print
name = "fingerprint card"
desc = "Records a set of fingerprints."
icon = 'icons/obj/card.dmi'
icon_state = "fingerprint0"
item_state = "paper"
/obj/item/sample/print/mechanics_hints(mob/user, distance, is_adjacent)
. += ..()
. += "A sample card for fingerprints. Risks putting your own prints on it if touched without gloves."
. += "Place the card in a microscope to examine the contents."
. += "Use in hand to put your prints on it."
. += "Target hands and click another creature to take their prints."
/obj/item/sample/print/attack_self(var/mob/user)
if(LAZYLEN(evidence))
return
if(!ishuman(user))
return
var/mob/living/carbon/human/H = user
if(H.gloves)
to_chat(user, SPAN_WARNING("Take \the [H.gloves] off first."))
return
to_chat(user, SPAN_NOTICE("You firmly press your fingertips onto the card."))
var/fullprint = H.get_full_print()
LAZYSET(evidence, fullprint, fullprint)
LAZYADD(source, "[H.name]")
name = "[initial(name)] (\the [H])"
icon_state = "fingerprint1"
/obj/item/sample/print/attack(mob/living/target_mob, mob/living/user, target_zone)
var/mob/living/carbon/human/H = target_mob
if(!istype(H))
return ..()
if(LAZYLEN(evidence))
return 0
if(H.gloves)
to_chat(user, SPAN_WARNING("\The [H] is wearing gloves."))
return 1
if(user != H && H.a_intent != "help" && !H.lying)
user.visible_message(SPAN_DANGER("\The [user] tries to take prints from \the [H], but they move away."))
return 1
if(target_zone == BP_R_HAND || target_zone == BP_L_HAND)
var/has_hand
var/obj/item/organ/external/O = H.organs_by_name[BP_R_HAND]
if(istype(O) && !O.is_stump())
has_hand = 1
else
O = H.organs_by_name[BP_L_HAND]
if(istype(O) && !O.is_stump())
has_hand = 1
if(!has_hand)
to_chat(user, SPAN_WARNING("They don't have any hands."))
return 1
user.visible_message("[user] takes a copy of \the [H]'s fingerprints.")
var/fullprint = H.get_full_print()
LAZYSET(evidence, fullprint, fullprint)
copy_evidence(src)
LAZYADD(source, "[H.name]")
name = "[initial(name)] (\the [H])"
icon_state = "fingerprint1"
return 1
return 0
/obj/item/sample/print/copy_evidence(var/atom/supplied)
if(supplied.fingerprints && supplied.fingerprints.len)
for(var/print in supplied.fingerprints)
LAZYSET(evidence, print, supplied.fingerprints[print])
supplied.fingerprints.Cut()
/obj/item/forensics/sample_kit
name = "fiber collection kit"
desc = "A magnifying glass and tweezers. Used to lift suit fibers."
icon_state = "m_glass"
w_class = WEIGHT_CLASS_SMALL
item_flags = ITEM_FLAG_NO_BLUDGEON
var/evidence_type = "fiber"
var/evidence_path = /obj/item/sample/fibers
/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)
/obj/item/forensics/sample_kit/proc/take_sample(var/mob/user, var/atom/supplied)
var/obj/item/sample/S = new evidence_path(get_turf(user), supplied)
to_chat(user, SPAN_NOTICE("You transfer [S.evidence.len] [evidence_type]\s to \the [S]."))
/obj/item/forensics/sample_kit/afterattack(var/atom/A, var/mob/user, var/proximity)
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
else
to_chat(user, SPAN_WARNING("You are unable to locate any [evidence_type]s on \the [A]."))
return ..()
/obj/item/forensics/sample_kit/resolve_attackby(atom/A, mob/user, var/click_parameters)
if(user.a_intent != I_HELP)
return FALSE
. = ..()
/obj/item/forensics/sample_kit/mouse_drop_dragged(atom/over, mob/user, src_location, over_location, params)
var/mob/M = loc
if(ismob(M) && (M.get_active_hand() == src || M.get_inactive_hand() == src))
afterattack(over, user, TRUE)
/obj/item/forensics/sample_kit/powder
name = "fingerprint powder"
desc = "A jar containing aluminum powder and a specialized brush."
icon_state = "dust"
evidence_type = "fingerprint"
evidence_path = /obj/item/sample/print
/obj/item/forensics/sample_kit/powder/can_take_sample(var/mob/user, var/atom/supplied)
return (supplied.fingerprints && supplied.fingerprints.len)