/* CONTAINS: T-RAY HEALTH ANALYZER GAS ANALYZER SLIME SCANNER NANITE SCANNER GENE SCANNER */ // Describes the three modes of scanning available for health analyzers #define SCANMODE_HEALTH 0 #define SCANMODE_WOUND 1 #define SCANMODE_COUNT 2 // Update this to be the number of scan modes if you add more #define SCANNER_CONDENSED 0 #define SCANNER_VERBOSE 1 /obj/item/t_scanner//SKYRAT EDIT - ICON OVERRIDEN BY AESTHETICS - SEE MODULE name = "\improper T-ray scanner" desc = "A terahertz-ray emitter and scanner used to detect underfloor objects such as cables and pipes." custom_price = PAYCHECK_ASSISTANT * 0.7 icon = 'icons/obj/device.dmi' icon_state = "t-ray0" var/on = FALSE slot_flags = ITEM_SLOT_BELT w_class = WEIGHT_CLASS_SMALL inhand_icon_state = "electronic" worn_icon_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/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(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" inhand_icon_state = "healthanalyzer" worn_icon_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. Has a side button to scan for chemicals, and can be toggled to scan wounds." 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 = PAYCHECK_HARD /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) % SCANMODE_COUNT switch(scanmode) if(SCANMODE_HEALTH) to_chat(user, "You switch the health analyzer to check physical health.") 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 if(ispodperson(M)&& !advanced) to_chat(user, "[M]'s biologal structure is too complex for the health analyzer.") return user.visible_message("[user] analyzes [M]'s vitals.", \ "You analyze [M]'s vitals.") switch (scanmode) if (SCANMODE_HEALTH) healthscan(user, M, mode, advanced) if (SCANMODE_WOUND) woundscan(user, M, src) add_fingerprint(user) /obj/item/healthanalyzer/attack_secondary(mob/living/victim, mob/living/user, params) chemscan(user, victim) return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN // Used by the PDA medical scanner too /proc/healthscan(mob/user, mob/living/M, mode = SCANNER_VERBOSE, advanced = FALSE) if(user.incapacitated()) return if(user.is_blind()) to_chat(user, "You realize that your scanner has no accessibility support for the 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 ? "Deceased" : "[round(M.health/M.maxHealth,0.01)*100]% healthy") if(HAS_TRAIT(M, TRAIT_FAKEDEATH) && !advanced) mob_status = "Deceased" oxy_loss = max(rand(1, 40), oxy_loss, (300 - (tox_loss + fire_loss + brute_loss))) // Random oxygen loss if(ishuman(M)) var/mob/living/carbon/human/H = M if(H.undergoing_cardiac_arrest() && H.stat != DEAD) render_list += "Subject suffering from heart attack: Apply defibrillation or other electric shock immediately!\n" if(H.has_reagent(/datum/reagent/inverse/technetium)) advanced = TRUE render_list += "Analyzing results for [M]:\nOverall status: [mob_status]\n" // Husk detection if(advanced && HAS_TRAIT_FROM(M, TRAIT_HUSK, BURN)) render_list += "Subject has been husked by severe burns.\n" else if (advanced && HAS_TRAIT_FROM(M, TRAIT_HUSK, CHANGELING_DRAIN)) render_list += "Subject has been husked by dessication.\n" else if(HAS_TRAIT(M, TRAIT_HUSK)) render_list += "Subject has been husked.\n" // Damage descriptions if(brute_loss > 10) render_list += "[brute_loss > 50 ? "Severe" : "Minor"] tissue damage detected.\n" if(fire_loss > 10) render_list += "[fire_loss > 50 ? "Severe" : "Minor"] burn damage detected.\n" if(oxy_loss > 10) render_list += "[oxy_loss > 50 ? "Severe" : "Minor"] oxygen deprivation detected.\n" if(tox_loss > 10) render_list += "[tox_loss > 50 ? "Severe" : "Minor"] amount of toxin damage detected.\n" if(M.getStaminaLoss()) render_list += "Subject appears to be suffering from fatigue.\n" if(advanced) render_list += "Fatigue Level: [M.getStaminaLoss()]%.\n" if (M.getCloneLoss()) render_list += "Subject appears to have [M.getCloneLoss() > 30 ? "Severe" : "Minor"] cellular damage.\n" if(advanced) render_list += "Cellular Damage Level: [M.getCloneLoss()].\n" if (!M.getorganslot(ORGAN_SLOT_BRAIN)) // brain not added to carbon/human check because it's funny to get to bully simple mobs render_list += "Subject lacks a brain.\n" if(ishuman(M)) var/mob/living/carbon/human/the_dude = M var/datum/species/the_dudes_species = the_dude.dna.species if (!(NOBLOOD in the_dudes_species.species_traits) && !the_dude.getorganslot(ORGAN_SLOT_HEART)) render_list += "Subject lacks a heart.\n" if (!(TRAIT_NOBREATH in the_dudes_species.species_traits) && !the_dude.getorganslot(ORGAN_SLOT_LUNGS)) render_list += "Subject lacks lungs.\n" if (!(TRAIT_NOMETABOLISM in the_dudes_species.species_traits) && !the_dude.getorganslot(ORGAN_SLOT_LIVER)) render_list += "Subject lacks a liver.\n" if (!(NOSTOMACH in the_dudes_species.species_traits) && !the_dude.getorganslot(ORGAN_SLOT_STOMACH)) render_list += "Subject lacks a stomach.\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_WOUND) trauma_desc += "fracture-derived " if(TRAUMA_RESILIENCE_MAGIC, TRAUMA_RESILIENCE_ABSOLUTE) trauma_desc += "permanent " trauma_desc += B.scan_desc trauma_text += trauma_desc render_list += "Cerebral traumas detected: subject appears to be suffering from [english_list(trauma_text)].\n" if(C.roundstart_quirks.len) render_list += "Subject Major Disabilities: [C.get_quirk_string(FALSE, CAT_QUIRK_MAJOR_DISABILITY)].\n" if(advanced) render_list += "Subject Minor Disabilities: [C.get_quirk_string(FALSE, CAT_QUIRK_MINOR_DISABILITY)].\n" if(advanced) render_list += "Brain Activity Level: [(200 - M.getOrganLoss(ORGAN_SLOT_BRAIN))/2]%.\n" if (M.radiation) render_list += "Subject is irradiated.\n" if(advanced) render_list += "Radiation Level: [M.radiation]%.\n" if(advanced && M.hallucinating()) render_list += "Subject is hallucinating.\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 = "General status:\ \ \ \ \ \ \ \ \ \ \ " for(var/o in damaged) var/obj/item/bodypart/org = o //head, left arm, right arm, etc. dmgreport += "\ \ " dmgreport += "
Damage:BruteBurnToxinSuffocation
Overall:[CEILING(brute_loss,1)][CEILING(fire_loss,1)][CEILING(tox_loss,1)][CEILING(oxy_loss,1)]
[capitalize(org.name)]:[(org.brute_dam > 0) ? "[CEILING(org.brute_dam,1)]" : "0"][(org.burn_dam > 0) ? "[CEILING(org.burn_dam,1)]" : "0"]
" 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 = "\nSubject does not have ears." if(istype(ears)) message = "" if(HAS_TRAIT_FROM(C, TRAIT_DEAF, GENETIC_MUTATION)) message = "\nSubject is genetically deaf." else if(HAS_TRAIT_FROM(C, TRAIT_DEAF, EAR_DAMAGE)) message = "\nSubject is deaf from ear damage." else if(HAS_TRAIT(C, TRAIT_DEAF)) message = "\nSubject is deaf." else if(ears.damage) message += "\nSubject has [ears.damage > ears.maxHealth ? "permanent ": "temporary "]hearing damage." if(ears.deaf) message += "\nSubject is [ears.damage > ears.maxHealth ? "permanently ": "temporarily "] deaf." render_list += "Ear status:[message == "" ? "\nHealthy." : message]\n" // Eye status var/obj/item/organ/eyes/eyes = C.getorganslot(ORGAN_SLOT_EYES) message = "\nSubject does not have eyes." if(istype(eyes)) message = "" if(C.is_blind()) message += "\nSubject is blind." if(HAS_TRAIT(C, TRAIT_NEARSIGHT)) message += "\nSubject is nearsighted." if(eyes.damage > 30) message += "\nSubject has severe eye damage." else if(eyes.damage > 20) message += "\nSubject has significant eye damage." else if(eyes.damage) message += "\nSubject has minor eye damage." render_list += "Eye status:[message == "" ? "\nHealthy." : 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 = "Organs:\ \ \ [advanced ? "" : ""]\ " for(var/obj/item/organ/organ in H.internal_organs) var/status = "" if(H.has_reagent(/datum/reagent/inverse/technetium)) if(organ.damage) status = " organ is [round((organ.damage/organ.maxHealth)*100, 1)]% damaged." else if (organ.organ_flags & ORGAN_FAILING) status = "Non-Functional" else if (organ.damage > organ.high_threshold) status = "Severely Damaged" else if (organ.damage > organ.low_threshold) status = "Mildly Damaged" if (status != "") render = TRUE toReport += "\ [advanced ? "" : ""]\ " if (render) render_list += toReport + "
Organ:DmgStatus
[organ.name]:[CEILING(organ.damage,1)][status]
" // tables do not need extra linebreak //Genetic damage if(advanced && H.has_dna()) render_list += "Genetic Stability: [H.dna.stability]%.\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.mutantbrain != initial(S.mutantbrain) \ || S.mutantheart != initial(S.mutantheart) \ || S.mutanteyes != initial(S.mutanteyes) \ || S.mutantears != initial(S.mutantears) \ || S.mutanthands != initial(S.mutanthands) \ || S.mutanttongue != initial(S.mutanttongue) \ || S.mutantliver != initial(S.mutantliver) \ || S.mutantstomach != initial(S.mutantstomach) \ || S.mutantappendix != initial(S.mutantappendix) \ || S.flying_species != initial(S.flying_species) render_list += "Species: [S.name][mutant ? "-derived mutant" : ""]\n" render_list += "Core temperature: [round(H.coretemperature-T0C,0.1)] °C ([round(H.coretemperature*1.8-459.67,0.1)] °F)\n" render_list += "Body temperature: [round(M.bodytemperature-T0C,0.1)] °C ([round(M.bodytemperature*1.8-459.67,0.1)] °F)\n" // Time of death if(M.tod && (M.stat == DEAD || ((HAS_TRAIT(M, TRAIT_FAKEDEATH)) && !advanced))) render_list += "Time of Death: [M.tod]\n" var/tdelta = round(world.time - M.timeofdeath) render_list += "Subject died [DisplayTimeText(tdelta)] ago.\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 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 += "
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 render_list += "
" for(var/thing in M.diseases) var/datum/disease/D = thing if(!(D.visibility_flags & HIDDEN_SCANNER)) render_list += "Warning: [D.form] detected\n\
Name: [D.name].\nType: [D.spread_text].\nStage: [D.stage]/[D.max_stages].\nPossible Cure: [D.cure_text]
\
" // 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.is_bleeding()) render_list += "Subject is bleeding!\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 += "Blood level: LOW [blood_percent] %, [C.blood_volume] cl, type: [blood_type]\n" else if(C.blood_volume <= BLOOD_VOLUME_OKAY) render_list += "Blood level: CRITICAL [blood_percent] %, [C.blood_volume] cl, type: [blood_type]\n" else render_list += "Blood level: [blood_percent] %, [C.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 += "[!cyberimp_detect ? "[CI.get_examine_string(user)]" : ", [CI.get_examine_string(user)]"]" if(cyberimp_detect) render_list += "Detected cybernetic modifications:\n" render_list += "[cyberimp_detect]\n" SEND_SIGNAL(M, COMSIG_NANITE_SCAN, user, FALSE) to_chat(user, jointext(render_list, ""), trailing_newline = FALSE) // we handled the last
so we don't need handholding /proc/chemscan(mob/living/user, mob/living/M) if(user.incapacitated()) return if(user.is_blind()) to_chat(user, "You realize that your scanner has no accessibility support for the blind!") return if(istype(M) && M.reagents) var/render_list = list() if(M.reagents.reagent_list.len) render_list += "Subject contains the following reagents in their blood:\n" for(var/r in M.reagents.reagent_list) var/datum/reagent/reagent = r if(reagent.chemical_flags & REAGENT_INVISIBLE) //Don't show hidden chems on scanners continue render_list += "[round(reagent.volume, 0.001)] units of [reagent.name][reagent.overdosed ? " - OVERDOSING" : ".
"]\n" else render_list += "Subject contains no reagents in their blood.\n" var/obj/item/organ/stomach/belly = M.getorganslot(ORGAN_SLOT_STOMACH) if(belly) if(belly.reagents.reagent_list.len) render_list += "Subject contains the following reagents in their stomach:\n" for(var/bile in belly.reagents.reagent_list) var/datum/reagent/bit = bile if(bit.chemical_flags & REAGENT_INVISIBLE) //Don't show hidden chems on scanners continue if(!belly.food_reagents[bit.type]) render_list += "[round(bit.volume, 0.001)] units of [bit.name][bit.overdosed ? " - OVERDOSING" : ".
"]\n" else var/bit_vol = bit.volume - belly.food_reagents[bit.type] if(bit_vol > 0) render_list += "[round(bit_vol, 0.001)] units of [bit.name][bit.overdosed ? " - OVERDOSING" : "."]\n" else render_list += "Subject contains no reagents in their stomach.\n" if(LAZYLEN(M.mind?.active_addictions)) render_list += "Subject is addicted to the following types of drug:\n" for(var/datum/addiction/addiction_type as anything in M.mind.active_addictions) render_list += "[initial(addiction_type.name)]\n" else render_list += "Subject is not addicted to any types of drug.\n" to_chat(user, jointext(render_list, ""), trailing_newline = FALSE) // we handled the last
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 /// 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) || user.incapacitated()) return if(user.is_blind()) to_chat(user, "You realize that your scanner has no accessibility support for the blind!") 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//SKYRAT EDIT - ICON OVERRIDEN BY AESTHETICS - SEE MODULE desc = "A hand-held environmental scanner which reports current gas levels. Alt-Click to use the built in barometer function." name = "analyzer" custom_price = PAYCHECK_ASSISTANT * 0.9 icon = 'icons/obj/device.dmi' icon_state = "analyzer" inhand_icon_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.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 += "Results:\ \nPressure: [round(pressure, 0.01)] kPa\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 += "Nitrogen: [round(n2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/nitrogen][MOLES], 0.01)] mol)\ \nOxygen: [round(o2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/oxygen][MOLES], 0.01)] mol)\ \nCO2: [round(co2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/carbon_dioxide][MOLES], 0.01)] mol)\ \nPlasma: [round(plasma_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/plasma][MOLES], 0.01)] mol)\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 += "[env_gases[id][GAS_META][META_GAS_NAME]]: [round(gas_concentration*100, 0.01)] % ([round(env_gases[id][MOLES], 0.01)] mol)\n" render_list += "Temperature: [round(environment.temperature-T0C, 0.01)] °C ([round(environment.temperature, 0.01)] K)\n" to_chat(user, jointext(render_list, ""), trailing_newline = FALSE) // we handled the last
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, "[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 ? timeleft(next_hit) : -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(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("[user] uses the analyzer on [icon2html(icon, viewers(user))] [target].", "You use the analyzer on [icon2html(icon, user)] [target].") render_list += "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 render_list += "Node [airs.Find(g)]" var/datum/gas_mixture/air_contents = g var/total_moles = air_contents.total_moles() var/pressure = air_contents.return_pressure() var/volume = air_contents.return_volume() //could just do mixture.volume... but safety, I guess? var/temperature = air_contents.temperature var/cached_scan_results = air_contents.analyzer_results if(total_moles > 0) render_list += "Moles: [round(total_moles, 0.01)] mol\ \nVolume: [volume] L\ \nPressure: [round(pressure,0.01)] kPa" var/list/cached_gases = air_contents.gases for(var/id in cached_gases) var/gas_concentration = cached_gases[id][MOLES]/total_moles render_list += "[cached_gases[id][GAS_META][META_GAS_NAME]]: [round(gas_concentration*100, 0.01)] % ([round(cached_gases[id][MOLES], 0.01)] mol)" render_list += "Temperature: [round(temperature - T0C,0.01)] °C ([round(temperature, 0.01)] K)" else render_list += airs.len > 1 ? "This node is empty!" : "[target] is empty!" if(cached_scan_results && cached_scan_results["fusion"]) //notify the user if a fusion reaction was detected render_list += "Large amounts of free neutrons detected in the air indicate that a fusion reaction took place.\ \nInstability of the last fusion reaction: [round(cached_scan_results["fusion"], 0.01)]." 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" inhand_icon_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, "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) var/to_render = "========================\ \nSlime scan results:\ \n[T.colour] [T.is_adult ? "adult" : "baby"] slime\ \nNutrition: [T.nutrition]/[T.get_max_nutrition()]" if (T.nutrition < T.get_starve_nutrition()) to_render += "\nWarning: slime is starving!" else if (T.nutrition < T.get_hunger_nutrition()) to_render += "\nWarning: slime is hungry" 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 += "\nCore mutation in progress: [T.effectmod]\ \nProgress in core mutation: [T.applied] / [SLIME_EXTRACT_CROSSING_REQUIRED]" to_chat(user, to_render + "\n========================") /obj/item/nanite_scanner name = "nanite scanner" icon = 'icons/obj/device.dmi' icon_state = "nanite_scanner" inhand_icon_state = "electronic" worn_icon_state = "electronic" 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] analyzes [M]'s nanites.", \ "You analyze [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//SKYRAT EDIT - ICON OVERRIDEN BY AESTHETICS - SEE MODULE name = "genetic sequence scanner" icon = 'icons/obj/device.dmi' icon_state = "gene" inhand_icon_state = "healthanalyzer" worn_icon_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_GENELESS) && !HAS_TRAIT(M, TRAIT_BADDNA)) //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] fails to analyze [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 /obj/item/scanner_wand name = "kiosk scanner wand" icon = 'icons/obj/device.dmi' icon_state = "scanner_wand" inhand_icon_state = "healthanalyzer" lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' desc = "A 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("[user] targets himself for scanning.", \ to_chat(user, "You try scanning [M], before realizing you're holding the scanner backwards. Whoops.")) selected_target = user return if(!ishuman(M)) to_chat(user, "You can only scan human-like, non-robotic beings.") selected_target = null return user.visible_message("[user] targets [M] for scanning.", \ "You target [M] vitals.") selected_target = M return /obj/item/scanner_wand/attack_self(mob/user) to_chat(user, "You clear the scanner's target.") selected_target = null /obj/item/scanner_wand/proc/return_patient() var/returned_target = selected_target return returned_target #undef SCANMODE_HEALTH #undef SCANMODE_WOUND #undef SCANMODE_COUNT #undef SCANNER_CONDENSED #undef SCANNER_VERBOSE