From 5cf5037a97c5f613296f6807a5c5fb115bda57b6 Mon Sep 17 00:00:00 2001 From: Dani Glore Date: Tue, 31 Jan 2023 04:30:25 -0500 Subject: [PATCH] Fix: DNA Infuser & Unit Tests, Organs Bugfixes (#73003) ## About The Pull Request >_"I don't remember buying tickets to Mutants on Ice."_ >-Duke Nukem This PR is (hopefully the final) part of a series of my continuing refactors of the DNA Infuser. This PR represents a "quality pass" which should also iron-out the rest of the most impactful bugs. Granular list of changes: - This PR adds unit tests for the DNA Infuser organs and `/datum/status_effect/organ_set_bonus` as recommended by @AnturK - I noticed that the base `/datum/infuser_entry` was being used in the machine for the Fly and "rejected" infusions, whereas usually we would expect it to be a base type used only as a development template. I corrected this issue and created `/datum/infuser_entry/fly` to be used for that use-case instead. - Added `/mob/proc/can_mutate()` and `/mob/living/carbon/can_mutate()` to replace a few copied lines across several files. The proc is normally used in the context of mutating a Human via their DNA. - I fixed a ton of typos in organ-related code, specifically where "receiver" was typo'd as "reciever". There are far more of those typos, but I limited the scope of my changes to organs. - I noticed a bug in `/datum/species/proc/regenerate_organs` wherein a race condition caused an organ to remove itself before it's done inserting itself. This happens because the Fly organ set bonus runs `regenerate_organs` which calls `Remove` on the organ while `Insert` is still in the call-stack. I added `INVOKE_ASYNC` as a workaround, and also changed the order the signals are emitted to prevent future bugs. This bug primarily only impacted the flyperson species transformation, which was part of the DNA Infuser's flyperson infusion organ set bonus. - In my last refactor PR #72745 I also introduced a bug in `/obj/machinery/dna_infuser/proc/infuse_organ` wherein I forgot to add the usage of `new` when attempting to implant new organs, and this PR fixes the erroneous code. - Fxed a bug which causes the organ set bonus to activate when mixing organs from different sources, which is caused by a developer oversight wherein all `/datum/status_effect/organ_set_bonus` had identical IDs. - Added a cleaner `replacetext`-based way of handling pronouns in `/datum/element/noticable_organ/proc/on_receiver_examine`, using custom macros `%PRONOUN_S` and `%PRONOUN_ES` as advised by @MrMelbert - This PR also fixes #72767 ## Why It's Good For The Game With the changes in this PR the machine will finally work as we expect it to. By adding unit tests we will also be able to ensure that it works as expected from now on. I feel confident saying that the completeness, algorithmic correctness, and code health of the DNA Infuser is much better than it was before. ## Changelog :cl: A.C.M.O. fix: Fully fixed the DNA Infuser, which will now infuse organs as expected. fix: Fixed flyperson species transformation and organ set bonus, which was throwing a runtime. fix: Fixed many typos in organ-related source code. /:cl: --------- Co-authored-by: Time-Green --- code/datums/dna.dm | 10 + code/datums/elements/noticable_organ.dm | 2 +- code/datums/elements/organ_set_bonus.dm | 34 +++- .../weather/weather_types/radiation_storm.dm | 2 +- .../game/machinery/dna_infuser/dna_infuser.dm | 35 ++-- .../machinery/dna_infuser/infuser_entries.dm | 65 ++++--- .../dna_infuser/organ_sets/carp_organs.dm | 29 +-- .../dna_infuser/organ_sets/fly_organs.dm | 33 ++-- .../dna_infuser/organ_sets/fox_organs.dm | 9 +- .../dna_infuser/organ_sets/goliath_organs.dm | 36 ++-- .../dna_infuser/organ_sets/rat_organs.dm | 69 +++---- code/game/objects/items/dna_injector.dm | 171 +++++++++--------- .../chemistry/reagents/toxin_reagents.dm | 2 +- code/modules/surgery/organs/_organ.dm | 31 ++-- code/modules/surgery/organs/ears.dm | 8 +- .../organs/external/_external_organs.dm | 16 +- code/modules/surgery/organs/external/tails.dm | 20 +- .../organs/external/wings/functional_wings.dm | 4 +- .../organs/external/wings/moth_wings.dm | 8 +- code/modules/surgery/organs/organ_internal.dm | 4 +- code/modules/unit_tests/_unit_tests.dm | 1 + code/modules/unit_tests/organ_set_bonus.dm | 86 +++++++++ .../wiremod/shell/brain_computer_interface.dm | 4 +- 23 files changed, 391 insertions(+), 288 deletions(-) create mode 100644 code/modules/unit_tests/organ_set_bonus.dm diff --git a/code/datums/dna.dm b/code/datums/dna.dm index 0f6172beb0f..849c0f542a1 100644 --- a/code/datums/dna.dm +++ b/code/datums/dna.dm @@ -502,6 +502,16 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block()) /mob/living/carbon/has_dna() return dna +/// Returns TRUE if the mob is allowed to mutate via its DNA, or FALSE if otherwise. +/// Only an organic Carbon with valid DNA may mutate; not robots, AIs, aliens, Ians, or other mobs. +/mob/proc/can_mutate() + return FALSE + +/mob/living/carbon/can_mutate() + if(!(mob_biotypes & MOB_ORGANIC)) + return FALSE + if(has_dna() && !HAS_TRAIT(src, TRAIT_GENELESS) && !HAS_TRAIT(src, TRAIT_BADDNA)) + return TRUE /// Sets the DNA of the mob to the given DNA. /mob/living/carbon/human/proc/hardset_dna(unique_identity, list/mutation_index, list/default_mutation_genes, newreal_name, newblood_type, datum/species/mrace, newfeatures, list/mutations, force_transfer_mutations) diff --git a/code/datums/elements/noticable_organ.dm b/code/datums/elements/noticable_organ.dm index b059cd37471..f293b74c472 100644 --- a/code/datums/elements/noticable_organ.dm +++ b/code/datums/elements/noticable_organ.dm @@ -45,4 +45,4 @@ if(body_zone && (body_zone in examined.get_covered_body_zones())) return - examine_list += span_notice("[body_zone ? examined.p_their(TRUE) : examined.p_they(TRUE)] [infused_desc]") + examine_list += span_notice(replacetext(replacetext("[body_zone ? examined.p_their(TRUE) : examined.p_they(TRUE)] [infused_desc]", "%PRONOUN_ES", examined.p_es()), "%PRONOUN_S", examined.p_s())) diff --git a/code/datums/elements/organ_set_bonus.dm b/code/datums/elements/organ_set_bonus.dm index fb90c3f4640..290e9865798 100644 --- a/code/datums/elements/organ_set_bonus.dm +++ b/code/datums/elements/organ_set_bonus.dm @@ -6,17 +6,15 @@ /datum/element/organ_set_bonus element_flags = ELEMENT_BESPOKE argument_hash_start_idx = 2 - ///status effect type to apply/update! - var/bonus_type + /// Status Effect typepath to instantiate and apply to the mob. + var/datum/status_effect/organ_set_bonus/bonus_type /datum/element/organ_set_bonus/Attach(datum/target, bonus_type) . = ..() if(!isorgan(target)) return ELEMENT_INCOMPATIBLE - src.bonus_type = bonus_type - RegisterSignal(target, COMSIG_ORGAN_IMPLANTED, PROC_REF(on_implanted)) RegisterSignal(target, COMSIG_ORGAN_REMOVED, PROC_REF(on_removed)) @@ -50,11 +48,15 @@ ///how many organs the carbon with this has in the set var/organs = 0 ///how many organs in the set you need to enable the bonus - var/organs_needed = 5 + var/organs_needed = 0 ///if the bonus is active var/bonus_active = FALSE var/bonus_activate_text = span_notice("??? DNA is deeply infused with you! You've learned how to make error reports!") var/bonus_deactivate_text = span_notice("Your DNA is no longer majority ???. You did make an issue report, right?") + /// Required mob bio-type. Also checks DNA validity it's set to MOB_ORGANIC. + var/required_biotype = MOB_ORGANIC + /// A Trait or list of Traits added to the mob upon bonus activation. + var/bonus_traits /datum/status_effect/organ_set_bonus/proc/set_organs(new_value) organs = new_value @@ -62,18 +64,36 @@ qdel(src) if(organs >= organs_needed) if(!bonus_active) - enable_bonus() + INVOKE_ASYNC(src, PROC_REF(enable_bonus)) else if(bonus_active) - disable_bonus() + INVOKE_ASYNC(src, PROC_REF(disable_bonus)) /datum/status_effect/organ_set_bonus/proc/enable_bonus() SHOULD_CALL_PARENT(TRUE) + if(required_biotype) + if(!(owner.mob_biotypes & required_biotype)) + return FALSE + if((required_biotype == MOB_ORGANIC) && !owner.can_mutate()) + return FALSE bonus_active = TRUE + if(bonus_traits) + if(islist(bonus_traits)) + for(var/trait in bonus_traits) + ADD_TRAIT(owner, trait, REF(src)) + else + ADD_TRAIT(owner, bonus_traits, REF(src)) if(bonus_activate_text) to_chat(owner, bonus_activate_text) + return TRUE /datum/status_effect/organ_set_bonus/proc/disable_bonus() SHOULD_CALL_PARENT(TRUE) bonus_active = FALSE + if(bonus_traits) + if(islist(bonus_traits)) + for(var/trait in bonus_traits) + REMOVE_TRAIT(owner, trait, REF(src)) + else + REMOVE_TRAIT(owner, bonus_traits, REF(src)) if(bonus_deactivate_text) to_chat(owner, bonus_deactivate_text) diff --git a/code/datums/weather/weather_types/radiation_storm.dm b/code/datums/weather/weather_types/radiation_storm.dm index 83f3aa73c48..5e736277f8f 100644 --- a/code/datums/weather/weather_types/radiation_storm.dm +++ b/code/datums/weather/weather_types/radiation_storm.dm @@ -38,7 +38,7 @@ return var/mob/living/carbon/human/H = L - if(!H.dna || HAS_TRAIT(H, TRAIT_GENELESS) || H.status_flags & GODMODE) + if(!H.can_mutate() || H.status_flags & GODMODE) return if(HAS_TRAIT(H, TRAIT_RADIMMUNE)) diff --git a/code/game/machinery/dna_infuser/dna_infuser.dm b/code/game/machinery/dna_infuser/dna_infuser.dm index 03fec110ac2..0ed2cb229e0 100644 --- a/code/game/machinery/dna_infuser/dna_infuser.dm +++ b/code/game/machinery/dna_infuser/dna_infuser.dm @@ -98,33 +98,38 @@ // TODO: In the future, this should have more logic: // - Replace non-mutant organs before mutant ones. /obj/machinery/dna_infuser/proc/infuse_organ(mob/living/carbon/human/target) - if(!ishuman(target) || !infusing_into) + if(!ishuman(target)) return FALSE var/obj/item/organ/new_organ = pick_organ(target) if(!new_organ) return FALSE + // Valid organ successfully picked. + new_organ = new new_organ() if(!istype(new_organ, /obj/item/organ/internal/brain)) // Organ ISN'T brain, insert normally. new_organ.Insert(target, special = TRUE, drop_if_replaced = FALSE) - else - // Organ IS brain, insert via special logic: - var/obj/item/organ/internal/brain/old_brain = target.getorganslot(ORGAN_SLOT_BRAIN) - // Brains REALLY like ghosting people. we need special tricks to avoid that, namely removing the old brain with no_id_transfer - old_brain.Remove(target, special = TRUE, no_id_transfer = TRUE) - qdel(old_brain) - var/obj/item/organ/internal/brain/new_brain = new_organ - new_brain.Insert(target, special = TRUE, drop_if_replaced = FALSE, no_id_transfer = TRUE) + return TRUE + // Organ IS brain, insert via special logic: + var/obj/item/organ/internal/brain/old_brain = target.getorganslot(ORGAN_SLOT_BRAIN) + // Brains REALLY like ghosting people. we need special tricks to avoid that, namely removing the old brain with no_id_transfer + old_brain.Remove(target, special = TRUE, no_id_transfer = TRUE) + qdel(old_brain) + var/obj/item/organ/internal/brain/new_brain = new_organ + new_brain.Insert(target, special = TRUE, drop_if_replaced = FALSE, no_id_transfer = TRUE) return TRUE /// Picks a random mutated organ from the infuser entry which is also compatible with the target mob. -/// Tries to return a valid mutant organ if all of the following criteria are true: +/// Tries to return a typepath of a valid mutant organ if all of the following criteria are true: /// 1. Target must have a pre-existing organ in the same organ slot as the new organ; /// - or the new organ must be external. /// 2. Target's pre-existing organ must be organic / not robotic. /// 3. Target must not have the same/identical organ. /obj/machinery/dna_infuser/proc/pick_organ(mob/living/carbon/human/target) + if(!infusing_into) + return FALSE var/list/obj/item/organ/potential_new_organs = infusing_into.output_organs.Copy() - for(var/obj/item/organ/new_organ in infusing_into.output_organs) + // Remove organ typepaths from the list if they're incompatible with target. + for(var/obj/item/organ/new_organ as anything in infusing_into.output_organs) var/obj/item/organ/old_organ = target.getorganslot(initial(new_organ.slot)) if(old_organ) if((old_organ.type != new_organ) && (old_organ.status == ORGAN_ORGANIC)) @@ -220,14 +225,14 @@ /// Verify that the occupant/target is organic, and has mutable DNA. /obj/machinery/dna_infuser/proc/is_valid_occupant(mob/living/carbon/human/human_target, mob/user) - // Invalid: Occupant isn't Human, isn't organic, lacks DNA / has TRAIT_GENELESS. - if(!ishuman(human_target) || !(human_target.mob_biotypes & MOB_ORGANIC) || !human_target.has_dna() || HAS_TRAIT(human_target, TRAIT_GENELESS)) - balloon_alert(user, "dna is missing!") - return FALSE // Invalid: DNA is too damaged to mutate anymore / has TRAIT_BADDNA. if(HAS_TRAIT(human_target, TRAIT_BADDNA)) balloon_alert(user, "dna is corrupted!") return FALSE + // Invalid: Occupant isn't Human, isn't organic, lacks DNA / has TRAIT_GENELESS. + if(!ishuman(human_target) || !human_target.can_mutate()) + balloon_alert(user, "dna is missing!") + return FALSE // Valid: Occupant is an organic Human who has undamaged and mutable DNA. return TRUE diff --git a/code/game/machinery/dna_infuser/infuser_entries.dm b/code/game/machinery/dna_infuser/infuser_entries.dm index 72de6965022..b3a163d6cb5 100644 --- a/code/game/machinery/dna_infuser/infuser_entries.dm +++ b/code/game/machinery/dna_infuser/infuser_entries.dm @@ -1,16 +1,17 @@ /// A list of all infuser entries -GLOBAL_LIST_INIT(infuser_entries, prepare_entries()) +GLOBAL_LIST_INIT(infuser_entries, prepare_infuser_entries()) /// just clarifying that no threshold does some special stuff, since only meme mutants have it -#define NO_THRESHOLD "" +#define DNA_INFUSION_NO_THRESHOLD "" -/proc/prepare_entries() +/// Global proc that sets up each [/datum/infuser_entry] sub-type as singleton instances in a list, and returns it. +/proc/prepare_infuser_entries() var/list/entries = list() - //regardless of names we want the failed mutant case to show first + // Regardless of names, we want the fly/failed mutant case to show first. var/prepended - for(var/datum/infuser_entry/entry_type as anything in typesof(/datum/infuser_entry)) + for(var/datum/infuser_entry/entry_type as anything in subtypesof(/datum/infuser_entry)) var/datum/infuser_entry/entry = new entry_type() - if(entry.type == /datum/infuser_entry) + if(entry.type == /datum/infuser_entry/fly) prepended = entry continue entries += entry @@ -19,32 +20,43 @@ GLOBAL_LIST_INIT(infuser_entries, prepare_entries()) return sorted /datum/infuser_entry - //info for the book - + //-- Vars for DNA Infusion Book --// /// name of the mutant you become - var/name = "Rejected" + var/name = "Mutant" /// what you have to infuse to become it - var/infuse_mob_name = "rejected creature" + var/infuse_mob_name = "some kind of mutant" /// general desc - var/desc = "For whatever reason, when the body rejects DNA, the DNA goes sour, ending up as some kind of fly-like DNA jumble." + var/desc = "The ignorants call you a mutant. I prefer to think of mutants as the future of mankind! They could use a guy like you on their team." /// desc of what passing the threshold gets you. if this is empty, there is no threshold, so this is also really a tally of whether this is a "meme" mutant or not - var/threshold_desc = "the DNA mess takes over, and you become a full-fledged flyperson." - /// various little bits + var/threshold_desc = "the DNA mess takes over, and you turn into a mutant freak!" + /// List of personal attributes added by the mutation. var/list/qualities = list( + "override this", + "puts pineapple on pizza", + "inspiration for birth control", + "just a weird guy", + ) + //-- Vars for DNA Infuser Machine --// + /// List of objects, mobs, and/or items, the machine will infuse to make output organs. + /// Rejected creatures, of course, are anything not covered by other recipes. This is a special case + var/list/input_obj_or_mob + /// List of organs that the machine could spit out in relation + var/list/output_organs + ///message the target gets while being infused + var/infusion_desc = "mutant-like" + +/datum/infuser_entry/fly + name = "Rejected" + infuse_mob_name = "rejected creature" + desc = "For whatever reason, when the body rejects DNA, the DNA goes sour, ending up as some kind of fly-like DNA jumble." + threshold_desc = "the DNA mess takes over, and you become a full-fledged flyperson." + qualities = list( "buzzy-like speech", "vomit drinking", "unidentifiable organs", "this is a bad idea", ) - - //info for the machine - - /// ...THINGS, mobs or items, the machine will infuse to make output organs - var/list/input_obj_or_mob = list( - ///rejected creatures, of course, are anything not covered by other recipes. This is a special case - ) - /// organs that the machine could spit out in relation - var/list/output_organs = list( + output_organs = list( /obj/item/organ/internal/appendix/fly, /obj/item/organ/internal/eyes/fly, /obj/item/organ/internal/heart/fly, @@ -52,8 +64,7 @@ GLOBAL_LIST_INIT(infuser_entries, prepare_entries()) /obj/item/organ/internal/stomach/fly, /obj/item/organ/internal/tongue/fly, ) - ///message the target gets while being infused - var/infusion_desc = "fly-like" + infusion_desc = "fly-like" /datum/infuser_entry/rat name = "Rat" @@ -105,7 +116,7 @@ GLOBAL_LIST_INIT(infuser_entries, prepare_entries()) name = "Cat" infuse_mob_name = "feline" desc = "EVERYONE CALM DOWN! I'm not implying anything with this entry. Are we really so surprised that felinids are humans with mixed feline DNA?" - threshold_desc = NO_THRESHOLD + threshold_desc = DNA_INFUSION_NO_THRESHOLD qualities = list( "oh, let me guess, you're a big fan of those japanese tourist bots", ) @@ -122,7 +133,7 @@ GLOBAL_LIST_INIT(infuser_entries, prepare_entries()) name = "Fox" infuse_mob_name = "vulpini" desc = "Foxes are now quite rare because of the \"fox ears\" craze back in 2555. I mean, also because we're spacefarers who destroyed foxes' natural habitats ages ago, but that applies to most animals." - threshold_desc = NO_THRESHOLD + threshold_desc = DNA_INFUSION_NO_THRESHOLD qualities = list( "oh come on really", "you bring SHAME to all geneticists", @@ -162,7 +173,7 @@ GLOBAL_LIST_INIT(infuser_entries, prepare_entries()) name = "Mothroach" infuse_mob_name = "Mothroach" desc = "So first they mixed moth and roach DNA to make mothroaches, and now we mix mothroach DNA with humanoids to make mothmen hybrids?" - threshold_desc = NO_THRESHOLD + threshold_desc = DNA_INFUSION_NO_THRESHOLD qualities = list( "eyes weak to bright lights", "you flutter when you talk", diff --git a/code/game/machinery/dna_infuser/organ_sets/carp_organs.dm b/code/game/machinery/dna_infuser/organ_sets/carp_organs.dm index 3cc4fca59e6..0835ce5aa09 100644 --- a/code/game/machinery/dna_infuser/organ_sets/carp_organs.dm +++ b/code/game/machinery/dna_infuser/organ_sets/carp_organs.dm @@ -1,23 +1,15 @@ - #define CARP_ORGAN_COLOR "#4caee7" #define CARP_SCLERA_COLOR "#ffffff" #define CARP_PUPIL_COLOR "#00b1b1" - #define CARP_COLORS CARP_ORGAN_COLOR + CARP_SCLERA_COLOR + CARP_PUPIL_COLOR ///bonus of the carp: you can swim through space! /datum/status_effect/organ_set_bonus/carp + id = "organ_set_bonus_carp" organs_needed = 4 bonus_activate_text = span_notice("Carp DNA is deeply infused with you! You've learned how to propel yourself through space!") bonus_deactivate_text = span_notice("Your DNA is once again mostly yours, and so fades your ability to space-swim...") - -/datum/status_effect/organ_set_bonus/carp/enable_bonus() - . = ..() - ADD_TRAIT(owner, TRAIT_SPACEWALK, REF(src)) - -/datum/status_effect/organ_set_bonus/carp/disable_bonus() - . = ..() - REMOVE_TRAIT(owner, TRAIT_SPACEWALK, REF(src)) + bonus_traits = TRAIT_SPACEWALK ///Carp lungs! You can breathe in space! Oh... you can't breathe on the station, you need low oxygen environments. /// Inverts behavior of lungs. Bypasses suffocation due to space / lack of gas, but also allows Oxygen to suffocate. @@ -61,6 +53,8 @@ if(!ishuman(tongue_owner)) return var/mob/living/carbon/human/human_receiver = tongue_owner + if(!human_receiver.can_mutate()) + return var/datum/species/rec_species = human_receiver.dna.species rec_species.update_no_equip_flags(tongue_owner, rec_species.no_equip_flags | ITEM_SLOT_MASK) var/obj/item/bodypart/head/head = human_receiver.get_bodypart(BODY_ZONE_HEAD) @@ -73,6 +67,8 @@ if(!ishuman(tongue_owner)) return var/mob/living/carbon/human/human_receiver = tongue_owner + if(!human_receiver.can_mutate()) + return var/datum/species/rec_species = human_receiver.dna.species rec_species.update_no_equip_flags(tongue_owner, initial(rec_species.no_equip_flags)) var/obj/item/bodypart/head/head = human_receiver.get_bodypart(BODY_ZONE_HEAD) @@ -112,10 +108,7 @@ /obj/item/organ/internal/brain/carp/Initialize(mapload) . = ..() AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/carp) - -/obj/item/organ/internal/brain/carp/Insert(mob/living/carbon/reciever, special, drop_if_replaced, no_id_transfer) - AddElement(/datum/element/noticable_organ, "seem[reciever.p_s()] unable to stay still.") - return ..() + AddElement(/datum/element/noticable_organ, "seem%PRONOUN_S unable to stay still.") /obj/item/organ/internal/brain/carp/Insert(mob/living/carbon/brain_owner, special, drop_if_replaced, no_id_transfer) . = ..() @@ -125,7 +118,7 @@ //technically you could get around the mood issue by extracting and reimplanting the brain but it will be far easier to just go one z there and back /obj/item/organ/internal/brain/carp/Remove(mob/living/carbon/brain_owner, special, no_id_transfer) . = ..() - UnregisterSignal(brain_owner) + UnregisterSignal(brain_owner, COMSIG_MOVABLE_Z_CHANGED) deltimer(cooldown_timer) /obj/item/organ/internal/brain/carp/get_attacking_limb(mob/living/carbon/human/target) @@ -153,14 +146,10 @@ /obj/item/organ/internal/heart/carp/Initialize(mapload) . = ..() + AddElement(/datum/element/noticable_organ, "skin has small patches of scales growing on it.", BODY_ZONE_CHEST) AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/carp) -/obj/item/organ/internal/heart/carp/Insert(mob/living/carbon/reciever, special = FALSE, drop_if_replaced = TRUE) - AddElement(/datum/element/noticable_organ, "[reciever.p_have()] small patches of scales growing on [reciever.p_their()] skin...") - return ..() - #undef CARP_ORGAN_COLOR #undef CARP_SCLERA_COLOR #undef CARP_PUPIL_COLOR - #undef CARP_COLORS diff --git a/code/game/machinery/dna_infuser/organ_sets/fly_organs.dm b/code/game/machinery/dna_infuser/organ_sets/fly_organs.dm index 0fc70b02ffa..2f1f57d5091 100644 --- a/code/game/machinery/dna_infuser/organ_sets/fly_organs.dm +++ b/code/game/machinery/dna_infuser/organ_sets/fly_organs.dm @@ -1,5 +1,9 @@ +#define FLY_INFUSED_ORGAN_DESC "You have no idea what the hell this is, or how it manages to keep something alive in any capacity." +#define FLY_INFUSED_ORGAN_ICON pick("brain-x-d", "liver-x", "kidneys-x", "spinner-x", "lungs-x", "random_fly_1", "random_fly_2", "random_fly_3", "random_fly_4", "random_fly_5") + ///bonus of the fly: you... are a flyperson now. sorry. /datum/status_effect/organ_set_bonus/fly + id = "organ_set_bonus_fly" organs_needed = 4 //there are actually 7 fly organs that count, but you only need 4 to go full-flyperson. Be careful! bonus_activate_text = null bonus_deactivate_text = null @@ -55,12 +59,12 @@ return ..() + /datum/language/buzzwords /obj/item/organ/internal/heart/fly - desc = "You have no idea what the hell this is, or how it manages to keep something alive in any capacity." + desc = FLY_INFUSED_ORGAN_DESC /obj/item/organ/internal/heart/fly/Initialize(mapload) . = ..() name = odd_organ_name() - icon_state = pick("brain-x-d", "liver-x", "kidneys-x", "spinner-x", "lungs-x", "random_fly_1", "random_fly_2", "random_fly_3", "random_fly_4", "random_fly_5") + icon_state = FLY_INFUSED_ORGAN_ICON AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/fly) /obj/item/organ/internal/heart/fly/update_icon_state() @@ -68,31 +72,31 @@ return //don't set icon thank you /obj/item/organ/internal/lungs/fly - desc = "You have no idea what the hell this is, or how it manages to keep something alive in any capacity." + desc = FLY_INFUSED_ORGAN_DESC /obj/item/organ/internal/lungs/fly/Initialize(mapload) . = ..() name = odd_organ_name() - icon_state = pick("brain-x-d", "liver-x", "kidneys-x", "spinner-x", "lungs-x", "random_fly_1", "random_fly_2", "random_fly_3", "random_fly_4", "random_fly_5") + icon_state = FLY_INFUSED_ORGAN_ICON AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/fly) /obj/item/organ/internal/liver/fly - desc = "You have no idea what the hell this is, or how it manages to keep something alive in any capacity." + desc = FLY_INFUSED_ORGAN_DESC alcohol_tolerance = 0.007 //flies eat vomit, so a lower alcohol tolerance is perfect! /obj/item/organ/internal/liver/fly/Initialize(mapload) . = ..() name = odd_organ_name() - icon_state = pick("brain-x-d", "liver-x", "kidneys-x", "spinner-x", "lungs-x", "random_fly_1", "random_fly_2", "random_fly_3", "random_fly_4", "random_fly_5") + icon_state = FLY_INFUSED_ORGAN_ICON AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/fly) /obj/item/organ/internal/stomach/fly - desc = "You have no idea what the hell this is, or how it manages to keep something alive in any capacity." + desc = FLY_INFUSED_ORGAN_DESC /obj/item/organ/internal/stomach/fly/Initialize(mapload) . = ..() name = odd_organ_name() - icon_state = pick("brain-x-d", "liver-x", "kidneys-x", "spinner-x", "lungs-x", "random_fly_1", "random_fly_2", "random_fly_3", "random_fly_4", "random_fly_5") + icon_state = FLY_INFUSED_ORGAN_ICON AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/fly) /obj/item/organ/internal/stomach/fly/after_eat(edible) @@ -108,28 +112,29 @@ return ..() /obj/item/organ/internal/appendix/fly - desc = "You have no idea what the hell this is, or how it manages to keep something alive in any capacity." + desc = FLY_INFUSED_ORGAN_DESC /obj/item/organ/internal/appendix/fly/Initialize(mapload) . = ..() name = odd_organ_name() - icon_state = pick("brain-x-d", "liver-x", "kidneys-x", "spinner-x", "lungs-x", "random_fly_1", "random_fly_2", "random_fly_3", "random_fly_4", "random_fly_5") + icon_state = FLY_INFUSED_ORGAN_ICON AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/fly) /obj/item/organ/internal/appendix/fly/update_appearance(updates=ALL) return ..(updates & ~(UPDATE_NAME|UPDATE_ICON)) //don't set name or icon thank you - - //useless organs we throw in just to fuck with surgeons a bit more. they aren't part of a bonus, just the (absolute) state of flies /obj/item/organ/internal/fly - desc = "You have no idea what the hell this is, or how it manages to keep something alive in any capacity." + desc = FLY_INFUSED_ORGAN_DESC visual = FALSE /obj/item/organ/internal/fly/Initialize(mapload) . = ..() name = odd_organ_name() - icon_state = pick("brain-x-d", "liver-x", "kidneys-x", "spinner-x", "lungs-x", "random_fly_1", "random_fly_2", "random_fly_3", "random_fly_4", "random_fly_5") + icon_state = FLY_INFUSED_ORGAN_ICON /obj/item/organ/internal/fly/groin //appendix is the only groin organ so we gotta have one of these too lol zone = BODY_ZONE_PRECISE_GROIN + +#undef FLY_INFUSED_ORGAN_DESC +#undef FLY_INFUSED_ORGAN_ICON diff --git a/code/game/machinery/dna_infuser/organ_sets/fox_organs.dm b/code/game/machinery/dna_infuser/organ_sets/fox_organs.dm index 187254c8101..24dadcb3b7c 100644 --- a/code/game/machinery/dna_infuser/organ_sets/fox_organs.dm +++ b/code/game/machinery/dna_infuser/organ_sets/fox_organs.dm @@ -1,4 +1,3 @@ - /obj/item/organ/internal/ears/fox name = "fox ears" icon = 'icons/obj/clothing/head/costume.dmi' @@ -8,16 +7,16 @@ damage_multiplier = 2 /obj/item/organ/internal/ears/fox/Insert(mob/living/carbon/human/ear_owner, special = 0, drop_if_replaced = TRUE) - ..() - if(istype(ear_owner)) + . = ..() + if(istype(ear_owner) && ear_owner.dna) color = ear_owner.hair_color ear_owner.dna.features["ears"] = ear_owner.dna.species.mutant_bodyparts["ears"] = "Fox" ear_owner.dna.update_uf_block(DNA_EARS_BLOCK) ear_owner.update_body() /obj/item/organ/internal/ears/fox/Remove(mob/living/carbon/human/ear_owner, special = 0) - ..() - if(istype(ear_owner)) + . = ..() + if(istype(ear_owner) && ear_owner.dna) color = ear_owner.hair_color ear_owner.dna.species.mutant_bodyparts -= "ears" ear_owner.update_body() diff --git a/code/game/machinery/dna_infuser/organ_sets/goliath_organs.dm b/code/game/machinery/dna_infuser/organ_sets/goliath_organs.dm index 6a084533094..7058621dfb1 100644 --- a/code/game/machinery/dna_infuser/organ_sets/goliath_organs.dm +++ b/code/game/machinery/dna_infuser/organ_sets/goliath_organs.dm @@ -1,23 +1,15 @@ - #define GOLIATH_ORGAN_COLOR "#875652" #define GOLIATH_SCLERA_COLOR "#ac0f32" #define GOLIATH_PUPIL_COLOR "#FF0000" - #define GOLIATH_COLORS GOLIATH_ORGAN_COLOR + GOLIATH_SCLERA_COLOR + GOLIATH_PUPIL_COLOR ///bonus of the goliath: you can swim through space! /datum/status_effect/organ_set_bonus/goliath + id = "organ_set_bonus_goliath" organs_needed = 4 bonus_activate_text = span_notice("goliath DNA is deeply infused with you! You can now endure walking on lava!") bonus_deactivate_text = span_notice("You feel your muscle mass shrink and the tendrils around your skin wither. Your Goliath DNA is mostly gone and so is your ability to survive lava.") - -/datum/status_effect/organ_set_bonus/goliath/enable_bonus() - . = ..() - ADD_TRAIT(owner, TRAIT_LAVA_IMMUNE, REF(src)) - -/datum/status_effect/organ_set_bonus/goliath/disable_bonus() - . = ..() - REMOVE_TRAIT(owner, TRAIT_LAVA_IMMUNE, REF(src)) + bonus_traits = TRAIT_LAVA_IMMUNE ///goliath eyes, simple night vision /obj/item/organ/internal/eyes/night_vision/goliath @@ -32,19 +24,13 @@ eye_color_left = "#FF0000" eye_color_right = "#FF0000" + organ_traits = list(TRAIT_UNNATURAL_RED_GLOWY_EYES) + /obj/item/organ/internal/eyes/night_vision/goliath/Initialize(mapload) . = ..() AddElement(/datum/element/noticable_organ, "eyes are blood red and stone-like.", BODY_ZONE_PRECISE_EYES) AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/goliath) -/obj/item/organ/internal/eyes/night_vision/goliath/Insert(mob/living/carbon/eyes_owner, special, drop_if_replaced) - . = ..() - ADD_TRAIT(eyes_owner, TRAIT_UNNATURAL_RED_GLOWY_EYES, ORGAN_TRAIT) - -/obj/item/organ/internal/eyes/night_vision/goliath/Remove(mob/living/carbon/eyes_owner, special, drop_if_replaced) - REMOVE_TRAIT(eyes_owner, TRAIT_UNNATURAL_RED_GLOWY_EYES, ORGAN_TRAIT) - return ..() - ///goliath lungs! You can breathe lavaland air mix but can't breath pure O2 from a tank anymore. /obj/item/organ/internal/lungs/lavaland/goliath name = "mutated goliath-lungs" @@ -82,9 +68,10 @@ if(!ishuman(brain_owner)) return var/mob/living/carbon/human/human_receiver = brain_owner + if(!human_receiver.can_mutate()) + return var/datum/species/rec_species = human_receiver.dna.species rec_species.update_no_equip_flags(brain_owner, rec_species.no_equip_flags | ITEM_SLOT_GLOVES) - hammer = new/obj/item/goliath_infuser_hammer brain_owner.put_in_hands(hammer) @@ -94,12 +81,13 @@ if(!ishuman(brain_owner)) return var/mob/living/carbon/human/human_receiver = brain_owner + if(!human_receiver.can_mutate()) + return var/datum/species/rec_species = human_receiver.dna.species rec_species.update_no_equip_flags(brain_owner, initial(rec_species.no_equip_flags)) if(hammer) brain_owner.visible_message(span_warning("\The [hammer] disintegrates!")) QDEL_NULL(hammer) - return ..() /obj/item/goliath_infuser_hammer name = "tendril hammer" @@ -167,10 +155,10 @@ /obj/item/organ/internal/heart/goliath/Initialize(mapload) . = ..() + AddElement(/datum/element/noticable_organ, "skin has visible hard plates growing from within.", BODY_ZONE_CHEST) AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/goliath) -/obj/item/organ/internal/heart/goliath/Insert(mob/living/carbon/reciever, special = FALSE, drop_if_replaced = TRUE) - AddElement(/datum/element/noticable_organ, "skin [reciever.p_have()] visible hard plates growing from within.", BODY_ZONE_CHEST) - return ..() - +#undef GOLIATH_ORGAN_COLOR +#undef GOLIATH_SCLERA_COLOR +#undef GOLIATH_PUPIL_COLOR #undef GOLIATH_COLORS diff --git a/code/game/machinery/dna_infuser/organ_sets/rat_organs.dm b/code/game/machinery/dna_infuser/organ_sets/rat_organs.dm index c35f8fc1568..1c36bef1364 100644 --- a/code/game/machinery/dna_infuser/organ_sets/rat_organs.dm +++ b/code/game/machinery/dna_infuser/organ_sets/rat_organs.dm @@ -1,25 +1,15 @@ - #define RAT_ORGAN_COLOR "#646464" #define RAT_SCLERA_COLOR "#f0e055" #define RAT_PUPIL_COLOR "#000000" - #define RAT_COLORS RAT_ORGAN_COLOR + RAT_SCLERA_COLOR + RAT_PUPIL_COLOR ///bonus of the rat: you can ventcrawl! /datum/status_effect/organ_set_bonus/rat + id = "organ_set_bonus_rat" organs_needed = 4 bonus_activate_text = span_notice("Rodent DNA is deeply infused with you! You've learned how to traverse ventilation!") bonus_deactivate_text = span_notice("Your DNA is no longer majority rat, and so fades your ventilation skills...") - -/datum/status_effect/organ_set_bonus/rat/enable_bonus() - . = ..() - ADD_TRAIT(owner, TRAIT_VENTCRAWLER_NUDE, REF(src)) - -/datum/status_effect/organ_set_bonus/rat/disable_bonus() - . = ..() - REMOVE_TRAIT(owner, TRAIT_VENTCRAWLER_NUDE, REF(src)) - - + bonus_traits = TRAIT_VENTCRAWLER_NUDE ///way better night vision, super sensitive. lotta things work like this, huh? /obj/item/organ/internal/eyes/night_vision/rat @@ -39,8 +29,6 @@ AddElement(/datum/element/noticable_organ, "eyes have deep, shifty black pupils, surrounded by a sickening yellow sclera.", BODY_ZONE_PRECISE_EYES) AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/rat) - - ///increases hunger, disgust recovers quicker, expands what is defined as "food" /obj/item/organ/internal/stomach/rat name = "mutated rat-stomach" @@ -51,27 +39,29 @@ icon_state = "stomach" greyscale_config = /datum/greyscale_config/mutant_organ greyscale_colors = RAT_COLORS + /// Multiplier of [physiology.hunger_mod]. + var/hunger_mod = 10 /obj/item/organ/internal/stomach/rat/Initialize(mapload) . = ..() AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/rat) + AddElement(/datum/element/noticable_organ, "mouth is drooling excessively.", BODY_ZONE_PRECISE_MOUTH) -/obj/item/organ/internal/stomach/rat/Insert(mob/living/carbon/reciever, special = FALSE, drop_if_replaced = TRUE) - AddElement(/datum/element/noticable_organ, "salivate[reciever.p_s()] excessively.", BODY_ZONE_HEAD) - return ..() - -/obj/item/organ/internal/stomach/rat/Insert(mob/living/carbon/reciever, special, drop_if_replaced) +/obj/item/organ/internal/stomach/rat/Insert(mob/living/carbon/receiver, special, drop_if_replaced) . = ..() - if(!ishuman(reciever)) + if(!ishuman(receiver)) + return + var/mob/living/carbon/human/human_holder = receiver + if(!human_holder.can_mutate()) return - var/mob/living/carbon/human/human_holder = reciever var/datum/species/species = human_holder.dna.species //mmm, cheese. doesn't especially like anything else species.liked_food = DAIRY //but a rat can eat anything without issue species.disliked_food = NONE species.toxic_food = NONE - human_holder.physiology.hunger_mod *= 10 + if(human_holder.physiology) + human_holder.physiology.hunger_mod *= hunger_mod RegisterSignal(human_holder, COMSIG_SPECIES_GAIN, PROC_REF(on_species_gain)) /obj/item/organ/internal/stomach/rat/proc/on_species_gain(datum/source, datum/species/new_species, datum/species/old_species) @@ -85,15 +75,16 @@ if(!ishuman(stomach_owner)) return var/mob/living/carbon/human/human_holder = stomach_owner + if(!human_holder.can_mutate()) + return var/datum/species/species = human_holder.dna.species species.liked_food = initial(species.liked_food) species.disliked_food = initial(species.disliked_food) species.toxic_food = initial(species.toxic_food) - human_holder.physiology.hunger_mod /= 10 + if(human_holder.physiology) + human_holder.physiology.hunger_mod /= hunger_mod UnregisterSignal(stomach_owner, COMSIG_SPECIES_GAIN) - - /// makes you smaller, walk over tables, and take 1.5x damage /obj/item/organ/internal/heart/rat name = "mutated rat-heart" @@ -107,29 +98,30 @@ /obj/item/organ/internal/heart/rat/Initialize(mapload) . = ..() AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/rat) + AddElement(/datum/element/noticable_organ, "hunch%PRONOUN_ES over unnaturally!") -/obj/item/organ/internal/heart/rat/Insert(mob/living/carbon/reciever, special = FALSE, drop_if_replaced = TRUE) - AddElement(/datum/element/noticable_organ, "hunch[owner.p_es()] over unnaturally!") - return ..() - -/obj/item/organ/internal/heart/rat/Insert(mob/living/carbon/reciever, special, drop_if_replaced) +/obj/item/organ/internal/heart/rat/Insert(mob/living/carbon/receiver, special, drop_if_replaced) . = ..() - if(!ishuman(reciever)) + if(!ishuman(receiver)) return - var/mob/living/carbon/human/human_reciever = reciever - human_reciever.dna.add_mutation(/datum/mutation/human/dwarfism) + var/mob/living/carbon/human/human_receiver = receiver + if(!human_receiver.can_mutate()) + return + human_receiver.dna.add_mutation(/datum/mutation/human/dwarfism) //but 1.5 damage - human_reciever.physiology.damage_resistance -= 50 + if(human_receiver.physiology) + human_receiver.physiology.damage_resistance -= 50 /obj/item/organ/internal/heart/rat/Remove(mob/living/carbon/heartless, special) . = ..() if(!ishuman(heartless)) return var/mob/living/carbon/human/human_heartless = heartless + if(!human_heartless.can_mutate()) + return human_heartless.dna.remove_mutation(/datum/mutation/human/dwarfism) - human_heartless.physiology.damage_resistance += 50 - - + if(human_heartless.physiology) + human_heartless.physiology.damage_resistance += 50 /// you occasionally squeak, and have some rat related verbal tics /obj/item/organ/internal/tongue/rat @@ -145,7 +137,7 @@ /obj/item/organ/internal/tongue/rat/Initialize(mapload) . = ..() - AddElement(/datum/element/noticable_organ, "teeth are oddly shaped and yellowing", BODY_ZONE_HEAD) + AddElement(/datum/element/noticable_organ, "teeth are oddly shaped and yellowing", BODY_ZONE_PRECISE_MOUTH) AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/rat) /obj/item/organ/internal/tongue/rat/modify_speech(datum/source, list/speech_args) @@ -181,5 +173,4 @@ #undef RAT_ORGAN_COLOR #undef RAT_SCLERA_COLOR #undef RAT_PUPIL_COLOR - #undef RAT_COLORS diff --git a/code/game/objects/items/dna_injector.dm b/code/game/objects/items/dna_injector.dm index ad255f94f07..02efd3c9f0a 100644 --- a/code/game/objects/items/dna_injector.dm +++ b/code/game/objects/items/dna_injector.dm @@ -41,30 +41,30 @@ return attack_hand(user, modifiers) /obj/item/dnainjector/proc/inject(mob/living/carbon/target, mob/user) - if(target.has_dna() && !HAS_TRAIT(target, TRAIT_GENELESS) && !HAS_TRAIT(target, TRAIT_BADDNA)) - for(var/removed_mutation in remove_mutations) - target.dna.remove_mutation(removed_mutation) - for(var/added_mutation in add_mutations) - if(added_mutation == /datum/mutation/human/race) - message_admins("[ADMIN_LOOKUPFLW(user)] injected [key_name_admin(target)] with the [name] [span_danger("(MONKEY)")]") - if(target.dna.mutation_in_sequence(added_mutation)) - target.dna.activate_mutation(added_mutation) - else - target.dna.add_mutation(added_mutation, MUT_EXTRA) - 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.dna.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) - return TRUE - return FALSE + if(!target.can_mutate()) + return FALSE + for(var/removed_mutation in remove_mutations) + target.dna.remove_mutation(removed_mutation) + for(var/added_mutation in add_mutations) + if(added_mutation == /datum/mutation/human/race) + message_admins("[ADMIN_LOOKUPFLW(user)] injected [key_name_admin(target)] with the [name] [span_danger("(MONKEY)")]") + if(target.dna.mutation_in_sequence(added_mutation)) + target.dna.activate_mutation(added_mutation) + else + target.dna.add_mutation(added_mutation, MUT_EXTRA) + 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.dna.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) + return TRUE /obj/item/dnainjector/attack(mob/target, mob/user) if(!ISADVANCEDTOOLUSER(user)) @@ -105,52 +105,50 @@ 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.has_dna() && !(HAS_TRAIT(target, TRAIT_BADDNA))) - var/endtime = world.time + duration - for(var/mutation in remove_mutations) - if(mutation == /datum/mutation/human/race) - if(!ismonkey(target)) - continue - target = target.dna.remove_mutation(mutation) - else - target.dna.remove_mutation(mutation) - for(var/mutation in add_mutations) - if(target.dna.get_mutation(mutation)) - continue //Skip permanent mutations we already have. - if(mutation == /datum/mutation/human/race && !ismonkey(target)) - message_admins("[ADMIN_LOOKUPFLW(user)] injected [key_name_admin(target)] with the [name] [span_danger("(MONKEY)")]") - target = target.dna.add_mutation(mutation, MUT_OTHER, endtime) - else - target.dna.add_mutation(mutation, MUT_OTHER, endtime) - if(fields) - if(fields["name"] && fields["UE"] && fields["blood_type"]) - 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.dna.blood_type - target.real_name = fields["name"] - target.dna.unique_enzymes = fields["UE"] - target.name = target.real_name - target.dna.blood_type = fields["blood_type"] - target.dna.temporary_mutations[UE_CHANGED] = endtime - if(fields["UI"]) //UI+UE - 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"]) - target.dna.temporary_mutations[UI_CHANGED] = endtime - if(fields["UF"]) //UI+UE - 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"]) - target.dna.temporary_mutations[UF_CHANGED] = endtime - if(fields["UI"] || fields["UF"]) - target.updateappearance(mutcolor_update = TRUE, mutations_overlay_update = TRUE) - return TRUE - else + if(!target.can_mutate()) return FALSE + var/endtime = world.time + duration + for(var/mutation in remove_mutations) + if(mutation == /datum/mutation/human/race) + if(!ismonkey(target)) + continue + target = target.dna.remove_mutation(mutation) + else + target.dna.remove_mutation(mutation) + for(var/mutation in add_mutations) + if(target.dna.get_mutation(mutation)) + continue //Skip permanent mutations we already have. + if(mutation == /datum/mutation/human/race && !ismonkey(target)) + message_admins("[ADMIN_LOOKUPFLW(user)] injected [key_name_admin(target)] with the [name] [span_danger("(MONKEY)")]") + target = target.dna.add_mutation(mutation, MUT_OTHER, endtime) + else + target.dna.add_mutation(mutation, MUT_OTHER, endtime) + if(fields) + if(fields["name"] && fields["UE"] && fields["blood_type"]) + 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.dna.blood_type + target.real_name = fields["name"] + target.dna.unique_enzymes = fields["UE"] + target.name = target.real_name + target.dna.blood_type = fields["blood_type"] + target.dna.temporary_mutations[UE_CHANGED] = endtime + if(fields["UI"]) //UI+UE + 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"]) + target.dna.temporary_mutations[UI_CHANGED] = endtime + if(fields["UF"]) //UI+UE + 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"]) + target.dna.temporary_mutations[UF_CHANGED] = endtime + if(fields["UI"] || fields["UF"]) + target.updateappearance(mutcolor_update = TRUE, mutations_overlay_update = TRUE) + return TRUE /obj/item/dnainjector/timed/hulk name = "\improper DNA injector (Hulk)" @@ -171,24 +169,23 @@ var/crispr_charge = FALSE // Look for viruses, look at symptoms, if research and Dormant DNA Activator or Viral Evolutionary Acceleration, set to true /obj/item/dnainjector/activator/inject(mob/living/carbon/target, mob/user) - if(target.has_dna() && !HAS_TRAIT(target, TRAIT_GENELESS) && !HAS_TRAIT(target, TRAIT_BADDNA)) - for(var/mutation in add_mutations) - var/datum/mutation/human/added_mutation = mutation - if(istype(added_mutation, /datum/mutation/human)) - mutation = added_mutation.type - if(!target.dna.activate_mutation(added_mutation)) - if(doitanyway) - target.dna.add_mutation(added_mutation, MUT_EXTRA) - else if(research && target.client) - filled = TRUE - - for(var/datum/disease/advance/disease in target.diseases) - for(var/datum/symptom/symp in disease.symptoms) - if((symp.type == /datum/symptom/genetic_mutation) || (symp.type == /datum/symptom/viralevolution)) - crispr_charge = TRUE - log_combat(user, target, "[!doitanyway ? "failed to inject" : "injected"]", "[src] ([mutation])[crispr_charge ? " with CRISPR charge" : ""]") - return TRUE - return FALSE + if(!target.can_mutate()) + return FALSE + for(var/mutation in add_mutations) + var/datum/mutation/human/added_mutation = mutation + if(istype(added_mutation, /datum/mutation/human)) + mutation = added_mutation.type + if(!target.dna.activate_mutation(added_mutation)) + if(doitanyway) + target.dna.add_mutation(added_mutation, MUT_EXTRA) + else if(research && target.client) + filled = TRUE + for(var/datum/disease/advance/disease in target.diseases) + for(var/datum/symptom/symp in disease.symptoms) + if((symp.type == /datum/symptom/genetic_mutation) || (symp.type == /datum/symptom/viralevolution)) + crispr_charge = TRUE + log_combat(user, target, "[!doitanyway ? "failed to inject" : "injected"]", "[src] ([mutation])[crispr_charge ? " with CRISPR charge" : ""]") + return TRUE /// DNA INJECTORS diff --git a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm index ed259a9318a..1641b20cde1 100644 --- a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm @@ -52,7 +52,7 @@ /datum/reagent/toxin/mutagen/expose_mob(mob/living/carbon/exposed_mob, methods=TOUCH, reac_volume) . = ..() - if(!exposed_mob.has_dna() || HAS_TRAIT(exposed_mob, TRAIT_GENELESS) || HAS_TRAIT(exposed_mob, TRAIT_BADDNA)) + if(!exposed_mob.can_mutate()) return //No robots, AIs, aliens, Ians or other mobs should be affected by this. if(((methods & VAPOR) && prob(min(33, reac_volume))) || (methods & (INGEST|PATCH|INJECT))) exposed_mob.random_mutate_unique_identity() diff --git a/code/modules/surgery/organs/_organ.dm b/code/modules/surgery/organs/_organ.dm index 12b2e20c568..d32787a2918 100644 --- a/code/modules/surgery/organs/_organ.dm +++ b/code/modules/surgery/organs/_organ.dm @@ -62,34 +62,35 @@ INITIALIZE_IMMEDIATE(/obj/item/organ) /* * Insert the organ into the select mob. * - * reciever - the mob who will get our organ + * receiver - the mob who will get our organ * special - "quick swapping" an organ out - when TRUE, the mob will be unaffected by not having that organ for the moment * drop_if_replaced - if there's an organ in the slot already, whether we drop it afterwards */ -/obj/item/organ/proc/Insert(mob/living/carbon/reciever, special = FALSE, drop_if_replaced = TRUE) - if(!iscarbon(reciever) || owner == reciever) +/obj/item/organ/proc/Insert(mob/living/carbon/receiver, special = FALSE, drop_if_replaced = TRUE) + if(!iscarbon(receiver) || owner == receiver) return FALSE - var/obj/item/organ/replaced = reciever.getorganslot(slot) + var/obj/item/organ/replaced = receiver.getorganslot(slot) if(replaced) - replaced.Remove(reciever, special = TRUE) + replaced.Remove(receiver, special = TRUE) if(drop_if_replaced) - replaced.forceMove(get_turf(reciever)) + replaced.forceMove(get_turf(receiver)) else qdel(replaced) - reciever.internal_organs |= src - reciever.internal_organs_slot[slot] = src + receiver.internal_organs |= src + receiver.internal_organs_slot[slot] = src + owner = receiver - SEND_SIGNAL(src, COMSIG_ORGAN_IMPLANTED, reciever) - SEND_SIGNAL(reciever, COMSIG_CARBON_GAIN_ORGAN, src, special) - - owner = reciever moveToNullspace() RegisterSignal(owner, COMSIG_PARENT_EXAMINE, PROC_REF(on_owner_examine)) - update_organ_traits(reciever) + update_organ_traits(receiver) for(var/datum/action/action as anything in actions) - action.Grant(reciever) + action.Grant(receiver) + + SEND_SIGNAL(src, COMSIG_ORGAN_IMPLANTED, receiver) + SEND_SIGNAL(receiver, COMSIG_CARBON_GAIN_ORGAN, src, special) + return TRUE /* @@ -100,7 +101,7 @@ INITIALIZE_IMMEDIATE(/obj/item/organ) */ /obj/item/organ/proc/Remove(mob/living/carbon/organ_owner, special = FALSE) - UnregisterSignal(owner, COMSIG_PARENT_EXAMINE) + UnregisterSignal(organ_owner, COMSIG_PARENT_EXAMINE) organ_owner.internal_organs -= src if(organ_owner.internal_organs_slot[slot] == src) diff --git a/code/modules/surgery/organs/ears.dm b/code/modules/surgery/organs/ears.dm index f6265c82273..44eb8b0215b 100644 --- a/code/modules/surgery/organs/ears.dm +++ b/code/modules/surgery/organs/ears.dm @@ -69,16 +69,16 @@ damage_multiplier = 2 /obj/item/organ/internal/ears/cat/Insert(mob/living/carbon/human/ear_owner, special = 0, drop_if_replaced = TRUE) - ..() - if(istype(ear_owner)) + . = ..() + if(istype(ear_owner) && ear_owner.dna) color = ear_owner.hair_color ear_owner.dna.features["ears"] = ear_owner.dna.species.mutant_bodyparts["ears"] = "Cat" ear_owner.dna.update_uf_block(DNA_EARS_BLOCK) ear_owner.update_body() /obj/item/organ/internal/ears/cat/Remove(mob/living/carbon/human/ear_owner, special = 0) - ..() - if(istype(ear_owner)) + . = ..() + if(istype(ear_owner) && ear_owner.dna) color = ear_owner.hair_color ear_owner.dna.species.mutant_bodyparts -= "ears" ear_owner.update_body() diff --git a/code/modules/surgery/organs/external/_external_organs.dm b/code/modules/surgery/organs/external/_external_organs.dm index 8fa38ff94c1..acf672caf56 100644 --- a/code/modules/surgery/organs/external/_external_organs.dm +++ b/code/modules/surgery/organs/external/_external_organs.dm @@ -63,8 +63,8 @@ return ..() -/obj/item/organ/external/Insert(mob/living/carbon/reciever, special, drop_if_replaced) - var/obj/item/bodypart/limb = reciever.get_bodypart(deprecise_zone(zone)) +/obj/item/organ/external/Insert(mob/living/carbon/receiver, special, drop_if_replaced) + var/obj/item/bodypart/limb = receiver.get_bodypart(deprecise_zone(zone)) if(!limb) return FALSE @@ -76,16 +76,16 @@ if(bodypart_overlay.imprint_on_next_insertion) //We only want this set *once* - bodypart_overlay.set_appearance_from_name(reciever.dna.features[bodypart_overlay.feature_key]) + bodypart_overlay.set_appearance_from_name(receiver.dna.features[bodypart_overlay.feature_key]) bodypart_overlay.imprint_on_next_insertion = FALSE ownerlimb = limb add_to_limb(ownerlimb) if(external_bodytypes) - limb.synchronize_bodytypes(reciever) + limb.synchronize_bodytypes(receiver) - reciever.update_body_parts() + receiver.update_body_parts() /obj/item/organ/external/Remove(mob/living/carbon/organ_owner, special, moving) . = ..() @@ -261,11 +261,11 @@ ///Store our old datum here for if our antennae are healed var/original_sprite_datum -/obj/item/organ/external/antennae/Insert(mob/living/carbon/reciever, special, drop_if_replaced) +/obj/item/organ/external/antennae/Insert(mob/living/carbon/receiver, special, drop_if_replaced) . = ..() - RegisterSignal(reciever, COMSIG_HUMAN_BURNING, PROC_REF(try_burn_antennae)) - RegisterSignal(reciever, COMSIG_LIVING_POST_FULLY_HEAL, PROC_REF(heal_antennae)) + RegisterSignal(receiver, COMSIG_HUMAN_BURNING, PROC_REF(try_burn_antennae)) + RegisterSignal(receiver, COMSIG_LIVING_POST_FULLY_HEAL, PROC_REF(heal_antennae)) /obj/item/organ/external/antennae/Remove(mob/living/carbon/organ_owner, special, moving) . = ..() diff --git a/code/modules/surgery/organs/external/tails.dm b/code/modules/surgery/organs/external/tails.dm index 45dc97d5098..4b50b9480b3 100644 --- a/code/modules/surgery/organs/external/tails.dm +++ b/code/modules/surgery/organs/external/tails.dm @@ -17,19 +17,19 @@ ///The original owner of this tail var/original_owner //Yay, snowflake code! -/obj/item/organ/external/tail/Insert(mob/living/carbon/reciever, special, drop_if_replaced) +/obj/item/organ/external/tail/Insert(mob/living/carbon/receiver, special, drop_if_replaced) . = ..() if(.) - RegisterSignal(reciever, COMSIG_ORGAN_WAG_TAIL, PROC_REF(wag)) - original_owner ||= WEAKREF(reciever) + RegisterSignal(receiver, COMSIG_ORGAN_WAG_TAIL, PROC_REF(wag)) + original_owner ||= WEAKREF(receiver) - reciever.clear_mood_event("tail_lost") - reciever.clear_mood_event("tail_balance_lost") + receiver.clear_mood_event("tail_lost") + receiver.clear_mood_event("tail_balance_lost") - if(IS_WEAKREF_OF(reciever, original_owner)) - reciever.clear_mood_event("wrong_tail_regained") - else if(type in reciever.dna.species.external_organs) - reciever.add_mood_event("wrong_tail_regained", /datum/mood_event/tail_regained_wrong) + if(IS_WEAKREF_OF(receiver, original_owner)) + receiver.clear_mood_event("wrong_tail_regained") + else if(type in receiver.dna.species.external_organs) + receiver.add_mood_event("wrong_tail_regained", /datum/mood_event/tail_regained_wrong) /obj/item/organ/external/tail/Remove(mob/living/carbon/organ_owner, special, moving) if(wag_flags & WAG_WAGGING) @@ -118,7 +118,7 @@ ///A reference to the paired_spines, since for some fucking reason tail spines are tied to the spines themselves. var/obj/item/organ/external/spines/paired_spines -/obj/item/organ/external/tail/lizard/Insert(mob/living/carbon/reciever, special, drop_if_replaced) +/obj/item/organ/external/tail/lizard/Insert(mob/living/carbon/receiver, special, drop_if_replaced) . = ..() if(.) paired_spines = ownerlimb.owner.getorganslot(ORGAN_SLOT_EXTERNAL_SPINES) diff --git a/code/modules/surgery/organs/external/wings/functional_wings.dm b/code/modules/surgery/organs/external/wings/functional_wings.dm index b5abe6f7b7a..902f2b6b6ac 100644 --- a/code/modules/surgery/organs/external/wings/functional_wings.dm +++ b/code/modules/surgery/organs/external/wings/functional_wings.dm @@ -26,12 +26,12 @@ ///Are our wings open or closed? var/wings_open = FALSE -/obj/item/organ/external/wings/functional/Insert(mob/living/carbon/reciever, special, drop_if_replaced) +/obj/item/organ/external/wings/functional/Insert(mob/living/carbon/receiver, special, drop_if_replaced) . = ..() if(isnull(fly)) fly = new - fly.Grant(reciever) + fly.Grant(receiver) /obj/item/organ/external/wings/functional/Remove(mob/living/carbon/organ_owner, special, moving) . = ..() diff --git a/code/modules/surgery/organs/external/wings/moth_wings.dm b/code/modules/surgery/organs/external/wings/moth_wings.dm index 4a305f3a9bd..8be02c29272 100644 --- a/code/modules/surgery/organs/external/wings/moth_wings.dm +++ b/code/modules/surgery/organs/external/wings/moth_wings.dm @@ -14,12 +14,12 @@ ///Store our old datum here for if our burned wings are healed var/original_sprite_datum -/obj/item/organ/external/wings/moth/Insert(mob/living/carbon/reciever, special, drop_if_replaced) +/obj/item/organ/external/wings/moth/Insert(mob/living/carbon/receiver, special, drop_if_replaced) . = ..() - RegisterSignal(reciever, COMSIG_HUMAN_BURNING, PROC_REF(try_burn_wings)) - RegisterSignal(reciever, COMSIG_LIVING_POST_FULLY_HEAL, PROC_REF(heal_wings)) - RegisterSignal(reciever, COMSIG_MOVABLE_PRE_MOVE, PROC_REF(update_float_move)) + RegisterSignal(receiver, COMSIG_HUMAN_BURNING, PROC_REF(try_burn_wings)) + RegisterSignal(receiver, COMSIG_LIVING_POST_FULLY_HEAL, PROC_REF(heal_wings)) + RegisterSignal(receiver, COMSIG_MOVABLE_PRE_MOVE, PROC_REF(update_float_move)) /obj/item/organ/external/wings/moth/Remove(mob/living/carbon/organ_owner, special, moving) . = ..() diff --git a/code/modules/surgery/organs/organ_internal.dm b/code/modules/surgery/organs/organ_internal.dm index 9bb3f42543f..640937e8be1 100644 --- a/code/modules/surgery/organs/organ_internal.dm +++ b/code/modules/surgery/organs/organ_internal.dm @@ -14,9 +14,9 @@ STOP_PROCESSING(SSobj, src) return ..() -/obj/item/organ/internal/Insert(mob/living/carbon/reciever, special = FALSE, drop_if_replaced = TRUE) +/obj/item/organ/internal/Insert(mob/living/carbon/receiver, special = FALSE, drop_if_replaced = TRUE) . = ..() - if(!.) + if(!. || !owner) return // internal_organs_slot must ALWAYS be ordered in the same way as organ_process_order diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index 07267bb443f..a0c0feab553 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -152,6 +152,7 @@ #include "objectives.dm" #include "orderable_items.dm" #include "operating_table.dm" +#include "organ_set_bonus.dm" #include "outfit_sanity.dm" #include "paintings.dm" #include "pills.dm" diff --git a/code/modules/unit_tests/organ_set_bonus.dm b/code/modules/unit_tests/organ_set_bonus.dm new file mode 100644 index 00000000000..f40c8430166 --- /dev/null +++ b/code/modules/unit_tests/organ_set_bonus.dm @@ -0,0 +1,86 @@ +/// Tests the "organ set bonus" Elements and Status Effects, which are for the DNA Infuser. +/// Ensures the developers properly change IDs to be unique. +/datum/unit_test/organ_set_bonus_id + +/datum/unit_test/organ_set_bonus_id/Run() + var/list/bonus_effects = typesof(/datum/status_effect/organ_set_bonus) + var/list/existing_ids = list() + for(var/datum/status_effect/organ_set_bonus/bonus_effect as anything in bonus_effects) + var/effect_id = initial(bonus_effect.id) + var/existing_status = (effect_id in existing_ids) + if(existing_status) + TEST_FAIL("The ID of [bonus_effect] was duplicated in another status effect.") + else + existing_ids += effect_id + +/datum/unit_test/organ_set_bonus_sanity/proc/check_status_type(mob/living/carbon/human/lab_rat, datum/status_effect/status_type) + var/datum/status_effect/organ_set_bonus/added_status + for(var/datum/status_effect/present_effect as anything in lab_rat.status_effects) + if(istype(present_effect, status_type)) + return present_effect + +/// Tests the "organ set bonus" Elements and Status Effects, which are for the DNA Infuser. +/// Ensures that each Element and Status Effect gets properly added/removed from mobs. +/datum/unit_test/organ_set_bonus_sanity + +/datum/unit_test/organ_set_bonus_sanity/Run() + // Fetch the globally instantiated DNA Infuser entries. + for(var/datum/infuser_entry/infuser_entry as anything in GLOB.infuser_entries) + var/output_organs = infuser_entry.output_organs + // Human which will reiceve organs. + var/mob/living/carbon/human/lab_rat = allocate(/mob/living/carbon/human/consistent) + var/list/obj/item/organ/inserted_organs = list() + for(var/obj/item/organ/organ as anything in output_organs) + organ = new organ() + var/implant_ok = organ.Insert(lab_rat, special = TRUE, drop_if_replaced = FALSE) + if(!implant_ok) + TEST_FAIL("The organ \"[organ.type]\" for \"[infuser_entry.type]\" was not inserted in the mob when expected.") + continue + inserted_organs += organ + + var/total_inserted = length(inserted_organs) + + // Search for added Status Effect. + var/datum/status_effect/organ_set_bonus/added_status = check_status_type(lab_rat, /datum/status_effect/organ_set_bonus) + + // Threshold description implies an organ set bonus. + // Without it, we'll assume there isn't a Status Effect to look for. + var/has_threshold = (infuser_entry.threshold_desc != DNA_INFUSION_NO_THRESHOLD) + var/total_organs_needed = added_status ? added_status.organs_needed : 0 + var/total_organs = length(infuser_entry.output_organs) + + // Found a Status Effect but no threshold description. + if(!has_threshold && added_status) + TEST_FAIL("The threshold_desc variable for \"[infuser_entry.type]\" was an empty string when a description was expected.") + + // Since threshold_desc is filled-in, we expect the organ set bonus. + if(has_threshold) + if(!added_status) + TEST_FAIL("The \"/datum/status_effect/organ_set_bonus\" for \"[infuser_entry.type]\" was not added to the mob when expected.") + else if(!total_organs_needed) + TEST_FAIL("The \"needed_organs\" variable for \"[added_status.type]\" was 0 or null, when a positive number was expected.") + else if(total_organs_needed > total_organs) + TEST_FAIL("The \"output_organs\" list for \"[infuser_entry.type]\" had a length of \"[length(infuser_entry.output_organs)]\" when a minimum of at least [total_organs_needed] organs was specified in \"[added_status.type]\".") + else if(!added_status.bonus_active) + TEST_FAIL("The \"[added_status.type]\" bonus was not activated after inserting [total_inserted] of the [total_organs_needed] required organs in the mob, when it was expected.") + + // Nothing to do. + if(!total_inserted) + continue + + // Bonus of the Fly mutation swaps out all the organs, making it rather permanent. + // As a result, the inserted_organs list is un-usable by this point. + if(istype(infuser_entry, /datum/infuser_entry/fly)) + continue + + // Remove all the organs which were just added. + var/total_removed = 0 + for(var/obj/item/organ/test_organ as anything in inserted_organs) + test_organ.Remove(lab_rat, special = TRUE) + total_removed += 1 + + added_status = check_status_type(lab_rat, /datum/status_effect/organ_set_bonus) + + // Search for added Status Effect. + if(added_status && added_status.bonus_active) + TEST_FAIL("The \"[added_status.type]\" bonus was not deactivated after removing [total_removed] of the [total_organs_needed] required organs from the mob, when it was expected.") diff --git a/code/modules/wiremod/shell/brain_computer_interface.dm b/code/modules/wiremod/shell/brain_computer_interface.dm index 62dd75c293b..7893a755b2c 100644 --- a/code/modules/wiremod/shell/brain_computer_interface.dm +++ b/code/modules/wiremod/shell/brain_computer_interface.dm @@ -17,11 +17,11 @@ new /obj/item/circuit_component/bci_core, ), SHELL_CAPACITY_SMALL, starting_circuit = circuit) -/obj/item/organ/internal/cyberimp/bci/Insert(mob/living/carbon/reciever, special, drop_if_replaced) +/obj/item/organ/internal/cyberimp/bci/Insert(mob/living/carbon/receiver, special, drop_if_replaced) . = ..() // Organs are put in nullspace, but this breaks circuit interactions - forceMove(reciever) + forceMove(receiver) /obj/item/organ/internal/cyberimp/bci/say(message, bubble_type, list/spans, sanitize, datum/language/language, ignore_spam, forced = null, filterproof = null, message_range = 7, datum/saymode/saymode = null) if (owner)