From b5bd125e024218e97640d4aeb5e9089b3175e9e1 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Mon, 15 Jul 2024 03:01:18 -0500 Subject: [PATCH] Adds cloning pod for IC admin respawning --- maplestation.dme | 1 + .../code/game/machinery/cloning_pod.dm | 314 ++++++++++++++++++ .../icons/obj/machines/cloning.dmi | Bin 0 -> 4531 bytes 3 files changed, 315 insertions(+) create mode 100644 maplestation_modules/code/game/machinery/cloning_pod.dm create mode 100644 maplestation_modules/icons/obj/machines/cloning.dmi diff --git a/maplestation.dme b/maplestation.dme index 24ded6fe9583..0c309c583e5f 100644 --- a/maplestation.dme +++ b/maplestation.dme @@ -6135,6 +6135,7 @@ #include "maplestation_modules\code\datums\ruins\space.dm" #include "maplestation_modules\code\datums\votes\transfer_vote.dm" #include "maplestation_modules\code\game\area\space_station_13_areas.dm" +#include "maplestation_modules\code\game\machinery\cloning_pod.dm" #include "maplestation_modules\code\game\machinery\fax_machine.dm" #include "maplestation_modules\code\game\machinery\towel_rack.dm" #include "maplestation_modules\code\game\objects\unique_examine_items.dm" diff --git a/maplestation_modules/code/game/machinery/cloning_pod.dm b/maplestation_modules/code/game/machinery/cloning_pod.dm new file mode 100644 index 000000000000..007e4a528f05 --- /dev/null +++ b/maplestation_modules/code/game/machinery/cloning_pod.dm @@ -0,0 +1,314 @@ +/obj/machinery/clonepod + name = "cloning pod" + desc = "An electronically-lockable pod for growing organic tissue." + density = TRUE + icon = 'maplestation_modules/icons/obj/machines/cloning.dmi' + icon_state = "pod_0" + base_icon_state = "pod" + verb_say = "states" + // circuit = /obj/item/circuitboard/machine/clonepod + obj_flags = NO_DECONSTRUCTION + resistance_flags = INDESTRUCTIBLE + + var/obj/item/radio/radio + var/initial_damage = 180 + var/list/things_to_attach = list() + var/num_things = 0 + +/obj/machinery/clonepod/Initialize(mapload) + . = ..() + radio = new /obj/item/radio/headset/headset_cent() + +/obj/machinery/clonepod/Destroy() + QDEL_NULL(radio) + return ..() + +/obj/machinery/clonepod/dump_inventory_contents(list/subset) + QDEL_LIST(things_to_attach) + return ..() + +/obj/machinery/clonepod/attack_ghost(mob/user) + . = ..() + if(!user.client?.holder) + return + pick_subject(user) + +/obj/machinery/clonepod/attack_hand(mob/user) + . = ..() + if(.) + return + if(!user.client?.holder) + return + pick_subject(user) + return TRUE + +/obj/machinery/clonepod/examine(mob/user) + . = ..() + if(is_operational && isliving(occupant)) + . += span_green("Current clone cycle is [get_completion()]% complete.") + +/obj/machinery/clonepod/proc/get_completion() + var/mob/living/mob_occupant = occupant + if(istype(mob_occupant)) + var/datum/status_effect/genetic_damage/damage = mob_occupant.has_status_effect(/datum/status_effect/genetic_damage) + return round(100 * ((initial_damage - damage?.total_damage) / initial_damage), 0.01) + return 0 + +/obj/machinery/clonepod/update_icon_state() + . = ..() + icon_state = "[base_icon_state]_[occupant ? 1 : 0]" + +/obj/machinery/clonepod/return_air() + // We want to simulate the clone not being in contact with + // the atmosphere, so we'll put them in a constant pressure + // nitrogen. They don't need to breathe while cloning anyway. + var/static/datum/gas_mixture/immutable/cloner/im_mix + if(!im_mix) + im_mix = new + return im_mix + +/obj/machinery/clonepod/proc/pick_subject(mob/user) + if(occupant) + var/eject = tgui_alert(user, "Someone's already being cloned. \ + Early eject?", "Clone", list("Yes (Partial clone)", "Yes (Healthy clone)", "Cancel")) + if(eject == "Yes (Healthy clone)") + if(isliving(occupant)) + var/mob/living/mob_occupant = occupant + for(var/obj/item/thing as anything in things_to_attach) + readd_thing(mob_occupant, thing) + mob_occupant.remove_status_effect(/datum/status_effect/genetic_damage) + open_machine() + else if(eject == "Yes (Partial clone)") + open_machine() + return + var/client/picked = tgui_input_list(user, "Pick someone to clone", "Clone", list("Cancel") + GLOB.clients) + if(occupant || picked == "Cancel" || QDELETED(picked) || QDELETED(src)) + return + var/brainless = tgui_alert(user, "Make a brainless clone? \ + (IE, just make a cadaver to send to the station)", "Clone", list("Yes", "No", "Cancel")) + if(occupant || brainless == "Cancel" || !brainless || QDELETED(picked) || QDELETED(src)) + return + + var/datum/mind/found_mind + if(brainless != "Yes") + found_mind = picked?.mob?.mind + if(!found_mind) + var/datum/record/locked/record = find_record(picked.prefs.read_preference(/datum/preference/name/real_name), TRUE) + found_mind = record?.mind_ref?.resolve() + + if(!found_mind) + var/welp = tgui_alert(user, "We couldn't find this person's mind, do you want to continue? \ + We'll just shove their client in the clone (which'll make a NEW mind datum)", "Clone", list("Yes", "Cancel")) + if(welp != "Yes") + return + + else if(isliving(found_mind.current) && (found_mind.current.stat != DEAD)) + var/r_u_sure = tgui_alert(user, "You're cloning someone who is alive and kicking, \ + are you sure? We'll drag them out of their existing body (keeping the mind the same).", "Clone", list("Yes", "Cancel")) + if(r_u_sure != "Yes") + return + + if(occupant || QDELETED(picked) || QDELETED(src)) + return + + grow_clone(picked, found_mind, brainless == "Yes") + +//Start growing a human clone in the pod! +/obj/machinery/clonepod/proc/grow_clone(client/cloning, datum/mind/cloning_mind, brainless = FALSE) + var/datum/preferences/from_prefs = cloning?.prefs + if(!from_prefs) + return + if(occupant) + return + + var/mob/living/carbon/human/clone = new(src) + close_machine(clone) + + from_prefs.apply_prefs_to(clone) + SSquirks.AssignQuirks(clone, cloning) + clone.set_cloned_appearance() + + if(brainless) + var/obj/item/organ/brainy = clone.get_organ_by_type(/obj/item/organ/internal/brain) + brainy.Remove(clone) + qdel(brainy) + + maim_clone(clone) + + if(!brainless) + clone.apply_status_effect(/datum/status_effect/fresh_clone) + if(cloning_mind) + cloning_mind.transfer_to(clone) + clone.notify_revival("You are being cloned by Central Command!", source = src) + else + window_flash(cloning) + SEND_SOUND(cloning, sound('sound/effects/genetics.ogg')) + to_chat(cloning, span_ghostalert("You are being cloned by Central Command!")) + clone.key = cloning?.key + + say("Cloning cycle of [clone] initiated.") + radio.talk_into(src, "Cloning cycle of [clone] initiated.", RADIO_CHANNEL_CENTCOM) + +/obj/machinery/clonepod/process() + if(!isliving(occupant)) + return + var/mob/living/mob_occupant = occupant + var/datum/status_effect/genetic_damage/damage = mob_occupant.has_status_effect(/datum/status_effect/genetic_damage) + if(!damage) + if(length(things_to_attach)) + readd_thing(mob_occupant, pick_n_take(things_to_attach)) + return + + if(mob_occupant.client) + mob_occupant.flash_act(intensity = INFINITY, override_blindness_check = TRUE, visual = TRUE) + to_chat(mob_occupant, span_boldnotice("You are ejected from [src] in a bright flash.")) + to_chat(mob_occupant, span_smallnoticeital("You feel like a new person.")) + open_machine() + say("Cloning process complete.") + radio.talk_into(src, "Cloning process complete.", RADIO_CHANNEL_CENTCOM) + return + + var/progress = initial_damage - damage.total_damage + var/milestone = initial_damage / num_things + var/installed = num_things - length(things_to_attach) + + if((progress / milestone) >= installed) + readd_thing(occupant, pick_n_take(things_to_attach)) + +/obj/machinery/clonepod/set_occupant(atom/movable/new_occupant) + var/mob/living/old_occupant_mob = occupant + . = ..() + if(isliving(old_occupant_mob)) + old_occupant_mob.remove_traits(list( + TRAIT_DEAF, + TRAIT_EMOTEMUTE, + TRAIT_KNOCKEDOUT, + TRAIT_MUTE, + TRAIT_NOBREATH, + TRAIT_NOCRITDAMAGE, + TRAIT_STABLEHEART, + TRAIT_STABLELIVER, + ), REF(src)) + num_things = 0 + old_occupant_mob.remove_status_effect(/datum/status_effect/fresh_clone) + old_occupant_mob.unset_pain_mod("cloning") + old_occupant_mob.Unconscious(8 SECONDS) + if(!old_occupant_mob.client && old_occupant_mob.stat != DEAD) + old_occupant_mob.notify_revival("You have been cloned by Central Command!", source = src) + + if(isliving(new_occupant)) + var/mob/living/new_occupant_mob = new_occupant + new_occupant_mob.add_traits(list( + TRAIT_DEAF, + TRAIT_EMOTEMUTE, + TRAIT_KNOCKEDOUT, + TRAIT_MUTE, + TRAIT_NOBREATH, + TRAIT_NOCRITDAMAGE, + TRAIT_STABLEHEART, + TRAIT_STABLELIVER, + ), REF(src)) + new_occupant_mob.set_pain_mod("cloning", 0) + update_appearance() + +/obj/machinery/clonepod/relaymove(mob/user) + container_resist_act(user) + +/obj/machinery/clonepod/container_resist_act(mob/living/user) + if(user.stat == CONSCIOUS) + open_machine() + +/obj/machinery/clonepod/proc/maim_clone(mob/living/carbon/human/clone) + for(var/atom/existing as anything in things_to_attach) + qdel(existing) + things_to_attach.Cut() + clone.apply_status_effect(/datum/status_effect/genetic_damage/cloning, initial_damage) + + // remove organs before removing limbs and taking things with them + for(var/obj/item/organ/internal/organ in clone.organs) + if(organ.organ_flags & (ORGAN_VITAL|ORGAN_UNREMOVABLE)) + continue + organ.organ_flags |= ORGAN_FROZEN + organ.Remove(clone, special = FALSE) // not special so we apply stuff like heart attacks and blindness + organ.forceMove(src) + things_to_attach += organ + + if(!HAS_TRAIT(clone, TRAIT_NODISMEMBER)) + for(var/obj/item/bodypart/part as anything in clone.bodyparts) + if(part.body_zone == BODY_ZONE_CHEST || part.body_zone == BODY_ZONE_HEAD) + continue + part.drop_limb() + part.forceMove(src) + things_to_attach += part + + num_things = length(things_to_attach) + +/obj/machinery/clonepod/proc/readd_thing(mob/living/carbon/human/clone, obj/item/thing) + things_to_attach -= thing + if(isorgan(thing)) + var/obj/item/organ/internal/organ = thing + organ.organ_flags &= ~ORGAN_FROZEN + organ.Insert(clone) + if(istype(thing, /obj/item/organ/internal/heart)) + to_chat(clone, span_smallnoticeital("You hear a faint thumping...")) + var/obj/item/organ/internal/heart/heart = organ + heart.Restart() + SEND_SOUND(clone, sound('sound/health/slowbeat.ogg', channel = CHANNEL_HEARTBEAT, volume = 33)) + addtimer(CALLBACK(clone, TYPE_PROC_REF(/mob, stop_sound_channel), CHANNEL_HEARTBEAT), 4.75 SECONDS) + if(istype(thing, /obj/item/organ/internal/lungs)) + to_chat(clone, span_smallnoticeital("You feel a sudden urge to breathe...")) + if(istype(thing, /obj/item/organ/internal/ears)) + to_chat(clone, span_smallnoticeital("You hear a faint buzzing...")) + if(istype(thing, /obj/item/organ/internal/eyes)) + to_chat(clone, span_smallnoticeital("You see a faint light...")) + return + + if(isbodypart(thing)) + var/obj/item/bodypart/part = thing + part.try_attach_limb(clone) + if(istype(part, /obj/item/bodypart/arm/left)) + to_chat(clone, span_smallnoticeital("You instinctively flex your fingers...")) + if(istype(part, /obj/item/bodypart/arm/right)) + to_chat(clone, span_smallnoticeital("You reach out to touch something...")) + if(istype(part, /obj/item/bodypart/leg/left)) + to_chat(clone, span_smallnoticeital("You stretch your leg[clone.num_legs >= 2 ? "s" : ""]...")) + if(istype(part, /obj/item/bodypart/leg/right)) + to_chat(clone, span_smallnoticeital("You kick your leg[clone.num_legs >= 2 ? "s" : ""] out...")) + return + +// Cloning pod gas mix +/datum/gas_mixture/immutable/cloner + initial_temperature = T20C + +/datum/gas_mixture/immutable/cloner/garbage_collect() + . = ..() + ASSERT_GAS(/datum/gas/nitrogen, src) + gases[/datum/gas/nitrogen][MOLES] = MOLES_O2STANDARD + MOLES_N2STANDARD + +/datum/gas_mixture/immutable/cloner/heat_capacity() + return (MOLES_O2STANDARD + MOLES_N2STANDARD) * 20 //specific heat of nitrogen is 20 + +// Cloning damage (RIP clone damage) +/datum/status_effect/genetic_damage/cloning + remove_per_second = 1 + +// Used to give people a message when they enter their clone +/datum/status_effect/fresh_clone + id = "fresh_clone" + alert_type = null + duration = -1 + tick_interval = -1 + +/datum/status_effect/fresh_clone/on_apply() + RegisterSignal(owner, COMSIG_MOB_CLIENT_LOGIN, PROC_REF(on_login)) + return TRUE + +/datum/status_effect/fresh_clone/on_remove() + UnregisterSignal(owner, COMSIG_MOB_CLIENT_LOGIN) + +/datum/status_effect/fresh_clone/proc/on_login(datum/source, client/incoming) + SIGNAL_HANDLER + if(istype(owner.loc, /obj/machinery/clonepod)) + to_chat(incoming, span_boldnotice("Consciousness slowly creeps over you as your body regenerates.")) + to_chat(incoming, span_smallnoticeital("So this is what cloning feels like?")) + qdel(src) diff --git a/maplestation_modules/icons/obj/machines/cloning.dmi b/maplestation_modules/icons/obj/machines/cloning.dmi new file mode 100644 index 0000000000000000000000000000000000000000..a3ab6665c161cae009bfd0196b7cf395fab5c935 GIT binary patch literal 4531 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 R3nKsk002ovPDHLkV1hwm;<*3- literal 0 HcmV?d00001