diff --git a/_maps/RandomRuins/SpaceRuins/infested_frigate.dmm b/_maps/RandomRuins/SpaceRuins/infested_frigate.dmm index 18d050e276e..18694d3b4a0 100644 --- a/_maps/RandomRuins/SpaceRuins/infested_frigate.dmm +++ b/_maps/RandomRuins/SpaceRuins/infested_frigate.dmm @@ -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, diff --git a/_maps/modular_generic/beach_l_ribs.dmm b/_maps/modular_generic/beach_l_ribs.dmm index ea0a2a9abd0..69992c2ed76 100644 --- a/_maps/modular_generic/beach_l_ribs.dmm +++ b/_maps/modular_generic/beach_l_ribs.dmm @@ -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 diff --git a/code/__DEFINES/colors.dm b/code/__DEFINES/colors.dm index 7408294770e..ca6a1660a01 100644 --- a/code/__DEFINES/colors.dm +++ b/code/__DEFINES/colors.dm @@ -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" diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index b30583544b8..05b91d64754 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -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 diff --git a/code/__DEFINES/surgery.dm b/code/__DEFINES/surgery.dm index a6ba350eee3..243b0c2b79d 100644 --- a/code/__DEFINES/surgery.dm +++ b/code/__DEFINES/surgery.dm @@ -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() diff --git a/code/__HELPERS/atoms.dm b/code/__HELPERS/atoms.dm index e94d58dd693..d1ff5a4f63c 100644 --- a/code/__HELPERS/atoms.dm +++ b/code/__HELPERS/atoms.dm @@ -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()) diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm index 9d0d91d8ce7..ee872c783f2 100644 --- a/code/__HELPERS/mobs.dm +++ b/code/__HELPERS/mobs.dm @@ -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")) diff --git a/code/_globalvars/lists/mobs.dm b/code/_globalvars/lists/mobs.dm index d88d08302ee..158e8ace8f0 100644 --- a/code/_globalvars/lists/mobs.dm +++ b/code/_globalvars/lists/mobs.dm @@ -114,6 +114,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 diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm index 73e2427f922..57c1af477d5 100644 --- a/code/_onclick/item_attack.dm +++ b/code/_onclick/item_attack.dm @@ -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)) diff --git a/code/datums/components/blood_walk.dm b/code/datums/components/blood_walk.dm index 6bd6f304446..f6d8a07e289 100644 --- a/code/datums/components/blood_walk.dm +++ b/code/datums/components/blood_walk.dm @@ -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)) diff --git a/code/datums/components/bloody_spreader.dm b/code/datums/components/bloody_spreader.dm index 823bc5e9507..ff9702f7133 100644 --- a/code/datums/components/bloody_spreader.dm +++ b/code/datums/components/bloody_spreader.dm @@ -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) diff --git a/code/datums/components/bloodysoles.dm b/code/datums/components/bloodysoles.dm index 342e27c4b29..bdd8942f2eb 100644 --- a/code/datums/components/bloodysoles.dm +++ b/code/datums/components/bloodysoles.dm @@ -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 diff --git a/code/datums/dna.dm b/code/datums/dna.dm index 98ad9f17375..268a0cde078 100644 --- a/code/datums/dna.dm +++ b/code/datums/dna.dm @@ -73,7 +73,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 @@ -127,7 +128,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 @@ -465,7 +466,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 @@ -510,7 +511,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 @@ -659,7 +660,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 diff --git a/code/datums/elements/decals/blood.dm b/code/datums/elements/decals/blood.dm index 9410558a60a..11f011a2e75 100644 --- a/code/datums/elements/decals/blood.dm +++ b/code/datums/elements/decals/blood.dm @@ -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) diff --git a/code/datums/materials/meat.dm b/code/datums/materials/meat.dm index 134449b712a..175772d01a7 100644 --- a/code/datums/materials/meat.dm +++ b/code/datums/materials/meat.dm @@ -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,\ ) diff --git a/code/datums/quirks/negative_quirks/blood_deficiency.dm b/code/datums/quirks/negative_quirks/blood_deficiency.dm index b0fa4cfeae4..d8a33ec3f36 100644 --- a/code/datums/quirks/negative_quirks/blood_deficiency.dm +++ b/code/datums/quirks/negative_quirks/blood_deficiency.dm @@ -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 diff --git a/code/datums/quirks/neutral_quirks/evil.dm b/code/datums/quirks/neutral_quirks/evil.dm index 95ecdb6bb8c..fc63937222f 100644 --- a/code/datums/quirks/neutral_quirks/evil.dm +++ b/code/datums/quirks/neutral_quirks/evil.dm @@ -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 diff --git a/code/datums/records/manifest.dm b/code/datums/records/manifest.dm index 0a0b8eb0785..f66de1bb759 100644 --- a/code/datums/records/manifest.dm +++ b/code/datums/records/manifest.dm @@ -124,7 +124,7 @@ GLOBAL_DATUM_INIT(manifest, /datum/manifest, new) var/datum/record/locked/lockfile = new( age = person.age, chrono_age = person.chrono_age, // SKYRAT EDIT ADDITION - Chronological 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), @@ -146,7 +146,7 @@ GLOBAL_DATUM_INIT(manifest, /datum/manifest, new) new /datum/record/crew( age = person.age, chrono_age = person.chrono_age, // SKYRAT EDIT ADDITION - Chronological 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), diff --git a/code/game/atom/_atom.dm b/code/game/atom/_atom.dm index c567f8214fa..f278b29f6b8 100644 --- a/code/game/atom/_atom.dm +++ b/code/game/atom/_atom.dm @@ -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))) diff --git a/code/game/machinery/computer/dna_console.dm b/code/game/machinery/computer/dna_console.dm index d224e31d3d3..a974499a8ec 100644 --- a/code/game/machinery/computer/dna_console.dm +++ b/code/game/machinery/computer/dna_console.dm @@ -1755,7 +1755,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 @@ -1773,7 +1773,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 diff --git a/code/game/machinery/computer/operating_computer.dm b/code/game/machinery/computer/operating_computer.dm index 57630c8f16b..e1ed92bb8d8 100644 --- a/code/game/machinery/computer/operating_computer.dm +++ b/code/game/machinery/computer/operating_computer.dm @@ -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() diff --git a/code/game/machinery/computer/records/medical.dm b/code/game/machinery/computer/records/medical.dm index 58f3fe65fc8..c564c71f310 100644 --- a/code/game/machinery/computer/records/medical.dm +++ b/code/game/machinery/computer/records/medical.dm @@ -150,7 +150,7 @@ target.age = 18 target.chrono_age = 18 // SKYRAT EDIT ADDITION - Chronological age - 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 = "" diff --git a/code/game/machinery/dna_scanner.dm b/code/game/machinery/dna_scanner.dm index c669c46e4c0..73426e3bdfb 100644 --- a/code/game/machinery/dna_scanner.dm +++ b/code/game/machinery/dna_scanner.dm @@ -185,6 +185,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" diff --git a/code/game/machinery/medical_kiosk.dm b/code/game/machinery/medical_kiosk.dm index 292df504a64..83e90bc98c5 100644 --- a/code/game/machinery/medical_kiosk.dm +++ b/code/game/machinery/medical_kiosk.dm @@ -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) diff --git a/code/game/machinery/recycler.dm b/code/game/machinery/recycler.dm index 551ea5d519b..2b32e30ac3d 100644 --- a/code/game/machinery/recycler.dm +++ b/code/game/machinery/recycler.dm @@ -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 diff --git a/code/game/objects/effects/decals/cleanable.dm b/code/game/objects/effects/decals/cleanable.dm index 15b8ca546af..66d01cd2f7d 100644 --- a/code/game/objects/effects/decals/cleanable.dm +++ b/code/game/objects/effects/decals/cleanable.dm @@ -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 diff --git a/code/game/objects/effects/decals/cleanable/aliens.dm b/code/game/objects/effects/decals/cleanable/aliens.dm index f2543f33c86..74c38afe755 100644 --- a/code/game/objects/effects/decals/cleanable/aliens.dm +++ b/code/game/objects/effects/decals/cleanable/aliens.dm @@ -4,16 +4,21 @@ name = "xeno blood" desc = "It's green and acidic. It looks like... blood?" 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))) diff --git a/code/game/objects/effects/decals/cleanable/humans.dm b/code/game/objects/effects/decals/cleanable/humans.dm index b146dec132e..45084d3ebf3 100644 --- a/code/game/objects/effects/decals/cleanable/humans.dm +++ b/code/game/objects/effects/decals/cleanable/humans.dm @@ -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) skyrat edit /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] [initial(item.name)]." - for(var/species in species_types) - // god help me - if(species == "unknown") - . += "Some feet." - else if(species == SPECIES_MONKEY) - . += "[icon2html('icons/mob/human/human.dmi', user, "monkey")] Some monkey paws." - else if(species == SPECIES_HUMAN) - . += "[icon2html('icons/mob/human/bodyparts.dmi', user, "default_human_l_leg")] Some human feet." - else - . += "[icon2html('icons/mob/human/bodyparts.dmi', user, "[species]_l_leg")] Some [species] feet." + 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] [initial(item.name)]." + + for(var/species in species_types) + // god help me + if(species == "unknown") + . += "Some feet." + else if(species == SPECIES_MONKEY) + . += "[icon2html('icons/mob/human/human.dmi', user, "monkey")] Some monkey paws." + else if(species == SPECIES_HUMAN) + . += "[icon2html('icons/mob/human/bodyparts.dmi', user, "default_human_l_leg")] Some human feet." + else + . += "[icon2html('icons/mob/human/bodyparts.dmi', user, "[species]_l_leg")] Some [species] feet." /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() diff --git a/code/game/objects/effects/decals/cleanable/misc.dm b/code/game/objects/effects/decals/cleanable/misc.dm index dc46585dd40..4921c4fa4ed 100644 --- a/code/game/objects/effects/decals/cleanable/misc.dm +++ b/code/game/objects/effects/decals/cleanable/misc.dm @@ -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" diff --git a/code/game/objects/effects/spawners/gibspawner.dm b/code/game/objects/effects/spawners/gibspawner.dm index 88d16c87acd..66006a8b3ec 100644 --- a/code/game/objects/effects/spawners/gibspawner.dm +++ b/code/game/objects/effects/spawners/gibspawner.dm @@ -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 ..() diff --git a/code/game/objects/effects/temporary_visuals/miscellaneous.dm b/code/game/objects/effects/temporary_visuals/miscellaneous.dm index 23687d205ff..9b9bb2b85ff 100644 --- a/code/game/objects/effects/temporary_visuals/miscellaneous.dm +++ b/code/game/objects/effects/temporary_visuals/miscellaneous.dm @@ -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" diff --git a/code/game/objects/items/devices/scanners/autopsy_scanner.dm b/code/game/objects/items/devices/scanners/autopsy_scanner.dm index a054b3c69d2..7b9fdd81c7c 100644 --- a/code/game/objects/items/devices/scanners/autopsy_scanner.dm +++ b/code/game/objects/items/devices/scanners/autopsy_scanner.dm @@ -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]
" + var/datum/blood_type/blood_type = scanned.dna.blood_type + autopsy_information += "Blood Type: [blood_type.name]
" autopsy_information += "Blood Volume: [scanned.blood_volume] cl ([blood_percent]%)
" for(var/datum/disease/diseases as anything in scanned.diseases) diff --git a/code/game/objects/items/devices/scanners/health_analyzer.dm b/code/game/objects/items/devices/scanners/health_analyzer.dm index 59b6fc8b934..cdc76f9d86e 100644 --- a/code/game/objects/items/devices/scanners/health_analyzer.dm +++ b/code/game/objects/items/devices/scanners/health_analyzer.dm @@ -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 += "Blood level: LOW [blood_percent]%, [carbontarget.blood_volume] cl, [span_info("type: [blood_type]")]
" + render_list += "Blood level: LOW [blood_percent]%, [carbontarget.blood_volume] cl, [span_info("type: [blood_type.name]")]
" + render_list += "Recommendation: [blood_type.restoration_chem::name] supplements or blood transfusion.
" else if(carbontarget.blood_volume <= BLOOD_VOLUME_OKAY) render_list += "Blood level: CRITICAL [blood_percent]%, [carbontarget.blood_volume] cl, [span_info("type: [blood_type]")]
" + render_list += "Recommendation: [blood_type.restoration_chem::name] supplements or blood transfusion.
" else render_list += "Blood level: [blood_percent]%, [carbontarget.blood_volume] cl, type: [blood_type]
" diff --git a/code/game/objects/items/dna_injector.dm b/code/game/objects/items/dna_injector.dm index 2bd19f5e619..5d298ff1db4 100644 --- a/code/game/objects/items/dna_injector.dm +++ b/code/game/objects/items/dna_injector.dm @@ -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"]) diff --git a/code/game/objects/items/food/meatslab.dm b/code/game/objects/items/food/meatslab.dm index c195b76f1ca..d0b79db44fd 100644 --- a/code/game/objects/items/food/meatslab.dm +++ b/code/game/objects/items/food/meatslab.dm @@ -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,\ ) diff --git a/code/modules/admin/create_mob.dm b/code/modules/admin/create_mob.dm index 0c5dea81d54..e7c932ab2be 100644 --- a/code/modules/admin/create_mob.dm +++ b/code/modules/admin/create_mob.dm @@ -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) // SKYRAT EDIT ADDITION BEGIN - CUSTOMIZATION human.dna.species.mutant_bodyparts = human.dna.mutant_bodyparts.Copy() human.dna.species.body_markings = human.dna.body_markings.Copy() @@ -65,7 +65,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) diff --git a/code/modules/admin/smites/cluwndice.dm b/code/modules/admin/smites/cluwndice.dm new file mode 100644 index 00000000000..94355eb98db --- /dev/null +++ b/code/modules/admin/smites/cluwndice.dm @@ -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') diff --git a/code/modules/admin/verbs/list_exposer.dm b/code/modules/admin/verbs/list_exposer.dm index 3c0b6caa256..2f7e3d67d5f 100644 --- a/code/modules/admin/verbs/list_exposer.dm +++ b/code/modules/admin/verbs/list_exposer.dm @@ -36,7 +36,7 @@ for(var/entry in GLOB.human_list) var/mob/living/carbon/human/subject = entry if(subject.ckey) - data += "[subject][subject.dna.unique_enzymes][subject.dna.blood_type]" + data += "[subject][subject.dna.unique_enzymes][subject.dna.blood_type.name]" data += "" var/datum/browser/browser = new(usr, "DNA", "DNA Log", 440, 410) diff --git a/code/modules/antagonists/abductor/equipment/glands/heal.dm b/code/modules/antagonists/abductor/equipment/glands/heal.dm index 13761ba0cdb..9800b4fa308 100644 --- a/code/modules/antagonists/abductor/equipment/glands/heal.dm +++ b/code/modules/antagonists/abductor/equipment/glands/heal.dm @@ -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) diff --git a/code/modules/antagonists/brother/brother.dm b/code/modules/antagonists/brother/brother.dm index b9cf9354e44..c3725212ceb 100644 --- a/code/modules/antagonists/brother/brother.dm +++ b/code/modules/antagonists/brother/brother.dm @@ -145,11 +145,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 diff --git a/code/modules/antagonists/changeling/powers/mutations.dm b/code/modules/antagonists/changeling/powers/mutations.dm index afb028e9947..b04def466a7 100644 --- a/code/modules/antagonists/changeling/powers/mutations.dm +++ b/code/modules/antagonists/changeling/powers/mutations.dm @@ -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 diff --git a/code/modules/antagonists/fugitive/hunters/hunter_gear.dm b/code/modules/antagonists/fugitive/hunters/hunter_gear.dm index 523db5a3214..46d04d7a4e4 100644 --- a/code/modules/antagonists/fugitive/hunters/hunter_gear.dm +++ b/code/modules/antagonists/fugitive/hunters/hunter_gear.dm @@ -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,\ ) diff --git a/code/modules/antagonists/heretic/items/heretic_blades.dm b/code/modules/antagonists/heretic/items/heretic_blades.dm index 41161b75c87..b59741c259a 100644 --- a/code/modules/antagonists/heretic/items/heretic_blades.dm +++ b/code/modules/antagonists/heretic/items/heretic_blades.dm @@ -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,\ ) diff --git a/code/modules/antagonists/obsessed/obsessed.dm b/code/modules/antagonists/obsessed/obsessed.dm index 483933b3b4a..7cb03b8b3a0 100644 --- a/code/modules/antagonists/obsessed/obsessed.dm +++ b/code/modules/antagonists/obsessed/obsessed.dm @@ -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) diff --git a/code/modules/clothing/gloves/_gloves.dm b/code/modules/clothing/gloves/_gloves.dm index 95cca7f2e5d..93fa2e6d0f1 100644 --- a/code/modules/clothing/gloves/_gloves.dm +++ b/code/modules/clothing/gloves/_gloves.dm @@ -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) ..() diff --git a/code/modules/clothing/head/_head.dm b/code/modules/clothing/head/_head.dm index a8690c1f10c..c5c36a03537 100644 --- a/code/modules/clothing/head/_head.dm +++ b/code/modules/clothing/head/_head.dm @@ -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) ..() diff --git a/code/modules/clothing/masks/_masks.dm b/code/modules/clothing/masks/_masks.dm index 35675909f17..9443730df49 100644 --- a/code/modules/clothing/masks/_masks.dm +++ b/code/modules/clothing/masks/_masks.dm @@ -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) ..() diff --git a/code/modules/clothing/neck/_neck.dm b/code/modules/clothing/neck/_neck.dm index ae4e6c6951b..5605cfd0827 100644 --- a/code/modules/clothing/neck/_neck.dm +++ b/code/modules/clothing/neck/_neck.dm @@ -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" diff --git a/code/modules/clothing/shoes/_shoes.dm b/code/modules/clothing/shoes/_shoes.dm index e1ddb1b431b..f5cbee74c41 100644 --- a/code/modules/clothing/shoes/_shoes.dm +++ b/code/modules/clothing/shoes/_shoes.dm @@ -66,9 +66,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 */ //SKYRAT EDIT REMOVAL END /obj/item/clothing/shoes/examine(mob/user) diff --git a/code/modules/clothing/suits/_suits.dm b/code/modules/clothing/suits/_suits.dm index 5932e16063f..df62e190ba6 100644 --- a/code/modules/clothing/suits/_suits.dm +++ b/code/modules/clothing/suits/_suits.dm @@ -51,7 +51,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) ..() diff --git a/code/modules/clothing/under/_under.dm b/code/modules/clothing/under/_under.dm index 0dc059e5ae2..f5726555feb 100644 --- a/code/modules/clothing/under/_under.dm +++ b/code/modules/clothing/under/_under.dm @@ -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)) diff --git a/code/modules/food_and_drinks/machinery/gibber.dm b/code/modules/food_and_drinks/machinery/gibber.dm index 1da68e417e1..5c6e7b6bca7 100644 --- a/code/modules/food_and_drinks/machinery/gibber.dm +++ b/code/modules/food_and_drinks/machinery/gibber.dm @@ -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 diff --git a/code/modules/forensics/_forensics.dm b/code/modules/forensics/_forensics.dm index 450521e6c62..a6801d63dbf 100644 --- a/code/modules/forensics/_forensics.dm +++ b/code/modules/forensics/_forensics.dm @@ -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)) diff --git a/code/modules/forensics/forensics_helpers.dm b/code/modules/forensics/forensics_helpers.dm index 8cb7f721842..e0edfafc9e6 100644 --- a/code/modules/forensics/forensics_helpers.dm +++ b/code/modules/forensics/forensics_helpers.dm @@ -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 /* diff --git a/code/modules/hallucination/blood_flow.dm b/code/modules/hallucination/blood_flow.dm index aaaba4ba2c9..71a58896bf3 100644 --- a/code/modules/hallucination/blood_flow.dm +++ b/code/modules/hallucination/blood_flow.dm @@ -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 diff --git a/code/modules/hydroponics/grown/replicapod.dm b/code/modules/hydroponics/grown/replicapod.dm index 03386e7d1ee..3602d5a9801 100644 --- a/code/modules/hydroponics/grown/replicapod.dm +++ b/code/modules/hydroponics/grown/replicapod.dm @@ -205,7 +205,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 diff --git a/code/modules/hydroponics/seeds.dm b/code/modules/hydroponics/seeds.dm index e5edfb2c0ef..d0cb9870059 100644 --- a/code/modules/hydroponics/seeds.dm +++ b/code/modules/hydroponics/seeds.dm @@ -289,7 +289,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) diff --git a/code/modules/jobs/job_types/clown.dm b/code/modules/jobs/job_types/clown.dm index 314f4931106..569e33728f2 100644 --- a/code/modules/jobs/job_types/clown.dm +++ b/code/modules/jobs/job_types/clown.dm @@ -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 diff --git a/code/modules/mapfluff/ruins/spaceruin_code/meateor.dm b/code/modules/mapfluff/ruins/spaceruin_code/meateor.dm index bd39621eb78..f46b2f78583 100644 --- a/code/modules/mapfluff/ruins/spaceruin_code/meateor.dm +++ b/code/modules/mapfluff/ruins/spaceruin_code/meateor.dm @@ -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,\ ) diff --git a/code/modules/mob/living/basic/alien/_alien.dm b/code/modules/mob/living/basic/alien/_alien.dm index 59aca1021d4..14d74409016 100644 --- a/code/modules/mob/living/basic/alien/_alien.dm +++ b/code/modules/mob/living/basic/alien/_alien.dm @@ -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) + diff --git a/code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart.dm b/code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart.dm index ae66369dd93..513ffde549e 100644 --- a/code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart.dm +++ b/code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart.dm @@ -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,\ ) diff --git a/code/modules/mob/living/blood.dm b/code/modules/mob/living/blood.dm index 18ac35d6733..1f22d4baed8 100644 --- a/code/modules/mob/living/blood.dm +++ b/code/modules/mob/living/blood.dm @@ -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))) @@ -374,14 +367,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. @@ -391,23 +384,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 diff --git a/code/modules/mob/living/blood_types.dm b/code/modules/mob/living/blood_types.dm new file mode 100644 index 00000000000..031eb402f30 --- /dev/null +++ b/code/modules/mob/living/blood_types.dm @@ -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" diff --git a/code/modules/mob/living/brain/brain.dm b/code/modules/mob/living/brain/brain.dm index ccce0269e54..54fbb132db2 100644 --- a/code/modules/mob/living/brain/brain.dm +++ b/code/modules/mob/living/brain/brain.dm @@ -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 diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 917ab423864..7ea50789ab6 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -1481,12 +1481,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 diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm index c6f41d420b5..9b89e649aab 100644 --- a/code/modules/mob/living/carbon/carbon_defense.dm +++ b/code/modules/mob/living/carbon/carbon_defense.dm @@ -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) var/nosound = FALSE //SKYRAT EDIT ADDITION - EMOTES if(on_fire) @@ -338,6 +355,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.")) //SKYRAT EDIT ADDITION BEGIN - EMOTES @@ -385,6 +403,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) @@ -753,4 +772,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 diff --git a/code/modules/mob/living/carbon/carbon_update_icons.dm b/code/modules/mob/living/carbon/carbon_update_icons.dm index 64455eacaf9..b7b55eeaffe 100644 --- a/code/modules/mob/living/carbon/carbon_update_icons.dm +++ b/code/modules/mob/living/carbon/carbon_update_icons.dm @@ -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)) @@ -516,7 +525,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 diff --git a/code/modules/mob/living/carbon/human/_species.dm b/code/modules/mob/living/carbon/human/_species.dm index e085e2a88bd..6cf5adae8d1 100644 --- a/code/modules/mob/living/carbon/human/_species.dm +++ b/code/modules/mob/living/carbon/human/_species.dm @@ -395,20 +395,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 = human_who_gained_species.visual_only_organs, visual_only = human_who_gained_species.visual_only_organs) // SKYRAT EDIT - Allows existing organs to be properly removed when regenerating organs - ORIGINAL: 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) @@ -787,10 +791,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) diff --git a/code/modules/mob/living/carbon/human/dummy.dm b/code/modules/mob/living/carbon/human/dummy.dm index d1fb4596dec..23c2516ecd3 100644 --- a/code/modules/mob/living/carbon/human/dummy.dm +++ b/code/modules/mob/living/carbon/human/dummy.dm @@ -122,7 +122,7 @@ INITIALIZE_IMMEDIATE(/mob/living/carbon/human/dummy) target.dna.features["pod_hair"] = get_consistent_feature_entry(SSaccessories.pod_hair_list) target.dna.features["caps"] = get_consistent_feature_entry(SSaccessories.caps_list) */ // SKYRAT EDIT REMOVAL END - 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 diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 40c6f6df473..1abb299631c 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -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( diff --git a/code/modules/mob/living/carbon/human/human_update_icons.dm b/code/modules/mob/living/carbon/human/human_update_icons.dm index 293f2d54af3..6c954104923 100644 --- a/code/modules/mob/living/carbon/human/human_update_icons.dm +++ b/code/modules/mob/living/carbon/human/human_update_icons.dm @@ -208,9 +208,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 @@ -1238,7 +1242,7 @@ mutant_styles: The mutant style - taur bodytype, STYLE_TESHARI, etc. // SKYRAT E 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() diff --git a/code/modules/mob/living/carbon/human/species_types/ethereal.dm b/code/modules/mob/living/carbon/human/species_types/ethereal.dm index 1bceca3543e..b98e5fc77cf 100644 --- a/code/modules/mob/living/carbon/human/species_types/ethereal.dm +++ b/code/modules/mob/living/carbon/human/species_types/ethereal.dm @@ -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( diff --git a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm index e8c62e1b340..7dbfab5011c 100644 --- a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm @@ -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 diff --git a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm index d935b0863c4..4927f55130e 100644 --- a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm @@ -24,7 +24,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 diff --git a/code/modules/mob/living/carbon/human/species_types/podpeople.dm b/code/modules/mob/living/carbon/human/species_types/podpeople.dm index 4c58a96eef6..1bb39cce4f3 100644 --- a/code/modules/mob/living/carbon/human/species_types/podpeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/podpeople.dm @@ -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 diff --git a/code/modules/mob/living/carbon/human/species_types/snail.dm b/code/modules/mob/living/carbon/human/species_types/snail.dm index edeb28f507e..da4a57cd375 100644 --- a/code/modules/mob/living/carbon/human/species_types/snail.dm +++ b/code/modules/mob/living/carbon/human/species_types/snail.dm @@ -17,6 +17,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, diff --git a/code/modules/mob/living/carbon/human/species_types/vampire.dm b/code/modules/mob/living/carbon/human/species_types/vampire.dm index e2ffbfccd3c..99e869c1cf8 100644 --- a/code/modules/mob/living/carbon/human/species_types/vampire.dm +++ b/code/modules/mob/living/carbon/human/species_types/vampire.dm @@ -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 diff --git a/code/modules/mob/living/carbon/life.dm b/code/modules/mob/living/carbon/life.dm index e793d02a7de..e894d38b17e 100644 --- a/code/modules/mob/living/carbon/life.dm +++ b/code/modules/mob/living/carbon/life.dm @@ -570,7 +570,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 diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 33c4682348e..40def8e0915 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -455,6 +455,7 @@ 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"]!")) // SKYRAT EDIT END + 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) @@ -1150,7 +1151,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 @@ -1162,14 +1163,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 ..() @@ -3127,3 +3128,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 diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm index 837db2ea81f..f7be3dc517b 100644 --- a/code/modules/reagents/chemistry/reagents/other_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm @@ -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 /* SKYRAT EDIT - Rebalancing blood for Hemophages - ORIGINAL: @@ -79,12 +82,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) @@ -159,7 +169,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)) @@ -1236,11 +1246,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." diff --git a/code/modules/reagents/reagent_containers/blood_pack.dm b/code/modules/reagents/reagent_containers/blood_pack.dm index 994eaa5ab99..8349141e32a 100644 --- a/code/modules/reagents/reagent_containers/blood_pack.dm +++ b/code/modules/reagents/reagent_containers/blood_pack.dm @@ -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)) diff --git a/code/modules/religion/burdened/psyker.dm b/code/modules/religion/burdened/psyker.dm index 2a6f863f725..8dd8c3bf040 100644 --- a/code/modules/religion/burdened/psyker.dm +++ b/code/modules/religion/burdened/psyker.dm @@ -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. diff --git a/code/modules/religion/religion_sects.dm b/code/modules/religion/religion_sects.dm index aa5de910cf5..41882eb356c 100644 --- a/code/modules/religion/religion_sects.dm +++ b/code/modules/religion/religion_sects.dm @@ -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) diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm index 66df0d4680d..047844fe78d 100644 --- a/code/modules/surgery/bodyparts/_bodyparts.dm +++ b/code/modules/surgery/bodyparts/_bodyparts.dm @@ -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 @@ -213,6 +213,8 @@ /// If we even wanna try and handle icons/overlays of the limb (Taurs don't, f.e.). See update_body_parts var/show_icon = TRUE // SKYRAT EDIT END + /// 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) . = ..() @@ -244,6 +246,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() @@ -962,6 +966,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 @@ -1044,12 +1056,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 @@ -1081,7 +1100,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) @@ -1091,7 +1110,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) diff --git a/code/modules/surgery/bodyparts/dismemberment.dm b/code/modules/surgery/bodyparts/dismemberment.dm index 34595198354..2a0b1a4ec91 100644 --- a/code/modules/surgery/bodyparts/dismemberment.dm +++ b/code/modules/surgery/bodyparts/dismemberment.dm @@ -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) @@ -98,6 +101,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) diff --git a/code/modules/surgery/bodyparts/head.dm b/code/modules/surgery/bodyparts/head.dm index 711138bf8f1..d49f3f4dea3 100644 --- a/code/modules/surgery/bodyparts/head.dm +++ b/code/modules/surgery/bodyparts/head.dm @@ -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) diff --git a/code/modules/surgery/organs/_organ.dm b/code/modules/surgery/organs/_organ.dm index 28efa5e4e36..ba3a351e0c5 100644 --- a/code/modules/surgery/organs/_organ.dm +++ b/code/modules/surgery/organs/_organ.dm @@ -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, \ diff --git a/code/modules/surgery/organs/organ_movement.dm b/code/modules/surgery/organs/organ_movement.dm index 47edbea1b69..2f5127e3131 100644 --- a/code/modules/surgery/organs/organ_movement.dm +++ b/code/modules/surgery/organs/organ_movement.dm @@ -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) @@ -281,7 +281,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? */ diff --git a/code/modules/surgery/surgery_step.dm b/code/modules/surgery/surgery_step.dm index aa5dab1e97b..38285e8ec28 100644 --- a/code/modules/surgery/surgery_step.dm +++ b/code/modules/surgery/surgery_step.dm @@ -240,6 +240,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) diff --git a/code/modules/unit_tests/quirks.dm b/code/modules/unit_tests/quirks.dm index 8816d6aec53..4a036ab8169 100644 --- a/code/modules/unit_tests/quirks.dm +++ b/code/modules/unit_tests/quirks.dm @@ -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])) diff --git a/code/modules/vehicles/cars/clowncar.dm b/code/modules/vehicles/cars/clowncar.dm index d4378ea7e83..a0828bf8a55 100644 --- a/code/modules/vehicles/cars/clowncar.dm +++ b/code/modules/vehicles/cars/clowncar.dm @@ -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) diff --git a/code/modules/vending/_vending.dm b/code/modules/vending/_vending.dm index 91e6e44a2d3..70ecf091c07 100644 --- a/code/modules/vending/_vending.dm +++ b/code/modules/vending/_vending.dm @@ -1053,7 +1053,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 diff --git a/icons/effects/64x64.dmi b/icons/effects/64x64.dmi index 2ae6d40fc33..ebf768329f9 100644 Binary files a/icons/effects/64x64.dmi and b/icons/effects/64x64.dmi differ diff --git a/icons/effects/blood.dmi b/icons/effects/blood.dmi index fb31da10ee9..420737cf34c 100644 Binary files a/icons/effects/blood.dmi and b/icons/effects/blood.dmi differ diff --git a/icons/effects/footprints.dmi b/icons/effects/footprints.dmi index a00c6a0f780..cef2898e893 100644 Binary files a/icons/effects/footprints.dmi and b/icons/effects/footprints.dmi differ diff --git a/icons/mob/effects/bleed_overlays.dmi b/icons/mob/effects/bleed_overlays.dmi index 68c167e11ef..f376eb05f03 100644 Binary files a/icons/mob/effects/bleed_overlays.dmi and b/icons/mob/effects/bleed_overlays.dmi differ diff --git a/icons/mob/effects/dam_mob.dmi b/icons/mob/effects/dam_mob.dmi index 6390a1b34d6..cc719551bc7 100644 Binary files a/icons/mob/effects/dam_mob.dmi and b/icons/mob/effects/dam_mob.dmi differ diff --git a/icons/obj/machines/kitchen.dmi b/icons/obj/machines/kitchen.dmi index 1bd254942b2..3c20847256f 100644 Binary files a/icons/obj/machines/kitchen.dmi and b/icons/obj/machines/kitchen.dmi differ diff --git a/icons/obj/machines/recycling.dmi b/icons/obj/machines/recycling.dmi index 9a6dffaaee2..770ee59b139 100644 Binary files a/icons/obj/machines/recycling.dmi and b/icons/obj/machines/recycling.dmi differ diff --git a/tgstation.dme b/tgstation.dme index f9c3a7f7dbf..f8ee9954f12 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -3146,6 +3146,7 @@ #include "code\modules\admin\smites\boneless.dm" #include "code\modules\admin\smites\brain_damage.dm" #include "code\modules\admin\smites\bsa.dm" +#include "code\modules\admin\smites\cluwndice.dm" #include "code\modules\admin\smites\curse_of_babel.dm" #include "code\modules\admin\smites\dock_pay.dm" #include "code\modules\admin\smites\dust.dm" @@ -5042,6 +5043,7 @@ #include "code\modules\mob\eye\camera\camera.dm" #include "code\modules\mob\eye\camera\remote.dm" #include "code\modules\mob\living\blood.dm" +#include "code\modules\mob\living\blood_types.dm" #include "code\modules\mob\living\damage_procs.dm" #include "code\modules\mob\living\death.dm" #include "code\modules\mob\living\emote.dm"