Files
Bubberstation/code/modules/forensics/forensics_helpers.dm
SmArtKar b4061f1800 [MDB IGNORE] Blood Refactor Chapter 2: Collector's Edition (#91054)
## About The Pull Request

Refactors most of blood handling code untouched by #90593 and completely
rewrites all blood decals, components and reagents.

- Blood types now have behavioral flags which allow them to control
where they leave decals/DNA/viruses. Oil no longer transfers DNA and
viruses with it, while podpeople water-blood doesn't leave visible
decals on turfs and items, but still can be picked up by DNA scanners.
- Multiple blood types have received unique handling - liquid
electricity blood now glows in the dark, oil trails are flammable and
lube ones are slippery. Oil blood can be restored with fuel, lube with
silicon and slime with stable plasma (as normal plasma already passively
regenerates their blood), instead of everything using iron. Saline
solution only supplements on iron-based blood and won't do anything to
help with bloodloss for species who rely on different blood types.
(Roundstart this applies only to Ethereals)
- All blood logic has been moved away from the blood reagent itself into
a blood element that is assigned to the blood reagent by default, and to
any reagent that's drawn from a mob as their "blood" (in
``transfer_blood_to``). This means that blood you draw from lizards will
be green and have lizard's blood description instead of mentioning red
blood cells, Ethereal "blood" will actually contain their DNA and genes,
etc.
- Refactored all blood decals. Blood states are no more, everything is
now handled via blood DNA. Credits to MrMelbert and Maplestation, as a
significant amount of code has been taken from
https://github.com/MrMelbert/MapleStationCode/pull/436 and many of his
followup PRs. Oil and xenomorph splatters are now subtypes of blood,
blood drying is now animated, blood trails now curve and can be
diagonal.
- Rewrote bloodysoles and bloody_spreader components, credits to Melbert
again for the former, while latter now makes more sense with its
interactions. Bloody soles no longer share blood DNA with your hands.
- Ported Melbert's bloody footprint sprites and bot-blood-spreading
functionality.
- Removed all species-side reagent interactions, instead they're handled
by said species' livers. (This previously included exotic blood
handling, thus the removal)
- Slightly optimized human rendering by removing inbetween overlay
holders for clothing when they're not needed.
- Blood-transmitted diseases will now get added to many more decals than
before.
- Cleaned up and partially refactored replica pods, fixed an issue where
monkeys/manipulators were unable to harvest mindless pods.
- Exotic bloodtype on species now automatically assigns their blood
reagent, without the need to assign them separately.
- Clown mobs now bleed (with colorful reagent instead of blood during
april fools), and so do vatbeasts (lizard blood)
- Implemented generic procs for handling bleeding checks, all sorts of
scanners now also correctly call your blood for what it is.
- Podpeople's guts are now lime-green like their organs, instead of
being weirdly greyish like their water-blood. (Their bleeding overlays
are still grey, as they're bleeding water)
- Slimepeople now can bleed. Their jelly is pale purple in color, but
their wound overlays copy their body color.
- Injecting/spraying/splashing/etc mob with a reagent preserves its
data, so you could theoretically recycle fine wines from someone's
bloodstream
- Fixed burdened chaplain's sect never actually giving a blessing when
applying effects, and giving a blessing when nothing can be healed.
Inverted check strikes again.

- Closes #91039 

#### Examples

A lot of blood here has dried, visually the blood colors are almost
exactly the same as before either of the blood refactors.


![dreamseeker_BSP7FE9pRB](https://github.com/user-attachments/assets/45711fa0-ae65-4ec2-9e89-753fa7dd876f)

![dreamseeker_zyv9ssh5VN](https://github.com/user-attachments/assets/7b112854-b7e3-4bfe-b78b-199a55b5b051)
2025-05-31 19:38:07 -05:00

257 lines
8.5 KiB
Plaintext

/// Adds a list of fingerprints to the atom
/atom/proc/add_fingerprint_list(list/fingerprints_to_add) //ASSOC LIST FINGERPRINT = FINGERPRINT
if (QDELETED(src))
return
if (isnull(fingerprints_to_add))
return
if (forensics)
forensics.inherit_new(fingerprints = fingerprints_to_add)
else
forensics = new(src, fingerprints = fingerprints_to_add)
return TRUE
/// Adds a single fingerprint to the atom
/atom/proc/add_fingerprint(mob/suspect, ignoregloves = FALSE) //Set ignoregloves to add prints irrespective of the mob having gloves on.
if (QDELETED(src))
return
if (isnull(forensics))
forensics = new(src)
forensics.add_fingerprint(suspect, ignoregloves)
return TRUE
/// Add a list of fibers to the atom
/atom/proc/add_fiber_list(list/fibers_to_add) //ASSOC LIST FIBERTEXT = FIBERTEXT
if (QDELETED(src))
return
if (isnull(fibers_to_add))
return
if (forensics)
forensics.inherit_new(fibers = fibers_to_add)
else
forensics = new(src, fibers = fibers_to_add)
return TRUE
/// Adds a single fiber to the atom
/atom/proc/add_fibers(mob/living/carbon/human/suspect)
if (QDELETED(src))
return
var/old = 0
if(suspect.gloves && istype(suspect.gloves, /obj/item/clothing))
var/obj/item/clothing/gloves/suspect_gloves = suspect.gloves
old = GET_ATOM_BLOOD_DNA_LENGTH(suspect_gloves)
if(suspect_gloves.transfer_blood > 1) //bloodied gloves transfer blood to touched objects
if(add_blood_DNA(GET_ATOM_BLOOD_DNA(suspect_gloves)) && GET_ATOM_BLOOD_DNA_LENGTH(suspect_gloves) > old) //only reduces the bloodiness of our gloves if the item wasn't already bloody
suspect_gloves.transfer_blood -= 1
else if(suspect.blood_in_hands > 1)
old = GET_ATOM_BLOOD_DNA_LENGTH(suspect)
if(add_blood_DNA(GET_ATOM_BLOOD_DNA(suspect)) && GET_ATOM_BLOOD_DNA_LENGTH(suspect) > old)
suspect.blood_in_hands -= 1
if (isnull(forensics))
forensics = new(src)
forensics.add_fibers(suspect)
return TRUE
/// Adds a list of hiddenprints to the atom
/atom/proc/add_hiddenprint_list(list/hiddenprints_to_add) //NOTE: THIS IS FOR ADMINISTRATION FINGERPRINTS, YOU MUST CUSTOM SET THIS TO INCLUDE CKEY/REAL NAMES! CHECK FORENSICS.DM
if (QDELETED(src))
return
if (isnull(hiddenprints_to_add))
return
if (forensics)
forensics.inherit_new(hiddenprints = hiddenprints_to_add)
else
forensics = new(src, hiddenprints = hiddenprints_to_add)
return TRUE
/// Adds a single hiddenprint to the atom
/atom/proc/add_hiddenprint(mob/suspect)
if (QDELETED(src))
return
if (isnull(forensics))
forensics = new(src)
forensics.add_hiddenprint(suspect)
return TRUE
/// Fetch current blood color
/atom/proc/get_blood_dna_color()
if (cached_blood_color)
return cached_blood_color
var/list/blood_DNA = GET_ATOM_BLOOD_DECALS(src)
if (!length(blood_DNA))
return
cached_blood_color = get_color_from_blood_list(blood_DNA)
return cached_blood_color
/// Check if we have any emissive blood on us
/// is_worn - When TRUE, we're fetching the value for mob overlays, in which case we bypass the cache
/atom/proc/get_blood_emissive_alpha(is_worn = FALSE)
if (cached_blood_emissive && !is_worn)
return cached_blood_emissive
var/list/blood_DNA = GET_ATOM_BLOOD_DECALS(src)
if (!length(blood_DNA))
return 0
var/blood_alpha = 0
for (var/blood_key in blood_DNA)
var/datum/blood_type/blood_type = blood_DNA[blood_key]
blood_alpha += blood_type.get_emissive_alpha(src, is_worn)
blood_alpha /= length(blood_DNA)
if (!is_worn)
cached_blood_emissive = blood_alpha
return blood_alpha
/// Adds blood dna to the atom
/atom/proc/add_blood_DNA(list/blood_DNA_to_add, list/datum/disease/diseases) //ASSOC LIST DNA = BLOODTYPE
return FALSE
/obj/add_blood_DNA(list/blood_DNA_to_add, list/datum/disease/diseases)
if (QDELETED(src))
return
. = ..()
if (isnull(blood_DNA_to_add))
return .
cached_blood_color = null
cached_blood_emissive = null
if (forensics)
forensics.inherit_new(blood_DNA = blood_DNA_to_add)
else
forensics = new(src, blood_DNA = blood_DNA_to_add)
return TRUE
/obj/effect/decal/cleanable/blood/add_blood_DNA(list/blood_DNA_to_add, list/datum/disease/diseases)
var/first_dna = GET_ATOM_BLOOD_DNA_LENGTH(src)
if(!..())
return FALSE
if(dried)
return TRUE
// Imperfect, ends up with some blood types being double-set-up, but harmless (for now)
for(var/blood_key in blood_DNA_to_add)
var/datum/blood_type/blood_type = blood_DNA_to_add[blood_key]
// We shouldn't ever get no valid blood types with BLOOD_COVER_TURFS and first_dna == 0 here so we're safe
if(blood_type.blood_flags & BLOOD_COVER_TURFS)
blood_type.set_up_blood(src, first_dna == 0)
add_diseases(diseases)
update_appearance()
add_atom_colour(get_blood_dna_color(), FIXED_COLOUR_PRIORITY)
return TRUE
/obj/item/add_blood_DNA(list/blood_DNA_to_add, list/datum/disease/diseases)
if(item_flags & NO_BLOOD_ON_ITEM)
return FALSE
return ..()
/obj/item/clothing/gloves/add_blood_DNA(list/blood_dna, list/datum/disease/diseases)
. = ..()
if (. && has_blood_flag(blood_dna, BLOOD_COVER_ITEMS))
transfer_blood = min(transfer_blood, rand(2, 4))
/turf/add_blood_DNA(list/blood_dna, list/datum/disease/diseases)
var/obj/effect/decal/cleanable/blood/splatter/blood_splatter = locate() in src
var/blood_flags = has_blood_flag(blood_dna, BLOOD_COVER_TURFS|BLOOD_ADD_DNA|BLOOD_TRANSFER_VIRAL_DATA)
if(!blood_splatter)
if(blood_flags & BLOOD_COVER_TURFS)
blood_splatter = new /obj/effect/decal/cleanable/blood/splatter(src, diseases, blood_dna)
else
if(blood_flags & BLOOD_ADD_DNA)
blood_splatter.add_blood_DNA(blood_dna)
if(blood_flags & BLOOD_TRANSFER_VIRAL_DATA)
blood_splatter.add_diseases(diseases)
return !QDELETED(blood_splatter) ? blood_splatter : null
/turf/closed/add_blood_DNA(list/blood_dna, list/datum/disease/diseases)
return FALSE
/obj/item/clothing/under/add_blood_DNA(list/blood_DNA_to_add, list/datum/disease/diseases)
. = ..()
if(!.)
return
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, diseases = diseases)
/// 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,
list/datum/disease/diseases,
)
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_covered_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, diseases)
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)
cached_blood_color = null
cached_blood_emissive = null
if(!has_blood_flag(blood_DNA_to_add, BLOOD_COVER_MOBS))
update_clothing(slots_to_bloody)
return
if(dirty_hands || dirty_feet || !length(all_worn))
if(isnull(forensics))
forensics = new(src)
forensics.inherit_new(blood_DNA = blood_DNA_to_add)
if(dirty_hands)
blood_in_hands = min(blood_in_hands, rand(2, 4))
if(dirty_feet)
AddComponent(/datum/component/bloodysoles/feet, blood_DNA_to_add) // Add blood to our feet
update_clothing(slots_to_bloody)
return TRUE
/mob/living/add_blood_DNA(list/blood_DNA_to_add, list/datum/disease/diseases)
if(QDELING(src))
return FALSE
if(!length(blood_DNA_to_add))
return FALSE
if(isnull(forensics))
forensics = new(src)
cached_blood_color = null
cached_blood_emissive = null
forensics.inherit_new(blood_DNA = blood_DNA_to_add)
return TRUE
/*
* Transfer all the fingerprints and hidden prints from [src] to [transfer_to].
*/
/atom/proc/transfer_fingerprints_to(atom/transfer_to)
transfer_to.add_fingerprint_list(GET_ATOM_FINGERPRINTS(src))
transfer_to.add_hiddenprint_list(GET_ATOM_HIDDENPRINTS(src))
transfer_to.fingerprintslast = fingerprintslast
/*
* Transfer all the fibers from [src] to [transfer_to].
*/
/atom/proc/transfer_fibers_to(atom/transfer_to)
transfer_to.add_fiber_list(GET_ATOM_FIBRES(src))