mirror of
https://github.com/Aurorastation/Aurora.3.git
synced 2026-07-21 12:56:10 +01:00
8111a8489b
Many, many, many items have inhand sprites in their .dmis but for whatever reasons do not display them in-game. This PR: 1. Updates many item definitions to point to their already-existing inhands correctly. This consists largely of held tools, but also gas tanks and jetpacks mounted in the suit storage slot. 2. Adds a few codersprites made by me for objects with either missing inhands or poorly matching mishands (IE, the tape recorder, which has a black case, reused the white inhand sprites of the health analyzer). The new sprites are modified or recolored variations of other inhand sprites from our repo, except for circuitboards which are new. <img width="444" height="400" alt="image" src="https://github.com/user-attachments/assets/7f107b9a-fe24-4e31-8f16-4d34768ee117" /> 3. Adds inhand sprites for Inflatables and Inflatable Boxes made by Tomixcomics. <img width="424" height="101" alt="image" src="https://github.com/user-attachments/assets/434107c4-8577-49a2-a58e-d6b014c03933" /> 4. Ports inhand sprites for the Hydraulic Rescue Tool from tg's Jaws of Life. <img width="224" height="94" alt="Screenshot 2026-02-07 172931" src="https://github.com/user-attachments/assets/070c7956-f6a8-4fb5-870f-10c64afcc8b3" /> 5. Some additional cleanup while in the area. The 'analyzer' has been renamed the 'gas analyzer' to be consistent with the other analyzer objects, standardized icon_state naming conventions where I saw oddballs, updated code docs to use DMDocs when in the area, etc. ### Asset Licenses The following assets that **have not** been created by myself are included in this PR: | Path | Original Author | License | | --- | --- | --- | | icons/obj/item/hydraulic_rescue_tool.dmi | [SomeAngryMiner (bee station)](https://github.com/BeeStation/BeeStation-Hornet/pull/2487), [maxymax (/tg/station)](https://github.com/tgstation/tgstation/pull/58616) | CC-BY-SA | | icons/obj/item/inflatables.dmi | [Tomixcomics](https://github.com/tomixcomics) | CC-BY-SA |
841 lines
31 KiB
Plaintext
841 lines
31 KiB
Plaintext
/*
|
|
CONTAINS:
|
|
HEALTH ANALYZER
|
|
GAS ANALYZER
|
|
MASS SPECTROMETER
|
|
REAGENT SCANNER
|
|
BREATH ANALYZER
|
|
*/
|
|
|
|
/obj/item/healthanalyzer
|
|
name = "health analyzer"
|
|
desc = "A hand-held body scanner able to distinguish vital signs of the subject."
|
|
icon = 'icons/obj/item/scanner.dmi'
|
|
icon_state = "healthanalyzer"
|
|
item_state = "healthanalyzer"
|
|
contained_sprite = TRUE
|
|
obj_flags = OBJ_FLAG_CONDUCTABLE
|
|
slot_flags = SLOT_BELT
|
|
throwforce = 3
|
|
w_class = WEIGHT_CLASS_SMALL
|
|
throw_speed = 5
|
|
throw_range = 10
|
|
matter = list(MATERIAL_ALUMINIUM = 200)
|
|
origin_tech = list(TECH_MAGNET = 1, TECH_BIO = 1)
|
|
var/last_scan = 0
|
|
var/mode = 1
|
|
var/sound_scan = FALSE
|
|
|
|
/obj/item/healthanalyzer/attack(mob/living/target_mob, mob/living/user, target_zone)
|
|
sound_scan = FALSE
|
|
if(last_scan <= world.time - 20) //Spam limiter.
|
|
last_scan = world.time
|
|
sound_scan = TRUE
|
|
user.setClickCooldown(DEFAULT_QUICK_COOLDOWN)
|
|
flick("[icon_state]-scan", src) //makes it so that it plays the scan animation on a successful scan
|
|
health_scan_mob(target_mob, user, mode, sound_scan = sound_scan)
|
|
add_fingerprint(user)
|
|
|
|
/obj/item/healthanalyzer/attack_self(mob/user)
|
|
sound_scan = FALSE
|
|
if(last_scan <= world.time - 20) //Spam limiter.
|
|
last_scan = world.time
|
|
sound_scan = TRUE
|
|
user.setClickCooldown(DEFAULT_QUICK_COOLDOWN)
|
|
flick("[icon_state]-scan", src) //makes it so that it plays the scan animation on a successful scan
|
|
health_scan_mob(user, user, mode, sound_scan = sound_scan)
|
|
add_fingerprint(user)
|
|
|
|
/// Calculates severity based on the ratios defined external limbs.
|
|
/proc/get_wound_severity(damage_ratio, can_heal_overkill, uppercase = FALSE)
|
|
var/degree = "none"
|
|
|
|
switch(damage_ratio)
|
|
if (0 to 10)
|
|
degree = "minor"
|
|
if (11 to 25)
|
|
degree = "moderate"
|
|
if (26 to 50)
|
|
degree = "significant"
|
|
if (51 to 75)
|
|
degree = "severe"
|
|
if (76 to 99)
|
|
degree = "extreme"
|
|
if (100 to INFINITY)
|
|
degree = can_heal_overkill ? "critical" : "irreparable"
|
|
|
|
if(uppercase)
|
|
degree = capitalize(degree)
|
|
return degree
|
|
|
|
/proc/get_severity(amount, can_heal_overkill, uppercase = FALSE)
|
|
var/output = "none"
|
|
|
|
switch(amount)
|
|
if (0)
|
|
output = "none"
|
|
if (1 to 10)
|
|
output = "minor"
|
|
if (11 to 25)
|
|
output = "moderate"
|
|
if (26 to 50)
|
|
output = "significant"
|
|
if (51 to 75)
|
|
output = "severe"
|
|
if (76 to 99)
|
|
output = "extreme"
|
|
if (100 to INFINITY)
|
|
output = can_heal_overkill ? "critical" : "irreparable"
|
|
|
|
if(uppercase)
|
|
output = capitalize(output)
|
|
return output
|
|
|
|
/proc/health_scan_mob(var/mob/M, var/mob/living/user, var/show_limb_damage = TRUE, var/just_scan = FALSE, var/sound_scan)
|
|
if(!just_scan)
|
|
if (((user.is_clumsy()) || (user.mutations & DUMB)) && prob(50))
|
|
user.visible_message("<b>[user]</b> runs the scanner over the floor.",
|
|
SPAN_NOTICE("You run the scanner over the floor."),
|
|
SPAN_NOTICE("You hear metal repeatedly clunking against the floor."))
|
|
|
|
to_chat(user, SPAN_NOTICE("<b>Scan results for the ERROR:</b>"))
|
|
if(sound_scan)
|
|
playsound(user.loc, 'sound/items/healthscanner/healthscanner_used.ogg', 25, extrarange = SILENCED_SOUND_EXTRARANGE)
|
|
return
|
|
|
|
if(!user.IsAdvancedToolUser())
|
|
to_chat(user, SPAN_WARNING("You don't have the dexterity to do this!"))
|
|
return
|
|
|
|
user.visible_message("<b>[user]</b> runs a scanner over [M].",SPAN_NOTICE("You run the scanner over [M]."))
|
|
|
|
if(!istype(M, /mob/living/carbon/human))
|
|
to_chat(user, SPAN_WARNING("This scanner is designed for humanoid patients only."))
|
|
if(sound_scan)
|
|
playsound(user.loc, 'sound/items/healthscanner/healthscanner_used.ogg', 25, extrarange = SILENCED_SOUND_EXTRARANGE)
|
|
return
|
|
|
|
var/mob/living/carbon/human/H = M
|
|
|
|
if(H.isSynthetic() && !H.isFBP())
|
|
to_chat(user, SPAN_WARNING("This scanner is designed for organic humanoid patients only."))
|
|
if(sound_scan)
|
|
playsound(user.loc, 'sound/items/healthscanner/healthscanner_used.ogg', 25, extrarange = SILENCED_SOUND_EXTRARANGE)
|
|
return
|
|
|
|
. = list()
|
|
var/header = list()
|
|
var/b
|
|
var/endb
|
|
var/dat = list()
|
|
|
|
header += "<style> .scan_notice{color: #5f94af;}</style>"
|
|
header += "<style> .scan_warning{color: #ff0000; font-style: italic;}</style>"
|
|
header += "<style> .scan_danger{color: #ff0000; font-weight: bold;}</style>"
|
|
header += "<style> .scan_red{color:red}</style>"
|
|
header += "<style> .scan_green{color:green}</style>"
|
|
header += "<style> .scan_blue{color: #5f94af}</style>"
|
|
header += "<style> .scan_orange{color:#ffa500}</style>"
|
|
b = "<b>"
|
|
endb = "</b>"
|
|
|
|
. += "[b]Scan results for \the [H]:[endb]"
|
|
|
|
if(H.stat == DEAD || H.status_flags & FAKEDEATH)
|
|
dat += "<span class='scan_warning'>[b]Time of Death:[endb] [worldtime2text(H.timeofdeath)]</span>"
|
|
|
|
// Brain activity.
|
|
var/brain_status = H.get_brain_status()
|
|
dat += "Brain activity: [brain_status]"
|
|
var/brain_result = H.get_brain_result()
|
|
|
|
if(sound_scan)
|
|
switch(brain_result)
|
|
if(0)
|
|
playsound(user.loc, 'sound/items/healthscanner/healthscanner_dead.ogg', 25, extrarange = SILENCED_SOUND_EXTRARANGE)
|
|
if(-1)
|
|
playsound(user.loc, 'sound/items/healthscanner/healthscanner_used.ogg', 25, extrarange = SILENCED_SOUND_EXTRARANGE)
|
|
else
|
|
if(brain_result <= 25)
|
|
playsound(user.loc, 'sound/items/healthscanner/healthscanner_critical.ogg', 25, extrarange = SHORT_RANGE_SOUND_EXTRARANGE)
|
|
else if(brain_result <= 50)
|
|
playsound(user.loc, 'sound/items/healthscanner/healthscanner_danger.ogg', 25, extrarange = SHORT_RANGE_SOUND_EXTRARANGE)
|
|
else if(brain_result <= 90)
|
|
playsound(user.loc, 'sound/items/healthscanner/healthscanner_used.ogg', 25, extrarange = SILENCED_SOUND_EXTRARANGE)
|
|
else
|
|
playsound(user.loc, 'sound/items/healthscanner/healthscanner_stable.ogg', 25, extrarange = SILENCED_SOUND_EXTRARANGE)
|
|
|
|
// Pulse rate.
|
|
var/pulse_result = "normal"
|
|
if(H.should_have_organ(BP_HEART))
|
|
if(H.status_flags & FAKEDEATH)
|
|
pulse_result = SPAN_DANGER("0")
|
|
else
|
|
pulse_result = H.get_pulse(GETPULSE_TOOL)
|
|
if(H.pulse() == PULSE_NONE)
|
|
pulse_result = "<span class='scan_danger'>[pulse_result] BPM</span>"
|
|
else if(H.pulse() < PULSE_NORM)
|
|
pulse_result = "<span class='scan_notice'>[pulse_result] BPM</span>"
|
|
else if(H.pulse() > PULSE_NORM)
|
|
pulse_result = "<span class='scan_warning'>[pulse_result] BPM</span>"
|
|
else
|
|
pulse_result = "<span class='scan_green'>[pulse_result] BPM</span>"
|
|
else
|
|
pulse_result = "<span class='scan_danger'>0</span>"
|
|
dat += "Pulse rate: [pulse_result]"
|
|
|
|
// Body temperature. Rounds to one digit after decimal.
|
|
var/temperature_string
|
|
if(H.bodytemperature < H.species.cold_level_1 || H.bodytemperature > H.species.heat_level_1)
|
|
temperature_string = "Body temperature: <span class='scan_warning'>[round(H.bodytemperature-T0C, 0.1)]°C ([round(H.bodytemperature*1.8-459.67, 0.1)]°F)</span>"
|
|
else
|
|
temperature_string = "Body temperature: <span class='scan_green'>[round(H.bodytemperature-T0C, 0.1)]°C ([round(H.bodytemperature*1.8-459.67, 0.1)]°F)</span>"
|
|
dat += temperature_string
|
|
|
|
// Blood pressure and blood type. Based on the idea of a normal blood pressure being 120 over 80.
|
|
if(H.should_have_organ(BP_HEART))
|
|
var/blood_pressure_string
|
|
switch(H.get_blood_pressure_alert())
|
|
if(1)
|
|
blood_pressure_string = "<span class='scan_danger'>[H.get_blood_pressure()]</span>"
|
|
if(2)
|
|
blood_pressure_string = "<span class='scan_green'>[H.get_blood_pressure()]</span>"
|
|
if(3)
|
|
blood_pressure_string = "<span class='scan_warning'>[H.get_blood_pressure()]</span>"
|
|
if(4)
|
|
blood_pressure_string = "<span class='scan_danger'>[H.get_blood_pressure()]</span>"
|
|
|
|
var/blood_volume_string = "<span class='scan_green'>\>[BLOOD_VOLUME_SAFE]%</span>"
|
|
switch(H.get_blood_volume())
|
|
if(BLOOD_VOLUME_OKAY to BLOOD_VOLUME_SAFE)
|
|
blood_volume_string = "<span class='scan_notice'>\<[BLOOD_VOLUME_SAFE]%</span>"
|
|
if(BLOOD_VOLUME_SURVIVE to BLOOD_VOLUME_OKAY)
|
|
blood_volume_string = "<span class='scan_warning'>\<[BLOOD_VOLUME_OKAY]%</span>"
|
|
if(-(INFINITY) to BLOOD_VOLUME_SURVIVE)
|
|
blood_volume_string = "<span class='scan_danger'>\<[BLOOD_VOLUME_SURVIVE]%</span>"
|
|
|
|
var/oxygenation = H.get_blood_oxygenation()
|
|
var/oxygenation_string = "<span class='scan_green'>[oxygenation]%</span>"
|
|
switch(oxygenation)
|
|
if(BLOOD_VOLUME_OKAY to BLOOD_VOLUME_SAFE)
|
|
oxygenation_string = "<span class='scan_notice'>[oxygenation]%</span>"
|
|
if(BLOOD_VOLUME_SURVIVE to BLOOD_VOLUME_OKAY)
|
|
oxygenation_string = "<span class='scan_warning'>[oxygenation]%</span>"
|
|
if(-(INFINITY) to BLOOD_VOLUME_SURVIVE)
|
|
oxygenation_string = "<span class='scan_danger'>[oxygenation]%</span>"
|
|
if(H.status_flags & FAKEDEATH)
|
|
oxygenation_string = "<span class='scan_danger'>[rand(0,10)]%</span>"
|
|
dat += "Blood pressure: [blood_pressure_string]"
|
|
dat += "Blood oxygenation: [oxygenation_string]"
|
|
dat += "Blood volume: [blood_volume_string]"
|
|
dat += "Blood type: <span class ='scan_green'>[H.dna.b_type]</span>"
|
|
else
|
|
dat += "Blood pressure: N/A"
|
|
|
|
// Traumatic shock.
|
|
if(H.is_asystole() || (H.status_flags & FAKEDEATH))
|
|
dat += "<span class='scan_danger'>Cardiovascular shock detected. Administer CPR immediately.</span>"
|
|
else if(H.shock_stage > 80)
|
|
dat += "<span class='scan_warning'>Imminent cardiovascular shock. Pain relief recommended.</span>"
|
|
|
|
if(H.getOxyLoss() > 50)
|
|
dat += "<span class='scan_blue'>[b]Severe oxygen deprivation detected.[endb]</span>"
|
|
if(H.getToxLoss() > 50)
|
|
dat += "<span class='scan_orange'>[b]Major systemic organ failure detected.[endb]</span>"
|
|
if(H.getFireLoss() > 50)
|
|
dat += "<span class='scan_orange'>[b]Severe burn damage detected.[endb]</span>"
|
|
if(H.getBruteLoss() > 50)
|
|
dat += "<span class='scan_red'>[b]Severe anatomical damage detected.[endb]</span>"
|
|
|
|
if(show_limb_damage)
|
|
var/list/damaged = H.get_damaged_organs(1,1)
|
|
if(damaged.len)
|
|
for(var/obj/item/organ/external/org in damaged)
|
|
var/limb_result = "[capitalize(org.name)][BP_IS_ROBOTIC(org) ? " (Cybernetic)" : ""]:"
|
|
if(org.brute_dam > 0)
|
|
limb_result = "[limb_result] \[<font color = 'red'><b>[get_severity(org.brute_dam, TRUE)] physical trauma</b></font>\]"
|
|
if(org.burn_dam > 0)
|
|
limb_result = "[limb_result] \[<font color = '#ffa500'><b>[get_severity(org.burn_dam, TRUE)] burns</b></font>\]"
|
|
if(org.status & ORGAN_BLEEDING)
|
|
limb_result = "[limb_result] \[<span class='scan_danger'>bleeding</span>\]"
|
|
var/is_bandaged = org.is_bandaged()
|
|
var/is_salved = org.is_salved()
|
|
if(is_bandaged && is_salved)
|
|
var/icon/B = icon('icons/obj/item/stacks/medical.dmi', "bandaged")
|
|
var/icon/S = icon('icons/obj/item/stacks/medical.dmi', "salved")
|
|
limb_result = "[limb_result] \[[icon2html(B, user)] | [icon2html(S, user)]\]"
|
|
else if(is_bandaged)
|
|
var/icon/B = icon('icons/obj/item/stacks/medical.dmi', "bandaged")
|
|
limb_result = "[limb_result] \[[icon2html(B, user)]\]"
|
|
else if(is_salved)
|
|
var/icon/S = icon('icons/obj/item/stacks/medical.dmi', "salved")
|
|
limb_result = "[limb_result] \[[icon2html(S, user)]\]"
|
|
dat += limb_result
|
|
else
|
|
dat += "No detectable limb injuries."
|
|
|
|
for(var/name in H.organs_by_name)
|
|
var/obj/item/organ/external/e = H.organs_by_name[name]
|
|
if(!e)
|
|
continue
|
|
var/limb = e.name
|
|
if(e.status & ORGAN_BROKEN)
|
|
if(((e.name == BP_L_ARM) || (e.name == BP_R_ARM) || (e.name == BP_L_LEG) || (e.name == BP_R_LEG)) && !(e.status & ORGAN_SPLINTED))
|
|
dat += "<span class='scan_warning'>Unsecured fracture in subject [limb]. Splinting recommended for transport.</span>"
|
|
|
|
for(var/name in H.organs_by_name)
|
|
var/obj/item/organ/external/e = H.organs_by_name[name]
|
|
if(e && e.status & ORGAN_BROKEN)
|
|
dat += "<span class='scan_warning'>Bone fractures detected. Advanced scanner required for location.</span>"
|
|
break
|
|
|
|
var/found_bleed
|
|
var/found_tendon
|
|
var/found_disloc
|
|
for(var/obj/item/organ/external/e in H.organs)
|
|
if(e)
|
|
if(!found_disloc && e.dislocated == 2)
|
|
dat += "<span class='scan_warning'>Dislocation detected. Advanced scanner required for location.</span>"
|
|
found_disloc = TRUE
|
|
if(!found_bleed && (e.status & ORGAN_ARTERY_CUT))
|
|
dat += "<span class='scan_warning'>Arterial bleeding detected. Advanced scanner required for location.</span>"
|
|
found_bleed = TRUE
|
|
if(!found_tendon && (e.tendon_status() & TENDON_CUT))
|
|
dat += "<span class='scan_warning'>Tendon or ligament damage detected. Advanced scanner required for location.</span>"
|
|
found_tendon = TRUE
|
|
if(found_disloc && found_bleed && found_tendon)
|
|
break
|
|
|
|
. += dat
|
|
dat = list()
|
|
|
|
// Reagent data.
|
|
. += "[b]Reagent scan:[endb]"
|
|
|
|
var/print_reagent_default_message = TRUE
|
|
|
|
if(H.reagents.total_volume)
|
|
var/unknown = 0
|
|
var/reagentdata[0]
|
|
for(var/_R in H.reagents.reagent_volumes)
|
|
var/singleton/reagent/R = GET_SINGLETON(_R)
|
|
if(R.scannable)
|
|
print_reagent_default_message = FALSE
|
|
reagentdata["[_R]"] = SPAN_NOTICE(" [round(REAGENT_VOLUME(H.reagents, _R), 1)]u [R.name]")
|
|
else
|
|
unknown++
|
|
if(reagentdata.len)
|
|
print_reagent_default_message = FALSE
|
|
dat += SPAN_NOTICE("Beneficial reagents detected in subject's blood:")
|
|
for(var/d in reagentdata)
|
|
dat += reagentdata[d]
|
|
if(unknown)
|
|
print_reagent_default_message = FALSE
|
|
dat += SPAN_WARNING("Warning: Unknown substance[(unknown>1)?"s":""] detected in subject's blood.")
|
|
|
|
var/datum/reagents/ingested = H.get_ingested_reagents()
|
|
if(ingested && ingested.total_volume)
|
|
var/unknown = 0
|
|
for(var/_R in ingested.reagent_volumes)
|
|
var/singleton/reagent/R = GET_SINGLETON(_R)
|
|
if(R.scannable)
|
|
print_reagent_default_message = FALSE
|
|
dat += SPAN_NOTICE("[R.name] found in subject's stomach.")
|
|
else
|
|
++unknown
|
|
if(unknown)
|
|
print_reagent_default_message = FALSE
|
|
dat += SPAN_WARNING("Non-medical reagent[(unknown > 1)?"s":""] found in subject's stomach.")
|
|
|
|
if(print_reagent_default_message)
|
|
dat += "No results."
|
|
|
|
. += dat
|
|
|
|
header = jointext(header, null)
|
|
. = jointext(.,"<br>")
|
|
. = jointext(list(header,.),null)
|
|
|
|
if(user)
|
|
to_chat(user, "<hr>")
|
|
to_chat(user, .)
|
|
to_chat(user, "<hr>")
|
|
|
|
/obj/item/healthanalyzer/verb/toggle_mode()
|
|
set name = "Switch Verbosity"
|
|
set category = "Object.Held"
|
|
set src in usr
|
|
|
|
mode = !mode
|
|
|
|
if(mode)
|
|
to_chat(usr, "The scanner now shows specific limb damage.")
|
|
else
|
|
to_chat(usr, "The scanner no longer shows limb damage.")
|
|
|
|
/obj/item/analyzer
|
|
name = "gas analyzer"
|
|
desc = "A hand-held environmental scanner which reports current gas levels."
|
|
icon = 'icons/obj/item/scanner.dmi'
|
|
icon_state = "airanalyzer"
|
|
item_state = "airanalyzer"
|
|
contained_sprite = TRUE
|
|
w_class = WEIGHT_CLASS_SMALL
|
|
obj_flags = OBJ_FLAG_CONDUCTABLE
|
|
slot_flags = SLOT_BELT
|
|
throwforce = 5
|
|
throw_speed = 4
|
|
throw_range = 20
|
|
|
|
matter = list(MATERIAL_ALUMINIUM = 30, MATERIAL_GLASS = 20)
|
|
|
|
origin_tech = list(TECH_MAGNET = 1, TECH_ENGINEERING = 1)
|
|
|
|
/obj/item/analyzer/atmosanalyze(var/mob/user)
|
|
if(!user) return
|
|
var/air = user.return_air()
|
|
if (!air) return
|
|
flick("[icon_state]-scan", src)
|
|
return atmosanalyzer_scan(src, air, user)
|
|
|
|
/obj/item/analyzer/attack_self(mob/user as mob)
|
|
|
|
if (user.stat)
|
|
return
|
|
if (!usr.IsAdvancedToolUser())
|
|
to_chat(usr, SPAN_WARNING("You don't have the dexterity to do this!"))
|
|
return
|
|
|
|
analyze_gases(src, user)
|
|
return
|
|
|
|
/obj/item/mass_spectrometer
|
|
name = "mass spectrometer"
|
|
desc = "A hand-held mass spectrometer which identifies trace chemicals in a blood sample."
|
|
icon = 'icons/obj/item/scanner.dmi'
|
|
icon_state = "spectrometer"
|
|
// Reuses the basic health analyzer inhands.
|
|
item_state = "healthanalyzer"
|
|
contained_sprite = TRUE
|
|
w_class = WEIGHT_CLASS_SMALL
|
|
atom_flags = ATOM_FLAG_OPEN_CONTAINER
|
|
obj_flags = OBJ_FLAG_CONDUCTABLE
|
|
slot_flags = SLOT_BELT
|
|
throwforce = 5
|
|
throw_speed = 4
|
|
throw_range = 20
|
|
|
|
matter = list(MATERIAL_ALUMINIUM = 30, MATERIAL_GLASS = 20)
|
|
|
|
origin_tech = list(TECH_MAGNET = 2, TECH_BIO = 2)
|
|
var/details = FALSE
|
|
|
|
/obj/item/mass_spectrometer/Initialize()
|
|
. = ..()
|
|
create_reagents(5)
|
|
|
|
/obj/item/mass_spectrometer/on_reagent_change()
|
|
clear_blood_overlay()
|
|
if(reagents.total_volume)
|
|
icon_state = initial(icon_state) + "_s"
|
|
var/image/I = image(icon, null, "[initial(icon_state)]-reagent")
|
|
I.color = reagents.get_color()
|
|
add_blood_overlay(I)
|
|
else
|
|
icon_state = initial(icon_state)
|
|
|
|
/obj/item/mass_spectrometer/proc/clear_blood_overlay()
|
|
underlays = null
|
|
|
|
/obj/item/mass_spectrometer/proc/add_blood_overlay(var/image/I)
|
|
underlays += I
|
|
|
|
/obj/item/mass_spectrometer/attack_self(mob/user)
|
|
if(use_check_and_message(user))
|
|
return
|
|
if(reagents.total_volume)
|
|
if(LAZYLEN(reagents.reagent_volumes) > 1)
|
|
to_chat(user, SPAN_WARNING("There isn't enough blood in the sample!"))
|
|
return
|
|
if(!REAGENT_DATA(reagents, /singleton/reagent/blood))
|
|
to_chat(user, SPAN_WARNING("The sample was contaminated with non-blood reagents!"))
|
|
return
|
|
var/list/blood_traces = reagents.reagent_data[/singleton/reagent/blood]["trace_chem"]
|
|
var/list/output_text = list("Trace Chemicals Found:")
|
|
for(var/_C in blood_traces)
|
|
var/singleton/reagent/C = GET_SINGLETON(_C)
|
|
if(C.spectro_hidden && !details)
|
|
continue
|
|
if(details)
|
|
output_text += "- [C] ([max(round(blood_traces[_C], 0.1), 0.1)] units)"
|
|
else
|
|
output_text += "- [C]"
|
|
if(length(output_text) == 1)
|
|
output_text[1] = SPAN_NOTICE("No trace chemicals found.")
|
|
to_chat(user, jointext(output_text, "\n"))
|
|
|
|
/obj/item/mass_spectrometer/adv
|
|
name = "advanced mass spectrometer"
|
|
icon_state = "spectrometer_adv"
|
|
details = TRUE
|
|
origin_tech = list(TECH_MAGNET = 4, TECH_BIO = 2)
|
|
|
|
/obj/item/mass_spectrometer/adv/clear_blood_overlay()
|
|
ClearOverlays()
|
|
|
|
/obj/item/mass_spectrometer/adv/add_blood_overlay(var/image/I)
|
|
AddOverlays(I)
|
|
|
|
/obj/item/reagent_scanner
|
|
name = "reagent scanner"
|
|
desc = "A hand-held reagent scanner which identifies chemical agents."
|
|
icon = 'icons/obj/item/scanner.dmi'
|
|
icon_state = "reagent_scanner"
|
|
// Reuses the basic health analyzer inhands.
|
|
item_state = "healthanalyzer"
|
|
contained_sprite = TRUE
|
|
w_class = WEIGHT_CLASS_SMALL
|
|
obj_flags = OBJ_FLAG_CONDUCTABLE
|
|
slot_flags = SLOT_BELT
|
|
throwforce = 5
|
|
throw_speed = 4
|
|
throw_range = 20
|
|
matter = list(MATERIAL_ALUMINIUM = 30, MATERIAL_GLASS = 20)
|
|
|
|
origin_tech = list(TECH_MAGNET = 2, TECH_BIO = 2)
|
|
var/details = 0
|
|
var/recent_fail = 0
|
|
|
|
/obj/item/reagent_scanner/afterattack(obj/O, mob/user, proximity)
|
|
if(!proximity)
|
|
return
|
|
if(use_check_and_message(user))
|
|
return
|
|
if(!istype(O))
|
|
return
|
|
if(isemptylist(O.reagents?.reagent_volumes))
|
|
to_chat(user, SPAN_WARNING("No active chemical agents found in [O]."))
|
|
return
|
|
|
|
var/dat = ""
|
|
var/one_percent = O.reagents.total_volume / 100
|
|
for (var/_R in O.reagents.reagent_volumes)
|
|
var/singleton/reagent/R = GET_SINGLETON(_R)
|
|
dat += "\n \t [R][details ? ": [O.reagents.reagent_volumes[_R] / one_percent]%" : ""]"
|
|
to_chat(user, SPAN_NOTICE("Chemicals found: [dat]"))
|
|
|
|
/obj/item/reagent_scanner/adv
|
|
name = "advanced reagent scanner"
|
|
icon_state = "reagent_scanner_adv"
|
|
// Reuses the advanced health analyzer inhands.
|
|
item_state = "healthanalyzer_adv"
|
|
details = 1
|
|
origin_tech = list(TECH_MAGNET = 4, TECH_BIO = 2)
|
|
|
|
/obj/item/slime_scanner
|
|
name = "slime scanner"
|
|
icon = 'icons/obj/item/scanner.dmi'
|
|
icon_state = "slime_scanner"
|
|
item_state = "slime_scanner"
|
|
contained_sprite = TRUE
|
|
origin_tech = list(TECH_BIO = 1)
|
|
w_class = WEIGHT_CLASS_SMALL
|
|
obj_flags = OBJ_FLAG_CONDUCTABLE
|
|
throwforce = 0
|
|
throw_speed = 3
|
|
throw_range = 7
|
|
matter = list(MATERIAL_ALUMINIUM = 30, MATERIAL_GLASS = 20)
|
|
|
|
/obj/item/slime_scanner/attack(mob/living/target_mob, mob/living/user, target_zone)
|
|
if(!isslime(target_mob))
|
|
to_chat(user, SPAN_WARNING("This device can only scan slimes!"))
|
|
return
|
|
|
|
var/mob/living/carbon/slime/T = target_mob
|
|
to_chat(user, SPAN_NOTICE("**************************"))
|
|
to_chat(user, SPAN_NOTICE("Slime scan results:"))
|
|
to_chat(user, SPAN_NOTICE(capitalize_first_letters("[T.colour] [T.is_adult ? "adult" : "baby"] slime")))
|
|
to_chat(user, SPAN_NOTICE("Health: [T.health]"))
|
|
to_chat(user, SPAN_NOTICE("Nutrition: [T.nutrition]/[T.get_max_nutrition()]"))
|
|
if(T.nutrition < T.get_starve_nutrition())
|
|
to_chat(user, SPAN_ALERT("Warning: slime is starving!"))
|
|
else if (T.nutrition < T.get_hunger_nutrition())
|
|
to_chat(user, SPAN_WARNING("Warning: slime is hungry!"))
|
|
to_chat(user, SPAN_NOTICE("Electric charge strength: [T.powerlevel]"))
|
|
to_chat(user, SPAN_NOTICE("Growth progress: [T.amount_grown]/10"))
|
|
if(T.cores > 1)
|
|
to_chat(user, SPAN_WARNING("Anomalous number of slime cores detected."))
|
|
else if(!T.cores)
|
|
to_chat(user, SPAN_WARNING("No slime cores detected."))
|
|
to_chat(user, SPAN_NOTICE("Genetic Information:"))
|
|
if(T.slime_mutation[4] == T.colour)
|
|
to_chat(user, SPAN_WARNING("This slime cannot evolve any further."))
|
|
else
|
|
var/list/poss_mutations = uniquelist(T.slime_mutation)
|
|
var/mutation_message = capitalize(english_list(poss_mutations))
|
|
to_chat(user, SPAN_NOTICE(mutation_message))
|
|
var/mut_chance = T.mutation_chance / (poss_mutations.len > 2 ? 1 : 2)
|
|
to_chat(user, SPAN_NOTICE("Instability: [mut_chance]% chance of mutation upon reproduction."))
|
|
to_chat(user, SPAN_NOTICE("**************************"))
|
|
|
|
/obj/item/price_scanner
|
|
name = "price scanner"
|
|
desc = "Using an up-to-date database of various costs and prices, this device estimates the market price of an item up to 0.001% accuracy."
|
|
icon = 'icons/obj/item/scanner.dmi'
|
|
icon_state = "price_scanner"
|
|
item_state = "price_scanner"
|
|
contained_sprite = TRUE
|
|
item_flags = ITEM_FLAG_NO_BLUDGEON
|
|
slot_flags = SLOT_BELT
|
|
w_class = WEIGHT_CLASS_SMALL
|
|
throwforce = 0
|
|
throw_speed = 3
|
|
throw_range = 3
|
|
matter = list(DEFAULT_WALL_MATERIAL = 25, MATERIAL_GLASS = 25)
|
|
|
|
/obj/item/price_scanner/afterattack(atom/movable/target, mob/user as mob, proximity)
|
|
if(!proximity)
|
|
return
|
|
|
|
var/value = get_value(target)
|
|
user.visible_message(SPAN_NOTICE("\The [user] scans \the [target] with \the [src]."))
|
|
to_chat(user, SPAN_NOTICE("\The [src] estimates the price of \the [target] at <b>[value ? value : "N/A"]电</b>."))
|
|
|
|
/obj/item/breath_analyzer
|
|
name = "breath analyzer"
|
|
desc = "A hand-held breath analyzer that provides a robust amount of information about the subject's respiratory system."
|
|
icon = 'icons/obj/item/scanner.dmi'
|
|
icon_state = "breath_analyzer"
|
|
// Reuses the basic health analyzer inhands.
|
|
item_state = "healthanalyzer"
|
|
contained_sprite = TRUE
|
|
w_class = WEIGHT_CLASS_SMALL
|
|
obj_flags = OBJ_FLAG_CONDUCTABLE
|
|
slot_flags = SLOT_BELT
|
|
throwforce = 0
|
|
throw_speed = 3
|
|
throw_range = 3
|
|
matter = list(MATERIAL_ALUMINIUM = 30, MATERIAL_GLASS = 20)
|
|
origin_tech = list(TECH_MAGNET = 2, TECH_BIO = 2)
|
|
|
|
/obj/item/breath_analyzer/attack(mob/living/target_mob, mob/living/user, target_zone)
|
|
|
|
var/mob/living/carbon/human/H = target_mob
|
|
|
|
if (!istype(H))
|
|
to_chat(user,SPAN_WARNING("You can't find a way to use \the [src] on [H]!"))
|
|
return
|
|
|
|
if ( ((user.is_clumsy()) || (user.mutations & DUMB)) && prob(20))
|
|
to_chat(user,SPAN_DANGER("Your hand slips from clumsiness!"))
|
|
if(!H.eyes_protected(src, FALSE))
|
|
eyestab(H,user)
|
|
to_chat(user,SPAN_DANGER("Alert: No breathing detected."))
|
|
return
|
|
|
|
if (!user.IsAdvancedToolUser())
|
|
to_chat(user,SPAN_WARNING("You don't have the dexterity to do this!"))
|
|
return
|
|
|
|
if(user == H && !H.can_eat(src))
|
|
return
|
|
else if(!H.can_force_feed(user, src))
|
|
return
|
|
|
|
user.setClickCooldown(DEFAULT_QUICK_COOLDOWN)
|
|
|
|
user.visible_message(SPAN_NOTICE("[user] is trying to take a breath sample from [H]."),
|
|
SPAN_NOTICE("You gently insert \the [src] into [H]'s mouth."))
|
|
|
|
if (!LAZYLEN(src.other_DNA))
|
|
LAZYADD(src.other_DNA, H.dna.unique_enzymes)
|
|
src.other_DNA_type = "saliva"
|
|
|
|
if (!do_after(user, 2 SECONDS, H, DO_UNIQUE & ~DO_BOTH_CAN_TURN))
|
|
return
|
|
|
|
user.visible_message(SPAN_NOTICE("[user] takes a breath sample from [H]."),
|
|
SPAN_NOTICE("\The [src] clicks as it finishes reading [H]'s breath sample."))
|
|
|
|
to_chat(user,"<b>Breath Sample Results:</b>")
|
|
|
|
if(H.stat == DEAD || H.losebreath || !H.breathing)
|
|
to_chat(user,SPAN_DANGER("Alert: No breathing detected."))
|
|
playsound(user.loc, 'sound/items/healthscanner/healthscanner_dead.ogg', 25, extrarange = SILENCED_SOUND_EXTRARANGE)
|
|
return
|
|
|
|
switch(H.getOxyLoss())
|
|
if(0 to 25)
|
|
to_chat(user,"Subject oxygen levels nominal.")
|
|
playsound(user.loc, 'sound/items/healthscanner/healthscanner_stable.ogg', 25, extrarange = SILENCED_SOUND_EXTRARANGE)
|
|
if(25 to 50)
|
|
to_chat(user,SPAN_NOTICE("Subject oxygen levels abnormal."))
|
|
playsound(user.loc, 'sound/items/healthscanner/healthscanner_danger.ogg', 25, extrarange = SHORT_RANGE_SOUND_EXTRARANGE)
|
|
if(50 to INFINITY)
|
|
to_chat(user,SPAN_NOTICE("<b>Severe oxygen deprivation detected.</b>"))
|
|
playsound(user.loc, 'sound/items/healthscanner/healthscanner_critical.ogg', 25, extrarange = SHORT_RANGE_SOUND_EXTRARANGE)
|
|
|
|
var/obj/item/organ/internal/L = H.internal_organs_by_name[BP_LUNGS]
|
|
if(istype(L))
|
|
if(L.is_bruised())
|
|
to_chat(user,SPAN_WARNING("<b>Ruptured lung detected.</b>"))
|
|
else if(L.is_damaged())
|
|
to_chat(user,"<b>Damaged lung detected.</b>")
|
|
else
|
|
to_chat(user,"Subject lung health nominal.")
|
|
else
|
|
to_chat(user,SPAN_WARNING("Subject lung health unknown."))
|
|
|
|
var/additional_string = "<font color='green'>\[NORMAL\]</font>"
|
|
var/bac = H.get_blood_alcohol()
|
|
switch(bac)
|
|
if(INTOX_JUDGEIMP to INTOX_MUSCLEIMP)
|
|
additional_string = "\[LIGHTLY INTOXICATED\]"
|
|
if(INTOX_MUSCLEIMP to INTOX_BALANCE)
|
|
additional_string = "\[MODERATELY INTOXICATED\]"
|
|
if(INTOX_BALANCE to INTOX_BLACKOUT)
|
|
additional_string = "<span class='warning'>\[HEAVILY INTOXICATED\]</span>"
|
|
if(INTOX_BLACKOUT to INTOX_DEATH)
|
|
additional_string = "<span class='warning'>\[ALCOHOL POISONING LIKELY\]</span>"
|
|
if(INTOX_DEATH to INFINITY)
|
|
additional_string = SPAN_WARNING("\[DEATH IMMINENT\]")
|
|
to_chat(user,"<span class='normal'>Blood Alcohol Content: [round(bac,0.01)] <b>[additional_string]</b></span>")
|
|
|
|
if(H.breathing && H.breathing.total_volume)
|
|
var/unknown = 0
|
|
for(var/_R in H.breathing.reagent_volumes)
|
|
var/singleton/reagent/R = GET_SINGLETON(_R)
|
|
if(R.scannable)
|
|
to_chat(user,SPAN_NOTICE("[R.name] found in subject's respiratory system."))
|
|
else
|
|
++unknown
|
|
if(unknown)
|
|
to_chat(user,SPAN_WARNING("Non-medical reagent[(unknown > 1)?"s":""] found in subject's respiratory system."))
|
|
|
|
|
|
/obj/item/advanced_healthanalyzer
|
|
name = "advanced health analyzer"
|
|
desc = "An expensive and varied-use health analyzer that prints full-body scans after a short scanning delay."
|
|
icon = 'icons/obj/item/scanner.dmi'
|
|
icon_state = "healthanalyzer_adv"
|
|
item_state = "healthanalyzer_adv"
|
|
contained_sprite = TRUE
|
|
slot_flags = SLOT_BELT
|
|
w_class = WEIGHT_CLASS_NORMAL
|
|
origin_tech = list(TECH_MAGNET = 2, TECH_BIO = 3)
|
|
var/obj/machinery/body_scanconsole/connected = null //this is used to print the date and to deal with extra
|
|
|
|
/obj/item/advanced_healthanalyzer/Initialize()
|
|
. = ..()
|
|
if(!connected)
|
|
var/obj/machinery/body_scanconsole/S = new (src)
|
|
S.forceMove(src)
|
|
S.update_use_power(POWER_USE_OFF)
|
|
connected = S
|
|
|
|
/obj/item/advanced_healthanalyzer/Destroy()
|
|
if(connected)
|
|
QDEL_NULL(connected)
|
|
return ..()
|
|
|
|
/obj/item/advanced_healthanalyzer/attack(mob/living/target_mob, mob/living/user, target_zone)
|
|
if(!connected)
|
|
return
|
|
user.setClickCooldown(DEFAULT_QUICK_COOLDOWN)
|
|
user.visible_message("<b>[user]</b> starts scanning \the [target_mob] with \the [src].", SPAN_NOTICE("You start scanning \the [target_mob] with \the [src]."))
|
|
flick("[icon_state]-scan", src)
|
|
if(do_after(user, 7 SECONDS, target_mob, DO_UNIQUE))
|
|
print_scan(target_mob, user)
|
|
add_fingerprint(user)
|
|
|
|
/obj/item/advanced_healthanalyzer/proc/print_scan(var/mob/M, var/mob/living/user)
|
|
var/obj/item/paper/medscan/R = new /obj/item/paper/medscan(src, connected.format_occupant_data(get_occupant_data(M)), "Scan ([M.name]) ([worldtime2text()])", M)
|
|
connected.print(R, message = "\The [src] beeps, printing \the [R] after a moment.", user = user)
|
|
|
|
/// Variant of print_scan(), main difference is different method to print the paper
|
|
/obj/item/advanced_healthanalyzer/cyborg/print_scan(var/mob/M, var/mob/living/user)
|
|
var/obj/item/paper/medscan/R = new /obj/item/paper/medscan(src, connected.format_occupant_data(get_occupant_data(M)), "Scan ([M.name]) ([worldtime2text()])", M)
|
|
user.visible_message(SPAN_NOTICE("\The [src] beeps, printing \the [R] after a moment."))
|
|
playsound(user.loc, SFX_PRINT, 50, 1)
|
|
R.forceMove(user.loc)
|
|
|
|
/obj/item/advanced_healthanalyzer/proc/get_occupant_data(var/mob/living/carbon/human/H)
|
|
if (!ishuman(H))
|
|
return
|
|
|
|
var/displayed_stat = H.stat
|
|
var/blood_oxygenation = H.get_blood_oxygenation()
|
|
if(H.status_flags & FAKEDEATH)
|
|
displayed_stat = DEAD
|
|
blood_oxygenation = min(blood_oxygenation, BLOOD_VOLUME_SURVIVE)
|
|
switch(displayed_stat)
|
|
if(CONSCIOUS)
|
|
displayed_stat = "Conscious"
|
|
if(UNCONSCIOUS)
|
|
displayed_stat = "Unconscious"
|
|
if(DEAD)
|
|
displayed_stat = "DEAD"
|
|
|
|
var/pulse_result
|
|
if(H.should_have_organ(BP_HEART))
|
|
var/obj/item/organ/internal/heart/heart = H.internal_organs_by_name[BP_HEART]
|
|
if(!heart)
|
|
pulse_result = 0
|
|
else if(BP_IS_ROBOTIC(heart))
|
|
pulse_result = -2
|
|
else if(H.status_flags & FAKEDEATH)
|
|
pulse_result = 0
|
|
else
|
|
pulse_result = H.get_pulse(GETPULSE_TOOL)
|
|
else
|
|
pulse_result = -1
|
|
|
|
if(pulse_result == ">250")
|
|
pulse_result = -3
|
|
|
|
var/datum/reagents/R = H.bloodstr
|
|
|
|
connected.has_internal_injuries = FALSE
|
|
connected.has_external_injuries = FALSE
|
|
var/list/bodyparts = connected.get_external_wound_data(H)
|
|
var/list/organs = connected.get_internal_wound_data(H)
|
|
|
|
var/list/occupant_data = list(
|
|
"stationtime" = worldtime2text(),
|
|
"stat" = displayed_stat,
|
|
"name" = H.name,
|
|
"species" = H.get_species(),
|
|
|
|
"brain_activity" = H.get_brain_status(),
|
|
"pulse" = text2num(pulse_result),
|
|
"blood_volume" = H.get_blood_volume(),
|
|
"blood_oxygenation" = H.get_blood_oxygenation(),
|
|
"blood_pressure" = H.get_blood_pressure(),
|
|
"blood_type" = H.dna.b_type,
|
|
|
|
"bruteloss" = get_severity(H.getBruteLoss(), TRUE),
|
|
"fireloss" = get_severity(H.getFireLoss(), TRUE),
|
|
"oxyloss" = get_severity(H.getOxyLoss(), TRUE),
|
|
"toxloss" = get_severity(H.getToxLoss(), TRUE),
|
|
"cloneloss" = get_severity(H.getCloneLoss(), TRUE),
|
|
|
|
"rads" = H.total_radiation,
|
|
"paralysis" = H.paralysis,
|
|
"bodytemp" = H.bodytemperature,
|
|
"borer_present" = H.has_brain_worms(),
|
|
"inaprovaline_amount" = REAGENT_VOLUME(R, /singleton/reagent/inaprovaline),
|
|
"dexalin_amount" = REAGENT_VOLUME(R, /singleton/reagent/dexalin),
|
|
"soporific_amount" = REAGENT_VOLUME(R, /singleton/reagent/soporific),
|
|
"bicaridine_amount" = REAGENT_VOLUME(R, /singleton/reagent/bicaridine),
|
|
"dermaline_amount" = REAGENT_VOLUME(R, /singleton/reagent/dermaline),
|
|
"thetamycin_amount" = REAGENT_VOLUME(R, /singleton/reagent/thetamycin),
|
|
"other_amount" = R.total_volume - (REAGENT_VOLUME(R, /singleton/reagent/inaprovaline) + REAGENT_VOLUME(R, /singleton/reagent/soporific) + REAGENT_VOLUME(R, /singleton/reagent/bicaridine) + REAGENT_VOLUME(R, /singleton/reagent/dexalin) + REAGENT_VOLUME(R, /singleton/reagent/dermaline) + REAGENT_VOLUME(R, /singleton/reagent/thetamycin)),
|
|
"bodyparts" = bodyparts,
|
|
"organs" = organs,
|
|
"has_internal_injuries" = connected.has_internal_injuries,
|
|
"has_external_injuries" = connected.has_external_injuries,
|
|
"missing_limbs" = connected.get_missing_limbs(H),
|
|
"missing_organs" = connected.get_missing_organs(H)
|
|
)
|
|
return occupant_data
|