mirror of
https://github.com/SPLURT-Station/S.P.L.U.R.T-Station-13.git
synced 2025-12-10 01:49:19 +00:00
990 lines
42 KiB
Plaintext
990 lines
42 KiB
Plaintext
|
|
/*
|
|
|
|
CONTAINS:
|
|
T-RAY
|
|
HEALTH ANALYZER
|
|
GAS ANALYZER
|
|
SLIME SCANNER
|
|
NANITE SCANNER
|
|
GENETICS SCANNER
|
|
|
|
*/
|
|
|
|
// Describes the three modes of scanning available for health analyzers
|
|
#define SCANMODE_HEALTH 0
|
|
#define SCANMODE_CHEMICAL 1
|
|
#define SCANMODE_WOUND 2
|
|
#define SCANNER_CONDENSED 0
|
|
#define SCANNER_VERBOSE 1
|
|
|
|
/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."
|
|
custom_price = PRICE_REALLY_CHEAP
|
|
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'
|
|
custom_materials = list(/datum/material/iron=150)
|
|
|
|
/obj/item/t_scanner/suicide_act(mob/living/carbon/user)
|
|
user.visible_message("<span class='suicide'>[user] begins to emit terahertz-rays into [user.p_their()] brain with [src]! It looks like [user.p_theyre()] trying to commit suicide!</span>")
|
|
return TOXLOSS
|
|
|
|
/obj/item/t_scanner/attack_self(mob/user)
|
|
|
|
on = !on
|
|
icon_state = copytext_char(icon_state, 1, -1) + "[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
|
|
custom_materials = list(/datum/material/iron=200)
|
|
var/mode = 1
|
|
var/scanmode = SCANMODE_HEALTH
|
|
var/advanced = FALSE
|
|
|
|
/obj/item/healthanalyzer/suicide_act(mob/living/carbon/user)
|
|
user.visible_message("<span class='suicide'>[user] begins to analyze [user.p_them()]self with [src]! The display shows that [user.p_theyre()] dead!</span>")
|
|
return BRUTELOSS
|
|
|
|
/obj/item/healthanalyzer/attack_self(mob/user)
|
|
scanmode = (scanmode + 1) % 3
|
|
switch(scanmode)
|
|
if(SCANMODE_HEALTH)
|
|
to_chat(user, "<span class='notice'>You switch the health analyzer to check physical health.</span>")
|
|
if(SCANMODE_CHEMICAL)
|
|
to_chat(user, "<span class='notice'>You switch the health analyzer to scan chemical contents.</span>")
|
|
if(SCANMODE_WOUND)
|
|
to_chat(user, "<span class='notice'>You switch the health analyzer to report extra info on wounds.</span>")
|
|
|
|
/obj/item/healthanalyzer/attack(mob/living/M, mob/living/carbon/human/user)
|
|
flick("[icon_state]-scan", src) //makes it so that it plays the scan animation upon scanning, including clumsy scanning
|
|
|
|
// Clumsiness/brain damage check
|
|
if ((HAS_TRAIT(user, TRAIT_CLUMSY) || HAS_TRAIT(user, TRAIT_DUMB)) && prob(50))
|
|
user.visible_message("<span class='warning'>[user] analyzes the floor's vitals!</span>", \
|
|
"<span class='notice'>You stupidly try to analyze the floor's vitals!</span>")
|
|
to_chat(user, "<span class='info'>Analyzing results for The floor:\n\tOverall status: <b>Healthy</b></span>\
|
|
\n<span class='info'>Key: <font color='blue'>Suffocation</font>/<font color='green'>Toxin</font>/<font color='#FF8000'>Burn</font>/<font color='red'>Brute</font></span>\
|
|
\n<span class='info'>\tDamage specifics: <font color='blue'>0</font>-<font color='green'>0</font>-<font color='#FF8000'>0</font>-<font color='red'>0</font></span>\
|
|
\n<span class='info'>Body temperature: ???</span>")
|
|
return
|
|
|
|
user.visible_message("<span class='notice'>[user] analyzes [M]'s vitals.</span>", \
|
|
"<span class='notice'>You analyze [M]'s vitals.</span>")
|
|
|
|
if(scanmode == SCANMODE_HEALTH)
|
|
healthscan(user, M, mode, advanced)
|
|
else if(scanmode == SCANMODE_CHEMICAL)
|
|
chemscan(user, M)
|
|
else
|
|
woundscan(user, M, src)
|
|
|
|
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 ? "<span class='alert'><b>Deceased</b></span>" : "<b>[round(M.health/M.maxHealth,0.01)*100] % healthy</b>")
|
|
|
|
if(HAS_TRAIT(M, TRAIT_FAKEDEATH) && !advanced)
|
|
mob_status = "<span class='alert'><b>Deceased</b></span>"
|
|
oxy_loss = max(rand(1, 40), oxy_loss, (300 - (tox_loss + fire_loss + brute_loss))) // Random oxygen loss
|
|
|
|
var/msg = "<div class='infobox'><span class='info'>Analyzing results for [M]:\n\tOverall status: [mob_status]</span>"
|
|
|
|
// Damage descriptions
|
|
if(brute_loss > 10)
|
|
msg += "\n\t<span class='alert'>[brute_loss > 50 ? "Severe" : "Minor"] tissue damage detected.</span>"
|
|
if(fire_loss > 10)
|
|
msg += "\n\t<span class='alert'>[fire_loss > 50 ? "Severe" : "Minor"] burn damage detected.</span>"
|
|
if(oxy_loss > 10)
|
|
msg += "\n\t<span class='info'><span class='alert'>[oxy_loss > 50 ? "Severe" : "Minor"] oxygen deprivation detected.</span>"
|
|
if(tox_loss > 10)
|
|
msg += "\n\t<span class='alert'>[tox_loss > 50 ? "Severe" : "Minor"] amount of [HAS_TRAIT(M, TRAIT_ROBOTIC_ORGANISM) ? "system corruption" : "toxin damage"] detected.</span>"
|
|
if(M.getStaminaLoss())
|
|
msg += "\n\t<span class='alert'>Subject appears to be suffering from fatigue.</span>"
|
|
if(advanced)
|
|
msg += "\n\t<span class='info'>Fatigue Level: [M.getStaminaLoss()]%.</span>"
|
|
if (M.getCloneLoss())
|
|
msg += "\n\t<span class='alert'>Subject appears to have [M.getCloneLoss() > 30 ? "Severe" : "Minor"] cellular damage.</span>"
|
|
if(advanced)
|
|
msg += "\n\t<span class='info'>Cellular Damage Level: [M.getCloneLoss()].</span>"
|
|
if(ishuman(M))
|
|
var/mob/living/carbon/human/H = M
|
|
if(advanced && H.has_dna())
|
|
msg += "\n\t<span class='info'>Genetic Stability: [H.dna.stability]%.</span>"
|
|
|
|
// Body part damage report
|
|
var/list/dmgreport = list()
|
|
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)
|
|
dmgreport += "<table style='margin-left:33px'><tr><font face='Verdana'>\
|
|
<td style='width: 90px;'><font color='#0000CC'>Damage:</font></td>\
|
|
<td style='width: 55px;'><font color='red'><b>Brute</b></font></td>\
|
|
<td style='width: 45px;'><font color='orange'><b>Burn</b></font></td>\
|
|
<td style='width: 45px;'><font color='green'><b>[HAS_TRAIT(C, TRAIT_ROBOTIC_ORGANISM) ? "Corruption" :"Toxin"]</b></font></td>\
|
|
<td style='width: 90px;'><font color='purple'><b>Suffocation</b></font></td></tr>\
|
|
<tr><td><font color='#0000CC'>Overall:</font></td>\
|
|
<td><font color='red'>[brute_loss]</font></td>\
|
|
<td><font color='orange'>[fire_loss]</font></td>\
|
|
<td><font color='green'>[tox_loss]</font></td>\
|
|
<td><font color='purple'>[oxy_loss]</font></td></tr>"
|
|
|
|
for(var/o in damaged)
|
|
var/obj/item/bodypart/org = o //head, left arm, right arm, etc.
|
|
dmgreport += "<tr><td><font color='#0000CC'>[capitalize(org.name)]:</font></td>\
|
|
<td><font color='red'>[(org.brute_dam > 0) ? "[org.brute_dam]" : "0"]</font></td>\
|
|
<td><font color='orange'>[(org.burn_dam > 0) ? "[org.burn_dam]" : "0"]</font></td></tr>"
|
|
dmgreport += "</table>"
|
|
msg += "\n[dmgreport.Join()]"
|
|
|
|
msg += "\n"
|
|
|
|
//Organ damages report
|
|
var/heart_ded = FALSE
|
|
if(iscarbon(M))
|
|
var/mob/living/carbon/C = M
|
|
var/mob/living/carbon/human/H = M
|
|
for(var/organ in C.internal_organs)
|
|
var/temp_message
|
|
var/damage_message
|
|
var/obj/item/organ/O = organ
|
|
|
|
//EYES
|
|
if(istype(O, /obj/item/organ/eyes))
|
|
var/obj/item/organ/eyes/eyes = O
|
|
if(advanced)
|
|
if(HAS_TRAIT(C, TRAIT_BLIND))
|
|
temp_message += " <span class='alert'>Subject is blind.</span>"
|
|
if(HAS_TRAIT(C, TRAIT_NEARSIGHT))
|
|
temp_message += " <span class='alert'>Subject is nearsighted.</span>"
|
|
if(eyes.damage > 30)
|
|
damage_message += " <span class='alert'>Subject has severe eye damage.</span>"
|
|
else if(eyes.damage > 20)
|
|
damage_message += " <span class='alert'>Subject has significant eye damage.</span>"
|
|
else if(eyes.damage)
|
|
damage_message += " <span class='alert'>Subject has minor eye damage.</span>"
|
|
|
|
|
|
//EARS
|
|
else if(istype(O, /obj/item/organ/ears))
|
|
var/obj/item/organ/ears/ears = O
|
|
if(advanced)
|
|
if(HAS_TRAIT_FROM(C, TRAIT_DEAF, GENETIC_MUTATION))
|
|
temp_message += " <span class='alert'>Subject is genetically deaf.</span>"
|
|
else if(HAS_TRAIT(C, TRAIT_DEAF))
|
|
temp_message += " <span class='alert'>Subject is deaf.</span>"
|
|
else
|
|
if(ears.damage)
|
|
damage_message += " <span class='alert'>Subject has [ears.damage > ears.maxHealth ? "permanent ": "temporary "]hearing damage.</span>"
|
|
if(ears.deaf)
|
|
damage_message += " <span class='alert'>Subject is [ears.damage > ears.maxHealth ? "permanently ": "temporarily "] deaf.</span>"
|
|
|
|
|
|
//BRAIN
|
|
else if(istype(O, /obj/item/organ/brain))
|
|
if (C.getOrganLoss(ORGAN_SLOT_BRAIN) >= 200)
|
|
damage_message += " <span class='alert'>Subject's brain non-functional. Neurine injection recomended.</span>"
|
|
else if (C.getOrganLoss(ORGAN_SLOT_BRAIN) >= 120)
|
|
damage_message += " <span class='alert'>Severe brain damage detected. Subject likely to have mental traumas.</span>"
|
|
else if (C.getOrganLoss(ORGAN_SLOT_BRAIN) >= 45)
|
|
damage_message += " <span class='alert'>Brain damage detected.</span>"
|
|
if(advanced)
|
|
temp_message += " <span class='info'>Brain Activity Level: [(200 - M.getOrganLoss(ORGAN_SLOT_BRAIN))/2]%.</span>"
|
|
|
|
//TRAUMAS
|
|
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
|
|
temp_message += " <span class='alert'>Cerebral traumas detected: subject appears to be suffering from [english_list(trauma_text)].</span>"
|
|
if(C.roundstart_quirks.len)
|
|
temp_message += " <span class='info'>Subject has the following physiological traits: [C.get_trait_string()].</span>"
|
|
|
|
if(ishuman(C) && advanced)
|
|
//MON PETIT CHAUFFEUR
|
|
if(H.hallucinating())
|
|
temp_message += " <span class='info'>Subject is hallucinating.</span>"
|
|
|
|
//MKUltra
|
|
if(H.has_status_effect(/datum/status_effect/chem/enthrall))
|
|
temp_message += " <span class='info'>Subject has abnormal brain fuctions.</span>"
|
|
|
|
//Astrogen shenanigans
|
|
if(H.reagents.has_reagent(/datum/reagent/fermi/astral))
|
|
if(H.mind)
|
|
temp_message += " <span class='danger'>Warning: subject may be possesed.</span>"
|
|
else
|
|
temp_message += " <span class='notice'>Subject appears to be astrally projecting.</span>"
|
|
|
|
|
|
//LIVER
|
|
else if(istype(O, /obj/item/organ/liver))
|
|
var/obj/item/organ/liver/L = O
|
|
if(L.organ_flags & ORGAN_FAILING && H.stat != DEAD) //might be depreciated
|
|
temp_message += "<span class='danger'>Subject is suffering from liver failure: Apply Corazone and begin a liver transplant immediately!</span>"
|
|
|
|
//HEART
|
|
else if(ishuman(M) && (istype(O, /obj/item/organ/heart)))
|
|
var/obj/item/organ/heart/He = O
|
|
if(H.undergoing_cardiac_arrest() && H.stat != DEAD)
|
|
temp_message += " <span class='danger'>Subject suffering from heart attack: Apply defibrillation or other electric shock <b>immediately!</b></span>"
|
|
if(He.organ_flags & ORGAN_FAILING)
|
|
heart_ded = TRUE
|
|
|
|
//TONGUE
|
|
else if(istype(O, /obj/item/organ/tongue))
|
|
var/obj/item/organ/tongue/T = O
|
|
if(T.name == "fluffy tongue")
|
|
temp_message += " <span class='danger'>Subject is suffering from a fluffified tongue. Suggested cure: Yamerol or a tongue transplant.</span>"
|
|
|
|
|
|
//GENERAL HANDLER
|
|
if(!damage_message)
|
|
if(O.organ_flags & ORGAN_FAILING)
|
|
damage_message += " <span class='alert'><b>Chronic [O.name] failure detected.</b></span>"
|
|
else if(O.damage > O.high_threshold)
|
|
damage_message += " <span class='alert'>Acute [O.name] failure detected.</span>"
|
|
else if(O.damage > O.low_threshold && advanced)
|
|
damage_message += " <font color='red'>Minor [O.name] failure detected.</span>"
|
|
|
|
if(temp_message || damage_message)
|
|
msg += "\t<b><span class='info'>[uppertext(O.name)]:</b></span> [damage_message] [temp_message]\n"
|
|
|
|
|
|
|
|
//END; LOOK FOR MISSING ORGANS?
|
|
var/breathes = TRUE
|
|
var/blooded = TRUE
|
|
if(C.dna && C.dna.species)
|
|
if(!HAS_TRAIT_FROM(C, TRAIT_AUXILIARY_LUNGS, SPECIES_TRAIT) && HAS_TRAIT_FROM(C, TRAIT_NOBREATH, SPECIES_TRAIT))
|
|
breathes = FALSE
|
|
if(NOBLOOD in C.dna.species.species_traits)
|
|
blooded = FALSE
|
|
var/has_liver = C.dna && !(NOLIVER in C.dna.species.species_traits)
|
|
var/has_stomach = C.dna && !(NOSTOMACH in C.dna.species.species_traits)
|
|
if(!M.getorganslot(ORGAN_SLOT_EYES))
|
|
msg += "\t<span class='alert'><b>Subject does not have eyes.</b></span>\n"
|
|
if(!M.getorganslot(ORGAN_SLOT_EARS))
|
|
msg += "\t<span class='alert'><b>Subject does not have ears.</b></span>\n"
|
|
if(!M.getorganslot(ORGAN_SLOT_BRAIN))
|
|
msg += "\t<span class='alert'><b>Subject's brain function is non-existent!</b></span>\n"
|
|
if(has_liver && !M.getorganslot(ORGAN_SLOT_LIVER))
|
|
msg += "\t<span class='alert'><b>Subject's liver is missing!</b></span>\n"
|
|
if(blooded && !M.getorganslot(ORGAN_SLOT_HEART))
|
|
msg += "\t<span class='alert'><b>Subject's heart is missing!</b></span>\n"
|
|
if(breathes && !M.getorganslot(ORGAN_SLOT_LUNGS))
|
|
msg += "\t<span class='alert'><b>Subject's lungs have collapsed from trauma!</b></span>\n"
|
|
if(has_stomach && !M.getorganslot(ORGAN_SLOT_STOMACH))
|
|
msg += "\t<span class='alert'><b>Subject's stomach is missing!</span>\n"
|
|
|
|
|
|
if(M.radiation)
|
|
msg += "\t<span class='alert'>Subject is irradiated.</span>\n"
|
|
msg += "\t<span class='info'>Radiation Level: [M.radiation] rad</span>\n"
|
|
|
|
|
|
|
|
// Species and body temperature
|
|
var/mob/living/carbon/human/H = M //Start to use human only stuff here
|
|
if(ishuman(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
|
|
else if (S.flying_species != initial(S.flying_species))
|
|
mutant = TRUE
|
|
|
|
msg += "\t<span class='info'>Reported Species: [H.spec_trait_examine_font()][H.dna.custom_species ? H.dna.custom_species : S.name]</font></span>\n"
|
|
msg += "\t<span class='info'>Base Species: [H.spec_trait_examine_font()][S.name]</font></span>\n"
|
|
if(mutant)
|
|
msg += "\t<span class='info'>Subject has mutations present.</span>\n"
|
|
msg += "\t<span class='info'>Body temperature: [round(M.bodytemperature-T0C,0.1)] °C ([round(M.bodytemperature*1.8-459.67,0.1)] °F)</span>\n"
|
|
|
|
// Time of death
|
|
if(M.tod && (M.stat == DEAD || ((HAS_TRAIT(M, TRAIT_FAKEDEATH)) && !advanced)))
|
|
msg += "<span class='info'>Time of Death:</span> [M.tod]\n"
|
|
var/tdelta = round(world.time - M.timeofdeath)
|
|
if(tdelta < (DEFIB_TIME_LIMIT * 10))
|
|
if(heart_ded)
|
|
msg += "<span class='danger'>Subject died [DisplayTimeText(tdelta)] ago, heart requires surgical intervention for defibrillation.</span>"
|
|
else
|
|
msg += "<span class='danger'>Subject died [DisplayTimeText(tdelta)] ago, defibrillation may be possible!</span>"
|
|
if(advanced)
|
|
if(H.get_ghost() || H.key || H.client)//Since it can last a while.
|
|
msg += "<span class='danger'> Intervention recommended.</span>\n"
|
|
else
|
|
msg += "\n"
|
|
// Wounds
|
|
if(iscarbon(M))
|
|
var/mob/living/carbon/C = M
|
|
var/list/wounded_parts = C.get_wounded_bodyparts()
|
|
for(var/i in wounded_parts)
|
|
var/obj/item/bodypart/wounded_part = i
|
|
msg += "<span class='alert ml-1'><b>Warning: Physical trauma[LAZYLEN(wounded_part.wounds) > 1? "s" : ""] detected in [wounded_part.name]</b>"
|
|
for(var/k in wounded_part.wounds)
|
|
var/datum/wound/W = k
|
|
msg += "<div class='ml-2'>Type: [W.name]\nSeverity: [W.severity_text()]\nRecommended Treatment: [W.treat_text]</div>\n" // less lines than in woundscan() so we don't overload people trying to get basic med info
|
|
msg += "</span>\n"
|
|
|
|
for(var/thing in M.diseases)
|
|
var/datum/disease/D = thing
|
|
if(!(D.visibility_flags & HIDDEN_SCANNER))
|
|
msg += "<span class='alert'><b>Warning: [D.form] detected</b>\nName: [D.name].\nType: [D.spread_text].\nStage: [D.stage]/[D.max_stages].\nPossible Cure: [D.cure_text]</span>\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))
|
|
if(H.is_bleeding())
|
|
msg += "<span class='danger'>Subject is bleeding!</span>\n"
|
|
var/blood_percent = round((C.scan_blood_volume() / (BLOOD_VOLUME_NORMAL * C.blood_ratio))*100)
|
|
var/integrated_blood_percent = round((C.integrating_blood / (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() + C.integrating_blood) <= (BLOOD_VOLUME_SAFE * C.blood_ratio) && (C.scan_blood_volume() + C.integrating_blood) > (BLOOD_VOLUME_OKAY*C.blood_ratio))
|
|
msg += "<span class='danger'>LOW [HAS_TRAIT(C, TRAIT_ROBOTIC_ORGANISM) ? "coolant" : "blood"] level [blood_percent] %, [C.scan_blood_volume()] cl[C.integrating_blood? ", with [integrated_blood_percent] % of it integrating, [C.integrating_blood] cl " : ""].</span> <span class='info'>type: [blood_type]</span>\n"
|
|
else if((C.scan_blood_volume() + C.integrating_blood) <= (BLOOD_VOLUME_OKAY * C.blood_ratio))
|
|
msg += "<span class='danger'>CRITICAL [HAS_TRAIT(C, TRAIT_ROBOTIC_ORGANISM) ? "coolant" : "blood"] level [blood_percent] %, [C.scan_blood_volume()] cl[C.integrating_blood? ", with [integrated_blood_percent] % of it integrating, [C.integrating_blood] cl " : ""].</span> <span class='info'>type: [blood_type]</span>\n"
|
|
else
|
|
msg += "<span class='info'>[HAS_TRAIT(C, TRAIT_ROBOTIC_ORGANISM) ? "Coolant" : "Blood"] level [blood_percent] %, [C.scan_blood_volume()] cl[C.integrating_blood? ", with [integrated_blood_percent] % of it integrating, [C.integrating_blood] cl " : ""]. type: [blood_type]</span>\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].<br>"
|
|
if(cyberimp_detect)
|
|
msg += "<span class='notice'>Detected cybernetic modifications:</span>\n"
|
|
msg += "<span class='notice'>[cyberimp_detect]</span>\n"
|
|
msg += "</div>"
|
|
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 = "<div class='infobox'><span class='info'>"
|
|
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 += "<span class='notice'>Subject contains the following reagents:</span>\n"
|
|
for(var/datum/reagent/R in reagents)
|
|
var/invalid_reagent = is_reagent_processing_invalid(R, M)
|
|
msg += "<span class='notice'>[invalid_reagent ? "<font color='grey'>" : ""][R.volume] units of [R.name][invalid_reagent ? "</font>" : ""][R.overdosed == 1 ? "</span> - <span class='boldannounce'>OVERDOSING</span>" : ".</span>"]\n"
|
|
else
|
|
msg += "<span class='notice'>Subject contains no reagents.</span>\n"
|
|
|
|
else
|
|
msg += "<span class='notice'>Subject contains no reagents.</span>\n"
|
|
if(M.reagents.addiction_list.len)
|
|
msg += "<span class='boldannounce'>Subject is addicted to the following reagents:</span>\n"
|
|
for(var/datum/reagent/R in M.reagents.addiction_list)
|
|
msg += "<span class='danger'>[R.name]</span>\n"
|
|
else
|
|
msg += "<span class='notice'>Subject is not addicted to any reagents.</span>\n"
|
|
|
|
var/datum/reagent/impure/fermiTox/F = M.reagents.has_reagent(/datum/reagent/impure/fermiTox)
|
|
if(istype(F,/datum/reagent/impure/fermiTox))
|
|
switch(F.volume)
|
|
if(5 to 10)
|
|
msg += "<span class='notice'>Subject contains a low amount of toxic isomers.</span>\n"
|
|
if(10 to 25)
|
|
msg += "<span class='danger'>Subject contains toxic isomers.</span>\n"
|
|
if(25 to 50)
|
|
msg += "<span class='danger'>Subject contains a substantial amount of toxic isomers.</span>\n"
|
|
if(50 to 95)
|
|
msg += "<span class='danger'>Subject contains a high amount of toxic isomers.</span>\n"
|
|
if(95 to INFINITY)
|
|
msg += "<span class='danger'>Subject contains a extremely dangerous amount of toxic isomers.</span>\n"
|
|
|
|
msg += "</span></div>"
|
|
to_chat(user, msg)
|
|
|
|
/obj/item/healthanalyzer/verb/toggle_mode()
|
|
set name = "Switch Verbosity"
|
|
set category = "Object"
|
|
|
|
var/mob/living/L = usr
|
|
if(!istype(L) || !CHECK_MOBILITY(L, MOBILITY_USE))
|
|
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
|
|
|
|
/// Displays wounds with extended information on their status vs medscanners
|
|
/proc/woundscan(mob/user, mob/living/carbon/patient, obj/item/healthanalyzer/wound/scanner)
|
|
if(!istype(patient))
|
|
return
|
|
|
|
var/render_list = ""
|
|
for(var/i in patient.get_wounded_bodyparts())
|
|
var/obj/item/bodypart/wounded_part = i
|
|
render_list += "<span class='alert ml-1'><b>Warning: Physical trauma[LAZYLEN(wounded_part.wounds) > 1? "s" : ""] detected in [wounded_part.name]</b>"
|
|
for(var/k in wounded_part.wounds)
|
|
var/datum/wound/W = k
|
|
render_list += "<div class='ml-2'>[W.get_scanner_description()]</div>\n"
|
|
render_list += "</span>"
|
|
|
|
if(render_list == "")
|
|
if(istype(scanner))
|
|
// Only emit the cheerful scanner message if this scan came from a scanner
|
|
playsound(scanner, 'sound/machines/ping.ogg', 50, FALSE)
|
|
to_chat(user, "<span class='notice'>\The [scanner] makes a happy ping and briefly displays a smiley face with several exclamation points! It's really excited to report that [patient] has no wounds!</span>")
|
|
else
|
|
to_chat(user, "<span class='notice ml-1'>No wounds detected in subject.</span>")
|
|
else
|
|
to_chat(user, jointext(render_list, ""))
|
|
|
|
/obj/item/healthanalyzer/wound
|
|
name = "first aid analyzer"
|
|
icon_state = "adv_spectrometer"
|
|
desc = "A prototype MeLo-Tech medical scanner used to diagnose injuries and recommend treatment for serious wounds, but offers no further insight into the patient's health. You hope the final version is less annoying to read!"
|
|
var/next_encouragement
|
|
var/greedy
|
|
|
|
/obj/item/healthanalyzer/wound/attack_self(mob/user)
|
|
if(next_encouragement < world.time)
|
|
playsound(src, 'sound/machines/ping.ogg', 50, FALSE)
|
|
var/list/encouragements = list("briefly displays a happy face, gazing emptily at you", "briefly displays a spinning cartoon heart", "displays an encouraging message about eating healthy and exercising", \
|
|
"reminds you that everyone is doing their best", "displays a message wishing you well", "displays a sincere thank-you for your interest in first-aid", "formally absolves you of all your sins")
|
|
to_chat(user, "<span class='notice'>\The [src] makes a happy ping and [pick(encouragements)]!</span>")
|
|
next_encouragement = world.time + 10 SECONDS
|
|
greedy = FALSE
|
|
else if(!greedy)
|
|
to_chat(user, "<span class='warning'>\The [src] displays an eerily high-definition frowny face, chastizing you for asking it for too much encouragement.</span>")
|
|
greedy = TRUE
|
|
else
|
|
playsound(src, 'sound/machines/buzz-sigh.ogg', 50, FALSE)
|
|
if(isliving(user))
|
|
var/mob/living/L = user
|
|
to_chat(L, "<span class='warning'>\The [src] makes a disappointed buzz and pricks your finger for being greedy. Ow!</span>")
|
|
L.adjustBruteLoss(4)
|
|
L.dropItemToGround(src)
|
|
|
|
/obj/item/healthanalyzer/wound/attack(mob/living/carbon/patient, mob/living/carbon/human/user)
|
|
add_fingerprint(user)
|
|
user.visible_message("<span class='notice'>[user] scans [patient] for serious injuries.</span>", "<span class='notice'>You scan [patient] for serious injuries.</span>")
|
|
|
|
if(!istype(patient))
|
|
playsound(src, 'sound/machines/buzz-sigh.ogg', 30, TRUE)
|
|
to_chat(user, "<span class='notice'>\The [src] makes a sad buzz and briefly displays a frowny face, indicating it can't scan [patient].</span>")
|
|
return
|
|
|
|
woundscan(user, patient, src)
|
|
|
|
/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
|
|
custom_materials = list(/datum/material/iron=30, /datum/material/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)
|
|
. = ..()
|
|
. += "<span class='notice'>Alt-click [src] to activate the barometer function.</span>"
|
|
|
|
/obj/item/analyzer/suicide_act(mob/living/carbon/user)
|
|
user.visible_message("<span class='suicide'>[user] begins to analyze [user.p_them()]self with [src]! The display shows that [user.p_theyre()] dead!</span>")
|
|
return BRUTELOSS
|
|
|
|
/obj/item/analyzer/attack_self(mob/user)
|
|
add_fingerprint(user)
|
|
|
|
if (user.stat || user.eye_blind)
|
|
return
|
|
|
|
//Functionality moved down to proc/scan_turf()
|
|
var/turf/location = get_turf(user)
|
|
if(!istype(location))
|
|
return
|
|
|
|
scan_turf(user, location)
|
|
|
|
/obj/item/analyzer/AltClick(mob/user) //Barometer output for measuring when the next storm happens
|
|
. = ..()
|
|
|
|
if(user.canUseTopic(src))
|
|
. = TRUE
|
|
if(cooldown)
|
|
to_chat(user, "<span class='warning'>[src]'s barometer function is preparing itself.</span>")
|
|
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, "<span class='warning'>[src]'s barometer function won't work indoors!</span>")
|
|
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, "<span class='warning'>[src]'s barometer function can't trace anything while the storm is [ongoing_weather.stage == MAIN_STAGE ? "already here!" : "winding down."]</span>")
|
|
return
|
|
|
|
to_chat(user, "<span class='notice'>The next [ongoing_weather] will hit in [butchertime(ongoing_weather.next_hit_time - world.time)].</span>")
|
|
if(ongoing_weather.aesthetic)
|
|
to_chat(user, "<span class='warning'>[src]'s barometer function says that the next storm will breeze on by.</span>")
|
|
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, "<span class='warning'>[src]'s barometer function was unable to trace any weather patterns.</span>")
|
|
else
|
|
to_chat(user, "<span class='warning'>[src]'s barometer function says a storm will land in approximately [butchertime(fixed)].</span>")
|
|
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, "<span class='notice'>[src]'s barometer function is ready!</span>")
|
|
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, visible = TRUE)
|
|
var/icon = target
|
|
if(visible)
|
|
user.visible_message("[user] has used the analyzer on [icon2html(icon, viewers(user))] [target].", "<span class='notice'>You use the analyzer on [icon2html(icon, user)] [target].</span>")
|
|
to_chat(user, "<span class='boldnotice'>Results of analysis of [icon2html(icon, user)] [target].</span>")
|
|
|
|
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, "<span class='boldnotice'>Node [airs.Find(g)]</span>")
|
|
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.return_temperature()
|
|
var/cached_scan_results = air_contents.analyzer_results
|
|
|
|
if(total_moles > 0)
|
|
to_chat(user, "<span class='notice'>Moles: [round(total_moles, 0.01)] mol</span>")
|
|
to_chat(user, "<span class='notice'>Volume: [volume] L</span>")
|
|
to_chat(user, "<span class='notice'>Pressure: [round(pressure,0.01)] kPa</span>")
|
|
|
|
for(var/id in air_contents.get_gases())
|
|
if(air_contents.get_moles(id) >= 0.005)
|
|
var/gas_concentration = air_contents.get_moles(id)/total_moles
|
|
to_chat(user, "<span class='notice'>[GLOB.gas_data.names[id]]: [round(gas_concentration*100, 0.01)] % ([round(air_contents.get_moles(id), 0.01)] mol)</span>")
|
|
to_chat(user, "<span class='notice'>Temperature: [round(temperature - T0C,0.01)] °C ([round(temperature, 0.01)] K)</span>")
|
|
|
|
else
|
|
if(airs.len > 1)
|
|
to_chat(user, "<span class='notice'>This node is empty!</span>")
|
|
else
|
|
to_chat(user, "<span class='notice'>[target] is empty!</span>")
|
|
|
|
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)
|
|
var/tier = instability2text(instability)
|
|
to_chat(user, "<span class='boldnotice'>Large amounts of free neutrons detected in the air indicate that a fusion reaction took place.</span>")
|
|
to_chat(user, "<span class='notice'>Instability of the last fusion reaction: [instability]\n This indicates it was [tier]</span>")
|
|
return
|
|
|
|
/obj/item/analyzer/proc/scan_turf(mob/user, turf/location)
|
|
|
|
var/datum/gas_mixture/environment = location.return_air()
|
|
|
|
var/pressure = environment.return_pressure()
|
|
var/total_moles = environment.total_moles()
|
|
var/cached_scan_results = environment.analyzer_results
|
|
|
|
to_chat(user, "<span class='info'><B>Results:</B></span>")
|
|
if(abs(pressure - ONE_ATMOSPHERE) < 10)
|
|
to_chat(user, "<span class='info'>Pressure: [round(pressure, 0.01)] kPa</span>")
|
|
else
|
|
to_chat(user, "<span class='alert'>Pressure: [round(pressure, 0.01)] kPa</span>")
|
|
if(total_moles)
|
|
|
|
var/o2_concentration = environment.get_moles(GAS_O2)/total_moles
|
|
var/n2_concentration = environment.get_moles(GAS_N2)/total_moles
|
|
var/co2_concentration = environment.get_moles(GAS_CO2)/total_moles
|
|
var/plasma_concentration = environment.get_moles(GAS_PLASMA)/total_moles
|
|
|
|
if(abs(n2_concentration - N2STANDARD) < 20)
|
|
to_chat(user, "<span class='info'>Nitrogen: [round(n2_concentration*100, 0.01)] % ([round(environment.get_moles(GAS_N2), 0.01)] mol)</span>")
|
|
else
|
|
to_chat(user, "<span class='alert'>Nitrogen: [round(n2_concentration*100, 0.01)] % ([round(environment.get_moles(GAS_N2), 0.01)] mol)</span>")
|
|
|
|
if(abs(o2_concentration - O2STANDARD) < 2)
|
|
to_chat(user, "<span class='info'>Oxygen: [round(o2_concentration*100, 0.01)] % ([round(environment.get_moles(GAS_O2), 0.01)] mol)</span>")
|
|
else
|
|
to_chat(user, "<span class='alert'>Oxygen: [round(o2_concentration*100, 0.01)] % ([round(environment.get_moles(GAS_O2), 0.01)] mol)</span>")
|
|
|
|
if(co2_concentration > 0.01)
|
|
to_chat(user, "<span class='alert'>CO2: [round(co2_concentration*100, 0.01)] % ([round(environment.get_moles(GAS_CO2), 0.01)] mol)</span>")
|
|
else
|
|
to_chat(user, "<span class='info'>CO2: [round(co2_concentration*100, 0.01)] % ([round(environment.get_moles(GAS_CO2), 0.01)] mol)</span>")
|
|
|
|
if(plasma_concentration > 0.005)
|
|
to_chat(user, "<span class='alert'>Plasma: [round(plasma_concentration*100, 0.01)] % ([round(environment.get_moles(GAS_PLASMA), 0.01)] mol)</span>")
|
|
else
|
|
to_chat(user, "<span class='info'>Plasma: [round(plasma_concentration*100, 0.01)] % ([round(environment.get_moles(GAS_PLASMA), 0.01)] mol)</span>")
|
|
|
|
for(var/id in environment.get_gases())
|
|
if(id in GLOB.hardcoded_gases)
|
|
continue
|
|
var/gas_concentration = environment.get_moles(id)/total_moles
|
|
to_chat(user, "<span class='alert'>[GLOB.gas_data.names[id]]: [round(gas_concentration*100, 0.01)] % ([round(environment.get_moles(id), 0.01)] mol)</span>")
|
|
to_chat(user, "<span class='info'>Temperature: [round(environment.return_temperature()-T0C, 0.01)] °C ([round(environment.return_temperature(), 0.01)] K)</span>")
|
|
|
|
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)
|
|
var/tier = instability2text(instability)
|
|
to_chat(user, "<span class='boldnotice'>Large amounts of free neutrons detected in the air indicate that a fusion reaction took place.</span>")
|
|
to_chat(user, "<span class='notice'>Instability of the last fusion reaction: [instability]\n This indicates it was [tier].</span>")
|
|
|
|
/obj/item/analyzer/ranged
|
|
desc = "A hand-held scanner which uses advanced spectroscopy and infrared readings to analyze gases as a distance. Alt-Click to use the built in barometer function."
|
|
name = "long-range analyzer"
|
|
icon = 'icons/obj/device.dmi'
|
|
icon_state = "ranged_analyzer"
|
|
|
|
/obj/item/analyzer/ranged/afterattack(atom/target, mob/user, proximity_flag, click_parameters)
|
|
. = ..()
|
|
if(target.tool_act(user, src, tool_behaviour))
|
|
return
|
|
// Tool act didn't scan it, so let's get it's turf.
|
|
var/turf/location = get_turf(target)
|
|
scan_turf(user, location)
|
|
|
|
//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
|
|
custom_materials = list(/datum/material/iron=30, /datum/material/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, "<span class='warning'>This device can only scan slimes!</span>")
|
|
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)
|
|
var/output = "<div class='infobox'><b>Slime scan results:</b>"
|
|
output += "\n<span class='notice'>[T.colour] [T.is_adult ? "adult" : "baby"] slime</span>"
|
|
output += "\nNutrition: [T.nutrition]/[T.get_max_nutrition()]"
|
|
if (T.nutrition < T.get_starve_nutrition())
|
|
output += "\n<span class='warning'>Warning: slime is starving!</span>"
|
|
else if (T.nutrition < T.get_hunger_nutrition())
|
|
output += "\n<span class='warning'>Warning: slime is hungry</span>"
|
|
output += "\nElectric change strength: [T.powerlevel]"
|
|
output += "\nHealth: [round(T.health/T.maxHealth,0.01)*100]%"
|
|
if (T.slime_mutation[4] == T.colour)
|
|
output += "\nThis 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])
|
|
output += "\nPossible mutation: [T.slime_mutation[3]]"
|
|
output += "\nGenetic destability: [T.mutation_chance/2] % chance of mutation on splitting"
|
|
else
|
|
output += "\nPossible mutations: [T.slime_mutation[1]], [T.slime_mutation[2]], [T.slime_mutation[3]] (x2)"
|
|
output += "\nGenetic destability: [T.mutation_chance] % chance of mutation on splitting"
|
|
else
|
|
output += "\nPossible mutations: [T.slime_mutation[1]], [T.slime_mutation[2]], [T.slime_mutation[3]], [T.slime_mutation[4]]"
|
|
output += "\nGenetic destability: [T.mutation_chance] % chance of mutation on splitting"
|
|
if (T.cores > 1)
|
|
output += "\nMultiple cores detected"
|
|
output += "\nGrowth progress: [T.amount_grown]/[SLIME_EVOLUTION_THRESHOLD]"
|
|
if(T.effectmod)
|
|
output += "\n<span class='notice'>Core mutation in progress: [T.effectmod]</span>"
|
|
output += "\n<span class = 'notice'>Progress in core mutation: [T.applied] / [SLIME_EXTRACT_CROSSING_REQUIRED]</span>"
|
|
|
|
to_chat(user, "[output]</div>")
|
|
|
|
|
|
/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
|
|
custom_materials = list(/datum/material/iron=200)
|
|
|
|
/obj/item/nanite_scanner/attack(mob/living/M, mob/living/carbon/human/user)
|
|
user.visible_message("<span class='notice'>[user] has analyzed [M]'s nanites.</span>")
|
|
|
|
add_fingerprint(user)
|
|
|
|
var/response = SEND_SIGNAL(M, COMSIG_NANITE_SCAN, user, TRUE)
|
|
if(!response)
|
|
to_chat(user, "<span class='info'>No nanites detected in the subject.</span>")
|
|
|
|
/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 for analyzing someones gene sequence on the fly. Hold near a DNA console to update the internal 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
|
|
custom_materials = list(/datum/material/iron=200)
|
|
var/list/discovered = list() //hit a dna console to update the scanners database
|
|
buffer = list()
|
|
var/ready = TRUE
|
|
var/cooldown = 200
|
|
|
|
/obj/item/sequence_scanner/attack(mob/living/M, mob/living/carbon/human/user)
|
|
add_fingerprint(user)
|
|
if (!HAS_TRAIT_NOT_FROM(M, TRAIT_RADIMMUNE,BLOODSUCKER_TRAIT)) //no scanning if its a husk or DNA-less Species
|
|
user.visible_message("<span class='notice'>[user] analyzes [M]'s genetic sequence.</span>", \
|
|
"<span class='notice'>You analyze [M]'s genetic sequence.</span>")
|
|
gene_scan(M, user)
|
|
|
|
else
|
|
user.visible_message("<span class='notice'>[user] failed to analyse [M]'s genetic sequence.</span>", "<span class='warning'>[M] has no readable genetic sequence!</span>")
|
|
|
|
/obj/item/sequence_scanner/attack_self(mob/user)
|
|
display_sequence(user)
|
|
|
|
/obj/item/sequence_scanner/attack_self_tk(mob/user)
|
|
return
|
|
|
|
/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, "<span class='notice'>[name] linked to central research database.</span>")
|
|
discovered = C.stored_research.discovered_mutations
|
|
else
|
|
to_chat(user,"<span class='warning'>No database to update from.</span>")
|
|
|
|
/obj/item/sequence_scanner/proc/gene_scan(mob/living/carbon/C, mob/living/user)
|
|
if(!iscarbon(C) || !C.has_dna())
|
|
return
|
|
buffer = C.dna.mutation_index
|
|
to_chat(user, "<span class='notice'>Subject [C.name]'s DNA sequence has been saved to buffer.</span>")
|
|
if(LAZYLEN(buffer))
|
|
for(var/A in buffer)
|
|
to_chat(user, "<span class='notice'>[get_display_name(A)]</span>")
|
|
|
|
|
|
/obj/item/sequence_scanner/proc/display_sequence(mob/living/user)
|
|
if(!LAZYLEN(buffer) || !ready)
|
|
return
|
|
var/list/options = list()
|
|
for(var/A in buffer)
|
|
options += get_display_name(A)
|
|
|
|
var/answer = input(user, "Analyze Potential", "Sequence Analyzer") as null|anything in sortList(options)
|
|
if(answer && ready && user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
|
|
var/sequence
|
|
for(var/A in buffer) //this physically hurts but i dont know what anything else short of an assoc list
|
|
if(get_display_name(A) == answer)
|
|
sequence = buffer[A]
|
|
break
|
|
|
|
if(sequence)
|
|
var/display
|
|
for(var/i in 0 to length_char(sequence) / DNA_MUTATION_BLOCKS-1)
|
|
if(i)
|
|
display += "-"
|
|
display += copytext_char(sequence, 1 + i*DNA_MUTATION_BLOCKS, DNA_MUTATION_BLOCKS*(1+i) + 1)
|
|
|
|
to_chat(user, "<span class='boldnotice'>[display]</span><br>")
|
|
|
|
ready = FALSE
|
|
icon_state = "[icon_state]_recharging"
|
|
addtimer(CALLBACK(src, .proc/recharge), cooldown, TIMER_UNIQUE)
|
|
|
|
/obj/item/sequence_scanner/proc/recharge()
|
|
icon_state = initial(icon_state)
|
|
ready = TRUE
|
|
|
|
/obj/item/sequence_scanner/proc/get_display_name(mutation)
|
|
var/datum/mutation/human/HM = GET_INITIALIZED_MUTATION(mutation)
|
|
if(!HM)
|
|
return "ERROR"
|
|
if(mutation in discovered)
|
|
return "[HM.name] ([HM.alias])"
|
|
else
|
|
return HM.alias
|
|
|
|
#undef SCANMODE_HEALTH
|
|
#undef SCANMODE_CHEMICAL
|
|
#undef SCANMODE_WOUND
|
|
#undef SCANNER_CONDENSED
|
|
#undef SCANNER_VERBOSE
|