Galatean BioAugments (#21013)

This PR "Gently refactors" organs in preparation for adding in new
variant organsas requested by HumanLore. Additionally, it adds in
several organs that were requested by Human Lore, as described in:

https://docs.google.com/document/d/1SLbll969PyJ_H9J1LP8cmdso9OMozUc8MGOVZkY4HbE/mobilebasic

The new augments include: Auxiliary Heart, Platelet Factories, and
Subdermal Carpace
Additionally, all of the new Galatean Bioaugs have been added to the
antag uplink.

The bulk of this PR consists of replacing a majority of all the organ
"Magic Numbers" with variables which could feasibly be modified by any
new kind of organ object. Additionally-- although it's unused in this
PR, here's also a new optional boolean for hearts to create a "Fake
Pulse", which would be useful in the future for event character shells
to be able to fool a pulse check. Finally, the heart systems are
configured to use Signals so that arbitrarily any component can
introduce their own modifiers to the heart statistics.

This PR was requested by Human Lore:
<img width="434" height="290" alt="image"
src="https://github.com/user-attachments/assets/98236886-1c19-4621-958e-c154ae66f2ad"
/>

Sprites have been made by @NobleRow
This commit is contained in:
VMSolidus
2025-08-04 08:29:41 -04:00
committed by GitHub
parent a45faab183
commit 2e939e4d9b
23 changed files with 817 additions and 248 deletions
+52 -46
View File
@@ -27,97 +27,99 @@
//Makes a blood drop, leaking amt units of blood from the mob
/mob/living/carbon/human/proc/drip(var/amt as num, var/tar = src, var/spraydir)
if(species && species.flags & NO_BLOOD) //TODO: Make drips come from the reagents instead.
return
if(!amt)
if(!amt || species && species.flags & NO_BLOOD) //TODO: Make drips come from the reagents instead.
return
vessel.remove_reagent(/singleton/reagent/blood,amt)
blood_splatter(tar, src, spray_dir = spraydir)
#define BLOOD_SPRAY_DISTANCE 2
/mob/living/carbon/human/proc/blood_squirt(var/amt, var/turf/sprayloc)
/mob/living/carbon/human/proc/blood_squirt(var/amt, var/turf/sprayloc, var/blood_spray_distance)
if(amt <= 0 || !istype(sprayloc))
return
var/spraydir = pick(GLOB.alldirs)
amt = Ceiling(amt/BLOOD_SPRAY_DISTANCE)
amt = Ceiling(amt/blood_spray_distance)
var/bled = 0
for(var/i = 1 to BLOOD_SPRAY_DISTANCE)
for(var/i = 1 to blood_spray_distance)
sprayloc = get_step(sprayloc, spraydir)
if(!istype(sprayloc) || (sprayloc.density && !iswall(sprayloc)))
break
var/hit_mob
for(var/thing in sprayloc)
var/atom/A = thing
if(!A.simulated)
if(!A.simulated || !ishuman(A) || !(A.CanPass(src, sprayloc)) || hit_mob)
continue
if(ishuman(A))
var/mob/living/carbon/human/H = A
if(!H.lying)
H.bloody_body(src)
H.bloody_hands(src)
var/blinding = FALSE
if(ran_zone(BP_HEAD, 75))
blinding = TRUE
for(var/obj/item/I in list(H.head, H.glasses, H.wear_mask))
if(I && (I.body_parts_covered & EYES))
blinding = FALSE
break
if(blinding)
H.eye_blurry = max(H.eye_blurry, 10)
H.eye_blind = max(H.eye_blind, 5)
to_chat(H, SPAN_DANGER("You are blinded by a spray of blood!"))
else
to_chat(H, SPAN_DANGER("You are hit by a spray of blood!"))
hit_mob = TRUE
if(!(A.CanPass(src, sprayloc)) || hit_mob)
var/mob/living/carbon/human/H = A
if(H.lying)
continue
H.bloody_body(src)
H.bloody_hands(src)
var/blinding = FALSE
if(ran_zone(BP_HEAD, 75))
blinding = TRUE
for(var/obj/item/I in list(H.head, H.glasses, H.wear_mask))
if(I && (I.body_parts_covered & EYES))
blinding = FALSE
break
if(blinding)
H.eye_blurry = max(H.eye_blurry, 10)
H.eye_blind = max(H.eye_blind, 5)
to_chat(H, SPAN_DANGER("You are blinded by a spray of blood!"))
else
to_chat(H, SPAN_DANGER("You are hit by a spray of blood!"))
hit_mob = TRUE
drip(amt, sprayloc, spraydir)
bled += amt
if(hit_mob || iswall(sprayloc))
break
return bled
#undef BLOOD_SPRAY_DISTANCE
/mob/living/carbon/human/proc/get_blood_volume()
return round((REAGENT_VOLUME(vessel, /singleton/reagent/blood)/species.blood_volume)*100)
/mob/living/carbon/human/proc/get_blood_circulation()
var/obj/item/organ/internal/heart/heart = internal_organs_by_name[BP_HEART]
var/blood_volume = get_blood_volume()
if(!heart)
return 0.25 * blood_volume
var/recent_pump = LAZYACCESS(heart.external_pump, 1) > world.time - (20 SECONDS)
var/pulse_mod = 1
var/pulse_mod = heart.base_pump_rate
var/min_efficiency = recent_pump ? 0.5 : 0.3
// Check if any components on the user wish to mess with the pump rate.
SEND_SIGNAL(src, COMSIG_HEART_PUMP_EVENT, heart, &blood_volume, &recent_pump, &pulse_mod, &min_efficiency)
if((status_flags & FAKEDEATH) || BP_IS_ROBOTIC(heart))
pulse_mod = 1
pulse_mod = heart.norm_pump_modifier
else
switch(heart.pulse)
if(PULSE_NONE)
if(recent_pump)
pulse_mod = LAZYACCESS(heart.external_pump, 2)
else
pulse_mod *= 0.25
pulse_mod *= heart.none_pump_modifier
if(PULSE_SLOW)
pulse_mod *= 0.9
pulse_mod *= heart.slow_pump_modifier
if(PULSE_NORM)
pulse_mod *= heart.norm_pump_modifier
if(PULSE_FAST)
pulse_mod *= 1.1
if(PULSE_2FAST, PULSE_THREADY)
pulse_mod *= 1.25
blood_volume *= pulse_mod
var/min_efficiency = recent_pump ? 0.5 : 0.3
blood_volume *= max(min_efficiency, (1-(heart.damage / heart.max_damage)))
pulse_mod *= heart.fast_pump_modifier
if(PULSE_2FAST)
pulse_mod *= heart.too_fast_pump_modifier
if(PULSE_THREADY)
pulse_mod *= heart.thready_pump_modifier
blood_volume *= pulse_mod * max(min_efficiency, (1-(heart.damage / heart.max_damage)))
return min(blood_volume, 100)
/mob/living/carbon/human/proc/get_blood_oxygenation()
var/blood_volume = get_blood_circulation()
if(is_asystole() || (status_flags & FAKEDEATH)) // Heart is missing or isn't beating and we're not breathing (hardcrit)
return min(blood_volume, BLOOD_VOLUME_SURVIVE)
@@ -125,12 +127,16 @@
return blood_volume
var/blood_volume_mod = max(0, 1 - getOxyLoss()/(species.total_health/2))
var/oxygenated_mult = 0
var/oxygenated_add = 0
// Check if any components on the user wish to mess with the blood oxygenation.
SEND_SIGNAL(src, COMSIG_BLOOD_OXYGENATION_EVENT, &blood_volume, &blood_volume_mod, &oxygenated_add)
if(chem_effects[CE_OXYGENATED] == 1) // Dexalin.
oxygenated_mult = 0.5
oxygenated_add += 0.5
else if(chem_effects[CE_OXYGENATED] >= 2) // Dexplus.
oxygenated_mult = 0.8
blood_volume_mod = blood_volume_mod + oxygenated_mult - (blood_volume_mod * oxygenated_mult)
oxygenated_add += 0.8
blood_volume_mod = blood_volume_mod + oxygenated_add - (blood_volume_mod * oxygenated_add)
blood_volume = blood_volume * blood_volume_mod
return min(blood_volume, 100)
+10 -11
View File
@@ -146,17 +146,16 @@
/obj/item/organ/internal/process(seconds_per_tick)
..()
if(istype(owner) && (toxin_type in owner.chem_effects))
take_damage(owner.chem_effects[toxin_type] * 0.1 * PROCESS_ACCURACY, SPT_PROB(1, seconds_per_tick))
handle_regeneration()
take_damage(owner.chem_effects[toxin_type] * seconds_per_tick)
handle_regeneration(seconds_per_tick)
tick_surge_damage() //Yes, this is intentional.
/obj/item/organ/internal/proc/handle_regeneration()
/obj/item/organ/internal/proc/handle_regeneration(seconds_per_tick)
SHOULD_CALL_PARENT(TRUE)
if(damage && !BP_IS_ROBOTIC(src) && istype(owner))
if(!owner.is_asystole())
if(!(owner.chem_effects[CE_TOXIN] || (toxin_type in owner.chem_effects)))
var/repair_modifier = owner.chem_effects[CE_ORGANREPAIR] || 0.1
if(damage < repair_modifier*max_damage)
heal_damage(repair_modifier)
return TRUE // regeneration is allowed
return FALSE // regeneration is prevented
if(!damage || BP_IS_ROBOTIC(src) || !istype(owner) || owner.is_asystole() || (owner.chem_effects[CE_TOXIN] || (toxin_type in owner.chem_effects)))
return FALSE
var/repair_modifier = owner.chem_effects[CE_ORGANREPAIR] || 0.1
if(damage < repair_modifier*max_damage)
heal_damage(repair_modifier)
return TRUE // regeneration is allowed
+32 -34
View File
@@ -14,38 +14,36 @@
/obj/item/organ/internal/appendix/process()
..()
if(inflamed && owner && !BP_IS_ROBOTIC(src))
var/can_feel_pain = ORGAN_CAN_FEEL_PAIN(src)
inflamed++
if(prob(5))
if(can_feel_pain)
owner.custom_pain("You feel a stinging pain in your abdomen!")
owner.visible_message("<B>\The [owner]</B> winces slightly.")
var/obj/item/organ/external/O = owner.get_organ(parent_organ)
if(O)
O.add_pain(10)
if(inflamed > 200)
if(prob(3))
take_internal_damage(0.1)
if(can_feel_pain)
owner.visible_message("<B>\The [owner]</B> winces painfully.")
var/obj/item/organ/external/O = owner.get_organ(parent_organ)
if(O)
O.add_pain(25)
owner.adjustToxLoss(1)
if(inflamed > 400)
if(prob(1))
germ_level += rand(2,6)
owner.vomit()
if(inflamed > 600)
if(prob(1))
if(can_feel_pain)
owner.custom_pain("You feel a stinging pain in your abdomen!")
owner.Weaken(10)
if(!owner || !inflamed || !BP_IS_ROBOTIC(src))
return
var/obj/item/organ/external/E = owner.get_organ(parent_organ)
E.sever_artery()
E.germ_level = max(INFECTION_LEVEL_TWO, E.germ_level)
owner.adjustToxLoss(25)
removed()
qdel(src)
var/can_feel_pain = ORGAN_CAN_FEEL_PAIN(src)
inflamed++
if(can_feel_pain && prob(5))
owner.custom_pain("You feel a stinging pain in your abdomen!")
owner.visible_message("<B>\The [owner]</B> winces slightly.")
var/obj/item/organ/external/O = owner.get_organ(parent_organ)
if(O)
O.add_pain(10)
if(inflamed > 200 && prob(3))
take_internal_damage(0.1)
if(can_feel_pain)
owner.visible_message("<B>\The [owner]</B> winces painfully.")
var/obj/item/organ/external/O = owner.get_organ(parent_organ)
if(O)
O.add_pain(25)
owner.adjustToxLoss(1)
if(inflamed > 400 && prob(1))
germ_level += rand(2,6)
owner.vomit()
if(inflamed > 600 && prob(1))
if(can_feel_pain)
owner.custom_pain("You feel a stinging pain in your abdomen!")
owner.Weaken(10)
var/obj/item/organ/external/E = owner.get_organ(parent_organ)
E.sever_artery()
E.germ_level = max(INFECTION_LEVEL_TWO, E.germ_level)
owner.adjustToxLoss(25)
removed()
qdel(src)
+196 -37
View File
@@ -18,16 +18,116 @@
var/next_blood_squirt = 0
var/list/external_pump
/obj/item/organ/internal/heart/process()
if(owner)
handle_pulse()
if(pulse)
if(pulse == PULSE_2FAST && prob(1))
take_internal_damage(0.5)
if(pulse == PULSE_THREADY && prob(5))
take_internal_damage(0.5)
handle_heartbeat()
handle_blood()
/**
* The amount of damage a heart takes per second while in fibrillation(red pulse).
* A Heart risks stopping as it takes damage.
*/
var/heart_fibrillation_damage = 0.16666
/**
* The amount of damage a heart takes per second while in tachycardia(yellow pulse).
* A Heart risks stopping as it takes damage.
*/
var/heart_tachycardia_damage = 0.033333
/// The percent chance each tick that a heart in fibrillation will immediately stop
var/fibrillation_stop_risk = 5
/// How much pain is required for a heart attack to be possible.
var/shock_stage_for_fibrillation = 120
/// Percent chance of a heart attack when above a certain amount of pain.
var/shock_risk_from_pain = 30
/// Over this amount of shock, worsen the heart's condition step by one step
var/first_shock_stage = 30
/// Over this amount of shock, worsen the heart's condition step by one step, cumulative with the previous stage.
var/second_shock_stage = 80
/// How much this heart modifies the owner's blood regeneration, needed for Bioaugmented hearts
var/blood_regen_modifier = 1
/// How much this heart modifies the rate of bloodloss per arterial wound.
var/base_arterial_bloodloss_modifier = 1
/// How much this heart modifies the rate of bloodloss per cut wound.
var/base_cut_bloodloss_modifier = 1
/// How much this heart modifies nutrition loss while regenerating blood.
var/nutrition_cost_per_blood_regen = 2
/// How much this heart modifies hydration loss while regenerating blood.
var/hydration_cost_per_blood_regen = 1
/// The temperature at which the heart "Freezes" in Cryostasis.
var/minimum_temperature_to_pump_blood = 170
/// The amount of time (in seconds) that pass between arterial sprays if injured as such.
var/time_between_arterial_sprays = 100
/// How much this heart modifies the rate of bloodloss from external dripping. This is a more general clotting factor.
var/bleed_drip_modifier = 1
/// The threshold of pain for which the player will experience "Heart palpitations", IE: Hearing your own heartbeat.
var/palpitations_threshold = 10
/// How much "Slow" pulse modifies bleed rate
var/slow_bleeding_modifier = 0.8
/// How much "Fast" pulse modifies bleed rate
var/fast_bleeding_modifier = 1.25
/// How much Tachycardia modifies bleed rate
var/too_fast_bleeding_modifier = 1.5
/// How much fibrillation modifies bleed rate
var/thready_bleeding_modifier = 1.5
/// The base rate at which this heart pumps blood regardless of pulse rate.
var/base_pump_rate = 1
/// The "base blood circulation" for if the heart is fully stopped. This can get floored later to as little as 0.075 on default settings.
var/none_pump_modifier = 0.25
/// Modifier on how much blood this heart pumps while at a "slow" pulse rate.
var/slow_pump_modifier = 0.9
/// Modifier on how much blood this heart pumps at a "normal" pulse rate.
var/norm_pump_modifier = 1
/// Modifier on how much blood this heart pumps at a "fast" pulse rate.
var/fast_pump_modifier = 1.1
/// Modifier on how much blood this heart pumps at a "2fast" pulse rate.
var/too_fast_pump_modifier = 1.25
/// Modifier on how much blood this heart pumps at a "thready" pulse rate.
var/thready_pump_modifier = 1.25
/// Useful for high end robotic hearts, whether they have ways of faking the appearance of a pulse so long as they're active.
var/fake_pulse = FALSE
/// How much damage this heart can take from chemically induced arythmia per instance.
var/damage_from_chemicals = 0.5
/// Sound effect used for heartbeat warnings, such as when the user is in pain/risk of dying.
var/heartbeat_sound = 'sound/effects/singlebeat.ogg'
/// Distance effects for arterial sprays.
var/blood_spray_distance = 2
/obj/item/organ/internal/heart/process(seconds_per_tick)
if(!owner)
return ..()
handle_pulse()
if(pulse)
if(pulse == PULSE_2FAST)
take_internal_damage(heart_tachycardia_damage * seconds_per_tick)
if(pulse == PULSE_THREADY)
take_internal_damage(heart_fibrillation_damage * seconds_per_tick)
handle_heartbeat()
handle_blood()
..()
/obj/item/organ/internal/heart/proc/handle_pulse()
@@ -36,22 +136,29 @@
return
// pulse mod starts out as just the chemical effect amount
var/pulse_mod = owner.chem_effects[CE_PULSE]
var/pulse_mod = owner.chem_effects[CE_PULSE] // TODO: Make chems go through signals.
var/is_stable = owner.chem_effects[CE_STABLE]
var/oxy = owner.get_blood_oxygenation()
var/circulation = owner.get_blood_circulation()
var/canceled = FALSE
// If you have enough heart chemicals to be over 2, you're likely to take extra damage.
// Check if any components on the user wish to mess with the pulse calculations.
SEND_SIGNAL(owner, COMSIG_HEART_PULSE_EVENT, &pulse_mod, &is_stable, &oxy, &circulation, &canceled)
if(canceled)
return // Oh hey someone stopped my heart from beating via a SIGNAL response!
// If you have enough heart chemicals to be over 2, take extra damage per tick.
if(pulse_mod > 2 && !is_stable)
var/damage_chance = (pulse_mod - 2) ** 2
if(prob(damage_chance))
take_internal_damage(0.5)
take_internal_damage(damage_from_chemicals)
// Now pulse mod is impacted by shock stage and other things too
if(owner.shock_stage > 30)
if(owner.shock_stage > first_shock_stage)
pulse_mod++
if(owner.shock_stage > 80)
if(owner.shock_stage > second_shock_stage)
pulse_mod++
var/oxy = owner.get_blood_oxygenation()
if(oxy < BLOOD_VOLUME_OKAY) //brain wants us to get moar oxygen
pulse_mod++
if(oxy < BLOOD_VOLUME_BAD) //MOAR
@@ -65,9 +172,9 @@
if(pulse == PULSE_NONE)
return
else //and if it's beating, let's see if it should
var/should_stop = prob(80) && owner.get_blood_circulation() < BLOOD_VOLUME_SURVIVE //cardiovascular shock, not enough liquid to pump
var/should_stop = prob(80) && circulation < BLOOD_VOLUME_SURVIVE //cardiovascular shock, not enough liquid to pump
should_stop = should_stop || prob(max(0, owner.getBrainLoss() - owner.maxHealth * 0.75)) //brain failing to work heart properly
should_stop = should_stop || (prob(5) && pulse == PULSE_THREADY) //erratic heart patterns, usually caused by oxyloss
should_stop = should_stop || (prob(fibrillation_stop_risk) && pulse == PULSE_THREADY) //erratic heart patterns, usually caused by oxyloss
should_stop = should_stop || owner.chem_effects[CE_NOPULSE]
if(should_stop) // The heart has stopped due to going into traumatic or cardiovascular shock.
to_chat(owner, SPAN_DANGER("Your heart has stopped!"))
@@ -78,7 +185,7 @@
pulse = clamp(PULSE_NORM + pulse_mod, PULSE_SLOW, PULSE_2FAST)
// If fibrillation, then it can be PULSE_THREADY
var/fibrillation = oxy <= BLOOD_VOLUME_SURVIVE || (prob(30) && owner.shock_stage > 120)
var/fibrillation = oxy <= BLOOD_VOLUME_SURVIVE || (owner.shock_stage > shock_stage_for_fibrillation && prob(shock_risk_from_pain))
if(pulse && fibrillation) //I SAID MOAR OXYGEN
pulse = PULSE_THREADY
@@ -90,7 +197,7 @@
pulse++
/obj/item/organ/internal/heart/proc/handle_heartbeat()
if(pulse >= PULSE_2FAST || owner.shock_stage >= 10)
if(pulse >= PULSE_2FAST || owner.shock_stage >= palpitations_threshold)
//PULSE_THREADY - maximum value for pulse, currently it is 5.
//High pulse value corresponds to a fast rate of heartbeat.
//Divided by 2, otherwise it is too slow.
@@ -103,7 +210,7 @@
if(heartbeat >= rate)
heartbeat = 0
sound_to(owner, sound('sound/effects/singlebeat.ogg', 0, 0, 0, 50))
sound_to(owner, sound(heartbeat_sound, 0, 0, 0, 50))
else
heartbeat++
@@ -114,19 +221,23 @@
if(owner.species && owner.species.flags & NO_BLOOD)
return
if(!owner || owner.stat == DEAD || owner.bodytemperature < 170 || owner.InStasis()) //Dead or cryosleep people do not pump the blood.
if(!owner || owner.stat == DEAD || owner.bodytemperature < minimum_temperature_to_pump_blood || owner.InStasis()) //Dead or cryosleep people do not pump the blood.
return
if(pulse != PULSE_NONE || BP_IS_ROBOTIC(src))
var/blood_volume = round(REAGENT_VOLUME(owner.vessel, /singleton/reagent/blood))
var/cut_bloodloss_modifier = base_cut_bloodloss_modifier
var/arterial_bloodloss_modifier = base_arterial_bloodloss_modifier
SEND_SIGNAL(owner, COMSIG_HEART_BLEED_EVENT, &blood_volume, &cut_bloodloss_modifier, &arterial_bloodloss_modifier)
//Blood regeneration if there is some space
if(blood_volume < species.blood_volume && blood_volume)
if(REAGENT_DATA(owner.vessel, /singleton/reagent/blood)) // Make sure there's blood at all
owner.vessel.add_reagent(/singleton/reagent/blood, BLOOD_REGEN_RATE + LAZYACCESS(owner.chem_effects, CE_BLOODRESTORE), temperature = species?.body_temperature)
owner.vessel.add_reagent(/singleton/reagent/blood, (BLOOD_REGEN_RATE * blood_regen_modifier) + LAZYACCESS(owner.chem_effects, CE_BLOODRESTORE), temperature = species?.body_temperature)
if(blood_volume <= BLOOD_VOLUME_SAFE) //We lose nutrition and hydration very slowly if our blood is too low
owner.adjustNutritionLoss(2)
owner.adjustHydrationLoss(1)
owner.adjustNutritionLoss(nutrition_cost_per_blood_regen)
owner.adjustHydrationLoss(hydration_cost_per_blood_regen)
//Bleeding out
var/blood_max = 0
@@ -142,9 +253,9 @@
var/mob/living/carbon/human/H = temp.applied_pressure
H.bloody_hands(src, 0)
var/min_eff_damage = max(0, W.damage - 10) / 6
blood_max += max(min_eff_damage, W.damage - 30) / 40
blood_max += max(min_eff_damage, W.damage - 30) / 40 * cut_bloodloss_modifier
else
blood_max += ((W.damage / 40) * species.bleed_mod)
blood_max += ((W.damage / 40) * species.bleed_mod * cut_bloodloss_modifier)
if(temp.status & ORGAN_ARTERY_CUT)
var/bleed_amount = FLOOR((owner.vessel.total_volume / (temp.applied_pressure || !open_wound ? 450 : 250)) * temp.arterial_bleed_severity, 0.1)
@@ -157,15 +268,19 @@
blood_max += bleed_amount
do_spray += "[temp.name]"
else
owner.vessel.remove_reagent(/singleton/reagent/blood, bleed_amount)
owner.vessel.remove_reagent(/singleton/reagent/blood, bleed_amount * arterial_bloodloss_modifier)
switch(pulse)
if(PULSE_SLOW)
blood_max *= 0.8
blood_max *= slow_bleeding_modifier
if(PULSE_FAST)
blood_max *= 1.25
if(PULSE_2FAST, PULSE_THREADY)
blood_max *= 1.5
blood_max *= fast_bleeding_modifier
if(PULSE_2FAST)
blood_max *= too_fast_bleeding_modifier
if (PULSE_THREADY)
blood_max *= thready_bleeding_modifier
blood_max *= bleed_drip_modifier
if(CE_BLOODCLOT in owner.chem_effects)
blood_max *= 0.7
@@ -177,11 +292,11 @@
SPAN_DANGER("<font size=3>Blood sprays out of your [pick(do_spray)]!</font>"))
owner.eye_blurry = 2
owner.Stun(1)
next_blood_squirt = world.time + 100
next_blood_squirt = world.time + time_between_arterial_sprays
var/turf/sprayloc = get_turf(owner)
blood_max -= owner.drip(Ceiling(blood_max/3), sprayloc)
if(blood_max > 0)
blood_max -= owner.blood_squirt(blood_max, sprayloc)
blood_max -= owner.blood_squirt(blood_max, sprayloc, blood_spray_distance)
if(blood_max > 0)
owner.drip(blood_max, get_turf(owner))
else
@@ -197,16 +312,22 @@
var/obj/item/organ/external/E = owner.organs_by_name[parent_organ]
if(prob(surge_damage))
owner.custom_pain(SPAN_DANGER("Your [E.name] stings horribly!"), 15, FALSE, E)
sound_to(owner, sound('sound/effects/singlebeat.ogg', 0, 0, 0, 100))
sound_to(owner, sound(heartbeat_sound, 0, 0, 0, 100))
/obj/item/organ/internal/heart/listen()
if(BP_IS_ROBOTIC(src) && is_working())
if((BP_IS_ROBOTIC(src) && is_working()) && !fake_pulse)
if(is_bruised())
return "sputtering pump"
else
return "steady whirr of the pump"
if(!pulse || (owner.status_flags & FAKEDEATH))
if (owner.status_flags & FAKEDEATH)
return "no pulse"
if (fake_pulse)
return "normal pulse"
if(!pulse)
return "no pulse"
var/pulsesound = "normal"
@@ -224,3 +345,41 @@
pulsesound = "extremely fast and faint"
. = "[pulsesound] pulse"
// Example heart item that has significantly higher statistics.
// Also to be used for the Galatean Bio-augments PRs.
// TODO: After refactoring the organ selector, make it so that this is a selectable heart type(For Galateans)
/obj/item/organ/internal/heart/boosted_heart
name = "boosted heart"
desc = "Intended for athletes, some workers, and soldiers, this improved heart increases blood flow and circulation." \
+ "It provides an improvement to blood oxygenation and stamina, at the cost of requiring more food and water." \
+ "Outside of Galatea, this augment is popular among professional athletes."
icon = 'icons/obj/organs/bioaugs.dmi'
icon_state = "boosted_heart"
max_damage = 80
min_broken_damage = 60
shock_stage_for_fibrillation = 140
fibrillation_stop_risk = 3.5
base_pump_rate = 1.15
nutrition_cost_per_blood_regen = 4
hydration_cost_per_blood_regen = 3
blood_regen_modifier = 1.1
bleed_drip_modifier = 1.1
blood_spray_distance = 3
// Example heart item that has significantly lowered statistics.
// TODO: After refactoring the organ selector, make it so that this is a selectable heart type.
/obj/item/organ/internal/heart/scarred_heart
name = "scarred heart"
desc = "Life has not been good to this old ticker."
icon = 'icons/obj/organs/bioaugs.dmi'
icon_state = "scarred_heart"
max_damage = 45
min_broken_damage = 30
fibrillation_stop_risk = 7.5
shock_stage_for_fibrillation = 100
blood_regen_modifier = 0.9
base_pump_rate = 0.9
thready_pump_modifier = 1.5
damage_from_chemicals = 0.7
blood_spray_distance = 1
+142 -50
View File
@@ -12,69 +12,161 @@
max_damage = 70
relative_size = 60
/obj/item/organ/internal/liver/process()
/// The base "efficiency" of a liver.
var/base_filter_strength = 1
/// The base "Filter effect" rating of a liver, used for calculating whether or not drinking alcohol causes lethal damage.
var/base_filter_effect = 3
/// How much dylovene contributes to "filter effect".
var/filter_effect_from_dylovene = 1
/**
* How much toxin damage per second a liver-haver takes if they combine alcohol with a non-functional liver.
* This gets multiplied by the inverse absolute average deviation from base filter effect.
*/
var/lethal_booze_dps = 1
/// How much a liver being in its "bruised" state modifies its efficiency.
var/intox_filter_bruised = 0.462
/// How much a liver being in its "broken" state modifies its efficiency.
var/intox_filter_broken = 0.198
/// How much a liver being robotic modifies its efficiency. TODO: Remove this after reworking the organ selector to make robotic organs actually different entities.
var/intox_filter_robotic = 1.1
/// The base rate at which this liver processes Dylovene into "Toxin healing" per second.
var/base_toxin_healing_rate = 6
/// The upper limit of how much toxin a liver can contain before its efficiency tanks dramatically.
var/toxin_critical_mass = 60
/// How much toxin volume above the toxin critical mass contributes to the filter effect.
var/filter_effect_from_critical_toxin = 2
/// Modifier on how efficiently this liver eliminates booze.
var/booze_filtering_modifier = 1
/// How much the liver being bruised contributes to filter effect.
var/filter_effect_from_bruise = 1
/// How much the liver being broken contributes to filter effect.
var/filter_effect_from_broken = 2
/// Modifier on how efficiently this liver eliminates booze while blackout drunk.
var/blackout_booze_filtering_modifier = 0.5
/// Message to play in chat to a liver-haver when they have an infection.
var/infection_level_one_warning = "Your skin itches."
/// A largely undamaged healthy liver will gradually heal itself over time by this amount per second
var/liver_regeneration_normal = 6
/// A "bruised"(lightly damaged) liver will gradually heal itself by this amount per second.
var/liver_regeneration_bruised = 4
/// A liver that is "broken" will heal itself by this amount per second. By default, this is no regeneration.
var/liver_regeneration_broken = 0
/obj/item/organ/internal/liver/process(seconds_per_tick)
..()
if(!owner)
return
if (germ_level > INFECTION_LEVEL_ONE)
owner.notify_message(SPAN_WARNING("Your skin itches."), 2 MINUTES)
if (germ_level > INFECTION_LEVEL_TWO)
if(prob(1))
spawn owner.delayed_vomit()
//Detox can heal small amounts of damage
if (damage < max_damage && !owner.chem_effects[CE_TOXIN])
heal_damage(0.3 * owner.chem_effects[CE_ANTITOXIN])
if(germ_level > INFECTION_LEVEL_ONE)
owner.notify_message(SPAN_WARNING(infection_level_one_warning), 2 MINUTES)
if(germ_level > INFECTION_LEVEL_TWO && prob(1))
spawn owner.delayed_vomit()
// Get the effectiveness of the liver.
var/filter_effect = 3
var/filter_effect = base_filter_effect
// Copy the base filter variables, so that we aren't modifying the original values.
var/filter_strength = base_filter_strength
var/toxin_healing_rate = base_toxin_healing_rate
// Optionally allow signal repliers to cancel liver filtration. Antifreeze could be a fun one.
var/canceled = FALSE
// Check if any components on the user wish to mess with liver filtration.
SEND_SIGNAL(owner, COMSIG_LIVER_FILTER_EVENT, &filter_effect, &filter_strength, &toxin_healing_rate, &canceled)
if(canceled)
return
//Detox can heal small amounts of damage
if(damage < max_damage && !owner.chem_effects[CE_TOXIN])
heal_damage(toxin_healing_rate * seconds_per_tick * owner.chem_effects[CE_ANTITOXIN])
if(is_bruised())
filter_effect -= 1
filter_strength *= intox_filter_bruised // Defaulted to 0.462
filter_effect -= filter_effect_from_bruise
if(is_broken())
filter_effect -= 2
// Robotic organs filter better but don't get benefits from dylovene for filtering.
filter_strength *= intox_filter_broken // Defaulted to 0.198
filter_effect -= filter_effect_from_broken
if(BP_IS_ROBOTIC(src))
filter_effect += 1
if(getToxLoss() >= 60) //Too many toxins to process. Abort, abort.
filter_effect -= 2
else if(owner.chem_effects[CE_ANTITOXIN])
filter_effect += 1
filter_strength *= intox_filter_robotic // Defaulted to 1.1
if(filter_effect < 2) //Trouble. You're not filtering well.
if(owner.intoxication > 0)
owner.adjustToxLoss(0.5 * max(2 - filter_effect, 0))
var/filter_strength = INTOX_FILTER_HEALTHY
if(is_bruised())
filter_strength = INTOX_FILTER_BRUISED
if(is_broken())
filter_strength = INTOX_FILTER_DAMAGED
if(BP_IS_ROBOTIC(src))
filter_strength *= 1.1
if (owner.intoxication > 0)
var/bac = owner.get_blood_alcohol()
var/res = owner.species ? owner.species.ethanol_resistance : 1
if(bac >= INTOX_BLACKOUT * res) //Excessive blood alcohol, difficult to filter
owner.intoxication -= min(owner.intoxication, filter_strength/2)
else
owner.intoxication -= min(owner.intoxication, filter_strength)
if(!owner.intoxication)
owner.handle_intoxication()
if(owner.intoxication > 0)
handle_alcohol_poisoning(filter_effect, filter_strength, seconds_per_tick)
if(!is_damaged())
return
if(toxin_type in owner.chem_effects)
if(is_damaged())
owner.adjustToxLoss(owner.chem_effects[toxin_type] * 0.1 * PROCESS_ACCURACY) // as actual organ damage is handled elsewhere
// Hepatoxic(liver damaging) chems start dealing lethal damage to the patient once the liver is broken.
owner.adjustToxLoss(max(0, (owner.chem_effects[toxin_type] - filter_strength)) * seconds_per_tick) // as actual organ damage is handled elsewhere
/obj/item/organ/internal/liver/handle_regeneration()
if(..())
if(!owner.total_radiation && damage > 0 && !(owner.get_blood_alcohol() > INTOX_BLACKOUT))
if(damage < min_broken_damage)
heal_damage(0.2)
if(damage < min_bruised_damage)
heal_damage(0.3)
return TRUE
/// Liver damage + alcohol = bad time.
/obj/item/organ/internal/liver/proc/handle_alcohol_poisoning(var/filter_effect, var/filter_strength, var/seconds_per_tick)
// Robotic organs filter better but don't get benefits from dylovene for filtering.
if(BP_IS_ROBOTIC(src)) // TODO: Just make robot livers have a base filter_effect of 4.
filter_effect += 1
else if(owner.chem_effects[CE_ANTITOXIN])
filter_effect += filter_effect_from_dylovene
if(getToxLoss() >= toxin_critical_mass) //Too many toxins to process. Abort, abort.
filter_effect -= filter_effect_from_critical_toxin
if(filter_effect < base_filter_effect)
// Take the inverse absolute average deviation from the base filter effect.
var/booze_damage_factor = base_filter_effect / abs(filter_effect - base_filter_effect)
// And then multiply it by our tickrate and desired DPS, to get toxin damage every tick.
// Previously this was hardcoded to always fail at 0 or less filter effect, now it arbitrarily doesn't matter if filter effect goes into negatives.
// You now take further scaling damage the lower you go into negatives. This also is no longer limited to integers.
owner.adjustToxLoss(lethal_booze_dps * booze_damage_factor * seconds_per_tick)
var/bac = owner.get_blood_alcohol()
var/res = owner.species ? owner.species.ethanol_resistance : 1
var/blackout_effects = (bac >= INTOX_BLACKOUT * res) ? blackout_booze_filtering_modifier : booze_filtering_modifier
owner.intoxication -= min(owner.intoxication, filter_strength * filter_effect * blackout_effects)
if(!owner.intoxication)
owner.handle_intoxication()
/obj/item/organ/internal/liver/handle_regeneration(seconds_per_tick)
if(!..() || owner.total_radiation || damage <= 0 || (owner.get_blood_alcohol() > INTOX_BLACKOUT))
return FALSE
// This can't be a switch statement because these aren't constants. I tried.
if(liver_regeneration_normal && damage < min_bruised_damage)
heal_damage(liver_regeneration_normal * seconds_per_tick)
return TRUE
if(liver_regeneration_bruised && damage < min_broken_damage)
heal_damage(liver_regeneration_bruised * seconds_per_tick)
return TRUE
if(liver_regeneration_broken) // A default "standard" liver returns false here, since it has a regeneration rate of 0 when broken.
heal_damage(liver_regeneration_broken * seconds_per_tick)
return TRUE
return FALSE
/obj/item/organ/internal/liver/boosted_liver
name = "boosted liver"
desc = "Designed primarily for diplomats or Galateans abroad, the boosted liver improves toxin filtering, giving a resistance to toxin damage. As a consequence, it makes it impossible for the user to get drunk."
//icon = 'icons/obj/organs/bioaugs.dmi'
//icon_state = "boosted_liver"
base_filter_strength = 1.5
base_filter_effect = 5
toxin_critical_mass = 90
booze_filtering_modifier = 100 // "Impossible to get drunk", this should make it impossible. :)
blackout_booze_filtering_modifier = 1
@@ -36,7 +36,8 @@
var/supports_limb = FALSE
/obj/item/organ/internal/augment/Initialize()
robotize()
if(robotic == ROBOTIC_MECHANICAL)
robotize()
. = ..()
/obj/item/organ/internal/augment/refresh_action_button()
@@ -82,3 +83,13 @@
/obj/item/organ/internal/augment/proc/do_bruised_act()
spark(get_turf(owner), 3)
return FALSE
/obj/item/organ/internal/augment/bioaug
name = "bioaugment"
icon = 'icons/obj/organs/bioaugs.dmi'
icon_state = "boosted_heart"
robotic = FALSE
species_restricted = list(
SPECIES_HUMAN_OFFWORLD,
SPECIES_HUMAN,
)
@@ -0,0 +1,34 @@
/obj/item/organ/internal/augment/bioaug/auxiliary_heart
name = "auxiliary heart"
desc = "Intended for soldiers and the elderly, the auxiliary heart is a small secondary heart implanted below the original." \
+ "Should the original heart shut down, the secondary heart will activate to help keep the user alive until the original can be restarted." \
+ "This doesn't work if the original heart is completely destroyed, as there needs to be a reasonably intact cardiovascular system."
icon_state = "auxiliary_heart"
organ_tag = BP_AUG_AUX_HEART
parent_organ = BP_CHEST
/obj/item/organ/internal/augment/bioaug/auxiliary_heart/Initialize()
. = ..()
if(!owner)
return
RegisterSignal(owner, COMSIG_HEART_PUMP_EVENT, PROC_REF(stabilize_circulation))
/obj/item/organ/internal/augment/bioaug/auxiliary_heart/replaced()
. = ..()
if(!owner)
return
RegisterSignal(owner, COMSIG_HEART_PUMP_EVENT, PROC_REF(stabilize_circulation))
/obj/item/organ/internal/augment/bioaug/auxiliary_heart/removed()
. = ..()
if(!owner)
return
UnregisterSignal(owner, COMSIG_HEART_PUMP_EVENT)
/obj/item/organ/internal/augment/bioaug/auxiliary_heart/proc/stabilize_circulation(var/obj/item/organ/internal/heart/heart, var/blood_volume, var/recent_pump, var/pulse_mod, var/min_efficiency)
SIGNAL_HANDLER
*min_efficiency *= 1.5
*blood_volume += 2.5 // This doesn't affect actual blood volume, only the "effective" volume used for calculating thresholds.
@@ -35,3 +35,22 @@
desc = "An augment installed inside the left hand of someone."
parent_organ = BP_L_HAND
/obj/item/organ/internal/augment/bioaug/head_fluff
name = "head bioaug"
desc = "A bioaug installed inside the head of someone."
parent_organ = BP_HEAD
/obj/item/organ/internal/augment/bioaug/head_fluff/chest_fluff
name = "chest bioaug"
desc = "A bioaug installed inside the chest of someone."
parent_organ = BP_CHEST
/obj/item/organ/internal/augment/bioaug/head_fluff/rhand_fluff
name = "right hand bioaug"
desc = "A bioaug installed inside the right hand of someone."
parent_organ = BP_R_HAND
/obj/item/organ/internal/augment/bioaug/head_fluff/lhand_fluff
name = "left hand bioaug"
desc = "A bioaug installed inside the left hand of someone."
parent_organ = BP_L_HAND
@@ -1,59 +1,70 @@
/obj/item/organ/internal/augment/mind_blanker
/obj/item/organ/internal/augment/bioaug/mind_blanker
name = "mind blanker"
desc = "A small, discrete organ attached near the base of the brainstem." \
+ "Any attempt to read the mind of an individual with this augment installed will fail, as will attempts at psychic brainwashing."
icon_state = "mind_blanker"
organ_tag = BP_AUG_MIND_BLANKER
parent_organ = BP_HEAD
robotic = 0
/obj/item/organ/internal/augment/mind_blanker/Initialize()
/obj/item/organ/internal/augment/bioaug/mind_blanker/Initialize()
. = ..()
if(owner)
RegisterSignal(owner, COMSIG_PSI_MIND_POWER, PROC_REF(cancel_power))
if(!owner)
return
/obj/item/organ/internal/augment/mind_blanker/replaced()
RegisterSignal(owner, COMSIG_PSI_MIND_POWER, PROC_REF(cancel_power))
/obj/item/organ/internal/augment/bioaug/mind_blanker/replaced()
. = ..()
if(owner)
RegisterSignal(owner, COMSIG_PSI_MIND_POWER, PROC_REF(cancel_power))
if(!owner)
return
/obj/item/organ/internal/augment/mind_blanker/removed()
RegisterSignal(owner, COMSIG_PSI_MIND_POWER, PROC_REF(cancel_power))
/obj/item/organ/internal/augment/bioaug/mind_blanker/removed()
. = ..()
if(owner)
UnregisterSignal(owner, COMSIG_PSI_MIND_POWER)
if(!owner)
return
/obj/item/organ/internal/augment/mind_blanker/proc/cancel_power(mob/caster)
UnregisterSignal(owner, COMSIG_PSI_MIND_POWER)
/obj/item/organ/internal/augment/bioaug/mind_blanker/proc/cancel_power(mob/caster, var/cancelled)
SIGNAL_HANDLER
return COMSIG_PSI_MIND_POWER_CANCELLED
*cancelled = TRUE
/obj/item/organ/internal/augment/mind_blanker_lethal
/obj/item/organ/internal/augment/bioaug/mind_blanker_lethal
name = "lethal mind blanker"
desc = "A small, discrete organ attached near the base of the brainstem." \
+ "Available only to higher-up MfAS agents and members of the Galatean government." \
+ "This enhanced variant of a mind blanker includes a psionic trap which inflicts severe neural damage on anyone attempting to read the user's mind."
organ_tag = BP_AUG_MIND_BLANKER_L
parent_organ = BP_HEAD
robotic = 0
/obj/item/organ/internal/augment/mind_blanker_lethal/Initialize()
/obj/item/organ/internal/augment/bioaug/mind_blanker_lethal/Initialize()
. = ..()
if(owner)
RegisterSignal(owner, COMSIG_PSI_MIND_POWER, PROC_REF(cancel_power_lethal))
if(!owner)
return
/obj/item/organ/internal/augment/mind_blanker_lethal/replaced()
RegisterSignal(owner, COMSIG_PSI_MIND_POWER, PROC_REF(cancel_power_lethal))
/obj/item/organ/internal/augment/bioaug/mind_blanker_lethal/replaced()
. = ..()
if(owner)
RegisterSignal(owner, COMSIG_PSI_MIND_POWER, PROC_REF(cancel_power_lethal))
if(!owner)
return
/obj/item/organ/internal/augment/mind_blanker_lethal/removed()
RegisterSignal(owner, COMSIG_PSI_MIND_POWER, PROC_REF(cancel_power_lethal))
/obj/item/organ/internal/augment/bioaug/mind_blanker_lethal/removed()
. = ..()
if(owner)
UnregisterSignal(owner, COMSIG_PSI_MIND_POWER)
if(!owner)
return
/obj/item/organ/internal/augment/mind_blanker_lethal/proc/cancel_power_lethal(mob/caster)
UnregisterSignal(owner, COMSIG_PSI_MIND_POWER)
/obj/item/organ/internal/augment/bioaug/mind_blanker_lethal/proc/cancel_power_lethal(mob/caster, var/cancelled)
SIGNAL_HANDLER
*cancelled = TRUE
if(isliving(caster))
var/mob/living/victim = caster
victim.adjustBrainLoss(20)
victim.confused += 20
to_chat(victim, SPAN_DANGER("Agony lances through my mind as [src]'s mind clamps down upon me!"))
return COMSIG_PSI_MIND_POWER_CANCELLED
@@ -0,0 +1,42 @@
/obj/item/organ/internal/augment/bioaug/platelet_factories
name = "platelet factories"
desc = "Designed for military applications, this implant massively increases the user's blood clotting factor." \
+ "This provides an extreme resistance to arterial bleeds, effectively all but preventing exsanguination." \
+ "This augment has a reputation for causing heart attacks and strokes at a high rate, and is usually combined with an auxiliary heart for safety."
icon_state = "platelet_factories"
organ_tag = BP_AUG_PLATELET_FACTORIES
parent_organ = BP_CHEST
/obj/item/organ/internal/augment/bioaug/platelet_factories/Initialize()
. = ..()
if(!owner)
return
RegisterSignal(owner, COMSIG_HEART_PUMP_EVENT, PROC_REF(stroke_risk))
RegisterSignal(owner, COMSIG_HEART_BLEED_EVENT, PROC_REF(reduce_bloodloss))
/obj/item/organ/internal/augment/bioaug/platelet_factories/replaced()
. = ..()
if(!owner)
return
RegisterSignal(owner, COMSIG_HEART_PUMP_EVENT, PROC_REF(stroke_risk))
RegisterSignal(owner, COMSIG_HEART_BLEED_EVENT, PROC_REF(reduce_bloodloss))
/obj/item/organ/internal/augment/bioaug/platelet_factories/removed()
. = ..()
if(!owner)
return
UnregisterSignal(owner, COMSIG_HEART_PUMP_EVENT)
UnregisterSignal(owner, COMSIG_HEART_BLEED_EVENT)
/obj/item/organ/internal/augment/bioaug/platelet_factories/proc/stroke_risk(var/obj/item/organ/internal/heart/heart, var/blood_volume, var/recent_pump, var/pulse_mod, var/min_efficiency)
SIGNAL_HANDLER
*min_efficiency *= 0.85
*pulse_mod *= 0.95
/obj/item/organ/internal/augment/bioaug/platelet_factories/proc/reduce_bloodloss(var/blood_volume, var/cut_bloodloss_modifier, var/arterial_bloodloss_modifier)
SIGNAL_HANDLER
*cut_bloodloss_modifier *= 0.1
*arterial_bloodloss_modifier *= 0.25
@@ -0,0 +1,30 @@
/obj/item/organ/internal/augment/bioaug/subdermal_carapace
name = "subdermal carapace"
desc = "Used by the Galatean military to provide additional ballistic protection. This augment causes accelerated bone growth in the ribcage," \
+ "transforming it into a solid, hardened plate of bone. This provides a small amount of protection to vital organs." \
+ "In combination with armor, it can turn lethal injuries into merely serious wounds."
icon_state = "subdermal_carapace"
organ_tag = BP_AUG_SUBDERMAL_CARAPACE
parent_organ = BP_CHEST
/obj/item/organ/internal/augment/bioaug/subdermal_carapace/Initialize()
. = ..()
if(!owner)
return
owner.AddComponent(/datum/component/armor, list(MELEE = ARMOR_MELEE_SMALL, BULLET = ARMOR_BALLISTIC_MINOR))
/obj/item/organ/internal/augment/bioaug/subdermal_carapace/replaced()
. = ..()
if(!owner)
return
owner.AddComponent(/datum/component/armor, list(MELEE = ARMOR_MELEE_SMALL, BULLET = ARMOR_BALLISTIC_MINOR))
/obj/item/organ/internal/augment/bioaug/subdermal_carapace/removed()
. = ..()
if(!owner)
return
var/datum/component/armor/armor_component = owner.GetComponent(/datum/component/armor)
qdel(armor_component)