#define BLOOD_MINIMUM_STOP_PROCESS 2.1 // Define to avoid hitting 0 blood. /**************************************************** BLOOD SYSTEM ****************************************************/ //Blood levels. These are percentages based on the species blood_volume var. //Retained for archival/reference purposes - KK /* BLOOD_VOLUME_SAFE = 85 BLOOD_VOLUME_OKAY = 75 BLOOD_VOLUME_BAD = 60 BLOOD_VOLUME_SURVIVE = 40 */ /mob/living/carbon/human/var/datum/reagents/vessel // Container for blood and BLOOD ONLY. Do not transfer other chems here. /mob/living/carbon/human/var/var/pale = 0 // Should affect how mob sprite is drawn, but currently doesn't. /***Initializes blood vessels * Called code/modules/mob/living/carbon/human/human.dm#L1259 set_species procedure with 0 args * Also called by inject_blood as fallback with amt = injected_amount * MUST be followed by calling fixblood() allways. ***/ /mob/living/carbon/human/proc/make_blood(var/amt = 0) if(vessel) return if(species.flags & NO_BLOOD) return vessel = new/datum/reagents(species.blood_volume) vessel.my_atom = src if(!should_have_organ(O_HEART)) //We want the var for safety but we can do without the actual blood. return if(!amt) vessel.add_reagent(REAGENT_ID_BLOOD,species.blood_volume) else vessel.add_reagent(REAGENT_ID_BLOOD, clamp(amt, 1, species.blood_volume)) //Resets blood data /mob/living/carbon/human/proc/fixblood() for(var/datum/reagent/blood/B in vessel.reagent_list) if(B.id == REAGENT_ID_BLOOD) B.data = list( "donor"=src,"viruses"=null,"species"=species.name,"blood_DNA"=dna.unique_enzymes,"blood_colour"= species.get_blood_colour(src),"blood_type"=dna.b_type, \ "resistances"=null,"trace_chem"=null, "virus2" = null, REAGENT_ID_ANTIBODIES = list(), "blood_name" = species.get_blood_name(src)) if(isSynthetic()) B.data["species"] = "synthetic" B.data["changeling"] = (!isnull(mind) && is_changeling(mind)) || species?.ambulant_blood || HAS_TRAIT(src, TRAIT_REDSPACE_CORRUPTED) B.color = B.data["blood_colour"] B.name = B.data["blood_name"] // Takes care blood loss and regeneration /mob/living/carbon/human/handle_blood() if(inStasisNow()) return if(!should_have_organ(O_HEART)) return if(stat != DEAD && bodytemperature >= 170) //Dead or cryosleep people do not pump the blood. var/blood_volume_raw = vessel.get_reagent_amount(REAGENT_ID_BLOOD) var/blood_volume = round((blood_volume_raw/species.blood_volume)*100) // Percentage. //Blood regeneration if there is some space if(blood_volume_raw < species.blood_volume) var/datum/reagent/blood/B = locate() in vessel.reagent_list //Grab some blood if(B) // Make sure there's some blood at all if(B.data["donor"] != src) //If it's not theirs, then we look for theirs for(var/datum/reagent/blood/D in vessel.reagent_list) if(D.data["donor"] == src) B = D break B.volume += 0.1 // regenerate blood VERY slowly if(CE_BLOODRESTORE in chem_effects) B.volume += chem_effects[CE_BLOODRESTORE] // Damaged heart virtually reduces the blood volume, as the blood isn't // being pumped properly anymore. if(species && should_have_organ(O_HEART)) var/obj/item/organ/internal/heart/heart = internal_organs_by_name[O_HEART] if(has_modifier_of_type(/datum/modifier/bloodpump)) blood_volume_raw *= 1 blood_volume *= 1 else if(!heart) blood_volume_raw = 0 blood_volume = 0 else if(heart.is_broken()) blood_volume_raw *= 0.3 blood_volume *= 0.3 else if(heart.is_bruised()) blood_volume_raw *= 0.7 blood_volume *= 0.7 else if(heart.damage) blood_volume_raw *= 0.8 blood_volume *= 0.8 //Effects of bloodloss var/dmg_coef = 1 //Lower means less damage taken var/threshold_coef = 1 //Lower means the damage caps off lower if(CE_STABLE in chem_effects) dmg_coef = 0.5 threshold_coef = 0.75 if(blood_volume_raw >= species.blood_volume*species.blood_level_safe) if(pale) pale = 0 update_icons_body() else if(blood_volume_raw >= species.blood_volume*species.blood_level_warning) if(!pale) pale = 1 update_icons_body() var/word = pick("dizzy","woozy","faint","disoriented","unsteady") to_chat(src, span_red("You feel slightly [word]")) if(prob(1)) var/word = pick("dizzy","woozy","faint","disoriented","unsteady") to_chat(src, span_red("You feel [word]")) if(getOxyLoss() < 20 * threshold_coef) adjustOxyLoss(3 * dmg_coef) else if(blood_volume_raw >= species.blood_volume*species.blood_level_danger) if(!pale) pale = 1 update_icons_body() eye_blurry = max(eye_blurry,6) if(getOxyLoss() < 50 * threshold_coef) adjustOxyLoss(10 * dmg_coef) adjustOxyLoss(1 * dmg_coef) if(prob(15)) Paralyse(rand(1,3)) var/word = pick("dizzy","woozy","faint","disoriented","unsteady") to_chat(src, span_red("You feel dangerously [word]")) else if(blood_volume_raw >= species.blood_volume*species.blood_level_fatal) adjustOxyLoss(5 * dmg_coef) // adjustToxLoss(3 * dmg_coef) if(prob(15)) var/word = pick("dizzy","woozy","faint","disoriented","unsteady") to_chat(src, span_red("You feel extremely [word]")) else //Not enough blood to survive (usually) if(!pale) pale = 1 update_icons_body() eye_blurry = max(eye_blurry,6) Paralyse(3) Sleeping(3) adjustToxLoss(3 * dmg_coef) adjustOxyLoss(75 * dmg_coef) // 15 more than dexp fixes (also more than dex+dexp+tricord) // Without enough blood you slowly go hungry. if(blood_volume_raw < species.blood_volume*species.blood_level_safe) if(nutrition >= 300) adjust_nutrition(-10) else if(nutrition >= 200) adjust_nutrition(-3) //Bleeding out caculate_bloodloss_and_bleed(bleed = TRUE) ///Calculates our bloodloss divisor and returns what it is. /mob/living/carbon/human/proc/calculate_bloodloss_divisor() var/blood_loss_divisor = 30.01 //lower factor = more blood loss // Some species bleed out differently blood_loss_divisor /= species.bloodloss_rate // Some modifiers can make bleeding better or worse. Higher multiplers = more bleeding. var/blood_loss_modifier_multiplier = 1.0 for(var/datum/modifier/M in modifiers) if(!isnull(M.bleeding_rate_percent)) blood_loss_modifier_multiplier += (M.bleeding_rate_percent - 1.0) blood_loss_divisor /= blood_loss_modifier_multiplier return blood_loss_divisor ///Calculates how much blood we should lose from our wounds and makes us bleed that amount if bleed is TRUE ///ARGS: /// bleed: If we bleed or not while checking. DEFAULT: True /// organ_to_check: The organ we want to check. If we don't, it checks ALL the bad organs. /// count_internal: Internal bleeding counts towards our bloodloss max. DEFAULT: False /// count_external: External bleeding counts towards our bloodloss max. DEFAULT: True /mob/living/carbon/human/proc/caculate_bloodloss_and_bleed(bleed = TRUE, obj/item/organ/external/organ_to_check, count_internal = FALSE, count_external = TRUE) var/total_blood_loss = 0 var/blood_max = 0 var/blood_loss_divisor = calculate_bloodloss_divisor() var/list/organs_to_check = list() if(organ_to_check) organs_to_check += organ_to_check else organs_to_check = bad_external_organs //This 30 is the "baseline" of a cut in the "vital" regions (head and torso). for(var/obj/item/organ/external/temp in organs_to_check) ///First, we make sure it's not robotic. if(temp.robotic >= ORGAN_ROBOT) continue ///Second, we process internal bleeding. if(bleed || count_internal) for(var/datum/wound/internal_bleeding/W in temp.wounds) var/internal_blood_to_lose = calculate_internal_bloodloss(W, applied_pressure = temp.applied_pressure) if(count_internal) total_blood_loss += internal_blood_to_lose if(bleed) remove_blood(internal_blood_to_lose) if(prob(1)) custom_pain("You feel a stabbing pain in your [temp.name]!", 50) ///Thirdly, we check to see if the limb is bleeding EXTERNALLY if(!(temp.status & ORGAN_BLEEDING)) continue ///Finally, we process external wounds. for(var/datum/wound/W in temp.wounds) if(W.bleeding()) var/temp_bld = blood_loss_divisor if(W.damage_type == PIERCE) //gunshots and spear stabs bleed more temp_bld = max(temp_bld - 5, 1) else if(W.damage_type == BRUISE) //bruises bleed less temp_bld = max(temp_bld + 5, 1) //the farther you get from those vital regions, the less you bleed //depending on how dangerous bleeding turns out to be, it might be better to only apply the reduction to hands and feet if((temp.organ_tag == BP_L_ARM) || (temp.organ_tag == BP_R_ARM) || (temp.organ_tag == BP_L_LEG) || (temp.organ_tag == BP_R_LEG)) temp_bld = max(temp_bld + 5, 1) else if((temp.organ_tag == BP_L_HAND) || (temp.organ_tag == BP_R_HAND) || (temp.organ_tag == BP_L_FOOT) || (temp.organ_tag == BP_R_FOOT)) temp_bld = max(temp_bld + 10, 1) if(CE_STABLE in chem_effects) //Inaprov slows bloodloss temp_bld = max(temp_bld + 10, 1) if(temp.applied_pressure) if(ishuman(temp.applied_pressure)) var/mob/living/carbon/human/H = temp.applied_pressure H.bloody_hands(src, 0) //somehow you can apply pressure to every wound on the organ at the same time //you're basically forced to do nothing at all, so let's make it pretty effective var/min_eff_damage = max(0, W.damage - 10) / (temp_bld / 5) //still want a little bit to drip out, for effect blood_max += max(min_eff_damage, W.damage - 30) / temp_bld else blood_max += W.damage / temp_bld if(temp.open) blood_max += 2 //Yer stomach is cut open if(bleed) blood_max = round(blood_max, 0.1) drip(blood_max) total_blood_loss += blood_max return round(total_blood_loss, 0.1) ///Calculates how much blood we should lose from an internal wound. /mob/living/carbon/human/proc/calculate_internal_bloodloss(datum/wound/internal_bleeding/wound_to_check, applied_pressure = FALSE) if(!wound_to_check) return 0 var/temp_bld = calculate_bloodloss_divisor() + 10 //IB is slower bloodloss than normal. var/bicardose if(reagents.get_reagent_amount(REAGENT_ID_BICARIDINE) || reagents.get_reagent_amount(REAGENT_ID_BICARIDAZE)) bicardose = TRUE var/inaprovaline if(reagents.get_reagent_amount(REAGENT_ID_INAPROVALINE) || reagents.get_reagent_amount(REAGENT_ID_INAPROVALAZE)) inaprovaline = TRUE var/myeldose = reagents.get_reagent_amount(REAGENT_ID_MYELAMINE) if(!(wound_to_check.can_autoheal() || (bicardose && inaprovaline) || myeldose)) //bicaridine and inaprovaline stop internal wounds from growing bigger with time, unless it is so small that it is already healing wound_to_check.open_wound(0.1) if((CE_STABLE in chem_effects) || myeldose) temp_bld = max(temp_bld + 30, 1) //Inaprovaline is great on internal wounds. if(applied_pressure) //Putting pressure on the afflicted wound helps stop the arterial bleeding. temp_bld += 30 if(wound_to_check.clamped) temp_bld = temp_bld * 10 //We hemostatted the internal bleeding. Bloodloss is 10 times slower. return round((wound_to_check.damage/temp_bld), 0.1) //Makes a blood drop, leaking amt units of blood from the mob /mob/living/carbon/human/proc/drip(var/amt) if(remove_blood(amt)) blood_splatter(src,src) /mob/living/carbon/human/proc/remove_blood(var/amt) if(!should_have_organ(O_HEART)) //TODO: Make drips come from the reagents instead. return 0 if(!amt) return 0 var/current_blood = vessel.get_reagent_amount(REAGENT_ID_BLOOD) if(current_blood < BLOOD_MINIMUM_STOP_PROCESS) return 0 //We stop processing under 3 units of blood because apparently weird shit can make it overflowrandomly. if(amt > current_blood) amt = current_blood - 2 // Bit of a safety net; it's impossible to add blood if there's not blood already in the vessel. return vessel.remove_reagent(REAGENT_ID_BLOOD,amt) /**************************************************** 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) var/datum/reagent/B = get_blood(container.reagents) if(!B) B = new /datum/reagent/blood B.holder = container B.volume += amount //set reagent data B.data["donor"] = src if(!B.data["viruses"]) B.data["viruses"] = list() for(var/datum/disease/D in GetViruses()) B.data["viruses"] |= D.Copy() if(!B.data["resistances"]) B.data["resistances"] = list() if(B.data["resistances"]) B.data["resistances"] |= GetResistances() B.data["blood_DNA"] = copytext(src.dna.unique_enzymes,1,0) B.data["blood_type"] = copytext(src.dna.b_type,1,0) B.data["changeling"] = (!isnull(mind) && is_changeling(mind)) || species?.ambulant_blood || HAS_TRAIT(src, TRAIT_REDSPACE_CORRUPTED) // Putting this here due to return shenanigans. if(ishuman(src)) var/mob/living/carbon/human/H = src B.data["blood_colour"] = H.species.get_blood_colour(H) B.color = B.data["blood_colour"] var/list/temp_chem = list() for(var/datum/reagent/R in src.reagents.reagent_list) temp_chem += R.id temp_chem[R.id] = R.volume B.data["trace_chem"] = list2params(temp_chem) return B //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(O_HEART)) return null if(vessel.get_reagent_amount(REAGENT_ID_BLOOD) < max(amount, BLOOD_MINIMUM_STOP_PROCESS)) return null . = ..() remove_blood(amount) // Removes blood if human //Transfers blood from container ot vessels /mob/living/carbon/proc/inject_blood(var/datum/reagent/blood/injected, var/amount) if (!injected || !istype(injected)) return var/list/sniffles = injected.data["viruses"] for(var/ID in sniffles) var/datum/disease/D = ID if(D.spread_flags & (DISEASE_SPREAD_SPECIAL | DISEASE_SPREAD_NON_CONTAGIOUS)) // Special/Non-Contagious stay in the blood, but they won't spread continue ForceContractDisease(D) if (injected.data["resistances"] && prob(5)) antibodies |= injected.data["resistances"] if (injected.data[REAGENT_ID_ANTIBODIES] && prob(5)) antibodies |= injected.data[REAGENT_ID_ANTIBODIES] var/list/chems = list() chems = params2list(injected.data["trace_chem"]) for(var/C in chems) src.reagents.add_reagent(C, (text2num(chems[C]) / species.blood_volume) * amount)//adds trace chemicals to owner's blood reagents.update_total() //Transfers blood from reagents to vessel, respecting blood types compatability. /mob/living/carbon/human/inject_blood(var/datum/reagent/blood/injected, var/amount) if(!should_have_organ(O_HEART)) reagents.add_reagent(REAGENT_ID_BLOOD, amount, injected.data) reagents.update_total() return var/datum/reagent/blood/our = get_blood(vessel) if (!injected) return if(!our) log_runtime("[src] has no blood reagent, proceeding with fallback reinitialization.") var/vessel_old = vessel vessel = null qdel(vessel_old) make_blood(amount) if(!vessel) log_runtime("Failed to re-initialize blood datums on [src]!") return if(vessel.total_volume < species.blood_volume) vessel.add_reagent(REAGENT_ID_BLOOD, species.blood_volume - vessel.total_volume) else if(vessel.total_volume > species.blood_volume) vessel.maximum_volume = species.blood_volume fixblood() our = get_blood(vessel) if(!our) log_runtime("Failed to re-initialize blood datums on [src]!") return if((is_changeling(src) || HAS_TRAIT(src, TRAIT_REDSPACE_CORRUPTED))) //Changelings don't reject blood! vessel.add_reagent(REAGENT_ID_BLOOD, amount, injected.data) vessel.update_total() else if(blood_incompatible(injected.data["blood_type"],our.data["blood_type"],injected.data["species"],our.data["species"]) ) reagents.add_reagent(REAGENT_ID_TOXIN,amount * 0.5) reagents.update_total() else vessel.add_reagent(REAGENT_ID_BLOOD, amount, injected.data) vessel.update_total() ..() //Gets human's own blood. /mob/living/carbon/proc/get_blood(datum/reagents/container) var/datum/reagent/blood/res = locate() in container.reagent_list //Grab some blood if(res) // Make sure there's some blood at all if(res.data["donor"] != src) //If it's not theirs, then we look for theirs for(var/datum/reagent/blood/D in container.reagent_list) if(D.data["donor"] == src) return D return res /proc/blood_incompatible(donor,receiver,donor_species,receiver_species) if(!donor || !receiver) return 0 if(donor_species && receiver_species) if(donor_species != receiver_species) return 1 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) if(donor_rh && !receiver_rh) return 1 switch(receiver_antigen) if("A") if(donor_antigen != "A" && donor_antigen != "O") return 1 if("B") if(donor_antigen != "B" && donor_antigen != "O") return 1 if("O") if(donor_antigen != "O") return 1 //AB is a universal receiver. return 0 /proc/blood_splatter(var/target,var/datum/reagent/blood/source,var/large) //Vorestation Edit Start - We're not going to splatter at all because we're in something and that's silly. if(istype(source,/atom/movable)) var/atom/movable/A = source if(!isturf(A.loc)) return //VOREStation Edit End var/obj/effect/decal/cleanable/blood/B var/decal_type = /obj/effect/decal/cleanable/blood/splatter var/turf/T = get_turf(target) var/synth = 0 if(ishuman(source)) var/mob/living/carbon/human/M = source if(M.isSynthetic()) synth = 1 source = M.get_blood(M.vessel) //Someone fed us a weird source. Let's log it. if(source && !istype(source, /datum/reagent/blood)) log_runtime("A blood splatter was made using non-blood datum [source]!") source = null //Clear the source since it's invalid. Fallback to non-source behavior. // 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 && drips.len < 3) decal_type = /obj/effect/decal/cleanable/blood/drip // Find a blood decal or create a new one. B = locate(decal_type) in T if(!B) B = new decal_type(T) var/obj/effect/decal/cleanable/blood/drip/drop = B if(istype(drop) && drips && drips.len && !large) drop.add_overlay(drips) drop.drips |= drips // If there's no data to copy, call it quits here. if(!istype(source)) return B // Update appearance. if(source.data["blood_colour"]) B.basecolor = source.data["blood_colour"] B.synthblood = synth B.update_icon() if(source.data["blood_name"]) B.name = source.data["blood_name"] // Update blood information. if(source.data["blood_DNA"]) var/list/new_data = list() if(source.data["blood_type"]) new_data[source.data["blood_DNA"]] = source.data["blood_type"] else new_data[source.data["blood_DNA"]] = "O+" B.init_forensic_data().merge_blooddna(null,new_data) // Update virus information. if(source.data["viruses"]) B.viruses = source.data["viruses"] B.fluorescent = 0 B.invisibility = INVISIBILITY_NONE return B #undef BLOOD_MINIMUM_STOP_PROCESS