Skip to content

Commit

Permalink
[MIRROR] DNA Infusion Refactor: Separates DNA Infusion Behavior from …
Browse files Browse the repository at this point in the history
…DNA Infuser (#2185)

* 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: tralezab <[email protected]>
  • Loading branch information
2 people authored and StealsThePRs committed Apr 25, 2024
1 parent 0cfd747 commit ba77643
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 92 deletions.
101 changes: 20 additions & 81 deletions code/game/machinery/dna_infuser/dna_infuser.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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...")
Expand All @@ -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(
Expand Down Expand Up @@ -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))
Expand All @@ -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
Expand Down
75 changes: 75 additions & 0 deletions code/game/machinery/dna_infuser/dna_infusion.dm
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion code/game/machinery/dna_infuser/infuser_book.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
11 changes: 2 additions & 9 deletions code/game/machinery/dna_infuser/infuser_entry.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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 --//
Expand Down
2 changes: 1 addition & 1 deletion code/modules/unit_tests/organ_set_bonus.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions tgstation.dme
Original file line number Diff line number Diff line change
Expand Up @@ -2193,6 +2193,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"
Expand Down

0 comments on commit ba77643

Please sign in to comment.