[MIRROR] Fix: DNA Infuser & Infusions [MDB IGNORE] (#18748)

* Fix: DNA Infuser & Infusions

* Fixes the damn conflict

* Fix: DNA Infuser Organ Filtering (#72745)

## About The Pull Request

My PR #72688 was merged mid-review. This is a followup PR to fix the
buggy code which was introduced by myself in between testing a naive
approach to organ filtering. The code does not work due to my own
oversight, as I was attempting to test buggy code in
`/obj/machinery/dna_infuser/proc/infuse_organ` when the prior PR was
merged.

I took this chance to simplify the code more, and I added an additional
proc to the DNA Infuser to replace inline code, `pick_organ`. The proc
picks a random mutated organ from the infuser entry which is also
compatible with the target mob. If none of the organs are compatible,
then the infusion still pricks you with needles but nearly-silently
fails by omitting the "You feel yourself becoming..." chat message.

`pick_organ` tries to return 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.

## Why It's Good For The Game

The DNA Infuser should filter organs depending on whether or not the
occupant has one already, if it is organic, and if it doesn't exist the
occupant can still get an external organ (such as a cat tail). This PR
introduces algorithmically correct code to filter the organs, and fixes
bugs introduced by my prior PR which caused the proc to throw a runtime.

## Changelog

🆑 A.C.M.O.
fix: Fixed buggy organ filtering code in the DNA Infuser which stopped
it from infusing organs.
/🆑

Co-authored-by: Dani Glore <fantasticdragons@gmail.com>
Co-authored-by: GoldenAlpharex <jerego1234@hotmail.com>
Co-authored-by: GoldenAlpharex <58045821+GoldenAlpharex@users.noreply.github.com>
This commit is contained in:
SkyratBot
2023-01-23 23:18:50 +01:00
committed by GitHub
parent 093d930b47
commit 8fcac218c1
5 changed files with 117 additions and 73 deletions
+5 -5
View File
@@ -6,9 +6,10 @@
/datum/element/noticable_organ
element_flags = ELEMENT_BESPOKE
argument_hash_start_idx = 2
// "[they] [desc here]", shows on examining someone with an infused organ.
/// "[they]|[their] [desc here]", shows on examining someone with an infused organ.
/// Uses a possessive pronoun (His/Her/Their) if a body zone is given, or a singular pronoun (He/She/They) otherwise.
var/infused_desc
///Which body zone has to be exposed. If none is set, this is always noticable.
/// Which body zone has to be exposed. If none is set, this is always noticable, and the description pronoun becomes singular instead of possesive.
var/body_zone
/datum/element/noticable_organ/Attach(datum/target, infused_desc, body_zone)
@@ -42,7 +43,6 @@
/datum/element/noticable_organ/proc/on_receiver_examine(mob/living/carbon/examined, mob/user, list/examine_list)
SIGNAL_HANDLER
var/list/covered = examined.get_covered_body_zones()
if(!body_zone || (body_zone in covered))
if(body_zone && (body_zone in examined.get_covered_body_zones()))
return
examine_list += span_notice("[examined.p_they(TRUE)] [infused_desc]")
examine_list += span_notice("[body_zone ? examined.p_their(TRUE) : examined.p_they(TRUE)] [infused_desc]")
+74 -41
View File
@@ -52,15 +52,20 @@
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))
playsound(src, 'sound/machines/scanbuzz.ogg', 35, vary = TRUE)
return
balloon_alert(user, "starting DNA infusion...")
start_infuse()
return
toggle_open(user)
/obj/machinery/dna_infuser/proc/start_infuse()
var/mob/living/carbon/human/human_occupant = occupant
infusing = TRUE
var/mob/living/carbon/human/hoccupant = occupant
visible_message(span_notice("[src] hums to life, beginning the infusion process!"))
// Replace infusing_into with a [/datum/infuser_entry]
for(var/datum/infuser_entry/entry as anything in GLOB.infuser_entries)
if(is_type_in_list(infusing_from, entry.input_obj_or_mob))
infusing_into = entry
@@ -68,49 +73,70 @@
if(!infusing_into)
//no valid recipe, so you get a fly mutation
infusing_into = GLOB.infuser_entries[1]
playsound(src, 'sound/machines/blender.ogg', 50, TRUE)
to_chat(hoccupant, span_danger("Little needles repeatedly prick you! And with each prick, you feel yourself becoming more... [infusing_into.infusion_desc]?"))
hoccupant.take_overall_damage(10)
hoccupant.add_mob_memory(/datum/memory/dna_infusion, protagonist = hoccupant, deuteragonist = infusing_from, mutantlike = infusing_into.infusion_desc)
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(15, 15, INFUSING_TIME)
addtimer(CALLBACK(occupant, TYPE_PROC_REF(/mob, emote), "scream"), INFUSING_TIME - 1 SECONDS)
addtimer(CALLBACK(human_occupant, TYPE_PROC_REF(/mob, emote), "scream"), INFUSING_TIME - 1 SECONDS)
addtimer(CALLBACK(src, PROC_REF(end_infuse)), INFUSING_TIME)
update_appearance()
/obj/machinery/dna_infuser/proc/end_infuse()
if(infuse_organ(occupant))
to_chat(occupant, span_danger("You feel yourself becoming more... [infusing_into.infusion_desc]?"))
infusing = FALSE
infuse_organ(occupant)
playsound(src, 'sound/machines/microwave/microwave-end.ogg', 100, FALSE)
infusing_into = null
QDEL_NULL(infusing_from)
playsound(src, 'sound/machines/microwave/microwave-end.ogg', 100, vary = FALSE)
toggle_open()
update_appearance()
//in the future, this should have more logic:
//- replace non-mutant organs before mutant ones
//- don't replace empty organ slots
/// 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) || !infusing_into)
//already filters humans from entering, but you know, just in case.
return
var/list/potential_new_organs = infusing_into.output_organs.Copy()
for(var/obj/item/organ/organ as anything in (target.internal_organs.Copy() + target.external_organs.Copy()))
if(organ.type in potential_new_organs)
//we already have this
potential_new_organs -= organ.type
if(potential_new_organs.len)
var/obj/item/organ/new_organ = pick(potential_new_organs)
new_organ = new new_organ()
if(istype(new_organ, /obj/item/organ/internal/brain))
// brains REALLY like ghosting people. we need special tricks to avoid that, namely removing the old brain with no_id_transfer
var/obj/item/organ/internal/brain/new_brain = new_organ
var/obj/item/organ/internal/brain/old_brain = target.getorganslot(ORGAN_SLOT_BRAIN)
if(old_brain)
old_brain.Remove(target, special = TRUE, no_id_transfer = TRUE)
qdel(old_brain)
new_brain.Insert(target, special = TRUE, drop_if_replaced = FALSE, no_id_transfer = TRUE)
else
new_organ.Insert(target, special = TRUE, drop_if_replaced = FALSE)
infusing_into = null
QDEL_NULL(infusing_from)
return FALSE
var/obj/item/organ/new_organ = pick_organ(target)
if(!new_organ)
return FALSE
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
/// 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:
/// 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)
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)
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))
continue // Old organ can be mutated!
else if(isexternalorgan(new_organ))
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
/obj/machinery/dna_infuser/update_icon_state()
//out of order
@@ -134,16 +160,13 @@
if(user)
balloon_alert(user, "close panel first!")
return
if(state_open)
close_machine()
return
else if(infusing)
if(user)
balloon_alert(user, "not while it's on!")
return
open_machine(drop = FALSE)
//we set drop to false to manually call it with an allowlist
dump_inventory_contents(list(occupant))
@@ -182,11 +205,9 @@
// if the machine is closed, already has a infusion target, or the target is not valid then no adding.
if(!state_open || !is_valid_infusion(target, user))
return
if(!user.transferItemToLoc(target, src))
to_chat(user, span_warning("[target] is stuck to your hand!"))
return
infusing_from = target
// mostly good for dead mobs like corpses (drag to add).
@@ -194,11 +215,23 @@
// if the machine is closed, already has a infusion target, or the target is not valid then no mouse drop.
if(!state_open || !is_valid_infusion(target, user))
return
infusing_from = target
infusing_from.forceMove(src)
if(iscarbon(target) && ishuman(target))
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: 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
// 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))
return FALSE
@@ -32,7 +32,7 @@
/obj/item/organ/internal/lungs/carp/Initialize(mapload)
. = ..()
AddElement(/datum/element/noticable_organ, "has odd neck gills.", BODY_ZONE_HEAD)
AddElement(/datum/element/noticable_organ, "neck has odd gills.", BODY_ZONE_HEAD)
AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/carp)
///occasionally sheds carp teeth, stronger melee (bite) attacks, but you can't cover your mouth anymore.
@@ -106,9 +106,12 @@
/obj/item/organ/internal/brain/carp/Initialize(mapload)
. = ..()
AddElement(/datum/element/noticable_organ, "seems unable to stay still.")
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 ..()
/obj/item/organ/internal/brain/carp/Insert(mob/living/carbon/brain_owner, special, drop_if_replaced, no_id_transfer)
. = ..()
cooldown_timer = addtimer(CALLBACK(src, PROC_REF(unsatisfied_nomad)), cooldown_time, TIMER_STOPPABLE|TIMER_OVERRIDE|TIMER_UNIQUE)
@@ -145,9 +148,12 @@
/obj/item/organ/internal/heart/carp/Initialize(mapload)
. = ..()
AddElement(/datum/element/noticable_organ, "skin has small patches of scales growing...")
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
@@ -34,7 +34,7 @@
/obj/item/organ/internal/eyes/night_vision/goliath/Initialize(mapload)
. = ..()
AddElement(/datum/element/noticable_organ, "eyes are blood red and stone like.", BODY_ZONE_HEAD)
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)
@@ -120,10 +120,8 @@
hitsound = 'sound/effects/bamf.ogg'
tool_behaviour = TOOL_MINING
toolspeed = 0.1
/// List of factions we deal bonus damage to
var/list/nemesis_factions = list("mining", "boss")
/// Amount of damage we deal to the above factions
var/faction_bonus_force = 80
/// Amount of damage we deal to the mining and boss factions.
var/mining_bonus_force = 80
/obj/item/goliath_infuser_hammer/Initialize(mapload)
. = ..()
@@ -134,28 +132,26 @@
user.changeNext_move(CLICK_CD_MELEE * 2) //hits slower but HARD
/obj/item/goliath_infuser_hammer/attack(mob/living/target, mob/living/carbon/human/user)
// Check for nemesis factions on the target.
if(!("mining" in target.faction) && !("boss" in target.faction))
// Target is not a nemesis, so attack normally.
return ..()
// Apply nemesis-specific effects.
nemesis_effects(user, target)
// Can't apply bonus force if target isn't "solid", or is occupying the same turf as the user.
if(!target.density || get_turf(target) == get_turf(user))
faction_bonus_force = 0
var/is_nemesis_faction = FALSE
for(var/found_faction in target.faction)
if(found_faction in nemesis_factions)
is_nemesis_faction = TRUE
force += faction_bonus_force
nemesis_effects(user, target)
break
return ..()
// Target is a nemesis, and we CAN apply bonus force.
force += mining_bonus_force
. = ..()
if(is_nemesis_faction)
force -= faction_bonus_force
force -= mining_bonus_force
/obj/item/goliath_infuser_hammer/proc/nemesis_effects(mob/living/user, mob/living/target)
if(istype(target, /mob/living/simple_animal/hostile/asteroid/elite))
return
///we obtain the relative direction from the bat itself to the target
var/relative_direction = get_cardinal_dir(src, target)
var/atom/throw_target = get_edge_target_turf(target, relative_direction)
if(!QDELETED(target))
var/whack_speed = (prob(60) ? 1 : 4)
target.throw_at(throw_target, rand(1, 2), whack_speed, user)
target.throw_at(get_edge_target_turf(target, get_cardinal_dir(src, target)), rand(1, 2), prob(60) ? 1 : 4, user)
/// goliath heart gives you the ability to survive ash storms.
/obj/item/organ/internal/heart/goliath
@@ -171,7 +167,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_COLORS
@@ -36,7 +36,7 @@
/obj/item/organ/internal/eyes/night_vision/rat/Initialize(mapload)
. = ..()
AddElement(/datum/element/noticable_organ, "has deep, shifty black pupils, surrounded by a sickening yellow sclera.", BODY_ZONE_PRECISE_EYES)
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)
@@ -54,9 +54,12 @@
/obj/item/organ/internal/stomach/rat/Initialize(mapload)
. = ..()
AddElement(/datum/element/noticable_organ, "salivates excessively.", BODY_ZONE_HEAD)
AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/rat)
/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)
. = ..()
if(!ishuman(reciever))
@@ -103,9 +106,12 @@
/obj/item/organ/internal/heart/rat/Initialize(mapload)
. = ..()
AddElement(/datum/element/noticable_organ, "hunches over unnaturally!")
AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/rat)
/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)
. = ..()
if(!ishuman(reciever))
@@ -139,7 +145,7 @@
/obj/item/organ/internal/tongue/rat/Initialize(mapload)
. = ..()
AddElement(/datum/element/noticable_organ, "has oddly shaped, yellowing teeth.", BODY_ZONE_HEAD)
AddElement(/datum/element/noticable_organ, "teeth are oddly shaped and yellowing", BODY_ZONE_HEAD)
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)