diff --git a/code/__DEFINES/logging.dm b/code/__DEFINES/logging.dm
index 2ba50123..c99db65c 100644
--- a/code/__DEFINES/logging.dm
+++ b/code/__DEFINES/logging.dm
@@ -35,6 +35,7 @@
#define LOG_GAME (1 << 12)
#define LOG_ADMIN_PRIVATE (1 << 13)
#define LOG_ASAY (1 << 14)
+#define LOG_CLONING (1 << 15)
//Individual logging panel pages
#define INDIVIDUAL_ATTACK_LOG (LOG_ATTACK)
diff --git a/code/__DEFINES/machines.dm b/code/__DEFINES/machines.dm
index 86245940..c2959d40 100644
--- a/code/__DEFINES/machines.dm
+++ b/code/__DEFINES/machines.dm
@@ -100,6 +100,10 @@
#define NUKE_ON_TIMING 2
#define NUKE_ON_EXPLODING 3
+//cloning defines. These are flags.
+#define CLONING_SUCCESS (1<<0)
+#define CLONING_DELETE_RECORD (1<<1)
+
//these flags are used to tell the DNA modifier if a plant gene cannot be extracted or modified.
#define PLANT_GENE_REMOVABLE (1<<0)
#define PLANT_GENE_EXTRACTABLE (1<<1)
diff --git a/code/__HELPERS/_logging.dm b/code/__HELPERS/_logging.dm
index ac17df57..864057ed 100644
--- a/code/__HELPERS/_logging.dm
+++ b/code/__HELPERS/_logging.dm
@@ -58,6 +58,10 @@
if (CONFIG_GET(flag/log_game))
WRITE_LOG(GLOB.world_game_log, "GAME: [text]")
+/proc/log_cloning(text, mob/initiator)
+ if(CONFIG_GET(flag/log_cloning))
+ WRITE_LOG(GLOB.world_cloning_log, "CLONING: [text]")
+
/proc/log_access(text)
if (CONFIG_GET(flag/log_access))
WRITE_LOG(GLOB.world_game_log, "ACCESS: [text]")
diff --git a/code/_globalvars/logging.dm b/code/_globalvars/logging.dm
index deffd25b..bf4ff55f 100644
--- a/code/_globalvars/logging.dm
+++ b/code/_globalvars/logging.dm
@@ -26,6 +26,8 @@ GLOBAL_VAR(query_debug_log)
GLOBAL_PROTECT(query_debug_log)
GLOBAL_VAR(world_job_debug_log)
GLOBAL_PROTECT(world_job_debug_log)
+GLOBAL_VAR(world_cloning_log)
+GLOBAL_PROTECT(world_cloning_log)
GLOBAL_LIST_EMPTY(bombers)
GLOBAL_PROTECT(bombers)
diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm
index 1f6e4623..a3d17ee0 100644
--- a/code/controllers/configuration/entries/general.dm
+++ b/code/controllers/configuration/entries/general.dm
@@ -39,6 +39,8 @@
/datum/config_entry/flag/log_game // log game events
+/datum/config_entry/flag/log_cloning // log cloning actions.
+
/datum/config_entry/flag/log_vote // log voting
/datum/config_entry/flag/log_whisper // log client whisper
diff --git a/code/datums/dna.dm b/code/datums/dna.dm
index e6f33680..b2e447ba 100644
--- a/code/datums/dna.dm
+++ b/code/datums/dna.dm
@@ -371,7 +371,7 @@
updateappearance(icon_update=0)
if(LAZYLEN(mutation_index))
- dna.mutation_index = mutation_index
+ dna.mutation_index = mutation_index.Copy()
domutcheck()
if(mrace || newfeatures || ui)
diff --git a/code/game/machinery/cloning.dm b/code/game/machinery/cloning.dm
index 5b7eff2b..761eb39e 100644
--- a/code/game/machinery/cloning.dm
+++ b/code/game/machinery/cloning.dm
@@ -74,6 +74,23 @@
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
@@ -129,41 +146,42 @@
return examine(user)
//Start growing a human clone in the pod!
-/obj/machinery/clonepod/proc/growclone(ckey, clonename, ui, mutation_index, mindref, datum/species/mrace, list/features, factions, list/quirks)
+/obj/machinery/clonepod/proc/growclone(clonename, ui, mutation_index, mindref, last_death, blood_type, datum/species/mrace, list/features, factions, list/quirks, datum/bank_account/insurance, list/traumas, empty)
if(panel_open)
return FALSE
if(mess || attempting)
return FALSE
- clonemind = locate(mindref) in SSticker.minds
- if(!istype(clonemind)) //not a mind
- return FALSE
- if(!QDELETED(clonemind.current))
- if(clonemind.current.stat != DEAD) //mind is associated with a non-dead body
+ 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 FALSE
- if(clonemind.current.suiciding) // Mind is associated with a body that is suiciding.
+// if(clonemind.last_death != last_death) //The soul has advanced, the record has not.
+// return FALSE
+ 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
+ icon_state = "pod_g"
+ update_icon()
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(G.suiciding) // The ghost came from a body that is suiciding.
- return FALSE
- if(clonemind.damnation_type) //Can't clone the damned.
- INVOKE_ASYNC(src, .proc/horrifyingsound)
- mess = TRUE
- update_icon()
- return FALSE
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.real_name, null, mrace, features)
+ H.hardset_dna(ui, mutation_index, H.real_name, blood_type, mrace, features)
if(prob(50 - efficiency*10)) //Chance to give a bad mutation.
H.easy_randmut(NEGATIVE+MINOR_NEGATIVE) //100% bad mutation. Can be cured with mutadone.
@@ -184,15 +202,16 @@
ADD_TRAIT(H, TRAIT_NOCRITDAMAGE, "cloning")
H.Unconscious(80)
- clonemind.transfer_to(H)
+ 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_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(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
@@ -381,6 +400,7 @@
unattached_flesh.Cut()
occupant = null
+ clonemind = null
/obj/machinery/clonepod/proc/malfunction()
var/mob/living/mob_occupant = occupant
@@ -391,7 +411,7 @@
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(mob_occupant.mind != clonemind)
+ 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)
diff --git a/code/game/machinery/computer/cloning.dm b/code/game/machinery/computer/cloning.dm
index eceb6042..2dfc5603 100644
--- a/code/game/machinery/computer/cloning.dm
+++ b/code/game/machinery/computer/cloning.dm
@@ -39,7 +39,7 @@
if(pods)
for(var/P in pods)
var/obj/machinery/clonepod/pod = P
- if(pod.occupant && pod.clonemind == mind)
+ if(pod.occupant && mind && pod.clonemind == mind)
return null
if(pod.is_operational() && !(pod.occupant || pod.mess))
return pod
@@ -60,6 +60,9 @@
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["factions"], R.fields["quirks"], R.fields["bank_account"], R.fields["traumas"], empty)
+
/obj/machinery/computer/cloning/process()
if(!(scanner && LAZYLEN(pods) && autoprocess))
return
@@ -76,8 +79,11 @@
if(pod.occupant)
continue //how though?
- if(pod.growclone(R.fields["ckey"], R.fields["name"], R.fields["UI"], R.fields["SE"], R.fields["mind"], R.fields["mrace"], R.fields["features"], R.fields["factions"], R.fields["quirks"]))
+ 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)
@@ -201,6 +207,7 @@
if(scanner_occupant)
dat += "Start Scan"
+ dat += "Body-Only Scan"
dat += "
[src.scanner.locked ? "Unlock Scanner" : "Lock Scanner"]"
else
dat += "Start Scan"
@@ -228,8 +235,11 @@
if (!src.active_record)
dat += "Record not found."
else
- dat += "