Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add two more ghost scanners, refactors and improves the handheld genetics scanner #12187

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions code/_onclick/observer.dm
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,22 @@
if(user.client)
if(user.gas_scan && atmosanalyzer_scan(user, src))
return TRUE
//additional arguments are here due to how extrapolators work
if(user.virus_scan && virusscan(user, src, 10, 10, list()))
return TRUE
else if(IsAdminGhost(user))
attack_ai(user)
else if(user.client.prefs.read_player_preference(/datum/preference/toggle/inquisitive_ghost))
user.examinate(src)
return FALSE

/mob/living/attack_ghost(mob/dead/observer/user)
if(user.client && user.health_scan)
healthscan(user, src, 1, TRUE)
chemscan(user, src, 1, TRUE)
if(user.client)
if(user.health_scan)
healthscan(user, src, 1, TRUE)
chemscan(user, src, 1, TRUE)
if(user.genetics_scan)
genescan(src, user)
return ..()

// ---------------------------------------
Expand Down
146 changes: 89 additions & 57 deletions code/game/objects/items/devices/scanners.dm
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,91 @@ GENE SCANNER
else
return(jointext(message, "\n"))

/**
* Scans an atom, showing any (detectable) diseases they may have.
*/
/proc/virusscan(mob/user, atom/target, var/maximum_stealth, var/maximum, var/list/extracted_ids)
. = TRUE
var/list/result = target?.extrapolator_act(user, target)
var/list/diseases = result[EXTRAPOLATOR_RESULT_DISEASES]
if(!length(diseases))
return FALSE
if(EXTRAPOLATOR_ACT_CHECK(result, EXTRAPOLATOR_ACT_PRIORITY_SPECIAL))
return
var/list/message = list()
if(length(diseases))
// costly_icon2html should be okay, as the extrapolator has a cooldown and is NOT spammable
message += span_noticebold("[costly_icon2html(target, user)] [target] scan results]")
for(var/datum/disease/disease in diseases)
if(istype(disease, /datum/disease/advance))
var/datum/disease/advance/advance_disease = disease
if(advance_disease.stealth >= maximum_stealth) //the extrapolator can detect diseases of higher stealth than a normal scanner
continue
var/list/properties
if(!advance_disease.mutable)
LAZYADD(properties, "immutable")
if(advance_disease.faltered)
LAZYADD(properties, "faltered")
if(advance_disease.carrier)
LAZYADD(properties, "carrier")
message += span_info("<b>[advance_disease.name]</b>[LAZYLEN(properties) ? " ([properties.Join(", ")])" : ""], [advance_disease.dormant ? "<i>dormant virus</i>" : "stage [advance_disease.stage]/5"]")
if(extracted_ids[advance_disease.GetDiseaseID()])
message += span_infoitalics("This virus has been extracted previously.")
message += span_infobold("[advance_disease.name] has the following symptoms:")
for(var/datum/symptom/symptom in advance_disease.symptoms)
message += "[symptom.name]"
else
message += span_info("<b>[disease.name]</b>, stage [disease.stage]/[disease.max_stages].")
to_chat(user, EXAMINE_BLOCK(jointext(message, "\n")), avoid_highlighting = TRUE, trailing_newline = FALSE, type = MESSAGE_TYPE_INFO)

/proc/genescan(mob/living/carbon/C, mob/user, list/discovered)
. = TRUE
if(!iscarbon(C) || !C.has_dna())
return FALSE
if(HAS_TRAIT(C, TRAIT_RADIMMUNE) || HAS_TRAIT(C, TRAIT_BADDNA))
return FALSE
var/list/message = list()
var/list/active_inherent_muts = list()
var/list/active_injected_muts = list()
var/list/inherent_muts = list()
var/list/mut_index = C.dna.mutation_index.Copy()

for(var/datum/mutation/each in C.dna.mutations)
//get name and alias if discovered (or no discovered list was provided) or just alias if not
var/datum/mutation/each_mutation = GET_INITIALIZED_MUTATION(each.type) //have to do this as instances of mutation do not have alias but global ones do....
var/each_mut_details = "ERROR"
if(!discovered || (each_mutation.type in discovered))
each_mut_details = span_info("[each_mutation.name] ([each_mutation.alias])")
else
each_mut_details = span_info("[each_mutation.alias]")

if(each_mutation.type in mut_index)
//add mutation readout for all active inherent mutations
active_inherent_muts += "[each_mut_details][span_infobold(" : Active ")]"
mut_index -= each_mutation.type
else
//add mutation readout for all injected (not inherent) mutations
active_injected_muts += each_mut_details

for(var/each in mut_index)
var/datum/mutation/each_mutation = GET_INITIALIZED_MUTATION(each)
var/each_mut_details = "ERROR"
if(each_mutation)
//repeating this code twice is nasty, but nested procs (if even possible??) or more global procs then needed is... less so
if(!discovered || (each_mutation.type in discovered))
each_mut_details = span_info("[each_mutation.name] ([each_mutation.alias])")
else
each_mut_details = span_info("[each_mutation.alias]")
inherent_muts += each_mut_details

message += span_noticebold("[C] scan results")
active_inherent_muts.len > 0 ? (message += "[jointext(active_inherent_muts, "\n")]") : ""
inherent_muts.len > 0 ? (message += "[jointext(inherent_muts, "\n")]") : ""
active_injected_muts.len > 0 ? (message += "[span_infobold("Injected mutations:\n")][jointext(active_injected_muts, "\n")]") : ""

to_chat(user, EXAMINE_BLOCK(jointext(message, "\n")), avoid_highlighting = TRUE, trailing_newline = FALSE, type = MESSAGE_TYPE_INFO)


/obj/item/healthanalyzer/advanced
name = "advanced health analyzer"
icon_state = "health_adv"
Expand Down Expand Up @@ -933,25 +1018,8 @@ GENE SCANNER
if(!iscarbon(C) || !C.has_dna())
return
buffer = C.dna.mutation_index
to_chat(user, span_notice("Subject [C.name]'s DNA sequence has been saved to buffer."))

var/list/full_list_mutations = list()
for(var/each in buffer) // get inherent mutations first
full_list_mutations[each] = FALSE
for(var/datum/mutation/each_mutation in C.dna.mutations)
if(each_mutation.type in buffer) // active inherent mutation
full_list_mutations[each_mutation.type] = "Activated"
else // active artificial mutation
full_list_mutations[each_mutation.type] = "Injected"
for(var/datum/mutation/each_mutation in C.dna.temporary_mutations)
full_list_mutations[each_mutation.type] = "Temporary"

for(var/A in full_list_mutations)
to_chat(user, "\t[span_notice("[get_display_name(A)]")]") // if you want to make the scanner tell which mutation is active, put "full_list_mutations[A]" to the second parameter of get_display_name() proc.
to_chat(user, "\t[span_info("Genetic Stability: [C.dna.stability]%.")]")
if(C.has_status_effect(STATUS_EFFECT_LING_TRANSFORMATION))
to_chat(user, "\t[span_info("Subject's DNA appears to be in an unstable state.")]")

to_chat(user, "<span class='notice'>Subject [C.name]'s DNA sequence has been saved to buffer.</span>")
genescan(C, user, discovered)

/obj/item/sequence_scanner/proc/display_sequence(mob/living/user)
if(!LAZYLEN(buffer) || !ready)
Expand Down Expand Up @@ -1132,7 +1200,7 @@ CREATION_TEST_IGNORE_SUBTYPES(/obj/item/extrapolator)
// extrapolator_act did some sort of special behavior, we don't need to do anything further
return
if(scan)
scan(user, target)
virusscan(user, target, maximum_stealth, maximum_level, extracted_ids)
else
extrapolate(user, target)
else
Expand All @@ -1148,43 +1216,7 @@ CREATION_TEST_IGNORE_SUBTYPES(/obj/item/extrapolator)
if(length(result[EXTRAPOLATOR_RESULT_DISEASES]))
. += target_to_try

/**
* Scans an atom, showing any (detectable) diseases they may have.
*/
/obj/item/extrapolator/proc/scan(mob/living/user, atom/target)
. = TRUE
var/list/result = target?.extrapolator_act(user, target)
var/list/diseases = result[EXTRAPOLATOR_RESULT_DISEASES]
if(!length(diseases))
return FALSE
if(EXTRAPOLATOR_ACT_CHECK(result, EXTRAPOLATOR_ACT_PRIORITY_SPECIAL))
return
var/list/message = list()
if(length(diseases))
// costly_icon2html should be okay, as the extrapolator has a cooldown and is NOT spammable
message += span_boldnotice("[costly_icon2html(target, user)] [target] scan results")
message += span_boldnotice("[icon2html(src, user)] \The [src] detects the following diseases:")
for(var/datum/disease/disease in diseases)
if(istype(disease, /datum/disease/advance))
var/datum/disease/advance/advance_disease = disease
if(advance_disease.stealth >= maximum_stealth) //the extrapolator can detect diseases of higher stealth than a normal scanner
continue
var/list/properties
if(!advance_disease.mutable)
LAZYADD(properties, "immutable")
if(advance_disease.faltered)
LAZYADD(properties, "faltered")
if(advance_disease.carrier)
LAZYADD(properties, "carrier")
message += span_info("<b>[advance_disease.name]</b>[LAZYLEN(properties) ? " ([properties.Join(", ")])" : ""], [advance_disease.dormant ? "<i>dormant virus</i>" : "stage [advance_disease.stage]/5"]")
if(extracted_ids[advance_disease.GetDiseaseID()])
message += span_infoitalics("This virus has been extracted by \the [src] previously.")
message += span_infobold("[advance_disease.name] has the following symptoms:")
for(var/datum/symptom/symptom in advance_disease.symptoms)
message += "[symptom.name]"
else
message += span_info("<b>[disease.name]</b>, stage [disease.stage]/[disease.max_stages].")
to_chat(user, EXAMINE_BLOCK(jointext(message, "\n")), avoid_highlighting = TRUE, trailing_newline = FALSE, type = MESSAGE_TYPE_INFO)


/**
* Attempts to either extract a disease from an atom, or isolate a symptom from an advance disease.
Expand Down
26 changes: 26 additions & 0 deletions code/modules/mob/dead/observer/observer.dm
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_SPIRIT)
var/ai_hud_on = FALSE //Is the AI Viewrange HUD currently enabled?
var/health_scan = FALSE //Are health scans currently enabled?
var/gas_scan = FALSE //Are gas scans currently enabled?
var/virus_scan = FALSE //Are virus extrapolator scans currently enabled?
var/genetics_scan = FALSE //Are genetic scans currently enabled?
var/list/datahuds = list(DATA_HUD_SECURITY_ADVANCED, DATA_HUD_MEDICAL_ADVANCED, DATA_HUD_DIAGNOSTIC_ADVANCED) //list of data HUDs shown to ghosts.
var/ghost_orbit = GHOST_ORBIT_CIRCLE

Expand Down Expand Up @@ -784,6 +786,30 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
to_chat(src, span_notice("Gas scan enabled."))
gas_scan = TRUE

/mob/dead/observer/verb/toggle_virus_scan()
set name = "Toggle Virus Scan"
set desc = "Toggles whether you scan for viruses on click"
set category = "Ghost"

if(virus_scan)
to_chat(src, span_notice("Virus scan disabled."))
virus_scan = FALSE
else
to_chat(src, span_notice("Virus scan enabled."))
virus_scan = TRUE

/mob/dead/observer/verb/toggle_genetics_scan()
set name = "Toggle Genetics Scan"
set desc = "Toggles whether you can scan the genetics of a living beings on click"
set category = "Ghost"

if(genetics_scan)
to_chat(src, span_notice("Genetics scan disabled."))
genetics_scan = FALSE
else
to_chat(src, span_notice("Genetics scan enabled."))
genetics_scan = TRUE

/mob/dead/observer/verb/restore_ghost_appearance()
set name = "Restore Ghost Character"
set desc = "Sets your deadchat name and ghost appearance to your \
Expand Down
Loading