From 8fcac218c19303cd5a16530fde4ac266d3bb085a Mon Sep 17 00:00:00 2001 From: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Date: Mon, 23 Jan 2023 23:18:50 +0100 Subject: [PATCH] [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 :cl: A.C.M.O. fix: Fixed buggy organ filtering code in the DNA Infuser which stopped it from infusing organs. /:cl: Co-authored-by: Dani Glore Co-authored-by: GoldenAlpharex Co-authored-by: GoldenAlpharex <58045821+GoldenAlpharex@users.noreply.github.com> --- code/datums/elements/noticable_organ.dm | 10 +- .../game/machinery/dna_infuser/dna_infuser.dm | 115 +++++++++++------- .../dna_infuser/organ_sets/carp_organs.dm | 12 +- .../dna_infuser/organ_sets/goliath_organs.dm | 39 +++--- .../dna_infuser/organ_sets/rat_organs.dm | 14 ++- 5 files changed, 117 insertions(+), 73 deletions(-) diff --git a/code/datums/elements/noticable_organ.dm b/code/datums/elements/noticable_organ.dm index defee33dbfc..b059cd37471 100644 --- a/code/datums/elements/noticable_organ.dm +++ b/code/datums/elements/noticable_organ.dm @@ -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]") diff --git a/code/game/machinery/dna_infuser/dna_infuser.dm b/code/game/machinery/dna_infuser/dna_infuser.dm index 2320025f23f..03fec110ac2 100644 --- a/code/game/machinery/dna_infuser/dna_infuser.dm +++ b/code/game/machinery/dna_infuser/dna_infuser.dm @@ -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 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 048c0d6ff16..440213e4785 100644 --- a/code/game/machinery/dna_infuser/organ_sets/carp_organs.dm +++ b/code/game/machinery/dna_infuser/organ_sets/carp_organs.dm @@ -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 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 e2db3be8d00..6a084533094 100644 --- a/code/game/machinery/dna_infuser/organ_sets/goliath_organs.dm +++ b/code/game/machinery/dna_infuser/organ_sets/goliath_organs.dm @@ -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 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 0cf374c069c..c35f8fc1568 100644 --- a/code/game/machinery/dna_infuser/organ_sets/rat_organs.dm +++ b/code/game/machinery/dna_infuser/organ_sets/rat_organs.dm @@ -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)