Revives Organ Datumization (#23397)

* first runthrough lol

* okay make this shit actually work

* nerfs slime people

* fixes

* more changes

* fix

* bim bim bam bam

* ok back in the head becuz i dont give a shit

* Sirryan review

* Sirryan review

* Fixes CI

* Stealthy CI fail

---------

Co-authored-by: Contrabang <91113370+Contrabang@users.noreply.github.com>
This commit is contained in:
DGamerL
2024-01-21 23:23:51 +01:00
committed by GitHub
parent 0b8c196f67
commit 79a7558df7
32 changed files with 653 additions and 521 deletions
+2 -1
View File
@@ -187,13 +187,14 @@
/obj/item/organ/internal/brain/prepare_eat()
return // Too important to eat.
// Hello I am from the ministry of rubber forehead aliens how are you
/obj/item/organ/internal/brain/slime
name = "slime core"
desc = "A complex, organic knot of jelly and crystalline particles."
icon = 'icons/mob/slimes.dmi'
icon_state = "green slime extract"
mmi_icon_state = "slime_mmi"
// parent_organ = "chest" Hello I am from the ministry of rubber forehead aliens how are you
organ_datums = list(/datum/organ/heart, /datum/organ/lungs)
/obj/item/organ/internal/brain/golem
name = "Runic mind"
+41 -72
View File
@@ -5,74 +5,35 @@
parent_organ = "chest"
slot = "heart"
origin_tech = "biotech=5"
var/beating = TRUE
dead_icon = "heart-off"
var/icon_base = "heart"
base_icon_state = "heart"
organ_datums = list(/datum/organ/heart)
/obj/item/organ/internal/heart/update_icon_state()
if(beating)
icon_state = "[icon_base]-on"
var/datum/organ/heart/heart = organ_datums["heart"]
if(heart.beating)
icon_state = "[base_icon_state]-on"
else
icon_state = "[icon_base]-off"
/obj/item/organ/internal/heart/remove(mob/living/carbon/M, special = 0)
. = ..()
if(ishuman(M))
var/mob/living/carbon/human/H = M
if(H.stat == DEAD)
Stop()
return
if(!special)
addtimer(CALLBACK(src, PROC_REF(stop_if_unowned)), 120)
/obj/item/organ/internal/heart/emp_act(intensity)
if(!is_robotic() || emp_proof)
return
Stop()
/obj/item/organ/internal/heart/necrotize()
..()
Stop()
icon_state = "[base_icon_state]-off"
/obj/item/organ/internal/heart/attack_self(mob/user)
..()
if(status & ORGAN_DEAD)
to_chat(user, "<span class='warning'>You can't restart a dead heart.</span>")
return
if(!beating)
Restart()
addtimer(CALLBACK(src, PROC_REF(stop_if_unowned)), 80)
var/datum/organ/heart/heart = organ_datums["heart"]
heart.try_restart(8 SECONDS)
/obj/item/organ/internal/heart/safe_replace(mob/living/carbon/human/target)
Restart()
var/datum/organ/heart/heart = organ_datums["heart"]
heart.change_beating(TRUE)
..()
/obj/item/organ/internal/heart/proc/stop_if_unowned()
if(!owner)
Stop()
/obj/item/organ/internal/heart/proc/Stop()
beating = FALSE
update_icon(UPDATE_ICON_STATE)
return TRUE
/obj/item/organ/internal/heart/proc/Restart()
beating = TRUE
update_icon(UPDATE_ICON_STATE)
return TRUE
/obj/item/organ/internal/heart/prepare_eat()
var/obj/S = ..()
if(S)
S.icon_state = dead_icon
return S
/obj/item/organ/internal/heart/cursed
name = "cursed heart"
desc = "it needs to be pumped..."
icon_state = "cursedheart-off"
icon_base = "cursedheart"
base_icon_state = "cursedheart"
origin_tech = "biotech=6"
actions_types = list(/datum/action/item_action/organ_action/cursed_heart)
var/last_pump = 0
@@ -215,7 +176,7 @@
name = "cybernetic heart"
desc = "An electronic device designed to mimic the functions of an organic human heart. Offers no benefit over an organic heart other than being easy to make."
icon_state = "heart-c-on"
icon_base = "heart-c"
base_icon_state = "heart-c"
dead_icon = "heart-c-off"
status = ORGAN_ROBOT
@@ -223,7 +184,7 @@
name = "upgraded cybernetic heart"
desc = "A more advanced version of a cybernetic heart. Grants the user additional stamina and heart stability, but the electronics are vulnerable to shock."
icon_state = "heart-c-u-on"
icon_base = "heart-c-u"
base_icon_state = "heart-c-u"
dead_icon = "heart-c-u-off"
var/attempted_restart = FALSE
@@ -242,27 +203,42 @@
if(!ishuman(owner))
return
if(!(status & ORGAN_DEAD) && !attempted_restart && !beating)
if(status & ORGAN_DEAD)
return
var/boost = emagged ? 2 : 1
owner.AdjustParalysis(-2 SECONDS * boost)
owner.AdjustStunned(-2 SECONDS * boost)
owner.AdjustWeakened(-2 SECONDS * boost)
owner.AdjustKnockDown(-2 SECONDS * boost)
owner.adjustStaminaLoss(-10 * boost)
if(attempted_restart)
return
var/datum/organ/heart/heart_datum = organ_datums["heart"]
if(!heart_datum.beating)
to_chat(owner, "<span class='warning'>Your [name] detects a cardiac event and attempts to return to its normal rhythm!</span>")
if(prob(20) && emagged)
attempted_restart = TRUE
Restart()
addtimer(CALLBACK(src, PROC_REF(message_to_owner), owner, "<span class='warning'>Your [name] returns to its normal rhythm!</span>"), 30)
addtimer(CALLBACK(src, PROC_REF(recharge)), 200)
heart_datum.change_beating(TRUE) // Mötley Crüe - Kickstart My Heart
addtimer(CALLBACK(src, PROC_REF(message_to_owner), owner, "<span class='warning'>Your [name] returns to its normal rhythm!</span>"), 3 SECONDS)
addtimer(CALLBACK(src, PROC_REF(recharge)), 20 SECONDS)
else if(prob(10))
attempted_restart = TRUE
Restart()
addtimer(CALLBACK(src, PROC_REF(message_to_owner), owner, "<span class='warning'>Your [name] returns to its normal rhythm!</span>"), 30)
addtimer(CALLBACK(src, PROC_REF(recharge)), 300)
heart_datum.change_beating(TRUE)
addtimer(CALLBACK(src, PROC_REF(message_to_owner), owner, "<span class='warning'>Your [name] returns to its normal rhythm!</span>"), 3 SECONDS)
addtimer(CALLBACK(src, PROC_REF(recharge)), 30 SECONDS)
else
attempted_restart = TRUE
if(emagged)
addtimer(CALLBACK(src, PROC_REF(recharge)), 200)
addtimer(CALLBACK(src, PROC_REF(recharge)), 20 SECONDS)
else
addtimer(CALLBACK(src, PROC_REF(recharge)), 300)
addtimer(CALLBACK(src, PROC_REF(message_to_owner), owner, "<span class='warning'>Your [name] fails to return to its normal rhythm!</span>"), 30)
addtimer(CALLBACK(src, PROC_REF(recharge)), 30 SECONDS)
addtimer(CALLBACK(src, PROC_REF(message_to_owner), owner, "<span class='warning'>Your [name] fails to return to its normal rhythm!</span>"), 3 SECONDS)
if(!(status & ORGAN_DEAD) && !attempted_restart && owner.HasDisease(new /datum/disease/critical/heart_failure(0)))
if(owner.HasDisease(new /datum/disease/critical/heart_failure(0)))
to_chat(owner, "<span class='warning'>Your [name] detects a cardiac event and attempts to return to its normal rhythm!</span>")
if(prob(40) && emagged)
attempted_restart = TRUE
@@ -284,14 +260,6 @@
addtimer(CALLBACK(src, PROC_REF(recharge)), 300)
addtimer(CALLBACK(src, PROC_REF(message_to_owner), owner, "<span class='warning'>Your [name] fails to return to its normal rhythm!</span>"), 30)
if(!(status & ORGAN_DEAD))
var/boost = emagged ? 2 : 1
owner.AdjustParalysis(-2 SECONDS * boost)
owner.AdjustStunned(-2 SECONDS * boost)
owner.AdjustWeakened(-2 SECONDS * boost)
owner.AdjustKnockDown(-2 SECONDS * boost)
owner.adjustStaminaLoss(-10 * boost)
/obj/item/organ/internal/heart/cybernetic/upgraded/proc/message_to_owner(mob/M, message)
to_chat(M, message)
@@ -333,7 +301,8 @@
owner.ForceContractDisease(new /datum/disease/critical/heart_failure(0))
if(prob(numMid))
to_chat(owner, "<span class='danger'>Your [name] stops beating!</span>")
Stop()
var/datum/organ/heart/heart_datum = organ_datums["heart"]
heart_datum.change_beating(FALSE) // Rambunctious Crew - Stop My Fucking Heart
if(prob(numLow))
to_chat(owner, "<span class='danger'>Your [name] shuts down!</span>")
necrotize()
+24 -341
View File
@@ -7,306 +7,7 @@
gender = PLURAL
w_class = WEIGHT_CLASS_NORMAL
//Breath damage
var/safe_oxygen_min = 16 // Minimum safe partial pressure of O2, in kPa
var/safe_oxygen_max = 0
var/safe_nitro_min = 0
var/safe_nitro_max = 0
var/safe_co2_min = 0
var/safe_co2_max = 10 // Yes it's an arbitrary value who cares?
var/safe_toxins_min = 0
var/safe_toxins_max = 0.05
var/SA_para_min = 1 //Sleeping agent
var/SA_sleep_min = 5 //Sleeping agent
var/oxy_breath_dam_min = MIN_TOXIC_GAS_DAMAGE
var/oxy_breath_dam_max = MAX_TOXIC_GAS_DAMAGE
var/oxy_damage_type = OXY
var/nitro_breath_dam_min = MIN_TOXIC_GAS_DAMAGE
var/nitro_breath_dam_max = MAX_TOXIC_GAS_DAMAGE
var/nitro_damage_type = OXY
var/co2_breath_dam_min = MIN_TOXIC_GAS_DAMAGE
var/co2_breath_dam_max = MAX_TOXIC_GAS_DAMAGE
var/co2_damage_type = OXY
var/tox_breath_dam_min = MIN_TOXIC_GAS_DAMAGE
var/tox_breath_dam_max = MAX_TOXIC_GAS_DAMAGE
var/tox_damage_type = TOX
var/cold_message = "your face freezing and an icicle forming"
var/cold_level_1_threshold = 260
var/cold_level_2_threshold = 200
var/cold_level_3_threshold = 120
var/cold_level_1_damage = COLD_GAS_DAMAGE_LEVEL_1 //Keep in mind with gas damage levels, you can set these to be negative, if you want someone to heal, instead.
var/cold_level_2_damage = COLD_GAS_DAMAGE_LEVEL_2
var/cold_level_3_damage = COLD_GAS_DAMAGE_LEVEL_3
var/cold_damage_types = list(BURN = 1)
var/hot_message = "your face burning and a searing heat"
var/heat_level_1_threshold = 360
var/heat_level_2_threshold = 400
var/heat_level_3_threshold = 1000
var/heat_level_1_damage = HEAT_GAS_DAMAGE_LEVEL_1
var/heat_level_2_damage = HEAT_GAS_DAMAGE_LEVEL_2
var/heat_level_3_damage = HEAT_GAS_DAMAGE_LEVEL_3
var/heat_damage_types = list(BURN = 1)
/obj/item/organ/internal/lungs/emp_act()
if(!is_robotic() || emp_proof)
return
if(owner)
owner.LoseBreath(40 SECONDS)
/obj/item/organ/internal/lungs/insert(mob/living/carbon/M, special = 0, dont_remove_slot = 0)
..()
for(var/thing in list("oxy", "tox", "co2", "nitro"))
M.clear_alert("not_enough_[thing]")
M.clear_alert("too_much_[thing]")
/obj/item/organ/internal/lungs/remove(mob/living/carbon/M, special = 0)
for(var/thing in list("oxy", "tox", "co2", "nitro"))
M.clear_alert("not_enough_[thing]")
M.clear_alert("too_much_[thing]")
return ..()
/obj/item/organ/internal/lungs/on_life()
if(germ_level > INFECTION_LEVEL_ONE)
if(prob(5))
owner.emote("cough") //respitory tract infection
if(is_bruised())
if(prob(2) && !(NO_BLOOD in owner.dna.species.species_traits))
owner.custom_emote(EMOTE_VISIBLE, "coughs up blood!")
owner.bleed(1)
if(prob(4))
owner.custom_emote(EMOTE_VISIBLE, "gasps for air!")
owner.AdjustLoseBreath(10 SECONDS)
/obj/item/organ/internal/lungs/proc/check_breath(datum/gas_mixture/breath, mob/living/carbon/human/H)
if((H.status_flags & GODMODE))
return
if(HAS_TRAIT(H, TRAIT_NOBREATH))
return
if(!breath || (breath.total_moles() == 0))
if(isspaceturf(H.loc))
H.adjustOxyLoss(10)
else
H.adjustOxyLoss(5)
if(safe_oxygen_min)
H.throw_alert("not_enough_oxy", /obj/screen/alert/not_enough_oxy)
else if(safe_toxins_min)
H.throw_alert("not_enough_tox", /obj/screen/alert/not_enough_tox)
else if(safe_co2_min)
H.throw_alert("not_enough_co2", /obj/screen/alert/not_enough_co2)
else if(safe_nitro_min)
H.throw_alert("not_enough_nitro", /obj/screen/alert/not_enough_nitro)
return FALSE
if(H.health < HEALTH_THRESHOLD_CRIT)
return FALSE
var/gas_breathed = 0
//Partial pressures in our breath
var/O2_pp = breath.get_breath_partial_pressure(breath.oxygen)
var/N2_pp = breath.get_breath_partial_pressure(breath.nitrogen)
var/Toxins_pp = breath.get_breath_partial_pressure(breath.toxins)
var/CO2_pp = breath.get_breath_partial_pressure(breath.carbon_dioxide)
var/SA_pp = breath.get_breath_partial_pressure(breath.sleeping_agent)
//-- OXY --//
//Too much oxygen! //Yes, some species may not like it.
if(safe_oxygen_max)
if(O2_pp > safe_oxygen_max)
var/ratio = (breath.oxygen / safe_oxygen_max / safe_oxygen_max) * 10
H.apply_damage_type(clamp(ratio, oxy_breath_dam_min, oxy_breath_dam_max), oxy_damage_type)
H.throw_alert("too_much_oxy", /obj/screen/alert/too_much_oxy)
else
H.clear_alert("too_much_oxy")
//Too little oxygen!
if(safe_oxygen_min)
if(O2_pp < safe_oxygen_min)
gas_breathed = handle_too_little_breath(H, O2_pp, safe_oxygen_min, breath.oxygen)
H.throw_alert("not_enough_oxy", /obj/screen/alert/not_enough_oxy)
else
H.adjustOxyLoss(-HUMAN_MAX_OXYLOSS)
gas_breathed = breath.oxygen
H.clear_alert("not_enough_oxy")
//Exhale
breath.oxygen -= gas_breathed
breath.carbon_dioxide += gas_breathed
gas_breathed = 0
//-- Nitrogen --//
//Too much nitrogen!
if(safe_nitro_max)
if(N2_pp > safe_nitro_max)
var/ratio = (breath.nitrogen / safe_nitro_max) * 10
H.apply_damage_type(clamp(ratio, nitro_breath_dam_min, nitro_breath_dam_max), nitro_damage_type)
H.throw_alert("too_much_nitro", /obj/screen/alert/too_much_nitro)
else
H.clear_alert("too_much_nitro")
//Too little nitrogen!
if(safe_nitro_min)
if(N2_pp < safe_nitro_min)
gas_breathed = handle_too_little_breath(H, N2_pp, safe_nitro_min, breath.nitrogen)
H.throw_alert("not_enough_nitro", /obj/screen/alert/not_enough_nitro)
else
H.adjustOxyLoss(-HUMAN_MAX_OXYLOSS)
gas_breathed = breath.nitrogen
H.clear_alert("not_enough_nitro")
//Exhale
breath.nitrogen -= gas_breathed
breath.carbon_dioxide += gas_breathed
gas_breathed = 0
//-- CO2 --//
//CO2 does not affect failed_last_breath. So if there was enough oxygen in the air but too much co2, this will hurt you, but only once per 4 ticks, instead of once per tick.
if(safe_co2_max)
if(CO2_pp > safe_co2_max)
if(!H.co2overloadtime) // If it's the first breath with too much CO2 in it, lets start a counter, then have them pass out after 12s or so.
H.co2overloadtime = world.time
else if(world.time - H.co2overloadtime > 120)
H.Paralyse(6 SECONDS)
H.apply_damage_type(HUMAN_MAX_OXYLOSS, co2_damage_type) // Lets hurt em a little, let them know we mean business
if(world.time - H.co2overloadtime > 300) // They've been in here 30s now, lets start to kill them for their own good!
H.apply_damage_type(15, co2_damage_type)
H.throw_alert("too_much_co2", /obj/screen/alert/too_much_co2)
if(prob(20)) // Lets give them some chance to know somethings not right though I guess.
H.emote("cough")
else
H.co2overloadtime = 0
H.clear_alert("too_much_co2")
//Too little CO2!
if(safe_co2_min)
if(CO2_pp < safe_co2_min)
gas_breathed = handle_too_little_breath(H, CO2_pp, safe_co2_min, breath.carbon_dioxide)
H.throw_alert("not_enough_co2", /obj/screen/alert/not_enough_co2)
else
H.adjustOxyLoss(-HUMAN_MAX_OXYLOSS)
gas_breathed = breath.carbon_dioxide
H.clear_alert("not_enough_co2")
//Exhale
breath.carbon_dioxide -= gas_breathed
breath.oxygen += gas_breathed
gas_breathed = 0
//-- TOX --//
//Too much toxins!
if(safe_toxins_max)
if(Toxins_pp > safe_toxins_max)
var/ratio = (breath.toxins / safe_toxins_max) * 10
H.apply_damage_type(clamp(ratio, tox_breath_dam_min, tox_breath_dam_max), tox_damage_type)
H.throw_alert("too_much_tox", /obj/screen/alert/too_much_tox)
else
H.clear_alert("too_much_tox")
//Too little toxins!
if(safe_toxins_min)
if(Toxins_pp < safe_toxins_min)
gas_breathed = handle_too_little_breath(H, Toxins_pp, safe_toxins_min, breath.toxins)
H.throw_alert("not_enough_tox", /obj/screen/alert/not_enough_tox)
else
H.adjustOxyLoss(-HUMAN_MAX_OXYLOSS)
gas_breathed = breath.toxins
H.clear_alert("not_enough_tox")
//Exhale
breath.toxins -= gas_breathed
breath.carbon_dioxide += gas_breathed
gas_breathed = 0
//-- TRACES --//
if(breath.sleeping_agent) // If there's some other shit in the air lets deal with it here.
if(SA_pp > SA_para_min)
H.Paralyse(6 SECONDS) // 6 seconds gives them one second to wake up and run away a bit!
if(SA_pp > SA_sleep_min) // Enough to make us sleep as well
H.AdjustSleeping(16 SECONDS, bound_lower = 0, bound_upper = 20 SECONDS)
else if(SA_pp > 0.01) // There is sleeping gas in their lungs, but only a little, so give them a bit of a warning
if(prob(20))
H.emote(pick("giggle", "laugh"))
handle_breath_temperature(breath, H)
return TRUE
/obj/item/organ/internal/lungs/proc/handle_too_little_breath(mob/living/carbon/human/H = null, breath_pp = 0, safe_breath_min = 0, true_pp = 0)
. = 0
if(!H || !safe_breath_min) //the other args are either: Ok being 0 or Specifically handled.
return FALSE
if(prob(20))
H.emote("gasp")
if(breath_pp > 0)
var/ratio = safe_breath_min/breath_pp
H.adjustOxyLoss(min(5*ratio, HUMAN_MAX_OXYLOSS)) // Don't fuck them up too fast (space only does HUMAN_MAX_OXYLOSS after all!
. = true_pp*ratio/6
else
H.adjustOxyLoss(HUMAN_MAX_OXYLOSS)
/obj/item/organ/internal/lungs/proc/handle_breath_temperature(datum/gas_mixture/breath, mob/living/carbon/human/H) // called by human/life, handles temperatures
var/breath_temperature = breath.temperature
if(!HAS_TRAIT(H, TRAIT_RESISTCOLD)) // COLD DAMAGE
var/CM = abs(H.dna.species.coldmod)
var/TC = 0
if(breath_temperature < cold_level_3_threshold)
TC = cold_level_3_damage
if(breath_temperature > cold_level_3_threshold && breath_temperature < cold_level_2_threshold)
TC = cold_level_2_damage
if(breath_temperature > cold_level_2_threshold && breath_temperature < cold_level_1_threshold)
TC = cold_level_1_damage
if(TC)
for(var/D in cold_damage_types)
H.apply_damage_type(TC * CM * cold_damage_types[D], D)
if(breath_temperature < cold_level_1_threshold)
if(prob(20))
to_chat(H, "<span class='warning'>You feel [cold_message] in your [name]!</span>")
if(!HAS_TRAIT(H, TRAIT_RESISTHEAT)) // HEAT DAMAGE
var/HM = abs(H.dna.species.heatmod)
var/TH = 0
if(breath_temperature > heat_level_1_threshold && breath_temperature < heat_level_2_threshold)
TH = heat_level_1_damage
if(breath_temperature > heat_level_2_threshold && breath_temperature < heat_level_3_threshold)
TH = heat_level_2_damage
if(breath_temperature > heat_level_3_threshold)
TH = heat_level_3_damage
if(TH)
for(var/D in heat_damage_types)
H.apply_damage_type(TH * HM * heat_damage_types[D], D)
if(breath_temperature > heat_level_1_threshold)
if(prob(20))
to_chat(H, "<span class='warning'>You feel [hot_message] in your [name]!</span>")
/obj/item/organ/internal/lungs/prepare_eat()
var/obj/S = ..()
if(S)
S.reagents.add_reagent("salbutamol", 5)
return S
organ_datums = list(/datum/organ/lungs)
/obj/item/organ/internal/lungs/plasmaman
name = "plasma filter"
@@ -314,9 +15,7 @@
icon = 'icons/obj/species_organs/plasmaman.dmi'
icon_state = "lungs"
safe_oxygen_min = 0 //We don't breath this
safe_toxins_min = 16 //We breathe THIS!
safe_toxins_max = 0
organ_datums = list(/datum/organ/lungs/plasmamen)
/obj/item/organ/internal/lungs/vox
name = "vox lungs"
@@ -325,20 +24,12 @@
icon_state = "lungs"
sterile = TRUE
safe_oxygen_min = 0 //We don't breathe this
safe_oxygen_max = 0.05 //This is toxic to us
safe_nitro_min = 16 //We breathe THIS!
oxy_damage_type = TOX //And it poisons us
organ_datums = list(/datum/organ/lungs/vox)
/obj/item/organ/internal/lungs/drask
icon = 'icons/obj/species_organs/drask.dmi'
cold_message = "an invigorating coldness"
cold_level_3_threshold = 60
cold_level_1_damage = -COLD_GAS_DAMAGE_LEVEL_1 //They heal when the air is cold
cold_level_2_damage = -COLD_GAS_DAMAGE_LEVEL_2
cold_level_3_damage = -COLD_GAS_DAMAGE_LEVEL_3
cold_damage_types = list(BRUTE = 0.5, BURN = 0.25)
organ_datums = list(/datum/organ/lungs/drask)
/obj/item/organ/internal/lungs/cybernetic
name = "cybernetic lungs"
@@ -346,7 +37,7 @@
icon_state = "lungs-c"
origin_tech = "biotech=4"
status = ORGAN_ROBOT
var/species_state = "human"
var/species_state = "default"
/obj/item/organ/internal/lungs/cybernetic/examine(mob/user)
. = ..()
@@ -356,28 +47,20 @@
. = TRUE
if(!I.use_tool(src, user, 0, volume = I.tool_volume))
return
switch(species_state)
if("human") // from human to vox
safe_oxygen_min = 0
safe_oxygen_max = safe_toxins_max
safe_nitro_min = 16
oxy_damage_type = TOX
to_chat(user, "<span class='notice'>You configure [src] to replace vox lungs.</span>")
species_state = "vox"
if("vox") // from vox to plasmamen
safe_oxygen_max = initial(safe_oxygen_max)
safe_toxins_min = 16
safe_toxins_max = 0
safe_nitro_min = initial(safe_nitro_min)
oxy_damage_type = OXY
to_chat(user, "<span class='notice'>You configure [src] to replace plasmamen lungs.</span>")
species_state = "plasmamen"
if("plasmamen") // from plasmamen to human
safe_oxygen_min = initial(safe_oxygen_min)
safe_toxins_min = initial(safe_toxins_min)
safe_toxins_max = initial(safe_toxins_max)
to_chat(user, "<span class='notice'>You configure [src] back to default settings.</span>")
species_state = "human"
var/possible = list("default" = /datum/organ/lungs, "vox" = /datum/organ/lungs/vox, "plasmamen" = /datum/organ/lungs/plasmamen)
var/chosen = input(user, "Select lung type", "What kind of lung settings?") as null|anything in possible
if(isnull(chosen) || chosen == species_state || !Adjacent(user) || !I.use_tool(src, user, 0, volume = I.tool_volume))
return
species_state = chosen
to_chat(user, "<span class='notice'>You configure [src] to [chosen] settings.</span>")
var/typepath = possible[chosen]
var/datum/organ/lungs/lungs = new typepath(src)
qdel(organ_datums[lungs.organ_tag])
organ_datums[lungs.organ_tag] = lungs
if(owner) // this should never happen, but in case it somehow does...
owner.internal_organ_datums[lungs.organ_tag] = lungs
/obj/item/organ/internal/lungs/cybernetic/upgraded
name = "upgraded cybernetic lungs"
@@ -385,9 +68,9 @@
icon_state = "lungs-c-u"
origin_tech = "biotech=5"
safe_toxins_max = 20
safe_co2_max = 20
organ_datums = list(/datum/organ/lungs/advanced_cyber)
cold_level_1_threshold = 200
cold_level_2_threshold = 140
cold_level_3_threshold = 100
/obj/item/organ/internal/lungs/cybernetic/upgraded/multitool_act(mob/user, obj/item/I)
. = ..()
var/datum/organ/lungs/lungs = organ_datums["lungs"]
lungs.make_advanced()
@@ -0,0 +1,47 @@
/datum/organ/heart
organ_tag = ORGAN_DATUM_HEART
var/beating = TRUE
var/always_beating = FALSE
/datum/organ/heart/on_remove(mob/living/carbon/removed_from, special = FALSE)
. = ..()
if(ishuman(removed_from))
var/mob/living/carbon/human/H = removed_from
if(H.stat == DEAD)
change_beating(FALSE)
return
if(!special)
addtimer(CALLBACK(src, PROC_REF(stop_if_unowned)), 12 SECONDS)
/datum/organ/heart/proc/change_beating(change_to = FALSE)
if(always_beating)
beating = TRUE
return
beating = change_to
linked_organ.update_icon()
/datum/organ/heart/on_successful_emp()
change_beating(FALSE)
return TRUE
/datum/organ/heart/on_necrotize()
change_beating(FALSE)
return TRUE
/datum/organ/heart/proc/try_restart(duration = 8 SECONDS)
if(beating)
return
change_beating(TRUE)
addtimer(CALLBACK(src, PROC_REF(stop_if_unowned)), duration)
/datum/organ/heart/proc/stop_if_unowned()
if(!linked_organ.owner)
change_beating(FALSE)
/datum/organ/heart/on_prepare_eat(obj/item/food/snacks/organ/snorgan)
snorgan.icon_state = linked_organ.dead_icon
/// A subtype that is always beating. Abductor glands and demon hearts use this.
/datum/organ/heart/always_beating
always_beating = TRUE
@@ -0,0 +1,345 @@
/datum/organ/lungs
organ_tag = ORGAN_DATUM_LUNGS
//Breath damage
var/safe_oxygen_min = 16 // Minimum safe partial pressure of O2, in kPa
var/safe_oxygen_max = 0
var/safe_nitro_min = 0
var/safe_nitro_max = 0
var/safe_co2_min = 0
var/safe_co2_max = 10 // Yes it's an arbitrary value who cares?
var/safe_toxins_min = 0
var/safe_toxins_max = 0.05
var/SA_para_min = 1 //Sleeping agent
var/SA_sleep_min = 5 //Sleeping agent
var/oxy_breath_dam_min = MIN_TOXIC_GAS_DAMAGE
var/oxy_breath_dam_max = MAX_TOXIC_GAS_DAMAGE
var/oxy_damage_type = OXY
var/nitro_breath_dam_min = MIN_TOXIC_GAS_DAMAGE
var/nitro_breath_dam_max = MAX_TOXIC_GAS_DAMAGE
var/nitro_damage_type = OXY
var/co2_breath_dam_min = MIN_TOXIC_GAS_DAMAGE
var/co2_breath_dam_max = MAX_TOXIC_GAS_DAMAGE
var/co2_damage_type = OXY
var/tox_breath_dam_min = MIN_TOXIC_GAS_DAMAGE
var/tox_breath_dam_max = MAX_TOXIC_GAS_DAMAGE
var/tox_damage_type = TOX
var/cold_message = "your face freezing and an icicle forming"
var/cold_level_1_threshold = 260
var/cold_level_2_threshold = 200
var/cold_level_3_threshold = 120
var/cold_level_1_damage = COLD_GAS_DAMAGE_LEVEL_1 //Keep in mind with gas damage levels, you can set these to be negative, if you want someone to heal, instead.
var/cold_level_2_damage = COLD_GAS_DAMAGE_LEVEL_2
var/cold_level_3_damage = COLD_GAS_DAMAGE_LEVEL_3
var/cold_damage_types = list(BURN = 1)
var/hot_message = "your face burning and a searing heat"
var/heat_level_1_threshold = 360
var/heat_level_2_threshold = 400
var/heat_level_3_threshold = 1000
var/heat_level_1_damage = HEAT_GAS_DAMAGE_LEVEL_1
var/heat_level_2_damage = HEAT_GAS_DAMAGE_LEVEL_2
var/heat_level_3_damage = HEAT_GAS_DAMAGE_LEVEL_3
var/heat_damage_types = list(BURN = 1)
/**
* LUNG ATMOS CODE, VENTURE FURTHER IF YOU DARE!!!
*/
/datum/organ/lungs/proc/check_breath(datum/gas_mixture/breath, mob/living/carbon/human/H)
if((H.status_flags & GODMODE))
return
if(HAS_TRAIT(H, TRAIT_NOBREATH))
return
if(!breath || (breath.total_moles() == 0))
if(isspaceturf(H.loc))
H.adjustOxyLoss(10)
else
H.adjustOxyLoss(5)
if(safe_oxygen_min)
H.throw_alert("not_enough_oxy", /obj/screen/alert/not_enough_oxy)
else if(safe_toxins_min)
H.throw_alert("not_enough_tox", /obj/screen/alert/not_enough_tox)
else if(safe_co2_min)
H.throw_alert("not_enough_co2", /obj/screen/alert/not_enough_co2)
else if(safe_nitro_min)
H.throw_alert("not_enough_nitro", /obj/screen/alert/not_enough_nitro)
return FALSE
if(H.health < HEALTH_THRESHOLD_CRIT)
return FALSE
var/gas_breathed = 0
//Partial pressures in our breath
var/O2_pp = breath.get_breath_partial_pressure(breath.oxygen)
var/N2_pp = breath.get_breath_partial_pressure(breath.nitrogen)
var/Toxins_pp = breath.get_breath_partial_pressure(breath.toxins)
var/CO2_pp = breath.get_breath_partial_pressure(breath.carbon_dioxide)
var/SA_pp = breath.get_breath_partial_pressure(breath.sleeping_agent)
//-- OXY --//
//Too much oxygen! //Yes, some species may not like it.
if(safe_oxygen_max)
if(O2_pp > safe_oxygen_max)
var/ratio = (breath.oxygen / safe_oxygen_max / safe_oxygen_max) * 10
H.apply_damage_type(clamp(ratio, oxy_breath_dam_min, oxy_breath_dam_max), oxy_damage_type)
H.throw_alert("too_much_oxy", /obj/screen/alert/too_much_oxy)
else
H.clear_alert("too_much_oxy")
//Too little oxygen!
if(safe_oxygen_min)
if(O2_pp < safe_oxygen_min)
gas_breathed = handle_too_little_breath(H, O2_pp, safe_oxygen_min, breath.oxygen)
H.throw_alert("not_enough_oxy", /obj/screen/alert/not_enough_oxy)
else
H.adjustOxyLoss(-HUMAN_MAX_OXYLOSS)
gas_breathed = breath.oxygen
H.clear_alert("not_enough_oxy")
//Exhale
breath.oxygen -= gas_breathed
breath.carbon_dioxide += gas_breathed
gas_breathed = 0
//-- Nitrogen --//
//Too much nitrogen!
if(safe_nitro_max)
if(N2_pp > safe_nitro_max)
var/ratio = (breath.nitrogen / safe_nitro_max) * 10
H.apply_damage_type(clamp(ratio, nitro_breath_dam_min, nitro_breath_dam_max), nitro_damage_type)
H.throw_alert("too_much_nitro", /obj/screen/alert/too_much_nitro)
else
H.clear_alert("too_much_nitro")
//Too little nitrogen!
if(safe_nitro_min)
if(N2_pp < safe_nitro_min)
gas_breathed = handle_too_little_breath(H, N2_pp, safe_nitro_min, breath.nitrogen)
H.throw_alert("not_enough_nitro", /obj/screen/alert/not_enough_nitro)
else
H.adjustOxyLoss(-HUMAN_MAX_OXYLOSS)
gas_breathed = breath.nitrogen
H.clear_alert("not_enough_nitro")
//Exhale
breath.nitrogen -= gas_breathed
breath.carbon_dioxide += gas_breathed
gas_breathed = 0
//-- CO2 --//
//CO2 does not affect failed_last_breath. So if there was enough oxygen in the air but too much co2, this will hurt you, but only once per 4 ticks, instead of once per tick.
if(safe_co2_max)
if(CO2_pp > safe_co2_max)
if(!H.co2overloadtime) // If it's the first breath with too much CO2 in it, lets start a counter, then have them pass out after 12s or so.
H.co2overloadtime = world.time
else if(world.time - H.co2overloadtime > 120)
H.Paralyse(6 SECONDS)
H.apply_damage_type(HUMAN_MAX_OXYLOSS, co2_damage_type) // Lets hurt em a little, let them know we mean business
if(world.time - H.co2overloadtime > 300) // They've been in here 30s now, lets start to kill them for their own good!
H.apply_damage_type(15, co2_damage_type)
H.throw_alert("too_much_co2", /obj/screen/alert/too_much_co2)
if(prob(20)) // Lets give them some chance to know somethings not right though I guess.
H.emote("cough")
else
H.co2overloadtime = 0
H.clear_alert("too_much_co2")
//Too little CO2!
if(safe_co2_min)
if(CO2_pp < safe_co2_min)
gas_breathed = handle_too_little_breath(H, CO2_pp, safe_co2_min, breath.carbon_dioxide)
H.throw_alert("not_enough_co2", /obj/screen/alert/not_enough_co2)
else
H.adjustOxyLoss(-HUMAN_MAX_OXYLOSS)
gas_breathed = breath.carbon_dioxide
H.clear_alert("not_enough_co2")
//Exhale
breath.carbon_dioxide -= gas_breathed
breath.oxygen += gas_breathed
gas_breathed = 0
//-- TOX --//
//Too much toxins!
if(safe_toxins_max)
if(Toxins_pp > safe_toxins_max)
var/ratio = (breath.toxins / safe_toxins_max) * 10
H.apply_damage_type(clamp(ratio, tox_breath_dam_min, tox_breath_dam_max), tox_damage_type)
H.throw_alert("too_much_tox", /obj/screen/alert/too_much_tox)
else
H.clear_alert("too_much_tox")
//Too little toxins!
if(safe_toxins_min)
if(Toxins_pp < safe_toxins_min)
gas_breathed = handle_too_little_breath(H, Toxins_pp, safe_toxins_min, breath.toxins)
H.throw_alert("not_enough_tox", /obj/screen/alert/not_enough_tox)
else
H.adjustOxyLoss(-HUMAN_MAX_OXYLOSS)
gas_breathed = breath.toxins
H.clear_alert("not_enough_tox")
//Exhale
breath.toxins -= gas_breathed
breath.carbon_dioxide += gas_breathed
gas_breathed = 0
//-- TRACES --//
if(breath.sleeping_agent) // If there's some other shit in the air lets deal with it here.
if(SA_pp > SA_para_min)
H.Paralyse(6 SECONDS) // 6 seconds gives them one second to wake up and run away a bit!
if(SA_pp > SA_sleep_min) // Enough to make us sleep as well
H.AdjustSleeping(16 SECONDS, bound_lower = 0, bound_upper = 20 SECONDS)
else if(SA_pp > 0.01) // There is sleeping gas in their lungs, but only a little, so give them a bit of a warning
if(prob(20))
H.emote(pick("giggle", "laugh"))
handle_breath_temperature(breath, H)
return TRUE
/datum/organ/lungs/proc/handle_too_little_breath(mob/living/carbon/human/H = null, breath_pp = 0, safe_breath_min = 0, true_pp = 0)
. = 0
if(!H || !safe_breath_min) //the other args are either: Ok being 0 or Specifically handled.
return FALSE
if(prob(20))
H.emote("gasp")
if(breath_pp > 0)
var/ratio = safe_breath_min/breath_pp
H.adjustOxyLoss(min(5*ratio, HUMAN_MAX_OXYLOSS)) // Don't fuck them up too fast (space only does HUMAN_MAX_OXYLOSS after all!
. = true_pp*ratio/6
else
H.adjustOxyLoss(HUMAN_MAX_OXYLOSS)
/datum/organ/lungs/proc/handle_breath_temperature(datum/gas_mixture/breath, mob/living/carbon/human/H) // called by human/life, handles temperatures
var/breath_temperature = breath.temperature
if(!HAS_TRAIT(H, TRAIT_RESISTCOLD)) // COLD DAMAGE
var/CM = abs(H.dna.species.coldmod)
var/TC = 0
if(breath_temperature < cold_level_3_threshold)
TC = cold_level_3_damage
if(breath_temperature > cold_level_3_threshold && breath_temperature < cold_level_2_threshold)
TC = cold_level_2_damage
if(breath_temperature > cold_level_2_threshold && breath_temperature < cold_level_1_threshold)
TC = cold_level_1_damage
if(TC)
for(var/D in cold_damage_types)
H.apply_damage_type(TC * CM * cold_damage_types[D], D)
if(breath_temperature < cold_level_1_threshold)
if(prob(20))
to_chat(H, "<span class='warning'>You feel [cold_message] in your [linked_organ.name]!</span>")
if(!HAS_TRAIT(H, TRAIT_RESISTHEAT)) // HEAT DAMAGE
var/HM = abs(H.dna.species.heatmod)
var/TH = 0
if(breath_temperature > heat_level_1_threshold && breath_temperature < heat_level_2_threshold)
TH = heat_level_1_damage
if(breath_temperature > heat_level_2_threshold && breath_temperature < heat_level_3_threshold)
TH = heat_level_2_damage
if(breath_temperature > heat_level_3_threshold)
TH = heat_level_3_damage
if(TH)
for(var/D in heat_damage_types)
H.apply_damage_type(TH * HM * heat_damage_types[D], D)
if(breath_temperature > heat_level_1_threshold)
if(prob(20))
to_chat(H, "<span class='warning'>You feel [hot_message] in your [linked_organ.name]!</span>")
/**
* Lung atmos code ends here. Thank god...
*/
/datum/organ/lungs/on_successful_emp()
linked_organ.owner?.LoseBreath(40 SECONDS)
/datum/organ/lungs/on_insert(mob/living/carbon/given_to)
clear_alerts(given_to)
/datum/organ/lungs/on_replace(mob/living/carbon/human/organ_owner)
clear_alerts(organ_owner)
/datum/organ/lungs/on_remove(mob/living/carbon/removed_from, special = FALSE)
clear_alerts(removed_from)
/datum/organ/lungs/proc/clear_alerts(mob/living/carbon/alert_owner)
for(var/thing in list("oxy", "tox", "co2", "nitro"))
alert_owner.clear_alert("not_enough_[thing]")
alert_owner.clear_alert("too_much_[thing]")
/datum/organ/lungs/on_life()
if(linked_organ.germ_level > INFECTION_LEVEL_ONE)
if(prob(5))
linked_organ.owner.emote("cough") //respitory tract infection
if(linked_organ.is_bruised())
if(prob(2) && !(NO_BLOOD in linked_organ.owner.dna.species.species_traits))
linked_organ.owner.custom_emote(EMOTE_VISIBLE, "coughs up blood!")
linked_organ.owner.bleed(1)
if(prob(4))
linked_organ.owner.custom_emote(EMOTE_VISIBLE, "gasps for air!")
linked_organ.owner.AdjustLoseBreath(10 SECONDS)
/datum/organ/lungs/on_prepare_eat(obj/item/food/snacks/organ/snorgan)
snorgan.reagents.add_reagent("salbutamol", 5)
/datum/organ/lungs/vox
safe_oxygen_min = 0 //We don't breathe this
safe_oxygen_max = 0.05 //This is toxic to us
safe_nitro_min = 16 //We breathe THIS!
oxy_damage_type = TOX //And it poisons us
/datum/organ/lungs/plasmamen
safe_oxygen_min = 0 //We don't breath this
safe_toxins_min = 16 //We breathe THIS!
safe_toxins_max = 0
/datum/organ/lungs/drask
cold_message = "an invigorating coldness"
cold_level_3_threshold = 60
cold_level_1_damage = -COLD_GAS_DAMAGE_LEVEL_1 //They heal when the air is cold
cold_level_2_damage = -COLD_GAS_DAMAGE_LEVEL_2
cold_level_3_damage = -COLD_GAS_DAMAGE_LEVEL_3
cold_damage_types = list(BRUTE = 0.5, BURN = 0.25)
/datum/organ/lungs/ashwalker
safe_oxygen_min = 4 // 4x as efficient as regular Unathi, can comfortably breathe on lavaland
/datum/organ/lungs/advanced_cyber/New(obj/item/organ/internal/link_em)
. = ..()
make_advanced()
/datum/organ/lungs/proc/make_advanced()
safe_toxins_max = 20
safe_co2_max = 20
cold_level_1_threshold = 200
cold_level_2_threshold = 140
cold_level_3_threshold = 100
@@ -0,0 +1,36 @@
/datum/organ
var/organ_tag // heart, brain, lungs, etc
var/obj/item/organ/internal/linked_organ
/datum/organ/New(obj/item/organ/internal/link_em)
..()
linked_organ = link_em
/datum/organ/Destroy(force, ...)
linked_organ = null
return ..()
/// Called when the linked organ is inserted.
/datum/organ/proc/on_insert(mob/living/carbon/human/given_to)
return
/// Called when another organ is removed, and this organ datum takes its place in the organ_owner.
/datum/organ/proc/on_replace(mob/living/carbon/human/organ_owner)
return
/// Called when the linked organ is removed.
/datum/organ/proc/on_remove(mob/living/carbon/removed_from, special = FALSE)
return
/datum/organ/proc/on_life()
return
/// Only called when the organ is robotic AND is not emp proof. Return true to override default functions
/datum/organ/proc/on_successful_emp()
return FALSE
/datum/organ/proc/on_necrotize()
return FALSE
/datum/organ/proc/on_prepare_eat(obj/item/food/snacks/organ/snorgan)
return FALSE
@@ -16,6 +16,10 @@
/mob/living/carbon/get_int_organ(typepath)
return (locate(typepath) in internal_organs)
/mob/living/carbon/proc/get_int_organ_datum(tag_to_check)
RETURN_TYPE(/datum/organ)
return internal_organ_datums[tag_to_check]
/mob/living/carbon/get_organs_zone(zone, subzones = 0)
var/list/returnorg = list()
+67 -1
View File
@@ -7,12 +7,34 @@
// DO NOT add slots with matching names to different zones - it will break internal_organs_slot list!
var/non_primary = 0
var/unremovable = FALSE //Whether it shows up as an option to remove during surgery.
/// An associated list of organ datums that this organ has.
var/list/datum/organ/organ_datums
/obj/item/organ/internal/New(mob/living/carbon/holder)
..()
if(istype(holder))
insert(holder)
/obj/item/organ/internal/Initialize(mapload)
. = ..()
if(!organ_datums)
return
var/list/temp_list = organ_datums.Copy()
organ_datums = list()
for(var/path in temp_list)
var/datum/organ/organ_datum = new path(src)
if(!organ_datum.organ_tag)
stack_trace("There was an organ datum [organ_datum] ([organ_datum.type]), that had no organ tag.")
continue
organ_datums[organ_datum.organ_tag] = organ_datum
/obj/item/organ/internal/Destroy()
if(owner) // we have to remove BEFORE organ_datums are qdel'd, or we can just live even if our heart organ got deleted
remove(owner, TRUE)
QDEL_LIST_ASSOC_VAL(organ_datums) // The removal from internal_organ_datums should be handled when the organ is removed
. = ..()
/obj/item/organ/internal/proc/insert(mob/living/carbon/M, special = 0, dont_remove_slot = 0)
if(!iscarbon(M) || owner == M)
return
@@ -28,6 +50,12 @@
M.internal_organs |= src
M.internal_organs_slot[slot] = src
for(var/organ_tag in organ_datums)
var/datum/organ/new_organ = organ_datums[organ_tag]
M.internal_organ_datums[new_organ.organ_tag] = new_organ
new_organ.on_insert(M)
var/obj/item/organ/external/parent
if(ishuman(M))
var/mob/living/carbon/human/H = M
@@ -56,6 +84,23 @@
M.internal_organs -= src
if(M.internal_organs_slot[slot] == src)
M.internal_organs_slot.Remove(slot)
for(var/removal_tag in organ_datums)
if(M.internal_organ_datums[removal_tag] == organ_datums[removal_tag])
M.internal_organ_datums -= removal_tag
var/datum/organ/removed = organ_datums[removal_tag]
removed.on_remove(M)
// Lets see if we have any backup organ datums from other internal organs.
for(var/obj/item/organ/internal/backup_organ in M.internal_organs)
for(var/replacement_tag in backup_organ.organ_datums)
if(M.internal_organ_datums[replacement_tag]) // some other organ is already covering it
continue
var/datum/organ/replacement_organ = backup_organ.organ_datums[replacement_tag]
M.internal_organ_datums[replacement_organ.organ_tag] = replacement_organ
replacement_organ.on_replace(M)
if(vital && !special)
if(M.stat != DEAD)//safety check!
M.death()
@@ -90,6 +135,12 @@
/obj/item/organ/internal/replaced(mob/living/carbon/human/target)
insert(target)
/obj/item/organ/internal/necrotize(update_sprite)
for(var/organ_tag in organ_datums) // let the organ datums handle first
var/datum/organ/dead_organ = organ_datums[organ_tag]
dead_organ.on_necrotize()
return ..()
/obj/item/organ/internal/item_action_slot_check(slot, mob/user)
return
@@ -114,6 +165,10 @@
S.origin_tech = origin_tech
S.w_class = w_class
for(var/organ_tag in organ_datums)
var/datum/organ/delicious = organ_datums[organ_tag]
delicious.on_prepare_eat(S)
return S
/obj/item/organ/internal/attempt_become_organ(obj/item/organ/external/parent,mob/living/carbon/human/H)
@@ -160,7 +215,7 @@
if(slot == "heart" && ("[slot]-c-on" in states) && ("[slot]-c-off" in states)) //Give the robotic heart its robotic heart icons if they exist.
var/obj/item/organ/internal/heart/H = src
H.icon = icon('icons/obj/surgery.dmi')
H.icon_base = "[slot]-c"
H.base_icon_state = "[slot]-c"
H.dead_icon = "[slot]-c-off"
H.update_icon()
else if("[slot]-c" in states) //Give the robotic organ its robotic organ icons if they exist.
@@ -318,6 +373,17 @@
/obj/item/organ/internal/emp_act(severity)
if(!is_robotic() || emp_proof)
return
var/we_done = FALSE
for(var/organ_tag in organ_datums)
var/datum/organ/borgan = organ_datums[organ_tag]
if(borgan.on_successful_emp())
we_done = TRUE
if(we_done)
return
// No EMP handling was done, lets just give em damage
switch(severity)
if(1)
receive_damage(20, 1)
@@ -1,15 +0,0 @@
/obj/item/organ/internal/heart/slime
icon = 'icons/obj/species_organs/slime.dmi'
name = "slime heart"
icon_state = "heart"
desc = "This is a slime's osmotic pressure regulator, it appears to be some kind of biological pump that uses osmotic pressure to regulate water flow. It seems to work similar to a heart."
dead_icon = null
/obj/item/organ/internal/heart/slime/update_icon_state()
return
/obj/item/organ/internal/lungs/slime
icon = 'icons/obj/species_organs/slime.dmi'
name = "slime lungs"
icon_state = "lungs"
desc = "This is a slime's gas exchange membrane, this membrane used for oxygen intake and gas exchange. These seem to work similar to lungs."
@@ -30,4 +30,4 @@
icon = 'icons/obj/species_organs/unathi.dmi'
/obj/item/organ/internal/lungs/unathi/ash_walker
safe_oxygen_min = 4 // 4x as efficient as regular Unathi, can comfortably breathe on lavaland
organ_datums = list(/datum/organ/lungs/ashwalker)