mirror of
https://github.com/Aurorastation/Aurora.3.git
synced 2026-07-19 03:49:10 +01:00
2e939e4d9b
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
327 lines
11 KiB
Plaintext
327 lines
11 KiB
Plaintext
/****************************************************
|
|
BLOOD SYSTEM
|
|
****************************************************/
|
|
|
|
/mob/living/carbon/human/var/datum/reagents/vessel //Container for blood and BLOOD ONLY. Do not transfer other chems here.
|
|
|
|
//Initializes blood vessels
|
|
/mob/living/carbon/human/proc/make_blood()
|
|
if(vessel)
|
|
return
|
|
|
|
vessel = new/datum/reagents(species.blood_volume, src)
|
|
|
|
if(species && species.flags & NO_BLOOD) //We want the var for safety but we can do without the actual blood.
|
|
return
|
|
|
|
vessel.add_reagent(/singleton/reagent/blood, species.blood_volume, temperature = species?.body_temperature)
|
|
fixblood()
|
|
|
|
//Resets blood data
|
|
/mob/living/carbon/human/proc/fixblood()
|
|
if(!REAGENT_VOLUME(vessel, /singleton/reagent/blood))
|
|
return
|
|
LAZYINITLIST(vessel.reagent_data)
|
|
LAZYSET(vessel.reagent_data, /singleton/reagent/blood, get_blood_data())
|
|
|
|
//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(!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)
|
|
|
|
/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)
|
|
var/bled = 0
|
|
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 || !ishuman(A) || !(A.CanPass(src, sprayloc)) || hit_mob)
|
|
continue
|
|
|
|
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
|
|
|
|
/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 = 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 = heart.norm_pump_modifier
|
|
else
|
|
switch(heart.pulse)
|
|
if(PULSE_NONE)
|
|
if(recent_pump)
|
|
pulse_mod = LAZYACCESS(heart.external_pump, 2)
|
|
else
|
|
pulse_mod *= heart.none_pump_modifier
|
|
if(PULSE_SLOW)
|
|
pulse_mod *= heart.slow_pump_modifier
|
|
if(PULSE_NORM)
|
|
pulse_mod *= heart.norm_pump_modifier
|
|
if(PULSE_FAST)
|
|
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)
|
|
|
|
if(!need_breathe())
|
|
return blood_volume
|
|
|
|
var/blood_volume_mod = max(0, 1 - getOxyLoss()/(species.total_health/2))
|
|
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_add += 0.5
|
|
else if(chem_effects[CE_OXYGENATED] >= 2) // Dexplus.
|
|
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)
|
|
|
|
/****************************************************
|
|
BLOOD TRANSFERS
|
|
****************************************************/
|
|
|
|
//Gets blood from mob to the container, preserving all data in it.
|
|
/mob/living/carbon/proc/take_blood(obj/item/reagent_containers/container, var/amount)
|
|
container.reagents.add_reagent(/singleton/reagent/blood, amount, get_blood_data(), temperature = species?.body_temperature)
|
|
return TRUE
|
|
|
|
//For humans, blood does not appear from blue, it comes from vessels.
|
|
/mob/living/carbon/human/take_blood(obj/item/reagent_containers/container, var/amount)
|
|
if(!should_have_organ(BP_HEART))
|
|
reagents.trans_to_obj(container, amount)
|
|
return TRUE
|
|
|
|
if(vessel.total_volume < amount)
|
|
return FALSE
|
|
|
|
vessel.trans_to_holder(container.reagents, amount)
|
|
|
|
if(vessel.has_reagent(/singleton/reagent/blood))
|
|
LAZYSET(vessel.reagent_data, /singleton/reagent/blood, get_blood_data())
|
|
vessel.trans_to_holder(container.reagents, amount)
|
|
return TRUE
|
|
|
|
//Transfers blood from container to vessels
|
|
/mob/living/carbon/proc/inject_blood(var/amount, var/datum/reagents/donor)
|
|
var/list/injected_data = REAGENT_DATA(donor, /singleton/reagent/blood)
|
|
var/chems = LAZYACCESS(injected_data, "trace_chem")
|
|
for(var/C in chems)
|
|
reagents.add_reagent(C, (text2num(chems[C]) / species.blood_volume) * amount)//adds trace chemicals to owner's blood
|
|
|
|
//Transfers blood from reagents to vessel, respecting blood types compatability.
|
|
/mob/living/carbon/human/inject_blood(var/amount, var/datum/reagents/donor)
|
|
if(!should_have_organ(BP_HEART))
|
|
reagents.add_reagent(/singleton/reagent/blood, amount, REAGENT_DATA(donor, /singleton/reagent/blood), temperature = species?.body_temperature)
|
|
return
|
|
// Incompatibility is handled in mix_data now.
|
|
vessel.add_reagent(/singleton/reagent/blood, amount, REAGENT_DATA(donor, /singleton/reagent/blood), temperature = species?.body_temperature)
|
|
..()
|
|
|
|
/// Returns true if blood is incompatible, false otherwise.
|
|
/// Checks donor and receiver blood types, both args are strings.
|
|
/proc/blood_incompatible(var/donor, var/receiver, var/donor_species, var/receiver_species)
|
|
// Early exit so as to not check bad args
|
|
if(!donor || !receiver)
|
|
return FALSE
|
|
|
|
// Check species
|
|
if(donor_species && receiver_species)
|
|
if(donor_species != receiver_species)
|
|
return TRUE
|
|
|
|
// Check for Synthetic Blood Substitute (SBS).
|
|
// SBS is supposed to be incompatible with non-SBS, and the other way around.
|
|
if(donor=="SBS" || receiver=="SBS")
|
|
if(donor!=receiver)
|
|
return TRUE
|
|
|
|
// Parse the blood type
|
|
var/donor_antigen = copytext(donor,1,length(donor))
|
|
var/receiver_antigen = copytext(receiver,1,length(receiver))
|
|
var/donor_rh = (findtext(donor,"+")>0)
|
|
var/receiver_rh = (findtext(receiver,"+")>0)
|
|
|
|
// Do the actual check
|
|
if(donor_rh && !receiver_rh)
|
|
return TRUE
|
|
switch(receiver_antigen)
|
|
if("A")
|
|
if(donor_antigen != "A" && donor_antigen != "O") return TRUE
|
|
if("B")
|
|
if(donor_antigen != "B" && donor_antigen != "O") return TRUE
|
|
if("O")
|
|
if(donor_antigen != "O") return TRUE
|
|
//AB is a universal receiver.
|
|
return FALSE
|
|
|
|
/mob/living/carbon/proc/get_blood_color()
|
|
if(dna?.b_type == "SBS") // synthetic blood substitute
|
|
return COLOR_SYNTH_BLOOD
|
|
else if(species)
|
|
return species.blood_color
|
|
else
|
|
return COLOR_HUMAN_BLOOD
|
|
|
|
/mob/living/carbon/proc/get_blood_data()
|
|
var/data = list()
|
|
data["donor"] = WEAKREF(src)
|
|
if(dna)
|
|
data["blood_DNA"] = dna.unique_enzymes
|
|
data["blood_type"] = dna.b_type
|
|
else
|
|
data["blood_DNA"] = md5(REF(src))
|
|
data["blood_type"] = "O+"
|
|
if(species)
|
|
data["species"] = species.bodytype
|
|
data["blood_colour"] = get_blood_color()
|
|
var/list/temp_chem = list()
|
|
for(var/R in reagents.reagent_volumes)
|
|
temp_chem[R] = REAGENT_VOLUME(reagents, R)
|
|
data["trace_chem"] = temp_chem
|
|
data["dose_chem"] = chem_doses.Copy()
|
|
return data
|
|
|
|
/mob/living/carbon/human/get_blood_data()
|
|
. = ..()
|
|
var/list/trace_chems = list()
|
|
if(LAZYLEN(vessel.reagent_data))
|
|
trace_chems = LAZYACCESS(vessel.reagent_data[/singleton/reagent/blood], "trace_chem") || list()
|
|
.["trace_chem"] = trace_chems.Copy()
|
|
|
|
/proc/blood_splatter(var/target, var/source, var/large, var/spray_dir, var/sourceless_color)
|
|
var/obj/effect/decal/cleanable/blood/splatter
|
|
var/decal_type = /obj/effect/decal/cleanable/blood/splatter
|
|
if(sourceless_color == COLOR_OIL)
|
|
decal_type = /obj/effect/decal/cleanable/blood/oil/streak
|
|
if(ishuman(source))
|
|
var/mob/living/carbon/human/H = source
|
|
if(H.isSynthetic())
|
|
decal_type = /obj/effect/decal/cleanable/blood/oil/streak
|
|
var/turf/T = get_turf(target)
|
|
|
|
// Are we dripping or splattering?
|
|
var/list/drips = list()
|
|
// Only a certain number of drips (or one large splatter) can be on a given turf.
|
|
for(var/obj/effect/decal/cleanable/blood/drip/drop in T)
|
|
drips |= drop.drips
|
|
qdel(drop)
|
|
if(!large && length(drips) < 3)
|
|
decal_type = /obj/effect/decal/cleanable/blood/drip
|
|
if(sourceless_color == COLOR_OIL)
|
|
decal_type = /obj/effect/decal/cleanable/blood/drip/oil
|
|
|
|
// Find a blood decal or create a new one.
|
|
if(T)
|
|
var/list/existing = filter_list(T.contents, decal_type)
|
|
if(length(existing) > 3)
|
|
splatter = pick(existing)
|
|
if(!splatter)
|
|
splatter = new decal_type(T)
|
|
|
|
var/obj/effect/decal/cleanable/blood/drip/drop = splatter
|
|
if(istype(drop) && drips && drips.len && !large)
|
|
drop.overlays |= drips
|
|
drop.drips |= drips
|
|
|
|
// If there's no data to copy, call it quits here.
|
|
var/list/blood_data
|
|
if(islist(source))
|
|
blood_data = source
|
|
if(ishuman(source))
|
|
var/mob/living/carbon/human/donor = source
|
|
blood_data = donor.get_blood_data()
|
|
else if(isatom(source))
|
|
var/atom/donor = source
|
|
blood_data = REAGENT_DATA(donor.reagents, /singleton/reagent/blood)
|
|
else if(!source)
|
|
if(sourceless_color)
|
|
splatter.basecolor = sourceless_color
|
|
splatter.update_icon()
|
|
return splatter
|
|
if(!islist(blood_data))
|
|
return splatter
|
|
|
|
// Update appearance.
|
|
if(blood_data["blood_colour"])
|
|
splatter.basecolor = blood_data["blood_colour"]
|
|
splatter.update_icon()
|
|
if(spray_dir)
|
|
splatter.icon_state = "squirt"
|
|
splatter.set_dir(spray_dir)
|
|
|
|
// Update blood information.
|
|
if(blood_data["blood_DNA"])
|
|
splatter.blood_DNA = list()
|
|
if(blood_data["blood_type"])
|
|
splatter.blood_DNA[blood_data["blood_DNA"]] = blood_data["blood_type"]
|
|
else
|
|
splatter.blood_DNA[blood_data["blood_DNA"]] = "O+"
|
|
|
|
splatter.fluorescent = 0
|
|
splatter.set_invisibility(0)
|
|
return splatter
|