From e68c1edad1d7bd44cd8aa85b11df5c101edcc3f8 Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Fri, 29 May 2026 23:48:16 -0500 Subject: [PATCH] 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 :cl: 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 /:cl: --------- Co-authored-by: John Willard <53777086+JohnFulpWillard@users.noreply.github.com> --- code/__DEFINES/DNA.dm | 10 +- .../signals/signals_mob/signals_mob_living.dm | 4 +- code/datums/diseases/_disease.dm | 12 +- code/datums/dna/dna.dm | 17 +- code/datums/mutations/_mutations.dm | 5 +- code/datums/mutations/body.dm | 2 + code/datums/mutations/void_magnet.dm | 2 +- code/datums/quirks/negative_quirks/asthma.dm | 5 +- .../debuffs/dna_transformation.dm | 74 ++++++-- code/game/machinery/computer/dna_console.dm | 164 +++++++++--------- .../experimental_cloner_pod.dm | 2 +- code/game/objects/items/dna_injector.dm | 107 ++++-------- .../items/weaponry/melee/soulscythe.dm | 4 +- code/modules/mob/living/blood.dm | 1 - .../mob/living/carbon/human/_species.dm | 5 - code/modules/mob/living/carbon/human/human.dm | 2 +- code/modules/mob/living/carbon/human/life.dm | 6 +- .../carbon/human/species_types/golems.dm | 7 +- .../carbon/human/species_types/jellypeople.dm | 22 ++- .../carbon/human/species_types/vampire.dm | 11 +- .../carbon/human/species_types/zombies.dm | 14 -- code/modules/mob/living/carbon/life.dm | 53 +----- code/modules/mob/living/life.dm | 23 +-- .../chemistry/reagents/medicine_reagents.dm | 2 +- code/modules/surgery/bodyparts/_bodyparts.dm | 4 - .../surgery/organs/internal/tongue/_tongue.dm | 13 ++ 26 files changed, 270 insertions(+), 301 deletions(-) diff --git a/code/__DEFINES/DNA.dm b/code/__DEFINES/DNA.dm index cf146f19572..2df4cfe03d0 100644 --- a/code/__DEFINES/DNA.dm +++ b/code/__DEFINES/DNA.dm @@ -21,8 +21,6 @@ #define MUTATION_SOURCE_ACTIVATED "activated" ///Source for mutations that have been added via mutators #define MUTATION_SOURCE_MUTATOR "mutator" -///From timed dna injectors. -#define MUTATION_SOURCE_TIMED_INJECTOR "timed_injector" ///From mob/living/carbon/human/proc/crewlike_monkify() #define MUTATION_SOURCE_CREW_MONKEY "crew_monkey" #define MUTATION_SOURCE_MEDIEVAL_CTF "medieval_ctf" @@ -78,14 +76,14 @@ #define FEATURE_TAILSPINES "tailspines" // Different from regular spines, these appear on tails #define FEATURE_LEGS "legs" -///flag for the transfer_flag argument from dna/proc/copy_dna(). This one makes it so the SE is copied too. +// flag for the transfer_flag argument from dna/proc/copy_dna(). +/// Copies SE (mob's innate mutations) #define COPY_DNA_SE (1<<0) -///flag for the transfer_flag argument from dna/proc/copy_dna(). This one copies the species. +/// Copies the species. #define COPY_DNA_SPECIES (1<<1) -///flag for the transfer_flag argument from dna/proc/copy_dna(). This one copies the mutations. +/// Copies active mutations and anything mutated from other means #define COPY_DNA_MUTATIONS (1<<2) - //organ slots #define ORGAN_SLOT_ADAMANTINE_RESONATOR "adamantine_resonator" #define ORGAN_SLOT_APPENDIX "appendix" diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm index 942706689e8..b44d42ceede 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm @@ -61,9 +61,11 @@ ///from base of mob/living/set_usable_legs() #define COMSIG_LIVING_LIMBLESS_SLOWDOWN "living_limbless_slowdown" ///From living/Life(). (deltatime) -#define COMSIG_LIVING_LIFE "living_life" +#define COMSIG_LIVING_PRE_LIFE "living_pre_life" /// Block the Life() proc from proceeding... this should really only be done in some really wacky situations. #define COMPONENT_LIVING_CANCEL_LIFE_PROCESSING (1<<0) +///From living/Life(). (deltatime) +#define COMSIG_LIVING_LIFE "living_life" ///From living/set_resting(): (new_resting, silent, instant) #define COMSIG_LIVING_RESTING "living_resting" diff --git a/code/datums/diseases/_disease.dm b/code/datums/diseases/_disease.dm index 8cfbf47aff9..e8054369d88 100644 --- a/code/datums/diseases/_disease.dm +++ b/code/datums/diseases/_disease.dm @@ -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% diff --git a/code/datums/dna/dna.dm b/code/datums/dna/dna.dm index 989882b4f45..effe0bd1ce5 100644 --- a/code/datums/dna/dna.dm +++ b/code/datums/dna/dna.dm @@ -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) diff --git a/code/datums/mutations/_mutations.dm b/code/datums/mutations/_mutations.dm index 8c8b30f98bb..7f88a696b5b 100644 --- a/code/datums/mutations/_mutations.dm +++ b/code/datums/mutations/_mutations.dm @@ -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 diff --git a/code/datums/mutations/body.dm b/code/datums/mutations/body.dm index c20046d3fe2..efdab6a92d2 100644 --- a/code/datums/mutations/body.dm +++ b/code/datums/mutations/body.dm @@ -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() . = ..() diff --git a/code/datums/mutations/void_magnet.dm b/code/datums/mutations/void_magnet.dm index 122fab4e198..aef24d19629 100644 --- a/code/datums/mutations/void_magnet.dm +++ b/code/datums/mutations/void_magnet.dm @@ -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)) diff --git a/code/datums/quirks/negative_quirks/asthma.dm b/code/datums/quirks/negative_quirks/asthma.dm index 1c4e53640f9..31532b2a3fe 100644 --- a/code/datums/quirks/negative_quirks/asthma.dm +++ b/code/datums/quirks/negative_quirks/asthma.dm @@ -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) diff --git a/code/datums/status_effects/debuffs/dna_transformation.dm b/code/datums/status_effects/debuffs/dna_transformation.dm index bd2a079d6f8..15a96bca42d 100644 --- a/code/datums/status_effects/debuffs/dna_transformation.dm +++ b/code/datums/status_effects/debuffs/dna_transformation.dm @@ -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() diff --git a/code/game/machinery/computer/dna_console.dm b/code/game/machinery/computer/dna_console.dm index d336c2fc9f8..45ebe295cfb 100644 --- a/code/game/machinery/computer/dna_console.dm +++ b/code/game/machinery/computer/dna_console.dm @@ -1303,93 +1303,19 @@ // number later // params["type"] - Type of injector to create // Expected results: - // "ue" - Unique Enzyme, changes name and blood type + // "ue" - Unique Enzyme, changes name and blood type // "ui" - Unique Identity, changes looks // "uf" - Unique Features, changes mutant bodyparts and mutcolors // "mixed" - Combination of both ue and ui if("makeup_injector") if(!COOLDOWN_FINISHED(src, enzyme_copy_timer)) return - // Convert the index to a number and clamp within the array range, then - // copy the data from the disk to that buffer - var/buffer_index = text2num(params["index"]) - buffer_index = clamp(buffer_index, 1, NUMBER_OF_BUFFERS) - var/list/buffer_slot = genetic_makeup_buffer[buffer_index] - - // GUARD CHECK - This shouldn't be possible to execute this on a null - // buffer. Unexpected resut - if(!istype(buffer_slot)) + // Convert the index to a number and clamp within the array range, then copy the data from the disk to that buffer + var/buffer_index = clamp(text2num(params["index"]), 1, NUMBER_OF_BUFFERS) + if(!make_cosmetic_dna_injector(dna_injector_type_to_flag(params["type"]), genetic_makeup_buffer[buffer_index])) + to_chat(usr, span_warning("Genetic data corrupted, unable to create injector.")) return - - var/type = params["type"] - var/obj/item/dnainjector/timed/I - - switch(type) - if("ui") - // GUARD CHECK - There's currently no way to save partial genetic data. - // However, if this is the case, we can't make a complete injector and - // this catches that edge case - if(!buffer_slot["UI"]) - to_chat(usr,span_warning("Genetic data corrupted, unable to create injector.")) - return - - I = new /obj/item/dnainjector/timed(loc) - I.fields = list("UI"=buffer_slot["UI"]) - - // If there is a connected scanner, we can use its upgrades to reduce - // the genetic damage generated by this injector - if(scanner_operational()) - I.damage_coeff = connected_scanner.damage_coeff - if("ue") - // GUARD CHECK - There's currently no way to save partial genetic data. - // However, if this is the case, we can't make a complete injector and - // this catches that edge case - if(!buffer_slot["name"] || !buffer_slot["UE"] || !buffer_slot["blood_type"]) - to_chat(usr,span_warning("Genetic data corrupted, unable to create injector.")) - return - - I = new /obj/item/dnainjector/timed(loc) - I.fields = list("name"=buffer_slot["name"], "UE"=buffer_slot["UE"], "blood_type"=buffer_slot["blood_type"]) - - // If there is a connected scanner, we can use its upgrades to reduce - // the genetic damage generated by this injector - if(scanner_operational()) - I.damage_coeff = connected_scanner.damage_coeff - if("uf") - // GUARD CHECK - There's currently no way to save partial genetic data. - // However, if this is the case, we can't make a complete injector and - // this catches that edge case - if(!buffer_slot["name"] || !buffer_slot["UF"] || !buffer_slot["blood_type"]) - to_chat(usr,span_warning("Genetic data corrupted, unable to create injector.")) - return - - I = new /obj/item/dnainjector/timed(loc) - I.fields = list("name"=buffer_slot["name"], "UF"=buffer_slot["UF"]) - - // If there is a connected scanner, we can use its upgrades to reduce - // the genetic damage generated by this injector - if(scanner_operational()) - I.damage_coeff = connected_scanner.damage_coeff - if("mixed") - // GUARD CHECK - There's currently no way to save partial genetic data. - // However, if this is the case, we can't make a complete injector and - // this catches that edge case - if(!buffer_slot["UI"] || !buffer_slot["name"] || !buffer_slot["UE"] || !buffer_slot["UF"] || !buffer_slot["blood_type"]) - to_chat(usr,span_warning("Genetic data corrupted, unable to create injector.")) - return - - I = new /obj/item/dnainjector/timed(loc) - I.fields = list("UI"=buffer_slot["UI"],"name"=buffer_slot["name"], "UE"=buffer_slot["UE"], "UF"=buffer_slot["UF"], "blood_type"=buffer_slot["blood_type"]) - - // If there is a connected scanner, we can use its upgrades to reduce - // the genetic damage generated by this injector - if(scanner_operational()) - I.damage_coeff = connected_scanner.damage_coeff - - // If we successfully created an injector, don't forget to set the new - // ready timer. - if(I) - injector_ready = world.time + MISC_INJECTOR_TIMEOUT + injector_ready = world.time + MISC_INJECTOR_TIMEOUT if(connected_scanner) connected_scanner.use_energy(connected_scanner.active_power_usage) else @@ -1785,12 +1711,90 @@ return TRUE return FALSE + /** * Checks if there is a connected DNA Scanner that is operational */ /obj/machinery/computer/dna_console/proc/scanner_operational() return connected_scanner?.is_operational +/** + * Gets the damage coefficient of the connected DNA Scanner, or 1 if there isn't an operational one + */ +/obj/machinery/computer/dna_console/proc/get_injector_damage_coeff() + if(scanner_operational()) + return connected_scanner.damage_coeff + return 1 + +/// Copy UI to the dna injector +#define DNA_INJECTOR_FLAG_UI (1<<0) +/// Copy UE to the dna injector +#define DNA_INJECTOR_FLAG_UE (1<<1) +/// Copy UF to the dna injector +#define DNA_INJECTOR_FLAG_UF (1<<2) +/// Copy name to the dna injector +#define DNA_INJECTOR_FLAG_NAME (1<<3) +/// Copy blood type to the dna injector +#define DNA_INJECTOR_FLAG_BLOOD (1<<4) + +/** + * Converts a string (from tgui) to a series of flags determine what we should put in a DNA Injector + */ +/obj/machinery/computer/dna_console/proc/dna_injector_type_to_flag(injector_type) + switch(injector_type) + if("ui") + return DNA_INJECTOR_FLAG_UI + if("ue") + return DNA_INJECTOR_FLAG_UE | DNA_INJECTOR_FLAG_NAME | DNA_INJECTOR_FLAG_BLOOD + if("uf") + return DNA_INJECTOR_FLAG_UF | DNA_INJECTOR_FLAG_NAME + if("mixed") + return ALL + return NONE + +/** + * Pass an injector flag and a genetic makeup buffer slot to create a DNA Injector + */ +/obj/machinery/computer/dna_console/proc/make_cosmetic_dna_injector(dna_flag, list/buffer_slot = list()) + if(!dna_flag || !length(buffer_slot)) + return FALSE + + var/datum/dna/stored_dna = new() + + if(dna_flag & DNA_INJECTOR_FLAG_NAME) + if(!buffer_slot["name"]) + return FALSE + stored_dna.real_name = buffer_slot["name"] + + if(dna_flag & DNA_INJECTOR_FLAG_BLOOD) + if(!buffer_slot["blood_type"]) + return FALSE + stored_dna.blood_type = buffer_slot["blood_type"] + + if(dna_flag & DNA_INJECTOR_FLAG_UI) + if(!buffer_slot["UI"]) + return FALSE + stored_dna.unique_identity = buffer_slot["UI"] + + if(dna_flag & DNA_INJECTOR_FLAG_UE) + if(!buffer_slot["UE"]) + return FALSE + stored_dna.unique_enzymes = buffer_slot["UE"] + + if(dna_flag & DNA_INJECTOR_FLAG_UF) + if(!buffer_slot["UF"]) + return FALSE + stored_dna.unique_features = buffer_slot["UF"] + + new /obj/item/dnainjector/timed(loc, stored_dna, get_injector_damage_coeff()) + return TRUE + +#undef DNA_INJECTOR_FLAG_UI +#undef DNA_INJECTOR_FLAG_UE +#undef DNA_INJECTOR_FLAG_UF +#undef DNA_INJECTOR_FLAG_NAME +#undef DNA_INJECTOR_FLAG_BLOOD + /** * Checks if there is a valid DNA Scanner occupant for genetic modification * diff --git a/code/game/machinery/experimental_cloner/experimental_cloner_pod.dm b/code/game/machinery/experimental_cloner/experimental_cloner_pod.dm index 460786c9ea8..617d013fab3 100644 --- a/code/game/machinery/experimental_cloner/experimental_cloner_pod.dm +++ b/code/game/machinery/experimental_cloner/experimental_cloner_pod.dm @@ -163,7 +163,7 @@ GLOBAL_VAR_INIT(experimental_cloner_fuckup_chance, 50) if (prob(75)) var/static/list/permitted_heights = list(HUMAN_HEIGHT_SHORTEST, HUMAN_HEIGHT_SHORT, HUMAN_HEIGHT_MEDIUM, HUMAN_HEIGHT_TALL, HUMAN_HEIGHT_TALLER, HUMAN_HEIGHT_TALLEST) - new_clone.dna.remove_mutation(/datum/mutation/dwarfism, list(MUTATION_SOURCE_ACTIVATED, MUTATION_SOURCE_MUTATOR)) + new_clone.dna.remove_mutation(/datum/mutation/dwarfism, GLOB.standard_mutation_sources) new_clone.set_mob_height(pick(permitted_heights - loaded_record.height)) // To differentiate the clones return new_clone diff --git a/code/game/objects/items/dna_injector.dm b/code/game/objects/items/dna_injector.dm index baf4bb85e62..178f67c0b00 100644 --- a/code/game/objects/items/dna_injector.dm +++ b/code/game/objects/items/dna_injector.dm @@ -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)" diff --git a/code/game/objects/items/weaponry/melee/soulscythe.dm b/code/game/objects/items/weaponry/melee/soulscythe.dm index a0430b34ce9..596166d14e0 100644 --- a/code/game/objects/items/weaponry/melee/soulscythe.dm +++ b/code/game/objects/items/weaponry/melee/soulscythe.dm @@ -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 diff --git a/code/modules/mob/living/blood.dm b/code/modules/mob/living/blood.dm index 6ed417d57eb..2bf1cfc922c 100644 --- a/code/modules/mob/living/blood.dm +++ b/code/modules/mob/living/blood.dm @@ -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. diff --git a/code/modules/mob/living/carbon/human/_species.dm b/code/modules/mob/living/carbon/human/_species.dm index 996a9e3ca84..09a15763191 100644 --- a/code/modules/mob/living/carbon/human/_species.dm +++ b/code/modules/mob/living/carbon/human/_species.dm @@ -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)) diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 598770ef778..7624d9d1191 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -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)) diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm index 4714ba39cb1..3c21ea44ed5 100644 --- a/code/modules/mob/living/carbon/human/life.dm +++ b/code/modules/mob/living/carbon/human/life.dm @@ -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) diff --git a/code/modules/mob/living/carbon/human/species_types/golems.dm b/code/modules/mob/living/carbon/human/species_types/golems.dm index 493540162b5..23e3554d170 100644 --- a/code/modules/mob/living/carbon/human/species_types/golems.dm +++ b/code/modules/mob/living/carbon/human/species_types/golems.dm @@ -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) 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 67b88d64dbc..fa2d34012cc 100644 --- a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm @@ -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" 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 02084030a40..fa31d7a89cf 100644 --- a/code/modules/mob/living/carbon/human/species_types/vampire.dm +++ b/code/modules/mob/living/carbon/human/species_types/vampire.dm @@ -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) diff --git a/code/modules/mob/living/carbon/human/species_types/zombies.dm b/code/modules/mob/living/carbon/human/species_types/zombies.dm index 3053374e7d7..3307bc4215a 100644 --- a/code/modules/mob/living/carbon/human/species_types/zombies.dm +++ b/code/modules/mob/living/carbon/human/species_types/zombies.dm @@ -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" diff --git a/code/modules/mob/living/carbon/life.dm b/code/modules/mob/living/carbon/life.dm index 5087244f22b..97a6604bc17 100644 --- a/code/modules/mob/living/carbon/life.dm +++ b/code/modules/mob/living/carbon/life.dm @@ -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 * diff --git a/code/modules/mob/living/life.dm b/code/modules/mob/living/life.dm index dc50f5ec56c..178679bfc91 100644 --- a/code/modules/mob/living/life.dm +++ b/code/modules/mob/living/life.dm @@ -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) diff --git a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm index 465b5a902d2..0f8f7b35177 100644 --- a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm @@ -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() diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm index dad783c27e9..041fe3a0e80 100644 --- a/code/modules/surgery/bodyparts/_bodyparts.dm +++ b/code/modules/surgery/bodyparts/_bodyparts.dm @@ -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 * diff --git a/code/modules/surgery/organs/internal/tongue/_tongue.dm b/code/modules/surgery/organs/internal/tongue/_tongue.dm index d7fcf96095b..2a8ef022c1c 100644 --- a/code/modules/surgery/organs/internal/tongue/_tongue.dm +++ b/code/modules/surgery/organs/internal/tongue/_tongue.dm @@ -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\"."