There will be (colorful) blood: datumizes bloodtypes, greyscales blood sprites, and fixes a lot of inconsistencies with gibs and forensic data (#90593)

## About The Pull Request


This PR:

- Converts all of the blood types into their own datums, which can be
set up to have their own colors, descriptions, and other fun unique
properties. For example, the clown blood that is constantly randomizing
itself.

- Converts all the blood decals into greyscale, which in turn eliminates
the need for separate xeno sprites. They both use the same ones now.

- Audit of blood splatters/gibs/bodyparts/organs to make sure that they
are getting the correct forensic data applied to them.

- For the admins: Adds a clown blood smite.

My primary goal with was to make the appearance of the new sprites look
almost indistinguishable to the original ones.

I consider this a "first pass", as in there are still some further
refactors I would like to do on the backend side, but am satisfied with
it enough to push it forward as a first step towards a better blood
system! I didn't want to do too much at once because of A) fatigue and
B) easier to test things to make sure I'm not breaking something
important this way.

This has been test-merged on Nova for over a week now and has been going
great, so I finally got around to upstreaming the bones to TG. Although
I did test it a bit you may want to TM it just in case I missed some
things when copying it over.
This commit is contained in:
Bloop
2025-04-28 01:57:59 -04:00
committed by GitHub
parent 0002db7ae3
commit 3d01e86e29
101 changed files with 1007 additions and 364 deletions

View File

@@ -1738,7 +1738,7 @@
},
/obj/item/ammo_casing/spent,
/obj/effect/decal/cleanable/xenoblood/xgibs{
icon_state = "xtracks";
icon_state = "tracks";
dir = 1
},
/turf/open/floor/mineral/plastitanium,

View File

@@ -373,7 +373,7 @@
dir = 1
},
/obj/effect/decal/cleanable/xenoblood{
icon_state = "xfloor4"
icon_state = "floor4"
},
/turf/open/misc/asteroid{
icon_state = "asteroid3"
@@ -523,7 +523,7 @@
"OJ" = (
/obj/structure/statue/bone/rib,
/obj/effect/decal/cleanable/xenoblood{
icon_state = "xfloor5"
icon_state = "floor5"
},
/turf/open/misc/asteroid{
icon_state = "asteroid0"
@@ -541,7 +541,7 @@
"PS" = (
/obj/structure/statue/bone/rib,
/obj/effect/decal/cleanable/xenoblood{
icon_state = "xfloor7"
icon_state = "floor7"
},
/obj/effect/turf_decal/trimline/dark/warning{
dir = 1
@@ -670,7 +670,7 @@
dir = 4
},
/obj/effect/decal/cleanable/xenoblood{
icon_state = "xfloor5"
icon_state = "floor5"
},
/obj/effect/turf_decal/trimline/dark/warning{
dir = 6

View File

@@ -216,6 +216,12 @@
#define COLOR_SAMPLE_BROWN "#91542d"
#define COLOR_SAMPLE_GRAY "#5e5856"
///Colors for blood greyscale sprites
#define BLOOD_COLOR_RED "#FF291E" // This is lighter than the blood reagent itself because for the greyscale to look the same as before it needs to be the lightest parts of the sprite.
#define BLOOD_COLOR_XENO "#D5FF2C"
#define BLOOD_COLOR_OIL "#262626"
#define BLOOD_COLOR_BLACK "#2C0903"
///Main colors for UI themes
#define COLOR_THEME_MIDNIGHT "#6086A0"
#define COLOR_THEME_PLASMAFIRE "#FFB200"

View File

@@ -32,6 +32,28 @@
/// Temperature at which blood loss and regen stops. [/mob/living/carbon/human/proc/handle_blood]
#define BLOOD_STOP_TEMP 225
// Bloodtype defines
#define BLOOD_TYPE_A_MINUS "A-"
#define BLOOD_TYPE_A_PLUS "A+"
#define BLOOD_TYPE_B_MINUS "B-"
#define BLOOD_TYPE_B_PLUS "B+"
#define BLOOD_TYPE_AB_MINUS "AB-"
#define BLOOD_TYPE_AB_PLUS "AB+"
#define BLOOD_TYPE_O_MINUS "O-"
#define BLOOD_TYPE_O_PLUS "O+"
#define BLOOD_TYPE_UNIVERSAL "U"
#define BLOOD_TYPE_LIZARD "L"
#define BLOOD_TYPE_VAMPIRE "V"
#define BLOOD_TYPE_ANIMAL "Y-"
#define BLOOD_TYPE_ETHEREAL "LE"
#define BLOOD_TYPE_TOX "TOX"
#define BLOOD_TYPE_OIL "Oil"
#define BLOOD_TYPE_MEAT "MT-"
#define BLOOD_TYPE_CLOWN "C"
#define BLOOD_TYPE_XENO "X*"
#define BLOOD_TYPE_H2O "H2O"
#define BLOOD_TYPE_SNAIL "S"
//Sizes of mobs, used by mob/living/var/mob_size
#define MOB_SIZE_TINY 0
#define MOB_SIZE_SMALL 1

View File

@@ -56,6 +56,8 @@
#define BODYPART_IMPLANTED (1<<2)
/// Bodypart never displays as a husk
#define BODYPART_UNHUSKABLE (1<<3)
/// Bodypart has never been added to a mob
#define BODYPART_VIRGIN (1<<4)
// Bodypart change blocking flags
///Bodypart does not get replaced during set_species()

View File

@@ -379,3 +379,6 @@ rough example of the "cone" made by the 3 dirs checked
"x" = icon_width > ICON_SIZE_X && pixel_x != 0 ? (icon_width - ICON_SIZE_X) * 0.5 : 0,
"y" = icon_height > ICON_SIZE_Y && pixel_y != 0 ? (icon_height - ICON_SIZE_Y) * 0.5 : 0,
)
/// Helper for easily adding blood from INSIDE a mob to an atom (NOT blood ON the mob)
#define add_mob_blood(from_who) add_blood_DNA(from_who.get_blood_dna_list())

View File

@@ -6,8 +6,19 @@
/// Two mobs one is facing a person, but the other is perpendicular
#define FACING_INIT_FACING_TARGET_TARGET_FACING_PERPENDICULAR 3 //Do I win the most informative but also most stupid define award?
/proc/random_blood_type()
return pick(4;"O-", 36;"O+", 3;"A-", 28;"A+", 1;"B-", 20;"B+", 1;"AB-", 5;"AB+")
/// Returns one of the human blood types at random, weighted by their rarity
/proc/random_human_blood_type()
return get_blood_type(pick_weight(
list(
BLOOD_TYPE_O_MINUS = 4,
BLOOD_TYPE_O_PLUS = 36,
BLOOD_TYPE_A_MINUS = 3,
BLOOD_TYPE_A_PLUS = 28,
BLOOD_TYPE_B_MINUS= 1,
BLOOD_TYPE_B_PLUS = 20,
BLOOD_TYPE_AB_MINUS = 1,
BLOOD_TYPE_AB_PLUS = 5,
)))
/proc/random_eye_color()
switch(pick(20;"brown",20;"hazel",20;"grey",15;"blue",15;"green",1;"amber",1;"albino"))

View File

@@ -113,6 +113,18 @@ GLOBAL_LIST_INIT(language_types_by_name, init_language_types_by_name())
lang_list[initial(lang_type.name)] = lang_type
return lang_list
// A list of all the possible blood types, keyed by id (which is just the name in most cases)
GLOBAL_LIST_INIT(blood_types, init_blood_types())
/// Initializes the list of blood type singletons
/proc/init_blood_types()
. = list()
for(var/datum/blood_type/blood_type_path as anything in subtypesof(/datum/blood_type))
if(blood_type_path::root_abstract_type == blood_type_path) // Don't instantiate abstract blood types
continue
var/datum/blood_type/new_type = new blood_type_path()
.[new_type.id] = new_type
/// An assoc list of species IDs to type paths
GLOBAL_LIST_INIT(species_list, init_species_list())
/// List of all species prototypes to reference, assoc [type] = prototype

View File

@@ -352,7 +352,21 @@
attacking_item.add_mob_blood(src)
add_splatter_floor(get_turf(src))
if(get_dist(attacker, src) <= 1)
attacker.add_mob_blood(src)
if(ishuman(attacker))
var/bloodied_things = ITEM_SLOT_GLOVES
if(damage_done >= 20 || (damage_done >= 15 && prob(25)))
bloodied_things |= ITEM_SLOT_ICLOTHING|ITEM_SLOT_OCLOTHING
if(prob(33) && damage_done >= 10)
bloodied_things |= ITEM_SLOT_FEET
if(prob(33) && damage_done >= 24) // fireaxe damage, because heeeeere's johnny
bloodied_things |= ITEM_SLOT_MASK
if(prob(33) && damage_done >= 30) // esword damage
bloodied_things |= ITEM_SLOT_HEAD
var/mob/living/carbon/human/human_attacker = attacker
human_attacker.add_blood_DNA_to_items(get_blood_dna_list(), bloodied_things)
else
attacker.add_mob_blood(src)
return TRUE
return FALSE
@@ -369,15 +383,10 @@
switch(hit_zone)
if(BODY_ZONE_HEAD)
if(.)
if(wear_mask)
wear_mask.add_mob_blood(src)
update_worn_mask()
if(head)
head.add_mob_blood(src)
update_worn_head()
if(glasses && prob(33))
glasses.add_mob_blood(src)
update_worn_glasses()
var/bloodied_things = ITEM_SLOT_MASK|ITEM_SLOT_HEAD
if(prob(33))
bloodied_things |= ITEM_SLOT_EYES
add_blood_DNA_to_items(get_blood_dna_list(), bloodied_things)
if(!attacking_item.get_sharpness() && !HAS_TRAIT(src, TRAIT_HEAD_INJURY_BLOCKED) && attacking_item.damtype == BRUTE)
if(prob(damage_done))
@@ -402,12 +411,7 @@
if(BODY_ZONE_CHEST)
if(.)
if(wear_suit)
wear_suit.add_mob_blood(src)
update_worn_oversuit()
if(w_uniform)
w_uniform.add_mob_blood(src)
update_worn_undersuit()
add_blood_DNA_to_items(get_blood_dna_list(), ITEM_SLOT_ICLOTHING|ITEM_SLOT_OCLOTHING)
if(stat == CONSCIOUS && !attacking_item.get_sharpness() && !HAS_TRAIT(src, TRAIT_BRAWLING_KNOCKDOWN_BLOCKED) && attacking_item.damtype == BRUTE)
if(prob(damage_done))

View File

@@ -26,6 +26,7 @@
target_dir_change = FALSE,
transfer_blood_dna = FALSE,
max_blood = INFINITY,
blood_dna_info = list("meaty DNA" = get_blood_type(BLOOD_TYPE_MEAT))
)
if(!ismovable(parent))

View File

@@ -32,8 +32,13 @@
/datum/component/bloody_spreader/proc/spread_yucky_blood(atom/parent, atom/bloody_fool)
SIGNAL_HANDLER
bloody_fool.add_blood_DNA(blood_dna, diseases)
blood_left--
if(ishuman(bloody_fool))
var/mob/living/carbon/human/bloody_fool_human = bloody_fool
bloody_fool_human.add_blood_DNA_to_items(blood_dna, ITEM_SLOT_GLOVES)
blood_left -= 3
else
bloody_fool.add_blood_DNA(blood_dna, diseases)
blood_left -= 1
if(blood_left <= 0)
qdel(src)

View File

@@ -111,8 +111,17 @@
if(HAS_TRAIT(parent_atom, TRAIT_LIGHT_STEP) || (wielder && HAS_TRAIT(wielder, TRAIT_LIGHT_STEP))) //the character is agile enough to don't mess their clothing and hands just from one blood splatter at floor
return TRUE
parent_atom.add_blood_DNA(GET_ATOM_BLOOD_DNA(pool))
update_icon()
if(ishuman(parent))
var/bloody_slots = ITEM_SLOT_OCLOTHING|ITEM_SLOT_ICLOTHING|ITEM_SLOT_FEET
var/mob/living/carbon/human/to_bloody = parent
if(to_bloody.body_position == LYING_DOWN)
bloody_slots |= ITEM_SLOT_HEAD|ITEM_SLOT_MASK|ITEM_SLOT_GLOVES
to_bloody.add_blood_DNA_to_items(GET_ATOM_BLOOD_DNA(pool), bloody_slots)
return
var/atom/to_bloody = parent
to_bloody.add_blood_DNA(GET_ATOM_BLOOD_DNA(pool))
/**
* Find a blood decal on a turf that matches our last_blood_state
@@ -275,7 +284,8 @@
if(footprint_sprite)
src.footprint_sprite = footprint_sprite
if(!bloody_feet)
bloody_feet = mutable_appearance('icons/effects/blood.dmi', "shoeblood", SHOES_LAYER)
bloody_feet = mutable_appearance('icons/effects/blood.dmi', "shoeblood", SHOES_LAYER, appearance_flags = RESET_COLOR)
bloody_feet.color = BLOOD_COLOR_RED
RegisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT, PROC_REF(on_clean))
RegisterSignal(parent, COMSIG_STEP_ON_BLOOD, PROC_REF(on_step_blood))
@@ -305,7 +315,6 @@
FP.species_types |= affecting.limb_id
break
/datum/component/bloodysoles/feet/is_obscured()
if(wielder.shoes)
return TRUE
@@ -323,6 +332,14 @@
..()
/datum/component/bloodysoles/feet/share_blood(obj/effect/decal/cleanable/pool)
. = ..()
if(.)
return
bloody_feet.color = get_blood_dna_color(GET_ATOM_BLOOD_DNA(pool))
update_icon()
/datum/component/bloodysoles/feet/proc/unequip_shoecover(datum/source)
SIGNAL_HANDLER

View File

@@ -55,7 +55,8 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block())
var/unique_enzymes
///Stores the hashed values of traits such as skin tones, hair style, and gender
var/unique_identity
var/blood_type
/// The singleton blood type
var/datum/blood_type/blood_type
///The type of mutant race the player is if applicable (i.e. potato-man)
var/datum/species/species = new /datum/species/human
/// Assoc list of feature keys to their value
@@ -109,7 +110,7 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block())
return
destination.dna.unique_enzymes = unique_enzymes
destination.dna.unique_identity = unique_identity
destination.dna.blood_type = blood_type
destination.set_blood_type(blood_type)
destination.dna.unique_features = unique_features
destination.dna.features = features.Copy()
destination.dna.real_name = real_name
@@ -428,7 +429,7 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block())
&& real_name == target_dna.real_name \
&& species.type == target_dna.species.type \
&& compare_list(features, target_dna.features) \
&& blood_type == target_dna.blood_type \
&& blood_type.type == target_dna.blood_type.type \
)
return TRUE
@@ -473,7 +474,7 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block())
* * create_mutation_blocks - If true, generate_dna_blocks is called, which is used to set up mutation blocks (what a mob can naturally mutate).
* * randomize_features - If true, all entries in the features list will be randomized.
*/
/datum/dna/proc/initialize_dna(newblood_type, create_mutation_blocks = TRUE, randomize_features = TRUE)
/datum/dna/proc/initialize_dna(newblood_type = random_human_blood_type(), create_mutation_blocks = TRUE, randomize_features = TRUE)
if(newblood_type)
blood_type = newblood_type
if(create_mutation_blocks) //I hate this
@@ -588,7 +589,7 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block())
dna.generate_unique_enzymes()
if(newblood_type)
dna.blood_type = newblood_type
set_blood_type(newblood_type)
if(unique_identity)
dna.unique_identity = unique_identity

View File

@@ -23,7 +23,7 @@
REMOVE_KEEP_TOGETHER(source_item, type)
return ..()
/datum/element/decal/blood/generate_appearance(_icon, _icon_state, _dir, _plane, _layer, _color, _alpha, _smoothing, source)
/datum/element/decal/blood/generate_appearance(_icon, _icon_state, _dir, _plane, _layer, _color = BLOOD_COLOR_RED, _alpha, _smoothing, source)
var/obj/item/as_item = source
ADD_KEEP_TOGETHER(as_item, type)
var/icon/icon_for_size = icon(as_item.icon, as_item.icon_state)

View File

@@ -52,7 +52,7 @@
source.AddComponent(
/datum/component/bloody_spreader,\
blood_left = (protein_count + fat_count) * 0.3,\
blood_dna = list("meaty DNA" = "MT-"),\
blood_dna = list("meaty DNA" = get_blood_type(BLOOD_TYPE_MEAT)),\
diseases = null,\
)

View File

@@ -13,10 +13,12 @@
/datum/quirk/blooddeficiency/add(client/client_source)
RegisterSignal(quirk_holder, COMSIG_HUMAN_ON_HANDLE_BLOOD, PROC_REF(lose_blood))
RegisterSignal(quirk_holder, COMSIG_SPECIES_GAIN, PROC_REF(update_mail))
var/mob/living/carbon/human/human_holder = quirk_holder
if(!istype(human_holder))
return
update_mail(new_species = human_holder.dna.species)
RegisterSignal(quirk_holder, COMSIG_SPECIES_GAIN, PROC_REF(update_mail))
/datum/quirk/blooddeficiency/remove()
UnregisterSignal(quirk_holder, list(COMSIG_HUMAN_ON_HANDLE_BLOOD, COMSIG_SPECIES_GAIN))
@@ -44,17 +46,17 @@
/datum/quirk/blooddeficiency/proc/update_mail(datum/source, datum/species/new_species, datum/species/old_species, pref_load, regenerate_icons)
SIGNAL_HANDLER
mail_goodies.Cut()
if(isnull(new_species.exotic_blood) && isnull(new_species.exotic_bloodtype))
if(TRAIT_NOBLOOD in new_species.inherent_traits)
if(TRAIT_NOBLOOD in new_species.inherent_traits) // jellypeople have both exotic_blood and TRAIT_NOBLOOD
mail_goodies.Cut()
return
mail_goodies += /obj/item/reagent_containers/blood/o_minus
var/mob/living/carbon/human/human_holder = quirk_holder
var/datum/blood_type/blood_type = human_holder.dna.blood_type
if(isnull(blood_type))
return
for(var/obj/item/reagent_containers/blood/blood_bag as anything in typesof(/obj/item/reagent_containers/blood))
var/right_blood_type = !isnull(new_species.exotic_bloodtype) && initial(blood_bag.blood_type) == new_species.exotic_bloodtype
var/right_blood_reagent = !isnull(new_species.exotic_blood) && initial(blood_bag.unique_blood) == new_species.exotic_blood
if(right_blood_type || right_blood_reagent)
mail_goodies += blood_bag
if(blood_bag::blood_type == blood_type.name)
mail_goodies = list(blood_bag)
return

View File

@@ -15,3 +15,31 @@
var/evil_policy = get_policy("[type]") || "Please note that while you may be [LOWER_TEXT(name)], this does NOT give you any additional right to attack people or cause chaos."
// We shouldn't need this, but it prevents people using it as a dumb excuse in ahelps.
to_chat(quirk_holder, span_big(span_info(evil_policy)))
RegisterSignal(quirk_holder, COMSIG_SPECIES_GAIN, PROC_REF(make_blood_evil))
/datum/quirk/evil/add(client/client_source)
var/mob/living/carbon/human/human_holder = quirk_holder
if(!istype(human_holder))
return
make_blood_evil(new_species = human_holder.dna.species)
/datum/quirk/evil/remove()
UnregisterSignal(quirk_holder, list(COMSIG_SPECIES_GAIN))
/// Get a dynamically generated blood type based off the mob's old blood type and make it 'evil'. Needs to happen whenever the species changes.
/datum/quirk/evil/proc/make_blood_evil(datum/source, datum/species/new_species, datum/species/old_species, pref_load, regenerate_icons)
SIGNAL_HANDLER
var/mob/living/carbon/human/human_holder = quirk_holder
if(!istype(human_holder))
return
// Try to find a corresponding evil blood type for this
var/datum/blood_type/new_blood_type = get_blood_type("[human_holder.dna.blood_type.id]_but_evil")
if(isnull(new_blood_type)) // this blood type doesn't exist yet in the global list, so make a new one
new_blood_type = new /datum/blood_type/evil(human_holder.dna.blood_type, human_holder.dna.blood_type.compatible_types)
GLOB.blood_types[new_blood_type.id] = new_blood_type
human_holder.set_blood_type(new_blood_type)
if(new_species.exotic_bloodtype)
new_species.exotic_bloodtype = new_blood_type

View File

@@ -118,7 +118,7 @@ GLOBAL_DATUM_INIT(manifest, /datum/manifest, new)
var/datum/record/locked/lockfile = new(
age = person.age,
blood_type = record_dna.blood_type,
blood_type = record_dna.blood_type.name,
character_appearance = character_appearance,
dna_string = record_dna.unique_enzymes,
fingerprint = md5(record_dna.unique_identity),
@@ -135,7 +135,7 @@ GLOBAL_DATUM_INIT(manifest, /datum/manifest, new)
new /datum/record/crew(
age = person.age,
blood_type = record_dna.blood_type,
blood_type = record_dna.blood_type.name,
character_appearance = character_appearance,
dna_string = record_dna.unique_enzymes,
fingerprint = md5(record_dna.unique_identity),

View File

@@ -490,44 +490,23 @@
/mob/living/proc/get_blood_dna_list()
if(get_blood_id() != /datum/reagent/blood)
return
return list("ANIMAL DNA" = "Y-")
return list("ANIMAL DNA" = get_blood_type(BLOOD_TYPE_ANIMAL))
///Get the mobs dna list
/mob/living/carbon/get_blood_dna_list()
if(get_blood_id() != /datum/reagent/blood)
return
var/list/blood_dna = list()
if(dna)
blood_dna[dna.unique_enzymes] = dna.blood_type
else
blood_dna["UNKNOWN DNA"] = "X*"
blood_dna["UNKNOWN DNA"] = get_blood_type(BLOOD_TYPE_XENO)
return blood_dna
/mob/living/carbon/alien/get_blood_dna_list()
return list("UNKNOWN DNA" = "X*")
return list("UNKNOWN DNA" = get_blood_type(BLOOD_TYPE_XENO))
/mob/living/silicon/get_blood_dna_list()
return
///to add a mob's dna info into an object's blood_dna list.
/atom/proc/transfer_mob_blood_dna(mob/living/injected_mob)
// Returns 0 if we have that blood already
var/new_blood_dna = injected_mob.get_blood_dna_list()
if(!new_blood_dna)
return FALSE
var/old_length = GET_ATOM_BLOOD_DNA_LENGTH(src)
add_blood_DNA(new_blood_dna)
if(GET_ATOM_BLOOD_DNA_LENGTH(src) == old_length)
return FALSE
return TRUE
///to add blood from a mob onto something, and transfer their dna info
/atom/proc/add_mob_blood(mob/living/injected_mob)
var/list/blood_dna = injected_mob.get_blood_dna_list()
if(!blood_dna)
return FALSE
return add_blood_DNA(blood_dna)
///Is this atom in space
/atom/proc/isinspace()
if(isspaceturf(get_turf(src)))

View File

@@ -1750,7 +1750,7 @@
scanner_occupant.real_name = buffer_slot["name"]
scanner_occupant.name = buffer_slot["name"]
scanner_occupant.dna.unique_enzymes = buffer_slot["UE"]
scanner_occupant.dna.blood_type = buffer_slot["blood_type"]
scanner_occupant.set_blood_type(buffer_slot["blood_type"])
scanner_occupant.apply_status_effect(/datum/status_effect/genetic_damage, damage_increase)
scanner_occupant.domutcheck()
return TRUE
@@ -1768,7 +1768,7 @@
scanner_occupant.real_name = buffer_slot["name"]
scanner_occupant.name = buffer_slot["name"]
scanner_occupant.dna.unique_enzymes = buffer_slot["UE"]
scanner_occupant.dna.blood_type = buffer_slot["blood_type"]
scanner_occupant.set_blood_type(buffer_slot["blood_type"])
scanner_occupant.apply_status_effect(/datum/status_effect/genetic_damage, damage_increase)
scanner_occupant.domutcheck()
return TRUE

View File

@@ -123,15 +123,7 @@
data["patient"]["stat"] = "Dead"
data["patient"]["statstate"] = "bad"
data["patient"]["health"] = patient.health
// check here to see if the patient has standard blood reagent, or special blood (like how ethereals bleed liquid electricity) to show the proper name in the computer
var/blood_id = patient.get_blood_id()
if(blood_id == /datum/reagent/blood)
data["patient"]["blood_type"] = patient.dna?.blood_type
else
var/datum/reagent/special_blood = GLOB.chemical_reagents_list[blood_id]
data["patient"]["blood_type"] = special_blood ? special_blood.name : blood_id
data["patient"]["blood_type"] = patient.dna?.blood_type.name
data["patient"]["maxHealth"] = patient.maxHealth
data["patient"]["minHealth"] = HEALTH_THRESHOLD_DEAD
data["patient"]["bruteLoss"] = patient.getBruteLoss()

View File

@@ -143,7 +143,7 @@
return FALSE
target.age = 18
target.blood_type = pick(list("A+", "A-", "B+", "B-", "O+", "O-", "AB+", "AB-"))
target.blood_type = pick(list(BLOOD_TYPE_A_PLUS, BLOOD_TYPE_A_MINUS, BLOOD_TYPE_B_PLUS, BLOOD_TYPE_B_MINUS, BLOOD_TYPE_O_PLUS, BLOOD_TYPE_O_MINUS, BLOOD_TYPE_AB_PLUS, BLOOD_TYPE_AB_MINUS))
target.dna_string = "Unknown"
target.gender = "Unknown"
target.major_disabilities = ""

View File

@@ -184,6 +184,10 @@
. = ..()
icon_state = "datadisk[rand(0,7)]"
add_overlay("datadisk_gene")
if(length(genetic_makeup_buffer))
var/datum/blood_type = genetic_makeup_buffer["blood_type"]
if(blood_type)
blood_type = get_blood_type(blood_type) || random_human_blood_type()
/obj/item/disk/data/debug
name = "\improper CentCom DNA disk"

View File

@@ -237,7 +237,7 @@
var/bleed_status = "Patient is not currently bleeding."
var/blood_status = " Patient either has no blood, or does not require it to function."
var/blood_percent = round((patient.blood_volume / BLOOD_VOLUME_NORMAL)*100)
var/blood_type = patient.dna.blood_type
var/datum/blood_type/blood_type = patient.dna.blood_type
var/blood_warning = " "
var/blood_alcohol = patient.get_blood_alcohol_content()
@@ -254,7 +254,7 @@
blood_warning = " Patient has low blood levels. Seek a large meal, or iron supplements."
if(blood_percent <= 60)
blood_warning = " Patient has DANGEROUSLY low blood levels. Seek a blood transfusion, iron supplements, or saline glucose immedietly. Ignoring treatment may lead to death!"
blood_status = "Patient blood levels are currently reading [blood_percent]%. Patient has [ blood_type] type blood. [blood_warning]"
blood_status = "Patient blood levels are currently reading [blood_percent]%. Patient has [ blood_type.name] type blood. [blood_warning]"
var/trauma_status = "Patient is free of unique brain trauma."
var/brain_loss = patient.get_organ_loss(ORGAN_SLOT_BRAIN)

View File

@@ -100,9 +100,22 @@
var/is_powered = !(machine_stat & (BROKEN|NOPOWER))
if(safety_mode)
is_powered = FALSE
icon_state = icon_name + "[is_powered]" + "[(bloody ? "bld" : "")]" // add the blood tag at the end
icon_state = icon_name + "[is_powered]"
return ..()
/obj/machinery/recycler/update_overlays()
. = ..()
if(!bloody)
return
var/mutable_appearance/blood_overlay = mutable_appearance(icon, "[icon_state]bld", appearance_flags = RESET_COLOR|KEEP_APART)
var/blood_dna = GET_ATOM_BLOOD_DNA(src)
if(blood_dna)
blood_overlay.color = get_blood_dna_color(blood_dna)
else
blood_overlay.color = BLOOD_COLOR_RED
. += blood_overlay
/obj/machinery/recycler/CanAllowThrough(atom/movable/mover, border_dir)
. = ..()
if(!anchored)
@@ -233,26 +246,26 @@
safety_mode = FALSE
update_appearance()
/obj/machinery/recycler/proc/crush_living(mob/living/L)
L.forceMove(loc)
/obj/machinery/recycler/proc/crush_living(mob/living/living_mob)
living_mob.forceMove(loc)
if(issilicon(L))
if(issilicon(living_mob))
playsound(src, 'sound/items/tools/welder.ogg', 50, TRUE)
else
playsound(src, 'sound/effects/splat.ogg', 50, TRUE)
if(iscarbon(L))
if(L.stat == CONSCIOUS)
L.say("ARRRRRRRRRRRGH!!!", forced="recycler grinding")
add_mob_blood(L)
if(iscarbon(living_mob))
if(living_mob.stat == CONSCIOUS)
living_mob.say("ARRRRRRRRRRRGH!!!", forced= "recycler grinding")
add_mob_blood(living_mob)
if(!bloody && !issilicon(L))
if(!bloody && !issilicon(living_mob))
bloody = TRUE
update_appearance()
// Instantly lie down, also go unconscious from the pain, before you die.
L.Unconscious(100)
L.adjustBruteLoss(crush_damage)
living_mob.Unconscious(100)
living_mob.adjustBruteLoss(crush_damage)
update_appearance()
/obj/machinery/recycler/on_deconstruction(disassembled)
safety_mode = TRUE

View File

@@ -150,6 +150,9 @@
return null
/obj/effect/decal/cleanable/blood/get_blood_color()
return color || ..()
/obj/effect/decal/cleanable/proc/handle_merge_decal(obj/effect/decal/cleanable/merger)
if(!merger)
return

View File

@@ -4,16 +4,21 @@
name = "xeno blood"
desc = "It's green and acidic. It looks like... <i>blood?</i>"
icon = 'icons/effects/blood.dmi'
icon_state = "xfloor1"
random_icon_states = list("xfloor1", "xfloor2", "xfloor3", "xfloor4", "xfloor5", "xfloor6", "xfloor7")
icon_state = "floor1"
random_icon_states = list("floor1", "floor2", "floor3", "floor4", "floor5", "floor6", "floor7")
bloodiness = BLOOD_AMOUNT_PER_DECAL
blood_state = BLOOD_STATE_XENO
beauty = -250
clean_type = CLEAN_TYPE_BLOOD
/obj/effect/decal/cleanable/xenoblood/add_blood_DNA(list/blood_DNA, no_visuals = FALSE)
. = ..()
if(!no_visuals && length(blood_DNA))
color = get_blood_dna_color(blood_DNA)
/obj/effect/decal/cleanable/xenoblood/Initialize(mapload)
. = ..()
add_blood_DNA(list("UNKNOWN DNA" = "X*"))
add_blood_DNA(list("UNKNOWN DNA" = get_blood_type(BLOOD_TYPE_XENO)))
/obj/effect/decal/cleanable/xenoblood/xsplatter
random_icon_states = list("xgibbl1", "xgibbl2", "xgibbl3", "xgibbl4", "xgibbl5")
@@ -33,6 +38,13 @@
/obj/effect/decal/cleanable/xenoblood/xgibs/Initialize(mapload)
. = ..()
RegisterSignal(src, COMSIG_MOVABLE_PIPE_EJECTING, PROC_REF(on_pipe_eject))
update_appearance(UPDATE_OVERLAYS)
/obj/effect/decal/cleanable/xenoblood/xgibs/update_overlays()
. = ..()
var/mutable_appearance/gib_overlay = mutable_appearance(icon, "[icon_state]-overlay", appearance_flags = KEEP_APART|RESET_COLOR)
if(gib_overlay)
. += gib_overlay
/obj/effect/decal/cleanable/xenoblood/xgibs/proc/streak(list/directions, mapload=FALSE)
SEND_SIGNAL(src, COMSIG_GIBS_STREAK, directions)
@@ -45,7 +57,8 @@
for (var/i in 1 to range)
var/turf/my_turf = get_turf(src)
if(!isgroundlessturf(my_turf) || GET_TURF_BELOW(my_turf))
new /obj/effect/decal/cleanable/xenoblood/xsplatter(my_turf)
var/obj/effect/decal/cleanable/xenoblood/xsplatter/new_splatter = new /obj/effect/decal/cleanable/xenoblood/xsplatter(my_turf)
new_splatter.add_blood_DNA(GET_ATOM_BLOOD_DNA(src))
if (!step_to(src, get_step(src, direction), 0))
break
return
@@ -106,9 +119,9 @@
random_icon_states = list("xgiblarvahead", "xgiblarvatorso")
/obj/effect/decal/cleanable/blood/xtracks
icon_state = "xtracks"
icon_state = "tracks"
random_icon_states = null
/obj/effect/decal/cleanable/blood/xtracks/Initialize(mapload)
. = ..()
add_blood_DNA(list("Unknown DNA" = "X*"))
add_blood_DNA(list("Unknown DNA" = get_blood_type(BLOOD_TYPE_XENO)))

View File

@@ -8,11 +8,14 @@
bloodiness = BLOOD_AMOUNT_PER_DECAL
beauty = -100
clean_type = CLEAN_TYPE_BLOOD
color = BLOOD_COLOR_RED
var/should_dry = TRUE
var/dryname = "dried blood" //when the blood lasts long enough, it becomes dry and gets a new name
var/drydesc = "Looks like it's been here a while. Eew." //as above
var/drytime = 0
var/footprint_sprite = null
/// If we've dried already
var/has_dried
/obj/effect/decal/cleanable/blood/Initialize(mapload)
. = ..()
@@ -21,7 +24,7 @@
if(bloodiness)
start_drying()
else
dry()
dry(freshly_made = TRUE)
/obj/effect/decal/cleanable/blood/Destroy()
STOP_PROCESSING(SSobj, src)
@@ -31,6 +34,11 @@
if(world.time > drytime)
dry()
/obj/effect/decal/cleanable/blood/add_blood_DNA(list/blood_DNA, no_visuals = FALSE)
. = ..()
if(!no_visuals && length(blood_DNA))
color = get_blood_dna_color(blood_DNA)
/obj/effect/decal/cleanable/blood/proc/get_timer()
drytime = world.time + 3 MINUTES
@@ -39,19 +47,36 @@
START_PROCESSING(SSobj, src)
///This is what actually "dries" the blood. Returns true if it's all out of blood to dry, and false otherwise
/obj/effect/decal/cleanable/blood/proc/dry()
/obj/effect/decal/cleanable/blood/proc/dry(freshly_made = FALSE)
if(freshly_made)
start_drying()
return FALSE
if(bloodiness > 20)
bloodiness -= BLOOD_AMOUNT_PER_DECAL
get_timer()
return FALSE
else
name = dryname
desc = drydesc
bloodiness = 0
color = COLOR_GRAY //not all blood splatters have their own sprites... It still looks pretty nice
STOP_PROCESSING(SSobj, src)
name = dryname
has_dried = TRUE
desc = drydesc
bloodiness = 0
STOP_PROCESSING(SSobj, src)
// We're not using a matrix so we're free to use BlendRGB
if(!islist(color))
add_atom_colour(BlendRGB(color, COLOR_BLACK, 0.5), FIXED_COLOUR_PRIORITY)
return TRUE
// We're using a matrix, so we need to halve all values
var/list/blood_matrix = color
for(var/i in 1 to min(length(blood_matrix), 16))
if (length(blood_matrix) == 12 && i > 9) // Don't modify constants
break
if (length(blood_matrix) >= 16 && i % 4 == 0) // Don't modify alpha either
continue
blood_matrix[i] *= 0.5
color = blood_matrix
return TRUE
/obj/effect/decal/cleanable/blood/replace_decal(obj/effect/decal/cleanable/blood/C)
C.add_blood_DNA(GET_ATOM_BLOOD_DNA(src))
if (bloodiness)
@@ -63,7 +88,7 @@
icon_state = "floor1-old"
/obj/effect/decal/cleanable/blood/old/Initialize(mapload, list/datum/disease/diseases)
add_blood_DNA(list("Non-human DNA" = random_blood_type())) // Needs to happen before ..()
add_blood_DNA(list("Non-human DNA" = random_human_blood_type())) // Needs to happen before ..()
return ..()
/obj/effect/decal/cleanable/blood/splatter
@@ -132,17 +157,24 @@
. = ..()
AddElement(/datum/element/squish_sound)
RegisterSignal(src, COMSIG_MOVABLE_PIPE_EJECTING, PROC_REF(on_pipe_eject))
update_appearance(UPDATE_OVERLAYS)
/obj/effect/decal/cleanable/blood/gibs/Destroy()
return ..()
/obj/effect/decal/cleanable/blood/gibs/update_overlays()
. = ..()
var/mutable_appearance/gib_overlay = mutable_appearance(icon, "[icon_state]-overlay", appearance_flags = KEEP_APART|RESET_COLOR)
if(gib_overlay)
if(has_dried)
gib_overlay.color = COLOR_GRAY
. += gib_overlay
/obj/effect/decal/cleanable/blood/gibs/replace_decal(obj/effect/decal/cleanable/C)
return FALSE //Never fail to place us
/obj/effect/decal/cleanable/blood/gibs/dry()
/obj/effect/decal/cleanable/blood/gibs/dry(freshly_made = FALSE)
. = ..()
if(!.)
return
update_appearance(UPDATE_OVERLAYS)
AddComponent(/datum/component/rot, 0, 5 MINUTES, 0.7)
/obj/effect/decal/cleanable/blood/gibs/ex_act(severity, target)
@@ -166,23 +198,27 @@
var/range = pick(0, 200; 1, 150; 2, 50; 3, 17; 50) //the 3% chance of 50 steps is intentional and played for laughs.
if(!step_to(src, get_step(src, direction), 0))
return
if(mapload)
for (var/i in 1 to range)
var/turf/my_turf = get_turf(src)
if(!isgroundlessturf(my_turf) || GET_TURF_BELOW(my_turf))
new /obj/effect/decal/cleanable/blood/splatter(my_turf)
if (!step_to(src, get_step(src, direction), 0))
break
if(!mapload)
var/datum/move_loop/loop = GLOB.move_manager.move_to(src, get_step(src, direction), delay = delay, timeout = range * delay, priority = MOVEMENT_ABOVE_SPACE_PRIORITY)
RegisterSignal(loop, COMSIG_MOVELOOP_POSTPROCESS, PROC_REF(spread_movement_effects))
return
var/datum/move_loop/loop = GLOB.move_manager.move_to(src, get_step(src, direction), delay = delay, timeout = range * delay, priority = MOVEMENT_ABOVE_SPACE_PRIORITY)
RegisterSignal(loop, COMSIG_MOVELOOP_POSTPROCESS, PROC_REF(spread_movement_effects))
for (var/i in 1 to range)
var/turf/my_turf = get_turf(src)
if(!isgroundlessturf(my_turf) || GET_TURF_BELOW(my_turf))
var/obj/effect/decal/cleanable/blood/splatter/new_splatter = new /obj/effect/decal/cleanable/blood/splatter(my_turf)
new_splatter.add_blood_DNA(GET_ATOM_BLOOD_DNA(src))
if (!step_to(src, get_step(src, direction), 0))
break
/obj/effect/decal/cleanable/blood/gibs/proc/spread_movement_effects(datum/move_loop/has_target/source)
SIGNAL_HANDLER
if(NeverShouldHaveComeHere(loc))
return
new /obj/effect/decal/cleanable/blood/splatter(loc)
var/obj/effect/decal/cleanable/blood/splatter/new_splatter = new /obj/effect/decal/cleanable/blood/splatter(loc)
new_splatter.add_blood_DNA(GET_ATOM_BLOOD_DNA(src))
/obj/effect/decal/cleanable/blood/gibs/up
icon_state = "gibup1"
@@ -220,7 +256,7 @@
/obj/effect/decal/cleanable/blood/gibs/old/Initialize(mapload, list/datum/disease/diseases)
. = ..()
setDir(pick(1,2,4,8))
add_blood_DNA(list("Non-human DNA" = random_blood_type()))
add_blood_DNA(list("Non-human DNA" = random_human_blood_type()))
AddElement(/datum/element/swabable, CELL_LINE_TABLE_SLUDGE, CELL_VIRUS_TABLE_GENERIC, rand(2,4), 10)
dry()
@@ -237,7 +273,6 @@
/obj/effect/decal/cleanable/blood/drip/can_bloodcrawl_in()
return TRUE
//BLOODY FOOTPRINTS
/obj/effect/decal/cleanable/blood/footprints
name = "footprints"
@@ -318,33 +353,39 @@ GLOBAL_LIST_EMPTY(bloody_footprints_cache)
var/image/bloodstep_overlay = GLOB.bloody_footprints_cache["entered-[footprint_sprite]-[blood_state]-[Ddir]"]
if(!bloodstep_overlay)
GLOB.bloody_footprints_cache["entered-[footprint_sprite]-[blood_state]-[Ddir]"] = bloodstep_overlay = image(icon, "[blood_state]_[footprint_sprite]_enter", dir = Ddir)
bloodstep_overlay.color = color
. += bloodstep_overlay
if(exited_dirs & Ddir)
var/image/bloodstep_overlay = GLOB.bloody_footprints_cache["exited-[footprint_sprite]-[blood_state]-[Ddir]"]
if(!bloodstep_overlay)
GLOB.bloody_footprints_cache["exited-[footprint_sprite]-[blood_state]-[Ddir]"] = bloodstep_overlay = image(icon, "[blood_state]_[footprint_sprite]_exit", dir = Ddir)
bloodstep_overlay.color = color
. += bloodstep_overlay
/obj/effect/decal/cleanable/blood/footprints/examine(mob/user)
. = ..()
if((shoe_types.len + species_types.len) > 0)
. += "You recognise \the [src] as belonging to:"
for(var/sole in shoe_types)
var/obj/item/clothing/item = sole
var/article = initial(item.gender) == PLURAL ? "Some" : "A"
. += "[icon2html(initial(item.icon), user, initial(item.icon_state))] [article] <B>[initial(item.name)]</B>."
for(var/species in species_types)
// god help me
if(species == "unknown")
. += "Some <B>feet</B>."
else if(species == SPECIES_MONKEY)
. += "[icon2html('icons/mob/human/human.dmi', user, "monkey")] Some <B>monkey paws</B>."
else if(species == SPECIES_HUMAN)
. += "[icon2html('icons/mob/human/bodyparts.dmi', user, "default_human_l_leg")] Some <B>human feet</B>."
else
. += "[icon2html('icons/mob/human/bodyparts.dmi', user, "[species]_l_leg")] Some <B>[species] feet</B>."
if(length(shoe_types) + length(species_types) == 0)
return
. += "You recognise \the [src] as belonging to:"
for(var/sole in shoe_types)
var/obj/item/clothing/item = sole
var/article = initial(item.gender) == PLURAL ? "Some" : "A"
. += "[icon2html(initial(item.icon), user, initial(item.icon_state))] [article] <B>[initial(item.name)]</B>."
for(var/species in species_types)
// god help me
if(species == "unknown")
. += "Some <B>feet</B>."
else if(species == SPECIES_MONKEY)
. += "[icon2html('icons/mob/human/human.dmi', user, "monkey")] Some <B>monkey paws</B>."
else if(species == SPECIES_HUMAN)
. += "[icon2html('icons/mob/human/bodyparts.dmi', user, "default_human_l_leg")] Some <B>human feet</B>."
else
. += "[icon2html('icons/mob/human/bodyparts.dmi', user, "[species]_l_leg")] Some <B>[species] feet</B>."
/obj/effect/decal/cleanable/blood/footprints/replace_decal(obj/effect/decal/cleanable/blood/blood_decal)
if(blood_state != blood_decal.blood_state || footprint_sprite != blood_decal.footprint_sprite) //We only replace footprints of the same type as us
@@ -381,12 +422,12 @@ GLOBAL_LIST_EMPTY(bloody_footprints_cache)
if(splatter_strength)
src.splatter_strength = splatter_strength
/obj/effect/decal/cleanable/blood/hitsplatter/Destroy()
/obj/effect/decal/cleanable/blood/hitsplatter/proc/expire()
if(isturf(loc) && !skip)
playsound(src, 'sound/effects/wounds/splatter.ogg', 60, TRUE, -1)
if(blood_dna_info)
loc.add_blood_DNA(blood_dna_info)
return ..()
qdel(src)
/// Set the splatter up to fly through the air until it rounds out of steam or hits something
/obj/effect/decal/cleanable/blood/hitsplatter/proc/fly_towards(turf/target_turf, range)
@@ -402,65 +443,63 @@ GLOBAL_LIST_EMPTY(bloody_footprints_cache)
/obj/effect/decal/cleanable/blood/hitsplatter/proc/post_move(datum/move_loop/source)
SIGNAL_HANDLER
for(var/atom/iter_atom in get_turf(src))
for(var/atom/movable/iter_atom in loc)
if(hit_endpoint)
return
if(iter_atom == src || iter_atom.invisibility || iter_atom.alpha <= 0 || (isobj(iter_atom) && !iter_atom.density))
continue
if(splatter_strength <= 0)
break
if(isitem(iter_atom))
iter_atom.add_blood_DNA(blood_dna_info)
splatter_strength--
else if(ishuman(iter_atom))
var/mob/living/carbon/human/splashed_human = iter_atom
if(splashed_human.wear_suit)
splashed_human.wear_suit.add_blood_DNA(blood_dna_info)
splashed_human.update_worn_oversuit() //updates mob overlays to show the new blood (no refresh)
if(splashed_human.w_uniform)
splashed_human.w_uniform.add_blood_DNA(blood_dna_info)
splashed_human.update_worn_undersuit() //updates mob overlays to show the new blood (no refresh)
splatter_strength--
iter_atom.add_blood_DNA(blood_dna_info)
splatter_strength--
if(splatter_strength <= 0) // we used all the puff so we delete it.
qdel(src)
expire()
/obj/effect/decal/cleanable/blood/hitsplatter/proc/loop_done(datum/source)
SIGNAL_HANDLER
if(!QDELETED(src))
qdel(src)
expire()
/obj/effect/decal/cleanable/blood/hitsplatter/Bump(atom/bumped_atom)
if(!iswallturf(bumped_atom) && !istype(bumped_atom, /obj/structure/window))
qdel(src)
expire()
return
if(istype(bumped_atom, /obj/structure/window))
var/obj/structure/window/bumped_window = bumped_atom
if(!bumped_window.fulltile)
hit_endpoint = TRUE
qdel(src)
expire()
return
hit_endpoint = TRUE
if(isturf(prev_loc))
if(!isturf(prev_loc)) // This will only happen if prev_loc is not even a turf, which is highly unlikely.
abstract_move(bumped_atom)
skip = TRUE
//Adjust pixel offset to make splatters appear on the wall
if(istype(bumped_atom, /obj/structure/window))
land_on_window(bumped_atom)
else
var/obj/effect/decal/cleanable/blood/splatter/over_window/final_splatter = new(prev_loc)
final_splatter.pixel_x = (dir == EAST ? 32 : (dir == WEST ? -32 : 0))
final_splatter.pixel_y = (dir == NORTH ? 32 : (dir == SOUTH ? -32 : 0))
else // This will only happen if prev_loc is not even a turf, which is highly unlikely.
abstract_move(bumped_atom)
qdel(src)
expire()
return
abstract_move(bumped_atom)
skip = TRUE
//Adjust pixel offset to make splatters appear on the wall
if(istype(bumped_atom, /obj/structure/window))
land_on_window(bumped_atom)
return
var/obj/effect/decal/cleanable/blood/splatter/over_window/final_splatter = new(prev_loc)
final_splatter.add_blood_DNA(blood_dna_info)
final_splatter.pixel_x = (dir == EAST ? 32 : (dir == WEST ? -32 : 0))
final_splatter.pixel_y = (dir == NORTH ? 32 : (dir == SOUTH ? -32 : 0))
/// A special case for hitsplatters hitting windows, since those can actually be moved around, store it in the window and slap it in the vis_contents
/obj/effect/decal/cleanable/blood/hitsplatter/proc/land_on_window(obj/structure/window/the_window)
if(!the_window.fulltile)
return
var/obj/effect/decal/cleanable/blood/splatter/over_window/final_splatter = new
var/obj/effect/decal/cleanable/final_splatter = new /obj/effect/decal/cleanable/blood/splatter/over_window(prev_loc)
final_splatter.add_blood_DNA(blood_dna_info)
final_splatter.forceMove(the_window)
the_window.vis_contents += final_splatter
the_window.bloodied = TRUE
qdel(src)
expire()

View File

@@ -313,8 +313,9 @@
name = "insect guts"
desc = "One bug squashed. Four more will rise in its place."
icon = 'icons/effects/blood.dmi'
icon_state = "xfloor1"
random_icon_states = list("xfloor1", "xfloor2", "xfloor3", "xfloor4", "xfloor5", "xfloor6", "xfloor7")
icon_state = "floor1"
random_icon_states = list("floor1", "floor2", "floor3", "floor4", "floor5", "floor6", "floor7")
color = BLOOD_COLOR_XENO
/obj/effect/decal/cleanable/confetti
name = "confetti"

View File

@@ -9,8 +9,9 @@
var/list/gibtypes = list() //typepaths of the gib decals to spawn
var/list/gibamounts = list() //amount to spawn for each gib decal type we'll spawn.
var/list/gibdirections = list() //of lists of possible directions to spread each gib decal type towards.
var/blood_dna_info // Cached blood_dna_info in case we do not have a source mob
/obj/effect/gibspawner/Initialize(mapload, mob/living/source_mob, list/datum/disease/diseases)
/obj/effect/gibspawner/Initialize(mapload, mob/living/source_mob, list/datum/disease/diseases, blood_dna_info)
. = ..()
if(gibtypes.len != gibamounts.len)
@@ -32,14 +33,16 @@
var/list/dna_to_add //find the dna to pass to the spawned gibs. do note this can be null if the mob doesn't have blood. add_blood_DNA() has built in null handling.
if(source_mob)
if(blood_dna_info)
dna_to_add = blood_dna_info
else if(source_mob)
dna_to_add = source_mob.get_blood_dna_list() //ez pz
else if(gib_mob_type)
var/mob/living/temp_mob = new gib_mob_type(src) //generate a fake mob so that we pull the right type of DNA for the gibs.
dna_to_add = temp_mob.get_blood_dna_list()
qdel(temp_mob)
else
dna_to_add = list("Non-human DNA" = random_blood_type()) //else, generate a random bloodtype for it.
dna_to_add = list("Non-human DNA" = random_human_blood_type()) //else, generate a random bloodtype for it.
for(var/i in 1 to gibtypes.len)
@@ -66,7 +69,7 @@
gibamounts = list(2, 2, 1)
sound_vol = 40
/obj/effect/gibspawner/generic/Initialize(mapload)
/obj/effect/gibspawner/generic/Initialize(mapload, blood_dna_info)
if(!gibdirections.len)
gibdirections = list(list(WEST, NORTHWEST, SOUTHWEST, NORTH),list(EAST, NORTHEAST, SOUTHEAST, SOUTH), list())
return ..()
@@ -80,7 +83,7 @@
gib_mob_type = /mob/living/carbon/human
sound_vol = 50
/obj/effect/gibspawner/human/Initialize(mapload)
/obj/effect/gibspawner/human/Initialize(mapload, mob/living/source_mob, list/datum/disease/diseases, blood_dna_info)
if(!gibdirections.len)
gibdirections = list(
list(NORTH, NORTHEAST, NORTHWEST),
@@ -91,6 +94,9 @@
GLOB.alldirs,
list(),
)
if(!iscarbon(source_mob) && isnull(blood_dna_info))
return ..(blood_dna_info = list("Human DNA" = random_human_blood_type()))
return ..()

View File

@@ -7,7 +7,14 @@
plane = GAME_PLANE
var/splatter_type = "splatter"
/obj/effect/temp_visual/dir_setting/bloodsplatter/Initialize(mapload, set_dir)
// set_color arg can be either a color string or a singleton /datum/blood_type to pull the color from
/obj/effect/temp_visual/dir_setting/bloodsplatter/Initialize(mapload, set_dir, set_color = BLOOD_COLOR_RED)
if(set_color)
var/datum/blood_type/blood_type = set_color
if(istype(blood_type))
color = blood_type.color
else
color = set_color
if(ISDIAGONALDIR(set_dir))
icon_state = "[splatter_type][pick(1, 2, 6)]"
else
@@ -44,6 +51,9 @@
/obj/effect/temp_visual/dir_setting/bloodsplatter/xenosplatter
splatter_type = "xsplatter"
/obj/effect/temp_visual/dir_setting/bloodsplatter/xenosplatter/Initialize(mapload, set_dir, set_color = GLOB.blood_types[/datum/blood_type/xeno::name])
return ..()
/obj/effect/temp_visual/dir_setting/speedbike_trail
name = "speedbike trails"
icon_state = "ion_fade"

View File

@@ -92,11 +92,8 @@
var/blood_id = scanned.get_blood_id()
if(blood_id)
var/blood_percent = round((scanned.blood_volume / BLOOD_VOLUME_NORMAL) * 100)
var/blood_type = scanned.dna.blood_type
if(blood_id != /datum/reagent/blood)
var/datum/reagent/reagents = GLOB.chemical_reagents_list[blood_id]
blood_type = reagents?.name || blood_id
autopsy_information += "Blood Type: [blood_type]<br>"
var/datum/blood_type/blood_type = scanned.dna.blood_type
autopsy_information += "Blood Type: [blood_type.name]<br>"
autopsy_information += "Blood Volume: [scanned.blood_volume] cl ([blood_percent]%) <br>"
for(var/datum/disease/diseases as anything in scanned.diseases)

View File

@@ -377,14 +377,13 @@
var/blood_id = carbontarget.get_blood_id()
if(blood_id)
var/blood_percent = round((carbontarget.blood_volume / BLOOD_VOLUME_NORMAL) * 100)
var/blood_type = carbontarget.dna.blood_type
if(blood_id != /datum/reagent/blood) // special blood substance
var/datum/reagent/real_reagent = GLOB.chemical_reagents_list[blood_id]
blood_type = real_reagent?.name || blood_id
var/datum/blood_type/blood_type = carbontarget.dna.blood_type
if(carbontarget.blood_volume <= BLOOD_VOLUME_SAFE && carbontarget.blood_volume > BLOOD_VOLUME_OKAY)
render_list += "<span class='alert ml-1'>Blood level: LOW [blood_percent]%, [carbontarget.blood_volume] cl,</span> [span_info("type: [blood_type]")]<br>"
render_list += "<span class='alert ml-1'>Blood level: LOW [blood_percent]%, [carbontarget.blood_volume] cl,</span> [span_info("type: [blood_type.name]")]<br>"
render_list += "<span class='alert ml-1'>Recommendation: [blood_type.restoration_chem::name] supplements or blood transfusion.</span><br>"
else if(carbontarget.blood_volume <= BLOOD_VOLUME_OKAY)
render_list += "<span class='alert ml-1'>Blood level: <b>CRITICAL [blood_percent]%</b>, [carbontarget.blood_volume] cl,</span> [span_info("type: [blood_type]")]<br>"
render_list += "<span class='alert ml-1'>Recommendation: [blood_type.restoration_chem::name] supplements or blood transfusion.</span><br>"
else
render_list += "<span class='info ml-1'>Blood level: [blood_percent]%, [carbontarget.blood_volume] cl, type: [blood_type]</span><br>"

View File

@@ -57,7 +57,7 @@
target.real_name = fields["name"]
target.dna.unique_enzymes = fields["UE"]
target.name = target.real_name
target.dna.blood_type = fields["blood_type"]
target.set_blood_type(fields["blood_type"])
if(fields["UI"]) //UI+UE
target.dna.unique_identity = merge_text(target.dna.unique_identity, fields["UI"])
if(fields["UF"])
@@ -135,7 +135,7 @@
target.real_name = fields["name"]
target.dna.unique_enzymes = fields["UE"]
target.name = target.real_name
target.dna.blood_type = fields["blood_type"]
target.set_blood_type(fields["blood_type"])
target.dna.temporary_mutations[UE_CHANGED] = endtime
if(fields["UI"]) //UI+UE
if(!target.dna.previous["UI"])

View File

@@ -6,7 +6,7 @@
var/subjectjob = null
var/blood_decal_type = /obj/effect/decal/cleanable/blood
/obj/item/food/meat/Initialize(mapload)
/obj/item/food/meat/Initialize(mapload, blood_dna_list = list("meaty DNA" = get_blood_type(BLOOD_TYPE_MEAT)))
. = ..()
if(!blood_decal_type)
@@ -16,13 +16,14 @@
/datum/component/blood_walk,\
blood_type = blood_decal_type,\
blood_spawn_chance = 45,\
transfer_blood_dna = TRUE,\
max_blood = custom_materials[custom_materials[1]] / SHEET_MATERIAL_AMOUNT,\
)
AddComponent(
/datum/component/bloody_spreader,\
blood_left = custom_materials[custom_materials[1]] / SHEET_MATERIAL_AMOUNT,\
blood_dna = list("meaty DNA" = "MT-"),\
blood_dna = blood_dna_list,\
diseases = null,\
)

View File

@@ -26,7 +26,7 @@
human.skin_tone = pick(GLOB.skin_tones)
human.dna.species.randomize_active_underwear_only(human)
// Needs to be called towards the end to update all the UIs just set above
human.dna.initialize_dna(newblood_type = random_blood_type(), create_mutation_blocks = randomize_mutations, randomize_features = TRUE)
human.dna.initialize_dna(newblood_type = random_human_blood_type(), create_mutation_blocks = randomize_mutations, randomize_features = TRUE)
// Snowflake for Ethereals
human.updatehealth()
human.updateappearance(mutcolor_update = TRUE)
@@ -55,7 +55,7 @@
if(facial_hair && facial_hair.natural_spawn && !facial_hair.locked)
human.set_facial_hairstyle(facial_hair.name, update = FALSE)
// Normal DNA init stuff, these can generally be wacky but we care less, they're aliens after all
human.dna.initialize_dna(newblood_type = random_blood_type(), create_mutation_blocks = randomize_mutations, randomize_features = TRUE)
human.dna.initialize_dna(newblood_type = random_human_blood_type(), create_mutation_blocks = randomize_mutations, randomize_features = TRUE)
human.updatehealth()
if(update_body)
human.updateappearance(mutcolor_update = TRUE)

View File

@@ -0,0 +1,14 @@
/// Makes the target's blood a beautiful rainbow
/datum/smite/clownify_blood
name = "Clownify blood"
/datum/smite/clownify_blood/effect(client/user, mob/living/target)
. = ..()
if (!iscarbon(target))
to_chat(user, span_warning("This must be used on a carbon mob."), confidential = TRUE)
return
var/mob/living/carbon/carbon_target = target
carbon_target.set_blood_type(get_blood_type(BLOOD_TYPE_CLOWN))
SEND_SOUND(carbon_target, 'sound/items/bikehorn.ogg')

View File

@@ -36,7 +36,7 @@
for(var/entry in GLOB.human_list)
var/mob/living/carbon/human/subject = entry
if(subject.ckey)
data += "<tr><td>[subject]</td><td>[subject.dna.unique_enzymes]</td><td>[subject.dna.blood_type]</td></tr>"
data += "<tr><td>[subject]</td><td>[subject.dna.unique_enzymes]</td><td>[subject.dna.blood_type.name]</td></tr>"
data += "</table>"
var/datum/browser/browser = new(usr, "DNA", "DNA Log", 440, 410)

View File

@@ -223,6 +223,7 @@
var/list/dirs = GLOB.alldirs.Copy()
for(var/i in 1 to 3)
var/obj/effect/decal/cleanable/blood/gibs/gibs = new(get_turf(owner))
gibs.add_blood_DNA(blood_dna_info)
gibs.streak(dirs)
var/obj/item/bodypart/chest/new_chest = new(null)

View File

@@ -143,11 +143,15 @@
brother2.set_species(/datum/species/moth)
var/icon/brother1_icon = render_preview_outfit(/datum/outfit/job/quartermaster, brother1)
brother1_icon.Blend(icon('icons/effects/blood.dmi', "maskblood"), ICON_OVERLAY)
var/icon/brother1_blood_icon = icon('icons/effects/blood.dmi', "maskblood")
brother1_blood_icon.Blend(BLOOD_COLOR_RED, ICON_MULTIPLY)
brother1_icon.Blend(brother1_blood_icon, ICON_OVERLAY)
brother1_icon.Shift(WEST, 8)
var/icon/brother2_icon = render_preview_outfit(/datum/outfit/job/scientist/consistent, brother2)
brother2_icon.Blend(icon('icons/effects/blood.dmi', "uniformblood"), ICON_OVERLAY)
var/icon/brother2_blood_icon = icon('icons/effects/blood.dmi', "uniformblood")
brother2_blood_icon.Blend(BLOOD_COLOR_RED, ICON_MULTIPLY)
brother2_icon.Blend(brother2_blood_icon, ICON_OVERLAY)
brother2_icon.Shift(EAST, 8)
var/icon/final_icon = brother1_icon

View File

@@ -385,7 +385,7 @@
victim.visible_message(span_danger("[user] impales [victim] with [user.p_their()] [weapon.name]!"), span_userdanger("[user] impales you with [user.p_their()] [weapon.name]!"))
victim.apply_damage(weapon.force, BRUTE, BODY_ZONE_CHEST, attacking_item = weapon)
user.do_item_attack_animation(victim, used_item = weapon, animation_type = ATTACK_ANIMATION_PIERCE)
user.add_mob_blood(victim)
user.add_blood_DNA_to_items(victim.get_blood_dna_list(), ITEM_SLOT_ICLOTHING|ITEM_SLOT_OCLOTHING)
playsound(get_turf(user),weapon.hitsound,75,TRUE)
return

View File

@@ -150,7 +150,7 @@
AddComponent(/datum/component/bloody_spreader,\
blood_left = INFINITY,\
blood_dna = list("meaty DNA" = "MT-"),\
blood_dna = list("meaty DNA" = get_blood_type(BLOOD_TYPE_MEAT)),\
diseases = null,\
)

View File

@@ -137,7 +137,7 @@
AddComponent(
/datum/component/bloody_spreader,\
blood_left = INFINITY,\
blood_dna = list("Unknown DNA" = "X*"),\
blood_dna = list("Unknown DNA" = BLOOD_TYPE_XENO),\
diseases = null,\
)

View File

@@ -64,7 +64,9 @@
victim_dummy.set_hairstyle("Messy", update = TRUE)
var/icon/obsessed_icon = render_preview_outfit(preview_outfit)
obsessed_icon.Blend(icon('icons/effects/blood.dmi', "uniformblood"), ICON_OVERLAY)
var/icon/blood_icon = icon('icons/effects/blood.dmi', "uniformblood")
blood_icon.Blend(BLOOD_COLOR_RED, ICON_MULTIPLY)
obsessed_icon.Blend(blood_icon, ICON_OVERLAY)
var/icon/final_icon = finish_preview_icon(obsessed_icon)

View File

@@ -56,7 +56,9 @@
if(isinhands)
return
if(GET_ATOM_BLOOD_DNA_LENGTH(src))
. += mutable_appearance('icons/effects/blood.dmi', "gloveblood")
var/mutable_appearance/blood_overlay = mutable_appearance('icons/effects/blood.dmi', "gloveblood")
blood_overlay.color = get_blood_dna_color(GET_ATOM_BLOOD_DNA(src))
. += blood_overlay
/obj/item/clothing/gloves/update_clothes_damaged_state(damaged_state = CLOTHING_DAMAGED)
..()

View File

@@ -67,9 +67,13 @@
return
if(GET_ATOM_BLOOD_DNA_LENGTH(src))
if(clothing_flags & LARGE_WORN_ICON)
. += mutable_appearance('icons/effects/64x64.dmi', "helmetblood_large")
var/mutable_appearance/blood_overlay = mutable_appearance('icons/effects/64x64.dmi', "helmetblood_large")
blood_overlay.color = get_blood_dna_color(GET_ATOM_BLOOD_DNA(src))
. += blood_overlay
else
. += mutable_appearance('icons/effects/blood.dmi', "helmetblood")
var/mutable_appearance/blood_overlay = mutable_appearance('icons/effects/blood.dmi', "helmetblood")
blood_overlay.color = get_blood_dna_color(GET_ATOM_BLOOD_DNA(src))
. += blood_overlay
/obj/item/clothing/head/update_clothes_damaged_state(damaged_state = CLOTHING_DAMAGED)
..()

View File

@@ -38,7 +38,9 @@
if(isinhands || !(body_parts_covered & HEAD))
return
if(GET_ATOM_BLOOD_DNA_LENGTH(src))
. += mutable_appearance('icons/effects/blood.dmi', "maskblood")
var/mutable_appearance/blood_overlay = mutable_appearance('icons/effects/blood.dmi', "maskblood")
blood_overlay.color = get_blood_dna_color(GET_ATOM_BLOOD_DNA(src))
. += blood_overlay
/obj/item/clothing/mask/update_clothes_damaged_state(damaged_state = CLOTHING_DAMAGED)
..()

View File

@@ -19,7 +19,9 @@
if(isinhands || !(body_parts_covered & HEAD))
return
if(GET_ATOM_BLOOD_DNA_LENGTH(src))
. += mutable_appearance('icons/effects/blood.dmi', "maskblood")
var/mutable_appearance/blood_overlay = mutable_appearance('icons/effects/blood.dmi', "maskblood")
blood_overlay.color = get_blood_dna_color(GET_ATOM_BLOOD_DNA(src))
. += blood_overlay
/obj/item/clothing/neck/bowtie
name = "bow tie"

View File

@@ -64,9 +64,13 @@
return
if(GET_ATOM_BLOOD_DNA_LENGTH(src))
if(clothing_flags & LARGE_WORN_ICON)
. += mutable_appearance('icons/effects/64x64.dmi', "shoeblood_large")
var/mutable_appearance/blood_overlay = mutable_appearance('icons/effects/64x64.dmi', "shoeblood_large")
blood_overlay.color = get_blood_dna_color(GET_ATOM_BLOOD_DNA(src))
. += blood_overlay
else
. += mutable_appearance('icons/effects/blood.dmi', "shoeblood")
var/mutable_appearance/blood_overlay = mutable_appearance('icons/effects/blood.dmi', "shoeblood")
blood_overlay.color = get_blood_dna_color(GET_ATOM_BLOOD_DNA(src))
. += blood_overlay
/obj/item/clothing/shoes/examine(mob/user)
. = ..()

View File

@@ -41,7 +41,9 @@
if(isinhands)
return
if(GET_ATOM_BLOOD_DNA_LENGTH(src))
. += mutable_appearance('icons/effects/blood.dmi', "[blood_overlay_type]blood")
var/mutable_appearance/blood_overlay = mutable_appearance('icons/effects/blood.dmi', "[blood_overlay_type]blood")
blood_overlay.color = get_blood_dna_color(GET_ATOM_BLOOD_DNA(src))
. += blood_overlay
/obj/item/clothing/suit/update_clothes_damaged_state(damaged_state = CLOTHING_DAMAGED)
..()

View File

@@ -108,7 +108,9 @@
if(isinhands)
return
if(GET_ATOM_BLOOD_DNA_LENGTH(src))
. += mutable_appearance('icons/effects/blood.dmi', "uniformblood")
var/mutable_appearance/blood_overlay = mutable_appearance('icons/effects/blood.dmi', "uniformblood")
blood_overlay.color = get_blood_dna_color(GET_ATOM_BLOOD_DNA(src))
. += blood_overlay
/obj/item/clothing/under/attackby(obj/item/attacking_item, mob/user, list/modifiers)
if(repair_sensors(attacking_item, user))

View File

@@ -7,12 +7,18 @@
circuit = /obj/item/circuitboard/machine/gibber
anchored_tabletop_offset = 8
var/operating = FALSE //Is it on?
var/dirty = FALSE // Does it need cleaning?
var/gibtime = 40 // Time from starting until meat appears
//Is it on?
var/operating = FALSE
/// Does it need cleaning?
var/dirty = FALSE
/// Time from starting until meat appears
var/gibtime = 40
/// How much meat we meet when we meat the meat
var/meat_produced = 2
/// If the gibber should give the 'Subject may not have abiotic items on' message
var/ignore_clothing = FALSE
/// The DNA info of the last gibbed mob
var/blood_dna_info
/obj/machinery/gibber/Initialize(mapload)
. = ..()
@@ -48,7 +54,12 @@
/obj/machinery/gibber/update_overlays()
. = ..()
if(dirty)
. += "grinder_bloody"
var/mutable_appearance/blood_overlay = mutable_appearance(icon, "grinder_bloody", appearance_flags = RESET_COLOR|KEEP_APART)
if(blood_dna_info)
blood_overlay.color = get_blood_dna_color(blood_dna_info)
else
blood_overlay.color = BLOOD_COLOR_RED
. += blood_overlay
if(machine_stat & (NOPOWER|BROKEN) || panel_open)
return
if(!occupant)
@@ -193,16 +204,18 @@
else if(gibee.dna && gibee.dna.species)
typeofmeat = gibee.dna.species.meat
typeofskin = gibee.dna.species.skinned_type
blood_dna_info = gibee.get_blood_dna_list()
else if(iscarbon(occupant))
var/mob/living/carbon/C = occupant
typeofmeat = C.type_of_meat
gibtype = C.gib_type
if(isalien(C))
var/mob/living/carbon/carbon_occupant = occupant
typeofmeat = carbon_occupant.type_of_meat
gibtype = carbon_occupant.gib_type
if(isalien(carbon_occupant))
typeofskin = /obj/item/stack/sheet/animalhide/xeno
blood_dna_info = carbon_occupant.get_blood_dna_list()
for (var/i in 1 to meat_produced)
var/obj/item/food/meat/slab/newmeat = new typeofmeat
var/obj/item/food/meat/slab/newmeat = new typeofmeat(null, blood_dna_info)
newmeat.name = "[sourcename] [newmeat.name]"
newmeat.set_custom_materials(list(GET_MATERIAL_REF(/datum/material/meat/mob_meat, occupant) = 4 * SHEET_MATERIAL_AMOUNT))
if(!istype(newmeat))
@@ -234,13 +247,15 @@
mob_occupant.ghostize()
set_occupant(null)
qdel(mob_occupant)
addtimer(CALLBACK(src, PROC_REF(make_meat), skin, results, meat_produced, gibtype, diseases), gibtime)
addtimer(CALLBACK(src, PROC_REF(make_meat), skin, results, meat_produced, gibtype, diseases, blood_dna_info), gibtime)
/obj/machinery/gibber/proc/make_meat(obj/item/stack/sheet/animalhide/skin, list/results, meat_produced, gibtype, list/datum/disease/diseases)
/obj/machinery/gibber/proc/make_meat(obj/item/stack/sheet/animalhide/skin, list/results, meat_produced, gibtype, list/datum/disease/diseases, blood_dna_info)
playsound(src.loc, 'sound/effects/splat.ogg', 50, TRUE)
operating = FALSE
if (!dirty && prob(50))
dirty = TRUE
if(blood_dna_info)
add_blood_DNA(blood_dna_info)
var/turf/T = get_turf(src)
var/list/turf/nearby_turfs = RANGE_TURFS(3,T) - T
if(skin)
@@ -259,7 +274,8 @@
diseases_to_add += disease
if(LAZYLEN(diseases_to_add))
meatslab.AddComponent(/datum/component/infective, diseases_to_add)
if(blood_dna_info)
meatslab.add_blood_DNA(blood_dna_info)
meatslab.forceMove(loc)
meatslab.throw_at(pick(nearby_turfs), iteration, 3)
@@ -268,8 +284,9 @@
for (var/i in 1 to meat_produced**2) //2 slabs: 4 giblets, 3 slabs: 9, etc.
var/turf/gibturf = pick(nearby_turfs)
if (!gibturf.density && (src in view(gibturf)))
new gibtype(gibturf, round(1 + i / meat_produced), diseases)
var/obj/effect/decal/cleanable/new_gibs = new gibtype(gibturf, round(1 + i / meat_produced), diseases)
if(blood_dna_info)
new_gibs.add_blood_DNA(blood_dna_info)
pixel_x = base_pixel_x //return to its spot after shaking
operating = FALSE

View File

@@ -231,4 +231,4 @@
return
if(!length(blood_DNA))
return
parent.AddElement(/datum/element/decal/blood)
parent.AddElement(/datum/element/decal/blood, _color = get_blood_dna_color(blood_DNA))

View File

@@ -109,24 +109,55 @@
/turf/closed/add_blood_DNA(list/blood_dna, list/datum/disease/diseases)
return FALSE
/mob/living/carbon/human/add_blood_DNA(list/blood_DNA_to_add, list/datum/disease/diseases)
if (QDELETED(src))
/obj/item/clothing/under/add_blood_DNA(list/blood_DNA_to_add)
. = ..()
if(!.)
return
if(wear_suit)
wear_suit.add_blood_DNA(blood_DNA_to_add)
update_worn_oversuit()
else if(w_uniform)
w_uniform.add_blood_DNA(blood_DNA_to_add)
update_worn_undersuit()
if(gloves)
var/obj/item/clothing/gloves/mob_gloves = gloves
mob_gloves.add_blood_DNA(blood_DNA_to_add)
else if(length(blood_DNA_to_add))
if (isnull(forensics))
for(var/obj/item/clothing/accessory/thing_accessory as anything in attached_accessories)
if(prob(66))
continue
thing_accessory.add_blood_DNA(blood_DNA_to_add)
/mob/living/carbon/human/add_blood_DNA(list/blood_DNA_to_add, list/datum/disease/diseases)
return add_blood_DNA_to_items(blood_DNA_to_add)
/// Adds blood DNA to certain slots the mob is wearing
/mob/living/carbon/human/proc/add_blood_DNA_to_items(
list/blood_DNA_to_add,
target_flags = ITEM_SLOT_ICLOTHING|ITEM_SLOT_OCLOTHING|ITEM_SLOT_GLOVES|ITEM_SLOT_HEAD|ITEM_SLOT_MASK,
)
if(QDELING(src))
return FALSE
if(!length(blood_DNA_to_add))
return FALSE
// Don't messy up our jumpsuit if we're got a coat
if((target_flags & ITEM_SLOT_OCLOTHING) && (wear_suit?.body_parts_covered & CHEST))
target_flags &= ~ITEM_SLOT_ICLOTHING
var/dirty_hands = !!(target_flags & (ITEM_SLOT_GLOVES|ITEM_SLOT_HANDS))
var/dirty_feet = !!(target_flags & ITEM_SLOT_FEET)
var/slots_to_bloody = target_flags & ~check_obscured_slots()
var/list/all_worn = get_equipped_items()
for(var/obj/item/thing as anything in all_worn)
if(thing.slot_flags & slots_to_bloody)
thing.add_blood_DNA(blood_DNA_to_add)
if(thing.body_parts_covered & HANDS)
dirty_hands = FALSE
if(thing.body_parts_covered & FEET)
dirty_feet = FALSE
if(slots_to_bloody & ITEM_SLOT_HANDS)
for(var/obj/item/thing in held_items)
thing.add_blood_DNA(blood_DNA_to_add)
if(dirty_hands || dirty_feet || !length(all_worn))
if(isnull(forensics))
forensics = new(src)
forensics.inherit_new(blood_DNA = blood_DNA_to_add)
blood_in_hands = rand(2, 4)
update_worn_gloves()
if(dirty_hands)
blood_in_hands = rand(2, 4)
update_clothing(slots_to_bloody)
return TRUE
/*

View File

@@ -9,7 +9,7 @@
return FALSE
var/mob/living/carbon/carb_hallucinator = hallucinator
if(!length(carb_hallucinator.bodyparts) || HAS_TRAIT(carb_hallucinator, TRAIT_NOBLOOD))
if(!length(carb_hallucinator.bodyparts) || HAS_TRAIT(carb_hallucinator, TRAIT_NOBLOOD) || carb_hallucinator.dna?.blood_type.no_bleed_overlays)
return FALSE
var/obj/item/bodypart/picked
var/list/bodyparts = carb_hallucinator.bodyparts.Copy()
@@ -39,6 +39,7 @@
icon_state = "[picked.body_zone]_[pick(2, 3)]",
loc = hallucinator,
)
bleeding.color = carb_hallucinator.dna.blood_type.get_color()
bleeding.layer = -WOUND_LAYER
hallucinator.client?.images += bleeding
return TRUE

View File

@@ -203,7 +203,16 @@
most_plentiful_reagent.Cut()
most_plentiful_reagent[reagent] = reagents_add[reagent]
podman.dna.species.exotic_blood = most_plentiful_reagent[1]
var/datum/reagent/new_blood_reagent = most_plentiful_reagent[1]
podman.dna.species.exotic_blood = new_blood_reagent
// Try to find a corresponding blood type for this reagent
var/datum/blood_type/new_blood_type = get_blood_type(new_blood_reagent)
if(isnull(new_blood_type)) // this blood type doesn't exist yet in the global list, so make a new one
new_blood_type = new /datum/blood_type/random_chemical(new_blood_reagent)
GLOB.blood_types[new_blood_type::id] = new_blood_type
podman.set_blood_type(new_blood_type)
investigate_log("[key_name(mind)] cloned as a podman via [src] in [parent]", INVESTIGATE_BOTANY)
parent.update_tray(user, 1)
return result

View File

@@ -277,7 +277,7 @@
var/amount = max(1, round((edible_vol)*(potency/100) * reagent_overflow_mod, 1)) //the plant will always have at least 1u of each of the reagents in its reagent production traits
var/list/data
if(rid == /datum/reagent/blood) // Hack to make blood in plants always O-
data = list("blood_type" = "O-")
data = list("blood_type" = get_blood_type(BLOOD_TYPE_O_MINUS))
if(istype(grown_edible) && (rid == /datum/reagent/consumable/nutriment || rid == /datum/reagent/consumable/nutriment/vitamin))
data = grown_edible.tastes // apple tastes of apple.
T.reagents.add_reagent(rid, amount, data, added_purity = reagent_purity)

View File

@@ -41,6 +41,10 @@
/datum/job/clown/after_spawn(mob/living/spawned, client/player_client)
if (ishuman(spawned))
spawned.apply_pref_name(/datum/preference/name/clown, player_client)
if(check_holidays(APRIL_FOOLS)) // Clown blood is real
var/mob/living/carbon/human/human_clown = spawned
human_clown.set_blood_type(get_blood_type(BLOOD_TYPE_CLOWN))
return ..()
/datum/outfit/job/clown

View File

@@ -47,7 +47,7 @@
. = ..()
AddComponent(/datum/component/bloody_spreader,\
blood_left = INFINITY,\
blood_dna = list("meaty DNA" = "MT-"),\
blood_dna = list("meaty DNA" = get_blood_type(BLOOD_TYPE_MEAT)),\
diseases = null,\
)

View File

@@ -85,3 +85,10 @@
return
visible_message(span_alertalien("[src] lays an egg!"))
new /obj/structure/alien/egg(loc)
/mob/living/basic/alien/spawn_gibs(drop_bitflags=NONE)
if(drop_bitflags & DROP_BODYPARTS)
new /obj/effect/gibspawner/xeno(drop_location(), src)
else
new /obj/effect/gibspawner/xeno/bodypartless(drop_location(), src)

View File

@@ -59,7 +59,7 @@
AddComponent(\
/datum/component/bloody_spreader,\
blood_left = INFINITY,\
blood_dna = list("meaty DNA" = "MT-"),\
blood_dna = list("meaty DNA" = get_blood_type(BLOOD_TYPE_MEAT)),\
diseases = null,\
)

View File

@@ -252,7 +252,8 @@
if((blood_disease.spread_flags & DISEASE_SPREAD_SPECIAL) || (blood_disease.spread_flags & DISEASE_SPREAD_NON_CONTAGIOUS))
continue
carbon_receiver.ForceContractDisease(blood_disease)
if(!(blood_data["blood_type"] in get_safe_blood(carbon_receiver.dna.blood_type)) && !(ignore_incompatibility))
var/datum/blood_type/blood_type = blood_data["blood_type"]
if(!ignore_incompatibility && !(blood_type.type_key() in carbon_receiver.dna.blood_type.compatible_types))
carbon_receiver.reagents.add_reagent(/datum/reagent/toxin, amount * 0.5)
return TRUE
@@ -322,42 +323,34 @@
return
return /datum/reagent/blood
// This is has more potential uses, and is probably faster than the old proc.
/proc/get_safe_blood(bloodtype)
. = list()
if(!bloodtype)
return
/// Returns the blood_type datum that corresponds to the string id key in GLOB.blood_types
/proc/get_blood_type(id)
return GLOB.blood_types[id]
var/static/list/bloodtypes_safe = list(
"A-" = list("A-", "O-"),
"A+" = list("A-", "A+", "O-", "O+"),
"B-" = list("B-", "O-"),
"B+" = list("B-", "B+", "O-", "O+"),
"AB-" = list("A-", "B-", "O-", "AB-"),
"AB+" = list("A-", "A+", "B-", "B+", "O-", "O+", "AB-", "AB+"),
"O-" = list("O-"),
"O+" = list("O-", "O+"),
"L" = list("L"),
"U" = list("A-", "A+", "B-", "B+", "O-", "O+", "AB-", "AB+", "L", "U")
)
var/safe = bloodtypes_safe[bloodtype]
if(safe)
. = safe
/// Returns the hex color string of a given blood_type datum given an assoc list of blood_DNA e.g. ("Unknown Blood Type", "*X")
/proc/get_blood_dna_color(list/blood_DNA)
var/datum/blood_type/blood_type
if(length(blood_DNA))
var/last_added_bloodtype_key = blood_DNA[length(blood_DNA)]
blood_type = blood_DNA[last_added_bloodtype_key]
if(!istype(blood_type))
blood_type = get_blood_type(blood_type) || random_human_blood_type()
return blood_type.get_color()
/**
* Returns TRUE if src is compatible with donor's blood, otherwise FALSE.
* * donor: Carbon mob, the one that is donating blood.
*/
/mob/living/carbon/proc/get_blood_compatibility(mob/living/carbon/donor)
var/patient_blood_data = get_blood_data(get_blood_id())
var/donor_blood_data = donor.get_blood_data(donor.get_blood_id())
return donor_blood_data["blood_type"] in get_safe_blood(patient_blood_data["blood_type"])
var/datum/blood_type/patient_blood_data = dna.blood_type
var/datum/blood_type/donor_blood_data = donor.dna.blood_type
return (donor_blood_data in patient_blood_data.compatible_types)
//to add a splatter of blood or other mob liquid.
/mob/living/proc/add_splatter_floor(turf/splatter_turf, small_drip)
if(get_blood_id() != /datum/reagent/blood)
/mob/living/proc/add_splatter_floor(turf/splatter_turf, small_drip, skip_reagents_check = FALSE)
if(!skip_reagents_check && !(get_blood_id() in list(/datum/reagent/blood, /datum/reagent/toxin/acid)))
return
if(!splatter_turf)
splatter_turf = get_turf(src)
if(isclosedturf(splatter_turf) || (isgroundlessturf(splatter_turf) && !GET_TURF_BELOW(splatter_turf)))
@@ -371,14 +364,14 @@
if(drop.drips < 5)
drop.drips++
drop.add_overlay(pick(drop.random_icon_states))
drop.transfer_mob_blood_dna(src)
drop.add_mob_blood(src)
return
else
temp_blood_DNA = GET_ATOM_BLOOD_DNA(drop) //we transfer the dna from the drip to the splatter
qdel(drop)//the drip is replaced by a bigger splatter
else
drop = new(splatter_turf, get_static_viruses())
drop.transfer_mob_blood_dna(src)
drop.add_mob_blood(src)
return
// Find a blood decal or create a new one.
@@ -388,23 +381,23 @@
if(QDELETED(blood_spew)) //Give it up
return
blood_spew.bloodiness = min((blood_spew.bloodiness + BLOOD_AMOUNT_PER_DECAL), BLOOD_POOL_MAX)
blood_spew.transfer_mob_blood_dna(src) //give blood info to the blood decal.
blood_spew.add_mob_blood(src) //give blood info to the blood decal.
if(temp_blood_DNA)
blood_spew.add_blood_DNA(temp_blood_DNA)
blood_spew.add_blood_DNA(temp_blood_DNA, no_visuals = small_drip)
/mob/living/carbon/human/add_splatter_floor(turf/splatter_turf, small_drip)
if(!HAS_TRAIT(src, TRAIT_NOBLOOD))
/mob/living/carbon/human/add_splatter_floor(turf/splatter_turf, small_drip, skip_reagents_check = TRUE)
if(!HAS_TRAIT(src, TRAIT_NOBLOOD) && !dna?.blood_type.no_bleed_overlays)
. = ..()
/mob/living/carbon/alien/add_splatter_floor(turf/splatter_turf, small_drip)
/mob/living/carbon/alien/add_splatter_floor(turf/splatter_turf, small_drip, skip_reagents_check)
if(!splatter_turf)
splatter_turf = get_turf(src)
var/obj/effect/decal/cleanable/xenoblood/xeno_blood_splatter = locate() in splatter_turf.contents
if(!xeno_blood_splatter)
xeno_blood_splatter = new(splatter_turf)
xeno_blood_splatter.add_blood_DNA(list("UNKNOWN DNA" = "X*"))
xeno_blood_splatter.add_blood_DNA(list("UNKNOWN DNA" = BLOOD_TYPE_XENO))
/mob/living/silicon/robot/add_splatter_floor(turf/splatter_turf, small_drip)
/mob/living/silicon/robot/add_splatter_floor(turf/splatter_turf, small_drip, skip_reagents_check)
if(!splatter_turf)
splatter_turf = get_turf(src)
var/obj/effect/decal/cleanable/oil/oil_splatter = locate() in splatter_turf.contents

View File

@@ -0,0 +1,247 @@
/datum/blood_type
/// Name of the blood type.
var/name = "?"
/// A description of the blood type.
var/desc
/// Unique identifier for the blood type in the global list of singletons. Typically this is just the name, but some blood types might have the same name (e.g. evil blood)
var/id
/// Shown color of the blood type.
var/color = BLOOD_COLOR_RED
/// Additional lightness multiplier for the blood color, useful for when the default lightness from the greyscaling doesn't cut it and you want something more vibrant.
/// When set, color will be transformed into a matrix with coefficients multiplied by this value
var/lightness_mult = null
/// The cached color matrix for blood with a lightness_mult. We only need to calculate this once since blood types are singletons
var/list/blood_color_matrix
/// Blood types that are safe to use with people that have this blood type (for blood transfusions)
var/compatible_types = list()
/// What reagent is represented by this blood type?
var/datum/reagent/reagent_type = /datum/reagent/blood
/// What chem is used to restore this blood type (outside of itself, of course)?
var/datum/reagent/restoration_chem = /datum/reagent/iron
/// Whether or not this blood type should create blood trails, blood sprays, etc
var/no_bleed_overlays
/// Exclude abstract root types from being initialized by defining them here
var/root_abstract_type
/// If this blood type is meant to persist across species changes
var/is_species_universal
/datum/blood_type/New()
. = ..()
id = name
compatible_types |= type_key()
/datum/blood_type/Destroy(force)
if(!force)
stack_trace("qdel called on blood type singleton! (use FORCE if necessary)")
return QDEL_HINT_LETMELIVE
return ..()
/**
* Key used to identify this blood type in compatible_types
*
* Allows for more complex or dynamically generated blood types
*/
/datum/blood_type/proc/type_key()
return type
/// Returns blood color or color matrix
/// Useful when you want to have a blood color with values out of normal hex bounds for that acidic look
/// set dynamic to TRUE to redo the matrix each time (e.g. for clown blood dynamically shifting each time)
/datum/blood_type/proc/get_color(dynamic = FALSE)
if(isnull(lightness_mult))
return color
if(!isnull(blood_color_matrix) && !dynamic)
return blood_color_matrix
blood_color_matrix = color_to_full_rgba_matrix(color)
for(var/i in 1 to min(length(blood_color_matrix), 16))
if (length(blood_color_matrix) == 12 && i > 9) // Don't modify constants
break
if (length(blood_color_matrix) >= 16 && i % 4 == 0) // Don't modify alpha either
continue
blood_color_matrix[i] *= lightness_mult
return blood_color_matrix
// human blood type, for organizational purposes mainly
/datum/blood_type/human
desc = "Blood cells suspended in plasma, the most abundant of which being the hemoglobin-containing red blood cells."
root_abstract_type = /datum/blood_type/human
/datum/blood_type/human/a_minus
name = BLOOD_TYPE_A_MINUS
compatible_types = list(/datum/blood_type/human/a_minus, /datum/blood_type/human/o_minus)
/datum/blood_type/human/a_plus
name = BLOOD_TYPE_A_PLUS
compatible_types = list(/datum/blood_type/human/a_minus, /datum/blood_type/human/a_plus, /datum/blood_type/human/o_minus, /datum/blood_type/human/o_plus)
/datum/blood_type/human/b_minus
name = BLOOD_TYPE_B_MINUS
compatible_types = list(
/datum/blood_type/human/b_minus,
/datum/blood_type/human/o_minus,
)
/datum/blood_type/human/b_plus
name = BLOOD_TYPE_B_PLUS
compatible_types = list(
/datum/blood_type/human/b_minus,
/datum/blood_type/human/b_plus,
/datum/blood_type/human/o_minus,
/datum/blood_type/human/o_plus,
)
/datum/blood_type/human/ab_minus
name = BLOOD_TYPE_AB_MINUS
compatible_types = list(
/datum/blood_type/human/a_minus,
/datum/blood_type/human/b_minus,
/datum/blood_type/human/ab_minus,
/datum/blood_type/human/o_minus,
)
/datum/blood_type/human/ab_plus
name = BLOOD_TYPE_AB_PLUS
compatible_types = list(
/datum/blood_type/human/a_minus,
/datum/blood_type/human/a_plus,
/datum/blood_type/human/b_minus,
/datum/blood_type/human/b_plus,
/datum/blood_type/human/o_minus,
/datum/blood_type/human/o_plus,
/datum/blood_type/human/ab_minus,
/datum/blood_type/human/ab_plus,
)
/datum/blood_type/human/o_minus
name = BLOOD_TYPE_O_MINUS
compatible_types = list(
/datum/blood_type/human/o_minus,
)
/datum/blood_type/human/o_plus
name = BLOOD_TYPE_O_PLUS
compatible_types = list(
/datum/blood_type/human/o_minus,
/datum/blood_type/human/o_plus,
)
/datum/blood_type/animal
name = BLOOD_TYPE_ANIMAL
compatible_types = list(
/datum/blood_type/animal,
)
/datum/blood_type/lizard
name = BLOOD_TYPE_LIZARD
compatible_types = list(
/datum/blood_type/lizard,
)
/datum/blood_type/ethereal
name = BLOOD_TYPE_ETHEREAL
color = /datum/reagent/consumable/liquidelectricity::color
lightness_mult = 1.255 // for more vibrant gatorade coloring
compatible_types = list(
/datum/blood_type/ethereal,
)
/datum/blood_type/oil
name = BLOOD_TYPE_OIL
color = BLOOD_COLOR_OIL
reagent_type = /datum/reagent/fuel/oil
/datum/blood_type/vampire
name = BLOOD_TYPE_VAMPIRE
compatible_types = list(
/datum/blood_type/vampire,
)
/datum/blood_type/meat // why does this exist
name = BLOOD_TYPE_MEAT
/datum/blood_type/universal
name = BLOOD_TYPE_UNIVERSAL
/datum/blood_type/universal/New()
. = ..()
compatible_types = subtypesof(/datum/blood_type)
/datum/blood_type/xeno
name = BLOOD_TYPE_XENO
color = BLOOD_COLOR_XENO
lightness_mult = 1.255 // For parity with pre-refactor xeno blood sprites
compatible_types = list(/datum/blood_type/xeno)
/// April fool's blood for clowns
/datum/blood_type/clown
name = BLOOD_TYPE_CLOWN
reagent_type = /datum/reagent/colorful_reagent
lightness_mult = 1.255
is_species_universal = TRUE
/// The cached list of random colors to pick from
var/list/random_color_list
/datum/blood_type/clown/get_color(dynamic = TRUE)
// Set up the random color list if we haven't done that yet. Only need to do this once.
if(isnull(random_color_list))
var/datum/reagent/colorful_reagent/clown_blood = new
random_color_list = clown_blood.random_color_list.Copy()
qdel(clown_blood)
color = pick(random_color_list)
return ..()
/// Slimeperson blood, aka 'toxin' blood type
/datum/blood_type/slime
name = BLOOD_TYPE_TOX
color = /datum/reagent/toxin/slimejelly::color
reagent_type = /datum/reagent/toxin/slimejelly
no_bleed_overlays = TRUE
/// Podpeople blood
/datum/blood_type/water
name = BLOOD_TYPE_H2O
color = /datum/reagent/water::color
reagent_type = /datum/reagent/water
no_bleed_overlays = TRUE
/// Snail blood
/datum/blood_type/snail
name = BLOOD_TYPE_SNAIL
reagent_type = /datum/reagent/lube
/// An abstract-ish blood type used particularly for species with blood set to random reagents, such as podpeople
/datum/blood_type/random_chemical
root_abstract_type = /datum/blood_type/random_chemical
/datum/blood_type/random_chemical/New(datum/reagent/reagent_type)
. = ..()
src.name = initial(reagent_type.name)
src.color = initial(reagent_type.color)
src.reagent_type = reagent_type
src.restoration_chem = reagent_type
src.root_abstract_type = null
/datum/blood_type/random_chemical/type_key()
return reagent_type
// Similar to the random reagents bloodtype, this one creates a 'but evil' bloodtype
/datum/blood_type/evil
root_abstract_type = /datum/blood_type/evil
/datum/blood_type/evil/New(datum/blood_type/real_blood_type, list/real_compatible_types)
src.name = real_blood_type.name
. = ..()
id = type_key()
src.color = BLOOD_COLOR_BLACK // why it gotta be black though
src.reagent_type = real_blood_type.reagent_type
src.restoration_chem = real_blood_type.reagent_type
src.compatible_types = LAZYCOPY(real_compatible_types)
src.root_abstract_type = null
/datum/blood_type/evil/type_key()
return "[name]_but_evil"

View File

@@ -9,7 +9,7 @@
/mob/living/brain/Initialize(mapload)
. = ..()
create_dna(src)
stored_dna.initialize_dna(random_blood_type())
stored_dna.initialize_dna(random_human_blood_type())
if(isturf(loc)) //not spawned in an MMI or brain organ (most likely adminspawned)
var/obj/item/organ/brain/OB = new(loc) //we create a new brain organ for it.
OB.brainmob = src

View File

@@ -1454,12 +1454,33 @@
/mob/living/carbon/proc/spray_blood(splatter_direction, splatter_strength = 3)
if(!isturf(loc))
return
if(dna.blood_type.no_bleed_overlays)
return
var/obj/effect/decal/cleanable/blood/hitsplatter/our_splatter = new(loc)
our_splatter.add_blood_DNA(GET_ATOM_BLOOD_DNA(src))
our_splatter.blood_dna_info = get_blood_dna_list()
our_splatter.color = get_blood_dna_color(our_splatter.blood_dna_info)
var/turf/targ = get_ranged_target_turf(src, splatter_direction, splatter_strength)
our_splatter.fly_towards(targ, splatter_strength)
/// Goes through the organs and bodyparts of the mob and updates their blood_dna_info, in case their blood type has changed (via set_species() or otherwise)
/mob/living/carbon/proc/update_cached_blood_dna_info()
var/list/blood_dna_info = get_blood_dna_list()
for(var/obj/item/organ/organ in organs)
organ.blood_dna_info = blood_dna_info
for(var/obj/item/bodypart/bodypart in bodyparts)
bodypart.blood_dna_info = blood_dna_info
/mob/living/carbon/set_blood_type(datum/blood_type/new_blood_type, update_cached_blood_dna_info = TRUE)
if(isnull(dna))
return
if(dna.blood_type == new_blood_type) // already has this blood type, we don't need to do anything.
return
dna.blood_type = new_blood_type
if(update_cached_blood_dna_info)
update_cached_blood_dna_info()
/mob/living/carbon/dropItemToGround(obj/item/item, force = FALSE, silent = FALSE, invdrop = TRUE)
if(item && ((item in organs) || (item in bodyparts))) //let's not do this, aight?
return FALSE

View File

@@ -289,6 +289,23 @@
else
Knockdown(stun_duration)
/// When another mob touches us, they may messy us up.
/mob/living/carbon/proc/share_blood_on_touch(mob/living/carbon/human/who_touched_us)
return
/mob/living/carbon/human/share_blood_on_touch(mob/living/carbon/human/who_touched_us, messy_slots = ITEM_SLOT_ICLOTHING|ITEM_SLOT_OCLOTHING)
if(!istype(who_touched_us) || !messy_slots)
return
for(var/obj/item/thing as anything in who_touched_us.get_equipped_items())
if((thing.body_parts_covered & HANDS) && prob(GET_ATOM_BLOOD_DNA_LENGTH(thing) * 25))
add_blood_DNA_to_items(GET_ATOM_BLOOD_DNA(who_touched_us.wear_suit), messy_slots)
return
if(prob(blood_in_hands * GET_ATOM_BLOOD_DNA_LENGTH(who_touched_us) * 10))
add_blood_DNA_to_items(GET_ATOM_BLOOD_DNA(who_touched_us), messy_slots)
who_touched_us.blood_in_hands -= 1
/mob/living/carbon/proc/help_shake_act(mob/living/carbon/helper, force_friendly)
if(on_fire)
to_chat(helper, span_warning("You can't put [p_them()] out with just your bare hands!"))
@@ -315,6 +332,7 @@
to_chat(helper, span_notice("You give [src] a pat on the head to make [p_them()] feel better!"))
to_chat(src, span_notice("[helper] gives you a pat on the head to make you feel better! "))
share_blood_on_touch(helper, ITEM_SLOT_HEAD|ITEM_SLOT_MASK)
if(HAS_TRAIT(src, TRAIT_BADTOUCH))
to_chat(helper, span_warning("[src] looks visibly upset as you pat [p_them()] on the head."))
@@ -351,6 +369,7 @@
to_chat(helper, span_notice("You hug [src] to make [p_them()] feel better!"))
to_chat(src, span_notice("[helper] hugs you to make you feel better!"))
share_blood_on_touch(helper, ITEM_SLOT_HEAD|ITEM_SLOT_MASK|ITEM_SLOT_GLOVES)
// Warm them up with hugs
share_bodytemperature(helper)
@@ -718,4 +737,7 @@
if(HAS_TRAIT(src, TRAIT_NO_SIDE_KICK)) // added as an extra check, just in case
. &= ~SHOVE_CAN_KICK_SIDE
/mob/living/carbon/create_splatter(splatter_dir)
new /obj/effect/temp_visual/dir_setting/bloodsplatter(get_turf(src), splatter_dir, dna?.blood_type.get_color())
#undef SHAKE_ANIMATION_OFFSET

View File

@@ -302,7 +302,11 @@
damage_overlay = mutable_appearance('icons/mob/effects/dam_mob.dmi', "blank", -DAMAGE_LAYER, appearance_flags = KEEP_TOGETHER)
damage_overlay.color = iter_part.damage_overlay_color
if(iter_part.brutestate)
damage_overlay.add_overlay("[iter_part.dmg_overlay_type]_[iter_part.body_zone]_[iter_part.brutestate]0") //we're adding icon_states of the base image as overlays
var/mutable_appearance/blood_damage_overlay = mutable_appearance('icons/mob/effects/dam_mob.dmi', "[iter_part.dmg_overlay_type]_[iter_part.body_zone]_[iter_part.brutestate]0", appearance_flags = RESET_COLOR) //we're adding icon_states of the base image as overlays
blood_damage_overlay.color = dna.blood_type.get_color()
var/mutable_appearance/brute_damage_overlay = mutable_appearance('icons/mob/effects/dam_mob.dmi', "[iter_part.dmg_overlay_type]_[iter_part.body_zone]_[iter_part.brutestate]0_overlay", appearance_flags = RESET_COLOR)
blood_damage_overlay.overlays += brute_damage_overlay
damage_overlay.add_overlay(blood_damage_overlay)
if(iter_part.burnstate)
damage_overlay.add_overlay("[iter_part.dmg_overlay_type]_[iter_part.body_zone]_0[iter_part.burnstate]")
@@ -315,10 +319,15 @@
/mob/living/carbon/update_wound_overlays()
remove_overlay(WOUND_LAYER)
if(dna?.blood_type.no_bleed_overlays)
return
var/mutable_appearance/wound_overlay
for(var/obj/item/bodypart/iter_part as anything in bodyparts)
if(iter_part.bleed_overlay_icon)
wound_overlay ||= mutable_appearance('icons/mob/effects/bleed_overlays.dmi', "blank", -WOUND_LAYER, appearance_flags = KEEP_TOGETHER)
var/mutable_appearance/blood_overlay = mutable_appearance('icons/mob/effects/bleed_overlays.dmi', "blank", -WOUND_LAYER, appearance_flags = KEEP_TOGETHER)
blood_overlay.color = dna.blood_type.get_color()
wound_overlay ||= blood_overlay
wound_overlay.add_overlay(iter_part.bleed_overlay_icon)
if(isnull(wound_overlay))
@@ -496,7 +505,7 @@
var/list/new_limbs = list()
for(var/obj/item/bodypart/limb as anything in bodyparts)
if(limb in needs_update)
var/bodypart_icon = limb.get_limb_icon()
var/bodypart_icon = limb.get_limb_icon(dropped = FALSE, update_on = src)
new_limbs += bodypart_icon
limb_icon_cache[icon_render_keys[limb.body_zone]] = bodypart_icon //Caches the icon with the bodypart key, as it is new
else

View File

@@ -381,20 +381,24 @@ GLOBAL_LIST_EMPTY(features_by_species)
if(old_species.type != type)
replace_body(human_who_gained_species, src)
if(!human_who_gained_species.dna.blood_type.is_species_universal) // Clown blood is forever.
//Assigns exotic blood type if the species has one
if(exotic_bloodtype && human_who_gained_species.dna.blood_type != exotic_bloodtype)
human_who_gained_species.set_blood_type(get_blood_type(exotic_bloodtype))
// updates the cached organ blood types in case our blood type changed
human_who_gained_species.update_cached_blood_dna_info()
//Otherwise, check if the previous species had an exotic bloodtype and we do not have one and assign a random blood type
//(why the fuck is blood type not tied to a fucking DNA block?)
else if(old_species.exotic_bloodtype && isnull(exotic_bloodtype))
human_who_gained_species.set_blood_type(random_human_blood_type())
human_who_gained_species.update_cached_blood_dna_info()
regenerate_organs(human_who_gained_species, old_species, replace_current = FALSE, visual_only = human_who_gained_species.visual_only_organs)
// Update locked slots AFTER all organ and body stuff is handled
human_who_gained_species.hud_used?.update_locked_slots()
// Drop the items the new species can't wear
INVOKE_ASYNC(src, PROC_REF(worn_items_fit_body_check), human_who_gained_species, TRUE)
//Assigns exotic blood type if the species has one
if(exotic_bloodtype && human_who_gained_species.dna.blood_type != exotic_bloodtype)
human_who_gained_species.dna.blood_type = exotic_bloodtype
//Otherwise, check if the previous species had an exotic bloodtype and we do not have one and assign a random blood type
//(why the fuck is blood type not tied to a fucking DNA block?)
else if(old_species.exotic_bloodtype && !exotic_bloodtype)
human_who_gained_species.dna.blood_type = random_blood_type()
//Resets blood if it is excessively high so they don't gib
normalize_blood(human_who_gained_species)
@@ -760,10 +764,16 @@ GLOBAL_LIST_EMPTY(features_by_species)
**/
/datum/species/proc/handle_chemical(datum/reagent/chem, mob/living/carbon/human/affected, seconds_per_tick, times_fired)
SHOULD_CALL_PARENT(TRUE)
if(chem.type == exotic_blood)
affected.blood_volume = min(affected.blood_volume + round(chem.volume, 0.1), BLOOD_VOLUME_MAXIMUM)
affected.reagents.del_reagent(chem.type)
return COMSIG_MOB_STOP_REAGENT_CHECK
if(!istype(chem, /datum/reagent/blood)) // the blood reagent handles this itself, this is for exotic blood types
var/datum/blood_type/blood_type = affected.dna.blood_type
if(chem.type == blood_type?.reagent_type)
affected.blood_volume = min(affected.blood_volume + round(chem.volume, 0.1), BLOOD_VOLUME_MAXIMUM)
affected.reagents.del_reagent(chem.type)
return COMSIG_MOB_STOP_REAGENT_CHECK
if(chem.type == blood_type?.restoration_chem && affected.blood_volume < BLOOD_VOLUME_NORMAL)
affected.blood_volume += BLOOD_REGEN_FACTOR * seconds_per_tick
affected.reagents.remove_reagent(chem.type, chem.metabolization_rate * seconds_per_tick)
return COMSIG_MOB_STOP_REAGENT_CHECK
if(!chem.overdosed && chem.overdose_threshold && chem.volume >= chem.overdose_threshold && !HAS_TRAIT(affected, TRAIT_OVERDOSEIMMUNE))
chem.overdosed = TRUE
chem.overdose_start(affected)

View File

@@ -120,7 +120,7 @@ INITIALIZE_IMMEDIATE(/mob/living/carbon/human/dummy)
target.dna.features["tail_monkey"] = get_consistent_feature_entry(SSaccessories.tails_list_monkey)
target.dna.features["pod_hair"] = get_consistent_feature_entry(SSaccessories.pod_hair_list)
target.dna.features["caps"] = get_consistent_feature_entry(SSaccessories.caps_list)
target.dna.initialize_dna(create_mutation_blocks = FALSE, randomize_features = FALSE)
target.dna.initialize_dna(newblood_type = get_blood_type(BLOOD_TYPE_O_MINUS), create_mutation_blocks = FALSE, randomize_features = FALSE)
// UF and UI are nondeterministic, even though the features are the same some blocks will randomize slightly
// In practice this doesn't matter, but this is for the sake of 100%(ish) consistency
var/static/consistent_UF

View File

@@ -26,7 +26,7 @@
RegisterSignal(src, COMSIG_COMPONENT_CLEAN_FACE_ACT, PROC_REF(clean_face))
AddComponent(/datum/component/personal_crafting, ui_human_crafting)
AddElement(/datum/element/footstep, FOOTSTEP_MOB_HUMAN, 1, -6)
AddComponent(/datum/component/bloodysoles/feet, FOOTPRINT_SPRITE_SHOES)
AddComponent(/datum/component/bloodysoles/feet)
AddElement(/datum/element/ridable, /datum/component/riding/creature/human)
AddElement(/datum/element/strippable, GLOB.strippable_human_items, TYPE_PROC_REF(/mob/living/carbon/human/, should_strip))
var/static/list/loc_connections = list(

View File

@@ -182,9 +182,13 @@ There are several things that need to be remembered:
// When byond gives us filters that respect dirs we can just use an alpha mask for this but until then, two icons weeeee
var/mutable_appearance/hands_combined = mutable_appearance(layer = -GLOVES_LAYER, appearance_flags = KEEP_TOGETHER)
if(has_left_hand(check_disabled = FALSE))
hands_combined.overlays += mutable_appearance('icons/effects/blood.dmi', "bloodyhands_left")
var/mutable_appearance/blood_overlay = mutable_appearance('icons/effects/blood.dmi', "bloodyhands_left")
blood_overlay.color = get_blood_dna_color(GET_ATOM_BLOOD_DNA(src))
hands_combined.overlays += blood_overlay
if(has_right_hand(check_disabled = FALSE))
hands_combined.overlays += mutable_appearance('icons/effects/blood.dmi', "bloodyhands_right")
var/mutable_appearance/blood_overlay = mutable_appearance('icons/effects/blood.dmi', "bloodyhands_right")
blood_overlay.color = get_blood_dna_color(GET_ATOM_BLOOD_DNA(src))
hands_combined.overlays += blood_overlay
overlays_standing[GLOVES_LAYER] = hands_combined
apply_overlay(GLOVES_LAYER)
return
@@ -908,7 +912,7 @@ generate/load female uniform sprites matching all previously decided variables
my_head.update_limb(is_creating = update_limb_data)
add_overlay(my_head.get_limb_icon())
add_overlay(my_head.get_limb_icon(dropped = FALSE, update_on = src))
update_worn_head()
update_worn_mask()

View File

@@ -8,7 +8,7 @@
mutanttongue = /obj/item/organ/tongue/ethereal
mutantheart = /obj/item/organ/heart/ethereal
exotic_blood = /datum/reagent/consumable/liquidelectricity //Liquid Electricity. fuck you think of something better gamer
exotic_bloodtype = "LE"
exotic_bloodtype = BLOOD_TYPE_ETHEREAL
siemens_coeff = 0.5 //They thrive on energy
payday_modifier = 1.0
inherent_traits = list(

View File

@@ -23,6 +23,7 @@
mutantheart = null
meat = /obj/item/food/meat/slab/human/mutant/slime
exotic_blood = /datum/reagent/toxin/slimejelly
exotic_bloodtype = BLOOD_TYPE_TOX
blood_deficiency_drain_rate = JELLY_REGEN_RATE + BLOOD_DEFICIENCY_MODIFIER
coldmod = 6 // = 3x cold damage
heatmod = 0.5 // = 1/4x heat damage

View File

@@ -26,7 +26,7 @@
species_cookie = /obj/item/food/meat/slab
meat = /obj/item/food/meat/slab/human/mutant/lizard
skinned_type = /obj/item/stack/sheet/animalhide/lizard
exotic_bloodtype = "L"
exotic_bloodtype = BLOOD_TYPE_LIZARD
inert_mutation = /datum/mutation/human/firebreath
death_sound = 'sound/mobs/humanoids/lizard/deathsound.ogg'
species_language_holder = /datum/language_holder/lizard

View File

@@ -17,6 +17,7 @@
payday_modifier = 1.0
meat = /obj/item/food/meat/slab/human/mutant/plant
exotic_blood = /datum/reagent/water
exotic_bloodtype = BLOOD_TYPE_H2O
changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_MAGIC | MIRROR_PRIDE | RACE_SWAP | ERT_SPAWN | SLIME_EXTRACT
species_language_holder = /datum/language_holder/plant

View File

@@ -16,6 +16,7 @@
mutanteyes = /obj/item/organ/eyes/snail
mutanttongue = /obj/item/organ/tongue/snail
exotic_blood = /datum/reagent/lube
exotic_bloodtype = BLOOD_TYPE_SNAIL
bodypart_overrides = list(
BODY_ZONE_HEAD = /obj/item/bodypart/head/snail,

View File

@@ -18,7 +18,7 @@
)
inherent_biotypes = MOB_UNDEAD|MOB_HUMANOID
changesource_flags = MIRROR_BADMIN | MIRROR_PRIDE | WABBAJACK | ERT_SPAWN
exotic_bloodtype = "V"
exotic_bloodtype = BLOOD_TYPE_VAMPIRE
blood_deficiency_drain_rate = BLOOD_DEFICIENCY_MODIFIER // vampires already passively lose blood, so this just makes them lose it slightly more quickly when they have blood deficiency.
mutantheart = /obj/item/organ/heart/vampire
mutanttongue = /obj/item/organ/tongue/vampire

View File

@@ -524,7 +524,7 @@
dna.unique_enzymes = dna.previous["UE"]
dna.previous.Remove("UE")
if(dna.previous["blood_type"])
dna.blood_type = dna.previous["blood_type"]
set_blood_type(dna.previous["blood_type"])
dna.previous.Remove("blood_type")
dna.temporary_mutations.Remove(mut)
continue

View File

@@ -428,6 +428,7 @@
M.visible_message(span_warning("[src] grabs [M] [grabbed_by_hands ? "by their hands":"passively"]!"), \
span_warning("[src] grabs you [grabbed_by_hands ? "by your hands":"passively"]!"), null, null, src)
to_chat(src, span_notice("You grab [M] [grabbed_by_hands ? "by their hands":"passively"]!"))
grabbed_human.share_blood_on_touch(src, grabbed_by_hands ? ITEM_SLOT_GLOVES : ITEM_SLOT_ICLOTHING|ITEM_SLOT_OCLOTHING)
else
M.visible_message(span_warning("[src] grabs [M] passively!"), \
span_warning("[src] grabs you passively!"), null, null, src)
@@ -1100,7 +1101,7 @@
trail.existing_dirs += newdir
trail.add_overlay(image('icons/effects/blood.dmi', trail_type, dir = newdir))
trail.transfer_mob_blood_dna(src)
trail.add_mob_blood(src)
trail.bloodiness = min(trail.bloodiness + bleed_amount, BLOOD_POOL_MAX)
found_trail = TRUE
break
@@ -1112,14 +1113,14 @@
trail.blood_state = trail_blood_type
trail.existing_dirs += newdir
trail.add_overlay(image('icons/effects/blood.dmi', trail_type, dir = newdir))
trail.transfer_mob_blood_dna(src)
trail.add_mob_blood(src)
trail.bloodiness = min(bleed_amount, BLOOD_POOL_MAX)
/mob/living/proc/get_trail_blood()
return BLOOD_STATE_HUMAN
/mob/living/carbon/human/makeTrail(turf/T)
if(HAS_TRAIT(src, TRAIT_NOBLOOD) || !is_bleeding() || HAS_TRAIT(src, TRAIT_NOBLOOD))
if(HAS_TRAIT(src, TRAIT_NOBLOOD) || !is_bleeding() || dna.blood_type.no_bleed_overlays)
return
..()
@@ -3052,3 +3053,7 @@ GLOBAL_LIST_EMPTY(fire_appearances)
if(HAS_TRAIT(src, TRAIT_ANALGESIA) && !force)
return
INVOKE_ASYNC(src, PROC_REF(emote), "scream")
/// Setter for changing a mob's blood type
/mob/living/proc/set_blood_type(datum/blood_type/new_blood_type, update_cached_blood_dna_info)
return

View File

@@ -59,8 +59,11 @@
if(iscarbon(exposed_mob))
var/mob/living/carbon/exposed_carbon = exposed_mob
if(exposed_carbon.get_blood_id() == type && ((methods & INJECT) || ((methods & INGEST) && HAS_TRAIT(exposed_carbon, TRAIT_DRINKS_BLOOD))))
if(!data || !(data["blood_type"] in get_safe_blood(exposed_carbon.dna.blood_type)))
var/datum/blood_type/carbon_blood_type = exposed_carbon.dna.blood_type
if(carbon_blood_type.reagent_type == type && ((methods & INJECT) || ((methods & INGEST) && HAS_TRAIT(exposed_carbon, TRAIT_DRINKS_BLOOD))))
var/datum/blood_type/recipient_blood_type = exposed_carbon.dna.blood_type
var/datum/blood_type/donor_blood_type = data["blood_type"]
if(!(donor_blood_type.type_key() in recipient_blood_type.compatible_types))
exposed_carbon.reagents.add_reagent(/datum/reagent/toxin, reac_volume * 0.5)
else
exposed_carbon.blood_volume = min(exposed_carbon.blood_volume + round(reac_volume, 0.1), BLOOD_VOLUME_MAXIMUM)
@@ -72,12 +75,19 @@
if(data["blood_DNA"] && data["blood_type"])
exposed_carbon.add_blood_DNA(list(data["blood_DNA"] = data["blood_type"]))
else
exposed_carbon.add_blood_DNA(list("Non-human DNA" = random_blood_type()))
exposed_carbon.add_blood_DNA(list("Non-human DNA" = random_human_blood_type()))
/datum/reagent/blood/on_new(list/data)
. = ..()
if(istype(data))
SetViruses(src, data)
if(!istype(data))
return
SetViruses(src, data)
var/datum/blood_type/blood_type = data["blood_type"]
if(!blood_type)
return
var/blood_color = blood_type.get_color()
if(blood_color != BLOOD_COLOR_RED) // If the blood is default red, just use the darker red color for the reagent.
color = blood_color
/datum/reagent/blood/on_merge(list/mix_data)
if(data && mix_data)
@@ -152,7 +162,7 @@
if(data["blood_DNA"] && data["blood_type"])
exposed_obj.add_blood_DNA(list(data["blood_DNA"] = data["blood_type"]))
else
exposed_obj.add_blood_DNA(list("Non-human DNA" = random_blood_type()))
exposed_obj.add_blood_DNA(list("Non-human DNA" = random_human_blood_type()))
/datum/reagent/blood/get_taste_description(mob/living/taster)
if(isnull(taster))
@@ -1216,11 +1226,6 @@
color = "#606060" //pure iron? let's make it violet of course
ph = 6
/datum/reagent/iron/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
. = ..()
if(affected_mob.blood_volume < BLOOD_VOLUME_NORMAL)
affected_mob.blood_volume += BLOOD_REGEN_FACTOR * seconds_per_tick
/datum/reagent/gold
name = "Gold"
description = "Gold is a dense, soft, shiny metal and the most malleable and ductile metal known."

View File

@@ -12,22 +12,23 @@
/obj/item/reagent_containers/blood/Initialize(mapload, vol)
. = ..()
if(blood_type != null)
reagents.add_reagent(unique_blood ? unique_blood : /datum/reagent/blood, 200, list("viruses"=null,"blood_DNA"=null,"blood_type"=blood_type,"resistances"=null,"trace_chem"=null))
reagents.add_reagent(unique_blood ? unique_blood : /datum/reagent/blood, 200, list("viruses"=null,"blood_DNA"=null,"blood_type"=get_blood_type(blood_type),"resistances"=null,"trace_chem"=null))
update_appearance()
/// Handles updating the container when the reagents change.
/obj/item/reagent_containers/blood/on_reagent_change(datum/reagents/holder, ...)
var/datum/reagent/blood/new_reagent = holder.has_reagent(/datum/reagent/blood)
if(new_reagent && new_reagent.data && new_reagent.data["blood_type"])
blood_type = new_reagent.data["blood_type"]
var/datum/blood_type/blood_type = new_reagent.data["blood_type"]
blood_type = blood_type.name
else if(holder.has_reagent(/datum/reagent/consumable/liquidelectricity))
blood_type = "LE"
blood_type = BLOOD_TYPE_ETHEREAL
else if(holder.has_reagent(/datum/reagent/lube))
blood_type = "S"
blood_type = BLOOD_TYPE_SNAIL
else if(holder.has_reagent(/datum/reagent/water))
blood_type = "H2O"
blood_type = BLOOD_TYPE_H2O
else if(holder.has_reagent(/datum/reagent/toxin/slimejelly))
blood_type = "TOX"
blood_type = BLOOD_TYPE_TOX
else
blood_type = null
return ..()
@@ -43,36 +44,36 @@
/obj/item/reagent_containers/blood/random/Initialize(mapload, vol)
icon_state = "bloodpack"
blood_type = pick("A+", "A-", "B+", "B-", "O+", "O-", "L")
blood_type = pick(BLOOD_TYPE_A_PLUS, BLOOD_TYPE_A_MINUS, BLOOD_TYPE_B_PLUS, BLOOD_TYPE_B_MINUS, BLOOD_TYPE_O_PLUS, BLOOD_TYPE_O_MINUS, BLOOD_TYPE_LIZARD)
return ..()
/obj/item/reagent_containers/blood/a_plus
blood_type = "A+"
blood_type = BLOOD_TYPE_A_PLUS
/obj/item/reagent_containers/blood/a_minus
blood_type = "A-"
blood_type = BLOOD_TYPE_A_MINUS
/obj/item/reagent_containers/blood/b_plus
blood_type = "B+"
blood_type = BLOOD_TYPE_B_PLUS
/obj/item/reagent_containers/blood/b_minus
blood_type = "B-"
blood_type = BLOOD_TYPE_B_MINUS
/obj/item/reagent_containers/blood/o_plus
blood_type = "O+"
blood_type = BLOOD_TYPE_O_PLUS
/obj/item/reagent_containers/blood/o_minus
blood_type = "O-"
blood_type = BLOOD_TYPE_O_MINUS
/obj/item/reagent_containers/blood/lizard
blood_type = "L"
blood_type = BLOOD_TYPE_LIZARD
/obj/item/reagent_containers/blood/ethereal
blood_type = "LE"
blood_type = BLOOD_TYPE_ETHEREAL
unique_blood = /datum/reagent/consumable/liquidelectricity
/obj/item/reagent_containers/blood/snail
blood_type = "S"
blood_type = BLOOD_TYPE_SNAIL
unique_blood = /datum/reagent/lube
/obj/item/reagent_containers/blood/snail/examine()
@@ -80,7 +81,7 @@
. += span_notice("It's a bit slimy... The label indicates that this is meant for snails.")
/obj/item/reagent_containers/blood/podperson
blood_type = "H2O"
blood_type = BLOOD_TYPE_H2O
unique_blood = /datum/reagent/water
/obj/item/reagent_containers/blood/podperson/examine()
@@ -89,7 +90,7 @@
// for slimepeople
/obj/item/reagent_containers/blood/toxin
blood_type = "TOX"
blood_type = BLOOD_TYPE_TOX
unique_blood = /datum/reagent/toxin/slimejelly
/obj/item/reagent_containers/blood/toxin/examine()
@@ -97,7 +98,7 @@
. += span_notice("There is a toxin warning on the label. This is for slimepeople.")
/obj/item/reagent_containers/blood/universal
blood_type = "U"
blood_type = BLOOD_TYPE_UNIVERSAL
/obj/item/reagent_containers/blood/attackby(obj/item/tool, mob/user, list/modifiers)
if (IS_WRITING_UTENSIL(tool))

View File

@@ -65,7 +65,7 @@
return
apply_damage(50, BRUTE, BODY_ZONE_HEAD)
to_chat(src, span_userdanger("Your head splits open! Your brain mutates!"))
new /obj/effect/gibspawner/generic(drop_location(), src)
new /obj/effect/gibspawner/generic(drop_location(), src, get_blood_dna_list())
emote("scream")
/// Proc with no side effects that turns someone into a psyker. returns FALSE if it could not psykerize.

View File

@@ -384,10 +384,10 @@
if(!HAS_TRAIT(chaplain, TRAIT_NOBLOOD))
if(target.blood_volume < BLOOD_VOLUME_SAFE)
var/target_blood_data = target.get_blood_data(target.get_blood_id())
var/chaplain_blood_data = chaplain.get_blood_data(chaplain.get_blood_id())
var/datum/blood_type/target_blood_data = target.dna.blood_type
var/datum/blood_type/chaplain_blood_data = chaplain.dna.blood_type
var/transferred_blood_amount = min(chaplain.blood_volume, BLOOD_VOLUME_SAFE - target.blood_volume)
if(transferred_blood_amount && (chaplain_blood_data["blood_type"] in get_safe_blood(target_blood_data["blood_type"])))
if(transferred_blood_amount && (chaplain_blood_data.type_key() in target_blood_data.compatible_types))
transferred = TRUE
chaplain.transfer_blood_to(target, transferred_blood_amount, forced = TRUE)
if(target.blood_volume > BLOOD_VOLUME_EXCESS)

View File

@@ -41,7 +41,7 @@
///Defines when a bodypart should not be changed. Example: BP_BLOCK_CHANGE_SPECIES prevents the limb from being overwritten on species gain
var/change_exempt_flags = NONE
///Random flags that describe this bodypart
var/bodypart_flags = NONE
var/bodypart_flags = BODYPART_VIRGIN
///Whether the bodypart (and the owner) is husked.
var/is_husked = FALSE
@@ -209,6 +209,8 @@
var/datum/bodypart_overlay/texture/texture_bodypart_overlay
/// Lazylist of /datum/status_effect/grouped/bodypart_effect types. Instances of this are applied to the carbon when added the limb is attached, and merged with similair limbs
var/list/bodypart_effects
/// The cached info about the blood this organ belongs to, set during on_removal()
var/list/blood_dna_info
/obj/item/bodypart/apply_fantasy_bonuses(bonus)
. = ..()
@@ -240,6 +242,8 @@
if(!IS_ORGANIC_LIMB(src))
grind_results = null
else
blood_dna_info = list("UNKNOWN DNA" = get_blood_type(BLOOD_TYPE_O_PLUS))
name = "[limb_id] [parse_zone(body_zone)]"
update_icon_dropped()
@@ -939,6 +943,14 @@
SHOULD_CALL_PARENT(TRUE)
if(IS_ORGANIC_LIMB(src))
// Try to add a cached blood type data, we must do it in here because for some reason DNA gets initialized AFTER the mob's limbs are created.
// Should be fine as this gets called before all the important stuff happens
if(!(bodypart_flags & ORGAN_VIRGIN) && owner?.dna?.blood_type)
blood_dna_info = owner.get_blood_dna_list()
// need to remove the synethic blood DNA that is initialized
// wash also adds the blood dna again
wash(CLEAN_TYPE_BLOOD)
bodypart_flags &= ~BODYPART_VIRGIN
if(!(bodypart_flags & BODYPART_UNHUSKABLE) && owner && HAS_TRAIT(owner, TRAIT_HUSK))
dmg_overlay_type = "" //no damage overlay shown when husked
is_husked = TRUE
@@ -1007,12 +1019,19 @@
SIGNAL_HANDLER
wash(clean_types)
/obj/item/bodypart/wash(clean_types)
. = ..()
// always add the original dna to the organ after it's washed
if(IS_ORGANIC_LIMB(src) && (clean_types & CLEAN_TYPE_BLOOD))
add_blood_DNA(blood_dna_info)
/// To update the bodypart's icon when not attached to a mob
/obj/item/bodypart/proc/update_icon_dropped()
SHOULD_CALL_PARENT(TRUE)
cut_overlays()
var/list/standing = get_limb_icon(TRUE)
var/list/standing = get_limb_icon(dropped = TRUE)
if(!standing.len)
icon_state = initial(icon_state)//no overlays found, we default back to initial icon.
return
@@ -1044,7 +1063,7 @@
update_icon_dropped()
///Generates an /image for the limb to be used as an overlay
/obj/item/bodypart/proc/get_limb_icon(dropped)
/obj/item/bodypart/proc/get_limb_icon(dropped, mob/living/carbon/update_on)
SHOULD_CALL_PARENT(TRUE)
RETURN_TYPE(/list)
@@ -1054,7 +1073,13 @@
if(dropped && dmg_overlay_type)
if(brutestate)
. += image('icons/mob/effects/dam_mob.dmi', "[dmg_overlay_type]_[body_zone]_[brutestate]0", -DAMAGE_LAYER)
// divided into two overlays: one that gets colored and one that doesn't.
var/image/brute_blood_overlay = image('icons/mob/effects/dam_mob.dmi', "[dmg_overlay_type]_[body_zone]_[brutestate]0", -DAMAGE_LAYER)
brute_blood_overlay.color = get_blood_dna_color(update_on ? update_on.get_blood_dna_list() : blood_dna_info) // living mobs can just get it fresh, dropped limbs use blood_dna_info
var/mutable_appearance/brute_damage_overlay = mutable_appearance('icons/mob/effects/dam_mob.dmi', "[dmg_overlay_type]_[body_zone]_[brutestate]0_overlay", -DAMAGE_LAYER, appearance_flags = RESET_COLOR)
if(brute_damage_overlay)
brute_blood_overlay.overlays += brute_damage_overlay
. += brute_blood_overlay
if(burnstate)
. += image('icons/mob/effects/dam_mob.dmi', "[dmg_overlay_type]_[body_zone]_0[burnstate]", -DAMAGE_LAYER)

View File

@@ -24,6 +24,9 @@
if (wounding_type)
LAZYSET(limb_owner.body_zone_dismembered_by, body_zone, wounding_type)
if (can_bleed())
limb_owner.bleed(rand(20, 40))
drop_limb(dismembered = TRUE)
limb_owner.update_equipment_speed_mods() // Update in case speed affecting item unequipped by dismemberment
@@ -37,8 +40,8 @@
burn()
return TRUE
if (can_bleed())
add_mob_blood(limb_owner)
limb_owner.bleed(rand(20, 40))
var/direction = pick(GLOB.cardinals)
var/t_range = rand(2,max(throw_range/2, 2))
var/turf/target_turf = get_turf(src)
@@ -90,6 +93,7 @@
SEND_SIGNAL(owner, COMSIG_CARBON_REMOVE_LIMB, src, special, dismembered)
SEND_SIGNAL(src, COMSIG_BODYPART_REMOVED, owner, special, dismembered)
bodypart_flags &= ~BODYPART_IMPLANTED //limb is out and about, it can't really be considered an implant
add_mob_blood(owner)
owner.remove_bodypart(src, special)
for(var/datum/scar/scar as anything in scars)

View File

@@ -170,7 +170,7 @@
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/obj/item/bodypart/head/get_limb_icon(dropped)
/obj/item/bodypart/head/get_limb_icon(dropped, mob/living/carbon/update_on)
. = ..()
. += get_hair_and_lips_icon(dropped)

View File

@@ -8,7 +8,7 @@
/// Reference to the limb we're inside of
var/obj/item/bodypart/bodypart_owner
/// The cached info about the blood this organ belongs to
var/list/blood_dna_info = list("Synthetic DNA" = "O+") // not every organ spawns inside a person
var/list/blood_dna_info // not every organ spawns inside a person
/// The body zone this organ is supposed to inhabit.
var/zone = BODY_ZONE_CHEST
/**
@@ -77,6 +77,7 @@ INITIALIZE_IMMEDIATE(/obj/item/organ)
/obj/item/organ/Initialize(mapload)
. = ..()
blood_dna_info = list("UNKNOWN DNA" = get_blood_type(BLOOD_TYPE_O_PLUS))
if(organ_flags & ORGAN_EDIBLE)
AddComponentFrom(
SOURCE_EDIBLE_INNATE, \

View File

@@ -233,12 +233,12 @@
return TRUE
/// Called on limb removal to remove limb specific limb effects or statusses
// Called on limb removal to remove limb specific limb effects or statuses
/obj/item/organ/proc/on_bodypart_remove(obj/item/bodypart/limb, movement_flags)
SHOULD_CALL_PARENT(TRUE)
if(!IS_ROBOTIC_ORGAN(src) && !(item_flags & NO_BLOOD_ON_ITEM) && !QDELING(src))
AddElement(/datum/element/decal/blood)
AddElement(/datum/element/decal/blood, _color = get_blood_dna_color(blood_dna_info))
item_flags &= ~ABSTRACT
REMOVE_TRAIT(src, TRAIT_NODROP, ORGAN_INSIDE_BODY_TRAIT)
@@ -280,7 +280,7 @@
/obj/item/organ/proc/on_surgical_removal(mob/living/user, mob/living/carbon/old_owner, target_zone, obj/item/tool)
SHOULD_CALL_PARENT(TRUE)
SEND_SIGNAL(src, COMSIG_ORGAN_SURGICALLY_REMOVED, user, old_owner, target_zone, tool)
RemoveElement(/datum/element/decal/blood)
RemoveElement(/datum/element/decal/blood, _color = old_owner.dna.blood_type.get_color())
/**
* Proc that gets called when the organ is surgically inserted by someone. Seem familiar?
*/

View File

@@ -224,6 +224,11 @@
span_notice("[user] succeeds!"),
span_notice("[user] finishes."),
)
if(ishuman(user))
var/mob/living/carbon/human/surgeon = user
surgeon.add_blood_DNA_to_items(target.get_blood_dna_list(), ITEM_SLOT_GLOVES)
else
user.add_mob_blood(target)
return TRUE
/datum/surgery_step/proc/play_success_sound(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)

View File

@@ -45,11 +45,11 @@
/// Ensures the blood deficiency quirk updates its mail goodies correctly
/datum/unit_test/blood_deficiency_mail
var/list/species_to_test = list(
/datum/species/human = /obj/item/reagent_containers/blood/o_minus,
/datum/species/lizard = /obj/item/reagent_containers/blood/lizard,
/datum/species/ethereal = /obj/item/reagent_containers/blood/ethereal,
/datum/species/skeleton = null, // Anyone with noblood should not get a blood bag
/datum/species/jelly = /obj/item/reagent_containers/blood/toxin,
/datum/species/human = /obj/item/reagent_containers/blood/o_minus,
)
/datum/unit_test/blood_deficiency_mail/Run()
@@ -57,10 +57,14 @@
dummy.add_quirk(/datum/quirk/blooddeficiency)
var/datum/quirk/blooddeficiency/quirk = dummy.get_quirk(/datum/quirk/blooddeficiency)
TEST_ASSERT((species_to_test[dummy.dna.species.type] in quirk.mail_goodies), "Blood deficiency quirk spawned with no mail goodies!")
TEST_ASSERT((species_to_test[dummy.dna.species.type] in quirk.mail_goodies), "Blood deficiency quirk did not get the right blood bag in its mail goodies for [dummy.dna.species.type]! \
It should be getting species_to_test[dummy.dna.species.type]." \
)
for(var/species_type in species_to_test)
var/last_species = dummy.dna.species.type
if(species_type == /datum/species/human) // we already tested this above, and setting species again will cause it to randomize
continue
dummy.set_species(species_type)
// Test that the new species has the correct blood bag
if(!isnull(species_to_test[species_type]))

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -134,7 +134,10 @@
carbon_occupant.set_eye_blur_if_lower(rand(10 SECONDS, 20 SECONDS))
hittarget_living.adjustBruteLoss(200)
new /obj/effect/decal/cleanable/blood/splatter(get_turf(hittarget_living))
var/obj/effect/decal/cleanable/blood/splatter/blood_splatter = new /obj/effect/decal/cleanable/blood/splatter(get_turf(hittarget_living))
if(iscarbon(hittarget_living))
var/mob/living/carbon/carbon_target = hittarget_living
blood_splatter.add_mob_blood(carbon_target)
log_combat(src, hittarget_living, "rammed into", null, "injuring all passengers and killing the [hittarget_living]")
dump_mobs(TRUE)

View File

@@ -1050,7 +1050,7 @@ GLOBAL_LIST_EMPTY(vending_machines_to_restock)
carbon_target.visible_message(span_danger("[carbon_head] explodes in a shower of gore beneath [src]!"), span_userdanger("Oh f-"))
carbon_head.drop_organs()
qdel(carbon_head)
new /obj/effect/gibspawner/human/bodypartless(get_turf(carbon_target))
new /obj/effect/gibspawner/human/bodypartless(get_turf(carbon_target), carbon_target)
return TRUE
return FALSE

Binary file not shown.

Before

Width:  |  Height:  |  Size: 397 KiB

After

Width:  |  Height:  |  Size: 388 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 KiB

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Some files were not shown because too many files have changed in this diff Show More