/* 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("[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_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("[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) scanmode = (scanmode + 1) % 3 switch(scanmode) if(SCANMODE_HEALTH) to_chat(user, "You switch the health analyzer to check physical health.") if(SCANMODE_CHEMICAL) to_chat(user, "You switch the health analyzer to scan chemical contents.") if(SCANMODE_WOUND) to_chat(user, "You switch the health analyzer to report extra info on wounds.") /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("[user] analyzes the floor's vitals!", \ "You stupidly try to analyze the floor's vitals!") to_chat(user, "Analyzing results for The floor:\n\tOverall status: Healthy\ \nKey: Suffocation/Toxin/Burn/Brute\ \n\tDamage specifics: 0-0-0-0\ \nBody temperature: ???") return user.visible_message("[user] analyzes [M]'s vitals.", \ "You analyze [M]'s vitals.") 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 ? "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 var/msg = "*---------*\nAnalyzing results for [M]:\n\tOverall status: [mob_status]" // Damage descriptions if(brute_loss > 10) msg += "\n\t[brute_loss > 50 ? "Severe" : "Minor"] tissue damage detected." if(fire_loss > 10) msg += "\n\t[fire_loss > 50 ? "Severe" : "Minor"] burn damage detected." if(oxy_loss > 10) msg += "\n\t[oxy_loss > 50 ? "Severe" : "Minor"] oxygen deprivation detected." if(tox_loss > 10) msg += "\n\t[tox_loss > 50 ? "Severe" : "Minor"] amount of [HAS_TRAIT(M, TRAIT_ROBOTIC_ORGANISM) ? "system corruption" : "toxin damage"] detected." if(M.getStaminaLoss()) msg += "\n\tSubject appears to be suffering from fatigue." if(advanced) msg += "\n\tFatigue Level: [M.getStaminaLoss()]%." if (M.getCloneLoss()) msg += "\n\tSubject appears to have [M.getCloneLoss() > 30 ? "Severe" : "Minor"] cellular damage." if(advanced) msg += "\n\tCellular Damage Level: [M.getCloneLoss()]." if(ishuman(M)) var/mob/living/carbon/human/H = M if(advanced && H.has_dna()) msg += "\n\tGenetic Stability: [H.dna.stability]%." to_chat(user, msg) msg = "" // 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 += "\ \ \ \ \ \ \ \ \ \ " for(var/o in damaged) var/obj/item/bodypart/org = o //head, left arm, right arm, etc. dmgreport += "\ \ " dmgreport += "
Damage:BruteBurn[HAS_TRAIT(C, TRAIT_ROBOTIC_ORGANISM) ? "Corruption" :"Toxin"]Suffocation
Overall:[brute_loss][fire_loss][tox_loss][oxy_loss]
[capitalize(org.name)]:[(org.brute_dam > 0) ? "[org.brute_dam]" : "0"][(org.burn_dam > 0) ? "[org.burn_dam]" : "0"]
" to_chat(user, dmgreport.Join()) //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 += " Subject is blind." if(HAS_TRAIT(C, TRAIT_NEARSIGHT)) temp_message += " Subject is nearsighted." if(eyes.damage > 30) damage_message += " Subject has severe eye damage." else if(eyes.damage > 20) damage_message += " Subject has significant eye damage." else if(eyes.damage) damage_message += " Subject has minor eye damage." //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 += " Subject is genetically deaf." else if(HAS_TRAIT(C, TRAIT_DEAF)) temp_message += " Subject is deaf." else if(ears.damage) damage_message += " Subject has [ears.damage > ears.maxHealth ? "permanent ": "temporary "]hearing damage." if(ears.deaf) damage_message += " Subject is [ears.damage > ears.maxHealth ? "permanently ": "temporarily "] deaf." //BRAIN else if(istype(O, /obj/item/organ/brain)) if (C.getOrganLoss(ORGAN_SLOT_BRAIN) >= 200) damage_message += " Subject's brain non-functional. Neurine injection recomended." else if (C.getOrganLoss(ORGAN_SLOT_BRAIN) >= 120) damage_message += " Severe brain damage detected. Subject likely to have mental traumas." else if (C.getOrganLoss(ORGAN_SLOT_BRAIN) >= 45) damage_message += " Brain damage detected." if(advanced) temp_message += " Brain Activity Level: [(200 - M.getOrganLoss(ORGAN_SLOT_BRAIN))/2]%." //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 += " Cerebral traumas detected: subject appears to be suffering from [english_list(trauma_text)]." if(C.roundstart_quirks.len) temp_message += " Subject has the following physiological traits: [C.get_trait_string()]." if(ishuman(C) && advanced) //MON PETIT CHAUFFEUR if(H.hallucinating()) temp_message += " Subject is hallucinating." //MKUltra if(H.has_status_effect(/datum/status_effect/chem/enthrall)) temp_message += " Subject has abnormal brain fuctions." //Astrogen shenanigans if(H.reagents.has_reagent(/datum/reagent/fermi/astral)) if(H.mind) temp_message += " Warning: subject may be possesed." else temp_message += " Subject appears to be astrally projecting." //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 += "Subject is suffering from liver failure: Apply Corazone and begin a liver transplant immediately!" //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 += " Subject suffering from heart attack: Apply defibrillation or other electric shock immediately!" 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 += " Subject is suffering from a fluffified tongue. Suggested cure: Yamerol or a tongue transplant." //GENERAL HANDLER if(!damage_message) if(O.organ_flags & ORGAN_FAILING) damage_message += " Chronic [O.name] failure detected." else if(O.damage > O.high_threshold) damage_message += " Acute [O.name] failure detected." else if(O.damage > O.low_threshold && advanced) damage_message += " Minor [O.name] failure detected.
" if(temp_message || damage_message) msg += "\t[uppertext(O.name)]:
[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_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 += "\tSubject does not have eyes.\n" if(!M.getorganslot(ORGAN_SLOT_EARS)) msg += "\tSubject does not have ears.\n" if(!M.getorganslot(ORGAN_SLOT_BRAIN)) msg += "\tSubject's brain function is non-existent!\n" if(has_liver && !M.getorganslot(ORGAN_SLOT_LIVER)) msg += "\tSubject's liver is missing!\n" if(blooded && !M.getorganslot(ORGAN_SLOT_HEART)) msg += "\tSubject's heart is missing!\n" if(breathes && !M.getorganslot(ORGAN_SLOT_LUNGS)) msg += "\tSubject's lungs have collapsed from trauma!\n" if(has_stomach && !M.getorganslot(ORGAN_SLOT_STOMACH)) msg += "\tSubject's stomach is missing!\n" if(M.radiation) msg += "\tSubject is irradiated.\n" msg += "\tRadiation Level: [M.radiation] rad\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 msg += "\tReported Species: [H.dna.custom_species ? H.dna.custom_species : S.name]\n" msg += "\tBase Species: [S.name]\n" if(mutant) msg += "\tSubject has mutations present.\n" msg += "\tBody 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)) if(heart_ded) msg += "Subject died [DisplayTimeText(tdelta)] ago, heart requires surgical intervention for defibrillation." else msg += "Subject died [DisplayTimeText(tdelta)] ago, defibrillation may be possible!" if(advanced) if(H.get_ghost() || H.key || H.client)//Since it can last a while. msg += " Intervention recommended.\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 += "Warning: Physical trauma[LAZYLEN(wounded_part.wounds) > 1? "s" : ""] detected in [wounded_part.name]" for(var/k in wounded_part.wounds) var/datum/wound/W = k msg += "
Type: [W.name]\nSeverity: [W.severity_text()]\nRecommended Treatment: [W.treat_text]
\n" // less lines than in woundscan() so we don't overload people trying to get basic med info msg += "
\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)) if(H.is_bleeding()) 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) if(istype(F,/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" 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 += "Warning: Physical trauma[LAZYLEN(wounded_part.wounds) > 1? "s" : ""] detected in [wounded_part.name]" for(var/k in wounded_part.wounds) var/datum/wound/W = k render_list += "
[W.get_scanner_description()]
\n" render_list += "
" 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, "\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!") else to_chat(user, "No wounds detected in subject.") 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, "\The [src] makes a happy ping and [pick(encouragements)]!") next_encouragement = world.time + 10 SECONDS greedy = FALSE else if(!greedy) to_chat(user, "\The [src] displays an eerily high-definition frowny face, chastizing you for asking it for too much encouragement.") greedy = TRUE else playsound(src, 'sound/machines/buzz-sigh.ogg', 50, FALSE) if(isliving(user)) var/mob/living/L = user to_chat(L, "\The [src] makes a disappointed buzz and pricks your finger for being greedy. Ow!") 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("[user] scans [patient] for serious injuries.", "You scan [patient] for serious injuries.") if(!istype(patient)) playsound(src, 'sound/machines/buzz-sigh.ogg', 30, TRUE) to_chat(user, "\The [src] makes a sad buzz and briefly displays a frowny face, indicating it can't scan [patient].") 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) . = ..() . += "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 //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, "[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, visible = TRUE) var/icon = target if(visible) user.visible_message("[user] has used the analyzer on [icon2html(icon, viewers(user))] [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.return_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") 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, "[GLOB.meta_gas_names[id]]: [round(gas_concentration*100, 0.01)] % ([round(air_contents.get_moles(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) var/tier = instability2text(instability) 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]\n This indicates it was [tier].") 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, "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/o2_concentration = environment.get_moles(/datum/gas/oxygen)/total_moles var/n2_concentration = environment.get_moles(/datum/gas/nitrogen)/total_moles var/co2_concentration = environment.get_moles(/datum/gas/carbon_dioxide)/total_moles var/plasma_concentration = environment.get_moles(/datum/gas/plasma)/total_moles if(abs(n2_concentration - N2STANDARD) < 20) to_chat(user, "Nitrogen: [round(n2_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/nitrogen), 0.01)] mol)") else to_chat(user, "Nitrogen: [round(n2_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/nitrogen), 0.01)] mol)") if(abs(o2_concentration - O2STANDARD) < 2) to_chat(user, "Oxygen: [round(o2_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/oxygen), 0.01)] mol)") else to_chat(user, "Oxygen: [round(o2_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/oxygen), 0.01)] mol)") if(co2_concentration > 0.01) to_chat(user, "CO2: [round(co2_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/carbon_dioxide), 0.01)] mol)") else to_chat(user, "CO2: [round(co2_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/carbon_dioxide), 0.01)] mol)") if(plasma_concentration > 0.005) to_chat(user, "Plasma: [round(plasma_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/plasma), 0.01)] mol)") else to_chat(user, "Plasma: [round(plasma_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/plasma), 0.01)] mol)") 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, "[GLOB.meta_gas_names[id]]: [round(gas_concentration*100, 0.01)] % ([round(environment.get_moles(id), 0.01)] mol)") to_chat(user, "Temperature: [round(environment.return_temperature()-T0C, 0.01)] °C ([round(environment.return_temperature(), 0.01)] K)") 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, "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]\n This indicates it was [tier].") /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, "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 custom_materials = list(/datum/material/iron=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 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("[user] analyzes [M]'s genetic sequence.", \ "You analyze [M]'s genetic sequence.") gene_scan(M, user) else user.visible_message("[user] failed to analyse [M]'s genetic sequence.", "[M] has no readable genetic sequence!") /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, "[name] linked to central research database.") discovered = C.stored_research.discovered_mutations else to_chat(user,"No database to update from.") /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, "Subject [C.name]'s DNA sequence has been saved to buffer.") if(LAZYLEN(buffer)) for(var/A in buffer) to_chat(user, "[get_display_name(A)]") /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, "[display]
") 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