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)