From 2c11b68f2fdb16d7cab83eab0ac0e5276482a4bb Mon Sep 17 00:00:00 2001
From: kevinz000 <2003111+kevinz000@users.noreply.github.com>
Date: Tue, 8 Oct 2019 00:33:21 -0700
Subject: [PATCH] Fixes chemscan
---
code/_globalvars/bitfields.dm | 2 -
code/game/objects/items/devices/scanners.dm | 1683 +++++++++--------
.../reagents/chemistry/fermi/readme.md | 23 +
3 files changed, 865 insertions(+), 843 deletions(-)
create mode 100644 code/modules/reagents/chemistry/fermi/readme.md
diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm
index f0b16b9d..8823579b 100644
--- a/code/_globalvars/bitfields.dm
+++ b/code/_globalvars/bitfields.dm
@@ -182,7 +182,6 @@ GLOBAL_LIST_INIT(bitfields, list(
"CAN_CARRY" = CAN_CARRY,
"CAN_RESIST" = CAN_RESIST
),
-
"chemical_flags" = list(
"REAGENT_DEAD_PROCESS" = REAGENT_DEAD_PROCESS,
"REAGENT_DONOTSPLIT" = REAGENT_DONOTSPLIT,
@@ -197,7 +196,6 @@ GLOBAL_LIST_INIT(bitfields, list(
"REACTION_CLEAR_IMPURE" = REACTION_CLEAR_IMPURE,
"REACTION_CLEAR_INVERSE" = REACTION_CLEAR_INVERSE
),
-
"organ_flags" = list(
"ORGAN_SYNTHETIC" = ORGAN_SYNTHETIC,
"ORGAN_FROZEN" = ORGAN_FROZEN,
diff --git a/code/game/objects/items/devices/scanners.dm b/code/game/objects/items/devices/scanners.dm
index 4a8a8018..4fdcca94 100644
--- a/code/game/objects/items/devices/scanners.dm
+++ b/code/game/objects/items/devices/scanners.dm
@@ -1,841 +1,842 @@
-
-/*
-
-CONTAINS:
-T-RAY
-HEALTH ANALYZER
-GAS ANALYZER
-SLIME SCANNER
-NANITE SCANNER
-GENE SCANNER
-
-*/
-/obj/item/t_scanner
- name = "\improper T-ray scanner"
- desc = "A terahertz-ray emitter and scanner used to detect underfloor objects such as cables and pipes."
- icon = 'icons/obj/device.dmi'
- icon_state = "t-ray0"
- var/on = FALSE
- slot_flags = ITEM_SLOT_BELT
- w_class = WEIGHT_CLASS_SMALL
- item_state = "electronic"
- lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
- materials = list(MAT_METAL=150)
-
-/obj/item/t_scanner/suicide_act(mob/living/carbon/user)
- user.visible_message("[user] begins to emit terahertz-rays into [user.p_their()] brain with [src]! It looks like [user.p_theyre()] trying to commit suicide!")
- return TOXLOSS
-
-/obj/item/t_scanner/attack_self(mob/user)
-
- on = !on
- icon_state = copytext(icon_state, 1, length(icon_state))+"[on]"
-
- if(on)
- START_PROCESSING(SSobj, src)
-
-/obj/item/t_scanner/process()
- if(!on)
- STOP_PROCESSING(SSobj, src)
- return null
- scan()
-
-/obj/item/t_scanner/proc/scan()
- t_ray_scan(loc)
-
-/proc/t_ray_scan(mob/viewer, flick_time = 8, distance = 3)
- if(!ismob(viewer) || !viewer.client)
- return
- var/list/t_ray_images = list()
- for(var/obj/O in orange(distance, viewer) )
- if(O.level != 1)
- continue
-
- if(O.invisibility == INVISIBILITY_MAXIMUM)
- var/image/I = new(loc = get_turf(O))
- var/mutable_appearance/MA = new(O)
- MA.alpha = 128
- MA.dir = O.dir
- I.appearance = MA
- t_ray_images += I
- if(t_ray_images.len)
- flick_overlay(t_ray_images, list(viewer.client), flick_time)
-
-/obj/item/healthanalyzer
- name = "health analyzer"
- icon = 'icons/obj/device.dmi'
- icon_state = "health"
- item_state = "healthanalyzer"
- lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
- desc = "A hand-held body scanner able to distinguish vital signs of the subject."
- flags_1 = CONDUCT_1
- item_flags = NOBLUDGEON
- slot_flags = ITEM_SLOT_BELT
- throwforce = 3
- w_class = WEIGHT_CLASS_TINY
- throw_speed = 3
- throw_range = 7
- materials = list(MAT_METAL=200)
- var/mode = 1
- var/scanmode = 0
- var/advanced = FALSE
-
-/obj/item/healthanalyzer/suicide_act(mob/living/carbon/user)
- user.visible_message("[user] begins to analyze [user.p_them()]self with [src]! The display shows that [user.p_theyre()] dead!")
- return BRUTELOSS
-
-/obj/item/healthanalyzer/attack_self(mob/user)
- if(!scanmode)
- to_chat(user, "You switch the health analyzer to scan chemical contents.")
- scanmode = 1
- else
- to_chat(user, "You switch the health analyzer to check physical health.")
- scanmode = 0
-
-/obj/item/healthanalyzer/attack(mob/living/M, mob/living/carbon/human/user)
-
- // Clumsiness/brain damage check
- if ((HAS_TRAIT(user, TRAIT_CLUMSY) || HAS_TRAIT(user, TRAIT_DUMB)) && prob(50))
- to_chat(user, "You stupidly try to analyze the floor's vitals!")
- user.visible_message("[user] has analyzed the floor's vitals!")
- var/msg = "*---------*\nAnalyzing results for The floor:\n\tOverall status: Healthy\n"
- msg += "Key: Suffocation/Toxin/Burn/Brute\n"
- msg += "\tDamage specifics: 0-0-0-0\n"
- msg += "Body temperature: ???\n"
- msg += "*---------*"
- to_chat(user, msg)
- return
-
- user.visible_message("[user] has analyzed [M]'s vitals.")
-
- if(scanmode == 0)
- healthscan(user, M, mode, advanced)
- else if(scanmode == 1)
- chemscan(user, M)
-
- add_fingerprint(user)
-
-
-// Used by the PDA medical scanner too
-/proc/healthscan(mob/user, mob/living/M, mode = 1, advanced = FALSE)
- if(isliving(user) && (user.incapacitated() || user.eye_blind))
- return
- //Damage specifics
- var/oxy_loss = M.getOxyLoss()
- var/tox_loss = M.getToxLoss()
- var/fire_loss = M.getFireLoss()
- var/brute_loss = M.getBruteLoss()
- var/mob_status = (M.stat == DEAD ? "Deceased" : "[round(M.health/M.maxHealth,0.01)*100] % healthy")
-
- if(HAS_TRAIT(M, TRAIT_FAKEDEATH) && !advanced)
- mob_status = "Deceased"
- oxy_loss = max(rand(1, 40), oxy_loss, (300 - (tox_loss + fire_loss + brute_loss))) // Random oxygen loss
-
- if(ishuman(M))
- var/mob/living/carbon/human/H = M
- if(H.undergoing_cardiac_arrest() && H.stat != DEAD)
- to_chat(user, "Subject suffering from heart attack: Apply defibrillation or other electric shock immediately!")
- if(H.undergoing_liver_failure() && H.stat != DEAD) //might be depreciated BUG_PROBABLE_CAUSE
- to_chat(user, "Subject is suffering from liver failure: Apply Corazone and begin a liver transplant immediately!")
-
- var/msg = "*---------*\nAnalyzing results for [M]:\n\tOverall status: [mob_status]\n"
-
- // Damage descriptions
- if(brute_loss > 10)
- msg += "\t[brute_loss > 50 ? "Severe" : "Minor"] tissue damage detected.\n"
- if(fire_loss > 10)
- msg += "\t[fire_loss > 50 ? "Severe" : "Minor"] burn damage detected.\n"
- if(oxy_loss > 10)
- msg += "\t[oxy_loss > 50 ? "Severe" : "Minor"] oxygen deprivation detected.\n"
- if(tox_loss > 10)
- msg += "\t[tox_loss > 50 ? "Severe" : "Minor"] amount of toxin damage detected.\n"
- if(M.getStaminaLoss())
- msg += "\tSubject appears to be suffering from fatigue.\n"
- if(advanced)
- msg += "\tFatigue Level: [M.getStaminaLoss()]%.\n"
- if (M.getCloneLoss())
- msg += "\tSubject appears to have [M.getCloneLoss() > 30 ? "Severe" : "Minor"] cellular damage.\n"
- if(advanced)
- msg += "\tCellular Damage Level: [M.getCloneLoss()].\n"
- if (!M.getorgan(/obj/item/organ/brain))
- to_chat(user, "\tSubject lacks a brain.") //Unsure how this won't proc for 50% of the cit playerbase (This is a joke everyone on cit a cute.)
- if(ishuman(M) && advanced) // Should I make this not advanced?
- var/mob/living/carbon/human/H = M
- var/obj/item/organ/liver/L = H.getorganslot("liver")
- if(L)
- if(L.swelling > 20)
- msg += "\tSubject is suffering from an enlarged liver.\n" //i.e. shrink their liver or give them a transplant.
- else
- msg += "\tSubject's liver is missing.\n"
- var/obj/item/organ/tongue/T = H.getorganslot("tongue")
- if(T)
- if(T.damage > 40)
- msg += "\tSubject is suffering from severe burn tissue on their tongue.\n" //i.e. their tongue is shot
- if(T.name == "fluffy tongue")
- msg += "\tSubject is suffering from a fluffified tongue. Suggested cure: Yamerol or a tongue transplant.\n"
- else
- msg += "\tSubject's tongue is missing.\n"
- var/obj/item/organ/lungs/Lung = H.getorganslot("lungs")
- if(Lung)
- if(Lung.damage > 150)
- msg += "\tSubject is suffering from acute emphysema leading to trouble breathing.\n" //i.e. Their lungs are shot
- else
- msg += "\tSubject's lungs have collapsed from trauma!\n"
- var/obj/item/organ/genital/penis/P = H.getorganslot("penis")
- if(P)
- if(P.length>20)
- msg += "\tSubject has a sizeable gentleman's organ at [P.length] inches.\n"
- var/obj/item/organ/genital/breasts/Br = H.getorganslot("breasts")
- if(Br)
- if(Br.cached_size>5)
- msg += "\tSubject has a sizeable bosom with a [Br.size] cup.\n"
- if (M.getOrganLoss(ORGAN_SLOT_BRAIN) >= 200 || !M.getorgan(/obj/item/organ/brain))
- msg += "\tSubject's brain function is non-existent.\n"
- else if (M.getOrganLoss(ORGAN_SLOT_BRAIN) >= 120)
- msg += "\tSevere brain damage detected. Subject likely to have mental traumas.\n"
- else if (M.getOrganLoss(ORGAN_SLOT_BRAIN) >= 45)
- msg += "\tBrain damage detected.\n"
-
- if(iscarbon(M))
-
- var/mob/living/carbon/C = M
- if(LAZYLEN(C.get_traumas()))
- var/list/trauma_text = list()
- for(var/datum/brain_trauma/B in C.get_traumas())
- var/trauma_desc = ""
- switch(B.resilience)
- if(TRAUMA_RESILIENCE_SURGERY)
- trauma_desc += "severe "
- if(TRAUMA_RESILIENCE_LOBOTOMY)
- trauma_desc += "deep-rooted "
- if(TRAUMA_RESILIENCE_MAGIC, TRAUMA_RESILIENCE_ABSOLUTE)
- trauma_desc += "permanent "
- trauma_desc += B.scan_desc
- trauma_text += trauma_desc
- msg += "\tCerebral traumas detected: subject appears to be suffering from [english_list(trauma_text)].\n"
- if(C.roundstart_quirks.len)
- msg += "\tSubject has the following physiological traits: [C.get_trait_string()].\n"
- if(advanced)
- msg += "\tBrain Activity Level: [(200 - M.getOrganLoss(ORGAN_SLOT_BRAIN))/2]%.\n"
- if(M.radiation)
- msg += "\tSubject is irradiated.\n"
- if(advanced)
- msg += "\tRadiation Level: [M.radiation]%.\n"
-
- if(advanced && M.hallucinating())
- msg += "\tSubject is hallucinating.\n"
-
- //MKUltra
- if(advanced && M.has_status_effect(/datum/status_effect/chem/enthrall))
- msg += "\tSubject has abnormal brain fuctions.\n"
-
- //Astrogen shenanigans
- if(advanced && M.reagents.has_reagent("astral"))
- if(M.mind)
- msg += "\tWarning: subject may be possesed.\n"
- else
- msg += "\tSubject appears to be astrally projecting.\n"
-
- //Eyes and ears
- if(advanced)
- if(iscarbon(M))
- var/mob/living/carbon/C = M
- var/obj/item/organ/ears/ears = C.getorganslot(ORGAN_SLOT_EARS)
- msg += "\t==EAR STATUS==\n"
- if(istype(ears))
- var/healthy = TRUE
- if(HAS_TRAIT_FROM(C, TRAIT_DEAF, GENETIC_MUTATION))
- healthy = FALSE
- msg += "\tSubject is genetically deaf.\n"
- else if(HAS_TRAIT(C, TRAIT_DEAF))
- healthy = FALSE
- msg += "\tSubject is deaf.\n"
- else
- if(ears.damage)
- to_chat(user, "\tSubject has [ears.damage > ears.maxHealth ? "permanent ": "temporary "]hearing damage.")
- healthy = FALSE
- if(ears.deaf)
- to_chat(user, "\tSubject is [ears.damage > ears.maxHealth ? "permanently ": "temporarily "] deaf.")
- healthy = FALSE
- if(healthy)
- msg += "\tHealthy.\n"
- else
- msg += "\tSubject does not have ears.\n"
- var/obj/item/organ/eyes/eyes = C.getorganslot(ORGAN_SLOT_EYES)
- msg += "\t==EYE STATUS==\n"
- if(istype(eyes))
- var/healthy = TRUE
- if(HAS_TRAIT(C, TRAIT_BLIND))
- msg += "\tSubject is blind.\n"
- healthy = FALSE
- if(HAS_TRAIT(C, TRAIT_NEARSIGHT))
- msg += "\tSubject is nearsighted.\n"
- healthy = FALSE
- if(eyes.damage > 30)
- msg += "\tSubject has severe eye damage.\n"
- healthy = FALSE
- else if(eyes.damage > 20)
- msg += "\tSubject has significant eye damage.\n"
- healthy = FALSE
- else if(eyes.damage)
- msg += "\tSubject has minor eye damage.\n"
- healthy = FALSE
- if(healthy)
- msg += "\tHealthy.\n"
- else
- msg += "\tSubject does not have eyes.\n"
-
-
- if(ishuman(M))
- var/mob/living/carbon/human/H = M
- var/ldamage = H.return_liver_damage()
- if(ldamage > 10)
- msg += "\t[ldamage > 45 ? "Severe" : "Minor"] liver damage detected.\n"
- if(advanced && H.has_dna())
- to_chat(user, "\tGenetic Stability: [H.dna.stability]%.")
- // Body part damage report
- if(iscarbon(M) && mode == 1)
- var/mob/living/carbon/C = M
- var/list/damaged = C.get_damaged_bodyparts(1,1)
- if(length(damaged)>0 || oxy_loss>0 || tox_loss>0 || fire_loss>0)
- msg += "\tDamage: Brute-Burn-Toxin-Suffocation\n\t\tSpecifics: [brute_loss]-[fire_loss]-[tox_loss]-[oxy_loss]\n"
- for(var/obj/item/bodypart/org in damaged)
- msg += "\t\t[capitalize(org.name)]: [(org.brute_dam > 0) ? "[org.brute_dam]" : "0"]-[(org.burn_dam > 0) ? "[org.burn_dam]" : "0"]\n"
-
- //Bones broken report! Hyperstation 13
- if(iscarbon(M) && mode == 1)
- var/mob/living/carbon/C = M
- for(var/X in C.bodyparts)
- var/obj/item/bodypart/LB = X
- var/broken = LB.broken
- if(broken == 1)
- msg += "\tSubjects [LB.name] is fractured!\n"
-
-//Organ damages report
- if(ishuman(M))
- var/mob/living/carbon/human/H = M
- var/minor_damage
- var/major_damage
- var/max_damage
- var/report_organs = FALSE
-
- //Piece together the lists to be reported
- for(var/O in H.internal_organs)
- var/obj/item/organ/organ = O
- if(organ.organ_flags & ORGAN_FAILING)
- report_organs = TRUE //if we report one organ, we report all organs, even if the lists are empty, just for consistency
- if(max_damage)
- max_damage += ", " //prelude the organ if we've already reported an organ
- max_damage += organ.name //this just slaps the organ name into the string of text
- else
- max_damage = "\tNon-Functional Organs: " //our initial statement
- max_damage += organ.name
- else if(organ.damage > organ.high_threshold)
- report_organs = TRUE
- if(major_damage)
- major_damage += ", "
- major_damage += organ.name
- else
- major_damage = "\tSeverely Damaged Organs: "
- major_damage += organ.name
- else if(organ.damage > organ.low_threshold)
- report_organs = TRUE
- if(minor_damage)
- minor_damage += ", "
- minor_damage += organ.name
- else
- minor_damage = "\tMildly Damaged Organs: "
- minor_damage += organ.name
-
- if(report_organs) //we either finish the list, or set it to be empty if no organs were reported in that category
- if(!max_damage)
- max_damage = "\tNon-Functional Organs: "
- else
- max_damage += ""
- if(!major_damage)
- major_damage = "\tSeverely Damaged Organs: "
- else
- major_damage += ""
- if(!minor_damage)
- minor_damage = "\tMildly Damaged Organs: "
- else
- minor_damage += ""
- msg += "[minor_damage]"
- msg += "[major_damage]"
- msg += "[max_damage]"
-
- // Species and body temperature
- if(ishuman(M))
- var/mob/living/carbon/human/H = M
- var/datum/species/S = H.dna.species
- var/mutant = FALSE
- if (H.dna.check_mutation(HULK))
- mutant = TRUE
- else if (S.mutantlungs != initial(S.mutantlungs))
- mutant = TRUE
- else if (S.mutant_brain != initial(S.mutant_brain))
- mutant = TRUE
- else if (S.mutant_heart != initial(S.mutant_heart))
- mutant = TRUE
- else if (S.mutanteyes != initial(S.mutanteyes))
- mutant = TRUE
- else if (S.mutantears != initial(S.mutantears))
- mutant = TRUE
- else if (S.mutanthands != initial(S.mutanthands))
- mutant = TRUE
- else if (S.mutanttongue != initial(S.mutanttongue))
- mutant = TRUE
- else if (S.mutanttail != initial(S.mutanttail))
- mutant = TRUE
- else if (S.mutantliver != initial(S.mutantliver))
- mutant = TRUE
- else if (S.mutantstomach != initial(S.mutantstomach))
- mutant = TRUE
-
- msg += "Species: [H.dna.custom_species ? H.dna.custom_species : S.name] Base: [S.name]\n"
- if(mutant)
- msg += "Subject has mutations present."
- msg += "Body temperature: [round(M.bodytemperature-T0C,0.1)] °C ([round(M.bodytemperature*1.8-459.67,0.1)] °F)\n"
-
- // Time of death
- if(M.tod && (M.stat == DEAD || ((HAS_TRAIT(M, TRAIT_FAKEDEATH)) && !advanced)))
- msg += "Time of Death: [M.tod]\n"
- var/tdelta = round(world.time - M.timeofdeath)
- if(tdelta < (DEFIB_TIME_LIMIT * 10))
- msg += "Subject died [DisplayTimeText(tdelta)] ago, defibrillation may be possible!\n"
-
- for(var/thing in M.diseases)
- var/datum/disease/D = thing
- if(!(D.visibility_flags & HIDDEN_SCANNER))
- msg += "Warning: [D.form] detected\nName: [D.name].\nType: [D.spread_text].\nStage: [D.stage]/[D.max_stages].\nPossible Cure: [D.cure_text]\n"
-
- // Blood Level
- if(M.has_dna())
- var/mob/living/carbon/C = M
- var/blood_typepath = C.get_blood_id()
- if(blood_typepath)
- if(ishuman(C))
- var/mob/living/carbon/human/H = C
- if(H.bleed_rate)
- msg += "Subject is bleeding!\n"
- var/blood_percent = round((C.scan_blood_volume() / (BLOOD_VOLUME_NORMAL * C.blood_ratio))*100)
- var/blood_type = C.dna.blood_type
- if(!(blood_typepath in GLOB.blood_reagent_types))
- var/datum/reagent/R = GLOB.chemical_reagents_list[blood_typepath]
- if(R)
- blood_type = R.name
- if(C.scan_blood_volume() <= (BLOOD_VOLUME_SAFE*C.blood_ratio) && C.scan_blood_volume() > (BLOOD_VOLUME_OKAY*C.blood_ratio))
- msg += "LOW blood level [blood_percent] %, [C.scan_blood_volume()] cl, type: [blood_type]\n"
- else if(C.scan_blood_volume() <= (BLOOD_VOLUME_OKAY*C.blood_ratio))
- msg += "CRITICAL blood level [blood_percent] %, [C.scan_blood_volume()] cl, type: [blood_type]\n"
- else
- msg += "Blood level [blood_percent] %, [C.scan_blood_volume()] cl, type: [blood_type]\n"
-
- var/cyberimp_detect
- for(var/obj/item/organ/cyberimp/CI in C.internal_organs)
- if(CI.status == ORGAN_ROBOTIC && !CI.syndicate_implant)
- cyberimp_detect += "[C.name] is modified with a [CI.name].
"
- if(cyberimp_detect)
- msg += "Detected cybernetic modifications:\n"
- msg += "[cyberimp_detect]\n"
- msg += "*---------*"
- to_chat(user, msg)
- SEND_SIGNAL(M, COMSIG_NANITE_SCAN, user, FALSE)
-
-/proc/chemscan(mob/living/user, mob/living/M)
- if(istype(M))
- if(M.reagents)
- var/msg = "*---------*\n"
- if(M.reagents.reagent_list.len)
- var/list/datum/reagent/reagents = list()
- for(var/datum/reagent/R in M.reagents.reagent_list)
- if(R.chemical_flags & REAGENT_INVISIBLE)
- continue
- reagents += R
-
- if(length(reagents))
- msg += "Subject contains the following reagents:\n"
- for(var/datum/reagent/R in reagents)
- msg += "[R.volume] units of [R.name][R.overdosed == 1 ? " - OVERDOSING" : "."]\n"
- else
- msg += "Subject contains no reagents.\n"
-
- else
- msg += "Subject contains no reagents.\n"
- if(M.reagents.addiction_list.len)
- msg += "Subject is addicted to the following reagents:\n"
- for(var/datum/reagent/R in M.reagents.addiction_list)
- msg += "[R.name]\n"
- else
- msg += "Subject is not addicted to any reagents.\n"
- msg += "*---------*"
- to_chat(user, msg)
-
- var/datum/reagent/impure/fermiTox/F = M.reagents.has_reagent(/datum/reagent/impure/fermiTox)
- switch(F?.volume)
- if(5 to 10)
- msg += "Subject contains a low amount of toxic isomers.\n"
- if(10 to 25)
- msg += "Subject contains toxic isomers.\n"
- if(25 to 50)
- msg += "Subject contains a substantial amount of toxic isomers.\n"
- if(50 to 95)
- msg += "Subject contains a high amount of toxic isomers.\n"
- if(95 to INFINITY)
- msg += "Subject contains a extremely dangerous amount of toxic isomers.\n"
-
-/obj/item/healthanalyzer/verb/toggle_mode()
- set name = "Switch Verbosity"
- set category = "Object"
-
- if(usr.stat || !usr.canmove || usr.restrained())
- return
-
- mode = !mode
- switch (mode)
- if(1)
- to_chat(usr, "The scanner now shows specific limb damage.")
- if(0)
- to_chat(usr, "The scanner no longer shows limb damage.")
-
-/obj/item/healthanalyzer/advanced
- name = "advanced health analyzer"
- icon_state = "health_adv"
- desc = "A hand-held body scanner able to distinguish vital signs of the subject with high accuracy."
- advanced = TRUE
-
-/obj/item/analyzer
- desc = "A hand-held environmental scanner which reports current gas levels. Alt-Click to use the built in barometer function."
- name = "analyzer"
- icon = 'icons/obj/device.dmi'
- icon_state = "analyzer"
- item_state = "analyzer"
- lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi'
- w_class = WEIGHT_CLASS_SMALL
- flags_1 = CONDUCT_1
- item_flags = NOBLUDGEON
- slot_flags = ITEM_SLOT_BELT
- throwforce = 0
- throw_speed = 3
- throw_range = 7
- tool_behaviour = TOOL_ANALYZER
- materials = list(MAT_METAL=30, MAT_GLASS=20)
- grind_results = list(/datum/reagent/mercury = 5, /datum/reagent/iron = 5, /datum/reagent/silicon = 5)
- var/cooldown = FALSE
- var/cooldown_time = 250
- var/accuracy // 0 is the best accuracy.
-
-/obj/item/analyzer/examine(mob/user)
- . = ..()
- . += "Alt-click [src] to activate the barometer function."
-
-/obj/item/analyzer/suicide_act(mob/living/carbon/user)
- user.visible_message("[user] begins to analyze [user.p_them()]self with [src]! The display shows that [user.p_theyre()] dead!")
- return BRUTELOSS
-
-/obj/item/analyzer/attack_self(mob/user)
- add_fingerprint(user)
-
- if (user.stat || user.eye_blind)
- return
-
- var/turf/location = user.loc
- if(!istype(location))
- return
-
- var/datum/gas_mixture/environment = location.return_air()
-
- var/pressure = environment.return_pressure()
- var/total_moles = environment.total_moles()
-
- to_chat(user, "Results:")
- if(abs(pressure - ONE_ATMOSPHERE) < 10)
- to_chat(user, "Pressure: [round(pressure, 0.01)] kPa")
- else
- to_chat(user, "Pressure: [round(pressure, 0.01)] kPa")
- if(total_moles)
- var/list/env_gases = environment.gases
-
- var/o2_concentration = env_gases[/datum/gas/oxygen]/total_moles
- var/n2_concentration = env_gases[/datum/gas/nitrogen]/total_moles
- var/co2_concentration = env_gases[/datum/gas/carbon_dioxide]/total_moles
- var/plasma_concentration = env_gases[/datum/gas/plasma]/total_moles
-
- if(abs(n2_concentration - N2STANDARD) < 20)
- to_chat(user, "Nitrogen: [round(n2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/nitrogen], 0.01)] mol)")
- else
- to_chat(user, "Nitrogen: [round(n2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/nitrogen], 0.01)] mol)")
-
- if(abs(o2_concentration - O2STANDARD) < 2)
- to_chat(user, "Oxygen: [round(o2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/oxygen], 0.01)] mol)")
- else
- to_chat(user, "Oxygen: [round(o2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/oxygen], 0.01)] mol)")
-
- if(co2_concentration > 0.01)
- to_chat(user, "CO2: [round(co2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/carbon_dioxide], 0.01)] mol)")
- else
- to_chat(user, "CO2: [round(co2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/carbon_dioxide], 0.01)] mol)")
-
- if(plasma_concentration > 0.005)
- to_chat(user, "Plasma: [round(plasma_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/plasma], 0.01)] mol)")
- else
- to_chat(user, "Plasma: [round(plasma_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/plasma], 0.01)] mol)")
-
- GAS_GARBAGE_COLLECT(environment.gases)
-
- for(var/id in env_gases)
- if(id in GLOB.hardcoded_gases)
- continue
- var/gas_concentration = env_gases[id]/total_moles
- to_chat(user, "[GLOB.meta_gas_names[id]]: [round(gas_concentration*100, 0.01)] % ([round(env_gases[id], 0.01)] mol)")
- to_chat(user, "Temperature: [round(environment.temperature-T0C, 0.01)] °C ([round(environment.temperature, 0.01)] K)")
-
-/obj/item/analyzer/AltClick(mob/user) //Barometer output for measuring when the next storm happens
- ..()
-
- if(user.canUseTopic(src))
-
- if(cooldown)
- to_chat(user, "[src]'s barometer function is preparing itself.")
- return
-
- var/turf/T = get_turf(user)
- if(!T)
- return
-
- playsound(src, 'sound/effects/pop.ogg', 100)
- var/area/user_area = T.loc
- var/datum/weather/ongoing_weather = null
-
- if(!user_area.outdoors)
- to_chat(user, "[src]'s barometer function won't work indoors!")
- return
-
- for(var/V in SSweather.processing)
- var/datum/weather/W = V
- if(W.barometer_predictable && (T.z in W.impacted_z_levels) && W.area_type == user_area.type && !(W.stage == END_STAGE))
- ongoing_weather = W
- break
-
- if(ongoing_weather)
- if((ongoing_weather.stage == MAIN_STAGE) || (ongoing_weather.stage == WIND_DOWN_STAGE))
- to_chat(user, "[src]'s barometer function can't trace anything while the storm is [ongoing_weather.stage == MAIN_STAGE ? "already here!" : "winding down."]")
- return
-
- to_chat(user, "The next [ongoing_weather] will hit in [butchertime(ongoing_weather.next_hit_time - world.time)].")
- if(ongoing_weather.aesthetic)
- to_chat(user, "[src]'s barometer function says that the next storm will breeze on by.")
- else
- var/next_hit = SSweather.next_hit_by_zlevel["[T.z]"]
- var/fixed = next_hit ? next_hit - world.time : -1
- if(fixed < 0)
- to_chat(user, "[src]'s barometer function was unable to trace any weather patterns.")
- else
- to_chat(user, "[src]'s barometer function says a storm will land in approximately [butchertime(fixed)].")
- cooldown = TRUE
- addtimer(CALLBACK(src,/obj/item/analyzer/proc/ping), cooldown_time)
-
-/obj/item/analyzer/proc/ping()
- if(isliving(loc))
- var/mob/living/L = loc
- to_chat(L, "[src]'s barometer function is ready!")
- playsound(src, 'sound/machines/click.ogg', 100)
- cooldown = FALSE
-
-/obj/item/analyzer/proc/butchertime(amount)
- if(!amount)
- return
- if(accuracy)
- var/inaccurate = round(accuracy*(1/3))
- if(prob(50))
- amount -= inaccurate
- if(prob(50))
- amount += inaccurate
- return DisplayTimeText(max(1,amount))
-
-/proc/atmosanalyzer_scan(mixture, mob/living/user, atom/target = src)
- var/icon = target
- user.visible_message("[user] has used the analyzer on [icon2html(icon, viewers(src))] [target].", "You use the analyzer on [icon2html(icon, user)] [target].")
- to_chat(user, "Results of analysis of [icon2html(icon, user)] [target].")
-
- var/list/airs = islist(mixture) ? mixture : list(mixture)
- for(var/g in airs)
- if(airs.len > 1) //not a unary gas mixture
- to_chat(user, "Node [airs.Find(g)]")
- var/datum/gas_mixture/air_contents = g
-
- var/total_moles = air_contents.total_moles()
- var/pressure = air_contents.return_pressure()
- var/volume = air_contents.return_volume() //could just do mixture.volume... but safety, I guess?
- var/temperature = air_contents.temperature
- var/cached_scan_results = air_contents.analyzer_results
-
- if(total_moles > 0)
- to_chat(user, "Moles: [round(total_moles, 0.01)] mol")
- to_chat(user, "Volume: [volume] L")
- to_chat(user, "Pressure: [round(pressure,0.01)] kPa")
-
- var/list/cached_gases = air_contents.gases
- for(var/id in cached_gases)
- var/gas_concentration = cached_gases[id]/total_moles
- to_chat(user, "[GLOB.meta_gas_names[id]]: [round(gas_concentration*100, 0.01)] % ([round(cached_gases[id], 0.01)] mol)")
- to_chat(user, "Temperature: [round(temperature - T0C,0.01)] °C ([round(temperature, 0.01)] K)")
-
- else
- if(airs.len > 1)
- to_chat(user, "This node is empty!")
- else
- to_chat(user, "[target] is empty!")
-
- if(cached_scan_results && cached_scan_results["fusion"]) //notify the user if a fusion reaction was detected
- var/instability = round(cached_scan_results["fusion"], 0.01)
- to_chat(user, "Large amounts of free neutrons detected in the air indicate that a fusion reaction took place.")
- to_chat(user, "Instability of the last fusion reaction: [instability].")
- return
-
-//slime scanner
-
-/obj/item/slime_scanner
- name = "slime scanner"
- desc = "A device that analyzes a slime's internal composition and measures its stats."
- icon = 'icons/obj/device.dmi'
- icon_state = "adv_spectrometer"
- item_state = "analyzer"
- lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi'
- w_class = WEIGHT_CLASS_SMALL
- flags_1 = CONDUCT_1
- throwforce = 0
- throw_speed = 3
- throw_range = 7
- materials = list(MAT_METAL=30, MAT_GLASS=20)
-
-/obj/item/slime_scanner/attack(mob/living/M, mob/living/user)
- if(user.stat || user.eye_blind)
- return
- if (!isslime(M))
- to_chat(user, "This device can only scan slimes!")
- return
- var/mob/living/simple_animal/slime/T = M
- slime_scan(T, user)
-
-/proc/slime_scan(mob/living/simple_animal/slime/T, mob/living/user)
- to_chat(user, "========================")
- to_chat(user, "Slime scan results:")
- to_chat(user, "[T.colour] [T.is_adult ? "adult" : "baby"] slime")
- to_chat(user, "Nutrition: [T.nutrition]/[T.get_max_nutrition()]")
- if (T.nutrition < T.get_starve_nutrition())
- to_chat(user, "Warning: slime is starving!")
- else if (T.nutrition < T.get_hunger_nutrition())
- to_chat(user, "Warning: slime is hungry")
- to_chat(user, "Electric change strength: [T.powerlevel]")
- to_chat(user, "Health: [round(T.health/T.maxHealth,0.01)*100]%")
- if (T.slime_mutation[4] == T.colour)
- to_chat(user, "This slime does not evolve any further.")
- else
- if (T.slime_mutation[3] == T.slime_mutation[4])
- if (T.slime_mutation[2] == T.slime_mutation[1])
- to_chat(user, "Possible mutation: [T.slime_mutation[3]]")
- to_chat(user, "Genetic destability: [T.mutation_chance/2] % chance of mutation on splitting")
- else
- to_chat(user, "Possible mutations: [T.slime_mutation[1]], [T.slime_mutation[2]], [T.slime_mutation[3]] (x2)")
- to_chat(user, "Genetic destability: [T.mutation_chance] % chance of mutation on splitting")
- else
- to_chat(user, "Possible mutations: [T.slime_mutation[1]], [T.slime_mutation[2]], [T.slime_mutation[3]], [T.slime_mutation[4]]")
- to_chat(user, "Genetic destability: [T.mutation_chance] % chance of mutation on splitting")
- if (T.cores > 1)
- to_chat(user, "Multiple cores detected")
- to_chat(user, "Growth progress: [T.amount_grown]/[SLIME_EVOLUTION_THRESHOLD]")
- if(T.effectmod)
- to_chat(user, "Core mutation in progress: [T.effectmod]")
- to_chat(user, "Progress in core mutation: [T.applied] / [SLIME_EXTRACT_CROSSING_REQUIRED]")
- to_chat(user, "========================")
-
-
-/obj/item/nanite_scanner
- name = "nanite scanner"
- icon = 'icons/obj/device.dmi'
- icon_state = "nanite_scanner"
- item_state = "nanite_remote"
- lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
- desc = "A hand-held body scanner able to detect nanites and their programming."
- flags_1 = CONDUCT_1
- item_flags = NOBLUDGEON
- slot_flags = ITEM_SLOT_BELT
- throwforce = 3
- w_class = WEIGHT_CLASS_TINY
- throw_speed = 3
- throw_range = 7
- materials = list(MAT_METAL=200)
-
-/obj/item/nanite_scanner/attack(mob/living/M, mob/living/carbon/human/user)
- user.visible_message("[user] has analyzed [M]'s nanites.")
-
- add_fingerprint(user)
-
- var/response = SEND_SIGNAL(M, COMSIG_NANITE_SCAN, user, TRUE)
- if(!response)
- to_chat(user, "No nanites detected in the subject.")
-
-/obj/item/sequence_scanner
- name = "genetic sequence scanner"
- icon = 'icons/obj/device.dmi'
- icon_state = "gene"
- item_state = "healthanalyzer"
- lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
- desc = "A hand-held scanner able to swiftly scan someone for potential mutations. Hold near a DNA console to update from their database."
- flags_1 = CONDUCT_1
- item_flags = NOBLUDGEON
- slot_flags = ITEM_SLOT_BELT
- throwforce = 3
- w_class = WEIGHT_CLASS_TINY
- throw_speed = 3
- throw_range = 7
- materials = list(MAT_METAL=200)
- var/list/discovered = list() //hit a dna console to update the scanners database
-
-/obj/item/sequence_scanner/attack(mob/living/M, mob/living/carbon/human/user)
- user.visible_message("[user] has analyzed [M]'s genetic sequence.")
-
- add_fingerprint(user)
-
- gene_scan(M, user, src)
-
-/obj/item/sequence_scanner/afterattack(obj/O, mob/user, proximity)
- . = ..()
- if(!istype(O) || !proximity)
- return
-
- if(istype(O, /obj/machinery/computer/scan_consolenew))
- var/obj/machinery/computer/scan_consolenew/C = O
- if(C.stored_research)
- to_chat(user, "[name] database updated.")
- discovered = C.stored_research.discovered_mutations
- else
- to_chat(user,"No database to update from.")
-
-/proc/gene_scan(mob/living/carbon/C, mob/living/user, obj/item/sequence_scanner/G)
- if(!iscarbon(C) || !C.has_dna())
- return
- to_chat(user, "[C.name]'s potential mutations.")
- for(var/A in C.dna.mutation_index)
- var/datum/mutation/human/HM = GET_INITIALIZED_MUTATION(A)
- var/mut_name
- if(G && (A in G.discovered))
- mut_name = "[HM.name] ([HM.alias])"
- else
- mut_name = HM.alias
- var/temp = GET_GENE_STRING(HM.type, C.dna)
- var/display
- for(var/i in 0 to length(temp) / DNA_MUTATION_BLOCKS-1)
- if(i)
- display += "-"
- display += copytext(temp, 1 + i*DNA_MUTATION_BLOCKS, DNA_MUTATION_BLOCKS*(1+i) + 1)
-
-
- to_chat(user, "- [mut_name] > [display]")
+
+/*
+
+CONTAINS:
+T-RAY
+HEALTH ANALYZER
+GAS ANALYZER
+SLIME SCANNER
+NANITE SCANNER
+GENE SCANNER
+
+*/
+/obj/item/t_scanner
+ name = "\improper T-ray scanner"
+ desc = "A terahertz-ray emitter and scanner used to detect underfloor objects such as cables and pipes."
+ icon = 'icons/obj/device.dmi'
+ icon_state = "t-ray0"
+ var/on = FALSE
+ slot_flags = ITEM_SLOT_BELT
+ w_class = WEIGHT_CLASS_SMALL
+ item_state = "electronic"
+ lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
+ materials = list(MAT_METAL=150)
+
+/obj/item/t_scanner/suicide_act(mob/living/carbon/user)
+ user.visible_message("[user] begins to emit terahertz-rays into [user.p_their()] brain with [src]! It looks like [user.p_theyre()] trying to commit suicide!")
+ return TOXLOSS
+
+/obj/item/t_scanner/attack_self(mob/user)
+
+ on = !on
+ icon_state = copytext(icon_state, 1, length(icon_state))+"[on]"
+
+ if(on)
+ START_PROCESSING(SSobj, src)
+
+/obj/item/t_scanner/process()
+ if(!on)
+ STOP_PROCESSING(SSobj, src)
+ return null
+ scan()
+
+/obj/item/t_scanner/proc/scan()
+ t_ray_scan(loc)
+
+/proc/t_ray_scan(mob/viewer, flick_time = 8, distance = 3)
+ if(!ismob(viewer) || !viewer.client)
+ return
+ var/list/t_ray_images = list()
+ for(var/obj/O in orange(distance, viewer) )
+ if(O.level != 1)
+ continue
+
+ if(O.invisibility == INVISIBILITY_MAXIMUM)
+ var/image/I = new(loc = get_turf(O))
+ var/mutable_appearance/MA = new(O)
+ MA.alpha = 128
+ MA.dir = O.dir
+ I.appearance = MA
+ t_ray_images += I
+ if(t_ray_images.len)
+ flick_overlay(t_ray_images, list(viewer.client), flick_time)
+
+/obj/item/healthanalyzer
+ name = "health analyzer"
+ icon = 'icons/obj/device.dmi'
+ icon_state = "health"
+ item_state = "healthanalyzer"
+ lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
+ desc = "A hand-held body scanner able to distinguish vital signs of the subject."
+ flags_1 = CONDUCT_1
+ item_flags = NOBLUDGEON
+ slot_flags = ITEM_SLOT_BELT
+ throwforce = 3
+ w_class = WEIGHT_CLASS_TINY
+ throw_speed = 3
+ throw_range = 7
+ materials = list(MAT_METAL=200)
+ var/mode = 1
+ var/scanmode = 0
+ var/advanced = FALSE
+
+/obj/item/healthanalyzer/suicide_act(mob/living/carbon/user)
+ user.visible_message("[user] begins to analyze [user.p_them()]self with [src]! The display shows that [user.p_theyre()] dead!")
+ return BRUTELOSS
+
+/obj/item/healthanalyzer/attack_self(mob/user)
+ if(!scanmode)
+ to_chat(user, "You switch the health analyzer to scan chemical contents.")
+ scanmode = 1
+ else
+ to_chat(user, "You switch the health analyzer to check physical health.")
+ scanmode = 0
+
+/obj/item/healthanalyzer/attack(mob/living/M, mob/living/carbon/human/user)
+
+ // Clumsiness/brain damage check
+ if ((HAS_TRAIT(user, TRAIT_CLUMSY) || HAS_TRAIT(user, TRAIT_DUMB)) && prob(50))
+ to_chat(user, "You stupidly try to analyze the floor's vitals!")
+ user.visible_message("[user] has analyzed the floor's vitals!")
+ var/msg = "*---------*\nAnalyzing results for The floor:\n\tOverall status: Healthy\n"
+ msg += "Key: Suffocation/Toxin/Burn/Brute\n"
+ msg += "\tDamage specifics: 0-0-0-0\n"
+ msg += "Body temperature: ???\n"
+ msg += "*---------*"
+ to_chat(user, msg)
+ return
+
+ user.visible_message("[user] has analyzed [M]'s vitals.")
+
+ if(scanmode == 0)
+ healthscan(user, M, mode, advanced)
+ else if(scanmode == 1)
+ chemscan(user, M)
+
+ add_fingerprint(user)
+
+
+// Used by the PDA medical scanner too
+/proc/healthscan(mob/user, mob/living/M, mode = 1, advanced = FALSE)
+ if(isliving(user) && (user.incapacitated() || user.eye_blind))
+ return
+ //Damage specifics
+ var/oxy_loss = M.getOxyLoss()
+ var/tox_loss = M.getToxLoss()
+ var/fire_loss = M.getFireLoss()
+ var/brute_loss = M.getBruteLoss()
+ var/mob_status = (M.stat == DEAD ? "Deceased" : "[round(M.health/M.maxHealth,0.01)*100] % healthy")
+
+ if(HAS_TRAIT(M, TRAIT_FAKEDEATH) && !advanced)
+ mob_status = "Deceased"
+ oxy_loss = max(rand(1, 40), oxy_loss, (300 - (tox_loss + fire_loss + brute_loss))) // Random oxygen loss
+
+ if(ishuman(M))
+ var/mob/living/carbon/human/H = M
+ if(H.undergoing_cardiac_arrest() && H.stat != DEAD)
+ to_chat(user, "Subject suffering from heart attack: Apply defibrillation or other electric shock immediately!")
+ if(H.undergoing_liver_failure() && H.stat != DEAD) //might be depreciated BUG_PROBABLE_CAUSE
+ to_chat(user, "Subject is suffering from liver failure: Apply Corazone and begin a liver transplant immediately!")
+
+ var/msg = "*---------*\nAnalyzing results for [M]:\n\tOverall status: [mob_status]\n"
+
+ // Damage descriptions
+ if(brute_loss > 10)
+ msg += "\t[brute_loss > 50 ? "Severe" : "Minor"] tissue damage detected.\n"
+ if(fire_loss > 10)
+ msg += "\t[fire_loss > 50 ? "Severe" : "Minor"] burn damage detected.\n"
+ if(oxy_loss > 10)
+ msg += "\t[oxy_loss > 50 ? "Severe" : "Minor"] oxygen deprivation detected.\n"
+ if(tox_loss > 10)
+ msg += "\t[tox_loss > 50 ? "Severe" : "Minor"] amount of toxin damage detected.\n"
+ if(M.getStaminaLoss())
+ msg += "\tSubject appears to be suffering from fatigue.\n"
+ if(advanced)
+ msg += "\tFatigue Level: [M.getStaminaLoss()]%.\n"
+ if (M.getCloneLoss())
+ msg += "\tSubject appears to have [M.getCloneLoss() > 30 ? "Severe" : "Minor"] cellular damage.\n"
+ if(advanced)
+ msg += "\tCellular Damage Level: [M.getCloneLoss()].\n"
+ if (!M.getorgan(/obj/item/organ/brain))
+ to_chat(user, "\tSubject lacks a brain.") //Unsure how this won't proc for 50% of the cit playerbase (This is a joke everyone on cit a cute.)
+ if(ishuman(M) && advanced) // Should I make this not advanced?
+ var/mob/living/carbon/human/H = M
+ var/obj/item/organ/liver/L = H.getorganslot("liver")
+ if(L)
+ if(L.swelling > 20)
+ msg += "\tSubject is suffering from an enlarged liver.\n" //i.e. shrink their liver or give them a transplant.
+ else
+ msg += "\tSubject's liver is missing.\n"
+ var/obj/item/organ/tongue/T = H.getorganslot("tongue")
+ if(T)
+ if(T.damage > 40)
+ msg += "\tSubject is suffering from severe burn tissue on their tongue.\n" //i.e. their tongue is shot
+ if(T.name == "fluffy tongue")
+ msg += "\tSubject is suffering from a fluffified tongue. Suggested cure: Yamerol or a tongue transplant.\n"
+ else
+ msg += "\tSubject's tongue is missing.\n"
+ var/obj/item/organ/lungs/Lung = H.getorganslot("lungs")
+ if(Lung)
+ if(Lung.damage > 150)
+ msg += "\tSubject is suffering from acute emphysema leading to trouble breathing.\n" //i.e. Their lungs are shot
+ else
+ msg += "\tSubject's lungs have collapsed from trauma!\n"
+ var/obj/item/organ/genital/penis/P = H.getorganslot("penis")
+ if(P)
+ if(P.length>20)
+ msg += "\tSubject has a sizeable gentleman's organ at [P.length] inches.\n"
+ var/obj/item/organ/genital/breasts/Br = H.getorganslot("breasts")
+ if(Br)
+ if(Br.cached_size>5)
+ msg += "\tSubject has a sizeable bosom with a [Br.size] cup.\n"
+ if (M.getOrganLoss(ORGAN_SLOT_BRAIN) >= 200 || !M.getorgan(/obj/item/organ/brain))
+ msg += "\tSubject's brain function is non-existent.\n"
+ else if (M.getOrganLoss(ORGAN_SLOT_BRAIN) >= 120)
+ msg += "\tSevere brain damage detected. Subject likely to have mental traumas.\n"
+ else if (M.getOrganLoss(ORGAN_SLOT_BRAIN) >= 45)
+ msg += "\tBrain damage detected.\n"
+
+ if(iscarbon(M))
+
+ var/mob/living/carbon/C = M
+ if(LAZYLEN(C.get_traumas()))
+ var/list/trauma_text = list()
+ for(var/datum/brain_trauma/B in C.get_traumas())
+ var/trauma_desc = ""
+ switch(B.resilience)
+ if(TRAUMA_RESILIENCE_SURGERY)
+ trauma_desc += "severe "
+ if(TRAUMA_RESILIENCE_LOBOTOMY)
+ trauma_desc += "deep-rooted "
+ if(TRAUMA_RESILIENCE_MAGIC, TRAUMA_RESILIENCE_ABSOLUTE)
+ trauma_desc += "permanent "
+ trauma_desc += B.scan_desc
+ trauma_text += trauma_desc
+ msg += "\tCerebral traumas detected: subject appears to be suffering from [english_list(trauma_text)].\n"
+ if(C.roundstart_quirks.len)
+ msg += "\tSubject has the following physiological traits: [C.get_trait_string()].\n"
+ if(advanced)
+ msg += "\tBrain Activity Level: [(200 - M.getOrganLoss(ORGAN_SLOT_BRAIN))/2]%.\n"
+ if(M.radiation)
+ msg += "\tSubject is irradiated.\n"
+ if(advanced)
+ msg += "\tRadiation Level: [M.radiation]%.\n"
+
+ if(advanced && M.hallucinating())
+ msg += "\tSubject is hallucinating.\n"
+
+ //MKUltra
+ if(advanced && M.has_status_effect(/datum/status_effect/chem/enthrall))
+ msg += "\tSubject has abnormal brain fuctions.\n"
+
+ //Astrogen shenanigans
+ if(M.reagents.has_reagent(/datum/reagent/fermi/astral))
+ if(M.mind)
+ msg += "\tWarning: subject may be possesed.\n"
+ else
+ msg += "\tSubject appears to be astrally projecting.\n"
+
+ //Eyes and ears
+ if(advanced)
+ if(iscarbon(M))
+ var/mob/living/carbon/C = M
+ var/obj/item/organ/ears/ears = C.getorganslot(ORGAN_SLOT_EARS)
+ msg += "\t==EAR STATUS==\n"
+ if(istype(ears))
+ var/healthy = TRUE
+ if(HAS_TRAIT_FROM(C, TRAIT_DEAF, GENETIC_MUTATION))
+ healthy = FALSE
+ msg += "\tSubject is genetically deaf.\n"
+ else if(HAS_TRAIT(C, TRAIT_DEAF))
+ healthy = FALSE
+ msg += "\tSubject is deaf.\n"
+ else
+ if(ears.damage)
+ to_chat(user, "\tSubject has [ears.damage > ears.maxHealth ? "permanent ": "temporary "]hearing damage.")
+ healthy = FALSE
+ if(ears.deaf)
+ to_chat(user, "\tSubject is [ears.damage > ears.maxHealth ? "permanently ": "temporarily "] deaf.")
+ healthy = FALSE
+ if(healthy)
+ msg += "\tHealthy.\n"
+ else
+ msg += "\tSubject does not have ears.\n"
+ var/obj/item/organ/eyes/eyes = C.getorganslot(ORGAN_SLOT_EYES)
+ msg += "\t==EYE STATUS==\n"
+ if(istype(eyes))
+ var/healthy = TRUE
+ if(HAS_TRAIT(C, TRAIT_BLIND))
+ msg += "\tSubject is blind.\n"
+ healthy = FALSE
+ if(HAS_TRAIT(C, TRAIT_NEARSIGHT))
+ msg += "\tSubject is nearsighted.\n"
+ healthy = FALSE
+ if(eyes.damage > 30)
+ msg += "\tSubject has severe eye damage.\n"
+ healthy = FALSE
+ else if(eyes.damage > 20)
+ msg += "\tSubject has significant eye damage.\n"
+ healthy = FALSE
+ else if(eyes.damage)
+ msg += "\tSubject has minor eye damage.\n"
+ healthy = FALSE
+ if(healthy)
+ msg += "\tHealthy.\n"
+ else
+ msg += "\tSubject does not have eyes.\n"
+
+
+ if(ishuman(M))
+ var/mob/living/carbon/human/H = M
+ var/ldamage = H.return_liver_damage()
+ if(ldamage > 10)
+ msg += "\t[ldamage > 45 ? "Severe" : "Minor"] liver damage detected.\n"
+ if(advanced && H.has_dna())
+ to_chat(user, "\tGenetic Stability: [H.dna.stability]%.")
+ // Body part damage report
+ if(iscarbon(M) && mode == 1)
+ var/mob/living/carbon/C = M
+ var/list/damaged = C.get_damaged_bodyparts(1,1)
+ if(length(damaged)>0 || oxy_loss>0 || tox_loss>0 || fire_loss>0)
+ msg += "\tDamage: Brute-Burn-Toxin-Suffocation\n\t\tSpecifics: [brute_loss]-[fire_loss]-[tox_loss]-[oxy_loss]\n"
+ for(var/obj/item/bodypart/org in damaged)
+ msg += "\t\t[capitalize(org.name)]: [(org.brute_dam > 0) ? "[org.brute_dam]" : "0"]-[(org.burn_dam > 0) ? "[org.burn_dam]" : "0"]\n"
+
+ //Bones broken report! Hyperstation 13
+ if(iscarbon(M) && mode == 1)
+ var/mob/living/carbon/C = M
+ for(var/X in C.bodyparts)
+ var/obj/item/bodypart/LB = X
+ var/broken = LB.broken
+ if(broken == 1)
+ msg += "\tSubjects [LB.name] is fractured!\n"
+
+//Organ damages report
+ if(ishuman(M))
+ var/mob/living/carbon/human/H = M
+ var/minor_damage
+ var/major_damage
+ var/max_damage
+ var/report_organs = FALSE
+
+ //Piece together the lists to be reported
+ for(var/O in H.internal_organs)
+ var/obj/item/organ/organ = O
+ if(organ.organ_flags & ORGAN_FAILING)
+ report_organs = TRUE //if we report one organ, we report all organs, even if the lists are empty, just for consistency
+ if(max_damage)
+ max_damage += ", " //prelude the organ if we've already reported an organ
+ max_damage += organ.name //this just slaps the organ name into the string of text
+ else
+ max_damage = "\tNon-Functional Organs: " //our initial statement
+ max_damage += organ.name
+ else if(organ.damage > organ.high_threshold)
+ report_organs = TRUE
+ if(major_damage)
+ major_damage += ", "
+ major_damage += organ.name
+ else
+ major_damage = "\tSeverely Damaged Organs: "
+ major_damage += organ.name
+ else if(organ.damage > organ.low_threshold)
+ report_organs = TRUE
+ if(minor_damage)
+ minor_damage += ", "
+ minor_damage += organ.name
+ else
+ minor_damage = "\tMildly Damaged Organs: "
+ minor_damage += organ.name
+
+ if(report_organs) //we either finish the list, or set it to be empty if no organs were reported in that category
+ if(!max_damage)
+ max_damage = "\tNon-Functional Organs: "
+ else
+ max_damage += ""
+ if(!major_damage)
+ major_damage = "\tSeverely Damaged Organs: "
+ else
+ major_damage += ""
+ if(!minor_damage)
+ minor_damage = "\tMildly Damaged Organs: "
+ else
+ minor_damage += ""
+ msg += "[minor_damage]"
+ msg += "[major_damage]"
+ msg += "[max_damage]"
+
+ // Species and body temperature
+ if(ishuman(M))
+ var/mob/living/carbon/human/H = M
+ var/datum/species/S = H.dna.species
+ var/mutant = FALSE
+ if (H.dna.check_mutation(HULK))
+ mutant = TRUE
+ else if (S.mutantlungs != initial(S.mutantlungs))
+ mutant = TRUE
+ else if (S.mutant_brain != initial(S.mutant_brain))
+ mutant = TRUE
+ else if (S.mutant_heart != initial(S.mutant_heart))
+ mutant = TRUE
+ else if (S.mutanteyes != initial(S.mutanteyes))
+ mutant = TRUE
+ else if (S.mutantears != initial(S.mutantears))
+ mutant = TRUE
+ else if (S.mutanthands != initial(S.mutanthands))
+ mutant = TRUE
+ else if (S.mutanttongue != initial(S.mutanttongue))
+ mutant = TRUE
+ else if (S.mutanttail != initial(S.mutanttail))
+ mutant = TRUE
+ else if (S.mutantliver != initial(S.mutantliver))
+ mutant = TRUE
+ else if (S.mutantstomach != initial(S.mutantstomach))
+ mutant = TRUE
+
+ msg += "Species: [H.dna.custom_species ? H.dna.custom_species : S.name] Base: [S.name]\n"
+ if(mutant)
+ msg += "Subject has mutations present."
+ msg += "Body temperature: [round(M.bodytemperature-T0C,0.1)] °C ([round(M.bodytemperature*1.8-459.67,0.1)] °F)\n"
+
+ // Time of death
+ if(M.tod && (M.stat == DEAD || ((HAS_TRAIT(M, TRAIT_FAKEDEATH)) && !advanced)))
+ msg += "Time of Death: [M.tod]\n"
+ var/tdelta = round(world.time - M.timeofdeath)
+ if(tdelta < (DEFIB_TIME_LIMIT * 10))
+ msg += "Subject died [DisplayTimeText(tdelta)] ago, defibrillation may be possible!\n"
+
+ for(var/thing in M.diseases)
+ var/datum/disease/D = thing
+ if(!(D.visibility_flags & HIDDEN_SCANNER))
+ msg += "Warning: [D.form] detected\nName: [D.name].\nType: [D.spread_text].\nStage: [D.stage]/[D.max_stages].\nPossible Cure: [D.cure_text]\n"
+
+ // Blood Level
+ if(M.has_dna())
+ var/mob/living/carbon/C = M
+ var/blood_typepath = C.get_blood_id()
+ if(blood_typepath)
+ if(ishuman(C))
+ var/mob/living/carbon/human/H = C
+ if(H.bleed_rate)
+ msg += "Subject is bleeding!\n"
+ var/blood_percent = round((C.scan_blood_volume() / (BLOOD_VOLUME_NORMAL * C.blood_ratio))*100)
+ var/blood_type = C.dna.blood_type
+ if(!(blood_typepath in GLOB.blood_reagent_types))
+ var/datum/reagent/R = GLOB.chemical_reagents_list[blood_typepath]
+ if(R)
+ blood_type = R.name
+ if(C.scan_blood_volume() <= (BLOOD_VOLUME_SAFE*C.blood_ratio) && C.scan_blood_volume() > (BLOOD_VOLUME_OKAY*C.blood_ratio))
+ msg += "LOW blood level [blood_percent] %, [C.scan_blood_volume()] cl, type: [blood_type]\n"
+ else if(C.scan_blood_volume() <= (BLOOD_VOLUME_OKAY*C.blood_ratio))
+ msg += "CRITICAL blood level [blood_percent] %, [C.scan_blood_volume()] cl, type: [blood_type]\n"
+ else
+ msg += "Blood level [blood_percent] %, [C.scan_blood_volume()] cl, type: [blood_type]\n"
+
+ var/cyberimp_detect
+ for(var/obj/item/organ/cyberimp/CI in C.internal_organs)
+ if(CI.status == ORGAN_ROBOTIC && !CI.syndicate_implant)
+ cyberimp_detect += "[C.name] is modified with a [CI.name].
"
+ if(cyberimp_detect)
+ msg += "Detected cybernetic modifications:\n"
+ msg += "[cyberimp_detect]\n"
+ msg += "*---------*"
+ to_chat(user, msg)
+ SEND_SIGNAL(M, COMSIG_NANITE_SCAN, user, FALSE)
+
+/proc/chemscan(mob/living/user, mob/living/M)
+ if(istype(M))
+ if(M.reagents)
+ var/msg = "*---------*\n"
+ if(M.reagents.reagent_list.len)
+ var/list/datum/reagent/reagents = list()
+ for(var/datum/reagent/R in M.reagents.reagent_list)
+ if(R.chemical_flags & REAGENT_INVISIBLE)
+ continue
+ reagents += R
+
+ if(length(reagents))
+ msg += "Subject contains the following reagents:\n"
+ for(var/datum/reagent/R in reagents)
+ msg += "[R.volume] units of [R.name][R.overdosed == 1 ? " - OVERDOSING" : "."]\n"
+ else
+ msg += "Subject contains no reagents.\n"
+
+ else
+ msg += "Subject contains no reagents.\n"
+ if(M.reagents.addiction_list.len)
+ msg += "Subject is addicted to the following reagents:\n"
+ for(var/datum/reagent/R in M.reagents.addiction_list)
+ msg += "[R.name]\n"
+ else
+ msg += "Subject is not addicted to any reagents.\n"
+
+ var/datum/reagent/impure/fermiTox/F = M.reagents.has_reagent(/datum/reagent/impure/fermiTox)
+ switch(F?.volume)
+ if(5 to 10)
+ msg += "Subject contains a low amount of toxic isomers.\n"
+ if(10 to 25)
+ msg += "Subject contains toxic isomers.\n"
+ if(25 to 50)
+ msg += "Subject contains a substantial amount of toxic isomers.\n"
+ if(50 to 95)
+ msg += "Subject contains a high amount of toxic isomers.\n"
+ if(95 to INFINITY)
+ msg += "Subject contains a extremely dangerous amount of toxic isomers.\n"
+
+ msg += "*---------*"
+ to_chat(user, msg)
+
+/obj/item/healthanalyzer/verb/toggle_mode()
+ set name = "Switch Verbosity"
+ set category = "Object"
+
+ if(usr.stat || !usr.canmove || usr.restrained())
+ return
+
+ mode = !mode
+ switch (mode)
+ if(1)
+ to_chat(usr, "The scanner now shows specific limb damage.")
+ if(0)
+ to_chat(usr, "The scanner no longer shows limb damage.")
+
+/obj/item/healthanalyzer/advanced
+ name = "advanced health analyzer"
+ icon_state = "health_adv"
+ desc = "A hand-held body scanner able to distinguish vital signs of the subject with high accuracy."
+ advanced = TRUE
+
+/obj/item/analyzer
+ desc = "A hand-held environmental scanner which reports current gas levels. Alt-Click to use the built in barometer function."
+ name = "analyzer"
+ icon = 'icons/obj/device.dmi'
+ icon_state = "analyzer"
+ item_state = "analyzer"
+ lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi'
+ w_class = WEIGHT_CLASS_SMALL
+ flags_1 = CONDUCT_1
+ item_flags = NOBLUDGEON
+ slot_flags = ITEM_SLOT_BELT
+ throwforce = 0
+ throw_speed = 3
+ throw_range = 7
+ tool_behaviour = TOOL_ANALYZER
+ materials = list(MAT_METAL=30, MAT_GLASS=20)
+ grind_results = list(/datum/reagent/mercury = 5, /datum/reagent/iron = 5, /datum/reagent/silicon = 5)
+ var/cooldown = FALSE
+ var/cooldown_time = 250
+ var/accuracy // 0 is the best accuracy.
+
+/obj/item/analyzer/examine(mob/user)
+ . = ..()
+ . += "Alt-click [src] to activate the barometer function."
+
+/obj/item/analyzer/suicide_act(mob/living/carbon/user)
+ user.visible_message("[user] begins to analyze [user.p_them()]self with [src]! The display shows that [user.p_theyre()] dead!")
+ return BRUTELOSS
+
+/obj/item/analyzer/attack_self(mob/user)
+ add_fingerprint(user)
+
+ if (user.stat || user.eye_blind)
+ return
+
+ var/turf/location = user.loc
+ if(!istype(location))
+ return
+
+ var/datum/gas_mixture/environment = location.return_air()
+
+ var/pressure = environment.return_pressure()
+ var/total_moles = environment.total_moles()
+
+ to_chat(user, "Results:")
+ if(abs(pressure - ONE_ATMOSPHERE) < 10)
+ to_chat(user, "Pressure: [round(pressure, 0.01)] kPa")
+ else
+ to_chat(user, "Pressure: [round(pressure, 0.01)] kPa")
+ if(total_moles)
+ var/list/env_gases = environment.gases
+
+ var/o2_concentration = env_gases[/datum/gas/oxygen]/total_moles
+ var/n2_concentration = env_gases[/datum/gas/nitrogen]/total_moles
+ var/co2_concentration = env_gases[/datum/gas/carbon_dioxide]/total_moles
+ var/plasma_concentration = env_gases[/datum/gas/plasma]/total_moles
+
+ if(abs(n2_concentration - N2STANDARD) < 20)
+ to_chat(user, "Nitrogen: [round(n2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/nitrogen], 0.01)] mol)")
+ else
+ to_chat(user, "Nitrogen: [round(n2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/nitrogen], 0.01)] mol)")
+
+ if(abs(o2_concentration - O2STANDARD) < 2)
+ to_chat(user, "Oxygen: [round(o2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/oxygen], 0.01)] mol)")
+ else
+ to_chat(user, "Oxygen: [round(o2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/oxygen], 0.01)] mol)")
+
+ if(co2_concentration > 0.01)
+ to_chat(user, "CO2: [round(co2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/carbon_dioxide], 0.01)] mol)")
+ else
+ to_chat(user, "CO2: [round(co2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/carbon_dioxide], 0.01)] mol)")
+
+ if(plasma_concentration > 0.005)
+ to_chat(user, "Plasma: [round(plasma_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/plasma], 0.01)] mol)")
+ else
+ to_chat(user, "Plasma: [round(plasma_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/plasma], 0.01)] mol)")
+
+ GAS_GARBAGE_COLLECT(environment.gases)
+
+ for(var/id in env_gases)
+ if(id in GLOB.hardcoded_gases)
+ continue
+ var/gas_concentration = env_gases[id]/total_moles
+ to_chat(user, "[GLOB.meta_gas_names[id]]: [round(gas_concentration*100, 0.01)] % ([round(env_gases[id], 0.01)] mol)")
+ to_chat(user, "Temperature: [round(environment.temperature-T0C, 0.01)] °C ([round(environment.temperature, 0.01)] K)")
+
+/obj/item/analyzer/AltClick(mob/user) //Barometer output for measuring when the next storm happens
+ ..()
+
+ if(user.canUseTopic(src))
+
+ if(cooldown)
+ to_chat(user, "[src]'s barometer function is preparing itself.")
+ return
+
+ var/turf/T = get_turf(user)
+ if(!T)
+ return
+
+ playsound(src, 'sound/effects/pop.ogg', 100)
+ var/area/user_area = T.loc
+ var/datum/weather/ongoing_weather = null
+
+ if(!user_area.outdoors)
+ to_chat(user, "[src]'s barometer function won't work indoors!")
+ return
+
+ for(var/V in SSweather.processing)
+ var/datum/weather/W = V
+ if(W.barometer_predictable && (T.z in W.impacted_z_levels) && W.area_type == user_area.type && !(W.stage == END_STAGE))
+ ongoing_weather = W
+ break
+
+ if(ongoing_weather)
+ if((ongoing_weather.stage == MAIN_STAGE) || (ongoing_weather.stage == WIND_DOWN_STAGE))
+ to_chat(user, "[src]'s barometer function can't trace anything while the storm is [ongoing_weather.stage == MAIN_STAGE ? "already here!" : "winding down."]")
+ return
+
+ to_chat(user, "The next [ongoing_weather] will hit in [butchertime(ongoing_weather.next_hit_time - world.time)].")
+ if(ongoing_weather.aesthetic)
+ to_chat(user, "[src]'s barometer function says that the next storm will breeze on by.")
+ else
+ var/next_hit = SSweather.next_hit_by_zlevel["[T.z]"]
+ var/fixed = next_hit ? next_hit - world.time : -1
+ if(fixed < 0)
+ to_chat(user, "[src]'s barometer function was unable to trace any weather patterns.")
+ else
+ to_chat(user, "[src]'s barometer function says a storm will land in approximately [butchertime(fixed)].")
+ cooldown = TRUE
+ addtimer(CALLBACK(src,/obj/item/analyzer/proc/ping), cooldown_time)
+
+/obj/item/analyzer/proc/ping()
+ if(isliving(loc))
+ var/mob/living/L = loc
+ to_chat(L, "[src]'s barometer function is ready!")
+ playsound(src, 'sound/machines/click.ogg', 100)
+ cooldown = FALSE
+
+/obj/item/analyzer/proc/butchertime(amount)
+ if(!amount)
+ return
+ if(accuracy)
+ var/inaccurate = round(accuracy*(1/3))
+ if(prob(50))
+ amount -= inaccurate
+ if(prob(50))
+ amount += inaccurate
+ return DisplayTimeText(max(1,amount))
+
+/proc/atmosanalyzer_scan(mixture, mob/living/user, atom/target = src)
+ var/icon = target
+ user.visible_message("[user] has used the analyzer on [icon2html(icon, viewers(src))] [target].", "You use the analyzer on [icon2html(icon, user)] [target].")
+ to_chat(user, "Results of analysis of [icon2html(icon, user)] [target].")
+
+ var/list/airs = islist(mixture) ? mixture : list(mixture)
+ for(var/g in airs)
+ if(airs.len > 1) //not a unary gas mixture
+ to_chat(user, "Node [airs.Find(g)]")
+ var/datum/gas_mixture/air_contents = g
+
+ var/total_moles = air_contents.total_moles()
+ var/pressure = air_contents.return_pressure()
+ var/volume = air_contents.return_volume() //could just do mixture.volume... but safety, I guess?
+ var/temperature = air_contents.temperature
+ var/cached_scan_results = air_contents.analyzer_results
+
+ if(total_moles > 0)
+ to_chat(user, "Moles: [round(total_moles, 0.01)] mol")
+ to_chat(user, "Volume: [volume] L")
+ to_chat(user, "Pressure: [round(pressure,0.01)] kPa")
+
+ var/list/cached_gases = air_contents.gases
+ for(var/id in cached_gases)
+ var/gas_concentration = cached_gases[id]/total_moles
+ to_chat(user, "[GLOB.meta_gas_names[id]]: [round(gas_concentration*100, 0.01)] % ([round(cached_gases[id], 0.01)] mol)")
+ to_chat(user, "Temperature: [round(temperature - T0C,0.01)] °C ([round(temperature, 0.01)] K)")
+
+ else
+ if(airs.len > 1)
+ to_chat(user, "This node is empty!")
+ else
+ to_chat(user, "[target] is empty!")
+
+ if(cached_scan_results && cached_scan_results["fusion"]) //notify the user if a fusion reaction was detected
+ var/instability = round(cached_scan_results["fusion"], 0.01)
+ to_chat(user, "Large amounts of free neutrons detected in the air indicate that a fusion reaction took place.")
+ to_chat(user, "Instability of the last fusion reaction: [instability].")
+ return
+
+//slime scanner
+
+/obj/item/slime_scanner
+ name = "slime scanner"
+ desc = "A device that analyzes a slime's internal composition and measures its stats."
+ icon = 'icons/obj/device.dmi'
+ icon_state = "adv_spectrometer"
+ item_state = "analyzer"
+ lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi'
+ w_class = WEIGHT_CLASS_SMALL
+ flags_1 = CONDUCT_1
+ throwforce = 0
+ throw_speed = 3
+ throw_range = 7
+ materials = list(MAT_METAL=30, MAT_GLASS=20)
+
+/obj/item/slime_scanner/attack(mob/living/M, mob/living/user)
+ if(user.stat || user.eye_blind)
+ return
+ if (!isslime(M))
+ to_chat(user, "This device can only scan slimes!")
+ return
+ var/mob/living/simple_animal/slime/T = M
+ slime_scan(T, user)
+
+/proc/slime_scan(mob/living/simple_animal/slime/T, mob/living/user)
+ to_chat(user, "========================")
+ to_chat(user, "Slime scan results:")
+ to_chat(user, "[T.colour] [T.is_adult ? "adult" : "baby"] slime")
+ to_chat(user, "Nutrition: [T.nutrition]/[T.get_max_nutrition()]")
+ if (T.nutrition < T.get_starve_nutrition())
+ to_chat(user, "Warning: slime is starving!")
+ else if (T.nutrition < T.get_hunger_nutrition())
+ to_chat(user, "Warning: slime is hungry")
+ to_chat(user, "Electric change strength: [T.powerlevel]")
+ to_chat(user, "Health: [round(T.health/T.maxHealth,0.01)*100]%")
+ if (T.slime_mutation[4] == T.colour)
+ to_chat(user, "This slime does not evolve any further.")
+ else
+ if (T.slime_mutation[3] == T.slime_mutation[4])
+ if (T.slime_mutation[2] == T.slime_mutation[1])
+ to_chat(user, "Possible mutation: [T.slime_mutation[3]]")
+ to_chat(user, "Genetic destability: [T.mutation_chance/2] % chance of mutation on splitting")
+ else
+ to_chat(user, "Possible mutations: [T.slime_mutation[1]], [T.slime_mutation[2]], [T.slime_mutation[3]] (x2)")
+ to_chat(user, "Genetic destability: [T.mutation_chance] % chance of mutation on splitting")
+ else
+ to_chat(user, "Possible mutations: [T.slime_mutation[1]], [T.slime_mutation[2]], [T.slime_mutation[3]], [T.slime_mutation[4]]")
+ to_chat(user, "Genetic destability: [T.mutation_chance] % chance of mutation on splitting")
+ if (T.cores > 1)
+ to_chat(user, "Multiple cores detected")
+ to_chat(user, "Growth progress: [T.amount_grown]/[SLIME_EVOLUTION_THRESHOLD]")
+ if(T.effectmod)
+ to_chat(user, "Core mutation in progress: [T.effectmod]")
+ to_chat(user, "Progress in core mutation: [T.applied] / [SLIME_EXTRACT_CROSSING_REQUIRED]")
+ to_chat(user, "========================")
+
+
+/obj/item/nanite_scanner
+ name = "nanite scanner"
+ icon = 'icons/obj/device.dmi'
+ icon_state = "nanite_scanner"
+ item_state = "nanite_remote"
+ lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
+ desc = "A hand-held body scanner able to detect nanites and their programming."
+ flags_1 = CONDUCT_1
+ item_flags = NOBLUDGEON
+ slot_flags = ITEM_SLOT_BELT
+ throwforce = 3
+ w_class = WEIGHT_CLASS_TINY
+ throw_speed = 3
+ throw_range = 7
+ materials = list(MAT_METAL=200)
+
+/obj/item/nanite_scanner/attack(mob/living/M, mob/living/carbon/human/user)
+ user.visible_message("[user] has analyzed [M]'s nanites.")
+
+ add_fingerprint(user)
+
+ var/response = SEND_SIGNAL(M, COMSIG_NANITE_SCAN, user, TRUE)
+ if(!response)
+ to_chat(user, "No nanites detected in the subject.")
+
+/obj/item/sequence_scanner
+ name = "genetic sequence scanner"
+ icon = 'icons/obj/device.dmi'
+ icon_state = "gene"
+ item_state = "healthanalyzer"
+ lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
+ desc = "A hand-held scanner able to swiftly scan someone for potential mutations. Hold near a DNA console to update from their database."
+ flags_1 = CONDUCT_1
+ item_flags = NOBLUDGEON
+ slot_flags = ITEM_SLOT_BELT
+ throwforce = 3
+ w_class = WEIGHT_CLASS_TINY
+ throw_speed = 3
+ throw_range = 7
+ materials = list(MAT_METAL=200)
+ var/list/discovered = list() //hit a dna console to update the scanners database
+
+/obj/item/sequence_scanner/attack(mob/living/M, mob/living/carbon/human/user)
+ user.visible_message("[user] has analyzed [M]'s genetic sequence.")
+
+ add_fingerprint(user)
+
+ gene_scan(M, user, src)
+
+/obj/item/sequence_scanner/afterattack(obj/O, mob/user, proximity)
+ . = ..()
+ if(!istype(O) || !proximity)
+ return
+
+ if(istype(O, /obj/machinery/computer/scan_consolenew))
+ var/obj/machinery/computer/scan_consolenew/C = O
+ if(C.stored_research)
+ to_chat(user, "[name] database updated.")
+ discovered = C.stored_research.discovered_mutations
+ else
+ to_chat(user,"No database to update from.")
+
+/proc/gene_scan(mob/living/carbon/C, mob/living/user, obj/item/sequence_scanner/G)
+ if(!iscarbon(C) || !C.has_dna())
+ return
+ to_chat(user, "[C.name]'s potential mutations.")
+ for(var/A in C.dna.mutation_index)
+ var/datum/mutation/human/HM = GET_INITIALIZED_MUTATION(A)
+ var/mut_name
+ if(G && (A in G.discovered))
+ mut_name = "[HM.name] ([HM.alias])"
+ else
+ mut_name = HM.alias
+ var/temp = GET_GENE_STRING(HM.type, C.dna)
+ var/display
+ for(var/i in 0 to length(temp) / DNA_MUTATION_BLOCKS-1)
+ if(i)
+ display += "-"
+ display += copytext(temp, 1 + i*DNA_MUTATION_BLOCKS, DNA_MUTATION_BLOCKS*(1+i) + 1)
+
+
+ to_chat(user, "- [mut_name] > [display]")
diff --git a/code/modules/reagents/chemistry/fermi/readme.md b/code/modules/reagents/chemistry/fermi/readme.md
new file mode 100644
index 00000000..4b897b6c
--- /dev/null
+++ b/code/modules/reagents/chemistry/fermi/readme.md
@@ -0,0 +1,23 @@
+How to code fermichem reactions:
+First off, probably read though the readme for standard reagent mechanisms, this builds on top of that.
+
+#bitflags
+for `datum/reagent/` you have the following options with `var/chemical_flags`:
+
+```
+REAGENT_DEAD_PROCESS calls on_mob_dead() if present in a dead body
+REAGENT_DONOTSPLIT Do not split the chem at all during processing
+REAGENT_ONLYINVERSE Only invert chem, no splitting
+REAGENT_ONMOBMERGE Call on_mob_life proc when reagents are merging.
+REAGENT_INVISIBLE Doesn't appear on handheld health analyzers.
+REAGENT_FORCEONNEW Forces a on_new() call without a data overhead
+REAGENT_SNEAKYNAME When inverted, the inverted chem uses the name of the original chem
+REAGENT_SPLITRETAINVOL Retains initial volume of chem when splitting
+```
+
+for `datum/chemical_reaction/` under `var/clear_conversion`
+
+```
+REACTION_CLEAR_IMPURE Convert into impure/pure on reaction completion
+REACTION_CLEAR_INVERSE Convert into inverse on reaction completion when purity is low enough
+```