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 22:48:16 -06:00
committed by GitHub
co-authored by John Willard
parent 7cf48344fb
commit e68c1edad1
26 changed files with 270 additions and 301 deletions
+11 -1
View File
@@ -96,6 +96,7 @@
/datum/disease/proc/register_disease_signals()
if(isnull(affected_mob))
return
RegisterSignal(affected_mob, COMSIG_LIVING_LIFE, PROC_REF(on_life))
if(spread_flags & DISEASE_SPREAD_AIRBORNE)
RegisterSignal(affected_mob, COMSIG_CARBON_PRE_BREATHE, PROC_REF(on_breath))
@@ -103,12 +104,21 @@
/datum/disease/proc/unregister_disease_signals()
if(isnull(affected_mob))
return
UnregisterSignal(affected_mob, COMSIG_CARBON_PRE_BREATHE)
UnregisterSignal(affected_mob, list(COMSIG_LIVING_LIFE, COMSIG_CARBON_PRE_BREATHE))
// Proc to determine if the virus can resist natural recovery
/datum/disease/proc/get_recovery_failure_chance()
return 0
/datum/disease/proc/on_life(datum/source, seconds_per_tick)
SIGNAL_HANDLER
PRIVATE_PROC(TRUE)
if(HAS_TRAIT(affected_mob, TRAIT_STASIS) || QDELETED(src) || (affected_mob.stat == DEAD && !process_dead))
return
stage_act(seconds_per_tick)
///Proc to process the disease and decide on whether to advance, cure or make the symptoms appear. Returns a boolean on whether to continue acting on the symptoms or not.
/datum/disease/proc/stage_act(seconds_per_tick)
var/slowdown = HAS_TRAIT(affected_mob, TRAIT_VIRUS_RESISTANCE) ? 0.5 : 1 // spaceacillin slows stage speed by 50%
+6 -11
View File
@@ -4,7 +4,7 @@
*/
GLOBAL_LIST_INIT(total_ui_len_by_block, populate_total_ui_len_by_block())
GLOBAL_LIST_INIT(standard_mutation_sources, list(MUTATION_SOURCE_ACTIVATED, MUTATION_SOURCE_MUTATOR, MUTATION_SOURCE_TIMED_INJECTOR))
GLOBAL_LIST_INIT(standard_mutation_sources, list(MUTATION_SOURCE_ACTIVATED, MUTATION_SOURCE_MUTATOR))
/proc/populate_total_ui_len_by_block()
. = list()
@@ -44,10 +44,6 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block())
var/real_name
///All mutations are from now on here
var/list/mutations
///Temporary changes to the UE
var/list/temporary_mutations
///For temporary name/ui/ue/blood_type modifications
var/list/previous
var/mob/living/holder
///List of which mutations this carbon has and its assigned block
var/mutation_index[DNA_MUTATION_BLOCKS]
@@ -77,8 +73,6 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block())
QDEL_NULL(species)
LAZYNULL(mutations) //This only references mutations, just dereference.
LAZYNULL(temporary_mutations) //^
LAZYNULL(previous) //^
return ..()
@@ -89,9 +83,9 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block())
new_dna.unique_features = unique_features
new_dna.features = features.Copy()
new_dna.real_name = real_name
new_dna.temporary_mutations = LAZYLISTDUPLICATE(temporary_mutations)
new_dna.mutation_index = mutation_index
new_dna.default_mutation_genes = default_mutation_genes
if(transfer_flags & COPY_DNA_SE)
new_dna.mutation_index = mutation_index
new_dna.default_mutation_genes = default_mutation_genes
//if the new DNA has a holder, transform them immediately, otherwise save it
if(new_dna.holder)
if (iscarbon(new_dna.holder))
@@ -155,7 +149,7 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block())
var/datum/mutation/actual_mutation = get_mutation(mutation_to_remove)
if(!actual_mutation || !(sources & actual_mutation.sources))
return
return FALSE
actual_mutation.sources -= sources
@@ -169,6 +163,7 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block())
qdel(actual_mutation)
update_instability(FALSE)
return TRUE
/datum/dna/proc/check_mutation(mutation_type)
return get_mutation(mutation_type)
+2 -3
View File
@@ -89,9 +89,8 @@
var/list/valid_chrom_list = list()
/// List of traits that are added or removed by the mutation with GENETIC_TRAIT source.
var/list/mutation_traits
/datum/mutation/New()
. = ..()
/// if TRUE admins get alerted when someone force-injects someone else with this mutation
var/warn_admins_on_inject = FALSE
/datum/mutation/Destroy()
power_path = null
+2
View File
@@ -246,6 +246,7 @@
instability = NEGATIVE_STABILITY_MAJOR // mmmonky
remove_on_aheal = FALSE
locked = TRUE //Species specific, keep out of actual gene pool
warn_admins_on_inject = TRUE
var/datum/species/original_species = /datum/species/human
var/original_name
@@ -533,6 +534,7 @@
difficulty = 12 //pretty good for traitors
quality = NEGATIVE //holy shit no eyes or tongue or ears
text_gain_indication = span_warning("Something feels off.")
warn_admins_on_inject = TRUE
/datum/mutation/headless/on_acquiring()
. = ..()
+1 -1
View File
@@ -60,7 +60,7 @@
/datum/action/cooldown/spell/void/cursed/proc/on_life(mob/living/source, seconds_per_tick)
SIGNAL_HANDLER
if(!isliving(source) || HAS_TRAIT(source, TRAIT_STASIS) || source.stat == DEAD || HAS_TRAIT(source, TRAIT_NO_TRANSFORM))
if(HAS_TRAIT(source, TRAIT_STASIS) || source.stat == DEAD)
return
if(!is_valid_target(source))
+1 -4
View File
@@ -84,10 +84,7 @@
/datum/quirk/item_quirk/asthma/proc/on_life(mob/living/source, seconds_per_tick)
SIGNAL_HANDLER
if (quirk_holder.stat == DEAD)
return
if (HAS_TRAIT(quirk_holder, TRAIT_STASIS) || HAS_TRAIT(quirk_holder, TRAIT_NO_TRANSFORM))
if (quirk_holder.stat == DEAD || HAS_TRAIT(quirk_holder, TRAIT_STASIS))
return
var/obj/item/organ/lungs/holder_lungs = quirk_holder.get_organ_slot(ORGAN_SLOT_LUNGS)
@@ -5,10 +5,12 @@
tick_interval = STATUS_EFFECT_NO_TICK
duration = 1 MINUTES // set in on creation, this just needs to be any value to process
alert_type = null
/// Flags used to determine what all we're copying over
VAR_PROTECTED/copy_dna_flags = COPY_DNA_SPECIES
/// A reference to a COPY of the DNA that the mob will be transformed into.
var/datum/dna/new_dna
VAR_PRIVATE/datum/dna/new_dna
/// A reference to a COPY of the DNA of the mob prior to transformation.
var/datum/dna/old_dna
VAR_PRIVATE/datum/dna/old_dna
/datum/status_effect/temporary_transformation/Destroy()
. = ..() // parent must be called first, so we clear DNA refs AFTER transforming back... yeah i know
@@ -16,10 +18,14 @@
QDEL_NULL(old_dna)
/datum/status_effect/temporary_transformation/on_creation(mob/living/new_owner, new_duration = 1 MINUTES, datum/dna/dna_to_copy)
if(!iscarbon(new_owner) || isnull(dna_to_copy))
qdel(src)
return
src.duration = new_duration
src.new_dna = new()
src.old_dna = new()
dna_to_copy.copy_dna(new_dna)
init_dna(new_owner, dna_to_copy)
return ..()
/datum/status_effect/temporary_transformation/on_apply()
@@ -30,27 +36,39 @@
if(!transforming.has_dna())
return FALSE
// Save the old DNA
transforming.dna.copy_dna(old_dna)
// Makes them into the new DNA
new_dna.copy_dna(transforming.dna, COPY_DNA_SPECIES)
transforming.real_name = new_dna.real_name
transforming.name = transforming.get_visible_name()
transforming.updateappearance(mutcolor_update = TRUE)
transforming.domutcheck()
save_dna()
apply_dna()
return TRUE
/datum/status_effect/temporary_transformation/on_remove()
var/mob/living/carbon/transforming = owner
if(!QDELING(owner)) // Don't really need to do appearance stuff if we're being deleted
old_dna.copy_dna(transforming.dna, COPY_DNA_SPECIES)
old_dna.copy_dna(transforming.dna, copy_dna_flags)
transforming.updateappearance(mutcolor_update = TRUE)
transforming.domutcheck()
transforming.real_name = old_dna.real_name // Name is fine though
transforming.name = transforming.get_visible_name()
/// Called when initializing the DNA that the mob is transforming into
/datum/status_effect/temporary_transformation/proc/init_dna(mob/living/carbon/new_owner, datum/dna/dna_to_copy)
dna_to_copy.copy_dna(new_dna, copy_dna_flags)
/// Called when saving the mob's DNA before transformation
/datum/status_effect/temporary_transformation/proc/save_dna()
var/mob/living/carbon/transforming = owner
transforming.dna.copy_dna(old_dna, copy_dna_flags)
/// Applies the DNA to the mob
/datum/status_effect/temporary_transformation/proc/apply_dna()
var/mob/living/carbon/transforming = owner
new_dna.copy_dna(transforming.dna, copy_dna_flags)
transforming.real_name = new_dna.real_name
transforming.name = transforming.get_visible_name()
transforming.updateappearance(mutcolor_update = TRUE)
transforming.domutcheck()
/datum/status_effect/temporary_transformation/trans_sting
/// Tracks the time left on the effect when the owner last died. Used to pause the effect.
var/time_before_pause = -1
@@ -89,3 +107,35 @@
else if(time_before_pause != -1)
duration = time_before_pause
time_before_pause = -1
/datum/status_effect/temporary_transformation/dna_injector
id = "temp_dna_injector_transformation"
status_type = STATUS_EFFECT_MULTIPLE
copy_dna_flags = NONE // no touching species or mutations
// when initting dna, any unset fields are copied from the mob's dna (so nothing changes effectively)
/datum/status_effect/temporary_transformation/dna_injector/init_dna(mob/living/carbon/new_owner, datum/dna/dna_to_copy)
. = ..()
new_dna.real_name ||= new_owner.dna.real_name
new_dna.unique_enzymes ||= new_owner.dna.unique_enzymes
new_dna.unique_features ||= new_owner.dna.unique_features
new_dna.unique_identity ||= new_owner.dna.unique_identity
new_dna.blood_type ||= new_owner.dna.blood_type
// just to put something there, it'll get updated if UF does anyways
new_dna.features = new_owner.dna.features.Copy()
// ensure secondary transformation make a copy of the original dna (to prevent latter effects that expire earlier from returning to the wrong dna)
/datum/status_effect/temporary_transformation/dna_injector/save_dna()
for(var/datum/status_effect/temporary_transformation/dna_injector/other_effect in owner.status_effects)
other_effect.old_dna.copy_dna(src.old_dna, copy_dna_flags)
return
return ..()
// when the effect ends, see if there's any other active effects, and re-apply them if necessary
/datum/status_effect/temporary_transformation/dna_injector/on_remove()
. = ..()
if(QDELING(owner))
return
for(var/datum/status_effect/temporary_transformation/dna_injector/other_effect in owner.status_effects)
other_effect.apply_dna()