/* CONTAINS: T-RAY SCANNER HEALTH ANALYZER MACHINE ANALYZER GAS ANALYZER REAGENT SCANNERS BODY SCANNERS SLIME SCANNER */ //////////////////////////////////////// // MARK: T-ray scanner //////////////////////////////////////// /obj/item/t_scanner name = "\improper T-ray scanner" desc = "A terahertz-ray emitter and scanner used to detect underfloor objects such as cables and pipes." icon = 'icons/obj/device.dmi' icon_state = "t-ray0" var/on = FALSE slot_flags = SLOT_FLAG_BELT w_class = WEIGHT_CLASS_SMALL item_state = "electronic" materials = list(MAT_METAL = 300) origin_tech = "magnets=1;engineering=1" /obj/item/t_scanner/Destroy() if(on) STOP_PROCESSING(SSobj, src) return ..() /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/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 if(MA.layer < TURF_LAYER) MA.layer += TRAY_SCAN_LAYER_OFFSET MA.plane = GAME_PLANE I.appearance = MA t_ray_images += I if(length(t_ray_images)) flick_overlay(t_ray_images, list(viewer.client), flick_time) //////////////////////////////////////// // MARK: Health analyser //////////////////////////////////////// #define SIMPLE_HEALTH_SCAN 0 #define DETAILED_HEALTH_SCAN 1 /proc/get_chemscan_results(mob/living/user, mob/living/M) var/msgs = list() if(!ishuman(M)) return var/mob/living/carbon/human/H = M if(length(H.reagents.reagent_list)) msgs += "Subject contains the following reagents:" for(var/datum/reagent/R in H.reagents.reagent_list) msgs += "[R.volume]u of [R.name][R.overdosed ? " - OVERDOSING" : "."]" else msgs += "Subject contains no reagents." if(length(H.reagents.addiction_list)) msgs += "Subject is addicted to the following reagents:" for(var/datum/reagent/R in H.reagents.addiction_list) msgs += "[R.name] Stage: [R.addiction_stage]/5" return msgs /proc/chemscan(mob/living/user, mob/living/M) if(ishuman(M)) var/list/results = get_chemscan_results(user, M) to_chat(user, results.Join("
")) /obj/item/healthanalyzer name = "health analyzer" desc = "A hand-held body scanner able to distinguish vital signs of the subject." icon = 'icons/obj/device.dmi' icon_state = "health" item_state = "healthanalyzer" belt_icon = "health_analyzer" flags = CONDUCT | NOBLUDGEON slot_flags = SLOT_FLAG_BELT w_class = WEIGHT_CLASS_SMALL throwforce = 3 throw_speed = 3 throw_range = 7 materials = list(MAT_METAL=200) origin_tech = "magnets=1;biotech=1" /// Can be SIMPLE_HEALTH_SCAN (damage is only shown as a single % value), or DETAILED_HEALTH_SCAN (shows the % value and also damage for every specific limb). var/mode = DETAILED_HEALTH_SCAN /// Is the health analyzer upgraded? Allows reagents in the body to be seen. var/advanced = FALSE /obj/item/healthanalyzer/examine(mob/user) . = ..() . += "Use [src] in hand to toggle showing localised damage." /obj/item/healthanalyzer/attack_self(mob/user) mode = !mode switch(mode) if(DETAILED_HEALTH_SCAN) to_chat(user, "The scanner is now showing localised limb damage.") if(SIMPLE_HEALTH_SCAN) to_chat(user, "The scanner is no longer showing localised limb damage.") /obj/item/healthanalyzer/attack(mob/living/M, mob/living/user) if((HAS_TRAIT(user, TRAIT_CLUMSY) || user.getBrainLoss() >= 60) && prob(50)) var/list/msgs = list() user.visible_message("[user] analyzes the floor's vitals!", "You stupidly try to analyze the floor's vitals!") msgs += "Analyzing results for The floor:\nOverall status: Healthy" msgs += "Key: Suffocation/Toxin/Burn/Brute" msgs += "Damage specifics: 0 - 0 - 0 - 0" msgs += "Body temperature: ???" to_chat(user, chat_box_healthscan(msgs.Join("
"))) return user.visible_message( "[user] analyzes [M]'s vitals.", "You analyze [M]'s vitals." ) healthscan(user, M, mode, advanced) add_fingerprint(user) // Used by the PDA medical scanner too. /proc/healthscan(mob/user, mob/living/M, mode = DETAILED_HEALTH_SCAN, advanced = FALSE) var/list/msgs = list() if(issimple_animal(M)) // No box here, keep it simple. if(M.stat == DEAD) to_chat(user, "Analyzing Results for [M]:\nOverall Status: Dead") return to_chat(user, "Analyzing Results for [M]:\nOverall Status: [round(M.health / M.maxHealth * 100, 0.1)]% Healthy") to_chat(user, "\t Damage Specifics: [M.maxHealth - M.health]") return // These sensors are designed for organic life. if(!ishuman(M) || ismachineperson(M)) msgs += "Analyzing Results for ERROR:\nOverall Status: ERROR" msgs += "Key: Suffocation/Toxin/Burns/Brute" msgs += "Damage Specifics: ? - ? - ? - ?" msgs += "Body Temperature: [M.bodytemperature-T0C]°C ([M.bodytemperature*1.8-459.67]°F)" msgs += "Warning: Blood Level ERROR: --% --cl.Type: ERROR" msgs += "Subject's pulse: -- bpm." to_chat(user, chat_box_healthscan(msgs.Join("
"))) return var/mob/living/carbon/human/H = M var/fake_oxy = max(rand(1,40), H.getOxyLoss(), (300 - (H.getToxLoss() + H.getFireLoss() + H.getBruteLoss()))) var/OX = H.getOxyLoss() > 50 ? "[H.getOxyLoss()]" : H.getOxyLoss() var/TX = H.getToxLoss() > 50 ? "[H.getToxLoss()]" : H.getToxLoss() var/BU = H.getFireLoss() > 50 ? "[H.getFireLoss()]" : H.getFireLoss() var/BR = H.getBruteLoss() > 50 ? "[H.getBruteLoss()]" : H.getBruteLoss() var/status = "Dead" // Dead by default to make it simpler var/DNR = !H.ghost_can_reenter() // If the ghost can't reenter if(H.stat == DEAD) if(DNR) status = "Dead (DNR)" else // Alive or unconscious if(HAS_TRAIT(H, TRAIT_FAKEDEATH)) // status still shows as "Dead" OX = fake_oxy > 50 ? "[fake_oxy]" : fake_oxy else status = "[H.health]% Healthy" msgs += "Analyzing Results for [H]:\nOverall Status: [status]" msgs += "Key: Suffocation/Toxin/Burns/Brute" msgs += "Damage Specifics: [OX] - [TX] - [BU] - [BR]" if(H.timeofdeath && (H.stat == DEAD || (HAS_TRAIT(H, TRAIT_FAKEDEATH)))) msgs += "Time of Death: [station_time_timestamp("hh:mm:ss", H.timeofdeath)]" var/tdelta = round(world.time - H.timeofdeath) if(H.is_revivable() && !DNR) msgs += "Subject died [DisplayTimeText(tdelta)] ago, defibrillation may be possible!" else msgs += "Subject died [DisplayTimeText(tdelta)] ago. Defibrillation is not possible!" if(mode == DETAILED_HEALTH_SCAN) var/list/damaged = H.get_damaged_organs(1,1) if(length(damaged)) msgs += "Localized Damage, Brute/Burn:" for(var/obj/item/organ/external/org in damaged) msgs += "[capitalize(org.name)]: [(org.brute_dam > 0) ? "[org.brute_dam]" : "0"]-[(org.burn_dam > 0) ? "[org.burn_dam]" : "0"]" if(advanced) msgs.Add(get_chemscan_results(user, H)) for(var/thing in H.viruses) var/datum/disease/D = thing if(D.visibility_flags & HIDDEN_SCANNER) continue // Snowflaking heart problems, because they are special (and common). if(istype(D, /datum/disease/critical)) msgs += "Warning: Subject is undergoing [D.name].\nStage: [D.stage]/[D.max_stages].\nPossible Cure: [D.cure_text]" continue msgs += "Warning: [D.form] detected\nName: [D.name].\nType: [D.spread_text].\nStage: [D.stage]/[D.max_stages].\nPossible Cure: [D.cure_text]" if(H.undergoing_cardiac_arrest()) var/datum/organ/heart/heart = H.get_int_organ_datum(ORGAN_DATUM_HEART) if(heart && !(heart.linked_organ.status & ORGAN_DEAD)) msgs += "The patient's heart has stopped.\nPossible Cure: Electric Shock" else if(heart && (heart.linked_organ.status & ORGAN_DEAD)) msgs += "Subject's heart is necrotic." else if(!heart) msgs += "Subject has no heart." if(H.getStaminaLoss()) msgs += "Subject appears to be suffering from fatigue." if(H.getCloneLoss()) msgs += "Subject appears to have [H.getCloneLoss() > 30 ? "severe" : "minor"] cellular damage." // Brain. if(H.get_int_organ(/obj/item/organ/internal/brain)) if(H.getBrainLoss() >= 100) msgs += "Subject is brain dead." else if(H.getBrainLoss() >= 60) msgs += "Severe brain damage detected. Subject likely to have dementia." else if(H.getBrainLoss() >= 10) msgs += "Significant brain damage detected. Subject may have had a concussion." else msgs += "Subject has no brain." // Broken bones, internal bleeding, infection, and critical burns. var/broken_bone = FALSE var/internal_bleed = FALSE var/burn_wound = FALSE for(var/name in H.bodyparts_by_name) var/obj/item/organ/external/e = H.bodyparts_by_name[name] if(!e) continue var/limb = e.name if(e.status & ORGAN_BROKEN) if((e.limb_name in list("l_arm", "r_arm", "l_hand", "r_hand", "l_leg", "r_leg", "l_foot", "r_foot")) && !(e.status & ORGAN_SPLINTED)) msgs += "Unsecured fracture in subject [limb]. Splinting recommended for transport." broken_bone = TRUE if(e.has_infected_wound()) msgs += "Infected wound detected in subject [limb]. Disinfection recommended." burn_wound = burn_wound || (e.status & ORGAN_BURNT) internal_bleed = internal_bleed || (e.status & ORGAN_INT_BLEEDING) if(broken_bone) msgs += "Bone fractures detected. Advanced scanner required for location." if(internal_bleed) msgs += "Internal bleeding detected. Advanced scanner required for location." if(burn_wound) msgs += "Critical burn detected. Examine patient's body for location." // Blood. var/blood_id = H.get_blood_id() if(blood_id) if(H.bleed_rate) msgs += "Subject is bleeding!" var/blood_percent = round((H.blood_volume / BLOOD_VOLUME_NORMAL)*100) var/blood_type = H.dna.blood_type var/blood_volume = round(H.blood_volume) if(blood_id != "blood")//special blood substance var/datum/reagent/R = GLOB.chemical_reagents_list[blood_id] if(R) blood_type = R.name else blood_type = blood_id if(H.blood_volume <= BLOOD_VOLUME_SAFE && H.blood_volume > BLOOD_VOLUME_OKAY) msgs += "LOW blood level [blood_percent] %, [blood_volume] cl, type: [blood_type]" else if(H.blood_volume <= BLOOD_VOLUME_OKAY) msgs += "CRITICAL blood level [blood_percent] %, [blood_volume] cl, type: [blood_type]" else msgs += "Blood level [blood_percent] %, [blood_volume] cl, type: [blood_type]" msgs += "Body Temperature: [round(H.bodytemperature-T0C, 0.01)]°C ([round(H.bodytemperature*1.8-459.67, 0.01)]°F)" msgs += "Subject's pulse: [H.get_pulse()] bpm." var/implant_detect for(var/obj/item/organ/internal/O in H.internal_organs) if(O.is_robotic() && !O.stealth_level) implant_detect += "[O.name].
" if(implant_detect) msgs += "Detected cybernetic modifications:" msgs += "[implant_detect]" // Do you have too many genetics superpowers? if(H.gene_stability < 40) msgs += "Subject's genes are quickly breaking down!" else if(H.gene_stability < 70) msgs += "Subject's genes are showing signs of spontaneous breakdown." else if(H.gene_stability < 85) msgs += "Subject's genes are showing minor signs of instability." if(HAS_TRAIT(H, TRAIT_HUSK)) msgs += "Subject is husked. Application of synthflesh is recommended." if(H.radiation > RAD_MOB_SAFE) msgs += "Subject is irradiated." to_chat(user, chat_box_healthscan(msgs.Join("
"))) /obj/item/healthanalyzer/attackby(obj/item/I, mob/user, params) if(!istype(I, /obj/item/healthupgrade)) return ..() if(advanced) to_chat(user, "An upgrade is already installed on [src].") return if(!user.unEquip(I)) to_chat(user, "[src] is stuck to your hand!") return to_chat(user, "You install the upgrade on [src].") add_overlay("advanced") playsound(loc, I.usesound, 50, TRUE) advanced = TRUE qdel(I) /obj/item/healthanalyzer/advanced name = "advanced health analyzer" advanced = TRUE /obj/item/healthanalyzer/advanced/Initialize(mapload) . = ..() add_overlay("advanced") /obj/item/healthupgrade name = "Health Analyzer Upgrade" desc = "An upgrade unit that can be installed on a health analyzer for expanded functionality." icon = 'icons/obj/device.dmi' icon_state = "healthupgrade" w_class = WEIGHT_CLASS_TINY origin_tech = "magnets=2;biotech=2" usesound = 'sound/items/deconstruct.ogg' #undef SIMPLE_HEALTH_SCAN #undef DETAILED_HEALTH_SCAN //////////////////////////////////////// // MARK: Machine analyzer //////////////////////////////////////// /obj/item/robotanalyzer name = "machine analyzer" desc = "A hand-held scanner able to diagnose robotic injuries and the condition of machinery." icon = 'icons/obj/device.dmi' icon_state = "robotanalyzer" item_state = "analyzer" flags = CONDUCT slot_flags = SLOT_FLAG_BELT throwforce = 3 w_class = WEIGHT_CLASS_SMALL throw_speed = 5 throw_range = 10 origin_tech = "magnets=1;biotech=1" /obj/item/robotanalyzer/proc/handle_clumsy(mob/living/user) var/list/msgs = list() user.visible_message("[user] has analyzed the floor's components!", "You try to analyze the floor's vitals!") msgs += "Analyzing Results for The floor:\n\t Overall Status: Unknown" msgs += "\t Damage Specifics: [0]/Key: Burns/Brute" msgs += "Chassis Temperature: ???" to_chat(user, chat_box_healthscan(msgs.Join("
"))) /obj/item/robotanalyzer/attack_obj(obj/machinery/M, mob/living/user) // Scanning a machine object if((HAS_TRAIT(user, TRAIT_CLUMSY) || user.getBrainLoss() >= 60) && prob(50)) handle_clumsy(user) return user.visible_message("[user] has analyzed [M]'s components with [src].", "You analyze [M]'s components with [src].") machine_scan(user, M) add_fingerprint(user) /obj/item/robotanalyzer/proc/machine_scan(mob/user, obj/machinery/M) if(M.obj_integrity == M.max_integrity) to_chat(user, "[M] is at full integrity.") return to_chat(user, "Structural damage detected! [M]'s overall estimated integrity is [round((M.obj_integrity / M.max_integrity) * 100)]%.") if(M.stat & BROKEN) // Displays alongside above message. Machines with a "broken" state do not become broken at 0% HP - anything that reaches that point is destroyed to_chat(user, "Further analysis: Catastrophic component failure detected! [M] requires reconstruction to fully repair.") /obj/item/robotanalyzer/attack(mob/living/M, mob/living/user) // Scanning borgs, IPCs/augmented crew, and AIs if((HAS_TRAIT(user, TRAIT_CLUMSY) || user.getBrainLoss() >= 60) && prob(50)) handle_clumsy(user) return user.visible_message("[user] has analyzed [M]'s components with [src].", "You analyze [M]'s components with [src].") robot_healthscan(user, M) add_fingerprint(user) /proc/robot_healthscan(mob/user, mob/living/M) var/scan_type var/list/msgs = list() if(isrobot(M)) scan_type = "robot" else if(ishuman(M)) scan_type = "prosthetics" else if(isAI(M)) scan_type = "ai" else to_chat(user, "You can't analyze non-robotic things!") return switch(scan_type) if("robot") var/burn = M.getFireLoss() > 50 ? "[M.getFireLoss()]" : M.getFireLoss() var/brute = M.getBruteLoss() > 50 ? "[M.getBruteLoss()]" : M.getBruteLoss() msgs += "Analyzing Results for [M]:\n\t Overall Status: [M.stat == DEAD ? "fully disabled" : "[M.health]% functional"]" msgs += "\t Key: Electronics/Brute" msgs += "\t Damage Specifics: [burn] - [brute]" if(M.timeofdeath && M.stat == DEAD) msgs += "Time of disable: [station_time_timestamp("hh:mm:ss", M.timeofdeath)]" var/mob/living/silicon/robot/H = M var/list/damaged = H.get_damaged_components(TRUE, TRUE, TRUE) // Get all except the missing ones var/list/missing = H.get_missing_components() msgs += "Localized Damage:" if(!LAZYLEN(damaged) && !LAZYLEN(missing)) msgs += "\t Components are OK." else if(LAZYLEN(damaged)) for(var/datum/robot_component/org in damaged) msgs += text("\t []: [][] - [] - [] - []", \ capitalize(org.name), \ (org.is_destroyed()) ? "DESTROYED " :"",\ (org.electronics_damage > 0) ? "[org.electronics_damage]" :0, \ (org.brute_damage > 0) ? "[org.brute_damage]" :0, \ (org.toggled) ? "Toggled ON" : "Toggled OFF",\ (org.powered) ? "Power ON" : "Power OFF") if(LAZYLEN(missing)) for(var/datum/robot_component/org in missing) msgs += "\t [capitalize(org.name)]: MISSING" if(H.emagged && prob(5)) msgs += "\t ERROR: INTERNAL SYSTEMS COMPROMISED" if("prosthetics") var/mob/living/carbon/human/H = M var/is_ipc = ismachineperson(H) msgs += "Analyzing Results for [M]: [is_ipc ? "\n\t Overall Status: [H.stat == DEAD ? "fully disabled" : "[H.health]% functional"]
" : "
"]" //for the record im sorry msgs += "\t Key: Electronics/Brute" msgs += "External prosthetics:" var/organ_found if(LAZYLEN(H.internal_organs)) for(var/obj/item/organ/external/E in H.bodyparts) if(!E.is_robotic() || (is_ipc && (E.get_damage() == 0))) //Non-IPCs have their cybernetics show up in the scan, even if undamaged continue organ_found = TRUE msgs += "[E.name]: [E.brute_dam] [E.burn_dam]" if(!organ_found) msgs += "No prosthetics located." msgs += "
" msgs += "Internal prosthetics:" organ_found = null if(LAZYLEN(H.internal_organs)) for(var/obj/item/organ/internal/O in H.internal_organs) if(!O.is_robotic() || istype(O, /obj/item/organ/internal/cyberimp) || O.stealth_level > 1) continue organ_found = TRUE msgs += "[capitalize(O.name)]: [O.damage]" if(!organ_found) msgs += "No prosthetics located." msgs += "
" msgs += "Cybernetic implants:" organ_found = null if(LAZYLEN(H.internal_organs)) for(var/obj/item/organ/internal/cyberimp/I in H.internal_organs) if(I.stealth_level > 1) continue organ_found = TRUE msgs += "[capitalize(I.name)]: [I.crit_fail ? "CRITICAL FAILURE" : I.damage]" if(!organ_found) msgs += "No implants located." msgs += "
" if(is_ipc) msgs.Add(get_chemscan_results(user, H)) msgs += "Subject temperature: [round(H.bodytemperature-T0C, 0.01)]°C ([round(H.bodytemperature*1.8-459.67, 0.01)]°F)" if("ai") var/mob/living/silicon/ai/A = M var/burn = A.getFireLoss() > 50 ? "[A.getFireLoss()]" : A.getFireLoss() var/brute = A.getBruteLoss() > 50 ? "[A.getBruteLoss()]" : A.getBruteLoss() msgs += "Analyzing Results for [M]:\n\t Overall Status: [A.stat == DEAD ? "fully disabled" : "[A.health]% functional"]" msgs += "\t Key: Electronics/Brute" msgs += "\t Damage Specifics: [burn] - [brute]" to_chat(user, chat_box_healthscan(msgs.Join("
"))) //////////////////////////////////////// // MARK: Gas analyzer //////////////////////////////////////// /obj/item/analyzer name = "analyzer" desc = "A hand-held environmental scanner which reports current gas levels." icon = 'icons/obj/device.dmi' icon_state = "atmos" item_state = "analyzer" slot_flags = SLOT_FLAG_BELT w_class = WEIGHT_CLASS_SMALL flags = CONDUCT throwforce = 0 throw_speed = 3 throw_range = 7 materials = list(MAT_METAL = 210, MAT_GLASS = 140) origin_tech = "magnets=1;engineering=1" 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/attack_self(mob/user as mob) if(user.stat) return var/turf/location = user.loc if(!isturf(location)) return atmos_scan(user, location) add_fingerprint(user) /obj/item/analyzer/AltClick(mob/user) //Barometer output for measuring when the next storm happens ..() if(!user.incapacitated() && Adjacent(user)) 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 == WEATHER_END_STAGE)) ongoing_weather = W break if(ongoing_weather) if((ongoing_weather.stage == WEATHER_MAIN_STAGE) || (ongoing_weather.stage == WEATHER_WIND_DOWN_STAGE)) to_chat(user, "[src]'s barometer function can't trace anything while the storm is [ongoing_weather.stage == WEATHER_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, PROC_REF(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)) /obj/item/analyzer/afterattack(atom/target, mob/user, proximity, params) . = ..() if(!can_see(user, target, 1)) return if(target.return_analyzable_air()) atmos_scan(user, target) else atmos_scan(user, get_turf(target)) /** * Outputs a message to the user describing the target's gasmixes. * Used in chat-based gas scans. */ /proc/atmos_scan(mob/user, atom/target, silent = FALSE, print = TRUE, milla_turf_details = FALSE) var/mixture var/list/milla = null if(milla_turf_details) milla = new/list(MILLA_TILE_SIZE) get_tile_atmos(target, milla) var/datum/gas_mixture/GM = new() GM.copy_from_milla(milla) mixture = GM else mixture = target.return_analyzable_air() if(!mixture) return FALSE var/list/message = list() if(!silent && isliving(user)) user.visible_message("[user] uses the analyzer on [target].", "You use the analyzer on [target].") message += "Results of analysis of [bicon(target)] [target]." if(!print) return TRUE var/list/airs = islist(mixture) ? mixture : list(mixture) for(var/datum/gas_mixture/air as anything in airs) var/mix_name = capitalize(lowertext(target.name)) if(length(air) > 1) //not a unary gas mixture var/mix_number = airs.Find(air) message += "Node [mix_number]" mix_name += " - Node [mix_number]" var/total_moles = air.total_moles() var/pressure = air.return_pressure() var/volume = air.return_volume() //could just do mixture.volume... but safety, I guess? var/heat_capacity = air.heat_capacity() var/thermal_energy = air.thermal_energy() if(total_moles) message += "Total: [round(total_moles, 0.01)] moles" if(air.oxygen() && (milla_turf_details || air.oxygen() / total_moles > 0.01)) message += " Oxygen: [round(air.oxygen(), 0.01)] moles ([round(air.oxygen() / total_moles * 100, 0.01)] %)" if(air.nitrogen() && (milla_turf_details || air.nitrogen() / total_moles > 0.01)) message += " Nitrogen: [round(air.nitrogen(), 0.01)] moles ([round(air.nitrogen() / total_moles * 100, 0.01)] %)" if(air.carbon_dioxide() && (milla_turf_details || air.carbon_dioxide() / total_moles > 0.01)) message += " Carbon Dioxide: [round(air.carbon_dioxide(), 0.01)] moles ([round(air.carbon_dioxide() / total_moles * 100, 0.01)] %)" if(air.toxins() && (milla_turf_details || air.toxins() / total_moles > 0.01)) message += " Plasma: [round(air.toxins(), 0.01)] moles ([round(air.toxins() / total_moles * 100, 0.01)] %)" if(air.sleeping_agent() && (milla_turf_details || air.sleeping_agent() / total_moles > 0.01)) message += " Nitrous Oxide: [round(air.sleeping_agent(), 0.01)] moles ([round(air.sleeping_agent() / total_moles * 100, 0.01)] %)" if(air.agent_b() && (milla_turf_details || air.agent_b() / total_moles > 0.01)) message += " Agent B: [round(air.agent_b(), 0.01)] moles ([round(air.agent_b() / total_moles * 100, 0.01)] %)" message += "Temperature: [round(air.temperature()-T0C)] °C ([round(air.temperature())] K)" message += "Volume: [round(volume)] Liters" message += "Pressure: [round(pressure, 0.1)] kPa" message += "Heat Capacity: [DisplayJoules(heat_capacity)] / K" message += "Thermal Energy: [DisplayJoules(thermal_energy)]" else message += length(airs) > 1 ? "This node is empty!" : "[target] is empty!" message += "Volume: [round(volume)] Liters" // don't want to change the order volume appears in, suck it if(milla) // Values from milla/src/lib.rs, +1 due to array indexing difference. message += "Airtight North: [(milla[MILLA_INDEX_AIRTIGHT_DIRECTIONS] & MILLA_NORTH) ? "yes" : "no"]" message += "Airtight East: [(milla[MILLA_INDEX_AIRTIGHT_DIRECTIONS] & MILLA_EAST) ? "yes" : "no"]" message += "Airtight South: [(milla[MILLA_INDEX_AIRTIGHT_DIRECTIONS] & MILLA_SOUTH) ? "yes" : "no"]" message += "Airtight West: [(milla[MILLA_INDEX_AIRTIGHT_DIRECTIONS] & MILLA_WEST) ? "yes" : "no"]" switch(milla[MILLA_INDEX_ATMOS_MODE]) // These are enum values, so they don't get increased. if(0) message += "Atmos Mode: Space" if(1) message += "Atmos Mode: Sealed" if(2) message += "Atmos Mode: Exposed to Environment (ID: [milla[MILLA_INDEX_ENVIRONMENT_ID]])" else message += "Atmos Mode: Unknown ([milla[MILLA_INDEX_ATMOS_MODE]]), contact a coder." message += "Superconductivity North: [milla[MILLA_INDEX_SUPERCONDUCTIVITY_NORTH]]" message += "Superconductivity East: [milla[MILLA_INDEX_SUPERCONDUCTIVITY_EAST]]" message += "Superconductivity South: [milla[MILLA_INDEX_SUPERCONDUCTIVITY_SOUTH]]" message += "Superconductivity West: [milla[MILLA_INDEX_SUPERCONDUCTIVITY_WEST]]" message += "Turf's Innate Heat Capacity: [milla[MILLA_INDEX_INNATE_HEAT_CAPACITY]]" to_chat(user, chat_box_examine(message.Join("\n"))) return TRUE //////////////////////////////////////// // MARK: Reagent scanners //////////////////////////////////////// /obj/item/reagent_scanner name = "reagent scanner" desc = "A hand-held reagent scanner which identifies chemical agents and blood types." icon = 'icons/obj/device.dmi' icon_state = "spectrometer" item_state = "analyzer" w_class = WEIGHT_CLASS_SMALL flags = CONDUCT slot_flags = SLOT_FLAG_BELT throwforce = 5 throw_speed = 4 throw_range = 20 materials = list(MAT_METAL=300, MAT_GLASS=200) origin_tech = "magnets=2;biotech=1;plasmatech=2" var/details = FALSE var/datatoprint = "" var/scanning = TRUE actions_types = list(/datum/action/item_action/print_report) /obj/item/reagent_scanner/afterattack(obj/O, mob/user as mob) if(user.stat) return if(!user.IsAdvancedToolUser()) to_chat(user, "You don't have the dexterity to do this!") return if(!istype(O)) return if(!isnull(O.reagents)) var/dat = "" var/blood_type = "" if(length(O.reagents.reagent_list) > 0) var/one_percent = O.reagents.total_volume / 100 for(var/datum/reagent/R in O.reagents.reagent_list) if(R.id != "blood") dat += "
[TAB][R] [details ? ":([R.volume / one_percent]%)" : ""]" else blood_type = R.data["blood_type"] dat += "
[TAB][blood_type ? "[blood_type]" : ""] [R.data["species"]] [R.name] [details ? ":([R.volume / one_percent]%)" : ""]" if(dat) to_chat(user, "Chemicals found: [dat]") datatoprint = dat scanning = FALSE else to_chat(user, "No active chemical agents found in [O].") else to_chat(user, "No significant chemical agents found in [O].") return /obj/item/reagent_scanner/adv name = "advanced reagent scanner" icon_state = "adv_spectrometer" details = TRUE origin_tech = "magnets=4;biotech=3;plasmatech=3" /obj/item/reagent_scanner/proc/print_report() if(!scanning) usr.visible_message("[src] rattles and prints out a sheet of paper.") playsound(loc, 'sound/goonstation/machines/printer_thermal.ogg', 50, 1) sleep(50) var/obj/item/paper/P = new(get_turf(src)) P.name = "Reagent Scanner Report: [station_time_timestamp()]" P.info = "
Reagent Scanner

Data Analysis:



Chemical agents detected:
[datatoprint]

" if(ismob(loc)) var/mob/M = loc M.put_in_hands(P) to_chat(M, "Report printed. Log cleared.") datatoprint = "" scanning = TRUE else to_chat(usr, "[src] has no logs or is already in use.") /obj/item/reagent_scanner/ui_action_click() print_report() //////////////////////////////////////// // MARK: Slime scanner //////////////////////////////////////// /obj/item/slime_scanner name = "slime scanner" icon = 'icons/obj/device.dmi' icon_state = "adv_spectrometer_s" item_state = "analyzer" origin_tech = "biotech=2" slot_flags = SLOT_FLAG_BELT w_class = WEIGHT_CLASS_SMALL flags = CONDUCT throwforce = 0 throw_speed = 3 throw_range = 7 materials = list(MAT_METAL=30, MAT_GLASS=20) /obj/item/slime_scanner/attack(mob/living/M, mob/living/user) if(user.incapacitated() || user.AmountBlinded()) return if(!isslime(M)) to_chat(user, "This device can only scan slimes!") return slime_scan(M, 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] % 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, "========================") //////////////////////////////////////// // MARK: Body analyzers //////////////////////////////////////// /obj/item/bodyanalyzer name = "handheld body analyzer" desc = "A handheld scanner capable of deep-scanning an entire body." icon = 'icons/obj/device.dmi' icon_state = "bodyanalyzer_0" item_state = "healthanalyser" slot_flags = SLOT_FLAG_BELT w_class = WEIGHT_CLASS_SMALL throwforce = 3 throw_speed = 5 throw_range = 10 origin_tech = "magnets=6;biotech=6" var/obj/item/stock_parts/cell/cell var/cell_type = /obj/item/stock_parts/cell/upgraded var/ready = TRUE // Ready to scan var/printing = FALSE var/time_to_use = 0 // How much time remaining before next scan is available. var/usecharge = 750 var/scan_time = 5 SECONDS //how long does it take to scan var/scan_cd = 30 SECONDS //how long before we can scan again /obj/item/bodyanalyzer/get_cell() return cell /obj/item/bodyanalyzer/advanced cell_type = /obj/item/stock_parts/cell/upgraded/plus /obj/item/bodyanalyzer/borg name = "cyborg body analyzer" desc = "Scan an entire body to prepare for field surgery. Consumes power for each scan." /obj/item/bodyanalyzer/borg/syndicate scan_time = 5 SECONDS scan_cd = 20 SECONDS /obj/item/bodyanalyzer/New() ..() cell = new cell_type(src) cell.give(cell.maxcharge) update_icon() /obj/item/bodyanalyzer/proc/setReady() ready = TRUE playsound(src, 'sound/machines/defib_saftyon.ogg', 50, 0) update_icon() /obj/item/bodyanalyzer/update_icon_state() if(!cell) icon_state = "bodyanalyzer_0" return if(ready) icon_state = "bodyanalyzer_1" else icon_state = "bodyanalyzer_2" /obj/item/bodyanalyzer/update_overlays() . = ..() var/percent = cell.percent() var/overlayid = round(percent / 10) . += "bodyanalyzer_charge[overlayid]" if(printing) . += "bodyanalyzer_printing" /obj/item/bodyanalyzer/attack(mob/living/M, mob/living/carbon/human/user) if(user.incapacitated() || !user.Adjacent(M)) return if(!ready) to_chat(user, "The scanner beeps angrily at you! It's currently recharging - [round((time_to_use - world.time) * 0.1)] seconds remaining.") playsound(user.loc, 'sound/machines/buzz-sigh.ogg', 50, 1) return if(cell.charge >= usecharge) mobScan(M, user) else to_chat(user, "The scanner beeps angrily at you! It's out of charge!") playsound(user.loc, 'sound/machines/buzz-sigh.ogg', 50, 1) /obj/item/bodyanalyzer/borg/attack(mob/living/M, mob/living/silicon/robot/user) if(user.incapacitated() || !user.Adjacent(M)) return if(!ready) to_chat(user, "[src] is currently recharging - [round((time_to_use - world.time) * 0.1)] seconds remaining.") return if(user.cell.charge >= usecharge) mobScan(M, user) else to_chat(user, "You need to recharge before you can use [src]") /obj/item/bodyanalyzer/proc/mobScan(mob/living/M, mob/user) if(ishuman(M)) var/report = generate_printing_text(M, user) user.visible_message("[user] begins scanning [M] with [src].", "You begin scanning [M].") if(do_after(user, scan_time, target = M)) var/obj/item/paper/printout = new printout.info = report printout.name = "Scan report - [M.name]" playsound(user.loc, 'sound/goonstation/machines/printer_dotmatrix.ogg', 50, 1) user.put_in_hands(printout) time_to_use = world.time + scan_cd if(isrobot(user)) var/mob/living/silicon/robot/R = user R.cell.use(usecharge) else cell.use(usecharge) ready = FALSE printing = TRUE update_icon() addtimer(CALLBACK(src, TYPE_PROC_REF(/obj/item/bodyanalyzer, setReady)), scan_cd) addtimer(VARSET_CALLBACK(src, printing, FALSE), 1.4 SECONDS) addtimer(CALLBACK(src, TYPE_PROC_REF(/atom, update_icon), UPDATE_OVERLAYS), 1.5 SECONDS) else if(iscorgi(M) && M.stat == DEAD) to_chat(user, "You wonder if [M.p_they()] was a good dog. [src] tells you they were the best...") // :'( playsound(loc, 'sound/machines/ping.ogg', 50, 0) ready = FALSE update_icon(UPDATE_ICON_STATE) addtimer(CALLBACK(src, TYPE_PROC_REF(/obj/item/bodyanalyzer, setReady)), scan_cd) time_to_use = world.time + scan_cd else to_chat(user, "Scanning error detected. Invalid specimen.") //Unashamedly ripped from adv_med.dm /obj/item/bodyanalyzer/proc/generate_printing_text(mob/living/M, mob/user) var/dat = "" var/mob/living/carbon/human/target = M dat = "Target Statistics:
" var/t1 switch(target.stat) // obvious, see what their status is if(CONSCIOUS) t1 = "Conscious" if(UNCONSCIOUS) t1 = "Unconscious" else t1 = "*dead*" dat += "[target.health > 50 ? "" : ""]\tHealth %: [target.health], ([t1])
" var/found_disease = FALSE for(var/thing in target.viruses) var/datum/disease/D = thing if(D.visibility_flags) //If any visibility flags are on. continue found_disease = TRUE break if(found_disease) dat += "Disease detected in target.
" var/extra_font = null extra_font = (target.getBruteLoss() < 60 ? "" : "") dat += "[extra_font]\t-Brute Damage %: [target.getBruteLoss()]
" extra_font = (target.getOxyLoss() < 60 ? "" : "") dat += "[extra_font]\t-Respiratory Damage %: [target.getOxyLoss()]
" extra_font = (target.getToxLoss() < 60 ? "" : "") dat += "[extra_font]\t-Toxin Content %: [target.getToxLoss()]
" extra_font = (target.getFireLoss() < 60 ? "" : "") dat += "[extra_font]\t-Burn Severity %: [target.getFireLoss()]
" extra_font = (target.radiation < 10 ?"" : "") dat += "[extra_font]\tRadiation Level %: [target.radiation]
" extra_font = (target.getCloneLoss() < 1 ?"" : "") dat += "[extra_font]\tGenetic Tissue Damage %: [target.getCloneLoss()]
" extra_font = (target.getBrainLoss() < 1 ?"" : "") dat += "[extra_font]\tApprox. Brain Damage %: [target.getBrainLoss()]
" dat += "Paralysis Summary %: [target.IsParalyzed()] ([round(target.AmountParalyzed() / 10)] seconds left!)
" dat += "Body Temperature: [target.bodytemperature-T0C]°C ([target.bodytemperature*1.8-459.67]°F)
" dat += "
" var/blood_percent = round((target.blood_volume / BLOOD_VOLUME_NORMAL)) blood_percent *= 100 extra_font = (target.blood_volume > 448 ? "" : "") dat += "[extra_font]\tBlood Level %: [blood_percent] ([target.blood_volume] units)
" if(target.reagents) dat += "Epinephrine units: [target.reagents.get_reagent_amount("Epinephrine")] units
" dat += "Ether: [target.reagents.get_reagent_amount("ether")] units
" extra_font = (target.reagents.get_reagent_amount("silver_sulfadiazine") < 30 ? "" : "") dat += "[extra_font]\tSilver Sulfadiazine: [target.reagents.get_reagent_amount("silver_sulfadiazine")]
" extra_font = (target.reagents.get_reagent_amount("styptic_powder") < 30 ? "" : "") dat += "[extra_font]\tStyptic Powder: [target.reagents.get_reagent_amount("styptic_powder")] units
" extra_font = (target.reagents.get_reagent_amount("salbutamol") < 30 ? "" : "") dat += "[extra_font]\tSalbutamol: [target.reagents.get_reagent_amount("salbutamol")] units
" dat += "
" dat += "" dat += "" dat += "" dat += "" dat += "" dat += "" for(var/obj/item/organ/external/e in target.bodyparts) dat += "" var/AN = "" var/open = "" var/infected = "" var/robot = "" var/imp = "" var/bled = "" var/splint = "" var/internal_bleeding = "" var/lung_ruptured = "" if(e.status & ORGAN_INT_BLEEDING) internal_bleeding = "
Internal bleeding" if(istype(e, /obj/item/organ/external/chest) && target.is_lung_ruptured()) lung_ruptured = "Lung ruptured:" if(e.status & ORGAN_SPLINTED) splint = "Splinted:" if(e.status & ORGAN_BROKEN) AN = "[e.broken_description]:" if(e.is_robotic()) robot = "Robotic:" if(e.open) open = "Open:" switch(e.germ_level) if(INFECTION_LEVEL_ONE to INFECTION_LEVEL_ONE + 200) infected = "Mild Infection:" if(INFECTION_LEVEL_ONE + 200 to INFECTION_LEVEL_ONE + 300) infected = "Mild Infection+:" if(INFECTION_LEVEL_ONE + 300 to INFECTION_LEVEL_ONE + 400) infected = "Mild Infection++:" if(INFECTION_LEVEL_TWO to INFECTION_LEVEL_TWO + 200) infected = "Acute Infection:" if(INFECTION_LEVEL_TWO + 200 to INFECTION_LEVEL_TWO + 300) infected = "Acute Infection+:" if(INFECTION_LEVEL_TWO + 300 to INFECTION_LEVEL_TWO + 400) infected = "Acute Infection++:" if(INFECTION_LEVEL_THREE to INFINITY) infected = "Septic:" var/unknown_body = 0 for(var/I in e.embedded_objects) unknown_body++ if(unknown_body || e.hidden) imp += "Unknown body present:" if(!AN && !open && !infected && !imp) AN = "None:" dat += "" dat += "" for(var/obj/item/organ/internal/i in target.internal_organs) var/mech = i.desc var/infection = "None" switch(i.germ_level) if(1 to INFECTION_LEVEL_ONE + 200) infection = "Mild Infection:" if(INFECTION_LEVEL_ONE + 200 to INFECTION_LEVEL_ONE + 300) infection = "Mild Infection+:" if(INFECTION_LEVEL_ONE + 300 to INFECTION_LEVEL_ONE + 400) infection = "Mild Infection++:" if(INFECTION_LEVEL_TWO to INFECTION_LEVEL_TWO + 200) infection = "Acute Infection:" if(INFECTION_LEVEL_TWO + 200 to INFECTION_LEVEL_TWO + 300) infection = "Acute Infection+:" if(INFECTION_LEVEL_TWO + 300 to INFINITY) infection = "Acute Infection++:" dat += "" dat += "" dat += "" dat += "
OrganBurn DamageBrute DamageOther Wounds
[e.name][e.burn_dam][e.brute_dam][robot][bled][AN][splint][open][infected][imp][internal_bleeding][lung_ruptured]
[i.name]N/A[i.damage][infection]:[mech]
" if(HAS_TRAIT(target, TRAIT_BLIND)) dat += "Cataracts detected.
" if(HAS_TRAIT(target, TRAIT_COLORBLIND)) dat += "Photoreceptor abnormalities detected.
" if(HAS_TRAIT(target, TRAIT_NEARSIGHT)) dat += "Retinal misalignment detected.
" return dat