From 95edb4c1af7b2870dd55a2e69265efea0acbc35e Mon Sep 17 00:00:00 2001 From: Iajret Creature <122297233+Steals-The-PRs@users.noreply.github.com> Date: Fri, 26 Apr 2024 10:19:30 +0300 Subject: [PATCH] [MIRROR] DNA Infusion Refactor: Separates DNA Infusion Behavior from DNA Infuser (#2185) (#3061) * DNA Infusion Refactor: Separates DNA Infusion Behavior from DNA Infuser (#82829) ## About The Pull Request - infuser entries global is now an assoc list type -> singleton. makes it easier to pick specific entries as needed - separated infusion behavior onto both movable level (for machine occupants and things that can potentially be infused) and human level (for the actual infusion into a human) - [x] tested ## Why It's Good For The Game Upcoming plans is to fix up maintenance sect's organ replacement system that just so happens to work a lot like how infusions do with actual infusion mechanics, and that requires this prerequisite. In general outside of that vision I see a lot of potential in alternate infusion sources, from wherever they may be. ## Changelog no player side changes, this is a refactor * DNA Infusion Refactor: Separates DNA Infusion Behavior from DNA Infuser --------- Co-authored-by: NovaBot <154629622+NovaBot13@users.noreply.github.com> Co-authored-by: tralezab <40974010+tralezab@users.noreply.github.com> --- .../game/machinery/dna_infuser/dna_infuser.dm | 101 ++++-------------- .../machinery/dna_infuser/dna_infusion.dm | 75 +++++++++++++ .../machinery/dna_infuser/infuser_book.dm | 2 +- .../machinery/dna_infuser/infuser_entry.dm | 11 +- code/modules/unit_tests/organ_set_bonus.dm | 2 +- tgstation.dme | 1 + 6 files changed, 100 insertions(+), 92 deletions(-) create mode 100644 code/game/machinery/dna_infuser/dna_infusion.dm diff --git a/code/game/machinery/dna_infuser/dna_infuser.dm b/code/game/machinery/dna_infuser/dna_infuser.dm index 6c239089f5d..7b5fcb501a0 100644 --- a/code/game/machinery/dna_infuser/dna_infuser.dm +++ b/code/game/machinery/dna_infuser/dna_infuser.dm @@ -64,8 +64,7 @@ balloon_alert(user, "not while it's on!") return if(occupant && infusing_from) - // Abort infusion if the occupant is invalid. - if(!is_valid_occupant(occupant, user)) + if(!occupant.can_infuse(user)) playsound(src, 'sound/machines/scanbuzz.ogg', 35, vary = TRUE) return balloon_alert(user, "starting DNA infusion...") @@ -77,92 +76,45 @@ var/mob/living/carbon/human/human_occupant = occupant infusing = TRUE visible_message(span_notice("[src] hums to life, beginning the infusion process!")) + + infusing_into = infusing_from.get_infusion_entry() var/fail_title = "" - var/fail_reason = "" - // Replace infusing_into with a [/datum/infuser_entry] - for(var/datum/infuser_entry/entry as anything in GLOB.infuser_entries) - if(entry.tier == DNA_MUTANT_UNOBTAINABLE) - continue - if(is_type_in_list(infusing_from, entry.input_obj_or_mob)) - if(entry.tier > max_tier_allowed) - fail_title = "Overcomplexity" - fail_reason = "DNA too complicated to infuse. The machine needs to infuse simpler DNA first." - infusing_into = entry - break - if(!infusing_into) - //no valid recipe, so you get a fly mutation - if(!fail_reason) - fail_title = "Unknown DNA" - fail_reason = "Unknown DNA. Consult the \"DNA infusion book\"." - infusing_into = GLOB.infuser_entries[1] + var/fail_explanation = "" + if(istype(infusing_into, /datum/infuser_entry/fly)) + fail_title = "Unknown DNA" + fail_explanation = "Unknown DNA. Consult the \"DNA infusion book\"." + if(infusing_into.tier > max_tier_allowed) + infusing_into = GLOB.infuser_entries[/datum/infuser_entry/fly] + fail_title = "Overcomplexity" + fail_explanation = "DNA too complicated to infuse. The machine needs to infuse simpler DNA first." playsound(src, 'sound/machines/blender.ogg', 50, vary = TRUE) to_chat(human_occupant, span_danger("Little needles repeatedly prick you!")) human_occupant.take_overall_damage(10) human_occupant.add_mob_memory(/datum/memory/dna_infusion, protagonist = human_occupant, deuteragonist = infusing_from, mutantlike = infusing_into.infusion_desc) Shake(duration = INFUSING_TIME) addtimer(CALLBACK(human_occupant, TYPE_PROC_REF(/mob, emote), "scream"), INFUSING_TIME - 1 SECONDS) - addtimer(CALLBACK(src, PROC_REF(end_infuse), fail_reason, fail_title), INFUSING_TIME) + addtimer(CALLBACK(src, PROC_REF(end_infuse), fail_explanation, fail_title), INFUSING_TIME) update_appearance() -/obj/machinery/dna_infuser/proc/end_infuse(fail_reason, fail_title) - if(infuse_organ(occupant)) +/obj/machinery/dna_infuser/proc/end_infuse(fail_explanation, fail_title) + var/mob/living/carbon/human/human_occupant = occupant + if(human_occupant.infuse_organ(infusing_into)) + check_tier_progression(src) to_chat(occupant, span_danger("You feel yourself becoming more... [infusing_into.infusion_desc]?")) infusing = FALSE infusing_into = null QDEL_NULL(infusing_from) playsound(src, 'sound/machines/microwave/microwave-end.ogg', 100, vary = FALSE) - if(fail_reason) + if(fail_explanation) playsound(src, 'sound/machines/printer.ogg', 100, TRUE) visible_message(span_notice("[src] prints an error report.")) var/obj/item/paper/printed_paper = new /obj/item/paper(loc) printed_paper.name = "error report - '[fail_title]'" - printed_paper.add_raw_text(fail_reason) + printed_paper.add_raw_text(fail_explanation) printed_paper.update_appearance() toggle_open() update_appearance() -/// Attempt to replace/add-to the occupant's organs with "mutated" equivalents. -/// Returns TRUE on success, FALSE on failure. -/// Requires the target mob to have an existing organic organ to "mutate". -// 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)) - 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() - new_organ.replace_into(target) - check_tier_progression(target) - return TRUE - -/// Picks a random mutated organ from the infuser entry which is also compatible with the target mob. -/// 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() - // 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.get_organ_slot(initial(new_organ.slot)) - if(old_organ) - if((old_organ.type != new_organ) && !IS_ROBOTIC_ORGAN(old_organ)) - continue // Old organ can be mutated! - else if(ispath(new_organ, /obj/item/organ/external)) - continue // External organ can be grown! - // Internal organ is either missing, or is non-organic. - potential_new_organs -= new_organ - // Pick a random organ from the filtered list. - if(length(potential_new_organs)) - return pick(potential_new_organs) - return FALSE - /// checks to see if the machine should progress a new tier. /obj/machinery/dna_infuser/proc/check_tier_progression(mob/living/carbon/human/target) if( @@ -254,19 +206,6 @@ infusing_from = target infusing_from.forceMove(src) -/// 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: 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 - /// Verify that the given infusion source/mob is a dead creature. /obj/machinery/dna_infuser/proc/is_valid_infusion(atom/movable/target, mob/user) if(user.stat != CONSCIOUS || HAS_TRAIT(user, TRAIT_UI_BLOCKED) || !Adjacent(user) || !user.Adjacent(target) || !ISADVANCEDTOOLUSER(user)) @@ -291,10 +230,10 @@ /obj/machinery/dna_infuser/click_alt(mob/user) if(infusing) balloon_alert(user, "not while it's on!") - return CLICK_ACTION_BLOCKING + return if(!infusing_from) balloon_alert(user, "no sample to eject!") - return CLICK_ACTION_BLOCKING + return balloon_alert(user, "ejected sample") infusing_from.forceMove(get_turf(src)) infusing_from = null diff --git a/code/game/machinery/dna_infuser/dna_infusion.dm b/code/game/machinery/dna_infuser/dna_infusion.dm new file mode 100644 index 00000000000..c902240404c --- /dev/null +++ b/code/game/machinery/dna_infuser/dna_infusion.dm @@ -0,0 +1,75 @@ + +///returns a boolean whether a machine occupant can be infused +/atom/movable/proc/can_infuse(mob/feedback_target) + if(feedback_target) + balloon_alert(feedback_target, "no dna!") + return FALSE + +/mob/living/can_infuse(mob/feedback_target) + if(feedback_target) + balloon_alert(feedback_target, "dna too simple!") + return FALSE + +/mob/living/carbon/human/can_infuse(mob/feedback_target) + // Checked by can_mutate but explicit feedback for this issue is good + if(HAS_TRAIT(src, TRAIT_BADDNA)) + if(feedback_target) + balloon_alert(feedback_target, "dna is corrupted!") + return FALSE + if(!can_mutate()) + if(feedback_target) + balloon_alert(feedback_target, "dna is missing!") + return FALSE + return TRUE + +///returns /datum/infuser_entry that matches an item being used for infusion, returns a fly mutation on failure +/atom/movable/proc/get_infusion_entry() as /datum/infuser_entry + var/datum/infuser_entry/found + for(var/datum/infuser_entry/entry as anything in flatten_list(GLOB.infuser_entries)) + if(entry.tier == DNA_MUTANT_UNOBTAINABLE) + continue + if(is_type_in_list(src, entry.input_obj_or_mob)) + found = entry + break + if(!found) + found = GLOB.infuser_entries[/datum/infuser_entry/fly] + return found + +/// Attempt to replace/add-to the occupant's organs with "mutated" equivalents. +/// Returns TRUE on success, FALSE on failure. +/// Requires the target mob to have an existing organic organ to "mutate". +// TODO: In the future, this should have more logic: +// - Replace non-mutant organs before mutant ones. +/mob/living/carbon/human/proc/infuse_organ(datum/infuser_entry/entry) + var/obj/item/organ/new_organ = pick_infusion_organ(entry) + if(!new_organ) + return FALSE + // Valid organ successfully picked. + new_organ = new new_organ() + new_organ.replace_into(src) + return TRUE + +/// Picks a random mutated organ from the given infuser entry which is also compatible with this human. +/// 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. +/mob/living/carbon/human/proc/pick_infusion_organ(datum/infuser_entry/entry) + if(!entry) + return FALSE + var/list/obj/item/organ/potential_new_organs = entry.output_organs.Copy() + // Remove organ typepaths from the list if they're incompatible with target. + for(var/obj/item/organ/new_organ as anything in entry.output_organs) + var/obj/item/organ/old_organ = get_organ_slot(initial(new_organ.slot)) + if(old_organ) + if((old_organ.type != new_organ) && !IS_ROBOTIC_ORGAN(old_organ)) + continue // Old organ can be mutated! + else if(ispath(new_organ, /obj/item/organ/external)) + continue // External organ can be grown! + // Internal organ is either missing, or is non-organic. + potential_new_organs -= new_organ + // Pick a random organ from the filtered list. + if(length(potential_new_organs)) + return pick(potential_new_organs) + return FALSE diff --git a/code/game/machinery/dna_infuser/infuser_book.dm b/code/game/machinery/dna_infuser/infuser_book.dm index 75632178cca..416ed038d64 100644 --- a/code/game/machinery/dna_infuser/infuser_book.dm +++ b/code/game/machinery/dna_infuser/infuser_book.dm @@ -29,7 +29,7 @@ var/list/data = list() // Collect all info from each intry. var/list/entry_data = list() - for(var/datum/infuser_entry/entry as anything in GLOB.infuser_entries) + for(var/datum/infuser_entry/entry as anything in flatten_list(GLOB.infuser_entries)) if(entry.tier == DNA_MUTANT_UNOBTAINABLE) continue var/list/individual_entry_data = list() diff --git a/code/game/machinery/dna_infuser/infuser_entry.dm b/code/game/machinery/dna_infuser/infuser_entry.dm index dfcdfbbe08a..8b0bcfb3f79 100644 --- a/code/game/machinery/dna_infuser/infuser_entry.dm +++ b/code/game/machinery/dna_infuser/infuser_entry.dm @@ -4,17 +4,10 @@ GLOBAL_LIST_INIT(infuser_entries, prepare_infuser_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 fly/failed mutant case to show first. - var/prepended 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/fly) - prepended = entry - continue - entries += entry - var/list/sorted = sort_names(entries) - sorted.Insert(1, prepended) - return sorted + entries[entry_type] = entry + return entries /datum/infuser_entry //-- Vars for DNA Infusion Book --// diff --git a/code/modules/unit_tests/organ_set_bonus.dm b/code/modules/unit_tests/organ_set_bonus.dm index e89769fe7cf..e822c572afd 100644 --- a/code/modules/unit_tests/organ_set_bonus.dm +++ b/code/modules/unit_tests/organ_set_bonus.dm @@ -22,7 +22,7 @@ /datum/infuser_entry/fly, )) // Fetch the globally instantiated DNA Infuser entries. - for(var/datum/infuser_entry/infuser_entry as anything in GLOB.infuser_entries) + for(var/datum/infuser_entry/infuser_entry as anything in flatten_list(GLOB.infuser_entries)) var/output_organs = infuser_entry.output_organs var/mob/living/carbon/human/lab_rat = allocate(/mob/living/carbon/human/consistent) lab_rat.dna.mutant_bodyparts["moth_antennae"] = list(MUTANT_INDEX_NAME = "Plain", MUTANT_INDEX_COLOR_LIST = list("#FFFFFF"), MUTANT_INDEX_EMISSIVE_LIST = list(FALSE)) // NOVA EDIT - Customization diff --git a/tgstation.dme b/tgstation.dme index 1fa2731feac..e4265235b0d 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -2194,6 +2194,7 @@ #include "code\game\machinery\computer\records\records.dm" #include "code\game\machinery\computer\records\security.dm" #include "code\game\machinery\dna_infuser\dna_infuser.dm" +#include "code\game\machinery\dna_infuser\dna_infusion.dm" #include "code\game\machinery\dna_infuser\infuser_book.dm" #include "code\game\machinery\dna_infuser\infuser_entry.dm" #include "code\game\machinery\dna_infuser\infuser_entries\infuser_tier_one_entries.dm"