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
+38 -69
View File
@@ -11,18 +11,34 @@
throw_range = 5
w_class = WEIGHT_CLASS_TINY
/// Modified to damage caused from injection, currently unused though
var/damage_coeff = 1
var/list/fields
var/list/add_mutations = list()
var/list/remove_mutations = list()
/// Adds all mutations in this list to the injected mob, activating it rather than mutating it if possible.
var/list/add_mutations
/// If the injected mob has any mutations in this list activated or mutated, they will be removed.
var/list/remove_mutations
/// Tracks if it's been used
VAR_FINAL/used = FALSE
/// Duration of the mutations added/activated or DNA changes. Does not affect removed mutations.
var/duration = INFINITY
/// A DNA datum that has its fields copied to the target on injection.
var/datum/dna/stored_dna
var/used = FALSE
/obj/item/dnainjector/Initialize(mapload)
/obj/item/dnainjector/Initialize(mapload, datum/dna/stored_dna, damage_coeff = 1)
. = ..()
AddElement(/datum/element/update_icon_updates_onmob)
if(used)
update_appearance()
src.stored_dna = stored_dna
src.damage_coeff = damage_coeff
/obj/item/dnainjector/Destroy()
if(stored_dna?.holder)
stack_trace("DNA injector got owned DNA somehow")
stored_dna = null
else
QDEL_NULL(stored_dna)
return ..()
/obj/item/dnainjector/vv_edit_var(vname, vval)
. = ..()
@@ -43,27 +59,26 @@
/obj/item/dnainjector/proc/inject(mob/living/carbon/target, mob/user)
if(!target.can_mutate())
return FALSE
if(target.stat == DEAD) //prevents dead people from having their DNA changed
to_chat(user, span_notice("You can't modify [target]'s DNA while [target.p_theyre()] dead."))
return FALSE
for(var/removed_mutation in remove_mutations)
target.dna.remove_mutation(removed_mutation, list(MUTATION_SOURCE_ACTIVATED, MUTATION_SOURCE_MUTATOR))
for(var/added_mutation in add_mutations)
if(added_mutation == /datum/mutation/race)
message_admins("[ADMIN_LOOKUPFLW(user)] injected [key_name_admin(target)] with \the [src] [span_danger("(MONKEY)")]")
target.dna.remove_mutation(removed_mutation, GLOB.standard_mutation_sources)
for(var/datum/mutation/added_mutation as anything in add_mutations)
if(added_mutation::warn_admins_on_inject && target != user && !ismonkey(target))
message_admins("[ADMIN_LOOKUPFLW(user)] injected [key_name_admin(target)] with [src] containing [added_mutation::name].")
if(target.dna.mutation_in_sequence(added_mutation))
target.dna.activate_mutation(added_mutation)
if(duration != INFINITY)
addtimer(CALLBACK(target.dna, TYPE_PROC_REF(/datum/dna, remove_mutation), added_mutation, MUTATION_SOURCE_ACTIVATED), duration)
else
target.dna.add_mutation(added_mutation, MUTATION_SOURCE_MUTATOR)
if(fields)
if(fields["name"] && fields["UE"] && fields["blood_type"])
target.real_name = fields["name"]
target.dna.unique_enzymes = fields["UE"]
target.name = target.real_name
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"])
target.dna.unique_features = merge_text(target.dna.unique_features, fields["UF"])
if(fields["UI"] || fields["UF"])
target.updateappearance(mutcolor_update = TRUE, mutations_overlay_update = TRUE)
if(duration != INFINITY)
addtimer(CALLBACK(target.dna, TYPE_PROC_REF(/datum/dna, remove_mutation), added_mutation, MUTATION_SOURCE_MUTATOR), duration)
if(stored_dna)
target.apply_status_effect(/datum/status_effect/temporary_transformation/dna_injector, duration, stored_dna)
return TRUE
/obj/item/dnainjector/attack(mob/target, mob/user)
@@ -100,53 +115,7 @@
update_appearance()
/obj/item/dnainjector/timed
var/duration = 60 SECONDS
/obj/item/dnainjector/timed/inject(mob/living/carbon/target, mob/user)
if(target.stat == DEAD) //prevents dead people from having their DNA changed
to_chat(user, span_notice("You can't modify [target]'s DNA while [target.p_theyre()] dead."))
return FALSE
if(!target.can_mutate())
return FALSE
var/endtime = world.time + duration
for(var/mutation in remove_mutations)
target.dna.remove_mutation(mutation, list(MUTATION_SOURCE_ACTIVATED, MUTATION_SOURCE_MUTATOR))
for(var/mutation in add_mutations)
if(target.dna.get_mutation(mutation))
continue //Skip permanent mutations we already have.
if(mutation == /datum/mutation/race && !ismonkey(target))
message_admins("[ADMIN_LOOKUPFLW(user)] injected [key_name_admin(target)] with \the [src] [span_danger("(MONKEY)")]")
target.dna.add_mutation(mutation, MUTATION_SOURCE_TIMED_INJECTOR)
addtimer(CALLBACK(target.dna, TYPE_PROC_REF(/datum/dna, remove_mutation), mutation, MUTATION_SOURCE_TIMED_INJECTOR), duration)
if(fields)
if(fields["name"] && fields["UE"] && fields["blood_type"])
LAZYINITLIST(target.dna.previous)
if(!target.dna.previous["name"])
target.dna.previous["name"] = target.real_name
if(!target.dna.previous["UE"])
target.dna.previous["UE"] = target.dna.unique_enzymes
if(!target.dna.previous["blood_type"])
target.dna.previous["blood_type"] = target.get_bloodtype()
target.real_name = fields["name"]
target.dna.unique_enzymes = fields["UE"]
target.name = target.real_name
target.set_blood_type(fields["blood_type"])
LAZYSET(target.dna.temporary_mutations, UE_CHANGED, endtime)
if(fields["UI"]) //UI+UE
LAZYINITLIST(target.dna.previous)
if(!target.dna.previous["UI"])
target.dna.previous["UI"] = target.dna.unique_identity
target.dna.unique_identity = merge_text(target.dna.unique_identity, fields["UI"])
LAZYSET(target.dna.temporary_mutations, UI_CHANGED, endtime)
if(fields["UF"]) //UI+UE
LAZYINITLIST(target.dna.previous)
if(!target.dna.previous["UF"])
target.dna.previous["UF"] = target.dna.unique_features
target.dna.unique_features = merge_text(target.dna.unique_features, fields["UF"])
LAZYSET(target.dna.temporary_mutations, UF_CHANGED, endtime)
if(fields["UI"] || fields["UF"])
target.updateappearance(mutcolor_update = TRUE, mutations_overlay_update = TRUE)
return TRUE
duration = 60 SECONDS
/obj/item/dnainjector/timed/hulk
name = "\improper DNA injector (Hulk)"
@@ -263,12 +263,10 @@
/mob/living/basic/soulscythe/Initialize(mapload)
. = ..()
add_traits(list(TRAIT_ASHSTORM_IMMUNE, TRAIT_SNOWSTORM_IMMUNE, TRAIT_LAVA_IMMUNE), INNATE_TRAIT)
RegisterSignal(src, COMSIG_LIVING_LIFE, PROC_REF(on_life))
/mob/living/basic/soulscythe/proc/on_life(datum/source, seconds_per_tick) // done like this because there's no need to go through all of life since the item does the work anyways
/mob/living/basic/soulscythe/Life(seconds_per_tick)
if(stat == CONSCIOUS)
adjust_blood_volume(round(1 * seconds_per_tick), maximum = MAX_BLOOD_LEVEL)
return COMPONENT_LIVING_CANCEL_LIFE_PROCESSING
/// Special projectile for the soulscythe.
/obj/projectile/soulscythe