mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-02-07 14:59:13 +00:00
805 lines
35 KiB
Plaintext
805 lines
35 KiB
Plaintext
|
|
/*
|
|
|
|
CONTAINS:
|
|
T-RAY
|
|
HEALTH ANALYZER
|
|
GAS ANALYZER
|
|
SLIME SCANNER
|
|
NANITE SCANNER
|
|
GENE SCANNER
|
|
|
|
*/
|
|
|
|
// Describes the two modes of scanning available for health analyzers
|
|
#define SCANMODE_HEALTH 0
|
|
#define SCANMODE_CHEMICAL 1
|
|
#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 = 150
|
|
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/proc/toggle_on()
|
|
on = !on
|
|
icon_state = copytext_char(icon_state, 1, -1) + "[on]"
|
|
if(on)
|
|
START_PROCESSING(SSobj, src)
|
|
else
|
|
STOP_PROCESSING(SSobj, src)
|
|
|
|
/obj/item/t_scanner/attack_self(mob/user)
|
|
toggle_on()
|
|
|
|
/obj/item/t_scanner/cyborg_unequip(mob/user)
|
|
if(!on)
|
|
return
|
|
toggle_on()
|
|
|
|
/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 || HAS_TRAIT(O, TRAIT_T_RAY_VISIBLE))
|
|
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 capable of distinguishing 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 = SCANNER_VERBOSE
|
|
var/scanmode = SCANMODE_HEALTH
|
|
var/advanced = FALSE
|
|
custom_price = 300
|
|
|
|
/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
|
|
to_chat(user, "<span class='notice'>You switch the health analyzer to [scanmode ? "scan chemical contents" : "check physical health"].</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
|
|
chemscan(user, M)
|
|
|
|
add_fingerprint(user)
|
|
|
|
|
|
// Used by the PDA medical scanner too
|
|
/proc/healthscan(mob/user, mob/living/M, mode = SCANNER_VERBOSE, advanced = FALSE)
|
|
if(isliving(user) && (user.incapacitated() || user.is_blind()))
|
|
return
|
|
|
|
// the final list of strings to render
|
|
var/render_list = list()
|
|
|
|
// 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
|
|
|
|
if(ishuman(M))
|
|
var/mob/living/carbon/human/H = M
|
|
if(H.undergoing_cardiac_arrest() && H.stat != DEAD)
|
|
render_list += "<span class='alert'>Subject suffering from heart attack: Apply defibrillation or other electric shock immediately!</span>\n"
|
|
|
|
render_list += "<span class='info'>Analyzing results for [M]:</span>\n<span class='info ml-1'>Overall status: [mob_status]</span>\n"
|
|
|
|
// Damage descriptions
|
|
if(brute_loss > 10)
|
|
render_list += "<span class='alert ml-1'>[brute_loss > 50 ? "Severe" : "Minor"] tissue damage detected.</span>\n"
|
|
if(fire_loss > 10)
|
|
render_list += "<span class='alert ml-1'>[fire_loss > 50 ? "Severe" : "Minor"] burn damage detected.</span>\n"
|
|
if(oxy_loss > 10)
|
|
render_list += "<span class='info ml-1'><span class='alert'>[oxy_loss > 50 ? "Severe" : "Minor"] oxygen deprivation detected.</span>\n"
|
|
if(tox_loss > 10)
|
|
render_list += "<span class='alert ml-1'>[tox_loss > 50 ? "Severe" : "Minor"] amount of toxin damage detected.</span>\n"
|
|
if(M.getStaminaLoss())
|
|
render_list += "<span class='alert ml-1'>Subject appears to be suffering from fatigue.</span>\n"
|
|
if(advanced)
|
|
render_list += "<span class='info ml-1'>Fatigue Level: [M.getStaminaLoss()]%.</span>\n"
|
|
if (M.getCloneLoss())
|
|
render_list += "<span class='alert ml-1'>Subject appears to have [M.getCloneLoss() > 30 ? "Severe" : "Minor"] cellular damage.</span>\n"
|
|
if(advanced)
|
|
render_list += "<span class='info ml-1'>Cellular Damage Level: [M.getCloneLoss()].</span>\n"
|
|
if (!M.getorgan(/obj/item/organ/brain))
|
|
render_list += "<span class='alert ml-1'>Subject lacks a brain.</span>\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
|
|
render_list += "<span class='alert ml-1'>Cerebral traumas detected: subject appears to be suffering from [english_list(trauma_text)].</span>\n"
|
|
if(C.roundstart_quirks.len)
|
|
render_list += "<span class='info ml-1'>Subject has the following physiological traits: [C.get_trait_string()].</span>\n"
|
|
if(advanced)
|
|
render_list += "<span class='info ml-1'>Brain Activity Level: [(200 - M.getOrganLoss(ORGAN_SLOT_BRAIN))/2]%.</span>\n"
|
|
|
|
if (M.radiation)
|
|
render_list += "<span class='alert ml-1'>Subject is irradiated.</span>\n"
|
|
if(advanced)
|
|
render_list += "<span class='info ml-1'>Radiation Level: [M.radiation]%.</span>\n"
|
|
|
|
if(advanced && M.hallucinating())
|
|
render_list += "<span class='info ml-1'>Subject is hallucinating.</span>\n"
|
|
|
|
// Body part damage report
|
|
if(iscarbon(M) && mode == SCANNER_VERBOSE)
|
|
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)
|
|
var/dmgreport = "<span class='info ml-1'>General status:</span>\
|
|
<table class='ml-2'><tr><font face='Verdana'>\
|
|
<td style='width:7em;'><font color='#0000CC'>Damage:</font></td>\
|
|
<td style='width:5em;'><font color='red'><b>Brute</b></font></td>\
|
|
<td style='width:4em;'><font color='orange'><b>Burn</b></font></td>\
|
|
<td style='width:4em;'><font color='green'><b>Toxin</b></font></td>\
|
|
<td style='width:8em;'><font color='purple'><b>Suffocation</b></font></td></tr>\
|
|
<tr><td><font color='#0000CC'>Overall:</font></td>\
|
|
<td><font color='red'>[CEILING(brute_loss,1)]</font></td>\
|
|
<td><font color='orange'>[CEILING(fire_loss,1)]</font></td>\
|
|
<td><font color='green'>[CEILING(tox_loss,1)]</font></td>\
|
|
<td><font color='blue'>[CEILING(oxy_loss,1)]</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) ? "[CEILING(org.brute_dam,1)]" : "0"]</font></td>\
|
|
<td><font color='orange'>[(org.burn_dam > 0) ? "[CEILING(org.burn_dam,1)]" : "0"]</font></td></tr>"
|
|
dmgreport += "</font></table>"
|
|
render_list += dmgreport // tables do not need extra linebreak
|
|
|
|
//Eyes and ears
|
|
if(advanced && iscarbon(M))
|
|
var/mob/living/carbon/C = M
|
|
|
|
// Ear status
|
|
var/obj/item/organ/ears/ears = C.getorganslot(ORGAN_SLOT_EARS)
|
|
var/message = "\n<span class='alert ml-2'>Subject does not have ears.</span>"
|
|
if(istype(ears))
|
|
message = ""
|
|
if(HAS_TRAIT_FROM(C, TRAIT_DEAF, GENETIC_MUTATION))
|
|
message = "\n<span class='alert ml-2'>Subject is genetically deaf.</span>"
|
|
else if(HAS_TRAIT(C, TRAIT_DEAF))
|
|
message = "\n<span class='alert ml-2'>Subject is deaf.</span>"
|
|
else
|
|
if(ears.damage)
|
|
message += "\n<span class='alert ml-2'>Subject has [ears.damage > ears.maxHealth ? "permanent ": "temporary "]hearing damage.</span>"
|
|
if(ears.deaf)
|
|
message += "\n<span class='alert ml-2'>Subject is [ears.damage > ears.maxHealth ? "permanently ": "temporarily "] deaf.</span>"
|
|
render_list += "<span class='info ml-1'>Ear status:</span>[message == "" ? "\n<span class='info ml-2'>Healthy.</span>" : message]\n"
|
|
|
|
// Eye status
|
|
var/obj/item/organ/eyes/eyes = C.getorganslot(ORGAN_SLOT_EYES)
|
|
message = "\n<span class='alert ml-2'>Subject does not have eyes.</span>"
|
|
if(istype(eyes))
|
|
message = ""
|
|
if(C.is_blind())
|
|
message += "\n<span class='alert ml-2'>Subject is blind.</span>"
|
|
if(HAS_TRAIT(C, TRAIT_NEARSIGHT))
|
|
message += "\n<span class='alert ml-2'>Subject is nearsighted.</span>"
|
|
if(eyes.damage > 30)
|
|
message += "\n<span class='alert ml-2'>Subject has severe eye damage.</span>"
|
|
else if(eyes.damage > 20)
|
|
message += "\n<span class='alert ml-2'>Subject has significant eye damage.</span>"
|
|
else if(eyes.damage)
|
|
message += "\n<span class='alert ml-2'>Subject has minor eye damage.</span>"
|
|
render_list += "<span class='info ml-1'>Eye status:</span>[message == "" ? "\n<span class='info ml-2'>Healthy.</span>" : message]\n"
|
|
|
|
if(ishuman(M))
|
|
var/mob/living/carbon/human/H = M
|
|
|
|
// Organ damage
|
|
if (H.internal_organs && H.internal_organs.len)
|
|
var/render = FALSE
|
|
var/toReport = "<span class='info ml-1'>Organs:</span>\
|
|
<table class='ml-2'><tr>\
|
|
<td style='width:6em;'><font color='#0000CC'><b>Organ</b></font></td>\
|
|
[advanced ? "<td style='width:3em;'><font color='#0000CC'><b>Dmg</b></font></td>" : ""]\
|
|
<td style='width:12em;'><font color='#0000CC'><b>Status</b></font></td>"
|
|
|
|
for(var/obj/item/organ/organ in H.internal_organs)
|
|
var/status = ""
|
|
if (organ.organ_flags & ORGAN_FAILING) status = "<font color='#E42426'>Non-Functional</font>"
|
|
else if (organ.damage > organ.high_threshold) status = "<font color='#EC6224'>Severely Damaged</font>"
|
|
else if (organ.damage > organ.low_threshold) status = "<font color='#F28F1F'>Mildly Damaged</font>"
|
|
if (status != "")
|
|
render = TRUE
|
|
toReport += "<tr><td><font color='#0000CC'>[organ.name]</font></td>\
|
|
[advanced ? "<td><font color='#0000CC'>[CEILING(organ.damage,1)]</font></td>" : ""]\
|
|
<td>[status]</td></tr>"
|
|
|
|
if (render)
|
|
render_list += toReport + "</table>" // tables do not need extra linebreak
|
|
|
|
//Genetic damage
|
|
if(advanced && H.has_dna())
|
|
render_list += "<span class='info ml-1'>Genetic Stability: [H.dna.stability]%.</span>\n"
|
|
|
|
// Species and body temperature
|
|
var/datum/species/S = H.dna.species
|
|
var/mutant = H.dna.check_mutation(HULK) \
|
|
|| S.mutantlungs != initial(S.mutantlungs) \
|
|
|| S.mutant_brain != initial(S.mutant_brain) \
|
|
|| S.mutant_heart != initial(S.mutant_heart) \
|
|
|| S.mutanteyes != initial(S.mutanteyes) \
|
|
|| S.mutantears != initial(S.mutantears) \
|
|
|| S.mutanthands != initial(S.mutanthands) \
|
|
|| S.mutanttongue != initial(S.mutanttongue) \
|
|
|| S.mutanttail != initial(S.mutanttail) \
|
|
|| S.mutantliver != initial(S.mutantliver) \
|
|
|| S.mutantstomach != initial(S.mutantstomach) \
|
|
|| S.flying_species != initial(S.flying_species)
|
|
|
|
render_list += "<span class='info ml-1'>Species: [S.name][mutant ? "-derived mutant" : ""]</span>\n"
|
|
render_list += "<span class='info ml-1'>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)))
|
|
render_list += "<span class='info ml-1'>Time of Death: [M.tod]</span>\n"
|
|
var/tdelta = round(world.time - M.timeofdeath)
|
|
render_list += "<span class='alert ml-1'><b>Subject died [DisplayTimeText(tdelta)] ago.</b></span>\n"
|
|
|
|
for(var/thing in M.diseases)
|
|
var/datum/disease/D = thing
|
|
if(!(D.visibility_flags & HIDDEN_SCANNER))
|
|
render_list += "<span class='alert ml-1'><b>Warning: [D.form] detected</b>\n\
|
|
<div class='ml-2'>Name: [D.name].\nType: [D.spread_text].\nStage: [D.stage]/[D.max_stages].\nPossible Cure: [D.cure_text]</div>\
|
|
</span>" // divs do not need extra linebreak
|
|
|
|
// Blood Level
|
|
if(M.has_dna())
|
|
var/mob/living/carbon/C = M
|
|
var/blood_id = C.get_blood_id()
|
|
if(blood_id)
|
|
if(ishuman(C))
|
|
var/mob/living/carbon/human/H = C
|
|
if(H.bleed_rate)
|
|
render_list += "<span class='alert ml-1'><b>Subject is bleeding!</b></span>\n"
|
|
var/blood_percent = round((C.blood_volume / BLOOD_VOLUME_NORMAL)*100)
|
|
var/blood_type = C.dna.blood_type
|
|
if(blood_id != /datum/reagent/blood) // special blood substance
|
|
var/datum/reagent/R = GLOB.chemical_reagents_list[blood_id]
|
|
blood_type = R ? R.name : blood_id
|
|
if(C.blood_volume <= BLOOD_VOLUME_SAFE && C.blood_volume > BLOOD_VOLUME_OKAY)
|
|
render_list += "<span class='alert ml-1'>Blood level: LOW [blood_percent] %, [C.blood_volume] cl,</span> <span class='info'>type: [blood_type]</span>\n"
|
|
else if(C.blood_volume <= BLOOD_VOLUME_OKAY)
|
|
render_list += "<span class='alert ml-1'>Blood level: <b>CRITICAL [blood_percent] %</b>, [C.blood_volume] cl,</span> <span class='info'>type: [blood_type]</span>\n"
|
|
else
|
|
render_list += "<span class='info ml-1'>Blood level: [blood_percent] %, [C.blood_volume] 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 += "[!cyberimp_detect ? "[CI.get_examine_string(user)]" : ", [CI.get_examine_string(user)]"]"
|
|
if(cyberimp_detect)
|
|
render_list += "<span class='notice ml-1'>Detected cybernetic modifications:</span>\n"
|
|
render_list += "<span class='notice ml-2'>[cyberimp_detect]</span>\n"
|
|
|
|
SEND_SIGNAL(M, COMSIG_NANITE_SCAN, user, FALSE)
|
|
to_chat(user, jointext(render_list, ""), trailing_newline = FALSE) // we handled the last <br> so we don't need handholding
|
|
|
|
/proc/chemscan(mob/living/user, mob/living/M)
|
|
if(istype(M) && M.reagents)
|
|
var/render_list = list()
|
|
if(M.reagents.reagent_list.len)
|
|
render_list += "<span class='notice ml-1'>Subject contains the following reagents:</span>\n"
|
|
for(var/datum/reagent/R in M.reagents.reagent_list)
|
|
render_list += "<span class='notice ml-2'>[round(R.volume, 0.001)] units of [R.name][R.overdosed == 1 ? "</span> - <span class='boldannounce'>OVERDOSING</span>" : ".</span>"]\n"
|
|
else
|
|
render_list += "<span class='notice ml-1'>Subject contains no reagents.</span>\n"
|
|
if(M.reagents.addiction_list.len)
|
|
render_list += "<span class='boldannounce ml-1'>Subject is addicted to the following reagents:</span>\n"
|
|
for(var/datum/reagent/R in M.reagents.addiction_list)
|
|
render_list += "<span class='alert ml-2'>[R.name]</span>\n"
|
|
else
|
|
render_list += "<span class='notice ml-1'>Subject is not addicted to any reagents.</span>\n"
|
|
|
|
to_chat(user, jointext(render_list, ""), trailing_newline = FALSE) // we handled the last <br> so we don't need handholding
|
|
|
|
/obj/item/healthanalyzer/verb/toggle_mode()
|
|
set name = "Switch Verbosity"
|
|
set category = "Object"
|
|
|
|
if(usr.incapacitated())
|
|
return
|
|
|
|
mode = !mode
|
|
to_chat(usr, mode == SCANNER_VERBOSE ? "The scanner now shows specific limb damage." : "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"
|
|
custom_price = 100
|
|
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.is_blind())
|
|
return
|
|
|
|
var/turf/location = user.loc
|
|
if(!istype(location))
|
|
return
|
|
|
|
var/render_list = list()
|
|
var/datum/gas_mixture/environment = location.return_air()
|
|
var/pressure = environment.return_pressure()
|
|
var/total_moles = environment.total_moles()
|
|
|
|
render_list += "<span class='info'><B>Results:</B></span>\
|
|
\n<span class='[abs(pressure - ONE_ATMOSPHERE) < 10 ? "info" : "alert"]'>Pressure: [round(pressure, 0.01)] kPa</span>\n"
|
|
if(total_moles)
|
|
var/list/env_gases = environment.gases
|
|
|
|
environment.assert_gases(arglist(GLOB.hardcoded_gases))
|
|
var/o2_concentration = env_gases[/datum/gas/oxygen][MOLES]/total_moles
|
|
var/n2_concentration = env_gases[/datum/gas/nitrogen][MOLES]/total_moles
|
|
var/co2_concentration = env_gases[/datum/gas/carbon_dioxide][MOLES]/total_moles
|
|
var/plasma_concentration = env_gases[/datum/gas/plasma][MOLES]/total_moles
|
|
|
|
render_list += "<span class='[abs(n2_concentration - N2STANDARD) < 20 ? "info" : "alert"]'>Nitrogen: [round(n2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/nitrogen][MOLES], 0.01)] mol)</span>\
|
|
\n<span class='[abs(o2_concentration - O2STANDARD) < 2 ? "info" : "alert"]'>Oxygen: [round(o2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/oxygen][MOLES], 0.01)] mol)</span>\
|
|
\n<span class='[co2_concentration > 0.01 ? "alert" : "info"]'>CO2: [round(co2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/carbon_dioxide][MOLES], 0.01)] mol)</span>\
|
|
\n<span class='[plasma_concentration > 0.005 ? "alert" : "info"]'>Plasma: [round(plasma_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/plasma][MOLES], 0.01)] mol)</span>\n"
|
|
|
|
environment.garbage_collect()
|
|
|
|
for(var/id in env_gases)
|
|
if(id in GLOB.hardcoded_gases)
|
|
continue
|
|
var/gas_concentration = env_gases[id][MOLES]/total_moles
|
|
render_list += "<span class='alert'>[env_gases[id][GAS_META][META_GAS_NAME]]: [round(gas_concentration*100, 0.01)] % ([round(env_gases[id][MOLES], 0.01)] mol)</span>\n"
|
|
render_list += "<span class='info'>Temperature: [round(environment.temperature-T0C, 0.01)] °C ([round(environment.temperature, 0.01)] K)</span>\n"
|
|
to_chat(user, jointext(render_list, ""), trailing_newline = FALSE) // we handled the last <br> so we don't need handholding
|
|
|
|
/obj/item/analyzer/AltClick(mob/user) //Barometer output for measuring when the next storm happens
|
|
..()
|
|
|
|
if(user.canUseTopic(src, BE_CLOSE))
|
|
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(mob/user, atom/target, silent=FALSE)
|
|
var/mixture = target.return_analyzable_air()
|
|
if(!mixture)
|
|
return FALSE
|
|
|
|
var/icon = target
|
|
var/render_list = list()
|
|
if(!silent && isliving(user))
|
|
user.visible_message("<span class='notice'>[user] has used the analyzer on [icon2html(icon, viewers(user))] [target].</span>", "<span class='notice'>You use the analyzer on [icon2html(icon, user)] [target].</span>")
|
|
render_list += "<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
|
|
render_list += "<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.temperature
|
|
var/cached_scan_results = air_contents.analyzer_results
|
|
|
|
if(total_moles > 0)
|
|
render_list += "<span class='notice'>Moles: [round(total_moles, 0.01)] mol</span>\
|
|
\n<span class='notice'>Volume: [volume] L</span>\
|
|
\n<span class='notice'>Pressure: [round(pressure,0.01)] kPa</span>"
|
|
|
|
var/list/cached_gases = air_contents.gases
|
|
for(var/id in cached_gases)
|
|
var/gas_concentration = cached_gases[id][MOLES]/total_moles
|
|
render_list += "<span class='notice'>[cached_gases[id][GAS_META][META_GAS_NAME]]: [round(gas_concentration*100, 0.01)] % ([round(cached_gases[id][MOLES], 0.01)] mol)</span>"
|
|
render_list += "<span class='notice'>Temperature: [round(temperature - T0C,0.01)] °C ([round(temperature, 0.01)] K)</span>"
|
|
else
|
|
render_list += airs.len > 1 ? "<span class='notice'>This node is empty!</span>" : "<span class='notice'>[target] is empty!</span>"
|
|
|
|
if(cached_scan_results && cached_scan_results["fusion"]) //notify the user if a fusion reaction was detected
|
|
render_list += "<span class='boldnotice'>Large amounts of free neutrons detected in the air indicate that a fusion reaction took place.</span>\
|
|
\n<span class='notice'>Instability of the last fusion reaction: [round(cached_scan_results["fusion"], 0.01)].</span>"
|
|
|
|
to_chat(user, jointext(render_list, "\n")) // we let the join apply newlines so we do need handholding
|
|
return TRUE
|
|
|
|
//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.is_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/to_render = "========================\
|
|
\n<b>Slime scan results:</b>\
|
|
\n<span class='notice'>[T.colour] [T.is_adult ? "adult" : "baby"] slime</span>\
|
|
\nNutrition: [T.nutrition]/[T.get_max_nutrition()]"
|
|
if (T.nutrition < T.get_starve_nutrition())
|
|
to_render += "\n<span class='warning'>Warning: slime is starving!</span>"
|
|
else if (T.nutrition < T.get_hunger_nutrition())
|
|
to_render += "\n<span class='warning'>Warning: slime is hungry</span>"
|
|
to_render += "\nElectric change strength: [T.powerlevel]\nHealth: [round(T.health/T.maxHealth,0.01)*100]%"
|
|
if (T.slime_mutation[4] == T.colour)
|
|
to_render += "\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])
|
|
to_render += "\nPossible mutation: [T.slime_mutation[3]]\
|
|
\nGenetic destability: [T.mutation_chance/2] % chance of mutation on splitting"
|
|
else
|
|
to_render += "\nPossible mutations: [T.slime_mutation[1]], [T.slime_mutation[2]], [T.slime_mutation[3]] (x2)\
|
|
\nGenetic destability: [T.mutation_chance] % chance of mutation on splitting"
|
|
else
|
|
to_render += "\nPossible mutations: [T.slime_mutation[1]], [T.slime_mutation[2]], [T.slime_mutation[3]], [T.slime_mutation[4]]\
|
|
\nGenetic destability: [T.mutation_chance] % chance of mutation on splitting"
|
|
if (T.cores > 1)
|
|
to_render += "\nMultiple cores detected"
|
|
to_render += "\nGrowth progress: [T.amount_grown]/[SLIME_EVOLUTION_THRESHOLD]"
|
|
if(T.effectmod)
|
|
to_render += "\n<span class='notice'>Core mutation in progress: [T.effectmod]</span>\
|
|
\n<span class='notice'>Progress in core mutation: [T.applied] / [SLIME_EXTRACT_CROSSING_REQUIRED]</span>"
|
|
to_chat(user, to_render + "\n========================")
|
|
|
|
|
|
/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] analyzes [M]'s nanites.</span>", \
|
|
"<span class='notice'>You analyze [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
|
|
var/list/buffer
|
|
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(M, TRAIT_RADIMMUNE) && !HAS_TRAIT(M, TRAIT_BADDNA)) //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
|
|
|
|
/obj/item/scanner_wand
|
|
name = "kiosk scanner wand"
|
|
icon = 'icons/obj/device.dmi'
|
|
icon_state = "scanner_wand"
|
|
item_state = "healthanalyzer"
|
|
lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
|
|
righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
|
|
desc = "An wand for scanning someone else for a medical analysis. Insert into a kiosk is make the scanned patient the target of a health scan."
|
|
force = 0
|
|
throwforce = 0
|
|
w_class = WEIGHT_CLASS_TINY
|
|
var/selected_target = null
|
|
|
|
/obj/item/scanner_wand/attack(mob/living/M, mob/living/carbon/human/user)
|
|
flick("[icon_state]_active", src) //nice little visual flash when scanning someone else.
|
|
|
|
if((HAS_TRAIT(user, TRAIT_CLUMSY) || HAS_TRAIT(user, TRAIT_DUMB)) && prob(25))
|
|
user.visible_message("<span class='warning'>[user] targets himself for scanning.</span>", \
|
|
to_chat(user, "<span class='info'>You try scanning [M], before realizing you're holding the scanner backwards. Whoops.</span>"))
|
|
selected_target = user
|
|
return
|
|
|
|
if(!ishuman(M))
|
|
to_chat(user, "<span class='info'>You can only scan human-like, non-robotic beings.</span>")
|
|
selected_target = null
|
|
return
|
|
|
|
user.visible_message("<span class='notice'>[user] targets [M] for scanning.</span>", \
|
|
"<span class='notice'>You target [M] vitals.</span>")
|
|
selected_target = M
|
|
return
|
|
|
|
/obj/item/scanner_wand/attack_self(mob/user)
|
|
to_chat(user, "<span class='info'>You clear the scanner's target.</span>")
|
|
selected_target = null
|
|
|
|
/obj/item/scanner_wand/proc/return_patient()
|
|
var/returned_target = selected_target
|
|
return returned_target
|
|
|
|
#undef SCANMODE_HEALTH
|
|
#undef SCANMODE_CHEMICAL
|
|
#undef SCANNER_CONDENSED
|
|
#undef SCANNER_VERBOSE
|