Files
Bubberstation/code/modules/detectivework/scanner.dm
Timberpoes 75a3717a04 Modifies right click logic so that it is not the same priority as modifier keys. (#59656)
Strips out the existing right click code - Due to the myriad of ways right clicking has been implemented, dedicated signals and procs for right clicking without modifiers are fundamentally incompatible with our system of primary and secondary attacks.

Adds additional signals to attacking code. These signals allow atoms to cancel the attack chain early on secondary attacks, or override the standard procs and not send signals to prevent any undesired behaviour from signal handlers.

Items that used RightClick procs have been converted to attack_hand_secondary.

The slaughter demon, having its own set of snowflake code as poor OOP principles have been applied in UnarmedAttack() procs with lacking calls to parent procs and arbitrary redefinition of behaviour, checks for a right click in its own UnarmedAttack() and performs a bodyslam off that.

Storage components now hijack the secondary attackby stage via signals to handle their opening and closing shortcuts on right click. When you right click a storage component equipped item with an object in your active hand, the object has an opportunity to perform its logic in pre secondary attack code and cancel the attack chain. If it does not cancel the attack chain in pre-attack, then the storage component takes over for attackby and, if possible, opens the relevant inventory and ends the attack chain.

The forensic scanner is a proof-of-concept of this working in action. With its scan logic moved from afterattack code to pre attack code for right clicking, right clicking with the scanner will now perform a scan where previously one was impossible. Left clicking still does what it always does - Scans at the very end of the attack chain.

The logic still isn't perfect - For example, you still can't attack containers in melee even in combat mode (you'll either open them or put your weapon into them regardless of which option you choose) - But this is a better setup overall which allows for items to at least override this behaviour in pre-attack if needed.
2021-06-24 14:30:33 -03:00

217 lines
6.5 KiB
Plaintext

//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."
icon = 'icons/obj/device.dmi'
icon_state = "forensicnew"
w_class = WEIGHT_CLASS_SMALL
inhand_icon_state = "electronic"
worn_icon_state = "electronic"
lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
flags_1 = CONDUCT_1
item_flags = NOBLUDGEON
slot_flags = ITEM_SLOT_BELT
var/scanning = FALSE
var/list/log = 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()
var/obj/item/detective_scanner/scanner = target
if(istype(scanner))
scanner.displayDetectiveScanResults(usr)
/obj/item/detective_scanner/attack_self(mob/user)
if(log.len && !scanning)
scanning = TRUE
to_chat(user, span_notice("Printing report, please wait..."))
addtimer(CALLBACK(src, .proc/PrintReport), 100)
else
to_chat(user, span_notice("The scanner has no logs or is in use."))
/obj/item/detective_scanner/proc/PrintReport()
// Create our paper
var/obj/item/paper/P = new(get_turf(src))
//This could be a global count like sec and med record printouts. See GLOB.data_core.medicalPrintCount AKA datacore.dm
var/frNum = ++forensicPrintCount
P.name = text("FR-[] 'Forensic Record'", frNum)
P.info = text("<center><B>Forensic Record - (FR-[])</B></center><HR><BR>", frNum)
P.info += jointext(log, "<BR>")
P.info += "<HR><B>Notes:</B><BR>"
P.update_appearance()
if(ismob(loc))
var/mob/M = loc
M.put_in_hands(P)
to_chat(M, span_notice("Report printed. Log cleared."))
// Clear the logs
log = list()
scanning = FALSE
/obj/item/detective_scanner/pre_attack_secondary(atom/A, mob/user, params)
scan(A, user)
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
/obj/item/detective_scanner/afterattack(atom/A, mob/user, params)
. = ..()
scan(A, user)
return FALSE
/obj/item/detective_scanner/proc/scan(atom/A, mob/user)
set waitfor = FALSE
if(!scanning)
// Can remotely scan objects and mobs.
if((get_dist(A, user) > range) || (!(A in view(range, user)) && view_check) || (loc != user))
return
scanning = TRUE
user.visible_message(span_notice("\The [user] points the [src.name] at \the [A] and performs a forensic scan."))
to_chat(user, span_notice("You scan \the [A]. The scanner is now analysing the results..."))
// GATHER INFORMATION
//Make our lists
var/list/fingerprints = list()
var/list/blood = A.return_blood_DNA()
var/list/fibers = A.return_fibers()
var/list/reagents = list()
var/target_name = A.name
// Start gathering
if(ishuman(A))
var/mob/living/carbon/human/H = A
if(!H.gloves)
fingerprints += md5(H.dna.unique_identity)
else if(!ismob(A))
fingerprints = A.return_fingerprints()
// Only get reagents from non-mobs.
if(A.reagents && A.reagents.reagent_list.len)
for(var/datum/reagent/R in A.reagents.reagent_list)
reagents[R.name] = R.volume
// Get blood data from the blood reagent.
if(istype(R, /datum/reagent/blood))
if(R.data["blood_DNA"] && R.data["blood_type"])
var/blood_DNA = R.data["blood_DNA"]
var/blood_type = R.data["blood_type"]
LAZYINITLIST(blood)
blood[blood_DNA] = blood_type
// We gathered everything. Create a fork and slowly display the results to the holder of the scanner.
var/found_something = FALSE
add_log("<B>[station_time_timestamp()][get_timestamp()] - [target_name]</B>", 0)
// Fingerprints
if(length(fingerprints))
sleep(30)
add_log(span_info("<B>Prints:</B>"))
for(var/finger in fingerprints)
add_log("[finger]")
found_something = TRUE
// Blood
if (length(blood))
sleep(30)
add_log(span_info("<B>Blood:</B>"))
found_something = TRUE
for(var/B in blood)
add_log("Type: <font color='red'>[blood[B]]</font> DNA (UE): <font color='red'>[B]</font>")
//Fibers
if(length(fibers))
sleep(30)
add_log(span_info("<B>Fibers:</B>"))
for(var/fiber in fibers)
add_log("[fiber]")
found_something = TRUE
//Reagents
if(length(reagents))
sleep(30)
add_log(span_info("<B>Reagents:</B>"))
for(var/R in reagents)
add_log("Reagent: <font color='red'>[R]</font> Volume: <font color='red'>[reagents[R]]</font>")
found_something = TRUE
// Get a new user
var/mob/holder = null
if(ismob(src.loc))
holder = src.loc
if(!found_something)
add_log("<I># No forensic traces found #</I>", 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)
scanning = FALSE
return
/obj/item/detective_scanner/proc/add_log(msg, broadcast = 1)
if(scanning)
if(broadcast && ismob(loc))
var/mob/M = loc
to_chat(M, msg)
log += "&nbsp;&nbsp;[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/AltClick(mob/living/user)
// Best way for checking if a player can use while not incapacitated, etc
if(!user.canUseTopic(src, be_close=TRUE))
return
if(!LAZYLEN(log))
to_chat(user, span_notice("Cannot clear logs, the scanner has no logs."))
return
if(scanning)
to_chat(user, span_notice("Cannot clear logs, the scanner is in use."))
return
to_chat(user, span_notice("The scanner logs are cleared."))
log = list()
/obj/item/detective_scanner/examine(mob/user)
. = ..()
if(LAZYLEN(log) && !scanning)
. += span_notice("Alt-click to clear scanner logs.")
/obj/item/detective_scanner/proc/displayDetectiveScanResults(mob/living/user)
// No need for can-use checks since the action button should do proper checks
if(!LAZYLEN(log))
to_chat(user, span_notice("Cannot display logs, the scanner has no logs."))
return
if(scanning)
to_chat(user, span_notice("Cannot display logs, the scanner is in use."))
return
to_chat(user, span_notice("<B>Scanner Report</B>"))
for(var/iterLog in log)
to_chat(user, iterLog)