Streamlines Life() a little (#96215)

## About The Pull Request

Main changes

- `handle_mutations` is gone, DNA Injectors are now managed via a status
effect
- `handle_diseases` is gone, disease stages are just handled via life
signal
- `handle_bodyparts` is gone, it was unused and in the future any
implementations should use a life signal
- `spec_life` is gone, the main content of it is now in `human/Life`,
most children implementations now use life signal, zombie tongues now
handle zombie groans
- Life signal was split in two (pre and active)

Other changes

- DNA injector code was cleaned up considerably
- HARS now alerts admins when you inject someone else with it like
Monkey
- `COPY_DNA_SE` is no longer mistakenly unused (meaning stuff like
transformation sting no longer copies "active mutations")

## Why It's Good For The Game

Across the course of a full round we spend the same amount of time doing
literally nothing in life as we spend on handling human breathing.

Now in the context of a full round this is 8 seconds. Which in the grand
scheme of things, not a whole lot, but if we can get a tiny performance
gain from... not doing literally nothing (especially when we can do
these things cleaner with signals) that's a win in my book

## Changelog

🆑 Melbert
refactor: Refactored dna injectors (both the ones that change appearance
and activate mutations), report any oddities with them like failing to
revert your appearance or mutations not applying correctly
code: Ever so slightly changed how diseases tick, report any oddities
code: Ever so slightly changed how some species mechanics tick, like
golems and slimes, report any oddities
code: The code behind printing appearance modifying dna injectors from
genetics has changed, report any oddities
code: Some backend transformation sting code changed slightly, report
any oddities
code: Zombie "idle" groaning is now tied to the tongue rather than the
species itself
admin: Force-injecting someone with HARS give an admin alert, the same
as force-injecting someone with Monkey
fix: Several methods of copying DNA (including transformation sting)
mistakenly copied "active mutations", this has been fixed
/🆑

---------

Co-authored-by: John Willard <53777086+JohnFulpWillard@users.noreply.github.com>
This commit is contained in:
MrMelbert
2026-05-29 23:48:16 -05:00
committed by GitHub
parent 7cf48344fb
commit e68c1edad1
26 changed files with 270 additions and 301 deletions
-1
View File
@@ -91,7 +91,6 @@
/// Updates effects that rely on blood volume or status, like blood HUDs.
/mob/living/proc/update_blood_effects()
living_flags &= ~BLOOD_UPDATE_QUEUED
blood_hud_set_status()
/// Updates effects that rely on whether the mob can have blood.
@@ -542,11 +542,6 @@ GLOBAL_LIST_EMPTY(features_by_species)
return new_features
/datum/species/proc/spec_life(mob/living/carbon/human/H, seconds_per_tick)
SHOULD_CALL_PARENT(TRUE)
if(HAS_TRAIT(H, TRAIT_NOBREATH) && (H.health < H.crit_threshold) && !HAS_TRAIT(H, TRAIT_NOCRITDAMAGE))
H.adjust_brute_loss(0.5 * seconds_per_tick)
/datum/species/proc/can_equip(obj/item/I, slot, disable_warning, mob/living/carbon/human/H, bypass_equip_delay_self = FALSE, ignore_equipped = FALSE, indirect_action = FALSE)
if(no_equip_flags & slot)
if(!I.species_exception || !is_type_in_list(src, I.species_exception))
@@ -696,7 +696,7 @@
if(heal_flags & HEAL_NEGATIVE_MUTATIONS)
for(var/datum/mutation/existing_mutation in dna.mutations)
if(existing_mutation.quality != POSITIVE && existing_mutation.remove_on_aheal)
dna.remove_mutation(existing_mutation, list(MUTATION_SOURCE_ACTIVATED, MUTATION_SOURCE_MUTATOR, MUTATION_SOURCE_TIMED_INJECTOR))
dna.remove_mutation(existing_mutation, GLOB.standard_mutation_sources)
if(heal_flags & HEAL_TEMP)
set_coretemperature(get_body_temp_normal(apply_change = FALSE))
+3 -3
View File
@@ -23,7 +23,6 @@
return
. = ..()
if(QDELETED(src))
return FALSE
@@ -45,8 +44,9 @@
handle_heart(seconds_per_tick)
// Handles liver failure effects, if we lack a liver
handle_liver(seconds_per_tick)
// For special species interactions
dna.species.spec_life(src, seconds_per_tick)
// Crit damage but specifically for people who don't get suffocate while in crit so they can actually die eventually
if(HAS_TRAIT(src, TRAIT_NOBREATH) && (health < crit_threshold) && !HAS_TRAIT(src, TRAIT_NOCRITDAMAGE))
adjust_brute_loss(0.5 * seconds_per_tick)
return stat != DEAD
/mob/living/carbon/human/calculate_affecting_pressure(pressure)
@@ -66,6 +66,7 @@
RegisterSignal(human_who_gained_species, COMSIG_CARBON_DEFIB_HEART_CHECK, PROC_REF(defib_check))
RegisterSignal(human_who_gained_species, COMSIG_ATOM_ITEM_INTERACTION, PROC_REF(rebuild_check))
RegisterSignal(human_who_gained_species, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
RegisterSignal(human_who_gained_species, COMSIG_LIVING_LIFE, PROC_REF(on_life))
// nutrition = health, so give people a head start
human_who_gained_species.set_nutrition(NUTRITION_LEVEL_WELL_FED)
@@ -81,14 +82,16 @@
COMSIG_CARBON_DEFIB_HEART_CHECK,
COMSIG_ATOM_ITEM_INTERACTION,
COMSIG_ATOM_EXAMINE,
COMSIG_LIVING_LIFE,
))
human_who_lost_species.physiology.stamina_mod /= 0.6
human_who_lost_species.physiology.stun_mod /= 0.6
human_who_lost_species.physiology.knockdown_mod /= 1.2
/datum/species/golem/spec_life(mob/living/carbon/human/source, seconds_per_tick)
. = ..()
/datum/species/golem/proc/on_life(mob/living/carbon/human/source, seconds_per_tick)
SIGNAL_HANDLER
if(source.nutrition <= 20)
// this is "hard crit" for golems
source.Unconscious(1.5 SECONDS * seconds_per_tick)
@@ -215,7 +215,10 @@
// so if someone mindswapped into them, they'd still be shared.
bodies = null
C.set_blood_volume(C.get_blood_volume(), maximum = BLOOD_VOLUME_NORMAL)
UnregisterSignal(C, COMSIG_LIVING_DEATH)
UnregisterSignal(C, list(
COMSIG_LIVING_DEATH,
COMSIG_LIVING_LIFE,
))
..()
/datum/species/jelly/slime/on_species_gain(mob/living/carbon/C, datum/species/old_species, pref_load, regenerate_icons)
@@ -232,6 +235,7 @@
bodies |= C
RegisterSignal(C, COMSIG_LIVING_DEATH, PROC_REF(on_death_move_body))
RegisterSignal(C, COMSIG_LIVING_LIFE, PROC_REF(on_life))
/datum/species/jelly/slime/proc/on_death_move_body(mob/living/carbon/human/source, gibbed)
SIGNAL_HANDLER
@@ -255,16 +259,16 @@
/datum/species/jelly/slime/copy_properties_from(datum/species/jelly/slime/old_species)
bodies = old_species.bodies
/datum/species/jelly/slime/spec_life(mob/living/carbon/human/H, seconds_per_tick)
. = ..()
if(H.get_blood_volume() >= BLOOD_VOLUME_SLIME_SPLIT)
/datum/species/jelly/slime/proc/on_life(mob/living/carbon/human/source, seconds_per_tick)
SIGNAL_HANDLER
if(source.get_blood_volume() >= BLOOD_VOLUME_SLIME_SPLIT)
if(SPT_PROB(2.5, seconds_per_tick))
to_chat(H, span_notice("You feel very bloated!"))
to_chat(source, span_notice("You feel very bloated!"))
else if(H.nutrition >= NUTRITION_LEVEL_WELL_FED)
H.adjust_blood_volume(1.5 * seconds_per_tick)
if(H.get_blood_volume() <= BLOOD_VOLUME_LOSE_NUTRITION)
H.adjust_nutrition(-1.25 * seconds_per_tick)
else if(source.nutrition >= NUTRITION_LEVEL_WELL_FED)
source.adjust_blood_volume(1.5 * seconds_per_tick)
if(source.get_blood_volume() <= BLOOD_VOLUME_LOSE_NUTRITION)
source.adjust_nutrition(-1.25 * seconds_per_tick)
/datum/action/innate/split_body
name = "Split Body"
@@ -33,16 +33,21 @@
new_vampire.skin_tone = "albino"
RegisterSignal(new_vampire, COMSIG_ATOM_ATTACKBY, PROC_REF(on_attackby))
RegisterSignal(new_vampire, COMSIG_MOB_HUD_CREATED, PROC_REF(on_hud_created))
RegisterSignal(new_vampire, COMSIG_LIVING_LIFE, PROC_REF(on_life))
if(new_vampire.hud_used)
on_hud_created(new_vampire)
/datum/species/human/vampire/on_species_loss(mob/living/carbon/human/old_vampire, datum/species/new_species, pref_load)
. = ..()
UnregisterSignal(old_vampire, COMSIG_ATOM_ATTACKBY)
UnregisterSignal(old_vampire, list(
COMSIG_ATOM_ATTACKBY,
COMSIG_MOB_HUD_CREATED,
COMSIG_LIVING_LIFE,
))
old_vampire.hud_used?.remove_screen_object(HUD_MOB_BLOOD_LEVEL)
/datum/species/human/vampire/spec_life(mob/living/carbon/human/vampire, seconds_per_tick)
. = ..()
/datum/species/human/vampire/proc/on_life(mob/living/carbon/human/vampire, seconds_per_tick)
SIGNAL_HANDLER
if(istype(vampire.loc, /obj/structure/closet/crate/coffin))
var/need_mob_update = FALSE
need_mob_update += vampire.heal_overall_damage(brute = 2 * seconds_per_tick, burn = 2 * seconds_per_tick, updating_health = FALSE, required_bodytype = BODYTYPE_ORGANIC)
@@ -47,15 +47,6 @@
BODY_ZONE_R_LEG = /obj/item/bodypart/leg/right/zombie
)
/// Spooky growls we sometimes play while alive
var/static/list/spooks = list(
'sound/effects/hallucinations/growl1.ogg',
'sound/effects/hallucinations/growl2.ogg',
'sound/effects/hallucinations/growl3.ogg',
'sound/effects/hallucinations/veryfar_noise.ogg',
'sound/effects/hallucinations/wail.ogg',
)
/// Zombies do not stabilize body temperature they are the walking dead and are cold blooded
/datum/species/zombie/body_temperature_core(mob/living/carbon/human/humi, seconds_per_tick)
return
@@ -178,11 +169,6 @@
/datum/species/zombie/infectious/spec_stun(mob/living/carbon/human/H,amount)
return min(2 SECONDS, amount)
/datum/species/zombie/infectious/spec_life(mob/living/carbon/carbon_mob, seconds_per_tick)
. = ..()
if(!HAS_TRAIT(carbon_mob, TRAIT_CRITICAL_CONDITION) && SPT_PROB(2, seconds_per_tick))
playsound(carbon_mob, pick(spooks), 50, TRUE, 10)
// Weaker subtype - less healing, weaker attacks, etc
/datum/species/zombie/infectious/mindless
name = "Mindless Infectious Zombie"
+3 -50
View File
@@ -13,6 +13,9 @@
if(HAS_TRAIT(src, TRAIT_STASIS))
. = ..()
if(QDELETED(src))
return
reagents?.handle_stasis_chems(src, seconds_per_tick)
else
//Reagent processing needs to come before breathing, to prevent edge cases.
@@ -31,9 +34,6 @@
GLOB.addictions[key].process_addiction(src, seconds_per_tick)
handle_brain_damage(seconds_per_tick)
if(stat != DEAD)
handle_bodyparts(seconds_per_tick)
if(stat != DEAD)
return TRUE
@@ -501,10 +501,6 @@
return COMPONENT_NO_EXPOSE_REAGENTS
/mob/living/carbon/proc/handle_bodyparts(seconds_per_tick)
for(var/obj/item/bodypart/limb as anything in get_bodyparts(include_stumps = TRUE))
. |= limb.on_life(seconds_per_tick)
/mob/living/carbon/proc/handle_organs(seconds_per_tick)
if(stat == DEAD)
if(reagents && (reagents.has_reagent(/datum/reagent/toxin/formaldehyde, 1) || reagents.has_reagent(/datum/reagent/cryostylane))) // No organ decay if the body contains formaldehyde.
@@ -526,49 +522,6 @@
if(organ?.owner) // This exist mostly because reagent metabolization can cause organ reshuffling
organ.on_life(seconds_per_tick)
/mob/living/carbon/handle_diseases(seconds_per_tick)
for(var/datum/disease/disease as anything in diseases)
if(QDELETED(disease)) //Got cured/deleted while the loop was still going.
continue
if(stat != DEAD || disease.process_dead)
disease.stage_act(seconds_per_tick)
/mob/living/carbon/handle_mutations(time_since_irradiated, seconds_per_tick)
if(!LAZYLEN(dna?.temporary_mutations))
return
for(var/mut, mut_data in dna.temporary_mutations)
if(mut_data < world.time)
if(!LAZYLEN(dna.previous))
continue
if(mut == UI_CHANGED)
if(dna.previous["UI"])
dna.unique_identity = merge_text(dna.unique_identity,dna.previous["UI"])
updateappearance(mutations_overlay_update=1)
dna.previous.Remove("UI")
LAZYREMOVE(dna.temporary_mutations, mut)
continue
if(mut == UF_CHANGED)
if(dna.previous["UF"])
dna.unique_features = merge_text(dna.unique_features,dna.previous["UF"])
updateappearance(mutcolor_update=1, mutations_overlay_update=1)
dna.previous.Remove("UF")
LAZYREMOVE(dna.temporary_mutations, mut)
continue
if(mut == UE_CHANGED)
if(dna.previous["name"])
real_name = dna.previous["name"]
name = real_name
dna.previous.Remove("name")
if(dna.previous["UE"])
dna.unique_enzymes = dna.previous["UE"]
dna.previous.Remove("UE")
if(dna.previous["blood_type"])
set_blood_type(dna.previous["blood_type"])
dna.previous.Remove("blood_type")
LAZYREMOVE(dna.temporary_mutations, mut)
continue
/**
* Returns a multiplier representing how effectively this mob can regenerate blood
*
+7 -16
View File
@@ -13,7 +13,7 @@
set waitfor = FALSE
SHOULD_NOT_SLEEP(TRUE)
var/signal_result = SEND_SIGNAL(src, COMSIG_LIVING_LIFE, seconds_per_tick)
var/signal_result = SEND_SIGNAL(src, COMSIG_LIVING_PRE_LIFE, seconds_per_tick)
if(signal_result & COMPONENT_LIVING_CANCEL_LIFE_PROCESSING) // mmm less work
return
@@ -41,18 +41,15 @@
if(isnull(loc) || HAS_TRAIT(src, TRAIT_NO_TRANSFORM))
return
SEND_SIGNAL(src, COMSIG_LIVING_LIFE, seconds_per_tick)
if(QDELETED(src)) // signal handlers such as diseases could delete the mob
return
if(!HAS_TRAIT(src, TRAIT_STASIS))
if(stat != DEAD)
//Mutations and radiation
handle_mutations(seconds_per_tick)
//Breathing, if applicable
handle_breathing(seconds_per_tick)
handle_diseases(seconds_per_tick) // DEAD check is in the proc itself; we want it to spread even if the mob is dead, but to handle its disease-y properties only if you're not.
if (QDELETED(src)) // Diseases can qdel the mob via transformations
return
// Handle temperature/pressure differences between body and environment
var/datum/gas_mixture/environment = loc.return_air()
if(environment)
@@ -64,21 +61,15 @@
update_nutrition()
living_flags &= ~QUEUE_NUTRITION_UPDATE
if (living_flags & BLOOD_UPDATE_QUEUED)
if(living_flags & BLOOD_UPDATE_QUEUED)
update_blood_effects()
living_flags &= ~BLOOD_UPDATE_QUEUED
if(stat != DEAD)
return TRUE
/mob/living/proc/handle_breathing(seconds_per_tick)
SEND_SIGNAL(src, COMSIG_LIVING_HANDLE_BREATHING, seconds_per_tick)
return
/mob/living/proc/handle_mutations(seconds_per_tick)
return
/mob/living/proc/handle_diseases(seconds_per_tick)
return
// Base mob environment handler for body temperature
/mob/living/proc/handle_environment(datum/gas_mixture/environment, seconds_per_tick)
@@ -1309,7 +1309,7 @@
if (ismonkey(human_mob))
if (!HAS_TRAIT(human_mob, TRAIT_BORN_MONKEY))
//This is the only time mutadone should remove monkeyism
human_mob.dna.remove_mutation(/datum/mutation/race, list(MUTATION_SOURCE_ACTIVATED, MUTATION_SOURCE_MUTATOR))
human_mob.dna.remove_mutation(/datum/mutation/race, GLOB.standard_mutation_sources)
else if (HAS_TRAIT(human_mob, TRAIT_BORN_MONKEY))
human_mob.monkeyize()
@@ -661,10 +661,6 @@
update_icon_dropped()
//Return TRUE to get whatever mob this is in to update health.
/obj/item/bodypart/proc/on_life(seconds_per_tick)
SHOULD_CALL_PARENT(TRUE)
/**
* #receive_damage
*
@@ -420,6 +420,14 @@
disliked_foodtypes = NONE
// List of english words that translate to zombie phrases
var/static/list/english_to_zombie = list()
/// Spooky growls we sometimes play while alive
var/static/list/spooks = list(
'sound/effects/hallucinations/growl1.ogg',
'sound/effects/hallucinations/growl2.ogg',
'sound/effects/hallucinations/growl3.ogg',
'sound/effects/hallucinations/veryfar_noise.ogg',
'sound/effects/hallucinations/wail.ogg',
)
/obj/item/organ/tongue/zombie/proc/add_word_to_translations(english_word, zombie_word)
english_to_zombie[english_word] = zombie_word
@@ -475,6 +483,11 @@
message = capitalize(message)
speech_args[SPEECH_MESSAGE] = message
/obj/item/organ/tongue/zombie/on_life(seconds_per_tick)
. = ..()
if(owner.stat == CONSCIOUS && SPT_PROB(2, seconds_per_tick))
playsound(owner, pick(spooks), 50, TRUE, 10)
/obj/item/organ/tongue/alien
name = "alien tongue"
desc = "According to leading xenobiologists the evolutionary benefit of having a second mouth in your mouth is \"that it looks badass\"."