From 4ba1520df8543dc63259d33a374cce4f6cd75192 Mon Sep 17 00:00:00 2001
From: Bloop <13398309+vinylspiders@users.noreply.github.com>
Date: Mon, 28 Apr 2025 01:57:59 -0400
Subject: [PATCH] There will be (colorful) blood: datumizes bloodtypes,
greyscales blood sprites, and fixes a lot of inconsistencies with gibs and
forensic data (#90593)
## About The Pull Request
This PR:
- Converts all of the blood types into their own datums, which can be
set up to have their own colors, descriptions, and other fun unique
properties. For example, the clown blood that is constantly randomizing
itself.
- Converts all the blood decals into greyscale, which in turn eliminates
the need for separate xeno sprites. They both use the same ones now.
- Audit of blood splatters/gibs/bodyparts/organs to make sure that they
are getting the correct forensic data applied to them.
- For the admins: Adds a clown blood smite.
My primary goal with was to make the appearance of the new sprites look
almost indistinguishable to the original ones.
I consider this a "first pass", as in there are still some further
refactors I would like to do on the backend side, but am satisfied with
it enough to push it forward as a first step towards a better blood
system! I didn't want to do too much at once because of A) fatigue and
B) easier to test things to make sure I'm not breaking something
important this way.
This has been test-merged on Nova for over a week now and has been going
great, so I finally got around to upstreaming the bones to TG. Although
I did test it a bit you may want to TM it just in case I missed some
things when copying it over.
---
.../SpaceRuins/infested_frigate.dmm | 2 +-
_maps/modular_generic/beach_l_ribs.dmm | 8 +-
code/__DEFINES/colors.dm | 6 +
code/__DEFINES/mobs.dm | 22 ++
code/__DEFINES/surgery.dm | 2 +
code/__HELPERS/atoms.dm | 3 +
code/__HELPERS/mobs.dm | 15 +-
code/_globalvars/lists/mobs.dm | 12 +
code/_onclick/item_attack.dm | 36 +--
code/datums/components/blood_walk.dm | 1 +
code/datums/components/bloody_spreader.dm | 9 +-
code/datums/components/bloodysoles.dm | 25 +-
code/datums/dna.dm | 11 +-
code/datums/elements/decals/blood.dm | 2 +-
code/datums/materials/meat.dm | 2 +-
.../negative_quirks/blood_deficiency.dm | 20 +-
code/datums/quirks/neutral_quirks/evil.dm | 28 ++
code/datums/records/manifest.dm | 4 +-
code/game/atom/_atom.dm | 27 +-
code/game/machinery/computer/dna_console.dm | 4 +-
.../machinery/computer/operating_computer.dm | 10 +-
.../machinery/computer/records/medical.dm | 2 +-
code/game/machinery/dna_scanner.dm | 4 +
code/game/machinery/medical_kiosk.dm | 4 +-
code/game/machinery/recycler.dm | 37 ++-
code/game/objects/effects/decals/cleanable.dm | 3 +
.../effects/decals/cleanable/aliens.dm | 25 +-
.../effects/decals/cleanable/humans.dm | 185 +++++++------
.../objects/effects/decals/cleanable/misc.dm | 5 +-
.../objects/effects/spawners/gibspawner.dm | 16 +-
.../temporary_visuals/miscellaneous.dm | 12 +-
.../items/devices/scanners/autopsy_scanner.dm | 7 +-
.../items/devices/scanners/health_analyzer.dm | 9 +-
code/game/objects/items/dna_injector.dm | 4 +-
code/game/objects/items/food/meatslab.dm | 5 +-
code/modules/admin/create_mob.dm | 4 +-
code/modules/admin/smites/cluwndice.dm | 14 +
code/modules/admin/verbs/list_exposer.dm | 2 +-
.../abductor/equipment/glands/heal.dm | 1 +
code/modules/antagonists/brother/brother.dm | 8 +-
.../changeling/powers/mutations.dm | 2 +-
.../fugitive/hunters/hunter_gear.dm | 2 +-
.../heretic/items/heretic_blades.dm | 2 +-
code/modules/antagonists/obsessed/obsessed.dm | 4 +-
code/modules/clothing/gloves/_gloves.dm | 4 +-
code/modules/clothing/head/_head.dm | 8 +-
code/modules/clothing/masks/_masks.dm | 4 +-
code/modules/clothing/neck/_neck.dm | 4 +-
code/modules/clothing/shoes/_shoes.dm | 8 +-
code/modules/clothing/suits/_suits.dm | 4 +-
code/modules/clothing/under/_under.dm | 4 +-
.../food_and_drinks/machinery/gibber.dm | 47 ++--
code/modules/forensics/_forensics.dm | 2 +-
code/modules/forensics/forensics_helpers.dm | 61 +++--
code/modules/hallucination/blood_flow.dm | 3 +-
code/modules/hydroponics/grown/replicapod.dm | 11 +-
code/modules/hydroponics/seeds.dm | 2 +-
code/modules/jobs/job_types/clown.dm | 4 +
.../mapfluff/ruins/spaceruin_code/meateor.dm | 2 +-
code/modules/mob/living/basic/alien/_alien.dm | 7 +
.../space_fauna/meteor_heart/meteor_heart.dm | 2 +-
code/modules/mob/living/blood.dm | 65 ++---
code/modules/mob/living/blood_types.dm | 247 ++++++++++++++++++
code/modules/mob/living/brain/brain.dm | 2 +-
code/modules/mob/living/carbon/carbon.dm | 21 ++
.../mob/living/carbon/carbon_defense.dm | 22 ++
.../mob/living/carbon/carbon_update_icons.dm | 15 +-
.../mob/living/carbon/human/_species.dm | 34 ++-
code/modules/mob/living/carbon/human/dummy.dm | 2 +-
code/modules/mob/living/carbon/human/human.dm | 2 +-
.../living/carbon/human/human_update_icons.dm | 10 +-
.../carbon/human/species_types/ethereal.dm | 2 +-
.../carbon/human/species_types/jellypeople.dm | 1 +
.../human/species_types/lizardpeople.dm | 2 +-
.../carbon/human/species_types/podpeople.dm | 1 +
.../carbon/human/species_types/snail.dm | 1 +
.../carbon/human/species_types/vampire.dm | 2 +-
code/modules/mob/living/carbon/life.dm | 2 +-
code/modules/mob/living/living.dm | 11 +-
.../chemistry/reagents/other_reagents.dm | 27 +-
.../reagents/reagent_containers/blood_pack.dm | 39 +--
code/modules/religion/burdened/psyker.dm | 2 +-
code/modules/religion/religion_sects.dm | 6 +-
code/modules/surgery/bodyparts/_bodyparts.dm | 33 ++-
.../surgery/bodyparts/dismemberment.dm | 6 +-
code/modules/surgery/bodyparts/head.dm | 2 +-
code/modules/surgery/organs/_organ.dm | 3 +-
code/modules/surgery/organs/organ_movement.dm | 6 +-
code/modules/surgery/surgery_step.dm | 5 +
code/modules/unit_tests/quirks.dm | 8 +-
code/modules/vehicles/cars/clowncar.dm | 5 +-
code/modules/vending/_vending.dm | 2 +-
icons/effects/64x64.dmi | Bin 406291 -> 397311 bytes
icons/effects/blood.dmi | Bin 166748 -> 177719 bytes
icons/effects/footprints.dmi | Bin 3962 -> 1793 bytes
icons/mob/effects/bleed_overlays.dmi | Bin 7058 -> 7004 bytes
icons/mob/effects/dam_mob.dmi | Bin 16182 -> 16656 bytes
icons/obj/machines/kitchen.dmi | Bin 20263 -> 18786 bytes
icons/obj/machines/recycling.dmi | Bin 53931 -> 43940 bytes
tgstation.dme | 2 +
100 files changed, 1007 insertions(+), 364 deletions(-)
create mode 100644 code/modules/admin/smites/cluwndice.dm
create mode 100644 code/modules/mob/living/blood_types.dm
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 2ae6d40fc33fb497c5902ec3d9193b49308457c4..ebf768329f957db0925ea23aa47b56c38f40405f 100644
GIT binary patch
literal 397311
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