diff --git a/code/_globalvars/lists/poll_ignore.dm b/code/_globalvars/lists/poll_ignore.dm
index 98c801c1ff0..fdfbe68a93c 100644
--- a/code/_globalvars/lists/poll_ignore.dm
+++ b/code/_globalvars/lists/poll_ignore.dm
@@ -42,7 +42,10 @@ GLOBAL_LIST_INIT(poll_ignore_desc, list(
POLL_IGNORE_IMAGINARYFRIEND = "Imaginary Friend",
POLL_IGNORE_SPLITPERSONALITY = "Split Personality",
POLL_IGNORE_CONTRACTOR_SUPPORT = "Contractor Support Unit",
- POLL_IGNORE_ACADEMY_WIZARD = "Academy Wizard Defender"
+ POLL_IGNORE_ACADEMY_WIZARD = "Academy Wizard Defender",
+ //SKYRAT EDIT BEGIN ADDITION - CLONING
+ POLL_IGNORE_DEFECTIVECLONE = "Defective clone"
+ //SKYRAT EDIT END
))
GLOBAL_LIST_INIT(poll_ignore, init_poll_ignore())
diff --git a/code/datums/dna.dm b/code/datums/dna.dm
index 3d44036dbbf..cb3cf31096f 100644
--- a/code/datums/dna.dm
+++ b/code/datums/dna.dm
@@ -15,6 +15,9 @@
var/default_mutation_genes[DNA_MUTATION_BLOCKS] //List of the default genes from this mutation to allow DNA Scanner highlighting
var/stability = 100
var/scrambled = FALSE //Did we take something like mutagen? In that case we cant get our genes scanned to instantly cheese all the powers.
+ //SKYRAT EDIT BEGIN - ADDITION - CLONING
+ var/delete_species = TRUE
+ //SKYRAT EDIT END
/datum/dna/New(mob/living/new_holder)
if(istype(new_holder))
@@ -27,7 +30,11 @@
cholder.dna = null
holder = null
- QDEL_NULL(species)
+ //SKYRAT EDIT BEGIN - CHANGE - CLONING
+ //QDEL_NULL(species) - SKYRAT ORIGINAL
+ if(delete_species)
+ QDEL_NULL(species)
+ //SKYRAT EDIT END
mutations.Cut() //This only references mutations, just dereference.
temporary_mutations.Cut() //^
diff --git a/code/datums/shuttles.dm b/code/datums/shuttles.dm
index 0e5e898847e..eef38be18e2 100644
--- a/code/datums/shuttles.dm
+++ b/code/datums/shuttles.dm
@@ -523,7 +523,10 @@
name = "Tr%nPo2r& Z3TA"
description = "A glitch appears on your monitor, flickering in and out of the options laid before you. \
It seems strange and alien, you may need a special technology to access the signal.."
- admin_notes = "Has alien surgery tools, and a void core that provides unlimited power."
+ //SKYRAT EDIT CHANGE BEGIN - CLONING
+ //admin_notes = "Has alien surgery tools, and a void core that provides unlimited power." - ORIGINAL
+ admin_notes = "Has an on-board experimental cloner that creates copies of its user, alien surgery tools, and a void core that provides unlimited power."
+ //SKYRAT EDIT CHANGE END
credit_cost = 8000
/datum/map_template/shuttle/emergency/zeta/prerequisites_met()
diff --git a/code/modules/antagonists/revenant/revenant_abilities.dm b/code/modules/antagonists/revenant/revenant_abilities.dm
index 0c86660c4b2..0626745d268 100644
--- a/code/modules/antagonists/revenant/revenant_abilities.dm
+++ b/code/modules/antagonists/revenant/revenant_abilities.dm
@@ -310,6 +310,12 @@
if(prob(50))
new /obj/effect/temp_visual/revenant(thing.loc)
thing.emag_act(null)
+ //SKYRAT EDIT ADDITION BEGIN - CLONING
+ else
+ if(!istype(thing, /obj/machinery/clonepod)) //I hate everything but mostly the fact there's no better way to do this without just not affecting it at all
+ thing.emp_act(EMP_HEAVY)
+ //SKYRAT EDIT END
+
for(var/mob/living/silicon/robot/S in T) //Only works on cyborgs, not AI
playsound(S, 'sound/machines/warning-buzzer.ogg', 50, TRUE)
new /obj/effect/temp_visual/revenant(S.loc)
diff --git a/code/modules/clothing/head/misc_special.dm b/code/modules/clothing/head/misc_special.dm
index 45729bf5d54..e1eb890341c 100644
--- a/code/modules/clothing/head/misc_special.dm
+++ b/code/modules/clothing/head/misc_special.dm
@@ -326,6 +326,9 @@
if(paranoia)
QDEL_NULL(paranoia)
paranoia = new()
+ //SKYRAT EDIT ADDITION BEGIN - CLONING
+ paranoia.clonable = FALSE
+ //SKYRAT EDIT END
user.gain_trauma(paranoia, TRAUMA_RESILIENCE_MAGIC)
to_chat(user, "As you don the foiled hat, an entire world of conspiracy theories and seemingly insane ideas suddenly rush into your mind. What you once thought unbelievable suddenly seems.. undeniable. Everything is connected and nothing happens just by accident. You know too much and now they're out to get you. ")
diff --git a/modular_skyrat/master_files/sounds/horror_scream.ogg b/modular_skyrat/master_files/sounds/horror_scream.ogg
new file mode 100644
index 00000000000..7b638fd334f
Binary files /dev/null and b/modular_skyrat/master_files/sounds/horror_scream.ogg differ
diff --git a/modular_skyrat/modules/cloning/code/_DEFINES/DNA.dm b/modular_skyrat/modules/cloning/code/_DEFINES/DNA.dm
new file mode 100644
index 00000000000..9839e7d5467
--- /dev/null
+++ b/modular_skyrat/modules/cloning/code/_DEFINES/DNA.dm
@@ -0,0 +1,2 @@
+#define CLONER_FRESH_CLONE "fresh"
+#define CLONER_MATURE_CLONE "mature"
diff --git a/modular_skyrat/modules/cloning/code/_DEFINES/access.dm b/modular_skyrat/modules/cloning/code/_DEFINES/access.dm
new file mode 100644
index 00000000000..de53c1b6ae3
--- /dev/null
+++ b/modular_skyrat/modules/cloning/code/_DEFINES/access.dm
@@ -0,0 +1,2 @@
+/// Cloning room and clone pod ejection
+#define ACCESS_CLONING 68
diff --git a/modular_skyrat/modules/cloning/code/_DEFINES/machines.dm b/modular_skyrat/modules/cloning/code/_DEFINES/machines.dm
new file mode 100644
index 00000000000..34ef9b3a897
--- /dev/null
+++ b/modular_skyrat/modules/cloning/code/_DEFINES/machines.dm
@@ -0,0 +1,3 @@
+//cloning defines. These are flags.
+#define CLONING_SUCCESS (1<<0)
+#define CLONING_DELETE_RECORD (1<<1)
diff --git a/modular_skyrat/modules/cloning/code/_DEFINES/poll_ignore.dm b/modular_skyrat/modules/cloning/code/_DEFINES/poll_ignore.dm
new file mode 100644
index 00000000000..c1fba026217
--- /dev/null
+++ b/modular_skyrat/modules/cloning/code/_DEFINES/poll_ignore.dm
@@ -0,0 +1,2 @@
+//Cloning defines
+#define POLL_IGNORE_DEFECTIVECLONE "defective_clone"
diff --git a/modular_skyrat/modules/cloning/code/datums/brain_damage/brain_trauma.dm b/modular_skyrat/modules/cloning/code/datums/brain_damage/brain_trauma.dm
new file mode 100644
index 00000000000..9951272920f
--- /dev/null
+++ b/modular_skyrat/modules/cloning/code/datums/brain_damage/brain_trauma.dm
@@ -0,0 +1,6 @@
+/datum/brain_trauma
+ var/clonable = TRUE // will this transfer if the brain is cloned?
+
+/datum/brain_trauma/proc/on_clone()
+ if(clonable)
+ return new type
diff --git a/modular_skyrat/modules/cloning/code/datums/brain_damage/phobia.dm b/modular_skyrat/modules/cloning/code/datums/brain_damage/phobia.dm
new file mode 100644
index 00000000000..6b68db68359
--- /dev/null
+++ b/modular_skyrat/modules/cloning/code/datums/brain_damage/phobia.dm
@@ -0,0 +1,3 @@
+/datum/brain_trauma/mild/phobia/on_clone()
+ if(clonable)
+ return new type(phobia_type)
diff --git a/modular_skyrat/modules/cloning/code/datums/brain_damage/special.dm b/modular_skyrat/modules/cloning/code/datums/brain_damage/special.dm
new file mode 100644
index 00000000000..3edacfd733e
--- /dev/null
+++ b/modular_skyrat/modules/cloning/code/datums/brain_damage/special.dm
@@ -0,0 +1,5 @@
+/datum/brain_trauma/special/psychotic_brawling/bath_salts
+ clonable = FALSE
+
+/datum/brain_trauma/special/beepsky
+ clonable = FALSE
diff --git a/modular_skyrat/modules/cloning/code/datums/mood_events/generic_negative_events.dm b/modular_skyrat/modules/cloning/code/datums/mood_events/generic_negative_events.dm
new file mode 100644
index 00000000000..7ae3eeae5f3
--- /dev/null
+++ b/modular_skyrat/modules/cloning/code/datums/mood_events/generic_negative_events.dm
@@ -0,0 +1,3 @@
+/datum/mood_event/cloned_corpse
+ description = "I recently saw my own corpse...\n"
+ mood_change = -6
diff --git a/modular_skyrat/modules/cloning/code/datums/ruins/space.dm b/modular_skyrat/modules/cloning/code/datums/ruins/space.dm
new file mode 100644
index 00000000000..2280d625d16
--- /dev/null
+++ b/modular_skyrat/modules/cloning/code/datums/ruins/space.dm
@@ -0,0 +1,5 @@
+/datum/map_template/ruin/space/cloning_facility
+ id = "cloning_facility"
+ suffix = "cloning_facility.dmm"
+ name = "Ancient Cloning Lab"
+ description = "An experimental cloning lab snapped off from an ancient ship. The cloner model inside lacks many modern functionalities and security measures."
diff --git a/modular_skyrat/modules/cloning/code/datums/traits/_quirk.dm b/modular_skyrat/modules/cloning/code/datums/traits/_quirk.dm
new file mode 100644
index 00000000000..d21063ddcc1
--- /dev/null
+++ b/modular_skyrat/modules/cloning/code/datums/traits/_quirk.dm
@@ -0,0 +1,2 @@
+/datum/quirk/proc/clone_data() //return additional data that should be remembered by cloning
+/datum/quirk/proc/on_clone(data) //create the quirk from clone data
diff --git a/modular_skyrat/modules/cloning/code/datums/traits/negative.dm b/modular_skyrat/modules/cloning/code/datums/traits/negative.dm
new file mode 100644
index 00000000000..f52de2d0c74
--- /dev/null
+++ b/modular_skyrat/modules/cloning/code/datums/traits/negative.dm
@@ -0,0 +1,5 @@
+/datum/quirk/family_heirloom/clone_data()
+ return heirloom
+
+/datum/quirk/family_heirloom/on_clone(data)
+ heirloom = data
diff --git a/modular_skyrat/modules/cloning/code/game/area/Space_Station_13_areas.dm b/modular_skyrat/modules/cloning/code/game/area/Space_Station_13_areas.dm
new file mode 100644
index 00000000000..dcf62aba9ac
--- /dev/null
+++ b/modular_skyrat/modules/cloning/code/game/area/Space_Station_13_areas.dm
@@ -0,0 +1,3 @@
+/area/medical/genetics/cloning
+ name = "Cloning Lab"
+ icon_state = "cloning"
diff --git a/modular_skyrat/modules/cloning/code/game/machinery/cloning.dm b/modular_skyrat/modules/cloning/code/game/machinery/cloning.dm
new file mode 100644
index 00000000000..c33d4c108b5
--- /dev/null
+++ b/modular_skyrat/modules/cloning/code/game/machinery/cloning.dm
@@ -0,0 +1,615 @@
+//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 150 //Clones in clonepods start with 150 cloneloss damage and 150 brainloss damage, thats just logical
+#define MINIMUM_HEAL_LEVEL 40
+
+#define SPEAK(message) radio.talk_into(src, message, radio_channel)
+
+/obj/machinery/clonepod
+ name = "cloning pod"
+ desc = "An electronically-lockable pod for growing organic tissue."
+ density = TRUE
+ icon = 'modular_skyrat/modules/cloning/icons/obj/machines/cloning_pod.dmi'
+ icon_state = "pod_0"
+ req_access = list(ACCESS_CLONING) //FOR PREMATURE UNLOCKING.
+ verb_say = "states"
+ circuit = /obj/item/circuitboard/machine/clonepod
+
+ var/heal_level //The clone is released once its health reaches this level.
+ var/obj/machinery/computer/cloning/connected //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/internal_radio = TRUE
+ var/obj/item/radio/radio
+ var/radio_key = /obj/item/encryptionkey/headset_med
+ var/radio_channel = RADIO_CHANNEL_MEDICAL
+
+ var/obj/effect/countdown/clonepod/countdown
+
+ var/list/unattached_flesh
+ var/flesh_number = 0
+ var/datum/bank_account/current_insurance
+ fair_market_price = 5 // He nodded, because he knew I was right. Then he swiped his credit card to pay me for arresting him.
+ payment_department = ACCOUNT_MED
+
+/obj/machinery/clonepod/Initialize()
+ . = ..()
+
+ countdown = new(src)
+
+ if(internal_radio)
+ radio = new(src)
+ radio.keyslot = new radio_key
+ radio.subspace_transmission = TRUE
+ radio.canhear_range = 0
+ radio.recalculateChannels()
+ update_icon()
+
+/obj/machinery/clonepod/Destroy()
+ var/mob/living/mob_occupant = occupant
+ go_out()
+ if(mob_occupant)
+ log_cloning("[key_name(mob_occupant)] ejected from [src] at [AREACOORD(src)] due to Destroy().")
+ QDEL_NULL(radio)
+ QDEL_NULL(countdown)
+ if(connected)
+ connected.DetachCloner(src)
+ QDEL_LIST(unattached_flesh)
+ . = ..()
+
+/obj/machinery/clonepod/RefreshParts()
+ speed_coeff = 0
+ efficiency = 0
+ for(var/obj/item/stock_parts/scanning_module/S in component_parts)
+ efficiency += S.rating
+ for(var/obj/item/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/machinery/clonepod/attack_hand(mob/user)
+ . = ..()
+ if(.)
+ return
+ user.examinate(src)
+
+/obj/machinery/clonepod/attack_ai(mob/user)
+ return attack_hand(user)
+
+/obj/machinery/clonepod/examine(mob/user)
+ . = ..()
+ . += "The linking device can be scanned with a multitool."
+ if(in_range(user, src) || isobserver(user))
+ . += "The status display reads: Cloning speed at [speed_coeff*50]%.
Predicted amount of cellular damage: [100-heal_level]%."
+ if(efficiency > 5)
+ . += "Pod has been upgraded to support autoprocessing and apply beneficial mutations."
+
+//The return of data disks?? Just for transferring between genetics machine/cloning machine.
+//TO-DO: Make the genetics machine accept them.
+/obj/item/disk/data/cloning
+ name = "cloning data disk"
+ icon_state = "datadisk0" //Gosh I hope syndies don't mistake them for the nuke disk.
+ var/list/fields = list()
+
+//Disk stuff.
+/obj/item/disk/data/Initialize()
+ . = ..()
+ icon_state = "datadisk[rand(0,6)]"
+ add_overlay("datadisk_gene")
+
+/obj/item/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/disk/data/examine(mob/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)
+ . += "It's filled with blood and viscera. You swear you can see it moving..."
+ if(is_operational && istype(mob_occupant))
+ if(mob_occupant.stat != DEAD)
+ . += "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 don't need to breathe while cloning anyway.
+ 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(clonename, ui, mutation_index, mindref, last_death, blood_type, datum/species/mrace, list/features, list/mutantparts, list/markings, factions, list/quirks, datum/bank_account/insurance, list/traumas, empty)
+ if(panel_open)
+ return NONE
+ if(mess || attempting)
+ return NONE
+
+ if(!empty) //Doesn't matter if we're just making a copy
+ clonemind = locate(mindref) in SSticker.minds
+ if(!istype(clonemind)) //not a mind
+ return NONE
+ if(clonemind.last_death != last_death) //The soul has advanced, the record has not.
+ return NONE
+ if(!QDELETED(clonemind.current))
+ if(clonemind.current.stat != DEAD) //mind is associated with a non-dead body
+ return NONE
+ if(clonemind.current.suiciding) // Mind is associated with a body that is suiciding.
+ return NONE
+ if(!clonemind.active)
+ // get_ghost() will fail if they're unable to reenter their body
+ var/mob/dead/observer/G = clonemind.get_ghost()
+ if(!G)
+ return NONE
+ if(G.suiciding) // The ghost came from a body that is suiciding.
+ return NONE
+ if(clonemind.damnation_type) //Can't clone the damned.
+ INVOKE_ASYNC(src, .proc/horrifyingsound)
+ mess = TRUE
+ update_icon()
+ return NONE
+ current_insurance = insurance
+ attempting = TRUE //One at a time!!
+ countdown.start()
+
+ var/mob/living/carbon/human/H = new /mob/living/carbon/human(src)
+
+ if(!clonename) //to prevent null names
+ clonename = "clone ([rand(1,999)])"
+ H.real_name = clonename
+
+ H.hardset_dna(ui, mutation_index, H.dna.default_mutation_genes, H.real_name, blood_type, mrace, features, H.dna.mutations, FALSE)
+ H.set_species(mrace, TRUE, null, features, mutantparts, markings)
+
+ if(!HAS_TRAIT(H, TRAIT_RADIMMUNE))//dont apply mutations if the species is Mutation proof.
+ 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.easy_randmut(POSITIVE)
+ if(efficiency < 3 && prob(50))
+ var/mob/M = H.easy_randmut(NEGATIVE+MINOR_NEGATIVE)
+ 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
+
+ //Get the clone body ready
+ maim_clone(H)
+ ADD_TRAIT(H, TRAIT_STABLEHEART, CLONING_POD_TRAIT)
+ ADD_TRAIT(H, TRAIT_STABLELIVER, CLONING_POD_TRAIT)
+ ADD_TRAIT(H, TRAIT_EMOTEMUTE, CLONING_POD_TRAIT)
+ ADD_TRAIT(H, TRAIT_MUTE, CLONING_POD_TRAIT)
+ ADD_TRAIT(H, TRAIT_NOBREATH, CLONING_POD_TRAIT)
+ ADD_TRAIT(H, TRAIT_NOCRITDAMAGE, CLONING_POD_TRAIT)
+ H.Unconscious(80)
+
+ if(!empty)
+ 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
+
+ for(var/V in quirks)
+ var/datum/quirk/Q = new V(H)
+ Q.on_clone(quirks[V])
+
+ for(var/t in traumas)
+ var/datum/brain_trauma/BT = t
+ var/datum/brain_trauma/cloned_trauma = BT.on_clone()
+ if(cloned_trauma)
+ H.gain_trauma(cloned_trauma, BT.resilience)
+
+ H.set_cloned_appearance()
+
+ H.set_suicide(FALSE)
+ attempting = FALSE
+ update_icon()
+ return CLONING_SUCCESS
+
+//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()
+ log_cloning("[key_name(mob_occupant)] ejected from [src] at [AREACOORD(src)] due to power loss.")
+
+ connected_message("Clone Ejected: Loss of power.")
+
+ else if(mob_occupant && (mob_occupant.loc == src))
+ if(SSeconomy.full_ancap)
+ if(!current_insurance)
+ go_out()
+ log_cloning("[key_name(mob_occupant)] ejected from [src] at [AREACOORD(src)] due to invalid bank account.")
+ connected_message("Clone Ejected: No bank account.")
+ if(internal_radio)
+ SPEAK("The cloning of [mob_occupant.real_name] has been terminated due to no bank account to draw payment from.")
+ else if(!current_insurance.adjust_money(-fair_market_price))
+ go_out()
+ log_cloning("[key_name(mob_occupant)] ejected from [src] at [AREACOORD(src)] due to insufficient funds.")
+ connected_message("Clone Ejected: Out of Money.")
+ if(internal_radio)
+ SPEAK("The cloning of [mob_occupant.real_name] has been ended prematurely due to being unable to pay.")
+ else
+ var/datum/bank_account/D = SSeconomy.get_dep_account(payment_department)
+ if(D)
+ D.adjust_money(fair_market_price)
+ if(mob_occupant && (mob_occupant.stat == DEAD) || (mob_occupant.suiciding)) //Autoeject corpses and suiciding dudes.
+ connected_message("Clone Rejected: Deceased.")
+ if(internal_radio)
+ SPEAK("The cloning of [mob_occupant.real_name] has been \
+ aborted due to unrecoverable tissue failure.")
+ go_out()
+ log_cloning("[key_name(mob_occupant)] ejected from [src] at [AREACOORD(src)] after suiciding.")
+
+ else if(mob_occupant && mob_occupant.cloneloss > (100 - heal_level))
+ mob_occupant.Unconscious(80)
+ var/dmg_mult = CONFIG_GET(number/damage_multiplier)
+ //Slowly get that clone healed and finished.
+ mob_occupant.adjustCloneLoss(-((speed_coeff / 2) * dmg_mult))
+ 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.organ_flags &= ~ORGAN_FROZEN
+ O.Insert(mob_occupant)
+ else if(isbodypart(I))
+ var/obj/item/bodypart/BP = I
+ BP.attach_limb(mob_occupant)
+
+ use_power(7500) //This might need tweaking.
+
+ else if(mob_occupant && (mob_occupant.cloneloss <= (100 - heal_level)))
+ connected_message("Cloning Process Complete.")
+ if(internal_radio)
+ 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.organ_flags &= ~ORGAN_FROZEN
+ O.Insert(mob_occupant)
+ else if(isbodypart(i))
+ var/obj/item/bodypart/BP = i
+ BP.attach_limb(mob_occupant)
+
+ go_out()
+ log_cloning("[key_name(mob_occupant)] completed cloning cycle in [src] at [AREACOORD(src)].")
+
+ else if (!mob_occupant || mob_occupant.loc != src)
+ occupant = null
+ if (!mess && !panel_open)
+ update_icon()
+ use_power(200)
+
+ update_icon()
+
+//Let's unlock this early I guess. Might be too early, needs tweaking.
+/obj/machinery/clonepod/attackby(obj/item/W, mob/user, params)
+ if(!(occupant || mess))
+ if(default_deconstruction_screwdriver(user, "[icon_state]_maintenance", "[initial(icon_state)]",W))
+ return
+
+ if(default_deconstruction_crowbar(W))
+ return
+
+ if(W.tool_behaviour == TOOL_MULTITOOL)
+ if(!multitool_check_buffer(user, W))
+ return
+ var/obj/item/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
+ add_fingerprint(user)
+ connected_message("Emergency Ejection")
+ SPEAK("An emergency ejection of [clonemind.name] has occurred. Survival not guaranteed.")
+ to_chat(user, "You force an emergency ejection. ")
+ go_out()
+ log_cloning("[key_name(user)] manually ejected [key_name(mob_occupant)] from [src] at [AREACOORD(src)].")
+ log_combat(user, mob_occupant, "ejected", W, "from [src]")
+ else
+ return ..()
+
+/obj/machinery/clonepod/emag_act(mob/user)
+ if(!occupant)
+ return
+ to_chat(user, "You corrupt the genetic compiler.")
+ malfunction()
+ add_fingerprint(user)
+ log_cloning("[key_name(user)] emagged [src] at [AREACOORD(src)], causing it to malfunction.")
+ if(user)
+ log_combat(user, src, "emagged", null, occupant ? "[occupant] inside, killing them via malfunction." : null)
+
+//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
+ var/turf/T = get_turf(src)
+
+ if(mess) //Clean that mess and dump those gibs!
+ for(var/obj/fl in unattached_flesh)
+ fl.forceMove(T)
+ if(istype(fl, /obj/item/organ))
+ var/obj/item/organ/O = fl
+ O.organ_flags &= ~ORGAN_FROZEN
+ unattached_flesh.Cut()
+ mess = FALSE
+ new /obj/effect/gibspawner/generic(get_turf(src), mob_occupant)
+ audible_message("You hear a splat.")
+ update_icon()
+ return
+
+ if(!mob_occupant)
+ return
+ current_insurance = null
+ REMOVE_TRAIT(mob_occupant, TRAIT_STABLEHEART, CLONING_POD_TRAIT)
+ REMOVE_TRAIT(mob_occupant, TRAIT_STABLELIVER, CLONING_POD_TRAIT)
+ REMOVE_TRAIT(mob_occupant, TRAIT_EMOTEMUTE, CLONING_POD_TRAIT)
+ REMOVE_TRAIT(mob_occupant, TRAIT_MUTE, CLONING_POD_TRAIT)
+ REMOVE_TRAIT(mob_occupant, TRAIT_NOCRITDAMAGE, CLONING_POD_TRAIT)
+ REMOVE_TRAIT(mob_occupant, TRAIT_NOBREATH, CLONING_POD_TRAIT)
+
+ 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()
+
+ occupant.forceMove(T)
+ update_icon()
+ mob_occupant.domutcheck(1) //Waiting until they're out before possible monkeyizing. The 1 argument forces powers to manifest.
+ for(var/fl in unattached_flesh)
+ qdel(fl)
+ unattached_flesh.Cut()
+
+ occupant = null
+ clonemind = 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
+ maim_clone(mob_occupant) //Remove every bit that's grown back so far to drop later, also destroys bits that haven't grown yet
+ update_icon()
+ if(clonemind && 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, 'sound/machines/warning-buzzer.ogg', 50)
+ SEND_SOUND(mob_occupant, sound('sound/hallucinations/veryfar_noise.ogg',0,1,50))
+ log_cloning("[key_name(mob_occupant)] destroyed within [src] at [AREACOORD(src)] due to malfunction.")
+ QDEL_IN(mob_occupant, 40)
+
+/obj/machinery/clonepod/container_resist_act(mob/living/user)
+ if(user.stat == CONSCIOUS)
+ go_out()
+
+/obj/machinery/clonepod/relaymove(mob/user)
+ container_resist_act(user)
+
+/obj/machinery/clonepod/emp_act(severity)
+ . = ..()
+ if (!(. & EMP_PROTECT_SELF))
+ var/mob/living/mob_occupant = occupant
+ if(mob_occupant && prob(100/(severity*efficiency)))
+ connected_message(Gibberish("EMP-caused Accidental Ejection"))
+ SPEAK(Gibberish("Exposure to electromagnetic fields has caused the ejection of [mob_occupant.real_name] prematurely."))
+ go_out()
+ log_cloning("[key_name(mob_occupant)] ejected from [src] at [AREACOORD(src)] due to EMP pulse.")
+
+/obj/machinery/clonepod/ex_act(severity, target)
+ ..()
+ if(!QDELETED(src) && occupant)
+ var/mob/living/mob_occupant = occupant
+ go_out()
+ log_cloning("[key_name(mob_occupant)] ejected from [src] at [AREACOORD(src)] due to explosion.")
+
+/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(src,pick('sound/hallucinations/growl1.ogg','sound/hallucinations/growl2.ogg','sound/hallucinations/growl3.ogg'), 100, (rand(95,105) * 0.01))
+ sleep(1)
+ sleep(10)
+ playsound(src,'sound/hallucinations/wail.ogg', 100, TRUE)
+
+/obj/machinery/clonepod/deconstruct(disassembled = TRUE)
+ if(occupant)
+ var/mob/living/mob_occupant = occupant
+ go_out()
+ log_cloning("[key_name(mob_occupant)] ejected from [src] at [AREACOORD(src)] due to deconstruction.")
+ ..()
+
+/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
+ // In addition to being cellularly damaged, they also have no limbs or internal organs.
+ // Applying brainloss is done when the clone leaves the pod, so application of traumas can happen
+ // based on the level of damage sustained.
+
+ if(!HAS_TRAIT(H, TRAIT_NODISMEMBER))
+ var/static/list/zones = list(BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG)
+ for(var/zone in zones)
+ var/obj/item/bodypart/BP = H.get_bodypart(zone)
+ if(BP)
+ 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.organ_flags & ORGAN_VITAL))
+ continue
+ organ.organ_flags |= ORGAN_FROZEN
+ organ.Remove(H, special=TRUE)
+ organ.forceMove(src)
+ unattached_flesh += organ
+
+ flesh_number = unattached_flesh.len
+
+/obj/machinery/clonepod/update_icon_state()
+ if(mess)
+ icon_state = "pod_g"
+ else if(occupant)
+ icon_state = "pod_1"
+ else
+ icon_state = "pod_0"
+ if(panel_open)
+ icon_state = "pod_0_maintenance"
+
+/obj/machinery/clonepod/update_overlays()
+ . = ..()
+ if(mess)
+ var/mutable_appearance/gib1 = mutable_appearance('modular_skyrat/modules/cloning/icons/obj/machines/cryo_mobs.dmi', "gibup")
+ var/mutable_appearance/gib2 = mutable_appearance('modular_skyrat/modules/cloning/icons/obj/machines/cryo_mobs.dmi', "gibdown")
+ gib1.pixel_y = 27 + round(sin(world.time) * 3)
+ gib1.pixel_x = round(sin(world.time * 3))
+ gib2.pixel_y = 27 + round(cos(world.time) * 3)
+ gib2.pixel_x = round(cos(world.time * 3))
+ . += gib2
+ . += gib1
+ else if(occupant)
+ var/mutable_appearance/occupant_overlay
+ var/unattached_fl_number = length(unattached_flesh) ? unattached_flesh.len : 0
+ var/completion = (flesh_number - unattached_fl_number) / flesh_number
+ if(completion > 0.7)
+ occupant_overlay = mutable_appearance(occupant.icon, occupant.icon_state)
+ occupant_overlay.copy_overlays(occupant)
+ else
+ occupant_overlay = mutable_appearance('modular_skyrat/modules/cloning/icons/obj/machines/cryo_mobs.dmi', "clone_meat")
+ var/matrix/tform = matrix()
+ tform.Scale(completion)
+ tform.Turn(cos(world.time * 2) * 3)
+ occupant_overlay.transform = tform
+ occupant_overlay.appearance_flags = NONE
+ occupant_overlay.dir = SOUTH
+ occupant_overlay.pixel_y = 27 + round(sin(world.time) * 3)
+ occupant_overlay.pixel_x = round(sin(world.time * 3))
+ . += occupant_overlay
+ . += "cover-on"
+ . += "panel"
+
+/*
+ * Manual -- A big ol' manual.
+ */
+
+/obj/item/paper/guides/jobs/medical/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
diff --git a/modular_skyrat/modules/cloning/code/game/machinery/computer/cloning.dm b/modular_skyrat/modules/cloning/code/game/machinery/computer/cloning.dm
new file mode 100644
index 00000000000..ece4f857971
--- /dev/null
+++ b/modular_skyrat/modules/cloning/code/game/machinery/computer/cloning.dm
@@ -0,0 +1,626 @@
+#define AUTOCLONING_MINIMAL_LEVEL 3
+
+/obj/machinery/computer/cloning
+ name = "cloning console"
+ desc = "Used to clone people and manage DNA."
+ icon_screen = "dna"
+ icon_keyboard = "med_key"
+ circuit = /obj/item/circuitboard/computer/cloning
+ req_access = list(ACCESS_GENETICS) //for modifying records
+ var/obj/machinery/dna_scannernew/scanner //Linked scanner. For scanning.
+ var/list/pods //Linked cloning pods
+ var/temp = "Inactive"
+ var/scantemp_ckey
+ var/scantemp = "Ready to Scan"
+ var/menu = 1 //Which menu screen to display
+ var/list/records = list()
+ var/datum/data/record/active_record
+ var/obj/item/disk/data/cloning/diskette //Incompatible format to genetics machine
+ //select which parts of the diskette to load
+ var/include_se = FALSE //mutations
+ var/include_ui = FALSE //appearance
+ var/include_ue = FALSE //blood type, UE, and name
+
+ var/loading = FALSE // Nice loading text
+ var/autoprocess = FALSE
+
+ light_color = LIGHT_COLOR_BLUE
+
+/obj/machinery/computer/cloning/Initialize()
+ . = ..()
+ updatemodules(TRUE)
+
+/obj/machinery/computer/cloning/Destroy()
+ if(pods)
+ for(var/P in pods)
+ DetachCloner(P)
+ pods = null
+ return ..()
+
+/obj/machinery/computer/cloning/proc/GetAvailablePod(mind = null)
+ if(pods)
+ for(var/P in pods)
+ var/obj/machinery/clonepod/pod = P
+ if(pod.occupant && mind && pod.clonemind == mind)
+ return null
+ if(pod.is_operational && !(pod.occupant || pod.mess))
+ return pod
+
+/obj/machinery/computer/cloning/proc/HasEfficientPod()
+ if(pods)
+ for(var/P in pods)
+ var/obj/machinery/clonepod/pod = P
+ if(pod.is_operational && pod.efficiency > 5)
+ return TRUE
+
+/obj/machinery/computer/cloning/proc/GetAvailableEfficientPod(mind = null)
+ if(pods)
+ for(var/P in pods)
+ var/obj/machinery/clonepod/pod = P
+ if(pod.occupant && pod.clonemind == mind)
+ return pod
+ else if(!. && pod.is_operational && !(pod.occupant || pod.mess) && pod.efficiency > 5)
+ . = pod
+
+/proc/grow_clone_from_record(obj/machinery/clonepod/pod, datum/data/record/R, empty)
+ return pod.growclone(R.fields["name"], R.fields["UI"], R.fields["SE"], R.fields["mindref"], R.fields["last_death"], R.fields["blood_type"], R.fields["mrace"], R.fields["features"], R.fields["mutant_bodyparts"], R.fields["body_markings"], R.fields["factions"], R.fields["quirks"], R.fields["bank_account"], R.fields["traumas"], empty)
+
+/obj/machinery/computer/cloning/process()
+ if(!(scanner && LAZYLEN(pods) && autoprocess))
+ return
+
+ if(scanner.occupant && scanner.scan_level > 2)
+ scan_occupant(scanner.occupant)
+
+ for(var/datum/data/record/R in records)
+ var/obj/machinery/clonepod/pod = GetAvailableEfficientPod(R.fields["mindref"])
+
+ if(!pod)
+ return
+
+ if(pod.occupant)
+ break
+
+ var/result = grow_clone_from_record(pod, R)
+ if(result & CLONING_SUCCESS)
+ temp = "[R.fields["name"]] => Cloning cycle in progress..."
+ log_cloning("Cloning of [key_name(R.fields["mindref"])] automatically started via autoprocess - [src] at [AREACOORD(src)]. Pod: [pod] at [AREACOORD(pod)].")
+ if(result & CLONING_DELETE_RECORD)
+ records -= R
+
+
+/obj/machinery/computer/cloning/proc/updatemodules(findfirstcloner)
+ scanner = findscanner()
+ if(findfirstcloner && !LAZYLEN(pods))
+ findcloner()
+ if(!autoprocess)
+ STOP_PROCESSING(SSmachines, src)
+ else
+ START_PROCESSING(SSmachines, src)
+
+/obj/machinery/computer/cloning/proc/findscanner()
+ var/obj/machinery/dna_scannernew/scannerf = null
+
+ // Loop through every direction
+ for(var/direction in GLOB.cardinals)
+
+ // Try to find a scanner in that direction
+ scannerf = locate(/obj/machinery/dna_scannernew, get_step(src, direction))
+
+ // If found and operational, return the scanner
+ if (!isnull(scannerf) && scannerf.is_operational)
+ return scannerf
+
+ // If no scanner was found, it will return null
+ return null
+
+/obj/machinery/computer/cloning/proc/findcloner()
+ var/obj/machinery/clonepod/podf = null
+
+ for(var/direction in GLOB.cardinals)
+
+ podf = locate(/obj/machinery/clonepod, get_step(src, direction))
+ if (!isnull(podf) && podf.is_operational)
+ AttachCloner(podf)
+
+/obj/machinery/computer/cloning/proc/AttachCloner(obj/machinery/clonepod/pod)
+ if(!pod.connected)
+ pod.connected = src
+ LAZYADD(pods, pod)
+
+/obj/machinery/computer/cloning/proc/DetachCloner(obj/machinery/clonepod/pod)
+ pod.connected = null
+ LAZYREMOVE(pods, pod)
+
+/obj/machinery/computer/cloning/attackby(obj/item/W, mob/user, params)
+ if(istype(W, /obj/item/disk/data)) //INSERT SOME DISKETTES
+ if (!diskette)
+ if (!user.transferItemToLoc(W,src))
+ return
+ diskette = W
+ to_chat(user, "You insert [W].")
+ playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE)
+ updateUsrDialog()
+ else if(W.tool_behaviour == TOOL_MULTITOOL)
+ if(!multitool_check_buffer(user, W))
+ return
+ var/obj/item/multitool/P = W
+
+ if(istype(P.buffer, /obj/machinery/clonepod))
+ 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/clonepod/pod = P.buffer
+ if(pod.connected)
+ pod.connected.DetachCloner(pod)
+ AttachCloner(pod)
+ else
+ P.buffer = src
+ to_chat(user, "-% Successfully stored [REF(P.buffer)] [P.buffer.name] in buffer %-")
+ return
+ else
+ return ..()
+
+/obj/machinery/computer/cloning/ui_interact(mob/living/user)
+ . = ..()
+
+ updatemodules(TRUE)
+
+ var/dat = ""
+ dat += "Refresh"
+
+ if(scanner && HasEfficientPod() && scanner.scan_level >= AUTOCLONING_MINIMAL_LEVEL)
+ if(!autoprocess)
+ dat += "Autoprocess"
+ else
+ dat += "Stop autoprocess"
+ else
+ dat += "Autoprocess"
+ dat += "Cloning Pod Status
"
+ dat += "[temp]
"
+ switch(menu)
+ if(1)
+ // Modules
+ if (isnull(scanner) || !LAZYLEN(pods))
+ dat += "Modules
"
+ //dat += "Reload Modules"
+ if (isnull(scanner))
+ dat += "ERROR: No Scanner detected!
"
+ if (!LAZYLEN(pods))
+ dat += "ERROR: No Pod detected
"
+
+ // Scanner
+ if (!isnull(scanner))
+ var/mob/living/scanner_occupant = get_mob_or_brainmob(scanner.occupant)
+
+ dat += "Scanner Functions
"
+
+ dat += ""
+ if(!scanner_occupant)
+ dat += "Scanner Unoccupied"
+ else if(loading)
+ dat += "[scanner_occupant] => Scanning..."
+ else
+ if(scanner_occupant.ckey != scantemp_ckey)
+ scantemp = "Ready to Scan"
+ scantemp_ckey = scanner_occupant.ckey
+ dat += "[scanner_occupant] => [scantemp]"
+ dat += "
"
+
+ if(scanner_occupant)
+ dat += "Start Scan"
+ dat += "Body-Only Scan"
+ dat += "
[scanner.locked ? "Unlock Scanner" : "Lock Scanner"]"
+ else
+ dat += "Start Scan"
+
+ // Database
+ dat += "Database Functions
"
+ if (records.len && records.len > 0)
+ dat += "View Records ([records.len])
"
+ else
+ dat += "View Records (0)
"
+ if (diskette)
+ dat += "Eject Disk
"
+
+
+
+ if(2)
+ dat += "Current records
"
+ dat += "<< Back
"
+ for(var/datum/data/record/R in records)
+ dat += "[R.fields["name"]]
Scan ID [R.fields["id"]] View Record"
+ if(3)
+ dat += "Selected Record
"
+ dat += "<< Back
"
+
+ if (!active_record)
+ dat += "Record not found."
+ else
+ var/body_only = active_record.fields["body_only"]
+ dat += "[active_record.fields["name"]][body_only ? " - BODY-ONLY" : ""]
"
+ dat += "Scan ID [active_record.fields["id"]] \
+ [!body_only ? "Clone" : "" ]\
+ Empty Clone
"
+
+ var/obj/item/implant/health/H = locate(active_record.fields["imp"])
+
+ if ((H) && (istype(H)))
+ dat += "Health Implant Data:
[H.sensehealth()]
"
+ else
+ dat += "Unable to locate Health Implant.
"
+
+ dat += "Unique Identifier:
[active_record.fields["UI"]]
"
+ dat += "Structural Enzymes:
"
+ for(var/key in active_record.fields["SE"])
+ if(key != RACEMUT)
+ var/val = active_record.fields["SE"][key]
+ var/alias = GLOB.all_mutations[key].alias
+ dat +="[alias]: [val]
"
+
+ dat += "
"
+
+ if(diskette && diskette.fields)
+ dat += ""
+ dat += "
Inserted Disk
"
+ dat += "
Contents: "
+ var/list/L = list()
+ if(diskette.fields["UI"])
+ L += "Unique Identifier"
+ if(diskette.fields["UE"] && diskette.fields["name"] && diskette.fields["blood_type"])
+ L += "Unique Enzymes"
+ if(diskette.fields["SE"])
+ L += "Structural Enzymes"
+ dat += english_list(L, "Empty", " + ", " + ")
+ var/can_load = FALSE
+ var/obj/item/card/id/C = user.get_idcard(TRUE)
+ if(C)
+ if(check_access(C))
+ can_load = TRUE
+ if(can_load)
+ dat += "
Load From Disk"
+ else
+ dat += "
Cannot Load From Disk: Access Denied"
+ if(diskette.fields["SE"])
+ if(!include_se)
+ dat += "
Currently Excluding SE"
+ else
+ dat += "
Currently Including SE"
+ if(diskette.fields["UI"])
+ if(!include_ui)
+ dat += "
Currently Excluding UI"
+ else
+ dat += "
Currently Including UI"
+ if(diskette.fields["UE"])
+ if(!include_ue)
+ dat += "
Currently Excluding UE"
+ else
+ dat += "
Currently Including UE"
+
+
+ dat += "
Save to Disk"
+ dat += "
"
+
+ dat += "Delete Record"
+
+ if(4)
+ if (!active_record)
+ menu = 2
+ ui_interact(user)
+ return
+ dat += "Please confirm.
"
+ dat += "Cancel"
+
+ var/datum/browser/popup = new(user, "cloning", "Cloning System Control")
+ popup.set_content(dat)
+ //popup.set_title_image(user.browse_rsc_icon(icon, icon_state))
+ popup.open()
+
+/obj/machinery/computer/cloning/Topic(href, href_list)
+ if(..())
+ return
+
+ if(loading)
+ return
+
+ if(href_list["task"])
+ switch(href_list["task"])
+ if("autoprocess")
+ if(scanner && HasEfficientPod() && scanner.scan_level >= AUTOCLONING_MINIMAL_LEVEL)
+ autoprocess = TRUE
+ START_PROCESSING(SSmachines, src)
+ playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE)
+ if("stopautoprocess")
+ autoprocess = FALSE
+ STOP_PROCESSING(SSmachines, src)
+ playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE)
+ if("include_se")
+ include_se = TRUE
+ if("exclude_se")
+ include_se = FALSE
+ if("include_ui")
+ include_ui = TRUE
+ if("exclude_ui")
+ include_ui = FALSE
+ if("include_ue")
+ include_ue = TRUE
+ if("exclude_ue")
+ include_ue = FALSE
+
+ else if ((href_list["scan"]) && !isnull(scanner) && scanner.is_operational)
+ scantemp = ""
+ var/body_only = href_list["body_only"]
+ loading = TRUE
+ updateUsrDialog()
+ playsound(src, 'sound/machines/terminal_prompt.ogg', 50, FALSE)
+ say("Initiating scan...")
+
+ addtimer(CALLBACK(src, .proc/do_scan, usr, body_only), 2 SECONDS)
+
+ //No locking an open scanner.
+ else if ((href_list["lock"]) && !isnull(scanner) && scanner.is_operational)
+ if ((!scanner.locked) && (scanner.occupant))
+ scanner.locked = TRUE
+ playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE)
+ else
+ scanner.locked = FALSE
+ playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE)
+
+ else if(href_list["view_rec"])
+ playsound(src, "terminal_type", 25, FALSE)
+ active_record = find_record("id", href_list["view_rec"], records)
+ if(active_record)
+ menu = 3
+ else
+ temp = "Record missing."
+
+ else if (href_list["del_rec"])
+ if ((!active_record) || (menu < 3))
+ return
+ if (menu == 3) //If we are viewing a record, confirm deletion
+ var/has_access = FALSE
+ if(ishuman(usr))
+ var/mob/living/carbon/human/user = usr
+ var/obj/item/card/id/C = user.get_idcard(TRUE)
+ if(C)
+ if(check_access(C))
+ has_access = TRUE
+ if(active_record.fields["body_only"]) //Body-only scans are not as important and can be deleted freely
+ has_access = TRUE
+ if(has_access)
+ temp = "Delete record?"
+ menu = 4
+ playsound(src, 'sound/machines/terminal_prompt.ogg', 50, FALSE)
+ else
+ temp = "Access Denied"
+ menu = 2
+ playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE)
+
+
+ else if (menu == 4)
+ log_cloning("[key_name(usr)] deleted [key_name(active_record.fields["mindref"])]'s cloning records from [src] at [AREACOORD(src)].")
+ temp = "[active_record.fields["name"]] => Record deleted."
+ records.Remove(active_record)
+ active_record = null
+ playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE)
+ menu = 2
+
+ else if (href_list["disk"]) //Load or eject.
+ switch(href_list["disk"])
+ if("load")
+
+
+ if (!diskette || !istype(diskette.fields))
+ temp = "Load error."
+ updateUsrDialog()
+ playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE)
+ return
+ if (!active_record)
+ temp = "Record error."
+ menu = 1
+ updateUsrDialog()
+ playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE)
+ return
+
+ if(include_ue)
+ overwrite_field_if_available(active_record, diskette, "UE")
+ overwrite_field_if_available(active_record, diskette, "name")
+ overwrite_field_if_available(active_record, diskette, "blood_type")
+ if(include_ui)
+ overwrite_field_if_available(active_record, diskette, "UI")
+ if(include_se)
+ overwrite_field_if_available(active_record, diskette, "SE")
+
+ log_cloning("[key_name(usr)] uploaded [key_name(active_record.fields["mindref"])]'s cloning records to [src] at [AREACOORD(src)] via [diskette].")
+ temp = "Load successful."
+ playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE)
+
+ if("eject")
+ if(diskette)
+ diskette.forceMove(drop_location())
+ diskette = null
+ playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE)
+ if("save")
+ if(!diskette || diskette.read_only || !active_record || !active_record.fields)
+ temp = "Save error."
+ updateUsrDialog()
+ playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE)
+ return
+
+ log_cloning("[key_name(usr)] added [key_name(active_record.fields["mindref"])]'s cloning records to [diskette] via [src] at [AREACOORD(src)].")
+ diskette.fields = active_record.fields.Copy()
+ diskette.name = "data disk - '[diskette.fields["name"]]'"
+ temp = "Save successful."
+ playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE)
+
+ else if (href_list["refresh"])
+ updateUsrDialog()
+ playsound(src, "terminal_type", 25, FALSE)
+
+ else if (href_list["clone"])
+ var/datum/data/record/C = find_record("id", href_list["clone"], records)
+ var/empty = href_list["empty"]
+ //Look for that player! They better be dead!
+ if(C)
+ if(C.fields["body_only"] && !empty)
+ temp = "Cannot initiate regular cloning with body-only scans."
+ playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE)
+ var/obj/machinery/clonepod/pod = GetAvailablePod()
+ var/success = FALSE
+ //Can't clone without someone to clone. Or a pod. Or if the pod is busy. Or full of gibs.
+ if(!LAZYLEN(pods))
+ temp = "No Clonepods detected."
+ playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE)
+ else if(!pod)
+ temp = "No Clonepods available."
+ playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE)
+ else if(pod.occupant)
+ temp = "Cloning cycle already in progress."
+ playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE)
+ else
+ var/result = grow_clone_from_record(pod, C, empty)
+ if(result & CLONING_SUCCESS)
+ temp = "[C.fields["name"]] => Cloning cycle in progress..."
+ playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE)
+ if(active_record == C)
+ active_record = null
+ menu = 1
+ success = TRUE
+ if(!empty)
+ log_cloning("[key_name(usr)] initiated cloning of [key_name(C.fields["mindref"])] via [src] at [AREACOORD(src)]. Pod: [pod] at [AREACOORD(pod)].")
+ else
+ log_cloning("[key_name(usr)] initiated EMPTY cloning of [key_name(C.fields["mindref"])] via [src] at [AREACOORD(src)]. Pod: [pod] at [AREACOORD(pod)].")
+ if(result & CLONING_DELETE_RECORD)
+ if(active_record == C)
+ active_record = null
+ menu = 1
+ records -= C
+
+ if(!success)
+ temp = "[C.fields["name"]] => Initialisation failure."
+ playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE)
+
+ else
+ temp = "Data corruption."
+ playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE)
+
+ else if (href_list["menu"])
+ menu = text2num(href_list["menu"])
+ playsound(src, "terminal_type", 25, FALSE)
+
+ add_fingerprint(usr)
+ updateUsrDialog()
+ return
+
+/obj/machinery/computer/cloning/proc/do_scan(mob/user, body_only)
+ scan_occupant(scanner.occupant, user, body_only)
+
+ loading = FALSE
+ updateUsrDialog()
+ playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE)
+
+/obj/machinery/computer/cloning/proc/scan_occupant(occupant, mob/M, body_only)
+ var/mob/living/mob_occupant = get_mob_or_brainmob(occupant)
+ var/datum/dna/dna
+ var/datum/bank_account/has_bank_account
+
+ // Do not use unless you know what they are.
+ var/mob/living/carbon/C = mob_occupant
+ var/mob/living/brain/B = mob_occupant
+
+ if(ishuman(mob_occupant))
+ dna = C.has_dna()
+ var/obj/item/card/id/I = C.get_idcard(TRUE)
+ if(I)
+ has_bank_account = I.registered_account
+ if(isbrain(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, FALSE)
+ return
+ if(!body_only && (mob_occupant.suiciding))
+ scantemp = "Subject's brain is not responding to scanning stimuli."
+ playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE)
+ return
+ if((HAS_TRAIT(mob_occupant, TRAIT_HUSK)) && (src.scanner.scan_level < 2))
+ scantemp = "Subject's body is too damaged to scan properly."
+ playsound(src, 'sound/machines/terminal_alert.ogg', 50, FALSE)
+ return
+ if(HAS_TRAIT(mob_occupant, TRAIT_BADDNA))
+ scantemp = "Subject's DNA is damaged beyond any hope of recovery."
+ playsound(src, 'sound/machines/terminal_alert.ogg', 50, FALSE)
+ return
+ if (!body_only && isnull(mob_occupant.mind))
+ scantemp = "Mental interface failure."
+ playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE)
+ return
+ if(!body_only && SSeconomy.full_ancap)
+ if(!has_bank_account)
+ scantemp = "Subject is either missing an ID card with a bank account on it, or does not have an account to begin with. Please ensure the ID card is on the body before attempting to scan."
+ playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE)
+ return
+ var/datum/data/record/R = new()
+ if(dna.species)
+ // We store the instance rather than the path, because some
+ // species (abductors, slimepeople) store state in their
+ // species datums
+ dna.delete_species = FALSE
+ R.fields["mrace"] = dna.species
+ else
+ var/datum/species/rando_race = pick(GLOB.roundstart_races)
+ R.fields["mrace"] = rando_race.type
+
+ R.fields["name"] = mob_occupant.real_name
+ R.fields["id"] = copytext_char(md5(mob_occupant.real_name), 2, 6)
+ R.fields["UE"] = dna.unique_enzymes
+ R.fields["UI"] = dna.uni_identity
+ R.fields["SE"] = dna.mutation_index
+ R.fields["blood_type"] = dna.blood_type
+ R.fields["features"] = dna.features
+ R.fields["mutant_bodyparts"] = dna.mutant_bodyparts
+ R.fields["body_markings"] = dna.body_markings
+ R.fields["factions"] = mob_occupant.faction
+ R.fields["quirks"] = list()
+ for(var/V in mob_occupant.roundstart_quirks)
+ var/datum/quirk/T = V
+ R.fields["quirks"][T.type] = T.clone_data()
+
+ R.fields["traumas"] = list()
+ if(ishuman(mob_occupant))
+ R.fields["traumas"] = C.get_traumas()
+ if(isbrain(mob_occupant))
+ R.fields["traumas"] = B.get_traumas()
+
+ R.fields["bank_account"] = has_bank_account
+ R.fields["mindref"] = "[REF(mob_occupant.mind)]"
+ R.fields["last_death"] = mob_occupant.stat == DEAD && mob_occupant.mind ? mob_occupant.mind.last_death : -1
+ R.fields["body_only"] = body_only
+
+ if(!body_only)
+ //Add an implant if needed
+ var/obj/item/implant/health/imp
+ for(var/obj/item/implant/health/HI in mob_occupant.implants)
+ imp = HI
+ break
+ if(!imp)
+ imp = new /obj/item/implant/health(mob_occupant)
+ imp.implant(mob_occupant)
+ R.fields["imp"] = "[REF(imp)]"
+
+ var/datum/data/record/old_record = find_record("mindref", REF(mob_occupant.mind), records)
+ if(body_only)
+ old_record = find_record("UE", dna.unique_enzymes, records) //Body-only records cannot be identified by mind, so we use the DNA
+ if(old_record && ((old_record.fields["UI"] != dna.uni_identity) || (!old_record.fields["body_only"]))) //Never overwrite a mind-and-body record if it exists
+ old_record = null
+ if(old_record)
+ records -= old_record
+ scantemp = "Record updated."
+ else
+ scantemp = "Subject successfully scanned."
+ records += R
+ log_cloning("[M ? key_name(M) : "Autoprocess"] added the [body_only ? "body-only " : ""]record of [key_name(mob_occupant)] to [src] at [AREACOORD(src)].")
+ playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50)
diff --git a/modular_skyrat/modules/cloning/code/game/machinery/exp_cloner.dm b/modular_skyrat/modules/cloning/code/game/machinery/exp_cloner.dm
new file mode 100644
index 00000000000..f87f8d8d3ff
--- /dev/null
+++ b/modular_skyrat/modules/cloning/code/game/machinery/exp_cloner.dm
@@ -0,0 +1,306 @@
+//Experimental cloner; clones a body regardless of the owner's status, letting a ghost control it instead
+/obj/machinery/clonepod/experimental
+ name = "experimental cloning pod"
+ desc = "An ancient cloning pod. It seems to be an early prototype of the experimental cloners used in Nanotrasen Stations."
+ icon = 'modular_skyrat/modules/cloning/icons/obj/machines/cloning.dmi'
+ icon_state = "pod_0"
+ req_access = null
+ circuit = /obj/item/circuitboard/machine/clonepod/experimental
+ internal_radio = FALSE
+
+//Start growing a human clone in the pod!
+/obj/machinery/clonepod/experimental/growclone(clonename, ui, mutation_index, mindref, last_death, blood_type, datum/species/mrace, list/features, list/mutantparts, list/markings, factions, list/quirks, datum/bank_account/insurance, list/traumas, empty)
+ if(panel_open)
+ return NONE
+ if(mess || attempting)
+ return NONE
+
+ attempting = TRUE //One at a time!!
+ countdown.start()
+
+ var/mob/living/carbon/human/H = new /mob/living/carbon/human(src)
+ H.hardset_dna(ui, mutation_index, H.dna.default_mutation_genes, H.real_name, blood_type, mrace, features, H.dna.mutations, FALSE)
+ H.set_species(mrace, TRUE, null, features, mutantparts, markings)
+
+ 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.easy_randmut(POSITIVE)
+ if(efficiency < 3 && prob(50))
+ var/mob/M = H.easy_randmut(NEGATIVE+MINOR_NEGATIVE)
+ 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(1,999)])"
+ H.real_name = clonename
+
+ //Get the clone body ready
+ maim_clone(H)
+ ADD_TRAIT(H, TRAIT_STABLEHEART, CLONING_POD_TRAIT)
+ ADD_TRAIT(H, TRAIT_STABLELIVER, CLONING_POD_TRAIT)
+ ADD_TRAIT(H, TRAIT_EMOTEMUTE, CLONING_POD_TRAIT)
+ ADD_TRAIT(H, TRAIT_MUTE, CLONING_POD_TRAIT)
+ ADD_TRAIT(H, TRAIT_NOBREATH, CLONING_POD_TRAIT)
+ ADD_TRAIT(H, TRAIT_NOCRITDAMAGE, CLONING_POD_TRAIT)
+ H.Unconscious(80)
+
+ var/list/candidates = pollCandidatesForMob("Do you want to play as [clonename]'s defective clone?", null, null, null, 100, H, POLL_IGNORE_DEFECTIVECLONE)
+ if(LAZYLEN(candidates))
+ var/mob/dead/observer/C = pick(candidates)
+ H.key = C.key
+
+ 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.set_suicide(FALSE)
+ attempting = FALSE
+ update_icon()
+ return CLONING_DELETE_RECORD | CLONING_SUCCESS //so that we don't spam clones with autoprocess unless we leave a body in the scanner
+
+
+//Prototype cloning console, much more rudimental and lacks modern functions such as saving records, autocloning, or safety checks.
+/obj/machinery/computer/prototype_cloning
+ name = "prototype cloning console"
+ desc = "Used to operate an experimental cloner."
+ icon_screen = "dna"
+ icon_keyboard = "med_key"
+ circuit = /obj/item/circuitboard/computer/prototype_cloning
+ var/obj/machinery/dna_scannernew/scanner = null //Linked scanner. For scanning.
+ var/list/pods //Linked experimental cloning pods
+ var/temp = "Inactive"
+ var/scantemp = "Ready to Scan"
+ var/loading = FALSE // Nice loading text
+
+ light_color = LIGHT_COLOR_BLUE
+
+/obj/machinery/computer/prototype_cloning/Initialize()
+ . = ..()
+ updatemodules(TRUE)
+
+/obj/machinery/computer/prototype_cloning/Destroy()
+ if(pods)
+ for(var/P in pods)
+ DetachCloner(P)
+ pods = null
+ return ..()
+
+/obj/machinery/computer/prototype_cloning/proc/GetAvailablePod(mind = null)
+ if(pods)
+ for(var/P in pods)
+ var/obj/machinery/clonepod/experimental/pod = P
+ if(pod.is_operational && !(pod.occupant || pod.mess))
+ return pod
+
+/obj/machinery/computer/prototype_cloning/proc/updatemodules(findfirstcloner)
+ scanner = findscanner()
+ if(findfirstcloner && !LAZYLEN(pods))
+ findcloner()
+
+/obj/machinery/computer/prototype_cloning/proc/findscanner()
+ var/obj/machinery/dna_scannernew/scannerf = null
+
+ // Loop through every direction
+ for(var/direction in GLOB.cardinals)
+ // Try to find a scanner in that direction
+ scannerf = locate(/obj/machinery/dna_scannernew, get_step(src, direction))
+ // If found and operational, return the scanner
+ if (!isnull(scannerf) && scannerf.is_operational)
+ return scannerf
+
+ // If no scanner was found, it will return null
+ return null
+
+/obj/machinery/computer/prototype_cloning/proc/findcloner()
+ var/obj/machinery/clonepod/experimental/podf = null
+ for(var/direction in GLOB.cardinals)
+ podf = locate(/obj/machinery/clonepod/experimental, get_step(src, direction))
+ if (!isnull(podf) && podf.is_operational)
+ AttachCloner(podf)
+
+/obj/machinery/computer/prototype_cloning/proc/AttachCloner(obj/machinery/clonepod/experimental/pod)
+ if(!pod.connected)
+ pod.connected = src
+ LAZYADD(pods, pod)
+
+/obj/machinery/computer/prototype_cloning/proc/DetachCloner(obj/machinery/clonepod/experimental/pod)
+ pod.connected = null
+ LAZYREMOVE(pods, pod)
+
+/obj/machinery/computer/prototype_cloning/attackby(obj/item/W, mob/user, params)
+ if(W.tool_behaviour == TOOL_MULTITOOL)
+ if(!multitool_check_buffer(user, W))
+ return
+ var/obj/item/multitool/P = W
+
+ if(istype(P.buffer, /obj/machinery/clonepod/experimental))
+ 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/clonepod/experimental/pod = P.buffer
+ if(pod.connected)
+ pod.connected.DetachCloner(pod)
+ AttachCloner(pod)
+ else
+ P.buffer = src
+ to_chat(user, "-% Successfully stored [REF(P.buffer)] [P.buffer.name] in buffer %-")
+ return
+ else
+ return ..()
+
+/obj/machinery/computer/prototype_cloning/attack_hand(mob/user)
+ if(..())
+ return
+ interact(user)
+
+/obj/machinery/computer/prototype_cloning/interact(mob/user)
+ user.set_machine(src)
+ add_fingerprint(user)
+
+ if(..())
+ return
+
+ updatemodules(TRUE)
+
+ var/dat = ""
+ dat += "Refresh"
+
+ dat += "Cloning Pod Status
"
+ dat += "[temp]
"
+
+ if (isnull(src.scanner) || !LAZYLEN(pods))
+ dat += "Modules
"
+ //dat += "Reload Modules"
+ if (isnull(src.scanner))
+ dat += "ERROR: No Scanner detected!
"
+ if (!LAZYLEN(pods))
+ dat += "ERROR: No Pod detected
"
+
+ // Scan-n-Clone
+ if (!isnull(src.scanner))
+ var/mob/living/scanner_occupant = get_mob_or_brainmob(scanner.occupant)
+
+ dat += "Cloning
"
+
+ dat += ""
+ if(!scanner_occupant)
+ dat += "Scanner Unoccupied"
+ else if(loading)
+ dat += "[scanner_occupant] => Scanning..."
+ else
+ scantemp = "Ready to Clone"
+ dat += "[scanner_occupant] => [scantemp]"
+ dat += "
"
+
+ if(scanner_occupant)
+ dat += "Clone"
+ dat += "
[src.scanner.locked ? "Unlock Scanner" : "Lock Scanner"]"
+ else
+ dat += "Clone"
+
+ var/datum/browser/popup = new(user, "cloning", "Prototype Cloning System Control")
+ popup.set_content(dat)
+ //popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state))
+ popup.open()
+
+/obj/machinery/computer/prototype_cloning/Topic(href, href_list)
+ if(..())
+ return
+
+ if(loading)
+ return
+
+ else if ((href_list["clone"]) && !isnull(scanner) && scanner.is_operational)
+ scantemp = ""
+
+ loading = TRUE
+ updateUsrDialog()
+ playsound(src, 'sound/machines/terminal_prompt.ogg', 50, FALSE)
+ say("Initiating scan...")
+
+ addtimer(CALLBACK(src, .proc/do_clone), 2 SECONDS)
+
+ //No locking an open scanner.
+ else if ((href_list["lock"]) && !isnull(scanner) && scanner.is_operational)
+ if ((!scanner.locked) && (scanner.occupant))
+ scanner.locked = TRUE
+ playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE)
+ else
+ scanner.locked = FALSE
+ playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE)
+
+ else if (href_list["refresh"])
+ updateUsrDialog()
+ playsound(src, "terminal_type", 25, FALSE)
+
+ add_fingerprint(usr)
+ updateUsrDialog()
+ return
+
+/obj/machinery/computer/prototype_cloning/proc/do_clone()
+ clone_occupant(scanner.occupant)
+ loading = FALSE
+ updateUsrDialog()
+ playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE)
+
+/obj/machinery/computer/prototype_cloning/proc/clone_occupant(occupant)
+ var/mob/living/mob_occupant = get_mob_or_brainmob(occupant)
+ var/datum/dna/dna
+ if(ishuman(mob_occupant))
+ var/mob/living/carbon/C = mob_occupant
+ dna = C.has_dna()
+ if(isbrain(mob_occupant))
+ 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, FALSE)
+ return
+ if((HAS_TRAIT(mob_occupant, TRAIT_HUSK)) && (src.scanner.scan_level < 2))
+ scantemp = "Subject's body is too damaged to scan properly."
+ playsound(src, 'sound/machines/terminal_alert.ogg', 50, FALSE)
+ return
+ if(HAS_TRAIT(mob_occupant, TRAIT_BADDNA))
+ scantemp = "Subject's DNA is damaged beyond any hope of recovery."
+ playsound(src, 'sound/machines/terminal_alert.ogg', 50, FALSE)
+ return
+
+ var/clone_species
+ if(dna.species)
+ clone_species = dna.species
+ else
+ var/datum/species/rando_race = pick(GLOB.roundstart_races)
+ clone_species = rando_race.type
+
+ var/obj/machinery/clonepod/pod = GetAvailablePod()
+ //Can't clone without someone to clone. Or a pod. Or if the pod is busy. Or full of gibs.
+ if(!LAZYLEN(pods))
+ temp = "No Clonepods detected."
+ playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE)
+ else if(!pod)
+ temp = "No Clonepods available."
+ playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE)
+ else if(pod.occupant)
+ temp = "Cloning cycle already in progress."
+ playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE)
+ else
+ pod.growclone(mob_occupant.real_name, dna.uni_identity, dna.mutation_index, null, null, dna.blood_type, clone_species, dna.features, dna.mutant_bodyparts, dna.body_markings, mob_occupant.faction)
+ temp = "[mob_occupant.real_name] => Cloning data sent to pod."
+ playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE)
diff --git a/modular_skyrat/modules/cloning/code/game/objects/effects/countdown.dm b/modular_skyrat/modules/cloning/code/game/objects/effects/countdown.dm
new file mode 100644
index 00000000000..0f996de7938
--- /dev/null
+++ b/modular_skyrat/modules/cloning/code/game/objects/effects/countdown.dm
@@ -0,0 +1,12 @@
+/obj/effect/countdown/clonepod
+ name = "cloning pod countdown"
+ color = "#18d100"
+ text_size = 1
+
+/obj/effect/countdown/clonepod/get_value()
+ var/obj/machinery/clonepod/C = attached_to
+ if(!istype(C))
+ return
+ else if(C.occupant)
+ var/completion = round(C.get_completion())
+ return completion
diff --git a/modular_skyrat/modules/cloning/code/game/objects/effects/spawners/lootdrop.dm b/modular_skyrat/modules/cloning/code/game/objects/effects/spawners/lootdrop.dm
new file mode 100644
index 00000000000..df403ebea21
--- /dev/null
+++ b/modular_skyrat/modules/cloning/code/game/objects/effects/spawners/lootdrop.dm
@@ -0,0 +1,6 @@
+/obj/effect/spawner/lootdrop/techstorage/medical/New()
+ . = ..()
+ loot += list(/obj/item/circuitboard/machine/dnascanner,
+ /obj/item/circuitboard/computer/cloning,
+ /obj/item/circuitboard/machine/clonepod
+ )
diff --git a/modular_skyrat/modules/cloning/code/game/objects/items/circuitboards/computer_circuitboards.dm b/modular_skyrat/modules/cloning/code/game/objects/items/circuitboards/computer_circuitboards.dm
new file mode 100644
index 00000000000..702add5c450
--- /dev/null
+++ b/modular_skyrat/modules/cloning/code/game/objects/items/circuitboards/computer_circuitboards.dm
@@ -0,0 +1,9 @@
+/obj/item/circuitboard/computer/cloning
+ name = "Cloning (Computer Board)"
+ icon_state = "medical"
+ build_path = /obj/machinery/computer/cloning
+
+/obj/item/circuitboard/computer/prototype_cloning
+ name = "Prototype Cloning (Computer Board)"
+ icon_state = "medical"
+ build_path = /obj/machinery/computer/prototype_cloning
diff --git a/modular_skyrat/modules/cloning/code/game/objects/items/circuitboards/machine_circuitboards.dm b/modular_skyrat/modules/cloning/code/game/objects/items/circuitboards/machine_circuitboards.dm
new file mode 100644
index 00000000000..5f18d68f9de
--- /dev/null
+++ b/modular_skyrat/modules/cloning/code/game/objects/items/circuitboards/machine_circuitboards.dm
@@ -0,0 +1,14 @@
+/obj/item/circuitboard/machine/clonepod
+ name = "Clone Pod (Machine Board)"
+ icon_state = "medical"
+ build_path = /obj/machinery/clonepod
+ req_components = list(
+ /obj/item/stack/cable_coil = 2,
+ /obj/item/stock_parts/scanning_module = 2,
+ /obj/item/stock_parts/manipulator = 2,
+ /obj/item/stack/sheet/glass = 1)
+
+/obj/item/circuitboard/machine/clonepod/experimental
+ name = "Experimental Clone Pod (Machine Board)"
+ icon_state = "medical"
+ build_path = /obj/machinery/clonepod/experimental
diff --git a/modular_skyrat/modules/cloning/code/game/objects/items/manuals.dm b/modular_skyrat/modules/cloning/code/game/objects/items/manuals.dm
new file mode 100644
index 00000000000..a06e16b9ecc
--- /dev/null
+++ b/modular_skyrat/modules/cloning/code/game/objects/items/manuals.dm
@@ -0,0 +1,6 @@
+/obj/item/book/manual/wiki/medical_cloning
+ name = "Cloning techniques of the 26th century"
+ icon_state ="bookCloning"
+ author = "Medical Journal, volume 3"
+ title = "Cloning techniques of the 26th century"
+ page_link = "Guide_to_genetics#Cloning"
diff --git a/modular_skyrat/modules/cloning/code/modules/atmospherics/gasmixtures/immutable_mixtures.dm b/modular_skyrat/modules/cloning/code/modules/atmospherics/gasmixtures/immutable_mixtures.dm
new file mode 100644
index 00000000000..cecbc05d1ed
--- /dev/null
+++ b/modular_skyrat/modules/cloning/code/modules/atmospherics/gasmixtures/immutable_mixtures.dm
@@ -0,0 +1,11 @@
+//used by cloners
+/datum/gas_mixture/immutable/cloner
+ initial_temperature = T20C
+
+/datum/gas_mixture/immutable/cloner/garbage_collect()
+ ..()
+ ADD_GAS(/datum/gas/nitrogen, gases)
+ 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
diff --git a/modular_skyrat/modules/cloning/code/modules/jobs/access.dm b/modular_skyrat/modules/cloning/code/modules/jobs/access.dm
new file mode 100644
index 00000000000..3b8c038d462
--- /dev/null
+++ b/modular_skyrat/modules/cloning/code/modules/jobs/access.dm
@@ -0,0 +1,19 @@
+/get_all_accesses()
+ . = ..()
+ . += ACCESS_CLONING
+
+/get_region_accesses(code)
+ . = ..()
+ if(code == 3)
+ . += ACCESS_CLONING
+ if(.)
+ return .
+
+/get_access_desc(A)
+ . = ..()
+ if(.)
+ return .
+ switch(A)
+ if(ACCESS_CLONING)
+ return "Cloning Room"
+
diff --git a/modular_skyrat/modules/cloning/code/modules/jobs/job_types/chemist.dm b/modular_skyrat/modules/cloning/code/modules/jobs/job_types/chemist.dm
new file mode 100644
index 00000000000..0b75a2ddfb3
--- /dev/null
+++ b/modular_skyrat/modules/cloning/code/modules/jobs/job_types/chemist.dm
@@ -0,0 +1,3 @@
+/datum/job/chemist/New()
+ access += ACCESS_CLONING
+ . = ..()
diff --git a/modular_skyrat/modules/cloning/code/modules/jobs/job_types/chief_medical_officer.dm b/modular_skyrat/modules/cloning/code/modules/jobs/job_types/chief_medical_officer.dm
new file mode 100644
index 00000000000..4179c93c4da
--- /dev/null
+++ b/modular_skyrat/modules/cloning/code/modules/jobs/job_types/chief_medical_officer.dm
@@ -0,0 +1,4 @@
+/datum/job/cmo/New()
+ access += ACCESS_CLONING
+ minimal_access += ACCESS_CLONING
+ . = ..()
diff --git a/modular_skyrat/modules/cloning/code/modules/jobs/job_types/geneticist.dm b/modular_skyrat/modules/cloning/code/modules/jobs/job_types/geneticist.dm
new file mode 100644
index 00000000000..29a5ed912cf
--- /dev/null
+++ b/modular_skyrat/modules/cloning/code/modules/jobs/job_types/geneticist.dm
@@ -0,0 +1,4 @@
+/datum/job/geneticist/New()
+ access += ACCESS_CLONING
+ minimal_access += ACCESS_CLONING
+ . = ..()
diff --git a/modular_skyrat/modules/cloning/code/modules/jobs/job_types/medical_doctor.dm b/modular_skyrat/modules/cloning/code/modules/jobs/job_types/medical_doctor.dm
new file mode 100644
index 00000000000..a135766f864
--- /dev/null
+++ b/modular_skyrat/modules/cloning/code/modules/jobs/job_types/medical_doctor.dm
@@ -0,0 +1,4 @@
+/datum/job/doctor/New()
+ access += ACCESS_CLONING
+ minimal_access += ACCESS_CLONING
+ . = ..()
diff --git a/modular_skyrat/modules/cloning/code/modules/jobs/job_types/paramedic.dm b/modular_skyrat/modules/cloning/code/modules/jobs/job_types/paramedic.dm
new file mode 100644
index 00000000000..39d62047d94
--- /dev/null
+++ b/modular_skyrat/modules/cloning/code/modules/jobs/job_types/paramedic.dm
@@ -0,0 +1,4 @@
+/datum/job/paramedic/New()
+ access += ACCESS_CLONING
+ minimal_access += ACCESS_CLONING
+ . = ..()
diff --git a/modular_skyrat/modules/cloning/code/modules/jobs/job_types/virologist.dm b/modular_skyrat/modules/cloning/code/modules/jobs/job_types/virologist.dm
new file mode 100644
index 00000000000..114c14cc78d
--- /dev/null
+++ b/modular_skyrat/modules/cloning/code/modules/jobs/job_types/virologist.dm
@@ -0,0 +1,3 @@
+/datum/job/virologist/New()
+ access += ACCESS_CLONING
+ . = ..()
diff --git a/modular_skyrat/modules/cloning/code/modules/library/lib_items.dm b/modular_skyrat/modules/cloning/code/modules/library/lib_items.dm
new file mode 100644
index 00000000000..3fa318c9e71
--- /dev/null
+++ b/modular_skyrat/modules/cloning/code/modules/library/lib_items.dm
@@ -0,0 +1,7 @@
+/obj/structure/bookcase/manuals/medical
+ name = "medical manuals bookcase"
+
+/obj/structure/bookcase/manuals/medical/Initialize()
+ . = ..()
+ new /obj/item/book/manual/wiki/medical_cloning(src)
+ update_icon()
diff --git a/modular_skyrat/modules/cloning/code/modules/mob/living/brain/brain_item.dm b/modular_skyrat/modules/cloning/code/modules/mob/living/brain/brain_item.dm
new file mode 100644
index 00000000000..e00ee2ee4dc
--- /dev/null
+++ b/modular_skyrat/modules/cloning/code/modules/mob/living/brain/brain_item.dm
@@ -0,0 +1,5 @@
+/obj/item/organ/brain/transfer_identity(mob/living/L)
+ . = ..()
+ var/obj/item/organ/zombie_infection/ZI = L.getorganslot(ORGAN_SLOT_ZOMBIE)
+ if(ZI)
+ brainmob.set_species(ZI.old_species) //For if the brain is cloned
diff --git a/modular_skyrat/modules/cloning/code/modules/research/designs/comp_board_designs.dm b/modular_skyrat/modules/cloning/code/modules/research/designs/comp_board_designs.dm
new file mode 100644
index 00000000000..4203a311db2
--- /dev/null
+++ b/modular_skyrat/modules/cloning/code/modules/research/designs/comp_board_designs.dm
@@ -0,0 +1,7 @@
+/datum/design/board/clonecontrol
+ name = "Computer Design (Cloning Console)"
+ desc = "Allows for the construction of circuit boards used to build a new Cloning console."
+ id = "clonecontrol"
+ build_path = /obj/item/circuitboard/computer/cloning
+ category = list("Computer Boards")
+ departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
diff --git a/modular_skyrat/modules/cloning/code/modules/research/designs/machine_designs.dm b/modular_skyrat/modules/cloning/code/modules/research/designs/machine_designs.dm
new file mode 100644
index 00000000000..dde3260ab69
--- /dev/null
+++ b/modular_skyrat/modules/cloning/code/modules/research/designs/machine_designs.dm
@@ -0,0 +1,7 @@
+/datum/design/board/clonepod
+ name = "Machine Design (Cloning Pod)"
+ desc = "Allows for the construction of circuit boards used to build a new cloning pod."
+ id = "clonepod"
+ build_path = /obj/item/circuitboard/machine/clonepod
+ category = list("Machine Boards")
+ departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
diff --git a/modular_skyrat/modules/cloning/code/modules/research/techweb/all_nodes.dm b/modular_skyrat/modules/cloning/code/modules/research/techweb/all_nodes.dm
new file mode 100644
index 00000000000..362ae8ed38b
--- /dev/null
+++ b/modular_skyrat/modules/cloning/code/modules/research/techweb/all_nodes.dm
@@ -0,0 +1,7 @@
+/datum/techweb_node/cloning
+ id = "cloning"
+ display_name = "Organic Replication(cloning)"
+ description = "We have the technology to MAKE him."
+ prereq_ids = list("biotech", "adv_biotech", "genetics")
+ design_ids = list("clonecontrol", "clonepod")
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 5000)
diff --git a/modular_skyrat/modules/cloning/code/modules/ruins/spaceruin_code/cloning_lab.dm b/modular_skyrat/modules/cloning/code/modules/ruins/spaceruin_code/cloning_lab.dm
new file mode 100644
index 00000000000..338f62ee779
--- /dev/null
+++ b/modular_skyrat/modules/cloning/code/modules/ruins/spaceruin_code/cloning_lab.dm
@@ -0,0 +1,35 @@
+/obj/item/paper/fluff/ruins/exp_cloning/manual
+ name = "paper - 'H-11 Cloning Apparatus Manual"
+ info = {"Getting Started
+ Congratulations, you are testing the H-11 experimental cloning device!
+ Using the H-11 is almost as simple as brain surgery! Simply insert the target humanoid into the scanning chamber and select the clone option to initiate cloning!
+ 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.
+ The provided CLONEPOD SYSTEM will produce the desired clone. Standard clone maturation times are roughly 90 seconds.
+ The cloning pod may be unlocked early after initial maturation is complete.
+ Please note that resulting clones will have a DEVELOPMENTAL DEFECT as a result of genetic drift. We hope to reduce this through further testing.
+ Clones may also experience memory loss and radical changes in personality as a result of the cloning process.
+
+ This technology produced under license from Thinktronic Systems, LTD."}
+
+/obj/item/paper/fluff/ruins/exp_cloning/log
+ name = "experiment log"
+ info = {"Day 1
+ We are very excited to be part of the first crew of the SC Irmanda!
+ This ship is made to test an innovative FTL technology. I had some concerns at first, \
+ but the engineers assure me that it is safe and there is absolutely no risk of the external wings breaking off from the acceleration.
+ We've been tasked with testing the latest model of the Thinktronic Cloning Pod. We'll stay in dock for a week before launching, but we're going to get started right away. \
+ If the engine is as fast as they say, we might not have the time to run all the routine tests on the cloned subject!
+
+ Day 2
+ We cloned an unknown corpse that was given to us by the medical crew. The genetic replication is good enough to let the subject survive outside of the pod, \
+ but the cellular damage remains a concern for his long-term survival. For safety we will be keeping him in quarantine.
+ We left him some books, but clearly we were too optimistic about his mental faculties. His brain seems to suffer from the same cloning decay that was caused by \
+ the previous models. We will run further tests to see if there are improvements.
+ Day 4
+ It seems we'll be launching even sooner than expected! Apparently the press is starting to lose interest, so we have to cut short the pre-flight checks \
+ and give them something to talk about. Hopefully this will end up with increased funding...
+ The crew has all been invited to the main hall, where we have seats for the initial FTL acceleration. Unfortunately the clone cannot leave the quarantine room \
+ without risking infection, so we will strap him into the bed and hope for the best. We can grow another clone if anything goes wrong, anyway.
+
+ Professor Galen Linkovich"}
diff --git a/modular_skyrat/modules/cloning/icons/obj/machines/cloning.dmi b/modular_skyrat/modules/cloning/icons/obj/machines/cloning.dmi
new file mode 100644
index 00000000000..3b2a41df671
Binary files /dev/null and b/modular_skyrat/modules/cloning/icons/obj/machines/cloning.dmi differ
diff --git a/modular_skyrat/modules/cloning/icons/obj/machines/cloning_pod.dmi b/modular_skyrat/modules/cloning/icons/obj/machines/cloning_pod.dmi
new file mode 100644
index 00000000000..0deb59aff8e
Binary files /dev/null and b/modular_skyrat/modules/cloning/icons/obj/machines/cloning_pod.dmi differ
diff --git a/modular_skyrat/modules/cloning/icons/obj/machines/cryo_mobs.dmi b/modular_skyrat/modules/cloning/icons/obj/machines/cryo_mobs.dmi
new file mode 100644
index 00000000000..427c9cd79b4
Binary files /dev/null and b/modular_skyrat/modules/cloning/icons/obj/machines/cryo_mobs.dmi differ
diff --git a/modular_skyrat/modules/cloning/readme.md b/modular_skyrat/modules/cloning/readme.md
new file mode 100644
index 00000000000..9a62d7d8479
--- /dev/null
+++ b/modular_skyrat/modules/cloning/readme.md
@@ -0,0 +1,43 @@
+https://github.com/Skyrat-SS13/Skyrat-tg/pull/151
+
+## Title: Cloning
+
+MODULE ID: CLONING
+
+### Description:
+
+Enables the crew to clone dead bodies via the cloning scanner.
+
+### TG Proc Changes:
+
+- code/_globalvars/lists/poll_ignore.dm > GLOBAL_LIST_INIT(poll_ignore_desc)
+- code/datums/dna.dm > /datum/dna/Destroy()
+- code/datums/shuttles.dm > /datum/map_template/shuttle/emergency/zeta
+- code/modules/antagonists/revenant/revenant_abilities.dm > /obj/effect/proc_holder/spell/aoe_turf/revenant/malfunction/proc/malfunction()
+- code/modules/clothing/head/misc_special.dm > /obj/item/clothing/head/foilhat/equipped()
+
+### Defines:
+
+- #define ACCESS_CLONING 68
+- #define CLONER_FRESH_CLONE "fresh"
+- #define CLONER_MATURE_CLONE "mature"
+- #define CLONING_SUCCESS (1<<0)
+- #define CLONING_DELETE_RECORD (1<<1)
+- #define POLL_IGNORE_DEFECTIVECLONE "defective_clone"
+
+
+
+
+### Master file additions
+
+- N/A
+
+
+### Included files that are not contained in this module:
+
+- N/A
+
+
+### Credits:
+
+
diff --git a/modular_skyrat/modules/customization/datums/dna.dm b/modular_skyrat/modules/customization/datums/dna.dm
index 9e6927a48ed..8d24b71352f 100644
--- a/modular_skyrat/modules/customization/datums/dna.dm
+++ b/modular_skyrat/modules/customization/datums/dna.dm
@@ -26,7 +26,7 @@
holder.transform = holder.transform.Translate(0, translate)
current_body_size = features["body_size"]
-/mob/living/carbon/set_species(datum/species/mrace, icon_update = TRUE, var/datum/preferences/pref_load)
+/mob/living/carbon/set_species(datum/species/mrace, icon_update = TRUE, var/datum/preferences/pref_load, var/list/override_features, var/list/override_mutantparts, var/list/override_markings)
if(mrace && has_dna())
var/datum/species/new_race
if(ispath(mrace))
@@ -48,9 +48,9 @@
dna.body_markings = pref_load.body_markings.Copy()
dna.species.body_markings = pref_load.body_markings.Copy()
else
- dna.features = new_race.get_random_features()
- dna.mutant_bodyparts = new_race.get_random_mutant_bodyparts(dna.features)
- dna.body_markings = new_race.get_random_body_markings(dna.features)
+ dna.features = override_features || new_race.get_random_features()
+ dna.mutant_bodyparts = override_mutantparts || new_race.get_random_mutant_bodyparts(dna.features)
+ dna.body_markings = override_markings || new_race.get_random_body_markings(dna.features)
dna.species.body_markings = dna.body_markings.Copy()
bodyparts_to_add = dna.mutant_bodyparts.Copy()
diff --git a/tgstation.dme b/tgstation.dme
index c55eb307390..bbceae3c0b4 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -1109,8 +1109,8 @@
#include "code\game\objects\items\food\egg.dm"
#include "code\game\objects\items\food\frozen.dm"
#include "code\game\objects\items\food\meat.dm"
-#include "code\game\objects\items\food\pie.dm"
#include "code\game\objects\items\food\misc.dm"
+#include "code\game\objects\items\food\pie.dm"
#include "code\game\objects\items\food\pizza.dm"
#include "code\game\objects\items\food\salad.dm"
#include "code\game\objects\items\food\sandwichtoast.dm"
@@ -3349,6 +3349,40 @@
#include "modular_skyrat\modules\blueshield\code\modules\clothing\under\jobs\Plasmaman\security.dm"
#include "modular_skyrat\modules\blueshield\code\modules\jobs\job_types\blueshield.dm"
#include "modular_skyrat\modules\blueshield\code\modules\projectiles\guns\energy\special.dm"
+#include "modular_skyrat\modules\cloning\code\_DEFINES\access.dm"
+#include "modular_skyrat\modules\cloning\code\_DEFINES\DNA.dm"
+#include "modular_skyrat\modules\cloning\code\_DEFINES\machines.dm"
+#include "modular_skyrat\modules\cloning\code\_DEFINES\poll_ignore.dm"
+#include "modular_skyrat\modules\cloning\code\datums\brain_damage\brain_trauma.dm"
+#include "modular_skyrat\modules\cloning\code\datums\brain_damage\phobia.dm"
+#include "modular_skyrat\modules\cloning\code\datums\brain_damage\special.dm"
+#include "modular_skyrat\modules\cloning\code\datums\mood_events\generic_negative_events.dm"
+#include "modular_skyrat\modules\cloning\code\datums\ruins\space.dm"
+#include "modular_skyrat\modules\cloning\code\datums\traits\_quirk.dm"
+#include "modular_skyrat\modules\cloning\code\datums\traits\negative.dm"
+#include "modular_skyrat\modules\cloning\code\game\area\Space_Station_13_areas.dm"
+#include "modular_skyrat\modules\cloning\code\game\machinery\cloning.dm"
+#include "modular_skyrat\modules\cloning\code\game\machinery\exp_cloner.dm"
+#include "modular_skyrat\modules\cloning\code\game\machinery\computer\cloning.dm"
+#include "modular_skyrat\modules\cloning\code\game\objects\effects\countdown.dm"
+#include "modular_skyrat\modules\cloning\code\game\objects\effects\spawners\lootdrop.dm"
+#include "modular_skyrat\modules\cloning\code\game\objects\items\manuals.dm"
+#include "modular_skyrat\modules\cloning\code\game\objects\items\circuitboards\computer_circuitboards.dm"
+#include "modular_skyrat\modules\cloning\code\game\objects\items\circuitboards\machine_circuitboards.dm"
+#include "modular_skyrat\modules\cloning\code\modules\atmospherics\gasmixtures\immutable_mixtures.dm"
+#include "modular_skyrat\modules\cloning\code\modules\jobs\access.dm"
+#include "modular_skyrat\modules\cloning\code\modules\jobs\job_types\chemist.dm"
+#include "modular_skyrat\modules\cloning\code\modules\jobs\job_types\chief_medical_officer.dm"
+#include "modular_skyrat\modules\cloning\code\modules\jobs\job_types\geneticist.dm"
+#include "modular_skyrat\modules\cloning\code\modules\jobs\job_types\medical_doctor.dm"
+#include "modular_skyrat\modules\cloning\code\modules\jobs\job_types\paramedic.dm"
+#include "modular_skyrat\modules\cloning\code\modules\jobs\job_types\virologist.dm"
+#include "modular_skyrat\modules\cloning\code\modules\library\lib_items.dm"
+#include "modular_skyrat\modules\cloning\code\modules\mob\living\brain\brain_item.dm"
+#include "modular_skyrat\modules\cloning\code\modules\research\designs\comp_board_designs.dm"
+#include "modular_skyrat\modules\cloning\code\modules\research\designs\machine_designs.dm"
+#include "modular_skyrat\modules\cloning\code\modules\research\techweb\all_nodes.dm"
+#include "modular_skyrat\modules\cloning\code\modules\ruins\spaceruin_code\cloning_lab.dm"
#include "modular_skyrat\modules\combat_indicator\code\combat_indicator.dm"
#include "modular_skyrat\modules\cryosleep\code\ai.dm"
#include "modular_skyrat\modules\cryosleep\code\config.dm"