diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index d0fcb3bcbe..f35a6eaa15 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -1399,3 +1399,20 @@ GLOBAL_PROTECT(valid_HTTPSGet) /proc/pass() return + +/proc/get_mob_or_brainmob(occupant) + var/mob/living/mob_occupant + + if(isliving(occupant)) + mob_occupant = occupant + + else if(isbodypart(occupant)) + var/obj/item/bodypart/head/head = occupant + + mob_occupant = head.brainmob + + else if(isorgan(occupant)) + var/obj/item/organ/brain/brain = occupant + mob_occupant = brain.brainmob + + return mob_occupant diff --git a/code/game/machinery/cloning.dm b/code/game/machinery/cloning.dm index b95dbff56d..ec87cf6ba9 100644 --- a/code/game/machinery/cloning.dm +++ b/code/game/machinery/cloning.dm @@ -1,499 +1,504 @@ -//Cloning revival method. -//The pod handles the actual cloning while the computer manages the clone profiles - -//Potential replacement for genetics revives or something I dunno (?) - -#define CLONE_INITIAL_DAMAGE 190 //Clones in clonepods start with 190 cloneloss damage and 190 brainloss damage, thats just logical -#define MINIMUM_HEAL_LEVEL 40 - -#define SPEAK(message) radio.talk_into(src, message, radio_channel, get_spans(), get_default_language()) - -/obj/machinery/clonepod - anchored = 1 - name = "cloning pod" - desc = "An electronically-lockable pod for growing organic tissue." - density = 1 - icon = 'icons/obj/cloning.dmi' - icon_state = "pod_0" - req_access = list(GLOB.access_cloning) //For premature unlocking. - verb_say = "states" - var/heal_level //The clone is released once its health reaches this level. - var/obj/machinery/computer/cloning/connected = null //So we remember the connected clone machine. - var/mess = FALSE //Need to clean out it if it's full of exploded clone. - var/attempting = FALSE //One clone attempt at a time thanks - var/speed_coeff - var/efficiency - - var/datum/mind/clonemind - var/grab_ghost_when = CLONER_MATURE_CLONE - - var/obj/item/device/radio/radio - var/radio_key = /obj/item/device/encryptionkey/headset_med - var/radio_channel = "Medical" - - var/obj/effect/countdown/clonepod/countdown - - var/list/unattached_flesh - var/flesh_number = 0 - - // The "brine" is the reagents that are automatically added in small - // amounts to the occupant. - var/static/list/brine_types = list( - "salbutamol", // anti-oxyloss - "bicaridine", // NOBREATHE species take brute in crit - "corazone", // prevents cardiac arrest damage - "mimesbane") // stops them gasping from lack of air. - -/obj/machinery/clonepod/New() - ..() - var/obj/item/weapon/circuitboard/machine/B = new /obj/item/weapon/circuitboard/machine/clonepod(null) - B.apply_default_parts(src) - - countdown = new(src) - - radio = new(src) - radio.keyslot = new radio_key - radio.subspace_transmission = 1 - radio.canhear_range = 0 - radio.recalculateChannels() - -/obj/machinery/clonepod/Destroy() - go_out() - qdel(radio) - radio = null - qdel(countdown) - countdown = null - if(connected) - connected.DetachCloner(src) - for(var/i in unattached_flesh) - qdel(i) - LAZYCLEARLIST(unattached_flesh) - unattached_flesh = null - . = ..() - -/obj/machinery/clonepod/RefreshParts() - speed_coeff = 0 - efficiency = 0 - for(var/obj/item/weapon/stock_parts/scanning_module/S in component_parts) - efficiency += S.rating - for(var/obj/item/weapon/stock_parts/manipulator/P in component_parts) - speed_coeff += P.rating - heal_level = (efficiency * 15) + 10 - if(heal_level < MINIMUM_HEAL_LEVEL) - heal_level = MINIMUM_HEAL_LEVEL - if(heal_level > 100) - heal_level = 100 - -/obj/item/weapon/circuitboard/machine/clonepod - name = "Clone Pod (Machine Board)" - build_path = /obj/machinery/clonepod - origin_tech = "programming=2;biotech=2" - req_components = list( - /obj/item/stack/cable_coil = 2, - /obj/item/weapon/stock_parts/scanning_module = 2, - /obj/item/weapon/stock_parts/manipulator = 2, - /obj/item/stack/sheet/glass = 1) - -//The return of data disks?? Just for transferring between genetics machine/cloning machine. -//TO-DO: Make the genetics machine accept them. -/obj/item/weapon/disk/data - name = "cloning data disk" - icon_state = "datadisk0" //Gosh I hope syndies don't mistake them for the nuke disk. - var/list/fields = list() - var/read_only = 0 //Well,it's still a floppy disk - -//Disk stuff. -/obj/item/weapon/disk/data/New() - ..() - icon_state = "datadisk[rand(0,6)]" - add_overlay("datadisk_gene") - -/obj/item/weapon/disk/data/attack_self(mob/user) - read_only = !read_only - to_chat(user, "You flip the write-protect tab to [read_only ? "protected" : "unprotected"].") - -/obj/item/weapon/disk/data/examine(mob/user) - ..() - to_chat(user, "The write-protect tab is set to [read_only ? "protected" : "unprotected"].") - - -//Clonepod - -/obj/machinery/clonepod/examine(mob/user) - ..() - var/mob/living/mob_occupant = occupant - if(mess) - to_chat(user, "It's filled with blood and viscera. You swear you can see it moving...") - if(is_operational() && mob_occupant) - if(mob_occupant.stat != DEAD) - to_chat(user, "Current clone cycle is [round(get_completion())]% complete.") - -/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'll breathe through the chemicals we pump into them. - var/static/datum/gas_mixture/immutable/cloner/GM //global so that there's only one instance made for all cloning pods - if(!GM) - GM = new - return GM - -/obj/machinery/clonepod/proc/get_completion() - . = FALSE - var/mob/living/mob_occupant = occupant - if(mob_occupant) - . = (100 * ((mob_occupant.health + 100) / (heal_level + 100))) - -/obj/machinery/clonepod/attack_ai(mob/user) - return examine(user) - -//Start growing a human clone in the pod! -/obj/machinery/clonepod/proc/growclone(ckey, clonename, ui, se, mindref, datum/species/mrace, list/features, factions) - if(panel_open) - return FALSE - if(mess || attempting) - return FALSE - clonemind = locate(mindref) - if(!istype(clonemind)) //not a mind - return FALSE - if( clonemind.current && clonemind.current.stat != DEAD ) //mind is associated with a non-dead body - return FALSE - if(clonemind.active) //somebody is using that mind - if( ckey(clonemind.key)!=ckey ) - return FALSE - else - // get_ghost() will fail if they're unable to reenter their body - var/mob/dead/observer/G = clonemind.get_ghost() - if(!G) - return FALSE - if(clonemind.damnation_type) //Can't clone the damned. - INVOKE_ASYNC(src, .proc/horrifyingsound) - mess = TRUE - icon_state = "pod_g" - update_icon() - return FALSE - - attempting = TRUE //One at a time!! - countdown.start() - - var/mob/living/carbon/human/H = new /mob/living/carbon/human(src) - - if(clonemind.changeling) - var/obj/item/organ/brain/B = H.getorganslot("brain") - B.vital = FALSE - B.decoy_override = TRUE - - H.hardset_dna(ui, se, H.real_name, null, mrace, features) - - if(efficiency > 2) - var/list/unclean_mutations = (GLOB.not_good_mutations|GLOB.bad_mutations) - H.dna.remove_mutation_group(unclean_mutations) - if(efficiency > 5 && prob(20)) - H.randmutvg() - if(efficiency < 3 && prob(50)) - var/mob/M = H.randmutb() - if(ismob(M)) - H = M - - H.silent = 20 //Prevents an extreme edge case where clones could speak if they said something at exactly the right moment. - occupant = H - - if(!clonename) //to prevent null names - clonename = "clone ([rand(0,999)])" - H.real_name = clonename - - icon_state = "pod_1" - //Get the clone body ready - maim_clone(H) - check_brine() // put in chemicals NOW to stop death via cardiac arrest - H.Paralyse(4) - - clonemind.transfer_to(H) - - if(grab_ghost_when == CLONER_FRESH_CLONE) - H.grab_ghost() - to_chat(H, "Consciousness slowly creeps over you as your body regenerates.
So this is what cloning feels like?
") - - if(grab_ghost_when == CLONER_MATURE_CLONE) - H.ghostize(TRUE) //Only does anything if they were still in their old body and not already a ghost - to_chat(H.get_ghost(TRUE), "Your body is beginning to regenerate in a cloning pod. You will become conscious when it is complete.") - - if(H) - H.faction |= factions - - H.set_cloned_appearance() - - H.suiciding = FALSE - attempting = FALSE - return TRUE - -//Grow clones to maturity then kick them out. FREELOADERS -/obj/machinery/clonepod/process() - var/mob/living/mob_occupant = occupant - - if(!is_operational()) //Autoeject if power is lost - if(mob_occupant) - go_out() - connected_message("Clone Ejected: Loss of power.") - - else if(mob_occupant && (mob_occupant.loc == src)) - if((mob_occupant.stat == DEAD) || (mob_occupant.suiciding) || mob_occupant.hellbound) //Autoeject corpses and suiciding dudes. - connected_message("Clone Rejected: Deceased.") - SPEAK("The cloning of [mob_occupant.real_name] has been \ - aborted due to unrecoverable tissue failure.") - go_out() - - else if(mob_occupant.cloneloss > (100 - heal_level)) - mob_occupant.Paralyse(4) - - //Slowly get that clone healed and finished. - mob_occupant.adjustCloneLoss(-((speed_coeff/2) * config.damage_multiplier)) - var/progress = CLONE_INITIAL_DAMAGE - mob_occupant.getCloneLoss() - // To avoid the default cloner making incomplete clones - progress += (100 - MINIMUM_HEAL_LEVEL) - var/milestone = CLONE_INITIAL_DAMAGE / flesh_number - var/installed = flesh_number - unattached_flesh.len - - if((progress / milestone) >= installed) - // attach some flesh - var/obj/item/I = pick_n_take(unattached_flesh) - if(isorgan(I)) - var/obj/item/organ/O = I - O.Insert(mob_occupant) - else if(isbodypart(I)) - var/obj/item/bodypart/BP = I - BP.attach_limb(mob_occupant) - - //Premature clones may have brain damage. - mob_occupant.adjustBrainLoss(-((speed_coeff/2) * config.damage_multiplier)) - - check_brine() - - use_power(7500) //This might need tweaking. - - else if((mob_occupant.cloneloss <= (100 - heal_level))) - connected_message("Cloning Process Complete.") - SPEAK("The cloning cycle of [mob_occupant.real_name] is complete.") - go_out() - - else if (!mob_occupant || mob_occupant.loc != src) - occupant = null - if (!mess && !panel_open) - icon_state = "pod_0" - use_power(200) - -//Let's unlock this early I guess. Might be too early, needs tweaking. -/obj/machinery/clonepod/attackby(obj/item/weapon/W, mob/user, params) - if(!(occupant || mess)) - if(default_deconstruction_screwdriver(user, "[icon_state]_maintenance", "[initial(icon_state)]",W)) - return - - if(exchange_parts(user, W)) - return - - if(default_deconstruction_crowbar(W)) - return - - if(istype(W,/obj/item/device/multitool)) - var/obj/item/device/multitool/P = W - - if(istype(P.buffer, /obj/machinery/computer/cloning)) - if(get_area(P.buffer) != get_area(src)) - to_chat(user, "-% Cannot link machines across power zones. Buffer cleared %-") - P.buffer = null - return - to_chat(user, "-% Successfully linked [P.buffer] with [src] %-") - var/obj/machinery/computer/cloning/comp = P.buffer - if(connected) - connected.DetachCloner(src) - comp.AttachCloner(src) - else - P.buffer = src - to_chat(user, "-% Successfully stored \ref[P.buffer] [P.buffer.name] in buffer %-") - return - - var/mob/living/mob_occupant = occupant - if(W.GetID()) - if(!check_access(W)) - to_chat(user, "Access Denied.") - return - if(!(mob_occupant || mess)) - to_chat(user, "Error: Pod has no occupant.") - return - else - connected_message("Authorized Ejection") - - SPEAK("An authorized ejection of [clonemind.name] has occurred.") - - to_chat(user, "You force an emergency ejection. ") - go_out() - else - return ..() - -/obj/machinery/clonepod/emag_act(mob/user) - if(!occupant) - return - to_chat(user, "You corrupt the genetic compiler.") - malfunction() - -//Put messages in the connected computer's temp var for display. -/obj/machinery/clonepod/proc/connected_message(message) - if ((isnull(connected)) || (!istype(connected, /obj/machinery/computer/cloning))) - return FALSE - if (!message) - return FALSE - - connected.temp = message - connected.updateUsrDialog() - return TRUE - -/obj/machinery/clonepod/proc/go_out() - countdown.stop() - var/mob/living/mob_occupant = occupant - - if(mess) //Clean that mess and dump those gibs! - mess = FALSE - new /obj/effect/gibspawner/generic(loc) - audible_message("You hear a splat.") - icon_state = "pod_0" - return - - if(!mob_occupant) - return - - - if(grab_ghost_when == CLONER_MATURE_CLONE) - mob_occupant.grab_ghost() - to_chat(occupant, "There is a bright flash!
You feel like a new being.
") - mob_occupant.flash_act() - - var/turf/T = get_turf(src) - occupant.forceMove(T) - icon_state = "pod_0" - mob_occupant.domutcheck(1) //Waiting until they're out before possible monkeyizing. The 1 argument forces powers to manifest. - - occupant = null - -/obj/machinery/clonepod/proc/malfunction() - var/mob/living/mob_occupant = occupant - if(mob_occupant) - connected_message("Critical Error!") - SPEAK("Critical error! Please contact a Thinktronic Systems \ - technician, as your warranty may be affected.") - mess = TRUE - - for(var/obj/item/O in unattached_flesh) - qdel(O) - - icon_state = "pod_g" - if(mob_occupant.mind != clonemind) - clonemind.transfer_to(mob_occupant) - mob_occupant.grab_ghost() // We really just want to make you suffer. - flash_color(mob_occupant, flash_color="#960000", flash_time=100) - to_chat(mob_occupant, "Agony blazes across your consciousness as your body is torn apart.
Is this what dying is like? Yes it is.
") - playsound(src.loc, 'sound/machines/warning-buzzer.ogg', 50, 0) - mob_occupant << sound('sound/hallucinations/veryfar_noise.ogg',0,1,50) - QDEL_IN(mob_occupant, 40) - -/obj/machinery/clonepod/relaymove(mob/user) - if(user.stat == CONSCIOUS) - go_out() - -/obj/machinery/clonepod/emp_act(severity) - - var/mob/living/mob_occupant = occupant - if(mob_occupant && prob(100/(severity*efficiency))) - connected_message(Gibberish("EMP-caused Accidental Ejection", 0)) - SPEAK(Gibberish("Exposure to electromagnetic fields has caused the ejection of [mob_occupant.real_name] prematurely." ,0)) - - go_out() - ..() - -/obj/machinery/clonepod/ex_act(severity, target) - ..() - if(!QDELETED(src)) - go_out() - -/obj/machinery/clonepod/handle_atom_del(atom/A) - if(A == occupant) - occupant = null - countdown.stop() - -/obj/machinery/clonepod/proc/horrifyingsound() - for(var/i in 1 to 5) - playsound(loc,pick('sound/hallucinations/growl1.ogg','sound/hallucinations/growl2.ogg','sound/hallucinations/growl3.ogg'), 100, rand(0.95,1.05)) - sleep(1) - sleep(10) - playsound(loc,'sound/hallucinations/wail.ogg',100,1) - -/obj/machinery/clonepod/deconstruct(disassembled = TRUE) - if(occupant) - go_out() - ..() - -/obj/machinery/clonepod/proc/maim_clone(mob/living/carbon/human/H) - if(!unattached_flesh) - unattached_flesh = list() - else - for(var/fl in unattached_flesh) - qdel(fl) - unattached_flesh.Cut() - - H.setCloneLoss(CLONE_INITIAL_DAMAGE) //Yeah, clones start with very low health, not with random, because why would they start with random health - H.setBrainLoss(CLONE_INITIAL_DAMAGE) - // In addition to being cellularly damaged and having barely any - // brain function, they also have no limbs or internal organs. - var/static/list/zones = list("r_arm", "l_arm", "r_leg", "l_leg") - for(var/zone in zones) - var/obj/item/bodypart/BP = H.get_bodypart(zone) - BP.drop_limb() - BP.forceMove(src) - unattached_flesh += BP - - for(var/o in H.internal_organs) - var/obj/item/organ/organ = o - if(!istype(organ) || organ.vital) - continue - organ.Remove(H, special=TRUE) - organ.forceMove(src) - unattached_flesh += organ - - flesh_number = unattached_flesh.len - -/obj/machinery/clonepod/proc/check_brine() - // Clones are in a pickled bath of mild chemicals, keeping - // them alive, despite their lack of internal organs - for(var/bt in brine_types) - if(occupant.reagents.get_reagent_amount(bt) < 1) - occupant.reagents.add_reagent(bt, 1) - -/* - * Manual -- A big ol' manual. - */ - -/obj/item/weapon/paper/Cloning - name = "paper - 'H-87 Cloning Apparatus Manual" - info = {"

Getting Started

- Congratulations, your station has purchased the H-87 industrial cloning device!
- Using the H-87 is almost as simple as brain surgery! Simply insert the target humanoid into the scanning chamber and select the scan option to create a new profile!
- That's all there is to it!
- Notice, cloning system cannot scan inorganic life or small primates. Scan may fail if subject has suffered extreme brain damage.
-

Clone profiles may be viewed through the profiles menu. Scanning implants a complementary HEALTH MONITOR IMPLANT into the subject, which may be viewed from each profile. - Profile Deletion has been restricted to \[Station Head\] level access.

-

Cloning from a profile

- Cloning is as simple as pressing the CLONE option at the bottom of the desired profile.
- Per your company's EMPLOYEE PRIVACY RIGHTS agreement, the H-87 has been blocked from cloning crewmembers while they are still alive.
-
-

The provided CLONEPOD SYSTEM will produce the desired clone. Standard clone maturation times (With SPEEDCLONE technology) are roughly 90 seconds. - The cloning pod may be unlocked early with any \[Medical Researcher\] ID after initial maturation is complete.


- Please note that resulting clones may have a small DEVELOPMENTAL DEFECT as a result of genetic drift.
-

Profile Management

-

The H-87 (as well as your station's standard genetics machine) can accept STANDARD DATA DISKETTES. - These diskettes are used to transfer genetic information between machines and profiles. - A load/save dialog will become available in each profile if a disk is inserted.


- A good diskette is a great way to counter aforementioned genetic drift!
-
- This technology produced under license from Thinktronic Systems, LTD."} - -#undef CLONE_INITIAL_DAMAGE -#undef SPEAK -#undef MINIMUM_HEAL_LEVEL +//Cloning revival method. +//The pod handles the actual cloning while the computer manages the clone profiles + +//Potential replacement for genetics revives or something I dunno (?) + +#define CLONE_INITIAL_DAMAGE 190 //Clones in clonepods start with 190 cloneloss damage and 190 brainloss damage, thats just logical +#define MINIMUM_HEAL_LEVEL 40 + +#define SPEAK(message) radio.talk_into(src, message, radio_channel, get_spans(), get_default_language()) + +/obj/machinery/clonepod + anchored = 1 + name = "cloning pod" + desc = "An electronically-lockable pod for growing organic tissue." + density = 1 + icon = 'icons/obj/cloning.dmi' + icon_state = "pod_0" + req_access = list(GLOB.access_cloning) //For premature unlocking. + verb_say = "states" + var/heal_level //The clone is released once its health reaches this level. + var/obj/machinery/computer/cloning/connected = null //So we remember the connected clone machine. + var/mess = FALSE //Need to clean out it if it's full of exploded clone. + var/attempting = FALSE //One clone attempt at a time thanks + var/speed_coeff + var/efficiency + + var/datum/mind/clonemind + var/grab_ghost_when = CLONER_MATURE_CLONE + + var/obj/item/device/radio/radio + var/radio_key = /obj/item/device/encryptionkey/headset_med + var/radio_channel = "Medical" + + var/obj/effect/countdown/clonepod/countdown + + var/list/unattached_flesh + var/flesh_number = 0 + + // The "brine" is the reagents that are automatically added in small + // amounts to the occupant. + var/static/list/brine_types = list( + "salbutamol", // anti-oxyloss + "bicaridine", // NOBREATHE species take brute in crit + "corazone", // prevents cardiac arrest damage + "mimesbane") // stops them gasping from lack of air. + +/obj/machinery/clonepod/New() + ..() + var/obj/item/weapon/circuitboard/machine/B = new /obj/item/weapon/circuitboard/machine/clonepod(null) + B.apply_default_parts(src) + + countdown = new(src) + + radio = new(src) + radio.keyslot = new radio_key + radio.subspace_transmission = 1 + radio.canhear_range = 0 + radio.recalculateChannels() + +/obj/machinery/clonepod/Destroy() + go_out() + qdel(radio) + radio = null + qdel(countdown) + countdown = null + if(connected) + connected.DetachCloner(src) + for(var/i in unattached_flesh) + qdel(i) + LAZYCLEARLIST(unattached_flesh) + unattached_flesh = null + . = ..() + +/obj/machinery/clonepod/RefreshParts() + speed_coeff = 0 + efficiency = 0 + for(var/obj/item/weapon/stock_parts/scanning_module/S in component_parts) + efficiency += S.rating + for(var/obj/item/weapon/stock_parts/manipulator/P in component_parts) + speed_coeff += P.rating + heal_level = (efficiency * 15) + 10 + if(heal_level < MINIMUM_HEAL_LEVEL) + heal_level = MINIMUM_HEAL_LEVEL + if(heal_level > 100) + heal_level = 100 + +/obj/item/weapon/circuitboard/machine/clonepod + name = "Clone Pod (Machine Board)" + build_path = /obj/machinery/clonepod + origin_tech = "programming=2;biotech=2" + req_components = list( + /obj/item/stack/cable_coil = 2, + /obj/item/weapon/stock_parts/scanning_module = 2, + /obj/item/weapon/stock_parts/manipulator = 2, + /obj/item/stack/sheet/glass = 1) + +//The return of data disks?? Just for transferring between genetics machine/cloning machine. +//TO-DO: Make the genetics machine accept them. +/obj/item/weapon/disk/data + name = "cloning data disk" + icon_state = "datadisk0" //Gosh I hope syndies don't mistake them for the nuke disk. + var/list/fields = list() + var/read_only = 0 //Well,it's still a floppy disk + +//Disk stuff. +/obj/item/weapon/disk/data/New() + ..() + icon_state = "datadisk[rand(0,6)]" + add_overlay("datadisk_gene") + +/obj/item/weapon/disk/data/attack_self(mob/user) + read_only = !read_only + to_chat(user, "You flip the write-protect tab to [read_only ? "protected" : "unprotected"].") + +/obj/item/weapon/disk/data/examine(mob/user) + ..() + to_chat(user, "The write-protect tab is set to [read_only ? "protected" : "unprotected"].") + + +//Clonepod + +/obj/machinery/clonepod/examine(mob/user) + ..() + var/mob/living/mob_occupant = occupant + if(mess) + to_chat(user, "It's filled with blood and viscera. You swear you can see it moving...") + if(is_operational() && mob_occupant) + if(mob_occupant.stat != DEAD) + to_chat(user, "Current clone cycle is [round(get_completion())]% complete.") + +/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'll breathe through the chemicals we pump into them. + var/static/datum/gas_mixture/immutable/cloner/GM //global so that there's only one instance made for all cloning pods + if(!GM) + GM = new + return GM + +/obj/machinery/clonepod/proc/get_completion() + . = FALSE + var/mob/living/mob_occupant = occupant + if(mob_occupant) + . = (100 * ((mob_occupant.health + 100) / (heal_level + 100))) + +/obj/machinery/clonepod/attack_ai(mob/user) + return examine(user) + +//Start growing a human clone in the pod! +/obj/machinery/clonepod/proc/growclone(ckey, clonename, ui, se, mindref, datum/species/mrace, list/features, factions) + if(panel_open) + return FALSE + if(mess || attempting) + return FALSE + clonemind = locate(mindref) + if(!istype(clonemind)) //not a mind + return FALSE + if( clonemind.current && clonemind.current.stat != DEAD ) //mind is associated with a non-dead body + return FALSE + if(clonemind.active) //somebody is using that mind + if( ckey(clonemind.key)!=ckey ) + return FALSE + else + // get_ghost() will fail if they're unable to reenter their body + var/mob/dead/observer/G = clonemind.get_ghost() + if(!G) + return FALSE + if(clonemind.damnation_type) //Can't clone the damned. + INVOKE_ASYNC(src, .proc/horrifyingsound) + mess = TRUE + icon_state = "pod_g" + update_icon() + return FALSE + + attempting = TRUE //One at a time!! + countdown.start() + + var/mob/living/carbon/human/H = new /mob/living/carbon/human(src) + + if(clonemind.changeling) + var/obj/item/organ/brain/B = H.getorganslot("brain") + B.vital = FALSE + B.decoy_override = TRUE + + H.hardset_dna(ui, se, H.real_name, null, mrace, features) + + if(efficiency > 2) + var/list/unclean_mutations = (GLOB.not_good_mutations|GLOB.bad_mutations) + H.dna.remove_mutation_group(unclean_mutations) + if(efficiency > 5 && prob(20)) + H.randmutvg() + if(efficiency < 3 && prob(50)) + var/mob/M = H.randmutb() + if(ismob(M)) + H = M + + H.silent = 20 //Prevents an extreme edge case where clones could speak if they said something at exactly the right moment. + occupant = H + + if(!clonename) //to prevent null names + clonename = "clone ([rand(0,999)])" + H.real_name = clonename + + icon_state = "pod_1" + //Get the clone body ready + maim_clone(H) + check_brine() // put in chemicals NOW to stop death via cardiac arrest + H.Paralyse(4) + + clonemind.transfer_to(H) + + if(grab_ghost_when == CLONER_FRESH_CLONE) + H.grab_ghost() + to_chat(H, "Consciousness slowly creeps over you as your body regenerates.
So this is what cloning feels like?
") + + if(grab_ghost_when == CLONER_MATURE_CLONE) + H.ghostize(TRUE) //Only does anything if they were still in their old body and not already a ghost + to_chat(H.get_ghost(TRUE), "Your body is beginning to regenerate in a cloning pod. You will become conscious when it is complete.") + + if(H) + H.faction |= factions + + H.set_cloned_appearance() + + H.suiciding = FALSE + attempting = FALSE + return TRUE + +//Grow clones to maturity then kick them out. FREELOADERS +/obj/machinery/clonepod/process() + var/mob/living/mob_occupant = occupant + + if(!is_operational()) //Autoeject if power is lost + if(mob_occupant) + go_out() + connected_message("Clone Ejected: Loss of power.") + + else if(mob_occupant && (mob_occupant.loc == src)) + if((mob_occupant.stat == DEAD) || (mob_occupant.suiciding) || mob_occupant.hellbound) //Autoeject corpses and suiciding dudes. + connected_message("Clone Rejected: Deceased.") + SPEAK("The cloning of [mob_occupant.real_name] has been \ + aborted due to unrecoverable tissue failure.") + go_out() + + else if(mob_occupant.cloneloss > (100 - heal_level)) + mob_occupant.Paralyse(4) + + //Slowly get that clone healed and finished. + mob_occupant.adjustCloneLoss(-((speed_coeff/2) * config.damage_multiplier)) + var/progress = CLONE_INITIAL_DAMAGE - mob_occupant.getCloneLoss() + // To avoid the default cloner making incomplete clones + progress += (100 - MINIMUM_HEAL_LEVEL) + var/milestone = CLONE_INITIAL_DAMAGE / flesh_number + var/installed = flesh_number - unattached_flesh.len + + if((progress / milestone) >= installed) + // attach some flesh + var/obj/item/I = pick_n_take(unattached_flesh) + if(isorgan(I)) + var/obj/item/organ/O = I + O.Insert(mob_occupant) + else if(isbodypart(I)) + var/obj/item/bodypart/BP = I + BP.attach_limb(mob_occupant) + + //Premature clones may have brain damage. + mob_occupant.adjustBrainLoss(-((speed_coeff/2) * config.damage_multiplier)) + + check_brine() + + use_power(7500) //This might need tweaking. + + else if((mob_occupant.cloneloss <= (100 - heal_level))) + connected_message("Cloning Process Complete.") + SPEAK("The cloning cycle of [mob_occupant.real_name] is complete.") + + // If the cloner is upgraded to debugging high levels, sometimes + // organs and limbs can be missing. + for(var/i in unattached_flesh) + if(isorgan(i)) + var/obj/item/organ/O = i + O.Insert(mob_occupant) + else if(isbodypart(i)) + var/obj/item/bodypart/BP = i + BP.attach_limb(mob_occupant) + + go_out() + + else if (!mob_occupant || mob_occupant.loc != src) + occupant = null + if (!mess && !panel_open) + icon_state = "pod_0" + use_power(200) + +//Let's unlock this early I guess. Might be too early, needs tweaking. +/obj/machinery/clonepod/attackby(obj/item/weapon/W, mob/user, params) + if(!(occupant || mess)) + if(default_deconstruction_screwdriver(user, "[icon_state]_maintenance", "[initial(icon_state)]",W)) + return + + if(exchange_parts(user, W)) + return + + if(default_deconstruction_crowbar(W)) + return + + if(istype(W,/obj/item/device/multitool)) + var/obj/item/device/multitool/P = W + + if(istype(P.buffer, /obj/machinery/computer/cloning)) + if(get_area(P.buffer) != get_area(src)) + to_chat(user, "-% Cannot link machines across power zones. Buffer cleared %-") + P.buffer = null + return + to_chat(user, "-% Successfully linked [P.buffer] with [src] %-") + var/obj/machinery/computer/cloning/comp = P.buffer + if(connected) + connected.DetachCloner(src) + comp.AttachCloner(src) + else + P.buffer = src + to_chat(user, "-% Successfully stored \ref[P.buffer] [P.buffer.name] in buffer %-") + return + + var/mob/living/mob_occupant = occupant + if(W.GetID()) + if(!check_access(W)) + to_chat(user, "Access Denied.") + return + if(!(mob_occupant || mess)) + to_chat(user, "Error: Pod has no occupant.") + return + else + connected_message("Authorized Ejection") + SPEAK("An authorized ejection of [clonemind.name] has occurred.") + to_chat(user, "You force an emergency ejection. ") + go_out() + else + return ..() + +/obj/machinery/clonepod/emag_act(mob/user) + if(!occupant) + return + to_chat(user, "You corrupt the genetic compiler.") + malfunction() + +//Put messages in the connected computer's temp var for display. +/obj/machinery/clonepod/proc/connected_message(message) + if ((isnull(connected)) || (!istype(connected, /obj/machinery/computer/cloning))) + return FALSE + if (!message) + return FALSE + + connected.temp = message + connected.updateUsrDialog() + return TRUE + +/obj/machinery/clonepod/proc/go_out() + countdown.stop() + var/mob/living/mob_occupant = occupant + + if(mess) //Clean that mess and dump those gibs! + mess = FALSE + new /obj/effect/gibspawner/generic(loc) + audible_message("You hear a splat.") + icon_state = "pod_0" + return + + if(!mob_occupant) + return + + + if(grab_ghost_when == CLONER_MATURE_CLONE) + mob_occupant.grab_ghost() + to_chat(occupant, "There is a bright flash!
You feel like a new being.
") + mob_occupant.flash_act() + + var/turf/T = get_turf(src) + occupant.forceMove(T) + icon_state = "pod_0" + mob_occupant.domutcheck(1) //Waiting until they're out before possible monkeyizing. The 1 argument forces powers to manifest. + + occupant = null + +/obj/machinery/clonepod/proc/malfunction() + var/mob/living/mob_occupant = occupant + if(mob_occupant) + connected_message("Critical Error!") + SPEAK("Critical error! Please contact a Thinktronic Systems \ + technician, as your warranty may be affected.") + mess = TRUE + for(var/obj/item/O in unattached_flesh) + qdel(O) + icon_state = "pod_g" + if(mob_occupant.mind != clonemind) + clonemind.transfer_to(mob_occupant) + mob_occupant.grab_ghost() // We really just want to make you suffer. + flash_color(mob_occupant, flash_color="#960000", flash_time=100) + to_chat(mob_occupant, "Agony blazes across your consciousness as your body is torn apart.
Is this what dying is like? Yes it is.
") + playsound(src.loc, 'sound/machines/warning-buzzer.ogg', 50, 0) + mob_occupant << sound('sound/hallucinations/veryfar_noise.ogg',0,1,50) + QDEL_IN(mob_occupant, 40) + +/obj/machinery/clonepod/relaymove(mob/user) + if(user.stat == CONSCIOUS) + go_out() + +/obj/machinery/clonepod/emp_act(severity) + var/mob/living/mob_occupant = occupant + if(mob_occupant && prob(100/(severity*efficiency))) + connected_message(Gibberish("EMP-caused Accidental Ejection", 0)) + SPEAK(Gibberish("Exposure to electromagnetic fields has caused the ejection of [mob_occupant.real_name] prematurely." ,0)) + go_out() + ..() + +/obj/machinery/clonepod/ex_act(severity, target) + ..() + if(!QDELETED(src)) + go_out() + +/obj/machinery/clonepod/handle_atom_del(atom/A) + if(A == occupant) + occupant = null + countdown.stop() + +/obj/machinery/clonepod/proc/horrifyingsound() + for(var/i in 1 to 5) + playsound(loc,pick('sound/hallucinations/growl1.ogg','sound/hallucinations/growl2.ogg','sound/hallucinations/growl3.ogg'), 100, rand(0.95,1.05)) + sleep(1) + sleep(10) + playsound(loc,'sound/hallucinations/wail.ogg',100,1) + +/obj/machinery/clonepod/deconstruct(disassembled = TRUE) + if(occupant) + go_out() + ..() + +/obj/machinery/clonepod/proc/maim_clone(mob/living/carbon/human/H) + if(!unattached_flesh) + unattached_flesh = list() + else + for(var/fl in unattached_flesh) + qdel(fl) + unattached_flesh.Cut() + + H.setCloneLoss(CLONE_INITIAL_DAMAGE) //Yeah, clones start with very low health, not with random, because why would they start with random health + H.setBrainLoss(CLONE_INITIAL_DAMAGE) + // In addition to being cellularly damaged and having barely any + // brain function, they also have no limbs or internal organs. + var/static/list/zones = list("r_arm", "l_arm", "r_leg", "l_leg") + for(var/zone in zones) + var/obj/item/bodypart/BP = H.get_bodypart(zone) + BP.drop_limb() + BP.forceMove(src) + unattached_flesh += BP + + for(var/o in H.internal_organs) + var/obj/item/organ/organ = o + if(!istype(organ) || organ.vital) + continue + organ.Remove(H, special=TRUE) + organ.forceMove(src) + unattached_flesh += organ + + flesh_number = unattached_flesh.len + +/obj/machinery/clonepod/proc/check_brine() + // Clones are in a pickled bath of mild chemicals, keeping + // them alive, despite their lack of internal organs + for(var/bt in brine_types) + if(occupant.reagents.get_reagent_amount(bt) < 1) + occupant.reagents.add_reagent(bt, 1) + +/* + * Manual -- A big ol' manual. + */ + +/obj/item/weapon/paper/Cloning + name = "paper - 'H-87 Cloning Apparatus Manual" + info = {"

Getting Started

+ Congratulations, your station has purchased the H-87 industrial cloning device!
+ Using the H-87 is almost as simple as brain surgery! Simply insert the target humanoid into the scanning chamber and select the scan option to create a new profile!
+ That's all there is to it!
+ Notice, cloning system cannot scan inorganic life or small primates. Scan may fail if subject has suffered extreme brain damage.
+

Clone profiles may be viewed through the profiles menu. Scanning implants a complementary HEALTH MONITOR IMPLANT into the subject, which may be viewed from each profile. + Profile Deletion has been restricted to \[Station Head\] level access.

+

Cloning from a profile

+ Cloning is as simple as pressing the CLONE option at the bottom of the desired profile.
+ Per your company's EMPLOYEE PRIVACY RIGHTS agreement, the H-87 has been blocked from cloning crewmembers while they are still alive.
+
+

The provided CLONEPOD SYSTEM will produce the desired clone. Standard clone maturation times (With SPEEDCLONE technology) are roughly 90 seconds. + The cloning pod may be unlocked early with any \[Medical Researcher\] ID after initial maturation is complete.


+ Please note that resulting clones may have a small DEVELOPMENTAL DEFECT as a result of genetic drift.
+

Profile Management

+

The H-87 (as well as your station's standard genetics machine) can accept STANDARD DATA DISKETTES. + These diskettes are used to transfer genetic information between machines and profiles. + A load/save dialog will become available in each profile if a disk is inserted.


+ A good diskette is a great way to counter aforementioned genetic drift!
+
+ This technology produced under license from Thinktronic Systems, LTD."} + +#undef CLONE_INITIAL_DAMAGE +#undef SPEAK +#undef MINIMUM_HEAL_LEVEL \ No newline at end of file diff --git a/code/game/machinery/computer/cloning.dm b/code/game/machinery/computer/cloning.dm index f20e92b721..9e367d43b1 100644 --- a/code/game/machinery/computer/cloning.dm +++ b/code/game/machinery/computer/cloning.dm @@ -1,4 +1,3 @@ - /obj/machinery/computer/cloning name = "cloning console" desc = "Used to clone people and manage DNA." @@ -61,7 +60,7 @@ return if(scanner.occupant && scanner.scan_level > 2) - scan_mob(scanner.occupant) + scan_occupant(scanner.occupant) for(var/datum/data/record/R in records) var/obj/machinery/clonepod/pod = GetAvailableEfficientPod(R.fields["mind"]) @@ -185,8 +184,7 @@ // Scanner if (!isnull(src.scanner)) - - var/mob/living/scanner_occupant = scanner.occupant + var/mob/living/scanner_occupant = get_mob_or_brainmob(scanner.occupant) dat += "

Scanner Functions

" @@ -303,7 +301,7 @@ say("Initiating scan...") spawn(20) - src.scan_mob(scanner.occupant) + src.scan_occupant(scanner.occupant) loading = 0 src.updateUsrDialog() @@ -435,64 +433,70 @@ src.updateUsrDialog() return -/obj/machinery/computer/cloning/proc/scan_mob(mob/living/carbon/human/subject) - if (!istype(subject)) +/obj/machinery/computer/cloning/proc/scan_occupant(occupant) + var/mob/living/mob_occupant = get_mob_or_brainmob(occupant) + var/datum/dna/dna + if(iscarbon(mob_occupant)) + var/mob/living/carbon/C = mob_occupant + dna = C.has_dna() + if(istype(mob_occupant, /mob/living/brain)) + var/mob/living/brain/B = mob_occupant + dna = B.stored_dna + + if(!istype(dna)) scantemp = "Unable to locate valid genetic data." playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) return - if (!subject.getorgan(/obj/item/organ/brain)) - scantemp = "No signs of intelligence detected." - playsound(src, 'sound/machines/terminal_alert.ogg', 50, 0) - return - if (subject.suiciding == 1 || subject.hellbound) + if(mob_occupant.suiciding || mob_occupant.hellbound) scantemp = "Subject's brain is not responding to scanning stimuli." playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) return - if ((subject.disabilities & NOCLONE) && (src.scanner.scan_level < 2)) + if((mob_occupant.disabilities & NOCLONE) && (src.scanner.scan_level < 2)) scantemp = "Subject no longer contains the fundamental materials required to create a living clone." playsound(src, 'sound/machines/terminal_alert.ogg', 50, 0) return - if ((!subject.ckey) || (!subject.client)) + if ((!mob_occupant.ckey) || (!mob_occupant.client)) scantemp = "Mental interface failure." playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) return - if (find_record("ckey", subject.ckey, records)) + if (find_record("ckey", mob_occupant.ckey, records)) scantemp = "Subject already in database." playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) return var/datum/data/record/R = new() - if(subject.dna.species) + if(dna.species) // We store the instance rather than the path, because some // species (abductors, slimepeople) store state in their // species datums - R.fields["mrace"] = subject.dna.species + R.fields["mrace"] = dna.species else var/datum/species/rando_race = pick(config.roundstart_races) R.fields["mrace"] = rando_race.type - R.fields["ckey"] = subject.ckey - R.fields["name"] = subject.real_name - R.fields["id"] = copytext(md5(subject.real_name), 2, 6) - R.fields["UE"] = subject.dna.unique_enzymes - R.fields["UI"] = subject.dna.uni_identity - R.fields["SE"] = subject.dna.struc_enzymes - R.fields["blood_type"] = subject.dna.blood_type - R.fields["features"] = subject.dna.features - R.fields["factions"] = subject.faction - //Add an implant if needed + R.fields["ckey"] = mob_occupant.ckey + R.fields["name"] = mob_occupant.real_name + R.fields["id"] = copytext(md5(mob_occupant.real_name), 2, 6) + R.fields["UE"] = dna.unique_enzymes + R.fields["UI"] = dna.uni_identity + R.fields["SE"] = dna.struc_enzymes + R.fields["blood_type"] = dna.blood_type + R.fields["features"] = dna.features + R.fields["factions"] = mob_occupant.faction + + if (!isnull(mob_occupant.mind)) //Save that mind so traitors can continue traitoring after cloning. + R.fields["mind"] = "\ref[mob_occupant.mind]" + + //Add an implant if needed var/obj/item/weapon/implant/health/imp - for(var/obj/item/weapon/implant/health/HI in subject.implants) + for(var/obj/item/weapon/implant/health/HI in mob_occupant.implants) imp = HI break if(!imp) - imp = new /obj/item/weapon/implant/health(subject) - imp.implant(subject) + imp = new /obj/item/weapon/implant/health(mob_occupant) + imp.implant(mob_occupant) R.fields["imp"] = "\ref[imp]" - if (!isnull(subject.mind)) //Save that mind so traitors can continue traitoring after cloning. - R.fields["mind"] = "\ref[subject.mind]" - src.records += R scantemp = "Subject successfully scanned." playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0) diff --git a/code/game/machinery/dna_scanner.dm b/code/game/machinery/dna_scanner.dm index d572b3e9dc..e5785e0dab 100644 --- a/code/game/machinery/dna_scanner.dm +++ b/code/game/machinery/dna_scanner.dm @@ -9,6 +9,7 @@ use_power = 1 idle_power_usage = 50 active_power_usage = 300 + occupant_typecache = list(/mob/living, /obj/item/bodypart/head, /obj/item/organ/brain) var/damage_coeff var/scan_level var/precision_coeff @@ -98,6 +99,13 @@ open_machine() +/obj/machinery/dna_scannernew/proc/locate_computer(type_) + for(dir in list(NORTH,EAST,SOUTH,WEST)) + var/C = locate(type_, get_step(src, dir)) + if(C) + return C + return null + /obj/machinery/dna_scannernew/close_machine() if(!state_open) return 0 @@ -105,22 +113,19 @@ ..() // search for ghosts, if the corpse is empty and the scanner is connected to a cloner - var/mob/living/mob_occupant = occupant - if(mob_occupant) - if(locate(/obj/machinery/computer/cloning, get_step(src, NORTH)) \ - || locate(/obj/machinery/computer/cloning, get_step(src, SOUTH)) \ - || locate(/obj/machinery/computer/cloning, get_step(src, EAST)) \ - || locate(/obj/machinery/computer/cloning, get_step(src, WEST))) + var/mob/living/mob_occupant = get_mob_or_brainmob(occupant) + if(istype(mob_occupant)) + if(locate_computer(/obj/machinery/computer/cloning)) if(!mob_occupant.suiciding && !(mob_occupant.disabilities & NOCLONE) && !mob_occupant.hellbound) mob_occupant.notify_ghost_cloning("Your corpse has been placed into a cloning scanner. Re-enter your corpse if you want to be cloned!", source = src) - var/obj/machinery/computer/scan_consolenew/console - for(dir in list(NORTH,EAST,SOUTH,WEST)) - console = locate(/obj/machinery/computer/scan_consolenew, get_step(src, dir)) - if(console) - console.on_scanner_close() - break - return 1 + // DNA manipulators cannot operate on severed heads or brains + if(isliving(occupant)) + var/obj/machinery/computer/scan_consolenew/console = locate_computer(/obj/machinery/computer/scan_consolenew) + if(console) + console.on_scanner_close() + + return TRUE /obj/machinery/dna_scannernew/open_machine() if(state_open) @@ -158,4 +163,4 @@ if(..(user,1,0)) //don't set the machine, since there's no dialog return - toggle_open(user) + toggle_open(user) \ No newline at end of file diff --git a/code/modules/mob/living/brain/brain.dm b/code/modules/mob/living/brain/brain.dm index 1f74ed1917..11700b7638 100644 --- a/code/modules/mob/living/brain/brain.dm +++ b/code/modules/mob/living/brain/brain.dm @@ -1,5 +1,3 @@ - - /mob/living/brain var/obj/item/device/mmi/container = null var/timeofhostdeath = 0